gtkapplicationwindow.c 36.4 KB
Newer Older
Ryan Lortie's avatar
Ryan Lortie committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*
 * Copyright © 2011 Canonical Limited
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the licence, 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
Javier Jardon's avatar
Javier Jardon committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
Ryan Lortie's avatar
Ryan Lortie committed
16 17 18 19 20 21 22 23
 *
 * Author: Ryan Lortie <desrt@desrt.ca>
 */

#include "config.h"

#include "gtkapplicationwindow.h"

24
#include "gtkapplicationprivate.h"
25
#include "gtkwidgetprivate.h"
26
#include "gtkwindowprivate.h"
27
#include "gtkheaderbar.h"
28
#include "gtkmenubar.h"
29
#include "gtkintl.h"
30
#include "gtksettings.h"
31
#include "gtkshortcutswindowprivate.h"
Ryan Lortie's avatar
Ryan Lortie committed
32

Christoph Reiter's avatar
Christoph Reiter committed
33
#if defined(HAVE_GIO_UNIX) && !defined(__APPLE__)
34 35 36
#include <gio/gdesktopappinfo.h>
#endif

Matthias Clasen's avatar
Matthias Clasen committed
37 38 39 40 41
/**
 * SECTION:gtkapplicationwindow
 * @title: GtkApplicationWindow
 * @short_description: GtkWindow subclass with GtkApplication support
 *
42
 * #GtkApplicationWindow is a #GtkWindow subclass that offers some
43 44
 * extra functionality for better integration with #GtkApplication
 * features.  Notably, it can handle both the application menu as well
45 46
 * as the menubar. See gtk_application_set_app_menu() and
 * gtk_application_set_menubar().
47 48 49 50
 *
 * This class implements the #GActionGroup and #GActionMap interfaces,
 * to let you add window-specific actions that will be exported by the
 * associated #GtkApplication, together with its application-wide
51 52
 * actions.  Window-specific actions are prefixed with the “win.”
 * prefix and application-wide actions are prefixed with the “app.”
53 54
 * prefix.  Actions must be addressed with the prefixed name when
 * referring to them from a #GMenuModel.
Matthias Clasen's avatar
Matthias Clasen committed
55
 *
56
 * Note that widgets that are placed inside a #GtkApplicationWindow
57
 * can also activate these actions, if they implement the
58
 * #GtkActionable interface.
59
 *
60 61 62 63
 * As with #GtkApplication, the GDK lock will be acquired when
 * processing actions arriving from other processes and should therefore
 * be held when activating actions locally (if GDK threads are enabled).
 *
64 65 66 67 68 69 70 71 72
 * The settings #GtkSettings:gtk-shell-shows-app-menu and
 * #GtkSettings:gtk-shell-shows-menubar tell GTK+ whether the
 * desktop environment is showing the application menu and menubar
 * models outside the application as part of the desktop shell.
 * For instance, on OS X, both menus will be displayed remotely;
 * on Windows neither will be. gnome-shell (starting with version 3.4)
 * will display the application menu, but not the menubar.
 *
 * If the desktop environment does not display the menubar, then
73
 * #GtkApplicationWindow will automatically show a #GtkMenuBar for it.
74 75
 * This behaviour can be overridden with the #GtkApplicationWindow:show-menubar
 * property. If the desktop environment does not display the application
76 77
 * menu, then it will automatically be included in the menubar or in the
 * windows client-side decorations.
Matthias Clasen's avatar
Matthias Clasen committed
78
 *
79 80
 * ## A GtkApplicationWindow with a menubar
 *
81
 * |[<!-- language="C" -->
82
 * GtkApplication *app = gtk_application_new ("org.gtk.test", 0);
Matthias Clasen's avatar
Matthias Clasen committed
83
 *
84
 * GtkBuilder *builder = gtk_builder_new_from_string (
Matthias Clasen's avatar
Matthias Clasen committed
85 86 87 88 89 90 91
 *     "<interface>"
 *     "  <menu id='menubar'>"
 *     "    <submenu label='_Edit'>"
 *     "      <item label='_Copy' action='win.copy'/>"
 *     "      <item label='_Paste' action='win.paste'/>"
 *     "    </submenu>"
 *     "  </menu>"
92 93
 *     "</interface>",
 *     -1);
94
 *
95 96 97
 * GMenuModel *menubar = G_MENU_MODEL (gtk_builder_get_object (builder,
 *                                                             "menubar"));
 * gtk_application_set_menubar (GTK_APPLICATION (app), menubar);
Matthias Clasen's avatar
Matthias Clasen committed
98 99
 * g_object_unref (builder);
 *
100
 * // ...
Matthias Clasen's avatar
Matthias Clasen committed
101
 *
102
 * GtkWidget *window = gtk_application_window_new (app);
103
 * ]|
104
 *
105 106
 * ## Handling fallback yourself
 *
107
 * [A simple example](https://git.gnome.org/browse/gtk+/tree/examples/sunny.c)
108
 *
109
 * The XML format understood by #GtkBuilder for #GMenuModel consists
110 111 112
 * of a toplevel `<menu>` element, which contains one or more `<item>`
 * elements. Each `<item>` element contains `<attribute>` and `<link>`
 * elements with a mandatory name attribute. `<link>` elements have the
113 114 115
 * same content model as `<menu>`. Instead of `<link name="submenu>` or
 * `<link name="section">`, you can use `<submenu>` or `<section>`
 * elements.
116 117
 *
 * Attribute values can be translated using gettext, like other #GtkBuilder
118 119
 * content. `<attribute>` elements can be marked for translation with a
 * `translatable="yes"` attribute. It is also possible to specify message
120
 * context and translator comments, using the context and comments attributes.
121 122
 * To make use of this, the #GtkBuilder must have been given the gettext
 * domain to use.
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
 *
 * The following attributes are used when constructing menu items:
 * - "label": a user-visible string to display
 * - "action": the prefixed name of the action to trigger
 * - "target": the parameter to use when activating the action
 * - "icon" and "verb-icon": names of icons that may be displayed
 * - "submenu-action": name of an action that may be used to determine
 *      if a submenu can be opened
 * - "hidden-when": a string used to determine when the item will be hidden.
 *      Possible values include "action-disabled", "action-missing", "macos-menubar".
 *
 * The following attributes are used when constructing sections:
 * - "label": a user-visible string to use as section heading
 * - "display-hint": a string used to determine special formatting for the section.
 *     Possible values include "horizontal-buttons".
138 139 140
 * - "text-direction": a string used to determine the #GtkTextDirection to use
 *     when "display-hint" is set to "horizontal-buttons". Possible values
 *     include "rtl", "ltr", and "none".
141 142 143 144
 *
 * The following attributes are used when constructing submenus:
 * - "label": a user-visible string to display
 * - "icon": icon name to display
Matthias Clasen's avatar
Matthias Clasen committed
145
 */
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224

