summaryrefslogtreecommitdiffstats
path: root/gedit
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 17:42:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 17:42:51 +0000
commitba429d344132c088177e853cce8ff7181570b221 (patch)
tree87ebf15269b4301737abd1735baabba71be93622 /gedit
parentInitial commit. (diff)
downloadgedit-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')
-rw-r--r--gedit/Gedit-3.0.metadata23
-rw-r--r--gedit/Gedit.py99
-rw-r--r--gedit/gedit-app-activatable.c117
-rw-r--r--gedit/gedit-app-activatable.h64
-rw-r--r--gedit/gedit-app-osx.h41
-rw-r--r--gedit/gedit-app-osx.m603
-rw-r--r--gedit/gedit-app-private.h50
-rw-r--r--gedit/gedit-app-win32.c138
-rw-r--r--gedit/gedit-app-win32.h39
-rw-r--r--gedit/gedit-app.c1700
-rw-r--r--gedit/gedit-app.h82
-rw-r--r--gedit/gedit-close-confirmation-dialog.c548
-rw-r--r--gedit/gedit-close-confirmation-dialog.h46
-rw-r--r--gedit/gedit-commands-documents.c106
-rw-r--r--gedit/gedit-commands-edit.c205
-rw-r--r--gedit/gedit-commands-file-print.c49
-rw-r--r--gedit/gedit-commands-file.c2169
-rw-r--r--gedit/gedit-commands-help.c132
-rw-r--r--gedit/gedit-commands-private.h172
-rw-r--r--gedit/gedit-commands-search.c696
-rw-r--r--gedit/gedit-commands-view.c181
-rw-r--r--gedit/gedit-commands.h63
-rw-r--r--gedit/gedit-debug.c232
-rw-r--r--gedit/gedit-debug.h83
-rw-r--r--gedit/gedit-dirs.c176
-rw-r--r--gedit/gedit-dirs.h55
-rw-r--r--gedit/gedit-document-private.h57
-rw-r--r--gedit/gedit-document.c1276
-rw-r--r--gedit/gedit-document.h77
-rw-r--r--gedit/gedit-documents-panel.c1740
-rw-r--r--gedit/gedit-documents-panel.h40
-rw-r--r--gedit/gedit-encoding-items.c108
-rw-r--r--gedit/gedit-encoding-items.h40
-rw-r--r--gedit/gedit-encodings-combo-box.c439
-rw-r--r--gedit/gedit-encodings-combo-box.h43
-rw-r--r--gedit/gedit-encodings-dialog.c884
-rw-r--r--gedit/gedit-encodings-dialog.h37
-rw-r--r--gedit/gedit-factory.c69
-rw-r--r--gedit/gedit-factory.h53
-rw-r--r--gedit/gedit-file-chooser-dialog-gtk.c465
-rw-r--r--gedit/gedit-file-chooser-dialog-gtk.h45
-rw-r--r--gedit/gedit-file-chooser-dialog.c258
-rw-r--r--gedit/gedit-file-chooser-dialog.h123
-rw-r--r--gedit/gedit-file-chooser-open-dialog.c125
-rw-r--r--gedit/gedit-file-chooser-open-dialog.h58
-rw-r--r--gedit/gedit-file-chooser-open-native.c84
-rw-r--r--gedit/gedit-file-chooser-open-native.h58
-rw-r--r--gedit/gedit-file-chooser-open.c77
-rw-r--r--gedit/gedit-file-chooser-open.h58
-rw-r--r--gedit/gedit-file-chooser.c785
-rw-r--r--gedit/gedit-file-chooser.h91
-rw-r--r--gedit/gedit-history-entry.c470
-rw-r--r--gedit/gedit-history-entry.h55
-rw-r--r--gedit/gedit-io-error-info-bar.c542
-rw-r--r--gedit/gedit-io-error-info-bar.h47
-rw-r--r--gedit/gedit-menu-extension.c187
-rw-r--r--gedit/gedit-menu-extension.h47
-rw-r--r--gedit/gedit-menu-stack-switcher.c420
-rw-r--r--gedit/gedit-menu-stack-switcher.h43
-rw-r--r--gedit/gedit-message-bus.c1259
-rw-r--r--gedit/gedit-message-bus.h154
-rw-r--r--gedit/gedit-message.c321
-rw-r--r--gedit/gedit-message.h77
-rw-r--r--gedit/gedit-multi-notebook.c1125
-rw-r--r--gedit/gedit-multi-notebook.h148
-rw-r--r--gedit/gedit-notebook-popup-menu.c297
-rw-r--r--gedit/gedit-notebook-popup-menu.h39
-rw-r--r--gedit/gedit-notebook-stack-switcher.c362
-rw-r--r--gedit/gedit-notebook-stack-switcher.h71
-rw-r--r--gedit/gedit-notebook.c672
-rw-r--r--gedit/gedit-notebook.h100
-rw-r--r--gedit/gedit-plugins-engine.c136
-rw-r--r--gedit/gedit-plugins-engine.h39
-rw-r--r--gedit/gedit-preferences-dialog.c841
-rw-r--r--gedit/gedit-preferences-dialog.h35
-rw-r--r--gedit/gedit-print-job.c819
-rw-r--r--gedit/gedit-print-job.h71
-rw-r--r--gedit/gedit-print-preview.c1158
-rw-r--r--gedit/gedit-print-preview.h39
-rw-r--r--gedit/gedit-recent-osx.c250
-rw-r--r--gedit/gedit-recent-osx.h54
-rw-r--r--gedit/gedit-recent.c113
-rw-r--r--gedit/gedit-recent.h38
-rw-r--r--gedit/gedit-replace-dialog.c819
-rw-r--r--gedit/gedit-replace-dialog.h58
-rw-r--r--gedit/gedit-settings.c322
-rw-r--r--gedit/gedit-settings.h106
-rw-r--r--gedit/gedit-status-menu-button.c146
-rw-r--r--gedit/gedit-status-menu-button.h44
-rw-r--r--gedit/gedit-statusbar.c241
-rw-r--r--gedit/gedit-statusbar.h53
-rw-r--r--gedit/gedit-tab-label.c295
-rw-r--r--gedit/gedit-tab-label.h41
-rw-r--r--gedit/gedit-tab-private.h81
-rw-r--r--gedit/gedit-tab.c3057
-rw-r--r--gedit/gedit-tab.h77
-rw-r--r--gedit/gedit-utils.c542
-rw-r--r--gedit/gedit-utils.h56
-rw-r--r--gedit/gedit-view-activatable.c96
-rw-r--r--gedit/gedit-view-activatable.h47
-rw-r--r--gedit/gedit-view-frame.c1592
-rw-r--r--gedit/gedit-view-frame.h45
-rw-r--r--gedit/gedit-view.c605
-rw-r--r--gedit/gedit-view.h67
-rw-r--r--gedit/gedit-window-activatable.c120
-rw-r--r--gedit/gedit-window-activatable.h49
-rw-r--r--gedit/gedit-window.c3561
-rw-r--r--gedit/gedit-window.h178
-rw-r--r--gedit/gedit.c203
-rw-r--r--gedit/meson.build263
-rw-r--r--gedit/resources/css/gedit-style-osx.css129
-rw-r--r--gedit/resources/css/gedit-style.css31
-rw-r--r--gedit/resources/css/gedit.adwaita.css61
-rw-r--r--gedit/resources/css/gedit.highcontrast.css5
-rw-r--r--gedit/resources/gedit.gresource.xml.in23
-rw-r--r--gedit/resources/gtk/menus-common.ui376
-rw-r--r--gedit/resources/gtk/menus-traditional.ui143
-rw-r--r--gedit/resources/gtk/menus.ui132
-rw-r--r--gedit/resources/meson.build21
-rw-r--r--gedit/resources/ui/gedit-encodings-dialog.ui278
-rw-r--r--gedit/resources/ui/gedit-preferences-dialog.ui675
-rw-r--r--gedit/resources/ui/gedit-print-preferences.ui518
-rw-r--r--gedit/resources/ui/gedit-print-preview.ui417
-rw-r--r--gedit/resources/ui/gedit-replace-dialog.ui246
-rw-r--r--gedit/resources/ui/gedit-shortcuts.ui487
-rw-r--r--gedit/resources/ui/gedit-status-menu-button.ui51
-rw-r--r--gedit/resources/ui/gedit-statusbar.ui90
-rw-r--r--gedit/resources/ui/gedit-tab-label.ui50
-rw-r--r--gedit/resources/ui/gedit-view-frame.ui89
-rw-r--r--gedit/resources/ui/gedit-window.ui404
130 files changed, 41978 insertions, 0 deletions
diff --git a/gedit/Gedit-3.0.metadata b/gedit/Gedit-3.0.metadata
new file mode 100644
index 0000000..e36d7cb
--- /dev/null
+++ b/gedit/Gedit-3.0.metadata
@@ -0,0 +1,23 @@
+App cheader_filename="gedit/gedit-app.h"
+AppActivatable cheader_filename="gedit/gedit-app-activatable.h"
+DebugSection cheader_filename="gedit/gedit-debug.h"
+Document cheader_filename="gedit/gedit-document.h"
+EncodingsComboBox cheader_filename="gedit/gedit-encodings-combo-box.h"
+MenuExtension cheader_filename="gedit/gedit-menu-extension.h"
+Message cheader_filename="gedit/gedit-message.h"
+MessageBus cheader_filename="gedit/gedit-message-bus.h"
+Statusbar cheader_filename="gedit/gedit-statusbar.h"
+Tab cheader_filename="gedit/gedit-tab.h"
+TabState cheader_filename="gedit/gedit-tab.h"
+View cheader_filename="gedit/gedit-view.h"
+ViewActivatable cheader_filename="gedit/gedit-view-activatable.h"
+Window cheader_filename="gedit/gedit-window.h"
+WindowActivatable cheader_filename="gedit/gedit-window-activatable.h"
+WindowState cheader_filename="gedit/gedit-window.h"
+
+commands_* cheader_filename="gedit/gedit-commands.h"
+debug* cheader_filename="gedit/gedit-debug.h"
+utils_* cheader_filename="gedit/gedit-utils.h"
+
+MessageBusForeach cheader_filename="gedit/gedit-message-bus.h"
+MessageCallback cheader_filename="gedit/gedit-message-bus.h"
diff --git a/gedit/Gedit.py b/gedit/Gedit.py
new file mode 100644
index 0000000..c3a1f43
--- /dev/null
+++ b/gedit/Gedit.py
@@ -0,0 +1,99 @@
+# Copyright (C) 2011 Jesse van den Kieboom <jesse@icecrew.nl>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gi.repository import GObject
+import inspect
+
+from ..overrides import override
+from ..importer import modules
+
+Gedit = modules['Gedit']._introspection_module
+__all__ = []
+
+class MessageBus(Gedit.MessageBus):
+ def create(self, object_path, method, **kwargs):
+ tp = self.lookup(object_path, method)
+
+ if not tp.is_a(Gedit.Message.__gtype__):
+ return None
+
+ kwargs['object-path'] = object_path
+ kwargs['method'] = method
+
+ return GObject.new(tp, **kwargs)
+
+ def send_sync(self, object_path, method, **kwargs):
+ msg = self.create(object_path, method, **kwargs)
+ self.send_message_sync(msg)
+
+ return msg
+
+ def send(self, object_path, method, **kwargs):
+ msg = self.create(object_path, method, **kwargs)
+ self.send_message(msg)
+
+ return msg
+
+MessageBus = override(MessageBus)
+__all__.append('MessageBus')
+
+class Message(Gedit.Message):
+ def __getattribute__(self, name):
+ try:
+ return Gedit.Message.__getattribute__(self, name)
+ except:
+ return getattr(self.props, name)
+
+Message = override(Message)
+__all__.append('Message')
+
+
+def get_trace_info(num_back_frames=0):
+ frame = inspect.currentframe().f_back
+ try:
+ for i in range(num_back_frames):
+ back_frame = frame.f_back
+ if back_frame is None:
+ break
+ frame = back_frame
+
+ filename = frame.f_code.co_filename
+
+ # http://code.activestate.com/recipes/145297-grabbing-the-current-line-number-easily/
+ lineno = frame.f_lineno
+
+ func_name = frame.f_code.co_name
+ try:
+ # http://stackoverflow.com/questions/2203424/python-how-to-retrieve-class-information-from-a-frame-object
+ cls_name = frame.f_locals["self"].__class__.__name__
+ except:
+ pass
+ else:
+ func_name = "%s.%s" % (cls_name, func_name)
+
+ return (filename, lineno, func_name)
+ finally:
+ frame = None
+
+orig_debug_plugin_message_func = Gedit.debug_plugin_message
+
+@override(Gedit.debug_plugin_message)
+def debug_plugin_message(format, *format_args):
+ filename, lineno, func_name = get_trace_info(1)
+ orig_debug_plugin_message_func(filename, lineno, func_name, format % format_args)
+__all__.append(debug_plugin_message)
+
+# vi:ex:ts=4:et
diff --git a/gedit/gedit-app-activatable.c b/gedit/gedit-app-activatable.c
new file mode 100644
index 0000000..f5dbd35
--- /dev/null
+++ b/gedit/gedit-app-activatable.c
@@ -0,0 +1,117 @@
+/*
+ * gedit-app-activatable.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 Steve Frécinaux
+ * Copyright (C) 2010 Jesse van den Kieboom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gedit-app-activatable.h"
+#include "gedit-app.h"
+#include "gedit-app-private.h"
+
+/**
+ * SECTION:gedit-app-activatable
+ * @short_description: Interface for activatable extensions on apps
+ * @see_also: #PeasExtensionSet
+ *
+ * #GeditAppActivatable is an interface which should be implemented by
+ * extensions that should be activated on a gedit application.
+ **/
+
+G_DEFINE_INTERFACE(GeditAppActivatable, gedit_app_activatable, G_TYPE_OBJECT)
+
+static void
+gedit_app_activatable_default_init (GeditAppActivatableInterface *iface)
+{
+ /**
+ * GeditAppActivatable:app:
+ *
+ * The app property contains the gedit app for this
+ * #GeditAppActivatable instance.
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_object ("app",
+ "App",
+ "The gedit app",
+ GEDIT_TYPE_APP,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+}
+
+/**
+ * gedit_app_activatable_activate:
+ * @activatable: A #GeditAppActivatable.
+ *
+ * Activates the extension on the application.
+ */
+void
+gedit_app_activatable_activate (GeditAppActivatable *activatable)
+{
+ GeditAppActivatableInterface *iface;
+
+ g_return_if_fail (GEDIT_IS_APP_ACTIVATABLE (activatable));
+
+ iface = GEDIT_APP_ACTIVATABLE_GET_IFACE (activatable);
+
+ if (iface->activate != NULL)
+ {
+ iface->activate (activatable);
+ }
+}
+
+/**
+ * gedit_app_activatable_deactivate:
+ * @activatable: A #GeditAppActivatable.
+ *
+ * Deactivates the extension from the application.
+ *
+ */
+void
+gedit_app_activatable_deactivate (GeditAppActivatable *activatable)
+{
+ GeditAppActivatableInterface *iface;
+
+ g_return_if_fail (GEDIT_IS_APP_ACTIVATABLE (activatable));
+
+ iface = GEDIT_APP_ACTIVATABLE_GET_IFACE (activatable);
+
+ if (iface->deactivate != NULL)
+ {
+ iface->deactivate (activatable);
+ }
+}
+
+GeditMenuExtension *
+gedit_app_activatable_extend_menu (GeditAppActivatable *activatable,
+ const gchar *extension_point)
+{
+ GeditApp *app;
+ GeditMenuExtension *ext;
+
+ g_return_val_if_fail (GEDIT_IS_APP_ACTIVATABLE (activatable), NULL);
+
+ g_object_get (G_OBJECT (activatable), "app", &app, NULL);
+ ext = _gedit_app_extend_menu (app, extension_point);
+ g_object_unref (app);
+
+ return ext;
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-app-activatable.h b/gedit/gedit-app-activatable.h
new file mode 100644
index 0000000..f3f6606
--- /dev/null
+++ b/gedit/gedit-app-activatable.h
@@ -0,0 +1,64 @@
+/*
+ * gedit-app-activatable.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Steve Frécinaux
+ * Copyright (C) 2010 - Jesse van den Kieboom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_APP_ACTIVATABLE_H
+#define GEDIT_APP_ACTIVATABLE_H
+
+#include <glib-object.h>
+#include <gedit/gedit-menu-extension.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_APP_ACTIVATABLE (gedit_app_activatable_get_type ())
+
+G_DECLARE_INTERFACE (GeditAppActivatable, gedit_app_activatable, GEDIT, APP_ACTIVATABLE, GObject)
+
+struct _GeditAppActivatableInterface
+{
+ GTypeInterface g_iface;
+
+ /* Virtual public methods */
+ void (*activate) (GeditAppActivatable *activatable);
+ void (*deactivate) (GeditAppActivatable *activatable);
+};
+
+void gedit_app_activatable_activate (GeditAppActivatable *activatable);
+void gedit_app_activatable_deactivate (GeditAppActivatable *activatable);
+
+/**
+ * gedit_app_activatable_extend_menu:
+ * @activatable: A #GeditAppActivatable.
+ * @extension_point: the extension point section of the menu to get.
+ *
+ * Gets the #GeditMenuExtension for the menu @extension_point. Note that
+ * the extension point could be in different menus (gear menu, app menu, etc)
+ * depending on the platform.
+ *
+ * Returns: (transfer full): a #GeditMenuExtension for the specific section
+ * or %NULL if not found.
+ */
+GeditMenuExtension *gedit_app_activatable_extend_menu (GeditAppActivatable *activatable,
+ const gchar *extension_point);
+
+G_END_DECLS
+
+#endif /* GEDIT_APP_ACTIVATABLE_H */
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-app-osx.h b/gedit/gedit-app-osx.h
new file mode 100644
index 0000000..a333f61
--- /dev/null
+++ b/gedit/gedit-app-osx.h
@@ -0,0 +1,41 @@
+/*
+ * gedit-app-osx.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Jesse van den Kieboom
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef GEDIT_APP_OSX_H
+#define GEDIT_APP_OSX_H
+
+#include "gedit-app.h"
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_APP_OSX (gedit_app_osx_get_type ())
+
+G_DECLARE_FINAL_TYPE (GeditAppOSX, gedit_app_osx, GEDIT, APP_OSX, GeditApp)
+
+gboolean gedit_app_osx_show_url (GeditAppOSX *app,
+ const gchar *url);
+
+G_END_DECLS
+
+#endif /* GEDIT_APP_OSX_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-app-osx.m b/gedit/gedit-app-osx.m
new file mode 100644
index 0000000..fd1675a
--- /dev/null
+++ b/gedit/gedit-app-osx.m
@@ -0,0 +1,603 @@
+/*
+ * gedit-app-osx.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Jesse van den Kieboom
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "gedit-app-osx.h"
+
+#include <gdk/gdkquartz.h>
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include "gedit-app-private.h"
+#include "gedit-dirs.h"
+#include "gedit-debug.h"
+#include "gedit-commands.h"
+#include "gedit-commands-private.h"
+#include "gedit-document-private.h"
+#include "gedit-recent-osx.h"
+#import <AppKit/AppKit.h>
+
+NSWindow *gdk_quartz_window_get_nswindow(GdkWindow *window);
+NSEvent *gdk_quartz_event_get_nsevent(GdkEvent *event);
+
+static GeditWindow *
+ensure_window (GeditAppOSX *app,
+ gboolean with_empty_document)
+{
+ GList *windows;
+ GeditWindow *ret = NULL;
+
+ windows = gtk_application_get_windows (GTK_APPLICATION (app));
+
+ while (windows)
+ {
+ GtkWindow *window;
+ GdkWindow *win;
+ NSWindow *nswin;
+
+ window = windows->data;
+ windows = g_list_next (windows);
+
+ if (!gtk_widget_get_realized (GTK_WIDGET (window)))
+ {
+ continue;
+ }
+
+ if (!GEDIT_IS_WINDOW (window))
+ {
+ continue;
+ }
+
+ win = gtk_widget_get_window (GTK_WIDGET (window));
+ nswin = gdk_quartz_window_get_nswindow (win);
+
+ if ([nswin isOnActiveSpace])
+ {
+ ret = GEDIT_WINDOW (window);
+ break;
+ }
+ }
+
+ if (!ret)
+ {
+ ret = gedit_app_create_window (GEDIT_APP (app), NULL);
+ gtk_widget_show (GTK_WIDGET (ret));
+ }
+
+ if (with_empty_document && gedit_window_get_active_document (ret) == NULL)
+ {
+ gedit_window_create_tab (ret, TRUE);
+ }
+
+ gtk_window_present (GTK_WINDOW (ret));
+ return ret;
+}
+
+@interface GeditAppOSXDelegate : NSObject
+{
+ GeditAppOSX *app;
+ id<NSApplicationDelegate> orig;
+}
+
+- (id)initWithApp:(GeditAppOSX *)theApp;
+- (void)release;
+
+- (id)forwardingTargetForSelector:(SEL)aSelector;
+- (BOOL)respondsToSelector:(SEL)aSelector;
+
+- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag;
+- (void)applicationWillBecomeActive:(NSNotification *)aNotification;
+- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames;
+
+@end
+
+@implementation GeditAppOSXDelegate
+- (id)initWithApp:(GeditAppOSX *)theApp
+{
+ [super init];
+ app = theApp;
+
+ orig = [NSApp delegate];
+ [NSApp setDelegate:self];
+
+ return self;
+}
+
+- (void)release
+{
+ [NSApp setDelegate:orig];
+ [super release];
+}
+
+- (id)forwardingTargetForSelector:(SEL)aSelector
+{
+ return orig;
+}
+
+- (BOOL)respondsToSelector:(SEL)aSelector
+{
+ return [super respondsToSelector:aSelector] || [orig respondsToSelector:aSelector];
+}
+
+- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag
+{
+ ensure_window (app, TRUE);
+ return NO;
+}
+
+- (void)applicationWillBecomeActive:(NSNotification *)aNotification
+{
+ ensure_window (app, TRUE);
+}
+
+- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
+{
+ ensure_window (app, FALSE);
+ [orig application:sender openFiles:filenames];
+}
+
+@end
+
+struct _GeditAppOSX
+{
+ GeditApp parent_instance;
+
+ GeditMenuExtension *recent_files_menu;
+ gulong recent_manager_changed_id;
+
+ GeditAppOSXDelegate *app_delegate;
+
+ GList *recent_actions;
+ GeditRecentConfiguration recent_config;
+};
+
+G_DEFINE_TYPE (GeditAppOSX, gedit_app_osx, GEDIT_TYPE_APP)
+
+static void
+remove_recent_actions (GeditAppOSX *app)
+{
+ while (app->recent_actions)
+ {
+ gchar *action_name = app->recent_actions->data;
+
+ g_action_map_remove_action (G_ACTION_MAP (app), action_name);
+ g_free (action_name);
+
+ app->recent_actions = g_list_delete_link (app->recent_actions,
+ app->recent_actions);
+ }
+}
+
+static void
+gedit_app_osx_finalize (GObject *object)
+{
+ GeditAppOSX *app = GEDIT_APP_OSX (object);
+
+ g_object_unref (app->recent_files_menu);
+
+ remove_recent_actions (app);
+
+ g_signal_handler_disconnect (app->recent_config.manager,
+ app->recent_manager_changed_id);
+
+ gedit_recent_configuration_destroy (&app->recent_config);
+
+ [app->app_delegate release];
+
+ G_OBJECT_CLASS (gedit_app_osx_parent_class)->finalize (object);
+}
+
+gboolean
+gedit_app_osx_show_url (GeditAppOSX *app,
+ const gchar *url)
+{
+ return [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithUTF8String:url]]];
+}
+
+static gboolean
+gedit_app_osx_show_help_impl (GeditApp *app,
+ GtkWindow *parent,
+ const gchar *name,
+ const gchar *link_id)
+{
+ gboolean ret = FALSE;
+
+ if (name == NULL || g_strcmp0 (name, "gedit") == 0)
+ {
+ gchar *link;
+
+ if (link_id)
+ {
+ link = g_strdup_printf ("https://gedit-technology.net/user-manuals/gedit/%s",
+ link_id);
+ }
+ else
+ {
+ link = g_strdup ("https://gedit-technology.net/user-manuals/gedit/");
+ }
+
+ ret = gedit_app_osx_show_url (GEDIT_APP_OSX (app), link);
+ g_free (link);
+ }
+
+ return ret;
+}
+
+static void
+gedit_app_osx_set_window_title_impl (GeditApp *app,
+ GeditWindow *window,
+ const gchar *title)
+{
+ NSWindow *native;
+ GeditDocument *document;
+ GdkWindow *wnd;
+
+ g_return_if_fail (GEDIT_IS_WINDOW (window));
+
+ wnd = gtk_widget_get_window (GTK_WIDGET (window));
+
+ if (wnd == NULL)
+ {
+ return;
+ }
+
+ native = gdk_quartz_window_get_nswindow (wnd);
+ document = gedit_window_get_active_document (window);
+
+ if (document)
+ {
+ bool ismodified;
+
+ if (_gedit_document_is_untitled (document))
+ {
+ [native setRepresentedURL:nil];
+ }
+ else
+ {
+ GtkSourceFile *file;
+ GFile *location;
+ gchar *uri;
+
+ file = gedit_document_get_file (document);
+ location = gtk_source_file_get_location (file);
+
+ uri = g_file_get_uri (location);
+
+ NSURL *nsurl = [NSURL URLWithString:[NSString stringWithUTF8String:uri]];
+
+ [native setRepresentedURL:nsurl];
+ g_free (uri);
+ }
+
+ ismodified = !tepl_buffer_is_untouched (TEPL_BUFFER (document));
+ [native setDocumentEdited:ismodified];
+ }
+ else
+ {
+ [native setRepresentedURL:nil];
+ [native setDocumentEdited:false];
+ }
+
+ GEDIT_APP_CLASS (gedit_app_osx_parent_class)->set_window_title (app, window, title);
+}
+
+typedef struct
+{
+ GeditAppOSX *app;
+ GtkRecentInfo *info;
+} RecentFileInfo;
+
+static void
+recent_file_info_free (gpointer data,
+ GClosure *closure)
+{
+ RecentFileInfo *info = data;
+
+ g_object_unref (info->app);
+ gtk_recent_info_unref (info->info);
+
+ g_slice_free (RecentFileInfo, data);
+}
+
+static void
+recent_file_activated (GAction *action,
+ GVariant *parameter,
+ RecentFileInfo *info)
+{
+ GeditWindow *window;
+ const gchar *uri;
+ GFile *file;
+
+ uri = gtk_recent_info_get_uri (info->info);
+ file = g_file_new_for_uri (uri);
+
+ window = ensure_window (info->app, FALSE);
+
+ gedit_commands_load_location (GEDIT_WINDOW (window), file, NULL, 0, 0);
+ g_object_unref (file);
+}
+
+static void
+recent_files_menu_populate (GeditAppOSX *app)
+{
+ GList *items;
+ gint i = 0;
+
+ gedit_menu_extension_remove_items (app->recent_files_menu);
+ remove_recent_actions (app);
+
+ items = gedit_recent_get_items (&app->recent_config);
+
+ while (items)
+ {
+ GtkRecentInfo *info = items->data;
+ GMenuItem *mitem;
+ const gchar *name;
+ gchar *acname;
+ gchar *acfullname;
+ GSimpleAction *action;
+ RecentFileInfo *finfo;
+
+ name = gtk_recent_info_get_display_name (info);
+
+ acname = g_strdup_printf ("recent-file-action-%d", ++i);
+ action = g_simple_action_new (acname, NULL);
+
+ finfo = g_slice_new (RecentFileInfo);
+ finfo->app = g_object_ref (app);
+ finfo->info = gtk_recent_info_ref (info);
+
+ g_signal_connect_data (action,
+ "activate",
+ G_CALLBACK (recent_file_activated),
+ finfo,
+ recent_file_info_free,
+ 0);
+
+ g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (action));
+ g_object_unref (action);
+
+ acfullname = g_strdup_printf ("app.%s", acname);
+
+ app->recent_actions = g_list_prepend (app->recent_actions, acname);
+
+ mitem = g_menu_item_new (name, acfullname);
+ gedit_menu_extension_append_menu_item (app->recent_files_menu, mitem);
+
+ g_free (acfullname);
+
+ g_object_unref (mitem);
+ gtk_recent_info_unref (info);
+
+ items = g_list_delete_link (items, items);
+ }
+}
+
+static void
+recent_manager_changed (GtkRecentManager *manager,
+ GeditAppOSX *app)
+{
+ recent_files_menu_populate (app);
+}
+
+static void
+open_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer userdata)
+{
+ _gedit_cmd_file_open (NULL, NULL, NULL);
+}
+
+static GActionEntry app_entries[] = {
+ { "open", open_activated, NULL, NULL, NULL }
+};
+
+static void
+update_open_sensitivity (GeditAppOSX *app)
+{
+ GAction *action;
+ gboolean has_windows;
+
+ has_windows = (gtk_application_get_windows (GTK_APPLICATION (app)) != NULL);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (app), "open");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !has_windows);
+}
+
+static void
+gedit_app_osx_startup (GApplication *application)
+{
+ const gchar *replace_accels[] = {
+ "<Primary><Alt>F",
+ NULL
+ };
+
+ const gchar *open_accels[] = {
+ "<Primary>O",
+ NULL
+ };
+
+ const gchar *fullscreen_accels[] = {
+ "<Primary><Control>F",
+ NULL
+ };
+
+ GeditAppOSX *app = GEDIT_APP_OSX (application);
+
+ G_APPLICATION_CLASS (gedit_app_osx_parent_class)->startup (application);
+
+ app->app_delegate = [[[GeditAppOSXDelegate alloc] initWithApp:app] retain];
+
+ g_action_map_add_action_entries (G_ACTION_MAP (application),
+ app_entries,
+ G_N_ELEMENTS (app_entries),
+ application);
+
+ gtk_application_set_accels_for_action (GTK_APPLICATION (application),
+ "win.replace",
+ replace_accels);
+
+ gtk_application_set_accels_for_action (GTK_APPLICATION (application),
+ "app.open",
+ open_accels);
+
+ gtk_application_set_accels_for_action (GTK_APPLICATION (application),
+ "win.fullscreen",
+ fullscreen_accels);
+
+ gedit_recent_configuration_init_default (&app->recent_config);
+
+ app->recent_files_menu = _gedit_app_extend_menu (GEDIT_APP (application),
+ "recent-files-section");
+
+ app->recent_manager_changed_id = g_signal_connect (app->recent_config.manager,
+ "changed",
+ G_CALLBACK (recent_manager_changed),
+ app);
+
+ recent_files_menu_populate (app);
+
+ g_application_hold (application);
+ update_open_sensitivity (app);
+}
+
+static void
+set_window_allow_fullscreen (GeditWindow *window)
+{
+ GdkWindow *wnd;
+ NSWindow *native;
+
+ wnd = gtk_widget_get_window (GTK_WIDGET (window));
+
+ if (wnd != NULL)
+ {
+ native = gdk_quartz_window_get_nswindow (wnd);
+ [native setCollectionBehavior: [native collectionBehavior] | NSWindowCollectionBehaviorFullScreenPrimary];
+ }
+}
+
+static void
+on_window_realized (GtkWidget *widget)
+{
+ set_window_allow_fullscreen (GEDIT_WINDOW (widget));
+}
+
+static GeditWindow *
+gedit_app_osx_create_window_impl (GeditApp *app)
+{
+ GeditWindow *window;
+
+ window = GEDIT_APP_CLASS (gedit_app_osx_parent_class)->create_window (app);
+
+ gtk_window_set_titlebar (GTK_WINDOW (window), NULL);
+
+ if (gtk_widget_get_realized (GTK_WIDGET (window)))
+ {
+ set_window_allow_fullscreen (window);
+ }
+ else
+ {
+ g_signal_connect (window, "realize", G_CALLBACK (on_window_realized), NULL);
+ }
+
+ return window;
+}
+
+static gboolean
+gedit_app_osx_process_window_event_impl (GeditApp *app,
+ GeditWindow *window,
+ GdkEvent *event)
+{
+ NSEvent *nsevent;
+
+ /* For OS X we will propagate the event to NSApp, which handles some OS X
+ * specific keybindings and the accelerators for the menu
+ */
+ nsevent = gdk_quartz_event_get_nsevent (event);
+ [NSApp sendEvent:nsevent];
+
+ /* It does not really matter what we return here since it's the last thing
+ * in the chain. Also we can't get from sendEvent whether the event was
+ * actually handled by NSApp anyway
+ */
+ return TRUE;
+}
+
+static void
+gedit_app_osx_constructed (GObject *object)
+{
+ /* FIXME: should we do this on all platforms? */
+ g_object_set (object, "register-session", TRUE, NULL);
+ G_OBJECT_CLASS (gedit_app_osx_parent_class)->constructed (object);
+}
+
+static void
+gedit_app_osx_window_added (GtkApplication *application,
+ GtkWindow *window)
+{
+ GTK_APPLICATION_CLASS (gedit_app_osx_parent_class)->window_added (application, window);
+
+ update_open_sensitivity (GEDIT_APP_OSX (application));
+}
+
+static void
+gedit_app_osx_window_removed (GtkApplication *application,
+ GtkWindow *window)
+{
+ GTK_APPLICATION_CLASS (gedit_app_osx_parent_class)->window_removed (application, window);
+
+ update_open_sensitivity (GEDIT_APP_OSX (application));
+}
+
+static void
+gedit_app_osx_class_init (GeditAppOSXClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GeditAppClass *app_class = GEDIT_APP_CLASS (klass);
+ GApplicationClass *application_class = G_APPLICATION_CLASS (klass);
+ GtkApplicationClass *gtkapplication_class = GTK_APPLICATION_CLASS (klass);
+
+ object_class->finalize = gedit_app_osx_finalize;
+ object_class->constructed = gedit_app_osx_constructed;
+
+ application_class->startup = gedit_app_osx_startup;
+
+ gtkapplication_class->window_added = gedit_app_osx_window_added;
+ gtkapplication_class->window_removed = gedit_app_osx_window_removed;
+
+ app_class->show_help = gedit_app_osx_show_help_impl;
+ app_class->set_window_title = gedit_app_osx_set_window_title_impl;
+ app_class->create_window = gedit_app_osx_create_window_impl;
+ app_class->process_window_event = gedit_app_osx_process_window_event_impl;
+}
+
+static void
+gedit_app_osx_init (GeditAppOSX *app)
+{
+ /* This is required so that Cocoa is not going to parse the
+ command line arguments by itself and generate OpenFile events.
+ We already parse the command line ourselves, so this is needed
+ to prevent opening files twice, etc. */
+ [[NSUserDefaults standardUserDefaults] setObject:@"NO"
+ forKey:@"NSTreatUnknownArgumentsAsOpen"];
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-app-private.h b/gedit/gedit-app-private.h
new file mode 100644
index 0000000..4888643
--- /dev/null
+++ b/gedit/gedit-app-private.h
@@ -0,0 +1,50 @@
+/*
+ * gedit-app-private.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2015 - Sébastien Wilmet <swilmet@gnome.org>
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_APP_PRIVATE_H
+#define GEDIT_APP_PRIVATE_H
+
+#include "gedit-app.h"
+#include "gedit-menu-extension.h"
+
+G_BEGIN_DECLS
+
+/* global print config */
+GtkPageSetup *_gedit_app_get_default_page_setup (GeditApp *app);
+void _gedit_app_set_default_page_setup (GeditApp *app,
+ GtkPageSetup *page_setup);
+GtkPrintSettings *_gedit_app_get_default_print_settings (GeditApp *app);
+void _gedit_app_set_default_print_settings (GeditApp *app,
+ GtkPrintSettings *settings);
+
+GMenuModel *_gedit_app_get_hamburger_menu (GeditApp *app);
+
+GMenuModel *_gedit_app_get_notebook_menu (GeditApp *app);
+
+GMenuModel *_gedit_app_get_tab_width_menu (GeditApp *app);
+
+GeditMenuExtension *_gedit_app_extend_menu (GeditApp *app,
+ const gchar *extension_point);
+
+G_END_DECLS
+
+#endif /* GEDIT_APP_PRIVATE_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-app-win32.c b/gedit/gedit-app-win32.c
new file mode 100644
index 0000000..61897a5
--- /dev/null
+++ b/gedit/gedit-app-win32.c
@@ -0,0 +1,138 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Jesse van den Kieboom
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "gedit-app-win32.h"
+
+#define SAVE_DATADIR DATADIR
+#undef DATADIR
+
+#include <io.h>
+#include <conio.h>
+
+#ifndef _WIN32_WINNT
+# define _WIN32_WINNT 0x0501
+#endif
+
+#include <windows.h>
+
+#define DATADIR SAVE_DATADIR
+#undef SAVE_DATADIR
+
+struct _GeditAppWin32
+{
+ GeditApp parent_instance;
+};
+
+G_DEFINE_TYPE (GeditAppWin32, gedit_app_win32, GEDIT_TYPE_APP)
+
+static gchar *
+gedit_app_win32_get_help_uri_impl (GeditApp *app,
+ const gchar *name_of_user_manual,
+ const gchar *link_id_within_user_manual)
+{
+ /* FIXME: name_of_user_manual is expected to be always "gedit" here. */
+
+ if (link_id_within_user_manual != NULL)
+ {
+ return g_strdup_printf ("https://gedit-technology.net/user-manuals/gedit/%s",
+ link_id_within_user_manual);
+ }
+
+ return g_strdup ("https://gedit-technology.net/user-manuals/gedit/");
+}
+
+static void
+setup_path (void)
+{
+ gchar *path;
+ gchar *installdir;
+ gchar *bin;
+
+ installdir = g_win32_get_package_installation_directory_of_module (NULL);
+
+ bin = g_build_filename (installdir, "bin", NULL);
+ g_free (installdir);
+
+ /* Set PATH to include the gedit executable's folder */
+ path = g_build_path (";", bin, g_getenv ("PATH"), NULL);
+ g_free (bin);
+
+ if (!g_setenv ("PATH", path, TRUE))
+ {
+ g_warning ("Could not set PATH for gedit");
+ }
+
+ g_free (path);
+}
+
+static void
+prep_console (void)
+{
+ /* If we open gedit from a console get the stdout printing */
+ if (fileno (stdout) != -1 &&
+ _get_osfhandle (fileno (stdout)) != -1)
+ {
+ /* stdout is fine, presumably redirected to a file or pipe */
+ }
+ else
+ {
+ typedef BOOL (* WINAPI AttachConsole_t) (DWORD);
+
+ AttachConsole_t p_AttachConsole =
+ (AttachConsole_t) GetProcAddress (GetModuleHandle ("kernel32.dll"),
+ "AttachConsole");
+
+ if (p_AttachConsole != NULL && p_AttachConsole (ATTACH_PARENT_PROCESS))
+ {
+ freopen ("CONOUT$", "w", stdout);
+ dup2 (fileno (stdout), 1);
+ freopen ("CONOUT$", "w", stderr);
+ dup2 (fileno (stderr), 2);
+ }
+ }
+}
+
+static void
+gedit_app_win32_startup (GApplication *application)
+{
+ G_APPLICATION_CLASS (gedit_app_win32_parent_class)->startup (application);
+
+ setup_path ();
+ prep_console ();
+}
+
+static void
+gedit_app_win32_class_init (GeditAppWin32Class *klass)
+{
+ GApplicationClass *gapp_class = G_APPLICATION_CLASS (klass);
+ GeditAppClass *app_class = GEDIT_APP_CLASS (klass);
+
+ gapp_class->startup = gedit_app_win32_startup;
+
+ app_class->get_help_uri = gedit_app_win32_get_help_uri_impl;
+}
+
+static void
+gedit_app_win32_init (GeditAppWin32 *self)
+{
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-app-win32.h b/gedit/gedit-app-win32.h
new file mode 100644
index 0000000..d30841e
--- /dev/null
+++ b/gedit/gedit-app-win32.h
@@ -0,0 +1,39 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Jesse van den Kieboom
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef GEDIT_APP_WIN32_H
+#define GEDIT_APP_WIN32_H
+
+#include "gedit-app.h"
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_APP_WIN32 (gedit_app_win32_get_type ())
+
+G_DECLARE_FINAL_TYPE (GeditAppWin32, gedit_app_win32,
+ GEDIT, APP_WIN32,
+ GeditApp)
+
+G_END_DECLS
+
+#endif /* GEDIT_APP_WIN32_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-app.c b/gedit/gedit-app.c
new file mode 100644
index 0000000..4dc8c29
--- /dev/null
+++ b/gedit/gedit-app.c
@@ -0,0 +1,1700 @@
+/*
+ * gedit-app.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2005-2006 - 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-app.h"
+#include "gedit-app-private.h"
+
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+#include <libpeas/peas-extension-set.h>
+#include <tepl/tepl.h>
+
+#include "gedit-commands-private.h"
+#include "gedit-notebook.h"
+#include "gedit-debug.h"
+#include "gedit-utils.h"
+#include "gedit-enum-types.h"
+#include "gedit-dirs.h"
+#include "gedit-settings.h"
+#include "gedit-app-activatable.h"
+#include "gedit-plugins-engine.h"
+#include "gedit-commands.h"
+#include "gedit-preferences-dialog.h"
+#include "gedit-tab.h"
+
+#define GEDIT_PAGE_SETUP_FILE "gedit-page-setup"
+#define GEDIT_PRINT_SETTINGS_FILE "gedit-print-settings"
+
+typedef struct
+{
+ GeditPluginsEngine *engine;
+
+ GtkCssProvider *theme_provider;
+
+ GtkPageSetup *page_setup;
+ GtkPrintSettings *print_settings;
+
+ GSettings *window_settings;
+
+ GMenuModel *hamburger_menu;
+ GMenuModel *notebook_menu;
+ GMenuModel *tab_width_menu;
+
+ PeasExtensionSet *extensions;
+
+ /* command line parsing */
+ gboolean new_window;
+ gboolean new_document;
+ const GtkSourceEncoding *encoding;
+ GInputStream *stdin_stream;
+ GSList *file_list;
+ gint line_position;
+ gint column_position;
+ GApplicationCommandLine *command_line;
+} GeditAppPrivate;
+
+static const GOptionEntry options[] =
+{
+ /* Version */
+ {
+ "version", 'V', 0, G_OPTION_ARG_NONE, NULL,
+ N_("Show the application’s version"), NULL
+ },
+
+ /* List available encodings */
+ {
+ "list-encodings", '\0', 0, G_OPTION_ARG_NONE, NULL,
+ N_("Display list of possible values for the encoding option"),
+ NULL
+ },
+
+ /* Encoding */
+ {
+ "encoding", '\0', 0, G_OPTION_ARG_STRING, NULL,
+ N_("Set the character encoding to be used to open the files listed on the command line"),
+ N_("ENCODING")
+ },
+
+ /* Open a new window */
+ {
+ "new-window", '\0', 0, G_OPTION_ARG_NONE, NULL,
+ N_("Create a new top-level window in an existing instance of gedit"),
+ NULL
+ },
+
+ /* Create a new empty document */
+ {
+ "new-document", '\0', 0, G_OPTION_ARG_NONE, NULL,
+ N_("Create a new document in an existing instance of gedit"),
+ NULL
+ },
+
+ /* Wait for closing documents */
+ {
+ "wait", 'w', 0, G_OPTION_ARG_NONE, NULL,
+ N_("Open files and block process until files are closed"),
+ NULL
+ },
+
+ /* New instance */
+ {
+ "standalone", 's', 0, G_OPTION_ARG_NONE, NULL,
+ N_("Run gedit in standalone mode"),
+ NULL
+ },
+
+ /* collects file arguments */
+ {
+ G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, NULL, NULL,
+ N_("[FILE…] [+LINE[:COLUMN]]")
+ },
+
+ {NULL}
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GeditApp, gedit_app, GTK_TYPE_APPLICATION)
+
+static void
+gedit_app_dispose (GObject *object)
+{
+ GeditAppPrivate *priv;
+
+ priv = gedit_app_get_instance_private (GEDIT_APP (object));
+
+ g_clear_object (&priv->window_settings);
+
+ g_clear_object (&priv->page_setup);
+ g_clear_object (&priv->print_settings);
+
+ /* Note that unreffing the extensions will automatically remove
+ * all extensions which in turn will deactivate the extension
+ */
+ g_clear_object (&priv->extensions);
+
+ g_clear_object (&priv->engine);
+
+ if (priv->theme_provider != NULL)
+ {
+ gtk_style_context_remove_provider_for_screen (gdk_screen_get_default (),
+ GTK_STYLE_PROVIDER (priv->theme_provider));
+ g_clear_object (&priv->theme_provider);
+ }
+
+ g_clear_object (&priv->hamburger_menu);
+ g_clear_object (&priv->notebook_menu);
+ g_clear_object (&priv->tab_width_menu);
+
+ G_OBJECT_CLASS (gedit_app_parent_class)->dispose (object);
+}
+
+static gchar *
+gedit_app_get_help_uri_impl (GeditApp *app,
+ const gchar *name_of_user_manual,
+ const gchar *link_id_within_user_manual)
+{
+ if (link_id_within_user_manual != NULL)
+ {
+ return g_strdup_printf ("help:%s/%s",
+ name_of_user_manual,
+ link_id_within_user_manual);
+ }
+ else
+ {
+ return g_strdup_printf ("help:%s", name_of_user_manual);
+ }
+}
+
+static gboolean
+gedit_app_show_help_impl (GeditApp *app,
+ GtkWindow *parent_window,
+ const gchar *name_of_user_manual,
+ const gchar *link_id_within_user_manual)
+{
+ gchar *uri;
+ gboolean ret;
+ GError *error = NULL;
+
+ if (name_of_user_manual == NULL)
+ {
+ name_of_user_manual = "gedit";
+ }
+
+ uri = GEDIT_APP_GET_CLASS (app)->get_help_uri (app,
+ name_of_user_manual,
+ link_id_within_user_manual);
+
+ ret = gtk_show_uri_on_window (parent_window,
+ uri,
+ GDK_CURRENT_TIME,
+ &error);
+
+ g_free (uri);
+
+ if (error != NULL)
+ {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (parent_window,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ _("There was an error displaying the help."));
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", error->message);
+
+ g_signal_connect (G_OBJECT (dialog),
+ "response",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ gtk_widget_show (dialog);
+
+ g_error_free (error);
+ }
+
+ return ret;
+}
+
+static void
+gedit_app_set_window_title_impl (GeditApp *app,
+ GeditWindow *window,
+ const gchar *title)
+{
+ gtk_window_set_title (GTK_WINDOW (window), title);
+}
+
+static GeditWindow *
+get_active_window (GtkApplication *app)
+{
+ GList *windows;
+ GList *l;
+
+ /* Gtk documentation says the window list is always in MRU order. */
+ windows = gtk_application_get_windows (app);
+ for (l = windows; l != NULL; l = l->next)
+ {
+ GtkWindow *window = l->data;
+
+ if (GEDIT_IS_WINDOW (window))
+ {
+ return GEDIT_WINDOW (window);
+ }
+ }
+
+ return NULL;
+}
+
+static void
+set_command_line_wait (GeditApp *app,
+ GeditTab *tab)
+{
+ GeditAppPrivate *priv;
+
+ priv = gedit_app_get_instance_private (app);
+
+ g_object_set_data_full (G_OBJECT (tab),
+ "GeditTabCommandLineWait",
+ g_object_ref (priv->command_line),
+ (GDestroyNotify)g_object_unref);
+}
+
+static void
+set_command_line_wait_doc (GeditDocument *doc,
+ GeditApp *app)
+{
+ GeditTab *tab = gedit_tab_get_from_document (doc);
+
+ set_command_line_wait (app, tab);
+}
+
+static void
+open_files (GApplication *application,
+ gboolean new_window,
+ gboolean new_document,
+ gint line_position,
+ gint column_position,
+ const GtkSourceEncoding *encoding,
+ GInputStream *stdin_stream,
+ GSList *file_list,
+ GApplicationCommandLine *command_line)
+{
+ GeditWindow *window = NULL;
+ GeditTab *tab;
+ gboolean doc_created = FALSE;
+
+ if (!new_window)
+ {
+ window = get_active_window (GTK_APPLICATION (application));
+ }
+
+ if (window == NULL)
+ {
+ gedit_debug_message (DEBUG_APP, "Create main window");
+ window = gedit_app_create_window (GEDIT_APP (application), NULL);
+
+ gedit_debug_message (DEBUG_APP, "Show window");
+ gtk_widget_show (GTK_WIDGET (window));
+ }
+
+ if (stdin_stream)
+ {
+ gedit_debug_message (DEBUG_APP, "Load stdin");
+
+ tab = gedit_window_create_tab_from_stream (window,
+ stdin_stream,
+ encoding,
+ line_position,
+ column_position,
+ TRUE);
+ doc_created = tab != NULL;
+
+ if (doc_created && command_line)
+ {
+ set_command_line_wait (GEDIT_APP (application),
+ tab);
+ }
+ g_input_stream_close (stdin_stream, NULL, NULL);
+ }
+
+ if (file_list != NULL)
+ {
+ GSList *loaded;
+
+ gedit_debug_message (DEBUG_APP, "Load files");
+ loaded = _gedit_cmd_load_files_from_prompt (window,
+ file_list,
+ encoding,
+ line_position,
+ column_position);
+
+ doc_created = doc_created || loaded != NULL;
+
+ if (command_line)
+ {
+ g_slist_foreach (loaded, (GFunc)set_command_line_wait_doc, GEDIT_APP (application));
+ }
+ g_slist_free (loaded);
+ }
+
+ if (!doc_created || new_document)
+ {
+ gedit_debug_message (DEBUG_APP, "Create tab");
+ tab = gedit_window_create_tab (window, TRUE);
+
+ if (command_line)
+ {
+ set_command_line_wait (GEDIT_APP (application),
+ tab);
+ }
+ }
+
+ gtk_window_present (GTK_WINDOW (window));
+}
+
+static void
+new_window_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditApp *app;
+ GeditWindow *window;
+
+ app = GEDIT_APP (user_data);
+ window = gedit_app_create_window (app, NULL);
+
+ gedit_debug_message (DEBUG_APP, "Show window");
+ gtk_widget_show (GTK_WIDGET (window));
+
+ gedit_debug_message (DEBUG_APP, "Create tab");
+ gedit_window_create_tab (window, TRUE);
+
+ gtk_window_present (GTK_WINDOW (window));
+}
+
+static void
+new_document_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GApplication *application = G_APPLICATION (user_data);
+
+ open_files (application,
+ FALSE,
+ TRUE,
+ 0,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+}
+
+static void
+preferences_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GtkApplication *app;
+ GeditWindow *window;
+
+ app = GTK_APPLICATION (user_data);
+ window = GEDIT_WINDOW (gtk_application_get_active_window (app));
+
+ gedit_show_preferences_dialog (window);
+}
+
+static void
+keyboard_shortcuts_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GtkApplication *app;
+ GeditWindow *window;
+
+ app = GTK_APPLICATION (user_data);
+ window = GEDIT_WINDOW (gtk_application_get_active_window (app));
+
+ _gedit_cmd_help_keyboard_shortcuts (window);
+}
+
+static void
+help_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GtkApplication *app;
+ GeditWindow *window;
+
+ app = GTK_APPLICATION (user_data);
+ window = GEDIT_WINDOW (gtk_application_get_active_window (app));
+
+ _gedit_cmd_help_contents (window);
+}
+
+static void
+about_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GtkApplication *app;
+ GeditWindow *window;
+
+ app = GTK_APPLICATION (user_data);
+ window = GEDIT_WINDOW (gtk_application_get_active_window (app));
+
+ _gedit_cmd_help_about (window);
+}
+
+static void
+quit_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ _gedit_cmd_file_quit (NULL, NULL, NULL);
+}
+
+static GActionEntry app_entries[] = {
+ { "new-window", new_window_activated, NULL, NULL, NULL },
+ { "new-document", new_document_activated, NULL, NULL, NULL },
+ { "preferences", preferences_activated, NULL, NULL, NULL },
+ { "shortcuts", keyboard_shortcuts_activated, NULL, NULL, NULL },
+ { "help", help_activated, NULL, NULL, NULL },
+ { "about", about_activated, NULL, NULL, NULL },
+ { "quit", quit_activated, NULL, NULL, NULL }
+};
+
+static void
+extension_added (PeasExtensionSet *extensions,
+ PeasPluginInfo *info,
+ PeasExtension *exten,
+ GeditApp *app)
+{
+ gedit_app_activatable_activate (GEDIT_APP_ACTIVATABLE (exten));
+}
+
+static void
+extension_removed (PeasExtensionSet *extensions,
+ PeasPluginInfo *info,
+ PeasExtension *exten,
+ GeditApp *app)
+{
+ gedit_app_activatable_deactivate (GEDIT_APP_ACTIVATABLE (exten));
+}
+
+static void
+load_accels (void)
+{
+ gchar *filename;
+
+ filename = g_build_filename (gedit_dirs_get_user_config_dir (),
+ "accels",
+ NULL);
+ if (filename != NULL)
+ {
+ gedit_debug_message (DEBUG_APP, "Loading keybindings from %s\n", filename);
+ gtk_accel_map_load (filename);
+ g_free (filename);
+ }
+}
+
+static GtkCssProvider *
+load_css_from_resource (const gchar *filename,
+ gboolean required)
+{
+ GError *error = NULL;
+ GFile *css_file;
+ GtkCssProvider *provider;
+ gchar *resource_name;
+
+ resource_name = g_strdup_printf ("resource:///org/gnome/gedit/css/%s", filename);
+ css_file = g_file_new_for_uri (resource_name);
+ g_free (resource_name);
+
+ if (!required && !g_file_query_exists (css_file, NULL))
+ {
+ g_object_unref (css_file);
+ return NULL;
+ }
+
+ provider = gtk_css_provider_new ();
+
+ if (gtk_css_provider_load_from_file (provider, css_file, &error))
+ {
+ gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ else
+ {
+ g_warning ("Could not load css provider: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (css_file);
+ return provider;
+}
+
+static void
+theme_changed (GtkSettings *settings,
+ GParamSpec *pspec,
+ GeditApp *app)
+{
+ GeditAppPrivate *priv;
+
+ priv = gedit_app_get_instance_private (app);
+
+ gchar *theme, *lc_theme, *theme_css;
+
+ g_object_get (settings, "gtk-theme-name", &theme, NULL);
+ lc_theme = g_ascii_strdown (theme, -1);
+ g_free (theme);
+
+ theme_css = g_strdup_printf ("gedit.%s.css", lc_theme);
+ g_free (lc_theme);
+
+ if (priv->theme_provider != NULL)
+ {
+ gtk_style_context_remove_provider_for_screen (gdk_screen_get_default (),
+ GTK_STYLE_PROVIDER (priv->theme_provider));
+ g_clear_object (&priv->theme_provider);
+ }
+
+ priv->theme_provider = load_css_from_resource (theme_css, FALSE);
+
+ g_free (theme_css);
+}
+
+static void
+setup_theme_extensions (GeditApp *app)
+{
+ GtkSettings *settings;
+
+ settings = gtk_settings_get_default ();
+ g_signal_connect (settings, "notify::gtk-theme-name",
+ G_CALLBACK (theme_changed), app);
+ theme_changed (settings, NULL, app);
+}
+
+static GMenuModel *
+get_menu_model (GeditApp *app,
+ const char *id)
+{
+ GMenu *menu;
+
+ menu = gtk_application_get_menu_by_id (GTK_APPLICATION (app), id);
+
+ return menu ? G_MENU_MODEL (g_object_ref_sink (menu)) : NULL;
+}
+
+static void
+add_accelerator (GtkApplication *app,
+ const gchar *action_name,
+ const gchar *accel)
+{
+ const gchar *vaccels[] = {
+ accel,
+ NULL
+ };
+
+ gtk_application_set_accels_for_action (app, action_name, vaccels);
+}
+
+static gboolean
+show_menubar (void)
+{
+ GtkSettings *settings = gtk_settings_get_default ();
+ gboolean result;
+
+ g_object_get (settings,
+ "gtk-shell-shows-menubar", &result,
+ NULL);
+
+ return result;
+}
+
+static void
+init_tepl_settings (void)
+{
+ GeditSettings *gedit_settings;
+ GSettings *editor_settings;
+ TeplSettings *tepl_settings;
+
+ gedit_settings = _gedit_settings_get_singleton ();
+ editor_settings = _gedit_settings_peek_editor_settings (gedit_settings);
+
+ tepl_settings = tepl_settings_get_singleton ();
+
+ tepl_settings_provide_font_settings (tepl_settings,
+ editor_settings,
+ GEDIT_SETTINGS_USE_DEFAULT_FONT,
+ GEDIT_SETTINGS_EDITOR_FONT);
+}
+
+static void
+gedit_app_startup (GApplication *application)
+{
+ GeditAppPrivate *priv;
+ GtkCssProvider *css_provider;
+ GtkSourceStyleSchemeManager *manager;
+
+ priv = gedit_app_get_instance_private (GEDIT_APP (application));
+
+ G_APPLICATION_CLASS (gedit_app_parent_class)->startup (application);
+
+ /* Setup debugging */
+ gedit_debug_init ();
+ gedit_debug_message (DEBUG_APP, "Startup");
+
+ setup_theme_extensions (GEDIT_APP (application));
+
+ /* Load/init settings */
+ _gedit_settings_get_singleton ();
+ priv->window_settings = g_settings_new ("org.gnome.gedit.state.window");
+ init_tepl_settings ();
+
+ g_action_map_add_action_entries (G_ACTION_MAP (application),
+ app_entries,
+ G_N_ELEMENTS (app_entries),
+ application);
+
+ /* menus */
+ if (!show_menubar ())
+ {
+ gtk_application_set_menubar (GTK_APPLICATION (application), NULL);
+ priv->hamburger_menu = get_menu_model (GEDIT_APP (application),
+ "hamburger-menu");
+ }
+
+ priv->notebook_menu = get_menu_model (GEDIT_APP (application), "notebook-menu");
+ priv->tab_width_menu = get_menu_model (GEDIT_APP (application), "tab-width-menu");
+
+ /* Accelerators */
+ add_accelerator (GTK_APPLICATION (application), "app.new-window", "<Primary>N");
+ add_accelerator (GTK_APPLICATION (application), "app.quit", "<Primary>Q");
+ add_accelerator (GTK_APPLICATION (application), "app.help", "F1");
+ add_accelerator (GTK_APPLICATION (application), "app.shortcuts", "<Primary>question");
+
+ add_accelerator (GTK_APPLICATION (application), "win.hamburger-menu", "F10");
+ add_accelerator (GTK_APPLICATION (application), "win.open", "<Primary>O");
+ add_accelerator (GTK_APPLICATION (application), "win.save", "<Primary>S");
+ add_accelerator (GTK_APPLICATION (application), "win.save-as", "<Primary><Shift>S");
+ add_accelerator (GTK_APPLICATION (application), "win.save-all", "<Primary><Shift>L");
+ add_accelerator (GTK_APPLICATION (application), "win.new-tab", "<Primary>T");
+ add_accelerator (GTK_APPLICATION (application), "win.reopen-closed-tab", "<Primary><Shift>T");
+ add_accelerator (GTK_APPLICATION (application), "win.close", "<Primary>W");
+ add_accelerator (GTK_APPLICATION (application), "win.close-all", "<Primary><Shift>W");
+ add_accelerator (GTK_APPLICATION (application), "win.print", "<Primary>P");
+ add_accelerator (GTK_APPLICATION (application), "win.find", "<Primary>F");
+ add_accelerator (GTK_APPLICATION (application), "win.find-next", "<Primary>G");
+ add_accelerator (GTK_APPLICATION (application), "win.find-prev", "<Primary><Shift>G");
+ add_accelerator (GTK_APPLICATION (application), "win.replace", "<Primary>H");
+ add_accelerator (GTK_APPLICATION (application), "win.clear-highlight", "<Primary><Shift>K");
+ add_accelerator (GTK_APPLICATION (application), "win.goto-line", "<Primary>I");
+ add_accelerator (GTK_APPLICATION (application), "win.focus-active-view", "Escape");
+ add_accelerator (GTK_APPLICATION (application), "win.side-panel", "F9");
+ add_accelerator (GTK_APPLICATION (application), "win.bottom-panel", "<Primary>F9");
+ add_accelerator (GTK_APPLICATION (application), "win.fullscreen", "F11");
+ add_accelerator (GTK_APPLICATION (application), "win.new-tab-group", "<Primary><Alt>N");
+ add_accelerator (GTK_APPLICATION (application), "win.previous-tab-group", "<Primary><Shift><Alt>Page_Up");
+ add_accelerator (GTK_APPLICATION (application), "win.next-tab-group", "<Primary><Shift><Alt>Page_Down");
+ add_accelerator (GTK_APPLICATION (application), "win.previous-document", "<Primary><Alt>Page_Up");
+ add_accelerator (GTK_APPLICATION (application), "win.next-document", "<Primary><Alt>Page_Down");
+
+ load_accels ();
+
+ /* Load custom css */
+ g_object_unref (load_css_from_resource ("gedit-style.css", TRUE));
+ css_provider = load_css_from_resource ("gedit-style-os.css", FALSE);
+ g_clear_object (&css_provider);
+
+ /*
+ * We use the default gtksourceview style scheme manager so that plugins
+ * can obtain it easily without a gedit specific api, but we need to
+ * add our search path at startup before the manager is actually used.
+ */
+ manager = gtk_source_style_scheme_manager_get_default ();
+ gtk_source_style_scheme_manager_append_search_path (manager,
+ gedit_dirs_get_user_styles_dir ());
+
+ priv->engine = gedit_plugins_engine_get_default ();
+ priv->extensions = peas_extension_set_new (PEAS_ENGINE (priv->engine),
+ GEDIT_TYPE_APP_ACTIVATABLE,
+ "app", GEDIT_APP (application),
+ NULL);
+
+ g_signal_connect (priv->extensions,
+ "extension-added",
+ G_CALLBACK (extension_added),
+ application);
+
+ g_signal_connect (priv->extensions,
+ "extension-removed",
+ G_CALLBACK (extension_removed),
+ application);
+
+ peas_extension_set_foreach (priv->extensions,
+ (PeasExtensionSetForeachFunc) extension_added,
+ application);
+}
+
+static void
+gedit_app_activate (GApplication *application)
+{
+ GeditAppPrivate *priv;
+
+ priv = gedit_app_get_instance_private (GEDIT_APP (application));
+
+ open_files (application,
+ priv->new_window,
+ priv->new_document,
+ priv->line_position,
+ priv->column_position,
+ priv->encoding,
+ priv->stdin_stream,
+ priv->file_list,
+ priv->command_line);
+}
+
+static void
+clear_options (GeditApp *app)
+{
+ GeditAppPrivate *priv;
+
+ priv = gedit_app_get_instance_private (app);
+
+ g_clear_object (&priv->stdin_stream);
+ g_slist_free_full (priv->file_list, g_object_unref);
+
+ priv->new_window = FALSE;
+ priv->new_document = FALSE;
+ priv->encoding = NULL;
+ priv->file_list = NULL;
+ priv->line_position = 0;
+ priv->column_position = 0;
+ priv->command_line = NULL;
+}
+
+static void
+get_line_column_position (const gchar *arg,
+ gint *line,
+ gint *column)
+{
+ gchar **split;
+
+ split = g_strsplit (arg, ":", 2);
+
+ if (split != NULL)
+ {
+ if (split[0] != NULL)
+ {
+ *line = atoi (split[0]);
+ }
+
+ if (split[1] != NULL)
+ {
+ *column = atoi (split[1]);
+ }
+ }
+
+ g_strfreev (split);
+}
+
+static gint
+gedit_app_command_line (GApplication *application,
+ GApplicationCommandLine *cl)
+{
+ GeditAppPrivate *priv;
+ GVariantDict *options;
+ const gchar *encoding_charset;
+ const gchar **remaining_args;
+
+ priv = gedit_app_get_instance_private (GEDIT_APP (application));
+
+ options = g_application_command_line_get_options_dict (cl);
+
+ g_variant_dict_lookup (options, "new-window", "b", &priv->new_window);
+ g_variant_dict_lookup (options, "new-document", "b", &priv->new_document);
+
+ if (g_variant_dict_contains (options, "wait"))
+ {
+ priv->command_line = cl;
+ }
+
+ if (g_variant_dict_lookup (options, "encoding", "&s", &encoding_charset))
+ {
+ priv->encoding = gtk_source_encoding_get_from_charset (encoding_charset);
+
+ if (priv->encoding == NULL)
+ {
+ g_application_command_line_printerr (cl,
+ _("%s: invalid encoding."),
+ encoding_charset);
+ }
+ }
+
+ /* Parse filenames */
+ if (g_variant_dict_lookup (options, G_OPTION_REMAINING, "^a&ay", &remaining_args))
+ {
+ gint i;
+
+ for (i = 0; remaining_args[i]; i++)
+ {
+ if (*remaining_args[i] == '+')
+ {
+ if (*(remaining_args[i] + 1) == '\0')
+ {
+ /* goto the last line of the document */
+ priv->line_position = G_MAXINT;
+ priv->column_position = 0;
+ }
+ else
+ {
+ get_line_column_position (remaining_args[i] + 1,
+ &priv->line_position,
+ &priv->column_position);
+ }
+ }
+ else if (*remaining_args[i] == '-' && *(remaining_args[i] + 1) == '\0')
+ {
+ priv->stdin_stream = g_application_command_line_get_stdin (cl);
+ }
+ else
+ {
+ GFile *file;
+
+ file = g_application_command_line_create_file_for_arg (cl, remaining_args[i]);
+ priv->file_list = g_slist_prepend (priv->file_list, file);
+ }
+ }
+
+ priv->file_list = g_slist_reverse (priv->file_list);
+ g_free (remaining_args);
+ }
+
+ g_application_activate (application);
+ clear_options (GEDIT_APP (application));
+
+ return 0;
+}
+
+static void
+print_all_encodings (void)
+{
+ GSList *all_encodings;
+ GSList *l;
+
+ all_encodings = gtk_source_encoding_get_all ();
+
+ for (l = all_encodings; l != NULL; l = l->next)
+ {
+ const GtkSourceEncoding *encoding = l->data;
+ g_print ("%s\n", gtk_source_encoding_get_charset (encoding));
+ }
+
+ g_slist_free (all_encodings);
+}
+
+static gint
+gedit_app_handle_local_options (GApplication *application,
+ GVariantDict *options)
+{
+ if (g_variant_dict_contains (options, "version"))
+ {
+ g_print ("%s - Version %s\n", g_get_application_name (), VERSION);
+ return 0;
+ }
+
+ if (g_variant_dict_contains (options, "list-encodings"))
+ {
+ print_all_encodings ();
+ return 0;
+ }
+
+ if (g_variant_dict_contains (options, "standalone"))
+ {
+ GApplicationFlags old_flags;
+
+ old_flags = g_application_get_flags (application);
+ g_application_set_flags (application, old_flags | G_APPLICATION_NON_UNIQUE);
+ }
+
+ if (g_variant_dict_contains (options, "wait"))
+ {
+ GApplicationFlags old_flags;
+
+ old_flags = g_application_get_flags (application);
+ g_application_set_flags (application, old_flags | G_APPLICATION_IS_LAUNCHER);
+ }
+
+ return -1;
+}
+
+/* Note: when launched from command line we do not reach this method
+ * since we manually handle the command line parameters in order to
+ * parse +LINE:COL, stdin, etc.
+ * However this method is called when open() is called via dbus, for
+ * instance when double clicking on a file in nautilus
+ */
+static void
+gedit_app_open (GApplication *application,
+ GFile **files,
+ gint n_files,
+ const gchar *hint)
+{
+ gint i;
+ GSList *file_list = NULL;
+
+ for (i = 0; i < n_files; i++)
+ {
+ file_list = g_slist_prepend (file_list, files[i]);
+ }
+
+ file_list = g_slist_reverse (file_list);
+
+ open_files (application,
+ FALSE,
+ FALSE,
+ 0,
+ 0,
+ NULL,
+ NULL,
+ file_list,
+ NULL);
+
+ g_slist_free (file_list);
+}
+
+static gboolean
+ensure_user_config_dir (void)
+{
+ const gchar *config_dir;
+ gboolean ret = TRUE;
+ gint res;
+
+ config_dir = gedit_dirs_get_user_config_dir ();
+ if (config_dir == NULL)
+ {
+ g_warning ("Could not get config directory\n");
+ return FALSE;
+ }
+
+ res = g_mkdir_with_parents (config_dir, 0755);
+ if (res < 0)
+ {
+ g_warning ("Could not create config directory\n");
+ ret = FALSE;
+ }
+
+ return ret;
+}
+
+static void
+save_accels (void)
+{
+ gchar *filename;
+
+ filename = g_build_filename (gedit_dirs_get_user_config_dir (),
+ "accels",
+ NULL);
+ if (filename != NULL)
+ {
+ gedit_debug_message (DEBUG_APP, "Saving keybindings in %s\n", filename);
+ gtk_accel_map_save (filename);
+ g_free (filename);
+ }
+}
+
+static gchar *
+get_page_setup_file (void)
+{
+ const gchar *config_dir;
+ gchar *setup = NULL;
+
+ config_dir = gedit_dirs_get_user_config_dir ();
+
+ if (config_dir != NULL)
+ {
+ setup = g_build_filename (config_dir,
+ GEDIT_PAGE_SETUP_FILE,
+ NULL);
+ }
+
+ return setup;
+}
+
+static void
+save_page_setup (GeditApp *app)
+{
+ GeditAppPrivate *priv;
+
+ priv = gedit_app_get_instance_private (app);
+
+ if (priv->page_setup != NULL)
+ {
+ gchar *filename;
+ GError *error = NULL;
+
+ filename = get_page_setup_file ();
+
+ gtk_page_setup_to_file (priv->page_setup,
+ filename,
+ &error);
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+
+ g_free (filename);
+ }
+}
+
+static gchar *
+get_print_settings_file (void)
+{
+ const gchar *config_dir;
+ gchar *settings = NULL;
+
+ config_dir = gedit_dirs_get_user_config_dir ();
+
+ if (config_dir != NULL)
+ {
+ settings = g_build_filename (config_dir,
+ GEDIT_PRINT_SETTINGS_FILE,
+ NULL);
+ }
+
+ return settings;
+}
+
+static void
+save_print_settings (GeditApp *app)
+{
+ GeditAppPrivate *priv;
+
+ priv = gedit_app_get_instance_private (app);
+
+ if (priv->print_settings != NULL)
+ {
+ gchar *filename;
+ GError *error = NULL;
+
+ filename = get_print_settings_file ();
+
+ gtk_print_settings_to_file (priv->print_settings,
+ filename,
+ &error);
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+
+ g_free (filename);
+ }
+}
+
+static void
+gedit_app_shutdown (GApplication *app)
+{
+ gedit_debug_message (DEBUG_APP, "Quitting\n");
+
+ /* Last window is gone... save some settings and exit */
+ ensure_user_config_dir ();
+
+ save_accels ();
+ save_page_setup (GEDIT_APP (app));
+ save_print_settings (GEDIT_APP (app));
+
+ G_APPLICATION_CLASS (gedit_app_parent_class)->shutdown (app);
+}
+
+static gboolean
+window_delete_event (GeditWindow *window,
+ GdkEvent *event,
+ GeditApp *app)
+{
+ GeditWindowState ws;
+
+ ws = gedit_window_get_state (window);
+
+ if (ws &
+ (GEDIT_WINDOW_STATE_SAVING | GEDIT_WINDOW_STATE_PRINTING))
+ {
+ return TRUE;
+ }
+
+ _gedit_cmd_file_quit (NULL, NULL, window);
+
+ /* Do not destroy the window */
+ return TRUE;
+}
+
+static GeditWindow *
+gedit_app_create_window_impl (GeditApp *app)
+{
+ GeditWindow *window;
+
+ window = g_object_new (GEDIT_TYPE_WINDOW, "application", app, NULL);
+
+ gedit_debug_message (DEBUG_APP, "Window created");
+
+ g_signal_connect (window,
+ "delete_event",
+ G_CALLBACK (window_delete_event),
+ app);
+
+ return window;
+}
+
+static void
+gedit_app_class_init (GeditAppClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GApplicationClass *app_class = G_APPLICATION_CLASS (klass);
+
+ object_class->dispose = gedit_app_dispose;
+
+ app_class->startup = gedit_app_startup;
+ app_class->activate = gedit_app_activate;
+ app_class->command_line = gedit_app_command_line;
+ app_class->handle_local_options = gedit_app_handle_local_options;
+ app_class->open = gedit_app_open;
+ app_class->shutdown = gedit_app_shutdown;
+
+ klass->show_help = gedit_app_show_help_impl;
+ klass->get_help_uri = gedit_app_get_help_uri_impl;
+ klass->set_window_title = gedit_app_set_window_title_impl;
+ klass->create_window = gedit_app_create_window_impl;
+}
+
+static void
+load_page_setup (GeditApp *app)
+{
+ GeditAppPrivate *priv;
+ gchar *filename;
+ GError *error = NULL;
+
+ priv = gedit_app_get_instance_private (app);
+
+ g_return_if_fail (priv->page_setup == NULL);
+
+ filename = get_page_setup_file ();
+
+ priv->page_setup = gtk_page_setup_new_from_file (filename, &error);
+ if (error)
+ {
+ /* Ignore file not found error */
+ if (error->domain != G_FILE_ERROR ||
+ error->code != G_FILE_ERROR_NOENT)
+ {
+ g_warning ("%s", error->message);
+ }
+
+ g_error_free (error);
+ }
+
+ g_free (filename);
+
+ /* fall back to default settings */
+ if (priv->page_setup == NULL)
+ {
+ priv->page_setup = gtk_page_setup_new ();
+ }
+}
+
+static void
+load_print_settings (GeditApp *app)
+{
+ GeditAppPrivate *priv;
+ gchar *filename;
+ GError *error = NULL;
+
+ priv = gedit_app_get_instance_private (app);
+
+ g_return_if_fail (priv->print_settings == NULL);
+
+ filename = get_print_settings_file ();
+
+ priv->print_settings = gtk_print_settings_new_from_file (filename, &error);
+ if (error != NULL)
+ {
+ /* - Ignore file not found error.
+ * - Ignore empty file error, i.e. group not found. This happens
+ * when we click on cancel in the print dialog, when using the
+ * printing for the first time in gedit.
+ */
+ if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT) &&
+ !g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND))
+ {
+ g_warning ("Load print settings error: %s", error->message);
+ }
+
+ g_error_free (error);
+ }
+
+ g_free (filename);
+
+ /* fall back to default settings */
+ if (priv->print_settings == NULL)
+ {
+ priv->print_settings = gtk_print_settings_new ();
+ }
+}
+
+static void
+gedit_app_init (GeditApp *app)
+{
+ TeplApplication *tepl_app;
+
+ g_set_application_name ("gedit");
+ gtk_window_set_default_icon_name ("org.gnome.gedit");
+
+ g_application_add_main_option_entries (G_APPLICATION (app), options);
+
+ tepl_app = tepl_application_get_from_gtk_application (GTK_APPLICATION (app));
+ tepl_application_handle_metadata (tepl_app);
+}
+
+/**
+ * gedit_app_create_window:
+ * @app: the #GeditApp
+ * @screen: (allow-none):
+ *
+ * Create a new #GeditWindow part of @app.
+ *
+ * Return value: (transfer none): the new #GeditWindow
+ */
+GeditWindow *
+gedit_app_create_window (GeditApp *app,
+ GdkScreen *screen)
+{
+ GeditAppPrivate *priv;
+ GeditWindow *window;
+ GdkWindowState state;
+ gint w, h;
+
+ gedit_debug (DEBUG_APP);
+
+ priv = gedit_app_get_instance_private (app);
+
+ window = GEDIT_APP_GET_CLASS (app)->create_window (app);
+
+ if (screen != NULL)
+ {
+ gtk_window_set_screen (GTK_WINDOW (window), screen);
+ }
+
+ state = g_settings_get_int (priv->window_settings,
+ GEDIT_SETTINGS_WINDOW_STATE);
+
+ g_settings_get (priv->window_settings,
+ GEDIT_SETTINGS_WINDOW_SIZE,
+ "(ii)", &w, &h);
+
+ gtk_window_set_default_size (GTK_WINDOW (window), w, h);
+
+ if ((state & GDK_WINDOW_STATE_MAXIMIZED) != 0)
+ {
+ gtk_window_maximize (GTK_WINDOW (window));
+ }
+ else
+ {
+ gtk_window_unmaximize (GTK_WINDOW (window));
+ }
+
+ if ((state & GDK_WINDOW_STATE_STICKY ) != 0)
+ {
+ gtk_window_stick (GTK_WINDOW (window));
+ }
+ else
+ {
+ gtk_window_unstick (GTK_WINDOW (window));
+ }
+
+ return window;
+}
+
+/**
+ * gedit_app_get_main_windows:
+ * @app: the #GeditApp
+ *
+ * Returns all #GeditWindows currently open in #GeditApp.
+ * This differs from gtk_application_get_windows() since it does not
+ * include the preferences dialog and other auxiliary windows.
+ *
+ * Return value: (element-type Gedit.Window) (transfer container):
+ * a newly allocated list of #GeditWindow objects
+ */
+GList *
+gedit_app_get_main_windows (GeditApp *app)
+{
+ GList *res = NULL;
+ GList *windows, *l;
+
+ g_return_val_if_fail (GEDIT_IS_APP (app), NULL);
+
+ windows = gtk_application_get_windows (GTK_APPLICATION (app));
+ for (l = windows; l != NULL; l = g_list_next (l))
+ {
+ if (GEDIT_IS_WINDOW (l->data))
+ {
+ res = g_list_prepend (res, l->data);
+ }
+ }
+
+ return g_list_reverse (res);
+}
+
+/**
+ * gedit_app_get_documents:
+ * @app: the #GeditApp
+ *
+ * Returns all the documents currently open in #GeditApp.
+ *
+ * Return value: (element-type Gedit.Document) (transfer container):
+ * a newly allocated list of #GeditDocument objects
+ */
+GList *
+gedit_app_get_documents (GeditApp *app)
+{
+ GList *res = NULL;
+ GList *windows, *l;
+
+ g_return_val_if_fail (GEDIT_IS_APP (app), NULL);
+
+ windows = gtk_application_get_windows (GTK_APPLICATION (app));
+ for (l = windows; l != NULL; l = g_list_next (l))
+ {
+ if (GEDIT_IS_WINDOW (l->data))
+ {
+ res = g_list_concat (res,
+ gedit_window_get_documents (GEDIT_WINDOW (l->data)));
+ }
+ }
+
+ return res;
+}
+
+/**
+ * gedit_app_get_views:
+ * @app: the #GeditApp
+ *
+ * Returns all the views currently present in #GeditApp.
+ *
+ * Return value: (element-type Gedit.View) (transfer container):
+ * a newly allocated list of #GeditView objects
+ */
+GList *
+gedit_app_get_views (GeditApp *app)
+{
+ GList *res = NULL;
+ GList *windows, *l;
+
+ g_return_val_if_fail (GEDIT_IS_APP (app), NULL);
+
+ windows = gtk_application_get_windows (GTK_APPLICATION (app));
+ for (l = windows; l != NULL; l = g_list_next (l))
+ {
+ if (GEDIT_IS_WINDOW (l->data))
+ {
+ res = g_list_concat (res,
+ gedit_window_get_views (GEDIT_WINDOW (l->data)));
+ }
+ }
+
+ return res;
+}
+
+/**
+ * gedit_app_show_help:
+ * @app: a #GeditApp.
+ * @parent_window: (nullable): the #GtkWindow where the request originates from.
+ * @name_of_user_manual: (nullable): %NULL for gedit's user manual, otherwise
+ * the name of another user manual (e.g., one from another application).
+ * @link_id_within_user_manual: (nullable): a link ID within the user manual, or
+ * %NULL to show the start page.
+ *
+ * To show the user manual.
+ *
+ * As a useful information to know, the gedit user documentation is currently
+ * written in Mallard. As such, this functionality can easily be tested with
+ * Yelp on Linux:
+ *
+ * With @name_of_user_manual and @link_id_within_user_manual both %NULL, it is
+ * equivalent to:
+ *
+ * `$ yelp 'help:gedit'`
+ *
+ * With @link_id_within_user_manual set to `"gedit-replace"` (a Mallard page
+ * id):
+ *
+ * `$ yelp 'help:gedit/gedit-replace'`
+ *
+ * It is also possible to refer to a section id within a page id, for example:
+ *
+ * `$ yelp 'help:gedit/gedit-spellcheck#dict'`
+ *
+ * Returns: whether the operation was successful.
+ */
+gboolean
+gedit_app_show_help (GeditApp *app,
+ GtkWindow *parent_window,
+ const gchar *name_of_user_manual,
+ const gchar *link_id_within_user_manual)
+{
+ g_return_val_if_fail (GEDIT_IS_APP (app), FALSE);
+ g_return_val_if_fail (parent_window == NULL || GTK_IS_WINDOW (parent_window), FALSE);
+
+ return GEDIT_APP_GET_CLASS (app)->show_help (app,
+ parent_window,
+ name_of_user_manual,
+ link_id_within_user_manual);
+}
+
+void
+gedit_app_set_window_title (GeditApp *app,
+ GeditWindow *window,
+ const gchar *title)
+{
+ g_return_if_fail (GEDIT_IS_APP (app));
+ g_return_if_fail (GEDIT_IS_WINDOW (window));
+
+ GEDIT_APP_GET_CLASS (app)->set_window_title (app, window, title);
+}
+
+gboolean
+gedit_app_process_window_event (GeditApp *app,
+ GeditWindow *window,
+ GdkEvent *event)
+{
+ g_return_val_if_fail (GEDIT_IS_APP (app), FALSE);
+ g_return_val_if_fail (GEDIT_IS_WINDOW (window), FALSE);
+
+ if (GEDIT_APP_GET_CLASS (app)->process_window_event)
+ {
+ return GEDIT_APP_GET_CLASS (app)->process_window_event (app, window, event);
+ }
+
+ return FALSE;
+}
+
+static GMenuModel *
+find_extension_point_section (GMenuModel *model,
+ const gchar *extension_point)
+{
+ gint i, n_items;
+ GMenuModel *section = NULL;
+
+ n_items = g_menu_model_get_n_items (model);
+
+ for (i = 0; i < n_items && !section; i++)
+ {
+ gchar *id = NULL;
+
+ if (g_menu_model_get_item_attribute (model, i, "id", "s", &id) &&
+ strcmp (id, extension_point) == 0)
+ {
+ section = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION);
+ }
+ else
+ {
+ GMenuModel *subsection;
+ GMenuModel *submenu;
+ gint j, j_items;
+
+ subsection = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION);
+
+ if (subsection == NULL)
+ {
+ subsection = model;
+ }
+
+ j_items = g_menu_model_get_n_items (subsection);
+
+ for (j = 0; j < j_items && !section; j++)
+ {
+ submenu = g_menu_model_get_item_link (subsection, j, G_MENU_LINK_SUBMENU);
+ if (submenu)
+ {
+ section = find_extension_point_section (submenu, extension_point);
+ }
+ }
+ }
+
+ g_free (id);
+ }
+
+ return section;
+}
+
+/* Returns a copy */
+GtkPageSetup *
+_gedit_app_get_default_page_setup (GeditApp *app)
+{
+ GeditAppPrivate *priv;
+
+ g_return_val_if_fail (GEDIT_IS_APP (app), NULL);
+
+ priv = gedit_app_get_instance_private (app);
+
+ if (priv->page_setup == NULL)
+ {
+ load_page_setup (app);
+ }
+
+ return gtk_page_setup_copy (priv->page_setup);
+}
+
+void
+_gedit_app_set_default_page_setup (GeditApp *app,
+ GtkPageSetup *page_setup)
+{
+ GeditAppPrivate *priv;
+
+ g_return_if_fail (GEDIT_IS_APP (app));
+ g_return_if_fail (GTK_IS_PAGE_SETUP (page_setup));
+
+ priv = gedit_app_get_instance_private (app);
+
+ g_set_object (&priv->page_setup, page_setup);
+}
+
+/* Returns a copy */
+GtkPrintSettings *
+_gedit_app_get_default_print_settings (GeditApp *app)
+{
+ GeditAppPrivate *priv;
+
+ g_return_val_if_fail (GEDIT_IS_APP (app), NULL);
+
+ priv = gedit_app_get_instance_private (app);
+
+ if (priv->print_settings == NULL)
+ {
+ load_print_settings (app);
+ }
+
+ return gtk_print_settings_copy (priv->print_settings);
+}
+
+void
+_gedit_app_set_default_print_settings (GeditApp *app,
+ GtkPrintSettings *settings)
+{
+ GeditAppPrivate *priv;
+
+ g_return_if_fail (GEDIT_IS_APP (app));
+ g_return_if_fail (GTK_IS_PRINT_SETTINGS (settings));
+
+ priv = gedit_app_get_instance_private (app);
+
+ if (priv->print_settings != NULL)
+ {
+ g_object_unref (priv->print_settings);
+ }
+
+ priv->print_settings = g_object_ref (settings);
+}
+
+GMenuModel *
+_gedit_app_get_hamburger_menu (GeditApp *app)
+{
+ GeditAppPrivate *priv;
+
+ g_return_val_if_fail (GEDIT_IS_APP (app), NULL);
+
+ priv = gedit_app_get_instance_private (app);
+
+ return priv->hamburger_menu;
+}
+
+GMenuModel *
+_gedit_app_get_notebook_menu (GeditApp *app)
+{
+ GeditAppPrivate *priv;
+
+ g_return_val_if_fail (GEDIT_IS_APP (app), NULL);
+
+ priv = gedit_app_get_instance_private (app);
+
+ return priv->notebook_menu;
+}
+
+GMenuModel *
+_gedit_app_get_tab_width_menu (GeditApp *app)
+{
+ GeditAppPrivate *priv;
+
+ g_return_val_if_fail (GEDIT_IS_APP (app), NULL);
+
+ priv = gedit_app_get_instance_private (app);
+
+ return priv->tab_width_menu;
+}
+
+GeditMenuExtension *
+_gedit_app_extend_menu (GeditApp *app,
+ const gchar *extension_point)
+{
+ GeditAppPrivate *priv;
+ GMenuModel *model;
+ GMenuModel *section;
+
+ g_return_val_if_fail (GEDIT_IS_APP (app), NULL);
+ g_return_val_if_fail (extension_point != NULL, NULL);
+
+ priv = gedit_app_get_instance_private (app);
+
+ /* First look in the gear or window menu */
+ if (priv->hamburger_menu)
+ {
+ model = priv->hamburger_menu;
+ }
+ else
+ {
+ model = gtk_application_get_menubar (GTK_APPLICATION (app));
+ }
+
+ section = find_extension_point_section (model, extension_point);
+
+ /* otherwise look in the app menu */
+ if (section == NULL)
+ {
+ model = gtk_application_get_app_menu (GTK_APPLICATION (app));
+
+ if (model != NULL)
+ {
+ section = find_extension_point_section (model, extension_point);
+ }
+ }
+
+ return section != NULL ? gedit_menu_extension_new (G_MENU (section)) : NULL;
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-app.h b/gedit/gedit-app.h
new file mode 100644
index 0000000..b652dbd
--- /dev/null
+++ b/gedit/gedit-app.h
@@ -0,0 +1,82 @@
+/*
+ * gedit-app.h
+ * 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/>.
+ */
+
+#ifndef GEDIT_APP_H
+#define GEDIT_APP_H
+
+#include <gtk/gtk.h>
+#include <gedit/gedit-window.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_APP (gedit_app_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (GeditApp, gedit_app, GEDIT, APP, GtkApplication)
+
+struct _GeditAppClass
+{
+ GtkApplicationClass parent_class;
+
+ gboolean (*show_help) (GeditApp *app,
+ GtkWindow *parent_window,
+ const gchar *name_of_user_manual,
+ const gchar *link_id_within_user_manual);
+
+ gchar *(*get_help_uri) (GeditApp *app,
+ const gchar *name_of_user_manual,
+ const gchar *link_id_within_user_manual);
+
+ void (*set_window_title) (GeditApp *app,
+ GeditWindow *window,
+ const gchar *title);
+
+ GeditWindow *(*create_window) (GeditApp *app);
+
+ gboolean (*process_window_event) (GeditApp *app,
+ GeditWindow *window,
+ GdkEvent *event);
+};
+
+GeditWindow *gedit_app_create_window (GeditApp *app,
+ GdkScreen *screen);
+
+GList *gedit_app_get_main_windows (GeditApp *app);
+
+GList *gedit_app_get_documents (GeditApp *app);
+
+GList *gedit_app_get_views (GeditApp *app);
+
+gboolean gedit_app_show_help (GeditApp *app,
+ GtkWindow *parent_window,
+ const gchar *name_of_user_manual,
+ const gchar *link_id_within_user_manual);
+
+void gedit_app_set_window_title (GeditApp *app,
+ GeditWindow *window,
+ const gchar *title);
+gboolean gedit_app_process_window_event (GeditApp *app,
+ GeditWindow *window,
+ GdkEvent *event);
+
+G_END_DECLS
+
+#endif /* GEDIT_APP_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-close-confirmation-dialog.c b/gedit/gedit-close-confirmation-dialog.c
new file mode 100644
index 0000000..fee6272
--- /dev/null
+++ b/gedit/gedit-close-confirmation-dialog.c
@@ -0,0 +1,548 @@
+/*
+ * gedit-close-confirmation-dialog.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2004-2005 GNOME Foundation
+ * Copyright (C) 2015 Sébastien Wilmet
+ *
+ * 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-close-confirmation-dialog.h"
+
+#include <glib/gi18n.h>
+
+#include <gedit/gedit-app.h>
+#include <gedit/gedit-document.h>
+#include <gedit/gedit-document-private.h>
+#include <gedit/gedit-utils.h>
+#include <gedit/gedit-window.h>
+
+/* Mode */
+enum
+{
+ SINGLE_DOC_MODE,
+ MULTIPLE_DOCS_MODE
+};
+
+#define GET_MODE(dlg) (((dlg->unsaved_documents != NULL) && \
+ (dlg->unsaved_documents->next == NULL)) ? \
+ SINGLE_DOC_MODE : MULTIPLE_DOCS_MODE)
+
+#define GEDIT_SAVE_DOCUMENT_KEY "gedit-save-document"
+
+struct _GeditCloseConfirmationDialog
+{
+ GtkMessageDialog parent_instance;
+
+ GList *unsaved_documents;
+ GList *selected_documents;
+ GtkWidget *list_box;
+};
+
+enum
+{
+ PROP_0,
+ PROP_UNSAVED_DOCUMENTS,
+ LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP];
+
+G_DEFINE_TYPE (GeditCloseConfirmationDialog,
+ gedit_close_confirmation_dialog,
+ GTK_TYPE_MESSAGE_DIALOG)
+
+static void set_unsaved_document (GeditCloseConfirmationDialog *dlg,
+ const GList *list);
+
+static GList *
+get_selected_docs (GtkWidget *list_box)
+{
+ GList *rows;
+ GList *l;
+ GList *ret = NULL;
+
+ rows = gtk_container_get_children (GTK_CONTAINER (list_box));
+ for (l = rows; l != NULL; l = l->next)
+ {
+ GtkWidget *row = l->data;
+ GtkWidget *check_button;
+
+ check_button = gtk_bin_get_child (GTK_BIN (row));
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_button)))
+ {
+ GeditDocument *doc;
+
+ doc = g_object_get_data (G_OBJECT (row), GEDIT_SAVE_DOCUMENT_KEY);
+ g_return_val_if_fail (doc != NULL, NULL);
+
+ ret = g_list_prepend (ret, doc);
+ }
+ }
+
+ g_list_free (rows);
+
+ return g_list_reverse (ret);
+}
+
+/* Since we connect in the constructor we are sure this handler will be called
+ * before the user ones.
+ */
+static void
+response_cb (GeditCloseConfirmationDialog *dlg,
+ gint response_id,
+ gpointer data)
+{
+ g_return_if_fail (GEDIT_IS_CLOSE_CONFIRMATION_DIALOG (dlg));
+
+ if (dlg->selected_documents != NULL)
+ {
+ g_list_free (dlg->selected_documents);
+ dlg->selected_documents = NULL;
+ }
+
+ if (response_id == GTK_RESPONSE_YES)
+ {
+ if (GET_MODE (dlg) == SINGLE_DOC_MODE)
+ {
+ dlg->selected_documents = g_list_copy (dlg->unsaved_documents);
+ }
+ else
+ {
+ dlg->selected_documents = get_selected_docs (dlg->list_box);
+ }
+ }
+}
+
+static void
+gedit_close_confirmation_dialog_init (GeditCloseConfirmationDialog *dlg)
+{
+ gtk_window_set_title (GTK_WINDOW (dlg), "");
+ gtk_window_set_modal (GTK_WINDOW (dlg), TRUE);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dlg), TRUE);
+
+ g_signal_connect (dlg,
+ "response",
+ G_CALLBACK (response_cb),
+ NULL);
+}
+
+static void
+gedit_close_confirmation_dialog_finalize (GObject *object)
+{
+ GeditCloseConfirmationDialog *dlg = GEDIT_CLOSE_CONFIRMATION_DIALOG (object);
+
+ g_list_free (dlg->unsaved_documents);
+ g_list_free (dlg->selected_documents);
+
+ /* Call the parent's destructor */
+ G_OBJECT_CLASS (gedit_close_confirmation_dialog_parent_class)->finalize (object);
+}
+
+static void
+gedit_close_confirmation_dialog_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditCloseConfirmationDialog *dlg;
+
+ dlg = GEDIT_CLOSE_CONFIRMATION_DIALOG (object);
+
+ switch (prop_id)
+ {
+ case PROP_UNSAVED_DOCUMENTS:
+ set_unsaved_document (dlg, g_value_get_pointer (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_close_confirmation_dialog_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditCloseConfirmationDialog *dlg = GEDIT_CLOSE_CONFIRMATION_DIALOG (object);
+
+ switch (prop_id)
+ {
+ case PROP_UNSAVED_DOCUMENTS:
+ g_value_set_pointer (value, dlg->unsaved_documents);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_close_confirmation_dialog_class_init (GeditCloseConfirmationDialogClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->set_property = gedit_close_confirmation_dialog_set_property;
+ gobject_class->get_property = gedit_close_confirmation_dialog_get_property;
+ gobject_class->finalize = gedit_close_confirmation_dialog_finalize;
+
+ properties[PROP_UNSAVED_DOCUMENTS] =
+ g_param_spec_pointer ("unsaved-documents",
+ "Unsaved Documents",
+ "List of Unsaved Documents",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, LAST_PROP, properties);
+}
+
+GList *
+gedit_close_confirmation_dialog_get_selected_documents (GeditCloseConfirmationDialog *dlg)
+{
+ g_return_val_if_fail (GEDIT_IS_CLOSE_CONFIRMATION_DIALOG (dlg), NULL);
+
+ return g_list_copy (dlg->selected_documents);
+}
+
+GtkWidget *
+gedit_close_confirmation_dialog_new (GtkWindow *parent,
+ GList *unsaved_documents)
+{
+ GtkWidget *dlg;
+
+ g_return_val_if_fail (unsaved_documents != NULL, NULL);
+
+ dlg = g_object_new (GEDIT_TYPE_CLOSE_CONFIRMATION_DIALOG,
+ "unsaved-documents", unsaved_documents,
+ "message-type", GTK_MESSAGE_QUESTION,
+ NULL);
+
+ if (parent != NULL)
+ {
+ gtk_window_group_add_window (gedit_window_get_group (GEDIT_WINDOW (parent)),
+ GTK_WINDOW (dlg));
+
+ gtk_window_set_transient_for (GTK_WINDOW (dlg), parent);
+ }
+
+ return dlg;
+}
+
+GtkWidget *
+gedit_close_confirmation_dialog_new_single (GtkWindow *parent,
+ GeditDocument *doc)
+{
+ GtkWidget *dlg;
+ GList *unsaved_documents;
+
+ g_return_val_if_fail (doc != NULL, NULL);
+
+ unsaved_documents = g_list_prepend (NULL, doc);
+ dlg = gedit_close_confirmation_dialog_new (parent, unsaved_documents);
+ g_list_free (unsaved_documents);
+
+ return dlg;
+}
+
+static void
+add_buttons (GeditCloseConfirmationDialog *dlg)
+{
+ GtkWidget *close_button;
+ gboolean save_as = FALSE;
+
+ close_button = gtk_dialog_add_button (GTK_DIALOG (dlg),
+ _("Close _without Saving"),
+ GTK_RESPONSE_NO);
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (close_button),
+ "destructive-action");
+
+ gtk_dialog_add_button (GTK_DIALOG (dlg), _("_Cancel"), GTK_RESPONSE_CANCEL);
+
+ if (GET_MODE (dlg) == SINGLE_DOC_MODE)
+ {
+ GeditDocument *doc;
+ GtkSourceFile *file;
+
+ doc = GEDIT_DOCUMENT (dlg->unsaved_documents->data);
+ file = gedit_document_get_file (doc);
+
+ if (gtk_source_file_is_readonly (file) ||
+ _gedit_document_is_untitled (doc))
+ {
+ save_as = TRUE;
+ }
+ }
+
+ gtk_dialog_add_button (GTK_DIALOG (dlg),
+ save_as ? _("_Save As…") : _("_Save"),
+ GTK_RESPONSE_YES);
+ gtk_dialog_set_default_response (GTK_DIALOG (dlg),
+ GTK_RESPONSE_YES);
+}
+
+static gchar *
+get_text_secondary_label (GeditDocument *doc)
+{
+ glong seconds;
+ gchar *secondary_msg;
+
+ seconds = MAX (1, _gedit_document_get_seconds_since_last_save_or_load (doc));
+
+ if (seconds < 55)
+ {
+ secondary_msg = g_strdup_printf (
+ ngettext ("If you don’t save, changes from the last %ld second "
+ "will be permanently lost.",
+ "If you don’t save, changes from the last %ld seconds "
+ "will be permanently lost.",
+ seconds),
+ seconds);
+ }
+ else if (seconds < 75) /* 55 <= seconds < 75 */
+ {
+ secondary_msg = g_strdup (_("If you don’t save, changes from the last minute "
+ "will be permanently lost."));
+ }
+ else if (seconds < 110) /* 75 <= seconds < 110 */
+ {
+ secondary_msg = g_strdup_printf (
+ ngettext ("If you don’t save, changes from the last minute and %ld "
+ "second will be permanently lost.",
+ "If you don’t save, changes from the last minute and %ld "
+ "seconds will be permanently lost.",
+ seconds - 60 ),
+ seconds - 60);
+ }
+ else if (seconds < 3600)
+ {
+ secondary_msg = g_strdup_printf (
+ ngettext ("If you don’t save, changes from the last %ld minute "
+ "will be permanently lost.",
+ "If you don’t save, changes from the last %ld minutes "
+ "will be permanently lost.",
+ seconds / 60),
+ seconds / 60);
+ }
+ else if (seconds < 7200)
+ {
+ gint minutes;
+ seconds -= 3600;
+
+ minutes = seconds / 60;
+ if (minutes < 5)
+ {
+ secondary_msg = g_strdup (_("If you don’t save, changes from the last hour "
+ "will be permanently lost."));
+ }
+ else
+ {
+ secondary_msg = g_strdup_printf (
+ ngettext ("If you don’t save, changes from the last hour and %d "
+ "minute will be permanently lost.",
+ "If you don’t save, changes from the last hour and %d "
+ "minutes will be permanently lost.",
+ minutes),
+ minutes);
+ }
+ }
+ else
+ {
+ gint hours;
+
+ hours = seconds / 3600;
+
+ secondary_msg = g_strdup_printf (
+ ngettext ("If you don’t save, changes from the last %d hour "
+ "will be permanently lost.",
+ "If you don’t save, changes from the last %d hours "
+ "will be permanently lost.",
+ hours),
+ hours);
+ }
+
+ return secondary_msg;
+}
+
+static void
+build_single_doc_dialog (GeditCloseConfirmationDialog *dlg)
+{
+ GeditDocument *doc;
+ gchar *doc_name;
+ gchar *str;
+ gchar *markup_str;
+
+ g_return_if_fail (dlg->unsaved_documents->data != NULL);
+ doc = GEDIT_DOCUMENT (dlg->unsaved_documents->data);
+
+ add_buttons (dlg);
+
+ /* Primary message */
+ doc_name = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc)));
+ str = g_markup_printf_escaped (_("Save changes to document “%s” before closing?"), doc_name);
+ g_free (doc_name);
+
+ markup_str = g_strconcat ("<span weight=\"bold\" size=\"larger\">", str, "</span>", NULL);
+ g_free (str);
+
+ gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dlg), markup_str);
+ g_free (markup_str);
+
+ /* Secondary message */
+ str = get_text_secondary_label (doc);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dlg), "%s", str);
+ g_free (str);
+}
+
+static GtkWidget *
+create_list_box (GeditCloseConfirmationDialog *dlg)
+{
+ GtkWidget *list_box;
+ GList *l;
+
+ list_box = gtk_list_box_new ();
+
+ for (l = dlg->unsaved_documents; l != NULL; l = l->next)
+ {
+ GeditDocument *doc = l->data;
+ gchar *name;
+ GtkWidget *check_button;
+ GtkWidget *row;
+
+ name = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc)));
+ check_button = gtk_check_button_new_with_label (name);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), TRUE);
+ gtk_widget_set_halign (check_button, GTK_ALIGN_START);
+ g_free (name);
+
+ row = gtk_list_box_row_new ();
+ gtk_container_add (GTK_CONTAINER (row), check_button);
+ gtk_widget_show_all (row);
+
+ g_object_set_data_full (G_OBJECT (row),
+ GEDIT_SAVE_DOCUMENT_KEY,
+ g_object_ref (doc),
+ (GDestroyNotify) g_object_unref);
+
+ gtk_list_box_insert (GTK_LIST_BOX (list_box), row, -1);
+ }
+
+ return list_box;
+}
+
+static void
+build_multiple_docs_dialog (GeditCloseConfirmationDialog *dlg)
+{
+ GtkWidget *content_area;
+ GtkWidget *vbox;
+ GtkWidget *select_label;
+ GtkWidget *scrolledwindow;
+ GtkWidget *secondary_label;
+ gchar *str;
+ gchar *markup_str;
+
+ add_buttons (dlg);
+
+ gtk_window_set_resizable (GTK_WINDOW (dlg), TRUE);
+
+ /* Primary message */
+ str = g_strdup_printf (
+ ngettext ("There is %d document with unsaved changes. "
+ "Save changes before closing?",
+ "There are %d documents with unsaved changes. "
+ "Save changes before closing?",
+ g_list_length (dlg->unsaved_documents)),
+ g_list_length (dlg->unsaved_documents));
+
+ markup_str = g_strconcat ("<span weight=\"bold\" size=\"larger\">", str, "</span>", NULL);
+ g_free (str);
+
+ gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dlg), markup_str);
+ g_free (markup_str);
+
+ /* List of unsaved documents */
+ content_area = gtk_dialog_get_content_area (GTK_DIALOG (dlg));
+ gtk_box_set_spacing (GTK_BOX (content_area), 10);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
+ gtk_widget_set_margin_start (vbox, 30);
+ gtk_widget_set_margin_end (vbox, 30);
+ gtk_widget_set_margin_bottom (vbox, 12);
+ gtk_box_pack_start (GTK_BOX (content_area), vbox, TRUE, TRUE, 0);
+
+ select_label = gtk_label_new_with_mnemonic (_("S_elect the documents you want to save:"));
+ gtk_box_pack_start (GTK_BOX (vbox), select_label, FALSE, FALSE, 0);
+ gtk_label_set_line_wrap (GTK_LABEL (select_label), TRUE);
+ gtk_label_set_max_width_chars (GTK_LABEL (select_label), 72);
+ gtk_widget_set_halign (select_label, GTK_ALIGN_START);
+
+ scrolledwindow = gtk_scrolled_window_new (NULL, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), scrolledwindow, TRUE, TRUE, 0);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow),
+ GTK_SHADOW_IN);
+ gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (scrolledwindow), 90);
+
+ dlg->list_box = create_list_box (dlg);
+ gtk_container_add (GTK_CONTAINER (scrolledwindow), dlg->list_box);
+
+ /* Secondary label */
+ secondary_label = gtk_label_new (_("If you don’t save, "
+ "all your changes will be permanently lost."));
+ gtk_box_pack_start (GTK_BOX (vbox), secondary_label, FALSE, FALSE, 0);
+ gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE);
+ gtk_widget_set_halign (secondary_label, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (secondary_label, GTK_ALIGN_START);
+ gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE);
+ gtk_label_set_max_width_chars (GTK_LABEL (secondary_label), 72);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (select_label), dlg->list_box);
+
+ gtk_widget_show_all (vbox);
+}
+
+static void
+set_unsaved_document (GeditCloseConfirmationDialog *dlg,
+ const GList *list)
+{
+ g_return_if_fail (list != NULL);
+
+ g_return_if_fail (dlg->unsaved_documents == NULL);
+
+ dlg->unsaved_documents = g_list_copy ((GList *)list);
+
+ if (GET_MODE (dlg) == SINGLE_DOC_MODE)
+ {
+ build_single_doc_dialog (dlg);
+ }
+ else
+ {
+ build_multiple_docs_dialog (dlg);
+ }
+}
+
+const GList *
+gedit_close_confirmation_dialog_get_unsaved_documents (GeditCloseConfirmationDialog *dlg)
+{
+ g_return_val_if_fail (GEDIT_IS_CLOSE_CONFIRMATION_DIALOG (dlg), NULL);
+
+ return dlg->unsaved_documents;
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-close-confirmation-dialog.h b/gedit/gedit-close-confirmation-dialog.h
new file mode 100644
index 0000000..cfdb06e
--- /dev/null
+++ b/gedit/gedit-close-confirmation-dialog.h
@@ -0,0 +1,46 @@
+/*
+ * gedit-close-confirmation-dialog.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2004-2005 GNOME Foundation
+ * Copyright (C) 2015 Sébastien Wilmet
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_CLOSE_CONFIRMATION_DIALOG_H
+#define GEDIT_CLOSE_CONFIRMATION_DIALOG_H
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <gedit/gedit-document.h>
+
+#define GEDIT_TYPE_CLOSE_CONFIRMATION_DIALOG (gedit_close_confirmation_dialog_get_type ())
+
+G_DECLARE_FINAL_TYPE (GeditCloseConfirmationDialog, gedit_close_confirmation_dialog,
+ GEDIT, CLOSE_CONFIRMATION_DIALOG,
+ GtkMessageDialog)
+
+GtkWidget *gedit_close_confirmation_dialog_new (GtkWindow *parent,
+ GList *unsaved_documents);
+
+GtkWidget *gedit_close_confirmation_dialog_new_single (GtkWindow *parent,
+ GeditDocument *doc);
+
+const GList *gedit_close_confirmation_dialog_get_unsaved_documents (GeditCloseConfirmationDialog *dlg);
+
+GList *gedit_close_confirmation_dialog_get_selected_documents (GeditCloseConfirmationDialog *dlg);
+
+#endif /* GEDIT_CLOSE_CONFIRMATION_DIALOG_H */
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-commands-documents.c b/gedit/gedit-commands-documents.c
new file mode 100644
index 0000000..34b7846
--- /dev/null
+++ b/gedit/gedit-commands-documents.c
@@ -0,0 +1,106 @@
+/*
+ * gedit-documents-commands.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-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-commands.h"
+#include "gedit-commands-private.h"
+
+#include <gtk/gtk.h>
+
+#include "gedit-window.h"
+#include "gedit-notebook.h"
+#include "gedit-multi-notebook.h"
+#include "gedit-debug.h"
+
+void
+_gedit_cmd_documents_previous_document (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GtkNotebook *notebook;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ notebook = GTK_NOTEBOOK (_gedit_window_get_notebook (window));
+ gtk_notebook_prev_page (notebook);
+}
+
+void
+_gedit_cmd_documents_next_document (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GtkNotebook *notebook;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ notebook = GTK_NOTEBOOK (_gedit_window_get_notebook (window));
+ gtk_notebook_next_page (notebook);
+}
+
+void
+_gedit_cmd_documents_move_to_new_window (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditTab *tab;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ tab = gedit_window_get_active_tab (window);
+
+ if (tab == NULL)
+ return;
+
+ _gedit_window_move_tab_to_new_window (window, tab);
+}
+
+/* Methods releated with the tab groups */
+void
+_gedit_cmd_documents_new_tab_group (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ gedit_multi_notebook_add_new_notebook (GEDIT_MULTI_NOTEBOOK (_gedit_window_get_multi_notebook (GEDIT_WINDOW (user_data))));
+}
+
+void
+_gedit_cmd_documents_previous_tab_group (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ gedit_multi_notebook_previous_notebook (GEDIT_MULTI_NOTEBOOK (_gedit_window_get_multi_notebook (GEDIT_WINDOW (user_data))));
+}
+
+void
+_gedit_cmd_documents_next_tab_group (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ gedit_multi_notebook_next_notebook (GEDIT_MULTI_NOTEBOOK (_gedit_window_get_multi_notebook (GEDIT_WINDOW (user_data))));
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-commands-edit.c b/gedit/gedit-commands-edit.c
new file mode 100644
index 0000000..66ea017
--- /dev/null
+++ b/gedit/gedit-commands-edit.c
@@ -0,0 +1,205 @@
+/*
+ * gedit-commands-edit.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-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-commands.h"
+#include "gedit-commands-private.h"
+
+#include <gtk/gtk.h>
+
+#include "gedit-window.h"
+#include "gedit-debug.h"
+#include "gedit-view.h"
+#include "gedit-preferences-dialog.h"
+
+void
+_gedit_cmd_edit_undo (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditView *active_view;
+ GtkSourceBuffer *active_document;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ active_view = gedit_window_get_active_view (window);
+ g_return_if_fail (active_view != NULL);
+
+ active_document = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (active_view)));
+
+ gtk_source_buffer_undo (active_document);
+
+ tepl_view_scroll_to_cursor (TEPL_VIEW (active_view));
+
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+}
+
+void
+_gedit_cmd_edit_redo (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditView *active_view;
+ GtkSourceBuffer *active_document;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ active_view = gedit_window_get_active_view (window);
+ g_return_if_fail (active_view != NULL);
+
+ active_document = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (active_view)));
+
+ gtk_source_buffer_redo (active_document);
+
+ tepl_view_scroll_to_cursor (TEPL_VIEW (active_view));
+
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+}
+
+void
+_gedit_cmd_edit_cut (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditView *active_view;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ active_view = gedit_window_get_active_view (window);
+ g_return_if_fail (active_view != NULL);
+
+ tepl_view_cut_clipboard (TEPL_VIEW (active_view));
+
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+}
+
+void
+_gedit_cmd_edit_copy (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditView *active_view;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ active_view = gedit_window_get_active_view (window);
+ g_return_if_fail (active_view != NULL);
+
+ tepl_view_copy_clipboard (TEPL_VIEW (active_view));
+
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+}
+
+void
+_gedit_cmd_edit_paste (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditView *active_view;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ active_view = gedit_window_get_active_view (window);
+ g_return_if_fail (active_view != NULL);
+
+ tepl_view_paste_clipboard (TEPL_VIEW (active_view));
+
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+}
+
+void
+_gedit_cmd_edit_delete (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditView *active_view;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ active_view = gedit_window_get_active_view (window);
+ g_return_if_fail (active_view != NULL);
+
+ tepl_view_delete_selection (TEPL_VIEW (active_view));
+
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+}
+
+void
+_gedit_cmd_edit_select_all (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditView *active_view;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ active_view = gedit_window_get_active_view (window);
+ g_return_if_fail (active_view != NULL);
+
+ tepl_view_select_all (TEPL_VIEW (active_view));
+
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+}
+
+void
+_gedit_cmd_edit_preferences (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ gedit_show_preferences_dialog (window);
+}
+
+void
+_gedit_cmd_edit_overwrite_mode (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditView *active_view;
+ gboolean overwrite;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ active_view = gedit_window_get_active_view (window);
+ g_return_if_fail (active_view);
+
+ overwrite = g_variant_get_boolean (state);
+ g_simple_action_set_state (action, state);
+
+ gtk_text_view_set_overwrite (GTK_TEXT_VIEW (active_view), overwrite);
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+}
+
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-commands-file-print.c b/gedit/gedit-commands-file-print.c
new file mode 100644
index 0000000..425a9be
--- /dev/null
+++ b/gedit/gedit-commands-file-print.c
@@ -0,0 +1,49 @@
+/*
+ * gedit-commands-file-print.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-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 "gedit-commands.h"
+#include "gedit-commands-private.h"
+
+#include "gedit-window.h"
+#include "gedit-tab.h"
+#include "gedit-tab-private.h"
+#include "gedit-debug.h"
+
+void
+_gedit_cmd_file_print (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditTab *tab;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ tab = gedit_window_get_active_tab (window);
+
+ if (tab != NULL)
+ {
+ _gedit_tab_print (tab);
+ }
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-commands-file.c b/gedit/gedit-commands-file.c
new file mode 100644
index 0000000..c430e48
--- /dev/null
+++ b/gedit/gedit-commands-file.c
@@ -0,0 +1,2169 @@
+/*
+ * gedit-commands-file.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2005 Paolo Maggi
+ * Copyright (C) 2014 Sébastien Wilmet
+ *
+ * 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-commands.h"
+#include "gedit-commands-private.h"
+
+#include <glib/gi18n.h>
+#include <tepl/tepl.h>
+
+#include "gedit-app.h"
+#include "gedit-debug.h"
+#include "gedit-document.h"
+#include "gedit-document-private.h"
+#include "gedit-tab.h"
+#include "gedit-tab-private.h"
+#include "gedit-window.h"
+#include "gedit-notebook.h"
+#include "gedit-statusbar.h"
+#include "gedit-utils.h"
+#include "gedit-file-chooser-dialog.h"
+#include "gedit-file-chooser-open.h"
+#include "gedit-close-confirmation-dialog.h"
+
+/* useful macro */
+#define GBOOLEAN_TO_POINTER(i) (GINT_TO_POINTER ((i) ? 2 : 1))
+#define GPOINTER_TO_BOOLEAN(i) ((gboolean) ((GPOINTER_TO_INT(i) == 2) ? TRUE : FALSE))
+
+#define GEDIT_IS_CLOSING_ALL "gedit-is-closing-all"
+#define GEDIT_NOTEBOOK_TO_CLOSE "gedit-notebook-to-close"
+#define GEDIT_IS_QUITTING "gedit-is-quitting"
+#define GEDIT_IS_QUITTING_ALL "gedit-is-quitting-all"
+
+void
+_gedit_cmd_file_new (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ gedit_window_create_tab (window, TRUE);
+}
+
+static GeditTab *
+get_tab_from_file (GList *docs,
+ GFile *file)
+{
+ GList *l;
+
+ for (l = docs; l != NULL; l = l->next)
+ {
+ GeditDocument *doc;
+ GtkSourceFile *source_file;
+ GFile *location;
+
+ doc = l->data;
+ source_file = gedit_document_get_file (doc);
+ location = gtk_source_file_get_location (source_file);
+
+ if (location != NULL && g_file_equal (location, file))
+ {
+ return gedit_tab_get_from_document (doc);
+ }
+ }
+
+ return NULL;
+}
+
+static gboolean
+is_duplicated_file (GSList *files,
+ GFile *file)
+{
+ GSList *l;
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ if (g_file_equal (l->data, file))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* File loading */
+static GSList *
+load_file_list (GeditWindow *window,
+ const GSList *files,
+ const GtkSourceEncoding *encoding,
+ gint line_pos,
+ gint column_pos,
+ gboolean create)
+{
+ GList *win_docs;
+ GSList *files_to_load = NULL;
+ GSList *loaded_files = NULL;
+ GeditTab *tab;
+ gboolean jump_to = TRUE; /* Whether to jump to the new tab */
+ const GSList *l;
+ gint num_loaded_files = 0;
+ GeditStatusbar *statusbar;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ win_docs = gedit_window_get_documents (window);
+
+ /* Remove the files corresponding to documents already opened in
+ * "window" and remove duplicates from the "files" list.
+ */
+ for (l = files; l != NULL; l = l->next)
+ {
+ GFile *file = l->data;
+
+ if (is_duplicated_file (files_to_load, file))
+ {
+ continue;
+ }
+
+ tab = get_tab_from_file (win_docs, file);
+
+ if (tab == NULL)
+ {
+ files_to_load = g_slist_prepend (files_to_load, file);
+ }
+ else
+ {
+ if (l == files)
+ {
+ TeplView *view;
+
+ gedit_window_set_active_tab (window, tab);
+ jump_to = FALSE;
+ view = TEPL_VIEW (gedit_tab_get_view (tab));
+
+ if (line_pos > 0)
+ {
+ if (column_pos > 0)
+ {
+ tepl_view_goto_line_offset (view,
+ line_pos - 1,
+ column_pos - 1);
+ }
+ else
+ {
+ tepl_view_goto_line (view, line_pos - 1);
+ }
+ }
+ }
+
+ ++num_loaded_files;
+ loaded_files = g_slist_prepend (loaded_files,
+ gedit_tab_get_document (tab));
+ }
+ }
+
+ g_list_free (win_docs);
+
+ if (files_to_load == NULL)
+ {
+ return g_slist_reverse (loaded_files);
+ }
+
+ files_to_load = g_slist_reverse (files_to_load);
+ l = files_to_load;
+
+ tab = gedit_window_get_active_tab (window);
+ if (tab != NULL)
+ {
+ GeditDocument *doc;
+
+ doc = gedit_tab_get_document (tab);
+
+ if (tepl_buffer_is_untouched (TEPL_BUFFER (doc)) &&
+ gedit_tab_get_state (tab) == GEDIT_TAB_STATE_NORMAL)
+ {
+ _gedit_tab_load (tab,
+ l->data,
+ encoding,
+ line_pos,
+ column_pos,
+ create);
+
+ /* make sure the view has focus */
+ gtk_widget_grab_focus (GTK_WIDGET (gedit_tab_get_view (tab)));
+
+ l = g_slist_next (l);
+ jump_to = FALSE;
+
+ ++num_loaded_files;
+ loaded_files = g_slist_prepend (loaded_files,
+ gedit_tab_get_document (tab));
+ }
+ }
+
+ while (l != NULL)
+ {
+ g_return_val_if_fail (l->data != NULL, NULL);
+
+ tab = gedit_window_create_tab_from_location (window,
+ l->data,
+ encoding,
+ line_pos,
+ column_pos,
+ create,
+ jump_to);
+
+ if (tab != NULL)
+ {
+ jump_to = FALSE;
+
+ ++num_loaded_files;
+ loaded_files = g_slist_prepend (loaded_files,
+ gedit_tab_get_document (tab));
+ }
+
+ l = g_slist_next (l);
+ }
+
+ loaded_files = g_slist_reverse (loaded_files);
+
+ statusbar = GEDIT_STATUSBAR (gedit_window_get_statusbar (window));
+
+ if (num_loaded_files == 1)
+ {
+ GeditDocument *doc;
+ gchar *full_name;
+
+ g_return_val_if_fail (tab != NULL, loaded_files);
+
+ doc = gedit_tab_get_document (tab);
+ full_name = tepl_file_get_full_name (tepl_buffer_get_file (TEPL_BUFFER (doc)));
+
+ _gedit_statusbar_flash_generic_message (statusbar,
+ _("Loading file “%s”\342\200\246"),
+ full_name);
+
+ g_free (full_name);
+ }
+ else
+ {
+ _gedit_statusbar_flash_generic_message (statusbar,
+ ngettext ("Loading %d file\342\200\246",
+ "Loading %d files\342\200\246",
+ num_loaded_files),
+ num_loaded_files);
+ }
+
+ g_slist_free (files_to_load);
+
+ return loaded_files;
+}
+
+/**
+ * gedit_commands_load_location:
+ * @window: a #GeditWindow
+ * @location: a #GFile to load
+ * @encoding: (allow-none): the #GtkSourceEncoding of @location
+ * @line_pos: the line position to place the cursor
+ * @column_pos: the line column to place the cursor
+ *
+ * Loads @location. Ignores non-existing locations.
+ */
+void
+gedit_commands_load_location (GeditWindow *window,
+ GFile *location,
+ const GtkSourceEncoding *encoding,
+ gint line_pos,
+ gint column_pos)
+{
+ GSList *locations = NULL;
+ gchar *uri;
+ GSList *ret;
+
+ g_return_if_fail (GEDIT_IS_WINDOW (window));
+ g_return_if_fail (G_IS_FILE (location));
+ g_return_if_fail (gedit_utils_is_valid_location (location));
+
+ uri = g_file_get_uri (location);
+ gedit_debug_message (DEBUG_COMMANDS, "Loading URI '%s'", uri);
+ g_free (uri);
+
+ locations = g_slist_prepend (locations, location);
+
+ ret = load_file_list (window, locations, encoding, line_pos, column_pos, FALSE);
+ g_slist_free (ret);
+
+ g_slist_free (locations);
+}
+
+/**
+ * gedit_commands_load_locations:
+ * @window: a #GeditWindow
+ * @locations: (element-type Gio.File): the locations to load
+ * @encoding: (allow-none): the #GtkSourceEncoding
+ * @line_pos: the line position to place the cursor
+ * @column_pos: the line column to place the cursor
+ *
+ * Loads @locations. Ignore non-existing locations.
+ *
+ * Returns: (element-type Gedit.Document) (transfer container): the locations
+ * that were loaded.
+ */
+GSList *
+gedit_commands_load_locations (GeditWindow *window,
+ const GSList *locations,
+ const GtkSourceEncoding *encoding,
+ gint line_pos,
+ gint column_pos)
+{
+ g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL);
+ g_return_val_if_fail (locations != NULL && locations->data != NULL, NULL);
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ return load_file_list (window, locations, encoding, line_pos, column_pos, FALSE);
+}
+
+/*
+ * From the command line we can specify a line position for the
+ * first doc. Beside specifying a non-existing file creates a
+ * titled document.
+ */
+GSList *
+_gedit_cmd_load_files_from_prompt (GeditWindow *window,
+ GSList *files,
+ const GtkSourceEncoding *encoding,
+ gint line_pos,
+ gint column_pos)
+{
+ gedit_debug (DEBUG_COMMANDS);
+
+ return load_file_list (window, files, encoding, line_pos, column_pos, TRUE);
+}
+
+static void
+file_chooser_open_done_cb (GeditFileChooserOpen *file_chooser,
+ gboolean accept,
+ GeditWindow *window)
+{
+ GSList *files;
+ const GtkSourceEncoding *encoding;
+ gchar *folder_uri;
+ GSList *loaded_documents;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ if (!accept)
+ {
+ g_object_unref (file_chooser);
+ return;
+ }
+
+ files = _gedit_file_chooser_open_get_files (file_chooser);
+ encoding = _gedit_file_chooser_get_encoding (GEDIT_FILE_CHOOSER (file_chooser));
+ folder_uri = _gedit_file_chooser_get_current_folder_uri (GEDIT_FILE_CHOOSER (file_chooser));
+ g_object_unref (file_chooser);
+
+ if (window == NULL)
+ {
+ window = gedit_app_create_window (GEDIT_APP (g_application_get_default ()), NULL);
+
+ gtk_widget_show (GTK_WIDGET (window));
+ gtk_window_present (GTK_WINDOW (window));
+ }
+
+ /* Remember the folder we navigated to. */
+ _gedit_window_set_file_chooser_folder_uri (window, GTK_FILE_CHOOSER_ACTION_OPEN, folder_uri);
+ g_free (folder_uri);
+
+ loaded_documents = gedit_commands_load_locations (window, files, encoding, 0, 0);
+
+ g_slist_free (loaded_documents);
+ g_slist_free_full (files, g_object_unref);
+}
+
+void
+_gedit_cmd_file_open (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = NULL;
+ GeditFileChooserOpen *file_chooser;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ if (user_data != NULL)
+ {
+ window = GEDIT_WINDOW (user_data);
+ }
+
+ file_chooser = _gedit_file_chooser_open_new ();
+
+ if (window != NULL)
+ {
+ const gchar *folder_uri;
+
+ _gedit_file_chooser_set_transient_for (GEDIT_FILE_CHOOSER (file_chooser),
+ GTK_WINDOW (window));
+
+ folder_uri = _gedit_window_get_file_chooser_folder_uri (window, GTK_FILE_CHOOSER_ACTION_OPEN);
+ if (folder_uri != NULL)
+ {
+ _gedit_file_chooser_set_current_folder_uri (GEDIT_FILE_CHOOSER (file_chooser),
+ folder_uri);
+ }
+ }
+
+ g_signal_connect (file_chooser,
+ "done",
+ G_CALLBACK (file_chooser_open_done_cb),
+ window);
+
+ _gedit_file_chooser_show (GEDIT_FILE_CHOOSER (file_chooser));
+}
+
+void
+_gedit_cmd_file_reopen_closed_tab (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GFile *file;
+
+ file = _gedit_window_pop_last_closed_doc (window);
+ if (file != NULL)
+ {
+ gedit_commands_load_location (window, file, NULL, 0, 0);
+ }
+}
+
+/* File saving */
+
+/* FIXME: modify this dialog to be similar to the one provided by gtk+ for
+ * already existing files - Paolo (Oct. 11, 2005) */
+static gboolean
+replace_read_only_file (GtkWindow *parent,
+ GFile *file)
+{
+ GtkWidget *dialog;
+ gint ret;
+ gchar *parse_name;
+ gchar *name_for_display;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ parse_name = g_file_get_parse_name (file);
+
+ /* Truncate the name so it doesn't get insanely wide. Note that even
+ * though the dialog uses wrapped text, if the name doesn't contain
+ * white space then the text-wrapping code is too stupid to wrap it.
+ */
+ name_for_display = tepl_utils_str_middle_truncate (parse_name, 50);
+ g_free (parse_name);
+
+ dialog = gtk_message_dialog_new (parent,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ _("The file “%s” is read-only."),
+ name_for_display);
+ g_free (name_for_display);
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("Do you want to try to replace it "
+ "with the one you are saving?"));
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Replace"), GTK_RESPONSE_YES,
+ NULL);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ GTK_RESPONSE_CANCEL);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ ret = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (dialog);
+
+ return ret == GTK_RESPONSE_YES;
+}
+
+static gboolean
+change_compression (GtkWindow *parent,
+ GFile *file,
+ gboolean compressed)
+{
+ GtkWidget *dialog;
+ gint ret;
+ gchar *parse_name;
+ gchar *name_for_display;
+ const gchar *primary_message;
+ const gchar *button_label;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ parse_name = g_file_get_parse_name (file);
+
+ /* Truncate the name so it doesn't get insanely wide. Note that even
+ * though the dialog uses wrapped text, if the name doesn't contain
+ * white space then the text-wrapping code is too stupid to wrap it.
+ */
+ name_for_display = tepl_utils_str_middle_truncate (parse_name, 50);
+ g_free (parse_name);
+
+ if (compressed)
+ {
+ primary_message = _("Save the file using compression?");
+ }
+ else
+ {
+ primary_message = _("Save the file as plain text?");
+ }
+
+ dialog = gtk_message_dialog_new (parent,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ "%s",
+ primary_message);
+
+ if (compressed)
+ {
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("The file “%s” was previously saved as plain "
+ "text and will now be saved using compression."),
+ name_for_display);
+
+ button_label = _("_Save Using Compression");
+ }
+ else
+ {
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("The file “%s” was previously saved "
+ "using compression and will now be saved as plain text."),
+ name_for_display);
+ button_label = _("_Save As Plain Text");
+ }
+
+ g_free (name_for_display);
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ button_label, GTK_RESPONSE_YES,
+ NULL);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ GTK_RESPONSE_CANCEL);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ ret = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (dialog);
+
+ return ret == GTK_RESPONSE_YES;
+}
+
+static GtkSourceCompressionType
+get_compression_type_from_file (GFile *file)
+{
+ gchar *name;
+ gchar *content_type;
+ GtkSourceCompressionType type;
+
+ name = g_file_get_basename (file);
+ content_type = g_content_type_guess (name, NULL, 0, NULL);
+
+ type = gedit_utils_get_compression_type_from_content_type (content_type);
+
+ g_free (name);
+ g_free (content_type);
+
+ return type;
+}
+
+static void
+tab_save_as_ready_cb (GeditTab *tab,
+ GAsyncResult *result,
+ GTask *task)
+{
+ gboolean success = _gedit_tab_save_finish (tab, result);
+ g_task_return_boolean (task, success);
+ g_object_unref (task);
+}
+
+static void
+save_dialog_response_cb (GeditFileChooserDialog *dialog,
+ gint response_id,
+ GTask *task)
+{
+ GeditTab *tab;
+ GeditWindow *window;
+ GeditDocument *doc;
+ GtkSourceFile *file;
+ GFile *location;
+ gchar *parse_name;
+ GtkSourceNewlineType newline_type;
+ GtkSourceCompressionType compression_type;
+ GtkSourceCompressionType current_compression_type;
+ const GtkSourceEncoding *encoding;
+ GeditStatusbar *statusbar;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ tab = g_task_get_source_object (task);
+ window = g_task_get_task_data (task);
+
+ if (response_id != GTK_RESPONSE_ACCEPT)
+ {
+ gedit_file_chooser_dialog_destroy (dialog);
+ g_task_return_boolean (task, FALSE);
+ g_object_unref (task);
+ return;
+ }
+
+ doc = gedit_tab_get_document (tab);
+ file = gedit_document_get_file (doc);
+
+ location = gedit_file_chooser_dialog_get_file (dialog);
+ g_return_if_fail (location != NULL);
+
+ compression_type = get_compression_type_from_file (location);
+ current_compression_type = gtk_source_file_get_compression_type (file);
+
+ if ((compression_type == GTK_SOURCE_COMPRESSION_TYPE_NONE) !=
+ (current_compression_type == GTK_SOURCE_COMPRESSION_TYPE_NONE))
+ {
+ GtkWindow *dialog_window = gedit_file_chooser_dialog_get_window (dialog);
+
+ if (!change_compression (dialog_window,
+ location,
+ compression_type != GTK_SOURCE_COMPRESSION_TYPE_NONE))
+ {
+ gedit_file_chooser_dialog_destroy (dialog);
+ g_object_unref (location);
+
+ g_task_return_boolean (task, FALSE);
+ g_object_unref (task);
+ return;
+ }
+ }
+
+ encoding = gedit_file_chooser_dialog_get_encoding (dialog);
+ newline_type = gedit_file_chooser_dialog_get_newline_type (dialog);
+
+ gedit_file_chooser_dialog_destroy (dialog);
+
+ parse_name = g_file_get_parse_name (location);
+
+ statusbar = GEDIT_STATUSBAR (gedit_window_get_statusbar (window));
+ _gedit_statusbar_flash_generic_message (statusbar,
+ _("Saving file “%s”\342\200\246"),
+ parse_name);
+
+ g_free (parse_name);
+
+ /* Let's remember the dir we navigated to, even if the saving fails... */
+ {
+ GFile *folder;
+
+ folder = g_file_get_parent (location);
+ if (folder != NULL)
+ {
+ gchar *folder_uri;
+
+ folder_uri = g_file_get_uri (folder);
+ _gedit_window_set_file_chooser_folder_uri (window,
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ folder_uri);
+
+ g_object_unref (folder);
+ g_free (folder_uri);
+ }
+ }
+
+ _gedit_tab_save_as_async (tab,
+ location,
+ encoding,
+ newline_type,
+ compression_type,
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback) tab_save_as_ready_cb,
+ task);
+
+ g_object_unref (location);
+}
+
+static GtkFileChooserConfirmation
+confirm_overwrite_callback (GeditFileChooserDialog *dialog,
+ gpointer data)
+{
+ GtkFileChooserConfirmation res;
+ GFile *file;
+ GFileInfo *info;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ /* fall back to the default confirmation dialog */
+ res = GTK_FILE_CHOOSER_CONFIRMATION_CONFIRM;
+
+ file = gedit_file_chooser_dialog_get_file (dialog);
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+
+ if (info != NULL)
+ {
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) &&
+ !g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+ {
+ GtkWindow *win;
+
+ win = gedit_file_chooser_dialog_get_window (dialog);
+
+ if (replace_read_only_file (win, file))
+ {
+ res = GTK_FILE_CHOOSER_CONFIRMATION_ACCEPT_FILENAME;
+ }
+ else
+ {
+ res = GTK_FILE_CHOOSER_CONFIRMATION_SELECT_AGAIN;
+ }
+ }
+
+ g_object_unref (info);
+ }
+
+ g_object_unref (file);
+
+ return res;
+}
+
+/* Call save_as_tab_finish() in @callback. */
+static void
+save_as_tab_async (GeditTab *tab,
+ GeditWindow *window,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ GeditFileChooserDialog *save_dialog;
+ GtkWindowGroup *window_group;
+ GtkWindow *dialog_window;
+ GeditDocument *doc;
+ GtkSourceFile *file;
+ GFile *location;
+ const GtkSourceEncoding *encoding;
+ GtkSourceNewlineType newline_type;
+
+ g_return_if_fail (GEDIT_IS_TAB (tab));
+ g_return_if_fail (GEDIT_IS_WINDOW (window));
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ task = g_task_new (tab, cancellable, callback, user_data);
+ g_task_set_task_data (task, g_object_ref (window), g_object_unref);
+
+ /* Translators: "Save As" is the title of the file chooser window. */
+ save_dialog = gedit_file_chooser_dialog_create (C_("window title", "Save As"),
+ GTK_WINDOW (window),
+ _("_Save"),
+ _("_Cancel"));
+
+ gedit_file_chooser_dialog_set_do_overwrite_confirmation (save_dialog, TRUE);
+
+ g_signal_connect (save_dialog,
+ "confirm-overwrite",
+ G_CALLBACK (confirm_overwrite_callback),
+ NULL);
+
+ window_group = gedit_window_get_group (window);
+
+ dialog_window = gedit_file_chooser_dialog_get_window (save_dialog);
+
+ if (dialog_window != NULL)
+ {
+ gtk_window_group_add_window (window_group, dialog_window);
+ }
+
+ /* Save As dialog is modal to its main window */
+ gedit_file_chooser_dialog_set_modal (save_dialog, TRUE);
+
+ /* Set the suggested file name */
+ doc = gedit_tab_get_document (tab);
+ file = gedit_document_get_file (doc);
+ location = gtk_source_file_get_location (file);
+
+ if (location != NULL)
+ {
+ gedit_file_chooser_dialog_set_file (save_dialog, location);
+ }
+ else
+ {
+ const gchar *default_folder_uri;
+ GFile *default_folder;
+ gchar *docname;
+
+ default_folder_uri = _gedit_window_get_file_chooser_folder_uri (window,
+ GTK_FILE_CHOOSER_ACTION_SAVE);
+ if (default_folder_uri != NULL)
+ {
+ default_folder = g_file_new_for_uri (default_folder_uri);
+ }
+ else
+ {
+ /* It's logical to take the home dir by default, and it fixes
+ * a problem on MS Windows (hang in C:\windows\system32).
+ *
+ * FIXME: it would be better to use GtkFileChooserNative
+ * to permanently fix the hang problem on MS Windows.
+ */
+ default_folder = g_file_new_for_path (g_get_home_dir ());
+ }
+
+ gedit_file_chooser_dialog_set_current_folder (save_dialog, default_folder);
+ g_object_unref (default_folder);
+
+ docname = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc)));
+ gedit_file_chooser_dialog_set_current_name (save_dialog, docname);
+ g_free (docname);
+ }
+
+ /* Set suggested encoding and newline type. */
+ encoding = gtk_source_file_get_encoding (file);
+
+ if (encoding == NULL)
+ {
+ encoding = gtk_source_encoding_get_utf8 ();
+ }
+
+ newline_type = gtk_source_file_get_newline_type (file);
+
+ gedit_file_chooser_dialog_set_encoding (GEDIT_FILE_CHOOSER_DIALOG (save_dialog),
+ encoding);
+
+ gedit_file_chooser_dialog_set_newline_type (GEDIT_FILE_CHOOSER_DIALOG (save_dialog),
+ newline_type);
+
+ g_signal_connect (save_dialog,
+ "response",
+ G_CALLBACK (save_dialog_response_cb),
+ task);
+
+ gedit_file_chooser_dialog_show (save_dialog);
+}
+
+static gboolean
+save_as_tab_finish (GeditTab *tab,
+ GAsyncResult *result)
+{
+ g_return_val_if_fail (g_task_is_valid (result, tab), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), NULL);
+}
+
+static void
+save_as_tab_ready_cb (GeditTab *tab,
+ GAsyncResult *result,
+ GTask *task)
+{
+ gboolean success = save_as_tab_finish (tab, result);
+
+ g_task_return_boolean (task, success);
+ g_object_unref (task);
+}
+
+static void
+tab_save_ready_cb (GeditTab *tab,
+ GAsyncResult *result,
+ GTask *task)
+{
+ gboolean success = _gedit_tab_save_finish (tab, result);
+
+ g_task_return_boolean (task, success);
+ g_object_unref (task);
+}
+
+/**
+ * gedit_commands_save_document_async:
+ * @document: the #GeditDocument to save.
+ * @window: a #GeditWindow.
+ * @cancellable: (nullable): optional #GCancellable object, %NULL to ignore.
+ * @callback: (scope async): a #GAsyncReadyCallback to call when the operation
+ * is finished.
+ * @user_data: (closure): the data to pass to the @callback function.
+ *
+ * Asynchronously save the @document. @document must belong to @window. The
+ * source object of the async task is @document (which will be the first
+ * parameter of the #GAsyncReadyCallback).
+ *
+ * When the operation is finished, @callback will be called. You can then call
+ * gedit_commands_save_document_finish() to get the result of the operation.
+ *
+ * Since: 3.14
+ */
+void
+gedit_commands_save_document_async (GeditDocument *document,
+ GeditWindow *window,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ GeditTab *tab;
+ GtkSourceFile *file;
+ gchar *full_name;
+ GeditStatusbar *statusbar;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ g_return_if_fail (GEDIT_IS_DOCUMENT (document));
+ g_return_if_fail (GEDIT_IS_WINDOW (window));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (document, cancellable, callback, user_data);
+
+ tab = gedit_tab_get_from_document (document);
+ file = gedit_document_get_file (document);
+
+ if (_gedit_document_is_untitled (document) ||
+ gtk_source_file_is_readonly (file))
+ {
+ gedit_debug_message (DEBUG_COMMANDS, "Untitled or Readonly");
+
+ save_as_tab_async (tab,
+ window,
+ cancellable,
+ (GAsyncReadyCallback) save_as_tab_ready_cb,
+ task);
+ return;
+ }
+
+ full_name = tepl_file_get_full_name (tepl_buffer_get_file (TEPL_BUFFER (document)));
+
+ statusbar = GEDIT_STATUSBAR (gedit_window_get_statusbar (window));
+ _gedit_statusbar_flash_generic_message (statusbar,
+ _("Saving file “%s”\342\200\246"),
+ full_name);
+
+ g_free (full_name);
+
+ _gedit_tab_save_async (tab,
+ cancellable,
+ (GAsyncReadyCallback) tab_save_ready_cb,
+ task);
+}
+
+/**
+ * gedit_commands_save_document_finish:
+ * @document: a #GeditDocument.
+ * @result: a #GAsyncResult.
+ *
+ * Finishes an asynchronous document saving operation started with
+ * gedit_commands_save_document_async().
+ *
+ * Note that there is no error parameter because the errors are already handled
+ * by gedit.
+ *
+ * Returns: %TRUE if the document has been correctly saved, %FALSE otherwise.
+ * Since: 3.14
+ */
+gboolean
+gedit_commands_save_document_finish (GeditDocument *document,
+ GAsyncResult *result)
+{
+ g_return_val_if_fail (g_task_is_valid (result, document), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), NULL);
+}
+
+static void
+save_tab_ready_cb (GeditDocument *doc,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ gedit_commands_save_document_finish (doc, result);
+}
+
+/* Save tab asynchronously, but without results. */
+static void
+save_tab (GeditTab *tab,
+ GeditWindow *window)
+{
+ GeditDocument *doc = gedit_tab_get_document (tab);
+
+ gedit_commands_save_document_async (doc,
+ window,
+ NULL,
+ (GAsyncReadyCallback) save_tab_ready_cb,
+ NULL);
+}
+
+void
+_gedit_cmd_file_save (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditTab *tab;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ tab = gedit_window_get_active_tab (window);
+ if (tab != NULL)
+ {
+ save_tab (tab, window);
+ }
+}
+
+static void
+_gedit_cmd_file_save_as_cb (GeditTab *tab,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ save_as_tab_finish (tab, result);
+}
+
+void
+_gedit_cmd_file_save_as (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditTab *tab;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ tab = gedit_window_get_active_tab (window);
+ if (tab != NULL)
+ {
+ save_as_tab_async (tab,
+ window,
+ NULL,
+ (GAsyncReadyCallback) _gedit_cmd_file_save_as_cb,
+ NULL);
+ }
+}
+
+static void
+quit_if_needed (GeditWindow *window)
+{
+ gboolean is_quitting;
+ gboolean is_quitting_all;
+
+ is_quitting = GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (window),
+ GEDIT_IS_QUITTING));
+
+ is_quitting_all = GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (window),
+ GEDIT_IS_QUITTING_ALL));
+
+ if (is_quitting)
+ {
+ gtk_widget_destroy (GTK_WIDGET (window));
+ }
+
+ if (is_quitting_all)
+ {
+ GtkApplication *app;
+
+ app = GTK_APPLICATION (g_application_get_default ());
+
+ if (gtk_application_get_windows (app) == NULL)
+ {
+ g_application_quit (G_APPLICATION (app));
+ }
+ }
+}
+
+static gboolean
+really_close_tab (GeditTab *tab)
+{
+ GtkWidget *toplevel;
+ GeditWindow *window;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ g_return_val_if_fail (gedit_tab_get_state (tab) == GEDIT_TAB_STATE_CLOSING,
+ FALSE);
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (tab));
+ g_return_val_if_fail (GEDIT_IS_WINDOW (toplevel), FALSE);
+
+ window = GEDIT_WINDOW (toplevel);
+
+ gedit_window_close_tab (window, tab);
+
+ if (gedit_window_get_active_tab (window) == NULL)
+ {
+ quit_if_needed (window);
+ }
+
+ return FALSE;
+}
+
+static void
+close_tab (GeditTab *tab)
+{
+ GeditDocument *doc;
+
+ doc = gedit_tab_get_document (tab);
+ g_return_if_fail (doc != NULL);
+
+ /* If the user has modified again the document, do not close the tab. */
+ if (_gedit_document_needs_saving (doc))
+ {
+ return;
+ }
+
+ /* Close the document only if it has been succesfully saved.
+ * Tab state is set to CLOSING (it is a state without exiting
+ * transitions) and the tab is closed in an idle handler.
+ */
+ _gedit_tab_mark_for_closing (tab);
+
+ g_idle_add_full (G_PRIORITY_HIGH_IDLE,
+ (GSourceFunc) really_close_tab,
+ tab,
+ NULL);
+}
+
+typedef struct _SaveAsData SaveAsData;
+
+struct _SaveAsData
+{
+ /* Reffed */
+ GeditWindow *window;
+
+ /* List of reffed GeditTab's */
+ GSList *tabs_to_save_as;
+
+ guint close_tabs : 1;
+};
+
+static void save_as_documents_list (SaveAsData *data);
+
+static void
+save_as_documents_list_cb (GeditTab *tab,
+ GAsyncResult *result,
+ SaveAsData *data)
+{
+ gboolean saved = save_as_tab_finish (tab, result);
+
+ if (saved && data->close_tabs)
+ {
+ close_tab (tab);
+ }
+
+ g_return_if_fail (tab == GEDIT_TAB (data->tabs_to_save_as->data));
+ g_object_unref (data->tabs_to_save_as->data);
+ data->tabs_to_save_as = g_slist_delete_link (data->tabs_to_save_as,
+ data->tabs_to_save_as);
+
+ if (data->tabs_to_save_as != NULL)
+ {
+ save_as_documents_list (data);
+ }
+ else
+ {
+ g_object_unref (data->window);
+ g_slice_free (SaveAsData, data);
+ }
+}
+
+static void
+save_as_documents_list (SaveAsData *data)
+{
+ GeditTab *next_tab = GEDIT_TAB (data->tabs_to_save_as->data);
+
+ gedit_window_set_active_tab (data->window, next_tab);
+
+ save_as_tab_async (next_tab,
+ data->window,
+ NULL,
+ (GAsyncReadyCallback) save_as_documents_list_cb,
+ data);
+}
+
+/*
+ * The docs in the list must belong to the same GeditWindow.
+ */
+static void
+save_documents_list (GeditWindow *window,
+ GList *docs)
+{
+ SaveAsData *data = NULL;
+ GList *l;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ g_return_if_fail ((gedit_window_get_state (window) & GEDIT_WINDOW_STATE_PRINTING) == 0);
+
+ for (l = docs; l != NULL; l = l->next)
+ {
+ GeditDocument *doc;
+ GeditTab *tab;
+ GeditTabState state;
+
+ g_return_if_fail (GEDIT_IS_DOCUMENT (l->data));
+ doc = l->data;
+
+ tab = gedit_tab_get_from_document (doc);
+ state = gedit_tab_get_state (tab);
+
+ g_return_if_fail (state != GEDIT_TAB_STATE_PRINTING);
+ g_return_if_fail (state != GEDIT_TAB_STATE_CLOSING);
+
+ if (state == GEDIT_TAB_STATE_NORMAL ||
+ state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW)
+ {
+ if (_gedit_document_needs_saving (doc))
+ {
+ GtkSourceFile *file = gedit_document_get_file (doc);
+
+ /* FIXME: manage the case of local readonly files owned by the
+ user is running gedit - Paolo (Dec. 8, 2005) */
+ if (_gedit_document_is_untitled (doc) ||
+ gtk_source_file_is_readonly (file))
+ {
+ if (data == NULL)
+ {
+ data = g_slice_new (SaveAsData);
+ data->window = g_object_ref (window);
+ data->tabs_to_save_as = NULL;
+ data->close_tabs = FALSE;
+ }
+
+ data->tabs_to_save_as = g_slist_prepend (data->tabs_to_save_as,
+ g_object_ref (tab));
+ }
+ else
+ {
+ save_tab (tab, window);
+ }
+ }
+ }
+ else
+ {
+ /* If the state is:
+ - GEDIT_TAB_STATE_LOADING: we do not save since we are sure the file is unmodified
+ - GEDIT_TAB_STATE_REVERTING: we do not save since the user wants
+ to return back to the version of the file she previously saved
+ - GEDIT_TAB_STATE_SAVING: well, we are already saving (no need to save again)
+ - GEDIT_TAB_STATE_PRINTING: there is not a
+ real reason for not saving in this case, we do not save to avoid to run
+ two operations using the message area at the same time (may be we can remove
+ this limitation in the future). Note that SaveAll, ClosAll
+ and Quit are unsensitive if the window state is PRINTING.
+ - GEDIT_TAB_STATE_GENERIC_ERROR: we do not save since the document contains
+ errors (I don't think this is a very frequent case, we should probably remove
+ this state)
+ - GEDIT_TAB_STATE_LOADING_ERROR: there is nothing to save
+ - GEDIT_TAB_STATE_REVERTING_ERROR: there is nothing to save and saving the current
+ document will overwrite the copy of the file the user wants to go back to
+ - GEDIT_TAB_STATE_SAVING_ERROR: we do not save since we just failed to save, so there is
+ no reason to automatically retry... we wait for user intervention
+ - GEDIT_TAB_STATE_CLOSING: this state is invalid in this case
+ */
+
+ gchar *full_name;
+
+ full_name = tepl_file_get_full_name (tepl_buffer_get_file (TEPL_BUFFER (doc)));
+ gedit_debug_message (DEBUG_COMMANDS,
+ "File '%s' not saved. State: %d",
+ full_name,
+ state);
+ g_free (full_name);
+ }
+ }
+
+ if (data != NULL)
+ {
+ data->tabs_to_save_as = g_slist_reverse (data->tabs_to_save_as);
+ save_as_documents_list (data);
+ }
+}
+
+/**
+ * gedit_commands_save_all_documents:
+ * @window: a #GeditWindow.
+ *
+ * Asynchronously save all documents belonging to @window. The result of the
+ * operation is not available, so it's difficult to know whether all the
+ * documents are correctly saved.
+ */
+void
+gedit_commands_save_all_documents (GeditWindow *window)
+{
+ GList *docs;
+
+ g_return_if_fail (GEDIT_IS_WINDOW (window));
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ docs = gedit_window_get_documents (window);
+
+ save_documents_list (window, docs);
+
+ g_list_free (docs);
+}
+
+void
+_gedit_cmd_file_save_all (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ gedit_commands_save_all_documents (GEDIT_WINDOW (user_data));
+}
+
+/**
+ * gedit_commands_save_document:
+ * @window: a #GeditWindow.
+ * @document: the #GeditDocument to save.
+ *
+ * Asynchronously save @document. @document must belong to @window. If you need
+ * the result of the operation, use gedit_commands_save_document_async().
+ */
+void
+gedit_commands_save_document (GeditWindow *window,
+ GeditDocument *document)
+{
+ GeditTab *tab;
+
+ g_return_if_fail (GEDIT_IS_WINDOW (window));
+ g_return_if_fail (GEDIT_IS_DOCUMENT (document));
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ tab = gedit_tab_get_from_document (document);
+ save_tab (tab, window);
+}
+
+/* File revert */
+static void
+do_revert (GeditWindow *window,
+ GeditTab *tab)
+{
+ GeditDocument *doc;
+ gchar *docname;
+ GeditStatusbar *statusbar;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ doc = gedit_tab_get_document (tab);
+ docname = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc)));
+
+ statusbar = GEDIT_STATUSBAR (gedit_window_get_statusbar (window));
+ _gedit_statusbar_flash_generic_message (statusbar,
+ _("Reverting the document “%s”\342\200\246"),
+ docname);
+
+ g_free (docname);
+
+ _gedit_tab_revert (tab);
+}
+
+static void
+revert_dialog_response_cb (GtkDialog *dialog,
+ gint response_id,
+ GeditWindow *window)
+{
+ GeditTab *tab;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ /* FIXME: we are relying on the fact that the dialog is
+ modal so the active tab can't be changed...
+ not very nice - Paolo (Oct 11, 2005) */
+ tab = gedit_window_get_active_tab (window);
+ if (tab == NULL)
+ {
+ return;
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ do_revert (window, tab);
+ }
+}
+
+static GtkWidget *
+revert_dialog (GeditWindow *window,
+ GeditDocument *doc)
+{
+ GtkWidget *dialog;
+ gchar *docname;
+ gchar *primary_msg;
+ gchar *secondary_msg;
+ glong seconds;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ docname = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc)));
+ primary_msg = g_strdup_printf (_("Revert unsaved changes to document “%s”?"),
+ docname);
+ g_free (docname);
+
+ seconds = MAX (1, _gedit_document_get_seconds_since_last_save_or_load (doc));
+
+ if (seconds < 55)
+ {
+ secondary_msg = g_strdup_printf (
+ ngettext ("Changes made to the document in the last %ld second "
+ "will be permanently lost.",
+ "Changes made to the document in the last %ld seconds "
+ "will be permanently lost.",
+ seconds),
+ seconds);
+ }
+ else if (seconds < 75) /* 55 <= seconds < 75 */
+ {
+ secondary_msg = g_strdup (_("Changes made to the document in the last minute "
+ "will be permanently lost."));
+ }
+ else if (seconds < 110) /* 75 <= seconds < 110 */
+ {
+ secondary_msg = g_strdup_printf (
+ ngettext ("Changes made to the document in the last minute and "
+ "%ld second will be permanently lost.",
+ "Changes made to the document in the last minute and "
+ "%ld seconds will be permanently lost.",
+ seconds - 60 ),
+ seconds - 60);
+ }
+ else if (seconds < 3600)
+ {
+ secondary_msg = g_strdup_printf (
+ ngettext ("Changes made to the document in the last %ld minute "
+ "will be permanently lost.",
+ "Changes made to the document in the last %ld minutes "
+ "will be permanently lost.",
+ seconds / 60),
+ seconds / 60);
+ }
+ else if (seconds < 7200)
+ {
+ gint minutes;
+ seconds -= 3600;
+
+ minutes = seconds / 60;
+ if (minutes < 5)
+ {
+ secondary_msg = g_strdup (_("Changes made to the document in the last hour "
+ "will be permanently lost."));
+ }
+ else
+ {
+ secondary_msg = g_strdup_printf (
+ ngettext ("Changes made to the document in the last hour and "
+ "%d minute will be permanently lost.",
+ "Changes made to the document in the last hour and "
+ "%d minutes will be permanently lost.",
+ minutes),
+ minutes);
+ }
+ }
+ else
+ {
+ gint hours;
+
+ hours = seconds / 3600;
+
+ secondary_msg = g_strdup_printf (
+ ngettext ("Changes made to the document in the last %d hour "
+ "will be permanently lost.",
+ "Changes made to the document in the last %d hours "
+ "will be permanently lost.",
+ hours),
+ hours);
+ }
+
+ dialog = gtk_message_dialog_new (GTK_WINDOW (window),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ "%s", primary_msg);
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", secondary_msg);
+ g_free (primary_msg);
+ g_free (secondary_msg);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Revert"), GTK_RESPONSE_OK,
+ NULL);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ GTK_RESPONSE_CANCEL);
+
+ return dialog;
+}
+
+void
+_gedit_cmd_file_revert (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditTab *tab;
+ GeditDocument *doc;
+ GtkWidget *dialog;
+ GtkWindowGroup *window_group;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ tab = gedit_window_get_active_tab (window);
+ g_return_if_fail (tab != NULL);
+
+ /* If we are already displaying a notification reverting will drop local
+ * modifications or if the document has not been modified, do not bug
+ * the user further.
+ */
+ if (gedit_tab_get_state (tab) == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION ||
+ _gedit_tab_get_can_close (tab))
+ {
+ do_revert (window, tab);
+ return;
+ }
+
+ doc = gedit_tab_get_document (tab);
+ g_return_if_fail (doc != NULL);
+ g_return_if_fail (!_gedit_document_is_untitled (doc));
+
+ dialog = revert_dialog (window, doc);
+
+ window_group = gedit_window_get_group (window);
+
+ gtk_window_group_add_window (window_group, GTK_WINDOW (dialog));
+
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+ g_signal_connect (dialog,
+ "response",
+ G_CALLBACK (revert_dialog_response_cb),
+ window);
+
+ gtk_widget_show (dialog);
+}
+
+static void
+tab_state_changed_while_saving (GeditTab *tab,
+ GParamSpec *pspec,
+ GeditWindow *window)
+{
+ GeditTabState state;
+
+ state = gedit_tab_get_state (tab);
+
+ gedit_debug_message (DEBUG_COMMANDS, "State while saving: %d\n", state);
+
+ /* When the state becomes NORMAL, it means the saving operation is
+ * finished.
+ */
+ if (state == GEDIT_TAB_STATE_NORMAL)
+ {
+ g_signal_handlers_disconnect_by_func (tab,
+ G_CALLBACK (tab_state_changed_while_saving),
+ window);
+
+ close_tab (tab);
+ }
+}
+
+static void
+save_and_close (GeditTab *tab,
+ GeditWindow *window)
+{
+ gedit_debug (DEBUG_COMMANDS);
+
+ /* Trace tab state changes */
+ g_signal_connect (tab,
+ "notify::state",
+ G_CALLBACK (tab_state_changed_while_saving),
+ window);
+
+ save_tab (tab, window);
+}
+
+static void
+save_and_close_documents (GList *docs,
+ GeditWindow *window,
+ GeditNotebook *notebook)
+{
+ GList *tabs;
+ GList *l;
+ GSList *sl;
+ SaveAsData *data = NULL;
+ GSList *tabs_to_save_and_close = NULL;
+ GList *tabs_to_close = NULL;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ g_return_if_fail ((gedit_window_get_state (window) & GEDIT_WINDOW_STATE_PRINTING) == 0);
+
+ if (notebook != NULL)
+ {
+ tabs = gtk_container_get_children (GTK_CONTAINER (notebook));
+ }
+ else
+ {
+ tabs = _gedit_window_get_all_tabs (window);
+ }
+
+ for (l = tabs; l != NULL; l = l->next)
+ {
+ GeditTab *tab = GEDIT_TAB (l->data);
+ GeditTabState state;
+ GeditDocument *doc;
+
+ state = gedit_tab_get_state (tab);
+ doc = gedit_tab_get_document (tab);
+
+ /* If the state is: ([*] invalid states)
+ - GEDIT_TAB_STATE_NORMAL: close (and if needed save)
+ - GEDIT_TAB_STATE_LOADING: close, we are sure the file is unmodified
+ - GEDIT_TAB_STATE_REVERTING: since the user wants
+ to return back to the version of the file she previously saved, we can close
+ without saving (CHECK: are we sure this is the right behavior, suppose the case
+ the original file has been deleted)
+ - [*] GEDIT_TAB_STATE_SAVING: invalid, ClosAll
+ and Quit are unsensitive if the window state is SAVING.
+ - [*] GEDIT_TAB_STATE_PRINTING: there is not a
+ real reason for not closing in this case, we do not save to avoid to run
+ two operations using the message area at the same time (may be we can remove
+ this limitation in the future). Note that ClosAll
+ and Quit are unsensitive if the window state is PRINTING.
+ - GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW: close (and if needed save)
+ - GEDIT_TAB_STATE_LOADING_ERROR: close without saving (if the state is LOADING_ERROR then the
+ document is not modified)
+ - GEDIT_TAB_STATE_REVERTING_ERROR: we do not close since the document contains errors
+ - GEDIT_TAB_STATE_SAVING_ERROR: we do not close since the document contains errors
+ - GEDIT_TAB_STATE_GENERIC_ERROR: we do not close since the document contains
+ errors (CHECK: we should problably remove this state)
+ - [*] GEDIT_TAB_STATE_CLOSING: this state is invalid in this case
+ */
+
+ g_return_if_fail (state != GEDIT_TAB_STATE_PRINTING);
+ g_return_if_fail (state != GEDIT_TAB_STATE_CLOSING);
+ g_return_if_fail (state != GEDIT_TAB_STATE_SAVING);
+
+ if (state != GEDIT_TAB_STATE_SAVING_ERROR &&
+ state != GEDIT_TAB_STATE_GENERIC_ERROR &&
+ state != GEDIT_TAB_STATE_REVERTING_ERROR)
+ {
+ if (g_list_index (docs, doc) >= 0 &&
+ state != GEDIT_TAB_STATE_LOADING &&
+ state != GEDIT_TAB_STATE_LOADING_ERROR &&
+ state != GEDIT_TAB_STATE_REVERTING) /* FIXME: is this the right behavior with REVERTING ?*/
+ {
+ GtkSourceFile *file = gedit_document_get_file (doc);
+
+ /* The document must be saved before closing */
+ g_return_if_fail (_gedit_document_needs_saving (doc));
+
+ /* FIXME: manage the case of local readonly files owned by the
+ * user is running gedit - Paolo (Dec. 8, 2005) */
+ if (_gedit_document_is_untitled (doc) ||
+ gtk_source_file_is_readonly (file))
+ {
+ if (data == NULL)
+ {
+ data = g_slice_new (SaveAsData);
+ data->window = g_object_ref (window);
+ data->tabs_to_save_as = NULL;
+ data->close_tabs = TRUE;
+ }
+
+ data->tabs_to_save_as = g_slist_prepend (data->tabs_to_save_as,
+ g_object_ref (tab));
+ }
+ else
+ {
+ tabs_to_save_and_close = g_slist_prepend (tabs_to_save_and_close, tab);
+ }
+ }
+ else
+ {
+ /* The document can be closed without saving */
+ tabs_to_close = g_list_prepend (tabs_to_close, tab);
+ }
+ }
+ }
+
+ g_list_free (tabs);
+
+ /* Close all tabs to close (in a sync way) */
+ gedit_window_close_tabs (window, tabs_to_close);
+ g_list_free (tabs_to_close);
+
+ /* Save and close all the files in tabs_to_save_and_close */
+ for (sl = tabs_to_save_and_close; sl != NULL; sl = sl->next)
+ {
+ save_and_close (GEDIT_TAB (sl->data), window);
+ }
+
+ g_slist_free (tabs_to_save_and_close);
+
+ /* Save As and close all the files in data->tabs_to_save_as. */
+ if (data != NULL)
+ {
+ data->tabs_to_save_as = g_slist_reverse (data->tabs_to_save_as);
+ save_as_documents_list (data);
+ }
+}
+
+static void
+save_and_close_document (const GList *docs,
+ GeditWindow *window)
+{
+ GeditTab *tab;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ g_return_if_fail (docs->next == NULL);
+
+ tab = gedit_tab_get_from_document (GEDIT_DOCUMENT (docs->data));
+ g_return_if_fail (tab != NULL);
+
+ save_and_close (tab, window);
+}
+
+static void
+close_all_tabs (GeditWindow *window)
+{
+ gedit_debug (DEBUG_COMMANDS);
+
+ /* There is no document to save -> close all tabs */
+ gedit_window_close_all_tabs (window);
+
+ quit_if_needed (window);
+}
+
+static void
+close_document (GeditWindow *window,
+ GeditDocument *doc)
+{
+ GeditTab *tab;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ tab = gedit_tab_get_from_document (doc);
+ g_return_if_fail (tab != NULL);
+
+ gedit_window_close_tab (window, tab);
+}
+
+static void
+close_confirmation_dialog_response_handler (GeditCloseConfirmationDialog *dlg,
+ gint response_id,
+ GeditWindow *window)
+{
+ GList *selected_documents;
+ gboolean is_closing_all;
+ GeditNotebook *notebook_to_close;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ is_closing_all = GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (window),
+ GEDIT_IS_CLOSING_ALL));
+
+ notebook_to_close = g_object_get_data (G_OBJECT (window), GEDIT_NOTEBOOK_TO_CLOSE);
+
+ gtk_widget_hide (GTK_WIDGET (dlg));
+
+ switch (response_id)
+ {
+ /* Save and Close */
+ case GTK_RESPONSE_YES:
+ selected_documents = gedit_close_confirmation_dialog_get_selected_documents (dlg);
+
+ if (selected_documents == NULL)
+ {
+ if (is_closing_all)
+ {
+ /* There is no document to save -> close all tabs */
+ /* We call gtk_widget_destroy before close_all_tabs
+ * because close_all_tabs could destroy the gedit window */
+ gtk_widget_destroy (GTK_WIDGET (dlg));
+
+ close_all_tabs (window);
+ return;
+ }
+ else if (notebook_to_close)
+ {
+ gedit_notebook_remove_all_tabs (notebook_to_close);
+ }
+ else
+ {
+ g_return_if_reached ();
+ }
+ }
+ else
+ {
+ if (is_closing_all || notebook_to_close)
+ {
+ GeditNotebook *notebook = is_closing_all ? NULL : notebook_to_close;
+
+ save_and_close_documents (selected_documents, window, notebook);
+ }
+ else
+ {
+ save_and_close_document (selected_documents, window);
+ }
+ }
+
+ g_list_free (selected_documents);
+ break;
+
+ /* Close without Saving */
+ case GTK_RESPONSE_NO:
+ if (is_closing_all)
+ {
+ /* We call gtk_widget_destroy before close_all_tabs
+ * because close_all_tabs could destroy the gedit window */
+ gtk_widget_destroy (GTK_WIDGET (dlg));
+
+ close_all_tabs (window);
+ return;
+ }
+ else if (notebook_to_close)
+ {
+ gedit_notebook_remove_all_tabs (notebook_to_close);
+ }
+ else
+ {
+ const GList *unsaved_documents;
+
+ unsaved_documents = gedit_close_confirmation_dialog_get_unsaved_documents (dlg);
+ g_return_if_fail (unsaved_documents->next == NULL);
+
+ close_document (window, GEDIT_DOCUMENT (unsaved_documents->data));
+ }
+
+ break;
+
+ /* Do not close */
+ default:
+ /* Reset is_quitting flag */
+ g_object_set_data (G_OBJECT (window),
+ GEDIT_IS_QUITTING,
+ GBOOLEAN_TO_POINTER (FALSE));
+
+ g_object_set_data (G_OBJECT (window),
+ GEDIT_IS_QUITTING_ALL,
+ GBOOLEAN_TO_POINTER (FALSE));
+ break;
+ }
+
+ g_object_set_data (G_OBJECT (window), GEDIT_NOTEBOOK_TO_CLOSE, NULL);
+
+ gtk_widget_destroy (GTK_WIDGET (dlg));
+}
+
+/* Returns TRUE if the tab can be immediately closed */
+static gboolean
+tab_can_close (GeditTab *tab,
+ GtkWindow *window)
+{
+ GeditDocument *doc;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ doc = gedit_tab_get_document (tab);
+
+ if (!_gedit_tab_get_can_close (tab))
+ {
+ GtkWidget *dlg;
+
+ dlg = gedit_close_confirmation_dialog_new_single (window, doc);
+ g_signal_connect (dlg,
+ "response",
+ G_CALLBACK (close_confirmation_dialog_response_handler),
+ window);
+
+ gtk_widget_show (dlg);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* FIXME: we probably need this one public for plugins...
+ * maybe even a _list variant. Or maybe it's better make
+ * gedit_window_close_tab always run the confirm dialog?
+ * we should not allow closing a tab without resetting the
+ * GEDIT_IS_CLOSING_ALL flag!
+ */
+void
+_gedit_cmd_file_close_tab (GeditTab *tab,
+ GeditWindow *window)
+{
+ gedit_debug (DEBUG_COMMANDS);
+
+ g_return_if_fail (GTK_WIDGET (window) == gtk_widget_get_toplevel (GTK_WIDGET (tab)));
+
+ g_object_set_data (G_OBJECT (window),
+ GEDIT_IS_CLOSING_ALL,
+ GBOOLEAN_TO_POINTER (FALSE));
+
+ g_object_set_data (G_OBJECT (window),
+ GEDIT_IS_QUITTING,
+ GBOOLEAN_TO_POINTER (FALSE));
+
+ g_object_set_data (G_OBJECT (window),
+ GEDIT_IS_QUITTING_ALL,
+ GBOOLEAN_TO_POINTER (FALSE));
+
+ if (tab_can_close (tab, GTK_WINDOW (window)))
+ {
+ gedit_window_close_tab (window, tab);
+ }
+}
+
+void
+_gedit_cmd_file_close (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditTab *active_tab;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ active_tab = gedit_window_get_active_tab (window);
+
+ if (active_tab == NULL)
+ {
+ gtk_widget_destroy (GTK_WIDGET (window));
+ return;
+ }
+
+ _gedit_cmd_file_close_tab (active_tab, window);
+}
+
+static void
+file_close_dialog (GeditWindow *window,
+ GList *unsaved_docs)
+{
+ GtkWidget *dlg;
+
+ if (unsaved_docs->next == NULL)
+ {
+ /* There is only one unsaved document */
+ GeditTab *tab;
+ GeditDocument *doc;
+
+ doc = GEDIT_DOCUMENT (unsaved_docs->data);
+
+ tab = gedit_tab_get_from_document (doc);
+ g_return_if_fail (tab != NULL);
+
+ gedit_window_set_active_tab (window, tab);
+
+ dlg = gedit_close_confirmation_dialog_new_single (GTK_WINDOW (window), doc);
+ }
+ else
+ {
+ dlg = gedit_close_confirmation_dialog_new (GTK_WINDOW (window), unsaved_docs);
+ }
+
+ g_signal_connect (dlg,
+ "response",
+ G_CALLBACK (close_confirmation_dialog_response_handler),
+ window);
+
+ gtk_widget_show (dlg);
+}
+
+static GList *
+notebook_get_unsaved_documents (GeditNotebook *notebook)
+{
+ GList *children;
+ GList *unsaved_docs = NULL;
+ GList *l;
+
+ children = gtk_container_get_children (GTK_CONTAINER (notebook));
+
+ for (l = children; l != NULL; l = g_list_next (l))
+ {
+ GeditTab *tab = GEDIT_TAB (l->data);
+
+ if (!_gedit_tab_get_can_close (tab))
+ {
+ GeditDocument *doc;
+
+ doc = gedit_tab_get_document (tab);
+ unsaved_docs = g_list_prepend (unsaved_docs, doc);
+ }
+ }
+
+ g_list_free (children);
+
+ return g_list_reverse (unsaved_docs);
+}
+
+/* Close a notebook */
+void
+_gedit_cmd_file_close_notebook (GeditWindow *window,
+ GeditNotebook *notebook)
+{
+ GList *unsaved_docs;
+
+ g_object_set_data (G_OBJECT (window), GEDIT_IS_CLOSING_ALL, GBOOLEAN_TO_POINTER (FALSE));
+ g_object_set_data (G_OBJECT (window), GEDIT_IS_QUITTING, GBOOLEAN_TO_POINTER (FALSE));
+ g_object_set_data (G_OBJECT (window), GEDIT_IS_QUITTING_ALL, GBOOLEAN_TO_POINTER (FALSE));
+
+ g_object_set_data (G_OBJECT (window), GEDIT_NOTEBOOK_TO_CLOSE, notebook);
+
+ unsaved_docs = notebook_get_unsaved_documents (notebook);
+
+ if (unsaved_docs == NULL)
+ {
+ /* There is no document to save -> close the notebook */
+ gedit_notebook_remove_all_tabs (GEDIT_NOTEBOOK (notebook));
+ }
+ else
+ {
+ file_close_dialog (window, unsaved_docs);
+
+ g_list_free (unsaved_docs);
+ }
+}
+
+/* Close all tabs */
+static void
+file_close_all (GeditWindow *window,
+ gboolean is_quitting)
+{
+ GList *unsaved_docs;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ g_return_if_fail (!(gedit_window_get_state (window) &
+ (GEDIT_WINDOW_STATE_SAVING |
+ GEDIT_WINDOW_STATE_PRINTING)));
+
+ g_object_set_data (G_OBJECT (window),
+ GEDIT_IS_CLOSING_ALL,
+ GBOOLEAN_TO_POINTER (TRUE));
+
+ g_object_set_data (G_OBJECT (window),
+ GEDIT_IS_QUITTING,
+ GBOOLEAN_TO_POINTER (is_quitting));
+
+ unsaved_docs = gedit_window_get_unsaved_documents (window);
+
+ if (unsaved_docs != NULL)
+ {
+ file_close_dialog (window, unsaved_docs);
+
+ g_list_free (unsaved_docs);
+ }
+ else
+ {
+ /* There is no document to save -> close all tabs */
+ gedit_window_close_all_tabs (window);
+ quit_if_needed (window);
+ }
+}
+
+void
+_gedit_cmd_file_close_all (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ g_return_if_fail (!(gedit_window_get_state (window) &
+ (GEDIT_WINDOW_STATE_SAVING |
+ GEDIT_WINDOW_STATE_PRINTING)));
+
+ file_close_all (window, FALSE);
+}
+
+/* Quit */
+static void
+quit_all (void)
+{
+ GList *windows;
+ GList *l;
+ GApplication *app;
+
+ app = g_application_get_default ();
+ windows = gedit_app_get_main_windows (GEDIT_APP (app));
+
+ if (windows == NULL)
+ {
+ g_application_quit (app);
+ return;
+ }
+
+ for (l = windows; l != NULL; l = g_list_next (l))
+ {
+ GeditWindow *window = l->data;
+
+ g_object_set_data (G_OBJECT (window),
+ GEDIT_IS_QUITTING_ALL,
+ GBOOLEAN_TO_POINTER (TRUE));
+
+ if (!(gedit_window_get_state (window) &
+ (GEDIT_WINDOW_STATE_SAVING | GEDIT_WINDOW_STATE_PRINTING)))
+ {
+ file_close_all (window, TRUE);
+ }
+ }
+
+ g_list_free (windows);
+}
+
+void
+_gedit_cmd_file_quit (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ if (window == NULL)
+ {
+ quit_all ();
+ return;
+ }
+
+ g_return_if_fail (!(gedit_window_get_state (window) &
+ (GEDIT_WINDOW_STATE_SAVING |
+ GEDIT_WINDOW_STATE_PRINTING)));
+
+ file_close_all (window, TRUE);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-commands-help.c b/gedit/gedit-commands-help.c
new file mode 100644
index 0000000..6a5d12e
--- /dev/null
+++ b/gedit/gedit-commands-help.c
@@ -0,0 +1,132 @@
+/*
+ * gedit-help-commands.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-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-commands.h"
+#include "gedit-commands-private.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "gedit-debug.h"
+#include "gedit-app.h"
+#include "gedit-dirs.h"
+
+void
+_gedit_cmd_help_keyboard_shortcuts (GeditWindow *window)
+{
+ static GtkWidget *shortcuts_window;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ if (shortcuts_window == NULL)
+ {
+ GtkBuilder *builder;
+
+ builder = gtk_builder_new_from_resource ("/org/gnome/gedit/ui/gedit-shortcuts.ui");
+ shortcuts_window = GTK_WIDGET (gtk_builder_get_object (builder, "shortcuts-gedit"));
+
+ g_signal_connect (shortcuts_window,
+ "destroy",
+ G_CALLBACK (gtk_widget_destroyed),
+ &shortcuts_window);
+
+ g_object_unref (builder);
+ }
+
+ if (GTK_WINDOW (window) != gtk_window_get_transient_for (GTK_WINDOW (shortcuts_window)))
+ {
+ gtk_window_set_transient_for (GTK_WINDOW (shortcuts_window), GTK_WINDOW (window));
+ }
+
+ gtk_widget_show_all (shortcuts_window);
+ gtk_window_present (GTK_WINDOW (shortcuts_window));
+}
+
+void
+_gedit_cmd_help_contents (GeditWindow *window)
+{
+ gedit_debug (DEBUG_COMMANDS);
+
+ gedit_app_show_help (GEDIT_APP (g_application_get_default ()),
+ GTK_WINDOW (window),
+ NULL,
+ NULL);
+}
+
+void
+_gedit_cmd_help_about (GeditWindow *window)
+{
+ const gchar * const authors[] = {
+ /* Main authors: the top 5 (to not have a too long list), by
+ * relative contribution (number of commits at the time of
+ * writing).
+ */
+ _("Main authors:"),
+ " Paolo Borelli",
+ " Sébastien Wilmet",
+ " Ignacio Casal Quinteiro",
+ " Jesse van den Kieboom",
+ " Paolo Maggi",
+ "",
+ _("Many thanks also to:"),
+ " Alex Roberts",
+ " Chema Celorio",
+ " Evan Lawrence",
+ " Federico Mena Quintero",
+ " Garrett Regier",
+ " James Willcox",
+ " Sébastien Lafargue",
+ " Steve Frécinaux",
+ "",
+ _("and many other contributors."),
+ "",
+ NULL
+ };
+
+ static const gchar * const documenters[] = {
+ "Daniel Neel",
+ "Eric Baudais",
+ "Jim Campbell",
+ "Sun GNOME Documentation Team",
+ NULL
+ };
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ gtk_show_about_dialog (GTK_WINDOW (window),
+ "program-name", "gedit",
+ "authors", authors,
+ "comments", _("gedit is an easy-to-use and general-purpose text editor"),
+ "copyright", "Copyright 1998-2023 – the gedit team",
+ "license-type", GTK_LICENSE_GPL_2_0,
+ "logo-icon-name", "org.gnome.gedit",
+ "documenters", documenters,
+ "translator-credits", _("translator-credits"),
+ "version", VERSION,
+ "website", "http://www.gedit.org",
+ "website-label", "www.gedit.org",
+ NULL);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-commands-private.h b/gedit/gedit-commands-private.h
new file mode 100644
index 0000000..0c2f0d7
--- /dev/null
+++ b/gedit/gedit-commands-private.h
@@ -0,0 +1,172 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gedit-commands.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-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/>.
+ */
+
+#ifndef GEDIT_COMMANDS_PRIVATE_H
+#define GEDIT_COMMANDS_PRIVATE_H
+
+#include <gtksourceview/gtksource.h>
+#include <gedit/gedit-window.h>
+#include <gedit/gedit-notebook.h>
+
+G_BEGIN_DECLS
+
+/* Create titled documens for non-existing URIs */
+GSList *_gedit_cmd_load_files_from_prompt (GeditWindow *window,
+ GSList *files,
+ const GtkSourceEncoding *encoding,
+ gint line_pos,
+ gint column_pos) G_GNUC_WARN_UNUSED_RESULT;
+
+void _gedit_cmd_file_new (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_file_open (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_file_reopen_closed_tab (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_file_save (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_file_save_as (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_file_save_all (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_file_revert (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_file_print (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_file_close (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_file_close_all (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_file_quit (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+
+void _gedit_cmd_edit_undo (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_edit_redo (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_edit_cut (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_edit_copy (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_edit_paste (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_edit_delete (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_edit_select_all (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_edit_preferences (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_edit_overwrite_mode (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+
+void _gedit_cmd_view_focus_active (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data);
+void _gedit_cmd_view_toggle_side_panel (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data);
+void _gedit_cmd_view_toggle_bottom_panel (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data);
+void _gedit_cmd_view_toggle_fullscreen_mode (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data);
+void _gedit_cmd_view_leave_fullscreen_mode (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_view_highlight_mode (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+
+void _gedit_cmd_search_find (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_search_find_next (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_search_find_prev (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_search_replace (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_search_clear_highlight (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_search_goto_line (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+
+void _gedit_cmd_documents_previous_document (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_documents_next_document (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_documents_move_to_new_window (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_documents_new_tab_group (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_documents_previous_tab_group (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+void _gedit_cmd_documents_next_tab_group (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+
+void _gedit_cmd_help_keyboard_shortcuts (GeditWindow *window);
+void _gedit_cmd_help_contents (GeditWindow *window);
+void _gedit_cmd_help_about (GeditWindow *window);
+
+void _gedit_cmd_file_close_tab (GeditTab *tab,
+ GeditWindow *window);
+
+void _gedit_cmd_file_close_notebook (GeditWindow *window,
+ GeditNotebook *notebook);
+
+G_END_DECLS
+
+#endif /* GEDIT_COMMANDS_PRIVATE_H */
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-commands-search.c b/gedit/gedit-commands-search.c
new file mode 100644
index 0000000..1054859
--- /dev/null
+++ b/gedit/gedit-commands-search.c
@@ -0,0 +1,696 @@
+/*
+ * gedit-commands-search.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2006 Paolo Maggi
+ * Copyright (C) 2013 Sébastien Wilmet
+ *
+ * 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-commands.h"
+#include "gedit-commands-private.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+#include <tepl/tepl.h>
+
+#include "gedit-debug.h"
+#include "gedit-statusbar.h"
+#include "gedit-tab.h"
+#include "gedit-tab-private.h"
+#include "gedit-view-frame.h"
+#include "gedit-window.h"
+#include "gedit-utils.h"
+#include "gedit-replace-dialog.h"
+
+#define GEDIT_REPLACE_DIALOG_KEY "gedit-replace-dialog-key"
+#define GEDIT_LAST_SEARCH_DATA_KEY "gedit-last-search-data-key"
+
+typedef struct _LastSearchData LastSearchData;
+struct _LastSearchData
+{
+ gint x;
+ gint y;
+};
+
+static void
+last_search_data_free (LastSearchData *data)
+{
+ g_slice_free (LastSearchData, data);
+}
+
+static void
+last_search_data_restore_position (GeditReplaceDialog *dlg)
+{
+ LastSearchData *data;
+
+ data = g_object_get_data (G_OBJECT (dlg), GEDIT_LAST_SEARCH_DATA_KEY);
+
+ if (data != NULL)
+ {
+ gtk_window_move (GTK_WINDOW (dlg),
+ data->x,
+ data->y);
+ }
+}
+
+static void
+last_search_data_store_position (GeditReplaceDialog *dlg)
+{
+ LastSearchData *data;
+
+ data = g_object_get_data (G_OBJECT (dlg), GEDIT_LAST_SEARCH_DATA_KEY);
+
+ if (data == NULL)
+ {
+ data = g_slice_new (LastSearchData);
+
+ g_object_set_data_full (G_OBJECT (dlg),
+ GEDIT_LAST_SEARCH_DATA_KEY,
+ data,
+ (GDestroyNotify) last_search_data_free);
+ }
+
+ gtk_window_get_position (GTK_WINDOW (dlg),
+ &data->x,
+ &data->y);
+}
+
+/* Use occurences only for Replace All */
+static void
+text_found (GeditWindow *window,
+ gint occurrences)
+{
+ GeditStatusbar *statusbar = GEDIT_STATUSBAR (gedit_window_get_statusbar (window));
+
+ if (occurrences > 1)
+ {
+ _gedit_statusbar_flash_generic_message (statusbar,
+ ngettext("Found and replaced %d occurrence",
+ "Found and replaced %d occurrences",
+ occurrences),
+ occurrences);
+ }
+ else if (occurrences == 1)
+ {
+ _gedit_statusbar_flash_generic_message (statusbar,
+ _("Found and replaced one occurrence"));
+ }
+ else
+ {
+ _gedit_statusbar_flash_generic_message (statusbar, " ");
+ }
+}
+
+#define MAX_MSG_LENGTH 40
+
+static void
+text_not_found (GeditWindow *window,
+ GeditReplaceDialog *replace_dialog)
+{
+ const gchar *search_text;
+ gchar *truncated_text;
+ GeditStatusbar *statusbar;
+
+ search_text = gedit_replace_dialog_get_search_text (replace_dialog);
+ truncated_text = tepl_utils_str_end_truncate (search_text, MAX_MSG_LENGTH);
+
+ statusbar = GEDIT_STATUSBAR (gedit_window_get_statusbar (window));
+
+ _gedit_statusbar_flash_generic_message (statusbar,
+ /* Translators: %s is replaced by the text entered
+ * by the user in the search box.
+ */
+ _("“%s” not found"), truncated_text);
+
+ g_free (truncated_text);
+}
+
+static void
+finish_search_from_dialog (GeditWindow *window,
+ gboolean found)
+{
+ GeditReplaceDialog *replace_dialog;
+
+ replace_dialog = g_object_get_data (G_OBJECT (window), GEDIT_REPLACE_DIALOG_KEY);
+
+ g_return_if_fail (replace_dialog != NULL);
+
+ if (found)
+ {
+ text_found (window, 0);
+ }
+ else
+ {
+ text_not_found (window, replace_dialog);
+ }
+}
+
+static gboolean
+forward_search_finished (GtkSourceSearchContext *search_context,
+ GAsyncResult *result,
+ GeditView *view)
+{
+ gboolean found;
+ GtkSourceBuffer *buffer;
+ GtkTextIter match_start;
+ GtkTextIter match_end;
+
+ found = gtk_source_search_context_forward_finish (search_context,
+ result,
+ &match_start,
+ &match_end,
+ NULL,
+ NULL);
+
+ buffer = gtk_source_search_context_get_buffer (search_context);
+
+ if (found)
+ {
+ gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer),
+ &match_start,
+ &match_end);
+
+ tepl_view_scroll_to_cursor (TEPL_VIEW (view));
+ }
+ else
+ {
+ GtkTextIter end_selection;
+
+ gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (buffer),
+ NULL,
+ &end_selection);
+
+ gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer),
+ &end_selection,
+ &end_selection);
+ }
+
+ return found;
+}
+
+static void
+forward_search_from_dialog_finished (GtkSourceSearchContext *search_context,
+ GAsyncResult *result,
+ GeditWindow *window)
+{
+ GeditView *view = gedit_window_get_active_view (window);
+ gboolean found;
+
+ if (view == NULL)
+ {
+ return;
+ }
+
+ found = forward_search_finished (search_context, result, view);
+
+ finish_search_from_dialog (window, found);
+}
+
+static void
+run_forward_search (GeditWindow *window,
+ gboolean from_dialog)
+{
+ GeditView *view;
+ GtkTextBuffer *buffer;
+ GtkTextIter start_at;
+ GtkSourceSearchContext *search_context;
+
+ view = gedit_window_get_active_view (window);
+
+ if (view == NULL)
+ {
+ return;
+ }
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ search_context = gedit_document_get_search_context (GEDIT_DOCUMENT (buffer));
+
+ if (search_context == NULL)
+ {
+ return;
+ }
+
+ gtk_text_buffer_get_selection_bounds (buffer, NULL, &start_at);
+
+ if (from_dialog)
+ {
+ gtk_source_search_context_forward_async (search_context,
+ &start_at,
+ NULL,
+ (GAsyncReadyCallback)forward_search_from_dialog_finished,
+ window);
+ }
+ else
+ {
+ gtk_source_search_context_forward_async (search_context,
+ &start_at,
+ NULL,
+ (GAsyncReadyCallback)forward_search_finished,
+ view);
+ }
+}
+
+static gboolean
+backward_search_finished (GtkSourceSearchContext *search_context,
+ GAsyncResult *result,
+ GeditView *view)
+{
+ gboolean found;
+ GtkTextIter match_start;
+ GtkTextIter match_end;
+ GtkSourceBuffer *buffer;
+
+ found = gtk_source_search_context_backward_finish (search_context,
+ result,
+ &match_start,
+ &match_end,
+ NULL,
+ NULL);
+
+ buffer = gtk_source_search_context_get_buffer (search_context);
+
+ if (found)
+ {
+ gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer),
+ &match_start,
+ &match_end);
+
+ tepl_view_scroll_to_cursor (TEPL_VIEW (view));
+ }
+ else
+ {
+ GtkTextIter start_selection;
+
+ gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (buffer),
+ &start_selection,
+ NULL);
+
+ gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer),
+ &start_selection,
+ &start_selection);
+ }
+
+ return found;
+}
+
+static void
+backward_search_from_dialog_finished (GtkSourceSearchContext *search_context,
+ GAsyncResult *result,
+ GeditWindow *window)
+{
+ GeditView *view = gedit_window_get_active_view (window);
+ gboolean found;
+
+ if (view == NULL)
+ {
+ return;
+ }
+
+ found = backward_search_finished (search_context, result, view);
+
+ finish_search_from_dialog (window, found);
+}
+
+static void
+run_backward_search (GeditWindow *window,
+ gboolean from_dialog)
+{
+ GeditView *view;
+ GtkTextBuffer *buffer;
+ GtkTextIter start_at;
+ GtkSourceSearchContext *search_context;
+
+ view = gedit_window_get_active_view (window);
+
+ if (view == NULL)
+ {
+ return;
+ }
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ search_context = gedit_document_get_search_context (GEDIT_DOCUMENT (buffer));
+
+ if (search_context == NULL)
+ {
+ return;
+ }
+
+ gtk_text_buffer_get_selection_bounds (buffer, &start_at, NULL);
+
+ if (from_dialog)
+ {
+ gtk_source_search_context_backward_async (search_context,
+ &start_at,
+ NULL,
+ (GAsyncReadyCallback)backward_search_from_dialog_finished,
+ window);
+ }
+ else
+ {
+ gtk_source_search_context_backward_async (search_context,
+ &start_at,
+ NULL,
+ (GAsyncReadyCallback)backward_search_finished,
+ view);
+ }
+}
+
+static void
+do_find (GeditReplaceDialog *dialog,
+ GeditWindow *window)
+{
+ if (gedit_replace_dialog_get_backwards (dialog))
+ {
+ run_backward_search (window, TRUE);
+ }
+ else
+ {
+ run_forward_search (window, TRUE);
+ }
+}
+
+static void
+do_replace (GeditReplaceDialog *dialog,
+ GeditWindow *window)
+{
+ GeditDocument *doc;
+ GtkSourceSearchContext *search_context;
+ const gchar *replace_entry_text;
+ gchar *unescaped_replace_text;
+ GtkTextIter start;
+ GtkTextIter end;
+ GError *error = NULL;
+
+ doc = gedit_window_get_active_document (window);
+
+ if (doc == NULL)
+ {
+ return;
+ }
+
+ search_context = gedit_document_get_search_context (doc);
+
+ if (search_context == NULL)
+ {
+ return;
+ }
+
+ /* replace text may be "", we just delete */
+ replace_entry_text = gedit_replace_dialog_get_replace_text (dialog);
+ g_return_if_fail (replace_entry_text != NULL);
+
+ unescaped_replace_text = gtk_source_utils_unescape_search_text (replace_entry_text);
+
+ gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc), &start, &end);
+
+ gtk_source_search_context_replace (search_context,
+ &start,
+ &end,
+ unescaped_replace_text,
+ -1,
+ &error);
+
+ g_free (unescaped_replace_text);
+
+ if (error != NULL)
+ {
+ gedit_replace_dialog_set_replace_error (dialog, error->message);
+ g_error_free (error);
+ }
+
+ do_find (dialog, window);
+}
+
+static void
+do_replace_all (GeditReplaceDialog *dialog,
+ GeditWindow *window)
+{
+ GeditView *view;
+ GtkSourceSearchContext *search_context;
+ GtkTextBuffer *buffer;
+ GtkSourceCompletion *completion;
+ const gchar *replace_entry_text;
+ gchar *unescaped_replace_text;
+ gint count;
+ GError *error = NULL;
+
+ view = gedit_window_get_active_view (window);
+
+ if (view == NULL)
+ {
+ return;
+ }
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ search_context = gedit_document_get_search_context (GEDIT_DOCUMENT (buffer));
+
+ if (search_context == NULL)
+ {
+ return;
+ }
+
+ /* FIXME: this should really be done automatically in gtksoureview, but
+ * it is an important performance fix, so let's do it here for now.
+ */
+ completion = gtk_source_view_get_completion (GTK_SOURCE_VIEW (view));
+ gtk_source_completion_block_interactive (completion);
+
+ /* replace text may be "", we just delete all occurrences */
+ replace_entry_text = gedit_replace_dialog_get_replace_text (dialog);
+ g_return_if_fail (replace_entry_text != NULL);
+
+ unescaped_replace_text = gtk_source_utils_unescape_search_text (replace_entry_text);
+
+ count = gtk_source_search_context_replace_all (search_context,
+ unescaped_replace_text,
+ -1,
+ &error);
+
+ g_free (unescaped_replace_text);
+
+ gtk_source_completion_unblock_interactive (completion);
+
+ if (count > 0)
+ {
+ text_found (window, count);
+ }
+ else if (error == NULL)
+ {
+ text_not_found (window, dialog);
+ }
+
+ if (error != NULL)
+ {
+ gedit_replace_dialog_set_replace_error (dialog, error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+replace_dialog_response_cb (GeditReplaceDialog *dialog,
+ gint response_id,
+ GeditWindow *window)
+{
+ gedit_debug (DEBUG_COMMANDS);
+
+ switch (response_id)
+ {
+ case GEDIT_REPLACE_DIALOG_FIND_RESPONSE:
+ do_find (dialog, window);
+ break;
+
+ case GEDIT_REPLACE_DIALOG_REPLACE_RESPONSE:
+ do_replace (dialog, window);
+ break;
+
+ case GEDIT_REPLACE_DIALOG_REPLACE_ALL_RESPONSE:
+ do_replace_all (dialog, window);
+ break;
+
+ default:
+ last_search_data_store_position (dialog);
+ gtk_widget_hide (GTK_WIDGET (dialog));
+ }
+}
+
+static void
+replace_dialog_destroyed (GeditWindow *window,
+ GeditReplaceDialog *dialog)
+{
+ gedit_debug (DEBUG_COMMANDS);
+
+ g_object_set_data (G_OBJECT (window),
+ GEDIT_REPLACE_DIALOG_KEY,
+ NULL);
+ g_object_set_data (G_OBJECT (dialog),
+ GEDIT_LAST_SEARCH_DATA_KEY,
+ NULL);
+}
+
+static GtkWidget *
+create_dialog (GeditWindow *window)
+{
+ GtkWidget *dialog = gedit_replace_dialog_new (window);
+
+ g_signal_connect (dialog,
+ "response",
+ G_CALLBACK (replace_dialog_response_cb),
+ window);
+
+ g_object_set_data (G_OBJECT (window),
+ GEDIT_REPLACE_DIALOG_KEY,
+ dialog);
+
+ g_object_weak_ref (G_OBJECT (dialog),
+ (GWeakNotify) replace_dialog_destroyed,
+ window);
+
+ return dialog;
+}
+
+void
+_gedit_cmd_search_find (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditTab *active_tab;
+ GeditViewFrame *frame;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ active_tab = gedit_window_get_active_tab (window);
+
+ if (active_tab == NULL)
+ {
+ return;
+ }
+
+ frame = _gedit_tab_get_view_frame (active_tab);
+ gedit_view_frame_popup_search (frame);
+}
+
+void
+_gedit_cmd_search_replace (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ gpointer data;
+ GtkWidget *replace_dialog;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ data = g_object_get_data (G_OBJECT (window), GEDIT_REPLACE_DIALOG_KEY);
+
+ if (data == NULL)
+ {
+ replace_dialog = create_dialog (window);
+ }
+ else
+ {
+ g_return_if_fail (GEDIT_IS_REPLACE_DIALOG (data));
+
+ replace_dialog = GTK_WIDGET (data);
+ }
+
+ gtk_widget_show (replace_dialog);
+ last_search_data_restore_position (GEDIT_REPLACE_DIALOG (replace_dialog));
+ gedit_replace_dialog_present_with_time (GEDIT_REPLACE_DIALOG (replace_dialog),
+ GDK_CURRENT_TIME);
+}
+
+void
+_gedit_cmd_search_find_next (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ run_forward_search (window, FALSE);
+}
+
+void
+_gedit_cmd_search_find_prev (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ run_backward_search (window, FALSE);
+}
+
+void
+_gedit_cmd_search_clear_highlight (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditTab *active_tab;
+ GeditViewFrame *frame;
+ GeditDocument *doc;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ active_tab = gedit_window_get_active_tab (window);
+
+ if (active_tab == NULL)
+ {
+ return;
+ }
+
+ frame = _gedit_tab_get_view_frame (active_tab);
+ gedit_view_frame_clear_search (frame);
+
+ doc = gedit_tab_get_document (active_tab);
+ gedit_document_set_search_context (doc, NULL);
+}
+
+void
+_gedit_cmd_search_goto_line (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GeditTab *active_tab;
+ GeditViewFrame *frame;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ active_tab = gedit_window_get_active_tab (window);
+
+ if (active_tab == NULL)
+ {
+ return;
+ }
+
+ frame = _gedit_tab_get_view_frame (active_tab);
+ gedit_view_frame_popup_goto_line (frame);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-commands-view.c b/gedit/gedit-commands-view.c
new file mode 100644
index 0000000..0259bdd
--- /dev/null
+++ b/gedit/gedit-commands-view.c
@@ -0,0 +1,181 @@
+/*
+ * gedit-view-commands.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-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-commands.h"
+#include "gedit-commands-private.h"
+#include <tepl/tepl.h>
+#include "gedit-debug.h"
+#include "gedit-window.h"
+
+void
+_gedit_cmd_view_focus_active (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ GeditView *active_view;
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ active_view = gedit_window_get_active_view (window);
+
+ if (active_view)
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+ }
+}
+
+void
+_gedit_cmd_view_toggle_side_panel (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GtkWidget *panel;
+ gboolean visible;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ panel = gedit_window_get_side_panel (window);
+
+ visible = g_variant_get_boolean (state);
+ gtk_widget_set_visible (panel, visible);
+
+ if (visible)
+ {
+ gtk_widget_grab_focus (panel);
+ }
+
+ g_simple_action_set_state (action, state);
+}
+
+void
+_gedit_cmd_view_toggle_bottom_panel (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ GtkWidget *panel;
+ gboolean visible;
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ panel = gedit_window_get_bottom_panel (window);
+
+ visible = g_variant_get_boolean (state);
+ gtk_widget_set_visible (panel, visible);
+
+ if (visible)
+ {
+ gtk_widget_grab_focus (panel);
+ }
+
+ g_simple_action_set_state (action, state);
+}
+
+void
+_gedit_cmd_view_toggle_fullscreen_mode (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+
+ gedit_debug (DEBUG_COMMANDS);
+
+ if (g_variant_get_boolean (state))
+ {
+ _gedit_window_fullscreen (window);
+ }
+ else
+ {
+ _gedit_window_unfullscreen (window);
+ }
+}
+
+void
+_gedit_cmd_view_leave_fullscreen_mode (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ _gedit_window_unfullscreen (GEDIT_WINDOW (user_data));
+}
+
+static void
+language_activated_cb (TeplLanguageChooserDialog *dialog,
+ 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_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+language_chooser_dialog_response_after_cb (TeplLanguageChooserDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+void
+_gedit_cmd_view_highlight_mode (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditWindow *window = GEDIT_WINDOW (user_data);
+ TeplLanguageChooserDialog *dialog;
+ GeditDocument *active_document;
+
+ dialog = tepl_language_chooser_dialog_new (GTK_WINDOW (window));
+
+ 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 (TEPL_LANGUAGE_CHOOSER (dialog), language);
+ }
+
+ g_signal_connect_object (dialog,
+ "language-activated",
+ G_CALLBACK (language_activated_cb),
+ window,
+ 0);
+
+ g_signal_connect_after (dialog,
+ "response",
+ G_CALLBACK (language_chooser_dialog_response_after_cb),
+ NULL);
+
+ gtk_widget_show (GTK_WIDGET (dialog));
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-commands.h b/gedit/gedit-commands.h
new file mode 100644
index 0000000..a003c3a
--- /dev/null
+++ b/gedit/gedit-commands.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gedit-commands.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-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/>.
+ */
+
+#ifndef GEDIT_COMMANDS_H
+#define GEDIT_COMMANDS_H
+
+#include <gtksourceview/gtksource.h>
+#include <gedit/gedit-window.h>
+
+G_BEGIN_DECLS
+
+/* Do nothing if URI does not exist */
+void gedit_commands_load_location (GeditWindow *window,
+ GFile *location,
+ const GtkSourceEncoding *encoding,
+ gint line_pos,
+ gint column_pos);
+
+/* Ignore non-existing URIs */
+GSList *gedit_commands_load_locations (GeditWindow *window,
+ const GSList *locations,
+ const GtkSourceEncoding *encoding,
+ gint line_pos,
+ gint column_pos) G_GNUC_WARN_UNUSED_RESULT;
+
+void gedit_commands_save_document (GeditWindow *window,
+ GeditDocument *document);
+
+void gedit_commands_save_document_async (GeditDocument *document,
+ GeditWindow *window,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean gedit_commands_save_document_finish (GeditDocument *document,
+ GAsyncResult *result);
+
+void gedit_commands_save_all_documents (GeditWindow *window);
+
+G_END_DECLS
+
+#endif /* GEDIT_COMMANDS_H */
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-debug.c b/gedit/gedit-debug.c
new file mode 100644
index 0000000..5aa82fa
--- /dev/null
+++ b/gedit/gedit-debug.c
@@ -0,0 +1,232 @@
+/*
+ * gedit-debug.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002 - 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 "gedit-debug.h"
+#include <stdio.h>
+
+#define ENABLE_PROFILING
+
+#ifdef ENABLE_PROFILING
+static GTimer *timer = NULL;
+static gdouble last_time = 0.0;
+#endif
+
+static GeditDebugSection enabled_sections = GEDIT_NO_DEBUG;
+
+#define DEBUG_IS_ENABLED(section) (enabled_sections & (section))
+
+/**
+ * gedit_debug_init:
+ *
+ * Initializes the debugging subsystem of gedit.
+ *
+ * The function checks for the existence of certain environment variables to
+ * determine whether to enable output for a debug section. To enable output
+ * for a specific debug section, set an environment variable of the same name;
+ * e.g. to enable output for the %GEDIT_DEBUG_PLUGINS section, set a
+ * <code>GEDIT_DEBUG_PLUGINS</code> environment variable. To enable output
+ * for all debug sections, set the <code>GEDIT_DEBUG</code> environment
+ * variable.
+ *
+ * This function must be called before any of the other debug functions are
+ * called. It must only be called once.
+ */
+void
+gedit_debug_init (void)
+{
+ if (g_getenv ("GEDIT_DEBUG") != NULL)
+ {
+ /* enable all debugging */
+ enabled_sections = ~GEDIT_NO_DEBUG;
+ goto out;
+ }
+
+ if (g_getenv ("GEDIT_DEBUG_VIEW") != NULL)
+ {
+ enabled_sections |= GEDIT_DEBUG_VIEW;
+ }
+ if (g_getenv ("GEDIT_DEBUG_PREFS") != NULL)
+ {
+ enabled_sections |= GEDIT_DEBUG_PREFS;
+ }
+ if (g_getenv ("GEDIT_DEBUG_WINDOW") != NULL)
+ {
+ enabled_sections |= GEDIT_DEBUG_WINDOW;
+ }
+ if (g_getenv ("GEDIT_DEBUG_PANEL") != NULL)
+ {
+ enabled_sections |= GEDIT_DEBUG_PANEL;
+ }
+ if (g_getenv ("GEDIT_DEBUG_PLUGINS") != NULL)
+ {
+ enabled_sections |= GEDIT_DEBUG_PLUGINS;
+ }
+ if (g_getenv ("GEDIT_DEBUG_TAB") != NULL)
+ {
+ enabled_sections |= GEDIT_DEBUG_TAB;
+ }
+ if (g_getenv ("GEDIT_DEBUG_DOCUMENT") != NULL)
+ {
+ enabled_sections |= GEDIT_DEBUG_DOCUMENT;
+ }
+ if (g_getenv ("GEDIT_DEBUG_COMMANDS") != NULL)
+ {
+ enabled_sections |= GEDIT_DEBUG_COMMANDS;
+ }
+ if (g_getenv ("GEDIT_DEBUG_APP") != NULL)
+ {
+ enabled_sections |= GEDIT_DEBUG_APP;
+ }
+ if (g_getenv ("GEDIT_DEBUG_UTILS") != NULL)
+ {
+ enabled_sections |= GEDIT_DEBUG_UTILS;
+ }
+
+out:
+
+#ifdef ENABLE_PROFILING
+ if (enabled_sections != GEDIT_NO_DEBUG)
+ {
+ timer = g_timer_new ();
+ }
+#endif
+}
+
+/**
+ * gedit_debug:
+ * @section: debug section.
+ * @file: file name.
+ * @line: line number.
+ * @function: name of the function that is calling gedit_debug().
+ *
+ * If @section is enabled, then logs the trace information @file, @line, and
+ * @function.
+ */
+void
+gedit_debug (GeditDebugSection section,
+ const gchar *file,
+ gint line,
+ const gchar *function)
+{
+ gedit_debug_message (section, file, line, function, "%s", "");
+}
+
+/**
+ * gedit_debug_message:
+ * @section: debug section.
+ * @file: file name.
+ * @line: line number.
+ * @function: name of the function that is calling gedit_debug_message().
+ * @format: A g_vprintf() format string.
+ * @...: The format string arguments.
+ *
+ * If @section is enabled, then logs the trace information @file, @line, and
+ * @function along with the message obtained by formatting @format with the
+ * given format string arguments.
+ */
+void
+gedit_debug_message (GeditDebugSection section,
+ const gchar *file,
+ gint line,
+ const gchar *function,
+ const gchar *format,
+ ...)
+{
+ if (G_UNLIKELY (DEBUG_IS_ENABLED (section)))
+ {
+ va_list args;
+ gchar *msg;
+
+#ifdef ENABLE_PROFILING
+ gdouble seconds;
+
+ g_return_if_fail (timer != NULL);
+
+ seconds = g_timer_elapsed (timer, NULL);
+#endif
+
+ g_return_if_fail (format != NULL);
+
+ va_start (args, format);
+ msg = g_strdup_vprintf (format, args);
+ va_end (args);
+
+#ifdef ENABLE_PROFILING
+ g_print ("[%f (%f)] %s:%d (%s) %s\n",
+ seconds,
+ seconds - last_time,
+ file,
+ line,
+ function,
+ msg);
+
+ last_time = seconds;
+#else
+ g_print ("%s:%d (%s) %s\n", file, line, function, msg);
+#endif
+
+ fflush (stdout);
+
+ g_free (msg);
+ }
+}
+
+/**
+ * gedit_debug_plugin_message:
+ * @file: file name.
+ * @line: line number.
+ * @function: name of the function that is calling gedit_debug_plugin_message().
+ * @message: a message.
+ *
+ * If the section %GEDIT_DEBUG_PLUGINS is enabled, then logs the trace
+ * information @file, @line, and @function along with @message.
+ *
+ * This function may be overridden by GObject Introspection language bindings
+ * to be more language-specific.
+ *
+ * <emphasis>Python</emphasis>
+ *
+ * A PyGObject override is provided that has the following signature:
+ * <informalexample>
+ * <programlisting>
+ * def debug_plugin_message(format_str, *format_args):
+ * #...
+ * </programlisting>
+ * </informalexample>
+ *
+ * It automatically supplies parameters @file, @line, and @function, and it
+ * formats <code>format_str</code> with the given format arguments. The syntax
+ * of the format string is the usual Python string formatting syntax described
+ * by <ulink url="http://docs.python.org/library/stdtypes.html#string-formatting">5.6.2. String Formatting Operations</ulink>.
+ *
+ * Since: 3.4
+ */
+void
+gedit_debug_plugin_message (const gchar *file,
+ gint line,
+ const gchar *function,
+ const gchar *message)
+{
+ gedit_debug_message (GEDIT_DEBUG_PLUGINS, file, line, function, "%s", message);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-debug.h b/gedit/gedit-debug.h
new file mode 100644
index 0000000..a9d7caf
--- /dev/null
+++ b/gedit/gedit-debug.h
@@ -0,0 +1,83 @@
+/*
+ * gedit-debug.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002 - 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/>.
+ */
+
+#ifndef GEDIT_DEBUG_H
+#define GEDIT_DEBUG_H
+
+#include <glib.h>
+
+/**
+ * GeditDebugSection:
+ *
+ * Enumeration of debug sections.
+ *
+ * Debugging output for a section is enabled by setting an environment variable
+ * of the same name. For example, setting the <code>GEDIT_DEBUG_PLUGINS</code>
+ * environment variable enables all debugging output for the %GEDIT_DEBUG_PLUGINS
+ * section. Setting the special environment variable <code>GEDIT_DEBUG</code>
+ * enables output for all sections.
+ */
+typedef enum {
+ GEDIT_NO_DEBUG = 0,
+ GEDIT_DEBUG_VIEW = 1 << 0,
+ GEDIT_DEBUG_PREFS = 1 << 1,
+ GEDIT_DEBUG_WINDOW = 1 << 2,
+ GEDIT_DEBUG_PANEL = 1 << 3,
+ GEDIT_DEBUG_PLUGINS = 1 << 4,
+ GEDIT_DEBUG_TAB = 1 << 5,
+ GEDIT_DEBUG_DOCUMENT = 1 << 6,
+ GEDIT_DEBUG_COMMANDS = 1 << 7,
+ GEDIT_DEBUG_APP = 1 << 8,
+ GEDIT_DEBUG_UTILS = 1 << 9,
+} GeditDebugSection;
+
+#define DEBUG_VIEW GEDIT_DEBUG_VIEW, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_PREFS GEDIT_DEBUG_PREFS, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_WINDOW GEDIT_DEBUG_WINDOW, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_PANEL GEDIT_DEBUG_PANEL, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_PLUGINS GEDIT_DEBUG_PLUGINS, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_TAB GEDIT_DEBUG_TAB, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_DOCUMENT GEDIT_DEBUG_DOCUMENT,__FILE__, __LINE__, G_STRFUNC
+#define DEBUG_COMMANDS GEDIT_DEBUG_COMMANDS,__FILE__, __LINE__, G_STRFUNC
+#define DEBUG_APP GEDIT_DEBUG_APP, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_UTILS GEDIT_DEBUG_UTILS, __FILE__, __LINE__, G_STRFUNC
+
+void gedit_debug_init (void);
+
+void gedit_debug (GeditDebugSection section,
+ const gchar *file,
+ gint line,
+ const gchar *function);
+
+void gedit_debug_message (GeditDebugSection section,
+ const gchar *file,
+ gint line,
+ const gchar *function,
+ const gchar *format, ...) G_GNUC_PRINTF(5, 6);
+
+void gedit_debug_plugin_message (const gchar *file,
+ gint line,
+ const gchar *function,
+ const gchar *message);
+
+#endif /* GEDIT_DEBUG_H */
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-dirs.c b/gedit/gedit-dirs.c
new file mode 100644
index 0000000..81f32c2
--- /dev/null
+++ b/gedit/gedit-dirs.c
@@ -0,0 +1,176 @@
+/*
+ * gedit-dirs.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2008 Ignacio Casal Quinteiro
+ *
+ * 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-dirs.h"
+
+#ifdef OS_OSX
+#include <gtkosxapplication.h>
+#endif
+
+static gchar *user_config_dir = NULL;
+static gchar *user_data_dir = NULL;
+static gchar *user_styles_dir = NULL;
+static gchar *user_plugins_dir = NULL;
+static gchar *gedit_locale_dir = NULL;
+static gchar *gedit_lib_dir = NULL;
+static gchar *gedit_plugins_dir = NULL;
+static gchar *gedit_plugins_data_dir = NULL;
+
+void
+gedit_dirs_init ()
+{
+#ifdef G_OS_WIN32
+ gchar *win32_dir;
+
+ win32_dir = g_win32_get_package_installation_directory_of_module (NULL);
+
+ gedit_locale_dir = g_build_filename (win32_dir,
+ "share",
+ "locale",
+ NULL);
+ gedit_lib_dir = g_build_filename (win32_dir,
+ "lib",
+ "gedit",
+ NULL);
+ gedit_plugins_data_dir = g_build_filename (win32_dir,
+ "share",
+ "gedit",
+ "plugins",
+ NULL);
+
+ g_free (win32_dir);
+#endif /* G_OS_WIN32 */
+
+#ifdef OS_OSX
+ if (gtkosx_application_get_bundle_id () != NULL)
+ {
+ const gchar *bundle_resource_dir = gtkosx_application_get_resource_path ();
+
+ gedit_locale_dir = g_build_filename (bundle_resource_dir,
+ "share",
+ "locale",
+ NULL);
+ gedit_lib_dir = g_build_filename (bundle_resource_dir,
+ "lib",
+ "gedit",
+ NULL);
+ gedit_plugins_data_dir = g_build_filename (bundle_resource_dir,
+ "share",
+ "gedit",
+ "plugins",
+ NULL);
+ }
+#endif /* OS_OSX */
+
+ if (gedit_locale_dir == NULL)
+ {
+ gedit_locale_dir = g_build_filename (DATADIR,
+ "locale",
+ NULL);
+ gedit_lib_dir = g_build_filename (LIBDIR,
+ "gedit",
+ NULL);
+ gedit_plugins_data_dir = g_build_filename (DATADIR,
+ "gedit",
+ "plugins",
+ NULL);
+ }
+
+ user_config_dir = g_build_filename (g_get_user_config_dir (),
+ "gedit",
+ NULL);
+ user_data_dir = g_build_filename (g_get_user_data_dir (),
+ "gedit",
+ NULL);
+ user_styles_dir = g_build_filename (user_data_dir,
+ "styles",
+ NULL);
+ user_plugins_dir = g_build_filename (user_data_dir,
+ "plugins",
+ NULL);
+ gedit_plugins_dir = g_build_filename (gedit_lib_dir,
+ "plugins",
+ NULL);
+}
+
+void
+gedit_dirs_shutdown ()
+{
+ g_clear_pointer (&user_config_dir, g_free);
+ g_clear_pointer (&user_data_dir, g_free);
+ g_clear_pointer (&user_styles_dir, g_free);
+ g_clear_pointer (&user_plugins_dir, g_free);
+ g_clear_pointer (&gedit_locale_dir, g_free);
+ g_clear_pointer (&gedit_lib_dir, g_free);
+ g_clear_pointer (&gedit_plugins_dir, g_free);
+ g_clear_pointer (&gedit_plugins_data_dir, g_free);
+}
+
+const gchar *
+gedit_dirs_get_user_config_dir (void)
+{
+ return user_config_dir;
+}
+
+const gchar *
+gedit_dirs_get_user_data_dir (void)
+{
+ return user_data_dir;
+}
+
+const gchar *
+gedit_dirs_get_user_styles_dir (void)
+{
+ return user_styles_dir;
+}
+
+const gchar *
+gedit_dirs_get_user_plugins_dir (void)
+{
+ return user_plugins_dir;
+}
+
+const gchar *
+gedit_dirs_get_gedit_locale_dir (void)
+{
+ return gedit_locale_dir;
+}
+
+const gchar *
+gedit_dirs_get_gedit_lib_dir (void)
+{
+ return gedit_lib_dir;
+}
+
+const gchar *
+gedit_dirs_get_gedit_plugins_dir (void)
+{
+ return gedit_plugins_dir;
+}
+
+const gchar *
+gedit_dirs_get_gedit_plugins_data_dir (void)
+{
+ return gedit_plugins_data_dir;
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-dirs.h b/gedit/gedit-dirs.h
new file mode 100644
index 0000000..3fc7ab5
--- /dev/null
+++ b/gedit/gedit-dirs.h
@@ -0,0 +1,55 @@
+/*
+ * gedit-dirs.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2008 Ignacio Casal Quinteiro
+ *
+ * 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/>.
+ */
+
+
+#ifndef GEDIT_DIRS_H
+#define GEDIT_DIRS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/* This function must be called before starting gedit */
+void gedit_dirs_init (void);
+/* This function must be called before exiting gedit */
+void gedit_dirs_shutdown (void);
+
+
+const gchar *gedit_dirs_get_user_config_dir (void);
+
+const gchar *gedit_dirs_get_user_data_dir (void);
+
+const gchar *gedit_dirs_get_user_styles_dir (void);
+
+const gchar *gedit_dirs_get_user_plugins_dir (void);
+
+const gchar *gedit_dirs_get_gedit_locale_dir (void);
+
+const gchar *gedit_dirs_get_gedit_lib_dir (void);
+
+const gchar *gedit_dirs_get_gedit_plugins_dir (void);
+
+const gchar *gedit_dirs_get_gedit_plugins_data_dir (void);
+
+G_END_DECLS
+
+#endif /* GEDIT_DIRS_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-document-private.h b/gedit/gedit-document-private.h
new file mode 100644
index 0000000..6a07827
--- /dev/null
+++ b/gedit/gedit-document-private.h
@@ -0,0 +1,57 @@
+/*
+ * gedit-document.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2005 Paolo Maggi
+ * Copyright (C) 2014, 2020 Sébastien Wilmet
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_DOCUMENT_PRIVATE_H
+#define GEDIT_DOCUMENT_PRIVATE_H
+
+#include "gedit-document.h"
+
+G_BEGIN_DECLS
+
+#define GEDIT_METADATA_ATTRIBUTE_POSITION "gedit-position"
+#define GEDIT_METADATA_ATTRIBUTE_ENCODING "gedit-encoding"
+#define GEDIT_METADATA_ATTRIBUTE_LANGUAGE "gedit-language"
+
+G_GNUC_INTERNAL
+glong _gedit_document_get_seconds_since_last_save_or_load (GeditDocument *doc);
+
+G_GNUC_INTERNAL
+gboolean _gedit_document_needs_saving (GeditDocument *doc);
+
+G_GNUC_INTERNAL
+gboolean _gedit_document_get_empty_search (GeditDocument *doc);
+
+G_GNUC_INTERNAL
+void _gedit_document_set_create (GeditDocument *doc,
+ gboolean create);
+
+G_GNUC_INTERNAL
+gboolean _gedit_document_get_create (GeditDocument *doc);
+
+G_GNUC_INTERNAL
+gboolean _gedit_document_is_untitled (GeditDocument *doc);
+
+G_END_DECLS
+
+#endif /* GEDIT_DOCUMENT_PRIVATE_H */
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-document.c b/gedit/gedit-document.c
new file mode 100644
index 0000000..b19038a
--- /dev/null
+++ b/gedit/gedit-document.c
@@ -0,0 +1,1276 @@
+/*
+ * gedit-document.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2005 Paolo Maggi
+ * Copyright (C) 2014-2020 Sébastien Wilmet
+ *
+ * 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-document.h"
+#include "gedit-document-private.h"
+#include <string.h>
+#include <glib/gi18n.h>
+#include "gedit-settings.h"
+#include "gedit-debug.h"
+#include "gedit-utils.h"
+
+#define NO_LANGUAGE_NAME "_NORMAL_"
+
+static void gedit_document_loaded_real (GeditDocument *doc);
+
+static void gedit_document_saved_real (GeditDocument *doc);
+
+static void set_content_type (GeditDocument *doc,
+ const gchar *content_type);
+
+typedef struct
+{
+ GtkSourceFile *file;
+
+ TeplMetadata *metadata;
+
+ gchar *content_type;
+
+ GDateTime *time_of_last_save_or_load;
+
+ /* The search context for the incremental search, or the search and
+ * replace. They are mutually exclusive.
+ */
+ GtkSourceSearchContext *search_context;
+
+ guint language_set_by_user : 1;
+
+ /* The search is empty if there is no search context, or if the
+ * search text is empty. It is used for the sensitivity of some menu
+ * actions.
+ */
+ guint empty_search : 1;
+
+ /* Create file if location points to a non existing file (for example
+ * when opened from the command line).
+ */
+ guint create : 1;
+} GeditDocumentPrivate;
+
+enum
+{
+ PROP_0,
+ PROP_CONTENT_TYPE,
+ PROP_MIME_TYPE,
+ PROP_EMPTY_SEARCH,
+ N_PROPERTIES
+};
+
+enum
+{
+ SIGNAL_LOAD,
+ SIGNAL_LOADED,
+ SIGNAL_SAVE,
+ SIGNAL_SAVED,
+ N_SIGNALS
+};
+
+static GParamSpec *properties[N_PROPERTIES];
+static guint document_signals[N_SIGNALS];
+
+G_DEFINE_TYPE_WITH_PRIVATE (GeditDocument, gedit_document, TEPL_TYPE_BUFFER)
+
+static void
+load_metadata_from_metadata_manager (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv = gedit_document_get_instance_private (doc);
+ GFile *location;
+
+ location = gtk_source_file_get_location (priv->file);
+
+ if (location != NULL)
+ {
+ TeplMetadataManager *manager;
+
+ manager = tepl_metadata_manager_get_singleton ();
+ tepl_metadata_manager_copy_from (manager, location, priv->metadata);
+ }
+}
+
+static void
+save_metadata_into_metadata_manager (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv = gedit_document_get_instance_private (doc);
+ GFile *location;
+
+ location = gtk_source_file_get_location (priv->file);
+
+ if (location != NULL)
+ {
+ TeplMetadataManager *manager;
+
+ manager = tepl_metadata_manager_get_singleton ();
+ tepl_metadata_manager_merge_into (manager, location, priv->metadata);
+ }
+}
+
+static void
+update_time_of_last_save_or_load (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv = gedit_document_get_instance_private (doc);
+
+ if (priv->time_of_last_save_or_load != NULL)
+ {
+ g_date_time_unref (priv->time_of_last_save_or_load);
+ }
+
+ priv->time_of_last_save_or_load = g_date_time_new_now_utc ();
+}
+
+static const gchar *
+get_language_string (GeditDocument *doc)
+{
+ GtkSourceLanguage *lang = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (doc));
+
+ return lang != NULL ? gtk_source_language_get_id (lang) : NO_LANGUAGE_NAME;
+}
+
+static void
+save_metadata (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv;
+ const gchar *language = NULL;
+ GtkTextIter iter;
+ gchar *position;
+
+ priv = gedit_document_get_instance_private (doc);
+ if (priv->language_set_by_user)
+ {
+ language = get_language_string (doc);
+ }
+
+ gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (doc),
+ &iter,
+ gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (doc)));
+
+ position = g_strdup_printf ("%d", gtk_text_iter_get_offset (&iter));
+
+ if (language == NULL)
+ {
+ gedit_document_set_metadata (doc,
+ GEDIT_METADATA_ATTRIBUTE_POSITION, position,
+ NULL);
+ }
+ else
+ {
+ gedit_document_set_metadata (doc,
+ GEDIT_METADATA_ATTRIBUTE_POSITION, position,
+ GEDIT_METADATA_ATTRIBUTE_LANGUAGE, language,
+ NULL);
+ }
+
+ g_free (position);
+}
+
+static void
+gedit_document_dispose (GObject *object)
+{
+ GeditDocument *doc = GEDIT_DOCUMENT (object);
+ GeditDocumentPrivate *priv = gedit_document_get_instance_private (doc);
+
+ gedit_debug (DEBUG_DOCUMENT);
+
+ /* Metadata must be saved here and not in finalize because the language
+ * is gone by the time finalize runs.
+ */
+ if (priv->metadata != NULL)
+ {
+ save_metadata (doc);
+
+ g_object_unref (priv->metadata);
+ priv->metadata = NULL;
+ }
+
+ g_clear_object (&priv->file);
+ g_clear_object (&priv->search_context);
+
+ G_OBJECT_CLASS (gedit_document_parent_class)->dispose (object);
+}
+
+static void
+gedit_document_finalize (GObject *object)
+{
+ GeditDocumentPrivate *priv = gedit_document_get_instance_private (GEDIT_DOCUMENT (object));
+
+ gedit_debug (DEBUG_DOCUMENT);
+
+ g_free (priv->content_type);
+
+ if (priv->time_of_last_save_or_load != NULL)
+ {
+ g_date_time_unref (priv->time_of_last_save_or_load);
+ }
+
+ G_OBJECT_CLASS (gedit_document_parent_class)->finalize (object);
+}
+
+static void
+gedit_document_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditDocument *doc = GEDIT_DOCUMENT (object);
+ GeditDocumentPrivate *priv = gedit_document_get_instance_private (doc);
+
+ switch (prop_id)
+ {
+ case PROP_CONTENT_TYPE:
+ g_value_take_string (value, gedit_document_get_content_type (doc));
+ break;
+
+ case PROP_MIME_TYPE:
+ g_value_take_string (value, gedit_document_get_mime_type (doc));
+ break;
+
+ case PROP_EMPTY_SEARCH:
+ g_value_set_boolean (value, priv->empty_search);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_document_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditDocument *doc = GEDIT_DOCUMENT (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONTENT_TYPE:
+ set_content_type (doc, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_document_constructed (GObject *object)
+{
+ GeditDocument *doc = GEDIT_DOCUMENT (object);
+ GeditSettings *settings;
+ GSettings *editor_settings;
+
+ settings = _gedit_settings_get_singleton ();
+ editor_settings = _gedit_settings_peek_editor_settings (settings);
+
+ /* Bind construct properties. */
+ g_settings_bind (editor_settings, GEDIT_SETTINGS_ENSURE_TRAILING_NEWLINE,
+ doc, "implicit-trailing-newline",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY);
+
+ G_OBJECT_CLASS (gedit_document_parent_class)->constructed (object);
+}
+
+static void
+gedit_document_class_init (GeditDocumentClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gedit_document_dispose;
+ object_class->finalize = gedit_document_finalize;
+ object_class->get_property = gedit_document_get_property;
+ object_class->set_property = gedit_document_set_property;
+ object_class->constructed = gedit_document_constructed;
+
+ klass->loaded = gedit_document_loaded_real;
+ klass->saved = gedit_document_saved_real;
+
+ /**
+ * GeditDocument:content-type:
+ *
+ * The document's content type.
+ */
+ properties[PROP_CONTENT_TYPE] =
+ g_param_spec_string ("content-type",
+ "content-type",
+ "",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GeditDocument:mime-type:
+ *
+ * The document's MIME type.
+ */
+ properties[PROP_MIME_TYPE] =
+ g_param_spec_string ("mime-type",
+ "mime-type",
+ "",
+ "text/plain",
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GeditDocument:empty-search:
+ *
+ * <warning>
+ * The property is used internally by gedit. It must not be used in a
+ * gedit plugin. The property can be modified or removed at any time.
+ * </warning>
+ *
+ * Whether the search is empty.
+ */
+ properties[PROP_EMPTY_SEARCH] =
+ g_param_spec_boolean ("empty-search",
+ "empty-search",
+ "",
+ TRUE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+
+ /**
+ * GeditDocument::load:
+ * @document: the #GeditDocument.
+ *
+ * The "load" signal is emitted at the beginning of a file loading.
+ *
+ * Before gedit 3.14 this signal contained parameters to configure the
+ * file loading (the location, encoding, etc). Plugins should not need
+ * those parameters.
+ */
+ document_signals[SIGNAL_LOAD] =
+ g_signal_new ("load",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditDocumentClass, load),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * GeditDocument::loaded:
+ * @document: the #GeditDocument.
+ *
+ * The "loaded" signal is emitted at the end of a successful file
+ * loading.
+ *
+ * Before gedit 3.14 this signal contained a #GError parameter, and the
+ * signal was also emitted if an error occurred. Plugins should not need
+ * the error parameter.
+ */
+ document_signals[SIGNAL_LOADED] =
+ g_signal_new ("loaded",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GeditDocumentClass, loaded),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * GeditDocument::save:
+ * @document: the #GeditDocument.
+ *
+ * The "save" signal is emitted at the beginning of a file saving.
+ *
+ * Before gedit 3.14 this signal contained parameters to configure the
+ * file saving (the location, encoding, etc). Plugins should not need
+ * those parameters.
+ */
+ document_signals[SIGNAL_SAVE] =
+ g_signal_new ("save",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditDocumentClass, save),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * GeditDocument::saved:
+ * @document: the #GeditDocument.
+ *
+ * The "saved" signal is emitted at the end of a successful file saving.
+ *
+ * Before gedit 3.14 this signal contained a #GError parameter, and the
+ * signal was also emitted if an error occurred. To save a document, a
+ * plugin can use the gedit_commands_save_document_async() function and
+ * get the result of the operation with
+ * gedit_commands_save_document_finish().
+ */
+ document_signals[SIGNAL_SAVED] =
+ g_signal_new ("saved",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GeditDocumentClass, saved),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+set_language (GeditDocument *doc,
+ GtkSourceLanguage *lang,
+ gboolean set_by_user)
+{
+ GeditDocumentPrivate *priv;
+ GtkSourceLanguage *old_lang;
+
+ gedit_debug (DEBUG_DOCUMENT);
+
+ priv = gedit_document_get_instance_private (doc);
+
+ old_lang = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (doc));
+
+ if (old_lang == lang)
+ {
+ return;
+ }
+
+ gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (doc), lang);
+
+ if (set_by_user)
+ {
+ const gchar *language = get_language_string (doc);
+
+ gedit_document_set_metadata (doc,
+ GEDIT_METADATA_ATTRIBUTE_LANGUAGE, language,
+ NULL);
+ }
+
+ priv->language_set_by_user = set_by_user;
+}
+
+static void
+save_encoding_metadata (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv;
+ const GtkSourceEncoding *encoding;
+ const gchar *charset;
+
+ gedit_debug (DEBUG_DOCUMENT);
+
+ priv = gedit_document_get_instance_private (doc);
+
+ encoding = gtk_source_file_get_encoding (priv->file);
+
+ if (encoding == NULL)
+ {
+ encoding = gtk_source_encoding_get_utf8 ();
+ }
+
+ charset = gtk_source_encoding_get_charset (encoding);
+
+ gedit_document_set_metadata (doc,
+ GEDIT_METADATA_ATTRIBUTE_ENCODING, charset,
+ NULL);
+}
+
+static GtkSourceLanguage *
+guess_language (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv;
+ gchar *data;
+ GtkSourceLanguageManager *manager = gtk_source_language_manager_get_default ();
+ GtkSourceLanguage *language = NULL;
+
+ priv = gedit_document_get_instance_private (doc);
+
+ data = gedit_document_get_metadata (doc, GEDIT_METADATA_ATTRIBUTE_LANGUAGE);
+
+ if (data != NULL)
+ {
+ gedit_debug_message (DEBUG_DOCUMENT, "Language from metadata: %s", data);
+
+ if (!g_str_equal (data, NO_LANGUAGE_NAME))
+ {
+ language = gtk_source_language_manager_get_language (manager, data);
+ }
+
+ g_free (data);
+ }
+ else
+ {
+ GFile *location;
+ gchar *basename = NULL;
+
+ location = gtk_source_file_get_location (priv->file);
+ gedit_debug_message (DEBUG_DOCUMENT, "Sniffing Language");
+
+ if (location != NULL)
+ {
+ basename = g_file_get_basename (location);
+ }
+
+ language = gtk_source_language_manager_guess_language (manager,
+ basename,
+ priv->content_type);
+
+ g_free (basename);
+ }
+
+ return language;
+}
+
+static void
+on_content_type_changed (GeditDocument *doc,
+ GParamSpec *pspec,
+ gpointer useless)
+{
+ GeditDocumentPrivate *priv;
+
+ priv = gedit_document_get_instance_private (doc);
+
+ if (!priv->language_set_by_user)
+ {
+ GtkSourceLanguage *language = guess_language (doc);
+
+ gedit_debug_message (DEBUG_DOCUMENT, "Language: %s",
+ language != NULL ? gtk_source_language_get_name (language) : "None");
+
+ set_language (doc, language, FALSE);
+ }
+}
+
+static gchar *
+get_default_content_type (void)
+{
+ return g_content_type_from_mime_type ("text/plain");
+}
+
+static void
+on_location_changed (GtkSourceFile *file,
+ GParamSpec *pspec,
+ GeditDocument *doc)
+{
+ gedit_debug (DEBUG_DOCUMENT);
+ load_metadata_from_metadata_manager (doc);
+}
+
+static void
+gedit_document_init (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv = gedit_document_get_instance_private (doc);
+ TeplFile *tepl_file;
+ GeditSettings *settings;
+ GSettings *editor_settings;
+
+ gedit_debug (DEBUG_DOCUMENT);
+
+ priv->content_type = get_default_content_type ();
+ priv->language_set_by_user = FALSE;
+ priv->empty_search = TRUE;
+
+ update_time_of_last_save_or_load (doc);
+
+ priv->file = gtk_source_file_new ();
+ tepl_file = tepl_buffer_get_file (TEPL_BUFFER (doc));
+
+ g_object_bind_property (priv->file, "location",
+ tepl_file, "location",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+ priv->metadata = tepl_metadata_new ();
+
+ g_signal_connect_object (priv->file,
+ "notify::location",
+ G_CALLBACK (on_location_changed),
+ doc,
+ 0);
+
+ settings = _gedit_settings_get_singleton ();
+ editor_settings = _gedit_settings_peek_editor_settings (settings);
+
+ g_settings_bind (editor_settings, GEDIT_SETTINGS_MAX_UNDO_ACTIONS,
+ doc, "max-undo-levels",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY);
+
+ g_settings_bind (editor_settings, GEDIT_SETTINGS_SYNTAX_HIGHLIGHTING,
+ doc, "highlight-syntax",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY);
+
+ g_settings_bind (editor_settings, GEDIT_SETTINGS_BRACKET_MATCHING,
+ doc, "highlight-matching-brackets",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY);
+
+ tepl_buffer_provide_style_scheme_id_gsetting (TEPL_BUFFER (doc),
+ editor_settings, GEDIT_SETTINGS_SCHEME,
+ TRUE);
+
+ g_signal_connect (doc,
+ "notify::content-type",
+ G_CALLBACK (on_content_type_changed),
+ NULL);
+}
+
+GeditDocument *
+gedit_document_new (void)
+{
+ return g_object_new (GEDIT_TYPE_DOCUMENT, NULL);
+}
+
+static gchar *
+get_content_type_from_content (GeditDocument *doc)
+{
+ gchar *content_type;
+ gchar *data;
+ GtkTextBuffer *buffer;
+ GtkTextIter start;
+ GtkTextIter end;
+
+ buffer = GTK_TEXT_BUFFER (doc);
+
+ gtk_text_buffer_get_start_iter (buffer, &start);
+ end = start;
+ gtk_text_iter_forward_chars (&end, 255);
+
+ data = gtk_text_buffer_get_text (buffer, &start, &end, TRUE);
+
+ content_type = g_content_type_guess (NULL,
+ (const guchar *)data,
+ strlen (data),
+ NULL);
+
+ g_free (data);
+
+ return content_type;
+}
+
+static void
+set_content_type_no_guess (GeditDocument *doc,
+ const gchar *content_type)
+{
+ GeditDocumentPrivate *priv;
+ gchar *dupped_content_type;
+
+ gedit_debug (DEBUG_DOCUMENT);
+
+ priv = gedit_document_get_instance_private (doc);
+
+ if (priv->content_type != NULL &&
+ content_type != NULL &&
+ g_str_equal (priv->content_type, content_type))
+ {
+ return;
+ }
+
+ g_free (priv->content_type);
+
+ /* For compression types, we try to just guess from the content */
+ if (gedit_utils_get_compression_type_from_content_type (content_type) !=
+ GTK_SOURCE_COMPRESSION_TYPE_NONE)
+ {
+ dupped_content_type = get_content_type_from_content (doc);
+ }
+ else
+ {
+ dupped_content_type = g_strdup (content_type);
+ }
+
+ if (dupped_content_type == NULL ||
+ g_content_type_is_unknown (dupped_content_type))
+ {
+ priv->content_type = get_default_content_type ();
+ g_free (dupped_content_type);
+ }
+ else
+ {
+ priv->content_type = dupped_content_type;
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (doc), properties[PROP_CONTENT_TYPE]);
+}
+
+static void
+set_content_type (GeditDocument *doc,
+ const gchar *content_type)
+{
+ GeditDocumentPrivate *priv;
+
+ gedit_debug (DEBUG_DOCUMENT);
+
+ priv = gedit_document_get_instance_private (doc);
+
+ if (content_type == NULL)
+ {
+ GFile *location;
+ gchar *guessed_type = NULL;
+
+ /* If content type is null, we guess from the filename */
+ location = gtk_source_file_get_location (priv->file);
+ if (location != NULL)
+ {
+ gchar *basename;
+
+ basename = g_file_get_basename (location);
+ guessed_type = g_content_type_guess (basename, NULL, 0, NULL);
+
+ g_free (basename);
+ }
+
+ set_content_type_no_guess (doc, guessed_type);
+ g_free (guessed_type);
+ }
+ else
+ {
+ set_content_type_no_guess (doc, content_type);
+ }
+}
+
+gchar *
+gedit_document_get_content_type (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv;
+
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL);
+
+ priv = gedit_document_get_instance_private (doc);
+
+ return g_strdup (priv->content_type);
+}
+
+/**
+ * gedit_document_get_mime_type:
+ * @doc: a #GeditDocument.
+ *
+ * Note: this never returns %NULL.
+ **/
+gchar *
+gedit_document_get_mime_type (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv;
+
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), g_strdup ("text/plain"));
+
+ priv = gedit_document_get_instance_private (doc);
+
+ if (priv->content_type != NULL &&
+ !g_content_type_is_unknown (priv->content_type))
+ {
+ return g_content_type_get_mime_type (priv->content_type);
+ }
+
+ return g_strdup ("text/plain");
+}
+
+static void
+loaded_query_info_cb (GFile *location,
+ GAsyncResult *result,
+ GeditDocument *doc)
+{
+ GFileInfo *info;
+ GError *error = NULL;
+
+ info = g_file_query_info_finish (location, result, &error);
+
+ if (error != NULL)
+ {
+ /* Ignore not found error as it can happen when opening a
+ * non-existent file from the command line.
+ */
+ if (error->domain != G_IO_ERROR ||
+ error->code != G_IO_ERROR_NOT_FOUND)
+ {
+ g_warning ("Document loading: query info error: %s", error->message);
+ }
+
+ g_error_free (error);
+ error = NULL;
+ }
+
+ if (info != NULL &&
+ g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE))
+ {
+ const gchar *content_type;
+
+ content_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
+
+ set_content_type (doc, content_type);
+ }
+
+ g_clear_object (&info);
+
+ /* Async operation finished. */
+ g_object_unref (doc);
+}
+
+static void
+gedit_document_loaded_real (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv;
+ GFile *location;
+
+ priv = gedit_document_get_instance_private (doc);
+
+ if (!priv->language_set_by_user)
+ {
+ GtkSourceLanguage *language = guess_language (doc);
+
+ gedit_debug_message (DEBUG_DOCUMENT, "Language: %s",
+ language != NULL ? gtk_source_language_get_name (language) : "None");
+
+ set_language (doc, language, FALSE);
+ }
+
+ update_time_of_last_save_or_load (doc);
+ set_content_type (doc, NULL);
+
+ location = gtk_source_file_get_location (priv->file);
+
+ if (location != NULL)
+ {
+ /* Keep the doc alive during the async operation. */
+ g_object_ref (doc);
+
+ g_file_query_info_async (location,
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ (GAsyncReadyCallback) loaded_query_info_cb,
+ doc);
+ }
+}
+
+static void
+saved_query_info_cb (GFile *location,
+ GAsyncResult *result,
+ GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv;
+ GFileInfo *info;
+ const gchar *content_type = NULL;
+ GError *error = NULL;
+
+ priv = gedit_document_get_instance_private (doc);
+
+ info = g_file_query_info_finish (location, result, &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Document saving: query info error: %s", error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+
+ if (info != NULL &&
+ g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE))
+ {
+ content_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
+ }
+
+ set_content_type (doc, content_type);
+
+ if (info != NULL)
+ {
+ /* content_type (owned by info) is no longer needed. */
+ g_object_unref (info);
+ }
+
+ update_time_of_last_save_or_load (doc);
+
+ priv->create = FALSE;
+
+ save_encoding_metadata (doc);
+
+ /* Async operation finished. */
+ g_object_unref (doc);
+}
+
+static void
+gedit_document_saved_real (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv;
+ GFile *location;
+
+ priv = gedit_document_get_instance_private (doc);
+
+ location = gtk_source_file_get_location (priv->file);
+
+ /* Keep the doc alive during the async operation. */
+ g_object_ref (doc);
+
+ g_file_query_info_async (location,
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ (GAsyncReadyCallback) saved_query_info_cb,
+ doc);
+}
+
+/* TODO: remove this function. */
+gboolean
+_gedit_document_is_untitled (GeditDocument *doc)
+{
+ TeplFile *file;
+
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), TRUE);
+
+ file = tepl_buffer_get_file (TEPL_BUFFER (doc));
+ return tepl_file_get_location (file) == NULL;
+}
+
+/*
+ * Deletion and external modification is only checked for local files.
+ */
+gboolean
+_gedit_document_needs_saving (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv;
+ gboolean externally_modified = FALSE;
+ gboolean deleted = FALSE;
+
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), FALSE);
+
+ priv = gedit_document_get_instance_private (doc);
+
+ if (gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc)))
+ {
+ return TRUE;
+ }
+
+ if (gtk_source_file_is_local (priv->file))
+ {
+ gtk_source_file_check_file_on_disk (priv->file);
+ externally_modified = gtk_source_file_is_externally_modified (priv->file);
+ deleted = gtk_source_file_is_deleted (priv->file);
+ }
+
+ return (externally_modified || deleted) && !priv->create;
+}
+
+/**
+ * gedit_document_set_language:
+ * @doc:
+ * @lang: (allow-none):
+ **/
+void
+gedit_document_set_language (GeditDocument *doc,
+ GtkSourceLanguage *lang)
+{
+ g_return_if_fail (GEDIT_IS_DOCUMENT (doc));
+
+ set_language (doc, lang, TRUE);
+}
+
+glong
+_gedit_document_get_seconds_since_last_save_or_load (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv;
+ GDateTime *now;
+ GTimeSpan n_microseconds;
+
+ gedit_debug (DEBUG_DOCUMENT);
+
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), -1);
+
+ priv = gedit_document_get_instance_private (doc);
+
+ if (priv->time_of_last_save_or_load == NULL)
+ {
+ return -1;
+ }
+
+ now = g_date_time_new_now_utc ();
+ if (now == NULL)
+ {
+ return -1;
+ }
+
+ n_microseconds = g_date_time_difference (now, priv->time_of_last_save_or_load);
+ g_date_time_unref (now);
+
+ return n_microseconds / (1000 * 1000);
+}
+
+/**
+ * gedit_document_get_metadata:
+ * @doc: a #GeditDocument
+ * @key: name of the key
+ *
+ * Gets the metadata assigned to @key.
+ *
+ * Returns: the value assigned to @key. Free with g_free().
+ */
+gchar *
+gedit_document_get_metadata (GeditDocument *doc,
+ const gchar *key)
+{
+ GeditDocumentPrivate *priv;
+
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL);
+ g_return_val_if_fail (key != NULL, NULL);
+
+ priv = gedit_document_get_instance_private (doc);
+
+ if (priv->metadata == NULL)
+ {
+ return NULL;
+ }
+
+ return tepl_metadata_get (priv->metadata, key);
+}
+
+/**
+ * gedit_document_set_metadata:
+ * @doc: a #GeditDocument
+ * @first_key: name of the first key to set
+ * @...: (allow-none): value for the first key, followed optionally by more key/value pairs,
+ * followed by %NULL.
+ *
+ * Sets metadata on a document.
+ */
+void
+gedit_document_set_metadata (GeditDocument *doc,
+ const gchar *first_key,
+ ...)
+{
+ GeditDocumentPrivate *priv;
+ va_list var_args;
+ const gchar *key;
+
+ g_return_if_fail (GEDIT_IS_DOCUMENT (doc));
+ g_return_if_fail (first_key != NULL);
+
+ priv = gedit_document_get_instance_private (doc);
+
+ if (priv->metadata == NULL)
+ {
+ return;
+ }
+
+ va_start (var_args, first_key);
+
+ for (key = first_key; key != NULL; key = va_arg (var_args, const gchar *))
+ {
+ const gchar *value = va_arg (var_args, const gchar *);
+ tepl_metadata_set (priv->metadata, key, value);
+ }
+
+ va_end (var_args);
+
+ save_metadata_into_metadata_manager (doc);
+}
+
+static void
+update_empty_search (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv;
+ gboolean new_value;
+
+ priv = gedit_document_get_instance_private (doc);
+
+ if (priv->search_context == NULL)
+ {
+ new_value = TRUE;
+ }
+ else
+ {
+ GtkSourceSearchSettings *search_settings;
+
+ search_settings = gtk_source_search_context_get_settings (priv->search_context);
+
+ new_value = gtk_source_search_settings_get_search_text (search_settings) == NULL;
+ }
+
+ if (priv->empty_search != new_value)
+ {
+ priv->empty_search = new_value;
+ g_object_notify_by_pspec (G_OBJECT (doc), properties[PROP_EMPTY_SEARCH]);
+ }
+}
+
+static void
+connect_search_settings (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv;
+ GtkSourceSearchSettings *search_settings;
+
+ priv = gedit_document_get_instance_private (doc);
+
+ search_settings = gtk_source_search_context_get_settings (priv->search_context);
+
+ /* Note: the signal handler is never disconnected. If the search context
+ * changes its search settings, the old search settings will most
+ * probably be destroyed, anyway. So it shouldn't cause performance
+ * problems.
+ */
+ g_signal_connect_object (search_settings,
+ "notify::search-text",
+ G_CALLBACK (update_empty_search),
+ doc,
+ G_CONNECT_SWAPPED);
+}
+
+/**
+ * gedit_document_set_search_context:
+ * @doc: a #GeditDocument
+ * @search_context: (allow-none): the new #GtkSourceSearchContext
+ *
+ * Sets the new search context for the document. Use this function only when the
+ * search occurrences are highlighted. So this function should not be used for
+ * background searches. The purpose is to have only one highlighted search
+ * context at a time in the document.
+ *
+ * After using this function, you should unref the @search_context. The @doc
+ * should be the only owner of the @search_context, so that the Clear Highlight
+ * action works. If you need the @search_context after calling this function,
+ * use gedit_document_get_search_context().
+ */
+void
+gedit_document_set_search_context (GeditDocument *doc,
+ GtkSourceSearchContext *search_context)
+{
+ GeditDocumentPrivate *priv;
+
+ g_return_if_fail (GEDIT_IS_DOCUMENT (doc));
+
+ priv = gedit_document_get_instance_private (doc);
+
+ if (priv->search_context != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (priv->search_context,
+ connect_search_settings,
+ doc);
+
+ g_object_unref (priv->search_context);
+ }
+
+ priv->search_context = search_context;
+
+ if (search_context != NULL)
+ {
+ GeditSettings *settings;
+ GSettings *editor_settings;
+
+ g_object_ref (search_context);
+
+ settings = _gedit_settings_get_singleton ();
+ editor_settings = _gedit_settings_peek_editor_settings (settings);
+
+ g_settings_bind (editor_settings, GEDIT_SETTINGS_SEARCH_HIGHLIGHTING,
+ search_context, "highlight",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY);
+
+ g_signal_connect_object (search_context,
+ "notify::settings",
+ G_CALLBACK (connect_search_settings),
+ doc,
+ G_CONNECT_SWAPPED);
+
+ connect_search_settings (doc);
+ }
+
+ update_empty_search (doc);
+}
+
+/**
+ * gedit_document_get_search_context:
+ * @doc: a #GeditDocument
+ *
+ * Gets the search context. Use this function only if you have used
+ * gedit_document_set_search_context() before. You should not alter other search
+ * contexts, so you have to verify that the returned search context is yours.
+ * One way to verify that is to compare the search settings object, or to mark
+ * the search context with g_object_set_data().
+ *
+ * Returns: (transfer none): the current search context of the document, or NULL
+ * if there is no current search context.
+ */
+GtkSourceSearchContext *
+gedit_document_get_search_context (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv;
+
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL);
+
+ priv = gedit_document_get_instance_private (doc);
+
+ return priv->search_context;
+}
+
+gboolean
+_gedit_document_get_empty_search (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv;
+
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), TRUE);
+
+ priv = gedit_document_get_instance_private (doc);
+
+ return priv->empty_search;
+}
+
+/**
+ * gedit_document_get_file:
+ * @doc: a #GeditDocument.
+ *
+ * Gets the associated #GtkSourceFile. You should use it only for reading
+ * purposes, not for creating a #GtkSourceFileLoader or #GtkSourceFileSaver,
+ * because gedit does some extra work when loading or saving a file and
+ * maintains an internal state. If you use in a plugin a file loader or saver on
+ * the returned #GtkSourceFile, the internal state of gedit won't be updated.
+ *
+ * If you want to save the #GeditDocument to a secondary file, you can create a
+ * new #GtkSourceFile and use a #GtkSourceFileSaver.
+ *
+ * Returns: (transfer none): the associated #GtkSourceFile.
+ * Since: 3.14
+ */
+GtkSourceFile *
+gedit_document_get_file (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv;
+
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL);
+
+ priv = gedit_document_get_instance_private (doc);
+
+ return priv->file;
+}
+
+void
+_gedit_document_set_create (GeditDocument *doc,
+ gboolean create)
+{
+ GeditDocumentPrivate *priv;
+
+ g_return_if_fail (GEDIT_IS_DOCUMENT (doc));
+
+ priv = gedit_document_get_instance_private (doc);
+
+ priv->create = create != FALSE;
+}
+
+gboolean
+_gedit_document_get_create (GeditDocument *doc)
+{
+ GeditDocumentPrivate *priv;
+
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), FALSE);
+
+ priv = gedit_document_get_instance_private (doc);
+
+ return priv->create;
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-document.h b/gedit/gedit-document.h
new file mode 100644
index 0000000..087fb28
--- /dev/null
+++ b/gedit/gedit-document.h
@@ -0,0 +1,77 @@
+/*
+ * gedit-document.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2005 Paolo Maggi
+ * Copyright (C) 2014-2020 Sébastien Wilmet
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_DOCUMENT_H
+#define GEDIT_DOCUMENT_H
+
+#include <tepl/tepl.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_DOCUMENT (gedit_document_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (GeditDocument, gedit_document, GEDIT, DOCUMENT, TeplBuffer)
+
+struct _GeditDocumentClass
+{
+ TeplBufferClass parent_class;
+
+ /* Signals */
+
+ void (* load) (GeditDocument *document);
+
+ void (* loaded) (GeditDocument *document);
+
+ void (* save) (GeditDocument *document);
+
+ void (* saved) (GeditDocument *document);
+};
+
+GeditDocument *gedit_document_new (void);
+
+GtkSourceFile *gedit_document_get_file (GeditDocument *doc);
+
+gchar *gedit_document_get_content_type (GeditDocument *doc);
+
+gchar *gedit_document_get_mime_type (GeditDocument *doc);
+
+void gedit_document_set_language (GeditDocument *doc,
+ GtkSourceLanguage *lang);
+
+gchar *gedit_document_get_metadata (GeditDocument *doc,
+ const gchar *key);
+
+void gedit_document_set_metadata (GeditDocument *doc,
+ const gchar *first_key,
+ ...);
+
+void gedit_document_set_search_context (GeditDocument *doc,
+ GtkSourceSearchContext *search_context);
+
+GtkSourceSearchContext *
+ gedit_document_get_search_context (GeditDocument *doc);
+
+G_END_DECLS
+
+#endif /* GEDIT_DOCUMENT_H */
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-documents-panel.c b/gedit/gedit-documents-panel.c
new file mode 100644
index 0000000..5ffe77e
--- /dev/null
+++ b/gedit/gedit-documents-panel.c
@@ -0,0 +1,1740 @@
+/*
+ * gedit-documents-panel.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Sébastien Lafargue <slafargue@gnome.org>
+ *
+ * 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-documents-panel.h"
+
+#include <glib/gi18n.h>
+#include <tepl/tepl.h>
+
+#include "gedit-debug.h"
+#include "gedit-document.h"
+#include "gedit-document-private.h"
+#include "gedit-multi-notebook.h"
+#include "gedit-notebook.h"
+#include "gedit-notebook-popup-menu.h"
+#include "gedit-tab.h"
+#include "gedit-tab-private.h"
+#include "gedit-utils.h"
+#include "gedit-commands-private.h"
+
+typedef struct _GeditDocumentsGenericRow GeditDocumentsGenericRow;
+typedef struct _GeditDocumentsGenericRow GeditDocumentsGroupRow;
+typedef struct _GeditDocumentsGenericRow GeditDocumentsDocumentRow;
+
+struct _GeditDocumentsGenericRow
+{
+ GtkListBoxRow parent_instance;
+
+ GeditDocumentsPanel *panel;
+ GtkWidget *ref;
+
+ GtkWidget *box;
+ GtkWidget *label;
+ GtkWidget *close_button;
+
+ /* Not used in GeditDocumentsGroupRow */
+ GtkWidget *image;
+ GtkWidget *status_label;
+};
+
+#define GEDIT_TYPE_DOCUMENTS_GROUP_ROW (gedit_documents_group_row_get_type ())
+#define GEDIT_DOCUMENTS_GROUP_ROW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_DOCUMENTS_GROUP_ROW, GeditDocumentsGroupRow))
+#define GEDIT_DOCUMENTS_GROUP_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_DOCUMENTS_GROUP_ROW, GeditDocumentsGroupRowClass))
+#define GEDIT_IS_DOCUMENTS_GROUP_ROW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_DOCUMENTS_GROUP_ROW))
+#define GEDIT_IS_DOCUMENTS_GROUP_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_DOCUMENTS_GROUP_ROW))
+#define GEDIT_DOCUMENTS_GROUP_ROW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_DOCUMENTS_GROUP_ROW, GeditDocumentsGroupRowClass))
+
+typedef struct _GeditDocumentsGroupRowClass GeditDocumentsGroupRowClass;
+
+struct _GeditDocumentsGroupRowClass
+{
+ GtkListBoxRowClass parent_class;
+};
+
+GType gedit_documents_group_row_get_type (void) G_GNUC_CONST;
+
+G_DEFINE_TYPE (GeditDocumentsGroupRow, gedit_documents_group_row, GTK_TYPE_LIST_BOX_ROW)
+
+#define GEDIT_TYPE_DOCUMENTS_DOCUMENT_ROW (gedit_documents_document_row_get_type ())
+#define GEDIT_DOCUMENTS_DOCUMENT_ROW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_DOCUMENTS_DOCUMENT_ROW, GeditDocumentsDocumentRow))
+#define GEDIT_DOCUMENTS_DOCUMENT_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_DOCUMENTS_DOCUMENT_ROW, GeditDocumentsDocumentRowClass))
+#define GEDIT_IS_DOCUMENTS_DOCUMENT_ROW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_DOCUMENTS_DOCUMENT_ROW))
+#define GEDIT_IS_DOCUMENTS_DOCUMENT_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_DOCUMENTS_DOCUMENT_ROW))
+#define GEDIT_DOCUMENTS_DOCUMENT_ROW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_DOCUMENTS_DOCUMENT_ROW, GeditDocumentsDocumentRowClass))
+
+typedef struct _GeditDocumentsDocumentRowClass GeditDocumentsDocumentRowClass;
+
+struct _GeditDocumentsDocumentRowClass
+{
+ GtkListBoxRowClass parent_class;
+};
+
+GType gedit_documents_document_row_get_type (void) G_GNUC_CONST;
+
+G_DEFINE_TYPE (GeditDocumentsDocumentRow, gedit_documents_document_row, GTK_TYPE_LIST_BOX_ROW)
+
+static GtkWidget *gedit_documents_document_row_new (GeditDocumentsPanel *panel,
+ GeditTab *tab);
+static GtkWidget *gedit_documents_group_row_new (GeditDocumentsPanel *panel,
+ GeditNotebook *notebook);
+
+struct _GeditDocumentsPanel
+{
+ GtkBox parent_instance;
+
+ GeditWindow *window;
+ GeditMultiNotebook *mnb;
+ GtkWidget *listbox;
+
+ guint selection_changed_handler_id;
+ guint tab_switched_handler_id;
+ gboolean is_in_tab_switched;
+
+ /* Flag to workaround first GroupRow selection at start ( we don't want to show it ) */
+ gboolean first_selection;
+ GtkWidget *current_selection;
+
+ GtkAdjustment *adjustment;
+
+ guint nb_row_notebook;
+ guint nb_row_tab;
+
+ GtkTargetList *source_targets;
+ GtkWidget *dnd_window;
+ GtkWidget *row_placeholder;
+ guint row_placeholder_index;
+ guint row_destination_index;
+ GtkWidget *drag_document_row;
+ gint row_source_row_offset;
+ gint document_row_height;
+ gint drag_document_row_x;
+ gint drag_document_row_y;
+ gint drag_root_x;
+ gint drag_root_y;
+ gboolean is_on_drag;
+};
+
+enum
+{
+ PROP_0,
+ PROP_WINDOW,
+ LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP];
+
+G_DEFINE_TYPE (GeditDocumentsPanel, gedit_documents_panel, GTK_TYPE_BOX)
+
+static const GtkTargetEntry panel_targets [] = {
+ {"GEDIT_DOCUMENTS_DOCUMENT_ROW", GTK_TARGET_SAME_APP, 0},
+};
+
+#define MAX_DOC_NAME_LENGTH 60
+
+#define ROW_OUTSIDE_LISTBOX -1
+
+static guint
+get_nb_visible_rows (GeditDocumentsPanel *panel)
+{
+ guint nb = 0;
+
+ if (panel->nb_row_notebook > 1)
+ {
+ nb += panel->nb_row_notebook;
+ }
+
+ nb += panel->nb_row_tab;
+
+ return nb;
+}
+
+static guint
+get_row_visible_index (GeditDocumentsPanel *panel,
+ GtkWidget *searched_row)
+{
+ GList *children;
+ GList *l;
+ guint nb_notebook_row = 0;
+ guint nb_tab_row = 0;
+
+ children = gtk_container_get_children (GTK_CONTAINER (panel->listbox));
+
+ for (l = children; l != NULL; l = g_list_next (l))
+ {
+ GtkWidget *row = l->data;
+
+ if (GEDIT_IS_DOCUMENTS_GROUP_ROW (row))
+ {
+ nb_notebook_row += 1;
+ }
+ else
+ {
+ nb_tab_row += 1;
+ }
+
+ if (row == searched_row)
+ {
+ break;
+ }
+ }
+
+ g_list_free (children);
+
+ if (panel->nb_row_notebook == 1)
+ {
+ nb_notebook_row = 0;
+ }
+
+ return nb_tab_row + nb_notebook_row - 1;
+}
+
+/* We do not grab focus on the row, so scroll it into view manually */
+static void
+make_row_visible (GeditDocumentsPanel *panel,
+ GtkWidget *row)
+{
+ gdouble adjustment_value;
+ gdouble adjustment_lower;
+ gdouble adjustment_upper;
+ gdouble adjustment_page_size;
+ gdouble nb_visible_rows;
+ gdouble row_visible_index;
+ gdouble row_size;
+ gdouble row_position;
+ gdouble offset;
+ gdouble new_adjustment_value;
+
+ adjustment_value = gtk_adjustment_get_value (panel->adjustment);
+ adjustment_lower = gtk_adjustment_get_lower (panel->adjustment);
+ adjustment_upper = gtk_adjustment_get_upper (panel->adjustment);
+ adjustment_page_size = gtk_adjustment_get_page_size (panel->adjustment);
+
+ nb_visible_rows = get_nb_visible_rows (panel);
+ row_visible_index = get_row_visible_index (panel, GTK_WIDGET (row));
+
+ row_size = (adjustment_upper - adjustment_lower) / nb_visible_rows;
+ row_position = row_size * row_visible_index;
+
+ if (row_position < adjustment_value)
+ {
+ new_adjustment_value = row_position;
+ }
+ else if ((row_position + row_size) > (adjustment_value + adjustment_page_size))
+ {
+ offset = (row_position + row_size) - (adjustment_value + adjustment_page_size);
+ new_adjustment_value = adjustment_value + offset;
+ }
+ else
+ {
+ new_adjustment_value = adjustment_value;
+ }
+
+ gtk_adjustment_set_value (panel->adjustment, new_adjustment_value);
+}
+
+/* This function is a GCompareFunc to use with g_list_find_custom */
+static gint
+listbox_search_function (gconstpointer row,
+ gconstpointer item)
+{
+ GeditDocumentsGenericRow *generic_row = (GeditDocumentsGenericRow *)row;
+ gpointer *searched_item = (gpointer *)generic_row->ref;
+
+ return searched_item == item ? 0 : -1;
+}
+
+static GtkListBoxRow *
+get_row_from_widget (GeditDocumentsPanel *panel,
+ GtkWidget *widget)
+{
+ GList *children;
+ GList *item;
+ GtkListBoxRow *row;
+
+ children = gtk_container_get_children (GTK_CONTAINER (panel->listbox));
+
+ item = g_list_find_custom (children, widget, listbox_search_function);
+ row = item ? item->data : NULL;
+
+ g_list_free (children);
+
+ return row;
+}
+
+static void
+row_select (GeditDocumentsPanel *panel,
+ GtkListBox *listbox,
+ GtkListBoxRow *row)
+{
+ GtkListBoxRow *selected_row = gtk_list_box_get_selected_row (listbox);
+
+ if (row != selected_row)
+ {
+ g_signal_handler_block (listbox, panel->selection_changed_handler_id);
+ gtk_list_box_select_row (listbox, row);
+ g_signal_handler_unblock (listbox, panel->selection_changed_handler_id);
+ }
+
+ panel->current_selection = GTK_WIDGET (row);
+ make_row_visible (panel, GTK_WIDGET (row));
+}
+
+static void
+insert_row (GeditDocumentsPanel *panel,
+ GtkListBox *listbox,
+ GtkWidget *row,
+ gint position)
+{
+ g_signal_handler_block (listbox, panel->selection_changed_handler_id);
+ gtk_list_box_insert (listbox, row, position);
+ g_signal_handler_unblock (listbox, panel->selection_changed_handler_id);
+}
+
+static void
+select_active_tab (GeditDocumentsPanel *panel)
+{
+ GeditNotebook *notebook;
+ gboolean have_tabs;
+ GeditTab *tab;
+
+ notebook = gedit_multi_notebook_get_active_notebook (panel->mnb);
+ have_tabs = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)) > 0;
+ tab = gedit_multi_notebook_get_active_tab (panel->mnb);
+
+ if (notebook != NULL && tab != NULL && have_tabs)
+ {
+ GtkListBoxRow *row = get_row_from_widget (panel, GTK_WIDGET (tab));
+
+ if (row)
+ {
+ row_select (panel, GTK_LIST_BOX (panel->listbox), row);
+ }
+ }
+}
+
+static GtkListBoxRow *
+get_first_notebook_found (GeditDocumentsPanel *panel)
+{
+ GList *children;
+ GList *l;
+ GtkListBoxRow *row = NULL;
+
+ children = gtk_container_get_children (GTK_CONTAINER (panel->listbox));
+
+ for (l = children; l != NULL; l = g_list_next (l))
+ {
+ if (GEDIT_IS_DOCUMENTS_GROUP_ROW (l->data))
+ {
+ row = l->data;
+ break;
+ }
+ }
+
+ g_list_free (children);
+
+ return row;
+}
+
+static void
+multi_notebook_tab_switched (GeditMultiNotebook *mnb,
+ GeditNotebook *old_notebook,
+ GeditTab *old_tab,
+ GeditNotebook *new_notebook,
+ GeditTab *new_tab,
+ GeditDocumentsPanel *panel)
+{
+ gedit_debug (DEBUG_PANEL);
+
+ if (!_gedit_window_is_removing_tabs (panel->window) &&
+ panel->is_in_tab_switched == FALSE)
+ {
+ GtkListBoxRow *row;
+
+ panel->is_in_tab_switched = TRUE;
+
+ row = get_row_from_widget (panel, GTK_WIDGET (new_tab));
+
+ if (row)
+ {
+ row_select (panel, GTK_LIST_BOX (panel->listbox), row);
+ }
+
+ panel->is_in_tab_switched = FALSE;
+ }
+}
+
+static void
+group_row_set_notebook_name (GtkWidget *row)
+{
+ GeditNotebook *notebook;
+ GeditMultiNotebook *mnb;
+ guint num;
+ gchar *name;
+ GeditDocumentsGroupRow *group_row = GEDIT_DOCUMENTS_GROUP_ROW (row);
+
+ notebook = GEDIT_NOTEBOOK (group_row->ref);
+
+ mnb = group_row->panel->mnb;
+ num = gedit_multi_notebook_get_notebook_num (mnb, notebook);
+
+ name = g_strdup_printf (_("Tab Group %i"), num + 1);
+
+ gtk_label_set_text (GTK_LABEL (group_row->label), name);
+
+ g_free (name);
+}
+
+static void
+group_row_update_names (GeditDocumentsPanel *panel,
+ GtkWidget *listbox)
+{
+ GList *children;
+ GList *l;
+ GtkWidget *row;
+
+ children = gtk_container_get_children (GTK_CONTAINER (listbox));
+
+ for (l = children; l != NULL; l = g_list_next (l))
+ {
+ row = l->data;
+
+ if (GEDIT_IS_DOCUMENTS_GROUP_ROW (row))
+ {
+ group_row_set_notebook_name (row);
+ }
+ }
+
+ g_list_free (children);
+}
+
+static void
+group_row_refresh_visibility (GeditDocumentsPanel *panel)
+{
+ gboolean notebook_is_unique;
+ GtkWidget *first_group_row;
+
+ notebook_is_unique = gedit_multi_notebook_get_n_notebooks (panel->mnb) <= 1;
+ first_group_row = GTK_WIDGET (get_first_notebook_found (panel));
+
+ gtk_widget_set_no_show_all (first_group_row, notebook_is_unique);
+ gtk_widget_set_visible (first_group_row, !notebook_is_unique);
+}
+
+static gchar *
+doc_get_name (GeditDocument *doc)
+{
+ gchar *name;
+ gchar *docname;
+
+ name = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc)));
+
+ /* Truncate the name so it doesn't get insanely wide. */
+ docname = tepl_utils_str_middle_truncate (name, MAX_DOC_NAME_LENGTH);
+
+ g_free (name);
+
+ return docname;
+}
+
+static void
+document_row_sync_tab_name_and_icon (GeditTab *tab,
+ GParamSpec *pspec,
+ GtkWidget *row)
+{
+ GeditDocumentsDocumentRow *document_row = GEDIT_DOCUMENTS_DOCUMENT_ROW (row);
+ GeditDocument *doc;
+ GtkSourceFile *file;
+ gchar *name;
+ GdkPixbuf *pixbuf;
+
+ doc = gedit_tab_get_document (tab);
+ name = doc_get_name (doc);
+
+ if (!gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc)))
+ {
+ gtk_label_set_text (GTK_LABEL (document_row->label), name);
+ }
+ else
+ {
+ gchar *markup;
+
+ markup = g_markup_printf_escaped ("<b>%s</b>", name);
+ gtk_label_set_markup (GTK_LABEL (document_row->label), markup);
+
+ g_free (markup);
+ }
+
+ g_free (name);
+
+ file = gedit_document_get_file (doc);
+
+ /* The status has as separate label to prevent ellipsizing */
+ if (!gtk_source_file_is_readonly (file))
+ {
+ gtk_widget_hide (GTK_WIDGET (document_row->status_label));
+ }
+ else
+ {
+ gchar *status;
+
+ status = g_strdup_printf ("[%s]", _("Read-Only"));
+
+ gtk_label_set_text (GTK_LABEL (document_row->status_label), status);
+ gtk_widget_show (GTK_WIDGET (document_row->status_label));
+
+ g_free (status);
+ }
+
+ /* Update header of the row */
+ pixbuf = _gedit_tab_get_icon (tab);
+
+ if (pixbuf)
+ {
+ gtk_image_set_from_pixbuf (GTK_IMAGE (document_row->image), pixbuf);
+ }
+ else
+ {
+ gtk_image_clear (GTK_IMAGE (document_row->image));
+ }
+}
+
+static void
+refresh_notebook (GeditDocumentsPanel *panel,
+ GeditNotebook *notebook)
+{
+ GList *tabs;
+ GList *l;
+
+ tabs = gtk_container_get_children (GTK_CONTAINER (notebook));
+
+ for (l = tabs; l != NULL; l = g_list_next (l))
+ {
+ GtkWidget *row;
+
+ row = gedit_documents_document_row_new (panel, GEDIT_TAB (l->data));
+ insert_row (panel, GTK_LIST_BOX (panel->listbox), row, -1);
+ panel->nb_row_tab += 1;
+ }
+
+ g_list_free (tabs);
+}
+
+static void
+refresh_notebook_foreach (GeditNotebook *notebook,
+ GeditDocumentsPanel *panel)
+{
+ GtkWidget *row;
+
+ row = gedit_documents_group_row_new (panel, notebook);
+ insert_row (panel, GTK_LIST_BOX (panel->listbox), row, -1);
+ panel->nb_row_notebook += 1;
+
+ group_row_refresh_visibility (panel);
+ refresh_notebook (panel, notebook);
+}
+
+static void
+refresh_list (GeditDocumentsPanel *panel)
+{
+ GList *children;
+ GList *l;
+
+ /* Clear the listbox */
+ children = gtk_container_get_children (GTK_CONTAINER (panel->listbox));
+
+ for (l = children; l != NULL; l = g_list_next (l))
+ {
+ GeditDocumentsGenericRow *row = l->data;
+
+ if (GEDIT_IS_DOCUMENTS_DOCUMENT_ROW (row))
+ {
+ GeditTab *tab = GEDIT_TAB (row->ref);
+ g_signal_handlers_disconnect_matched (tab,
+ G_SIGNAL_MATCH_FUNC,
+ 0,
+ 0,
+ NULL,
+ G_CALLBACK (document_row_sync_tab_name_and_icon),
+ NULL);
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (row));
+ }
+
+ g_list_free (children);
+
+ gedit_multi_notebook_foreach_notebook (panel->mnb,
+ (GtkCallback)refresh_notebook_foreach,
+ panel);
+ select_active_tab (panel);
+}
+
+static void
+multi_notebook_tab_removed (GeditMultiNotebook *mnb,
+ GeditNotebook *notebook,
+ GeditTab *tab,
+ GeditDocumentsPanel *panel)
+{
+ GtkListBoxRow *row;
+
+ gedit_debug (DEBUG_PANEL);
+
+ row = get_row_from_widget (panel, GTK_WIDGET (tab));
+
+ /* Disconnect before destroy it so document_row_sync_tab_name_and_icon()
+ * don't get invalid data */
+ g_signal_handlers_disconnect_by_func (GEDIT_DOCUMENTS_DOCUMENT_ROW (row)->ref,
+ G_CALLBACK (document_row_sync_tab_name_and_icon),
+ row);
+
+ gtk_widget_destroy (GTK_WIDGET (row));
+ panel->nb_row_tab -= 1;
+}
+
+static gint
+get_dest_position_for_tab (GeditDocumentsPanel *panel,
+ GeditNotebook *notebook,
+ GeditTab *tab)
+{
+ gint page_num;
+ GList *children;
+ GList *item;
+ gint res = -1;
+
+ /* Get tab's position in notebook and notebook's position in GtkListBox
+ * then return future tab's position in GtkListBox */
+
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (notebook), GTK_WIDGET (tab));
+
+ children = gtk_container_get_children (GTK_CONTAINER (panel->listbox));
+ item = g_list_find_custom (children, notebook, listbox_search_function);
+
+ if (item)
+ {
+ res = 1 + page_num + g_list_position (children, item);
+ }
+
+ g_list_free (children);
+
+ return res;
+}
+
+static void
+multi_notebook_tab_added (GeditMultiNotebook *mnb,
+ GeditNotebook *notebook,
+ GeditTab *tab,
+ GeditDocumentsPanel *panel)
+{
+ gint position;
+ GtkWidget *row;
+
+ gedit_debug (DEBUG_PANEL);
+
+ position = get_dest_position_for_tab (panel, notebook, tab);
+
+ if (position == -1)
+ {
+ panel->nb_row_tab = 0;
+ panel->nb_row_notebook = 0;
+
+ refresh_list (panel);
+ }
+ else
+ {
+ /* Add a new tab's row to the listbox */
+ row = gedit_documents_document_row_new (panel, tab);
+ insert_row (panel, GTK_LIST_BOX (panel->listbox), row, position);
+
+ panel->nb_row_tab += 1;
+
+ if (tab == gedit_multi_notebook_get_active_tab (mnb))
+ {
+ row_select (panel, GTK_LIST_BOX (panel->listbox), GTK_LIST_BOX_ROW (row));
+ }
+ }
+}
+
+static void
+multi_notebook_notebook_removed (GeditMultiNotebook *mnb,
+ GeditNotebook *notebook,
+ GeditDocumentsPanel *panel)
+{
+ GtkListBoxRow *row;
+
+ gedit_debug (DEBUG_PANEL);
+
+ row = get_row_from_widget (panel, GTK_WIDGET (notebook));
+ gtk_container_remove (GTK_CONTAINER (panel->listbox), GTK_WIDGET (row));
+
+ panel->nb_row_notebook -= 1;
+
+ group_row_refresh_visibility (panel);
+ group_row_update_names (panel, panel->listbox);
+}
+
+static void
+row_move (GeditDocumentsPanel *panel,
+ GeditNotebook *notebook,
+ GtkWidget *tab,
+ GtkWidget *row)
+{
+ gint position;
+
+ g_object_ref (row);
+
+ gtk_container_remove (GTK_CONTAINER (panel->listbox), GTK_WIDGET (row));
+ position = get_dest_position_for_tab (panel, notebook, GEDIT_TAB (tab));
+
+ g_signal_handler_block (panel->listbox, panel->selection_changed_handler_id);
+
+ gtk_list_box_insert (GTK_LIST_BOX (panel->listbox), row, position);
+ g_object_unref (row);
+
+ g_signal_handler_unblock (GTK_LIST_BOX (panel->listbox), panel->selection_changed_handler_id);
+}
+
+static void
+multi_notebook_tabs_reordered (GeditMultiNotebook *mnb,
+ GeditNotebook *notebook,
+ GtkWidget *page,
+ gint page_num,
+ GeditDocumentsPanel *panel)
+{
+ GtkListBoxRow *row;
+
+ gedit_debug (DEBUG_PANEL);
+
+ row = get_row_from_widget (panel, GTK_WIDGET (page));
+
+ row_move (panel, notebook, page, GTK_WIDGET (row));
+
+ row_select (panel, GTK_LIST_BOX (panel->listbox), GTK_LIST_BOX_ROW (row));
+}
+
+static void
+set_window (GeditDocumentsPanel *panel,
+ GeditWindow *window)
+{
+ panel->window = g_object_ref (window);
+ panel->mnb = GEDIT_MULTI_NOTEBOOK (_gedit_window_get_multi_notebook (window));
+
+ g_signal_connect (panel->mnb,
+ "notebook-removed",
+ G_CALLBACK (multi_notebook_notebook_removed),
+ panel);
+ g_signal_connect (panel->mnb,
+ "tab-added",
+ G_CALLBACK (multi_notebook_tab_added),
+ panel);
+ g_signal_connect (panel->mnb,
+ "tab-removed",
+ G_CALLBACK (multi_notebook_tab_removed),
+ panel);
+ g_signal_connect (panel->mnb,
+ "page-reordered",
+ G_CALLBACK (multi_notebook_tabs_reordered),
+ panel);
+
+ panel->tab_switched_handler_id = g_signal_connect (panel->mnb,
+ "switch-tab",
+ G_CALLBACK (multi_notebook_tab_switched),
+ panel);
+
+ panel->first_selection = TRUE;
+
+ refresh_list (panel);
+ group_row_refresh_visibility (panel);
+}
+
+static void
+listbox_selection_changed (GtkListBox *listbox,
+ GtkListBoxRow *row,
+ GeditDocumentsPanel *panel)
+{
+ if (row == NULL)
+ {
+ /* No selection on document panel */
+ return;
+ }
+
+ /* When the window is shown, the first notebook row is selected
+ * and therefore also shown - we don't want this */
+
+ if (panel->first_selection)
+ {
+ panel->first_selection = FALSE;
+ group_row_refresh_visibility (panel);
+ }
+
+ g_signal_handler_block (panel->mnb, panel->tab_switched_handler_id);
+
+ if (GEDIT_IS_DOCUMENTS_DOCUMENT_ROW (row))
+ {
+ gedit_multi_notebook_set_active_tab (panel->mnb,
+ GEDIT_TAB (GEDIT_DOCUMENTS_DOCUMENT_ROW (row)->ref));
+
+ panel->current_selection = GTK_WIDGET (row);
+ }
+ else if (GEDIT_IS_DOCUMENTS_GROUP_ROW (row) && panel->current_selection)
+ {
+ row_select (panel,
+ GTK_LIST_BOX (panel->listbox),
+ GTK_LIST_BOX_ROW (panel->current_selection));
+
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ g_signal_handler_unblock (panel->mnb, panel->tab_switched_handler_id);
+}
+
+static void
+gedit_documents_panel_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditDocumentsPanel *panel = GEDIT_DOCUMENTS_PANEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ set_window (panel, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_documents_panel_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditDocumentsPanel *panel = GEDIT_DOCUMENTS_PANEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ g_value_set_object (value, panel->window);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_documents_panel_finalize (GObject *object)
+{
+ GeditDocumentsPanel *panel = GEDIT_DOCUMENTS_PANEL (object);
+
+ g_signal_handlers_disconnect_by_func (panel->mnb,
+ G_CALLBACK (multi_notebook_notebook_removed),
+ panel);
+ g_signal_handlers_disconnect_by_func (panel->mnb,
+ G_CALLBACK (multi_notebook_tab_added),
+ panel);
+ g_signal_handlers_disconnect_by_func (panel->mnb,
+ G_CALLBACK (multi_notebook_tab_removed),
+ panel);
+ g_signal_handlers_disconnect_by_func (panel->mnb,
+ G_CALLBACK (multi_notebook_tabs_reordered),
+ panel);
+ g_signal_handlers_disconnect_by_func (panel->mnb,
+ G_CALLBACK (multi_notebook_tab_switched),
+ panel);
+
+ G_OBJECT_CLASS (gedit_documents_panel_parent_class)->finalize (object);
+}
+
+static void
+gedit_documents_panel_dispose (GObject *object)
+{
+ GeditDocumentsPanel *panel = GEDIT_DOCUMENTS_PANEL (object);
+
+ g_clear_object (&panel->window);
+
+ if (panel->source_targets)
+ {
+ gtk_target_list_unref (panel->source_targets);
+ panel->source_targets = NULL;
+ }
+
+ G_OBJECT_CLASS (gedit_documents_panel_parent_class)->dispose (object);
+}
+
+static GtkWidget *
+create_placeholder_row (gint height)
+{
+ GtkStyleContext *context;
+
+ GtkWidget *placeholder_row = gtk_list_box_row_new ();
+
+ context = gtk_widget_get_style_context (placeholder_row);
+ gtk_style_context_add_class (context, "gedit-document-panel-placeholder-row");
+
+ gtk_widget_set_size_request (placeholder_row, -1, height);
+
+ return placeholder_row;
+}
+
+static void
+panel_on_drag_begin (GtkWidget *widget,
+ GdkDragContext *context)
+{
+ GeditDocumentsPanel *panel = GEDIT_DOCUMENTS_PANEL (widget);
+ GtkWidget *drag_document_row;
+ GtkAllocation allocation;
+ const gchar *name;
+ GtkWidget *label;
+ gint width, height;
+ GtkWidget *image_box;
+ GtkWidget *box;
+ GtkStyleContext *style_context;
+
+ drag_document_row = panel->drag_document_row;
+ gtk_widget_get_allocation (drag_document_row, &allocation);
+ gtk_widget_hide (drag_document_row);
+
+ panel->document_row_height = allocation.height;
+
+ name = gtk_label_get_label (GTK_LABEL (GEDIT_DOCUMENTS_DOCUMENT_ROW (drag_document_row)->label));
+
+ label = gtk_label_new (NULL);
+ gtk_label_set_markup (GTK_LABEL (label), name);
+ gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
+ gtk_widget_set_halign (label, GTK_ALIGN_START);
+ gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
+
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height);
+ image_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_set_size_request (image_box, width, height);
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
+
+ gtk_box_pack_start (GTK_BOX (box), image_box, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
+
+ panel->dnd_window = gtk_window_new (GTK_WINDOW_POPUP);
+ gtk_widget_set_size_request (panel->dnd_window, allocation.width, allocation.height);
+ gtk_window_set_screen (GTK_WINDOW (panel->dnd_window),
+ gtk_widget_get_screen (drag_document_row));
+
+ style_context = gtk_widget_get_style_context (panel->dnd_window);
+ gtk_style_context_add_class (style_context, "gedit-document-panel-dragged-row");
+
+ gtk_container_add (GTK_CONTAINER (panel->dnd_window), box);
+ gtk_widget_show_all (panel->dnd_window);
+ gtk_widget_set_opacity (panel->dnd_window, 0.8);
+
+ gtk_drag_set_icon_widget (context,
+ panel->dnd_window,
+ panel->drag_document_row_x,
+ panel->drag_document_row_y);
+}
+
+static gboolean
+panel_on_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ GeditDocumentsPanel *panel = GEDIT_DOCUMENTS_PANEL (widget);
+ GeditDocumentsGenericRow *generic_row;
+ GtkWidget *source_panel;
+ gint dest_x, dest_y;
+ gint row_placeholder_index;
+ gint row_index;
+
+ GdkAtom target = gtk_drag_dest_find_target (widget, context, NULL);
+
+ if (target != gdk_atom_intern_static_string ("GEDIT_DOCUMENTS_DOCUMENT_ROW"))
+ {
+ gdk_drag_status (context, 0, time);
+ return FALSE;
+ }
+
+ gtk_widget_translate_coordinates (widget, panel->listbox,
+ x, y,
+ &dest_x, &dest_y);
+
+ generic_row = (GeditDocumentsGenericRow *)gtk_list_box_get_row_at_y (GTK_LIST_BOX (panel->listbox), dest_y);
+ source_panel = gtk_drag_get_source_widget (context);
+
+ if (!panel->row_placeholder)
+ {
+ if (!generic_row)
+ {
+ /* We don't have a row height to use, so use the source one */
+ panel->document_row_height = GEDIT_DOCUMENTS_PANEL (source_panel)->document_row_height;
+ }
+ else
+ {
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (GTK_WIDGET (generic_row), &allocation);
+ panel->document_row_height = allocation.height;
+ }
+
+ panel->row_placeholder = create_placeholder_row (panel->document_row_height);
+ gtk_widget_show (panel->row_placeholder);
+ g_object_ref_sink (panel->row_placeholder);
+ }
+ else if (GTK_WIDGET (generic_row) == panel->row_placeholder)
+ {
+ /* cursor on placeholder */
+ gdk_drag_status (context, GDK_ACTION_MOVE, time);
+
+ return TRUE;
+ }
+
+ if (!generic_row)
+ {
+ /* cursor on empty space => put the placeholder at end of list */
+ GList *children = gtk_container_get_children (GTK_CONTAINER (panel->listbox));
+
+ row_placeholder_index = g_list_length (children);
+ g_list_free (children);
+ }
+ else
+ {
+ row_index = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (generic_row));
+
+ gtk_widget_translate_coordinates (widget, GTK_WIDGET (generic_row),
+ x, y,
+ &dest_x, &dest_y);
+
+ if (dest_y <= panel->document_row_height / 2 && row_index > 0)
+ {
+ row_placeholder_index = row_index;
+ }
+ else
+ {
+ row_placeholder_index = row_index + 1;
+ }
+ }
+
+ if (source_panel == widget)
+ {
+ /* Adjustment because of hidden source row */
+ gint source_row_index = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (panel->drag_document_row));
+ panel->row_source_row_offset = source_row_index < row_placeholder_index ? -1 : 0;
+ }
+
+ if (panel->row_placeholder_index != row_placeholder_index)
+ {
+ if (panel->row_placeholder_index != ROW_OUTSIDE_LISTBOX)
+ {
+ gtk_container_remove (GTK_CONTAINER (panel->listbox),
+ panel->row_placeholder);
+
+ if (panel->row_placeholder_index < row_placeholder_index)
+ {
+ /* Adjustment because of existing placeholder row */
+ row_placeholder_index -= 1;
+ }
+ }
+
+ panel->row_destination_index = panel->row_placeholder_index = row_placeholder_index;
+
+ gtk_list_box_insert (GTK_LIST_BOX (panel->listbox),
+ panel->row_placeholder,
+ panel->row_placeholder_index);
+ }
+
+ gdk_drag_status (context, GDK_ACTION_MOVE, time);
+
+ return TRUE;
+}
+
+static void
+panel_on_drag_leave (GtkWidget *widget,
+ GdkDragContext *context,
+ guint time)
+{
+ GeditDocumentsPanel *panel = GEDIT_DOCUMENTS_PANEL (widget);
+
+ if (panel->row_placeholder_index != ROW_OUTSIDE_LISTBOX)
+ {
+ gtk_container_remove (GTK_CONTAINER (panel->listbox), panel->row_placeholder);
+ panel->row_placeholder_index = ROW_OUTSIDE_LISTBOX;
+ }
+}
+
+static gboolean
+panel_on_drag_drop (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ GeditDocumentsPanel *panel = GEDIT_DOCUMENTS_PANEL (widget);
+
+ GdkAtom target = gtk_drag_dest_find_target (widget, context, NULL);
+ GtkWidget *source_widget = gtk_drag_get_source_widget (context);
+
+ if (GEDIT_IS_DOCUMENTS_PANEL (source_widget))
+ {
+ gtk_widget_show (GEDIT_DOCUMENTS_PANEL (source_widget)->drag_document_row);
+ }
+
+ if (target == gdk_atom_intern_static_string ("GEDIT_DOCUMENTS_DOCUMENT_ROW"))
+ {
+ gtk_drag_get_data (widget, context, target, time);
+ return TRUE;
+ }
+
+ panel->row_placeholder_index = ROW_OUTSIDE_LISTBOX;
+ return FALSE;
+}
+
+static void
+panel_on_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *data,
+ guint info,
+ guint time)
+{
+ GeditDocumentsPanel *panel = GEDIT_DOCUMENTS_PANEL (widget);
+ GdkAtom target = gtk_selection_data_get_target (data);
+ GdkAtom result;
+
+ if (target == gdk_atom_intern_static_string ("GEDIT_DOCUMENTS_DOCUMENT_ROW"))
+ {
+ gtk_selection_data_set (data,
+ target,
+ 8,
+ (void*)&panel->drag_document_row,
+ sizeof (gpointer));
+ return;
+ }
+
+ result = gtk_drag_dest_find_target (widget, context, panel->source_targets);
+
+ if (result != GDK_NONE)
+ {
+ GeditTab *tab;
+ GeditDocument *doc;
+ gchar *full_name;
+
+ tab = GEDIT_TAB (GEDIT_DOCUMENTS_DOCUMENT_ROW (panel->drag_document_row)->ref);
+ doc = gedit_tab_get_document (tab);
+
+ if (!_gedit_document_is_untitled (doc))
+ {
+ GtkSourceFile *file = gedit_document_get_file (doc);
+ GFile *location = gtk_source_file_get_location (file);
+ full_name = g_file_get_parse_name (location);
+
+ gtk_selection_data_set (data,
+ target,
+ 8,
+ (guchar *)full_name,
+ strlen (full_name));
+ g_free (full_name);
+ }
+ }
+
+ gtk_widget_show (panel->drag_document_row);
+}
+
+static GeditNotebook *
+get_notebook_and_position_from_document_row (GeditDocumentsPanel *panel,
+ gint row_index,
+ gint *position)
+{
+ GList *l;
+ gint index = 0;
+ GeditDocumentsGroupRow *row;
+
+ GList *children = gtk_container_get_children (GTK_CONTAINER (panel->listbox));
+ gint nb_elements = g_list_length (children);
+
+ if (nb_elements == 1)
+ {
+ row = children->data;
+ }
+ else
+ {
+ l = g_list_nth (children, row_index - 1);
+
+ while (TRUE)
+ {
+ row = l->data;
+
+ if (GEDIT_IS_DOCUMENTS_GROUP_ROW (row))
+ {
+ break;
+ }
+
+ l = g_list_previous (l);
+ index += 1;
+ }
+ }
+
+ g_list_free (children);
+
+ *position = index;
+ return GEDIT_NOTEBOOK (row->ref);
+}
+
+static void
+panel_on_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *data,
+ guint info,
+ guint time)
+{
+ GeditDocumentsPanel *panel = GEDIT_DOCUMENTS_PANEL (widget);
+ GeditDocumentsPanel *source_panel = NULL;
+
+ GtkWidget *source_widget = gtk_drag_get_source_widget (context);
+
+ if (GEDIT_IS_DOCUMENTS_PANEL (source_widget))
+ {
+ source_panel = GEDIT_DOCUMENTS_PANEL (source_widget);
+ }
+
+ GtkWidget **source_row = (void*) gtk_selection_data_get_data (data);
+
+ if (source_panel &&
+ gtk_selection_data_get_target (data) == gdk_atom_intern_static_string ("GEDIT_DOCUMENTS_DOCUMENT_ROW"))
+ {
+ gint source_index = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (*source_row));
+
+ /* And finally, we can move the row */
+ if (source_panel != panel ||
+ (panel->row_destination_index != source_index &&
+ panel->row_destination_index != source_index + 1))
+ {
+ GeditNotebook *old_notebook, *new_notebook;
+ gint position;
+
+ GeditTab *tab = GEDIT_TAB (GEDIT_DOCUMENTS_DOCUMENT_ROW (*source_row)->ref);
+
+ old_notebook = gedit_multi_notebook_get_notebook_for_tab (source_panel->mnb, tab);
+ new_notebook = get_notebook_and_position_from_document_row (panel,
+ panel->row_destination_index,
+ &position);
+ if (old_notebook == new_notebook)
+ {
+ gtk_widget_show (*source_row);
+
+ gtk_notebook_reorder_child (GTK_NOTEBOOK (new_notebook),
+ GTK_WIDGET (tab),
+ position + panel->row_source_row_offset);
+ }
+ else
+ {
+ gedit_notebook_move_tab (old_notebook, new_notebook, tab, position);
+ }
+
+ if (tab != gedit_multi_notebook_get_active_tab (panel->mnb))
+ {
+ g_signal_handler_block (panel->mnb, panel->tab_switched_handler_id);
+ gedit_multi_notebook_set_active_tab (panel->mnb, tab);
+ g_signal_handler_unblock (panel->mnb, panel->tab_switched_handler_id);
+ }
+ }
+
+ gtk_drag_finish (context, TRUE, FALSE, time);
+ }
+ else
+ {
+ gtk_drag_finish (context, FALSE, FALSE, time);
+ }
+
+ panel->row_destination_index = panel->row_placeholder_index = ROW_OUTSIDE_LISTBOX;
+
+ if (panel->row_placeholder)
+ {
+ gtk_widget_destroy (panel->row_placeholder);
+ panel->row_placeholder = NULL;
+ }
+}
+
+static void
+panel_on_drag_end (GtkWidget *widget,
+ GdkDragContext *context)
+{
+ GeditDocumentsPanel *panel = GEDIT_DOCUMENTS_PANEL (widget);
+
+ panel->drag_document_row = NULL;
+ panel->is_on_drag = FALSE;
+
+ gtk_widget_destroy (panel->dnd_window);
+ panel->dnd_window = NULL;
+}
+
+static gboolean
+panel_on_drag_failed (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkDragResult result)
+{
+ GtkWidget *source_widget = gtk_drag_get_source_widget (context);
+
+ if (GEDIT_IS_DOCUMENTS_PANEL (source_widget))
+ {
+ gtk_widget_show (GEDIT_DOCUMENTS_PANEL (source_widget)->drag_document_row);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+panel_on_motion_notify (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ GeditDocumentsPanel *panel = GEDIT_DOCUMENTS_PANEL (widget);
+
+ if (panel->drag_document_row == NULL || panel->is_on_drag)
+ {
+ return FALSE;
+ }
+
+ if (!(event->state & GDK_BUTTON1_MASK))
+ {
+ panel->drag_document_row = NULL;
+
+ return FALSE;
+ }
+
+ if (gtk_drag_check_threshold (widget,
+ panel->drag_root_x, panel->drag_root_y,
+ event->x_root, event->y_root))
+ {
+ panel->is_on_drag = TRUE;
+
+ gtk_drag_begin_with_coordinates (widget, panel->source_targets, GDK_ACTION_MOVE,
+ GDK_BUTTON_PRIMARY, (GdkEvent*)event,
+ -1, -1);
+ }
+
+ return FALSE;
+}
+
+static void
+gedit_documents_panel_class_init (GeditDocumentsPanelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = gedit_documents_panel_finalize;
+ object_class->dispose = gedit_documents_panel_dispose;
+ object_class->get_property = gedit_documents_panel_get_property;
+ object_class->set_property = gedit_documents_panel_set_property;
+
+ widget_class->motion_notify_event = panel_on_motion_notify;
+
+ widget_class->drag_begin = panel_on_drag_begin;
+ widget_class->drag_end = panel_on_drag_end;
+ widget_class->drag_failed = panel_on_drag_failed;
+ widget_class->drag_motion = panel_on_drag_motion;
+ widget_class->drag_leave = panel_on_drag_leave;
+ widget_class->drag_drop = panel_on_drag_drop;
+ widget_class->drag_data_get = panel_on_drag_data_get;
+ widget_class->drag_data_received = panel_on_drag_data_received;
+
+ properties[PROP_WINDOW] =
+ g_param_spec_object ("window",
+ "Window",
+ "The GeditWindow this GeditDocumentsPanel is associated with",
+ GEDIT_TYPE_WINDOW,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+gedit_documents_panel_init (GeditDocumentsPanel *panel)
+{
+ GtkWidget *sw;
+ GtkStyleContext *context;
+
+ gedit_debug (DEBUG_PANEL);
+
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (panel),
+ GTK_ORIENTATION_VERTICAL);
+
+ /* Create the scrolled window */
+ sw = gtk_scrolled_window_new (NULL, NULL);
+
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_show (sw);
+ gtk_box_pack_start (GTK_BOX (panel), sw, TRUE, TRUE, 0);
+
+ /* Create the listbox */
+ panel->listbox = gtk_list_box_new ();
+
+ gtk_container_add (GTK_CONTAINER (sw), panel->listbox);
+
+ panel->adjustment = gtk_list_box_get_adjustment (GTK_LIST_BOX (panel->listbox));
+
+ /* Disable focus so it doesn't steal focus each time from the view */
+ gtk_widget_set_can_focus (panel->listbox, FALSE);
+
+ /* Css style */
+ context = gtk_widget_get_style_context (panel->listbox);
+ gtk_style_context_add_class (context, "gedit-document-panel");
+
+ panel->selection_changed_handler_id = g_signal_connect (panel->listbox,
+ "row-selected",
+ G_CALLBACK (listbox_selection_changed),
+ panel);
+ panel->is_in_tab_switched = FALSE;
+ panel->current_selection = NULL;
+ panel->nb_row_notebook = 0;
+ panel->nb_row_tab = 0;
+
+ /* Drag and drop support */
+ panel->source_targets = gtk_target_list_new (panel_targets, G_N_ELEMENTS (panel_targets));
+ gtk_target_list_add_text_targets (panel->source_targets, 0);
+
+ gtk_drag_dest_set (GTK_WIDGET (panel), 0,
+ panel_targets, G_N_ELEMENTS (panel_targets),
+ GDK_ACTION_MOVE);
+
+ gtk_drag_dest_set_track_motion (GTK_WIDGET (panel), TRUE);
+
+ panel->drag_document_row = NULL;
+ panel->row_placeholder = NULL;
+ panel->row_placeholder_index = ROW_OUTSIDE_LISTBOX;
+ panel->row_destination_index = ROW_OUTSIDE_LISTBOX;
+ panel->row_source_row_offset = 0;
+ panel->is_on_drag = FALSE;
+}
+
+GtkWidget *
+gedit_documents_panel_new (GeditWindow *window)
+{
+ g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL);
+
+ return g_object_new (GEDIT_TYPE_DOCUMENTS_PANEL,
+ "window", window,
+ NULL);
+}
+
+static void
+row_on_close_button_clicked (GtkWidget *close_button,
+ GtkWidget *row)
+{
+ GeditDocumentsGenericRow *generic_row = (GeditDocumentsGenericRow *)row;
+ GeditWindow *window = generic_row->panel->window;
+ GtkWidget *ref;
+
+ if (GEDIT_IS_DOCUMENTS_GROUP_ROW (row))
+ {
+ ref = GEDIT_DOCUMENTS_GROUP_ROW (row)->ref;
+ _gedit_cmd_file_close_notebook (window, GEDIT_NOTEBOOK (ref));
+ }
+ else if (GEDIT_IS_DOCUMENTS_DOCUMENT_ROW (row))
+ {
+ ref = GEDIT_DOCUMENTS_DOCUMENT_ROW (row)->ref;
+ _gedit_cmd_file_close_tab (GEDIT_TAB (ref), window);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+}
+
+static gboolean
+row_on_button_pressed (GtkWidget *row_event_box,
+ GdkEventButton *event,
+ GtkWidget *row)
+{
+ if (gdk_event_get_event_type ((GdkEvent *)event) == GDK_BUTTON_PRESS &&
+ GEDIT_IS_DOCUMENTS_DOCUMENT_ROW (row))
+ {
+ GeditDocumentsDocumentRow *document_row = GEDIT_DOCUMENTS_DOCUMENT_ROW (row);
+ GeditDocumentsPanel *panel = document_row->panel;
+
+ if (event->button == GDK_BUTTON_PRIMARY)
+ {
+ /* memorize row and clicked position for possible drag'n drop */
+ panel->drag_document_row = row;
+ panel->drag_document_row_x = (gint)event->x;
+ panel->drag_document_row_y = (gint)event->y;
+
+ panel->drag_root_x = event->x_root;
+ panel->drag_root_y = event->y_root;
+
+ return FALSE;
+ }
+
+ panel->drag_document_row = NULL;
+
+ if (gdk_event_triggers_context_menu ((GdkEvent *)event))
+ {
+ GeditWindow *window = panel->window;
+ GeditTab *tab = GEDIT_TAB (document_row->ref);
+ GtkWidget *menu = gedit_notebook_popup_menu_new (window, tab);
+
+ g_signal_connect (menu,
+ "selection-done",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ gtk_menu_popup_at_pointer (GTK_MENU (menu), (GdkEvent *)event);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+document_row_create_header (GtkWidget *row)
+{
+ GeditDocumentsDocumentRow *document_row = GEDIT_DOCUMENTS_DOCUMENT_ROW (row);
+ GtkWidget *image_box;
+ gint width, height;
+
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height);
+
+ image_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_set_size_request (image_box, width, height);
+
+ document_row->image = gtk_image_new ();
+
+ gtk_container_add (GTK_CONTAINER (image_box), document_row->image);
+
+ gtk_box_pack_start (GTK_BOX (document_row->box),
+ image_box, FALSE, FALSE, 0);
+
+ /* Set the header on front of all other widget in the row */
+ gtk_box_reorder_child (GTK_BOX (document_row->box),
+ image_box, 0);
+
+ gtk_widget_show_all (image_box);
+}
+
+static GtkWidget *
+row_create (GtkWidget *row)
+{
+ GeditDocumentsGenericRow *generic_row = (GeditDocumentsGenericRow *)row;
+ GtkWidget *event_box;
+ GtkStyleContext *context;
+ GtkWidget *image;
+ GIcon *icon;
+
+ gedit_debug (DEBUG_PANEL);
+
+ event_box = gtk_event_box_new ();
+ generic_row->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
+
+ gtk_container_add (GTK_CONTAINER (event_box), generic_row->box);
+
+ generic_row->label = gtk_label_new (NULL);
+ gtk_label_set_ellipsize (GTK_LABEL (generic_row->label), PANGO_ELLIPSIZE_END);
+ gtk_widget_set_halign (generic_row->label, GTK_ALIGN_START);
+ gtk_widget_set_valign (generic_row->label, GTK_ALIGN_CENTER);
+
+ generic_row->status_label = gtk_label_new (NULL);
+ gtk_widget_set_halign (generic_row->status_label, GTK_ALIGN_END);
+ gtk_widget_set_valign (generic_row->status_label, GTK_ALIGN_CENTER);
+
+ generic_row->close_button = GTK_WIDGET (g_object_new (GTK_TYPE_BUTTON,
+ "relief", GTK_RELIEF_NONE,
+ "focus-on-click", FALSE,
+ NULL));
+
+ context = gtk_widget_get_style_context (generic_row->close_button);
+ gtk_style_context_add_class (context, "flat");
+ gtk_style_context_add_class (context, "small-button");
+
+ icon = g_themed_icon_new_with_default_fallbacks ("window-close-symbolic");
+ image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image);
+ g_object_unref (icon);
+
+ gtk_container_add (GTK_CONTAINER (generic_row->close_button), image);
+
+ gtk_box_pack_start (GTK_BOX (generic_row->box),
+ generic_row->label, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (generic_row->box),
+ generic_row->status_label, FALSE, FALSE, 0);
+
+ gtk_box_pack_end (GTK_BOX (generic_row->box),
+ generic_row->close_button, FALSE, FALSE, 0);
+
+ g_signal_connect (event_box,
+ "button-press-event",
+ G_CALLBACK (row_on_button_pressed),
+ row);
+ g_signal_connect (generic_row->close_button,
+ "clicked",
+ G_CALLBACK (row_on_close_button_clicked),
+ row);
+
+ gtk_widget_set_no_show_all (generic_row->status_label, TRUE);
+ gtk_widget_show_all (event_box);
+
+ return event_box;
+}
+
+static gboolean
+document_row_query_tooltip (GtkWidget *row,
+ gint x,
+ gint y,
+ gboolean keyboard_tip,
+ GtkTooltip *tooltip)
+{
+ GeditDocumentsGenericRow *generic_row = (GeditDocumentsGenericRow *)row;
+ gchar *markup;
+
+ if (!GEDIT_IS_DOCUMENTS_DOCUMENT_ROW (row))
+ {
+ return FALSE;
+ }
+
+ markup = _gedit_tab_get_tooltip (GEDIT_TAB (generic_row->ref));
+ gtk_tooltip_set_markup (tooltip, markup);
+
+ g_free (markup);
+
+ return TRUE;
+}
+
+/* Gedit Document Row */
+static void
+gedit_documents_document_row_class_init (GeditDocumentsDocumentRowClass *klass)
+{
+}
+
+static void
+gedit_documents_document_row_init (GeditDocumentsDocumentRow *row)
+{
+ GtkWidget *row_widget;
+ GtkStyleContext *context;
+
+ gedit_debug (DEBUG_PANEL);
+
+ row_widget = row_create (GTK_WIDGET (row));
+ gtk_container_add (GTK_CONTAINER (row), row_widget);
+
+ document_row_create_header (GTK_WIDGET (row));
+
+ gtk_widget_set_has_tooltip (GTK_WIDGET (row), TRUE);
+
+ /* Css style */
+ context = gtk_widget_get_style_context (GTK_WIDGET (row));
+ gtk_style_context_add_class (context, "gedit-document-panel-document-row");
+
+ gtk_widget_show_all (GTK_WIDGET (row));
+
+ gtk_widget_set_can_focus (GTK_WIDGET (row), FALSE);
+}
+
+/* Gedit Group Row */
+static void
+gedit_documents_group_row_class_init (GeditDocumentsGroupRowClass *klass)
+{
+}
+
+static void
+gedit_documents_group_row_init (GeditDocumentsGroupRow *row)
+{
+ GtkWidget *row_widget;
+ GtkStyleContext *context;
+
+ gedit_debug (DEBUG_PANEL);
+
+ row_widget = row_create (GTK_WIDGET (row));
+ gtk_container_add (GTK_CONTAINER (row), row_widget);
+
+ /* Css style */
+ context = gtk_widget_get_style_context (GTK_WIDGET (row));
+ gtk_style_context_add_class (context, "gedit-document-panel-group-row");
+
+ gtk_widget_show_all (GTK_WIDGET (row));
+
+ gtk_widget_set_can_focus (GTK_WIDGET (row), FALSE);
+}
+
+static GtkWidget *
+gedit_documents_document_row_new (GeditDocumentsPanel *panel,
+ GeditTab *tab)
+{
+ GeditDocumentsDocumentRow *row;
+
+ g_return_val_if_fail (GEDIT_IS_DOCUMENTS_PANEL (panel), NULL);
+ g_return_val_if_fail (GEDIT_IS_TAB (tab), NULL);
+
+ gedit_debug (DEBUG_PANEL);
+
+ row = g_object_new (GEDIT_TYPE_DOCUMENTS_DOCUMENT_ROW, NULL);
+ row->ref = GTK_WIDGET (tab);
+ row->panel = panel;
+
+ g_signal_connect (row->ref,
+ "notify::name",
+ G_CALLBACK (document_row_sync_tab_name_and_icon),
+ row);
+ g_signal_connect (row->ref,
+ "notify::state",
+ G_CALLBACK (document_row_sync_tab_name_and_icon),
+ row);
+ g_signal_connect (row,
+ "query-tooltip",
+ G_CALLBACK (document_row_query_tooltip),
+ NULL);
+
+ document_row_sync_tab_name_and_icon (GEDIT_TAB (row->ref), NULL, GTK_WIDGET (row));
+
+ return GTK_WIDGET (row);
+}
+
+static GtkWidget *
+gedit_documents_group_row_new (GeditDocumentsPanel *panel,
+ GeditNotebook *notebook)
+{
+ GeditDocumentsGroupRow *row;
+
+ g_return_val_if_fail (GEDIT_IS_DOCUMENTS_PANEL (panel), NULL);
+ g_return_val_if_fail (GEDIT_IS_NOTEBOOK (notebook), NULL);
+
+ gedit_debug (DEBUG_PANEL);
+
+ row = g_object_new (GEDIT_TYPE_DOCUMENTS_GROUP_ROW, NULL);
+ row->ref = GTK_WIDGET (notebook);
+ row->panel = panel;
+
+ group_row_set_notebook_name (GTK_WIDGET (row));
+
+ return GTK_WIDGET (row);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-documents-panel.h b/gedit/gedit-documents-panel.h
new file mode 100644
index 0000000..08f0e63
--- /dev/null
+++ b/gedit/gedit-documents-panel.h
@@ -0,0 +1,40 @@
+/*
+ * gedit-documents-panel.h
+ * 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/>.
+ */
+
+#ifndef GEDIT_DOCUMENTS_PANEL_H
+#define GEDIT_DOCUMENTS_PANEL_H
+
+#include <gtk/gtk.h>
+
+#include <gedit/gedit-window.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_DOCUMENTS_PANEL (gedit_documents_panel_get_type())
+
+G_DECLARE_FINAL_TYPE (GeditDocumentsPanel, gedit_documents_panel, GEDIT, DOCUMENTS_PANEL, GtkBox)
+
+GtkWidget *gedit_documents_panel_new (GeditWindow *window);
+
+G_END_DECLS
+
+#endif /* GEDIT_DOCUMENTS_PANEL_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-encoding-items.c b/gedit/gedit-encoding-items.c
new file mode 100644
index 0000000..93bd488
--- /dev/null
+++ b/gedit/gedit-encoding-items.c
@@ -0,0 +1,108 @@
+/*
+ * gedit-encoding-items.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * 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 "gedit-encoding-items.h"
+
+#include <glib/gi18n.h>
+
+#include "gedit-settings.h"
+
+struct _GeditEncodingItem
+{
+ const GtkSourceEncoding *encoding;
+ gchar *name;
+};
+
+static GeditEncodingItem *
+gedit_encoding_item_new (const GtkSourceEncoding *encoding,
+ gchar *name)
+{
+ GeditEncodingItem *item = g_slice_new (GeditEncodingItem);
+
+ item->encoding = encoding;
+ item->name = name;
+
+ return item;
+}
+
+void
+gedit_encoding_item_free (GeditEncodingItem *item)
+{
+ if (item == NULL)
+ {
+ return;
+ }
+
+ g_free (item->name);
+ g_slice_free (GeditEncodingItem, item);
+}
+
+const GtkSourceEncoding *
+gedit_encoding_item_get_encoding (GeditEncodingItem *item)
+{
+ g_return_val_if_fail (item != NULL, NULL);
+
+ return item->encoding;
+}
+
+const gchar *
+gedit_encoding_item_get_name (GeditEncodingItem *item)
+{
+ g_return_val_if_fail (item != NULL, NULL);
+
+ return item->name;
+}
+
+GSList *
+gedit_encoding_items_get (void)
+{
+ const GtkSourceEncoding *current_encoding;
+ GSList *encodings;
+ GSList *items = NULL;
+ GSList *l;
+
+ encodings = gedit_settings_get_candidate_encodings (NULL);
+
+ current_encoding = gtk_source_encoding_get_current ();
+
+ for (l = encodings; l != NULL; l = l->next)
+ {
+ const GtkSourceEncoding *enc = l->data;
+ gchar *name;
+
+ if (enc == current_encoding)
+ {
+ name = g_strdup_printf (_("Current Locale (%s)"),
+ gtk_source_encoding_get_charset (enc));
+ }
+ else
+ {
+ name = gtk_source_encoding_to_string (enc);
+ }
+
+ items = g_slist_prepend (items, gedit_encoding_item_new (enc, name));
+ }
+
+ g_slist_free (encodings);
+
+ return g_slist_reverse (items);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-encoding-items.h b/gedit/gedit-encoding-items.h
new file mode 100644
index 0000000..07e3917
--- /dev/null
+++ b/gedit/gedit-encoding-items.h
@@ -0,0 +1,40 @@
+/*
+ * gedit-encoding-items.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_ENCODING_ITEMS_H
+#define GEDIT_ENCODING_ITEMS_H
+
+#include <gtksourceview/gtksource.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GeditEncodingItem GeditEncodingItem;
+
+GSList *gedit_encoding_items_get (void);
+
+void gedit_encoding_item_free (GeditEncodingItem *item);
+const GtkSourceEncoding *gedit_encoding_item_get_encoding (GeditEncodingItem *item);
+const gchar *gedit_encoding_item_get_name (GeditEncodingItem *item);
+
+G_END_DECLS
+
+#endif /* GEDIT_ENCODING_ITEMS_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-encodings-combo-box.c b/gedit/gedit-encodings-combo-box.c
new file mode 100644
index 0000000..af2088f
--- /dev/null
+++ b/gedit/gedit-encodings-combo-box.c
@@ -0,0 +1,439 @@
+/*
+ * gedit-encodings-combo-box.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2003-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 "gedit-encodings-combo-box.h"
+#include <glib/gi18n.h>
+#include "gedit-encoding-items.h"
+#include "gedit-encodings-dialog.h"
+
+struct _GeditEncodingsComboBox
+{
+ GtkComboBox parent_instance;
+
+ GtkListStore *store;
+ glong changed_id;
+
+ guint activated_item;
+
+ guint save_mode : 1;
+};
+
+enum
+{
+ COLUMN_NAME,
+ COLUMN_ENCODING,
+ COLUMN_CONFIGURE_ROW, /* TRUE for the "Add or Remove..." row. */
+ N_COLUMNS
+};
+
+enum
+{
+ PROP_0,
+ PROP_SAVE_MODE,
+ LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP];
+
+G_DEFINE_TYPE (GeditEncodingsComboBox, gedit_encodings_combo_box, GTK_TYPE_COMBO_BOX)
+
+static void update_menu (GeditEncodingsComboBox *combo_box);
+
+static void
+gedit_encodings_combo_box_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditEncodingsComboBox *combo;
+
+ combo = GEDIT_ENCODINGS_COMBO_BOX (object);
+
+ switch (prop_id)
+ {
+ case PROP_SAVE_MODE:
+ combo->save_mode = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_encodings_combo_box_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditEncodingsComboBox *combo;
+
+ combo = GEDIT_ENCODINGS_COMBO_BOX (object);
+
+ switch (prop_id)
+ {
+ case PROP_SAVE_MODE:
+ g_value_set_boolean (value, combo->save_mode);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_encodings_combo_box_dispose (GObject *object)
+{
+ GeditEncodingsComboBox *combo = GEDIT_ENCODINGS_COMBO_BOX (object);
+
+ g_clear_object (&combo->store);
+
+ G_OBJECT_CLASS (gedit_encodings_combo_box_parent_class)->dispose (object);
+}
+
+static void
+gedit_encodings_combo_box_constructed (GObject *object)
+{
+ GeditEncodingsComboBox *combo = GEDIT_ENCODINGS_COMBO_BOX (object);
+ GtkCellRenderer *text_renderer;
+
+ G_OBJECT_CLASS (gedit_encodings_combo_box_parent_class)->constructed (object);
+
+ /* Setup up the cells */
+ text_renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (combo),
+ text_renderer, TRUE);
+
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo),
+ text_renderer,
+ "text",
+ COLUMN_NAME,
+ NULL);
+
+ update_menu (combo);
+}
+
+static void
+gedit_encodings_combo_box_class_init (GeditEncodingsComboBoxClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gedit_encodings_combo_box_set_property;
+ object_class->get_property = gedit_encodings_combo_box_get_property;
+ object_class->dispose = gedit_encodings_combo_box_dispose;
+ object_class->constructed = gedit_encodings_combo_box_constructed;
+
+ /**
+ * GeditEncodingsComboBox:save-mode:
+ *
+ * Whether the combo box should be used for saving a content. If
+ * %FALSE, the combo box is used for loading a content (e.g. a file)
+ * and the row "Automatically Detected" is added.
+ */
+ /* TODO It'd be clearer if "save-mode" is renamed as "mode" with an
+ * enum: loading, saving. Or something like that.
+ */
+ properties[PROP_SAVE_MODE] =
+ g_param_spec_boolean ("save-mode",
+ "Save Mode",
+ "Save Mode",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+dialog_response_cb (GtkDialog *dialog,
+ gint response_id,
+ GeditEncodingsComboBox *menu)
+{
+ update_menu (menu);
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+configure_encodings (GeditEncodingsComboBox *menu)
+{
+ GtkWidget *dialog;
+
+ GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (menu));
+
+ if (!gtk_widget_is_toplevel (toplevel))
+ {
+ toplevel = NULL;
+ }
+
+ g_signal_handler_block (menu, menu->changed_id);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (menu),
+ menu->activated_item);
+ g_signal_handler_unblock (menu, menu->changed_id);
+
+ dialog = gedit_encodings_dialog_new ();
+
+ if (toplevel != NULL)
+ {
+ GtkWindowGroup *wg;
+
+ gtk_window_set_transient_for (GTK_WINDOW (dialog),
+ GTK_WINDOW (toplevel));
+
+ if (gtk_window_has_group (GTK_WINDOW (toplevel)))
+ {
+ wg = gtk_window_get_group (GTK_WINDOW (toplevel));
+ }
+ else
+ {
+ wg = gtk_window_group_new ();
+ gtk_window_group_add_window (wg, GTK_WINDOW (toplevel));
+ }
+
+ gtk_window_group_add_window (wg, GTK_WINDOW (dialog));
+ }
+
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+ g_signal_connect_after (dialog,
+ "response",
+ G_CALLBACK (dialog_response_cb),
+ menu);
+
+ gtk_widget_show (dialog);
+}
+
+static void
+changed_cb (GeditEncodingsComboBox *menu,
+ GtkTreeModel *model)
+{
+ GtkTreeIter iter;
+ gboolean configure = FALSE;
+
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (menu), &iter))
+ {
+ gtk_tree_model_get (model, &iter,
+ COLUMN_CONFIGURE_ROW, &configure,
+ -1);
+ }
+
+ if (configure)
+ {
+ configure_encodings (menu);
+ }
+ else
+ {
+ menu->activated_item = gtk_combo_box_get_active (GTK_COMBO_BOX (menu));
+ }
+}
+
+static gboolean
+separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ gchar *str;
+ gboolean ret;
+
+ gtk_tree_model_get (model, iter, COLUMN_NAME, &str, -1);
+ ret = (str == NULL || str[0] == '\0');
+ g_free (str);
+
+ return ret;
+}
+
+static void
+add_separator (GtkListStore *store)
+{
+ GtkTreeIter iter;
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_NAME, "",
+ COLUMN_ENCODING, NULL,
+ COLUMN_CONFIGURE_ROW, FALSE,
+ -1);
+}
+
+static void
+update_menu (GeditEncodingsComboBox *menu)
+{
+ GtkListStore *store;
+ GtkTreeIter iter;
+ GSList *encodings;
+
+ store = menu->store;
+
+ /* Unset the previous model */
+ g_signal_handler_block (menu, menu->changed_id);
+ gtk_list_store_clear (store);
+ gtk_combo_box_set_model (GTK_COMBO_BOX (menu), NULL);
+
+ if (!menu->save_mode)
+ {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_NAME, _("Automatically Detected"),
+ COLUMN_ENCODING, NULL,
+ COLUMN_CONFIGURE_ROW, FALSE,
+ -1);
+
+ add_separator (store);
+ }
+
+ encodings = gedit_encoding_items_get ();
+
+ while (encodings)
+ {
+ GeditEncodingItem *item = encodings->data;
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_NAME, gedit_encoding_item_get_name (item),
+ COLUMN_ENCODING, gedit_encoding_item_get_encoding (item),
+ COLUMN_CONFIGURE_ROW, FALSE,
+ -1);
+
+ gedit_encoding_item_free (item);
+ encodings = g_slist_delete_link (encodings, encodings);
+ }
+
+ add_separator (store);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_NAME, _("Add or Remove…"),
+ COLUMN_ENCODING, NULL,
+ COLUMN_CONFIGURE_ROW, TRUE,
+ -1);
+
+ /* set the model back */
+ gtk_combo_box_set_model (GTK_COMBO_BOX (menu),
+ GTK_TREE_MODEL (menu->store));
+ gtk_combo_box_set_active (GTK_COMBO_BOX (menu), 0);
+
+ g_signal_handler_unblock (menu, menu->changed_id);
+}
+
+static void
+gedit_encodings_combo_box_init (GeditEncodingsComboBox *menu)
+{
+ menu->store = gtk_list_store_new (N_COLUMNS,
+ G_TYPE_STRING,
+ G_TYPE_POINTER,
+ G_TYPE_BOOLEAN);
+
+ gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (menu),
+ separator_func, NULL,
+ NULL);
+
+ menu->changed_id = g_signal_connect (menu,
+ "changed",
+ G_CALLBACK (changed_cb),
+ menu->store);
+}
+
+/**
+ * gedit_encodings_combo_box_new:
+ * @save_mode: whether the combo box is used for saving a content.
+ *
+ * Creates a new encodings combo box object. If @save_mode is %FALSE, it means
+ * that the combo box is used for loading a content (e.g. a file), so the row
+ * "Automatically Detected" is added. For saving a content, the encoding must be
+ * provided.
+ *
+ * Returns: a new #GeditEncodingsComboBox object.
+ */
+GtkWidget *
+gedit_encodings_combo_box_new (gboolean save_mode)
+{
+ return g_object_new (GEDIT_TYPE_ENCODINGS_COMBO_BOX,
+ "save_mode", save_mode,
+ NULL);
+}
+
+/**
+ * gedit_encodings_combo_box_get_selected_encoding:
+ * @menu: a #GeditEncodingsComboBox.
+ *
+ * Returns: the selected #GtkSourceEncoding, or %NULL if the encoding should be
+ * auto-detected (only for loading mode, not for saving).
+ */
+const GtkSourceEncoding *
+gedit_encodings_combo_box_get_selected_encoding (GeditEncodingsComboBox *menu)
+{
+ GtkTreeIter iter;
+
+ g_return_val_if_fail (GEDIT_IS_ENCODINGS_COMBO_BOX (menu), NULL);
+
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (menu), &iter))
+ {
+ const GtkSourceEncoding *ret;
+ GtkTreeModel *model;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (menu));
+
+ gtk_tree_model_get (model, &iter,
+ COLUMN_ENCODING, &ret,
+ -1);
+
+ return ret;
+ }
+
+ return NULL;
+}
+
+/**
+ * gedit_encodings_combo_box_set_selected_encoding:
+ * @menu: a #GeditEncodingsComboBox.
+ * @encoding: the #GtkSourceEncoding.
+ *
+ * Sets the selected encoding.
+ */
+void
+gedit_encodings_combo_box_set_selected_encoding (GeditEncodingsComboBox *menu,
+ const GtkSourceEncoding *encoding)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ gboolean b;
+
+ g_return_if_fail (GEDIT_IS_ENCODINGS_COMBO_BOX (menu));
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (menu));
+ b = gtk_tree_model_get_iter_first (model, &iter);
+
+ while (b)
+ {
+ const GtkSourceEncoding *enc;
+
+ gtk_tree_model_get (model, &iter,
+ COLUMN_ENCODING, &enc,
+ -1);
+
+ if (enc == encoding)
+ {
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (menu), &iter);
+ return;
+ }
+
+ b = gtk_tree_model_iter_next (model, &iter);
+ }
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-encodings-combo-box.h b/gedit/gedit-encodings-combo-box.h
new file mode 100644
index 0000000..c37f341
--- /dev/null
+++ b/gedit/gedit-encodings-combo-box.h
@@ -0,0 +1,43 @@
+/*
+ * gedit-encodings-combo-box.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2003-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/>.
+ */
+
+#ifndef GEDIT_ENCODINGS_COMBO_BOX_H
+#define GEDIT_ENCODINGS_COMBO_BOX_H
+
+#include <gtksourceview/gtksource.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_ENCODINGS_COMBO_BOX (gedit_encodings_combo_box_get_type ())
+
+G_DECLARE_FINAL_TYPE (GeditEncodingsComboBox, gedit_encodings_combo_box, GEDIT, ENCODINGS_COMBO_BOX, GtkComboBox)
+
+GtkWidget *gedit_encodings_combo_box_new (gboolean save_mode);
+
+const GtkSourceEncoding *gedit_encodings_combo_box_get_selected_encoding (GeditEncodingsComboBox *menu);
+
+void gedit_encodings_combo_box_set_selected_encoding (GeditEncodingsComboBox *menu,
+ const GtkSourceEncoding *encoding);
+
+G_END_DECLS
+
+#endif /* GEDIT_ENCODINGS_COMBO_BOX_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-encodings-dialog.c b/gedit/gedit-encodings-dialog.c
new file mode 100644
index 0000000..196690a
--- /dev/null
+++ b/gedit/gedit-encodings-dialog.c
@@ -0,0 +1,884 @@
+/*
+ * gedit-encodings-dialog.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ * Copyright (C) 2015 Sébastien Wilmet
+ *
+ * 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-encodings-dialog.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gtksourceview/gtksource.h>
+
+#include "gedit-settings.h"
+
+typedef enum _State
+{
+ STATE_UNMODIFIED,
+ STATE_MODIFIED,
+ STATE_RESET
+} State;
+
+struct _GeditEncodingsDialog
+{
+ GtkDialog parent_instance;
+
+ GSettings *enc_settings;
+
+ /* Available encodings */
+ GtkListStore *liststore_available;
+ GtkTreeModelSort *sort_available;
+ GtkTreeView *treeview_available;
+ GtkWidget *add_button;
+
+ /* Chosen encodings */
+ GtkListStore *liststore_chosen;
+ GtkTreeView *treeview_chosen;
+ GtkWidget *remove_button;
+ GtkWidget *up_button;
+ GtkWidget *down_button;
+ GtkWidget *reset_button;
+
+ State state;
+};
+
+enum
+{
+ COLUMN_NAME,
+ COLUMN_CHARSET,
+ COLUMN_ENCODING,
+ N_COLUMNS
+};
+
+G_DEFINE_TYPE (GeditEncodingsDialog, gedit_encodings_dialog, GTK_TYPE_DIALOG)
+
+static void
+set_modified (GeditEncodingsDialog *dialog)
+{
+ dialog->state = STATE_MODIFIED;
+ gtk_widget_set_sensitive (dialog->reset_button, TRUE);
+}
+
+static void
+append_encoding (GtkListStore *liststore,
+ const GtkSourceEncoding *encoding)
+{
+ GtkTreeIter iter;
+
+ gtk_list_store_append (liststore, &iter);
+ gtk_list_store_set (liststore, &iter,
+ COLUMN_NAME, gtk_source_encoding_get_name (encoding),
+ COLUMN_ENCODING, encoding,
+ -1);
+
+ if (encoding == gtk_source_encoding_get_current ())
+ {
+ gchar *charset = g_strdup_printf (_("%s (Current Locale)"),
+ gtk_source_encoding_get_charset (encoding));
+
+ gtk_list_store_set (liststore, &iter,
+ COLUMN_CHARSET, charset,
+ -1);
+
+ g_free (charset);
+ }
+ else
+ {
+ gtk_list_store_set (liststore, &iter,
+ COLUMN_CHARSET, gtk_source_encoding_get_charset (encoding),
+ -1);
+ }
+}
+
+static void
+init_liststores (GeditEncodingsDialog *dialog,
+ gboolean reset)
+{
+ gboolean default_candidates;
+ GSList *chosen_encodings;
+ GSList *all_encodings;
+ GSList *l;
+
+ /* Chosen encodings */
+
+ if (reset)
+ {
+ chosen_encodings = gtk_source_encoding_get_default_candidates ();
+ default_candidates = TRUE;
+ }
+ else
+ {
+ chosen_encodings = gedit_settings_get_candidate_encodings (&default_candidates);
+ }
+
+ gtk_widget_set_sensitive (dialog->reset_button, !default_candidates);
+
+ for (l = chosen_encodings; l != NULL; l = l->next)
+ {
+ const GtkSourceEncoding *cur_encoding = l->data;
+ append_encoding (dialog->liststore_chosen, cur_encoding);
+ }
+
+ /* Available encodings */
+
+ all_encodings = gtk_source_encoding_get_all ();
+
+ for (l = chosen_encodings; l != NULL; l = l->next)
+ {
+ const GtkSourceEncoding *chosen_encoding = l->data;
+ all_encodings = g_slist_remove (all_encodings, chosen_encoding);
+ }
+
+ for (l = all_encodings; l != NULL; l = l->next)
+ {
+ const GtkSourceEncoding *cur_encoding = l->data;
+ append_encoding (dialog->liststore_available, cur_encoding);
+ }
+
+ g_slist_free (chosen_encodings);
+ g_slist_free (all_encodings);
+}
+
+static void
+reset_dialog_response_cb (GtkDialog *msg_dialog,
+ gint response,
+ GeditEncodingsDialog *dialog)
+{
+ if (response == GTK_RESPONSE_ACCEPT)
+ {
+ gtk_list_store_clear (dialog->liststore_available);
+ gtk_list_store_clear (dialog->liststore_chosen);
+
+ init_liststores (dialog, TRUE);
+ dialog->state = STATE_RESET;
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (msg_dialog));
+}
+
+static void
+reset_button_clicked_cb (GtkWidget *button,
+ GeditEncodingsDialog *dialog)
+{
+ GtkDialog *msg_dialog;
+
+ msg_dialog = GTK_DIALOG (gtk_message_dialog_new (GTK_WINDOW (dialog),
+ GTK_DIALOG_DESTROY_WITH_PARENT |
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ "%s",
+ _("Do you really want to reset the "
+ "character encodings’ preferences?")));
+
+ gtk_dialog_add_buttons (msg_dialog,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Reset"), GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ g_signal_connect (msg_dialog,
+ "response",
+ G_CALLBACK (reset_dialog_response_cb),
+ dialog);
+
+ gtk_widget_show_all (GTK_WIDGET (msg_dialog));
+}
+
+static GSList *
+get_chosen_encodings_list (GeditEncodingsDialog *dialog)
+{
+ GtkTreeModel *model = GTK_TREE_MODEL (dialog->liststore_chosen);
+ GtkTreeIter iter;
+ gboolean iter_set;
+ GSList *ret = NULL;
+
+ iter_set = gtk_tree_model_get_iter_first (model, &iter);
+
+ while (iter_set)
+ {
+ const GtkSourceEncoding *encoding = NULL;
+
+ gtk_tree_model_get (model, &iter,
+ COLUMN_ENCODING, &encoding,
+ -1);
+
+ ret = g_slist_prepend (ret, (gpointer)encoding);
+
+ iter_set = gtk_tree_model_iter_next (model, &iter);
+ }
+
+ return g_slist_reverse (ret);
+}
+
+static gchar **
+encoding_list_to_strv (const GSList *enc_list)
+{
+ GSList *l;
+ GPtrArray *array;
+
+ array = g_ptr_array_sized_new (g_slist_length ((GSList *)enc_list) + 1);
+
+ for (l = (GSList *)enc_list; l != NULL; l = l->next)
+ {
+ const GtkSourceEncoding *enc = l->data;
+ const gchar *charset = gtk_source_encoding_get_charset (enc);
+
+ g_return_val_if_fail (charset != NULL, NULL);
+
+ g_ptr_array_add (array, g_strdup (charset));
+ }
+
+ g_ptr_array_add (array, NULL);
+
+ return (gchar **)g_ptr_array_free (array, FALSE);
+}
+
+static void
+apply_settings (GeditEncodingsDialog *dialog)
+{
+ switch (dialog->state)
+ {
+ case STATE_MODIFIED:
+ {
+ GSList *enc_list;
+ gchar **enc_strv;
+
+ enc_list = get_chosen_encodings_list (dialog);
+ enc_strv = encoding_list_to_strv (enc_list);
+
+ g_settings_set_strv (dialog->enc_settings,
+ GEDIT_SETTINGS_CANDIDATE_ENCODINGS,
+ (const gchar * const *)enc_strv);
+
+ g_slist_free (enc_list);
+ g_strfreev (enc_strv);
+ break;
+ }
+
+ case STATE_RESET:
+ g_settings_reset (dialog->enc_settings,
+ GEDIT_SETTINGS_CANDIDATE_ENCODINGS);
+ break;
+
+ case STATE_UNMODIFIED:
+ /* Do nothing. */
+ break;
+
+ default:
+ g_assert_not_reached ();
+
+ }
+}
+
+static void
+gedit_encodings_dialog_response (GtkDialog *gtk_dialog,
+ gint response_id)
+{
+ GeditEncodingsDialog *dialog = GEDIT_ENCODINGS_DIALOG (gtk_dialog);
+
+ switch (response_id)
+ {
+ case GTK_RESPONSE_APPLY:
+ apply_settings (dialog);
+ break;
+
+ case GTK_RESPONSE_CANCEL:
+ default:
+ /* Do nothing */
+ break;
+ }
+}
+
+static void
+gedit_encodings_dialog_dispose (GObject *object)
+{
+ GeditEncodingsDialog *dialog = GEDIT_ENCODINGS_DIALOG (object);
+
+ g_clear_object (&dialog->enc_settings);
+ g_clear_object (&dialog->add_button);
+ g_clear_object (&dialog->remove_button);
+ g_clear_object (&dialog->up_button);
+ g_clear_object (&dialog->down_button);
+ g_clear_object (&dialog->reset_button);
+
+ G_OBJECT_CLASS (gedit_encodings_dialog_parent_class)->dispose (object);
+}
+
+static void
+gedit_encodings_dialog_class_init (GeditEncodingsDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass);
+
+ object_class->dispose = gedit_encodings_dialog_dispose;
+
+ dialog_class->response = gedit_encodings_dialog_response;
+
+ /* Bind class to template */
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/gedit/ui/gedit-encodings-dialog.ui");
+ gtk_widget_class_bind_template_child (widget_class, GeditEncodingsDialog, liststore_available);
+ gtk_widget_class_bind_template_child (widget_class, GeditEncodingsDialog, liststore_chosen);
+ gtk_widget_class_bind_template_child (widget_class, GeditEncodingsDialog, sort_available);
+ gtk_widget_class_bind_template_child (widget_class, GeditEncodingsDialog, treeview_available);
+ gtk_widget_class_bind_template_child (widget_class, GeditEncodingsDialog, treeview_chosen);
+ gtk_widget_class_bind_template_child_full (widget_class, "scrolledwindow_available", FALSE, 0);
+ gtk_widget_class_bind_template_child_full (widget_class, "scrolledwindow_chosen", FALSE, 0);
+ gtk_widget_class_bind_template_child_full (widget_class, "toolbar_available", FALSE, 0);
+ gtk_widget_class_bind_template_child_full (widget_class, "toolbar_chosen", FALSE, 0);
+}
+
+static void
+update_add_button_sensitivity (GeditEncodingsDialog *dialog)
+{
+ GtkTreeSelection *selection;
+ gint count;
+
+ selection = gtk_tree_view_get_selection (dialog->treeview_available);
+ count = gtk_tree_selection_count_selected_rows (selection);
+ gtk_widget_set_sensitive (dialog->add_button, count > 0);
+}
+
+static void
+update_remove_button_sensitivity (GeditEncodingsDialog *dialog)
+{
+ const GtkSourceEncoding *utf8_encoding;
+ const GtkSourceEncoding *current_encoding;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GList *selected_rows;
+ GList *l;
+ gboolean sensitive;
+
+ utf8_encoding = gtk_source_encoding_get_utf8 ();
+ current_encoding = gtk_source_encoding_get_current ();
+
+ selection = gtk_tree_view_get_selection (dialog->treeview_chosen);
+
+ selected_rows = gtk_tree_selection_get_selected_rows (selection, &model);
+ g_return_if_fail (model == GTK_TREE_MODEL (dialog->liststore_chosen));
+
+ sensitive = FALSE;
+ for (l = selected_rows; l != NULL; l = l->next)
+ {
+ GtkTreePath *path = l->data;
+ GtkTreeIter iter;
+ const GtkSourceEncoding *encoding = NULL;
+
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ {
+ g_warning ("Remove button: invalid path");
+ continue;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ COLUMN_ENCODING, &encoding,
+ -1);
+
+ /* If at least one encoding other than UTF-8 or current is
+ * selected, set the Remove button as sensitive. But if UTF-8 or
+ * current is selected, it won't be removed. So Ctrl+A works
+ * fine to remove (almost) all encodings in one go.
+ */
+ if (encoding != utf8_encoding &&
+ encoding != current_encoding)
+ {
+ sensitive = TRUE;
+ break;
+ }
+ }
+
+ gtk_widget_set_sensitive (dialog->remove_button, sensitive);
+
+ g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
+}
+
+static void
+update_up_down_buttons_sensitivity (GeditEncodingsDialog *dialog)
+{
+ GtkTreeSelection *selection;
+ gint count;
+ GList *selected_rows;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ gint *indices;
+ gint depth;
+ gint items_count;
+ gboolean first_item_selected;
+ gboolean last_item_selected;
+
+ selection = gtk_tree_view_get_selection (dialog->treeview_chosen);
+ count = gtk_tree_selection_count_selected_rows (selection);
+
+ if (count != 1)
+ {
+ gtk_widget_set_sensitive (dialog->up_button, FALSE);
+ gtk_widget_set_sensitive (dialog->down_button, FALSE);
+ return;
+ }
+
+ selected_rows = gtk_tree_selection_get_selected_rows (selection, &model);
+ g_assert (g_list_length (selected_rows) == 1);
+
+ path = selected_rows->data;
+ indices = gtk_tree_path_get_indices_with_depth (path, &depth);
+ g_assert (depth == 1);
+
+ items_count = gtk_tree_model_iter_n_children (model, NULL);
+
+ first_item_selected = indices[0] == 0;
+ last_item_selected = indices[0] == (items_count - 1);
+
+ gtk_widget_set_sensitive (dialog->up_button, !first_item_selected);
+ gtk_widget_set_sensitive (dialog->down_button, !last_item_selected);
+
+ g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
+}
+
+static void
+update_chosen_buttons_sensitivity (GeditEncodingsDialog *dialog)
+{
+ update_remove_button_sensitivity (dialog);
+ update_up_down_buttons_sensitivity (dialog);
+}
+
+/* Removes all @paths from @orig, and append them at the end of @dest in the
+ * same order.
+ */
+static void
+transfer_encodings (GList *paths,
+ GtkListStore *orig,
+ GtkListStore *dest)
+{
+ GtkTreeModel *model_orig = GTK_TREE_MODEL (orig);
+ GList *refs = NULL;
+ GList *l;
+
+ for (l = paths; l != NULL; l = l->next)
+ {
+ GtkTreePath *path = l->data;
+ refs = g_list_prepend (refs, gtk_tree_row_reference_new (model_orig, path));
+ }
+
+ refs = g_list_reverse (refs);
+
+ for (l = refs; l != NULL; l = l->next)
+ {
+ GtkTreeRowReference *ref = l->data;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ const GtkSourceEncoding *encoding = NULL;
+
+ path = gtk_tree_row_reference_get_path (ref);
+
+ if (!gtk_tree_model_get_iter (model_orig, &iter, path))
+ {
+ gtk_tree_path_free (path);
+ g_warning ("Remove encoding: invalid path");
+ continue;
+ }
+
+ /* Transfer encoding */
+ gtk_tree_model_get (model_orig, &iter,
+ COLUMN_ENCODING, &encoding,
+ -1);
+
+ append_encoding (dest, encoding);
+
+ gtk_list_store_remove (orig, &iter);
+
+ gtk_tree_path_free (path);
+ }
+
+ g_list_free_full (refs, (GDestroyNotify) gtk_tree_row_reference_free);
+}
+
+static void
+add_button_clicked_cb (GtkWidget *button,
+ GeditEncodingsDialog *dialog)
+{
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GList *filter_paths;
+ GList *children_paths = NULL;
+ GList *l;
+
+ selection = gtk_tree_view_get_selection (dialog->treeview_available);
+ filter_paths = gtk_tree_selection_get_selected_rows (selection, &model);
+
+ g_return_if_fail (model == GTK_TREE_MODEL (dialog->sort_available));
+
+ for (l = filter_paths; l != NULL; l = l->next)
+ {
+ GtkTreePath *filter_path = l->data;
+ GtkTreePath *child_path;
+
+ child_path = gtk_tree_model_sort_convert_path_to_child_path (dialog->sort_available,
+ filter_path);
+
+ children_paths = g_list_prepend (children_paths, child_path);
+ }
+
+ children_paths = g_list_reverse (children_paths);
+
+ transfer_encodings (children_paths,
+ dialog->liststore_available,
+ dialog->liststore_chosen);
+
+ set_modified (dialog);
+
+ /* For the treeview_available, it's more natural to unselect the added
+ * encodings.
+ * Note that when removing encodings from treeview_chosen, it is
+ * desirable to keep a selection, so we can remove several elements in a
+ * row (if the user doesn't know that several elements can be selected
+ * at once with Ctrl or Shift).
+ */
+ gtk_tree_selection_unselect_all (selection);
+
+ g_list_free_full (filter_paths, (GDestroyNotify) gtk_tree_path_free);
+ g_list_free_full (children_paths, (GDestroyNotify) gtk_tree_path_free);
+}
+
+static void
+remove_button_clicked_cb (GtkWidget *button,
+ GeditEncodingsDialog *dialog)
+{
+ const GtkSourceEncoding *utf8_encoding;
+ const GtkSourceEncoding *current_encoding;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GList *selected_rows;
+ GList *to_remove = NULL;
+ GList *l;
+
+ utf8_encoding = gtk_source_encoding_get_utf8 ();
+ current_encoding = gtk_source_encoding_get_current ();
+
+ selection = gtk_tree_view_get_selection (dialog->treeview_chosen);
+ selected_rows = gtk_tree_selection_get_selected_rows (selection, &model);
+
+ g_return_if_fail (model == GTK_TREE_MODEL (dialog->liststore_chosen));
+
+ /* Ensure that UTF-8 and the current locale encodings cannot be removed. */
+ for (l = selected_rows; l != NULL; l = l->next)
+ {
+ GtkTreePath *path = l->data;
+ GtkTreeIter iter;
+ const GtkSourceEncoding *encoding = NULL;
+
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ {
+ gtk_tree_path_free (path);
+ g_warning ("Remove button: invalid path");
+ continue;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ COLUMN_ENCODING, &encoding,
+ -1);
+
+ if (encoding == utf8_encoding ||
+ encoding == current_encoding)
+ {
+ gtk_tree_path_free (path);
+ }
+ else
+ {
+ to_remove = g_list_prepend (to_remove, path);
+ }
+ }
+
+ to_remove = g_list_reverse (to_remove);
+
+ transfer_encodings (to_remove,
+ dialog->liststore_chosen,
+ dialog->liststore_available);
+
+ set_modified (dialog);
+
+ g_list_free (selected_rows);
+ g_list_free_full (to_remove, (GDestroyNotify) gtk_tree_path_free);
+}
+
+static void
+up_button_clicked_cb (GtkWidget *button,
+ GeditEncodingsDialog *dialog)
+{
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GList *selected_rows;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ GtkTreeIter prev_iter;
+
+ selection = gtk_tree_view_get_selection (dialog->treeview_chosen);
+ selected_rows = gtk_tree_selection_get_selected_rows (selection, &model);
+
+ g_return_if_fail (model == GTK_TREE_MODEL (dialog->liststore_chosen));
+ g_return_if_fail (g_list_length (selected_rows) == 1);
+
+ path = selected_rows->data;
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ {
+ g_return_if_reached ();
+ }
+
+ prev_iter = iter;
+ if (!gtk_tree_model_iter_previous (model, &prev_iter))
+ {
+ g_return_if_reached ();
+ }
+
+ gtk_list_store_move_before (dialog->liststore_chosen,
+ &iter,
+ &prev_iter);
+
+ set_modified (dialog);
+
+ update_chosen_buttons_sensitivity (dialog);
+
+ g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
+}
+
+static void
+down_button_clicked_cb (GtkWidget *button,
+ GeditEncodingsDialog *dialog)
+{
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GList *selected_rows;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ GtkTreeIter next_iter;
+
+ selection = gtk_tree_view_get_selection (dialog->treeview_chosen);
+ selected_rows = gtk_tree_selection_get_selected_rows (selection, &model);
+
+ g_return_if_fail (model == GTK_TREE_MODEL (dialog->liststore_chosen));
+ g_return_if_fail (g_list_length (selected_rows) == 1);
+
+ path = selected_rows->data;
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ {
+ g_return_if_reached ();
+ }
+
+ next_iter = iter;
+ if (!gtk_tree_model_iter_next (model, &next_iter))
+ {
+ g_return_if_reached ();
+ }
+
+ gtk_list_store_move_after (dialog->liststore_chosen,
+ &iter,
+ &next_iter);
+
+ set_modified (dialog);
+
+ update_chosen_buttons_sensitivity (dialog);
+
+ g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
+}
+
+static void
+init_toolbar_available (GeditEncodingsDialog *dialog)
+{
+ GtkWidget *scrolled_window;
+ GtkToolbar *toolbar;
+ GtkStyleContext *context;
+
+ scrolled_window = GTK_WIDGET (gtk_widget_get_template_child (GTK_WIDGET (dialog),
+ GEDIT_TYPE_ENCODINGS_DIALOG,
+ "scrolledwindow_available"));
+
+ toolbar = GTK_TOOLBAR (gtk_widget_get_template_child (GTK_WIDGET (dialog),
+ GEDIT_TYPE_ENCODINGS_DIALOG,
+ "toolbar_available"));
+
+ context = gtk_widget_get_style_context (scrolled_window);
+ gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM);
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (toolbar));
+ gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_INLINE_TOOLBAR);
+
+ /* Add button */
+ dialog->add_button = GTK_WIDGET (gtk_tool_button_new (NULL, NULL));
+ g_object_ref_sink (dialog->add_button);
+
+ gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (dialog->add_button), "list-add-symbolic");
+ gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (dialog->add_button), _("Add"));
+
+ gtk_toolbar_insert (toolbar, GTK_TOOL_ITEM (dialog->add_button), -1);
+
+ g_signal_connect_object (dialog->add_button,
+ "clicked",
+ G_CALLBACK (add_button_clicked_cb),
+ dialog,
+ 0);
+
+ gtk_widget_show_all (GTK_WIDGET (toolbar));
+}
+
+static void
+init_toolbar_chosen (GeditEncodingsDialog *dialog)
+{
+ GtkWidget *scrolled_window;
+ GtkToolbar *toolbar;
+ GtkStyleContext *context;
+ GtkWidget *left_box;
+ GtkWidget *right_box;
+ GtkToolItem *left_group;
+ GtkToolItem *right_group;
+ GtkToolItem *separator;
+
+ scrolled_window = GTK_WIDGET (gtk_widget_get_template_child (GTK_WIDGET (dialog),
+ GEDIT_TYPE_ENCODINGS_DIALOG,
+ "scrolledwindow_chosen"));
+
+ toolbar = GTK_TOOLBAR (gtk_widget_get_template_child (GTK_WIDGET (dialog),
+ GEDIT_TYPE_ENCODINGS_DIALOG,
+ "toolbar_chosen"));
+
+ context = gtk_widget_get_style_context (scrolled_window);
+ gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM);
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (toolbar));
+ gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_INLINE_TOOLBAR);
+
+ /* Remove button */
+ dialog->remove_button = gtk_button_new_from_icon_name ("list-remove-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
+ g_object_ref_sink (dialog->remove_button);
+ gtk_widget_set_tooltip_text (dialog->remove_button, _("Remove"));
+
+ g_signal_connect_object (dialog->remove_button,
+ "clicked",
+ G_CALLBACK (remove_button_clicked_cb),
+ dialog,
+ 0);
+
+ /* Up button */
+ dialog->up_button = gtk_button_new_from_icon_name ("go-up-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
+ g_object_ref_sink (dialog->up_button);
+ gtk_widget_set_tooltip_text (dialog->up_button, _("Move to a higher priority"));
+
+ g_signal_connect_object (dialog->up_button,
+ "clicked",
+ G_CALLBACK (up_button_clicked_cb),
+ dialog,
+ 0);
+
+ /* Down button */
+ dialog->down_button = gtk_button_new_from_icon_name ("go-down-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
+ g_object_ref_sink (dialog->down_button);
+ gtk_widget_set_tooltip_text (dialog->down_button, _("Move to a lower priority"));
+
+ g_signal_connect_object (dialog->down_button,
+ "clicked",
+ G_CALLBACK (down_button_clicked_cb),
+ dialog,
+ 0);
+
+ /* Left group (with a trick for rounded borders) */
+ left_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ left_group = gtk_tool_item_new ();
+ gtk_box_pack_start (GTK_BOX (left_box), dialog->remove_button, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (left_box), dialog->up_button, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (left_box), dialog->down_button, FALSE, FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (left_group), left_box);
+ gtk_toolbar_insert (toolbar, left_group, -1);
+
+ /* Separator */
+ separator = gtk_separator_tool_item_new ();
+ gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (separator), FALSE);
+ gtk_tool_item_set_expand (separator, TRUE);
+ gtk_toolbar_insert (toolbar, separator, -1);
+
+ /* Reset button */
+ dialog->reset_button = gtk_button_new_with_mnemonic (_("_Reset"));
+ g_object_ref_sink (dialog->reset_button);
+
+ g_signal_connect_object (dialog->reset_button,
+ "clicked",
+ G_CALLBACK (reset_button_clicked_cb),
+ dialog,
+ 0);
+
+ /* Right group */
+ right_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ right_group = gtk_tool_item_new ();
+ gtk_box_pack_start (GTK_BOX (right_box), dialog->reset_button, FALSE, FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (right_group), right_box);
+ gtk_toolbar_insert (toolbar, right_group, -1);
+
+ gtk_widget_show_all (GTK_WIDGET (toolbar));
+}
+
+static void
+gedit_encodings_dialog_init (GeditEncodingsDialog *dialog)
+{
+ GtkTreeSelection *selection;
+
+ dialog->enc_settings = g_settings_new ("org.gnome.gedit.preferences.encodings");
+
+ gtk_widget_init_template (GTK_WIDGET (dialog));
+
+ init_toolbar_available (dialog);
+ init_toolbar_chosen (dialog);
+ init_liststores (dialog, FALSE);
+ dialog->state = STATE_UNMODIFIED;
+
+ /* Available encodings */
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (dialog->sort_available),
+ COLUMN_NAME,
+ GTK_SORT_ASCENDING);
+
+ selection = gtk_tree_view_get_selection (dialog->treeview_available);
+
+ g_signal_connect_swapped (selection,
+ "changed",
+ G_CALLBACK (update_add_button_sensitivity),
+ dialog);
+
+ update_add_button_sensitivity (dialog);
+
+ /* Chosen encodings */
+ selection = gtk_tree_view_get_selection (dialog->treeview_chosen);
+
+ g_signal_connect_swapped (selection,
+ "changed",
+ G_CALLBACK (update_chosen_buttons_sensitivity),
+ dialog);
+
+ update_chosen_buttons_sensitivity (dialog);
+}
+
+GtkWidget *
+gedit_encodings_dialog_new (void)
+{
+ return g_object_new (GEDIT_TYPE_ENCODINGS_DIALOG,
+ "use-header-bar", TRUE,
+ NULL);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-encodings-dialog.h b/gedit/gedit-encodings-dialog.h
new file mode 100644
index 0000000..4acd17a
--- /dev/null
+++ b/gedit/gedit-encodings-dialog.h
@@ -0,0 +1,37 @@
+/*
+ * gedit-encodings-dialog.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2003-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/>.
+ */
+
+#ifndef GEDIT_ENCODINGS_DIALOG_H
+#define GEDIT_ENCODINGS_DIALOG_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_ENCODINGS_DIALOG (gedit_encodings_dialog_get_type())
+
+G_DECLARE_FINAL_TYPE (GeditEncodingsDialog, gedit_encodings_dialog, GEDIT, ENCODINGS_DIALOG, GtkDialog)
+
+GtkWidget *gedit_encodings_dialog_new (void);
+
+G_END_DECLS
+
+#endif /* GEDIT_ENCODINGS_DIALOG_H */
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-factory.c b/gedit/gedit-factory.c
new file mode 100644
index 0000000..f1d70ad
--- /dev/null
+++ b/gedit/gedit-factory.c
@@ -0,0 +1,69 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2020 Sébastien Wilmet <swilmet@gnome.org>
+ *
+ * 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 "gedit-factory.h"
+#include <glib/gi18n.h>
+#include "gedit-dirs.h"
+
+G_DEFINE_TYPE (GeditFactory, gedit_factory, TEPL_TYPE_ABSTRACT_FACTORY)
+
+static gchar *
+untitled_file_cb (gint untitled_file_number)
+{
+ return g_strdup_printf (_("Untitled Document %d"), untitled_file_number);
+}
+
+static TeplFile *
+gedit_factory_create_file (TeplAbstractFactory *factory)
+{
+ TeplFile *file;
+
+ file = tepl_file_new ();
+ tepl_file_set_untitled_file_callback (file, untitled_file_cb);
+
+ return file;
+}
+
+static GFile *
+gedit_factory_create_metadata_manager_file (TeplAbstractFactory *factory)
+{
+ return g_file_new_build_filename (gedit_dirs_get_user_data_dir (),
+ "gedit-metadata.xml",
+ NULL);
+}
+
+static void
+gedit_factory_class_init (GeditFactoryClass *klass)
+{
+ TeplAbstractFactoryClass *factory_class = TEPL_ABSTRACT_FACTORY_CLASS (klass);
+
+ factory_class->create_file = gedit_factory_create_file;
+ factory_class->create_metadata_manager_file = gedit_factory_create_metadata_manager_file;
+}
+
+static void
+gedit_factory_init (GeditFactory *factory)
+{
+}
+
+GeditFactory *
+gedit_factory_new (void)
+{
+ return g_object_new (GEDIT_TYPE_FACTORY, NULL);
+}
diff --git a/gedit/gedit-factory.h b/gedit/gedit-factory.h
new file mode 100644
index 0000000..05e19f7
--- /dev/null
+++ b/gedit/gedit-factory.h
@@ -0,0 +1,53 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2020 Sébastien Wilmet <swilmet@gnome.org>
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_FACTORY_H
+#define GEDIT_FACTORY_H
+
+#include <tepl/tepl.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FACTORY (gedit_factory_get_type ())
+#define GEDIT_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FACTORY, GeditFactory))
+#define GEDIT_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FACTORY, GeditFactoryClass))
+#define GEDIT_IS_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FACTORY))
+#define GEDIT_IS_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FACTORY))
+#define GEDIT_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FACTORY, GeditFactoryClass))
+
+typedef struct _GeditFactory GeditFactory;
+typedef struct _GeditFactoryClass GeditFactoryClass;
+
+struct _GeditFactory
+{
+ TeplAbstractFactory parent;
+};
+
+struct _GeditFactoryClass
+{
+ TeplAbstractFactoryClass parent_class;
+};
+
+GType gedit_factory_get_type (void);
+
+GeditFactory * gedit_factory_new (void);
+
+G_END_DECLS
+
+#endif /* GEDIT_FACTORY_H */
diff --git a/gedit/gedit-file-chooser-dialog-gtk.c b/gedit/gedit-file-chooser-dialog-gtk.c
new file mode 100644
index 0000000..4b8b4db
--- /dev/null
+++ b/gedit/gedit-file-chooser-dialog-gtk.c
@@ -0,0 +1,465 @@
+/*
+ * gedit-file-chooser-dialog-gtk.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2005-2007 - Paolo Maggi
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * 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/>.
+ */
+
+/* TODO: Override set_extra_widget */
+/* TODO: add encoding property */
+
+#include "config.h"
+#include "gedit-file-chooser-dialog-gtk.h"
+#include <string.h>
+#include <glib/gi18n.h>
+#include "gedit-encodings-combo-box.h"
+#include "gedit-debug.h"
+#include "gedit-enum-types.h"
+#include "gedit-settings.h"
+#include "gedit-utils.h"
+#include "gedit-file-chooser.h"
+
+struct _GeditFileChooserDialogGtk
+{
+ GtkFileChooserDialog parent_instance;
+
+ GeditFileChooser *gedit_file_chooser;
+
+ GtkWidget *option_menu;
+ GtkWidget *extra_widget;
+
+ GtkWidget *newline_label;
+ GtkWidget *newline_combo;
+ GtkListStore *newline_store;
+};
+
+static void gedit_file_chooser_dialog_gtk_chooser_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_EXTENDED (GeditFileChooserDialogGtk,
+ gedit_file_chooser_dialog_gtk,
+ GTK_TYPE_FILE_CHOOSER_DIALOG,
+ 0,
+ G_IMPLEMENT_INTERFACE (GEDIT_TYPE_FILE_CHOOSER_DIALOG,
+ gedit_file_chooser_dialog_gtk_chooser_init))
+
+
+static void
+chooser_set_encoding (GeditFileChooserDialog *dialog,
+ const GtkSourceEncoding *encoding)
+{
+ GeditFileChooserDialogGtk *dialog_gtk = GEDIT_FILE_CHOOSER_DIALOG_GTK (dialog);
+
+ g_return_if_fail (GEDIT_IS_ENCODINGS_COMBO_BOX (dialog_gtk->option_menu));
+
+ gedit_encodings_combo_box_set_selected_encoding (GEDIT_ENCODINGS_COMBO_BOX (dialog_gtk->option_menu),
+ encoding);
+}
+
+static const GtkSourceEncoding *
+chooser_get_encoding (GeditFileChooserDialog *dialog)
+{
+ GeditFileChooserDialogGtk *dialog_gtk = GEDIT_FILE_CHOOSER_DIALOG_GTK (dialog);
+
+ g_return_val_if_fail (GEDIT_IS_ENCODINGS_COMBO_BOX (dialog_gtk->option_menu), NULL);
+ g_return_val_if_fail ((gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)) == GTK_FILE_CHOOSER_ACTION_OPEN ||
+ gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)) == GTK_FILE_CHOOSER_ACTION_SAVE), NULL);
+
+ return gedit_encodings_combo_box_get_selected_encoding (
+ GEDIT_ENCODINGS_COMBO_BOX (dialog_gtk->option_menu));
+}
+
+static void
+set_enum_combo (GtkComboBox *combo,
+ gint value)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+
+ model = gtk_combo_box_get_model (combo);
+
+ if (!gtk_tree_model_get_iter_first (model, &iter))
+ {
+ return;
+ }
+
+ do
+ {
+ gint nt;
+
+ gtk_tree_model_get (model, &iter, 1, &nt, -1);
+
+ if (value == nt)
+ {
+ gtk_combo_box_set_active_iter (combo, &iter);
+ break;
+ }
+ } while (gtk_tree_model_iter_next (model, &iter));
+}
+
+static void
+chooser_set_newline_type (GeditFileChooserDialog *dialog,
+ GtkSourceNewlineType newline_type)
+{
+ GeditFileChooserDialogGtk *dialog_gtk = GEDIT_FILE_CHOOSER_DIALOG_GTK (dialog);
+
+ g_return_if_fail (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)) == GTK_FILE_CHOOSER_ACTION_SAVE);
+
+ set_enum_combo (GTK_COMBO_BOX (dialog_gtk->newline_combo), newline_type);
+}
+
+static GtkSourceNewlineType
+chooser_get_newline_type (GeditFileChooserDialog *dialog)
+{
+ GeditFileChooserDialogGtk *dialog_gtk = GEDIT_FILE_CHOOSER_DIALOG_GTK (dialog);
+ GtkTreeIter iter;
+ GtkSourceNewlineType newline_type;
+
+ g_return_val_if_fail (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)) == GTK_FILE_CHOOSER_ACTION_SAVE,
+ GTK_SOURCE_NEWLINE_TYPE_DEFAULT);
+
+ gtk_combo_box_get_active_iter (GTK_COMBO_BOX (dialog_gtk->newline_combo),
+ &iter);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (dialog_gtk->newline_store),
+ &iter,
+ 1,
+ &newline_type,
+ -1);
+
+ return newline_type;
+}
+
+static void
+chooser_set_current_folder (GeditFileChooserDialog *dialog,
+ GFile *folder)
+{
+ gchar *uri = NULL;
+
+ if (folder != NULL)
+ {
+ uri = g_file_get_uri (folder);
+ }
+
+ gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dialog), uri);
+ g_free (uri);
+}
+
+static void
+chooser_set_current_name (GeditFileChooserDialog *dialog,
+ const gchar *name)
+{
+ gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), name);
+}
+
+static void
+chooser_set_file (GeditFileChooserDialog *dialog,
+ GFile *file)
+{
+ gtk_file_chooser_set_file (GTK_FILE_CHOOSER (dialog), file, NULL);
+}
+
+static GFile *
+chooser_get_file (GeditFileChooserDialog *dialog)
+{
+ return gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+}
+
+static void
+chooser_set_do_overwrite_confirmation (GeditFileChooserDialog *dialog,
+ gboolean overwrite_confirmation)
+{
+ gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), overwrite_confirmation);
+}
+
+static void
+chooser_show (GeditFileChooserDialog *dialog)
+{
+ gtk_window_present (GTK_WINDOW (dialog));
+ gtk_widget_grab_focus (GTK_WIDGET (dialog));
+}
+
+static void
+chooser_destroy (GeditFileChooserDialog *dialog)
+{
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+chooser_set_modal (GeditFileChooserDialog *dialog,
+ gboolean is_modal)
+{
+ gtk_window_set_modal (GTK_WINDOW (dialog), is_modal);
+}
+
+static GtkWindow *
+chooser_get_window (GeditFileChooserDialog *dialog)
+{
+ return GTK_WINDOW (dialog);
+}
+
+static void
+gedit_file_chooser_dialog_gtk_chooser_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ GeditFileChooserDialogInterface *iface = g_iface;
+
+ iface->set_encoding = chooser_set_encoding;
+ iface->get_encoding = chooser_get_encoding;
+
+ iface->set_newline_type = chooser_set_newline_type;
+ iface->get_newline_type = chooser_get_newline_type;
+
+ iface->set_current_folder = chooser_set_current_folder;
+ iface->set_current_name = chooser_set_current_name;
+ iface->set_file = chooser_set_file;
+ iface->get_file = chooser_get_file;
+ iface->set_do_overwrite_confirmation = chooser_set_do_overwrite_confirmation;
+ iface->show = chooser_show;
+ iface->destroy = chooser_destroy;
+ iface->set_modal = chooser_set_modal;
+ iface->get_window = chooser_get_window;
+}
+
+static void
+gedit_file_chooser_dialog_gtk_dispose (GObject *object)
+{
+ GeditFileChooserDialogGtk *dialog = GEDIT_FILE_CHOOSER_DIALOG_GTK (object);
+
+ g_clear_object (&dialog->gedit_file_chooser);
+
+ G_OBJECT_CLASS (gedit_file_chooser_dialog_gtk_parent_class)->dispose (object);
+}
+
+static void
+gedit_file_chooser_dialog_gtk_class_init (GeditFileChooserDialogGtkClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gedit_file_chooser_dialog_gtk_dispose;
+}
+
+static void
+create_option_menu (GeditFileChooserDialogGtk *dialog)
+{
+ GtkWidget *label;
+ GtkWidget *menu;
+ gboolean save_mode;
+
+ label = gtk_label_new_with_mnemonic (_("C_haracter Encoding:"));
+ gtk_widget_set_halign (label, GTK_ALIGN_START);
+
+ save_mode = TRUE;
+ menu = gedit_encodings_combo_box_new (save_mode);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), menu);
+
+ gtk_box_pack_start (GTK_BOX (dialog->extra_widget),
+ label,
+ FALSE,
+ TRUE,
+ 0);
+
+ gtk_box_pack_start (GTK_BOX (dialog->extra_widget),
+ menu,
+ TRUE,
+ TRUE,
+ 0);
+
+ gtk_widget_show (label);
+ gtk_widget_show (menu);
+
+ dialog->option_menu = menu;
+}
+
+static void
+update_newline_visibility (GeditFileChooserDialogGtk *dialog)
+{
+ gboolean visible = gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)) == GTK_FILE_CHOOSER_ACTION_SAVE;
+
+ gtk_widget_set_visible (dialog->newline_label, visible);
+ gtk_widget_set_visible (dialog->newline_combo, visible);
+}
+
+static void
+newline_combo_append (GtkComboBox *combo,
+ GtkListStore *store,
+ GtkTreeIter *iter,
+ const gchar *label,
+ GtkSourceNewlineType newline_type)
+{
+ gtk_list_store_append (store, iter);
+ gtk_list_store_set (store, iter, 0, label, 1, newline_type, -1);
+
+ if (newline_type == GTK_SOURCE_NEWLINE_TYPE_DEFAULT)
+ {
+ gtk_combo_box_set_active_iter (combo, iter);
+ }
+}
+
+static void
+create_newline_combo (GeditFileChooserDialogGtk *dialog)
+{
+ GtkWidget *label, *combo;
+ GtkListStore *store;
+ GtkCellRenderer *renderer;
+ GtkTreeIter iter;
+
+ label = gtk_label_new_with_mnemonic (_("L_ine Ending:"));
+ gtk_widget_set_halign (label, GTK_ALIGN_START);
+
+ store = gtk_list_store_new (2, G_TYPE_STRING, GTK_SOURCE_TYPE_NEWLINE_TYPE);
+ combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
+ renderer = gtk_cell_renderer_text_new ();
+
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo),
+ renderer,
+ TRUE);
+
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo),
+ renderer,
+ "text",
+ 0);
+
+ newline_combo_append (GTK_COMBO_BOX (combo),
+ store,
+ &iter,
+ gedit_utils_newline_type_to_string (GTK_SOURCE_NEWLINE_TYPE_LF),
+ GTK_SOURCE_NEWLINE_TYPE_LF);
+
+ newline_combo_append (GTK_COMBO_BOX (combo),
+ store,
+ &iter,
+ gedit_utils_newline_type_to_string (GTK_SOURCE_NEWLINE_TYPE_CR),
+ GTK_SOURCE_NEWLINE_TYPE_CR);
+
+ newline_combo_append (GTK_COMBO_BOX (combo),
+ store,
+ &iter,
+ gedit_utils_newline_type_to_string (GTK_SOURCE_NEWLINE_TYPE_CR_LF),
+ GTK_SOURCE_NEWLINE_TYPE_CR_LF);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
+
+ gtk_box_pack_start (GTK_BOX (dialog->extra_widget),
+ label,
+ FALSE,
+ TRUE,
+ 0);
+
+ gtk_box_pack_start (GTK_BOX (dialog->extra_widget),
+ combo,
+ TRUE,
+ TRUE,
+ 0);
+
+ dialog->newline_combo = combo;
+ dialog->newline_label = label;
+ dialog->newline_store = store;
+
+ update_newline_visibility (dialog);
+}
+
+static void
+create_extra_widget (GeditFileChooserDialogGtk *dialog)
+{
+ dialog->extra_widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_widget_show (dialog->extra_widget);
+
+ create_option_menu (dialog);
+ create_newline_combo (dialog);
+
+ gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (dialog), dialog->extra_widget);
+}
+
+static void
+action_changed (GeditFileChooserDialogGtk *dialog,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ GtkFileChooserAction action;
+
+ action = gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog));
+
+ switch (action)
+ {
+ case GTK_FILE_CHOOSER_ACTION_OPEN:
+ g_object_set (dialog->option_menu,
+ "save_mode", FALSE,
+ NULL);
+ gtk_widget_show (dialog->option_menu);
+ break;
+ case GTK_FILE_CHOOSER_ACTION_SAVE:
+ g_object_set (dialog->option_menu,
+ "save_mode", TRUE,
+ NULL);
+ gtk_widget_show (dialog->option_menu);
+ break;
+ default:
+ gtk_widget_hide (dialog->option_menu);
+ }
+
+ update_newline_visibility (dialog);
+}
+
+static void
+gedit_file_chooser_dialog_gtk_init (GeditFileChooserDialogGtk *dialog)
+{
+}
+
+GeditFileChooserDialog *
+gedit_file_chooser_dialog_gtk_create (const gchar *title,
+ GtkWindow *parent,
+ const gchar *accept_label,
+ const gchar *cancel_label)
+{
+ GeditFileChooserDialogGtk *result;
+
+ result = g_object_new (GEDIT_TYPE_FILE_CHOOSER_DIALOG_GTK,
+ "title", title,
+ "local-only", FALSE,
+ "action", GTK_FILE_CHOOSER_ACTION_SAVE,
+ "select-multiple", FALSE,
+ NULL);
+
+ create_extra_widget (result);
+
+ g_signal_connect (result,
+ "notify::action",
+ G_CALLBACK (action_changed),
+ NULL);
+
+ /* FIXME: attention, there is a ref cycle here. This will be fixed when
+ * GeditFileChooserSave will be created (and this class removed).
+ */
+ result->gedit_file_chooser = _gedit_file_chooser_new ();
+ _gedit_file_chooser_set_gtk_file_chooser (result->gedit_file_chooser,
+ GTK_FILE_CHOOSER (result));
+
+ if (parent != NULL)
+ {
+ gtk_window_set_transient_for (GTK_WINDOW (result), parent);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (result), TRUE);
+ }
+
+ gtk_dialog_add_button (GTK_DIALOG (result), cancel_label, GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (GTK_DIALOG (result), accept_label, GTK_RESPONSE_ACCEPT);
+ gtk_dialog_set_default_response (GTK_DIALOG (result), GTK_RESPONSE_ACCEPT);
+
+ return GEDIT_FILE_CHOOSER_DIALOG (result);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-file-chooser-dialog-gtk.h b/gedit/gedit-file-chooser-dialog-gtk.h
new file mode 100644
index 0000000..0bf6a0d
--- /dev/null
+++ b/gedit/gedit-file-chooser-dialog-gtk.h
@@ -0,0 +1,45 @@
+/*
+ * gedit-file-chooser-dialog-gtk.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_FILE_CHOOSER_DIALOG_GTK_H
+#define GEDIT_FILE_CHOOSER_DIALOG_GTK_H
+
+#include <gtk/gtk.h>
+#include "gedit-file-chooser-dialog.h"
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_CHOOSER_DIALOG_GTK (gedit_file_chooser_dialog_gtk_get_type ())
+
+G_DECLARE_FINAL_TYPE (GeditFileChooserDialogGtk, gedit_file_chooser_dialog_gtk,
+ GEDIT, FILE_CHOOSER_DIALOG_GTK,
+ GtkFileChooserDialog)
+
+GeditFileChooserDialog * gedit_file_chooser_dialog_gtk_create (const gchar *title,
+ GtkWindow *parent,
+ const gchar *accept_label,
+ const gchar *cancel_label);
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_CHOOSER_DIALOG_GTK_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-file-chooser-dialog.c b/gedit/gedit-file-chooser-dialog.c
new file mode 100644
index 0000000..56edbae
--- /dev/null
+++ b/gedit/gedit-file-chooser-dialog.c
@@ -0,0 +1,258 @@
+/*
+ * gedit-app-file-chooser-dialog.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 Jesse van den Kieboom
+ *
+ * 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-file-chooser-dialog.h"
+#include "gedit-file-chooser-dialog-gtk.h"
+
+G_DEFINE_INTERFACE (GeditFileChooserDialog, gedit_file_chooser_dialog, G_TYPE_OBJECT)
+
+static gboolean
+confirm_overwrite_accumulator (GSignalInvocationHint *ihint,
+ GValue *return_accu,
+ const GValue *handler_return,
+ gpointer dummy)
+{
+ gboolean continue_emission;
+ GtkFileChooserConfirmation conf;
+
+ conf = g_value_get_enum (handler_return);
+ g_value_set_enum (return_accu, conf);
+ continue_emission = (conf == GTK_FILE_CHOOSER_CONFIRMATION_CONFIRM);
+
+ return continue_emission;
+}
+
+static void
+gedit_file_chooser_dialog_default_init (GeditFileChooserDialogInterface *iface)
+{
+ g_signal_new ("response",
+ G_TYPE_FROM_INTERFACE (iface),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_INT);
+
+ g_signal_new ("confirm-overwrite",
+ G_TYPE_FROM_INTERFACE (iface),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ confirm_overwrite_accumulator, NULL, NULL,
+ GTK_TYPE_FILE_CHOOSER_CONFIRMATION,
+ 0);
+}
+
+GeditFileChooserDialog *
+gedit_file_chooser_dialog_create (const gchar *title,
+ GtkWindow *parent,
+ const gchar *accept_label,
+ const gchar *cancel_label)
+{
+ return gedit_file_chooser_dialog_gtk_create (title,
+ parent,
+ accept_label,
+ cancel_label);
+}
+
+void
+gedit_file_chooser_dialog_set_encoding (GeditFileChooserDialog *dialog,
+ const GtkSourceEncoding *encoding)
+{
+ GeditFileChooserDialogInterface *iface;
+
+ g_return_if_fail (GEDIT_IS_FILE_CHOOSER_DIALOG (dialog));
+
+ iface = GEDIT_FILE_CHOOSER_DIALOG_GET_IFACE (dialog);
+ g_return_if_fail (iface->set_encoding != NULL);
+
+ iface->set_encoding (dialog, encoding);
+}
+
+const GtkSourceEncoding *
+gedit_file_chooser_dialog_get_encoding (GeditFileChooserDialog *dialog)
+{
+ GeditFileChooserDialogInterface *iface;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_CHOOSER_DIALOG (dialog), NULL);
+
+ iface = GEDIT_FILE_CHOOSER_DIALOG_GET_IFACE (dialog);
+ g_return_val_if_fail (iface->get_encoding != NULL, NULL);
+
+ return iface->get_encoding (dialog);
+}
+
+void
+gedit_file_chooser_dialog_set_newline_type (GeditFileChooserDialog *dialog,
+ GtkSourceNewlineType newline_type)
+{
+ GeditFileChooserDialogInterface *iface;
+
+ g_return_if_fail (GEDIT_IS_FILE_CHOOSER_DIALOG (dialog));
+
+ iface = GEDIT_FILE_CHOOSER_DIALOG_GET_IFACE (dialog);
+ g_return_if_fail (iface->set_newline_type != NULL);
+
+ iface->set_newline_type (dialog, newline_type);
+}
+
+GtkSourceNewlineType
+gedit_file_chooser_dialog_get_newline_type (GeditFileChooserDialog *dialog)
+{
+ GeditFileChooserDialogInterface *iface;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_CHOOSER_DIALOG (dialog), GTK_SOURCE_NEWLINE_TYPE_DEFAULT);
+
+ iface = GEDIT_FILE_CHOOSER_DIALOG_GET_IFACE (dialog);
+ g_return_val_if_fail (iface->get_newline_type != NULL, GTK_SOURCE_NEWLINE_TYPE_DEFAULT);
+
+ return iface->get_newline_type (dialog);
+}
+
+
+void
+gedit_file_chooser_dialog_set_current_folder (GeditFileChooserDialog *dialog,
+ GFile *folder)
+{
+ GeditFileChooserDialogInterface *iface;
+
+ g_return_if_fail (GEDIT_IS_FILE_CHOOSER_DIALOG (dialog));
+
+ iface = GEDIT_FILE_CHOOSER_DIALOG_GET_IFACE (dialog);
+ g_return_if_fail (iface->set_current_folder != NULL);
+
+ iface->set_current_folder (dialog, folder);
+}
+
+void
+gedit_file_chooser_dialog_set_current_name (GeditFileChooserDialog *dialog,
+ const gchar *name)
+{
+ GeditFileChooserDialogInterface *iface;
+
+ g_return_if_fail (GEDIT_IS_FILE_CHOOSER_DIALOG (dialog));
+
+ iface = GEDIT_FILE_CHOOSER_DIALOG_GET_IFACE (dialog);
+ g_return_if_fail (iface->set_current_name != NULL);
+
+ iface->set_current_name (dialog, name);
+}
+
+void
+gedit_file_chooser_dialog_set_file (GeditFileChooserDialog *dialog,
+ GFile *file)
+{
+ GeditFileChooserDialogInterface *iface;
+
+ g_return_if_fail (GEDIT_IS_FILE_CHOOSER_DIALOG (dialog));
+ g_return_if_fail (file == NULL || G_IS_FILE (file));
+
+ iface = GEDIT_FILE_CHOOSER_DIALOG_GET_IFACE (dialog);
+ g_return_if_fail (iface->set_file != NULL);
+
+ iface->set_file (dialog, file);
+}
+
+GFile *
+gedit_file_chooser_dialog_get_file (GeditFileChooserDialog *dialog)
+{
+ GeditFileChooserDialogInterface *iface;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_CHOOSER_DIALOG (dialog), NULL);
+
+ iface = GEDIT_FILE_CHOOSER_DIALOG_GET_IFACE (dialog);
+ g_return_val_if_fail (iface->get_file != NULL, NULL);
+
+ return iface->get_file (dialog);
+}
+
+void
+gedit_file_chooser_dialog_set_do_overwrite_confirmation (GeditFileChooserDialog *dialog,
+ gboolean overwrite_confirmation)
+{
+ GeditFileChooserDialogInterface *iface;
+
+ g_return_if_fail (GEDIT_IS_FILE_CHOOSER_DIALOG (dialog));
+
+ iface = GEDIT_FILE_CHOOSER_DIALOG_GET_IFACE (dialog);
+ g_return_if_fail (iface->set_do_overwrite_confirmation != NULL);
+
+ iface->set_do_overwrite_confirmation (dialog, overwrite_confirmation);
+}
+
+void
+gedit_file_chooser_dialog_show (GeditFileChooserDialog *dialog)
+{
+ GeditFileChooserDialogInterface *iface;
+
+ g_return_if_fail (GEDIT_IS_FILE_CHOOSER_DIALOG (dialog));
+
+ iface = GEDIT_FILE_CHOOSER_DIALOG_GET_IFACE (dialog);
+ g_return_if_fail (iface->show != NULL);
+
+ iface->show (dialog);
+}
+
+void
+gedit_file_chooser_dialog_destroy (GeditFileChooserDialog *dialog)
+{
+ GeditFileChooserDialogInterface *iface;
+
+ g_return_if_fail (GEDIT_IS_FILE_CHOOSER_DIALOG (dialog));
+
+ iface = GEDIT_FILE_CHOOSER_DIALOG_GET_IFACE (dialog);
+ g_return_if_fail (iface->destroy != NULL);
+
+ iface->destroy (dialog);
+}
+
+void
+gedit_file_chooser_dialog_set_modal (GeditFileChooserDialog *dialog,
+ gboolean is_modal)
+{
+ GeditFileChooserDialogInterface *iface;
+
+ g_return_if_fail (GEDIT_IS_FILE_CHOOSER_DIALOG (dialog));
+
+ iface = GEDIT_FILE_CHOOSER_DIALOG_GET_IFACE (dialog);
+ g_return_if_fail (iface->set_modal != NULL);
+
+ iface->set_modal (dialog, is_modal);
+}
+
+GtkWindow *
+gedit_file_chooser_dialog_get_window (GeditFileChooserDialog *dialog)
+{
+ GeditFileChooserDialogInterface *iface;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_CHOOSER_DIALOG (dialog), NULL);
+
+ iface = GEDIT_FILE_CHOOSER_DIALOG_GET_IFACE (dialog);
+
+ if (iface->get_window)
+ {
+ return iface->get_window (dialog);
+ }
+
+ return NULL;
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-file-chooser-dialog.h b/gedit/gedit-file-chooser-dialog.h
new file mode 100644
index 0000000..b759c65
--- /dev/null
+++ b/gedit/gedit-file-chooser-dialog.h
@@ -0,0 +1,123 @@
+/*
+ * gedit-file-chooser-dialog.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_FILE_CHOOSER_DIALOG_H
+#define GEDIT_FILE_CHOOSER_DIALOG_H
+
+#include <gtksourceview/gtksource.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_CHOOSER_DIALOG (gedit_file_chooser_dialog_get_type ())
+
+G_DECLARE_INTERFACE (GeditFileChooserDialog, gedit_file_chooser_dialog,
+ GEDIT, FILE_CHOOSER_DIALOG,
+ GObject)
+
+struct _GeditFileChooserDialogInterface
+{
+ GTypeInterface g_iface;
+
+ /* Virtual public methods */
+ void (*set_encoding) (GeditFileChooserDialog *dialog,
+ const GtkSourceEncoding *encoding);
+
+ const GtkSourceEncoding *
+ (*get_encoding) (GeditFileChooserDialog *dialog);
+
+ void (*set_newline_type) (GeditFileChooserDialog *dialog,
+ GtkSourceNewlineType newline_type);
+
+ GtkSourceNewlineType
+ (*get_newline_type) (GeditFileChooserDialog *dialog);
+
+ void (*set_current_folder) (GeditFileChooserDialog *dialog,
+ GFile *folder);
+
+ void (*set_current_name) (GeditFileChooserDialog *dialog,
+ const gchar *name);
+
+ void (*set_file) (GeditFileChooserDialog *dialog,
+ GFile *file);
+
+ GFile * (*get_file) (GeditFileChooserDialog *dialog);
+
+ void (*set_do_overwrite_confirmation)
+ (GeditFileChooserDialog *dialog,
+ gboolean overwrite_confirmation);
+
+ void (*show) (GeditFileChooserDialog *dialog);
+
+ void (*destroy) (GeditFileChooserDialog *dialog);
+
+ void (*set_modal) (GeditFileChooserDialog *dialog,
+ gboolean is_modal);
+
+ GtkWindow *
+ (*get_window) (GeditFileChooserDialog *dialog);
+};
+
+GeditFileChooserDialog *
+ gedit_file_chooser_dialog_create (const gchar *title,
+ GtkWindow *parent,
+ const gchar *accept_label,
+ const gchar *cancel_label);
+
+void gedit_file_chooser_dialog_destroy (GeditFileChooserDialog *dialog);
+
+void gedit_file_chooser_dialog_set_encoding (GeditFileChooserDialog *dialog,
+ const GtkSourceEncoding *encoding);
+
+const GtkSourceEncoding *
+ gedit_file_chooser_dialog_get_encoding (GeditFileChooserDialog *dialog);
+
+void gedit_file_chooser_dialog_set_newline_type (GeditFileChooserDialog *dialog,
+ GtkSourceNewlineType newline_type);
+
+GtkSourceNewlineType
+ gedit_file_chooser_dialog_get_newline_type (GeditFileChooserDialog *dialog);
+
+void gedit_file_chooser_dialog_set_current_folder (GeditFileChooserDialog *dialog,
+ GFile *folder);
+
+void gedit_file_chooser_dialog_set_current_name (GeditFileChooserDialog *dialog,
+ const gchar *name);
+
+void gedit_file_chooser_dialog_set_file (GeditFileChooserDialog *dialog,
+ GFile *file);
+
+GFile *gedit_file_chooser_dialog_get_file (GeditFileChooserDialog *dialog);
+
+void gedit_file_chooser_dialog_set_do_overwrite_confirmation (
+ GeditFileChooserDialog *dialog,
+ gboolean overwrite_confirmation);
+
+void gedit_file_chooser_dialog_show (GeditFileChooserDialog *dialog);
+
+void gedit_file_chooser_dialog_set_modal (GeditFileChooserDialog *dialog,
+ gboolean is_modal);
+
+GtkWindow *gedit_file_chooser_dialog_get_window (GeditFileChooserDialog *dialog);
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_CHOOSER_DIALOG_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-file-chooser-open-dialog.c b/gedit/gedit-file-chooser-open-dialog.c
new file mode 100644
index 0000000..1975f51
--- /dev/null
+++ b/gedit/gedit-file-chooser-open-dialog.c
@@ -0,0 +1,125 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2020 Sébastien Wilmet <swilmet@gnome.org>
+ *
+ * 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 "gedit-file-chooser-open-dialog.h"
+#include <glib/gi18n.h>
+#include "gedit-encodings-combo-box.h"
+
+/* A GtkFileChooserDialog to *open* files. */
+
+struct _GeditFileChooserOpenDialogPrivate
+{
+ GeditEncodingsComboBox *encodings_combo_box;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GeditFileChooserOpenDialog, _gedit_file_chooser_open_dialog, GEDIT_TYPE_FILE_CHOOSER_OPEN)
+
+static void
+setup_encoding_extra_widget (GeditFileChooserOpenDialog *chooser,
+ GtkFileChooser *gtk_chooser)
+{
+ GtkWidget *label;
+ GtkWidget *encodings_combo_box;
+ GtkWidget *hgrid;
+
+ g_assert (chooser->priv->encodings_combo_box == NULL);
+
+ label = gtk_label_new_with_mnemonic (_("C_haracter Encoding:"));
+ encodings_combo_box = gedit_encodings_combo_box_new (FALSE);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), encodings_combo_box);
+
+ hgrid = gtk_grid_new ();
+ gtk_grid_set_column_spacing (GTK_GRID (hgrid), 6);
+ gtk_container_add (GTK_CONTAINER (hgrid), label);
+ gtk_container_add (GTK_CONTAINER (hgrid), encodings_combo_box);
+
+ chooser->priv->encodings_combo_box = GEDIT_ENCODINGS_COMBO_BOX (encodings_combo_box);
+ g_object_ref_sink (chooser->priv->encodings_combo_box);
+
+ gtk_widget_show_all (hgrid);
+ gtk_file_chooser_set_extra_widget (gtk_chooser, hgrid);
+}
+
+static void
+_gedit_file_chooser_open_dialog_dispose (GObject *object)
+{
+ GeditFileChooserOpenDialog *chooser = GEDIT_FILE_CHOOSER_OPEN_DIALOG (object);
+
+ g_clear_object (&chooser->priv->encodings_combo_box);
+
+ G_OBJECT_CLASS (_gedit_file_chooser_open_dialog_parent_class)->dispose (object);
+}
+
+static GtkFileChooser *
+chooser_create_gtk_file_chooser (GeditFileChooser *chooser)
+{
+ GtkWidget *file_chooser;
+
+ /* Translators: "Open Files" is the title of the file chooser window. */
+ file_chooser = gtk_file_chooser_dialog_new (C_("window title", "Open Files"),
+ NULL,
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Open"), GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (file_chooser), GTK_RESPONSE_ACCEPT);
+
+ setup_encoding_extra_widget (GEDIT_FILE_CHOOSER_OPEN_DIALOG (chooser),
+ GTK_FILE_CHOOSER (file_chooser));
+
+ if (g_object_is_floating (file_chooser))
+ {
+ g_object_ref_sink (file_chooser);
+ }
+
+ return GTK_FILE_CHOOSER (file_chooser);
+}
+
+static const GtkSourceEncoding *
+chooser_get_encoding (GeditFileChooser *_chooser)
+{
+ GeditFileChooserOpenDialog *chooser = GEDIT_FILE_CHOOSER_OPEN_DIALOG (_chooser);
+
+ return gedit_encodings_combo_box_get_selected_encoding (chooser->priv->encodings_combo_box);
+}
+
+static void
+_gedit_file_chooser_open_dialog_class_init (GeditFileChooserOpenDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GeditFileChooserClass *file_chooser_class = GEDIT_FILE_CHOOSER_CLASS (klass);
+
+ object_class->dispose = _gedit_file_chooser_open_dialog_dispose;
+
+ file_chooser_class->create_gtk_file_chooser = chooser_create_gtk_file_chooser;
+ file_chooser_class->get_encoding = chooser_get_encoding;
+}
+
+static void
+_gedit_file_chooser_open_dialog_init (GeditFileChooserOpenDialog *chooser)
+{
+ chooser->priv = _gedit_file_chooser_open_dialog_get_instance_private (chooser);
+}
+
+GeditFileChooserOpen *
+_gedit_file_chooser_open_dialog_new (void)
+{
+ return g_object_new (GEDIT_TYPE_FILE_CHOOSER_OPEN_DIALOG, NULL);
+}
diff --git a/gedit/gedit-file-chooser-open-dialog.h b/gedit/gedit-file-chooser-open-dialog.h
new file mode 100644
index 0000000..d7f0d51
--- /dev/null
+++ b/gedit/gedit-file-chooser-open-dialog.h
@@ -0,0 +1,58 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2020 Sébastien Wilmet <swilmet@gnome.org>
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_FILE_CHOOSER_OPEN_DIALOG_H
+#define GEDIT_FILE_CHOOSER_OPEN_DIALOG_H
+
+#include "gedit-file-chooser-open.h"
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_CHOOSER_OPEN_DIALOG (_gedit_file_chooser_open_dialog_get_type ())
+#define GEDIT_FILE_CHOOSER_OPEN_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_CHOOSER_OPEN_DIALOG, GeditFileChooserOpenDialog))
+#define GEDIT_FILE_CHOOSER_OPEN_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_CHOOSER_OPEN_DIALOG, GeditFileChooserOpenDialogClass))
+#define GEDIT_IS_FILE_CHOOSER_OPEN_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_CHOOSER_OPEN_DIALOG))
+#define GEDIT_IS_FILE_CHOOSER_OPEN_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_CHOOSER_OPEN_DIALOG))
+#define GEDIT_FILE_CHOOSER_OPEN_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_CHOOSER_OPEN_DIALOG, GeditFileChooserOpenDialogClass))
+
+typedef struct _GeditFileChooserOpenDialog GeditFileChooserOpenDialog;
+typedef struct _GeditFileChooserOpenDialogClass GeditFileChooserOpenDialogClass;
+typedef struct _GeditFileChooserOpenDialogPrivate GeditFileChooserOpenDialogPrivate;
+
+struct _GeditFileChooserOpenDialog
+{
+ GeditFileChooserOpen parent;
+
+ GeditFileChooserOpenDialogPrivate *priv;
+};
+
+struct _GeditFileChooserOpenDialogClass
+{
+ GeditFileChooserOpenClass parent_class;
+};
+
+G_GNUC_INTERNAL
+GType _gedit_file_chooser_open_dialog_get_type (void);
+
+G_GNUC_INTERNAL
+GeditFileChooserOpen * _gedit_file_chooser_open_dialog_new (void);
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_CHOOSER_OPEN_DIALOG_H */
diff --git a/gedit/gedit-file-chooser-open-native.c b/gedit/gedit-file-chooser-open-native.c
new file mode 100644
index 0000000..3a3ba5a
--- /dev/null
+++ b/gedit/gedit-file-chooser-open-native.c
@@ -0,0 +1,84 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2020 Sébastien Wilmet <swilmet@gnome.org>
+ *
+ * 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 "gedit-file-chooser-open-native.h"
+#include <glib/gi18n.h>
+
+/* A GtkFileChooserNative to *open* files. */
+/* TODO: finish the implementation. */
+
+struct _GeditFileChooserOpenNativePrivate
+{
+ gint something;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GeditFileChooserOpenNative, _gedit_file_chooser_open_native, GEDIT_TYPE_FILE_CHOOSER_OPEN)
+
+#if 0
+static GtkNativeDialog *
+get_native_dialog (GeditFileChooserOpenNative *chooser)
+{
+ return GTK_NATIVE_DIALOG (_gedit_file_chooser_get_gtk_file_chooser (GEDIT_FILE_CHOOSER (chooser)));
+}
+#endif
+
+static void
+_gedit_file_chooser_open_native_dispose (GObject *object)
+{
+
+ G_OBJECT_CLASS (_gedit_file_chooser_open_native_parent_class)->dispose (object);
+}
+
+static GtkFileChooser *
+chooser_create_gtk_file_chooser (GeditFileChooser *chooser)
+{
+ GtkFileChooserNative *native_chooser;
+
+ /* Translators: "Open Files" is the title of the file chooser window. */
+ native_chooser = gtk_file_chooser_native_new (C_("window title", "Open Files"),
+ NULL,
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ NULL,
+ NULL);
+
+ return GTK_FILE_CHOOSER (native_chooser);
+}
+
+static void
+_gedit_file_chooser_open_native_class_init (GeditFileChooserOpenNativeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GeditFileChooserClass *chooser_class = GEDIT_FILE_CHOOSER_CLASS (klass);
+
+ object_class->dispose = _gedit_file_chooser_open_native_dispose;
+
+ chooser_class->create_gtk_file_chooser = chooser_create_gtk_file_chooser;
+}
+
+static void
+_gedit_file_chooser_open_native_init (GeditFileChooserOpenNative *chooser)
+{
+ chooser->priv = _gedit_file_chooser_open_native_get_instance_private (chooser);
+}
+
+GeditFileChooserOpen *
+_gedit_file_chooser_open_native_new (void)
+{
+ return g_object_new (GEDIT_TYPE_FILE_CHOOSER_OPEN_NATIVE, NULL);
+}
diff --git a/gedit/gedit-file-chooser-open-native.h b/gedit/gedit-file-chooser-open-native.h
new file mode 100644
index 0000000..5cac81d
--- /dev/null
+++ b/gedit/gedit-file-chooser-open-native.h
@@ -0,0 +1,58 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2020 Sébastien Wilmet <swilmet@gnome.org>
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_FILE_CHOOSER_OPEN_NATIVE_H
+#define GEDIT_FILE_CHOOSER_OPEN_NATIVE_H
+
+#include "gedit-file-chooser-open.h"
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_CHOOSER_OPEN_NATIVE (_gedit_file_chooser_open_native_get_type ())
+#define GEDIT_FILE_CHOOSER_OPEN_NATIVE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_CHOOSER_OPEN_NATIVE, GeditFileChooserOpenNative))
+#define GEDIT_FILE_CHOOSER_OPEN_NATIVE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_CHOOSER_OPEN_NATIVE, GeditFileChooserOpenNativeClass))
+#define GEDIT_IS_FILE_CHOOSER_OPEN_NATIVE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_CHOOSER_OPEN_NATIVE))
+#define GEDIT_IS_FILE_CHOOSER_OPEN_NATIVE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_CHOOSER_OPEN_NATIVE))
+#define GEDIT_FILE_CHOOSER_OPEN_NATIVE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_CHOOSER_OPEN_NATIVE, GeditFileChooserOpenNativeClass))
+
+typedef struct _GeditFileChooserOpenNative GeditFileChooserOpenNative;
+typedef struct _GeditFileChooserOpenNativeClass GeditFileChooserOpenNativeClass;
+typedef struct _GeditFileChooserOpenNativePrivate GeditFileChooserOpenNativePrivate;
+
+struct _GeditFileChooserOpenNative
+{
+ GeditFileChooserOpen parent;
+
+ GeditFileChooserOpenNativePrivate *priv;
+};
+
+struct _GeditFileChooserOpenNativeClass
+{
+ GeditFileChooserOpenClass parent_class;
+};
+
+G_GNUC_INTERNAL
+GType _gedit_file_chooser_open_native_get_type (void);
+
+G_GNUC_INTERNAL
+GeditFileChooserOpen * _gedit_file_chooser_open_native_new (void);
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_CHOOSER_OPEN_NATIVE_H */
diff --git a/gedit/gedit-file-chooser-open.c b/gedit/gedit-file-chooser-open.c
new file mode 100644
index 0000000..96f0182
--- /dev/null
+++ b/gedit/gedit-file-chooser-open.c
@@ -0,0 +1,77 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2020 Sébastien Wilmet <swilmet@gnome.org>
+ *
+ * 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 "gedit-file-chooser-open.h"
+#include "gedit-file-chooser-open-dialog.h"
+#include "gedit-file-chooser-open-native.h"
+
+/* Common code for file choosers that *open* files. */
+
+G_DEFINE_TYPE (GeditFileChooserOpen, _gedit_file_chooser_open, GEDIT_TYPE_FILE_CHOOSER)
+
+static GtkFileChooser *
+get_gtk_file_chooser (GeditFileChooserOpen *chooser)
+{
+ return _gedit_file_chooser_get_gtk_file_chooser (GEDIT_FILE_CHOOSER (chooser));
+}
+
+static void
+_gedit_file_chooser_open_constructed (GObject *object)
+{
+ GeditFileChooserOpen *chooser = GEDIT_FILE_CHOOSER_OPEN (object);
+
+ if (G_OBJECT_CLASS (_gedit_file_chooser_open_parent_class)->constructed != NULL)
+ {
+ G_OBJECT_CLASS (_gedit_file_chooser_open_parent_class)->constructed (object);
+ }
+
+ gtk_file_chooser_set_select_multiple (get_gtk_file_chooser (chooser), TRUE);
+}
+
+static void
+_gedit_file_chooser_open_class_init (GeditFileChooserOpenClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = _gedit_file_chooser_open_constructed;
+}
+
+static void
+_gedit_file_chooser_open_init (GeditFileChooserOpen *chooser)
+{
+}
+
+GeditFileChooserOpen *
+_gedit_file_chooser_open_new (void)
+{
+ if (_gedit_file_chooser_is_native ())
+ {
+ return _gedit_file_chooser_open_native_new ();
+ }
+
+ return _gedit_file_chooser_open_dialog_new ();
+}
+
+GSList *
+_gedit_file_chooser_open_get_files (GeditFileChooserOpen *chooser)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_CHOOSER_OPEN (chooser), NULL);
+
+ return gtk_file_chooser_get_files (get_gtk_file_chooser (chooser));
+}
diff --git a/gedit/gedit-file-chooser-open.h b/gedit/gedit-file-chooser-open.h
new file mode 100644
index 0000000..80f1a16
--- /dev/null
+++ b/gedit/gedit-file-chooser-open.h
@@ -0,0 +1,58 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2020 Sébastien Wilmet <swilmet@gnome.org>
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_FILE_CHOOSER_OPEN_H
+#define GEDIT_FILE_CHOOSER_OPEN_H
+
+#include "gedit-file-chooser.h"
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_CHOOSER_OPEN (_gedit_file_chooser_open_get_type ())
+#define GEDIT_FILE_CHOOSER_OPEN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_CHOOSER_OPEN, GeditFileChooserOpen))
+#define GEDIT_FILE_CHOOSER_OPEN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_CHOOSER_OPEN, GeditFileChooserOpenClass))
+#define GEDIT_IS_FILE_CHOOSER_OPEN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_CHOOSER_OPEN))
+#define GEDIT_IS_FILE_CHOOSER_OPEN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_CHOOSER_OPEN))
+#define GEDIT_FILE_CHOOSER_OPEN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_CHOOSER_OPEN, GeditFileChooserOpenClass))
+
+typedef struct _GeditFileChooserOpen GeditFileChooserOpen;
+typedef struct _GeditFileChooserOpenClass GeditFileChooserOpenClass;
+
+struct _GeditFileChooserOpen
+{
+ GeditFileChooser parent;
+};
+
+struct _GeditFileChooserOpenClass
+{
+ GeditFileChooserClass parent_class;
+};
+
+G_GNUC_INTERNAL
+GType _gedit_file_chooser_open_get_type (void);
+
+G_GNUC_INTERNAL
+GeditFileChooserOpen * _gedit_file_chooser_open_new (void);
+
+G_GNUC_INTERNAL
+GSList * _gedit_file_chooser_open_get_files (GeditFileChooserOpen *chooser);
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_CHOOSER_OPEN_H */
diff --git a/gedit/gedit-file-chooser.c b/gedit/gedit-file-chooser.c
new file mode 100644
index 0000000..a5c9547
--- /dev/null
+++ b/gedit/gedit-file-chooser.c
@@ -0,0 +1,785 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2020 Sébastien Wilmet <swilmet@gnome.org>
+ *
+ * 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 "gedit-file-chooser.h"
+#include <glib/gi18n.h>
+#include "gedit-settings.h"
+
+/* Common code between the different GeditFileChooser's. */
+
+struct _GeditFileChooserPrivate
+{
+ GtkFileChooser *gtk_chooser;
+};
+
+enum
+{
+ SIGNAL_DONE,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+G_DEFINE_TYPE_WITH_PRIVATE (GeditFileChooser, _gedit_file_chooser, G_TYPE_OBJECT)
+
+#define ALL_FILES _("All Files")
+#define ALL_TEXT_FILES _("All Text Files")
+
+/* Whether to use GtkFileChooserNative or GtkFileChooserDialog. */
+gboolean
+_gedit_file_chooser_is_native (void)
+{
+ /* TODO: finish the implementation of the native variants. */
+ return FALSE;
+}
+
+static gboolean
+mime_types_are_supported (void)
+{
+/* Note that the #ifdef could be moved to where this function is called, to have
+ * much more code between the #ifdef/#else/#endif. The goal is to always compile
+ * all the code on all platforms, to catch compilation problems earlier.
+ */
+#ifdef G_OS_WIN32
+ /* See the GtkFileChooserNative documentation, a GtkFileFilter with
+ * mime-types is not supported on Windows.
+ */
+ return FALSE;
+#else
+ return TRUE;
+#endif
+}
+
+/* Returns: (transfer none) (element-type utf8): a list containing "text/plain"
+ * and "application/x-zerosize" first and then the list of mime-types unrelated
+ * to "text/plain" and not equal to "application/x-zerosize" that GtkSourceView
+ * supports for the syntax highlighting.
+ */
+static GSList *
+get_supported_mime_types (void)
+{
+ static GSList *supported_mime_types = NULL;
+ static gboolean initialized = FALSE;
+
+ GtkSourceLanguageManager *language_manager;
+ const gchar * const *language_ids;
+ gint language_num;
+
+ if (initialized)
+ {
+ return supported_mime_types;
+ }
+
+ language_manager = gtk_source_language_manager_get_default ();
+ language_ids = gtk_source_language_manager_get_language_ids (language_manager);
+ for (language_num = 0; language_ids != NULL && language_ids[language_num] != NULL; language_num++)
+ {
+ const gchar *cur_language_id = language_ids[language_num];
+ GtkSourceLanguage *language;
+ gchar **mime_types;
+ gint mime_type_num;
+
+ language = gtk_source_language_manager_get_language (language_manager, cur_language_id);
+ mime_types = gtk_source_language_get_mime_types (language);
+
+ if (mime_types == NULL)
+ {
+ continue;
+ }
+
+ for (mime_type_num = 0; mime_types[mime_type_num] != NULL; mime_type_num++)
+ {
+ const gchar *cur_mime_type = mime_types[mime_type_num];
+
+ if (!g_content_type_is_a (cur_mime_type, "text/plain") &&
+ !g_content_type_equals (cur_mime_type, "application/x-zerosize"))
+ {
+ //g_message ("Mime-type '%s' is not related to 'text/plain'", cur_mime_type);
+ supported_mime_types = g_slist_prepend (supported_mime_types,
+ g_strdup (cur_mime_type));
+ }
+ }
+
+ g_strfreev (mime_types);
+ }
+
+ supported_mime_types = g_slist_prepend (supported_mime_types, g_strdup ("application/x-zerosize"));
+
+ // Note that all "text/*" mime-types are subclasses of "text/plain", see:
+ // https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html#subclassing
+ supported_mime_types = g_slist_prepend (supported_mime_types, g_strdup ("text/plain"));
+
+ initialized = TRUE;
+ return supported_mime_types;
+}
+
+static const gchar * const *
+get_supported_globs (void)
+{
+ /* List generated with Tepl's shared-mime-info-list-text-plain-globs
+ * tool.
+ *
+ * TODO: can be improved by including globs from GtkSourceLanguage's.
+ */
+ static const gchar * const supported_globs[] =
+ {
+ "*.abw",
+ "*.adb",
+ "*.ads",
+ "*.al",
+ "*.asc",
+ "*.asp",
+ "*.ass",
+ "*.atom",
+ "*.automount",
+ "*.awk",
+ "*.bib",
+ "*.build",
+ "*.c",
+ "*.c++",
+ "*.cbl",
+ "*.cc",
+ "*.ccmx",
+ "*.cl",
+ "*.cls",
+ "*.cmake",
+ "*.cob",
+ "*.coffee",
+ "*.cpp",
+ "*.cs",
+ "*.csh",
+ "*.css",
+ "*.csv",
+ "*.csvs",
+ "*.cue",
+ "*.cxx",
+ "*.d",
+ "*.dbk",
+ "*.dcl",
+ "*.desktop",
+ "*.device",
+ "*.di",
+ "*.dia",
+ "*.diff",
+ "*.docbook",
+ "*.dot",
+ "*.dsl",
+ "*.dtd",
+ "*.dtx",
+ "*.e",
+ "*.eif",
+ "*.el",
+ "*.eml",
+ "*.ent",
+ "*.eps",
+ "*.epsf",
+ "*.epsi",
+ "*.erl",
+ "*.es",
+ "*.etx",
+ "*.f",
+ "*.f90",
+ "*.f95",
+ "*.fb2",
+ "*.feature",
+ "*.fl",
+ "*.flatpakref",
+ "*.flatpakrepo",
+ "*.fo",
+ "*.fodg",
+ "*.fodp",
+ "*.fods",
+ "*.fodt",
+ "*.for",
+ "*.gcode",
+ "*.gcrd",
+ "*.geojson",
+ "*.glade",
+ "*.gml",
+ "*.gnuplot",
+ "*.go",
+ "*.gp",
+ "*.gpg",
+ "*.gplt",
+ "*.gpx",
+ "*.gradle",
+ "*.groovy",
+ "*.gs",
+ "*.gsf",
+ "*.gsh",
+ "*.gv",
+ "*.gvp",
+ "*.gvy",
+ "*.gy",
+ "*.h",
+ "*.h++",
+ "*.hh",
+ "*.hp",
+ "*.hpp",
+ "*.hs",
+ "*.htm",
+ "*.html",
+ "*.hxx",
+ "*.ica",
+ "*.ics",
+ "*.idl",
+ "*.iges",
+ "*.igs",
+ "*.ime",
+ "*.imy",
+ "*.ins",
+ "*.iptables",
+ "*.ipynb",
+ "*.it87",
+ "*.jad",
+ "*.java",
+ "*.jnlp",
+ "*.jrd",
+ "*.js",
+ "*.jsm",
+ "*.json",
+ "*.jsonld",
+ "*.json-patch",
+ "*.kdelnk",
+ "*.key",
+ "*.kino",
+ "*.kml",
+ "*.la",
+ "*.latex",
+ "*.ldif",
+ "*.lhs",
+ "*.log",
+ "*.ltx",
+ "*.lua",
+ "*.ly",
+ "*.lyx",
+ "*.m",
+ "*.m1u",
+ "*.m3u",
+ "*.m3u8",
+ "*.m4",
+ "*.m4u",
+ "*.mab",
+ "*.mak",
+ "*.man",
+ "*.manifest",
+ "*.markdown",
+ "*.mbox",
+ "*.mc2",
+ "*.md",
+ "*.me",
+ "*.meta4",
+ "*.metalink",
+ "*.mgp",
+ "*.mjs",
+ "*.mk",
+ "*.mkd",
+ "*.ml",
+ "*.mli",
+ "*.mm",
+ "*.mml",
+ "*.mo",
+ "*.moc",
+ "*.mof",
+ "*.mount",
+ "*.mrl",
+ "*.mrml",
+ "*.ms",
+ "*.mup",
+ "*.mxu",
+ "*.nb",
+ "*.nfo",
+ "*.not",
+ "*.nzb",
+ "*.ocl",
+ "*.ooc",
+ "*.opml",
+ "*.owl",
+ "*.owx",
+ "*.p",
+ "*.p7s",
+ "*.pas",
+ "*.patch",
+ "*.path",
+ "*.perl",
+ "*.pfa",
+ "*.pfb",
+ "*.pgn",
+ "*.pgp",
+ "*.php",
+ "*.php3",
+ "*.php4",
+ "*.php5",
+ "*.phps",
+ "*.pkr",
+ "*.pl",
+ "*.pm",
+ "*.po",
+ "*.pod",
+ "*.pot",
+ "*.ps",
+ "*.py",
+ "*.py3",
+ "*.py3x",
+ "*.pyx",
+ "*.qml",
+ "*.qmlproject",
+ "*.qmltypes",
+ "*.qti",
+ "*.raml",
+ "*.rb",
+ "*.rdf",
+ "*.rdfs",
+ "*.rej",
+ "*.rnc",
+ "*.rng",
+ "*.roff",
+ "*.rs",
+ "*.rss",
+ "*.rst",
+ "*.rt",
+ "*.rtf",
+ "*.rtx",
+ "*.sami",
+ "*.sass",
+ "*.scala",
+ "*.scm",
+ "*.scope",
+ "*.scss",
+ "*.sdp",
+ "*.service",
+ "*.sgf",
+ "*.sgm",
+ "*.sgml",
+ "*.sh",
+ "*.shape",
+ "*.sig",
+ "*.siv",
+ "*.skr",
+ "*.slice",
+ "*.slk",
+ "*.smi",
+ "*.smil",
+ "*.sml",
+ "*.socket",
+ "*.spec",
+ "*.sql",
+ "*.src",
+ "*.srt",
+ "*.ss",
+ "*.ssa",
+ "*.sty",
+ "*.sub",
+ "*.sv",
+ "*.svg",
+ "*.svh",
+ "*.swap",
+ "*.sylk",
+ "*.t",
+ "*.t2t",
+ "*.target",
+ "*.tcl",
+ "*.tex",
+ "*.texi",
+ "*.texinfo",
+ "*.theme",
+ "*.timer",
+ "*.tk",
+ "*.toc",
+ "*.tr",
+ "*.trig",
+ "*.ts",
+ "*.tsv",
+ "*.ttl",
+ "*.ttx",
+ "*.twig",
+ "*.txt",
+ "*.ufraw",
+ "*.ui",
+ "*.uil",
+ "*.uue",
+ "*.v",
+ "*.vala",
+ "*.vapi",
+ "*.vcard",
+ "*.vcf",
+ "*.vcs",
+ "*.vct",
+ "*.vhd",
+ "*.vhdl",
+ "*.vlc",
+ "*.vrm",
+ "*.vrml",
+ "*.vtt",
+ "*.wml",
+ "*.wmls",
+ "*.wrl",
+ "*.wsgi",
+ "*.xbel",
+ "*.xbl",
+ "*.xht",
+ "*.xhtml",
+ "*.xlf",
+ "*.xliff",
+ "*.xmi",
+ "*.xml",
+ "*.xsd",
+ "*.xsl",
+ "*.xslfo",
+ "*.xslt",
+ "*.xspf",
+ "*.xul",
+ "*.yaml",
+ "*.yml",
+ "*.zabw",
+ NULL
+ };
+
+ return supported_globs;
+}
+
+static GtkFileFilter *
+create_all_text_files_filter (void)
+{
+ GtkFileFilter *filter;
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (filter, ALL_TEXT_FILES);
+
+ if (mime_types_are_supported ())
+ {
+ GSList *supported_mime_types;
+ GSList *l;
+
+ supported_mime_types = get_supported_mime_types ();
+ for (l = supported_mime_types; l != NULL; l = l->next)
+ {
+ const gchar *mime_type = l->data;
+
+ gtk_file_filter_add_mime_type (filter, mime_type);
+ }
+
+ /* FIXME: use globs too - Paolo (Aug. 27, 2007).
+ *
+ * Yes, use globs too - swilmet (June 2020). Get the list of
+ * globs from GtkSourceLanguage's, and add them to the filter
+ * (only those that are not covered by a previously added
+ * mime-type). Note that the list of extra globs here must be
+ * computed differently than the list of extra globs for
+ * get_supported_globs() (see the TODO comment there).
+ */
+ }
+ else
+ {
+ const gchar * const *supported_globs;
+ gint i;
+
+ supported_globs = get_supported_globs ();
+ for (i = 0; supported_globs != NULL && supported_globs[i] != NULL; i++)
+ {
+ const gchar *glob = supported_globs[i];
+
+ gtk_file_filter_add_pattern (filter, glob);
+ }
+ }
+
+ return filter;
+}
+
+static void
+notify_filter_cb (GtkFileChooser *gtk_chooser,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GtkFileFilter *filter;
+ const gchar *name;
+ gint id = 0;
+ GeditSettings *settings;
+ GSettings *file_chooser_state_settings;
+
+ /* Remember the selected filter. */
+
+ filter = gtk_file_chooser_get_filter (gtk_chooser);
+ if (filter == NULL)
+ {
+ return;
+ }
+
+ name = gtk_file_filter_get_name (filter);
+ if (g_strcmp0 (name, ALL_FILES) == 0)
+ {
+ id = 1;
+ }
+
+ settings = _gedit_settings_get_singleton ();
+ file_chooser_state_settings = _gedit_settings_peek_file_chooser_state_settings (settings);
+ g_settings_set_int (file_chooser_state_settings, GEDIT_SETTINGS_ACTIVE_FILE_FILTER, id);
+}
+
+static void
+setup_filters (GeditFileChooser *chooser)
+{
+ GeditSettings *settings;
+ GSettings *file_chooser_state_settings;
+ gint active_filter;
+ GtkFileFilter *filter;
+
+ settings = _gedit_settings_get_singleton ();
+ file_chooser_state_settings = _gedit_settings_peek_file_chooser_state_settings (settings);
+ active_filter = g_settings_get_int (file_chooser_state_settings, GEDIT_SETTINGS_ACTIVE_FILE_FILTER);
+
+ /* "All Text Files" filter */
+ filter = create_all_text_files_filter ();
+
+ g_object_ref_sink (filter);
+ gtk_file_chooser_add_filter (chooser->priv->gtk_chooser, filter);
+ if (active_filter != 1)
+ {
+ /* Use this filter if set by user and as default. */
+ gtk_file_chooser_set_filter (chooser->priv->gtk_chooser, filter);
+ }
+ g_object_unref (filter);
+
+ /* "All Files" filter */
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (filter, ALL_FILES);
+ gtk_file_filter_add_pattern (filter, "*");
+
+ g_object_ref_sink (filter);
+ gtk_file_chooser_add_filter (chooser->priv->gtk_chooser, filter);
+ if (active_filter == 1)
+ {
+ /* Use this filter if set by user. */
+ gtk_file_chooser_set_filter (chooser->priv->gtk_chooser, filter);
+ }
+ g_object_unref (filter);
+
+ g_signal_connect (chooser->priv->gtk_chooser,
+ "notify::filter",
+ G_CALLBACK (notify_filter_cb),
+ NULL);
+}
+
+/* Set the dialog as modal. It's a workaround for this bug:
+ * https://gitlab.gnome.org/GNOME/gtk/issues/2824
+ * "GtkNativeDialog: non-modal and gtk_native_dialog_show(), doesn't present the
+ * window"
+ *
+ * - For opening files: drag-and-drop files from the file chooser to the
+ * GeditWindow: OK, it still works.
+ * - Other main windows not being "blocked"/insensitive (GtkWindowGroup): OK,
+ * calling gtk_native_dialog_set_transient_for() does the right thing.
+ *
+ * Even if the above GTK bug is fixed, the file chooser can be kept modal,
+ * except if there was a good reason for not being modal (what reason(s)?).
+ */
+static void
+set_modal (GeditFileChooser *chooser)
+{
+ if (_gedit_file_chooser_is_native ())
+ {
+ gtk_native_dialog_set_modal (GTK_NATIVE_DIALOG (chooser->priv->gtk_chooser), TRUE);
+ }
+ else
+ {
+ gtk_window_set_modal (GTK_WINDOW (chooser->priv->gtk_chooser), TRUE);
+ }
+}
+
+static void
+response_cb (GtkFileChooser *gtk_chooser,
+ gint response_id,
+ GeditFileChooser *chooser)
+{
+ gboolean accept;
+
+ accept = response_id == GTK_RESPONSE_ACCEPT;
+ g_signal_emit (chooser, signals[SIGNAL_DONE], 0, accept);
+}
+
+static void
+_gedit_file_chooser_constructed (GObject *object)
+{
+ GeditFileChooser *chooser = GEDIT_FILE_CHOOSER (object);
+ GeditFileChooserClass *klass = GEDIT_FILE_CHOOSER_GET_CLASS (chooser);
+
+ if (G_OBJECT_CLASS (_gedit_file_chooser_parent_class)->constructed != NULL)
+ {
+ G_OBJECT_CLASS (_gedit_file_chooser_parent_class)->constructed (object);
+ }
+
+ if (klass->create_gtk_file_chooser != NULL)
+ {
+ g_return_if_fail (chooser->priv->gtk_chooser == NULL);
+ chooser->priv->gtk_chooser = klass->create_gtk_file_chooser (chooser);
+
+ setup_filters (chooser);
+ set_modal (chooser);
+ gtk_file_chooser_set_local_only (chooser->priv->gtk_chooser, FALSE);
+
+ g_signal_connect_object (chooser->priv->gtk_chooser,
+ "response",
+ G_CALLBACK (response_cb),
+ chooser,
+ 0);
+ }
+}
+
+static void
+_gedit_file_chooser_dispose (GObject *object)
+{
+ GeditFileChooser *chooser = GEDIT_FILE_CHOOSER (object);
+
+ if (chooser->priv->gtk_chooser != NULL)
+ {
+ if (_gedit_file_chooser_is_native ())
+ {
+ g_object_unref (chooser->priv->gtk_chooser);
+ }
+ else
+ {
+ gtk_widget_destroy (GTK_WIDGET (chooser->priv->gtk_chooser));
+ }
+
+ chooser->priv->gtk_chooser = NULL;
+ }
+
+ G_OBJECT_CLASS (_gedit_file_chooser_parent_class)->dispose (object);
+}
+
+static void
+_gedit_file_chooser_class_init (GeditFileChooserClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = _gedit_file_chooser_constructed;
+ object_class->dispose = _gedit_file_chooser_dispose;
+
+ /*
+ * GeditFileChooser::done:
+ * @chooser: the #GeditFileChooser emitting the signal.
+ * @accept: whether the response code is %GTK_RESPONSE_ACCEPT.
+ */
+ signals[SIGNAL_DONE] =
+ g_signal_new ("done",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1, G_TYPE_BOOLEAN);
+}
+
+static void
+_gedit_file_chooser_init (GeditFileChooser *chooser)
+{
+ chooser->priv = _gedit_file_chooser_get_instance_private (chooser);
+}
+
+GeditFileChooser *
+_gedit_file_chooser_new (void)
+{
+ return g_object_new (GEDIT_TYPE_FILE_CHOOSER, NULL);
+}
+
+/* TODO: this function will go away. */
+void
+_gedit_file_chooser_set_gtk_file_chooser (GeditFileChooser *chooser,
+ GtkFileChooser *gtk_chooser)
+{
+ g_return_if_fail (GEDIT_IS_FILE_CHOOSER (chooser));
+ g_return_if_fail (GTK_IS_FILE_CHOOSER (gtk_chooser));
+ g_return_if_fail (chooser->priv->gtk_chooser == NULL);
+
+ chooser->priv->gtk_chooser = g_object_ref_sink (gtk_chooser);
+ setup_filters (chooser);
+}
+
+GtkFileChooser *
+_gedit_file_chooser_get_gtk_file_chooser (GeditFileChooser *chooser)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_CHOOSER (chooser), NULL);
+ return chooser->priv->gtk_chooser;
+}
+
+void
+_gedit_file_chooser_set_transient_for (GeditFileChooser *chooser,
+ GtkWindow *parent)
+{
+ g_return_if_fail (GEDIT_IS_FILE_CHOOSER (chooser));
+ g_return_if_fail (parent == NULL || GTK_IS_WINDOW (parent));
+
+ if (_gedit_file_chooser_is_native ())
+ {
+ gtk_native_dialog_set_transient_for (GTK_NATIVE_DIALOG (chooser->priv->gtk_chooser), parent);
+ }
+ else
+ {
+ gtk_window_set_transient_for (GTK_WINDOW (chooser->priv->gtk_chooser), parent);
+
+ if (parent != NULL)
+ {
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (chooser->priv->gtk_chooser), TRUE);
+ }
+ }
+}
+
+void
+_gedit_file_chooser_show (GeditFileChooser *chooser)
+{
+ g_return_if_fail (GEDIT_IS_FILE_CHOOSER (chooser));
+
+ if (_gedit_file_chooser_is_native ())
+ {
+ gtk_native_dialog_show (GTK_NATIVE_DIALOG (chooser->priv->gtk_chooser));
+ }
+ else
+ {
+ gtk_window_present (GTK_WINDOW (chooser->priv->gtk_chooser));
+ }
+}
+
+gchar *
+_gedit_file_chooser_get_current_folder_uri (GeditFileChooser *chooser)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_CHOOSER (chooser), NULL);
+
+ return gtk_file_chooser_get_current_folder_uri (chooser->priv->gtk_chooser);
+}
+
+void
+_gedit_file_chooser_set_current_folder_uri (GeditFileChooser *chooser,
+ const gchar *uri)
+{
+ g_return_if_fail (GEDIT_IS_FILE_CHOOSER (chooser));
+
+ gtk_file_chooser_set_current_folder_uri (chooser->priv->gtk_chooser, uri);
+}
+
+const GtkSourceEncoding *
+_gedit_file_chooser_get_encoding (GeditFileChooser *chooser)
+{
+ GeditFileChooserClass *klass;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_CHOOSER (chooser), NULL);
+
+ klass = GEDIT_FILE_CHOOSER_GET_CLASS (chooser);
+ g_return_val_if_fail (klass->get_encoding != NULL, NULL);
+
+ return klass->get_encoding (chooser);
+}
diff --git a/gedit/gedit-file-chooser.h b/gedit/gedit-file-chooser.h
new file mode 100644
index 0000000..d47cb9f
--- /dev/null
+++ b/gedit/gedit-file-chooser.h
@@ -0,0 +1,91 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2020 Sébastien Wilmet <swilmet@gnome.org>
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_FILE_CHOOSER_H
+#define GEDIT_FILE_CHOOSER_H
+
+#include <gtksourceview/gtksource.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_CHOOSER (_gedit_file_chooser_get_type ())
+#define GEDIT_FILE_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_CHOOSER, GeditFileChooser))
+#define GEDIT_FILE_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_CHOOSER, GeditFileChooserClass))
+#define GEDIT_IS_FILE_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_CHOOSER))
+#define GEDIT_IS_FILE_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_CHOOSER))
+#define GEDIT_FILE_CHOOSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_CHOOSER, GeditFileChooserClass))
+
+typedef struct _GeditFileChooser GeditFileChooser;
+typedef struct _GeditFileChooserClass GeditFileChooserClass;
+typedef struct _GeditFileChooserPrivate GeditFileChooserPrivate;
+
+struct _GeditFileChooser
+{
+ GObject parent;
+
+ GeditFileChooserPrivate *priv;
+};
+
+struct _GeditFileChooserClass
+{
+ GObjectClass parent_class;
+
+ /* Returns: (transfer full). */
+ GtkFileChooser * (* create_gtk_file_chooser) (GeditFileChooser *chooser);
+
+ const GtkSourceEncoding * (* get_encoding) (GeditFileChooser *chooser);
+};
+
+G_GNUC_INTERNAL
+gboolean _gedit_file_chooser_is_native (void);
+
+G_GNUC_INTERNAL
+GType _gedit_file_chooser_get_type (void);
+
+G_GNUC_INTERNAL
+GeditFileChooser * _gedit_file_chooser_new (void);
+
+G_GNUC_INTERNAL
+void _gedit_file_chooser_set_gtk_file_chooser (GeditFileChooser *chooser,
+ GtkFileChooser *gtk_chooser);
+
+G_GNUC_INTERNAL
+GtkFileChooser * _gedit_file_chooser_get_gtk_file_chooser (GeditFileChooser *chooser);
+
+G_GNUC_INTERNAL
+void _gedit_file_chooser_set_transient_for (GeditFileChooser *chooser,
+ GtkWindow *parent);
+
+G_GNUC_INTERNAL
+void _gedit_file_chooser_show (GeditFileChooser *chooser);
+
+G_GNUC_INTERNAL
+gchar * _gedit_file_chooser_get_current_folder_uri (GeditFileChooser *chooser);
+
+G_GNUC_INTERNAL
+void _gedit_file_chooser_set_current_folder_uri (GeditFileChooser *chooser,
+ const gchar *uri);
+
+G_GNUC_INTERNAL
+const GtkSourceEncoding *
+ _gedit_file_chooser_get_encoding (GeditFileChooser *chooser);
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_CHOOSER_H */
diff --git a/gedit/gedit-history-entry.c b/gedit/gedit-history-entry.c
new file mode 100644
index 0000000..e241641
--- /dev/null
+++ b/gedit/gedit-history-entry.c
@@ -0,0 +1,470 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2006 - Paolo Borelli
+ *
+ * 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-history-entry.h"
+#include <string.h>
+#include <glib/gi18n.h>
+
+#define MIN_ITEM_LEN 3
+#define HISTORY_LENGTH_DEFAULT 10
+
+struct _GeditHistoryEntry
+{
+ GtkComboBoxText parent_instance;
+
+ gchar *history_id;
+ guint history_length;
+
+ GtkEntryCompletion *completion;
+
+ GSettings *settings;
+};
+
+enum
+{
+ PROP_0,
+ PROP_HISTORY_ID,
+ PROP_HISTORY_LENGTH,
+ PROP_ENABLE_COMPLETION,
+ N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES];
+
+G_DEFINE_TYPE (GeditHistoryEntry, gedit_history_entry, GTK_TYPE_COMBO_BOX_TEXT)
+
+static void
+gedit_history_entry_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *spec)
+{
+ GeditHistoryEntry *entry = GEDIT_HISTORY_ENTRY (object);
+
+ switch (prop_id)
+ {
+ case PROP_HISTORY_ID:
+ entry->history_id = g_value_dup_string (value);
+ break;
+
+ case PROP_HISTORY_LENGTH:
+ gedit_history_entry_set_history_length (entry, g_value_get_uint (value));
+ break;
+
+ case PROP_ENABLE_COMPLETION:
+ gedit_history_entry_set_enable_completion (entry, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, spec);
+ }
+}
+
+static void
+gedit_history_entry_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *spec)
+{
+ GeditHistoryEntry *entry = GEDIT_HISTORY_ENTRY (object);
+
+ switch (prop_id)
+ {
+ case PROP_HISTORY_ID:
+ g_value_set_string (value, entry->history_id);
+ break;
+
+ case PROP_HISTORY_LENGTH:
+ g_value_set_uint (value, gedit_history_entry_get_history_length (entry));
+ break;
+
+ case PROP_ENABLE_COMPLETION:
+ g_value_set_boolean (value, gedit_history_entry_get_enable_completion (entry));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, spec);
+ }
+}
+
+static void
+gedit_history_entry_dispose (GObject *object)
+{
+ GeditHistoryEntry *entry = GEDIT_HISTORY_ENTRY (object);
+
+ gedit_history_entry_set_enable_completion (entry, FALSE);
+
+ g_clear_object (&entry->settings);
+
+ G_OBJECT_CLASS (gedit_history_entry_parent_class)->dispose (object);
+}
+
+static void
+gedit_history_entry_finalize (GObject *object)
+{
+ GeditHistoryEntry *entry = GEDIT_HISTORY_ENTRY (object);
+
+ g_free (entry->history_id);
+
+ G_OBJECT_CLASS (gedit_history_entry_parent_class)->finalize (object);
+}
+
+static void
+gedit_history_entry_load_history (GeditHistoryEntry *entry)
+{
+ gchar **items;
+ gsize i;
+
+ items = g_settings_get_strv (entry->settings, entry->history_id);
+ i = 0;
+
+ gtk_combo_box_text_remove_all (GTK_COMBO_BOX_TEXT (entry));
+
+ /* Now the default value is an empty string so we have to take care
+ of it to not add the empty string in the search list */
+ while (items[i] != NULL && *items[i] != '\0' &&
+ i < entry->history_length)
+ {
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (entry), items[i]);
+ i++;
+ }
+
+ g_strfreev (items);
+}
+
+static void
+gedit_history_entry_class_init (GeditHistoryEntryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gedit_history_entry_set_property;
+ object_class->get_property = gedit_history_entry_get_property;
+ object_class->dispose = gedit_history_entry_dispose;
+ object_class->finalize = gedit_history_entry_finalize;
+
+ properties[PROP_HISTORY_ID] =
+ g_param_spec_string ("history-id",
+ "history-id",
+ "",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_HISTORY_LENGTH] =
+ g_param_spec_uint ("history-length",
+ "history-length",
+ "",
+ 0,
+ G_MAXUINT,
+ HISTORY_LENGTH_DEFAULT,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_ENABLE_COMPLETION] =
+ g_param_spec_boolean ("enable-completion",
+ "enable-completion",
+ "",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+}
+
+static GtkListStore *
+get_history_store (GeditHistoryEntry *entry)
+{
+ GtkTreeModel *store;
+
+ store = gtk_combo_box_get_model (GTK_COMBO_BOX (entry));
+ g_return_val_if_fail (GTK_IS_LIST_STORE (store), NULL);
+
+ return (GtkListStore *) store;
+}
+
+static gchar **
+get_history_items (GeditHistoryEntry *entry)
+{
+ GtkListStore *store;
+ GtkTreeIter iter;
+ GPtrArray *array;
+ gboolean valid;
+ gint n_children;
+ gint text_column;
+
+ store = get_history_store (entry);
+ text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (entry));
+
+ valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store),
+ &iter);
+ n_children = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store),
+ NULL);
+
+ array = g_ptr_array_sized_new (n_children + 1);
+
+ while (valid)
+ {
+ gchar *str;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (store),
+ &iter,
+ text_column, &str,
+ -1);
+
+ g_ptr_array_add (array, str);
+
+ valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store),
+ &iter);
+ }
+
+ g_ptr_array_add (array, NULL);
+
+ return (gchar **)g_ptr_array_free (array, FALSE);
+}
+
+static void
+gedit_history_entry_save_history (GeditHistoryEntry *entry)
+{
+ gchar **items;
+
+ g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (entry));
+
+ items = get_history_items (entry);
+
+ g_settings_set_strv (entry->settings,
+ entry->history_id,
+ (const gchar * const *)items);
+
+ g_strfreev (items);
+}
+
+static gboolean
+remove_item (GeditHistoryEntry *entry,
+ const gchar *text)
+{
+ GtkListStore *store;
+ GtkTreeIter iter;
+ gint text_column;
+
+ g_return_val_if_fail (text != NULL, FALSE);
+
+ store = get_history_store (entry);
+ text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (entry));
+
+ if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
+ return FALSE;
+
+ do
+ {
+ gchar *item_text;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (store),
+ &iter,
+ text_column,
+ &item_text,
+ -1);
+
+ if (item_text != NULL &&
+ strcmp (item_text, text) == 0)
+ {
+ gtk_list_store_remove (store, &iter);
+ g_free (item_text);
+ return TRUE;
+ }
+
+ g_free (item_text);
+
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
+
+ return FALSE;
+}
+
+static void
+clamp_list_store (GtkListStore *store,
+ guint max)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ /* -1 because TreePath counts from 0 */
+ path = gtk_tree_path_new_from_indices (max - 1, -1);
+
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path))
+ {
+ while (TRUE)
+ {
+ if (!gtk_list_store_remove (store, &iter))
+ break;
+ }
+ }
+
+ gtk_tree_path_free (path);
+}
+
+void
+gedit_history_entry_prepend_text (GeditHistoryEntry *entry,
+ const gchar *text)
+{
+ GtkListStore *store;
+
+ g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (entry));
+ g_return_if_fail (text != NULL);
+
+ if (g_utf8_strlen (text, -1) <= MIN_ITEM_LEN)
+ {
+ return;
+ }
+
+ store = get_history_store (entry);
+
+ if (!remove_item (entry, text))
+ {
+ clamp_list_store (store, entry->history_length - 1);
+ }
+
+ gtk_combo_box_text_prepend_text (GTK_COMBO_BOX_TEXT (entry), text);
+ gedit_history_entry_save_history (entry);
+}
+
+static void
+gedit_history_entry_init (GeditHistoryEntry *entry)
+{
+ entry->history_id = NULL;
+ entry->history_length = HISTORY_LENGTH_DEFAULT;
+
+ entry->completion = NULL;
+
+ entry->settings = g_settings_new ("org.gnome.gedit.state.history-entry");
+}
+
+void
+gedit_history_entry_set_history_length (GeditHistoryEntry *entry,
+ guint history_length)
+{
+ g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (entry));
+ g_return_if_fail (history_length > 0);
+
+ entry->history_length = history_length;
+
+ /* TODO: update if we currently have more items than max */
+}
+
+guint
+gedit_history_entry_get_history_length (GeditHistoryEntry *entry)
+{
+ g_return_val_if_fail (GEDIT_IS_HISTORY_ENTRY (entry), 0);
+
+ return entry->history_length;
+}
+
+void
+gedit_history_entry_set_enable_completion (GeditHistoryEntry *entry,
+ gboolean enable)
+{
+ g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (entry));
+
+ if (enable)
+ {
+ if (entry->completion != NULL)
+ {
+ return;
+ }
+
+ entry->completion = gtk_entry_completion_new ();
+ gtk_entry_completion_set_model (entry->completion,
+ GTK_TREE_MODEL (get_history_store (entry)));
+
+ /* Use model column 0 as the text column */
+ gtk_entry_completion_set_text_column (entry->completion, 0);
+
+ gtk_entry_completion_set_minimum_key_length (entry->completion,
+ MIN_ITEM_LEN);
+
+ gtk_entry_completion_set_popup_completion (entry->completion, FALSE);
+ gtk_entry_completion_set_inline_completion (entry->completion, TRUE);
+
+ /* Assign the completion to the entry */
+ gtk_entry_set_completion (GTK_ENTRY (gedit_history_entry_get_entry (entry)),
+ entry->completion);
+ }
+ else
+ {
+ if (entry->completion == NULL)
+ {
+ return;
+ }
+
+ gtk_entry_set_completion (GTK_ENTRY (gedit_history_entry_get_entry (entry)), NULL);
+ g_clear_object (&entry->completion);
+ }
+}
+
+gboolean
+gedit_history_entry_get_enable_completion (GeditHistoryEntry *entry)
+{
+ g_return_val_if_fail (GEDIT_IS_HISTORY_ENTRY (entry), FALSE);
+
+ return entry->completion != NULL;
+}
+
+GtkWidget *
+gedit_history_entry_new (const gchar *history_id,
+ gboolean enable_completion)
+{
+ GeditHistoryEntry *entry;
+
+ g_return_val_if_fail (history_id != NULL, NULL);
+
+ enable_completion = (enable_completion != FALSE);
+
+ entry = g_object_new (GEDIT_TYPE_HISTORY_ENTRY,
+ "has-entry", TRUE,
+ "entry-text-column", 0,
+ "id-column", 1,
+ "history-id", history_id,
+ "enable-completion", enable_completion,
+ NULL);
+
+ /* We must load the history after the object has been constructed,
+ * to ensure that the model is set properly.
+ */
+ gedit_history_entry_load_history (entry);
+
+ return GTK_WIDGET (entry);
+}
+
+/*
+ * Utility function to get the editable text entry internal widget.
+ * I would prefer to not expose this implementation detail and
+ * simply make the GeditHistoryEntry widget implement the
+ * GtkEditable interface. Unfortunately both GtkEditable and
+ * GtkComboBox have a "changed" signal and I am not sure how to
+ * handle the conflict.
+ */
+GtkWidget *
+gedit_history_entry_get_entry (GeditHistoryEntry *entry)
+{
+ g_return_val_if_fail (GEDIT_IS_HISTORY_ENTRY (entry), NULL);
+
+ return gtk_bin_get_child (GTK_BIN (entry));
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-history-entry.h b/gedit/gedit-history-entry.h
new file mode 100644
index 0000000..4d76322
--- /dev/null
+++ b/gedit/gedit-history-entry.h
@@ -0,0 +1,55 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2006 - Paolo Borelli
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_HISTORY_ENTRY_H
+#define GEDIT_HISTORY_ENTRY_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_HISTORY_ENTRY (gedit_history_entry_get_type ())
+
+G_DECLARE_FINAL_TYPE (GeditHistoryEntry, gedit_history_entry,
+ GEDIT, HISTORY_ENTRY,
+ GtkComboBoxText)
+
+GtkWidget * gedit_history_entry_new (const gchar *history_id,
+ gboolean enable_completion);
+
+void gedit_history_entry_prepend_text (GeditHistoryEntry *entry,
+ const gchar *text);
+
+void gedit_history_entry_set_history_length (GeditHistoryEntry *entry,
+ guint max_saved);
+
+guint gedit_history_entry_get_history_length (GeditHistoryEntry *entry);
+
+void gedit_history_entry_set_enable_completion (GeditHistoryEntry *entry,
+ gboolean enable);
+
+gboolean gedit_history_entry_get_enable_completion (GeditHistoryEntry *entry);
+
+GtkWidget * gedit_history_entry_get_entry (GeditHistoryEntry *entry);
+
+G_END_DECLS
+
+#endif /* GEDIT_HISTORY_ENTRY_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-io-error-info-bar.c b/gedit/gedit-io-error-info-bar.c
new file mode 100644
index 0000000..8acb332
--- /dev/null
+++ b/gedit/gedit-io-error-info-bar.c
@@ -0,0 +1,542 @@
+/*
+ * 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 "gedit-io-error-info-bar.h"
+#include <tepl/tepl.h>
+#include <glib/gi18n.h>
+#include "gedit-encodings-combo-box.h"
+
+/* Verbose error reporting for file I/O operations (load, save, revert).
+ *
+ * For testing, not all I/O errors are easy to reproduce. Here is some
+ * documentation:
+ *
+ * G_IO_ERROR_NOT_REGULAR_FILE:
+ * Open for example /dev/null from the command line.
+ *
+ * G_IO_ERROR_IS_DIRECTORY:
+ * The easiest is from the command line.
+ *
+ * G_IO_ERROR_TIMED_OUT:
+ * For example configure your router to add an http(s) domain on the block list
+ * in the firewall. Then pass on the command line such an https:// address
+ * (normally GIO/GVfs is able to open https:// files) and wait 2 min.
+ * There are also probably online services that mock that behavior.
+ */
+
+static gboolean
+is_recoverable_error (const GError *error)
+{
+ if (error->domain == G_IO_ERROR)
+ {
+ switch (error->code)
+ {
+ case G_IO_ERROR_PERMISSION_DENIED:
+ case G_IO_ERROR_NOT_FOUND:
+ case G_IO_ERROR_HOST_NOT_FOUND:
+ case G_IO_ERROR_TIMED_OUT:
+ case G_IO_ERROR_NOT_MOUNTABLE_FILE:
+ case G_IO_ERROR_NOT_MOUNTED:
+ case G_IO_ERROR_BUSY:
+ return TRUE;
+
+ default:
+ break;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+is_gio_error (const GError *error,
+ gint code)
+{
+ return error->domain == G_IO_ERROR && error->code == code;
+}
+
+static GtkWidget *
+create_io_loading_error_info_bar (const gchar *primary_msg,
+ const gchar *secondary_msg,
+ gboolean recoverable_error)
+{
+ TeplInfoBar *info_bar;
+
+ info_bar = tepl_info_bar_new_simple (GTK_MESSAGE_ERROR,
+ primary_msg,
+ secondary_msg);
+
+ if (recoverable_error)
+ {
+ /* Since there are several buttons, don't use
+ * gtk_info_bar_set_show_close_button().
+ */
+ gtk_info_bar_add_button (GTK_INFO_BAR (info_bar),
+ _("_Retry"),
+ GTK_RESPONSE_OK);
+
+ gtk_info_bar_add_button (GTK_INFO_BAR (info_bar),
+ _("_Cancel"),
+ GTK_RESPONSE_CLOSE);
+ }
+ else
+ {
+ gtk_info_bar_set_show_close_button (GTK_INFO_BAR (info_bar), TRUE);
+ }
+
+ return GTK_WIDGET (info_bar);
+}
+
+static void
+get_detailed_error_messages (GFile *location,
+ const gchar *uri,
+ const GError *error,
+ gchar **primary_msg,
+ gchar **secondary_msg)
+{
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+ {
+ *secondary_msg = g_strdup (_("File not found."));
+ }
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
+ {
+ gchar *uri_scheme = NULL;
+
+ if (location != NULL)
+ {
+ uri_scheme = g_file_get_uri_scheme (location);
+ }
+
+ if (uri_scheme != NULL &&
+ g_utf8_validate (uri_scheme, -1, NULL))
+ {
+ /* How to reproduce this case: from the command line,
+ * try to open a URI such as: foo://example.net/file
+ */
+
+ /* Translators: %s is a URI scheme (like for example http:, ftp:, etc.) */
+ *secondary_msg = g_strdup_printf (_("“%s:” locations are not supported."),
+ uri_scheme);
+ }
+
+ g_free (uri_scheme);
+ }
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTABLE_FILE) ||
+ g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED))
+ {
+ *secondary_msg = g_strdup (_("The location of the file cannot be accessed."));
+ }
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME))
+ {
+ *primary_msg = g_strdup_printf (_("“%s” is not a valid location."), uri);
+ *secondary_msg = g_strdup (_("Please check that you typed the "
+ "location correctly and try again."));
+ }
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND))
+ {
+ /* How to reproduce this case: from the command line, try to
+ * open a URI such as: ssh://something-that-does-not-exist/file
+ *
+ * But this case is also hit for legitimate network addresses
+ * when the proxy is set up wrong.
+ */
+ gchar *file_uri = NULL;
+ gchar *hn = NULL;
+ gboolean uri_decoded = FALSE;
+
+ if (location != NULL)
+ {
+ file_uri = g_file_get_uri (location);
+ }
+
+ if (file_uri != NULL)
+ {
+ uri_decoded = tepl_utils_decode_uri (file_uri, NULL, NULL, &hn, NULL, NULL);
+ }
+
+ if (uri_decoded && hn != NULL)
+ {
+ gchar *host_name;
+ gchar *msg1;
+ const gchar *msg2;
+
+ host_name = g_utf8_make_valid (hn, -1);
+
+ msg1 = g_strdup_printf (_("Hostname “%s” not known."), host_name);
+ msg2 = _("The problem could come from the proxy settings.");
+
+ *secondary_msg = g_strconcat (msg1, "\n", msg2, NULL);
+
+ g_free (host_name);
+ g_free (msg1);
+ }
+
+ g_free (file_uri);
+ g_free (hn);
+ }
+
+ if (*primary_msg == NULL && *secondary_msg == NULL)
+ {
+ *secondary_msg = g_strdup (error->message);
+ }
+}
+
+GtkWidget *
+gedit_unrecoverable_reverting_error_info_bar_new (GFile *location,
+ const GError *error)
+{
+ gchar *uri;
+ gchar *primary_msg = NULL;
+ gchar *secondary_msg = NULL;
+ GtkWidget *info_bar;
+
+ g_return_val_if_fail (G_IS_FILE (location), NULL);
+ g_return_val_if_fail (error != NULL, NULL);
+
+ uri = g_file_get_parse_name (location);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+ {
+ secondary_msg = g_strdup (_("File not found. "
+ "Perhaps it has recently been deleted."));
+ }
+ else
+ {
+ get_detailed_error_messages (location, uri, error, &primary_msg, &secondary_msg);
+ }
+
+ if (primary_msg == NULL)
+ {
+ primary_msg = g_strdup_printf (_("Could not revert the file “%s”."), uri);
+ }
+
+ info_bar = create_io_loading_error_info_bar (primary_msg,
+ secondary_msg,
+ FALSE);
+
+ g_free (uri);
+ g_free (primary_msg);
+ g_free (secondary_msg);
+ return info_bar;
+}
+
+static void
+add_encodings_combo_box (TeplInfoBar *info_bar)
+{
+ GtkWidget *hgrid;
+ gchar *label_markup;
+ GtkWidget *label;
+ GtkWidget *combo_box;
+
+ hgrid = gtk_grid_new ();
+ gtk_grid_set_column_spacing (GTK_GRID (hgrid), 6);
+
+ label_markup = g_strdup_printf ("<small>%s</small>",
+ _("Ch_aracter Encoding:"));
+ label = gtk_label_new_with_mnemonic (label_markup);
+ g_free (label_markup);
+ gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+
+ combo_box = gedit_encodings_combo_box_new (TRUE);
+ g_object_set_data (G_OBJECT (info_bar),
+ "gedit-info-bar-encoding-combo-box",
+ combo_box);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo_box);
+
+ gtk_container_add (GTK_CONTAINER (hgrid), label);
+ gtk_container_add (GTK_CONTAINER (hgrid), combo_box);
+ gtk_widget_show_all (hgrid);
+
+ tepl_info_bar_add_content_widget (info_bar, hgrid, TEPL_INFO_BAR_LOCATION_ALONGSIDE_ICON);
+}
+
+static GtkWidget *
+create_conversion_error_info_bar (const gchar *primary_msg,
+ const gchar *secondary_msg,
+ gboolean edit_anyway)
+{
+ GtkMessageType msg_type;
+ TeplInfoBar *info_bar;
+
+ msg_type = edit_anyway ? GTK_MESSAGE_WARNING : GTK_MESSAGE_ERROR;
+
+ info_bar = tepl_info_bar_new_simple (msg_type, primary_msg, secondary_msg);
+
+ gtk_info_bar_add_button (GTK_INFO_BAR (info_bar),
+ _("_Retry"),
+ GTK_RESPONSE_OK);
+
+ if (edit_anyway)
+ {
+ gtk_info_bar_add_button (GTK_INFO_BAR (info_bar),
+ _("_Edit Anyway"),
+ GTK_RESPONSE_YES);
+ }
+
+ gtk_info_bar_add_button (GTK_INFO_BAR (info_bar),
+ _("_Cancel"),
+ GTK_RESPONSE_CLOSE);
+
+ add_encodings_combo_box (info_bar);
+
+ return GTK_WIDGET (info_bar);
+}
+
+GtkWidget *
+gedit_io_loading_error_info_bar_new (GFile *location,
+ const GtkSourceEncoding *encoding,
+ const GError *error)
+{
+ gchar *uri;
+ gchar *primary_msg = NULL;
+ gchar *secondary_msg = NULL;
+ gboolean edit_anyway = FALSE;
+ gboolean convert_error = FALSE;
+ GtkWidget *info_bar;
+
+ g_return_val_if_fail (error != NULL, NULL);
+
+ if (location != NULL)
+ {
+ uri = g_file_get_parse_name (location);
+ }
+ else
+ {
+ uri = g_strdup ("stdin");
+ }
+
+ if (is_gio_error (error, G_IO_ERROR_TOO_MANY_LINKS))
+ {
+ secondary_msg = g_strdup (_("The number of followed links is limited and the actual "
+ "file could not be found within this limit."));
+ }
+ else if (is_gio_error (error, G_IO_ERROR_PERMISSION_DENIED))
+ {
+ secondary_msg = g_strdup (_("You do not have the permissions necessary to open the file."));
+ }
+ else if ((is_gio_error (error, G_IO_ERROR_INVALID_DATA) && encoding == NULL) ||
+ (error->domain == GTK_SOURCE_FILE_LOADER_ERROR &&
+ error->code == GTK_SOURCE_FILE_LOADER_ERROR_ENCODING_AUTO_DETECTION_FAILED))
+ {
+ secondary_msg = g_strconcat (_("Unable to detect the character encoding."), "\n",
+ _("Please check that you are not trying to open a binary file."), "\n",
+ _("Select a character encoding from the menu and try again."), NULL);
+ convert_error = TRUE;
+ }
+ else if (error->domain == GTK_SOURCE_FILE_LOADER_ERROR &&
+ error->code == GTK_SOURCE_FILE_LOADER_ERROR_CONVERSION_FALLBACK)
+ {
+ primary_msg = g_strdup_printf (_("There was a problem opening the file “%s”."), uri);
+ secondary_msg = g_strconcat (_("The file you opened has some invalid characters. "
+ "If you continue editing this file you could corrupt this "
+ "document."), "\n",
+ _("You can also choose another character encoding and try again."),
+ NULL);
+ edit_anyway = TRUE;
+ convert_error = TRUE;
+ }
+ else if (is_gio_error (error, G_IO_ERROR_INVALID_DATA) && encoding != NULL)
+ {
+ gchar *encoding_name = gtk_source_encoding_to_string (encoding);
+
+ primary_msg = g_strdup_printf (_("Could not open the file “%s” using the “%s” character encoding."),
+ uri,
+ encoding_name);
+ secondary_msg = g_strconcat (_("Please check that you are not trying to open a binary file."), "\n",
+ _("Select a different character encoding from the menu and try again."), NULL);
+ convert_error = TRUE;
+
+ g_free (encoding_name);
+ }
+ else
+ {
+ get_detailed_error_messages (location, uri, error, &primary_msg, &secondary_msg);
+ }
+
+ if (primary_msg == NULL)
+ {
+ primary_msg = g_strdup_printf (_("Could not open the file “%s”."), uri);
+ }
+
+ if (convert_error)
+ {
+ info_bar = create_conversion_error_info_bar (primary_msg,
+ secondary_msg,
+ edit_anyway);
+ }
+ else
+ {
+ info_bar = create_io_loading_error_info_bar (primary_msg,
+ secondary_msg,
+ is_recoverable_error (error));
+ }
+
+ g_free (uri);
+ g_free (primary_msg);
+ g_free (secondary_msg);
+ return info_bar;
+}
+
+GtkWidget *
+gedit_conversion_error_while_saving_info_bar_new (GFile *location,
+ const GtkSourceEncoding *encoding)
+{
+ gchar *uri;
+ gchar *encoding_name;
+ gchar *primary_msg = NULL;
+ gchar *secondary_msg = NULL;
+ GtkWidget *info_bar;
+
+ g_return_val_if_fail (G_IS_FILE (location), NULL);
+ g_return_val_if_fail (encoding != NULL, NULL);
+
+ uri = g_file_get_parse_name (location);
+ encoding_name = gtk_source_encoding_to_string (encoding);
+
+ primary_msg = g_strdup_printf (_("Could not save the file “%s” using the “%s” character encoding."),
+ uri,
+ encoding_name);
+ secondary_msg = g_strconcat (_("The document contains one or more characters that cannot be encoded "
+ "using the specified character encoding."), "\n",
+ _("Select a different character encoding from the menu and try again."), NULL);
+
+ info_bar = create_conversion_error_info_bar (primary_msg,
+ secondary_msg,
+ FALSE);
+
+ g_free (uri);
+ g_free (encoding_name);
+ g_free (primary_msg);
+ g_free (secondary_msg);
+ return info_bar;
+}
+
+const GtkSourceEncoding *
+gedit_conversion_error_info_bar_get_encoding (GtkWidget *info_bar)
+{
+ gpointer combo_box;
+
+ g_return_val_if_fail (GTK_IS_INFO_BAR (info_bar), NULL);
+
+ combo_box = g_object_get_data (G_OBJECT (info_bar),
+ "gedit-info-bar-encoding-combo-box");
+ if (combo_box != NULL)
+ {
+ return gedit_encodings_combo_box_get_selected_encoding (GEDIT_ENCODINGS_COMBO_BOX (combo_box));
+ }
+
+ return NULL;
+}
+
+GtkWidget *
+gedit_unrecoverable_saving_error_info_bar_new (GFile *location,
+ const GError *error)
+{
+ gchar *uri;
+ gchar *primary_msg = NULL;
+ gchar *secondary_msg = NULL;
+ TeplInfoBar *info_bar;
+
+ g_return_val_if_fail (G_IS_FILE (location), NULL);
+ g_return_val_if_fail (error != NULL, NULL);
+
+ uri = g_file_get_parse_name (location);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
+ {
+ gchar *uri_scheme = g_file_get_uri_scheme (location);
+
+ if (uri_scheme != NULL &&
+ g_utf8_validate (uri_scheme, -1, NULL))
+ {
+ /* Translators: %s is a URI scheme (like for example http:, ftp:, etc.) */
+ secondary_msg = g_strdup_printf (_("Cannot handle “%s:” locations in write mode. "
+ "Please check that you typed the "
+ "location correctly and try again."),
+ uri_scheme);
+ }
+ else
+ {
+ secondary_msg = g_strdup (_("Cannot handle this location in write mode. "
+ "Please check that you typed the "
+ "location correctly and try again."));
+ }
+
+ g_free (uri_scheme);
+ }
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME))
+ {
+ secondary_msg = g_strdup_printf (_("“%s” is not a valid location. "
+ "Please check that you typed the "
+ "location correctly and try again."),
+ uri);
+ }
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
+ {
+ secondary_msg = g_strdup (_("You do not have the permissions necessary to save the file. "
+ "Please check that you typed the "
+ "location correctly and try again."));
+ }
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE))
+ {
+ secondary_msg = g_strdup (_("There is not enough disk space to save the file. "
+ "Please free some disk space and try again."));
+ }
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_READ_ONLY))
+ {
+ secondary_msg = g_strdup (_("You are trying to save the file on a read-only disk. "
+ "Please check that you typed the location "
+ "correctly and try again."));
+ }
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+ {
+ secondary_msg = g_strdup (_("A file with the same name already exists. "
+ "Please use a different name."));
+ }
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_FILENAME_TOO_LONG))
+ {
+ secondary_msg = g_strdup (_("The disk where you are trying to save the file has "
+ "a limitation on length of the file names. "
+ "Please use a shorter name."));
+ }
+ else
+ {
+ get_detailed_error_messages (location,
+ uri,
+ error,
+ &primary_msg,
+ &secondary_msg);
+ }
+
+ if (primary_msg == NULL)
+ {
+ primary_msg = g_strdup_printf (_("Could not save the file “%s”."), uri);
+ }
+
+ info_bar = tepl_info_bar_new_simple (GTK_MESSAGE_ERROR, primary_msg, secondary_msg);
+ gtk_info_bar_set_show_close_button (GTK_INFO_BAR (info_bar), TRUE);
+
+ g_free (uri);
+ g_free (primary_msg);
+ g_free (secondary_msg);
+ return GTK_WIDGET (info_bar);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-io-error-info-bar.h b/gedit/gedit-io-error-info-bar.h
new file mode 100644
index 0000000..a104ad5
--- /dev/null
+++ b/gedit/gedit-io-error-info-bar.h
@@ -0,0 +1,47 @@
+/*
+ * 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/>.
+ */
+
+#ifndef GEDIT_IO_ERROR_INFO_BAR_H
+#define GEDIT_IO_ERROR_INFO_BAR_H
+
+#include <gtksourceview/gtksource.h>
+
+G_BEGIN_DECLS
+
+GtkWidget * gedit_io_loading_error_info_bar_new (GFile *location,
+ const GtkSourceEncoding *encoding,
+ const GError *error);
+
+GtkWidget * gedit_unrecoverable_reverting_error_info_bar_new (GFile *location,
+ const GError *error);
+
+GtkWidget * gedit_conversion_error_while_saving_info_bar_new (GFile *location,
+ const GtkSourceEncoding *encoding);
+
+const GtkSourceEncoding *
+ gedit_conversion_error_info_bar_get_encoding (GtkWidget *info_bar);
+
+GtkWidget * gedit_unrecoverable_saving_error_info_bar_new (GFile *location,
+ const GError *error);
+
+G_END_DECLS
+
+#endif /* GEDIT_IO_ERROR_INFO_BAR_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-menu-extension.c b/gedit/gedit-menu-extension.c
new file mode 100644
index 0000000..5e937b7
--- /dev/null
+++ b/gedit/gedit-menu-extension.c
@@ -0,0 +1,187 @@
+/*
+ * gedit-menu-extension.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Ignacio Casal Quinteiro
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gedit-menu-extension.h"
+
+#include <string.h>
+
+static guint last_merge_id = 0;
+
+struct _GeditMenuExtension
+{
+ GObject parent_instance;
+
+ GMenu *menu;
+ guint merge_id;
+ gboolean dispose_has_run;
+};
+
+enum
+{
+ PROP_0,
+ PROP_MENU,
+ LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP];
+
+G_DEFINE_TYPE (GeditMenuExtension, gedit_menu_extension, G_TYPE_OBJECT)
+
+static void
+gedit_menu_extension_dispose (GObject *object)
+{
+ GeditMenuExtension *menu = GEDIT_MENU_EXTENSION (object);
+
+ if (!menu->dispose_has_run)
+ {
+ gedit_menu_extension_remove_items (menu);
+ menu->dispose_has_run = TRUE;
+ }
+
+ g_clear_object (&menu->menu);
+
+ G_OBJECT_CLASS (gedit_menu_extension_parent_class)->dispose (object);
+}
+
+static void
+gedit_menu_extension_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditMenuExtension *menu = GEDIT_MENU_EXTENSION (object);
+
+ switch (prop_id)
+ {
+ case PROP_MENU:
+ g_value_set_object (value, menu->menu);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_menu_extension_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditMenuExtension *menu = GEDIT_MENU_EXTENSION (object);
+
+ switch (prop_id)
+ {
+ case PROP_MENU:
+ menu->menu = g_value_dup_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_menu_extension_class_init (GeditMenuExtensionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gedit_menu_extension_dispose;
+ object_class->get_property = gedit_menu_extension_get_property;
+ object_class->set_property = gedit_menu_extension_set_property;
+
+ properties[PROP_MENU] =
+ g_param_spec_object ("menu",
+ "Menu",
+ "The main menu",
+ G_TYPE_MENU,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+gedit_menu_extension_init (GeditMenuExtension *menu)
+{
+ menu->merge_id = ++last_merge_id;
+}
+
+GeditMenuExtension *
+gedit_menu_extension_new (GMenu *menu)
+{
+ return g_object_new (GEDIT_TYPE_MENU_EXTENSION, "menu", menu, NULL);
+}
+
+void
+gedit_menu_extension_append_menu_item (GeditMenuExtension *menu,
+ GMenuItem *item)
+{
+ g_return_if_fail (GEDIT_IS_MENU_EXTENSION (menu));
+ g_return_if_fail (G_IS_MENU_ITEM (item));
+
+ if (menu->menu != NULL)
+ {
+ g_menu_item_set_attribute (item, "gedit-merge-id", "u", menu->merge_id);
+ g_menu_append_item (menu->menu, item);
+ }
+}
+
+void
+gedit_menu_extension_prepend_menu_item (GeditMenuExtension *menu,
+ GMenuItem *item)
+{
+ g_return_if_fail (GEDIT_IS_MENU_EXTENSION (menu));
+ g_return_if_fail (G_IS_MENU_ITEM (item));
+
+ if (menu->menu != NULL)
+ {
+ g_menu_item_set_attribute (item, "gedit-merge-id", "u", menu->merge_id);
+ g_menu_prepend_item (menu->menu, item);
+ }
+}
+
+void
+gedit_menu_extension_remove_items (GeditMenuExtension *menu)
+{
+ gint i, n_items;
+
+ g_return_if_fail (GEDIT_IS_MENU_EXTENSION (menu));
+
+ n_items = g_menu_model_get_n_items (G_MENU_MODEL (menu->menu));
+ i = 0;
+ while (i < n_items)
+ {
+ guint id = 0;
+
+ if (g_menu_model_get_item_attribute (G_MENU_MODEL (menu->menu),
+ i, "gedit-merge-id", "u", &id) &&
+ id == menu->merge_id)
+ {
+ g_menu_remove (menu->menu, i);
+ n_items--;
+ }
+ else
+ {
+ i++;
+ }
+ }
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-menu-extension.h b/gedit/gedit-menu-extension.h
new file mode 100644
index 0000000..1803b3b
--- /dev/null
+++ b/gedit/gedit-menu-extension.h
@@ -0,0 +1,47 @@
+/*
+ * gedit-menu-extension.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Ignacio Casal Quinteiro
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_MENU_EXTENSION_H
+#define GEDIT_MENU_EXTENSION_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_MENU_EXTENSION (gedit_menu_extension_get_type ())
+
+G_DECLARE_FINAL_TYPE (GeditMenuExtension, gedit_menu_extension, GEDIT, MENU_EXTENSION, GObject)
+
+GeditMenuExtension *gedit_menu_extension_new (GMenu *menu);
+
+void gedit_menu_extension_append_menu_item (GeditMenuExtension *menu,
+ GMenuItem *item);
+
+void gedit_menu_extension_prepend_menu_item (GeditMenuExtension *menu,
+ GMenuItem *item);
+
+void gedit_menu_extension_remove_items (GeditMenuExtension *menu);
+
+G_END_DECLS
+
+#endif /* GEDIT_MENU_EXTENSION_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-menu-stack-switcher.c b/gedit/gedit-menu-stack-switcher.c
new file mode 100644
index 0000000..4c4315c
--- /dev/null
+++ b/gedit/gedit-menu-stack-switcher.c
@@ -0,0 +1,420 @@
+/*
+ * gedit-menu-stack-switcher.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Steve Frécinaux
+ *
+ * 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-menu-stack-switcher.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+struct _GeditMenuStackSwitcher
+{
+ GtkMenuButton parent_instance;
+
+ GtkStack *stack;
+ GtkWidget *label;
+ GtkWidget *button_box;
+ GtkWidget *popover;
+ GHashTable *buttons;
+ gboolean in_child_changed;
+};
+
+enum {
+ PROP_0,
+ PROP_STACK,
+ LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP];
+
+G_DEFINE_TYPE (GeditMenuStackSwitcher, gedit_menu_stack_switcher, GTK_TYPE_MENU_BUTTON)
+
+static void
+gedit_menu_stack_switcher_init (GeditMenuStackSwitcher *switcher)
+{
+ GtkWidget *box;
+ GtkWidget *arrow;
+ GtkStyleContext *context;
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+
+ arrow = gtk_image_new_from_icon_name ("pan-down-symbolic", GTK_ICON_SIZE_BUTTON);
+ gtk_box_pack_end (GTK_BOX (box), arrow, FALSE, TRUE, 0);
+ gtk_widget_set_valign (arrow, GTK_ALIGN_BASELINE);
+
+ switcher->label = gtk_label_new (NULL);
+ gtk_widget_set_valign (switcher->label, GTK_ALIGN_BASELINE);
+ gtk_box_pack_start (GTK_BOX (box), switcher->label, TRUE, TRUE, 6);
+
+ // FIXME: this is not correct if this widget becomes more generic
+ // and used also outside the header bar, but for now we just want
+ // the same style as title labels
+ context = gtk_widget_get_style_context (switcher->label);
+ gtk_style_context_add_class (context, "title");
+
+ gtk_widget_show_all (box);
+ gtk_container_add (GTK_CONTAINER (switcher), box);
+
+ switcher->popover = gtk_popover_new (GTK_WIDGET (switcher));
+ gtk_popover_set_position (GTK_POPOVER (switcher->popover), GTK_POS_BOTTOM);
+ context = gtk_widget_get_style_context (switcher->popover);
+ gtk_style_context_add_class (context, "gedit-menu-stack-switcher");
+
+ switcher->button_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+
+ gtk_widget_show (switcher->button_box);
+
+ gtk_container_add (GTK_CONTAINER (switcher->popover), switcher->button_box);
+
+ gtk_menu_button_set_popover (GTK_MENU_BUTTON (switcher), switcher->popover);
+
+ switcher->buttons = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+static void
+clear_popover (GeditMenuStackSwitcher *switcher)
+{
+ gtk_container_foreach (GTK_CONTAINER (switcher->button_box), (GtkCallback) gtk_widget_destroy, switcher);
+}
+
+static void
+on_button_clicked (GtkWidget *widget,
+ GeditMenuStackSwitcher *switcher)
+{
+ GtkWidget *child;
+
+ if (!switcher->in_child_changed)
+ {
+ child = g_object_get_data (G_OBJECT (widget), "stack-child");
+ gtk_stack_set_visible_child (switcher->stack, child);
+ gtk_widget_hide (switcher->popover);
+ }
+}
+
+static void
+update_button (GeditMenuStackSwitcher *switcher,
+ GtkWidget *widget,
+ GtkWidget *button)
+{
+ GList *children;
+
+ /* We get spurious notifications while the stack is being
+ * destroyed, so for now check the child actually exists
+ */
+ children = gtk_container_get_children (GTK_CONTAINER (switcher->stack));
+ if (g_list_index (children, widget) >= 0)
+ {
+ gchar *title;
+
+ gtk_container_child_get (GTK_CONTAINER (switcher->stack), widget,
+ "title", &title,
+ NULL);
+
+ gtk_button_set_label (GTK_BUTTON (button), title);
+ gtk_widget_set_visible (button, gtk_widget_get_visible (widget) && (title != NULL));
+ gtk_widget_set_size_request (button, 100, -1);
+
+ if (widget == gtk_stack_get_visible_child (switcher->stack))
+ {
+ gtk_label_set_label (GTK_LABEL (switcher->label), title);
+ }
+
+ g_free (title);
+ }
+
+ g_list_free (children);
+}
+
+static void
+on_title_icon_visible_updated (GtkWidget *widget,
+ GParamSpec *pspec,
+ GeditMenuStackSwitcher *switcher)
+{
+ GtkWidget *button;
+
+ button = g_hash_table_lookup (switcher->buttons, widget);
+ update_button (switcher, widget, button);
+}
+
+static void
+on_position_updated (GtkWidget *widget,
+ GParamSpec *pspec,
+ GeditMenuStackSwitcher *switcher)
+{
+ GtkWidget *button;
+ gint position;
+
+ button = g_hash_table_lookup (switcher->buttons, widget);
+
+ gtk_container_child_get (GTK_CONTAINER (switcher->stack), widget,
+ "position", &position,
+ NULL);
+
+ gtk_box_reorder_child (GTK_BOX (switcher->button_box), button, position);
+}
+
+static void
+add_child (GeditMenuStackSwitcher *switcher,
+ GtkWidget *widget)
+{
+ GtkWidget *button;
+ GList *group;
+
+ button = gtk_radio_button_new (NULL);
+ gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE);
+ gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
+
+ update_button (switcher, widget, button);
+
+ group = gtk_container_get_children (GTK_CONTAINER (switcher->button_box));
+ if (group != NULL)
+ {
+ gtk_radio_button_join_group (GTK_RADIO_BUTTON (button), GTK_RADIO_BUTTON (group->data));
+ g_list_free (group);
+ }
+
+ gtk_container_add (GTK_CONTAINER (switcher->button_box), button);
+
+ g_object_set_data (G_OBJECT (button), "stack-child", widget);
+ g_signal_connect (button, "clicked", G_CALLBACK (on_button_clicked), switcher);
+ g_signal_connect (widget, "notify::visible", G_CALLBACK (on_title_icon_visible_updated), switcher);
+ g_signal_connect (widget, "child-notify::title", G_CALLBACK (on_title_icon_visible_updated), switcher);
+ g_signal_connect (widget, "child-notify::icon-name", G_CALLBACK (on_title_icon_visible_updated), switcher);
+ g_signal_connect (widget, "child-notify::position", G_CALLBACK (on_position_updated), switcher);
+
+ g_hash_table_insert (switcher->buttons, widget, button);
+}
+
+static void
+foreach_stack (GtkWidget *widget,
+ GeditMenuStackSwitcher *switcher)
+{
+ add_child (switcher, widget);
+}
+
+static void
+populate_popover (GeditMenuStackSwitcher *switcher)
+{
+ gtk_container_foreach (GTK_CONTAINER (switcher->stack), (GtkCallback)foreach_stack, switcher);
+}
+
+static void
+on_child_changed (GtkWidget *widget,
+ GParamSpec *pspec,
+ GeditMenuStackSwitcher *switcher)
+{
+ GtkWidget *child;
+ GtkWidget *button;
+
+ child = gtk_stack_get_visible_child (GTK_STACK (widget));
+ if (child)
+ {
+ gchar *title;
+
+ gtk_container_child_get (GTK_CONTAINER (switcher->stack), child,
+ "title", &title,
+ NULL);
+
+ gtk_label_set_label (GTK_LABEL (switcher->label), title);
+ g_free (title);
+ }
+
+ button = g_hash_table_lookup (switcher->buttons, child);
+ if (button != NULL)
+ {
+ switcher->in_child_changed = TRUE;
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+ switcher->in_child_changed = FALSE;
+ }
+}
+
+static void
+on_stack_child_added (GtkStack *stack,
+ GtkWidget *widget,
+ GeditMenuStackSwitcher *switcher)
+{
+ add_child (switcher, widget);
+}
+
+static void
+on_stack_child_removed (GtkStack *stack,
+ GtkWidget *widget,
+ GeditMenuStackSwitcher *switcher)
+{
+ GtkWidget *button;
+
+ g_signal_handlers_disconnect_by_func (widget, on_title_icon_visible_updated, switcher);
+ g_signal_handlers_disconnect_by_func (widget, on_title_icon_visible_updated, switcher);
+ g_signal_handlers_disconnect_by_func (widget, on_title_icon_visible_updated, switcher);
+ g_signal_handlers_disconnect_by_func (widget, on_position_updated, switcher);
+
+ button = g_hash_table_lookup (switcher->buttons, widget);
+ gtk_container_remove (GTK_CONTAINER (switcher->button_box), button);
+ g_hash_table_remove (switcher->buttons, widget);
+}
+
+static void
+disconnect_stack_signals (GeditMenuStackSwitcher *switcher)
+{
+ g_signal_handlers_disconnect_by_func (switcher->stack, on_stack_child_added, switcher);
+ g_signal_handlers_disconnect_by_func (switcher->stack, on_stack_child_removed, switcher);
+ g_signal_handlers_disconnect_by_func (switcher->stack, on_child_changed, switcher);
+ g_signal_handlers_disconnect_by_func (switcher->stack, disconnect_stack_signals, switcher);
+}
+
+static void
+connect_stack_signals (GeditMenuStackSwitcher *switcher)
+{
+ g_signal_connect (switcher->stack, "add",
+ G_CALLBACK (on_stack_child_added), switcher);
+ g_signal_connect (switcher->stack, "remove",
+ G_CALLBACK (on_stack_child_removed), switcher);
+ g_signal_connect (switcher->stack, "notify::visible-child",
+ G_CALLBACK (on_child_changed), switcher);
+ g_signal_connect_swapped (switcher->stack, "destroy",
+ G_CALLBACK (disconnect_stack_signals), switcher);
+}
+
+void
+gedit_menu_stack_switcher_set_stack (GeditMenuStackSwitcher *switcher,
+ GtkStack *stack)
+{
+ g_return_if_fail (GEDIT_IS_MENU_STACK_SWITCHER (switcher));
+ g_return_if_fail (stack == NULL || GTK_IS_STACK (stack));
+
+ if (switcher->stack == stack)
+ return;
+
+ if (switcher->stack)
+ {
+ disconnect_stack_signals (switcher);
+ clear_popover (switcher);
+ g_clear_object (&switcher->stack);
+ }
+
+ if (stack)
+ {
+ switcher->stack = g_object_ref (stack);
+ populate_popover (switcher);
+ connect_stack_signals (switcher);
+ }
+
+ gtk_widget_queue_resize (GTK_WIDGET (switcher));
+
+ g_object_notify_by_pspec (G_OBJECT (switcher), properties[PROP_STACK]);
+}
+
+GtkStack *
+gedit_menu_stack_switcher_get_stack (GeditMenuStackSwitcher *switcher)
+{
+ g_return_val_if_fail (GEDIT_IS_MENU_STACK_SWITCHER (switcher), NULL);
+
+ return switcher->stack;
+}
+
+static void
+gedit_menu_stack_switcher_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditMenuStackSwitcher *switcher = GEDIT_MENU_STACK_SWITCHER (object);
+
+ switch (prop_id)
+ {
+ case PROP_STACK:
+ g_value_set_object (value, switcher->stack);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_menu_stack_switcher_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditMenuStackSwitcher *switcher = GEDIT_MENU_STACK_SWITCHER (object);
+
+ switch (prop_id)
+ {
+ case PROP_STACK:
+ gedit_menu_stack_switcher_set_stack (switcher, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_menu_stack_switcher_dispose (GObject *object)
+{
+ GeditMenuStackSwitcher *switcher = GEDIT_MENU_STACK_SWITCHER (object);
+
+ gedit_menu_stack_switcher_set_stack (switcher, NULL);
+
+ G_OBJECT_CLASS (gedit_menu_stack_switcher_parent_class)->dispose (object);
+}
+
+static void
+gedit_menu_stack_switcher_finalize (GObject *object)
+{
+ GeditMenuStackSwitcher *switcher = GEDIT_MENU_STACK_SWITCHER (object);
+
+ g_hash_table_destroy (switcher->buttons);
+
+ G_OBJECT_CLASS (gedit_menu_stack_switcher_parent_class)->finalize (object);
+}
+
+static void
+gedit_menu_stack_switcher_class_init (GeditMenuStackSwitcherClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = gedit_menu_stack_switcher_get_property;
+ object_class->set_property = gedit_menu_stack_switcher_set_property;
+ object_class->dispose = gedit_menu_stack_switcher_dispose;
+ object_class->finalize = gedit_menu_stack_switcher_finalize;
+
+ properties[PROP_STACK] =
+ g_param_spec_object ("stack",
+ "Stack",
+ "Stack",
+ GTK_TYPE_STACK,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+GtkWidget *
+gedit_menu_stack_switcher_new (void)
+{
+ return g_object_new (GEDIT_TYPE_MENU_STACK_SWITCHER, NULL);
+}
+
+/* ex:set ts=2 sw=2 et: */
diff --git a/gedit/gedit-menu-stack-switcher.h b/gedit/gedit-menu-stack-switcher.h
new file mode 100644
index 0000000..4a4e3b1
--- /dev/null
+++ b/gedit/gedit-menu-stack-switcher.h
@@ -0,0 +1,43 @@
+/*
+ * gedit-menu-stack-switcher.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Steve Frécinaux
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_MENU_STACK_SWITCHER_H
+#define GEDIT_MENU_STACK_SWITCHER_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_MENU_STACK_SWITCHER (gedit_menu_stack_switcher_get_type())
+
+G_DECLARE_FINAL_TYPE (GeditMenuStackSwitcher, gedit_menu_stack_switcher, GEDIT, MENU_STACK_SWITCHER, GtkMenuButton)
+
+GtkWidget * gedit_menu_stack_switcher_new (void);
+
+void gedit_menu_stack_switcher_set_stack (GeditMenuStackSwitcher *switcher,
+ GtkStack *stack);
+
+GtkStack * gedit_menu_stack_switcher_get_stack (GeditMenuStackSwitcher *switcher);
+
+G_END_DECLS
+
+#endif /* GEDIT_MENU_STACK_SWITCHER_H */
+
+/* ex:set ts=2 sw=2 et: */
diff --git a/gedit/gedit-message-bus.c b/gedit/gedit-message-bus.c
new file mode 100644
index 0000000..a5ed195
--- /dev/null
+++ b/gedit/gedit-message-bus.c
@@ -0,0 +1,1259 @@
+/*
+ * gedit-message-bus.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2008-2010 - Jesse van den Kieboom
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "gedit-message-bus.h"
+
+#include <string.h>
+#include <stdarg.h>
+#include <gobject/gvaluecollector.h>
+
+/**
+ * GeditMessageCallback:
+ * @bus: the #GeditMessageBus on which the message was sent
+ * @message: the #GeditMessage which was sent
+ * @user_data: the supplied user data when connecting the callback
+ *
+ * Callback signature used for connecting callback functions to be called
+ * when a message is received (see gedit_message_bus_connect()).
+ *
+ */
+
+/**
+ * SECTION:gedit-message-bus
+ * @short_description: internal message communication bus
+ * @include: gedit/gedit-message-bus.h
+ *
+ * gedit has a communication bus very similar to DBus. Its primary use is to
+ * allow easy communication between plugins, but it can also be used to expose
+ * gedit functionality to external applications by providing DBus bindings for
+ * the internal gedit message bus.
+ *
+ * There are two different communication busses available. The default bus
+ * (see gedit_message_bus_get_default()) is an application wide communication
+ * bus. In addition, each #GeditWindow has a separate, private bus
+ * (see gedit_window_get_message_bus()). This makes it easier for plugins to
+ * communicate to other plugins in the same window.
+ *
+ * The concept of the message bus is very simple. You can register a message
+ * type on the bus, specified as a Method at a specific Object Path with a
+ * certain set of Method Arguments. You can then connect callback functions
+ * for this message type on the bus. Whenever a message with the Object Path
+ * and Method for which callbacks are connected is sent over the bus, the
+ * callbacks are called. There is no distinction between Methods and Signals
+ * (signals are simply messages where sender and receiver have switched places).
+ *
+ * <example>
+ * <title>Registering a message type</title>
+ * <programlisting>
+ * GeditMessageBus *bus = gedit_message_bus_get_default ();
+ *
+ * // Register 'method' at '/plugins/example' with one required
+ * // string argument 'arg1'
+ * gedit_message_bus_register (bus, EXAMPLE_TYPE_METHOD_MESSAGE,
+ * "/plugins/example", "method");
+ * </programlisting>
+ * </example>
+ * <example>
+ * <title>Connecting a callback</title>
+ * <programlisting>
+ * static void
+ * example_method_cb (GeditMessageBus *bus,
+ * GeditMessage *message,
+ * gpointer user_data)
+ * {
+ * gchar *arg1 = NULL;
+ *
+ * gedit_message_get (message, "arg1", &arg1, NULL);
+ * g_message ("Evoked /plugins/example.method with: %s", arg1);
+ * g_free (arg1);
+ * }
+ *
+ * GeditMessageBus *bus = gedit_message_bus_get_default ();
+ *
+ * guint id = gedit_message_bus_connect (bus,
+ * "/plugins/example", "method",
+ * example_method_cb,
+ * NULL,
+ * NULL);
+ *
+ * </programlisting>
+ * </example>
+ * <example>
+ * <title>Sending a message</title>
+ * <programlisting>
+ * GeditMessageBus *bus = gedit_message_bus_get_default ();
+ *
+ * gedit_message_bus_send (bus,
+ * "/plugins/example", "method",
+ * "arg1", "Hello World",
+ * NULL);
+ * </programlisting>
+ * </example>
+ */
+
+typedef struct
+{
+ gchar *object_path;
+ gchar *method;
+
+ gchar *identifier;
+} MessageIdentifier;
+
+typedef struct
+{
+ MessageIdentifier *identifier;
+
+ GList *listeners;
+} Message;
+
+typedef struct
+{
+ guint id;
+ gboolean blocked;
+
+ GDestroyNotify destroy_data;
+ GeditMessageCallback callback;
+ gpointer user_data;
+} Listener;
+
+typedef struct
+{
+ Message *message;
+ GList *listener;
+} IdMap;
+
+struct _GeditMessageBusPrivate
+{
+ GHashTable *messages;
+ GHashTable *idmap;
+
+ GList *message_queue;
+ guint idle_id;
+
+ guint next_id;
+
+ GHashTable *types; /* mapping from identifier to GeditMessageType */
+};
+
+/* signals */
+enum
+{
+ DISPATCH,
+ REGISTERED,
+ UNREGISTERED,
+ LAST_SIGNAL
+};
+
+static guint message_bus_signals[LAST_SIGNAL];
+
+static void gedit_message_bus_dispatch_real (GeditMessageBus *bus,
+ GeditMessage *message);
+
+G_DEFINE_TYPE_WITH_PRIVATE (GeditMessageBus, gedit_message_bus, G_TYPE_OBJECT)
+
+static MessageIdentifier *
+message_identifier_new (const gchar *object_path,
+ const gchar *method)
+{
+ MessageIdentifier *ret;
+
+ ret = g_slice_new (MessageIdentifier);
+
+ ret->object_path = g_strdup (object_path);
+ ret->method = g_strdup (method);
+
+ ret->identifier = gedit_message_type_identifier (object_path, method);
+
+ return ret;
+}
+
+static void
+message_identifier_free (MessageIdentifier *identifier)
+{
+ g_free (identifier->object_path);
+ g_free (identifier->method);
+ g_free (identifier->identifier);
+
+ g_slice_free (MessageIdentifier, identifier);
+}
+
+static guint
+message_identifier_hash (gconstpointer id)
+{
+ return g_str_hash (((MessageIdentifier *)id)->identifier);
+}
+
+static gboolean
+message_identifier_equal (gconstpointer id1,
+ gconstpointer id2)
+{
+ return g_str_equal (((MessageIdentifier *)id1)->identifier,
+ ((MessageIdentifier *)id2)->identifier);
+}
+
+static void
+listener_free (Listener *listener)
+{
+ if (listener->destroy_data)
+ {
+ listener->destroy_data (listener->user_data);
+ }
+
+ g_slice_free (Listener, listener);
+}
+
+static void
+message_free (Message *message)
+{
+ message_identifier_free (message->identifier);
+
+ g_list_free_full (message->listeners, (GDestroyNotify) listener_free);
+ g_slice_free (Message, message);
+}
+
+static void
+message_queue_free (GList *queue)
+{
+ g_list_free_full (queue, g_object_unref);
+}
+
+static void
+gedit_message_bus_finalize (GObject *object)
+{
+ GeditMessageBus *bus = GEDIT_MESSAGE_BUS (object);
+
+ if (bus->priv->idle_id != 0)
+ {
+ g_source_remove (bus->priv->idle_id);
+ }
+
+ message_queue_free (bus->priv->message_queue);
+
+ g_hash_table_destroy (bus->priv->messages);
+ g_hash_table_destroy (bus->priv->idmap);
+ g_hash_table_destroy (bus->priv->types);
+
+ G_OBJECT_CLASS (gedit_message_bus_parent_class)->finalize (object);
+}
+
+static void
+gedit_message_bus_class_init (GeditMessageBusClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gedit_message_bus_finalize;
+
+ klass->dispatch = gedit_message_bus_dispatch_real;
+
+ /**
+ * GeditMessageBus::dispatch:
+ * @bus: a #GeditMessageBus
+ * @message: the #GeditMessage to dispatch
+ *
+ * The "dispatch" signal is emitted when a message is to be dispatched.
+ * The message is dispatched in the default handler of this signal.
+ * Primary use of this signal is to customize the dispatch of a message
+ * (for instance to automatically dispatch all messages over DBus).
+ *
+ */
+ message_bus_signals[DISPATCH] =
+ g_signal_new ("dispatch",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditMessageBusClass, dispatch),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ GEDIT_TYPE_MESSAGE);
+
+ /**
+ * GeditMessageBus::registered:
+ * @bus: a #GeditMessageBus
+ * @object_path: the registered object path.
+ * @method: the registered method
+ *
+ * The "registered" signal is emitted when a message has been registered
+ * on the bus.
+ *
+ */
+ message_bus_signals[REGISTERED] =
+ g_signal_new ("registered",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditMessageBusClass, registered),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ /**
+ * GeditMessageBus::unregistered:
+ * @bus: a #GeditMessageBus
+ * @object_path: the unregistered object path.
+ * @method: the unregistered method
+ *
+ * The "unregistered" signal is emitted when a message has been
+ * unregistered from the bus.
+ *
+ */
+ message_bus_signals[UNREGISTERED] =
+ g_signal_new ("unregistered",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditMessageBusClass, unregistered),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+}
+
+static Message *
+message_new (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method)
+{
+ Message *message = g_slice_new (Message);
+
+ message->identifier = message_identifier_new (object_path, method);
+ message->listeners = NULL;
+
+ g_hash_table_insert (bus->priv->messages,
+ message->identifier,
+ message);
+
+ return message;
+}
+
+static Message *
+lookup_message (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ gboolean create)
+{
+ MessageIdentifier *identifier;
+ Message *message;
+
+ identifier = message_identifier_new (object_path, method);
+ message = g_hash_table_lookup (bus->priv->messages, identifier);
+ message_identifier_free (identifier);
+
+ if (!message && !create)
+ {
+ return NULL;
+ }
+
+ if (!message)
+ {
+ message = message_new (bus, object_path, method);
+ }
+
+ return message;
+}
+
+static guint
+add_listener (GeditMessageBus *bus,
+ Message *message,
+ GeditMessageCallback callback,
+ gpointer user_data,
+ GDestroyNotify destroy_data)
+{
+ Listener *listener;
+ IdMap *idmap;
+
+ listener = g_slice_new (Listener);
+ listener->id = ++bus->priv->next_id;
+ listener->callback = callback;
+ listener->user_data = user_data;
+ listener->blocked = FALSE;
+ listener->destroy_data = destroy_data;
+
+ message->listeners = g_list_append (message->listeners, listener);
+
+ idmap = g_new (IdMap, 1);
+ idmap->message = message;
+ idmap->listener = g_list_last (message->listeners);
+
+ g_hash_table_insert (bus->priv->idmap, GINT_TO_POINTER (listener->id), idmap);
+
+ return listener->id;
+}
+
+static void
+remove_listener (GeditMessageBus *bus,
+ Message *message,
+ GList *listener)
+{
+ Listener *lst;
+
+ lst = (Listener *)listener->data;
+
+ /* remove from idmap */
+ g_hash_table_remove (bus->priv->idmap, GINT_TO_POINTER (lst->id));
+ listener_free (lst);
+
+ /* remove from list of listeners */
+ message->listeners = g_list_delete_link (message->listeners, listener);
+
+ if (!message->listeners)
+ {
+ /* remove message because it does not have any listeners */
+ g_hash_table_remove (bus->priv->messages, message->identifier);
+ }
+}
+
+static void
+block_listener (GeditMessageBus *bus,
+ Message *message,
+ GList *listener)
+{
+ Listener *lst;
+
+ lst = listener->data;
+ lst->blocked = TRUE;
+}
+
+static void
+unblock_listener (GeditMessageBus *bus,
+ Message *message,
+ GList *listener)
+{
+ Listener *lst;
+
+ lst = listener->data;
+ lst->blocked = FALSE;
+}
+
+static void
+dispatch_message_real (GeditMessageBus *bus,
+ Message *msg,
+ GeditMessage *message)
+{
+ GList *item;
+
+ for (item = msg->listeners; item; item = item->next)
+ {
+ Listener *listener = (Listener *)item->data;
+
+ if (!listener->blocked)
+ {
+ listener->callback (bus, message, listener->user_data);
+ }
+ }
+}
+
+static void
+gedit_message_bus_dispatch_real (GeditMessageBus *bus,
+ GeditMessage *message)
+{
+ const gchar *object_path;
+ const gchar *method;
+ Message *msg;
+
+ object_path = gedit_message_get_object_path (message);
+ method = gedit_message_get_method (message);
+
+ g_return_if_fail (object_path != NULL);
+ g_return_if_fail (method != NULL);
+
+ msg = lookup_message (bus, object_path, method, FALSE);
+
+ if (msg)
+ {
+ dispatch_message_real (bus, msg, message);
+ }
+}
+
+static void
+dispatch_message (GeditMessageBus *bus,
+ GeditMessage *message)
+{
+ g_signal_emit (bus, message_bus_signals[DISPATCH], 0, message);
+}
+
+static gboolean
+idle_dispatch (GeditMessageBus *bus)
+{
+ GList *list;
+ GList *item;
+
+ /* make sure to set idle_id to 0 first so that any new async messages
+ will be queued properly */
+ bus->priv->idle_id = 0;
+
+ /* reverse queue to get correct delivery order */
+ list = g_list_reverse (bus->priv->message_queue);
+ bus->priv->message_queue = NULL;
+
+ for (item = list; item; item = item->next)
+ {
+ GeditMessage *msg = GEDIT_MESSAGE (item->data);
+
+ dispatch_message (bus, msg);
+ }
+
+ message_queue_free (list);
+ return FALSE;
+}
+
+typedef void (*MatchCallback) (GeditMessageBus *, Message *, GList *);
+
+static void
+process_by_id (GeditMessageBus *bus,
+ guint id,
+ MatchCallback processor)
+{
+ IdMap *idmap;
+
+ idmap = (IdMap *)g_hash_table_lookup (bus->priv->idmap, GINT_TO_POINTER (id));
+
+ if (idmap == NULL)
+ {
+ g_warning ("No handler registered with id `%d'", id);
+ return;
+ }
+
+ processor (bus, idmap->message, idmap->listener);
+}
+
+static void
+process_by_match (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ GeditMessageCallback callback,
+ gpointer user_data,
+ MatchCallback processor)
+{
+ Message *message;
+ GList *item;
+
+ message = lookup_message (bus, object_path, method, FALSE);
+
+ if (!message)
+ {
+ g_warning ("No such handler registered for %s.%s", object_path, method);
+ return;
+ }
+
+ for (item = message->listeners; item; item = item->next)
+ {
+ Listener *listener = (Listener *)item->data;
+
+ if (listener->callback == callback &&
+ listener->user_data == user_data)
+ {
+ processor (bus, message, item);
+ return;
+ }
+ }
+
+ g_warning ("No such handler registered for %s.%s", object_path, method);
+}
+
+static void
+free_type (gpointer data)
+{
+ g_slice_free (GType, data);
+}
+
+static void
+gedit_message_bus_init (GeditMessageBus *self)
+{
+ self->priv = gedit_message_bus_get_instance_private (self);
+
+ self->priv->messages = g_hash_table_new_full (message_identifier_hash,
+ message_identifier_equal,
+ NULL,
+ (GDestroyNotify) message_free);
+
+ self->priv->idmap = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ (GDestroyNotify) g_free);
+
+ self->priv->types = g_hash_table_new_full (message_identifier_hash,
+ message_identifier_equal,
+ (GDestroyNotify) message_identifier_free,
+ (GDestroyNotify) free_type);
+}
+
+/**
+ * gedit_message_bus_get_default:
+ *
+ * Get the default application #GeditMessageBus.
+ *
+ * Return value: (transfer none): the default #GeditMessageBus
+ *
+ */
+GeditMessageBus *
+gedit_message_bus_get_default (void)
+{
+ static GeditMessageBus *default_bus = NULL;
+
+ if (G_UNLIKELY (default_bus == NULL))
+ {
+ default_bus = g_object_new (GEDIT_TYPE_MESSAGE_BUS, NULL);
+
+ g_object_add_weak_pointer (G_OBJECT (default_bus),
+ (gpointer) &default_bus);
+ }
+
+ return default_bus;
+}
+
+/**
+ * gedit_message_bus_new:
+ *
+ * Create a new message bus. Use gedit_message_bus_get_default() to get the
+ * default, application wide, message bus. Creating a new bus is useful for
+ * associating a specific bus with for instance a #GeditWindow.
+ *
+ * Return value: a new #GeditMessageBus
+ *
+ */
+GeditMessageBus *
+gedit_message_bus_new (void)
+{
+ return GEDIT_MESSAGE_BUS (g_object_new (GEDIT_TYPE_MESSAGE_BUS, NULL));
+}
+
+/**
+ * gedit_message_bus_lookup:
+ * @bus: a #GeditMessageBus
+ * @object_path: the object path
+ * @method: the method
+ *
+ * Get the registered #GeditMessageType for @method at @object_path. The
+ * returned #GeditMessageType is owned by the bus and should not be unreffed.
+ *
+ * Return value: the registered #GeditMessageType or %NULL if no message type
+ * is registered for @method at @object_path
+ *
+ */
+GType
+gedit_message_bus_lookup (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method)
+{
+ MessageIdentifier *identifier;
+ GType *message_type;
+
+ g_return_val_if_fail (GEDIT_IS_MESSAGE_BUS (bus), G_TYPE_INVALID);
+ g_return_val_if_fail (object_path != NULL, G_TYPE_INVALID);
+ g_return_val_if_fail (method != NULL, G_TYPE_INVALID);
+
+ identifier = message_identifier_new (object_path, method);
+ message_type = g_hash_table_lookup (bus->priv->types, identifier);
+ message_identifier_free (identifier);
+
+ if (!message_type)
+ {
+ return G_TYPE_INVALID;
+ }
+ else
+ {
+ return *message_type;
+ }
+}
+
+/**
+ * gedit_message_bus_register:
+ * @bus: a #GeditMessageBus
+ * @message_type: the message type
+ * @object_path: the object path
+ * @method: the method to register
+ *
+ * Register a message on the bus. A message must be registered on the bus before
+ * it can be send. This function registers the type for @method at
+ * @object_path.
+ *
+ * This function emits a #GeditMessageBus::registered signal.
+ *
+ */
+void
+gedit_message_bus_register (GeditMessageBus *bus,
+ GType message_type,
+ const gchar *object_path,
+ const gchar *method)
+{
+ MessageIdentifier *identifier;
+ GType *ntype;
+
+ g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus));
+ g_return_if_fail (gedit_message_is_valid_object_path (object_path));
+ g_return_if_fail (g_type_is_a (message_type, GEDIT_TYPE_MESSAGE));
+
+ if (gedit_message_bus_is_registered (bus, object_path, method))
+ {
+ g_warning ("Message type for '%s.%s' is already registered",
+ object_path,
+ method);
+ }
+
+ identifier = message_identifier_new (object_path, method);
+ ntype = g_slice_new (GType);
+
+ *ntype = message_type;
+
+ g_hash_table_insert (bus->priv->types,
+ identifier,
+ ntype);
+
+ g_signal_emit (bus,
+ message_bus_signals[REGISTERED],
+ 0,
+ object_path,
+ method);
+}
+
+static void
+gedit_message_bus_unregister_real (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ gboolean remove_from_store)
+{
+ MessageIdentifier *identifier;
+
+ identifier = message_identifier_new (object_path, method);
+
+ if (!remove_from_store || g_hash_table_remove (bus->priv->types,
+ identifier))
+ {
+ g_signal_emit (bus,
+ message_bus_signals[UNREGISTERED],
+ 0,
+ object_path,
+ method);
+ }
+
+ message_identifier_free (identifier);
+}
+
+/**
+ * gedit_message_bus_unregister:
+ * @bus: a #GeditMessageBus
+ * @object_path: the object path
+ * @method: the method
+ *
+ * Unregisters a previously registered message type. This is especially useful
+ * for plugins which should unregister message types when they are deactivated.
+ *
+ * This function emits the #GeditMessageBus::unregistered signal.
+ *
+ */
+void
+gedit_message_bus_unregister (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method)
+{
+ g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus));
+ g_return_if_fail (object_path != NULL);
+ g_return_if_fail (method != NULL);
+
+ gedit_message_bus_unregister_real (bus,
+ object_path,
+ method,
+ TRUE);
+}
+
+typedef struct
+{
+ GeditMessageBus *bus;
+ const gchar *object_path;
+} UnregisterInfo;
+
+static gboolean
+unregister_each (MessageIdentifier *identifier,
+ GType *gtype,
+ UnregisterInfo *info)
+{
+ if (g_strcmp0 (identifier->object_path, info->object_path) == 0)
+ {
+ gedit_message_bus_unregister_real (info->bus,
+ identifier->object_path,
+ identifier->method,
+ FALSE);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * gedit_message_bus_unregister_all:
+ * @bus: a #GeditMessageBus
+ * @object_path: the object path
+ *
+ * Unregisters all message types for @object_path. This is especially useful for
+ * plugins which should unregister message types when they are deactivated.
+ *
+ * This function emits the #GeditMessageBus::unregistered signal for all
+ * unregistered message types.
+ *
+ */
+void
+gedit_message_bus_unregister_all (GeditMessageBus *bus,
+ const gchar *object_path)
+{
+ UnregisterInfo info = {bus, object_path};
+
+ g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus));
+ g_return_if_fail (object_path != NULL);
+
+ g_hash_table_foreach_remove (bus->priv->types,
+ (GHRFunc)unregister_each,
+ &info);
+}
+
+/**
+ * gedit_message_bus_is_registered:
+ * @bus: a #GeditMessageBus
+ * @object_path: the object path
+ * @method: the method
+ *
+ * Check whether a message type @method at @object_path is registered on the
+ * bus.
+ *
+ * Return value: %TRUE if the @method at @object_path is a registered message
+ * type on the bus
+ *
+ */
+gboolean
+gedit_message_bus_is_registered (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method)
+{
+ MessageIdentifier *identifier;
+ gboolean ret;
+
+ g_return_val_if_fail (GEDIT_IS_MESSAGE_BUS (bus), FALSE);
+ g_return_val_if_fail (object_path != NULL, FALSE);
+ g_return_val_if_fail (method != NULL, FALSE);
+
+ identifier = message_identifier_new (object_path, method);
+ ret = g_hash_table_lookup (bus->priv->types, identifier) != NULL;
+ message_identifier_free (identifier);
+
+ return ret;
+}
+
+typedef struct
+{
+ GeditMessageBusForeach func;
+ gpointer user_data;
+} ForeachInfo;
+
+static void
+foreach_type (MessageIdentifier *identifier,
+ GType *message_type,
+ ForeachInfo *info)
+{
+ info->func (identifier->object_path,
+ identifier->method,
+ info->user_data);
+}
+
+/**
+ * gedit_message_bus_foreach:
+ * @bus: the #GeditMessageBus
+ * @func: (scope call): the callback function
+ * @user_data: the user data to supply to the callback function
+ *
+ * Calls @func for each message type registered on the bus
+ *
+ */
+void
+gedit_message_bus_foreach (GeditMessageBus *bus,
+ GeditMessageBusForeach func,
+ gpointer user_data)
+{
+ ForeachInfo info = {func, user_data};
+
+ g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus));
+ g_return_if_fail (func != NULL);
+
+ g_hash_table_foreach (bus->priv->types, (GHFunc)foreach_type, &info);
+}
+
+/**
+ * gedit_message_bus_connect:
+ * @bus: a #GeditMessageBus
+ * @object_path: the object path
+ * @method: the method
+ * @callback: function to be called when message @method at @object_path is sent
+ * @user_data: (allow-none): user_data to use for the callback
+ * @destroy_data: (allow-none): function to evoke with @user_data as argument when @user_data
+ * needs to be freed
+ *
+ * Connect a callback handler to be evoked when message @method at @object_path
+ * is sent over the bus.
+ *
+ * Return value: the callback identifier
+ *
+ */
+guint
+gedit_message_bus_connect (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ GeditMessageCallback callback,
+ gpointer user_data,
+ GDestroyNotify destroy_data)
+{
+ Message *message;
+
+ g_return_val_if_fail (GEDIT_IS_MESSAGE_BUS (bus), 0);
+ g_return_val_if_fail (object_path != NULL, 0);
+ g_return_val_if_fail (method != NULL, 0);
+ g_return_val_if_fail (callback != NULL, 0);
+
+ /* lookup the message and create if it does not exist yet */
+ message = lookup_message (bus, object_path, method, TRUE);
+
+ return add_listener (bus, message, callback, user_data, destroy_data);
+}
+
+/**
+ * gedit_message_bus_disconnect:
+ * @bus: a #GeditMessageBus
+ * @id: the callback id as returned by gedit_message_bus_connect()
+ *
+ * Disconnects a previously connected message callback.
+ *
+ */
+void
+gedit_message_bus_disconnect (GeditMessageBus *bus,
+ guint id)
+{
+ g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus));
+
+ process_by_id (bus, id, remove_listener);
+}
+
+/**
+ * gedit_message_bus_disconnect_by_func:
+ * @bus: a #GeditMessageBus
+ * @object_path: the object path
+ * @method: the method
+ * @callback: (scope call): the connected callback
+ * @user_data: the user_data with which the callback was connected
+ *
+ * Disconnects a previously connected message callback by matching the
+ * provided callback function and user_data. See also
+ * gedit_message_bus_disconnect().
+ *
+ */
+void
+gedit_message_bus_disconnect_by_func (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ GeditMessageCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus));
+
+ process_by_match (bus,
+ object_path,
+ method,
+ callback,
+ user_data,
+ remove_listener);
+}
+
+/**
+ * gedit_message_bus_block:
+ * @bus: a #GeditMessageBus
+ * @id: the callback id
+ *
+ * Blocks evoking the callback specified by @id. Unblock the callback by
+ * using gedit_message_bus_unblock().
+ *
+ */
+void
+gedit_message_bus_block (GeditMessageBus *bus,
+ guint id)
+{
+ g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus));
+
+ process_by_id (bus, id, block_listener);
+}
+
+/**
+ * gedit_message_bus_block_by_func:
+ * @bus: a #GeditMessageBus
+ * @object_path: the object path
+ * @method: the method
+ * @callback: (scope call): the callback to block
+ * @user_data: the user_data with which the callback was connected
+ *
+ * Blocks evoking the callback that matches provided @callback and @user_data.
+ * Unblock the callback using gedit_message_bus_unblock_by_func().
+ *
+ */
+void
+gedit_message_bus_block_by_func (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ GeditMessageCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus));
+
+ process_by_match (bus,
+ object_path,
+ method,
+ callback,
+ user_data,
+ block_listener);
+}
+
+/**
+ * gedit_message_bus_unblock:
+ * @bus: a #GeditMessageBus
+ * @id: the callback id
+ *
+ * Unblocks the callback specified by @id.
+ *
+ */
+void
+gedit_message_bus_unblock (GeditMessageBus *bus,
+ guint id)
+{
+ g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus));
+
+ process_by_id (bus, id, unblock_listener);
+}
+
+/**
+ * gedit_message_bus_unblock_by_func:
+ * @bus: a #GeditMessageBus
+ * @object_path: the object path
+ * @method: the method
+ * @callback: (scope call): the callback to block
+ * @user_data: the user_data with which the callback was connected
+ *
+ * Unblocks the callback that matches provided @callback and @user_data.
+ *
+ */
+void
+gedit_message_bus_unblock_by_func (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ GeditMessageCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus));
+
+ process_by_match (bus,
+ object_path,
+ method,
+ callback,
+ user_data,
+ unblock_listener);
+}
+
+static void
+send_message_real (GeditMessageBus *bus,
+ GeditMessage *message)
+{
+ bus->priv->message_queue = g_list_prepend (bus->priv->message_queue,
+ g_object_ref (message));
+
+ if (bus->priv->idle_id == 0)
+ {
+ bus->priv->idle_id = g_idle_add_full (G_PRIORITY_HIGH,
+ (GSourceFunc)idle_dispatch,
+ bus,
+ NULL);
+ }
+}
+
+/**
+ * gedit_message_bus_send_message:
+ * @bus: a #GeditMessageBus
+ * @message: the message to send
+ *
+ * This sends the provided @message asynchronously over the bus. To send
+ * a message synchronously, use gedit_message_bus_send_message_sync(). The
+ * convenience function gedit_message_bus_send() can be used to easily send
+ * a message without constructing the message object explicitly first.
+ *
+ */
+void
+gedit_message_bus_send_message (GeditMessageBus *bus,
+ GeditMessage *message)
+{
+ g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus));
+ g_return_if_fail (GEDIT_IS_MESSAGE (message));
+
+ send_message_real (bus, message);
+}
+
+/**
+ * gedit_message_bus_send_message_sync:
+ * @bus: a #GeditMessageBus
+ * @message: the message to send
+ *
+ * This sends the provided @message synchronously over the bus. To send
+ * a message asynchronously, use gedit_message_bus_send_message(). The
+ * convenience function gedit_message_bus_send_sync() can be used to easily send
+ * a message without constructing the message object explicitly first.
+ *
+ */
+void
+gedit_message_bus_send_message_sync (GeditMessageBus *bus,
+ GeditMessage *message)
+{
+ g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus));
+ g_return_if_fail (GEDIT_IS_MESSAGE (message));
+
+ dispatch_message (bus, message);
+}
+
+static GeditMessage *
+create_message (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ const gchar *first_property,
+ va_list var_args)
+{
+ GType message_type;
+ GeditMessage *msg;
+
+ message_type = gedit_message_bus_lookup (bus, object_path, method);
+
+ if (message_type == G_TYPE_INVALID)
+ {
+ g_warning ("Could not find message type for '%s.%s'",
+ object_path,
+ method);
+
+ return NULL;
+ }
+
+ msg = GEDIT_MESSAGE (g_object_new_valist (message_type,
+ first_property,
+ var_args));
+
+ if (msg)
+ {
+ g_object_set (msg,
+ "object_path",
+ object_path,
+ "method",
+ method,
+ NULL);
+ }
+
+ return msg;
+}
+
+/**
+ * gedit_message_bus_send:
+ * @bus: a #GeditMessageBus
+ * @object_path: the object path
+ * @method: the method
+ * @first_property: the first property
+ * @...: NULL terminated list of key/value pairs
+ *
+ * This provides a convenient way to quickly send a message @method at
+ * @object_path asynchronously over the bus. The variable argument list
+ * specifies key (string) value pairs used to construct the message arguments.
+ * To send a message synchronously use gedit_message_bus_send_sync().
+ */
+void
+gedit_message_bus_send (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ const gchar *first_property,
+ ...)
+{
+ va_list var_args;
+ GeditMessage *message;
+
+ va_start (var_args, first_property);
+
+ message = create_message (bus,
+ object_path,
+ method,
+ first_property,
+ var_args);
+
+ if (message)
+ {
+ send_message_real (bus, message);
+ g_object_unref (message);
+ }
+ else
+ {
+ g_warning ("Could not instantiate message");
+ }
+
+ va_end (var_args);
+}
+
+/**
+ * gedit_message_bus_send_sync:
+ * @bus: a #GeditMessageBus
+ * @object_path: the object path
+ * @method: the method
+ * @first_property: the first property
+ * @...: (allow-none): %NULL terminated list of key/value pairs
+ *
+ * This provides a convenient way to quickly send a message @method at
+ * @object_path synchronously over the bus. The variable argument list
+ * specifies key (string) value pairs used to construct the message
+ * arguments. To send a message asynchronously use gedit_message_bus_send().
+ *
+ * Return value: (allow-none) (transfer full): the constructed #GeditMessage.
+ * The caller owns a reference to the #GeditMessage and should
+ * call g_object_unref() when it is no longer needed.
+ */
+GeditMessage *
+gedit_message_bus_send_sync (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ const gchar *first_property,
+ ...)
+{
+ va_list var_args;
+ GeditMessage *message;
+
+ va_start (var_args, first_property);
+ message = create_message (bus,
+ object_path,
+ method,
+ first_property,
+ var_args);
+
+ if (message)
+ {
+ dispatch_message (bus, message);
+ }
+
+ va_end (var_args);
+
+ return message;
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-message-bus.h b/gedit/gedit-message-bus.h
new file mode 100644
index 0000000..e8403e0
--- /dev/null
+++ b/gedit/gedit-message-bus.h
@@ -0,0 +1,154 @@
+/*
+ * gedit-message-bus.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2008-2010 - Jesse van den Kieboom
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef GEDIT_MESSAGE_BUS_H
+#define GEDIT_MESSAGE_BUS_H
+
+#include <glib-object.h>
+#include <gedit/gedit-message.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_MESSAGE_BUS (gedit_message_bus_get_type ())
+#define GEDIT_MESSAGE_BUS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_MESSAGE_BUS, GeditMessageBus))
+#define GEDIT_MESSAGE_BUS_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_MESSAGE_BUS, GeditMessageBus const))
+#define GEDIT_MESSAGE_BUS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_MESSAGE_BUS, GeditMessageBusClass))
+#define GEDIT_IS_MESSAGE_BUS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_MESSAGE_BUS))
+#define GEDIT_IS_MESSAGE_BUS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_MESSAGE_BUS))
+#define GEDIT_MESSAGE_BUS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_MESSAGE_BUS, GeditMessageBusClass))
+
+typedef struct _GeditMessageBus GeditMessageBus;
+typedef struct _GeditMessageBusClass GeditMessageBusClass;
+typedef struct _GeditMessageBusPrivate GeditMessageBusPrivate;
+
+struct _GeditMessageBus
+{
+ GObject parent;
+
+ GeditMessageBusPrivate *priv;
+};
+
+struct _GeditMessageBusClass
+{
+ GObjectClass parent_class;
+
+ void (*dispatch) (GeditMessageBus *bus,
+ GeditMessage *message);
+ void (*registered) (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method);
+ void (*unregistered) (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method);
+};
+
+typedef void (* GeditMessageCallback) (GeditMessageBus *bus,
+ GeditMessage *message,
+ gpointer user_data);
+
+typedef void (* GeditMessageBusForeach) (gchar const *object_path,
+ gchar const *method,
+ gpointer user_data);
+
+GType gedit_message_bus_get_type (void) G_GNUC_CONST;
+
+GeditMessageBus *gedit_message_bus_get_default (void);
+GeditMessageBus *gedit_message_bus_new (void);
+
+GType gedit_message_bus_lookup (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method);
+
+void gedit_message_bus_register (GeditMessageBus *bus,
+ GType message_type,
+ const gchar *object_path,
+ const gchar *method);
+
+void gedit_message_bus_unregister (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method);
+
+void gedit_message_bus_unregister_all (GeditMessageBus *bus,
+ const gchar *object_path);
+
+gboolean gedit_message_bus_is_registered (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method);
+
+void gedit_message_bus_foreach (GeditMessageBus *bus,
+ GeditMessageBusForeach func,
+ gpointer user_data);
+
+guint gedit_message_bus_connect (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ GeditMessageCallback callback,
+ gpointer user_data,
+ GDestroyNotify destroy_data);
+
+void gedit_message_bus_disconnect (GeditMessageBus *bus,
+ guint id);
+
+void gedit_message_bus_disconnect_by_func (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ GeditMessageCallback callback,
+ gpointer user_data);
+
+void gedit_message_bus_block (GeditMessageBus *bus,
+ guint id);
+void gedit_message_bus_block_by_func (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ GeditMessageCallback callback,
+ gpointer user_data);
+
+void gedit_message_bus_unblock (GeditMessageBus *bus,
+ guint id);
+void gedit_message_bus_unblock_by_func (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ GeditMessageCallback callback,
+ gpointer user_data);
+
+void gedit_message_bus_send_message (GeditMessageBus *bus,
+ GeditMessage *message);
+void gedit_message_bus_send_message_sync (GeditMessageBus *bus,
+ GeditMessage *message);
+
+void gedit_message_bus_send (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ const gchar *first_property,
+ ...) G_GNUC_NULL_TERMINATED;
+
+GeditMessage *gedit_message_bus_send_sync (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ const gchar *first_property,
+ ...) G_GNUC_NULL_TERMINATED;
+
+G_END_DECLS
+
+#endif /* GEDIT_MESSAGE_BUS_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-message.c b/gedit/gedit-message.c
new file mode 100644
index 0000000..e015b5b
--- /dev/null
+++ b/gedit/gedit-message.c
@@ -0,0 +1,321 @@
+/*
+ * gedit-message.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2008, 2010 - Jesse van den Kieboom
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "gedit-message.h"
+
+#include <string.h>
+
+/**
+ * SECTION:gedit-message
+ * @short_description: message bus message object
+ * @include: gedit/gedit-message.h
+ *
+ * Communication on a #GeditMessageBus is done through messages. Messages are
+ * sent over the bus and received by connecting callbacks on the message bus.
+ * A #GeditMessage is an instantiation of a #GeditMessageType, containing
+ * values for the arguments as specified in the message type.
+ *
+ * A message can be seen as a method call, or signal emission depending on
+ * who is the sender and who is the receiver. There is no explicit distinction
+ * between methods and signals.
+ */
+
+struct _GeditMessagePrivate
+{
+ gchar *object_path;
+ gchar *method;
+};
+
+enum
+{
+ PROP_0,
+ PROP_OBJECT_PATH,
+ PROP_METHOD,
+ LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP];
+
+G_DEFINE_TYPE_WITH_PRIVATE (GeditMessage, gedit_message, G_TYPE_OBJECT)
+
+static void
+gedit_message_finalize (GObject *object)
+{
+ GeditMessage *message = GEDIT_MESSAGE (object);
+
+ g_free (message->priv->object_path);
+ g_free (message->priv->method);
+
+ G_OBJECT_CLASS (gedit_message_parent_class)->finalize (object);
+}
+
+static void
+gedit_message_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditMessage *msg = GEDIT_MESSAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value,
+ msg->priv->object_path);
+ break;
+ case PROP_METHOD:
+ g_value_set_string (value,
+ msg->priv->method);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_message_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditMessage *msg = GEDIT_MESSAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_free (msg->priv->object_path);
+ msg->priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_METHOD:
+ g_free (msg->priv->method);
+ msg->priv->method = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_message_class_init (GeditMessageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->finalize = gedit_message_finalize;
+
+ object_class->get_property = gedit_message_get_property;
+ object_class->set_property = gedit_message_set_property;
+
+ /**
+ * GeditMessage:object_path:
+ *
+ * The messages object path (e.g. /gedit/object/path).
+ *
+ */
+ properties[PROP_OBJECT_PATH] =
+ g_param_spec_string ("object-path",
+ "OBJECT_PATH",
+ "The message object path",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GeditMessage:method:
+ *
+ * The messages method.
+ *
+ */
+ properties[PROP_METHOD] =
+ g_param_spec_string ("method",
+ "METHOD",
+ "The message method",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+gedit_message_init (GeditMessage *self)
+{
+ self->priv = gedit_message_get_instance_private (self);
+}
+
+/**
+ * gedit_message_get_method:
+ * @message: the #GeditMessage
+ *
+ * Get the message method.
+ *
+ * Return value: the message method
+ *
+ */
+const gchar *
+gedit_message_get_method (GeditMessage *message)
+{
+ g_return_val_if_fail (GEDIT_IS_MESSAGE (message), NULL);
+
+ return message->priv->method;
+}
+
+/**
+ * gedit_message_get_object_path:
+ * @message: the #GeditMessage
+ *
+ * Get the message object path.
+ *
+ * Return value: the message object path
+ *
+ */
+const gchar *
+gedit_message_get_object_path (GeditMessage *message)
+{
+ g_return_val_if_fail (GEDIT_IS_MESSAGE (message), NULL);
+
+ return message->priv->object_path;
+}
+
+/**
+ * gedit_message_is_valid_object_path:
+ * @object_path: (allow-none): the object path
+ *
+ * Returns whether @object_path is a valid object path
+ *
+ * Return value: %TRUE if @object_path is a valid object path
+ *
+ */
+gboolean
+gedit_message_is_valid_object_path (const gchar *object_path)
+{
+ if (!object_path)
+ {
+ return FALSE;
+ }
+
+ /* needs to start with / */
+ if (*object_path != '/')
+ {
+ return FALSE;
+ }
+
+ while (*object_path)
+ {
+ if (*object_path == '/')
+ {
+ ++object_path;
+
+ if (!*object_path || !(g_ascii_isalpha (*object_path) || *object_path == '_'))
+ {
+ return FALSE;
+ }
+ }
+ else if (!(g_ascii_isalnum (*object_path) || *object_path == '_'))
+ {
+ return FALSE;
+ }
+
+ ++object_path;
+ }
+
+ return TRUE;
+}
+
+/**
+ * gedit_message_type_identifier:
+ * @object_path: (allow-none): the object path
+ * @method: (allow-none): the method
+ *
+ * Get the string identifier for @method at @object_path.
+ *
+ * Return value: the identifier for @method at @object_path
+ *
+ */
+gchar *
+gedit_message_type_identifier (const gchar *object_path,
+ const gchar *method)
+{
+ return g_strconcat (object_path, ".", method, NULL);
+}
+
+/**
+ * gedit_message_has:
+ * @message: the #GeditMessage
+ * @propname: the property name
+ *
+ * Check if a message has a certain property.
+ *
+ * Return Value: %TRUE if message has @propname, %FALSE otherwise
+ *
+ */
+gboolean
+gedit_message_has (GeditMessage *message,
+ const gchar *propname)
+{
+ GObjectClass *klass;
+
+ g_return_val_if_fail (GEDIT_IS_MESSAGE (message), FALSE);
+ g_return_val_if_fail (propname != NULL, FALSE);
+
+ klass = G_OBJECT_GET_CLASS (G_OBJECT (message));
+
+ return g_object_class_find_property (klass, propname) != NULL;
+}
+
+gboolean
+gedit_message_type_has (GType gtype,
+ const gchar *propname)
+{
+ GObjectClass *klass;
+ gboolean ret;
+
+ g_return_val_if_fail (g_type_is_a (gtype, GEDIT_TYPE_MESSAGE), FALSE);
+ g_return_val_if_fail (propname != NULL, FALSE);
+
+ klass = g_type_class_ref (gtype);
+ ret = g_object_class_find_property (klass, propname) != NULL;
+ g_type_class_unref (klass);
+
+ return ret;
+}
+
+gboolean
+gedit_message_type_check (GType gtype,
+ const gchar *propname,
+ GType value_type)
+{
+ GObjectClass *klass;
+ gboolean ret;
+ GParamSpec *spec;
+
+ g_return_val_if_fail (g_type_is_a (gtype, GEDIT_TYPE_MESSAGE), FALSE);
+ g_return_val_if_fail (propname != NULL, FALSE);
+
+ klass = g_type_class_ref (gtype);
+ spec = g_object_class_find_property (klass, propname);
+ ret = spec != NULL && spec->value_type == value_type;
+ g_type_class_unref (klass);
+
+ return ret;
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-message.h b/gedit/gedit-message.h
new file mode 100644
index 0000000..8f15734
--- /dev/null
+++ b/gedit/gedit-message.h
@@ -0,0 +1,77 @@
+/*
+ * gedit-message.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2008, 2010 - Jesse van den Kieboom
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef GEDIT_MESSAGE_H
+#define GEDIT_MESSAGE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_MESSAGE (gedit_message_get_type ())
+#define GEDIT_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_MESSAGE, GeditMessage))
+#define GEDIT_MESSAGE_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_MESSAGE, GeditMessage const))
+#define GEDIT_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_MESSAGE, GeditMessageClass))
+#define GEDIT_IS_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_MESSAGE))
+#define GEDIT_IS_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_MESSAGE))
+#define GEDIT_MESSAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_MESSAGE, GeditMessageClass))
+
+typedef struct _GeditMessage GeditMessage;
+typedef struct _GeditMessageClass GeditMessageClass;
+typedef struct _GeditMessagePrivate GeditMessagePrivate;
+
+struct _GeditMessage
+{
+ GObject parent;
+
+ GeditMessagePrivate *priv;
+};
+
+struct _GeditMessageClass
+{
+ GObjectClass parent_class;
+};
+
+GType gedit_message_get_type (void) G_GNUC_CONST;
+
+const gchar *gedit_message_get_object_path (GeditMessage *message);
+const gchar *gedit_message_get_method (GeditMessage *message);
+
+gboolean gedit_message_type_has (GType gtype,
+ const gchar *propname);
+
+gboolean gedit_message_type_check (GType gtype,
+ const gchar *propname,
+ GType value_type);
+
+gboolean gedit_message_has (GeditMessage *message,
+ const gchar *propname);
+
+gboolean gedit_message_is_valid_object_path (const gchar *object_path);
+gchar *gedit_message_type_identifier (const gchar *object_path,
+ const gchar *method);
+
+G_END_DECLS
+
+#endif /* GEDIT_MESSAGE_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-multi-notebook.c b/gedit/gedit-multi-notebook.c
new file mode 100644
index 0000000..46d75ed
--- /dev/null
+++ b/gedit/gedit-multi-notebook.c
@@ -0,0 +1,1125 @@
+/*
+ * gedit-multi-notebook.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Ignacio Casal Quinteiro
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "gedit-multi-notebook.h"
+
+#include "gedit-enum-types-private.h"
+#include "gedit-settings.h"
+#include "gedit-tab-private.h"
+#include "gedit-tab.h"
+
+struct _GeditMultiNotebookPrivate
+{
+ GtkWidget *active_notebook;
+ GList *notebooks;
+ gint total_tabs;
+
+ GeditTab *active_tab;
+
+ GeditNotebookShowTabsModeType show_tabs_mode;
+ GSettings *ui_settings;
+
+ guint show_tabs : 1;
+ guint removing_notebook : 1;
+};
+
+enum
+{
+ PROP_0,
+ PROP_ACTIVE_NOTEBOOK,
+ PROP_ACTIVE_TAB,
+ PROP_SHOW_TABS_MODE,
+ LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP];
+
+enum
+{
+ NOTEBOOK_ADDED,
+ NOTEBOOK_REMOVED,
+ TAB_ADDED,
+ TAB_REMOVED,
+ SWITCH_TAB,
+ TAB_CLOSE_REQUEST,
+ CREATE_WINDOW,
+ PAGE_REORDERED,
+ SHOW_POPUP_MENU,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE_WITH_PRIVATE (GeditMultiNotebook, gedit_multi_notebook, GTK_TYPE_GRID)
+
+static void remove_notebook (GeditMultiNotebook *mnb,
+ GtkWidget *notebook);
+
+static void update_tabs_visibility (GeditMultiNotebook *mnb);
+
+static void
+gedit_multi_notebook_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditMultiNotebook *mnb = GEDIT_MULTI_NOTEBOOK (object);
+
+ switch (prop_id)
+ {
+ case PROP_ACTIVE_NOTEBOOK:
+ g_value_set_object (value,
+ mnb->priv->active_notebook);
+ break;
+ case PROP_ACTIVE_TAB:
+ g_value_set_object (value,
+ mnb->priv->active_tab);
+ break;
+ case PROP_SHOW_TABS_MODE:
+ g_value_set_enum (value,
+ mnb->priv->show_tabs_mode);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_multi_notebook_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditMultiNotebook *mnb = GEDIT_MULTI_NOTEBOOK (object);
+
+ switch (prop_id)
+ {
+ case PROP_SHOW_TABS_MODE:
+ mnb->priv->show_tabs_mode = g_value_get_enum (value);
+ update_tabs_visibility (mnb);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_multi_notebook_dispose (GObject *object)
+{
+ GeditMultiNotebook *mnb = GEDIT_MULTI_NOTEBOOK (object);
+
+ g_clear_object (&mnb->priv->ui_settings);
+
+ G_OBJECT_CLASS (gedit_multi_notebook_parent_class)->dispose (object);
+}
+
+static void
+gedit_multi_notebook_finalize (GObject *object)
+{
+ GeditMultiNotebook *mnb = GEDIT_MULTI_NOTEBOOK (object);
+
+ g_list_free (mnb->priv->notebooks);
+
+ G_OBJECT_CLASS (gedit_multi_notebook_parent_class)->finalize (object);
+}
+
+static void
+gedit_multi_notebook_class_init (GeditMultiNotebookClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gedit_multi_notebook_dispose;
+ object_class->finalize = gedit_multi_notebook_finalize;
+ object_class->get_property = gedit_multi_notebook_get_property;
+ object_class->set_property = gedit_multi_notebook_set_property;
+
+ properties[PROP_ACTIVE_NOTEBOOK] =
+ g_param_spec_object ("active-notebook",
+ "Active Notebook",
+ "The Active Notebook",
+ GEDIT_TYPE_NOTEBOOK,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ properties[PROP_ACTIVE_TAB] =
+ g_param_spec_object ("active-tab",
+ "Active Tab",
+ "The Active Tab",
+ GEDIT_TYPE_TAB,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ properties[PROP_SHOW_TABS_MODE] =
+ g_param_spec_enum ("show-tabs-mode",
+ "Show Tabs Mode",
+ "When tabs should be shown",
+ GEDIT_TYPE_NOTEBOOK_SHOW_TABS_MODE_TYPE,
+ GEDIT_NOTEBOOK_SHOW_TABS_ALWAYS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ signals[NOTEBOOK_ADDED] =
+ g_signal_new ("notebook-added",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GeditMultiNotebookClass, notebook_added),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ GEDIT_TYPE_NOTEBOOK);
+ signals[NOTEBOOK_REMOVED] =
+ g_signal_new ("notebook-removed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GeditMultiNotebookClass, notebook_removed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ GEDIT_TYPE_NOTEBOOK);
+ signals[TAB_ADDED] =
+ g_signal_new ("tab-added",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GeditMultiNotebookClass, tab_added),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 2,
+ GEDIT_TYPE_NOTEBOOK,
+ GEDIT_TYPE_TAB);
+ signals[TAB_REMOVED] =
+ g_signal_new ("tab-removed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GeditMultiNotebookClass, tab_removed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 2,
+ GEDIT_TYPE_NOTEBOOK,
+ GEDIT_TYPE_TAB);
+ signals[SWITCH_TAB] =
+ g_signal_new ("switch-tab",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GeditMultiNotebookClass, switch_tab),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 4,
+ GEDIT_TYPE_NOTEBOOK,
+ GEDIT_TYPE_TAB,
+ GEDIT_TYPE_NOTEBOOK,
+ GEDIT_TYPE_TAB);
+ signals[TAB_CLOSE_REQUEST] =
+ g_signal_new ("tab-close-request",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GeditMultiNotebookClass, tab_close_request),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 2,
+ GEDIT_TYPE_NOTEBOOK,
+ GEDIT_TYPE_TAB);
+ signals[CREATE_WINDOW] =
+ g_signal_new ("create-window",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditMultiNotebookClass, create_window),
+ NULL, NULL, NULL,
+ GTK_TYPE_NOTEBOOK, 4,
+ GEDIT_TYPE_NOTEBOOK, GTK_TYPE_WIDGET,
+ G_TYPE_INT, G_TYPE_INT);
+ signals[PAGE_REORDERED] =
+ g_signal_new ("page-reordered",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GeditMultiNotebookClass, page_reordered),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 3,
+ GEDIT_TYPE_NOTEBOOK, GTK_TYPE_WIDGET,
+ G_TYPE_INT);
+ signals[SHOW_POPUP_MENU] =
+ g_signal_new ("show-popup-menu",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GeditMultiNotebookClass, show_popup_menu),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 2,
+ GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE,
+ GEDIT_TYPE_TAB);
+}
+
+static void
+notebook_show_popup_menu (GtkNotebook *notebook,
+ GdkEvent *event,
+ GeditTab *tab,
+ GeditMultiNotebook *mnb)
+{
+ g_signal_emit (G_OBJECT (mnb), signals[SHOW_POPUP_MENU], 0, event, tab);
+}
+
+static void
+notebook_tab_close_request (GeditNotebook *notebook,
+ GeditTab *tab,
+ GeditMultiNotebook *mnb)
+{
+ g_signal_emit (G_OBJECT (mnb), signals[TAB_CLOSE_REQUEST], 0,
+ notebook, tab);
+}
+
+static GtkNotebook *
+notebook_create_window (GeditNotebook *notebook,
+ GtkWidget *child,
+ gint x,
+ gint y,
+ GeditMultiNotebook *mnb)
+{
+ GtkNotebook *dest_notebook;
+
+ g_signal_emit (G_OBJECT (mnb), signals[CREATE_WINDOW], 0,
+ notebook, child, x, y, &dest_notebook);
+
+ return dest_notebook;
+}
+
+static void
+notebook_page_reordered (GeditNotebook *notebook,
+ GtkWidget *child,
+ guint page_num,
+ GeditMultiNotebook *mnb)
+{
+ g_signal_emit (G_OBJECT (mnb), signals[PAGE_REORDERED], 0, notebook,
+ child, page_num);
+}
+
+static void
+set_active_tab (GeditMultiNotebook *mnb,
+ GeditTab *tab)
+{
+ mnb->priv->active_tab = tab;
+ g_object_notify_by_pspec (G_OBJECT (mnb), properties[PROP_ACTIVE_TAB]);
+}
+
+static void
+notebook_page_removed (GtkNotebook *notebook,
+ GtkWidget *child,
+ guint page_num,
+ GeditMultiNotebook *mnb)
+{
+ GeditTab *tab = GEDIT_TAB (child);
+ guint num_tabs;
+ gboolean last_notebook;
+
+ --mnb->priv->total_tabs;
+ num_tabs = gtk_notebook_get_n_pages (notebook);
+ last_notebook = (mnb->priv->notebooks->next == NULL);
+
+ if (mnb->priv->total_tabs == 0)
+ {
+ set_active_tab (mnb, NULL);
+ }
+
+ g_signal_emit (G_OBJECT (mnb), signals[TAB_REMOVED], 0, notebook, tab);
+
+ /* Not last notebook but last tab of the notebook, this means we have
+ to remove the current notebook */
+ if (num_tabs == 0 && !mnb->priv->removing_notebook &&
+ !last_notebook)
+ {
+ remove_notebook (mnb, GTK_WIDGET (notebook));
+ }
+
+ update_tabs_visibility (mnb);
+}
+
+static void
+notebook_page_added (GtkNotebook *notebook,
+ GtkWidget *child,
+ guint page_num,
+ GeditMultiNotebook *mnb)
+{
+ GeditTab *tab = GEDIT_TAB (child);
+
+ ++mnb->priv->total_tabs;
+
+ update_tabs_visibility (mnb);
+
+ g_signal_emit (G_OBJECT (mnb), signals[TAB_ADDED], 0, notebook, tab);
+}
+
+static void
+notebook_switch_page (GtkNotebook *book,
+ GtkWidget *pg,
+ gint page_num,
+ GeditMultiNotebook *mnb)
+{
+ GeditTab *tab;
+
+ /* When we switch a tab from a notebook that it is not the active one
+ the switch page is emitted before the set focus, so we do this check
+ and we avoid to call switch page twice */
+ if (GTK_WIDGET (book) != mnb->priv->active_notebook)
+ return;
+
+ /* CHECK: I don't know why but it seems notebook_switch_page is called
+ two times every time the user change the active tab */
+ tab = GEDIT_TAB (gtk_notebook_get_nth_page (book, page_num));
+ if (tab != mnb->priv->active_tab)
+ {
+ GeditTab *old_tab;
+
+ old_tab = mnb->priv->active_tab;
+ set_active_tab (mnb, tab);
+ g_signal_emit (G_OBJECT (mnb), signals[SWITCH_TAB], 0,
+ mnb->priv->active_notebook, old_tab,
+ book, tab);
+ }
+}
+
+/* We need to figure out if the any of the internal widget of the notebook
+ has got the focus to set the active notebook */
+static void
+notebook_set_focus (GtkContainer *container,
+ GtkWidget *widget,
+ GeditMultiNotebook *mnb)
+{
+ if (GEDIT_IS_NOTEBOOK (container) &&
+ GTK_WIDGET (container) != mnb->priv->active_notebook)
+ {
+ gint page_num;
+
+ mnb->priv->active_notebook = GTK_WIDGET (container);
+
+ page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (container));
+ notebook_switch_page (GTK_NOTEBOOK (container), NULL,
+ page_num, mnb);
+
+ g_object_notify_by_pspec (G_OBJECT (mnb), properties[PROP_ACTIVE_NOTEBOOK]);
+ }
+}
+
+static void
+show_tabs_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer *data)
+{
+ update_tabs_visibility (GEDIT_MULTI_NOTEBOOK (data));
+}
+
+static void
+update_tabs_visibility (GeditMultiNotebook *mnb)
+{
+ gboolean show_tabs;
+ GList *l;
+
+ if (mnb->priv->notebooks == NULL)
+ return;
+
+ if (!mnb->priv->show_tabs)
+ {
+ show_tabs = FALSE;
+ }
+ else if (mnb->priv->notebooks->next == NULL) /* only one notebook */
+ {
+ switch (mnb->priv->show_tabs_mode)
+ {
+ case GEDIT_NOTEBOOK_SHOW_TABS_NEVER:
+ show_tabs = FALSE;
+ break;
+ case GEDIT_NOTEBOOK_SHOW_TABS_AUTO:
+ show_tabs = gtk_notebook_get_n_pages (GTK_NOTEBOOK (mnb->priv->notebooks->data)) > 1;
+ break;
+ case GEDIT_NOTEBOOK_SHOW_TABS_ALWAYS:
+ default:
+ show_tabs = TRUE;
+ break;
+ }
+ }
+ else
+ {
+ show_tabs = (mnb->priv->show_tabs_mode != GEDIT_NOTEBOOK_SHOW_TABS_NEVER);
+ }
+
+ g_signal_handlers_block_by_func (mnb, show_tabs_changed, NULL);
+
+ for (l = mnb->priv->notebooks; l != NULL; l = l->next)
+ {
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (l->data), show_tabs);
+ }
+
+ g_signal_handlers_unblock_by_func (mnb, show_tabs_changed, NULL);
+}
+
+static void
+connect_notebook_signals (GeditMultiNotebook *mnb,
+ GtkWidget *notebook)
+{
+ g_signal_connect (notebook,
+ "set-focus-child",
+ G_CALLBACK (notebook_set_focus),
+ mnb);
+ g_signal_connect (notebook,
+ "page-added",
+ G_CALLBACK (notebook_page_added),
+ mnb);
+ g_signal_connect (notebook,
+ "page-removed",
+ G_CALLBACK (notebook_page_removed),
+ mnb);
+ g_signal_connect (notebook,
+ "switch-page",
+ G_CALLBACK (notebook_switch_page),
+ mnb);
+ g_signal_connect (notebook,
+ "page-reordered",
+ G_CALLBACK (notebook_page_reordered),
+ mnb);
+ g_signal_connect (notebook,
+ "create-window",
+ G_CALLBACK (notebook_create_window),
+ mnb);
+ g_signal_connect (notebook,
+ "tab-close-request",
+ G_CALLBACK (notebook_tab_close_request),
+ mnb);
+ g_signal_connect (notebook,
+ "show-popup-menu",
+ G_CALLBACK (notebook_show_popup_menu),
+ mnb);
+ g_signal_connect (notebook,
+ "notify::show-tabs",
+ G_CALLBACK (show_tabs_changed),
+ mnb);
+}
+
+static void
+disconnect_notebook_signals (GeditMultiNotebook *mnb,
+ GtkWidget *notebook)
+{
+ g_signal_handlers_disconnect_by_func (notebook, notebook_set_focus,
+ mnb);
+ g_signal_handlers_disconnect_by_func (notebook, notebook_switch_page,
+ mnb);
+ g_signal_handlers_disconnect_by_func (notebook, notebook_page_added,
+ mnb);
+ g_signal_handlers_disconnect_by_func (notebook, notebook_page_removed,
+ mnb);
+ g_signal_handlers_disconnect_by_func (notebook, notebook_page_reordered,
+ mnb);
+ g_signal_handlers_disconnect_by_func (notebook, notebook_create_window,
+ mnb);
+ g_signal_handlers_disconnect_by_func (notebook, notebook_tab_close_request,
+ mnb);
+ g_signal_handlers_disconnect_by_func (notebook, notebook_show_popup_menu,
+ mnb);
+ g_signal_handlers_disconnect_by_func (notebook, show_tabs_changed,
+ mnb);
+}
+
+static void
+add_notebook (GeditMultiNotebook *mnb,
+ GtkWidget *notebook,
+ gboolean main_container)
+{
+ gtk_widget_set_hexpand (notebook, TRUE);
+ gtk_widget_set_vexpand (notebook, TRUE);
+
+ if (main_container)
+ {
+ gtk_container_add (GTK_CONTAINER (mnb), notebook);
+
+ mnb->priv->notebooks = g_list_append (mnb->priv->notebooks,
+ notebook);
+ }
+ else
+ {
+ GtkWidget *paned;
+ GtkWidget *parent;
+ GtkAllocation allocation;
+ GtkWidget *active_notebook = mnb->priv->active_notebook;
+ gint active_nb_pos;
+
+ paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_widget_show (paned);
+
+ /* First we remove the active container from its parent to make
+ this we add a ref to it*/
+ g_object_ref (active_notebook);
+ parent = gtk_widget_get_parent (active_notebook);
+ gtk_widget_get_allocation (active_notebook, &allocation);
+
+ gtk_container_remove (GTK_CONTAINER (parent), active_notebook);
+ gtk_container_add (GTK_CONTAINER (parent), paned);
+
+ gtk_paned_pack1 (GTK_PANED (paned), active_notebook, TRUE, FALSE);
+ g_object_unref (active_notebook);
+
+ gtk_paned_pack2 (GTK_PANED (paned), notebook, FALSE, FALSE);
+
+ /* We need to set the new paned in the right place */
+ gtk_paned_set_position (GTK_PANED (paned),
+ allocation.width / 2);
+
+ active_nb_pos = g_list_index (mnb->priv->notebooks,
+ active_notebook);
+ mnb->priv->notebooks = g_list_insert (mnb->priv->notebooks,
+ notebook,
+ active_nb_pos + 1);
+ }
+
+ gtk_widget_show (notebook);
+
+ connect_notebook_signals (mnb, notebook);
+
+ g_signal_emit (G_OBJECT (mnb), signals[NOTEBOOK_ADDED], 0, notebook);
+}
+
+static void
+remove_notebook (GeditMultiNotebook *mnb,
+ GtkWidget *notebook)
+{
+ GtkWidget *parent;
+ GtkWidget *grandpa;
+ GList *children;
+ GtkWidget *new_notebook;
+ GList *current;
+
+ if (mnb->priv->notebooks->next == NULL)
+ {
+ g_warning ("You are trying to remove the main notebook");
+ return;
+ }
+
+ current = g_list_find (mnb->priv->notebooks,
+ notebook);
+
+ if (current->next != NULL)
+ {
+ new_notebook = GTK_WIDGET (current->next->data);
+ }
+ else
+ {
+ new_notebook = GTK_WIDGET (mnb->priv->notebooks->data);
+ }
+
+ parent = gtk_widget_get_parent (notebook);
+
+ /* Now we destroy the widget, we get the children of parent and we destroy
+ parent too as the parent is an useless paned. Finally we add the child
+ into the grand parent */
+ g_object_ref (notebook);
+ mnb->priv->removing_notebook = TRUE;
+
+ gtk_widget_destroy (notebook);
+
+ mnb->priv->notebooks = g_list_remove (mnb->priv->notebooks,
+ notebook);
+
+ mnb->priv->removing_notebook = FALSE;
+
+ children = gtk_container_get_children (GTK_CONTAINER (parent));
+ if (children->next != NULL)
+ {
+ g_warning ("The parent is not a paned");
+ return;
+ }
+ grandpa = gtk_widget_get_parent (parent);
+
+ g_object_ref (children->data);
+ gtk_container_remove (GTK_CONTAINER (parent),
+ GTK_WIDGET (children->data));
+ gtk_widget_destroy (parent);
+ gtk_container_add (GTK_CONTAINER (grandpa),
+ GTK_WIDGET (children->data));
+ g_object_unref (children->data);
+ g_list_free (children);
+
+ disconnect_notebook_signals (mnb, notebook);
+
+ g_signal_emit (G_OBJECT (mnb), signals[NOTEBOOK_REMOVED], 0, notebook);
+ g_object_unref (notebook);
+
+ /* Let's make the active notebook grab the focus */
+ gtk_widget_grab_focus (new_notebook);
+}
+
+static void
+gedit_multi_notebook_init (GeditMultiNotebook *mnb)
+{
+ GeditMultiNotebookPrivate *priv;
+
+ mnb->priv = gedit_multi_notebook_get_instance_private (mnb);
+ priv = mnb->priv;
+
+ priv->removing_notebook = FALSE;
+
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (mnb),
+ GTK_ORIENTATION_VERTICAL);
+
+ priv->show_tabs_mode = GEDIT_NOTEBOOK_SHOW_TABS_ALWAYS;
+ priv->show_tabs = TRUE;
+
+ priv->ui_settings = g_settings_new ("org.gnome.gedit.preferences.ui");
+ g_settings_bind (priv->ui_settings,
+ GEDIT_SETTINGS_SHOW_TABS_MODE,
+ mnb,
+ "show-tabs-mode",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+
+ priv->active_notebook = gedit_notebook_new ();
+ add_notebook (mnb, priv->active_notebook, TRUE);
+}
+
+GeditMultiNotebook *
+gedit_multi_notebook_new ()
+{
+ return g_object_new (GEDIT_TYPE_MULTI_NOTEBOOK, NULL);
+}
+
+GeditNotebook *
+gedit_multi_notebook_get_active_notebook (GeditMultiNotebook *mnb)
+{
+ g_return_val_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb), NULL);
+
+ return GEDIT_NOTEBOOK (mnb->priv->active_notebook);
+}
+
+gint
+gedit_multi_notebook_get_n_notebooks (GeditMultiNotebook *mnb)
+{
+ g_return_val_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb), 0);
+
+ return g_list_length (mnb->priv->notebooks);
+}
+
+GeditNotebook *
+gedit_multi_notebook_get_nth_notebook (GeditMultiNotebook *mnb,
+ gint notebook_num)
+{
+ g_return_val_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb), NULL);
+
+ return g_list_nth_data (mnb->priv->notebooks, notebook_num);
+}
+
+GeditNotebook *
+gedit_multi_notebook_get_notebook_for_tab (GeditMultiNotebook *mnb,
+ GeditTab *tab)
+{
+ GList *l;
+ gint page_num;
+
+ g_return_val_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb), NULL);
+ g_return_val_if_fail (GEDIT_IS_TAB (tab), NULL);
+
+ l = mnb->priv->notebooks;
+
+ do
+ {
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (l->data),
+ GTK_WIDGET (tab));
+ if (page_num != -1)
+ break;
+
+ l = g_list_next (l);
+ } while (l != NULL && page_num == -1);
+
+ g_return_val_if_fail (page_num != -1, NULL);
+
+ return GEDIT_NOTEBOOK (l->data);
+}
+
+gint
+gedit_multi_notebook_get_notebook_num (GeditMultiNotebook *mnb,
+ GeditNotebook *notebook)
+{
+ g_return_val_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb), -1);
+ g_return_val_if_fail (GEDIT_IS_NOTEBOOK (notebook), -1);
+
+ return g_list_index (mnb->priv->notebooks, notebook);
+}
+
+gint
+gedit_multi_notebook_get_n_tabs (GeditMultiNotebook *mnb)
+{
+ g_return_val_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb), 0);
+
+ return mnb->priv->total_tabs;
+}
+
+gint
+gedit_multi_notebook_get_page_num (GeditMultiNotebook *mnb,
+ GeditTab *tab)
+{
+ GList *l;
+ gint real_page_num = 0;
+
+ for (l = mnb->priv->notebooks; l != NULL; l = g_list_next (l))
+ {
+ gint page_num;
+
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (l->data),
+ GTK_WIDGET (tab));
+
+ if (page_num != -1)
+ {
+ real_page_num += page_num;
+ break;
+ }
+
+ real_page_num += gtk_notebook_get_n_pages (GTK_NOTEBOOK (l->data));
+ }
+
+ return real_page_num;
+}
+
+GeditTab *
+gedit_multi_notebook_get_active_tab (GeditMultiNotebook *mnb)
+{
+ g_return_val_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb), NULL);
+
+ return (mnb->priv->active_tab == NULL) ?
+ NULL : GEDIT_TAB (mnb->priv->active_tab);
+}
+
+void
+gedit_multi_notebook_set_active_tab (GeditMultiNotebook *mnb,
+ GeditTab *tab)
+{
+ GList *l;
+ gint page_num;
+
+ g_return_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb));
+ g_return_if_fail (GEDIT_IS_TAB (tab) || tab == NULL);
+
+ /* use plain C cast since the active tab can be null */
+ if (tab == (GeditTab *) mnb->priv->active_tab)
+ {
+ return;
+ }
+
+ if (tab == NULL)
+ {
+ set_active_tab (mnb, NULL);
+ return;
+ }
+
+ l = mnb->priv->notebooks;
+
+ do
+ {
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (l->data),
+ GTK_WIDGET (tab));
+ if (page_num != -1)
+ break;
+
+ l = g_list_next (l);
+ } while (l != NULL && page_num == -1);
+
+ g_return_if_fail (page_num != -1);
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (l->data), page_num);
+
+ if (GTK_WIDGET (l->data) != mnb->priv->active_notebook)
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (l->data));
+ }
+}
+
+void
+gedit_multi_notebook_set_current_page (GeditMultiNotebook *mnb,
+ gint page_num)
+{
+ GList *l;
+ gint pages = 0;
+ gint single_num = page_num;
+
+ g_return_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb));
+
+ for (l = mnb->priv->notebooks; l != NULL; l = g_list_next (l))
+ {
+ gint p;
+
+ p = gtk_notebook_get_n_pages (GTK_NOTEBOOK (l->data));
+ pages += p;
+
+ if ((pages - 1) >= page_num)
+ break;
+
+ single_num -= p;
+ }
+
+ if (l == NULL)
+ return;
+
+ if (GTK_WIDGET (l->data) != mnb->priv->active_notebook)
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (l->data));
+ }
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (l->data), single_num);
+}
+
+GList *
+gedit_multi_notebook_get_all_tabs (GeditMultiNotebook *mnb)
+{
+ GList *nbs;
+ GList *ret = NULL;
+
+ g_return_val_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb), NULL);
+
+ for (nbs = mnb->priv->notebooks; nbs != NULL; nbs = g_list_next (nbs))
+ {
+ GList *l, *children;
+
+ children = gtk_container_get_children (GTK_CONTAINER (nbs->data));
+
+ for (l = children; l != NULL; l = g_list_next (l))
+ {
+ ret = g_list_prepend (ret, l->data);
+ }
+
+ g_list_free (children);
+ }
+
+ ret = g_list_reverse (ret);
+
+ return ret;
+}
+
+void
+gedit_multi_notebook_close_tabs (GeditMultiNotebook *mnb,
+ const GList *tabs)
+{
+ GList *l;
+
+ g_return_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb));
+
+ for (l = (GList *)tabs; l != NULL; l = g_list_next (l))
+ {
+ GList *nbs;
+
+ for (nbs = mnb->priv->notebooks; nbs != NULL; nbs = g_list_next (nbs))
+ {
+ gint n;
+
+ n = gtk_notebook_page_num (GTK_NOTEBOOK (nbs->data),
+ GTK_WIDGET (l->data));
+
+ if (n != -1)
+ {
+ gtk_container_remove (GTK_CONTAINER (nbs->data),
+ GTK_WIDGET (l->data));
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * gedit_multi_notebook_close_all_tabs:
+ * @mnb: a #GeditMultiNotebook
+ *
+ * Closes all opened tabs.
+ */
+void
+gedit_multi_notebook_close_all_tabs (GeditMultiNotebook *mnb)
+{
+ GList *nbs, *l;
+
+ g_return_if_fail (GEDIT_MULTI_NOTEBOOK (mnb));
+
+ /* We copy the list because the main one is going to have the items
+ removed */
+ nbs = g_list_copy (mnb->priv->notebooks);
+
+ for (l = nbs; l != NULL; l = g_list_next (l))
+ {
+ gedit_notebook_remove_all_tabs (GEDIT_NOTEBOOK (l->data));
+ }
+
+ g_list_free (nbs);
+}
+
+void
+gedit_multi_notebook_add_new_notebook (GeditMultiNotebook *mnb)
+{
+ GtkWidget *notebook;
+ GeditTab *tab;
+
+ g_return_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb));
+
+ notebook = gedit_notebook_new ();
+ add_notebook (mnb, notebook, FALSE);
+
+ tab = _gedit_tab_new ();
+ gtk_widget_show (GTK_WIDGET (tab));
+
+ /* When gtk_notebook_insert_page is called the focus is set in
+ the notebook, we don't want this to happen until the page is added.
+ Also we don't want to call switch_page when we add the tab
+ but when we switch the notebook. */
+ g_signal_handlers_block_by_func (notebook, notebook_set_focus, mnb);
+ g_signal_handlers_block_by_func (notebook, notebook_switch_page, mnb);
+
+ gedit_notebook_add_tab (GEDIT_NOTEBOOK (notebook),
+ tab,
+ -1,
+ TRUE);
+
+ g_signal_handlers_unblock_by_func (notebook, notebook_switch_page, mnb);
+ g_signal_handlers_unblock_by_func (notebook, notebook_set_focus, mnb);
+
+ notebook_set_focus (GTK_CONTAINER (notebook), NULL, mnb);
+}
+
+void
+gedit_multi_notebook_add_new_notebook_with_tab (GeditMultiNotebook *mnb,
+ GeditTab *tab)
+{
+ GtkWidget *notebook;
+ GeditNotebook *old_notebook;
+
+ g_return_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb));
+ g_return_if_fail (GEDIT_IS_TAB (tab));
+
+ notebook = gedit_notebook_new ();
+ add_notebook (mnb, notebook, FALSE);
+
+ old_notebook = gedit_multi_notebook_get_notebook_for_tab (mnb, tab);
+
+ /* When gtk_notebook_insert_page is called the focus is set in
+ the notebook, we don't want this to happen until the page is added.
+ Also we don't want to call switch_page when we add the tab
+ but when we switch the notebook. */
+ g_signal_handlers_block_by_func (old_notebook, notebook_set_focus, mnb);
+ g_signal_handlers_block_by_func (old_notebook, notebook_switch_page, mnb);
+
+ gedit_notebook_move_tab (old_notebook,
+ GEDIT_NOTEBOOK (notebook),
+ tab,
+ -1);
+
+ g_signal_handlers_unblock_by_func (old_notebook, notebook_switch_page, mnb);
+ g_signal_handlers_unblock_by_func (old_notebook, notebook_set_focus, mnb);
+
+ notebook_set_focus (GTK_CONTAINER (notebook), NULL, mnb);
+}
+
+void
+gedit_multi_notebook_remove_active_notebook (GeditMultiNotebook *mnb)
+{
+ g_return_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb));
+
+ gedit_notebook_remove_all_tabs (GEDIT_NOTEBOOK (mnb->priv->active_notebook));
+}
+
+void
+gedit_multi_notebook_previous_notebook (GeditMultiNotebook *mnb)
+{
+ GList *current;
+ GtkWidget *notebook;
+
+ g_return_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb));
+
+ current = g_list_find (mnb->priv->notebooks,
+ mnb->priv->active_notebook);
+
+ if (current->prev != NULL)
+ notebook = GTK_WIDGET (current->prev->data);
+ else
+ notebook = GTK_WIDGET (g_list_last (mnb->priv->notebooks)->data);
+
+ gtk_widget_grab_focus (notebook);
+}
+
+void
+gedit_multi_notebook_next_notebook (GeditMultiNotebook *mnb)
+{
+ GList *current;
+ GtkWidget *notebook;
+
+ g_return_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb));
+
+ current = g_list_find (mnb->priv->notebooks,
+ mnb->priv->active_notebook);
+
+ if (current->next != NULL)
+ notebook = GTK_WIDGET (current->next->data);
+ else
+ notebook = GTK_WIDGET (mnb->priv->notebooks->data);
+
+ gtk_widget_grab_focus (notebook);
+}
+
+void
+gedit_multi_notebook_foreach_notebook (GeditMultiNotebook *mnb,
+ GtkCallback callback,
+ gpointer callback_data)
+{
+ GList *l;
+
+ g_return_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb));
+
+ for (l = mnb->priv->notebooks; l != NULL; l = g_list_next (l))
+ {
+ callback (GTK_WIDGET (l->data), callback_data);
+ }
+}
+
+void
+gedit_multi_notebook_foreach_tab (GeditMultiNotebook *mnb,
+ GtkCallback callback,
+ gpointer callback_data)
+{
+ GList *nb;
+
+ g_return_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb));
+
+ for (nb = mnb->priv->notebooks; nb != NULL; nb = g_list_next (nb))
+ {
+ GList *l, *children;
+
+ children = gtk_container_get_children (GTK_CONTAINER (nb->data));
+
+ for (l = children; l != NULL; l = g_list_next (l))
+ {
+ callback (GTK_WIDGET (l->data), callback_data);
+ }
+
+ g_list_free (children);
+ }
+}
+
+/* We only use this to hide tabs in fullscreen mode so for now
+ * we do not have a real property etc.
+ */
+void
+_gedit_multi_notebook_set_show_tabs (GeditMultiNotebook *mnb,
+ gboolean show)
+{
+ g_return_if_fail (GEDIT_IS_MULTI_NOTEBOOK (mnb));
+
+ mnb->priv->show_tabs = show != FALSE;
+
+ update_tabs_visibility (mnb);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-multi-notebook.h b/gedit/gedit-multi-notebook.h
new file mode 100644
index 0000000..2022bd8
--- /dev/null
+++ b/gedit/gedit-multi-notebook.h
@@ -0,0 +1,148 @@
+/*
+ * gedit-multi-notebook.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Ignacio Casal Quinteiro
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+
+#ifndef GEDIT_MULTI_NOTEBOOK_H
+#define GEDIT_MULTI_NOTEBOOK_H
+
+#include <gtk/gtk.h>
+
+#include "gedit-tab.h"
+#include "gedit-notebook.h"
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_MULTI_NOTEBOOK (gedit_multi_notebook_get_type ())
+#define GEDIT_MULTI_NOTEBOOK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_MULTI_NOTEBOOK, GeditMultiNotebook))
+#define GEDIT_MULTI_NOTEBOOK_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_MULTI_NOTEBOOK, GeditMultiNotebook const))
+#define GEDIT_MULTI_NOTEBOOK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_MULTI_NOTEBOOK, GeditMultiNotebookClass))
+#define GEDIT_IS_MULTI_NOTEBOOK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_MULTI_NOTEBOOK))
+#define GEDIT_IS_MULTI_NOTEBOOK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_MULTI_NOTEBOOK))
+#define GEDIT_MULTI_NOTEBOOK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_MULTI_NOTEBOOK, GeditMultiNotebookClass))
+
+typedef struct _GeditMultiNotebook GeditMultiNotebook;
+typedef struct _GeditMultiNotebookClass GeditMultiNotebookClass;
+typedef struct _GeditMultiNotebookPrivate GeditMultiNotebookPrivate;
+
+struct _GeditMultiNotebook
+{
+ GtkGrid parent;
+
+ GeditMultiNotebookPrivate *priv;
+};
+
+struct _GeditMultiNotebookClass
+{
+ GtkGridClass parent_class;
+
+ /* Signals */
+ void (* notebook_added) (GeditMultiNotebook *mnb,
+ GeditNotebook *notebook);
+ void (* notebook_removed) (GeditMultiNotebook *mnb,
+ GeditNotebook *notebook);
+ void (* tab_added) (GeditMultiNotebook *mnb,
+ GeditNotebook *notebook,
+ GeditTab *tab);
+ void (* tab_removed) (GeditMultiNotebook *mnb,
+ GeditNotebook *notebook,
+ GeditTab *tab);
+ void (* switch_tab) (GeditMultiNotebook *mnb,
+ GeditNotebook *old_notebook,
+ GeditTab *old_tab,
+ GeditNotebook *new_notebook,
+ GeditTab *new_tab);
+ void (* tab_close_request) (GeditMultiNotebook *mnb,
+ GeditNotebook *notebook,
+ GeditTab *tab);
+ GtkNotebook * (* create_window) (GeditMultiNotebook *mnb,
+ GeditNotebook *notebook,
+ GtkWidget *page,
+ gint x,
+ gint y);
+ void (* page_reordered) (GeditMultiNotebook *mnb);
+ void (* show_popup_menu) (GeditMultiNotebook *mnb,
+ GdkEvent *event,
+ GeditTab *tab);
+};
+
+GType gedit_multi_notebook_get_type (void) G_GNUC_CONST;
+
+GeditMultiNotebook *gedit_multi_notebook_new (void);
+
+GeditNotebook *gedit_multi_notebook_get_active_notebook (GeditMultiNotebook *mnb);
+
+gint gedit_multi_notebook_get_n_notebooks (GeditMultiNotebook *mnb);
+
+GeditNotebook *gedit_multi_notebook_get_nth_notebook (GeditMultiNotebook *mnb,
+ gint notebook_num);
+
+GeditNotebook *gedit_multi_notebook_get_notebook_for_tab (GeditMultiNotebook *mnb,
+ GeditTab *tab);
+
+gint gedit_multi_notebook_get_notebook_num (GeditMultiNotebook *mnb,
+ GeditNotebook *notebook);
+
+gint gedit_multi_notebook_get_n_tabs (GeditMultiNotebook *mnb);
+
+gint gedit_multi_notebook_get_page_num (GeditMultiNotebook *mnb,
+ GeditTab *tab);
+
+GeditTab *gedit_multi_notebook_get_active_tab (GeditMultiNotebook *mnb);
+void gedit_multi_notebook_set_active_tab (GeditMultiNotebook *mnb,
+ GeditTab *tab);
+
+void gedit_multi_notebook_set_current_page (GeditMultiNotebook *mnb,
+ gint page_num);
+
+GList *gedit_multi_notebook_get_all_tabs (GeditMultiNotebook *mnb);
+
+void gedit_multi_notebook_close_tabs (GeditMultiNotebook *mnb,
+ const GList *tabs);
+
+void gedit_multi_notebook_close_all_tabs (GeditMultiNotebook *mnb);
+
+void gedit_multi_notebook_add_new_notebook (GeditMultiNotebook *mnb);
+
+void gedit_multi_notebook_add_new_notebook_with_tab (GeditMultiNotebook *mnb,
+ GeditTab *tab);
+
+void gedit_multi_notebook_remove_active_notebook (GeditMultiNotebook *mnb);
+
+void gedit_multi_notebook_previous_notebook (GeditMultiNotebook *mnb);
+void gedit_multi_notebook_next_notebook (GeditMultiNotebook *mnb);
+
+void gedit_multi_notebook_foreach_notebook (GeditMultiNotebook *mnb,
+ GtkCallback callback,
+ gpointer callback_data);
+
+void gedit_multi_notebook_foreach_tab (GeditMultiNotebook *mnb,
+ GtkCallback callback,
+ gpointer callback_data);
+
+void _gedit_multi_notebook_set_show_tabs (GeditMultiNotebook *mnb,
+ gboolean show);
+
+G_END_DECLS
+
+#endif /* GEDIT_MULTI_NOTEBOOK_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-notebook-popup-menu.c b/gedit/gedit-notebook-popup-menu.c
new file mode 100644
index 0000000..95ef458
--- /dev/null
+++ b/gedit/gedit-notebook-popup-menu.c
@@ -0,0 +1,297 @@
+/*
+ * gedit-notebook-popup-menu.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2011 - Ignacio Casal Quinteiro
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gedit-notebook-popup-menu.h"
+
+#include <glib/gi18n.h>
+
+#include "gedit-app.h"
+#include "gedit-app-private.h"
+#include "gedit-commands-private.h"
+#include "gedit-multi-notebook.h"
+
+struct _GeditNotebookPopupMenu
+{
+ GtkMenu parent_instance;
+
+ GeditWindow *window;
+ GeditTab *tab;
+
+ GSimpleActionGroup *action_group;
+};
+
+enum
+{
+ PROP_0,
+ PROP_WINDOW,
+ PROP_TAB,
+ LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP];
+
+G_DEFINE_TYPE (GeditNotebookPopupMenu, gedit_notebook_popup_menu, GTK_TYPE_MENU)
+
+static void
+gedit_notebook_popup_menu_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditNotebookPopupMenu *menu = GEDIT_NOTEBOOK_POPUP_MENU (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ menu->window = GEDIT_WINDOW (g_value_get_object (value));
+ break;
+
+ case PROP_TAB:
+ menu->tab = GEDIT_TAB (g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_notebook_popup_menu_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditNotebookPopupMenu *menu = GEDIT_NOTEBOOK_POPUP_MENU (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ g_value_set_object (value, menu->window);
+ break;
+
+ case PROP_TAB:
+ g_value_set_object (value, menu->tab);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+update_sensitivity (GeditNotebookPopupMenu *menu)
+{
+ GeditTabState state;
+ GeditMultiNotebook *mnb;
+ GtkNotebook *notebook;
+ gint page_num;
+ gint n_pages;
+ guint n_tabs;
+ GAction *action;
+
+ state = gedit_tab_get_state (menu->tab);
+
+ mnb = GEDIT_MULTI_NOTEBOOK (_gedit_window_get_multi_notebook (menu->window));
+
+ notebook = GTK_NOTEBOOK (gedit_multi_notebook_get_notebook_for_tab (mnb, menu->tab));
+ n_pages = gtk_notebook_get_n_pages (notebook);
+ n_tabs = gedit_multi_notebook_get_n_tabs(mnb);
+ page_num = gtk_notebook_page_num (notebook, GTK_WIDGET (menu->tab));
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (menu->action_group),
+ "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 (menu->action_group),
+ "move-to-new-window");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), n_tabs > 1);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (menu->action_group),
+ "move-to-new-tab-group");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), n_pages > 1);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (menu->action_group),
+ "move-left");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), page_num > 0);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (menu->action_group),
+ "move-right");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), page_num < n_pages - 1);
+}
+
+static void
+gedit_notebook_popup_menu_constructed (GObject *object)
+{
+ GeditNotebookPopupMenu *menu = GEDIT_NOTEBOOK_POPUP_MENU (object);
+
+ update_sensitivity (menu);
+
+ G_OBJECT_CLASS (gedit_notebook_popup_menu_parent_class)->constructed (object);
+}
+
+static void
+gedit_notebook_popup_menu_class_init (GeditNotebookPopupMenuClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = gedit_notebook_popup_menu_get_property;
+ object_class->set_property = gedit_notebook_popup_menu_set_property;
+ object_class->constructed = gedit_notebook_popup_menu_constructed;
+
+ properties[PROP_WINDOW] =
+ g_param_spec_object ("window",
+ "Window",
+ "The GeditWindow",
+ GEDIT_TYPE_WINDOW,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_TAB] =
+ g_param_spec_object ("tab",
+ "Tab",
+ "The GeditTab",
+ GEDIT_TYPE_TAB,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+on_move_left_activate (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditNotebookPopupMenu *menu = GEDIT_NOTEBOOK_POPUP_MENU (user_data);
+ GeditMultiNotebook *mnb;
+ GtkNotebook *notebook;
+ gint page_num;
+
+ mnb = GEDIT_MULTI_NOTEBOOK (_gedit_window_get_multi_notebook (menu->window));
+
+ notebook = GTK_NOTEBOOK (gedit_multi_notebook_get_notebook_for_tab (mnb, menu->tab));
+ page_num = gtk_notebook_page_num (notebook, GTK_WIDGET (menu->tab));
+
+ if (page_num > 0)
+ {
+ gtk_notebook_reorder_child (notebook,
+ GTK_WIDGET (menu->tab),
+ page_num - 1);
+ }
+}
+
+static void
+on_move_right_activate (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditNotebookPopupMenu *menu = GEDIT_NOTEBOOK_POPUP_MENU (user_data);
+ GeditMultiNotebook *mnb;
+ GtkNotebook *notebook;
+ gint page_num;
+ gint n_pages;
+
+ mnb = GEDIT_MULTI_NOTEBOOK (_gedit_window_get_multi_notebook (menu->window));
+
+ notebook = GTK_NOTEBOOK (gedit_multi_notebook_get_notebook_for_tab (mnb, menu->tab));
+ n_pages = gtk_notebook_get_n_pages (notebook);
+ page_num = gtk_notebook_page_num (notebook, GTK_WIDGET (menu->tab));
+
+ if (page_num < (n_pages - 1))
+ {
+ gtk_notebook_reorder_child (notebook,
+ GTK_WIDGET (menu->tab),
+ page_num + 1);
+ }
+}
+
+static void
+on_move_to_new_window_activate (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditNotebookPopupMenu *menu = GEDIT_NOTEBOOK_POPUP_MENU (user_data);
+
+ _gedit_window_move_tab_to_new_window (menu->window, menu->tab);
+}
+
+static void
+on_move_to_new_tab_group_activate (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditNotebookPopupMenu *menu = GEDIT_NOTEBOOK_POPUP_MENU (user_data);
+
+ _gedit_window_move_tab_to_new_tab_group (menu->window, menu->tab);
+}
+
+static void
+on_close_activate (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditNotebookPopupMenu *menu = GEDIT_NOTEBOOK_POPUP_MENU (user_data);
+
+ _gedit_cmd_file_close_tab (menu->tab, menu->window);
+}
+
+static GActionEntry action_entries[] = {
+ { "move-left", on_move_left_activate },
+ { "move-right", on_move_right_activate },
+ { "move-to-new-window", on_move_to_new_window_activate },
+ { "move-to-new-tab-group", on_move_to_new_tab_group_activate },
+ { "close", on_close_activate }
+};
+
+static void
+gedit_notebook_popup_menu_init (GeditNotebookPopupMenu *menu)
+{
+ gtk_menu_shell_bind_model (GTK_MENU_SHELL (menu),
+ _gedit_app_get_notebook_menu (GEDIT_APP (g_application_get_default ())),
+ "popup",
+ TRUE);
+
+ menu->action_group = g_simple_action_group_new ();
+ g_action_map_add_action_entries (G_ACTION_MAP (menu->action_group),
+ action_entries,
+ G_N_ELEMENTS (action_entries),
+ menu);
+
+ gtk_widget_insert_action_group (GTK_WIDGET (menu),
+ "popup",
+ G_ACTION_GROUP (menu->action_group));
+}
+
+GtkWidget *
+gedit_notebook_popup_menu_new (GeditWindow *window,
+ GeditTab *tab)
+{
+ return g_object_new (GEDIT_TYPE_NOTEBOOK_POPUP_MENU,
+ "window", window,
+ "tab", tab,
+ NULL);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-notebook-popup-menu.h b/gedit/gedit-notebook-popup-menu.h
new file mode 100644
index 0000000..10b3c6a
--- /dev/null
+++ b/gedit/gedit-notebook-popup-menu.h
@@ -0,0 +1,39 @@
+/*
+ * gedit-notebook-popup-menu.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2011 - Ignacio Casal Quinteiro
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef GEDIT_NOTEBOOK_POPUP_MENU_H
+#define GEDIT_NOTEBOOK_POPUP_MENU_H
+
+#include <gtk/gtk.h>
+#include "gedit-window.h"
+#include "gedit-tab.h"
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_NOTEBOOK_POPUP_MENU (gedit_notebook_popup_menu_get_type ())
+G_DECLARE_FINAL_TYPE (GeditNotebookPopupMenu, gedit_notebook_popup_menu, GEDIT, NOTEBOOK_POPUP_MENU, GtkMenu)
+
+GtkWidget *gedit_notebook_popup_menu_new (GeditWindow *window,
+ GeditTab *tab);
+
+G_END_DECLS
+
+#endif /* GEDIT_NOTEBOOK_POPUP_MENU_H */
diff --git a/gedit/gedit-notebook-stack-switcher.c b/gedit/gedit-notebook-stack-switcher.c
new file mode 100644
index 0000000..ecb661a
--- /dev/null
+++ b/gedit/gedit-notebook-stack-switcher.c
@@ -0,0 +1,362 @@
+/*
+ * gedit-notebook-stack-switcher.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Paolo Borelli
+ *
+ * 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-notebook-stack-switcher.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+/*
+ * This widget is a rather ugly kludge: it uses a GtkNotebook full of empty
+ * pages to create a stack switcher for the bottom pane. This is needed
+ * because we want to expose GtkStack in the API but we want the tabs styled
+ * as notebook tabs. Hopefully Gtk itself will grow a "tabs" stack switcher...
+ */
+
+struct _GeditNotebookStackSwitcherPrivate
+{
+ GtkWidget *notebook;
+ GtkStack *stack;
+};
+
+enum {
+ PROP_0,
+ PROP_STACK
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GeditNotebookStackSwitcher, gedit_notebook_stack_switcher, GTK_TYPE_BIN)
+
+static void
+gedit_notebook_stack_switcher_init (GeditNotebookStackSwitcher *switcher)
+{
+ GeditNotebookStackSwitcherPrivate *priv;
+
+ priv = gedit_notebook_stack_switcher_get_instance_private (switcher);
+ switcher->priv = priv;
+
+ priv->notebook = gtk_notebook_new ();
+
+ gtk_notebook_set_tab_pos (GTK_NOTEBOOK (priv->notebook), GTK_POS_BOTTOM);
+ gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (priv->notebook), FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (priv->notebook), 0);
+
+ gtk_widget_show (priv->notebook);
+ gtk_container_add (GTK_CONTAINER (switcher), priv->notebook);
+}
+
+static GtkWidget *
+find_notebook_child (GeditNotebookStackSwitcher *switcher,
+ GtkWidget *stack_child)
+{
+ GeditNotebookStackSwitcherPrivate *priv = switcher->priv;
+ GList *pages;
+ GList *p;
+ GtkWidget *ret = NULL;
+
+ if (stack_child == NULL)
+ {
+ return NULL;
+ }
+
+ pages = gtk_container_get_children (GTK_CONTAINER (priv->notebook));
+ for (p = pages; p != NULL; p = p->next)
+ {
+ GtkWidget *child;
+
+ child = g_object_get_data (p->data, "stack-child");
+ if (stack_child == child)
+ {
+ ret = p->data;
+ break;
+ }
+ }
+
+ g_list_free (pages);
+
+ return ret;
+}
+
+static void
+sync_label (GeditNotebookStackSwitcher *switcher,
+ GtkWidget *stack_child,
+ GtkWidget *notebook_child)
+{
+ GeditNotebookStackSwitcherPrivate *priv = switcher->priv;
+
+ if (stack_child != NULL && notebook_child != NULL)
+ {
+ gchar *title;
+
+ gtk_widget_set_visible (notebook_child,
+ gtk_widget_get_visible (stack_child));
+
+ gtk_container_child_get (GTK_CONTAINER (priv->stack), stack_child,
+ "title", &title,
+ NULL);
+
+ gtk_notebook_set_tab_label_text (GTK_NOTEBOOK (priv->notebook),
+ notebook_child,
+ title);
+
+ g_free (title);
+ }
+}
+
+static void
+on_child_prop_changed (GtkWidget *widget,
+ GParamSpec *pspec,
+ GeditNotebookStackSwitcher *switcher)
+{
+ GtkWidget *nb_child;
+
+ nb_child = find_notebook_child (switcher, widget);
+ sync_label (switcher, widget, nb_child);
+}
+
+static void
+on_child_changed (GtkWidget *widget,
+ GParamSpec *pspec,
+ GeditNotebookStackSwitcher *switcher)
+{
+ GtkNotebook *notebook;
+ GtkWidget *child;
+ GtkWidget *nb_child;
+ gint nb_page;
+
+ notebook = GTK_NOTEBOOK (switcher->priv->notebook);
+
+ child = gtk_stack_get_visible_child (GTK_STACK (widget));
+ nb_child = find_notebook_child (switcher, child);
+
+ nb_page = gtk_notebook_page_num (notebook, nb_child);
+
+ g_signal_handlers_block_by_func (widget, on_child_prop_changed, switcher);
+ gtk_notebook_set_current_page (notebook, nb_page);
+ g_signal_handlers_unblock_by_func (widget, on_child_prop_changed, switcher);
+
+ sync_label (switcher, child, nb_child);
+}
+
+static void
+on_stack_child_added (GtkStack *stack,
+ GtkWidget *widget,
+ GeditNotebookStackSwitcher *switcher)
+{
+ GeditNotebookStackSwitcherPrivate *priv = switcher->priv;
+ GtkWidget *dummy;
+
+ dummy = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ g_object_set_data (G_OBJECT (dummy), "stack-child", widget);
+ gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook),
+ dummy,
+ NULL);
+
+ g_signal_connect (widget, "notify::visible",
+ G_CALLBACK (on_child_prop_changed), switcher);
+ g_signal_connect (widget, "child-notify::title",
+ G_CALLBACK (on_child_prop_changed), switcher);
+
+ sync_label (switcher, widget, dummy);
+}
+
+static void
+on_stack_child_removed (GtkStack *stack,
+ GtkWidget *widget,
+ GeditNotebookStackSwitcher *switcher)
+{
+ GeditNotebookStackSwitcherPrivate *priv = switcher->priv;
+ GtkWidget *nb_child;
+
+ g_signal_handlers_disconnect_by_func (widget, on_child_prop_changed, switcher);
+
+ nb_child = find_notebook_child (switcher, widget);
+ gtk_container_remove (GTK_CONTAINER (priv->notebook), nb_child);
+}
+
+static void
+on_notebook_switch_page (GtkNotebook *notebook,
+ GtkWidget *page,
+ guint page_num,
+ GeditNotebookStackSwitcher *switcher)
+{
+ GeditNotebookStackSwitcherPrivate *priv = switcher->priv;
+ GtkWidget *child;
+
+ child = g_object_get_data (G_OBJECT (page), "stack-child");
+
+ /* NOTE: we make the assumption here that if there is no visible child
+ * it means that the child does not contain any child already, this is
+ * to avoid an assertion when closing gedit, since the remove signal
+ * runs first on the stack handler so we try to set a visible child
+ * when the stack already does not handle that child
+ */
+ if (child != NULL && gtk_stack_get_visible_child (priv->stack) != NULL)
+ {
+ gtk_stack_set_visible_child (priv->stack, child);
+ }
+}
+
+static void
+disconnect_signals (GeditNotebookStackSwitcher *switcher)
+{
+ GeditNotebookStackSwitcherPrivate *priv = switcher->priv;
+
+ g_signal_handlers_disconnect_by_func (priv->stack, on_stack_child_added, switcher);
+ g_signal_handlers_disconnect_by_func (priv->stack, on_stack_child_removed, switcher);
+ g_signal_handlers_disconnect_by_func (priv->stack, on_child_changed, switcher);
+ g_signal_handlers_disconnect_by_func (priv->stack, disconnect_signals, switcher);
+ g_signal_handlers_disconnect_by_func (priv->notebook, on_notebook_switch_page, switcher);
+}
+
+static void
+connect_signals (GeditNotebookStackSwitcher *switcher)
+{
+ GeditNotebookStackSwitcherPrivate *priv = switcher->priv;
+
+ g_signal_connect (priv->stack, "add",
+ G_CALLBACK (on_stack_child_added), switcher);
+ g_signal_connect (priv->stack, "remove",
+ G_CALLBACK (on_stack_child_removed), switcher);
+ g_signal_connect (priv->stack, "notify::visible-child",
+ G_CALLBACK (on_child_changed), switcher);
+ g_signal_connect_swapped (priv->stack, "destroy",
+ G_CALLBACK (disconnect_signals), switcher);
+ g_signal_connect (priv->notebook, "switch-page",
+ G_CALLBACK (on_notebook_switch_page), switcher);
+}
+
+void
+gedit_notebook_stack_switcher_set_stack (GeditNotebookStackSwitcher *switcher,
+ GtkStack *stack)
+{
+ GeditNotebookStackSwitcherPrivate *priv;
+
+ g_return_if_fail (GEDIT_IS_NOTEBOOK_STACK_SWITCHER (switcher));
+ g_return_if_fail (stack == NULL || GTK_IS_STACK (stack));
+
+ priv = switcher->priv;
+
+ if (priv->stack == stack)
+ return;
+
+ if (priv->stack)
+ {
+ disconnect_signals (switcher);
+ g_clear_object (&priv->stack);
+ }
+
+ if (stack)
+ {
+ priv->stack = g_object_ref (stack);
+ connect_signals (switcher);
+ }
+
+ g_object_notify (G_OBJECT (switcher), "stack");
+}
+
+GtkStack *
+gedit_notebook_stack_switcher_get_stack (GeditNotebookStackSwitcher *switcher)
+{
+ g_return_val_if_fail (GEDIT_IS_NOTEBOOK_STACK_SWITCHER (switcher), NULL);
+
+ return switcher->priv->stack;
+}
+
+static void
+gedit_notebook_stack_switcher_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditNotebookStackSwitcher *switcher = GEDIT_NOTEBOOK_STACK_SWITCHER (object);
+ GeditNotebookStackSwitcherPrivate *priv = switcher->priv;
+
+ switch (prop_id)
+ {
+ case PROP_STACK:
+ g_value_set_object (value, priv->stack);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_notebook_stack_switcher_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditNotebookStackSwitcher *switcher = GEDIT_NOTEBOOK_STACK_SWITCHER (object);
+
+ switch (prop_id)
+ {
+ case PROP_STACK:
+ gedit_notebook_stack_switcher_set_stack (switcher, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_notebook_stack_switcher_dispose (GObject *object)
+{
+ GeditNotebookStackSwitcher *switcher = GEDIT_NOTEBOOK_STACK_SWITCHER (object);
+
+ gedit_notebook_stack_switcher_set_stack (switcher, NULL);
+
+ G_OBJECT_CLASS (gedit_notebook_stack_switcher_parent_class)->dispose (object);
+}
+
+static void
+gedit_notebook_stack_switcher_class_init (GeditNotebookStackSwitcherClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = gedit_notebook_stack_switcher_get_property;
+ object_class->set_property = gedit_notebook_stack_switcher_set_property;
+ object_class->dispose = gedit_notebook_stack_switcher_dispose;
+
+ g_object_class_install_property (object_class,
+ PROP_STACK,
+ g_param_spec_object ("stack",
+ "Stack",
+ "Stack",
+ GTK_TYPE_STACK,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+GtkWidget *
+gedit_notebook_stack_switcher_new (void)
+{
+ return g_object_new (GEDIT_TYPE_NOTEBOOK_STACK_SWITCHER, NULL);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-notebook-stack-switcher.h b/gedit/gedit-notebook-stack-switcher.h
new file mode 100644
index 0000000..ec3f929
--- /dev/null
+++ b/gedit/gedit-notebook-stack-switcher.h
@@ -0,0 +1,71 @@
+/*
+ * gedit-notebook-stack-switcher.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Paolo Borelli
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_NOTEBOOK_STACK_SWITCHER_H
+#define GEDIT_NOTEBOOK_STACK_SWITCHER_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_NOTEBOOK_STACK_SWITCHER (gedit_notebook_stack_switcher_get_type())
+#define GEDIT_NOTEBOOK_STACK_SWITCHER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_NOTEBOOK_STACK_SWITCHER, GeditNotebookStackSwitcher))
+#define GEDIT_NOTEBOOK_STACK_SWITCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_NOTEBOOK_STACK_SWITCHER, GeditNotebookStackSwitcherClass))
+#define GEDIT_IS_NOTEBOOK_STACK_SWITCHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_NOTEBOOK_STACK_SWITCHER))
+#define GEDIT_IS_NOTEBOOK_STACK_SWITCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_NOTEBOOK_STACK_SWITCHER))
+#define GEDIT_NOTEBOOK_STACK_SWITCHER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_NOTEBOOK_STACK_SWITCHER, GeditNotebookStackSwitcherClass))
+
+typedef struct _GeditNotebookStackSwitcher GeditNotebookStackSwitcher;
+typedef struct _GeditNotebookStackSwitcherClass GeditNotebookStackSwitcherClass;
+typedef struct _GeditNotebookStackSwitcherPrivate GeditNotebookStackSwitcherPrivate;
+
+struct _GeditNotebookStackSwitcher
+{
+ GtkBin parent;
+
+ /*< private >*/
+ GeditNotebookStackSwitcherPrivate *priv;
+};
+
+struct _GeditNotebookStackSwitcherClass
+{
+ GtkBinClass parent_class;
+
+ /* Padding for future expansion */
+ void (*_gedit_reserved1) (void);
+ void (*_gedit_reserved2) (void);
+ void (*_gedit_reserved3) (void);
+ void (*_gedit_reserved4) (void);
+};
+
+GType gedit_notebook_stack_switcher_get_type (void) G_GNUC_CONST;
+
+GtkWidget *gedit_notebook_stack_switcher_new (void);
+
+void gedit_notebook_stack_switcher_set_stack (GeditNotebookStackSwitcher *switcher,
+ GtkStack *stack);
+
+GtkStack *gedit_notebook_stack_switcher_get_stack (GeditNotebookStackSwitcher *switcher);
+
+G_END_DECLS
+
+#endif /* GEDIT_NOTEBOOK_STACK_SWITCHER_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-notebook.c b/gedit/gedit-notebook.c
new file mode 100644
index 0000000..d06dd49
--- /dev/null
+++ b/gedit/gedit-notebook.c
@@ -0,0 +1,672 @@
+/*
+ * gedit-notebook.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2015 - Sébastien Wilmet
+ *
+ * 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/>.
+ */
+
+/* This file is a modified version of the epiphany file ephy-notebook.c
+ * Here the relevant copyright:
+ *
+ * Copyright (C) 2002 Christophe Fergeau
+ * Copyright (C) 2003 Marco Pesenti Gritti
+ * Copyright (C) 2003, 2004 Christian Persch
+ */
+
+#include "gedit-notebook.h"
+#include "gedit-tab-label.h"
+
+#define GEDIT_NOTEBOOK_GROUP_NAME "GeditNotebookGroup"
+
+/* The DND targets defined in GeditView start at 100.
+ * Those defined in GtkSourceView start at 200.
+ */
+#define TARGET_TAB 150
+
+struct _GeditNotebookPrivate
+{
+ /* History of focused pages. The first element contains the most recent
+ * one.
+ */
+ GList *focused_pages;
+
+ guint ignore_focused_page_update : 1;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GeditNotebook, gedit_notebook, GTK_TYPE_NOTEBOOK)
+
+enum
+{
+ TAB_CLOSE_REQUEST,
+ SHOW_POPUP_MENU,
+ CHANGE_TO_PAGE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+gedit_notebook_finalize (GObject *object)
+{
+ GeditNotebook *notebook = GEDIT_NOTEBOOK (object);
+
+ g_list_free (notebook->priv->focused_pages);
+
+ G_OBJECT_CLASS (gedit_notebook_parent_class)->finalize (object);
+}
+
+static void
+gedit_notebook_grab_focus (GtkWidget *widget)
+{
+ GtkNotebook *notebook = GTK_NOTEBOOK (widget);
+ gint current_page;
+ GtkWidget *tab;
+
+ current_page = gtk_notebook_get_current_page (notebook);
+ tab = gtk_notebook_get_nth_page (notebook, current_page);
+
+ if (tab != NULL)
+ {
+ gtk_widget_grab_focus (tab);
+ }
+}
+
+static gint
+find_tab_num_at_pos (GtkNotebook *notebook,
+ gint screen_x,
+ gint screen_y)
+{
+ GtkPositionType tab_pos;
+ GtkAllocation tab_allocation;
+ gint page_num;
+
+ tab_pos = gtk_notebook_get_tab_pos (notebook);
+
+ for (page_num = 0; ; page_num++)
+ {
+ GtkWidget *page;
+ GtkWidget *tab_label;
+ gint max_x, max_y, x_root, y_root;
+
+ page = gtk_notebook_get_nth_page (notebook, page_num);
+
+ if (page == NULL)
+ {
+ break;
+ }
+
+ tab_label = gtk_notebook_get_tab_label (notebook, page);
+ g_return_val_if_fail (tab_label != NULL, -1);
+
+ if (!gtk_widget_get_mapped (tab_label))
+ {
+ continue;
+ }
+
+ gdk_window_get_origin (gtk_widget_get_window (tab_label), &x_root, &y_root);
+
+ gtk_widget_get_allocation (tab_label, &tab_allocation);
+ max_x = x_root + tab_allocation.x + tab_allocation.width;
+ max_y = y_root + tab_allocation.y + tab_allocation.height;
+
+ if ((tab_pos == GTK_POS_TOP || tab_pos == GTK_POS_BOTTOM) &&
+ screen_x <= max_x)
+ {
+ return page_num;
+ }
+
+ if ((tab_pos == GTK_POS_LEFT || tab_pos == GTK_POS_RIGHT) &&
+ screen_y <= max_y)
+ {
+ return page_num;
+ }
+ }
+
+ return -1;
+}
+
+static gboolean
+gedit_notebook_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GtkNotebook *notebook = GTK_NOTEBOOK (widget);
+
+ if (event->type == GDK_BUTTON_PRESS &&
+ (event->state & gtk_accelerator_get_default_mod_mask ()) == 0)
+ {
+ gint tab_clicked;
+
+ tab_clicked = find_tab_num_at_pos (notebook, event->x_root, event->y_root);
+ if (tab_clicked >= 0)
+ {
+ GtkWidget *tab;
+
+ tab = gtk_notebook_get_nth_page (notebook, tab_clicked);
+ switch (event->button)
+ {
+ case GDK_BUTTON_SECONDARY:
+ g_signal_emit (G_OBJECT (widget), signals[SHOW_POPUP_MENU], 0, event, tab);
+ return GDK_EVENT_STOP;
+
+ case GDK_BUTTON_MIDDLE:
+ g_signal_emit (G_OBJECT (notebook), signals[TAB_CLOSE_REQUEST], 0, tab);
+ return GDK_EVENT_STOP;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ return GTK_WIDGET_CLASS (gedit_notebook_parent_class)->button_press_event (widget, event);
+}
+
+/*
+ * We need to override this because when we don't show the tabs, like in
+ * fullscreen we need to have wrap around too
+ */
+static gboolean
+gedit_notebook_change_current_page (GtkNotebook *notebook,
+ gint offset)
+{
+ gint current;
+
+ current = gtk_notebook_get_current_page (notebook);
+
+ if (current != -1)
+ {
+ gint target;
+ gboolean wrap_around;
+
+ target = current + offset;
+
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (notebook)),
+ "gtk-keynav-wrap-around", &wrap_around,
+ NULL);
+
+ if (wrap_around)
+ {
+ if (target < 0)
+ {
+ target = gtk_notebook_get_n_pages (notebook) - 1;
+ }
+ else if (target >= gtk_notebook_get_n_pages (notebook))
+ {
+ target = 0;
+ }
+ }
+
+ gtk_notebook_set_current_page (notebook, target);
+ }
+ else
+ {
+ gtk_widget_error_bell (GTK_WIDGET (notebook));
+ }
+
+ return TRUE;
+}
+
+static void
+gedit_notebook_switch_page (GtkNotebook *notebook,
+ GtkWidget *page,
+ guint page_num)
+{
+ GeditNotebookPrivate *priv = GEDIT_NOTEBOOK (notebook)->priv;
+
+ GTK_NOTEBOOK_CLASS (gedit_notebook_parent_class)->switch_page (notebook, page, page_num);
+
+ if (!priv->ignore_focused_page_update)
+ {
+ gint page_num;
+
+ /* Get again page_num and page, the signal handler may have
+ * changed them.
+ */
+ page_num = gtk_notebook_get_current_page (notebook);
+ if (page_num != -1)
+ {
+ GtkWidget *page = gtk_notebook_get_nth_page (notebook, page_num);
+ g_assert (page != NULL);
+
+ /* Remove the old page, we dont want to grow unnecessarily
+ * the list.
+ */
+ priv->focused_pages = g_list_remove (priv->focused_pages, page);
+
+ priv->focused_pages = g_list_prepend (priv->focused_pages, page);
+ }
+ }
+
+ /* give focus to the tab */
+ gtk_widget_grab_focus (page);
+}
+
+static void
+close_button_clicked_cb (GeditTabLabel *tab_label,
+ GeditNotebook *notebook)
+{
+ GeditTab *tab;
+
+ tab = gedit_tab_label_get_tab (tab_label);
+ g_signal_emit (notebook, signals[TAB_CLOSE_REQUEST], 0, tab);
+}
+
+static void
+switch_to_last_focused_page (GeditNotebook *notebook,
+ GeditTab *tab)
+{
+ if (notebook->priv->focused_pages != NULL)
+ {
+ GList *node;
+ GtkWidget *page;
+ gint page_num;
+
+ node = notebook->priv->focused_pages;
+ page = GTK_WIDGET (node->data);
+
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (notebook), page);
+ g_return_if_fail (page_num != -1);
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), page_num);
+ }
+}
+
+static void
+drag_data_received_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint timestamp)
+{
+ GtkWidget *notebook;
+ GtkWidget *new_notebook;
+ GtkWidget *page;
+
+ if (info != TARGET_TAB)
+ {
+ return;
+ }
+
+ notebook = gtk_drag_get_source_widget (context);
+
+ if (!GTK_IS_WIDGET (notebook))
+ {
+ return;
+ }
+
+ page = *(GtkWidget **) gtk_selection_data_get_data (selection_data);
+ g_return_if_fail (page != NULL);
+
+ /* We need to iterate and get the notebook of the target view
+ * because we can have several notebooks per window.
+ */
+ new_notebook = gtk_widget_get_ancestor (widget, GEDIT_TYPE_NOTEBOOK);
+ g_return_if_fail (new_notebook != NULL);
+
+ if (notebook != new_notebook)
+ {
+ gedit_notebook_move_tab (GEDIT_NOTEBOOK (notebook),
+ GEDIT_NOTEBOOK (new_notebook),
+ GEDIT_TAB (page),
+ 0);
+ }
+
+ gtk_drag_finish (context, TRUE, TRUE, timestamp);
+}
+
+static void
+gedit_notebook_page_removed (GtkNotebook *notebook,
+ GtkWidget *page,
+ guint page_num)
+{
+ GeditNotebookPrivate *priv = GEDIT_NOTEBOOK (notebook)->priv;
+ gboolean current_page;
+
+ /* The page removed was the current page. */
+ current_page = (priv->focused_pages != NULL &&
+ priv->focused_pages->data == page);
+
+ priv->focused_pages = g_list_remove (priv->focused_pages, page);
+
+ if (current_page)
+ {
+ switch_to_last_focused_page (GEDIT_NOTEBOOK (notebook),
+ GEDIT_TAB (page));
+ }
+}
+
+static void
+gedit_notebook_page_added (GtkNotebook *notebook,
+ GtkWidget *page,
+ guint page_num)
+{
+ GtkWidget *tab_label;
+ GeditView *view;
+
+ g_return_if_fail (GEDIT_IS_TAB (page));
+
+ tab_label = gtk_notebook_get_tab_label (notebook, page);
+ g_return_if_fail (GEDIT_IS_TAB_LABEL (tab_label));
+
+ /* For a DND from one notebook to another, the same tab_label can be
+ * used, so we need to connect the signal here.
+ * More precisely, the same tab_label is used when the drop zone is in
+ * the tab labels (not the GeditView), that is, when the DND is handled
+ * by the GtkNotebook implementation.
+ */
+ g_signal_connect (tab_label,
+ "close-clicked",
+ G_CALLBACK (close_button_clicked_cb),
+ notebook);
+
+ view = gedit_tab_get_view (GEDIT_TAB (page));
+ g_signal_connect (view,
+ "drag-data-received",
+ G_CALLBACK (drag_data_received_cb),
+ NULL);
+}
+
+static void
+gedit_notebook_remove (GtkContainer *container,
+ GtkWidget *widget)
+{
+ GtkNotebook *notebook = GTK_NOTEBOOK (container);
+ GeditNotebookPrivate *priv = GEDIT_NOTEBOOK (container)->priv;
+ GtkWidget *tab_label;
+ GeditView *view;
+
+ g_return_if_fail (GEDIT_IS_TAB (widget));
+
+ tab_label = gtk_notebook_get_tab_label (notebook, widget);
+ g_return_if_fail (GEDIT_IS_TAB_LABEL (tab_label));
+
+ /* For a DND from one notebook to another, the same tab_label can be
+ * used, so we need to disconnect the signal.
+ */
+ g_signal_handlers_disconnect_by_func (tab_label,
+ G_CALLBACK (close_button_clicked_cb),
+ notebook);
+
+ view = gedit_tab_get_view (GEDIT_TAB (widget));
+ g_signal_handlers_disconnect_by_func (view, drag_data_received_cb, NULL);
+
+ /* This is where GtkNotebook will remove the page. By doing so, it
+ * will also switch to a new page, messing up our focus list. So we
+ * set a flag here to ignore the switch temporarily.
+ */
+ priv->ignore_focused_page_update = TRUE;
+
+ if (GTK_CONTAINER_CLASS (gedit_notebook_parent_class)->remove != NULL)
+ {
+ GTK_CONTAINER_CLASS (gedit_notebook_parent_class)->remove (container,
+ widget);
+ }
+
+ priv->ignore_focused_page_update = FALSE;
+}
+
+static gboolean
+gedit_notebook_change_to_page (GeditNotebook *notebook,
+ gint page_num)
+{
+ gint n_pages;
+
+ n_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook));
+
+ if (page_num >= n_pages)
+ {
+ return FALSE;
+ }
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook),
+ page_num);
+
+ return TRUE;
+}
+
+static void
+gedit_notebook_class_init (GeditNotebookClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+ GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
+ GtkBindingSet *binding_set;
+ gint i;
+
+ object_class->finalize = gedit_notebook_finalize;
+
+ widget_class->grab_focus = gedit_notebook_grab_focus;
+ widget_class->button_press_event = gedit_notebook_button_press_event;
+
+ container_class->remove = gedit_notebook_remove;
+
+ notebook_class->change_current_page = gedit_notebook_change_current_page;
+ notebook_class->switch_page = gedit_notebook_switch_page;
+ notebook_class->page_removed = gedit_notebook_page_removed;
+ notebook_class->page_added = gedit_notebook_page_added;
+
+ klass->change_to_page = gedit_notebook_change_to_page;
+
+ signals[TAB_CLOSE_REQUEST] =
+ g_signal_new ("tab-close-request",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditNotebookClass, tab_close_request),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ GEDIT_TYPE_TAB);
+
+ signals[SHOW_POPUP_MENU] =
+ g_signal_new ("show-popup-menu",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GeditNotebookClass, show_popup_menu),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 2,
+ GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE,
+ GEDIT_TYPE_TAB);
+
+ signals[CHANGE_TO_PAGE] =
+ g_signal_new ("change-to-page",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GeditNotebookClass, change_to_page),
+ NULL, NULL, NULL,
+ G_TYPE_BOOLEAN, 1,
+ G_TYPE_INT);
+
+ binding_set = gtk_binding_set_by_class (klass);
+ for (i = 1; i < 10; i++)
+ {
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_KEY_0 + i, GDK_MOD1_MASK,
+ "change-to-page", 1,
+ G_TYPE_INT, i - 1);
+ }
+}
+
+/**
+ * gedit_notebook_new:
+ *
+ * Creates a new #GeditNotebook object.
+ *
+ * Returns: a new #GeditNotebook
+ */
+GtkWidget *
+gedit_notebook_new (void)
+{
+ return GTK_WIDGET (g_object_new (GEDIT_TYPE_NOTEBOOK, NULL));
+}
+
+static void
+gedit_notebook_init (GeditNotebook *notebook)
+{
+ notebook->priv = gedit_notebook_get_instance_private (notebook);
+
+ gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook), TRUE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (notebook), FALSE);
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), TRUE);
+ gtk_notebook_set_group_name (GTK_NOTEBOOK (notebook),
+ GEDIT_NOTEBOOK_GROUP_NAME);
+ gtk_container_set_border_width (GTK_CONTAINER (notebook), 0);
+}
+
+/**
+ * gedit_notebook_add_tab:
+ * @notebook: a #GeditNotebook
+ * @tab: a #GeditTab
+ * @position: the position where the @tab should be added
+ * @jump_to: %TRUE to set the @tab as active
+ *
+ * Adds the specified @tab to the @notebook.
+ */
+void
+gedit_notebook_add_tab (GeditNotebook *notebook,
+ GeditTab *tab,
+ gint position,
+ gboolean jump_to)
+{
+ GtkWidget *tab_label;
+ GeditView *view;
+ GtkTargetList *target_list;
+
+ g_return_if_fail (GEDIT_IS_NOTEBOOK (notebook));
+ g_return_if_fail (GEDIT_IS_TAB (tab));
+
+ tab_label = gedit_tab_label_new (tab);
+
+ gtk_notebook_insert_page (GTK_NOTEBOOK (notebook),
+ GTK_WIDGET (tab),
+ tab_label,
+ position);
+
+ gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (notebook),
+ GTK_WIDGET (tab),
+ TRUE);
+
+ gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (notebook),
+ GTK_WIDGET (tab),
+ TRUE);
+
+ gtk_container_child_set (GTK_CONTAINER (notebook),
+ GTK_WIDGET (tab),
+ "tab-expand", TRUE,
+ NULL);
+
+ /* Drag and drop support: move a tab to another notebook, with the drop
+ * zone in the GeditView. The drop zone in the tab labels is already
+ * implemented by GtkNotebook.
+ */
+ view = gedit_tab_get_view (tab);
+ target_list = gtk_drag_dest_get_target_list (GTK_WIDGET (view));
+
+ if (target_list != NULL)
+ {
+ gtk_target_list_add (target_list,
+ gdk_atom_intern_static_string ("GTK_NOTEBOOK_TAB"),
+ GTK_TARGET_SAME_APP,
+ TARGET_TAB);
+ }
+
+ /* The signal handler may have reordered the tabs */
+ position = gtk_notebook_page_num (GTK_NOTEBOOK (notebook),
+ GTK_WIDGET (tab));
+
+ if (jump_to)
+ {
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), position);
+ gtk_widget_grab_focus (GTK_WIDGET (tab));
+ }
+}
+
+/**
+ * gedit_notebook_move_tab:
+ * @src: a #GeditNotebook
+ * @dest: a #GeditNotebook
+ * @tab: a #GeditTab
+ * @dest_position: the position for @tab
+ *
+ * Moves @tab from @src to @dest.
+ * If @dest_position is greater than or equal to the number of tabs
+ * of the destination nootebook or negative, tab will be moved to the
+ * end of the tabs.
+ */
+void
+gedit_notebook_move_tab (GeditNotebook *src,
+ GeditNotebook *dest,
+ GeditTab *tab,
+ gint dest_position)
+{
+ g_return_if_fail (GEDIT_IS_NOTEBOOK (src));
+ g_return_if_fail (GEDIT_IS_NOTEBOOK (dest));
+ g_return_if_fail (src != dest);
+ g_return_if_fail (GEDIT_IS_TAB (tab));
+
+ /* Make sure the tab isn't destroyed while we move it. */
+ g_object_ref (tab);
+
+ /* Make sure the @src notebook isn't destroyed during the tab
+ * detachment, to prevent a crash in gtk_notebook_detach_tab(). In fact,
+ * if @tab is the last tab of @src, and if @src is not the last notebook
+ * of the GeditMultiNotebook, then @src will be destroyed when
+ * gtk_container_remove() is called by gtk_notebook_detach_tab().
+ */
+ g_object_ref (src);
+ gtk_notebook_detach_tab (GTK_NOTEBOOK (src), GTK_WIDGET (tab));
+ g_object_unref (src);
+
+ gedit_notebook_add_tab (dest, tab, dest_position, TRUE);
+
+ g_object_unref (tab);
+}
+
+/**
+ * gedit_notebook_remove_all_tabs:
+ * @notebook: a #GeditNotebook
+ *
+ * Removes all #GeditTab from @notebook.
+ */
+void
+gedit_notebook_remove_all_tabs (GeditNotebook *notebook)
+{
+ GList *tabs;
+ GList *t;
+
+ g_return_if_fail (GEDIT_IS_NOTEBOOK (notebook));
+
+ g_list_free (notebook->priv->focused_pages);
+ notebook->priv->focused_pages = NULL;
+
+ /* Remove tabs in reverse order since it is faster
+ * due to how GtkNotebook works.
+ */
+ tabs = gtk_container_get_children (GTK_CONTAINER (notebook));
+ for (t = g_list_last (tabs); t != NULL; t = t->prev)
+ {
+ GtkWidget *tab = t->data;
+ gtk_container_remove (GTK_CONTAINER (notebook), tab);
+ }
+
+ g_list_free (tabs);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-notebook.h b/gedit/gedit-notebook.h
new file mode 100644
index 0000000..e4a3e6b
--- /dev/null
+++ b/gedit/gedit-notebook.h
@@ -0,0 +1,100 @@
+/*
+ * gedit-notebook.h
+ * 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/>.
+ */
+
+/* This file is a modified version of the epiphany file ephy-notebook.h
+ * Here the relevant copyright:
+ *
+ * Copyright (C) 2002 Christophe Fergeau
+ * Copyright (C) 2003 Marco Pesenti Gritti
+ * Copyright (C) 2003, 2004 Christian Persch
+ *
+ */
+
+#ifndef GEDIT_NOTEBOOK_H
+#define GEDIT_NOTEBOOK_H
+
+#include <gedit/gedit-tab.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_NOTEBOOK (gedit_notebook_get_type ())
+#define GEDIT_NOTEBOOK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_NOTEBOOK, GeditNotebook))
+#define GEDIT_NOTEBOOK_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_NOTEBOOK, GeditNotebookClass))
+#define GEDIT_IS_NOTEBOOK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_NOTEBOOK))
+#define GEDIT_IS_NOTEBOOK_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_NOTEBOOK))
+#define GEDIT_NOTEBOOK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_NOTEBOOK, GeditNotebookClass))
+
+typedef struct _GeditNotebook GeditNotebook;
+typedef struct _GeditNotebookClass GeditNotebookClass;
+typedef struct _GeditNotebookPrivate GeditNotebookPrivate;
+
+/* This is now used in multi-notebook but we keep the same enum for
+ * backward compatibility since it is used in the gsettings schema */
+typedef enum
+{
+ GEDIT_NOTEBOOK_SHOW_TABS_NEVER,
+ GEDIT_NOTEBOOK_SHOW_TABS_AUTO,
+ GEDIT_NOTEBOOK_SHOW_TABS_ALWAYS
+} GeditNotebookShowTabsModeType;
+
+struct _GeditNotebook
+{
+ GtkNotebook notebook;
+
+ /*< private >*/
+ GeditNotebookPrivate *priv;
+};
+
+struct _GeditNotebookClass
+{
+ GtkNotebookClass parent_class;
+
+ /* Signals */
+ void (* tab_close_request) (GeditNotebook *notebook,
+ GeditTab *tab);
+ void (* show_popup_menu) (GeditNotebook *notebook,
+ GdkEvent *event,
+ GeditTab *tab);
+ gboolean(* change_to_page) (GeditNotebook *notebook,
+ gint page_num);
+};
+
+GType gedit_notebook_get_type (void) G_GNUC_CONST;
+
+GtkWidget *gedit_notebook_new (void);
+
+void gedit_notebook_add_tab (GeditNotebook *nb,
+ GeditTab *tab,
+ gint position,
+ gboolean jump_to);
+
+void gedit_notebook_move_tab (GeditNotebook *src,
+ GeditNotebook *dest,
+ GeditTab *tab,
+ gint dest_position);
+
+void gedit_notebook_remove_all_tabs (GeditNotebook *nb);
+
+G_END_DECLS
+
+#endif /* GEDIT_NOTEBOOK_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-plugins-engine.c b/gedit/gedit-plugins-engine.c
new file mode 100644
index 0000000..28e6096
--- /dev/null
+++ b/gedit/gedit-plugins-engine.c
@@ -0,0 +1,136 @@
+/*
+ * gedit-plugins-engine.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ * Copyright (C) 2010 Steve Frécinaux
+ *
+ * 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-plugins-engine.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <girepository.h>
+#include "gedit-debug.h"
+#include "gedit-dirs.h"
+#include "gedit-settings.h"
+
+struct _GeditPluginsEngine
+{
+ PeasEngine parent_instance;
+
+ GSettings *plugin_settings;
+};
+
+G_DEFINE_TYPE (GeditPluginsEngine, gedit_plugins_engine, PEAS_TYPE_ENGINE)
+
+static GeditPluginsEngine *default_engine = NULL;
+
+static void
+gedit_plugins_engine_init (GeditPluginsEngine *engine)
+{
+ gchar *typelib_dir;
+ GError *error = NULL;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ peas_engine_enable_loader (PEAS_ENGINE (engine), "python3");
+
+ engine->plugin_settings = g_settings_new ("org.gnome.gedit.plugins");
+
+ /* Require gedit's typelib. */
+ typelib_dir = g_build_filename (gedit_dirs_get_gedit_lib_dir (),
+ "girepository-1.0",
+ NULL);
+
+ if (!g_irepository_require_private (g_irepository_get_default (),
+ typelib_dir, "Gedit", "3.0", 0, &error))
+ {
+ g_warning ("Could not load Gedit repository: %s", error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+
+ g_free (typelib_dir);
+
+ /* This should be moved to libpeas */
+ if (!g_irepository_require (g_irepository_get_default (),
+ "Peas", "1.0", 0, &error))
+ {
+ g_warning ("Could not load Peas repository: %s", error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+
+ if (!g_irepository_require (g_irepository_get_default (),
+ "PeasGtk", "1.0", 0, &error))
+ {
+ g_warning ("Could not load PeasGtk repository: %s", error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+
+ peas_engine_add_search_path (PEAS_ENGINE (engine),
+ gedit_dirs_get_user_plugins_dir (),
+ gedit_dirs_get_user_plugins_dir ());
+
+ peas_engine_add_search_path (PEAS_ENGINE (engine),
+ gedit_dirs_get_gedit_plugins_dir (),
+ gedit_dirs_get_gedit_plugins_data_dir ());
+
+ g_settings_bind (engine->plugin_settings,
+ GEDIT_SETTINGS_ACTIVE_PLUGINS,
+ engine,
+ "loaded-plugins",
+ G_SETTINGS_BIND_DEFAULT);
+}
+
+static void
+gedit_plugins_engine_dispose (GObject *object)
+{
+ GeditPluginsEngine *engine = GEDIT_PLUGINS_ENGINE (object);
+
+ g_clear_object (&engine->plugin_settings);
+
+ G_OBJECT_CLASS (gedit_plugins_engine_parent_class)->dispose (object);
+}
+
+static void
+gedit_plugins_engine_class_init (GeditPluginsEngineClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gedit_plugins_engine_dispose;
+}
+
+GeditPluginsEngine *
+gedit_plugins_engine_get_default (void)
+{
+ if (default_engine == NULL)
+ {
+ default_engine = GEDIT_PLUGINS_ENGINE (g_object_new (GEDIT_TYPE_PLUGINS_ENGINE,
+ NULL));
+
+ g_object_add_weak_pointer (G_OBJECT (default_engine),
+ (gpointer) &default_engine);
+ }
+
+ return default_engine;
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-plugins-engine.h b/gedit/gedit-plugins-engine.h
new file mode 100644
index 0000000..124937b
--- /dev/null
+++ b/gedit/gedit-plugins-engine.h
@@ -0,0 +1,39 @@
+/*
+ * gedit-plugins-engine.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2002-2005 - Paolo Maggi
+ * Copyright (C) 2010 - Steve Frécinaux
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_PLUGINS_ENGINE_H
+#define GEDIT_PLUGINS_ENGINE_H
+
+#include <glib.h>
+#include <libpeas/peas.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_PLUGINS_ENGINE (gedit_plugins_engine_get_type ())
+G_DECLARE_FINAL_TYPE(GeditPluginsEngine, gedit_plugins_engine, GEDIT, PLUGINS_ENGINE, PeasEngine)
+
+GeditPluginsEngine *gedit_plugins_engine_get_default (void);
+
+G_END_DECLS
+
+#endif /* GEDIT_PLUGINS_ENGINE_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-preferences-dialog.c b/gedit/gedit-preferences-dialog.c
new file mode 100644
index 0000000..d857059
--- /dev/null
+++ b/gedit/gedit-preferences-dialog.c
@@ -0,0 +1,841 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gedit-preferences-dialog.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2001-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-preferences-dialog.h"
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <tepl/tepl.h>
+#include <libpeas-gtk/peas-gtk.h>
+
+#include "gedit-debug.h"
+#include "gedit-dirs.h"
+#include "gedit-settings.h"
+
+/*
+ * gedit-preferences dialog is a singleton since we don't
+ * want two dialogs showing an inconsistent state of the
+ * preferences.
+ * When gedit_show_preferences_dialog is called and there
+ * is already a prefs dialog dialog open, it is reparented
+ * and shown.
+ */
+
+static GtkWidget *preferences_dialog = NULL;
+
+#define GEDIT_SCHEME_ROW_ID_KEY "gedit-scheme-row-id"
+
+#define GEDIT_TYPE_PREFERENCES_DIALOG (gedit_preferences_dialog_get_type())
+
+G_DECLARE_FINAL_TYPE (GeditPreferencesDialog, gedit_preferences_dialog, GEDIT, PREFERENCES_DIALOG, GtkWindow)
+
+enum
+{
+ ID_COLUMN = 0,
+ NAME_COLUMN,
+ DESC_COLUMN,
+ NUM_COLUMNS
+};
+
+enum
+{
+ CLOSE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+struct _GeditPreferencesDialog
+{
+ GtkWindow parent_instance;
+
+ GSettings *editor;
+ GSettings *uisettings; /* unfortunately our settings are split for historical reasons */
+
+ GtkWidget *notebook;
+
+ /* Style Scheme */
+ GtkWidget *schemes_list;
+ GtkWidget *install_scheme_button;
+ GtkWidget *uninstall_scheme_button;
+ GtkWidget *schemes_toolbar;
+ GtkFileChooserNative *
+ install_scheme_file_chooser;
+
+ /* Tabs */
+ GtkWidget *tabs_width_spinbutton;
+ GtkWidget *insert_spaces_checkbutton;
+
+ /* Auto indentation */
+ GtkWidget *auto_indent_checkbutton;
+
+ /* Text Wrapping */
+ GtkWidget *wrap_text_checkbutton;
+ GtkWidget *split_checkbutton;
+
+ /* File Saving */
+ GtkWidget *backup_copy_checkbutton;
+ GtkWidget *auto_save_checkbutton;
+ GtkWidget *auto_save_spinbutton;
+
+ GtkWidget *display_line_numbers_checkbutton;
+ GtkWidget *display_statusbar_checkbutton;
+ GtkWidget *display_grid_checkbutton;
+
+ /* Right margin */
+ GtkWidget *right_margin_checkbutton;
+ GtkWidget *right_margin_position_grid;
+ GtkWidget *right_margin_position_spinbutton;
+
+ /* Highlighting */
+ GtkWidget *highlight_current_line_checkbutton;
+ GtkWidget *bracket_matching_checkbutton;
+
+ /* Plugin manager */
+ GtkWidget *plugin_manager;
+
+ /* Placeholders */
+ GtkGrid *font_component_placeholder;
+};
+
+G_DEFINE_TYPE (GeditPreferencesDialog, gedit_preferences_dialog, GTK_TYPE_WINDOW)
+
+static void
+gedit_preferences_dialog_dispose (GObject *object)
+{
+ GeditPreferencesDialog *dlg = GEDIT_PREFERENCES_DIALOG (object);
+
+ g_clear_object (&dlg->editor);
+ g_clear_object (&dlg->uisettings);
+
+ G_OBJECT_CLASS (gedit_preferences_dialog_parent_class)->dispose (object);
+}
+
+static void
+gedit_preferences_dialog_close (GeditPreferencesDialog *dialog)
+{
+ gtk_window_close (GTK_WINDOW (dialog));
+}
+
+static void
+gedit_preferences_dialog_class_init (GeditPreferencesDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkBindingSet *binding_set;
+
+ /* Otherwise libpeas-gtk might not be linked */
+ g_type_ensure (PEAS_GTK_TYPE_PLUGIN_MANAGER);
+
+ object_class->dispose = gedit_preferences_dialog_dispose;
+
+ signals[CLOSE] =
+ g_signal_new_class_handler ("close",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_CALLBACK (gedit_preferences_dialog_close),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+
+ binding_set = gtk_binding_set_by_class (klass);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "close", 0);
+
+ /* Bind class to template */
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/gedit/ui/gedit-preferences-dialog.ui");
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, notebook);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, display_line_numbers_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, display_statusbar_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, display_grid_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, right_margin_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, right_margin_position_grid);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, right_margin_position_spinbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, highlight_current_line_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, bracket_matching_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, wrap_text_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, split_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, tabs_width_spinbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, insert_spaces_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, auto_indent_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, backup_copy_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, auto_save_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, auto_save_spinbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, schemes_list);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, install_scheme_button);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, uninstall_scheme_button);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, schemes_toolbar);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, plugin_manager);
+ gtk_widget_class_bind_template_child (widget_class, GeditPreferencesDialog, font_component_placeholder);
+}
+
+static void
+setup_editor_page (GeditPreferencesDialog *dlg)
+{
+ gedit_debug (DEBUG_PREFS);
+
+ /* Connect signal */
+ g_settings_bind (dlg->editor,
+ GEDIT_SETTINGS_TABS_SIZE,
+ dlg->tabs_width_spinbutton,
+ "value",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+ g_settings_bind (dlg->editor,
+ GEDIT_SETTINGS_INSERT_SPACES,
+ dlg->insert_spaces_checkbutton,
+ "active",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+ g_settings_bind (dlg->editor,
+ GEDIT_SETTINGS_AUTO_INDENT,
+ dlg->auto_indent_checkbutton,
+ "active",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+ g_settings_bind (dlg->editor,
+ GEDIT_SETTINGS_CREATE_BACKUP_COPY,
+ dlg->backup_copy_checkbutton,
+ "active",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+ g_settings_bind (dlg->editor,
+ GEDIT_SETTINGS_BRACKET_MATCHING,
+ dlg->bracket_matching_checkbutton,
+ "active",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+ g_settings_bind (dlg->editor,
+ GEDIT_SETTINGS_AUTO_SAVE_INTERVAL,
+ dlg->auto_save_spinbutton,
+ "value",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+ g_settings_bind (dlg->editor,
+ GEDIT_SETTINGS_AUTO_SAVE,
+ dlg->auto_save_spinbutton,
+ "sensitive",
+ G_SETTINGS_BIND_GET);
+ g_settings_bind (dlg->editor,
+ GEDIT_SETTINGS_AUTO_SAVE,
+ dlg->auto_save_checkbutton,
+ "active",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+}
+
+static void
+wrap_mode_checkbutton_toggled (GtkToggleButton *button,
+ GeditPreferencesDialog *dlg)
+{
+ GtkWrapMode mode;
+
+ if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dlg->wrap_text_checkbutton)))
+ {
+ mode = GTK_WRAP_NONE;
+
+ gtk_widget_set_sensitive (dlg->split_checkbutton,
+ FALSE);
+ gtk_toggle_button_set_inconsistent (
+ GTK_TOGGLE_BUTTON (dlg->split_checkbutton), TRUE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (dlg->split_checkbutton,
+ TRUE);
+
+ gtk_toggle_button_set_inconsistent (
+ GTK_TOGGLE_BUTTON (dlg->split_checkbutton), FALSE);
+
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dlg->split_checkbutton)))
+ {
+ g_settings_set_enum (dlg->editor,
+ GEDIT_SETTINGS_WRAP_LAST_SPLIT_MODE,
+ GTK_WRAP_WORD);
+
+ mode = GTK_WRAP_WORD;
+ }
+ else
+ {
+ g_settings_set_enum (dlg->editor,
+ GEDIT_SETTINGS_WRAP_LAST_SPLIT_MODE,
+ GTK_WRAP_CHAR);
+
+ mode = GTK_WRAP_CHAR;
+ }
+ }
+
+ g_settings_set_enum (dlg->editor,
+ GEDIT_SETTINGS_WRAP_MODE,
+ mode);
+}
+
+static void
+grid_checkbutton_toggled (GtkToggleButton *button,
+ GeditPreferencesDialog *dlg)
+{
+ GtkSourceBackgroundPatternType background_type;
+
+ background_type = gtk_toggle_button_get_active (button) ?
+ GTK_SOURCE_BACKGROUND_PATTERN_TYPE_GRID :
+ GTK_SOURCE_BACKGROUND_PATTERN_TYPE_NONE;
+ g_settings_set_enum (dlg->editor,
+ GEDIT_SETTINGS_BACKGROUND_PATTERN,
+ background_type);
+}
+
+static void
+setup_view_page (GeditPreferencesDialog *dlg)
+{
+ GtkWrapMode wrap_mode;
+ GtkWrapMode last_split_mode;
+ GtkSourceBackgroundPatternType background_pattern;
+ gboolean display_right_margin;
+ guint right_margin_position;
+
+ gedit_debug (DEBUG_PREFS);
+
+ /* Get values */
+ display_right_margin = g_settings_get_boolean (dlg->editor,
+ GEDIT_SETTINGS_DISPLAY_RIGHT_MARGIN);
+ g_settings_get (dlg->editor, GEDIT_SETTINGS_RIGHT_MARGIN_POSITION,
+ "u", &right_margin_position);
+ background_pattern = g_settings_get_enum (dlg->editor,
+ GEDIT_SETTINGS_BACKGROUND_PATTERN);
+
+ wrap_mode = g_settings_get_enum (dlg->editor,
+ GEDIT_SETTINGS_WRAP_MODE);
+
+ /* Set initial state */
+ switch (wrap_mode)
+ {
+ case GTK_WRAP_WORD:
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dlg->wrap_text_checkbutton), TRUE);
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dlg->split_checkbutton), TRUE);
+
+ g_settings_set_enum (dlg->editor,
+ GEDIT_SETTINGS_WRAP_LAST_SPLIT_MODE,
+ GTK_WRAP_WORD);
+ break;
+ case GTK_WRAP_CHAR:
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dlg->wrap_text_checkbutton), TRUE);
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dlg->split_checkbutton), FALSE);
+
+ g_settings_set_enum (dlg->editor,
+ GEDIT_SETTINGS_WRAP_LAST_SPLIT_MODE,
+ GTK_WRAP_CHAR);
+ break;
+ default:
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dlg->wrap_text_checkbutton), FALSE);
+
+ last_split_mode = g_settings_get_enum (dlg->editor,
+ GEDIT_SETTINGS_WRAP_LAST_SPLIT_MODE);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->split_checkbutton),
+ last_split_mode == GTK_WRAP_WORD);
+
+ gtk_toggle_button_set_inconsistent (
+ GTK_TOGGLE_BUTTON (dlg->split_checkbutton), TRUE);
+ }
+
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dlg->right_margin_checkbutton),
+ display_right_margin);
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dlg->display_grid_checkbutton),
+ background_pattern == GTK_SOURCE_BACKGROUND_PATTERN_TYPE_GRID);
+
+ /* Set widgets sensitivity */
+ gtk_widget_set_sensitive (dlg->split_checkbutton,
+ (wrap_mode != GTK_WRAP_NONE));
+
+ g_settings_bind (dlg->editor,
+ GEDIT_SETTINGS_DISPLAY_LINE_NUMBERS,
+ dlg->display_line_numbers_checkbutton,
+ "active",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+ g_settings_bind (dlg->editor,
+ GEDIT_SETTINGS_HIGHLIGHT_CURRENT_LINE,
+ dlg->highlight_current_line_checkbutton,
+ "active",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+ g_settings_bind (dlg->uisettings,
+ GEDIT_SETTINGS_STATUSBAR_VISIBLE,
+ dlg->display_statusbar_checkbutton,
+ "active",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+ g_settings_bind (dlg->editor,
+ GEDIT_SETTINGS_DISPLAY_RIGHT_MARGIN,
+ dlg->right_margin_checkbutton,
+ "active",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+ g_settings_bind (dlg->editor,
+ GEDIT_SETTINGS_DISPLAY_RIGHT_MARGIN,
+ dlg->right_margin_position_grid,
+ "sensitive",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+ g_settings_bind (dlg->editor,
+ GEDIT_SETTINGS_RIGHT_MARGIN_POSITION,
+ dlg->right_margin_position_spinbutton,
+ "value",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+ g_settings_bind (dlg->editor,
+ GEDIT_SETTINGS_AUTO_SAVE_INTERVAL,
+ dlg->auto_save_spinbutton,
+ "value",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+ g_signal_connect (dlg->wrap_text_checkbutton,
+ "toggled",
+ G_CALLBACK (wrap_mode_checkbutton_toggled),
+ dlg);
+ g_signal_connect (dlg->split_checkbutton,
+ "toggled",
+ G_CALLBACK (wrap_mode_checkbutton_toggled),
+ dlg);
+ g_signal_connect (dlg->display_grid_checkbutton,
+ "toggled",
+ G_CALLBACK (grid_checkbutton_toggled),
+ dlg);
+}
+
+static void
+setup_font_colors_page_font_section (GeditPreferencesDialog *dlg)
+{
+ GtkWidget *font_component;
+
+ font_component = tepl_prefs_create_font_component (dlg->editor,
+ GEDIT_SETTINGS_USE_DEFAULT_FONT,
+ GEDIT_SETTINGS_EDITOR_FONT);
+
+ gtk_container_add (GTK_CONTAINER (dlg->font_component_placeholder),
+ font_component);
+}
+
+static void
+update_style_scheme_buttons_sensisitivity (GeditPreferencesDialog *dlg)
+{
+ GtkSourceStyleScheme *selected_style_scheme;
+ gboolean editable = FALSE;
+
+ selected_style_scheme = gtk_source_style_scheme_chooser_get_style_scheme (GTK_SOURCE_STYLE_SCHEME_CHOOSER (dlg->schemes_list));
+
+ if (selected_style_scheme != NULL)
+ {
+ const gchar *filename;
+
+ filename = gtk_source_style_scheme_get_filename (selected_style_scheme);
+ if (filename != NULL)
+ {
+ editable = g_str_has_prefix (filename, gedit_dirs_get_user_styles_dir ());
+ }
+ }
+
+ gtk_widget_set_sensitive (dlg->uninstall_scheme_button, editable);
+}
+
+static void
+style_scheme_notify_cb (GtkSourceStyleSchemeChooser *chooser,
+ GParamSpec *pspec,
+ GeditPreferencesDialog *dlg)
+{
+ update_style_scheme_buttons_sensisitivity (dlg);
+}
+
+static GFile *
+get_user_style_scheme_destination_file (GFile *src_file)
+{
+ gchar *basename;
+ const gchar *styles_dir;
+ GFile *dest_file;
+
+ basename = g_file_get_basename (src_file);
+ g_return_val_if_fail (basename != NULL, NULL);
+
+ styles_dir = gedit_dirs_get_user_styles_dir ();
+ dest_file = g_file_new_build_filename (styles_dir, basename, NULL);
+
+ g_free (basename);
+ return dest_file;
+}
+
+/* Returns: whether @src_file has been correctly copied to @dest_file. */
+static gboolean
+copy_file (GFile *src_file,
+ GFile *dest_file,
+ GError **error)
+{
+ if (g_file_equal (src_file, dest_file))
+ {
+ return FALSE;
+ }
+
+ if (!tepl_utils_create_parent_directories (dest_file, NULL, error))
+ {
+ return FALSE;
+ }
+
+ return g_file_copy (src_file,
+ dest_file,
+ G_FILE_COPY_OVERWRITE | G_FILE_COPY_TARGET_DEFAULT_PERMS,
+ NULL, /* cancellable */
+ NULL, NULL, /* progress callback */
+ error);
+}
+
+/* Get the style scheme ID of @user_style_scheme_file if it has been correctly
+ * installed and @user_style_scheme_file is a valid style scheme file.
+ */
+static const gchar *
+get_style_scheme_id_after_installing_user_style_scheme (GFile *user_style_scheme_file)
+{
+ GtkSourceStyleSchemeManager *manager;
+ const gchar * const *scheme_ids;
+ gint i;
+
+ manager = gtk_source_style_scheme_manager_get_default ();
+ gtk_source_style_scheme_manager_force_rescan (manager);
+
+ scheme_ids = gtk_source_style_scheme_manager_get_scheme_ids (manager);
+
+ for (i = 0; scheme_ids != NULL && scheme_ids[i] != NULL; i++)
+ {
+ const gchar *cur_scheme_id = scheme_ids[i];
+ GtkSourceStyleScheme *scheme;
+ const gchar *filename;
+ GFile *scheme_file;
+
+ scheme = gtk_source_style_scheme_manager_get_scheme (manager, cur_scheme_id);
+ filename = gtk_source_style_scheme_get_filename (scheme);
+ if (filename == NULL)
+ {
+ continue;
+ }
+
+ scheme_file = g_file_new_for_path (filename);
+ if (g_file_equal (scheme_file, user_style_scheme_file))
+ {
+ g_object_unref (scheme_file);
+ return cur_scheme_id;
+ }
+
+ g_object_unref (scheme_file);
+ }
+
+ return NULL;
+}
+
+/* Returns: (nullable): the installed style scheme ID, or %NULL on failure. */
+static const gchar *
+install_style_scheme (GFile *src_file,
+ GError **error)
+{
+ GFile *dest_file;
+ gboolean copied;
+ const gchar *installed_style_scheme_id = NULL;
+ GError *my_error = NULL;
+
+ g_return_val_if_fail (G_IS_FILE (src_file), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ dest_file = get_user_style_scheme_destination_file (src_file);
+ g_return_val_if_fail (dest_file != NULL, NULL);
+
+ copied = copy_file (src_file, dest_file, &my_error);
+ if (my_error != NULL)
+ {
+ g_propagate_error (error, my_error);
+ g_object_unref (dest_file);
+ return NULL;
+ }
+
+ installed_style_scheme_id = get_style_scheme_id_after_installing_user_style_scheme (dest_file);
+
+ if (installed_style_scheme_id == NULL && copied)
+ {
+ /* The style scheme has not been correctly installed. */
+ g_file_delete (dest_file, NULL, &my_error);
+ if (my_error != NULL)
+ {
+ gchar *dest_file_parse_name = g_file_get_parse_name (dest_file);
+
+ g_warning ("Failed to delete the file “%s”: %s",
+ dest_file_parse_name,
+ my_error->message);
+
+ g_free (dest_file_parse_name);
+ g_clear_error (&my_error);
+ }
+ }
+
+ g_object_unref (dest_file);
+ return installed_style_scheme_id;
+}
+
+/*
+ * uninstall_style_scheme:
+ * @scheme: a #GtkSourceStyleScheme
+ *
+ * Uninstall a user scheme.
+ *
+ * Returns: %TRUE on success, %FALSE otherwise.
+ */
+static gboolean
+uninstall_style_scheme (GtkSourceStyleScheme *scheme)
+{
+ GtkSourceStyleSchemeManager *manager;
+ const gchar *filename;
+
+ g_return_val_if_fail (GTK_SOURCE_IS_STYLE_SCHEME (scheme), FALSE);
+
+ manager = gtk_source_style_scheme_manager_get_default ();
+
+ filename = gtk_source_style_scheme_get_filename (scheme);
+ if (filename == NULL)
+ return FALSE;
+
+ if (g_unlink (filename) == -1)
+ return FALSE;
+
+ /* Reload the available style schemes */
+ gtk_source_style_scheme_manager_force_rescan (manager);
+
+ return TRUE;
+}
+
+static void
+add_scheme_chooser_response_cb (GtkFileChooserNative *chooser,
+ gint response_id,
+ GeditPreferencesDialog *dialog)
+{
+ GFile *file;
+ const gchar *scheme_id;
+ GeditSettings *settings;
+ GSettings *editor_settings;
+ GError *error = NULL;
+
+ if (response_id != GTK_RESPONSE_ACCEPT)
+ {
+ return;
+ }
+
+ file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (chooser));
+ if (file == NULL)
+ {
+ return;
+ }
+
+ scheme_id = install_style_scheme (file, &error);
+ g_object_unref (file);
+
+ if (scheme_id == NULL)
+ {
+ if (error != NULL)
+ {
+ tepl_utils_show_warning_dialog (GTK_WINDOW (dialog),
+ _("The selected color scheme cannot be installed: %s"),
+ error->message);
+ }
+ else
+ {
+ tepl_utils_show_warning_dialog (GTK_WINDOW (dialog),
+ _("The selected color scheme cannot be installed."));
+ }
+
+ g_clear_error (&error);
+ return;
+ }
+
+ settings = _gedit_settings_get_singleton ();
+ editor_settings = _gedit_settings_peek_editor_settings (settings);
+ g_settings_set_string (editor_settings, GEDIT_SETTINGS_SCHEME, scheme_id);
+}
+
+static void
+install_scheme_clicked (GtkButton *button,
+ GeditPreferencesDialog *dialog)
+{
+ GtkFileChooserNative *chooser;
+ GtkFileFilter *scheme_filter;
+ GtkFileFilter *all_filter;
+
+ if (dialog->install_scheme_file_chooser != NULL)
+ {
+ gtk_native_dialog_show (GTK_NATIVE_DIALOG (dialog->install_scheme_file_chooser));
+ return;
+ }
+
+ chooser = gtk_file_chooser_native_new (_("Add Color Scheme"),
+ GTK_WINDOW (dialog),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ _("_Add Scheme"),
+ _("_Cancel"));
+
+ /* Filters */
+ scheme_filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (scheme_filter, _("Color Scheme Files"));
+ gtk_file_filter_add_pattern (scheme_filter, "*.xml");
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), scheme_filter);
+
+ all_filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (all_filter, _("All Files"));
+ gtk_file_filter_add_pattern (all_filter, "*");
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), all_filter);
+
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (chooser), scheme_filter);
+
+ g_signal_connect (chooser,
+ "response",
+ G_CALLBACK (add_scheme_chooser_response_cb),
+ dialog);
+
+ g_set_weak_pointer (&dialog->install_scheme_file_chooser, chooser);
+
+ gtk_native_dialog_show (GTK_NATIVE_DIALOG (chooser));
+}
+
+static void
+uninstall_scheme_clicked (GtkButton *button,
+ GeditPreferencesDialog *dlg)
+{
+ GtkSourceStyleScheme *scheme;
+ GtkSourceStyleScheme *new_selected_scheme;
+
+ scheme = gtk_source_style_scheme_chooser_get_style_scheme (GTK_SOURCE_STYLE_SCHEME_CHOOSER (dlg->schemes_list));
+
+ if (scheme == NULL)
+ {
+ return;
+ }
+
+ if (!uninstall_style_scheme (scheme))
+ {
+ tepl_utils_show_warning_dialog (GTK_WINDOW (dlg),
+ _("Could not remove color scheme “%s”."),
+ gtk_source_style_scheme_get_name (scheme));
+ return;
+ }
+
+ new_selected_scheme = gtk_source_style_scheme_chooser_get_style_scheme (GTK_SOURCE_STYLE_SCHEME_CHOOSER (dlg->schemes_list));
+ if (new_selected_scheme == NULL)
+ {
+ GeditSettings *settings;
+ GSettings *editor_settings;
+
+ settings = _gedit_settings_get_singleton ();
+ editor_settings = _gedit_settings_peek_editor_settings (settings);
+
+ g_settings_reset (editor_settings, GEDIT_SETTINGS_SCHEME);
+ }
+}
+
+static void
+setup_font_colors_page_style_scheme_section (GeditPreferencesDialog *dlg)
+{
+ GtkStyleContext *context;
+ GeditSettings *settings;
+ GSettings *editor_settings;
+
+ gedit_debug (DEBUG_PREFS);
+
+ /* junction between the schemes list and the toolbar */
+ context = gtk_widget_get_style_context (dlg->schemes_list);
+ gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM);
+ context = gtk_widget_get_style_context (dlg->schemes_toolbar);
+ gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP);
+
+ /* Connect signals */
+ g_signal_connect (dlg->schemes_list,
+ "notify::style-scheme",
+ G_CALLBACK (style_scheme_notify_cb),
+ dlg);
+ g_signal_connect (dlg->install_scheme_button,
+ "clicked",
+ G_CALLBACK (install_scheme_clicked),
+ dlg);
+ g_signal_connect (dlg->uninstall_scheme_button,
+ "clicked",
+ G_CALLBACK (uninstall_scheme_clicked),
+ dlg);
+
+ settings = _gedit_settings_get_singleton ();
+ editor_settings = _gedit_settings_peek_editor_settings (settings);
+ g_settings_bind (editor_settings, GEDIT_SETTINGS_SCHEME,
+ dlg->schemes_list, "tepl-style-scheme-id",
+ G_SETTINGS_BIND_DEFAULT);
+
+ update_style_scheme_buttons_sensisitivity (dlg);
+}
+
+static void
+setup_font_colors_page (GeditPreferencesDialog *dlg)
+{
+ setup_font_colors_page_font_section (dlg);
+ setup_font_colors_page_style_scheme_section (dlg);
+}
+
+static void
+setup_plugins_page (GeditPreferencesDialog *dlg)
+{
+ gtk_widget_show_all (dlg->plugin_manager);
+}
+
+static void
+gedit_preferences_dialog_init (GeditPreferencesDialog *dlg)
+{
+ gedit_debug (DEBUG_PREFS);
+
+ dlg->editor = g_settings_new ("org.gnome.gedit.preferences.editor");
+ dlg->uisettings = g_settings_new ("org.gnome.gedit.preferences.ui");
+
+ gtk_widget_init_template (GTK_WIDGET (dlg));
+
+ setup_editor_page (dlg);
+ setup_view_page (dlg);
+ setup_font_colors_page (dlg);
+ setup_plugins_page (dlg);
+}
+
+void
+gedit_show_preferences_dialog (GeditWindow *parent)
+{
+ gedit_debug (DEBUG_PREFS);
+
+ if (preferences_dialog == NULL)
+ {
+ preferences_dialog = GTK_WIDGET (g_object_new (GEDIT_TYPE_PREFERENCES_DIALOG,
+ "application", g_application_get_default (),
+ NULL));
+ g_signal_connect (preferences_dialog,
+ "destroy",
+ G_CALLBACK (gtk_widget_destroyed),
+ &preferences_dialog);
+ }
+
+ if (GTK_WINDOW (parent) != gtk_window_get_transient_for (GTK_WINDOW (preferences_dialog)))
+ {
+ gtk_window_set_transient_for (GTK_WINDOW (preferences_dialog),
+ GTK_WINDOW (parent));
+ }
+
+ gtk_window_present (GTK_WINDOW (preferences_dialog));
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-preferences-dialog.h b/gedit/gedit-preferences-dialog.h
new file mode 100644
index 0000000..1d35559
--- /dev/null
+++ b/gedit/gedit-preferences-dialog.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gedit-preferences-dialog.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2001-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/>.
+ */
+
+#ifndef GEDIT_PREFERENCES_DIALOG_H
+#define GEDIT_PREFERENCES_DIALOG_H
+
+#include "gedit-window.h"
+
+G_BEGIN_DECLS
+
+void gedit_show_preferences_dialog (GeditWindow *parent);
+
+G_END_DECLS
+
+#endif /* GEDIT_PREFERENCES_DIALOG_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-print-job.c b/gedit/gedit-print-job.c
new file mode 100644
index 0000000..fab454f
--- /dev/null
+++ b/gedit/gedit-print-job.c
@@ -0,0 +1,819 @@
+/*
+ * gedit-print-job.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2000-2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2008 Paolo Maggi
+ * Copyright (C) 2015 Sébastien Wilmet
+ *
+ * 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 "gedit-print-job.h"
+#include <glib/gi18n.h>
+#include "gedit-print-preview.h"
+#include "gedit-settings.h"
+
+struct _GeditPrintJob
+{
+ GObject parent_instance;
+
+ GSettings *gsettings;
+
+ TeplView *view;
+
+ GtkPrintOperation *operation;
+ GtkSourcePrintCompositor *compositor;
+
+ GtkWidget *preview;
+
+ gchar *status_string;
+ gdouble progress;
+
+ /* Widgets part of the custom print preferences widget.
+ * These pointers are valid just when the dialog is displayed.
+ */
+ GtkToggleButton *syntax_checkbutton;
+ GtkToggleButton *page_header_checkbutton;
+ GtkToggleButton *line_numbers_checkbutton;
+ GtkSpinButton *line_numbers_spinbutton;
+ GtkToggleButton *text_wrapping_checkbutton;
+ GtkToggleButton *do_not_split_checkbutton;
+ GtkFontButton *body_fontbutton;
+ GtkFontButton *headers_fontbutton;
+ GtkFontButton *numbers_fontbutton;
+
+ guint is_preview : 1;
+};
+
+enum
+{
+ PROP_0,
+ PROP_VIEW,
+ LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP];
+
+enum
+{
+ PRINTING,
+ SHOW_PREVIEW,
+ DONE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (GeditPrintJob, gedit_print_job, G_TYPE_OBJECT)
+
+static void
+gedit_print_job_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditPrintJob *job = GEDIT_PRINT_JOB (object);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ g_value_set_object (value, job->view);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_print_job_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditPrintJob *job = GEDIT_PRINT_JOB (object);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ job->view = g_value_get_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_print_job_dispose (GObject *object)
+{
+ GeditPrintJob *job = GEDIT_PRINT_JOB (object);
+
+ g_clear_object (&job->gsettings);
+ g_clear_object (&job->operation);
+ g_clear_object (&job->compositor);
+ g_clear_object (&job->preview);
+
+ G_OBJECT_CLASS (gedit_print_job_parent_class)->dispose (object);
+}
+
+static void
+gedit_print_job_finalize (GObject *object)
+{
+ GeditPrintJob *job = GEDIT_PRINT_JOB (object);
+
+ g_free (job->status_string);
+
+ G_OBJECT_CLASS (gedit_print_job_parent_class)->finalize (object);
+}
+
+static void
+gedit_print_job_printing (GeditPrintJob *job,
+ GeditPrintJobStatus status)
+{
+}
+
+static void
+gedit_print_job_show_preview (GeditPrintJob *job,
+ GtkWidget *preview)
+{
+}
+
+static void
+gedit_print_job_done (GeditPrintJob *job,
+ GeditPrintJobResult result,
+ const GError *error)
+{
+}
+
+static void
+gedit_print_job_class_init (GeditPrintJobClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = gedit_print_job_get_property;
+ object_class->set_property = gedit_print_job_set_property;
+ object_class->dispose = gedit_print_job_dispose;
+ object_class->finalize = gedit_print_job_finalize;
+
+ properties[PROP_VIEW] =
+ g_param_spec_object ("view",
+ "view",
+ "",
+ TEPL_TYPE_VIEW,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ signals[PRINTING] =
+ g_signal_new_class_handler ("printing",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_CALLBACK (gedit_print_job_printing),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_UINT);
+
+ signals[SHOW_PREVIEW] =
+ g_signal_new_class_handler ("show-preview",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_CALLBACK (gedit_print_job_show_preview),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ GTK_TYPE_WIDGET);
+
+ signals[DONE] =
+ g_signal_new_class_handler ("done",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_CALLBACK (gedit_print_job_done),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_UINT,
+ G_TYPE_POINTER);
+}
+
+static void
+gedit_print_job_init (GeditPrintJob *job)
+{
+ job->gsettings = g_settings_new ("org.gnome.gedit.preferences.print");
+
+ job->status_string = g_strdup (_("Preparing…"));
+}
+
+static void
+restore_button_clicked (GtkButton *button,
+ GeditPrintJob *job)
+
+{
+ g_settings_reset (job->gsettings, GEDIT_SETTINGS_PRINT_FONT_BODY_PANGO);
+ g_settings_reset (job->gsettings, GEDIT_SETTINGS_PRINT_FONT_HEADER_PANGO);
+ g_settings_reset (job->gsettings, GEDIT_SETTINGS_PRINT_FONT_NUMBERS_PANGO);
+}
+
+static GObject *
+create_custom_widget_cb (GtkPrintOperation *operation,
+ GeditPrintJob *job)
+{
+ GtkBuilder *builder;
+ GtkWidget *contents;
+ GtkWidget *line_numbers_hbox;
+ GtkWidget *restore_button;
+ guint line_numbers;
+ GtkWrapMode wrap_mode;
+
+ gchar *root_objects[] = {
+ "adjustment1",
+ "contents",
+ NULL
+ };
+
+ builder = gtk_builder_new ();
+ gtk_builder_add_objects_from_resource (builder, "/org/gnome/gedit/ui/gedit-print-preferences.ui",
+ root_objects, NULL);
+ contents = GTK_WIDGET (gtk_builder_get_object (builder, "contents"));
+ g_object_ref (contents);
+ job->syntax_checkbutton = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "syntax_checkbutton"));
+ job->line_numbers_checkbutton = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "line_numbers_checkbutton"));
+ line_numbers_hbox = GTK_WIDGET (gtk_builder_get_object (builder, "line_numbers_hbox"));
+ job->line_numbers_spinbutton = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "line_numbers_spinbutton"));
+ job->page_header_checkbutton = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "page_header_checkbutton"));
+ job->text_wrapping_checkbutton = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "text_wrapping_checkbutton"));
+ job->do_not_split_checkbutton = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "do_not_split_checkbutton"));
+ job->body_fontbutton = GTK_FONT_BUTTON (gtk_builder_get_object (builder, "body_fontbutton"));
+ job->headers_fontbutton = GTK_FONT_BUTTON (gtk_builder_get_object (builder, "headers_fontbutton"));
+ job->numbers_fontbutton = GTK_FONT_BUTTON (gtk_builder_get_object (builder, "numbers_fontbutton"));
+ restore_button = GTK_WIDGET (gtk_builder_get_object (builder, "restore_button"));
+ g_object_unref (builder);
+
+ /* Syntax highlighting */
+ g_settings_bind (job->gsettings, GEDIT_SETTINGS_PRINT_SYNTAX_HIGHLIGHTING,
+ job->syntax_checkbutton, "active",
+ G_SETTINGS_BIND_GET);
+
+ /* Print header */
+ g_settings_bind (job->gsettings, GEDIT_SETTINGS_PRINT_HEADER,
+ job->page_header_checkbutton, "active",
+ G_SETTINGS_BIND_GET);
+
+ /* Line numbers */
+ g_settings_get (job->gsettings, GEDIT_SETTINGS_PRINT_LINE_NUMBERS,
+ "u", &line_numbers);
+
+ if (line_numbers > 0)
+ {
+ gtk_spin_button_set_value (job->line_numbers_spinbutton, line_numbers);
+ }
+ else
+ {
+ gtk_spin_button_set_value (job->line_numbers_spinbutton, 1);
+ }
+
+ gtk_toggle_button_set_active (job->line_numbers_checkbutton,
+ line_numbers > 0);
+
+ g_object_bind_property (job->line_numbers_checkbutton, "active",
+ line_numbers_hbox, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ /* Fonts */
+ g_settings_bind (job->gsettings, GEDIT_SETTINGS_PRINT_FONT_BODY_PANGO,
+ job->body_fontbutton, "font-name",
+ G_SETTINGS_BIND_GET);
+
+ g_settings_bind (job->gsettings, GEDIT_SETTINGS_PRINT_FONT_HEADER_PANGO,
+ job->headers_fontbutton, "font-name",
+ G_SETTINGS_BIND_GET);
+
+ g_settings_bind (job->gsettings, GEDIT_SETTINGS_PRINT_FONT_NUMBERS_PANGO,
+ job->numbers_fontbutton, "font-name",
+ G_SETTINGS_BIND_GET);
+
+ /* Wrap mode */
+ wrap_mode = g_settings_get_enum (job->gsettings, GEDIT_SETTINGS_PRINT_WRAP_MODE);
+
+ switch (wrap_mode)
+ {
+ case GTK_WRAP_WORD:
+ gtk_toggle_button_set_active (job->text_wrapping_checkbutton, TRUE);
+ gtk_toggle_button_set_active (job->do_not_split_checkbutton, TRUE);
+ break;
+
+ case GTK_WRAP_CHAR:
+ gtk_toggle_button_set_active (job->text_wrapping_checkbutton, TRUE);
+ gtk_toggle_button_set_active (job->do_not_split_checkbutton, FALSE);
+ break;
+
+ default:
+ gtk_toggle_button_set_active (job->text_wrapping_checkbutton, FALSE);
+ break;
+ }
+
+ g_object_bind_property (job->text_wrapping_checkbutton, "active",
+ job->do_not_split_checkbutton, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (job->text_wrapping_checkbutton, "active",
+ job->do_not_split_checkbutton, "inconsistent",
+ G_BINDING_INVERT_BOOLEAN | G_BINDING_SYNC_CREATE);
+
+ /* Restore */
+ g_signal_connect (restore_button,
+ "clicked",
+ G_CALLBACK (restore_button_clicked),
+ job);
+
+ return G_OBJECT (contents);
+}
+
+static void
+custom_widget_apply_cb (GtkPrintOperation *operation,
+ GtkWidget *widget,
+ GeditPrintJob *job)
+{
+ gboolean syntax;
+ gboolean page_header;
+ const gchar *body_font;
+ const gchar *header_font;
+ const gchar *numbers_font;
+ GtkWrapMode wrap_mode;
+
+ syntax = gtk_toggle_button_get_active (job->syntax_checkbutton);
+ page_header = gtk_toggle_button_get_active (job->page_header_checkbutton);
+ body_font = gtk_font_chooser_get_font (GTK_FONT_CHOOSER (job->body_fontbutton));
+ header_font = gtk_font_chooser_get_font (GTK_FONT_CHOOSER (job->headers_fontbutton));
+ numbers_font = gtk_font_chooser_get_font (GTK_FONT_CHOOSER (job->numbers_fontbutton));
+
+ g_settings_set_boolean (job->gsettings,
+ GEDIT_SETTINGS_PRINT_SYNTAX_HIGHLIGHTING,
+ syntax);
+
+ g_settings_set_boolean (job->gsettings,
+ GEDIT_SETTINGS_PRINT_HEADER,
+ page_header);
+
+ g_settings_set_string (job->gsettings,
+ GEDIT_SETTINGS_PRINT_FONT_BODY_PANGO,
+ body_font);
+
+ g_settings_set_string (job->gsettings,
+ GEDIT_SETTINGS_PRINT_FONT_HEADER_PANGO,
+ header_font);
+
+ g_settings_set_string (job->gsettings,
+ GEDIT_SETTINGS_PRINT_FONT_NUMBERS_PANGO,
+ numbers_font);
+
+ if (gtk_toggle_button_get_active (job->line_numbers_checkbutton))
+ {
+ gint num;
+
+ num = gtk_spin_button_get_value_as_int (job->line_numbers_spinbutton);
+
+ g_settings_set (job->gsettings,
+ GEDIT_SETTINGS_PRINT_LINE_NUMBERS,
+ "u", MAX (1, num));
+ }
+ else
+ {
+ g_settings_set (job->gsettings,
+ GEDIT_SETTINGS_PRINT_LINE_NUMBERS,
+ "u", 0);
+ }
+
+ if (gtk_toggle_button_get_active (job->text_wrapping_checkbutton))
+ {
+ if (gtk_toggle_button_get_active (job->do_not_split_checkbutton))
+ {
+ wrap_mode = GTK_WRAP_WORD;
+ }
+ else
+ {
+ wrap_mode = GTK_WRAP_CHAR;
+ }
+ }
+ else
+ {
+ wrap_mode = GTK_WRAP_NONE;
+ }
+
+ g_settings_set_enum (job->gsettings,
+ GEDIT_SETTINGS_PRINT_WRAP_MODE,
+ wrap_mode);
+}
+
+static void
+preview_ready (GtkPrintOperationPreview *gtk_preview,
+ GtkPrintContext *context,
+ GeditPrintJob *job)
+{
+ job->is_preview = TRUE;
+
+ g_signal_emit (job, signals[SHOW_PREVIEW], 0, job->preview);
+
+ g_clear_object (&job->preview);
+}
+
+static gboolean
+preview_cb (GtkPrintOperation *op,
+ GtkPrintOperationPreview *gtk_preview,
+ GtkPrintContext *context,
+ GtkWindow *parent,
+ GeditPrintJob *job)
+{
+ g_clear_object (&job->preview);
+ job->preview = gedit_print_preview_new (op, gtk_preview, context);
+ g_object_ref_sink (job->preview);
+
+ g_signal_connect_after (gtk_preview,
+ "ready",
+ G_CALLBACK (preview_ready),
+ job);
+
+ return TRUE;
+}
+
+static void
+create_compositor (GeditPrintJob *job)
+{
+ GtkSourceBuffer *buf;
+ gchar *print_font_body;
+ gchar *print_font_header;
+ gchar *print_font_numbers;
+ gboolean syntax_hl;
+ GtkWrapMode wrap_mode;
+ guint print_line_numbers;
+ gboolean print_header;
+ guint tab_width;
+ gdouble margin;
+
+ buf = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (job->view)));
+
+ print_font_body = g_settings_get_string (job->gsettings,
+ GEDIT_SETTINGS_PRINT_FONT_BODY_PANGO);
+
+ print_font_header = g_settings_get_string (job->gsettings,
+ GEDIT_SETTINGS_PRINT_FONT_HEADER_PANGO);
+
+ print_font_numbers = g_settings_get_string (job->gsettings,
+ GEDIT_SETTINGS_PRINT_FONT_NUMBERS_PANGO);
+
+ g_settings_get (job->gsettings,
+ GEDIT_SETTINGS_PRINT_LINE_NUMBERS,
+ "u", &print_line_numbers);
+
+ print_header = g_settings_get_boolean (job->gsettings,
+ GEDIT_SETTINGS_PRINT_HEADER);
+
+ wrap_mode = g_settings_get_enum (job->gsettings,
+ GEDIT_SETTINGS_PRINT_WRAP_MODE);
+
+ syntax_hl = g_settings_get_boolean (job->gsettings,
+ GEDIT_SETTINGS_PRINT_SYNTAX_HIGHLIGHTING);
+
+ syntax_hl &= gtk_source_buffer_get_highlight_syntax (buf);
+
+ tab_width = gtk_source_view_get_tab_width (GTK_SOURCE_VIEW (job->view));
+
+ job->compositor = GTK_SOURCE_PRINT_COMPOSITOR (
+ g_object_new (GTK_SOURCE_TYPE_PRINT_COMPOSITOR,
+ "buffer", buf,
+ "tab-width", tab_width,
+ "highlight-syntax", syntax_hl,
+ "wrap-mode", wrap_mode,
+ "print-line-numbers", print_line_numbers,
+ "print-header", print_header,
+ "print-footer", FALSE,
+ "body-font-name", print_font_body,
+ "line-numbers-font-name", print_font_numbers,
+ "header-font-name", print_font_header,
+ NULL));
+
+ margin = g_settings_get_double (job->gsettings, GEDIT_SETTINGS_PRINT_MARGIN_LEFT);
+ gtk_source_print_compositor_set_left_margin (job->compositor, margin, GTK_UNIT_MM);
+
+ margin = g_settings_get_double (job->gsettings, GEDIT_SETTINGS_PRINT_MARGIN_TOP);
+ gtk_source_print_compositor_set_top_margin (job->compositor, margin, GTK_UNIT_MM);
+
+ margin = g_settings_get_double (job->gsettings, GEDIT_SETTINGS_PRINT_MARGIN_RIGHT);
+ gtk_source_print_compositor_set_right_margin (job->compositor, margin, GTK_UNIT_MM);
+
+ margin = g_settings_get_double (job->gsettings, GEDIT_SETTINGS_PRINT_MARGIN_BOTTOM);
+ gtk_source_print_compositor_set_bottom_margin (job->compositor, margin, GTK_UNIT_MM);
+
+ if (print_header)
+ {
+ gchar *doc_name;
+ gchar *name_to_display;
+ gchar *left;
+
+ doc_name = tepl_file_get_full_name (tepl_buffer_get_file (TEPL_BUFFER (buf)));
+ name_to_display = tepl_utils_str_middle_truncate (doc_name, 60);
+
+ left = g_strdup_printf (_("File: %s"), name_to_display);
+
+ gtk_source_print_compositor_set_header_format (job->compositor,
+ TRUE,
+ left,
+ NULL,
+ /* Translators: %N is the current page number, %Q is the total
+ * number of pages (ex. Page 2 of 10)
+ */
+ _("Page %N of %Q"));
+
+ g_free (doc_name);
+ g_free (name_to_display);
+ g_free (left);
+ }
+
+ g_free (print_font_body);
+ g_free (print_font_header);
+ g_free (print_font_numbers);
+}
+
+static void
+begin_print_cb (GtkPrintOperation *operation,
+ GtkPrintContext *context,
+ GeditPrintJob *job)
+{
+ create_compositor (job);
+
+ job->progress = 0.0;
+
+ g_signal_emit (job,
+ signals[PRINTING],
+ 0,
+ GEDIT_PRINT_JOB_STATUS_PAGINATING);
+}
+
+static gboolean
+paginate_cb (GtkPrintOperation *operation,
+ GtkPrintContext *context,
+ GeditPrintJob *job)
+{
+ gboolean finished;
+
+ finished = gtk_source_print_compositor_paginate (job->compositor, context);
+
+ if (finished)
+ {
+ gint n_pages;
+
+ n_pages = gtk_source_print_compositor_get_n_pages (job->compositor);
+ gtk_print_operation_set_n_pages (job->operation, n_pages);
+ }
+
+ job->progress = gtk_source_print_compositor_get_pagination_progress (job->compositor);
+
+ /* When previewing, the progress is just for pagination, when printing
+ * it's split between pagination and rendering.
+ */
+ if (!job->is_preview)
+ {
+ job->progress /= 2.0;
+ }
+
+ g_signal_emit (job,
+ signals[PRINTING],
+ 0,
+ GEDIT_PRINT_JOB_STATUS_PAGINATING);
+
+ return finished;
+}
+
+static void
+draw_page_cb (GtkPrintOperation *operation,
+ GtkPrintContext *context,
+ gint page_nr,
+ GeditPrintJob *job)
+{
+ /* In preview, pages are drawn on the fly, so rendering is
+ * not part of the progress.
+ */
+ if (!job->is_preview)
+ {
+ gint n_pages;
+
+ n_pages = gtk_source_print_compositor_get_n_pages (job->compositor);
+
+ g_free (job->status_string);
+ job->status_string = g_strdup_printf (_("Rendering page %d of %d…"), page_nr + 1, n_pages);
+
+ job->progress = page_nr / (2.0 * n_pages) + 0.5;
+
+ g_signal_emit (job,
+ signals[PRINTING],
+ 0,
+ GEDIT_PRINT_JOB_STATUS_DRAWING);
+ }
+
+ gtk_source_print_compositor_draw_page (job->compositor, context, page_nr);
+}
+
+static void
+end_print_cb (GtkPrintOperation *operation,
+ GtkPrintContext *context,
+ GeditPrintJob *job)
+{
+ g_clear_object (&job->compositor);
+}
+
+static void
+done_cb (GtkPrintOperation *operation,
+ GtkPrintOperationResult result,
+ GeditPrintJob *job)
+{
+ GError *error = NULL;
+ GeditPrintJobResult print_result;
+
+ switch (result)
+ {
+ case GTK_PRINT_OPERATION_RESULT_CANCEL:
+ print_result = GEDIT_PRINT_JOB_RESULT_CANCEL;
+ break;
+
+ case GTK_PRINT_OPERATION_RESULT_APPLY:
+ print_result = GEDIT_PRINT_JOB_RESULT_OK;
+ break;
+
+ case GTK_PRINT_OPERATION_RESULT_ERROR:
+ print_result = GEDIT_PRINT_JOB_RESULT_ERROR;
+ gtk_print_operation_get_error (operation, &error);
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+
+ /* Avoid that job is destroyed in the handler of the "done" message. */
+ g_object_ref (job);
+ g_signal_emit (job, signals[DONE], 0, print_result, error);
+ g_object_unref (job);
+}
+
+GeditPrintJob *
+gedit_print_job_new (TeplView *view)
+{
+ g_return_val_if_fail (TEPL_IS_VIEW (view), NULL);
+
+ return g_object_new (GEDIT_TYPE_PRINT_JOB,
+ "view", view,
+ NULL);
+}
+
+/* Note that gedit_print_job_print() can only be called once on a given
+ * GeditPrintJob.
+ */
+GtkPrintOperationResult
+gedit_print_job_print (GeditPrintJob *job,
+ GtkPrintOperationAction action,
+ GtkPageSetup *page_setup,
+ GtkPrintSettings *settings,
+ GtkWindow *parent,
+ GError **error)
+{
+ TeplBuffer *buffer;
+ gchar *job_name;
+
+ g_return_val_if_fail (job->operation == NULL, GTK_PRINT_OPERATION_RESULT_ERROR);
+ g_return_val_if_fail (job->compositor == NULL, GTK_PRINT_OPERATION_RESULT_ERROR);
+
+ job->operation = gtk_print_operation_new ();
+
+ job->is_preview = action == GTK_PRINT_OPERATION_ACTION_PREVIEW;
+
+ if (settings != NULL)
+ {
+ gtk_print_operation_set_print_settings (job->operation,
+ settings);
+ }
+
+ if (page_setup != NULL)
+ {
+ gtk_print_operation_set_default_page_setup (job->operation,
+ page_setup);
+ }
+
+ buffer = TEPL_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (job->view)));
+ job_name = tepl_file_get_short_name (tepl_buffer_get_file (buffer));
+ gtk_print_operation_set_job_name (job->operation, job_name);
+ g_free (job_name);
+
+ gtk_print_operation_set_embed_page_setup (job->operation, TRUE);
+
+ gtk_print_operation_set_custom_tab_label (job->operation, _("Text Editor"));
+
+ gtk_print_operation_set_allow_async (job->operation, TRUE);
+
+ g_signal_connect (job->operation,
+ "create-custom-widget",
+ G_CALLBACK (create_custom_widget_cb),
+ job);
+
+ g_signal_connect (job->operation,
+ "custom-widget-apply",
+ G_CALLBACK (custom_widget_apply_cb),
+ job);
+
+ g_signal_connect (job->operation,
+ "preview",
+ G_CALLBACK (preview_cb),
+ job);
+
+ g_signal_connect (job->operation,
+ "begin-print",
+ G_CALLBACK (begin_print_cb),
+ job);
+
+ g_signal_connect (job->operation,
+ "paginate",
+ G_CALLBACK (paginate_cb),
+ job);
+
+ g_signal_connect (job->operation,
+ "draw-page",
+ G_CALLBACK (draw_page_cb),
+ job);
+
+ g_signal_connect_object (job->operation,
+ "end-print",
+ G_CALLBACK (end_print_cb),
+ job,
+ 0);
+
+ g_signal_connect_object (job->operation,
+ "done",
+ G_CALLBACK (done_cb),
+ job,
+ 0);
+
+ return gtk_print_operation_run (job->operation,
+ action,
+ parent,
+ error);
+}
+
+void
+gedit_print_job_cancel (GeditPrintJob *job)
+{
+ g_return_if_fail (GEDIT_IS_PRINT_JOB (job));
+
+ gtk_print_operation_cancel (job->operation);
+}
+
+const gchar *
+gedit_print_job_get_status_string (GeditPrintJob *job)
+{
+ g_return_val_if_fail (GEDIT_IS_PRINT_JOB (job), NULL);
+ g_return_val_if_fail (job->status_string != NULL, NULL);
+
+ return job->status_string;
+}
+
+gdouble
+gedit_print_job_get_progress (GeditPrintJob *job)
+{
+ g_return_val_if_fail (GEDIT_IS_PRINT_JOB (job), 0.0);
+
+ return job->progress;
+}
+
+GtkPrintSettings *
+gedit_print_job_get_print_settings (GeditPrintJob *job)
+{
+ g_return_val_if_fail (GEDIT_IS_PRINT_JOB (job), NULL);
+
+ return gtk_print_operation_get_print_settings (job->operation);
+}
+
+GtkPageSetup *
+gedit_print_job_get_page_setup (GeditPrintJob *job)
+{
+ g_return_val_if_fail (GEDIT_IS_PRINT_JOB (job), NULL);
+
+ return gtk_print_operation_get_default_page_setup (job->operation);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-print-job.h b/gedit/gedit-print-job.h
new file mode 100644
index 0000000..248627e
--- /dev/null
+++ b/gedit/gedit-print-job.h
@@ -0,0 +1,71 @@
+/*
+ * gedit-print-job.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2000-2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2008 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/>.
+ */
+
+#ifndef GEDIT_PRINT_JOB_H
+#define GEDIT_PRINT_JOB_H
+
+#include <tepl/tepl.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_PRINT_JOB (gedit_print_job_get_type())
+
+G_DECLARE_FINAL_TYPE (GeditPrintJob, gedit_print_job,
+ GEDIT, PRINT_JOB,
+ GObject)
+
+typedef enum
+{
+ GEDIT_PRINT_JOB_STATUS_PAGINATING,
+ GEDIT_PRINT_JOB_STATUS_DRAWING
+} GeditPrintJobStatus;
+
+typedef enum
+{
+ GEDIT_PRINT_JOB_RESULT_OK,
+ GEDIT_PRINT_JOB_RESULT_CANCEL,
+ GEDIT_PRINT_JOB_RESULT_ERROR
+} GeditPrintJobResult;
+
+GeditPrintJob *gedit_print_job_new (TeplView *view);
+
+GtkPrintOperationResult gedit_print_job_print (GeditPrintJob *job,
+ GtkPrintOperationAction action,
+ GtkPageSetup *page_setup,
+ GtkPrintSettings *settings,
+ GtkWindow *parent,
+ GError **error);
+
+void gedit_print_job_cancel (GeditPrintJob *job);
+
+const gchar *gedit_print_job_get_status_string (GeditPrintJob *job);
+
+gdouble gedit_print_job_get_progress (GeditPrintJob *job);
+
+GtkPrintSettings *gedit_print_job_get_print_settings (GeditPrintJob *job);
+
+GtkPageSetup *gedit_print_job_get_page_setup (GeditPrintJob *job);
+
+G_END_DECLS
+
+#endif /* GEDIT_PRINT_JOB_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-print-preview.c b/gedit/gedit-print-preview.c
new file mode 100644
index 0000000..0ef4a9e
--- /dev/null
+++ b/gedit/gedit-print-preview.c
@@ -0,0 +1,1158 @@
+/*
+ * gedit-print-preview.c
+ *
+ * Copyright (C) 2008 Paolo Borelli
+ * Copyright (C) 2015 Sébastien Wilmet
+ *
+ * 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 "gedit-print-preview.h"
+
+#include <math.h>
+#include <stdlib.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+#include <cairo-pdf.h>
+
+#define PRINTER_DPI (72.0)
+#define TOOLTIP_THRESHOLD 20
+#define PAGE_PAD 12
+#define PAGE_SHADOW_OFFSET 5
+#define ZOOM_IN_FACTOR (1.2)
+#define ZOOM_OUT_FACTOR (1.0 / ZOOM_IN_FACTOR)
+
+struct _GeditPrintPreview
+{
+ GtkGrid parent_instance;
+
+ GtkPrintOperation *operation;
+ GtkPrintContext *context;
+ GtkPrintOperationPreview *gtk_preview;
+
+ GtkButton *prev_button;
+ GtkButton *next_button;
+ GtkEntry *page_entry;
+ GtkLabel *last_page_label;
+ GtkButton *multi_pages_button;
+ GtkButton *zoom_one_button;
+ GtkButton *zoom_fit_button;
+ GtkButton *zoom_in_button;
+ GtkButton *zoom_out_button;
+ GtkButton *close_button;
+
+ /* The GtkLayout is where the pages are drawn. The layout should have
+ * the focus, because key-press-events and scroll-events are handled on
+ * the layout. It is AFAIK not easily possible to handle those events on
+ * the GeditPrintPreview itself because when a toolbar item has the
+ * focus, some key presses (like the arrows) moves the focus to a
+ * sibling toolbar item instead.
+ */
+ GtkLayout *layout;
+
+ gdouble scale;
+
+ /* multipage support */
+ gint n_columns;
+
+ /* FIXME: handle correctly page selection (e.g. print only
+ * page 1-3, 7 and 12.
+ */
+ guint cur_page; /* starts at 0 */
+
+ gint cursor_x;
+ gint cursor_y;
+
+ guint has_tooltip : 1;
+};
+
+G_DEFINE_TYPE (GeditPrintPreview, gedit_print_preview, GTK_TYPE_GRID)
+
+static void
+gedit_print_preview_dispose (GObject *object)
+{
+ GeditPrintPreview *preview = GEDIT_PRINT_PREVIEW (object);
+
+ if (preview->gtk_preview != NULL)
+ {
+ GtkPrintOperationPreview *gtk_preview;
+
+ /* Set preview->gtk_preview to NULL because when calling
+ * end_preview() this dispose() function can be run a second
+ * time.
+ */
+ gtk_preview = preview->gtk_preview;
+ preview->gtk_preview = NULL;
+
+ gtk_print_operation_preview_end_preview (gtk_preview);
+
+ g_object_unref (gtk_preview);
+ }
+
+ g_clear_object (&preview->operation);
+ g_clear_object (&preview->context);
+
+ G_OBJECT_CLASS (gedit_print_preview_parent_class)->dispose (object);
+}
+
+static void
+gedit_print_preview_grab_focus (GtkWidget *widget)
+{
+ GeditPrintPreview *preview = GEDIT_PRINT_PREVIEW (widget);
+
+ gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
+}
+
+static void
+gedit_print_preview_class_init (GeditPrintPreviewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = gedit_print_preview_dispose;
+
+ widget_class->grab_focus = gedit_print_preview_grab_focus;
+
+ /* Bind class to template */
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/gedit/ui/gedit-print-preview.ui");
+ gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, prev_button);
+ gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, next_button);
+ gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, page_entry);
+ gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, last_page_label);
+ gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, multi_pages_button);
+ gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, zoom_one_button);
+ gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, zoom_fit_button);
+ gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, zoom_in_button);
+ gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, zoom_out_button);
+ gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, close_button);
+ gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, layout);
+}
+
+static gint
+get_n_pages (GeditPrintPreview *preview)
+{
+ gint n_pages;
+
+ g_object_get (preview->operation, "n-pages", &n_pages, NULL);
+
+ return n_pages;
+}
+
+static gdouble
+get_screen_dpi (GeditPrintPreview *preview)
+{
+ GdkScreen *screen;
+ gdouble dpi;
+ static gboolean warning_shown = FALSE;
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (preview));
+
+ if (screen == NULL)
+ {
+ return PRINTER_DPI;
+ }
+
+ dpi = gdk_screen_get_resolution (screen);
+ if (dpi < 30.0 || 600.0 < dpi)
+ {
+ if (!warning_shown)
+ {
+ g_warning ("Invalid the x-resolution for the screen, assuming 96dpi");
+ warning_shown = TRUE;
+ }
+
+ dpi = 96.0;
+ }
+
+ return dpi;
+}
+
+/* Get the paper size in points: these must be used only
+ * after the widget has been mapped and the dpi is known.
+ */
+static gdouble
+get_paper_width (GeditPrintPreview *preview)
+{
+ GtkPageSetup *page_setup;
+ gdouble paper_width;
+
+ page_setup = gtk_print_context_get_page_setup (preview->context);
+ paper_width = gtk_page_setup_get_paper_width (page_setup, GTK_UNIT_INCH);
+
+ return paper_width * get_screen_dpi (preview);
+}
+
+static gdouble
+get_paper_height (GeditPrintPreview *preview)
+{
+ GtkPageSetup *page_setup;
+ gdouble paper_height;
+
+ page_setup = gtk_print_context_get_page_setup (preview->context);
+ paper_height = gtk_page_setup_get_paper_height (page_setup, GTK_UNIT_INCH);
+
+ return paper_height * get_screen_dpi (preview);
+}
+
+/* The tile size is the size in pixels of the area where a page will be
+ * drawn, including the padding. The size is independent of the
+ * orientation.
+ */
+static void
+get_tile_size (GeditPrintPreview *preview,
+ gint *tile_width,
+ gint *tile_height)
+{
+ if (tile_width != NULL)
+ {
+ *tile_width = 2 * PAGE_PAD + round (preview->scale * get_paper_width (preview));
+ }
+
+ if (tile_height != NULL)
+ {
+ *tile_height = 2 * PAGE_PAD + round (preview->scale * get_paper_height (preview));
+ }
+}
+
+static void
+get_adjustments (GeditPrintPreview *preview,
+ GtkAdjustment **hadj,
+ GtkAdjustment **vadj)
+{
+ *hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (preview->layout));
+ *vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (preview->layout));
+}
+
+static void
+update_layout_size (GeditPrintPreview *preview)
+{
+ gint tile_width;
+ gint tile_height;
+
+ get_tile_size (preview, &tile_width, &tile_height);
+
+ /* force size of the drawing area to make the scrolled window work */
+ gtk_layout_set_size (preview->layout,
+ tile_width * preview->n_columns,
+ tile_height);
+
+ gtk_widget_queue_draw (GTK_WIDGET (preview->layout));
+}
+
+/* Zoom should always be set with one of these two function
+ * so that the tile size is properly updated.
+ */
+
+static void
+set_zoom_factor (GeditPrintPreview *preview,
+ gdouble zoom)
+{
+ preview->scale = zoom;
+ update_layout_size (preview);
+}
+
+static void
+set_zoom_fit_to_size (GeditPrintPreview *preview)
+{
+ GtkAdjustment *hadj, *vadj;
+ gdouble width, height;
+ gdouble paper_width, paper_height;
+ gdouble zoomx, zoomy;
+
+ get_adjustments (preview, &hadj, &vadj);
+
+ width = gtk_adjustment_get_page_size (hadj);
+ height = gtk_adjustment_get_page_size (vadj);
+
+ width /= preview->n_columns;
+
+ paper_width = get_paper_width (preview);
+ paper_height = get_paper_height (preview);
+
+ zoomx = MAX (1, width - 2 * PAGE_PAD) / paper_width;
+ zoomy = MAX (1, height - 2 * PAGE_PAD) / paper_height;
+
+ set_zoom_factor (preview, zoomx <= zoomy ? zoomx : zoomy);
+}
+
+static void
+zoom_in (GeditPrintPreview *preview)
+{
+ set_zoom_factor (preview, preview->scale * ZOOM_IN_FACTOR);
+}
+
+static void
+zoom_out (GeditPrintPreview *preview)
+{
+ set_zoom_factor (preview, preview->scale * ZOOM_OUT_FACTOR);
+}
+
+static void
+goto_page (GeditPrintPreview *preview,
+ gint page)
+{
+ gchar *page_str;
+ gint n_pages;
+
+ page_str = g_strdup_printf ("%d", page + 1);
+ gtk_entry_set_text (preview->page_entry, page_str);
+ g_free (page_str);
+
+ n_pages = get_n_pages (preview);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (preview->prev_button),
+ page > 0 &&
+ n_pages > 1);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (preview->next_button),
+ page < (n_pages - 1) &&
+ n_pages > 1);
+
+ if (page != preview->cur_page)
+ {
+ preview->cur_page = page;
+ if (n_pages > 0)
+ {
+ gtk_widget_queue_draw (GTK_WIDGET (preview->layout));
+ }
+ }
+}
+
+static void
+prev_button_clicked (GtkWidget *button,
+ GeditPrintPreview *preview)
+{
+ GdkEvent *event;
+ gint page;
+
+ event = gtk_get_current_event ();
+
+ if (event->button.state & GDK_SHIFT_MASK)
+ {
+ page = 0;
+ }
+ else
+ {
+ page = preview->cur_page - preview->n_columns;
+ }
+
+ goto_page (preview, MAX (page, 0));
+
+ gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
+
+ gdk_event_free (event);
+}
+
+static void
+next_button_clicked (GtkWidget *button,
+ GeditPrintPreview *preview)
+{
+ GdkEvent *event;
+ gint page;
+ gint n_pages = get_n_pages (preview);
+
+ event = gtk_get_current_event ();
+
+ if (event->button.state & GDK_SHIFT_MASK)
+ {
+ page = n_pages - 1;
+ }
+ else
+ {
+ page = preview->cur_page + preview->n_columns;
+ }
+
+ goto_page (preview, MIN (page, n_pages - 1));
+
+ gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
+
+ gdk_event_free (event);
+}
+
+static void
+page_entry_activated (GtkEntry *entry,
+ GeditPrintPreview *preview)
+{
+ const gchar *text;
+ gint page;
+ gint n_pages = get_n_pages (preview);
+
+ text = gtk_entry_get_text (entry);
+
+ page = CLAMP (atoi (text), 1, n_pages) - 1;
+ goto_page (preview, page);
+
+ gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
+}
+
+static void
+page_entry_insert_text (GtkEditable *editable,
+ const gchar *text,
+ gint length,
+ gint *position)
+{
+ const gchar *end;
+ const gchar *p;
+
+ end = text + length;
+
+ for (p = text; p < end; p = g_utf8_next_char (p))
+ {
+ if (!g_unichar_isdigit (g_utf8_get_char (p)))
+ {
+ g_signal_stop_emission_by_name (editable, "insert-text");
+ break;
+ }
+ }
+}
+
+static gboolean
+page_entry_focus_out (GtkEntry *entry,
+ GdkEventFocus *event,
+ GeditPrintPreview *preview)
+{
+ const gchar *text;
+ gint page;
+
+ text = gtk_entry_get_text (entry);
+ page = atoi (text) - 1;
+
+ /* Reset the page number only if really needed */
+ if (page != preview->cur_page)
+ {
+ gchar *str;
+
+ str = g_strdup_printf ("%d", preview->cur_page + 1);
+ gtk_entry_set_text (entry, str);
+ g_free (str);
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+on_1x1_clicked (GtkMenuItem *item,
+ GeditPrintPreview *preview)
+{
+ preview->n_columns = 1;
+ update_layout_size (preview);
+ gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
+}
+
+static void
+on_1x2_clicked (GtkMenuItem *item,
+ GeditPrintPreview *preview)
+{
+ preview->n_columns = 2;
+ set_zoom_fit_to_size (preview);
+ gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
+}
+
+static void
+multi_pages_button_clicked (GtkWidget *button,
+ GeditPrintPreview *preview)
+{
+ GtkWidget *menu;
+ GtkWidget *item;
+
+ menu = gtk_menu_new ();
+ gtk_widget_show (menu);
+ g_signal_connect (menu,
+ "selection-done",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ item = gtk_menu_item_new_with_label ("1x1");
+ gtk_widget_show (item);
+ gtk_menu_attach (GTK_MENU (menu), item, 0, 1, 0, 1);
+ g_signal_connect (item, "activate", G_CALLBACK (on_1x1_clicked), preview);
+
+ item = gtk_menu_item_new_with_label ("1x2");
+ gtk_widget_show (item);
+ gtk_menu_attach (GTK_MENU (menu), item, 1, 2, 0, 1);
+ g_signal_connect (item, "activate", G_CALLBACK (on_1x2_clicked), preview);
+
+ gtk_menu_popup_at_pointer (GTK_MENU (menu), NULL);
+}
+
+static void
+zoom_one_button_clicked (GtkWidget *button,
+ GeditPrintPreview *preview)
+{
+ set_zoom_factor (preview, 1);
+ gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
+}
+
+static void
+zoom_fit_button_clicked (GtkWidget *button,
+ GeditPrintPreview *preview)
+{
+ set_zoom_fit_to_size (preview);
+ gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
+}
+
+static void
+zoom_in_button_clicked (GtkWidget *button,
+ GeditPrintPreview *preview)
+{
+ zoom_in (preview);
+ gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
+}
+
+static void
+zoom_out_button_clicked (GtkWidget *button,
+ GeditPrintPreview *preview)
+{
+ zoom_out (preview);
+ gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
+}
+
+static void
+close_button_clicked (GtkWidget *button,
+ GeditPrintPreview *preview)
+{
+ gtk_widget_destroy (GTK_WIDGET (preview));
+}
+
+static gboolean
+scroll_event_activated (GtkWidget *widget,
+ GdkEventScroll *event,
+ GeditPrintPreview *preview)
+{
+ if (event->state & GDK_CONTROL_MASK)
+ {
+ if ((event->direction == GDK_SCROLL_UP) ||
+ (event->direction == GDK_SCROLL_SMOOTH &&
+ event->delta_y < 0))
+ {
+ zoom_in (preview);
+ }
+ else if ((event->direction == GDK_SCROLL_DOWN) ||
+ (event->direction == GDK_SCROLL_SMOOTH &&
+ event->delta_y > 0))
+ {
+ zoom_out (preview);
+ }
+
+ return GDK_EVENT_STOP;
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gint
+get_first_page_displayed (GeditPrintPreview *preview)
+{
+ return preview->cur_page - (preview->cur_page % preview->n_columns);
+}
+
+/* Returns the page number (starting from 0) or -1 if no page. */
+static gint
+get_page_at_coords (GeditPrintPreview *preview,
+ gint x,
+ gint y)
+{
+ gint tile_width, tile_height;
+ GtkAdjustment *hadj, *vadj;
+ gint col, page;
+
+ get_tile_size (preview, &tile_width, &tile_height);
+
+ if (tile_height <= 0 || tile_width <= 0)
+ {
+ return -1;
+ }
+
+ get_adjustments (preview, &hadj, &vadj);
+
+ x += gtk_adjustment_get_value (hadj);
+ y += gtk_adjustment_get_value (vadj);
+
+ col = x / tile_width;
+
+ if (col >= preview->n_columns || y > tile_height)
+ {
+ return -1;
+ }
+
+ page = get_first_page_displayed (preview) + col;
+
+ if (page >= get_n_pages (preview))
+ {
+ return -1;
+ }
+
+ /* FIXME: we could try to be picky and check if we actually are inside
+ * the page (i.e. not in the padding or shadow).
+ */
+ return page;
+}
+
+static gboolean
+on_preview_layout_motion_notify (GtkWidget *widget,
+ GdkEvent *event,
+ GeditPrintPreview *preview)
+{
+ gint temp_x;
+ gint temp_y;
+ gint diff_x;
+ gint diff_y;
+
+ temp_x = ((GdkEventMotion*)event)->x;
+ temp_y = ((GdkEventMotion*)event)->y;
+ diff_x = abs (temp_x - preview->cursor_x);
+ diff_y = abs (temp_y - preview->cursor_y);
+
+ if ((diff_x >= TOOLTIP_THRESHOLD) || (diff_y >= TOOLTIP_THRESHOLD))
+ {
+ preview->has_tooltip = FALSE;
+ preview->cursor_x = temp_x;
+ preview->cursor_y = temp_y;
+ }
+ else
+ {
+ preview->has_tooltip = TRUE;
+ }
+
+ return GDK_EVENT_STOP;
+}
+
+static gboolean
+preview_layout_query_tooltip (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_tip,
+ GtkTooltip *tooltip,
+ GeditPrintPreview *preview)
+{
+ if (preview->has_tooltip)
+ {
+ gint page;
+ gchar *tip;
+
+ page = get_page_at_coords (preview, x, y);
+ if (page < 0)
+ {
+ return FALSE;
+ }
+
+ tip = g_strdup_printf (_("Page %d of %d"),
+ page + 1,
+ get_n_pages (preview));
+
+ gtk_tooltip_set_text (tooltip, tip);
+ g_free (tip);
+
+ return TRUE;
+ }
+ else
+ {
+ preview->has_tooltip = TRUE;
+ return FALSE;
+ }
+}
+
+static gint
+preview_layout_key_press (GtkWidget *widget,
+ GdkEventKey *event,
+ GeditPrintPreview *preview)
+{
+ GtkAdjustment *hadj, *vadj;
+ gdouble x, y;
+ gdouble hlower, vlower;
+ gdouble hupper, vupper;
+ gdouble visible_width, visible_height;
+ gdouble hstep, vstep;
+ gint n_pages;
+ gboolean do_move = FALSE;
+
+ get_adjustments (preview, &hadj, &vadj);
+
+ x = gtk_adjustment_get_value (hadj);
+ y = gtk_adjustment_get_value (vadj);
+
+ hlower = gtk_adjustment_get_lower (hadj);
+ vlower = gtk_adjustment_get_lower (vadj);
+
+ hupper = gtk_adjustment_get_upper (hadj);
+ vupper = gtk_adjustment_get_upper (vadj);
+
+ visible_width = gtk_adjustment_get_page_size (hadj);
+ visible_height = gtk_adjustment_get_page_size (vadj);
+
+ hstep = 10;
+ vstep = 10;
+
+ n_pages = get_n_pages (preview);
+
+ switch (event->keyval)
+ {
+ case '1':
+ set_zoom_fit_to_size (preview);
+ break;
+
+ case '+':
+ case '=':
+ case GDK_KEY_KP_Add:
+ zoom_in (preview);
+ break;
+
+ case '-':
+ case '_':
+ case GDK_KEY_KP_Subtract:
+ zoom_out (preview);
+ break;
+
+ case GDK_KEY_KP_Right:
+ case GDK_KEY_Right:
+ if (event->state & GDK_SHIFT_MASK)
+ x = hupper - visible_width;
+ else
+ x = MIN (hupper - visible_width, x + hstep);
+ do_move = TRUE;
+ break;
+
+ case GDK_KEY_KP_Left:
+ case GDK_KEY_Left:
+ if (event->state & GDK_SHIFT_MASK)
+ x = hlower;
+ else
+ x = MAX (hlower, x - hstep);
+ do_move = TRUE;
+ break;
+
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_Up:
+ if (event->state & GDK_SHIFT_MASK)
+ goto page_up;
+
+ y = MAX (vlower, y - vstep);
+ do_move = TRUE;
+ break;
+
+ case GDK_KEY_KP_Down:
+ case GDK_KEY_Down:
+ if (event->state & GDK_SHIFT_MASK)
+ goto page_down;
+
+ y = MIN (vupper - visible_height, y + vstep);
+ do_move = TRUE;
+ break;
+
+ case GDK_KEY_KP_Page_Up:
+ case GDK_KEY_Page_Up:
+ case GDK_KEY_Delete:
+ case GDK_KEY_KP_Delete:
+ case GDK_KEY_BackSpace:
+ page_up:
+ if (y <= vlower)
+ {
+ if (preview->cur_page > 0)
+ {
+ goto_page (preview, preview->cur_page - 1);
+ y = (vupper - visible_height);
+ }
+ }
+ else
+ {
+ y = vlower;
+ }
+ do_move = TRUE;
+ break;
+
+ case GDK_KEY_KP_Page_Down:
+ case GDK_KEY_Page_Down:
+ case ' ':
+ page_down:
+ if (y >= (vupper - visible_height))
+ {
+ if (preview->cur_page < n_pages - 1)
+ {
+ goto_page (preview, preview->cur_page + 1);
+ y = vlower;
+ }
+ }
+ else
+ {
+ y = (vupper - visible_height);
+ }
+ do_move = TRUE;
+ break;
+
+ case GDK_KEY_KP_Home:
+ case GDK_KEY_Home:
+ goto_page (preview, 0);
+ y = vlower;
+ do_move = TRUE;
+ break;
+
+ case GDK_KEY_KP_End:
+ case GDK_KEY_End:
+ goto_page (preview, n_pages - 1);
+ y = vlower;
+ do_move = TRUE;
+ break;
+
+ case GDK_KEY_Escape:
+ gtk_widget_destroy (GTK_WIDGET (preview));
+ break;
+
+ case 'p':
+ if (event->state & GDK_MOD1_MASK)
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (preview->page_entry));
+ }
+ break;
+
+ default:
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ if (do_move)
+ {
+ gtk_adjustment_set_value (hadj, x);
+ gtk_adjustment_set_value (vadj, y);
+ }
+
+ return GDK_EVENT_STOP;
+}
+
+static void
+gedit_print_preview_init (GeditPrintPreview *preview)
+{
+ preview->cur_page = 0;
+ preview->scale = 1.0;
+ preview->n_columns = 1;
+ preview->cursor_x = 0;
+ preview->cursor_y = 0;
+ preview->has_tooltip = TRUE;
+
+ gtk_widget_init_template (GTK_WIDGET (preview));
+
+ g_signal_connect (preview->prev_button,
+ "clicked",
+ G_CALLBACK (prev_button_clicked),
+ preview);
+
+ g_signal_connect (preview->next_button,
+ "clicked",
+ G_CALLBACK (next_button_clicked),
+ preview);
+
+ g_signal_connect (preview->page_entry,
+ "activate",
+ G_CALLBACK (page_entry_activated),
+ preview);
+
+ g_signal_connect (preview->page_entry,
+ "insert-text",
+ G_CALLBACK (page_entry_insert_text),
+ NULL);
+
+ g_signal_connect (preview->page_entry,
+ "focus-out-event",
+ G_CALLBACK (page_entry_focus_out),
+ preview);
+
+ g_signal_connect (preview->multi_pages_button,
+ "clicked",
+ G_CALLBACK (multi_pages_button_clicked),
+ preview);
+
+ g_signal_connect (preview->zoom_one_button,
+ "clicked",
+ G_CALLBACK (zoom_one_button_clicked),
+ preview);
+
+ g_signal_connect (preview->zoom_fit_button,
+ "clicked",
+ G_CALLBACK (zoom_fit_button_clicked),
+ preview);
+
+ g_signal_connect (preview->zoom_in_button,
+ "clicked",
+ G_CALLBACK (zoom_in_button_clicked),
+ preview);
+
+ g_signal_connect (preview->zoom_out_button,
+ "clicked",
+ G_CALLBACK (zoom_out_button_clicked),
+ preview);
+
+ g_signal_connect (preview->close_button,
+ "clicked",
+ G_CALLBACK (close_button_clicked),
+ preview);
+
+ g_signal_connect (preview->layout,
+ "query-tooltip",
+ G_CALLBACK (preview_layout_query_tooltip),
+ preview);
+
+ g_signal_connect (preview->layout,
+ "key-press-event",
+ G_CALLBACK (preview_layout_key_press),
+ preview);
+
+ g_signal_connect (preview->layout,
+ "scroll-event",
+ G_CALLBACK (scroll_event_activated),
+ preview);
+
+ /* hide the tooltip once we move the cursor, since gtk does not do it for us */
+ g_signal_connect (preview->layout,
+ "motion-notify-event",
+ G_CALLBACK (on_preview_layout_motion_notify),
+ preview);
+
+ gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
+}
+
+static void
+draw_page_content (cairo_t *cr,
+ gint page_number,
+ GeditPrintPreview *preview)
+{
+ gdouble dpi;
+
+ /* scale to the desired size */
+ cairo_scale (cr, preview->scale, preview->scale);
+
+ dpi = get_screen_dpi (preview);
+ gtk_print_context_set_cairo_context (preview->context, cr, dpi, dpi);
+
+ gtk_print_operation_preview_render_page (preview->gtk_preview,
+ page_number);
+}
+
+/* For the frame, we scale and rotate manually, since
+ * the line width should not depend on the zoom and
+ * the drop shadow should be on the bottom right no matter
+ * the orientation.
+ */
+static void
+draw_page_frame (cairo_t *cr,
+ GeditPrintPreview *preview)
+{
+ gdouble width;
+ gdouble height;
+
+ width = get_paper_width (preview) * preview->scale;
+ height = get_paper_height (preview) * preview->scale;
+
+ /* drop shadow */
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_rectangle (cr,
+ PAGE_SHADOW_OFFSET, PAGE_SHADOW_OFFSET,
+ width, height);
+ cairo_fill (cr);
+
+ /* page frame */
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ cairo_rectangle (cr,
+ 0, 0,
+ width, height);
+ cairo_fill_preserve (cr);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_set_line_width (cr, 1);
+ cairo_stroke (cr);
+}
+
+static void
+draw_page (cairo_t *cr,
+ gdouble x,
+ gdouble y,
+ gint page_number,
+ GeditPrintPreview *preview)
+{
+ cairo_save (cr);
+
+ /* move to the page top left corner */
+ cairo_translate (cr, x + PAGE_PAD, y + PAGE_PAD);
+
+ draw_page_frame (cr, preview);
+ draw_page_content (cr, page_number, preview);
+
+ cairo_restore (cr);
+}
+
+static gboolean
+preview_draw (GtkWidget *widget,
+ cairo_t *cr,
+ GeditPrintPreview *preview)
+{
+ GdkWindow *bin_window;
+ gint tile_width;
+ gint page_num;
+ gint n_pages;
+ gint col;
+
+ bin_window = gtk_layout_get_bin_window (preview->layout);
+
+ if (!gtk_cairo_should_draw_window (cr, bin_window))
+ {
+ return GDK_EVENT_STOP;
+ }
+
+ cairo_save (cr);
+
+ gtk_cairo_transform_to_window (cr, widget, bin_window);
+
+ get_tile_size (preview, &tile_width, NULL);
+ n_pages = get_n_pages (preview);
+
+ col = 0;
+ page_num = get_first_page_displayed (preview);
+
+ while (col < preview->n_columns && page_num < n_pages)
+ {
+ if (!gtk_print_operation_preview_is_selected (preview->gtk_preview, page_num))
+ {
+ page_num++;
+ continue;
+ }
+
+ draw_page (cr,
+ col * tile_width,
+ 0,
+ page_num,
+ preview);
+
+ col++;
+ page_num++;
+ }
+
+ cairo_restore (cr);
+
+ return GDK_EVENT_STOP;
+}
+
+static void
+init_last_page_label (GeditPrintPreview *preview)
+{
+ gchar *str;
+
+ str = g_strdup_printf ("%d", get_n_pages (preview));
+ gtk_label_set_text (preview->last_page_label, str);
+ g_free (str);
+}
+
+static void
+preview_ready (GtkPrintOperationPreview *gtk_preview,
+ GtkPrintContext *context,
+ GeditPrintPreview *preview)
+{
+ init_last_page_label (preview);
+ goto_page (preview, 0);
+
+ set_zoom_factor (preview, 1.0);
+
+ /* let the default gtklayout handler clear the background */
+ g_signal_connect_after (preview->layout,
+ "draw",
+ G_CALLBACK (preview_draw),
+ preview);
+
+ gtk_widget_queue_draw (GTK_WIDGET (preview->layout));
+}
+
+/* HACK: we need a dummy surface to paginate... can we use something simpler? */
+
+static cairo_status_t
+dummy_write_func (G_GNUC_UNUSED gpointer closure,
+ G_GNUC_UNUSED const guchar *data,
+ G_GNUC_UNUSED guint length)
+{
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_surface_t *
+create_preview_surface_platform (GtkPaperSize *paper_size,
+ gdouble *dpi_x,
+ gdouble *dpi_y)
+{
+ gdouble width, height;
+
+ width = gtk_paper_size_get_width (paper_size, GTK_UNIT_POINTS);
+ height = gtk_paper_size_get_height (paper_size, GTK_UNIT_POINTS);
+
+ *dpi_x = *dpi_y = PRINTER_DPI;
+
+ return cairo_pdf_surface_create_for_stream (dummy_write_func, NULL,
+ width, height);
+}
+
+static cairo_surface_t *
+create_preview_surface (GeditPrintPreview *preview,
+ gdouble *dpi_x,
+ gdouble *dpi_y)
+{
+ GtkPageSetup *page_setup;
+ GtkPaperSize *paper_size;
+
+ page_setup = gtk_print_context_get_page_setup (preview->context);
+
+ /* Note: gtk_page_setup_get_paper_size() swaps width and height for
+ * landscape.
+ */
+ paper_size = gtk_page_setup_get_paper_size (page_setup);
+
+ return create_preview_surface_platform (paper_size, dpi_x, dpi_y);
+}
+
+GtkWidget *
+gedit_print_preview_new (GtkPrintOperation *operation,
+ GtkPrintOperationPreview *gtk_preview,
+ GtkPrintContext *context)
+{
+ GeditPrintPreview *preview;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ gdouble dpi_x, dpi_y;
+
+ g_return_val_if_fail (GTK_IS_PRINT_OPERATION (operation), NULL);
+ g_return_val_if_fail (GTK_IS_PRINT_OPERATION_PREVIEW (gtk_preview), NULL);
+
+ preview = g_object_new (GEDIT_TYPE_PRINT_PREVIEW, NULL);
+
+ preview->operation = g_object_ref (operation);
+ preview->gtk_preview = g_object_ref (gtk_preview);
+ preview->context = g_object_ref (context);
+
+ /* FIXME: is this legal?? */
+ gtk_print_operation_set_unit (operation, GTK_UNIT_POINTS);
+
+ g_signal_connect_object (gtk_preview,
+ "ready",
+ G_CALLBACK (preview_ready),
+ preview,
+ 0);
+
+ /* FIXME: we need a cr to paginate... but we can't get the drawing
+ * area surface because it's not there yet... for now I create
+ * a dummy pdf surface.
+ * gtk_print_context_set_cairo_context() should be called in the
+ * got-page-size handler.
+ */
+ surface = create_preview_surface (preview, &dpi_x, &dpi_y);
+ cr = cairo_create (surface);
+ gtk_print_context_set_cairo_context (context, cr, dpi_x, dpi_y);
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+
+ return GTK_WIDGET (preview);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-print-preview.h b/gedit/gedit-print-preview.h
new file mode 100644
index 0000000..40d85c8
--- /dev/null
+++ b/gedit/gedit-print-preview.h
@@ -0,0 +1,39 @@
+/*
+ * gedit-print-preview.h
+ *
+ * Copyright (C) 2008 Paolo Borelli
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_PRINT_PREVIEW_H
+#define GEDIT_PRINT_PREVIEW_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_PRINT_PREVIEW (gedit_print_preview_get_type ())
+
+G_DECLARE_FINAL_TYPE (GeditPrintPreview, gedit_print_preview, GEDIT, PRINT_PREVIEW, GtkGrid)
+
+GtkWidget *gedit_print_preview_new (GtkPrintOperation *operation,
+ GtkPrintOperationPreview *gtk_preview,
+ GtkPrintContext *context);
+
+G_END_DECLS
+
+#endif /* GEDIT_PRINT_PREVIEW_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-recent-osx.c b/gedit/gedit-recent-osx.c
new file mode 100644
index 0000000..7aabeda
--- /dev/null
+++ b/gedit/gedit-recent-osx.c
@@ -0,0 +1,250 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2014 - Paolo Borelli
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * 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 "gedit-recent-osx.h"
+
+static gint
+sort_recent_items_mru (GtkRecentInfo *a,
+ GtkRecentInfo *b,
+ gpointer unused)
+{
+ g_assert (a != NULL && b != NULL);
+ return gtk_recent_info_get_modified (b) - gtk_recent_info_get_modified (a);
+}
+
+static void
+populate_filter_info (GtkRecentInfo *info,
+ GtkRecentFilterInfo *filter_info,
+ GtkRecentFilterFlags needed)
+{
+ filter_info->uri = gtk_recent_info_get_uri (info);
+ filter_info->mime_type = gtk_recent_info_get_mime_type (info);
+
+ filter_info->contains = GTK_RECENT_FILTER_URI | GTK_RECENT_FILTER_MIME_TYPE;
+
+ if (needed & GTK_RECENT_FILTER_DISPLAY_NAME)
+ {
+ filter_info->display_name = gtk_recent_info_get_display_name (info);
+ filter_info->contains |= GTK_RECENT_FILTER_DISPLAY_NAME;
+ }
+ else
+ {
+ filter_info->uri = NULL;
+ }
+
+ if (needed & GTK_RECENT_FILTER_APPLICATION)
+ {
+ filter_info->applications = (const gchar **) gtk_recent_info_get_applications (info, NULL);
+ filter_info->contains |= GTK_RECENT_FILTER_APPLICATION;
+ }
+ else
+ {
+ filter_info->applications = NULL;
+ }
+
+ if (needed & GTK_RECENT_FILTER_GROUP)
+ {
+ filter_info->groups = (const gchar **) gtk_recent_info_get_groups (info, NULL);
+ filter_info->contains |= GTK_RECENT_FILTER_GROUP;
+ }
+ else
+ {
+ filter_info->groups = NULL;
+ }
+
+ if (needed & GTK_RECENT_FILTER_AGE)
+ {
+ filter_info->age = gtk_recent_info_get_age (info);
+ filter_info->contains |= GTK_RECENT_FILTER_AGE;
+ }
+ else
+ {
+ filter_info->age = -1;
+ }
+}
+
+/* The GeditRecentConfiguration struct is allocated and owned by the caller */
+void
+gedit_recent_configuration_init_default (GeditRecentConfiguration *config)
+{
+ config->manager = gtk_recent_manager_get_default ();
+
+ if (config->filter != NULL)
+ {
+ g_object_unref (config->filter);
+ }
+
+ config->filter = gtk_recent_filter_new ();
+ gtk_recent_filter_add_application (config->filter, g_get_application_name ());
+ gtk_recent_filter_add_mime_type (config->filter, "text/plain");
+ gtk_recent_filter_add_mime_type (config->filter, "application/x-zerosize");
+ g_object_ref_sink (config->filter);
+
+ config->limit = 5;
+ config->show_not_found = TRUE;
+ config->show_private = FALSE;
+ config->local_only = FALSE;
+
+ config->substring_filter = NULL;
+}
+
+/* The GeditRecentConfiguration struct is owned and destroyed by the caller */
+void
+gedit_recent_configuration_destroy (GeditRecentConfiguration *config)
+{
+ g_clear_object (&config->filter);
+ config->manager = NULL;
+
+ g_clear_pointer (&config->substring_filter, (GDestroyNotify)g_free);
+}
+
+GList *
+gedit_recent_get_items (GeditRecentConfiguration *config)
+{
+ GtkRecentFilterFlags needed;
+ GList *items;
+ GList *retitems = NULL;
+ gint length;
+ char *substring_filter = NULL;
+
+ if (config->limit == 0)
+ {
+ return NULL;
+ }
+
+ items = gtk_recent_manager_get_items (config->manager);
+
+ if (!items)
+ {
+ return NULL;
+ }
+
+ needed = gtk_recent_filter_get_needed (config->filter);
+ if (config->substring_filter && *config->substring_filter != '\0')
+ {
+ gchar *filter_normalized;
+
+ filter_normalized = g_utf8_normalize (config->substring_filter, -1, G_NORMALIZE_ALL);
+ substring_filter = g_utf8_casefold (filter_normalized, -1);
+ g_free (filter_normalized);
+ }
+
+ while (items)
+ {
+ GtkRecentInfo *info;
+ GtkRecentFilterInfo filter_info;
+ gboolean is_filtered;
+
+ info = items->data;
+ is_filtered = FALSE;
+
+ if (config->local_only && !gtk_recent_info_is_local (info))
+ {
+ is_filtered = TRUE;
+ }
+ else if (!config->show_private && gtk_recent_info_get_private_hint (info))
+ {
+ is_filtered = TRUE;
+ }
+ else if (!config->show_not_found && !gtk_recent_info_exists (info))
+ {
+ is_filtered = TRUE;
+ }
+ else
+ {
+ if (substring_filter)
+ {
+ gchar *uri_normalized;
+ gchar *uri_casefolded;
+
+ uri_normalized = g_utf8_normalize (gtk_recent_info_get_uri_display (info), -1, G_NORMALIZE_ALL);
+ uri_casefolded = g_utf8_casefold (uri_normalized, -1);
+ g_free (uri_normalized);
+
+ if (strstr (uri_casefolded, substring_filter) == NULL)
+ {
+ is_filtered = TRUE;
+ }
+
+ g_free (uri_casefolded);
+ }
+
+ if (!is_filtered)
+ {
+ populate_filter_info (info, &filter_info, needed);
+ is_filtered = !gtk_recent_filter_filter (config->filter, &filter_info);
+
+ /* these we own */
+ if (filter_info.applications)
+ {
+ g_strfreev ((gchar **) filter_info.applications);
+ }
+
+ if (filter_info.groups)
+ {
+ g_strfreev ((gchar **) filter_info.groups);
+ }
+ }
+ }
+
+ if (!is_filtered)
+ {
+ retitems = g_list_prepend (retitems, info);
+ }
+ else
+ {
+ gtk_recent_info_unref (info);
+ }
+
+ items = g_list_delete_link (items, items);
+ }
+
+ g_free (substring_filter);
+
+ if (!retitems)
+ {
+ return NULL;
+ }
+
+ retitems = g_list_sort_with_data (retitems, (GCompareDataFunc) sort_recent_items_mru, NULL);
+ length = g_list_length (retitems);
+
+ if ((config->limit != -1) && (length > config->limit))
+ {
+ GList *clamp, *l;
+
+ clamp = g_list_nth (retitems, config->limit - 1);
+
+ if (!clamp)
+ {
+ return retitems;
+ }
+
+ l = clamp->next;
+ clamp->next = NULL;
+
+ g_list_free_full (l, (GDestroyNotify) gtk_recent_info_unref);
+ }
+
+ return retitems;
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-recent-osx.h b/gedit/gedit-recent-osx.h
new file mode 100644
index 0000000..df77ca7
--- /dev/null
+++ b/gedit/gedit-recent-osx.h
@@ -0,0 +1,54 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2014 - Paolo Borelli
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * 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
+ * MERCHANWINDOWILITY 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/>.
+ */
+
+#ifndef GEDIT_RECENT_OSX_H
+#define GEDIT_RECENT_OSX_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/* TODO: this code can be simplified, the struct can be made private, the dead
+ * code can be removed, etc.
+ */
+
+typedef struct
+{
+ GtkRecentManager *manager;
+ GtkRecentFilter *filter;
+
+ gint limit;
+ gchar *substring_filter;
+
+ guint show_private : 1;
+ guint show_not_found : 1;
+ guint local_only : 1;
+} GeditRecentConfiguration;
+
+void gedit_recent_configuration_init_default (GeditRecentConfiguration *config);
+void gedit_recent_configuration_destroy (GeditRecentConfiguration *config);
+GList *gedit_recent_get_items (GeditRecentConfiguration *config);
+
+G_END_DECLS
+
+#endif /* GEDIT_RECENT_OSX_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-recent.c b/gedit/gedit-recent.c
new file mode 100644
index 0000000..d8cd7bd
--- /dev/null
+++ b/gedit/gedit-recent.c
@@ -0,0 +1,113 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2014 - Paolo Borelli
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ * Copyright (C) 2022 - Sébastien Wilmet
+ *
+ * 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 "gedit-recent.h"
+
+void
+gedit_recent_add_document (GeditDocument *document)
+{
+ TeplFile *file;
+ GFile *location;
+ GtkRecentManager *recent_manager;
+ GtkRecentData *recent_data;
+ gchar *uri;
+
+ g_return_if_fail (GEDIT_IS_DOCUMENT (document));
+
+ file = tepl_buffer_get_file (TEPL_BUFFER (document));
+ location = tepl_file_get_location (file);
+
+ if (location == NULL)
+ {
+ return;
+ }
+
+ recent_manager = gtk_recent_manager_get_default ();
+
+ /* Ensures to initialize the whole struct to 0's. Useful if the struct
+ * is extended.
+ */
+ recent_data = g_new0 (GtkRecentData, 1);
+
+ /* We use gtk_recent_manager_add_full() because GeditDocument's mime
+ * type is more accurate. The other fields are normally set to the same
+ * values as what gtk_recent_manager_add_item() does.
+ */
+ recent_data->mime_type = gedit_document_get_mime_type (document);
+ recent_data->app_name = (gchar *) g_get_application_name ();
+ recent_data->app_exec = g_strjoin (" ", g_get_prgname (), "%u", NULL);
+
+ uri = g_file_get_uri (location);
+
+ if (!gtk_recent_manager_add_full (recent_manager, uri, recent_data))
+ {
+ g_warning ("Failed to add uri '%s' to the recent manager.", uri);
+ }
+
+ g_free (recent_data->mime_type);
+ g_free (recent_data->app_exec);
+ g_free (recent_data);
+ g_free (uri);
+}
+
+/* If a file is local, chances are that if load/save fails the file has been
+ * removed and the failure is permanent so we remove it from the list of recent
+ * files. For remote files the failure may be just transitory and we keep the
+ * file in the list.
+ *
+ * FIXME: for files coming from external disks, USB keys etc, if the external
+ * storage device is not mounted, do not remove the file from the list. On the
+ * other hand, if the external storage device is mounted and the file isn't
+ * there, remove it from the list.
+ *
+ * FIXME: for remote files, perhaps do the same as for external storage devices,
+ * IF the connection to the server succeeds (the directory is there).
+ *
+ * FIXME: for the above FIXMEs, we should probably rely (in part) on the GError
+ * we receive.
+ * And if it was a file loading, a useful information to have is whether the
+ * file was opened from the list of recent files. If it was *not*, the user can
+ * also just navigate again through the file hierarchy to re-open it later if
+ * the file comes back.
+ *
+ * IDEA: or just never remove files from the recent list, and call it the
+ * Recent History or something like that.
+ */
+void
+gedit_recent_remove_if_local (GFile *location)
+{
+ g_return_if_fail (G_IS_FILE (location));
+
+ if (g_file_has_uri_scheme (location, "file"))
+ {
+ GtkRecentManager *recent_manager;
+ gchar *uri;
+
+ recent_manager = gtk_recent_manager_get_default ();
+
+ uri = g_file_get_uri (location);
+ gtk_recent_manager_remove_item (recent_manager, uri, NULL);
+ g_free (uri);
+ }
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-recent.h b/gedit/gedit-recent.h
new file mode 100644
index 0000000..068d89c
--- /dev/null
+++ b/gedit/gedit-recent.h
@@ -0,0 +1,38 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2014 - Paolo Borelli
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * 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
+ * MERCHANWINDOWILITY 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/>.
+ */
+
+#ifndef GEDIT_RECENT_H
+#define GEDIT_RECENT_H
+
+#include <gtk/gtk.h>
+#include <gedit/gedit-document.h>
+
+G_BEGIN_DECLS
+
+void gedit_recent_add_document (GeditDocument *document);
+
+void gedit_recent_remove_if_local (GFile *location);
+
+G_END_DECLS
+
+#endif /* GEDIT_RECENT_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-replace-dialog.c b/gedit/gedit-replace-dialog.c
new file mode 100644
index 0000000..3197ae1
--- /dev/null
+++ b/gedit/gedit-replace-dialog.c
@@ -0,0 +1,819 @@
+/*
+ * gedit-replace-dialog.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2005 Paolo Maggi
+ * Copyright (C) 2013 Sébastien Wilmet
+ *
+ * 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-replace-dialog.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "gedit-history-entry.h"
+#include "gedit-document.h"
+
+#define GEDIT_SEARCH_CONTEXT_KEY "gedit-search-context-key"
+
+struct _GeditReplaceDialog
+{
+ GtkDialog parent_instance;
+
+ GtkWidget *grid;
+ GtkWidget *search_label;
+ GtkWidget *search_entry;
+ GtkWidget *search_text_entry;
+ GtkWidget *replace_label;
+ GtkWidget *replace_entry;
+ GtkWidget *replace_text_entry;
+ GtkWidget *match_case_checkbutton;
+ GtkWidget *entire_word_checkbutton;
+ GtkWidget *regex_checkbutton;
+ GtkWidget *backwards_checkbutton;
+ GtkWidget *wrap_around_checkbutton;
+ GtkWidget *close_button;
+
+ GeditDocument *active_document;
+
+ guint idle_update_sensitivity_id;
+};
+
+G_DEFINE_TYPE (GeditReplaceDialog, gedit_replace_dialog, GTK_TYPE_DIALOG)
+
+static GtkSourceSearchContext *
+get_search_context (GeditReplaceDialog *dialog,
+ GeditDocument *doc)
+{
+ GtkSourceSearchContext *search_context;
+
+ if (doc == NULL)
+ {
+ return NULL;
+ }
+
+ search_context = gedit_document_get_search_context (doc);
+
+ if (search_context != NULL &&
+ g_object_get_data (G_OBJECT (search_context), GEDIT_SEARCH_CONTEXT_KEY) == dialog)
+ {
+ return search_context;
+ }
+
+ return NULL;
+}
+
+/* The search settings between the dialog's widgets (checkbuttons and the text
+ * entry) and the SearchSettings object are not bound. Instead, this function is
+ * called to set the search settings from the widgets to the SearchSettings
+ * object.
+ *
+ * The reason: the search and replace dialog is not an incremental search. You
+ * have to press the buttons to have an effect. The SearchContext is created
+ * only when a button is pressed, not before. If the SearchContext was created
+ * directly when the dialog window is shown, or when the document tab is
+ * switched, there would be a problem. When we switch betweeen tabs to find on
+ * which tab(s) we want to do the search, we may have older searches (still
+ * highlighted) that we don't want to lose, and we don't want the new search to
+ * appear on each tab that we open. Only when we press a button. So when the
+ * SearchContext is not already created, this is not an incremental search. Once
+ * the SearchContext is created, it's better to be consistent, and therefore we
+ * don't want the incremental search: we have to always press a button to
+ * execute the search.
+ *
+ * Likewise, each created SearchContext (from the GeditReplaceDialog) contains a
+ * different SearchSettings. When set_search_settings() is called for one
+ * document tab (and thus one SearchSettings), it doesn't have an effect on the
+ * other tabs. But the dialog widgets don't change.
+ */
+static void
+set_search_settings (GeditReplaceDialog *dialog)
+{
+ GtkSourceSearchContext *search_context;
+ GtkSourceSearchSettings *search_settings;
+ gboolean case_sensitive;
+ gboolean at_word_boundaries;
+ gboolean regex_enabled;
+ gboolean wrap_around;
+ const gchar *search_text;
+
+ search_context = get_search_context (dialog, dialog->active_document);
+
+ if (search_context == NULL)
+ {
+ return;
+ }
+
+ search_settings = gtk_source_search_context_get_settings (search_context);
+
+ case_sensitive = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->match_case_checkbutton));
+ gtk_source_search_settings_set_case_sensitive (search_settings, case_sensitive);
+
+ at_word_boundaries = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->entire_word_checkbutton));
+ gtk_source_search_settings_set_at_word_boundaries (search_settings, at_word_boundaries);
+
+ regex_enabled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->regex_checkbutton));
+ gtk_source_search_settings_set_regex_enabled (search_settings, regex_enabled);
+
+ wrap_around = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->wrap_around_checkbutton));
+ gtk_source_search_settings_set_wrap_around (search_settings, wrap_around);
+
+ search_text = gtk_entry_get_text (GTK_ENTRY (dialog->search_text_entry));
+
+ if (regex_enabled)
+ {
+ gtk_source_search_settings_set_search_text (search_settings, search_text);
+ }
+ else
+ {
+ gchar *unescaped_search_text = gtk_source_utils_unescape_search_text (search_text);
+ gtk_source_search_settings_set_search_text (search_settings, unescaped_search_text);
+ g_free (unescaped_search_text);
+ }
+}
+
+static GeditWindow *
+get_gedit_window (GeditReplaceDialog *dialog)
+{
+ GtkWindow *transient_for = gtk_window_get_transient_for (GTK_WINDOW (dialog));
+
+ return transient_for != NULL ? GEDIT_WINDOW (transient_for) : NULL;
+}
+
+static GeditDocument *
+get_active_document (GeditReplaceDialog *dialog)
+{
+ GeditWindow *window = get_gedit_window (dialog);
+
+ return window != NULL ? gedit_window_get_active_document (window) : NULL;
+}
+
+void
+gedit_replace_dialog_present_with_time (GeditReplaceDialog *dialog,
+ guint32 timestamp)
+{
+ g_return_if_fail (GEDIT_REPLACE_DIALOG (dialog));
+
+ gtk_window_present_with_time (GTK_WINDOW (dialog), timestamp);
+
+ gtk_widget_grab_focus (dialog->search_text_entry);
+}
+
+static gboolean
+gedit_replace_dialog_delete_event (GtkWidget *widget,
+ GdkEventAny *event)
+{
+ /* prevent destruction */
+ return TRUE;
+}
+
+static void
+set_error (GtkEntry *entry,
+ const gchar *error_msg)
+{
+ if (error_msg == NULL || error_msg[0] == '\0')
+ {
+ gtk_entry_set_icon_from_gicon (entry, GTK_ENTRY_ICON_SECONDARY, NULL);
+ gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY, NULL);
+ }
+ else
+ {
+ GIcon *icon = g_themed_icon_new_with_default_fallbacks ("dialog-error-symbolic");
+
+ gtk_entry_set_icon_from_gicon (entry, GTK_ENTRY_ICON_SECONDARY, icon);
+ gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY, error_msg);
+
+ g_object_unref (icon);
+ }
+}
+
+static void
+set_search_error (GeditReplaceDialog *dialog,
+ const gchar *error_msg)
+{
+ set_error (GTK_ENTRY (dialog->search_text_entry), error_msg);
+}
+
+void
+gedit_replace_dialog_set_replace_error (GeditReplaceDialog *dialog,
+ const gchar *error_msg)
+{
+ set_error (GTK_ENTRY (dialog->replace_text_entry), error_msg);
+}
+
+static gboolean
+has_search_error (GeditReplaceDialog *dialog)
+{
+ GIcon *icon;
+
+ icon = gtk_entry_get_icon_gicon (GTK_ENTRY (dialog->search_text_entry),
+ GTK_ENTRY_ICON_SECONDARY);
+
+ return icon != NULL;
+}
+
+static gboolean
+has_replace_error (GeditReplaceDialog *dialog)
+{
+ GIcon *icon;
+
+ icon = gtk_entry_get_icon_gicon (GTK_ENTRY (dialog->replace_text_entry),
+ GTK_ENTRY_ICON_SECONDARY);
+
+ return icon != NULL;
+}
+
+static void
+update_regex_error (GeditReplaceDialog *dialog)
+{
+ GtkSourceSearchContext *search_context;
+ GError *regex_error;
+
+ set_search_error (dialog, NULL);
+
+ search_context = get_search_context (dialog, dialog->active_document);
+
+ if (search_context == NULL)
+ {
+ return;
+ }
+
+ regex_error = gtk_source_search_context_get_regex_error (search_context);
+
+ if (regex_error != NULL)
+ {
+ set_search_error (dialog, regex_error->message);
+ g_error_free (regex_error);
+ }
+}
+
+static gboolean
+update_replace_response_sensitivity_cb (GeditReplaceDialog *dialog)
+{
+ GtkSourceSearchContext *search_context;
+ GtkTextIter start;
+ GtkTextIter end;
+ gint pos;
+
+ if (has_replace_error (dialog))
+ {
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ GEDIT_REPLACE_DIALOG_REPLACE_RESPONSE,
+ FALSE);
+
+ dialog->idle_update_sensitivity_id = 0;
+ return G_SOURCE_REMOVE;
+ }
+
+ search_context = get_search_context (dialog, dialog->active_document);
+
+ if (search_context == NULL)
+ {
+ dialog->idle_update_sensitivity_id = 0;
+ return G_SOURCE_REMOVE;
+ }
+
+ gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (dialog->active_document),
+ &start,
+ &end);
+
+ pos = gtk_source_search_context_get_occurrence_position (search_context,
+ &start,
+ &end);
+
+ if (pos < 0)
+ {
+ return G_SOURCE_CONTINUE;
+ }
+
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ GEDIT_REPLACE_DIALOG_REPLACE_RESPONSE,
+ pos > 0);
+
+ dialog->idle_update_sensitivity_id = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static void
+install_idle_update_sensitivity (GeditReplaceDialog *dialog)
+{
+ if (dialog->idle_update_sensitivity_id != 0)
+ {
+ return;
+ }
+
+ dialog->idle_update_sensitivity_id =
+ g_idle_add ((GSourceFunc)update_replace_response_sensitivity_cb,
+ dialog);
+}
+
+static void
+mark_set_cb (GtkTextBuffer *buffer,
+ GtkTextIter *location,
+ GtkTextMark *mark,
+ GeditReplaceDialog *dialog)
+{
+ GtkTextMark *insert;
+ GtkTextMark *selection_bound;
+
+ insert = gtk_text_buffer_get_insert (buffer);
+ selection_bound = gtk_text_buffer_get_selection_bound (buffer);
+
+ if (mark == insert || mark == selection_bound)
+ {
+ install_idle_update_sensitivity (dialog);
+ }
+}
+
+static void
+update_responses_sensitivity (GeditReplaceDialog *dialog)
+{
+ const gchar *search_text;
+ gboolean sensitive = TRUE;
+
+ install_idle_update_sensitivity (dialog);
+
+ search_text = gtk_entry_get_text (GTK_ENTRY (dialog->search_text_entry));
+
+ if (search_text[0] == '\0')
+ {
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ GEDIT_REPLACE_DIALOG_FIND_RESPONSE,
+ FALSE);
+
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ GEDIT_REPLACE_DIALOG_REPLACE_ALL_RESPONSE,
+ FALSE);
+
+ return;
+ }
+
+ sensitive = !has_search_error (dialog);
+
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ GEDIT_REPLACE_DIALOG_FIND_RESPONSE,
+ sensitive);
+
+ if (has_replace_error (dialog))
+ {
+ sensitive = FALSE;
+ }
+
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ GEDIT_REPLACE_DIALOG_REPLACE_ALL_RESPONSE,
+ sensitive);
+}
+
+static void
+regex_error_notify_cb (GeditReplaceDialog *dialog)
+{
+ update_regex_error (dialog);
+ update_responses_sensitivity (dialog);
+}
+
+static void
+disconnect_document (GeditReplaceDialog *dialog)
+{
+ GtkSourceSearchContext *search_context;
+
+ if (dialog->active_document == NULL)
+ {
+ return;
+ }
+
+ search_context = get_search_context (dialog, dialog->active_document);
+
+ if (search_context != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (search_context,
+ regex_error_notify_cb,
+ dialog);
+ }
+
+ g_signal_handlers_disconnect_by_func (dialog->active_document,
+ mark_set_cb,
+ dialog);
+
+ g_clear_object (&dialog->active_document);
+}
+
+static void
+connect_active_document (GeditReplaceDialog *dialog)
+{
+ GeditDocument *doc;
+ GtkSourceSearchContext *search_context;
+
+ disconnect_document (dialog);
+
+ doc = get_active_document (dialog);
+
+ if (doc == NULL)
+ {
+ return;
+ }
+
+ dialog->active_document = g_object_ref (doc);
+
+ search_context = get_search_context (dialog, doc);
+
+ if (search_context == NULL)
+ {
+ GtkSourceSearchSettings *settings = gtk_source_search_settings_new ();
+
+ search_context = gtk_source_search_context_new (GTK_SOURCE_BUFFER (doc),
+ settings);
+
+ /* Mark the search context that it comes from the search and
+ * replace dialog. Search contexts can be created also from the
+ * GeditViewFrame.
+ */
+ g_object_set_data (G_OBJECT (search_context),
+ GEDIT_SEARCH_CONTEXT_KEY,
+ dialog);
+
+ gedit_document_set_search_context (doc, search_context);
+
+ g_object_unref (settings);
+ g_object_unref (search_context);
+ }
+
+ g_signal_connect_object (search_context,
+ "notify::regex-error",
+ G_CALLBACK (regex_error_notify_cb),
+ dialog,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (doc,
+ "mark-set",
+ G_CALLBACK (mark_set_cb),
+ dialog,
+ 0);
+
+ update_regex_error (dialog);
+ update_responses_sensitivity (dialog);
+}
+
+static void
+response_cb (GtkDialog *dialog,
+ gint response_id)
+{
+ GeditReplaceDialog *dlg = GEDIT_REPLACE_DIALOG (dialog);
+ const gchar *str;
+
+ switch (response_id)
+ {
+ case GEDIT_REPLACE_DIALOG_REPLACE_RESPONSE:
+ case GEDIT_REPLACE_DIALOG_REPLACE_ALL_RESPONSE:
+ str = gtk_entry_get_text (GTK_ENTRY (dlg->replace_text_entry));
+ if (*str != '\0')
+ {
+ gedit_history_entry_prepend_text
+ (GEDIT_HISTORY_ENTRY (dlg->replace_entry),
+ str);
+ }
+ /* fall through, so that we also save the find entry */
+ case GEDIT_REPLACE_DIALOG_FIND_RESPONSE:
+ str = gtk_entry_get_text (GTK_ENTRY (dlg->search_text_entry));
+ if (*str != '\0')
+ {
+ gedit_history_entry_prepend_text
+ (GEDIT_HISTORY_ENTRY (dlg->search_entry),
+ str);
+ }
+ }
+
+ switch (response_id)
+ {
+ case GEDIT_REPLACE_DIALOG_REPLACE_RESPONSE:
+ case GEDIT_REPLACE_DIALOG_REPLACE_ALL_RESPONSE:
+ case GEDIT_REPLACE_DIALOG_FIND_RESPONSE:
+ connect_active_document (GEDIT_REPLACE_DIALOG (dialog));
+ set_search_settings (GEDIT_REPLACE_DIALOG (dialog));
+ }
+}
+
+static void
+gedit_replace_dialog_dispose (GObject *object)
+{
+ GeditReplaceDialog *dialog = GEDIT_REPLACE_DIALOG (object);
+
+ g_clear_object (&dialog->active_document);
+
+ if (dialog->idle_update_sensitivity_id != 0)
+ {
+ g_source_remove (dialog->idle_update_sensitivity_id);
+ dialog->idle_update_sensitivity_id = 0;
+ }
+
+ G_OBJECT_CLASS (gedit_replace_dialog_parent_class)->dispose (object);
+}
+
+static void
+gedit_replace_dialog_class_init (GeditReplaceDialogClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gobject_class->dispose = gedit_replace_dialog_dispose;
+ widget_class->delete_event = gedit_replace_dialog_delete_event;
+
+ /* Bind class to template */
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/gedit/ui/gedit-replace-dialog.ui");
+ gtk_widget_class_bind_template_child (widget_class, GeditReplaceDialog, grid);
+ gtk_widget_class_bind_template_child (widget_class, GeditReplaceDialog, search_label);
+ gtk_widget_class_bind_template_child (widget_class, GeditReplaceDialog, replace_label);
+ gtk_widget_class_bind_template_child (widget_class, GeditReplaceDialog, match_case_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditReplaceDialog, entire_word_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditReplaceDialog, regex_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditReplaceDialog, backwards_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditReplaceDialog, wrap_around_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, GeditReplaceDialog, close_button);
+}
+
+static void
+search_text_entry_changed (GtkEditable *editable,
+ GeditReplaceDialog *dialog)
+{
+ set_search_error (dialog, NULL);
+
+ update_responses_sensitivity (dialog);
+}
+
+static void
+replace_text_entry_changed (GtkEditable *editable,
+ GeditReplaceDialog *dialog)
+{
+ gedit_replace_dialog_set_replace_error (dialog, NULL);
+
+ update_responses_sensitivity (dialog);
+}
+
+static void
+regex_checkbutton_toggled (GtkToggleButton *checkbutton,
+ GeditReplaceDialog *dialog)
+{
+ if (!gtk_toggle_button_get_active (checkbutton))
+ {
+ /* Remove the regex error state so the user can search again */
+ set_search_error (dialog, NULL);
+ update_responses_sensitivity (dialog);
+ }
+}
+
+/* TODO: move in gedit-document.c and share it with gedit-view-frame */
+static gboolean
+get_selected_text (GtkTextBuffer *doc,
+ gchar **selected_text,
+ gint *len)
+{
+ GtkTextIter start, end;
+
+ g_return_val_if_fail (selected_text != NULL, FALSE);
+ g_return_val_if_fail (*selected_text == NULL, FALSE);
+
+ if (!gtk_text_buffer_get_selection_bounds (doc, &start, &end))
+ {
+ if (len != NULL)
+ {
+ len = 0;
+ }
+
+ return FALSE;
+ }
+
+ *selected_text = gtk_text_buffer_get_slice (doc, &start, &end, TRUE);
+
+ if (len != NULL)
+ {
+ *len = g_utf8_strlen (*selected_text, -1);
+ }
+
+ return TRUE;
+}
+
+static void
+show_cb (GeditReplaceDialog *dialog)
+{
+ GeditWindow *window;
+ GeditDocument *doc;
+ gboolean selection_exists;
+ gchar *selection = NULL;
+ gint selection_length;
+
+ window = get_gedit_window (dialog);
+
+ if (window == NULL)
+ {
+ return;
+ }
+
+ doc = get_active_document (dialog);
+
+ if (doc == NULL)
+ {
+ return;
+ }
+
+ selection_exists = get_selected_text (GTK_TEXT_BUFFER (doc),
+ &selection,
+ &selection_length);
+
+ if (selection_exists && selection != NULL && selection_length < 80)
+ {
+ gboolean regex_enabled;
+ gchar *escaped_selection;
+
+ regex_enabled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->regex_checkbutton));
+
+ if (regex_enabled)
+ {
+ escaped_selection = g_regex_escape_string (selection, -1);
+ }
+ else
+ {
+ escaped_selection = gtk_source_utils_escape_search_text (selection);
+ }
+
+ gtk_entry_set_text (GTK_ENTRY (dialog->search_text_entry),
+ escaped_selection);
+
+ g_free (escaped_selection);
+ }
+
+ g_free (selection);
+}
+
+static void
+hide_cb (GeditReplaceDialog *dialog)
+{
+ disconnect_document (dialog);
+}
+
+static void
+gedit_replace_dialog_init (GeditReplaceDialog *dlg)
+{
+ gtk_widget_init_template (GTK_WIDGET (dlg));
+
+ dlg->search_entry = gedit_history_entry_new ("search-for-entry", TRUE);
+ gtk_widget_set_size_request (dlg->search_entry, 300, -1);
+ gtk_widget_set_hexpand (GTK_WIDGET (dlg->search_entry), TRUE);
+ dlg->search_text_entry = gedit_history_entry_get_entry (GEDIT_HISTORY_ENTRY (dlg->search_entry));
+ gtk_entry_set_activates_default (GTK_ENTRY (dlg->search_text_entry), TRUE);
+ gtk_grid_attach_next_to (GTK_GRID (dlg->grid),
+ dlg->search_entry,
+ dlg->search_label,
+ GTK_POS_RIGHT, 1, 1);
+ gtk_widget_show_all (dlg->search_entry);
+
+ dlg->replace_entry = gedit_history_entry_new ("replace-with-entry", TRUE);
+ gtk_widget_set_hexpand (GTK_WIDGET (dlg->replace_entry), TRUE);
+ dlg->replace_text_entry = gedit_history_entry_get_entry (GEDIT_HISTORY_ENTRY (dlg->replace_entry));
+ gtk_entry_set_placeholder_text (GTK_ENTRY (dlg->replace_text_entry), _("Nothing"));
+ gtk_entry_set_activates_default (GTK_ENTRY (dlg->replace_text_entry), TRUE);
+ gtk_grid_attach_next_to (GTK_GRID (dlg->grid),
+ dlg->replace_entry,
+ dlg->replace_label,
+ GTK_POS_RIGHT, 1, 1);
+ gtk_widget_show_all (dlg->replace_entry);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (dlg->search_label),
+ dlg->search_entry);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (dlg->replace_label),
+ dlg->replace_entry);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dlg),
+ GEDIT_REPLACE_DIALOG_FIND_RESPONSE);
+
+ /* insensitive by default */
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dlg),
+ GEDIT_REPLACE_DIALOG_FIND_RESPONSE,
+ FALSE);
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dlg),
+ GEDIT_REPLACE_DIALOG_REPLACE_RESPONSE,
+ FALSE);
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dlg),
+ GEDIT_REPLACE_DIALOG_REPLACE_ALL_RESPONSE,
+ FALSE);
+
+ g_signal_connect (dlg->search_text_entry,
+ "changed",
+ G_CALLBACK (search_text_entry_changed),
+ dlg);
+
+ g_signal_connect (dlg->replace_text_entry,
+ "changed",
+ G_CALLBACK (replace_text_entry_changed),
+ dlg);
+
+ g_signal_connect (dlg->regex_checkbutton,
+ "toggled",
+ G_CALLBACK (regex_checkbutton_toggled),
+ dlg);
+
+ g_signal_connect (dlg,
+ "show",
+ G_CALLBACK (show_cb),
+ NULL);
+
+ g_signal_connect (dlg,
+ "hide",
+ G_CALLBACK (hide_cb),
+ NULL);
+
+ /* We connect here to make sure this handler runs before the others so
+ * that the search context is created.
+ */
+ g_signal_connect (dlg,
+ "response",
+ G_CALLBACK (response_cb),
+ NULL);
+}
+
+GtkWidget *
+gedit_replace_dialog_new (GeditWindow *window)
+{
+ GeditReplaceDialog *dialog;
+ gboolean use_header;
+
+ g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL);
+
+ dialog = g_object_new (GEDIT_TYPE_REPLACE_DIALOG,
+ "transient-for", window,
+ "destroy-with-parent", TRUE,
+ "use-header-bar", FALSE,
+ NULL);
+
+ /* We want the Find/Replace/ReplaceAll buttons at the bottom,
+ * so we turn off the automatic header bar, but we check the
+ * setting and if a header bar should be used, we create it
+ * manually and use it for the close button.
+ */
+ g_object_get (gtk_settings_get_default (),
+ "gtk-dialogs-use-header", &use_header,
+ NULL);
+
+ if (use_header)
+ {
+ GtkWidget *header_bar;
+
+ header_bar = gtk_header_bar_new ();
+ gtk_header_bar_set_title (GTK_HEADER_BAR (header_bar), _("Find and Replace"));
+ gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (header_bar), TRUE);
+ gtk_widget_show (header_bar);
+ gtk_window_set_titlebar (GTK_WINDOW (dialog), header_bar);
+ }
+ else
+ {
+ gtk_widget_set_no_show_all (dialog->close_button, FALSE);
+ gtk_widget_show (dialog->close_button);
+ }
+
+ return GTK_WIDGET (dialog);
+}
+
+const gchar *
+gedit_replace_dialog_get_replace_text (GeditReplaceDialog *dialog)
+{
+ g_return_val_if_fail (GEDIT_IS_REPLACE_DIALOG (dialog), NULL);
+
+ return gtk_entry_get_text (GTK_ENTRY (dialog->replace_text_entry));
+}
+
+gboolean
+gedit_replace_dialog_get_backwards (GeditReplaceDialog *dialog)
+{
+ g_return_val_if_fail (GEDIT_IS_REPLACE_DIALOG (dialog), FALSE);
+
+ return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->backwards_checkbutton));
+}
+
+/* This function returns the original search text. The search text from the
+ * search settings has been unescaped, and the escape function is not
+ * reciprocal. So to avoid bugs, we have to deal with the original search text.
+ */
+const gchar *
+gedit_replace_dialog_get_search_text (GeditReplaceDialog *dialog)
+{
+ g_return_val_if_fail (GEDIT_IS_REPLACE_DIALOG (dialog), NULL);
+
+ return gtk_entry_get_text (GTK_ENTRY (dialog->search_text_entry));
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-replace-dialog.h b/gedit/gedit-replace-dialog.h
new file mode 100644
index 0000000..235b587
--- /dev/null
+++ b/gedit/gedit-replace-dialog.h
@@ -0,0 +1,58 @@
+/*
+ * gedit-replace-dialog.h
+ * 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/>.
+ */
+
+#ifndef GEDIT_REPLACE_DIALOG_H
+#define GEDIT_REPLACE_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <gtksourceview/gtksource.h>
+#include "gedit-window.h"
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_REPLACE_DIALOG (gedit_replace_dialog_get_type ())
+G_DECLARE_FINAL_TYPE (GeditReplaceDialog, gedit_replace_dialog, GEDIT, REPLACE_DIALOG, GtkDialog)
+
+enum
+{
+ GEDIT_REPLACE_DIALOG_FIND_RESPONSE = 100,
+ GEDIT_REPLACE_DIALOG_REPLACE_RESPONSE,
+ GEDIT_REPLACE_DIALOG_REPLACE_ALL_RESPONSE
+};
+
+GtkWidget *gedit_replace_dialog_new (GeditWindow *window);
+
+void gedit_replace_dialog_present_with_time (GeditReplaceDialog *dialog,
+ guint32 timestamp);
+
+const gchar *gedit_replace_dialog_get_search_text (GeditReplaceDialog *dialog);
+
+const gchar *gedit_replace_dialog_get_replace_text (GeditReplaceDialog *dialog);
+
+gboolean gedit_replace_dialog_get_backwards (GeditReplaceDialog *dialog);
+
+void gedit_replace_dialog_set_replace_error (GeditReplaceDialog *dialog,
+ const gchar *error_msg);
+
+G_END_DECLS
+
+#endif /* GEDIT_REPLACE_DIALOG_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-settings.c b/gedit/gedit-settings.c
new file mode 100644
index 0000000..515949e
--- /dev/null
+++ b/gedit/gedit-settings.c
@@ -0,0 +1,322 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2002-2005 - Paolo Maggi
+ * Copyright (C) 2009 - Ignacio Casal Quinteiro
+ * Copyright (C) 2020 - Sébastien Wilmet <swilmet@gnome.org>
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "gedit-settings.h"
+#include <gtksourceview/gtksource.h>
+#include "gedit-app.h"
+
+struct _GeditSettings
+{
+ GObject parent_instance;
+
+ GSettings *settings_editor;
+ GSettings *settings_ui;
+ GSettings *settings_file_chooser_state;
+};
+
+static GeditSettings *singleton = NULL;
+
+G_DEFINE_TYPE (GeditSettings, gedit_settings, G_TYPE_OBJECT)
+
+static void
+gedit_settings_dispose (GObject *object)
+{
+ GeditSettings *self = GEDIT_SETTINGS (object);
+
+ g_clear_object (&self->settings_editor);
+ g_clear_object (&self->settings_ui);
+ g_clear_object (&self->settings_file_chooser_state);
+
+ G_OBJECT_CLASS (gedit_settings_parent_class)->dispose (object);
+}
+
+static void
+gedit_settings_finalize (GObject *object)
+{
+ GeditSettings *self = GEDIT_SETTINGS (object);
+
+ if (singleton == self)
+ {
+ singleton = NULL;
+ }
+
+ G_OBJECT_CLASS (gedit_settings_parent_class)->finalize (object);
+}
+
+static void
+gedit_settings_class_init (GeditSettingsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gedit_settings_dispose;
+ object_class->finalize = gedit_settings_finalize;
+}
+
+static void
+on_auto_save_changed (GSettings *settings,
+ const gchar *key,
+ GeditSettings *self)
+{
+ gboolean auto_save;
+ GList *docs;
+ GList *l;
+
+ auto_save = g_settings_get_boolean (settings, key);
+
+ docs = gedit_app_get_documents (GEDIT_APP (g_application_get_default ()));
+
+ for (l = docs; l != NULL; l = l->next)
+ {
+ GeditTab *tab = gedit_tab_get_from_document (GEDIT_DOCUMENT (l->data));
+ gedit_tab_set_auto_save_enabled (tab, auto_save);
+ }
+
+ g_list_free (docs);
+}
+
+static void
+on_auto_save_interval_changed (GSettings *settings,
+ const gchar *key,
+ GeditSettings *self)
+{
+ guint auto_save_interval;
+ GList *docs;
+ GList *l;
+
+ auto_save_interval = g_settings_get_uint (settings, key);
+
+ docs = gedit_app_get_documents (GEDIT_APP (g_application_get_default ()));
+
+ for (l = docs; l != NULL; l = l->next)
+ {
+ GeditTab *tab = gedit_tab_get_from_document (GEDIT_DOCUMENT (l->data));
+ gedit_tab_set_auto_save_interval (tab, auto_save_interval);
+ }
+
+ g_list_free (docs);
+}
+
+static void
+on_syntax_highlighting_changed (GSettings *settings,
+ const gchar *key,
+ GeditSettings *self)
+{
+ gboolean enable;
+ GList *docs;
+ GList *windows;
+ GList *l;
+
+ enable = g_settings_get_boolean (settings, key);
+
+ docs = gedit_app_get_documents (GEDIT_APP (g_application_get_default ()));
+
+ for (l = docs; l != NULL; l = l->next)
+ {
+ GtkSourceBuffer *buffer = GTK_SOURCE_BUFFER (l->data);
+ gtk_source_buffer_set_highlight_syntax (buffer, enable);
+ }
+
+ g_list_free (docs);
+
+ /* update the sensitivity of the Higlight Mode menu item */
+ windows = gedit_app_get_main_windows (GEDIT_APP (g_application_get_default ()));
+
+ for (l = windows; l != NULL; l = l->next)
+ {
+ GAction *action;
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (l->data), "highlight-mode");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enable);
+ }
+
+ g_list_free (windows);
+}
+
+static void
+gedit_settings_init (GeditSettings *self)
+{
+ self->settings_editor = g_settings_new ("org.gnome.gedit.preferences.editor");
+ self->settings_ui = g_settings_new ("org.gnome.gedit.preferences.ui");
+ self->settings_file_chooser_state = g_settings_new ("org.gnome.gedit.state.file-chooser");
+
+ g_signal_connect_object (self->settings_editor,
+ "changed::auto-save",
+ G_CALLBACK (on_auto_save_changed),
+ self,
+ 0);
+
+ g_signal_connect_object (self->settings_editor,
+ "changed::auto-save-interval",
+ G_CALLBACK (on_auto_save_interval_changed),
+ self,
+ 0);
+
+ g_signal_connect_object (self->settings_editor,
+ "changed::syntax-highlighting",
+ G_CALLBACK (on_syntax_highlighting_changed),
+ self,
+ 0);
+}
+
+GeditSettings *
+_gedit_settings_get_singleton (void)
+{
+ if (singleton == NULL)
+ {
+ singleton = g_object_new (GEDIT_TYPE_SETTINGS, NULL);
+ }
+
+ return singleton;
+}
+
+void
+gedit_settings_unref_singleton (void)
+{
+ if (singleton != NULL)
+ {
+ g_object_unref (singleton);
+ }
+
+ /* singleton is not set to NULL here, it is set to NULL in
+ * gedit_settings_finalize() (i.e. when we are sure that the ref count
+ * reaches 0).
+ */
+}
+
+GSettings *
+_gedit_settings_peek_editor_settings (GeditSettings *self)
+{
+ g_return_val_if_fail (GEDIT_IS_SETTINGS (self), NULL);
+
+ return self->settings_editor;
+}
+
+GSettings *
+_gedit_settings_peek_file_chooser_state_settings (GeditSettings *self)
+{
+ g_return_val_if_fail (GEDIT_IS_SETTINGS (self), NULL);
+
+ return self->settings_file_chooser_state;
+}
+
+static gboolean
+strv_is_empty (gchar **strv)
+{
+ if (strv == NULL || strv[0] == NULL)
+ {
+ return TRUE;
+ }
+
+ /* Contains one empty string. */
+ if (strv[1] == NULL && strv[0][0] == '\0')
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static GSList *
+encoding_strv_to_list (const gchar * const *encoding_strv)
+{
+ GSList *list = NULL;
+ gchar **p;
+
+ for (p = (gchar **)encoding_strv; p != NULL && *p != NULL; p++)
+ {
+ const gchar *charset = *p;
+ const GtkSourceEncoding *encoding;
+
+ encoding = gtk_source_encoding_get_from_charset (charset);
+
+ if (encoding != NULL &&
+ g_slist_find (list, encoding) == NULL)
+ {
+ list = g_slist_prepend (list, (gpointer)encoding);
+ }
+ }
+
+ return g_slist_reverse (list);
+}
+
+/* Take in priority the candidate encodings from GSettings. If the gsetting is
+ * empty, take the default candidates of GtkSourceEncoding.
+ * Also, ensure that UTF-8 and the current locale encoding are present.
+ * Returns: a list of GtkSourceEncodings. Free with g_slist_free().
+ */
+GSList *
+gedit_settings_get_candidate_encodings (gboolean *default_candidates)
+{
+ const GtkSourceEncoding *utf8_encoding;
+ const GtkSourceEncoding *current_encoding;
+ GSettings *settings;
+ gchar **settings_strv;
+ GSList *candidates;
+
+ utf8_encoding = gtk_source_encoding_get_utf8 ();
+ current_encoding = gtk_source_encoding_get_current ();
+
+ settings = g_settings_new ("org.gnome.gedit.preferences.encodings");
+
+ settings_strv = g_settings_get_strv (settings, GEDIT_SETTINGS_CANDIDATE_ENCODINGS);
+
+ if (strv_is_empty (settings_strv))
+ {
+ if (default_candidates != NULL)
+ {
+ *default_candidates = TRUE;
+ }
+
+ candidates = gtk_source_encoding_get_default_candidates ();
+ }
+ else
+ {
+ if (default_candidates != NULL)
+ {
+ *default_candidates = FALSE;
+ }
+
+ candidates = encoding_strv_to_list ((const gchar * const *) settings_strv);
+
+ /* Ensure that UTF-8 is present. */
+ if (utf8_encoding != current_encoding &&
+ g_slist_find (candidates, utf8_encoding) == NULL)
+ {
+ candidates = g_slist_prepend (candidates, (gpointer)utf8_encoding);
+ }
+
+ /* Ensure that the current locale encoding is present (if not
+ * present, it must be the first encoding).
+ */
+ if (g_slist_find (candidates, current_encoding) == NULL)
+ {
+ candidates = g_slist_prepend (candidates, (gpointer)current_encoding);
+ }
+ }
+
+ g_object_unref (settings);
+ g_strfreev (settings_strv);
+ return candidates;
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-settings.h b/gedit/gedit-settings.h
new file mode 100644
index 0000000..cf4d504
--- /dev/null
+++ b/gedit/gedit-settings.h
@@ -0,0 +1,106 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2002 - Paolo Maggi
+ * Copyright (C) 2009 - Ignacio Casal Quinteiro
+ * Copyright (C) 2020 - Sébastien Wilmet <swilmet@gnome.org>
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef GEDIT_SETTINGS_H
+#define GEDIT_SETTINGS_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_SETTINGS (gedit_settings_get_type ())
+
+G_DECLARE_FINAL_TYPE (GeditSettings, gedit_settings, GEDIT, SETTINGS, GObject)
+
+G_GNUC_INTERNAL
+GeditSettings * _gedit_settings_get_singleton (void);
+
+void gedit_settings_unref_singleton (void);
+
+G_GNUC_INTERNAL
+GSettings * _gedit_settings_peek_editor_settings (GeditSettings *self);
+
+G_GNUC_INTERNAL
+GSettings * _gedit_settings_peek_file_chooser_state_settings (GeditSettings *self);
+
+GSList * gedit_settings_get_candidate_encodings (gboolean *default_candidates);
+
+/* key constants */
+#define GEDIT_SETTINGS_USE_DEFAULT_FONT "use-default-font"
+#define GEDIT_SETTINGS_EDITOR_FONT "editor-font"
+#define GEDIT_SETTINGS_SCHEME "scheme"
+#define GEDIT_SETTINGS_CREATE_BACKUP_COPY "create-backup-copy"
+#define GEDIT_SETTINGS_AUTO_SAVE "auto-save"
+#define GEDIT_SETTINGS_AUTO_SAVE_INTERVAL "auto-save-interval"
+#define GEDIT_SETTINGS_MAX_UNDO_ACTIONS "max-undo-actions"
+#define GEDIT_SETTINGS_WRAP_MODE "wrap-mode"
+#define GEDIT_SETTINGS_WRAP_LAST_SPLIT_MODE "wrap-last-split-mode"
+#define GEDIT_SETTINGS_TABS_SIZE "tabs-size"
+#define GEDIT_SETTINGS_INSERT_SPACES "insert-spaces"
+#define GEDIT_SETTINGS_AUTO_INDENT "auto-indent"
+#define GEDIT_SETTINGS_DISPLAY_LINE_NUMBERS "display-line-numbers"
+#define GEDIT_SETTINGS_HIGHLIGHT_CURRENT_LINE "highlight-current-line"
+#define GEDIT_SETTINGS_BRACKET_MATCHING "bracket-matching"
+#define GEDIT_SETTINGS_DISPLAY_RIGHT_MARGIN "display-right-margin"
+#define GEDIT_SETTINGS_RIGHT_MARGIN_POSITION "right-margin-position"
+#define GEDIT_SETTINGS_SMART_HOME_END "smart-home-end"
+#define GEDIT_SETTINGS_RESTORE_CURSOR_POSITION "restore-cursor-position"
+#define GEDIT_SETTINGS_SYNTAX_HIGHLIGHTING "syntax-highlighting"
+#define GEDIT_SETTINGS_SEARCH_HIGHLIGHTING "search-highlighting"
+#define GEDIT_SETTINGS_BACKGROUND_PATTERN "background-pattern"
+#define GEDIT_SETTINGS_STATUSBAR_VISIBLE "statusbar-visible"
+#define GEDIT_SETTINGS_SIDE_PANEL_VISIBLE "side-panel-visible"
+#define GEDIT_SETTINGS_BOTTOM_PANEL_VISIBLE "bottom-panel-visible"
+#define GEDIT_SETTINGS_PRINT_SYNTAX_HIGHLIGHTING "print-syntax-highlighting"
+#define GEDIT_SETTINGS_PRINT_HEADER "print-header"
+#define GEDIT_SETTINGS_PRINT_WRAP_MODE "print-wrap-mode"
+#define GEDIT_SETTINGS_PRINT_LINE_NUMBERS "print-line-numbers"
+#define GEDIT_SETTINGS_PRINT_FONT_BODY_PANGO "print-font-body-pango"
+#define GEDIT_SETTINGS_PRINT_FONT_HEADER_PANGO "print-font-header-pango"
+#define GEDIT_SETTINGS_PRINT_FONT_NUMBERS_PANGO "print-font-numbers-pango"
+#define GEDIT_SETTINGS_PRINT_MARGIN_LEFT "margin-left"
+#define GEDIT_SETTINGS_PRINT_MARGIN_TOP "margin-top"
+#define GEDIT_SETTINGS_PRINT_MARGIN_RIGHT "margin-right"
+#define GEDIT_SETTINGS_PRINT_MARGIN_BOTTOM "margin-bottom"
+#define GEDIT_SETTINGS_CANDIDATE_ENCODINGS "candidate-encodings"
+#define GEDIT_SETTINGS_ACTIVE_PLUGINS "active-plugins"
+#define GEDIT_SETTINGS_ENSURE_TRAILING_NEWLINE "ensure-trailing-newline"
+
+/* window state keys */
+#define GEDIT_SETTINGS_WINDOW_STATE "state"
+#define GEDIT_SETTINGS_WINDOW_SIZE "size"
+#define GEDIT_SETTINGS_SHOW_TABS_MODE "show-tabs-mode"
+#define GEDIT_SETTINGS_SIDE_PANEL_SIZE "side-panel-size"
+#define GEDIT_SETTINGS_SIDE_PANEL_ACTIVE_PAGE "side-panel-active-page"
+#define GEDIT_SETTINGS_BOTTOM_PANEL_SIZE "bottom-panel-size"
+#define GEDIT_SETTINGS_BOTTOM_PANEL_ACTIVE_PAGE "bottom-panel-active-page"
+
+/* file chooser state keys */
+#define GEDIT_SETTINGS_ACTIVE_FILE_FILTER "filter-id"
+#define GEDIT_SETTINGS_FILE_CHOOSER_OPEN_RECENT "open-recent"
+
+G_END_DECLS
+
+#endif /* GEDIT_SETTINGS_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-status-menu-button.c b/gedit/gedit-status-menu-button.c
new file mode 100644
index 0000000..98deed7
--- /dev/null
+++ b/gedit/gedit-status-menu-button.c
@@ -0,0 +1,146 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ *
+ * 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 "gedit-status-menu-button.h"
+
+struct _GeditStatusMenuButton
+{
+ GtkMenuButton parent_instance;
+
+ GtkLabel *label;
+};
+
+enum
+{
+ PROP_0,
+ PROP_LABEL
+};
+
+G_DEFINE_TYPE (GeditStatusMenuButton, gedit_status_menu_button, GTK_TYPE_MENU_BUTTON)
+
+static void
+gedit_status_menu_button_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditStatusMenuButton *button = GEDIT_STATUS_MENU_BUTTON (object);
+
+ switch (prop_id)
+ {
+ case PROP_LABEL:
+ g_value_set_string (value, gedit_status_menu_button_get_label (button));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_status_menu_button_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditStatusMenuButton *button = GEDIT_STATUS_MENU_BUTTON (object);
+
+ switch (prop_id)
+ {
+ case PROP_LABEL:
+ gedit_status_menu_button_set_label (button, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_status_menu_button_class_init (GeditStatusMenuButtonClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = gedit_status_menu_button_get_property;
+ object_class->set_property = gedit_status_menu_button_set_property;
+
+ g_object_class_override_property (object_class, PROP_LABEL, "label");
+
+ /* Bind class to template */
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/gedit/ui/gedit-status-menu-button.ui");
+ gtk_widget_class_bind_template_child_internal (widget_class, GeditStatusMenuButton, label);
+}
+
+static void
+gedit_status_menu_button_init (GeditStatusMenuButton *button)
+{
+ GtkCssProvider *css_provider;
+ GtkStyleContext *context;
+ const gchar *css_style =
+ "* {\n"
+ " padding: 1px 8px 2px 4px;\n"
+ " border: 0;\n"
+ " outline-width: 0;\n"
+ "}\n";
+
+ gtk_widget_init_template (GTK_WIDGET (button));
+
+ /* Make it as small as possible. */
+ css_provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_data (css_provider, css_style, -1, NULL);
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (button));
+ gtk_style_context_add_provider (context,
+ GTK_STYLE_PROVIDER (css_provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ g_object_unref (css_provider);
+}
+
+GtkWidget *
+gedit_status_menu_button_new (void)
+{
+ return g_object_new (GEDIT_TYPE_STATUS_MENU_BUTTON, NULL);
+}
+
+/* We cannot rely on gtk_button_set_label() since it manually replaces the
+ * internal child instead of just setting the property :(
+ */
+
+void
+gedit_status_menu_button_set_label (GeditStatusMenuButton *button,
+ const gchar *label)
+{
+ g_return_if_fail (GEDIT_IS_STATUS_MENU_BUTTON (button));
+
+ gtk_label_set_markup (button->label, label);
+}
+
+const gchar *
+gedit_status_menu_button_get_label (GeditStatusMenuButton *button)
+{
+ g_return_val_if_fail (GEDIT_IS_STATUS_MENU_BUTTON (button), NULL);
+
+ return gtk_label_get_label (button->label);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-status-menu-button.h b/gedit/gedit-status-menu-button.h
new file mode 100644
index 0000000..1e19a0b
--- /dev/null
+++ b/gedit/gedit-status-menu-button.h
@@ -0,0 +1,44 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_STATUS_MENU_BUTTON_H
+#define GEDIT_STATUS_MENU_BUTTON_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_STATUS_MENU_BUTTON (gedit_status_menu_button_get_type ())
+
+G_DECLARE_FINAL_TYPE (GeditStatusMenuButton, gedit_status_menu_button,
+ GEDIT, STATUS_MENU_BUTTON,
+ GtkMenuButton)
+
+GtkWidget * gedit_status_menu_button_new (void);
+
+void gedit_status_menu_button_set_label (GeditStatusMenuButton *button,
+ const gchar *label);
+
+const gchar * gedit_status_menu_button_get_label (GeditStatusMenuButton *button);
+
+G_END_DECLS
+
+#endif /* GEDIT_STATUS_MENU_BUTTON_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-statusbar.c b/gedit/gedit-statusbar.c
new file mode 100644
index 0000000..4dd6491
--- /dev/null
+++ b/gedit/gedit-statusbar.c
@@ -0,0 +1,241 @@
+/*
+ * gedit-statusbar.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2005 - Paolo Borelli
+ *
+ * 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-statusbar.h"
+#include <glib/gi18n.h>
+#include "gedit-app.h"
+#include "gedit-status-menu-button.h"
+
+struct _GeditStatusbar
+{
+ GtkStatusbar parent_instance;
+
+ GtkWidget *error_frame;
+ GtkWidget *error_image;
+ GtkWidget *state_frame;
+ GtkWidget *load_image;
+ GtkWidget *save_image;
+ GtkWidget *print_image;
+
+ /* tmp flash timeout data */
+ guint flash_timeout;
+ guint flash_context_id;
+ guint flash_message_id;
+
+ guint generic_message_context_id;
+};
+
+G_DEFINE_TYPE (GeditStatusbar, gedit_statusbar, GTK_TYPE_STATUSBAR)
+
+static void
+gedit_statusbar_dispose (GObject *object)
+{
+ GeditStatusbar *statusbar = GEDIT_STATUSBAR (object);
+
+ if (statusbar->flash_timeout > 0)
+ {
+ g_source_remove (statusbar->flash_timeout);
+ statusbar->flash_timeout = 0;
+ }
+
+ G_OBJECT_CLASS (gedit_statusbar_parent_class)->dispose (object);
+}
+
+static void
+gedit_statusbar_class_init (GeditStatusbarClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = gedit_statusbar_dispose;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/gedit/ui/gedit-statusbar.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, GeditStatusbar, error_frame);
+ gtk_widget_class_bind_template_child (widget_class, GeditStatusbar, error_image);
+ gtk_widget_class_bind_template_child (widget_class, GeditStatusbar, state_frame);
+ gtk_widget_class_bind_template_child (widget_class, GeditStatusbar, load_image);
+ gtk_widget_class_bind_template_child (widget_class, GeditStatusbar, save_image);
+ gtk_widget_class_bind_template_child (widget_class, GeditStatusbar, print_image);
+}
+
+static void
+gedit_statusbar_init (GeditStatusbar *statusbar)
+{
+ gtk_widget_init_template (GTK_WIDGET (statusbar));
+
+ statusbar->generic_message_context_id =
+ gtk_statusbar_get_context_id (GTK_STATUSBAR (statusbar), "generic_message");
+}
+
+/**
+ * gedit_statusbar_new:
+ *
+ * Creates a new #GeditStatusbar.
+ *
+ * Return value: the new #GeditStatusbar object
+ */
+GtkWidget *
+gedit_statusbar_new (void)
+{
+ return g_object_new (GEDIT_TYPE_STATUSBAR, NULL);
+}
+
+static gboolean
+remove_message_timeout (GeditStatusbar *statusbar)
+{
+ gtk_statusbar_remove (GTK_STATUSBAR (statusbar),
+ statusbar->flash_context_id,
+ statusbar->flash_message_id);
+
+ /* Remove the timeout. */
+ statusbar->flash_timeout = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static void
+flash_text (GeditStatusbar *statusbar,
+ guint context_id,
+ const gchar *text)
+{
+ const guint32 flash_length = 3000; /* Three seconds. */
+
+ /* Remove a currently ongoing flash message. */
+ if (statusbar->flash_timeout > 0)
+ {
+ g_source_remove (statusbar->flash_timeout);
+ statusbar->flash_timeout = 0;
+
+ gtk_statusbar_remove (GTK_STATUSBAR (statusbar),
+ statusbar->flash_context_id,
+ statusbar->flash_message_id);
+ }
+
+ statusbar->flash_context_id = context_id;
+ statusbar->flash_message_id = gtk_statusbar_push (GTK_STATUSBAR (statusbar),
+ context_id,
+ text);
+
+ statusbar->flash_timeout = g_timeout_add (flash_length,
+ (GSourceFunc) remove_message_timeout,
+ statusbar);
+}
+
+/* FIXME this is an issue for introspection */
+/**
+ * gedit_statusbar_flash_message:
+ * @statusbar: a #GeditStatusbar
+ * @context_id: message context_id
+ * @format: message to flash on the statusbar
+ * @...: the arguments to insert in @format
+ *
+ * Flash a temporary message on the statusbar.
+ */
+void
+gedit_statusbar_flash_message (GeditStatusbar *statusbar,
+ guint context_id,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+ gchar *text;
+
+ g_return_if_fail (GEDIT_IS_STATUSBAR (statusbar));
+ g_return_if_fail (format != NULL);
+
+ va_start (args, format);
+ text = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ flash_text (statusbar, context_id, text);
+
+ g_free (text);
+}
+
+void
+_gedit_statusbar_flash_generic_message (GeditStatusbar *statusbar,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+ gchar *text;
+
+ g_return_if_fail (GEDIT_IS_STATUSBAR (statusbar));
+ g_return_if_fail (format != NULL);
+
+ va_start (args, format);
+ text = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ flash_text (statusbar, statusbar->generic_message_context_id, text);
+
+ g_free (text);
+}
+
+void
+gedit_statusbar_set_window_state (GeditStatusbar *statusbar,
+ GeditWindowState state,
+ gint num_of_errors)
+{
+ g_return_if_fail (GEDIT_IS_STATUSBAR (statusbar));
+
+ gtk_widget_hide (statusbar->state_frame);
+ gtk_widget_hide (statusbar->save_image);
+ gtk_widget_hide (statusbar->load_image);
+ gtk_widget_hide (statusbar->print_image);
+
+ if (state & GEDIT_WINDOW_STATE_SAVING)
+ {
+ gtk_widget_show (statusbar->state_frame);
+ gtk_widget_show (statusbar->save_image);
+ }
+ if (state & GEDIT_WINDOW_STATE_LOADING)
+ {
+ gtk_widget_show (statusbar->state_frame);
+ gtk_widget_show (statusbar->load_image);
+ }
+ if (state & GEDIT_WINDOW_STATE_PRINTING)
+ {
+ gtk_widget_show (statusbar->state_frame);
+ gtk_widget_show (statusbar->print_image);
+ }
+ if (state & GEDIT_WINDOW_STATE_ERROR)
+ {
+ gchar *tip;
+
+ tip = g_strdup_printf (ngettext("There is a tab with errors",
+ "There are %d tabs with errors",
+ num_of_errors),
+ num_of_errors);
+
+ gtk_widget_set_tooltip_text (statusbar->error_image, tip);
+ g_free (tip);
+
+ gtk_widget_show (statusbar->error_frame);
+ }
+ else
+ {
+ gtk_widget_hide (statusbar->error_frame);
+ }
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-statusbar.h b/gedit/gedit-statusbar.h
new file mode 100644
index 0000000..c7d90c1
--- /dev/null
+++ b/gedit/gedit-statusbar.h
@@ -0,0 +1,53 @@
+/*
+ * gedit-statusbar.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2005 - Paolo Borelli
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_STATUSBAR_H
+#define GEDIT_STATUSBAR_H
+
+#include <gtk/gtk.h>
+#include <gedit/gedit-window.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_STATUSBAR (gedit_statusbar_get_type ())
+
+G_DECLARE_FINAL_TYPE (GeditStatusbar, gedit_statusbar, GEDIT, STATUSBAR, GtkStatusbar)
+
+GtkWidget *gedit_statusbar_new (void);
+
+void gedit_statusbar_set_window_state (GeditStatusbar *statusbar,
+ GeditWindowState state,
+ gint num_of_errors);
+
+void gedit_statusbar_flash_message (GeditStatusbar *statusbar,
+ guint context_id,
+ const gchar *format,
+ ...) G_GNUC_PRINTF(3, 4);
+
+G_GNUC_INTERNAL
+void _gedit_statusbar_flash_generic_message (GeditStatusbar *statusbar,
+ const gchar *format,
+ ...) G_GNUC_PRINTF(2, 3);
+
+G_END_DECLS
+
+#endif
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-tab-label.c b/gedit/gedit-tab-label.c
new file mode 100644
index 0000000..d24b2e5
--- /dev/null
+++ b/gedit/gedit-tab-label.c
@@ -0,0 +1,295 @@
+/*
+ * gedit-tab-label.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Paolo Borelli
+ *
+ * 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 "gedit-tab-label.h"
+#include "gedit-tab-private.h"
+
+struct _GeditTabLabel
+{
+ GtkBox parent_instance;
+
+ GeditTab *tab;
+
+ GtkWidget *spinner;
+ GtkWidget *icon;
+ GtkWidget *label;
+ GtkWidget *close_button;
+};
+
+enum
+{
+ PROP_0,
+ PROP_TAB,
+ LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP];
+
+enum
+{
+ CLOSE_CLICKED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (GeditTabLabel, gedit_tab_label, GTK_TYPE_BOX)
+
+static void
+gedit_tab_label_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditTabLabel *tab_label = GEDIT_TAB_LABEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_TAB:
+ g_return_if_fail (tab_label->tab == NULL);
+ tab_label->tab = GEDIT_TAB (g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_tab_label_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditTabLabel *tab_label = GEDIT_TAB_LABEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_TAB:
+ g_value_set_object (value, tab_label->tab);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+close_button_clicked_cb (GtkWidget *widget,
+ GeditTabLabel *tab_label)
+{
+ g_signal_emit (tab_label, signals[CLOSE_CLICKED], 0, NULL);
+}
+
+static void
+sync_tooltip (GeditTab *tab,
+ GeditTabLabel *tab_label)
+{
+ gchar *str;
+
+ str = _gedit_tab_get_tooltip (tab);
+ g_return_if_fail (str != NULL);
+
+ gtk_widget_set_tooltip_markup (GTK_WIDGET (tab_label), str);
+ g_free (str);
+}
+
+static void
+sync_name (GeditTab *tab,
+ GParamSpec *pspec,
+ GeditTabLabel *tab_label)
+{
+ gchar *str;
+
+ g_return_if_fail (tab == tab_label->tab);
+
+ str = _gedit_tab_get_name (tab);
+ g_return_if_fail (str != NULL);
+
+ gtk_label_set_text (GTK_LABEL (tab_label->label), str);
+ g_free (str);
+
+ sync_tooltip (tab, tab_label);
+}
+
+static void
+update_close_button_sensitivity (GeditTabLabel *tab_label)
+{
+ GeditTabState state = gedit_tab_get_state (tab_label->tab);
+
+ gtk_widget_set_sensitive (tab_label->close_button,
+ (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));
+}
+
+static void
+sync_state (GeditTab *tab,
+ GParamSpec *pspec,
+ GeditTabLabel *tab_label)
+{
+ GeditTabState state;
+
+ g_return_if_fail (tab == tab_label->tab);
+
+ update_close_button_sensitivity (tab_label);
+
+ state = gedit_tab_get_state (tab);
+
+ if ((state == GEDIT_TAB_STATE_LOADING) ||
+ (state == GEDIT_TAB_STATE_SAVING) ||
+ (state == GEDIT_TAB_STATE_REVERTING))
+ {
+ gtk_widget_hide (tab_label->icon);
+
+ gtk_widget_show (tab_label->spinner);
+ gtk_spinner_start (GTK_SPINNER (tab_label->spinner));
+ }
+ else
+ {
+ GdkPixbuf *pixbuf;
+
+ pixbuf = _gedit_tab_get_icon (tab);
+
+ if (pixbuf != NULL)
+ {
+ gtk_image_set_from_pixbuf (GTK_IMAGE (tab_label->icon),
+ pixbuf);
+
+ g_clear_object (&pixbuf);
+
+ gtk_widget_show (tab_label->icon);
+ }
+ else
+ {
+ gtk_widget_hide (tab_label->icon);
+ }
+
+ gtk_spinner_stop (GTK_SPINNER (tab_label->spinner));
+ gtk_widget_hide (tab_label->spinner);
+ }
+
+ /* sync tip since encoding is known only after load/save end */
+ sync_tooltip (tab, tab_label);
+}
+
+static void
+gedit_tab_label_constructed (GObject *object)
+{
+ GeditTabLabel *tab_label = GEDIT_TAB_LABEL (object);
+
+ if (tab_label->tab == NULL)
+ {
+ g_critical ("The tab label was not properly constructed");
+ return;
+ }
+
+ sync_name (tab_label->tab, NULL, tab_label);
+ sync_state (tab_label->tab, NULL, tab_label);
+
+ g_signal_connect_object (tab_label->tab,
+ "notify::name",
+ G_CALLBACK (sync_name),
+ tab_label,
+ 0);
+
+ g_signal_connect_object (tab_label->tab,
+ "notify::state",
+ G_CALLBACK (sync_state),
+ tab_label,
+ 0);
+
+ G_OBJECT_CLASS (gedit_tab_label_parent_class)->constructed (object);
+}
+
+static void
+gedit_tab_label_close_clicked (GeditTabLabel *tab_label)
+{
+}
+
+static void
+gedit_tab_label_class_init (GeditTabLabelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->set_property = gedit_tab_label_set_property;
+ object_class->get_property = gedit_tab_label_get_property;
+ object_class->constructed = gedit_tab_label_constructed;
+
+ properties[PROP_TAB] =
+ g_param_spec_object ("tab",
+ "Tab",
+ "The GeditTab",
+ GEDIT_TYPE_TAB,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ signals[CLOSE_CLICKED] =
+ g_signal_new_class_handler ("close-clicked",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_CALLBACK (gedit_tab_label_close_clicked),
+ 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-tab-label.ui");
+ gtk_widget_class_bind_template_child (widget_class, GeditTabLabel, spinner);
+ gtk_widget_class_bind_template_child (widget_class, GeditTabLabel, icon);
+ gtk_widget_class_bind_template_child (widget_class, GeditTabLabel, label);
+ gtk_widget_class_bind_template_child (widget_class, GeditTabLabel, close_button);
+}
+
+static void
+gedit_tab_label_init (GeditTabLabel *tab_label)
+{
+ gtk_widget_init_template (GTK_WIDGET (tab_label));
+
+ g_signal_connect (tab_label->close_button,
+ "clicked",
+ G_CALLBACK (close_button_clicked_cb),
+ tab_label);
+}
+
+GeditTab *
+gedit_tab_label_get_tab (GeditTabLabel *tab_label)
+{
+ g_return_val_if_fail (GEDIT_IS_TAB_LABEL (tab_label), NULL);
+
+ return tab_label->tab;
+}
+
+GtkWidget *
+gedit_tab_label_new (GeditTab *tab)
+{
+ return g_object_new (GEDIT_TYPE_TAB_LABEL,
+ "tab", tab,
+ NULL);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-tab-label.h b/gedit/gedit-tab-label.h
new file mode 100644
index 0000000..df54eae
--- /dev/null
+++ b/gedit/gedit-tab-label.h
@@ -0,0 +1,41 @@
+/*
+ * gedit-tab-label.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Paolo Borelli
+ *
+ * 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/>.
+ */
+
+#ifndef GEDIT_TAB_LABEL_H
+#define GEDIT_TAB_LABEL_H
+
+#include <gtk/gtk.h>
+#include <gedit/gedit-tab.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_TAB_LABEL (gedit_tab_label_get_type ())
+
+G_DECLARE_FINAL_TYPE (GeditTabLabel, gedit_tab_label, GEDIT, TAB_LABEL, GtkBox)
+
+GtkWidget *gedit_tab_label_new (GeditTab *tab);
+
+GeditTab *gedit_tab_label_get_tab (GeditTabLabel *tab_label);
+
+G_END_DECLS
+
+#endif /* GEDIT_TAB_LABEL_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-tab-private.h b/gedit/gedit-tab-private.h
new file mode 100644
index 0000000..1b84f13
--- /dev/null
+++ b/gedit/gedit-tab-private.h
@@ -0,0 +1,81 @@
+/*
+ * gedit-tab.h
+ * 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/>.
+ */
+
+#ifndef GEDIT_TAB_PRIVATE_H
+#define GEDIT_TAB_PRIVATE_H
+
+#include "gedit-tab.h"
+#include "gedit-view-frame.h"
+
+G_BEGIN_DECLS
+
+GeditTab *_gedit_tab_new (void);
+
+gchar *_gedit_tab_get_name (GeditTab *tab);
+
+gchar *_gedit_tab_get_tooltip (GeditTab *tab);
+
+GdkPixbuf *_gedit_tab_get_icon (GeditTab *tab);
+
+void _gedit_tab_load (GeditTab *tab,
+ GFile *location,
+ const GtkSourceEncoding *encoding,
+ gint line_pos,
+ gint column_pos,
+ gboolean create);
+
+void _gedit_tab_load_stream (GeditTab *tab,
+ GInputStream *location,
+ const GtkSourceEncoding *encoding,
+ gint line_pos,
+ gint column_pos);
+
+void _gedit_tab_revert (GeditTab *tab);
+
+void _gedit_tab_save_async (GeditTab *tab,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean _gedit_tab_save_finish (GeditTab *tab,
+ GAsyncResult *result);
+
+void _gedit_tab_save_as_async (GeditTab *tab,
+ GFile *location,
+ const GtkSourceEncoding *encoding,
+ GtkSourceNewlineType newline_type,
+ GtkSourceCompressionType compression_type,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+void _gedit_tab_print (GeditTab *tab);
+
+void _gedit_tab_mark_for_closing (GeditTab *tab);
+
+gboolean _gedit_tab_get_can_close (GeditTab *tab);
+
+GeditViewFrame *_gedit_tab_get_view_frame (GeditTab *tab);
+
+G_END_DECLS
+
+#endif /* GEDIT_TAB_PRIVATE_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-tab.c b/gedit/gedit-tab.c
new file mode 100644
index 0000000..3e3fc5b
--- /dev/null
+++ b/gedit/gedit-tab.c
@@ -0,0 +1,3057 @@
+/*
+ * gedit-tab.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2014, 2015 - Sébastien Wilmet
+ *
+ * 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 "gedit-tab.h"
+#include "gedit-tab-private.h"
+
+#include <stdlib.h>
+#include <glib/gi18n.h>
+#include <tepl/tepl.h>
+
+#include "gedit-app.h"
+#include "gedit-app-private.h"
+#include "gedit-recent.h"
+#include "gedit-utils.h"
+#include "gedit-io-error-info-bar.h"
+#include "gedit-print-job.h"
+#include "gedit-print-preview.h"
+#include "gedit-debug.h"
+#include "gedit-document.h"
+#include "gedit-document-private.h"
+#include "gedit-enum-types.h"
+#include "gedit-settings.h"
+#include "gedit-view-frame.h"
+
+#define GEDIT_TAB_KEY "GEDIT_TAB_KEY"
+
+struct _GeditTab
+{
+ GtkBox parent_instance;
+
+ GeditTabState state;
+
+ GSettings *editor_settings;
+
+ GeditViewFrame *frame;
+
+ GtkWidget *info_bar;
+
+ GeditPrintJob *print_job;
+ GtkWidget *print_preview;
+
+ GtkSourceFileSaverFlags save_flags;
+
+ guint scroll_timeout;
+ guint scroll_idle;
+
+ gint auto_save_interval;
+ guint auto_save_timeout;
+
+ GCancellable *cancellable;
+
+ guint editable : 1;
+ guint auto_save : 1;
+
+ guint ask_if_externally_modified : 1;
+};
+
+typedef struct _SaverData SaverData;
+typedef struct _LoaderData LoaderData;
+
+struct _SaverData
+{
+ GtkSourceFileSaver *saver;
+
+ GTimer *timer;
+
+ /* Notes about the create_backup saver flag:
+ * - At the beginning of a new file saving, force_no_backup is FALSE.
+ * The create_backup flag is set to the saver if it is enabled in
+ * GSettings and if it isn't an auto-save.
+ * - If creating the backup gives an error, and if the user wants to
+ * save the file without the backup, force_no_backup is set to TRUE
+ * and the create_backup flag is removed from the saver.
+ * force_no_backup as TRUE means that the create_backup flag should
+ * never be added again to the saver (for the current file saving).
+ * - When another error occurs and if the user explicitly retry again
+ * the file saving, the create_backup flag is added to the saver if
+ * (1) it is enabled in GSettings, (2) if force_no_backup is FALSE.
+ * - The create_backup flag is added when the user expressed his or her
+ * willing to save the file, by pressing a button for example. For an
+ * auto-save, the create_backup flag is thus not added initially, but
+ * can be added later when an error occurs and the user clicks on a
+ * button in the info bar to retry the file saving.
+ */
+ guint force_no_backup : 1;
+};
+
+struct _LoaderData
+{
+ GeditTab *tab;
+ GtkSourceFileLoader *loader;
+ GTimer *timer;
+ gint line_pos;
+ gint column_pos;
+ guint user_requested_encoding : 1;
+};
+
+G_DEFINE_TYPE (GeditTab, gedit_tab, GTK_TYPE_BOX)
+
+enum
+{
+ PROP_0,
+ PROP_NAME,
+ PROP_STATE,
+ PROP_AUTO_SAVE,
+ PROP_AUTO_SAVE_INTERVAL,
+ PROP_CAN_CLOSE,
+ LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP];
+
+enum
+{
+ DROP_URIS,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static gboolean gedit_tab_auto_save (GeditTab *tab);
+
+static void launch_loader (GTask *loading_task,
+ const GtkSourceEncoding *encoding);
+
+static void launch_saver (GTask *saving_task);
+
+static SaverData *
+saver_data_new (void)
+{
+ return g_slice_new0 (SaverData);
+}
+
+static void
+saver_data_free (SaverData *data)
+{
+ if (data != NULL)
+ {
+ if (data->saver != NULL)
+ {
+ g_object_unref (data->saver);
+ }
+
+ if (data->timer != NULL)
+ {
+ g_timer_destroy (data->timer);
+ }
+
+ g_slice_free (SaverData, data);
+ }
+}
+
+static LoaderData *
+loader_data_new (void)
+{
+ return g_slice_new0 (LoaderData);
+}
+
+static void
+loader_data_free (LoaderData *data)
+{
+ if (data != NULL)
+ {
+ if (data->loader != NULL)
+ {
+ g_object_unref (data->loader);
+ }
+
+ if (data->timer != NULL)
+ {
+ g_timer_destroy (data->timer);
+ }
+
+ g_slice_free (LoaderData, data);
+ }
+}
+
+static void
+set_editable (GeditTab *tab,
+ gboolean editable)
+{
+ GeditView *view;
+ gboolean val;
+
+ tab->editable = editable != FALSE;
+
+ view = gedit_tab_get_view (tab);
+
+ val = (tab->state == GEDIT_TAB_STATE_NORMAL &&
+ tab->editable);
+
+ gtk_text_view_set_editable (GTK_TEXT_VIEW (view), val);
+}
+
+static void
+install_auto_save_timeout (GeditTab *tab)
+{
+ if (tab->auto_save_timeout == 0)
+ {
+ g_return_if_fail (tab->auto_save_interval > 0);
+
+ tab->auto_save_timeout = g_timeout_add_seconds (tab->auto_save_interval * 60,
+ (GSourceFunc) gedit_tab_auto_save,
+ tab);
+ }
+}
+
+static void
+remove_auto_save_timeout (GeditTab *tab)
+{
+ gedit_debug (DEBUG_TAB);
+
+ if (tab->auto_save_timeout > 0)
+ {
+ g_source_remove (tab->auto_save_timeout);
+ tab->auto_save_timeout = 0;
+ }
+}
+
+static void
+update_auto_save_timeout (GeditTab *tab)
+{
+ GeditDocument *doc;
+ GtkSourceFile *file;
+
+ gedit_debug (DEBUG_TAB);
+
+ doc = gedit_tab_get_document (tab);
+ file = gedit_document_get_file (doc);
+
+ if (tab->state == GEDIT_TAB_STATE_NORMAL &&
+ tab->auto_save &&
+ !_gedit_document_is_untitled (doc) &&
+ !gtk_source_file_is_readonly (file))
+ {
+ install_auto_save_timeout (tab);
+ }
+ else
+ {
+ remove_auto_save_timeout (tab);
+ }
+}
+
+static void
+gedit_tab_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditTab *tab = GEDIT_TAB (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ g_value_take_string (value, _gedit_tab_get_name (tab));
+ break;
+
+ case PROP_STATE:
+ g_value_set_enum (value, gedit_tab_get_state (tab));
+ break;
+
+ case PROP_AUTO_SAVE:
+ g_value_set_boolean (value, gedit_tab_get_auto_save_enabled (tab));
+ break;
+
+ case PROP_AUTO_SAVE_INTERVAL:
+ g_value_set_int (value, gedit_tab_get_auto_save_interval (tab));
+ break;
+
+ case PROP_CAN_CLOSE:
+ g_value_set_boolean (value, _gedit_tab_get_can_close (tab));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_tab_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditTab *tab = GEDIT_TAB (object);
+
+ switch (prop_id)
+ {
+ case PROP_AUTO_SAVE:
+ gedit_tab_set_auto_save_enabled (tab, g_value_get_boolean (value));
+ break;
+
+ case PROP_AUTO_SAVE_INTERVAL:
+ gedit_tab_set_auto_save_interval (tab, g_value_get_int (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_tab_dispose (GObject *object)
+{
+ GeditTab *tab = GEDIT_TAB (object);
+
+ g_clear_object (&tab->editor_settings);
+ g_clear_object (&tab->print_job);
+ g_clear_object (&tab->print_preview);
+
+ remove_auto_save_timeout (tab);
+
+ if (tab->scroll_timeout != 0)
+ {
+ g_source_remove (tab->scroll_timeout);
+ tab->scroll_timeout = 0;
+ }
+
+ if (tab->scroll_idle != 0)
+ {
+ g_source_remove (tab->scroll_idle);
+ tab->scroll_idle = 0;
+ }
+
+ if (tab->cancellable != NULL)
+ {
+ g_cancellable_cancel (tab->cancellable);
+ g_clear_object (&tab->cancellable);
+ }
+
+ G_OBJECT_CLASS (gedit_tab_parent_class)->dispose (object);
+}
+
+static void
+gedit_tab_grab_focus (GtkWidget *widget)
+{
+ GeditTab *tab = GEDIT_TAB (widget);
+
+ GTK_WIDGET_CLASS (gedit_tab_parent_class)->grab_focus (widget);
+
+ if (tab->info_bar != NULL)
+ {
+ gtk_widget_grab_focus (tab->info_bar);
+ }
+ else
+ {
+ GeditView *view = gedit_tab_get_view (tab);
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+ }
+}
+
+static void
+gedit_tab_drop_uris (GeditTab *tab,
+ gchar **uri_list)
+{
+ gedit_debug (DEBUG_TAB);
+}
+
+static void
+gedit_tab_class_init (GeditTabClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = gedit_tab_dispose;
+ object_class->get_property = gedit_tab_get_property;
+ object_class->set_property = gedit_tab_set_property;
+
+ gtkwidget_class->grab_focus = gedit_tab_grab_focus;
+
+ properties[PROP_NAME] =
+ g_param_spec_string ("name",
+ "Name",
+ "The tab's name",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_STATE] =
+ g_param_spec_enum ("state",
+ "State",
+ "The tab's state",
+ GEDIT_TYPE_TAB_STATE,
+ GEDIT_TAB_STATE_NORMAL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_AUTO_SAVE] =
+ g_param_spec_boolean ("autosave",
+ "Autosave",
+ "Autosave feature",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_AUTO_SAVE_INTERVAL] =
+ g_param_spec_int ("autosave-interval",
+ "AutosaveInterval",
+ "Time between two autosaves",
+ 0,
+ G_MAXINT,
+ 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_CAN_CLOSE] =
+ g_param_spec_boolean ("can-close",
+ "Can close",
+ "Whether the tab can be closed",
+ TRUE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ signals[DROP_URIS] =
+ g_signal_new_class_handler ("drop-uris",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_CALLBACK (gedit_tab_drop_uris),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_STRV);
+}
+
+/**
+ * gedit_tab_get_state:
+ * @tab: a #GeditTab
+ *
+ * Gets the #GeditTabState of @tab.
+ *
+ * Returns: the #GeditTabState of @tab
+ */
+GeditTabState
+gedit_tab_get_state (GeditTab *tab)
+{
+ g_return_val_if_fail (GEDIT_IS_TAB (tab), GEDIT_TAB_STATE_NORMAL);
+
+ return tab->state;
+}
+
+static void
+set_cursor_according_to_state (GtkTextView *view,
+ GeditTabState state)
+{
+ GdkDisplay *display;
+ GdkCursor *cursor;
+ GdkWindow *text_window;
+ GdkWindow *left_window;
+
+ display = gtk_widget_get_display (GTK_WIDGET (view));
+
+ text_window = gtk_text_view_get_window (view, GTK_TEXT_WINDOW_TEXT);
+ left_window = gtk_text_view_get_window (view, GTK_TEXT_WINDOW_LEFT);
+
+ if ((state == GEDIT_TAB_STATE_LOADING) ||
+ (state == GEDIT_TAB_STATE_REVERTING) ||
+ (state == GEDIT_TAB_STATE_SAVING) ||
+ (state == GEDIT_TAB_STATE_PRINTING) ||
+ (state == GEDIT_TAB_STATE_CLOSING))
+ {
+ cursor = gdk_cursor_new_from_name (display, "progress");
+
+ if (text_window != NULL)
+ gdk_window_set_cursor (text_window, cursor);
+ if (left_window != NULL)
+ gdk_window_set_cursor (left_window, cursor);
+
+ g_clear_object (&cursor);
+ }
+ else
+ {
+ cursor = gdk_cursor_new_from_name (display, "text");
+
+ if (text_window != NULL)
+ gdk_window_set_cursor (text_window, cursor);
+ if (left_window != NULL)
+ gdk_window_set_cursor (left_window, NULL);
+
+ g_clear_object (&cursor);
+ }
+}
+
+static void
+view_realized (GtkTextView *view,
+ GeditTab *tab)
+{
+ set_cursor_according_to_state (view, tab->state);
+}
+
+static void
+set_view_properties_according_to_state (GeditTab *tab,
+ GeditTabState state)
+{
+ GeditView *view;
+ gboolean val;
+ gboolean hl_current_line;
+
+ hl_current_line = g_settings_get_boolean (tab->editor_settings,
+ GEDIT_SETTINGS_HIGHLIGHT_CURRENT_LINE);
+
+ view = gedit_tab_get_view (tab);
+
+ val = ((state == GEDIT_TAB_STATE_NORMAL) &&
+ tab->editable);
+ gtk_text_view_set_editable (GTK_TEXT_VIEW (view), val);
+
+ val = ((state != GEDIT_TAB_STATE_LOADING) &&
+ (state != GEDIT_TAB_STATE_CLOSING));
+ gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view), val);
+
+ val = ((state != GEDIT_TAB_STATE_LOADING) &&
+ (state != GEDIT_TAB_STATE_CLOSING) &&
+ (hl_current_line));
+ gtk_source_view_set_highlight_current_line (GTK_SOURCE_VIEW (view), val);
+}
+
+static void
+gedit_tab_set_state (GeditTab *tab,
+ GeditTabState state)
+{
+ g_return_if_fail ((state >= 0) && (state < GEDIT_TAB_NUM_OF_STATES));
+
+ if (tab->state == state)
+ {
+ return;
+ }
+
+ tab->state = state;
+
+ set_view_properties_according_to_state (tab, state);
+
+ /* Hide or show the document.
+ * For GEDIT_TAB_STATE_LOADING_ERROR, tab->frame is either shown or
+ * hidden, depending on the error.
+ */
+ if (state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW)
+ {
+ gtk_widget_hide (GTK_WIDGET (tab->frame));
+ }
+ else if (state != GEDIT_TAB_STATE_LOADING_ERROR)
+ {
+ gtk_widget_show (GTK_WIDGET (tab->frame));
+ }
+
+ set_cursor_according_to_state (GTK_TEXT_VIEW (gedit_tab_get_view (tab)),
+ state);
+
+ update_auto_save_timeout (tab);
+
+ g_object_notify_by_pspec (G_OBJECT (tab), properties[PROP_STATE]);
+ g_object_notify_by_pspec (G_OBJECT (tab), properties[PROP_CAN_CLOSE]);
+}
+
+static void
+document_location_notify_handler (GtkSourceFile *file,
+ GParamSpec *pspec,
+ GeditTab *tab)
+{
+ gedit_debug (DEBUG_TAB);
+
+ /* Notify the change in the location */
+ g_object_notify_by_pspec (G_OBJECT (tab), properties[PROP_NAME]);
+}
+
+static void
+document_shortname_notify_handler (TeplFile *file,
+ GParamSpec *pspec,
+ GeditTab *tab)
+{
+ gedit_debug (DEBUG_TAB);
+
+ /* Notify the change in the shortname */
+ g_object_notify_by_pspec (G_OBJECT (tab), properties[PROP_NAME]);
+}
+
+static void
+document_modified_changed (GtkTextBuffer *document,
+ GeditTab *tab)
+{
+ g_object_notify_by_pspec (G_OBJECT (tab), properties[PROP_NAME]);
+ g_object_notify_by_pspec (G_OBJECT (tab), properties[PROP_CAN_CLOSE]);
+}
+
+/* This function must be used carefully, and should be replaced by
+ * tepl_tab_add_info_bar() (note the *add*, not *set*).
+ * When certain infobars are set, it also configures GeditTab to be in a certain
+ * state (e.g. non-editable) and it waits a response from the infobar to restore
+ * the GeditTab state. If another infobar is set in the meantime, there will be
+ * a bug.
+ */
+static void
+set_info_bar (GeditTab *tab,
+ GtkWidget *info_bar)
+{
+ if (tab->info_bar == info_bar)
+ {
+ return;
+ }
+
+ if (tab->info_bar != NULL)
+ {
+ gtk_widget_destroy (tab->info_bar);
+ tab->info_bar = NULL;
+ }
+
+ tab->info_bar = info_bar;
+
+ if (info_bar != NULL)
+ {
+ gtk_box_pack_start (GTK_BOX (tab), info_bar, FALSE, FALSE, 0);
+ gtk_widget_show (info_bar);
+ }
+}
+
+static void
+remove_tab (GeditTab *tab)
+{
+ GtkWidget *notebook;
+
+ notebook = gtk_widget_get_parent (GTK_WIDGET (tab));
+ gtk_container_remove (GTK_CONTAINER (notebook), GTK_WIDGET (tab));
+}
+
+static void
+io_loading_error_info_bar_response (GtkWidget *info_bar,
+ gint response_id,
+ GTask *loading_task)
+{
+ LoaderData *data = g_task_get_task_data (loading_task);
+ GFile *location;
+ const GtkSourceEncoding *encoding;
+
+ location = gtk_source_file_loader_get_location (data->loader);
+
+ switch (response_id)
+ {
+ case GTK_RESPONSE_OK:
+ encoding = gedit_conversion_error_info_bar_get_encoding (GTK_WIDGET (info_bar));
+
+ set_info_bar (data->tab, NULL);
+ gedit_tab_set_state (data->tab, GEDIT_TAB_STATE_LOADING);
+
+ launch_loader (loading_task, encoding);
+ break;
+
+ case GTK_RESPONSE_YES:
+ /* This means that we want to edit the document anyway */
+ set_editable (data->tab, TRUE);
+ set_info_bar (data->tab, NULL);
+ gedit_tab_set_state (data->tab, GEDIT_TAB_STATE_NORMAL);
+
+ g_task_return_boolean (loading_task, TRUE);
+ g_object_unref (loading_task);
+ break;
+
+ default:
+ if (location != NULL)
+ {
+ gedit_recent_remove_if_local (location);
+ }
+
+ remove_tab (data->tab);
+
+ g_task_return_boolean (loading_task, FALSE);
+ g_object_unref (loading_task);
+ break;
+ }
+}
+
+static void
+file_already_open_warning_info_bar_response (GtkWidget *info_bar,
+ gint response_id,
+ GeditTab *tab)
+{
+ GeditView *view = gedit_tab_get_view (tab);
+
+ if (response_id == GTK_RESPONSE_YES)
+ {
+ set_editable (tab, TRUE);
+ }
+
+ set_info_bar (tab, NULL);
+
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+}
+
+static void
+load_cancelled (GtkWidget *bar,
+ gint response_id,
+ GTask *loading_task)
+{
+ LoaderData *data = g_task_get_task_data (loading_task);
+
+ g_return_if_fail (TEPL_IS_PROGRESS_INFO_BAR (data->tab->info_bar));
+
+ g_cancellable_cancel (g_task_get_cancellable (loading_task));
+ remove_tab (data->tab);
+}
+
+static void
+unrecoverable_reverting_error_info_bar_response (GtkWidget *info_bar,
+ gint response_id,
+ GTask *loading_task)
+{
+ LoaderData *data = g_task_get_task_data (loading_task);
+ GeditView *view;
+
+ gedit_tab_set_state (data->tab, GEDIT_TAB_STATE_NORMAL);
+
+ set_info_bar (data->tab, NULL);
+
+ view = gedit_tab_get_view (data->tab);
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+
+ g_task_return_boolean (loading_task, FALSE);
+ g_object_unref (loading_task);
+}
+
+#define MAX_MSG_LENGTH 100
+
+static void
+show_loading_info_bar (GTask *loading_task)
+{
+ LoaderData *data = g_task_get_task_data (loading_task);
+ TeplProgressInfoBar *bar;
+ GeditDocument *doc;
+ gchar *name;
+ gchar *dirname = NULL;
+ gchar *msg = NULL;
+ gchar *name_markup;
+ gchar *dirname_markup;
+ gint len;
+
+ if (data->tab->info_bar != NULL)
+ {
+ return;
+ }
+
+ gedit_debug (DEBUG_TAB);
+
+ doc = gedit_tab_get_document (data->tab);
+
+ 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_MSG_LENGTH)
+ {
+ gchar *str;
+
+ str = tepl_utils_str_middle_truncate (name, MAX_MSG_LENGTH);
+ g_free (name);
+ name = str;
+ }
+ else
+ {
+ GtkSourceFile *file = gedit_document_get_file (doc);
+ 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_MSG_LENGTH - len));
+ g_free (str);
+ }
+ }
+
+ name_markup = g_markup_printf_escaped ("<b>%s</b>", name);
+
+ if (data->tab->state == GEDIT_TAB_STATE_REVERTING)
+ {
+ if (dirname != NULL)
+ {
+ dirname_markup = g_markup_printf_escaped ("<b>%s</b>", dirname);
+
+ /* Translators: the first %s is a file name (e.g. test.txt) the second one
+ is a directory (e.g. ssh://master.gnome.org/home/users/paolo) */
+ msg = g_strdup_printf (_("Reverting %s from %s"),
+ name_markup,
+ dirname_markup);
+ g_free (dirname_markup);
+ }
+ else
+ {
+ msg = g_strdup_printf (_("Reverting %s"), name_markup);
+ }
+
+ bar = tepl_progress_info_bar_new ("document-revert", msg, TRUE);
+ }
+ else
+ {
+ if (dirname != NULL)
+ {
+ dirname_markup = g_markup_printf_escaped ("<b>%s</b>", dirname);
+
+ /* Translators: the first %s is a file name (e.g. test.txt) the second one
+ is a directory (e.g. ssh://master.gnome.org/home/users/paolo) */
+ msg = g_strdup_printf (_("Loading %s from %s"),
+ name_markup,
+ dirname_markup);
+ g_free (dirname_markup);
+ }
+ else
+ {
+ msg = g_strdup_printf (_("Loading %s"), name_markup);
+ }
+
+ bar = tepl_progress_info_bar_new ("document-open", msg, TRUE);
+ }
+
+ g_signal_connect_object (bar,
+ "response",
+ G_CALLBACK (load_cancelled),
+ loading_task,
+ 0);
+
+ set_info_bar (data->tab, GTK_WIDGET (bar));
+
+ g_free (msg);
+ g_free (name);
+ g_free (name_markup);
+ g_free (dirname);
+}
+
+static void
+show_saving_info_bar (GTask *saving_task)
+{
+ GeditTab *tab = g_task_get_source_object (saving_task);
+ TeplProgressInfoBar *bar;
+ GeditDocument *doc;
+ gchar *short_name;
+ gchar *from;
+ gchar *to = NULL;
+ gchar *from_markup;
+ gchar *to_markup;
+ gchar *msg = NULL;
+ gint len;
+
+ if (tab->info_bar != NULL)
+ {
+ return;
+ }
+
+ gedit_debug (DEBUG_TAB);
+
+ doc = gedit_tab_get_document (tab);
+ short_name = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc)));
+ len = g_utf8_strlen (short_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_MSG_LENGTH)
+ {
+ from = tepl_utils_str_middle_truncate (short_name, MAX_MSG_LENGTH);
+ g_free (short_name);
+ }
+ else
+ {
+ gchar *str;
+ SaverData *data;
+ GFile *location;
+
+ data = g_task_get_task_data (saving_task);
+ location = gtk_source_file_saver_get_location (data->saver);
+
+ from = short_name;
+ to = g_file_get_parse_name (location);
+ str = tepl_utils_str_middle_truncate (to, MAX (20, MAX_MSG_LENGTH - len));
+ g_free (to);
+
+ to = str;
+ }
+
+ from_markup = g_markup_printf_escaped ("<b>%s</b>", from);
+
+ if (to != NULL)
+ {
+ to_markup = g_markup_printf_escaped ("<b>%s</b>", to);
+
+ /* Translators: the first %s is a file name (e.g. test.txt) the second one
+ is a directory (e.g. ssh://master.gnome.org/home/users/paolo) */
+ msg = g_strdup_printf (_("Saving %s to %s"), from_markup, to_markup);
+ g_free (to_markup);
+ }
+ else
+ {
+ msg = g_strdup_printf (_("Saving %s"), from_markup);
+ }
+
+ bar = tepl_progress_info_bar_new ("document-save", msg, FALSE);
+
+ set_info_bar (tab, GTK_WIDGET (bar));
+
+ g_free (msg);
+ g_free (to);
+ g_free (from);
+ g_free (from_markup);
+}
+
+static void
+info_bar_set_progress (GeditTab *tab,
+ goffset size,
+ goffset total_size)
+{
+ TeplProgressInfoBar *progress_info_bar;
+
+ if (tab->info_bar == NULL)
+ {
+ return;
+ }
+
+ gedit_debug_message (DEBUG_TAB, "%" G_GOFFSET_FORMAT "/%" G_GOFFSET_FORMAT, size, total_size);
+
+ g_return_if_fail (TEPL_IS_PROGRESS_INFO_BAR (tab->info_bar));
+
+ progress_info_bar = TEPL_PROGRESS_INFO_BAR (tab->info_bar);
+
+ if (total_size != 0)
+ {
+ gdouble frac = (gdouble)size / (gdouble)total_size;
+
+ tepl_progress_info_bar_set_fraction (progress_info_bar, frac);
+ }
+ else if (size != 0)
+ {
+ tepl_progress_info_bar_pulse (progress_info_bar);
+ }
+ else
+ {
+ tepl_progress_info_bar_set_fraction (progress_info_bar, 0);
+ }
+}
+
+/* Returns whether progress info should be shown. */
+static gboolean
+should_show_progress_info (GTimer **timer,
+ goffset size,
+ goffset total_size)
+{
+ gdouble elapsed_time;
+ gdouble total_time;
+ gdouble remaining_time;
+
+ g_assert (timer != NULL);
+
+ if (*timer == NULL)
+ {
+ return TRUE;
+ }
+
+ elapsed_time = g_timer_elapsed (*timer, NULL);
+
+ /* Wait a little, because at the very beginning it's maybe not very
+ * accurate (it takes initially more time for the first bytes, the
+ * following chunks should arrive more quickly, as a rough guess).
+ */
+ if (elapsed_time < 0.5)
+ {
+ return FALSE;
+ }
+
+ /* elapsed_time / total_time = size / total_size */
+ total_time = (elapsed_time * total_size) / size;
+
+ remaining_time = total_time - elapsed_time;
+
+ /* Approximately more than 3 seconds remaining. */
+ if (remaining_time > 3.0)
+ {
+ /* Once the progress info bar is shown, it must remain
+ * shown until the end, so we don't need the timer
+ * anymore.
+ */
+ g_timer_destroy (*timer);
+ *timer = NULL;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+scroll_timeout_cb (GeditTab *tab)
+{
+ GeditView *view;
+
+ view = gedit_tab_get_view (tab);
+ tepl_view_scroll_to_cursor (TEPL_VIEW (view));
+
+ tab->scroll_timeout = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+scroll_idle_cb (GeditTab *tab)
+{
+ /* The idle is not enough, for a detailed analysis of this, see:
+ * https://wiki.gnome.org/Apps/Gedit/FixingTextCutOffBug
+ * or the commit message that changed this.
+ * (here it's a hack, a proper solution in GTK/GtkTextView should be
+ * found).
+ */
+ if (tab->scroll_timeout == 0)
+ {
+ /* Same number of ms as GtkSearchEntry::search-changed delay.
+ * Small enough to not be noticeable, but needs to be at least a
+ * few frames from the GdkFrameClock (during app startup).
+ */
+ tab->scroll_timeout = g_timeout_add (150, (GSourceFunc)scroll_timeout_cb, tab);
+ }
+
+ tab->scroll_idle = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static void
+unrecoverable_saving_error_info_bar_response (GtkWidget *info_bar,
+ gint response_id,
+ GTask *saving_task)
+{
+ GeditTab *tab = g_task_get_source_object (saving_task);
+ GeditView *view;
+
+ gedit_tab_set_state (tab, GEDIT_TAB_STATE_NORMAL);
+
+ set_info_bar (tab, NULL);
+
+ view = gedit_tab_get_view (tab);
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+
+ g_task_return_boolean (saving_task, FALSE);
+ g_object_unref (saving_task);
+}
+
+/* Sets the save flags after an info bar response. */
+static void
+response_set_save_flags (GTask *saving_task,
+ GtkSourceFileSaverFlags save_flags)
+{
+ GeditTab *tab = g_task_get_source_object (saving_task);
+ SaverData *data = g_task_get_task_data (saving_task);
+ gboolean create_backup;
+
+ create_backup = g_settings_get_boolean (tab->editor_settings,
+ GEDIT_SETTINGS_CREATE_BACKUP_COPY);
+
+ /* If we are here, it means that the user expressed his or her willing
+ * to save the file, by pressing a button in the info bar. So even if
+ * the file saving was initially an auto-save, we set the create_backup
+ * flag (if the conditions are met).
+ */
+ if (create_backup && !data->force_no_backup)
+ {
+ save_flags |= GTK_SOURCE_FILE_SAVER_FLAGS_CREATE_BACKUP;
+ }
+ else
+ {
+ save_flags &= ~GTK_SOURCE_FILE_SAVER_FLAGS_CREATE_BACKUP;
+ }
+
+ gtk_source_file_saver_set_flags (data->saver, save_flags);
+}
+
+static void
+invalid_character_info_bar_response (GtkWidget *info_bar,
+ gint response_id,
+ GTask *saving_task)
+{
+ if (response_id == GTK_RESPONSE_YES)
+ {
+ GeditTab *tab = g_task_get_source_object (saving_task);
+ SaverData *data = g_task_get_task_data (saving_task);
+ GtkSourceFileSaverFlags save_flags;
+
+ set_info_bar (tab, NULL);
+
+ /* Don't bug the user again with this... */
+ tab->save_flags |= GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_INVALID_CHARS;
+
+ save_flags = gtk_source_file_saver_get_flags (data->saver);
+ save_flags |= GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_INVALID_CHARS;
+ response_set_save_flags (saving_task, save_flags);
+
+ /* Force saving */
+ launch_saver (saving_task);
+ }
+ else
+ {
+ unrecoverable_saving_error_info_bar_response (info_bar, response_id, saving_task);
+ }
+}
+
+static void
+cant_create_backup_error_info_bar_response (GtkWidget *info_bar,
+ gint response_id,
+ GTask *saving_task)
+{
+ if (response_id == GTK_RESPONSE_YES)
+ {
+ GeditTab *tab = g_task_get_source_object (saving_task);
+ SaverData *data = g_task_get_task_data (saving_task);
+ GtkSourceFileSaverFlags save_flags;
+
+ set_info_bar (tab, NULL);
+
+ data->force_no_backup = TRUE;
+ save_flags = gtk_source_file_saver_get_flags (data->saver);
+ response_set_save_flags (saving_task, save_flags);
+
+ /* Force saving */
+ launch_saver (saving_task);
+ }
+ else
+ {
+ unrecoverable_saving_error_info_bar_response (info_bar, response_id, saving_task);
+ }
+}
+
+static void
+externally_modified_error_info_bar_response (GtkWidget *info_bar,
+ gint response_id,
+ GTask *saving_task)
+{
+ if (response_id == GTK_RESPONSE_YES)
+ {
+ GeditTab *tab = g_task_get_source_object (saving_task);
+ SaverData *data = g_task_get_task_data (saving_task);
+ GtkSourceFileSaverFlags save_flags;
+
+ set_info_bar (tab, NULL);
+
+ /* ignore_modification_time should not be persisted in save
+ * flags across saves (i.e. tab->save_flags is not modified).
+ */
+ save_flags = gtk_source_file_saver_get_flags (data->saver);
+ save_flags |= GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_MODIFICATION_TIME;
+ response_set_save_flags (saving_task, save_flags);
+
+ /* Force saving */
+ launch_saver (saving_task);
+ }
+ else
+ {
+ unrecoverable_saving_error_info_bar_response (info_bar, response_id, saving_task);
+ }
+}
+
+static void
+recoverable_saving_error_info_bar_response (GtkWidget *info_bar,
+ gint response_id,
+ GTask *saving_task)
+{
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ GeditTab *tab = g_task_get_source_object (saving_task);
+ SaverData *data = g_task_get_task_data (saving_task);
+ const GtkSourceEncoding *encoding;
+
+ set_info_bar (tab, NULL);
+
+ encoding = gedit_conversion_error_info_bar_get_encoding (GTK_WIDGET (info_bar));
+ g_return_if_fail (encoding != NULL);
+
+ gtk_source_file_saver_set_encoding (data->saver, encoding);
+ launch_saver (saving_task);
+ }
+ else
+ {
+ unrecoverable_saving_error_info_bar_response (info_bar, response_id, saving_task);
+ }
+}
+
+static void
+externally_modified_notification_info_bar_response (GtkWidget *info_bar,
+ gint response_id,
+ GeditTab *tab)
+{
+ GeditView *view;
+
+ set_info_bar (tab, NULL);
+
+ view = gedit_tab_get_view (tab);
+
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ _gedit_tab_revert (tab);
+ }
+ else
+ {
+ tab->ask_if_externally_modified = FALSE;
+
+ /* go back to normal state */
+ gedit_tab_set_state (tab, GEDIT_TAB_STATE_NORMAL);
+ }
+
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+}
+
+static void
+display_externally_modified_notification (GeditTab *tab)
+{
+ TeplInfoBar *info_bar;
+ GeditDocument *doc;
+ GtkSourceFile *file;
+ GFile *location;
+ gboolean document_modified;
+
+ doc = gedit_tab_get_document (tab);
+ file = gedit_document_get_file (doc);
+
+ /* we're here because the file we're editing changed on disk */
+ location = gtk_source_file_get_location (file);
+ g_return_if_fail (location != NULL);
+
+ document_modified = gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc));
+ info_bar = tepl_io_error_info_bar_externally_modified (location, document_modified);
+
+ set_info_bar (tab, GTK_WIDGET (info_bar));
+
+ g_signal_connect (info_bar,
+ "response",
+ G_CALLBACK (externally_modified_notification_info_bar_response),
+ tab);
+}
+
+static gboolean
+view_focused_in (GtkWidget *widget,
+ GdkEventFocus *event,
+ GeditTab *tab)
+{
+ GeditDocument *doc;
+ GtkSourceFile *file;
+
+ g_return_val_if_fail (GEDIT_IS_TAB (tab), GDK_EVENT_PROPAGATE);
+
+ /* we try to detect file changes only in the normal state */
+ if (tab->state != GEDIT_TAB_STATE_NORMAL)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ /* we already asked, don't bug the user again */
+ if (!tab->ask_if_externally_modified)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ doc = gedit_tab_get_document (tab);
+ file = gedit_document_get_file (doc);
+
+ /* If file was never saved or is remote we do not check */
+ if (gtk_source_file_is_local (file))
+ {
+ gtk_source_file_check_file_on_disk (file);
+
+ if (gtk_source_file_is_externally_modified (file))
+ {
+ gedit_tab_set_state (tab, GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION);
+
+ display_externally_modified_notification (tab);
+ }
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+on_drop_uris (GeditView *view,
+ gchar **uri_list,
+ GeditTab *tab)
+{
+ g_signal_emit (G_OBJECT (tab), signals[DROP_URIS], 0, uri_list);
+}
+
+static void
+gedit_tab_init (GeditTab *tab)
+{
+ gboolean auto_save;
+ gint auto_save_interval;
+ GeditDocument *doc;
+ GeditView *view;
+ GtkSourceFile *file;
+ TeplFile *tepl_file;
+
+ tab->state = GEDIT_TAB_STATE_NORMAL;
+
+ tab->editor_settings = g_settings_new ("org.gnome.gedit.preferences.editor");
+
+ tab->editable = TRUE;
+
+ tab->ask_if_externally_modified = TRUE;
+
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (tab),
+ GTK_ORIENTATION_VERTICAL);
+
+ /* Manage auto save data */
+ auto_save = g_settings_get_boolean (tab->editor_settings,
+ GEDIT_SETTINGS_AUTO_SAVE);
+ g_settings_get (tab->editor_settings, GEDIT_SETTINGS_AUTO_SAVE_INTERVAL,
+ "u", &auto_save_interval);
+ tab->auto_save = auto_save != FALSE;
+ tab->auto_save_interval = auto_save_interval;
+
+ /* Create the frame */
+ tab->frame = gedit_view_frame_new ();
+ gtk_widget_show (GTK_WIDGET (tab->frame));
+
+ gtk_box_pack_end (GTK_BOX (tab), GTK_WIDGET (tab->frame), TRUE, TRUE, 0);
+
+ doc = gedit_tab_get_document (tab);
+ g_object_set_data (G_OBJECT (doc), GEDIT_TAB_KEY, tab);
+
+ file = gedit_document_get_file (doc);
+ tepl_file = tepl_buffer_get_file (TEPL_BUFFER (doc));
+
+ g_signal_connect_object (file,
+ "notify::location",
+ G_CALLBACK (document_location_notify_handler),
+ tab,
+ 0);
+
+ g_signal_connect_object (tepl_file,
+ "notify::short-name",
+ G_CALLBACK (document_shortname_notify_handler),
+ tab,
+ 0);
+
+ g_signal_connect (doc,
+ "modified_changed",
+ G_CALLBACK (document_modified_changed),
+ tab);
+
+ view = gedit_tab_get_view (tab);
+
+ g_signal_connect_after (view,
+ "focus-in-event",
+ G_CALLBACK (view_focused_in),
+ tab);
+
+ g_signal_connect_after (view,
+ "realize",
+ G_CALLBACK (view_realized),
+ tab);
+
+ g_signal_connect (view,
+ "drop-uris",
+ G_CALLBACK (on_drop_uris),
+ tab);
+}
+
+GeditTab *
+_gedit_tab_new (void)
+{
+ return g_object_new (GEDIT_TYPE_TAB, NULL);
+}
+
+/**
+ * gedit_tab_get_view:
+ * @tab: a #GeditTab
+ *
+ * Gets the #GeditView inside @tab.
+ *
+ * Returns: (transfer none): the #GeditView inside @tab
+ */
+GeditView *
+gedit_tab_get_view (GeditTab *tab)
+{
+ g_return_val_if_fail (GEDIT_IS_TAB (tab), NULL);
+
+ return gedit_view_frame_get_view (tab->frame);
+}
+
+/**
+ * gedit_tab_get_document:
+ * @tab: a #GeditTab
+ *
+ * Gets the #GeditDocument associated to @tab.
+ *
+ * Returns: (transfer none): the #GeditDocument associated to @tab
+ */
+GeditDocument *
+gedit_tab_get_document (GeditTab *tab)
+{
+ GeditView *view;
+
+ g_return_val_if_fail (GEDIT_IS_TAB (tab), NULL);
+
+ view = gedit_view_frame_get_view (tab->frame);
+
+ return GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+}
+
+#define MAX_DOC_NAME_LENGTH 40
+
+gchar *
+_gedit_tab_get_name (GeditTab *tab)
+{
+ GeditDocument *doc;
+ gchar *name;
+ gchar *docname;
+ gchar *tab_name;
+
+ g_return_val_if_fail (GEDIT_IS_TAB (tab), NULL);
+
+ doc = gedit_tab_get_document (tab);
+ name = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc)));
+
+ /* Truncate the name so it doesn't get insanely wide. */
+ docname = tepl_utils_str_middle_truncate (name, MAX_DOC_NAME_LENGTH);
+
+ if (gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc)))
+ {
+ tab_name = g_strdup_printf ("*%s", docname);
+ }
+ else
+ {
+ tab_name = g_strdup (docname);
+ }
+
+ g_free (docname);
+ g_free (name);
+
+ return tab_name;
+}
+
+gchar *
+_gedit_tab_get_tooltip (GeditTab *tab)
+{
+ GeditDocument *doc;
+ gchar *full_name;
+ gchar *full_name_markup;
+ gchar *tip;
+
+ g_return_val_if_fail (GEDIT_IS_TAB (tab), NULL);
+
+ doc = gedit_tab_get_document (tab);
+ full_name = tepl_file_get_full_name (tepl_buffer_get_file (TEPL_BUFFER (doc)));
+ full_name_markup = g_markup_printf_escaped ("<i>%s</i>", full_name);
+
+ switch (tab->state)
+ {
+ gchar *content_type;
+ gchar *mime_type;
+ gchar *content_description;
+ gchar *content_full_description;
+ gchar *encoding;
+ GtkSourceFile *file;
+ const GtkSourceEncoding *enc;
+
+ case GEDIT_TAB_STATE_LOADING_ERROR:
+ tip = g_strdup_printf (_("Error opening file %s"), full_name_markup);
+ break;
+
+ case GEDIT_TAB_STATE_REVERTING_ERROR:
+ tip = g_strdup_printf (_("Error reverting file %s"), full_name_markup);
+ break;
+
+ case GEDIT_TAB_STATE_SAVING_ERROR:
+ tip = g_strdup_printf (_("Error saving file %s"), full_name_markup);
+ break;
+
+ default:
+ content_type = gedit_document_get_content_type (doc);
+ mime_type = gedit_document_get_mime_type (doc);
+ content_description = g_content_type_get_description (content_type);
+
+ if (content_description == NULL)
+ content_full_description = g_strdup (mime_type);
+ else
+ content_full_description = g_strdup_printf ("%s (%s)",
+ content_description, mime_type);
+
+ g_free (content_type);
+ g_free (mime_type);
+ g_free (content_description);
+
+ file = gedit_document_get_file (doc);
+ enc = gtk_source_file_get_encoding (file);
+
+ if (enc == NULL)
+ {
+ enc = gtk_source_encoding_get_utf8 ();
+ }
+
+ encoding = gtk_source_encoding_to_string (enc);
+
+ tip = g_markup_printf_escaped ("<b>%s</b> %s\n\n"
+ "<b>%s</b> %s\n"
+ "<b>%s</b> %s",
+ _("Name:"), full_name,
+ _("MIME Type:"), content_full_description,
+ _("Encoding:"), encoding);
+
+ g_free (encoding);
+ g_free (content_full_description);
+ break;
+ }
+
+ g_free (full_name);
+ g_free (full_name_markup);
+ return tip;
+}
+
+GdkPixbuf *
+_gedit_tab_get_icon (GeditTab *tab)
+{
+ const gchar *icon_name;
+ GdkPixbuf *pixbuf = NULL;
+
+ g_return_val_if_fail (GEDIT_IS_TAB (tab), NULL);
+
+ switch (tab->state)
+ {
+ case GEDIT_TAB_STATE_PRINTING:
+ icon_name = "printer-printing-symbolic";
+ break;
+
+ case GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW:
+ icon_name = "printer-symbolic";
+ 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:
+ icon_name = "dialog-error-symbolic";
+ break;
+
+ case GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION:
+ icon_name = "dialog-warning-symbolic";
+ break;
+
+ default:
+ icon_name = NULL;
+ }
+
+ if (icon_name != NULL)
+ {
+ GdkScreen *screen;
+ GtkIconTheme *theme;
+ gint icon_size;
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (tab));
+ theme = gtk_icon_theme_get_for_screen (screen);
+ g_return_val_if_fail (theme != NULL, NULL);
+
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, NULL, &icon_size);
+
+ pixbuf = gtk_icon_theme_load_icon (theme, icon_name, icon_size, 0, NULL);
+ }
+
+ return pixbuf;
+}
+
+/**
+ * gedit_tab_get_from_document:
+ * @doc: a #GeditDocument
+ *
+ * Gets the #GeditTab associated with @doc.
+ *
+ * Returns: (transfer none): the #GeditTab associated with @doc
+ */
+GeditTab *
+gedit_tab_get_from_document (GeditDocument *doc)
+{
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL);
+
+ return g_object_get_data (G_OBJECT (doc), GEDIT_TAB_KEY);
+}
+
+static void
+loader_progress_cb (goffset size,
+ goffset total_size,
+ GTask *loading_task)
+{
+ LoaderData *data = g_task_get_task_data (loading_task);
+
+ g_return_if_fail (data->tab->state == GEDIT_TAB_STATE_LOADING ||
+ data->tab->state == GEDIT_TAB_STATE_REVERTING);
+
+ if (should_show_progress_info (&data->timer, size, total_size))
+ {
+ show_loading_info_bar (loading_task);
+ info_bar_set_progress (data->tab, size, total_size);
+ }
+}
+
+static void
+goto_line (GTask *loading_task)
+{
+ LoaderData *data = g_task_get_task_data (loading_task);
+ GeditDocument *doc = gedit_tab_get_document (data->tab);
+ gboolean check_is_cursor_position = FALSE;
+ GtkTextIter iter;
+
+ /* To the top by default. */
+ gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (doc), &iter);
+
+ /* At the requested line/column if set. */
+ if (data->line_pos > 0)
+ {
+ gtk_text_buffer_get_iter_at_line_offset (GTK_TEXT_BUFFER (doc),
+ &iter,
+ data->line_pos - 1,
+ MAX (0, data->column_pos - 1));
+ check_is_cursor_position = TRUE;
+ }
+
+ /* From metadata. */
+ else if (g_settings_get_boolean (data->tab->editor_settings,
+ GEDIT_SETTINGS_RESTORE_CURSOR_POSITION))
+ {
+ gchar *position_str;
+ guint64 offset = 0;
+
+ position_str = gedit_document_get_metadata (doc, GEDIT_METADATA_ATTRIBUTE_POSITION);
+
+ if (position_str != NULL &&
+ g_ascii_string_to_unsigned (position_str,
+ 10,
+ 0,
+ G_MAXINT,
+ &offset,
+ NULL))
+ {
+ gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc),
+ &iter,
+ (gint) offset);
+ check_is_cursor_position = TRUE;
+ }
+
+ g_free (position_str);
+ }
+
+ /* Make sure it's a valid position, to not end up in the middle of a
+ * utf8 character cluster.
+ */
+ if (check_is_cursor_position &&
+ !gtk_text_iter_is_cursor_position (&iter))
+ {
+ gtk_text_iter_set_line_offset (&iter, 0);
+ }
+
+ gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (doc), &iter);
+
+ /* Scroll to the cursor when the document is loaded, we need to do it in
+ * an idle as after the document is loaded the textview is still
+ * redrawing and relocating its internals.
+ */
+ if (data->tab->scroll_idle == 0 &&
+ !gtk_text_iter_is_start (&iter))
+ {
+ data->tab->scroll_idle = g_idle_add ((GSourceFunc)scroll_idle_cb, data->tab);
+ }
+}
+
+static gboolean
+file_already_opened (GeditDocument *doc,
+ GFile *location)
+{
+ GList *all_documents;
+ GList *l;
+ gboolean already_opened = FALSE;
+
+ if (location == NULL)
+ {
+ return FALSE;
+ }
+
+ all_documents = gedit_app_get_documents (GEDIT_APP (g_application_get_default ()));
+
+ for (l = all_documents; l != NULL; l = l->next)
+ {
+ GeditDocument *cur_doc = l->data;
+ GtkSourceFile *cur_file;
+ GFile *cur_location;
+
+ if (cur_doc == doc)
+ {
+ continue;
+ }
+
+ cur_file = gedit_document_get_file (cur_doc);
+ cur_location = gtk_source_file_get_location (cur_file);
+
+ if (cur_location != NULL &&
+ g_file_equal (location, cur_location))
+ {
+ already_opened = TRUE;
+ break;
+ }
+ }
+
+ g_list_free (all_documents);
+
+ return already_opened;
+}
+
+static void
+successful_load (GTask *loading_task)
+{
+ LoaderData *data = g_task_get_task_data (loading_task);
+ GeditDocument *doc = gedit_tab_get_document (data->tab);
+ GtkSourceFile *file = gedit_document_get_file (doc);
+ GFile *location;
+
+ if (data->user_requested_encoding)
+ {
+ const GtkSourceEncoding *encoding = gtk_source_file_loader_get_encoding (data->loader);
+ const gchar *charset = gtk_source_encoding_get_charset (encoding);
+
+ gedit_document_set_metadata (doc,
+ GEDIT_METADATA_ATTRIBUTE_ENCODING, charset,
+ NULL);
+ }
+
+ goto_line (loading_task);
+
+ location = gtk_source_file_loader_get_location (data->loader);
+
+ /* If the document is readonly we don't care how many times the file
+ * is opened.
+ */
+ if (!gtk_source_file_is_readonly (file) &&
+ file_already_opened (doc, location))
+ {
+ TeplInfoBar *info_bar;
+
+ set_editable (data->tab, FALSE);
+
+ info_bar = tepl_io_error_info_bar_file_already_open (location);
+
+ g_signal_connect (info_bar,
+ "response",
+ G_CALLBACK (file_already_open_warning_info_bar_response),
+ data->tab);
+
+ set_info_bar (data->tab, GTK_WIDGET (info_bar));
+ }
+
+ /* When loading from stdin, the contents may not be saved, so set the
+ * buffer as modified.
+ */
+ if (location == NULL)
+ {
+ gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (doc), TRUE);
+ }
+
+ data->tab->ask_if_externally_modified = TRUE;
+
+ g_signal_emit_by_name (doc, "loaded");
+}
+
+static void
+load_cb (GtkSourceFileLoader *loader,
+ GAsyncResult *result,
+ GTask *loading_task)
+{
+ LoaderData *data = g_task_get_task_data (loading_task);
+ GeditDocument *doc;
+ GFile *location = gtk_source_file_loader_get_location (loader);
+ gboolean create_named_new_doc;
+ GError *error = NULL;
+
+ g_clear_pointer (&data->timer, g_timer_destroy);
+
+ gtk_source_file_loader_load_finish (loader, result, &error);
+
+ if (error != NULL)
+ {
+ gedit_debug_message (DEBUG_TAB, "File loading error: %s", error->message);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_task_return_boolean (loading_task, FALSE);
+ g_object_unref (loading_task);
+
+ g_error_free (error);
+ return;
+ }
+ }
+
+ doc = gedit_tab_get_document (data->tab);
+
+ g_return_if_fail (data->tab->state == GEDIT_TAB_STATE_LOADING ||
+ data->tab->state == GEDIT_TAB_STATE_REVERTING);
+
+ set_info_bar (data->tab, NULL);
+
+ /* Special case creating a named new doc. */
+ create_named_new_doc = (_gedit_document_get_create (doc) &&
+ g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) &&
+ g_file_has_uri_scheme (location, "file"));
+
+ if (create_named_new_doc)
+ {
+ g_error_free (error);
+ error = NULL;
+ }
+
+ if (g_error_matches (error,
+ GTK_SOURCE_FILE_LOADER_ERROR,
+ GTK_SOURCE_FILE_LOADER_ERROR_CONVERSION_FALLBACK))
+ {
+ GtkWidget *info_bar;
+ const GtkSourceEncoding *encoding;
+
+ /* Set the tab as not editable as we have an error, the user can
+ * decide to make it editable again.
+ */
+ set_editable (data->tab, FALSE);
+
+ encoding = gtk_source_file_loader_get_encoding (loader);
+
+ info_bar = gedit_io_loading_error_info_bar_new (location, encoding, error);
+
+ g_signal_connect (info_bar,
+ "response",
+ G_CALLBACK (io_loading_error_info_bar_response),
+ loading_task);
+
+ set_info_bar (data->tab, info_bar);
+
+ if (data->tab->state == GEDIT_TAB_STATE_LOADING)
+ {
+ gtk_widget_show (GTK_WIDGET (data->tab->frame));
+ gedit_tab_set_state (data->tab, GEDIT_TAB_STATE_LOADING_ERROR);
+ }
+ else
+ {
+ gedit_tab_set_state (data->tab, GEDIT_TAB_STATE_REVERTING_ERROR);
+ }
+
+ /* The loading was successful, despite some invalid characters. */
+ successful_load (loading_task);
+ gedit_recent_add_document (doc);
+
+ g_error_free (error);
+ return;
+ }
+
+ if (error != NULL)
+ {
+ GtkWidget *info_bar;
+
+ if (data->tab->state == GEDIT_TAB_STATE_LOADING)
+ {
+ gtk_widget_hide (GTK_WIDGET (data->tab->frame));
+ gedit_tab_set_state (data->tab, GEDIT_TAB_STATE_LOADING_ERROR);
+ }
+ else
+ {
+ gedit_tab_set_state (data->tab, GEDIT_TAB_STATE_REVERTING_ERROR);
+ }
+
+ if (location != NULL)
+ {
+ gedit_recent_remove_if_local (location);
+ }
+
+ if (data->tab->state == GEDIT_TAB_STATE_LOADING_ERROR)
+ {
+ const GtkSourceEncoding *encoding;
+
+ encoding = gtk_source_file_loader_get_encoding (loader);
+
+ info_bar = gedit_io_loading_error_info_bar_new (location, encoding, error);
+
+ g_signal_connect (info_bar,
+ "response",
+ G_CALLBACK (io_loading_error_info_bar_response),
+ loading_task);
+ }
+ else
+ {
+ g_return_if_fail (data->tab->state == GEDIT_TAB_STATE_REVERTING_ERROR);
+
+ info_bar = gedit_unrecoverable_reverting_error_info_bar_new (location, error);
+
+ g_signal_connect (info_bar,
+ "response",
+ G_CALLBACK (unrecoverable_reverting_error_info_bar_response),
+ loading_task);
+ }
+
+ set_info_bar (data->tab, info_bar);
+
+ g_error_free (error);
+ return;
+ }
+
+ g_assert (error == NULL);
+
+ gedit_tab_set_state (data->tab, GEDIT_TAB_STATE_NORMAL);
+ successful_load (loading_task);
+
+ if (!create_named_new_doc)
+ {
+ gedit_recent_add_document (doc);
+ }
+
+ g_task_return_boolean (loading_task, TRUE);
+ g_object_unref (loading_task);
+}
+
+/* The returned list may contain duplicated encodings. Only the first occurrence
+ * of a duplicated encoding should be kept, like it is done by
+ * gtk_source_file_loader_set_candidate_encodings().
+ */
+static GSList *
+get_candidate_encodings (GeditTab *tab)
+{
+ GSList *candidates = NULL;
+ GeditDocument *doc;
+ GtkSourceFile *file;
+ gchar *metadata_charset;
+ const GtkSourceEncoding *file_encoding;
+
+ candidates = gedit_settings_get_candidate_encodings (NULL);
+
+ /* Prepend the encoding stored in the metadata. */
+ doc = gedit_tab_get_document (tab);
+ metadata_charset = gedit_document_get_metadata (doc, GEDIT_METADATA_ATTRIBUTE_ENCODING);
+
+ if (metadata_charset != NULL)
+ {
+ const GtkSourceEncoding *metadata_enc;
+
+ metadata_enc = gtk_source_encoding_get_from_charset (metadata_charset);
+
+ if (metadata_enc != NULL)
+ {
+ candidates = g_slist_prepend (candidates, (gpointer)metadata_enc);
+ }
+ }
+
+ /* Finally prepend the GtkSourceFile's encoding, if previously set by a
+ * file loader or file saver.
+ */
+ file = gedit_document_get_file (doc);
+ file_encoding = gtk_source_file_get_encoding (file);
+
+ if (file_encoding != NULL)
+ {
+ candidates = g_slist_prepend (candidates, (gpointer)file_encoding);
+ }
+
+ g_free (metadata_charset);
+ return candidates;
+}
+
+static void
+launch_loader (GTask *loading_task,
+ const GtkSourceEncoding *encoding)
+{
+ LoaderData *data = g_task_get_task_data (loading_task);
+ GSList *candidate_encodings = NULL;
+ GeditDocument *doc;
+
+ if (encoding != NULL)
+ {
+ data->user_requested_encoding = TRUE;
+ candidate_encodings = g_slist_append (NULL, (gpointer) encoding);
+ }
+ else
+ {
+ data->user_requested_encoding = FALSE;
+ candidate_encodings = get_candidate_encodings (data->tab);
+ }
+
+ gtk_source_file_loader_set_candidate_encodings (data->loader, candidate_encodings);
+ g_slist_free (candidate_encodings);
+
+ doc = gedit_tab_get_document (data->tab);
+ g_signal_emit_by_name (doc, "load");
+
+ if (data->timer != NULL)
+ {
+ g_timer_destroy (data->timer);
+ }
+
+ data->timer = g_timer_new ();
+
+ gtk_source_file_loader_load_async (data->loader,
+ G_PRIORITY_DEFAULT,
+ g_task_get_cancellable (loading_task),
+ (GFileProgressCallback) loader_progress_cb,
+ loading_task,
+ NULL,
+ (GAsyncReadyCallback) load_cb,
+ loading_task);
+}
+
+static void
+load_async (GeditTab *tab,
+ GFile *location,
+ const GtkSourceEncoding *encoding,
+ gint line_pos,
+ gint column_pos,
+ gboolean create,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GeditDocument *doc;
+ GtkSourceFile *file;
+ GTask *loading_task;
+ LoaderData *data;
+
+ g_return_if_fail (GEDIT_IS_TAB (tab));
+ g_return_if_fail (G_IS_FILE (location));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (tab->state == GEDIT_TAB_STATE_NORMAL);
+
+ gedit_tab_set_state (tab, GEDIT_TAB_STATE_LOADING);
+
+ doc = gedit_tab_get_document (tab);
+ file = gedit_document_get_file (doc);
+ gtk_source_file_set_location (file, location);
+
+ loading_task = g_task_new (NULL, cancellable, callback, user_data);
+
+ data = loader_data_new ();
+ g_task_set_task_data (loading_task, data, (GDestroyNotify) loader_data_free);
+
+ data->tab = tab;
+ data->loader = gtk_source_file_loader_new (GTK_SOURCE_BUFFER (doc), file);
+ data->line_pos = line_pos;
+ data->column_pos = column_pos;
+
+ _gedit_document_set_create (doc, create);
+
+ launch_loader (loading_task, encoding);
+}
+
+static gboolean
+load_finish (GeditTab *tab,
+ GAsyncResult *result)
+{
+ g_return_val_if_fail (g_task_is_valid (result, tab), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), NULL);
+}
+
+void
+_gedit_tab_load (GeditTab *tab,
+ GFile *location,
+ const GtkSourceEncoding *encoding,
+ gint line_pos,
+ gint column_pos,
+ gboolean create)
+{
+ if (tab->cancellable != NULL)
+ {
+ g_cancellable_cancel (tab->cancellable);
+ g_object_unref (tab->cancellable);
+ }
+
+ tab->cancellable = g_cancellable_new ();
+
+ load_async (tab,
+ location,
+ encoding,
+ line_pos,
+ column_pos,
+ create,
+ tab->cancellable,
+ (GAsyncReadyCallback) load_finish,
+ NULL);
+}
+
+static void
+load_stream_async (GeditTab *tab,
+ GInputStream *stream,
+ const GtkSourceEncoding *encoding,
+ gint line_pos,
+ gint column_pos,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GeditDocument *doc;
+ GtkSourceFile *file;
+ GTask *loading_task;
+ LoaderData *data;
+
+ g_return_if_fail (GEDIT_IS_TAB (tab));
+ g_return_if_fail (G_IS_INPUT_STREAM (stream));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (tab->state == GEDIT_TAB_STATE_NORMAL);
+
+ gedit_tab_set_state (tab, GEDIT_TAB_STATE_LOADING);
+
+ doc = gedit_tab_get_document (tab);
+ file = gedit_document_get_file (doc);
+
+ gtk_source_file_set_location (file, NULL);
+
+ loading_task = g_task_new (NULL, cancellable, callback, user_data);
+
+ data = loader_data_new ();
+ g_task_set_task_data (loading_task, data, (GDestroyNotify) loader_data_free);
+
+ data->tab = tab;
+ data->loader = gtk_source_file_loader_new_from_stream (GTK_SOURCE_BUFFER (doc),
+ file,
+ stream);
+ data->line_pos = line_pos;
+ data->column_pos = column_pos;
+
+ _gedit_document_set_create (doc, FALSE);
+
+ launch_loader (loading_task, encoding);
+}
+
+void
+_gedit_tab_load_stream (GeditTab *tab,
+ GInputStream *stream,
+ const GtkSourceEncoding *encoding,
+ gint line_pos,
+ gint column_pos)
+{
+ if (tab->cancellable != NULL)
+ {
+ g_cancellable_cancel (tab->cancellable);
+ g_object_unref (tab->cancellable);
+ }
+
+ tab->cancellable = g_cancellable_new ();
+
+ load_stream_async (tab,
+ stream,
+ encoding,
+ line_pos,
+ column_pos,
+ tab->cancellable,
+ (GAsyncReadyCallback) load_finish,
+ NULL);
+}
+
+static void
+revert_async (GeditTab *tab,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GeditDocument *doc;
+ GtkSourceFile *file;
+ GFile *location;
+ GTask *loading_task;
+ LoaderData *data;
+
+ g_return_if_fail (GEDIT_IS_TAB (tab));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (tab->state == GEDIT_TAB_STATE_NORMAL ||
+ tab->state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION);
+
+ if (tab->state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)
+ {
+ set_info_bar (tab, NULL);
+ }
+
+ doc = gedit_tab_get_document (tab);
+ file = gedit_document_get_file (doc);
+ location = gtk_source_file_get_location (file);
+ g_return_if_fail (location != NULL);
+
+ gedit_tab_set_state (tab, GEDIT_TAB_STATE_REVERTING);
+
+ loading_task = g_task_new (NULL, cancellable, callback, user_data);
+
+ data = loader_data_new ();
+ g_task_set_task_data (loading_task, data, (GDestroyNotify) loader_data_free);
+
+ data->tab = tab;
+ data->loader = gtk_source_file_loader_new (GTK_SOURCE_BUFFER (doc), file);
+ data->line_pos = 0;
+ data->column_pos = 0;
+
+ launch_loader (loading_task, NULL);
+}
+
+void
+_gedit_tab_revert (GeditTab *tab)
+{
+ if (tab->cancellable != NULL)
+ {
+ g_cancellable_cancel (tab->cancellable);
+ g_object_unref (tab->cancellable);
+ }
+
+ tab->cancellable = g_cancellable_new ();
+
+ revert_async (tab,
+ tab->cancellable,
+ (GAsyncReadyCallback) load_finish,
+ NULL);
+}
+
+static void
+close_printing (GeditTab *tab)
+{
+ if (tab->print_preview != NULL)
+ {
+ gtk_widget_destroy (tab->print_preview);
+ }
+
+ g_clear_object (&tab->print_job);
+ g_clear_object (&tab->print_preview);
+
+ /* destroy the info bar */
+ set_info_bar (tab, NULL);
+
+ gedit_tab_set_state (tab, GEDIT_TAB_STATE_NORMAL);
+}
+
+static void
+saver_progress_cb (goffset size,
+ goffset total_size,
+ GTask *saving_task)
+{
+ GeditTab *tab = g_task_get_source_object (saving_task);
+ SaverData *data = g_task_get_task_data (saving_task);
+
+ g_return_if_fail (tab->state == GEDIT_TAB_STATE_SAVING);
+
+ if (should_show_progress_info (&data->timer, size, total_size))
+ {
+ show_saving_info_bar (saving_task);
+ info_bar_set_progress (tab, size, total_size);
+ }
+}
+
+static void
+save_cb (GtkSourceFileSaver *saver,
+ GAsyncResult *result,
+ GTask *saving_task)
+{
+ GeditTab *tab = g_task_get_source_object (saving_task);
+ SaverData *data = g_task_get_task_data (saving_task);
+ GeditDocument *doc = gedit_tab_get_document (tab);
+ GFile *location = gtk_source_file_saver_get_location (saver);
+ GError *error = NULL;
+
+ g_return_if_fail (tab->state == GEDIT_TAB_STATE_SAVING);
+
+ gtk_source_file_saver_save_finish (saver, result, &error);
+
+ if (error != NULL)
+ {
+ gedit_debug_message (DEBUG_TAB, "File saving error: %s", error->message);
+ }
+
+ if (data->timer != NULL)
+ {
+ g_timer_destroy (data->timer);
+ data->timer = NULL;
+ }
+
+ set_info_bar (tab, NULL);
+
+ if (error != NULL)
+ {
+ GtkWidget *info_bar;
+
+ gedit_tab_set_state (tab, GEDIT_TAB_STATE_SAVING_ERROR);
+
+ if (error->domain == GTK_SOURCE_FILE_SAVER_ERROR &&
+ error->code == GTK_SOURCE_FILE_SAVER_ERROR_EXTERNALLY_MODIFIED)
+ {
+ /* This error is recoverable */
+ info_bar = GTK_WIDGET (tepl_io_error_info_bar_saving_externally_modified (location));
+
+ g_signal_connect (info_bar,
+ "response",
+ G_CALLBACK (externally_modified_error_info_bar_response),
+ saving_task);
+ }
+ else if (error->domain == G_IO_ERROR &&
+ error->code == G_IO_ERROR_CANT_CREATE_BACKUP)
+ {
+ /* This error is recoverable */
+ info_bar = GTK_WIDGET (tepl_io_error_info_bar_cant_create_backup (location, error));
+ g_return_if_fail (info_bar != NULL);
+
+ g_signal_connect (info_bar,
+ "response",
+ G_CALLBACK (cant_create_backup_error_info_bar_response),
+ saving_task);
+ }
+ else if (error->domain == GTK_SOURCE_FILE_SAVER_ERROR &&
+ error->code == GTK_SOURCE_FILE_SAVER_ERROR_INVALID_CHARS)
+ {
+ /* If we have any invalid char in the document we must warn the user
+ * as it can make the document useless if it is saved.
+ */
+ info_bar = GTK_WIDGET (tepl_io_error_info_bar_invalid_characters (location));
+ g_return_if_fail (info_bar != NULL);
+
+ g_signal_connect (info_bar,
+ "response",
+ G_CALLBACK (invalid_character_info_bar_response),
+ saving_task);
+ }
+ else if (error->domain == GTK_SOURCE_FILE_SAVER_ERROR ||
+ (error->domain == G_IO_ERROR &&
+ error->code != G_IO_ERROR_INVALID_DATA &&
+ error->code != G_IO_ERROR_PARTIAL_INPUT))
+ {
+ /* These errors are _NOT_ recoverable */
+ gedit_recent_remove_if_local (location);
+
+ info_bar = gedit_unrecoverable_saving_error_info_bar_new (location, error);
+ g_return_if_fail (info_bar != NULL);
+
+ g_signal_connect (info_bar,
+ "response",
+ G_CALLBACK (unrecoverable_saving_error_info_bar_response),
+ saving_task);
+ }
+ else
+ {
+ const GtkSourceEncoding *encoding;
+
+ /* This error is recoverable */
+ g_return_if_fail (error->domain == G_CONVERT_ERROR ||
+ error->domain == G_IO_ERROR);
+
+ encoding = gtk_source_file_saver_get_encoding (saver);
+
+ info_bar = gedit_conversion_error_while_saving_info_bar_new (location, encoding);
+ g_return_if_fail (info_bar != NULL);
+
+ g_signal_connect (info_bar,
+ "response",
+ G_CALLBACK (recoverable_saving_error_info_bar_response),
+ saving_task);
+ }
+
+ set_info_bar (tab, info_bar);
+ }
+ else
+ {
+ gedit_recent_add_document (doc);
+
+ gedit_tab_set_state (tab, GEDIT_TAB_STATE_NORMAL);
+
+ tab->ask_if_externally_modified = TRUE;
+
+ g_signal_emit_by_name (doc, "saved");
+ g_task_return_boolean (saving_task, TRUE);
+ g_object_unref (saving_task);
+ }
+
+ if (error != NULL)
+ {
+ g_error_free (error);
+ }
+}
+
+static void
+launch_saver (GTask *saving_task)
+{
+ GeditTab *tab = g_task_get_source_object (saving_task);
+ GeditDocument *doc = gedit_tab_get_document (tab);
+ SaverData *data = g_task_get_task_data (saving_task);
+
+ gedit_tab_set_state (tab, GEDIT_TAB_STATE_SAVING);
+
+ g_signal_emit_by_name (doc, "save");
+
+ if (data->timer != NULL)
+ {
+ g_timer_destroy (data->timer);
+ }
+
+ data->timer = g_timer_new ();
+
+ gtk_source_file_saver_save_async (data->saver,
+ G_PRIORITY_DEFAULT,
+ g_task_get_cancellable (saving_task),
+ (GFileProgressCallback) saver_progress_cb,
+ saving_task,
+ NULL,
+ (GAsyncReadyCallback) save_cb,
+ saving_task);
+}
+
+/* Gets the initial save flags, when launching a new FileSaver. */
+static GtkSourceFileSaverFlags
+get_initial_save_flags (GeditTab *tab,
+ gboolean auto_save)
+{
+ GtkSourceFileSaverFlags save_flags;
+ gboolean create_backup;
+
+ save_flags = tab->save_flags;
+
+ create_backup = g_settings_get_boolean (tab->editor_settings,
+ GEDIT_SETTINGS_CREATE_BACKUP_COPY);
+
+ /* In case of autosaving, we need to preserve the backup that was produced
+ * the last time the user "manually" saved the file. So we don't set the
+ * CREATE_BACKUP flag for an automatic file saving.
+ */
+ if (create_backup && !auto_save)
+ {
+ save_flags |= GTK_SOURCE_FILE_SAVER_FLAGS_CREATE_BACKUP;
+ }
+
+ return save_flags;
+}
+
+void
+_gedit_tab_save_async (GeditTab *tab,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *saving_task;
+ SaverData *data;
+ GeditDocument *doc;
+ GtkSourceFile *file;
+ GtkSourceFileSaverFlags save_flags;
+
+ g_return_if_fail (GEDIT_IS_TAB (tab));
+ g_return_if_fail (tab->state == GEDIT_TAB_STATE_NORMAL ||
+ tab->state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION ||
+ tab->state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW);
+
+ /* The Save and Save As window actions are insensitive when the print
+ * preview is shown, but it's still possible to save several documents
+ * at once (with the Save All action or when quitting gedit). In that
+ * case, the print preview is simply closed. Handling correctly the
+ * document saving when the print preview is shown is more complicated
+ * and error-prone, it doesn't worth the effort. (the print preview
+ * would need to be updated when the filename changes, dealing with file
+ * saving errors is also more complicated, etc).
+ */
+ if (tab->state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW)
+ {
+ close_printing (tab);
+ }
+
+ doc = gedit_tab_get_document (tab);
+ g_return_if_fail (!_gedit_document_is_untitled (doc));
+
+ saving_task = g_task_new (tab, cancellable, callback, user_data);
+
+ data = saver_data_new ();
+ g_task_set_task_data (saving_task, data, (GDestroyNotify) saver_data_free);
+
+ save_flags = get_initial_save_flags (tab, FALSE);
+
+ if (tab->state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)
+ {
+ /* We already told the user about the external modification:
+ * hide the message bar and set the save flag.
+ */
+ set_info_bar (tab, NULL);
+ save_flags |= GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_MODIFICATION_TIME;
+ }
+
+ file = gedit_document_get_file (doc);
+
+ data->saver = gtk_source_file_saver_new (GTK_SOURCE_BUFFER (doc), file);
+
+ gtk_source_file_saver_set_flags (data->saver, save_flags);
+
+ launch_saver (saving_task);
+}
+
+gboolean
+_gedit_tab_save_finish (GeditTab *tab,
+ GAsyncResult *result)
+{
+ g_return_val_if_fail (g_task_is_valid (result, tab), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), NULL);
+}
+
+static void
+auto_save_finished_cb (GeditTab *tab,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ _gedit_tab_save_finish (tab, result);
+}
+
+static gboolean
+gedit_tab_auto_save (GeditTab *tab)
+{
+ GTask *saving_task;
+ SaverData *data;
+ GeditDocument *doc;
+ GtkSourceFile *file;
+ GtkSourceFileSaverFlags save_flags;
+
+ gedit_debug (DEBUG_TAB);
+
+ doc = gedit_tab_get_document (tab);
+ file = gedit_document_get_file (doc);
+
+ g_return_val_if_fail (!_gedit_document_is_untitled (doc), G_SOURCE_REMOVE);
+ g_return_val_if_fail (!gtk_source_file_is_readonly (file), G_SOURCE_REMOVE);
+
+ if (!gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc)))
+ {
+ gedit_debug_message (DEBUG_TAB, "Document not modified");
+
+ return G_SOURCE_CONTINUE;
+ }
+
+ if (tab->state != GEDIT_TAB_STATE_NORMAL)
+ {
+ gedit_debug_message (DEBUG_TAB, "Retry after 30 seconds");
+
+ tab->auto_save_timeout = g_timeout_add_seconds (30,
+ (GSourceFunc) gedit_tab_auto_save,
+ tab);
+
+ /* Destroy the old timeout. */
+ return G_SOURCE_REMOVE;
+ }
+
+ /* Set auto_save_timeout to 0 since the timeout is going to be destroyed */
+ tab->auto_save_timeout = 0;
+
+ saving_task = g_task_new (tab,
+ NULL,
+ (GAsyncReadyCallback) auto_save_finished_cb,
+ NULL);
+
+ data = saver_data_new ();
+ g_task_set_task_data (saving_task, data, (GDestroyNotify) saver_data_free);
+
+ data->saver = gtk_source_file_saver_new (GTK_SOURCE_BUFFER (doc), file);
+
+ save_flags = get_initial_save_flags (tab, TRUE);
+ gtk_source_file_saver_set_flags (data->saver, save_flags);
+
+ launch_saver (saving_task);
+
+ return G_SOURCE_REMOVE;
+}
+
+/* Call _gedit_tab_save_finish() in @callback, there is no
+ * _gedit_tab_save_as_finish().
+ */
+void
+_gedit_tab_save_as_async (GeditTab *tab,
+ GFile *location,
+ const GtkSourceEncoding *encoding,
+ GtkSourceNewlineType newline_type,
+ GtkSourceCompressionType compression_type,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *saving_task;
+ SaverData *data;
+ GeditDocument *doc;
+ GtkSourceFile *file;
+ GtkSourceFileSaverFlags save_flags;
+
+ g_return_if_fail (GEDIT_IS_TAB (tab));
+ g_return_if_fail (tab->state == GEDIT_TAB_STATE_NORMAL ||
+ tab->state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION ||
+ tab->state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW);
+ g_return_if_fail (G_IS_FILE (location));
+ g_return_if_fail (encoding != NULL);
+
+ /* See note at _gedit_tab_save_async(). */
+ if (tab->state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW)
+ {
+ close_printing (tab);
+ }
+
+ saving_task = g_task_new (tab, cancellable, callback, user_data);
+
+ data = saver_data_new ();
+ g_task_set_task_data (saving_task, data, (GDestroyNotify) saver_data_free);
+
+ doc = gedit_tab_get_document (tab);
+
+ /* reset the save flags, when saving as */
+ tab->save_flags = GTK_SOURCE_FILE_SAVER_FLAGS_NONE;
+
+ save_flags = get_initial_save_flags (tab, FALSE);
+
+ if (tab->state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)
+ {
+ /* We already told the user about the external modification:
+ * hide the message bar and set the save flag.
+ */
+ set_info_bar (tab, NULL);
+ save_flags |= GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_MODIFICATION_TIME;
+ }
+
+ file = gedit_document_get_file (doc);
+
+ data->saver = gtk_source_file_saver_new_with_target (GTK_SOURCE_BUFFER (doc),
+ file,
+ location);
+
+ gtk_source_file_saver_set_encoding (data->saver, encoding);
+ gtk_source_file_saver_set_newline_type (data->saver, newline_type);
+ gtk_source_file_saver_set_compression_type (data->saver, compression_type);
+ gtk_source_file_saver_set_flags (data->saver, save_flags);
+
+ launch_saver (saving_task);
+}
+
+#define GEDIT_PAGE_SETUP_KEY "gedit-page-setup-key"
+#define GEDIT_PRINT_SETTINGS_KEY "gedit-print-settings-key"
+
+static GtkPageSetup *
+get_page_setup (GeditTab *tab)
+{
+ gpointer data;
+ GeditDocument *doc;
+
+ doc = gedit_tab_get_document (tab);
+
+ data = g_object_get_data (G_OBJECT (doc),
+ GEDIT_PAGE_SETUP_KEY);
+
+ if (data == NULL)
+ {
+ return _gedit_app_get_default_page_setup (GEDIT_APP (g_application_get_default ()));
+ }
+ else
+ {
+ return gtk_page_setup_copy (GTK_PAGE_SETUP (data));
+ }
+}
+
+static GtkPrintSettings *
+get_print_settings (GeditTab *tab)
+{
+ gpointer data;
+ GeditDocument *doc;
+ GtkPrintSettings *settings;
+ gchar *name;
+
+ doc = gedit_tab_get_document (tab);
+
+ data = g_object_get_data (G_OBJECT (doc),
+ GEDIT_PRINT_SETTINGS_KEY);
+
+ if (data == NULL)
+ {
+ settings = _gedit_app_get_default_print_settings (GEDIT_APP (g_application_get_default ()));
+ }
+ else
+ {
+ settings = gtk_print_settings_copy (GTK_PRINT_SETTINGS (data));
+ }
+
+ /* Be sure the OUTPUT_URI is unset, because otherwise the
+ * OUTPUT_BASENAME is not taken into account.
+ */
+ gtk_print_settings_set (settings, GTK_PRINT_SETTINGS_OUTPUT_URI, NULL);
+
+ name = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc)));
+ gtk_print_settings_set (settings, GTK_PRINT_SETTINGS_OUTPUT_BASENAME, name);
+ g_free (name);
+
+ return settings;
+}
+
+/* FIXME: show the info bar only if the operation will be "long" */
+static void
+printing_cb (GeditPrintJob *job,
+ GeditPrintJobStatus status,
+ GeditTab *tab)
+{
+ g_return_if_fail (TEPL_IS_PROGRESS_INFO_BAR (tab->info_bar));
+
+ gtk_widget_show (tab->info_bar);
+
+ tepl_progress_info_bar_set_text (TEPL_PROGRESS_INFO_BAR (tab->info_bar),
+ gedit_print_job_get_status_string (job));
+
+ tepl_progress_info_bar_set_fraction (TEPL_PROGRESS_INFO_BAR (tab->info_bar),
+ gedit_print_job_get_progress (job));
+}
+
+static void
+store_print_settings (GeditTab *tab,
+ GeditPrintJob *job)
+{
+ GeditDocument *doc;
+ GtkPrintSettings *settings;
+ GtkPageSetup *page_setup;
+
+ doc = gedit_tab_get_document (tab);
+
+ settings = gedit_print_job_get_print_settings (job);
+
+ /* clear n-copies settings since we do not want to
+ * persist that one */
+ gtk_print_settings_unset (settings,
+ GTK_PRINT_SETTINGS_N_COPIES);
+
+ /* remember settings for this document */
+ g_object_set_data_full (G_OBJECT (doc),
+ GEDIT_PRINT_SETTINGS_KEY,
+ g_object_ref (settings),
+ (GDestroyNotify)g_object_unref);
+
+ /* make them the default */
+ _gedit_app_set_default_print_settings (GEDIT_APP (g_application_get_default ()),
+ settings);
+
+ page_setup = gedit_print_job_get_page_setup (job);
+
+ /* remember page setup for this document */
+ g_object_set_data_full (G_OBJECT (doc),
+ GEDIT_PAGE_SETUP_KEY,
+ g_object_ref (page_setup),
+ (GDestroyNotify)g_object_unref);
+
+ /* make it the default */
+ _gedit_app_set_default_page_setup (GEDIT_APP (g_application_get_default ()),
+ page_setup);
+}
+
+static void
+done_printing_cb (GeditPrintJob *job,
+ GeditPrintJobResult result,
+ GError *error,
+ GeditTab *tab)
+{
+ GeditView *view;
+
+ g_return_if_fail (tab->state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW ||
+ tab->state == GEDIT_TAB_STATE_PRINTING);
+
+ if (result == GEDIT_PRINT_JOB_RESULT_OK)
+ {
+ store_print_settings (tab, job);
+ }
+
+ /* TODO Show the error in an info bar. */
+ if (error != NULL)
+ {
+ g_warning ("Printing error: %s", error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+
+ close_printing (tab);
+
+ view = gedit_tab_get_view (tab);
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+}
+
+static void
+show_preview_cb (GeditPrintJob *job,
+ GeditPrintPreview *preview,
+ GeditTab *tab)
+{
+ g_return_if_fail (tab->print_preview == NULL);
+
+ /* destroy the info bar */
+ set_info_bar (tab, NULL);
+
+ tab->print_preview = GTK_WIDGET (preview);
+ g_object_ref_sink (tab->print_preview);
+
+ gtk_box_pack_end (GTK_BOX (tab), tab->print_preview, TRUE, TRUE, 0);
+
+ gtk_widget_show (tab->print_preview);
+ gtk_widget_grab_focus (tab->print_preview);
+
+ gedit_tab_set_state (tab, GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW);
+}
+
+static void
+print_cancelled (GtkWidget *bar,
+ gint response_id,
+ GeditTab *tab)
+{
+ gedit_debug (DEBUG_TAB);
+
+ if (tab->print_job != NULL)
+ {
+ gedit_print_job_cancel (tab->print_job);
+ }
+}
+
+static void
+add_printing_info_bar (GeditTab *tab)
+{
+ TeplProgressInfoBar *bar;
+
+ bar = tepl_progress_info_bar_new ("document-print", NULL, TRUE);
+
+ g_signal_connect (bar,
+ "response",
+ G_CALLBACK (print_cancelled),
+ tab);
+
+ set_info_bar (tab, GTK_WIDGET (bar));
+
+ /* hide until we start printing */
+ gtk_widget_hide (GTK_WIDGET (bar));
+}
+
+void
+_gedit_tab_print (GeditTab *tab)
+{
+ GeditView *view;
+ GtkPageSetup *setup;
+ GtkPrintSettings *settings;
+ GtkPrintOperationResult res;
+ GError *error = NULL;
+
+ g_return_if_fail (GEDIT_IS_TAB (tab));
+
+ /* FIXME: currently we can have just one printoperation going on at a
+ * given time, so before starting the print we close the preview.
+ * Would be nice to handle it properly though.
+ */
+ if (tab->state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW)
+ {
+ close_printing (tab);
+ }
+
+ g_return_if_fail (tab->print_job == NULL);
+ g_return_if_fail (tab->state == GEDIT_TAB_STATE_NORMAL);
+
+ view = gedit_tab_get_view (tab);
+
+ tab->print_job = gedit_print_job_new (TEPL_VIEW (view));
+
+ add_printing_info_bar (tab);
+
+ g_signal_connect_object (tab->print_job,
+ "printing",
+ G_CALLBACK (printing_cb),
+ tab,
+ 0);
+
+ g_signal_connect_object (tab->print_job,
+ "show-preview",
+ G_CALLBACK (show_preview_cb),
+ tab,
+ 0);
+
+ g_signal_connect_object (tab->print_job,
+ "done",
+ G_CALLBACK (done_printing_cb),
+ tab,
+ 0);
+
+ gedit_tab_set_state (tab, GEDIT_TAB_STATE_PRINTING);
+
+ setup = get_page_setup (tab);
+ settings = get_print_settings (tab);
+
+ res = gedit_print_job_print (tab->print_job,
+ GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
+ setup,
+ settings,
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))),
+ &error);
+
+ /* TODO: manage res in the correct way */
+ if (res == GTK_PRINT_OPERATION_RESULT_ERROR)
+ {
+ /* FIXME: go in error state */
+ g_warning ("Async print preview failed (%s)", error->message);
+ g_error_free (error);
+
+ close_printing (tab);
+ }
+
+ g_object_unref (setup);
+ g_object_unref (settings);
+}
+
+void
+_gedit_tab_mark_for_closing (GeditTab *tab)
+{
+ g_return_if_fail (GEDIT_IS_TAB (tab));
+ g_return_if_fail (tab->state == GEDIT_TAB_STATE_NORMAL);
+
+ gedit_tab_set_state (tab, GEDIT_TAB_STATE_CLOSING);
+}
+
+gboolean
+_gedit_tab_get_can_close (GeditTab *tab)
+{
+ GeditDocument *doc;
+
+ g_return_val_if_fail (GEDIT_IS_TAB (tab), FALSE);
+
+ /* if we are loading or reverting, the tab can be closed */
+ if (tab->state == GEDIT_TAB_STATE_LOADING ||
+ tab->state == GEDIT_TAB_STATE_LOADING_ERROR ||
+ tab->state == GEDIT_TAB_STATE_REVERTING ||
+ tab->state == GEDIT_TAB_STATE_REVERTING_ERROR) /* CHECK: I'm not sure this is the right behavior for REVERTING ERROR */
+ {
+ return TRUE;
+ }
+
+ /* Do not close tab with saving errors */
+ if (tab->state == GEDIT_TAB_STATE_SAVING_ERROR)
+ {
+ return FALSE;
+ }
+
+ doc = gedit_tab_get_document (tab);
+
+ if (_gedit_document_needs_saving (doc))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * gedit_tab_get_auto_save_enabled:
+ * @tab: a #GeditTab
+ *
+ * Gets the current state for the autosave feature
+ *
+ * Return value: %TRUE if the autosave is enabled, else %FALSE
+ **/
+gboolean
+gedit_tab_get_auto_save_enabled (GeditTab *tab)
+{
+ gedit_debug (DEBUG_TAB);
+
+ g_return_val_if_fail (GEDIT_IS_TAB (tab), FALSE);
+
+ return tab->auto_save;
+}
+
+/**
+ * gedit_tab_set_auto_save_enabled:
+ * @tab: a #GeditTab
+ * @enable: enable (%TRUE) or disable (%FALSE) auto save
+ *
+ * Enables or disables the autosave feature. It does not install an
+ * autosave timeout if the document is new or is read-only
+ **/
+void
+gedit_tab_set_auto_save_enabled (GeditTab *tab,
+ gboolean enable)
+{
+ gedit_debug (DEBUG_TAB);
+
+ g_return_if_fail (GEDIT_IS_TAB (tab));
+
+ enable = enable != FALSE;
+
+ if (tab->auto_save != enable)
+ {
+ tab->auto_save = enable;
+ update_auto_save_timeout (tab);
+ return;
+ }
+}
+
+/**
+ * gedit_tab_get_auto_save_interval:
+ * @tab: a #GeditTab
+ *
+ * Gets the current interval for the autosaves
+ *
+ * Return value: the value of the autosave
+ **/
+gint
+gedit_tab_get_auto_save_interval (GeditTab *tab)
+{
+ gedit_debug (DEBUG_TAB);
+
+ g_return_val_if_fail (GEDIT_IS_TAB (tab), 0);
+
+ return tab->auto_save_interval;
+}
+
+/**
+ * gedit_tab_set_auto_save_interval:
+ * @tab: a #GeditTab
+ * @interval: the new interval
+ *
+ * Sets the interval for the autosave feature.
+ */
+void
+gedit_tab_set_auto_save_interval (GeditTab *tab,
+ gint interval)
+{
+ g_return_if_fail (GEDIT_IS_TAB (tab));
+ g_return_if_fail (interval > 0);
+
+ gedit_debug (DEBUG_TAB);
+
+ if (tab->auto_save_interval != interval)
+ {
+ tab->auto_save_interval = interval;
+ remove_auto_save_timeout (tab);
+ update_auto_save_timeout (tab);
+ }
+}
+
+void
+gedit_tab_set_info_bar (GeditTab *tab,
+ GtkWidget *info_bar)
+{
+ g_return_if_fail (GEDIT_IS_TAB (tab));
+ g_return_if_fail (info_bar == NULL || GTK_IS_WIDGET (info_bar));
+
+ /* FIXME: this can cause problems with the tab state machine */
+ set_info_bar (tab, info_bar);
+}
+
+GeditViewFrame *
+_gedit_tab_get_view_frame (GeditTab *tab)
+{
+ return tab->frame;
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-tab.h b/gedit/gedit-tab.h
new file mode 100644
index 0000000..e3d67d7
--- /dev/null
+++ b/gedit/gedit-tab.h
@@ -0,0 +1,77 @@
+/*
+ * gedit-tab.h
+ * 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/>.
+ */
+
+#ifndef GEDIT_TAB_H
+#define GEDIT_TAB_H
+
+#include <gtksourceview/gtksource.h>
+#include <gedit/gedit-view.h>
+#include <gedit/gedit-document.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ GEDIT_TAB_STATE_NORMAL = 0,
+ GEDIT_TAB_STATE_LOADING,
+ GEDIT_TAB_STATE_REVERTING,
+ GEDIT_TAB_STATE_SAVING,
+ GEDIT_TAB_STATE_PRINTING,
+ GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW,
+ GEDIT_TAB_STATE_LOADING_ERROR,
+ GEDIT_TAB_STATE_REVERTING_ERROR,
+ GEDIT_TAB_STATE_SAVING_ERROR,
+ GEDIT_TAB_STATE_GENERIC_ERROR,
+ GEDIT_TAB_STATE_CLOSING,
+ GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION,
+ GEDIT_TAB_NUM_OF_STATES /* This is not a valid state */
+} GeditTabState;
+
+#define GEDIT_TYPE_TAB (gedit_tab_get_type())
+
+G_DECLARE_FINAL_TYPE (GeditTab, gedit_tab, GEDIT, TAB, GtkBox)
+
+GeditView *gedit_tab_get_view (GeditTab *tab);
+
+/* This is only an helper function */
+GeditDocument *gedit_tab_get_document (GeditTab *tab);
+
+GeditTab *gedit_tab_get_from_document (GeditDocument *doc);
+
+GeditTabState gedit_tab_get_state (GeditTab *tab);
+
+gboolean gedit_tab_get_auto_save_enabled (GeditTab *tab);
+
+void gedit_tab_set_auto_save_enabled (GeditTab *tab,
+ gboolean enable);
+
+gint gedit_tab_get_auto_save_interval (GeditTab *tab);
+
+void gedit_tab_set_auto_save_interval (GeditTab *tab,
+ gint interval);
+
+void gedit_tab_set_info_bar (GeditTab *tab,
+ GtkWidget *info_bar);
+
+G_END_DECLS
+
+#endif /* GEDIT_TAB_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-utils.c b/gedit/gedit-utils.c
new file mode 100644
index 0000000..9fc9e4f
--- /dev/null
+++ b/gedit/gedit-utils.c
@@ -0,0 +1,542 @@
+/*
+ * gedit-utils.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2002 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2003-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 "gedit-utils.h"
+#include <string.h>
+#include <glib/gi18n.h>
+#include <tepl/tepl.h>
+#include "gedit-debug.h"
+
+gboolean
+gedit_utils_menu_position_under_tree_view (GtkTreeView *tree_view,
+ GdkRectangle *rect)
+{
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ gint count_rows;
+ GList *rows;
+ gint widget_x, widget_y;
+
+ model = gtk_tree_view_get_model (tree_view);
+ g_return_val_if_fail (model != NULL, FALSE);
+
+ selection = gtk_tree_view_get_selection (tree_view);
+ g_return_val_if_fail (selection != NULL, FALSE);
+
+ count_rows = gtk_tree_selection_count_selected_rows (selection);
+ if (count_rows != 1)
+ return FALSE;
+
+ rows = gtk_tree_selection_get_selected_rows (selection, &model);
+ gtk_tree_view_get_cell_area (tree_view, (GtkTreePath *)(rows->data),
+ gtk_tree_view_get_column (tree_view, 0),
+ rect);
+
+ gtk_tree_view_convert_bin_window_to_widget_coords (tree_view, rect->x, rect->y, &widget_x, &widget_y);
+ rect->x = widget_x;
+ rect->y = widget_y;
+
+ g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free);
+ return TRUE;
+}
+
+/**
+ * gedit_utils_set_atk_name_description:
+ * @widget: The Gtk widget for which name/description to be set
+ * @name: Atk name string
+ * @description: Atk description string
+ *
+ * This function sets up name and description
+ * for a specified gtk widget.
+ */
+void
+gedit_utils_set_atk_name_description (GtkWidget *widget,
+ const gchar *name,
+ const gchar *description)
+{
+ AtkObject *aobj;
+
+ aobj = gtk_widget_get_accessible (widget);
+
+ if (!(GTK_IS_ACCESSIBLE (aobj)))
+ return;
+
+ if (name)
+ atk_object_set_name (aobj, name);
+
+ if (description)
+ atk_object_set_description (aobj, description);
+}
+
+static gchar *
+uri_get_dirname (const gchar *uri)
+{
+ gchar *res;
+ gchar *str;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ /* CHECK: does it work with uri chaining? - Paolo */
+ str = g_path_get_dirname (uri);
+ g_return_val_if_fail (str != NULL, g_strdup ("."));
+
+ if ((strlen (str) == 1) && (*str == '.'))
+ {
+ g_free (str);
+
+ return NULL;
+ }
+
+ res = tepl_utils_replace_home_dir_with_tilde (str);
+
+ g_free (str);
+
+ return res;
+}
+
+/**
+ * gedit_utils_location_get_dirname_for_display:
+ * @location: the location
+ *
+ * Returns a string suitable to be displayed in the UI indicating
+ * the name of the directory where the file is located.
+ * For remote files it may also contain the hostname etc.
+ * For local files it tries to replace the home dir with ~.
+ *
+ * Returns: (transfer full): a string to display the dirname
+ */
+gchar *
+gedit_utils_location_get_dirname_for_display (GFile *location)
+{
+ gchar *uri;
+ gchar *res;
+ GMount *mount;
+
+ g_return_val_if_fail (location != NULL, NULL);
+
+ /* we use the parse name, that is either the local path
+ * or an uri but which is utf8 safe */
+ uri = g_file_get_parse_name (location);
+
+ /* FIXME: this is sync... is it a problem? */
+ mount = g_file_find_enclosing_mount (location, NULL, NULL);
+ if (mount != NULL)
+ {
+ gchar *mount_name;
+ gchar *path = NULL;
+ gchar *dirname;
+
+ mount_name = g_mount_get_name (mount);
+ g_object_unref (mount);
+
+ /* obtain the "path" part of the uri */
+ tepl_utils_decode_uri (uri,
+ NULL, NULL,
+ NULL, NULL,
+ &path);
+
+ if (path == NULL)
+ {
+ dirname = uri_get_dirname (uri);
+ }
+ else
+ {
+ dirname = uri_get_dirname (path);
+ }
+
+ if (dirname == NULL || strcmp (dirname, ".") == 0)
+ {
+ res = mount_name;
+ }
+ else
+ {
+ res = g_strdup_printf ("%s %s", mount_name, dirname);
+ g_free (mount_name);
+ }
+
+ g_free (path);
+ g_free (dirname);
+ }
+ else
+ {
+ /* fallback for local files or uris without mounts */
+ res = uri_get_dirname (uri);
+ }
+
+ g_free (uri);
+
+ return res;
+}
+
+static gboolean
+is_valid_scheme_character (gchar c)
+{
+ return g_ascii_isalnum (c) || c == '+' || c == '-' || c == '.';
+}
+
+static gboolean
+has_valid_scheme (const gchar *uri)
+{
+ const gchar *p;
+
+ p = uri;
+
+ if (!is_valid_scheme_character (*p))
+ {
+ return FALSE;
+ }
+
+ do
+ {
+ p++;
+ } while (is_valid_scheme_character (*p));
+
+ return *p == ':';
+}
+
+gboolean
+gedit_utils_is_valid_location (GFile *location)
+{
+ const guchar *p;
+ gchar *uri;
+ gboolean is_valid;
+
+ if (location == NULL)
+ return FALSE;
+
+ uri = g_file_get_uri (location);
+
+ if (!has_valid_scheme (uri))
+ {
+ g_free (uri);
+ return FALSE;
+ }
+
+ is_valid = TRUE;
+
+ /* We expect to have a fully valid set of characters */
+ for (p = (const guchar *)uri; *p; p++) {
+ if (*p == '%')
+ {
+ ++p;
+ if (!g_ascii_isxdigit (*p))
+ {
+ is_valid = FALSE;
+ break;
+ }
+
+ ++p;
+ if (!g_ascii_isxdigit (*p))
+ {
+ is_valid = FALSE;
+ break;
+ }
+ }
+ else
+ {
+ if (*p <= 32 || *p >= 128)
+ {
+ is_valid = FALSE;
+ break;
+ }
+ }
+ }
+
+ g_free (uri);
+
+ return is_valid;
+}
+
+
+static gchar *
+make_canonical_uri_from_shell_arg (const gchar *str)
+{
+ GFile *gfile;
+ gchar *uri;
+
+ g_return_val_if_fail (str != NULL, NULL);
+ g_return_val_if_fail (*str != '\0', NULL);
+
+ /* Note for the future:
+ * FIXME: is still still relevant?
+ *
+ * <federico> paolo: and flame whoever tells
+ * you that file:///gnome/test_files/hëllò
+ * doesn't work --- that's not a valid URI
+ *
+ * <paolo> federico: well, another solution that
+ * does not requires patch to _from_shell_args
+ * is to check that the string returned by it
+ * contains only ASCII chars
+ * <federico> paolo: hmmmm, isn't there
+ * gnome_vfs_is_uri_valid() or something?
+ * <paolo>: I will use gedit_utils_is_valid_uri ()
+ *
+ */
+
+ gfile = g_file_new_for_commandline_arg (str);
+
+ if (gedit_utils_is_valid_location (gfile))
+ {
+ uri = g_file_get_uri (gfile);
+ g_object_unref (gfile);
+ return uri;
+ }
+
+ g_object_unref (gfile);
+ return NULL;
+}
+
+
+/**
+ * gedit_utils_basename_for_display:
+ * @location: location for which the basename should be displayed
+ *
+ * Returns: (transfer full): the basename of a file suitable for display to users.
+ */
+gchar *
+gedit_utils_basename_for_display (GFile *location)
+{
+ gchar *name;
+ gchar *hn;
+ gchar *uri;
+
+ g_return_val_if_fail (G_IS_FILE (location), NULL);
+
+ uri = g_file_get_uri (location);
+
+ /* First, try to query the display name, but only on local files */
+ if (g_file_has_uri_scheme (location, "file"))
+ {
+ GFileInfo *info;
+
+ info = g_file_query_info (location,
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+
+ if (info)
+ {
+ /* Simply get the display name to use as the basename */
+ name = g_strdup (g_file_info_get_display_name (info));
+ g_object_unref (info);
+ }
+ else
+ {
+ /* This is a local file, and therefore we will use
+ * g_filename_display_basename on the local path */
+ gchar *local_path;
+
+ local_path = g_file_get_path (location);
+ name = g_filename_display_basename (local_path);
+ g_free (local_path);
+ }
+ }
+ else if (g_file_has_parent (location, NULL) ||
+ !tepl_utils_decode_uri (uri, NULL, NULL, &hn, NULL, NULL))
+ {
+ /* For remote files with a parent (so not just http://foo.com)
+ or remote file for which the decoding of the host name fails,
+ use the _parse_name and take basename of that */
+ gchar *parse_name;
+ gchar *base;
+
+ parse_name = g_file_get_parse_name (location);
+ base = g_filename_display_basename (parse_name);
+ name = g_uri_unescape_string (base, NULL);
+
+ g_free (base);
+ g_free (parse_name);
+ }
+ else
+ {
+ /* display '/ on <host>' using the decoded host */
+ gchar *hn_utf8;
+
+ if (hn != NULL)
+ {
+ hn_utf8 = g_utf8_make_valid (hn, -1);
+ }
+ else
+ {
+ /* we should never get here */
+ hn_utf8 = g_strdup ("?");
+ }
+
+ /* Translators: '/ on <remote-share>' */
+ name = g_strdup_printf (_("/ on %s"), hn_utf8);
+
+ g_free (hn_utf8);
+ g_free (hn);
+ }
+
+ g_free (uri);
+
+ return name;
+}
+
+/**
+ * gedit_utils_drop_get_uris:
+ * @selection_data: the #GtkSelectionData from drag_data_received
+ *
+ * Create a list of valid uri's from a uri-list drop.
+ *
+ * Returns: (transfer full): a string array which will hold the uris or
+ * %NULL if there were no valid uris. g_strfreev should be used when
+ * the string array is no longer used
+ */
+gchar **
+gedit_utils_drop_get_uris (GtkSelectionData *selection_data)
+{
+ gchar **uris;
+ gint i;
+ gint p = 0;
+ gchar **uri_list;
+
+ uris = g_uri_list_extract_uris ((gchar *) gtk_selection_data_get_data (selection_data));
+ uri_list = g_new0(gchar *, g_strv_length (uris) + 1);
+
+ for (i = 0; uris[i] != NULL; i++)
+ {
+ gchar *uri;
+
+ uri = make_canonical_uri_from_shell_arg (uris[i]);
+
+ /* Silently ignore malformed URI/filename */
+ if (uri != NULL)
+ uri_list[p++] = uri;
+ }
+
+ if (*uri_list == NULL)
+ {
+ g_free(uri_list);
+ g_strfreev (uris);
+ return NULL;
+ }
+
+ g_strfreev (uris);
+ return uri_list;
+}
+
+GtkSourceCompressionType
+gedit_utils_get_compression_type_from_content_type (const gchar *content_type)
+{
+ if (content_type == NULL)
+ {
+ return GTK_SOURCE_COMPRESSION_TYPE_NONE;
+ }
+
+ if (g_content_type_is_a (content_type, "application/x-gzip"))
+ {
+ return GTK_SOURCE_COMPRESSION_TYPE_GZIP;
+ }
+
+ return GTK_SOURCE_COMPRESSION_TYPE_NONE;
+}
+
+/* Copied from nautilus */
+static gchar *
+get_direct_save_filename (GdkDragContext *context)
+{
+ guchar *prop_text;
+ gint prop_len;
+
+ if (!gdk_property_get (gdk_drag_context_get_source_window (context), gdk_atom_intern ("XdndDirectSave0", FALSE),
+ gdk_atom_intern ("text/plain", FALSE), 0, 1024, FALSE, NULL, NULL,
+ &prop_len, &prop_text) && prop_text != NULL) {
+ return NULL;
+ }
+
+ /* Zero-terminate the string */
+ prop_text = g_realloc (prop_text, prop_len + 1);
+ prop_text[prop_len] = '\0';
+
+ /* Verify that the file name provided by the source is valid */
+ if (*prop_text == '\0' ||
+ strchr ((const gchar *) prop_text, G_DIR_SEPARATOR) != NULL) {
+ gedit_debug_message (DEBUG_UTILS, "Invalid filename provided by XDS drag site");
+ g_free (prop_text);
+ return NULL;
+ }
+
+ return (gchar *)prop_text;
+}
+
+gchar *
+gedit_utils_set_direct_save_filename (GdkDragContext *context)
+{
+ gchar *uri;
+ gchar *filename;
+
+ uri = NULL;
+ filename = get_direct_save_filename (context);
+
+ if (filename != NULL)
+ {
+ gchar *tempdir;
+ gchar *path;
+
+ tempdir = g_dir_make_tmp ("gedit-drop-XXXXXX", NULL);
+ if (tempdir == NULL)
+ {
+ tempdir = g_strdup (g_get_tmp_dir ());
+ }
+
+ path = g_build_filename (tempdir,
+ filename,
+ NULL);
+
+ uri = g_filename_to_uri (path, NULL, NULL);
+
+ /* Change the property */
+ 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 *) uri,
+ strlen (uri));
+
+ g_free (tempdir);
+ g_free (path);
+ g_free (filename);
+ }
+
+ return uri;
+}
+
+const gchar *
+gedit_utils_newline_type_to_string (GtkSourceNewlineType newline_type)
+{
+ switch (newline_type)
+ {
+ case GTK_SOURCE_NEWLINE_TYPE_LF:
+ return _("Unix/Linux");
+ case GTK_SOURCE_NEWLINE_TYPE_CR:
+ return _("Mac OS Classic");
+ case GTK_SOURCE_NEWLINE_TYPE_CR_LF:
+ return _("Windows");
+ }
+
+ return NULL;
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-utils.h b/gedit/gedit-utils.h
new file mode 100644
index 0000000..a6b423d
--- /dev/null
+++ b/gedit/gedit-utils.h
@@ -0,0 +1,56 @@
+/*
+ * gedit-utils.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002 - 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/>.
+ */
+
+#ifndef GEDIT_UTILS_H
+#define GEDIT_UTILS_H
+
+#include <gtksourceview/gtksource.h>
+
+G_BEGIN_DECLS
+
+gboolean gedit_utils_menu_position_under_tree_view (GtkTreeView *tree_view,
+ GdkRectangle *rect);
+
+void gedit_utils_set_atk_name_description (GtkWidget *widget,
+ const gchar *name,
+ const gchar *description);
+
+gchar *gedit_utils_location_get_dirname_for_display (GFile *location);
+
+gboolean gedit_utils_is_valid_location (GFile *location);
+
+gchar *gedit_utils_basename_for_display (GFile *location);
+
+/* Turns data from a drop into a list of well formatted uris */
+gchar **gedit_utils_drop_get_uris (GtkSelectionData *selection_data);
+
+gchar *gedit_utils_set_direct_save_filename (GdkDragContext *context);
+
+GtkSourceCompressionType gedit_utils_get_compression_type_from_content_type (const gchar *content_type);
+
+const gchar *gedit_utils_newline_type_to_string (GtkSourceNewlineType newline_type);
+
+G_END_DECLS
+
+#endif /* GEDIT_UTILS_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-view-activatable.c b/gedit/gedit-view-activatable.c
new file mode 100644
index 0000000..7ec2876
--- /dev/null
+++ b/gedit/gedit-view-activatable.c
@@ -0,0 +1,96 @@
+/*
+ * gedit-view-activatable.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 Steve Frécinaux
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gedit-view-activatable.h"
+
+#include "gedit-view.h"
+
+/**
+ * SECTION:gedit-view-activatable
+ * @short_description: Interface for activatable extensions on views
+ * @see_also: #PeasExtensionSet
+ *
+ * #GeditViewActivatable is an interface which should be implemented by
+ * extensions that should be activated on a gedit view.
+ **/
+
+G_DEFINE_INTERFACE(GeditViewActivatable, gedit_view_activatable, G_TYPE_OBJECT)
+
+static void
+gedit_view_activatable_default_init (GeditViewActivatableInterface *iface)
+{
+ /**
+ * GeditViewActivatable:view:
+ *
+ * The window property contains the gedit window for this
+ * #GeditViewActivatable instance.
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_object ("view",
+ "view",
+ "A gedit view",
+ GEDIT_TYPE_VIEW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+}
+
+/**
+ * gedit_view_activatable_activate:
+ * @activatable: A #GeditViewActivatable.
+ *
+ * Activates the extension on the window property.
+ */
+void
+gedit_view_activatable_activate (GeditViewActivatable *activatable)
+{
+ GeditViewActivatableInterface *iface;
+
+ g_return_if_fail (GEDIT_IS_VIEW_ACTIVATABLE (activatable));
+
+ iface = GEDIT_VIEW_ACTIVATABLE_GET_IFACE (activatable);
+ if (iface->activate != NULL)
+ {
+ iface->activate (activatable);
+ }
+}
+
+/**
+ * gedit_view_activatable_deactivate:
+ * @activatable: A #GeditViewActivatable.
+ *
+ * Deactivates the extension on the window property.
+ */
+void
+gedit_view_activatable_deactivate (GeditViewActivatable *activatable)
+{
+ GeditViewActivatableInterface *iface;
+
+ g_return_if_fail (GEDIT_IS_VIEW_ACTIVATABLE (activatable));
+
+ iface = GEDIT_VIEW_ACTIVATABLE_GET_IFACE (activatable);
+ if (iface->deactivate != NULL)
+ {
+ iface->deactivate (activatable);
+ }
+}
+
diff --git a/gedit/gedit-view-activatable.h b/gedit/gedit-view-activatable.h
new file mode 100644
index 0000000..77799ea
--- /dev/null
+++ b/gedit/gedit-view-activatable.h
@@ -0,0 +1,47 @@
+/*
+ * gedit-view-activatable.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Steve Frécinaux
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_VIEW_ACTIVATABLE_H
+#define GEDIT_VIEW_ACTIVATABLE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_VIEW_ACTIVATABLE (gedit_view_activatable_get_type ())
+
+G_DECLARE_INTERFACE (GeditViewActivatable, gedit_view_activatable, GEDIT, VIEW_ACTIVATABLE, GObject)
+
+struct _GeditViewActivatableInterface
+{
+ GTypeInterface g_iface;
+
+ /* Virtual public methods */
+ void (*activate) (GeditViewActivatable *activatable);
+ void (*deactivate) (GeditViewActivatable *activatable);
+};
+
+void gedit_view_activatable_activate (GeditViewActivatable *activatable);
+void gedit_view_activatable_deactivate (GeditViewActivatable *activatable);
+
+G_END_DECLS
+
+#endif /* GEDIT_VIEW_ACTIVATABLE_H */
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-view-frame.c b/gedit/gedit-view-frame.c
new file mode 100644
index 0000000..f41734c
--- /dev/null
+++ b/gedit/gedit-view-frame.c
@@ -0,0 +1,1592 @@
+/*
+ * gedit-view-frame.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Ignacio Casal Quinteiro
+ * Copyright (C) 2013, 2019 - Sébastien Wilmet
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "gedit-view-frame.h"
+
+#include <gtksourceview/gtksource.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <stdlib.h>
+
+#include "gedit-debug.h"
+#include "gedit-utils.h"
+#include "gedit-settings.h"
+#include "libgd/gd.h"
+
+#define FLUSH_TIMEOUT_DURATION 30 /* in seconds */
+
+#define SEARCH_POPUP_MARGIN 12
+
+typedef enum
+{
+ GOTO_LINE,
+ SEARCH
+} SearchMode;
+
+typedef enum
+{
+ SEARCH_STATE_NORMAL,
+ SEARCH_STATE_NOT_FOUND
+} SearchState;
+
+struct _GeditViewFrame
+{
+ GtkOverlay parent_instance;
+
+ GeditView *view;
+
+ SearchMode search_mode;
+
+ /* Where the search has started. When the user presses escape in the
+ * search entry (to cancel the search), we return to the start_mark.
+ */
+ GtkTextMark *start_mark;
+
+ GtkRevealer *revealer;
+ GdTaggedEntry *search_entry;
+ GdTaggedEntryTag *entry_tag;
+ GtkWidget *go_up_button;
+ GtkWidget *go_down_button;
+
+ guint flush_timeout_id;
+ guint idle_update_entry_tag_id;
+ guint remove_entry_tag_timeout_id;
+ gulong view_scroll_event_id;
+ gulong search_entry_focus_out_id;
+ gulong search_entry_changed_id;
+
+ GtkSourceSearchSettings *search_settings;
+
+ /* Used to restore the search state if an incremental search is
+ * cancelled.
+ */
+ GtkSourceSearchSettings *old_search_settings;
+
+ /* The original search texts. In search_settings and
+ * old_search_settings, the search text is unescaped. Since the escape
+ * function is not reciprocal, we need to store the original search
+ * texts.
+ */
+ gchar *search_text;
+ gchar *old_search_text;
+};
+
+G_DEFINE_TYPE (GeditViewFrame, gedit_view_frame, GTK_TYPE_OVERLAY)
+
+static GeditDocument *
+get_document (GeditViewFrame *frame)
+{
+ return GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view)));
+}
+
+static void
+get_iter_at_start_mark (GeditViewFrame *frame,
+ GtkTextIter *iter)
+{
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+
+ if (frame->start_mark != NULL)
+ {
+ gtk_text_buffer_get_iter_at_mark (buffer, iter, frame->start_mark);
+ }
+ else
+ {
+ g_warn_if_reached ();
+ gtk_text_buffer_get_start_iter (buffer, iter);
+ }
+}
+
+static void
+gedit_view_frame_dispose (GObject *object)
+{
+ GeditViewFrame *frame = GEDIT_VIEW_FRAME (object);
+ GtkTextBuffer *buffer = NULL;
+
+ if (frame->view != NULL)
+ {
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+ }
+
+ if (frame->start_mark != NULL && buffer != NULL)
+ {
+ gtk_text_buffer_delete_mark (buffer, frame->start_mark);
+ frame->start_mark = NULL;
+ }
+
+ if (frame->flush_timeout_id != 0)
+ {
+ g_source_remove (frame->flush_timeout_id);
+ frame->flush_timeout_id = 0;
+ }
+
+ if (frame->idle_update_entry_tag_id != 0)
+ {
+ g_source_remove (frame->idle_update_entry_tag_id);
+ frame->idle_update_entry_tag_id = 0;
+ }
+
+ if (frame->remove_entry_tag_timeout_id != 0)
+ {
+ g_source_remove (frame->remove_entry_tag_timeout_id);
+ frame->remove_entry_tag_timeout_id = 0;
+ }
+
+ if (buffer != NULL)
+ {
+ GtkSourceFile *file = gedit_document_get_file (GEDIT_DOCUMENT (buffer));
+ gtk_source_file_set_mount_operation_factory (file, NULL, NULL, NULL);
+ }
+
+ g_clear_object (&frame->entry_tag);
+ g_clear_object (&frame->search_settings);
+ g_clear_object (&frame->old_search_settings);
+
+ G_OBJECT_CLASS (gedit_view_frame_parent_class)->dispose (object);
+}
+
+static void
+gedit_view_frame_finalize (GObject *object)
+{
+ GeditViewFrame *frame = GEDIT_VIEW_FRAME (object);
+
+ g_free (frame->search_text);
+ g_free (frame->old_search_text);
+
+ G_OBJECT_CLASS (gedit_view_frame_parent_class)->finalize (object);
+}
+
+static void
+hide_search_widget (GeditViewFrame *frame,
+ gboolean cancel)
+{
+ GtkTextBuffer *buffer;
+
+ if (!gtk_revealer_get_reveal_child (frame->revealer))
+ {
+ return;
+ }
+
+ if (frame->view_scroll_event_id != 0)
+ {
+ g_signal_handler_disconnect (frame->view,
+ frame->view_scroll_event_id);
+ frame->view_scroll_event_id = 0;
+ }
+
+ if (frame->flush_timeout_id != 0)
+ {
+ g_source_remove (frame->flush_timeout_id);
+ frame->flush_timeout_id = 0;
+ }
+
+ gtk_revealer_set_reveal_child (frame->revealer, FALSE);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+
+ if (cancel && frame->start_mark != NULL)
+ {
+ GtkTextIter iter;
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter,
+ frame->start_mark);
+ gtk_text_buffer_place_cursor (buffer, &iter);
+
+ tepl_view_scroll_to_cursor (TEPL_VIEW (frame->view));
+ }
+
+ if (frame->start_mark != NULL)
+ {
+ gtk_text_buffer_delete_mark (buffer, frame->start_mark);
+ frame->start_mark = NULL;
+ }
+}
+
+static gboolean
+search_entry_flush_timeout (GeditViewFrame *frame)
+{
+ frame->flush_timeout_id = 0;
+ hide_search_widget (frame, FALSE);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+renew_flush_timeout (GeditViewFrame *frame)
+{
+ if (frame->flush_timeout_id != 0)
+ {
+ g_source_remove (frame->flush_timeout_id);
+ }
+
+ frame->flush_timeout_id =
+ g_timeout_add_seconds (FLUSH_TIMEOUT_DURATION,
+ (GSourceFunc)search_entry_flush_timeout,
+ frame);
+}
+
+static GtkSourceSearchContext *
+get_search_context (GeditViewFrame *frame)
+{
+ GeditDocument *doc;
+ GtkSourceSearchContext *search_context;
+ GtkSourceSearchSettings *search_settings;
+
+ doc = get_document (frame);
+ search_context = gedit_document_get_search_context (doc);
+
+ if (search_context == NULL)
+ {
+ return NULL;
+ }
+
+ search_settings = gtk_source_search_context_get_settings (search_context);
+
+ if (search_settings == frame->search_settings)
+ {
+ return search_context;
+ }
+
+ return NULL;
+}
+
+static void
+set_search_state (GeditViewFrame *frame,
+ SearchState state)
+{
+ GtkStyleContext *context;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (frame->search_entry));
+
+ if (state == SEARCH_STATE_NOT_FOUND)
+ {
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_ERROR);
+ }
+ else
+ {
+ gtk_style_context_remove_class (context, GTK_STYLE_CLASS_ERROR);
+ }
+}
+
+static void
+finish_search (GeditViewFrame *frame,
+ gboolean found)
+{
+ const gchar *entry_text = gtk_entry_get_text (GTK_ENTRY (frame->search_entry));
+
+ if (found || (entry_text[0] == '\0'))
+ {
+ tepl_view_scroll_to_cursor (TEPL_VIEW (frame->view));
+
+ set_search_state (frame, SEARCH_STATE_NORMAL);
+ }
+ else
+ {
+ set_search_state (frame, SEARCH_STATE_NOT_FOUND);
+ }
+}
+
+static void
+start_search_finished (GtkSourceSearchContext *search_context,
+ GAsyncResult *result,
+ GeditViewFrame *frame)
+{
+ GtkTextIter match_start;
+ GtkTextIter match_end;
+ gboolean found;
+ GtkSourceBuffer *buffer;
+
+ found = gtk_source_search_context_forward_finish (search_context,
+ result,
+ &match_start,
+ &match_end,
+ NULL,
+ NULL);
+
+ buffer = gtk_source_search_context_get_buffer (search_context);
+
+ if (found)
+ {
+ gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer),
+ &match_start,
+ &match_end);
+ }
+ else if (frame->start_mark != NULL)
+ {
+ GtkTextIter start_at;
+
+ gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
+ &start_at,
+ frame->start_mark);
+
+ gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer),
+ &start_at,
+ &start_at);
+ }
+
+ finish_search (frame, found);
+}
+
+static void
+start_search (GeditViewFrame *frame)
+{
+ GtkSourceSearchContext *search_context;
+ GtkTextIter start_at;
+
+ g_return_if_fail (frame->search_mode == SEARCH);
+
+ search_context = get_search_context (frame);
+
+ if (search_context == NULL)
+ {
+ return;
+ }
+
+ get_iter_at_start_mark (frame, &start_at);
+
+ gtk_source_search_context_forward_async (search_context,
+ &start_at,
+ NULL,
+ (GAsyncReadyCallback)start_search_finished,
+ frame);
+}
+
+static void
+forward_search_finished (GtkSourceSearchContext *search_context,
+ GAsyncResult *result,
+ GeditViewFrame *frame)
+{
+ GtkTextIter match_start;
+ GtkTextIter match_end;
+ gboolean found;
+
+ found = gtk_source_search_context_forward_finish (search_context,
+ result,
+ &match_start,
+ &match_end,
+ NULL,
+ NULL);
+
+ if (found)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+
+ gtk_text_buffer_select_range (buffer,
+ &match_start,
+ &match_end);
+ }
+
+ finish_search (frame, found);
+}
+
+static void
+forward_search (GeditViewFrame *frame)
+{
+ GtkTextIter start_at;
+ GtkTextBuffer *buffer;
+ GtkSourceSearchContext *search_context;
+
+ g_return_if_fail (frame->search_mode == SEARCH);
+
+ search_context = get_search_context (frame);
+
+ if (search_context == NULL)
+ {
+ return;
+ }
+
+ renew_flush_timeout (frame);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+
+ gtk_text_buffer_get_selection_bounds (buffer, NULL, &start_at);
+
+ gtk_source_search_context_forward_async (search_context,
+ &start_at,
+ NULL,
+ (GAsyncReadyCallback)forward_search_finished,
+ frame);
+}
+
+static void
+backward_search_finished (GtkSourceSearchContext *search_context,
+ GAsyncResult *result,
+ GeditViewFrame *frame)
+{
+ GtkTextIter match_start;
+ GtkTextIter match_end;
+ gboolean found;
+ GtkSourceBuffer *buffer;
+
+ found = gtk_source_search_context_backward_finish (search_context,
+ result,
+ &match_start,
+ &match_end,
+ NULL,
+ NULL);
+
+ buffer = gtk_source_search_context_get_buffer (search_context);
+
+ if (found)
+ {
+ gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer),
+ &match_start,
+ &match_end);
+ }
+
+ finish_search (frame, found);
+}
+
+static void
+backward_search (GeditViewFrame *frame)
+{
+ GtkTextIter start_at;
+ GtkTextBuffer *buffer;
+ GtkSourceSearchContext *search_context;
+
+ g_return_if_fail (frame->search_mode == SEARCH);
+
+ search_context = get_search_context (frame);
+
+ if (search_context == NULL)
+ {
+ return;
+ }
+
+ renew_flush_timeout (frame);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+
+ gtk_text_buffer_get_selection_bounds (buffer, &start_at, NULL);
+
+ gtk_source_search_context_backward_async (search_context,
+ &start_at,
+ NULL,
+ (GAsyncReadyCallback)backward_search_finished,
+ frame);
+}
+
+static gboolean
+search_widget_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event,
+ GeditViewFrame *frame)
+{
+ if (frame->search_mode == GOTO_LINE)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ if (event->state & GDK_CONTROL_MASK)
+ {
+ if (event->direction == GDK_SCROLL_UP)
+ {
+ backward_search (frame);
+ return GDK_EVENT_STOP;
+ }
+ else if (event->direction == GDK_SCROLL_DOWN)
+ {
+ forward_search (frame);
+ return GDK_EVENT_STOP;
+ }
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static GtkSourceSearchSettings *
+copy_search_settings (GtkSourceSearchSettings *settings)
+{
+ GtkSourceSearchSettings *new_settings = gtk_source_search_settings_new ();
+ gboolean val;
+ const gchar *text;
+
+ if (settings == NULL)
+ {
+ return new_settings;
+ }
+
+ val = gtk_source_search_settings_get_case_sensitive (settings);
+ gtk_source_search_settings_set_case_sensitive (new_settings, val);
+
+ val = gtk_source_search_settings_get_wrap_around (settings);
+ gtk_source_search_settings_set_wrap_around (new_settings, val);
+
+ val = gtk_source_search_settings_get_at_word_boundaries (settings);
+ gtk_source_search_settings_set_at_word_boundaries (new_settings, val);
+
+ val = gtk_source_search_settings_get_regex_enabled (settings);
+ gtk_source_search_settings_set_regex_enabled (new_settings, val);
+
+ text = gtk_source_search_settings_get_search_text (settings);
+ gtk_source_search_settings_set_search_text (new_settings, text);
+
+ return new_settings;
+}
+
+static gboolean
+search_widget_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ GeditViewFrame *frame)
+{
+ /* Close window */
+ if (event->keyval == GDK_KEY_Tab)
+ {
+ hide_search_widget (frame, FALSE);
+ gtk_widget_grab_focus (GTK_WIDGET (frame->view));
+
+ return GDK_EVENT_STOP;
+ }
+
+ if (frame->search_mode == GOTO_LINE)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ /* SEARCH mode */
+
+ /* select previous matching iter */
+ if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up)
+ {
+ backward_search (frame);
+ return GDK_EVENT_STOP;
+ }
+
+ /* select next matching iter */
+ if (event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down)
+ {
+ forward_search (frame);
+ return GDK_EVENT_STOP;
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+remove_entry_tag_timeout_cb (GeditViewFrame *frame)
+{
+ frame->remove_entry_tag_timeout_id = 0;
+
+ gd_tagged_entry_remove_tag (frame->search_entry,
+ frame->entry_tag);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+update_entry_tag (GeditViewFrame *frame)
+{
+ GtkSourceSearchContext *search_context;
+ GtkTextBuffer *buffer;
+ GtkTextIter select_start;
+ GtkTextIter select_end;
+ gint count;
+ gint pos;
+ gchar *label;
+
+ if (frame->search_mode == GOTO_LINE)
+ {
+ gd_tagged_entry_remove_tag (frame->search_entry,
+ frame->entry_tag);
+ return;
+ }
+
+ search_context = get_search_context (frame);
+
+ if (search_context == NULL)
+ {
+ return;
+ }
+
+ count = gtk_source_search_context_get_occurrences_count (search_context);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+ gtk_text_buffer_get_selection_bounds (buffer, &select_start, &select_end);
+
+ pos = gtk_source_search_context_get_occurrence_position (search_context,
+ &select_start,
+ &select_end);
+
+ if (count == -1 || pos == -1)
+ {
+ /* The buffer is not fully scanned. Remove the tag after a short
+ * delay. If we don't remove the tag at all, the information can
+ * be outdated during a too long time (for big buffers). And if
+ * the tag is removed directly, there is some flashing for small
+ * buffers: the tag disappears and reappear after a really short
+ * time.
+ */
+
+ if (frame->remove_entry_tag_timeout_id == 0)
+ {
+ frame->remove_entry_tag_timeout_id =
+ g_timeout_add (500,
+ (GSourceFunc)remove_entry_tag_timeout_cb,
+ frame);
+ }
+
+ return;
+ }
+
+ if (count == 0 || pos == 0)
+ {
+ gd_tagged_entry_remove_tag (frame->search_entry,
+ frame->entry_tag);
+ return;
+ }
+
+ if (frame->remove_entry_tag_timeout_id != 0)
+ {
+ g_source_remove (frame->remove_entry_tag_timeout_id);
+ frame->remove_entry_tag_timeout_id = 0;
+ }
+
+ /* Translators: the first %d is the position of the current search
+ * occurrence, and the second %d is the total number of search
+ * occurrences.
+ */
+ label = g_strdup_printf (_("%d of %d"), pos, count);
+
+ gd_tagged_entry_tag_set_label (frame->entry_tag, label);
+
+ gd_tagged_entry_add_tag (frame->search_entry,
+ frame->entry_tag);
+
+ g_free (label);
+}
+
+static gboolean
+update_entry_tag_idle_cb (GeditViewFrame *frame)
+{
+ frame->idle_update_entry_tag_id = 0;
+
+ update_entry_tag (frame);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+install_update_entry_tag_idle (GeditViewFrame *frame)
+{
+ if (frame->idle_update_entry_tag_id == 0)
+ {
+ frame->idle_update_entry_tag_id = g_idle_add ((GSourceFunc)update_entry_tag_idle_cb,
+ frame);
+ }
+}
+
+static void
+update_search_text (GeditViewFrame *frame)
+{
+ const gchar *entry_text = gtk_entry_get_text (GTK_ENTRY (frame->search_entry));
+
+ g_free (frame->search_text);
+ frame->search_text = g_strdup (entry_text);
+
+ if (gtk_source_search_settings_get_regex_enabled (frame->search_settings))
+ {
+ gtk_source_search_settings_set_search_text (frame->search_settings,
+ entry_text);
+ }
+ else
+ {
+ gchar *unescaped_entry_text = gtk_source_utils_unescape_search_text (entry_text);
+
+ gtk_source_search_settings_set_search_text (frame->search_settings,
+ unescaped_entry_text);
+
+ g_free (unescaped_entry_text);
+ }
+}
+
+static void
+regex_toggled_cb (GtkCheckMenuItem *menu_item,
+ GeditViewFrame *frame)
+{
+ gtk_source_search_settings_set_regex_enabled (frame->search_settings,
+ gtk_check_menu_item_get_active (menu_item));
+
+ start_search (frame);
+}
+
+static void
+at_word_boundaries_toggled_cb (GtkCheckMenuItem *menu_item,
+ GeditViewFrame *frame)
+{
+ gtk_source_search_settings_set_at_word_boundaries (frame->search_settings,
+ gtk_check_menu_item_get_active (menu_item));
+
+ start_search (frame);
+}
+
+static void
+case_sensitive_toggled_cb (GtkCheckMenuItem *menu_item,
+ GeditViewFrame *frame)
+{
+ gtk_source_search_settings_set_case_sensitive (frame->search_settings,
+ gtk_check_menu_item_get_active (menu_item));
+
+ start_search (frame);
+}
+
+static void
+add_popup_menu_items (GeditViewFrame *frame,
+ GtkWidget *menu)
+{
+ GtkWidget *menu_item;
+ gboolean val;
+
+ /* create "Wrap Around" menu item. */
+ menu_item = gtk_check_menu_item_new_with_mnemonic (_("_Wrap Around"));
+
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+ gtk_widget_show (menu_item);
+
+ g_object_bind_property (frame->search_settings, "wrap-around",
+ menu_item, "active",
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+ /* create "Match as Regular Expression" menu item. */
+ menu_item = gtk_check_menu_item_new_with_mnemonic (_("Match as _Regular Expression"));
+
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+ gtk_widget_show (menu_item);
+
+ val = gtk_source_search_settings_get_regex_enabled (frame->search_settings);
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), val);
+
+ g_signal_connect (menu_item,
+ "toggled",
+ G_CALLBACK (regex_toggled_cb),
+ frame);
+
+ /* create "Match Entire Word Only" menu item. */
+ menu_item = gtk_check_menu_item_new_with_mnemonic (_("Match _Entire Word Only"));
+
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+ gtk_widget_show (menu_item);
+
+ val = gtk_source_search_settings_get_at_word_boundaries (frame->search_settings);
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), val);
+
+ g_signal_connect (menu_item,
+ "toggled",
+ G_CALLBACK (at_word_boundaries_toggled_cb),
+ frame);
+
+ /* create "Match Case" menu item. */
+ menu_item = gtk_check_menu_item_new_with_mnemonic (_("_Match Case"));
+
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+ gtk_widget_show (menu_item);
+
+ val = gtk_source_search_settings_get_case_sensitive (frame->search_settings);
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), val);
+
+ g_signal_connect (menu_item,
+ "toggled",
+ G_CALLBACK (case_sensitive_toggled_cb),
+ frame);
+}
+
+static void
+popup_menu_hide_cb (GeditViewFrame *frame)
+{
+ renew_flush_timeout (frame);
+
+ g_signal_handler_unblock (frame->search_entry,
+ frame->search_entry_focus_out_id);
+}
+
+static void
+setup_popup_menu (GeditViewFrame *frame,
+ GtkWidget *menu)
+{
+ if (frame->flush_timeout_id != 0)
+ {
+ g_source_remove (frame->flush_timeout_id);
+ frame->flush_timeout_id = 0;
+ }
+
+ g_signal_handler_block (frame->search_entry,
+ frame->search_entry_focus_out_id);
+
+ g_signal_connect_swapped (menu,
+ "hide",
+ G_CALLBACK (popup_menu_hide_cb),
+ frame);
+}
+
+static void
+search_entry_escaped (GtkSearchEntry *entry,
+ GeditViewFrame *frame)
+{
+ GtkSourceSearchContext *search_context = get_search_context (frame);
+
+ if (frame->search_mode == SEARCH &&
+ search_context != NULL)
+ {
+ GtkSourceSearchContext *search_context;
+ GtkTextBuffer *buffer;
+
+ g_clear_object (&frame->search_settings);
+ frame->search_settings = copy_search_settings (frame->old_search_settings);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+ search_context = gtk_source_search_context_new (GTK_SOURCE_BUFFER (buffer),
+ frame->search_settings);
+ gedit_document_set_search_context (GEDIT_DOCUMENT (buffer), search_context);
+ g_object_unref (search_context);
+
+ g_free (frame->search_text);
+ frame->search_text = NULL;
+
+ if (frame->old_search_text != NULL)
+ {
+ frame->search_text = g_strdup (frame->old_search_text);
+ }
+ }
+
+ hide_search_widget (frame, TRUE);
+ gtk_widget_grab_focus (GTK_WIDGET (frame->view));
+}
+
+static void
+search_entry_previous_match (GtkSearchEntry *entry,
+ GeditViewFrame *frame)
+{
+ backward_search (frame);
+}
+
+static void
+search_entry_next_match (GtkSearchEntry *entry,
+ GeditViewFrame *frame)
+{
+ forward_search (frame);
+}
+
+static void
+search_entry_populate_popup (GtkEntry *entry,
+ GtkMenu *menu,
+ GeditViewFrame *frame)
+{
+ GtkWidget *menu_item;
+
+ if (frame->search_mode == GOTO_LINE)
+ {
+ return;
+ }
+
+ setup_popup_menu (frame, GTK_WIDGET (menu));
+
+ /* separator */
+ menu_item = gtk_separator_menu_item_new ();
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+ gtk_widget_show (menu_item);
+
+ add_popup_menu_items (frame, GTK_WIDGET (menu));
+}
+
+static void
+search_entry_icon_release (GtkEntry *entry,
+ GtkEntryIconPosition icon_pos,
+ GdkEventButton *event,
+ GeditViewFrame *frame)
+{
+ GtkWidget *menu;
+
+ if (frame->search_mode == GOTO_LINE ||
+ icon_pos != GTK_ENTRY_ICON_PRIMARY)
+ {
+ return;
+ }
+
+ menu = gtk_menu_new ();
+ gtk_widget_show (menu);
+
+ setup_popup_menu (frame, menu);
+ add_popup_menu_items (frame, menu);
+
+ g_signal_connect (menu,
+ "selection-done",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ gtk_menu_popup_at_widget (GTK_MENU (menu), GTK_WIDGET (entry), GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
+}
+
+static void
+search_entry_activate (GtkEntry *entry,
+ GeditViewFrame *frame)
+{
+ hide_search_widget (frame, FALSE);
+ gtk_widget_grab_focus (GTK_WIDGET (frame->view));
+}
+
+static void
+search_entry_insert_text (GtkEditable *editable,
+ const gchar *text,
+ gint length,
+ gint *position,
+ GeditViewFrame *frame)
+{
+ gunichar c;
+ const gchar *p;
+ const gchar *end;
+ const gchar *next;
+
+ if (frame->search_mode == SEARCH)
+ {
+ return;
+ }
+
+ p = text;
+ end = text + length;
+
+ if (p == end)
+ {
+ return;
+ }
+
+ c = g_utf8_get_char (p);
+
+ if (((c == '-' || c == '+') && *position == 0) ||
+ (c == ':' && *position != 0))
+ {
+ gchar *s = NULL;
+
+ if (c == ':')
+ {
+ s = gtk_editable_get_chars (editable, 0, -1);
+ s = g_utf8_strchr (s, -1, ':');
+ }
+
+ if (s == NULL || s == p)
+ {
+ next = g_utf8_next_char (p);
+ p = next;
+ }
+
+ g_free (s);
+ }
+
+ while (p != end)
+ {
+ next = g_utf8_next_char (p);
+
+ c = g_utf8_get_char (p);
+
+ if (!g_unichar_isdigit (c))
+ {
+ g_signal_stop_emission_by_name (editable, "insert_text");
+ gtk_widget_error_bell (GTK_WIDGET (frame->search_entry));
+ break;
+ }
+
+ p = next;
+ }
+}
+
+static void
+customize_for_search_mode (GeditViewFrame *frame)
+{
+ GIcon *icon;
+ gint width_request;
+
+ if (frame->search_mode == SEARCH)
+ {
+ icon = g_themed_icon_new_with_default_fallbacks ("edit-find-symbolic");
+
+ width_request = 260;
+
+ gtk_widget_set_tooltip_text (GTK_WIDGET (frame->search_entry),
+ _("String you want to search for"));
+
+ gtk_widget_show (frame->go_up_button);
+ gtk_widget_show (frame->go_down_button);
+ }
+ else
+ {
+ icon = g_themed_icon_new_with_default_fallbacks ("go-jump-symbolic");
+
+ width_request = 160;
+
+ gtk_widget_set_tooltip_text (GTK_WIDGET (frame->search_entry),
+ _("Line you want to move the cursor to"));
+
+ gtk_widget_hide (frame->go_up_button);
+ gtk_widget_hide (frame->go_down_button);
+ }
+
+ gtk_entry_set_icon_from_gicon (GTK_ENTRY (frame->search_entry),
+ GTK_ENTRY_ICON_PRIMARY,
+ icon);
+
+ gtk_widget_set_size_request (GTK_WIDGET (frame->search_entry),
+ width_request,
+ -1);
+
+ g_object_unref (icon);
+}
+
+static void
+update_goto_line (GeditViewFrame *frame)
+{
+ const gchar *entry_text;
+ gboolean moved;
+ gboolean moved_offset;
+ gint line;
+ gint offset_line = 0;
+ gint line_offset = 0;
+ gchar **split_text = NULL;
+ const gchar *text;
+ GtkTextIter iter;
+
+ entry_text = gtk_entry_get_text (GTK_ENTRY (frame->search_entry));
+
+ if (entry_text[0] == '\0')
+ {
+ return;
+ }
+
+ get_iter_at_start_mark (frame, &iter);
+
+ split_text = g_strsplit (entry_text, ":", -1);
+
+ if (g_strv_length (split_text) > 1)
+ {
+ text = split_text[0];
+ }
+ else
+ {
+ text = entry_text;
+ }
+
+ if (text[0] == '-')
+ {
+ gint cur_line = gtk_text_iter_get_line (&iter);
+
+ if (text[1] != '\0')
+ {
+ offset_line = MAX (atoi (text + 1), 0);
+ }
+
+ line = MAX (cur_line - offset_line, 0);
+ }
+ else if (entry_text[0] == '+')
+ {
+ gint cur_line = gtk_text_iter_get_line (&iter);
+
+ if (text[1] != '\0')
+ {
+ offset_line = MAX (atoi (text + 1), 0);
+ }
+
+ line = cur_line + offset_line;
+ }
+ else
+ {
+ line = MAX (atoi (text) - 1, 0);
+ }
+
+ if (split_text[1] != NULL)
+ {
+ line_offset = atoi (split_text[1]);
+ }
+
+ g_strfreev (split_text);
+
+ moved = tepl_view_goto_line (TEPL_VIEW (frame->view), line);
+ moved_offset = tepl_view_goto_line_offset (TEPL_VIEW (frame->view), line, line_offset);
+
+ if (!moved || !moved_offset)
+ {
+ set_search_state (frame, SEARCH_STATE_NOT_FOUND);
+ }
+ else
+ {
+ set_search_state (frame, SEARCH_STATE_NORMAL);
+ }
+}
+
+static void
+search_entry_changed_cb (GtkEntry *entry,
+ GeditViewFrame *frame)
+{
+ renew_flush_timeout (frame);
+
+ if (frame->search_mode == SEARCH)
+ {
+ update_search_text (frame);
+ start_search (frame);
+ }
+ else
+ {
+ update_goto_line (frame);
+ }
+}
+
+static gboolean
+search_entry_focus_out_event (GtkWidget *widget,
+ GdkEventFocus *event,
+ GeditViewFrame *frame)
+{
+ hide_search_widget (frame, FALSE);
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+mark_set_cb (GtkTextBuffer *buffer,
+ GtkTextIter *location,
+ GtkTextMark *mark,
+ GeditViewFrame *frame)
+{
+ GtkTextMark *insert;
+ GtkTextMark *selection_bound;
+
+ insert = gtk_text_buffer_get_insert (buffer);
+ selection_bound = gtk_text_buffer_get_selection_bound (buffer);
+
+ if (mark == insert || mark == selection_bound)
+ {
+ install_update_entry_tag_idle (frame);
+ }
+}
+
+static gboolean
+get_selected_text (GtkTextBuffer *doc,
+ gchar **selected_text,
+ gint *len)
+{
+ GtkTextIter start;
+ GtkTextIter end;
+
+ g_return_val_if_fail (selected_text != NULL, FALSE);
+ g_return_val_if_fail (*selected_text == NULL, FALSE);
+
+ if (!gtk_text_buffer_get_selection_bounds (doc, &start, &end))
+ {
+ if (len != NULL)
+ {
+ *len = 0;
+ }
+
+ return FALSE;
+ }
+
+ *selected_text = gtk_text_buffer_get_slice (doc, &start, &end, TRUE);
+
+ if (len != NULL)
+ {
+ *len = g_utf8_strlen (*selected_text, -1);
+ }
+
+ return TRUE;
+}
+
+static void
+init_search_entry (GeditViewFrame *frame)
+{
+ if (frame->search_mode == GOTO_LINE)
+ {
+ gint line;
+ gchar *line_str;
+ GtkTextIter iter;
+
+ get_iter_at_start_mark (frame, &iter);
+
+ line = gtk_text_iter_get_line (&iter);
+
+ line_str = g_strdup_printf ("%d", line + 1);
+
+ gtk_entry_set_text (GTK_ENTRY (frame->search_entry), line_str);
+
+ gtk_editable_select_region (GTK_EDITABLE (frame->search_entry),
+ 0, -1);
+
+ g_free (line_str);
+ }
+ else
+ {
+ /* SEARCH mode */
+ GtkTextBuffer *buffer;
+ gboolean selection_exists;
+ gchar *search_text = NULL;
+ gint selection_len = 0;
+ GtkSourceSearchContext *search_context;
+
+ if (frame->search_settings == NULL)
+ {
+ frame->search_settings = gtk_source_search_settings_new ();
+ gtk_source_search_settings_set_wrap_around (frame->search_settings, TRUE);
+ }
+
+ g_clear_object (&frame->old_search_settings);
+ frame->old_search_settings = copy_search_settings (frame->search_settings);
+
+ g_free (frame->old_search_text);
+ frame->old_search_text = NULL;
+
+ if (frame->search_text != NULL)
+ {
+ frame->old_search_text = g_strdup (frame->search_text);
+ }
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+
+ search_context = get_search_context (frame);
+
+ if (search_context == NULL)
+ {
+ search_context = gtk_source_search_context_new (GTK_SOURCE_BUFFER (buffer),
+ frame->search_settings);
+
+ gedit_document_set_search_context (GEDIT_DOCUMENT (buffer),
+ search_context);
+
+ g_signal_connect_swapped (search_context,
+ "notify::occurrences-count",
+ G_CALLBACK (install_update_entry_tag_idle),
+ frame);
+
+ g_object_unref (search_context);
+ }
+
+ selection_exists = get_selected_text (buffer,
+ &search_text,
+ &selection_len);
+
+ if (selection_exists && (search_text != NULL) && (selection_len <= 160))
+ {
+ gchar *search_text_escaped;
+
+ if (gtk_source_search_settings_get_regex_enabled (frame->search_settings))
+ {
+ search_text_escaped = g_regex_escape_string (search_text, -1);
+ }
+ else
+ {
+ search_text_escaped = gtk_source_utils_escape_search_text (search_text);
+ }
+
+ if (g_strcmp0 (search_text_escaped, frame->search_text) == 0)
+ {
+ /* The search text is the same, no need to
+ * trigger the search again. We prefer to select
+ * the text in the search entry, so the user can
+ * easily search something else.
+ */
+ g_signal_handler_block (frame->search_entry,
+ frame->search_entry_changed_id);
+
+ gtk_entry_set_text (GTK_ENTRY (frame->search_entry),
+ search_text_escaped);
+
+ gtk_editable_select_region (GTK_EDITABLE (frame->search_entry),
+ 0, -1);
+
+ g_signal_handler_unblock (frame->search_entry,
+ frame->search_entry_changed_id);
+ }
+ else
+ {
+ /* search_text_escaped is new, so we trigger the
+ * search (by not blocking the signal), and we
+ * don't select the text in the search entry
+ * because the user wants to search for
+ * search_text_escaped, not for something else.
+ */
+ gtk_entry_set_text (GTK_ENTRY (frame->search_entry),
+ search_text_escaped);
+
+ gtk_editable_set_position (GTK_EDITABLE (frame->search_entry), -1);
+ }
+
+ g_free (search_text_escaped);
+ }
+ else if (frame->search_text != NULL)
+ {
+ g_signal_handler_block (frame->search_entry,
+ frame->search_entry_changed_id);
+
+ gtk_entry_set_text (GTK_ENTRY (frame->search_entry),
+ frame->search_text);
+
+ gtk_editable_select_region (GTK_EDITABLE (frame->search_entry),
+ 0, -1);
+
+ g_signal_handler_unblock (frame->search_entry,
+ frame->search_entry_changed_id);
+ }
+
+ g_free (search_text);
+ }
+}
+
+static void
+start_interactive_search_real (GeditViewFrame *frame,
+ SearchMode request_search_mode)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+
+ if (gtk_revealer_get_reveal_child (frame->revealer))
+ {
+ if (frame->search_mode != request_search_mode)
+ {
+ hide_search_widget (frame, TRUE);
+ }
+ else
+ {
+ gtk_editable_select_region (GTK_EDITABLE (frame->search_entry),
+ 0, -1);
+ return;
+ }
+ }
+
+ frame->search_mode = request_search_mode;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+
+ if (frame->search_mode == SEARCH)
+ {
+ gtk_text_buffer_get_selection_bounds (buffer, &iter, NULL);
+ }
+ else
+ {
+ GtkTextMark *mark = gtk_text_buffer_get_insert (buffer);
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark);
+ }
+
+ if (frame->start_mark != NULL)
+ {
+ gtk_text_buffer_delete_mark (buffer, frame->start_mark);
+ }
+
+ frame->start_mark = gtk_text_buffer_create_mark (buffer, NULL, &iter, FALSE);
+
+ gtk_revealer_set_reveal_child (frame->revealer, TRUE);
+
+ /* NOTE: we must be very careful here to not have any text before
+ focusing the entry because when the entry is focused the text is
+ selected, and gtk+ doesn't allow us to have more than one selection
+ active */
+ g_signal_handler_block (frame->search_entry,
+ frame->search_entry_changed_id);
+
+ gtk_entry_set_text (GTK_ENTRY (frame->search_entry), "");
+
+ g_signal_handler_unblock (frame->search_entry,
+ frame->search_entry_changed_id);
+
+ gtk_widget_grab_focus (GTK_WIDGET (frame->search_entry));
+
+ customize_for_search_mode (frame);
+ init_search_entry (frame);
+
+ /* Manage the scroll also for the view */
+ frame->view_scroll_event_id =
+ g_signal_connect (frame->view, "scroll-event",
+ G_CALLBACK (search_widget_scroll_event),
+ frame);
+
+ renew_flush_timeout (frame);
+
+ install_update_entry_tag_idle (frame);
+}
+
+static void
+gedit_view_frame_class_init (GeditViewFrameClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = gedit_view_frame_dispose;
+ object_class->finalize = gedit_view_frame_finalize;
+
+ /* Bind class to template */
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/gedit/ui/gedit-view-frame.ui");
+ gtk_widget_class_bind_template_child (widget_class, GeditViewFrame, view);
+ gtk_widget_class_bind_template_child (widget_class, GeditViewFrame, revealer);
+ gtk_widget_class_bind_template_child (widget_class, GeditViewFrame, search_entry);
+ gtk_widget_class_bind_template_child (widget_class, GeditViewFrame, go_up_button);
+ gtk_widget_class_bind_template_child (widget_class, GeditViewFrame, go_down_button);
+}
+
+static GMountOperation *
+view_frame_mount_operation_factory (GtkSourceFile *file,
+ gpointer user_data)
+{
+ GtkWidget *view_frame = user_data;
+ GtkWidget *window = gtk_widget_get_toplevel (view_frame);
+
+ return gtk_mount_operation_new (GTK_WINDOW (window));
+}
+
+static void
+gedit_view_frame_init (GeditViewFrame *frame)
+{
+ GeditDocument *doc;
+ GtkSourceFile *file;
+
+ gedit_debug (DEBUG_WINDOW);
+
+ gtk_widget_init_template (GTK_WIDGET (frame));
+
+ doc = get_document (frame);
+ file = gedit_document_get_file (doc);
+
+ gtk_source_file_set_mount_operation_factory (file,
+ view_frame_mount_operation_factory,
+ frame,
+ NULL);
+
+ frame->entry_tag = gd_tagged_entry_tag_new ("");
+
+ gd_tagged_entry_tag_set_style (frame->entry_tag,
+ "gedit-search-entry-occurrences-tag");
+
+ gd_tagged_entry_tag_set_has_close_button (frame->entry_tag, FALSE);
+
+ gtk_widget_set_margin_end (GTK_WIDGET (frame->revealer),
+ SEARCH_POPUP_MARGIN);
+
+ g_signal_connect (doc,
+ "mark-set",
+ G_CALLBACK (mark_set_cb),
+ frame);
+
+ g_signal_connect (frame->revealer,
+ "key-press-event",
+ G_CALLBACK (search_widget_key_press_event),
+ frame);
+
+ g_signal_connect (frame->revealer,
+ "scroll-event",
+ G_CALLBACK (search_widget_scroll_event),
+ frame);
+
+ g_signal_connect (frame->search_entry,
+ "populate-popup",
+ G_CALLBACK (search_entry_populate_popup),
+ frame);
+
+ g_signal_connect (frame->search_entry,
+ "icon-release",
+ G_CALLBACK (search_entry_icon_release),
+ frame);
+
+ g_signal_connect (frame->search_entry,
+ "activate",
+ G_CALLBACK (search_entry_activate),
+ frame);
+
+ g_signal_connect (frame->search_entry,
+ "insert-text",
+ G_CALLBACK (search_entry_insert_text),
+ frame);
+
+ g_signal_connect (frame->search_entry,
+ "stop-search",
+ G_CALLBACK (search_entry_escaped),
+ frame);
+
+ g_signal_connect (frame->search_entry,
+ "next-match",
+ G_CALLBACK (search_entry_next_match),
+ frame);
+
+ g_signal_connect (frame->search_entry,
+ "previous-match",
+ G_CALLBACK (search_entry_previous_match),
+ frame);
+
+ frame->search_entry_changed_id =
+ g_signal_connect (frame->search_entry,
+ "changed",
+ G_CALLBACK (search_entry_changed_cb),
+ frame);
+
+ frame->search_entry_focus_out_id =
+ g_signal_connect (frame->search_entry,
+ "focus-out-event",
+ G_CALLBACK (search_entry_focus_out_event),
+ frame);
+
+ g_signal_connect_swapped (frame->go_up_button,
+ "clicked",
+ G_CALLBACK (backward_search),
+ frame);
+
+ g_signal_connect_swapped (frame->go_down_button,
+ "clicked",
+ G_CALLBACK (forward_search),
+ frame);
+}
+
+GeditViewFrame *
+gedit_view_frame_new (void)
+{
+ return g_object_new (GEDIT_TYPE_VIEW_FRAME, NULL);
+}
+
+GeditView *
+gedit_view_frame_get_view (GeditViewFrame *frame)
+{
+ g_return_val_if_fail (GEDIT_IS_VIEW_FRAME (frame), NULL);
+
+ return frame->view;
+}
+
+void
+gedit_view_frame_popup_search (GeditViewFrame *frame)
+{
+ g_return_if_fail (GEDIT_IS_VIEW_FRAME (frame));
+
+ start_interactive_search_real (frame, SEARCH);
+}
+
+void
+gedit_view_frame_popup_goto_line (GeditViewFrame *frame)
+{
+ g_return_if_fail (GEDIT_IS_VIEW_FRAME (frame));
+
+ start_interactive_search_real (frame, GOTO_LINE);
+}
+
+void
+gedit_view_frame_clear_search (GeditViewFrame *frame)
+{
+ g_return_if_fail (GEDIT_IS_VIEW_FRAME (frame));
+
+ g_signal_handler_block (frame->search_entry,
+ frame->search_entry_changed_id);
+
+ gtk_entry_set_text (GTK_ENTRY (frame->search_entry), "");
+
+ g_signal_handler_unblock (frame->search_entry,
+ frame->search_entry_changed_id);
+
+ gtk_widget_grab_focus (GTK_WIDGET (frame->view));
+}
diff --git a/gedit/gedit-view-frame.h b/gedit/gedit-view-frame.h
new file mode 100644
index 0000000..78ac469
--- /dev/null
+++ b/gedit/gedit-view-frame.h
@@ -0,0 +1,45 @@
+/*
+ * gedit-view-frame.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Ignacio Casal Quinteiro
+ *
+ * gedit 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.
+ *
+ * gedit 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 gedit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_VIEW_FRAME_H
+#define GEDIT_VIEW_FRAME_H
+
+#include <gtk/gtk.h>
+#include "gedit-document.h"
+#include "gedit-view.h"
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_VIEW_FRAME (gedit_view_frame_get_type ())
+G_DECLARE_FINAL_TYPE (GeditViewFrame, gedit_view_frame, GEDIT, VIEW_FRAME, GtkOverlay)
+
+GeditViewFrame *gedit_view_frame_new (void);
+
+GeditView *gedit_view_frame_get_view (GeditViewFrame *frame);
+
+void gedit_view_frame_popup_search (GeditViewFrame *frame);
+
+void gedit_view_frame_popup_goto_line (GeditViewFrame *frame);
+
+void gedit_view_frame_clear_search (GeditViewFrame *frame);
+
+G_END_DECLS
+
+#endif /* GEDIT_VIEW_FRAME_H */
diff --git a/gedit/gedit-view.c b/gedit/gedit-view.c
new file mode 100644
index 0000000..f74f16d
--- /dev/null
+++ b/gedit/gedit-view.c
@@ -0,0 +1,605 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2002 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2003-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 "gedit-view.h"
+#include <libpeas/peas-extension-set.h>
+#include "gedit-view-activatable.h"
+#include "gedit-plugins-engine.h"
+#include "gedit-debug.h"
+#include "gedit-utils.h"
+#include "gedit-settings.h"
+
+struct _GeditViewPrivate
+{
+ PeasExtensionSet *extensions;
+
+ gchar *direct_save_uri;
+
+ TeplSignalGroup *file_signal_group;
+};
+
+enum
+{
+ TARGET_URI_LIST = 100,
+ TARGET_XDNDDIRECTSAVE
+};
+
+enum
+{
+ SIGNAL_DROP_URIS,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+G_DEFINE_TYPE_WITH_PRIVATE (GeditView, gedit_view, TEPL_TYPE_VIEW)
+
+static void
+update_editable (GeditView *view)
+{
+ GeditDocument *doc;
+ GtkSourceFile *file;
+
+ doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+ file = gedit_document_get_file (doc);
+
+ gtk_text_view_set_editable (GTK_TEXT_VIEW (view),
+ !gtk_source_file_is_readonly (file));
+}
+
+static void
+file_read_only_notify_cb (GtkSourceFile *file,
+ GParamSpec *pspec,
+ GeditView *view)
+{
+ update_editable (view);
+}
+
+static void
+buffer_changed (GeditView *view)
+{
+ GeditDocument *doc;
+ GtkSourceFile *file;
+
+ doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+ file = gedit_document_get_file (doc);
+
+ tepl_signal_group_clear (&view->priv->file_signal_group);
+ view->priv->file_signal_group = tepl_signal_group_new (G_OBJECT (file));
+
+ tepl_signal_group_add (view->priv->file_signal_group,
+ g_signal_connect (file,
+ "notify::read-only",
+ G_CALLBACK (file_read_only_notify_cb),
+ view));
+
+ update_editable (view);
+}
+
+static void
+buffer_notify_cb (GeditView *view,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ buffer_changed (view);
+}
+
+static void
+gedit_view_init (GeditView *view)
+{
+ GtkTargetList *target_list;
+ GtkStyleContext *style_context;
+
+ gedit_debug (DEBUG_VIEW);
+
+ view->priv = gedit_view_get_instance_private (view);
+
+ /* Drag and drop support */
+ view->priv->direct_save_uri = NULL;
+ target_list = gtk_drag_dest_get_target_list (GTK_WIDGET (view));
+
+ if (target_list != NULL)
+ {
+ gtk_target_list_add (target_list,
+ gdk_atom_intern ("XdndDirectSave0", FALSE),
+ 0,
+ TARGET_XDNDDIRECTSAVE);
+ gtk_target_list_add_uri_targets (target_list, TARGET_URI_LIST);
+ }
+
+ /* GeditViewActivatable */
+ view->priv->extensions =
+ peas_extension_set_new (PEAS_ENGINE (gedit_plugins_engine_get_default ()),
+ GEDIT_TYPE_VIEW_ACTIVATABLE,
+ "view", view,
+ NULL);
+
+ /* Act on buffer changes */
+ buffer_changed (view);
+ g_signal_connect (view,
+ "notify::buffer",
+ G_CALLBACK (buffer_notify_cb),
+ NULL);
+
+ /* CSS stuff */
+ style_context = gtk_widget_get_style_context (GTK_WIDGET (view));
+ gtk_style_context_add_class (style_context, "gedit-view");
+}
+
+static void
+gedit_view_dispose (GObject *object)
+{
+ GeditView *view = GEDIT_VIEW (object);
+
+ g_clear_object (&view->priv->extensions);
+ tepl_signal_group_clear (&view->priv->file_signal_group);
+
+ /* Disconnect notify buffer because the destroy of the textview will set
+ * the buffer to NULL, and we call get_buffer in the notify which would
+ * reinstate a buffer which we don't want.
+ * There is no problem calling g_signal_handlers_disconnect_by_func()
+ * several times (if dispose() is called several times).
+ */
+ g_signal_handlers_disconnect_by_func (view, buffer_notify_cb, NULL);
+
+ G_OBJECT_CLASS (gedit_view_parent_class)->dispose (object);
+}
+
+static void
+update_font (GeditView *view)
+{
+ TeplSettings *settings;
+ gchar *selected_font;
+
+ settings = tepl_settings_get_singleton ();
+
+ selected_font = tepl_settings_get_selected_font (settings);
+ tepl_utils_override_font_string (GTK_WIDGET (view), selected_font);
+ g_free (selected_font);
+}
+
+static void
+font_changed_cb (TeplSettings *settings,
+ GeditView *view)
+{
+ update_font (view);
+}
+
+static void
+gedit_view_constructed (GObject *object)
+{
+ GeditView *view = GEDIT_VIEW (object);
+ GeditSettings *gedit_settings;
+ TeplSettings *tepl_settings;
+ GSettings *editor_settings;
+
+ G_OBJECT_CLASS (gedit_view_parent_class)->constructed (object);
+
+ gedit_settings = _gedit_settings_get_singleton ();
+ tepl_settings = tepl_settings_get_singleton ();
+
+ editor_settings = _gedit_settings_peek_editor_settings (gedit_settings);
+
+ update_font (view);
+ g_signal_connect_object (tepl_settings,
+ "font-changed",
+ G_CALLBACK (font_changed_cb),
+ view,
+ 0);
+
+ g_settings_bind (editor_settings, GEDIT_SETTINGS_DISPLAY_LINE_NUMBERS,
+ view, "show-line-numbers",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY);
+
+ g_settings_bind (editor_settings, GEDIT_SETTINGS_AUTO_INDENT,
+ view, "auto-indent",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY);
+
+ g_settings_bind (editor_settings, GEDIT_SETTINGS_TABS_SIZE,
+ view, "tab-width",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY);
+
+ g_settings_bind (editor_settings, GEDIT_SETTINGS_INSERT_SPACES,
+ view, "insert-spaces-instead-of-tabs",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY);
+
+ g_settings_bind (editor_settings, GEDIT_SETTINGS_DISPLAY_RIGHT_MARGIN,
+ view, "show-right-margin",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY);
+
+ g_settings_bind (editor_settings, GEDIT_SETTINGS_BACKGROUND_PATTERN,
+ view, "background-pattern",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY);
+
+ g_settings_bind (editor_settings, GEDIT_SETTINGS_RIGHT_MARGIN_POSITION,
+ view, "right-margin-position",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY);
+
+ g_settings_bind (editor_settings, GEDIT_SETTINGS_HIGHLIGHT_CURRENT_LINE,
+ view, "highlight-current-line",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY);
+
+ g_settings_bind (editor_settings, GEDIT_SETTINGS_WRAP_MODE,
+ view, "wrap-mode",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY);
+
+ g_settings_bind (editor_settings, GEDIT_SETTINGS_SMART_HOME_END,
+ view, "smart-home-end",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY);
+}
+
+static GdkAtom
+drag_get_uri_target (GtkWidget *widget,
+ GdkDragContext *context)
+{
+ GdkAtom target;
+ GtkTargetList *target_list;
+
+ target_list = gtk_target_list_new (NULL, 0);
+ gtk_target_list_add_uri_targets (target_list, 0);
+
+ target = gtk_drag_dest_find_target (widget, context, target_list);
+ gtk_target_list_unref (target_list);
+
+ return target;
+}
+
+static gboolean
+gedit_view_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint timestamp)
+{
+ gboolean drop_zone;
+
+ /* Chain up to allow textview to scroll and position dnd mark, note
+ * that this needs to be checked if gtksourceview or gtktextview
+ * changes drag_motion behaviour.
+ */
+ drop_zone = GTK_WIDGET_CLASS (gedit_view_parent_class)->drag_motion (widget,
+ context,
+ x, y,
+ timestamp);
+
+ /* If this is a URL, deal with it here */
+ if (drag_get_uri_target (widget, context) != GDK_NONE)
+ {
+ gdk_drag_status (context,
+ gdk_drag_context_get_suggested_action (context),
+ timestamp);
+ drop_zone = TRUE;
+ }
+
+ return drop_zone;
+}
+
+static void
+gedit_view_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint timestamp)
+{
+ /* If this is an URL emit SIGNAL_DROP_URIS, otherwise chain up the
+ * signal.
+ */
+ switch (info)
+ {
+ case TARGET_URI_LIST:
+ {
+ gchar **uri_list;
+
+ uri_list = gedit_utils_drop_get_uris (selection_data);
+
+ if (uri_list != NULL)
+ {
+ g_signal_emit (widget, signals[SIGNAL_DROP_URIS], 0, uri_list);
+ g_strfreev (uri_list);
+
+ gtk_drag_finish (context, TRUE, FALSE, timestamp);
+ }
+
+ break;
+
+ }
+ case TARGET_XDNDDIRECTSAVE:
+ {
+ GeditView *view;
+
+ view = GEDIT_VIEW (widget);
+
+ /* 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' &&
+ view->priv->direct_save_uri != NULL)
+ {
+ gchar **uris;
+
+ uris = g_new (gchar *, 2);
+ uris[0] = view->priv->direct_save_uri;
+ uris[1] = NULL;
+ g_signal_emit (widget, signals[SIGNAL_DROP_URIS], 0, uris);
+ g_free (uris);
+ }
+
+ g_free (view->priv->direct_save_uri);
+ view->priv->direct_save_uri = NULL;
+
+ gtk_drag_finish (context, TRUE, FALSE, timestamp);
+
+ break;
+ }
+ default:
+ {
+ GTK_WIDGET_CLASS (gedit_view_parent_class)->drag_data_received (widget,
+ context,
+ x, y,
+ selection_data,
+ info,
+ timestamp);
+ break;
+ }
+ }
+}
+
+static gboolean
+gedit_view_drag_drop (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint timestamp)
+{
+ gboolean drop_zone;
+ GdkAtom target;
+ guint info;
+ gboolean found;
+ GtkTargetList *target_list;
+
+ target_list = gtk_drag_dest_get_target_list (widget);
+ target = gtk_drag_dest_find_target (widget, context, target_list);
+ found = gtk_target_list_find (target_list, target, &info);
+
+ if (found && (info == TARGET_URI_LIST || info == TARGET_XDNDDIRECTSAVE))
+ {
+ if (info == TARGET_XDNDDIRECTSAVE)
+ {
+ gchar *uri;
+ uri = gedit_utils_set_direct_save_filename (context);
+
+ if (uri != NULL)
+ {
+ GeditView *view = GEDIT_VIEW (widget);
+ g_free (view->priv->direct_save_uri);
+ view->priv->direct_save_uri = uri;
+ }
+ }
+
+ gtk_drag_get_data (widget, context, target, timestamp);
+ drop_zone = TRUE;
+ }
+ else
+ {
+ /* Chain up */
+ drop_zone = GTK_WIDGET_CLASS (gedit_view_parent_class)->drag_drop (widget,
+ context,
+ x, y,
+ timestamp);
+ }
+
+ return drop_zone;
+}
+
+static void
+extension_added (PeasExtensionSet *extensions,
+ PeasPluginInfo *info,
+ PeasExtension *exten,
+ GeditView *view)
+{
+ gedit_view_activatable_activate (GEDIT_VIEW_ACTIVATABLE (exten));
+}
+
+static void
+extension_removed (PeasExtensionSet *extensions,
+ PeasPluginInfo *info,
+ PeasExtension *exten,
+ GeditView *view)
+{
+ gedit_view_activatable_deactivate (GEDIT_VIEW_ACTIVATABLE (exten));
+}
+
+static void
+gedit_view_realize (GtkWidget *widget)
+{
+ GeditView *view = GEDIT_VIEW (widget);
+
+ GTK_WIDGET_CLASS (gedit_view_parent_class)->realize (widget);
+
+ g_signal_connect (view->priv->extensions,
+ "extension-added",
+ G_CALLBACK (extension_added),
+ view);
+
+ g_signal_connect (view->priv->extensions,
+ "extension-removed",
+ G_CALLBACK (extension_removed),
+ view);
+
+ /* We only activate the extensions when the view is realized,
+ * because most plugins will expect this behaviour, and we won't
+ * change the buffer later anyway.
+ */
+ peas_extension_set_foreach (view->priv->extensions,
+ (PeasExtensionSetForeachFunc) extension_added,
+ view);
+}
+
+static void
+gedit_view_unrealize (GtkWidget *widget)
+{
+ GeditView *view = GEDIT_VIEW (widget);
+
+ g_signal_handlers_disconnect_by_func (view->priv->extensions, extension_added, view);
+ g_signal_handlers_disconnect_by_func (view->priv->extensions, extension_removed, view);
+
+ /* We need to deactivate the extension on unrealize because it is not
+ * mandatory that a view has been realized when we dispose it, leading
+ * to deactivating the plugin without being activated.
+ */
+ peas_extension_set_foreach (view->priv->extensions,
+ (PeasExtensionSetForeachFunc) extension_removed,
+ view);
+
+ GTK_WIDGET_CLASS (gedit_view_parent_class)->unrealize (widget);
+}
+
+static GtkTextBuffer *
+gedit_view_create_buffer (GtkTextView *text_view)
+{
+ return GTK_TEXT_BUFFER (gedit_document_new ());
+}
+
+static void
+gedit_view_class_init (GeditViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkTextViewClass *text_view_class = GTK_TEXT_VIEW_CLASS (klass);
+ GtkBindingSet *binding_set;
+
+ object_class->dispose = gedit_view_dispose;
+ object_class->constructed = gedit_view_constructed;
+
+ /* Override the gtk_text_view_drag_motion and drag_drop
+ * functions to get URIs
+ *
+ * If the mime type is text/uri-list, then we will accept
+ * the potential drop, or request the data (depending on the
+ * function).
+ *
+ * If the drag context has any other mime type, then pass the
+ * information onto the GtkTextView's standard handlers.
+ *
+ * See bug #89881 for details
+ */
+ widget_class->drag_motion = gedit_view_drag_motion;
+ widget_class->drag_data_received = gedit_view_drag_data_received;
+ widget_class->drag_drop = gedit_view_drag_drop;
+
+ widget_class->realize = gedit_view_realize;
+ widget_class->unrealize = gedit_view_unrealize;
+
+ text_view_class->create_buffer = gedit_view_create_buffer;
+
+ /**
+ * GeditView::drop-uris:
+ * @view: a #GeditView.
+ * @uri_list: a %NULL-terminated list of URIs.
+ *
+ * The #GeditView::drop-uris signal allows plugins to intercept the
+ * default drag-and-drop behaviour of 'text/uri-list'. #GeditView
+ * handles drag-and-drop in the default handlers of
+ * #GtkWidget::drag-drop, #GtkWidget::drag-motion and
+ * #GtkWidget::drag-data-received. The view emits the
+ * #GeditView::drop-uris signal from #GtkWidget::drag-data-received if
+ * valid URIs have been dropped. Plugins should connect to
+ * #GtkWidget::drag-motion, #GtkWidget::drag-drop and
+ * #GtkWidget::drag-data-received to change this default behaviour. They
+ * should NOT use this signal because this will not prevent gedit from
+ * loading the URI.
+ */
+ signals[SIGNAL_DROP_URIS] =
+ g_signal_new ("drop-uris",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GeditViewClass, drop_uris),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_STRV);
+
+ /* FIXME: some of these bindings - especially for the "change-case" -
+ * could be moved to a plugin that enables more advanced keyboard
+ * shortcuts. Enabling too many keyboard shortcuts by default makes the
+ * user experience worse, because when mistyping a key it could trigger
+ * an unknown/unexpected action, so it's a bit a WTF moment for the
+ * user. gedit must not become like Emacs or Vim. More advanced stuff
+ * are put in plugins. Also, the "change-case" is available in the
+ * right-click menu, so it's already easily accessible.
+ */
+ binding_set = gtk_binding_set_by_class (klass);
+
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_KEY_d,
+ GDK_CONTROL_MASK,
+ "delete-from-cursor", 2,
+ G_TYPE_ENUM, GTK_DELETE_PARAGRAPHS,
+ G_TYPE_INT, 1);
+
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_KEY_u,
+ GDK_CONTROL_MASK,
+ "change-case", 1,
+ G_TYPE_ENUM, GTK_SOURCE_CHANGE_CASE_UPPER);
+
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_KEY_l,
+ GDK_CONTROL_MASK,
+ "change-case", 1,
+ G_TYPE_ENUM, GTK_SOURCE_CHANGE_CASE_LOWER);
+
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_KEY_asciitilde,
+ GDK_CONTROL_MASK,
+ "change-case", 1,
+ G_TYPE_ENUM, GTK_SOURCE_CHANGE_CASE_TOGGLE);
+}
+
+/**
+ * gedit_view_new:
+ * @doc: a #GeditDocument
+ *
+ * Creates a new #GeditView object displaying the @doc document.
+ * @doc cannot be %NULL.
+ *
+ * Returns: a new #GeditView.
+ */
+GtkWidget *
+gedit_view_new (GeditDocument *doc)
+{
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL);
+
+ return g_object_new (GEDIT_TYPE_VIEW,
+ "buffer", doc,
+ NULL);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-view.h b/gedit/gedit-view.h
new file mode 100644
index 0000000..c05d685
--- /dev/null
+++ b/gedit/gedit-view.h
@@ -0,0 +1,67 @@
+/*
+ * This file is part of gedit
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-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/>.
+ */
+
+#ifndef GEDIT_VIEW_H
+#define GEDIT_VIEW_H
+
+#include <gedit/gedit-document.h>
+#include <tepl/tepl.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_VIEW (gedit_view_get_type ())
+#define GEDIT_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_VIEW, GeditView))
+#define GEDIT_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_VIEW, GeditViewClass))
+#define GEDIT_IS_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_VIEW))
+#define GEDIT_IS_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_VIEW))
+#define GEDIT_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_VIEW, GeditViewClass))
+
+typedef struct _GeditView GeditView;
+typedef struct _GeditViewClass GeditViewClass;
+typedef struct _GeditViewPrivate GeditViewPrivate;
+
+struct _GeditView
+{
+ TeplView view;
+
+ /*< private >*/
+ GeditViewPrivate *priv;
+};
+
+struct _GeditViewClass
+{
+ TeplViewClass parent_class;
+
+ void (*drop_uris) (GeditView *view,
+ gchar **uri_list);
+
+ gpointer padding;
+};
+
+GType gedit_view_get_type (void);
+
+GtkWidget * gedit_view_new (GeditDocument *doc);
+
+G_END_DECLS
+
+#endif /* GEDIT_VIEW_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-window-activatable.c b/gedit/gedit-window-activatable.c
new file mode 100644
index 0000000..37a6170
--- /dev/null
+++ b/gedit/gedit-window-activatable.c
@@ -0,0 +1,120 @@
+/*
+ * gedit-window-activatable.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 Steve Frécinaux
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gedit-window-activatable.h"
+
+#include <string.h>
+
+#include "gedit-window.h"
+
+/**
+ * SECTION:gedit-window-activatable
+ * @short_description: Interface for activatable extensions on windows
+ * @see_also: #PeasExtensionSet
+ *
+ * #GeditWindowActivatable is an interface which should be implemented by
+ * extensions that should be activated on a gedit main window.
+ **/
+
+G_DEFINE_INTERFACE(GeditWindowActivatable, gedit_window_activatable, G_TYPE_OBJECT)
+
+static void
+gedit_window_activatable_default_init (GeditWindowActivatableInterface *iface)
+{
+ /**
+ * GeditWindowActivatable:window:
+ *
+ * The window property contains the gedit window for this
+ * #GeditWindowActivatable instance.
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_object ("window",
+ "Window",
+ "The gedit window",
+ GEDIT_TYPE_WINDOW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+}
+
+/**
+ * gedit_window_activatable_activate:
+ * @activatable: A #GeditWindowActivatable.
+ *
+ * Activates the extension on the window property.
+ */
+void
+gedit_window_activatable_activate (GeditWindowActivatable *activatable)
+{
+ GeditWindowActivatableInterface *iface;
+
+ g_return_if_fail (GEDIT_IS_WINDOW_ACTIVATABLE (activatable));
+
+ iface = GEDIT_WINDOW_ACTIVATABLE_GET_IFACE (activatable);
+ if (iface->activate != NULL)
+ {
+ iface->activate (activatable);
+ }
+}
+
+/**
+ * gedit_window_activatable_deactivate:
+ * @activatable: A #GeditWindowActivatable.
+ *
+ * Deactivates the extension on the window property.
+ */
+void
+gedit_window_activatable_deactivate (GeditWindowActivatable *activatable)
+{
+ GeditWindowActivatableInterface *iface;
+
+ g_return_if_fail (GEDIT_IS_WINDOW_ACTIVATABLE (activatable));
+
+ iface = GEDIT_WINDOW_ACTIVATABLE_GET_IFACE (activatable);
+ if (iface->deactivate != NULL)
+ {
+ iface->deactivate (activatable);
+ }
+}
+
+/**
+ * gedit_window_activatable_update_state:
+ * @activatable: A #GeditWindowActivatable.
+ *
+ * Triggers an update of the extension internal state to take into account
+ * state changes in the window, due to some event or user action.
+ */
+void
+gedit_window_activatable_update_state (GeditWindowActivatable *activatable)
+{
+ GeditWindowActivatableInterface *iface;
+
+ g_return_if_fail (GEDIT_IS_WINDOW_ACTIVATABLE (activatable));
+
+ iface = GEDIT_WINDOW_ACTIVATABLE_GET_IFACE (activatable);
+ if (iface->update_state != NULL)
+ {
+ iface->update_state (activatable);
+ }
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-window-activatable.h b/gedit/gedit-window-activatable.h
new file mode 100644
index 0000000..e0ab35f
--- /dev/null
+++ b/gedit/gedit-window-activatable.h
@@ -0,0 +1,49 @@
+/*
+ * gedit-window-activatable.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Steve Frécinaux
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_WINDOW_ACTIVATABLE_H
+#define GEDIT_WINDOW_ACTIVATABLE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_WINDOW_ACTIVATABLE (gedit_window_activatable_get_type ())
+
+G_DECLARE_INTERFACE (GeditWindowActivatable, gedit_window_activatable, GEDIT, WINDOW_ACTIVATABLE, GObject)
+
+struct _GeditWindowActivatableInterface
+{
+ GTypeInterface g_iface;
+
+ /* Virtual public methods */
+ void (*activate) (GeditWindowActivatable *activatable);
+ void (*deactivate) (GeditWindowActivatable *activatable);
+ void (*update_state) (GeditWindowActivatable *activatable);
+};
+
+void gedit_window_activatable_activate (GeditWindowActivatable *activatable);
+void gedit_window_activatable_deactivate (GeditWindowActivatable *activatable);
+void gedit_window_activatable_update_state (GeditWindowActivatable *activatable);
+
+G_END_DECLS
+
+#endif /* GEDIT_WINDOW_ACTIVATABLE_H */
+/* ex:set ts=8 noet: */
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: */
diff --git a/gedit/gedit-window.h b/gedit/gedit-window.h
new file mode 100644
index 0000000..74195c1
--- /dev/null
+++ b/gedit/gedit-window.h
@@ -0,0 +1,178 @@
+/*
+ * gedit-window.h
+ * 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
+ * MERCHANWINDOWILITY 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/>.
+ */
+
+#ifndef GEDIT_WINDOW_H
+#define GEDIT_WINDOW_H
+
+#include <gtksourceview/gtksource.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include <gedit/gedit-tab.h>
+#include <gedit/gedit-message-bus.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ GEDIT_WINDOW_STATE_NORMAL = 0,
+ GEDIT_WINDOW_STATE_SAVING = 1 << 1,
+ GEDIT_WINDOW_STATE_PRINTING = 1 << 2,
+ GEDIT_WINDOW_STATE_LOADING = 1 << 3,
+ GEDIT_WINDOW_STATE_ERROR = 1 << 4
+} GeditWindowState;
+
+#define GEDIT_TYPE_WINDOW (gedit_window_get_type())
+#define GEDIT_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_WINDOW, GeditWindow))
+#define GEDIT_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_WINDOW, GeditWindowClass))
+#define GEDIT_IS_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_WINDOW))
+#define GEDIT_IS_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_WINDOW))
+#define GEDIT_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_WINDOW, GeditWindowClass))
+
+typedef struct _GeditWindow GeditWindow;
+typedef struct _GeditWindowClass GeditWindowClass;
+typedef struct _GeditWindowPrivate GeditWindowPrivate;
+
+struct _GeditWindow
+{
+ GtkApplicationWindow window;
+
+ /*< private > */
+ GeditWindowPrivate *priv;
+};
+
+struct _GeditWindowClass
+{
+ GtkApplicationWindowClass parent_class;
+
+ /* Signals */
+ void (* tab_added) (GeditWindow *window,
+ GeditTab *tab);
+ void (* tab_removed) (GeditWindow *window,
+ GeditTab *tab);
+ void (* tabs_reordered) (GeditWindow *window);
+ void (* active_tab_changed) (GeditWindow *window,
+ GeditTab *tab);
+ void (* active_tab_state_changed)
+ (GeditWindow *window);
+};
+
+/* Public methods */
+GType gedit_window_get_type (void) G_GNUC_CONST;
+
+GeditTab *gedit_window_create_tab (GeditWindow *window,
+ gboolean jump_to);
+
+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);
+
+GeditTab *gedit_window_create_tab_from_stream (GeditWindow *window,
+ GInputStream *stream,
+ const GtkSourceEncoding *encoding,
+ gint line_pos,
+ gint column_pos,
+ gboolean jump_to);
+
+void gedit_window_close_tab (GeditWindow *window,
+ GeditTab *tab);
+
+void gedit_window_close_all_tabs (GeditWindow *window);
+
+void gedit_window_close_tabs (GeditWindow *window,
+ const GList *tabs);
+
+GeditTab *gedit_window_get_active_tab (GeditWindow *window);
+
+void gedit_window_set_active_tab (GeditWindow *window,
+ GeditTab *tab);
+
+/* Helper functions */
+GeditView *gedit_window_get_active_view (GeditWindow *window);
+GeditDocument *gedit_window_get_active_document (GeditWindow *window);
+
+/* Returns a newly allocated list with all the documents in the window */
+GList *gedit_window_get_documents (GeditWindow *window);
+
+/* Returns a newly allocated list with all the documents that need to be
+ saved before closing the window */
+GList *gedit_window_get_unsaved_documents (GeditWindow *window);
+
+/* Returns a newly allocated list with all the views in the window */
+GList *gedit_window_get_views (GeditWindow *window);
+
+GtkWindowGroup *gedit_window_get_group (GeditWindow *window);
+
+GtkWidget *gedit_window_get_side_panel (GeditWindow *window);
+
+GtkWidget *gedit_window_get_bottom_panel (GeditWindow *window);
+
+GtkWidget *gedit_window_get_statusbar (GeditWindow *window);
+
+GeditWindowState gedit_window_get_state (GeditWindow *window);
+
+GeditTab *gedit_window_get_tab_from_location (GeditWindow *window,
+ GFile *location);
+
+/* Message bus */
+GeditMessageBus *gedit_window_get_message_bus (GeditWindow *window);
+
+/*
+ * Non exported functions
+ */
+GtkWidget *_gedit_window_get_multi_notebook (GeditWindow *window);
+GtkWidget *_gedit_window_get_notebook (GeditWindow *window);
+
+GMenuModel *_gedit_window_get_hamburger_menu (GeditWindow *window);
+
+GeditWindow *_gedit_window_move_tab_to_new_window (GeditWindow *window,
+ GeditTab *tab);
+void _gedit_window_move_tab_to_new_tab_group(GeditWindow *window,
+ GeditTab *tab);
+gboolean _gedit_window_is_removing_tabs (GeditWindow *window);
+
+const gchar *_gedit_window_get_file_chooser_folder_uri
+ (GeditWindow *window,
+ GtkFileChooserAction action);
+
+void _gedit_window_set_file_chooser_folder_uri
+ (GeditWindow *window,
+ GtkFileChooserAction action,
+ const gchar *folder_uri);
+
+void _gedit_window_fullscreen (GeditWindow *window);
+
+void _gedit_window_unfullscreen (GeditWindow *window);
+
+gboolean _gedit_window_is_fullscreen (GeditWindow *window);
+
+GList *_gedit_window_get_all_tabs (GeditWindow *window);
+
+GFile *_gedit_window_pop_last_closed_doc (GeditWindow *window);
+
+G_END_DECLS
+
+#endif /* GEDIT_WINDOW_H */
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit.c b/gedit/gedit.c
new file mode 100644
index 0000000..079b517
--- /dev/null
+++ b/gedit/gedit.c
@@ -0,0 +1,203 @@
+/*
+ * gedit.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-app.h"
+
+#if defined OS_OSX
+# include "gedit-app-osx.h"
+#elif defined G_OS_WIN32
+# include "gedit-app-win32.h"
+#endif
+
+#include <locale.h>
+#include <libintl.h>
+#include <tepl/tepl.h>
+
+#include "gedit-dirs.h"
+#include "gedit-debug.h"
+#include "gedit-factory.h"
+#include "gedit-settings.h"
+
+#ifdef G_OS_WIN32
+#include <gmodule.h>
+static GModule *libgedit_dll = NULL;
+
+/* This code must live in gedit.exe, not in libgedit.dll, since the whole
+ * point is to find and load libgedit.dll.
+ */
+static gboolean
+gedit_w32_load_private_dll (void)
+{
+ gchar *dllpath;
+ gchar *prefix;
+
+ prefix = g_win32_get_package_installation_directory_of_module (NULL);
+
+ if (prefix != NULL)
+ {
+ /* Instead of g_module_open () it may be possible to do any of the
+ * following:
+ * A) Change PATH to "${dllpath}/lib/gedit;$PATH"
+ * B) Call SetDllDirectory ("${dllpath}/lib/gedit")
+ * C) Call AddDllDirectory ("${dllpath}/lib/gedit")
+ * But since we only have one library, and its name is known, may as well
+ * use gmodule.
+ */
+ dllpath = g_build_filename (prefix, "lib", "gedit", "lib" PACKAGE_STRING ".dll", NULL);
+ g_free (prefix);
+
+ libgedit_dll = g_module_open (dllpath, 0);
+ if (libgedit_dll == NULL)
+ {
+ g_printerr ("Failed to load '%s': %s\n",
+ dllpath, g_module_error ());
+ }
+
+ g_free (dllpath);
+ }
+
+ if (libgedit_dll == NULL)
+ {
+ libgedit_dll = g_module_open ("lib" PACKAGE_STRING ".dll", 0);
+ if (libgedit_dll == NULL)
+ {
+ g_printerr ("Failed to load 'lib" PACKAGE_STRING ".dll': %s\n",
+ g_module_error ());
+ }
+ }
+
+ return (libgedit_dll != NULL);
+}
+
+static void
+gedit_w32_unload_private_dll (void)
+{
+ if (libgedit_dll)
+ {
+ g_module_close (libgedit_dll);
+ libgedit_dll = NULL;
+ }
+}
+#endif /* G_OS_WIN32 */
+
+static void
+setup_i18n (void)
+{
+ const gchar *dir;
+
+ setlocale (LC_ALL, "");
+
+ dir = gedit_dirs_get_gedit_locale_dir ();
+ bindtextdomain (GETTEXT_PACKAGE, dir);
+
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+}
+
+static void
+setup_pango (void)
+{
+#ifdef G_OS_WIN32
+ PangoFontMap *font_map;
+
+ /* Prefer the fontconfig backend of pangocairo. The win32 backend gives
+ * ugly fonts. The fontconfig backend is what is used on Linux.
+ * Another way would be to set the environment variable:
+ * PANGOCAIRO_BACKEND=fontconfig
+ * See also the documentation of pango_cairo_font_map_new().
+ */
+ font_map = pango_cairo_font_map_new_for_font_type (CAIRO_FONT_TYPE_FT);
+
+ if (font_map != NULL)
+ {
+ pango_cairo_font_map_set_default (PANGO_CAIRO_FONT_MAP (font_map));
+ g_object_unref (font_map);
+ }
+#endif
+}
+
+int
+main (int argc, char *argv[])
+{
+ GType type;
+ GeditFactory *factory;
+ GeditApp *app;
+ gint status;
+
+#if defined OS_OSX
+ type = GEDIT_TYPE_APP_OSX;
+#elif defined G_OS_WIN32
+ if (!gedit_w32_load_private_dll ())
+ {
+ return 1;
+ }
+
+ type = GEDIT_TYPE_APP_WIN32;
+#else
+ type = GEDIT_TYPE_APP;
+#endif
+
+ /* NOTE: we should not make any calls to the gedit API before the
+ * private library is loaded.
+ */
+ gedit_dirs_init ();
+
+ setup_i18n ();
+ setup_pango ();
+ tepl_init ();
+ factory = gedit_factory_new ();
+ tepl_abstract_factory_set_singleton (TEPL_ABSTRACT_FACTORY (factory));
+
+ app = g_object_new (type,
+ "application-id", "org.gnome.gedit",
+ "flags", G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_HANDLES_OPEN,
+ NULL);
+
+ status = g_application_run (G_APPLICATION (app), argc, argv);
+
+ gedit_settings_unref_singleton ();
+
+ /* Break reference cycles caused by the PeasExtensionSet
+ * for GeditAppActivatable which holds a ref on the GeditApp
+ */
+ g_object_run_dispose (G_OBJECT (app));
+
+ g_object_add_weak_pointer (G_OBJECT (app), (gpointer *) &app);
+ g_object_unref (app);
+
+ if (app != NULL)
+ {
+ gedit_debug_message (DEBUG_APP, "Leaking with %i refs",
+ G_OBJECT (app)->ref_count);
+ }
+
+ tepl_finalize ();
+ gedit_dirs_shutdown ();
+
+#ifdef G_OS_WIN32
+ gedit_w32_unload_private_dll ();
+#endif
+
+ return status;
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/meson.build b/gedit/meson.build
new file mode 100644
index 0000000..6c3d854
--- /dev/null
+++ b/gedit/meson.build
@@ -0,0 +1,263 @@
+libgedit_public_headers = [
+ 'gedit-app-activatable.h',
+ 'gedit-app.h',
+ 'gedit-commands.h',
+ 'gedit-debug.h',
+ 'gedit-document.h',
+ 'gedit-encodings-combo-box.h',
+ 'gedit-menu-extension.h',
+ 'gedit-message-bus.h',
+ 'gedit-message.h',
+ 'gedit-statusbar.h',
+ 'gedit-tab.h',
+ 'gedit-utils.h',
+ 'gedit-view-activatable.h',
+ 'gedit-view.h',
+ 'gedit-window-activatable.h',
+ 'gedit-window.h',
+]
+
+libgedit_public_sources = [
+ 'gedit-app-activatable.c',
+ 'gedit-app.c',
+ 'gedit-commands-file.c',
+ 'gedit-debug.c',
+ 'gedit-document.c',
+ 'gedit-encodings-combo-box.c',
+ 'gedit-menu-extension.c',
+ 'gedit-message-bus.c',
+ 'gedit-message.c',
+ 'gedit-statusbar.c',
+ 'gedit-tab.c',
+ 'gedit-utils.c',
+ 'gedit-view-activatable.c',
+ 'gedit-view.c',
+ 'gedit-window-activatable.c',
+ 'gedit-window.c',
+]
+
+libgedit_private_headers = [
+ 'gedit-app-osx.h',
+ 'gedit-app-win32.h',
+ 'gedit-close-confirmation-dialog.h',
+ 'gedit-dirs.h',
+ 'gedit-document-private.h',
+ 'gedit-documents-panel.h',
+ 'gedit-encoding-items.h',
+ 'gedit-encodings-dialog.h',
+ 'gedit-factory.h',
+ 'gedit-file-chooser-dialog-gtk.h',
+ 'gedit-file-chooser-dialog.h',
+ 'gedit-file-chooser.h',
+ 'gedit-file-chooser-open-dialog.h',
+ 'gedit-file-chooser-open.h',
+ 'gedit-file-chooser-open-native.h',
+ 'gedit-history-entry.h',
+ 'gedit-io-error-info-bar.h',
+ 'gedit-menu-stack-switcher.h',
+ 'gedit-multi-notebook.h',
+ 'gedit-notebook.h',
+ 'gedit-notebook-popup-menu.h',
+ 'gedit-notebook-stack-switcher.h',
+ 'gedit-plugins-engine.h',
+ 'gedit-preferences-dialog.h',
+ 'gedit-print-job.h',
+ 'gedit-print-preview.h',
+ 'gedit-recent.h',
+ 'gedit-recent-osx.h',
+ 'gedit-replace-dialog.h',
+ 'gedit-settings.h',
+ 'gedit-status-menu-button.h',
+ 'gedit-tab-label.h',
+ 'gedit-view-frame.h',
+]
+
+libgedit_private_sources = [
+ 'gedit-close-confirmation-dialog.c',
+ 'gedit-commands-documents.c',
+ 'gedit-commands-edit.c',
+ 'gedit-commands-file-print.c',
+ 'gedit-commands-help.c',
+ 'gedit-commands-search.c',
+ 'gedit-commands-view.c',
+ 'gedit-dirs.c',
+ 'gedit-documents-panel.c',
+ 'gedit-encoding-items.c',
+ 'gedit-encodings-dialog.c',
+ 'gedit-factory.c',
+ 'gedit-file-chooser.c',
+ 'gedit-file-chooser-dialog.c',
+ 'gedit-file-chooser-dialog-gtk.c',
+ 'gedit-file-chooser-open.c',
+ 'gedit-file-chooser-open-dialog.c',
+ 'gedit-file-chooser-open-native.c',
+ 'gedit-history-entry.c',
+ 'gedit-io-error-info-bar.c',
+ 'gedit-menu-stack-switcher.c',
+ 'gedit-multi-notebook.c',
+ 'gedit-notebook.c',
+ 'gedit-notebook-popup-menu.c',
+ 'gedit-notebook-stack-switcher.c',
+ 'gedit-plugins-engine.c',
+ 'gedit-preferences-dialog.c',
+ 'gedit-print-job.c',
+ 'gedit-print-preview.c',
+ 'gedit-recent.c',
+ 'gedit-replace-dialog.c',
+ 'gedit-settings.c',
+ 'gedit-status-menu-button.c',
+ 'gedit-tab-label.c',
+ 'gedit-view-frame.c',
+]
+
+libgedit_c_args = []
+libgedit_link_args = []
+
+libgedit_deps = [
+ deps_basic_list,
+ libgd_dep,
+]
+
+if host_machine.system() == 'darwin'
+ libgedit_private_sources += [
+ 'gedit-app-osx.m',
+ 'gedit-recent-osx.c',
+ ]
+ libgedit_c_args += [
+ '-DOS_OSX=1',
+ ]
+ libgedit_link_args += [
+ '-Wl,-framework', '-Wl,Foundation',
+ '-Wl,-framework', '-Wl,AppKit',
+ ]
+ libgedit_deps += [
+ dependency('gtk-mac-integration-gtk3'),
+ ]
+elif host_machine.system() == 'windows'
+ libgedit_private_sources += [
+ 'gedit-app-win32.c',
+ ]
+endif
+
+headers_install_dir = get_option('includedir') / 'gedit-@0@/gedit/'.format(api_version)
+install_headers(
+ libgedit_public_headers,
+ install_dir: headers_install_dir,
+)
+
+libgedit_public_enum_types = gnome.mkenums_simple(
+ 'gedit-enum-types',
+ sources: libgedit_public_headers,
+ install_header: true,
+ install_dir: headers_install_dir,
+)
+
+libgedit_private_enum_types = gnome.mkenums_simple(
+ 'gedit-enum-types-private',
+ sources: ['gedit-notebook.h'],
+)
+
+libgedit_private_headers += 'gedit-enum-types-private.h'
+
+subdir('resources')
+
+libgedit_shared_lib = shared_library(
+ 'gedit-@0@'.format(api_version),
+ [libgedit_public_sources,
+ libgedit_private_sources,
+ libgedit_public_enum_types,
+ libgedit_private_enum_types,
+ libgedit_gresources],
+ include_directories: root_include_dir,
+ dependencies: libgedit_deps,
+ c_args: libgedit_c_args,
+ link_args: libgedit_link_args,
+ install: true,
+ install_dir: get_option('libdir') / 'gedit',
+)
+
+# GObject Introspection
+libgedit_gir = gnome.generate_gir(
+ libgedit_shared_lib,
+ sources: [
+ libgedit_public_headers,
+ libgedit_public_sources,
+ libgedit_public_enum_types,
+ ],
+ nsversion: '3.0',
+ namespace: 'Gedit',
+ symbol_prefix: 'gedit',
+ identifier_prefix: 'Gedit',
+ export_packages: 'gedit-@0@'.format(api_version),
+ includes: ['Gtk-3.0', 'GtkSource-4'],
+ link_with: libgedit_shared_lib,
+ install: true,
+ install_dir_gir: get_option('datadir') / 'gedit/gir-1.0',
+ install_dir_typelib: get_option('libdir') / 'gedit/girepository-1.0',
+)
+
+python3.install_sources(
+ 'Gedit.py',
+ subdir: 'gi/overrides',
+)
+
+# Vala API
+libgedit_vapi = gnome.generate_vapi(
+ 'gedit',
+ sources: libgedit_gir[0],
+ packages: ['gio-2.0', 'atk', 'gdk-3.0', 'gtk+-3.0', 'gtksourceview-4'],
+ install: true,
+ install_dir: get_option('datadir') / 'vala/vapi',
+)
+
+libgedit_dep = declare_dependency(
+ include_directories: root_include_dir,
+ link_with: libgedit_shared_lib,
+ sources: [libgedit_public_enum_types[1], libgedit_private_enum_types[1]],
+ dependencies: libgedit_deps,
+)
+
+pkg_config.generate(
+ libgedit_shared_lib,
+ filebase: 'gedit',
+ name: 'gedit',
+ description: 'GNOME text editor',
+ subdirs: 'gedit-@0@'.format(api_version),
+ libraries: libgedit_public_deps,
+ install_dir: get_option('libdir') / 'pkgconfig'
+)
+
+# FIXME: https://github.com/mesonbuild/meson/issues/1687
+custom_target(
+ 'org.gnome.gedit.enums.xml',
+ input : ['gedit-notebook.h'],
+ output: 'org.gnome.gedit.enums.xml',
+ capture: true,
+ command: [
+ 'glib-mkenums',
+ '--comments', '<!-- @comment@ -->',
+ '--fhead', '<schemalist>',
+ '--vhead', ' <@type@ id="org.gnome.gedit.@EnumName@">',
+ '--vprod', ' <value nick="@valuenick@" value="@valuenum@"/>',
+ '--vtail', ' </@type@>',
+ '--ftail', '</schemalist>',
+ '@INPUT@'
+ ],
+ install: true,
+ install_dir: get_option('datadir') / 'glib-2.0/schemas'
+)
+
+gedit_c_args = []
+if host_machine.system() == 'darwin'
+ gedit_c_args += '-DOS_OSX=1'
+endif
+
+executable(
+ 'gedit',
+ 'gedit.c',
+ dependencies: libgedit_dep,
+ c_args: gedit_c_args,
+ install: true,
+ install_rpath: get_option('prefix') / get_option('libdir') / 'gedit',
+ gui_app: true,
+)
diff --git a/gedit/resources/css/gedit-style-osx.css b/gedit/resources/css/gedit-style-osx.css
new file mode 100644
index 0000000..f3c7c0b
--- /dev/null
+++ b/gedit/resources/css/gedit-style-osx.css
@@ -0,0 +1,129 @@
+@binding-set gtk-osx-editable {
+ bind "<primary>c" { "copy-clipboard" () };
+ bind "<primary>x" { "cut-clipboard" () };
+ bind "<primary>v" { "paste-clipboard" () };
+
+ unbind "<control>c";
+ unbind "<control>x";
+ unbind "<control>v";
+
+ bind "<primary>Left" { "move-cursor" (display-line-ends, -1, 0) };
+ bind "<primary>KP_Left" { "move-cursor" (display-line-ends, -1, 0) };
+ bind "<primary><shift>Left" { "move-cursor" (display-line-ends, -1, 1) };
+ bind "<primary><shift>KP_Left" { "move-cursor" (display-line-ends, -1, 1) };
+
+ bind "<primary>Right" { "move-cursor" (display-line-ends, 1, 0) };
+ bind "<primary>KP_Right" { "move-cursor" (display-line-ends, 1, 0) };
+ bind "<primary><shift>Right" { "move-cursor" (display-line-ends, 1, 1) };
+ bind "<primary><shift>KP_Right" { "move-cursor" (display-line-ends, 1, 1) };
+
+ unbind "<control>Left";
+ unbind "<control>KP_Left";
+ unbind "<control><shift>Left";
+ unbind "<control><shift>KP_Left";
+ unbind "<control>Right";
+ unbind "<control>KP_Right";
+ unbind "<control><shift>Right";
+ unbind "<control><shift>KP_Right";
+
+ bind "<alt>Right" { "move-cursor" (words, 1, 0) };
+ bind "<alt>KP_Right" { "move-cursor" (words, 1, 0) };
+ bind "<alt>Left" { "move-cursor" (words, -1, 0) };
+ bind "<alt>KP_Left" { "move-cursor" (words, -1, 0) };
+ bind "<shift><alt>Right" { "move-cursor" (words, 1, 1) };
+ bind "<shift><alt>KP_Right" { "move-cursor" (words, 1, 1) };
+ bind "<shift><alt>Left" { "move-cursor" (words, -1, 1) };
+ bind "<shift><alt>KP_Left" { "move-cursor" (words, -1, 1) };
+
+ bind "<alt>Delete" { "delete-from-cursor" (word-ends, 1) };
+ bind "<alt>KP_Delete" { "delete-from-cursor" (word-ends, 1) };
+ bind "<alt>BackSpace" { "delete-from-cursor" (word-ends, -1) };
+
+ bind "<primary>Down" { "move-cursor" (buffer-ends, 1, 0) };
+ bind "<primary><shift>Down" { "move-cursor" (buffer-ends, 1, 1) };
+ bind "<primary>KP_Down" { "move-cursor" (buffer-ends, 1, 0) };
+ bind "<primary><shift>KP_Down" { "move-cursor" (buffer-ends, 1, 1) };
+
+ bind "<primary>Up" { "move-cursor" (buffer-ends, -1, 0) };
+ bind "<primary><shift>Up" { "move-cursor" (buffer-ends, -1, 1) };
+ bind "<primary>KP_Up" { "move-cursor" (buffer-ends, -1, 0) };
+ bind "<primary><shift>KP_Up" { "move-cursor" (buffer-ends, -1, 1) };
+
+ bind "<primary><alt>I" { "toggle-overwrite" () };
+ unbind "Insert";
+
+ unbind "<control>Down";
+ unbind "<control>KP_Down";
+ unbind "<control><shift>Down";
+ unbind "<control><shift>KP_Down";
+ unbind "<control>Up";
+ unbind "<control>KP_Up";
+ unbind "<control><shift>Up";
+ unbind "<control><shift>KP_Up";
+}
+
+@binding-set gtk-osx-text-entry {
+ bind "<primary>a" {
+ "move-cursor" (buffer-ends, -1, 0)
+ "move-cursor" (buffer-ends, 1, 1)
+ };
+
+ unbind "<control>a";
+}
+
+@binding-set gtk-osx-text-view {
+ bind "<primary>a" { "select-all" (1) };
+ unbind "<control>a";
+}
+
+@binding-set gtk-osx-tree-view {
+ bind "<primary>s" { "start-interactive-search" () };
+ unbind "<control>s";
+}
+
+@binding-set gtk-osx-source-view {
+ bind "<primary>z" { "undo" () };
+ unbind "<control>z";
+
+ bind "<primary><shift>z" { "redo" () };
+ unbind "<control><shift>z";
+}
+
+@binding-set gedit-osx-view {
+ bind "<primary>d" { "delete-from-cursor" (GTK_DELETE_PARAGRAPHS, 1) };
+ unbind "<control>d";
+}
+
+entry {
+ -gtk-key-bindings: gtk-osx-editable, gtk-osx-text-entry;
+}
+
+textview {
+ -gtk-key-bindings: gtk-osx-editable, gtk-osx-text-view;
+}
+
+textview.sourceview {
+ -gtk-key-bindings: gtk-osx-editable, gtk-osx-text-view, gtk-osx-source-view;
+}
+
+textview.gedit-view {
+ -gtk-key-bindings: gtk-osx-editable, gtk-osx-text-view, gtk-osx-source-view, gedit-osx-view;
+}
+
+treeview {
+ -gtk-key-bindings: gtk-osx-tree-view;
+}
+
+notebook {
+ padding: 0px;
+}
+
+notebook tab {
+ padding: 4px 2px 2px 2px;
+}
+
+.gedit-side-panel-stack-switcher {
+ border: 0;
+ border-radius: 0;
+ border-bottom: 1px solid @borders;
+}
diff --git a/gedit/resources/css/gedit-style.css b/gedit/resources/css/gedit-style.css
new file mode 100644
index 0000000..eb43a82
--- /dev/null
+++ b/gedit/resources/css/gedit-style.css
@@ -0,0 +1,31 @@
+.gedit-side-panel-paned.pane-separator:dir(ltr),
+.gedit-side-panel-paned.pane-separator:hover:dir(ltr) {
+ border-radius: 0;
+ border-width: 0 1px 0 0;
+}
+
+.gedit-side-panel-paned.pane-separator:dir(rtl),
+.gedit-side-panel-paned.pane-separator:hover:dir(rtl) {
+ border-radius: 0;
+ border-width: 0 0 0 1px;
+}
+
+.gedit-menu-stack-switcher {
+ padding: 12px;
+}
+
+statusbar frame {
+ border: none;
+ padding-left: 6px;
+ padding-right: 6px;
+}
+
+statusbar button.flat {
+ border-radius: 0;
+ border-bottom: none;
+}
+
+GeditFileBrowserWidget .small-button {
+ padding: 2px 4px;
+}
+
diff --git a/gedit/resources/css/gedit.adwaita.css b/gedit/resources/css/gedit.adwaita.css
new file mode 100644
index 0000000..784e72a
--- /dev/null
+++ b/gedit/resources/css/gedit.adwaita.css
@@ -0,0 +1,61 @@
+.gedit-document-panel {
+ background-color: @sidebar_bg;
+}
+
+.gedit-document-panel:backdrop {
+ color: #b0b2b2;
+}
+
+.gedit-document-panel row:selected:backdrop {
+ background-color: #8b8e8f;
+}
+
+.gedit-document-panel-group-row,
+.gedit-document-panel-group-row:hover {
+ border-top: 1px solid alpha(currentColor, 0.3);
+}
+
+.gedit-document-panel-group-row:first-child,
+.gedit-document-panel-group-row:first-child:hover {
+ border-top: 0px;
+}
+
+/* Try to look as the notebook tab close button */
+.gedit-document-panel row button.flat {
+ padding: 0;
+ margin-top: 8px;
+ margin-bottom: 8px;
+ min-width: 18px;
+ min-height: 18px;
+ color: alpha(currentColor,0.3);
+}
+
+.gedit-document-panel row:hover button.flat {
+ color: alpha(currentColor,0.5);
+}
+
+.gedit-document-panel row button.flat:hover {
+ color: @theme_fg_color;
+}
+
+statusbar {
+ border-top: 1px solid @borders;
+}
+
+.gedit-search-slider {
+ background-color: @theme_base_color;
+ padding: 6px;
+ border-color: @borders;
+ border-radius: 0 0 3px 3px;
+ border-width: 0 1px 1px 1px;
+ border-style: solid;
+}
+
+.gedit-search-entry-occurrences-tag {
+ background-color: @theme_base_color;
+ background-image: none;
+ color: shade (@theme_unfocused_fg_color, 0.8);
+ border: 0px;
+ margin: 2px;
+ padding: 2px;
+}
diff --git a/gedit/resources/css/gedit.highcontrast.css b/gedit/resources/css/gedit.highcontrast.css
new file mode 100644
index 0000000..feca0fc
--- /dev/null
+++ b/gedit/resources/css/gedit.highcontrast.css
@@ -0,0 +1,5 @@
+.gedit-search-entry-occurrences-tag {
+ color: @theme_unfocused_fg_color;
+ margin: 2px;
+ padding: 2px;
+}
diff --git a/gedit/resources/gedit.gresource.xml.in b/gedit/resources/gedit.gresource.xml.in
new file mode 100644
index 0000000..a590511
--- /dev/null
+++ b/gedit/resources/gedit.gresource.xml.in
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/gedit">
+ <file preprocess="xml-stripblanks">gtk/menus.ui</file>
+ <file preprocess="xml-stripblanks">gtk/menus-common.ui</file>
+ <file preprocess="xml-stripblanks">ui/gedit-encodings-dialog.ui</file>
+ <file preprocess="xml-stripblanks">ui/gedit-preferences-dialog.ui</file>
+ <file preprocess="xml-stripblanks">ui/gedit-replace-dialog.ui</file>
+ <file preprocess="xml-stripblanks">ui/gedit-print-preview.ui</file>
+ <file preprocess="xml-stripblanks">ui/gedit-print-preferences.ui</file>
+ <file preprocess="xml-stripblanks">ui/gedit-status-menu-button.ui</file>
+ <file preprocess="xml-stripblanks">ui/gedit-tab-label.ui</file>
+ <file preprocess="xml-stripblanks">ui/gedit-view-frame.ui</file>
+ <file preprocess="xml-stripblanks">ui/gedit-window.ui</file>
+ <file preprocess="xml-stripblanks">ui/gedit-shortcuts.ui</file>
+ <file preprocess="xml-stripblanks">ui/gedit-statusbar.ui</file>
+ <file>css/gedit-style.css</file>
+ <file>css/gedit.adwaita.css</file>
+ <file>css/gedit.highcontrast.css</file>
+
+ @OS_DEPENDENT_RESOURCE_FILES@
+ </gresource>
+</gresources>
diff --git a/gedit/resources/gtk/menus-common.ui b/gedit/resources/gtk/menus-common.ui
new file mode 100644
index 0000000..af027a7
--- /dev/null
+++ b/gedit/resources/gtk/menus-common.ui
@@ -0,0 +1,376 @@
+<?xml version="1.0"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <menu id="notebook-menu">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Move _Left</attribute>
+ <attribute name="action">move-left</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Move _Right</attribute>
+ <attribute name="action">move-right</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Move to New _Window</attribute>
+ <attribute name="action">move-to-new-window</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Move to New Tab _Group</attribute>
+ <attribute name="action">move-to-new-tab-group</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Close</attribute>
+ <attribute name="action">close</attribute>
+ </item>
+ </section>
+ </menu>
+ <menu id="tab-width-menu">
+ <section>
+ <item>
+ <attribute name="label">2</attribute>
+ <attribute name="action">win.tab-width</attribute>
+ <attribute name="target" type="u">2</attribute>
+ </item>
+ <item>
+ <attribute name="label">4</attribute>
+ <attribute name="action">win.tab-width</attribute>
+ <attribute name="target" type="u">4</attribute>
+ </item>
+ <item>
+ <attribute name="label">8</attribute>
+ <attribute name="action">win.tab-width</attribute>
+ <attribute name="target" type="u">8</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Use Spaces</attribute>
+ <attribute name="action">win.use-spaces</attribute>
+ </item>
+ </section>
+ </menu>
+ <!-- menubar is in common since on ubuntu would be picked from menus-traditional,
+ and on osx from menus -->
+ <menu id="menubar">
+ <section>
+ <submenu>
+ <attribute name="label" translatable="yes">_File</attribute>
+ <section>
+ <attribute name="id">file-section-0</attribute>
+ <item>
+ <attribute name="label" translatable="yes" comments="_New is the menu item under the File menu on OS X which creates a new empty document.">_New</attribute>
+ <attribute name="action">win.new-tab</attribute>
+ <attribute name="accel">&lt;Primary&gt;T</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">file-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Open</attribute>
+ <attribute name="action">win.open</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ <attribute name="accel">&lt;Primary&gt;O</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Open</attribute>
+ <attribute name="action">app.open</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <submenu>
+ <attribute name="label" translatable="yes">Open _Recent</attribute>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Reopen Closed _Tab</attribute>
+ <attribute name="action">win.reopen-closed-tab</attribute>
+ <attribute name="accel">&lt;Primary&gt;&lt;Shift&gt;T</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">recent-files-section</attribute>
+ </section>
+ </submenu>
+ </section>
+ <section>
+ <attribute name="id">file-section-1</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Save</attribute>
+ <attribute name="action">win.save</attribute>
+ <attribute name="accel">&lt;Primary&gt;S</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Save _As…</attribute>
+ <attribute name="action">win.save-as</attribute>
+ <attribute name="accel">&lt;Primary&gt;&lt;Shift&gt;S</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">app-commands-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_New Window</attribute>
+ <attribute name="action">app.new-window</attribute>
+ <attribute name="accel">&lt;Primary&gt;N</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">file-section-2</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Reload</attribute>
+ <attribute name="action">win.revert</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">file-section-3</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Print…</attribute>
+ <attribute name="action">win.print</attribute>
+ <attribute name="accel">&lt;Primary&gt;P</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">close-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Close</attribute>
+ <attribute name="action">win.close</attribute>
+ <attribute name="accel">&lt;Primary&gt;W</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Quit</attribute>
+ <attribute name="hidden-when">macos-menubar</attribute>
+ <attribute name="action">app.quit</attribute>
+ <attribute name="accel">&lt;Primary&gt;Q</attribute>
+ </item>
+ </section>
+ </submenu>
+ <submenu>
+ <attribute name="label" translatable="yes">_Edit</attribute>
+ <section>
+ <attribute name="id">edit-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Undo</attribute>
+ <attribute name="action">win.undo</attribute>
+ <attribute name="accel">&lt;Primary&gt;Z</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Redo</attribute>
+ <attribute name="action">win.redo</attribute>
+ <attribute name="accel">&lt;Primary&gt;&lt;Shift&gt;Z</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">C_ut</attribute>
+ <attribute name="action">win.cut</attribute>
+ <attribute name="accel">&lt;Primary&gt;X</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Copy</attribute>
+ <attribute name="action">win.copy</attribute>
+ <attribute name="accel">&lt;Primary&gt;C</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Paste</attribute>
+ <attribute name="action">win.paste</attribute>
+ <attribute name="accel">&lt;Primary&gt;V</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Delete</attribute>
+ <attribute name="action">win.delete</attribute>
+ <attribute name="accel">Delete</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">edit-section-1</attribute>
+ <item>
+ <attribute name="label" translatable="yes">Overwrite _Mode</attribute>
+ <attribute name="action">win.overwrite-mode</attribute>
+ <attribute name="accel">Insert</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">edit-section-2</attribute>
+ <item>
+ <attribute name="label" translatable="yes">Select _All</attribute>
+ <attribute name="action">win.select-all</attribute>
+ <attribute name="accel">&lt;Primary&gt;A</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Preferences</attribute>
+ <attribute name="hidden-when">macos-menubar</attribute>
+ <attribute name="action">app.preferences</attribute>
+ </item>
+ </section>
+ </submenu>
+ <submenu>
+ <attribute name="label" translatable="yes">_View</attribute>
+ <section>
+ <attribute name="id">view-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">Side _Panel</attribute>
+ <attribute name="action">win.side-panel</attribute>
+ <attribute name="accel">F9</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Bottom Panel</attribute>
+ <attribute name="action">win.bottom-panel</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ <attribute name="accel">&lt;Primary&gt;F9</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">view-section-1</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Fullscreen</attribute>
+ <attribute name="action">win.fullscreen</attribute>
+ <attribute name="accel">F11</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">view-section-2</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Highlight Mode…</attribute>
+ <attribute name="action">win.highlight-mode</attribute>
+ </item>
+ </section>
+ </submenu>
+ <submenu>
+ <attribute name="label" translatable="yes">_Search</attribute>
+ <section>
+ <attribute name="id">search-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Find…</attribute>
+ <attribute name="action">win.find</attribute>
+ <attribute name="accel">&lt;Primary&gt;F</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Find Ne_xt</attribute>
+ <attribute name="action">win.find-next</attribute>
+ <attribute name="accel">&lt;Primary&gt;G</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Find Pre_vious</attribute>
+ <attribute name="action">win.find-prev</attribute>
+ <attribute name="accel">&lt;Primary&gt;&lt;Shift&gt;G</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">search-section-1</attribute>
+ <item>
+ <attribute name="label" translatable="yes">Find and _Replace…</attribute>
+ <attribute name="action">win.replace</attribute>
+ <attribute name="accel">&lt;Primary&gt;H</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">search-section-2</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Clear Highlight</attribute>
+ <attribute name="action">win.clear-highlight</attribute>
+ <attribute name="accel">&lt;Primary&gt;&lt;Shift&gt;K</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">search-section-3</attribute>
+ <item>
+ <attribute name="label" translatable="yes">Go to _Line…</attribute>
+ <attribute name="action">win.goto-line</attribute>
+ <attribute name="accel">&lt;Primary&gt;I</attribute>
+ </item>
+ </section>
+ </submenu>
+ <submenu>
+ <attribute name="label" translatable="yes">_Tools</attribute>
+ <section>
+ <attribute name="id">spell-section</attribute>
+ </section>
+ <section>
+ <attribute name="id">tools-section</attribute>
+ </section>
+ <section>
+ <attribute name="id">tools-section-1</attribute>
+ </section>
+ <section>
+ <attribute name="id">preferences-section</attribute>
+ </section>
+ </submenu>
+ <submenu>
+ <attribute name="label" translatable="yes">_Documents</attribute>
+ <section>
+ <attribute name="id">documents-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Save All</attribute>
+ <attribute name="action">win.save-all</attribute>
+ <attribute name="accel">&lt;Primary&gt;&lt;Shift&gt;L</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Close All</attribute>
+ <attribute name="action">win.close-all</attribute>
+ <attribute name="accel">&lt;Primary&gt;&lt;Shift&gt;W</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">documents-section-1</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_New Tab Group</attribute>
+ <attribute name="action">win.new-tab-group</attribute>
+ <attribute name="accel">&lt;Primary&gt;&lt;Alt&gt;N</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">P_revious Tab Group</attribute>
+ <attribute name="action">win.previous-tab-group</attribute>
+ <attribute name="accel">&lt;Primary&gt;&lt;Shift&gt;&lt;Alt&gt;Page_Up</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Nex_t Tab Group</attribute>
+ <attribute name="action">win.next-tab-group</attribute>
+ <attribute name="accel">&lt;Primary&gt;&lt;Shift&gt;&lt;Alt&gt;Page_Down</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">documents-section-2</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Previous Document</attribute>
+ <attribute name="action">win.previous-document</attribute>
+ <attribute name="accel">&lt;Primary&gt;&lt;Alt&gt;Page_Up</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">N_ext Document</attribute>
+ <attribute name="action">win.next-document</attribute>
+ <attribute name="accel">&lt;Primary&gt;&lt;Alt&gt;Page_Down</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">documents-section-3</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Move To New Window</attribute>
+ <attribute name="action">win.move-to-new-window</attribute>
+ </item>
+ </section>
+ </submenu>
+ <submenu>
+ <attribute name="label" translatable="yes">_Help</attribute>
+ <section>
+ <attribute name="id">help-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Help</attribute>
+ <attribute name="action">app.help</attribute>
+ <attribute name="accel">F1</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_About gedit</attribute>
+ <attribute name="hidden-when">macos-menubar</attribute>
+ <attribute name="action">app.about</attribute>
+ </item>
+ </section>
+ </submenu>
+ </section>
+ </menu>
+</interface>
diff --git a/gedit/resources/gtk/menus-traditional.ui b/gedit/resources/gtk/menus-traditional.ui
new file mode 100644
index 0000000..45cc40f
--- /dev/null
+++ b/gedit/resources/gtk/menus-traditional.ui
@@ -0,0 +1,143 @@
+<?xml version="1.0"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <menu id="hamburger-menu">
+ <section>
+ <attribute name="id">juntion-section</attribute>
+ <attribute name="display-hint">horizontal-buttons</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Reload</attribute>
+ <attribute name="action">win.revert</attribute>
+ <attribute name="verb-icon">view-refresh-symbolic</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Print…</attribute>
+ <attribute name="action">win.print</attribute>
+ <attribute name="verb-icon">printer-symbolic</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Fullscreen</attribute>
+ <attribute name="action">win.fullscreen</attribute>
+ <attribute name="verb-icon">view-fullscreen-symbolic</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">app-commands-section</attribute>
+ </section>
+ <section>
+ <attribute name="id">file-section</attribute>
+ </section>
+ <section>
+ <attribute name="id">file-section-1</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Save As…</attribute>
+ <attribute name="action">win.save-as</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Save _All</attribute>
+ <attribute name="action">win.save-all</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">edit-section</attribute>
+ </section>
+ <section>
+ <attribute name="id">edit-section-1</attribute>
+ </section>
+ <section>
+ <attribute name="id">search-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Find…</attribute>
+ <attribute name="action">win.find</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Find and Replace…</attribute>
+ <attribute name="action">win.replace</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Clear Highlight</attribute>
+ <attribute name="action">win.clear-highlight</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Go to Line…</attribute>
+ <attribute name="action">win.goto-line</attribute>
+ </item>
+ </section>
+ <section>
+ <submenu>
+ <attribute name="label" translatable="yes">_View</attribute>
+ <section>
+ <attribute name="id">view-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">Side _Panel</attribute>
+ <attribute name="action">win.side-panel</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Bottom Panel</attribute>
+ <attribute name="action">win.bottom-panel</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">view-section-1</attribute>
+ </section>
+ <section>
+ <attribute name="id">view-section-2</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Highlight Mode…</attribute>
+ <attribute name="action">win.highlight-mode</attribute>
+ </item>
+ </section>
+ </submenu>
+ <submenu>
+ <attribute name="label" translatable="yes">_Tools</attribute>
+ <section>
+ <attribute name="id">spell-section</attribute>
+ </section>
+ <section>
+ <attribute name="id">tools-section</attribute>
+ </section>
+ <section>
+ <attribute name="id">tools-section-1</attribute>
+ </section>
+ </submenu>
+ </section>
+ <section>
+ <attribute name="id">preferences-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Preferences</attribute>
+ <attribute name="action">app.preferences</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">help-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Keyboard Shortcuts</attribute>
+ <attribute name="action">app.shortcuts</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Help</attribute>
+ <attribute name="action">app.help</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_About gedit</attribute>
+ <attribute name="action">app.about</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">close-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Close All</attribute>
+ <attribute name="action">win.close-all</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Close</attribute>
+ <attribute name="action">win.close</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Quit</attribute>
+ <attribute name="action">app.quit</attribute>
+ </item>
+ </section>
+ </menu>
+</interface>
diff --git a/gedit/resources/gtk/menus.ui b/gedit/resources/gtk/menus.ui
new file mode 100644
index 0000000..5ff01ed
--- /dev/null
+++ b/gedit/resources/gtk/menus.ui
@@ -0,0 +1,132 @@
+<?xml version="1.0"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <menu id="hamburger-menu">
+ <section>
+ <attribute name="id">juntion-section</attribute>
+ <attribute name="display-hint">horizontal-buttons</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Reload</attribute>
+ <attribute name="action">win.revert</attribute>
+ <attribute name="verb-icon">view-refresh-symbolic</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Print…</attribute>
+ <attribute name="action">win.print</attribute>
+ <attribute name="verb-icon">printer-symbolic</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Fullscreen</attribute>
+ <attribute name="action">win.fullscreen</attribute>
+ <attribute name="verb-icon">view-fullscreen-symbolic</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">app-commands-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_New Window</attribute>
+ <attribute name="action">app.new-window</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">file-section</attribute>
+ </section>
+ <section>
+ <attribute name="id">file-section-1</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Save As…</attribute>
+ <attribute name="action">win.save-as</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Save _All</attribute>
+ <attribute name="action">win.save-all</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">edit-section</attribute>
+ </section>
+ <section>
+ <attribute name="id">edit-section-1</attribute>
+ </section>
+ <section>
+ <attribute name="id">search-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Find…</attribute>
+ <attribute name="action">win.find</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Find and Replace…</attribute>
+ <attribute name="action">win.replace</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Clear Highlight</attribute>
+ <attribute name="action">win.clear-highlight</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Go to Line…</attribute>
+ <attribute name="action">win.goto-line</attribute>
+ </item>
+ </section>
+ <section>
+ <submenu>
+ <attribute name="label" translatable="yes">_View</attribute>
+ <section>
+ <attribute name="id">view-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">Side _Panel</attribute>
+ <attribute name="action">win.side-panel</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Bottom Panel</attribute>
+ <attribute name="action">win.bottom-panel</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">view-section-1</attribute>
+ </section>
+ <section>
+ <attribute name="id">view-section-2</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Highlight Mode…</attribute>
+ <attribute name="action">win.highlight-mode</attribute>
+ </item>
+ </section>
+ </submenu>
+ <submenu>
+ <attribute name="label" translatable="yes">_Tools</attribute>
+ <section>
+ <attribute name="id">spell-section</attribute>
+ </section>
+ <section>
+ <attribute name="id">tools-section</attribute>
+ </section>
+ <section>
+ <attribute name="id">tools-section-1</attribute>
+ </section>
+ </submenu>
+ </section>
+ <section>
+ <attribute name="id">preferences-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Preferences</attribute>
+ <attribute name="action">app.preferences</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="id">app-section</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_Keyboard Shortcuts</attribute>
+ <attribute name="action">app.shortcuts</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Help</attribute>
+ <attribute name="action">app.help</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_About gedit</attribute>
+ <attribute name="action">app.about</attribute>
+ </item>
+ </section>
+ </menu>
+</interface>
diff --git a/gedit/resources/meson.build b/gedit/resources/meson.build
new file mode 100644
index 0000000..8d2e5a5
--- /dev/null
+++ b/gedit/resources/meson.build
@@ -0,0 +1,21 @@
+gresource_config_data = configuration_data()
+
+if host_machine.system() == 'darwin'
+ gresource_config_data.set(
+ 'OS_DEPENDENT_RESOURCE_FILES',
+ '<file preprocess="xml-stripblanks">gtk/menus-traditional.ui</file>'
+ )
+else
+ gresource_config_data.set('OS_DEPENDENT_RESOURCE_FILES', '')
+endif
+
+gresource_xml_file = configure_file(
+ input: 'gedit.gresource.xml.in',
+ output: 'gedit.gresource.xml',
+ configuration: gresource_config_data
+)
+
+libgedit_gresources = gnome.compile_resources(
+ 'gedit-resources',
+ gresource_xml_file,
+)
diff --git a/gedit/resources/ui/gedit-encodings-dialog.ui b/gedit/resources/ui/gedit-encodings-dialog.ui
new file mode 100644
index 0000000..31adf33
--- /dev/null
+++ b/gedit/resources/ui/gedit-encodings-dialog.ui
@@ -0,0 +1,278 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.1 -->
+<interface>
+ <requires lib="gtk+" version="3.10"/>
+ <object class="GtkListStore" id="liststore_available">
+ <columns>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ <!-- column-name charset -->
+ <column type="gchararray"/>
+ <!-- column-name encoding -->
+ <column type="GtkSourceEncoding"/>
+ </columns>
+ </object>
+ <object class="GtkTreeModelSort" id="sort_available">
+ <property name="model">liststore_available</property>
+ </object>
+ <object class="GtkListStore" id="liststore_chosen">
+ <columns>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ <!-- column-name charset -->
+ <column type="gchararray"/>
+ <!-- column-name encoding -->
+ <column type="GtkSourceEncoding"/>
+ </columns>
+ </object>
+ <template class="GeditEncodingsDialog" parent="GtkDialog">
+ <property name="width_request">700</property>
+ <property name="height_request">500</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="modal">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child type="titlebar">
+ <object class="GtkHeaderBar" id="header_bar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_subtitle">False</property>
+ <property name="title" translatable="yes">Character Encodings</property>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">_Cancel Changes</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="apply_button">
+ <property name="label" translatable="yes">_Save Settings</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="can_default">True</property>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkGrid" id="encodings_dialog_contents">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">18</property>
+ <property name="column_homogeneous">True</property>
+ <child>
+ <object class="GtkLabel" id="available_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="label" translatable="yes">_Other Available Encodings</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">treeview_available</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="chosen_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="label" translatable="yes">_Favorites</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">treeview_chosen</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box_available">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow_available">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="treeview_available">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="model">sort_available</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview_selection">
+ <property name="mode">multiple</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="treeviewcolumn1">
+ <property name="title" translatable="yes">Ca_tegory</property>
+ <property name="sort_column_id">0</property>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="treeviewcolumn2">
+ <property name="title" translatable="yes">_Encoding</property>
+ <property name="sort_column_id">1</property>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext2"/>
+ <attributes>
+ <attribute name="text">1</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolbar" id="toolbar_available">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="toolbar_style">icons</property>
+ <property name="icon_size">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box_chosen">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow_chosen">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="treeview_chosen">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="model">liststore_chosen</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection1">
+ <property name="mode">multiple</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="treeviewcolumn3">
+ <property name="title" translatable="yes">Ca_tegory</property>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext3"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="treeviewcolumn4">
+ <property name="title" translatable="yes">_Encoding</property>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext4"/>
+ <attributes>
+ <attribute name="text">1</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolbar" id="toolbar_chosen">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_size">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="cancel">cancel_button</action-widget>
+ <action-widget response="apply" default="true">apply_button</action-widget>
+ </action-widgets>
+ </template>
+</interface>
diff --git a/gedit/resources/ui/gedit-preferences-dialog.ui b/gedit/resources/ui/gedit-preferences-dialog.ui
new file mode 100644
index 0000000..cbfac0f
--- /dev/null
+++ b/gedit/resources/ui/gedit-preferences-dialog.ui
@@ -0,0 +1,675 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <requires lib="libpeas-gtk" version="1.0"/>
+ <template class="GeditPreferencesDialog" parent="GtkWindow">
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Preferences</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="border_width">0</property>
+ <child type="titlebar">
+ <object class="GtkHeaderBar" id="header_bar">
+ <property name="title" translatable="yes">Preferences</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="show-close-button">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="notebook">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="border_width">0</property>
+ <property name="show_border">False</property>
+ <child>
+ <object class="GtkGrid" id="grid6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="row_spacing">18</property>
+ <child>
+ <object class="GtkGrid" id="grid1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="display_line_numbers_checkbutton">
+ <property name="label" translatable="yes">_Display line numbers</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="right_margin_checkbutton">
+ <property name="label" translatable="yes">Display right _margin at column:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="right_margin_position_grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkSpinButton" id="right_margin_position_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">adjustment1</property>
+ <property name="climb_rate">1</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="display_statusbar_checkbutton">
+ <property name="label" translatable="yes">Display _statusbar</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="display_grid_checkbutton">
+ <property name="label" translatable="yes">Display _grid pattern</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label848">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Text Wrapping</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="wrap_text_checkbutton">
+ <property name="label" translatable="yes">Enable text _wrapping</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="split_checkbutton">
+ <property name="label" translatable="yes">Do not _split words over two lines</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label876">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Highlighting</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="highlight_current_line_checkbutton">
+ <property name="label" translatable="yes">Highlight current _line</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="bracket_matching_checkbutton">
+ <property name="label" translatable="yes">Highlight matching _brackets</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_expand">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label853">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">View</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="row_spacing">18</property>
+ <child>
+ <object class="GtkGrid" id="grid7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label846">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Tab Stops</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid8">
+ <property name="name">12</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label98">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">_Tab width:</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ <property name="mnemonic_widget">tabs_width_spinbutton</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="tabs_width_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">adjustment2</property>
+ <property name="climb_rate">1</property>
+ <property name="numeric">True</property>
+ <property name="value">8</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="insert_spaces_checkbutton">
+ <property name="label" translatable="yes">Insert _spaces instead of tabs</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="auto_indent_checkbutton">
+ <property name="label" translatable="yes">_Enable automatic indentation</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label859">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">File Saving</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="backup_copy_checkbutton">
+ <property name="label" translatable="yes">Create a _backup copy of files before saving</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="auto_save_checkbutton">
+ <property name="label" translatable="yes">_Autosave files every</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label97">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_minutes</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ <property name="mnemonic_widget">auto_save_spinbutton</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="auto_save_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">adjustment3</property>
+ <property name="climb_rate">1</property>
+ <property name="numeric">True</property>
+ <property name="value">8</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_expand">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label829">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Editor</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="row_spacing">18</property>
+ <child>
+ <object class="GtkGrid" id="font_component_placeholder">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label798">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Color Scheme</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="TeplStyleSchemeChooserWidget" id="schemes_list">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolbar" id="schemes_toolbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="toolbar_style">icons</property>
+ <property name="icon_size">1</property>
+ <child>
+ <object class="GtkToolButton" id="install_scheme_button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_markup" translatable="yes">Install scheme</property>
+ <property name="tooltip_text" translatable="yes">Install scheme</property>
+ <property name="label" translatable="yes">Install Scheme</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">list-add-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="uninstall_scheme_button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_markup" translatable="yes">Uninstall scheme</property>
+ <property name="tooltip_text" translatable="yes">Uninstall scheme</property>
+ <property name="label" translatable="yes">Uninstall Scheme</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">list-remove-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <style>
+ <class name="inline-toolbar"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_expand">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label830">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Font &amp; Colors</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="PeasGtkPluginManager" id="plugin_manager">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab_expand">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label868">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Plugins</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="lower">1</property>
+ <property name="upper">1000</property>
+ <property name="value">80</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment2">
+ <property name="lower">1</property>
+ <property name="upper">24</property>
+ <property name="value">8</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">4</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment3">
+ <property name="lower">1</property>
+ <property name="upper">100</property>
+ <property name="value">8</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+</interface>
diff --git a/gedit/resources/ui/gedit-print-preferences.ui b/gedit/resources/ui/gedit-print-preferences.ui
new file mode 100644
index 0000000..40a4f69
--- /dev/null
+++ b/gedit/resources/ui/gedit-print-preferences.ui
@@ -0,0 +1,518 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="lower">1</property>
+ <property name="upper">100</property>
+ <property name="value">1</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkWindow" id="window1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="title">window1</property>
+ <child>
+ <object class="GtkBox" id="contents">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkBox" id="vbox20">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkBox" id="vbox26">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label33">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Syntax Highlighting</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="syntax_checkbutton">
+ <property name="label" translatable="yes">Print synta_x highlighting</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="margin_start">12</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox22">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label27">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Line Numbers</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="line_numbers_checkbutton">
+ <property name="label" translatable="yes">Print line nu_mbers</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="line_numbers_hbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0.47999998927116394</property>
+ <property name="label" translatable="yes" comments="'Number every' from 'Number every 3 lines' in the 'Text Editor' tab of the print preferences.">_Number every</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">line_numbers_spinbutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="line_numbers_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment1</property>
+ <property name="climb_rate">1</property>
+ <property name="value">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="'lines' from 'Number every 3 lines' in the 'Text Editor' tab of the print preferences.">lines</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox24">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label31">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Text Wrapping</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox25">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="text_wrapping_checkbutton">
+ <property name="label" translatable="yes">Enable text _wrapping</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox20">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkCheckButton" id="do_not_split_checkbutton">
+ <property name="label" translatable="yes">Do not _split words over two lines</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox39">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label45">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Page header</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="page_header_checkbutton">
+ <property name="label" translatable="yes">Print page _headers</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="margin_start">12</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox36">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkBox" id="vbox37">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label43">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Fonts</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkGrid" id="fonts_grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">12</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="body_font_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Body:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">body_fontbutton</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFontButton" id="body_fontbutton">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="font">Sans 12</property>
+ <property name="preview_text"/>
+ <property name="show_preview_entry">False</property>
+ <property name="use_font">True</property>
+ <property name="show_style">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="numbers_font_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Line numbers:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">numbers_fontbutton</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFontButton" id="numbers_fontbutton">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="font">Sans 12</property>
+ <property name="preview_text"/>
+ <property name="show_preview_entry">False</property>
+ <property name="use_font">True</property>
+ <property name="show_style">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="headers_font_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">He_aders and footers:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">headers_fontbutton</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFontButton" id="headers_fontbutton">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="font">Sans 12</property>
+ <property name="preview_text"/>
+ <property name="show_preview_entry">False</property>
+ <property name="use_font">True</property>
+ <property name="show_style">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="restore_button">
+ <property name="label" translatable="yes">_Restore Default Fonts</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/gedit/resources/ui/gedit-print-preview.ui b/gedit/resources/ui/gedit-print-preview.ui
new file mode 100644
index 0000000..9745fea
--- /dev/null
+++ b/gedit/resources/ui/gedit-print-preview.ui
@@ -0,0 +1,417 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.8 -->
+ <template class="GeditPrintPreview" parent="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <child>
+ <object class="GtkToolbar" id="toolbar1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <property name="toolbar_style">both-horiz</property>
+ <child>
+ <object class="GtkToolItem" id="toolbutton1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vexpand">False</property>
+ <property name="valign">center</property>
+ <property name="margin-end">2</property>
+ <style>
+ <class name="raised"/>
+ </style>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="prev_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Show the previous page</property>
+ <property name="use_underline">True</property>
+ <property name="image">previous_image</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="next_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Show the next page</property>
+ <property name="use_underline">True</property>
+ <property name="image">next_image</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="toolseparator1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolItem" id="toolbutton2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <child>
+ <object class="GtkGrid" id="grid1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <property name="valign">center</property>
+ <property name="column_spacing">4</property>
+ <child>
+ <object class="GtkEntry" id="page_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <property name="max_length">6</property>
+ <property name="width_chars">3</property>
+ <property name="primary_icon_tooltip_text" translatable="yes">Current page (Alt+P)</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <property name="label" translatable="yes" comments="the &quot;of&quot; from &quot;1 of 19&quot; in print preview">of</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="last_page_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="last-atkobject">
+ <property name="AtkObject::accessible-name" translatable="yes">Page total</property>
+ <property name="AtkObject::accessible-description" translatable="yes">The total number of pages in the document</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="toolseparator2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolItem" id="toolbutton3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vexpand">False</property>
+ <property name="valign">center</property>
+ <property name="margin-end">2</property>
+ <style>
+ <class name="raised"/>
+ </style>
+ <child>
+ <object class="GtkBox" id="box2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="multi_pages_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Show multiple pages</property>
+ <property name="use_underline">True</property>
+ <property name="image">multi_image</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="toolseparator3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolItem" id="toolbutton4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vexpand">False</property>
+ <property name="valign">center</property>
+ <property name="margin-end">2</property>
+ <style>
+ <class name="raised"/>
+ </style>
+ <child>
+ <object class="GtkBox" id="box3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="zoom_one_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Zoom 1:1</property>
+ <property name="use_underline">True</property>
+ <property name="image">zoom_one_image</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="zoom_fit_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Zoom to fit the whole page</property>
+ <property name="use_underline">True</property>
+ <property name="image">zoom_fit_image</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="zoom_in_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Zoom the page in</property>
+ <property name="use_underline">True</property>
+ <property name="image">zoom_in_image</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="zoom_out_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Zoom the page out</property>
+ <property name="use_underline">True</property>
+ <property name="image">zoom_out_image</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="toolseparator4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolItem" id="toolbutton5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vexpand">False</property>
+ <property name="valign">center</property>
+ <property name="margin-end">2</property>
+ <style>
+ <class name="raised"/>
+ </style>
+ <child>
+ <object class="GtkBox" id="box4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="close_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Close print preview</property>
+ <property name="label" translatable="yes">_Close Preview</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">False</property>
+ </packing>
+ </child>
+ <style>
+ <class name="inline-toolbar"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkLayout" id="layout">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK | GDK_STRUCTURE_MASK</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="layout1-atkobject">
+ <property name="AtkObject::accessible-name" translatable="yes">Page Preview</property>
+ <property name="AtkObject::accessible-description" translatable="yes">The preview of a page in the document to be printed</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </template>
+ <object class="GtkImage" id="previous_image">
+ <property name="visible">True</property>
+ <property name="icon_name">go-previous-symbolic</property>
+ </object>
+ <object class="GtkImage" id="next_image">
+ <property name="visible">True</property>
+ <property name="icon_name">go-next-symbolic</property>
+ </object>
+ <object class="GtkImage" id="multi_image">
+ <property name="visible">True</property>
+ <property name="icon_name">view-grid-symbolic</property>
+ </object>
+ <object class="GtkImage" id="zoom_in_image">
+ <property name="visible">True</property>
+ <property name="icon_name">zoom-in-symbolic</property>
+ </object>
+ <object class="GtkImage" id="zoom_out_image">
+ <property name="visible">True</property>
+ <property name="icon_name">zoom-out-symbolic</property>
+ </object>
+ <object class="GtkImage" id="zoom_one_image">
+ <property name="visible">True</property>
+ <property name="icon_name">zoom-original-symbolic</property>
+ </object>
+ <object class="GtkImage" id="zoom_fit_image">
+ <property name="visible">True</property>
+ <property name="icon_name">zoom-fit-best-symbolic</property>
+ </object>
+</interface>
diff --git a/gedit/resources/ui/gedit-replace-dialog.ui b/gedit/resources/ui/gedit-replace-dialog.ui
new file mode 100644
index 0000000..74e4239
--- /dev/null
+++ b/gedit/resources/ui/gedit-replace-dialog.ui
@@ -0,0 +1,246 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <template class="GeditReplaceDialog" parent="GtkDialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Find and Replace</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="close_button">
+ <property name="label" translatable="yes">_Close</property>
+ <property name="use_action_appearance">False</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="no_show_all">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="replace_all_button">
+ <property name="label" translatable="yes">Replace _All</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="replace_button">
+ <property name="label" translatable="yes">_Replace</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="find_button">
+ <property name="label" translatable="yes" context="label of the find button">_Find</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="row_spacing">12</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="search_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes" context="label on the left of the GtkEntry containing text to search">F_ind </property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="replace_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Replace _with </property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="checkbox_grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">4</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkCheckButton" id="match_case_checkbutton">
+ <property name="label" translatable="yes">_Match case</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="entire_word_checkbutton">
+ <property name="label" translatable="yes">Match _entire word only</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="regex_checkbutton">
+ <property name="label" translatable="yes">Re_gular expression</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="backwards_checkbutton">
+ <property name="label" translatable="yes">Search _backwards</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="wrap_around_checkbutton">
+ <property name="label" translatable="yes">Wra_p around</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">close_button</action-widget>
+ <action-widget response="102">replace_all_button</action-widget>
+ <action-widget response="101">replace_button</action-widget>
+ <action-widget response="100">find_button</action-widget>
+ </action-widgets>
+ </template>
+</interface>
diff --git a/gedit/resources/ui/gedit-shortcuts.ui b/gedit/resources/ui/gedit-shortcuts.ui
new file mode 100644
index 0000000..0d2f59d
--- /dev/null
+++ b/gedit/resources/ui/gedit-shortcuts.ui
@@ -0,0 +1,487 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.17 -->
+ <object class="GtkShortcutsWindow" id="shortcuts-gedit">
+ <property name="modal">1</property>
+ <child>
+ <object class="GtkShortcutsSection">
+ <property name="visible">1</property>
+ <property name="section-name">shortcuts</property>
+ <property name="max-height">12</property>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes" context="shortcut window">Documents</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;T</property>
+ <property name="title" translatable="yes" context="shortcut window">Create a new document in a tab</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;O</property>
+ <property name="title" translatable="yes" context="shortcut window">Open a document</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;S</property>
+ <property name="title" translatable="yes" context="shortcut window">Save the document</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;&lt;shift&gt;S</property>
+ <property name="title" translatable="yes" context="shortcut window">Save the document with a new filename</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;&lt;shift&gt;L</property>
+ <property name="title" translatable="yes" context="shortcut window">Save all the documents</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;W</property>
+ <property name="title" translatable="yes" context="shortcut window">Close the document</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;&lt;shift&gt;W</property>
+ <property name="title" translatable="yes" context="shortcut window">Close all the documents</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;&lt;shift&gt;T</property>
+ <property name="title" translatable="yes" context="shortcut window">Reopen the most recently closed document</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;&lt;Alt&gt;Page_Down</property>
+ <property name="title" translatable="yes" context="shortcut window">Switch to the next document</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;&lt;Alt&gt;Page_Up</property>
+ <property name="title" translatable="yes" context="shortcut window">Switch to the previous document</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;Alt&gt;1...9</property>
+ <property name="title" translatable="yes" context="shortcut window">Switch to the first — ninth document</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes" context="shortcut window">Windows and Panels</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;N</property>
+ <property name="title" translatable="yes" context="shortcut window">Create a new document in a window</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;&lt;alt&gt;N</property>
+ <property name="title" translatable="yes" context="shortcut window">Create a new tab group</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">F9</property>
+ <property name="title" translatable="yes" context="shortcut window">Show side panel</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;F9</property>
+ <property name="title" translatable="yes" context="shortcut window">Show bottom panel</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">F11</property>
+ <property name="title" translatable="yes" context="shortcut window">Fullscreen on / off</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;Q</property>
+ <property name="title" translatable="yes" context="shortcut window">Quit the application</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes" context="shortcut window">Find and Replace</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;F</property>
+ <property name="title" translatable="yes" context="shortcut window">Find</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;G</property>
+ <property name="title" translatable="yes" context="shortcut window">Find the next match</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;&lt;Shift&gt;G</property>
+ <property name="title" translatable="yes" context="shortcut window">Find the previous match</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;H</property>
+ <property name="title" translatable="yes" context="shortcut window">Find and Replace</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;&lt;Shift&gt;K</property>
+ <property name="title" translatable="yes" context="shortcut window">Clear highlight</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes" context="shortcut window">Undo and Redo</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;Z</property>
+ <property name="title" translatable="yes" context="shortcut window">Undo previous command</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;&lt;shift&gt;Z</property>
+ <property name="title" translatable="yes" context="shortcut window">Redo previous command</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes" context="shortcut window">Selection</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;A</property>
+ <property name="title" translatable="yes" context="shortcut window">Select all text</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;backslash</property>
+ <property name="title" translatable="yes" context="shortcut window">Unselect all text</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes" context="shortcut window">Copy and Paste</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;C</property>
+ <property name="title" translatable="yes" context="shortcut window">Copy selected text to clipboard</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;X</property>
+ <property name="title" translatable="yes" context="shortcut window">Cut selected text to clipboard</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;V</property>
+ <property name="title" translatable="yes" context="shortcut window">Paste text from clipboard</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes" context="shortcut window">Navigation</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;I</property>
+ <property name="title" translatable="yes" context="shortcut window">Go to line</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">Home</property>
+ <property name="title" translatable="yes" context="shortcut window">Move to the beginning of the current line</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">End</property>
+ <property name="title" translatable="yes" context="shortcut window">Move to the end of the current line</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;Home</property>
+ <property name="title" translatable="yes" context="shortcut window">Move to the beginning of the document</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;End</property>
+ <property name="title" translatable="yes" context="shortcut window">Move to the end of the document</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;alt&gt;&lt;shift&gt;Up</property>
+ <property name="title" translatable="yes" context="shortcut window">Move viewport up within the file</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;alt&gt;&lt;shift&gt;Down</property>
+ <property name="title" translatable="yes" context="shortcut window">Move viewport down within the file</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;alt&gt;&lt;shift&gt;End</property>
+ <property name="title" translatable="yes" context="shortcut window">Move viewport to end of file</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;alt&gt;&lt;shift&gt;Home</property>
+ <property name="title" translatable="yes" context="shortcut window">Move viewport to beginning of file</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;percent</property>
+ <property name="title" translatable="yes" context="shortcut window">Move to matching bracket</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;Shift&gt;&lt;Ctrl&gt;&lt;Alt&gt;Page_Up</property>
+ <property name="title" translatable="yes" context="shortcut window">Go to previous tab group</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;Shift&gt;&lt;Ctrl&gt;&lt;Alt&gt;Page_Down</property>
+ <property name="title" translatable="yes" context="shortcut window">Go to next tab group</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes" context="shortcut window">Editing</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">Insert</property>
+ <property name="title" translatable="yes" context="shortcut window">Toggle insert / overwrite</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">F7</property>
+ <property name="title" translatable="yes" context="shortcut window">Toggle cursor visibility</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;D</property>
+ <property name="title" translatable="yes" context="shortcut window">Delete current line</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;alt&gt;Up</property>
+ <property name="title" translatable="yes" context="shortcut window">Move current line up</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;alt&gt;Down</property>
+ <property name="title" translatable="yes" context="shortcut window">Move current line down</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;alt&gt;Left</property>
+ <property name="title" translatable="yes" context="shortcut window">Move current word left</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;alt&gt;Right</property>
+ <property name="title" translatable="yes" context="shortcut window">Move current word right</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;U</property>
+ <property name="title" translatable="yes" context="shortcut window">Convert to uppercase</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;L</property>
+ <property name="title" translatable="yes" context="shortcut window">Convert to lowercase</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;asciitilde</property>
+ <property name="title" translatable="yes" context="shortcut window">Invert case</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;&lt;shift&gt;a</property>
+ <property name="title" translatable="yes" context="shortcut window">Increment number at cursor</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;&lt;shift&gt;x</property>
+ <property name="title" translatable="yes" context="shortcut window">Decrement number at cursor</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes" context="shortcut window">Tools</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;shift&gt;F7</property>
+ <property name="title" translatable="yes" context="shortcut window">Check spelling</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;P</property>
+ <property name="title" translatable="yes" context="shortcut window">Print the document</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;space</property>
+ <property name="title" translatable="yes" context="shortcut window">Show completion window</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes" context="shortcut window">General</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">F1</property>
+ <property name="title" translatable="yes" context="shortcut window">Show help</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">F10</property>
+ <property name="title" translatable="yes" context="shortcut window">Open menu</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;question</property>
+ <property name="title" translatable="yes" context="shortcut window">Keyboard shortcuts</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/gedit/resources/ui/gedit-status-menu-button.ui b/gedit/resources/ui/gedit-status-menu-button.ui
new file mode 100644
index 0000000..54ae28f
--- /dev/null
+++ b/gedit/resources/ui/gedit-status-menu-button.ui
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.8 -->
+ <template class="GeditStatusMenuButton" parent="GtkMenuButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <property name="receives_default">True</property>
+ <property name="relief">none</property>
+ <property name="direction">up</property>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkLabel" id="label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <property name="halign">start</property>
+ <property name="valign">baseline</property>
+ <property name="label">label</property>
+ <property name="single_line_mode">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="arrow">
+ <property name="visible">True</property>
+ <property name="valign">baseline</property>
+ <property name="icon_name">pan-down-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/gedit/resources/ui/gedit-statusbar.ui b/gedit/resources/ui/gedit-statusbar.ui
new file mode 100644
index 0000000..a290e7f
--- /dev/null
+++ b/gedit/resources/ui/gedit-statusbar.ui
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="GeditStatusbar" parent="GtkStatusbar">
+ <property name="can_focus">False</property>
+ <property name="margin_top">0</property>
+ <property name="margin_end">0</property>
+ <property name="margin_bottom">0</property>
+ <property name="margin_start">0</property>
+ <child>
+ <object class="GtkFrame" id="error_frame">
+ <property name="visible">False</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkImage" id="error_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">dialog-error-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="state_frame">
+ <property name="visible">False</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImage" id="load_image">
+ <property name="visible">False</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-open-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="save_image">
+ <property name="visible">False</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-save-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="print_image">
+ <property name="visible">False</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">printer-printing-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/gedit/resources/ui/gedit-tab-label.ui b/gedit/resources/ui/gedit-tab-label.ui
new file mode 100644
index 0000000..e274cca
--- /dev/null
+++ b/gedit/resources/ui/gedit-tab-label.ui
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.10 -->
+ <template class="GeditTabLabel" parent="GtkBox">
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="orientation">horizontal</property>
+ <child type="center">
+ <object class="GtkLabel" id="label">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="icon">
+ <property name="visible">False</property>
+ </object>
+ <packing>
+ <property name="pack_type">start</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinner" id="spinner">
+ <property name="visible">False</property>
+ </object>
+ <packing>
+ <property name="pack_type">start</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="close_button">
+ <property name="visible">True</property>
+ <property name="relief">none</property>
+ <property name="focus-on-click">False</property>
+ <property name="tooltip_text" translatable="yes">Close Document</property>
+ <property name="image">close_button_image</property>
+ <style>
+ <class name="small-button"/>
+ <class name="flat"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ </template>
+ <object class="GtkImage" id="close_button_image">
+ <property name="visible">True</property>
+ <property name="icon_name">window-close-symbolic</property>
+ </object>
+</interface>
diff --git a/gedit/resources/ui/gedit-view-frame.ui b/gedit/resources/ui/gedit-view-frame.ui
new file mode 100644
index 0000000..486be6c
--- /dev/null
+++ b/gedit/resources/ui/gedit-view-frame.ui
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.8 -->
+ <template class="GeditViewFrame" parent="GtkOverlay">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_window">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="overlay_scrolling">False</property>
+ <child>
+ <object class="GeditView" id="view">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="input-hints">word-completion</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="overlay">
+ <object class="GtkRevealer" id="revealer">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="valign">start</property>
+ <child>
+ <object class="GtkFrame" id="frame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="shadow_type">none</property>
+ <style>
+ <class name="gedit-search-slider"/>
+ </style>
+ <child>
+ <object class="GtkBox" id="hbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">horizontal</property>
+ <style>
+ <class name="linked"/>
+ </style>
+ <child>
+ <object class="GdTaggedEntry" id="search_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary-icon-activatable">True</property>
+ <property name="primary-icon-sensitive">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="go_up_button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImage" id="up_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_size">1</property>
+ <property name="icon_name">go-up-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="go_down_button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImage" id="down_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_size">1</property>
+ <property name="icon_name">go-down-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/gedit/resources/ui/gedit-window.ui b/gedit/resources/ui/gedit-window.ui
new file mode 100644
index 0000000..f2dcb1d
--- /dev/null
+++ b/gedit/resources/ui/gedit-window.ui
@@ -0,0 +1,404 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.10 -->
+ <template class="GeditWindow" parent="GtkApplicationWindow">
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <child type="titlebar">
+ <object class="GtkPaned" id="titlebar_paned">
+ <property name="visible">True</property>
+ <property name="position" bind-source="hpaned" bind-property="position" bind-flags="bidirectional|sync-create"/>
+ <child>
+ <object class="GtkHeaderBar" id="side_headerbar">
+ <property name="visible" bind-source="side_panel" bind-property="visible" bind-flags="sync-create"/>
+ <property name="show_close_button">True</property>
+ <property name="title" translatable="yes">Documents</property>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHeaderBar" id="headerbar">
+ <property name="visible">True</property>
+ <property name="show_close_button">True</property>
+ <child>
+ <object class="GtkButton" id="new_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Create a new document</property>
+ <property name="action_name">win.new-tab</property>
+ <property name="image">new_tab_image</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="new_button_a11y">
+ <property name="accessible-name" translatable="yes">New</property>
+ <property name="accessible-description" translatable="yes">Create a new document</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="gear_button">
+ <property name="visible">True</property>
+ <property name="use_popover">True</property>
+ <property name="image">menu_image</property>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="save_button">
+ <property name="label" translatable="yes">_Save</property>
+ <property name="use-underline">True</property>
+ <property name="visible">True</property>
+ <property name="tooltip_text" translatable="yes">Save the current file</property>
+ <property name="action_name">win.save</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="save_button_a11y">
+ <property name="accessible-name" translatable="yes">Save</property>
+ <property name="accessible-description" translatable="yes">Save the current file</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkOverlay" id="fullscreen_overlay">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <child>
+ <object class="GtkBox" id="main_box">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkPaned" id="hpaned">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <style>
+ <class name="gedit-side-panel-paned"/>
+ </style>
+ <child>
+ <object class="GtkBox" id="side_panel_box">
+ <property name="visible" bind-source="side_panel" bind-property="visible" bind-flags="sync-create"/>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GeditMenuStackSwitcher" id="side_panel_inline_stack_switcher">
+ <property name="visible">False</property>
+ <property name="stack">side_panel</property>
+ <style>
+ <class name="gedit-side-panel-stack-switcher"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStack" id="side_panel">
+ <property name="visible">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="content_box">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkPaned" id="vpaned">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <property name="orientation">vertical</property>
+ <style>
+ <class name="gedit-bottom-panel-paned"/>
+ </style>
+ <child>
+ <object class="GeditMultiNotebook" id="multi_notebook">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="bottom_panel_box">
+ <property name="visible" bind-source="bottom_panel" bind-property="visible" bind-flags="sync-create"/>
+ <property name="orientation">horizontal</property>
+ <style>
+ <class name="gedit-bottom-panel"/>
+ </style>
+ <child>
+ <object class="GtkBox" id="bottom_panel_stack_box">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkStack" id="bottom_panel">
+ <property name="visible">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GeditNotebookStackSwitcher" id="bottom_panel_stack_switcher">
+ <property name="visible">True</property>
+ <property name="stack">bottom_panel</property>
+ <style>
+ <class name="gedit-bottom-panel-stack-switcher"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="bottom_panel_sidebar">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkButton" id="bottom_panel_close_button">
+ <property name="visible">True</property>
+ <property name="relief">none</property>
+ <property name="focus-on-click">False</property>
+ <property name="tooltip-text" translatable="yes">Hide panel</property>
+ <property name="action_name">win.bottom-panel</property>
+ <property name="image">close_button_image</property>
+ <style>
+ <class name="small-button"/>
+ <class name="flat"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GeditStatusbar" id="statusbar">
+ <property name="visible">True</property>
+ <child>
+ <object class="GeditStatusMenuButton" id="tab_width_button">
+ <property name="visible">True</property>
+ <property name="use_popover">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GeditStatusMenuButton" id="language_button">
+ <property name="visible">True</property>
+ <property name="use_popover">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="overlay">
+ <object class="GtkEventBox" id="fullscreen_eventbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <child>
+ <object class="GtkRevealer" id="fullscreen_revealer">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="reveal_child">False</property>
+ <property name="valign">start</property>
+ <child>
+ <object class="GtkHeaderBar" id="fullscreen_headerbar">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkButton" id="fullscreen_new_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Create a new document</property>
+ <property name="action_name">win.new-tab</property>
+ <property name="image">fullscreen_new_tab_image</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="fullscreen_new_button_a11y">
+ <property name="accessible-name" translatable="yes">New</property>
+ <property name="accessible-description" translatable="yes">Create a new document</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="leave_fullscreen_button">
+ <property name="visible">True</property>
+ <property name="tooltip_text" translatable="yes">Leave Fullscreen</property>
+ <property name="action_name">win.leave-fullscreen</property>
+ <property name="image">leave_fullscreen_image</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="leave_fullscreen_button_a11y">
+ <property name="accessible-name" translatable="yes">Leave Fullscreen</property>
+ <property name="accessible-description" translatable="yes">Leave Fullscreen</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="fullscreen_gear_button">
+ <property name="visible">True</property>
+ <property name="image">fullscreen_menu_image</property>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="fullscreen_save_button">
+ <property name="label" translatable="yes">Save</property>
+ <property name="visible">True</property>
+ <property name="tooltip_text" translatable="yes">Save the current file</property>
+ <property name="action_name">win.save</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="fullscreen_save_button_a11y">
+ <property name="accessible-name" translatable="yes">Save</property>
+ <property name="accessible-description" translatable="yes">Save the current file</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkImage" id="new_tab_image">
+ <property name="visible">True</property>
+ <property name="icon_name">tab-new-symbolic</property>
+ </object>
+ <object class="GtkImage" id="menu_image">
+ <property name="visible">True</property>
+ <property name="icon_name">open-menu-symbolic</property>
+ </object>
+ <object class="GtkImage" id="fullscreen_new_tab_image">
+ <property name="visible">True</property>
+ <property name="icon_name">tab-new-symbolic</property>
+ </object>
+ <object class="GtkImage" id="fullscreen_menu_image">
+ <property name="visible">True</property>
+ <property name="icon_name">open-menu-symbolic</property>
+ </object>
+ <object class="GtkImage" id="leave_fullscreen_image">
+ <property name="visible">True</property>
+ <property name="icon_name">view-restore-symbolic</property>
+ </object>
+ <object class="GtkImage" id="close_button_image">
+ <property name="visible">True</property>
+ <property name="icon_name">window-close-symbolic</property>
+ </object>
+ <object class="GtkSizeGroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="side_headerbar"/>
+ <widget name="side_panel"/>
+ </widgets>
+ </object>
+</interface>
+