/* Metacity window menu */

/* 
 * Copyright (C) 2001 Havoc Pennington
 * Copyright (C) 2004 Rob Adams
 * Copyright (C) 2005 Elijah Newren
 * 
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include <config.h>
#include <stdio.h>
#include <string.h>
#include "menu.h"
#include "main.h"
#include "util.h"
#include "core.h"
#include "themewidget.h"
#include "metaaccellabel.h"

typedef enum
{
  MENU_ITEM_SEPARATOR = 0,
  MENU_ITEM_CHECKBOX,
  MENU_ITEM_IMAGE,
  MENU_ITEM_NORMAL
} MetaMenuItemType;

/* Each item in the window menu is a MenuItem. Not all attributes in
  the MenuItem is used by each MenuItem instance. Only MenuItems of
  type MENU_ITEM_IMAGE uses stock_id to locate the image to
  draw. MENU_ITEM_CHECKBOX uses checked to determine whether the
  MenuItems checkbox should be drawn crossed or not.
*/
typedef struct
{
  MetaMenuItemType type;
  MetaMenuOp op;
  const char *stock_id;
  const gboolean checked;
  const char *label;
} MenuItem;

typedef struct
{
  MetaWindowMenu *menu;
  MetaMenuOp op;
} MenuData;

static MenuItem menuitems[] = {
  { MENU_ITEM_IMAGE,
    META_MENU_OP_MINIMIZE,
    METACITY_STOCK_MINIMIZE,
    FALSE,
    N_("Mi_nimize") },
  { MENU_ITEM_IMAGE,
    META_MENU_OP_MAXIMIZE,
    METACITY_STOCK_MAXIMIZE,
    FALSE,
    N_("Ma_ximize") },
  { MENU_ITEM_IMAGE,
    META_MENU_OP_UNMAXIMIZE,
    NULL,
    FALSE,
    N_("Unma_ximize") },
  { MENU_ITEM_NORMAL,
    META_MENU_OP_SHADE,
    NULL,
    FALSE,
    N_("Roll _Up") },
  { MENU_ITEM_NORMAL,
    META_MENU_OP_UNSHADE,
    NULL,
    FALSE,
    N_("_Unroll") },
  { MENU_ITEM_CHECKBOX,
    META_MENU_OP_ABOVE,
    NULL,
    FALSE,
    N_("On _Top") },
  { MENU_ITEM_CHECKBOX,
    META_MENU_OP_UNABOVE,
    NULL,
    TRUE,
    N_("On _Top") },
  { MENU_ITEM_NORMAL,
    META_MENU_OP_MOVE,
    NULL,
    FALSE,
    N_("_Move") },
  { MENU_ITEM_NORMAL,
    META_MENU_OP_RESIZE,
    NULL,
    FALSE,
    N_("_Resize") },
  { MENU_ITEM_NORMAL,
    META_MENU_OP_RECOVER,
    NULL,
    FALSE,
    N_("Move Titlebar On_screen") },
  
  { MENU_ITEM_SEPARATOR, 0, NULL, FALSE, NULL },
  
  { MENU_ITEM_IMAGE,
    META_MENU_OP_DELETE,
    METACITY_STOCK_DELETE,
    FALSE, N_("_Close") },
  
  { MENU_ITEM_SEPARATOR, META_MENU_OP_WORKSPACES, NULL, FALSE, NULL },
  
  { MENU_ITEM_CHECKBOX,
    META_MENU_OP_STICK,
    NULL,
    FALSE,
    N_("_Always on Visible Workspace") },
  { MENU_ITEM_CHECKBOX,
    META_MENU_OP_UNSTICK,
    NULL,
    TRUE,
    N_("_Always on Visible Workspace") },
  { MENU_ITEM_NORMAL,
    META_MENU_OP_MOVE_LEFT,
    NULL,
    FALSE,
    N_("Move to Workspace _Left") },
  { MENU_ITEM_NORMAL,
    META_MENU_OP_MOVE_RIGHT,
    NULL,
    FALSE,
    N_("Move to Workspace R_ight") },
  { MENU_ITEM_NORMAL,
    META_MENU_OP_MOVE_UP,
    NULL,
    FALSE,
    N_("Move to Workspace _Up") },
  { MENU_ITEM_NORMAL,
    META_MENU_OP_MOVE_DOWN,
    NULL,
    FALSE,
    N_("Move to Workspace _Down") }
};

static void
popup_position_func (GtkMenu   *menu,
                     gint      *x,
                     gint      *y,
                     gboolean  *push_in,
                     gpointer	user_data)
{
  GtkRequisition req;      
  GdkPoint *pos;

  pos = user_data;
  
  gtk_widget_size_request (GTK_WIDGET (menu), &req);

  *x = pos->x;
  *y = pos->y;
  
  /* Ensure onscreen */
  *x = CLAMP (*x, 0, MAX (0, gdk_screen_width () - req.width));
  *y = CLAMP (*y, 0, MAX (0, gdk_screen_height () - req.height));
}

