menu.c

Go to the documentation of this file.
00001 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
00002 
00003 /* Metacity window menu */
00004 
00005 /* 
00006  * Copyright (C) 2001 Havoc Pennington
00007  * Copyright (C) 2004 Rob Adams
00008  * Copyright (C) 2005 Elijah Newren
00009  * 
00010  * This program is free software; you can redistribute it and/or
00011  * modify it under the terms of the GNU General Public License as
00012  * published by the Free Software Foundation; either version 2 of the
00013  * License, or (at your option) any later version.
00014  *
00015  * This program is distributed in the hope that it will be useful, but
00016  * WITHOUT ANY WARRANTY; without even the implied warranty of
00017  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00018  * General Public License for more details.
00019  * 
00020  * You should have received a copy of the GNU General Public License
00021  * along with this program; if not, write to the Free Software
00022  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
00023  * 02111-1307, USA.
00024  */
00025 
00026 #include <config.h>
00027 #include <stdio.h>
00028 #include <string.h>
00029 #include "menu.h"
00030 #include "main.h"
00031 #include "util.h"
00032 #include "core.h"
00033 #include "themewidget.h"
00034 #include "metaaccellabel.h"
00035 #include "ui.h"
00036 
00037 typedef struct _MenuItem MenuItem;
00038 typedef struct _MenuData MenuData;
00039 
00040 typedef enum
00041 {
00042   MENU_ITEM_SEPARATOR = 0,
00043   MENU_ITEM_NORMAL,
00044   MENU_ITEM_IMAGE,
00045   MENU_ITEM_CHECKBOX,
00046   MENU_ITEM_RADIOBUTTON,
00047   MENU_ITEM_WORKSPACE_LIST,
00048 } MetaMenuItemType;
00049 
00050 struct _MenuItem
00051 {
00052   MetaMenuOp op;
00053   MetaMenuItemType type;
00054   const char *stock_id;
00055   const gboolean checked;
00056   const char *label;
00057 };
00058 
00059 
00060 struct _MenuData
00061 {
00062   MetaWindowMenu *menu;
00063   MetaMenuOp op;
00064 };
00065 
00066 static void activate_cb (GtkWidget *menuitem, gpointer data);
00067 
00068 static MenuItem menuitems[] = {
00069   /* Translators: Translate this string the same way as you do in libwnck! */
00070   { META_MENU_OP_MINIMIZE, MENU_ITEM_IMAGE, METACITY_STOCK_MINIMIZE, FALSE, N_("Mi_nimize") },
00071   /* Translators: Translate this string the same way as you do in libwnck! */
00072   { META_MENU_OP_MAXIMIZE, MENU_ITEM_IMAGE, METACITY_STOCK_MAXIMIZE, FALSE, N_("Ma_ximize") },
00073   /* Translators: Translate this string the same way as you do in libwnck! */
00074   { META_MENU_OP_UNMAXIMIZE, MENU_ITEM_NORMAL, NULL, FALSE, N_("Unma_ximize") },
00075   /* Translators: Translate this string the same way as you do in libwnck! */
00076   { META_MENU_OP_SHADE, MENU_ITEM_NORMAL, NULL, FALSE, N_("Roll _Up") },
00077   /* Translators: Translate this string the same way as you do in libwnck! */
00078   { META_MENU_OP_UNSHADE, MENU_ITEM_NORMAL, NULL, FALSE, N_("_Unroll") },
00079   /* Translators: Translate this string the same way as you do in libwnck! */
00080   { META_MENU_OP_MOVE, MENU_ITEM_NORMAL, NULL, FALSE, N_("_Move") },
00081   /* Translators: Translate this string the same way as you do in libwnck! */
00082   { META_MENU_OP_RESIZE, MENU_ITEM_NORMAL, NULL, FALSE, N_("_Resize") },
00083   /* Translators: Translate this string the same way as you do in libwnck! */
00084   { META_MENU_OP_RECOVER, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move Titlebar On_screen") },
00085   { META_MENU_OP_WORKSPACES, MENU_ITEM_SEPARATOR, NULL, FALSE, NULL }, /* separator */
00086   /* Translators: Translate this string the same way as you do in libwnck! */
00087   { META_MENU_OP_ABOVE, MENU_ITEM_CHECKBOX, NULL, FALSE, N_("Always on _Top") },
00088   /* Translators: Translate this string the same way as you do in libwnck! */
00089   { META_MENU_OP_UNABOVE, MENU_ITEM_CHECKBOX, NULL, TRUE, N_("Always on _Top") },
00090   /* Translators: Translate this string the same way as you do in libwnck! */
00091   { META_MENU_OP_STICK, MENU_ITEM_RADIOBUTTON, NULL, FALSE, N_("_Always on Visible Workspace") },
00092   /* Translators: Translate this string the same way as you do in libwnck! */
00093   { META_MENU_OP_UNSTICK, MENU_ITEM_RADIOBUTTON, NULL, FALSE,  N_("_Only on This Workspace") },
00094   /* Translators: Translate this string the same way as you do in libwnck! */
00095   { META_MENU_OP_MOVE_LEFT, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move to Workspace _Left") },
00096   /* Translators: Translate this string the same way as you do in libwnck! */
00097   { META_MENU_OP_MOVE_RIGHT, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move to Workspace R_ight") },
00098   /* Translators: Translate this string the same way as you do in libwnck! */
00099   { META_MENU_OP_MOVE_UP, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move to Workspace _Up") },
00100   /* Translators: Translate this string the same way as you do in libwnck! */
00101   { META_MENU_OP_MOVE_DOWN, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move to Workspace _Down") },
00102   { 0, MENU_ITEM_WORKSPACE_LIST, NULL, FALSE, NULL },
00103   { 0, MENU_ITEM_SEPARATOR, NULL, FALSE, NULL }, /* separator */
00104   /* Translators: Translate this string the same way as you do in libwnck! */
00105   { META_MENU_OP_DELETE, MENU_ITEM_IMAGE, METACITY_STOCK_DELETE, FALSE, N_("_Close") }
00106 };
00107 
00108 static void
00109 popup_position_func (GtkMenu   *menu,
00110                      gint      *x,
00111                      gint      *y,
00112                      gboolean  *push_in,
00113                      gpointer  user_data)
00114 {
00115   GtkRequisition req;      
00116   GdkPoint *pos;
00117 
00118   pos = user_data;
00119   
00120   gtk_widget_size_request (GTK_WIDGET (menu), &req);
00121 
00122   *x = pos->x;
00123   *y = pos->y;
00124   
00125   if (meta_ui_get_direction() == META_UI_DIRECTION_RTL)
00126     *x = MAX (0, *x - req.width); 
00127 
00128   /* Ensure onscreen */
00129   *x = CLAMP (*x, 0, MAX (0, gdk_screen_width () - req.width));
00130   *y = CLAMP (*y, 0, MAX (0, gdk_screen_height () - req.height));
00131 }
00132 
00133 static void
00134 menu_closed (GtkMenu *widget,
00135              gpointer data)
00136 {
00137   MetaWindowMenu *menu;
00138   
00139   menu = data;
00140 
00141   meta_frames_notify_menu_hide (menu->frames);
00142   (* menu->func) (menu, gdk_display,
00143                   menu->client_xwindow,
00144                   gtk_get_current_event_time (),
00145                   0, 0,
00146                   menu->data);
00147   
00148   /* menu may now be freed */
00149 }
00150 
00151 static void
00152 activate_cb (GtkWidget *menuitem, gpointer data)
00153 {
00154   MenuData *md;
00155   
00156   g_return_if_fail (GTK_IS_WIDGET (menuitem));
00157   
00158   md = data;
00159 
00160   meta_frames_notify_menu_hide (md->menu->frames);
00161   (* md->menu->func) (md->menu, gdk_display,
00162                       md->menu->client_xwindow,
00163                       gtk_get_current_event_time (),
00164                       md->op,
00165                       GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menuitem),
00166                                                           "workspace")),
00167                       md->menu->data);
00168 
00169   /* menu may now be freed */
00170 }
00171 
00172 /*
00173  * Given a Display and an index, get the workspace name and add any
00174  * accelerators. At the moment this means adding a _ if the name is of
00175  * the form "Workspace n" where n is less than 10, and escaping any
00176  * other '_'s so they do not create inadvertant accelerators.
00177  * 
00178  * The calling code owns the string, and is reponsible to free the
00179  * memory after use.
00180  *
00181  * See also http://mail.gnome.org/archives/gnome-i18n/2008-March/msg00380.html
00182  * which discusses possible i18n concerns.
00183  */
00184 static char*
00185 get_workspace_name_with_accel (Display *display,
00186                                Window   xroot,
00187                                int      index)
00188 {
00189   const char *name;
00190   int number;
00191   int charcount=0;
00192 
00193   name = meta_core_get_workspace_name_with_index (display, xroot, index);
00194 
00195   g_assert (name != NULL);
00196   
00197   /*
00198    * If the name is of the form "Workspace x" where x is an unsigned
00199    * integer, insert a '_' before the number if it is less than 10 and
00200    * return it
00201    */
00202   number = 0;
00203   if (sscanf (name, _("Workspace %d%n"), &number, &charcount) != 0 &&
00204       *(name + charcount)=='\0')
00205     {
00206       char *new_name;
00207       
00208       /*
00209        * Above name is a pointer into the Workspace struct. Here we make
00210        * a copy copy so we can have our wicked way with it.
00211        */
00212       if (number == 10)
00213         new_name = g_strdup_printf (_("Workspace 1_0"));
00214       else
00215         new_name = g_strdup_printf (_("Workspace %s%d"),
00216                                     number < 10 ? "_" : "",
00217                                     number);
00218       return new_name;
00219     }
00220   else
00221     {
00222       /*
00223        * Otherwise this is just a normal name. Escape any _ characters so that
00224        * the user's workspace names do not get mangled.  If the number is less
00225        * than 10 we provide an accelerator.
00226        */
00227       char *new_name;
00228       const char *source;
00229       char *dest;
00230 
00231       /*
00232        * Assume the worst case, that every character is a _.  We also
00233        * provide memory for " (_#)"
00234        */
00235       new_name = g_malloc0 (strlen (name) * 2 + 6 + 1);
00236 
00237       /*
00238        * Now iterate down the strings, adding '_' to escape as we go
00239        */
00240       dest = new_name;
00241       source = name;
00242       while (*source != '\0')
00243         {
00244           if (*source == '_')
00245             *dest++ = '_';
00246           *dest++ = *source++;
00247         }
00248 
00249       /* People don't start at workspace 0, but workspace 1 */
00250       if (index < 9)
00251         {
00252           g_snprintf (dest, 6, " (_%d)", index + 1);
00253         }
00254       else if (index == 9)
00255         {
00256           g_snprintf (dest, 6, " (_0)");
00257         }
00258 
00259       return new_name;
00260     }
00261 }
00262 
00263 static GtkWidget *
00264 menu_item_new (MenuItem *menuitem, int workspace_id)
00265 {
00266   unsigned int key;
00267   MetaVirtualModifier mods;
00268   const char *i18n_label;
00269   GtkWidget *mi;
00270   GtkWidget *accel_label;
00271 
00272   if (menuitem->type == MENU_ITEM_NORMAL)
00273     {
00274       mi = gtk_menu_item_new ();
00275     }
00276   else if (menuitem->type == MENU_ITEM_IMAGE)
00277     {
00278       GtkWidget *image;
00279       
00280       image = gtk_image_new_from_stock (menuitem->stock_id, GTK_ICON_SIZE_MENU);
00281       mi = gtk_image_menu_item_new ();
00282      
00283       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), image);
00284       gtk_widget_show (image);
00285     }
00286   else if (menuitem->type == MENU_ITEM_CHECKBOX)
00287     {
00288       mi = gtk_check_menu_item_new ();
00289       
00290       gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mi),
00291                                       menuitem->checked);
00292     }    
00293   else if (menuitem->type == MENU_ITEM_RADIOBUTTON)
00294     {
00295       mi = gtk_check_menu_item_new ();
00296 
00297       gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (mi),
00298                                              TRUE);
00299       gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mi),
00300                                       menuitem->checked);
00301     }
00302   else if (menuitem->type == MENU_ITEM_WORKSPACE_LIST)
00303     return NULL;
00304   else
00305     return gtk_separator_menu_item_new ();
00306 
00307   i18n_label = _(menuitem->label);
00308   meta_core_get_menu_accelerator (menuitem->op, workspace_id, &key, &mods);
00309 
00310   accel_label = meta_accel_label_new_with_mnemonic (i18n_label);
00311   gtk_misc_set_alignment (GTK_MISC (accel_label), 0.0, 0.5);
00312 
00313   gtk_container_add (GTK_CONTAINER (mi), accel_label);
00314   gtk_widget_show (accel_label);
00315 
00316   meta_accel_label_set_accelerator (META_ACCEL_LABEL (accel_label),
00317                                     key, mods);
00318   
00319   return mi;
00320 }
00321 
00322 MetaWindowMenu*
00323 meta_window_menu_new   (MetaFrames         *frames,
00324                         MetaMenuOp          ops,
00325                         MetaMenuOp          insensitive,
00326                         Window              client_xwindow,
00327                         unsigned long       active_workspace,
00328                         int                 n_workspaces,
00329                         MetaWindowMenuFunc  func,
00330                         gpointer            data)
00331 {
00332   int i;
00333   MetaWindowMenu *menu;
00334 
00335   /* FIXME: Modifications to 'ops' should happen in meta_window_show_menu */
00336   if (n_workspaces < 2)
00337     ops &= ~(META_MENU_OP_STICK | META_MENU_OP_UNSTICK | META_MENU_OP_WORKSPACES);
00338   else if (n_workspaces == 2) 
00339     /* #151183: If we only have two workspaces, disable the menu listing them. */
00340     ops &= ~(META_MENU_OP_WORKSPACES);
00341   
00342   menu = g_new (MetaWindowMenu, 1);
00343   menu->frames = frames;
00344   menu->client_xwindow = client_xwindow;
00345   menu->func = func;
00346   menu->data = data;
00347   menu->ops = ops;
00348   menu->insensitive = insensitive;  
00349   
00350   menu->menu = gtk_menu_new ();
00351 
00352   gtk_menu_set_screen (GTK_MENU (menu->menu),
00353                        gtk_widget_get_screen (GTK_WIDGET (frames)));
00354 
00355   for (i = 0; i < (int) G_N_ELEMENTS (menuitems); i++)
00356     {
00357       MenuItem menuitem = menuitems[i];
00358       if (ops & menuitem.op || menuitem.op == 0)
00359         {
00360           GtkWidget *mi;
00361           MenuData *md;
00362           unsigned int key;
00363           MetaVirtualModifier mods;
00364 
00365           mi = menu_item_new (&menuitem, -1);
00366 
00367           /* Set the activeness of radiobuttons. */
00368           switch (menuitem.op)
00369             {
00370             case META_MENU_OP_STICK:
00371               gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mi),
00372                                               active_workspace == 0xFFFFFFFF);
00373               break;
00374             case META_MENU_OP_UNSTICK:
00375               gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mi),
00376                                               active_workspace != 0xFFFFFFFF);
00377               break;
00378             default:
00379               break;
00380             }
00381 
00382           if (menuitem.type == MENU_ITEM_WORKSPACE_LIST)
00383             {
00384               if (ops & META_MENU_OP_WORKSPACES)
00385                 {
00386                   Display *display;
00387                   Window xroot;
00388                   GdkScreen *screen;
00389                   GtkWidget *submenu;
00390                   int j;
00391 
00392                   MenuItem to_another_workspace = {
00393                     0, MENU_ITEM_NORMAL,
00394                     NULL, FALSE,
00395                     N_("Move to Another _Workspace")
00396                   };
00397 
00398                   meta_verbose ("Creating %d-workspace menu current space %lu\n",
00399                       n_workspaces, active_workspace);
00400 
00401                   display = gdk_x11_drawable_get_xdisplay (GTK_WIDGET (frames)->window);
00402 
00403                   screen = gdk_drawable_get_screen (GTK_WIDGET (frames)->window);
00404                   xroot = GDK_DRAWABLE_XID (gdk_screen_get_root_window (screen));
00405 
00406                   submenu = gtk_menu_new ();
00407 
00408                   g_assert (mi==NULL);
00409                   mi = menu_item_new (&to_another_workspace, -1);
00410                   gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), submenu);
00411 
00412                   for (j = 0; j < n_workspaces; j++)
00413                     {
00414                       char *label;
00415                       MenuData *md;
00416                       unsigned int key;
00417                       MetaVirtualModifier mods;
00418                       MenuItem moveitem;
00419                       GtkWidget *submi;
00420 
00421                       meta_core_get_menu_accelerator (META_MENU_OP_WORKSPACES,
00422                           j + 1,
00423                           &key, &mods);
00424 
00425                       label = get_workspace_name_with_accel (display, xroot, j);
00426 
00427                       moveitem.type = MENU_ITEM_NORMAL;
00428                       moveitem.op = META_MENU_OP_WORKSPACES;
00429                       moveitem.label = label;
00430                       submi = menu_item_new (&moveitem, j + 1);
00431 
00432                       g_free (label);
00433 
00434                       if ((active_workspace == (unsigned)j) && (ops & META_MENU_OP_UNSTICK))
00435                         gtk_widget_set_sensitive (submi, FALSE);
00436 
00437                       md = g_new (MenuData, 1);
00438 
00439                       md->menu = menu;
00440                       md->op = META_MENU_OP_WORKSPACES;
00441 
00442                       g_object_set_data (G_OBJECT (submi),
00443                           "workspace",
00444                           GINT_TO_POINTER (j));
00445 
00446                       gtk_signal_connect_full (GTK_OBJECT (submi),
00447                           "activate",
00448                           GTK_SIGNAL_FUNC (activate_cb),
00449                           NULL,
00450                           md,
00451                           g_free, FALSE, FALSE);
00452 
00453                       gtk_menu_shell_append (GTK_MENU_SHELL (submenu), submi);
00454 
00455                       gtk_widget_show (submi);
00456                     }
00457                   }
00458                 else
00459                   meta_verbose ("not creating workspace menu\n");
00460             }
00461           else if (menuitem.type != MENU_ITEM_SEPARATOR)
00462             {
00463               meta_core_get_menu_accelerator (menuitems[i].op, -1,
00464                                               &key, &mods);
00465 
00466               if (insensitive & menuitem.op)
00467                 gtk_widget_set_sensitive (mi, FALSE);
00468               
00469               md = g_new (MenuData, 1);
00470               
00471               md->menu = menu;
00472               md->op = menuitem.op;
00473               
00474               gtk_signal_connect_full (GTK_OBJECT (mi),
00475                                        "activate",
00476                                        GTK_SIGNAL_FUNC (activate_cb),
00477                                        NULL,
00478                                        md,
00479                                        g_free, FALSE, FALSE);
00480             }
00481 
00482           if (mi)
00483             {
00484               gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), mi);
00485           
00486               gtk_widget_show (mi);
00487             }
00488         }
00489     }
00490 
00491  
00492   g_signal_connect (menu->menu, "selection_done",
00493                     G_CALLBACK (menu_closed), menu);  
00494 
00495   return menu;
00496 }
00497 
00498 void
00499 meta_window_menu_popup (MetaWindowMenu     *menu,
00500                         int                 root_x,
00501                         int                 root_y,
00502                         int                 button,
00503                         guint32             timestamp)
00504 {
00505   GdkPoint *pt;
00506   
00507   pt = g_new (GdkPoint, 1);
00508 
00509   g_object_set_data_full (G_OBJECT (menu->menu),
00510                           "destroy-point",
00511                           pt,
00512                           g_free);
00513 
00514   pt->x = root_x;
00515   pt->y = root_y;
00516   
00517   gtk_menu_popup (GTK_MENU (menu->menu),
00518                   NULL, NULL,
00519                   popup_position_func, pt,
00520                   button,
00521                   timestamp);
00522 
00523   if (!GTK_MENU_SHELL (menu->menu)->have_xgrab)
00524     meta_warning ("GtkMenu failed to grab the pointer\n");
00525 }
00526 
00527 void
00528 meta_window_menu_free (MetaWindowMenu *menu)
00529 {
00530   gtk_widget_destroy (menu->menu);
00531   g_free (menu);
00532 }

Generated on Sat Aug 23 22:04:18 2008 for metacity by  doxygen 1.5.5