typedef GSimpleActionGroupClass GtkApplicationWindowActionsClass;
typedef struct
{
  GSimpleActionGroup parent_instance;
  GtkWindow *window;
} GtkApplicationWindowActions;

static GType gtk_application_window_actions_get_type   (void);
static void  gtk_application_window_actions_iface_init (GRemoteActionGroupInterface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkApplicationWindowActions, gtk_application_window_actions, G_TYPE_SIMPLE_ACTION_GROUP,
                         G_IMPLEMENT_INTERFACE (G_TYPE_REMOTE_ACTION_GROUP, gtk_application_window_actions_iface_init))

static void
gtk_application_window_actions_activate_action_full (GRemoteActionGroup *remote,
                                                     const gchar        *action_name,
                                                     GVariant           *parameter,
                                                     GVariant           *platform_data)
{
  GtkApplicationWindowActions *actions = (GtkApplicationWindowActions *) remote;
  GApplication *application;
  GApplicationClass *class;

  application = G_APPLICATION (gtk_window_get_application (actions->window));
  class = G_APPLICATION_GET_CLASS (application);

  class->before_emit (application, platform_data);
  g_action_group_activate_action (G_ACTION_GROUP (actions), action_name, parameter);
  class->after_emit (application, platform_data);
}

static void
gtk_application_window_actions_change_action_state_full (GRemoteActionGroup *remote,
                                                         const gchar        *action_name,
                                                         GVariant           *value,
                                                         GVariant           *platform_data)
{
  GtkApplicationWindowActions *actions = (GtkApplicationWindowActions *) remote;
  GApplication *application;
  GApplicationClass *class;

  application = G_APPLICATION (gtk_window_get_application (actions->window));
  class = G_APPLICATION_GET_CLASS (application);

  class->before_emit (application, platform_data);
  g_action_group_change_action_state (G_ACTION_GROUP (actions), action_name, value);
  class->after_emit (application, platform_data);
}

static void
gtk_application_window_actions_init (GtkApplicationWindowActions *actions)
{
}

static void
gtk_application_window_actions_iface_init (GRemoteActionGroupInterface *iface)
{
  iface->activate_action_full = gtk_application_window_actions_activate_action_full;
  iface->change_action_state_full = gtk_application_window_actions_change_action_state_full;
}

static void
gtk_application_window_actions_class_init (GtkApplicationWindowActionsClass *class)
{
}

static GSimpleActionGroup *
gtk_application_window_actions_new (GtkApplicationWindow *window)
{
  GtkApplicationWindowActions *actions;

  actions = g_object_new (gtk_application_window_actions_get_type (), NULL);
  actions->window = GTK_WINDOW (window);

  return G_SIMPLE_ACTION_GROUP (actions);
}

/* Now onto GtkApplicationWindow... */

Ryan Lortie's avatar
Ryan Lortie committed
225 226
struct _GtkApplicationWindowPrivate
{
227
  GSimpleActionGroup *actions;
228
  GtkWidget *menubar;
Ryan Lortie's avatar
Ryan Lortie committed
229

230
  gboolean show_menubar;
231 232
  GMenu *app_menu_section;
  GMenu *menubar_section;
233

234
  guint            id;
235 236

