diff options
Diffstat (limited to 'src/terminal-window.c')
-rw-r--r-- | src/terminal-window.c | 3259 |
1 files changed, 3259 insertions, 0 deletions
diff --git a/src/terminal-window.c b/src/terminal-window.c new file mode 100644 index 0000000..8adb134 --- /dev/null +++ b/src/terminal-window.c @@ -0,0 +1,3259 @@ +/* + * Copyright © 2001 Havoc Pennington + * Copyright © 2002 Red Hat, Inc. + * Copyright © 2007, 2008, 2009, 2011, 2017 Christian Persch + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> +#include <stdlib.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include <gtk/gtk.h> +#include <uuid.h> + +#include "terminal-app.h" +#include "terminal-debug.h" +#include "terminal-enums.h" +#include "terminal-headerbar.h" +#include "terminal-icon-button.h" +#include "terminal-intl.h" +#include "terminal-mdi-container.h" +#include "terminal-menu-button.h" +#include "terminal-notebook.h" +#include "terminal-schemas.h" +#include "terminal-screen-container.h" +#include "terminal-search-popover.h" +#include "terminal-tab-label.h" +#include "terminal-util.h" +#include "terminal-window.h" +#include "terminal-libgsystem.h" + +struct _TerminalWindowPrivate +{ + char *uuid; + + GtkClipboard *clipboard; + + TerminalScreenPopupInfo *popup_info; + + GtkWidget *menubar; + TerminalMdiContainer *mdi_container; + GtkWidget *main_vbox; + TerminalScreen *active_screen; + + /* Size of a character cell in pixels */ + int old_char_width; + int old_char_height; + + /* Width and height added to the actual terminal grid by "chrome" inside + * what was traditionally the X11 window: menu bar, title bar, + * style-provided padding. This must be included when resizing the window + * and also included in geometry hints. */ + int old_chrome_width; + int old_chrome_height; + + /* Width and height added to the window by client-side decorations. + * This must be included in geometry hints but must not be included when + * resizing the window. */ + int old_csd_width; + int old_csd_height; + + /* Width and height of the padding around the geometry widget. */ + int old_padding_width; + int old_padding_height; + + void *old_geometry_widget; /* only used for pointer value as it may be freed */ + + GtkWidget *confirm_close_dialog; + TerminalSearchPopover *search_popover; + + guint use_default_menubar_visibility : 1; + + guint disposed : 1; + guint present_on_insert : 1; + + guint realized : 1; +}; + +#define TERMINAL_WINDOW_CSS_NAME "terminal-window" + +#define MIN_WIDTH_CHARS 4 +#define MIN_HEIGHT_CHARS 1 + +#if 1 +/* + * We don't want to enable content saving until vte supports it async. + * So we disable this code for stable versions. + */ +#include "terminal-version.h" + +#if (TERMINAL_MINOR_VERSION & 1) != 0 +#define ENABLE_SAVE +#else +#undef ENABLE_SAVE +#endif +#endif + +/* See bug #789356 */ +#define WINDOW_STATE_TILED (GDK_WINDOW_STATE_TILED | \ + GDK_WINDOW_STATE_LEFT_TILED | \ + GDK_WINDOW_STATE_RIGHT_TILED | \ + GDK_WINDOW_STATE_TOP_TILED | \ + GDK_WINDOW_STATE_BOTTOM_TILED) + +static void terminal_window_dispose (GObject *object); +static void terminal_window_finalize (GObject *object); +static gboolean terminal_window_state_event (GtkWidget *widget, + GdkEventWindowState *event); + +static gboolean terminal_window_delete_event (GtkWidget *widget, + GdkEvent *event, + gpointer data); + +static gboolean notebook_button_press_cb (GtkWidget *notebook, + GdkEventButton *event, + TerminalWindow *window); +static gboolean notebook_popup_menu_cb (GtkWidget *notebook, + TerminalWindow *window); +static void mdi_screen_switched_cb (TerminalMdiContainer *container, + TerminalScreen *old_active_screen, + TerminalScreen *screen, + TerminalWindow *window); +static void mdi_screen_added_cb (TerminalMdiContainer *container, + TerminalScreen *screen, + TerminalWindow *window); +static void mdi_screen_removed_cb (TerminalMdiContainer *container, + TerminalScreen *screen, + TerminalWindow *window); +static void mdi_screens_reordered_cb (TerminalMdiContainer *container, + TerminalWindow *window); +static void screen_close_request_cb (TerminalMdiContainer *container, + TerminalScreen *screen, + TerminalWindow *window); + +/* Menu action callbacks */ +static gboolean find_larger_zoom_factor (double *zoom); +static gboolean find_smaller_zoom_factor (double *zoom); +static void terminal_window_update_zoom_sensitivity (TerminalWindow *window); +static void terminal_window_update_search_sensitivity (TerminalScreen *screen, + TerminalWindow *window); +static void terminal_window_update_paste_sensitivity (TerminalWindow *window); + +static void terminal_window_show (GtkWidget *widget); + +static gboolean confirm_close_window_or_tab (TerminalWindow *window, + TerminalScreen *screen); + +G_DEFINE_TYPE (TerminalWindow, terminal_window, GTK_TYPE_APPLICATION_WINDOW) + +/* Zoom helpers */ + +static const double zoom_factors[] = { + TERMINAL_SCALE_MINIMUM, + TERMINAL_SCALE_XXXXX_SMALL, + TERMINAL_SCALE_XXXX_SMALL, + TERMINAL_SCALE_XXX_SMALL, + PANGO_SCALE_XX_SMALL, + PANGO_SCALE_X_SMALL, + PANGO_SCALE_SMALL, + PANGO_SCALE_MEDIUM, + PANGO_SCALE_LARGE, + PANGO_SCALE_X_LARGE, + PANGO_SCALE_XX_LARGE, + TERMINAL_SCALE_XXX_LARGE, + TERMINAL_SCALE_XXXX_LARGE, + TERMINAL_SCALE_XXXXX_LARGE, + TERMINAL_SCALE_MAXIMUM +}; + +static gboolean +find_larger_zoom_factor (double *zoom) +{ + double current = *zoom; + guint i; + + for (i = 0; i < G_N_ELEMENTS (zoom_factors); ++i) + { + /* Find a font that's larger than this one */ + if ((zoom_factors[i] - current) > 1e-6) + { + *zoom = zoom_factors[i]; + return TRUE; + } + } + + return FALSE; +} + +static gboolean +find_smaller_zoom_factor (double *zoom) +{ + double current = *zoom; + int i; + + i = (int) G_N_ELEMENTS (zoom_factors) - 1; + while (i >= 0) + { + /* Find a font that's smaller than this one */ + if ((current - zoom_factors[i]) > 1e-6) + { + *zoom = zoom_factors[i]; + return TRUE; + } + + --i; + } + + return FALSE; +} + +static inline GSimpleAction * +lookup_action (TerminalWindow *window, + const char *name) +{ + GAction *action; + + action = g_action_map_lookup_action (G_ACTION_MAP (window), name); + g_return_val_if_fail (action != NULL, NULL); + + return G_SIMPLE_ACTION (action); +} + +/* Context menu helpers */ + +/* We don't want context menus to show accelerators. + * Setting the menu's accel group and/or accel path to NULL + * unfortunately doesn't hide accelerators; we need to walk + * the menu items and remove the accelerators on each, + * manually. + */ +static void +popup_menu_remove_accelerators (GtkWidget *menu) +{ + gs_free_list GList *menu_items; + GList *l; + + menu_items = gtk_container_get_children (GTK_CONTAINER (menu)); + for (l = menu_items; l != NULL; l = l ->next) { + GtkMenuItem *item = (GtkMenuItem*) (l->data); + GtkWidget *label, *submenu; + + if (!GTK_IS_MENU_ITEM (item)) + continue; + + if (GTK_IS_ACCEL_LABEL ((label = gtk_bin_get_child (GTK_BIN (item))))) + gtk_accel_label_set_accel (GTK_ACCEL_LABEL (label), 0, 0); + + /* Recurse into submenus */ + if ((submenu = gtk_menu_item_get_submenu (item))) + popup_menu_remove_accelerators (submenu); + } +} + +/* Because we're using gtk_menu_attach_to_widget(), the attach + * widget holds a strong reference to the menu, causing it not to + * be automatically destroyed once popped down. So we need to + * detach the menu from the attach widget manually, which will + * cause the menu to be destroyed. We cannot do so in the + * "deactivate" handler however, since that causes the menu + * item activation to be lost. The "selection-done" signal + * appears to be the right place. + */ + +static void +popup_menu_destroy_cb (GtkWidget *menu, + gpointer user_data) +{ + /* g_printerr ("Menu %p destroyed!\n", menu); */ +} + +static void +popup_menu_selection_done_cb (GtkMenu *menu, + gpointer user_data) +{ + g_signal_handlers_disconnect_by_func + (menu, G_CALLBACK (popup_menu_selection_done_cb), user_data); + + /* g_printerr ("selection-done %p\n", menu); */ + + /* This will remove the ref from the attach widget widget, and destroy the menu */ + if (gtk_menu_get_attach_widget (menu) != NULL) + gtk_menu_detach (menu); +} + +static void +popup_menu_detach_cb (GtkWidget *attach_widget, + GtkMenu *menu) +{ + gtk_menu_shell_deactivate (GTK_MENU_SHELL (menu)); +} + +static GtkWidget * +context_menu_new (GMenuModel *menu, + GtkWidget *widget) +{ + GtkWidget *popup_menu; + + popup_menu = gtk_menu_new_from_model (menu); + gtk_style_context_add_class (gtk_widget_get_style_context (popup_menu), + GTK_STYLE_CLASS_CONTEXT_MENU); + gtk_menu_attach_to_widget (GTK_MENU (popup_menu), widget, + (GtkMenuDetachFunc)popup_menu_detach_cb); + + popup_menu_remove_accelerators (popup_menu); + + /* Staggered destruction */ + g_signal_connect (popup_menu, "selection-done", + G_CALLBACK (popup_menu_selection_done_cb), widget); + g_signal_connect (popup_menu, "destroy", + G_CALLBACK (popup_menu_destroy_cb), widget); + + return popup_menu; +} + +/* GAction callbacks */ + +static void +action_new_terminal_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + TerminalApp *app; + TerminalSettingsList *profiles_list; + gs_unref_object GSettings *profile = NULL; + gboolean can_toggle = FALSE; + + g_assert (TERMINAL_IS_WINDOW (window)); + + app = terminal_app_get (); + + const char *mode_str, *uuid_str; + g_variant_get (parameter, "(&s&s)", &mode_str, &uuid_str); + + TerminalNewTerminalMode mode; + if (g_str_equal (mode_str, "tab")) + mode = TERMINAL_NEW_TERMINAL_MODE_TAB; + else if (g_str_equal (mode_str, "window")) + mode = TERMINAL_NEW_TERMINAL_MODE_WINDOW; + else if (g_str_equal (mode_str, "tab-default")) { + mode = TERMINAL_NEW_TERMINAL_MODE_TAB; + can_toggle = TRUE; + } else { + mode = g_settings_get_enum (terminal_app_get_global_settings (app), + TERMINAL_SETTING_NEW_TERMINAL_MODE_KEY); + can_toggle = TRUE; + } + + if (can_toggle) { + GdkEvent *event = gtk_get_current_event (); + if (event != NULL) { + GdkModifierType modifiers; + + if ((gdk_event_get_state (event, &modifiers) && + (modifiers & gtk_accelerator_get_default_mod_mask () & GDK_CONTROL_MASK))) { + /* Invert */ + if (mode == TERMINAL_NEW_TERMINAL_MODE_WINDOW) + mode = TERMINAL_NEW_TERMINAL_MODE_TAB; + else + mode = TERMINAL_NEW_TERMINAL_MODE_WINDOW; + } + gdk_event_free (event); + } + } + + TerminalScreen *parent_screen = priv->active_screen; + + profiles_list = terminal_app_get_profiles_list (app); + if (g_str_equal (uuid_str, "current")) + profile = terminal_screen_ref_profile (parent_screen); + else if (g_str_equal (uuid_str, "default")) + profile = terminal_settings_list_ref_default_child (profiles_list); + else + profile = terminal_settings_list_ref_child (profiles_list, uuid_str); + + if (profile == NULL) + return; + + if (mode == TERMINAL_NEW_TERMINAL_MODE_WINDOW) + window = terminal_window_new (G_APPLICATION (app)); + + TerminalScreen *screen = terminal_screen_new (profile, + NULL /* title */, + 1.0); + + /* Now add the new screen to the window */ + terminal_window_add_screen (window, screen, -1); + terminal_window_switch_screen (window, screen); + gtk_widget_grab_focus (GTK_WIDGET (screen)); + + /* Start child process, if possible by using the same args as the parent screen */ + terminal_screen_reexec_from_screen (screen, parent_screen, NULL, NULL); + + if (mode == TERMINAL_NEW_TERMINAL_MODE_WINDOW) + gtk_window_present (GTK_WINDOW (window)); +} + +#ifdef ENABLE_SAVE + +static void +save_contents_dialog_on_response (GtkDialog *dialog, gint response_id, gpointer terminal) +{ + GtkWindow *parent; + gs_free gchar *filename_uri = NULL; + gs_unref_object GFile *file = NULL; + GOutputStream *stream; + gs_free_error GError *error = NULL; + + if (response_id != GTK_RESPONSE_ACCEPT) + { + gtk_widget_destroy (GTK_WIDGET (dialog)); + return; + } + + parent = (GtkWindow*) gtk_widget_get_ancestor (GTK_WIDGET (terminal), GTK_TYPE_WINDOW); + filename_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog)); + + gtk_widget_destroy (GTK_WIDGET (dialog)); + + if (filename_uri == NULL) + return; + + file = g_file_new_for_uri (filename_uri); + stream = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error)); + + if (stream) + { + /* XXX + * FIXME + * This is a sync operation. + * Should be replaced with the async version when vte implements that. + */ + vte_terminal_write_contents_sync (terminal, stream, + VTE_WRITE_DEFAULT, + NULL, &error); + g_object_unref (stream); + } + + if (error) + { + terminal_util_show_error_dialog (parent, NULL, error, + "%s", _("Could not save contents")); + } +} + +static void +action_save_contents_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + GtkWidget *dialog = NULL; + TerminalWindowPrivate *priv = window->priv; + VteTerminal *terminal; + + if (priv->active_screen == NULL) + return; + + terminal = VTE_TERMINAL (priv->active_screen); + g_return_if_fail (VTE_IS_TERMINAL (terminal)); + + dialog = gtk_file_chooser_dialog_new (_("Save as…"), + GTK_WINDOW(window), + GTK_FILE_CHOOSER_ACTION_SAVE, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Save"), GTK_RESPONSE_ACCEPT, + NULL); + + gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE); + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS)); + + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (window)); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); + + g_signal_connect (dialog, "response", G_CALLBACK (save_contents_dialog_on_response), terminal); + g_signal_connect (dialog, "delete_event", G_CALLBACK (terminal_util_dialog_response_on_delete), NULL); + + gtk_window_present (GTK_WINDOW (dialog)); +} + +#endif /* ENABLE_SAVE */ + +#ifdef ENABLE_PRINT + +static void +print_begin_cb (GtkPrintOperation *op, + GtkPrintContext *context, + TerminalApp *app) +{ + GtkPrintSettings *settings; + GtkPageSetup *page_setup; + + /* Don't save if the print dialogue was cancelled */ + if (gtk_print_operation_get_status(op) == GTK_PRINT_STATUS_FINISHED_ABORTED) + return; + + settings = gtk_print_operation_get_print_settings (op); + page_setup = gtk_print_operation_get_default_page_setup (op); + terminal_util_save_print_settings (settings, page_setup); +} + +static void +print_done_cb (GtkPrintOperation *op, + GtkPrintOperationResult result, + TerminalWindow *window) +{ + if (result != GTK_PRINT_OPERATION_RESULT_ERROR) + return; + + /* FIXME: show error */ +} + +static void +action_print_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + gs_unref_object GtkPrintSettings *settings = NULL; + gs_unref_object GtkPageSetup *page_setup = NULL; + gs_unref_object GtkPrintOperation *op = NULL; + gs_free_error GError *error = NULL; + GtkPrintOperationResult result; + + if (priv->active_screen == NULL) + return; + + op = vte_print_operation_new (VTE_TERMINAL (priv->active_screen), + VTE_PRINT_OPERATION_DEFAULT /* flags */); + if (op == NULL) + return; + + terminal_util_load_print_settings (&settings, &page_setup); + if (settings != NULL) + gtk_print_operation_set_print_settings (op, settings); + if (page_setup != NULL) + gtk_print_operation_set_default_page_setup (op, page_setup); + + g_signal_connect (op, "begin-print", G_CALLBACK (print_begin_cb), window); + g_signal_connect (op, "done", G_CALLBACK (print_done_cb), window); + + /* FIXME: show progress better */ + + result = gtk_print_operation_run (op, + /* this is the only supported one: */ + GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, + GTK_WINDOW (window), + &error); + /* VtePrintOperation always runs async */ + g_assert_cmpint (result, ==, GTK_PRINT_OPERATION_RESULT_IN_PROGRESS); +} + +#endif /* ENABLE_PRINT */ + +#ifdef ENABLE_EXPORT + +static void +action_export_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + gs_unref_object VteExportOperation *op = NULL; + gs_free_error GError *error = NULL; + + if (priv->active_screen == NULL) + return; + + op = vte_export_operation_new (VTE_TERMINAL (priv->active_screen), + TRUE /* interactive */, + VTE_EXPORT_FORMAT_ASK /* allow user to choose export format */, + NULL, NULL /* GSettings & key to load/store default directory from, FIXME */, + NULL, NULL /* progress callback & user data, FIXME */); + if (op == NULL) + return; + + /* FIXME: show progress better */ + + vte_export_operation_run_async (op, GTK_WINDOW (window), NULL /* cancellable */); +} + +#endif /* ENABLE_EXPORT */ + +static void +action_close_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + TerminalScreen *screen; + const char *mode_str; + + g_assert_nonnull (parameter); + g_variant_get (parameter, "&s", &mode_str); + + if (g_str_equal (mode_str, "tab")) + screen = priv->active_screen; + else if (g_str_equal (mode_str, "window")) + screen = NULL; + else + return; + + if (confirm_close_window_or_tab (window, screen)) + return; + + if (screen) + terminal_window_remove_screen (window, screen); + else + gtk_widget_destroy (GTK_WIDGET (window)); +} + +static void +action_copy_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + const char *format_str; + VteFormat format; + + if (priv->active_screen == NULL) + return; + + g_assert_nonnull (parameter); + g_variant_get (parameter, "&s", &format_str); + + if (g_str_equal (format_str, "text")) + format = VTE_FORMAT_TEXT; + else if (g_str_equal (format_str, "html")) + format = VTE_FORMAT_HTML; + else + return; + + vte_terminal_copy_clipboard_format (VTE_TERMINAL (priv->active_screen), format); +} + +/* Clipboard helpers */ + +typedef struct { + GWeakRef screen_weak_ref; +} PasteData; + +static void +clipboard_uris_received_cb (GtkClipboard *clipboard, + /* const */ char **uris, + PasteData *data) +{ + gs_unref_object TerminalScreen *screen = NULL; + + if (uris != NULL && uris[0] != NULL && + (screen = g_weak_ref_get (&data->screen_weak_ref))) { + gs_free char *text; + gsize len; + + /* This potentially modifies the strings in |uris| but that's ok */ + terminal_util_transform_uris_to_quoted_fuse_paths (uris); + text = terminal_util_concat_uris (uris, &len); + + vte_terminal_feed_child (VTE_TERMINAL (screen), text, len); + } + + g_weak_ref_clear (&data->screen_weak_ref); + g_slice_free (PasteData, data); +} + +static void +request_clipboard_contents_for_paste (TerminalWindow *window, + gboolean paste_as_uris) +{ + TerminalWindowPrivate *priv = window->priv; + GdkAtom *targets; + int n_targets; + + if (priv->active_screen == NULL) + return; + + targets = terminal_app_get_clipboard_targets (terminal_app_get (), + priv->clipboard, + &n_targets); + if (targets == NULL) + return; + + if (paste_as_uris && gtk_targets_include_uri (targets, n_targets)) { + PasteData *data = g_slice_new (PasteData); + g_weak_ref_init (&data->screen_weak_ref, priv->active_screen); + + gtk_clipboard_request_uris (priv->clipboard, + (GtkClipboardURIReceivedFunc) clipboard_uris_received_cb, + data); + return; + } else if (gtk_targets_include_text (targets, n_targets)) { + vte_terminal_paste_clipboard (VTE_TERMINAL (priv->active_screen)); + } +} + +static void +action_paste_text_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + + request_clipboard_contents_for_paste (window, FALSE); +} + +static void +action_paste_uris_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + + request_clipboard_contents_for_paste (window, TRUE); +} + +static void +action_select_all_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + + if (priv->active_screen == NULL) + return; + + vte_terminal_select_all (VTE_TERMINAL (priv->active_screen)); +} + +static void +action_reset_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + + g_assert_nonnull (parameter); + + if (priv->active_screen == NULL) + return; + + vte_terminal_reset (VTE_TERMINAL (priv->active_screen), + TRUE, + g_variant_get_boolean (parameter)); +} + +static void +tab_switch_relative (TerminalWindow *window, + int change) +{ + TerminalWindowPrivate *priv = window->priv; + int n_screens, value; + + n_screens = terminal_mdi_container_get_n_screens (priv->mdi_container); + value = terminal_mdi_container_get_active_screen_num (priv->mdi_container) + change; + + gboolean keynav_wrap_around; + g_object_get (gtk_widget_get_settings (GTK_WIDGET (window)), + "gtk-keynav-wrap-around", &keynav_wrap_around, + NULL); + if (keynav_wrap_around) { + if (value < 0) + value += n_screens; + else if (value >= n_screens) + value -= n_screens; + } + + if (value < 0 || value >= n_screens) + return; + + g_action_change_state (G_ACTION (lookup_action (window, "active-tab")), + g_variant_new_int32 (value)); +} + +static void +action_tab_switch_left_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + + tab_switch_relative (window, -1); +} + +static void +action_tab_switch_right_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + + tab_switch_relative (window, 1); +} + +static void +action_tab_move_left_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + int change; + + if (priv->active_screen == NULL) + return; + + change = gtk_widget_get_direction (GTK_WIDGET (window)) == GTK_TEXT_DIR_RTL ? 1 : -1; + terminal_mdi_container_reorder_screen (priv->mdi_container, + terminal_mdi_container_get_active_screen (priv->mdi_container), + change); +} + +static void +action_tab_move_right_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + int change; + + if (priv->active_screen == NULL) + return; + + change = gtk_widget_get_direction (GTK_WIDGET (window)) == GTK_TEXT_DIR_RTL ? -1 : 1; + terminal_mdi_container_reorder_screen (priv->mdi_container, + terminal_mdi_container_get_active_screen (priv->mdi_container), + change); +} + +static void +action_zoom_in_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + double zoom; + + if (priv->active_screen == NULL) + return; + + zoom = vte_terminal_get_font_scale (VTE_TERMINAL (priv->active_screen)); + if (!find_larger_zoom_factor (&zoom)) + return; + + vte_terminal_set_font_scale (VTE_TERMINAL (priv->active_screen), zoom); + terminal_window_update_zoom_sensitivity (window); +} + +static void +action_zoom_out_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + double zoom; + + if (priv->active_screen == NULL) + return; + + zoom = vte_terminal_get_font_scale (VTE_TERMINAL (priv->active_screen)); + if (!find_smaller_zoom_factor (&zoom)) + return; + + vte_terminal_set_font_scale (VTE_TERMINAL (priv->active_screen), zoom); + terminal_window_update_zoom_sensitivity (window); +} + +static void +action_zoom_normal_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + + if (priv->active_screen == NULL) + return; + + vte_terminal_set_font_scale (VTE_TERMINAL (priv->active_screen), PANGO_SCALE_MEDIUM); + terminal_window_update_zoom_sensitivity (window); +} + +static void +action_tab_detach_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + TerminalApp *app; + TerminalWindow *new_window; + TerminalScreen *screen; + char geometry[32]; + int width, height; + + app = terminal_app_get (); + + screen = priv->active_screen; + + terminal_screen_get_size (screen, &width, &height); + g_snprintf (geometry, sizeof (geometry), "%dx%d", width, height); + + new_window = terminal_window_new (G_APPLICATION (app)); + + terminal_window_move_screen (window, new_window, screen, -1); + + terminal_window_parse_geometry (new_window, geometry); + + gtk_window_present_with_time (GTK_WINDOW (new_window), gtk_get_current_event_time ()); +} + +static void +action_help_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + terminal_util_show_help (NULL); +} + +static void +action_about_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + terminal_util_show_about (); +} + +static void +action_edit_preferences_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + + terminal_app_edit_preferences (terminal_app_get (), + terminal_screen_get_profile (priv->active_screen), + NULL); +} + +static void +action_size_to_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + guint width, height; + + g_assert_nonnull (parameter); + + if (priv->active_screen == NULL) + return; + + g_variant_get (parameter, "(uu)", &width, &height); + if (width < MIN_WIDTH_CHARS || height < MIN_HEIGHT_CHARS || + width > 256 || height > 256) + return; + + vte_terminal_set_size (VTE_TERMINAL (priv->active_screen), width, height); + terminal_window_update_size (window); +} + +static void +action_open_match_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + TerminalScreenPopupInfo *info = priv->popup_info; + + if (info == NULL) + return; + if (info->url == NULL) + return; + + terminal_util_open_url (GTK_WIDGET (window), info->url, info->url_flavor, + gtk_get_current_event_time ()); +} + +static void +action_copy_match_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + TerminalScreenPopupInfo *info = priv->popup_info; + + if (info == NULL) + return; + if (info->url == NULL) + return; + + gtk_clipboard_set_text (priv->clipboard, info->url, -1); +} + +static void +action_open_hyperlink_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + TerminalScreenPopupInfo *info = priv->popup_info; + + if (info == NULL) + return; + if (info->hyperlink == NULL) + return; + + terminal_util_open_url (GTK_WIDGET (window), info->hyperlink, FLAVOR_AS_IS, + gtk_get_current_event_time ()); +} + +static void +action_copy_hyperlink_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + TerminalScreenPopupInfo *info = priv->popup_info; + + if (info == NULL) + return; + if (info->hyperlink == NULL) + return; + + gtk_clipboard_set_text (priv->clipboard, info->hyperlink, -1); +} + +static void +action_enter_fullscreen_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + + g_action_group_change_action_state (G_ACTION_GROUP (window), "fullscreen", + g_variant_new_boolean (TRUE)); +} + +static void +action_leave_fullscreen_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + + g_action_group_change_action_state (G_ACTION_GROUP (window), "fullscreen", + g_variant_new_boolean (FALSE)); +} + +static void +action_inspector_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + gtk_window_set_interactive_debugging (TRUE); +} + +static void +search_popover_search_cb (TerminalSearchPopover *popover, + gboolean backward, + TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + + if (G_UNLIKELY (priv->active_screen == NULL)) + return; + + if (backward) + vte_terminal_search_find_previous (VTE_TERMINAL (priv->active_screen)); + else + vte_terminal_search_find_next (VTE_TERMINAL (priv->active_screen)); +} + +static void +search_popover_notify_regex_cb (TerminalSearchPopover *popover, + GParamSpec *pspec G_GNUC_UNUSED, + TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + VteRegex *regex; + + if (G_UNLIKELY (priv->active_screen == NULL)) + return; + + regex = terminal_search_popover_get_regex (popover); + vte_terminal_search_set_regex (VTE_TERMINAL (priv->active_screen), regex, 0); + + terminal_window_update_search_sensitivity (priv->active_screen, window); +} + +static void +search_popover_notify_wrap_around_cb (TerminalSearchPopover *popover, + GParamSpec *pspec G_GNUC_UNUSED, + TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + gboolean wrap; + + if (G_UNLIKELY (priv->active_screen == NULL)) + return; + + wrap = terminal_search_popover_get_wrap_around (popover); + vte_terminal_search_set_wrap_around (VTE_TERMINAL (priv->active_screen), wrap); +} + +static void +action_find_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + + if (G_UNLIKELY(priv->active_screen == NULL)) + return; + + if (priv->search_popover != NULL) { + search_popover_notify_regex_cb (priv->search_popover, NULL, window); + search_popover_notify_wrap_around_cb (priv->search_popover, NULL, window); + + gtk_window_present_with_time (GTK_WINDOW (priv->search_popover), + gtk_get_current_event_time ()); + gtk_widget_grab_focus (GTK_WIDGET (priv->search_popover)); + return; + } + + if (priv->active_screen == NULL) + return; + + priv->search_popover = terminal_search_popover_new (GTK_WIDGET (window)); + + g_signal_connect (priv->search_popover, "search", G_CALLBACK (search_popover_search_cb), window); + + search_popover_notify_regex_cb (priv->search_popover, NULL, window); + g_signal_connect (priv->search_popover, "notify::regex", G_CALLBACK (search_popover_notify_regex_cb), window); + + search_popover_notify_wrap_around_cb (priv->search_popover, NULL, window); + g_signal_connect (priv->search_popover, "notify::wrap-around", G_CALLBACK (search_popover_notify_wrap_around_cb), window); + + g_signal_connect (priv->search_popover, "destroy", G_CALLBACK (gtk_widget_destroyed), &priv->search_popover); + + gtk_window_present_with_time (GTK_WINDOW (priv->search_popover), gtk_get_current_event_time ()); + gtk_widget_grab_focus (GTK_WIDGET (priv->search_popover)); +} + +static void +action_find_forward_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + + if (priv->active_screen == NULL) + return; + + vte_terminal_search_find_next (VTE_TERMINAL (priv->active_screen)); +} + +static void +action_find_backward_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + + if (priv->active_screen == NULL) + return; + + vte_terminal_search_find_previous (VTE_TERMINAL (priv->active_screen)); +} + +static void +action_find_clear_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + + if (priv->active_screen == NULL) + return; + + vte_terminal_search_set_regex (VTE_TERMINAL (priv->active_screen), NULL, 0); + vte_terminal_unselect_all (VTE_TERMINAL (priv->active_screen)); +} + +static void +action_shadow_activate_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + gs_free char *param = g_variant_print(parameter, TRUE); + + _terminal_debug_print (TERMINAL_DEBUG_ACCELS, + "Window %p shadow action activated for %s\n", + window, param); + + /* We make sure in terminal-accels to always install the keybinding + * for the real action first, so that it's first in line for activation. + * That means we can make this here a NOP, instead of forwarding the + * activation to the shadowed action. + */ +} + +static void +action_menubar_visible_state_cb (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + TerminalWindow *window = user_data; + gboolean active; + + active = g_variant_get_boolean (state); + terminal_window_set_menubar_visible (window, active); /* this also sets the action state */ +} + +static void +action_fullscreen_state_cb (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + TerminalWindow *window = user_data; + + if (!gtk_widget_get_realized (GTK_WIDGET (window))) + return; + + if (g_variant_get_boolean (state)) + gtk_window_fullscreen (GTK_WINDOW (window)); + else + gtk_window_unfullscreen (GTK_WINDOW (window)); + + /* The window-state-changed callback will update the action's actual state */ +} + +static void +action_read_only_state_cb (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + + g_assert_nonnull (state); + + g_simple_action_set_state (action, state); + + terminal_window_update_paste_sensitivity (window); + + if (priv->active_screen == NULL) + return; + + vte_terminal_set_input_enabled (VTE_TERMINAL (priv->active_screen), + !g_variant_get_boolean (state)); +} + +static void +action_profile_state_cb (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + TerminalSettingsList *profiles_list; + const gchar *uuid; + gs_unref_object GSettings *profile; + + g_assert_nonnull (state); + + uuid = g_variant_get_string (state, NULL); + profiles_list = terminal_app_get_profiles_list (terminal_app_get ()); + profile = terminal_settings_list_ref_child (profiles_list, uuid); + if (profile == NULL) + return; + + g_simple_action_set_state (action, state); + + terminal_screen_set_profile (priv->active_screen, profile); +} + +static void +action_active_tab_set_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + int value, n_screens; + + g_assert_nonnull (parameter); + + n_screens = terminal_mdi_container_get_n_screens (priv->mdi_container); + + value = g_variant_get_int32 (parameter); + if (value < 0) + value += n_screens; + if (value < 0 || value >= n_screens) + return; + + g_action_change_state (G_ACTION (action), g_variant_new_int32 (value)); +} + +static void +action_active_tab_state_cb (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + TerminalWindow *window = user_data; + TerminalWindowPrivate *priv = window->priv; + + g_assert_nonnull (state); + + g_simple_action_set_state (action, state); + + terminal_mdi_container_set_active_screen_num (priv->mdi_container, g_variant_get_int32 (state)); +} + +/* Menubar mnemonics & accel settings handling */ + +static void +enable_menubar_accel_changed_cb (GSettings *settings, + const char *key, + GtkSettings *gtk_settings) +{ + if (g_settings_get_boolean (settings, key)) + gtk_settings_reset_property (gtk_settings, "gtk-menu-bar-accel"); + else + g_object_set (gtk_settings, "gtk-menu-bar-accel", NULL, NULL); +} + +/* The menubar is shown by the app, and the use of mnemonics (e.g. Alt+F for File) is toggled. + * The mnemonic modifier is per window, so it doesn't affect the Find or Preferences windows. + * If the menubar is shown by the shell, a non-mnemonic variant of the menu is loaded instead + * in terminal-app.c. See over there for further details. */ +static void +enable_mnemonics_changed_cb (GSettings *settings, + const char *key, + TerminalWindow *window) +{ + gboolean enabled = g_settings_get_boolean (settings, key); + + if (enabled) + gtk_window_set_mnemonic_modifier (GTK_WINDOW (window), GDK_MOD1_MASK); + else + gtk_window_set_mnemonic_modifier (GTK_WINDOW (window), GDK_MODIFIER_MASK & ~GDK_RELEASE_MASK); +} + +static void +app_setting_notify_destroy_cb (GtkSettings *gtk_settings) +{ + g_signal_handlers_disconnect_by_func (terminal_app_get_global_settings (terminal_app_get ()), + G_CALLBACK (enable_menubar_accel_changed_cb), + gtk_settings); +} + +/* utility functions */ + +static int +find_tab_num_at_pos (GtkNotebook *notebook, + int screen_x, + int screen_y) +{ + GtkPositionType tab_pos; + int page_num = 0; + GtkNotebook *nb = GTK_NOTEBOOK (notebook); + GtkWidget *page; + GtkAllocation tab_allocation; + + tab_pos = gtk_notebook_get_tab_pos (GTK_NOTEBOOK (notebook)); + + while ((page = gtk_notebook_get_nth_page (nb, page_num))) + { + GtkWidget *tab; + int max_x, max_y, x_root, y_root; + + tab = gtk_notebook_get_tab_label (nb, page); + g_return_val_if_fail (tab != NULL, -1); + + if (!gtk_widget_get_mapped (GTK_WIDGET (tab))) + { + page_num++; + continue; + } + + gdk_window_get_origin (gtk_widget_get_window (tab), &x_root, &y_root); + + gtk_widget_get_allocation (tab, &tab_allocation); + max_x = x_root + tab_allocation.x + tab_allocation.width; + max_y = y_root + tab_allocation.y + tab_allocation.height; + + if ((tab_pos == GTK_POS_TOP || tab_pos == GTK_POS_BOTTOM) && screen_x <= max_x) + return page_num; + + if ((tab_pos == GTK_POS_LEFT || tab_pos == GTK_POS_RIGHT) && screen_y <= max_y) + return page_num; + + page_num++; + } + + return -1; +} + +static void +terminal_window_update_set_profile_menu_active_profile (TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + GSettings *new_active_profile; + TerminalSettingsList *profiles_list; + char *uuid; + + if (priv->active_screen == NULL) + return; + + new_active_profile = terminal_screen_get_profile (priv->active_screen); + + profiles_list = terminal_app_get_profiles_list (terminal_app_get ()); + uuid = terminal_settings_list_dup_uuid_from_child (profiles_list, new_active_profile); + + g_simple_action_set_state (lookup_action (window, "profile"), + g_variant_new_take_string (uuid)); +} + +static void +terminal_window_update_terminal_menu (TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + + if (priv->active_screen == NULL) + return; + + gboolean read_only = !vte_terminal_get_input_enabled (VTE_TERMINAL (priv->active_screen)); + g_simple_action_set_state (lookup_action (window, "read-only"), + g_variant_new_boolean (read_only)); +} + +/* Actions stuff */ + +static void +terminal_window_update_copy_sensitivity (TerminalScreen *screen, + TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + gboolean can_copy; + + if (screen != priv->active_screen) + return; + + can_copy = vte_terminal_get_has_selection (VTE_TERMINAL (screen)); + g_simple_action_set_enabled (lookup_action (window, "copy"), can_copy); +} + +static void +terminal_window_update_zoom_sensitivity (TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + TerminalScreen *screen; + + screen = priv->active_screen; + if (screen == NULL) + return; + + double v; + double zoom = v = vte_terminal_get_font_scale (VTE_TERMINAL (screen)); + g_simple_action_set_enabled (lookup_action (window, "zoom-in"), + find_larger_zoom_factor (&v)); + + v = zoom; + g_simple_action_set_enabled (lookup_action (window, "zoom-out"), + find_smaller_zoom_factor (&v)); +} + +static void +terminal_window_update_search_sensitivity (TerminalScreen *screen, + TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + + if (screen != priv->active_screen) + return; + + gboolean can_search = vte_terminal_search_get_regex (VTE_TERMINAL (screen)) != NULL; + + g_simple_action_set_enabled (lookup_action (window, "find-forward"), can_search); + g_simple_action_set_enabled (lookup_action (window, "find-backward"), can_search); + g_simple_action_set_enabled (lookup_action (window, "find-clear"), can_search); +} + +static void +clipboard_targets_changed_cb (TerminalApp *app, + GtkClipboard *clipboard, + TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + + if (clipboard != priv->clipboard) + return; + + terminal_window_update_paste_sensitivity (window); +} + +static void +terminal_window_update_paste_sensitivity (TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + + GdkAtom *targets; + int n_targets; + targets = terminal_app_get_clipboard_targets (terminal_app_get(), priv->clipboard, &n_targets); + + gboolean can_paste; + gboolean can_paste_uris; + if (n_targets) { + can_paste = gtk_targets_include_text (targets, n_targets); + can_paste_uris = gtk_targets_include_uri (targets, n_targets); + } else { + can_paste = can_paste_uris = FALSE; + } + + gs_unref_variant GVariant *ro_state = g_action_get_state (g_action_map_lookup_action (G_ACTION_MAP (window), "read-only")); + gboolean read_only = g_variant_get_boolean (ro_state); + + g_simple_action_set_enabled (lookup_action (window, "paste-text"), can_paste && !read_only); + g_simple_action_set_enabled (lookup_action (window, "paste-uris"), can_paste_uris && !read_only); +} + +static void +screen_resize_window_cb (TerminalScreen *screen, + guint columns, + guint rows, + TerminalWindow* window) +{ + TerminalWindowPrivate *priv = window->priv; + GtkWidget *widget = GTK_WIDGET (screen); + + if (gtk_widget_get_realized (widget) && + (gdk_window_get_state (gtk_widget_get_window (widget)) & (GDK_WINDOW_STATE_MAXIMIZED | + GDK_WINDOW_STATE_FULLSCREEN | + WINDOW_STATE_TILED)) != 0) + return; + + vte_terminal_set_size (VTE_TERMINAL (priv->active_screen), columns, rows); + + if (screen == priv->active_screen) + terminal_window_update_size (window); +} + +static void +terminal_window_update_tabs_actions_sensitivity (TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + + if (priv->disposed) + return; + + int num_pages = terminal_mdi_container_get_n_screens (priv->mdi_container); + int page_num = terminal_mdi_container_get_active_screen_num (priv->mdi_container); + + gboolean not_only = num_pages > 1; + gboolean not_first = page_num > 0; + gboolean not_last = page_num + 1 < num_pages; + + gboolean not_first_lr, not_last_lr; + if (gtk_widget_get_direction (GTK_WIDGET (window)) == GTK_TEXT_DIR_RTL) { + not_first_lr = not_last; + not_last_lr = not_first; + } else { + not_first_lr = not_first; + not_last_lr = not_last; + } + + /* Hide the tabs menu in single-tab windows */ + g_simple_action_set_enabled (lookup_action (window, "tabs-menu"), not_only); + + /* Disable shadowing of MDI actions in SDI windows */ + g_simple_action_set_enabled (lookup_action (window, "shadow-mdi"), not_only); + + /* Disable tab switching (and all its shortcuts) in SDI windows */ + g_simple_action_set_enabled (lookup_action (window, "active-tab"), not_only); + + /* Set the active tab */ + g_simple_action_set_state (lookup_action (window, "active-tab"), + g_variant_new_int32 (page_num)); + + /* Keynav wraps around? See bug #92139 */ + gboolean keynav_wrap_around; + g_object_get (gtk_widget_get_settings (GTK_WIDGET (window)), + "gtk-keynav-wrap-around", &keynav_wrap_around, + NULL); + + gboolean wrap = keynav_wrap_around && not_only; + g_simple_action_set_enabled (lookup_action (window, "tab-switch-left"), not_first || wrap); + g_simple_action_set_enabled (lookup_action (window, "tab-switch-right"), not_last || wrap); + g_simple_action_set_enabled (lookup_action (window, "tab-move-left"), not_first_lr || wrap); + g_simple_action_set_enabled (lookup_action (window, "tab-move-right"), not_last_lr || wrap); + g_simple_action_set_enabled (lookup_action (window, "tab-detach"), not_only); +} + +static GtkNotebook * +handle_tab_droped_on_desktop (GtkNotebook *source_notebook, + GtkWidget *container, + gint x, + gint y, + gpointer data G_GNUC_UNUSED) +{ + TerminalWindow *source_window; + TerminalWindow *new_window; + TerminalWindowPrivate *new_priv; + + source_window = TERMINAL_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (source_notebook))); + g_return_val_if_fail (TERMINAL_IS_WINDOW (source_window), NULL); + + new_window = terminal_window_new (G_APPLICATION (terminal_app_get ())); + new_priv = new_window->priv; + new_priv->present_on_insert = TRUE; + + return GTK_NOTEBOOK (new_priv->mdi_container); +} + +/* Terminal screen popup menu handling */ + +static void +remove_popup_info (TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + + if (priv->popup_info != NULL) + { + terminal_screen_popup_info_unref (priv->popup_info); + priv->popup_info = NULL; + } +} + +static void +screen_popup_menu_selection_done_cb (GtkWidget *popup, + GtkWidget *window) +{ + g_signal_handlers_disconnect_by_func + (popup, G_CALLBACK (screen_popup_menu_selection_done_cb), window); + + GtkWidget *attach_widget = gtk_menu_get_attach_widget (GTK_MENU (popup)); + if (attach_widget != window || !TERMINAL_IS_WINDOW (attach_widget)) + return; + + remove_popup_info (TERMINAL_WINDOW (attach_widget)); +} + +static void +screen_show_popup_menu_cb (TerminalScreen *screen, + TerminalScreenPopupInfo *info, + TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + TerminalApp *app = terminal_app_get (); + + if (screen != priv->active_screen) + return; + + remove_popup_info (window); + priv->popup_info = terminal_screen_popup_info_ref (info); + + gs_unref_object GMenu *menu = g_menu_new (); + + /* Hyperlink section */ + if (info->hyperlink != NULL) { + gs_unref_object GMenu *section1 = g_menu_new (); + + g_menu_append (section1, _("Open _Hyperlink"), "win.open-hyperlink"); + g_menu_append (section1, _("Copy Hyperlink _Address"), "win.copy-hyperlink"); + g_menu_append_section (menu, NULL, G_MENU_MODEL (section1)); + } + /* Matched link section */ + else if (info->url != NULL) { + gs_unref_object GMenu *section2 = g_menu_new (); + + const char *open_label = NULL, *copy_label = NULL; + switch (info->url_flavor) { + case FLAVOR_EMAIL: + open_label = _("Send Mail _To…"); + copy_label = _("Copy Mail _Address"); + break; + case FLAVOR_VOIP_CALL: + open_label = _("Call _To…"); + copy_label = _("Copy Call _Address "); + break; + case FLAVOR_AS_IS: + case FLAVOR_DEFAULT_TO_HTTP: + default: + open_label = _("_Open Link"); + copy_label = _("Copy _Link"); + break; + } + + g_menu_append (section2, open_label, "win.open-match"); + g_menu_append (section2, copy_label, "win.copy-match"); + g_menu_append_section (menu, NULL, G_MENU_MODEL (section2)); + } + + /* Info section */ + if (info->number_info != NULL) { + gs_unref_object GMenu *section3 = g_menu_new (); + /* Non-existent action will make this item insensitive */ + gs_unref_object GMenuItem *item3 = g_menu_item_new (info->number_info, "win.notexist"); + g_menu_append_item (section3, item3); + g_menu_append_section (menu, NULL, G_MENU_MODEL (section3)); + } + + /* Clipboard section */ + gs_unref_object GMenu *section4 = g_menu_new (); + + g_menu_append (section4, _("_Copy"), "win.copy::text"); + g_menu_append (section4, _("Copy as _HTML"), "win.copy::html"); + g_menu_append (section4, _("_Paste"), "win.paste-text"); + if (g_action_get_enabled (G_ACTION (lookup_action (window, "paste-uris")))) + g_menu_append (section4, _("Paste as _Filenames"), "win.paste-uris"); + + g_menu_append_section (menu, NULL, G_MENU_MODEL (section4)); + + /* Profile and property section */ + gs_unref_object GMenu *section5 = g_menu_new (); + g_menu_append (section5, _("Read-_Only"), "win.read-only"); + + GMenuModel *profiles_menu = terminal_app_get_profile_section (app); + if (profiles_menu != NULL && g_menu_model_get_n_items (profiles_menu) > 1) { + gs_unref_object GMenu *submenu5 = g_menu_new (); + g_menu_append_section (submenu5, NULL, profiles_menu); + + gs_unref_object GMenuItem *item5 = g_menu_item_new (_("P_rofiles"), NULL); + g_menu_item_set_submenu (item5, G_MENU_MODEL (submenu5)); + g_menu_append_item (section5, item5); + } + + g_menu_append (section5, _("_Preferences"), "win.edit-preferences"); + + g_menu_append_section (menu, NULL, G_MENU_MODEL (section5)); + + /* New Terminal section */ + gs_unref_object GMenu *section6 = g_menu_new (); + if (terminal_app_get_menu_unified (app)) { + gs_unref_object GMenuItem *item6 = g_menu_item_new (_("New _Terminal"), NULL); + g_menu_item_set_action_and_target (item6, "win.new-terminal", + "(ss)", "default", "current"); + g_menu_append_item (section6, item6); + } else { + gs_unref_object GMenuItem *item61 = g_menu_item_new (_("New _Window"), NULL); + g_menu_item_set_action_and_target (item61, "win.new-terminal", + "(ss)", "window", "current"); + g_menu_append_item (section6, item61); + gs_unref_object GMenuItem *item62 = g_menu_item_new (_("New _Tab"), NULL); + g_menu_item_set_action_and_target (item62, "win.new-terminal", + "(ss)", "tab", "current"); + g_menu_append_item (section6, item62); + } + g_menu_append_section (menu, NULL, G_MENU_MODEL (section6)); + + /* Window section */ + gs_unref_object GMenu *section7 = g_menu_new (); + + /* Only show this if the WM doesn't show the menubar */ + if (g_action_get_enabled (G_ACTION (lookup_action (window, "menubar-visible")))) + g_menu_append (section7, _("Show _Menubar"), "win.menubar-visible"); + if (g_action_get_enabled (G_ACTION (lookup_action (window, "leave-fullscreen")))) + g_menu_append (section7, _("L_eave Full Screen"), "win.leave-fullscreen"); + + g_menu_append_section (menu, NULL, G_MENU_MODEL (section7)); + + /* Now create the popup menu and show it */ + GtkWidget *popup_menu = context_menu_new (G_MENU_MODEL (menu), GTK_WIDGET (window)); + + /* Remove the popup info after the menu is done */ + g_signal_connect (popup_menu, "selection-done", + G_CALLBACK (screen_popup_menu_selection_done_cb), window); + + gtk_menu_popup (GTK_MENU (popup_menu), NULL, NULL, + NULL, NULL, + info->button, + info->timestamp); + + if (info->button == 0) + gtk_menu_shell_select_first (GTK_MENU_SHELL (popup_menu), FALSE); +} + +static gboolean +screen_match_clicked_cb (TerminalScreen *screen, + const char *url, + int url_flavor, + guint state, + TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + + if (screen != priv->active_screen) + return FALSE; + + gtk_widget_grab_focus (GTK_WIDGET (screen)); + terminal_util_open_url (GTK_WIDGET (window), url, url_flavor, + gtk_get_current_event_time ()); + + return TRUE; +} + +static void +screen_close_cb (TerminalScreen *screen, + TerminalWindow *window) +{ + terminal_window_remove_screen (window, screen); +} + +static void +notebook_update_tabs_menu_cb (GtkMenuButton *button, + TerminalWindow *window) +{ + gs_unref_object GMenu *menu; + gs_free_list GList *tabs; + GList *t; + int i; + + menu = g_menu_new (); + tabs = terminal_window_list_screen_containers (window); + + for (t = tabs, i = 0; t != NULL; t = t->next, i++) { + TerminalScreenContainer *container = t->data; + TerminalScreen *screen = terminal_screen_container_get_screen (container); + gs_unref_object GMenuItem *item; + const char *title; + + if (t->next == NULL) { + /* Last entry. If it has no dedicated shortcut "Switch to Tab N", + * display the accel of "Switch to Last Tab". */ + GtkApplication *app = GTK_APPLICATION (g_application_get_default ()); + gs_free gchar *detailed_action = g_strdup_printf("win.active-tab(%d)", i); + gs_strfreev gchar **accels = gtk_application_get_accels_for_action (app, detailed_action); + if (accels[0] == NULL) + i = -1; + } + + title = terminal_screen_get_title (screen); + + item = g_menu_item_new (title && title[0] ? title : _("Terminal"), NULL); + g_menu_item_set_action_and_target (item, "win.active-tab", "i", i); + g_menu_append_item (menu, item); + } + + gtk_menu_button_set_menu_model (button, G_MENU_MODEL (menu)); + + /* Need this so the menu is positioned correctly */ + gtk_widget_set_halign (GTK_WIDGET (gtk_menu_button_get_popup (button)), GTK_ALIGN_END); +} + +static void +terminal_window_fill_notebook_action_box (TerminalWindow *window, + gboolean add_new_tab_button) +{ + TerminalWindowPrivate *priv = window->priv; + GtkWidget *box, *new_tab_button, *tabs_menu_button; + + box = terminal_notebook_get_action_box (TERMINAL_NOTEBOOK (priv->mdi_container), GTK_PACK_END); + + /* Create the NewTerminal button */ + if (add_new_tab_button) + { + new_tab_button = terminal_icon_button_new ("tab-new-symbolic"); + gtk_actionable_set_action_name (GTK_ACTIONABLE (new_tab_button), "win.new-terminal"); + gtk_actionable_set_action_target (GTK_ACTIONABLE (new_tab_button), "(ss)", "tab", "current"); + gtk_box_pack_start (GTK_BOX (box), new_tab_button, FALSE, FALSE, 0); + gtk_widget_show (new_tab_button); + } + + /* Create Tabs menu button */ + tabs_menu_button = terminal_menu_button_new (); + g_signal_connect (tabs_menu_button, "update-menu", + G_CALLBACK (notebook_update_tabs_menu_cb), window); + gtk_box_pack_start (GTK_BOX (box), tabs_menu_button, FALSE, FALSE, 0); + gtk_menu_button_set_align_widget (GTK_MENU_BUTTON (tabs_menu_button), box); + gtk_widget_show (tabs_menu_button); +} + +/*****************************************/ + +#ifdef ENABLE_DEBUG +static void +terminal_window_size_request_cb (GtkWidget *widget, + GtkRequisition *req) +{ + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, + "[window %p] size-request result %d : %d\n", + widget, req->width, req->height); +} + +static void +terminal_window_size_allocate_cb (GtkWidget *widget, + GtkAllocation *allocation) +{ + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, + "[window %p] size-alloc result %d : %d at (%d, %d)\n", + widget, + allocation->width, allocation->height, + allocation->x, allocation->y); +} +#endif /* ENABLE_DEBUG */ + +static void +terminal_window_realize (GtkWidget *widget) +{ + TerminalWindow *window = TERMINAL_WINDOW (widget); + TerminalWindowPrivate *priv = window->priv; + GtkAllocation widget_allocation; + + gtk_widget_get_allocation (widget, &widget_allocation); + + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, + "[window %p] realize, size %d : %d at (%d, %d)\n", + widget, + widget_allocation.width, widget_allocation.height, + widget_allocation.x, widget_allocation.y); + + GTK_WIDGET_CLASS (terminal_window_parent_class)->realize (widget); + + /* Now that we've been realized, we should know precisely how large the + * client-side decorations are going to be. Recalculate the geometry hints, + * export them to the windowing system, and resize the window accordingly. */ + priv->realized = TRUE; + terminal_window_update_size (window); +} + +static gboolean +terminal_window_state_event (GtkWidget *widget, + GdkEventWindowState *event) +{ + gboolean (* window_state_event) (GtkWidget *, GdkEventWindowState *event) = + GTK_WIDGET_CLASS (terminal_window_parent_class)->window_state_event; + + if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) + { + TerminalWindow *window = TERMINAL_WINDOW (widget); + gboolean is_fullscreen; + + is_fullscreen = (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) != 0; + + g_simple_action_set_state (lookup_action (window, "fullscreen"), + g_variant_new_boolean (is_fullscreen)); + g_simple_action_set_enabled (lookup_action (window, "leave-fullscreen"), + is_fullscreen); + g_simple_action_set_enabled (lookup_action (window, "size-to"), + !is_fullscreen); + } + + if (window_state_event) + return window_state_event (widget, event); + + return FALSE; +} + +static void +terminal_window_screen_update (TerminalWindow *window, + GdkScreen *screen) +{ + GSettings *settings; + GtkSettings *gtk_settings; + + if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (screen), "GT::HasSettingsConnection"))) + return; + + settings = terminal_app_get_global_settings (terminal_app_get ()); + gtk_settings = gtk_settings_get_for_screen (screen); + + g_object_set_data_full (G_OBJECT (screen), "GT::HasSettingsConnection", + gtk_settings, + (GDestroyNotify) app_setting_notify_destroy_cb); + + g_settings_bind (settings, + TERMINAL_SETTING_ENABLE_SHORTCUTS_KEY, + gtk_settings, + "gtk-enable-accels", + G_SETTINGS_BIND_GET); + + enable_menubar_accel_changed_cb (settings, + TERMINAL_SETTING_ENABLE_MENU_BAR_ACCEL_KEY, + gtk_settings); + g_signal_connect (settings, "changed::" TERMINAL_SETTING_ENABLE_MENU_BAR_ACCEL_KEY, + G_CALLBACK (enable_menubar_accel_changed_cb), + gtk_settings); +} + +static void +terminal_window_screen_changed (GtkWidget *widget, + GdkScreen *previous_screen) +{ + TerminalWindow *window = TERMINAL_WINDOW (widget); + void (* screen_changed) (GtkWidget *, GdkScreen *) = + GTK_WIDGET_CLASS (terminal_window_parent_class)->screen_changed; + GdkScreen *screen; + + if (screen_changed) + screen_changed (widget, previous_screen); + + screen = gtk_widget_get_screen (widget); + if (previous_screen == screen) + return; + + if (!screen) + return; + + terminal_window_screen_update (window, screen); +} + +static void +terminal_window_init (TerminalWindow *window) +{ + const GActionEntry action_entries[] = { + /* Actions without state */ + { "about", action_about_cb, NULL, NULL, NULL }, + { "close", action_close_cb, "s", NULL, NULL }, + { "copy", action_copy_cb, "s", NULL, NULL }, + { "copy-hyperlink", action_copy_hyperlink_cb, NULL, NULL, NULL }, + { "copy-match", action_copy_match_cb, NULL, NULL, NULL }, + { "edit-preferences", action_edit_preferences_cb, NULL, NULL, NULL }, + { "enter-fullscreen", action_enter_fullscreen_cb, NULL, NULL, NULL }, + { "find", action_find_cb, NULL, NULL, NULL }, + { "find-backward", action_find_backward_cb, NULL, NULL, NULL }, + { "find-clear", action_find_clear_cb, NULL, NULL, NULL }, + { "find-forward", action_find_forward_cb, NULL, NULL, NULL }, + { "help", action_help_cb, NULL, NULL, NULL }, + { "inspector", action_inspector_cb, NULL, NULL, NULL }, + { "leave-fullscreen", action_leave_fullscreen_cb, NULL, NULL, NULL }, + { "new-terminal", action_new_terminal_cb, "(ss)", NULL, NULL }, + { "open-match", action_open_match_cb, NULL, NULL, NULL }, + { "open-hyperlink", action_open_hyperlink_cb, NULL, NULL, NULL }, + { "paste-text", action_paste_text_cb, NULL, NULL, NULL }, + { "paste-uris", action_paste_uris_cb, NULL, NULL, NULL }, + { "reset", action_reset_cb, "b", NULL, NULL }, + { "select-all", action_select_all_cb, NULL, NULL, NULL }, + { "size-to", action_size_to_cb, "(uu)", NULL, NULL }, + { "tab-detach", action_tab_detach_cb, NULL, NULL, NULL }, + { "tab-move-left", action_tab_move_left_cb, NULL, NULL, NULL }, + { "tab-move-right", action_tab_move_right_cb, NULL, NULL, NULL }, + { "tab-switch-left", action_tab_switch_left_cb, NULL, NULL, NULL }, + { "tab-switch-right", action_tab_switch_right_cb, NULL, NULL, NULL }, + { "tabs-menu", NULL, NULL, NULL, NULL }, + { "zoom-in", action_zoom_in_cb, NULL, NULL, NULL }, + { "zoom-normal", action_zoom_normal_cb, NULL, NULL, NULL }, + { "zoom-out", action_zoom_out_cb, NULL, NULL, NULL }, +#ifdef ENABLE_EXPORT + { "export", action_export_cb, NULL, NULL, NULL }, +#endif +#ifdef ENABLE_PRINT + { "print", action_print_cb, NULL, NULL, NULL }, +#endif +#ifdef ENABLE_SAVE + { "save-contents", action_save_contents_cb, NULL, NULL, NULL }, +#endif + + /* Shadow actions for keybinding comsumption, see comment in terminal-accels.c */ + { "shadow", action_shadow_activate_cb, "s", NULL, NULL }, + { "shadow-mdi", action_shadow_activate_cb, "s", NULL, NULL }, + + /* Actions with state */ + { "active-tab", action_active_tab_set_cb, "i", "@i 0", action_active_tab_state_cb }, + { "header-menu", NULL /* toggles state */, NULL, "false", NULL }, + { "fullscreen", NULL /* toggles state */, NULL, "false", action_fullscreen_state_cb }, + { "menubar-visible", NULL /* toggles state */, NULL, "true", action_menubar_visible_state_cb }, + { "profile", NULL /* changes state */, "s", "''", action_profile_state_cb }, + { "read-only", NULL /* toggles state */, NULL, "false", action_read_only_state_cb }, + }; + TerminalWindowPrivate *priv; + TerminalApp *app; + GSettings *gtk_debug_settings; + GtkWindowGroup *window_group; + // GtkAccelGroup *accel_group; + uuid_t u; + char uuidstr[37], role[64]; + gboolean shell_shows_menubar; + gboolean use_headerbar; + GSimpleAction *action; + + app = terminal_app_get (); + + priv = window->priv = G_TYPE_INSTANCE_GET_PRIVATE (window, TERMINAL_TYPE_WINDOW, TerminalWindowPrivate); + + gtk_widget_init_template (GTK_WIDGET (window)); + + uuid_generate (u); + uuid_unparse (u, uuidstr); + priv->uuid = g_strdup (uuidstr); + + g_signal_connect (G_OBJECT (window), "delete_event", + G_CALLBACK(terminal_window_delete_event), + NULL); +#ifdef ENABLE_DEBUG + _TERMINAL_DEBUG_IF (TERMINAL_DEBUG_GEOMETRY) + { + g_signal_connect_after (window, "size-request", G_CALLBACK (terminal_window_size_request_cb), NULL); + g_signal_connect_after (window, "size-allocate", G_CALLBACK (terminal_window_size_allocate_cb), NULL); + } +#endif + + use_headerbar = terminal_app_get_use_headerbar (app); + if (use_headerbar) { + GtkWidget *headerbar; + + headerbar = terminal_headerbar_new (); + gtk_window_set_titlebar (GTK_WINDOW (window), headerbar); + } + + gtk_window_set_title (GTK_WINDOW (window), _("Terminal")); + + priv->active_screen = NULL; + + priv->main_vbox = gtk_bin_get_child (GTK_BIN (window)); + + priv->mdi_container = TERMINAL_MDI_CONTAINER (terminal_notebook_new ()); + + g_signal_connect (priv->mdi_container, "screen-close-request", + G_CALLBACK (screen_close_request_cb), window); + + g_signal_connect_after (priv->mdi_container, "screen-switched", + G_CALLBACK (mdi_screen_switched_cb), window); + g_signal_connect_after (priv->mdi_container, "screen-added", + G_CALLBACK (mdi_screen_added_cb), window); + g_signal_connect_after (priv->mdi_container, "screen-removed", + G_CALLBACK (mdi_screen_removed_cb), window); + g_signal_connect_after (priv->mdi_container, "screens-reordered", + G_CALLBACK (mdi_screens_reordered_cb), window); + + g_signal_connect_swapped (priv->mdi_container, "notify::tab-pos", + G_CALLBACK (terminal_window_update_geometry), window); + g_signal_connect_swapped (priv->mdi_container, "notify::show-tabs", + G_CALLBACK (terminal_window_update_geometry), window); + + /* FIXME hack hack! */ + if (GTK_IS_NOTEBOOK (priv->mdi_container)) { + g_signal_connect (priv->mdi_container, "button-press-event", + G_CALLBACK (notebook_button_press_cb), window); + g_signal_connect (priv->mdi_container, "popup-menu", + G_CALLBACK (notebook_popup_menu_cb), window); + g_signal_connect (priv->mdi_container, "create-window", + G_CALLBACK (handle_tab_droped_on_desktop), window); + } + + gtk_box_pack_end (GTK_BOX (priv->main_vbox), GTK_WIDGET (priv->mdi_container), TRUE, TRUE, 0); + gtk_widget_show (GTK_WIDGET (priv->mdi_container)); + + priv->old_char_width = -1; + priv->old_char_height = -1; + + priv->old_chrome_width = -1; + priv->old_chrome_height = -1; + priv->old_csd_width = -1; + priv->old_csd_height = -1; + priv->old_padding_width = -1; + priv->old_padding_height = -1; + + priv->old_geometry_widget = NULL; + + /* GAction setup */ + g_action_map_add_action_entries (G_ACTION_MAP (window), + action_entries, G_N_ELEMENTS (action_entries), + window); + + g_simple_action_set_enabled (lookup_action (window, "leave-fullscreen"), FALSE); + + GSettings *global_settings = terminal_app_get_global_settings (app); + enable_mnemonics_changed_cb (global_settings, TERMINAL_SETTING_ENABLE_MNEMONICS_KEY, window); + g_signal_connect (global_settings, "changed::" TERMINAL_SETTING_ENABLE_MNEMONICS_KEY, + G_CALLBACK (enable_mnemonics_changed_cb), window); + + /* Hide "menubar-visible" when the menubar is shown by the shell */ + g_object_get (gtk_widget_get_settings (GTK_WIDGET (window)), + "gtk-shell-shows-menubar", &shell_shows_menubar, + NULL); + if (shell_shows_menubar) { + g_simple_action_set_enabled (lookup_action (window, "menubar-visible"), FALSE); + } else { + priv->menubar = gtk_menu_bar_new_from_model (terminal_app_get_menubar (app)); + gtk_box_pack_start (GTK_BOX (priv->main_vbox), + priv->menubar, + FALSE, FALSE, 0); + + terminal_window_set_menubar_visible (window, !use_headerbar); + priv->use_default_menubar_visibility = !use_headerbar; + } + + /* Maybe make Inspector available */ + action = lookup_action (window, "inspector"); + gtk_debug_settings = terminal_app_get_gtk_debug_settings (app); + if (gtk_debug_settings != NULL) + g_settings_bind (gtk_debug_settings, + "enable-inspector-keybinding", + action, + "enabled", + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY); + else + g_simple_action_set_enabled (action, FALSE); + + priv->clipboard = gtk_widget_get_clipboard (GTK_WIDGET (window), GDK_SELECTION_CLIPBOARD); + clipboard_targets_changed_cb (app, priv->clipboard, window); + g_signal_connect (app, "clipboard-targets-changed", + G_CALLBACK (clipboard_targets_changed_cb), window); + + terminal_window_fill_notebook_action_box (window, !use_headerbar); + + /* We have to explicitly call this, since screen-changed is NOT + * emitted for the toplevel the first time! + */ + terminal_window_screen_update (window, gtk_widget_get_screen (GTK_WIDGET (window))); + + window_group = gtk_window_group_new (); + gtk_window_group_add_window (window_group, GTK_WINDOW (window)); + g_object_unref (window_group); + + g_snprintf (role, sizeof (role), "gnome-terminal-window-%s", uuidstr); + gtk_window_set_role (GTK_WINDOW (window), role); +} + +static void +terminal_window_style_updated (GtkWidget *widget) +{ + TerminalWindow *window = TERMINAL_WINDOW (widget); + + GTK_WIDGET_CLASS (terminal_window_parent_class)->style_updated (widget); + + terminal_window_update_size (window); +} + +static void +terminal_window_class_init (TerminalWindowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = terminal_window_dispose; + object_class->finalize = terminal_window_finalize; + + widget_class->show = terminal_window_show; + widget_class->realize = terminal_window_realize; + widget_class->window_state_event = terminal_window_state_event; + widget_class->screen_changed = terminal_window_screen_changed; + widget_class->style_updated = terminal_window_style_updated; + + GtkWindowClass *window_klass; + GtkBindingSet *binding_set; + + window_klass = g_type_class_ref (GTK_TYPE_WINDOW); + binding_set = gtk_binding_set_by_class (window_klass); + gtk_binding_entry_skip (binding_set, GDK_KEY_I, GDK_CONTROL_MASK|GDK_SHIFT_MASK); + gtk_binding_entry_skip (binding_set, GDK_KEY_D, GDK_CONTROL_MASK|GDK_SHIFT_MASK); + g_type_class_unref (window_klass); + + g_type_class_add_private (object_class, sizeof (TerminalWindowPrivate)); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/terminal/ui/window.ui"); + + gtk_widget_class_set_css_name(widget_class, TERMINAL_WINDOW_CSS_NAME); +} + +static void +terminal_window_dispose (GObject *object) +{ + TerminalWindow *window = TERMINAL_WINDOW (object); + TerminalWindowPrivate *priv = window->priv; + TerminalApp *app = terminal_app_get (); + + if (!priv->disposed) { + GSettings *global_settings = terminal_app_get_global_settings (app); + g_signal_handlers_disconnect_by_func (global_settings, + G_CALLBACK (enable_mnemonics_changed_cb), + window); + } + + priv->disposed = TRUE; + + if (priv->clipboard != NULL) { + g_signal_handlers_disconnect_by_func (app, + G_CALLBACK (clipboard_targets_changed_cb), + window); + priv->clipboard = NULL; + } + + remove_popup_info (window); + + if (priv->search_popover != NULL) + { + g_signal_handlers_disconnect_matched (priv->search_popover, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, window); + gtk_widget_destroy (GTK_WIDGET (priv->search_popover)); + priv->search_popover = NULL; + } + + G_OBJECT_CLASS (terminal_window_parent_class)->dispose (object); +} + +static void +terminal_window_finalize (GObject *object) +{ + TerminalWindow *window = TERMINAL_WINDOW (object); + TerminalWindowPrivate *priv = window->priv; + + if (priv->confirm_close_dialog) + gtk_dialog_response (GTK_DIALOG (priv->confirm_close_dialog), + GTK_RESPONSE_DELETE_EVENT); + + g_free (priv->uuid); + + G_OBJECT_CLASS (terminal_window_parent_class)->finalize (object); +} + +static gboolean +terminal_window_delete_event (GtkWidget *widget, + GdkEvent *event, + gpointer data) +{ + return confirm_close_window_or_tab (TERMINAL_WINDOW (widget), NULL); +} + +static void +terminal_window_show (GtkWidget *widget) +{ + TerminalWindow *window = TERMINAL_WINDOW (widget); + TerminalWindowPrivate *priv = window->priv; + GtkAllocation widget_allocation; + + gtk_widget_get_allocation (widget, &widget_allocation); + + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, + "[window %p] show, size %d : %d at (%d, %d)\n", + widget, + widget_allocation.width, widget_allocation.height, + widget_allocation.x, widget_allocation.y); + + /* Because of the unexpected reentrancy caused by adding the tab to the notebook + * showing the TerminalWindow, we can get here when the first page has been + * added but not yet set current. By setting the page current, we get the + * right size when we first show the window */ + if (GTK_IS_NOTEBOOK (priv->mdi_container) && + gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->mdi_container)) == -1) + gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->mdi_container), 0); + + if (priv->active_screen != NULL) + { + /* At this point, we have our GdkScreen, and hence the right + * font size, so we can go ahead and size the window. */ + terminal_window_update_size (window); + } + + GTK_WIDGET_CLASS (terminal_window_parent_class)->show (widget); +} + +TerminalWindow* +terminal_window_new (GApplication *app) +{ + return g_object_new (TERMINAL_TYPE_WINDOW, + "application", app, + "show-menubar", FALSE, + NULL); +} + +static void +profile_set_cb (TerminalScreen *screen, + GSettings *old_profile, + TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + + if (!gtk_widget_get_realized (GTK_WIDGET (window))) + return; + + if (screen != priv->active_screen) + return; + + terminal_window_update_set_profile_menu_active_profile (window); +} + +static void +sync_screen_title (TerminalScreen *screen, + GParamSpec *psepc, + TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + const char *title; + + if (screen != priv->active_screen) + return; + + title = terminal_screen_get_title (screen); + gtk_window_set_title (GTK_WINDOW (window), + title && title[0] ? title : _("Terminal")); +} + +static void +screen_font_any_changed_cb (TerminalScreen *screen, + GParamSpec *psepc, + TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + + if (!gtk_widget_get_realized (GTK_WIDGET (window))) + return; + + if (screen != priv->active_screen) + return; + + terminal_window_update_size (window); +} + +static void +screen_hyperlink_hover_uri_changed (TerminalScreen *screen, + const char *uri, + const GdkRectangle *bbox G_GNUC_UNUSED, + TerminalWindow *window G_GNUC_UNUSED) +{ + gs_free char *label = NULL; + + if (!gtk_widget_get_realized (GTK_WIDGET (screen))) + return; + + label = terminal_util_hyperlink_uri_label (uri); + + gtk_widget_set_tooltip_text (GTK_WIDGET (screen), label); +} + +/* MDI container callbacks */ + +static void +screen_close_request_cb (TerminalMdiContainer *container, + TerminalScreen *screen, + TerminalWindow *window) +{ + if (confirm_close_window_or_tab (window, screen)) + return; + + terminal_window_remove_screen (window, screen); +} + +int +terminal_window_get_active_screen_num (TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + return terminal_mdi_container_get_active_screen_num (priv->mdi_container); +} + +void +terminal_window_add_screen (TerminalWindow *window, + TerminalScreen *screen, + int position) +{ + TerminalWindowPrivate *priv = window->priv; + GtkWidget *old_window; + + old_window = gtk_widget_get_toplevel (GTK_WIDGET (screen)); + if (gtk_widget_is_toplevel (old_window) && + TERMINAL_IS_WINDOW (old_window) && + TERMINAL_WINDOW (old_window)== window) + return; + + if (TERMINAL_IS_WINDOW (old_window)) + terminal_window_remove_screen (TERMINAL_WINDOW (old_window), screen); + + if (position == -1) { + GSettings *global_settings = terminal_app_get_global_settings (terminal_app_get ()); + TerminalNewTabPosition position_pref = g_settings_get_enum (global_settings, + TERMINAL_SETTING_NEW_TAB_POSITION_KEY); + switch (position_pref) { + case TERMINAL_NEW_TAB_POSITION_NEXT: + position = terminal_window_get_active_screen_num (window) + 1; + break; + + default: + case TERMINAL_NEW_TAB_POSITION_LAST: + position = -1; + break; + } + } + + terminal_mdi_container_add_screen (priv->mdi_container, screen, position); +} + +void +terminal_window_remove_screen (TerminalWindow *window, + TerminalScreen *screen) +{ + TerminalWindowPrivate *priv = window->priv; + + terminal_mdi_container_remove_screen (priv->mdi_container, screen); +} + +void +terminal_window_move_screen (TerminalWindow *source_window, + TerminalWindow *dest_window, + TerminalScreen *screen, + int dest_position) +{ + TerminalScreenContainer *screen_container; + + g_return_if_fail (TERMINAL_IS_WINDOW (source_window)); + g_return_if_fail (TERMINAL_IS_WINDOW (dest_window)); + g_return_if_fail (TERMINAL_IS_SCREEN (screen)); + g_return_if_fail (gtk_widget_get_toplevel (GTK_WIDGET (screen)) == GTK_WIDGET (source_window)); + g_return_if_fail (dest_position >= -1); + + screen_container = terminal_screen_container_get_from_screen (screen); + g_assert (TERMINAL_IS_SCREEN_CONTAINER (screen_container)); + + /* We have to ref the screen container as well as the screen, + * because otherwise removing the screen container from the source + * window's notebook will cause the container and its containing + * screen to be gtk_widget_destroy()ed! + */ + g_object_ref_sink (screen_container); + g_object_ref_sink (screen); + terminal_window_remove_screen (source_window, screen); + + /* Now we can safely remove the screen from the container and let the container die */ + gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (GTK_WIDGET (screen))), GTK_WIDGET (screen)); + g_object_unref (screen_container); + + terminal_window_add_screen (dest_window, screen, dest_position); + terminal_mdi_container_set_active_screen (dest_window->priv->mdi_container, screen); + g_object_unref (screen); +} + +GList* +terminal_window_list_screen_containers (TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + + return terminal_mdi_container_list_screen_containers (priv->mdi_container); +} + +void +terminal_window_set_menubar_visible (TerminalWindow *window, + gboolean setting) +{ + TerminalWindowPrivate *priv = window->priv; + + if (priv->menubar == NULL) + return; + + /* it's been set now, so don't override when adding a screen. + * this side effect must happen before we short-circuit below. + */ + priv->use_default_menubar_visibility = FALSE; + + g_simple_action_set_state (lookup_action (window, "menubar-visible"), + g_variant_new_boolean (setting)); + + g_object_set (priv->menubar, "visible", setting, NULL); + + /* FIXMEchpe: use gtk_widget_get_realized instead? */ + if (priv->active_screen) + { + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, + "[window %p] setting size after toggling menubar visibility\n", + window); + + terminal_window_update_size (window); + } +} + +GtkWidget * +terminal_window_get_mdi_container (TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + + g_return_val_if_fail (TERMINAL_IS_WINDOW (window), NULL); + + return GTK_WIDGET (priv->mdi_container); +} + +void +terminal_window_update_size (TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + int grid_width, grid_height; + int pixel_width, pixel_height; + GdkWindow *gdk_window; + + gdk_window = gtk_widget_get_window (GTK_WIDGET (window)); + + if (gdk_window != NULL && + (gdk_window_get_state (gdk_window) & + (GDK_WINDOW_STATE_MAXIMIZED | WINDOW_STATE_TILED | GDK_WINDOW_STATE_FULLSCREEN))) + { + /* Don't adjust the size of maximized or tiled (snapped, half-maximized) + * windows: if we do, there will be ugly gaps of up to 1 character cell + * around otherwise tiled windows. */ + return; + } + + /* be sure our geometry is up-to-date */ + terminal_window_update_geometry (window); + + terminal_screen_get_size (priv->active_screen, &grid_width, &grid_height); + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, + "[window %p] size is %dx%d cells of %dx%d px\n", + window, grid_width, grid_height, + priv->old_char_width, priv->old_char_height); + + /* the "old" struct members were updated by update_geometry */ + pixel_width = priv->old_chrome_width + grid_width * priv->old_char_width; + pixel_height = priv->old_chrome_height + grid_height * priv->old_char_height; + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, + "[window %p] %dx%d + %dx%d = %dx%d\n", + window, grid_width * priv->old_char_width, + grid_height * priv->old_char_height, + priv->old_chrome_width, priv->old_chrome_height, + pixel_width, pixel_height); + + gtk_window_resize (GTK_WINDOW (window), pixel_width, pixel_height); +} + +void +terminal_window_switch_screen (TerminalWindow *window, + TerminalScreen *screen) +{ + TerminalWindowPrivate *priv = window->priv; + + terminal_mdi_container_set_active_screen (priv->mdi_container, screen); +} + +TerminalScreen* +terminal_window_get_active (TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + + return terminal_mdi_container_get_active_screen (priv->mdi_container); +} + +static void +notebook_show_context_menu (TerminalWindow *window, + GdkEvent *event, + guint button, + guint32 timestamp) +{ + /* Load the UI */ + gs_unref_object GMenu *menu; + terminal_util_load_objects_resource ("/org/gnome/terminal/ui/notebook-menu.ui", + "notebook-popup", &menu, + NULL); + + GtkWidget *popup_menu = context_menu_new (G_MENU_MODEL (menu), GTK_WIDGET (window)); + + gtk_widget_set_halign (popup_menu, GTK_ALIGN_START); + gtk_menu_popup (GTK_MENU (popup_menu), NULL, NULL, + NULL, NULL, + button, timestamp); + + if (button == 0) + gtk_menu_shell_select_first (GTK_MENU_SHELL (popup_menu), FALSE); +} + +static gboolean +notebook_button_press_cb (GtkWidget *widget, + GdkEventButton *event, + TerminalWindow *window) +{ + GtkNotebook *notebook = GTK_NOTEBOOK (widget); + int tab_clicked; + + if (event->type != GDK_BUTTON_PRESS || + event->button != GDK_BUTTON_SECONDARY || + (event->state & gtk_accelerator_get_default_mod_mask ()) != 0) + return FALSE; + + tab_clicked = find_tab_num_at_pos (notebook, event->x_root, event->y_root); + if (tab_clicked < 0) + return FALSE; + + /* switch to the page the mouse is over */ + gtk_notebook_set_current_page (notebook, tab_clicked); + + notebook_show_context_menu (window, (GdkEvent*)event, event->button, event->time); + return TRUE; +} + +static gboolean +notebook_popup_menu_cb (GtkWidget *widget, + TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + GtkWidget *focus_widget; + + focus_widget = gtk_window_get_focus (GTK_WINDOW (window)); + /* Only respond if the notebook is the actual focus */ + if (focus_widget != GTK_WIDGET (priv->mdi_container)) + return FALSE; + + notebook_show_context_menu (window, NULL, 0, gtk_get_current_event_time ()); + return TRUE; +} + +static void +mdi_screen_switched_cb (TerminalMdiContainer *container, + TerminalScreen *old_active_screen, + TerminalScreen *screen, + TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + int old_grid_width, old_grid_height; + + _terminal_debug_print (TERMINAL_DEBUG_MDI, + "[window %p] MDI: screen-switched old %p new %p\n", + window, old_active_screen, screen); + + if (priv->disposed) + return; + + if (screen == NULL || old_active_screen == screen) + return; + + if (priv->search_popover != NULL) + gtk_widget_hide (GTK_WIDGET (priv->search_popover)); + + _terminal_debug_print (TERMINAL_DEBUG_MDI, + "[window %p] MDI: setting active tab to screen %p (old active screen %p)\n", + window, screen, priv->active_screen); + + if (old_active_screen != NULL && screen != NULL) { + terminal_screen_get_size (old_active_screen, &old_grid_width, &old_grid_height); + + /* This is so that we maintain the same grid */ + vte_terminal_set_size (VTE_TERMINAL (screen), old_grid_width, old_grid_height); + } + + priv->active_screen = screen; + + /* Override menubar setting if it wasn't restored from session */ + if (priv->use_default_menubar_visibility) + { + gboolean setting = + g_settings_get_boolean (terminal_app_get_global_settings (terminal_app_get ()), + TERMINAL_SETTING_DEFAULT_SHOW_MENUBAR_KEY); + + terminal_window_set_menubar_visible (window, setting); + } + + sync_screen_title (screen, NULL, window); + + /* set size of window to current grid size */ + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, + "[window %p] setting size after flipping notebook pages\n", + window); + terminal_window_update_size (window); + + terminal_window_update_tabs_actions_sensitivity (window); + terminal_window_update_terminal_menu (window); + terminal_window_update_set_profile_menu_active_profile (window); + terminal_window_update_copy_sensitivity (screen, window); + terminal_window_update_zoom_sensitivity (window); + terminal_window_update_search_sensitivity (screen, window); + terminal_window_update_paste_sensitivity (window); +} + +static void +mdi_screen_added_cb (TerminalMdiContainer *container, + TerminalScreen *screen, + TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + int pages; + + _terminal_debug_print (TERMINAL_DEBUG_MDI, + "[window %p] MDI: screen %p inserted\n", + window, screen); + + g_signal_connect (G_OBJECT (screen), + "profile-set", + G_CALLBACK (profile_set_cb), + window); + + /* FIXME: only connect on the active screen, not all screens! */ + g_signal_connect (screen, "notify::title", + G_CALLBACK (sync_screen_title), window); + g_signal_connect (screen, "notify::font-desc", + G_CALLBACK (screen_font_any_changed_cb), window); + g_signal_connect (screen, "notify::font-scale", + G_CALLBACK (screen_font_any_changed_cb), window); + g_signal_connect (screen, "notify::cell-height-scale", + G_CALLBACK (screen_font_any_changed_cb), window); + g_signal_connect (screen, "notify::cell-width-scale", + G_CALLBACK (screen_font_any_changed_cb), window); + g_signal_connect (screen, "selection-changed", + G_CALLBACK (terminal_window_update_copy_sensitivity), window); + g_signal_connect (screen, "hyperlink-hover-uri-changed", + G_CALLBACK (screen_hyperlink_hover_uri_changed), window); + + g_signal_connect (screen, "show-popup-menu", + G_CALLBACK (screen_show_popup_menu_cb), window); + g_signal_connect (screen, "match-clicked", + G_CALLBACK (screen_match_clicked_cb), window); + g_signal_connect (screen, "resize-window", + G_CALLBACK (screen_resize_window_cb), window); + + g_signal_connect (screen, "close-screen", + G_CALLBACK (screen_close_cb), window); + + terminal_window_update_tabs_actions_sensitivity (window); + terminal_window_update_search_sensitivity (screen, window); + terminal_window_update_paste_sensitivity (window); + +#if 0 + /* FIXMEchpe: wtf is this doing? */ + + /* If we have an active screen, match its size and zoom */ + if (priv->active_screen) + { + int current_width, current_height; + double scale; + + terminal_screen_get_size (priv->active_screen, ¤t_width, ¤t_height); + vte_terminal_set_size (VTE_TERMINAL (screen), current_width, current_height); + + scale = terminal_screen_get_font_scale (priv->active_screen); + terminal_screen_set_font_scale (screen, scale); + } +#endif + + if (priv->present_on_insert) + { + gtk_window_present_with_time (GTK_WINDOW (window), gtk_get_current_event_time ()); + priv->present_on_insert = FALSE; + } + + pages = terminal_mdi_container_get_n_screens (container); + if (pages == 2) + { + terminal_window_update_size (window); + } +} + +static void +mdi_screen_removed_cb (TerminalMdiContainer *container, + TerminalScreen *screen, + TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + int pages; + + if (priv->disposed) + return; + + _terminal_debug_print (TERMINAL_DEBUG_MDI, + "[window %p] MDI: screen %p removed\n", + window, screen); + + g_signal_handlers_disconnect_by_func (G_OBJECT (screen), + G_CALLBACK (profile_set_cb), + window); + + g_signal_handlers_disconnect_by_func (G_OBJECT (screen), + G_CALLBACK (sync_screen_title), + window); + + g_signal_handlers_disconnect_by_func (G_OBJECT (screen), + G_CALLBACK (screen_font_any_changed_cb), + window); + + g_signal_handlers_disconnect_by_func (G_OBJECT (screen), + G_CALLBACK (terminal_window_update_copy_sensitivity), + window); + + g_signal_handlers_disconnect_by_func (G_OBJECT (screen), + G_CALLBACK (screen_hyperlink_hover_uri_changed), + window); + + g_signal_handlers_disconnect_by_func (screen, + G_CALLBACK (screen_show_popup_menu_cb), + window); + + g_signal_handlers_disconnect_by_func (screen, + G_CALLBACK (screen_match_clicked_cb), + window); + g_signal_handlers_disconnect_by_func (screen, + G_CALLBACK (screen_resize_window_cb), + window); + + g_signal_handlers_disconnect_by_func (screen, + G_CALLBACK (screen_close_cb), + window); + + /* We already got a switch-page signal whose handler sets the active tab to the + * new active tab, unless this screen was the only one in the notebook, so + * priv->active_tab is valid here. + */ + + pages = terminal_mdi_container_get_n_screens (container); + if (pages == 0) + { + priv->active_screen = NULL; + + /* That was the last tab in the window; close it. */ + gtk_widget_destroy (GTK_WIDGET (window)); + return; + } + + terminal_window_update_tabs_actions_sensitivity (window); + terminal_window_update_search_sensitivity (screen, window); + + if (pages == 1) + { + TerminalScreen *active_screen = terminal_mdi_container_get_active_screen (container); + gtk_widget_grab_focus (GTK_WIDGET(active_screen)); /* bug 742422 */ + + terminal_window_update_size (window); + } +} + +static void +mdi_screens_reordered_cb (TerminalMdiContainer *container, + TerminalWindow *window) +{ + terminal_window_update_tabs_actions_sensitivity (window); +} + +gboolean +terminal_window_parse_geometry (TerminalWindow *window, + const char *geometry) +{ + TerminalWindowPrivate *priv = window->priv; + + /* gtk_window_parse_geometry() needs to have the right base size + * and width/height increment to compute the window size from + * the geometry. + */ + terminal_window_update_geometry (window); + + if (!gtk_window_parse_geometry (GTK_WINDOW (window), geometry)) + return FALSE; + + /* We won't actually get allocated at the size parsed out of the + * geometry until the window is shown. If terminal_window_update_size() + * is called between now and then, that could result in us getting + * snapped back to the old grid size. So we need to immediately + * update the size of the active terminal to grid size from the + * geometry. + */ + if (priv->active_screen) + { + int grid_width, grid_height; + + /* After parse_geometry(), the default size is in units of the + * width/height increment, not a pixel size */ + gtk_window_get_default_size (GTK_WINDOW (window), &grid_width, &grid_height); + + vte_terminal_set_size (VTE_TERMINAL (priv->active_screen), + grid_width, grid_height); + } + + return TRUE; +} + +void +terminal_window_update_geometry (TerminalWindow *window) +{ + TerminalWindowPrivate *priv = window->priv; + GtkWidget *widget; + GdkGeometry hints; + GtkBorder padding; + GtkRequisition vbox_request, widget_request; + int grid_width, grid_height; + int char_width, char_height; + int chrome_width, chrome_height; + int csd_width = 0, csd_height = 0; + + if (gtk_widget_in_destruction (GTK_WIDGET (window))) + return; + + if (priv->active_screen == NULL) + return; + + widget = GTK_WIDGET (priv->active_screen); + + /* We set geometry hints from the active term; best thing + * I can think of to do. Other option would be to try to + * get some kind of union of all hints from all terms in the + * window, but that doesn't make too much sense. + */ + terminal_screen_get_cell_size (priv->active_screen, &char_width, &char_height); + + terminal_screen_get_size (priv->active_screen, &grid_width, &grid_height); + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "%dx%d cells of %dx%d px = %dx%d px\n", + grid_width, grid_height, char_width, char_height, + char_width * grid_width, char_height * grid_height); + + gtk_style_context_get_padding(gtk_widget_get_style_context(widget), + gtk_widget_get_state_flags(widget), + &padding); + + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "padding = %dx%d px\n", + padding.left + padding.right, + padding.top + padding.bottom); + + gtk_widget_get_preferred_size (priv->main_vbox, NULL, &vbox_request); + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "content area requests %dx%d px\n", + vbox_request.width, vbox_request.height); + + + chrome_width = vbox_request.width - (char_width * grid_width); + chrome_height = vbox_request.height - (char_height * grid_height); + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "chrome: %dx%d px\n", + chrome_width, chrome_height); + + if (priv->realized) + { + /* Only when having been realize the CSD can be calculated. Do this by + * using the actual allocation rather then the preferred size as the + * the preferred size takes the natural size of e.g. the title bar into + * account which can be far wider then the contents size when using a + * very long title */ + GtkAllocation toplevel_allocation, vbox_allocation; + + gtk_widget_get_allocation (GTK_WIDGET (priv->main_vbox), &vbox_allocation); + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, + "terminal widget allocation %dx%d px\n", + vbox_allocation.width, vbox_allocation.height); + + gtk_widget_get_allocation (GTK_WIDGET (window), &toplevel_allocation); + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "window allocation %dx%d px\n", + toplevel_allocation.width, toplevel_allocation.height); + + csd_width = toplevel_allocation.width - vbox_allocation.width; + csd_height = toplevel_allocation.height - vbox_allocation.height; + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "CSDs: %dx%d px\n", + csd_width, csd_height); + } + + gtk_widget_get_preferred_size (widget, NULL, &widget_request); + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "terminal widget requests %dx%d px\n", + widget_request.width, widget_request.height); + + if (!priv->realized) + { + /* Don't actually set the geometry hints until we have been realized, + * because we don't know how large the client-side decorations are going + * to be. We also avoid setting priv->old_csd_width or + * priv->old_csd_height, so that next time through this function we'll + * definitely recalculate the hints. + * + * Similarly, the size request doesn't seem to include the padding + * until we've been redrawn at least once. Don't resize the window + * until we've done that. */ + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "not realized yet\n"); + } + else if (char_width != priv->old_char_width || + char_height != priv->old_char_height || + padding.left + padding.right != priv->old_padding_width || + padding.top + padding.bottom != priv->old_padding_height || + chrome_width != priv->old_chrome_width || + chrome_height != priv->old_chrome_height || + csd_width != priv->old_csd_width || + csd_height != priv->old_csd_height || + widget != (GtkWidget*) priv->old_geometry_widget) + { + hints.base_width = chrome_width + csd_width; + hints.base_height = chrome_height + csd_height; + + hints.width_inc = char_width; + hints.height_inc = char_height; + + /* min size is min size of the whole window, remember. */ + hints.min_width = hints.base_width + hints.width_inc * MIN_WIDTH_CHARS; + hints.min_height = hints.base_height + hints.height_inc * MIN_HEIGHT_CHARS; + + gtk_window_set_geometry_hints (GTK_WINDOW (window), + NULL, + &hints, + GDK_HINT_RESIZE_INC | + GDK_HINT_MIN_SIZE | + GDK_HINT_BASE_SIZE); + + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, + "[window %p] hints: base %dx%d min %dx%d inc %d %d\n", + window, + hints.base_width, + hints.base_height, + hints.min_width, + hints.min_height, + hints.width_inc, + hints.height_inc); + + priv->old_csd_width = csd_width; + priv->old_csd_height = csd_height; + priv->old_geometry_widget = widget; + } + else + { + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, + "[window %p] hints: increment unchanged, not setting\n", + window); + } + + /* We need these for the size calculation in terminal_window_update_size() + * (at least under GTK >= 3.19), so we set them unconditionally. */ + priv->old_char_width = char_width; + priv->old_char_height = char_height; + priv->old_chrome_width = chrome_width; + priv->old_chrome_height = chrome_height; + priv->old_padding_width = padding.left + padding.right; + priv->old_padding_height = padding.top + padding.bottom; +} + +static void +confirm_close_response_cb (GtkWidget *dialog, + int response, + TerminalWindow *window) +{ + TerminalScreen *screen; + + screen = g_object_get_data (G_OBJECT (dialog), "close-screen"); + + gtk_widget_destroy (dialog); + + if (response != GTK_RESPONSE_ACCEPT) + return; + + if (screen) + terminal_window_remove_screen (window, screen); + else + gtk_widget_destroy (GTK_WIDGET (window)); +} + +/* Returns: TRUE if closing needs to wait until user confirmation; + * FALSE if the terminal or window can close immediately. + */ +static gboolean +confirm_close_window_or_tab (TerminalWindow *window, + TerminalScreen *screen) +{ + TerminalWindowPrivate *priv = window->priv; + GtkWidget *dialog; + gboolean do_confirm; + int n_tabs; + + if (priv->confirm_close_dialog) + { + /* WTF, already have one? It's modal, so how did that happen? */ + gtk_dialog_response (GTK_DIALOG (priv->confirm_close_dialog), + GTK_RESPONSE_DELETE_EVENT); + } + + do_confirm = g_settings_get_boolean (terminal_app_get_global_settings (terminal_app_get ()), + TERMINAL_SETTING_CONFIRM_CLOSE_KEY); + if (!do_confirm) + return FALSE; + + if (screen) + { + do_confirm = terminal_screen_has_foreground_process (screen, NULL, NULL); + n_tabs = 1; + } + else + { + GList *tabs, *t; + + do_confirm = FALSE; + + tabs = terminal_window_list_screen_containers (window); + n_tabs = g_list_length (tabs); + + for (t = tabs; t != NULL; t = t->next) + { + TerminalScreen *terminal_screen; + + terminal_screen = terminal_screen_container_get_screen (TERMINAL_SCREEN_CONTAINER (t->data)); + if (terminal_screen_has_foreground_process (terminal_screen, NULL, NULL)) + { + do_confirm = TRUE; + break; + } + } + g_list_free (tabs); + } + + if (!do_confirm) + return FALSE; + + dialog = priv->confirm_close_dialog = + gtk_message_dialog_new (GTK_WINDOW (window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_CANCEL, + "%s", n_tabs > 1 ? _("Close this window?") : _("Close this terminal?")); + + if (n_tabs > 1) + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", _("There are still processes running in some terminals in this window. " + "Closing the window will kill all of them.")); + else + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", _("There is still a process running in this terminal. " + "Closing the terminal will kill it.")); + + gtk_window_set_title (GTK_WINDOW (dialog), ""); + + gtk_dialog_add_button (GTK_DIALOG (dialog), n_tabs > 1 ? _("C_lose Window") : _("C_lose Terminal"), GTK_RESPONSE_ACCEPT); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); + + g_object_set_data (G_OBJECT (dialog), "close-screen", screen); + + g_signal_connect (dialog, "destroy", + G_CALLBACK (gtk_widget_destroyed), &priv->confirm_close_dialog); + g_signal_connect (dialog, "response", + G_CALLBACK (confirm_close_response_cb), window); + + gtk_window_present (GTK_WINDOW (dialog)); + + return TRUE; +} + +void +terminal_window_request_close (TerminalWindow *window) +{ + g_return_if_fail (TERMINAL_IS_WINDOW (window)); + + if (confirm_close_window_or_tab (window, NULL)) + return; + + gtk_widget_destroy (GTK_WIDGET (window)); +} + +const char * +terminal_window_get_uuid (TerminalWindow *window) +{ + g_return_val_if_fail (TERMINAL_IS_WINDOW (window), NULL); + + return window->priv->uuid; +} |