menu.c 13.7 KB
Newer Older
rhp's avatar
...  
rhp committed
1 2 3 4
/* Metacity window menu */

/* 
 * Copyright (C) 2001 Havoc Pennington
5
 * Copyright (C) 2004 Rob Adams
rhp's avatar
...  
rhp committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 * 
 * 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.
 */

23
#include <config.h>
24
#include <stdio.h>
25
#include <string.h>
rhp's avatar
...  
rhp committed
26 27
#include "menu.h"
#include "main.h"
rhp's avatar
...  
rhp committed
28 29
#include "util.h"
#include "core.h"
30
#include "themewidget.h"
31
#include "metaaccellabel.h"
rhp's avatar
...  
rhp committed
32

rhp's avatar
...  
rhp committed
33 34 35 36 37
typedef struct _MenuItem MenuItem;
typedef struct _MenuData MenuData;

struct _MenuItem
{
rhp's avatar
...  
rhp committed
38
  MetaMenuOp op;
rhp's avatar
...  
rhp committed
39
  const char *stock_id;
40
  const gboolean checked;
rhp's avatar
...  
rhp committed
41 42 43 44 45 46
  const char *label;
};


struct _MenuData
{
rhp's avatar
...  
rhp committed
47
  MetaWindowMenu *menu;
rhp's avatar
...  
rhp committed
48
  MetaMenuOp op;
rhp's avatar
...  
rhp committed
49 50 51 52 53
};

static void activate_cb (GtkWidget *menuitem, gpointer data);

static MenuItem menuitems[] = {
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
  { META_MENU_OP_MINIMIZE, METACITY_STOCK_MINIMIZE, FALSE, N_("Mi_nimize") },
  { META_MENU_OP_MAXIMIZE, METACITY_STOCK_MAXIMIZE, FALSE, N_("Ma_ximize") },
  { META_MENU_OP_UNMAXIMIZE, NULL, FALSE, N_("Unma_ximize") },
  { META_MENU_OP_SHADE, NULL, FALSE, N_("Roll _Up") },
  { META_MENU_OP_UNSHADE, NULL, FALSE, N_("_Unroll") },
  { META_MENU_OP_ABOVE, NULL, FALSE, N_("On _Top") },
  { META_MENU_OP_UNABOVE, NULL, TRUE, N_("On _Top") },
  { META_MENU_OP_MOVE, NULL, FALSE, N_("_Move") },
  { META_MENU_OP_RESIZE, NULL, FALSE, N_("_Resize") },
  { 0, NULL, FALSE, NULL }, /* separator */
  { META_MENU_OP_DELETE, METACITY_STOCK_DELETE, FALSE, N_("_Close") },
  { 0, NULL, FALSE, NULL }, /* separator */
  { META_MENU_OP_STICK, NULL, FALSE, N_("Put on _All Workspaces") },
  { META_MENU_OP_UNSTICK, NULL, FALSE,  N_("Only on _This Workspace") },
  { META_MENU_OP_MOVE_LEFT, NULL, FALSE, N_("Move to Workspace _Left") },
  { META_MENU_OP_MOVE_RIGHT, NULL, FALSE, N_("Move to Workspace R_ight") },
  { META_MENU_OP_MOVE_UP, NULL, FALSE, N_("Move to Workspace _Up") },
  { META_MENU_OP_MOVE_DOWN, NULL, FALSE, N_("Move to Workspace _Down") }
rhp's avatar
...  
rhp committed
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
};

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));
}

rhp's avatar
...  
rhp committed
96
static void
rhp's avatar
...  
rhp committed
97
menu_closed (GtkMenu *widget,
rhp's avatar
...  
rhp committed
98 99
             gpointer data)
{
rhp's avatar
...  
rhp committed
100 101 102
  MetaWindowMenu *menu;
  
  menu = data;
rhp's avatar
...  
rhp committed
103

rhp's avatar
...  
rhp committed
104 105 106
  meta_frames_notify_menu_hide (menu->frames);
  (* menu->func) (menu, gdk_display,
                  menu->client_xwindow,
107
		  gtk_get_current_event_time (),
rhp's avatar
...  
rhp committed
108 109 110 111
                  0, 0,
                  menu->data);
  
  /* menu may now be freed */
rhp's avatar
rhp committed
112 113
}