  GtkShortcutsWindow *help_overlay;
Ryan Lortie's avatar
Ryan Lortie committed
237 238
};

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
static void
gtk_application_window_update_menubar (GtkApplicationWindow *window)
{
  gboolean should_have_menubar;
  gboolean have_menubar;

  have_menubar = window->priv->menubar != NULL;

  should_have_menubar = window->priv->show_menubar &&
                        (g_menu_model_get_n_items (G_MENU_MODEL (window->priv->app_menu_section)) ||
                         g_menu_model_get_n_items (G_MENU_MODEL (window->priv->menubar_section)));

  if (have_menubar && !should_have_menubar)
    {
      gtk_widget_unparent (window->priv->menubar);
      window->priv->menubar = NULL;

      gtk_widget_queue_resize (GTK_WIDGET (window));
    }

  if (!have_menubar && should_have_menubar)
    {
      GMenu *combined;

      combined = g_menu_new ();
      g_menu_append_section (combined, NULL, G_MENU_MODEL (window->priv->app_menu_section));
      g_menu_append_section (combined, NULL, G_MENU_MODEL (window->priv->menubar_section));

267
      window->priv->menubar = gtk_menu_bar_new_from_model (G_MENU_MODEL (combined));
268 269 270 271 272 273 274
      gtk_widget_set_parent (window->priv->menubar, GTK_WIDGET (window));
      gtk_widget_show_all (window->priv->menubar);
      g_object_unref (combined);

      gtk_widget_queue_resize (GTK_WIDGET (window));
    }
}
275

276
static gchar *
Matthias Clasen's avatar
Matthias Clasen committed
277
gtk_application_window_get_app_desktop_name (void)
278 279 280
{
  gchar *retval = NULL;

Christoph Reiter's avatar
Christoph Reiter committed
281
#if defined(HAVE_GIO_UNIX) && !defined(__APPLE__)
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
  GDesktopAppInfo *app_info;
  const gchar *app_name = NULL;
  gchar *desktop_file;

  desktop_file = g_strconcat (g_get_prgname (), ".desktop", NULL);
  app_info = g_desktop_app_info_new (desktop_file);
  g_free (desktop_file);

  if (app_info != NULL)
    app_name = g_app_info_get_name (G_APP_INFO (app_info));

  if (app_name != NULL)
    retval = g_strdup (app_name);

  g_clear_object (&app_info);
#endif /* HAVE_GIO_UNIX */

  return retval;
}

302
static void
303 304
gtk_application_window_update_shell_shows_app_menu (GtkApplicationWindow *window,
                                                    GtkSettings          *settings)
305
{
306
  gboolean shown_by_shell;
307
  gboolean shown_by_titlebar;
308

309
  g_object_get (settings, "gtk-shell-shows-app-menu", &shown_by_shell, NULL);
310

311
  shown_by_titlebar = _gtk_window_titlebar_shows_app_menu (GTK_WINDOW (window));
312 313

  if (shown_by_shell || shown_by_titlebar)
314 315 316 317 318 319 320 321 322 323
    {
      /* the shell shows it, so don't show it locally */
      if (g_menu_model_get_n_items (G_MENU_MODEL (window->priv->app_menu_section)) != 0)
        g_menu_remove (window->priv->app_menu_section, 0);
    }
  else
    {
      /* the shell does not show it, so make sure we show it */
      if (g_menu_model_get_n_items (G_MENU_MODEL (window->priv->app_menu_section)) == 0)
        {
324
          GMenuModel *app_menu = NULL;
325

326 327
          if (gtk_window_get_application (GTK_WINDOW (window)) != NULL)
            app_menu = gtk_application_get_app_menu (gtk_window_get_application (GTK_WINDOW (window)));
328 329

          if (app_menu != NULL)
330
            {
331 332
              const gchar *app_name;
              gchar *name;
333

334 335
              app_name = g_get_application_name ();
              if (app_name != g_get_prgname ())
336
                {
337 338
                  /* the app has set its application name, use it */
                  name = g_strdup (app_name);
339
                }
340 341 342 343 344 345 346 347
              else
                {
                  /* get the name from .desktop file */
                  name = gtk_application_window_get_app_desktop_name ();
                  if (name == NULL)
                    name = g_strdup (_("Application"));
                }

348
              g_menu_append_submenu (window->priv->app_menu_section, name, app_menu);
349
              g_free (name);
350
            }
351 352 353 354 355
        }
    }
}

static void
356 357
gtk_application_window_update_shell_shows_menubar (GtkApplicationWindow *window,
                                                   GtkSettings          *settings)
358 359 360
{
  gboolean shown_by_shell;

361
  g_object_get (settings, "gtk-shell-shows-menubar", &shown_by_shell, NULL);
362 363 364 365 366 367 368 369

  if (shown_by_shell)
    {
      /* the shell shows it, so don't show it locally */
      if (g_menu_model_get_n_items (G_MENU_MODEL (window->priv->menubar_section)) != 0)
        g_menu_remove (window->priv->menubar_section, 0);
    }
  else
370
    {
371 372 373
      /* the shell does not show it, so make sure we show it */
      if (g_menu_model_get_n_items (G_MENU_MODEL (window->priv->menubar_section)) == 0)
        {
374
          GMenuModel *menubar = NULL;
375

376 377
          if (gtk_window_get_application (GTK_WINDOW (window)) != NULL)
            menubar = gtk_application_get_menubar (gtk_window_get_application (GTK_WINDOW (window)));
378 379 380 381

          if (menubar != NULL)
            g_menu_append_section (window->priv->menubar_section, NULL, menubar);
        }
382
    }
383 384
}

