diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 17:42:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 17:42:51 +0000 |
commit | ba429d344132c088177e853cce8ff7181570b221 (patch) | |
tree | 87ebf15269b4301737abd1735baabba71be93622 /gedit/gedit-window.c | |
parent | Initial commit. (diff) | |
download | gedit-ba429d344132c088177e853cce8ff7181570b221.tar.xz gedit-ba429d344132c088177e853cce8ff7181570b221.zip |
Adding upstream version 44.2.upstream/44.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gedit/gedit-window.c')
-rw-r--r-- | gedit/gedit-window.c | 3561 |
1 files changed, 3561 insertions, 0 deletions
diff --git a/gedit/gedit-window.c b/gedit/gedit-window.c new file mode 100644 index 0000000..f2f9118 --- /dev/null +++ b/gedit/gedit-window.c @@ -0,0 +1,3561 @@ +/* + * gedit-window.c + * This file is part of gedit + * + * Copyright (C) 2005 - Paolo Maggi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "gedit-window.h" + +#include <time.h> +#include <sys/types.h> +#include <string.h> + +#include <glib/gi18n.h> +#include <libpeas/peas-extension-set.h> +#include <tepl/tepl.h> + +#include "gedit-app.h" +#include "gedit-app-private.h" +#include "gedit-notebook.h" +#include "gedit-notebook-popup-menu.h" +#include "gedit-multi-notebook.h" +#include "gedit-statusbar.h" +#include "gedit-tab.h" +#include "gedit-tab-private.h" +#include "gedit-view-frame.h" +#include "gedit-utils.h" +#include "gedit-commands.h" +#include "gedit-commands-private.h" +#include "gedit-debug.h" +#include "gedit-document.h" +#include "gedit-document-private.h" +#include "gedit-documents-panel.h" +#include "gedit-plugins-engine.h" +#include "gedit-window-activatable.h" +#include "gedit-enum-types.h" +#include "gedit-dirs.h" +#include "gedit-status-menu-button.h" +#include "gedit-settings.h" +#include "gedit-menu-stack-switcher.h" + +struct _GeditWindowPrivate +{ + GSettings *editor_settings; + GSettings *ui_settings; + GSettings *window_settings; + + GeditMultiNotebook *multi_notebook; + + GtkWidget *side_panel; + GtkWidget *side_stack_switcher; + GtkWidget *side_panel_inline_stack_switcher; + GtkWidget *bottom_panel; + + GtkWidget *hpaned; + GtkWidget *vpaned; + + GeditMessageBus *message_bus; + PeasExtensionSet *extensions; + + /* Widgets for fullscreen mode */ + GtkWidget *fullscreen_eventbox; + GtkRevealer *fullscreen_revealer; + GtkWidget *fullscreen_headerbar; + GtkMenuButton *fullscreen_gear_button; + GtkMenuButton *fullscreen_open_recent_button; + + /* statusbar and context ids for statusbar messages */ + GtkWidget *statusbar; + TeplOverwriteIndicator *overwrite_indicator; + TeplLineColumnIndicator *line_column_indicator; + GtkWidget *tab_width_button; + GtkWidget *language_button; + GtkWidget *language_popover; + guint bracket_match_message_cid; + guint tab_width_id; + guint language_changed_id; + + /* Headerbars */ + GtkWidget *side_headerbar; + GtkWidget *headerbar; + + GtkMenuButton *gear_button; + + gint num_tabs_with_error; + + gint width; + gint height; + GdkWindowState window_state; + + gint side_panel_size; + gint bottom_panel_size; + + GeditWindowState state; + + guint inhibition_cookie; + + gint bottom_panel_item_removed_handler_id; + + GtkWindowGroup *window_group; + + gchar *file_chooser_folder_uri; + + gchar *direct_save_uri; + + GSList *closed_docs_stack; + + guint removing_tabs : 1; + guint dispose_has_run : 1; + + guint in_fullscreen_eventbox : 1; +}; + +enum +{ + PROP_0, + PROP_STATE, + LAST_PROP +}; + +static GParamSpec *properties[LAST_PROP]; + +enum +{ + TAB_ADDED, + TAB_REMOVED, + TABS_REORDERED, + ACTIVE_TAB_CHANGED, + ACTIVE_TAB_STATE_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +enum +{ + TARGET_URI_LIST = 100, + TARGET_XDNDDIRECTSAVE +}; + +static const GtkTargetEntry drop_types [] = { + { "XdndDirectSave0", 0, TARGET_XDNDDIRECTSAVE }, /* XDS Protocol Type */ + { "text/uri-list", 0, TARGET_URI_LIST} +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GeditWindow, gedit_window, GTK_TYPE_APPLICATION_WINDOW) + +/* Prototypes */ +static void remove_actions (GeditWindow *window); + +static void +gedit_window_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditWindow *window = GEDIT_WINDOW (object); + + switch (prop_id) + { + case PROP_STATE: + g_value_set_flags (value, + gedit_window_get_state (window)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +save_panels_state (GeditWindow *window) +{ + const gchar *panel_page; + + gedit_debug (DEBUG_WINDOW); + + if (window->priv->side_panel_size > 0) + { + g_settings_set_int (window->priv->window_settings, + GEDIT_SETTINGS_SIDE_PANEL_SIZE, + window->priv->side_panel_size); + } + + panel_page = gtk_stack_get_visible_child_name (GTK_STACK (window->priv->side_panel)); + if (panel_page != NULL) + { + g_settings_set_string (window->priv->window_settings, + GEDIT_SETTINGS_SIDE_PANEL_ACTIVE_PAGE, + panel_page); + } + + if (window->priv->bottom_panel_size > 0) + { + g_settings_set_int (window->priv->window_settings, + GEDIT_SETTINGS_BOTTOM_PANEL_SIZE, + window->priv->bottom_panel_size); + } + + panel_page = gtk_stack_get_visible_child_name (GTK_STACK (window->priv->bottom_panel)); + if (panel_page != NULL) + { + g_settings_set_string (window->priv->window_settings, + GEDIT_SETTINGS_BOTTOM_PANEL_ACTIVE_PAGE, + panel_page); + } + + g_settings_apply (window->priv->window_settings); +} + +static void +save_window_state (GtkWidget *widget) +{ + GeditWindow *window = GEDIT_WINDOW (widget); + + if ((window->priv->window_state & + (GDK_WINDOW_STATE_MAXIMIZED | GDK_WINDOW_STATE_FULLSCREEN)) == 0) + { + gtk_window_get_size (GTK_WINDOW (widget), &window->priv->width, &window->priv->height); + + g_settings_set (window->priv->window_settings, GEDIT_SETTINGS_WINDOW_SIZE, + "(ii)", window->priv->width, window->priv->height); + } +} + +static void +gedit_window_dispose (GObject *object) +{ + GeditWindow *window; + + gedit_debug (DEBUG_WINDOW); + + window = GEDIT_WINDOW (object); + + /* Stop tracking removal of panels otherwise we always + * end up with thinking we had no panel active, since they + * should all be removed below */ + if (window->priv->bottom_panel_item_removed_handler_id != 0) + { + g_signal_handler_disconnect (window->priv->bottom_panel, + window->priv->bottom_panel_item_removed_handler_id); + window->priv->bottom_panel_item_removed_handler_id = 0; + } + + /* First of all, force collection so that plugins + * really drop some of the references. + */ + peas_engine_garbage_collect (PEAS_ENGINE (gedit_plugins_engine_get_default ())); + + /* save the panels position and make sure to deactivate plugins + * for this window, but only once */ + if (!window->priv->dispose_has_run) + { + save_window_state (GTK_WIDGET (window)); + save_panels_state (window); + + /* Note that unreffing the extensions will automatically remove + all extensions which in turn will deactivate the extension */ + g_object_unref (window->priv->extensions); + + peas_engine_garbage_collect (PEAS_ENGINE (gedit_plugins_engine_get_default ())); + + window->priv->dispose_has_run = TRUE; + } + + g_clear_object (&window->priv->message_bus); + g_clear_object (&window->priv->window_group); + + /* We must free the settings after saving the panels */ + g_clear_object (&window->priv->editor_settings); + g_clear_object (&window->priv->ui_settings); + g_clear_object (&window->priv->window_settings); + + /* Now that there have broken some reference loops, + * force collection again. + */ + peas_engine_garbage_collect (PEAS_ENGINE (gedit_plugins_engine_get_default ())); + + g_clear_object (&window->priv->side_stack_switcher); + + /* GTK+/GIO unref the action map in an idle. For the last GeditWindow, + * the application quits before the idle, so the action map is not + * unreffed, and some objects are not finalized on application shutdown + * (GeditView for example). + * So this is just for making the debugging of object references a bit + * nicer. + */ + remove_actions (window); + + window->priv->fullscreen_open_recent_button = NULL; + + G_OBJECT_CLASS (gedit_window_parent_class)->dispose (object); +} + +static void +gedit_window_finalize (GObject *object) +{ + GeditWindow *window = GEDIT_WINDOW (object); + + g_free (window->priv->file_chooser_folder_uri); + g_slist_free_full (window->priv->closed_docs_stack, (GDestroyNotify)g_object_unref); + + G_OBJECT_CLASS (gedit_window_parent_class)->finalize (object); +} + +static void +update_fullscreen (GeditWindow *window, + gboolean is_fullscreen) +{ + GAction *fullscreen_action; + + _gedit_multi_notebook_set_show_tabs (window->priv->multi_notebook, !is_fullscreen); + + if (is_fullscreen) + { + gtk_widget_hide (window->priv->statusbar); + } + else + { + if (g_settings_get_boolean (window->priv->ui_settings, "statusbar-visible")) + { + gtk_widget_show (window->priv->statusbar); + } + } + +#ifndef OS_OSX + if (is_fullscreen) + { + gtk_widget_show_all (window->priv->fullscreen_eventbox); + } + else + { + gtk_widget_hide (window->priv->fullscreen_eventbox); + } +#endif + + fullscreen_action = g_action_map_lookup_action (G_ACTION_MAP (window), + "fullscreen"); + + g_simple_action_set_state (G_SIMPLE_ACTION (fullscreen_action), + g_variant_new_boolean (is_fullscreen)); +} + +static gboolean +gedit_window_window_state_event (GtkWidget *widget, + GdkEventWindowState *event) +{ + GeditWindow *window = GEDIT_WINDOW (widget); + + window->priv->window_state = event->new_window_state; + + g_settings_set_int (window->priv->window_settings, GEDIT_SETTINGS_WINDOW_STATE, + window->priv->window_state); + + if ((event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) != 0) + { + update_fullscreen (window, (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) != 0); + } + + return GTK_WIDGET_CLASS (gedit_window_parent_class)->window_state_event (widget, event); +} + +static gboolean +gedit_window_configure_event (GtkWidget *widget, + GdkEventConfigure *event) +{ + GeditWindow *window = GEDIT_WINDOW (widget); + + if (gtk_widget_get_realized (widget) && + (window->priv->window_state & + (GDK_WINDOW_STATE_MAXIMIZED | GDK_WINDOW_STATE_FULLSCREEN)) == 0) + { + save_window_state (widget); + } + + return GTK_WIDGET_CLASS (gedit_window_parent_class)->configure_event (widget, event); +} + +/* + * GtkWindow catches keybindings for the menu items _before_ passing them to + * the focused widget. This is unfortunate and means that pressing ctrl+V + * in an entry on a panel ends up pasting text in the TextView. + * Here we override GtkWindow's handler to do the same things that it + * does, but in the opposite order and then we chain up to the grand + * parent handler, skipping gtk_window_key_press_event. + */ +static gboolean +gedit_window_key_press_event (GtkWidget *widget, + GdkEventKey *event) +{ + static gpointer grand_parent_class = NULL; + + GtkWindow *window = GTK_WINDOW (widget); + gboolean handled = FALSE; + + if (grand_parent_class == NULL) + { + grand_parent_class = g_type_class_peek_parent (gedit_window_parent_class); + } + + /* handle focus widget key events */ + if (!handled) + { + handled = gtk_window_propagate_key_event (window, event); + } + + /* handle mnemonics and accelerators */ + if (!handled) + { + handled = gtk_window_activate_key (window, event); + } + + /* Chain up, invokes binding set on window */ + if (!handled) + { + handled = GTK_WIDGET_CLASS (grand_parent_class)->key_press_event (widget, event); + } + + if (!handled) + { + return gedit_app_process_window_event (GEDIT_APP (g_application_get_default ()), + GEDIT_WINDOW (widget), + (GdkEvent *)event); + } + + return TRUE; +} + +static void +gedit_window_tab_removed (GeditWindow *window, + GeditTab *tab) +{ + peas_engine_garbage_collect (PEAS_ENGINE (gedit_plugins_engine_get_default ())); +} + +static void +gedit_window_class_init (GeditWindowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + klass->tab_removed = gedit_window_tab_removed; + + object_class->dispose = gedit_window_dispose; + object_class->finalize = gedit_window_finalize; + object_class->get_property = gedit_window_get_property; + + widget_class->window_state_event = gedit_window_window_state_event; + widget_class->configure_event = gedit_window_configure_event; + widget_class->key_press_event = gedit_window_key_press_event; + + properties[PROP_STATE] = + g_param_spec_flags ("state", + "State", + "The window's state", + GEDIT_TYPE_WINDOW_STATE, + GEDIT_WINDOW_STATE_NORMAL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, LAST_PROP, properties); + + signals[TAB_ADDED] = + g_signal_new ("tab-added", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GeditWindowClass, tab_added), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, + GEDIT_TYPE_TAB); + signals[TAB_REMOVED] = + g_signal_new ("tab-removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GeditWindowClass, tab_removed), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, + GEDIT_TYPE_TAB); + signals[TABS_REORDERED] = + g_signal_new ("tabs-reordered", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GeditWindowClass, tabs_reordered), + NULL, NULL, NULL, + G_TYPE_NONE, + 0); + signals[ACTIVE_TAB_CHANGED] = + g_signal_new ("active-tab-changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GeditWindowClass, active_tab_changed), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, + GEDIT_TYPE_TAB); + signals[ACTIVE_TAB_STATE_CHANGED] = + g_signal_new ("active-tab-state-changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GeditWindowClass, active_tab_state_changed), + NULL, NULL, NULL, + G_TYPE_NONE, + 0); + + /* Bind class to template */ + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/gedit/ui/gedit-window.ui"); + gtk_widget_class_bind_template_child_private (widget_class, GeditWindow, side_headerbar); + gtk_widget_class_bind_template_child_private (widget_class, GeditWindow, headerbar); + gtk_widget_class_bind_template_child_private (widget_class, GeditWindow, gear_button); + gtk_widget_class_bind_template_child_private (widget_class, GeditWindow, hpaned); + gtk_widget_class_bind_template_child_private (widget_class, GeditWindow, side_panel); + gtk_widget_class_bind_template_child_private (widget_class, GeditWindow, side_panel_inline_stack_switcher); + gtk_widget_class_bind_template_child_private (widget_class, GeditWindow, vpaned); + gtk_widget_class_bind_template_child_private (widget_class, GeditWindow, multi_notebook); + gtk_widget_class_bind_template_child_private (widget_class, GeditWindow, bottom_panel); + gtk_widget_class_bind_template_child_private (widget_class, GeditWindow, statusbar); + gtk_widget_class_bind_template_child_private (widget_class, GeditWindow, language_button); + gtk_widget_class_bind_template_child_private (widget_class, GeditWindow, tab_width_button); + gtk_widget_class_bind_template_child_private (widget_class, GeditWindow, fullscreen_eventbox); + gtk_widget_class_bind_template_child_private (widget_class, GeditWindow, fullscreen_revealer); + gtk_widget_class_bind_template_child_private (widget_class, GeditWindow, fullscreen_headerbar); + gtk_widget_class_bind_template_child_private (widget_class, GeditWindow, fullscreen_gear_button); +} + +static void +received_clipboard_contents (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + GeditWindow *window) +{ + GeditTab *tab; + gboolean enabled; + GAction *action; + + /* getting clipboard contents is async, so we need to + * get the current tab and its state */ + + tab = gedit_window_get_active_tab (window); + + if (tab != NULL) + { + GeditTabState state; + gboolean state_normal; + + state = gedit_tab_get_state (tab); + state_normal = (state == GEDIT_TAB_STATE_NORMAL); + + enabled = state_normal && + gtk_selection_data_targets_include_text (selection_data); + } + else + { + enabled = FALSE; + } + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "paste"); + + /* Since this is emitted async, the disposal of the actions may have + * already happened. Ensure that we have an action before setting the + * state. + */ + if (action != NULL) + { + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled); + } + + g_object_unref (window); +} + +static void +set_paste_sensitivity_according_to_clipboard (GeditWindow *window, + GtkClipboard *clipboard) +{ + GdkDisplay *display; + + display = gtk_clipboard_get_display (clipboard); + + if (gdk_display_supports_selection_notification (display)) + { + gtk_clipboard_request_contents (clipboard, + gdk_atom_intern_static_string ("TARGETS"), + (GtkClipboardReceivedFunc) received_clipboard_contents, + g_object_ref (window)); + } + else + { + GAction *action; + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "paste"); + /* XFIXES extension not availbale, make + * Paste always sensitive */ + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + } +} + +static void +extension_update_state (PeasExtensionSet *extensions, + PeasPluginInfo *info, + PeasExtension *exten, + GeditWindow *window) +{ + gedit_window_activatable_update_state (GEDIT_WINDOW_ACTIVATABLE (exten)); +} + +static void +update_actions_sensitivity (GeditWindow *window) +{ + GeditNotebook *notebook; + GeditTab *tab; + gint num_notebooks; + gint num_tabs; + GeditTabState state = GEDIT_TAB_STATE_NORMAL; + GeditDocument *doc = NULL; + GtkSourceFile *file = NULL; + GeditView *view = NULL; + gint tab_number = -1; + GAction *action; + gboolean editable = FALSE; + gboolean empty_search = FALSE; + GtkClipboard *clipboard; + gboolean enable_syntax_highlighting; + + gedit_debug (DEBUG_WINDOW); + + notebook = gedit_multi_notebook_get_active_notebook (window->priv->multi_notebook); + tab = gedit_multi_notebook_get_active_tab (window->priv->multi_notebook); + num_notebooks = gedit_multi_notebook_get_n_notebooks (window->priv->multi_notebook); + num_tabs = gedit_multi_notebook_get_n_tabs (window->priv->multi_notebook); + + if (notebook != NULL && tab != NULL) + { + state = gedit_tab_get_state (tab); + view = gedit_tab_get_view (tab); + doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); + file = gedit_document_get_file (doc); + tab_number = gtk_notebook_page_num (GTK_NOTEBOOK (notebook), GTK_WIDGET (tab)); + editable = gtk_text_view_get_editable (GTK_TEXT_VIEW (view)); + empty_search = _gedit_document_get_empty_search (doc); + } + + clipboard = gtk_widget_get_clipboard (GTK_WIDGET (window), GDK_SELECTION_CLIPBOARD); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "save"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + ((state == GEDIT_TAB_STATE_NORMAL) || + (state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)) && + (file != NULL) && !gtk_source_file_is_readonly (file)); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "save-as"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + ((state == GEDIT_TAB_STATE_NORMAL) || + (state == GEDIT_TAB_STATE_SAVING_ERROR) || + (state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)) && + (doc != NULL)); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "revert"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + ((state == GEDIT_TAB_STATE_NORMAL) || + (state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)) && + (doc != NULL) && !_gedit_document_is_untitled (doc)); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "reopen-closed-tab"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), (window->priv->closed_docs_stack != NULL)); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "print"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + ((state == GEDIT_TAB_STATE_NORMAL) || + (state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW)) && + (doc != NULL)); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "close"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + (state != GEDIT_TAB_STATE_CLOSING) && + (state != GEDIT_TAB_STATE_SAVING) && + (state != GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW) && + (state != GEDIT_TAB_STATE_PRINTING) && + (state != GEDIT_TAB_STATE_SAVING_ERROR)); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "undo"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + (state == GEDIT_TAB_STATE_NORMAL) && + (doc != NULL) && gtk_source_buffer_can_undo (GTK_SOURCE_BUFFER (doc))); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "redo"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + (state == GEDIT_TAB_STATE_NORMAL) && + (doc != NULL) && gtk_source_buffer_can_redo (GTK_SOURCE_BUFFER (doc))); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "cut"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + (state == GEDIT_TAB_STATE_NORMAL) && + editable && + (doc != NULL) && gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (doc))); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "copy"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + ((state == GEDIT_TAB_STATE_NORMAL) || + (state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)) && + (doc != NULL) && gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (doc))); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "paste"); + if (num_tabs > 0 && (state == GEDIT_TAB_STATE_NORMAL) && editable) + { + set_paste_sensitivity_according_to_clipboard (window, clipboard); + } + else + { + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + } + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "delete"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + (state == GEDIT_TAB_STATE_NORMAL) && + editable && + (doc != NULL) && gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (doc))); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "overwrite-mode"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), doc != NULL); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "find"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + ((state == GEDIT_TAB_STATE_NORMAL) || + (state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)) && + (doc != NULL)); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "replace"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + (state == GEDIT_TAB_STATE_NORMAL) && + (doc != NULL) && editable); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "find-next"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + ((state == GEDIT_TAB_STATE_NORMAL) || + (state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)) && + (doc != NULL) && !empty_search); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "find-prev"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + ((state == GEDIT_TAB_STATE_NORMAL) || + (state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)) && + (doc != NULL) && !empty_search); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "clear-highlight"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + ((state == GEDIT_TAB_STATE_NORMAL) || + (state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)) && + (doc != NULL) && !empty_search); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "goto-line"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + ((state == GEDIT_TAB_STATE_NORMAL) || + (state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)) && + (doc != NULL)); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "highlight-mode"); + enable_syntax_highlighting = g_settings_get_boolean (window->priv->editor_settings, + GEDIT_SETTINGS_SYNTAX_HIGHLIGHTING); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + (state != GEDIT_TAB_STATE_CLOSING) && + (doc != NULL) && enable_syntax_highlighting); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "move-to-new-window"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + num_tabs > 1); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), + "previous-document"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + tab_number > 0); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), + "next-document"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + tab_number >= 0 && + tab_number < gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)) - 1); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "new-tab-group"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + num_tabs > 0); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "previous-tab-group"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + num_notebooks > 1); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "next-tab-group"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + num_notebooks > 1); + + /* We disable File->Quit/SaveAll/CloseAll while printing to avoid to have two + operations (save and print/print preview) that uses the message area at + the same time (may be we can remove this limitation in the future) */ + /* We disable File->Quit/CloseAll if state is saving since saving cannot be + cancelled (may be we can remove this limitation in the future) */ + action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()), + "quit"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + !(window->priv->state & GEDIT_WINDOW_STATE_SAVING) && + !(window->priv->state & GEDIT_WINDOW_STATE_PRINTING)); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "save-all"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + !(window->priv->state & GEDIT_WINDOW_STATE_PRINTING) && + num_tabs > 0); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "close-all"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + num_tabs > 0 && + !(window->priv->state & GEDIT_WINDOW_STATE_SAVING) && + !(window->priv->state & GEDIT_WINDOW_STATE_PRINTING) && + num_tabs > 0); + + peas_extension_set_foreach (window->priv->extensions, + (PeasExtensionSetForeachFunc) extension_update_state, + window); +} + +static void +language_chooser_show_cb (TeplLanguageChooser *language_chooser, + GeditWindow *window) +{ + GeditDocument *active_document; + + active_document = gedit_window_get_active_document (window); + if (active_document != NULL) + { + GtkSourceLanguage *language; + + language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (active_document)); + tepl_language_chooser_select_language (language_chooser, language); + } +} + +static void +language_activated_cb (TeplLanguageChooser *language_chooser, + GtkSourceLanguage *language, + GeditWindow *window) +{ + GeditDocument *active_document; + + active_document = gedit_window_get_active_document (window); + if (active_document != NULL) + { + gedit_document_set_language (active_document, language); + } + + gtk_widget_hide (window->priv->language_popover); +} + +static void +setup_statusbar (GeditWindow *window) +{ + TeplLanguageChooserWidget *language_chooser; + + gedit_debug (DEBUG_WINDOW); + + window->priv->bracket_match_message_cid = gtk_statusbar_get_context_id + (GTK_STATUSBAR (window->priv->statusbar), "bracket_match_message"); + + g_settings_bind (window->priv->ui_settings, + "statusbar-visible", + window->priv->statusbar, + "visible", + G_SETTINGS_BIND_GET); + + /* Insert/Overwrite indicator */ + window->priv->overwrite_indicator = tepl_overwrite_indicator_new (); + gtk_widget_show (GTK_WIDGET (window->priv->overwrite_indicator)); + gtk_box_pack_end (GTK_BOX (window->priv->statusbar), + GTK_WIDGET (window->priv->overwrite_indicator), + FALSE, FALSE, 0); + // Explicit positioning. + gtk_box_reorder_child (GTK_BOX (window->priv->statusbar), + GTK_WIDGET (window->priv->overwrite_indicator), + 0); + + /* Line/Column indicator */ + window->priv->line_column_indicator = tepl_line_column_indicator_new (); + gtk_widget_show (GTK_WIDGET (window->priv->line_column_indicator)); + gtk_box_pack_end (GTK_BOX (window->priv->statusbar), + GTK_WIDGET (window->priv->line_column_indicator), + FALSE, FALSE, 0); + // Explicit positioning. + gtk_box_reorder_child (GTK_BOX (window->priv->statusbar), + GTK_WIDGET (window->priv->line_column_indicator), + 1); + + /* Tab Width button */ + gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (window->priv->tab_width_button), + _gedit_app_get_tab_width_menu (GEDIT_APP (g_application_get_default ()))); + + /* Language button */ + window->priv->language_popover = gtk_popover_new (window->priv->language_button); + gtk_menu_button_set_popover (GTK_MENU_BUTTON (window->priv->language_button), + window->priv->language_popover); + + language_chooser = tepl_language_chooser_widget_new (); + + g_signal_connect (language_chooser, + "show", + G_CALLBACK (language_chooser_show_cb), + window); + + g_signal_connect (language_chooser, + "language-activated", + G_CALLBACK (language_activated_cb), + window); + + gtk_container_add (GTK_CONTAINER (window->priv->language_popover), GTK_WIDGET (language_chooser)); + gtk_widget_show (GTK_WIDGET (language_chooser)); +} + +static GeditWindow * +clone_window (GeditWindow *origin) +{ + GeditWindow *window; + GdkScreen *screen; + GeditApp *app; + const gchar *panel_page; + + gedit_debug (DEBUG_WINDOW); + + app = GEDIT_APP (g_application_get_default ()); + + screen = gtk_window_get_screen (GTK_WINDOW (origin)); + window = gedit_app_create_window (app, screen); + + gtk_window_set_default_size (GTK_WINDOW (window), + origin->priv->width, + origin->priv->height); + + if ((origin->priv->window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0) + gtk_window_maximize (GTK_WINDOW (window)); + else + gtk_window_unmaximize (GTK_WINDOW (window)); + + if ((origin->priv->window_state & GDK_WINDOW_STATE_STICKY) != 0) + gtk_window_stick (GTK_WINDOW (window)); + else + gtk_window_unstick (GTK_WINDOW (window)); + + /* set the panels size, the paned position will be set when + * they are mapped */ + window->priv->side_panel_size = origin->priv->side_panel_size; + window->priv->bottom_panel_size = origin->priv->bottom_panel_size; + + panel_page = gtk_stack_get_visible_child_name (GTK_STACK (origin->priv->side_panel)); + + if (panel_page) + { + gtk_stack_set_visible_child_name (GTK_STACK (window->priv->side_panel), panel_page); + } + + panel_page = gtk_stack_get_visible_child_name (GTK_STACK (origin->priv->bottom_panel)); + + if (panel_page) + { + gtk_stack_set_visible_child_name (GTK_STACK (window->priv->bottom_panel), panel_page); + } + + gtk_widget_set_visible (window->priv->side_panel, + gtk_widget_get_visible (origin->priv->side_panel)); + gtk_widget_set_visible (window->priv->bottom_panel, + gtk_widget_get_visible (origin->priv->bottom_panel)); + + return window; +} + +static void +bracket_matched_cb (GtkSourceBuffer *buffer, + GtkTextIter *iter, + GtkSourceBracketMatchType result, + GeditWindow *window) +{ + if (buffer != GTK_SOURCE_BUFFER (gedit_window_get_active_document (window))) + return; + + switch (result) + { + case GTK_SOURCE_BRACKET_MATCH_NONE: + gtk_statusbar_pop (GTK_STATUSBAR (window->priv->statusbar), + window->priv->bracket_match_message_cid); + break; + case GTK_SOURCE_BRACKET_MATCH_OUT_OF_RANGE: + gedit_statusbar_flash_message (GEDIT_STATUSBAR (window->priv->statusbar), + window->priv->bracket_match_message_cid, + _("Bracket match is out of range")); + break; + case GTK_SOURCE_BRACKET_MATCH_NOT_FOUND: + gedit_statusbar_flash_message (GEDIT_STATUSBAR (window->priv->statusbar), + window->priv->bracket_match_message_cid, + _("Bracket match not found")); + break; + case GTK_SOURCE_BRACKET_MATCH_FOUND: + gedit_statusbar_flash_message (GEDIT_STATUSBAR (window->priv->statusbar), + window->priv->bracket_match_message_cid, + _("Bracket match found on line: %d"), + gtk_text_iter_get_line (iter) + 1); + break; + default: + g_assert_not_reached (); + } +} + +static void +set_overwrite_mode (GeditWindow *window, + gboolean overwrite) +{ + GAction *action; + + tepl_overwrite_indicator_set_overwrite (window->priv->overwrite_indicator, overwrite); + gtk_widget_show (GTK_WIDGET (window->priv->overwrite_indicator)); + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "overwrite-mode"); + g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (overwrite)); +} + +static void +overwrite_mode_changed (GtkTextView *view, + GParamSpec *pspec, + GeditWindow *window) +{ + if (view != GTK_TEXT_VIEW (gedit_window_get_active_view (window))) + return; + + set_overwrite_mode (window, gtk_text_view_get_overwrite (view)); +} + +#define MAX_TITLE_LENGTH 100 + +static void +set_title (GeditWindow *window) +{ + GeditTab *tab; + GeditDocument *doc = NULL; + GtkSourceFile *file; + gchar *name; + gchar *dirname = NULL; + gchar *main_title = NULL; + gchar *title = NULL; + gchar *subtitle = NULL; + gint len; + + tab = gedit_window_get_active_tab (window); + + if (tab == NULL) + { + gedit_app_set_window_title (GEDIT_APP (g_application_get_default ()), + window, + "gedit"); + gtk_header_bar_set_title (GTK_HEADER_BAR (window->priv->headerbar), + "gedit"); + gtk_header_bar_set_subtitle (GTK_HEADER_BAR (window->priv->headerbar), + NULL); + gtk_header_bar_set_title (GTK_HEADER_BAR (window->priv->fullscreen_headerbar), + "gedit"); + gtk_header_bar_set_subtitle (GTK_HEADER_BAR (window->priv->fullscreen_headerbar), + NULL); + return; + } + + doc = gedit_tab_get_document (tab); + g_return_if_fail (doc != NULL); + + file = gedit_document_get_file (doc); + + name = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc))); + len = g_utf8_strlen (name, -1); + + /* if the name is awfully long, truncate it and be done with it, + * otherwise also show the directory (ellipsized if needed) + */ + if (len > MAX_TITLE_LENGTH) + { + gchar *tmp; + + tmp = tepl_utils_str_middle_truncate (name, + MAX_TITLE_LENGTH); + g_free (name); + name = tmp; + } + else + { + GFile *location = gtk_source_file_get_location (file); + + if (location != NULL) + { + gchar *str = gedit_utils_location_get_dirname_for_display (location); + + /* use the remaining space for the dir, but use a min of 20 chars + * so that we do not end up with a dirname like "(a...b)". + * This means that in the worst case when the filename is long 99 + * we have a title long 99 + 20, but I think it's a rare enough + * case to be acceptable. It's justa darn title afterall :) + */ + dirname = tepl_utils_str_middle_truncate (str, + MAX (20, MAX_TITLE_LENGTH - len)); + g_free (str); + } + } + + if (gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc))) + { + gchar *tmp_name; + + tmp_name = g_strdup_printf ("*%s", name); + g_free (name); + + name = tmp_name; + } + + if (gtk_source_file_is_readonly (file)) + { + title = g_strdup_printf ("%s [%s]", + name, _("Read-Only")); + + if (dirname != NULL) + { + main_title = g_strdup_printf ("%s [%s] (%s) - gedit", + name, + _("Read-Only"), + dirname); + subtitle = dirname; + } + else + { + main_title = g_strdup_printf ("%s [%s] - gedit", + name, + _("Read-Only")); + } + } + else + { + title = g_strdup (name); + + if (dirname != NULL) + { + main_title = g_strdup_printf ("%s (%s) - gedit", + name, + dirname); + subtitle = dirname; + } + else + { + main_title = g_strdup_printf ("%s - gedit", + name); + } + } + + gedit_app_set_window_title (GEDIT_APP (g_application_get_default ()), + window, + main_title); + + gtk_header_bar_set_title (GTK_HEADER_BAR (window->priv->headerbar), + title); + gtk_header_bar_set_subtitle (GTK_HEADER_BAR (window->priv->headerbar), + subtitle); + gtk_header_bar_set_title (GTK_HEADER_BAR (window->priv->fullscreen_headerbar), + title); + gtk_header_bar_set_subtitle (GTK_HEADER_BAR (window->priv->fullscreen_headerbar), + subtitle); + + g_free (dirname); + g_free (name); + g_free (title); + g_free (main_title); +} + +#undef MAX_TITLE_LENGTH + +static void +tab_width_changed (GObject *object, + GParamSpec *pspec, + GeditWindow *window) +{ + guint new_tab_width; + gchar *label; + + new_tab_width = gtk_source_view_get_tab_width (GTK_SOURCE_VIEW (object)); + + label = g_strdup_printf (_("Tab Width: %u"), new_tab_width); + gedit_status_menu_button_set_label (GEDIT_STATUS_MENU_BUTTON (window->priv->tab_width_button), label); + g_free (label); +} + +static void +language_changed (GObject *object, + GParamSpec *pspec, + GeditWindow *window) +{ + GtkSourceLanguage *new_language; + const gchar *label; + + new_language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (object)); + + if (new_language) + label = gtk_source_language_get_name (new_language); + else + label = _("Plain Text"); + + gedit_status_menu_button_set_label (GEDIT_STATUS_MENU_BUTTON (window->priv->language_button), label); + + peas_extension_set_foreach (window->priv->extensions, + (PeasExtensionSetForeachFunc) extension_update_state, + window); +} + +static void +remove_actions (GeditWindow *window) +{ + g_action_map_remove_action (G_ACTION_MAP (window), "tab-width"); + g_action_map_remove_action (G_ACTION_MAP (window), "use-spaces"); +} + +static void +sync_current_tab_actions (GeditWindow *window, + GeditView *old_view, + GeditView *new_view) +{ + if (old_view != NULL) + { + remove_actions (window); + } + + if (new_view != NULL) + { + GPropertyAction *action; + + action = g_property_action_new ("tab-width", new_view, "tab-width"); + g_action_map_add_action (G_ACTION_MAP (window), G_ACTION (action)); + g_object_unref (action); + + action = g_property_action_new ("use-spaces", new_view, "insert-spaces-instead-of-tabs"); + g_action_map_add_action (G_ACTION_MAP (window), G_ACTION (action)); + g_object_unref (action); + } +} + +static void +update_statusbar (GeditWindow *window, + GeditView *old_view, + GeditView *new_view) +{ + if (old_view) + { + if (window->priv->tab_width_id) + { + g_signal_handler_disconnect (old_view, + window->priv->tab_width_id); + + window->priv->tab_width_id = 0; + } + + if (window->priv->language_changed_id) + { + g_signal_handler_disconnect (gtk_text_view_get_buffer (GTK_TEXT_VIEW (old_view)), + window->priv->language_changed_id); + + window->priv->language_changed_id = 0; + } + } + + if (new_view) + { + GeditDocument *doc; + + doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (new_view))); + + set_overwrite_mode (window, gtk_text_view_get_overwrite (GTK_TEXT_VIEW (new_view))); + + tepl_line_column_indicator_set_view (window->priv->line_column_indicator, + TEPL_VIEW (new_view)); + gtk_widget_show (GTK_WIDGET (window->priv->line_column_indicator)); + + gtk_widget_show (window->priv->tab_width_button); + gtk_widget_show (window->priv->language_button); + + window->priv->tab_width_id = g_signal_connect (new_view, + "notify::tab-width", + G_CALLBACK (tab_width_changed), + window); + + window->priv->language_changed_id = g_signal_connect (doc, + "notify::language", + G_CALLBACK (language_changed), + window); + + /* call it for the first time */ + tab_width_changed (G_OBJECT (new_view), NULL, window); + language_changed (G_OBJECT (doc), NULL, window); + } +} + +static void +tab_switched (GeditMultiNotebook *mnb, + GeditNotebook *old_notebook, + GeditTab *old_tab, + GeditNotebook *new_notebook, + GeditTab *new_tab, + GeditWindow *window) +{ + GeditView *old_view, *new_view; + + old_view = old_tab ? gedit_tab_get_view (old_tab) : NULL; + new_view = new_tab ? gedit_tab_get_view (new_tab) : NULL; + + sync_current_tab_actions (window, old_view, new_view); + update_statusbar (window, old_view, new_view); + + if (new_tab == NULL || window->priv->dispose_has_run) + return; + + set_title (window); + update_actions_sensitivity (window); + + g_signal_emit (G_OBJECT (window), + signals[ACTIVE_TAB_CHANGED], + 0, + new_tab); +} + +static void +analyze_tab_state (GeditTab *tab, + GeditWindow *window) +{ + GeditTabState ts; + + ts = gedit_tab_get_state (tab); + + switch (ts) + { + case GEDIT_TAB_STATE_LOADING: + case GEDIT_TAB_STATE_REVERTING: + window->priv->state |= GEDIT_WINDOW_STATE_LOADING; + break; + + case GEDIT_TAB_STATE_SAVING: + window->priv->state |= GEDIT_WINDOW_STATE_SAVING; + break; + + case GEDIT_TAB_STATE_PRINTING: + window->priv->state |= GEDIT_WINDOW_STATE_PRINTING; + break; + + case GEDIT_TAB_STATE_LOADING_ERROR: + case GEDIT_TAB_STATE_REVERTING_ERROR: + case GEDIT_TAB_STATE_SAVING_ERROR: + case GEDIT_TAB_STATE_GENERIC_ERROR: + window->priv->state |= GEDIT_WINDOW_STATE_ERROR; + ++window->priv->num_tabs_with_error; + default: + /* NOP */ + break; + } +} + +static void +update_window_state (GeditWindow *window) +{ + GeditWindowState old_ws; + gint old_num_of_errors; + + gedit_debug_message (DEBUG_WINDOW, "Old state: %x", window->priv->state); + + old_ws = window->priv->state; + old_num_of_errors = window->priv->num_tabs_with_error; + + window->priv->state = 0; + window->priv->num_tabs_with_error = 0; + + gedit_multi_notebook_foreach_tab (window->priv->multi_notebook, + (GtkCallback)analyze_tab_state, + window); + + gedit_debug_message (DEBUG_WINDOW, "New state: %x", window->priv->state); + + if (old_ws != window->priv->state) + { + update_actions_sensitivity (window); + + gedit_statusbar_set_window_state (GEDIT_STATUSBAR (window->priv->statusbar), + window->priv->state, + window->priv->num_tabs_with_error); + + g_object_notify_by_pspec (G_OBJECT (window), properties[PROP_STATE]); + } + else if (old_num_of_errors != window->priv->num_tabs_with_error) + { + gedit_statusbar_set_window_state (GEDIT_STATUSBAR (window->priv->statusbar), + window->priv->state, + window->priv->num_tabs_with_error); + } +} + +static void +update_can_close (GeditWindow *window) +{ + GeditWindowPrivate *priv = window->priv; + GList *tabs; + GList *l; + gboolean can_close = TRUE; + + gedit_debug (DEBUG_WINDOW); + + tabs = gedit_multi_notebook_get_all_tabs (priv->multi_notebook); + + for (l = tabs; l != NULL; l = g_list_next (l)) + { + GeditTab *tab = l->data; + + if (!_gedit_tab_get_can_close (tab)) + { + can_close = FALSE; + break; + } + } + + if (can_close && (priv->inhibition_cookie != 0)) + { + gtk_application_uninhibit (GTK_APPLICATION (g_application_get_default ()), + priv->inhibition_cookie); + priv->inhibition_cookie = 0; + } + else if (!can_close && (priv->inhibition_cookie == 0)) + { + priv->inhibition_cookie = gtk_application_inhibit (GTK_APPLICATION (g_application_get_default ()), + GTK_WINDOW (window), + GTK_APPLICATION_INHIBIT_LOGOUT, + _("There are unsaved documents")); + } + + g_list_free (tabs); +} + +static void +sync_state (GeditTab *tab, + GParamSpec *pspec, + GeditWindow *window) +{ + gedit_debug (DEBUG_WINDOW); + + update_window_state (window); + + if (tab == gedit_window_get_active_tab (window)) + { + update_actions_sensitivity (window); + + g_signal_emit (G_OBJECT (window), signals[ACTIVE_TAB_STATE_CHANGED], 0); + } +} + +static void +sync_name (GeditTab *tab, + GParamSpec *pspec, + GeditWindow *window) +{ + if (tab == gedit_window_get_active_tab (window)) + { + set_title (window); + update_actions_sensitivity (window); + } +} + +static void +sync_can_close (GeditTab *tab, + GParamSpec *pspec, + GeditWindow *window) +{ + update_can_close (window); +} + +static GeditWindow * +get_drop_window (GtkWidget *widget) +{ + GtkWidget *target_window; + + target_window = gtk_widget_get_toplevel (widget); + g_return_val_if_fail (GEDIT_IS_WINDOW (target_window), NULL); + + return GEDIT_WINDOW (target_window); +} + +static void +load_uris_from_drop (GeditWindow *window, + gchar **uri_list) +{ + GSList *locations = NULL; + gint i; + GSList *loaded; + + if (uri_list == NULL) + return; + + for (i = 0; uri_list[i] != NULL; ++i) + { + locations = g_slist_prepend (locations, g_file_new_for_uri (uri_list[i])); + } + + locations = g_slist_reverse (locations); + loaded = gedit_commands_load_locations (window, + locations, + NULL, + 0, + 0); + + g_slist_free (loaded); + g_slist_free_full (locations, g_object_unref); +} + +/* Handle drops on the GeditWindow */ +static void +drag_data_received_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint timestamp, + gpointer data) +{ + GeditWindow *window; + gchar **uri_list; + + window = get_drop_window (widget); + + if (window == NULL) + return; + + switch (info) + { + case TARGET_URI_LIST: + uri_list = gedit_utils_drop_get_uris(selection_data); + load_uris_from_drop (window, uri_list); + g_strfreev (uri_list); + + gtk_drag_finish (context, TRUE, FALSE, timestamp); + + break; + + case TARGET_XDNDDIRECTSAVE: + /* Indicate that we don't provide "F" fallback */ + if (gtk_selection_data_get_format (selection_data) == 8 && + gtk_selection_data_get_length (selection_data) == 1 && + gtk_selection_data_get_data (selection_data)[0] == 'F') + { + gdk_property_change (gdk_drag_context_get_source_window (context), + gdk_atom_intern ("XdndDirectSave0", FALSE), + gdk_atom_intern ("text/plain", FALSE), 8, + GDK_PROP_MODE_REPLACE, (const guchar *) "", 0); + } + else if (gtk_selection_data_get_format (selection_data) == 8 && + gtk_selection_data_get_length (selection_data) == 1 && + gtk_selection_data_get_data (selection_data)[0] == 'S' && + window->priv->direct_save_uri != NULL) + { + gchar **uris; + + uris = g_new (gchar *, 2); + uris[0] = window->priv->direct_save_uri; + uris[1] = NULL; + + load_uris_from_drop (window, uris); + g_free (uris); + } + + g_free (window->priv->direct_save_uri); + window->priv->direct_save_uri = NULL; + + gtk_drag_finish (context, TRUE, FALSE, timestamp); + + break; + } +} + +static void +drag_drop_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + gpointer user_data) +{ + GeditWindow *window; + GtkTargetList *target_list; + GdkAtom target; + + window = get_drop_window (widget); + + target_list = gtk_drag_dest_get_target_list (widget); + target = gtk_drag_dest_find_target (widget, context, target_list); + + if (target != GDK_NONE) + { + guint info; + gboolean found; + + found = gtk_target_list_find (target_list, target, &info); + g_assert (found); + + if (info == TARGET_XDNDDIRECTSAVE) + { + gchar *uri; + uri = gedit_utils_set_direct_save_filename (context); + + if (uri != NULL) + { + g_free (window->priv->direct_save_uri); + window->priv->direct_save_uri = uri; + } + } + + gtk_drag_get_data (GTK_WIDGET (widget), context, + target, time); + } +} + +/* Handle drops on the GeditView */ +static void +drop_uris_cb (GtkWidget *widget, + gchar **uri_list, + GeditWindow *window) +{ + load_uris_from_drop (window, uri_list); +} + +static void +update_fullscreen_revealer_state (GeditWindow *window) +{ + gboolean open_recent_menu_is_active; + gboolean hamburger_menu_is_active; + + open_recent_menu_is_active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (window->priv->fullscreen_open_recent_button)); + hamburger_menu_is_active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (window->priv->fullscreen_gear_button)); + + gtk_revealer_set_reveal_child (window->priv->fullscreen_revealer, + (window->priv->in_fullscreen_eventbox || + open_recent_menu_is_active || + hamburger_menu_is_active)); +} + +static gboolean +on_fullscreen_eventbox_enter_notify_event (GtkWidget *fullscreen_eventbox, + GdkEventCrossing *event, + GeditWindow *window) +{ + window->priv->in_fullscreen_eventbox = TRUE; + update_fullscreen_revealer_state (window); + + return GDK_EVENT_PROPAGATE; +} + +static gboolean +on_fullscreen_eventbox_leave_notify_event (GtkWidget *fullscreen_eventbox, + GdkEventCrossing *event, + GeditWindow *window) +{ + if (-1.0 <= event->y && event->y <= 0.0) + { + /* Ignore the event. + * + * Leave notify events are received with -1 <= y <= 0 + * coordinates, although the GeditWindow is in fullscreen mode + * and when there are no screens above (it's maybe a bug in an + * underlying library). + * If we hide the headerbar when those events happen, then it + * makes the headerbar to be shown/hidden a lot of time in a + * short period of time, i.e. a "stuttering". In other words + * lots of leave/enter events are received when moving the mouse + * upwards on the screen when the mouse is already at the top. + * The expected leave event has a positive event->y value being + * >= to the height of the headerbar (approximately + * 40 <= y <= 50). So clearly when we receive a leave event with + * event->y <= 0, it means that the mouse has left the eventbox + * on the wrong side. + * The -1.0 <= event->y is there (instead of just <= 0.0) in the + * case that there is another screen *above*, even if this + * heuristic/workaround is not perfect in that case. But that + * case is quite rare, so it's probably a good enough solution. + * + * Note that apparently the "stuttering" occurs only on an Xorg + * session, not on Wayland (tested with GNOME). + * + * If you see a better solution... + */ + return GDK_EVENT_PROPAGATE; + } + + window->priv->in_fullscreen_eventbox = FALSE; + update_fullscreen_revealer_state (window); + + return GDK_EVENT_PROPAGATE; +} + +static void +setup_fullscreen_eventbox (GeditWindow *window) +{ + gtk_widget_set_size_request (window->priv->fullscreen_eventbox, -1, 1); + gtk_widget_hide (window->priv->fullscreen_eventbox); + + g_signal_connect (window->priv->fullscreen_eventbox, + "enter-notify-event", + G_CALLBACK (on_fullscreen_eventbox_enter_notify_event), + window); + + g_signal_connect (window->priv->fullscreen_eventbox, + "leave-notify-event", + G_CALLBACK (on_fullscreen_eventbox_leave_notify_event), + window); +} + +static void +empty_search_notify_cb (GeditDocument *doc, + GParamSpec *pspec, + GeditWindow *window) +{ + if (doc == gedit_window_get_active_document (window)) + { + update_actions_sensitivity (window); + } +} + +static void +can_undo (GeditDocument *doc, + GParamSpec *pspec, + GeditWindow *window) +{ + if (doc == gedit_window_get_active_document (window)) + { + update_actions_sensitivity (window); + } +} + +static void +can_redo (GeditDocument *doc, + GParamSpec *pspec, + GeditWindow *window) +{ + if (doc == gedit_window_get_active_document (window)) + { + update_actions_sensitivity (window); + } +} + +static void +selection_changed (GeditDocument *doc, + GParamSpec *pspec, + GeditWindow *window) +{ + if (doc == gedit_window_get_active_document (window)) + { + update_actions_sensitivity (window); + } +} + +static void +readonly_changed (GtkSourceFile *file, + GParamSpec *pspec, + GeditWindow *window) +{ + update_actions_sensitivity (window); + + sync_name (gedit_window_get_active_tab (window), NULL, window); + + peas_extension_set_foreach (window->priv->extensions, + (PeasExtensionSetForeachFunc) extension_update_state, + window); +} + +static void +editable_changed (GeditView *view, + GParamSpec *arg1, + GeditWindow *window) +{ + peas_extension_set_foreach (window->priv->extensions, + (PeasExtensionSetForeachFunc) extension_update_state, + window); +} + +static void +on_tab_added (GeditMultiNotebook *multi, + GeditNotebook *notebook, + GeditTab *tab, + GeditWindow *window) +{ + GeditView *view; + GeditDocument *doc; + GtkSourceFile *file; + + gedit_debug (DEBUG_WINDOW); + + update_actions_sensitivity (window); + + view = gedit_tab_get_view (tab); + doc = gedit_tab_get_document (tab); + file = gedit_document_get_file (doc); + + /* IMPORTANT: remember to disconnect the signal in notebook_tab_removed + * if a new signal is connected here */ + + g_signal_connect (tab, + "notify::name", + G_CALLBACK (sync_name), + window); + g_signal_connect (tab, + "notify::state", + G_CALLBACK (sync_state), + window); + g_signal_connect (tab, + "notify::can-close", + G_CALLBACK (sync_can_close), + window); + g_signal_connect (tab, + "drop_uris", + G_CALLBACK (drop_uris_cb), + window); + g_signal_connect (doc, + "bracket-matched", + G_CALLBACK (bracket_matched_cb), + window); + g_signal_connect (doc, + "notify::empty-search", + G_CALLBACK (empty_search_notify_cb), + window); + g_signal_connect (doc, + "notify::can-undo", + G_CALLBACK (can_undo), + window); + g_signal_connect (doc, + "notify::can-redo", + G_CALLBACK (can_redo), + window); + g_signal_connect (doc, + "notify::has-selection", + G_CALLBACK (selection_changed), + window); + g_signal_connect (view, + "notify::overwrite", + G_CALLBACK (overwrite_mode_changed), + window); + g_signal_connect (view, + "notify::editable", + G_CALLBACK (editable_changed), + window); + g_signal_connect (file, + "notify::read-only", + G_CALLBACK (readonly_changed), + window); + + update_window_state (window); + update_can_close (window); + + g_signal_emit (G_OBJECT (window), signals[TAB_ADDED], 0, tab); +} + +static void +push_last_closed_doc (GeditWindow *window, + GeditDocument *doc) +{ + GeditWindowPrivate *priv = window->priv; + GtkSourceFile *file = gedit_document_get_file (doc); + GFile *location = gtk_source_file_get_location (file); + + if (location != NULL) + { + priv->closed_docs_stack = g_slist_prepend (priv->closed_docs_stack, location); + g_object_ref (location); + } +} + +GFile * +_gedit_window_pop_last_closed_doc (GeditWindow *window) +{ + GeditWindowPrivate *priv = window->priv; + GFile *f = NULL; + + if (window->priv->closed_docs_stack != NULL) + { + f = priv->closed_docs_stack->data; + priv->closed_docs_stack = g_slist_remove (priv->closed_docs_stack, f); + } + + return f; +} + +static void +on_tab_removed (GeditMultiNotebook *multi, + GeditNotebook *notebook, + GeditTab *tab, + GeditWindow *window) +{ + GeditView *view; + GeditDocument *doc; + gint num_tabs; + + gedit_debug (DEBUG_WINDOW); + + num_tabs = gedit_multi_notebook_get_n_tabs (multi); + + view = gedit_tab_get_view (tab); + doc = gedit_tab_get_document (tab); + + g_signal_handlers_disconnect_by_func (tab, + G_CALLBACK (sync_name), + window); + g_signal_handlers_disconnect_by_func (tab, + G_CALLBACK (sync_state), + window); + g_signal_handlers_disconnect_by_func (tab, + G_CALLBACK (sync_can_close), + window); + g_signal_handlers_disconnect_by_func (tab, + G_CALLBACK (drop_uris_cb), + window); + g_signal_handlers_disconnect_by_func (doc, + G_CALLBACK (bracket_matched_cb), + window); + g_signal_handlers_disconnect_by_func (doc, + G_CALLBACK (empty_search_notify_cb), + window); + g_signal_handlers_disconnect_by_func (doc, + G_CALLBACK (can_undo), + window); + g_signal_handlers_disconnect_by_func (doc, + G_CALLBACK (can_redo), + window); + g_signal_handlers_disconnect_by_func (doc, + G_CALLBACK (selection_changed), + window); + g_signal_handlers_disconnect_by_func (doc, + G_CALLBACK (readonly_changed), + window); + g_signal_handlers_disconnect_by_func (view, + G_CALLBACK (overwrite_mode_changed), + window); + g_signal_handlers_disconnect_by_func (view, + G_CALLBACK (editable_changed), + window); + + if (tab == gedit_multi_notebook_get_active_tab (multi)) + { + if (window->priv->tab_width_id) + { + g_signal_handler_disconnect (view, window->priv->tab_width_id); + window->priv->tab_width_id = 0; + } + + if (window->priv->language_changed_id) + { + g_signal_handler_disconnect (doc, window->priv->language_changed_id); + window->priv->language_changed_id = 0; + } + + gedit_multi_notebook_set_active_tab (multi, NULL); + } + + g_return_if_fail (num_tabs >= 0); + if (num_tabs == 0) + { + set_title (window); + + /* hide the additional widgets */ + gtk_widget_hide (GTK_WIDGET (window->priv->overwrite_indicator)); + gtk_widget_hide (GTK_WIDGET (window->priv->line_column_indicator)); + gtk_widget_hide (window->priv->tab_width_button); + gtk_widget_hide (window->priv->language_button); + } + + if (!window->priv->dispose_has_run) + { + push_last_closed_doc (window, doc); + + if ((!window->priv->removing_tabs && + gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)) > 0) || + num_tabs == 0) + { + update_actions_sensitivity (window); + } + } + + update_window_state (window); + update_can_close (window); + + g_signal_emit (G_OBJECT (window), signals[TAB_REMOVED], 0, tab); +} + +static void +on_page_reordered (GeditMultiNotebook *multi, + GeditNotebook *notebook, + GtkWidget *page, + gint page_num, + GeditWindow *window) +{ + update_actions_sensitivity (window); + + g_signal_emit (G_OBJECT (window), signals[TABS_REORDERED], 0); +} + +static GtkNotebook * +on_notebook_create_window (GeditMultiNotebook *mnb, + GtkNotebook *notebook, + GtkWidget *page, + gint x, + gint y, + GeditWindow *window) +{ + GeditWindow *new_window; + GtkWidget *new_notebook; + + new_window = clone_window (window); + + gtk_window_move (GTK_WINDOW (new_window), x, y); + gtk_widget_show (GTK_WIDGET (new_window)); + + new_notebook = _gedit_window_get_notebook (GEDIT_WINDOW (new_window)); + + return GTK_NOTEBOOK (new_notebook); +} + +static void +on_tab_close_request (GeditMultiNotebook *multi, + GeditNotebook *notebook, + GeditTab *tab, + GtkWindow *window) +{ + /* Note: we are destroying the tab before the default handler + * seems to be ok, but we need to keep an eye on this. */ + _gedit_cmd_file_close_tab (tab, GEDIT_WINDOW (window)); +} + +static void +on_show_popup_menu (GeditMultiNotebook *multi, + GdkEventButton *event, + GeditTab *tab, + GeditWindow *window) +{ + GtkWidget *menu; + + if (event == NULL) + { + return; + } + + menu = gedit_notebook_popup_menu_new (window, tab); + + g_signal_connect (menu, + "selection-done", + G_CALLBACK (gtk_widget_destroy), + NULL); + + gtk_widget_show (menu); + gtk_menu_popup_at_pointer (GTK_MENU (menu), (GdkEvent *)event); +} + +static void +on_notebook_changed (GeditMultiNotebook *mnb, + GParamSpec *pspec, + GeditWindow *window) +{ + update_actions_sensitivity (window); +} + +static void +on_notebook_removed (GeditMultiNotebook *mnb, + GeditNotebook *notebook, + GeditWindow *window) +{ + update_actions_sensitivity (window); +} + +static void +on_fullscreen_toggle_button_toggled (GtkToggleButton *fullscreen_toggle_button, + GeditWindow *window) +{ + update_fullscreen_revealer_state (window); +} + +static void +side_panel_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + GeditWindow *window) +{ + window->priv->side_panel_size = allocation->width; +} + +static void +bottom_panel_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + GeditWindow *window) +{ + window->priv->bottom_panel_size = allocation->height; +} + +static void +hpaned_restore_position (GtkWidget *widget, + GeditWindow *window) +{ + gint pos; + + gedit_debug_message (DEBUG_WINDOW, + "Restoring hpaned position: side panel size %d", + window->priv->side_panel_size); + + pos = MAX (100, window->priv->side_panel_size); + gtk_paned_set_position (GTK_PANED (window->priv->hpaned), pos); + + /* start monitoring the size */ + g_signal_connect (window->priv->side_panel, + "size-allocate", + G_CALLBACK (side_panel_size_allocate), + window); + + /* run this only once */ + g_signal_handlers_disconnect_by_func (widget, hpaned_restore_position, window); +} + +static void +vpaned_restore_position (GtkWidget *widget, + GeditWindow *window) +{ + gint pos; + GtkAllocation allocation; + + gedit_debug_message (DEBUG_WINDOW, + "Restoring vpaned position: bottom panel size %d", + window->priv->bottom_panel_size); + + gtk_widget_get_allocation (widget, &allocation); + pos = allocation.height - + MAX (50, window->priv->bottom_panel_size); + gtk_paned_set_position (GTK_PANED (window->priv->vpaned), pos); + + /* start monitoring the size */ + g_signal_connect (window->priv->bottom_panel, + "size-allocate", + G_CALLBACK (bottom_panel_size_allocate), + window); + + /* run this only once */ + g_signal_handlers_disconnect_by_func (widget, vpaned_restore_position, window); +} + +static void +side_panel_visibility_changed (GtkWidget *panel, + GParamSpec *pspec, + GeditWindow *window) +{ + gboolean visible; + GAction *action; + gchar *layout_desc; + + visible = gtk_widget_get_visible (panel); + + g_settings_set_boolean (window->priv->ui_settings, + GEDIT_SETTINGS_SIDE_PANEL_VISIBLE, + visible); + + /* sync the action state if the panel visibility was changed programmatically */ + action = g_action_map_lookup_action (G_ACTION_MAP (window), "side-panel"); + g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (visible)); + + /* focus the right widget and set the right styles */ + if (visible) + { + gtk_widget_grab_focus (window->priv->side_panel); + } + else + { + gtk_widget_grab_focus (GTK_WIDGET (window->priv->multi_notebook)); + } + + g_object_get (gtk_settings_get_default (), + "gtk-decoration-layout", &layout_desc, + NULL); + if (visible) + { + gchar **tokens; + + tokens = g_strsplit (layout_desc, ":", 2); + if (tokens) + { + gchar *layout_headerbar; + + layout_headerbar = g_strdup_printf ("%c%s", ':', tokens[1]); + gtk_header_bar_set_decoration_layout (GTK_HEADER_BAR (window->priv->headerbar), layout_headerbar); + gtk_header_bar_set_decoration_layout (GTK_HEADER_BAR (window->priv->side_headerbar), tokens[0]); + + g_free (layout_headerbar); + g_strfreev (tokens); + } + } + else + { + gtk_header_bar_set_decoration_layout (GTK_HEADER_BAR (window->priv->headerbar), layout_desc); + gtk_header_bar_set_decoration_layout (GTK_HEADER_BAR (window->priv->side_headerbar), NULL); + } + + g_free (layout_desc); +} + +static void +on_side_panel_stack_children_number_changed (GtkStack *stack, + GtkWidget *widget, + GeditWindow *window) +{ + GeditWindowPrivate *priv = window->priv; + GList *children; + + children = gtk_container_get_children (GTK_CONTAINER (priv->side_panel)); + + if (children != NULL && children->next != NULL) + { + gtk_widget_show (priv->side_stack_switcher); + +#ifndef OS_OSX + gtk_header_bar_set_custom_title (GTK_HEADER_BAR (priv->side_headerbar), priv->side_stack_switcher); +#endif + } + else + { + /* side_stack_switcher can get NULL in dispose, before stack children + are being removed */ + if (priv->side_stack_switcher != NULL) + { + gtk_widget_hide (priv->side_stack_switcher); + } + +#ifndef OS_OSX + gtk_header_bar_set_custom_title (GTK_HEADER_BAR (priv->side_headerbar), NULL); +#endif + } + + g_list_free (children); +} + +static void +setup_side_panel (GeditWindow *window) +{ + GeditWindowPrivate *priv = window->priv; + GtkWidget *documents_panel; + + gedit_debug (DEBUG_WINDOW); + + g_signal_connect_after (priv->side_panel, + "notify::visible", + G_CALLBACK (side_panel_visibility_changed), + window); + +#ifdef OS_OSX + priv->side_stack_switcher = priv->side_panel_inline_stack_switcher; +#else + priv->side_stack_switcher = gedit_menu_stack_switcher_new (); +#endif + + gtk_button_set_relief (GTK_BUTTON (priv->side_stack_switcher), GTK_RELIEF_NONE); + g_object_ref_sink (priv->side_stack_switcher); + + gedit_utils_set_atk_name_description (priv->side_stack_switcher, _("Change side panel page"), NULL); + + gedit_menu_stack_switcher_set_stack (GEDIT_MENU_STACK_SWITCHER (priv->side_stack_switcher), + GTK_STACK (priv->side_panel)); + + g_signal_connect (priv->side_panel, + "add", + G_CALLBACK (on_side_panel_stack_children_number_changed), + window); + + g_signal_connect (priv->side_panel, + "remove", + G_CALLBACK (on_side_panel_stack_children_number_changed), + window); + + documents_panel = gedit_documents_panel_new (window); + gtk_widget_show_all (documents_panel); + gtk_stack_add_titled (GTK_STACK (priv->side_panel), + documents_panel, + "GeditWindowDocumentsPanel", + _("Documents")); +} + +static void +bottom_panel_visibility_changed (GtkWidget *panel_box, + GParamSpec *pspec, + GeditWindow *window) +{ + gboolean visible; + GAction *action; + + visible = gtk_widget_get_visible (panel_box); + + g_settings_set_boolean (window->priv->ui_settings, + GEDIT_SETTINGS_BOTTOM_PANEL_VISIBLE, + visible); + + /* sync the action state if the panel visibility was changed programmatically */ + action = g_action_map_lookup_action (G_ACTION_MAP (window), "bottom-panel"); + g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (visible)); + + /* focus the right widget */ + if (visible) + { + gtk_widget_grab_focus (window->priv->side_panel); + } + else + { + gtk_widget_grab_focus (GTK_WIDGET (window->priv->multi_notebook)); + } +} + +static void +bottom_panel_item_removed (GtkStack *panel, + GtkWidget *item, + GeditWindow *window) +{ + gtk_widget_set_visible (window->priv->bottom_panel, + gtk_stack_get_visible_child (panel) != NULL); + + update_actions_sensitivity (window); +} + +static void +bottom_panel_item_added (GtkStack *panel, + GtkWidget *item, + GeditWindow *window) +{ + GList *children; + int n_children; + + children = gtk_container_get_children (GTK_CONTAINER (panel)); + n_children = g_list_length (children); + g_list_free (children); + + /* First item added. */ + if (n_children == 1) + { + gboolean show; + + show = g_settings_get_boolean (window->priv->ui_settings, + "bottom-panel-visible"); + if (show) + { + gtk_widget_show (window->priv->bottom_panel); + } + + update_actions_sensitivity (window); + } +} + +static void +setup_bottom_panel (GeditWindow *window) +{ + gedit_debug (DEBUG_WINDOW); + + g_signal_connect_after (window->priv->bottom_panel, + "notify::visible", + G_CALLBACK (bottom_panel_visibility_changed), + window); +} + +static void +init_panels_visibility (GeditWindow *window) +{ + gchar *panel_page; + GtkWidget *panel_child; + gboolean side_panel_visible; + gboolean bottom_panel_visible; + + gedit_debug (DEBUG_WINDOW); + + /* side panel */ + panel_page = g_settings_get_string (window->priv->window_settings, + GEDIT_SETTINGS_SIDE_PANEL_ACTIVE_PAGE); + panel_child = gtk_stack_get_child_by_name (GTK_STACK (window->priv->side_panel), + panel_page); + if (panel_child != NULL) + { + gtk_stack_set_visible_child (GTK_STACK (window->priv->side_panel), + panel_child); + } + + g_free (panel_page); + + side_panel_visible = g_settings_get_boolean (window->priv->ui_settings, + GEDIT_SETTINGS_SIDE_PANEL_VISIBLE); + bottom_panel_visible = g_settings_get_boolean (window->priv->ui_settings, + GEDIT_SETTINGS_BOTTOM_PANEL_VISIBLE); + + if (side_panel_visible) + { + gtk_widget_show (window->priv->side_panel); + } + + /* bottom pane, it can be empty */ + if (gtk_stack_get_visible_child (GTK_STACK (window->priv->bottom_panel)) != NULL) + { + panel_page = g_settings_get_string (window->priv->window_settings, + GEDIT_SETTINGS_BOTTOM_PANEL_ACTIVE_PAGE); + panel_child = gtk_stack_get_child_by_name (GTK_STACK (window->priv->side_panel), + panel_page); + if (panel_child) + { + gtk_stack_set_visible_child (GTK_STACK (window->priv->bottom_panel), + panel_child); + } + + if (bottom_panel_visible) + { + gtk_widget_show (window->priv->bottom_panel); + } + + g_free (panel_page); + } + + /* start track sensitivity after the initial state is set */ + window->priv->bottom_panel_item_removed_handler_id = + g_signal_connect (window->priv->bottom_panel, + "remove", + G_CALLBACK (bottom_panel_item_removed), + window); + + g_signal_connect_after (window->priv->bottom_panel, + "add", + G_CALLBACK (bottom_panel_item_added), + window); +} + +static void +clipboard_owner_change (GtkClipboard *clipboard, + GdkEventOwnerChange *event, + GeditWindow *window) +{ + set_paste_sensitivity_according_to_clipboard (window, + clipboard); +} + +static void +window_realized (GtkWidget *window, + gpointer *data) +{ + GtkClipboard *clipboard; + + clipboard = gtk_widget_get_clipboard (window, + GDK_SELECTION_CLIPBOARD); + + g_signal_connect (clipboard, + "owner_change", + G_CALLBACK (clipboard_owner_change), + window); +} + +static void +window_unrealized (GtkWidget *window, + gpointer *data) +{ + GtkClipboard *clipboard; + + clipboard = gtk_widget_get_clipboard (window, + GDK_SELECTION_CLIPBOARD); + + g_signal_handlers_disconnect_by_func (clipboard, + G_CALLBACK (clipboard_owner_change), + window); +} + +static void +extension_added (PeasExtensionSet *extensions, + PeasPluginInfo *info, + PeasExtension *exten, + GeditWindow *window) +{ + gedit_window_activatable_activate (GEDIT_WINDOW_ACTIVATABLE (exten)); +} + +static void +extension_removed (PeasExtensionSet *extensions, + PeasPluginInfo *info, + PeasExtension *exten, + GeditWindow *window) +{ + gedit_window_activatable_deactivate (GEDIT_WINDOW_ACTIVATABLE (exten)); +} + +static GActionEntry win_entries[] = { + { "new-tab", _gedit_cmd_file_new }, + { "open", _gedit_cmd_file_open }, + { "revert", _gedit_cmd_file_revert }, + { "reopen-closed-tab", _gedit_cmd_file_reopen_closed_tab }, + { "save", _gedit_cmd_file_save }, + { "save-as", _gedit_cmd_file_save_as }, + { "save-all", _gedit_cmd_file_save_all }, + { "close", _gedit_cmd_file_close }, + { "close-all", _gedit_cmd_file_close_all }, + { "print", _gedit_cmd_file_print }, + { "focus-active-view", NULL, NULL, "false", _gedit_cmd_view_focus_active }, + { "side-panel", NULL, NULL, "false", _gedit_cmd_view_toggle_side_panel }, + { "bottom-panel", NULL, NULL, "false", _gedit_cmd_view_toggle_bottom_panel }, + { "fullscreen", NULL, NULL, "false", _gedit_cmd_view_toggle_fullscreen_mode }, + { "leave-fullscreen", _gedit_cmd_view_leave_fullscreen_mode }, + { "find", _gedit_cmd_search_find }, + { "find-next", _gedit_cmd_search_find_next }, + { "find-prev", _gedit_cmd_search_find_prev }, + { "replace", _gedit_cmd_search_replace }, + { "clear-highlight", _gedit_cmd_search_clear_highlight }, + { "goto-line", _gedit_cmd_search_goto_line }, + { "new-tab-group", _gedit_cmd_documents_new_tab_group }, + { "previous-tab-group", _gedit_cmd_documents_previous_tab_group }, + { "next-tab-group", _gedit_cmd_documents_next_tab_group }, + { "previous-document", _gedit_cmd_documents_previous_document }, + { "next-document", _gedit_cmd_documents_next_document }, + { "move-to-new-window", _gedit_cmd_documents_move_to_new_window }, + { "undo", _gedit_cmd_edit_undo }, + { "redo", _gedit_cmd_edit_redo }, + { "cut", _gedit_cmd_edit_cut }, + { "copy", _gedit_cmd_edit_copy }, + { "paste", _gedit_cmd_edit_paste }, + { "delete", _gedit_cmd_edit_delete }, + { "select-all", _gedit_cmd_edit_select_all }, + { "highlight-mode", _gedit_cmd_view_highlight_mode }, + { "overwrite-mode", NULL, NULL, "false", _gedit_cmd_edit_overwrite_mode } +}; + +static void +sync_fullscreen_actions (GeditWindow *window, + gboolean fullscreen) +{ + GtkMenuButton *button; + GPropertyAction *action; + + button = fullscreen ? window->priv->fullscreen_gear_button : window->priv->gear_button; + g_action_map_remove_action (G_ACTION_MAP (window), "hamburger-menu"); + action = g_property_action_new ("hamburger-menu", button, "active"); + g_action_map_add_action (G_ACTION_MAP (window), G_ACTION (action)); + g_object_unref (action); +} + +static void +init_amtk_application_window (GeditWindow *gedit_window) +{ + AmtkApplicationWindow *amtk_window; + + amtk_window = amtk_application_window_get_from_gtk_application_window (GTK_APPLICATION_WINDOW (gedit_window)); + amtk_application_window_set_statusbar (amtk_window, GTK_STATUSBAR (gedit_window->priv->statusbar)); +} + +static void +open_recent_menu_item_activated_cb (GtkRecentChooser *recent_chooser, + gpointer user_data) +{ + GeditWindow *window = GEDIT_WINDOW (user_data); + gchar *uri; + GFile *location; + + uri = gtk_recent_chooser_get_current_uri (recent_chooser); + location = g_file_new_for_uri (uri); + + gedit_commands_load_location (window, location, NULL, 0, 0); + + g_free (uri); + g_object_unref (location); +} + +static GtkWidget * +create_open_buttons (GeditWindow *window, + GtkMenuButton **open_recent_button) +{ + GtkWidget *hbox; + GtkStyleContext *style_context; + GtkWidget *open_dialog_button; + GtkWidget *my_open_recent_button; + AmtkApplicationWindow *amtk_window; + GtkRecentChooserMenu *recent_menu; + + /* It currently needs to be a GtkBox, not a GtkGrid, because GtkGrid and + * GTK_STYLE_CLASS_LINKED doesn't work as expected in a RTL locale. + * Probably a GtkGrid bug. + */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + style_context = gtk_widget_get_style_context (hbox); + gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_LINKED); + + open_dialog_button = gtk_button_new_with_mnemonic (_("_Open")); + gtk_widget_set_tooltip_text (open_dialog_button, _("Open a file")); + gtk_actionable_set_action_name (GTK_ACTIONABLE (open_dialog_button), "win.open"); + + my_open_recent_button = gtk_menu_button_new (); + gtk_widget_set_tooltip_text (my_open_recent_button, _("Open a recently used file")); + + recent_menu = amtk_application_window_create_open_recent_menu_base (); + + amtk_window = amtk_application_window_get_from_gtk_application_window (GTK_APPLICATION_WINDOW (window)); + amtk_application_window_connect_recent_chooser_menu_to_statusbar (amtk_window, recent_menu); + + g_signal_connect_object (recent_menu, + "item-activated", + G_CALLBACK (open_recent_menu_item_activated_cb), + window, + 0); + + gtk_menu_button_set_popup (GTK_MENU_BUTTON (my_open_recent_button), + GTK_WIDGET (recent_menu)); + + gtk_container_add (GTK_CONTAINER (hbox), open_dialog_button); + gtk_container_add (GTK_CONTAINER (hbox), my_open_recent_button); + gtk_widget_show_all (hbox); + + if (open_recent_button != NULL) + { + *open_recent_button = GTK_MENU_BUTTON (my_open_recent_button); + } + + return hbox; +} + +static void +init_open_buttons (GeditWindow *window) +{ + gtk_container_add_with_properties (GTK_CONTAINER (window->priv->headerbar), + create_open_buttons (window, NULL), + "position", 0, /* The first on the left. */ + NULL); + + gtk_container_add_with_properties (GTK_CONTAINER (window->priv->fullscreen_headerbar), + create_open_buttons (window, &(window->priv->fullscreen_open_recent_button)), + "position", 0, /* The first on the left. */ + NULL); + + g_signal_connect (GTK_TOGGLE_BUTTON (window->priv->fullscreen_open_recent_button), + "toggled", + G_CALLBACK (on_fullscreen_toggle_button_toggled), + window); +} + +static void +gedit_window_init (GeditWindow *window) +{ + GtkTargetList *tl; + GMenuModel *hamburger_menu; + + gedit_debug (DEBUG_WINDOW); + + window->priv = gedit_window_get_instance_private (window); + + window->priv->removing_tabs = FALSE; + window->priv->state = GEDIT_WINDOW_STATE_NORMAL; + window->priv->inhibition_cookie = 0; + window->priv->dispose_has_run = FALSE; + window->priv->direct_save_uri = NULL; + window->priv->closed_docs_stack = NULL; + window->priv->editor_settings = g_settings_new ("org.gnome.gedit.preferences.editor"); + window->priv->ui_settings = g_settings_new ("org.gnome.gedit.preferences.ui"); + + /* window settings are applied only once the window is closed. We do not + want to keep writing to disk when the window is dragged around */ + window->priv->window_settings = g_settings_new ("org.gnome.gedit.state.window"); + g_settings_delay (window->priv->window_settings); + + window->priv->message_bus = gedit_message_bus_new (); + + gtk_widget_init_template (GTK_WIDGET (window)); + init_amtk_application_window (window); + init_open_buttons (window); + + g_action_map_add_action_entries (G_ACTION_MAP (window), + win_entries, + G_N_ELEMENTS (win_entries), + window); + + window->priv->window_group = gtk_window_group_new (); + gtk_window_group_add_window (window->priv->window_group, GTK_WINDOW (window)); + + setup_fullscreen_eventbox (window); + sync_fullscreen_actions (window, FALSE); + + hamburger_menu = _gedit_app_get_hamburger_menu (GEDIT_APP (g_application_get_default ())); + if (hamburger_menu) + { + gtk_menu_button_set_menu_model (window->priv->gear_button, hamburger_menu); + gtk_menu_button_set_menu_model (window->priv->fullscreen_gear_button, hamburger_menu); + } + else + { + gtk_widget_hide (GTK_WIDGET (window->priv->gear_button)); + gtk_widget_hide (GTK_WIDGET (window->priv->fullscreen_gear_button)); + gtk_widget_set_no_show_all (GTK_WIDGET (window->priv->gear_button), TRUE); + gtk_widget_set_no_show_all (GTK_WIDGET (window->priv->fullscreen_gear_button), TRUE); + } + + g_signal_connect (GTK_TOGGLE_BUTTON (window->priv->fullscreen_gear_button), + "toggled", + G_CALLBACK (on_fullscreen_toggle_button_toggled), + window); + + /* Setup status bar */ + setup_statusbar (window); + + /* Setup main area */ + g_signal_connect (window->priv->multi_notebook, + "notebook-removed", + G_CALLBACK (on_notebook_removed), + window); + g_signal_connect (window->priv->multi_notebook, + "notify::active-notebook", + G_CALLBACK (on_notebook_changed), + window); + + g_signal_connect (window->priv->multi_notebook, + "tab-added", + G_CALLBACK (on_tab_added), + window); + + g_signal_connect (window->priv->multi_notebook, + "tab-removed", + G_CALLBACK (on_tab_removed), + window); + + g_signal_connect (window->priv->multi_notebook, + "switch-tab", + G_CALLBACK (tab_switched), + window); + + g_signal_connect (window->priv->multi_notebook, + "tab-close-request", + G_CALLBACK (on_tab_close_request), + window); + + g_signal_connect (window->priv->multi_notebook, + "page-reordered", + G_CALLBACK (on_page_reordered), + window); + + g_signal_connect (window->priv->multi_notebook, + "create-window", + G_CALLBACK (on_notebook_create_window), + window); + + g_signal_connect (window->priv->multi_notebook, + "show-popup-menu", + G_CALLBACK (on_show_popup_menu), + window); + + /* side and bottom panels */ + setup_side_panel (window); + setup_bottom_panel (window); + + /* panels' state must be restored after panels have been mapped, + * since the bottom panel position depends on the size of the vpaned. */ + window->priv->side_panel_size = g_settings_get_int (window->priv->window_settings, + GEDIT_SETTINGS_SIDE_PANEL_SIZE); + window->priv->bottom_panel_size = g_settings_get_int (window->priv->window_settings, + GEDIT_SETTINGS_BOTTOM_PANEL_SIZE); + + g_signal_connect_after (window->priv->hpaned, + "map", + G_CALLBACK (hpaned_restore_position), + window); + g_signal_connect_after (window->priv->vpaned, + "map", + G_CALLBACK (vpaned_restore_position), + window); + + /* Drag and drop support */ + gtk_drag_dest_set (GTK_WIDGET (window), + GTK_DEST_DEFAULT_MOTION | + GTK_DEST_DEFAULT_HIGHLIGHT | + GTK_DEST_DEFAULT_DROP, + drop_types, + G_N_ELEMENTS (drop_types), + GDK_ACTION_COPY); + + /* Add uri targets */ + tl = gtk_drag_dest_get_target_list (GTK_WIDGET (window)); + + if (tl == NULL) + { + tl = gtk_target_list_new (drop_types, G_N_ELEMENTS (drop_types)); + gtk_drag_dest_set_target_list (GTK_WIDGET (window), tl); + gtk_target_list_unref (tl); + } + + gtk_target_list_add_uri_targets (tl, TARGET_URI_LIST); + + /* connect instead of override, so that we can + * share the cb code with the view */ + g_signal_connect (window, + "drag_data_received", + G_CALLBACK (drag_data_received_cb), + NULL); + g_signal_connect (window, + "drag_drop", + G_CALLBACK (drag_drop_cb), + NULL); + + /* we can get the clipboard only after the widget + * is realized */ + g_signal_connect (window, + "realize", + G_CALLBACK (window_realized), + NULL); + g_signal_connect (window, + "unrealize", + G_CALLBACK (window_unrealized), + NULL); + + gedit_debug_message (DEBUG_WINDOW, "Update plugins ui"); + + window->priv->extensions = peas_extension_set_new (PEAS_ENGINE (gedit_plugins_engine_get_default ()), + GEDIT_TYPE_WINDOW_ACTIVATABLE, + "window", window, + NULL); + g_signal_connect (window->priv->extensions, + "extension-added", + G_CALLBACK (extension_added), + window); + g_signal_connect (window->priv->extensions, + "extension-removed", + G_CALLBACK (extension_removed), + window); + peas_extension_set_foreach (window->priv->extensions, + (PeasExtensionSetForeachFunc) extension_added, + window); + + /* set visibility of panels. + * This needs to be done after plugins activatation */ + init_panels_visibility (window); + + update_actions_sensitivity (window); + + gedit_debug_message (DEBUG_WINDOW, "END"); +} + +/** + * gedit_window_get_active_view: + * @window: a #GeditWindow + * + * Gets the active #GeditView. + * + * Returns: (transfer none): the active #GeditView + */ +GeditView * +gedit_window_get_active_view (GeditWindow *window) +{ + GeditTab *tab; + GeditView *view; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + tab = gedit_window_get_active_tab (window); + + if (tab == NULL) + return NULL; + + view = gedit_tab_get_view (tab); + + return view; +} + +/** + * gedit_window_get_active_document: + * @window: a #GeditWindow + * + * Gets the active #GeditDocument. + * + * Returns: (transfer none): the active #GeditDocument + */ +GeditDocument * +gedit_window_get_active_document (GeditWindow *window) +{ + GeditView *view; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + view = gedit_window_get_active_view (window); + if (view == NULL) + return NULL; + + return GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); +} + +GtkWidget * +_gedit_window_get_multi_notebook (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return GTK_WIDGET (window->priv->multi_notebook); +} + +GtkWidget * +_gedit_window_get_notebook (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return GTK_WIDGET (gedit_multi_notebook_get_active_notebook (window->priv->multi_notebook)); +} + +static GeditTab * +process_create_tab (GeditWindow *window, + GtkWidget *notebook, + GeditTab *tab, + gboolean jump_to) +{ + if (tab == NULL) + { + return NULL; + } + + gedit_debug (DEBUG_WINDOW); + + gtk_widget_show (GTK_WIDGET (tab)); + gedit_notebook_add_tab (GEDIT_NOTEBOOK (notebook), + tab, + -1, + jump_to); + + if (!gtk_widget_get_visible (GTK_WIDGET (window))) + { + gtk_window_present (GTK_WINDOW (window)); + } + + return tab; +} + +/** + * gedit_window_create_tab: + * @window: a #GeditWindow + * @jump_to: %TRUE to set the new #GeditTab as active + * + * Creates a new #GeditTab and adds the new tab to the #GtkNotebook. + * In case @jump_to is %TRUE the #GtkNotebook switches to that new #GeditTab. + * + * Returns: (transfer none): a new #GeditTab + */ +GeditTab * +gedit_window_create_tab (GeditWindow *window, + gboolean jump_to) +{ + GtkWidget *notebook; + GeditTab *tab; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + gedit_debug (DEBUG_WINDOW); + + notebook = _gedit_window_get_notebook (window); + tab = _gedit_tab_new (); + gtk_widget_show (GTK_WIDGET (tab)); + + return process_create_tab (window, notebook, tab, jump_to); +} + +/** + * gedit_window_create_tab_from_location: + * @window: a #GeditWindow + * @location: the location of the document + * @encoding: (allow-none): a #GtkSourceEncoding, or %NULL + * @line_pos: the line position to visualize + * @column_pos: the column position to visualize + * @create: %TRUE to create a new document in case @uri does exist + * @jump_to: %TRUE to set the new #GeditTab as active + * + * Creates a new #GeditTab loading the document specified by @uri. + * In case @jump_to is %TRUE the #GtkNotebook swithes to that new #GeditTab. + * Whether @create is %TRUE, creates a new empty document if location does + * not refer to an existing file + * + * Returns: (transfer none): a new #GeditTab + */ +GeditTab * +gedit_window_create_tab_from_location (GeditWindow *window, + GFile *location, + const GtkSourceEncoding *encoding, + gint line_pos, + gint column_pos, + gboolean create, + gboolean jump_to) +{ + GtkWidget *notebook; + GeditTab *tab; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + g_return_val_if_fail (G_IS_FILE (location), NULL); + + gedit_debug (DEBUG_WINDOW); + + tab = _gedit_tab_new (); + + _gedit_tab_load (tab, + location, + encoding, + line_pos, + column_pos, + create); + + notebook = _gedit_window_get_notebook (window); + + return process_create_tab (window, notebook, tab, jump_to); +} + +/** + * gedit_window_create_tab_from_stream: + * @window: a #GeditWindow + * @stream: a #GInputStream + * @encoding: (allow-none): a #GtkSourceEncoding, or %NULL + * @line_pos: the line position to visualize + * @column_pos: the column position to visualize + * @jump_to: %TRUE to set the new #GeditTab as active + * + * Returns: (transfer none): a new #GeditTab + */ +GeditTab * +gedit_window_create_tab_from_stream (GeditWindow *window, + GInputStream *stream, + const GtkSourceEncoding *encoding, + gint line_pos, + gint column_pos, + gboolean jump_to) +{ + GtkWidget *notebook; + GeditTab *tab; + + gedit_debug (DEBUG_WINDOW); + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + g_return_val_if_fail (G_IS_INPUT_STREAM (stream), NULL); + + tab = _gedit_tab_new (); + + _gedit_tab_load_stream (tab, + stream, + encoding, + line_pos, + column_pos); + + notebook = _gedit_window_get_notebook (window); + + return process_create_tab (window, notebook, tab, jump_to); +} + +/** + * gedit_window_get_active_tab: + * @window: a GeditWindow + * + * Gets the active #GeditTab in the @window. + * + * Returns: (transfer none): the active #GeditTab in the @window. + */ +GeditTab * +gedit_window_get_active_tab (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return (window->priv->multi_notebook == NULL) ? NULL : + gedit_multi_notebook_get_active_tab (window->priv->multi_notebook); +} + +static void +add_document (GeditTab *tab, + GList **res) +{ + GeditDocument *doc; + + doc = gedit_tab_get_document (tab); + + *res = g_list_prepend (*res, doc); +} + +/** + * gedit_window_get_documents: + * @window: a #GeditWindow + * + * Gets a newly allocated list with all the documents in the window. + * This list must be freed. + * + * Returns: (element-type Gedit.Document) (transfer container): a newly + * allocated list with all the documents in the window + */ +GList * +gedit_window_get_documents (GeditWindow *window) +{ + GList *res = NULL; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + gedit_multi_notebook_foreach_tab (window->priv->multi_notebook, + (GtkCallback)add_document, + &res); + + res = g_list_reverse (res); + + return res; +} + +static void +add_view (GeditTab *tab, + GList **res) +{ + GeditView *view; + + view = gedit_tab_get_view (tab); + + *res = g_list_prepend (*res, view); +} + +/** + * gedit_window_get_views: + * @window: a #GeditWindow + * + * Gets a list with all the views in the window. This list must be freed. + * + * Returns: (element-type Gedit.View) (transfer container): a newly allocated + * list with all the views in the window + */ +GList * +gedit_window_get_views (GeditWindow *window) +{ + GList *res = NULL; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + gedit_multi_notebook_foreach_tab (window->priv->multi_notebook, + (GtkCallback)add_view, + &res); + + res = g_list_reverse (res); + + return res; +} + +/** + * gedit_window_close_tab: + * @window: a #GeditWindow + * @tab: the #GeditTab to close + * + * Closes the @tab. + */ +void +gedit_window_close_tab (GeditWindow *window, + GeditTab *tab) +{ + GList *tabs = NULL; + + g_return_if_fail (GEDIT_IS_WINDOW (window)); + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail ((gedit_tab_get_state (tab) != GEDIT_TAB_STATE_SAVING) && + (gedit_tab_get_state (tab) != GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW)); + + tabs = g_list_append (tabs, tab); + gedit_multi_notebook_close_tabs (window->priv->multi_notebook, tabs); + g_list_free (tabs); +} + +/** + * gedit_window_close_all_tabs: + * @window: a #GeditWindow + * + * Closes all opened tabs. + */ +void +gedit_window_close_all_tabs (GeditWindow *window) +{ + g_return_if_fail (GEDIT_IS_WINDOW (window)); + g_return_if_fail (!(window->priv->state & GEDIT_WINDOW_STATE_SAVING)); + + window->priv->removing_tabs = TRUE; + + gedit_multi_notebook_close_all_tabs (window->priv->multi_notebook); + + window->priv->removing_tabs = FALSE; +} + +/** + * gedit_window_close_tabs: + * @window: a #GeditWindow + * @tabs: (element-type Gedit.Tab): a list of #GeditTab + * + * Closes all tabs specified by @tabs. + */ +void +gedit_window_close_tabs (GeditWindow *window, + const GList *tabs) +{ + g_return_if_fail (GEDIT_IS_WINDOW (window)); + g_return_if_fail (!(window->priv->state & GEDIT_WINDOW_STATE_SAVING)); + + window->priv->removing_tabs = TRUE; + + gedit_multi_notebook_close_tabs (window->priv->multi_notebook, tabs); + + window->priv->removing_tabs = FALSE; +} + +GeditWindow * +_gedit_window_move_tab_to_new_window (GeditWindow *window, + GeditTab *tab) +{ + GeditWindow *new_window; + GeditNotebook *old_notebook; + GeditNotebook *new_notebook; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + g_return_val_if_fail (GEDIT_IS_TAB (tab), NULL); + g_return_val_if_fail (gedit_multi_notebook_get_n_notebooks ( + window->priv->multi_notebook) > 1 || + gedit_multi_notebook_get_n_tabs ( + window->priv->multi_notebook) > 1, + NULL); + + new_window = clone_window (window); + + old_notebook = GEDIT_NOTEBOOK (gtk_widget_get_parent (GTK_WIDGET (tab))); + new_notebook = gedit_multi_notebook_get_active_notebook (new_window->priv->multi_notebook); + + gedit_notebook_move_tab (old_notebook, + new_notebook, + tab, + -1); + + gtk_widget_show (GTK_WIDGET (new_window)); + + return new_window; +} + +void +_gedit_window_move_tab_to_new_tab_group (GeditWindow *window, + GeditTab *tab) +{ + g_return_if_fail (GEDIT_IS_WINDOW (window)); + g_return_if_fail (GEDIT_IS_TAB (tab)); + + gedit_multi_notebook_add_new_notebook_with_tab (window->priv->multi_notebook, + tab); +} + +/** + * gedit_window_set_active_tab: + * @window: a #GeditWindow + * @tab: a #GeditTab + * + * Switches to the tab that matches with @tab. + */ +void +gedit_window_set_active_tab (GeditWindow *window, + GeditTab *tab) +{ + g_return_if_fail (GEDIT_IS_WINDOW (window)); + + gedit_multi_notebook_set_active_tab (window->priv->multi_notebook, + tab); +} + +/** + * gedit_window_get_group: + * @window: a #GeditWindow + * + * Gets the #GtkWindowGroup in which @window resides. + * + * Returns: (transfer none): the #GtkWindowGroup + */ +GtkWindowGroup * +gedit_window_get_group (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return window->priv->window_group; +} + +gboolean +_gedit_window_is_removing_tabs (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), FALSE); + + return window->priv->removing_tabs; +} + +/** + * gedit_window_get_side_panel: + * @window: a #GeditWindow + * + * Gets the side panel of the @window. + * + * Returns: (transfer none): the side panel's #GtkStack. + */ +GtkWidget * +gedit_window_get_side_panel (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return window->priv->side_panel; +} + +/** + * gedit_window_get_bottom_panel: + * @window: a #GeditWindow + * + * Gets the bottom panel of the @window. + * + * Returns: (transfer none): the bottom panel's #GtkStack. + */ +GtkWidget * +gedit_window_get_bottom_panel (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return window->priv->bottom_panel; +} + +/** + * gedit_window_get_statusbar: + * @window: a #GeditWindow + * + * Gets the #GeditStatusbar of the @window. + * + * Returns: (transfer none): the #GeditStatusbar of the @window. + */ +GtkWidget * +gedit_window_get_statusbar (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return window->priv->statusbar; +} + +/** + * gedit_window_get_state: + * @window: a #GeditWindow + * + * Retrieves the state of the @window. + * + * Returns: the current #GeditWindowState of the @window. + */ +GeditWindowState +gedit_window_get_state (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), GEDIT_WINDOW_STATE_NORMAL); + + return window->priv->state; +} + +const gchar * +_gedit_window_get_file_chooser_folder_uri (GeditWindow *window, + GtkFileChooserAction action) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + g_return_val_if_fail ((action == GTK_FILE_CHOOSER_ACTION_OPEN) || + (action == GTK_FILE_CHOOSER_ACTION_SAVE), NULL); + + if (action == GTK_FILE_CHOOSER_ACTION_OPEN) + { + GeditSettings *settings; + GSettings *file_chooser_state_settings; + + settings = _gedit_settings_get_singleton (); + file_chooser_state_settings = _gedit_settings_peek_file_chooser_state_settings (settings); + + if (g_settings_get_boolean (file_chooser_state_settings, + GEDIT_SETTINGS_FILE_CHOOSER_OPEN_RECENT)) + { + return NULL; + } + } + + return window->priv->file_chooser_folder_uri; +} + +void +_gedit_window_set_file_chooser_folder_uri (GeditWindow *window, + GtkFileChooserAction action, + const gchar *folder_uri) +{ + g_return_if_fail (GEDIT_IS_WINDOW (window)); + g_return_if_fail ((action == GTK_FILE_CHOOSER_ACTION_OPEN) || + (action == GTK_FILE_CHOOSER_ACTION_SAVE)); + + if (action == GTK_FILE_CHOOSER_ACTION_OPEN) + { + GeditSettings *settings; + GSettings *file_chooser_state_settings; + gboolean open_recent = folder_uri == NULL; + + settings = _gedit_settings_get_singleton (); + file_chooser_state_settings = _gedit_settings_peek_file_chooser_state_settings (settings); + + g_settings_set_boolean (file_chooser_state_settings, + GEDIT_SETTINGS_FILE_CHOOSER_OPEN_RECENT, + open_recent); + + if (open_recent) + { + /* Do not set window->priv->file_chooser_folder_uri to + * NULL, to not lose the folder for the Save action. + */ + return; + } + } + + g_free (window->priv->file_chooser_folder_uri); + window->priv->file_chooser_folder_uri = g_strdup (folder_uri); +} + +static void +add_unsaved_doc (GeditTab *tab, + GList **res) +{ + if (!_gedit_tab_get_can_close (tab)) + { + GeditDocument *doc; + + doc = gedit_tab_get_document (tab); + *res = g_list_prepend (*res, doc); + } +} + +/** + * gedit_window_get_unsaved_documents: + * @window: a #GeditWindow + * + * Gets the list of documents that need to be saved before closing the window. + * + * Returns: (element-type Gedit.Document) (transfer container): a list of + * #GeditDocument that need to be saved before closing the window + */ +GList * +gedit_window_get_unsaved_documents (GeditWindow *window) +{ + GList *res = NULL; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + gedit_multi_notebook_foreach_tab (window->priv->multi_notebook, + (GtkCallback)add_unsaved_doc, + &res); + + return g_list_reverse (res); +} + +GList * +_gedit_window_get_all_tabs (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return gedit_multi_notebook_get_all_tabs (window->priv->multi_notebook); +} + +void +_gedit_window_fullscreen (GeditWindow *window) +{ + g_return_if_fail (GEDIT_IS_WINDOW (window)); + + if (_gedit_window_is_fullscreen (window)) + return; + + sync_fullscreen_actions (window, TRUE); + + /* Go to fullscreen mode and hide bars */ + gtk_window_fullscreen (GTK_WINDOW (&window->window)); +} + +void +_gedit_window_unfullscreen (GeditWindow *window) +{ + g_return_if_fail (GEDIT_IS_WINDOW (window)); + + if (!_gedit_window_is_fullscreen (window)) + return; + + sync_fullscreen_actions (window, FALSE); + + /* Unfullscreen and show bars */ + gtk_window_unfullscreen (GTK_WINDOW (&window->window)); +} + +gboolean +_gedit_window_is_fullscreen (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), FALSE); + + return window->priv->window_state & GDK_WINDOW_STATE_FULLSCREEN; +} + +/** + * gedit_window_get_tab_from_location: + * @window: a #GeditWindow + * @location: a #GFile + * + * Gets the #GeditTab that matches with the given @location. + * + * Returns: (transfer none): the #GeditTab that matches with the given @location. + */ +GeditTab * +gedit_window_get_tab_from_location (GeditWindow *window, + GFile *location) +{ + GList *tabs; + GList *l; + GeditTab *ret = NULL; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + g_return_val_if_fail (G_IS_FILE (location), NULL); + + tabs = gedit_multi_notebook_get_all_tabs (window->priv->multi_notebook); + + for (l = tabs; l != NULL; l = g_list_next (l)) + { + GeditDocument *doc; + GtkSourceFile *file; + GeditTab *tab; + GFile *cur_location; + + tab = GEDIT_TAB (l->data); + doc = gedit_tab_get_document (tab); + file = gedit_document_get_file (doc); + cur_location = gtk_source_file_get_location (file); + + if (cur_location != NULL) + { + gboolean found = g_file_equal (location, cur_location); + + if (found) + { + ret = tab; + break; + } + } + } + + g_list_free (tabs); + + return ret; +} + +/** + * gedit_window_get_message_bus: + * @window: a #GeditWindow + * + * Gets the #GeditMessageBus associated with @window. The returned reference + * is owned by the window and should not be unreffed. + * + * Return value: (transfer none): the #GeditMessageBus associated with @window + */ +GeditMessageBus * +gedit_window_get_message_bus (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return window->priv->message_bus; +} + +/* ex:set ts=8 noet: */ |