/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * gimpmenu.c
 * Copyright (C) 2023 Jehan
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <gegl.h>
#include <gtk/gtk.h>

#include "widgets-types.h"

#include "libgimpwidgets/gimpwidgets.h"

#include "core/gimp.h"

#include "gimpaction.h"
#include "gimpenumaction.h"
#include "gimphelp-ids.h"
#include "gimpmenu.h"
#include "gimpmenumodel.h"
#include "gimpmenushell.h"
#include "gimpprocedureaction.h"
#include "gimpradioaction.h"
#include "gimpuimanager.h"
#include "gimpwidgets-utils.h"

#define GIMP_MENU_ACTION_KEY "gimp-menu-action"


/**
 * GimpMenu:
 *
 * Our own menu widget.
 *
 * We cannot use the simpler gtk_menu_new_from_model() because it lacks
 * tooltip support and unfortunately GTK does not plan to implement this:
 * https://gitlab.gnome.org/GNOME/gtk/-/issues/785
 *
 * This is why we need to implement our own GimpMenu subclass. It looks very
 * minimal right now, but the whole point is that it is also a GimpMenuShell
 * (where all the real work, with proxy items, syncing with actions and such,
 * happens).
 */


struct _GimpMenuPrivate
{
  GTree      *submenus;

  GHashTable *sections;
};


static void     gimp_menu_iface_init              (GimpMenuShellInterface  *iface);

static void     gimp_menu_finalize                (GObject                 *object);

static void     gimp_menu_append                  (GimpMenuShell           *shell,
                                                   GimpMenuModel           *model);
static void     gimp_menu_add_ui                  (GimpMenuShell           *shell,
                                                   const gchar            **paths,
                                                   const gchar             *action_name,
                                                   gboolean                 top);
static void     gimp_menu_remove_ui               (GimpMenuShell           *shell,
                                                   const gchar            **paths,
                                                   const gchar             *action_name);
static void     gimp_menu_model_deleted           (GimpMenuShell           *shell);

static void     gimp_menu_add_action              (GimpMenu                *menu,
                                                   const gchar             *action_name,
                                                   gboolean                 long_label,
                                                   GtkWidget               *sibling,
                                                   gboolean                 top,
                                                   GtkRadioMenuItem       **group);
static void     gimp_menu_remove_action           (GimpMenu                *menu,
                                                   const gchar             *action_name);
static void     gimp_menu_append_section          (GimpMenu                *menu,
                                                   GimpMenuModel           *model,
                                                   GtkWidget               *start_separator);

static void     gimp_menu_section_items_changed   (GMenuModel              *model,
                                                   gint                     position,
                                                   gint                     removed,
                                                   gint                     added,
                                                   GimpMenu                *menu);

static void     gimp_menu_submenu_notify_title    (GimpMenuModel           *model,
                                                   const GParamSpec        *pspec,
                                                   GtkMenuItem             *item);
static void     gimp_menu_submenu_notify_color    (GimpMenuModel           *model,
                                                   const GParamSpec        *pspec,
                                                   GtkMenuItem             *item);

static void     gimp_menu_toggle_item_toggled     (GtkWidget               *item,
                                                   GAction                 *action);

static void     gimp_menu_toggle_action_toggled   (GimpAction              *action,
                                                   GtkCheckMenuItem        *item);
static void     gimp_menu_action_notify_visible   (GimpAction              *action,
                                                   const GParamSpec        *pspec,
                                                   GtkWidget               *item);

static void     gimp_menu_help_fun                (const gchar             *bogus_help_id,
                                                   gpointer                 help_data);
static void     gimp_menu_update_visibility       (GimpMenu                *menu);


G_DEFINE_TYPE_WITH_CODE (GimpMenu, gimp_menu, GTK_TYPE_MENU,
                         G_ADD_PRIVATE (GimpMenu)
                         G_IMPLEMENT_INTERFACE (GIMP_TYPE_MENU_SHELL, gimp_menu_iface_init))

#define parent_class gimp_menu_parent_class


/*  Class functions  */

static void
gimp_menu_class_init (GimpMenuClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->finalize     = gimp_menu_finalize;
  object_class->get_property = gimp_menu_shell_get_property;
  object_class->set_property = gimp_menu_shell_set_property;

  gimp_menu_shell_install_properties (object_class);
}