static void
menu_closed (GtkMenu *widget,
             gpointer data)
{
  MetaWindowMenu *menu;
  
  menu = data;

  meta_frames_notify_menu_hide (menu->frames);
  (* menu->func) (menu, gdk_display,
                  menu->client_xwindow,
		  gtk_get_current_event_time (),
                  0, 0,
                  menu->data);
  
  /* menu may now be freed */
}

static void
activate_cb (GtkWidget *menuitem, gpointer data)
{
  MenuData *md;
  
  g_return_if_fail (GTK_IS_WIDGET (menuitem));
  
  md = data;

  meta_frames_notify_menu_hide (md->menu->frames);
  (* md->menu->func) (md->menu, gdk_display,
                      md->menu->client_xwindow,
		      gtk_get_current_event_time (),
                      md->op,
                      GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menuitem),
                                                          "workspace")),
                      md->menu->data);

  /* menu may now be freed */
}

/*
 * Given a Display and an index, get the workspace name and add any
 * accelerators. At the moment this means adding a _ if the name is of
 * the form "Workspace n" where n is less than 10, and escaping any
 * other '_'s so they do not create inadvertant accelerators.
 * 
 * The calling code owns the string, and is reponsible to free the
 * memory after use.
 */
static char*
get_workspace_name_with_accel (Display *display,
                               Window   xroot,
                               int      index)
{
  const char *name;
  int number;

  name = meta_core_get_workspace_name_with_index (display, xroot, index);

  g_assert (name != NULL);
  
  /*
   * If the name is of the form "Workspace x" where x is an unsigned
   * integer, insert a '_' before the number if it is less than 10 and
   * return it
   */
  number = 0;
  if (sscanf (name, _("Workspace %d"), &number) == 1)
    {
      char *new_name;
      
      /*
       * Above name is a pointer into the Workspace struct. Here we make
       * a copy copy so we can have our wicked way with it.
       */
      if (number == 10)
        new_name = g_strdup_printf (_("Workspace 1_0"));
      else
        new_name = g_strdup_printf (_("Workspace %s%d"),
                                    number < 10 ? "_" : "",
                                    number);
      return new_name;
    }
  else
    {
      /*
       * Otherwise this is just a normal name. Escape any _ characters so that
       * the user's workspace names do not get mangled.  If the number is less
       * than 10 we provide an accelerator.
       */
      char *new_name;
      const char *source;
      char *dest;

      /*
       * Assume the worst case, that every character is a _.  We also
       * provide memory for " (_#)"
       */
      new_name = g_malloc0 (strlen (name) * 2 + 6 + 1);

      /*
       * Now iterate down the strings, adding '_' to escape as we go
       */
      dest = new_name;
      source = name;
      while (*source != '\0')
        {
          if (*source == '_')
            *dest++ = '_';
          *dest++ = *source++;
        }

      /* People don't start at workstation 0, but workstation 1 */
      if (index < 9)
        {
          g_snprintf (dest, 6, " (_%d)", index + 1);
        }
      else if (index == 9)
        {
          g_snprintf (dest, 6, " (_0)");
        }

      return new_name;
    }
}

/* Create a GtkWidget from the MenuItem definition menuitem which can
  be added to a GTK menu. workspace_idx is a number which is used by
  meta_core_get_menu_accelerator to decide which keybinding to give
  the menuitem. It should be -1 if the MenuItems op should be used to
  find the keybinding instead.
*/
static GtkWidget*
menu_item_new (MenuItem *menuitem, int workspace_idx)
{
  unsigned int key;
  MetaVirtualModifier mods;
  const char *i18n_label;
  GtkWidget *mi;
  GtkWidget *accel_label;

  if (menuitem->type == MENU_ITEM_IMAGE)
    {
      GtkWidget *image;
      mi = gtk_image_menu_item_new ();
      image = gtk_image_new_from_stock (menuitem->stock_id,
                                        GTK_ICON_SIZE_MENU);
      gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), image);
      gtk_widget_show (image);
    }
  else if (menuitem->type == MENU_ITEM_CHECKBOX)
    {
      mi = gtk_check_menu_item_new ();    
      gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mi),
                                      menuitem->checked);
    }
  else if (menuitem->type == MENU_ITEM_NORMAL)
    mi = gtk_menu_item_new ();
  else
    return gtk_separator_menu_item_new ();

  i18n_label = _(menuitem->label);
  meta_core_get_menu_accelerator (menuitem->op, workspace_idx, &key, &mods);
  
  accel_label = meta_accel_label_new_with_mnemonic (i18n_label);
  gtk_misc_set_alignment (GTK_MISC (accel_label), 0.0, 0.5);
  gtk_container_add (GTK_CONTAINER (mi), accel_label);
  gtk_widget_show (accel_label);

  meta_accel_label_set_accelerator (META_ACCEL_LABEL (accel_label),
                                    key, mods);
  return mi;
}

