[tepl] amtk: create amtk/ directory for Actions, Menus and Toolbars Kit



commit b4faabeb8d55d592a6bc702191a55be0c2e99eee
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Thu Jul 13 11:57:09 2017 +0200

    amtk: create amtk/ directory for Actions, Menus and Toolbars Kit
    
    Those classes are not really related to text editors, they are more
    general.
    
    As a first step the files are simply copied, with the following files
    adapted:
    - amtk/tepl-types.h
    - amtk/tepl.h

 amtk/tepl-action-info-central-store.c |  156 +++++++++++
 amtk/tepl-action-info-central-store.h |   71 +++++
 amtk/tepl-action-info-store.c         |  475 +++++++++++++++++++++++++++++++++
 amtk/tepl-action-info-store.h         |   80 ++++++
 amtk/tepl-action-info.c               |  405 ++++++++++++++++++++++++++++
 amtk/tepl-action-info.h               |  110 ++++++++
 amtk/tepl-action-map.c                |  105 ++++++++
 amtk/tepl-action-map.h                |   38 +++
 amtk/tepl-menu-item.c                 |  174 ++++++++++++
 amtk/tepl-menu-item.h                 |   41 +++
 amtk/tepl-menu-shell.c                |  413 ++++++++++++++++++++++++++++
 amtk/tepl-menu-shell.h                |   74 +++++
 amtk/tepl-types.h                     |   39 +++
 amtk/tepl.h                           |   36 +++
 14 files changed, 2217 insertions(+), 0 deletions(-)