static void
gimp_menu_init (GimpMenu *menu)
{
  menu->priv = gimp_menu_get_instance_private (menu);

  menu->priv->submenus     = g_tree_new_full ((GCompareDataFunc) g_strcmp0, NULL,
                                              g_free, NULL);
  menu->priv->sections     = g_hash_table_new_full (g_direct_hash, g_direct_equal,
                                                    g_object_unref, NULL);

  gimp_menu_shell_init (GIMP_MENU_SHELL (menu));

  gimp_help_connect (GTK_WIDGET (menu), NULL, gimp_menu_help_fun,
                     GIMP_HELP_MAIN, menu, NULL);
}

static void
gimp_menu_iface_init (GimpMenuShellInterface *iface)
{
  iface->append        = gimp_menu_append;
  iface->add_ui        = gimp_menu_add_ui;
  iface->remove_ui     = gimp_menu_remove_ui;
  iface->model_deleted = gimp_menu_model_deleted;
}

static void
gimp_menu_finalize (GObject *object)
{
  GimpMenu *menu = GIMP_MENU (object);

  g_clear_pointer (&menu->priv->submenus, g_tree_unref);
  g_hash_table_unref (menu->priv->sections);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gimp_menu_append (GimpMenuShell *shell,
                  GimpMenuModel *model)
{
  GtkRadioMenuItem *group   = NULL;
  GimpMenu         *menu    = GIMP_MENU (shell);
  GimpUIManager    *manager = gimp_menu_shell_get_manager (GIMP_MENU_SHELL (shell));
  gint              n_items;

  g_return_if_fail (GTK_IS_CONTAINER (shell));

  n_items = g_menu_model_get_n_items (G_MENU_MODEL (model));
  for (gint i = 0; i < n_items; i++)
    {
      GMenuModel *subsection;
      GMenuModel *submenu;
      gchar      *label       = NULL;
      gchar      *action_name = NULL;

      subsection = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SECTION);
      submenu    = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SUBMENU);
      g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
      g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name);

      if (subsection != NULL)
        {
          GtkWidget *item;

          group = NULL;

          item = gtk_separator_menu_item_new ();
          gtk_container_add (GTK_CONTAINER (shell), item);
          gtk_widget_show (item);

          /* Don't use gimp_menu_shell_append() here because we don't want to
           * override the main model for this menu.
           * Instead we keep track of each subsection model and their position.
           */
          gimp_menu_append_section (menu, GIMP_MENU_MODEL (subsection), item);

          item = gtk_separator_menu_item_new ();
          gtk_container_add (GTK_CONTAINER (shell), item);
          gtk_widget_show (item);
        }
      else if (submenu != NULL)
        {
          GtkWidget *subcontainer;
          GtkWidget *item;

          group = NULL;

          /* I don't show the item on purpose because
           * gimp_menu_append() will show the parent item if any of
           * the added actions are visible.
           */
          item = gtk_menu_item_new_with_mnemonic (label);
          gtk_container_add (GTK_CONTAINER (shell), item);

          subcontainer = gimp_menu_new (manager);
          gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), subcontainer);
          gimp_menu_shell_append (GIMP_MENU_SHELL (subcontainer), GIMP_MENU_MODEL (submenu));
          gtk_widget_show (subcontainer);

          g_tree_insert (menu->priv->submenus,
                         gimp_utils_make_canonical_menu_label (label),
                         subcontainer);
          g_signal_connect (submenu, "notify::title",
                            G_CALLBACK (gimp_menu_submenu_notify_title),
                            item);
          g_signal_connect (submenu, "notify::color",
                            G_CALLBACK (gimp_menu_submenu_notify_color),
                            item);

          /* End any radio group of the completed submenu.
           * Any subsequent radio menu items start a new radio group.
           */
          group = NULL;
        }
      else
        {
          gchar *label_variant = NULL;

          g_return_if_fail (action_name != NULL);

          g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, "label-variant", "s", &label_variant);
          gimp_menu_add_action (menu, action_name,
                                /* By default, we use the short label in menus,
                                 * unless "label-variant" attribute is set to
                                 * "long".
                                 */
                                g_strcmp0 (label_variant, "long") == 0,
                                NULL, FALSE, &group);
          g_free (label_variant);
        }

      g_free (label);
      g_free (action_name);
      g_clear_object (&submenu);
      g_clear_object (&subsection);
    }

  gimp_menu_update_visibility (menu);
}

