/* * Copyright 2014 Javier S. Pedro * * This file is part of TopMenu. * * TopMenu is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * TopMenu is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with TopMenu. If not, see . */ #include #include #include #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)); g_object_unref(menu); } Q_FOREACH(GtkMenuItem *item, m_items) { gtk_widget_destroy(GTK_WIDGET(item)); g_object_unref(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 = getItemForAction(action); if (item) { // Action is already on this proxy. return 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) { // Action is not on this proxy return; } gtk_widget_destroy(GTK_WIDGET(item)); g_object_unref(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)); g_object_unref(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(event); addAction(actionEvent->action(), actionEvent->before(), static_cast(src)); break; case QEvent::ActionRemoved: actionEvent = static_cast(event); removeAction(actionEvent->action()); break; case QEvent::ActionChanged: actionEvent = static_cast(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"); } }