1 /*****************************************************************************
2  *   Copyright 2003 - 2010 Craig Drummond <craig.p.drummond@gmail.com>       *
3  *   Copyright 2013 - 2015 Yichao Yu <yyc1992@gmail.com>                     *
4  *                                                                           *
5  *   This program is free software; you can redistribute it and/or modify    *
6  *   it under the terms of the GNU Lesser General Public License as          *
7  *   published by the Free Software Foundation; either version 2.1 of the    *
8  *   License, or (at your option) version 3, or any later version accepted   *
9  *   by the membership of KDE e.V. (or its successor approved by the         *
10  *   membership of KDE e.V.), which shall act as a proxy defined in          *
11  *   Section 6 of version 3 of the license.                                  *
12  *                                                                           *
13  *   This program is distributed in the hope that it will be useful,         *
14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       *
16  *   Lesser General Public License for more details.                         *
17  *                                                                           *
18  *   You should have received a copy of the GNU Lesser General Public        *
19  *   License along with this library. If not,                                *
20  *   see <http://www.gnu.org/licenses/>.                                     *
21  *****************************************************************************/
22 
23 #include "menu.h"
24 
25 #include <qtcurve-utils/gtkprops.h>
26 #include <qtcurve-utils/x11qtc.h>
27 #include <qtcurve-cairo/utils.h>
28 
29 #include <gdk/gdkx.h>
30 #include <common/common.h>
31 
32 namespace QtCurve {
33 namespace Menu {
34 
35 #define EXTEND_MENUBAR_ITEM_HACK
36 
37 #ifdef EXTEND_MENUBAR_ITEM_HACK
38 static bool
isSelectable(GtkWidget * menu)39 isSelectable(GtkWidget *menu)
40 {
41     return !((!gtk_bin_get_child(GTK_BIN(menu)) &&
42               G_OBJECT_TYPE(menu) == GTK_TYPE_MENU_ITEM) ||
43              GTK_IS_SEPARATOR_MENU_ITEM(menu) ||
44              !gtk_widget_is_sensitive(menu) ||
45              !gtk_widget_get_visible(menu));
46 }
47 
48 static gboolean
shellButtonPress(GtkWidget * widget,GdkEventButton * event,void *)49 shellButtonPress(GtkWidget *widget, GdkEventButton *event, void*)
50 {
51     if (GTK_IS_MENU_BAR(widget)) {
52         // QtCurve's menubars have a 2 pixel border ->
53         // but want the left/top to be 'active'...
54         int nx, ny;
55         gdk_window_get_origin(gtk_widget_get_window(widget), &nx, &ny);
56         if ((event->x_root - nx) <= 2.0 || (event->y_root - ny) <= 2.0) {
57             if ((event->x_root - nx) <= 2.0) {
58                 event->x_root += 2.0;
59             }
60             if ((event->y_root - ny) <= 2.0) {
61                 event->y_root += 2.0;
62             }
63 
64             GtkMenuShell *menuShell = GTK_MENU_SHELL(widget);
65             GList *children =
66                 gtk_container_get_children(GTK_CONTAINER(menuShell));
67             bool rv = false;
68             for (GList *child = children;child;child = child->next) {
69                 GtkWidget *item = (GtkWidget*)child->data;
70                 QtcRect alloc = Widget::getAllocation(item);
71                 int cx = alloc.x + nx;
72                 int cy = alloc.y + ny;
73                 int cw = alloc.width;
74                 int ch = alloc.height;
75                 if (cx <= event->x_root && cy <= event->y_root &&
76                     (cx + cw) > event->x_root && (cy + ch) > event->y_root) {
77                     if (isSelectable(item)) {
78                         if (event->type == GDK_BUTTON_PRESS) {
79                             if (item != menuShell->active_menu_item) {
80                                 menuShell->active = false;
81                                 gtk_menu_shell_select_item(menuShell, item);
82                                 menuShell->active = true;
83                             } else {
84                                 menuShell->active = true;
85                                 gtk_menu_shell_deselect(menuShell);
86                                 menuShell->active = false;
87                             }
88                         }
89                         rv = true;
90                     }
91                     break;
92                 }
93             }
94             if (children) {
95                 g_list_free(children);
96             }
97             return rv;
98         }
99     }
100     return false;
101 }
102 #endif
103 
104 static void
shellCleanup(GtkWidget * widget)105 shellCleanup(GtkWidget *widget)
106 {
107     if (GTK_IS_MENU_BAR(widget)) {
108         GtkWidgetProps props(widget);
109         props->menuShellMotion.disconn();
110         props->menuShellLeave.disconn();
111         props->menuShellDestroy.disconn();
112         props->menuShellStyleSet.disconn();
113 #ifdef EXTEND_MENUBAR_ITEM_HACK
114         props->menuShellButtonPress.disconn();
115         props->menuShellButtonRelease.disconn();
116 #endif
117         props->menuShellHacked = true;
118     }
119 }
120 
121 static gboolean
shellStyleSet(GtkWidget * widget,GtkStyle *,void *)122 shellStyleSet(GtkWidget *widget, GtkStyle*, void*)
123 {
124     shellCleanup(widget);
125     return false;
126 }
127 
128 static gboolean
shellDestroy(GtkWidget * widget,GdkEvent *,void *)129 shellDestroy(GtkWidget *widget, GdkEvent*, void*)
130 {
131     shellCleanup(widget);
132     return false;
133 }
134 
135 static gboolean
shellMotion(GtkWidget * widget,GdkEventMotion *,void *)136 shellMotion(GtkWidget *widget, GdkEventMotion*, void*)
137 {
138     if (GTK_IS_MENU_SHELL(widget)) {
139         int pointer_x, pointer_y;
140         GdkModifierType pointer_mask;
141 
142         gdk_window_get_pointer(gtk_widget_get_window(widget), &pointer_x,
143                                &pointer_y, &pointer_mask);
144 
145         if (GTK_IS_CONTAINER(widget)) {
146             GList *children = gtk_container_get_children(GTK_CONTAINER(widget));
147             for (GList *child = children;child;child = g_list_next(child)) {
148                 if ((child->data) && GTK_IS_WIDGET(child->data) &&
149                     (gtk_widget_get_state(GTK_WIDGET(child->data)) !=
150                      GTK_STATE_INSENSITIVE)) {
151                     QtcRect alloc =
152                         Widget::getAllocation(GTK_WIDGET(child->data));
153 
154                     if ((pointer_x >= alloc.x) && (pointer_y >= alloc.y) &&
155                         (pointer_x < (alloc.x + alloc.width)) &&
156                         (pointer_y < (alloc.y + alloc.height))) {
157                         gtk_widget_set_state(GTK_WIDGET(child->data),
158                                              GTK_STATE_PRELIGHT);
159                     } else {
160                         gtk_widget_set_state(GTK_WIDGET(child->data),
161                                              GTK_STATE_NORMAL);
162                     }
163                 }
164             }
165             if (children) {
166                 g_list_free(children);
167             }
168         }
169     }
170 
171     return false;
172 }
173 
174 static gboolean
shellLeave(GtkWidget * widget,GdkEventCrossing *,void *)175 shellLeave(GtkWidget *widget, GdkEventCrossing*, void*)
176 {
177     if (GTK_IS_MENU_SHELL(widget) && GTK_IS_CONTAINER(widget)) {
178         GList *children = gtk_container_get_children(GTK_CONTAINER(widget));
179         for (GList *child = children;child;child = g_list_next(child)) {
180             if ((child->data) && GTK_IS_MENU_ITEM(child->data) &&
181                 (gtk_widget_get_state(GTK_WIDGET(child->data)) !=
182                  GTK_STATE_INSENSITIVE)) {
183                 GtkWidget *submenu =
184                     gtk_menu_item_get_submenu(GTK_MENU_ITEM(child->data));
185                 GtkWidget *topLevel =
186                     submenu ? gtk_widget_get_toplevel(submenu) : nullptr;
187 
188                 if (submenu &&
189                     ((!GTK_IS_MENU(submenu)) ||
190                      (!(gtk_widget_get_realized(submenu) &&
191                         gtk_widget_get_visible(submenu) &&
192                         gtk_widget_get_realized(topLevel) &&
193                         gtk_widget_get_visible(topLevel))))) {
194                     gtk_widget_set_state(GTK_WIDGET(child->data),
195                                          GTK_STATE_NORMAL);
196                 }
197             }
198         }
199         if (children) {
200             g_list_free(children);
201         }
202     }
203     return false;
204 }
205 
206 void
shellSetup(GtkWidget * widget)207 shellSetup(GtkWidget *widget)
208 {
209     GtkWidgetProps props(widget);
210     if (GTK_IS_MENU_BAR(widget) && !props->menuShellHacked) {
211         props->menuShellHacked = true;
212         props->menuShellMotion.conn("motion-notify-event", shellMotion);
213         props->menuShellLeave.conn("leave-notify-event", shellLeave);
214         props->menuShellDestroy.conn("destroy-event", shellDestroy);
215         props->menuShellStyleSet.conn("style-set", shellStyleSet);
216 #ifdef EXTEND_MENUBAR_ITEM_HACK
217         props->menuShellButtonPress.conn("button-press-event",
218                                          shellButtonPress);
219         props->menuShellButtonRelease.conn("button-release-event",
220                                            shellButtonPress);
221 #endif
222     }
223 }
224 
225 bool
emitSize(GtkWidget * w,unsigned size)226 emitSize(GtkWidget *w, unsigned size)
227 {
228     if (w) {
229         GtkWidgetProps props(w);
230         unsigned oldSize = props->menuBarSize;
231 
232         if (oldSize != size) {
233             GtkWidget *topLevel = gtk_widget_get_toplevel(w);
234             xcb_window_t wid =
235                 GDK_WINDOW_XID(gtk_widget_get_window(GTK_WIDGET(topLevel)));
236 
237             if (size == 0xFFFF) {
238                 size = 0;
239             }
240             props->menuBarSize = size;
241             qtcX11SetMenubarSize(wid, size);
242             return true;
243         }
244     }
245     return false;
246 }
247 
248 }
249 }
250