static void
gimp_menu_add_ui (GimpMenuShell  *shell,
                  const gchar   **paths,
                  const gchar    *action_name,
                  gboolean        top)
{
  GimpMenu      *menu    = GIMP_MENU (shell);
  GimpUIManager *manager = gimp_menu_shell_get_manager (GIMP_MENU_SHELL (shell));
  GtkWidget     *submenu;

  g_return_if_fail (paths != NULL && paths[0] != NULL);

  submenu = g_tree_lookup (menu->priv->submenus, paths[0]);

  if (submenu == NULL)
    {
      GtkWidget *item;

      item = gtk_menu_item_new_with_mnemonic (paths[0]);
      gtk_container_add (GTK_CONTAINER (shell), item);

      submenu = gimp_menu_new (manager);
      gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
      gtk_widget_show (submenu);

      g_tree_insert (menu->priv->submenus, g_strdup (paths[0]), submenu);
    }

  gimp_menu_add_ui (GIMP_MENU_SHELL (submenu), paths + 1, action_name, top);

  gimp_menu_update_visibility (menu);
}

static void
gimp_menu_remove_ui (GimpMenuShell  *shell,
                     const gchar   **paths,
                     const gchar    *action_name)
{
  GimpMenu *menu = GIMP_MENU (shell);

  g_return_if_fail (paths != NULL);

  if (paths[0] == NULL)
    {
      gimp_menu_remove_action (menu, action_name);
    }
  else
    {
      GtkWidget *submenu = NULL;

      submenu = g_tree_lookup (menu->priv->submenus, paths[0]);

      g_return_if_fail (submenu != NULL);

      gimp_menu_remove_ui (GIMP_MENU_SHELL (submenu), paths + 1, action_name);
    }

  gimp_menu_update_visibility (menu);
}

static void
gimp_menu_model_deleted (GimpMenuShell *shell)
{
  /* This will unref the sub-models, hence will disconnect the "items-changed"
   * signal handlers.
   */
  g_hash_table_remove_all (GIMP_MENU (shell)->priv->sections);
}


/* Public functions */

GtkWidget *
gimp_menu_new (GimpUIManager *manager)
{
  g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), NULL);

  return g_object_new (GIMP_TYPE_MENU,
                       "manager", manager,
                       NULL);
}

void
gimp_menu_merge (GimpMenu *menu,
                 GimpMenu *menu2,
                 gboolean  top)
{
  GList *children;
  GList *iter;

  children = gtk_container_get_children (GTK_CONTAINER (menu2));
  iter     = top ? g_list_last (children) : children;
  for (; iter; iter = top ? iter->prev : iter->next)
    {
      GtkWidget *item = iter->data;

      g_object_ref (item);
      gtk_container_remove (GTK_CONTAINER (menu2), item);
      if (top)
        gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
      else
        gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
      g_object_unref (item);
    }

  gtk_widget_destroy (GTK_WIDGET (menu2));
  g_list_free (children);
}


/* Private functions */