385 386 387 388 389 390 391
static void
gtk_application_window_shell_shows_app_menu_changed (GObject    *object,
                                                     GParamSpec *pspec,
                                                     gpointer    user_data)
{
  GtkApplicationWindow *window = user_data;

392
  gtk_application_window_update_shell_shows_app_menu (window, GTK_SETTINGS (object));
393 394 395 396 397 398 399 400 401 402
  gtk_application_window_update_menubar (window);
}

static void
gtk_application_window_shell_shows_menubar_changed (GObject    *object,
                                                    GParamSpec *pspec,
                                                    gpointer    user_data)
{
  GtkApplicationWindow *window = user_data;

403
  gtk_application_window_update_shell_shows_menubar (window, GTK_SETTINGS (object));
404 405 406
  gtk_application_window_update_menubar (window);
}

407 408 409 410 411
static gchar **
gtk_application_window_list_actions (GActionGroup *group)
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (group);

412 413 414 415
  /* may be NULL after dispose has run */
  if (!window->priv->actions)
    return g_new0 (char *, 0 + 1);

416 417 418 419 420 421 422 423 424 425 426 427 428 429
  return g_action_group_list_actions (G_ACTION_GROUP (window->priv->actions));
}

static gboolean
gtk_application_window_query_action (GActionGroup        *group,
                                     const gchar         *action_name,
                                     gboolean            *enabled,
                                     const GVariantType **parameter_type,
                                     const GVariantType **state_type,
                                     GVariant           **state_hint,
                                     GVariant           **state)
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (group);

430 431 432
  if (!window->priv->actions)
    return FALSE;

433 434 435 436
  return g_action_group_query_action (G_ACTION_GROUP (window->priv->actions),
                                      action_name, enabled, parameter_type, state_type, state_hint, state);
}

437 438 439 440 441 442 443
static void
gtk_application_window_activate_action (GActionGroup *group,
                                        const gchar  *action_name,
                                        GVariant     *parameter)
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (group);

444 445 446 447
  if (!window->priv->actions)
    return;

  g_action_group_activate_action (G_ACTION_GROUP (window->priv->actions), action_name, parameter);
448 449 450 451 452 453 454 455 456
}

static void
gtk_application_window_change_action_state (GActionGroup *group,
                                            const gchar  *action_name,
                                            GVariant     *state)
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (group);

457 458 459 460
  if (!window->priv->actions)
    return;

  g_action_group_change_action_state (G_ACTION_GROUP (window->priv->actions), action_name, state);
461 462
}

463
static GAction *
Matthias Clasen's avatar
Matthias Clasen committed
464
gtk_application_window_lookup_action (GActionMap  *action_map,
465 466 467 468
                                      const gchar *action_name)
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (action_map);

469 470 471
  if (!window->priv->actions)
    return NULL;

472 473 474 475 476 477 478 479 480
  return g_action_map_lookup_action (G_ACTION_MAP (window->priv->actions), action_name);
}

static void
gtk_application_window_add_action (GActionMap *action_map,
                                   GAction    *action)
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (action_map);

481 482 483
  if (!window->priv->actions)
    return;

484 485 486 487 488 489 490 491 492
  g_action_map_add_action (G_ACTION_MAP (window->priv->actions), action);
}

static void
gtk_application_window_remove_action (GActionMap  *action_map,
                                      const gchar *action_name)
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (action_map);

493 494 495
  if (!window->priv->actions)
    return;

496 497 498 499 500 501 502 503
  g_action_map_remove_action (G_ACTION_MAP (window->priv->actions), action_name);
}

static void
gtk_application_window_group_iface_init (GActionGroupInterface *iface)
{
  iface->list_actions = gtk_application_window_list_actions;
  iface->query_action = gtk_application_window_query_action;
504 505
  iface->activate_action = gtk_application_window_activate_action;
  iface->change_action_state = gtk_application_window_change_action_state;
506 507 508 509 510 511 512 513 514 515 516
}

static void
gtk_application_window_map_iface_init (GActionMapInterface *iface)
{
  iface->lookup_action = gtk_application_window_lookup_action;
  iface->add_action = gtk_application_window_add_action;
  iface->remove_action = gtk_application_window_remove_action;
}

G_DEFINE_TYPE_WITH_CODE (GtkApplicationWindow, gtk_application_window, GTK_TYPE_WINDOW,
517
                         G_ADD_PRIVATE (GtkApplicationWindow)
518 519
                         G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, gtk_application_window_group_iface_init)
                         G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_MAP, gtk_application_window_map_iface_init))
Ryan Lortie's avatar
Ryan Lortie committed
520 521 522

enum {
  PROP_0,
523
  PROP_SHOW_MENUBAR,
Ryan Lortie's avatar
Ryan Lortie committed
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541
  N_PROPS
};
static GParamSpec *gtk_application_window_properties[N_PROPS];

static void
gtk_application_window_real_get_preferred_height (GtkWidget *widget,
                                                  gint      *minimum_height,
                                                  gint      *natural_height)
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (widget);

  GTK_WIDGET_CLASS (gtk_application_window_parent_class)
    ->get_preferred_height (widget, minimum_height, natural_height);

  if (window->priv->menubar != NULL)
    {
      gint menubar_min_height, menubar_nat_height;

542
      gtk_widget_get_preferred_height (window->priv->menubar, &menubar_min_height, &menubar_nat_height);
Ryan Lortie's avatar
Ryan Lortie committed
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562
      *minimum_height += menubar_min_height;
      *natural_height += menubar_nat_height;
    }
}

