[gedit-plugins] New session saver plugin that allows to load and save sets of previously open files
- From: Jordi Mas <jmas src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gedit-plugins] New session saver plugin that allows to load and save sets of previously open files
- Date: Thu, 4 Jul 2019 21:35:25 +0000 (UTC)
commit 0bcba8c0739936aaa85ab604a6e028bdb5b28de0
Author: Jordi Mas <jmas softcatala org>
Date: Thu Jul 4 23:35:06 2019 +0200
New session saver plugin that allows to load and save sets of previously open files
help/C/session-saver.page | 83 ++++++++
help/meson.build | 1 +
meson.build | 1 +
meson_options.txt | 1 +
plugins/sessionsaver/meson.build | 41 ++++
.../sessionsaver/sessionsaver.plugin.desktop.in.in | 10 +
plugins/sessionsaver/sessionsaver/__init__.py | 26 +++
plugins/sessionsaver/sessionsaver/appactivable.py | 72 +++++++
plugins/sessionsaver/sessionsaver/dialogs.py | 197 ++++++++++++++++++
plugins/sessionsaver/sessionsaver/store/session.py | 42 ++++
.../sessionsaver/store/sessionstore.py | 80 ++++++++
.../sessionsaver/store/xmlsessionstore.py | 95 +++++++++
.../sessionsaver/sessionsaver/ui/sessionsaver.ui | 220 +++++++++++++++++++++
.../sessionsaver/sessionsaver/windowactivable.py | 111 +++++++++++
plugins/sessionsaver/tests/meson.build | 23 +++
plugins/sessionsaver/tests/test-saved-sessions.xml | 19 ++
plugins/sessionsaver/tests/testsession.py | 40 ++++
plugins/sessionsaver/tests/testsessionstore.py | 87 ++++++++
plugins/sessionsaver/tests/testxmlsessionstore.py | 58 ++++++
po/POTFILES.in | 5 +
20 files changed, 1212 insertions(+)
---
diff --git a/help/C/session-saver.page b/help/C/session-saver.page
new file mode 100644
index 0000000..ec56d2c
--- /dev/null
+++ b/help/C/session-saver.page
@@ -0,0 +1,83 @@
+<page xmlns="http://projectmallard.org/1.0/"
+ xmlns:its="http://www.w3.org/2005/11/its"
+ type="topic" style="task"
+ id="plugin-session-saver">
+
+ <info>
+ <link type="guide" xref="gedit-plugin-guide#gedit-additional-plugins"/>
+ <revision version="1.0" date="2018-03-04" status="review"/>
+
+ <credit type="author">
+ <name>Jordi Mas i Hernàndez</name>
+ <email its:translate="no">jmas softcatala org</email>
+ </credit>
+
+ <include href="legal-plugins.xml" xmlns="http://www.w3.org/2001/XInclude"/>
+
+ <desc>Save and restore your working sessions</desc>
+ </info>
+
+ <title>Session Saver</title>
+
+ <p>The <app>Session Saver</app> plugin allows you to save your current open documents and open them
again.</p>
+
+ <section id="enable-session-saver">
+ <title>Enable Session Saver Plugin</title>
+
+ <steps>
+ <title>To enable Session Saver plugin:</title>
+ <item>
+ <p>Select <guiseq><gui style="menu">gedit</gui>
+ <gui style="menuitem">Preferences</gui>
+ <gui style="tab">Plugins</gui></guiseq>.</p>
+ </item>
+ <item>
+ <p>Select <gui style="menuitem">Session Saver</gui> to enable the
+ plugin.</p>
+ </item>
+ </steps>
+
+ </section>
+
+ <section id="enable-session-saver">
+ <title>Save a session</title>
+ <p>Save all your open files to be able to open them later.</p>
+ <steps>
+ <title>To save a session:</title>
+ <item>
+ <p>Select <guiseq><gui style="menu">gedit</gui>
+ <gui style="menuitem">Tools</gui>
+ <gui style="tab">Save Session...</gui></guiseq>.</p>
+ </item>
+ </steps>
+ </section>
+
+ <section id="open-session-saver">
+ <title>Open a session</title>
+ <p>Open all the files from an already saved session.</p>
+ <steps>
+ <title>To open a session:</title>
+ <item>
+ <p>Select <guiseq><gui style="menu">gedit</gui>
+ <gui style="menuitem">Tools</gui></guiseq></p>
+ </item>
+ <item>
+ <p>Select one of the saved sessions from the menu.</p>
+ </item>
+ </steps>
+ </section>
+
+ <section id="manage-session-saver">
+ <title>Manage Sessions</title>
+ <p>Open and delete your already saved sessions</p>
+ <steps>
+ <title>Manage sessions</title>
+ <item>
+ <p>Select <guiseq><gui style="menu">gedit</gui>
+ <gui style="menuitem">Tools</gui>
+ <gui style="tab">Manage Saved Sessions...</gui></guiseq></p>
+ </item>
+ </steps>
+ </section>
+
+</page>
diff --git a/help/meson.build b/help/meson.build
index fe54ce1..bf6b967 100644
--- a/help/meson.build
+++ b/help/meson.build
@@ -10,6 +10,7 @@ gedit_help_sources = [
'join-split-lines.page',
'legal-plugins.xml',
'multi-edit.page',
+ 'session-saver.page',
'terminal.page',
'text-size.page',
'translate.page',
diff --git a/meson.build b/meson.build
index b1c295e..6f07951 100644
--- a/meson.build
+++ b/meson.build
@@ -91,6 +91,7 @@ all_plugins = {
'git': {'language': 'python'},
'joinlines': {'language': 'python'},
'multiedit': {'language': 'python'},
+ 'sessionsaver': {'language': 'python'},
'smartspaces': {'language': 'python'},
'terminal': {'language': 'python'},
'textsize': {'language': 'python'},
diff --git a/meson_options.txt b/meson_options.txt
index d2770b7..a7a578f 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -10,6 +10,7 @@ option('plugin_findinfiles', type: 'boolean', value: false)
option('plugin_git', type: 'boolean')
option('plugin_joinlines', type: 'boolean')
option('plugin_multiedit', type: 'boolean')
+option('plugin_sessionsaver', type: 'boolean')
option('plugin_smartspaces', type: 'boolean')
option('plugin_terminal', type: 'boolean')
option('plugin_textsize', type: 'boolean')
diff --git a/plugins/sessionsaver/meson.build b/plugins/sessionsaver/meson.build
new file mode 100644
index 0000000..927715e
--- /dev/null
+++ b/plugins/sessionsaver/meson.build
@@ -0,0 +1,41 @@
+install_subdir(
+ 'sessionsaver',
+ exclude_directories: ['ui'],
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ )
+)
+
+install_data(
+ 'sessionsaver/ui/sessionsaver.ui',
+ install_dir: join_paths(
+ pkgdatadir,
+ 'plugins',
+ 'sessionsaver',
+ 'ui',
+ )
+)
+
+subdir('tests')
+
+sessionsaver_plugin_in = configure_file(
+ input: 'sessionsaver.plugin.desktop.in.in',
+ output: 'sessionsaver.plugin.desktop.in',
+ configuration: plugin_in,
+ install: false,
+)
+
+sessionsaver_plugin = custom_target(
+ 'sessionsaver.plugin',
+ input: sessionsaver_plugin_in,
+ output: 'sessionsaver.plugin',
+ command: msgfmt_plugin_cmd,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ )
+)
+
+
diff --git a/plugins/sessionsaver/sessionsaver.plugin.desktop.in.in
b/plugins/sessionsaver/sessionsaver.plugin.desktop.in.in
new file mode 100644
index 0000000..04829a2
--- /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;Jordi Mas i Hernàndez
+Copyright=Copyright © 2006 Steve Frécinaux;Copyright © 2019 Jordi Mas i Hernàndez
+Website=http://www.gedit.org
+Version=@VERSION@
diff --git a/plugins/sessionsaver/sessionsaver/__init__.py b/plugins/sessionsaver/sessionsaver/__init__.py
new file mode 100644
index 0000000..790a3ef
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver/__init__.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+#
+# Copyrignt (C) 2019 Jordi Mas <jmas softcatala org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+import gi
+gi.require_version('Gtk', '3.0')
+gi.require_version('GtkSource', '4')
+gi.require_version('PeasGtk', '1.0')
+
+from .appactivable import SessionSaverAppActivatable
+from .windowactivable import SessionSaverWindowActivatable
diff --git a/plugins/sessionsaver/sessionsaver/appactivable.py
b/plugins/sessionsaver/sessionsaver/appactivable.py
new file mode 100644
index 0000000..f271c40
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver/appactivable.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+#
+# Copyrignt (C) 2019 Jordi Mas <jmas softcatala org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+from gi.repository import GObject, Gio, Gedit
+from .store.xmlsessionstore import XMLSessionStore
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit-plugins')
+ gettext.textdomain('gedit-plugins')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+
+
+class SessionSaverAppActivatable(GObject.Object, Gedit.AppActivatable):
+
+ app = GObject.Property(type=Gedit.App)
+ __instance = None
+
+ def __init__(self):
+ GObject.Object.__init__(self)
+ SessionSaverAppActivatable.__instance = self
+
+ @classmethod
+ def get_instance(cls):
+ return cls.__instance
+
+ def do_activate(self):
+ self._insert_session_menu()
+
+ def do_deactivate(self):
+ self.menu_ext = None
+
+ def _insert_session_menu(self):
+ self.menu_ext = self.extend_menu("tools-section")
+
+ item = Gio.MenuItem.new(_("_Manage Saved Sessions..."), "win.managedsession")
+ self.menu_ext.append_menu_item(item)
+
+ item = Gio.MenuItem.new(_("_Save Session..."), "win.savesession")
+ self.menu_ext.append_menu_item(item)
+
+ self.sessions = XMLSessionStore()
+ for i, session in enumerate(self.sessions):
+ session_id = 'win.session_{0}'.format(i)
+ item = Gio.MenuItem.new(_("Recover '{0}' Session").format(session.name), session_id)
+ self.menu_ext.append_menu_item(item)
+
+ def _remove_session_menu(self):
+ self.menu_ext.remove_items()
+
+ def update_session_menu(self):
+ self._remove_session_menu()
+ self._insert_session_menu()
diff --git a/plugins/sessionsaver/sessionsaver/dialogs.py b/plugins/sessionsaver/sessionsaver/dialogs.py
new file mode 100644
index 0000000..f8b80ee
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver/dialogs.py
@@ -0,0 +1,197 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2007 - Steve Frécinaux <code istique net>
+# Copyright (c) 2010 - Kenny Meyer <knny myer gmail com>
+# Licence: GPL2 or later
+
+from gi.repository import GObject, Gtk, Gedit
+import os.path
+from gpdefs import GETTEXT_PACKAGE
+from .store.session import Session
+
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit-plugins')
+ gettext.textdomain('gedit-plugins')
+ _ = gettext.gettext
+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 __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, 'ui', 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, sessions, current_session, on_updated_sessions, data_dir):
+ super(SaveSessionDialog, self).__init__('save-session-dialog',
+ data_dir,
+ window)
+
+ self.NAME_COLUMN = 1
+ self.on_updated_sessions = on_updated_sessions
+ self.sessions = sessions
+
+ model = SessionModel(sessions)
+
+ self.combobox = self['session-name']
+ self.combobox.set_model(model)
+ self.combobox.set_entry_text_column(self.NAME_COLUMN)
+ self.combobox.connect("changed", self.on_name_combo_changed)
+
+ if current_session is None:
+ self.on_name_combo_changed(self.combobox)
+ else:
+ self._set_combobox_active_by_name(current_session)
+
+ self.dialog.connect('response', self.on_response)
+
+ def _set_combobox_active_by_name(self, option_name):
+ model = self.combobox.get_model()
+ piter = model.get_iter_first()
+ while piter is not None:
+ if model.get_value(piter, self.NAME_COLUMN) == option_name:
+ self.combobox.set_active_iter(piter)
+ return True
+ piter = model.iter_next(piter)
+ return False
+
+ def on_name_combo_changed(self, combo):
+ name = combo.get_child().get_text()
+ self['save_button'].set_sensitive(len(name) > 0)
+
+ 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.combobox.get_child().get_text()
+ self.sessions.add(Session(name, files))
+ self.sessions.save()
+ self.on_updated_sessions()
+ self.destroy()
+
+class SessionManagerDialog(Dialog):
+ def __init__(self, window, on_updated_sessions, on_load_session, sessions, data_dir):
+ super(SessionManagerDialog, self).__init__('session-manager-dialog',
+ data_dir,
+ window)
+
+ self.on_updated_sessions = on_updated_sessions
+ self.on_load_session = on_load_session
+ self.sessions = sessions
+ self.sessions_updated = False
+
+ 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._should_save_sessions()
+ 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.on_load_session(session)
+
+ def on_delete_button_clicked(self, button):
+ session = self.get_current_session()
+ self.sessions.remove(session)
+ self.sessions_updated = True
+
+ def _should_save_sessions(self):
+ if self.sessions_updated == False:
+ return
+
+ self.sessions.save()
+ self.on_updated_sessions()
+ self.sessions_updated = False
+
+ def on_close_button_clicked(self, button):
+ self._should_save_sessions()
+ self.destroy()
+
+
+# ex:ts=4:et:
diff --git a/plugins/sessionsaver/sessionsaver/store/session.py
b/plugins/sessionsaver/sessionsaver/store/session.py
new file mode 100644
index 0000000..bcbd56b
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver/store/session.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# store.py
+# This file is part of gedit Session Saver Plugin
+#
+# Copyright (C) 2006-2007 - Steve Frécinaux <code istique net>
+# Copyright (C) 2010 - Kenny Meyer <knny myer gmail com>
+#
+# 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 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 __eq__(self, session):
+ return (self.name.lower() == session.name.lower())
+
+ def add_file(self, filename):
+ self.files.append(Gio.file_new_for_uri(filename))
+
+# ex:ts=4:et:
diff --git a/plugins/sessionsaver/sessionsaver/store/sessionstore.py
b/plugins/sessionsaver/sessionsaver/store/sessionstore.py
new file mode 100644
index 0000000..dd80a9a
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver/store/sessionstore.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+# store.py
+# This file is part of gedit Session Saver Plugin
+#
+# Copyright (C) 2006-2007 - Steve Frécinaux <code istique net>
+# Copyright (C) 2010 - Kenny Meyer <knny myer gmail com>
+#
+# 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 gi.repository import GObject, GLib, Gio
+from .session import Session
+
+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,))
+ }
+
+ 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)
+
+# ex:ts=4:et:
diff --git a/plugins/sessionsaver/sessionsaver/store/xmlsessionstore.py
b/plugins/sessionsaver/sessionsaver/store/xmlsessionstore.py
new file mode 100644
index 0000000..2b824f4
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver/store/xmlsessionstore.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+# store.py
+# This file is part of gedit Session Saver Plugin
+#
+# Copyright (C) 2006-2007 - Steve Frécinaux <code istique net>
+# Copyright (C) 2010 - Kenny Meyer <knny myer gmail com>
+#
+# 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 GLib
+from .sessionstore import SessionStore
+from .session import 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 __init__(self, filename):
+ super(XMLSessionStore, self).__init__()
+ self.filename = filename
+ self.load()
+
+ def _escape(self, string):
+ return string.replace('&', '&') \
+ .replace('<', '<') \
+ .replace('>', '>') \
+ .replace('"', '"')
+
+ def _dump_session(self, session):
+ files = ''.join([' <file path="%s"/>\n' % self._escape(location.get_uri())
+ for location in session.files])
+ session_name = self._escape(str(session.name))
+ return '<session name="%s">\n%s</session>\n' % (session_name, files)
+
+ def dump(self):
+ dump = [self._dump_session(session) for session in self]
+ return '<saved-sessions>\n%s</saved-sessions>\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('<?xml version="1.0" encoding="UTF-8"?>\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:
diff --git a/plugins/sessionsaver/sessionsaver/ui/sessionsaver.ui
b/plugins/sessionsaver/sessionsaver/ui/sessionsaver.ui
new file mode 100644
index 0000000..882066a
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver/ui/sessionsaver.ui
@@ -0,0 +1,220 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkDialog" id="save-session-dialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="title" translatable="yes">Save session</property>
+ <property name="resizable">False</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button2">
+ <property name="label">gtk-cancel</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="save_button">
+ <property name="label">gtk-save</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Session name:</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="session-name">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_entry">True</property>
+ <child internal-child="entry">
+ <object class="GtkEntry" id="combobox-entry2">
+ <property name="can_focus">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">button2</action-widget>
+ <action-widget response="-5">save_button</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkWindow" id="session-manager-dialog">
+ <property name="width_request">400</property>
+ <property name="height_request">200</property>
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Saved Sessions</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="session-view">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="headers_visible">False</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection1"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVButtonBox" id="vbuttonbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <property name="layout_style">start</property>
+ <child>
+ <object class="GtkButton" id="open-button">
+ <property name="label">gtk-open</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_open_button_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="delete-button">
+ <property name="label">gtk-delete</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_delete_button_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="close-button">
+ <property name="label">gtk-close</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_close_button_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ <property name="secondary">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/plugins/sessionsaver/sessionsaver/windowactivable.py
b/plugins/sessionsaver/sessionsaver/windowactivable.py
new file mode 100644
index 0000000..60f8469
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver/windowactivable.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+#
+# Copyrignt (C) 2019 Jordi Mas <jmas softcatala org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+from gi.repository import GObject, Gio, Gedit
+from .dialogs import SaveSessionDialog, SessionManagerDialog
+from .store.xmlsessionstore import XMLSessionStore
+from .appactivable import SessionSaverAppActivatable
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit-plugins')
+ gettext.textdomain('gedit-plugins')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+
+class SessionSaverWindowActivatable(GObject.Object, Gedit.WindowActivatable):
+
+ __gtype_name__ = "SessionSaverWindowActivatable"
+ window = GObject.Property(type=Gedit.Window)
+
+ def __init__(self):
+ GObject.Object.__init__(self)
+ self.sessions = XMLSessionStore()
+ self.n_sessions = 0
+ self.current_session = None
+
+ def do_activate(self):
+ self._insert_menus()
+
+ def _insert_menus(self):
+ action = Gio.SimpleAction(name="managedsession")
+ action.connect('activate', lambda a, p: self._on_manage_sessions_action())
+ self.window.add_action(action)
+
+ action = Gio.SimpleAction(name="savesession")
+ action.connect('activate', lambda a, p: self._on_save_session_action())
+ self.window.add_action(action)
+
+ self.sessions = XMLSessionStore()
+ self.n_sessions = len(self.sessions)
+ for i, session in enumerate(self.sessions):
+ session_id = 'session_{0}'.format(i)
+ action = Gio.SimpleAction(name=session_id)
+ action.connect('activate', self._session_menu_action, session)
+ self.window.add_action(action)
+
+ def _remove_menus(self):
+ self.window.remove_action("managedsession")
+ self.window.remove_action("savesession")
+
+ for i in range(self.n_sessions):
+ session_id = 'session_{0}'.format(i)
+ self.window.remove_action(session_id)
+
+ def _session_menu_action(self, action, parameter, session):
+ self.load_session(session)
+
+ def do_deactivate(self):
+ self._remove_menus()
+ return
+
+ def do_update_state(self):
+ return
+
+ def _on_manage_sessions_action(self):
+ data_dir = SessionSaverAppActivatable.get_instance().plugin_info.get_data_dir()
+ dialog = SessionManagerDialog(self.window, self.on_updated_sessions, self.load_session,
self.sessions, data_dir)
+ dialog.run()
+
+ def _on_save_session_action(self):
+ data_dir = SessionSaverAppActivatable.get_instance().plugin_info.get_data_dir()
+ dialog = SaveSessionDialog(self.window, self.sessions, self.current_session,
self.on_updated_sessions, data_dir)
+ dialog.run()
+
+ def on_updated_sessions(self):
+ SessionSaverAppActivatable.get_instance().update_session_menu()
+ self._remove_menus()
+ self._insert_menus()
+
+ 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)
+ self.current_session = session.name
diff --git a/plugins/sessionsaver/tests/meson.build b/plugins/sessionsaver/tests/meson.build
new file mode 100644
index 0000000..eaf7da6
--- /dev/null
+++ b/plugins/sessionsaver/tests/meson.build
@@ -0,0 +1,23 @@
+sessionsaver_tests = {
+ 'session': files('testsession.py'),
+ 'sessionstore': files('testsessionstore.py'),
+ 'xmlsessionstore': files('testxmlsessionstore.py'),
+}
+
+sessionsaver_srcdir = join_paths(
+ srcdir,
+ 'plugins',
+ 'sessionsaver',
+ 'sessionsaver',
+)
+
+foreach test_name, test_script : sessionsaver_tests
+ test(
+ 'test-sessionsaver-@0@'.format(test_name),
+ python3,
+ args: [test_script],
+ env: [
+ 'PYTHONPATH=@0@'.format(sessionsaver_srcdir),
+ ]
+ )
+endforeach
diff --git a/plugins/sessionsaver/tests/test-saved-sessions.xml
b/plugins/sessionsaver/tests/test-saved-sessions.xml
new file mode 100644
index 0000000..4be2aae
--- /dev/null
+++ b/plugins/sessionsaver/tests/test-saved-sessions.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<saved-sessions>
+<session name="files">
+ <file path="file:///xmlsessionstore.py"/>
+</session>
+<session name="gspell">
+ <file path="file:///home/jordi/dev/gedit/gspell/gspell/gspellregion.c"/>
+ <file path="file:///home/jordi/dev/gedit/gspell/gspell/gspell-text-view.c"/>
+ <file path="file:///home/jordi/dev/gedit/gspell/gspell/gspell-text-buffer.c"/>
+ <file path="file:///home/jordi/dev/gedit/gspell/gspell/gspell-navigator.c"/>
+ <file path="file:///home/jordi/dev/gedit/gspell/gspell/gspell-language-chooser.c"/>
+ <file path="file:///home/jordi/dev/gedit/gspell/gspell/gspell-language-chooser-dialog.c"/>
+ <file path="file:///home/jordi/dev/gedit/gspell/gspell/gspell-language-chooser-button.c"/>
+</session>
+<session name="window-activable">
+ <file path="file:///home/jordi/dev/gedit/gedit-plugins/plugins/sessionsaver/meson.build"/>
+ <file
path="file:///home/jordi/dev/gedit/gedit-plugins/plugins/sessionsaver/sessionsaver/windowactivable.py"/>
+</session>
+</saved-sessions>
diff --git a/plugins/sessionsaver/tests/testsession.py b/plugins/sessionsaver/tests/testsession.py
new file mode 100644
index 0000000..e3cad66
--- /dev/null
+++ b/plugins/sessionsaver/tests/testsession.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2019 Jordi Mas i Hernandez <jmas softcatala org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+
+import unittest
+from store.session import Session
+
+class TestSession(unittest.TestCase):
+
+ def test_add_file(self):
+ session = Session("session_A")
+ session.add_file('file')
+ self.assertEqual(1, len(session.files))
+
+ def test_compare_objects(self):
+ session_a = Session("session_A")
+ session_b = Session("session_B")
+ self.assertTrue(session_a < session_b)
+ self.assertTrue(session_b > session_a)
+ self.assertTrue(session_a != session_b)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/plugins/sessionsaver/tests/testsessionstore.py b/plugins/sessionsaver/tests/testsessionstore.py
new file mode 100644
index 0000000..06ec32f
--- /dev/null
+++ b/plugins/sessionsaver/tests/testsessionstore.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2019 Jordi Mas i Hernandez <jmas softcatala org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+
+import unittest
+from store.session import Session
+from store.sessionstore import SessionStore
+
+class TestSessionStore(unittest.TestCase):
+
+ def test_new_two_objects(self):
+ session_a = Session("session_A")
+ store_a = SessionStore()
+ store_a.add(session_a)
+
+ session_b = Session("session_B")
+ session_c = Session("session_C")
+ store_b = SessionStore()
+ store_b.add(session_b)
+ store_b.add(session_c)
+
+ self.assertEqual(1, len(store_a))
+ self.assertEqual(2, len(store_b))
+
+ def _on_session_added(self, store, session):
+ self.added_called = True
+
+ def test_add(self):
+ self.added_called = False
+ session = Session("session_A")
+ store = SessionStore()
+ store.connect_after('session-added', self._on_session_added)
+ store.add(session)
+
+ self.assertEqual(1, len(store))
+ self.assertTrue(self.added_called)
+
+ def _on_session_updated(self, store, session):
+ self.updated_called = True
+
+
+ def test_add_same_object_update(self):
+ self.updated_called = False
+ session = Session("session_A")
+ store = SessionStore()
+ store.connect_after('session-added', self._on_session_updated)
+ store.add(session)
+ session.name = 'Session B'
+ store.add(session)
+
+ self.assertEqual(1, len(store))
+ self.assertEqual('Session B', store[0].name)
+ self.assertTrue(self.updated_called)
+
+ def test_add_equal_object(self):
+ session_a = Session("session_A")
+ session_b = Session("session_A")
+ store = SessionStore()
+ store.add(session_a)
+ store.add(session_b)
+ self.assertEqual(1, len(store))
+
+ def test_remove(self):
+ session = Session("session_A")
+ store = SessionStore()
+ store.add(session)
+ store.remove(session)
+ self.assertEqual(0, len(store))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/plugins/sessionsaver/tests/testxmlsessionstore.py
b/plugins/sessionsaver/tests/testxmlsessionstore.py
new file mode 100644
index 0000000..48d088f
--- /dev/null
+++ b/plugins/sessionsaver/tests/testxmlsessionstore.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2019 Jordi Mas i Hernandez <jmas softcatala org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+
+import unittest
+import tempfile
+from store.session import Session
+from store.xmlsessionstore import XMLSessionStore
+from os import path
+
+class TestXmlSessionStore(unittest.TestCase):
+
+ def test_load(self):
+
+ store_file = path.dirname(path.realpath(__file__))
+ store_file += '/test-saved-sessions.xml'
+ print(store_file)
+
+ store = XMLSessionStore(store_file)
+ self.assertEqual(3, len(store))
+ self.assertEqual('files', store[0].name)
+ self.assertEqual('gspell', store[1].name)
+ self.assertEqual('window-activable', store[2].name)
+
+ self.assertEqual(1, len(store[0].files))
+ self.assertEqual("/xmlsessionstore.py", store[0].files[0].get_path())
+
+ def test_save(self):
+ session_name = "session_A"
+ tmpfile = tempfile.NamedTemporaryFile()
+ store = XMLSessionStore(tmpfile.name)
+ session = Session(session_name)
+ store.add(session)
+ store.save()
+
+ store = XMLSessionStore(tmpfile.name)
+ self.assertEqual(1, len(store))
+ self.assertEqual(session_name, store[0].name)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0f4a3ae..dca1ae5 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -42,6 +42,11 @@ plugins/multiedit/gedit-multiedit.metainfo.xml.in
plugins/multiedit/multiedit/appactivatable.py
plugins/multiedit/multiedit.plugin.desktop.in.in
plugins/multiedit/multiedit/viewactivatable.py
+plugins/sessionsaver/sessionsaver/appactivable.py
+plugins/sessionsaver/sessionsaver/dialogs.py
+plugins/sessionsaver/sessionsaver.plugin.desktop.in.in
+plugins/sessionsaver/sessionsaver/ui/sessionsaver.ui
+plugins/sessionsaver/sessionsaver/windowactivable.py
plugins/smartspaces/gedit-smartspaces.metainfo.xml.in
plugins/smartspaces/smartspaces.plugin.desktop.in.in
plugins/terminal/gedit-terminal.metainfo.xml.in
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]