From e14cd196901034c42b979b3dd5cc3baffda7ef22 Mon Sep 17 00:00:00 2001 From: Stephen Boddy Date: Wed, 28 Aug 2013 23:09:17 +0200 Subject: [PATCH 1/6] Initial cut of layout launcher. It 'aint pretty, but it works --- doc/terminator.1 | 11 ++- terminator | 19 ++++-- terminatorlib/config.py | 3 +- terminatorlib/layoutlauncher.glade | 94 ++++++++++++++++++++++++++ terminatorlib/layoutlauncher.py | 103 +++++++++++++++++++++++++++++ terminatorlib/optionparse.py | 4 +- terminatorlib/prefseditor.py | 3 +- terminatorlib/terminal.py | 19 ++---- terminatorlib/terminator.py | 24 +++++++ terminatorlib/util.py | 15 +++++ 10 files changed, 269 insertions(+), 26 deletions(-) create mode 100644 terminatorlib/layoutlauncher.glade create mode 100755 terminatorlib/layoutlauncher.py diff --git a/doc/terminator.1 b/doc/terminator.1 index c63edc16..71d86a20 100644 --- a/doc/terminator.1 +++ b/doc/terminator.1 @@ -45,7 +45,9 @@ based on the wishes of the child shell. Specifies the preferred size and position of Terminator's window; see X(7). .TP .B \-e, \-\-command=COMMAND -Runs the specified command instead of your default shell or profile specified command +Runs the specified command instead of your default shell or profile specified command. Note: if +Terminator is launched as x-terminal-emulator \-e behaves like \-x, and the longform becomes +\-\-execute2=COMMAND .TP .B \-x, \-\-execute COMMAND [ARGS] Runs \fBthe rest of the command line\fR instead of your default shell or profile specified command. @@ -63,6 +65,9 @@ Set a custom name (WM_CLASS) property on the window Start Terminator with a specific layout. The argument here is the name of a saved layout. .TP +.B \-s, \-\-select-layout=LAYOUT +Open the layout launcher window instead of the normal terminal. +.TP .B \-p, \-\-profile=PROFILE Use a different profile as the default .TP @@ -94,10 +99,10 @@ used to spawn a new tab in the first Terminator window. The following keybindings can be used to control Terminator: .TP .B Super+R -\fBR\fotate terminals clockwise. +\fBR\fRotate terminals clockwise. .TP .B Super+Shift+R -\fBR\fotate terminals counter-clockwise. +\fBR\fRotate terminals counter-clockwise. .TP .B Ctrl+Shift+O Split terminals H\fBo\fRrizontally. diff --git a/terminator b/terminator index 4302e11d..7175d264 100755 --- a/terminator +++ b/terminator @@ -47,6 +47,7 @@ from terminatorlib.terminator import Terminator from terminatorlib.factory import Factory from terminatorlib.version import APP_NAME, APP_VERSION from terminatorlib.util import dbg, err +from terminatorlib.layoutlauncher import LayoutLauncher if __name__ == '__main__': dbus_service = None @@ -97,13 +98,17 @@ if __name__ == '__main__': TERMINATOR.set_origcwd(ORIGCWD) TERMINATOR.set_dbus_data(dbus_service) TERMINATOR.reconfigure() - try: - dbg('Creating a terminal with layout: %s' % OPTIONS.layout) - TERMINATOR.create_layout(OPTIONS.layout) - except (KeyError,ValueError), ex: - err('layout creation failed, creating a window ("%s")' % ex) - TERMINATOR.new_window() - TERMINATOR.layout_done() + if OPTIONS.select: + # launch gui, return selection + LAYOUTLAUNCHER=LayoutLauncher() + else: + try: + dbg('Creating a terminal with layout: %s' % OPTIONS.layout) + TERMINATOR.create_layout(OPTIONS.layout) + except (KeyError,ValueError), ex: + err('layout creation failed, creating a window ("%s")' % ex) + TERMINATOR.new_window() + TERMINATOR.layout_done() if OPTIONS.debug > 2: import terminatorlib.debugserver as debugserver diff --git a/terminatorlib/config.py b/terminatorlib/config.py index ec581bc4..b1feaf10 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -173,7 +173,8 @@ DEFAULTS = { 'broadcast_all' : '', 'insert_number' : '', 'insert_padded' : '', - 'edit_window_title': '' + 'edit_window_title': '', + 'layout_launcher' : '' }, 'profiles': { 'default': { diff --git a/terminatorlib/layoutlauncher.glade b/terminatorlib/layoutlauncher.glade new file mode 100644 index 00000000..989f3a1b --- /dev/null +++ b/terminatorlib/layoutlauncher.glade @@ -0,0 +1,94 @@ + + + + + + False + Terminator Layout Launcher + + + + True + False + 7 + 7 + + + True + False + 0 + 0 + in + + + 150 + 200 + True + True + layoutstore + False + 0 + + + Layout + + + + 0 + + + + + + + + + + + + True + True + 0 + + + + + True + False + + + + + + Launch + 100 + True + True + True + False + + + + False + True + end + 1 + + + + + False + True + 1 + + + + + + + + + + + + diff --git a/terminatorlib/layoutlauncher.py b/terminatorlib/layoutlauncher.py new file mode 100755 index 00000000..a087162d --- /dev/null +++ b/terminatorlib/layoutlauncher.py @@ -0,0 +1,103 @@ +#!/usr/bin/python +"""Layout Launcher for Terminator. + +Load a UIBuilder config file, display it, populate it with our current layouts, +then allow launching it as a new instance + +""" + +import os +import gtk + +from util import dbg, err, spawn_new_terminator +import config +from translation import _ +from terminator import Terminator +from plugin import PluginRegistry + +class LayoutLauncher: + """Class implementing the various parts of the preferences editor""" + terminator = None + config = None + registry = None + plugins = None + keybindings = None + window = None + builder = None + layouttreeview = None + layouttreestore = None + + def __init__ (self): #, term): + self.terminator = Terminator() + self.terminator.register_launcher_window(self) + + self.config = config.Config() + #self.term = term + self.builder = gtk.Builder() + #self.keybindings = Keybindings() + try: + # Figure out where our library is on-disk so we can open our + (head, _tail) = os.path.split(config.__file__) + librarypath = os.path.join(head, 'layoutlauncher.glade') + gladefile = open(librarypath, 'r') + gladedata = gladefile.read() + except Exception, ex: + print "Failed to find layoutlauncher.glade" + print ex + return + + self.builder.add_from_string(gladedata) + self.window = self.builder.get_object('layoutlauncherwin') + #self.layouteditor = LayoutEditor(self.builder) + self.builder.connect_signals(self) + self.window.connect('destroy', self.on_destroy_event) + #self.layouteditor.prepare() + self.window.show_all() + self.layouttreeview = self.builder.get_object('layoutlist') + self.layouttreestore = self.builder.get_object('layoutstore') + self.update_layouts() + try: + #self.config.inhibit_save() + #self.set_values() + pass + except Exception, e: + err('Unable to set values: %s' % e) + #self.config.uninhibit_save() + + def on_destroy_event(self, widget, data=None): + """Handle window destruction""" + dbg('destroying self') + self.terminator.deregister_launcher_window(self) + self.window.destroy() + del(self.window) + + def update_layouts(self): + """Update the contents of the layout""" + self.layouttreestore.clear() + layouts = self.config.list_layouts() + for layout in sorted(layouts, cmp=lambda x,y: cmp(x.lower(), y.lower())): + if layout != "default": + self.layouttreestore.append([layout]) + else: + self.layouttreestore.prepend([layout]) + + def on_launchbutton_clicked(self, widget): + """Launch the selected layout as new instance""" + dbg('We have takeoff!') + selection=self.layouttreeview.get_selection() + (listmodel, rowiter) = selection.get_selected() + if not rowiter: + # Something is wrong, just jump to the first item in the list + selection.select_iter(self.layouttreestore.get_iter_first()) + (listmodel, rowiter) = selection.get_selected() + layout = listmodel.get_value(rowiter, 0) + dbg('Clicked for %s' % layout) + spawn_new_terminator(self.terminator.origcwd, ['-u', '-l', layout]) + +if __name__ == '__main__': + import util + util.DEBUG = True + import terminal + LAYOUTLAUNCHER = LayoutLauncher() + + gtk.main() diff --git a/terminatorlib/optionparse.py b/terminatorlib/optionparse.py index e395f590..1c36ad21 100755 --- a/terminatorlib/optionparse.py +++ b/terminatorlib/optionparse.py @@ -86,7 +86,9 @@ icon for the window (by file or name)')) parser.add_option('-r', '--role', dest='role', help=_('Set a custom WM_WINDOW_ROLE property on the window')) parser.add_option('-l', '--layout', dest='layout', - help=_('Select a layout')) + help=_('Launch with the given layout')) + parser.add_option('-s', '--select-layout', action='store_true', + dest='select', help=_('Select a layout from a list')) parser.add_option('-p', '--profile', dest='profile', help=_('Use a different profile as the default')) parser.add_option('-u', '--no-dbus', action='store_true', dest='nodbus', diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 46c4aca5..a5f09c49 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -143,7 +143,8 @@ class PrefsEditor: 'broadcast_all' : 'Broadcast key events to all', 'insert_number' : 'Insert terminal number', 'insert_padded' : 'Insert zero padded terminal number', - 'edit_window_title': 'Edit window title' + 'edit_window_title': 'Edit window title', + 'layout_launcher' : 'Open layout launcher window' } def __init__ (self, term): diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 8f5eaf73..e7fe740f 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -15,7 +15,7 @@ import pango import subprocess import urllib -from util import dbg, err, gerr +from util import dbg, err, gerr, spawn_new_terminator import util from config import Config from cwd import get_default_cwd @@ -27,6 +27,7 @@ from searchbar import Searchbar from translation import _ from signalman import Signalman import plugin +from terminatorlib.layoutlauncher import LayoutLauncher try: import vte @@ -1680,18 +1681,7 @@ class Terminal(gtk.VBox): self.terminator.new_window(self.terminator.pid_cwd(self.pid)) def key_new_terminator(self): - cmd = sys.argv[0] - - if not os.path.isabs(cmd): - # Command is not an absolute path. Figure out where we are - cmd = os.path.join (self.origcwd, sys.argv[0]) - if not os.path.isfile(cmd): - # we weren't started as ./terminator in a path. Give up - err('Terminal::key_new_window: Unable to locate Terminator') - return False - - dbg("Terminal::key_new_window: Spawning: %s" % cmd) - subprocess.Popen([cmd, '-u']) + spawn_new_terminator(self.origcwd, ['-u']) def key_broadcast_off(self): self.set_groupsend(None, self.terminator.groupsend_type['off']) @@ -1742,6 +1732,9 @@ class Terminal(gtk.VBox): dialog.destroy() return + def key_layout_launcher(self): + LAYOUTLAUNCHER=LayoutLauncher() + def key_page_up(self): self.scroll_by_page(-1) diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index 464d4c3c..c4f9fce4 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -19,6 +19,7 @@ class Terminator(Borg): """master object for the application""" windows = None + launcher_windows = None windowtitle = None terminals = None groups = None @@ -48,6 +49,8 @@ class Terminator(Borg): if not self.windows: self.windows = [] + if not self.launcher_windows: + self.launcher_windows = [] if not self.terminals: self.terminals = [] if not self.groups: @@ -129,6 +132,27 @@ class Terminator(Borg): dbg('no windows remain, quitting') gtk.main_quit() + def register_launcher_window(self, window): + """Register a new launcher window widget""" + if window not in self.launcher_windows: + dbg('Terminator::register_launcher_window: registering %s:%s' % (id(window), + type(window))) + self.launcher_windows.append(window) + + def deregister_launcher_window(self, window): + """de-register a launcher window widget""" + dbg('Terminator::deregister_launcher_window: de-registering %s:%s' % + (id(window), type(window))) + if window in self.launcher_windows: + self.launcher_windows.remove(window) + else: + err('%s is not in registered window list' % window) + + if len(self.launcher_windows) == 0 and len(self.windows) == 0: + # We have no windows left, we should exit + dbg('no windows remain, quitting') + gtk.main_quit() + def register_terminal(self, terminal): """Register a new terminal widget""" if terminal not in self.terminals: diff --git a/terminatorlib/util.py b/terminatorlib/util.py index b8ad5aba..c9143df2 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -29,6 +29,7 @@ import os import pwd import inspect import uuid +import subprocess # set this to true to enable debugging output DEBUG = False @@ -289,3 +290,17 @@ def inject_uuid(target): else: dbg("Object already has a UUID: %s" % target) +def spawn_new_terminator(cwd, args): + """Start a new terminator instance with the given arguments""" + cmd = sys.argv[0] + + if not os.path.isabs(cmd): + # Command is not an absolute path. Figure out where we are + cmd = os.path.join (cwd, sys.argv[0]) + if not os.path.isfile(cmd): + # we weren't started as ./terminator in a path. Give up + err('Unable to locate Terminator') + return False + + dbg("Spawning: %s" % cmd) + subprocess.Popen([cmd]+args) From 0dab69f76f0701bb41466a563b19d5b4e4b1ee65 Mon Sep 17 00:00:00 2001 From: Stephen Boddy Date: Thu, 29 Aug 2013 13:00:20 +0200 Subject: [PATCH 2/6] Need to add the glade file to setup, or the debuild doesn't add it to the package --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4e5ee4c7..f8ea03fd 100755 --- a/setup.py +++ b/setup.py @@ -180,7 +180,7 @@ setup(name=APP_NAME.capitalize(), ], packages=['terminatorlib', 'terminatorlib.configobj', 'terminatorlib.plugins'], - package_data={'terminatorlib': ['preferences.glade']}, + package_data={'terminatorlib': ['preferences.glade', 'layoutlauncher.glade']}, cmdclass={'build': BuildData, 'install_data': InstallData, 'uninstall': Uninstall}, distclass=TerminatorDist ) From 27555740b39e6160bfadf08e9bd1cfa025d48810 Mon Sep 17 00:00:00 2001 From: Stephen Boddy Date: Thu, 29 Aug 2013 19:43:24 +0200 Subject: [PATCH 3/6] This fixes the issue where another instance of terminator can overwrite config changes you make. Makes setting up layouts more usable. --- terminatorlib/config.py | 5 +++++ terminatorlib/prefseditor.py | 1 + 2 files changed, 6 insertions(+) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index b1feaf10..f122287a 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -620,6 +620,11 @@ class ConfigBase(Borg): section_name) self.loaded = True + + def reload(self): + """Force a reload of the base config""" + self.loaded = False + self.load() def save(self): """Save the config to a file""" diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index a5f09c49..bbc063b7 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -149,6 +149,7 @@ class PrefsEditor: def __init__ (self, term): self.config = config.Config() + self.config.base.reload() self.term = term self.builder = gtk.Builder() self.keybindings = Keybindings() From 7f49bcfab9ec26add737cde9b051239559445976 Mon Sep 17 00:00:00 2001 From: Stephen Boddy Date: Mon, 2 Sep 2013 16:54:07 +0200 Subject: [PATCH 4/6] Fix spliiter positions in Layouts. --- terminatorlib/container.py | 3 +++ terminatorlib/paned.py | 7 ++++--- terminatorlib/terminator.py | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 59acdb3b..fba84fa4 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -256,6 +256,9 @@ the %s will also close all terminals within it.') % (reqtype, reqtype)) if hasattr(position, '__iter__'): position = ':'.join([str(x) for x in position]) layout['position'] = position + + if hasattr(self, 'ratio'): + layout['ratio'] = self.ratio if hasattr(self, 'get_size'): layout['size'] = self.get_size() diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index 3a3eafab..119b437d 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -350,9 +350,10 @@ class Paned(Container): self.get_child1().create_layout(children[keys[0]]) self.get_child2().create_layout(children[keys[1]]) - # Store the position for later - if layout['position']: - self.position = int(layout['position']) + # Set the position with ratio. For some reason more reliable than by pos. + if layout.has_key('ratio'): + self.ratio = float(layout['ratio']) + self.set_position_by_ratio() def grab_focus(self): """We don't want focus, we want a Terminal to have it""" diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index c4f9fce4..831bd094 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -262,7 +262,6 @@ class Terminator(Borg): raise(ValueError) dbg('Creating a window') window, terminal = self.new_window() - window.create_layout(layout[windef]) if layout[windef].has_key('position'): parts = layout[windef]['position'].split(':') if len(parts) == 2: @@ -275,6 +274,7 @@ class Terminator(Borg): window.resize(winx, winy) if layout[windef].has_key('title'): window.title.force_title(layout[windef]['title']) + window.create_layout(layout[windef]) def layout_done(self): """Layout operations have finished, record that fact""" From 4f7464b91537ebf0848dd6cdde156cf826a19946 Mon Sep 17 00:00:00 2001 From: Stephen Boddy Date: Mon, 2 Sep 2013 17:21:12 +0200 Subject: [PATCH 5/6] Layout launcher now responds to double-clicks and Return/Space activation --- terminatorlib/layoutlauncher.glade | 1 + terminatorlib/layoutlauncher.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/terminatorlib/layoutlauncher.glade b/terminatorlib/layoutlauncher.glade index 989f3a1b..eef81e5f 100644 --- a/terminatorlib/layoutlauncher.glade +++ b/terminatorlib/layoutlauncher.glade @@ -28,6 +28,7 @@ layoutstore False 0 + Layout diff --git a/terminatorlib/layoutlauncher.py b/terminatorlib/layoutlauncher.py index a087162d..0590a24d 100755 --- a/terminatorlib/layoutlauncher.py +++ b/terminatorlib/layoutlauncher.py @@ -82,6 +82,14 @@ class LayoutLauncher: self.layouttreestore.prepend([layout]) def on_launchbutton_clicked(self, widget): + """Handle button click""" + self.launch_layout() + + def on_row_activated(self, widget, path, view_column): + """Handle item double-click and return""" + self.launch_layout() + + def launch_layout(self): """Launch the selected layout as new instance""" dbg('We have takeoff!') selection=self.layouttreeview.get_selection() From 103e609faedcaee14267b2f7a192006bf288fdfb Mon Sep 17 00:00:00 2001 From: Stephen Boddy Date: Mon, 2 Sep 2013 17:35:22 +0200 Subject: [PATCH 6/6] Bit of cleanup of old un-needed cruft, and place standard header. --- terminatorlib/layoutlauncher.glade | 2 +- terminatorlib/layoutlauncher.py | 24 +++++------------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/terminatorlib/layoutlauncher.glade b/terminatorlib/layoutlauncher.glade index eef81e5f..477077fc 100644 --- a/terminatorlib/layoutlauncher.glade +++ b/terminatorlib/layoutlauncher.glade @@ -1,6 +1,6 @@ - + False diff --git a/terminatorlib/layoutlauncher.py b/terminatorlib/layoutlauncher.py index 0590a24d..41a0c416 100755 --- a/terminatorlib/layoutlauncher.py +++ b/terminatorlib/layoutlauncher.py @@ -1,10 +1,7 @@ #!/usr/bin/python -"""Layout Launcher for Terminator. - -Load a UIBuilder config file, display it, populate it with our current layouts, -then allow launching it as a new instance - -""" +# Terminator by Chris Jones +# GPL v2 only +"""layoutlauncher.py - class for the Layout Launcher window""" import os import gtk @@ -27,16 +24,14 @@ class LayoutLauncher: layouttreeview = None layouttreestore = None - def __init__ (self): #, term): + def __init__ (self): self.terminator = Terminator() self.terminator.register_launcher_window(self) self.config = config.Config() - #self.term = term self.builder = gtk.Builder() - #self.keybindings = Keybindings() try: - # Figure out where our library is on-disk so we can open our + # Figure out where our library is on-disk so we can open our UI (head, _tail) = os.path.split(config.__file__) librarypath = os.path.join(head, 'layoutlauncher.glade') gladefile = open(librarypath, 'r') @@ -48,21 +43,12 @@ class LayoutLauncher: self.builder.add_from_string(gladedata) self.window = self.builder.get_object('layoutlauncherwin') - #self.layouteditor = LayoutEditor(self.builder) self.builder.connect_signals(self) self.window.connect('destroy', self.on_destroy_event) - #self.layouteditor.prepare() self.window.show_all() self.layouttreeview = self.builder.get_object('layoutlist') self.layouttreestore = self.builder.get_object('layoutstore') self.update_layouts() - try: - #self.config.inhibit_save() - #self.set_values() - pass - except Exception, e: - err('Unable to set values: %s' % e) - #self.config.uninhibit_save() def on_destroy_event(self, widget, data=None): """Handle window destruction"""