static void
gimp_menu_add_action (GimpMenu          *menu,
                      const gchar       *action_name,
                      gboolean           long_label,
                      GtkWidget         *sibling,
                      gboolean           top,
                      GtkRadioMenuItem **group)
{
  GimpUIManager *manager;
  GimpAction    *action;
  const gchar   *action_label;
  GtkWidget     *item;
  gboolean       visible;

  g_return_if_fail (GIMP_IS_MENU (menu));

  manager = gimp_menu_shell_get_manager (GIMP_MENU_SHELL (menu));
  action  = gimp_ui_manager_find_action (manager, NULL, action_name);

  g_return_if_fail (GIMP_IS_ACTION (action));

  if (long_label)
    action_label = gimp_action_get_label (action);
  else
    action_label = gimp_action_get_short_label (action);
  g_return_if_fail (action_label != NULL);

  if (GIMP_IS_TOGGLE_ACTION (action))
    {
      if (GIMP_IS_RADIO_ACTION (action))
        item = gtk_radio_menu_item_new_with_mnemonic_from_widget (group ? *group : NULL,
                                                                  action_label);
      else
        item = gtk_check_menu_item_new_with_mnemonic (action_label);

      gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
                                      gimp_toggle_action_get_active (GIMP_TOGGLE_ACTION (action)));

      if (group)
        {
          if (GIMP_IS_RADIO_ACTION (action))
            *group = GTK_RADIO_MENU_ITEM (item);
          else
            *group = NULL;
        }

      g_signal_connect (item, "toggled",
                        G_CALLBACK (gimp_menu_toggle_item_toggled),
                        action);
      g_signal_connect_object (action, "toggled",
                               G_CALLBACK (gimp_menu_toggle_action_toggled),
                               item, 0);
    }
  else if (GIMP_IS_PROCEDURE_ACTION (action) ||
           GIMP_IS_ENUM_ACTION (action))
    {
      item = gtk_menu_item_new_with_mnemonic (action_label);

      if (group)
        *group = NULL;

      g_signal_connect_swapped (item, "activate",
                                G_CALLBACK (gimp_action_activate),
                                action);
    }
  else
    {
      item = gtk_menu_item_new_with_mnemonic (action_label);

      if (group)
        *group = NULL;

      g_signal_connect_swapped (item, "activate",
                                G_CALLBACK (gimp_action_activate),
                                action);
    }

  gimp_action_set_proxy (action, item);
  g_object_set_data (G_OBJECT (item), GIMP_MENU_ACTION_KEY, action);

  if (sibling)
    {
      GList *children;
      gint   position = 0;

      /* I am assuming that the order of the children list reflects the
       * position, though it is not clearly specified in the function docs. Yet
       * I could find no other function giving me the position of some child in
       * a container.
       */
      children = gtk_container_get_children (GTK_CONTAINER (menu));

      for (GList *iter = children; iter; iter = iter->next)
        {
          if (iter->data == sibling)
            break;
          position++;
        }
      if (! top)
        position++;

      gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, position);

      g_list_free (children);
    }
  else
    {
      if (top)
        gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
      else
        gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
    }

  visible = gimp_action_is_visible (action);
  gtk_widget_set_visible (item, visible);
  if (visible && GTK_IS_MENU (menu))
    {
      GtkWidget *parent = GTK_WIDGET (menu);

      while (parent != NULL && GIMP_IS_MENU (parent))
        {
          /* Note that this is not the container we must show, but the menu item
           * attached to the parent, in order not to leave empty submenus.
           */
          GtkWidget *menu_item = gtk_menu_get_attach_widget (GTK_MENU (parent));

          if (menu_item == NULL)
            break;

          if (G_TYPE_FROM_INSTANCE (menu_item) == GTK_TYPE_MENU_ITEM)
            gtk_widget_show (menu_item);

          parent = gtk_widget_get_parent (menu_item);
        }
    }

  g_signal_connect_object (action, "notify::visible",
                           G_CALLBACK (gimp_menu_action_notify_visible),
                           item, 0);
}

static void
gimp_menu_remove_action (GimpMenu    *menu,
                         const gchar *action_name)
{
  GimpUIManager *manager;
  GList         *children;
  GimpAction    *action;

  g_return_if_fail (GIMP_IS_MENU (menu));

  manager = gimp_menu_shell_get_manager (GIMP_MENU_SHELL (menu));
  action  = gimp_ui_manager_find_action (manager, NULL, action_name);

  g_return_if_fail (GIMP_IS_ACTION (action));

  children = gtk_container_get_children (GTK_CONTAINER (menu));

  for (GList *iter = children; iter; iter = iter->next)
    {
      GtkWidget  *child = iter->data;
      GimpAction *item_action;

      item_action = g_object_get_data (G_OBJECT (child), GIMP_MENU_ACTION_KEY);
      if (item_action == action)
        {
          gtk_widget_destroy (child);
          break;
        }
    }

  g_list_free (children);
}

static void
gimp_menu_append_section (GimpMenu      *menu,
                          GimpMenuModel *model,
                          GtkWidget     *start_separator)
{
  g_hash_table_insert (menu->priv->sections, g_object_ref (model), start_separator);
  gimp_menu_append (GIMP_MENU_SHELL (menu), model);

  g_signal_connect_object (model, "items-changed",
                           G_CALLBACK (gimp_menu_section_items_changed),
                           menu, 0);
}

