diff --git a/plugins/sessionsaver/Makefile.am b/plugins/sessionsaver/Makefile.am new file mode 100644 index 0000000..3f46506 --- /dev/null +++ b/plugins/sessionsaver/Makefile.am @@ -0,0 +1,20 @@ +# Session Saver Plugin + +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) +plugin_in_files = sessionsaver.plugin.desktop.in +%.plugin: %.plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache + +sessionsaverdir=$(plugindir)/sessionsaver +sessionsaver_PYTHON = __init__.py store.py dialogs.py + +uidir=$(GEDIT_PLUGINS_DATA_DIR)/sessionsaver +ui_DATA = sessionsaver.ui + +plugin_DATA = $(plugin_in_files:.plugin.desktop.in=.plugin) + +EXTRA_DIST = $(plugin_in_files) $(ui_DATA) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + +-include $(top_srcdir)/git.mk diff --git a/plugins/sessionsaver/__init__.py b/plugins/sessionsaver/__init__.py new file mode 100644 index 0000000..8903d72 --- /dev/null +++ b/plugins/sessionsaver/__init__.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +# __init__.py +# This file is part of gedit Session Saver Plugin +# +# Copyright (C) 2006-2007 - Steve Frécinaux +# Copyright (C) 2010 - Kenny Meyer +# +# gedit Session Saver Plugin 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 Session Saver Plugin 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 Session Saver Plugin; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301 USA + +from gi.repository import GObject, Gtk, Gedit +import gettext +from .store import XMLSessionStore +from .dialogs import SaveSessionDialog, SessionManagerDialog +from gpdefs import * + +try: + gettext.bindtextdomain(GETTEXT_PACKAGE, GP_LOCALEDIR) + _ = lambda s: gettext.dgettext(GETTEXT_PACKAGE, s); +except: + _ = lambda s: s + +ui_string = """ + + + + + + + + + + + + + + + +""" + +class SessionSaverPlugin(GObject.Object, Gedit.WindowActivatable): + __gtype_name__ = "SessionSaverPlugin" + + window = GObject.property(type=Gedit.Window) + + SESSION_MENU_PATH = '/MenuBar/FileMenu/FileOps_2/FileSessionMenu/SessionPluginPlaceHolder' + + def __init__(self): + GObject.Object.__init__(self) + self.sessions = XMLSessionStore() + + def do_activate(self): + self._insert_menu() + + def do_deactivate(self): + self._remove_menu() + + def do_update_state(self): + self._action_group.get_action("FileSessionSave").set_sensitive(self.window.get_active_document() != None) + + def _insert_menu(self): + ui_manager = self.window.get_ui_manager() + + self._action_group = Gtk.ActionGroup("SessionSaverPluginActions") + self._action_group.add_actions( + [("FileSession", None, _("Sa_ved sessions"), None, None), + ("FileSessionSave", Gtk.STOCK_SAVE_AS, + _("_Save current session"), None, + _("Save the current document list as a new session"), + self.on_save_session_action), + ("FileSessionManage", None, + _("_Manage saved sessions..."), None, + _("Open the saved session manager"), + self.on_manage_sessions_action) + ]) + ui_manager.insert_action_group(self._action_group) + + self._ui_id = ui_manager.add_ui_from_string(ui_string) + + self._insert_session_menu() + + ui_manager.ensure_update() + + def _remove_menu(self): + ui_manager = self.window.get_ui_manager() + + self._remove_session_menu() + + ui_manager.remove_ui(self._ui_id) + ui_manager.remove_action_group(self._action_group) + + ui_manager.ensure_update() + + def _insert_session_menu(self): + ui_manager = self.window.get_ui_manager() + + self._merge_id = ui_manager.new_merge_id() + + self._session_action_group = Gtk.ActionGroup(name="SessionSaverPluginSessionActions") + ui_manager.insert_action_group(self._session_action_group) + + for i, session in enumerate(self.sessions): + action_name = 'SessionSaver%X' % i + action = Gtk.Action(action_name, + session.name, + _("Recover '%s' session") % session.name, + None) + handler = action.connect("activate", self.session_menu_action, session) + + self._session_action_group.add_action(action) + + ui_manager.add_ui(self._merge_id, + self.SESSION_MENU_PATH, + action_name, + action_name, + Gtk.UIManagerItemType.MENUITEM, + False) + + def _remove_session_menu(self): + ui_manager = self.window.get_ui_manager() + + for action in self._session_action_group.list_actions(): + action.disconnect_by_func(self.session_menu_action) + + ui_manager.remove_ui(self._merge_id) + ui_manager.remove_action_group(self._session_action_group) + + ui_manager.ensure_update() + + def _update_session_menu(self): + self._remove_session_menu() + self._insert_session_menu() + + def _load_session(self, session): + # Note: a session has to stand on its own window. + tab = self.window.get_active_tab() + if tab is not None and \ + not (tab.get_document().is_untouched() and \ + tab.get_state() == Gedit.TabState.STATE_NORMAL): + # Create a new gedit window + window = Gedit.App.get_default().create_window(None) + window.show() + else: + window = self.window + + Gedit.commands_load_locations(window, session.files, None, 0, 0) + + def on_save_session_action(self, action): + SaveSessionDialog(self.window, self, self.sessions).run() + + def on_manage_sessions_action(self, action): + SessionManagerDialog(self, self.sessions).run() + + def session_menu_action(self, action, session): + self._load_session(session) + +# ex:ts=4:et: diff --git a/plugins/sessionsaver/dialogs.py b/plugins/sessionsaver/dialogs.py new file mode 100644 index 0000000..70cdaae --- /dev/null +++ b/plugins/sessionsaver/dialogs.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2007 - Steve Frécinaux +# Copyright (c) 2010 - Kenny Meyer +# Licence: GPL2 or later + +from gi.repository import GObject, Gtk, Gedit +import os.path +import gettext + +from .store import Session + +try: + from gpdefs import * + gettext.bindtextdomain(GETTEXT_PACKAGE, GP_LOCALEDIR) + _ = lambda s: gettext.dgettext(GETTEXT_PACKAGE, s); +except: + _ = lambda s: s + +class SessionModel(Gtk.ListStore): + OBJECT_COLUMN = 0 + NAME_COLUMN = 1 + N_COLUMNS = 2 + + def __init__(self, store): + super(SessionModel, self).__init__(GObject.TYPE_PYOBJECT, str) + self.store = store + for session in store: + row = { self.OBJECT_COLUMN : session, + self.NAME_COLUMN: session.name } + self.append(row.values()) + self.store.connect_after('session-added', self.on_session_added) + self.store.connect('session-removed', self.on_session_removed) + + def on_session_added(self, store, session): + row = { self.OBJECT_COLUMN : session, + self.NAME_COLUMN: session.name } + self.append(row.values()) + + def on_session_removed(self, store, session): + it = self.get_iter_first() + if it is not None: + while True: + stored_session = self.get_value(it, self.OBJECT_COLUMN) + if stored_session == session: + self.remove(it) + break + it = self.iter_next(it) + if not it: + break + +class Dialog(object): + UI_FILE = "sessionsaver.ui" + + def __new__(cls, *args): + if not ('_instance' in cls.__dict__) or cls._instance is None: + cls._instance = object.__new__(cls, *args) + return cls._instance + + def __init__(self, main_widget, datadir, parent_window = None): + super(Dialog, self).__init__() + + if parent_window is None: + parent_window = Gedit.App.get_default().get_active_window() + self.parent = parent_window + + self.ui = Gtk.Builder() + self.ui.set_translation_domain(GETTEXT_PACKAGE) + self.ui.add_from_file(os.path.join(datadir, self.UI_FILE)) + self.dialog = self.ui.get_object(main_widget) + self.dialog.connect('delete-event', self.on_delete_event) + + def __getitem__(self, item): + return self.ui.get_object(item) + + def on_delete_event(self, dialog, event): + dialog.hide() + return True + + def __del__(self): + self.__class__._instance = None + + def run(self): + self.dialog.set_transient_for(self.parent) + self.dialog.show() + + def destroy(self): + self.dialog.destroy() + self.__del__() + +class SaveSessionDialog(Dialog): + def __init__(self, window, plugin, sessions): + super(SaveSessionDialog, self).__init__('save-session-dialog', + plugin.plugin_info.get_data_dir(), + window) + self.plugin = plugin + self.sessions = sessions + self.sessionsaver = plugin + + model = SessionModel(sessions) + + combobox = self['session-name'] + combobox.set_model(model) + combobox.set_entry_text_column(1) + + self.dialog.connect('response', self.on_response) + + def on_response(self, dialog, response_id): + if response_id == Gtk.ResponseType.OK: + files = [doc.get_location() + for doc in self.parent.get_documents() + if doc.get_location() is not None] + name = self['session-name'].get_child().get_text() + self.sessions.add(Session(name, files)) + self.sessions.save() + self.sessionsaver.sessions = self.sessions + self.sessionsaver._update_session_menu() + self.destroy() + +class SessionManagerDialog(Dialog): + def __init__(self, plugin, sessions): + super(SessionManagerDialog, self).__init__('session-manager-dialog', + plugin.plugin_info.get_data_dir()) + self.plugin = plugin + self.sessions = sessions + + model = SessionModel(sessions) + + self.view = self['session-view'] + self.view.set_model(model) + + renderer = Gtk.CellRendererText() + column = Gtk.TreeViewColumn(_("Session Name"), renderer, text = model.NAME_COLUMN) + self.view.append_column(column) + + handlers = { + 'on_close_button_clicked': self.on_close_button_clicked, + 'on_open_button_clicked': self.on_open_button_clicked, + 'on_delete_button_clicked': self.on_delete_button_clicked + } + self.ui.connect_signals(handlers) + + def on_delete_event(self, dialog, event): + dialog.hide() + self.sessions.save() + return True + + def get_current_session(self): + (model, selected) = self.view.get_selection().get_selected() + if selected is None: + return None + return model.get_value(selected, SessionModel.OBJECT_COLUMN) + + def on_open_button_clicked(self, button): + session = self.get_current_session() + if session is not None: + self.plugin._load_session(session) + + def on_delete_button_clicked(self, button): + session = self.get_current_session() + self.sessions.remove(session) + self.plugin._update_session_menu() + + def on_close_button_clicked(self, button): + self.sessions.save() + self.destroy() + +# ex:ts=4:et: diff --git a/plugins/sessionsaver/sessionsaver.plugin.desktop.in.in b/plugins/sessionsaver/sessionsaver.plugin.desktop.in.in new file mode 100644 index 0000000..61d4b84 --- /dev/null +++ b/plugins/sessionsaver/sessionsaver.plugin.desktop.in.in @@ -0,0 +1,10 @@ +[Plugin] +Loader=python3 +Module=sessionsaver +IAge=3 +_Name=Session Saver +_Description=Save and restore your working sessions +Authors=Steve Frécinaux +Copyright=Copyright © 2006 Steve Frécinaux +Website=http://www.gedit.org +Version= VERSION@ diff --git a/plugins/sessionsaver/sessionsaver.ui b/plugins/sessionsaver/sessionsaver.ui new file mode 100644 index 0000000..7e333df --- /dev/null +++ b/plugins/sessionsaver/sessionsaver.ui @@ -0,0 +1,213 @@ + + + + + False + 6 + Save session + False + dialog + + + True + False + vertical + + + True + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK + end + + + gtk-cancel + True + False + False + False + True + + + False + True + 0 + + + + + gtk-save + True + False + True + True + False + False + True + + + False + True + 1 + + + + + False + True + end + 0 + + + + + True + False + 6 + vertical + 6 + + + True + False + 0 + Session name: + + + False + True + 0 + + + + + True + False + True + + + True + + + + + False + True + 1 + + + + + False + True + 1 + + + + + + button2 + button1 + + + + 400 + 200 + False + Saved Sessions + dialog + + + True + False + 6 + 6 + + + True + False + True + never + in + + + True + False + False + + + + + + + + False + True + 0 + + + + + True + False + 6 + start + + + gtk-open + True + False + False + False + True + + + + False + True + 0 + + + + + gtk-delete + True + False + False + False + True + + + + False + True + 1 + + + + + gtk-close + True + False + False + False + True + + + + False + True + 2 + True + + + + + False + True + 1 + + + + + + diff --git a/plugins/sessionsaver/store.py b/plugins/sessionsaver/store.py new file mode 100644 index 0000000..3348441 --- /dev/null +++ b/plugins/sessionsaver/store.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +# store.py +# This file is part of gedit Session Saver Plugin +# +# Copyright (C) 2006-2007 - Steve Frécinaux +# Copyright (C) 2010 - Kenny Meyer +# +# gedit Session Saver Plugin 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 Session Saver Plugin 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 Session Saver Plugin; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301 USA + +import os.path +from xml.parsers import expat +from gi.repository import GObject, GLib, Gio + +class Session(object): + def __init__(self, name, files = None): + super(Session, self).__init__() + self.name = name + if files is None: + files = [] + self.files = files + + def __lt__(self, session): + return (self.name.lower() < session.name.lower()) + + def add_file(self, filename): + self.files.append(Gio.file_new_for_uri(filename)) + +class SessionStore(GObject.Object): + __gsignals__ = { + "session-added": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,)), + "session-changed": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,)), + "session-removed": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,)) + } + + _instance = None + def __new__(cls): + if cls._instance is None: + cls._instance = GObject.Object.__new__(cls) + return cls._instance + + def __init__(self): + super(SessionStore, self).__init__() + self._sessions = [] + + def __iter__(self): + return iter(self._sessions) + + def __getitem__(self, index): + return self._sessions[index] + + def __getslice__(self, i, j): + return self._sessions[i:j] + + def __len__(self): + return len(self._sessions) + + def do_session_added(self, session): + self._sessions.append(session) + self._sessions.sort() + + def do_session_changed(self, session): + index = self._sessions.index(session) + self._sessions[index] = session + + def add(self, session): + assert isinstance(session, Session) + + if session in self: + self.emit('session-changed', session) + else: + self.emit('session-added', session) + + def do_session_removed(self, session): + self._sessions.remove(session) + + def remove(self, session): + assert isinstance(session, Session) + if session in self: + self.emit('session-removed', session) + + def index(self, session): + return self._sessions.index(session) + +class XMLSessionStore(SessionStore): + def __init__(self): + super(XMLSessionStore, self).__init__() + self.filename = os.path.join(GLib.get_user_config_dir(), 'gedit/saved-sessions.xml') + self.load() + + def _escape(self, string): + return string.replace('&', '&') \ + .replace('<', '<') \ + .replace('>', '>') \ + .replace('"', '"') + + def _dump_session(self, session): + files = ''.join([' \n' % self._escape(location.get_uri()) + for location in session.files]) + session_name = self._escape(str(session.name)) + return '\n%s\n' % (session_name, files) + + def dump(self): + dump = [self._dump_session(session) for session in self] + return '\n%s\n' % ''.join(dump) + + def save(self): + dirname = os.path.dirname(self.filename) + if not os.path.isdir(dirname): + os.makedirs(dirname) + + fp = open(self.filename, "wb") + fp.write(bytes('\n','UTF-8')) + fp.write(bytes(self.dump(),'UTF-8')) + fp.close() + + def load(self): + if not os.path.isfile(self.filename): + return + + parser = expat.ParserCreate('UTF-8') + parser.buffer_text = True + parser.StartElementHandler = self._expat_start_handler + parser.EndElementHandler = self._expat_end_handler + + self._current_session = None + try: + parser.ParseFile(open(self.filename, 'rb')) + except: + return + del self._current_session + + def _expat_start_handler(self, tag, attr): + if tag == 'file': + assert self._current_session is not None + self._current_session.add_file(str(attr['path'])) + elif tag == 'session': + assert self._current_session is None + self._current_session = Session(attr['name']) + + def _expat_end_handler(self, tag): + if tag == 'session': + self.add(self._current_session) + self._current_session = None + +# ex:ts=4:et: