window-action-menu.c 35.8 KB
Newer Older
1
/* window action menu (ops on a single window) */
2
/* vim: set sw=2 et: */
3 4 5

/*
 * Copyright (C) 2001 Havoc Pennington
6
 * Copyright (C) 2006-2007 Vincent Untz
7 8 9 10 11 12 13 14 15 16 17 18
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
19
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
20 21
 */

Vincent Untz's avatar
Vincent Untz committed
22 23
#include <config.h>

24 25
#include <string.h>
#include <stdio.h>
Vincent Untz's avatar
Vincent Untz committed
26
#include <glib/gi18n-lib.h>
27

28
#include "window-action-menu.h"
Havoc Pennington's avatar
Havoc Pennington committed
29
#include "private.h"
30

31 32
/**
 * SECTION:window-action-menu
33
 * @short_description: a menu widget, used to manipulate a window.
34 35 36
 * @see_also: #WnckWindow
 * @stability: Unstable
 *
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
 * A #WnckActionMenu is a menu containing items to manipulate a window.
 * Relevant actions are displayed in the menu, and updated if the window state
 * changes. The content of this menu is synchronized with the similar menu
 * available in Metacity.
 *
 * <note>
 *  <para>
 * If there is only one workspace with a viewport, the #WnckActionMenu will
 * contain items to move the window in the viewport as if the viewport feature
 * was used to create workspaces. This is useful since viewport is generally
 * used as an alternative way to create virtual desktops.
 *  </para>
 *  <para>
 * The #WnckActionMenu does not support moving the window in the viewport if
 * there are multiple workspaces on the screen: those two notions are so
 * similar that having both at the same time would result in a menu which would
 * be confusing to the user.
 *  </para>
 * </note>
56 57
 */

58 59 60 61 62
typedef enum
{
  CLOSE,
  MINIMIZE,
  MAXIMIZE,
63
  ABOVE,
64
  MOVE,
65
  RESIZE,
66
  PIN,
67
  UNPIN,
68 69 70 71
  LEFT,
  RIGHT,
  UP,
  DOWN,
72
  MOVE_TO_WORKSPACE
73 74
} WindowAction;

75
struct _WnckActionMenuPrivate
76 77 78 79
{
  WnckWindow *window;
  GtkWidget *minimize_item;
  GtkWidget *maximize_item;
80
  GtkWidget *above_item;
81 82
  GtkWidget *move_item;
  GtkWidget *resize_item;
83
  GtkWidget *close_item;
84
  GtkWidget *workspace_separator;
85
  GtkWidget *pin_item;
86
  GtkWidget *unpin_item;
87 88 89 90
  GtkWidget *left_item;
  GtkWidget *right_item;
  GtkWidget *up_item;
  GtkWidget *down_item;
91
  GtkWidget *workspace_item;
92 93 94
  guint idle_handler;
};

95 96 97 98 99
enum {
	PROP_0,
	PROP_WINDOW
};

100
G_DEFINE_TYPE_WITH_PRIVATE (WnckActionMenu, wnck_action_menu, GTK_TYPE_MENU);
101

102
static void wnck_action_menu_dispose (GObject *object);
103

Havoc Pennington's avatar
Havoc Pennington committed
104 105 106
static void window_weak_notify (gpointer data,
                                GObject *window);

107 108
static void refill_submenu_workspace (WnckActionMenu *menu);
static void refill_submenu_viewport (WnckActionMenu *menu);
109

110 111 112 113
static void
window_weak_notify (gpointer data,
                    GObject *window)
{
114
  WNCK_ACTION_MENU(data)->priv->window = NULL;
115
  gtk_widget_destroy (GTK_WIDGET (data));
Havoc Pennington's avatar
Havoc Pennington committed
116 117
}

118 119
static WnckActionMenu*
get_action_menu (GtkWidget *widget)
120
{
121 122
  while (widget) {
    if (GTK_IS_MENU_ITEM (widget))
123
      widget = gtk_widget_get_parent (widget);
124

125 126
    if (WNCK_IS_ACTION_MENU (widget))
      return WNCK_ACTION_MENU (widget);
127 128 129 130 131 132 133

    widget = gtk_menu_get_attach_widget (GTK_MENU (widget));
    if (widget == NULL)
      break;
  }

  return NULL;
134 135 136 137 138 139
}

static void
item_activated_callback (GtkWidget *menu_item,
                         gpointer   data)
{
140 141
  WnckActionMenu *menu;
  WnckWindow *window;
142
  WindowAction action = GPOINTER_TO_INT (data);
143 144 145
  WnckScreen *screen;
  gboolean viewport_mode;

146 147
  menu = get_action_menu (menu_item);
  if (menu == NULL)
148 149
    return;

150 151 152
  window = menu->priv->window;

  screen = wnck_window_get_screen (window);
153 154 155 156
  viewport_mode = wnck_screen_get_workspace_count (screen) == 1 &&
                  wnck_workspace_is_virtual (wnck_screen_get_workspace (screen,
                                                                        0));

157 158 159
  switch (action)
    {
    case CLOSE:
160
      /* In an activate callback, so gtk_get_current_event_time() suffices */
161
      wnck_window_close (window,
162
			 gtk_get_current_event_time ());
163 164
      break;
    case MINIMIZE:
165 166
      if (wnck_window_is_minimized (window))
        wnck_window_unminimize (window,
167
                                gtk_get_current_event_time ());
168
      else
169
        wnck_window_minimize (window);
170 171
      break;
    case MAXIMIZE:
172 173
      if (wnck_window_is_maximized (window))
        wnck_window_unmaximize (window);
174
      else
175
        wnck_window_maximize (window);
176
      break;
177
    case ABOVE:
178 179
      if (wnck_window_is_above (window))
        wnck_window_unmake_above (window);
180
      else
181
        wnck_window_make_above (window);
182 183
      break;
    case MOVE:
184
      wnck_window_keyboard_move (window);
185 186
      break;
    case RESIZE:
187
      wnck_window_keyboard_size (window);
188
      break;
189
    case PIN:
190
      if (!viewport_mode)
191
        wnck_window_pin (window);
192
      else
193
        wnck_window_stick (window);
194 195
      break;
    case UNPIN:
196
      if (!viewport_mode)
197
        wnck_window_unpin (window);
198
      else
199
        wnck_window_unstick (window);
200
      break;
201
    case LEFT:
202 203 204
      if (!viewport_mode)
        {
          WnckWorkspace *workspace;
205
          workspace = wnck_workspace_get_neighbor (wnck_window_get_workspace (window),
206
                                                   WNCK_MOTION_LEFT);
207
          wnck_window_move_to_workspace (window, workspace);
208 209 210 211 212 213
        }
      else
        {
          int width, xw, yw, ww, hw;

          width = wnck_screen_get_width (screen);
214
          wnck_window_get_geometry (window, &xw, &yw, &ww, &hw);
215
          wnck_window_unstick (window);
216
          wnck_window_set_geometry (window, 0,
217 218 219 220 221
                                    WNCK_WINDOW_CHANGE_X | WNCK_WINDOW_CHANGE_Y,
                                    xw - width, yw,
                                    ww, hw);
        }
      break;
222
    case RIGHT:
223 224 225
      if (!viewport_mode)
        {
          WnckWorkspace *workspace;
226
          workspace = wnck_workspace_get_neighbor (wnck_window_get_workspace (window),
227
                                                   WNCK_MOTION_RIGHT);
228
          wnck_window_move_to_workspace (window, workspace);
229 230 231 232 233 234
        }
      else
        {
          int width, xw, yw, ww, hw;

          width = wnck_screen_get_width (screen);
235
          wnck_window_get_geometry (window, &xw, &yw, &ww, &hw);
236
          wnck_window_unstick (window);
237
          wnck_window_set_geometry (window, 0,
238 239 240 241 242
                                    WNCK_WINDOW_CHANGE_X | WNCK_WINDOW_CHANGE_Y,
                                    xw + width, yw,
                                    ww, hw);
        }
      break;
243
    case UP:
244 245 246
      if (!viewport_mode)
        {
          WnckWorkspace *workspace;
247
          workspace = wnck_workspace_get_neighbor (wnck_window_get_workspace (window),
248
                                                   WNCK_MOTION_UP);
249
          wnck_window_move_to_workspace (window, workspace);
250 251 252 253 254 255
        }
      else
        {
          int height, xw, yw, ww, hw;

          height = wnck_screen_get_height (screen);
256
          wnck_window_get_geometry (window, &xw, &yw, &ww, &hw);
257
          wnck_window_unstick (window);
258
          wnck_window_set_geometry (window, 0,
259 260 261 262 263
                                    WNCK_WINDOW_CHANGE_X | WNCK_WINDOW_CHANGE_Y,
                                    xw, yw - height,
                                    ww, hw);
        }
      break;
264
    case DOWN:
265 266 267
      if (!viewport_mode)
        {
          WnckWorkspace *workspace;
268
          workspace = wnck_workspace_get_neighbor (wnck_window_get_workspace (window),
269
                                                   WNCK_MOTION_DOWN);
270
          wnck_window_move_to_workspace (window, workspace);
271 272 273 274 275 276
        }
      else
        {
          int height, xw, yw, ww, hw;

          height = wnck_screen_get_height (screen);
277
          wnck_window_get_geometry (window, &xw, &yw, &ww, &hw);
278
          wnck_window_unstick (window);
279
          wnck_window_set_geometry (window, 0,
280 281 282 283 284
                                    WNCK_WINDOW_CHANGE_X | WNCK_WINDOW_CHANGE_Y,
                                    xw, yw + height,
                                    ww, hw);
        }
      break;
285
    case MOVE_TO_WORKSPACE:
286 287 288 289
      if (!viewport_mode)
        {
          int workspace_index;
          WnckWorkspace *workspace;
290

291 292
          workspace_index = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item),
                                                                "workspace"));
293

294
          workspace = wnck_screen_get_workspace (screen, workspace_index);
295
          wnck_window_move_to_workspace (window, workspace);
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
        }
      else
        {
          WnckWorkspace *workspace;
          int new_viewport_x, new_viewport_y;
          int xw, yw, ww, hw;
          int viewport_x, viewport_y;

          new_viewport_x = GPOINTER_TO_INT (
                                      g_object_get_data (G_OBJECT (menu_item),
                                                         "viewport_x"));
          new_viewport_y = GPOINTER_TO_INT (
                                      g_object_get_data (G_OBJECT (menu_item),
                                                         "viewport_y"));

          workspace = wnck_screen_get_workspace (screen, 0);

313
          wnck_window_get_geometry (window, &xw, &yw, &ww, &hw);
314 315 316 317

          viewport_x = wnck_workspace_get_viewport_x (workspace);
          viewport_y = wnck_workspace_get_viewport_y (workspace);

318
          wnck_window_unstick (window);
319
          wnck_window_set_geometry (window, 0,
320
                                    WNCK_WINDOW_CHANGE_X | WNCK_WINDOW_CHANGE_Y,
321 322
                                    xw + new_viewport_x - viewport_x,
                                    yw + new_viewport_y - viewport_y,
323 324 325 326 327
                                    ww, hw);
        }
      break;
    default:
      g_assert_not_reached ();
328 329 330 331 332 333 334
    }
}

static void
set_item_text (GtkWidget  *mi,
               const char *text)
{
335 336 337
  GtkLabel *label;

  label = GTK_LABEL (gtk_bin_get_child (GTK_BIN (mi)));
338
  gtk_label_set_text_with_mnemonic (label, text);
339
  gtk_label_set_use_underline (label, TRUE);
340 341 342
}

static gboolean
343
update_menu_state (WnckActionMenu *menu)
344
{
345 346 347 348 349 350
  WnckActionMenuPrivate *priv;
  WnckWindowActions      actions;
  WnckScreen            *screen;
  WnckWorkspace         *workspace;
  gboolean               viewport_mode;
  gboolean               move_workspace_sensitive;
351

352
  priv = menu->priv;
353

354 355 356 357
  priv->idle_handler = 0;

  actions = wnck_window_get_actions (priv->window);
  screen  = wnck_window_get_screen  (priv->window);
358 359 360 361 362 363 364

  viewport_mode = wnck_screen_get_workspace_count (screen) == 1 &&
                  wnck_workspace_is_virtual (wnck_screen_get_workspace (screen,
                                                                        0));
  move_workspace_sensitive = viewport_mode ||
                             (actions & WNCK_WINDOW_ACTION_CHANGE_WORKSPACE) != 0;

365
  if (wnck_window_is_minimized (priv->window))
366
    {
367 368
      set_item_text (priv->minimize_item, _("Unmi_nimize"));
      gtk_widget_set_sensitive (priv->minimize_item,
369 370 371 372
                                (actions & WNCK_WINDOW_ACTION_UNMINIMIZE) != 0);
    }
  else
    {
373 374
      set_item_text (priv->minimize_item, _("Mi_nimize"));
      gtk_widget_set_sensitive (priv->minimize_item,
375 376 377
                                (actions & WNCK_WINDOW_ACTION_MINIMIZE) != 0);
    }

378
  if (wnck_window_is_maximized (priv->window))
379
    {
380 381
      set_item_text (priv->maximize_item, _("Unma_ximize"));
      gtk_widget_set_sensitive (priv->maximize_item,
382 383 384 385
                                (actions & WNCK_WINDOW_ACTION_UNMAXIMIZE) != 0);
    }
  else
    {
386 387
      set_item_text (priv->maximize_item, _("Ma_ximize"));
      gtk_widget_set_sensitive (priv->maximize_item,
388 389 390
                                (actions & WNCK_WINDOW_ACTION_MAXIMIZE) != 0);
    }

391
  g_signal_handlers_block_by_func (G_OBJECT (priv->above_item),
392 393
                                   item_activated_callback,
                                   GINT_TO_POINTER (ABOVE));
394 395 396
  gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (priv->above_item),
                                  wnck_window_is_above (priv->window));
  g_signal_handlers_unblock_by_func (G_OBJECT (priv->above_item),
397 398 399
                                     item_activated_callback,
                                     GINT_TO_POINTER (ABOVE));

400
  gtk_widget_set_sensitive (priv->above_item,
401
                            (actions & WNCK_WINDOW_ACTION_ABOVE) != 0);
402

403
  g_signal_handlers_block_by_func (G_OBJECT (priv->pin_item),
404 405
                                   item_activated_callback,
                                   GINT_TO_POINTER (PIN));
406
  g_signal_handlers_block_by_func (G_OBJECT (priv->unpin_item),
407 408
                                   item_activated_callback,
                                   GINT_TO_POINTER (UNPIN));
409 410 411
  if ((viewport_mode  && wnck_window_is_sticky (priv->window)) ||
      (!viewport_mode && wnck_window_is_pinned (priv->window)))
          gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (priv->pin_item),
412
                                          TRUE);
413
  else
414
          gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (priv->unpin_item),
415
                                          TRUE);
416
  g_signal_handlers_unblock_by_func (G_OBJECT (priv->pin_item),
417 418
                                     item_activated_callback,
                                     GINT_TO_POINTER (PIN));
419
  g_signal_handlers_unblock_by_func (G_OBJECT (priv->unpin_item),
420 421 422
                                     item_activated_callback,
                                     GINT_TO_POINTER (UNPIN));

423
  gtk_widget_set_sensitive (priv->pin_item,
424
                            move_workspace_sensitive);
425

426
  gtk_widget_set_sensitive (priv->unpin_item,
427 428
                            move_workspace_sensitive);

429
  gtk_widget_set_sensitive (priv->close_item,
430
                            (actions & WNCK_WINDOW_ACTION_CLOSE) != 0);
431

432
  gtk_widget_set_sensitive (priv->move_item,
433 434
                            (actions & WNCK_WINDOW_ACTION_MOVE) != 0);

435
  gtk_widget_set_sensitive (priv->resize_item,
436
                            (actions & WNCK_WINDOW_ACTION_RESIZE) != 0);
437

438
  gtk_widget_set_sensitive (priv->workspace_item,
439
                            move_workspace_sensitive);
440

441
  gtk_widget_set_sensitive (priv->left_item,
442
                            move_workspace_sensitive);
443
  gtk_widget_set_sensitive (priv->right_item,
444
                            move_workspace_sensitive);
445
  gtk_widget_set_sensitive (priv->up_item,
446
                            move_workspace_sensitive);
447
  gtk_widget_set_sensitive (priv->down_item,
448
                            move_workspace_sensitive);
449

450
  workspace = wnck_window_get_workspace (priv->window);
451

452
  if (viewport_mode && !wnck_window_is_sticky (priv->window))
453 454 455 456 457 458 459 460 461
    {
      int window_x, window_y;
      int viewport_x, viewport_y;
      int viewport_width, viewport_height;
      int screen_width, screen_height;

      if (!workspace)
        workspace = wnck_screen_get_workspace (screen, 0);

462
      wnck_window_get_geometry (priv->window, &window_x, &window_y, NULL, NULL);
463 464 465 466 467 468 469 470 471 472 473 474 475 476

      viewport_x = wnck_workspace_get_viewport_x (workspace);
      viewport_y = wnck_workspace_get_viewport_y (workspace);

      window_x += viewport_x;
      window_y += viewport_y;

      viewport_width = wnck_workspace_get_width (workspace);
      viewport_height = wnck_workspace_get_height (workspace);

      screen_width = wnck_screen_get_width (screen);
      screen_height = wnck_screen_get_height (screen);

      if (window_x >= screen_width)
477
        gtk_widget_show (priv->left_item);
478
      else
479
        gtk_widget_hide (priv->left_item);
480 481

      if (window_x < viewport_width - screen_width)
482
        gtk_widget_show (priv->right_item);
483
      else
484
        gtk_widget_hide (priv->right_item);
485 486

      if (window_y >= screen_height)
487
        gtk_widget_show (priv->up_item);
488
      else
489
        gtk_widget_hide (priv->up_item);
490 491

      if (window_y < viewport_height - screen_height)
492
        gtk_widget_show (priv->down_item);
493
      else
494
        gtk_widget_hide (priv->down_item);
495
    }
496
  else if (!viewport_mode && workspace && !wnck_window_is_pinned (priv->window))
497 498
    {
      if (wnck_workspace_get_neighbor (workspace, WNCK_MOTION_LEFT))
499
        gtk_widget_show (priv->left_item);
500
      else
501
        gtk_widget_hide (priv->left_item);
502

503
      if (wnck_workspace_get_neighbor (workspace, WNCK_MOTION_RIGHT))
504
        gtk_widget_show (priv->right_item);
505
      else
506
        gtk_widget_hide (priv->right_item);
507

508
      if (wnck_workspace_get_neighbor (workspace, WNCK_MOTION_UP))
509
        gtk_widget_show (priv->up_item);
510
      else
511
        gtk_widget_hide (priv->up_item);
512

513
      if (wnck_workspace_get_neighbor (workspace, WNCK_MOTION_DOWN))
514
        gtk_widget_show (priv->down_item);
515
      else
516
        gtk_widget_hide (priv->down_item);
517 518 519
    }
  else
    {
520 521 522 523
      gtk_widget_hide (priv->left_item);
      gtk_widget_hide (priv->right_item);
      gtk_widget_hide (priv->up_item);
      gtk_widget_hide (priv->down_item);
524
    }
525

526 527 528 529 530 531 532 533 534 535 536
  if (viewport_mode)
    {
      int viewport_width, viewport_height;
      int screen_width, screen_height;

      viewport_width = wnck_workspace_get_width (workspace);
      viewport_height = wnck_workspace_get_height (workspace);

      screen_width = wnck_screen_get_width (screen);
      screen_height = wnck_screen_get_height (screen);

537 538 539
      gtk_widget_show (priv->workspace_separator);
      gtk_widget_show (priv->pin_item);
      gtk_widget_show (priv->unpin_item);
540 541 542
      if (viewport_width  >= 2 * screen_width ||
          viewport_height >= 2 * screen_height)
        {
543 544
          gtk_widget_show (priv->workspace_item);
          refill_submenu_viewport (menu);
545 546 547
        }
      else
        {
548 549
          gtk_widget_hide (priv->workspace_item);
          gtk_menu_popdown (GTK_MENU (gtk_menu_item_get_submenu (GTK_MENU_ITEM (priv->workspace_item))));
550 551 552
        }
    }
  else if (wnck_screen_get_workspace_count (screen) > 1)
553
    {
554 555 556 557 558
      gtk_widget_show (priv->workspace_separator);
      gtk_widget_show (priv->pin_item);
      gtk_widget_show (priv->unpin_item);
      gtk_widget_show (priv->workspace_item);
      refill_submenu_workspace (menu);
559 560 561
    }
  else
    {
562 563 564 565 566
      gtk_widget_hide (priv->workspace_separator);
      gtk_widget_hide (priv->pin_item);
      gtk_widget_hide (priv->unpin_item);
      gtk_widget_hide (priv->workspace_item);
      gtk_menu_popdown (GTK_MENU (gtk_menu_item_get_submenu (GTK_MENU_ITEM (priv->workspace_item))));
567
    }
568

569
  gtk_menu_reposition (GTK_MENU (menu));
570

571 572 573 574
  return FALSE;
}

static void
575
queue_update (WnckActionMenu *menu)
576
{
577 578 579
  if (menu->priv->idle_handler == 0)
    menu->priv->idle_handler = g_idle_add ((GSourceFunc)update_menu_state,
                                           menu);
580 581 582 583 584 585 586 587
}

static void
state_changed_callback (WnckWindow     *window,
                        WnckWindowState changed_mask,
                        WnckWindowState new_state,
                        gpointer        data)
{
588
  queue_update (WNCK_ACTION_MENU (data));
589 590 591 592 593 594 595 596
}

static void
actions_changed_callback (WnckWindow       *window,
                          WnckWindowActions changed_mask,
                          WnckWindowActions new_actions,
                          gpointer          data)
{
597
  queue_update (WNCK_ACTION_MENU (data));
598 599
}

600 601 602 603
static void
workspace_changed_callback (WnckWindow *window,
                            gpointer    data)
{
604
  queue_update (WNCK_ACTION_MENU (data));
605 606 607 608 609 610 611
}

static void
screen_workspace_callback (WnckWindow    *window,
                           WnckWorkspace *space,
                           gpointer       data)
{
612
  queue_update (WNCK_ACTION_MENU (data));
613 614
}

615 616 617 618
static void
viewports_changed_callback (WnckWindow *window,
                            gpointer    data)
{
619
  queue_update (WNCK_ACTION_MENU (data));
620 621
}

622
static GtkWidget*
623 624 625
make_radio_menu_item (WindowAction   action,
                      GSList       **group,
                      const gchar   *mnemonic_text)
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
{
  GtkWidget *mi;

  mi = gtk_radio_menu_item_new_with_mnemonic (*group, mnemonic_text);
  *group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (mi));

  g_signal_connect (G_OBJECT (mi), "activate",
                    G_CALLBACK (item_activated_callback),
                    GINT_TO_POINTER (action));

  gtk_widget_show (mi);

  return mi;
}

641
static GtkWidget*
642 643
make_check_menu_item (WindowAction  action,
                      const gchar  *mnemonic_text)
644 645 646 647 648 649 650 651 652 653 654 655 656 657
{
  GtkWidget *mi;

  mi = gtk_check_menu_item_new_with_mnemonic (mnemonic_text);

  g_signal_connect (G_OBJECT (mi), "activate",
                    G_CALLBACK (item_activated_callback),
                    GINT_TO_POINTER (action));

  gtk_widget_show (mi);

  return mi;
}

658
static GtkWidget*
659
make_menu_item (WindowAction action)
660 661
{
  GtkWidget *mi;
662

663
  mi = gtk_menu_item_new_with_label ("");
664

665 666 667
  g_signal_connect (G_OBJECT (mi), "activate",
                    G_CALLBACK (item_activated_callback),
                    GINT_TO_POINTER (action));
668

669 670 671 672 673
  gtk_widget_show (mi);

  return mi;
}

674 675 676 677 678 679
static char *
get_workspace_name_with_accel (WnckWindow *window,
			       int index)
{
  const char *name;
  int number;
680

681 682 683 684 685 686 687 688 689 690 691 692
  name = wnck_workspace_get_name (wnck_screen_get_workspace (wnck_window_get_screen (window),
				  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) {
693
      /* Keep this in sync with what is in refill_submenu_viewport() */
694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746
      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;
  }
}

747
static void
748
refill_submenu_workspace (WnckActionMenu *menu)
749 750 751 752 753 754 755
{
  GtkWidget *submenu;
  GList *children;
  GList *l;
  int num_workspaces, window_space, i;
  WnckWorkspace *workspace;

756
  submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu->priv->workspace_item));
757 758 759 760 761 762 763

  /* Remove existing items */
  children = gtk_container_get_children (GTK_CONTAINER (submenu));
  for (l = children; l; l = l->next)
    gtk_container_remove (GTK_CONTAINER (submenu), l->data);
  g_list_free (children);

764
  workspace = wnck_window_get_workspace (menu->priv->window);
765

766
  num_workspaces = wnck_screen_get_workspace_count (wnck_window_get_screen (menu->priv->window));
767 768 769 770 771 772 773 774

  if (workspace)
    window_space = wnck_workspace_get_number (workspace);
  else
    window_space = -1;

  for (i = 0; i < num_workspaces; i++)
    {
775
      char      *name;
776
      GtkWidget *item;
Vincent Untz's avatar
Vincent Untz committed
777

778
      name = get_workspace_name_with_accel (menu->priv->window, i);
779

780
      item = make_menu_item (MOVE_TO_WORKSPACE);
781 782 783 784 785 786
      g_object_set_data (G_OBJECT (item), "workspace", GINT_TO_POINTER (i));

      if (i == window_space)
        gtk_widget_set_sensitive (item, FALSE);

      gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
787
      set_item_text (item, name);
788 789

      g_free (name);
790 791 792 793 794 795
    }

  gtk_menu_reposition (GTK_MENU (submenu));
}

static void
796
refill_submenu_viewport (WnckActionMenu *menu)
797 798 799 800 801 802 803 804 805 806 807 808 809
{
  GtkWidget *submenu;
  GList *children;
  GList *l;
  WnckScreen *screen;
  WnckWorkspace *workspace;
  int window_x, window_y;
  int viewport_x, viewport_y;
  int viewport_width, viewport_height;
  int screen_width, screen_height;
  int x, y;
  int number;

810
  submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu->priv->workspace_item));
811 812 813 814 815 816 817

  /* Remove existing items */
  children = gtk_container_get_children (GTK_CONTAINER (submenu));
  for (l = children; l; l = l->next)
    gtk_container_remove (GTK_CONTAINER (submenu), l->data);
  g_list_free (children);

818
  screen = wnck_window_get_screen (menu->priv->window);
819 820
  workspace = wnck_screen_get_workspace (screen, 0);

821 822
  wnck_window_get_geometry (menu->priv->window,
                            &window_x, &window_y, NULL, NULL);
823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853

  viewport_x = wnck_workspace_get_viewport_x (workspace);
  viewport_y = wnck_workspace_get_viewport_y (workspace);

  window_x += viewport_x;
  window_y += viewport_y;

  viewport_width = wnck_workspace_get_width (workspace);
  viewport_height = wnck_workspace_get_height (workspace);

  screen_width = wnck_screen_get_width (screen);
  screen_height = wnck_screen_get_height (screen);

  number = 1;
  for (y = 0; y < viewport_height; y += screen_height)
    {
      char      *label;
      GtkWidget *item;

      for (x = 0; x < viewport_width; x += screen_width)
        {
          /* Keep this in sync with what is in get_workspace_name_with_accel()
           */
          if (number == 10)
            label = g_strdup_printf (_("Workspace 1_0"));
          else
            label = g_strdup_printf (_("Workspace %s%d"),
                                     number < 10 ? "_" : "",
                                     number);
          number++;

854
          item = make_menu_item (MOVE_TO_WORKSPACE);
855 856 857 858 859 860 861 862 863 864 865 866 867 868
          g_object_set_data (G_OBJECT (item), "viewport_x",
                             GINT_TO_POINTER (x));
          g_object_set_data (G_OBJECT (item), "viewport_y",
                             GINT_TO_POINTER (y));

          if (window_x >= x && window_x < x + screen_width &&
              window_y >= y && window_y < y + screen_height)
            gtk_widget_set_sensitive (item, FALSE);

          gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
          set_item_text (item, label);

          g_free (label);
        }
869 870 871 872 873
    }

  gtk_menu_reposition (GTK_MENU (submenu));
}

874 875 876 877 878
static void
wnck_action_menu_get_property (GObject    *object,
                               guint       prop_id,
                               GValue     *value,
                               GParamSpec *pspec)