static void
gtk_application_window_real_get_preferred_height_for_width (GtkWidget *widget,
                                                            gint       width,
                                                            gint      *minimum_height,
                                                            gint      *natural_height)
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (widget);

  GTK_WIDGET_CLASS (gtk_application_window_parent_class)
    ->get_preferred_height_for_width (widget, width, minimum_height, natural_height);

  if (window->priv->menubar != NULL)
    {
      gint menubar_min_height, menubar_nat_height;

563
      gtk_widget_get_preferred_height_for_width (window->priv->menubar, width, &menubar_min_height, &menubar_nat_height);
Ryan Lortie's avatar
Ryan Lortie committed
564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
      *minimum_height += menubar_min_height;
      *natural_height += menubar_nat_height;
    }
}

static void
gtk_application_window_real_get_preferred_width (GtkWidget *widget,
                                                 gint      *minimum_width,
                                                 gint      *natural_width)
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (widget);

  GTK_WIDGET_CLASS (gtk_application_window_parent_class)
    ->get_preferred_width (widget, minimum_width, natural_width);

  if (window->priv->menubar != NULL)
    {
      gint menubar_min_width, menubar_nat_width;
582 583
      gint border_width;
      GtkBorder border = { 0 };
Ryan Lortie's avatar
Ryan Lortie committed
584

585
      gtk_widget_get_preferred_width (window->priv->menubar, &menubar_min_width, &menubar_nat_width);
586 587

      border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
588
      _gtk_window_get_shadow_width (GTK_WINDOW (widget), &border);
589 590 591 592

      menubar_min_width += 2 * border_width + border.left + border.right;
      menubar_nat_width += 2 * border_width + border.left + border.right;

Ryan Lortie's avatar
Ryan Lortie committed
593 594 595 596 597 598 599 600 601 602 603 604
      *minimum_width = MAX (*minimum_width, menubar_min_width);
      *natural_width = MAX (*natural_width, menubar_nat_width);
    }
}

static void
gtk_application_window_real_get_preferred_width_for_height (GtkWidget *widget,
                                                            gint       height,
                                                            gint      *minimum_width,
                                                            gint      *natural_width)
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (widget);
605 606 607 608 609 610
  gint menubar_height;

  if (window->priv->menubar != NULL)
    gtk_widget_get_preferred_height (window->priv->menubar, &menubar_height, NULL);
  else
    menubar_height = 0;
Ryan Lortie's avatar
Ryan Lortie committed
611 612

  GTK_WIDGET_CLASS (gtk_application_window_parent_class)
613
    ->get_preferred_width_for_height (widget, height - menubar_height, minimum_width, natural_width);
Ryan Lortie's avatar
Ryan Lortie committed
614 615 616 617

  if (window->priv->menubar != NULL)
    {
      gint menubar_min_width, menubar_nat_width;
618 619
      gint border_width;
      GtkBorder border = { 0 };
Ryan Lortie's avatar
Ryan Lortie committed
620

621
      gtk_widget_get_preferred_width_for_height (window->priv->menubar, menubar_height, &menubar_min_width, &menubar_nat_width);
622 623

      border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
624
      _gtk_window_get_shadow_width (GTK_WINDOW (widget), &border);
625 626 627 628

      menubar_min_width += 2 * border_width + border.left + border.right;
      menubar_nat_width += 2 * border_width + border.left + border.right;

Ryan Lortie's avatar
Ryan Lortie committed
629 630 631 632 633 634 635 636 637 638 639 640 641
      *minimum_width = MAX (*minimum_width, menubar_min_width);
      *natural_width = MAX (*natural_width, menubar_nat_width);
    }
}

static void
gtk_application_window_real_size_allocate (GtkWidget     *widget,
                                           GtkAllocation *allocation)
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (widget);

  if (window->priv->menubar != NULL)
    {
642 643
      GtkAllocation menubar_allocation;
      GtkAllocation child_allocation;
644
      gint menubar_height;
Ryan Lortie's avatar
Ryan Lortie committed
645 646
      GtkWidget *child;

647 648
      _gtk_window_set_allocation (GTK_WINDOW (widget), allocation, &child_allocation);
      menubar_allocation = child_allocation;
649

650 651 652
      gtk_widget_get_preferred_height_for_width (window->priv->menubar,
                                                 menubar_allocation.width,
                                                 &menubar_height, NULL);
Ryan Lortie's avatar
Ryan Lortie committed
653

654
      menubar_allocation.height = menubar_height;
655
      gtk_widget_size_allocate (window->priv->menubar, &menubar_allocation);
Ryan Lortie's avatar
Ryan Lortie committed
656

657 658
      child_allocation.y += menubar_height;
      child_allocation.height -= menubar_height;
Ryan Lortie's avatar
Ryan Lortie committed
659 660
      child = gtk_bin_get_child (GTK_BIN (window));
      if (child != NULL && gtk_widget_get_visible (child))
661
        gtk_widget_size_allocate (child, &child_allocation);
Ryan Lortie's avatar
Ryan Lortie committed
662 663 664 665 666 667
    }
  else
    GTK_WIDGET_CLASS (gtk_application_window_parent_class)
      ->size_allocate (widget, allocation);
}