static void
gimp_menu_section_items_changed (GMenuModel *model,
                                 gint        position,
                                 gint        removed,
                                 gint        added,
                                 GimpMenu   *menu)
{
  GList     *children;
  GList     *iter;
  GtkWidget *separator;
  gboolean   found = FALSE;
  gint       count = position;
  gint       real_pos = 0;

  separator = g_hash_table_lookup (menu->priv->sections, model);
  g_return_if_fail (separator != NULL);

  children = gtk_container_get_children (GTK_CONTAINER (menu));
  for (iter = children; iter; iter = iter->next)
    {
      real_pos++;

      if (! found)
        {
          if (iter->data == separator)
            found = TRUE;

          continue;
        }

      if (count > 0)
        {
          /* Assume we don't have sections within a section, i.e. in particular
           * we don't have more separators, which would mess the count!
           */
          count--;
          continue;
        }
      else if (removed > 0)
        {
          gtk_widget_destroy (iter->data);
          removed--;
          continue;
        }
      else
        {
          break;
        }
    }

  while (added > 0)
    {
      gchar *action_name   = NULL;
      gchar *label_variant = NULL;

      g_menu_model_get_item_attribute (G_MENU_MODEL (model), position,
                                       G_MENU_ATTRIBUTE_ACTION, "s", &action_name);
      g_menu_model_get_item_attribute (G_MENU_MODEL (model), position,
                                       "label-variant", "s", &label_variant);

      g_return_if_fail (action_name != NULL);
      gimp_menu_add_action (menu, action_name,
                            g_strcmp0 (label_variant, "long") == 0,
                            iter ? iter->data : NULL,
                            iter ? TRUE : FALSE, NULL);
      g_free (action_name);
      g_free (label_variant);

      added--;
      position++;
    }
  g_list_free (children);

  gimp_menu_update_visibility (menu);
}

static void
gimp_menu_submenu_notify_title (GimpMenuModel    *model,
                                const GParamSpec *pspec,
                                GtkMenuItem      *item)
{
  gchar *title;

  g_object_get (model,
                "title", &title,
                NULL);
  gtk_menu_item_set_label (item, title);
  g_free (title);
}

static void
gimp_menu_submenu_notify_color (GimpMenuModel    *model,
                                const GParamSpec *pspec,
                                GtkMenuItem      *item)
{
  GeglColor *color   = NULL;
  GtkWidget *image = NULL;
  gint       width, height;

  g_object_get (model,
                "color", &color,
                NULL);

  if (color)
    {
      image = gimp_color_area_new (color, GIMP_COLOR_AREA_SMALL_CHECKS, 0);
      gimp_color_area_set_draw_border (GIMP_COLOR_AREA (image), TRUE);

      /* TODO: the color area should be color-managed. */
      /*gimp_color_area_set_color_config (GIMP_COLOR_AREA (image),*/
      /*gimp->config->color_management);*/

      gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height);
      gtk_widget_set_size_request (image, width, height);
      gtk_widget_show (image);

      g_object_unref (color);
    }

  gimp_menu_item_set_image (item, image, NULL);
}

static void
gimp_menu_toggle_item_toggled (GtkWidget *item,
                               GAction   *action)
{
  gboolean active = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));

  g_signal_handlers_block_by_func (action,
                                   G_CALLBACK (gimp_menu_toggle_action_toggled),
                                   item);
  gimp_toggle_action_set_active (GIMP_TOGGLE_ACTION (action), active);
  g_signal_handlers_unblock_by_func (action,
                                     G_CALLBACK (gimp_menu_toggle_action_toggled),
                                     item);
}

static void
gimp_menu_toggle_action_toggled (GimpAction       *action,
                                 GtkCheckMenuItem *item)
{
  gboolean active = gimp_toggle_action_get_active (GIMP_TOGGLE_ACTION (action));

  g_signal_handlers_block_by_func (item,
                                   G_CALLBACK (gimp_menu_toggle_item_toggled),
                                   action);
  gtk_check_menu_item_set_active (item, active);
  g_signal_handlers_unblock_by_func (item,
                                   G_CALLBACK (gimp_menu_toggle_item_toggled),
                                   action);
}

