diff options
Diffstat (limited to 'libqmenumodel/src/ayatanamenumodel.cpp')
-rw-r--r-- | libqmenumodel/src/ayatanamenumodel.cpp | 1064 |
1 files changed, 1064 insertions, 0 deletions
diff --git a/libqmenumodel/src/ayatanamenumodel.cpp b/libqmenumodel/src/ayatanamenumodel.cpp new file mode 100644 index 0000000..24bfa40 --- /dev/null +++ b/libqmenumodel/src/ayatanamenumodel.cpp @@ -0,0 +1,1064 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program 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; version 3. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#include "ayatanamenumodel.h" +#include "converter.h" +#include "actionstateparser.h" +#include "ayatanamenumodelevents.h" +#include "ayatanamenuaction.h" +#include "ayatanamenuactionevents.h" +#include "logging.h" + +#include <QIcon> +#include <QQmlComponent> +#include <QCoreApplication> +#include <QKeySequence> + +extern "C" { + #include "gtk/gtkactionmuxer.h" + #include "gtk/gtkmenutracker.h" + #include "gtk/gtksimpleactionobserver.h" +} + +Q_LOGGING_CATEGORY(unitymenumodel, "qmenumodel.unitymenumodel", QtCriticalMsg) + +G_DEFINE_QUARK (UNITY_MENU_MODEL, unity_menu_model) +G_DEFINE_QUARK (UNITY_SUBMENU_MODEL, unity_submenu_model) +G_DEFINE_QUARK (UNITY_MENU_ITEM_EXTENDED_ATTRIBUTES, unity_menu_item_extended_attributes) +G_DEFINE_QUARK (UNITY_MENU_ACTION, unity_menu_action) + + +enum MenuRoles { + LabelRole = Qt::DisplayRole + 1, + SensitiveRole, + IsSeparatorRole, + IconRole, + TypeRole, + ExtendedAttributesRole, + ActionRole, + ActionStateRole, + IsCheckRole, + IsRadioRole, + IsToggledRole, + ShortcutRole, + HasSubmenuRole +}; + +class UnityMenuModelPrivate +{ +public: + UnityMenuModelPrivate(UnityMenuModel *model); + UnityMenuModelPrivate(const UnityMenuModelPrivate& other, UnityMenuModel *model); + ~UnityMenuModelPrivate(); + + void clearItems(bool resetModel=true); + void clearName(); + void updateActions(); + void updateMenuModel(); + QVariant itemState(GtkMenuTrackerItem *item); + + UnityMenuModel *model; + GtkActionMuxer *muxer; + GtkMenuTracker *menutracker; + GSequence *items; + GDBusConnection *connection; + QByteArray busName; + QByteArray nameOwner; + guint nameWatchId; + QVariantMap actions; + QByteArray menuObjectPath; + QHash<QByteArray, int> roles; + ActionStateParser* actionStateParser; + QHash<UnityMenuAction*, GtkSimpleActionObserver*> registeredActions; + bool destructorGuard; + + static void nameAppeared(GDBusConnection *connection, const gchar *name, const gchar *owner, gpointer user_data); + static void nameVanished(GDBusConnection *connection, const gchar *name, gpointer user_data); + static void menuItemInserted(GPtrArray *items, gint position, gpointer user_data); + static void menuItemRemoved(gint position, gint n_items, gpointer user_data); + static void menuItemChanged(GObject *object, GParamSpec *pspec, gpointer user_data); + + static void registeredActionAdded(GtkSimpleActionObserver *observer_item, + const gchar *action_name, + gboolean enabled, + GVariant *state); + static void registeredActionEnabledChanged(GtkSimpleActionObserver *observer_item, const gchar *action_name, gboolean enabled); + static void registeredActionStateChanged(GtkSimpleActionObserver *observer_item, const gchar *action_name, GVariant *state); + static void registeredActionRemoved(GtkSimpleActionObserver *observer_item, const gchar *action_name); + + gchar * fullActionName(UnityMenuAction *action); + void updateRegisteredAction(UnityMenuAction *action); +}; + +void menu_item_free (gpointer data) +{ + GtkMenuTrackerItem *item = (GtkMenuTrackerItem *) data; + + g_signal_handlers_disconnect_by_func (item, (gpointer) UnityMenuModelPrivate::menuItemChanged, NULL); + g_object_unref (item); +} + +UnityMenuModelPrivate::UnityMenuModelPrivate(UnityMenuModel *model) +{ + this->model = model; + this->menutracker = NULL; + this->connection = NULL; + this->nameWatchId = 0; + this->actionStateParser = new ActionStateParser(model); + this->destructorGuard = false; + + this->muxer = gtk_action_muxer_new (); + + this->items = g_sequence_new (menu_item_free); +} + +UnityMenuModelPrivate::UnityMenuModelPrivate(const UnityMenuModelPrivate& other, UnityMenuModel *model) +{ + this->model = model; + this->menutracker = NULL; + this->connection = NULL; + this->nameWatchId = 0; + this->actionStateParser = new ActionStateParser(model); + this->destructorGuard = false; + + this->muxer = GTK_ACTION_MUXER( g_object_ref(other.muxer)); + + this->items = g_sequence_new (menu_item_free); +} + +UnityMenuModelPrivate::~UnityMenuModelPrivate() +{ + this->destructorGuard = true; + this->clearItems(false); + + g_sequence_free(this->items); + g_clear_pointer (&this->menutracker, gtk_menu_tracker_free); + g_clear_object (&this->muxer); + g_clear_object (&this->connection); + + QHash<UnityMenuAction*, GtkSimpleActionObserver*>::const_iterator it = this->registeredActions.constBegin(); + for (; it != this->registeredActions.constEnd(); ++it) { + g_object_unref(it.value()); + it.key()->setModel(NULL); + } + this->registeredActions.clear(); + + if (this->nameWatchId) + g_bus_unwatch_name (this->nameWatchId); +} + +void UnityMenuModelPrivate::clearItems(bool resetModel) +{ + UnityMenuModelClearEvent ummce(resetModel); + QCoreApplication::sendEvent(model, &ummce); +} + +void UnityMenuModelPrivate::clearName() +{ + this->clearItems(); + + this->nameOwner = QByteArray(); + + this->updateActions(); + this->updateMenuModel(); + + Q_EMIT model->nameOwnerChanged (this->nameOwner); +} + +void UnityMenuModelPrivate::updateActions() +{ + Q_FOREACH (QString prefix, this->actions.keys()) + gtk_action_muxer_remove (this->muxer, prefix.toUtf8()); + + if (this->nameOwner.isEmpty()) + return; + + for (QVariantMap::const_iterator it = this->actions.constBegin(); it != this->actions.constEnd(); ++it) { + GDBusActionGroup *actions; + + actions = g_dbus_action_group_get (this->connection, this->nameOwner, it.value().toByteArray()); + gtk_action_muxer_insert (this->muxer, it.key().toUtf8(), G_ACTION_GROUP (actions)); + + g_object_unref (actions); + } +} + +void UnityMenuModelPrivate::updateMenuModel() +{ + this->clearItems(); + g_clear_pointer (&this->menutracker, gtk_menu_tracker_free); + + if (!this->nameOwner.isEmpty()) { + GDBusMenuModel *menu; + + menu = g_dbus_menu_model_get (this->connection, this->nameOwner, this->menuObjectPath.constData()); + this->menutracker = gtk_menu_tracker_new (GTK_ACTION_OBSERVABLE (this->muxer), + G_MENU_MODEL (menu), TRUE, NULL, + menuItemInserted, menuItemRemoved, this); + + g_object_unref (menu); + } +} + +QVariant UnityMenuModelPrivate::itemState(GtkMenuTrackerItem *item) +{ + QVariant result; + + GVariant *state = gtk_menu_tracker_item_get_action_state (item); + if (state != NULL) { + if (actionStateParser != NULL) { + result = actionStateParser->toQVariant(state); + } + g_variant_unref (state); + } + + return result; +} + +void UnityMenuModelPrivate::nameAppeared(GDBusConnection *connection, const gchar *name, const gchar *owner, gpointer user_data) +{ + UnityMenuModelPrivate *priv = (UnityMenuModelPrivate *)user_data; + + priv->connection = (GDBusConnection *) g_object_ref (connection); + priv->nameOwner = owner; + + priv->updateActions(); + priv->updateMenuModel(); + + Q_EMIT priv->model->nameOwnerChanged (priv->nameOwner); +} + +void UnityMenuModelPrivate::nameVanished(GDBusConnection *connection, const gchar *name, gpointer user_data) +{ + UnityMenuModelPrivate *priv = (UnityMenuModelPrivate *)user_data; + + priv->clearName(); +} + +void UnityMenuModelPrivate::menuItemInserted(GPtrArray *items, gint position, gpointer user_data) +{ + UnityMenuModelPrivate *priv = (UnityMenuModelPrivate *)user_data; + + UnityMenuModelAddRowEvent ummare(items, position); + QCoreApplication::sendEvent(priv->model, &ummare); +} + +void UnityMenuModelPrivate::menuItemRemoved(gint position, gint n_items, gpointer user_data) +{ + UnityMenuModelPrivate *priv = (UnityMenuModelPrivate *)user_data; + + UnityMenuModelRemoveRowEvent ummrre(position, n_items); + QCoreApplication::sendEvent(priv->model, &ummrre); +} + +void UnityMenuModelPrivate::menuItemChanged(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + GSequenceIter *it = (GSequenceIter *) user_data; + GtkMenuTrackerItem *item; + GtkActionObservable *muxer; + UnityMenuModel *model; + gint position; + + item = (GtkMenuTrackerItem *) g_sequence_get (it); + muxer = _gtk_menu_tracker_item_get_observable (item); + model = (UnityMenuModel *) g_object_get_qdata (G_OBJECT (item), unity_menu_model_quark ()); + position = g_sequence_iter_get_position (it); + + UnityMenuModelDataChangeEvent ummdce(position); + QCoreApplication::sendEvent(model, &ummdce); +} + +UnityMenuModel::UnityMenuModel(QObject *parent): + QAbstractListModel(parent) +{ + priv = new UnityMenuModelPrivate(this); +} + +UnityMenuModel::UnityMenuModel(const UnityMenuModelPrivate& other, UnityMenuModel *parent): + QAbstractListModel(parent) +{ + priv = new UnityMenuModelPrivate(other, this); +} + +UnityMenuModel::~UnityMenuModel() +{ + delete priv; +} + +QByteArray UnityMenuModel::busName() const +{ + return priv->busName; +} + +QByteArray UnityMenuModel::nameOwner() const +{ + return priv->nameOwner; +} + +void UnityMenuModel::setBusName(const QByteArray &name) +{ + if (name == priv->busName) + return; + + priv->clearName(); + + if (priv->nameWatchId) + g_bus_unwatch_name (priv->nameWatchId); + + priv->nameWatchId = g_bus_watch_name (G_BUS_TYPE_SESSION, name.constData(), G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + UnityMenuModelPrivate::nameAppeared, UnityMenuModelPrivate::nameVanished, + priv, NULL); + priv->busName = name; + Q_EMIT busNameChanged (priv->busName); +} + +QVariantMap UnityMenuModel::actions() const +{ + return priv->actions; +} + +void UnityMenuModel::setActions(const QVariantMap &actions) +{ + priv->actions = actions; + priv->updateActions(); +} + +QByteArray UnityMenuModel::menuObjectPath() const +{ + return priv->menuObjectPath; +} + +void UnityMenuModel::setMenuObjectPath(const QByteArray &path) +{ + priv->menuObjectPath = path; + priv->updateMenuModel(); +} + +ActionStateParser* UnityMenuModel::actionStateParser() const +{ + return priv->actionStateParser; +} + +void UnityMenuModel::setActionStateParser(ActionStateParser* actionStateParser) +{ + if (priv->actionStateParser != actionStateParser) { + priv->actionStateParser = actionStateParser; + Q_EMIT actionStateParserChanged(actionStateParser); + } +} + +int UnityMenuModel::rowCount(const QModelIndex &parent) const +{ + return !parent.isValid() ? g_sequence_get_length (priv->items) : 0; +} + +int UnityMenuModel::columnCount(const QModelIndex &parent) const +{ + return 1; +} + +static QString iconUri(GIcon *icon) +{ + QString uri; + + if (G_IS_THEMED_ICON (icon)) { + const gchar* const* iconNames = g_themed_icon_get_names (G_THEMED_ICON (icon)); + guint index = 0; + while(iconNames[index] != NULL) { + if (QIcon::hasThemeIcon(iconNames[index])) { + uri = QString("image://theme/") + iconNames[index]; + break; + } + index++; + } + } + else if (G_IS_FILE_ICON (icon)) { + GFile *file; + + file = g_file_icon_get_file (G_FILE_ICON (icon)); + if (file) { + gchar *fileuri; + + fileuri = g_file_get_uri (file); + uri = QString(fileuri); + + g_free (fileuri); + } + } + else if (G_IS_BYTES_ICON (icon)) { + gsize size; + gconstpointer data; + gchar *base64; + + data = g_bytes_get_data (g_bytes_icon_get_bytes (G_BYTES_ICON (icon)), &size); + base64 = g_base64_encode ((const guchar *) data, size); + + uri = QString("data://"); + uri.append (base64); + + g_free (base64); + } + + return uri; +} + +QVariant UnityMenuModel::data(const QModelIndex &index, int role) const +{ + GSequenceIter *it; + GtkMenuTrackerItem *item; + + it = g_sequence_get_iter_at_pos (priv->items, index.row()); + if (g_sequence_iter_is_end (it)) { + return QVariant(); + } + + item = (GtkMenuTrackerItem *) g_sequence_get (it); + if (!item) { + return QVariant(); + } + + switch (role) { + case LabelRole: + return gtk_menu_tracker_item_get_label (item); + + case SensitiveRole: + return gtk_menu_tracker_item_get_sensitive (item) == TRUE ? true : false; + + case IsSeparatorRole: + return gtk_menu_tracker_item_get_is_separator (item) == TRUE ? true : false; + + case IconRole: { + GIcon *icon = gtk_menu_tracker_item_get_icon (item); + if (icon) { + QString uri = iconUri(icon); + g_object_unref (icon); + return uri; + } + else + return QString(); + } + + case TypeRole: { + gchar *type; + auto ret = gtk_menu_tracker_item_get_attribute (item, "x-ayatana-type", "s", &type); + + // If we can't get x-ayatana-type, try legacy x-canonical-type type + if (!ret) + ret = gtk_menu_tracker_item_get_attribute (item, "x-canonical-type", "s", &type); + + if (ret) { + QVariant v(type); + g_free (type); + return v; + } + else + return QVariant(); + } + + case ExtendedAttributesRole: { + QVariantMap *map = (QVariantMap *) g_object_get_qdata (G_OBJECT (item), unity_menu_item_extended_attributes_quark ()); + return map ? *map : QVariant(); + } + + case ActionRole: { + gchar *action_name = gtk_menu_tracker_item_get_action_name (item); + QString v(action_name); + g_free(action_name); + return v; + } + + case ActionStateRole: + return priv->itemState(item); + + case IsCheckRole: + return gtk_menu_tracker_item_get_role (item) == GTK_MENU_TRACKER_ITEM_ROLE_CHECK; + + case IsRadioRole: + return gtk_menu_tracker_item_get_role (item) == GTK_MENU_TRACKER_ITEM_ROLE_RADIO; + + case IsToggledRole: + return gtk_menu_tracker_item_get_toggled (item) != FALSE; + + case ShortcutRole: + return QKeySequence(gtk_menu_tracker_item_get_accel (item), QKeySequence::NativeText); + + case HasSubmenuRole: + return gtk_menu_tracker_item_get_has_submenu (item) != FALSE; + + default: + return QVariant(); + } +} + +QModelIndex UnityMenuModel::index(int row, int column, const QModelIndex &parent) const +{ + return createIndex(row, column); +} + +QModelIndex UnityMenuModel::parent(const QModelIndex &index) const +{ + return QModelIndex(); +} + +QHash<int, QByteArray> UnityMenuModel::roleNames() const +{ + QHash<int, QByteArray> names; + + names[LabelRole] = "label"; + names[SensitiveRole] = "sensitive"; + names[IsSeparatorRole] = "isSeparator"; + names[IconRole] = "icon"; + names[TypeRole] = "type"; + names[ExtendedAttributesRole] = "ext"; + names[ActionRole] = "action"; + names[ActionStateRole] = "actionState"; + names[IsCheckRole] = "isCheck"; + names[IsRadioRole] = "isRadio"; + names[IsToggledRole] = "isToggled"; + names[ShortcutRole] = "shortcut"; + names[HasSubmenuRole] = "hasSubmenu"; + + return names; +} + +QObject * UnityMenuModel::submenu(int position, QQmlComponent* actionStateParser) +{ + GSequenceIter *it; + GtkMenuTrackerItem *item; + UnityMenuModel *model; + + it = g_sequence_get_iter_at_pos (priv->items, position); + if (g_sequence_iter_is_end (it)) { + return NULL; + } + + item = (GtkMenuTrackerItem *) g_sequence_get (it); + if (!item || !gtk_menu_tracker_item_get_has_submenu (item)) { + return NULL; + } + + model = (UnityMenuModel *) g_object_get_qdata (G_OBJECT (item), unity_submenu_model_quark ()); + if (model == NULL) { + model = new UnityMenuModel(*priv, this); + + if (actionStateParser) { + ActionStateParser* parser = qobject_cast<ActionStateParser*>(actionStateParser->create()); + if (parser) { + model->setActionStateParser(parser); + } + } + + model->priv->menutracker = gtk_menu_tracker_new_for_item_submenu (item, + UnityMenuModelPrivate::menuItemInserted, + UnityMenuModelPrivate::menuItemRemoved, + model->priv); + g_object_set_qdata (G_OBJECT (item), unity_submenu_model_quark (), model); + } + + return model; +} + +static void freeExtendedAttrs(gpointer data) +{ + QVariantMap *extendedAttrs = (QVariantMap *) data; + delete extendedAttrs; +} + +static QVariant attributeToQVariant(GVariant *value, const QString &type) +{ + QVariant result; + + if (type == "int") { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_INT32)) { + result = QVariant(g_variant_get_int32(value)); + } + } + else if (type == "int64") { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_INT64)) { + result = QVariant((qlonglong)g_variant_get_int64(value)); + } + } + else if (type == "bool") { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)) { + result = QVariant(g_variant_get_boolean(value)); + } + } + else if (type == "string") { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) { + result = QVariant(g_variant_get_string(value, NULL)); + } + } + else if (type == "double") { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_DOUBLE)) { + result = QVariant(g_variant_get_double(value)); + } + } + else if (type == "variant") { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_VARIANT)) { + result = Converter::toQVariant(value); + } + } + else if (type == "icon") { + GIcon *icon = g_icon_deserialize (value); + if (icon) { + result = iconUri(icon); + g_object_unref (icon); + } + else { + result = QVariant(""); + } + } + + return result; +} + +/* convert 'some-key' to 'someKey' or 'SomeKey'. (from dconf-qt) */ +static QString qtify_name(const char *name) +{ + bool next_cap = false; + QString result; + + while (*name) { + if (*name == '-') { + next_cap = true; + } else if (next_cap) { + result.append(toupper(*name)); + next_cap = false; + } else { + result.append(*name); + } + + name++; + } + + return result; +} + +bool UnityMenuModel::loadExtendedAttributes(int position, const QVariantMap &schema) +{ + GSequenceIter *it; + GtkMenuTrackerItem *item; + QVariantMap *extendedAttrs; + + it = g_sequence_get_iter_at_pos (priv->items, position); + if (g_sequence_iter_is_end (it)) { + return false; + } + + item = (GtkMenuTrackerItem *) g_sequence_get (it); + if (!item) { + return false; + } + + extendedAttrs = new QVariantMap; + + for (QVariantMap::const_iterator it = schema.constBegin(); it != schema.constEnd(); ++it) { + QString name = it.key(); + QString type = it.value().toString(); + + GVariant *value = gtk_menu_tracker_item_get_attribute_value (item, name.toUtf8(), NULL); + if (value == NULL) { + qCWarning(unitymenumodel, "loadExtendedAttributes: menu item does not contain '%s'", it.key().toUtf8().constData()); + continue; + } + + const QVariant &qvalue = attributeToQVariant(value, type); + if (qvalue.isValid()) + extendedAttrs->insert(qtify_name (name.toUtf8()), qvalue); + else + qCWarning(unitymenumodel, "loadExtendedAttributes: key '%s' is of type '%s' (expected '%s')", + name.toUtf8().constData(), g_variant_get_type_string(value), type.constData()); + + g_variant_unref (value); + } + + g_object_set_qdata_full (G_OBJECT (item), unity_menu_item_extended_attributes_quark (), + extendedAttrs, freeExtendedAttrs); + + Q_EMIT dataChanged(index(position, 0), index(position, 0), QVector<int>() << ExtendedAttributesRole); + return true; +} + +QVariant UnityMenuModel::get(int row, const QByteArray &role) +{ + if (priv->roles.isEmpty()) { + QHash<int, QByteArray> names = roleNames(); + Q_FOREACH (int role, names.keys()) + priv->roles.insert(names[role], role); + } + + return this->data(this->index(row, 0), priv->roles[role]); +} + +void UnityMenuModel::activate(int index, const QVariant& parameter) +{ + GSequenceIter *it; + GtkMenuTrackerItem *item; + GVariant *value; + const GVariantType *parameter_type; + + it = g_sequence_get_iter_at_pos (priv->items, index); + if (g_sequence_iter_is_end (it)) { + return; + } + + item = (GtkMenuTrackerItem *) g_sequence_get (it); + if (!item) { + return; + } + + if (parameter.isValid()) { + gchar *action; + + action = gtk_menu_tracker_item_get_action_name (item); + parameter_type = g_action_group_get_action_parameter_type (G_ACTION_GROUP (priv->muxer), action); + value = Converter::toGVariantWithSchema(parameter, g_variant_type_peek_string (parameter_type)); + g_action_group_activate_action (G_ACTION_GROUP (priv->muxer), action, value); + + g_free (action); + } else { + gtk_menu_tracker_item_activated (item); + } +} + +void UnityMenuModel::aboutToShow(int index) +{ + GSequenceIter *it = g_sequence_get_iter_at_pos (priv->items, index); + if (g_sequence_iter_is_end (it)) { + return; + } + + auto item = static_cast<GtkMenuTrackerItem*>(g_sequence_get(it)); + if (!item) { + return; + } + + quint64 actionTag; + if (gtk_menu_tracker_item_get_attribute (item, "qtubuntu-tag", "t", &actionTag)) { + // Child UnityMenuModel have priv->connection null, so climb to the parent until we find a non null one + UnityMenuModelPrivate *privToUse = priv; + while (privToUse && !privToUse->connection) { + auto pModel = dynamic_cast<UnityMenuModel*>(privToUse->model->QObject::parent()); + if (pModel) { + privToUse = pModel->priv; + } else { + privToUse = nullptr; + } + } + if (privToUse) { + g_dbus_connection_call (privToUse->connection, + privToUse->busName, + privToUse->menuObjectPath, + "qtubuntu.actions.extra", + "aboutToShow", + g_variant_new("(t)", actionTag), + nullptr, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + G_MAXINT, + nullptr, + nullptr, + nullptr); + } + } +} + +void UnityMenuModel::activateByVariantString(int index, const QString& parameter) +{ + activate(index, Converter::toQVariantFromVariantString(parameter)); +} + +void UnityMenuModel::changeStateByVariantString(int index, const QString& parameter) +{ + changeState(index, Converter::toQVariantFromVariantString(parameter)); +} + +void UnityMenuModel::changeState(int index, const QVariant& parameter) +{ + GSequenceIter *it; + GtkMenuTrackerItem* item; + GVariant* data; + GVariant* current_state; + + it = g_sequence_get_iter_at_pos (priv->items, index); + if (g_sequence_iter_is_end (it)) { + return; + } + + item = (GtkMenuTrackerItem *) g_sequence_get (it); + if (!item) { + return; + } + + current_state = gtk_menu_tracker_item_get_action_state (item); + if (current_state) { + // Attempt to convert the parameter to the expected type + data = Converter::toGVariantWithSchema(parameter, g_variant_get_type_string(current_state)); + g_variant_unref (current_state); + } else { + data = Converter::toGVariant(parameter); + } + + gtk_menu_tracker_item_change_state (item, data); + if (data) { + g_variant_unref(data); + } +} + + +bool UnityMenuModel::event(QEvent* e) +{ + if (e->type() == UnityMenuModelClearEvent::eventType) { + UnityMenuModelClearEvent *emmce = static_cast<UnityMenuModelClearEvent*>(e); + + GSequenceIter *begin; + GSequenceIter *end; + + if (emmce->reset) + beginResetModel(); + + begin = g_sequence_get_begin_iter (priv->items); + end = g_sequence_get_end_iter (priv->items); + g_sequence_remove_range (begin, end); + + if (emmce->reset) + endResetModel(); + + return true; + } else if (e->type() == UnityMenuModelAddRowEvent::eventType) { + UnityMenuModelAddRowEvent *ummrce = static_cast<UnityMenuModelAddRowEvent*>(e); + + GSequenceIter *it; + it = g_sequence_get_iter_at_pos (priv->items, ummrce->position); + + beginInsertRows(QModelIndex(), ummrce->position, ummrce->position + ummrce->items->len - 1); + + for (gint i = ummrce->items->len - 1; i >= 0; --i) { + GtkMenuTrackerItem *item = (GtkMenuTrackerItem*)g_ptr_array_index(ummrce->items, i); + it = g_sequence_insert_before (it, g_object_ref (item)); + g_object_set_qdata (G_OBJECT (item), unity_menu_model_quark (), this); + g_signal_connect (item, "notify", G_CALLBACK (UnityMenuModelPrivate::menuItemChanged), it); + } + + endInsertRows(); + return true; + } else if (e->type() == UnityMenuModelRemoveRowEvent::eventType) { + UnityMenuModelRemoveRowEvent *ummrre = static_cast<UnityMenuModelRemoveRowEvent*>(e); + + beginRemoveRows(QModelIndex(), ummrre->position, ummrre->position + ummrre->nItems - 1); + for (int i = 0; i < ummrre->nItems; ++i) { + GSequenceIter *it = g_sequence_get_iter_at_pos (priv->items, ummrre->position); + if (!g_sequence_iter_is_end (it)) { + g_sequence_remove (it); + } + } + endRemoveRows(); + + return true; + } else if (e->type() == UnityMenuModelDataChangeEvent::eventType) { + UnityMenuModelDataChangeEvent *ummdce = static_cast<UnityMenuModelDataChangeEvent*>(e); + + Q_EMIT dataChanged(index(ummdce->position, 0), index(ummdce->position, 0)); + return true; + } + return QAbstractListModel::event(e); +} + +void UnityMenuModel::registerAction(UnityMenuAction* action) +{ + if (priv->destructorGuard) + return; + + if (!priv->registeredActions.contains(action)) { + GtkSimpleActionObserver* observer_item; + observer_item = gtk_simple_action_observer_new(GTK_ACTION_OBSERVABLE (priv->muxer), + UnityMenuModelPrivate::registeredActionAdded, + UnityMenuModelPrivate::registeredActionEnabledChanged, + UnityMenuModelPrivate::registeredActionStateChanged, + UnityMenuModelPrivate::registeredActionRemoved); + + g_object_set_qdata (G_OBJECT (observer_item), unity_menu_action_quark (), action); + + priv->registeredActions[action] = observer_item; + + connect(action, SIGNAL(nameChanged(const QString&)), SLOT(onRegisteredActionNameChanged(const QString&))); + connect(action, SIGNAL(indexChanged(int)), SLOT(onRegisteredActionIndexChanged(int))); + connect(action, SIGNAL(activate(const QVariant&)), SLOT(onRegisteredActionActivated(const QVariant&))); + connect(action, SIGNAL(changeState(const QVariant&)), SLOT(onRegisteredActionStateChanged(const QVariant&))); + } +} + +void UnityMenuModel::unregisterAction(UnityMenuAction* action) +{ + if (priv->destructorGuard) + return; + + if (priv->registeredActions.contains(action)) { + GtkSimpleActionObserver* observer_item; + observer_item = priv->registeredActions[action]; + g_object_unref(observer_item); + priv->registeredActions.remove(action); + + disconnect(action); + } +} + +/* Returns the full name for @action + * + * If @action is associated with a menu item that is inside of a + * section or submenu with "action-namespace" set, this namespace + * is prepended to @action->name() + */ +char * UnityMenuModelPrivate::fullActionName(UnityMenuAction *action) +{ + GSequenceIter *iter; + QByteArray bytes; + const gchar *name; + gchar *full_name = NULL; + + bytes = action->name().toUtf8(); + name = bytes.constData(); + + iter = g_sequence_get_iter_at_pos (this->items, action->index()); + if (!g_sequence_iter_is_end (iter)) { + GtkMenuTrackerItem *item; + const gchar *action_namespace; + + item = (GtkMenuTrackerItem *) g_sequence_get (iter); + if (!item) { + return g_strdup (name); + } + + action_namespace = gtk_menu_tracker_item_get_action_namespace (item); + if (action_namespace != NULL) + return g_strjoin (".", action_namespace, name, NULL); + } + + return g_strdup (name); +} + +void UnityMenuModelPrivate::updateRegisteredAction(UnityMenuAction *action) +{ + GtkSimpleActionObserver *observer_item; + gchar *action_name; + gboolean enabled; + GVariant *state; + + if (!action || !this->registeredActions.contains(action)) + return; + + action_name = fullActionName(action); + + observer_item = this->registeredActions[action]; + gtk_simple_action_observer_register_action (observer_item, action_name); + + if (g_action_group_query_action (G_ACTION_GROUP (this->muxer), action_name, + &enabled, NULL, NULL, NULL, &state)) + { + UnityMenuActionAddEvent umaae(enabled, Converter::toQVariant(state)); + QCoreApplication::sendEvent(action, &umaae); + + if (state) { + g_variant_unref (state); + } + } + + g_free(action_name); +} + +void UnityMenuModel::onRegisteredActionNameChanged(const QString& name) +{ + priv->updateRegisteredAction(qobject_cast<UnityMenuAction*>(sender())); +} + +void UnityMenuModel::onRegisteredActionIndexChanged(int index) +{ + priv->updateRegisteredAction(qobject_cast<UnityMenuAction*>(sender())); +} + +void UnityMenuModel::onRegisteredActionActivated(const QVariant& parameter) +{ + UnityMenuAction* action = qobject_cast<UnityMenuAction*>(sender()); + if (!action || action->name().isEmpty()) + return; + + gchar* action_name = priv->fullActionName(action); + + g_action_group_activate_action (G_ACTION_GROUP (priv->muxer), action_name, Converter::toGVariant(parameter)); + + g_free(action_name); +} + +void UnityMenuModel::onRegisteredActionStateChanged(const QVariant& parameter) +{ + UnityMenuAction* action = qobject_cast<UnityMenuAction*>(sender()); + if (!action || action->name().isEmpty()) + return; + + gchar* action_name = priv->fullActionName(action); + + g_action_group_change_action_state (G_ACTION_GROUP (priv->muxer), action_name, Converter::toGVariant(parameter)); + + g_free(action_name); +} + +void UnityMenuModelPrivate::registeredActionAdded(GtkSimpleActionObserver *observer_item, + const gchar *action_name, + gboolean enabled, + GVariant *state) +{ + UnityMenuAction *action; + action = (UnityMenuAction *) g_object_get_qdata (G_OBJECT (observer_item), unity_menu_action_quark ()); + + if (action) { + UnityMenuActionAddEvent umaae(enabled, Converter::toQVariant(state)); + QCoreApplication::sendEvent(action, &umaae); + } +} + +void UnityMenuModelPrivate::registeredActionEnabledChanged(GtkSimpleActionObserver *observer_item, const gchar *action_name, gboolean enabled) +{ + UnityMenuAction *action; + action = (UnityMenuAction *) g_object_get_qdata (G_OBJECT (observer_item), unity_menu_action_quark ()); + + if (action) { + UnityMenuActionEnabledChangedEvent umaece(enabled); + QCoreApplication::sendEvent(action, &umaece); + } +} + +void UnityMenuModelPrivate::registeredActionStateChanged(GtkSimpleActionObserver *observer_item, const gchar *action_name, GVariant *state) +{ + UnityMenuAction *action; + action = (UnityMenuAction *) g_object_get_qdata (G_OBJECT (observer_item), unity_menu_action_quark ()); + + if (action) { + UnityMenuActionStateChangeEvent umasce(Converter::toQVariant(state)); + QCoreApplication::sendEvent(action, &umasce); + } +} + +void UnityMenuModelPrivate::registeredActionRemoved(GtkSimpleActionObserver *observer_item, const gchar *action_name) +{ + UnityMenuAction *action; + action = (UnityMenuAction *) g_object_get_qdata (G_OBJECT (observer_item), unity_menu_action_quark ()); + + if (action) { + UnityMenuActionRemoveEvent umare; + QCoreApplication::sendEvent(action, &umare); + } +} |