668 669 670 671
static void
gtk_application_window_real_realize (GtkWidget *widget)
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (widget);
672
  GtkSettings *settings;
673

674 675 676 677 678 679 680
  settings = gtk_widget_get_settings (widget);

  g_signal_connect (settings, "notify::gtk-shell-shows-app-menu",
                    G_CALLBACK (gtk_application_window_shell_shows_app_menu_changed), window);
  g_signal_connect (settings, "notify::gtk-shell-shows-menubar",
                    G_CALLBACK (gtk_application_window_shell_shows_menubar_changed), window);

681 682
  GTK_WIDGET_CLASS (gtk_application_window_parent_class)->realize (widget);

683 684
  gtk_application_window_update_shell_shows_app_menu (window, settings);
  gtk_application_window_update_shell_shows_menubar (window, settings);
685
  gtk_application_window_update_menubar (window);
686 687
}

688 689 690 691 692 693 694 695 696 697
static void
gtk_application_window_real_unrealize (GtkWidget *widget)
{
  GtkSettings *settings;

  settings = gtk_widget_get_settings (widget);

  g_signal_handlers_disconnect_by_func (settings, gtk_application_window_shell_shows_app_menu_changed, widget);
  g_signal_handlers_disconnect_by_func (settings, gtk_application_window_shell_shows_menubar_changed, widget);

698
  GTK_WIDGET_CLASS (gtk_application_window_parent_class)->unrealize (widget);
699 700
}

Ryan Lortie's avatar
Ryan Lortie committed
701 702
GActionGroup *
gtk_application_window_get_action_group (GtkApplicationWindow *window)
703
{
Ryan Lortie's avatar
Ryan Lortie committed
704
  return G_ACTION_GROUP (window->priv->actions);
705 706
}

Ryan Lortie's avatar
Ryan Lortie committed
707 708 709 710 711
static void
gtk_application_window_real_map (GtkWidget *widget)
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (widget);

712
  /* XXX could eliminate this by tweaking gtk_window_map */
Ryan Lortie's avatar
Ryan Lortie committed
713
  if (window->priv->menubar)
714
    gtk_widget_map (window->priv->menubar);
Ryan Lortie's avatar
Ryan Lortie committed
715

716
  GTK_WIDGET_CLASS (gtk_application_window_parent_class)->map (widget);
Ryan Lortie's avatar
Ryan Lortie committed
717 718
}

719 720 721 722 723 724 725 726 727 728 729 730
static void
gtk_application_window_real_unmap (GtkWidget *widget)
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (widget);

  /* XXX could eliminate this by tweaking gtk_window_unmap */
  if (window->priv->menubar)
    gtk_widget_unmap (window->priv->menubar);

  GTK_WIDGET_CLASS (gtk_application_window_parent_class)->unmap (widget);
}

Ryan Lortie's avatar
Ryan Lortie committed
731 732 733 734 735 736 737 738 739
static void
gtk_application_window_real_forall_internal (GtkContainer *container,
                                             gboolean      include_internal,
                                             GtkCallback   callback,
                                             gpointer      user_data)
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (container);

  if (window->priv->menubar)
740
    callback (window->priv->menubar, user_data);
Ryan Lortie's avatar
Ryan Lortie committed
741 742 743 744 745 746

  GTK_CONTAINER_CLASS (gtk_application_window_parent_class)
    ->forall (container, include_internal, callback, user_data);
}

static void
747 748 749 750
gtk_application_window_get_property (GObject    *object,
                                     guint       prop_id,
                                     GValue     *value,
                                     GParamSpec *pspec)
Ryan Lortie's avatar
Ryan Lortie committed
751 752 753 754 755
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (object);

  switch (prop_id)
    {
756
    case PROP_SHOW_MENUBAR:
757
      g_value_set_boolean (value, window->priv->show_menubar);
Ryan Lortie's avatar
Ryan Lortie committed
758 759 760 761 762 763 764 765
      break;

    default:
      g_assert_not_reached ();
    }
}

static void
766 767 768 769
gtk_application_window_set_property (GObject      *object,
                                     guint         prop_id,
                                     const GValue *value,
                                     GParamSpec   *pspec)
Ryan Lortie's avatar
Ryan Lortie committed
770 771 772 773 774
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (object);

  switch (prop_id)
    {
775 776
    case PROP_SHOW_MENUBAR:
      gtk_application_window_set_show_menubar (window, g_value_get_boolean (value));
Ryan Lortie's avatar
Ryan Lortie committed
777 778 779 780 781 782 783 784
      break;

    default:
      g_assert_not_reached ();
    }
}

static void
785
gtk_application_window_dispose (GObject *object)
Ryan Lortie's avatar
Ryan Lortie committed
786 787 788
{
  GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (object);

789 790 791 792 793
  if (window->priv->menubar)
    {
      gtk_widget_unparent (window->priv->menubar);
      window->priv->menubar = NULL;
    }
794 795 796

  g_clear_object (&window->priv->app_menu_section);
  g_clear_object (&window->priv->menubar_section);
797 798 799 800 801 802

  if (window->priv->help_overlay)
    {
      gtk_widget_destroy (GTK_WIDGET (window->priv->help_overlay));
      g_clear_object (&window->priv->help_overlay);
    }
Ryan Lortie's avatar
Ryan Lortie committed
803

804
  G_OBJECT_CLASS (gtk_application_window_parent_class)->dispose (object);
805 806 807 808 809 810

  /* We do this below the chain-up above to give us a chance to be
   * removed from the GtkApplication (which is done in the dispose
   * handler of GtkWindow).
   *
   * That reduces our chances of being watched as a GActionGroup from a
811
   * muxer constructed by GtkApplication.
812
   */
813
  g_clear_object (&window->priv->actions);
Ryan Lortie's avatar
Ryan Lortie committed
814 815 816 817 818
}