static void
gimp_menu_action_notify_visible (GimpAction       *action,
                                 const GParamSpec *pspec,
                                 GtkWidget        *item)
{
  GtkWidget *container;

  gtk_widget_set_visible (item, gimp_action_is_visible (action));

  container = gtk_widget_get_parent (item);
  if (gimp_action_is_visible (GIMP_ACTION (action)))
    {
      GtkWidget *widget = gtk_menu_get_attach_widget (GTK_MENU (container));

      /* We must show the GtkMenuItem associated as submenu to the parent
       * container.
       */
      if (G_TYPE_FROM_INSTANCE (widget) == GTK_TYPE_MENU_ITEM)
        gtk_widget_show (widget);
    }
  else
    {
      GList    *children      = gtk_container_get_children (GTK_CONTAINER (container));
      gboolean  all_invisible = TRUE;

      for (GList *iter = children; iter; iter = iter->next)
        {
          if (gtk_widget_get_visible (iter->data))
            {
              all_invisible = FALSE;
              break;
            }
        }
      g_list_free (children);

      if (all_invisible)
        {
          GtkWidget *widget;

          /* No need to leave empty submenus. */
          widget = gtk_menu_get_attach_widget (GTK_MENU (container));
          if (G_TYPE_FROM_INSTANCE (widget) == GTK_TYPE_MENU_ITEM)
            gtk_widget_hide (widget);
        }
    }

  gimp_menu_update_visibility (GIMP_MENU (container));
}

static void
gimp_menu_help_fun (const gchar *bogus_help_id,
                    gpointer     help_data)
{
  gchar     *help_id     = NULL;
  GimpMenu  *menu;
  Gimp      *gimp;
  GtkWidget *item;
  gchar     *help_domain = NULL;
  gchar     *help_string = NULL;
  gchar     *domain_separator;

  g_return_if_fail (GIMP_IS_MENU (help_data));

  menu = GIMP_MENU (help_data);
  gimp = gimp_menu_shell_get_manager (GIMP_MENU_SHELL (menu))->gimp;

  g_return_if_fail (GIMP_IS_GIMP (gimp));

  item = gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (menu));

  if (item)
    help_id = g_object_get_qdata (G_OBJECT (item), GIMP_HELP_ID);

  if (help_id == NULL || strlen (help_id) == 0)
    help_id = (gchar *) bogus_help_id;

  help_id = g_strdup (help_id);

  domain_separator = strchr (help_id, '?');

  if (domain_separator)
    {
      *domain_separator = '\0';

      help_domain = g_strdup (help_id);
      help_string = g_strdup (domain_separator + 1);
    }
  else
    {
      help_string = g_strdup (help_id);
    }

  gimp_help (gimp, NULL, help_domain, help_string);

  g_free (help_domain);
  g_free (help_string);
  g_free (help_id);
}

/* With successive sections, we will end up with double separators (end one then
 * start one of the next section). Moreover sometimes, empty sections (e.g.
 * because items are expected to be added later) would make even 3 to 4
 * separators next to each other. This renders very ugly. We need to call this
 * function to hide and show separators after changes.
 *
 * This also hides start and end separators in the menu.
 *
 * Finally this function shows/hides the whole menu itself, in cases it
 * has no visible items. There is no need to show an empty submenu.
 */
static void
gimp_menu_update_visibility (GimpMenu *menu)
{
  GList     *children;
  GList     *iter;
  GtkWidget *prev_item = NULL;
  GtkWidget *menu_item = gtk_menu_get_attach_widget (GTK_MENU (menu));
  gint       n_items   = 0;

  children = gtk_container_get_children (GTK_CONTAINER (menu));
  for (iter = children; iter; iter = iter->next)
    {
      GtkWidget *item = iter->data;

      if (GTK_IS_SEPARATOR_MENU_ITEM (item))
        {
          if (prev_item == NULL ||
              GTK_IS_SEPARATOR_MENU_ITEM (prev_item))
            {
              gtk_widget_hide (item);
            }
          else
            {
              gtk_widget_show (item);
              prev_item = item;
            }
        }
      else if (gtk_widget_get_visible (item))
        {
          /* Right now, all but separators are meaningful items.
           * XXX: though I should likely also check submenus. If a menu
           * contains another empty submenu, we should likely hide the
           * whole hierarchy.
           */
          n_items++;
          prev_item = item;
        }
    }

  if (prev_item != NULL && GTK_IS_SEPARATOR_MENU_ITEM (prev_item))
    gtk_widget_hide (prev_item);

  g_list_free (children);

  if (menu_item != NULL && G_TYPE_FROM_INSTANCE (menu_item) == GTK_TYPE_MENU_ITEM)
    gtk_widget_set_visible (GTK_WIDGET (menu_item), n_items > 0);
}