MetaWindowMenu*
meta_window_menu_new   (MetaFrames         *frames,
                        MetaMenuOp          ops,
                        MetaMenuOp          insensitive,
                        Window              client_xwindow,
                        int                 active_workspace,
                        int                 n_workspaces,
                        MetaWindowMenuFunc  func,
                        gpointer            data)
{
  int i;
  MetaWindowMenu *menu;

  if (n_workspaces < 2)
    ops &= ~(META_MENU_OP_STICK | META_MENU_OP_UNSTICK | META_MENU_OP_WORKSPACES);
  else if (n_workspaces == 2) 
    /* #151183: If we only have two workspaces, disable the menu listing them. */
    ops &= ~(META_MENU_OP_WORKSPACES);
  
  menu = g_new (MetaWindowMenu, 1);
  menu->frames = frames;
  menu->client_xwindow = client_xwindow;
  menu->func = func;
  menu->data = data;
  menu->ops = ops;
  menu->insensitive = insensitive;  
  
  menu->menu = gtk_menu_new ();

  gtk_menu_set_screen (GTK_MENU (menu->menu),
		       gtk_widget_get_screen (GTK_WIDGET (frames)));

  for (i = 0; i < (int) G_N_ELEMENTS (menuitems); i++)
    {
      MenuItem menuitem = menuitems[i];
      if (ops & menuitem.op || menuitem.op == 0)
        {
          GtkWidget *mi = menu_item_new (&menuitem, -1);
          MenuData *md;

          if (insensitive & menuitem.op)
            gtk_widget_set_sensitive (mi, FALSE);

          md = g_new (MenuData, 1);
              
          md->menu = menu;
          md->op = menuitem.op;
              
          gtk_signal_connect_full (GTK_OBJECT (mi),
                                   "activate",
                                   GTK_SIGNAL_FUNC (activate_cb),
                                   NULL,
                                   md,
                                   g_free, FALSE, FALSE);

          gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), mi);
          gtk_widget_show (mi);
        }
    }
  if (ops & META_MENU_OP_WORKSPACES)
    {
      GtkWidget *submenu;
      GtkWidget *submenuitem;
      int i;
      Display *display;
      GdkScreen *screen;
      Window xroot;
      MenuItem to_another_workspace = {
        MENU_ITEM_NORMAL,
        0, NULL, FALSE,
        N_("Move to Another _Workspace")
      };

      submenu = gtk_menu_new ();
      submenuitem = menu_item_new (&to_another_workspace, -1);

      gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), submenuitem);
      gtk_menu_item_set_submenu (GTK_MENU_ITEM (submenuitem), submenu);
      gtk_widget_show (submenuitem);

      screen = gdk_drawable_get_screen (GTK_WIDGET (frames)->window);
      display = gdk_x11_drawable_get_xdisplay (GTK_WIDGET (frames)->window);
      xroot = GDK_DRAWABLE_XID (gdk_screen_get_root_window (screen));
      
      for (i = 0; i < n_workspaces; i++)
        {
          MenuItem moveitem;
          GtkWidget *mi;
          char *label;
          MenuData *md;

          label = get_workspace_name_with_accel (display, xroot, i);
          
          moveitem.type = MENU_ITEM_NORMAL;
          moveitem.op = META_MENU_OP_WORKSPACES;
          moveitem.label = label;
          mi = menu_item_new (&moveitem, i + 1);
          g_free (label);

          if (!(ops & META_MENU_OP_UNSTICK) &&
              (active_workspace == i ||
               insensitive & META_MENU_OP_WORKSPACES))
            gtk_widget_set_sensitive (mi, FALSE);
          else if (insensitive & META_MENU_OP_WORKSPACES)
            gtk_widget_set_sensitive (mi, FALSE);

          md = g_new (MenuData, 1);

          md->menu = menu;
          md->op = META_MENU_OP_WORKSPACES;

          g_object_set_data (G_OBJECT (mi),
                             "workspace",
                             GINT_TO_POINTER (i));
          
          gtk_signal_connect_full (GTK_OBJECT (mi),
                                   "activate",
                                   GTK_SIGNAL_FUNC (activate_cb),
                                   NULL,
                                   md,
                                   g_free, FALSE, FALSE);

          gtk_menu_shell_append (GTK_MENU_SHELL (submenu), mi);
          gtk_widget_show (mi);
        }
    }
  else
    meta_verbose ("not creating workspace menu\n");
  
  g_signal_connect (menu->menu, "selection_done",
                    G_CALLBACK (menu_closed), menu);  

  return menu;
}

void
meta_window_menu_popup (MetaWindowMenu     *menu,
                        int                 root_x,
                        int                 root_y,
                        int                 button,
                        guint32             timestamp)
{
  GdkPoint *pt;
  
  pt = g_new (GdkPoint, 1);

  g_object_set_data_full (G_OBJECT (menu->menu),
                          "destroy-point",
                          pt,
                          g_free);

  pt->x = root_x;
  pt->y = root_y;
  
  gtk_menu_popup (GTK_MENU (menu->menu),
                  NULL, NULL,
                  popup_position_func, pt,
                  button,
                  timestamp);

  if (!GTK_MENU_SHELL (menu->menu)->have_xgrab)
    meta_warning ("GtkMenu failed to grab the pointer\n");
}

void
meta_window_menu_free (MetaWindowMenu *menu)
{
  gtk_widget_destroy (menu->menu);
  g_free (menu);
}