static void
gtk_application_window_init (GtkApplicationWindow *window)
{
819
  window->priv = gtk_application_window_get_instance_private (window);
820

821
  window->priv->actions = gtk_application_window_actions_new (window);
822 823
  window->priv->app_menu_section = g_menu_new ();
  window->priv->menubar_section = g_menu_new ();
824

825 826
  gtk_widget_insert_action_group (GTK_WIDGET (window), "win", G_ACTION_GROUP (window->priv->actions));

827
  /* window->priv->actions is the one and only ref on the group, so when
828
   * we dispose, the action group will die, disconnecting all signals.
829 830 831 832 833 834 835 836 837
   */
  g_signal_connect_swapped (window->priv->actions, "action-added",
                            G_CALLBACK (g_action_group_action_added), window);
  g_signal_connect_swapped (window->priv->actions, "action-enabled-changed",
                            G_CALLBACK (g_action_group_action_enabled_changed), window);
  g_signal_connect_swapped (window->priv->actions, "action-state-changed",
                            G_CALLBACK (g_action_group_action_state_changed), window);
  g_signal_connect_swapped (window->priv->actions, "action-removed",
                            G_CALLBACK (g_action_group_action_removed), window);
Ryan Lortie's avatar
Ryan Lortie committed
838 839 840 841 842 843 844 845 846 847 848 849 850 851 852
}