---
diff --git a/amtk/tepl-action-info-central-store.c b/amtk/tepl-action-info-central-store.c
new file mode 100644
index 0000000..ba607aa
--- /dev/null
+++ b/amtk/tepl-action-info-central-store.c
@@ -0,0 +1,156 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2017 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tepl-action-info-central-store.h"
+#include "tepl-action-info.h"
+
+/**
+ * SECTION:action-info-central-store
+ * @Short_description: Aggregation of all TeplActionInfoStore's
+ * @Title: TeplActionInfoCentralStore
+ * @See_also: #TeplActionInfoStore
+ *
+ * #TeplActionInfoCentralStore is a singleton class containing the aggregation
+ * of all #TeplActionInfoStore's. Each time a #TeplActionInfo is added to a
+ * #TeplActionInfoStore, it is also added to the #TeplActionInfoCentralStore.
+ */
+
+/* API design:
+ *
+ * Why both TeplActionInfoStore and TeplActionInfoCentralStore are needed?
+ *
+ * Advantages of TeplActionInfoStore:
+ * - tepl_action_info_store_new() takes an optional GtkApplication parameter. It
+ *   doesn't rely on g_application_get_default() (calling
+ *   g_application_get_default() in a library is not really a good practice I
+ *   think. In theory an app can have several GApplication instances).
+ * - tepl_action_info_store_check_all_used()
+ *
+ * Advantages of TeplActionInfoCentralStore:
+ * - The central store checks if there are no duplicated action names
+ *   (globally).
+ * - [For the menu bar, easy to retrieve the tooltip to show it in the
+ *   statusbar.] No longer relevant with tepl_menu_item_get_long_description().
+ *
+ * If there was only one of the two classes, hacks would be needed to achieve
+ * the above items. So by having the two classes, we have the best of both
+ * worlds. We should not be afraid to create a lot of classes, and see things in
+ * big.
+ */
+
+struct _TeplActionInfoCentralStorePrivate
+{
+       /* Key: owned gchar*: action name.
+        * Value: owned TeplActionInfo.
+        */
+       GHashTable *hash_table;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (TeplActionInfoCentralStore, tepl_action_info_central_store, G_TYPE_OBJECT)
+
+static void
+tepl_action_info_central_store_finalize (GObject *object)
+{
+       TeplActionInfoCentralStore *central_store = TEPL_ACTION_INFO_CENTRAL_STORE (object);
+
+       g_hash_table_unref (central_store->priv->hash_table);
+
+       G_OBJECT_CLASS (tepl_action_info_central_store_parent_class)->finalize (object);
+}
+
+static void
+tepl_action_info_central_store_class_init (TeplActionInfoCentralStoreClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tepl_action_info_central_store_finalize;
+}
+
+static void
+tepl_action_info_central_store_init (TeplActionInfoCentralStore *central_store)
+{
+       central_store->priv = tepl_action_info_central_store_get_instance_private (central_store);
+
+       central_store->priv->hash_table = g_hash_table_new_full (g_str_hash,
+                                                                g_str_equal,
+                                                                g_free,
+                                                                (GDestroyNotify) tepl_action_info_unref);
+}
+
+/**
+ * tepl_action_info_central_store_get_instance:
+ *
+ * Returns: (transfer none): the #TeplActionInfoCentralStore singleton instance.
+ * Since: 2.0
+ */
+TeplActionInfoCentralStore *
+tepl_action_info_central_store_get_instance (void)
+{
+       static TeplActionInfoCentralStore *instance = NULL;
+
+       if (G_UNLIKELY (instance == NULL))
+       {
+               instance = g_object_new (TEPL_TYPE_ACTION_INFO_CENTRAL_STORE, NULL);
+       }
+
+       return instance;
+}
+
+void
+_tepl_action_info_central_store_add (TeplActionInfoCentralStore *central_store,
+                                    TeplActionInfo             *info)
+{
+       const gchar *action_name;
+
+       g_return_if_fail (TEPL_IS_ACTION_INFO_CENTRAL_STORE (central_store));
+       g_return_if_fail (info != NULL);
+
+       action_name = tepl_action_info_get_action_name (info);
+       g_return_if_fail (action_name != NULL);
+
+       if (g_hash_table_lookup (central_store->priv->hash_table, action_name) != NULL)
+       {
+               g_warning ("The TeplActionInfoCentralStore already contains a TeplActionInfo "
+                          "with the action name “%s”. Libraries must namespace their action names.",
+                          action_name);
+               return;
+       }
+
+       g_hash_table_insert (central_store->priv->hash_table,
+                            g_strdup (action_name),
+                            tepl_action_info_ref (info));
+}
+
+/**
+ * tepl_action_info_central_store_lookup:
+ * @central_store: a #TeplActionInfoCentralStore.
+ * @action_name: an action name.
+ *
+ * Returns: (transfer none): the found #TeplActionInfo, or %NULL.
+ * Since: 2.0
+ */
+const TeplActionInfo *
+tepl_action_info_central_store_lookup (TeplActionInfoCentralStore *central_store,
+                                      const gchar                *action_name)
+{
+       g_return_val_if_fail (TEPL_IS_ACTION_INFO_CENTRAL_STORE (central_store), NULL);
+       g_return_val_if_fail (action_name != NULL, NULL);
+
+       return g_hash_table_lookup (central_store->priv->hash_table, action_name);
+}
diff --git a/amtk/tepl-action-info-central-store.h b/amtk/tepl-action-info-central-store.h
new file mode 100644
index 0000000..a6806a8
--- /dev/null
+++ b/amtk/tepl-action-info-central-store.h
@@ -0,0 +1,71 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2017 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEPL_ACTION_INFO_CENTRAL_STORE_H
+#define TEPL_ACTION_INFO_CENTRAL_STORE_H
+
+#if !defined (TEPL_H_INSIDE) && !defined (TEPL_COMPILATION)
+#error "Only <tepl/tepl.h> can be included directly."
+#endif
+
+#include <glib-object.h>
+#include <tepl/tepl-types.h>
+
+G_BEGIN_DECLS
+
+#define TEPL_TYPE_ACTION_INFO_CENTRAL_STORE             (tepl_action_info_central_store_get_type ())
+#define TEPL_ACTION_INFO_CENTRAL_STORE(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
TEPL_TYPE_ACTION_INFO_CENTRAL_STORE, TeplActionInfoCentralStore))
+#define TEPL_ACTION_INFO_CENTRAL_STORE_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), 
TEPL_TYPE_ACTION_INFO_CENTRAL_STORE, TeplActionInfoCentralStoreClass))
+#define TEPL_IS_ACTION_INFO_CENTRAL_STORE(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
TEPL_TYPE_ACTION_INFO_CENTRAL_STORE))
+#define TEPL_IS_ACTION_INFO_CENTRAL_STORE_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), 
TEPL_TYPE_ACTION_INFO_CENTRAL_STORE))
+#define TEPL_ACTION_INFO_CENTRAL_STORE_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), 
TEPL_TYPE_ACTION_INFO_CENTRAL_STORE, TeplActionInfoCentralStoreClass))
+
+typedef struct _TeplActionInfoCentralStoreClass    TeplActionInfoCentralStoreClass;
+typedef struct _TeplActionInfoCentralStorePrivate  TeplActionInfoCentralStorePrivate;
+
+struct _TeplActionInfoCentralStore
+{
+       GObject parent;
+
+       TeplActionInfoCentralStorePrivate *priv;
+};
+
+struct _TeplActionInfoCentralStoreClass
+{
+       GObjectClass parent_class;
+
+       gpointer padding[12];
+};
+
+GType          tepl_action_info_central_store_get_type         (void) G_GNUC_CONST;
+
+TeplActionInfoCentralStore *
+               tepl_action_info_central_store_get_instance     (void);
+
+const TeplActionInfo *
+               tepl_action_info_central_store_lookup           (TeplActionInfoCentralStore *central_store,
+                                                                const gchar                *action_name);
+
+G_GNUC_INTERNAL
+void           _tepl_action_info_central_store_add             (TeplActionInfoCentralStore *central_store,
+                                                                TeplActionInfo             *info);
+
+G_END_DECLS
+
+#endif /* TEPL_ACTION_INFO_CENTRAL_STORE_H */
diff --git a/amtk/tepl-action-info-store.c b/amtk/tepl-action-info-store.c
new file mode 100644
index 0000000..e88baf7
--- /dev/null
+++ b/amtk/tepl-action-info-store.c
@@ -0,0 +1,475 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2017 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tepl-action-info-store.h"
+#include "tepl-action-info.h"
+#include "tepl-action-info-central-store.h"
+#include "tepl-menu-item.h"
+
+/**
+ * SECTION:action-info-store
+ * @Short_description: A store of TeplActionInfo's
+ * @Title: TeplActionInfoStore
+ * @See_also: #TeplActionInfo, #TeplActionInfoCentralStore
+ *
+ * #TeplActionInfoStore contains a set of #TeplActionInfo's.
+ *
+ * #TeplActionInfoStore is add-only, a #TeplActionInfo cannot be removed. If
+ * needed, the remove operation will be added in the future.
+ *
+ * A #GtkApplication can be associated so that when a widget is created,
+ * gtk_application_set_accels_for_action() is called. See
+ * tepl_action_info_store_create_menu_item() for more details. Note that this
+ * happens on widget creation, not when adding a #TeplActionInfo to the store,
+ * so that the accelerator is bound to the application only if the
+ * #TeplActionInfo is actually used.
+ *
+ * #TeplActionInfoStore is designed so that libraries can provide their own
+ * store, to share action information (with translations) and possibly the
+ * #GAction implementations as well. Application-specific #TeplActionInfo's can
+ * be added to the store returned by
+ * tepl_application_get_app_action_info_store().
+ *
+ * A library #TeplActionInfoStore must namespace the action names to not have
+ * conflicts when a #TeplActionInfo is added to the #TeplActionInfoCentralStore.
+ * Examples of namespaced action names: `"win.tepl-save"` or `"app.tepl-quit"`.
+ */
+
+struct _TeplActionInfoStorePrivate
+{
+       /* Weak ref, because usually GtkApplication owns (indirectly) a
+        * TeplActionInfoStore.
+        */
+       GtkApplication *app;
+
+       /* Key: owned gchar*: action name.
+        * Value: owned TeplActionInfo.
+        */
+       GHashTable *hash_table;
+};
+
+enum
+{
+       PROP_0,
+       PROP_APPLICATION,
+       N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES];
+
+G_DEFINE_TYPE_WITH_PRIVATE (TeplActionInfoStore, tepl_action_info_store, G_TYPE_OBJECT)
+
+static void
+set_application (TeplActionInfoStore *store,
+                GtkApplication      *app)
+{
+       g_return_if_fail (app == NULL || GTK_IS_APPLICATION (app));
+
+       g_assert (store->priv->app == NULL);
+
+       if (app == NULL)
+       {
+               return;
+       }
+
+       store->priv->app = app;
+       g_object_add_weak_pointer (G_OBJECT (store->priv->app),
+                                  (gpointer *) &store->priv->app);
+
+       g_object_notify_by_pspec (G_OBJECT (store), properties[PROP_APPLICATION]);
+}
+
+static void
+tepl_action_info_store_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+       TeplActionInfoStore *store = TEPL_ACTION_INFO_STORE (object);
+
+       switch (prop_id)
+       {
+               case PROP_APPLICATION:
+                       g_value_set_object (value, tepl_action_info_store_get_application (store));
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+tepl_action_info_store_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+       TeplActionInfoStore *store = TEPL_ACTION_INFO_STORE (object);
+
+       switch (prop_id)
+       {
+               case PROP_APPLICATION:
+                       set_application (store, g_value_get_object (value));
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+tepl_action_info_store_dispose (GObject *object)
+{
+       TeplActionInfoStore *store = TEPL_ACTION_INFO_STORE (object);
+
+       if (store->priv->app != NULL)
+       {
+               g_object_remove_weak_pointer (G_OBJECT (store->priv->app),
+                                             (gpointer *) &store->priv->app);
+               store->priv->app = NULL;
+       }
+
+       G_OBJECT_CLASS (tepl_action_info_store_parent_class)->dispose (object);
+}
+
+static void
+tepl_action_info_store_finalize (GObject *object)
+{
+       TeplActionInfoStore *store = TEPL_ACTION_INFO_STORE (object);
+
+       g_hash_table_unref (store->priv->hash_table);
+
+       G_OBJECT_CLASS (tepl_action_info_store_parent_class)->finalize (object);
+}
+
+static void
+tepl_action_info_store_class_init (TeplActionInfoStoreClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->get_property = tepl_action_info_store_get_property;
+       object_class->set_property = tepl_action_info_store_set_property;
+       object_class->dispose = tepl_action_info_store_dispose;
+       object_class->finalize = tepl_action_info_store_finalize;
+
+       /**
+        * TeplActionInfoStore:application:
+        *
+        * The associated #GtkApplication. #TeplActionInfoStore has a weak
+        * reference to the #GtkApplication.
+        *
+        * Since: 2.0
+        */
+       properties[PROP_APPLICATION] =
+               g_param_spec_object ("application",
+                                    "GtkApplication",
+                                    "",
+                                    GTK_TYPE_APPLICATION,
+                                    G_PARAM_READWRITE |
+                                    G_PARAM_CONSTRUCT_ONLY |
+                                    G_PARAM_STATIC_STRINGS);
+
+       g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+}
+
+static void
+tepl_action_info_store_init (TeplActionInfoStore *store)
+{
+       store->priv = tepl_action_info_store_get_instance_private (store);
+
+       store->priv->hash_table = g_hash_table_new_full (g_str_hash,
+                                                        g_str_equal,
+                                                        g_free,
+                                                        (GDestroyNotify) tepl_action_info_unref);
+}
+
+/**
+ * tepl_action_info_store_new:
+ * @application: (nullable): a #GtkApplication, or %NULL.
+ *
+ * Creates a new #TeplActionInfoStore object. Associating a #GtkApplication is
+ * optional.
+ *
+ * Returns: a new #TeplActionInfoStore.
+ * Since: 2.0
+ */
+TeplActionInfoStore *
+tepl_action_info_store_new (GtkApplication *application)
+{
+       g_return_val_if_fail (application == NULL || GTK_IS_APPLICATION (application), NULL);
+
+       return g_object_new (TEPL_TYPE_ACTION_INFO_STORE,
+                            "application", application,
+                            NULL);
+}
+
+/**
+ * tepl_action_info_store_get_application:
+ * @store: a #TeplActionInfoStore.
+ *
+ * Returns: (transfer none) (nullable): the associated #GtkApplication, or
+ * %NULL.
+ */
+GtkApplication *
+tepl_action_info_store_get_application (TeplActionInfoStore *store)
+{
+       g_return_val_if_fail (TEPL_IS_ACTION_INFO_STORE (store), NULL);
+
+       return store->priv->app;
+}
+
+/**
+ * tepl_action_info_store_add:
+ * @store: a #TeplActionInfoStore.
+ * @info: a #TeplActionInfo.
+ *
+ * Inserts @info into @store and into the #TeplActionInfoCentralStore. Both the
+ * @store and central store must <emphasis>not</emphasis> already contain a
+ * #TeplActionInfo with the same action name. The stores take their own
+ * reference on @info.
+ *
+ * Since: 2.0
+ */
+void
+tepl_action_info_store_add (TeplActionInfoStore *store,
+                           TeplActionInfo      *info)
+{
+       const gchar *action_name;
+       TeplActionInfoCentralStore *central_store;
+
+       g_return_if_fail (TEPL_IS_ACTION_INFO_STORE (store));
+       g_return_if_fail (info != NULL);
+
+       action_name = tepl_action_info_get_action_name (info);
+       g_return_if_fail (action_name != NULL);
+
+       if (g_hash_table_lookup (store->priv->hash_table, action_name) != NULL)
+       {
+               g_warning ("%s(): the TeplActionInfoStore already contains a TeplActionInfo "
+                          "with the action name “%s”.",
+                          G_STRFUNC,
+                          action_name);
+               return;
+       }
+
+       g_hash_table_insert (store->priv->hash_table,
+                            g_strdup (action_name),
+                            tepl_action_info_ref (info));
+
+       central_store = tepl_action_info_central_store_get_instance ();
+       _tepl_action_info_central_store_add (central_store, info);
+}
+
+/**
+ * tepl_action_info_store_add_entries:
+ * @store: a #TeplActionInfoStore.
+ * @entries: (array length=n_entries) (element-type TeplActionInfoEntry): a
+ * pointer to the first item in an array of #TeplActionInfoEntry structs.
+ * @n_entries: the length of @entries, or -1 if @entries is %NULL-terminated.
+ * @translation_domain: (nullable): a gettext domain, or %NULL.
+ *
+ * Calls tepl_action_info_store_add() for each entry.
+ *
+ * If @translation_domain is not %NULL, g_dgettext() is used to translate the
+ * @label and @tooltip of each entry before setting them to the #TeplActionInfo.
+ *
+ * An API similar to g_action_map_add_action_entries().
+ *
+ * Since: 2.0
+ */
+void
+tepl_action_info_store_add_entries (TeplActionInfoStore       *store,
+                                   const TeplActionInfoEntry *entries,
+                                   gint                       n_entries,
+                                   const gchar               *translation_domain)
+{
+       gint i;
+
+       g_return_if_fail (TEPL_IS_ACTION_INFO_STORE (store));
+       g_return_if_fail (n_entries >= -1);
+       g_return_if_fail (entries != NULL || n_entries == 0);
+
+       for (i = 0; n_entries == -1 ? entries[i].action_name != NULL : i < n_entries; i++)
+       {
+               TeplActionInfo *info;
+
+               info = tepl_action_info_new_from_entry (&entries[i], translation_domain);
+               tepl_action_info_store_add (store, info);
+               tepl_action_info_unref (info);
+       }
+}
+
+/**
+ * tepl_action_info_store_lookup:
+ * @store: a #TeplActionInfoStore.
+ * @action_name: an action name.
+ *
+ * Returns: (transfer none): the found #TeplActionInfo, or %NULL.
+ * Since: 2.0
+ */
+const TeplActionInfo *
+tepl_action_info_store_lookup (TeplActionInfoStore *store,
+                              const gchar         *action_name)
+{
+       g_return_val_if_fail (TEPL_IS_ACTION_INFO_STORE (store), NULL);
+       g_return_val_if_fail (action_name != NULL, NULL);
+
+       return g_hash_table_lookup (store->priv->hash_table, action_name);
+}
+
+/**
+ * tepl_action_info_store_create_menu_item:
+ * @store: a #TeplActionInfoStore.
+ * @action_name: an action name.
+ *
+ * Creates a new #GtkMenuItem for @action_name. The @store must contain a
+ * #TeplActionInfo for @action_name.
+ *
+ * gtk_actionable_set_action_name() is called on the menu item with
+ * @action_name. The label is set with the #GtkMenuItem:use-underline property
+ * enabled. The first accelerator is set to the #GtkAccelLabel of the menu item.
+ * The icon is set. And the tooltip is set with
+ * tepl_menu_item_set_long_description().
+ *
+ * If #TeplActionInfoStore:application is non-%NULL, this function also calls
+ * gtk_application_set_accels_for_action() with the accelerators returned by
+ * tepl_action_info_get_accels() (this will erase previously set accelerators
+ * for that action, if any).
+ *
+ * Returns: (transfer floating): a new #GtkMenuItem for @action_name.
+ * Since: 2.0
+ */
+GtkWidget *
+tepl_action_info_store_create_menu_item (TeplActionInfoStore *store,
+                                        const gchar         *action_name)
+{
+       GtkMenuItem *menu_item;
+       TeplActionInfo *action_info;
+       const gchar * const *accels;
+       const gchar *icon_name;
+       const gchar *tooltip;
+
+       g_return_val_if_fail (TEPL_IS_ACTION_INFO_STORE (store), NULL);
+       g_return_val_if_fail (action_name != NULL, NULL);
+
+       action_info = g_hash_table_lookup (store->priv->hash_table, action_name);
+
+       if (action_info == NULL)
+       {
+               g_warning ("%s(): action name '%s' not found.",
+                          G_STRFUNC,
+                          action_name);
+
+               return NULL;
+       }
+
+       menu_item = GTK_MENU_ITEM (gtk_menu_item_new ());
+
+       gtk_actionable_set_action_name (GTK_ACTIONABLE (menu_item), action_name);
+
+       gtk_menu_item_set_use_underline (menu_item, TRUE);
+       gtk_menu_item_set_label (menu_item, tepl_action_info_get_label (action_info));
+
+       /* Set accel before setting icon, because
+        * tepl_menu_item_set_icon_name() adds a GtkBox.
+        */
+       accels = tepl_action_info_get_accels (action_info);
+       if (accels != NULL && accels[0] != NULL)
+       {
+               guint accel_key;
+               GdkModifierType accel_mods;
+
+               gtk_accelerator_parse (accels[0], &accel_key, &accel_mods);
+
+               if (accel_key != 0 || accel_mods != 0)
+               {
+                       GtkWidget *child;
+
+                       child = gtk_bin_get_child (GTK_BIN (menu_item));
+
+                       gtk_accel_label_set_accel (GTK_ACCEL_LABEL (child),
+                                                  accel_key,
+                                                  accel_mods);
+               }
+       }
+
+       icon_name = tepl_action_info_get_icon_name (action_info);
+       if (icon_name != NULL)
+       {
+               tepl_menu_item_set_icon_name (menu_item, icon_name);
+       }
+
+       tooltip = tepl_action_info_get_tooltip (action_info);
+       if (tooltip != NULL)
+       {
+               tepl_menu_item_set_long_description (menu_item, tooltip);
+       }
+
+       if (store->priv->app != NULL)
+       {
+               gtk_application_set_accels_for_action (store->priv->app,
+                                                      action_name,
+                                                      accels);
+       }
+
+       _tepl_action_info_set_used (action_info);
+
+       return GTK_WIDGET (menu_item);
+}
+
+static void
+check_used_cb (gpointer key,
+              gpointer value,
+              gpointer user_data)
+{
+       const gchar *action_name = key;
+       const TeplActionInfo *action_info = value;
+
+       if (!_tepl_action_info_get_used (action_info))
+       {
+               g_warning ("TeplActionInfo with action_name='%s' has not been used.",
+                          action_name);
+       }
+}
+
+/**
+ * tepl_action_info_store_check_all_used:
+ * @store: a #TeplActionInfoStore.
+ *
+ * Checks that all #TeplActionInfo's of @store have been used by
+ * tepl_action_info_store_create_menu_item(). If not, a warning is printed and
+ * might indicate dead code.
+ *
+ * You probably want to call this function on the store returned by
+ * tepl_application_get_app_action_info_store(). But it can also be useful for a
+ * store provided by a library, to easily see which actions you don't use.
+ *
+ * Since: 2.0
+ */
+void
+tepl_action_info_store_check_all_used (TeplActionInfoStore *store)
+{
+       g_return_if_fail (TEPL_IS_ACTION_INFO_STORE (store));
+
+       g_hash_table_foreach (store->priv->hash_table,
+                             check_used_cb,
+                             NULL);
+}
diff --git a/amtk/tepl-action-info-store.h b/amtk/tepl-action-info-store.h
new file mode 100644
index 0000000..437019e
--- /dev/null
+++ b/amtk/tepl-action-info-store.h
@@ -0,0 +1,80 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2017 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEPL_ACTION_INFO_STORE_H
+#define TEPL_ACTION_INFO_STORE_H
+
+#if !defined (TEPL_H_INSIDE) && !defined (TEPL_COMPILATION)
+#error "Only <tepl/tepl.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+#include <tepl/tepl-types.h>
+
+G_BEGIN_DECLS
+
+#define TEPL_TYPE_ACTION_INFO_STORE             (tepl_action_info_store_get_type ())
+#define TEPL_ACTION_INFO_STORE(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
TEPL_TYPE_ACTION_INFO_STORE, TeplActionInfoStore))
+#define TEPL_ACTION_INFO_STORE_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), 
TEPL_TYPE_ACTION_INFO_STORE, TeplActionInfoStoreClass))
+#define TEPL_IS_ACTION_INFO_STORE(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
TEPL_TYPE_ACTION_INFO_STORE))
+#define TEPL_IS_ACTION_INFO_STORE_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), 
TEPL_TYPE_ACTION_INFO_STORE))
+#define TEPL_ACTION_INFO_STORE_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), 
TEPL_TYPE_ACTION_INFO_STORE, TeplActionInfoStoreClass))
+
+typedef struct _TeplActionInfoStoreClass    TeplActionInfoStoreClass;
+typedef struct _TeplActionInfoStorePrivate  TeplActionInfoStorePrivate;
+
+struct _TeplActionInfoStore
+{
+       GObject parent;
+
+       TeplActionInfoStorePrivate *priv;
+};
+
+struct _TeplActionInfoStoreClass
+{
+       GObjectClass parent_class;
+
+       gpointer padding[12];
+};
+
+GType                  tepl_action_info_store_get_type                 (void) G_GNUC_CONST;
+
+TeplActionInfoStore *  tepl_action_info_store_new                      (GtkApplication *application);
+
+GtkApplication *       tepl_action_info_store_get_application          (TeplActionInfoStore *store);
+
+void                   tepl_action_info_store_add                      (TeplActionInfoStore *store,
+                                                                        TeplActionInfo      *info);
+
+void                   tepl_action_info_store_add_entries              (TeplActionInfoStore       *store,
+                                                                        const TeplActionInfoEntry *entries,
+                                                                        gint                       n_entries,
+                                                                        const gchar               
*translation_domain);
+
+const TeplActionInfo * tepl_action_info_store_lookup                   (TeplActionInfoStore *store,
+                                                                        const gchar         *action_name);
+
+GtkWidget *            tepl_action_info_store_create_menu_item         (TeplActionInfoStore *store,
+                                                                        const gchar         *action_name);
+
+void                   tepl_action_info_store_check_all_used           (TeplActionInfoStore *store);
+
+G_END_DECLS
+
+#endif /* TEPL_ACTION_INFO_STORE_H */
diff --git a/amtk/tepl-action-info.c b/amtk/tepl-action-info.c
new file mode 100644
index 0000000..af9d7bd
--- /dev/null
+++ b/amtk/tepl-action-info.c
@@ -0,0 +1,405 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2017 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "tepl-action-info.h"
+#include <glib/gi18n-lib.h>
+#include "tepl-utils.h"
+
+/**
+ * SECTION:action-info
+ * @Short_description: GAction information
+ * @Title: TeplActionInfo
+ * @See_also: #TeplActionInfoStore
+ *
+ * A #TeplActionInfo instance contains a set of information about a #GAction.
+ * Those pieces of information are useful to create UI elements that trigger the
+ * #GAction, for example a menu item or a toolbar item.
+ *
+ * When writing an XML file to create a #GMenu, with the format understood by
+ * #GtkBuilder (see the class description of #GtkApplicationWindow), the
+ * information in the XML file can be used only to create a #GMenu. The initial
+ * goal with #TeplActionInfo and its related classes is to encode the
+ * information just once, and be able to create both a menu and a toolbar easily
+ * (to have a traditional user interface).
+ */
+
+struct _TeplActionInfo
+{
+       gchar *action_name;
+       gchar *icon_name;
+       gchar *label;
+       gchar *tooltip;
+
+       /* Must never be NULL, must be a NULL-terminated array. This way, it
+        * can be used directly as an argument to
+        * gtk_application_set_accels_for_action().
+        */
+       gchar **accels;
+
+       gint ref_count;
+
+       guint used : 1;
+};
+
+static void _tepl_action_info_free (TeplActionInfo *info);
+
+G_DEFINE_BOXED_TYPE (TeplActionInfo, tepl_action_info,
+                    tepl_action_info_copy,
+                    _tepl_action_info_free)
+
+static void
+_tepl_action_info_free (TeplActionInfo *info)
+{
+       if (info != NULL)
+       {
+               g_free (info->action_name);
+               g_free (info->icon_name);
+               g_free (info->label);
+               g_free (info->tooltip);
+               g_strfreev (info->accels);
+
+               g_free (info);
+       }
+}
+
+/**
+ * tepl_action_info_new:
+ *
+ * Returns: a new #TeplActionInfo.
+ * Since: 2.0
+ */
+TeplActionInfo *
+tepl_action_info_new (void)
+{
+       TeplActionInfo *info;
+
+       info = g_new0 (TeplActionInfo, 1);
+       info->accels = g_malloc0 (sizeof (gchar *));
+       info->ref_count = 1;
+
+       return info;
+}
+
+/**
+ * tepl_action_info_new_from_entry:
+ * @info_entry: a #TeplActionInfoEntry.
+ * @translation_domain: (nullable): a gettext domain, or %NULL.
+ *
+ * Creates a new #TeplActionInfo from a #TeplActionInfoEntry.
+ *
+ * If @translation_domain is not %NULL, g_dgettext() is used to translate the
+ * @label and @tooltip before setting them to the #TeplActionInfo.
+ *
+ * Returns: a new #TeplActionInfo.
+ * Since: 2.0
+ */
+TeplActionInfo *
+tepl_action_info_new_from_entry (const TeplActionInfoEntry *info_entry,
+                                const gchar               *translation_domain)
+{
+       TeplActionInfo *info;
+
+       info = tepl_action_info_new ();
+       info->action_name = g_strdup (info_entry->action_name);
+       info->icon_name = g_strdup (info_entry->icon_name);
+
+       if (translation_domain != NULL)
+       {
+               info->label = g_strdup (g_dgettext (translation_domain, info_entry->label));
+               info->tooltip = g_strdup (g_dgettext (translation_domain, info_entry->tooltip));
+       }
+       else
+       {
+               info->label = g_strdup (info_entry->label);
+               info->tooltip = g_strdup (info_entry->tooltip);
+       }
+
+       if (info_entry->accel != NULL)
+       {
+               g_strfreev (info->accels);
+
+               info->accels = g_malloc (2 * sizeof (gchar *));
+               info->accels[0] = g_strdup (info_entry->accel);
+               info->accels[1] = NULL;
+       }
+
+       return info;
+}
+
+/**
+ * tepl_action_info_ref:
+ * @info: a #TeplActionInfo.
+ *
+ * Increments the reference count of @info by one.
+ *
+ * Returns: the passed in @info.
+ * Since: 2.0
+ */
+TeplActionInfo *
+tepl_action_info_ref (TeplActionInfo *info)
+{
+       g_return_val_if_fail (info != NULL, NULL);
+
+       info->ref_count++;
+
+       return info;
+}
+
+/**
+ * tepl_action_info_unref:
+ * @info: a #TeplActionInfo.
+ *
+ * Decrements the reference count of @info by one. If the reference count drops
+ * to 0, @info is freed.
+ *
+ * Since: 2.0
+ */
+void
+tepl_action_info_unref (TeplActionInfo *info)
+{
+       g_return_if_fail (info != NULL);
+
+       info->ref_count--;
+
+       if (info->ref_count == 0)
+       {
+               _tepl_action_info_free (info);
+       }
+}
+
+/**
+ * tepl_action_info_copy:
+ * @info: a #TeplActionInfo.
+ *
+ * Returns: (transfer full): a copy of @info. The copy will have a reference
+ * count of one.
+ * Since: 2.0
+ */
+TeplActionInfo *
+tepl_action_info_copy (const TeplActionInfo *info)
+{
+       TeplActionInfo *new_info;
+
+       g_return_val_if_fail (info != NULL, NULL);
+
+       new_info = tepl_action_info_new ();
+
+       new_info->action_name = g_strdup (info->action_name);
+       new_info->icon_name = g_strdup (info->icon_name);
+       new_info->label = g_strdup (info->label);
+       new_info->tooltip = g_strdup (info->tooltip);
+
+       tepl_action_info_set_accels (new_info, (const gchar * const *)info->accels);
+
+       return new_info;
+}
+
+/**
+ * tepl_action_info_get_action_name:
+ * @info: a #TeplActionInfo.
+ *
+ * Returns: (nullable): the action name, or %NULL. Example: `"win.save"`.
+ * Since: 2.0
+ */
+const gchar *
+tepl_action_info_get_action_name (const TeplActionInfo *info)
+{
+       g_return_val_if_fail (info != NULL, NULL);
+
+       return info->action_name;
+}
+
+/**
+ * tepl_action_info_set_action_name:
+ * @info: a #TeplActionInfo.
+ * @action_name: the action name.
+ *
+ * Sets the action name, for example `"win.save"`.
+ *
+ * Since: 2.0
+ */
+void
+tepl_action_info_set_action_name (TeplActionInfo *info,
+                                 const gchar    *action_name)
+{
+       g_return_if_fail (info != NULL);
+       g_return_if_fail (action_name != NULL);
+
+       g_free (info->action_name);
+       info->action_name = g_strdup (action_name);
+}
+
+/**
+ * tepl_action_info_get_icon_name:
+ * @info: a #TeplActionInfo.
+ *
+ * Returns: (nullable): the icon name, or %NULL.
+ * Since: 2.0
+ */
+const gchar *
+tepl_action_info_get_icon_name (const TeplActionInfo *info)
+{
+       g_return_val_if_fail (info != NULL, NULL);
+
+       return info->icon_name;
+}
+
+/**
+ * tepl_action_info_set_icon_name:
+ * @info: a #TeplActionInfo.
+ * @icon_name: (nullable): the icon name, or %NULL.
+ *
+ * Since: 2.0
+ */
+void
+tepl_action_info_set_icon_name (TeplActionInfo *info,
+                               const gchar    *icon_name)
+{
+       g_return_if_fail (info != NULL);
+
+       g_free (info->icon_name);
+       info->icon_name = g_strdup (icon_name);
+}
+
+/**
+ * tepl_action_info_get_label:
+ * @info: a #TeplActionInfo.
+ *
+ * Returns: (nullable): the label (i.e. a short description), or %NULL.
+ * Since: 2.0
+ */
+const gchar *
+tepl_action_info_get_label (const TeplActionInfo *info)
+{
+       g_return_val_if_fail (info != NULL, NULL);
+
+       return info->label;
+}
+
+/**
+ * tepl_action_info_set_label:
+ * @info: a #TeplActionInfo.
+ * @label: (nullable): the label (i.e. a short description), or %NULL.
+ *
+ * Since: 2.0
+ */
+void
+tepl_action_info_set_label (TeplActionInfo *info,
+                           const gchar    *label)
+{
+       g_return_if_fail (info != NULL);
+
+       g_free (info->label);
+       info->label = g_strdup (label);
+}
+
+/**
+ * tepl_action_info_get_tooltip:
+ * @info: a #TeplActionInfo.
+ *
+ * Returns: (nullable): the tooltip (i.e. a long description), or %NULL.
+ * Since: 2.0
+ */
+const gchar *
+tepl_action_info_get_tooltip (const TeplActionInfo *info)
+{
+       g_return_val_if_fail (info != NULL, NULL);
+
+       return info->tooltip;
+}
+
+/**
+ * tepl_action_info_set_tooltip:
+ * @info: a #TeplActionInfo.
+ * @tooltip: (nullable): the tooltip (i.e. a long description), or %NULL.
+ *
+ * Since: 2.0
+ */
+void
+tepl_action_info_set_tooltip (TeplActionInfo *info,
+                             const gchar    *tooltip)
+{
+       g_return_if_fail (info != NULL);
+
+       g_free (info->tooltip);
+       info->tooltip = g_strdup (tooltip);
+}
+
+/**
+ * tepl_action_info_get_accels:
+ * @info: a #TeplActionInfo.
+ *
+ * Returns the accelerators. This function never returns %NULL, it always
+ * returns a %NULL-terminated array, to be suitable for
+ * gtk_application_set_accels_for_action().
+ *
+ * Returns: (transfer none) (array zero-terminated=1): a %NULL-terminated array
+ * of accelerators in the format understood by gtk_accelerator_parse().
+ * Since: 2.0
+ */
+const gchar * const *
+tepl_action_info_get_accels (const TeplActionInfo *info)
+{
+       g_return_val_if_fail (info != NULL, NULL);
+
+       g_assert (info->accels != NULL);
+
+       return (const gchar * const *)info->accels;
+}
+
+/**
+ * tepl_action_info_set_accels:
+ * @info: a #TeplActionInfo.
+ * @accels: (array zero-terminated=1): a %NULL-terminated array of accelerators
+ * in the format understood by gtk_accelerator_parse().
+ *
+ * A function similar to gtk_application_set_accels_for_action().
+ *
+ * @accels must not be %NULL, it must be a %NULL-terminated array, to be
+ * consistent with gtk_application_set_accels_for_action().
+ *
+ * Since: 2.0
+ */
+void
+tepl_action_info_set_accels (TeplActionInfo      *info,
+                            const gchar * const *accels)
+{
+       g_return_if_fail (info != NULL);
+       g_return_if_fail (accels != NULL);
+
+       g_strfreev (info->accels);
+       info->accels = _tepl_utils_strv_copy (accels);
+}
+
+gboolean
+_tepl_action_info_get_used (const TeplActionInfo *info)
+{
+       g_return_val_if_fail (info != NULL, FALSE);
+
+       return info->used;
+}
+
+void
+_tepl_action_info_set_used (TeplActionInfo *info)
+{
+       g_return_if_fail (info != NULL);
+
+       info->used = TRUE;
+}
diff --git a/amtk/tepl-action-info.h b/amtk/tepl-action-info.h
new file mode 100644
index 0000000..6f46d63
--- /dev/null
+++ b/amtk/tepl-action-info.h
@@ -0,0 +1,110 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2017 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEPL_ACTION_INFO_H
+#define TEPL_ACTION_INFO_H
+
+#if !defined (TEPL_H_INSIDE) && !defined (TEPL_COMPILATION)
+#error "Only <tepl/tepl.h> can be included directly."
+#endif
+
+#include <glib-object.h>
+#include <tepl/tepl-types.h>
+
+G_BEGIN_DECLS
+
+#define TEPL_TYPE_ACTION_INFO (tepl_action_info_get_type ())
+
+/**
+ * TeplActionInfoEntry:
+ * @action_name: the action name.
+ * @icon_name: the icon name, or %NULL.
+ * @label: the label (i.e. a short description), or %NULL.
+ * @accel: the accelerator, in the format understood by gtk_accelerator_parse().
+ * Or %NULL.
+ * @tooltip: the tooltip (i.e. a long description), or %NULL.
+ *
+ * This struct defines a set of information for a single action. It is for use
+ * with tepl_action_info_store_add_entries().
+ *
+ * Like #GActionEntry, it is permissible to use an incomplete initialiser in
+ * order to leave some of the later values as %NULL. Additional optional fields
+ * may be added in the future.
+ *
+ * Since: 2.0
+ */
+struct _TeplActionInfoEntry
+{
+       const gchar *action_name;
+       const gchar *icon_name;
+       const gchar *label;
+       const gchar *accel;
+       const gchar *tooltip;
+
+       /*< private >*/
+       gpointer padding[3];
+};
+
+GType                  tepl_action_info_get_type               (void) G_GNUC_CONST;
+
+TeplActionInfo *       tepl_action_info_new                    (void);
+
+TeplActionInfo *       tepl_action_info_new_from_entry         (const TeplActionInfoEntry *info_entry,
+                                                                const gchar               
*translation_domain);
+
+TeplActionInfo *       tepl_action_info_ref                    (TeplActionInfo *info);
+
+void                   tepl_action_info_unref                  (TeplActionInfo *info);
+
+TeplActionInfo *       tepl_action_info_copy                   (const TeplActionInfo *info);
+
+const gchar *          tepl_action_info_get_action_name        (const TeplActionInfo *info);
+
+void                   tepl_action_info_set_action_name        (TeplActionInfo *info,
+                                                                const gchar    *action_name);
+
+const gchar *          tepl_action_info_get_icon_name          (const TeplActionInfo *info);
+
+void                   tepl_action_info_set_icon_name          (TeplActionInfo *info,
+                                                                const gchar    *icon_name);
+
+const gchar *          tepl_action_info_get_label              (const TeplActionInfo *info);
+
+void                   tepl_action_info_set_label              (TeplActionInfo *info,
+                                                                const gchar    *label);
+
+const gchar *          tepl_action_info_get_tooltip            (const TeplActionInfo *info);
+
+void                   tepl_action_info_set_tooltip            (TeplActionInfo *info,
+                                                                const gchar    *tooltip);
+
+const gchar * const *  tepl_action_info_get_accels             (const TeplActionInfo *info);
+
+void                   tepl_action_info_set_accels             (TeplActionInfo      *info,
+                                                                const gchar * const *accels);
+
+G_GNUC_INTERNAL
+gboolean               _tepl_action_info_get_used              (const TeplActionInfo *info);
+
+G_GNUC_INTERNAL
+void                   _tepl_action_info_set_used              (TeplActionInfo *info);
+
+G_END_DECLS
+
+#endif  /* TEPL_ACTION_INFO_H */
diff --git a/amtk/tepl-action-map.c b/amtk/tepl-action-map.c
new file mode 100644
index 0000000..7ffc046
--- /dev/null
+++ b/amtk/tepl-action-map.c
@@ -0,0 +1,105 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2017 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tepl-action-map.h"
+
+/**
+ * SECTION:action-map
+ * @Short_description: GActionMap wrapper functions
+ * @Title: TeplActionMap
+ *
+ * #GActionMap wrapper functions.
+ */
+
+static void
+check_dups_in_array (const GActionEntry *entries,
+                    const gchar        *action_name,
+                    gint                action_num)
+{
+       gint i;
+
+       for (i = 0; i < action_num; i++)
+       {
+               const GActionEntry *entry = &entries[i];
+
+               if (g_strcmp0 (action_name, entry->name) == 0)
+               {
+                       g_warning ("tepl_action_map_add_action_entries_check_dups(): "
+                                  "the GActionEntry array contains duplicated entries for the action name 
'%s'. "
+                                  "The first one will be dropped from the GActionMap.",
+                                  action_name);
+                       return;
+               }
+       }
+}
+
+/**
+ * tepl_action_map_add_action_entries_check_dups:
+ * @action_map: a #GActionMap.
+ * @entries: (array length=n_entries) (element-type GActionEntry): a pointer to
+ *           the first item in an array of #GActionEntry structs.
+ * @n_entries: the length of @entries, or -1 if @entries is %NULL-terminated.
+ * @user_data: the user data for signal connections.
+ *
+ * A wrapper function for g_action_map_add_action_entries() that checks
+ * duplicates.
+ *
+ * This function first checks - for each entry - that the @action_map doesn't
+ * already contain a #GAction with the same name. A warning is printed if an old
+ * action will be dropped. In any case, it then calls
+ * g_action_map_add_action_entries() with the same arguments as passed to this
+ * function.
+ *
+ * This function also checks if there are duplicates in the @entries array
+ * itself.
+ *
+ * Since: 2.0
+ */
+void
+tepl_action_map_add_action_entries_check_dups (GActionMap         *action_map,
+                                              const GActionEntry *entries,
+                                              gint                n_entries,
+                                              gpointer            user_data)
+{
+       gint i;
+
+       g_return_if_fail (G_IS_ACTION_MAP (action_map));
+       g_return_if_fail (n_entries >= -1);
+       g_return_if_fail (entries != NULL || n_entries == 0);
+
+       for (i = 0; n_entries == -1 ? entries[i].name != NULL : i < n_entries; i++)
+       {
+               const GActionEntry *entry = &entries[i];
+
+               if (g_action_map_lookup_action (action_map, entry->name) != NULL)
+               {
+                       g_warning ("%s(): the GActionMap already contains a GAction with the name '%s'. "
+                                  "The old GAction will be dropped from the GActionMap.",
+                                  G_STRFUNC,
+                                  entry->name);
+               }
+
+               check_dups_in_array (entries, entry->name, i);
+       }
+
+       g_action_map_add_action_entries (action_map,
+                                        entries,
+                                        n_entries,
+                                        user_data);
+}
diff --git a/amtk/tepl-action-map.h b/amtk/tepl-action-map.h
new file mode 100644
index 0000000..6252ca1
--- /dev/null
+++ b/amtk/tepl-action-map.h
@@ -0,0 +1,38 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2017 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEPL_ACTION_MAP_H
+#define TEPL_ACTION_MAP_H
+
+#if !defined (TEPL_H_INSIDE) && !defined (TEPL_COMPILATION)
+#error "Only <tepl/tepl.h> can be included directly."
+#endif
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+void   tepl_action_map_add_action_entries_check_dups   (GActionMap         *action_map,
+                                                        const GActionEntry *entries,
+                                                        gint                n_entries,
+                                                        gpointer            user_data);
+
+G_END_DECLS
+
+#endif /* TEPL_ACTION_MAP_H */
diff --git a/amtk/tepl-menu-item.c b/amtk/tepl-menu-item.c
new file mode 100644
index 0000000..d095ceb
--- /dev/null
+++ b/amtk/tepl-menu-item.c
@@ -0,0 +1,174 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2017 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tepl-menu-item.h"
+
+/**
+ * SECTION:menu-item
+ * @Short_description: GtkMenuItem functions
+ * @Title: TeplMenuItem
+ *
+ * #GtkMenuItem functions.
+ */
+
+#define LONG_DESCRIPTION_KEY "tepl-menu-item-long-description-key"
+
+/**
+ * tepl_menu_item_get_long_description:
+ * @menu_item: a #GtkMenuItem.
+ *
+ * Returns: (nullable): the long description of @menu_item, previously set with
+ *   tepl_menu_item_set_long_description().
+ * Since: 2.0
+ */
+const gchar *
+tepl_menu_item_get_long_description (GtkMenuItem *menu_item)
+{
+       g_return_val_if_fail (GTK_IS_MENU_ITEM (menu_item), NULL);
+
+       return g_object_get_data (G_OBJECT (menu_item), LONG_DESCRIPTION_KEY);
+}
+
+/**
+ * tepl_menu_item_set_long_description:
+ * @menu_item: a #GtkMenuItem.
+ * @long_description: (nullable): the long description, or %NULL to unset it.
+ *
+ * Sets the long description of @menu_item. A possible use-case is to display it
+ * in a #GtkStatusbar, or as a tooltip.
+ *
+ * Since: 2.0
+ */
+void
+tepl_menu_item_set_long_description (GtkMenuItem *menu_item,
+                                    const gchar *long_description)
+{
+       g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
+
+       g_object_set_data_full (G_OBJECT (menu_item),
+                               LONG_DESCRIPTION_KEY,
+                               g_strdup (long_description),
+                               g_free);
+}
+
+/**
+ * tepl_menu_item_set_icon_name:
+ * @item: a #GtkMenuItem.
+ * @icon_name: an icon name.
+ *
+ * Sets an icon to a #GtkMenuItem.
+ *
+ * If the child widget of @item is already a #GtkBox, all #GtkImage widgets
+ * inside that box are first destroyed. A #GtkImage for @icon_name is then
+ * inserted to the box.
+ *
+ * If the child widget of @item is not a #GtkBox (it's usually the
+ * #GtkAccelLabel), it is replaced by a new #GtkBox and the initial child widget
+ * is inserted to the #GtkBox, alongside the icon.
+ *
+ * As a consequence, if you want to call functions on the #GtkAccelLabel, it's
+ * easier to do it before calling this function.
+ *
+ * Since: 2.0
+ */
+/* Based on gtk_model_menu_item_set_icon() from gtkmodelmenuitem.c (private
+ * GTK+ class).
+ * Copyright 2011, 2013 Canonical Limited
+ */
+void
+tepl_menu_item_set_icon_name (GtkMenuItem *item,
+                             const gchar *icon_name)
+{
+       GtkWidget *child;
+
+       g_return_if_fail (GTK_IS_MENU_ITEM (item));
+
+       child = gtk_bin_get_child (GTK_BIN (item));
+
+       /* There are only three possibilities here:
+        *
+        *   - no child
+        *   - accel label child
+        *   - already a box
+        *
+        * Handle the no-child case by having GtkMenuItem create the accel
+        * label, then we will only have two possible cases.
+        */
+       if (child == NULL)
+       {
+               gtk_menu_item_get_label (item);
+               child = gtk_bin_get_child (GTK_BIN (item));
+               g_return_if_fail (GTK_IS_LABEL (child));
+       }
+
+       /* If it is a box, make sure there are no images inside of it already. */
+       if (GTK_IS_BOX (child))
+       {
+               GList *children;
+
+               children = gtk_container_get_children (GTK_CONTAINER (child));
+               while (children != NULL)
+               {
+                       if (GTK_IS_IMAGE (children->data))
+                       {
+                               gtk_widget_destroy (children->data);
+                       }
+
+                       children = g_list_delete_link (children, children);
+               }
+       }
+       else
+       {
+               GtkWidget *box;
+
+               if (icon_name == NULL)
+               {
+                       return;
+               }
+
+               box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+
+               /* Reparent the child without destroying it */
+               g_object_ref (child);
+               gtk_container_remove (GTK_CONTAINER (item), child);
+               gtk_box_pack_end (GTK_BOX (box), child, TRUE, TRUE, 0);
+               g_object_unref (child);
+
+               gtk_container_add (GTK_CONTAINER (item), box);
+               gtk_widget_show (box);
+
+               /* Now we have a box */
+               child = box;
+       }
+
+       g_assert (GTK_IS_BOX (child));
+
+       /* child is now a box containing a label and no image. Add the icon,
+        * if appropriate.
+        */
+       if (icon_name != NULL)
+       {
+               GtkWidget *image;
+
+               image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
+               gtk_image_set_pixel_size (GTK_IMAGE (image), 16);
+               gtk_box_pack_start (GTK_BOX (child), image, FALSE, FALSE, 0);
+               gtk_widget_show (image);
+       }
+}
diff --git a/amtk/tepl-menu-item.h b/amtk/tepl-menu-item.h
new file mode 100644
index 0000000..ebb371a
--- /dev/null
+++ b/amtk/tepl-menu-item.h
@@ -0,0 +1,41 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2017 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEPL_MENU_ITEM_H
+#define TEPL_MENU_ITEM_H
+
+#if !defined (TEPL_H_INSIDE) && !defined (TEPL_COMPILATION)
+#error "Only <tepl/tepl.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+const gchar *  tepl_menu_item_get_long_description     (GtkMenuItem *menu_item);
+
+void           tepl_menu_item_set_long_description     (GtkMenuItem *menu_item,
+                                                        const gchar *long_description);
+
+void           tepl_menu_item_set_icon_name            (GtkMenuItem *item,
+                                                        const gchar *icon_name);
+
+G_END_DECLS
+
+#endif  /* TEPL_MENU_ITEM_H */
diff --git a/amtk/tepl-menu-shell.c b/amtk/tepl-menu-shell.c
new file mode 100644
index 0000000..ddf641f
--- /dev/null
+++ b/amtk/tepl-menu-shell.c
@@ -0,0 +1,413 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2016, 2017 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tepl-menu-shell.h"
+
+/**
+ * SECTION:menu-shell
+ * @Short_description: An extension of GtkMenuShell
+ * @Title: TeplMenuShell
+ *
+ * #TeplMenuShell extends the #GtkMenuShell abstract class with the
+ * #TeplMenuShell::menu-item-selected and #TeplMenuShell::menu-item-deselected
+ * convenience signals.
+ *
+ * One possible use-case is to push/pop longer descriptions of menu items to a
+ * #GtkStatusbar, exactly like
+ * tepl_application_window_connect_menu_to_statusbar() does.
+ */
+
+struct _TeplMenuShellPrivate
+{
+       GtkMenuShell *gtk_menu_shell;
+};
+
+enum
+{
+       PROP_0,
+       PROP_MENU_SHELL,
+       N_PROPERTIES
+};
+
+enum
+{
+       SIGNAL_MENU_ITEM_SELECTED,
+       SIGNAL_MENU_ITEM_DESELECTED,
+       N_SIGNALS
+};
+
+#define TEPL_MENU_SHELL_KEY "tepl-menu-shell-key"
+
+static GParamSpec *properties[N_PROPERTIES];
+static guint signals[N_SIGNALS];
+
+G_DEFINE_TYPE_WITH_PRIVATE (TeplMenuShell, tepl_menu_shell, G_TYPE_OBJECT)
+
+/* Prototypes */
+static void connect_menu_shell         (TeplMenuShell *tepl_menu_shell,
+                                        GtkMenuShell  *gtk_menu_shell);
+
+static void disconnect_menu_shell      (TeplMenuShell *tepl_menu_shell,
+                                        GtkMenuShell  *gtk_menu_shell);
+
+static void
+menu_item_select_cb (GtkMenuItem *menu_item,
+                    gpointer     user_data)
+{
+       TeplMenuShell *tepl_menu_shell = TEPL_MENU_SHELL (user_data);
+       GtkWidget *submenu;
+
+       submenu = gtk_menu_item_get_submenu (menu_item);
+
+       if (GTK_IS_MENU_SHELL (submenu))
+       {
+               connect_menu_shell (tepl_menu_shell, GTK_MENU_SHELL (submenu));
+       }
+
+       g_signal_emit (tepl_menu_shell,
+                      signals[SIGNAL_MENU_ITEM_SELECTED], 0,
+                      menu_item);
+}
+
+static void
+menu_item_deselect_cb (GtkMenuItem *menu_item,
+                      gpointer     user_data)
+{
+       TeplMenuShell *tepl_menu_shell = TEPL_MENU_SHELL (user_data);
+       GtkWidget *submenu;
+
+       submenu = gtk_menu_item_get_submenu (menu_item);
+
+       if (GTK_IS_MENU_SHELL (submenu))
+       {
+               disconnect_menu_shell (tepl_menu_shell, GTK_MENU_SHELL (submenu));
+       }
+
+       g_signal_emit (tepl_menu_shell,
+                      signals[SIGNAL_MENU_ITEM_DESELECTED], 0,
+                      menu_item);
+}
+
+static void
+connect_menu_item (TeplMenuShell *tepl_menu_shell,
+                  GtkMenuItem   *menu_item)
+{
+       g_signal_connect_object (menu_item,
+                                "select",
+                                G_CALLBACK (menu_item_select_cb),
+                                tepl_menu_shell,
+                                0);
+
+       g_signal_connect_object (menu_item,
+                                "deselect",
+                                G_CALLBACK (menu_item_deselect_cb),
+                                tepl_menu_shell,
+                                0);
+}
+
+static void
+disconnect_menu_item (TeplMenuShell *tepl_menu_shell,
+                     GtkMenuItem   *menu_item)
+{
+       g_signal_handlers_disconnect_by_func (menu_item,
+                                             menu_item_select_cb,
+                                             tepl_menu_shell);
+
+       g_signal_handlers_disconnect_by_func (menu_item,
+                                             menu_item_deselect_cb,
+                                             tepl_menu_shell);
+}
+
+static void
+insert_cb (GtkMenuShell *gtk_menu_shell,
+          GtkWidget    *child,
+          gint          position,
+          gpointer      user_data)
+{
+       TeplMenuShell *tepl_menu_shell = TEPL_MENU_SHELL (user_data);
+
+       if (GTK_IS_MENU_ITEM (child))
+       {
+               connect_menu_item (tepl_menu_shell, GTK_MENU_ITEM (child));
+       }
+}
+
+static void
+remove_cb (GtkContainer *container,
+          GtkWidget    *child,
+          gpointer      user_data)
+{
+       TeplMenuShell *tepl_menu_shell = TEPL_MENU_SHELL (user_data);
+
+       if (GTK_IS_MENU_ITEM (child))
+       {
+               disconnect_menu_item (tepl_menu_shell, GTK_MENU_ITEM (child));
+       }
+}
+
+static void
+connect_menu_shell (TeplMenuShell *tepl_menu_shell,
+                   GtkMenuShell  *gtk_menu_shell)
+{
+       GList *children;
+       GList *l;
+
+       children = gtk_container_get_children (GTK_CONTAINER (gtk_menu_shell));
+
+       for (l = children; l != NULL; l = l->next)
+       {
+               GtkMenuItem *menu_item = l->data;
+
+               if (GTK_IS_MENU_ITEM (menu_item))
+               {
+                       connect_menu_item (tepl_menu_shell, menu_item);
+               }
+       }
+
+       g_list_free (children);
+
+       g_signal_connect_object (gtk_menu_shell,
+                                "insert",
+                                G_CALLBACK (insert_cb),
+                                tepl_menu_shell,
+                                0);
+
+       g_signal_connect_object (gtk_menu_shell,
+                                "remove",
+                                G_CALLBACK (remove_cb),
+                                tepl_menu_shell,
+                                0);
+}
+
+static void
+disconnect_menu_shell (TeplMenuShell *tepl_menu_shell,
+                      GtkMenuShell  *gtk_menu_shell)
+{
+       GList *children;
+       GList *l;
+
+       children = gtk_container_get_children (GTK_CONTAINER (gtk_menu_shell));
+
+       for (l = children; l != NULL; l = l->next)
+       {
+               GtkMenuItem *menu_item = l->data;
+
+               if (GTK_IS_MENU_ITEM (menu_item))
+               {
+                       disconnect_menu_item (tepl_menu_shell, menu_item);
+               }
+       }
+
+       g_list_free (children);
+
+       g_signal_handlers_disconnect_by_func (gtk_menu_shell,
+                                             insert_cb,
+                                             tepl_menu_shell);
+
+       g_signal_handlers_disconnect_by_func (gtk_menu_shell,
+                                             remove_cb,
+                                             tepl_menu_shell);
+}
+
+static void
+set_menu_shell (TeplMenuShell *tepl_menu_shell,
+               GtkMenuShell  *gtk_menu_shell)
+{
+       g_assert (tepl_menu_shell->priv->gtk_menu_shell == NULL);
+       g_return_if_fail (GTK_IS_MENU_SHELL (gtk_menu_shell));
+
+       tepl_menu_shell->priv->gtk_menu_shell = gtk_menu_shell;
+       connect_menu_shell (tepl_menu_shell, gtk_menu_shell);
+}
+
+static void
+tepl_menu_shell_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+       TeplMenuShell *tepl_menu_shell = TEPL_MENU_SHELL (object);
+
+       switch (prop_id)
+       {
+               case PROP_MENU_SHELL:
+                       g_value_set_object (value, tepl_menu_shell_get_menu_shell (tepl_menu_shell));
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+tepl_menu_shell_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+       TeplMenuShell *tepl_menu_shell = TEPL_MENU_SHELL (object);
+
+       switch (prop_id)
+       {
+               case PROP_MENU_SHELL:
+                       set_menu_shell (tepl_menu_shell, g_value_get_object (value));
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+tepl_menu_shell_dispose (GObject *object)
+{
+       TeplMenuShell *tepl_menu_shell = TEPL_MENU_SHELL (object);
+
+       tepl_menu_shell->priv->gtk_menu_shell = NULL;
+
+       G_OBJECT_CLASS (tepl_menu_shell_parent_class)->dispose (object);
+}
+
+static void
+tepl_menu_shell_class_init (TeplMenuShellClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->get_property = tepl_menu_shell_get_property;
+       object_class->set_property = tepl_menu_shell_set_property;
+       object_class->dispose = tepl_menu_shell_dispose;
+
+       /**
+        * TeplMenuShell:menu-shell:
+        *
+        * The #GtkMenuShell.
+        *
+        * Since: 2.0
+        */
+       properties[PROP_MENU_SHELL] =
+               g_param_spec_object ("menu-shell",
+                                    "GtkMenuShell",
+                                    "",
+                                    GTK_TYPE_MENU_SHELL,
+                                    G_PARAM_READWRITE |
+                                    G_PARAM_CONSTRUCT_ONLY |
+                                    G_PARAM_STATIC_STRINGS);
+
+       g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+
+       /**
+        * TeplMenuShell::menu-item-selected:
+        * @tepl_menu_shell: the #TeplMenuShell emitting the signal.
+        * @menu_item: the #GtkMenuItem that has been selected.
+        *
+        * The ::menu-item-selected signal is emitted when the
+        * #GtkMenuItem::select signal is emitted on a #GtkMenuItem belonging
+        * (directly or indirectly through submenus) to @tepl_menu_shell.
+        *
+        * Since: 2.0
+        */
+       signals[SIGNAL_MENU_ITEM_SELECTED] =
+               g_signal_new ("menu-item-selected",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_FIRST,
+                             G_STRUCT_OFFSET (TeplMenuShellClass, menu_item_selected),
+                             NULL, NULL, NULL,
+                             G_TYPE_NONE,
+                             1, GTK_TYPE_MENU_ITEM);
+
+       /**
+        * TeplMenuShell::menu-item-deselected:
+        * @tepl_menu_shell: the #TeplMenuShell emitting the signal.
+        * @menu_item: the #GtkMenuItem that has been deselected.
+        *
+        * The ::menu-item-deselected signal is emitted when the
+        * #GtkMenuItem::deselect signal is emitted on a #GtkMenuItem belonging
+        * (directly or indirectly through submenus) to @tepl_menu_shell.
+        *
+        * Since: 2.0
+        */
+       signals[SIGNAL_MENU_ITEM_DESELECTED] =
+               g_signal_new ("menu-item-deselected",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_FIRST,
+                             G_STRUCT_OFFSET (TeplMenuShellClass, menu_item_deselected),
+                             NULL, NULL, NULL,
+                             G_TYPE_NONE,
+                             1, GTK_TYPE_MENU_ITEM);
+}
+
+static void
+tepl_menu_shell_init (TeplMenuShell *tepl_menu_shell)
+{
+       tepl_menu_shell->priv = tepl_menu_shell_get_instance_private (tepl_menu_shell);
+}
+
+/**
+ * tepl_menu_shell_get_from_gtk_menu_shell:
+ * @gtk_menu_shell: a #GtkMenuShell.
+ *
+ * Returns the #TeplMenuShell of @gtk_menu_shell. The returned object is
+ * guaranteed to be the same for the lifetime of @gtk_menu_shell.
+ *
+ * Returns: (transfer none): the #TeplMenuShell of @gtk_menu_shell.
+ * Since: 2.0
+ */
+TeplMenuShell *
+tepl_menu_shell_get_from_gtk_menu_shell (GtkMenuShell *gtk_menu_shell)
+{
+       TeplMenuShell *tepl_menu_shell;
+
+       g_return_val_if_fail (GTK_IS_MENU_SHELL (gtk_menu_shell), NULL);
+
+       tepl_menu_shell = g_object_get_data (G_OBJECT (gtk_menu_shell), TEPL_MENU_SHELL_KEY);
+
+       if (tepl_menu_shell == NULL)
+       {
+               tepl_menu_shell = g_object_new (TEPL_TYPE_MENU_SHELL,
+                                               "menu-shell", gtk_menu_shell,
+                                               NULL);
+
+               g_object_set_data_full (G_OBJECT (gtk_menu_shell),
+                                       TEPL_MENU_SHELL_KEY,
+                                       tepl_menu_shell,
+                                       g_object_unref);
+       }
+
+       g_return_val_if_fail (TEPL_IS_MENU_SHELL (tepl_menu_shell), NULL);
+       return tepl_menu_shell;
+}
+
+/**
+ * tepl_menu_shell_get_menu_shell:
+ * @tepl_menu_shell: a #TeplMenuShell.
+ *
+ * Returns: (transfer none): the #GtkMenuShell of @tepl_menu_shell.
+ * Since: 2.0
+ */
+GtkMenuShell *
+tepl_menu_shell_get_menu_shell (TeplMenuShell *tepl_menu_shell)
+{
+       g_return_val_if_fail (TEPL_IS_MENU_SHELL (tepl_menu_shell), NULL);
+
+       return tepl_menu_shell->priv->gtk_menu_shell;
+}
+
+/* ex:set ts=8 noet: */
diff --git a/amtk/tepl-menu-shell.h b/amtk/tepl-menu-shell.h
new file mode 100644
index 0000000..6059c9e
--- /dev/null
+++ b/amtk/tepl-menu-shell.h
@@ -0,0 +1,74 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2017 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEPL_MENU_SHELL_H
+#define TEPL_MENU_SHELL_H
+
+#if !defined (TEPL_H_INSIDE) && !defined (TEPL_COMPILATION)
+#error "Only <tepl/tepl.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+#include <tepl/tepl-types.h>
+
+G_BEGIN_DECLS
+
+#define TEPL_TYPE_MENU_SHELL             (tepl_menu_shell_get_type ())
+#define TEPL_MENU_SHELL(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEPL_TYPE_MENU_SHELL, 
TeplMenuShell))
+#define TEPL_MENU_SHELL_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), TEPL_TYPE_MENU_SHELL, 
TeplMenuShellClass))
+#define TEPL_IS_MENU_SHELL(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEPL_TYPE_MENU_SHELL))
+#define TEPL_IS_MENU_SHELL_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), TEPL_TYPE_MENU_SHELL))
+#define TEPL_MENU_SHELL_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), TEPL_TYPE_MENU_SHELL, 
TeplMenuShellClass))
+
+typedef struct _TeplMenuShellClass    TeplMenuShellClass;
+typedef struct _TeplMenuShellPrivate  TeplMenuShellPrivate;
+
+struct _TeplMenuShell
+{
+       GObject parent;
+
+       TeplMenuShellPrivate *priv;
+};
+
+struct _TeplMenuShellClass
+{
+       GObjectClass parent_class;
+
+       /* Signals */
+
+       void (* menu_item_selected)     (TeplMenuShell *tepl_menu_shell,
+                                        GtkMenuItem   *menu_item);
+
+       void (* menu_item_deselected)   (TeplMenuShell *tepl_menu_shell,
+                                        GtkMenuItem   *menu_item);
+
+       gpointer padding[12];
+};
+
+GType          tepl_menu_shell_get_type                (void) G_GNUC_CONST;
+
+TeplMenuShell *        tepl_menu_shell_get_from_gtk_menu_shell (GtkMenuShell *gtk_menu_shell);
+
+GtkMenuShell * tepl_menu_shell_get_menu_shell          (TeplMenuShell *tepl_menu_shell);
+
+G_END_DECLS
+
+#endif /* TEPL_MENU_SHELL_H */
+
+/* ex:set ts=8 noet: */
diff --git a/amtk/tepl-types.h b/amtk/tepl-types.h
new file mode 100644
index 0000000..e9ce253
--- /dev/null
+++ b/amtk/tepl-types.h
@@ -0,0 +1,39 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2017 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEPL_TYPES_H
+#define TEPL_TYPES_H
+
+#if !defined (TEPL_H_INSIDE) && !defined (TEPL_COMPILATION)
+#error "Only <tepl/tepl.h> can be included directly."
+#endif
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TeplActionInfo                 TeplActionInfo;
+typedef struct _TeplActionInfoEntry            TeplActionInfoEntry;
+typedef struct _TeplActionInfoStore            TeplActionInfoStore;
+typedef struct _TeplActionInfoCentralStore     TeplActionInfoCentralStore;
+typedef struct _TeplMenuShell                  TeplMenuShell;
+
+G_END_DECLS
+
+#endif /* TEPL_TYPES_H */
diff --git a/amtk/tepl.h b/amtk/tepl.h
new file mode 100644
index 0000000..0f0f0d7
--- /dev/null
+++ b/amtk/tepl.h
@@ -0,0 +1,36 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2017 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEPL_H
+#define TEPL_H
+
+#define TEPL_H_INSIDE
+
+#include <tepl/tepl-types.h>
+
+#include <tepl/tepl-action-info.h>
+#include <tepl/tepl-action-info-store.h>
+#include <tepl/tepl-action-info-central-store.h>
+#include <tepl/tepl-action-map.h>
+#include <tepl/tepl-menu-item.h>
+#include <tepl/tepl-menu-shell.h>
+
+#undef TEPL_H_INSIDE
+
+#endif /* TEPL_H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]