window.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. #if defined(Hiro_Window)
  2. namespace hiro {
  3. static auto Window_close(GtkWidget* widget, GdkEvent* event, pWindow* p) -> int {
  4. if(p->state().onClose) {
  5. p->self().doClose();
  6. } else {
  7. p->self().setVisible(false);
  8. }
  9. if(p->self().modal() && !p->self().visible()) p->self().setModal(false);
  10. return true;
  11. }
  12. //GTK3 draw: called into by GTK2 expose-event
  13. static auto Window_draw(GtkWidget* widget, cairo_t* context, pWindow* p) -> int {
  14. if(auto color = p->state().backgroundColor) {
  15. double red = (double)color.red() / 255.0;
  16. double green = (double)color.green() / 255.0;
  17. double blue = (double)color.blue() / 255.0;
  18. double alpha = (double)color.alpha() / 255.0;
  19. if(gdk_screen_is_composited(gdk_screen_get_default())
  20. && gdk_screen_get_rgba_visual(gdk_screen_get_default())
  21. ) {
  22. cairo_set_source_rgba(context, red, green, blue, alpha);
  23. } else {
  24. cairo_set_source_rgb(context, red, green, blue);
  25. }
  26. cairo_set_operator(context, CAIRO_OPERATOR_SOURCE);
  27. cairo_paint(context);
  28. } else {
  29. #if HIRO_GTK==3
  30. auto style = gtk_widget_get_style_context(widget);
  31. GtkAllocation allocation;
  32. gtk_widget_get_allocation(widget, &allocation);
  33. gtk_render_background(style, context, 0, 0, allocation.width, allocation.height);
  34. #endif
  35. }
  36. return false;
  37. }
  38. //GTK2 expose-event
  39. static auto Window_expose(GtkWidget* widget, GdkEvent* event, pWindow* p) -> int {
  40. cairo_t* context = gdk_cairo_create(gtk_widget_get_window(widget));
  41. Window_draw(widget, context, p);
  42. cairo_destroy(context);
  43. return false;
  44. }
  45. static auto Window_configure(GtkWidget* widget, GdkEvent* event, pWindow* p) -> int {
  46. p->_synchronizeMargin();
  47. return false;
  48. }
  49. static auto Window_drop(GtkWidget* widget, GdkDragContext* context, int x, int y,
  50. GtkSelectionData* data, uint type, uint timestamp, pWindow* p) -> void {
  51. if(!p->state().droppable) return;
  52. auto paths = DropPaths(data);
  53. if(!paths) return;
  54. p->self().doDrop(paths);
  55. }
  56. static auto Window_getPreferredWidth(GtkWidget* widget, int* minimalWidth, int* naturalWidth) -> void {
  57. if(auto p = (pWindow*)g_object_get_data(G_OBJECT(widget), "hiro::window")) {
  58. *minimalWidth = 1;
  59. *naturalWidth = p->state().geometry.width();
  60. }
  61. }
  62. static auto Window_getPreferredHeight(GtkWidget* widget, int* minimalHeight, int* naturalHeight) -> void {
  63. if(auto p = (pWindow*)g_object_get_data(G_OBJECT(widget), "hiro::window")) {
  64. *minimalHeight = 1;
  65. *naturalHeight = p->state().geometry.height();
  66. }
  67. }
  68. static auto Window_keyPress(GtkWidget* widget, GdkEventKey* event, pWindow* p) -> int {
  69. if(auto key = pKeyboard::_translate(event->keyval)) {
  70. p->self().doKeyPress(key);
  71. }
  72. if(p->state().dismissable && event->keyval == GDK_KEY_Escape) {
  73. if(p->state().onClose) {
  74. p->self().doClose();
  75. } else {
  76. p->self().setVisible(false);
  77. }
  78. if(p->state().modal && !p->pObject::state().visible) p->self().setModal(false);
  79. }
  80. return false;
  81. }
  82. static auto Window_keyRelease(GtkWidget* widget, GdkEventKey* event, pWindow* p) -> int {
  83. if(auto key = pKeyboard::_translate(event->keyval)) {
  84. p->self().doKeyRelease(key);
  85. }
  86. return false;
  87. }
  88. static auto Window_realize(GtkWidget* widget, pWindow* p) -> void {
  89. }
  90. static auto Window_sizeAllocate(GtkWidget* widget, GtkAllocation* allocation, pWindow* p) -> void {
  91. p->_synchronizeState();
  92. p->_synchronizeGeometry();
  93. p->_synchronizeMargin();
  94. return;
  95. }
  96. static auto Window_sizeRequest(GtkWidget* widget, GtkRequisition* requisition, pWindow* p) -> void {
  97. requisition->width = p->state().geometry.width();
  98. requisition->height = p->state().geometry.height();
  99. }
  100. static auto Window_stateEvent(GtkWidget* widget, GdkEvent* event, pWindow* p) -> void {
  101. p->_synchronizeState();
  102. if(event->type == GDK_WINDOW_STATE) {
  103. auto windowStateEvent = (GdkEventWindowState*)event;
  104. if(windowStateEvent->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) {
  105. p->state().maximized = windowStateEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED;
  106. }
  107. if(windowStateEvent->changed_mask & GDK_WINDOW_STATE_ICONIFIED) {
  108. p->state().minimized = windowStateEvent->new_window_state & GDK_WINDOW_STATE_ICONIFIED;
  109. }
  110. }
  111. }
  112. static auto Window_unrealize(GtkWidget* widget, pWindow* p) -> void {
  113. }
  114. auto pWindow::construct() -> void {
  115. widget = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  116. gtk_window_set_resizable(GTK_WINDOW(widget), true);
  117. //if program was given a name, try and set the window taskbar icon from one of the pixmaps folders
  118. if(!Application::state().name);
  119. else if(_setIcon({Path::user(), ".local/share/icons/"}));
  120. else if(_setIcon("/usr/local/share/pixmaps/"));
  121. else if(_setIcon("/usr/share/pixmaps/"));
  122. auto visual = gdk_screen_get_rgba_visual(gdk_screen_get_default());
  123. if(!visual) visual = gdk_screen_get_system_visual(gdk_screen_get_default());
  124. if(visual) gtk_widget_set_visual(widget, visual);
  125. gtk_widget_set_app_paintable(widget, true);
  126. gtk_widget_add_events(widget, GDK_CONFIGURE);
  127. #if HIRO_GTK==2
  128. menuContainer = gtk_vbox_new(false, 0);
  129. #elif HIRO_GTK==3
  130. menuContainer = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
  131. #endif
  132. gtk_container_add(GTK_CONTAINER(widget), menuContainer);
  133. gtk_widget_show(menuContainer);
  134. gtkMenu = gtk_menu_bar_new();
  135. gtk_box_pack_start(GTK_BOX(menuContainer), gtkMenu, false, false, 0);
  136. formContainer = gtk_fixed_new();
  137. gtk_box_pack_start(GTK_BOX(menuContainer), formContainer, true, true, 0);
  138. gtk_widget_show(formContainer);
  139. statusContainer = gtk_event_box_new();
  140. gtkStatus = gtk_statusbar_new();
  141. #if HIRO_GTK==2
  142. gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(gtkStatus), true);
  143. #elif HIRO_GTK==3
  144. gtk_window_set_has_resize_grip(GTK_WINDOW(widget), true);
  145. #endif
  146. gtk_container_add(GTK_CONTAINER(statusContainer), gtkStatus);
  147. gtk_box_pack_start(GTK_BOX(menuContainer), statusContainer, false, false, 0);
  148. gtk_widget_show(statusContainer);
  149. setBackgroundColor(state().backgroundColor);
  150. setDroppable(state().droppable);
  151. setGeometry(state().geometry);
  152. setResizable(state().resizable);
  153. setMaximized(state().maximized);
  154. setMinimized(state().minimized);
  155. setTitle(state().title);
  156. g_signal_connect(G_OBJECT(widget), "delete-event", G_CALLBACK(Window_close), (gpointer)this);
  157. #if HIRO_GTK==2
  158. g_signal_connect(G_OBJECT(widget), "expose-event", G_CALLBACK(Window_expose), (gpointer)this);
  159. #elif HIRO_GTK==3
  160. g_signal_connect(G_OBJECT(widget), "draw", G_CALLBACK(Window_draw), (gpointer)this);
  161. #endif
  162. g_signal_connect(G_OBJECT(widget), "configure-event", G_CALLBACK(Window_configure), (gpointer)this);
  163. g_signal_connect(G_OBJECT(widget), "drag-data-received", G_CALLBACK(Window_drop), (gpointer)this);
  164. g_signal_connect(G_OBJECT(widget), "key-press-event", G_CALLBACK(Window_keyPress), (gpointer)this);
  165. g_signal_connect(G_OBJECT(widget), "key-release-event", G_CALLBACK(Window_keyRelease), (gpointer)this);
  166. g_signal_connect(G_OBJECT(widget), "realize", G_CALLBACK(Window_realize), (gpointer)this);
  167. g_signal_connect(G_OBJECT(formContainer), "size-allocate", G_CALLBACK(Window_sizeAllocate), (gpointer)this);
  168. #if HIRO_GTK==2
  169. g_signal_connect(G_OBJECT(formContainer), "size-request", G_CALLBACK(Window_sizeRequest), (gpointer)this);
  170. #elif HIRO_GTK==3
  171. auto widgetClass = GTK_WIDGET_GET_CLASS(formContainer);
  172. widgetClass->get_preferred_width = Window_getPreferredWidth;
  173. widgetClass->get_preferred_height = Window_getPreferredHeight;
  174. #endif
  175. g_signal_connect(G_OBJECT(widget), "unrealize", G_CALLBACK(Window_unrealize), (gpointer)this);
  176. g_signal_connect(G_OBJECT(widget), "window-state-event", G_CALLBACK(Window_stateEvent), (gpointer)this);
  177. g_object_set_data(G_OBJECT(widget), "hiro::window", (gpointer)this);
  178. g_object_set_data(G_OBJECT(formContainer), "hiro::window", (gpointer)this);
  179. pApplication::state().windows.append(this);
  180. }
  181. auto pWindow::destruct() -> void {
  182. for(uint offset : range(pApplication::state().windows.size())) {
  183. if(pApplication::state().windows[offset] == this) {
  184. pApplication::state().windows.remove(offset);
  185. break;
  186. }
  187. }
  188. gtk_widget_destroy(widget);
  189. }
  190. auto pWindow::append(sMenuBar menuBar) -> void {
  191. _setMenuEnabled(menuBar->enabled(true));
  192. _setMenuFont(menuBar->font(true));
  193. _setMenuVisible(menuBar->visible(true));
  194. }
  195. auto pWindow::append(sSizable sizable) -> void {
  196. }
  197. auto pWindow::append(sStatusBar statusBar) -> void {
  198. _setStatusEnabled(statusBar->enabled(true));
  199. _setStatusFont(statusBar->font(true));
  200. _setStatusText(statusBar->text());
  201. _setStatusVisible(statusBar->visible(true));
  202. }
  203. auto pWindow::focused() const -> bool {
  204. return gtk_window_is_active(GTK_WINDOW(widget));
  205. }
  206. auto pWindow::frameMargin() const -> Geometry {
  207. if(state().fullScreen) return {
  208. 0, _menuHeight(),
  209. 0, _menuHeight() + _statusHeight()
  210. };
  211. return {
  212. settings.geometry.frameX,
  213. settings.geometry.frameY + _menuHeight(),
  214. settings.geometry.frameWidth,
  215. settings.geometry.frameHeight + _menuHeight() + _statusHeight()
  216. };
  217. }
  218. auto pWindow::handle() const -> uintptr_t {
  219. #if defined(DISPLAY_WINDOWS)
  220. return (uintptr)GDK_WINDOW_HWND(gtk_widget_get_window(widget));
  221. #endif
  222. #if defined(DISPLAY_XORG)
  223. return GDK_WINDOW_XID(gtk_widget_get_window(widget));
  224. #endif
  225. return (uintptr)nullptr;
  226. }
  227. auto pWindow::monitor() const -> uint {
  228. if(!gtk_widget_get_realized(widget)) return 0;
  229. auto window = gtk_widget_get_window(widget);
  230. return gdk_screen_get_monitor_at_window(gdk_screen_get_default(), window);
  231. }
  232. auto pWindow::remove(sMenuBar menuBar) -> void {
  233. _setMenuVisible(false);
  234. }
  235. auto pWindow::remove(sSizable sizable) -> void {
  236. }
  237. auto pWindow::remove(sStatusBar statusBar) -> void {
  238. _setStatusVisible(false);
  239. }
  240. auto pWindow::setBackgroundColor(Color color) -> void {
  241. GdkColor gdkColor = CreateColor(color);
  242. gtk_widget_modify_bg(widget, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
  243. }
  244. auto pWindow::setDismissable(bool dismissable) -> void {
  245. }
  246. auto pWindow::setDroppable(bool droppable) -> void {
  247. gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, nullptr, 0, GDK_ACTION_COPY);
  248. if(droppable) gtk_drag_dest_add_uri_targets(widget);
  249. }
  250. auto pWindow::setEnabled(bool enabled) -> void {
  251. if(auto& menuBar = state().menuBar) {
  252. if(auto self = menuBar->self()) self->setEnabled(menuBar->enabled(true));
  253. }
  254. if(auto& statusBar = state().statusBar) {
  255. if(auto self = statusBar->self()) self->setEnabled(statusBar->enabled(true));
  256. }
  257. }
  258. auto pWindow::setFocused() -> void {
  259. gtk_window_present(GTK_WINDOW(widget));
  260. }
  261. auto pWindow::setFullScreen(bool fullScreen) -> void {
  262. if(fullScreen) {
  263. gtk_window_fullscreen(GTK_WINDOW(widget));
  264. } else {
  265. gtk_window_unfullscreen(GTK_WINDOW(widget));
  266. }
  267. auto time = chrono::millisecond();
  268. while(chrono::millisecond() - time < 20) {
  269. Application::processEvents();
  270. }
  271. }
  272. auto pWindow::setGeometry(Geometry geometry) -> void {
  273. auto margin = frameMargin();
  274. setMaximumSize(state().maximumSize);
  275. setMinimumSize(state().minimumSize);
  276. auto time1 = chrono::millisecond();
  277. while(chrono::millisecond() - time1 < 20) {
  278. Application::processEvents();
  279. }
  280. gtk_window_move(GTK_WINDOW(widget), geometry.x() - margin.x(), geometry.y() - margin.y());
  281. gtk_window_resize(GTK_WINDOW(widget), geometry.width(), geometry.height() + _menuHeight() + _statusHeight());
  282. auto time2 = chrono::millisecond();
  283. while(chrono::millisecond() - time2 < 20) {
  284. Application::processEvents();
  285. }
  286. }
  287. auto pWindow::setMaximized(bool maximized) -> void {
  288. auto lock = acquire();
  289. if(maximized) {
  290. gtk_window_maximize(GTK_WINDOW(widget));
  291. } else {
  292. gtk_window_unmaximize(GTK_WINDOW(widget));
  293. }
  294. }
  295. auto pWindow::setMaximumSize(Size size) -> void {
  296. if(!state().resizable) size = state().geometry.size();
  297. //TODO: this doesn't have any effect in GTK2 or GTK3
  298. GdkGeometry geometry;
  299. if(size.height()) size.setHeight(size.height() + _menuHeight() + _statusHeight());
  300. geometry.max_width = !state().resizable ? state().geometry.width() : size.width() ? size.width() : 32767;
  301. geometry.max_height = !state().resizable ? state().geometry.height() : size.height() ? size.height() : 32767;
  302. gtk_window_set_geometry_hints(GTK_WINDOW(widget), nullptr, &geometry, GDK_HINT_MAX_SIZE);
  303. }
  304. auto pWindow::setMinimized(bool minimized) -> void {
  305. auto lock = acquire();
  306. if(minimized) {
  307. gtk_window_iconify(GTK_WINDOW(widget));
  308. } else {
  309. gtk_window_deiconify(GTK_WINDOW(widget));
  310. }
  311. }
  312. auto pWindow::setMinimumSize(Size size) -> void {
  313. if(!state().resizable) size = state().geometry.size();
  314. //for GTK3
  315. gtk_widget_set_size_request(formContainer, size.width(), size.height());
  316. //for GTK2
  317. GdkGeometry geometry;
  318. if(size.height()) size.setHeight(size.height() + _menuHeight() + _statusHeight());
  319. geometry.min_width = !state().resizable ? state().geometry.width() : size.width() ? size.width() : 1;
  320. geometry.min_height = !state().resizable ? state().geometry.height() : size.height() ? size.height() : 1;
  321. gtk_window_set_geometry_hints(GTK_WINDOW(widget), nullptr, &geometry, GDK_HINT_MIN_SIZE); //for GTK2
  322. }
  323. auto pWindow::setModal(bool modal) -> void {
  324. if(modal) {
  325. gtk_window_set_modal(GTK_WINDOW(widget), true);
  326. while(!Application::state().quit && state().modal) {
  327. if(Application::state().onMain) {
  328. Application::doMain();
  329. } else {
  330. usleep(20 * 1000);
  331. }
  332. Application::processEvents();
  333. }
  334. gtk_window_set_modal(GTK_WINDOW(widget), false);
  335. }
  336. }
  337. auto pWindow::setResizable(bool resizable) -> void {
  338. gtk_window_set_resizable(GTK_WINDOW(widget), resizable);
  339. #if HIRO_GTK==2
  340. gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(gtkStatus), resizable);
  341. #elif HIRO_GTK==3
  342. bool statusBarVisible = false;
  343. if(auto statusBar = state().statusBar) statusBarVisible = statusBar->visible();
  344. gtk_window_set_has_resize_grip(GTK_WINDOW(widget), resizable && statusBarVisible);
  345. #endif
  346. setMaximumSize(state().maximumSize);
  347. setMinimumSize(state().minimumSize);
  348. }
  349. auto pWindow::setTitle(const string& title) -> void {
  350. gtk_window_set_title(GTK_WINDOW(widget), title ? title : " ");
  351. }
  352. auto pWindow::setVisible(bool visible) -> void {
  353. gtk_widget_set_visible(widget, visible);
  354. _synchronizeGeometry();
  355. _synchronizeMargin();
  356. }
  357. auto pWindow::_append(mWidget& widget) -> void {
  358. if(auto pWidget = widget.self()) {
  359. if(auto parent = widget.parentWidget(true)) {
  360. if(auto instance = parent->self()) {
  361. pWidget->gtkParent = instance->container(widget);
  362. }
  363. } else {
  364. pWidget->gtkParent = formContainer;
  365. }
  366. if(pWidget->gtkParent) {
  367. gtk_fixed_put(GTK_FIXED(pWidget->gtkParent), pWidget->gtkWidget, 0, 0);
  368. }
  369. }
  370. }
  371. auto pWindow::_append(mMenu& menu) -> void {
  372. if(auto pMenu = menu.self()) {
  373. gtk_menu_shell_append(GTK_MENU_SHELL(gtkMenu), pMenu->widget);
  374. }
  375. }
  376. auto pWindow::_menuHeight() const -> int {
  377. if(auto& menuBar = state().menuBar) {
  378. if(menuBar->visible()) {
  379. return settings.geometry.menuHeight + _menuTextHeight();
  380. }
  381. }
  382. return 0;
  383. }
  384. auto pWindow::_menuTextHeight() const -> int {
  385. int height = 0;
  386. if(auto& menuBar = state().menuBar) {
  387. for(auto& menu : menuBar->state.menus) {
  388. height = max(height, menu->font(true).size(menu->text()).height());
  389. }
  390. }
  391. return height;
  392. }
  393. auto pWindow::_setIcon(const string& pathname) -> bool {
  394. string filename;
  395. filename = {pathname, Application::state().name, ".svg"};
  396. if(file::exists(filename)) {
  397. gtk_window_set_icon_from_file(GTK_WINDOW(widget), filename, nullptr);
  398. return true;
  399. }
  400. filename = {pathname, Application::state().name, ".png"};
  401. if(file::exists(filename)) {
  402. //maximum image size GTK+ supports is 256x256; scale image down if necessary to prevent error
  403. image icon(filename);
  404. icon.scale(min(256u, icon.width()), min(256u, icon.height()), true);
  405. auto pixbuf = CreatePixbuf(icon);
  406. gtk_window_set_icon(GTK_WINDOW(widget), pixbuf);
  407. g_object_unref(G_OBJECT(pixbuf));
  408. return true;
  409. }
  410. return false;
  411. }
  412. auto pWindow::_setMenuEnabled(bool enabled) -> void {
  413. gtk_widget_set_sensitive(gtkMenu, enabled);
  414. }
  415. auto pWindow::_setMenuFont(const Font& font) -> void {
  416. pFont::setFont(gtkMenu, font);
  417. }
  418. auto pWindow::_setMenuVisible(bool visible) -> void {
  419. gtk_widget_set_visible(gtkMenu, visible);
  420. }
  421. auto pWindow::_setStatusEnabled(bool enabled) -> void {
  422. gtk_widget_set_sensitive(gtkStatus, enabled);
  423. }
  424. auto pWindow::_setStatusFont(const Font& font) -> void {
  425. pFont::setFont(gtkStatus, font);
  426. }
  427. auto pWindow::_setStatusText(const string& text) -> void {
  428. gtk_statusbar_pop(GTK_STATUSBAR(gtkStatus), 1);
  429. gtk_statusbar_push(GTK_STATUSBAR(gtkStatus), 1, text);
  430. }
  431. auto pWindow::_setStatusVisible(bool visible) -> void {
  432. gtk_widget_set_visible(gtkStatus, visible);
  433. setResizable(self().resizable());
  434. }
  435. auto pWindow::_statusHeight() const -> int {
  436. if(auto& statusBar = state().statusBar) {
  437. if(statusBar->visible()) {
  438. return settings.geometry.statusHeight + _statusTextHeight();
  439. }
  440. }
  441. return 0;
  442. }
  443. auto pWindow::_statusTextHeight() const -> int {
  444. int height = 0;
  445. if(auto& statusBar = state().statusBar) {
  446. height = statusBar->font(true).size(statusBar->text()).height();
  447. }
  448. return height;
  449. }
  450. //GTK is absolutely hopeless with window sizing
  451. //it will send size-allocate events during resizing of widgets during size-allocate events
  452. //instead of fighting with configure-event and size-allocate, just poll the state instead
  453. auto pWindow::_synchronizeGeometry() -> void {
  454. if(locked()) return;
  455. auto lock = acquire();
  456. if(!gtk_widget_get_realized(widget)) return;
  457. if(!gtk_widget_get_visible(widget)) return;
  458. //get_allocation(formContainer) returns the same values as get_allocation(widget) ...
  459. //as a result, we have to compensate for the window margin ourselves here.
  460. GtkAllocation allocation;
  461. gtk_widget_get_allocation(widget, &allocation);
  462. allocation.height -= _menuHeight();
  463. allocation.height -= _statusHeight();
  464. if(allocation.width != lastSize.width || allocation.height != lastSize.height) {
  465. auto size = self().geometry().size();
  466. state().geometry.setSize({allocation.width, allocation.height});
  467. if(auto& sizable = state().sizable) {
  468. sizable->setGeometry(self().geometry().setPosition());
  469. }
  470. self().doSize();
  471. //for GTK3: the window will not update after changing widget sizes until sending size-allocate
  472. //size-allocate will also call _synchronizeMargin() which is also important for window sizing
  473. //GtkAllocation is a typedef of GdkRectangle
  474. GtkAllocation rectangle;
  475. gtk_widget_get_allocation(widget, &rectangle);
  476. g_signal_emit_by_name(G_OBJECT(widget), "size-allocate", &rectangle, (gpointer)this, nullptr);
  477. }
  478. lastSize = allocation;
  479. auto gdkWindow = gtk_widget_get_window(widget);
  480. gdk_window_get_origin(gdkWindow, &allocation.x, &allocation.y);
  481. allocation.y += _menuHeight();
  482. if(allocation.x != lastMove.x || allocation.y != lastMove.y) {
  483. state().geometry.setPosition({allocation.x, allocation.y});
  484. self().doMove();
  485. }
  486. lastMove = allocation;
  487. }
  488. auto pWindow::_synchronizeMargin() -> void {
  489. if(locked()) return;
  490. auto lock = acquire();
  491. if(!gtk_widget_get_realized(widget)) return;
  492. if(!gtk_widget_get_visible(widget)) return;
  493. if(state().fullScreen || state().maximized || state().minimized) return;
  494. auto window = gtk_widget_get_window(widget);
  495. GdkRectangle border, client;
  496. gdk_window_get_frame_extents(window, &border);
  497. gdk_window_get_origin(window, &client.x, &client.y);
  498. #if HIRO_GTK==2
  499. gdk_window_get_geometry(window, nullptr, nullptr, &client.width, &client.height, nullptr);
  500. #elif HIRO_GTK==3
  501. gdk_window_get_geometry(window, nullptr, nullptr, &client.width, &client.height);
  502. #endif
  503. settings.geometry.frameX = client.x - border.x;
  504. settings.geometry.frameY = client.y - border.y;
  505. settings.geometry.frameWidth = border.width - client.width;
  506. settings.geometry.frameHeight = border.height - client.height;
  507. if(gtk_widget_get_visible(gtkMenu)) {
  508. GtkAllocation allocation;
  509. auto time = chrono::millisecond();
  510. while(chrono::millisecond() - time < 20) {
  511. gtk_widget_get_allocation(gtkMenu, &allocation);
  512. if(allocation.height > 1) {
  513. settings.geometry.menuHeight = allocation.height - _menuTextHeight();
  514. break;
  515. }
  516. }
  517. }
  518. if(gtk_widget_get_visible(gtkStatus)) {
  519. GtkAllocation allocation;
  520. auto time = chrono::millisecond();
  521. while(chrono::millisecond() - time < 20) {
  522. gtk_widget_get_allocation(gtkStatus, &allocation);
  523. if(allocation.height > 1) {
  524. settings.geometry.statusHeight = allocation.height - _statusTextHeight();
  525. break;
  526. }
  527. }
  528. }
  529. }
  530. //GTK doesn't add gtk_window_is_maximized() until 3.12;
  531. //and doesn't appear to have a companion gtk_window_is_(hidden,iconic,minimized);
  532. //so we have to do this the hard way
  533. auto pWindow::_synchronizeState() -> void {
  534. if(locked()) return;
  535. auto lock = acquire();
  536. if(!gtk_widget_get_realized(widget)) return;
  537. #if defined(DISPLAY_WINDOWS)
  538. auto window = (HWND)GDK_WINDOW_HWND(gtk_widget_get_window(widget));
  539. bool maximized = IsZoomed(window);
  540. bool minimized = IsIconic(window);
  541. bool doSize = false;
  542. if(state().minimized != minimized) doSize = true;
  543. state().maximized = maximized;
  544. state().minimized = minimized;
  545. if(doSize) self().doSize();
  546. #endif
  547. #if defined(DISPLAY_XORG)
  548. auto display = XOpenDisplay(nullptr);
  549. int screen = DefaultScreen(display);
  550. auto window = GDK_WINDOW_XID(gtk_widget_get_window(widget));
  551. XlibAtom wmState = XInternAtom(display, "_NET_WM_STATE", XlibFalse);
  552. XlibAtom atom;
  553. int format;
  554. unsigned long items, after;
  555. unsigned char* data = nullptr;
  556. int result = XGetWindowProperty(
  557. display, window, wmState, 0, LONG_MAX, XlibFalse, AnyPropertyType, &atom, &format, &items, &after, &data
  558. );
  559. auto atoms = (unsigned long*)data;
  560. if(result == Success) {
  561. bool maximizedHorizontal = false;
  562. bool maximizedVertical = false;
  563. bool minimized = false;
  564. for(auto index : range(items)) {
  565. auto memory = XGetAtomName(display, atoms[index]);
  566. auto name = string{memory};
  567. if(name == "_NET_WM_STATE_MAXIMIZED_HORZ") maximizedHorizontal = true;
  568. if(name == "_NET_WM_STATE_MAXIMIZED_VERT") maximizedVertical = true;
  569. if(name == "_NET_WM_STATE_HIDDEN") minimized = true;
  570. XFree(memory);
  571. }
  572. bool doSize = false;
  573. //maximize sends size-allocate, which triggers doSize()
  574. if(state().minimized != minimized) doSize = true;
  575. //windows do not act bizarrely when maximized in only one direction
  576. //so for this reason, consider a window maximized only if it's in both directions
  577. state().maximized = maximizedHorizontal && maximizedVertical;
  578. state().minimized = minimized;
  579. if(doSize) self().doSize();
  580. }
  581. XCloseDisplay(display);
  582. #endif
  583. }
  584. }
  585. #endif