static void
gtk_application_window_class_init (GtkApplicationWindowClass *class)
{
  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
  GObjectClass *object_class = G_OBJECT_CLASS (class);

  container_class->forall = gtk_application_window_real_forall_internal;
  widget_class->get_preferred_height = gtk_application_window_real_get_preferred_height;
  widget_class->get_preferred_height_for_width = gtk_application_window_real_get_preferred_height_for_width;
  widget_class->get_preferred_width = gtk_application_window_real_get_preferred_width;
  widget_class->get_preferred_width_for_height = gtk_application_window_real_get_preferred_width_for_height;
  widget_class->size_allocate = gtk_application_window_real_size_allocate;
853
  widget_class->realize = gtk_application_window_real_realize;
854
  widget_class->unrealize = gtk_application_window_real_unrealize;
Ryan Lortie's avatar
Ryan Lortie committed
855
  widget_class->map = gtk_application_window_real_map;
856
  widget_class->unmap = gtk_application_window_real_unmap;
Ryan Lortie's avatar
Ryan Lortie committed
857 858
  object_class->get_property = gtk_application_window_get_property;
  object_class->set_property = gtk_application_window_set_property;
859
  object_class->dispose = gtk_application_window_dispose;
Ryan Lortie's avatar
Ryan Lortie committed
860

861 862 863 864 865
  /**
   * GtkApplicationWindow:show-menubar:
   *
   * If this property is %TRUE, the window will display a menubar
   * that includes the app menu and menubar, unless these are
866 867
   * shown by the desktop shell. See gtk_application_set_app_menu()
   * and gtk_application_set_menubar().
868 869 870 871
   *
   * If %FALSE, the window will not display a menubar, regardless
   * of whether the desktop shell is showing the menus or not.
   */
872 873 874
  gtk_application_window_properties[PROP_SHOW_MENUBAR] =
    g_param_spec_boolean ("show-menubar",
                          P_("Show a menubar"),
875 876
                          P_("TRUE if the window should show a "
                             "menubar at the top of the window"),
877
                          TRUE, G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
Ryan Lortie's avatar
Ryan Lortie committed
878 879 880
  g_object_class_install_properties (object_class, N_PROPS, gtk_application_window_properties);
}

881 882 883 884 885 886 887 888 889 890
/**
 * gtk_application_window_new:
 * @application: a #GtkApplication
 *
 * Creates a new #GtkApplicationWindow.
 *
 * Returns: a newly created #GtkApplicationWindow
 *
 * Since: 3.4
 */
Ryan Lortie's avatar
Ryan Lortie committed
891 892 893 894 895 896 897 898 899 900
GtkWidget *
gtk_application_window_new (GtkApplication *application)
{
  g_return_val_if_fail (GTK_IS_APPLICATION (application), NULL);

  return g_object_new (GTK_TYPE_APPLICATION_WINDOW,
                       "application", application,
                       NULL);
}

901 902 903 904 905 906 907 908 909 910 911
/**
 * gtk_application_window_get_show_menubar:
 * @window: a #GtkApplicationWindow
 *
 * Returns whether the window will display a menubar for the app menu
 * and menubar as needed.
 *
 * Returns: %TRUE if @window will display a menubar when needed
 *
 * Since: 3.4
 */
Ryan Lortie's avatar
Ryan Lortie committed
912
gboolean
913
gtk_application_window_get_show_menubar (GtkApplicationWindow *window)
Ryan Lortie's avatar
Ryan Lortie committed
914
{
915
  return window->priv->show_menubar;
916 917
}

918 919 920 921 922 923 924 925 926 927
/**
 * gtk_application_window_set_show_menubar:
 * @window: a #GtkApplicationWindow
 * @show_menubar: whether to show a menubar when needed
 *
 * Sets whether the window will display a menubar for the app menu
 * and menubar as needed.
 *
 * Since: 3.4
 */
928 929 930
void
gtk_application_window_set_show_menubar (GtkApplicationWindow *window,
                                         gboolean              show_menubar)
931
{
932
  g_return_if_fail (GTK_IS_APPLICATION_WINDOW (window));
933

934
  show_menubar = !!show_menubar;
935

936
  if (window->priv->show_menubar != show_menubar)
937
    {
938 939 940
      window->priv->show_menubar = show_menubar;

      gtk_application_window_update_menubar (window);
Ryan Lortie's avatar
Ryan Lortie committed
941

942
      g_object_notify_by_pspec (G_OBJECT (window), gtk_application_window_properties[PROP_SHOW_MENUBAR]);
Ryan Lortie's avatar
Ryan Lortie committed
943 944
    }
}
945

946 947 948 949 950
/**
 * gtk_application_window_get_id:
 * @window: a #GtkApplicationWindow
 *
 * Returns the unique ID of the window. If the window has not yet been added to
951
 * a #GtkApplication, returns `0`.
952
 *
953
 * Returns: the unique ID for @window, or `0` if the window
954 955 956 957 958 959 960 961 962 963 964
 *   has not yet been added to a #GtkApplication
 *
 * Since: 3.6
 */
guint
gtk_application_window_get_id (GtkApplicationWindow *window)
{
  g_return_val_if_fail (GTK_IS_APPLICATION_WINDOW (window), 0);

  return window->priv->id;
}
Ryan Lortie's avatar
Ryan Lortie committed
965 966 967 968 969 970 971 972

void
gtk_application_window_set_id (GtkApplicationWindow *window,
                               guint                 id)
{
  g_return_if_fail (GTK_IS_APPLICATION_WINDOW (window));
  window->priv->id = id;
}
973 974 975 976 977 978 979 980 981 982 983 984 985 986 987

static void
show_help_overlay (GSimpleAction *action,
                   GVariant      *parameter,
                   gpointer       user_data)
{
  GtkApplicationWindow *window = user_data;

  if (window->priv->help_overlay)
    gtk_widget_show (GTK_WIDGET (window->priv->help_overlay));
}

/**
 * gtk_application_window_set_help_overlay:
 * @window: a #GtkApplicationWindow
988
 * @help_overlay: (nullable): a #GtkShortcutsWindow
989 990
 *
 * Associates a shortcuts window with the application window, and
991
 * sets up an action with the name win.show-help-overlay to present
992 993
 * it.
 *
994 995
 * @window takes resposibility for destroying @help_overlay.
 *
996 997 998 999 1000 1001 1002 1003 1004 1005
 * Since: 3.20
 */
void
gtk_application_window_set_help_overlay (GtkApplicationWindow *window,
                                         GtkShortcutsWindow   *help_overlay)
{
  g_return_if_fail (GTK_IS_APPLICATION_WINDOW (window));
  g_return_if_fail (help_overlay == NULL || GTK_IS_SHORTCUTS_WINDOW (help_overlay));

  if (window->priv->help_overlay)
1006
    gtk_widget_destroy (GTK_WIDGET (window->priv->help_overlay));
1007 1008 1009 1010 1011 1012 1013
  g_set_object (&window->priv->help_overlay, help_overlay);

  if (!window->priv->help_overlay)
    return;

  gtk_window_set_modal (GTK_WINDOW (help_overlay), TRUE);
  gtk_window_set_transient_for (GTK_WINDOW (help_overlay), GTK_WINDOW (window));
1014 1015
  gtk_shortcuts_window_set_window (help_overlay, GTK_WINDOW (window));

1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026
  g_signal_connect (help_overlay, "delete-event",
                    G_CALLBACK (gtk_widget_hide_on_delete), NULL);

  if (!g_action_map_lookup_action (G_ACTION_MAP (window->priv->actions), "show-help-overlay"))
    {
      GSimpleAction *action;

      action = g_simple_action_new ("show-help-overlay", NULL);
      g_signal_connect (action, "activate", G_CALLBACK (show_help_overlay), window);

      g_action_map_add_action (G_ACTION_MAP (window->priv->actions), G_ACTION (action));
1027
      g_object_unref (G_OBJECT (action));
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
    }
}

/**
 * gtk_application_window_get_help_overlay:
 * @window: a #GtkApplicationWindow
 *
 * Gets the #GtkShortcutsWindow that has been set up with
 * a prior call to gtk_application_window_set_help_overlay().
 *
1038
 * Returns: (transfer none) (nullable): the help overlay associated with @window, or %NULL
1039 1040 1041 1042 1043 1044 1045 1046 1047 1048
 *
 * Since: 3.20
 */
GtkShortcutsWindow *
gtk_application_window_get_help_overlay (GtkApplicationWindow *window)
{
  g_return_val_if_fail (GTK_IS_APPLICATION_WINDOW (window), NULL);

  return window->priv->help_overlay;
}