rhp's avatar
...  
rhp committed
114 115
static void
activate_cb (GtkWidget *menuitem, gpointer data)
rhp's avatar
...  
rhp committed
116
{
rhp's avatar
...  
rhp committed
117
  MenuData *md;
rhp's avatar
...  
rhp committed
118
  
rhp's avatar
...  
rhp committed
119
  g_return_if_fail (GTK_IS_WIDGET (menuitem));
rhp's avatar
...  
rhp committed
120
  
rhp's avatar
...  
rhp committed
121
  md = data;
rhp's avatar
...  
rhp committed
122

rhp's avatar
...  
rhp committed
123 124 125
  meta_frames_notify_menu_hide (md->menu->frames);
  (* md->menu->func) (md->menu, gdk_display,
                      md->menu->client_xwindow,
126
		      gtk_get_current_event_time (),
rhp's avatar
...  
rhp committed
127 128 129 130
                      md->op,
                      GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menuitem),
                                                          "workspace")),
                      md->menu->data);
rhp's avatar
...  
rhp committed
131

rhp's avatar
...  
rhp committed
132 133
  /* menu may now be freed */
}
rhp's avatar
...  
rhp committed
134

135 136 137 138 139 140 141 142 143
/*
 * 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.
 */
Havoc Pennington's avatar
Havoc Pennington committed
144
static char*
145
get_workspace_name_with_accel (Display *display,
146 147
                               Window   xroot,
                               int      index)