879
{
880
  WnckActionMenu *menu;
881

882
  g_return_if_fail (WNCK_IS_ACTION_MENU (object));
883

884
  menu = WNCK_ACTION_MENU (object);
885

886 887 888 889 890 891 892 893 894 895
  switch (prop_id)
    {
      case PROP_WINDOW:
        g_value_set_pointer (value, menu->priv->window);
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}
896

897

898 899 900 901 902 903 904
static void
wnck_action_menu_set_property (GObject      *object,
                               guint         prop_id,
                               const GValue *value,
                               GParamSpec   *pspec)
{
  WnckActionMenu *menu;
905

906
  g_return_if_fail (WNCK_IS_ACTION_MENU (object));
907

908
  menu = WNCK_ACTION_MENU (object);
909

910 911 912
  switch (prop_id)
    {
      case PROP_WINDOW:
913 914
        g_return_if_fail (WNCK_IS_WINDOW (g_value_get_pointer (value)));

915 916 917 918 919 920 921 922 923 924 925 926
        menu->priv->window = g_value_get_pointer (value);
        g_object_notify (G_OBJECT (menu), "window");
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

static void
wnck_action_menu_init (WnckActionMenu *menu)
{
927
  menu->priv = wnck_action_menu_get_instance_private (menu);
928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967

  menu->priv->window = NULL;
  menu->priv->minimize_item = NULL;
  menu->priv->maximize_item = NULL;
  menu->priv->above_item = NULL;
  menu->priv->move_item = NULL;
  menu->priv->resize_item = NULL;
  menu->priv->close_item = NULL;
  menu->priv->workspace_separator = NULL;
  menu->priv->pin_item = NULL;
  menu->priv->unpin_item = NULL;
  menu->priv->left_item = NULL;
  menu->priv->right_item = NULL;
  menu->priv->up_item = NULL;
  menu->priv->down_item = NULL;
  menu->priv->workspace_item = NULL;
  menu->priv->idle_handler = 0;
}

static GObject *
wnck_action_menu_constructor (GType                  type,
                              guint                  n_construct_properties,
                              GObjectConstructParam *construct_properties)
{
  GObject               *obj;
  WnckActionMenu        *menu;
  WnckActionMenuPrivate *priv;
  GtkWidget             *submenu;
  GtkWidget             *separator;
  GSList                *pin_group;
  WnckScreen            *screen;


  obj = G_OBJECT_CLASS (wnck_action_menu_parent_class)->constructor (type,
                                                                     n_construct_properties,
                                                                     construct_properties);

  menu = WNCK_ACTION_MENU (obj);
  priv = menu->priv;

968 969 970 971 972 973
  if (priv->window == NULL)
    {
      g_warning ("No window specified during creation of the action menu");
      return obj;
    }

974 975 976
  g_object_weak_ref (G_OBJECT (priv->window), window_weak_notify, menu);

  priv->minimize_item = make_menu_item (MINIMIZE);
977

978
  gtk_menu_shell_append (GTK_MENU_SHELL (menu),
979
                         priv->minimize_item);
980

981
  priv->maximize_item = make_menu_item (MAXIMIZE);
982

983
  gtk_menu_shell_append (GTK_MENU_SHELL (menu),
984
                         priv->maximize_item);
985

986
  priv->move_item = make_menu_item (MOVE);
987
  gtk_menu_shell_append (GTK_MENU_SHELL (menu),
988
                         priv->move_item);
989

990
  set_item_text (priv->move_item, _("_Move"));
991

992
  priv->resize_item = make_menu_item (RESIZE);
993
  gtk_menu_shell_append (GTK_MENU_SHELL (menu),
994
                         priv->resize_item);
995

996
  set_item_text (priv->resize_item, _("_Resize"));
997

998
  priv->workspace_separator = separator = gtk_separator_menu_item_new ();
999 1000 1001 1002
  gtk_widget_show (separator);
  gtk_menu_shell_append (GTK_MENU_SHELL (menu),
                         separator);

1003
  priv->above_item = make_check_menu_item (ABOVE,
1004
                                          _("Always On _Top"));
1005

1006
  gtk_menu_shell_append (GTK_MENU_SHELL (menu),
1007
                         priv->above_item);
1008

1009 1010
  pin_group = NULL;

1011
  priv->pin_item = make_radio_menu_item (PIN, &pin_group,
1012 1013
                                        _("_Always on Visible Workspace"));
  gtk_menu_shell_append (GTK_MENU_SHELL (menu),
1014
                         priv->pin_item);
1015

1016
  priv->unpin_item = make_radio_menu_item (UNPIN, &pin_group,
1017
                                          _("_Only on This Workspace"));
1018
  gtk_menu_shell_append (GTK_MENU_SHELL (menu),
1019
                         priv->unpin_item);
1020

1021
  priv->left_item = make_menu_item (LEFT);
1022
  gtk_menu_shell_append (GTK_MENU_SHELL (menu),
1023 1024
                         priv->left_item);
  set_item_text (priv->left_item, _("Move to Workspace _Left"));
1025

1026
  priv->right_item = make_menu_item (RIGHT);
1027
  gtk_menu_shell_append (GTK_MENU_SHELL (menu),
1028 1029
                         priv->right_item);
  set_item_text (priv->right_item, _("Move to Workspace R_ight"));
1030

1031
  priv->up_item = make_menu_item (UP);
1032
  gtk_menu_shell_append (GTK_MENU_SHELL (menu),
1033 1034
                         priv->up_item);
  set_item_text (priv->up_item, _("Move to Workspace _Up"));
1035

1036
  priv->down_item = make_menu_item (DOWN);
1037
  gtk_menu_shell_append (GTK_MENU_SHELL (menu),
1038 1039
                         priv->down_item);
  set_item_text (priv->down_item, _("Move to Workspace _Down"));
1040

1041 1042
  priv->workspace_item = gtk_menu_item_new_with_mnemonic (_("Move to Another _Workspace"));
  gtk_widget_show (priv->workspace_item);
1043 1044

  submenu = gtk_menu_new ();
1045
  gtk_menu_item_set_submenu (GTK_MENU_ITEM (priv->workspace_item),
1046 1047 1048
                             submenu);

  gtk_menu_shell_append (GTK_MENU_SHELL (menu),
1049
                         priv->workspace_item);
1050

1051 1052 1053 1054 1055
  separator = gtk_separator_menu_item_new ();
  gtk_widget_show (separator);
  gtk_menu_shell_append (GTK_MENU_SHELL (menu),
                         separator);

1056
  priv->close_item = make_menu_item (CLOSE);
1057

1058
  gtk_menu_shell_append (GTK_MENU_SHELL (menu),
1059
                         priv->close_item);
1060

1061
  set_item_text (priv->close_item, _("_Close"));
1062

1063
  g_signal_connect_object (G_OBJECT (priv->window),
1064 1065 1066 1067 1068
                           "state_changed",
                           G_CALLBACK (state_changed_callback),
                           G_OBJECT (menu),
                           0);

1069
  g_signal_connect_object (G_OBJECT (priv->window),
1070 1071 1072 1073 1074
                           "actions_changed",
                           G_CALLBACK (actions_changed_callback),
                           G_OBJECT (menu),
                           0);

1075
  g_signal_connect_object (G_OBJECT (priv->window),
1076 1077 1078 1079 1080
                           "workspace_changed",
                           G_CALLBACK (workspace_changed_callback),
                           G_OBJECT (menu),
                           0);

1081
  screen = wnck_window_get_screen (priv->window);
1082

1083
  g_signal_connect_object (G_OBJECT (screen),
1084 1085 1086 1087
                           "workspace_created",
                           G_CALLBACK (screen_workspace_callback),
                           G_OBJECT (menu),
                           0);
1088 1089

  g_signal_connect_object (G_OBJECT (screen),
1090 1091 1092 1093 1094
                           "workspace_destroyed",
                           G_CALLBACK (screen_workspace_callback),
                           G_OBJECT (menu),
                           0);

1095 1096 1097 1098 1099 1100
  g_signal_connect_object (G_OBJECT (screen),
                           "viewports_changed",
                           G_CALLBACK (viewports_changed_callback),
                           G_OBJECT (menu),
                           0);

1101
  update_menu_state (menu);
1102

1103
  return obj;
1104 1105
}

1106 1107 1108 1109 1110 1111 1112 1113
static void
wnck_action_menu_class_init (WnckActionMenuClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->constructor = wnck_action_menu_constructor;
  object_class->get_property = wnck_action_menu_get_property;
  object_class->set_property = wnck_action_menu_set_property;
1114
  object_class->dispose = wnck_action_menu_dispose;
1115 1116 1117 1118 1119 1120 1121 1122 1123 1124

  g_object_class_install_property (object_class,
                                   PROP_WINDOW,
                                   g_param_spec_pointer ("window",
                                                         "Window",
                                                         "The window that will be manipulated through this menu",
                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
}

static void
1125
wnck_action_menu_dispose (GObject *object)
1126 1127 1128 1129 1130 1131
{
  WnckActionMenu *menu;

  menu = WNCK_ACTION_MENU (object);

  if (menu->priv->idle_handler)
1132 1133 1134 1135 1136 1137 1138
    {
      g_source_remove (menu->priv->idle_handler);
      menu->priv->idle_handler = 0;
    }

  if (WNCK_IS_WINDOW (menu->priv->window))
    {
1139 1140
      WnckScreen *screen;

1141 1142 1143
      g_object_weak_unref (G_OBJECT (menu->priv->window), window_weak_notify, menu);
      g_signal_handlers_disconnect_by_data (menu->priv->window, menu);

1144
      screen = wnck_window_get_screen (menu->priv->window);
1145 1146 1147 1148
      g_signal_handlers_disconnect_by_data (screen, menu);

      menu->priv->window = NULL;
    }
1149

1150
  G_OBJECT_CLASS (wnck_action_menu_parent_class)->dispose (object);
1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172
}

/**
 * wnck_action_menu_new:
 * @window: the #WnckWindow for which a menu will be created.
 *
 * Creates a new #WnckActionMenu. The #WnckActionMenu will be filled with menu
 * items for window operations on @window.
 *
 * Return value: a newly created #WnckActionMenu.
 *
 * Since: 2.22
 **/
GtkWidget*
wnck_action_menu_new (WnckWindow *window)
{
  g_return_val_if_fail (WNCK_IS_WINDOW (window), NULL);

  return g_object_new (WNCK_TYPE_ACTION_MENU,
                       "window", window,
                       NULL);
}