aboutsummaryrefslogtreecommitdiff
path: root/module/menuproxy.cc
diff options
context:
space:
mode:
Diffstat (limited to 'module/menuproxy.cc')
-rw-r--r--module/menuproxy.cc286
1 files changed, 286 insertions, 0 deletions
diff --git a/module/menuproxy.cc b/module/menuproxy.cc
new file mode 100644
index 0000000..5536bf5
--- /dev/null
+++ b/module/menuproxy.cc
@@ -0,0 +1,286 @@
+#include <gtk/gtk.h>
+#include <QtCore/QDebug>
+#include <QtGui/QActionEvent>
+
+#include "menuproxy.h"
+#include "qtkeysyms.h"
+
+static inline int index_of_menu_item(GtkMenuShell *shell, GtkMenuItem *item)
+{
+ return g_list_index(shell->children, item);
+}
+
+static inline GtkMenuItem * get_nth_menu_item(GtkMenuShell *shell, int i)
+{
+ return GTK_MENU_ITEM(g_list_nth_data(shell->children, i));
+}
+
+static bool transform_key_sequence(const QKeySequence &keys, guint *keyp, GdkModifierType *modsp)
+{
+ uint count = keys.count();
+ if (count != 1) return false; // TODO
+
+ guint mods = 0;
+ guint key = 0;
+
+ for (uint i = 0; i < count; i++) {
+ const uint qt_key = keys[i];
+ if (qt_key & Qt::CTRL) mods |= GDK_CONTROL_MASK;
+ if (qt_key & Qt::ALT) mods |= GDK_MOD1_MASK;
+ if (qt_key & Qt::SHIFT) mods |= GDK_SHIFT_MASK;
+
+ key = qt_to_gdk_key(qt_key & ~Qt::MODIFIER_MASK);
+ }
+
+ *keyp = key;
+ *modsp = GdkModifierType(mods);
+
+ return true;
+}
+
+static void destroy_image_data(guchar *pixels, gpointer data)
+{
+ Q_UNUSED(data);
+ g_free(pixels);
+}
+
+MenuProxy::MenuProxy(QObject *parent)
+ : QObject(parent), m_target(0), m_accel(0)
+{
+}
+
+MenuProxy::~MenuProxy()
+{
+ Q_FOREACH(GtkMenu *menu, m_menus) {
+ gtk_widget_destroy(GTK_WIDGET(menu));
+ }
+ Q_FOREACH(GtkMenuItem *item, m_items) {
+ gtk_widget_destroy(GTK_WIDGET(item));
+ }
+ if (m_accel) {
+ g_object_unref(m_accel);
+ m_accel = 0;
+ }
+}
+
+void MenuProxy::setTargetMenu(GtkMenuShell *shell)
+{
+ m_target = shell;
+ if (!m_accel) {
+ m_accel = gtk_accel_group_new();
+ }
+}
+
+GtkMenuItem * MenuProxy::addAction(QAction* action, QAction* before, QMenu* parent)
+{
+ GtkMenuShell *g_parent;
+ if (parent) {
+ g_parent = GTK_MENU_SHELL(m_menus.value(parent));
+ } else {
+ g_parent = m_target;
+ }
+
+ GtkMenuItem *item;
+ if (action->isSeparator()) {
+ item = GTK_MENU_ITEM(gtk_separator_menu_item_new());
+ } else if (action->isCheckable()) {
+ QString label = transformMnemonic(action->text());
+ item = GTK_MENU_ITEM(gtk_check_menu_item_new_with_mnemonic(label.toUtf8().constData()));
+ if (action->actionGroup() && action->actionGroup()->isExclusive()) {
+ gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(item), TRUE);
+ }
+ } else {
+ QString label = transformMnemonic(action->text());
+ QIcon icon = action->icon();
+ if (!icon.isNull()) {
+ QImage image = icon.pixmap(16, 16).toImage().convertToFormat(QImage::Format_ARGB32).rgbSwapped();
+ gsize size = image.byteCount();
+ guchar *data = (guchar*) g_malloc(size);
+ memcpy(data, image.constBits(), size);
+ GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB,
+ image.hasAlphaChannel(), 8,
+ image.width(), image.height(),
+ image.bytesPerLine(),
+ destroy_image_data, NULL);
+ item = GTK_MENU_ITEM(gtk_image_menu_item_new_with_mnemonic(label.toUtf8().constData()));
+ gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
+ gtk_image_new_from_pixbuf(pixbuf));
+ g_object_unref(pixbuf);
+ } else {
+ item = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(label.toUtf8().constData()));
+ }
+ }
+
+ m_items.insert(action, item);
+ g_object_ref_sink(item);
+
+ if (before) {
+ GtkMenuItem *g_before = m_items.value(before);
+ Q_ASSERT(g_before);
+ gint position = index_of_menu_item(g_parent, g_before);
+ gtk_menu_shell_insert(g_parent, GTK_WIDGET(item), position);
+ } else {
+ gtk_menu_shell_append(g_parent, GTK_WIDGET(item));
+ }
+
+ QMenu *menu = action->menu();
+ if (menu) {
+ GtkMenu * g_menu = addMenu(menu);
+ gtk_menu_item_set_submenu(item, GTK_WIDGET(g_menu));
+ }
+
+ updateAction(action);
+
+ return item;
+}
+
+GtkMenu * MenuProxy::addMenu(QMenu *menu)
+{
+ menu->installEventFilter(this);
+
+ GtkMenu *g_menu = GTK_MENU(gtk_menu_new());
+
+ m_menus.insert(menu, g_menu);
+ g_object_ref_sink(g_menu);
+
+ // Items might have been added already
+ foreach (QAction *action, menu->actions()) {
+ addAction(action, 0, menu);
+ }
+
+ return g_menu;
+}
+
+void MenuProxy::removeAction(QAction *action)
+{
+ QMenu* menu = action->menu();
+
+ if (menu) {
+ removeMenu(menu);
+ }
+
+ GtkMenuItem *item = m_items.value(action);
+ if (!item) {
+ return;
+ }
+
+ gtk_widget_destroy(GTK_WIDGET(item));
+ m_items.remove(action);
+}
+
+void MenuProxy::removeMenu(QMenu *menu)
+{
+ GtkMenu *g_menu = m_menus.value(menu);
+ Q_ASSERT(g_menu);
+ GtkWidget *g_item = gtk_menu_get_attach_widget(g_menu);
+ Q_ASSERT(g_item);
+ GtkMenu *g_parent = GTK_MENU(gtk_widget_get_parent(g_item));
+ Q_ASSERT(g_parent),
+
+ menu->removeEventFilter(this);
+
+ gtk_widget_destroy(GTK_WIDGET(g_menu));
+ m_menus.remove(menu);
+}
+
+void MenuProxy::updateAction(QAction *action)
+{
+ GtkMenuItem *item = m_items.value(action);
+ Q_ASSERT(item);
+
+ g_signal_handlers_disconnect_by_data(item, action);
+
+ if (action->isSeparator()) {
+ // TODO Decide visibility of separators using Qt rules.
+ gtk_widget_set_visible(GTK_WIDGET(item), action->isVisible());
+ } else {
+ QString label = action->text().replace(QChar('&'), QChar('_'));
+ gtk_menu_item_set_label(item, label.toUtf8().constData());
+
+ if (GTK_IS_CHECK_MENU_ITEM(item)) {
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
+ action->isChecked());
+ }
+
+ GtkWidget *child = gtk_bin_get_child(GTK_BIN(item));
+ if (GTK_IS_ACCEL_LABEL(child)) {
+ Q_FOREACH(const QKeySequence& shortcut, action->shortcuts()) {
+ guint key;
+ GdkModifierType mods;
+ if (transform_key_sequence(shortcut, &key, &mods)) {
+ gtk_widget_add_accelerator(GTK_WIDGET(item), "activate", m_accel,
+ key, mods,
+ GTK_ACCEL_VISIBLE);
+ }
+ }
+ }
+
+ gtk_widget_set_sensitive(GTK_WIDGET(item), action->isEnabled());
+ gtk_widget_set_visible(GTK_WIDGET(item), action->isVisible());
+ }
+
+ g_signal_connect(item, "activate",
+ G_CALLBACK(handleMenuItemActivated), action);
+ g_signal_connect(item, "select",
+ G_CALLBACK(handleMenuItemSelected), action);
+ g_signal_connect(item, "deselect",
+ G_CALLBACK(handleMenuItemDeselected), action);
+}
+
+GtkMenuItem *MenuProxy::getItemForAction(QAction *action)
+{
+ return m_items.value(action, 0);
+}
+
+QString MenuProxy::transformMnemonic(const QString &text)
+{
+ QString s(text);
+ return s.replace(QChar('&'), QChar('_'));
+}
+
+bool MenuProxy::eventFilter(QObject* src, QEvent* event)
+{
+ QActionEvent *actionEvent;
+ switch (event->type()) {
+ case QEvent::ActionAdded:
+ actionEvent = static_cast<QActionEvent *>(event);
+ addAction(actionEvent->action(), actionEvent->before(), static_cast<QMenu*>(src));
+ break;
+ case QEvent::ActionRemoved:
+ actionEvent = static_cast<QActionEvent *>(event);
+ removeAction(actionEvent->action());
+ break;
+ case QEvent::ActionChanged:
+ actionEvent = static_cast<QActionEvent *>(event);
+ updateAction(actionEvent->action());
+ break;
+ default:
+ break;
+ }
+ return false;
+}
+
+void MenuProxy::handleMenuItemActivated(GtkMenuItem *item, QAction *action)
+{
+ Q_UNUSED(item);
+ action->activate(QAction::Trigger);
+}
+
+void MenuProxy::handleMenuItemSelected(GtkMenuItem *item, QAction *action)
+{
+ Q_UNUSED(item);
+ action->activate(QAction::Hover);
+ QMenu *submenu = action->menu();
+ if (submenu) {
+ QMetaObject::invokeMethod(submenu, "aboutToShow");
+ }
+}
+
+void MenuProxy::handleMenuItemDeselected(GtkMenuItem *item, QAction *action)
+{
+ Q_UNUSED(item);
+ QMenu *submenu = action->menu();
+ if (submenu) {
+ QMetaObject::invokeMethod(submenu, "aboutToHide");
+ }
+}