1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtGui module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qgtk3menu.h" |
41 | |
42 | #include <QtGui/qwindow.h> |
43 | #include <QtGui/qpa/qplatformtheme.h> |
44 | #include <QtGui/qpa/qplatformwindow.h> |
45 | |
46 | #undef signals |
47 | #include <gtk/gtk.h> |
48 | |
49 | QT_BEGIN_NAMESPACE |
50 | |
51 | #if QT_CONFIG(shortcut) |
52 | static guint qt_gdkKey(const QKeySequence &shortcut) |
53 | { |
54 | if (shortcut.isEmpty()) |
55 | return 0; |
56 | |
57 | // TODO: proper mapping |
58 | Qt::KeyboardModifiers mods = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier; |
59 | return (shortcut[0].toCombined() ^ mods) & shortcut[0].toCombined(); |
60 | } |
61 | |
62 | static GdkModifierType qt_gdkModifiers(const QKeySequence &shortcut) |
63 | { |
64 | if (shortcut.isEmpty()) |
65 | return GdkModifierType(0); |
66 | |
67 | guint mods = 0; |
68 | Qt::KeyboardModifiers m = shortcut[0].keyboardModifiers(); |
69 | if (m & Qt::ShiftModifier) |
70 | mods |= GDK_SHIFT_MASK; |
71 | if (m & Qt::ControlModifier) |
72 | mods |= GDK_CONTROL_MASK; |
73 | if (m & Qt::AltModifier) |
74 | mods |= GDK_MOD1_MASK; |
75 | if (m & Qt::MetaModifier) |
76 | mods |= GDK_META_MASK; |
77 | |
78 | return static_cast<GdkModifierType>(mods); |
79 | } |
80 | #endif |
81 | |
82 | QGtk3MenuItem::() |
83 | : m_visible(true), |
84 | m_separator(false), |
85 | m_checkable(false), |
86 | m_checked(false), |
87 | m_enabled(true), |
88 | m_underline(false), |
89 | m_invalid(true), |
90 | m_menu(nullptr), |
91 | m_item(nullptr) |
92 | { |
93 | } |
94 | |
95 | QGtk3MenuItem::() |
96 | { |
97 | } |
98 | |
99 | bool QGtk3MenuItem::() const |
100 | { |
101 | return m_invalid; |
102 | } |
103 | |
104 | GtkWidget *QGtk3MenuItem::() |
105 | { |
106 | if (m_invalid) { |
107 | if (m_item) { |
108 | gtk_widget_destroy(m_item); |
109 | m_item = nullptr; |
110 | } |
111 | m_invalid = false; |
112 | } |
113 | |
114 | if (!m_item) { |
115 | if (m_separator) { |
116 | m_item = gtk_separator_menu_item_new(); |
117 | } else { |
118 | if (m_checkable) { |
119 | m_item = gtk_check_menu_item_new(); |
120 | gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(m_item), m_checked); |
121 | g_signal_connect(m_item, "toggled" , G_CALLBACK(onToggle), this); |
122 | } else { |
123 | m_item = gtk_menu_item_new(); |
124 | g_signal_connect(m_item, "activate" , G_CALLBACK(onActivate), this); |
125 | } |
126 | gtk_menu_item_set_label(GTK_MENU_ITEM(m_item), m_text.toUtf8()); |
127 | gtk_menu_item_set_use_underline(GTK_MENU_ITEM(m_item), m_underline); |
128 | if (m_menu) |
129 | gtk_menu_item_set_submenu(GTK_MENU_ITEM(m_item), m_menu->handle()); |
130 | g_signal_connect(m_item, "select" , G_CALLBACK(onSelect), this); |
131 | #if QT_CONFIG(shortcut) |
132 | if (!m_shortcut.isEmpty()) { |
133 | GtkWidget *label = gtk_bin_get_child(GTK_BIN(m_item)); |
134 | gtk_accel_label_set_accel(GTK_ACCEL_LABEL(label), qt_gdkKey(m_shortcut), qt_gdkModifiers(m_shortcut)); |
135 | } |
136 | #endif |
137 | } |
138 | gtk_widget_set_sensitive(m_item, m_enabled); |
139 | gtk_widget_set_visible(m_item, m_visible); |
140 | if (GTK_IS_CHECK_MENU_ITEM(m_item)) |
141 | g_object_set(m_item, "draw-as-radio" , m_exclusive, NULL); |
142 | } |
143 | |
144 | return m_item; |
145 | } |
146 | |
147 | GtkWidget *QGtk3MenuItem::handle() const |
148 | { |
149 | return m_item; |
150 | } |
151 | |
152 | QString QGtk3MenuItem::() const |
153 | { |
154 | return m_text; |
155 | } |
156 | |
157 | static QString convertMnemonics(QString text, bool *found) |
158 | { |
159 | *found = false; |
160 | |
161 | int i = text.length() - 1; |
162 | while (i >= 0) { |
163 | const QChar c = text.at(i); |
164 | if (c == QLatin1Char('&')) { |
165 | if (i == 0 || text.at(i - 1) != QLatin1Char('&')) { |
166 | // convert Qt to GTK mnemonic |
167 | if (i < text.length() - 1 && !text.at(i + 1).isSpace()) { |
168 | text.replace(i, 1, QLatin1Char('_')); |
169 | *found = true; |
170 | } |
171 | } else if (text.at(i - 1) == QLatin1Char('&')) { |
172 | // unescape ampersand |
173 | text.replace(--i, 2, QLatin1Char('&')); |
174 | } |
175 | } else if (c == QLatin1Char('_')) { |
176 | // escape GTK mnemonic |
177 | text.insert(i, QLatin1Char('_')); |
178 | } |
179 | --i; |
180 | } |
181 | |
182 | return text; |
183 | } |
184 | |
185 | void QGtk3MenuItem::(const QString &text) |
186 | { |
187 | m_text = convertMnemonics(text, &m_underline); |
188 | if (GTK_IS_MENU_ITEM(m_item)) { |
189 | gtk_menu_item_set_label(GTK_MENU_ITEM(m_item), m_text.toUtf8()); |
190 | gtk_menu_item_set_use_underline(GTK_MENU_ITEM(m_item), m_underline); |
191 | } |
192 | } |
193 | |
194 | QGtk3Menu *QGtk3MenuItem::() const |
195 | { |
196 | return m_menu; |
197 | } |
198 | |
199 | void QGtk3MenuItem::(QPlatformMenu *) |
200 | { |
201 | m_menu = qobject_cast<QGtk3Menu *>(menu); |
202 | if (GTK_IS_MENU_ITEM(m_item)) |
203 | gtk_menu_item_set_submenu(GTK_MENU_ITEM(m_item), m_menu ? m_menu->handle() : nullptr); |
204 | } |
205 | |
206 | bool QGtk3MenuItem::() const |
207 | { |
208 | return m_visible; |
209 | } |
210 | |
211 | void QGtk3MenuItem::(bool visible) |
212 | { |
213 | if (m_visible == visible) |
214 | return; |
215 | |
216 | m_visible = visible; |
217 | if (GTK_IS_MENU_ITEM(m_item)) |
218 | gtk_widget_set_visible(m_item, visible); |
219 | } |
220 | |
221 | bool QGtk3MenuItem::() const |
222 | { |
223 | return m_separator; |
224 | } |
225 | |
226 | void QGtk3MenuItem::(bool separator) |
227 | { |
228 | if (m_separator == separator) |
229 | return; |
230 | |
231 | m_invalid = true; |
232 | m_separator = separator; |
233 | } |
234 | |
235 | bool QGtk3MenuItem::() const |
236 | { |
237 | return m_checkable; |
238 | } |
239 | |
240 | void QGtk3MenuItem::(bool checkable) |
241 | { |
242 | if (m_checkable == checkable) |
243 | return; |
244 | |
245 | m_invalid = true; |
246 | m_checkable = checkable; |
247 | } |
248 | |
249 | bool QGtk3MenuItem::() const |
250 | { |
251 | return m_checked; |
252 | } |
253 | |
254 | void QGtk3MenuItem::(bool checked) |
255 | { |
256 | if (m_checked == checked) |
257 | return; |
258 | |
259 | m_checked = checked; |
260 | if (GTK_IS_CHECK_MENU_ITEM(m_item)) |
261 | gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(m_item), checked); |
262 | } |
263 | |
264 | #if QT_CONFIG(shortcut) |
265 | QKeySequence QGtk3MenuItem::() const |
266 | { |
267 | return m_shortcut; |
268 | } |
269 | |
270 | void QGtk3MenuItem::(const QKeySequence& shortcut) |
271 | { |
272 | if (m_shortcut == shortcut) |
273 | return; |
274 | |
275 | m_shortcut = shortcut; |
276 | if (GTK_IS_MENU_ITEM(m_item)) { |
277 | GtkWidget *label = gtk_bin_get_child(GTK_BIN(m_item)); |
278 | gtk_accel_label_set_accel(GTK_ACCEL_LABEL(label), qt_gdkKey(m_shortcut), qt_gdkModifiers(m_shortcut)); |
279 | } |
280 | } |
281 | #endif |
282 | |
283 | bool QGtk3MenuItem::() const |
284 | { |
285 | return m_enabled; |
286 | } |
287 | |
288 | void QGtk3MenuItem::(bool enabled) |
289 | { |
290 | if (m_enabled == enabled) |
291 | return; |
292 | |
293 | m_enabled = enabled; |
294 | if (m_item) |
295 | gtk_widget_set_sensitive(m_item, enabled); |
296 | } |
297 | |
298 | bool QGtk3MenuItem::() const |
299 | { |
300 | return m_exclusive; |
301 | } |
302 | |
303 | void QGtk3MenuItem::(bool exclusive) |
304 | { |
305 | if (m_exclusive == exclusive) |
306 | return; |
307 | |
308 | m_exclusive = exclusive; |
309 | if (GTK_IS_CHECK_MENU_ITEM(m_item)) |
310 | g_object_set(m_item, "draw-as-radio" , exclusive, NULL); |
311 | } |
312 | |
313 | void QGtk3MenuItem::(GtkMenuItem *, void *data) |
314 | { |
315 | QGtk3MenuItem *item = static_cast<QGtk3MenuItem *>(data); |
316 | if (item) |
317 | emit item->hovered(); |
318 | } |
319 | |
320 | void QGtk3MenuItem::(GtkMenuItem *, void *data) |
321 | { |
322 | QGtk3MenuItem *item = static_cast<QGtk3MenuItem *>(data); |
323 | if (item) |
324 | emit item->activated(); |
325 | } |
326 | |
327 | void QGtk3MenuItem::(GtkCheckMenuItem *check, void *data) |
328 | { |
329 | QGtk3MenuItem *item = static_cast<QGtk3MenuItem *>(data); |
330 | if (item) { |
331 | bool active = gtk_check_menu_item_get_active(check); |
332 | if (active != item->isChecked()) { |
333 | item->setChecked(active); |
334 | emit item->activated(); |
335 | } |
336 | } |
337 | } |
338 | |
339 | QGtk3Menu::() |
340 | { |
341 | m_menu = gtk_menu_new(); |
342 | |
343 | g_signal_connect(m_menu, "show" , G_CALLBACK(onShow), this); |
344 | g_signal_connect(m_menu, "hide" , G_CALLBACK(onHide), this); |
345 | } |
346 | |
347 | QGtk3Menu::() |
348 | { |
349 | if (GTK_IS_WIDGET(m_menu)) |
350 | gtk_widget_destroy(m_menu); |
351 | } |
352 | |
353 | GtkWidget *QGtk3Menu::handle() const |
354 | { |
355 | return m_menu; |
356 | } |
357 | |
358 | void QGtk3Menu::(QPlatformMenuItem *item, QPlatformMenuItem *before) |
359 | { |
360 | QGtk3MenuItem *gitem = static_cast<QGtk3MenuItem *>(item); |
361 | if (!gitem || m_items.contains(gitem)) |
362 | return; |
363 | |
364 | GtkWidget *handle = gitem->create(); |
365 | int index = m_items.indexOf(static_cast<QGtk3MenuItem *>(before)); |
366 | if (index < 0) |
367 | index = m_items.count(); |
368 | m_items.insert(index, gitem); |
369 | gtk_menu_shell_insert(GTK_MENU_SHELL(m_menu), handle, index); |
370 | } |
371 | |
372 | void QGtk3Menu::(QPlatformMenuItem *item) |
373 | { |
374 | QGtk3MenuItem *gitem = static_cast<QGtk3MenuItem *>(item); |
375 | if (!gitem || !m_items.removeOne(gitem)) |
376 | return; |
377 | |
378 | GtkWidget *handle = gitem->handle(); |
379 | if (handle) |
380 | gtk_container_remove(GTK_CONTAINER(m_menu), handle); |
381 | } |
382 | |
383 | void QGtk3Menu::(QPlatformMenuItem *item) |
384 | { |
385 | QGtk3MenuItem *gitem = static_cast<QGtk3MenuItem *>(item); |
386 | int index = m_items.indexOf(gitem); |
387 | if (index == -1 || !gitem->isInvalid()) |
388 | return; |
389 | |
390 | GtkWidget *handle = gitem->create(); |
391 | if (handle) |
392 | gtk_menu_shell_insert(GTK_MENU_SHELL(m_menu), handle, index); |
393 | } |
394 | |
395 | void QGtk3Menu::(bool enable) |
396 | { |
397 | Q_UNUSED(enable); |
398 | } |
399 | |
400 | void QGtk3Menu::(bool enabled) |
401 | { |
402 | gtk_widget_set_sensitive(m_menu, enabled); |
403 | } |
404 | |
405 | void QGtk3Menu::(bool visible) |
406 | { |
407 | gtk_widget_set_visible(m_menu, visible); |
408 | } |
409 | |
410 | static void (GtkMenu *, gint *x, gint *y, gboolean *push_in, gpointer data) |
411 | { |
412 | QGtk3Menu * = static_cast<QGtk3Menu *>(data); |
413 | QPoint targetPos = menu->targetPos(); |
414 | #if GTK_CHECK_VERSION(3, 10, 0) |
415 | targetPos /= gtk_widget_get_scale_factor(menu->handle()); |
416 | #endif |
417 | *x = targetPos.x(); |
418 | *y = targetPos.y(); |
419 | *push_in = true; |
420 | } |
421 | |
422 | QPoint QGtk3Menu::() const |
423 | { |
424 | return m_targetPos; |
425 | } |
426 | |
427 | void QGtk3Menu::(const QWindow *parentWindow, const QRect &targetRect, const QPlatformMenuItem *item) |
428 | { |
429 | const QGtk3MenuItem * = static_cast<const QGtk3MenuItem *>(item); |
430 | if (menuItem) |
431 | gtk_menu_shell_select_item(GTK_MENU_SHELL(m_menu), menuItem->handle()); |
432 | |
433 | m_targetPos = QPoint(targetRect.x(), targetRect.y() + targetRect.height()); |
434 | |
435 | QPlatformWindow *pw = parentWindow ? parentWindow->handle() : nullptr; |
436 | if (pw) |
437 | m_targetPos = pw->mapToGlobal(m_targetPos); |
438 | |
439 | gtk_menu_popup(GTK_MENU(m_menu), nullptr, nullptr, qt_gtk_menu_position_func, this, 0, gtk_get_current_event_time()); |
440 | } |
441 | |
442 | void QGtk3Menu::() |
443 | { |
444 | gtk_menu_popdown(GTK_MENU(m_menu)); |
445 | } |
446 | |
447 | QPlatformMenuItem *QGtk3Menu::(int position) const |
448 | { |
449 | return m_items.value(position); |
450 | } |
451 | |
452 | QPlatformMenuItem *QGtk3Menu::(quintptr tag) const |
453 | { |
454 | for (QGtk3MenuItem *item : m_items) { |
455 | if (item->tag() == tag) |
456 | return item; |
457 | } |
458 | return nullptr; |
459 | } |
460 | |
461 | QPlatformMenuItem *QGtk3Menu::() const |
462 | { |
463 | return new QGtk3MenuItem; |
464 | } |
465 | |
466 | QPlatformMenu *QGtk3Menu::() const |
467 | { |
468 | return new QGtk3Menu; |
469 | } |
470 | |
471 | void QGtk3Menu::(GtkWidget *, void *data) |
472 | { |
473 | QGtk3Menu * = static_cast<QGtk3Menu *>(data); |
474 | if (menu) |
475 | emit menu->aboutToShow(); |
476 | } |
477 | |
478 | void QGtk3Menu::(GtkWidget *, void *data) |
479 | { |
480 | QGtk3Menu * = static_cast<QGtk3Menu *>(data); |
481 | if (menu) |
482 | emit menu->aboutToHide(); |
483 | } |
484 | |
485 | QT_END_NAMESPACE |
486 | |