148
{
149 150
  const char *name;
  int number;
151

152
  name = meta_core_get_workspace_name_with_index (display, xroot, index);
153

154 155
  g_assert (name != NULL);
  
156 157 158 159 160
  /*
   * 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
   */
Havoc Pennington's avatar
Havoc Pennington committed
161
  number = 0;
162
  if (sscanf (name, _("Workspace %d"), &number) == 1)
163
    {
164 165
      char *new_name;
      
166 167 168 169
      /*
       * Above name is a pointer into the Workspace struct. Here we make
       * a copy copy so we can have our wicked way with it.
       */
170 171 172 173 174 175
      if (number == 10)
        new_name = g_strdup_printf (_("Workspace 1_0"));
      else
        new_name = g_strdup_printf (_("Workspace %s%d"),
                                    number < 10 ? "_" : "",
                                    number);
176
      return new_name;
177 178 179 180
    }
  else
    {
      /*
181 182 183
       * 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.
184
       */
185 186 187
      char *new_name;
      const char *source;
      char *dest;
Havoc Pennington's avatar
Havoc Pennington committed
188

189
      /*
190 191
       * Assume the worst case, that every character is a _.  We also
       * provide memory for " (_#)"
192
       */
193
      new_name = g_malloc0 (strlen (name) * 2 + 6 + 1);
Havoc Pennington's avatar
Havoc Pennington committed
194

195 196 197
      /*
       * Now iterate down the strings, adding '_' to escape as we go
       */
Havoc Pennington's avatar
Havoc Pennington committed
198 199
      dest = new_name;
      source = name;
200 201 202 203 204 205
      while (*source != '\0')
        {
          if (*source == '_')
            *dest++ = '_';
          *dest++ = *source++;
        }
Havoc Pennington's avatar
Havoc Pennington committed
206

207 208 209 210 211 212 213 214 215 216
      /* 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)");
        }

217
      return new_name;
218
    }
219
}
220

221 222 223
static GtkWidget*
menu_item_new (const char         *label,
               gboolean            with_image,
224
	       gboolean		   with_check,
225 226 227 228 229 230
               unsigned int        key,
               MetaVirtualModifier mods)
{
  GtkWidget *menu_item;
  GtkWidget *accel_label;

231 232 233 234 235 236 237
  if (with_check)
    {
      menu_item = gtk_check_menu_item_new ();    
      gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item),
                                      TRUE);
    }
  else if (with_image)
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
    menu_item = gtk_image_menu_item_new ();
  else
    menu_item = gtk_menu_item_new ();
  accel_label = meta_accel_label_new_with_mnemonic (label);
  gtk_misc_set_alignment (GTK_MISC (accel_label), 0.0, 0.5);

  gtk_container_add (GTK_CONTAINER (menu_item), accel_label);
  gtk_widget_show (accel_label);

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

rhp's avatar
...  
rhp committed
253 254 255 256 257 258 259 260 261 262 263 264 265
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;

266 267 268
  if (n_workspaces < 2)
    ops &= ~(META_MENU_OP_STICK | META_MENU_OP_UNSTICK | META_MENU_OP_WORKSPACES);
  
rhp's avatar
...  
rhp committed
269 270 271 272 273 274
  menu = g_new (MetaWindowMenu, 1);
  menu->frames = frames;
  menu->client_xwindow = client_xwindow;
  menu->func = func;
  menu->data = data;
  menu->ops = ops;
275
  menu->insensitive = insensitive;  
rhp's avatar
...  
rhp committed
276
  
rhp's avatar
...  
rhp committed
277
  menu->menu = gtk_menu_new ();
278

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

rhp's avatar
...  
rhp committed
282
  i = 0;
283
  while (i < (int) G_N_ELEMENTS (menuitems))
rhp's avatar
...  
rhp committed
284
    {
rhp's avatar
...  
rhp committed
285
      if (ops & menuitems[i].op || menuitems[i].op == 0)
rhp's avatar
...  
rhp committed
286 287 288
        {
          GtkWidget *mi;
          MenuData *md;
289 290 291
          unsigned int key;
          MetaVirtualModifier mods;
          
rhp's avatar
...  
rhp committed
292
          if (menuitems[i].op == 0)
rhp's avatar
...  
rhp committed
293
            {
rhp's avatar
...  
rhp committed
294
              mi = gtk_separator_menu_item_new ();
rhp's avatar
...  
rhp committed
295 296 297
            }
          else
            {
rhp's avatar
...  
rhp committed
298 299 300 301
              GtkWidget *image;

              image = NULL;
              
302
              if (menuitems[i].stock_id)
rhp's avatar
...  
rhp committed
303
                {
rhp's avatar
...  
rhp committed
304 305
                  image = gtk_image_new_from_stock (menuitems[i].stock_id,
                                                    GTK_ICON_SIZE_MENU);
rhp's avatar
...  
rhp committed
306 307

                }
308 309 310

              meta_core_get_menu_accelerator (menuitems[i].op, -1,
                                              &key, &mods);
311

rhp's avatar
...  
rhp committed
312 313
              if (image)
                {
314
                  mi = menu_item_new (_(menuitems[i].label), TRUE, FALSE, key, mods);
rhp's avatar
...  
rhp committed
315 316 317 318 319 320
                  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi),
                                                 image);
                  gtk_widget_show (image);
                }
              else
                {
321 322
                  mi = menu_item_new (_(menuitems[i].label), FALSE, 
                                      menuitems[i].checked, key, mods);
rhp's avatar
...  
rhp committed
323 324 325 326 327 328 329
                }
              
              if (insensitive & menuitems[i].op)
                gtk_widget_set_sensitive (mi, FALSE);
              
              md = g_new (MenuData, 1);
              
rhp's avatar
...  
rhp committed
330
              md->menu = menu;
rhp's avatar
...  
rhp committed
331 332
              md->op = menuitems[i].op;
              
rhp's avatar
...  
rhp committed
333 334 335 336 337 338
              gtk_signal_connect_full (GTK_OBJECT (mi),
                                       "activate",
                                       GTK_SIGNAL_FUNC (activate_cb),
                                       NULL,
                                       md,
                                       g_free, FALSE, FALSE);
rhp's avatar
...  
rhp committed
339 340
            }
          
rhp's avatar
...  
rhp committed
341
          gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu),
rhp's avatar
...  
rhp committed
342 343 344 345 346 347
                                 mi);
          
          gtk_widget_show (mi);
        }
      ++i;
    }
rhp's avatar
rhp committed
348

rhp's avatar
...  
rhp committed
349
  if (ops & META_MENU_OP_WORKSPACES)
rhp's avatar
rhp committed
350
    {
351 352 353 354 355

      GtkWidget *mi;
      Display *display;
      Window xroot;
      GdkScreen *screen;
356 357
      GtkWidget *submenu;
      GtkWidget *submenuitem;
358

Havoc Pennington's avatar
Havoc Pennington committed
359
      meta_verbose ("Creating %d-workspace menu current space %d\n",
rhp's avatar
...  
rhp committed
360
                    n_workspaces, active_workspace);
361
          
362
      display = gdk_x11_drawable_get_xdisplay (GTK_WIDGET (frames)->window);
363
          
364 365
      screen = gdk_drawable_get_screen (GTK_WIDGET (frames)->window);
      xroot = GDK_DRAWABLE_XID (gdk_screen_get_root_window (screen));
366 367

      submenu = gtk_menu_new ();
368
      submenuitem = menu_item_new (_("Move to Another _Workspace"), FALSE, FALSE, 0, 0);
369 370 371 372 373
      gtk_menu_item_set_submenu (GTK_MENU_ITEM (submenuitem), submenu);
      gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu),
			     submenuitem);
      gtk_widget_show (submenuitem);
           
374 375 376
      i = 0;
      while (i < n_workspaces)
        {
377
          char *label;
378 379 380
          MenuData *md;
          unsigned int key;
          MetaVirtualModifier mods;
381
              
382 383 384
          meta_core_get_menu_accelerator (META_MENU_OP_WORKSPACES,
                                          i + 1,
                                          &key, &mods);
385
              
386 387
          label = get_workspace_name_with_accel (display, xroot, i);
          mi = menu_item_new (label, FALSE, FALSE, key, mods);
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404

          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));
rhp's avatar
rhp committed
405
          
406 407 408 409 410 411
          gtk_signal_connect_full (GTK_OBJECT (mi),
                                   "activate",
                                   GTK_SIGNAL_FUNC (activate_cb),
                                   NULL,
                                   md,
                                   g_free, FALSE, FALSE);
rhp's avatar
...  
rhp committed
412

413
          gtk_menu_shell_append (GTK_MENU_SHELL (submenu),
414
                                 mi);
rhp's avatar
rhp committed
415
          
416
          gtk_widget_show (mi);
rhp's avatar
rhp committed
417
          
418
          ++i;
rhp's avatar
rhp committed
419 420 421
        }
    }
  else
rhp's avatar
...  
rhp committed
422 423
    meta_verbose ("not creating workspace menu\n");
  
rhp's avatar
...  
rhp committed
424
  gtk_signal_connect (GTK_OBJECT (menu->menu),
rhp's avatar
...  
rhp committed
425 426
                      "selection_done",
                      GTK_SIGNAL_FUNC (menu_closed),
rhp's avatar
...  
rhp committed
427 428 429 430 431 432 433 434 435 436 437 438 439
                      menu);  

  return menu;
}

void
meta_window_menu_popup (MetaWindowMenu     *menu,
                        int                 root_x,
                        int                 root_y,
                        int                 button,
                        guint32             timestamp)
{
  GdkPoint *pt;
rhp's avatar
...  
rhp committed
440 441 442
  
  pt = g_new (GdkPoint, 1);

rhp's avatar
...  
rhp committed
443
  g_object_set_data_full (G_OBJECT (menu->menu),
rhp's avatar
...  
rhp committed
444 445 446 447 448 449 450
                          "destroy-point",
                          pt,
                          g_free);

  pt->x = root_x;
  pt->y = root_y;
  
rhp's avatar
...  
rhp committed
451
  gtk_menu_popup (GTK_MENU (menu->menu),
rhp's avatar
...  
rhp committed
452 453 454 455 456
                  NULL, NULL,
                  popup_position_func, pt,
                  button,
                  timestamp);

rhp's avatar
...  
rhp committed
457
  if (!GTK_MENU_SHELL (menu->menu)->have_xgrab)
rhp's avatar
...  
rhp committed
458
    meta_warning ("GtkMenu failed to grab the pointer\n");
rhp's avatar
rhp committed
459 460
}

rhp's avatar
...  
rhp committed
461 462
void
meta_window_menu_free (MetaWindowMenu *menu)
rhp's avatar
...  
rhp committed
463
{
rhp's avatar
...  
rhp committed
464 465
  gtk_widget_destroy (menu->menu);
  g_free (menu);
rhp's avatar
...  
rhp committed
466
}