From ba429d344132c088177e853cce8ff7181570b221 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 19:42:51 +0200 Subject: Adding upstream version 44.2. Signed-off-by: Daniel Baumann --- gedit/Gedit-3.0.metadata | 23 + gedit/Gedit.py | 99 + gedit/gedit-app-activatable.c | 117 + gedit/gedit-app-activatable.h | 64 + gedit/gedit-app-osx.h | 41 + gedit/gedit-app-osx.m | 603 ++++ gedit/gedit-app-private.h | 50 + gedit/gedit-app-win32.c | 138 + gedit/gedit-app-win32.h | 39 + gedit/gedit-app.c | 1700 +++++++++++ gedit/gedit-app.h | 82 + gedit/gedit-close-confirmation-dialog.c | 548 ++++ gedit/gedit-close-confirmation-dialog.h | 46 + gedit/gedit-commands-documents.c | 106 + gedit/gedit-commands-edit.c | 205 ++ gedit/gedit-commands-file-print.c | 49 + gedit/gedit-commands-file.c | 2169 +++++++++++++++ gedit/gedit-commands-help.c | 132 + gedit/gedit-commands-private.h | 172 ++ gedit/gedit-commands-search.c | 696 +++++ gedit/gedit-commands-view.c | 181 ++ gedit/gedit-commands.h | 63 + gedit/gedit-debug.c | 232 ++ gedit/gedit-debug.h | 83 + gedit/gedit-dirs.c | 176 ++ gedit/gedit-dirs.h | 55 + gedit/gedit-document-private.h | 57 + gedit/gedit-document.c | 1276 +++++++++ gedit/gedit-document.h | 77 + gedit/gedit-documents-panel.c | 1740 ++++++++++++ gedit/gedit-documents-panel.h | 40 + gedit/gedit-encoding-items.c | 108 + gedit/gedit-encoding-items.h | 40 + gedit/gedit-encodings-combo-box.c | 439 +++ gedit/gedit-encodings-combo-box.h | 43 + gedit/gedit-encodings-dialog.c | 884 ++++++ gedit/gedit-encodings-dialog.h | 37 + gedit/gedit-factory.c | 69 + gedit/gedit-factory.h | 53 + gedit/gedit-file-chooser-dialog-gtk.c | 465 ++++ gedit/gedit-file-chooser-dialog-gtk.h | 45 + gedit/gedit-file-chooser-dialog.c | 258 ++ gedit/gedit-file-chooser-dialog.h | 123 + gedit/gedit-file-chooser-open-dialog.c | 125 + gedit/gedit-file-chooser-open-dialog.h | 58 + gedit/gedit-file-chooser-open-native.c | 84 + gedit/gedit-file-chooser-open-native.h | 58 + gedit/gedit-file-chooser-open.c | 77 + gedit/gedit-file-chooser-open.h | 58 + gedit/gedit-file-chooser.c | 785 ++++++ gedit/gedit-file-chooser.h | 91 + gedit/gedit-history-entry.c | 470 ++++ gedit/gedit-history-entry.h | 55 + gedit/gedit-io-error-info-bar.c | 542 ++++ gedit/gedit-io-error-info-bar.h | 47 + gedit/gedit-menu-extension.c | 187 ++ gedit/gedit-menu-extension.h | 47 + gedit/gedit-menu-stack-switcher.c | 420 +++ gedit/gedit-menu-stack-switcher.h | 43 + gedit/gedit-message-bus.c | 1259 +++++++++ gedit/gedit-message-bus.h | 154 + gedit/gedit-message.c | 321 +++ gedit/gedit-message.h | 77 + gedit/gedit-multi-notebook.c | 1125 ++++++++ gedit/gedit-multi-notebook.h | 148 + gedit/gedit-notebook-popup-menu.c | 297 ++ gedit/gedit-notebook-popup-menu.h | 39 + gedit/gedit-notebook-stack-switcher.c | 362 +++ gedit/gedit-notebook-stack-switcher.h | 71 + gedit/gedit-notebook.c | 672 +++++ gedit/gedit-notebook.h | 100 + gedit/gedit-plugins-engine.c | 136 + gedit/gedit-plugins-engine.h | 39 + gedit/gedit-preferences-dialog.c | 841 ++++++ gedit/gedit-preferences-dialog.h | 35 + gedit/gedit-print-job.c | 819 ++++++ gedit/gedit-print-job.h | 71 + gedit/gedit-print-preview.c | 1158 ++++++++ gedit/gedit-print-preview.h | 39 + gedit/gedit-recent-osx.c | 250 ++ gedit/gedit-recent-osx.h | 54 + gedit/gedit-recent.c | 113 + gedit/gedit-recent.h | 38 + gedit/gedit-replace-dialog.c | 819 ++++++ gedit/gedit-replace-dialog.h | 58 + gedit/gedit-settings.c | 322 +++ gedit/gedit-settings.h | 106 + gedit/gedit-status-menu-button.c | 146 + gedit/gedit-status-menu-button.h | 44 + gedit/gedit-statusbar.c | 241 ++ gedit/gedit-statusbar.h | 53 + gedit/gedit-tab-label.c | 295 ++ gedit/gedit-tab-label.h | 41 + gedit/gedit-tab-private.h | 81 + gedit/gedit-tab.c | 3057 ++++++++++++++++++++ gedit/gedit-tab.h | 77 + gedit/gedit-utils.c | 542 ++++ gedit/gedit-utils.h | 56 + gedit/gedit-view-activatable.c | 96 + gedit/gedit-view-activatable.h | 47 + gedit/gedit-view-frame.c | 1592 +++++++++++ gedit/gedit-view-frame.h | 45 + gedit/gedit-view.c | 605 ++++ gedit/gedit-view.h | 67 + gedit/gedit-window-activatable.c | 120 + gedit/gedit-window-activatable.h | 49 + gedit/gedit-window.c | 3561 ++++++++++++++++++++++++ gedit/gedit-window.h | 178 ++ gedit/gedit.c | 203 ++ gedit/meson.build | 263 ++ gedit/resources/css/gedit-style-osx.css | 129 + gedit/resources/css/gedit-style.css | 31 + gedit/resources/css/gedit.adwaita.css | 61 + gedit/resources/css/gedit.highcontrast.css | 5 + gedit/resources/gedit.gresource.xml.in | 23 + gedit/resources/gtk/menus-common.ui | 376 +++ gedit/resources/gtk/menus-traditional.ui | 143 + gedit/resources/gtk/menus.ui | 132 + gedit/resources/meson.build | 21 + gedit/resources/ui/gedit-encodings-dialog.ui | 278 ++ gedit/resources/ui/gedit-preferences-dialog.ui | 675 +++++ gedit/resources/ui/gedit-print-preferences.ui | 518 ++++ gedit/resources/ui/gedit-print-preview.ui | 417 +++ gedit/resources/ui/gedit-replace-dialog.ui | 246 ++ gedit/resources/ui/gedit-shortcuts.ui | 487 ++++ gedit/resources/ui/gedit-status-menu-button.ui | 51 + gedit/resources/ui/gedit-statusbar.ui | 90 + gedit/resources/ui/gedit-tab-label.ui | 50 + gedit/resources/ui/gedit-view-frame.ui | 89 + gedit/resources/ui/gedit-window.ui | 404 +++ 130 files changed, 41978 insertions(+) create mode 100644 gedit/Gedit-3.0.metadata create mode 100644 gedit/Gedit.py create mode 100644 gedit/gedit-app-activatable.c create mode 100644 gedit/gedit-app-activatable.h create mode 100644 gedit/gedit-app-osx.h create mode 100644 gedit/gedit-app-osx.m create mode 100644 gedit/gedit-app-private.h create mode 100644 gedit/gedit-app-win32.c create mode 100644 gedit/gedit-app-win32.h create mode 100644 gedit/gedit-app.c create mode 100644 gedit/gedit-app.h create mode 100644 gedit/gedit-close-confirmation-dialog.c create mode 100644 gedit/gedit-close-confirmation-dialog.h create mode 100644 gedit/gedit-commands-documents.c create mode 100644 gedit/gedit-commands-edit.c create mode 100644 gedit/gedit-commands-file-print.c create mode 100644 gedit/gedit-commands-file.c create mode 100644 gedit/gedit-commands-help.c create mode 100644 gedit/gedit-commands-private.h create mode 100644 gedit/gedit-commands-search.c create mode 100644 gedit/gedit-commands-view.c create mode 100644 gedit/gedit-commands.h create mode 100644 gedit/gedit-debug.c create mode 100644 gedit/gedit-debug.h create mode 100644 gedit/gedit-dirs.c create mode 100644 gedit/gedit-dirs.h create mode 100644 gedit/gedit-document-private.h create mode 100644 gedit/gedit-document.c create mode 100644 gedit/gedit-document.h create mode 100644 gedit/gedit-documents-panel.c create mode 100644 gedit/gedit-documents-panel.h create mode 100644 gedit/gedit-encoding-items.c create mode 100644 gedit/gedit-encoding-items.h create mode 100644 gedit/gedit-encodings-combo-box.c create mode 100644 gedit/gedit-encodings-combo-box.h create mode 100644 gedit/gedit-encodings-dialog.c create mode 100644 gedit/gedit-encodings-dialog.h create mode 100644 gedit/gedit-factory.c create mode 100644 gedit/gedit-factory.h create mode 100644 gedit/gedit-file-chooser-dialog-gtk.c create mode 100644 gedit/gedit-file-chooser-dialog-gtk.h create mode 100644 gedit/gedit-file-chooser-dialog.c create mode 100644 gedit/gedit-file-chooser-dialog.h create mode 100644 gedit/gedit-file-chooser-open-dialog.c create mode 100644 gedit/gedit-file-chooser-open-dialog.h create mode 100644 gedit/gedit-file-chooser-open-native.c create mode 100644 gedit/gedit-file-chooser-open-native.h create mode 100644 gedit/gedit-file-chooser-open.c create mode 100644 gedit/gedit-file-chooser-open.h create mode 100644 gedit/gedit-file-chooser.c create mode 100644 gedit/gedit-file-chooser.h create mode 100644 gedit/gedit-history-entry.c create mode 100644 gedit/gedit-history-entry.h create mode 100644 gedit/gedit-io-error-info-bar.c create mode 100644 gedit/gedit-io-error-info-bar.h create mode 100644 gedit/gedit-menu-extension.c create mode 100644 gedit/gedit-menu-extension.h create mode 100644 gedit/gedit-menu-stack-switcher.c create mode 100644 gedit/gedit-menu-stack-switcher.h create mode 100644 gedit/gedit-message-bus.c create mode 100644 gedit/gedit-message-bus.h create mode 100644 gedit/gedit-message.c create mode 100644 gedit/gedit-message.h create mode 100644 gedit/gedit-multi-notebook.c create mode 100644 gedit/gedit-multi-notebook.h create mode 100644 gedit/gedit-notebook-popup-menu.c create mode 100644 gedit/gedit-notebook-popup-menu.h create mode 100644 gedit/gedit-notebook-stack-switcher.c create mode 100644 gedit/gedit-notebook-stack-switcher.h create mode 100644 gedit/gedit-notebook.c create mode 100644 gedit/gedit-notebook.h create mode 100644 gedit/gedit-plugins-engine.c create mode 100644 gedit/gedit-plugins-engine.h create mode 100644 gedit/gedit-preferences-dialog.c create mode 100644 gedit/gedit-preferences-dialog.h create mode 100644 gedit/gedit-print-job.c create mode 100644 gedit/gedit-print-job.h create mode 100644 gedit/gedit-print-preview.c create mode 100644 gedit/gedit-print-preview.h create mode 100644 gedit/gedit-recent-osx.c create mode 100644 gedit/gedit-recent-osx.h create mode 100644 gedit/gedit-recent.c create mode 100644 gedit/gedit-recent.h create mode 100644 gedit/gedit-replace-dialog.c create mode 100644 gedit/gedit-replace-dialog.h create mode 100644 gedit/gedit-settings.c create mode 100644 gedit/gedit-settings.h create mode 100644 gedit/gedit-status-menu-button.c create mode 100644 gedit/gedit-status-menu-button.h create mode 100644 gedit/gedit-statusbar.c create mode 100644 gedit/gedit-statusbar.h create mode 100644 gedit/gedit-tab-label.c create mode 100644 gedit/gedit-tab-label.h create mode 100644 gedit/gedit-tab-private.h create mode 100644 gedit/gedit-tab.c create mode 100644 gedit/gedit-tab.h create mode 100644 gedit/gedit-utils.c create mode 100644 gedit/gedit-utils.h create mode 100644 gedit/gedit-view-activatable.c create mode 100644 gedit/gedit-view-activatable.h create mode 100644 gedit/gedit-view-frame.c create mode 100644 gedit/gedit-view-frame.h create mode 100644 gedit/gedit-view.c create mode 100644 gedit/gedit-view.h create mode 100644 gedit/gedit-window-activatable.c create mode 100644 gedit/gedit-window-activatable.h create mode 100644 gedit/gedit-window.c create mode 100644 gedit/gedit-window.h create mode 100644 gedit/gedit.c create mode 100644 gedit/meson.build create mode 100644 gedit/resources/css/gedit-style-osx.css create mode 100644 gedit/resources/css/gedit-style.css create mode 100644 gedit/resources/css/gedit.adwaita.css create mode 100644 gedit/resources/css/gedit.highcontrast.css create mode 100644 gedit/resources/gedit.gresource.xml.in create mode 100644 gedit/resources/gtk/menus-common.ui create mode 100644 gedit/resources/gtk/menus-traditional.ui create mode 100644 gedit/resources/gtk/menus.ui create mode 100644 gedit/resources/meson.build create mode 100644 gedit/resources/ui/gedit-encodings-dialog.ui create mode 100644 gedit/resources/ui/gedit-preferences-dialog.ui create mode 100644 gedit/resources/ui/gedit-print-preferences.ui create mode 100644 gedit/resources/ui/gedit-print-preview.ui create mode 100644 gedit/resources/ui/gedit-replace-dialog.ui create mode 100644 gedit/resources/ui/gedit-shortcuts.ui create mode 100644 gedit/resources/ui/gedit-status-menu-button.ui create mode 100644 gedit/resources/ui/gedit-statusbar.ui create mode 100644 gedit/resources/ui/gedit-tab-label.ui create mode 100644 gedit/resources/ui/gedit-view-frame.ui create mode 100644 gedit/resources/ui/gedit-window.ui (limited to 'gedit') 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 +# +# 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 . + */ + +#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 . + */ + +#ifndef GEDIT_APP_ACTIVATABLE_H +#define GEDIT_APP_ACTIVATABLE_H + +#include +#include + +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 +#include +#include + +#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 + +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 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[] = { + "F", + NULL + }; + + const gchar *open_accels[] = { + "O", + NULL + }; + + const gchar *fullscreen_accels[] = { + "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 + * + * 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 . + */ + +#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 +#include + +#ifndef _WIN32_WINNT +# define _WIN32_WINNT 0x0501 +#endif + +#include + +#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 . + */ + +#include "config.h" + +#include "gedit-app.h" +#include "gedit-app-private.h" + +#include +#include +#include + +#include +#include +#include + +#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", "N"); + add_accelerator (GTK_APPLICATION (application), "app.quit", "Q"); + add_accelerator (GTK_APPLICATION (application), "app.help", "F1"); + add_accelerator (GTK_APPLICATION (application), "app.shortcuts", "question"); + + add_accelerator (GTK_APPLICATION (application), "win.hamburger-menu", "F10"); + add_accelerator (GTK_APPLICATION (application), "win.open", "O"); + add_accelerator (GTK_APPLICATION (application), "win.save", "S"); + add_accelerator (GTK_APPLICATION (application), "win.save-as", "S"); + add_accelerator (GTK_APPLICATION (application), "win.save-all", "L"); + add_accelerator (GTK_APPLICATION (application), "win.new-tab", "T"); + add_accelerator (GTK_APPLICATION (application), "win.reopen-closed-tab", "T"); + add_accelerator (GTK_APPLICATION (application), "win.close", "W"); + add_accelerator (GTK_APPLICATION (application), "win.close-all", "W"); + add_accelerator (GTK_APPLICATION (application), "win.print", "P"); + add_accelerator (GTK_APPLICATION (application), "win.find", "F"); + add_accelerator (GTK_APPLICATION (application), "win.find-next", "G"); + add_accelerator (GTK_APPLICATION (application), "win.find-prev", "G"); + add_accelerator (GTK_APPLICATION (application), "win.replace", "H"); + add_accelerator (GTK_APPLICATION (application), "win.clear-highlight", "K"); + add_accelerator (GTK_APPLICATION (application), "win.goto-line", "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", "F9"); + add_accelerator (GTK_APPLICATION (application), "win.fullscreen", "F11"); + add_accelerator (GTK_APPLICATION (application), "win.new-tab-group", "N"); + add_accelerator (GTK_APPLICATION (application), "win.previous-tab-group", "Page_Up"); + add_accelerator (GTK_APPLICATION (application), "win.next-tab-group", "Page_Down"); + add_accelerator (GTK_APPLICATION (application), "win.previous-document", "Page_Up"); + add_accelerator (GTK_APPLICATION (application), "win.next-document", "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 . + */ + +#ifndef GEDIT_APP_H +#define GEDIT_APP_H + +#include +#include + +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 . + */ + +#include "config.h" + +#include "gedit-close-confirmation-dialog.h" + +#include + +#include +#include +#include +#include +#include + +/* 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 ("", str, "", 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 ("", str, "", 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 . + */ + +#ifndef GEDIT_CLOSE_CONFIRMATION_DIALOG_H +#define GEDIT_CLOSE_CONFIRMATION_DIALOG_H + +#include +#include +#include + +#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 . + */ + +#include "config.h" + +#include "gedit-commands.h" +#include "gedit-commands-private.h" + +#include + +#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 . + */ + +#include "config.h" + +#include "gedit-commands.h" +#include "gedit-commands-private.h" + +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include "gedit-commands.h" +#include "gedit-commands-private.h" + +#include +#include + +#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 . + */ + +#include "config.h" + +#include "gedit-commands.h" +#include "gedit-commands-private.h" + +#include +#include + +#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 . + */ + +#ifndef GEDIT_COMMANDS_PRIVATE_H +#define GEDIT_COMMANDS_PRIVATE_H + +#include +#include +#include + +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 . + */ + +#include "config.h" + +#include "gedit-commands.h" +#include "gedit-commands-private.h" + +#include +#include +#include +#include + +#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 . + */ + +#include "config.h" +#include "gedit-commands.h" +#include "gedit-commands-private.h" +#include +#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 . + */ + +#ifndef GEDIT_COMMANDS_H +#define GEDIT_COMMANDS_H + +#include +#include + +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 . + */ + +#include "gedit-debug.h" +#include + +#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 + * GEDIT_DEBUG_PLUGINS environment variable. To enable output + * for all debug sections, set the GEDIT_DEBUG 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. + * + * Python + * + * A PyGObject override is provided that has the following signature: + * + * + * def debug_plugin_message(format_str, *format_args): + * #... + * + * + * + * It automatically supplies parameters @file, @line, and @function, and it + * formats format_str with the given format arguments. The syntax + * of the format string is the usual Python string formatting syntax described + * by 5.6.2. String Formatting Operations. + * + * 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 . + */ + +#ifndef GEDIT_DEBUG_H +#define GEDIT_DEBUG_H + +#include + +/** + * 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 GEDIT_DEBUG_PLUGINS + * environment variable enables all debugging output for the %GEDIT_DEBUG_PLUGINS + * section. Setting the special environment variable GEDIT_DEBUG + * 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 . + */ + +#include "config.h" + +#include "gedit-dirs.h" + +#ifdef OS_OSX +#include +#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 . + */ + + +#ifndef GEDIT_DIRS_H +#define GEDIT_DIRS_H + +#include + +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 . + */ + +#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 . + */ + +#include "config.h" +#include "gedit-document.h" +#include "gedit-document-private.h" +#include +#include +#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: + * + * + * 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. + * + * + * 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 . + */ + +#ifndef GEDIT_DOCUMENT_H +#define GEDIT_DOCUMENT_H + +#include + +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 + * + * 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 . + */ + +#include "config.h" + +#include "gedit-documents-panel.h" + +#include +#include + +#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 ("%s", 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 . + */ + +#ifndef GEDIT_DOCUMENTS_PANEL_H +#define GEDIT_DOCUMENTS_PANEL_H + +#include + +#include + +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 . + */ + +#include "gedit-encoding-items.h" + +#include + +#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 . + */ + +#ifndef GEDIT_ENCODING_ITEMS_H +#define GEDIT_ENCODING_ITEMS_H + +#include + +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 . + */ + +#include "gedit-encodings-combo-box.h" +#include +#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 . + */ + +#ifndef GEDIT_ENCODINGS_COMBO_BOX_H +#define GEDIT_ENCODINGS_COMBO_BOX_H + +#include + +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 . + */ + +#include "config.h" + +#include "gedit-encodings-dialog.h" + +#include +#include +#include + +#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 . + */ + +#ifndef GEDIT_ENCODINGS_DIALOG_H +#define GEDIT_ENCODINGS_DIALOG_H + +#include + +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 + * + * 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 . + */ + +#include "gedit-factory.h" +#include +#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 + * + * 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 . + */ + +#ifndef GEDIT_FACTORY_H +#define GEDIT_FACTORY_H + +#include + +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 . + */ + +/* TODO: Override set_extra_widget */ +/* TODO: add encoding property */ + +#include "config.h" +#include "gedit-file-chooser-dialog-gtk.h" +#include +#include +#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 . + */ + +#ifndef GEDIT_FILE_CHOOSER_DIALOG_GTK_H +#define GEDIT_FILE_CHOOSER_DIALOG_GTK_H + +#include +#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 . + */ + +#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 . + */ + +#ifndef GEDIT_FILE_CHOOSER_DIALOG_H +#define GEDIT_FILE_CHOOSER_DIALOG_H + +#include + +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 + * + * 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 . + */ + +#include "gedit-file-chooser-open-dialog.h" +#include +#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 + * + * 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 . + */ + +#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 + * + * 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 . + */ + +#include "gedit-file-chooser-open-native.h" +#include + +/* 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 + * + * 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 . + */ + +#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 + * + * 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 . + */ + +#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 + * + * 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 . + */ + +#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 + * + * 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 . + */ + +#include "gedit-file-chooser.h" +#include +#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 + * + * 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 . + */ + +#ifndef GEDIT_FILE_CHOOSER_H +#define GEDIT_FILE_CHOOSER_H + +#include + +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 . + */ + +#include "config.h" +#include "gedit-history-entry.h" +#include +#include + +#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 . + */ + +#ifndef GEDIT_HISTORY_ENTRY_H +#define GEDIT_HISTORY_ENTRY_H + +#include + +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 . + */ + +#include "gedit-io-error-info-bar.h" +#include +#include +#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 ("%s", + _("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 . + */ + +#ifndef GEDIT_IO_ERROR_INFO_BAR_H +#define GEDIT_IO_ERROR_INFO_BAR_H + +#include + +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 . + */ + +#include "gedit-menu-extension.h" + +#include + +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 . + */ + +#ifndef GEDIT_MENU_EXTENSION_H +#define GEDIT_MENU_EXTENSION_H + +#include +#include + +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 . + */ + +#include "config.h" + +#include "gedit-menu-stack-switcher.h" + +#include +#include +#include + +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 . + */ + +#ifndef GEDIT_MENU_STACK_SWITCHER_H +#define GEDIT_MENU_STACK_SWITCHER_H + +#include + +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 +#include +#include + +/** + * 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). + * + * + * Registering a message type + * + * 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"); + * + * + * + * Connecting a callback + * + * 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); + * + * + * + * + * Sending a message + * + * GeditMessageBus *bus = gedit_message_bus_get_default (); + * + * gedit_message_bus_send (bus, + * "/plugins/example", "method", + * "arg1", "Hello World", + * NULL); + * + * + */ + +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 +#include + +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 + +/** + * 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 + +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 + +#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 . + */ + +#include "gedit-notebook-popup-menu.h" + +#include + +#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 . + */ + + +#ifndef GEDIT_NOTEBOOK_POPUP_MENU_H +#define GEDIT_NOTEBOOK_POPUP_MENU_H + +#include +#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 . + */ + +#include "config.h" + +#include "gedit-notebook-stack-switcher.h" + +#include +#include +#include + +/* + * 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 . + */ + +#ifndef GEDIT_NOTEBOOK_STACK_SWITCHER_H +#define GEDIT_NOTEBOOK_STACK_SWITCHER_H + +#include + +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 . + */ + +/* 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 . + */ + +/* 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 +#include + +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 . + */ + +#include "config.h" + +#include "gedit-plugins-engine.h" + +#include +#include +#include +#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 . + */ + +#ifndef GEDIT_PLUGINS_ENGINE_H +#define GEDIT_PLUGINS_ENGINE_H + +#include +#include + +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 . + */ + +#include "config.h" + +#include "gedit-preferences-dialog.h" + +#include +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "gedit-print-job.h" +#include +#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 . + */ + +#ifndef GEDIT_PRINT_JOB_H +#define GEDIT_PRINT_JOB_H + +#include + +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 . + */ + +#include "gedit-print-preview.h" + +#include +#include +#include +#include +#include + +#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 . + */ + +#ifndef GEDIT_PRINT_PREVIEW_H +#define GEDIT_PRINT_PREVIEW_H + +#include + +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 . + */ + +#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 . + */ + +#ifndef GEDIT_RECENT_OSX_H +#define GEDIT_RECENT_OSX_H + +#include + +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 . + */ + +#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 . + */ + +#ifndef GEDIT_RECENT_H +#define GEDIT_RECENT_H + +#include +#include + +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 . + */ + +#include "config.h" + +#include "gedit-replace-dialog.h" + +#include +#include +#include + +#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 . + */ + +#ifndef GEDIT_REPLACE_DIALOG_H +#define GEDIT_REPLACE_DIALOG_H + +#include +#include +#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 + * + * 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 +#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 + * + * 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 + +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 . + */ + +#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 . + */ + +#ifndef GEDIT_STATUS_MENU_BUTTON_H +#define GEDIT_STATUS_MENU_BUTTON_H + +#include + +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 . + */ + +#include "config.h" +#include "gedit-statusbar.h" +#include +#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 . + */ + +#ifndef GEDIT_STATUSBAR_H +#define GEDIT_STATUSBAR_H + +#include +#include + +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 . + */ + +#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 . + */ + +#ifndef GEDIT_TAB_LABEL_H +#define GEDIT_TAB_LABEL_H + +#include +#include + +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 . + */ + +#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 . + */ + +#include "gedit-tab.h" +#include "gedit-tab-private.h" + +#include +#include +#include + +#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 ("%s", name); + + if (data->tab->state == GEDIT_TAB_STATE_REVERTING) + { + if (dirname != NULL) + { + dirname_markup = g_markup_printf_escaped ("%s", 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 ("%s", 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 ("%s", from); + + if (to != NULL) + { + to_markup = g_markup_printf_escaped ("%s", 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 ("%s", 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 ("%s %s\n\n" + "%s %s\n" + "%s %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 . + */ + +#ifndef GEDIT_TAB_H +#define GEDIT_TAB_H + +#include +#include +#include + +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 . + */ + +#include "gedit-utils.h" +#include +#include +#include +#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? + * + * paolo: and flame whoever tells + * you that file:///gnome/test_files/hëllò + * doesn't work --- that's not a valid URI + * + * 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 + * paolo: hmmmm, isn't there + * gnome_vfs_is_uri_valid() or something? + * : 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 ' 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 ' */ + 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 . + */ + +#ifndef GEDIT_UTILS_H +#define GEDIT_UTILS_H + +#include + +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 . + */ + +#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 . + */ + +#ifndef GEDIT_VIEW_ACTIVATABLE_H +#define GEDIT_VIEW_ACTIVATABLE_H + +#include + +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 +#include +#include +#include + +#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 . + */ + +#ifndef GEDIT_VIEW_FRAME_H +#define GEDIT_VIEW_FRAME_H + +#include +#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 . + */ + +#include "gedit-view.h" +#include +#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 . + */ + +#ifndef GEDIT_VIEW_H +#define GEDIT_VIEW_H + +#include +#include + +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 . + */ + +#include "config.h" + +#include "gedit-window-activatable.h" + +#include + +#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 . + */ + +#ifndef GEDIT_WINDOW_ACTIVATABLE_H +#define GEDIT_WINDOW_ACTIVATABLE_H + +#include + +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 . + */ + +#include "config.h" + +#include "gedit-window.h" + +#include +#include +#include + +#include +#include +#include + +#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 . + */ + +#ifndef GEDIT_WINDOW_H +#define GEDIT_WINDOW_H + +#include +#include +#include + +#include +#include + +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 . + */ + +#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 +#include +#include + +#include "gedit-dirs.h" +#include "gedit-debug.h" +#include "gedit-factory.h" +#include "gedit-settings.h" + +#ifdef G_OS_WIN32 +#include +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', '', + '--fhead', '', + '--vhead', ' <@type@ id="org.gnome.gedit.@EnumName@">', + '--vprod', ' ', + '--vtail', ' ', + '--ftail', '', + '@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 "c" { "copy-clipboard" () }; + bind "x" { "cut-clipboard" () }; + bind "v" { "paste-clipboard" () }; + + unbind "c"; + unbind "x"; + unbind "v"; + + bind "Left" { "move-cursor" (display-line-ends, -1, 0) }; + bind "KP_Left" { "move-cursor" (display-line-ends, -1, 0) }; + bind "Left" { "move-cursor" (display-line-ends, -1, 1) }; + bind "KP_Left" { "move-cursor" (display-line-ends, -1, 1) }; + + bind "Right" { "move-cursor" (display-line-ends, 1, 0) }; + bind "KP_Right" { "move-cursor" (display-line-ends, 1, 0) }; + bind "Right" { "move-cursor" (display-line-ends, 1, 1) }; + bind "KP_Right" { "move-cursor" (display-line-ends, 1, 1) }; + + unbind "Left"; + unbind "KP_Left"; + unbind "Left"; + unbind "KP_Left"; + unbind "Right"; + unbind "KP_Right"; + unbind "Right"; + unbind "KP_Right"; + + bind "Right" { "move-cursor" (words, 1, 0) }; + bind "KP_Right" { "move-cursor" (words, 1, 0) }; + bind "Left" { "move-cursor" (words, -1, 0) }; + bind "KP_Left" { "move-cursor" (words, -1, 0) }; + bind "Right" { "move-cursor" (words, 1, 1) }; + bind "KP_Right" { "move-cursor" (words, 1, 1) }; + bind "Left" { "move-cursor" (words, -1, 1) }; + bind "KP_Left" { "move-cursor" (words, -1, 1) }; + + bind "Delete" { "delete-from-cursor" (word-ends, 1) }; + bind "KP_Delete" { "delete-from-cursor" (word-ends, 1) }; + bind "BackSpace" { "delete-from-cursor" (word-ends, -1) }; + + bind "Down" { "move-cursor" (buffer-ends, 1, 0) }; + bind "Down" { "move-cursor" (buffer-ends, 1, 1) }; + bind "KP_Down" { "move-cursor" (buffer-ends, 1, 0) }; + bind "KP_Down" { "move-cursor" (buffer-ends, 1, 1) }; + + bind "Up" { "move-cursor" (buffer-ends, -1, 0) }; + bind "Up" { "move-cursor" (buffer-ends, -1, 1) }; + bind "KP_Up" { "move-cursor" (buffer-ends, -1, 0) }; + bind "KP_Up" { "move-cursor" (buffer-ends, -1, 1) }; + + bind "I" { "toggle-overwrite" () }; + unbind "Insert"; + + unbind "Down"; + unbind "KP_Down"; + unbind "Down"; + unbind "KP_Down"; + unbind "Up"; + unbind "KP_Up"; + unbind "Up"; + unbind "KP_Up"; +} + +@binding-set gtk-osx-text-entry { + bind "a" { + "move-cursor" (buffer-ends, -1, 0) + "move-cursor" (buffer-ends, 1, 1) + }; + + unbind "a"; +} + +@binding-set gtk-osx-text-view { + bind "a" { "select-all" (1) }; + unbind "a"; +} + +@binding-set gtk-osx-tree-view { + bind "s" { "start-interactive-search" () }; + unbind "s"; +} + +@binding-set gtk-osx-source-view { + bind "z" { "undo" () }; + unbind "z"; + + bind "z" { "redo" () }; + unbind "z"; +} + +@binding-set gedit-osx-view { + bind "d" { "delete-from-cursor" (GTK_DELETE_PARAGRAPHS, 1) }; + unbind "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 @@ + + + + gtk/menus.ui + gtk/menus-common.ui + ui/gedit-encodings-dialog.ui + ui/gedit-preferences-dialog.ui + ui/gedit-replace-dialog.ui + ui/gedit-print-preview.ui + ui/gedit-print-preferences.ui + ui/gedit-status-menu-button.ui + ui/gedit-tab-label.ui + ui/gedit-view-frame.ui + ui/gedit-window.ui + ui/gedit-shortcuts.ui + ui/gedit-statusbar.ui + css/gedit-style.css + css/gedit.adwaita.css + css/gedit.highcontrast.css + + @OS_DEPENDENT_RESOURCE_FILES@ + + 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 @@ + + + + +
+ + Move _Left + move-left + + + Move _Right + move-right + +
+
+ + Move to New _Window + move-to-new-window + + + Move to New Tab _Group + move-to-new-tab-group + +
+
+ + _Close + close + +
+
+ +
+ + 2 + win.tab-width + 2 + + + 4 + win.tab-width + 4 + + + 8 + win.tab-width + 8 + +
+
+ + Use Spaces + win.use-spaces + +
+
+ + +
+ + _File +
+ file-section-0 + + _New + win.new-tab + <Primary>T + +
+
+ file-section + + _Open + win.open + action-disabled + <Primary>O + + + _Open + app.open + action-disabled + + + Open _Recent +
+ + Reopen Closed _Tab + win.reopen-closed-tab + <Primary><Shift>T + +
+
+ recent-files-section +
+
+
+
+ file-section-1 + + _Save + win.save + <Primary>S + + + Save _As… + win.save-as + <Primary><Shift>S + +
+
+ app-commands-section + + _New Window + app.new-window + <Primary>N + +
+
+ file-section-2 + + _Reload + win.revert + +
+
+ file-section-3 + + _Print… + win.print + <Primary>P + +
+
+ close-section + + _Close + win.close + <Primary>W + + + _Quit + macos-menubar + app.quit + <Primary>Q + +
+
+ + _Edit +
+ edit-section + + _Undo + win.undo + <Primary>Z + + + _Redo + win.redo + <Primary><Shift>Z + +
+
+ + C_ut + win.cut + <Primary>X + + + _Copy + win.copy + <Primary>C + + + _Paste + win.paste + <Primary>V + + + _Delete + win.delete + Delete + +
+
+ edit-section-1 + + Overwrite _Mode + win.overwrite-mode + Insert + +
+
+ edit-section-2 + + Select _All + win.select-all + <Primary>A + +
+
+ + _Preferences + macos-menubar + app.preferences + +
+
+ + _View +
+ view-section + + Side _Panel + win.side-panel + F9 + + + _Bottom Panel + win.bottom-panel + action-disabled + <Primary>F9 + +
+
+ view-section-1 + + _Fullscreen + win.fullscreen + F11 + +
+
+ view-section-2 + + _Highlight Mode… + win.highlight-mode + +
+
+ + _Search +
+ search-section + + _Find… + win.find + <Primary>F + + + Find Ne_xt + win.find-next + <Primary>G + + + Find Pre_vious + win.find-prev + <Primary><Shift>G + +
+
+ search-section-1 + + Find and _Replace… + win.replace + <Primary>H + +
+
+ search-section-2 + + _Clear Highlight + win.clear-highlight + <Primary><Shift>K + +
+
+ search-section-3 + + Go to _Line… + win.goto-line + <Primary>I + +
+
+ + _Tools +
+ spell-section +
+
+ tools-section +
+
+ tools-section-1 +
+
+ preferences-section +
+
+ + _Documents +
+ documents-section + + _Save All + win.save-all + <Primary><Shift>L + + + _Close All + win.close-all + <Primary><Shift>W + +
+
+ documents-section-1 + + _New Tab Group + win.new-tab-group + <Primary><Alt>N + + + P_revious Tab Group + win.previous-tab-group + <Primary><Shift><Alt>Page_Up + + + Nex_t Tab Group + win.next-tab-group + <Primary><Shift><Alt>Page_Down + +
+
+ documents-section-2 + + _Previous Document + win.previous-document + <Primary><Alt>Page_Up + + + N_ext Document + win.next-document + <Primary><Alt>Page_Down + +
+
+ documents-section-3 + + _Move To New Window + win.move-to-new-window + +
+
+ + _Help +
+ help-section + + _Help + app.help + F1 + +
+
+ + _About gedit + macos-menubar + app.about + +
+
+
+
+
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 @@ + + + + +
+ juntion-section + horizontal-buttons + + _Reload + win.revert + view-refresh-symbolic + + + _Print… + win.print + printer-symbolic + + + _Fullscreen + win.fullscreen + view-fullscreen-symbolic + +
+
+ app-commands-section +
+
+ file-section +
+
+ file-section-1 + + _Save As… + win.save-as + + + Save _All + win.save-all + +
+
+ edit-section +
+
+ edit-section-1 +
+
+ search-section + + _Find… + win.find + + + _Find and Replace… + win.replace + + + _Clear Highlight + win.clear-highlight + + + _Go to Line… + win.goto-line + +
+
+ + _View +
+ view-section + + Side _Panel + win.side-panel + + + _Bottom Panel + win.bottom-panel + action-disabled + +
+
+ view-section-1 +
+
+ view-section-2 + + _Highlight Mode… + win.highlight-mode + +
+
+ + _Tools +
+ spell-section +
+
+ tools-section +
+
+ tools-section-1 +
+
+
+
+ preferences-section + + _Preferences + app.preferences + +
+
+ help-section + + _Keyboard Shortcuts + app.shortcuts + + + _Help + app.help + + + _About gedit + app.about + +
+
+ close-section + + _Close All + win.close-all + + + _Close + win.close + + + _Quit + app.quit + +
+
+
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 @@ + + + + +
+ juntion-section + horizontal-buttons + + _Reload + win.revert + view-refresh-symbolic + + + _Print… + win.print + printer-symbolic + + + _Fullscreen + win.fullscreen + view-fullscreen-symbolic + +
+
+ app-commands-section + + _New Window + app.new-window + +
+
+ file-section +
+
+ file-section-1 + + _Save As… + win.save-as + + + Save _All + win.save-all + +
+
+ edit-section +
+
+ edit-section-1 +
+
+ search-section + + _Find… + win.find + + + _Find and Replace… + win.replace + + + _Clear Highlight + win.clear-highlight + + + _Go to Line… + win.goto-line + +
+
+ + _View +
+ view-section + + Side _Panel + win.side-panel + + + _Bottom Panel + win.bottom-panel + action-disabled + +
+
+ view-section-1 +
+
+ view-section-2 + + _Highlight Mode… + win.highlight-mode + +
+
+ + _Tools +
+ spell-section +
+
+ tools-section +
+
+ tools-section-1 +
+
+
+
+ preferences-section + + _Preferences + app.preferences + +
+
+ app-section + + _Keyboard Shortcuts + app.shortcuts + + + _Help + app.help + + + _About gedit + app.about + +
+
+
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', + 'gtk/menus-traditional.ui' + ) +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 @@ + + + + + + + + + + + + + + + + liststore_available + + + + + + + + + + + + + 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 @@ + + + + + + + + 1 + 1000 + 80 + 1 + 10 + + + 1 + 24 + 8 + 1 + 4 + + + 1 + 100 + 8 + 1 + 10 + + 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 @@ + + + + + + 1 + 100 + 1 + 1 + 10 + + + True + False + window1 + + + True + False + 12 + 18 + + + True + False + vertical + 18 + + + True + False + vertical + 6 + + + True + False + 0 + Syntax Highlighting + + + + + + False + False + 0 + + + + + Print synta_x highlighting + False + True + True + False + 12 + True + True + + + False + True + 1 + + + + + False + True + 0 + + + + + True + False + vertical + 6 + + + True + False + 0 + Line Numbers + + + + + + False + False + 0 + + + + + True + False + 12 + vertical + 6 + + + Print line nu_mbers + False + True + True + False + True + True + + + False + False + 0 + + + + + True + False + 6 + + + True + False + 0.47999998927116394 + _Number every + True + line_numbers_spinbutton + + + False + False + 0 + + + + + True + True + adjustment1 + 1 + 1 + + + False + False + 1 + + + + + True + False + lines + + + False + False + 2 + + + + + False + False + 1 + + + + + False + True + 1 + + + + + False + True + 1 + + + + + True + False + vertical + 6 + + + True + False + 0 + Text Wrapping + + + + + + False + False + 0 + + + + + True + False + 12 + vertical + 6 + + + Enable text _wrapping + False + True + True + False + True + True + + + False + False + 0 + + + + + True + False + + + Do not _split words over two lines + False + True + True + False + True + True + + + False + False + 0 + + + + + False + True + 1 + + + + + False + True + 1 + + + + + False + False + 2 + + + + + True + False + vertical + 6 + + + True + False + 0 + Page header + + + + + + False + False + 0 + + + + + Print page _headers + False + True + True + False + 12 + True + True + + + False + True + 1 + + + + + False + True + 3 + + + + + False + True + 0 + + + + + True + False + vertical + 18 + + + True + False + vertical + 6 + + + True + False + 0 + Fonts + + + + + + False + False + 0 + + + + + True + False + 12 + vertical + 12 + + + True + False + 12 + 12 + + + True + False + 0 + _Body: + True + body_fontbutton + + + 0 + 0 + + + + + False + True + True + True + Sans 12 + + False + True + False + + + 1 + 0 + + + + + True + False + 0 + _Line numbers: + True + numbers_fontbutton + + + 0 + 1 + + + + + False + True + True + True + Sans 12 + + False + True + False + + + 1 + 1 + + + + + True + False + 0 + He_aders and footers: + True + headers_fontbutton + + + 0 + 2 + + + + + False + True + True + True + Sans 12 + + False + True + False + + + 1 + 2 + + + + + False + True + 0 + + + + + True + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + end + + + _Restore Default Fonts + False + True + True + False + True + + + False + False + 0 + + + + + False + True + 1 + + + + + False + True + 1 + + + + + False + True + 0 + + + + + False + False + 1 + + + + + + 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 @@ + + + + + + True + go-previous-symbolic + + + True + go-next-symbolic + + + True + view-grid-symbolic + + + True + zoom-in-symbolic + + + True + zoom-out-symbolic + + + True + zoom-original-symbolic + + + True + zoom-fit-best-symbolic + + 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 @@ + + + + + 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 @@ + + + + + 1 + + + 1 + shortcuts + 12 + + + 1 + Documents + + + 1 + <ctrl>T + Create a new document in a tab + + + + + 1 + <ctrl>O + Open a document + + + + + 1 + <ctrl>S + Save the document + + + + + 1 + <ctrl><shift>S + Save the document with a new filename + + + + + 1 + <ctrl><shift>L + Save all the documents + + + + + 1 + <ctrl>W + Close the document + + + + + 1 + <ctrl><shift>W + Close all the documents + + + + + 1 + <ctrl><shift>T + Reopen the most recently closed document + + + + + 1 + <ctrl><Alt>Page_Down + Switch to the next document + + + + + 1 + <ctrl><Alt>Page_Up + Switch to the previous document + + + + + 1 + <Alt>1...9 + Switch to the first — ninth document + + + + + + + 1 + Windows and Panels + + + 1 + <ctrl>N + Create a new document in a window + + + + + 1 + <ctrl><alt>N + Create a new tab group + + + + + 1 + F9 + Show side panel + + + + + 1 + <ctrl>F9 + Show bottom panel + + + + + 1 + F11 + Fullscreen on / off + + + + + 1 + <ctrl>Q + Quit the application + + + + + + + 1 + Find and Replace + + + 1 + <ctrl>F + Find + + + + + 1 + <ctrl>G + Find the next match + + + + + 1 + <ctrl><Shift>G + Find the previous match + + + + + 1 + <ctrl>H + Find and Replace + + + + + 1 + <ctrl><Shift>K + Clear highlight + + + + + + + 1 + Undo and Redo + + + 1 + <ctrl>Z + Undo previous command + + + + + 1 + <ctrl><shift>Z + Redo previous command + + + + + + + 1 + Selection + + + 1 + <ctrl>A + Select all text + + + + + 1 + <ctrl>backslash + Unselect all text + + + + + + + 1 + Copy and Paste + + + 1 + <ctrl>C + Copy selected text to clipboard + + + + + 1 + <ctrl>X + Cut selected text to clipboard + + + + + 1 + <ctrl>V + Paste text from clipboard + + + + + + + 1 + Navigation + + + 1 + <ctrl>I + Go to line + + + + + 1 + Home + Move to the beginning of the current line + + + + + 1 + End + Move to the end of the current line + + + + + 1 + <ctrl>Home + Move to the beginning of the document + + + + + 1 + <ctrl>End + Move to the end of the document + + + + + 1 + <alt><shift>Up + Move viewport up within the file + + + + + 1 + <alt><shift>Down + Move viewport down within the file + + + + + 1 + <alt><shift>End + Move viewport to end of file + + + + + 1 + <alt><shift>Home + Move viewport to beginning of file + + + + + 1 + <ctrl>percent + Move to matching bracket + + + + + 1 + <Shift><Ctrl><Alt>Page_Up + Go to previous tab group + + + + + 1 + <Shift><Ctrl><Alt>Page_Down + Go to next tab group + + + + + + + 1 + Editing + + + 1 + Insert + Toggle insert / overwrite + + + + + 1 + F7 + Toggle cursor visibility + + + + + 1 + <ctrl>D + Delete current line + + + + + 1 + <alt>Up + Move current line up + + + + + 1 + <alt>Down + Move current line down + + + + + 1 + <alt>Left + Move current word left + + + + + 1 + <alt>Right + Move current word right + + + + + 1 + <ctrl>U + Convert to uppercase + + + + + 1 + <ctrl>L + Convert to lowercase + + + + + 1 + <ctrl>asciitilde + Invert case + + + + + 1 + <ctrl><shift>a + Increment number at cursor + + + + + 1 + <ctrl><shift>x + Decrement number at cursor + + + + + + + 1 + Tools + + + 1 + <shift>F7 + Check spelling + + + + + 1 + <ctrl>P + Print the document + + + + + 1 + <ctrl>space + Show completion window + + + + + + + 1 + General + + + 1 + F1 + Show help + + + + + 1 + F10 + Open menu + + + + + 1 + <ctrl>question + Keyboard shortcuts + + + + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + + + + True + window-close-symbolic + + 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 @@ + + + + + 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 @@ + + + + + + True + tab-new-symbolic + + + True + open-menu-symbolic + + + True + tab-new-symbolic + + + True + open-menu-symbolic + + + True + view-restore-symbolic + + + True + window-close-symbolic + + + horizontal + + + + + + + -- cgit v1.2.3