From c2cf103374ceaa171b9cdee0fbe5c12b0a206aad Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 7 Aug 2009 10:21:37 +0100 Subject: [PATCH 001/331] start an epic refactor --- terminatorlib/container.py | 87 ++++++++++++++++++++++++++++++++++++++ terminatorlib/util.py | 30 +++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 terminatorlib/container.py create mode 100755 terminatorlib/util.py diff --git a/terminatorlib/container.py b/terminatorlib/container.py new file mode 100644 index 00000000..a2b58b85 --- /dev/null +++ b/terminatorlib/container.py @@ -0,0 +1,87 @@ +#!/usr/bin/python +# Terminator by Chris Jones +# GPL v2 only +"""container.py - classes necessary to contain Terminal widgets""" + +import gtk + +from version import APP_NAME, APP_VERSION +from util import debug, dbg, err + +try: + import deskbar.core.keybinder as bindkey +except ImportError: + err('Unable to find python bindings for deskbar, "hide_window" is not' \ + 'available.') + +class Container: + """Base class for Terminator Containers""" + + immutable = None + children = None + config = None + + def __init__(self, configobject): + """Class initialiser""" + self.children = [] + self.config = configobject + + def get_offspring(self): + """Return a list of child widgets, if any""" + return(self.children) + +class Window(Container, gtk.Window): + """Class implementing a top-level Terminator window""" + + title = None + isfullscreen = None + + def __init__(self, configobject): + """Class initialiser""" + Container.__init__(self, configobject) + gtk.Window.__init__(self) + + self.set_property('allow-shrink', True) + self.register_callbacks() + self.apply_config() + + def register_callbacks(self): + """Connect the GTK+ signals we care about""" + self.connect('key-press-event', self.on_key_press) + self.connect('delete_event', self.on_delete_event) + self.connect('destroy', self.on_destroy_event) + self.connect('window-state-event', self.on_window_state_changed) + + try: + bindkey.tomboy_keybinder_bind( + self.config['keybindings']['hide_window'], + self.on_hide_window) + except KeyError: + dbg('Unable to bind hide_window key, another instance has it.') + + def apply_config(self): + """Apply various configuration options""" + self.set_fullscreen(self.config['fullscreen']) + self.set_maximised(self.config['maximised']) + self.set_borderless(self.config['borderless']) + self.enable_rgba(self.config['enable_real_transparency']) + self.set_hidden(self.config['hidden']) + + def on_key_press(self, window, event): + pass + + def on_delete_event(self): + pass + + def on_destroy_event(self): + pass + + def on_window_state_changed(self): + pass + + def set_fullscreen(self, value): + pass + +CONFIG = {} +WINDOW = Window(CONFIG) +gtk.main() diff --git a/terminatorlib/util.py b/terminatorlib/util.py new file mode 100755 index 00000000..5031e750 --- /dev/null +++ b/terminatorlib/util.py @@ -0,0 +1,30 @@ +#!/usr/bin/python +# TerminatorConfig - layered config classes +# Copyright (C) 2006-2008 cmsj@tenshu.net +# +# 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, version 2 only. +# +# 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 sys + +# set this to true to enable debugging output +debug = False + +def dbg (log = ""): + """Print a message if debugging is enabled""" + if debug: + print >> sys.stderr, log + +def err (log = ""): + """Print an error message""" + print >> sys.stderr, log From 8700c9456f13e3ccfbf60871423ad8d711eee7cd Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 8 Aug 2009 00:31:44 +0100 Subject: [PATCH 002/331] migrate window class to its own file --- terminatorlib/container.py | 71 ++++------------------ terminatorlib/window.py | 119 +++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 61 deletions(-) create mode 100644 terminatorlib/window.py diff --git a/terminatorlib/container.py b/terminatorlib/container.py index a2b58b85..9eed28d7 100644 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -3,17 +3,8 @@ # GPL v2 only """container.py - classes necessary to contain Terminal widgets""" -import gtk - -from version import APP_NAME, APP_VERSION from util import debug, dbg, err -try: - import deskbar.core.keybinder as bindkey -except ImportError: - err('Unable to find python bindings for deskbar, "hide_window" is not' \ - 'available.') - class Container: """Base class for Terminator Containers""" @@ -30,58 +21,16 @@ class Container: """Return a list of child widgets, if any""" return(self.children) -class Window(Container, gtk.Window): - """Class implementing a top-level Terminator window""" + def split_horiz(self, widget): + """Split this container horizontally""" + return(self.split_axis(widget, True)) - title = None - isfullscreen = None + def split_vert(self, widget): + """Split this container vertically""" + return(self.split_axis(widget, False)) - def __init__(self, configobject): - """Class initialiser""" - Container.__init__(self, configobject) - gtk.Window.__init__(self) + def split_axis(self, widget, vertical=True): + """Default axis splitter. This should be implemented by subclasses""" + dbg('split_axis called from base class. This is a bug') + return(False) - self.set_property('allow-shrink', True) - self.register_callbacks() - self.apply_config() - - def register_callbacks(self): - """Connect the GTK+ signals we care about""" - self.connect('key-press-event', self.on_key_press) - self.connect('delete_event', self.on_delete_event) - self.connect('destroy', self.on_destroy_event) - self.connect('window-state-event', self.on_window_state_changed) - - try: - bindkey.tomboy_keybinder_bind( - self.config['keybindings']['hide_window'], - self.on_hide_window) - except KeyError: - dbg('Unable to bind hide_window key, another instance has it.') - - def apply_config(self): - """Apply various configuration options""" - self.set_fullscreen(self.config['fullscreen']) - self.set_maximised(self.config['maximised']) - self.set_borderless(self.config['borderless']) - self.enable_rgba(self.config['enable_real_transparency']) - self.set_hidden(self.config['hidden']) - - def on_key_press(self, window, event): - pass - - def on_delete_event(self): - pass - - def on_destroy_event(self): - pass - - def on_window_state_changed(self): - pass - - def set_fullscreen(self, value): - pass - -CONFIG = {} -WINDOW = Window(CONFIG) -gtk.main() diff --git a/terminatorlib/window.py b/terminatorlib/window.py new file mode 100644 index 00000000..1ab30c5b --- /dev/null +++ b/terminatorlib/window.py @@ -0,0 +1,119 @@ +#!/usr/bin/python +# Terminator by Chris Jones +# GPL v2 only +"""window.py - class for the main Terminator window""" + +import pygtk +pygtk.require('2.0') +import gobject +import gtk +import pango + +from version import APP_NAME, APP_VERSION +from util import debug, dbg, err + +from container import Container + +try: + import deskbar.core.keybinder as bindkey +except ImportError: + err('Unable to find python bindings for deskbar, "hide_window" is not' \ + 'available.') + +class Window(Container, gtk.Window): + """Class implementing a top-level Terminator window""" + + title = None + isfullscreen = None + hidebound = None + + def __init__(self, configobject): + """Class initialiser""" + Container.__init__(self, configobject) + gtk.Window.__init__(self) + + self.set_property('allow-shrink', True) + self.register_callbacks() + self.apply_config() + + def register_callbacks(self): + """Connect the GTK+ signals we care about""" + self.connect('key-press-event', self.on_key_press) + self.connect('delete_event', self.on_delete_event) + self.connect('destroy', self.on_destroy_event) + self.connect('window-state-event', self.on_window_state_changed) + + self.hidebound = False + try: + self.couldbind = bindkey.tomboy_keybinder_bind( + self.config['keybindings']['hide_window'], + self.on_hide_window) + except KeyError: + dbg('Unable to bind hide_window key, another instance has it.') + + def apply_config(self): + """Apply various configuration options""" + self.set_fullscreen(self.config['fullscreen']) + self.set_maximised(self.config['maximised']) + self.set_borderless(self.config['borderless']) + self.set_real_transparency(self.config['enable_real_transparency']) + if self.hidebound: + self.set_hidden(self.config['hidden']) + else: + self.set_iconified(self.config['hidden']) + + def on_key_press(self, window, event): + pass + + def on_delete_event(self, window, event, data=None): + pass + + def on_destroy_event(self, widget, data=None): + pass + + def on_window_state_changed(self, window, event): + self.isfullscreen = bool(event.new_window_state & + gtk.gdk.WINDOW_STATE_FULLSCREEN) + self.ismaximised = bool(event.new_window_state & + gtk.gdk.WINDOW_STATE_MAXIMIZED) + dbg('window state changed: fullscreen %s, maximised %s' % + (self.isfullscreen, self.ismaximised)) + return(False) + + def set_maximised(self, value): + if value == True: + self.maximize() + else: + self.unmaximize() + + def set_fullscreen(self, value): + if value == True: + self.fullscreen() + else: + self.unfullscreen() + + def set_borderless(self, value): + self.set_decorated (not value) + + def set_hidden(self, value): + pass + + def set_iconified(self, value): + pass + + def set_real_transparency(self, value): + screen = self.get_screen() + if value: + colormap = screen.get_rgba_colormap() + else: + colormap = screen.get_rgb_colormap() + + if colormap: + self.set_colormap(colormap) + +CONFIG = {'fullscreen':False, 'maximised':False, 'borderless':False, 'enable_real_transparency':True, 'hidden':False} +WINDOW = Window(CONFIG) +WINDOW.show_all() +gtk.main() + +# vim: set expandtab ts=4 sw=4: From b15a78fdea5995d05c7f265e391924e8bb9b7a87 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 8 Aug 2009 01:00:58 +0100 Subject: [PATCH 003/331] import the name and version into the library root --- terminatorlib/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/terminatorlib/__init__.py b/terminatorlib/__init__.py index 21c69fda..cdcfd509 100644 --- a/terminatorlib/__init__.py +++ b/terminatorlib/__init__.py @@ -17,3 +17,4 @@ """Terminator by Chris Jones """ +from version import APP_NAME, APP_VERSION From 5d1ea593d86f510a172bd9d9fd165923c84187e8 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 8 Aug 2009 01:22:31 +0100 Subject: [PATCH 004/331] migrate dbg,err to util.py --- terminatorlib/config.py | 15 +-------------- terminatorlib/configfile.py | 2 +- terminatorlib/debugserver.py | 2 +- terminatorlib/prefs_profile.py | 3 ++- terminatorlib/terminator.py | 2 +- terminatorlib/terminatorterm.py | 2 +- 6 files changed, 7 insertions(+), 19 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 41db77c7..10b321ab 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -45,20 +45,7 @@ except ImportError: gconf = None from terminatorlib import translation - -# set this to true to enable debugging output -# These should be moved somewhere better. -debug = False - -def dbg (log = ""): - """Print a message if debugging is enabled""" - if debug: - print >> sys.stderr, log - -def err (log = ""): - """Print an error message""" - print >> sys.stderr, log - +from terminatorlib.util import dbg, err from terminatorlib.configfile import ConfigFile, ParsedWithErrors DEFAULTS = { diff --git a/terminatorlib/configfile.py b/terminatorlib/configfile.py index f71f39f0..f40b9587 100644 --- a/terminatorlib/configfile.py +++ b/terminatorlib/configfile.py @@ -1,7 +1,7 @@ #!/usr/bin/python import re -from terminatorlib.config import dbg, debug +from terminatorlib.util import dbg from terminatorlib import translation def group(*choices): return '(' + '|'.join(choices) + ')' diff --git a/terminatorlib/debugserver.py b/terminatorlib/debugserver.py index bcb13d85..2621331a 100644 --- a/terminatorlib/debugserver.py +++ b/terminatorlib/debugserver.py @@ -5,7 +5,7 @@ # Use of this file is unrestricted provided this notice is retained. # If you use it, it'd be nice if you dropped me a note. Also beer. -from terminatorlib.config import dbg, err +from terminatorlib.util import dbg, err from terminatorlib.version import APP_NAME, APP_VERSION import socket diff --git a/terminatorlib/prefs_profile.py b/terminatorlib/prefs_profile.py index bd045e5b..bb991d02 100644 --- a/terminatorlib/prefs_profile.py +++ b/terminatorlib/prefs_profile.py @@ -1,6 +1,7 @@ #!/usr/bin/python -from terminatorlib.config import dbg,err,DEFAULTS,TerminatorConfValuestoreRC +from terminatorlib.util import dbg,err +from terminatorlib.config import DEFAULTS,TerminatorConfValuestoreRC from terminatorlib.keybindings import TerminatorKeybindings from terminatorlib.version import APP_NAME, APP_VERSION from terminatorlib import translation diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index ef0632e1..33a1bb73 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -25,7 +25,7 @@ import gobject, gtk, pango from terminatorlib.version import APP_NAME, APP_VERSION from terminatorlib import config -from config import dbg, err, debug +from util import dbg, err, debug from terminatorlib.keybindings import TerminatorKeybindings from terminatorlib.terminatorterm import TerminatorTerm diff --git a/terminatorlib/terminatorterm.py b/terminatorlib/terminatorterm.py index c5d604ad..2f544a6f 100755 --- a/terminatorlib/terminatorterm.py +++ b/terminatorlib/terminatorterm.py @@ -26,7 +26,7 @@ from terminatorlib.version import * # import our configuration loader from terminatorlib import config -from terminatorlib.config import dbg, err, debug +from terminatorlib.util import dbg, err, debug #import encoding list from terminatorlib.encoding import TerminatorEncoding From ea767f41646b940d0b9e2dd4b1d856d7ccc31c69 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 9 Aug 2009 18:48:06 +0100 Subject: [PATCH 005/331] migrate many more methods --- terminatorlib/container.py | 58 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 9eed28d7..3229704c 100644 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -3,7 +3,7 @@ # GPL v2 only """container.py - classes necessary to contain Terminal widgets""" -from util import debug, dbg, err +from util import debug, err class Container: """Base class for Terminator Containers""" @@ -11,6 +11,11 @@ class Container: immutable = None children = None config = None + state_zoomed = None + + states_zoom = { 'none' : 0, + 'zoomed' : 1, + 'maximised' : 2 } def __init__(self, configobject): """Class initialiser""" @@ -31,6 +36,55 @@ class Container: def split_axis(self, widget, vertical=True): """Default axis splitter. This should be implemented by subclasses""" - dbg('split_axis called from base class. This is a bug') + err('split_axis called from base class. This is a bug') return(False) + def unsplit(self, widget, keep=False): + """Default unsplitter. This should be implemented by subclasses""" + err('unsplit called from base class. This is a bug') + return(False) + + def closeterm(self, widget): + """Handle the closure of a terminal""" + if self.state_zoomed != self.states_zoom['normal']: + self.unzoom(widget) + + if not self.remove(widget): + return(False) + + self.group_hoover() + return(True) + + def closegroupterms(self, widget): + """Handle the closure of a group of terminals""" + if self.state_zoomed != self.states_zoom['normal']: + self.unzoom(widget) + + all_closed = True + for term in self.term_list[:]: + if term._group == widget._group and not self.remove(term): + all_closed = False + + self.group_hoover() + return(all_closed) + + def resizeterm(self, widget, keyname): + """Handle a keyboard event requesting a terminal resize""" + err('resizeterm called from base class. This is a bug') + + def toggle_zoom(self, widget, fontscale = False): + """Toggle the existing zoom state""" + if self.state_zoomed != self.states_zoom['normal']: + self.unzoom(widget) + else: + self.zoom(widget, fontscale) + + def zoom(self, widget, fontscale = False): + """Zoom a terminal""" + err('zoom called from base class. This is a bug') + + def unzoom(self, widget): + """Unzoom a terminal""" + err('unzoom called from base class. This is a bug') + +# vim: set expandtab ts=4 sw=4: From 3df46d1d8563fd1745fbe3e2db59216d4cb81d79 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 9 Aug 2009 22:00:43 +0100 Subject: [PATCH 006/331] attempt to dodge gobject's inability to do multiple inheritance --- terminatorlib/container.py | 29 ++++++++++++++++++++++++++--- terminatorlib/window.py | 3 +++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 3229704c..d8343ba9 100644 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -3,9 +3,12 @@ # GPL v2 only """container.py - classes necessary to contain Terminal widgets""" +import gobject +import gtk + from util import debug, err -class Container: +class Container(): """Base class for Terminator Containers""" immutable = None @@ -17,11 +20,27 @@ class Container: 'zoomed' : 1, 'maximised' : 2 } + signals = [ {'name': 'group-hoover-needed', + 'flags': gobject.SIGNAL_RUN_LAST, + 'return_type': gobject.TYPE_BOOLEAN, + 'param_types': () + } + ] + def __init__(self, configobject): """Class initialiser""" self.children = [] self.config = configobject + def register_signals(self, object): + """Register gobject signals in a way that avoids multiple inheritance""" + for signal in self.signals: + gobject.signal_new(signal['name'], + object, + signal['flags'], + signal['return_type'], + signal['param_types']) + def get_offspring(self): """Return a list of child widgets, if any""" return(self.children) @@ -44,6 +63,10 @@ class Container: err('unsplit called from base class. This is a bug') return(False) + def remove(self, widget): + """Remove a widget from the container""" + err('remove called from base class. This is a bug') + def closeterm(self, widget): """Handle the closure of a terminal""" if self.state_zoomed != self.states_zoom['normal']: @@ -52,7 +75,7 @@ class Container: if not self.remove(widget): return(False) - self.group_hoover() + self.emit('need_group_hoover') return(True) def closegroupterms(self, widget): @@ -65,7 +88,7 @@ class Container: if term._group == widget._group and not self.remove(term): all_closed = False - self.group_hoover() + self.emit('need_group_hoover') return(all_closed) def resizeterm(self, widget, keyname): diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 1ab30c5b..2ed65f89 100644 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -31,6 +31,8 @@ class Window(Container, gtk.Window): """Class initialiser""" Container.__init__(self, configobject) gtk.Window.__init__(self) + gobject.type_register(Window) + self.register_signals(Window) self.set_property('allow-shrink', True) self.register_callbacks() @@ -112,6 +114,7 @@ class Window(Container, gtk.Window): self.set_colormap(colormap) CONFIG = {'fullscreen':False, 'maximised':False, 'borderless':False, 'enable_real_transparency':True, 'hidden':False} + WINDOW = Window(CONFIG) WINDOW.show_all() gtk.main() From 598eededd21659c77257cea19bd653ef9f6b7954 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 9 Aug 2009 23:25:53 +0100 Subject: [PATCH 007/331] Implement equivalent functionality as previous iterations wrt handling the availability of the hide_window keybinding --- terminatorlib/window.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 2ed65f89..d50bafdf 100644 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -26,6 +26,7 @@ class Window(Container, gtk.Window): title = None isfullscreen = None hidebound = None + hidefunc = None def __init__(self, configobject): """Class initialiser""" @@ -45,13 +46,16 @@ class Window(Container, gtk.Window): self.connect('destroy', self.on_destroy_event) self.connect('window-state-event', self.on_window_state_changed) - self.hidebound = False - try: - self.couldbind = bindkey.tomboy_keybinder_bind( - self.config['keybindings']['hide_window'], - self.on_hide_window) - except KeyError: + # Attempt to grab a global hotkey for hiding the window. + # If we fail, we'll never hide the window, iconifying instead. + self.hidebound = bindkey.tomboy_keybinder_bind( + self.config['keybindings']['hide_window'], + self.on_hide_window) + if not self.hidebound: dbg('Unable to bind hide_window key, another instance has it.') + self.hidefunc = self.iconify + else: + self.hidefunc = self.hide def apply_config(self): """Apply various configuration options""" From adb1c2a1f8c686403a3e5c7de5bedd5dee866dcf Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 9 Aug 2009 23:54:14 +0100 Subject: [PATCH 008/331] raise the correct exception if methods are called from the base class that must be overridden by subclasses. Remove unnecessary dependencies and achieve 10/10 from pylint --- terminatorlib/container.py | 45 +++++++++++++++----------------------- 1 file changed, 18 insertions(+), 27 deletions(-) mode change 100644 => 100755 terminatorlib/container.py diff --git a/terminatorlib/container.py b/terminatorlib/container.py old mode 100644 new mode 100755 index d8343ba9..234529d9 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -4,11 +4,9 @@ """container.py - classes necessary to contain Terminal widgets""" import gobject -import gtk -from util import debug, err - -class Container(): +# pylint: disable-msg=R0921 +class Container(object): """Base class for Terminator Containers""" immutable = None @@ -32,15 +30,19 @@ class Container(): self.children = [] self.config = configobject - def register_signals(self, object): + def register_signals(self, widget): """Register gobject signals in a way that avoids multiple inheritance""" for signal in self.signals: gobject.signal_new(signal['name'], - object, + widget, signal['flags'], signal['return_type'], signal['param_types']) + def emit(self, signal): + """Emit a gobject signal""" + raise NotImplementedError('emit') + def get_offspring(self): """Return a list of child widgets, if any""" return(self.children) @@ -55,17 +57,15 @@ class Container(): def split_axis(self, widget, vertical=True): """Default axis splitter. This should be implemented by subclasses""" - err('split_axis called from base class. This is a bug') - return(False) + raise NotImplementedError('split_axis') def unsplit(self, widget, keep=False): """Default unsplitter. This should be implemented by subclasses""" - err('unsplit called from base class. This is a bug') - return(False) + raise NotImplementedError('unsplit') def remove(self, widget): """Remove a widget from the container""" - err('remove called from base class. This is a bug') + raise NotImplementedError('remove') def closeterm(self, widget): """Handle the closure of a terminal""" @@ -78,22 +78,9 @@ class Container(): self.emit('need_group_hoover') return(True) - def closegroupterms(self, widget): - """Handle the closure of a group of terminals""" - if self.state_zoomed != self.states_zoom['normal']: - self.unzoom(widget) - - all_closed = True - for term in self.term_list[:]: - if term._group == widget._group and not self.remove(term): - all_closed = False - - self.emit('need_group_hoover') - return(all_closed) - def resizeterm(self, widget, keyname): """Handle a keyboard event requesting a terminal resize""" - err('resizeterm called from base class. This is a bug') + raise NotImplementedError('resizeterm') def toggle_zoom(self, widget, fontscale = False): """Toggle the existing zoom state""" @@ -104,10 +91,14 @@ class Container(): def zoom(self, widget, fontscale = False): """Zoom a terminal""" - err('zoom called from base class. This is a bug') + raise NotImplementedError('zoom') def unzoom(self, widget): """Unzoom a terminal""" - err('unzoom called from base class. This is a bug') + raise NotImplementedError('unzoom') + +if __name__ == '__main__': + CONTAINER = Container() + CONTAINER.zoom() # vim: set expandtab ts=4 sw=4: From 1ab776bcb828be5ba391474def6f2cfd7a621bf9 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 9 Aug 2009 23:56:08 +0100 Subject: [PATCH 009/331] remove the unnecessary __main__ code --- terminatorlib/container.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 234529d9..107f9f7b 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -97,8 +97,4 @@ class Container(object): """Unzoom a terminal""" raise NotImplementedError('unzoom') -if __name__ == '__main__': - CONTAINER = Container() - CONTAINER.zoom() - # vim: set expandtab ts=4 sw=4: From 034e264fe5aa82a95913848f950b0614736301dc Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 10 Aug 2009 00:07:40 +0100 Subject: [PATCH 010/331] add some docstrings, fix the hide_window handling even more, and generally quiesce pylint --- terminatorlib/window.py | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) mode change 100644 => 100755 terminatorlib/window.py diff --git a/terminatorlib/window.py b/terminatorlib/window.py old mode 100644 new mode 100755 index d50bafdf..3bb7efb4 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -7,9 +7,7 @@ import pygtk pygtk.require('2.0') import gobject import gtk -import pango -from version import APP_NAME, APP_VERSION from util import debug, dbg, err from container import Container @@ -25,6 +23,7 @@ class Window(Container, gtk.Window): title = None isfullscreen = None + ismaximised = None hidebound = None hidefunc = None @@ -48,9 +47,13 @@ class Window(Container, gtk.Window): # Attempt to grab a global hotkey for hiding the window. # If we fail, we'll never hide the window, iconifying instead. - self.hidebound = bindkey.tomboy_keybinder_bind( - self.config['keybindings']['hide_window'], - self.on_hide_window) + try: + self.hidebound = bindkey.tomboy_keybinder_bind( + self.config['keybindings']['hide_window'], + self.on_hide_window) + except NameError: + pass + if not self.hidebound: dbg('Unable to bind hide_window key, another instance has it.') self.hidefunc = self.iconify @@ -69,45 +72,60 @@ class Window(Container, gtk.Window): self.set_iconified(self.config['hidden']) def on_key_press(self, window, event): + """Handle a keyboard event""" pass def on_delete_event(self, window, event, data=None): + """Handle a window close request""" pass def on_destroy_event(self, widget, data=None): + """Handle window descruction""" + pass + + def on_hide_window(self, data): + """Handle a request to hide/show the window""" pass def on_window_state_changed(self, window, event): + """Handle the state of the window changing""" self.isfullscreen = bool(event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN) self.ismaximised = bool(event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED) dbg('window state changed: fullscreen %s, maximised %s' % (self.isfullscreen, self.ismaximised)) + return(False) def set_maximised(self, value): + """Set the maximised state of the window from the supplied value""" if value == True: self.maximize() else: self.unmaximize() def set_fullscreen(self, value): + """Set the fullscreen state of the window from the supplied value""" if value == True: self.fullscreen() else: self.unfullscreen() def set_borderless(self, value): + """Set the state of the window border from the supplied value""" self.set_decorated (not value) def set_hidden(self, value): + """Set the visibility of the window from the supplied value""" pass def set_iconified(self, value): + """Set the minimised state of the window from the value""" pass def set_real_transparency(self, value): + """Enable RGBA if supported on the current screen""" screen = self.get_screen() if value: colormap = screen.get_rgba_colormap() @@ -117,7 +135,15 @@ class Window(Container, gtk.Window): if colormap: self.set_colormap(colormap) -CONFIG = {'fullscreen':False, 'maximised':False, 'borderless':False, 'enable_real_transparency':True, 'hidden':False} +# Temporary config object until that code is refactored +CONFIG = {'fullscreen':False, + 'maximised':False, + 'borderless':False, + 'enable_real_transparency':True, + 'hidden':False, + 'keybindings':{'hide_window': 'a', + } + } WINDOW = Window(CONFIG) WINDOW.show_all() From dee9745d99b9604deb691826621b660b2c8d6e9e Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 10 Aug 2009 00:10:08 +0100 Subject: [PATCH 011/331] improve pylint compliance of util --- terminatorlib/util.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/terminatorlib/util.py b/terminatorlib/util.py index 5031e750..576b1ad3 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -1,5 +1,5 @@ #!/usr/bin/python -# TerminatorConfig - layered config classes +# Terminator.util - misc utility functions # Copyright (C) 2006-2008 cmsj@tenshu.net # # This program is free software; you can redistribute it and/or modify @@ -14,17 +14,18 @@ # 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 +"""Terminator.util - misc utility functions""" import sys # set this to true to enable debugging output -debug = False +DEBUG = False def dbg (log = ""): - """Print a message if debugging is enabled""" - if debug: - print >> sys.stderr, log + """Print a message if debugging is enabled""" + if DEBUG: + print >> sys.stderr, log def err (log = ""): - """Print an error message""" - print >> sys.stderr, log + """Print an error message""" + print >> sys.stderr, log From 70173e69781f407e60e54057b2cb657c763a52b5 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 10 Aug 2009 00:11:31 +0100 Subject: [PATCH 012/331] remove unnecessary import and default to debugging for now --- terminatorlib/util.py | 2 +- terminatorlib/window.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/terminatorlib/util.py b/terminatorlib/util.py index 576b1ad3..6ff3521f 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -19,7 +19,7 @@ import sys # set this to true to enable debugging output -DEBUG = False +DEBUG = True def dbg (log = ""): """Print a message if debugging is enabled""" diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 3bb7efb4..3deb4424 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -8,7 +8,7 @@ pygtk.require('2.0') import gobject import gtk -from util import debug, dbg, err +from util import dbg, err from container import Container From 0f702e32b61ac0a5006b54c3310e6b021c63773d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 10 Aug 2009 23:04:39 +0100 Subject: [PATCH 013/331] Migrate WindowTitle class --- terminatorlib/window.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 3deb4424..b439fecf 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -135,6 +135,44 @@ class Window(Container, gtk.Window): if colormap: self.set_colormap(colormap) +class WindowTitle(object): + """Class to handle the setting of the window title""" + + window = None + text = None + forced = None + + def __init__(self, window): + """Class initialiser""" + self.window = window + self.forced = False + + def set_title(self, newtext): + """Set the title""" + if not self.forced: + self.text = newtext + self.update() + + def force_title(self, newtext): + """Force a specific title""" + if newtext: + self.set_title(newtext) + self.forced = True + else: + self.forced = False + + def update(self): + """Update the title automatically""" + title = None + + # FIXME: What the hell is this for?! + if self.forced: + title = self.text + else: + title = "%s" % self.text + + self.window.set_title(title) + # Temporary config object until that code is refactored CONFIG = {'fullscreen':False, 'maximised':False, @@ -146,6 +184,8 @@ CONFIG = {'fullscreen':False, } WINDOW = Window(CONFIG) +WINDOWTITLE = WindowTitle(WINDOW) +WINDOWTITLE.update() WINDOW.show_all() gtk.main() From 49510266ed4a3e11720c69fa4d8ba26d5b51066c Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 10 Aug 2009 23:09:49 +0100 Subject: [PATCH 014/331] Migrate window icon setting --- terminatorlib/window.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index b439fecf..736bb5dc 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -9,7 +9,7 @@ import gobject import gtk from util import dbg, err - +from version import APP_NAME from container import Container try: @@ -35,6 +35,8 @@ class Window(Container, gtk.Window): self.register_signals(Window) self.set_property('allow-shrink', True) + self.apply_icon() + self.register_callbacks() self.apply_config() @@ -71,6 +73,18 @@ class Window(Container, gtk.Window): else: self.set_iconified(self.config['hidden']) + def apply_icon(self): + """Set the window icon""" + icon_theme = gtk.IconTheme() + + try: + icon = icon_theme.load_icon(APP_NAME, 48, 0) + except NameError: + dbg('Unable to load 48px Terminator icon') + icon = self.render_icon(gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_BUTTON) + + self.set_icon(icon) + def on_key_press(self, window, event): """Handle a keyboard event""" pass From b3635d29128ee2c3a661c8af58ac2f5cdd052170 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 11 Aug 2009 00:15:31 +0100 Subject: [PATCH 015/331] Prepare for the new Terminator class --- terminatorlib/window.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 736bb5dc..a70053b4 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -11,6 +11,7 @@ import gtk from util import dbg, err from version import APP_NAME from container import Container +from newterminator import Terminator try: import deskbar.core.keybinder as bindkey @@ -21,6 +22,7 @@ except ImportError: class Window(Container, gtk.Window): """Class implementing a top-level Terminator window""" + terminator = None title = None isfullscreen = None ismaximised = None @@ -29,6 +31,8 @@ class Window(Container, gtk.Window): def __init__(self, configobject): """Class initialiser""" + self.terminator = Terminator() + Container.__init__(self, configobject) gtk.Window.__init__(self) gobject.type_register(Window) @@ -40,6 +44,8 @@ class Window(Container, gtk.Window): self.register_callbacks() self.apply_config() + self.default_setup() + def register_callbacks(self): """Connect the GTK+ signals we care about""" self.connect('key-press-event', self.on_key_press) @@ -85,6 +91,15 @@ class Window(Container, gtk.Window): self.set_icon(icon) + def default_setup(self): + """Set up the default child widget""" + terminal = self.terminator.new_terminal() + + self.add(terminal) + terminal.hide_titlebar() + self.show() + terminal.spawn_child() + def on_key_press(self, window, event): """Handle a keyboard event""" pass @@ -197,10 +212,4 @@ CONFIG = {'fullscreen':False, } } -WINDOW = Window(CONFIG) -WINDOWTITLE = WindowTitle(WINDOW) -WINDOWTITLE.update() -WINDOW.show_all() -gtk.main() - # vim: set expandtab ts=4 sw=4: From d81de4569c4c63ce61bd86f163eb8c80a7339c8e Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 11 Aug 2009 00:15:40 +0100 Subject: [PATCH 016/331] Initial import of new Terminator class --- terminatorlib/newterminator.py | 62 ++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100755 terminatorlib/newterminator.py diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py new file mode 100755 index 00000000..772d9fee --- /dev/null +++ b/terminatorlib/newterminator.py @@ -0,0 +1,62 @@ +#!/usr/bin/python +# Terminator by Chris Jones +# GPL v2 only +"""terminator.py - class for the master Terminator singleton""" + +from util import dbg, err +from version import APP_NAME, APP_VERSION + +_terminator = _Terminator() +def Terminator(): + """Return the instance""" + return(_terminator) + +class _Terminator(object): + """master object for the application""" + + window = None + windowtitle = None + terminals = None + groups = None + config = None + + def __init__(self): + """Class initialiser""" + + self.terminals = [] + self.groups = [] + + self.window = Window(self.config) + self.windowtitle = WindowTitle() + + def new_terminal(self): + """Create and register a new terminal widget""" + + terminal = Terminal() + self.terminals.append(terminal) + + def reconfigure_terminals(self): + """Tell all terminals to update their configuration""" + + for terminal in self.terminals: + terminal.reconfigure() + + def group_hoover(self): + """Clean out unused groups""" + + if self.config['autoclean_groups']: + todestroy = [] + for group in self.groups: + for terminal in self.terminals: + save = False + if terminal.group == group: + save = True + break; + + if not save: + todestroy.append(group) + + for group in todestroy: + self.groups.remove(group) + +# vim: set expandtab ts=4 sw=4: From 0953fca37b29550edab11da5994c5f9fa5436ba9 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 11 Aug 2009 00:20:00 +0100 Subject: [PATCH 017/331] move the instance creation to after definition, remove dependency on window.py --- terminatorlib/newterminator.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index 772d9fee..fa5703e1 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -6,11 +6,6 @@ from util import dbg, err from version import APP_NAME, APP_VERSION -_terminator = _Terminator() -def Terminator(): - """Return the instance""" - return(_terminator) - class _Terminator(object): """master object for the application""" @@ -26,9 +21,6 @@ class _Terminator(object): self.terminals = [] self.groups = [] - self.window = Window(self.config) - self.windowtitle = WindowTitle() - def new_terminal(self): """Create and register a new terminal widget""" @@ -59,4 +51,10 @@ class _Terminator(object): for group in todestroy: self.groups.remove(group) + +_terminator = _Terminator() +def Terminator(): + """Return the instance""" + return(_terminator) + # vim: set expandtab ts=4 sw=4: From eb99ed2c926a586ea948cc12b5722ef8e509a5cb Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 11 Aug 2009 00:22:55 +0100 Subject: [PATCH 018/331] remove unnecessary dependencies, appease pylint some more --- terminatorlib/newterminator.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index fa5703e1..f9bb1330 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -3,8 +3,7 @@ # GPL v2 only """terminator.py - class for the master Terminator singleton""" -from util import dbg, err -from version import APP_NAME, APP_VERSION +from terminal import Terminal class _Terminator(object): """master object for the application""" @@ -43,7 +42,7 @@ class _Terminator(object): save = False if terminal.group == group: save = True - break; + break if not save: todestroy.append(group) @@ -51,10 +50,9 @@ class _Terminator(object): for group in todestroy: self.groups.remove(group) - -_terminator = _Terminator() +TERMINATOR = _Terminator() def Terminator(): """Return the instance""" - return(_terminator) + return(TERMINATOR) # vim: set expandtab ts=4 sw=4: From 0dd4ec66bfaa68e8a9692248d908b9deb21484ca Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 11 Aug 2009 00:42:39 +0100 Subject: [PATCH 019/331] clear some migrated code from terminator.py --- terminatorlib/terminal.py | 34 +++++++++++++++++++++++++ terminatorlib/terminator.py | 51 +++++-------------------------------- 2 files changed, 41 insertions(+), 44 deletions(-) create mode 100755 terminatorlib/terminal.py diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py new file mode 100755 index 00000000..577aa106 --- /dev/null +++ b/terminatorlib/terminal.py @@ -0,0 +1,34 @@ +#!/usr/bin/python +# Terminator by Chris Jones +# GPL v2 only +"""terminal.py - classes necessary to provide Terminal widgets""" + +import sys + +import pygtk +pygtk.require('2.0') +import gobject +import gtk +import pango + +try: + import vte +except ImportError: + error = gtk.MessageDialog(None, + gtk.DIALOG_MODAL, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_OK, + 'You need to install python bindings for libvte') + error.run() + sys.exit(1) + +from terminator import Terminator + +class Terminal(gtk.VBox): + """Class implementing the VTE widget and its wrappings""" + + def __init__(self): + """Class initialiser""" + pass + +# vim: set expandtab ts=4 sw=4: diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index 33a1bb73..42493e49 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -22,53 +22,16 @@ import pygtk pygtk.require ("2.0") import gobject, gtk, pango -from terminatorlib.version import APP_NAME, APP_VERSION - -from terminatorlib import config +from version import APP_NAME, APP_VERSION +import config from util import dbg, err, debug -from terminatorlib.keybindings import TerminatorKeybindings -from terminatorlib.terminatorterm import TerminatorTerm -from terminatorlib.prefs_profile import ProfileEditor -from terminatorlib import translation +from keybindings import TerminatorKeybindings +from terminatorterm import TerminatorTerm +from prefs_profile import ProfileEditor +import translation -try: - import deskbar.core.keybinder as bindkey -except: - dbg (_("Unable to find python bindings for deskbar, "\ - "hide_window is not available.")) - pass - -class TerminatorWindowTitle: - _window = None - text = None - _forced = False - - def __init__ (self, window): - self._window = window - - def set_title (self, newtext): - if not self._forced: - self.text = newtext - self.update () - - def force_title (self, newtext): - if newtext: - self.set_title (newtext) - self._forced = True - else: - self._forced = False - - def update (self): - title = None - - if self._forced: - title = self.text - else: - title = "%s" % self.text - - self._window.set_title (title) - +# FIXME: Move to notebook.py class TerminatorNotebookTabLabel(gtk.HBox): _terminator = None _notebook = None From fe37448440fc7299c44d5c1690b9d38d90bf57a6 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 11 Aug 2009 23:19:06 +0100 Subject: [PATCH 020/331] switch from a singleton to a borg, and don't do circular imports --- terminatorlib/borg.py | 11 +++++++++++ terminatorlib/newterminator.py | 20 ++++++++++++-------- terminatorlib/terminal.py | 2 -- 3 files changed, 23 insertions(+), 10 deletions(-) create mode 100755 terminatorlib/borg.py diff --git a/terminatorlib/borg.py b/terminatorlib/borg.py new file mode 100755 index 00000000..622c9af6 --- /dev/null +++ b/terminatorlib/borg.py @@ -0,0 +1,11 @@ +#!/usr/bin/python +# Terminator by Chris Jones +# GPL v2 only +"""borg.py - We are the borg. Resistance is futile. + http://code.activestate.com/recipes/66531/""" + +class Borg: + __shared_state = {} + def __init__(self): + self.__dict__ = self.__shared_state + diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index f9bb1330..4287abd1 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -3,9 +3,10 @@ # GPL v2 only """terminator.py - class for the master Terminator singleton""" +from borg import Borg from terminal import Terminal -class _Terminator(object): +class Terminator(Borg): """master object for the application""" window = None @@ -17,8 +18,16 @@ class _Terminator(object): def __init__(self): """Class initialiser""" - self.terminals = [] - self.groups = [] + Borg.__init__(self) + self.prepare_attributes() + + def prepare_attributes(self): + """Initialise anything that isn't alread""" + + if not self.terminals: + self.terminals = [] + if not self.groups: + self.groups = [] def new_terminal(self): """Create and register a new terminal widget""" @@ -50,9 +59,4 @@ class _Terminator(object): for group in todestroy: self.groups.remove(group) -TERMINATOR = _Terminator() -def Terminator(): - """Return the instance""" - return(TERMINATOR) - # vim: set expandtab ts=4 sw=4: diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 577aa106..46aba4a4 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -22,8 +22,6 @@ except ImportError: error.run() sys.exit(1) -from terminator import Terminator - class Terminal(gtk.VBox): """Class implementing the VTE widget and its wrappings""" From c2891b26c7a36971747d860daf08c8e0b618e79d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 11 Aug 2009 23:23:34 +0100 Subject: [PATCH 021/331] appease pylint --- terminatorlib/borg.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/terminatorlib/borg.py b/terminatorlib/borg.py index 622c9af6..6b65cd53 100755 --- a/terminatorlib/borg.py +++ b/terminatorlib/borg.py @@ -4,8 +4,11 @@ """borg.py - We are the borg. Resistance is futile. http://code.activestate.com/recipes/66531/""" +# pylint: disable-msg=R0903 class Borg: + """Definition of a class that can never be duplicated""" __shared_state = {} def __init__(self): - self.__dict__ = self.__shared_state + self.__dict__ = self.__shared_state +# vim: set expandtab ts=4 sw=4: From 92fe7007ed9274fc72f2f92e128a049533a2355f Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 11 Aug 2009 23:26:18 +0100 Subject: [PATCH 022/331] add pylint script for laziness --- terminatorlib/pylint.sh | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100755 terminatorlib/pylint.sh diff --git a/terminatorlib/pylint.sh b/terminatorlib/pylint.sh new file mode 100755 index 00000000..4e126560 --- /dev/null +++ b/terminatorlib/pylint.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +for file in *.py; do + echo -n "$file: " + pylint $file 2>&1 | grep "^Your code has been rated" +done From 7547eaad4b3bee84753c9267fafd82320e7b6a05 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 11 Aug 2009 23:27:56 +0100 Subject: [PATCH 023/331] appease pylint and begin the terminal class --- terminatorlib/terminal.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 46aba4a4..d94ee9cf 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -14,19 +14,23 @@ import pango try: import vte except ImportError: - error = gtk.MessageDialog(None, + ERROR = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, 'You need to install python bindings for libvte') - error.run() + ERROR.run() sys.exit(1) class Terminal(gtk.VBox): """Class implementing the VTE widget and its wrappings""" + vte = None + def __init__(self): """Class initialiser""" - pass + gtk.VBox.__init__(self) + + self.vte = vte.Terminal() # vim: set expandtab ts=4 sw=4: From f110bca10348f6d0b7df2e7279da4366dcf1bc72 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 11 Aug 2009 23:36:37 +0100 Subject: [PATCH 024/331] migrate cwd getting function --- terminatorlib/cwd.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100755 terminatorlib/cwd.py diff --git a/terminatorlib/cwd.py b/terminatorlib/cwd.py new file mode 100755 index 00000000..b3d39a01 --- /dev/null +++ b/terminatorlib/cwd.py @@ -0,0 +1,34 @@ +#!/usr/bin/python +# Terminator by Chris Jones +# GPL v2 only +"""cwd.py - function necessary to get the cwd for a given pid on various OSes""" + +import platform +import os +from util import dbg + +def get_pidcwd(): + """Determine an appropriate cwd function for the OS we are running on""" + + func = lambda pid: None + system = platform.system() + + if system == 'Linux': + dbg('Using Linux get_pid_cwd') + func = lambda pid: os.path.realpath('/proc/%s/cwd' % pid) + elif system == 'FreeBSD': + try: + import freebsd + func = freebsd.get_process_cwd + dbg('Using FreeBSD get_pid_cwd') + except (OSError, NotImplementedError, ImportError): + dbg('FreeBSD version too old for get_pid_cwd') + elif system == 'SunOS': + dbg('Using SunOS get_pid_cwd') + func = lambda pid: os.path.realpath('/proc/%s/path/cwd' % pid) + else: + dbg('Unable to determine a get_pid_cwd for OS: %s' % system) + + return(func) + +# vim: set expandtab ts=4 sw=4: From 4150249f87a7089a18971146a611c8341e5ef902 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 11 Aug 2009 23:48:19 +0100 Subject: [PATCH 025/331] refactor some cwd code and start setting some vte attributes --- terminatorlib/cwd.py | 9 ++++++++- terminatorlib/terminal.py | 6 +++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/terminatorlib/cwd.py b/terminatorlib/cwd.py index b3d39a01..b4c87a9e 100755 --- a/terminatorlib/cwd.py +++ b/terminatorlib/cwd.py @@ -5,9 +5,16 @@ import platform import os +import pwd from util import dbg -def get_pidcwd(): +def get_default_cwd(): + """Determine a reasonable default cwd""" + cwd = os.getcwd() + if not os.path.exists(cwd) or not os.path.isdir(cwd): + cwd = pwd.getpwuid(os.getuid())[5] + +def get_pid_cwd(): """Determine an appropriate cwd function for the OS we are running on""" func = lambda pid: None diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index d94ee9cf..a4cfa91d 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -4,7 +4,6 @@ """terminal.py - classes necessary to provide Terminal widgets""" import sys - import pygtk pygtk.require('2.0') import gobject @@ -22,6 +21,8 @@ except ImportError: ERROR.run() sys.exit(1) +from cwd import get_pid_cwd, get_default_cwd + class Terminal(gtk.VBox): """Class implementing the VTE widget and its wrappings""" @@ -32,5 +33,8 @@ class Terminal(gtk.VBox): gtk.VBox.__init__(self) self.vte = vte.Terminal() + self.vte.set_size(80, 24) + self.vte._expose_data = None + self.vte.show() # vim: set expandtab ts=4 sw=4: From d00e2fe9ddad21c77e8e324a010dc48d86776d68 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 12:35:06 +0100 Subject: [PATCH 026/331] reformat slightly so we get a pylint 10 --- terminatorlib/translation.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/terminatorlib/translation.py b/terminatorlib/translation.py index 77477539..0e825a86 100644 --- a/terminatorlib/translation.py +++ b/terminatorlib/translation.py @@ -17,16 +17,19 @@ """Terminator by Chris Jones """ -from terminatorlib.version import APP_NAME +from version import APP_NAME +from util import dbg try: import gettext - gettext.install (APP_NAME) + gettext.install(APP_NAME) except ImportError: - print "Using fallback _()" + dbg("Using fallback _()") import __builtin__ + def dummytrans (text): - """A _ function for systems without gettext. Effectively a NOOP""" - return text + """A _ function for systems without gettext. Effectively a NOOP""" + return(text) + __builtin__.__dict__['_'] = dummytrans From 1f156248eee00545b60bda16f8a1c7f7212934e2 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 12:46:41 +0100 Subject: [PATCH 027/331] Refactor the config. For now it's defaults only --- terminatorlib/config.py | 408 ++-------------------------------------- 1 file changed, 18 insertions(+), 390 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 10b321ab..e9175c30 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -15,38 +15,12 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -"""TerminatorConfig by Chris Jones +"""Terminator by Chris Jones -The config scheme works in layers, with defaults at the base, -and a simple/flexible class which can be placed over the top -in multiple layers. This was written for Terminator, but -could be used generically. Its original use is to guarantee -default values for any config item, while allowing them to be -overridden by at least two other stores of configuration values. -Those being gconf and a plain config file. -In addition to the value, the default layer must also provide -the datatype (str, int, float and bool are currently supported). -values are found as attributes of the TerminatorConfig object. -Trying to read a value that doesn't exist will raise an -AttributeError. This is by design. If you want to look something -up, set a default for it first.""" +Classes relating to configuratoin""" -import os import platform -import sys -import re -import pwd -import gtk -import pango - -try: - import gconf -except ImportError: - gconf = None - -from terminatorlib import translation -from terminatorlib.util import dbg, err -from terminatorlib.configfile import ConfigFile, ParsedWithErrors +from borg import Borg DEFAULTS = { 'gt_dir' : '/apps/gnome-terminal', @@ -77,7 +51,12 @@ DEFAULTS = { 'scrollback_lines' : 500, 'focus' : 'click', 'exit_action' : 'close', - 'palette' : '#000000000000:#CDCD00000000:#0000CDCD0000:#CDCDCDCD0000:#30BF30BFA38E:#A53C212FA53C:#0000CDCDCDCD:#FAFAEBEBD7D7:#404040404040:#FFFF00000000:#0000FFFF0000:#FFFFFFFF0000:#00000000FFFF:#FFFF0000FFFF:#0000FFFFFFFF:#FFFFFFFFFFFF', + 'palette' : '#000000000000:#CDCD00000000:#0000CDCD0000:\ + #CDCDCDCD0000:#30BF30BFA38E:#A53C212FA53C:\ + #0000CDCDCDCD:#FAFAEBEBD7D7:#404040404040:\ + #FFFF00000000:#0000FFFF0000:#FFFFFFFF0000:\ + #00000000FFFF:#FFFF0000FFFF:#0000FFFFFFFF:\ + #FFFFFFFFFFFF', 'word_chars' : '-A-Za-z0-9,./?%&#:_', 'mouse_autohide' : True, 'update_records' : True, @@ -166,367 +145,16 @@ DEFAULTS = { } } +class Config(Borg, dict): + """Class to provide access to our user configuration""" -class TerminatorConfig(object): - """This class is used as the base point of the config system""" - callback = None - sources = None - _keys = None + def __init__(self): + """Class initialiser""" - def __init__ (self, sources): - self.sources = [] + Borg.__init__(self) + dict.__init__(self) - for source in sources: - if isinstance(source, TerminatorConfValuestore): - self.sources.append (source) - - # We always add a default valuestore last so no valid config item ever - # goes unset - source = TerminatorConfValuestoreDefault () - self.sources.append (source) - - def _merge_keybindings(self): - if self._keys: - return self._keys - - self._keys = {} - for source in reversed(self.sources): - try: - val = source['keybindings'] - self._keys.update(val) - except: - pass - return self._keys - - keybindings = property(_merge_keybindings) - - def __getattr__ (self, keyname): - for source in self.sources: - dbg ("TConfig: Looking for: '%s' in '%s'"%(keyname, source.type)) - try: - val = source[keyname] - dbg (" TConfig: got: '%s' from a '%s'"%(val, source.type)) - return (val) - except KeyError: - pass - - dbg (" TConfig: Out of sources") - raise (AttributeError) - -class TerminatorConfValuestore(object): - type = "Base" - values = None - reconfigure_callback = None - - def __init__ (self): - self.values = {} - - # Our settings - def __getitem__ (self, keyname): - if self.values.has_key (keyname): - value = self.values[keyname] - dbg ("Returning '%s':'%s'"%(keyname, value)) - return value - else: - dbg ("Failed to find '%s'"%keyname) - raise (KeyError) - -class TerminatorConfValuestoreDefault (TerminatorConfValuestore): - def __init__ (self): - TerminatorConfValuestore.__init__ (self) - self.type = "Default" - self.values = DEFAULTS - -class TerminatorConfValuestoreRC (TerminatorConfValuestore): - rcfilename = "" - type = "RCFile" - def __init__ (self): - TerminatorConfValuestore.__init__ (self) - try: - directory = os.environ['XDG_CONFIG_HOME'] - except KeyError: - dbg(" VS_RCFile: XDG_CONFIG_HOME not found. defaulting to ~/.config") - directory = os.path.join (os.path.expanduser("~"), ".config") - self.rcfilename = os.path.join(directory, "terminator/config") - dbg(" VS_RCFile: config file located at %s" % self.rcfilename) - self.call_parser(True) - - def set_reconfigure_callback (self, function): - dbg (" VS_RCFile: setting callback to: %s"%function) - self.reconfigure_callback = function - return (True) - - def call_parser (self, is_init = False): - dbg (" VS_RCFile: parsing config file") - try: - ini = ConfigFile(self.rcfilename, self._rc_set_callback()) - ini.parse() - except IOError, ex: - dbg (" VS_RCFile: unable to open %s (%r)" % (self.rcfilename, ex)) - except ParsedWithErrors, ex: - # We don't really want to produce an error dialog every run - if not is_init: - pass - msg = _("""Configuration error - -Errors were encountered while parsing terminator_config(5) file: - - %s - -%d line(s) have been ignored.""") % (self.rcfilename, len(ex.errors)) - - dialog = gtk.Dialog(_("Configuration error"), None, gtk.DIALOG_MODAL, - (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) - dialog.set_has_separator(False) - dialog.set_resizable(False) - - image = gtk.image_new_from_stock(gtk.STOCK_DIALOG_WARNING, - gtk.ICON_SIZE_DIALOG) - image.set_alignment (0.5, 0) - dmsg = gtk.Label(msg) - dmsg.set_use_markup(True) - dmsg.set_alignment(0, 0.5) - - textbuff = gtk.TextBuffer() - textbuff.set_text("\n".join(map(lambda ex: str(ex), ex.errors))) - textview = gtk.TextView(textbuff) - textview.set_editable(False) - - textview.modify_font(pango.FontDescription(DEFAULTS['font'])) - textscroll = gtk.ScrolledWindow() - textscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - textscroll.add(textview) - # This should be scaled with the size of the text and font - textscroll.set_size_request(600, 200) - - root = gtk.VBox() - root.pack_start(dmsg, padding = 6) - root.pack_start(textscroll, padding = 6) - - box = gtk.HBox() - box.pack_start (image, False, False, 6) - box.pack_start (root, False, False, 6) - - vbox = dialog.get_content_area() - vbox.pack_start (box, False, False, 12) - dialog.show_all() - - dialog.run() - dialog.destroy() - - dbg("ConfigFile settings are: %r" % self.values) - - def _rc_set_callback(self): - def callback(sections, key, value): - dbg("Setting: section=%r with %r => %r" % (sections, key, value)) - section = None - if len(sections) > 0: - section = sections[0] - if section is None: - # handle some deprecated configs - if key == 'silent_bell': - err ("silent_bell config option is deprecated, for the new bell related config options, see: man terminator_config") - if value: - self.values['audible_bell'] = False - else: - self.values['audible_bell'] = True - key = 'visible_bell' - - if not DEFAULTS.has_key (key): - raise ValueError("Unknown configuration option %r" % key) - deftype = DEFAULTS[key].__class__.__name__ - if key.endswith('_color'): - try: - gtk.gdk.color_parse(value) - self.values[key] = value - except ValueError: - raise ValueError(_("Setting %r value %r not a valid colour; ignoring") % (key, value)) - elif key == 'tab_position': - if value.lower() in ('top', 'left', 'bottom', 'right'): - self.values[key] = value.lower() - else: - raise ValueError(_("%s must be one of: top, left, right, bottom") % key) - elif deftype == 'bool': - if value.lower () in ('true', 'yes', 'on'): - self.values[key] = True - elif value.lower () in ('false', 'no', 'off'): - self.values[key] = False - else: - raise ValueError(_("Boolean setting %s expecting one of: yes, no, true, false, on, off") % key) - elif deftype == 'int': - self.values[key] = int (value) - elif deftype == 'float': - self.values[key] = float (value) - elif deftype == 'list': - raise ValueError(_("Reading list values from terminator_config(5) is not currently supported")) - elif deftype == 'dict': - if type(value) != dict: - raise ValueError(_("Setting %r should be a section name") % key) - self.values[key] = value - else: - self.values[key] = value - - dbg (" VS_RCFile: Set value %r to %r" % (key, self.values[key])) - elif section == 'keybindings': - self.values.setdefault(section, {}) - if not DEFAULTS[section].has_key(key): - raise ValueError("Keybinding name %r is unknown" % key) - else: - self.values[section][key] = value - else: - raise ValueError("Section name %r is unknown" % section) - return callback - -class TerminatorConfValuestoreGConf (TerminatorConfValuestore): - profile = "" - client = None - cache = None - notifies = None - - def __init__ (self, profileName = None): - TerminatorConfValuestore.__init__ (self) - self.type = "GConf" - self.inactive = False - self.cache = {} - self.notifies = {} - - import gconf - - self.client = gconf.client_get_default () - - # Grab a couple of values from base class to avoid recursing with our __getattr__ - self._gt_dir = DEFAULTS['gt_dir'] - self._profile_dir = DEFAULTS['profile_dir'] - - dbg ('VSGConf: Profile bet on is: "%s"'%profileName) - profiles = self.client.get_list (self._gt_dir + '/global/profile_list','string') - dbg ('VSGConf: Found profiles: "%s"'%profiles) - - dbg ('VSGConf: Profile requested is: "%s"'%profileName) - if not profileName: - profile = self.client.get_string (self._gt_dir + '/global/default_profile') - else: - profile = profileName - # In newer gnome-terminal, the profile keys are named Profile0/1 etc. - # We have to match using visible_name instead - for p in profiles: - profileName2 = self.client.get_string ( - self._profile_dir + '/' + p + '/visible_name') - if profileName == profileName2: - profile = p - - #need to handle the list of Gconf.value - if profile in profiles: - dbg (" VSGConf: Found profile '%s' in profile_list"%profile) - self.profile = '%s/%s' % (self._profile_dir, profile) - elif "Default" in profiles: - dbg (" VSGConf: profile '%s' not found, but 'Default' exists" % profile) - self.profile = '%s/%s'%(self._profile_dir, "Default") - else: - # We're a bit stuck, there is no profile in the list - # FIXME: Find a better way to handle this than setting a non-profile - dbg ("VSGConf: No profile found, marking inactive") - self.inactive = True - return - - #set up the active encoding list - self.active_encodings = self.client.get_list (self._gt_dir + '/global/active_encodings', 'string') - - self.client.add_dir (self.profile, gconf.CLIENT_PRELOAD_RECURSIVE) - if self.on_gconf_notify: - self.client.notify_add (self.profile, self.on_gconf_notify) - - self.client.add_dir ('/apps/metacity/general', gconf.CLIENT_PRELOAD_RECURSIVE) - self.client.notify_add ('/apps/metacity/general/focus_mode', self.on_gconf_notify) - self.client.add_dir ('/desktop/gnome/interface', gconf.CLIENT_PRELOAD_RECURSIVE) - self.client.notify_add ('/desktop/gnome/interface/monospace_font_name', self.on_gconf_notify) - # FIXME: Do we need to watch more non-profile stuff here? - - def set_reconfigure_callback (self, function): - dbg (" VSConf: setting callback to: %s"%function) - self.reconfigure_callback = function - return (True) - - def on_gconf_notify (self, client, cnxn_id, entry, what): - dbg (" VSGConf: invalidating cache") - self.cache = {} - dbg (" VSGConf: gconf changed, may run a callback. %s, %s"%(entry.key, entry.value)) - if entry.key[-12:] == 'visible_name': - dbg (" VSGConf: only a visible_name change, ignoring") - return False - if self.reconfigure_callback: - dbg (" VSGConf: callback is: %s"%self.reconfigure_callback) - self.reconfigure_callback () - - def __getitem__ (self, key = ""): - if self.inactive: - raise KeyError - - if self.cache.has_key (key): - dbg (" VSGConf: returning cached value: %s"%self.cache[key]) - return (self.cache[key]) - - ret = None - value = None - - dbg (' VSGConf: preparing: %s/%s'%(self.profile, key)) - - # FIXME: Ugly special cases we should look to fix in some other way. - if key == 'font' and self['use_system_font']: - value = self.client.get ('/desktop/gnome/interface/monospace_font_name') - elif key == 'focus': - value = self.client.get ('/apps/metacity/general/focus_mode') - elif key == 'http_proxy': - if self.client.get_bool ('/system/http_proxy/use_http_proxy'): - dbg ('HACK: Mangling http_proxy') - - if self.client.get_bool ('/system/http_proxy/use_authentication'): - dbg ('HACK: Using proxy authentication') - value = 'http://%s:%s@%s:%s/' % ( - self.client.get_string ('/system/http_proxy/authentication_user'), - self.client.get_string ('/system/http_proxy/authentication_password'), - self.client.get_string ('/system/http_proxy/host'), - self.client.get_int ('/system/http_proxy/port')) - else: - dbg ('HACK: Not using proxy authentication') - value = 'http://%s:%s/' % ( - self.client.get_string ('/system/http_proxy/host'), - self.client.get_int ('/system/http_proxy/port')) - elif key == 'cursor_blink': - tmp = self.client.get_string('%s/cursor_blink_mode' % self.profile) - if tmp in ['on', 'off'] and self.notifies.has_key ('cursor_blink'): - self.client.notify_remove (self.notifies['cursor_blink']) - del (self.notifies['cursor_blink']) - if tmp == 'on': - value = True - elif tmp == 'off': - value = False - elif tmp == 'system': - value = self.client.get_bool ('/desktop/gnome/interface/cursor_blink') - self.notifies['cursor_blink'] = self.client.notify_add ('/desktop/gnome/interface/cursor_blink', self.on_gconf_notify) - else: - value = self.client.get ('%s/%s'%(self.profile, key)) - else: - value = self.client.get ('%s/%s'%(self.profile, key)) - - if value != None: - from types import StringType, BooleanType - if type(value) in [StringType, BooleanType]: - ret = value - else: - funcname = "get_" + DEFAULTS[key].__class__.__name__ - dbg (' GConf: picked function: %s'%funcname) - # Special case for str - if funcname == "get_str": - funcname = "get_string" - # Special case for strlist - if funcname == "get_strlist": - funcname = "get_list" - typefunc = getattr (value, funcname) - ret = typefunc () - - self.cache[key] = ret - return (ret) - else: - raise (KeyError) + def __getitem__(self, key): + """Look up a configuration item""" + return(DEFAULTS[key]) From ad372bbdcdf148c9041a02779ebf9a7ab797e246 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 12:52:06 +0100 Subject: [PATCH 028/331] Make a generic graphical error function --- terminatorlib/util.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/terminatorlib/util.py b/terminatorlib/util.py index 6ff3521f..eb08b4ab 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -21,11 +21,21 @@ import sys # set this to true to enable debugging output DEBUG = True -def dbg (log = ""): +def dbg(log = ""): """Print a message if debugging is enabled""" if DEBUG: print >> sys.stderr, log -def err (log = ""): +def err(log = ""): """Print an error message""" print >> sys.stderr, log + +def gerr(message = None): + """Display a graphical error. This should only be used for serious + errors as it will halt execution""" + + import gtk + dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message) + dialog.run() + From 28a5b963f0a5b8a69efba52d19b8f82ae2f17832 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 12:52:30 +0100 Subject: [PATCH 029/331] Remove static config object --- terminatorlib/window.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index a70053b4..b0d3a323 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -202,14 +202,4 @@ class WindowTitle(object): self.window.set_title(title) -# Temporary config object until that code is refactored -CONFIG = {'fullscreen':False, - 'maximised':False, - 'borderless':False, - 'enable_real_transparency':True, - 'hidden':False, - 'keybindings':{'hide_window': 'a', - } - } - # vim: set expandtab ts=4 sw=4: From 1e12ece0ea2bffa7afc148779ae99f38064164be Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 12:55:37 +0100 Subject: [PATCH 030/331] tidy up the imports, move the graphical error to a generic gerr() function in util.py and add a function for injecting URL regexps --- terminatorlib/terminal.py | 70 ++++++++++++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 8 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index a4cfa91d..a1f15fb0 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -10,31 +10,85 @@ import gobject import gtk import pango +from cwd import get_pid_cwd, get_default_cwd +from util import dbg, err, gerr +from config import Config + try: import vte except ImportError: - ERROR = gtk.MessageDialog(None, - gtk.DIALOG_MODAL, - gtk.MESSAGE_ERROR, - gtk.BUTTONS_OK, - 'You need to install python bindings for libvte') - ERROR.run() + gerr('You need to install python bindings for libvte') sys.exit(1) -from cwd import get_pid_cwd, get_default_cwd - class Terminal(gtk.VBox): """Class implementing the VTE widget and its wrappings""" vte = None + matches = None + config = None def __init__(self): """Class initialiser""" gtk.VBox.__init__(self) + self.matches = {} + + self.config = Config() self.vte = vte.Terminal() self.vte.set_size(80, 24) self.vte._expose_data = None self.vte.show() + self.update_url_matches(self.config['try_posix_regexp']) + + def update_url_matches(self, posix = True): + """Update the regexps used to match URLs""" + userchars = "-A-Za-z0-9" + passchars = "-A-Za-z0-9,?;.:/!%$^*&~\"#'" + hostchars = "-A-Za-z0-9" + pathchars = "-A-Za-z0-9_$.+!*(),;:@&=?/~#%'\"" + schemes = "(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:)" + user = "[" + userchars + "]+(:[" + passchars + "]+)?" + urlpath = "/[" + pathchars + "]*[^]'.}>) \t\r\n,\\\"]" + + if posix: + dbg ('update_url_matches: Trying POSIX URL regexps. Set \ + try_posix_regexp = False in config to only try GNU \ + if you get (harmless) VTE warnings.') + lboundry = "[[:<:]]" + rboundry = "[[:>:]]" + else: # GNU + dbg ('update_url_matches: Trying GNU URL regexps. Set \ + try_posix_regexp = True in config if URLs are not \ + detected.') + lboundry = "\\<" + rboundry = "\\>" + + self.matches['full_uri'] = self._vte.match_add(lboundry + schemes + + "//(" + user + "@)?[" + hostchars +".]+(:[0-9]+)?(" + + urlpath + ")?" + rboundry + "/?") + + if self.matches['full_uri'] == -1: + if posix: + err ('update_url_matches: POSIX match failed, trying GNU') + self.update_url_matches(posix = False) + else: + err ('update_url_matches: Failed adding URL match patterns') + else: + self.matches['voip'] = self._vte.match_add(lboundry + + '(callto:|h323:|sip:)' + "[" + userchars + "+][" + + userchars + ".]*(:[0-9]+)?@?[" + pathchars + "]+" + + rboundry) + self.matches['addr_only'] = self._vte.match_add (lboundry + + "(www|ftp)[" + hostchars + "]*\.[" + hostchars + + ".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?") + self.matches['email'] = self._vte.match_add (lboundry + + "(mailto:)?[a-zA-Z0-9][a-zA-Z0-9.+-]*@[a-zA-Z0-9][a-zA-Z0-9-]*\.[a-zA-Z0-9][a-zA-Z0-9-]+[.a-zA-Z0-9-]*" + rboundry) + self.matches['nntp'] = self._vte.match_add (lboundry + + '''news:[-A-Z\^_a-z{|}~!"#$%&'()*+,./0-9;:=?`]+@[-A-Za-z0-9.]+(:[0-9]+)?''' + rboundry) + # if the url looks like a Launchpad changelog closure entry + # LP: #92953 - make it a url to http://bugs.launchpad.net + self.matches['launchpad'] = self._vte.match_add ( + '\\bLP:? #?[0-9]+\\b') + # vim: set expandtab ts=4 sw=4: From 1cb1f166cf7d871fd6d8cf5b3454694e77edd1de Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 12:55:52 +0100 Subject: [PATCH 031/331] fix a typo --- terminatorlib/newterminator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index 4287abd1..d301932d 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -22,7 +22,7 @@ class Terminator(Borg): self.prepare_attributes() def prepare_attributes(self): - """Initialise anything that isn't alread""" + """Initialise anything that isn't already""" if not self.terminals: self.terminals = [] From 65767693df16c5d14bbc5bb0123fbaf1ebd99349 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 12:59:06 +0100 Subject: [PATCH 032/331] It always helps to return the thing you're called for --- terminatorlib/cwd.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/terminatorlib/cwd.py b/terminatorlib/cwd.py index b4c87a9e..703a7732 100755 --- a/terminatorlib/cwd.py +++ b/terminatorlib/cwd.py @@ -13,6 +13,8 @@ def get_default_cwd(): cwd = os.getcwd() if not os.path.exists(cwd) or not os.path.isdir(cwd): cwd = pwd.getpwuid(os.getuid())[5] + + return(cwd) def get_pid_cwd(): """Determine an appropriate cwd function for the OS we are running on""" From d6d3192d82bc3943ee684fdccf153b03a1998c3d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 13:43:18 +0100 Subject: [PATCH 033/331] refactoring out of the search bar into its own class --- terminatorlib/searchbar.py | 103 +++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100755 terminatorlib/searchbar.py diff --git a/terminatorlib/searchbar.py b/terminatorlib/searchbar.py new file mode 100755 index 00000000..4ebfe05b --- /dev/null +++ b/terminatorlib/searchbar.py @@ -0,0 +1,103 @@ +#!/usr/bin/python +# Terminator by Chris Jones +# GPL v2 only +"""searchbar.py - classes necessary to provide a terminal search bar""" + +import gtk +import gobject + +from translation import _ + +class Searchbar(gtk.HBox): + """Class implementing the Searchbar widget""" + + __gsignals__ = { + 'key-press-event': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + ()), + 'do-search': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'next-search': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'end-search': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + } + + entry = None + reslabel = None + next = None + + def __init__(self): + """Class initialiser""" + gtk.HBox.__init__(self) + self.__gobject__init() + + # Search text + self.entry = gtk.Entry() + self.entry.set_activates_default(True) + self.entry.show() + self.entry.connect('activate', self.do_search) + self.entry.connect('key-press-event', self.search_keypress) + + # Label + label = gtk.Label(_('Search:')) + label.show() + + # Result label + self.reslabel = gtk.Label('') + self.reslabel.show() + + # Close Button + close = gtk.Button() + close.set_relief(gtk.RELIEF_NONE) + close.set_focus_on_click(False) + icon = gtk.Image() + icon.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) + close.add(icon) + close.set_name('terminator-search-close-button') + if hasattr(close, 'set_tooltip_text'): + close.set_tooltip_text(_('Close Search bar')) + close.connect('clicked', self.end_search) + close.show_all() + + # Next Button + self.next = gtk.Button(_('Next')) + self.next.connect('clicked', self.next_search) + + self.pack_start(label, False) + self.pack_start(self.entry) + self.pack_start(self.reslabel, False) + self.pack_start(self.next, False, False) + self.pack_end(close, False, False) + + self.show() + + def search_keypress(self, widget, event): + """Trap and re-emit the key-press-event signal""" + self.emit('key-press-event', widget, event) + + def do_search(self, widget): + """Trap and re-emit the clicked signal""" + self.emit('do-search', widget) + + def next_search(self, widget): + """Trap and re-emit the next-search signal""" + self.emit('next-search', widget) + + def end_search(self, widget): + """Trap and re-emit the end-search signal""" + self.emit('end-search', widget) + + def get_search_term(self): + """Return the currently set search term""" + return(self.entry.get_text()) + + def set_search_label(self, string = ''): + """Set the search label""" + self.reslabel.set_text(string) + + def hide_next(self): + """Hide the Next button""" + self.next.hide() + + def show_next(self): + """Show the Next button""" + self.next.show() + +gobject.type_register(Searchbar) From a5cccbe7574be518ebf118a6a31b583ff2b8857e Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 13:43:39 +0100 Subject: [PATCH 034/331] refactor this to not use gettext's __builtin__ hack --- terminatorlib/translation.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/terminatorlib/translation.py b/terminatorlib/translation.py index 0e825a86..62b6a7f0 100644 --- a/terminatorlib/translation.py +++ b/terminatorlib/translation.py @@ -20,16 +20,18 @@ from version import APP_NAME from util import dbg +_ = None + try: import gettext - gettext.install(APP_NAME) + gettext.textdomain(APP_NAME) + _ = gettext.gettext except ImportError: dbg("Using fallback _()") - import __builtin__ def dummytrans (text): """A _ function for systems without gettext. Effectively a NOOP""" return(text) - __builtin__.__dict__['_'] = dummytrans + _ = dummytrans From 1fdd357058f351bbf2806e900300fbd28032f920 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 13:44:16 +0100 Subject: [PATCH 035/331] switch to new style translation import --- terminatorlib/encoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/encoding.py b/terminatorlib/encoding.py index e205497b..403ac8b8 100644 --- a/terminatorlib/encoding.py +++ b/terminatorlib/encoding.py @@ -23,7 +23,7 @@ This list is taken from gnome-terminal's src/terminal-encoding.c and src/encoding.c """ -from terminatorlib import translation +from translation import _ class TerminatorEncoding: """Class to store encoding details""" From 53f33109b42db51d22d0900561fba62f6af4c984 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 13:44:41 +0100 Subject: [PATCH 036/331] start fleshing out Terminal --- terminatorlib/terminal.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index a1f15fb0..456fe26f 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -13,6 +13,8 @@ import pango from cwd import get_pid_cwd, get_default_cwd from util import dbg, err, gerr from config import Config +from titlebar import Titlebar +from searchbox import Searchbox try: import vte @@ -24,8 +26,12 @@ class Terminal(gtk.VBox): """Class implementing the VTE widget and its wrappings""" vte = None + titlebar = None + searchbar = None + matches = None config = None + default_encoding = None def __init__(self): """Class initialiser""" @@ -38,9 +44,17 @@ class Terminal(gtk.VBox): self.vte.set_size(80, 24) self.vte._expose_data = None self.vte.show() - + self.default_encoding = self.vte.get_encoding() self.update_url_matches(self.config['try_posix_regexp']) + self.titlebar = Titlebar() + self.searchbar = Searchbar() + + self.show() + self.pack_start(self.titlebar, False) + self.pack_start(self.terminalbox) + self.pack_end(self.searchbar) + def update_url_matches(self, posix = True): """Update the regexps used to match URLs""" userchars = "-A-Za-z0-9" From d538b47a50419c8866b0aa8ee20e3d10bb6950ea Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 13:45:57 +0100 Subject: [PATCH 037/331] initial import of a titlebar class --- terminatorlib/titlebar.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100755 terminatorlib/titlebar.py diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py new file mode 100755 index 00000000..224d5800 --- /dev/null +++ b/terminatorlib/titlebar.py @@ -0,0 +1,21 @@ +#!/usr/bin/python +# Terminator by Chris Jones +# GPL v2 only +"""titlebar.py - classes necessary to provide a terminal title bar""" + +import gtk +import gobject + +from translation import _ + +class Titlebar(gtk.EventBox): + """Class implementing the Titlebar widget""" + + def __init__(self): + """Class initialiser""" + gtk.EventBox.__init__(self) + self.__gobject__init() + + self.show() + +gobject.type_register(Titlebar) From 5f339da4e138409bb1f318c611564fe6258a4212 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 13:47:46 +0100 Subject: [PATCH 038/331] Use the right class name --- terminatorlib/terminal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 456fe26f..d17097ed 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -14,7 +14,7 @@ from cwd import get_pid_cwd, get_default_cwd from util import dbg, err, gerr from config import Config from titlebar import Titlebar -from searchbox import Searchbox +from searchbar import Searchbar try: import vte From f0260456c9dc241404245be2bac981c838afa614 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 13:48:04 +0100 Subject: [PATCH 039/331] we don't need to re-emit the search keypress, all it does is conditionally emit end-search anyway --- terminatorlib/searchbar.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/terminatorlib/searchbar.py b/terminatorlib/searchbar.py index 4ebfe05b..acf563b9 100755 --- a/terminatorlib/searchbar.py +++ b/terminatorlib/searchbar.py @@ -12,8 +12,6 @@ class Searchbar(gtk.HBox): """Class implementing the Searchbar widget""" __gsignals__ = { - 'key-press-event': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, - ()), 'do-search': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'next-search': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'end-search': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), @@ -69,8 +67,10 @@ class Searchbar(gtk.HBox): self.show() def search_keypress(self, widget, event): - """Trap and re-emit the key-press-event signal""" - self.emit('key-press-event', widget, event) + """Handle keypress events""" + key = gtk.gdk.keyval_name(event.keyval) + if key == 'Escape': + self.end_search() def do_search(self, widget): """Trap and re-emit the clicked signal""" From 089b708e2c38d0ba617f66e0da7bb84f861e63fe Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 13:51:09 +0100 Subject: [PATCH 040/331] shut pylint up --- terminatorlib/searchbar.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/terminatorlib/searchbar.py b/terminatorlib/searchbar.py index acf563b9..1a6d3b59 100755 --- a/terminatorlib/searchbar.py +++ b/terminatorlib/searchbar.py @@ -8,6 +8,7 @@ import gobject from translation import _ +# pylint: disable-msg=R0904 class Searchbar(gtk.HBox): """Class implementing the Searchbar widget""" @@ -66,6 +67,7 @@ class Searchbar(gtk.HBox): self.show() + # pylint: disable-msg=W0613 def search_keypress(self, widget, event): """Handle keypress events""" key = gtk.gdk.keyval_name(event.keyval) From 0b90e29b1664a78bf529b6716e154e8fd455ea01 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 13:52:02 +0100 Subject: [PATCH 041/331] remove unused import and disable a pointless pylint check --- terminatorlib/titlebar.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index 224d5800..427a5c1d 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -6,8 +6,7 @@ import gtk import gobject -from translation import _ - +# pylint: disable-msg=R0904 class Titlebar(gtk.EventBox): """Class implementing the Titlebar widget""" From d6ac973f54570d69b8e0a5470687c785c913a3d2 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 13:54:46 +0100 Subject: [PATCH 042/331] remove some currently unused imports and split up some overly long lines --- terminatorlib/terminal.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index d17097ed..f1281b88 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -6,11 +6,8 @@ import sys import pygtk pygtk.require('2.0') -import gobject import gtk -import pango -from cwd import get_pid_cwd, get_default_cwd from util import dbg, err, gerr from config import Config from titlebar import Titlebar @@ -22,6 +19,7 @@ except ImportError: gerr('You need to install python bindings for libvte') sys.exit(1) +# pylint: disable-msg=R0904 class Terminal(gtk.VBox): """Class implementing the VTE widget and its wrappings""" @@ -97,9 +95,12 @@ class Terminal(gtk.VBox): "(www|ftp)[" + hostchars + "]*\.[" + hostchars + ".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?") self.matches['email'] = self._vte.match_add (lboundry + - "(mailto:)?[a-zA-Z0-9][a-zA-Z0-9.+-]*@[a-zA-Z0-9][a-zA-Z0-9-]*\.[a-zA-Z0-9][a-zA-Z0-9-]+[.a-zA-Z0-9-]*" + rboundry) + "(mailto:)?[a-zA-Z0-9][a-zA-Z0-9.+-]*@[a-zA-Z0-9]\ + [a-zA-Z0-9-]*\.[a-zA-Z0-9][a-zA-Z0-9-]+\ + [.a-zA-Z0-9-]*" + rboundry) self.matches['nntp'] = self._vte.match_add (lboundry + - '''news:[-A-Z\^_a-z{|}~!"#$%&'()*+,./0-9;:=?`]+@[-A-Za-z0-9.]+(:[0-9]+)?''' + rboundry) + '''news:[-A-Z\^_a-z{|}~!"#$%&'()*+,./0-9;:=?`]+@\ + [-A-Za-z0-9.]+(:[0-9]+)?''' + rboundry) # if the url looks like a Launchpad changelog closure entry # LP: #92953 - make it a url to http://bugs.launchpad.net self.matches['launchpad'] = self._vte.match_add ( From 196dde427b17648129c1c34046ba42655a9b012f Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 13:57:35 +0100 Subject: [PATCH 043/331] improve pylint score by disabling tests. winrar \o/ --- terminatorlib/window.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index b0d3a323..568f4556 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -19,6 +19,7 @@ except ImportError: err('Unable to find python bindings for deskbar, "hide_window" is not' \ 'available.') +# pylint: disable-msg=R0904 class Window(Container, gtk.Window): """Class implementing a top-level Terminator window""" @@ -116,6 +117,7 @@ class Window(Container, gtk.Window): """Handle a request to hide/show the window""" pass + # pylint: disable-msg=W0613 def on_window_state_changed(self, window, event): """Handle the state of the window changing""" self.isfullscreen = bool(event.new_window_state & From 4d7d734587a0b3ba2e20a831d164658513d33f28 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 19 Aug 2009 01:04:37 +0100 Subject: [PATCH 044/331] Simple bootstrapper, temporary during epic refactor --- terminatorlib/test.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100755 terminatorlib/test.py diff --git a/terminatorlib/test.py b/terminatorlib/test.py new file mode 100755 index 00000000..71e38032 --- /dev/null +++ b/terminatorlib/test.py @@ -0,0 +1,15 @@ +#!/usr/bin/python + +import gtk + +from newterminator import Terminator +from window import Window + +window = Window() +foo = Terminator() +term = foo.new_terminal() + +window.add(term) +window.show() + +gtk.main() From 96114aa86382b4305d7d2950d082150ae6398a6e Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 19 Aug 2009 01:04:53 +0100 Subject: [PATCH 045/331] now config is borg we don't need to pass handles around --- terminatorlib/container.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 107f9f7b..979f06a0 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -5,6 +5,8 @@ import gobject +from config import Config + # pylint: disable-msg=R0921 class Container(object): """Base class for Terminator Containers""" @@ -25,10 +27,10 @@ class Container(object): } ] - def __init__(self, configobject): + def __init__(self): """Class initialiser""" self.children = [] - self.config = configobject + self.config = Config() def register_signals(self, widget): """Register gobject signals in a way that avoids multiple inheritance""" From ac7769b55620767beeeb2fdda384e1303e9cc3db Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 19 Aug 2009 01:05:15 +0100 Subject: [PATCH 046/331] return the newly created terminal --- terminatorlib/newterminator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index d301932d..be8132eb 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -34,6 +34,7 @@ class Terminator(Borg): terminal = Terminal() self.terminals.append(terminal) + return(terminal) def reconfigure_terminals(self): """Tell all terminals to update their configuration""" From 09780a650021e7422979252adcae855efb580a71 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 19 Aug 2009 01:05:30 +0100 Subject: [PATCH 047/331] Use the correct gobject init call --- terminatorlib/searchbar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/searchbar.py b/terminatorlib/searchbar.py index 1a6d3b59..e2c7cbb7 100755 --- a/terminatorlib/searchbar.py +++ b/terminatorlib/searchbar.py @@ -25,7 +25,7 @@ class Searchbar(gtk.HBox): def __init__(self): """Class initialiser""" gtk.HBox.__init__(self) - self.__gobject__init() + self.__gobject_init__() # Search text self.entry = gtk.Entry() From f5ee9d54c5b729971b6ff964047a1fe7be144392 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 19 Aug 2009 01:05:44 +0100 Subject: [PATCH 048/331] use the correct gobject init call and add a stub function --- terminatorlib/titlebar.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index 427a5c1d..33c84351 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -13,8 +13,12 @@ class Titlebar(gtk.EventBox): def __init__(self): """Class initialiser""" gtk.EventBox.__init__(self) - self.__gobject__init() + self.__gobject_init__() self.show() + def connect_icon(self, func): + """Connect the supplied function to clicking on the group icon""" + pass + gobject.type_register(Titlebar) From f3d5fa61e4ba17af56407d09f804ce1365ce2fdd Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 19 Aug 2009 01:06:07 +0100 Subject: [PATCH 049/331] config is now a borg, so there's no need to pass a reference around. Also fix a config keyerror --- terminatorlib/window.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 568f4556..e8a7afe5 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -30,11 +30,11 @@ class Window(Container, gtk.Window): hidebound = None hidefunc = None - def __init__(self, configobject): + def __init__(self): """Class initialiser""" self.terminator = Terminator() - Container.__init__(self, configobject) + Container.__init__(self) gtk.Window.__init__(self) gobject.type_register(Window) self.register_signals(Window) @@ -72,7 +72,7 @@ class Window(Container, gtk.Window): def apply_config(self): """Apply various configuration options""" self.set_fullscreen(self.config['fullscreen']) - self.set_maximised(self.config['maximised']) + self.set_maximised(self.config['maximise']) self.set_borderless(self.config['borderless']) self.set_real_transparency(self.config['enable_real_transparency']) if self.hidebound: From 71fff759f1e32dcf0a6feeae4e050b5ec29a7c48 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 19 Aug 2009 01:06:38 +0100 Subject: [PATCH 050/331] fix gobject init, add drag&drop functionality, add widget signals and a load of stub functions --- terminatorlib/terminal.py | 191 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 185 insertions(+), 6 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index f1281b88..d7f84152 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -4,12 +4,15 @@ """terminal.py - classes necessary to provide Terminal widgets""" import sys +import os import pygtk pygtk.require('2.0') import gtk +import gobject from util import dbg, err, gerr from config import Config +from cwd import get_default_cwd from titlebar import Titlebar from searchbar import Searchbar @@ -23,29 +26,53 @@ except ImportError: class Terminal(gtk.VBox): """Class implementing the VTE widget and its wrappings""" + __gsignals__ = { + 'close-term': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + } + + TARGET_TYPE_VTE = 8 + vte = None + terminalbox = None titlebar = None searchbar = None + cwd = None + clipboard = None + matches = None config = None default_encoding = None + composite_support = None + def __init__(self): """Class initialiser""" gtk.VBox.__init__(self) + self.__gobject_init__() + self.matches = {} self.config = Config() + self.cwd = get_default_cwd() + self.clipboard = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD) + self.vte = vte.Terminal() self.vte.set_size(80, 24) self.vte._expose_data = None + if not hasattr(self.vte, "set_opacity") or not hasattr(self.vte, + "is_composited"): + self.composite_support = False self.vte.show() + self.default_encoding = self.vte.get_encoding() self.update_url_matches(self.config['try_posix_regexp']) + self.terminalbox = self.create_terminalbox() + self.titlebar = Titlebar() + self.titlebar.connect_icon(self.on_group_button_press) self.searchbar = Searchbar() self.show() @@ -53,6 +80,36 @@ class Terminal(gtk.VBox): self.pack_start(self.terminalbox) self.pack_end(self.searchbar) + self.connect_signals() + + os.putenv('COLORTERM', 'gnome-terminal') + + env_proxy = os.getenv('http_proxy') + if not env_proxy: + if self.config['http_proxy'] and self.config['http_proxy'] != '': + os.putenv('http_proxy', self.config['http_proxy']) + + def create_terminalbox(self): + """Create a GtkHBox containing the terminal and a scrollbar""" + + terminalbox = gtk.HBox() + scrollbar = gtk.VScrollbar(self.vte.get_adjustment()) + position = self.config['scrollbar_position'] + + if position not in ('hidden', 'disabled'): + scrollbar.show() + + if position == 'left': + func = terminalbox.pack_end + else: + func = terminalbox.pack_start + + func(self.vte) + func(scrollbar, False) + terminalbox.show() + + return(terminalbox) + def update_url_matches(self, posix = True): """Update the regexps used to match URLs""" userchars = "-A-Za-z0-9" @@ -76,7 +133,7 @@ class Terminal(gtk.VBox): lboundry = "\\<" rboundry = "\\>" - self.matches['full_uri'] = self._vte.match_add(lboundry + schemes + + self.matches['full_uri'] = self.vte.match_add(lboundry + schemes + "//(" + user + "@)?[" + hostchars +".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?") @@ -87,23 +144,145 @@ class Terminal(gtk.VBox): else: err ('update_url_matches: Failed adding URL match patterns') else: - self.matches['voip'] = self._vte.match_add(lboundry + + self.matches['voip'] = self.vte.match_add(lboundry + '(callto:|h323:|sip:)' + "[" + userchars + "+][" + userchars + ".]*(:[0-9]+)?@?[" + pathchars + "]+" + rboundry) - self.matches['addr_only'] = self._vte.match_add (lboundry + + self.matches['addr_only'] = self.vte.match_add (lboundry + "(www|ftp)[" + hostchars + "]*\.[" + hostchars + ".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?") - self.matches['email'] = self._vte.match_add (lboundry + + self.matches['email'] = self.vte.match_add (lboundry + "(mailto:)?[a-zA-Z0-9][a-zA-Z0-9.+-]*@[a-zA-Z0-9]\ [a-zA-Z0-9-]*\.[a-zA-Z0-9][a-zA-Z0-9-]+\ [.a-zA-Z0-9-]*" + rboundry) - self.matches['nntp'] = self._vte.match_add (lboundry + + self.matches['nntp'] = self.vte.match_add (lboundry + '''news:[-A-Z\^_a-z{|}~!"#$%&'()*+,./0-9;:=?`]+@\ [-A-Za-z0-9.]+(:[0-9]+)?''' + rboundry) # if the url looks like a Launchpad changelog closure entry # LP: #92953 - make it a url to http://bugs.launchpad.net - self.matches['launchpad'] = self._vte.match_add ( + self.matches['launchpad'] = self.vte.match_add ( '\\bLP:? #?[0-9]+\\b') + def connect_signals(self): + """Connect all the gtk signals and drag-n-drop mechanics""" + + self.vte.connect('key-press-event', self.on_keypress) + self.vte.connect('button-press-event', self.on_buttonpress) + self.vte.connect('popup-menu', self.popup_menu) + + srcvtetargets = [("vte", gtk.TARGET_SAME_APP, self.TARGET_TYPE_VTE)] + dsttargets = [("vte", gtk.TARGET_SAME_APP, self.TARGET_TYPE_VTE), + ('text/plain', 0, 0), ('STRING', 0, 0), ('COMPOUND_TEXT', 0, 0)] + + for (widget, mask) in [ + (self.vte, gtk.gdk.CONTROL_MASK | gtk.gdk.BUTTON3_MASK), + (self.titlebar, gtk.gdk.CONTROL_MASK)]: + widget.drag_source_set(mask, srcvtetargets, gtk.gdk.ACTION_MOVE) + + self.vte.drag_dest_set(gtk.DEST_DEFAULT_MOTION | + gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP, + dsttargets, gtk.gdk.ACTION_MOVE) + + for widget in [self.vte, self.titlebar]: + widget.connect('drag-begin', self.on_drag_begin, self) + widget.connect('drag-data-get', self.on_drag_data_get, + self) + + self.vte.connect('drag-motion', self.on_drag_motion, self) + self.vte.connect('drag-data-received', + self.on_drag_data_received, self) + + if self.config['copy_on_selection']: + self.vte.connect('selection-changed', lambda widget: + self.vte.copy_clipboard()) + + if self.composite_support: + self.vte.connect('composited-changed', + self.on_composited_changed) + + self.vte.connect('window-title-changed', + self.on_vte_title_change) + self.vte.connect('grab-focus', self.on_vte_focus) + self.vte.connect('focus-out-event', self.on_vte_focus_out) + self.vte.connect('focus-in-event', self.on_vte_focus_in) + self.vte.connect('resize-window', self.on_resize_window) + self.vte.connect('size-allocate', self.on_vte_size_allocate) + + if self.config['exit_action'] == 'restart': + self.vte.connect('child-exited', self.spawn_child) + elif self.config['exit_action'] in ('close', 'left'): + self.vte.connect('child-exited', self.close_term) + + self.vte.add_events(gtk.gdk.ENTER_NOTIFY_MASK) + self.vte.connect('enter_notify_event', + self.on_vte_notify_enter) + + self.vte.connect_after('realize', self.reconfigure) + + def reconfigure(self): + """Reconfigure our settings""" + pass + + def on_group_button_press(self): + """Handler for the group button""" + pass + + def on_keypress(self): + """Handler for keyboard events""" + pass + + def on_buttonpress(self): + """Handler for mouse events""" + pass + + def popup_menu(self): + """Display the context menu""" + pass + + def on_drag_begin(self): + pass + + def on_drag_data_get(self): + pass + + def on_drag_motion(self): + pass + + def on_drag_data_received(self): + pass + + def on_vte_title_change(self): + pass + + def on_vte_focus(self): + pass + + def on_vte_focus_out(self): + pass + + def on_vte_focus_in(self): + pass + + def on_resize_window(self): + pass + + def on_vte_size_allocate(self): + pass + + def on_vte_notify_enter(self): + pass + + def close_term(self): + self.emit('close-term') + + def hide_titlebar(self): + self.titlebar.hide() + + def show_titlebar(self): + self.titlebar.show() + + def spawn_child(self): + pass + +gobject.type_register(Terminal) # vim: set expandtab ts=4 sw=4: From 428f931bf3765417c868605405215bbb665246d4 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 19 Aug 2009 19:00:33 +0100 Subject: [PATCH 051/331] oops, don't show the searchbar by default --- terminatorlib/searchbar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/searchbar.py b/terminatorlib/searchbar.py index e2c7cbb7..f4669046 100755 --- a/terminatorlib/searchbar.py +++ b/terminatorlib/searchbar.py @@ -65,7 +65,7 @@ class Searchbar(gtk.HBox): self.pack_start(self.next, False, False) self.pack_end(close, False, False) - self.show() + self.hide() # pylint: disable-msg=W0613 def search_keypress(self, widget, event): From 326bb4fa949187e4225c622c8075b53c633313ad Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 19 Aug 2009 19:00:47 +0100 Subject: [PATCH 052/331] Remove the default_setup function, it's very broken --- terminatorlib/window.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index e8a7afe5..1512ec72 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -45,8 +45,6 @@ class Window(Container, gtk.Window): self.register_callbacks() self.apply_config() - self.default_setup() - def register_callbacks(self): """Connect the GTK+ signals we care about""" self.connect('key-press-event', self.on_key_press) @@ -92,15 +90,6 @@ class Window(Container, gtk.Window): self.set_icon(icon) - def default_setup(self): - """Set up the default child widget""" - terminal = self.terminator.new_terminal() - - self.add(terminal) - terminal.hide_titlebar() - self.show() - terminal.spawn_child() - def on_key_press(self, window, event): """Handle a keyboard event""" pass From d17981b581591600fe0cd53d362707e56d22bc79 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 19 Aug 2009 19:01:11 +0100 Subject: [PATCH 053/331] Add another stub function to the titlebar class --- terminatorlib/titlebar.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index 33c84351..6580e833 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -21,4 +21,8 @@ class Titlebar(gtk.EventBox): """Connect the supplied function to clicking on the group icon""" pass + def update_terminal_size(self, width, height): + """Update the displayed terminal size""" + pass + gobject.type_register(Titlebar) From 4111a80f7a1ec5b13d4ce25e7d236e02f0870c30 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 19 Aug 2009 19:02:33 +0100 Subject: [PATCH 054/331] reduce debug verbosity a little, add a handler to automagically spawn children. add the correct number of parameters to various signal handlers. flesh out the child spawning codepath --- terminatorlib/terminal.py | 123 +++++++++++++++++++++++++++++++------- 1 file changed, 101 insertions(+), 22 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index d7f84152..fda7e3f4 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -3,6 +3,7 @@ # GPL v2 only """terminal.py - classes necessary to provide Terminal widgets""" +import pwd import sys import os import pygtk @@ -121,15 +122,11 @@ class Terminal(gtk.VBox): urlpath = "/[" + pathchars + "]*[^]'.}>) \t\r\n,\\\"]" if posix: - dbg ('update_url_matches: Trying POSIX URL regexps. Set \ - try_posix_regexp = False in config to only try GNU \ - if you get (harmless) VTE warnings.') + dbg ('update_url_matches: Trying POSIX URL regexps') lboundry = "[[:<:]]" rboundry = "[[:>:]]" else: # GNU - dbg ('update_url_matches: Trying GNU URL regexps. Set \ - try_posix_regexp = True in config if URLs are not \ - detected.') + dbg ('update_url_matches: Trying GNU URL regexps') lboundry = "\\<" rboundry = "\\>" @@ -218,8 +215,9 @@ class Terminal(gtk.VBox): self.on_vte_notify_enter) self.vte.connect_after('realize', self.reconfigure) + self.vte.connect_after('realize', self.spawn_child) - def reconfigure(self): + def reconfigure(self, widget=None): """Reconfigure our settings""" pass @@ -227,11 +225,11 @@ class Terminal(gtk.VBox): """Handler for the group button""" pass - def on_keypress(self): + def on_keypress(self, vte, event): """Handler for keyboard events""" pass - def on_buttonpress(self): + def on_buttonpress(self, vte, event): """Handler for mouse events""" pass @@ -239,40 +237,44 @@ class Terminal(gtk.VBox): """Display the context menu""" pass - def on_drag_begin(self): + def on_drag_begin(self, widget, drag_context, data): pass - def on_drag_data_get(self): + def on_drag_data_get(self, widget, drag_context, selection_data, info, time, + data): pass - def on_drag_motion(self): + def on_drag_motion(self, widget, drag_context, x, y, time, data): pass - def on_drag_data_received(self): + def on_drag_data_received(self, widget, drag_context, x, y, selection_data, + info, time, data): pass - def on_vte_title_change(self): + def on_vte_title_change(self, vte): pass - def on_vte_focus(self): + def on_vte_focus(self, vte): pass - def on_vte_focus_out(self): + def on_vte_focus_out(self, vte, event): pass - def on_vte_focus_in(self): + def on_vte_focus_in(self, vte, event): pass def on_resize_window(self): pass - def on_vte_size_allocate(self): + def on_vte_size_allocate(self, widget, allocation): + self.titlebar.update_terminal_size(self.vte.get_column_count(), + self.vte.get_row_count()) pass - def on_vte_notify_enter(self): + def on_vte_notify_enter(self, term, event): pass - def close_term(self): + def close_term(self, widget): self.emit('close-term') def hide_titlebar(self): @@ -281,8 +283,85 @@ class Terminal(gtk.VBox): def show_titlebar(self): self.titlebar.show() - def spawn_child(self): - pass + def spawn_child(self, widget=None): + update_records = self.config['update_records'] + login = self.config['login_shell'] + args = [] + shell = None + command = None + + if self.config['use_custom_command']: + command = self.config['custom_command'] + + shell = self.shell_lookup() + + if self.config['login_shell']: + args.insert(0, "-%s" % shell) + else: + args.insert(0, shell) + + if command is not None: + args += ['-c', command] + + if shell is None: + self.vte.feed(_('Unable to find a shell')) + return(-1) + + os.putenv('WINDOWID', '%s' % self.vte.get_parent_window().xid) + + self.pid = self.vte.fork_command(command=shell, argv=args, envv=[], + loglastlog=login, logwtmp=update_records, + logutmp=update_records, directory=self.cwd) + + self.on_vte_title_change(self.vte) + self.titlebar.update() + + if self.pid == -1: + self.vte.feed(_('Unable to start shell:') + shell) + return(-1) + + def shell_lookup(self): + """Find an appropriate shell for the user""" + shells = [os.getenv('SHELL'), pwd.getpwuid(os.getuid())[6], 'bash', + 'zsh', 'tcsh', 'ksh', 'csh', 'sh'] + + for shell in shells: + if shell is None: continue + elif os.path.isfile(shell): + return(shell) + else: + rshell = self.path_lookup(shell) + if rshell is not None: + dbg('shell_lookup: Found %s at %s' % (shell, rshell)) + return(rshell) + dbg('shell_lookup: Unable to locate a shell') + + def path_lookup(self, command): + if os.path.isabs(command): + if os.path.isfile(command): + return(command) + else: + return(None) + elif command[:2] == './' and os.path.isfile(command): + dbg('path_lookup: Relative filename %s found in cwd' % command) + return(command) + + try: + paths = os.environ['PATH'].split(':') + if len(paths[0]) == 0: raise(ValueError) + except (ValueError, NameError): + dbg('path_lookup: PATH not set in environment, using fallbacks') + paths = ['/usr/local/bin', '/usr/bin', '/bin'] + + dbg('path_lookup: Using %d paths: %s', (len(paths), paths)) + + for path in paths: + target = os.path.join(path, command) + if os.path.isfile(target): + dbg('path_lookup: found %s' % target) + return(target) + + dbg('path_lookup: Unable to locate %s' % command) gobject.type_register(Terminal) # vim: set expandtab ts=4 sw=4: From 93d8118c33ea40cdb0c446bbc7b6d510ef8263b7 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 28 Aug 2009 00:20:22 +0100 Subject: [PATCH 055/331] add some more add/remove love to Window --- terminatorlib/container.py | 11 +++++++++-- terminatorlib/window.py | 10 ++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 979f06a0..39a40b5a 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -6,6 +6,7 @@ import gobject from config import Config +from util import dbg # pylint: disable-msg=R0921 class Container(object): @@ -31,6 +32,7 @@ class Container(object): """Class initialiser""" self.children = [] self.config = Config() + self.state_zoomed = self.states_zoom['none'] def register_signals(self, widget): """Register gobject signals in a way that avoids multiple inheritance""" @@ -65,13 +67,18 @@ class Container(object): """Default unsplitter. This should be implemented by subclasses""" raise NotImplementedError('unsplit') + def add(self, widget): + """Add a widget to the container""" + raise NotImplementedError('add') + def remove(self, widget): """Remove a widget from the container""" raise NotImplementedError('remove') def closeterm(self, widget): """Handle the closure of a terminal""" - if self.state_zoomed != self.states_zoom['normal']: + if self.state_zoomed != self.states_zoom['none']: + dbg('closeterm: current zoomed state is: %s' % self.state_zoomed) self.unzoom(widget) if not self.remove(widget): @@ -86,7 +93,7 @@ class Container(object): def toggle_zoom(self, widget, fontscale = False): """Toggle the existing zoom state""" - if self.state_zoomed != self.states_zoom['normal']: + if self.state_zoomed != self.states_zoom['none']: self.unzoom(widget) else: self.zoom(widget, fontscale) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 1512ec72..bb787ba1 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -155,6 +155,16 @@ class Window(Container, gtk.Window): if colormap: self.set_colormap(colormap) + def add(self, widget): + """Add a widget to the window by way of gtk.Window.add()""" + widget.connect('close-term', self.closeterm) + gtk.Window.add(self, widget) + + def remove(self, widget): + """Remove our child widget by way of gtk.Window.remove()""" + gtk.Window.remove(self, widget) + self.destroy() + class WindowTitle(object): """Class to handle the setting of the window title""" From f136b6d78e6ed2f5a81d42759471b1c9de8f1dc9 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 28 Aug 2009 01:11:13 +0100 Subject: [PATCH 056/331] add a stub function to the titlebar --- terminatorlib/titlebar.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index 6580e833..5283499c 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -21,6 +21,10 @@ class Titlebar(gtk.EventBox): """Connect the supplied function to clicking on the group icon""" pass + def update(self): + """Update our contents""" + pass + def update_terminal_size(self, width, height): """Update the displayed terminal size""" pass From 187484271cb94157c8cf23af2d613bdb35cce0ec Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 2 Sep 2009 20:43:45 +0100 Subject: [PATCH 057/331] clean up EditableLabel --- ...natoreditablelabel.py => editablelabel.py} | 25 ++++++++++--------- terminatorlib/terminator.py | 4 +-- terminatorlib/terminatorterm.py | 4 +-- 3 files changed, 17 insertions(+), 16 deletions(-) rename terminatorlib/{terminatoreditablelabel.py => editablelabel.py} (90%) diff --git a/terminatorlib/terminatoreditablelabel.py b/terminatorlib/editablelabel.py similarity index 90% rename from terminatorlib/terminatoreditablelabel.py rename to terminatorlib/editablelabel.py index 65209c8a..f2dd8058 100644 --- a/terminatorlib/terminatoreditablelabel.py +++ b/terminatorlib/editablelabel.py @@ -18,22 +18,23 @@ # , Boston, MA 02110-1301 USA # pylint: disable-msg=W0212 -''' Editable Label class''' +""" Editable Label class""" import gtk -class TerminatorEditableLabel( gtk.EventBox ): - ''' +class EditableLabel(gtk.EventBox): + """ An eventbox that partialy emulate a gtk.Label On double-click, the label is editable, entering an empty will revert back to automatic text - ''' + """ _label = None _ebox = None _autotext = None _custom = None _entry = None _entry_handler_id = [] + def __init__(self, text = ""): - ''' Class initialiser''' + """ Class initialiser""" gtk.EventBox.__init__(self) self._label = gtk.Label(text) self._custom = False @@ -42,21 +43,21 @@ class TerminatorEditableLabel( gtk.EventBox ): self.connect ("button-press-event", self._on_click_text) def set_angle(self, angle ): - '''set angle of the label''' + """set angle of the label""" self._label.set_angle( angle ) def set_text( self, text, force=False): - '''set the text of the label''' + """set the text of the label""" self._autotext = text if not self._custom or force: self._label.set_text(text) def get_text( self ): - '''get the text from the label''' + """get the text from the label""" return self._label.get_text() def _on_click_text(self, widget, event): - '''event handling text edition''' + """event handling text edition""" if event.type == gtk.gdk._2BUTTON_PRESS : self.remove (self._label) self._entry = gtk.Entry () @@ -77,7 +78,7 @@ class TerminatorEditableLabel( gtk.EventBox ): return False def _entry_to_label (self, widget, event): - '''replace gtk.Entry by the gtk.Label''' + """replace gtk.Entry by the gtk.Label""" if self._entry and self._entry in self.get_children(): #disconnect signals to avoid segfault :s for sig in self._entry_handler_id: @@ -94,7 +95,7 @@ class TerminatorEditableLabel( gtk.EventBox ): return False def _on_entry_activated (self, widget): - '''get the text entered in gtk.Entry''' + """get the text entered in gtk.Entry""" entry = self._entry.get_text () label = self._label.get_text () if entry == '': @@ -110,7 +111,7 @@ class TerminatorEditableLabel( gtk.EventBox ): return def _on_entry_keypress (self, widget, event): - '''handle keypressed in gtk.Entry''' + """handle keypressed in gtk.Entry""" key = gtk.gdk.keyval_name (event.keyval) if key == 'Escape': self._entry_to_label (None, None) diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index 907a6706..996d49b9 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -29,7 +29,7 @@ from util import dbg, err, debug from keybindings import TerminatorKeybindings from terminatorterm import TerminatorTerm from prefs_profile import ProfileEditor -from terminatoreditablelabel import TerminatorEditableLabel +from editablelabel import EditableLabel import translation # FIXME: Move to notebook.py @@ -45,7 +45,7 @@ class TerminatorNotebookTabLabel(gtk.HBox): self._notebook = notebook self._terminator = terminator - self._label = TerminatorEditableLabel(title) + self._label = EditableLabel(title) self.update_angle() self.pack_start(self._label, True, True) diff --git a/terminatorlib/terminatorterm.py b/terminatorlib/terminatorterm.py index 74492d1a..3d136a96 100755 --- a/terminatorlib/terminatorterm.py +++ b/terminatorlib/terminatorterm.py @@ -30,7 +30,7 @@ from terminatorlib.util import dbg, err, debug #import encoding list from terminatorlib.encoding import TerminatorEncoding -from terminatorlib.terminatoreditablelabel import TerminatorEditableLabel +from editablelabel import EditableLabel # import translation support from terminatorlib import translation @@ -62,7 +62,7 @@ class TerminatorTermTitle (gtk.EventBox): def __init__ (self, terminal, terminator, configwanted = False): gtk.EventBox.__init__ (self) - self._title = TerminatorEditableLabel() + self._title = EditableLabel() self._group = gtk.Label () self._separator = gtk.VSeparator () self._ebox = gtk.EventBox () From f00c265f4cb34c48e83ead29b74a9b4e365ac1c9 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 2 Sep 2009 21:10:28 +0100 Subject: [PATCH 058/331] Make the window title update with the terminal title --- terminatorlib/terminal.py | 21 ++++++++++++++++++++- terminatorlib/titlebar.py | 6 ++++++ terminatorlib/window.py | 8 ++++++-- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index fda7e3f4..96599595 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -29,6 +29,8 @@ class Terminal(gtk.VBox): __gsignals__ = { 'close-term': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'title-change': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_STRING,)), } TARGET_TYPE_VTE = 8 @@ -39,6 +41,7 @@ class Terminal(gtk.VBox): searchbar = None cwd = None + command = None clipboard = None matches = None @@ -221,6 +224,10 @@ class Terminal(gtk.VBox): """Reconfigure our settings""" pass + def get_window_title(self): + """Return the window title""" + return(self.vte.get_window_title() or str(self.command)) + def on_group_button_press(self): """Handler for the group button""" pass @@ -252,7 +259,18 @@ class Terminal(gtk.VBox): pass def on_vte_title_change(self, vte): - pass + title = self.get_window_title() + if title == self.titlebar.oldtitle: + # Title hasn't changed, don't do anything + return + self.titlebar.oldtitle = title + + if self.config['titletips']: + vte.set_property('has-tooltip', True) + vte.set_property('tooltip-text', title) + + self.titlebar.set_terminal_title(title) + self.emit('title-change', title) def on_vte_focus(self, vte): pass @@ -312,6 +330,7 @@ class Terminal(gtk.VBox): self.pid = self.vte.fork_command(command=shell, argv=args, envv=[], loglastlog=login, logwtmp=update_records, logutmp=update_records, directory=self.cwd) + self.command = shell self.on_vte_title_change(self.vte) self.titlebar.update() diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index 5283499c..cf0ad1b0 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -10,6 +10,8 @@ import gobject class Titlebar(gtk.EventBox): """Class implementing the Titlebar widget""" + oldtitle = None + def __init__(self): """Class initialiser""" gtk.EventBox.__init__(self) @@ -29,4 +31,8 @@ class Titlebar(gtk.EventBox): """Update the displayed terminal size""" pass + def set_terminal_title(self, title): + """Update the terminal title""" + pass + gobject.type_register(Titlebar) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index bb787ba1..fd654f14 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -45,6 +45,9 @@ class Window(Container, gtk.Window): self.register_callbacks() self.apply_config() + self.title = WindowTitle(self) + self.title.update() + def register_callbacks(self): """Connect the GTK+ signals we care about""" self.connect('key-press-event', self.on_key_press) @@ -158,6 +161,7 @@ class Window(Container, gtk.Window): def add(self, widget): """Add a widget to the window by way of gtk.Window.add()""" widget.connect('close-term', self.closeterm) + widget.connect('title-change', self.title.set_title) gtk.Window.add(self, widget) def remove(self, widget): @@ -177,10 +181,10 @@ class WindowTitle(object): self.window = window self.forced = False - def set_title(self, newtext): + def set_title(self, widget, text): """Set the title""" if not self.forced: - self.text = newtext + self.text = text self.update() def force_title(self, newtext): From 9f7834fb5b53fd2fbf81274f8cadac327fced829 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 2 Sep 2009 21:18:36 +0100 Subject: [PATCH 059/331] simplify title changes --- terminatorlib/terminal.py | 15 +++------------ terminatorlib/titlebar.py | 2 +- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 96599595..53550480 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -77,6 +77,8 @@ class Terminal(gtk.VBox): self.titlebar = Titlebar() self.titlebar.connect_icon(self.on_group_button_press) + self.connect('title-change', self.titlebar.set_terminal_title) + self.searchbar = Searchbar() self.show() @@ -259,18 +261,7 @@ class Terminal(gtk.VBox): pass def on_vte_title_change(self, vte): - title = self.get_window_title() - if title == self.titlebar.oldtitle: - # Title hasn't changed, don't do anything - return - self.titlebar.oldtitle = title - - if self.config['titletips']: - vte.set_property('has-tooltip', True) - vte.set_property('tooltip-text', title) - - self.titlebar.set_terminal_title(title) - self.emit('title-change', title) + self.emit('title-change', self.get_window_title()) def on_vte_focus(self, vte): pass diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index cf0ad1b0..6a9e9420 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -31,7 +31,7 @@ class Titlebar(gtk.EventBox): """Update the displayed terminal size""" pass - def set_terminal_title(self, title): + def set_terminal_title(self, widget, title): """Update the terminal title""" pass From d007bc45c5acdef2c5860fe048f43d35a0709830 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 2 Sep 2009 22:38:27 +0100 Subject: [PATCH 060/331] bare minimum titlebar functionality --- terminatorlib/titlebar.py | 49 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index 6a9e9420..959f510b 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -6,18 +6,47 @@ import gtk import gobject +from editablelabel import EditableLabel + # pylint: disable-msg=R0904 class Titlebar(gtk.EventBox): """Class implementing the Titlebar widget""" oldtitle = None + termtext = None + sizetext = None + label = None + hbox = None + ebox = None + grouphbox = None + groupicon = None + grouplabel = None def __init__(self): """Class initialiser""" gtk.EventBox.__init__(self) self.__gobject_init__() - self.show() + self.label = EditableLabel() + self.ebox = gtk.EventBox() + self.grouphbox = gtk.HBox() + self.grouplabel = gtk.Label() + self.groupicon = gtk.Image() + + # FIXME: How do we decide which icon to use? + + self.grouphbox.pack_start(self.groupicon, False, True, 2) + self.grouphbox.pack_start(self.grouplabel, False, True, 2) + self.ebox.add(self.grouphbox) + self.ebox.show_all() + + self.hbox = gtk.HBox() + self.hbox.pack_start(self.ebox, False, True, 0) + self.hbox.pack_start(gtk.VSeparator(), False, True, 0) + self.hbox.pack_start(self.label, True, True) + + self.add(self.hbox) + self.show_all() def connect_icon(self, func): """Connect the supplied function to clicking on the group icon""" @@ -25,14 +54,28 @@ class Titlebar(gtk.EventBox): def update(self): """Update our contents""" - pass + self.label.set_text("%s %s" % (self.termtext, self.sizetext)) def update_terminal_size(self, width, height): """Update the displayed terminal size""" - pass + self.sizetext = "%sx%s" % (width, height) + self.update() def set_terminal_title(self, widget, title): """Update the terminal title""" + self.termtext = title + self.update() + + def set_group_label(self, name): + """Set the name of the group""" + if name: + self.grouplabel.set_text(name) + self.grouplabel.show() + else: + self.grouplabel.hide() + + def on_clicked(self, widget, event): + """Handle a click on the label""" pass gobject.type_register(Titlebar) From 0f5cf7c4967f11f0fe3746c6adb12d1bd5d90ce6 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 2 Sep 2009 23:17:08 +0100 Subject: [PATCH 061/331] remove titletips, it's a terrible option --- doc/terminator_config.5 | 4 ---- terminatorlib/config.py | 1 - 2 files changed, 5 deletions(-) diff --git a/doc/terminator_config.5 b/doc/terminator_config.5 index 4dcbe8e7..11fdfcb9 100644 --- a/doc/terminator_config.5 +++ b/doc/terminator_config.5 @@ -115,10 +115,6 @@ Default value: \fBTrue\fR If true, a titlebar will be drawn on zoomed/maximised terminals which indicates how many are hidden. Default value: \fBTrue\fR .TP -.B titletips -If true, a tooltip will be available for each terminal which shows the current title of that terminal. -Default value: \fBFalse\fR -.TP .B title_tx_txt_color Sets the colour of the text shown in the titlebar of the active terminal. Default value: \fB#FFFFFF\fR diff --git a/terminatorlib/config.py b/terminatorlib/config.py index e9175c30..5b193d9e 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -27,7 +27,6 @@ DEFAULTS = { 'profile_dir' : '/apps/gnome-terminal/profiles', 'titlebars' : True, 'zoomedtitlebar' : True, - 'titletips' : False, 'allow_bold' : True, 'audible_bell' : False, 'visible_bell' : True, From bf20587edc51e21202b77bc74d4d8d7297a64819 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 2 Sep 2009 23:17:54 +0100 Subject: [PATCH 062/331] decouple Terminal from Terminator, and add the minimum necessary group icon handling --- terminatorlib/newterminator.py | 12 +++++++----- terminatorlib/test.py | 4 +++- terminatorlib/titlebar.py | 23 ++++++++++++++++++++++- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index be8132eb..b1853aa1 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -4,7 +4,8 @@ """terminator.py - class for the master Terminator singleton""" from borg import Borg -from terminal import Terminal + +groupsend_type = {'all':0, 'group':1, 'off':2} class Terminator(Borg): """master object for the application""" @@ -14,6 +15,7 @@ class Terminator(Borg): terminals = None groups = None config = None + groupsend = None def __init__(self): """Class initialiser""" @@ -28,13 +30,13 @@ class Terminator(Borg): self.terminals = [] if not self.groups: self.groups = [] + if not self.groupsend: + self.groupsend = groupsend_type['group'] - def new_terminal(self): - """Create and register a new terminal widget""" + def register_terminal(self, terminal): + """Register a new terminal widget""" - terminal = Terminal() self.terminals.append(terminal) - return(terminal) def reconfigure_terminals(self): """Tell all terminals to update their configuration""" diff --git a/terminatorlib/test.py b/terminatorlib/test.py index 71e38032..62f83f2e 100755 --- a/terminatorlib/test.py +++ b/terminatorlib/test.py @@ -4,10 +4,12 @@ import gtk from newterminator import Terminator from window import Window +from terminal import Terminal window = Window() foo = Terminator() -term = foo.new_terminal() +term = Terminal() +foo.register_terminal(term) window.add(term) window.show() diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index 959f510b..99deb360 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -6,12 +6,15 @@ import gtk import gobject +from version import APP_NAME +from newterminator import Terminator,groupsend_type from editablelabel import EditableLabel # pylint: disable-msg=R0904 class Titlebar(gtk.EventBox): """Class implementing the Titlebar widget""" + terminator = None oldtitle = None termtext = None sizetext = None @@ -27,13 +30,22 @@ class Titlebar(gtk.EventBox): gtk.EventBox.__init__(self) self.__gobject_init__() + self.terminator = Terminator() + self.label = EditableLabel() self.ebox = gtk.EventBox() self.grouphbox = gtk.HBox() self.grouplabel = gtk.Label() self.groupicon = gtk.Image() - # FIXME: How do we decide which icon to use? + if self.terminator.groupsend == groupsend_type['all']: + icon_name = 'all' + elif self.terminator.groupsend == groupsend_type['group']: + icon_name = 'group' + elif self.terminator.groupsend == groupsend_type['off']: + icon_name = 'off' + self.set_from_icon_name('_active_broadcast_%s' % icon_name, + gtk.ICON_SIZE_MENU) self.grouphbox.pack_start(self.groupicon, False, True, 2) self.grouphbox.pack_start(self.grouplabel, False, True, 2) @@ -56,6 +68,15 @@ class Titlebar(gtk.EventBox): """Update our contents""" self.label.set_text("%s %s" % (self.termtext, self.sizetext)) + def set_from_icon_name(self, name, size = gtk.ICON_SIZE_MENU): + """Set an icon for the group label""" + if not name: + self.groupicon.hide() + return + + self.groupicon.set_from_icon_name(APP_NAME + name, size) + self.groupicon.show() + def update_terminal_size(self, width, height): """Update the displayed terminal size""" self.sizetext = "%sx%s" % (width, height) From 846e0c0fff38a79ff9794754b31b5ccc79537966 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 3 Sep 2009 00:51:46 +0100 Subject: [PATCH 063/331] Emit a signal when editing is done --- terminatorlib/editablelabel.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/terminatorlib/editablelabel.py b/terminatorlib/editablelabel.py index f2dd8058..d997c2e7 100644 --- a/terminatorlib/editablelabel.py +++ b/terminatorlib/editablelabel.py @@ -20,6 +20,7 @@ # pylint: disable-msg=W0212 """ Editable Label class""" import gtk +import gobject class EditableLabel(gtk.EventBox): """ @@ -33,9 +34,15 @@ class EditableLabel(gtk.EventBox): _entry = None _entry_handler_id = [] + __gsignals__ = { + 'edit-done': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + } + def __init__(self, text = ""): """ Class initialiser""" gtk.EventBox.__init__(self) + self.__gobject_init__() + self._label = gtk.Label(text) self._custom = False self.set_visible_window (False) @@ -58,7 +65,7 @@ class EditableLabel(gtk.EventBox): def _on_click_text(self, widget, event): """event handling text edition""" - if event.type == gtk.gdk._2BUTTON_PRESS : + if event.type == gtk.gdk._2BUTTON_PRESS: self.remove (self._label) self._entry = gtk.Entry () self._entry.set_text (self._label.get_text ()) @@ -89,6 +96,7 @@ class EditableLabel(gtk.EventBox): self.add (self._label) self._entry = None self.show_all () + self.emit('edit-done') return True #make pylint happy if 1 or widget or event: @@ -119,3 +127,4 @@ class EditableLabel(gtk.EventBox): if 1 or widget or event: return +gobject.type_register(EditableLabel) From 9bac02579518856907495fe480e642de299e929a Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 3 Sep 2009 00:52:36 +0100 Subject: [PATCH 064/331] Add support for grabbing focus after a label is edited and the rudimentary basics of the group popup menu --- terminatorlib/terminal.py | 62 +++++++++++++++++++++++++++++++++++++-- terminatorlib/titlebar.py | 16 ++++++++-- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 53550480..f6f04913 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -16,6 +16,7 @@ from config import Config from cwd import get_default_cwd from titlebar import Titlebar from searchbar import Searchbar +from translation import _ try: import vte @@ -77,6 +78,7 @@ class Terminal(gtk.VBox): self.titlebar = Titlebar() self.titlebar.connect_icon(self.on_group_button_press) + self.titlebar.connect('edit-done', self.on_edit_done) self.connect('title-change', self.titlebar.set_terminal_title) self.searchbar = Searchbar() @@ -222,6 +224,56 @@ class Terminal(gtk.VBox): self.vte.connect_after('realize', self.reconfigure) self.vte.connect_after('realize', self.spawn_child) + def create_popup_group_menu(self, widget, event = None): + """Pop up a menu for the group widget""" + if event: + button = event.button + time = event.time + else: + button = 0 + time = 0 + + menu = self.populate_group_menu() + menu.show_all() + menu.popup(None, None, self.position_popup_group_menu, button, time, + widget) + return(True) + + def populate_group_menu(self): + """Fill out a group menu""" + menu = gtk.Menu() + groupitem = None + + item = gtk.MenuItem(_('Assign to group...')) + item.connect('activate', self.create_group) + menu.append(item) + + #FIXME: Add the rest of the menu + + return(menu) + + def position_popup_group_menu(self, menu, widget): + """Calculate the position of the group popup menu""" + screen_w = gtk.gdk.screen_width() + screen_h = gtk.gdk.screen_height() + + widget_win = widget.get_window() + widget_x, widget_y = widget_win.get_origin() + widget_w, widget_h = widget_win.get_size() + + menu_w, menu_h = menu.size_request() + + if widget_y + widget_h + menu_h > screen_h: + menu_y = max(widget_y - menu_h, 0) + else: + menu_y = widget_y + widget_h + + return(widget_x, menu_y, 1) + + def create_group(self, item): + """Create a new group""" + pass + def reconfigure(self, widget=None): """Reconfigure our settings""" pass @@ -230,9 +282,11 @@ class Terminal(gtk.VBox): """Return the window title""" return(self.vte.get_window_title() or str(self.command)) - def on_group_button_press(self): + def on_group_button_press(self, widget, event): """Handler for the group button""" - pass + if event.button == 1: + self.create_popup_group_menu(widget, event) + return(False) def on_keypress(self, vte, event): """Handler for keyboard events""" @@ -272,6 +326,10 @@ class Terminal(gtk.VBox): def on_vte_focus_in(self, vte, event): pass + def on_edit_done(self, widget): + """A child widget is done editing a label, return focus to VTE""" + self.vte.grab_focus() + def on_resize_window(self): pass diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index 99deb360..f72c415c 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -25,6 +25,11 @@ class Titlebar(gtk.EventBox): groupicon = None grouplabel = None + __gsignals__ = { + 'clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'edit-done': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + } + def __init__(self): """Class initialiser""" gtk.EventBox.__init__(self) @@ -33,6 +38,7 @@ class Titlebar(gtk.EventBox): self.terminator = Terminator() self.label = EditableLabel() + self.label.connect('edit-done', self.on_edit_done) self.ebox = gtk.EventBox() self.grouphbox = gtk.HBox() self.grouplabel = gtk.Label() @@ -60,9 +66,11 @@ class Titlebar(gtk.EventBox): self.add(self.hbox) self.show_all() + self.connect('button-press-event', self.on_clicked) + def connect_icon(self, func): """Connect the supplied function to clicking on the group icon""" - pass + self.ebox.connect('button-release-event', func) def update(self): """Update our contents""" @@ -97,6 +105,10 @@ class Titlebar(gtk.EventBox): def on_clicked(self, widget, event): """Handle a click on the label""" - pass + self.emit('clicked') + + def on_edit_done(self, widget): + """Re-emit an edit-done signal from an EditableLabel""" + self.emit('edit-done') gobject.type_register(Titlebar) From 799d508708bcf95e151628b2b45e91d967beeb11 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 3 Sep 2009 09:03:37 +0100 Subject: [PATCH 065/331] Make _entry_handler_id not be a static class attribute because it doesn't need to be --- terminatorlib/editablelabel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/terminatorlib/editablelabel.py b/terminatorlib/editablelabel.py index d997c2e7..a0abbcc4 100644 --- a/terminatorlib/editablelabel.py +++ b/terminatorlib/editablelabel.py @@ -32,7 +32,7 @@ class EditableLabel(gtk.EventBox): _autotext = None _custom = None _entry = None - _entry_handler_id = [] + _entry_handler_id = None __gsignals__ = { 'edit-done': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), @@ -43,6 +43,7 @@ class EditableLabel(gtk.EventBox): gtk.EventBox.__init__(self) self.__gobject_init__() + self._entry_handler_id = [] self._label = gtk.Label(text) self._custom = False self.set_visible_window (False) From 1e4af9883bc5a5d7357070f9bb7d93837e47438e Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 3 Sep 2009 10:34:31 +0100 Subject: [PATCH 066/331] keep pylint a little happier --- terminatorlib/titlebar.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index f72c415c..8f7c3e1e 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -7,10 +7,11 @@ import gtk import gobject from version import APP_NAME -from newterminator import Terminator,groupsend_type +from newterminator import Terminator, groupsend_type from editablelabel import EditableLabel # pylint: disable-msg=R0904 +# pylint: disable-msg=W0613 class Titlebar(gtk.EventBox): """Class implementing the Titlebar widget""" From c40c360c20fba307b5e8363c926912de574d0ce6 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 3 Sep 2009 13:59:17 +0100 Subject: [PATCH 067/331] no reason for the groupsend types to be global --- terminatorlib/newterminator.py | 4 ++-- terminatorlib/titlebar.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index b1853aa1..b3b9e772 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -5,7 +5,6 @@ from borg import Borg -groupsend_type = {'all':0, 'group':1, 'off':2} class Terminator(Borg): """master object for the application""" @@ -16,6 +15,7 @@ class Terminator(Borg): groups = None config = None groupsend = None + groupsend_type = {'all':0, 'group':1, 'off':2} def __init__(self): """Class initialiser""" @@ -31,7 +31,7 @@ class Terminator(Borg): if not self.groups: self.groups = [] if not self.groupsend: - self.groupsend = groupsend_type['group'] + self.groupsend = self.groupsend_type['group'] def register_terminal(self, terminal): """Register a new terminal widget""" diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index 8f7c3e1e..38f32c04 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -7,7 +7,7 @@ import gtk import gobject from version import APP_NAME -from newterminator import Terminator, groupsend_type +from newterminator import Terminator from editablelabel import EditableLabel # pylint: disable-msg=R0904 @@ -45,6 +45,7 @@ class Titlebar(gtk.EventBox): self.grouplabel = gtk.Label() self.groupicon = gtk.Image() + groupsend_type = self.terminator.groupsend_type if self.terminator.groupsend == groupsend_type['all']: icon_name = 'all' elif self.terminator.groupsend == groupsend_type['group']: From 09a6913d98e1d471e8f5e75e057283e136eded74 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 3 Sep 2009 14:47:14 +0100 Subject: [PATCH 068/331] Flesh out the Terminal button handler and some associated functions --- terminatorlib/terminal.py | 40 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index f6f04913..5319460c 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -294,9 +294,28 @@ class Terminal(gtk.VBox): def on_buttonpress(self, vte, event): """Handler for mouse events""" - pass + # Any button event should grab focus + self.vte.grab_focus() + + if event.button == 1: + # Ctrl+leftclick on a URL should open it + if event.state & gtk.gdk.CONTROL_MASK == gtk.gdk.CONTROL_MASK: + url = self.check_for_url(event) + if url: + self.open_url(url, prepare=True) + elif event.button == 2: + # middleclick should paste the clipboard + self.paste_clipboard(True) + return(True) + elif event.button == 3: + # rightclick should display a context menu if Ctrl is not pressed + if event.state & gtk.gdk.CONTROL_MASK == 0: + self.popup_menu(self.vte, event) + return(True) + + return(False) - def popup_menu(self): + def popup_menu(self, widget, event=None): """Display the context menu""" pass @@ -431,5 +450,22 @@ class Terminal(gtk.VBox): dbg('path_lookup: Unable to locate %s' % command) + def check_for_url(self, event): + """Check if the mouse is over a URL""" + return (self.vte.match_check(int(event.x / self.vte.get_char_width()), + int(event.y / self.vte.get_char_height()))) + + def open_url(self, url, prepare=False): + """Open a given URL, conditionally preparing it""" + pass + + def paste_clipboard(self, primary=False): + """Paste one of the two clipboards""" + # FIXME: Make this work across a group + if primary: + self.vte.paste_primary() + else: + self.vte.paste_clipboard() + self.vte.grab_focus() gobject.type_register(Terminal) # vim: set expandtab ts=4 sw=4: From b58a7224aaac951c973190bf7c6af9ab1bff9f13 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 4 Sep 2009 20:12:04 +0100 Subject: [PATCH 069/331] handle the window closing properly --- terminatorlib/test.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/terminatorlib/test.py b/terminatorlib/test.py index 62f83f2e..d809c477 100755 --- a/terminatorlib/test.py +++ b/terminatorlib/test.py @@ -6,6 +6,10 @@ from newterminator import Terminator from window import Window from terminal import Terminal +def on_window_destroyed(widget): + """Window destroyed, so exit""" + gtk.main_quit() + window = Window() foo = Terminator() term = Terminal() @@ -14,4 +18,6 @@ foo.register_terminal(term) window.add(term) window.show() +window.connect("destroy", on_window_destroyed) + gtk.main() From 718a15706fbd3106d3d86ad02f666e3c2a1ba544 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 4 Sep 2009 20:12:35 +0100 Subject: [PATCH 070/331] handle URLs --- terminatorlib/terminal.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 5319460c..ba516bf6 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -317,6 +317,7 @@ class Terminal(gtk.VBox): def popup_menu(self, widget, event=None): """Display the context menu""" + pass def on_drag_begin(self, widget, drag_context, data): @@ -455,9 +456,38 @@ class Terminal(gtk.VBox): return (self.vte.match_check(int(event.x / self.vte.get_char_width()), int(event.y / self.vte.get_char_height()))) + def prepare_url(self, urlmatch): + """Prepare a URL from a VTE match""" + url = urlmatch[0] + match = urlmatch[1] + + if match == self.matches['email'] and url[0:7] != 'mailto:': + url = 'mailto:' + url + elif match == self.matches['addr_only'] and url[0:3] == 'ftp': + url = 'ftp://' + url + elif match == self.matches['addr_only']: + url = 'http://' + url + elif match == self.matches['launchpad'] + for item in re.findall(r'[0-9]+', url): + url = 'https://bugs.launchpad.net/bugs/%s' % item + return(url) + else: + return(url) + def open_url(self, url, prepare=False): - """Open a given URL, conditionally preparing it""" - pass + """Open a given URL, conditionally unpacking it from a VTE match""" + if prepare == True: + url = self.prepare_url(url) + dbg('open_url: URL: %s (prepared: %s)' % (url, prepare)) + try: + subprocess.Popen(['xdg-open', url]) + except OSError: + dbg('open_url: xdg-open failed') + try: + self.terminator.url_show(url) + except: + dbg('open_url: url_show failed. Giving up') + pass def paste_clipboard(self, primary=False): """Paste one of the two clipboards""" @@ -467,5 +497,6 @@ class Terminal(gtk.VBox): else: self.vte.paste_clipboard() self.vte.grab_focus() + gobject.type_register(Terminal) # vim: set expandtab ts=4 sw=4: From 28e7ee61161a211b335f17e20c8637e0303578e3 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 4 Sep 2009 22:11:52 +0100 Subject: [PATCH 071/331] Add some more of our attributes --- terminatorlib/newterminator.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index b3b9e772..938b237c 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -5,7 +5,6 @@ from borg import Borg - class Terminator(Borg): """master object for the application""" @@ -14,6 +13,9 @@ class Terminator(Borg): terminals = None groups = None config = None + + splittogroup = None + autocleangroups = None groupsend = None groupsend_type = {'all':0, 'group':1, 'off':2} @@ -32,6 +34,10 @@ class Terminator(Borg): self.groups = [] if not self.groupsend: self.groupsend = self.groupsend_type['group'] + if not self.splittogroup: + self.splittogroup = False + if not self.autocleangroups: + self.autocleangroups = True def register_terminal(self, terminal): """Register a new terminal widget""" From 2a659182094d1e81fb66a4539c216877858755c3 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 4 Sep 2009 22:12:13 +0100 Subject: [PATCH 072/331] Fix some typos and flesh out the group menu --- terminatorlib/terminal.py | 96 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 3 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index ba516bf6..e48062af 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -14,6 +14,7 @@ import gobject from util import dbg, err, gerr from config import Config from cwd import get_default_cwd +from newterminator import Terminator from titlebar import Titlebar from searchbar import Searchbar from translation import _ @@ -36,11 +37,13 @@ class Terminal(gtk.VBox): TARGET_TYPE_VTE = 8 + terminator = None vte = None terminalbox = None titlebar = None searchbar = None + group = None cwd = None command = None clipboard = None @@ -56,6 +59,7 @@ class Terminal(gtk.VBox): gtk.VBox.__init__(self) self.__gobject_init__() + self.terminator = Terminator() self.matches = {} self.config = Config() @@ -247,8 +251,79 @@ class Terminal(gtk.VBox): item = gtk.MenuItem(_('Assign to group...')) item.connect('activate', self.create_group) menu.append(item) - - #FIXME: Add the rest of the menu + + if len(self.terminator.groups) > 0: + groupitem = gtk.RadioMenuItem(groupitem, _('None')) + groupitem.set_active(self.group == None) + groupitem.connect('activate', self.set_group, None) + menu.append(groupitem) + + for group in self.terminator.groups: + item = gtk.RadioMenuItem(groupitem, group, False) + item.set_active(self.group == group) + item.connect('toggled', self.set_group, group) + menu.append(item) + groupitem = item + + if self.group != None or len(self.terminator.groups) > 0: + menu.append(gtk.MenuItem()) + + if self.group != None: + item = gtk.MenuItem(_('Remove group %s') % self.group) + item.connect('activate', self.ungroup, self.group) + menu.append(item) + + # FIXME: Functions to group/ungroup all terms in current tab + + if len(self.terminator.groups) > 0: + item = gtk.MenuItem(_('Remove all groups')) + item.connect('activate', self.ungroup_all) # FIXME: ungroup_all should be in Terminator() ? + menu.append(item) + + if self.group != None: + menu.append(gtk.MenuItem()) + + item = gtk.MenuItem(_('Close group %s') % self.group) + item.connect('activate', lambda menu_item: + self.terminator.closegroupedterms(self)) + menu.append(item) + + menu.append(gtk.MenuItem()) + + groupitem = None + + for key,value in {_('Broadcast off'):'off', + _('Broadcast group'):'group', + _('Broadcast all'):'all'}.items(): + groupitem = gtk.RadioMenuItem(groupitem, key) + groupitem.set_active(self.terminator.groupsend == + self.terminator.groupsend_type[value]) + groupitem.connect('activate', self.set_groupsend, + self.terminator.groupsend_type[value]) + menu.append(groupitem) + + menu.append(gtk.MenuItem()) + + item = gtk.CheckMenuItem(_('Split to this group')) + item.set_active(self.terminator.splittogroup) + item.connect('toggled', lambda menu_item: self.do_splittogroup_toggle()) + menu.append(item) + + item = gtk.CheckMenuItem(_('Autoclean groups')) + item.set_active(self.terminator.autocleangroups) + item.connect('toggled', lambda menu_item: + self.do_autocleangroups_toggle()) + menu.append(item) + + menu.append(gtk.MenuItem()) + + item = gtk.MenuItem(_('Insert terminal number')) + item.connect('activate', lambda menu_item: self.do_enumerate()) + menu.append(item) + + item = gtk.MenuItem(_('Insert padded terminal number')) + item.connect('activate', lambda menu_item: self.do_enumerate(pad=True)) + menu.append(item) return(menu) @@ -274,6 +349,21 @@ class Terminal(gtk.VBox): """Create a new group""" pass + def set_groupsend(self, widget, value): + """Set the groupsend mode""" + # FIXME: Can we think of a smarter way of doing this than poking? + self.terminator.groupsend = value + + def do_splittogroup_toggle(self): + """Toggle the splittogroup mode""" + # FIXME: Can we think of a smarter way of doing this than poking? + self.terminator.splittogroup = not self.terminator.splittogroup + + def do_autocleangroups_toggle(self): + """Toggle the autocleangroups mode""" + # FIXME: Can we think of a smarter way of doing this than poking? + self.terminator.autocleangroups = not self.terminator.autocleangroups + def reconfigure(self, widget=None): """Reconfigure our settings""" pass @@ -467,7 +557,7 @@ class Terminal(gtk.VBox): url = 'ftp://' + url elif match == self.matches['addr_only']: url = 'http://' + url - elif match == self.matches['launchpad'] + elif match == self.matches['launchpad']: for item in re.findall(r'[0-9]+', url): url = 'https://bugs.launchpad.net/bugs/%s' % item return(url) From bb073887fc04ae820e16dc919ac08f834980c0bc Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 4 Sep 2009 22:48:35 +0100 Subject: [PATCH 073/331] hook up the terminal enumeration menu items --- terminatorlib/newterminator.py | 25 +++++++++++++++++++++++++ terminatorlib/terminal.py | 22 +++++++++++++++------- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index 938b237c..39b312b4 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -68,4 +68,29 @@ class Terminator(Borg): for group in todestroy: self.groups.remove(group) + def do_enumerate(self, widget, pad): + """Insert the number of each terminal in a group, into that terminal""" + if pad: + numstr='%0'+str(len(str(len(self.terminals))))+'d' + else: + numstr='%d' + + for term in self.get_target_terms(widget): + idx = self.terminals.index(term) + term.feed(numstr % (idx + 1)) + + def get_target_terms(self, widget): + """Get the terminals we should currently be broadcasting to""" + if self.groupsend == self.groupsend_type['all']: + return(self.terminals) + elif self.groupsend == self.groupsend_type['group']: + set = [] + for term in self.terminals: + if term == widget or (term.group != None and term.group == + widget.group): + set.append(term) + return(set) + else: + return([widget]) + # vim: set expandtab ts=4 sw=4: diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index e48062af..f682cced 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -33,6 +33,8 @@ class Terminal(gtk.VBox): 'close-term': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'title-change': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), + 'enumerate': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_INT,)), } TARGET_TYPE_VTE = 8 @@ -60,6 +62,8 @@ class Terminal(gtk.VBox): self.__gobject_init__() self.terminator = Terminator() + self.connect('enumerate', self.terminator.do_enumerate) + self.matches = {} self.config = Config() @@ -318,11 +322,11 @@ class Terminal(gtk.VBox): menu.append(gtk.MenuItem()) item = gtk.MenuItem(_('Insert terminal number')) - item.connect('activate', lambda menu_item: self.do_enumerate()) + item.connect('activate', lambda menu_item: self.emit('enumerate', False)) menu.append(item) item = gtk.MenuItem(_('Insert padded terminal number')) - item.connect('activate', lambda menu_item: self.do_enumerate(pad=True)) + item.connect('activate', lambda menu_item: self.emit('enumerate', True)) menu.append(item) return(menu) @@ -581,12 +585,16 @@ class Terminal(gtk.VBox): def paste_clipboard(self, primary=False): """Paste one of the two clipboards""" - # FIXME: Make this work across a group - if primary: - self.vte.paste_primary() - else: - self.vte.paste_clipboard() + for term in self.terminator.get_target_terms(): + if primary: + term.vte.paste_primary() + else: + term.vte.paste_clipboard() self.vte.grab_focus() + def feed(self, text): + """Feed the supplied text to VTE""" + self.vte.feed_child(text) + gobject.type_register(Terminal) # vim: set expandtab ts=4 sw=4: From 5e54d42eca377ff3c54d26a539241bfc4d351ff6 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 5 Sep 2009 00:34:09 +0100 Subject: [PATCH 074/331] work on grouping/ungrouping of all terminals in a tab --- terminatorlib/newterminator.py | 7 +++++++ terminatorlib/terminal.py | 17 +++++++++++++++-- terminatorlib/util.py | 10 +++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index 39b312b4..e9466b71 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -93,4 +93,11 @@ class Terminator(Borg): else: return([widget]) + def group_tab(self, widget): + """Group all the terminals in a tab""" + pass + + def ungroup_tab(self, widget): + """Ungroup all the terminals in a tab""" + pass # vim: set expandtab ts=4 sw=4: diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index f682cced..22605135 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -11,7 +11,7 @@ pygtk.require('2.0') import gtk import gobject -from util import dbg, err, gerr +from util import dbg, err, gerr, has_ancestor from config import Config from cwd import get_default_cwd from newterminator import Terminator @@ -35,6 +35,8 @@ class Terminal(gtk.VBox): (gobject.TYPE_STRING,)), 'enumerate': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT,)), + 'group-tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'ungroup-tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), } TARGET_TYPE_VTE = 8 @@ -63,6 +65,8 @@ class Terminal(gtk.VBox): self.terminator = Terminator() self.connect('enumerate', self.terminator.do_enumerate) + self.connect('group-tab', self.terminator.group_tab) + self.connect('ungroup-tab', self.terminator.ungroup_tab) self.matches = {} @@ -277,7 +281,16 @@ class Terminal(gtk.VBox): item.connect('activate', self.ungroup, self.group) menu.append(item) - # FIXME: Functions to group/ungroup all terms in current tab + if has_ancestor(self, gtk.Notebook): + item = gtk.MenuItem(_('G_roup all in tab')) + item.connect('activate', lambda menu_item: self.emit('group_tab')) + menu.append(item) + + if len(self.terminator.groups) > 0: + item = gtk.MenuItem(_('Ungr_oup all in tab')) + item.connect('activate', lambda menu_item: + self.emit('ungroup_tab')) + menu.append(item) if len(self.terminator.groups) > 0: item = gtk.MenuItem(_('Remove all groups')) diff --git a/terminatorlib/util.py b/terminatorlib/util.py index eb08b4ab..6bde7b95 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -17,6 +17,7 @@ """Terminator.util - misc utility functions""" import sys +import gtk # set this to true to enable debugging output DEBUG = True @@ -34,8 +35,15 @@ def gerr(message = None): """Display a graphical error. This should only be used for serious errors as it will halt execution""" - import gtk dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message) dialog.run() +def has_ancestor(widget, type): + """Walk up the family tree of widget to see if any ancestors are of type""" + while widget: + widget = widget.get_parent() + if isinstance(widget, type): + return(True) + return(False) + From 690bb61788de6bd850b1cf6bcfabf2202785a7d1 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 6 Sep 2009 21:54:33 +0100 Subject: [PATCH 075/331] add a signal for ungrouping all terminals, and tidy up some lambdas --- terminatorlib/terminal.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 22605135..2591394f 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -37,6 +37,7 @@ class Terminal(gtk.VBox): (gobject.TYPE_INT,)), 'group-tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'ungroup-tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'ungroup-all': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), } TARGET_TYPE_VTE = 8 @@ -283,25 +284,24 @@ class Terminal(gtk.VBox): if has_ancestor(self, gtk.Notebook): item = gtk.MenuItem(_('G_roup all in tab')) - item.connect('activate', lambda menu_item: self.emit('group_tab')) + item.connect('activate', lambda x: self.emit('group_tab')) menu.append(item) if len(self.terminator.groups) > 0: item = gtk.MenuItem(_('Ungr_oup all in tab')) - item.connect('activate', lambda menu_item: - self.emit('ungroup_tab')) + item.connect('activate', lambda x: self.emit('ungroup_tab')) menu.append(item) if len(self.terminator.groups) > 0: item = gtk.MenuItem(_('Remove all groups')) - item.connect('activate', self.ungroup_all) # FIXME: ungroup_all should be in Terminator() ? + item.connect('activate', lambda x: self.emit('ungroup-all')) menu.append(item) if self.group != None: menu.append(gtk.MenuItem()) item = gtk.MenuItem(_('Close group %s') % self.group) - item.connect('activate', lambda menu_item: + item.connect('activate', lambda x: self.terminator.closegroupedterms(self)) menu.append(item) @@ -323,23 +323,22 @@ class Terminal(gtk.VBox): item = gtk.CheckMenuItem(_('Split to this group')) item.set_active(self.terminator.splittogroup) - item.connect('toggled', lambda menu_item: self.do_splittogroup_toggle()) + item.connect('toggled', lambda x: self.do_splittogroup_toggle()) menu.append(item) item = gtk.CheckMenuItem(_('Autoclean groups')) item.set_active(self.terminator.autocleangroups) - item.connect('toggled', lambda menu_item: - self.do_autocleangroups_toggle()) + item.connect('toggled', lambda x: self.do_autocleangroups_toggle()) menu.append(item) menu.append(gtk.MenuItem()) item = gtk.MenuItem(_('Insert terminal number')) - item.connect('activate', lambda menu_item: self.emit('enumerate', False)) + item.connect('activate', lambda x: self.emit('enumerate', False)) menu.append(item) item = gtk.MenuItem(_('Insert padded terminal number')) - item.connect('activate', lambda menu_item: self.emit('enumerate', True)) + item.connect('activate', lambda x: self.emit('enumerate', True)) menu.append(item) return(menu) From 0912fb8aae6e2296f56468c62fa6fe5f6ec3c3c8 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 6 Sep 2009 22:54:52 +0100 Subject: [PATCH 076/331] fix up a pylint error and migrate some pure functions from terminal.py --- terminatorlib/util.py | 52 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/terminatorlib/util.py b/terminatorlib/util.py index 6bde7b95..b4d8db64 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -18,6 +18,8 @@ import sys import gtk +import os +import pwd # set this to true to enable debugging output DEBUG = True @@ -39,11 +41,57 @@ def gerr(message = None): gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message) dialog.run() -def has_ancestor(widget, type): +def has_ancestor(widget, wtype): """Walk up the family tree of widget to see if any ancestors are of type""" while widget: widget = widget.get_parent() - if isinstance(widget, type): + if isinstance(widget, wtype): return(True) return(False) +def path_lookup(command): + '''Find a command in our path''' + if os.path.isabs(command): + if os.path.isfile(command): + return(command) + else: + return(None) + elif command[:2] == './' and os.path.isfile(command): + dbg('path_lookup: Relative filename %s found in cwd' % command) + return(command) + + try: + paths = os.environ['PATH'].split(':') + if len(paths[0]) == 0: + raise(ValueError) + except (ValueError, NameError): + dbg('path_lookup: PATH not set in environment, using fallbacks') + paths = ['/usr/local/bin', '/usr/bin', '/bin'] + + dbg('path_lookup: Using %d paths: %s', (len(paths), paths)) + + for path in paths: + target = os.path.join(path, command) + if os.path.isfile(target): + dbg('path_lookup: found %s' % target) + return(target) + + dbg('path_lookup: Unable to locate %s' % command) + +def shell_lookup(): + """Find an appropriate shell for the user""" + shells = [os.getenv('SHELL'), pwd.getpwuid(os.getuid())[6], 'bash', + 'zsh', 'tcsh', 'ksh', 'csh', 'sh'] + + for shell in shells: + if shell is None: + continue + elif os.path.isfile(shell): + return(shell) + else: + rshell = path_lookup(shell) + if rshell is not None: + dbg('shell_lookup: Found %s at %s' % (shell, rshell)) + return(rshell) + dbg('shell_lookup: Unable to locate a shell') + From 1d2e96ac72f5891fc96343df1f04656266c182bc Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 6 Sep 2009 22:55:37 +0100 Subject: [PATCH 077/331] tidy up some more lambdas, pylint errors and start fleshing out the context menu --- terminatorlib/terminal.py | 172 ++++++++++++++++++++++++-------------- 1 file changed, 110 insertions(+), 62 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 2591394f..c61cd550 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -3,15 +3,18 @@ # GPL v2 only """terminal.py - classes necessary to provide Terminal widgets""" -import pwd import sys import os import pygtk pygtk.require('2.0') import gtk import gobject +import subprocess +import re -from util import dbg, err, gerr, has_ancestor +from version import APP_NAME +from util import dbg, err, gerr +import util from config import Config from cwd import get_default_cwd from newterminator import Terminator @@ -38,6 +41,10 @@ class Terminal(gtk.VBox): 'group-tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'ungroup-tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'ungroup-all': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'split-horiz': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'split-vert': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'tab-new': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'tab-top-new': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), } TARGET_TYPE_VTE = 8 @@ -52,6 +59,7 @@ class Terminal(gtk.VBox): cwd = None command = None clipboard = None + pid = None matches = None config = None @@ -228,7 +236,7 @@ class Terminal(gtk.VBox): if self.config['exit_action'] == 'restart': self.vte.connect('child-exited', self.spawn_child) elif self.config['exit_action'] in ('close', 'left'): - self.vte.connect('child-exited', self.close_term) + self.vte.connect('child-exited', lambda x: self.emit('close-term')) self.vte.add_events(gtk.gdk.ENTER_NOTIFY_MASK) self.vte.connect('enter_notify_event', @@ -282,7 +290,7 @@ class Terminal(gtk.VBox): item.connect('activate', self.ungroup, self.group) menu.append(item) - if has_ancestor(self, gtk.Notebook): + if util.has_ancestor(self, gtk.Notebook): item = gtk.MenuItem(_('G_roup all in tab')) item.connect('activate', lambda x: self.emit('group_tab')) menu.append(item) @@ -309,7 +317,7 @@ class Terminal(gtk.VBox): groupitem = None - for key,value in {_('Broadcast off'):'off', + for key, value in {_('Broadcast off'):'off', _('Broadcast group'):'group', _('Broadcast all'):'all'}.items(): groupitem = gtk.RadioMenuItem(groupitem, key) @@ -394,14 +402,14 @@ class Terminal(gtk.VBox): self.create_popup_group_menu(widget, event) return(False) - def on_keypress(self, vte, event): + def on_keypress(self, widget, event): """Handler for keyboard events""" pass - def on_buttonpress(self, vte, event): + def on_buttonpress(self, widget, event): """Handler for mouse events""" # Any button event should grab focus - self.vte.grab_focus() + widget.grab_focus() if event.button == 1: # Ctrl+leftclick on a URL should open it @@ -416,15 +424,102 @@ class Terminal(gtk.VBox): elif event.button == 3: # rightclick should display a context menu if Ctrl is not pressed if event.state & gtk.gdk.CONTROL_MASK == 0: - self.popup_menu(self.vte, event) + self.popup_menu(widget, event) return(True) return(False) def popup_menu(self, widget, event=None): """Display the context menu""" + menu = gtk.Menu() + url = None + button = None + time = None - pass + if event: + url = self.check_for_url(event) + button = event.button + time = event.time + + if url: + if url[1] == self.matches['email']: + nameopen = _('_Send email to...') + namecopy = _('_Copy email address') + elif url[1] == self.matches['voip']: + nameopen = _('Ca_ll VoIP address') + namecopy = _('_Copy VoIP address') + else: + nameopen = _('_Open link') + namecopy = _('_Copy address') + + icon = gtk.image_new_from_stock(gtk.STOCK_JUMP_TO, + gtk.ICON_SIZE_MENU) + item = gtk.ImageMenuItem(nameopen) + item.set_property('image', icon) + item.connect('activate', lambda x: self.open_url(url, True)) + menu.append(item) + + item = gtk.MenuItem(namecopy) + item.connect('activate', lambda x: self.clipboard.set_text(url[0])) + menu.append(item) + + menu.append(gtk.MenuItem()) + + item = gtk.ImageMenuItem(gtk.STOCK_COPY) + item.connect('activate', lambda x: self.vte.copy_clipboard()) + item.set_sensitive(self.vte.get_has_selection()) + menu.append(item) + + item = gtk.ImageMenuItem(gtk.STOCK_PASTE) + item.connect('activate', lambda x: self.paste_clipboard()) + menu.append(item) + + menu.append(gtk.MenuItem()) + + #FIXME: These split/tab items should be conditional on not being zoomed + if True: + item = gtk.ImageMenuItem('Split H_orizontally') + image = gtk.Image() + image.set_from_icon_name(APP_NAME + '_horiz', gtk.ICON_SIZE_MENU) + item.set_image(image) + if hasattr(item, 'set_always_show_image'): + item.set_always_show_image(True) + item.connect('activate', lambda x: self.emit('split-horiz')) + menu.append(item) + + item = gtk.ImageMenuItem('Split V_ertically') + image = gtk.Image() + image.set_from_icon_name(APP_NAME + '_vert', gtk.ICON_SIZE_MENU) + item.set_image(image) + if hasattr(item, 'set_always_show_image'): + item.set_always_show_image(True) + item.connect('activate', lambda x: self.emit('split-vert')) + menu.append(item) + + item = gtk.MenuItem(_('Open _Tab')) + item.connect('activate', lambda x: self.emit('tab-new')) + menu.append(item) + + if self.config['extreme_tabs']: + item = gtk.MenuItem(_('Open top level tab')) + item.connect('activate', lambda x: self.emit('tab-top-new')) + menu.append(item) + + menu.append(gtk.MenuItem()) + + item = gtk.ImageMenuItem(gtk.STOCK_CLOSE) + item.connect('activate', lambda x: self.emit('close-term')) + menu.append(item) + + menu.append(gtk.MenuItem()) + + # FIXME: Add menu items for (un)zoom, (un)maximise, (un)showing + # scrollbar, (un)showing titlebar, profile editing, encodings + + menu.show_all() + menu.popup(None, None, None, button, time) + + return(True) def on_drag_begin(self, widget, drag_context, data): pass @@ -440,16 +535,16 @@ class Terminal(gtk.VBox): info, time, data): pass - def on_vte_title_change(self, vte): + def on_vte_title_change(self, widget): self.emit('title-change', self.get_window_title()) - def on_vte_focus(self, vte): + def on_vte_focus(self, widget): pass - def on_vte_focus_out(self, vte, event): + def on_vte_focus_out(self, widget, event): pass - def on_vte_focus_in(self, vte, event): + def on_vte_focus_in(self, widget, event): pass def on_edit_done(self, widget): @@ -467,9 +562,6 @@ class Terminal(gtk.VBox): def on_vte_notify_enter(self, term, event): pass - def close_term(self, widget): - self.emit('close-term') - def hide_titlebar(self): self.titlebar.hide() @@ -486,7 +578,7 @@ class Terminal(gtk.VBox): if self.config['use_custom_command']: command = self.config['custom_command'] - shell = self.shell_lookup() + shell = util.shell_lookup() if self.config['login_shell']: args.insert(0, "-%s" % shell) @@ -514,49 +606,6 @@ class Terminal(gtk.VBox): self.vte.feed(_('Unable to start shell:') + shell) return(-1) - def shell_lookup(self): - """Find an appropriate shell for the user""" - shells = [os.getenv('SHELL'), pwd.getpwuid(os.getuid())[6], 'bash', - 'zsh', 'tcsh', 'ksh', 'csh', 'sh'] - - for shell in shells: - if shell is None: continue - elif os.path.isfile(shell): - return(shell) - else: - rshell = self.path_lookup(shell) - if rshell is not None: - dbg('shell_lookup: Found %s at %s' % (shell, rshell)) - return(rshell) - dbg('shell_lookup: Unable to locate a shell') - - def path_lookup(self, command): - if os.path.isabs(command): - if os.path.isfile(command): - return(command) - else: - return(None) - elif command[:2] == './' and os.path.isfile(command): - dbg('path_lookup: Relative filename %s found in cwd' % command) - return(command) - - try: - paths = os.environ['PATH'].split(':') - if len(paths[0]) == 0: raise(ValueError) - except (ValueError, NameError): - dbg('path_lookup: PATH not set in environment, using fallbacks') - paths = ['/usr/local/bin', '/usr/bin', '/bin'] - - dbg('path_lookup: Using %d paths: %s', (len(paths), paths)) - - for path in paths: - target = os.path.join(path, command) - if os.path.isfile(target): - dbg('path_lookup: found %s' % target) - return(target) - - dbg('path_lookup: Unable to locate %s' % command) - def check_for_url(self, event): """Check if the mouse is over a URL""" return (self.vte.match_check(int(event.x / self.vte.get_char_width()), @@ -593,7 +642,6 @@ class Terminal(gtk.VBox): self.terminator.url_show(url) except: dbg('open_url: url_show failed. Giving up') - pass def paste_clipboard(self, primary=False): """Paste one of the two clipboards""" From d5ff98211c6109a3a3c1738beaaeda9cc7d99e2b Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 6 Sep 2009 23:52:58 +0100 Subject: [PATCH 078/331] Swap out some noop code for pylint suppression --- terminatorlib/editablelabel.py | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/terminatorlib/editablelabel.py b/terminatorlib/editablelabel.py index a0abbcc4..db942bf8 100644 --- a/terminatorlib/editablelabel.py +++ b/terminatorlib/editablelabel.py @@ -17,12 +17,13 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor # , Boston, MA 02110-1301 USA -# pylint: disable-msg=W0212 """ Editable Label class""" import gtk import gobject class EditableLabel(gtk.EventBox): + # pylint: disable-msg=W0212 + # pylint: disable-msg=R0904 """ An eventbox that partialy emulate a gtk.Label On double-click, the label is editable, entering an empty will revert back to automatic text @@ -62,9 +63,10 @@ class EditableLabel(gtk.EventBox): def get_text( self ): """get the text from the label""" - return self._label.get_text() + return(self._label.get_text()) def _on_click_text(self, widget, event): + # pylint: disable-msg=W0613 """event handling text edition""" if event.type == gtk.gdk._2BUTTON_PRESS: self.remove (self._label) @@ -80,12 +82,11 @@ class EditableLabel(gtk.EventBox): self._on_entry_keypress) self._entry_handler_id.append(sig) self._entry.grab_focus () - return True - # make pylint happy - if 1 or widget or event: - return False + return(True) + return(False) def _entry_to_label (self, widget, event): + # pylint: disable-msg=W0613 """replace gtk.Entry by the gtk.Label""" if self._entry and self._entry in self.get_children(): #disconnect signals to avoid segfault :s @@ -98,12 +99,11 @@ class EditableLabel(gtk.EventBox): self._entry = None self.show_all () self.emit('edit-done') - return True - #make pylint happy - if 1 or widget or event: - return False + return(True) + return(False) def _on_entry_activated (self, widget): + # pylint: disable-msg=W0613 """get the text entered in gtk.Entry""" entry = self._entry.get_text () label = self._label.get_text () @@ -115,17 +115,11 @@ class EditableLabel(gtk.EventBox): self._label.set_text (entry) self._entry_to_label (None, None) - # make pylint happy - if 1 or widget: - return - def _on_entry_keypress (self, widget, event): + # pylint: disable-msg=W0613 """handle keypressed in gtk.Entry""" key = gtk.gdk.keyval_name (event.keyval) if key == 'Escape': self._entry_to_label (None, None) - # make pylint happy - if 1 or widget or event: - return gobject.type_register(EditableLabel) From a31c0ef411ad9e6226b51ec8d8fd5b9b4f1e588c Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 6 Sep 2009 23:53:21 +0100 Subject: [PATCH 079/331] If something isn't used outside one function, don't make it be a class variable --- terminatorlib/titlebar.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index 38f32c04..3176c2f9 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -20,9 +20,7 @@ class Titlebar(gtk.EventBox): termtext = None sizetext = None label = None - hbox = None ebox = None - grouphbox = None groupicon = None grouplabel = None @@ -41,7 +39,7 @@ class Titlebar(gtk.EventBox): self.label = EditableLabel() self.label.connect('edit-done', self.on_edit_done) self.ebox = gtk.EventBox() - self.grouphbox = gtk.HBox() + grouphbox = gtk.HBox() self.grouplabel = gtk.Label() self.groupicon = gtk.Image() @@ -55,17 +53,17 @@ class Titlebar(gtk.EventBox): self.set_from_icon_name('_active_broadcast_%s' % icon_name, gtk.ICON_SIZE_MENU) - self.grouphbox.pack_start(self.groupicon, False, True, 2) - self.grouphbox.pack_start(self.grouplabel, False, True, 2) - self.ebox.add(self.grouphbox) + grouphbox.pack_start(self.groupicon, False, True, 2) + grouphbox.pack_start(self.grouplabel, False, True, 2) + self.ebox.add(grouphbox) self.ebox.show_all() - self.hbox = gtk.HBox() - self.hbox.pack_start(self.ebox, False, True, 0) - self.hbox.pack_start(gtk.VSeparator(), False, True, 0) - self.hbox.pack_start(self.label, True, True) + hbox = gtk.HBox() + hbox.pack_start(self.ebox, False, True, 0) + hbox.pack_start(gtk.VSeparator(), False, True, 0) + hbox.pack_start(self.label, True, True) - self.add(self.hbox) + self.add(hbox) self.show_all() self.connect('button-press-event', self.on_clicked) From 2aae5da8f1f5025f6782afe1c47adff393581357 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 6 Sep 2009 23:53:48 +0100 Subject: [PATCH 080/331] lose a function to re-emit a signal, and an unnecessary invocation thereof --- terminatorlib/terminal.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index c61cd550..1053b917 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -225,8 +225,8 @@ class Terminal(gtk.VBox): self.vte.connect('composited-changed', self.on_composited_changed) - self.vte.connect('window-title-changed', - self.on_vte_title_change) + self.vte.connect('window-title-changed', lambda x: + self.emit('title-change', self.get_window_title())) self.vte.connect('grab-focus', self.on_vte_focus) self.vte.connect('focus-out-event', self.on_vte_focus_out) self.vte.connect('focus-in-event', self.on_vte_focus_in) @@ -535,9 +535,6 @@ class Terminal(gtk.VBox): info, time, data): pass - def on_vte_title_change(self, widget): - self.emit('title-change', self.get_window_title()) - def on_vte_focus(self, widget): pass @@ -599,7 +596,6 @@ class Terminal(gtk.VBox): logutmp=update_records, directory=self.cwd) self.command = shell - self.on_vte_title_change(self.vte) self.titlebar.update() if self.pid == -1: From 7ea1e18f20399fa630e25e108f9ea2407d544c6e Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 1 Oct 2009 22:15:50 +0100 Subject: [PATCH 081/331] Tiny cleanup --- terminatorlib/terminal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 1053b917..685e36b4 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -87,8 +87,8 @@ class Terminal(gtk.VBox): self.vte = vte.Terminal() self.vte.set_size(80, 24) self.vte._expose_data = None - if not hasattr(self.vte, "set_opacity") or not hasattr(self.vte, - "is_composited"): + if not hasattr(self.vte, "set_opacity") or \ + not hasattr(self.vte, "is_composited"): self.composite_support = False self.vte.show() From 414e7f073194f805f9313810abb8391c98052d87 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 5 Oct 2009 22:15:22 +0100 Subject: [PATCH 082/331] Add a mechanism to de-register a Terminal() that's being destroyed --- terminatorlib/container.py | 1 + terminatorlib/newterminator.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 39a40b5a..d43d615e 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -84,6 +84,7 @@ class Container(object): if not self.remove(widget): return(False) + self.terminator.deregister_terminal(widget) self.emit('need_group_hoover') return(True) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index e9466b71..bd63069e 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -44,6 +44,11 @@ class Terminator(Borg): self.terminals.append(terminal) + def deregister_terminal(self, terminal): + """De-register a terminal widget""" + + self.terminals.remove(terminal) + def reconfigure_terminals(self): """Tell all terminals to update their configuration""" From 209caf37458dd26f612c93ff306359e040e1046c Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 5 Oct 2009 22:16:28 +0100 Subject: [PATCH 083/331] start some basic splitting in Window() and try to be slightly smarter about handling the terminal's signals --- terminatorlib/window.py | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index fd654f14..6bf6bf69 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -12,6 +12,7 @@ from util import dbg, err from version import APP_NAME from container import Container from newterminator import Terminator +from terminal import Terminal try: import deskbar.core.keybinder as bindkey @@ -29,10 +30,12 @@ class Window(Container, gtk.Window): ismaximised = None hidebound = None hidefunc = None + cnxids = None def __init__(self): """Class initialiser""" self.terminator = Terminator() + self.cnxids = [] Container.__init__(self) gtk.Window.__init__(self) @@ -160,14 +163,40 @@ class Window(Container, gtk.Window): def add(self, widget): """Add a widget to the window by way of gtk.Window.add()""" - widget.connect('close-term', self.closeterm) - widget.connect('title-change', self.title.set_title) + if isinstance(widget, Terminal): + self.cnxids.append(widget.connect('close-term', self.closeterm)) + self.cnxids.append(widget.connect('title-change', + self.title.set_title)) + self.cnxids.append(widget.connect('split-horiz', self.split_horiz)) + self.cnxids.append(widget.connect('split-vert', self.split_vert)) gtk.Window.add(self, widget) def remove(self, widget): """Remove our child widget by way of gtk.Window.remove()""" gtk.Window.remove(self, widget) - self.destroy() + for cnxid in self.cnxids: + widget.disconnect(cnxid) + self.cnxids = [] + + def split_axis(self, widget, vertical=True): + """Split the window""" + # FIXME: this .remove isn't good enough, what about signal handlers? + self.remove(widget) + + # FIXME: we should be creating proper containers, not these gtk widgets + if vertical: + container = gtk.VPaned() + else: + container = gtk.HPaned() + + sibling = Terminal() + self.terminator.register_terminal(sibling) + + container.pack1(widget, True, True) + container.pack2(sibling, True, True) + container.show() + + self.add(container) class WindowTitle(object): """Class to handle the setting of the window title""" From d9e9cf364a0ca79194624fd34898fe827cc8a482 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 7 Oct 2009 00:08:13 +0100 Subject: [PATCH 084/331] ditch extreme_tabs, it's a terrible feature. Flesh out the terminal context menu some more, including figuring out how we will track whether a window is in a zoomed state - gobject properties --- terminatorlib/config.py | 1 - terminatorlib/terminal.py | 77 +++++++++++++++++++++++++++++++++------ terminatorlib/window.py | 2 + 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 5b193d9e..2a078637 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -68,7 +68,6 @@ DEFAULTS = { 'ignore_hosts' : ['localhost','127.0.0.0/8','*.local'], 'encoding' : 'UTF-8', 'active_encodings' : ['UTF-8', 'ISO-8859-1'], - 'extreme_tabs' : False, 'fullscreen' : False, 'borderless' : False, 'maximise' : False, diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 685e36b4..6d179f82 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -52,6 +52,7 @@ class Terminal(gtk.VBox): terminator = None vte = None terminalbox = None + scrollbar = None titlebar = None searchbar = None @@ -122,11 +123,11 @@ class Terminal(gtk.VBox): """Create a GtkHBox containing the terminal and a scrollbar""" terminalbox = gtk.HBox() - scrollbar = gtk.VScrollbar(self.vte.get_adjustment()) + self.scrollbar = gtk.VScrollbar(self.vte.get_adjustment()) position = self.config['scrollbar_position'] if position not in ('hidden', 'disabled'): - scrollbar.show() + self.scrollbar.show() if position == 'left': func = terminalbox.pack_end @@ -134,7 +135,7 @@ class Terminal(gtk.VBox): func = terminalbox.pack_start func(self.vte) - func(scrollbar, False) + func(self.scrollbar, False) terminalbox.show() return(terminalbox) @@ -476,8 +477,7 @@ class Terminal(gtk.VBox): menu.append(gtk.MenuItem()) - #FIXME: These split/tab items should be conditional on not being zoomed - if True: + if not self.is_zoomed(): item = gtk.ImageMenuItem('Split H_orizontally') image = gtk.Image() image.set_from_icon_name(APP_NAME + '_horiz', gtk.ICON_SIZE_MENU) @@ -500,11 +500,6 @@ class Terminal(gtk.VBox): item.connect('activate', lambda x: self.emit('tab-new')) menu.append(item) - if self.config['extreme_tabs']: - item = gtk.MenuItem(_('Open top level tab')) - item.connect('activate', lambda x: self.emit('tab-top-new')) - menu.append(item) - menu.append(gtk.MenuItem()) item = gtk.ImageMenuItem(gtk.STOCK_CLOSE) @@ -513,7 +508,30 @@ class Terminal(gtk.VBox): menu.append(gtk.MenuItem()) - # FIXME: Add menu items for (un)zoom, (un)maximise, (un)showing + if not self.is_zoomed(): + item = gtk.MenuItem(_('_Zoom terminal')) + item.connect('activate', self.zoom) + menu.append(item) + + item = gtk.MenuItem(_('Ma_ximise terminal')) + item.connect('activate', self.maximise) + menu.append(item) + + menu.append(gtk.MenuItem()) + + item = gtk.CheckMenuItem(_('Show _scrollbar')) + item.set_active(self.scrollbar.get_property('visible')) + item.connect('toggled', lambda x: self.do_scrollbar_toggle()) + menu.append(item) + + item = gtk.CheckMenuItem(_('Show _titlebar')) + item.set_active(self.titlebar.get_property('visible')) + item.connect('toggled', lambda x: self.do_title_toggle()) + if self.group: + item.set_sensitive(False) + menu.append(item) + + # FIXME: Add menu items for (un)showing # scrollbar, (un)showing titlebar, profile editing, encodings menu.show_all() @@ -521,6 +539,18 @@ class Terminal(gtk.VBox): return(True) + def do_scrollbar_toggle(self): + self.toggle_widget_visibility(self.scrollbar) + + def do_title_toggle(self): + self.toggle_widget_visibility(self.titlebar) + + def toggle_widget_visibility(self, widget): + if widget.get_property('visible'): + widget.hide() + else: + widget.show() + def on_drag_begin(self, widget, drag_context, data): pass @@ -565,6 +595,31 @@ class Terminal(gtk.VBox): def show_titlebar(self): self.titlebar.show() + def is_zoomed(self): + """Determine if we are a zoomed terminal""" + widget = self.get_parent() + while True: + tmp = widget.get_parent() + if not tmp: + break + else: + widget = tmp + + try: + prop = widget.get_property('term-zoomed') + except TypeError: + prop = False + + return(prop) + + def zoom(self): + """Zoom ourself to fill the window""" + pass + + def maximise(self): + """Maximise ourself to fill the window""" + pass + def spawn_child(self, widget=None): update_records = self.config['update_records'] login = self.config['login_shell'] diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 6bf6bf69..a2598a64 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -32,6 +32,8 @@ class Window(Container, gtk.Window): hidefunc = None cnxids = None + term_zoomed = gobject.property(type=bool, default=False) + def __init__(self): """Class initialiser""" self.terminator = Terminator() From f0d7f2a9ca865cd2de682366112177552a07865d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 8 Oct 2009 20:27:49 +0100 Subject: [PATCH 085/331] refactor the popup menu out because it's so annoyingly long --- terminatorlib/terminal.py | 109 +--------------------- terminatorlib/terminal_popup_menu.py | 130 +++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 106 deletions(-) create mode 100755 terminatorlib/terminal_popup_menu.py diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 6d179f82..8f4ca48b 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -19,6 +19,7 @@ from config import Config from cwd import get_default_cwd from newterminator import Terminator from titlebar import Titlebar +from terminal_popup_menu import TerminalPopupMenu from searchbar import Searchbar from translation import _ @@ -432,112 +433,8 @@ class Terminal(gtk.VBox): def popup_menu(self, widget, event=None): """Display the context menu""" - menu = gtk.Menu() - url = None - button = None - time = None - - if event: - url = self.check_for_url(event) - button = event.button - time = event.time - - if url: - if url[1] == self.matches['email']: - nameopen = _('_Send email to...') - namecopy = _('_Copy email address') - elif url[1] == self.matches['voip']: - nameopen = _('Ca_ll VoIP address') - namecopy = _('_Copy VoIP address') - else: - nameopen = _('_Open link') - namecopy = _('_Copy address') - - icon = gtk.image_new_from_stock(gtk.STOCK_JUMP_TO, - gtk.ICON_SIZE_MENU) - item = gtk.ImageMenuItem(nameopen) - item.set_property('image', icon) - item.connect('activate', lambda x: self.open_url(url, True)) - menu.append(item) - - item = gtk.MenuItem(namecopy) - item.connect('activate', lambda x: self.clipboard.set_text(url[0])) - menu.append(item) - - menu.append(gtk.MenuItem()) - - item = gtk.ImageMenuItem(gtk.STOCK_COPY) - item.connect('activate', lambda x: self.vte.copy_clipboard()) - item.set_sensitive(self.vte.get_has_selection()) - menu.append(item) - - item = gtk.ImageMenuItem(gtk.STOCK_PASTE) - item.connect('activate', lambda x: self.paste_clipboard()) - menu.append(item) - - menu.append(gtk.MenuItem()) - - if not self.is_zoomed(): - item = gtk.ImageMenuItem('Split H_orizontally') - image = gtk.Image() - image.set_from_icon_name(APP_NAME + '_horiz', gtk.ICON_SIZE_MENU) - item.set_image(image) - if hasattr(item, 'set_always_show_image'): - item.set_always_show_image(True) - item.connect('activate', lambda x: self.emit('split-horiz')) - menu.append(item) - - item = gtk.ImageMenuItem('Split V_ertically') - image = gtk.Image() - image.set_from_icon_name(APP_NAME + '_vert', gtk.ICON_SIZE_MENU) - item.set_image(image) - if hasattr(item, 'set_always_show_image'): - item.set_always_show_image(True) - item.connect('activate', lambda x: self.emit('split-vert')) - menu.append(item) - - item = gtk.MenuItem(_('Open _Tab')) - item.connect('activate', lambda x: self.emit('tab-new')) - menu.append(item) - - menu.append(gtk.MenuItem()) - - item = gtk.ImageMenuItem(gtk.STOCK_CLOSE) - item.connect('activate', lambda x: self.emit('close-term')) - menu.append(item) - - menu.append(gtk.MenuItem()) - - if not self.is_zoomed(): - item = gtk.MenuItem(_('_Zoom terminal')) - item.connect('activate', self.zoom) - menu.append(item) - - item = gtk.MenuItem(_('Ma_ximise terminal')) - item.connect('activate', self.maximise) - menu.append(item) - - menu.append(gtk.MenuItem()) - - item = gtk.CheckMenuItem(_('Show _scrollbar')) - item.set_active(self.scrollbar.get_property('visible')) - item.connect('toggled', lambda x: self.do_scrollbar_toggle()) - menu.append(item) - - item = gtk.CheckMenuItem(_('Show _titlebar')) - item.set_active(self.titlebar.get_property('visible')) - item.connect('toggled', lambda x: self.do_title_toggle()) - if self.group: - item.set_sensitive(False) - menu.append(item) - - # FIXME: Add menu items for (un)showing - # scrollbar, (un)showing titlebar, profile editing, encodings - - menu.show_all() - menu.popup(None, None, None, button, time) - - return(True) + menu = TerminalPopupMenu(self) + menu.show(widget, event) def do_scrollbar_toggle(self): self.toggle_widget_visibility(self.scrollbar) diff --git a/terminatorlib/terminal_popup_menu.py b/terminatorlib/terminal_popup_menu.py new file mode 100755 index 00000000..0a83f701 --- /dev/null +++ b/terminatorlib/terminal_popup_menu.py @@ -0,0 +1,130 @@ +#!/usr/bin/python +# Terminator by Chris Jones +# GPL v2 only +"""terminal_popup_menu.py - classes necessary to provide a terminal context +menu""" + +import gtk + +from version import APP_NAME +from translation import _ + +class TerminalPopupMenu(object): + """Class implementing the Terminal context menu""" + terminal = None + + def __init__(self, terminal): + """Class initialiser""" + self.terminal = terminal + + def show(self, widget, event=None): + """Display the context menu""" + terminal = self.terminal + + menu = gtk.Menu() + url = None + button = None + time = None + + if event: + url = terminal.check_for_url(event) + button = event.button + time = event.time + + if url: + if url[1] == terminal.matches['email']: + nameopen = _('_Send email to...') + namecopy = _('_Copy email address') + elif url[1] == terminal.matches['voip']: + nameopen = _('Ca_ll VoIP address') + namecopy = _('_Copy VoIP address') + else: + nameopen = _('_Open link') + namecopy = _('_Copy address') + + icon = gtk.image_new_from_stock(gtk.STOCK_JUMP_TO, + gtk.ICON_SIZE_MENU) + item = gtk.ImageMenuItem(nameopen) + item.set_property('image', icon) + item.connect('activate', lambda x: terminal.open_url(url, True)) + menu.append(item) + + item = gtk.MenuItem(namecopy) + item.connect('activate', + lambda x: terminal.clipboard.set_text(url[0])) + menu.append(item) + + menu.append(gtk.MenuItem()) + + item = gtk.ImageMenuItem(gtk.STOCK_COPY) + item.connect('activate', lambda x: terminal.vte.copy_clipboard()) + item.set_sensitive(terminal.vte.get_has_selection()) + menu.append(item) + + item = gtk.ImageMenuItem(gtk.STOCK_PASTE) + item.connect('activate', lambda x: terminal.paste_clipboard()) + menu.append(item) + + menu.append(gtk.MenuItem()) + + if not terminal.is_zoomed(): + item = gtk.ImageMenuItem('Split H_orizontally') + image = gtk.Image() + image.set_from_icon_name(APP_NAME + '_horiz', gtk.ICON_SIZE_MENU) + item.set_image(image) + if hasattr(item, 'set_always_show_image'): + item.set_always_show_image(True) + item.connect('activate', lambda x: terminal.emit('split-horiz')) + menu.append(item) + + item = gtk.ImageMenuItem('Split V_ertically') + image = gtk.Image() + image.set_from_icon_name(APP_NAME + '_vert', gtk.ICON_SIZE_MENU) + item.set_image(image) + if hasattr(item, 'set_always_show_image'): + item.set_always_show_image(True) + item.connect('activate', lambda x: terminal.emit('split-vert')) + menu.append(item) + + item = gtk.MenuItem(_('Open _Tab')) + item.connect('activate', lambda x: terminal.emit('tab-new')) + menu.append(item) + + menu.append(gtk.MenuItem()) + + item = gtk.ImageMenuItem(gtk.STOCK_CLOSE) + item.connect('activate', lambda x: terminal.emit('close-term')) + menu.append(item) + + menu.append(gtk.MenuItem()) + + if not terminal.is_zoomed(): + item = gtk.MenuItem(_('_Zoom terminal')) + item.connect('activate', terminal.zoom) + menu.append(item) + + item = gtk.MenuItem(_('Ma_ximise terminal')) + item.connect('activate', terminal.maximise) + menu.append(item) + + menu.append(gtk.MenuItem()) + + item = gtk.CheckMenuItem(_('Show _scrollbar')) + item.set_active(terminal.scrollbar.get_property('visible')) + item.connect('toggled', lambda x: terminal.do_scrollbar_toggle()) + menu.append(item) + + item = gtk.CheckMenuItem(_('Show _titlebar')) + item.set_active(terminal.titlebar.get_property('visible')) + item.connect('toggled', lambda x: terminal.do_title_toggle()) + if terminal.group: + item.set_sensitive(False) + menu.append(item) + + # FIXME: Add menu items for profile editing, encodings + + menu.show_all() + menu.popup(None, None, None, button, time) + + return(True) + From 3d78428aaeb545bf773c55bc4c96bf0c8a7729af Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 8 Oct 2009 21:27:00 +0100 Subject: [PATCH 086/331] now make the encoding submenu work --- terminatorlib/terminal.py | 8 ++++ terminatorlib/terminal_popup_menu.py | 72 +++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 8f4ca48b..073482ec 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -448,6 +448,14 @@ class Terminal(gtk.VBox): else: widget.show() + def on_encoding_change(self, widget, encoding): + """Handle the encoding changing""" + current = self.vte.get_encoding() + if current != encoding: + dbg('on_encoding_change: setting encoding to: %s' % encoding) + self.custom_encoding = not (encoding == self.config['encoding']) + self.vte.set_encoding(encoding) + def on_drag_begin(self, widget, drag_context, data): pass diff --git a/terminatorlib/terminal_popup_menu.py b/terminatorlib/terminal_popup_menu.py index 0a83f701..4e92f3c2 100755 --- a/terminatorlib/terminal_popup_menu.py +++ b/terminatorlib/terminal_popup_menu.py @@ -8,6 +8,7 @@ import gtk from version import APP_NAME from translation import _ +from encoding import TerminatorEncoding class TerminalPopupMenu(object): """Class implementing the Terminal context menu""" @@ -121,10 +122,79 @@ class TerminalPopupMenu(object): item.set_sensitive(False) menu.append(item) - # FIXME: Add menu items for profile editing, encodings + item = gtk.MenuItem(_('Ed_it profile')) + item.connect('activate', lambda x: + terminal.terminator.edit_profile(terminal)) + menu.append(item) + + self.add_encoding_items(menu) menu.show_all() menu.popup(None, None, None, button, time) return(True) + + def add_encoding_items(self, menu): + """Add the encoding list to the menu""" + terminal = self.terminal + active_encodings = terminal.config['active_encodings'] + item = gtk.MenuItem (_("Encodings")) + menu.append (item) + submenu = gtk.Menu () + item.set_submenu (submenu) + encodings = TerminatorEncoding ().get_list () + encodings.sort (lambda x, y: cmp (x[2].lower (), y[2].lower ())) + + current_encoding = terminal.vte.get_encoding () + group = None + + if current_encoding not in active_encodings: + active_encodings.insert (0, _(current_encoding)) + + for encoding in active_encodings: + if encoding == terminal.default_encoding: + extratext = " (%s)" % _("Default") + elif encoding == current_encoding and terminal.custom_encoding == True: + extratext = " (%s)" % _("User defined") + else: + extratext = "" + + radioitem = gtk.RadioMenuItem (group, _(encoding) + extratext) + + if encoding == current_encoding: + radioitem.set_active (True) + + if group is None: + group = radioitem + + radioitem.connect ('activate', terminal.on_encoding_change, encoding) + submenu.append (radioitem) + + item = gtk.MenuItem (_("Other Encodings")) + submenu.append (item) + #second level + + submenu = gtk.Menu () + item.set_submenu (submenu) + group = None + + for encoding in encodings: + if encoding[1] in active_encodings: + continue + + if encoding[1] is None: + label = "%s %s"%(encoding[2], terminal.vte.get_encoding ()) + else: + label = "%s %s"%(encoding[2], encoding[1]) + + radioitem = gtk.RadioMenuItem (group, label) + if group is None: + group = radioitem + + if encoding[1] == current_encoding: + radioitem.set_active (True) + + radioitem.connect ('activate', terminal.on_encoding_change, encoding[1]) + submenu.append (radioitem) + From 9d6b961fc31be29a33130369cd112dd385cf81a2 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 8 Oct 2009 21:29:45 +0100 Subject: [PATCH 087/331] undo an indenting fail --- terminatorlib/terminal_popup_menu.py | 46 ++++++++++++++-------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/terminatorlib/terminal_popup_menu.py b/terminatorlib/terminal_popup_menu.py index 4e92f3c2..2a191ac1 100755 --- a/terminatorlib/terminal_popup_menu.py +++ b/terminatorlib/terminal_popup_menu.py @@ -171,30 +171,30 @@ class TerminalPopupMenu(object): radioitem.connect ('activate', terminal.on_encoding_change, encoding) submenu.append (radioitem) - item = gtk.MenuItem (_("Other Encodings")) - submenu.append (item) - #second level + item = gtk.MenuItem (_("Other Encodings")) + submenu.append (item) + #second level - submenu = gtk.Menu () - item.set_submenu (submenu) - group = None + submenu = gtk.Menu () + item.set_submenu (submenu) + group = None - for encoding in encodings: - if encoding[1] in active_encodings: - continue - - if encoding[1] is None: - label = "%s %s"%(encoding[2], terminal.vte.get_encoding ()) - else: - label = "%s %s"%(encoding[2], encoding[1]) - - radioitem = gtk.RadioMenuItem (group, label) - if group is None: - group = radioitem - - if encoding[1] == current_encoding: - radioitem.set_active (True) + for encoding in encodings: + if encoding[1] in active_encodings: + continue - radioitem.connect ('activate', terminal.on_encoding_change, encoding[1]) - submenu.append (radioitem) + if encoding[1] is None: + label = "%s %s"%(encoding[2], terminal.vte.get_encoding ()) + else: + label = "%s %s"%(encoding[2], encoding[1]) + + radioitem = gtk.RadioMenuItem (group, label) + if group is None: + group = radioitem + + if encoding[1] == current_encoding: + radioitem.set_active (True) + + radioitem.connect ('activate', terminal.on_encoding_change, encoding[1]) + submenu.append (radioitem) From 49f374e3b2b4795aeda0ae682c8142b0ab01621f Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 9 Oct 2009 00:22:01 +0100 Subject: [PATCH 088/331] hook up focus changes to Terminator so it can update all the titlebars --- terminatorlib/newterminator.py | 6 ++++++ terminatorlib/terminal.py | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index bd63069e..c7a7fced 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -105,4 +105,10 @@ class Terminator(Borg): def ungroup_tab(self, widget): """Ungroup all the terminals in a tab""" pass + + def focus_changed(self, widget): + """We just moved focus to a new terminal""" + for terminal in self.terminals: + terminal.titlebar.update() + return # vim: set expandtab ts=4 sw=4: diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 073482ec..8893b467 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -46,6 +46,7 @@ class Terminal(gtk.VBox): 'split-vert': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'tab-new': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'tab-top-new': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'focus-in': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), } TARGET_TYPE_VTE = 8 @@ -78,6 +79,7 @@ class Terminal(gtk.VBox): self.connect('enumerate', self.terminator.do_enumerate) self.connect('group-tab', self.terminator.group_tab) self.connect('ungroup-tab', self.terminator.ungroup_tab) + self.connect('focus-in', self.terminator.focus_changed) self.matches = {} @@ -230,7 +232,6 @@ class Terminal(gtk.VBox): self.vte.connect('window-title-changed', lambda x: self.emit('title-change', self.get_window_title())) self.vte.connect('grab-focus', self.on_vte_focus) - self.vte.connect('focus-out-event', self.on_vte_focus_out) self.vte.connect('focus-in-event', self.on_vte_focus_in) self.vte.connect('resize-window', self.on_resize_window) self.vte.connect('size-allocate', self.on_vte_size_allocate) @@ -477,6 +478,7 @@ class Terminal(gtk.VBox): pass def on_vte_focus_in(self, widget, event): + self.emit('focus-in') pass def on_edit_done(self, widget): From 7605957efaf788fba0b73c576ff45956b4ad18af Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 9 Oct 2009 00:24:58 +0100 Subject: [PATCH 089/331] pylint fix --- terminatorlib/container.py | 1 + 1 file changed, 1 insertion(+) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index d43d615e..cd01c609 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -12,6 +12,7 @@ from util import dbg class Container(object): """Base class for Terminator Containers""" + terminator = None immutable = None children = None config = None From c1c81d3a91980383c4586bc3bd7282d6054408a7 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 9 Oct 2009 00:30:03 +0100 Subject: [PATCH 090/331] pylint fixes --- terminatorlib/encoding.py | 167 +++++++++++++++++---------------- terminatorlib/newterminator.py | 10 +- 2 files changed, 89 insertions(+), 88 deletions(-) diff --git a/terminatorlib/encoding.py b/terminatorlib/encoding.py index 403ac8b8..6ca8d146 100644 --- a/terminatorlib/encoding.py +++ b/terminatorlib/encoding.py @@ -26,92 +26,93 @@ This list is taken from gnome-terminal's src/terminal-encoding.c from translation import _ class TerminatorEncoding: - """Class to store encoding details""" + """Class to store encoding details""" # The commented out entries below are so marked because gnome-terminal has done # the same. - encodings = [ - [True, None, _("Current Locale")], - [False, "ISO-8859-1", _("Western")], - [False, "ISO-8859-2", _("Central European")], - [False, "ISO-8859-3", _("South European") ], - [False, "ISO-8859-4", _("Baltic") ], - [False, "ISO-8859-5", _("Cyrillic") ], - [False, "ISO-8859-6", _("Arabic") ], - [False, "ISO-8859-7", _("Greek") ], - [False, "ISO-8859-8", _("Hebrew Visual") ], - [False, "ISO-8859-8-I", _("Hebrew") ], - [False, "ISO-8859-9", _("Turkish") ], - [False, "ISO-8859-10", _("Nordic") ], - [False, "ISO-8859-13", _("Baltic") ], - [False, "ISO-8859-14", _("Celtic") ], - [False, "ISO-8859-15", _("Western") ], - [False, "ISO-8859-16", _("Romanian") ], -# [False, "UTF-7", _("Unicode") ], - [False, "UTF-8", _("Unicode") ], -# [False, "UTF-16", _("Unicode") ], -# [False, "UCS-2", _("Unicode") ], -# [False, "UCS-4", _("Unicode") ], - [False, "ARMSCII-8", _("Armenian") ], - [False, "BIG5", _("Chinese Traditional") ], - [False, "BIG5-HKSCS", _("Chinese Traditional") ], - [False, "CP866", _("Cyrillic/Russian") ], - [False, "EUC-JP", _("Japanese") ], - [False, "EUC-KR", _("Korean") ], - [False, "EUC-TW", _("Chinese Traditional") ], - [False, "GB18030", _("Chinese Simplified") ], - [False, "GB2312", _("Chinese Simplified") ], - [False, "GBK", _("Chinese Simplified") ], - [False, "GEORGIAN-PS", _("Georgian") ], - [False, "HZ", _("Chinese Simplified") ], - [False, "IBM850", _("Western") ], - [False, "IBM852", _("Central European") ], - [False, "IBM855", _("Cyrillic") ], - [False, "IBM857", _("Turkish") ], - [False, "IBM862", _("Hebrew") ], - [False, "IBM864", _("Arabic") ], - [False, "ISO-2022-JP", _("Japanese") ], - [False, "ISO-2022-KR", _("Korean") ], - [False, "ISO-IR-111", _("Cyrillic") ], -# [False, "JOHAB", _("Korean") ], - [False, "KOI8-R", _("Cyrillic") ], - [False, "KOI8-U", _("Cyrillic/Ukrainian") ], - [False, "MAC_ARABIC", _("Arabic") ], - [False, "MAC_CE", _("Central European") ], - [False, "MAC_CROATIAN", _("Croatian") ], - [False, "MAC-CYRILLIC", _("Cyrillic") ], - [False, "MAC_DEVANAGARI", _("Hindi") ], - [False, "MAC_FARSI", _("Persian") ], - [False, "MAC_GREEK", _("Greek") ], - [False, "MAC_GUJARATI", _("Gujarati") ], - [False, "MAC_GURMUKHI", _("Gurmukhi") ], - [False, "MAC_HEBREW", _("Hebrew") ], - [False, "MAC_ICELANDIC", _("Icelandic") ], - [False, "MAC_ROMAN", _("Western") ], - [False, "MAC_ROMANIAN", _("Romanian") ], - [False, "MAC_TURKISH", _("Turkish") ], - [False, "MAC_UKRAINIAN", _("Cyrillic/Ukrainian") ], - [False, "SHIFT-JIS", _("Japanese") ], - [False, "TCVN", _("Vietnamese") ], - [False, "TIS-620", _("Thai") ], - [False, "UHC", _("Korean") ], - [False, "VISCII", _("Vietnamese") ], - [False, "WINDOWS-1250", _("Central European") ], - [False, "WINDOWS-1251", _("Cyrillic") ], - [False, "WINDOWS-1252", _("Western") ], - [False, "WINDOWS-1253", _("Greek") ], - [False, "WINDOWS-1254", _("Turkish") ], - [False, "WINDOWS-1255", _("Hebrew") ], - [False, "WINDOWS-1256", _("Arabic") ], - [False, "WINDOWS-1257", _("Baltic") ], - [False, "WINDOWS-1258", _("Vietnamese") ] - ] + encodings = [ + [True, None, _("Current Locale")], + [False, "ISO-8859-1", _("Western")], + [False, "ISO-8859-2", _("Central European")], + [False, "ISO-8859-3", _("South European") ], + [False, "ISO-8859-4", _("Baltic") ], + [False, "ISO-8859-5", _("Cyrillic") ], + [False, "ISO-8859-6", _("Arabic") ], + [False, "ISO-8859-7", _("Greek") ], + [False, "ISO-8859-8", _("Hebrew Visual") ], + [False, "ISO-8859-8-I", _("Hebrew") ], + [False, "ISO-8859-9", _("Turkish") ], + [False, "ISO-8859-10", _("Nordic") ], + [False, "ISO-8859-13", _("Baltic") ], + [False, "ISO-8859-14", _("Celtic") ], + [False, "ISO-8859-15", _("Western") ], + [False, "ISO-8859-16", _("Romanian") ], + # [False, "UTF-7", _("Unicode") ], + [False, "UTF-8", _("Unicode") ], + # [False, "UTF-16", _("Unicode") ], + # [False, "UCS-2", _("Unicode") ], + # [False, "UCS-4", _("Unicode") ], + [False, "ARMSCII-8", _("Armenian") ], + [False, "BIG5", _("Chinese Traditional") ], + [False, "BIG5-HKSCS", _("Chinese Traditional") ], + [False, "CP866", _("Cyrillic/Russian") ], + [False, "EUC-JP", _("Japanese") ], + [False, "EUC-KR", _("Korean") ], + [False, "EUC-TW", _("Chinese Traditional") ], + [False, "GB18030", _("Chinese Simplified") ], + [False, "GB2312", _("Chinese Simplified") ], + [False, "GBK", _("Chinese Simplified") ], + [False, "GEORGIAN-PS", _("Georgian") ], + [False, "HZ", _("Chinese Simplified") ], + [False, "IBM850", _("Western") ], + [False, "IBM852", _("Central European") ], + [False, "IBM855", _("Cyrillic") ], + [False, "IBM857", _("Turkish") ], + [False, "IBM862", _("Hebrew") ], + [False, "IBM864", _("Arabic") ], + [False, "ISO-2022-JP", _("Japanese") ], + [False, "ISO-2022-KR", _("Korean") ], + [False, "ISO-IR-111", _("Cyrillic") ], + # [False, "JOHAB", _("Korean") ], + [False, "KOI8-R", _("Cyrillic") ], + [False, "KOI8-U", _("Cyrillic/Ukrainian") ], + [False, "MAC_ARABIC", _("Arabic") ], + [False, "MAC_CE", _("Central European") ], + [False, "MAC_CROATIAN", _("Croatian") ], + [False, "MAC-CYRILLIC", _("Cyrillic") ], + [False, "MAC_DEVANAGARI", _("Hindi") ], + [False, "MAC_FARSI", _("Persian") ], + [False, "MAC_GREEK", _("Greek") ], + [False, "MAC_GUJARATI", _("Gujarati") ], + [False, "MAC_GURMUKHI", _("Gurmukhi") ], + [False, "MAC_HEBREW", _("Hebrew") ], + [False, "MAC_ICELANDIC", _("Icelandic") ], + [False, "MAC_ROMAN", _("Western") ], + [False, "MAC_ROMANIAN", _("Romanian") ], + [False, "MAC_TURKISH", _("Turkish") ], + [False, "MAC_UKRAINIAN", _("Cyrillic/Ukrainian") ], + [False, "SHIFT-JIS", _("Japanese") ], + [False, "TCVN", _("Vietnamese") ], + [False, "TIS-620", _("Thai") ], + [False, "UHC", _("Korean") ], + [False, "VISCII", _("Vietnamese") ], + [False, "WINDOWS-1250", _("Central European") ], + [False, "WINDOWS-1251", _("Cyrillic") ], + [False, "WINDOWS-1252", _("Western") ], + [False, "WINDOWS-1253", _("Greek") ], + [False, "WINDOWS-1254", _("Turkish") ], + [False, "WINDOWS-1255", _("Hebrew") ], + [False, "WINDOWS-1256", _("Arabic") ], + [False, "WINDOWS-1257", _("Baltic") ], + [False, "WINDOWS-1258", _("Vietnamese") ] + ] - def __init__(self): - pass + def __init__(self): + pass - def get_list(): - """Return a list of supported encodings""" - return TerminatorEncoding.encodings - get_list = staticmethod(get_list) + def get_list(): + """Return a list of supported encodings""" + return TerminatorEncoding.encodings + + get_list = staticmethod(get_list) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index c7a7fced..fbd83917 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -76,9 +76,9 @@ class Terminator(Borg): def do_enumerate(self, widget, pad): """Insert the number of each terminal in a group, into that terminal""" if pad: - numstr='%0'+str(len(str(len(self.terminals))))+'d' + numstr = '%0'+str(len(str(len(self.terminals))))+'d' else: - numstr='%d' + numstr = '%d' for term in self.get_target_terms(widget): idx = self.terminals.index(term) @@ -89,12 +89,12 @@ class Terminator(Borg): if self.groupsend == self.groupsend_type['all']: return(self.terminals) elif self.groupsend == self.groupsend_type['group']: - set = [] + termset = [] for term in self.terminals: if term == widget or (term.group != None and term.group == widget.group): - set.append(term) - return(set) + termset.append(term) + return(termset) else: return([widget]) From b43695b23d08aeb1b8f56a490758a3a3f49712a7 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 9 Oct 2009 12:25:06 +0100 Subject: [PATCH 091/331] Flesh out the borg a little --- terminatorlib/borg.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/terminatorlib/borg.py b/terminatorlib/borg.py index 6b65cd53..dc3d17bf 100755 --- a/terminatorlib/borg.py +++ b/terminatorlib/borg.py @@ -6,9 +6,38 @@ # pylint: disable-msg=R0903 class Borg: - """Definition of a class that can never be duplicated""" + """Definition of a class that can never be duplicated. Correct usage is + thus: + + from borg import Borg + class foo(Borg): + # All attributes on a borg class *must* = None + attribute = None + + def __init__(self): + Borg.__init__(self) + + def prepare_attributes(self): + if not self.attribute: + self.attribute = [] + + bar = foo() + bar.prepare_attributes() + + The important thing to note is that all attributes of borg classes *must* be + declared as being None. If you attempt to use static class attributes you + will get unpredicted behaviour. Instead, prepare_attributes() must be called + which will then see the attributes in the shared state, and initialise them + if necessary.""" __shared_state = {} + def __init__(self): + """Class initialiser. Overwrite our class dictionary with the shared + state. This makes us identical to every other instance of this class + type.""" self.__dict__ = self.__shared_state -# vim: set expandtab ts=4 sw=4: + def prepare_attributes(self): + """This should be used to prepare any attributes of the borg class.""" + raise NotImplementedError('prepare_attributes') + From fdcd1c89f932d85b15c2ab178bbe5cc875bd47f3 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 12 Oct 2009 22:05:19 +0100 Subject: [PATCH 092/331] Add signals for when we want to be zoomed or maximised --- terminatorlib/terminal.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 8893b467..a83eaa43 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -47,6 +47,8 @@ class Terminal(gtk.VBox): 'tab-new': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'tab-top-new': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'focus-in': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'zoom': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'maximise': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), } TARGET_TYPE_VTE = 8 @@ -521,11 +523,11 @@ class Terminal(gtk.VBox): def zoom(self): """Zoom ourself to fill the window""" - pass + self.emit('zoom') def maximise(self): """Maximise ourself to fill the window""" - pass + self.emit('maximise') def spawn_child(self, widget=None): update_records = self.config['update_records'] From cd1d858d3ce02af27fd2711f5ca108fee2f539fb Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 14 Oct 2009 13:05:07 +0100 Subject: [PATCH 093/331] Start making keybindings work in Terminal(). They don't work yet --- terminatorlib/keybindings.py | 192 ++++++++++++++++----------------- terminatorlib/newterminator.py | 8 ++ terminatorlib/terminal.py | 44 +++++++- 3 files changed, 147 insertions(+), 97 deletions(-) diff --git a/terminatorlib/keybindings.py b/terminatorlib/keybindings.py index 76af7997..6702264d 100644 --- a/terminatorlib/keybindings.py +++ b/terminatorlib/keybindings.py @@ -1,6 +1,6 @@ #!/usr/bin/python -# Terminator - multiple gnome terminals in one window -# Copyright (C) 2006-2008 cmsj@tenshu.net +# Terminator - multiple gnome terminals in one window +# Copyright (C) 2006-2008 cmsj@tenshu.net # # 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 @@ -24,112 +24,112 @@ keyboard shortcuts. import re import gtk -from terminatorlib.config import err +from util import err class KeymapError(Exception): - """Custom exception for errors in keybinding configurations""" - def __init__(self, value): - Exception.__init__(self, value) - self.value = value - self.action = 'unknown' + """Custom exception for errors in keybinding configurations""" + def __init__(self, value): + Exception.__init__(self, value) + self.value = value + self.action = 'unknown' - def __str__(self): - return "Keybinding '%s' invalid: %s" % (self.action, self.value) + def __str__(self): + return "Keybinding '%s' invalid: %s" % (self.action, self.value) MODIFIER = re.compile('<([^<]+)>') -class TerminatorKeybindings: - """Class to handle loading and lookup of Terminator keybindings""" +class Keybindings: + """Class to handle loading and lookup of Terminator keybindings""" - modifiers = { - 'ctrl': gtk.gdk.CONTROL_MASK, - 'control': gtk.gdk.CONTROL_MASK, - 'shift': gtk.gdk.SHIFT_MASK, - 'alt': gtk.gdk.MOD1_MASK, - 'super': gtk.gdk.SUPER_MASK, - } + modifiers = { + 'ctrl': gtk.gdk.CONTROL_MASK, + 'control': gtk.gdk.CONTROL_MASK, + 'shift': gtk.gdk.SHIFT_MASK, + 'alt': gtk.gdk.MOD1_MASK, + 'super': gtk.gdk.SUPER_MASK, + } - empty = {} - keys = None - _masks = None - _lookup = None + empty = {} + keys = None + _masks = None + _lookup = None - def __init__(self): - self.keymap = gtk.gdk.keymap_get_default() - self.configure({}) + def __init__(self): + self.keymap = gtk.gdk.keymap_get_default() + self.configure({}) - def configure(self, bindings): - """Accept new bindings and reconfigure with them""" - self.keys = bindings - self.reload() + def configure(self, bindings): + """Accept new bindings and reconfigure with them""" + self.keys = bindings + self.reload() - def reload(self): - """Parse bindings and mangle into an appropriate form""" - self._lookup = {} - self._masks = 0 - for action, bindings in self.keys.items(): - if not isinstance(bindings, tuple): - bindings = (bindings,) + def reload(self): + """Parse bindings and mangle into an appropriate form""" + self._lookup = {} + self._masks = 0 + for action, bindings in self.keys.items(): + if not isinstance(bindings, tuple): + bindings = (bindings,) - for binding in bindings: - if binding is None or binding == "None": - continue + for binding in bindings: + if binding is None or binding == "None": + continue + try: + keyval, mask = self._parsebinding(binding) + # Does much the same, but with poorer error handling. + #keyval, mask = gtk.accelerator_parse(binding) + except KeymapError, ex: + ex.action = action + raise ex + else: + if mask & gtk.gdk.SHIFT_MASK: + if keyval == gtk.keysyms.Tab: + keyval = gtk.keysyms.ISO_Left_Tab + mask &= ~gtk.gdk.SHIFT_MASK + else: + keyvals = gtk.gdk.keyval_convert_case(keyval) + if keyvals[0] != keyvals[1]: + keyval = keyvals[1] + mask &= ~gtk.gdk.SHIFT_MASK + else: + keyval = gtk.gdk.keyval_to_lower(keyval) + self._lookup.setdefault(mask, {}) + self._lookup[mask][keyval] = action + self._masks |= mask + + def _parsebinding(self, binding): + """Parse an individual binding using gtk's binding function""" + mask = 0 + modifiers = re.findall(MODIFIER, binding) + if modifiers: + for modifier in modifiers: + mask |= self._lookup_modifier(modifier) + key = re.sub(MODIFIER, '', binding) + if key == '': + raise KeymapError('No key found') + keyval = gtk.gdk.keyval_from_name(key) + if keyval == 0: + raise KeymapError("Key '%s' is unrecognised" % key) + return (keyval, mask) + + def _lookup_modifier(self, modifier): + """Map modifier names to gtk values""" try: - keyval, mask = self._parsebinding(binding) - # Does much the same, but with poorer error handling. - #keyval, mask = gtk.accelerator_parse(binding) - except KeymapError, ex: - ex.action = action - raise ex - else: - if mask & gtk.gdk.SHIFT_MASK: - if keyval == gtk.keysyms.Tab: - keyval = gtk.keysyms.ISO_Left_Tab - mask &= ~gtk.gdk.SHIFT_MASK - else: - keyvals = gtk.gdk.keyval_convert_case(keyval) - if keyvals[0] != keyvals[1]: - keyval = keyvals[1] - mask &= ~gtk.gdk.SHIFT_MASK - else: - keyval = gtk.gdk.keyval_to_lower(keyval) - self._lookup.setdefault(mask, {}) - self._lookup[mask][keyval] = action - self._masks |= mask + return self.modifiers[modifier.lower()] + except KeyError: + raise KeymapError("Unhandled modifier '<%s>'" % modifier) - def _parsebinding(self, binding): - """Parse an individual binding using gtk's binding function""" - mask = 0 - modifiers = re.findall(MODIFIER, binding) - if modifiers: - for modifier in modifiers: - mask |= self._lookup_modifier(modifier) - key = re.sub(MODIFIER, '', binding) - if key == '': - raise KeymapError('No key found') - keyval = gtk.gdk.keyval_from_name(key) - if keyval == 0: - raise KeymapError("Key '%s' is unrecognised" % key) - return (keyval, mask) - - def _lookup_modifier(self, modifier): - """Map modifier names to gtk values""" - try: - return self.modifiers[modifier.lower()] - except KeyError: - raise KeymapError("Unhandled modifier '<%s>'" % modifier) - - def lookup(self, event): - """Translate a keyboard event into a mapped key""" - try: - keyval, egroup, level, consumed = self.keymap.translate_keyboard_state( - event.hardware_keycode, - event.state & ~gtk.gdk.LOCK_MASK, - event.group) - except TypeError: - err ("keybindings.lookup failed to translate keyboard event: %s" % - dir(event)) - return None - mask = (event.state & ~consumed) & self._masks - return self._lookup.get(mask, self.empty).get(keyval, None) + def lookup(self, event): + """Translate a keyboard event into a mapped key""" + try: + keyval, egroup, level, consumed = self.keymap.translate_keyboard_state( + event.hardware_keycode, + event.state & ~gtk.gdk.LOCK_MASK, + event.group) + except TypeError: + err ("keybindings.lookup failed to translate keyboard event: %s" % + dir(event)) + return None + mask = (event.state & ~consumed) & self._masks + return self._lookup.get(mask, self.empty).get(keyval, None) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index fbd83917..7df9d86f 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -4,6 +4,8 @@ """terminator.py - class for the master Terminator singleton""" from borg import Borg +from config import Config +from keybindings import Keybindings class Terminator(Borg): """master object for the application""" @@ -13,6 +15,7 @@ class Terminator(Borg): terminals = None groups = None config = None + keybindings = None splittogroup = None autocleangroups = None @@ -38,6 +41,11 @@ class Terminator(Borg): self.splittogroup = False if not self.autocleangroups: self.autocleangroups = True + if not self.config: + self.config = Config() + if not self.keybindings: + self.keybindings = Keybindings() + self.keybindings.configure(self.config['keybindings']) def register_terminal(self, terminal): """Register a new terminal widget""" diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index a83eaa43..aa4af704 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -376,6 +376,7 @@ class Terminal(gtk.VBox): def create_group(self, item): """Create a new group""" + #FIXME: Make this work pass def set_groupsend(self, widget, value): @@ -395,6 +396,7 @@ class Terminal(gtk.VBox): def reconfigure(self, widget=None): """Reconfigure our settings""" + # FIXME: actually reconfigure our settings pass def get_window_title(self): @@ -409,7 +411,39 @@ class Terminal(gtk.VBox): def on_keypress(self, widget, event): """Handler for keyboard events""" - pass + if not event: + dbg('Terminal::on_keypress: Called on %s with no event' % widget) + return(False) + + # FIXME: Does keybindings really want to live in Terminator()? + mapping = self.terminator.keybindings.lookup(event) + + if mapping == "hide_window": + return(False) + + if mapping and mapping not in ['close_window', 'full_screen']: + dbg('Terminal::on_keypress: lookup found: %r' % mapping) + # handle the case where user has re-bound copy to ctrl+ + # we only copy if there is a selection otherwise let it fall through + # to ^ + if (mapping == "copy" and event.state & gtk.gdk.CONTROL_MASK): + if self._vte.get_has_selection (): + getattr(self, "key_" + mapping)() + return(True) + else: + getattr(self, "key_" + mapping)() + return(True) + + # FIXME: This is all clearly wrong. We should be doing this better + # FIXMS: maybe we can emit the key event and let Terminator() care? + if self.terminator.groupsend != 0 and self.vte.is_focus(): + if self.group and self.terminator.groupsend == 1: + self.terminator.group_emit(self, self.group, 'key-press-event', + event) + if self.terminator.groupsend == 2: + self.terminator.all_emit(self, 'key-press-event', event) + + return(False) def on_buttonpress(self, widget, event): """Handler for mouse events""" @@ -460,23 +494,29 @@ class Terminal(gtk.VBox): self.vte.set_encoding(encoding) def on_drag_begin(self, widget, drag_context, data): + # FIXME: Implement this pass def on_drag_data_get(self, widget, drag_context, selection_data, info, time, data): + # FIXME: Implement this pass def on_drag_motion(self, widget, drag_context, x, y, time, data): + # FIXME: Implement this pass def on_drag_data_received(self, widget, drag_context, x, y, selection_data, info, time, data): + # FIXME: Implement this pass def on_vte_focus(self, widget): + # FIXME: Implement this pass def on_vte_focus_out(self, widget, event): + # FIXME: Implement this pass def on_vte_focus_in(self, widget, event): @@ -488,6 +528,7 @@ class Terminal(gtk.VBox): self.vte.grab_focus() def on_resize_window(self): + # FIXME: Implement this pass def on_vte_size_allocate(self, widget, allocation): @@ -496,6 +537,7 @@ class Terminal(gtk.VBox): pass def on_vte_notify_enter(self, term, event): + # FIXME: Implement this pass def hide_titlebar(self): From 7970033368a409c3804ccb3fea213ea288682d60 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 15 Oct 2009 12:54:16 +0100 Subject: [PATCH 094/331] Add an important FIXME --- terminatorlib/terminal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index aa4af704..cdc3b22e 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -633,6 +633,7 @@ class Terminal(gtk.VBox): def open_url(self, url, prepare=False): """Open a given URL, conditionally unpacking it from a VTE match""" + # FIXME: Use gtk.show_uri() if prepare == True: url = self.prepare_url(url) dbg('open_url: URL: %s (prepared: %s)' % (url, prepare)) From 667d31e3944f2327cc992ecb62b856ca7d3f9a1a Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 15 Oct 2009 12:57:50 +0100 Subject: [PATCH 095/331] Drop all our URL madness and make it pygtk's problem. Requires pygtk >=2.14 --- terminatorlib/terminal.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index cdc3b22e..8aa0f079 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -9,7 +9,6 @@ import pygtk pygtk.require('2.0') import gtk import gobject -import subprocess import re from version import APP_NAME @@ -633,18 +632,10 @@ class Terminal(gtk.VBox): def open_url(self, url, prepare=False): """Open a given URL, conditionally unpacking it from a VTE match""" - # FIXME: Use gtk.show_uri() if prepare == True: url = self.prepare_url(url) dbg('open_url: URL: %s (prepared: %s)' % (url, prepare)) - try: - subprocess.Popen(['xdg-open', url]) - except OSError: - dbg('open_url: xdg-open failed') - try: - self.terminator.url_show(url) - except: - dbg('open_url: url_show failed. Giving up') + gtk.show_uri(None, url, gtk.gdk.CURRENT_TIME) def paste_clipboard(self, primary=False): """Paste one of the two clipboards""" From 5652f104ada2a1665d40c58f6416c591e2db0b4d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 15 Oct 2009 12:58:23 +0100 Subject: [PATCH 096/331] Update pygtk dependency --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index fb61bebb..0717af3b 100644 --- a/debian/control +++ b/debian/control @@ -14,7 +14,7 @@ Homepage: http://www.tenshu.net/terminator/ Package: terminator Architecture: all -Depends: ${python:Depends}, ${misc:Depends}, python-vte, python-gnome2, python-gobject, python-gtk2, gconf2, libgtk2.0-bin +Depends: ${python:Depends}, ${misc:Depends}, python-vte, python-gnome2, python-gobject, python-gtk2 (>= 2.14.0), gconf2, libgtk2.0-bin XB-Python-Version: ${python:Versions} Provides: x-terminal-emulator Recommends: xdg-utils, python-xdg From e2086578bb34251030455f1ed5f5c9152822761b Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 15 Oct 2009 14:17:37 +0100 Subject: [PATCH 097/331] Group creation is now presented via the titlebar, but still doesn't actually have any backending in functional reality yet. --- terminatorlib/terminal.py | 11 ++++++++--- terminatorlib/titlebar.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 8aa0f079..392734e0 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -106,6 +106,7 @@ class Terminal(gtk.VBox): self.titlebar.connect_icon(self.on_group_button_press) self.titlebar.connect('edit-done', self.on_edit_done) self.connect('title-change', self.titlebar.set_terminal_title) + self.titlebar.connect('create-group', self.really_create_group) self.searchbar = Searchbar() @@ -374,9 +375,13 @@ class Terminal(gtk.VBox): return(widget_x, menu_y, 1) def create_group(self, item): - """Create a new group""" - #FIXME: Make this work - pass + """Trigger the creation of a group via the titlebar (because popup + windows are really lame)""" + self.titlebar.create_group() + + def really_create_group(self, groupname): + """The titlebar has spoken, let a group be created""" + # FIXME: Actually create the group def set_groupsend(self, widget, value): """Set the groupsend mode""" diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index 3176c2f9..f44c5571 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -7,6 +7,7 @@ import gtk import gobject from version import APP_NAME +from util import dbg from newterminator import Terminator from editablelabel import EditableLabel @@ -23,10 +24,13 @@ class Titlebar(gtk.EventBox): ebox = None groupicon = None grouplabel = None + groupentry = None __gsignals__ = { 'clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'edit-done': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'create-group': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_STRING,)), } def __init__(self): @@ -43,6 +47,12 @@ class Titlebar(gtk.EventBox): self.grouplabel = gtk.Label() self.groupicon = gtk.Image() + self.groupentry = gtk.Entry() + self.groupentry.set_no_show_all(True) + self.groupentry.connect('focus-out-event', self.groupentry_cancel) + self.groupentry.connect('activate', self.groupentry_activate) + self.groupentry.connect('key-press-event', self.groupentry_keypress) + groupsend_type = self.terminator.groupsend_type if self.terminator.groupsend == groupsend_type['all']: icon_name = 'all' @@ -55,6 +65,8 @@ class Titlebar(gtk.EventBox): grouphbox.pack_start(self.groupicon, False, True, 2) grouphbox.pack_start(self.grouplabel, False, True, 2) + grouphbox.pack_start(self.groupentry, False, True, 2) + self.ebox.add(grouphbox) self.ebox.show_all() @@ -111,4 +123,27 @@ class Titlebar(gtk.EventBox): """Re-emit an edit-done signal from an EditableLabel""" self.emit('edit-done') + def create_group(self): + """Create a new group""" + self.groupentry.show() + self.groupentry.grab_focus() + + def groupentry_cancel(self, widget, event): + """Hide the group name entry""" + self.groupentry.set_text('') + self.groupentry.hide() + + def groupentry_activate(self, widget): + """Actually cause a group to be created""" + groupname = self.groupentry.get_text() + dbg('creating group: %s' % groupname) + self.groupentry_cancel(None, None) + self.emit('create-group', groupname) + + def groupentry_keypress(self, widget, event): + """Handle keypresses on the entry widget""" + key = gtk.gdk.keyval_name(event.keyval) + if key == 'Escape': + self.groupentry_cancel(None, None) + gobject.type_register(Titlebar) From 0ef7c99022779c343907d9e9b6178fa0d6c5b8ee Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 26 Oct 2009 13:36:34 +0000 Subject: [PATCH 098/331] remove an unused variable --- terminatorlib/terminal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 392734e0..b26a4f99 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -11,7 +11,6 @@ import gtk import gobject import re -from version import APP_NAME from util import dbg, err, gerr import util from config import Config From f7d0d957cd3df94e9936d5c0d679d9cad3154c41 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 27 Oct 2009 21:03:11 +0000 Subject: [PATCH 099/331] Add function to snapshot a widget and its children as a pixbuf --- terminatorlib/util.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/terminatorlib/util.py b/terminatorlib/util.py index b4d8db64..86ecb525 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -95,3 +95,23 @@ def shell_lookup(): return(rshell) dbg('shell_lookup: Unable to locate a shell') +def widget_pixbuf(widget, maxsize=None): + """Generate a pixbuf of a widget""" + pixmap = widget.get_snapshot() + (width, height) = pixmap.get_size() + pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height) + pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, width, + height) + + longest = max(width, height) + + if maxsize is not None: + factor = float(maxsize) / float(longest) + + if not maxsize or (width * factor) > width or (height * factor) > height: + factor = 1 + + scaledpixbuf = pixbuf.scale_simple(int(width * factor), int(height * factor), gtk.gdk.INTERP_BILINEAR) + + return(scaledpixbuf) + From ef1e82e5b07240803a1d1a6d9a1c41e8ed84d0e1 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 27 Oct 2009 23:05:12 +0000 Subject: [PATCH 100/331] Fix titlebar drag starting Remove useless resize-window handler Implement the Terminal() end of group creation Finish drag and drop handling Implement methods relating to focus changes Fill out a few method docstrings --- terminatorlib/terminal.py | 167 ++++++++++++++++++++++++++++++++------ 1 file changed, 142 insertions(+), 25 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index b26a4f99..b803c637 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -11,7 +11,7 @@ import gtk import gobject import re -from util import dbg, err, gerr +from util import dbg, err, gerr, widget_pixbuf import util from config import Config from cwd import get_default_cwd @@ -206,7 +206,7 @@ class Terminal(gtk.VBox): for (widget, mask) in [ (self.vte, gtk.gdk.CONTROL_MASK | gtk.gdk.BUTTON3_MASK), - (self.titlebar, gtk.gdk.CONTROL_MASK)]: + (self.titlebar, gtk.gdk.BUTTON1_MASK)]: widget.drag_source_set(mask, srcvtetargets, gtk.gdk.ACTION_MOVE) self.vte.drag_dest_set(gtk.DEST_DEFAULT_MOTION | @@ -234,7 +234,6 @@ class Terminal(gtk.VBox): self.emit('title-change', self.get_window_title())) self.vte.connect('grab-focus', self.on_vte_focus) self.vte.connect('focus-in-event', self.on_vte_focus_in) - self.vte.connect('resize-window', self.on_resize_window) self.vte.connect('size-allocate', self.on_vte_size_allocate) if self.config['exit_action'] == 'restart': @@ -380,12 +379,14 @@ class Terminal(gtk.VBox): def really_create_group(self, groupname): """The titlebar has spoken, let a group be created""" - # FIXME: Actually create the group + self.terminator.create_group(groupname) + self.group = groupname def set_groupsend(self, widget, value): """Set the groupsend mode""" # FIXME: Can we think of a smarter way of doing this than poking? - self.terminator.groupsend = value + if value in self.terminator.groupsend_type: + self.terminator.groupsend = value def do_splittogroup_toggle(self): """Toggle the splittogroup mode""" @@ -497,56 +498,172 @@ class Terminal(gtk.VBox): self.vte.set_encoding(encoding) def on_drag_begin(self, widget, drag_context, data): - # FIXME: Implement this - pass + """Handle the start of a drag event""" + widget.drag_source_set_icon_pixbuf(util.widget_pixbuf(self, 512)) def on_drag_data_get(self, widget, drag_context, selection_data, info, time, data): - # FIXME: Implement this - pass + """I have no idea what this does, drag and drop is a mystery. sorry.""" + selection_data.set('vte', info, + str(data.terminator.terminals.index(self))) def on_drag_motion(self, widget, drag_context, x, y, time, data): - # FIXME: Implement this - pass + """*shrug*""" + if 'text/plain' in drag_context.targets: + # copy text from another widget + return + srcwidget = drag_context.get_source_widget() + if(isinstance(srcwidget, gtk.EventBox) and + srcwidget == self.titlebar) or widget == srcwidget: + # on self + return + + alloc = widget.allocation + rect = gtk.gdk.Rectangle(0, 0, alloc.width, alloc.height) + + if self.config['use_theme_colors']: + color = self.vte.get_style().text[gtk.STATE_NORMAL] + else: + color = gtk.gdk.color_parse(self.config['foreground_color']) + + pos = self.get_location(widget, x, y) + topleft = (0,0) + topright = (alloc.width,0) + topmiddle = (alloc.width/2,0) + bottomleft = (0, alloc.height) + bottomright = (alloc.width,alloc.height) + bottommiddle = (alloc.width/2, alloc.height) + middle = (alloc.width/2, alloc.height/2) + middleleft = (0, alloc.height/2) + middleright = (alloc.width, alloc.height/2) + #print "%f %f %d %d" %(coef1, coef2, b1,b2) + coord = () + if pos == "right": + coord = (topright, topmiddle, bottommiddle, bottomright) + elif pos == "top": + coord = (topleft, topright, middleright , middleleft) + elif pos == "left": + coord = (topleft, topmiddle, bottommiddle, bottomleft) + elif pos == "bottom": + coord = (bottomleft, bottomright, middleright , middleleft) + + #here, we define some widget internal values + widget._expose_data = { 'color': color, 'coord' : coord } + #redraw by forcing an event + connec = widget.connect_after('expose-event', self.on_expose_event) + widget.window.invalidate_rect(rect, True) + widget.window.process_updates(True) + #finaly reset the values + widget.disconnect(connec) + widget._expose_data = None + + def on_expose_event(self, widget, event): + """Handle an expose event while dragging""" + if not widget._expose_data: + return(False) + + color = widget._expose_data['color'] + coord = widget._expose_data['coord'] + + context = widget.window.cairo_create() + context.set_source_rgba(color.red, color.green, color.blue, 0.5) + if len(coord) > 0 : + context.move_to(coord[len(coord)-1][0],coord[len(coord)-1][1]) + for i in coord: + context.line_to(i[0],i[1]) + + context.fill() + return(False) def on_drag_data_received(self, widget, drag_context, x, y, selection_data, info, time, data): - # FIXME: Implement this - pass + if selection_data.type == 'text/plain': + # copy text to destination + txt = selection_data.data.strip() + if txt[0:7] == 'file://': + text = "'%s'" % urllib.unquote(txt[7:]) + for term in self.terminator.get_target_terms(): + term.feed(txt) + return + + widgetsrc = data.terminator.terminals[int(selection_data.data)] + srcvte = drag_context.get_source_widget() + #check if computation requireds + if (isinstance(srcvte, gtk.EventBox) and + srcvte == self.titlebar) or srcvte == widget: + return + + srchbox = widgetsrc + dsthbox = widget.get_parent().get_parent() + + dstpaned = dsthbox.get_parent() + srcpaned = srchbox.get_parent() + if isinstance(dstpaned, gtk.Window) and isinstance(srcpaned, gtk.Window): + return + + pos = self.get_location(widget, x, y) + + data.terminator.remove(widgetsrc, True) + data.terminator.add(self, widgetsrc, pos) + + def get_location(self, vte, x, y): + """Get our location within the terminal""" + pos = '' + #get the diagonales function for the receiving widget + coef1 = float(vte.allocation.height)/float(vte.allocation.width) + coef2 = -float(vte.allocation.height)/float(vte.allocation.width) + b1 = 0 + b2 = vte.allocation.height + #determine position in rectangle + """ + -------- + |\ /| + | \ / | + | \/ | + | /\ | + | / \ | + |/ \| + -------- + """ + if (x*coef1 + b1 > y ) and (x*coef2 + b2 < y ): + pos = "right" + if (x*coef1 + b1 > y ) and (x*coef2 + b2 > y ): + pos = "top" + if (x*coef1 + b1 < y ) and (x*coef2 + b2 > y ): + pos = "left" + if (x*coef1 + b1 < y ) and (x*coef2 + b2 < y ): + pos = "bottom" + return pos def on_vte_focus(self, widget): - # FIXME: Implement this - pass + self.emit('title-change', self.get_window_title()) def on_vte_focus_out(self, widget, event): - # FIXME: Implement this - pass + return def on_vte_focus_in(self, widget, event): self.emit('focus-in') - pass def on_edit_done(self, widget): """A child widget is done editing a label, return focus to VTE""" self.vte.grab_focus() - def on_resize_window(self): - # FIXME: Implement this - pass - def on_vte_size_allocate(self, widget, allocation): self.titlebar.update_terminal_size(self.vte.get_column_count(), self.vte.get_row_count()) - pass def on_vte_notify_enter(self, term, event): - # FIXME: Implement this - pass + """Handle the mouse entering this terminal""" + if self.config['focus'] in ['sloppy', 'mouse']: + term.grab_focus() + return(False) def hide_titlebar(self): + """Hide the titlebar""" self.titlebar.hide() def show_titlebar(self): + """Show the titlebar""" self.titlebar.show() def is_zoomed(self): From ede7fc7552cd7af06778cb48a31d6c77ae7a9084 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 27 Oct 2009 23:23:59 +0000 Subject: [PATCH 101/331] Fix bogus child spawning. realize happens more than you might hope. --- terminatorlib/terminal.py | 1 - terminatorlib/test.py | 1 + terminatorlib/window.py | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index b803c637..fe256191 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -246,7 +246,6 @@ class Terminal(gtk.VBox): self.on_vte_notify_enter) self.vte.connect_after('realize', self.reconfigure) - self.vte.connect_after('realize', self.spawn_child) def create_popup_group_menu(self, widget, event = None): """Pop up a menu for the group widget""" diff --git a/terminatorlib/test.py b/terminatorlib/test.py index d809c477..9438b9b7 100755 --- a/terminatorlib/test.py +++ b/terminatorlib/test.py @@ -17,6 +17,7 @@ foo.register_terminal(term) window.add(term) window.show() +term.spawn_child() window.connect("destroy", on_window_destroyed) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index a2598a64..ea00964c 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -199,6 +199,7 @@ class Window(Container, gtk.Window): container.show() self.add(container) + sibling.spawn_child() class WindowTitle(object): """Class to handle the setting of the window title""" From 6f2fd988b429864c21ea030da2417ae0a18d1d77 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 28 Oct 2009 12:53:18 +0000 Subject: [PATCH 102/331] Fix keybinding dispatch in Terminal and add handler functions for all of the keybindings, but basically every single one is broken right now --- terminatorlib/terminal.py | 161 +++++++++++++++++++++++++++++++++++++- 1 file changed, 157 insertions(+), 4 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index fe256191..59245370 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -430,12 +430,12 @@ class Terminal(gtk.VBox): # we only copy if there is a selection otherwise let it fall through # to ^ if (mapping == "copy" and event.state & gtk.gdk.CONTROL_MASK): - if self._vte.get_has_selection (): - getattr(self, "key_" + mapping)() - return(True) - else: + if self.vte.get_has_selection (): getattr(self, "key_" + mapping)() return(True) + else: + getattr(self, "key_" + mapping)() + return(True) # FIXME: This is all clearly wrong. We should be doing this better # FIXMS: maybe we can emit the key event and let Terminator() care? @@ -770,5 +770,158 @@ class Terminal(gtk.VBox): """Feed the supplied text to VTE""" self.vte.feed_child(text) + # There now begins a great list of keyboard event handlers + # FIXME: Probably a bunch of these are wrong. TEST! + def key_zoom_in(self): + self.zoom(True) + + def key_zoom_out(self): + self.zoom(False) + + def key_copy(self): + self.vte.copy_clipboard() + + def key_paste(self): + self.vte-paste_clipboard() + + def key_toggle_scrollbar(self): + self.do_scrollbar_toggle() + + def key_zoom_normal(self): + self.zoom_orig () + + def key_search(self): + self.start_search() + + # bindings that should be moved to Terminator as they all just call + # a function of Terminator. It would be cleaner if TerminatorTerm + # has absolutely no reference to Terminator. + # N (next) - P (previous) - O (horizontal) - E (vertical) - W (close) + def key_new_root_tab(self): + self.terminator.newtab (self, True) + + def key_go_next(self): + self.terminator.go_next (self) + + def key_go_prev(self): + self.terminator.go_prev (self) + + def key_go_up(self): + self.terminator.go_up (self) + + def key_go_down(self): + self.terminator.go_down (self) + + def key_go_left(self): + self.terminator.go_left (self) + + def key_go_right(self): + self.terminator.go_right (self) + + def key_split_horiz(self): + self.emit('split-horiz') + + def key_split_vert(self): + self.emit('split-vert') + + def key_close_term(self): + self.terminator.closeterm (self) + + def key_new_tab(self): + self.terminator.newtab(self) + + def key_resize_up(self): + self.terminator.resizeterm (self, 'Up') + + def key_resize_down(self): + self.terminator.resizeterm (self, 'Down') + + def key_resize_left(self): + self.terminator.resizeterm (self, 'Left') + + def key_resize_right(self): + self.terminator.resizeterm (self, 'Right') + + def key_move_tab_right(self): + self.terminator.move_tab (self, 'right') + + def key_move_tab_left(self): + self.terminator.move_tab (self, 'left') + + def key_toggle_zoom(self): + self.terminator.toggle_zoom (self) + + def key_scaled_zoom(self): + self.terminator.toggle_zoom (self, True) + + def key_next_tab(self): + self.terminator.next_tab (self) + + def key_prev_tab(self): + self.terminator.previous_tab (self) + + def key_switch_to_tab_1(self): + self.terminator.switch_to_tab (self, 0) + + def key_switch_to_tab_2(self): + self.terminator.switch_to_tab (self, 1) + + def key_switch_to_tab_3(self): + self.terminator.switch_to_tab (self, 2) + + def key_switch_to_tab_4(self): + self.terminator.switch_to_tab (self, 3) + + def key_switch_to_tab_5(self): + self.terminator.switch_to_tab (self, 4) + + def key_switch_to_tab_6(self): + self.terminator.switch_to_tab (self, 5) + + def key_switch_to_tab_7(self): + self.terminator.switch_to_tab (self, 6) + + def key_switch_to_tab_8(self): + self.terminator.switch_to_tab (self, 7) + + def key_switch_to_tab_9(self): + self.terminator.switch_to_tab (self, 8) + + def key_switch_to_tab_10(self): + self.terminator.switch_to_tab (self, 9) + + def key_reset(self): + self.vte.reset (True, False) + + def key_reset_clear(self): + self.vte.reset (True, True) + + def key_group_all(self): + self.group_all(self) + + def key_ungroup_all(self): + self.ungroup_all(self) + + def key_group_tab(self): + self.group_tab(self) + + def key_ungroup_tab(self): + self.ungroup_tab(self) + + def key_new_window(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.terminator.origcwd, 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,]) +# End key events + gobject.type_register(Terminal) # vim: set expandtab ts=4 sw=4: From 565257672a30199e11c179fe8b1de0e7baf38b46 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 28 Oct 2009 23:07:42 +0000 Subject: [PATCH 103/331] fix terminal spawning and font zooming --- terminatorlib/terminal.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 59245370..1f6f1249 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -9,7 +9,9 @@ import pygtk pygtk.require('2.0') import gtk import gobject +import pango import re +import subprocess from util import dbg, err, gerr, widget_pixbuf import util @@ -770,13 +772,39 @@ class Terminal(gtk.VBox): """Feed the supplied text to VTE""" self.vte.feed_child(text) + def zoom_in(self): + """Increase the font size""" + self.zoom_font(True) + + def zoom_out(self): + """Decrease the font size""" + self.zoom_font(False) + + def zoom_font(self, zoom_in): + """Change the font size""" + pangodesc = self.vte.get_font() + fontsize = pangodesc.get_size() + + if fontsize > pango.SCALE and not zoom_in: + fontsize -= pango.SCALE + elif zoom_in: + fontsize += pango.SCALE + + pangodesc.set_size(fontsize) + self.vte.set_font(pangodesc) + + def zoom_orig(self): + """Restore original font size""" + print "restoring font to: %s" % self.config['font'] + self.vte.set_font(pango.FontDescription(self.config['font'])) + # There now begins a great list of keyboard event handlers # FIXME: Probably a bunch of these are wrong. TEST! def key_zoom_in(self): - self.zoom(True) + self.zoom_in() def key_zoom_out(self): - self.zoom(False) + self.zoom_out() def key_copy(self): self.vte.copy_clipboard() @@ -913,7 +941,7 @@ class Terminal(gtk.VBox): if not os.path.isabs(cmd): # Command is not an absolute path. Figure out where we are - cmd = os.path.join (self.terminator.origcwd, sys.argv[0]) + cmd = os.path.join (self.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') From 8d19561f8527aeaac193cf2de3e9baf8cd01e35c Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 4 Nov 2009 18:06:34 +0000 Subject: [PATCH 104/331] fix up Terminal some more and make the searching work, and include a hacky backwards search --- terminatorlib/searchbar.py | 107 ++++++++++++++++++++++++++++++++++--- terminatorlib/terminal.py | 21 ++++++-- 2 files changed, 118 insertions(+), 10 deletions(-) diff --git a/terminatorlib/searchbar.py b/terminatorlib/searchbar.py index f4669046..9af5cdd4 100755 --- a/terminatorlib/searchbar.py +++ b/terminatorlib/searchbar.py @@ -7,26 +7,37 @@ import gtk import gobject from translation import _ +from config import Config +from util import dbg # pylint: disable-msg=R0904 class Searchbar(gtk.HBox): """Class implementing the Searchbar widget""" __gsignals__ = { - 'do-search': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), - 'next-search': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'end-search': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), } entry = None reslabel = None next = None + prev = None + + vte = None + config = None + + searchstring = None + searchrow = None + + searchits = None def __init__(self): """Class initialiser""" gtk.HBox.__init__(self) self.__gobject_init__() + self.config = Config() + # Search text self.entry = gtk.Entry() self.entry.set_activates_default(True) @@ -59,14 +70,25 @@ class Searchbar(gtk.HBox): self.next = gtk.Button(_('Next')) self.next.connect('clicked', self.next_search) + # Previous Button + self.prev = gtk.Button(_('Prev')) + self.prev.connect('clicked', self.prev_search) + self.pack_start(label, False) self.pack_start(self.entry) self.pack_start(self.reslabel, False) + self.pack_start(self.prev, False, False) self.pack_start(self.next, False, False) self.pack_end(close, False, False) self.hide() + def get_vte(self): + """Find our parent widget""" + parent = self.get_parent() + if parent: + self.vte = parent.vte + # pylint: disable-msg=W0613 def search_keypress(self, widget, event): """Handle keypress events""" @@ -74,17 +96,88 @@ class Searchbar(gtk.HBox): if key == 'Escape': self.end_search() + def start_search(self): + """Show ourselves""" + if not self.vte: + self.get_vte() + + self.show() + self.entry.grab_focus() + def do_search(self, widget): """Trap and re-emit the clicked signal""" - self.emit('do-search', widget) + self.searchhits = [] + searchtext = self.entry.get_text() + if searchtext == '': + return + + if searchtext != self.searchstring: + self.searchrow = self.get_vte_buffer_range()[0] + self.searchstring = searchtext + + self.reslabel.set_text(_("Searching scrollback")) + self.next_search(None) def next_search(self, widget): - """Trap and re-emit the next-search signal""" - self.emit('next-search', widget) + """Search forwards and jump to the next result, if any""" + # FIXME: I think we should emit a signal and have Terminal() do this. + startrow,endrow = self.get_vte_buffer_range() + while True: + if self.searchrow == endrow: + self.searchrow = startrow + self.reslabel.set_text(_("Finished search")) + self.next.hide() + self.prev.hide() + return + buffer = self.vte.get_text_range(self.searchrow, 0, + self.searchrow, -1, + self.search_character) - def end_search(self, widget): + index = buffer.find(self.searchstring) + if index != -1: + self.searchhits.append(self.searchrow) + self.search_hit(self.searchrow) + self.searchrow += 1 + return + self.searchrow += 1 + + def prev_search(self, widget): + """Jump back to the previous search""" + row = self.searchhits.pop() + position = self.get_parent().scrollbar_position() + while row >= position: + row = self.searchhits.pop() + self.search_hit(row) + self.searchrow -= 1 + + def search_hit(self, row): + """Update the UI for a search hit""" + dbg('Searchbar::search_hit row %d, history of %d' % (row, + len(self.searchhits))) + self.reslabel.set_text("%s %d" % (_('Found at row'), row)) + self.get_parent().scrollbar_jump(row) + self.next.show() + if len(self.searchhits) > 1: + self.prev.show() + else: + self.prev.hide() + + def search_character(self, widget, col, row, junk): + """We have to have a callback for each character""" + return(True) + + def get_vte_buffer_range(self): + """Get the range of a vte widget""" + column, endrow = self.vte.get_cursor_position() + startrow = max(0, endrow - self.config['scrollback_lines']) + return(startrow, endrow) + + def end_search(self, widget=None): """Trap and re-emit the end-search signal""" - self.emit('end-search', widget) + self.searchrow = 0 + self.searchstring = None + self.reslabel.set_text('') + self.emit('end-search') def get_search_term(self): """Return the currently set search term""" diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 1f6f1249..fc54f4d5 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -110,6 +110,7 @@ class Terminal(gtk.VBox): self.titlebar.connect('create-group', self.really_create_group) self.searchbar = Searchbar() + self.searchbar.connect('end-search', self.on_search_done) self.show() self.pack_start(self.titlebar, False) @@ -645,6 +646,20 @@ class Terminal(gtk.VBox): def on_vte_focus_in(self, widget, event): self.emit('focus-in') + def scrollbar_jump(self, position): + """Move the scrollbar to a particular row""" + self.scrollbar.set_value(position) + + def scrollbar_position(self): + """Return the current position of the scrollbar""" + return(self.scrollbar.get_value()) + + def on_search_done(self, widget): + """We've finished searching, so clean up""" + self.searchbar.hide() + self.scrollbar.set_value(self.vte.get_cursor_position()[1]) + self.vte.grab_focus() + def on_edit_done(self, widget): """A child widget is done editing a label, return focus to VTE""" self.vte.grab_focus() @@ -761,7 +776,7 @@ class Terminal(gtk.VBox): def paste_clipboard(self, primary=False): """Paste one of the two clipboards""" - for term in self.terminator.get_target_terms(): + for term in self.terminator.get_target_terms(self): if primary: term.vte.paste_primary() else: @@ -810,7 +825,7 @@ class Terminal(gtk.VBox): self.vte.copy_clipboard() def key_paste(self): - self.vte-paste_clipboard() + self.vte.paste_clipboard() def key_toggle_scrollbar(self): self.do_scrollbar_toggle() @@ -819,7 +834,7 @@ class Terminal(gtk.VBox): self.zoom_orig () def key_search(self): - self.start_search() + self.searchbar.start_search() # bindings that should be moved to Terminator as they all just call # a function of Terminator. It would be cleaner if TerminatorTerm From 55c79f56d538343de2ba21490f1ecd3e9b572012 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 4 Nov 2009 20:41:01 +0000 Subject: [PATCH 105/331] remove some cruft and replace the hacky backwards search with a more correct one. There are still some issues though --- terminatorlib/searchbar.py | 56 ++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/terminatorlib/searchbar.py b/terminatorlib/searchbar.py index 9af5cdd4..b7b982d6 100755 --- a/terminatorlib/searchbar.py +++ b/terminatorlib/searchbar.py @@ -68,10 +68,14 @@ class Searchbar(gtk.HBox): # Next Button self.next = gtk.Button(_('Next')) + self.next.show() + self.next.set_sensitive(False) self.next.connect('clicked', self.next_search) # Previous Button self.prev = gtk.Button(_('Prev')) + self.prev.show() + self.prev.set_sensitive(False) self.prev.connect('clicked', self.prev_search) self.pack_start(label, False) @@ -106,7 +110,6 @@ class Searchbar(gtk.HBox): def do_search(self, widget): """Trap and re-emit the clicked signal""" - self.searchhits = [] searchtext = self.entry.get_text() if searchtext == '': return @@ -116,18 +119,17 @@ class Searchbar(gtk.HBox): self.searchstring = searchtext self.reslabel.set_text(_("Searching scrollback")) + self.next.set_sensitive(True) + self.prev.set_sensitive(True) self.next_search(None) def next_search(self, widget): """Search forwards and jump to the next result, if any""" - # FIXME: I think we should emit a signal and have Terminal() do this. startrow,endrow = self.get_vte_buffer_range() while True: if self.searchrow == endrow: self.searchrow = startrow - self.reslabel.set_text(_("Finished search")) - self.next.hide() - self.prev.hide() + self.reslabel.set_text(_('No more results')) return buffer = self.vte.get_text_range(self.searchrow, 0, self.searchrow, -1, @@ -135,32 +137,38 @@ class Searchbar(gtk.HBox): index = buffer.find(self.searchstring) if index != -1: - self.searchhits.append(self.searchrow) self.search_hit(self.searchrow) self.searchrow += 1 return self.searchrow += 1 + # FIXME: There is an issue in switching search direction, probably because + # we increment/decrement self.searchrow after each search iteration def prev_search(self, widget): """Jump back to the previous search""" - row = self.searchhits.pop() - position = self.get_parent().scrollbar_position() - while row >= position: - row = self.searchhits.pop() - self.search_hit(row) - self.searchrow -= 1 + startrow,endrow = self.get_vte_buffer_range() + while True: + if self.searchrow == startrow: + self.searchrow = endrow + self.reslabel.set_text(_('No more results')) + return + buffer = self.vte.get_text_range(self.searchrow, 0, + self.searchrow, -1, + self.search_character) + + index = buffer.find(self.searchstring) + if index != -1: + self.search_hit(self.searchrow) + self.searchrow -= 1 + return + self.searchrow -= 1 def search_hit(self, row): """Update the UI for a search hit""" - dbg('Searchbar::search_hit row %d, history of %d' % (row, - len(self.searchhits))) self.reslabel.set_text("%s %d" % (_('Found at row'), row)) self.get_parent().scrollbar_jump(row) self.next.show() - if len(self.searchhits) > 1: - self.prev.show() - else: - self.prev.hide() + self.prev.show() def search_character(self, widget, col, row, junk): """We have to have a callback for each character""" @@ -183,16 +191,4 @@ class Searchbar(gtk.HBox): """Return the currently set search term""" return(self.entry.get_text()) - def set_search_label(self, string = ''): - """Set the search label""" - self.reslabel.set_text(string) - - def hide_next(self): - """Hide the Next button""" - self.next.hide() - - def show_next(self): - """Show the Next button""" - self.next.show() - gobject.type_register(Searchbar) From 830863d71123908cd5096550a0cf6ccb62a5b5e9 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 4 Nov 2009 23:28:09 +0000 Subject: [PATCH 106/331] more group work --- terminatorlib/newterminator.py | 15 +++++++++++++-- terminatorlib/terminal.py | 23 ++++++++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index 7df9d86f..74d46c3a 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -49,8 +49,8 @@ class Terminator(Borg): def register_terminal(self, terminal): """Register a new terminal widget""" - self.terminals.append(terminal) + terminal.connect('ungroup-all', self.ungroup_all) def deregister_terminal(self, terminal): """De-register a terminal widget""" @@ -63,10 +63,21 @@ class Terminator(Borg): for terminal in self.terminals: terminal.reconfigure() + def create_group(self, name): + """Create a new group""" + if name not in self.groups: + self.groups.append(name) + + def ungroup_all(self, widget): + """Remove all groups""" + for terminal in self.terminals: + terminal.set_group(None, None) + self.groups = [] + def group_hoover(self): """Clean out unused groups""" - if self.config['autoclean_groups']: + if self.autocleangroups: todestroy = [] for group in self.groups: for terminal in self.terminals: diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index fc54f4d5..41b1887a 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -270,7 +270,7 @@ class Terminal(gtk.VBox): menu = gtk.Menu() groupitem = None - item = gtk.MenuItem(_('Assign to group...')) + item = gtk.MenuItem(_('New group...')) item.connect('activate', self.create_group) menu.append(item) @@ -374,15 +374,32 @@ class Terminal(gtk.VBox): return(widget_x, menu_y, 1) + def set_group(self, item, name): + """Set a particular group""" + if self.group == name: + # already in this group, no action needed + return + self.group = name + self.titlebar.set_group_label(name) + self.terminator.group_hoover() + def create_group(self, item): """Trigger the creation of a group via the titlebar (because popup windows are really lame)""" self.titlebar.create_group() - def really_create_group(self, groupname): + def really_create_group(self, widget, groupname): """The titlebar has spoken, let a group be created""" self.terminator.create_group(groupname) - self.group = groupname + self.set_group(None, groupname) + + def ungroup(self, widget, data): + """Remove a group""" + # FIXME: Could we emit and have Terminator do this? + for term in self.terminator.terminals: + if term.group == data: + term.set_group(None, None) + self.terminator.group_hoover() def set_groupsend(self, widget, value): """Set the groupsend mode""" From c60aae8bedce94a1e50a14e3553e29e30681b6c3 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 4 Nov 2009 23:42:54 +0000 Subject: [PATCH 107/331] make closing of group terminals work --- terminatorlib/newterminator.py | 6 ++++++ terminatorlib/terminal.py | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index 74d46c3a..cf6be427 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -74,6 +74,12 @@ class Terminator(Borg): terminal.set_group(None, None) self.groups = [] + def closegroupedterms(self, group): + """Close all terminals in a group""" + for terminal in self.terminals: + if terminal.group == group: + terminal.close() + def group_hoover(self): """Clean out unused groups""" diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 41b1887a..f0ba5184 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -126,6 +126,10 @@ class Terminal(gtk.VBox): if self.config['http_proxy'] and self.config['http_proxy'] != '': os.putenv('http_proxy', self.config['http_proxy']) + def close(self): + """Close ourselves""" + self.emit('close-term') + def create_terminalbox(self): """Create a GtkHBox containing the terminal and a scrollbar""" @@ -315,7 +319,7 @@ class Terminal(gtk.VBox): item = gtk.MenuItem(_('Close group %s') % self.group) item.connect('activate', lambda x: - self.terminator.closegroupedterms(self)) + self.terminator.closegroupedterms(self.group)) menu.append(item) menu.append(gtk.MenuItem()) From 6b4c660cbc565a47c1320bb8e167be329bdfa243 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 7 Nov 2009 01:40:43 +0000 Subject: [PATCH 108/331] Start implementing our Paned container and switch to it --- terminatorlib/container.py | 14 ++---- terminatorlib/paned.py | 94 ++++++++++++++++++++++++++++++++++++++ terminatorlib/searchbar.py | 1 + terminatorlib/terminal.py | 5 +- terminatorlib/window.py | 13 +++--- 5 files changed, 109 insertions(+), 18 deletions(-) create mode 100755 terminatorlib/paned.py diff --git a/terminatorlib/container.py b/terminatorlib/container.py index cd01c609..bba4f7dd 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -22,12 +22,7 @@ class Container(object): 'zoomed' : 1, 'maximised' : 2 } - signals = [ {'name': 'group-hoover-needed', - 'flags': gobject.SIGNAL_RUN_LAST, - 'return_type': gobject.TYPE_BOOLEAN, - 'param_types': () - } - ] + signals = [] def __init__(self): """Class initialiser""" @@ -38,16 +33,13 @@ class Container(object): def register_signals(self, widget): """Register gobject signals in a way that avoids multiple inheritance""" for signal in self.signals: + dbg("Container:: registering signal for %s on %s" % (signal['name'], widget)) gobject.signal_new(signal['name'], widget, signal['flags'], signal['return_type'], signal['param_types']) - def emit(self, signal): - """Emit a gobject signal""" - raise NotImplementedError('emit') - def get_offspring(self): """Return a list of child widgets, if any""" return(self.children) @@ -86,7 +78,7 @@ class Container(object): return(False) self.terminator.deregister_terminal(widget) - self.emit('need_group_hoover') + self.terminator.group_hoover() return(True) def resizeterm(self, widget, keyname): diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py new file mode 100755 index 00000000..f05da200 --- /dev/null +++ b/terminatorlib/paned.py @@ -0,0 +1,94 @@ +#!/usr/bin/python +# Terminator by Chris Jones +# GPL v2 only +"""paned.py - a base Paned container class and the vertical/horizontal +variants""" + +import gobject +import gtk + +from newterminator import Terminator +from container import Container + +# pylint: disable-msg=R0921 +class Paned(Container): + """Base class for Paned Containers""" + cnxids = None + + def __init__(self): + """Class initialiser""" + self.terminator = Terminator() + self.cnxids = {} + + Container.__init__(self) + gobject.type_register(HPaned) + self.register_signals(HPaned) + + def split_axis(self, widget, vertical=True): + """Default axis splitter. This should be implemented by subclasses""" + raise NotImplementedError('split_axis') + + def add(self, widget): + """Add a widget to the container""" + if len(self.children) == 0: + self.pack1(widget, True, True) + self.children.append(widget) + elif len(self.children) == 1: + self.pack2(widget, True, True) + self.children.append(widget) + else: + raise ValueError('already have two children') + + self.cnxids[widget] = [] + self.cnxids[widget].append(widget.connect('close-term', + self.wrapcloseterm)) + # FIXME: somehow propagate the title-change signal to the Window + self.cnxids[widget].append(widget.connect('split-horiz', + self.split_horiz)) + self.cnxids[widget].append(widget.connect('split-vert', + self.split_vert)) + + def remove(self, widget): + """Remove a widget from the container""" + gtk.Paned.remove(self, widget) + for cnxid in self.cnxids[widget]: + widget.disconnect(cnxid) + del(self.cnxids[widget]) + self.children.remove(widget) + return(True) + + def wrapcloseterm(self, widget): + """A child terminal has closed, so this container must die""" + if self.closeterm(widget): + parent = self.get_parent() + parent.remove(self) + + # At this point we only have one child, which is the surviving term + sibling = self.children[0] + self.remove(sibling) + parent.add(sibling) + del(self) + else: + print "self.closeterm failed" + + def resizeterm(self, widget, keyname): + """Handle a keyboard event requesting a terminal resize""" + raise NotImplementedError('resizeterm') + +class HPaned(Paned, gtk.HPaned): + """Merge gtk.HPaned into our base Paned Container""" + def __init__(self): + """Class initialiser""" + Paned.__init__(self) + gtk.HPaned.__init__(self) + +class VPaned(Paned, gtk.VPaned): + """Merge gtk.VPaned into our base Paned Container""" + def __init__(self): + """Class initialiser""" + Paned.__init__(self) + gtk.VPaned.__init__(self) + +gobject.type_register(HPaned) +gobject.type_register(VPaned) +# vim: set expandtab ts=4 sw=4: diff --git a/terminatorlib/searchbar.py b/terminatorlib/searchbar.py index b7b982d6..9f0da760 100755 --- a/terminatorlib/searchbar.py +++ b/terminatorlib/searchbar.py @@ -86,6 +86,7 @@ class Searchbar(gtk.HBox): self.pack_end(close, False, False) self.hide() + self.set_no_show_all(True) def get_vte(self): """Find our parent widget""" diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index f0ba5184..ac636ce1 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -752,7 +752,10 @@ class Terminal(gtk.VBox): self.vte.feed(_('Unable to find a shell')) return(-1) - os.putenv('WINDOWID', '%s' % self.vte.get_parent_window().xid) + try: + os.putenv('WINDOWID', '%s' % self.vte.get_parent_window().xid) + except AttributeError: + pass self.pid = self.vte.fork_command(command=shell, argv=args, envv=[], loglastlog=login, logwtmp=update_records, diff --git a/terminatorlib/window.py b/terminatorlib/window.py index ea00964c..1d86f7c3 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -13,6 +13,7 @@ from version import APP_NAME from container import Container from newterminator import Terminator from terminal import Terminal +from paned import HPaned,VPaned try: import deskbar.core.keybinder as bindkey @@ -37,6 +38,7 @@ class Window(Container, gtk.Window): def __init__(self): """Class initialiser""" self.terminator = Terminator() + self.terminator.window = self self.cnxids = [] Container.__init__(self) @@ -182,21 +184,20 @@ class Window(Container, gtk.Window): def split_axis(self, widget, vertical=True): """Split the window""" - # FIXME: this .remove isn't good enough, what about signal handlers? self.remove(widget) # FIXME: we should be creating proper containers, not these gtk widgets if vertical: - container = gtk.VPaned() + container = VPaned() else: - container = gtk.HPaned() + container = HPaned() sibling = Terminal() self.terminator.register_terminal(sibling) - container.pack1(widget, True, True) - container.pack2(sibling, True, True) - container.show() + for term in [widget, sibling]: + container.add(term) + container.show_all() self.add(container) sibling.spawn_child() From 53527319842126943a6e4d8245f0e1cdab11b5f1 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 8 Nov 2009 01:06:31 +0000 Subject: [PATCH 109/331] make paneds be splittable, set their initial position properly and don't call reconfigure() all the time --- terminatorlib/paned.py | 53 ++++++++++++++++++++++++++++++++------- terminatorlib/terminal.py | 10 +++++++- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index f05da200..f4adb045 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -8,6 +8,7 @@ import gobject import gtk from newterminator import Terminator +from terminal import Terminal from container import Container # pylint: disable-msg=R0921 @@ -24,9 +25,35 @@ class Paned(Container): gobject.type_register(HPaned) self.register_signals(HPaned) + def set_initial_position(self, widget, event): + """Set the initial position of the widget""" + if isinstance(self, gtk.VPaned): + position = self.allocation.height / 2 + else: + position = self.allocation.width / 2 + + print "Setting position to: %d" % position + self.set_position(position) + self.disconnect(self.cnxids['init']) + del(self.cnxids['init']) + def split_axis(self, widget, vertical=True): """Default axis splitter. This should be implemented by subclasses""" - raise NotImplementedError('split_axis') + self.remove(widget) + if vertical: + container = VPaned() + else: + container = HPaned() + + sibling = Terminal() + self.terminator.register_terminal(sibling) + sibling.spawn_child() + + container.add(widget) + container.add(sibling) + + self.add(container) + self.show_all() def add(self, widget): """Add a widget to the container""" @@ -34,19 +61,23 @@ class Paned(Container): self.pack1(widget, True, True) self.children.append(widget) elif len(self.children) == 1: - self.pack2(widget, True, True) + if self.get_child1(): + self.pack2(widget, True, True) + else: + self.pack1(widget, True, True) self.children.append(widget) else: raise ValueError('already have two children') self.cnxids[widget] = [] - self.cnxids[widget].append(widget.connect('close-term', - self.wrapcloseterm)) - # FIXME: somehow propagate the title-change signal to the Window - self.cnxids[widget].append(widget.connect('split-horiz', - self.split_horiz)) - self.cnxids[widget].append(widget.connect('split-vert', - self.split_vert)) + if isinstance(widget, Terminal): + self.cnxids[widget].append(widget.connect('close-term', + self.wrapcloseterm)) + # FIXME: somehow propagate the title-change signal to the Window + self.cnxids[widget].append(widget.connect('split-horiz', + self.split_horiz)) + self.cnxids[widget].append(widget.connect('split-vert', + self.split_vert)) def remove(self, widget): """Remove a widget from the container""" @@ -81,6 +112,8 @@ class HPaned(Paned, gtk.HPaned): """Class initialiser""" Paned.__init__(self) gtk.HPaned.__init__(self) + self.cnxids['init'] = self.connect('expose-event', + self.set_initial_position) class VPaned(Paned, gtk.VPaned): """Merge gtk.VPaned into our base Paned Container""" @@ -88,6 +121,8 @@ class VPaned(Paned, gtk.VPaned): """Class initialiser""" Paned.__init__(self) gtk.VPaned.__init__(self) + self.cnxids['init'] = self.connect('expose-event', + self.set_initial_position) gobject.type_register(HPaned) gobject.type_register(VPaned) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index ac636ce1..dcb77d03 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -72,6 +72,8 @@ class Terminal(gtk.VBox): composite_support = None + cnxid = None + def __init__(self): """Class initialiser""" gtk.VBox.__init__(self) @@ -252,7 +254,7 @@ class Terminal(gtk.VBox): self.vte.connect('enter_notify_event', self.on_vte_notify_enter) - self.vte.connect_after('realize', self.reconfigure) + self.cnxid = self.vte.connect_after('realize', self.reconfigure) def create_popup_group_menu(self, widget, event = None): """Pop up a menu for the group widget""" @@ -423,6 +425,12 @@ class Terminal(gtk.VBox): def reconfigure(self, widget=None): """Reconfigure our settings""" + dbg('Terminal::reconfigure') + if self.cnxid: + dbg('Terminal::reconfigure: disconnecting') + self.vte.disconnect(self.cnxid) + self.cnxid = None + # FIXME: actually reconfigure our settings pass From 5dd6ae0154f087a9fe62bca3551716c65faf3528 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 8 Nov 2009 23:06:26 +0000 Subject: [PATCH 110/331] improve container signal registration function to never double-register. add a fake resize-term signal to Paned for nested terminal resizes. Implement terminal resizing. --- terminatorlib/container.py | 17 +++++++++++------ terminatorlib/paned.py | 39 +++++++++++++++++++++++++++++++++++++- terminatorlib/terminal.py | 12 ++++++++---- 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index bba4f7dd..8f1f598c 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -32,13 +32,18 @@ class Container(object): def register_signals(self, widget): """Register gobject signals in a way that avoids multiple inheritance""" + existing = gobject.signal_list_names(widget) for signal in self.signals: - dbg("Container:: registering signal for %s on %s" % (signal['name'], widget)) - gobject.signal_new(signal['name'], - widget, - signal['flags'], - signal['return_type'], - signal['param_types']) + if signal['name'] in existing: + dbg('Container:: skipping signal %s for %s, already exists' % ( + signal['name'], widget)) + else: + dbg('Container:: registering signal for %s on %s' % (signal['name'], widget)) + gobject.signal_new(signal['name'], + widget, + signal['flags'], + signal['return_type'], + signal['param_types']) def get_offspring(self): """Return a list of child widgets, if any""" diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index f4adb045..fcc1af31 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -20,6 +20,10 @@ class Paned(Container): """Class initialiser""" self.terminator = Terminator() self.cnxids = {} + self.signals.append({'name': 'resize-term', + 'flags': gobject.SIGNAL_RUN_LAST, + 'return_type': gobject.TYPE_NONE, + 'param_types': (gobject.TYPE_STRING,)}) Container.__init__(self) gobject.type_register(HPaned) @@ -78,6 +82,11 @@ class Paned(Container): self.split_horiz)) self.cnxids[widget].append(widget.connect('split-vert', self.split_vert)) + self.cnxids[widget].append(widget.connect('resize-term', + self.resizeterm)) + elif isinstance(widget, gtk.Paned): + self.cnxids[widget].append(widget.connect('resize-term', + self.resizeterm)) def remove(self, widget): """Remove a widget from the container""" @@ -104,7 +113,35 @@ class Paned(Container): def resizeterm(self, widget, keyname): """Handle a keyboard event requesting a terminal resize""" - raise NotImplementedError('resizeterm') + if keyname in ['up', 'down'] and isinstance(self, gtk.VPaned): + # This is a key we can handle + position = self.get_position() + + if isinstance(widget, Terminal): + fontheight = widget.vte.get_char_height() + else: + fontheight = 10 + + if keyname == 'up': + self.set_position(position - fontheight) + else: + self.set_position(position + fontheight) + elif keyname in ['left', 'right'] and isinstance(self, gtk.HPaned): + # This is a key we can handle + position = self.get_position() + + if isinstance(widget, Terminal): + fontwidth = widget.vte.get_char_width() + else: + fontwidth = 10 + + if keyname == 'left': + self.set_position(position - fontwidth) + else: + self.set_position(position + fontwidth) + else: + # This is not a key we can handle + self.emit('resize-term', keyname) class HPaned(Paned, gtk.HPaned): """Merge gtk.HPaned into our base Paned Container""" diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index dcb77d03..7b594123 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -49,6 +49,8 @@ class Terminal(gtk.VBox): 'focus-in': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'zoom': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'maximise': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'resize-term': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_STRING,)), } TARGET_TYPE_VTE = 8 @@ -743,6 +745,8 @@ class Terminal(gtk.VBox): shell = None command = None + self.vte.grab_focus() + if self.config['use_custom_command']: command = self.config['custom_command'] @@ -906,16 +910,16 @@ class Terminal(gtk.VBox): self.terminator.newtab(self) def key_resize_up(self): - self.terminator.resizeterm (self, 'Up') + self.emit('resize-term', 'up') def key_resize_down(self): - self.terminator.resizeterm (self, 'Down') + self.emit('resize-term', 'down') def key_resize_left(self): - self.terminator.resizeterm (self, 'Left') + self.emit('resize-term', 'left') def key_resize_right(self): - self.terminator.resizeterm (self, 'Right') + self.emit('resize-term', 'right') def key_move_tab_right(self): self.terminator.move_tab (self, 'right') From 4284cf67eefb65f23331b80c928804c3596a7c6c Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 9 Nov 2009 22:33:17 +0000 Subject: [PATCH 111/331] Start making some navigation work --- terminatorlib/config.py | 2 ++ terminatorlib/newterminator.py | 37 +++++++++++++++++++++++++++------- terminatorlib/terminal.py | 25 +++++++++++------------ 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 2a078637..a3395743 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -91,6 +91,8 @@ DEFAULTS = { 'hide_tabbar' : False, 'scroll_tabbar' : False, 'alternate_screen_scroll': True, + 'split_to_group' : False, + 'autoclean_groups' : True, 'keybindings' : { 'zoom_in' : 'plus', 'zoom_out' : 'minus', diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index cf6be427..6eaf30e6 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -17,8 +17,6 @@ class Terminator(Borg): config = None keybindings = None - splittogroup = None - autocleangroups = None groupsend = None groupsend_type = {'all':0, 'group':1, 'off':2} @@ -37,10 +35,6 @@ class Terminator(Borg): self.groups = [] if not self.groupsend: self.groupsend = self.groupsend_type['group'] - if not self.splittogroup: - self.splittogroup = False - if not self.autocleangroups: - self.autocleangroups = True if not self.config: self.config = Config() if not self.keybindings: @@ -51,6 +45,7 @@ class Terminator(Borg): """Register a new terminal widget""" self.terminals.append(terminal) terminal.connect('ungroup-all', self.ungroup_all) + terminal.connect('navigate', self.navigate_terminal) def deregister_terminal(self, terminal): """De-register a terminal widget""" @@ -63,6 +58,34 @@ class Terminator(Borg): for terminal in self.terminals: terminal.reconfigure() + def navigate_terminal(self, terminal, direction): + """Nagivate around the terminals""" + current = self.terminals.index(terminal) + length = len(self.terminals) + next = None + + if length <= 1: + return + + print "Current term: %d" % current + print "Number of terms: %d" % length + + if direction == 'next': + next = current + 1 + if next >= length: + next = 0 + elif direction == 'prev': + next = current - 1 + if next < 0: + next = length - 1 + else: + raise NotImplementedError + # FIXME: Do the directional navigation + + if next is not None: + print "sending focus to term %d" % next + self.terminals[next].vte.grab_focus() + def create_group(self, name): """Create a new group""" if name not in self.groups: @@ -83,7 +106,7 @@ class Terminator(Borg): def group_hoover(self): """Clean out unused groups""" - if self.autocleangroups: + if self.config['autoclean_groups']: todestroy = [] for group in self.groups: for terminal in self.terminals: diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 7b594123..1f67b421 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -51,6 +51,8 @@ class Terminal(gtk.VBox): 'maximise': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'resize-term': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), + 'navigate': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_STRING,)), } TARGET_TYPE_VTE = 8 @@ -343,12 +345,12 @@ class Terminal(gtk.VBox): menu.append(gtk.MenuItem()) item = gtk.CheckMenuItem(_('Split to this group')) - item.set_active(self.terminator.splittogroup) + item.set_active(self.config['split_to_group']) item.connect('toggled', lambda x: self.do_splittogroup_toggle()) menu.append(item) item = gtk.CheckMenuItem(_('Autoclean groups')) - item.set_active(self.terminator.autocleangroups) + item.set_active(self.config['autoclean_groups']) item.connect('toggled', lambda x: self.do_autocleangroups_toggle()) menu.append(item) @@ -417,19 +419,16 @@ class Terminal(gtk.VBox): def do_splittogroup_toggle(self): """Toggle the splittogroup mode""" - # FIXME: Can we think of a smarter way of doing this than poking? - self.terminator.splittogroup = not self.terminator.splittogroup + self.config['split_to_group'] = not self.config['split_to_group'] def do_autocleangroups_toggle(self): """Toggle the autocleangroups mode""" - # FIXME: Can we think of a smarter way of doing this than poking? - self.terminator.autocleangroups = not self.terminator.autocleangroups + self.config['autoclean_groups'] = not self.config['autoclean_groups'] def reconfigure(self, widget=None): """Reconfigure our settings""" dbg('Terminal::reconfigure') if self.cnxid: - dbg('Terminal::reconfigure: disconnecting') self.vte.disconnect(self.cnxid) self.cnxid = None @@ -880,22 +879,22 @@ class Terminal(gtk.VBox): self.terminator.newtab (self, True) def key_go_next(self): - self.terminator.go_next (self) + self.emit('navigate', 'next') def key_go_prev(self): - self.terminator.go_prev (self) + self.emit('navigate', 'prev') def key_go_up(self): - self.terminator.go_up (self) + self.emit('navigate', 'up') def key_go_down(self): - self.terminator.go_down (self) + self.emit('navigate', 'down') def key_go_left(self): - self.terminator.go_left (self) + self.emit('navigate', 'left') def key_go_right(self): - self.terminator.go_right (self) + self.emit('navigate', 'right') def key_split_horiz(self): self.emit('split-horiz') From 095ba4dbe337b4240e48c49b4cb972e24e3e75e1 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 9 Nov 2009 22:35:55 +0000 Subject: [PATCH 112/331] decouple a little more --- terminatorlib/newterminator.py | 2 +- terminatorlib/terminal.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index 6eaf30e6..f251ea1b 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -84,7 +84,7 @@ class Terminator(Borg): if next is not None: print "sending focus to term %d" % next - self.terminals[next].vte.grab_focus() + self.terminals[next].grab_focus() def create_group(self, name): """Create a new group""" diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 1f67b421..88fff44b 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -667,6 +667,9 @@ class Terminal(gtk.VBox): pos = "bottom" return pos + def grab_focus(self): + self.vte.grab_focus() + def on_vte_focus(self, widget): self.emit('title-change', self.get_window_title()) From e5301362e465876b703983e42ca74d283d4195a7 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 14 Nov 2009 17:30:03 +0000 Subject: [PATCH 113/331] tidy up some pylint issues --- terminatorlib/container.py | 4 ---- terminatorlib/paned.py | 3 +++ terminatorlib/terminal.py | 3 ++- terminatorlib/window.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 8f1f598c..111748e7 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -61,10 +61,6 @@ class Container(object): """Default axis splitter. This should be implemented by subclasses""" raise NotImplementedError('split_axis') - def unsplit(self, widget, keep=False): - """Default unsplitter. This should be implemented by subclasses""" - raise NotImplementedError('unsplit') - def add(self, widget): """Add a widget to the container""" raise NotImplementedError('add') diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index fcc1af31..82ff697e 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -12,6 +12,7 @@ from terminal import Terminal from container import Container # pylint: disable-msg=R0921 +# pylint: disable-msg=E1101 class Paned(Container): """Base class for Paned Containers""" cnxids = None @@ -29,6 +30,7 @@ class Paned(Container): gobject.type_register(HPaned) self.register_signals(HPaned) + # pylint: disable-msg=W0613 def set_initial_position(self, widget, event): """Set the initial position of the widget""" if isinstance(self, gtk.VPaned): @@ -41,6 +43,7 @@ class Paned(Container): self.disconnect(self.cnxids['init']) del(self.cnxids['init']) + # pylint: disable-msg=W0613 def split_axis(self, widget, vertical=True): """Default axis splitter. This should be implemented by subclasses""" self.remove(widget) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 88fff44b..58b4e73d 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -47,7 +47,8 @@ class Terminal(gtk.VBox): 'tab-new': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'tab-top-new': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'focus-in': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), - 'zoom': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'zoom': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_BOOL,)), 'maximise': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'resize-term': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 1d86f7c3..f29f5c8c 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -13,7 +13,7 @@ from version import APP_NAME from container import Container from newterminator import Terminator from terminal import Terminal -from paned import HPaned,VPaned +from paned import HPaned, VPaned try: import deskbar.core.keybinder as bindkey From 0190f2dea95d7f475054d97ca8707b3858af34b2 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 14 Nov 2009 18:54:12 +0000 Subject: [PATCH 114/331] make signal registration more robust, and add a function to walk up the widget tree to find the top-level Window object --- terminatorlib/container.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 111748e7..9865b602 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -6,7 +6,7 @@ import gobject from config import Config -from util import dbg +from util import dbg, err # pylint: disable-msg=R0921 class Container(object): @@ -39,16 +39,29 @@ class Container(object): signal['name'], widget)) else: dbg('Container:: registering signal for %s on %s' % (signal['name'], widget)) - gobject.signal_new(signal['name'], - widget, - signal['flags'], - signal['return_type'], - signal['param_types']) + try: + gobject.signal_new(signal['name'], + widget, + signal['flags'], + signal['return_type'], + signal['param_types']) + except RuntimeError: + err('Container:: registering signal for %s on %s failed' % + (signal['name'], widget)) def get_offspring(self): """Return a list of child widgets, if any""" return(self.children) + def get_top_window(self, startpoint): + """Return the Window instance this container belongs to""" + widget = startpoint + parent = widget.get_parent() + while parent: + widget = parent + parent = widget.get_parent() + return(widget) + def split_horiz(self, widget): """Split this container horizontally""" return(self.split_axis(widget, True)) From 6f93a51b5ff841d0887e9a2e83ac4769badc1c8e Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 14 Nov 2009 18:55:07 +0000 Subject: [PATCH 115/331] fix stupid bug that registered all Paned widgets as HPaned ones. Fix ordering of adding children so things are never de-parented. Hook up terminal zooming signals. --- terminatorlib/paned.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index 82ff697e..75e8ad24 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -7,6 +7,7 @@ variants""" import gobject import gtk +from util import dbg, err from newterminator import Terminator from terminal import Terminal from container import Container @@ -27,8 +28,6 @@ class Paned(Container): 'param_types': (gobject.TYPE_STRING,)}) Container.__init__(self) - gobject.type_register(HPaned) - self.register_signals(HPaned) # pylint: disable-msg=W0613 def set_initial_position(self, widget, event): @@ -38,7 +37,7 @@ class Paned(Container): else: position = self.allocation.width / 2 - print "Setting position to: %d" % position + dbg("Paned::set_initial_position: Setting position to: %d" % position) self.set_position(position) self.disconnect(self.cnxids['init']) del(self.cnxids['init']) @@ -56,10 +55,12 @@ class Paned(Container): self.terminator.register_terminal(sibling) sibling.spawn_child() + self.add(container) + self.show_all() + container.add(widget) container.add(sibling) - self.add(container) self.show_all() def add(self, widget): @@ -87,9 +88,18 @@ class Paned(Container): self.split_vert)) self.cnxids[widget].append(widget.connect('resize-term', self.resizeterm)) + top_window = self.get_top_window(self) + self.cnxids[widget].append(widget.connect('zoom', + top_window.terminal_zoom)) + self.cnxids[widget].append(widget.connect('maximise', + top_window.terminal_zoom, + False)) elif isinstance(widget, gtk.Paned): - self.cnxids[widget].append(widget.connect('resize-term', + try: + self.cnxids[widget].append(widget.connect('resize-term', self.resizeterm)) + except TypeError: + err('Paned::add: %s has no signal resize-term' % widget) def remove(self, widget): """Remove a widget from the container""" @@ -112,7 +122,7 @@ class Paned(Container): parent.add(sibling) del(self) else: - print "self.closeterm failed" + dbg("Paned::wrapcloseterm: self.closeterm failed") def resizeterm(self, widget, keyname): """Handle a keyboard event requesting a terminal resize""" @@ -152,6 +162,7 @@ class HPaned(Paned, gtk.HPaned): """Class initialiser""" Paned.__init__(self) gtk.HPaned.__init__(self) + self.register_signals(HPaned) self.cnxids['init'] = self.connect('expose-event', self.set_initial_position) @@ -161,6 +172,7 @@ class VPaned(Paned, gtk.VPaned): """Class initialiser""" Paned.__init__(self) gtk.VPaned.__init__(self) + self.register_signals(VPaned) self.cnxids['init'] = self.connect('expose-event', self.set_initial_position) From 4ee756b5ec3054b0b0407f7a8448fdce34d14124 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 14 Nov 2009 18:55:51 +0000 Subject: [PATCH 116/331] fix up zoom signal, add an unzoom signal. generally make zooming work --- terminatorlib/terminal.py | 94 ++++++++++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 20 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 58b4e73d..d27905f7 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -47,9 +47,9 @@ class Terminal(gtk.VBox): 'tab-new': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'tab-top-new': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'focus-in': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), - 'zoom': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, - (gobject.TYPE_BOOL,)), + 'zoom': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'maximise': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'unzoom': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'resize-term': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), 'navigate': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, @@ -77,7 +77,8 @@ class Terminal(gtk.VBox): composite_support = None - cnxid = None + confcnxid = None + zoomcnxid = None def __init__(self): """Class initialiser""" @@ -259,7 +260,7 @@ class Terminal(gtk.VBox): self.vte.connect('enter_notify_event', self.on_vte_notify_enter) - self.cnxid = self.vte.connect_after('realize', self.reconfigure) + self.confcnxid = self.vte.connect_after('realize', self.reconfigure) def create_popup_group_menu(self, widget, event = None): """Pop up a menu for the group widget""" @@ -429,9 +430,9 @@ class Terminal(gtk.VBox): def reconfigure(self, widget=None): """Reconfigure our settings""" dbg('Terminal::reconfigure') - if self.cnxid: - self.vte.disconnect(self.cnxid) - self.cnxid = None + if self.confcnxid: + self.vte.disconnect(self.confcnxid) + self.confcnxid = None # FIXME: actually reconfigure our settings pass @@ -716,31 +717,78 @@ class Terminal(gtk.VBox): """Show the titlebar""" self.titlebar.show() + def get_zoom_data(self): + """Return a dict of information for Window""" + data = {} + data['old_font'] = self.vte.get_font() + data['old_char_height'] = self.vte.get_char_height() + data['old_char_width'] = self.vte.get_char_width() + data['old_allocation'] = self.vte.get_allocation() + data['old_padding'] = self.vte.get_padding() + data['old_columns'] = self.vte.get_column_count() + data['old_rows'] = self.vte.get_row_count() + data['old_parent'] = self.get_parent() + + return(data) + + def zoom_scale(self, widget, allocation, old_data): + """Scale our font correctly based on how big we are not vs before""" + self.disconnect(self.zoomcnxid) + self.zoomcnxid = None + + new_columns = self.vte.get_column_count() + new_rows = self.vte.get_row_count() + new_font = self.vte.get_font() + new_allocation = self.vte.get_allocation() + + old_alloc = {'x': old_data['old_allocation'].width - \ + old_data['old_padding'][0], + 'y': old_data['old_allocation'].height - \ + old_data['old_padding'][1] + } + + dbg('Terminal::zoom_scale: Resized from %dx%d to %dx%d' % ( + old_data['old_columns'], + old_data['old_rows'], + new_columns, + new_rows)) + + if (new_rows == old_data['old_rows'] or new_columns == old_data['old_columns']): + dbg('Terminal::zoom_scale: One axis unchanged, not scaling') + return + + old_area = old_data['old_columns'] * old_data['old_rows'] + new_area = new_columns * new_rows + area_factor = (new_area / old_area) / 2 + + new_font.set_size(old_data['old_font'].get_size() * area_factor) + self.vte.set_font(new_font) + def is_zoomed(self): """Determine if we are a zoomed terminal""" - widget = self.get_parent() - while True: - tmp = widget.get_parent() - if not tmp: - break - else: - widget = tmp + prop = None + parent = self.get_parent() + window = parent.get_top_window(self) try: - prop = widget.get_property('term-zoomed') + prop = window.get_property('term-zoomed') except TypeError: prop = False return(prop) - def zoom(self): + def zoom(self, widget=None): """Zoom ourself to fill the window""" self.emit('zoom') - def maximise(self): + def maximise(self, widget=None): """Maximise ourself to fill the window""" self.emit('maximise') + def unzoom(self, widget=None): + """Restore normal layout""" + self.emit('unzoom') + def spawn_child(self, widget=None): update_records = self.config['update_records'] login = self.config['login_shell'] @@ -849,7 +897,7 @@ class Terminal(gtk.VBox): def zoom_orig(self): """Restore original font size""" - print "restoring font to: %s" % self.config['font'] + dbg("Terminal::zoom_orig: restoring font to: %s" % self.config['font']) self.vte.set_font(pango.FontDescription(self.config['font'])) # There now begins a great list of keyboard event handlers @@ -931,10 +979,16 @@ class Terminal(gtk.VBox): self.terminator.move_tab (self, 'left') def key_toggle_zoom(self): - self.terminator.toggle_zoom (self) + if self.is_zoomed(): + self.unzoom() + else: + self.maximise() def key_scaled_zoom(self): - self.terminator.toggle_zoom (self, True) + if self.is_zoomed(): + self.unzoom() + else: + self.zoom() def key_next_tab(self): self.terminator.next_tab (self) From e93a95162d60f6f988a417fb946c7a1337f485c0 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 14 Nov 2009 18:56:34 +0000 Subject: [PATCH 117/331] Add a menu item to restore zoomed/maximised layouts --- terminatorlib/terminal_popup_menu.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/terminatorlib/terminal_popup_menu.py b/terminatorlib/terminal_popup_menu.py index 2a191ac1..9f18386c 100755 --- a/terminatorlib/terminal_popup_menu.py +++ b/terminatorlib/terminal_popup_menu.py @@ -108,6 +108,12 @@ class TerminalPopupMenu(object): item.connect('activate', terminal.maximise) menu.append(item) + menu.append(gtk.MenuItem()) + else: + item = gtk.MenuItem(_('_Restore all terminals')) + item.connect('activate', terminal.unzoom) + menu.append(item) + menu.append(gtk.MenuItem()) item = gtk.CheckMenuItem(_('Show _scrollbar')) From 6e0718c8295cb22762bd14b22dd9a4f0fdb39c65 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 14 Nov 2009 18:56:50 +0000 Subject: [PATCH 118/331] make zooming and unzooming work --- terminatorlib/window.py | 49 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index f29f5c8c..68c618c7 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -33,6 +33,7 @@ class Window(Container, gtk.Window): hidefunc = None cnxids = None + zoom_data = None term_zoomed = gobject.property(type=bool, default=False) def __init__(self): @@ -173,6 +174,7 @@ class Window(Container, gtk.Window): self.title.set_title)) self.cnxids.append(widget.connect('split-horiz', self.split_horiz)) self.cnxids.append(widget.connect('split-vert', self.split_vert)) + self.cnxids.append(widget.connect('unzoom', self.terminal_unzoom)) gtk.Window.add(self, widget) def remove(self, widget): @@ -194,14 +196,59 @@ class Window(Container, gtk.Window): sibling = Terminal() self.terminator.register_terminal(sibling) + self.add(container) + container.show_all() for term in [widget, sibling]: container.add(term) container.show_all() - self.add(container) sibling.spawn_child() + def terminal_zoom(self, widget, font_scale=True): + """Zoom a terminal widget""" + print font_scale + children = self.get_children() + + if widget in children: + # This widget is a direct child of ours and we're a Window + # so zooming is a no-op + return + + self.zoom_data = widget.get_zoom_data() + self.zoom_data['widget'] = widget + self.zoom_data['old_child'] = children[0] + self.zoom_data['font_scale'] = font_scale + + self.remove(self.zoom_data['old_child']) + self.zoom_data['old_parent'].remove(widget) + self.add(widget) + self.set_property('term_zoomed', True) + + if font_scale: + widget.zoomcnxid = widget.connect('size-allocate', + widget.zoom_scale, self.zoom_data) + + widget.grab_focus() + + def terminal_unzoom(self, widget): + """Restore normal terminal layout""" + if not self.get_property('term_zoomed'): + # We're not zoomed anyway + dbg('Window::terminal_unzoom: not zoomed, no-op') + return + + widget = self.zoom_data['widget'] + if self.zoom_data['font_scale']: + widget.vte.set_font(self.zoom_data['old_font']) + + self.remove(widget) + self.add(self.zoom_data['old_child']) + self.zoom_data['old_parent'].add(widget) + widget.grab_focus() + self.zoom_data = None + self.set_property('term_zoomed', False) + class WindowTitle(object): """Class to handle the setting of the window title""" From 29d661b26ade10b56936cafd73e5d5c5b07fb6ba Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 14 Nov 2009 22:57:35 +0000 Subject: [PATCH 119/331] self.signals shouldn't be a class variable. add helper functions to connect child widget signals and disconnect them. --- terminatorlib/container.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 9865b602..70fd4808 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -22,11 +22,14 @@ class Container(object): 'zoomed' : 1, 'maximised' : 2 } - signals = [] + signals = None + cnxids = None def __init__(self): """Class initialiser""" self.children = [] + self.signals = [] + self.cnxids = {} self.config = Config() self.state_zoomed = self.states_zoom['none'] @@ -49,6 +52,28 @@ class Container(object): err('Container:: registering signal for %s on %s failed' % (signal['name'], widget)) + def connect_child(self, widget, signal, handler, data=None): + """Register the requested signal and record its connection ID""" + if not self.cnxids.has_key(widget): + self.cnxids[widget] = [] + + if data is not None: + self.cnxids[widget].append(widget.connect(signal, handler, data)) + dbg('Container::connect_child: registering %s(%s) to handle %s::%s' % + (handler.__name__, data, widget.__class__.__name__, signal)) + else: + self.cnxids[widget].append(widget.connect(signal, handler)) + dbg('Container::connect_child: registering %s to handle %s::%s' % + (handler.__name__, widget.__class__.__name__, signal)) + + def disconnect_child(self, widget): + """De-register the signals for a child""" + if self.cnxids.has_key(widget): + for cnxid in self.cnxids[widget]: + # FIXME: Look up the IDs to print a useful debugging message + widget.disconnect(cnxid) + del(self.cnxids[widget]) + def get_offspring(self): """Return a list of child widgets, if any""" return(self.children) From 0f7a9aa40ba7563de18adb2441a079d5dfcf0de5 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 14 Nov 2009 22:58:22 +0000 Subject: [PATCH 120/331] Improve some error message and switch to using new Container methods for handling child signals. --- terminatorlib/paned.py | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index 75e8ad24..6d1fa17b 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -16,18 +16,16 @@ from container import Container # pylint: disable-msg=E1101 class Paned(Container): """Base class for Paned Containers""" - cnxids = None def __init__(self): """Class initialiser""" self.terminator = Terminator() - self.cnxids = {} + Container.__init__(self) self.signals.append({'name': 'resize-term', 'flags': gobject.SIGNAL_RUN_LAST, 'return_type': gobject.TYPE_NONE, 'param_types': (gobject.TYPE_STRING,)}) - Container.__init__(self) # pylint: disable-msg=W0613 def set_initial_position(self, widget, event): @@ -75,38 +73,35 @@ class Paned(Container): self.pack1(widget, True, True) self.children.append(widget) else: - raise ValueError('already have two children') + raise ValueError('Paned widgets can only have two children') self.cnxids[widget] = [] if isinstance(widget, Terminal): - self.cnxids[widget].append(widget.connect('close-term', - self.wrapcloseterm)) - # FIXME: somehow propagate the title-change signal to the Window - self.cnxids[widget].append(widget.connect('split-horiz', - self.split_horiz)) - self.cnxids[widget].append(widget.connect('split-vert', - self.split_vert)) - self.cnxids[widget].append(widget.connect('resize-term', - self.resizeterm)) top_window = self.get_top_window(self) - self.cnxids[widget].append(widget.connect('zoom', - top_window.terminal_zoom)) - self.cnxids[widget].append(widget.connect('maximise', - top_window.terminal_zoom, - False)) + + # FIXME: somehow propagate the title-change signal to the Window + signals = {'close-term': self.wrapcloseterm, + 'split-horiz': self.split_horiz, + 'split-vert': self.split_vert, + 'resize-term': self.resizeterm, + 'zoom': top_window.terminal_zoom} + + for signal in signals: + self.connect_child(widget, signal, signals[signal]) + + self.connect_child(widget, 'maximise', top_window.terminal_zoom, + False) + elif isinstance(widget, gtk.Paned): try: - self.cnxids[widget].append(widget.connect('resize-term', - self.resizeterm)) + self.connect_child(widget, 'resize-term', self.resizeterm) except TypeError: err('Paned::add: %s has no signal resize-term' % widget) def remove(self, widget): """Remove a widget from the container""" gtk.Paned.remove(self, widget) - for cnxid in self.cnxids[widget]: - widget.disconnect(cnxid) - del(self.cnxids[widget]) + self.disconnect_child(widget) self.children.remove(widget) return(True) From bf771065297558c29479f958bdf315674a3f8672 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 14 Nov 2009 22:58:42 +0000 Subject: [PATCH 121/331] switch to using new Container methods for handling child signals. --- terminatorlib/window.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 68c618c7..9627723f 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -31,7 +31,6 @@ class Window(Container, gtk.Window): ismaximised = None hidebound = None hidefunc = None - cnxids = None zoom_data = None term_zoomed = gobject.property(type=bool, default=False) @@ -40,7 +39,6 @@ class Window(Container, gtk.Window): """Class initialiser""" self.terminator = Terminator() self.terminator.window = self - self.cnxids = [] Container.__init__(self) gtk.Window.__init__(self) @@ -169,20 +167,22 @@ class Window(Container, gtk.Window): def add(self, widget): """Add a widget to the window by way of gtk.Window.add()""" if isinstance(widget, Terminal): - self.cnxids.append(widget.connect('close-term', self.closeterm)) - self.cnxids.append(widget.connect('title-change', - self.title.set_title)) - self.cnxids.append(widget.connect('split-horiz', self.split_horiz)) - self.cnxids.append(widget.connect('split-vert', self.split_vert)) - self.cnxids.append(widget.connect('unzoom', self.terminal_unzoom)) + signals = {'close-term': self.closeterm, + 'title-change': self.title.set_title, + 'split-horiz': self.split_horiz, + 'split-vert': self.split_vert, + 'unzoom': self.terminal_unzoom} + + for signal in signals: + self.connect_child(widget, signal, signals[signal]) + gtk.Window.add(self, widget) def remove(self, widget): """Remove our child widget by way of gtk.Window.remove()""" gtk.Window.remove(self, widget) - for cnxid in self.cnxids: - widget.disconnect(cnxid) - self.cnxids = [] + self.disconnect_child(widget) + return(True) def split_axis(self, widget, vertical=True): """Split the window""" @@ -207,7 +207,6 @@ class Window(Container, gtk.Window): def terminal_zoom(self, widget, font_scale=True): """Zoom a terminal widget""" - print font_scale children = self.get_children() if widget in children: From 22dccb6728f322a3cf3704bdd982fdae4a186d9b Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 14 Nov 2009 23:04:15 +0000 Subject: [PATCH 122/331] make focus always switch to the most relevant terminal --- terminatorlib/paned.py | 2 ++ terminatorlib/window.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index 6d1fa17b..62c52276 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -92,6 +92,8 @@ class Paned(Container): self.connect_child(widget, 'maximise', top_window.terminal_zoom, False) + widget.grab_focus() + elif isinstance(widget, gtk.Paned): try: self.connect_child(widget, 'resize-term', self.resizeterm) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 9627723f..520e1f52 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -166,6 +166,7 @@ class Window(Container, gtk.Window): def add(self, widget): """Add a widget to the window by way of gtk.Window.add()""" + gtk.Window.add(self, widget) if isinstance(widget, Terminal): signals = {'close-term': self.closeterm, 'title-change': self.title.set_title, @@ -176,7 +177,7 @@ class Window(Container, gtk.Window): for signal in signals: self.connect_child(widget, signal, signals[signal]) - gtk.Window.add(self, widget) + widget.grab_focus() def remove(self, widget): """Remove our child widget by way of gtk.Window.remove()""" From 3591aec862225933c410d2567525f1eccf06341b Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 14 Nov 2009 23:12:38 +0000 Subject: [PATCH 123/331] Add very simple ability to register de-register window widgets --- terminatorlib/newterminator.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index f251ea1b..3c8398b8 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -6,11 +6,12 @@ from borg import Borg from config import Config from keybindings import Keybindings +from util import dbg class Terminator(Borg): """master object for the application""" - window = None + windows = None windowtitle = None terminals = None groups = None @@ -29,6 +30,8 @@ class Terminator(Borg): def prepare_attributes(self): """Initialise anything that isn't already""" + if not self.windows: + self.windows = [] if not self.terminals: self.terminals = [] if not self.groups: @@ -41,15 +44,26 @@ class Terminator(Borg): self.keybindings = Keybindings() self.keybindings.configure(self.config['keybindings']) + def register_window(self, window): + """Register a new window widget""" + dbg('Terminator::register_window: registering %s' % window) + self.windows.append(window) + + def deregister_window(self, window): + """de-register a window widget""" + dbg('Terminator::deregister_window: de-registering %s' % window) + self.windows.remove(window) + def register_terminal(self, terminal): """Register a new terminal widget""" + dbg('Terminator::register_terminal: registering %s' % terminal) self.terminals.append(terminal) terminal.connect('ungroup-all', self.ungroup_all) terminal.connect('navigate', self.navigate_terminal) def deregister_terminal(self, terminal): """De-register a terminal widget""" - + dbg('Terminator::deregister_terminal: de-registering %s' % terminal) self.terminals.remove(terminal) def reconfigure_terminals(self): From c48c1d4fd6fcf1f46bae21df45fed52c939b05e3 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 14 Nov 2009 23:12:58 +0000 Subject: [PATCH 124/331] fix up debugging statements --- terminatorlib/terminal.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index d27905f7..a3d792df 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -170,11 +170,11 @@ class Terminal(gtk.VBox): urlpath = "/[" + pathchars + "]*[^]'.}>) \t\r\n,\\\"]" if posix: - dbg ('update_url_matches: Trying POSIX URL regexps') + dbg ('Terminal::update_url_matches: Trying POSIX URL regexps') lboundry = "[[:<:]]" rboundry = "[[:>:]]" else: # GNU - dbg ('update_url_matches: Trying GNU URL regexps') + dbg ('Terminal::update_url_matches: Trying GNU URL regexps') lboundry = "\\<" rboundry = "\\>" @@ -184,10 +184,10 @@ class Terminal(gtk.VBox): if self.matches['full_uri'] == -1: if posix: - err ('update_url_matches: POSIX match failed, trying GNU') + err ('Terminal::update_url_matches: POSIX match failed, trying GNU') self.update_url_matches(posix = False) else: - err ('update_url_matches: Failed adding URL match patterns') + err ('Terminal::update_url_matches: Failed adding URL match patterns') else: self.matches['voip'] = self.vte.match_add(lboundry + '(callto:|h323:|sip:)' + "[" + userchars + "+][" + From ec1c20540ab35bca53e1f9d6da824632b72e829e Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 16 Nov 2009 21:58:12 -0600 Subject: [PATCH 125/331] switch to new window registration logic --- terminatorlib/window.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 520e1f52..c66ba94b 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -38,7 +38,7 @@ class Window(Container, gtk.Window): def __init__(self): """Class initialiser""" self.terminator = Terminator() - self.terminator.window = self + self.terminator.register_window(self) Container.__init__(self) gtk.Window.__init__(self) @@ -109,6 +109,7 @@ class Window(Container, gtk.Window): def on_destroy_event(self, widget, data=None): """Handle window descruction""" + self.terminator.deregister_window(self) pass def on_hide_window(self, data): @@ -122,7 +123,7 @@ class Window(Container, gtk.Window): gtk.gdk.WINDOW_STATE_FULLSCREEN) self.ismaximised = bool(event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED) - dbg('window state changed: fullscreen %s, maximised %s' % + dbg('Window::on_window_state_changed: fullscreen=%s, maximised=%s' % (self.isfullscreen, self.ismaximised)) return(False) From 7d9575df5fbe193cc691f9851d0f0d27389455d6 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 16 Nov 2009 22:56:55 -0600 Subject: [PATCH 126/331] Make drag and drop of terminals work --- terminatorlib/container.py | 2 +- terminatorlib/paned.py | 5 +++-- terminatorlib/terminal.py | 4 ++-- terminatorlib/window.py | 5 +++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 70fd4808..db95b353 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -95,7 +95,7 @@ class Container(object): """Split this container vertically""" return(self.split_axis(widget, False)) - def split_axis(self, widget, vertical=True): + def split_axis(self, widget, vertical=True, sibling=None): """Default axis splitter. This should be implemented by subclasses""" raise NotImplementedError('split_axis') diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index 62c52276..2571284d 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -41,7 +41,7 @@ class Paned(Container): del(self.cnxids['init']) # pylint: disable-msg=W0613 - def split_axis(self, widget, vertical=True): + def split_axis(self, widget, vertical=True, sibling=None): """Default axis splitter. This should be implemented by subclasses""" self.remove(widget) if vertical: @@ -49,7 +49,8 @@ class Paned(Container): else: container = HPaned() - sibling = Terminal() + if not sibling: + sibling = Terminal() self.terminator.register_terminal(sibling) sibling.spawn_child() diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index a3d792df..6d689649 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -637,8 +637,8 @@ class Terminal(gtk.VBox): pos = self.get_location(widget, x, y) - data.terminator.remove(widgetsrc, True) - data.terminator.add(self, widgetsrc, pos) + srcpaned.remove(widgetsrc) + dstpaned.split_axis(dsthbox, pos in ['top', 'bottom'], widgetsrc) def get_location(self, vte, x, y): """Get our location within the terminal""" diff --git a/terminatorlib/window.py b/terminatorlib/window.py index c66ba94b..d00bb23b 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -186,7 +186,7 @@ class Window(Container, gtk.Window): self.disconnect_child(widget) return(True) - def split_axis(self, widget, vertical=True): + def split_axis(self, widget, vertical=True, sibling=None): """Split the window""" self.remove(widget) @@ -196,7 +196,8 @@ class Window(Container, gtk.Window): else: container = HPaned() - sibling = Terminal() + if not sibling: + sibling = Terminal() self.terminator.register_terminal(sibling) self.add(container) container.show_all() From 132daeb447889627109a8cbe47804b7cbf14b17d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 19 Nov 2009 23:16:20 -0600 Subject: [PATCH 127/331] Fix up some handling of unzooming and closing terminals while zoomed --- terminatorlib/container.py | 31 +++++++++++++++++-------------- terminatorlib/paned.py | 4 ++-- terminatorlib/window.py | 8 ++++---- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index db95b353..9eef6cdf 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -16,12 +16,6 @@ class Container(object): immutable = None children = None config = None - state_zoomed = None - - states_zoom = { 'none' : 0, - 'zoomed' : 1, - 'maximised' : 2 } - signals = None cnxids = None @@ -31,7 +25,6 @@ class Container(object): self.signals = [] self.cnxids = {} self.config = Config() - self.state_zoomed = self.states_zoom['none'] def register_signals(self, widget): """Register gobject signals in a way that avoids multiple inheritance""" @@ -109,9 +102,15 @@ class Container(object): def closeterm(self, widget): """Handle the closure of a terminal""" - if self.state_zoomed != self.states_zoom['none']: - dbg('closeterm: current zoomed state is: %s' % self.state_zoomed) - self.unzoom(widget) + try: + if self.get_property('term_zoomed'): + # We're zoomed, so unzoom and then start closing again + dbg('Container::closeterm: terminal zoomed, unzooming') + self.unzoom(widget) + widget.close() + return(True) + except TypeError: + pass if not self.remove(widget): return(False) @@ -126,10 +125,14 @@ class Container(object): def toggle_zoom(self, widget, fontscale = False): """Toggle the existing zoom state""" - if self.state_zoomed != self.states_zoom['none']: - self.unzoom(widget) - else: - self.zoom(widget, fontscale) + try: + if self.get_property('term_zoomed'): + self.unzoom(widget) + else: + self.zoom(widget, fontscale) + except TypeError: + err('Container::toggle_zoom: %s is unable to handle zooming, for \ + %s' % (self, widget)) def zoom(self, widget, fontscale = False): """Zoom a terminal""" diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index 2571284d..c4850a8a 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -85,12 +85,12 @@ class Paned(Container): 'split-horiz': self.split_horiz, 'split-vert': self.split_vert, 'resize-term': self.resizeterm, - 'zoom': top_window.terminal_zoom} + 'zoom': top_window.zoom} for signal in signals: self.connect_child(widget, signal, signals[signal]) - self.connect_child(widget, 'maximise', top_window.terminal_zoom, + self.connect_child(widget, 'maximise', top_window.zoom, False) widget.grab_focus() diff --git a/terminatorlib/window.py b/terminatorlib/window.py index d00bb23b..cb72a252 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -173,7 +173,7 @@ class Window(Container, gtk.Window): 'title-change': self.title.set_title, 'split-horiz': self.split_horiz, 'split-vert': self.split_vert, - 'unzoom': self.terminal_unzoom} + 'unzoom': self.unzoom} for signal in signals: self.connect_child(widget, signal, signals[signal]) @@ -208,7 +208,7 @@ class Window(Container, gtk.Window): sibling.spawn_child() - def terminal_zoom(self, widget, font_scale=True): + def zoom(self, widget, font_scale=True): """Zoom a terminal widget""" children = self.get_children() @@ -233,11 +233,11 @@ class Window(Container, gtk.Window): widget.grab_focus() - def terminal_unzoom(self, widget): + def unzoom(self, widget): """Restore normal terminal layout""" if not self.get_property('term_zoomed'): # We're not zoomed anyway - dbg('Window::terminal_unzoom: not zoomed, no-op') + dbg('Window::unzoom: not zoomed, no-op') return widget = self.zoom_data['widget'] From 0ff5aa3159c1ceb53b12e8c10e707b6bd50fb5f9 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 19 Nov 2009 23:40:31 -0600 Subject: [PATCH 128/331] Tidy up some debugging and fix group hoovering --- terminatorlib/newterminator.py | 20 ++++++++++++-------- terminatorlib/terminal.py | 2 +- terminatorlib/titlebar.py | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index 3c8398b8..6829925d 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -103,6 +103,7 @@ class Terminator(Borg): def create_group(self, name): """Create a new group""" if name not in self.groups: + dbg('Terminator::create_group: registering group %s' % name) self.groups.append(name) def ungroup_all(self, widget): @@ -121,17 +122,20 @@ class Terminator(Borg): """Clean out unused groups""" if self.config['autoclean_groups']: + inuse = [] todestroy = [] + + for terminal in self.terminals: + if terminal.group: + if not terminal.group in inuse: + inuse.append(terminal.group) + for group in self.groups: - for terminal in self.terminals: - save = False - if terminal.group == group: - save = True - break - - if not save: - todestroy.append(group) + if not group in inuse: + todestroy.append(group) + dbg('Terminator::group_hoover: %d groups, hoovering %d' % + (len(self.groups), len(todestroy))) for group in todestroy: self.groups.remove(group) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 6d689649..da1ada97 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -285,7 +285,7 @@ class Terminal(gtk.VBox): item = gtk.MenuItem(_('New group...')) item.connect('activate', self.create_group) menu.append(item) - + if len(self.terminator.groups) > 0: groupitem = gtk.RadioMenuItem(groupitem, _('None')) groupitem.set_active(self.group == None) diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index f44c5571..852b2d11 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -136,7 +136,7 @@ class Titlebar(gtk.EventBox): def groupentry_activate(self, widget): """Actually cause a group to be created""" groupname = self.groupentry.get_text() - dbg('creating group: %s' % groupname) + dbg('Titlebar::groupentry_activate: creating group: %s' % groupname) self.groupentry_cancel(None, None) self.emit('create-group', groupname) From efbeffb0603e0e1de0dd43f893387ad6c5751fc4 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 19 Nov 2009 23:42:49 -0600 Subject: [PATCH 129/331] Always return focus to the VTE widget after playing with group names --- terminatorlib/titlebar.py | 1 + 1 file changed, 1 insertion(+) diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index 852b2d11..6c206048 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -132,6 +132,7 @@ class Titlebar(gtk.EventBox): """Hide the group name entry""" self.groupentry.set_text('') self.groupentry.hide() + self.get_parent().grab_focus() def groupentry_activate(self, widget): """Actually cause a group to be created""" From 7a3f553d8253bb00c2ef2b611d644b6f6ec72820 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 19 Nov 2009 23:45:33 -0600 Subject: [PATCH 130/331] If there are no more Terminals, destroy all Windows --- terminatorlib/newterminator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index 6829925d..603a322f 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -66,6 +66,10 @@ class Terminator(Borg): dbg('Terminator::deregister_terminal: de-registering %s' % terminal) self.terminals.remove(terminal) + if len(self.terminals) == 0: + for window in self.windows: + window.destroy() + def reconfigure_terminals(self): """Tell all terminals to update their configuration""" From a51454e9f676582e3560d3d9e847163bc6ac9cfd Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 20 Nov 2009 00:11:12 -0600 Subject: [PATCH 131/331] make the broadcast menu items activate correctly --- terminatorlib/terminal.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index da1ada97..1138355e 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -334,10 +334,13 @@ class Terminal(gtk.VBox): groupitem = None - for key, value in {_('Broadcast off'):'off', + for key, value in {_('Broadcast all'):'all', _('Broadcast group'):'group', - _('Broadcast all'):'all'}.items(): + _('Broadcast off'):'off'}.items(): groupitem = gtk.RadioMenuItem(groupitem, key) + dbg('Terminal::populate_group_menu: %s active: %s' % + (key, self.terminator.groupsend == + self.terminator.groupsend_type[value])) groupitem.set_active(self.terminator.groupsend == self.terminator.groupsend_type[value]) groupitem.connect('activate', self.set_groupsend, @@ -416,7 +419,8 @@ class Terminal(gtk.VBox): def set_groupsend(self, widget, value): """Set the groupsend mode""" # FIXME: Can we think of a smarter way of doing this than poking? - if value in self.terminator.groupsend_type: + if value in self.terminator.groupsend_type.values(): + dbg('Terminal::set_groupsend: setting groupsend to %s' % value) self.terminator.groupsend = value def do_splittogroup_toggle(self): From 39098fdefb6155a700e92d138bc4d56c188d71ff Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 20 Nov 2009 00:18:21 -0600 Subject: [PATCH 132/331] Add functions for emitting events --- terminatorlib/newterminator.py | 14 ++++++++++++++ terminatorlib/terminal.py | 1 + 2 files changed, 15 insertions(+) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index 603a322f..2bd11e38 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -143,6 +143,20 @@ class Terminator(Borg): for group in todestroy: self.groups.remove(group) + def group_emit(self, terminal, group, type, event): + """Emit to each terminal in a group""" + dbg('Terminator::group_emit: emitting a keystroke for group %s' % + group) + for term in self.terminals: + if term != terminal and term.group == group: + term.vte.emit(type, event) + + def all_emit(self, terminal, type, event): + """Emit to all terminals""" + for term in self.terminals: + if term != terminal: + term.vte.emit(type, event) + def do_enumerate(self, widget, pad): """Insert the number of each terminal in a group, into that terminal""" if pad: diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 1138355e..caeb841c 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -394,6 +394,7 @@ class Terminal(gtk.VBox): if self.group == name: # already in this group, no action needed return + dbg('Terminal::set_group: Setting group to %s' % name) self.group = name self.titlebar.set_group_label(name) self.terminator.group_hoover() From aa531014ef3c2ed06287b37d1d7491562a84c972 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 20 Nov 2009 09:30:28 -0600 Subject: [PATCH 133/331] improve debugging and ensure that we can't register terminals/windows multiple times --- terminatorlib/newterminator.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index 2bd11e38..167afc37 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -46,8 +46,9 @@ class Terminator(Borg): def register_window(self, window): """Register a new window widget""" - dbg('Terminator::register_window: registering %s' % window) - self.windows.append(window) + if window not in self.windows: + dbg('Terminator::register_window: registering %s' % window) + self.windows.append(window) def deregister_window(self, window): """de-register a window widget""" @@ -56,10 +57,11 @@ class Terminator(Borg): def register_terminal(self, terminal): """Register a new terminal widget""" - dbg('Terminator::register_terminal: registering %s' % terminal) - self.terminals.append(terminal) - terminal.connect('ungroup-all', self.ungroup_all) - terminal.connect('navigate', self.navigate_terminal) + if terminal not in self.terminals: + dbg('Terminator::register_terminal: registering %s' % terminal) + self.terminals.append(terminal) + terminal.connect('ungroup-all', self.ungroup_all) + terminal.connect('navigate', self.navigate_terminal) def deregister_terminal(self, terminal): """De-register a terminal widget""" @@ -69,6 +71,9 @@ class Terminator(Borg): if len(self.terminals) == 0: for window in self.windows: window.destroy() + else: + dbg('Terminator::deregister_terminal: %d terminals remain' % + len(self.terminals)) def reconfigure_terminals(self): """Tell all terminals to update their configuration""" From 1db6a242b12ace46b463e4afaaa1359316808fe8 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 20 Nov 2009 09:30:50 -0600 Subject: [PATCH 134/331] add a sanity check function. This probably shouldn't exist, but is useful right now --- terminatorlib/paned.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index c4850a8a..15b558bb 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -122,6 +122,17 @@ class Paned(Container): else: dbg("Paned::wrapcloseterm: self.closeterm failed") + def hoover(self): + """Check that we still have a reason to exist""" + if len(self.children) == 1: + dbg('Paned::hoover: We only have one child, die') + parent = self.get_parent() + parent.remove(self) + child = self.children[0] + self.remove(child) + parent.add(child) + del(self) + def resizeterm(self, widget, keyname): """Handle a keyboard event requesting a terminal resize""" if keyname in ['up', 'down'] and isinstance(self, gtk.VPaned): From 279ebf55d6f1c263eacbacbe09860f788bbfa40b Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 20 Nov 2009 09:31:07 -0600 Subject: [PATCH 135/331] use the paned hoovering function introduced in revision 910 --- terminatorlib/terminal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index caeb841c..f82ea817 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -644,6 +644,7 @@ class Terminal(gtk.VBox): srcpaned.remove(widgetsrc) dstpaned.split_axis(dsthbox, pos in ['top', 'bottom'], widgetsrc) + srcpaned.hoover() def get_location(self, vte, x, y): """Get our location within the terminal""" From 025c5d3dd1334143461481aa2d861665a5e2e024 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 20 Nov 2009 16:46:50 -0600 Subject: [PATCH 136/331] Fix up config.py slightly to make pylint happier --- terminatorlib/config.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index a3395743..6b918439 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -148,13 +148,20 @@ DEFAULTS = { class Config(Borg, dict): """Class to provide access to our user configuration""" + defaults = None + def __init__(self): """Class initialiser""" Borg.__init__(self) dict.__init__(self) + def prepare_attributes(self): + """Set up our borg environment""" + if self.defaults is None: + self.defaults = DEFAULTS + def __getitem__(self, key): """Look up a configuration item""" - return(DEFAULTS[key]) + return(self.defaults[key]) From ac1a58e8b340afc5059244b85e78620c6f38de96 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 20 Nov 2009 16:52:26 -0600 Subject: [PATCH 137/331] Always a good idea to call your new function --- terminatorlib/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 6b918439..9a6b8246 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -156,6 +156,8 @@ class Config(Borg, dict): Borg.__init__(self) dict.__init__(self) + self.prepare_attributes() + def prepare_attributes(self): """Set up our borg environment""" if self.defaults is None: From 0deda2434dc8560bef64692db9ba0b2eaf496ff1 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 20 Nov 2009 16:52:39 -0600 Subject: [PATCH 138/331] pylint fixes --- terminatorlib/container.py | 14 +++----------- terminatorlib/util.py | 8 ++++++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 9eef6cdf..030a9f03 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -34,7 +34,8 @@ class Container(object): dbg('Container:: skipping signal %s for %s, already exists' % ( signal['name'], widget)) else: - dbg('Container:: registering signal for %s on %s' % (signal['name'], widget)) + dbg('Container:: registering signal for %s on %s' % + (signal['name'], widget)) try: gobject.signal_new(signal['name'], widget, @@ -52,7 +53,7 @@ class Container(object): if data is not None: self.cnxids[widget].append(widget.connect(signal, handler, data)) - dbg('Container::connect_child: registering %s(%s) to handle %s::%s' % + dbg('Container::connect_child: connecting %s(%s) for %s::%s' % (handler.__name__, data, widget.__class__.__name__, signal)) else: self.cnxids[widget].append(widget.connect(signal, handler)) @@ -71,15 +72,6 @@ class Container(object): """Return a list of child widgets, if any""" return(self.children) - def get_top_window(self, startpoint): - """Return the Window instance this container belongs to""" - widget = startpoint - parent = widget.get_parent() - while parent: - widget = parent - parent = widget.get_parent() - return(widget) - def split_horiz(self, widget): """Split this container horizontally""" return(self.split_axis(widget, True)) diff --git a/terminatorlib/util.py b/terminatorlib/util.py index 86ecb525..71b77e4f 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -49,6 +49,14 @@ def has_ancestor(widget, wtype): return(True) return(False) +def get_top_window(widget): + """Return the Window instance a widget belongs to""" + parent = widget.get_parent() + while parent: + widget = parent + parent = widget.get_parent() + return(widget) + def path_lookup(command): '''Find a command in our path''' if os.path.isabs(command): From c5711cbe152729088c285f9f8a54d7abe080d240 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 20 Nov 2009 16:53:06 -0600 Subject: [PATCH 139/331] get_top_window moved to util --- terminatorlib/paned.py | 4 ++-- terminatorlib/terminal.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index 15b558bb..3358e4d1 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -7,7 +7,7 @@ variants""" import gobject import gtk -from util import dbg, err +from util import dbg, err, get_top_window from newterminator import Terminator from terminal import Terminal from container import Container @@ -78,7 +78,7 @@ class Paned(Container): self.cnxids[widget] = [] if isinstance(widget, Terminal): - top_window = self.get_top_window(self) + top_window = get_top_window(self) # FIXME: somehow propagate the title-change signal to the Window signals = {'close-term': self.wrapcloseterm, diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index f82ea817..9405f2d4 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -13,7 +13,7 @@ import pango import re import subprocess -from util import dbg, err, gerr, widget_pixbuf +from util import dbg, err, gerr, widget_pixbuf, get_top_window import util from config import Config from cwd import get_default_cwd @@ -774,7 +774,7 @@ class Terminal(gtk.VBox): """Determine if we are a zoomed terminal""" prop = None parent = self.get_parent() - window = parent.get_top_window(self) + window = get_top_window(self) try: prop = window.get_property('term-zoomed') From db3ac951717a1b357f7551bc3a511e106671adb3 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 21 Nov 2009 12:09:47 -0600 Subject: [PATCH 140/331] introduce the dialog to confirm closing something --- terminatorlib/container.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 030a9f03..17572927 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -4,9 +4,11 @@ """container.py - classes necessary to contain Terminal widgets""" import gobject +import gtk from config import Config from util import dbg, err +from translation import _ # pylint: disable-msg=R0921 class Container(object): @@ -134,4 +136,37 @@ class Container(object): """Unzoom a terminal""" raise NotImplementedError('unzoom') + def construct_confirm_close(self, window, type): + """Create a confirmation dialog for closing things""" + dialog = gtk.Dialog(_('Close?'), window, gtk.DIALOG_MODAL) + dialog.set_has_separator(False) + dialog.set_resizable(False) + + cancel = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT) + close_all = dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT) + label = close_all.get_children()[0].get_children()[0].get_children()[1].set_label(_('Close _Terminals')) + + primary = gtk.Label(_('Close multiple temrinals?')) + primary.set_use_markup(True) + primary.set_alignment(0, 0.5) + secondary = gtk.Label(_('This %s has several terminals open. Closing the \ +%s will also close all terminals within it.') % (type, type)) + secondary.set_line_wrap(True) + + labels = gtk.VBox() + labels.pack_start(primary, False, False, 6) + labels.pack_start(secondary, False, False, 6) + + image = gtk.image_new_from_stock(gtk.STOCK_DIALOG_WARNING, + gtk.ICON_SIZE_DIALOG) + image.set_alignment(0.5, 0) + + box = gtk.HBox() + box.pack_start(image, False, False, 6) + box.pack_start(labels, False, False, 6) + dialog.vbox.pack_start(box, False, False, 12) + + dialog.show_all() + return(dialog) + # vim: set expandtab ts=4 sw=4: From 5e104972a763a077e1c7961440575ed68721fc7b Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 21 Nov 2009 12:11:50 -0600 Subject: [PATCH 141/331] Fix keybinding for closing ourself --- terminatorlib/terminal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 9405f2d4..7e16ce9d 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -961,7 +961,7 @@ class Terminal(gtk.VBox): self.emit('split-vert') def key_close_term(self): - self.terminator.closeterm (self) + self.close() def key_new_tab(self): self.terminator.newtab(self) From cdf7d37e5afb0ebe9450489933bcf01c5931ec84 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 21 Nov 2009 12:19:01 -0600 Subject: [PATCH 142/331] we hate the buggy past and love the futur. assume rgba is win for everyone --- terminatorlib/terminator.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index 996d49b9..87c5ba9d 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -263,9 +263,7 @@ class Terminator: # Set RGBA colormap if possible so VTE can use real alpha # channels for transparency. - if self.conf.enable_real_transparency: - dbg ('H9TRANS: Enabling real transparency') - self.enable_rgba(True) + self.enable_rgba(True) # Start out with just one terminal # FIXME: This should be really be decided from some kind of profile From ded962d6510fec0e2d56859e16819690a3c11ccd Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 21 Nov 2009 12:47:38 -0600 Subject: [PATCH 143/331] introduce global keybindings, force real transparency, handle window closing with the confirmation dialog --- terminatorlib/window.py | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index cb72a252..0bf43c2d 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -9,6 +9,7 @@ import gobject import gtk from util import dbg, err +from translation import _ from version import APP_NAME from container import Container from newterminator import Terminator @@ -81,7 +82,7 @@ class Window(Container, gtk.Window): self.set_fullscreen(self.config['fullscreen']) self.set_maximised(self.config['maximise']) self.set_borderless(self.config['borderless']) - self.set_real_transparency(self.config['enable_real_transparency']) + self.set_real_transparency() if self.hidebound: self.set_hidden(self.config['hidden']) else: @@ -101,16 +102,43 @@ class Window(Container, gtk.Window): def on_key_press(self, window, event): """Handle a keyboard event""" - pass + # FIXME: We probably want to cancel window urgency here + + mapping = self.terminator.keybindings.lookup(event) + + if mapping: + dbg('Window::on_key_press: looked up %r' % mapping) + if mapping == 'full_screen': + self.set_fullscreen(not self.isfullscreen) + elif mapping == 'close_window': + if not self.on_delete_event(window, + gtk.gdk.Event(gtk.gdk.DELETE)): + self.on_destroy_event(window, + gtk.gdk.Event(gtk.gdk.DESTROY)) + else: + return(False) + return(True) def on_delete_event(self, window, event, data=None): """Handle a window close request""" - pass + if isinstance(self.get_child(), Terminal): + dbg('Window::on_delete_event: Only one child, closing is fine') + return(False) + return(self.confirm_close(window, _('window'))) + + def confirm_close(self, window, type): + """Display a confirmation dialog when the user is closing multiple + terminals in one window""" + dialog = self.construct_confirm_close(window, type) + result = dialog.run() + dialog.destroy() + return(not (result == gtk.RESPONSE_ACCEPT)) def on_destroy_event(self, widget, data=None): """Handle window descruction""" self.terminator.deregister_window(self) - pass + self.destroy() + del(self) def on_hide_window(self, data): """Handle a request to hide/show the window""" @@ -154,7 +182,7 @@ class Window(Container, gtk.Window): """Set the minimised state of the window from the value""" pass - def set_real_transparency(self, value): + def set_real_transparency(self, value=True): """Enable RGBA if supported on the current screen""" screen = self.get_screen() if value: From 8842d3ecf666a6822fb1bbd0971e1d574de7dbbc Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 21 Nov 2009 21:53:01 -0600 Subject: [PATCH 144/331] Add a FIXME note for later --- terminatorlib/titlebar.py | 1 + 1 file changed, 1 insertion(+) diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index 6c206048..da6a6df6 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -87,6 +87,7 @@ class Titlebar(gtk.EventBox): def update(self): """Update our contents""" self.label.set_text("%s %s" % (self.termtext, self.sizetext)) + # FIXME: Aren't we supposed to be setting a colour here too? def set_from_icon_name(self, name, size = gtk.ICON_SIZE_MENU): """Set an icon for the group label""" From b6703153a104aced2ce62e1ef40d6dbc17a01018 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 21 Nov 2009 21:53:38 -0600 Subject: [PATCH 145/331] Start a new class derived from Container for gtk.Notebook --- terminatorlib/notebook.py | 172 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100755 terminatorlib/notebook.py diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py new file mode 100755 index 00000000..17572927 --- /dev/null +++ b/terminatorlib/notebook.py @@ -0,0 +1,172 @@ +#!/usr/bin/python +# Terminator by Chris Jones +# GPL v2 only +"""container.py - classes necessary to contain Terminal widgets""" + +import gobject +import gtk + +from config import Config +from util import dbg, err +from translation import _ + +# pylint: disable-msg=R0921 +class Container(object): + """Base class for Terminator Containers""" + + terminator = None + immutable = None + children = None + config = None + signals = None + cnxids = None + + def __init__(self): + """Class initialiser""" + self.children = [] + self.signals = [] + self.cnxids = {} + self.config = Config() + + def register_signals(self, widget): + """Register gobject signals in a way that avoids multiple inheritance""" + existing = gobject.signal_list_names(widget) + for signal in self.signals: + if signal['name'] in existing: + dbg('Container:: skipping signal %s for %s, already exists' % ( + signal['name'], widget)) + else: + dbg('Container:: registering signal for %s on %s' % + (signal['name'], widget)) + try: + gobject.signal_new(signal['name'], + widget, + signal['flags'], + signal['return_type'], + signal['param_types']) + except RuntimeError: + err('Container:: registering signal for %s on %s failed' % + (signal['name'], widget)) + + def connect_child(self, widget, signal, handler, data=None): + """Register the requested signal and record its connection ID""" + if not self.cnxids.has_key(widget): + self.cnxids[widget] = [] + + if data is not None: + self.cnxids[widget].append(widget.connect(signal, handler, data)) + dbg('Container::connect_child: connecting %s(%s) for %s::%s' % + (handler.__name__, data, widget.__class__.__name__, signal)) + else: + self.cnxids[widget].append(widget.connect(signal, handler)) + dbg('Container::connect_child: registering %s to handle %s::%s' % + (handler.__name__, widget.__class__.__name__, signal)) + + def disconnect_child(self, widget): + """De-register the signals for a child""" + if self.cnxids.has_key(widget): + for cnxid in self.cnxids[widget]: + # FIXME: Look up the IDs to print a useful debugging message + widget.disconnect(cnxid) + del(self.cnxids[widget]) + + def get_offspring(self): + """Return a list of child widgets, if any""" + return(self.children) + + def split_horiz(self, widget): + """Split this container horizontally""" + return(self.split_axis(widget, True)) + + def split_vert(self, widget): + """Split this container vertically""" + return(self.split_axis(widget, False)) + + def split_axis(self, widget, vertical=True, sibling=None): + """Default axis splitter. This should be implemented by subclasses""" + raise NotImplementedError('split_axis') + + def add(self, widget): + """Add a widget to the container""" + raise NotImplementedError('add') + + def remove(self, widget): + """Remove a widget from the container""" + raise NotImplementedError('remove') + + def closeterm(self, widget): + """Handle the closure of a terminal""" + try: + if self.get_property('term_zoomed'): + # We're zoomed, so unzoom and then start closing again + dbg('Container::closeterm: terminal zoomed, unzooming') + self.unzoom(widget) + widget.close() + return(True) + except TypeError: + pass + + if not self.remove(widget): + return(False) + + self.terminator.deregister_terminal(widget) + self.terminator.group_hoover() + return(True) + + def resizeterm(self, widget, keyname): + """Handle a keyboard event requesting a terminal resize""" + raise NotImplementedError('resizeterm') + + def toggle_zoom(self, widget, fontscale = False): + """Toggle the existing zoom state""" + try: + if self.get_property('term_zoomed'): + self.unzoom(widget) + else: + self.zoom(widget, fontscale) + except TypeError: + err('Container::toggle_zoom: %s is unable to handle zooming, for \ + %s' % (self, widget)) + + def zoom(self, widget, fontscale = False): + """Zoom a terminal""" + raise NotImplementedError('zoom') + + def unzoom(self, widget): + """Unzoom a terminal""" + raise NotImplementedError('unzoom') + + def construct_confirm_close(self, window, type): + """Create a confirmation dialog for closing things""" + dialog = gtk.Dialog(_('Close?'), window, gtk.DIALOG_MODAL) + dialog.set_has_separator(False) + dialog.set_resizable(False) + + cancel = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT) + close_all = dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT) + label = close_all.get_children()[0].get_children()[0].get_children()[1].set_label(_('Close _Terminals')) + + primary = gtk.Label(_('Close multiple temrinals?')) + primary.set_use_markup(True) + primary.set_alignment(0, 0.5) + secondary = gtk.Label(_('This %s has several terminals open. Closing the \ +%s will also close all terminals within it.') % (type, type)) + secondary.set_line_wrap(True) + + labels = gtk.VBox() + labels.pack_start(primary, False, False, 6) + labels.pack_start(secondary, False, False, 6) + + image = gtk.image_new_from_stock(gtk.STOCK_DIALOG_WARNING, + gtk.ICON_SIZE_DIALOG) + image.set_alignment(0.5, 0) + + box = gtk.HBox() + box.pack_start(image, False, False, 6) + box.pack_start(labels, False, False, 6) + dialog.vbox.pack_start(box, False, False, 12) + + dialog.show_all() + return(dialog) + +# vim: set expandtab ts=4 sw=4: From 9cbc6f128276a77b93c2898b2cc92ad764c7a418 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 21 Nov 2009 22:28:39 -0600 Subject: [PATCH 146/331] Add the very barest of support for tabs. --- terminatorlib/notebook.py | 171 +++++++++----------------------------- terminatorlib/terminal.py | 5 +- terminatorlib/window.py | 5 ++ 3 files changed, 46 insertions(+), 135 deletions(-) diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index 17572927..247d3bd3 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -1,86 +1,47 @@ #!/usr/bin/python # Terminator by Chris Jones # GPL v2 only -"""container.py - classes necessary to contain Terminal widgets""" +"""notebook.py - classes for the notebook widget""" import gobject import gtk -from config import Config -from util import dbg, err -from translation import _ +from container import Container +from newterminator import Terminator +from terminal import Terminal +from util import err -# pylint: disable-msg=R0921 -class Container(object): - """Base class for Terminator Containers""" +class Notebook(Container, gtk.Notebook): + """Class implementing a gtk.Notebook container""" - terminator = None - immutable = None - children = None - config = None - signals = None - cnxids = None - - def __init__(self): + def __init__(self, window): """Class initialiser""" - self.children = [] - self.signals = [] - self.cnxids = {} - self.config = Config() + if isinstance(window.get_child(), gtk.Notebook): + err('There is already a Notebook at the top of this window') + raise(ValueError) - def register_signals(self, widget): - """Register gobject signals in a way that avoids multiple inheritance""" - existing = gobject.signal_list_names(widget) - for signal in self.signals: - if signal['name'] in existing: - dbg('Container:: skipping signal %s for %s, already exists' % ( - signal['name'], widget)) - else: - dbg('Container:: registering signal for %s on %s' % - (signal['name'], widget)) - try: - gobject.signal_new(signal['name'], - widget, - signal['flags'], - signal['return_type'], - signal['param_types']) - except RuntimeError: - err('Container:: registering signal for %s on %s failed' % - (signal['name'], widget)) + Container.__init__(self) + gtk.Notebook.__init__(self) + self.terminator = Terminator() + gobject.type_register(Notebook) + self.register_signals(Notebook) + self.configure() - def connect_child(self, widget, signal, handler, data=None): - """Register the requested signal and record its connection ID""" - if not self.cnxids.has_key(widget): - self.cnxids[widget] = [] + child = window.get_child() + window.remove(child) + window.add(self) + self.newtab(child) + self.show_all() - if data is not None: - self.cnxids[widget].append(widget.connect(signal, handler, data)) - dbg('Container::connect_child: connecting %s(%s) for %s::%s' % - (handler.__name__, data, widget.__class__.__name__, signal)) - else: - self.cnxids[widget].append(widget.connect(signal, handler)) - dbg('Container::connect_child: registering %s to handle %s::%s' % - (handler.__name__, widget.__class__.__name__, signal)) + def configure(self): + """Apply widget-wide settings""" + #self.connect('page-reordered', self.on_page_reordered) + self.set_property('homogeneous', not self.config['scroll_tabbar']) + self.set_scrollable(self.config['scroll_tabbar']) - def disconnect_child(self, widget): - """De-register the signals for a child""" - if self.cnxids.has_key(widget): - for cnxid in self.cnxids[widget]: - # FIXME: Look up the IDs to print a useful debugging message - widget.disconnect(cnxid) - del(self.cnxids[widget]) - - def get_offspring(self): - """Return a list of child widgets, if any""" - return(self.children) - - def split_horiz(self, widget): - """Split this container horizontally""" - return(self.split_axis(widget, True)) - - def split_vert(self, widget): - """Split this container vertically""" - return(self.split_axis(widget, False)) + pos = getattr(gtk, 'POS_%s' % self.config['tab_position'].upper()) + self.set_tab_pos(pos) + self.set_show_tabs(not self.config['hide_tabbar']) def split_axis(self, widget, vertical=True, sibling=None): """Default axis splitter. This should be implemented by subclasses""" @@ -94,40 +55,21 @@ class Container(object): """Remove a widget from the container""" raise NotImplementedError('remove') - def closeterm(self, widget): - """Handle the closure of a terminal""" - try: - if self.get_property('term_zoomed'): - # We're zoomed, so unzoom and then start closing again - dbg('Container::closeterm: terminal zoomed, unzooming') - self.unzoom(widget) - widget.close() - return(True) - except TypeError: - pass - - if not self.remove(widget): - return(False) - - self.terminator.deregister_terminal(widget) - self.terminator.group_hoover() - return(True) + def newtab(self, widget=None): + """Add a new tab, optionally supplying a child widget""" + if not widget: + widget = Terminal() + self.terminator.register_terminal(widget) + widget.spawn_child() + self.set_tab_reorderable(widget, True) + self.append_page(widget, None) + widget.grab_focus() + def resizeterm(self, widget, keyname): """Handle a keyboard event requesting a terminal resize""" raise NotImplementedError('resizeterm') - def toggle_zoom(self, widget, fontscale = False): - """Toggle the existing zoom state""" - try: - if self.get_property('term_zoomed'): - self.unzoom(widget) - else: - self.zoom(widget, fontscale) - except TypeError: - err('Container::toggle_zoom: %s is unable to handle zooming, for \ - %s' % (self, widget)) - def zoom(self, widget, fontscale = False): """Zoom a terminal""" raise NotImplementedError('zoom') @@ -136,37 +78,4 @@ class Container(object): """Unzoom a terminal""" raise NotImplementedError('unzoom') - def construct_confirm_close(self, window, type): - """Create a confirmation dialog for closing things""" - dialog = gtk.Dialog(_('Close?'), window, gtk.DIALOG_MODAL) - dialog.set_has_separator(False) - dialog.set_resizable(False) - - cancel = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT) - close_all = dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT) - label = close_all.get_children()[0].get_children()[0].get_children()[1].set_label(_('Close _Terminals')) - - primary = gtk.Label(_('Close multiple temrinals?')) - primary.set_use_markup(True) - primary.set_alignment(0, 0.5) - secondary = gtk.Label(_('This %s has several terminals open. Closing the \ -%s will also close all terminals within it.') % (type, type)) - secondary.set_line_wrap(True) - - labels = gtk.VBox() - labels.pack_start(primary, False, False, 6) - labels.pack_start(secondary, False, False, 6) - - image = gtk.image_new_from_stock(gtk.STOCK_DIALOG_WARNING, - gtk.ICON_SIZE_DIALOG) - image.set_alignment(0.5, 0) - - box = gtk.HBox() - box.pack_start(image, False, False, 6) - box.pack_start(labels, False, False, 6) - dialog.vbox.pack_start(box, False, False, 12) - - dialog.show_all() - return(dialog) - # vim: set expandtab ts=4 sw=4: diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 7e16ce9d..6a00a213 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -464,7 +464,7 @@ class Terminal(gtk.VBox): if mapping == "hide_window": return(False) - if mapping and mapping not in ['close_window', 'full_screen']: + if mapping and mapping not in ['close_window', 'full_screen', 'new_tab']: dbg('Terminal::on_keypress: lookup found: %r' % mapping) # handle the case where user has re-bound copy to ctrl+ # we only copy if there is a selection otherwise let it fall through @@ -963,9 +963,6 @@ class Terminal(gtk.VBox): def key_close_term(self): self.close() - def key_new_tab(self): - self.terminator.newtab(self) - def key_resize_up(self): self.emit('resize-term', 'up') diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 0bf43c2d..53731a37 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -12,6 +12,7 @@ from util import dbg, err from translation import _ from version import APP_NAME from container import Container +from notebook import Notebook from newterminator import Terminator from terminal import Terminal from paned import HPaned, VPaned @@ -115,6 +116,10 @@ class Window(Container, gtk.Window): gtk.gdk.Event(gtk.gdk.DELETE)): self.on_destroy_event(window, gtk.gdk.Event(gtk.gdk.DESTROY)) + elif mapping == 'new_tab': + if not isinstance(self.get_child(), Notebook): + notebook = Notebook(self) + self.get_child().newtab() else: return(False) return(True) From 1d7adc82838cd88f84fe3684940e539a95474a83 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 23 Nov 2009 15:17:33 +0000 Subject: [PATCH 147/331] Port TabLabel across and integrate it. --- terminatorlib/notebook.py | 100 +++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index 247d3bd3..3f5e7fcc 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -6,13 +6,17 @@ import gobject import gtk -from container import Container from newterminator import Terminator +from config import Config +from container import Container from terminal import Terminal +from editablelabel import EditableLabel +from translation import _ from util import err class Notebook(Container, gtk.Notebook): """Class implementing a gtk.Notebook container""" + window = None def __init__(self, window): """Class initialiser""" @@ -23,6 +27,7 @@ class Notebook(Container, gtk.Notebook): Container.__init__(self) gtk.Notebook.__init__(self) self.terminator = Terminator() + self.window = window gobject.type_register(Notebook) self.register_signals(Notebook) self.configure() @@ -31,6 +36,13 @@ class Notebook(Container, gtk.Notebook): window.remove(child) window.add(self) self.newtab(child) + + label = TabLabel(self.window.get_title(), self) + self.set_tab_label(child, label) + self.set_tab_label_packing(child, not self.config['scroll_tabbar'], + not self.config['scroll_tabbar'], + gtk.PACK_START) + self.show_all() def configure(self): @@ -63,6 +75,17 @@ class Notebook(Container, gtk.Notebook): widget.spawn_child() self.set_tab_reorderable(widget, True) + label = TabLabel(self.window.get_title(), self) + + label.show_all() + widget.show_all() + + self.set_tab_label(widget, label) + self.set_tab_label_packing(widget, not self.config['scroll_tabbar'], + not self.config['scroll_tabbar'], + gtk.PACK_START) + + self.append_page(widget, None) widget.grab_focus() @@ -78,4 +101,79 @@ class Notebook(Container, gtk.Notebook): """Unzoom a terminal""" raise NotImplementedError('unzoom') +class TabLabel(gtk.HBox): + """Class implementing a label widget for Notebook tabs""" + notebook = None + terminator = None + config = None + label = None + icon = None + button = None + + def __init__(self, title, notebook): + """Class initialiser""" + gtk.HBox.__init__(self) + self.notebook = notebook + self.terminator = Terminator() + self.config = Config() + + self.label = EditableLabel(title) + self.update_angle() + + self.pack_start(self.label, True, True) + + self.update_button() + self.show_all() + + def update_button(self): + """Update the state of our close button""" + if not self.config['close_button_on_tab']: + if self.button: + self.button.remove(self.icon) + self.remove(self.button) + del(self.button) + del(self.icon) + self.button = None + self.icon = None + return + + if not self.button: + self.button = gtk.Button() + if not self.icon: + self.icon = gtk.Image() + self.icon.set_from_stock(gtk.STOCK_CLOSE, + gtk.ICON_SIZE_MENU) + + self.button.set_relief(gtk.RELIEF_NONE) + self.button.set_focus_on_click(False) + # FIXME: Why on earth are we doing this twice? + self.button.set_relief(gtk.RELIEF_NONE) + self.button.add(self.icon) + self.button.connect('clicked', self.on_close) + self.button.set_name('terminator-tab-close-button') + self.button.connect('style-set', self.on_style_set) + if hasattr(self.button, 'set_tooltip_text'): + self.button.set_tooltip_text(_('Close Tab')) + self.pack_start(self.button, False, False) + self.show_all() + + def update_angle(self): + """Update the angle of a label""" + position = self.notebook.get_tab_pos() + if position == gtk.POS_LEFT: + self.label.set_angle(90) + elif position == gtk.POS_RIGHT: + self.label.set_angle(270) + else: + self.label.set_angle(0) + + def on_style_set(self, widget, prevstyle): + """Style changed, recalculate icon size""" + x, y = gtk.icon_size_lookup_for_settings(self.button.get_settings(), + gtk.ICON_SIZE_MENU) + self.button.set_size_request(x + 2, y + 2) + + def on_close(self, widget): + """The close button has been clicked. Destroy the tab""" + pass # vim: set expandtab ts=4 sw=4: From 26e793af10cebe3ab27573c46812643d958b46d7 Mon Sep 17 00:00:00 2001 From: Emmanuel Bretelle Date: Mon, 23 Nov 2009 19:59:13 +0100 Subject: [PATCH 148/331] Working with tabs --- terminatorlib/notebook.py | 43 +++++++++++++++++++++++++++++++++++++-- terminatorlib/paned.py | 16 +++++++++++---- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index 3f5e7fcc..fe7a233b 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -12,6 +12,7 @@ from container import Container from terminal import Terminal from editablelabel import EditableLabel from translation import _ +from paned import VPaned, HPaned from util import err class Notebook(Container, gtk.Notebook): @@ -37,6 +38,7 @@ class Notebook(Container, gtk.Notebook): window.add(self) self.newtab(child) + label = TabLabel(self.window.get_title(), self) self.set_tab_label(child, label) self.set_tab_label_packing(child, not self.config['scroll_tabbar'], @@ -57,7 +59,30 @@ class Notebook(Container, gtk.Notebook): def split_axis(self, widget, vertical=True, sibling=None): """Default axis splitter. This should be implemented by subclasses""" - raise NotImplementedError('split_axis') + #raise NotImplementedError('split_axis') + page_num = self.page_num( widget ) + if page_num == -1: + raise NotImplementedError('split_axis widget cannot be found') + self.remove_page(page_num) + if vertical: + container = VPaned() + else: + container = HPaned() + + if not sibling: + sibling = Terminal() + self.terminator.register_terminal(sibling) + sibling.spawn_child() + + self.insert_page(container, None, page_num ) + self.show_all() + + container.add(widget) + container.add(sibling) + self.set_current_page( page_num ) + + self.show_all() + def add(self, widget): """Add a widget to the container""" @@ -65,7 +90,11 @@ class Notebook(Container, gtk.Notebook): def remove(self, widget): """Remove a widget from the container""" - raise NotImplementedError('remove') + page_num = self.page_num( widget ) + if page_num == -1: + print('remove current page = -1') + return(False) + self.remove_page( page_num ) def newtab(self, widget=None): """Add a new tab, optionally supplying a child widget""" @@ -73,6 +102,15 @@ class Notebook(Container, gtk.Notebook): widget = Terminal() self.terminator.register_terminal(widget) widget.spawn_child() + + signals = {'close-term': self.closeterm, + #'title-change': self.title.set_title, + 'split-horiz': self.split_horiz, + 'split-vert': self.split_vert, + 'unzoom': self.unzoom} + + for signal in signals: + self.connect_child(widget, signal, signals[signal]) self.set_tab_reorderable(widget, True) label = TabLabel(self.window.get_title(), self) @@ -87,6 +125,7 @@ class Notebook(Container, gtk.Notebook): self.append_page(widget, None) + self.set_current_page(-1) widget.grab_focus() def resizeterm(self, widget, keyname): diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index 3358e4d1..16fcadee 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -12,6 +12,9 @@ from newterminator import Terminator from terminal import Terminal from container import Container +# forward declaration of class Notebook +class Notebook: + pass # pylint: disable-msg=R0921 # pylint: disable-msg=E1101 class Paned(Container): @@ -111,13 +114,18 @@ class Paned(Container): def wrapcloseterm(self, widget): """A child terminal has closed, so this container must die""" if self.closeterm(widget): - parent = self.get_parent() - parent.remove(self) - # At this point we only have one child, which is the surviving term sibling = self.children[0] self.remove(sibling) - parent.add(sibling) + + parent = self.get_parent() + if isinstance( parent, Notebook ): + page_num = parent.page_num( self ) + parent.remove_page( page_num ) + parent.insert_page( sibling, None, page_num ) + else: + parent.remove(self) + parent.add(sibling) del(self) else: dbg("Paned::wrapcloseterm: self.closeterm failed") From a6e94a9fc8e2938dec6f7c83dd8ec3810b0657b1 Mon Sep 17 00:00:00 2001 From: Emmanuel Bretelle Date: Mon, 23 Nov 2009 20:31:06 +0100 Subject: [PATCH 149/331] Destroying terminal in last paned of tab works... *dirty*... --- terminatorlib/paned.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index 16fcadee..49479648 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -12,9 +12,6 @@ from newterminator import Terminator from terminal import Terminal from container import Container -# forward declaration of class Notebook -class Notebook: - pass # pylint: disable-msg=R0921 # pylint: disable-msg=E1101 class Paned(Container): @@ -119,7 +116,7 @@ class Paned(Container): self.remove(sibling) parent = self.get_parent() - if isinstance( parent, Notebook ): + if 'remove_page' in dir(parent): page_num = parent.page_num( self ) parent.remove_page( page_num ) parent.insert_page( sibling, None, page_num ) From 2dd44ddc8a4abeebc61871dec4c8681637901bad Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 24 Nov 2009 23:47:32 +0000 Subject: [PATCH 150/331] Add an object factory to get us out of our dependency hell --- terminatorlib/factory.py | 81 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100755 terminatorlib/factory.py diff --git a/terminatorlib/factory.py b/terminatorlib/factory.py new file mode 100755 index 00000000..7c54f370 --- /dev/null +++ b/terminatorlib/factory.py @@ -0,0 +1,81 @@ +#!/usr/bin/python +# Terminator by Chris Jones +# GPL v2 only +"""factory.py - Maker of objects""" + +from borg import Borg +from util import dbg, err + +class Factory(Borg): + """Definition of a class that makes other classes""" + def __init__(self): + """Class initialiser""" + Borg.__init__(self) + self.prepare_attributes() + + def prepare_attributes(self): + """Required by the borg, but a no-op here""" + pass + + def make(self, product, *args): + """Make the requested product""" + try: + func = getattr(self, 'make_%s' % product.lower()) + except AttributeError: + err('Factory::make: requested object does not exist: %s' % product) + return(None) + + dbg('Factory::make: created a %s' % product) + return(func(args)) + + def make_terminal(self, *args): + """Make a Terminal""" + import terminal + return(terminal.Terminal()) + + def make_hpaned(self, *args): + """Make an HPaned""" + import paned + return(paned.HPaned()) + + def make_vpaned(self, *args): + """Make a VPaned""" + import paned + return(paned.VPaned()) + + def make_notebook(self, *args): + """Make a Notebook""" + import notebook + return(notebook.Notebook(args[0][0])) + +if __name__ == '__main__': + # Do some testing + + fact = Factory() + objects = [] + + # Test making a Terminal + terminal = fact.make('terminal') + objects.append(terminal) + + # Test making a Notebook + import gtk + win = gtk.Window() + win.add(terminal) + notebook = fact.make('notebook', win) + objects.append(notebook) + + # Test making an HPaned + hpaned = fact.make('hpaned') + objects.append(hpaned) + + # Test making a VPaned + vpaned = fact.make('vpaned') + objects.append(vpaned) + + for item in objects: + print item.__class__.__name__ + + # Test making something fake + fail = fact.make('counterfeit') + From f8ab3d8308cf77da00fa6ed10cf49bd15252ff7a Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 24 Nov 2009 23:47:44 +0000 Subject: [PATCH 151/331] remove testing --- terminatorlib/factory.py | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/terminatorlib/factory.py b/terminatorlib/factory.py index 7c54f370..80eae84b 100755 --- a/terminatorlib/factory.py +++ b/terminatorlib/factory.py @@ -48,34 +48,3 @@ class Factory(Borg): import notebook return(notebook.Notebook(args[0][0])) -if __name__ == '__main__': - # Do some testing - - fact = Factory() - objects = [] - - # Test making a Terminal - terminal = fact.make('terminal') - objects.append(terminal) - - # Test making a Notebook - import gtk - win = gtk.Window() - win.add(terminal) - notebook = fact.make('notebook', win) - objects.append(notebook) - - # Test making an HPaned - hpaned = fact.make('hpaned') - objects.append(hpaned) - - # Test making a VPaned - vpaned = fact.make('vpaned') - objects.append(vpaned) - - for item in objects: - print item.__class__.__name__ - - # Test making something fake - fail = fact.make('counterfeit') - From 0447c8f6f201680caac55b687921536047825b76 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 24 Nov 2009 23:49:03 +0000 Subject: [PATCH 152/331] quiesce pylint --- terminatorlib/factory.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/terminatorlib/factory.py b/terminatorlib/factory.py index 80eae84b..b4968235 100755 --- a/terminatorlib/factory.py +++ b/terminatorlib/factory.py @@ -6,6 +6,8 @@ from borg import Borg from util import dbg, err +# pylint: disable-msg=R0201 +# pylint: disable-msg=W0613 class Factory(Borg): """Definition of a class that makes other classes""" def __init__(self): From 2a76382e47430780f0b025ba4cfd4ef49262ada8 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 25 Nov 2009 00:37:29 +0000 Subject: [PATCH 153/331] migrate to using the factory and extend it to have an isinstance() --- terminatorlib/factory.py | 21 ++++++++++++++++ terminatorlib/notebook.py | 51 ++++++++++++++++++++++++++++++++++++--- terminatorlib/paned.py | 28 ++++++++++++++------- terminatorlib/test.py | 5 ++-- terminatorlib/window.py | 22 +++++++++-------- 5 files changed, 102 insertions(+), 25 deletions(-) diff --git a/terminatorlib/factory.py b/terminatorlib/factory.py index b4968235..3f22964a 100755 --- a/terminatorlib/factory.py +++ b/terminatorlib/factory.py @@ -19,6 +19,27 @@ class Factory(Borg): """Required by the borg, but a no-op here""" pass + def isinstance(self, product, classtype): + """Check if a given product is a particular type of object""" + if classtype == 'Terminal': + import terminal + return(isinstance(product, terminal.Terminal)) + elif classtype == 'VPaned': + import paned + return(isinstance(product, paned.VPaned)) + elif classtype == 'HPaned': + import paned + return(isinstance(product, paned.HPaned)) + elif classtype == 'Paned': + import paned + return(isinstance(product, paned.Paned)) + elif classtype == 'Notebook': + import notebook + return(isinstance(product, notebook.Notebook)) + else: + err('Factory::isinstance: unknown class type: %s' % classtype) + return(False) + def make(self, product, *args): """Make the requested product""" try: diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index 3f5e7fcc..e363ee5c 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -8,8 +8,8 @@ import gtk from newterminator import Terminator from config import Config +from factory import Factory from container import Container -from terminal import Terminal from editablelabel import EditableLabel from translation import _ from util import err @@ -57,7 +57,32 @@ class Notebook(Container, gtk.Notebook): def split_axis(self, widget, vertical=True, sibling=None): """Default axis splitter. This should be implemented by subclasses""" - raise NotImplementedError('split_axis') + page_num = self.page_num(widget) + if page_num == -1: + err('Notebook::split_axis: %s not found in Notebook' % widget) + return + + self.remove_page(page_num) + + maker = Factory() + if vertical: + container = maker.make('vpaned') + else: + container = maker.make('hpaned') + + if not sibling: + sibling = maker.make('terminal') + self.terminator.register_terminal(sibling) + sibling.spawn_child() + + self.insert_page(container, None, page_num) + self.show_all() + + container.add(widget) + container.add(sibling) + self.set_current_page(page_num) + + self.show_all() def add(self, widget): """Add a widget to the container""" @@ -65,15 +90,32 @@ class Notebook(Container, gtk.Notebook): def remove(self, widget): """Remove a widget from the container""" - raise NotImplementedError('remove') + page_num = self.page_num(widget) + if page_num == -1: + err('Notebook::remove: %s not found in Notebook' % widget) + return(False) + self.remove_page(page_num) def newtab(self, widget=None): """Add a new tab, optionally supplying a child widget""" if not widget: - widget = Terminal() + maker = Factory() + widget = maker.make('terminal') self.terminator.register_terminal(widget) widget.spawn_child() + # FIXME: We likely need a wrapcloseterm() to handle + # things like removing Notebook when there is only + # one tab left. + signals = {'close-term': self.closeterm, + #'title-change': self.title.set_title, + 'split-horiz': self.split_horiz, + 'split-vert': self.split_vert, + 'unzoom': self.unzoom} + + for signal in signals: + self.connect_child(widget, signal, signals[signal]) + self.set_tab_reorderable(widget, True) label = TabLabel(self.window.get_title(), self) @@ -87,6 +129,7 @@ class Notebook(Container, gtk.Notebook): self.append_page(widget, None) + self.set_current_page(-1) widget.grab_focus() def resizeterm(self, widget, keyname): diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index 3358e4d1..6a8e1886 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -9,7 +9,7 @@ import gtk from util import dbg, err, get_top_window from newterminator import Terminator -from terminal import Terminal +from factory import Factory from container import Container # pylint: disable-msg=R0921 @@ -43,6 +43,8 @@ class Paned(Container): # pylint: disable-msg=W0613 def split_axis(self, widget, vertical=True, sibling=None): """Default axis splitter. This should be implemented by subclasses""" + maker = Factory() + self.remove(widget) if vertical: container = VPaned() @@ -50,7 +52,7 @@ class Paned(Container): container = HPaned() if not sibling: - sibling = Terminal() + sibling = maker.make('terminal') self.terminator.register_terminal(sibling) sibling.spawn_child() @@ -64,6 +66,7 @@ class Paned(Container): def add(self, widget): """Add a widget to the container""" + maker = Factory() if len(self.children) == 0: self.pack1(widget, True, True) self.children.append(widget) @@ -77,7 +80,7 @@ class Paned(Container): raise ValueError('Paned widgets can only have two children') self.cnxids[widget] = [] - if isinstance(widget, Terminal): + if maker.isinstance(widget, 'Terminal'): top_window = get_top_window(self) # FIXME: somehow propagate the title-change signal to the Window @@ -111,13 +114,19 @@ class Paned(Container): def wrapcloseterm(self, widget): """A child terminal has closed, so this container must die""" if self.closeterm(widget): - parent = self.get_parent() - parent.remove(self) - # At this point we only have one child, which is the surviving term sibling = self.children[0] self.remove(sibling) - parent.add(sibling) + + parent = self.get_parent() + maker = Factory() + if maker.isinstance(parent, 'Notebook'): + page_num = parent.page_num(self) + parent.remove_page(page_num) + parent.insert_page(sibling, None, page_num) + else: + parent.remove(self) + parent.add(sibling) del(self) else: dbg("Paned::wrapcloseterm: self.closeterm failed") @@ -135,11 +144,12 @@ class Paned(Container): def resizeterm(self, widget, keyname): """Handle a keyboard event requesting a terminal resize""" + maker = Factory() if keyname in ['up', 'down'] and isinstance(self, gtk.VPaned): # This is a key we can handle position = self.get_position() - if isinstance(widget, Terminal): + if maker.isinstance(widget, 'Terminal'): fontheight = widget.vte.get_char_height() else: fontheight = 10 @@ -152,7 +162,7 @@ class Paned(Container): # This is a key we can handle position = self.get_position() - if isinstance(widget, Terminal): + if maker.isinstance(widget, 'Terminal'): fontwidth = widget.vte.get_char_width() else: fontwidth = 10 diff --git a/terminatorlib/test.py b/terminatorlib/test.py index 9438b9b7..a16e6291 100755 --- a/terminatorlib/test.py +++ b/terminatorlib/test.py @@ -4,15 +4,16 @@ import gtk from newterminator import Terminator from window import Window -from terminal import Terminal +from factory import Factory def on_window_destroyed(widget): """Window destroyed, so exit""" gtk.main_quit() +maker = Factory() window = Window() foo = Terminator() -term = Terminal() +term = maker.make('Terminal') foo.register_terminal(term) window.add(term) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 53731a37..8ac54936 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -12,10 +12,8 @@ from util import dbg, err from translation import _ from version import APP_NAME from container import Container -from notebook import Notebook +from factory import Factory from newterminator import Terminator -from terminal import Terminal -from paned import HPaned, VPaned try: import deskbar.core.keybinder as bindkey @@ -103,6 +101,7 @@ class Window(Container, gtk.Window): def on_key_press(self, window, event): """Handle a keyboard event""" + maker = Factory() # FIXME: We probably want to cancel window urgency here mapping = self.terminator.keybindings.lookup(event) @@ -117,8 +116,8 @@ class Window(Container, gtk.Window): self.on_destroy_event(window, gtk.gdk.Event(gtk.gdk.DESTROY)) elif mapping == 'new_tab': - if not isinstance(self.get_child(), Notebook): - notebook = Notebook(self) + if not maker.isinstance(self.get_child(), 'Notebook'): + notebook = maker.make('Notebook', self) self.get_child().newtab() else: return(False) @@ -126,7 +125,8 @@ class Window(Container, gtk.Window): def on_delete_event(self, window, event, data=None): """Handle a window close request""" - if isinstance(self.get_child(), Terminal): + maker = Factory() + if maker.isinstance(self.get_child(), 'Terminal'): dbg('Window::on_delete_event: Only one child, closing is fine') return(False) return(self.confirm_close(window, _('window'))) @@ -200,8 +200,9 @@ class Window(Container, gtk.Window): def add(self, widget): """Add a widget to the window by way of gtk.Window.add()""" + maker = Factory() gtk.Window.add(self, widget) - if isinstance(widget, Terminal): + if maker.isinstance(widget, 'Terminal'): signals = {'close-term': self.closeterm, 'title-change': self.title.set_title, 'split-horiz': self.split_horiz, @@ -221,16 +222,17 @@ class Window(Container, gtk.Window): def split_axis(self, widget, vertical=True, sibling=None): """Split the window""" + maker = Factory() self.remove(widget) # FIXME: we should be creating proper containers, not these gtk widgets if vertical: - container = VPaned() + container = maker.make('VPaned') else: - container = HPaned() + container = maker.make('HPaned') if not sibling: - sibling = Terminal() + sibling = maker.make('Terminal') self.terminator.register_terminal(sibling) self.add(container) container.show_all() From 4577c14c6e8a1d562a5922e3dbc1705cdbcab525 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 25 Nov 2009 09:07:48 +0000 Subject: [PATCH 154/331] Only connect terminal signals to terminals --- terminatorlib/notebook.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index e363ee5c..1dfd94bb 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -113,8 +113,10 @@ class Notebook(Container, gtk.Notebook): 'split-vert': self.split_vert, 'unzoom': self.unzoom} - for signal in signals: - self.connect_child(widget, signal, signals[signal]) + maker = Factory() + if maker.isinstance(widget, 'Terminal'): + for signal in signals: + self.connect_child(widget, signal, signals[signal]) self.set_tab_reorderable(widget, True) label = TabLabel(self.window.get_title(), self) From 05fb49bed69cd47ffd0059d573a42a94e0e86a7f Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 25 Nov 2009 12:51:14 +0000 Subject: [PATCH 155/331] Make Paned widgets eat themselves when they go from 2 to 1 tabs --- terminatorlib/notebook.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index 1dfd94bb..7c7caed0 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -47,6 +47,7 @@ class Notebook(Container, gtk.Notebook): def configure(self): """Apply widget-wide settings""" + # FIXME: Should all of our widgets have this? #self.connect('page-reordered', self.on_page_reordered) self.set_property('homogeneous', not self.config['scroll_tabbar']) self.set_scrollable(self.config['scroll_tabbar']) @@ -95,6 +96,7 @@ class Notebook(Container, gtk.Notebook): err('Notebook::remove: %s not found in Notebook' % widget) return(False) self.remove_page(page_num) + return(True) def newtab(self, widget=None): """Add a new tab, optionally supplying a child widget""" @@ -104,10 +106,7 @@ class Notebook(Container, gtk.Notebook): self.terminator.register_terminal(widget) widget.spawn_child() - # FIXME: We likely need a wrapcloseterm() to handle - # things like removing Notebook when there is only - # one tab left. - signals = {'close-term': self.closeterm, + signals = {'close-term': self.wrapcloseterm, #'title-change': self.title.set_title, 'split-horiz': self.split_horiz, 'split-vert': self.split_vert, @@ -133,7 +132,18 @@ class Notebook(Container, gtk.Notebook): self.append_page(widget, None) self.set_current_page(-1) widget.grab_focus() - + + def wrapcloseterm(self, widget): + """A child terminal has closed""" + if self.closeterm(widget): + if self.get_n_pages() == 1: + child = self.get_nth_page(0) + self.remove_page(0) + parent = self.get_parent() + parent.remove(self) + parent.add(child) + del(self) + def resizeterm(self, widget, keyname): """Handle a keyboard event requesting a terminal resize""" raise NotImplementedError('resizeterm') From 595a3a02e54df8e295c5c4319d20a4a7f25dfdba Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 30 Nov 2009 09:51:34 +0000 Subject: [PATCH 156/331] Might as well do this for all errors --- terminatorlib/translation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/translation.py b/terminatorlib/translation.py index 62b6a7f0..de63a465 100644 --- a/terminatorlib/translation.py +++ b/terminatorlib/translation.py @@ -26,7 +26,7 @@ try: import gettext gettext.textdomain(APP_NAME) _ = gettext.gettext -except ImportError: +except: dbg("Using fallback _()") def dummytrans (text): From ced0e4f3f5ec5550eacfcc382dc18825d8407402 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 30 Nov 2009 09:52:38 +0000 Subject: [PATCH 157/331] And make pylint happy about it --- terminatorlib/translation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/terminatorlib/translation.py b/terminatorlib/translation.py index de63a465..6262d0b4 100644 --- a/terminatorlib/translation.py +++ b/terminatorlib/translation.py @@ -22,6 +22,7 @@ from util import dbg _ = None +# pylint: disable-msg=W0702 try: import gettext gettext.textdomain(APP_NAME) From 0796e70808db7dfc0eca53ddc6141a99847b5fa5 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 7 Dec 2009 20:32:47 +0000 Subject: [PATCH 158/331] Don't create a one-off TabLabel for the first tab, Notebook.newtab() makes them and now the order of creation is fixed they even show up properly --- terminatorlib/notebook.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index 7c7caed0..21f68679 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -37,12 +37,6 @@ class Notebook(Container, gtk.Notebook): window.add(self) self.newtab(child) - label = TabLabel(self.window.get_title(), self) - self.set_tab_label(child, label) - self.set_tab_label_packing(child, not self.config['scroll_tabbar'], - not self.config['scroll_tabbar'], - gtk.PACK_START) - self.show_all() def configure(self): @@ -123,13 +117,12 @@ class Notebook(Container, gtk.Notebook): label.show_all() widget.show_all() + self.append_page(widget, None) self.set_tab_label(widget, label) self.set_tab_label_packing(widget, not self.config['scroll_tabbar'], not self.config['scroll_tabbar'], gtk.PACK_START) - - self.append_page(widget, None) self.set_current_page(-1) widget.grab_focus() From 2db2e0f75e33e78b6e48fe83535f306a7b2a7946 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 8 Dec 2009 09:10:39 +0000 Subject: [PATCH 159/331] Beginnings of making the tab close button work, only handles the simplest case so far --- terminatorlib/factory.py | 3 +++ terminatorlib/notebook.py | 43 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/terminatorlib/factory.py b/terminatorlib/factory.py index 3f22964a..093c4bea 100755 --- a/terminatorlib/factory.py +++ b/terminatorlib/factory.py @@ -36,6 +36,9 @@ class Factory(Borg): elif classtype == 'Notebook': import notebook return(isinstance(product, notebook.Notebook)) + elif classtype == 'Container': + import container + return(isinstance(product, container.Container)) else: err('Factory::isinstance: unknown class type: %s' % classtype) return(False) diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index 21f68679..a7d8e662 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -113,6 +113,7 @@ class Notebook(Container, gtk.Notebook): self.set_tab_reorderable(widget, True) label = TabLabel(self.window.get_title(), self) + label.connect('close-clicked', self.closetab) label.show_all() widget.show_all() @@ -137,6 +138,38 @@ class Notebook(Container, gtk.Notebook): parent.add(child) del(self) + def closetab(self, widget, label): + """Close a tab""" + tabnum = None + try: + nb = widget.notebook + except AttributeError: + err('TabLabel::closetab: called on non-Notebook: %s' % widget) + return + + for i in xrange(0, nb.get_n_pages()): + if label == nb.get_tab_label(nb.get_nth_page(i)): + tabnum = i + break + + if not tabnum: + err('TabLabel::closetab: %s not in %s' % (label, nb)) + return + + maker = Factory() + child = nb.get_nth_page(tabnum) + + if maker.isinstance(child, 'Terminal'): + child.close() + elif maker.isinstance(child, 'Container'): + #FIXME: Handle this case + dbg('Notebook::closetab: Container children not yet handled') + else: + err('Notebook::closetab: Unknown child type %s' % child) + + nb.remove_page(tabnum) + del(label) + def resizeterm(self, widget, keyname): """Handle a keyboard event requesting a terminal resize""" raise NotImplementedError('resizeterm') @@ -158,9 +191,16 @@ class TabLabel(gtk.HBox): icon = None button = None + __gsignals__ = { + 'close-clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_OBJECT,)), + } + def __init__(self, title, notebook): """Class initialiser""" gtk.HBox.__init__(self) + self.__gobject_init__() + self.notebook = notebook self.terminator = Terminator() self.config = Config() @@ -223,5 +263,6 @@ class TabLabel(gtk.HBox): def on_close(self, widget): """The close button has been clicked. Destroy the tab""" - pass + self.emit('close-clicked', self) + # vim: set expandtab ts=4 sw=4: From f9725242ec7e6660fe0c6174a48d6c02e177df1c Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 8 Dec 2009 13:01:13 +0000 Subject: [PATCH 160/331] Make tab opening conext menu work --- terminatorlib/newterminator.py | 9 ++++++++- terminatorlib/notebook.py | 8 ++++++-- terminatorlib/window.py | 11 ++++++++--- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index 167afc37..0fa427cc 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -6,7 +6,7 @@ from borg import Borg from config import Config from keybindings import Keybindings -from util import dbg +from util import dbg, get_top_window class Terminator(Borg): """master object for the application""" @@ -62,6 +62,7 @@ class Terminator(Borg): self.terminals.append(terminal) terminal.connect('ungroup-all', self.ungroup_all) terminal.connect('navigate', self.navigate_terminal) + terminal.connect('tab-new', self.tab_new) def deregister_terminal(self, terminal): """De-register a terminal widget""" @@ -81,6 +82,12 @@ class Terminator(Borg): for terminal in self.terminals: terminal.reconfigure() + def tab_new(self, terminal): + """A terminal asked for a new tab. This function is an indirection + to the Window object""" + window = get_top_window(terminal) + window.tab_new() + def navigate_terminal(self, terminal, direction): """Nagivate around the terminals""" current = self.terminals.index(terminal) diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index a7d8e662..28197e82 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -162,8 +162,12 @@ class Notebook(Container, gtk.Notebook): if maker.isinstance(child, 'Terminal'): child.close() elif maker.isinstance(child, 'Container'): - #FIXME: Handle this case - dbg('Notebook::closetab: Container children not yet handled') + dialog = self.construct_confirm_close(self.get_window(), _('tab')) + result = dialog.run() + dialog.destroy() + + if result is True: + print child.get_children() else: err('Notebook::closetab: Unknown child type %s' % child) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 8ac54936..47b237fb 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -116,13 +116,18 @@ class Window(Container, gtk.Window): self.on_destroy_event(window, gtk.gdk.Event(gtk.gdk.DESTROY)) elif mapping == 'new_tab': - if not maker.isinstance(self.get_child(), 'Notebook'): - notebook = maker.make('Notebook', self) - self.get_child().newtab() + self.tab_new() else: return(False) return(True) + def tab_new(self): + """Make a new tab""" + maker = Factory() + if not maker.isinstance(self.get_child(), 'Notebook'): + notebook = maker.make('Notebook', self) + self.get_child().newtab() + def on_delete_event(self, window, event, data=None): """Handle a window close request""" maker = Factory() From 34da8ffe3aed9df1be2fc0c404b1921077ccb234 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 8 Dec 2009 13:57:29 +0000 Subject: [PATCH 161/331] fix a bunch of bugs in tab handling and attempt to handle closing a tab with multiple children, but all of this is still entirely buggy --- terminatorlib/container.py | 3 +++ terminatorlib/notebook.py | 55 +++++++++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 17572927..d1c524c8 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -67,6 +67,8 @@ class Container(object): if self.cnxids.has_key(widget): for cnxid in self.cnxids[widget]: # FIXME: Look up the IDs to print a useful debugging message + dbg('Container::disconnect_child: removing handler on %s' % + widget.__class__.__name__) widget.disconnect(cnxid) del(self.cnxids[widget]) @@ -107,6 +109,7 @@ class Container(object): pass if not self.remove(widget): + dbg('Container::closeterm: self.remove() failed for %s' % widget) return(False) self.terminator.deregister_terminal(widget) diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index 28197e82..f776e20b 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -12,7 +12,7 @@ from factory import Factory from container import Container from editablelabel import EditableLabel from translation import _ -from util import err +from util import err, dbg class Notebook(Container, gtk.Notebook): """Class implementing a gtk.Notebook container""" @@ -51,13 +51,14 @@ class Notebook(Container, gtk.Notebook): self.set_show_tabs(not self.config['hide_tabbar']) def split_axis(self, widget, vertical=True, sibling=None): - """Default axis splitter. This should be implemented by subclasses""" + """Split the axis of a terminal inside us""" page_num = self.page_num(widget) if page_num == -1: err('Notebook::split_axis: %s not found in Notebook' % widget) return - self.remove_page(page_num) + label = self.get_tab_label(widget) + self.remove(widget) maker = Factory() if vertical: @@ -71,6 +72,7 @@ class Notebook(Container, gtk.Notebook): sibling.spawn_child() self.insert_page(container, None, page_num) + self.set_tab_label(container, label) self.show_all() container.add(widget) @@ -81,15 +83,17 @@ class Notebook(Container, gtk.Notebook): def add(self, widget): """Add a widget to the container""" - raise NotImplementedError('add') + self.newtab(widget) def remove(self, widget): """Remove a widget from the container""" page_num = self.page_num(widget) if page_num == -1: - err('Notebook::remove: %s not found in Notebook' % widget) + err('Notebook::remove: %s not found in Notebook. Actual parent is: %s' % + (widget, widget.get_parent())) return(False) self.remove_page(page_num) + self.disconnect_child(widget) return(True) def newtab(self, widget=None): @@ -130,13 +134,20 @@ class Notebook(Container, gtk.Notebook): def wrapcloseterm(self, widget): """A child terminal has closed""" if self.closeterm(widget): + dbg('Notebook::wrapcloseterm: closeterm succeeded') if self.get_n_pages() == 1: + dbg('Notebook::wrapcloseterm: last page, removing self') child = self.get_nth_page(0) self.remove_page(0) parent = self.get_parent() parent.remove(self) parent.add(child) del(self) + else: + dbg('Notebook::wrapcloseterm: %d pages remain' % + self.get_n_pages()) + else: + dbg('Notebook::wrapcloseterm: closeterm failed') def closetab(self, widget, label): """Close a tab""" @@ -160,16 +171,42 @@ class Notebook(Container, gtk.Notebook): child = nb.get_nth_page(tabnum) if maker.isinstance(child, 'Terminal'): + dbg('Notebook::closetab: child is a single Terminal') child.close() elif maker.isinstance(child, 'Container'): - dialog = self.construct_confirm_close(self.get_window(), _('tab')) + dbg('Notebook::closetab: child is a Container') + dialog = self.construct_confirm_close(self.window, _('tab')) result = dialog.run() dialog.destroy() - if result is True: - print child.get_children() + if result == gtk.RESPONSE_ACCEPT: + containers = [] + objects = [] + for descendant in child.get_children(): + if maker.isinstance(descendant, 'Container'): + containers.append(descendant) + elif maker.isinstance(descendant, 'Terminal'): + objects.append(descendant) + + while len(containers) > 0: + child = containers.pop() + for descendant in child.get_children(): + if maker.isinstance(descendant, 'Container'): + containers.append(descendant) + elif maker.isinstance(descendant, 'Terminal'): + objects.append(descendant) + + for descendant in objects: + descendant.close() + while gtk.events_pending(): + gtk.main_iteration() + return + else: + dbg('Notebook::closetab: user cancelled request') + return else: - err('Notebook::closetab: Unknown child type %s' % child) + err('Notebook::closetab: child is unknown type %s' % child) + return nb.remove_page(tabnum) del(label) From 7b24823fd665a8a989059787c46bddc8a593202a Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 9 Dec 2009 13:01:41 +0000 Subject: [PATCH 162/331] Fix a stupid typo --- terminatorlib/container.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index d1c524c8..42290c94 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -149,7 +149,7 @@ class Container(object): close_all = dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT) label = close_all.get_children()[0].get_children()[0].get_children()[1].set_label(_('Close _Terminals')) - primary = gtk.Label(_('Close multiple temrinals?')) + primary = gtk.Label(_('Close multiple terminals?')) primary.set_use_markup(True) primary.set_alignment(0, 0.5) secondary = gtk.Label(_('This %s has several terminals open. Closing the \ From 24b60821556f27e4ed96a4f61d0af0508500c5bb Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 9 Dec 2009 13:02:13 +0000 Subject: [PATCH 163/331] Add more debugging and make sure we don't fall into the trap of iterating over a list we're modifying --- terminatorlib/notebook.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index f776e20b..1ab76e50 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -133,6 +133,7 @@ class Notebook(Container, gtk.Notebook): def wrapcloseterm(self, widget): """A child terminal has closed""" + dbg('Notebook::wrapcloseterm: called on %s' % widget) if self.closeterm(widget): dbg('Notebook::wrapcloseterm: closeterm succeeded') if self.get_n_pages() == 1: @@ -164,7 +165,7 @@ class Notebook(Container, gtk.Notebook): break if not tabnum: - err('TabLabel::closetab: %s not in %s' % (label, nb)) + err('TabLabel::closetab: %s not in %s. Bailing.' % (label, nb)) return maker = Factory() @@ -196,8 +197,10 @@ class Notebook(Container, gtk.Notebook): elif maker.isinstance(descendant, 'Terminal'): objects.append(descendant) - for descendant in objects: + while len(objects) > 0: + descendant = objects.pop() descendant.close() + # FIXME: Is this mainloop iterations stuff necessary? while gtk.events_pending(): gtk.main_iteration() return From 67b79ff1daf4c9cedff77e7a43e2ac5cb9a5b01d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 9 Dec 2009 13:02:31 +0000 Subject: [PATCH 164/331] There's no reason anymore to special-case notebook here --- terminatorlib/paned.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index 6a8e1886..bee7a28a 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -113,20 +113,15 @@ class Paned(Container): def wrapcloseterm(self, widget): """A child terminal has closed, so this container must die""" + dbg('Paned::wrapcloseterm: Called on %s' % widget) if self.closeterm(widget): # At this point we only have one child, which is the surviving term sibling = self.children[0] self.remove(sibling) parent = self.get_parent() - maker = Factory() - if maker.isinstance(parent, 'Notebook'): - page_num = parent.page_num(self) - parent.remove_page(page_num) - parent.insert_page(sibling, None, page_num) - else: - parent.remove(self) - parent.add(sibling) + parent.remove(self) + parent.add(sibling) del(self) else: dbg("Paned::wrapcloseterm: self.closeterm failed") From a432d10d2441b529af2d8450cd1275213122c66b Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 9 Dec 2009 13:02:45 +0000 Subject: [PATCH 165/331] Add a quick debugging entry --- terminatorlib/terminal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 6a00a213..2e5049d9 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -136,6 +136,7 @@ class Terminal(gtk.VBox): def close(self): """Close ourselves""" + dbg('Terminal::close: emitting close-term') self.emit('close-term') def create_terminalbox(self): From 7ed5a573d52c2c86a556312d9b97ddeaffc5e9c5 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 9 Dec 2009 13:22:27 +0000 Subject: [PATCH 166/331] at the suggestion of pylint, refactor Factory::isinstance() to be more succinct --- terminatorlib/factory.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/terminatorlib/factory.py b/terminatorlib/factory.py index 093c4bea..606d43d0 100755 --- a/terminatorlib/factory.py +++ b/terminatorlib/factory.py @@ -21,24 +21,15 @@ class Factory(Borg): def isinstance(self, product, classtype): """Check if a given product is a particular type of object""" - if classtype == 'Terminal': - import terminal - return(isinstance(product, terminal.Terminal)) - elif classtype == 'VPaned': - import paned - return(isinstance(product, paned.VPaned)) - elif classtype == 'HPaned': - import paned - return(isinstance(product, paned.HPaned)) - elif classtype == 'Paned': - import paned - return(isinstance(product, paned.Paned)) - elif classtype == 'Notebook': - import notebook - return(isinstance(product, notebook.Notebook)) - elif classtype == 'Container': - import container - return(isinstance(product, container.Container)) + types = {'Terminal': 'terminal', + 'VPaned': 'paned', + 'HPaned': 'paned', + 'Paned': 'paned', + 'Notebook': 'notebook', + 'Container': 'container'} + if classtype in types.keys(): + module = __import__(types[classtype], None, None, ['']) + return(isinstance(product, getattr(module, classtype))) else: err('Factory::isinstance: unknown class type: %s' % classtype) return(False) From c31758179ec3bd28e1ef8870d572f55f3f1a337a Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 9 Dec 2009 13:22:34 +0000 Subject: [PATCH 167/331] pylint fixes --- terminatorlib/borg.py | 1 + terminatorlib/container.py | 12 ++++++------ terminatorlib/encoding.py | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/terminatorlib/borg.py b/terminatorlib/borg.py index dc3d17bf..e4132dc3 100755 --- a/terminatorlib/borg.py +++ b/terminatorlib/borg.py @@ -5,6 +5,7 @@ http://code.activestate.com/recipes/66531/""" # pylint: disable-msg=R0903 +# pylint: disable-msg=R0921 class Borg: """Definition of a class that can never be duplicated. Correct usage is thus: diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 42290c94..5609cc74 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -139,21 +139,21 @@ class Container(object): """Unzoom a terminal""" raise NotImplementedError('unzoom') - def construct_confirm_close(self, window, type): + def construct_confirm_close(self, window, reqtype): """Create a confirmation dialog for closing things""" dialog = gtk.Dialog(_('Close?'), window, gtk.DIALOG_MODAL) dialog.set_has_separator(False) dialog.set_resizable(False) - cancel = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT) - close_all = dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT) - label = close_all.get_children()[0].get_children()[0].get_children()[1].set_label(_('Close _Terminals')) + dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT) + c_all = dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT) + c_all.get_children()[0].get_children()[0].get_children()[1].set_label(_('Close _Terminals')) primary = gtk.Label(_('Close multiple terminals?')) primary.set_use_markup(True) primary.set_alignment(0, 0.5) - secondary = gtk.Label(_('This %s has several terminals open. Closing the \ -%s will also close all terminals within it.') % (type, type)) + secondary = gtk.Label(_('This %s has several terminals open. Closing \ +the %s will also close all terminals within it.') % (reqtype, reqtype)) secondary.set_line_wrap(True) labels = gtk.VBox() diff --git a/terminatorlib/encoding.py b/terminatorlib/encoding.py index 6ca8d146..f2f7920e 100644 --- a/terminatorlib/encoding.py +++ b/terminatorlib/encoding.py @@ -25,6 +25,7 @@ This list is taken from gnome-terminal's src/terminal-encoding.c from translation import _ +#pylint: disable-msg=R0903 class TerminatorEncoding: """Class to store encoding details""" From 0d319e8a578f5f211391344a662c79b081817bef Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 9 Dec 2009 13:32:35 +0000 Subject: [PATCH 168/331] Improve pylint.sh to be much less noisy and annoying --- terminatorlib/pylint.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/terminatorlib/pylint.sh b/terminatorlib/pylint.sh index 4e126560..208bdb7a 100755 --- a/terminatorlib/pylint.sh +++ b/terminatorlib/pylint.sh @@ -1,6 +1,11 @@ #!/bin/bash for file in *.py; do - echo -n "$file: " - pylint $file 2>&1 | grep "^Your code has been rated" + line=$(pylint $file 2>&1 | grep "^Your code has been rated") + rating=$(echo $line | cut -f 7 -d ' ') + previous=$(echo $line | cut -f 10 -d ' ') + + if [ "$rating" != "10.00/10" ]; then + echo "$file rated $rating (previously $previous)" + fi done From dfe85f1e9b65882d5fe732b4f13a739bcfe9eefb Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 10 Dec 2009 11:36:46 +0000 Subject: [PATCH 169/331] pylint quiescing --- terminatorlib/terminal_popup_menu.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/terminatorlib/terminal_popup_menu.py b/terminatorlib/terminal_popup_menu.py index 9f18386c..59d6dd08 100755 --- a/terminatorlib/terminal_popup_menu.py +++ b/terminatorlib/terminal_popup_menu.py @@ -161,7 +161,8 @@ class TerminalPopupMenu(object): for encoding in active_encodings: if encoding == terminal.default_encoding: extratext = " (%s)" % _("Default") - elif encoding == current_encoding and terminal.custom_encoding == True: + elif encoding == current_encoding and \ + terminal.custom_encoding == True: extratext = " (%s)" % _("User defined") else: extratext = "" @@ -174,7 +175,8 @@ class TerminalPopupMenu(object): if group is None: group = radioitem - radioitem.connect ('activate', terminal.on_encoding_change, encoding) + radioitem.connect ('activate', terminal.on_encoding_change, + encoding) submenu.append (radioitem) item = gtk.MenuItem (_("Other Encodings")) @@ -201,6 +203,7 @@ class TerminalPopupMenu(object): if encoding[1] == current_encoding: radioitem.set_active (True) - radioitem.connect ('activate', terminal.on_encoding_change, encoding[1]) + radioitem.connect ('activate', terminal.on_encoding_change, + encoding[1]) submenu.append (radioitem) From 33e6254ad27935d6d2042da9efc082ceaed4cffe Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 10 Dec 2009 11:37:53 +0000 Subject: [PATCH 170/331] pylint --- terminatorlib/terminal_popup_menu.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terminatorlib/terminal_popup_menu.py b/terminatorlib/terminal_popup_menu.py index 59d6dd08..f004de22 100755 --- a/terminatorlib/terminal_popup_menu.py +++ b/terminatorlib/terminal_popup_menu.py @@ -192,9 +192,9 @@ class TerminalPopupMenu(object): continue if encoding[1] is None: - label = "%s %s"%(encoding[2], terminal.vte.get_encoding ()) + label = "%s %s" % (encoding[2], terminal.vte.get_encoding ()) else: - label = "%s %s"%(encoding[2], encoding[1]) + label = "%s %s" % (encoding[2], encoding[1]) radioitem = gtk.RadioMenuItem (group, label) if group is None: From f10f43fa8ad3d0212e224aeadc78310bd7668582 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 10 Dec 2009 11:52:26 +0000 Subject: [PATCH 171/331] pylint --- terminatorlib/terminal.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 2e5049d9..bdee825b 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -185,10 +185,10 @@ class Terminal(gtk.VBox): if self.matches['full_uri'] == -1: if posix: - err ('Terminal::update_url_matches: POSIX match failed, trying GNU') + err ('Terminal::update_url_matches: POSIX failed, trying GNU') self.update_url_matches(posix = False) else: - err ('Terminal::update_url_matches: Failed adding URL match patterns') + err ('Terminal::update_url_matches: Failed adding URL matches') else: self.matches['voip'] = self.vte.match_add(lboundry + '(callto:|h323:|sip:)' + "[" + userchars + "+][" + @@ -465,7 +465,9 @@ class Terminal(gtk.VBox): if mapping == "hide_window": return(False) - if mapping and mapping not in ['close_window', 'full_screen', 'new_tab']: + if mapping and mapping not in ['close_window', + 'full_screen', + 'new_tab']: dbg('Terminal::on_keypress: lookup found: %r' % mapping) # handle the case where user has re-bound copy to ctrl+ # we only copy if there is a selection otherwise let it fall through @@ -638,7 +640,8 @@ class Terminal(gtk.VBox): dstpaned = dsthbox.get_parent() srcpaned = srchbox.get_parent() - if isinstance(dstpaned, gtk.Window) and isinstance(srcpaned, gtk.Window): + if isinstance(dstpaned, gtk.Window) and \ + isinstance(srcpaned, gtk.Window): return pos = self.get_location(widget, x, y) @@ -667,13 +670,13 @@ class Terminal(gtk.VBox): -------- """ if (x*coef1 + b1 > y ) and (x*coef2 + b2 < y ): - pos = "right" + pos = "right" if (x*coef1 + b1 > y ) and (x*coef2 + b2 > y ): - pos = "top" + pos = "top" if (x*coef1 + b1 < y ) and (x*coef2 + b2 > y ): - pos = "left" + pos = "left" if (x*coef1 + b1 < y ) and (x*coef2 + b2 < y ): - pos = "bottom" + pos = "bottom" return pos def grab_focus(self): @@ -760,7 +763,8 @@ class Terminal(gtk.VBox): new_columns, new_rows)) - if (new_rows == old_data['old_rows'] or new_columns == old_data['old_columns']): + if new_rows == old_data['old_rows'] or \ + new_columns == old_data['old_columns']: dbg('Terminal::zoom_scale: One axis unchanged, not scaling') return From 602c6a3b5744bf5e2069b257cc58da776d69994d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 10 Dec 2009 13:20:03 +0000 Subject: [PATCH 172/331] Make title changes propagate better, albeit not perfectly yet --- terminatorlib/container.py | 19 ++++++++++++++++++- terminatorlib/editablelabel.py | 4 ++-- terminatorlib/factory.py | 3 ++- terminatorlib/notebook.py | 18 +++++++++++++++++- terminatorlib/paned.py | 2 +- terminatorlib/titlebar.py | 2 ++ 6 files changed, 42 insertions(+), 6 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 5609cc74..57599546 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -6,6 +6,7 @@ import gobject import gtk +from factory import Factory from config import Config from util import dbg, err from translation import _ @@ -171,5 +172,21 @@ the %s will also close all terminals within it.') % (reqtype, reqtype)) dialog.show_all() return(dialog) - + + def propagate_title_change(self, widget, title): + """Pass a title change up the widget stack""" + maker = Factory() + parent = self.get_parent() + title = widget.get_window_title() + + dbg('Container::propagate_title_change: I am %s. My parent is %s' % + (self, parent)) + if maker.isinstance(self, 'Notebook'): + self.update_tab_label_text(widget, title) + elif maker.isinstance(self, 'Window'): + self.title.set_title(widget, title) + + if maker.isinstance(parent, 'Container'): + parent.propagate_title_change(widget, title) + # vim: set expandtab ts=4 sw=4: diff --git a/terminatorlib/editablelabel.py b/terminatorlib/editablelabel.py index db942bf8..e0216e07 100644 --- a/terminatorlib/editablelabel.py +++ b/terminatorlib/editablelabel.py @@ -55,13 +55,13 @@ class EditableLabel(gtk.EventBox): """set angle of the label""" self._label.set_angle( angle ) - def set_text( self, text, force=False): + def set_text(self, text, force=False): """set the text of the label""" self._autotext = text if not self._custom or force: self._label.set_text(text) - def get_text( self ): + def get_text(self): """get the text from the label""" return(self._label.get_text()) diff --git a/terminatorlib/factory.py b/terminatorlib/factory.py index 606d43d0..2cc6ff5f 100755 --- a/terminatorlib/factory.py +++ b/terminatorlib/factory.py @@ -26,7 +26,8 @@ class Factory(Borg): 'HPaned': 'paned', 'Paned': 'paned', 'Notebook': 'notebook', - 'Container': 'container'} + 'Container': 'container', + 'Window': 'window'} if classtype in types.keys(): module = __import__(types[classtype], None, None, ['']) return(isinstance(product, getattr(module, classtype))) diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index 1ab76e50..264dce93 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -105,9 +105,9 @@ class Notebook(Container, gtk.Notebook): widget.spawn_child() signals = {'close-term': self.wrapcloseterm, - #'title-change': self.title.set_title, 'split-horiz': self.split_horiz, 'split-vert': self.split_vert, + 'title-change': self.propagate_title_change, 'unzoom': self.unzoom} maker = Factory() @@ -226,6 +226,18 @@ class Notebook(Container, gtk.Notebook): """Unzoom a terminal""" raise NotImplementedError('unzoom') + def update_tab_label_text(self, widget, text): + """Update the text of a tab label""" + # FIXME: get_tab_label() doesn't descend the widget tree. We need + # something that does or this only works for Notebook->Terminal, not + # Notebook->Container->...->Terminal + label = self.get_tab_label(widget) + if not label: + err('Notebook::update_tab_label_text: %s not found' % widget) + return + + label.set_label(text) + class TabLabel(gtk.HBox): """Class implementing a label widget for Notebook tabs""" notebook = None @@ -257,6 +269,10 @@ class TabLabel(gtk.HBox): self.update_button() self.show_all() + def set_label(self, text): + """Update the text of our label""" + self.label.set_text(text) + def update_button(self): """Update the state of our close button""" if not self.config['close_button_on_tab']: diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index bee7a28a..8a6c9588 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -83,10 +83,10 @@ class Paned(Container): if maker.isinstance(widget, 'Terminal'): top_window = get_top_window(self) - # FIXME: somehow propagate the title-change signal to the Window signals = {'close-term': self.wrapcloseterm, 'split-horiz': self.split_horiz, 'split-vert': self.split_vert, + 'title-change': self.propagate_title_change, 'resize-term': self.resizeterm, 'zoom': top_window.zoom} diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index da6a6df6..fc47df9c 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -107,6 +107,8 @@ class Titlebar(gtk.EventBox): """Update the terminal title""" self.termtext = title self.update() + # Return False so we don't interrupt any chains of signal handling + return False def set_group_label(self, name): """Set the name of the group""" From 7d897eaef7e98c36fae0661bc59a0ba91bf629c0 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 10 Dec 2009 23:25:52 +0000 Subject: [PATCH 173/331] Make title propagation work properly --- terminatorlib/container.py | 2 -- terminatorlib/notebook.py | 21 +++++++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 57599546..6b52ee53 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -179,8 +179,6 @@ the %s will also close all terminals within it.') % (reqtype, reqtype)) parent = self.get_parent() title = widget.get_window_title() - dbg('Container::propagate_title_change: I am %s. My parent is %s' % - (self, parent)) if maker.isinstance(self, 'Notebook'): self.update_tab_label_text(widget, title) elif maker.isinstance(self, 'Window'): diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index 264dce93..4eb69e10 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -226,12 +226,25 @@ class Notebook(Container, gtk.Notebook): """Unzoom a terminal""" raise NotImplementedError('unzoom') + def find_tab_root(self, widget): + """Look for the tab child which is or ultimately contains the supplied + widget""" + parent = widget.get_parent() + previous = parent + + while parent is not None and parent is not self: + previous = parent + parent = parent.get_parent() + + if previous == self: + return(widget) + else: + return(previous) + def update_tab_label_text(self, widget, text): """Update the text of a tab label""" - # FIXME: get_tab_label() doesn't descend the widget tree. We need - # something that does or this only works for Notebook->Terminal, not - # Notebook->Container->...->Terminal - label = self.get_tab_label(widget) + notebook = self.find_tab_root(widget) + label = self.get_tab_label(notebook) if not label: err('Notebook::update_tab_label_text: %s not found' % widget) return From 88cd14ac644e9e4880992745c4cdb0d702d75910 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 11 Dec 2009 14:25:38 +0000 Subject: [PATCH 174/331] Remove a couple of stale FIXMEs --- terminatorlib/container.py | 1 - terminatorlib/window.py | 1 - 2 files changed, 2 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 6b52ee53..7524c809 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -67,7 +67,6 @@ class Container(object): """De-register the signals for a child""" if self.cnxids.has_key(widget): for cnxid in self.cnxids[widget]: - # FIXME: Look up the IDs to print a useful debugging message dbg('Container::disconnect_child: removing handler on %s' % widget.__class__.__name__) widget.disconnect(cnxid) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 47b237fb..9a536b06 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -230,7 +230,6 @@ class Window(Container, gtk.Window): maker = Factory() self.remove(widget) - # FIXME: we should be creating proper containers, not these gtk widgets if vertical: container = maker.make('VPaned') else: From fabc62ba99d201c5f706b94bcf037e7f24ba7d4c Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 11 Dec 2009 14:43:19 +0000 Subject: [PATCH 175/331] who knows, we might even stick to a testing regime this time --- terminatorlib/borg.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/terminatorlib/borg.py b/terminatorlib/borg.py index e4132dc3..7d0b0956 100755 --- a/terminatorlib/borg.py +++ b/terminatorlib/borg.py @@ -2,7 +2,23 @@ # Terminator by Chris Jones # GPL v2 only """borg.py - We are the borg. Resistance is futile. - http://code.activestate.com/recipes/66531/""" + http://code.activestate.com/recipes/66531/ + +>>> obj1 = TestBorg() +>>> obj2 = TestBorg() +>>> obj1.attribute +0 +>>> obj2.attribute +0 +>>> obj1.attribute = 12345 +>>> obj1.attribute +12345 +>>> obj2.attribute +12345 +>>> obj2.attribute = 54321 +>>> obj1.attribute +54321 +""" # pylint: disable-msg=R0903 # pylint: disable-msg=R0921 @@ -42,3 +58,19 @@ class Borg: """This should be used to prepare any attributes of the borg class.""" raise NotImplementedError('prepare_attributes') +if __name__ == '__main__': + class TestBorg(Borg): + attribute = None + + def __init__(self): + Borg.__init__(self) + self.prepare_attributes() + + def prepare_attributes(self): + if not self.attribute: + self.attribute = 0 + + import doctest + (failed, attempted) = doctest.testmod() + print "%d/%d tests failed" % (failed, attempted) + From ecdbe15420193b248070194a683a521a3c9045b8 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 16 Dec 2009 09:15:53 +0000 Subject: [PATCH 176/331] Add some fixmes for things I know are currently broken, and improve a couple of logging messages --- terminatorlib/terminal.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index bdee825b..7950dcaf 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -999,12 +999,15 @@ class Terminal(gtk.VBox): self.zoom() def key_next_tab(self): + # FIXME: Implement this self.terminator.next_tab (self) def key_prev_tab(self): + # FIXME: Implement this self.terminator.previous_tab (self) def key_switch_to_tab_1(self): + # FIXME: Implement these self.terminator.switch_to_tab (self, 0) def key_switch_to_tab_2(self): @@ -1041,12 +1044,15 @@ class Terminal(gtk.VBox): self.vte.reset (True, True) def key_group_all(self): + # FIXME: Implement this self.group_all(self) def key_ungroup_all(self): + # FIXME: Implement this self.ungroup_all(self) def key_group_tab(self): + # FIXME: Implement this self.group_tab(self) def key_ungroup_tab(self): @@ -1060,10 +1066,10 @@ class Terminal(gtk.VBox): cmd = os.path.join (self.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') + err('Terminal::key_new_window: Unable to locate Terminator') return False - dbg("Spawning: %s" % cmd) + dbg("Terminal::key_new_window: Spawning: %s" % cmd) subprocess.Popen([cmd,]) # End key events From 2660d0795f239e599a58579b6e2f884ff9aa5356 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 17 Dec 2009 01:07:01 +0000 Subject: [PATCH 177/331] Add a first run at a plugin system --- terminatorlib/plugin.py | 59 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 terminatorlib/plugin.py diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py new file mode 100755 index 00000000..375de62c --- /dev/null +++ b/terminatorlib/plugin.py @@ -0,0 +1,59 @@ +#!/usr/bin/python +# Terminator by Chris Jones +# GPL v2 only +"""plugin.py - Base plugin system""" + +import sys +import os +import borg +from util import dbg + +class Plugin(object): + """Definition of our base plugin class""" + capabilities = None + + def __init__(self): + """Class initialiser.""" + pass + +class PluginRegistry(borg.Borg): + """Definition of a class to store plugin instances""" + instances = None + path = None + + def __init__(self): + """Class initialiser""" + borg.Borg.__init__(self) + self.prepare_attributes() + + def prepare_attributes(self): + """Prepare our attributes""" + if not self.instances: + self.instances = {} + if not self.path: + (head, tail) = os.path.split(borg.__file__) + self.path = os.path.join(head, 'plugins') + dbg('PluginRegistry::prepare_attributes: Plugin path: %s' % + self.path) + + def load_plugins(self): + """Load all plugins present in the plugins/ directory in our module""" + sys.path.insert(0, self.path) + files = os.listdir(self.path) + for plugin in files: + if os.path.isfile(os.path.join(self.path, plugin)) and \ + plugin[-3:] == '.py': + dbg('PluginRegistry::load_plugins: Importing plugin %s' % + plugin) + __import__(plugin[:-3], None, None, ['']) + + def get_plugins_by_capability(self, capability): + """Return a list of plugins with a particular capability""" + result = [] + for plugin in Plugin.__subclasses__(): + if capability in plugin.capabilities: + if not plugin in self.instances: + self.instances[plugin] = plugin() + result.append(self.instances[plugin]) + return result + From 209c37aa34e31a3b2454469b604419210ef2bd56 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 17 Dec 2009 01:09:13 +0000 Subject: [PATCH 178/331] ridiculously simple and non-functional plugin --- terminatorlib/plugins/url_handlers.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 terminatorlib/plugins/url_handlers.py diff --git a/terminatorlib/plugins/url_handlers.py b/terminatorlib/plugins/url_handlers.py new file mode 100644 index 00000000..fd89aa67 --- /dev/null +++ b/terminatorlib/plugins/url_handlers.py @@ -0,0 +1,7 @@ +import plugin + +class LaunchpadURLHandler(plugin.Plugin): + capabilities = ['url_handler'] + + def do_test(self): + return "Launchpad blah" From 6088084fdfd588600302a42598f11b8dbdf3d923 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 17 Dec 2009 12:54:42 +0000 Subject: [PATCH 179/331] handle import failures and add some very borked doctesting --- terminatorlib/plugin.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py index 375de62c..24f7d332 100755 --- a/terminatorlib/plugin.py +++ b/terminatorlib/plugin.py @@ -1,12 +1,21 @@ #!/usr/bin/python # Terminator by Chris Jones # GPL v2 only -"""plugin.py - Base plugin system""" +"""plugin.py - Base plugin system + +>>> registry = PluginRegistry() +>>> registry.load_plugins() +>>> registry.get_plugins_by_capability('test') +[] +>>> registry.get_plugins_by_capability('this_should_not_ever_exist') +[] + +""" import sys import os import borg -from util import dbg +from util import dbg, err class Plugin(object): """Definition of our base plugin class""" @@ -45,11 +54,17 @@ class PluginRegistry(borg.Borg): plugin[-3:] == '.py': dbg('PluginRegistry::load_plugins: Importing plugin %s' % plugin) - __import__(plugin[:-3], None, None, ['']) + try: + __import__(plugin[:-3], None, None, ['']) + except Exception as e: + err('PluginRegistry::load_plugins: Importing plugin %s \ +failed: %s' % (plugin, e)) def get_plugins_by_capability(self, capability): """Return a list of plugins with a particular capability""" result = [] + dbg('PluginRegistry::get_plugins_by_capability: searching %d plugins \ +for %s' % (len(Plugin.__subclasses__()), capability)) for plugin in Plugin.__subclasses__(): if capability in plugin.capabilities: if not plugin in self.instances: @@ -57,3 +72,9 @@ class PluginRegistry(borg.Borg): result.append(self.instances[plugin]) return result +if __name__ == '__main__': + import doctest + sys.path.insert(0, 'plugins') + import testplugin + (failed, attempted) = doctest.testmod() + print "%d/%d tests failed" % (failed, attempted) From 9029e519f9b89cd70b2c32af85a986e576018488 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 17 Dec 2009 12:54:47 +0000 Subject: [PATCH 180/331] add a test plugin --- terminatorlib/plugins/testplugin.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 terminatorlib/plugins/testplugin.py diff --git a/terminatorlib/plugins/testplugin.py b/terminatorlib/plugins/testplugin.py new file mode 100644 index 00000000..94c2de13 --- /dev/null +++ b/terminatorlib/plugins/testplugin.py @@ -0,0 +1,8 @@ +import plugin + +class TestPlugin(plugin.Plugin): + capabilities = ['test'] + + def do_test(self): + return('TestPluginWin') + From 01bb454d9020a668ab7652be93f2e9fcd64a83db Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 17 Dec 2009 13:51:55 +0000 Subject: [PATCH 181/331] stop (ab)using __subclasses__ isntead interrogating the imported plugin modules for a predefined list of the plugin classes they contain, and always instantiate them when they are imported. Add a test plugin only useful for doctest --- terminatorlib/plugin.py | 35 ++++++++++++++++++--------- terminatorlib/plugins/testplugin.py | 3 +++ terminatorlib/plugins/url_handlers.py | 2 ++ 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py index 24f7d332..639d3e57 100755 --- a/terminatorlib/plugin.py +++ b/terminatorlib/plugin.py @@ -4,11 +4,18 @@ """plugin.py - Base plugin system >>> registry = PluginRegistry() +>>> registry.instances +{} >>> registry.load_plugins() ->>> registry.get_plugins_by_capability('test') -[] +>>> plugins = registry.get_plugins_by_capability('test') +>>> len(plugins) +1 +>>> plugins[0] #doctest: +ELLIPSIS + >>> registry.get_plugins_by_capability('this_should_not_ever_exist') [] +>>> plugins[0].do_test() +'TestPluginWin' """ @@ -50,12 +57,16 @@ class PluginRegistry(borg.Borg): sys.path.insert(0, self.path) files = os.listdir(self.path) for plugin in files: - if os.path.isfile(os.path.join(self.path, plugin)) and \ - plugin[-3:] == '.py': + pluginpath = os.path.join(self.path, plugin) + if os.path.isfile(pluginpath) and plugin[-3:] == '.py': dbg('PluginRegistry::load_plugins: Importing plugin %s' % plugin) try: - __import__(plugin[:-3], None, None, ['']) + module = __import__(plugin[:-3], None, None, ['']) + for item in getattr(module, 'available'): + if item not in self.instances: + func = getattr(module, item) + self.instances[item] = func() except Exception as e: err('PluginRegistry::load_plugins: Importing plugin %s \ failed: %s' % (plugin, e)) @@ -64,17 +75,19 @@ failed: %s' % (plugin, e)) """Return a list of plugins with a particular capability""" result = [] dbg('PluginRegistry::get_plugins_by_capability: searching %d plugins \ -for %s' % (len(Plugin.__subclasses__()), capability)) - for plugin in Plugin.__subclasses__(): - if capability in plugin.capabilities: - if not plugin in self.instances: - self.instances[plugin] = plugin() +for %s' % (len(self.instances), capability)) + for plugin in self.instances: + if capability in self.instances[plugin].capabilities: result.append(self.instances[plugin]) return result + def get_all_plugins(self): + """Return all plugins""" + return(self.instances) + if __name__ == '__main__': import doctest sys.path.insert(0, 'plugins') - import testplugin (failed, attempted) = doctest.testmod() print "%d/%d tests failed" % (failed, attempted) + diff --git a/terminatorlib/plugins/testplugin.py b/terminatorlib/plugins/testplugin.py index 94c2de13..dda17ce2 100644 --- a/terminatorlib/plugins/testplugin.py +++ b/terminatorlib/plugins/testplugin.py @@ -1,5 +1,8 @@ import plugin +# available must contain a list of all the classes that you want exposed +available = ['TestPlugin'] + class TestPlugin(plugin.Plugin): capabilities = ['test'] diff --git a/terminatorlib/plugins/url_handlers.py b/terminatorlib/plugins/url_handlers.py index fd89aa67..f5ca3699 100644 --- a/terminatorlib/plugins/url_handlers.py +++ b/terminatorlib/plugins/url_handlers.py @@ -1,5 +1,7 @@ import plugin +available = ['LaunchpadURLHandler'] + class LaunchpadURLHandler(plugin.Plugin): capabilities = ['url_handler'] From 603da6ec167a22270606ef4f7366ac26e5b242e4 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 17 Dec 2009 23:16:42 +0000 Subject: [PATCH 182/331] Since PluginRegistry is a borg we can easily track if we have been loaded before and if so, not load plugins again. --- terminatorlib/plugin.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py index 639d3e57..4f6f4a3e 100755 --- a/terminatorlib/plugin.py +++ b/terminatorlib/plugin.py @@ -36,6 +36,7 @@ class PluginRegistry(borg.Borg): """Definition of a class to store plugin instances""" instances = None path = None + done = None def __init__(self): """Class initialiser""" @@ -51,9 +52,15 @@ class PluginRegistry(borg.Borg): self.path = os.path.join(head, 'plugins') dbg('PluginRegistry::prepare_attributes: Plugin path: %s' % self.path) + if not self.done: + self.done = False def load_plugins(self): """Load all plugins present in the plugins/ directory in our module""" + if self.done: + dbg('PluginRegistry::load_plugins: Already loaded') + return + sys.path.insert(0, self.path) files = os.listdir(self.path) for plugin in files: @@ -71,6 +78,8 @@ class PluginRegistry(borg.Borg): err('PluginRegistry::load_plugins: Importing plugin %s \ failed: %s' % (plugin, e)) + self.done = True + def get_plugins_by_capability(self, capability): """Return a list of plugins with a particular capability""" result = [] From b84f5934046169717baaa40c477ab4b2bdb4a6c1 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 17 Dec 2009 23:17:03 +0000 Subject: [PATCH 183/331] port over launchpad specific URL handling from Terminal --- terminatorlib/plugins/url_handlers.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/terminatorlib/plugins/url_handlers.py b/terminatorlib/plugins/url_handlers.py index f5ca3699..a6071a1f 100644 --- a/terminatorlib/plugins/url_handlers.py +++ b/terminatorlib/plugins/url_handlers.py @@ -1,9 +1,27 @@ +import re import plugin available = ['LaunchpadURLHandler'] -class LaunchpadURLHandler(plugin.Plugin): +class URLHandler(plugin.Plugin): + """Base class for URL handlers""" capabilities = ['url_handler'] + handler_name = None + match = None + + def callback(self, url): + raise NotImplementedError + +class LaunchpadURLHandler(URLHandler): + """Launchpad URL handler. If the URL looks like a Launchpad changelog + closure entry... 'LP: #12345' then it should be transformed into a + Launchpad URL""" + capabilities = ['url_handler'] + handler_name = 'launchpad' + match = '\\bLP:? #?[0-9]+\\b' + + def callback(self, url): + for item in re.findall(r'[0-9]+', url): + url = 'https://bugs.launchpad.net/bugs/%s' % item + return(url) - def do_test(self): - return "Launchpad blah" From 30f52258841f6df1f4ab8eaad984fcc684475f10 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 17 Dec 2009 23:17:32 +0000 Subject: [PATCH 184/331] Add crashproof code for adding URL handlers from plugins, and reacting to them --- terminatorlib/terminal.py | 43 ++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 7950dcaf..99198293 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -22,6 +22,7 @@ from titlebar import Titlebar from terminal_popup_menu import TerminalPopupMenu from searchbar import Searchbar from translation import _ +import plugin try: import vte @@ -204,11 +205,22 @@ class Terminal(gtk.VBox): self.matches['nntp'] = self.vte.match_add (lboundry + '''news:[-A-Z\^_a-z{|}~!"#$%&'()*+,./0-9;:=?`]+@\ [-A-Za-z0-9.]+(:[0-9]+)?''' + rboundry) - # if the url looks like a Launchpad changelog closure entry - # LP: #92953 - make it a url to http://bugs.launchpad.net - self.matches['launchpad'] = self.vte.match_add ( - '\\bLP:? #?[0-9]+\\b') + # Now add any matches from plugins + try: + registry = plugin.PluginRegistry() + registry.load_plugins() + plugins = registry.get_plugins_by_capability('url_handler') + + for urlplugin in plugins: + name = urlplugin.handler_name + match = urlplugin.match + self.matches[name] = self.vte.match_add(match) + dbg('Terminal::update_matches: added plugin URL handler \ +for %s' % name) + except Exception as ex: + err('Terminal::update_url_matches: %s' % ex) + def connect_signals(self): """Connect all the gtk signals and drag-n-drop mechanics""" @@ -858,12 +870,23 @@ class Terminal(gtk.VBox): url = 'ftp://' + url elif match == self.matches['addr_only']: url = 'http://' + url - elif match == self.matches['launchpad']: - for item in re.findall(r'[0-9]+', url): - url = 'https://bugs.launchpad.net/bugs/%s' % item - return(url) - else: - return(url) + elif match in self.matches.values(): + # We have a match, but it's not a hard coded one, so it's a plugin + try: + registry = plugin.PluginRegistry() + registry.load_plugins() + plugins = registry.get_plugins_by_capability('url_handler') + + for urlplugin in plugins: + if match == self.matches[urlplugin.handler_name]: + newurl = urlplugin.callback(url) + if newurl is not None: + url = newurl + break; + except Exception as ex: + err('Terminal::prepare_url: %s' % ex) + + return(url) def open_url(self, url, prepare=False): """Open a given URL, conditionally unpacking it from a VTE match""" From 5dc643a3b2a60e4a48dd1ce8ddef2d73173efb32 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 17 Dec 2009 23:30:12 +0000 Subject: [PATCH 185/331] Add some docstrings --- terminatorlib/plugins/url_handlers.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/terminatorlib/plugins/url_handlers.py b/terminatorlib/plugins/url_handlers.py index a6071a1f..fbad40be 100644 --- a/terminatorlib/plugins/url_handlers.py +++ b/terminatorlib/plugins/url_handlers.py @@ -1,6 +1,10 @@ +# Terminator by Chris Jones Date: Fri, 18 Dec 2009 00:45:08 +0000 Subject: [PATCH 186/331] Integrate the plugin system into the context menus for Terminal --- terminatorlib/plugins/terminal_menu.py | 29 ++++++++++++++++++++++++++ terminatorlib/terminal_popup_menu.py | 14 +++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 terminatorlib/plugins/terminal_menu.py diff --git a/terminatorlib/plugins/terminal_menu.py b/terminatorlib/plugins/terminal_menu.py new file mode 100644 index 00000000..e2fcc1a6 --- /dev/null +++ b/terminatorlib/plugins/terminal_menu.py @@ -0,0 +1,29 @@ +# Terminator by Chris Jones Date: Fri, 18 Dec 2009 09:24:23 +0000 Subject: [PATCH 187/331] Add a FIXME --- terminatorlib/plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py index 4f6f4a3e..9c5feaae 100755 --- a/terminatorlib/plugin.py +++ b/terminatorlib/plugin.py @@ -49,6 +49,8 @@ class PluginRegistry(borg.Borg): self.instances = {} if not self.path: (head, tail) = os.path.split(borg.__file__) + # FIXME: self.path should really be a list so we can have something + # in the users home directory self.path = os.path.join(head, 'plugins') dbg('PluginRegistry::prepare_attributes: Plugin path: %s' % self.path) From 0b5ac38793473154f373e844b365ad0bb9973259 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 19 Dec 2009 02:08:35 +0000 Subject: [PATCH 188/331] Hand merge in from trunk --- ChangeLog | 5 + README | 2 +- debian/changelog | 6 + debian/control | 4 +- doc/terminator_config.5 | 4 + po/ar.po | 274 ++++++++------- po/bg.po | 297 ++++++++-------- po/ca.po | 2 +- po/cs.po | 128 +++---- po/da.po | 20 +- po/de.po | 36 +- po/el.po | 210 +++++------ po/en_CA.po | 27 +- po/en_GB.po | 2 +- po/eo.po | 80 ++--- po/es.po | 26 +- po/et.po | 62 ++-- po/eu.po | 76 ++-- po/fa.po | 54 +-- po/fi.po | 2 +- po/fr.po | 26 +- po/fy.po | 78 ++-- po/ga.po | 44 +-- po/gl.po | 2 +- po/he.po | 115 +++--- po/hi.po | 16 +- po/hu.po | 144 ++++---- po/id.po | 2 +- po/it.po | 36 +- po/ja.po | 214 +++++------ po/jv.po | 2 +- po/ko.po | 30 +- po/lt.po | 218 ++++++------ po/lv.po | 242 ++++++------- po/mk.po | 152 ++++---- po/mr.po | 44 +-- po/ms.po | 606 ++++++++++++++++---------------- po/nb.po | 42 +-- po/nl.po | 223 ++++++------ po/nn.po | 74 ++-- po/pl.po | 26 +- po/pt.po | 8 +- po/pt_BR.po | 24 +- po/ro.po | 133 +++---- po/ru.po | 64 ++-- po/si.po | 2 +- po/sk.po | 27 +- po/sq.po | 164 ++++----- po/sr.po | 2 +- po/sv.po | 112 +++--- po/ta.po | 12 +- po/te.po | 28 +- po/tr.po | 56 +-- po/uk.po | 337 +++++++++--------- po/zh_CN.po | 110 +++--- po/zh_HK.po | 274 +++++++-------- po/zh_TW.po | 256 +++++++------- terminator | 4 +- terminator.spec | 2 +- terminatorlib/config.py | 1 + terminatorlib/keybindings.py | 3 +- terminatorlib/prefs_profile.py | 12 +- terminatorlib/terminator.py | 131 ++++++- terminatorlib/terminatorterm.py | 32 +- terminatorlib/version.py | 2 +- 65 files changed, 2834 insertions(+), 2615 deletions(-) diff --git a/ChangeLog b/ChangeLog index d4571dd3..4b570c87 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,11 @@ terminator 0.14: * Major reworking of the grouping interface by Stephen Boddy + * Keybindings can now be disabled by setting them to "None" + * Change default behaviour to enable full transparency + * Terminal titlebars can now be edited like tab labels + * Geometry hinting is now available and enabled by default + * Lots of bug fixing terminator 0.13: * Bug fixes diff --git a/README b/README index 6b2ec1ca..74c8cd9b 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Terminator 0.13 +Terminator 0.14 by Chris Jones and others. The goal of this project is to produce a useful tool for arranging terminals. diff --git a/debian/changelog b/debian/changelog index d4ba2780..16aea692 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +terminator (0.14) karmic; urgency=low + + * New upstream release + + -- Chris Jones Thu, 03 Dec 2009 12:54:57 +0000 + terminator (0.13) karmic; urgency=low * New upstream release diff --git a/debian/control b/debian/control index 0717af3b..b37d6f84 100644 --- a/debian/control +++ b/debian/control @@ -14,10 +14,10 @@ Homepage: http://www.tenshu.net/terminator/ Package: terminator Architecture: all -Depends: ${python:Depends}, ${misc:Depends}, python-vte, python-gnome2, python-gobject, python-gtk2 (>= 2.14.0), gconf2, libgtk2.0-bin +Depends: ${python:Depends}, ${misc:Depends}, python-vte, python-gobject, python-gtk2 (>= 2.14.0), gconf2, libgtk2.0-bin XB-Python-Version: ${python:Versions} Provides: x-terminal-emulator -Recommends: xdg-utils, python-xdg +Recommends: xdg-utils, python-xdg, python-gnome2 Description: multiple GNOME terminals in one window Terminator is a little project to produce an efficient way of filling a large area of screen space with terminals. diff --git a/doc/terminator_config.5 b/doc/terminator_config.5 index 11fdfcb9..b2f1ec35 100644 --- a/doc/terminator_config.5 +++ b/doc/terminator_config.5 @@ -95,6 +95,10 @@ Default value: \fBblock\fR Sets what type of terminal should be emulated. Default value: \fBxterm\fR .TP +.B geometry_hinting +If True the window will resize in step with font sizes, if False it will follow pixels +Default value: \fBTrue\fR +.TP .B font An Pango font name. Examples are "Sans 12" or "Monospace Bold 14". Default value: \fBMono 8\fR diff --git a/po/ar.po b/po/ar.po index 85201166..67e93b87 100644 --- a/po/ar.po +++ b/po/ar.po @@ -8,23 +8,86 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-01-27 14:45+0000\n" -"Last-Translator: Nizar Kerkeni \n" +"PO-Revision-Date: 2009-08-25 13:56+0000\n" +"Last-Translator: Nizar Kerkeni \n" "Language-Team: Arabic \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" +#: ../terminator:46 +msgid "" +"You need to install the python bindings for gobject, gtk and pango to run " +"Terminator." +msgstr "" + +#: ../terminator:129 +msgid "" +"You need to run terminator in an X environment. Make sure DISPLAY is " +"properly set" +msgstr "" + +#: ../terminatorlib/configfile.py:96 +msgid "Unterminated quoted string" +msgstr "" + #: ../terminatorlib/configfile.py:147 msgid "Setting without a value" msgstr "من دون تحديد قيمة" +#: ../terminatorlib/configfile.py:152 +msgid "Unexpected token" +msgstr "" + +#: ../terminatorlib/config.py:250 +#, python-format +msgid "" +"Configuration error\n" +"\n" +"Errors were encountered while parsing terminator_config(5) file:\n" +"\n" +" %s\n" +"\n" +"%d line(s) have been ignored." +msgstr "" +"خطأ في الإعدادات\n" +"تمت مصادفة أخطاء أثناء تحليل الملف terminator_config(5) :\n" +"\n" +" %s \n" +"\n" +"%d سطر قد تم تجاهلها." + #: ../terminatorlib/config.py:258 msgid "Configuration error" msgstr "خطأ في الإعدادات" +#: ../terminatorlib/config.py:311 +#, python-format +msgid "Setting %r value %r not a valid colour; ignoring" +msgstr "" + +#: ../terminatorlib/config.py:316 +#, python-format +msgid "%s must be one of: top, left, right, bottom" +msgstr "" + +#: ../terminatorlib/config.py:323 +#, python-format +msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" +msgstr "" + +#: ../terminatorlib/config.py:329 +msgid "" +"Reading list values from terminator_config(5) is not currently supported" +msgstr "" + +#: ../terminatorlib/config.py:332 +#, python-format +msgid "Setting %r should be a section name" +msgstr "" + #: ../terminatorlib/encoding.py:29 msgid "Current Locale" msgstr "المحلية الحالية" @@ -174,6 +237,15 @@ msgstr "اسم" msgid "Action" msgstr "إجراء" +#: ../terminatorlib/prefs_profile.py:384 +msgid "Keyboard shortcut" +msgstr "" + +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "اغلق اللسان" @@ -182,6 +254,15 @@ msgstr "اغلق اللسان" msgid "tab" msgstr "لسان" +#: ../terminatorlib/terminator.py:230 +#, python-format +msgid "Invalid geometry string %r" +msgstr "" + +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "" + #: ../terminatorlib/terminator.py:413 msgid "window" msgstr "نافذة" @@ -191,6 +272,21 @@ msgstr "نافذة" msgid "Close?" msgstr "أأغلق؟" +#: ../terminatorlib/terminator.py:423 +msgid "Close _Terminals" +msgstr "" + +#: ../terminatorlib/terminator.py:425 +msgid "Close multiple terminals?" +msgstr "" + +#: ../terminatorlib/terminator.py:428 +#, python-format +msgid "" +"This %s has several terminals open. Closing the %s will also close all " +"terminals within it." +msgstr "" + #: ../terminatorlib/terminatorterm.py:39 msgid "" "You need to install python bindings for libvte (\"python-vte\" in " @@ -207,6 +303,15 @@ msgstr "إبحث:" msgid "Next" msgstr "التالي" +#. Give up, we're completely stuck +#: ../terminatorlib/terminatorterm.py:518 +msgid "Unable to find a shell" +msgstr "" + +#: ../terminatorlib/terminatorterm.py:539 +msgid "Unable to start shell: " +msgstr "" + #: ../terminatorlib/terminatorterm.py:1033 msgid "_Open Link" msgstr "ا_فتح الوصلة" @@ -223,6 +328,14 @@ msgstr "أر_سل رسالة إلى..." msgid "_Copy Email Address" msgstr "ا_نسخ عنوان البريد الإلكتروني" +#: ../terminatorlib/terminatorterm.py:1071 +msgid "Show _scrollbar" +msgstr "" + +#: ../terminatorlib/terminatorterm.py:1076 +msgid "Show _titlebar" +msgstr "" + #: ../terminatorlib/terminatorterm.py:1089 msgid "Split H_orizontally" msgstr "اقسم أف_قيا" @@ -235,137 +348,6 @@ msgstr "اقسم ع_موديا" msgid "Open _Tab" msgstr "افتح _لسان" -#: ../terminatorlib/terminatorterm.py:1127 -msgid "_Zoom terminal" -msgstr "ت_كبير الطرفية" - -#: ../terminatorlib/terminatorterm.py:1179 -msgid "None" -msgstr "لاشيء" - -#: ../terminatorlib/terminatorterm.py:1216 -msgid "Group name:" -msgstr "اسم المجموعة:" - -#: ../terminatorlib/terminatorterm.py:1262 -msgid "All" -msgstr "الجميع" - -#: ../terminatorlib/terminatorterm.py:1282 -msgid "Encodings" -msgstr "الترميزات" - -#: ../terminatorlib/terminatorterm.py:1300 -msgid "Other Encodings" -msgstr "ترميزات اخرى" - -#: ../terminator:46 -msgid "" -"You need to install the python bindings for gobject, gtk and pango to run " -"Terminator." -msgstr "" - -#: ../terminator:129 -msgid "" -"You need to run terminator in an X environment. Make sure DISPLAY is " -"properly set" -msgstr "" - -#: ../terminatorlib/configfile.py:96 -msgid "Unterminated quoted string" -msgstr "" - -#: ../terminatorlib/configfile.py:152 -msgid "Unexpected token" -msgstr "" - -#: ../terminatorlib/config.py:250 -#, python-format -msgid "" -"Configuration error\n" -"\n" -"Errors were encountered while parsing terminator_config(5) file:\n" -"\n" -" %s\n" -"\n" -"%d line(s) have been ignored." -msgstr "" - -#: ../terminatorlib/config.py:311 -#, python-format -msgid "Setting %r value %r not a valid colour; ignoring" -msgstr "" - -#: ../terminatorlib/config.py:316 -#, python-format -msgid "%s must be one of: top, left, right, bottom" -msgstr "" - -#: ../terminatorlib/config.py:323 -#, python-format -msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" -msgstr "" - -#: ../terminatorlib/config.py:329 -msgid "" -"Reading list values from terminator_config(5) is not currently supported" -msgstr "" - -#: ../terminatorlib/config.py:332 -#, python-format -msgid "Setting %r should be a section name" -msgstr "" - -#: ../terminatorlib/prefs_profile.py:384 -msgid "Keyboard shortcut" -msgstr "" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:230 -#, python-format -msgid "Invalid geometry string %r" -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - -#: ../terminatorlib/terminator.py:423 -msgid "Close _Terminals" -msgstr "" - -#: ../terminatorlib/terminator.py:425 -msgid "Close multiple terminals?" -msgstr "" - -#: ../terminatorlib/terminator.py:428 -#, python-format -msgid "" -"This %s has several terminals open. Closing the %s will also close all " -"terminals within it." -msgstr "" - -#. Give up, we're completely stuck -#: ../terminatorlib/terminatorterm.py:518 -msgid "Unable to find a shell" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:539 -msgid "Unable to start shell: " -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1071 -msgid "Show _scrollbar" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1076 -msgid "Show _titlebar" -msgstr "" - #: ../terminatorlib/terminatorterm.py:1112 msgid "Open _Debug Tab" msgstr "" @@ -374,6 +356,10 @@ msgstr "" msgid "Open Top Level Tab" msgstr "" +#: ../terminatorlib/terminatorterm.py:1127 +msgid "_Zoom terminal" +msgstr "ت_كبير الطرفية" + #: ../terminatorlib/terminatorterm.py:1131 msgid "Ma_ximise terminal" msgstr "" @@ -394,6 +380,10 @@ msgstr "" msgid "_Group" msgstr "" +#: ../terminatorlib/terminatorterm.py:1179 +msgid "None" +msgstr "لاشيء" + #: ../terminatorlib/terminatorterm.py:1194 msgid "_New group" msgstr "" @@ -406,6 +396,22 @@ msgstr "" msgid "_Ungroup all" msgstr "" +#: ../terminatorlib/terminatorterm.py:1216 +msgid "Group name:" +msgstr "اسم المجموعة:" + +#: ../terminatorlib/terminatorterm.py:1262 +msgid "All" +msgstr "الجميع" + +#: ../terminatorlib/terminatorterm.py:1282 +msgid "Encodings" +msgstr "الترميزات" + +#: ../terminatorlib/terminatorterm.py:1300 +msgid "Other Encodings" +msgstr "ترميزات اخرى" + #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" msgstr "" diff --git a/po/bg.po b/po/bg.po index 1f1cc439..9263cf39 100644 --- a/po/bg.po +++ b/po/bg.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2008-07-29 22:15+0000\n" -"Last-Translator: Narf \n" +"PO-Revision-Date: 2009-10-08 14:51+0000\n" +"Last-Translator: Dragomir Minkovski \n" "Language-Team: Bulgarian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -25,119 +25,6 @@ msgstr "" "Трябва да инсталирате python bind-овете за gobject, gtk и pango за да можете " "да използвате Terminator." -#: ../terminatorlib/encoding.py:34 ../terminatorlib/encoding.py:64 -#: ../terminatorlib/encoding.py:70 ../terminatorlib/encoding.py:72 -#: ../terminatorlib/encoding.py:77 ../terminatorlib/encoding.py:95 -msgid "Cyrillic" -msgstr "Кирилица" - -#: ../terminatorlib/encoding.py:36 ../terminatorlib/encoding.py:80 -#: ../terminatorlib/encoding.py:97 -msgid "Greek" -msgstr "Гръцки" - -#: ../terminatorlib/encoding.py:38 ../terminatorlib/encoding.py:66 -#: ../terminatorlib/encoding.py:83 ../terminatorlib/encoding.py:99 -msgid "Hebrew" -msgstr "Иврит" - -#: ../terminatorlib/encoding.py:39 ../terminatorlib/encoding.py:65 -#: ../terminatorlib/encoding.py:87 ../terminatorlib/encoding.py:98 -msgid "Turkish" -msgstr "Турски" - -#: ../terminatorlib/encoding.py:42 -msgid "Celtic" -msgstr "Келтски" - -#: ../terminatorlib/encoding.py:44 ../terminatorlib/encoding.py:86 -msgid "Romanian" -msgstr "Румънски" - -#: ../terminatorlib/encoding.py:50 -msgid "Armenian" -msgstr "Арменски" - -#: ../terminatorlib/encoding.py:53 -msgid "Cyrillic/Russian" -msgstr "Кирилица/Руски" - -#: ../terminatorlib/encoding.py:54 ../terminatorlib/encoding.py:68 -#: ../terminatorlib/encoding.py:89 -msgid "Japanese" -msgstr "Японски" - -#: ../terminatorlib/encoding.py:55 ../terminatorlib/encoding.py:69 -#: ../terminatorlib/encoding.py:71 ../terminatorlib/encoding.py:92 -msgid "Korean" -msgstr "Корейски" - -#: ../terminatorlib/encoding.py:73 ../terminatorlib/encoding.py:88 -msgid "Cyrillic/Ukrainian" -msgstr "Кирилица/Украински" - -#: ../terminatorlib/encoding.py:76 -msgid "Croatian" -msgstr "Хърватски" - -#: ../terminatorlib/encoding.py:78 -msgid "Hindi" -msgstr "Хинди" - -#: ../terminatorlib/encoding.py:79 -msgid "Persian" -msgstr "Персийски" - -#: ../terminatorlib/encoding.py:84 -msgid "Icelandic" -msgstr "Исландски" - -#: ../terminatorlib/encoding.py:90 ../terminatorlib/encoding.py:93 -#: ../terminatorlib/encoding.py:102 -msgid "Vietnamese" -msgstr "Виетнамски" - -#. show dialog -#: ../terminatorlib/terminator.py:417 -msgid "Close?" -msgstr "Затваряне?" - -#: ../terminatorlib/terminatorterm.py:39 -msgid "" -"You need to install python bindings for libvte (\"python-vte\" in " -"debian/ubuntu)" -msgstr "" -"Трябва да инсталирате python bind-ове за libvte (\"python-vte\" в " -"debian/ubuntu)" - -#: ../terminatorlib/terminatorterm.py:1033 -msgid "_Open Link" -msgstr "_Отваряне на връзка" - -#: ../terminatorlib/terminatorterm.py:1034 -msgid "_Copy Link Address" -msgstr "_Копиране на адреса на връзката" - -#: ../terminatorlib/terminatorterm.py:1044 -msgid "_Send Mail To..." -msgstr "Из_пращане на е-поща до..." - -#: ../terminatorlib/terminatorterm.py:1045 -msgid "_Copy Email Address" -msgstr "Копиране на _адреса на е-пощата" - -#: ../terminatorlib/terminatorterm.py:1071 -msgid "Show _scrollbar" -msgstr "Покажи _скролбар" - -#: ../terminatorlib/terminatorterm.py:1089 -msgid "Split H_orizontally" -msgstr "Раздели х_оризонално" - -#: ../terminatorlib/terminatorterm.py:1090 -msgid "Split V_ertically" -msgstr "Раздели в_ертикално" - #: ../terminator:129 msgid "" "You need to run terminator in an X environment. Make sure DISPLAY is " @@ -167,6 +54,13 @@ msgid "" "\n" "%d line(s) have been ignored." msgstr "" +"Конфигурационна грешка\n" +"\n" +"Грешка при прочитане на файла terminator_config(5):\n" +"\n" +" %s\n" +"\n" +"%d ред(а) са игнорирани." #: ../terminatorlib/config.py:258 msgid "Configuration error" @@ -180,7 +74,7 @@ msgstr "" #: ../terminatorlib/config.py:316 #, python-format msgid "%s must be one of: top, left, right, bottom" -msgstr "" +msgstr "%s трябва да е: горе, дясно или долу" #: ../terminatorlib/config.py:323 #, python-format @@ -214,53 +108,125 @@ msgstr "" #: ../terminatorlib/encoding.py:32 msgid "South European" -msgstr "" +msgstr "Южноевропейски" #: ../terminatorlib/encoding.py:33 ../terminatorlib/encoding.py:41 #: ../terminatorlib/encoding.py:101 msgid "Baltic" -msgstr "" +msgstr "Балтийски" + +#: ../terminatorlib/encoding.py:34 ../terminatorlib/encoding.py:64 +#: ../terminatorlib/encoding.py:70 ../terminatorlib/encoding.py:72 +#: ../terminatorlib/encoding.py:77 ../terminatorlib/encoding.py:95 +msgid "Cyrillic" +msgstr "Кирилица" #: ../terminatorlib/encoding.py:35 ../terminatorlib/encoding.py:67 #: ../terminatorlib/encoding.py:74 ../terminatorlib/encoding.py:100 msgid "Arabic" -msgstr "" +msgstr "Арабски" + +#: ../terminatorlib/encoding.py:36 ../terminatorlib/encoding.py:80 +#: ../terminatorlib/encoding.py:97 +msgid "Greek" +msgstr "Гръцки" #: ../terminatorlib/encoding.py:37 msgid "Hebrew Visual" -msgstr "" +msgstr "Иврит (визуален)" + +#: ../terminatorlib/encoding.py:38 ../terminatorlib/encoding.py:66 +#: ../terminatorlib/encoding.py:83 ../terminatorlib/encoding.py:99 +msgid "Hebrew" +msgstr "Иврит" + +#: ../terminatorlib/encoding.py:39 ../terminatorlib/encoding.py:65 +#: ../terminatorlib/encoding.py:87 ../terminatorlib/encoding.py:98 +msgid "Turkish" +msgstr "Турски" #: ../terminatorlib/encoding.py:40 msgid "Nordic" -msgstr "" +msgstr "Скандинавски" + +#: ../terminatorlib/encoding.py:42 +msgid "Celtic" +msgstr "Келтски" + +#: ../terminatorlib/encoding.py:44 ../terminatorlib/encoding.py:86 +msgid "Romanian" +msgstr "Румънски" #: ../terminatorlib/encoding.py:45 ../terminatorlib/encoding.py:46 #: ../terminatorlib/encoding.py:47 ../terminatorlib/encoding.py:48 #: ../terminatorlib/encoding.py:49 msgid "Unicode" -msgstr "" +msgstr "Уникод" + +#: ../terminatorlib/encoding.py:50 +msgid "Armenian" +msgstr "Арменски" #: ../terminatorlib/encoding.py:51 ../terminatorlib/encoding.py:52 #: ../terminatorlib/encoding.py:56 msgid "Chinese Traditional" -msgstr "" +msgstr "Традниционен китайски" + +#: ../terminatorlib/encoding.py:53 +msgid "Cyrillic/Russian" +msgstr "Кирилица/Руски" + +#: ../terminatorlib/encoding.py:54 ../terminatorlib/encoding.py:68 +#: ../terminatorlib/encoding.py:89 +msgid "Japanese" +msgstr "Японски" + +#: ../terminatorlib/encoding.py:55 ../terminatorlib/encoding.py:69 +#: ../terminatorlib/encoding.py:71 ../terminatorlib/encoding.py:92 +msgid "Korean" +msgstr "Корейски" #: ../terminatorlib/encoding.py:57 ../terminatorlib/encoding.py:58 #: ../terminatorlib/encoding.py:59 ../terminatorlib/encoding.py:61 msgid "Chinese Simplified" -msgstr "" +msgstr "Упростен китайски" #: ../terminatorlib/encoding.py:60 msgid "Georgian" -msgstr "" +msgstr "Грузински" + +#: ../terminatorlib/encoding.py:73 ../terminatorlib/encoding.py:88 +msgid "Cyrillic/Ukrainian" +msgstr "Кирилица/Украински" + +#: ../terminatorlib/encoding.py:76 +msgid "Croatian" +msgstr "Хърватски" + +#: ../terminatorlib/encoding.py:78 +msgid "Hindi" +msgstr "Хинди" + +#: ../terminatorlib/encoding.py:79 +msgid "Persian" +msgstr "Персийски" #: ../terminatorlib/encoding.py:81 msgid "Gujarati" -msgstr "" +msgstr "Гуджарати" #: ../terminatorlib/encoding.py:82 msgid "Gurmukhi" -msgstr "" +msgstr "Гурмуки" + +#: ../terminatorlib/encoding.py:84 +msgid "Icelandic" +msgstr "Исландски" + +#: ../terminatorlib/encoding.py:90 ../terminatorlib/encoding.py:93 +#: ../terminatorlib/encoding.py:102 +msgid "Vietnamese" +msgstr "Виетнамски" #: ../terminatorlib/encoding.py:91 msgid "Thai" @@ -268,15 +234,15 @@ msgstr "" #: ../terminatorlib/prefs_profile.py:370 msgid "Name" -msgstr "" +msgstr "Име" #: ../terminatorlib/prefs_profile.py:377 msgid "Action" -msgstr "" +msgstr "Действие" #: ../terminatorlib/prefs_profile.py:384 msgid "Keyboard shortcut" -msgstr "" +msgstr "Клавишна комбинация" #: ../terminatorlib/terminator.py:37 msgid "" @@ -285,11 +251,11 @@ msgstr "" #: ../terminatorlib/terminator.py:76 msgid "Close Tab" -msgstr "" +msgstr "Затваряне на подпрозореца" #: ../terminatorlib/terminator.py:105 msgid "tab" -msgstr "" +msgstr "табулация" #: ../terminatorlib/terminator.py:230 #, python-format @@ -302,7 +268,12 @@ msgstr "" #: ../terminatorlib/terminator.py:413 msgid "window" -msgstr "" +msgstr "прозорец" + +#. show dialog +#: ../terminatorlib/terminator.py:417 +msgid "Close?" +msgstr "Затваряне?" #: ../terminatorlib/terminator.py:423 msgid "Close _Terminals" @@ -310,7 +281,7 @@ msgstr "" #: ../terminatorlib/terminator.py:425 msgid "Close multiple terminals?" -msgstr "" +msgstr "Затваряне на всички терминали?" #: ../terminatorlib/terminator.py:428 #, python-format @@ -319,14 +290,22 @@ msgid "" "terminals within it." msgstr "" +#: ../terminatorlib/terminatorterm.py:39 +msgid "" +"You need to install python bindings for libvte (\"python-vte\" in " +"debian/ubuntu)" +msgstr "" +"Трябва да инсталирате python bind-ове за libvte (\"python-vte\" в " +"debian/ubuntu)" + #: ../terminatorlib/terminatorterm.py:133 msgid "Search:" -msgstr "" +msgstr "Търсене:" #. Button for the next result. Explicitly not show()n by default. #: ../terminatorlib/terminatorterm.py:150 msgid "Next" -msgstr "" +msgstr "Следващ" #. Give up, we're completely stuck #: ../terminatorlib/terminatorterm.py:518 @@ -335,15 +314,43 @@ msgstr "" #: ../terminatorlib/terminatorterm.py:539 msgid "Unable to start shell: " -msgstr "" +msgstr "Шел не може да бъде стартиран " + +#: ../terminatorlib/terminatorterm.py:1033 +msgid "_Open Link" +msgstr "_Отваряне на връзка" + +#: ../terminatorlib/terminatorterm.py:1034 +msgid "_Copy Link Address" +msgstr "_Копиране на адреса на връзката" + +#: ../terminatorlib/terminatorterm.py:1044 +msgid "_Send Mail To..." +msgstr "Из_пращане на е-поща до..." + +#: ../terminatorlib/terminatorterm.py:1045 +msgid "_Copy Email Address" +msgstr "Копиране на _адреса на е-пощата" + +#: ../terminatorlib/terminatorterm.py:1071 +msgid "Show _scrollbar" +msgstr "Покажи _скролбар" #: ../terminatorlib/terminatorterm.py:1076 msgid "Show _titlebar" msgstr "" +#: ../terminatorlib/terminatorterm.py:1089 +msgid "Split H_orizontally" +msgstr "Раздели х_оризонално" + +#: ../terminatorlib/terminatorterm.py:1090 +msgid "Split V_ertically" +msgstr "Раздели в_ертикално" + #: ../terminatorlib/terminatorterm.py:1107 msgid "Open _Tab" -msgstr "" +msgstr "Отвори _подпрозорец" #: ../terminatorlib/terminatorterm.py:1112 msgid "Open _Debug Tab" @@ -371,7 +378,7 @@ msgstr "" #: ../terminatorlib/terminatorterm.py:1148 msgid "Ed_it profile" -msgstr "" +msgstr "Ре_дактирай профила" #: ../terminatorlib/terminatorterm.py:1155 msgid "_Group" @@ -379,11 +386,11 @@ msgstr "" #: ../terminatorlib/terminatorterm.py:1179 msgid "None" -msgstr "" +msgstr "Нищо" #: ../terminatorlib/terminatorterm.py:1194 msgid "_New group" -msgstr "" +msgstr "_Нова група" #: ../terminatorlib/terminatorterm.py:1201 msgid "_Group all" @@ -399,11 +406,11 @@ msgstr "" #: ../terminatorlib/terminatorterm.py:1262 msgid "All" -msgstr "" +msgstr "Всичко" #: ../terminatorlib/terminatorterm.py:1282 msgid "Encodings" -msgstr "" +msgstr "Кодова таблица" #: ../terminatorlib/terminatorterm.py:1300 msgid "Other Encodings" @@ -415,4 +422,4 @@ msgstr "" #: ../data/terminator.desktop.in.h:2 msgid "Terminator" -msgstr "" +msgstr "Терминатор" diff --git a/po/ca.po b/po/ca.po index d9346222..46595b39 100644 --- a/po/ca.po +++ b/po/ca.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 diff --git a/po/cs.po b/po/cs.po index 07ccf559..2de75bd8 100644 --- a/po/cs.po +++ b/po/cs.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-05-22 00:25+0000\n" -"Last-Translator: Roman Horník \n" +"PO-Revision-Date: 2009-11-05 14:48+0000\n" +"Last-Translator: Kuvaly \n" "Language-Team: Czech \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -252,6 +252,11 @@ msgstr "Akce" msgid "Keyboard shortcut" msgstr "Klávesová zkratka" +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "Zavřít panel" @@ -265,6 +270,10 @@ msgstr "záložka" msgid "Invalid geometry string %r" msgstr "Nesprávný geometry string %r" +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "" + #: ../terminatorlib/terminator.py:413 msgid "window" msgstr "okno" @@ -274,6 +283,25 @@ msgstr "okno" msgid "Close?" msgstr "Uzavřít?" +#: ../terminatorlib/terminator.py:423 +msgid "Close _Terminals" +msgstr "Zavřít _terminály" + +#: ../terminatorlib/terminator.py:425 +msgid "Close multiple terminals?" +msgstr "" +"Copy text \t\r\n" +"Zavřít více terminálů?" + +#: ../terminatorlib/terminator.py:428 +#, python-format +msgid "" +"This %s has several terminals open. Closing the %s will also close all " +"terminals within it." +msgstr "" +"V %s je několik terminálů otevřených. Zavřením %s současně zavřete všechny " +"obsažené terminály." + #: ../terminatorlib/terminatorterm.py:39 msgid "" "You need to install python bindings for libvte (\"python-vte\" in " @@ -344,66 +372,6 @@ msgstr "Otevřít _Debug záložku" msgid "Open Top Level Tab" msgstr "Otevřít vrchní panel záložek" -#: ../terminatorlib/terminatorterm.py:1155 -msgid "_Group" -msgstr "_Seskupit" - -#: ../terminatorlib/terminatorterm.py:1179 -msgid "None" -msgstr "Žádný" - -#: ../terminatorlib/terminatorterm.py:1194 -msgid "_New group" -msgstr "_Nová skupina" - -#: ../terminatorlib/terminatorterm.py:1201 -msgid "_Group all" -msgstr "_Všechny skupiny" - -#: ../terminatorlib/terminatorterm.py:1216 -msgid "Group name:" -msgstr "Název skupiny:" - -#: ../terminatorlib/terminatorterm.py:1262 -msgid "All" -msgstr "Vše" - -#: ../terminatorlib/terminatorterm.py:1282 -msgid "Encodings" -msgstr "Kódování" - -#: ../terminatorlib/terminatorterm.py:1300 -msgid "Other Encodings" -msgstr "Jiné kódování" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - -#: ../terminatorlib/terminator.py:423 -msgid "Close _Terminals" -msgstr "Zavřít _terminály" - -#: ../terminatorlib/terminator.py:425 -msgid "Close multiple terminals?" -msgstr "" -"Copy text \t\r\n" -"Zavřít více terminálů?" - -#: ../terminatorlib/terminator.py:428 -#, python-format -msgid "" -"This %s has several terminals open. Closing the %s will also close all " -"terminals within it." -msgstr "" -"V %s je několik terminálů otevřených. Zavřením %s současně zavřete všechny " -"obsažené terminály." - #: ../terminatorlib/terminatorterm.py:1127 msgid "_Zoom terminal" msgstr "_Přiblížit terminál" @@ -424,9 +392,41 @@ msgstr "" msgid "Ed_it profile" msgstr "U_pravit profil" +#: ../terminatorlib/terminatorterm.py:1155 +msgid "_Group" +msgstr "_Seskupit" + +#: ../terminatorlib/terminatorterm.py:1179 +msgid "None" +msgstr "Žádný" + +#: ../terminatorlib/terminatorterm.py:1194 +msgid "_New group" +msgstr "_Nová skupina" + +#: ../terminatorlib/terminatorterm.py:1201 +msgid "_Group all" +msgstr "_Všechny skupiny" + #: ../terminatorlib/terminatorterm.py:1206 msgid "_Ungroup all" -msgstr "" +msgstr "_Odebrat ze skupin vše" + +#: ../terminatorlib/terminatorterm.py:1216 +msgid "Group name:" +msgstr "Název skupiny:" + +#: ../terminatorlib/terminatorterm.py:1262 +msgid "All" +msgstr "Vše" + +#: ../terminatorlib/terminatorterm.py:1282 +msgid "Encodings" +msgstr "Kódování" + +#: ../terminatorlib/terminatorterm.py:1300 +msgid "Other Encodings" +msgstr "Jiné kódování" #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" diff --git a/po/da.po b/po/da.po index 372ad6b4..56fbaf4f 100644 --- a/po/da.po +++ b/po/da.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -248,6 +248,11 @@ msgstr "Handling" msgid "Keyboard shortcut" msgstr "Tastaturgenvej" +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "Luk faneblad" @@ -261,6 +266,10 @@ msgstr "tab" msgid "Invalid geometry string %r" msgstr "ugyldig geometry tekststreng %r" +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "" + #: ../terminatorlib/terminator.py:413 msgid "window" msgstr "vindue" @@ -413,15 +422,6 @@ msgstr "Tegnsæt" msgid "Other Encodings" msgstr "Andre Tegnsæt" -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" msgstr "Flere terminaler i et vindue" diff --git a/po/de.po b/po/de.po index 92fba75d..0f731aac 100644 --- a/po/de.po +++ b/po/de.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-06-11 08:43+0000\n" -"Last-Translator: herbert \n" +"PO-Revision-Date: 2009-10-17 09:55+0000\n" +"Last-Translator: Moritz Baumann \n" "Language-Team: German \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -35,7 +35,7 @@ msgstr "" #: ../terminatorlib/configfile.py:96 msgid "Unterminated quoted string" -msgstr "unbegrenzte zitierte Zeichenkette" +msgstr "Das schließende Anführungszeichen fehlt" #: ../terminatorlib/configfile.py:147 msgid "Setting without a value" @@ -71,35 +71,35 @@ msgstr "Konfigurationsfehler" #: ../terminatorlib/config.py:311 #, python-format msgid "Setting %r value %r not a valid colour; ignoring" -msgstr "Wert %r setzen, %r ist keine erlaubte Farbe - wird ignoriert" +msgstr "Setze %r Wert, %r ist keine gültige Farbe; wird ignoriert" #: ../terminatorlib/config.py:316 #, python-format msgid "%s must be one of: top, left, right, bottom" -msgstr "%s erwartet eine dieser Variablen: top, left, right, bottom" +msgstr "%s zulässige Eingaben: top, left, right, bottom" #: ../terminatorlib/config.py:323 #, python-format msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" msgstr "" -"Booleansche Einstellung %s kann nur einen der folgenden Werte annehmen: yes, " +"Boolesche Einstellung %s kann nur einen der folgenden Werte annehmen: yes, " "no, true, false, on, off" #: ../terminatorlib/config.py:329 msgid "" "Reading list values from terminator_config(5) is not currently supported" msgstr "" -"Einlesen der Werte in der terminator_config(5)-Datei wird zur Zeit noch " -"nicht unterstützt." +"Das Auslesen von Listenwerten aus terminator_config(5) wird momentan nicht " +"unterstützt." #: ../terminatorlib/config.py:332 #, python-format msgid "Setting %r should be a section name" -msgstr "Einstellung %r sollte ein Abschnittsname sein" +msgstr "Die Einstellung %r sollte ein Abschnittsname sein." #: ../terminatorlib/encoding.py:29 msgid "Current Locale" -msgstr "Derzeitiges Gebietsschema" +msgstr "Aktuelles Gebietsschema" #: ../terminatorlib/encoding.py:30 ../terminatorlib/encoding.py:43 #: ../terminatorlib/encoding.py:62 ../terminatorlib/encoding.py:85 @@ -270,6 +270,10 @@ msgstr "Tab" msgid "Invalid geometry string %r" msgstr "Ungültige Geometriekette %r" +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "Konnte \"Fenster verstecken\"-Taste nicht festlegen" + #: ../terminatorlib/terminator.py:413 msgid "window" msgstr "Fenster" @@ -281,7 +285,7 @@ msgstr "Schließen?" #: ../terminatorlib/terminator.py:423 msgid "Close _Terminals" -msgstr "Schließe_Terminals" +msgstr "_Terminals schließen" #: ../terminatorlib/terminator.py:425 msgid "Close multiple terminals?" @@ -364,11 +368,11 @@ msgstr "Das Fehlersuch-Fenster öffnen" #: ../terminatorlib/terminatorterm.py:1118 msgid "Open Top Level Tab" -msgstr "" +msgstr "Obersten Reiter öffnen" #: ../terminatorlib/terminatorterm.py:1127 msgid "_Zoom terminal" -msgstr "" +msgstr "_Zoom Terminal" #: ../terminatorlib/terminatorterm.py:1131 msgid "Ma_ximise terminal" @@ -422,10 +426,6 @@ msgstr "Zeichensätze" msgid "Other Encodings" msgstr "Weitere Zeichensätze" -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" msgstr "Mehrfache Terminals in einem Fenster" diff --git a/po/el.po b/po/el.po index c7509a6f..14bdcbfc 100644 --- a/po/el.po +++ b/po/el.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-06-04 18:58+0000\n" -"Last-Translator: j0hn \n" +"PO-Revision-Date: 2009-09-02 19:32+0000\n" +"Last-Translator: Alex P. Natsios \n" "Language-Team: Greek \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -33,6 +33,18 @@ msgstr "" "Πρέπει να εκτελέσετε το terminator σε Χ περιβάλλον. Βεβαιωθείτε πως οι " "ρυθμίσεις απεικόνισης είναι σωστές." +#: ../terminatorlib/configfile.py:96 +msgid "Unterminated quoted string" +msgstr "" + +#: ../terminatorlib/configfile.py:147 +msgid "Setting without a value" +msgstr "Ρύθμιση χωρίς τιμή" + +#: ../terminatorlib/configfile.py:152 +msgid "Unexpected token" +msgstr "" + #: ../terminatorlib/config.py:250 #, python-format msgid "" @@ -57,6 +69,11 @@ msgstr "" msgid "Configuration error" msgstr "Σφάλμα ρυθμίσεων" +#: ../terminatorlib/config.py:311 +#, python-format +msgid "Setting %r value %r not a valid colour; ignoring" +msgstr "" + #: ../terminatorlib/config.py:316 #, python-format msgid "%s must be one of: top, left, right, bottom" @@ -69,6 +86,16 @@ msgstr "" "Η ρύθμιση %s του τελεστή αναμένει ένα από τα παρακάτω: ναι, όχι, αληθές, " "ψευδές, ανοιχτό, κλειστό" +#: ../terminatorlib/config.py:329 +msgid "" +"Reading list values from terminator_config(5) is not currently supported" +msgstr "" + +#: ../terminatorlib/config.py:332 +#, python-format +msgid "Setting %r should be a section name" +msgstr "Η ρύθμιση %r θα έπρεπε να είναι ένα όνομα τμήματος" + #: ../terminatorlib/encoding.py:29 msgid "Current Locale" msgstr "Τρέχουσα εντοπιότητα (locale)" @@ -193,6 +220,10 @@ msgstr "Περσικά" msgid "Gujarati" msgstr "Γκουτζαράτι" +#: ../terminatorlib/encoding.py:82 +msgid "Gurmukhi" +msgstr "Gurmukhi" + #: ../terminatorlib/encoding.py:84 msgid "Icelandic" msgstr "Ισλανδικά" @@ -206,15 +237,64 @@ msgstr "Βιετναμέζικα" msgid "Thai" msgstr "Ταϋλανδέζικα" +#: ../terminatorlib/prefs_profile.py:370 +msgid "Name" +msgstr "Όνομα" + +#: ../terminatorlib/prefs_profile.py:377 +msgid "Action" +msgstr "Ενέργεια" + +#: ../terminatorlib/prefs_profile.py:384 +msgid "Keyboard shortcut" +msgstr "Συντόμευση πληκτρολογίου" + +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "Κλείσιμο καρτέλας" +#: ../terminatorlib/terminator.py:105 +msgid "tab" +msgstr "στηλοθέτης" + +#: ../terminatorlib/terminator.py:230 +#, python-format +msgid "Invalid geometry string %r" +msgstr "" + +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "" + +#: ../terminatorlib/terminator.py:413 +msgid "window" +msgstr "παράθυρο" + #. show dialog #: ../terminatorlib/terminator.py:417 msgid "Close?" msgstr "Κλείσιμο;" +#: ../terminatorlib/terminator.py:423 +msgid "Close _Terminals" +msgstr "Κλείσιμο _Τερματικών" + +#: ../terminatorlib/terminator.py:425 +msgid "Close multiple terminals?" +msgstr "Κλείσιμο πολλαπλών τερματικών;" + +#: ../terminatorlib/terminator.py:428 +#, python-format +msgid "" +"This %s has several terminals open. Closing the %s will also close all " +"terminals within it." +msgstr "" + #: ../terminatorlib/terminatorterm.py:39 msgid "" "You need to install python bindings for libvte (\"python-vte\" in " @@ -223,6 +303,15 @@ msgstr "" "Πρέπει να εγκαταστήσετε τις ακόλουθες python συνδέσεις για το libvte " "(\"python-vte\" στο debian/ubuntu)" +#: ../terminatorlib/terminatorterm.py:133 +msgid "Search:" +msgstr "Αναζήτηση:" + +#. Button for the next result. Explicitly not show()n by default. +#: ../terminatorlib/terminatorterm.py:150 +msgid "Next" +msgstr "Επόμενο" + #. Give up, we're completely stuck #: ../terminatorlib/terminatorterm.py:518 msgid "Unable to find a shell" @@ -292,114 +381,17 @@ msgstr "_Απομάκρυνση τερματικού" msgid "Unma_ximise terminal" msgstr "Σμί_κρυνση τερματικού" -#: ../terminatorlib/terminatorterm.py:1282 -msgid "Encodings" -msgstr "Κωδικοποιήσεις" - -#: ../terminatorlib/terminatorterm.py:1300 -msgid "Other Encodings" -msgstr "Άλλες Κωδικοποιήσεις" - -#: ../terminatorlib/configfile.py:96 -msgid "Unterminated quoted string" -msgstr "" - -#: ../terminatorlib/configfile.py:147 -msgid "Setting without a value" -msgstr "Ρύθμιση χωρίς τιμή" - -#: ../terminatorlib/configfile.py:152 -msgid "Unexpected token" -msgstr "" - -#: ../terminatorlib/config.py:311 -#, python-format -msgid "Setting %r value %r not a valid colour; ignoring" -msgstr "" - -#: ../terminatorlib/config.py:329 -msgid "" -"Reading list values from terminator_config(5) is not currently supported" -msgstr "" - -#: ../terminatorlib/config.py:332 -#, python-format -msgid "Setting %r should be a section name" -msgstr "" - -#: ../terminatorlib/encoding.py:82 -msgid "Gurmukhi" -msgstr "" - -#: ../terminatorlib/prefs_profile.py:370 -msgid "Name" -msgstr "Όνομα" - -#: ../terminatorlib/prefs_profile.py:377 -msgid "Action" -msgstr "Ενέργεια" - -#: ../terminatorlib/prefs_profile.py:384 -msgid "Keyboard shortcut" -msgstr "Συντόμευση πληκτρολογίου" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:105 -msgid "tab" -msgstr "" - -#: ../terminatorlib/terminator.py:230 -#, python-format -msgid "Invalid geometry string %r" -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - -#: ../terminatorlib/terminator.py:413 -msgid "window" -msgstr "παράθυρο" - -#: ../terminatorlib/terminator.py:423 -msgid "Close _Terminals" -msgstr "Κλείσιμο _Τερματικών" - -#: ../terminatorlib/terminator.py:425 -msgid "Close multiple terminals?" -msgstr "Κλείσιμο πολλαπλών τερματικών;" - -#: ../terminatorlib/terminator.py:428 -#, python-format -msgid "" -"This %s has several terminals open. Closing the %s will also close all " -"terminals within it." -msgstr "" - -#: ../terminatorlib/terminatorterm.py:133 -msgid "Search:" -msgstr "Αναζήτηση:" - -#. Button for the next result. Explicitly not show()n by default. -#: ../terminatorlib/terminatorterm.py:150 -msgid "Next" -msgstr "Επόμενο" - #: ../terminatorlib/terminatorterm.py:1148 msgid "Ed_it profile" -msgstr "" +msgstr "Επεξεργασία Προφίλ" #: ../terminatorlib/terminatorterm.py:1155 msgid "_Group" -msgstr "" +msgstr "_Ομαδοποίηση" #: ../terminatorlib/terminatorterm.py:1179 msgid "None" -msgstr "" +msgstr "Κανένα" #: ../terminatorlib/terminatorterm.py:1194 msgid "_New group" @@ -421,10 +413,18 @@ msgstr "Όνομα ομάδας:" msgid "All" msgstr "Όλα" +#: ../terminatorlib/terminatorterm.py:1282 +msgid "Encodings" +msgstr "Κωδικοποιήσεις" + +#: ../terminatorlib/terminatorterm.py:1300 +msgid "Other Encodings" +msgstr "Άλλες Κωδικοποιήσεις" + #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" -msgstr "" +msgstr "Πολλαπλά τερματικά σε ένα παράθυρο" #: ../data/terminator.desktop.in.h:2 msgid "Terminator" -msgstr "" +msgstr "Terminator" diff --git a/po/en_CA.po b/po/en_CA.po index 7967c1ca..98126307 100644 --- a/po/en_CA.po +++ b/po/en_CA.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-03-13 05:26+0000\n" +"PO-Revision-Date: 2009-06-27 06:47+0000\n" "Last-Translator: Itai Molenaar \n" "Language-Team: English (Canada) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -247,6 +247,12 @@ msgstr "Action" msgid "Keyboard shortcut" msgstr "Keyboard shortcut" +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" +"Unable to find python bindings for deskbar, hide_window is not available." + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "Close Tab" @@ -260,6 +266,10 @@ msgstr "tab" msgid "Invalid geometry string %r" msgstr "Invalid geometry string %r" +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "Unable to bind hide_window key" + #: ../terminatorlib/terminator.py:413 msgid "window" msgstr "window" @@ -412,19 +422,10 @@ msgstr "Encodings" msgid "Other Encodings" msgstr "Other Encodings" -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" -msgstr "" +msgstr "Multiple terminals in one window" #: ../data/terminator.desktop.in.h:2 msgid "Terminator" -msgstr "" +msgstr "Terminator" diff --git a/po/en_GB.po b/po/en_GB.po index 5e24f5b7..889e7094 100644 --- a/po/en_GB.po +++ b/po/en_GB.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 diff --git a/po/eo.po b/po/eo.po index 8f03b58c..668b2c76 100644 --- a/po/eo.po +++ b/po/eo.po @@ -8,52 +8,15 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2008-07-01 03:05+0000\n" -"Last-Translator: Brian Croom \n" +"PO-Revision-Date: 2009-08-03 12:52+0000\n" +"Last-Translator: Kristjan \n" "Language-Team: Esperanto \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" -#. show dialog -#: ../terminatorlib/terminator.py:417 -msgid "Close?" -msgstr "Ĉu Fermi?" - -#: ../terminatorlib/terminatorterm.py:39 -msgid "" -"You need to install python bindings for libvte (\"python-vte\" in " -"debian/ubuntu)" -msgstr "" -"Necesas instalitaj python-aj bindaĵoj de libvte (\"python-vte\" en " -"debian/ubuntu)" - -#: ../terminatorlib/terminatorterm.py:1033 -msgid "_Open Link" -msgstr "_Malfermi Ligon" - -#: ../terminatorlib/terminatorterm.py:1034 -msgid "_Copy Link Address" -msgstr "_Kopii Ligiladreson" - -#: ../terminatorlib/terminatorterm.py:1044 -msgid "_Send Mail To..." -msgstr "_Sendi Retpoŝton al..." - -#: ../terminatorlib/terminatorterm.py:1045 -msgid "_Copy Email Address" -msgstr "_Kopii Retpoŝtadreson" - -#: ../terminatorlib/terminatorterm.py:1089 -msgid "Split H_orizontally" -msgstr "Dividi _Horizontale" - -#: ../terminatorlib/terminatorterm.py:1090 -msgid "Split V_ertically" -msgstr "Dividi _Vertikale" - #: ../terminator:46 msgid "" "You need to install the python bindings for gobject, gtk and pango to run " @@ -298,6 +261,11 @@ msgstr "" msgid "window" msgstr "" +#. show dialog +#: ../terminatorlib/terminator.py:417 +msgid "Close?" +msgstr "Ĉu Fermi?" + #: ../terminatorlib/terminator.py:423 msgid "Close _Terminals" msgstr "" @@ -313,6 +281,14 @@ msgid "" "terminals within it." msgstr "" +#: ../terminatorlib/terminatorterm.py:39 +msgid "" +"You need to install python bindings for libvte (\"python-vte\" in " +"debian/ubuntu)" +msgstr "" +"Necesas instalitaj python-aj bindaĵoj de libvte (\"python-vte\" en " +"debian/ubuntu)" + #: ../terminatorlib/terminatorterm.py:133 msgid "Search:" msgstr "" @@ -331,6 +307,22 @@ msgstr "" msgid "Unable to start shell: " msgstr "" +#: ../terminatorlib/terminatorterm.py:1033 +msgid "_Open Link" +msgstr "_Malfermi ligilon" + +#: ../terminatorlib/terminatorterm.py:1034 +msgid "_Copy Link Address" +msgstr "_Kopii ligiladreson" + +#: ../terminatorlib/terminatorterm.py:1044 +msgid "_Send Mail To..." +msgstr "_Sendi Retpoŝton al..." + +#: ../terminatorlib/terminatorterm.py:1045 +msgid "_Copy Email Address" +msgstr "_Kopii Retpoŝtadreson" + #: ../terminatorlib/terminatorterm.py:1071 msgid "Show _scrollbar" msgstr "" @@ -339,6 +331,14 @@ msgstr "" msgid "Show _titlebar" msgstr "" +#: ../terminatorlib/terminatorterm.py:1089 +msgid "Split H_orizontally" +msgstr "Dividi _Horizontale" + +#: ../terminatorlib/terminatorterm.py:1090 +msgid "Split V_ertically" +msgstr "Dividi _Vertikale" + #: ../terminatorlib/terminatorterm.py:1107 msgid "Open _Tab" msgstr "" diff --git a/po/es.po b/po/es.po index 2ea3ae4d..f5bb31c8 100644 --- a/po/es.po +++ b/po/es.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-06-21 16:13+0000\n" -"Last-Translator: DiegoJ \n" +"PO-Revision-Date: 2009-09-18 16:08+0000\n" +"Last-Translator: Paco Molinero \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -22,7 +22,7 @@ msgid "" "You need to install the python bindings for gobject, gtk and pango to run " "Terminator." msgstr "" -"Necesita instalar los vinculos de python para gobject.gtk y pango para " +"Necesita instalar los vínculos de python para gobject, gtk y pango para " "ejecutar Terminator" #: ../terminator:129 @@ -83,7 +83,7 @@ msgstr "" #, python-format msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" msgstr "" -"Valor de configuración %s boleano esperaba uno de los siguientes: yes, no, " +"Valor de configuración %s booleano esperaba uno de los siguientes: yes, no, " "true, false, on, off" #: ../terminatorlib/config.py:329 @@ -111,11 +111,11 @@ msgstr "Occidental" #: ../terminatorlib/encoding.py:31 ../terminatorlib/encoding.py:63 #: ../terminatorlib/encoding.py:75 ../terminatorlib/encoding.py:94 msgid "Central European" -msgstr "Europa central" +msgstr "Europeo central" #: ../terminatorlib/encoding.py:32 msgid "South European" -msgstr "Europa del sur" +msgstr "Europeo del sur" #: ../terminatorlib/encoding.py:33 ../terminatorlib/encoding.py:41 #: ../terminatorlib/encoding.py:101 @@ -273,7 +273,7 @@ msgstr "Cadena de geometría inválida %r" #: ../terminatorlib/terminator.py:277 msgid "Unable to bind hide_window key" -msgstr "No se puede asociar la clave hide_window" +msgstr "No se puede asociar la tecla de hide_window" #: ../terminatorlib/terminator.py:413 msgid "window" @@ -290,7 +290,7 @@ msgstr "Cerrar_Terminales" #: ../terminatorlib/terminator.py:425 msgid "Close multiple terminals?" -msgstr "¿Cerar Multiples Termnales?" +msgstr "¿Cerar Multiples Terminales?" #: ../terminatorlib/terminator.py:428 #, python-format @@ -306,7 +306,7 @@ msgid "" "You need to install python bindings for libvte (\"python-vte\" in " "debian/ubuntu)" msgstr "" -"Necesita instalar los «bindings» de Python para libvte (python-vte en " +"Necesita instalar los vínculos de Python para libvte (python-vte en " "Debian/Ubuntu)" #: ../terminatorlib/terminatorterm.py:133 @@ -373,7 +373,7 @@ msgstr "Abrir Pestana de Nivel Superior" #: ../terminatorlib/terminatorterm.py:1127 msgid "_Zoom terminal" -msgstr "Hacer acercamiento (_zoom) en terminal" +msgstr "Acercar en la terminal (_zoom)" #: ../terminatorlib/terminatorterm.py:1131 msgid "Ma_ximise terminal" @@ -381,7 +381,7 @@ msgstr "Ma_ximizar terminal" #: ../terminatorlib/terminatorterm.py:1136 msgid "_Unzoom terminal" -msgstr "Deshacer acercamiento en terminal (_zoom)" +msgstr "Alejar en la terminal (_unzoom)" #: ../terminatorlib/terminatorterm.py:1141 msgid "Unma_ximise terminal" @@ -393,7 +393,7 @@ msgstr "Ed_itar perfil" #: ../terminatorlib/terminatorterm.py:1155 msgid "_Group" -msgstr "_Agrupar" +msgstr "A_grupar" #: ../terminatorlib/terminatorterm.py:1179 msgid "None" diff --git a/po/et.po b/po/et.po index 6f4e45e5..b1fe7c78 100644 --- a/po/et.po +++ b/po/et.po @@ -9,43 +9,14 @@ msgstr "" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" "PO-Revision-Date: 2008-02-24 08:18+0000\n" -"Last-Translator: Jaan Janesmae \n" +"Last-Translator: Jaan Janesmae \n" "Language-Team: Estonian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" -#. show dialog -#: ../terminatorlib/terminator.py:417 -msgid "Close?" -msgstr "Sulge?" - -#: ../terminatorlib/terminatorterm.py:1033 -msgid "_Open Link" -msgstr "_Ava viit" - -#: ../terminatorlib/terminatorterm.py:1034 -msgid "_Copy Link Address" -msgstr "_Kopeeri viida aadress" - -#: ../terminatorlib/terminatorterm.py:1044 -msgid "_Send Mail To..." -msgstr "_Saada e-sõnum..." - -#: ../terminatorlib/terminatorterm.py:1045 -msgid "_Copy Email Address" -msgstr "_Kopeeri e-postiaadress" - -#: ../terminatorlib/terminatorterm.py:1089 -msgid "Split H_orizontally" -msgstr "Poolita H_orisontaalselt" - -#: ../terminatorlib/terminatorterm.py:1090 -msgid "Split V_ertically" -msgstr "Poolita V_ertikaalselt" - #: ../terminator:46 msgid "" "You need to install the python bindings for gobject, gtk and pango to run " @@ -290,6 +261,11 @@ msgstr "" msgid "window" msgstr "" +#. show dialog +#: ../terminatorlib/terminator.py:417 +msgid "Close?" +msgstr "Sulge?" + #: ../terminatorlib/terminator.py:423 msgid "Close _Terminals" msgstr "" @@ -329,6 +305,22 @@ msgstr "" msgid "Unable to start shell: " msgstr "" +#: ../terminatorlib/terminatorterm.py:1033 +msgid "_Open Link" +msgstr "_Ava viit" + +#: ../terminatorlib/terminatorterm.py:1034 +msgid "_Copy Link Address" +msgstr "_Kopeeri viida aadress" + +#: ../terminatorlib/terminatorterm.py:1044 +msgid "_Send Mail To..." +msgstr "_Saada e-sõnum..." + +#: ../terminatorlib/terminatorterm.py:1045 +msgid "_Copy Email Address" +msgstr "_Kopeeri e-postiaadress" + #: ../terminatorlib/terminatorterm.py:1071 msgid "Show _scrollbar" msgstr "" @@ -337,6 +329,14 @@ msgstr "" msgid "Show _titlebar" msgstr "" +#: ../terminatorlib/terminatorterm.py:1089 +msgid "Split H_orizontally" +msgstr "Poolita H_orisontaalselt" + +#: ../terminatorlib/terminatorterm.py:1090 +msgid "Split V_ertically" +msgstr "Poolita V_ertikaalselt" + #: ../terminatorlib/terminatorterm.py:1107 msgid "Open _Tab" msgstr "" diff --git a/po/eu.po b/po/eu.po index 77fde372..19f71da0 100644 --- a/po/eu.po +++ b/po/eu.po @@ -14,46 +14,9 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" -#. show dialog -#: ../terminatorlib/terminator.py:417 -msgid "Close?" -msgstr "Itxi?" - -#: ../terminatorlib/terminatorterm.py:39 -msgid "" -"You need to install python bindings for libvte (\"python-vte\" in " -"debian/ubuntu)" -msgstr "" -"libvte-rako python-en binding-ak (\"python-vte debian/ubuntu) instalatu " -"behar dituzu" - -#: ../terminatorlib/terminatorterm.py:1033 -msgid "_Open Link" -msgstr "_Ireki Lotura" - -#: ../terminatorlib/terminatorterm.py:1034 -msgid "_Copy Link Address" -msgstr "_Kopiatu Loturaren Helbidea" - -#: ../terminatorlib/terminatorterm.py:1044 -msgid "_Send Mail To..." -msgstr "_Bidali Posta Hona..." - -#: ../terminatorlib/terminatorterm.py:1045 -msgid "_Copy Email Address" -msgstr "_Kopiatu helbide elektronikoa" - -#: ../terminatorlib/terminatorterm.py:1089 -msgid "Split H_orizontally" -msgstr "Zatitu H_orizontalki" - -#: ../terminatorlib/terminatorterm.py:1090 -msgid "Split V_ertically" -msgstr "Zatitu V_ertikalki" - #: ../terminator:46 msgid "" "You need to install the python bindings for gobject, gtk and pango to run " @@ -298,6 +261,11 @@ msgstr "" msgid "window" msgstr "" +#. show dialog +#: ../terminatorlib/terminator.py:417 +msgid "Close?" +msgstr "Itxi?" + #: ../terminatorlib/terminator.py:423 msgid "Close _Terminals" msgstr "" @@ -313,6 +281,14 @@ msgid "" "terminals within it." msgstr "" +#: ../terminatorlib/terminatorterm.py:39 +msgid "" +"You need to install python bindings for libvte (\"python-vte\" in " +"debian/ubuntu)" +msgstr "" +"libvte-rako python-en binding-ak (\"python-vte debian/ubuntu) instalatu " +"behar dituzu" + #: ../terminatorlib/terminatorterm.py:133 msgid "Search:" msgstr "" @@ -331,6 +307,22 @@ msgstr "" msgid "Unable to start shell: " msgstr "" +#: ../terminatorlib/terminatorterm.py:1033 +msgid "_Open Link" +msgstr "_Ireki Lotura" + +#: ../terminatorlib/terminatorterm.py:1034 +msgid "_Copy Link Address" +msgstr "_Kopiatu Loturaren Helbidea" + +#: ../terminatorlib/terminatorterm.py:1044 +msgid "_Send Mail To..." +msgstr "_Bidali Posta Hona..." + +#: ../terminatorlib/terminatorterm.py:1045 +msgid "_Copy Email Address" +msgstr "_Kopiatu helbide elektronikoa" + #: ../terminatorlib/terminatorterm.py:1071 msgid "Show _scrollbar" msgstr "" @@ -339,6 +331,14 @@ msgstr "" msgid "Show _titlebar" msgstr "" +#: ../terminatorlib/terminatorterm.py:1089 +msgid "Split H_orizontally" +msgstr "Zatitu H_orizontalki" + +#: ../terminatorlib/terminatorterm.py:1090 +msgid "Split V_ertically" +msgstr "Zatitu V_ertikalki" + #: ../terminatorlib/terminatorterm.py:1107 msgid "Open _Tab" msgstr "" diff --git a/po/fa.po b/po/fa.po index 686b37a4..487c2f18 100644 --- a/po/fa.po +++ b/po/fa.po @@ -8,36 +8,15 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2008-03-14 09:32+0000\n" -"Last-Translator: Ali Sattari \n" +"PO-Revision-Date: 2009-08-30 20:00+0000\n" +"Last-Translator: ataeyan \n" "Language-Team: Persian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" -#. show dialog -#: ../terminatorlib/terminator.py:417 -msgid "Close?" -msgstr "بستن؟" - -#: ../terminatorlib/terminatorterm.py:1033 -msgid "_Open Link" -msgstr "_باز کردن پیوند" - -#: ../terminatorlib/terminatorterm.py:1034 -msgid "_Copy Link Address" -msgstr "_نسخه‌برداری از نشانی پیوند" - -#: ../terminatorlib/terminatorterm.py:1044 -msgid "_Send Mail To..." -msgstr "_ارسال پست الکترونیک به..." - -#: ../terminatorlib/terminatorterm.py:1045 -msgid "_Copy Email Address" -msgstr "_نسخه‌برداری از نشانی پست الکترونیکی" - #: ../terminator:46 msgid "" "You need to install the python bindings for gobject, gtk and pango to run " @@ -56,7 +35,7 @@ msgstr "" #: ../terminatorlib/configfile.py:147 msgid "Setting without a value" -msgstr "" +msgstr "تنظیم بدون یک مقدار" #: ../terminatorlib/configfile.py:152 msgid "Unexpected token" @@ -76,7 +55,7 @@ msgstr "" #: ../terminatorlib/config.py:258 msgid "Configuration error" -msgstr "" +msgstr "خطای پیکربندی" #: ../terminatorlib/config.py:311 #, python-format @@ -86,7 +65,7 @@ msgstr "" #: ../terminatorlib/config.py:316 #, python-format msgid "%s must be one of: top, left, right, bottom" -msgstr "" +msgstr "%s باید یکی از: بالا، چپ، راست، پایین باشد." #: ../terminatorlib/config.py:323 #, python-format @@ -282,6 +261,11 @@ msgstr "" msgid "window" msgstr "" +#. show dialog +#: ../terminatorlib/terminator.py:417 +msgid "Close?" +msgstr "بستن؟" + #: ../terminatorlib/terminator.py:423 msgid "Close _Terminals" msgstr "" @@ -321,6 +305,22 @@ msgstr "" msgid "Unable to start shell: " msgstr "" +#: ../terminatorlib/terminatorterm.py:1033 +msgid "_Open Link" +msgstr "_باز کردن پیوند" + +#: ../terminatorlib/terminatorterm.py:1034 +msgid "_Copy Link Address" +msgstr "_نسخه‌برداری از نشانی پیوند" + +#: ../terminatorlib/terminatorterm.py:1044 +msgid "_Send Mail To..." +msgstr "_ارسال پست الکترونیک به..." + +#: ../terminatorlib/terminatorterm.py:1045 +msgid "_Copy Email Address" +msgstr "_نسخه‌برداری از نشانی پست الکترونیکی" + #: ../terminatorlib/terminatorterm.py:1071 msgid "Show _scrollbar" msgstr "" diff --git a/po/fi.po b/po/fi.po index 3e0adcdd..2d9c1b18 100644 --- a/po/fi.po +++ b/po/fi.po @@ -16,7 +16,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 diff --git a/po/fr.po b/po/fr.po index 2629760d..f18a01e6 100644 --- a/po/fr.po +++ b/po/fr.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-04-25 20:14+0000\n" -"Last-Translator: gregseth \n" +"PO-Revision-Date: 2009-07-22 07:29+0000\n" +"Last-Translator: dommy9111 \n" "Language-Team: French \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -249,6 +249,13 @@ msgstr "Action" msgid "Keyboard shortcut" msgstr "Raccourci clavier" +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" +"La librairie python de deskbar est introuvable, hide_window n'est pas " +"disponible." + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "Fermer l'onglet" @@ -262,6 +269,10 @@ msgstr "tab" msgid "Invalid geometry string %r" msgstr "%r n'est pas une valeur valide pour le paramètre geometry" +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "Impossible d'associer la touche hide_window" + #: ../terminatorlib/terminator.py:413 msgid "window" msgstr "fenêtre" @@ -421,12 +432,3 @@ msgstr "Plusieurs terminaux dans une fenêtre" #: ../data/terminator.desktop.in.h:2 msgid "Terminator" msgstr "Terminator" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" diff --git a/po/fy.po b/po/fy.po index cf599a49..fe1df437 100644 --- a/po/fy.po +++ b/po/fy.po @@ -9,51 +9,14 @@ msgstr "" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" "PO-Revision-Date: 2008-04-02 09:28+0000\n" -"Last-Translator: Wander Nauta \n" +"Last-Translator: Wander Nauta \n" "Language-Team: Frisian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" -#. show dialog -#: ../terminatorlib/terminator.py:417 -msgid "Close?" -msgstr "Slûte?" - -#: ../terminatorlib/terminatorterm.py:39 -msgid "" -"You need to install python bindings for libvte (\"python-vte\" in " -"debian/ubuntu)" -msgstr "" -"Jo moatte de python-byningen foar libvte (\"python-vte\" yn Debian/Ubuntu) " -"installeare." - -#: ../terminatorlib/terminatorterm.py:1033 -msgid "_Open Link" -msgstr "Keppe_ling iepenje" - -#: ../terminatorlib/terminatorterm.py:1034 -msgid "_Copy Link Address" -msgstr "Keppelingsadres _kopierje" - -#: ../terminatorlib/terminatorterm.py:1044 -msgid "_Send Mail To..." -msgstr "E-mail _stjoere nei..." - -#: ../terminatorlib/terminatorterm.py:1045 -msgid "_Copy Email Address" -msgstr "E-mailadres kopiearje" - -#: ../terminatorlib/terminatorterm.py:1089 -msgid "Split H_orizontally" -msgstr "H_orizontaal splitse" - -#: ../terminatorlib/terminatorterm.py:1090 -msgid "Split V_ertically" -msgstr "V_ertikaal splitse" - #: ../terminator:46 msgid "" "You need to install the python bindings for gobject, gtk and pango to run " @@ -298,6 +261,11 @@ msgstr "" msgid "window" msgstr "" +#. show dialog +#: ../terminatorlib/terminator.py:417 +msgid "Close?" +msgstr "Slûte?" + #: ../terminatorlib/terminator.py:423 msgid "Close _Terminals" msgstr "" @@ -313,6 +281,14 @@ msgid "" "terminals within it." msgstr "" +#: ../terminatorlib/terminatorterm.py:39 +msgid "" +"You need to install python bindings for libvte (\"python-vte\" in " +"debian/ubuntu)" +msgstr "" +"Jo moatte de python-byningen foar libvte (\"python-vte\" yn Debian/Ubuntu) " +"installeare." + #: ../terminatorlib/terminatorterm.py:133 msgid "Search:" msgstr "" @@ -331,6 +307,22 @@ msgstr "" msgid "Unable to start shell: " msgstr "" +#: ../terminatorlib/terminatorterm.py:1033 +msgid "_Open Link" +msgstr "Keppe_ling iepenje" + +#: ../terminatorlib/terminatorterm.py:1034 +msgid "_Copy Link Address" +msgstr "Keppelingsadres _kopierje" + +#: ../terminatorlib/terminatorterm.py:1044 +msgid "_Send Mail To..." +msgstr "E-mail _stjoere nei..." + +#: ../terminatorlib/terminatorterm.py:1045 +msgid "_Copy Email Address" +msgstr "E-mailadres kopiearje" + #: ../terminatorlib/terminatorterm.py:1071 msgid "Show _scrollbar" msgstr "" @@ -339,6 +331,14 @@ msgstr "" msgid "Show _titlebar" msgstr "" +#: ../terminatorlib/terminatorterm.py:1089 +msgid "Split H_orizontally" +msgstr "H_orizontaal splitse" + +#: ../terminatorlib/terminatorterm.py:1090 +msgid "Split V_ertically" +msgstr "V_ertikaal splitse" + #: ../terminatorlib/terminatorterm.py:1107 msgid "Open _Tab" msgstr "" diff --git a/po/ga.po b/po/ga.po index 9a07d605..0df09bec 100644 --- a/po/ga.po +++ b/po/ga.po @@ -14,30 +14,9 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" -#. show dialog -#: ../terminatorlib/terminator.py:417 -msgid "Close?" -msgstr "Dún?" - -#: ../terminatorlib/terminatorterm.py:1033 -msgid "_Open Link" -msgstr "_Oscail Nasc" - -#: ../terminatorlib/terminatorterm.py:1034 -msgid "_Copy Link Address" -msgstr "_Macasamhlaigh Seoladh an Naisc" - -#: ../terminatorlib/terminatorterm.py:1044 -msgid "_Send Mail To..." -msgstr "_Seol Post Chuig..." - -#: ../terminatorlib/terminatorterm.py:1045 -msgid "_Copy Email Address" -msgstr "_Macasamhlaigh an Seoladh Ríomhphost" - #: ../terminator:46 msgid "" "You need to install the python bindings for gobject, gtk and pango to run " @@ -282,6 +261,11 @@ msgstr "" msgid "window" msgstr "" +#. show dialog +#: ../terminatorlib/terminator.py:417 +msgid "Close?" +msgstr "Dún?" + #: ../terminatorlib/terminator.py:423 msgid "Close _Terminals" msgstr "" @@ -321,6 +305,22 @@ msgstr "" msgid "Unable to start shell: " msgstr "" +#: ../terminatorlib/terminatorterm.py:1033 +msgid "_Open Link" +msgstr "_Oscail Nasc" + +#: ../terminatorlib/terminatorterm.py:1034 +msgid "_Copy Link Address" +msgstr "_Macasamhlaigh Seoladh an Naisc" + +#: ../terminatorlib/terminatorterm.py:1044 +msgid "_Send Mail To..." +msgstr "_Seol Post Chuig..." + +#: ../terminatorlib/terminatorterm.py:1045 +msgid "_Copy Email Address" +msgstr "_Macasamhlaigh an Seoladh Ríomhphost" + #: ../terminatorlib/terminatorterm.py:1071 msgid "Show _scrollbar" msgstr "" diff --git a/po/gl.po b/po/gl.po index 480f213f..e3d56f82 100644 --- a/po/gl.po +++ b/po/gl.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 diff --git a/po/he.po b/po/he.po index 035c3649..8fcdba94 100644 --- a/po/he.po +++ b/po/he.po @@ -8,71 +8,28 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2008-10-20 08:20+0000\n" -"Last-Translator: FD_F \n" +"PO-Revision-Date: 2009-10-05 10:11+0000\n" +"Last-Translator: Yaron \n" "Language-Team: Hebrew \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" -#. show dialog -#: ../terminatorlib/terminator.py:417 -msgid "Close?" -msgstr "לסגור?" - -#: ../terminatorlib/terminatorterm.py:39 -msgid "" -"You need to install python bindings for libvte (\"python-vte\" in " -"debian/ubuntu)" -msgstr "" -"הינך צריך להתקין מקשר לפייטון עבור libvte (\"python-vte\" ב- debian/ubuntu)" - -#. Give up, we're completely stuck -#: ../terminatorlib/terminatorterm.py:518 -msgid "Unable to find a shell" -msgstr "לא ניתן למצוא מעטפת" - -#: ../terminatorlib/terminatorterm.py:539 -msgid "Unable to start shell: " -msgstr "לא ניתן להתחיל מעטפת " - -#: ../terminatorlib/terminatorterm.py:1033 -msgid "_Open Link" -msgstr "_פתח קישור" - -#: ../terminatorlib/terminatorterm.py:1034 -msgid "_Copy Link Address" -msgstr "_העתק את כתובת הקישור" - -#: ../terminatorlib/terminatorterm.py:1044 -msgid "_Send Mail To..." -msgstr "שלח _דוא\"ל אל..." - -#: ../terminatorlib/terminatorterm.py:1045 -msgid "_Copy Email Address" -msgstr "_העתק כתובת דוא\"ל" - -#: ../terminatorlib/terminatorterm.py:1089 -msgid "Split H_orizontally" -msgstr "פצל א_ופקית" - -#: ../terminatorlib/terminatorterm.py:1090 -msgid "Split V_ertically" -msgstr "פצל א_נכית" - #: ../terminator:46 msgid "" "You need to install the python bindings for gobject, gtk and pango to run " "Terminator." msgstr "" +"יש להתקין את מאגדי ה־python עבור gobject, gtk ו־pango כדי להפעיל את " +"Terminator." #: ../terminator:129 msgid "" "You need to run terminator in an X environment. Make sure DISPLAY is " "properly set" -msgstr "" +msgstr "יש להריץ את Terminator בסביבת X. יש לוודא ש־DISPLAY מוגדר כראוי" #: ../terminatorlib/configfile.py:96 msgid "Unterminated quoted string" @@ -80,11 +37,11 @@ msgstr "" #: ../terminatorlib/configfile.py:147 msgid "Setting without a value" -msgstr "" +msgstr "הגדרה ללא ערך" #: ../terminatorlib/configfile.py:152 msgid "Unexpected token" -msgstr "" +msgstr "אסימון בלתי צפוי" #: ../terminatorlib/config.py:250 #, python-format @@ -97,10 +54,17 @@ msgid "" "\n" "%d line(s) have been ignored." msgstr "" +"שגיאת תצורה\n" +"\n" +"ארעו שגיאות בעת נתיחת הקובץ terminator_config(5):\n" +"\n" +" %s\n" +"\n" +"%d שורה/ות לא נכללו." #: ../terminatorlib/config.py:258 msgid "Configuration error" -msgstr "" +msgstr "שגיאת תצורה" #: ../terminatorlib/config.py:311 #, python-format @@ -110,7 +74,7 @@ msgstr "" #: ../terminatorlib/config.py:316 #, python-format msgid "%s must be one of: top, left, right, bottom" -msgstr "" +msgstr "על %s להיות אחד מהבאים: למעלה, שמאל, ימין, למטה" #: ../terminatorlib/config.py:323 #, python-format @@ -306,6 +270,11 @@ msgstr "" msgid "window" msgstr "" +#. show dialog +#: ../terminatorlib/terminator.py:417 +msgid "Close?" +msgstr "לסגור?" + #: ../terminatorlib/terminator.py:423 msgid "Close _Terminals" msgstr "" @@ -321,6 +290,13 @@ msgid "" "terminals within it." msgstr "" +#: ../terminatorlib/terminatorterm.py:39 +msgid "" +"You need to install python bindings for libvte (\"python-vte\" in " +"debian/ubuntu)" +msgstr "" +"הינך צריך להתקין מקשר לפייטון עבור libvte (\"python-vte\" ב- debian/ubuntu)" + #: ../terminatorlib/terminatorterm.py:133 msgid "Search:" msgstr "" @@ -330,6 +306,31 @@ msgstr "" msgid "Next" msgstr "" +#. Give up, we're completely stuck +#: ../terminatorlib/terminatorterm.py:518 +msgid "Unable to find a shell" +msgstr "לא ניתן למצוא מעטפת" + +#: ../terminatorlib/terminatorterm.py:539 +msgid "Unable to start shell: " +msgstr "לא ניתן להתחיל מעטפת " + +#: ../terminatorlib/terminatorterm.py:1033 +msgid "_Open Link" +msgstr "_פתח קישור" + +#: ../terminatorlib/terminatorterm.py:1034 +msgid "_Copy Link Address" +msgstr "_העתק את כתובת הקישור" + +#: ../terminatorlib/terminatorterm.py:1044 +msgid "_Send Mail To..." +msgstr "שלח _דוא\"ל אל..." + +#: ../terminatorlib/terminatorterm.py:1045 +msgid "_Copy Email Address" +msgstr "_העתק כתובת דוא\"ל" + #: ../terminatorlib/terminatorterm.py:1071 msgid "Show _scrollbar" msgstr "" @@ -338,6 +339,14 @@ msgstr "" msgid "Show _titlebar" msgstr "" +#: ../terminatorlib/terminatorterm.py:1089 +msgid "Split H_orizontally" +msgstr "פצל א_ופקית" + +#: ../terminatorlib/terminatorterm.py:1090 +msgid "Split V_ertically" +msgstr "פצל א_נכית" + #: ../terminatorlib/terminatorterm.py:1107 msgid "Open _Tab" msgstr "" diff --git a/po/hi.po b/po/hi.po index 3ca6c9d8..eb8e6b3a 100644 --- a/po/hi.po +++ b/po/hi.po @@ -14,16 +14,9 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" -#: ../terminatorlib/terminatorterm.py:39 -msgid "" -"You need to install python bindings for libvte (\"python-vte\" in " -"debian/ubuntu)" -msgstr "" -"आप को libvte (डेबियन/उबुन्टू में \"python-vte\" ) इन्स्टाल करने की अरूरत है." - #: ../terminator:46 msgid "" "You need to install the python bindings for gobject, gtk and pango to run " @@ -288,6 +281,13 @@ msgid "" "terminals within it." msgstr "" +#: ../terminatorlib/terminatorterm.py:39 +msgid "" +"You need to install python bindings for libvte (\"python-vte\" in " +"debian/ubuntu)" +msgstr "" +"आप को libvte (डेबियन/उबुन्टू में \"python-vte\" ) इन्स्टाल करने की अरूरत है." + #: ../terminatorlib/terminatorterm.py:133 msgid "Search:" msgstr "" diff --git a/po/hu.po b/po/hu.po index 0140b813..2d49aed7 100644 --- a/po/hu.po +++ b/po/hu.po @@ -8,15 +8,27 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-05-27 12:37+0000\n" -"Last-Translator: Oli \n" +"PO-Revision-Date: 2009-09-24 08:14+0000\n" +"Last-Translator: Muszela Balázs \n" "Language-Team: Hungarian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" +#: ../terminator:46 +msgid "" +"You need to install the python bindings for gobject, gtk and pango to run " +"Terminator." +msgstr "" + +#: ../terminator:129 +msgid "" +"You need to run terminator in an X environment. Make sure DISPLAY is " +"properly set" +msgstr "" + #: ../terminatorlib/configfile.py:96 msgid "Unterminated quoted string" msgstr "Befejezetlen idézőjeles string" @@ -51,11 +63,31 @@ msgstr "" msgid "Configuration error" msgstr "Beállítási hiba" +#: ../terminatorlib/config.py:311 +#, python-format +msgid "Setting %r value %r not a valid colour; ignoring" +msgstr "Beállított %r érték %r nem valós szín; átugorva" + #: ../terminatorlib/config.py:316 #, python-format msgid "%s must be one of: top, left, right, bottom" msgstr "%s a következők egyike kell legyen: top, left, right, bottom" +#: ../terminatorlib/config.py:323 +#, python-format +msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" +msgstr "" + +#: ../terminatorlib/config.py:329 +msgid "" +"Reading list values from terminator_config(5) is not currently supported" +msgstr "" + +#: ../terminatorlib/config.py:332 +#, python-format +msgid "Setting %r should be a section name" +msgstr "" + #: ../terminatorlib/encoding.py:29 msgid "Current Locale" msgstr "Jelenlegi területi beállítás" @@ -209,6 +241,11 @@ msgstr "Művelet" msgid "Keyboard shortcut" msgstr "Gyorsbillentyű" +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "Lap bezárása" @@ -217,6 +254,15 @@ msgstr "Lap bezárása" msgid "tab" msgstr "lap" +#: ../terminatorlib/terminator.py:230 +#, python-format +msgid "Invalid geometry string %r" +msgstr "" + +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "" + #: ../terminatorlib/terminator.py:413 msgid "window" msgstr "ablak" @@ -230,6 +276,17 @@ msgstr "Bezárás?" msgid "Close _Terminals" msgstr "_Terminálok bezárása" +#: ../terminatorlib/terminator.py:425 +msgid "Close multiple terminals?" +msgstr "" + +#: ../terminatorlib/terminator.py:428 +#, python-format +msgid "" +"This %s has several terminals open. Closing the %s will also close all " +"terminals within it." +msgstr "" + #: ../terminatorlib/terminatorterm.py:39 msgid "" "You need to install python bindings for libvte (\"python-vte\" in " @@ -276,6 +333,10 @@ msgstr "E-mail cím _másolása" msgid "Show _scrollbar" msgstr "Gördítő_sáv mutatása" +#: ../terminatorlib/terminatorterm.py:1076 +msgid "Show _titlebar" +msgstr "" + #: ../terminatorlib/terminatorterm.py:1089 msgid "Split H_orizontally" msgstr "Elválasztás _vízszintesen" @@ -288,67 +349,6 @@ msgstr "Elválasztás _függőlegesen" msgid "Open _Tab" msgstr "_Lap megnyitása" -#: ../terminator:46 -msgid "" -"You need to install the python bindings for gobject, gtk and pango to run " -"Terminator." -msgstr "" - -#: ../terminator:129 -msgid "" -"You need to run terminator in an X environment. Make sure DISPLAY is " -"properly set" -msgstr "" - -#: ../terminatorlib/config.py:311 -#, python-format -msgid "Setting %r value %r not a valid colour; ignoring" -msgstr "Beállított %r érték %r nem valós szín; átugorva" - -#: ../terminatorlib/config.py:323 -#, python-format -msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" -msgstr "" - -#: ../terminatorlib/config.py:329 -msgid "" -"Reading list values from terminator_config(5) is not currently supported" -msgstr "" - -#: ../terminatorlib/config.py:332 -#, python-format -msgid "Setting %r should be a section name" -msgstr "" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:230 -#, python-format -msgid "Invalid geometry string %r" -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - -#: ../terminatorlib/terminator.py:425 -msgid "Close multiple terminals?" -msgstr "" - -#: ../terminatorlib/terminator.py:428 -#, python-format -msgid "" -"This %s has several terminals open. Closing the %s will also close all " -"terminals within it." -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1076 -msgid "Show _titlebar" -msgstr "" - #: ../terminatorlib/terminatorterm.py:1112 msgid "Open _Debug Tab" msgstr "" @@ -379,15 +379,15 @@ msgstr "" #: ../terminatorlib/terminatorterm.py:1155 msgid "_Group" -msgstr "" +msgstr "_Csoport" #: ../terminatorlib/terminatorterm.py:1179 msgid "None" -msgstr "" +msgstr "Nincs" #: ../terminatorlib/terminatorterm.py:1194 msgid "_New group" -msgstr "" +msgstr "_Új csoport" #: ../terminatorlib/terminatorterm.py:1201 msgid "_Group all" @@ -399,23 +399,23 @@ msgstr "" #: ../terminatorlib/terminatorterm.py:1216 msgid "Group name:" -msgstr "" +msgstr "Csoportnév:" #: ../terminatorlib/terminatorterm.py:1262 msgid "All" -msgstr "" +msgstr "Összes" #: ../terminatorlib/terminatorterm.py:1282 msgid "Encodings" -msgstr "" +msgstr "Kódolások" #: ../terminatorlib/terminatorterm.py:1300 msgid "Other Encodings" -msgstr "" +msgstr "Egyéb Kódolások" #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" -msgstr "" +msgstr "Több terminál egy ablakban" #: ../data/terminator.desktop.in.h:2 msgid "Terminator" diff --git a/po/id.po b/po/id.po index f6fa553a..74b211fa 100644 --- a/po/id.po +++ b/po/id.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 diff --git a/po/it.po b/po/it.po index 34be34b8..632f824f 100644 --- a/po/it.po +++ b/po/it.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-04-22 11:47+0000\n" +"PO-Revision-Date: 2009-11-28 13:47+0000\n" "Last-Translator: Milo Casagrande \n" "Language-Team: Romanian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -35,7 +35,7 @@ msgstr "" #: ../terminatorlib/configfile.py:96 msgid "Unterminated quoted string" -msgstr "Stringa tra virgolette non chiuse" +msgstr "Stringa tra virgolette non terminata" #: ../terminatorlib/configfile.py:147 msgid "Setting without a value" @@ -112,7 +112,7 @@ msgstr "Europa centrale" #: ../terminatorlib/encoding.py:32 msgid "South European" -msgstr "Europa del Sud" +msgstr "Sud europeo" #: ../terminatorlib/encoding.py:33 ../terminatorlib/encoding.py:41 #: ../terminatorlib/encoding.py:101 @@ -174,7 +174,7 @@ msgstr "Armeno" #: ../terminatorlib/encoding.py:51 ../terminatorlib/encoding.py:52 #: ../terminatorlib/encoding.py:56 msgid "Chinese Traditional" -msgstr "Cinese tradizionale" +msgstr "Cinese Tradizionale" #: ../terminatorlib/encoding.py:53 msgid "Cyrillic/Russian" @@ -246,7 +246,7 @@ msgstr "Azione" #: ../terminatorlib/prefs_profile.py:384 msgid "Keyboard shortcut" -msgstr "Scorciatoia" +msgstr "Scorciatoia da tastiera" #: ../terminatorlib/terminator.py:37 msgid "" @@ -287,7 +287,7 @@ msgstr "Chiudi _terminali" #: ../terminatorlib/terminator.py:425 msgid "Close multiple terminals?" -msgstr "Chiudere più terminali?" +msgstr "Chiudere i terminali?" #: ../terminatorlib/terminator.py:428 #, python-format @@ -330,15 +330,15 @@ msgstr "_Apri collegamento" #: ../terminatorlib/terminatorterm.py:1034 msgid "_Copy Link Address" -msgstr "Copia indirizzo co_llegamento" +msgstr "_Copia indirizzo collegamento" #: ../terminatorlib/terminatorterm.py:1044 msgid "_Send Mail To..." -msgstr "In_via email a..." +msgstr "_Invia email a..." #: ../terminatorlib/terminatorterm.py:1045 msgid "_Copy Email Address" -msgstr "Co_pia indirizzo email" +msgstr "_Copia indirizzo email" #: ../terminatorlib/terminatorterm.py:1071 msgid "Show _scrollbar" @@ -350,7 +350,7 @@ msgstr "Mostra barra del _titolo" #: ../terminatorlib/terminatorterm.py:1089 msgid "Split H_orizontally" -msgstr "Dividi _orizzontalmente" +msgstr "Dividi o_rizzontalmente" #: ../terminatorlib/terminatorterm.py:1090 msgid "Split V_ertically" @@ -366,15 +366,15 @@ msgstr "Apri scheda _debug" #: ../terminatorlib/terminatorterm.py:1118 msgid "Open Top Level Tab" -msgstr "Apre scheda livello superiore" +msgstr "Apri scheda superiore" #: ../terminatorlib/terminatorterm.py:1127 msgid "_Zoom terminal" -msgstr "I_ngrandisci terminale" +msgstr "_Ingrandisci terminale" #: ../terminatorlib/terminatorterm.py:1131 msgid "Ma_ximise terminal" -msgstr "_Massimizza terminale" +msgstr "Ma_ssimizza terminale" #: ../terminatorlib/terminatorterm.py:1136 msgid "_Unzoom terminal" @@ -382,11 +382,11 @@ msgstr "_Rimpicciolisci terminale" #: ../terminatorlib/terminatorterm.py:1141 msgid "Unma_ximise terminal" -msgstr "_Demassimizza terminale" +msgstr "Dema_ssimizza terminale" #: ../terminatorlib/terminatorterm.py:1148 msgid "Ed_it profile" -msgstr "_Modifica profilo" +msgstr "Modi_fica profilo" #: ../terminatorlib/terminatorterm.py:1155 msgid "_Group" @@ -402,7 +402,7 @@ msgstr "_Nuovo gruppo" #: ../terminatorlib/terminatorterm.py:1201 msgid "_Group all" -msgstr "Ra_ggruppa tutto" +msgstr "_Raggruppa tutto" #: ../terminatorlib/terminatorterm.py:1206 msgid "_Ungroup all" @@ -410,7 +410,7 @@ msgstr "_Rilascia tutto" #: ../terminatorlib/terminatorterm.py:1216 msgid "Group name:" -msgstr "Nome gruppo:" +msgstr "Nome del gruppo:" #: ../terminatorlib/terminatorterm.py:1262 msgid "All" diff --git a/po/ja.po b/po/ja.po index 96957dcc..0e8e8617 100644 --- a/po/ja.po +++ b/po/ja.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -34,15 +34,55 @@ msgstr "TerminatorはX環境で実行して下さい。DISPLAYに適切な値が msgid "Unterminated quoted string" msgstr "文字列の引用符が閉じていません" +#: ../terminatorlib/configfile.py:147 +msgid "Setting without a value" +msgstr "" + +#: ../terminatorlib/configfile.py:152 +msgid "Unexpected token" +msgstr "" + +#: ../terminatorlib/config.py:250 +#, python-format +msgid "" +"Configuration error\n" +"\n" +"Errors were encountered while parsing terminator_config(5) file:\n" +"\n" +" %s\n" +"\n" +"%d line(s) have been ignored." +msgstr "" + #: ../terminatorlib/config.py:258 msgid "Configuration error" msgstr "設定エラー" +#: ../terminatorlib/config.py:311 +#, python-format +msgid "Setting %r value %r not a valid colour; ignoring" +msgstr "" + +#: ../terminatorlib/config.py:316 +#, python-format +msgid "%s must be one of: top, left, right, bottom" +msgstr "" + #: ../terminatorlib/config.py:323 #, python-format msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" msgstr "ブール型の設定 %s は次の中からのみ指定できます: yes, no, true, false, on, off" +#: ../terminatorlib/config.py:329 +msgid "" +"Reading list values from terminator_config(5) is not currently supported" +msgstr "" + +#: ../terminatorlib/config.py:332 +#, python-format +msgid "Setting %r should be a section name" +msgstr "" + #: ../terminatorlib/encoding.py:29 msgid "Current Locale" msgstr "現在利用しているロケール" @@ -184,21 +224,79 @@ msgstr "ベトナム語" msgid "Thai" msgstr "タイ語" +#: ../terminatorlib/prefs_profile.py:370 +msgid "Name" +msgstr "" + +#: ../terminatorlib/prefs_profile.py:377 +msgid "Action" +msgstr "" + +#: ../terminatorlib/prefs_profile.py:384 +msgid "Keyboard shortcut" +msgstr "" + +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "タブを閉じる" +#: ../terminatorlib/terminator.py:105 +msgid "tab" +msgstr "" + +#: ../terminatorlib/terminator.py:230 +#, python-format +msgid "Invalid geometry string %r" +msgstr "" + +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "" + +#: ../terminatorlib/terminator.py:413 +msgid "window" +msgstr "" + #. show dialog #: ../terminatorlib/terminator.py:417 msgid "Close?" msgstr "閉じますか?" +#: ../terminatorlib/terminator.py:423 +msgid "Close _Terminals" +msgstr "" + +#: ../terminatorlib/terminator.py:425 +msgid "Close multiple terminals?" +msgstr "" + +#: ../terminatorlib/terminator.py:428 +#, python-format +msgid "" +"This %s has several terminals open. Closing the %s will also close all " +"terminals within it." +msgstr "" + #: ../terminatorlib/terminatorterm.py:39 msgid "" "You need to install python bindings for libvte (\"python-vte\" in " "debian/ubuntu)" msgstr "libvteをバインドするpythonのインストールが必要です (debian/ubuntuでは\"python-vte\" )" +#: ../terminatorlib/terminatorterm.py:133 +msgid "Search:" +msgstr "" + +#. Button for the next result. Explicitly not show()n by default. +#: ../terminatorlib/terminatorterm.py:150 +msgid "Next" +msgstr "" + #. Give up, we're completely stuck #: ../terminatorlib/terminatorterm.py:518 msgid "Unable to find a shell" @@ -244,6 +342,10 @@ msgstr "垂直で分割(_E)" msgid "Open _Tab" msgstr "タブを開く(_T)" +#: ../terminatorlib/terminatorterm.py:1112 +msgid "Open _Debug Tab" +msgstr "" + #: ../terminatorlib/terminatorterm.py:1118 msgid "Open Top Level Tab" msgstr "最上位タブを開く" @@ -292,6 +394,10 @@ msgstr "全てグループ化解除(_U)" msgid "Group name:" msgstr "グループの名前:" +#: ../terminatorlib/terminatorterm.py:1262 +msgid "All" +msgstr "" + #: ../terminatorlib/terminatorterm.py:1282 msgid "Encodings" msgstr "エンコーディング" @@ -300,112 +406,6 @@ msgstr "エンコーディング" msgid "Other Encodings" msgstr "その他のエンコーディング" -#: ../terminatorlib/configfile.py:147 -msgid "Setting without a value" -msgstr "" - -#: ../terminatorlib/configfile.py:152 -msgid "Unexpected token" -msgstr "" - -#: ../terminatorlib/config.py:250 -#, python-format -msgid "" -"Configuration error\n" -"\n" -"Errors were encountered while parsing terminator_config(5) file:\n" -"\n" -" %s\n" -"\n" -"%d line(s) have been ignored." -msgstr "" - -#: ../terminatorlib/config.py:311 -#, python-format -msgid "Setting %r value %r not a valid colour; ignoring" -msgstr "" - -#: ../terminatorlib/config.py:316 -#, python-format -msgid "%s must be one of: top, left, right, bottom" -msgstr "" - -#: ../terminatorlib/config.py:329 -msgid "" -"Reading list values from terminator_config(5) is not currently supported" -msgstr "" - -#: ../terminatorlib/config.py:332 -#, python-format -msgid "Setting %r should be a section name" -msgstr "" - -#: ../terminatorlib/prefs_profile.py:370 -msgid "Name" -msgstr "" - -#: ../terminatorlib/prefs_profile.py:377 -msgid "Action" -msgstr "" - -#: ../terminatorlib/prefs_profile.py:384 -msgid "Keyboard shortcut" -msgstr "" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:105 -msgid "tab" -msgstr "" - -#: ../terminatorlib/terminator.py:230 -#, python-format -msgid "Invalid geometry string %r" -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - -#: ../terminatorlib/terminator.py:413 -msgid "window" -msgstr "" - -#: ../terminatorlib/terminator.py:423 -msgid "Close _Terminals" -msgstr "" - -#: ../terminatorlib/terminator.py:425 -msgid "Close multiple terminals?" -msgstr "" - -#: ../terminatorlib/terminator.py:428 -#, python-format -msgid "" -"This %s has several terminals open. Closing the %s will also close all " -"terminals within it." -msgstr "" - -#: ../terminatorlib/terminatorterm.py:133 -msgid "Search:" -msgstr "" - -#. Button for the next result. Explicitly not show()n by default. -#: ../terminatorlib/terminatorterm.py:150 -msgid "Next" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1112 -msgid "Open _Debug Tab" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1262 -msgid "All" -msgstr "" - #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" msgstr "" diff --git a/po/jv.po b/po/jv.po index 3f691a60..b9879b31 100644 --- a/po/jv.po +++ b/po/jv.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 diff --git a/po/ko.po b/po/ko.po index 58841e1f..e1afad46 100644 --- a/po/ko.po +++ b/po/ko.po @@ -8,26 +8,26 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-01-29 12:22+0000\n" +"PO-Revision-Date: 2009-07-01 11:11+0000\n" "Last-Translator: wariua \n" "Language-Team: Korean \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 msgid "" "You need to install the python bindings for gobject, gtk and pango to run " "Terminator." -msgstr "Terminator를 실행하기 위해선 gobject, gtk, pango의 파이썬 바인딩을 설치해야 합니다." +msgstr "터미네이터를 실행하기 위해선 gobject, gtk, pango의 파이썬 바인딩을 설치해야 합니다." #: ../terminator:129 msgid "" "You need to run terminator in an X environment. Make sure DISPLAY is " "properly set" -msgstr "Terminator는 X 환경에서 실행해야 합니다. DISPLAY 변수가 올바로 설정돼 있는지 확인해 주십시오." +msgstr "터미네이터는 X 환경에서 실행해야 합니다. DISPLAY 변수가 올바로 설정돼 있는지 확인해 주십시오." #: ../terminatorlib/configfile.py:96 msgid "Unterminated quoted string" @@ -242,6 +242,11 @@ msgstr "동작" msgid "Keyboard shortcut" msgstr "키보드 바로 가기" +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "deskbar 파이썬 바인딩을 찾을 수 없습니다. hide_window를 사용할 수 없습니다." + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "탭 닫기" @@ -255,6 +260,10 @@ msgstr "탭" msgid "Invalid geometry string %r" msgstr "잘못된 위치 문자열 %r" +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "hide_window 키를 연결할 수 없음" + #: ../terminatorlib/terminator.py:413 msgid "window" msgstr "창" @@ -403,19 +412,10 @@ msgstr "인코딩" msgid "Other Encodings" msgstr "기타 인코딩" -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" -msgstr "" +msgstr "창 하나에 터미널 여러 개 쓰기" #: ../data/terminator.desktop.in.h:2 msgid "Terminator" -msgstr "" +msgstr "터미네이터" diff --git a/po/lt.po b/po/lt.po index 7fa8095b..639c742e 100644 --- a/po/lt.po +++ b/po/lt.po @@ -14,9 +14,74 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" +#: ../terminator:46 +msgid "" +"You need to install the python bindings for gobject, gtk and pango to run " +"Terminator." +msgstr "" + +#: ../terminator:129 +msgid "" +"You need to run terminator in an X environment. Make sure DISPLAY is " +"properly set" +msgstr "" + +#: ../terminatorlib/configfile.py:96 +msgid "Unterminated quoted string" +msgstr "" + +#: ../terminatorlib/configfile.py:147 +msgid "Setting without a value" +msgstr "" + +#: ../terminatorlib/configfile.py:152 +msgid "Unexpected token" +msgstr "" + +#: ../terminatorlib/config.py:250 +#, python-format +msgid "" +"Configuration error\n" +"\n" +"Errors were encountered while parsing terminator_config(5) file:\n" +"\n" +" %s\n" +"\n" +"%d line(s) have been ignored." +msgstr "" + +#: ../terminatorlib/config.py:258 +msgid "Configuration error" +msgstr "" + +#: ../terminatorlib/config.py:311 +#, python-format +msgid "Setting %r value %r not a valid colour; ignoring" +msgstr "" + +#: ../terminatorlib/config.py:316 +#, python-format +msgid "%s must be one of: top, left, right, bottom" +msgstr "" + +#: ../terminatorlib/config.py:323 +#, python-format +msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" +msgstr "" + +#: ../terminatorlib/config.py:329 +msgid "" +"Reading list values from terminator_config(5) is not currently supported" +msgstr "" + +#: ../terminatorlib/config.py:332 +#, python-format +msgid "Setting %r should be a section name" +msgstr "" + #: ../terminatorlib/encoding.py:29 msgid "Current Locale" msgstr "Dabartinė lokalė" @@ -117,6 +182,10 @@ msgstr "Korėjiečių" msgid "Chinese Simplified" msgstr "Supaprastinta kiniečių" +#: ../terminatorlib/encoding.py:60 +msgid "Georgian" +msgstr "" + #: ../terminatorlib/encoding.py:73 ../terminatorlib/encoding.py:88 msgid "Cyrillic/Ukrainian" msgstr "Kirilica/Ukrainiečių" @@ -154,114 +223,6 @@ msgstr "Vietnamiečių" msgid "Thai" msgstr "Tajų" -#: ../terminatorlib/terminator.py:76 -msgid "Close Tab" -msgstr "Užverti kortelę" - -#. show dialog -#: ../terminatorlib/terminator.py:417 -msgid "Close?" -msgstr "Uždaryti?" - -#: ../terminatorlib/terminatorterm.py:39 -msgid "" -"You need to install python bindings for libvte (\"python-vte\" in " -"debian/ubuntu)" -msgstr "Turite įdiegti Python modulį libvte (\"python-vte\" debian/ubuntu)" - -#: ../terminatorlib/terminatorterm.py:1033 -msgid "_Open Link" -msgstr "_Atverti nuorodą" - -#: ../terminatorlib/terminatorterm.py:1034 -msgid "_Copy Link Address" -msgstr "_Kopijuoti nuorodos adresą" - -#: ../terminatorlib/terminatorterm.py:1044 -msgid "_Send Mail To..." -msgstr "_Siųsti laišką į..." - -#: ../terminatorlib/terminatorterm.py:1045 -msgid "_Copy Email Address" -msgstr "_Kopijuoti el. pašto adresą" - -#: ../terminatorlib/terminatorterm.py:1089 -msgid "Split H_orizontally" -msgstr "Padalinti H_orizontaliai" - -#: ../terminatorlib/terminatorterm.py:1090 -msgid "Split V_ertically" -msgstr "Padalinti V_ertikaliai" - -#: ../terminator:46 -msgid "" -"You need to install the python bindings for gobject, gtk and pango to run " -"Terminator." -msgstr "" - -#: ../terminator:129 -msgid "" -"You need to run terminator in an X environment. Make sure DISPLAY is " -"properly set" -msgstr "" - -#: ../terminatorlib/configfile.py:96 -msgid "Unterminated quoted string" -msgstr "" - -#: ../terminatorlib/configfile.py:147 -msgid "Setting without a value" -msgstr "" - -#: ../terminatorlib/configfile.py:152 -msgid "Unexpected token" -msgstr "" - -#: ../terminatorlib/config.py:250 -#, python-format -msgid "" -"Configuration error\n" -"\n" -"Errors were encountered while parsing terminator_config(5) file:\n" -"\n" -" %s\n" -"\n" -"%d line(s) have been ignored." -msgstr "" - -#: ../terminatorlib/config.py:258 -msgid "Configuration error" -msgstr "" - -#: ../terminatorlib/config.py:311 -#, python-format -msgid "Setting %r value %r not a valid colour; ignoring" -msgstr "" - -#: ../terminatorlib/config.py:316 -#, python-format -msgid "%s must be one of: top, left, right, bottom" -msgstr "" - -#: ../terminatorlib/config.py:323 -#, python-format -msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" -msgstr "" - -#: ../terminatorlib/config.py:329 -msgid "" -"Reading list values from terminator_config(5) is not currently supported" -msgstr "" - -#: ../terminatorlib/config.py:332 -#, python-format -msgid "Setting %r should be a section name" -msgstr "" - -#: ../terminatorlib/encoding.py:60 -msgid "Georgian" -msgstr "" - #: ../terminatorlib/prefs_profile.py:370 msgid "Name" msgstr "" @@ -279,6 +240,10 @@ msgid "" "Unable to find python bindings for deskbar, hide_window is not available." msgstr "" +#: ../terminatorlib/terminator.py:76 +msgid "Close Tab" +msgstr "Užverti kortelę" + #: ../terminatorlib/terminator.py:105 msgid "tab" msgstr "" @@ -296,6 +261,11 @@ msgstr "" msgid "window" msgstr "" +#. show dialog +#: ../terminatorlib/terminator.py:417 +msgid "Close?" +msgstr "Uždaryti?" + #: ../terminatorlib/terminator.py:423 msgid "Close _Terminals" msgstr "" @@ -311,6 +281,12 @@ msgid "" "terminals within it." msgstr "" +#: ../terminatorlib/terminatorterm.py:39 +msgid "" +"You need to install python bindings for libvte (\"python-vte\" in " +"debian/ubuntu)" +msgstr "Turite įdiegti Python modulį libvte (\"python-vte\" debian/ubuntu)" + #: ../terminatorlib/terminatorterm.py:133 msgid "Search:" msgstr "" @@ -329,6 +305,22 @@ msgstr "" msgid "Unable to start shell: " msgstr "" +#: ../terminatorlib/terminatorterm.py:1033 +msgid "_Open Link" +msgstr "_Atverti nuorodą" + +#: ../terminatorlib/terminatorterm.py:1034 +msgid "_Copy Link Address" +msgstr "_Kopijuoti nuorodos adresą" + +#: ../terminatorlib/terminatorterm.py:1044 +msgid "_Send Mail To..." +msgstr "_Siųsti laišką į..." + +#: ../terminatorlib/terminatorterm.py:1045 +msgid "_Copy Email Address" +msgstr "_Kopijuoti el. pašto adresą" + #: ../terminatorlib/terminatorterm.py:1071 msgid "Show _scrollbar" msgstr "" @@ -337,6 +329,14 @@ msgstr "" msgid "Show _titlebar" msgstr "" +#: ../terminatorlib/terminatorterm.py:1089 +msgid "Split H_orizontally" +msgstr "Padalinti H_orizontaliai" + +#: ../terminatorlib/terminatorterm.py:1090 +msgid "Split V_ertically" +msgstr "Padalinti V_ertikaliai" + #: ../terminatorlib/terminatorterm.py:1107 msgid "Open _Tab" msgstr "" diff --git a/po/lv.po b/po/lv.po index 7496fc75..4ea23aa5 100644 --- a/po/lv.po +++ b/po/lv.po @@ -14,13 +14,74 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" +#: ../terminator:46 +msgid "" +"You need to install the python bindings for gobject, gtk and pango to run " +"Terminator." +msgstr "" + +#: ../terminator:129 +msgid "" +"You need to run terminator in an X environment. Make sure DISPLAY is " +"properly set" +msgstr "" + +#: ../terminatorlib/configfile.py:96 +msgid "Unterminated quoted string" +msgstr "" + +#: ../terminatorlib/configfile.py:147 +msgid "Setting without a value" +msgstr "" + +#: ../terminatorlib/configfile.py:152 +msgid "Unexpected token" +msgstr "" + +#: ../terminatorlib/config.py:250 +#, python-format +msgid "" +"Configuration error\n" +"\n" +"Errors were encountered while parsing terminator_config(5) file:\n" +"\n" +" %s\n" +"\n" +"%d line(s) have been ignored." +msgstr "" + #: ../terminatorlib/config.py:258 msgid "Configuration error" msgstr "Konfigurācijas kļūda" +#: ../terminatorlib/config.py:311 +#, python-format +msgid "Setting %r value %r not a valid colour; ignoring" +msgstr "" + +#: ../terminatorlib/config.py:316 +#, python-format +msgid "%s must be one of: top, left, right, bottom" +msgstr "" + +#: ../terminatorlib/config.py:323 +#, python-format +msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" +msgstr "" + +#: ../terminatorlib/config.py:329 +msgid "" +"Reading list values from terminator_config(5) is not currently supported" +msgstr "" + +#: ../terminatorlib/config.py:332 +#, python-format +msgid "Setting %r should be a section name" +msgstr "" + #: ../terminatorlib/encoding.py:29 msgid "Current Locale" msgstr "Pašreizējā lokāle" @@ -61,6 +122,10 @@ msgstr "Arābu" msgid "Greek" msgstr "Grieķu" +#: ../terminatorlib/encoding.py:37 +msgid "Hebrew Visual" +msgstr "" + #: ../terminatorlib/encoding.py:38 ../terminatorlib/encoding.py:66 #: ../terminatorlib/encoding.py:83 ../terminatorlib/encoding.py:99 msgid "Hebrew" @@ -166,6 +231,15 @@ msgstr "Nosaukums" msgid "Action" msgstr "Darbība" +#: ../terminatorlib/prefs_profile.py:384 +msgid "Keyboard shortcut" +msgstr "" + +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "Aizvērt Cilni" @@ -174,6 +248,15 @@ msgstr "Aizvērt Cilni" msgid "tab" msgstr "tab" +#: ../terminatorlib/terminator.py:230 +#, python-format +msgid "Invalid geometry string %r" +msgstr "" + +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "" + #: ../terminatorlib/terminator.py:413 msgid "window" msgstr "logs" @@ -187,126 +270,6 @@ msgstr "Aizvērt?" msgid "Close _Terminals" msgstr "Aizvērt Termināli" -#: ../terminatorlib/terminatorterm.py:133 -msgid "Search:" -msgstr "Meklēt:" - -#. Button for the next result. Explicitly not show()n by default. -#: ../terminatorlib/terminatorterm.py:150 -msgid "Next" -msgstr "Tālāk" - -#: ../terminatorlib/terminatorterm.py:1033 -msgid "_Open Link" -msgstr "_Atvērt saiti" - -#: ../terminatorlib/terminatorterm.py:1034 -msgid "_Copy Link Address" -msgstr "_Kopēt saites adresi" - -#: ../terminatorlib/terminatorterm.py:1044 -msgid "_Send Mail To..." -msgstr "_Sūtīt e-pastu..." - -#: ../terminatorlib/terminatorterm.py:1045 -msgid "_Copy Email Address" -msgstr "_Kopēt e-pasta adresi" - -#: ../terminatorlib/terminatorterm.py:1107 -msgid "Open _Tab" -msgstr "Atvērt _Cilni" - -#: ../terminatorlib/terminatorterm.py:1282 -msgid "Encodings" -msgstr "Kodējumi" - -#: ../terminatorlib/terminatorterm.py:1300 -msgid "Other Encodings" -msgstr "Citi kodējumi" - -#: ../terminator:46 -msgid "" -"You need to install the python bindings for gobject, gtk and pango to run " -"Terminator." -msgstr "" - -#: ../terminator:129 -msgid "" -"You need to run terminator in an X environment. Make sure DISPLAY is " -"properly set" -msgstr "" - -#: ../terminatorlib/configfile.py:96 -msgid "Unterminated quoted string" -msgstr "" - -#: ../terminatorlib/configfile.py:147 -msgid "Setting without a value" -msgstr "" - -#: ../terminatorlib/configfile.py:152 -msgid "Unexpected token" -msgstr "" - -#: ../terminatorlib/config.py:250 -#, python-format -msgid "" -"Configuration error\n" -"\n" -"Errors were encountered while parsing terminator_config(5) file:\n" -"\n" -" %s\n" -"\n" -"%d line(s) have been ignored." -msgstr "" - -#: ../terminatorlib/config.py:311 -#, python-format -msgid "Setting %r value %r not a valid colour; ignoring" -msgstr "" - -#: ../terminatorlib/config.py:316 -#, python-format -msgid "%s must be one of: top, left, right, bottom" -msgstr "" - -#: ../terminatorlib/config.py:323 -#, python-format -msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" -msgstr "" - -#: ../terminatorlib/config.py:329 -msgid "" -"Reading list values from terminator_config(5) is not currently supported" -msgstr "" - -#: ../terminatorlib/config.py:332 -#, python-format -msgid "Setting %r should be a section name" -msgstr "" - -#: ../terminatorlib/encoding.py:37 -msgid "Hebrew Visual" -msgstr "" - -#: ../terminatorlib/prefs_profile.py:384 -msgid "Keyboard shortcut" -msgstr "" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:230 -#, python-format -msgid "Invalid geometry string %r" -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - #: ../terminatorlib/terminator.py:425 msgid "Close multiple terminals?" msgstr "" @@ -324,6 +287,15 @@ msgid "" "debian/ubuntu)" msgstr "" +#: ../terminatorlib/terminatorterm.py:133 +msgid "Search:" +msgstr "Meklēt:" + +#. Button for the next result. Explicitly not show()n by default. +#: ../terminatorlib/terminatorterm.py:150 +msgid "Next" +msgstr "Tālāk" + #. Give up, we're completely stuck #: ../terminatorlib/terminatorterm.py:518 msgid "Unable to find a shell" @@ -333,6 +305,22 @@ msgstr "" msgid "Unable to start shell: " msgstr "" +#: ../terminatorlib/terminatorterm.py:1033 +msgid "_Open Link" +msgstr "_Atvērt saiti" + +#: ../terminatorlib/terminatorterm.py:1034 +msgid "_Copy Link Address" +msgstr "_Kopēt saites adresi" + +#: ../terminatorlib/terminatorterm.py:1044 +msgid "_Send Mail To..." +msgstr "_Sūtīt e-pastu..." + +#: ../terminatorlib/terminatorterm.py:1045 +msgid "_Copy Email Address" +msgstr "_Kopēt e-pasta adresi" + #: ../terminatorlib/terminatorterm.py:1071 msgid "Show _scrollbar" msgstr "" @@ -349,6 +337,10 @@ msgstr "" msgid "Split V_ertically" msgstr "" +#: ../terminatorlib/terminatorterm.py:1107 +msgid "Open _Tab" +msgstr "Atvērt _Cilni" + #: ../terminatorlib/terminatorterm.py:1112 msgid "Open _Debug Tab" msgstr "" @@ -405,6 +397,14 @@ msgstr "" msgid "All" msgstr "" +#: ../terminatorlib/terminatorterm.py:1282 +msgid "Encodings" +msgstr "Kodējumi" + +#: ../terminatorlib/terminatorterm.py:1300 +msgid "Other Encodings" +msgstr "Citi kodējumi" + #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" msgstr "" diff --git a/po/mk.po b/po/mk.po index cabb5e01..3536d820 100644 --- a/po/mk.po +++ b/po/mk.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-04-09 09:21+0000\n" +"PO-Revision-Date: 2009-06-26 11:31+0000\n" "Last-Translator: Мартин Спасовски \n" "Language-Team: Macedonian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -30,12 +30,12 @@ msgid "" "You need to run terminator in an X environment. Make sure DISPLAY is " "properly set" msgstr "" -"Мора да го извршите Терминатор во X околина. Осигурајте се дека ДИСПЛЕЈ е " -"правилно конфигуриран." +"You need to run terminator in an X environment. Make sure DISPLAY is " +"properly set" #: ../terminatorlib/configfile.py:96 msgid "Unterminated quoted string" -msgstr "Непрекинат цитиран стринг" +msgstr "Unterminated quoted string" #: ../terminatorlib/configfile.py:147 msgid "Setting without a value" @@ -66,7 +66,12 @@ msgstr "" #: ../terminatorlib/config.py:258 msgid "Configuration error" -msgstr "Конфигурациска грешка" +msgstr "Грешка во конфигурацијата" + +#: ../terminatorlib/config.py:311 +#, python-format +msgid "Setting %r value %r not a valid colour; ignoring" +msgstr "" #: ../terminatorlib/config.py:316 #, python-format @@ -87,71 +92,76 @@ msgstr "" "Читање на листа на вредности од terminator_config(5) не е поддржано во " "моментот" +#: ../terminatorlib/config.py:332 +#, python-format +msgid "Setting %r should be a section name" +msgstr "" + #: ../terminatorlib/encoding.py:29 msgid "Current Locale" -msgstr "Тековен локалитет" +msgstr "Тековен локал" #: ../terminatorlib/encoding.py:30 ../terminatorlib/encoding.py:43 #: ../terminatorlib/encoding.py:62 ../terminatorlib/encoding.py:85 #: ../terminatorlib/encoding.py:96 msgid "Western" -msgstr "Западео" +msgstr "Западен" #: ../terminatorlib/encoding.py:31 ../terminatorlib/encoding.py:63 #: ../terminatorlib/encoding.py:75 ../terminatorlib/encoding.py:94 msgid "Central European" -msgstr "Централно европско" +msgstr "Централно Европски" #: ../terminatorlib/encoding.py:32 msgid "South European" -msgstr "Јужно европско" +msgstr "Јужно Европски" #: ../terminatorlib/encoding.py:33 ../terminatorlib/encoding.py:41 #: ../terminatorlib/encoding.py:101 msgid "Baltic" -msgstr "Балтичко" +msgstr "Балтички" #: ../terminatorlib/encoding.py:34 ../terminatorlib/encoding.py:64 #: ../terminatorlib/encoding.py:70 ../terminatorlib/encoding.py:72 #: ../terminatorlib/encoding.py:77 ../terminatorlib/encoding.py:95 msgid "Cyrillic" -msgstr "Кирилично" +msgstr "Кирилица" #: ../terminatorlib/encoding.py:35 ../terminatorlib/encoding.py:67 #: ../terminatorlib/encoding.py:74 ../terminatorlib/encoding.py:100 msgid "Arabic" -msgstr "Арапско" +msgstr "Арапски" #: ../terminatorlib/encoding.py:36 ../terminatorlib/encoding.py:80 #: ../terminatorlib/encoding.py:97 msgid "Greek" -msgstr "Грчко" +msgstr "Грчки" #: ../terminatorlib/encoding.py:37 msgid "Hebrew Visual" -msgstr "Еврејско визуелно" +msgstr "Хебрејски Визуелен" #: ../terminatorlib/encoding.py:38 ../terminatorlib/encoding.py:66 #: ../terminatorlib/encoding.py:83 ../terminatorlib/encoding.py:99 msgid "Hebrew" -msgstr "Еврејско" +msgstr "Хебрејски" #: ../terminatorlib/encoding.py:39 ../terminatorlib/encoding.py:65 #: ../terminatorlib/encoding.py:87 ../terminatorlib/encoding.py:98 msgid "Turkish" -msgstr "Турско" +msgstr "Турски" #: ../terminatorlib/encoding.py:40 msgid "Nordic" -msgstr "Нордиско" +msgstr "Нордиски" #: ../terminatorlib/encoding.py:42 msgid "Celtic" -msgstr "Келтско" +msgstr "Келтски" #: ../terminatorlib/encoding.py:44 ../terminatorlib/encoding.py:86 msgid "Romanian" -msgstr "Романско" +msgstr "Романски" #: ../terminatorlib/encoding.py:45 ../terminatorlib/encoding.py:46 #: ../terminatorlib/encoding.py:47 ../terminatorlib/encoding.py:48 @@ -161,68 +171,68 @@ msgstr "Уникод" #: ../terminatorlib/encoding.py:50 msgid "Armenian" -msgstr "Ерменско" +msgstr "Ерменски" #: ../terminatorlib/encoding.py:51 ../terminatorlib/encoding.py:52 #: ../terminatorlib/encoding.py:56 msgid "Chinese Traditional" -msgstr "Кинеско традиционално" +msgstr "Традиционален Кинески" #: ../terminatorlib/encoding.py:53 msgid "Cyrillic/Russian" -msgstr "Кирилично/руско" +msgstr "Кирилица/Руски" #: ../terminatorlib/encoding.py:54 ../terminatorlib/encoding.py:68 #: ../terminatorlib/encoding.py:89 msgid "Japanese" -msgstr "Јапонско" +msgstr "Јапонски" #: ../terminatorlib/encoding.py:55 ../terminatorlib/encoding.py:69 #: ../terminatorlib/encoding.py:71 ../terminatorlib/encoding.py:92 msgid "Korean" -msgstr "Корејско" +msgstr "Корејски" #: ../terminatorlib/encoding.py:57 ../terminatorlib/encoding.py:58 #: ../terminatorlib/encoding.py:59 ../terminatorlib/encoding.py:61 msgid "Chinese Simplified" -msgstr "Кинеско поедноставено" +msgstr "Упростен Кинески" #: ../terminatorlib/encoding.py:60 msgid "Georgian" -msgstr "Грузиско" +msgstr "Грузијски" #: ../terminatorlib/encoding.py:73 ../terminatorlib/encoding.py:88 msgid "Cyrillic/Ukrainian" -msgstr "Кирилично/украинско" +msgstr "Кирилица/Украински" #: ../terminatorlib/encoding.py:76 msgid "Croatian" -msgstr "Хрватско" +msgstr "Хрватски" #: ../terminatorlib/encoding.py:78 msgid "Hindi" -msgstr "Хинди" +msgstr "Индиски" #: ../terminatorlib/encoding.py:79 msgid "Persian" -msgstr "Персиско" +msgstr "Персиски" #: ../terminatorlib/encoding.py:81 msgid "Gujarati" -msgstr "Гуџаратско" +msgstr "Гуџарати" #: ../terminatorlib/encoding.py:82 msgid "Gurmukhi" -msgstr "Гурмки" +msgstr "Гурмукхи" #: ../terminatorlib/encoding.py:84 msgid "Icelandic" -msgstr "Исландско" +msgstr "Исландски" #: ../terminatorlib/encoding.py:90 ../terminatorlib/encoding.py:93 #: ../terminatorlib/encoding.py:102 msgid "Vietnamese" -msgstr "Виетнамско" +msgstr "Виетнамски" #: ../terminatorlib/encoding.py:91 msgid "Thai" @@ -240,13 +250,27 @@ msgstr "Дејство" msgid "Keyboard shortcut" msgstr "Кратенка за тастатура" +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "Затвори јазиче" #: ../terminatorlib/terminator.py:105 msgid "tab" -msgstr "tab" +msgstr "табулатор" + +#: ../terminatorlib/terminator.py:230 +#, python-format +msgid "Invalid geometry string %r" +msgstr "" + +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "" #: ../terminatorlib/terminator.py:413 msgid "window" @@ -265,6 +289,13 @@ msgstr "Затвори _терминали" msgid "Close multiple terminals?" msgstr "Да ги затворам сите терминали?" +#: ../terminatorlib/terminator.py:428 +#, python-format +msgid "" +"This %s has several terminals open. Closing the %s will also close all " +"terminals within it." +msgstr "" + #: ../terminatorlib/terminatorterm.py:39 msgid "" "You need to install python bindings for libvte (\"python-vte\" in " @@ -275,7 +306,7 @@ msgstr "" #: ../terminatorlib/terminatorterm.py:133 msgid "Search:" -msgstr "Пребарување:" +msgstr "Барање:" #. Button for the next result. Explicitly not show()n by default. #: ../terminatorlib/terminatorterm.py:150 @@ -351,6 +382,10 @@ msgstr "_Одзумирај терминал" msgid "Unma_ximise terminal" msgstr "На_мали терминал" +#: ../terminatorlib/terminatorterm.py:1148 +msgid "Ed_it profile" +msgstr "" + #: ../terminatorlib/terminatorterm.py:1155 msgid "_Group" msgstr "_Групирај" @@ -367,6 +402,10 @@ msgstr "_Нова група" msgid "_Group all" msgstr "_Групирај ги сите" +#: ../terminatorlib/terminatorterm.py:1206 +msgid "_Ungroup all" +msgstr "" + #: ../terminatorlib/terminatorterm.py:1216 msgid "Group name:" msgstr "Име на групата:" @@ -390,42 +429,3 @@ msgstr "Повеќе терминали во еден прозорец" #: ../data/terminator.desktop.in.h:2 msgid "Terminator" msgstr "Терминатор" - -#: ../terminatorlib/config.py:311 -#, python-format -msgid "Setting %r value %r not a valid colour; ignoring" -msgstr "" - -#: ../terminatorlib/config.py:332 -#, python-format -msgid "Setting %r should be a section name" -msgstr "" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:230 -#, python-format -msgid "Invalid geometry string %r" -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - -#: ../terminatorlib/terminator.py:428 -#, python-format -msgid "" -"This %s has several terminals open. Closing the %s will also close all " -"terminals within it." -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1148 -msgid "Ed_it profile" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1206 -msgid "_Ungroup all" -msgstr "" diff --git a/po/mr.po b/po/mr.po index e7bcb626..f748e4c0 100644 --- a/po/mr.po +++ b/po/mr.po @@ -14,30 +14,9 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" -#. show dialog -#: ../terminatorlib/terminator.py:417 -msgid "Close?" -msgstr "बंद करायचे?" - -#: ../terminatorlib/terminatorterm.py:1033 -msgid "_Open Link" -msgstr "दुवा उघडा(_O)" - -#: ../terminatorlib/terminatorterm.py:1034 -msgid "_Copy Link Address" -msgstr "दुव्याच्या पत्त्याची नक्कल करा(_C)" - -#: ../terminatorlib/terminatorterm.py:1044 -msgid "_Send Mail To..." -msgstr "विरोप पाठवा (_S)" - -#: ../terminatorlib/terminatorterm.py:1045 -msgid "_Copy Email Address" -msgstr "विरोपाच्या पत्त्याची नक्कल करा(_C)" - #: ../terminator:46 msgid "" "You need to install the python bindings for gobject, gtk and pango to run " @@ -282,6 +261,11 @@ msgstr "" msgid "window" msgstr "" +#. show dialog +#: ../terminatorlib/terminator.py:417 +msgid "Close?" +msgstr "बंद करायचे?" + #: ../terminatorlib/terminator.py:423 msgid "Close _Terminals" msgstr "" @@ -321,6 +305,22 @@ msgstr "" msgid "Unable to start shell: " msgstr "" +#: ../terminatorlib/terminatorterm.py:1033 +msgid "_Open Link" +msgstr "दुवा उघडा(_O)" + +#: ../terminatorlib/terminatorterm.py:1034 +msgid "_Copy Link Address" +msgstr "दुव्याच्या पत्त्याची नक्कल करा(_C)" + +#: ../terminatorlib/terminatorterm.py:1044 +msgid "_Send Mail To..." +msgstr "विरोप पाठवा (_S)" + +#: ../terminatorlib/terminatorterm.py:1045 +msgid "_Copy Email Address" +msgstr "विरोपाच्या पत्त्याची नक्कल करा(_C)" + #: ../terminatorlib/terminatorterm.py:1071 msgid "Show _scrollbar" msgstr "" diff --git a/po/ms.po b/po/ms.po index edde6081..e75bf829 100644 --- a/po/ms.po +++ b/po/ms.po @@ -8,67 +8,320 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2008-12-01 03:06+0000\n" -"Last-Translator: wan_ubuntuseekers \n" +"PO-Revision-Date: 2009-09-26 08:40+0000\n" +"Last-Translator: KatieKitty \n" "Language-Team: Malay \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" +#: ../terminator:46 +msgid "" +"You need to install the python bindings for gobject, gtk and pango to run " +"Terminator." +msgstr "" +"Anda perlu install binding python untuk gobject, gtk dan pango untuk " +"menggunakan Terminator." + +#: ../terminator:129 +msgid "" +"You need to run terminator in an X environment. Make sure DISPLAY is " +"properly set" +msgstr "" +"Anda perlu menggunakan terminator dalam X. Pastikan konfigurasi DISPLAY " +"telah disetkan." + +#: ../terminatorlib/configfile.py:96 +msgid "Unterminated quoted string" +msgstr "" + #: ../terminatorlib/configfile.py:147 msgid "Setting without a value" msgstr "Aturan tanpa ada nilai" +#: ../terminatorlib/configfile.py:152 +msgid "Unexpected token" +msgstr "" + +#: ../terminatorlib/config.py:250 +#, python-format +msgid "" +"Configuration error\n" +"\n" +"Errors were encountered while parsing terminator_config(5) file:\n" +"\n" +" %s\n" +"\n" +"%d line(s) have been ignored." +msgstr "" +"Masalah Tetapan\n" +"\n" +"Ralat ketika menguraikan fail terminator_config(5)\n" +"\n" +" %s\n" +"\n" +"barisan %d telah diabaikan." + #: ../terminatorlib/config.py:258 msgid "Configuration error" -msgstr "Masalah Konfigurasi" +msgstr "Masalah tetapan" + +#: ../terminatorlib/config.py:311 +#, python-format +msgid "Setting %r value %r not a valid colour; ignoring" +msgstr "Tetapan %r value %r bukan warna yang sah; mengabaikan" #: ../terminatorlib/config.py:316 #, python-format msgid "%s must be one of: top, left, right, bottom" msgstr "%s harus diantara: atas, kiri, kanan, bawah" +#: ../terminatorlib/config.py:323 +#, python-format +msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" +msgstr "" +"Tetapan boolean %s menganggapkan antara: ya, tidak, benar, salah, hidup, " +"padam" + +#: ../terminatorlib/config.py:329 +msgid "" +"Reading list values from terminator_config(5) is not currently supported" +msgstr "" + +#: ../terminatorlib/config.py:332 +#, python-format +msgid "Setting %r should be a section name" +msgstr "Tetapan %r sepatutnya adalah nama seksyen" + +#: ../terminatorlib/encoding.py:29 +msgid "Current Locale" +msgstr "Lokaliti Semasa" + +#: ../terminatorlib/encoding.py:30 ../terminatorlib/encoding.py:43 +#: ../terminatorlib/encoding.py:62 ../terminatorlib/encoding.py:85 +#: ../terminatorlib/encoding.py:96 +msgid "Western" +msgstr "Barat" + +#: ../terminatorlib/encoding.py:31 ../terminatorlib/encoding.py:63 +#: ../terminatorlib/encoding.py:75 ../terminatorlib/encoding.py:94 +msgid "Central European" +msgstr "Eropah Tengah" + +#: ../terminatorlib/encoding.py:32 +msgid "South European" +msgstr "Eropah Selatan" + +#: ../terminatorlib/encoding.py:33 ../terminatorlib/encoding.py:41 +#: ../terminatorlib/encoding.py:101 +msgid "Baltic" +msgstr "Baltik" + +#: ../terminatorlib/encoding.py:34 ../terminatorlib/encoding.py:64 +#: ../terminatorlib/encoding.py:70 ../terminatorlib/encoding.py:72 +#: ../terminatorlib/encoding.py:77 ../terminatorlib/encoding.py:95 +msgid "Cyrillic" +msgstr "Cyril" + +#: ../terminatorlib/encoding.py:35 ../terminatorlib/encoding.py:67 +#: ../terminatorlib/encoding.py:74 ../terminatorlib/encoding.py:100 +msgid "Arabic" +msgstr "Arab" + #: ../terminatorlib/encoding.py:36 ../terminatorlib/encoding.py:80 #: ../terminatorlib/encoding.py:97 msgid "Greek" msgstr "Yunani" +#: ../terminatorlib/encoding.py:37 +msgid "Hebrew Visual" +msgstr "Paparan Ibrani" + +#: ../terminatorlib/encoding.py:38 ../terminatorlib/encoding.py:66 +#: ../terminatorlib/encoding.py:83 ../terminatorlib/encoding.py:99 +msgid "Hebrew" +msgstr "Ibrani" + +#: ../terminatorlib/encoding.py:39 ../terminatorlib/encoding.py:65 +#: ../terminatorlib/encoding.py:87 ../terminatorlib/encoding.py:98 +msgid "Turkish" +msgstr "Turki" + +#: ../terminatorlib/encoding.py:40 +msgid "Nordic" +msgstr "Nordic" + +#: ../terminatorlib/encoding.py:42 +msgid "Celtic" +msgstr "Celt" + +#: ../terminatorlib/encoding.py:44 ../terminatorlib/encoding.py:86 +msgid "Romanian" +msgstr "Roman" + #: ../terminatorlib/encoding.py:45 ../terminatorlib/encoding.py:46 #: ../terminatorlib/encoding.py:47 ../terminatorlib/encoding.py:48 #: ../terminatorlib/encoding.py:49 msgid "Unicode" msgstr "Unikod" +#: ../terminatorlib/encoding.py:50 +msgid "Armenian" +msgstr "Armenia" + +#: ../terminatorlib/encoding.py:51 ../terminatorlib/encoding.py:52 +#: ../terminatorlib/encoding.py:56 +msgid "Chinese Traditional" +msgstr "Cina Tradisional" + +#: ../terminatorlib/encoding.py:53 +msgid "Cyrillic/Russian" +msgstr "Cyril/Rusia" + #: ../terminatorlib/encoding.py:54 ../terminatorlib/encoding.py:68 #: ../terminatorlib/encoding.py:89 msgid "Japanese" -msgstr "Bahasa Jepun" +msgstr "Jepun" #: ../terminatorlib/encoding.py:55 ../terminatorlib/encoding.py:69 #: ../terminatorlib/encoding.py:71 ../terminatorlib/encoding.py:92 msgid "Korean" -msgstr "Bahasa Korea" +msgstr "Korea" #: ../terminatorlib/encoding.py:57 ../terminatorlib/encoding.py:58 #: ../terminatorlib/encoding.py:59 ../terminatorlib/encoding.py:61 msgid "Chinese Simplified" -msgstr "Bahasa Cina Mudah" +msgstr "Cina Mudah" + +#: ../terminatorlib/encoding.py:60 +msgid "Georgian" +msgstr "Georgia" + +#: ../terminatorlib/encoding.py:73 ../terminatorlib/encoding.py:88 +msgid "Cyrillic/Ukrainian" +msgstr "Cyril/Ukraine" + +#: ../terminatorlib/encoding.py:76 +msgid "Croatian" +msgstr "Croatia" + +#: ../terminatorlib/encoding.py:78 +msgid "Hindi" +msgstr "Hindi" + +#: ../terminatorlib/encoding.py:79 +msgid "Persian" +msgstr "Parsi" + +#: ../terminatorlib/encoding.py:81 +msgid "Gujarati" +msgstr "Gujarati" + +#: ../terminatorlib/encoding.py:82 +msgid "Gurmukhi" +msgstr "Gurmukhī" + +#: ../terminatorlib/encoding.py:84 +msgid "Icelandic" +msgstr "Iceland" + +#: ../terminatorlib/encoding.py:90 ../terminatorlib/encoding.py:93 +#: ../terminatorlib/encoding.py:102 +msgid "Vietnamese" +msgstr "Vietnam" + +#: ../terminatorlib/encoding.py:91 +msgid "Thai" +msgstr "Thai" + +#: ../terminatorlib/prefs_profile.py:370 +msgid "Name" +msgstr "Nama" + +#: ../terminatorlib/prefs_profile.py:377 +msgid "Action" +msgstr "Tindakan" + +#: ../terminatorlib/prefs_profile.py:384 +msgid "Keyboard shortcut" +msgstr "Kekunci pintasan" + +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" +"Gagal mencari binding python untuk bar paparan, hide_window tidak dijumpai." + +#: ../terminatorlib/terminator.py:76 +msgid "Close Tab" +msgstr "Tutup Tab" + +#: ../terminatorlib/terminator.py:105 +msgid "tab" +msgstr "tab" + +#: ../terminatorlib/terminator.py:230 +#, python-format +msgid "Invalid geometry string %r" +msgstr "String geometri %r tidak sah" + +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "Gagal untuk bind kekunci hide_window" + +#: ../terminatorlib/terminator.py:413 +msgid "window" +msgstr "tetingkap" #. show dialog #: ../terminatorlib/terminator.py:417 msgid "Close?" msgstr "Tutup?" +#: ../terminatorlib/terminator.py:423 +msgid "Close _Terminals" +msgstr "Tutup _Terminal" + +#: ../terminatorlib/terminator.py:425 +msgid "Close multiple terminals?" +msgstr "Tutup semua terminal?" + +#: ../terminatorlib/terminator.py:428 +#, python-format +msgid "" +"This %s has several terminals open. Closing the %s will also close all " +"terminals within it." +msgstr "" +"%s ini mempunyai beberapa terminal yang dibuka. Menutup %s akan menyebabkan " +"semua terminal didalamnya ditutup." + +#: ../terminatorlib/terminatorterm.py:39 +msgid "" +"You need to install python bindings for libvte (\"python-vte\" in " +"debian/ubuntu)" +msgstr "" +"Anda hendaklah memasang binding python untuk libvte (\"python-vte\" dalam " +"debian/ubuntu)" + +#: ../terminatorlib/terminatorterm.py:133 +msgid "Search:" +msgstr "Cari:" + +#. Button for the next result. Explicitly not show()n by default. +#: ../terminatorlib/terminatorterm.py:150 +msgid "Next" +msgstr "Seterusnya" + #. Give up, we're completely stuck #: ../terminatorlib/terminatorterm.py:518 msgid "Unable to find a shell" -msgstr "Tidak dapat mencari cangkerang" +msgstr "Gagal mencari shell" #: ../terminatorlib/terminatorterm.py:539 msgid "Unable to start shell: " -msgstr "Tidak dapat memulakan cangkerang: " +msgstr "Gagal memulakan shell: " #: ../terminatorlib/terminatorterm.py:1033 msgid "_Open Link" @@ -86,6 +339,14 @@ msgstr "_Hantar Email Kepada..." msgid "_Copy Email Address" msgstr "_Salin Alamat Emel" +#: ../terminatorlib/terminatorterm.py:1071 +msgid "Show _scrollbar" +msgstr "Papar _scrollbar" + +#: ../terminatorlib/terminatorterm.py:1076 +msgid "Show _titlebar" +msgstr "Papar _titlebar" + #: ../terminatorlib/terminatorterm.py:1089 msgid "Split H_orizontally" msgstr "Bahagikan secara Mencancang" @@ -96,7 +357,11 @@ msgstr "bahagikan secara Mendatar" #: ../terminatorlib/terminatorterm.py:1107 msgid "Open _Tab" -msgstr "Buka_Tab" +msgstr "Buka _Tab" + +#: ../terminatorlib/terminatorterm.py:1112 +msgid "Open _Debug Tab" +msgstr "Buka _Debug Tab" #: ../terminatorlib/terminatorterm.py:1118 msgid "Open Top Level Tab" @@ -104,7 +369,7 @@ msgstr "Buka Aras tertinggi Tab" #: ../terminatorlib/terminatorterm.py:1127 msgid "_Zoom terminal" -msgstr "_Zoom terminal" +msgstr "_Besarkan terminal" #: ../terminatorlib/terminatorterm.py:1131 msgid "Ma_ximise terminal" @@ -112,303 +377,56 @@ msgstr "Besar_kan terminal" #: ../terminatorlib/terminatorterm.py:1136 msgid "_Unzoom terminal" -msgstr "_Nyahzoom terminal" +msgstr "_Kecilkan terminal" #: ../terminatorlib/terminatorterm.py:1141 msgid "Unma_ximise terminal" msgstr "Kecil_kan terminal" +#: ../terminatorlib/terminatorterm.py:1148 +msgid "Ed_it profile" +msgstr "Ubah_ profil" + +#: ../terminatorlib/terminatorterm.py:1155 +msgid "_Group" +msgstr "_Kumpulan" + +#: ../terminatorlib/terminatorterm.py:1179 +msgid "None" +msgstr "Tiada" + +#: ../terminatorlib/terminatorterm.py:1194 +msgid "_New group" +msgstr "_Kumpulan baru" + +#: ../terminatorlib/terminatorterm.py:1201 +msgid "_Group all" +msgstr "_Kumpulkan semua" + +#: ../terminatorlib/terminatorterm.py:1206 +msgid "_Ungroup all" +msgstr "_Leraikan semua" + +#: ../terminatorlib/terminatorterm.py:1216 +msgid "Group name:" +msgstr "Nama kumpulan:" + +#: ../terminatorlib/terminatorterm.py:1262 +msgid "All" +msgstr "Semua" + #: ../terminatorlib/terminatorterm.py:1282 msgid "Encodings" -msgstr "Pengenkodan" +msgstr "Mengenkodkan" #: ../terminatorlib/terminatorterm.py:1300 msgid "Other Encodings" msgstr "Pengenkodan Lain" -#: ../terminator:46 -msgid "" -"You need to install the python bindings for gobject, gtk and pango to run " -"Terminator." -msgstr "" - -#: ../terminator:129 -msgid "" -"You need to run terminator in an X environment. Make sure DISPLAY is " -"properly set" -msgstr "" - -#: ../terminatorlib/configfile.py:96 -msgid "Unterminated quoted string" -msgstr "" - -#: ../terminatorlib/configfile.py:152 -msgid "Unexpected token" -msgstr "" - -#: ../terminatorlib/config.py:250 -#, python-format -msgid "" -"Configuration error\n" -"\n" -"Errors were encountered while parsing terminator_config(5) file:\n" -"\n" -" %s\n" -"\n" -"%d line(s) have been ignored." -msgstr "" - -#: ../terminatorlib/config.py:311 -#, python-format -msgid "Setting %r value %r not a valid colour; ignoring" -msgstr "" - -#: ../terminatorlib/config.py:323 -#, python-format -msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" -msgstr "" - -#: ../terminatorlib/config.py:329 -msgid "" -"Reading list values from terminator_config(5) is not currently supported" -msgstr "" - -#: ../terminatorlib/config.py:332 -#, python-format -msgid "Setting %r should be a section name" -msgstr "" - -#: ../terminatorlib/encoding.py:29 -msgid "Current Locale" -msgstr "" - -#: ../terminatorlib/encoding.py:30 ../terminatorlib/encoding.py:43 -#: ../terminatorlib/encoding.py:62 ../terminatorlib/encoding.py:85 -#: ../terminatorlib/encoding.py:96 -msgid "Western" -msgstr "" - -#: ../terminatorlib/encoding.py:31 ../terminatorlib/encoding.py:63 -#: ../terminatorlib/encoding.py:75 ../terminatorlib/encoding.py:94 -msgid "Central European" -msgstr "" - -#: ../terminatorlib/encoding.py:32 -msgid "South European" -msgstr "" - -#: ../terminatorlib/encoding.py:33 ../terminatorlib/encoding.py:41 -#: ../terminatorlib/encoding.py:101 -msgid "Baltic" -msgstr "" - -#: ../terminatorlib/encoding.py:34 ../terminatorlib/encoding.py:64 -#: ../terminatorlib/encoding.py:70 ../terminatorlib/encoding.py:72 -#: ../terminatorlib/encoding.py:77 ../terminatorlib/encoding.py:95 -msgid "Cyrillic" -msgstr "" - -#: ../terminatorlib/encoding.py:35 ../terminatorlib/encoding.py:67 -#: ../terminatorlib/encoding.py:74 ../terminatorlib/encoding.py:100 -msgid "Arabic" -msgstr "" - -#: ../terminatorlib/encoding.py:37 -msgid "Hebrew Visual" -msgstr "" - -#: ../terminatorlib/encoding.py:38 ../terminatorlib/encoding.py:66 -#: ../terminatorlib/encoding.py:83 ../terminatorlib/encoding.py:99 -msgid "Hebrew" -msgstr "" - -#: ../terminatorlib/encoding.py:39 ../terminatorlib/encoding.py:65 -#: ../terminatorlib/encoding.py:87 ../terminatorlib/encoding.py:98 -msgid "Turkish" -msgstr "" - -#: ../terminatorlib/encoding.py:40 -msgid "Nordic" -msgstr "" - -#: ../terminatorlib/encoding.py:42 -msgid "Celtic" -msgstr "" - -#: ../terminatorlib/encoding.py:44 ../terminatorlib/encoding.py:86 -msgid "Romanian" -msgstr "" - -#: ../terminatorlib/encoding.py:50 -msgid "Armenian" -msgstr "" - -#: ../terminatorlib/encoding.py:51 ../terminatorlib/encoding.py:52 -#: ../terminatorlib/encoding.py:56 -msgid "Chinese Traditional" -msgstr "" - -#: ../terminatorlib/encoding.py:53 -msgid "Cyrillic/Russian" -msgstr "" - -#: ../terminatorlib/encoding.py:60 -msgid "Georgian" -msgstr "" - -#: ../terminatorlib/encoding.py:73 ../terminatorlib/encoding.py:88 -msgid "Cyrillic/Ukrainian" -msgstr "" - -#: ../terminatorlib/encoding.py:76 -msgid "Croatian" -msgstr "" - -#: ../terminatorlib/encoding.py:78 -msgid "Hindi" -msgstr "" - -#: ../terminatorlib/encoding.py:79 -msgid "Persian" -msgstr "" - -#: ../terminatorlib/encoding.py:81 -msgid "Gujarati" -msgstr "" - -#: ../terminatorlib/encoding.py:82 -msgid "Gurmukhi" -msgstr "" - -#: ../terminatorlib/encoding.py:84 -msgid "Icelandic" -msgstr "" - -#: ../terminatorlib/encoding.py:90 ../terminatorlib/encoding.py:93 -#: ../terminatorlib/encoding.py:102 -msgid "Vietnamese" -msgstr "" - -#: ../terminatorlib/encoding.py:91 -msgid "Thai" -msgstr "" - -#: ../terminatorlib/prefs_profile.py:370 -msgid "Name" -msgstr "" - -#: ../terminatorlib/prefs_profile.py:377 -msgid "Action" -msgstr "" - -#: ../terminatorlib/prefs_profile.py:384 -msgid "Keyboard shortcut" -msgstr "" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:76 -msgid "Close Tab" -msgstr "" - -#: ../terminatorlib/terminator.py:105 -msgid "tab" -msgstr "" - -#: ../terminatorlib/terminator.py:230 -#, python-format -msgid "Invalid geometry string %r" -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - -#: ../terminatorlib/terminator.py:413 -msgid "window" -msgstr "" - -#: ../terminatorlib/terminator.py:423 -msgid "Close _Terminals" -msgstr "" - -#: ../terminatorlib/terminator.py:425 -msgid "Close multiple terminals?" -msgstr "" - -#: ../terminatorlib/terminator.py:428 -#, python-format -msgid "" -"This %s has several terminals open. Closing the %s will also close all " -"terminals within it." -msgstr "" - -#: ../terminatorlib/terminatorterm.py:39 -msgid "" -"You need to install python bindings for libvte (\"python-vte\" in " -"debian/ubuntu)" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:133 -msgid "Search:" -msgstr "" - -#. Button for the next result. Explicitly not show()n by default. -#: ../terminatorlib/terminatorterm.py:150 -msgid "Next" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1071 -msgid "Show _scrollbar" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1076 -msgid "Show _titlebar" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1112 -msgid "Open _Debug Tab" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1148 -msgid "Ed_it profile" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1155 -msgid "_Group" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1179 -msgid "None" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1194 -msgid "_New group" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1201 -msgid "_Group all" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1206 -msgid "_Ungroup all" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1216 -msgid "Group name:" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1262 -msgid "All" -msgstr "" - #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" -msgstr "" +msgstr "Kesemua terminal dalam satu tetingkap" #: ../data/terminator.desktop.in.h:2 msgid "Terminator" -msgstr "" +msgstr "Terminator" diff --git a/po/nb.po b/po/nb.po index b46f6cf3..4a7818b4 100644 --- a/po/nb.po +++ b/po/nb.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-05-17 23:58+0000\n" -"Last-Translator: Christian Aasan \n" +"PO-Revision-Date: 2009-10-18 22:02+0000\n" +"Last-Translator: jraregris \n" "Language-Team: Norwegian Bokmal \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -22,7 +22,7 @@ msgid "" "You need to install the python bindings for gobject, gtk and pango to run " "Terminator." msgstr "" -"Du må installere python bindingene for gobject, gtk og pango for å kjøre " +"Du må installere python-bindingene for gobject, gtk og pango for å kjøre " "Terminator." #: ../terminator:129 @@ -30,7 +30,7 @@ msgid "" "You need to run terminator in an X environment. Make sure DISPLAY is " "properly set" msgstr "" -"Du må kjøre terminator i et X miljø. Sjekk om variabelen DISPLAY er satt " +"Du må kjøre terminator i et X-miljø. Sjekk om variabelen DISPLAY er satt " "korrekt." #: ../terminatorlib/configfile.py:96 @@ -56,14 +56,13 @@ msgid "" "\n" "%d line(s) have been ignored." msgstr "" -"konfigurasjons feil\n" +"konfigurasjonsfeil\n" "\n" -"Feil ble oppdaget were encountered while parsing terminator_config(5) " -"file:\n" +"Feil ble oppdaget under lesing av terminator_config(5) file:\n" "\n" "%s\n" "\n" -"%d line(s) have been ignored." +"%d linje(r) ignoreres." #: ../terminatorlib/config.py:258 msgid "Configuration error" @@ -83,18 +82,18 @@ msgstr "%s må være en av: topp, venstre, høyre, bunn" #, python-format msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" msgstr "" -"Den boleanske innstillingen %s forventer en av: ja, nei, sant, usant, på, av" +"Den boolske innstillingen %s forventer en av: ja, nei, sant, usant, på, av" #: ../terminatorlib/config.py:329 msgid "" "Reading list values from terminator_config(5) is not currently supported" msgstr "" -"Lesing av liste-verdier fra terminator_config(5) er for tiden ikke støttet" +"Lesing av listeverdier fra terminator_config(5) er for tiden ikke støttet" #: ../terminatorlib/config.py:332 #, python-format msgid "Setting %r should be a section name" -msgstr "Instilling %r bør være et seksjons-navn" +msgstr "Innstilling %r bør være et seksjonsnavn" #: ../terminatorlib/encoding.py:29 msgid "Current Locale" @@ -249,6 +248,12 @@ msgstr "Handling" msgid "Keyboard shortcut" msgstr "Tastatur snarvei" +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" +"Kan ikke finne python bindings for deskbar, hide_window er ikke tilgjengelig." + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "Lukk fane" @@ -262,6 +267,10 @@ msgstr "tabulator" msgid "Invalid geometry string %r" msgstr "Ugjyldig geometrisk string %r" +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "Kan ikke binde hide_window tast" + #: ../terminatorlib/terminator.py:413 msgid "window" msgstr "vindu" @@ -413,15 +422,6 @@ msgstr "Tegnkodinger" msgid "Other Encodings" msgstr "Andre tegnkodinger" -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" msgstr "Flere terminaler i et vindu" diff --git a/po/nl.po b/po/nl.po index 317be92a..3260855c 100644 --- a/po/nl.po +++ b/po/nl.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-05-19 20:50+0000\n" -"Last-Translator: Pieter Lexis \n" +"PO-Revision-Date: 2009-09-11 13:28+0000\n" +"Last-Translator: Arioch \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -33,10 +33,18 @@ msgstr "" "Terminator draait alleen in een X omgeving. Controleer eerst of je DISPLAY " "variable goed is ingesteld." +#: ../terminatorlib/configfile.py:96 +msgid "Unterminated quoted string" +msgstr "Niet-afgesloten aanhalingsteken" + #: ../terminatorlib/configfile.py:147 msgid "Setting without a value" msgstr "Optie zonder een waarde" +#: ../terminatorlib/configfile.py:152 +msgid "Unexpected token" +msgstr "Onverwacht symbool" + #: ../terminatorlib/config.py:250 #, python-format msgid "" @@ -57,6 +65,27 @@ msgstr "" "\n" "%d lijn(en) zijn overgeslagen." +#: ../terminatorlib/config.py:258 +msgid "Configuration error" +msgstr "Configuratiefout" + +#: ../terminatorlib/config.py:311 +#, python-format +msgid "Setting %r value %r not a valid colour; ignoring" +msgstr "Instelling %r waarde %r is niet een geldige kleur; wordt genegeerd" + +#: ../terminatorlib/config.py:316 +#, python-format +msgid "%s must be one of: top, left, right, bottom" +msgstr "%s moet een van de volgende zijn: boven, links, rechts, onder" + +#: ../terminatorlib/config.py:323 +#, python-format +msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" +msgstr "" +"Booleaanse instelling %s verwacht een van de volgende: yes, no, true, false, " +"on, off" + #: ../terminatorlib/config.py:329 msgid "" "Reading list values from terminator_config(5) is not currently supported" @@ -64,6 +93,11 @@ msgstr "" "Het lezen van lijsten van het terminator_config(5) bestand is momenteel niet " "ondersteund" +#: ../terminatorlib/config.py:332 +#, python-format +msgid "Setting %r should be a section name" +msgstr "Instelling %r dient een sectienaam te zijn" + #: ../terminatorlib/encoding.py:29 msgid "Current Locale" msgstr "Huidige locale" @@ -106,7 +140,7 @@ msgstr "Grieks" #: ../terminatorlib/encoding.py:37 msgid "Hebrew Visual" -msgstr "Visueel Hebreeuws" +msgstr "Hebreeuws Visueel" #: ../terminatorlib/encoding.py:38 ../terminatorlib/encoding.py:66 #: ../terminatorlib/encoding.py:83 ../terminatorlib/encoding.py:99 @@ -205,10 +239,45 @@ msgstr "Vietnamees" msgid "Thai" msgstr "Thais" +#: ../terminatorlib/prefs_profile.py:370 +msgid "Name" +msgstr "Naam" + +#: ../terminatorlib/prefs_profile.py:377 +msgid "Action" +msgstr "Actie:" + +#: ../terminatorlib/prefs_profile.py:384 +msgid "Keyboard shortcut" +msgstr "Sneltoets" + +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" +"Kan pythonbindings deskbar, hide_window niet vinden, deze zijn onbeschikbaar" + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "Tabblad sluiten" +#: ../terminatorlib/terminator.py:105 +msgid "tab" +msgstr "tabblad" + +#: ../terminatorlib/terminator.py:230 +#, python-format +msgid "Invalid geometry string %r" +msgstr "ongeldige geometrie string %r" + +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "Het is niet mogelijk de hide_window toets in te stellen" + +#: ../terminatorlib/terminator.py:413 +msgid "window" +msgstr "venster" + #. show dialog #: ../terminatorlib/terminator.py:417 msgid "Close?" @@ -218,6 +287,19 @@ msgstr "Sluiten?" msgid "Close _Terminals" msgstr "Sluit_Terminals" +#: ../terminatorlib/terminator.py:425 +msgid "Close multiple terminals?" +msgstr "Meerdere terminals sluiten?" + +#: ../terminatorlib/terminator.py:428 +#, python-format +msgid "" +"This %s has several terminals open. Closing the %s will also close all " +"terminals within it." +msgstr "" +"Deze %s heeft meerdere terminals open. Het sluiten van de %s zal ook alle " +"terminals er in sluiten." + #: ../terminatorlib/terminatorterm.py:39 msgid "" "You need to install python bindings for libvte (\"python-vte\" in " @@ -226,6 +308,15 @@ msgstr "" "Je moet de python-bindingen installeren voor libvte (\"python-vte\" in " "debian/ubuntu)" +#: ../terminatorlib/terminatorterm.py:133 +msgid "Search:" +msgstr "Zoeken:" + +#. Button for the next result. Explicitly not show()n by default. +#: ../terminatorlib/terminatorterm.py:150 +msgid "Next" +msgstr "Volgende" + #. Give up, we're completely stuck #: ../terminatorlib/terminatorterm.py:518 msgid "Unable to find a shell" @@ -241,7 +332,7 @@ msgstr "Koppeling _openen" #: ../terminatorlib/terminatorterm.py:1034 msgid "_Copy Link Address" -msgstr "Koppelingsadres _kopiëren" +msgstr "Kopieer Koppeling Adres" #: ../terminatorlib/terminatorterm.py:1044 msgid "_Send Mail To..." @@ -271,6 +362,10 @@ msgstr "Splits V_erticaal" msgid "Open _Tab" msgstr "Nieuw _tabblad" +#: ../terminatorlib/terminatorterm.py:1112 +msgid "Open _Debug Tab" +msgstr "Open_Debug Tabblad" + #: ../terminatorlib/terminatorterm.py:1118 msgid "Open Top Level Tab" msgstr "Open bovenste niveau tab" @@ -287,109 +382,9 @@ msgstr "Ma_ximaliseer" msgid "_Unzoom terminal" msgstr "Zoom terminal uit" -#: ../terminatorlib/terminatorterm.py:1282 -msgid "Encodings" -msgstr "Tekensets" - -#: ../terminatorlib/terminatorterm.py:1300 -msgid "Other Encodings" -msgstr "Andere tekensets" - -#: ../terminatorlib/configfile.py:96 -msgid "Unterminated quoted string" -msgstr "Niet-afgesloten aanhalingsteken" - -#: ../terminatorlib/configfile.py:152 -msgid "Unexpected token" -msgstr "Onverwacht symbool" - -#: ../terminatorlib/config.py:258 -msgid "Configuration error" -msgstr "" - -#: ../terminatorlib/config.py:311 -#, python-format -msgid "Setting %r value %r not a valid colour; ignoring" -msgstr "Instelling %r waarde %r is niet een geldige kleur; wordt genegeerd" - -#: ../terminatorlib/config.py:316 -#, python-format -msgid "%s must be one of: top, left, right, bottom" -msgstr "%s moet een van de volgende zijn: boven, links, rechts, onder" - -#: ../terminatorlib/config.py:323 -#, python-format -msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" -msgstr "" - -#: ../terminatorlib/config.py:332 -#, python-format -msgid "Setting %r should be a section name" -msgstr "Instelling %r dient een sectienaam te zijn" - -#: ../terminatorlib/prefs_profile.py:370 -msgid "Name" -msgstr "Naam" - -#: ../terminatorlib/prefs_profile.py:377 -msgid "Action" -msgstr "Actie:" - -#: ../terminatorlib/prefs_profile.py:384 -msgid "Keyboard shortcut" -msgstr "Sneltoets" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:105 -msgid "tab" -msgstr "" - -#: ../terminatorlib/terminator.py:230 -#, python-format -msgid "Invalid geometry string %r" -msgstr "ongeldige geometrie string %r" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - -#: ../terminatorlib/terminator.py:413 -msgid "window" -msgstr "venster" - -#: ../terminatorlib/terminator.py:425 -msgid "Close multiple terminals?" -msgstr "Meerdere terminals sluiten?" - -#: ../terminatorlib/terminator.py:428 -#, python-format -msgid "" -"This %s has several terminals open. Closing the %s will also close all " -"terminals within it." -msgstr "" -"Deze %s heeft meerdere terminals open. Het sluiten van de %s zal ook alle " -"terminals er in sluiten." - -#: ../terminatorlib/terminatorterm.py:133 -msgid "Search:" -msgstr "Zoeken:" - -#. Button for the next result. Explicitly not show()n by default. -#: ../terminatorlib/terminatorterm.py:150 -msgid "Next" -msgstr "Volgende" - -#: ../terminatorlib/terminatorterm.py:1112 -msgid "Open _Debug Tab" -msgstr "" - #: ../terminatorlib/terminatorterm.py:1141 msgid "Unma_ximise terminal" -msgstr "" +msgstr "Unma_ximise terminal" #: ../terminatorlib/terminatorterm.py:1148 msgid "Ed_it profile" @@ -401,11 +396,11 @@ msgstr "_Groep" #: ../terminatorlib/terminatorterm.py:1179 msgid "None" -msgstr "" +msgstr "Geen" #: ../terminatorlib/terminatorterm.py:1194 msgid "_New group" -msgstr "" +msgstr "_Nieuwe groep" #: ../terminatorlib/terminatorterm.py:1201 msgid "_Group all" @@ -417,11 +412,19 @@ msgstr "_het Groepering op" #: ../terminatorlib/terminatorterm.py:1216 msgid "Group name:" -msgstr "Groep naam:" +msgstr "Groepsnaam:" #: ../terminatorlib/terminatorterm.py:1262 msgid "All" -msgstr "" +msgstr "Alles" + +#: ../terminatorlib/terminatorterm.py:1282 +msgid "Encodings" +msgstr "Tekensets" + +#: ../terminatorlib/terminatorterm.py:1300 +msgid "Other Encodings" +msgstr "Andere tekensets" #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" diff --git a/po/nn.po b/po/nn.po index c679dc16..fd34e74f 100644 --- a/po/nn.po +++ b/po/nn.po @@ -14,45 +14,9 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" -#. show dialog -#: ../terminatorlib/terminator.py:417 -msgid "Close?" -msgstr "Lukk?" - -#: ../terminatorlib/terminatorterm.py:39 -msgid "" -"You need to install python bindings for libvte (\"python-vte\" in " -"debian/ubuntu)" -msgstr "" -"Du må installere python bindinga for libvte (\"python-vte\" i debian/ubuntu)" - -#: ../terminatorlib/terminatorterm.py:1033 -msgid "_Open Link" -msgstr "_Opne lenkje" - -#: ../terminatorlib/terminatorterm.py:1034 -msgid "_Copy Link Address" -msgstr "_Kopier lenkjeadresse" - -#: ../terminatorlib/terminatorterm.py:1044 -msgid "_Send Mail To..." -msgstr "_Send e-post til..." - -#: ../terminatorlib/terminatorterm.py:1045 -msgid "_Copy Email Address" -msgstr "_Kopier e-postadresse" - -#: ../terminatorlib/terminatorterm.py:1089 -msgid "Split H_orizontally" -msgstr "Splitt H_orisontalt" - -#: ../terminatorlib/terminatorterm.py:1090 -msgid "Split V_ertically" -msgstr "Splitt V_ertikalt" - #: ../terminator:46 msgid "" "You need to install the python bindings for gobject, gtk and pango to run " @@ -297,6 +261,11 @@ msgstr "" msgid "window" msgstr "" +#. show dialog +#: ../terminatorlib/terminator.py:417 +msgid "Close?" +msgstr "Lukk?" + #: ../terminatorlib/terminator.py:423 msgid "Close _Terminals" msgstr "" @@ -312,6 +281,13 @@ msgid "" "terminals within it." msgstr "" +#: ../terminatorlib/terminatorterm.py:39 +msgid "" +"You need to install python bindings for libvte (\"python-vte\" in " +"debian/ubuntu)" +msgstr "" +"Du må installere python bindinga for libvte (\"python-vte\" i debian/ubuntu)" + #: ../terminatorlib/terminatorterm.py:133 msgid "Search:" msgstr "" @@ -330,6 +306,22 @@ msgstr "" msgid "Unable to start shell: " msgstr "" +#: ../terminatorlib/terminatorterm.py:1033 +msgid "_Open Link" +msgstr "_Opne lenkje" + +#: ../terminatorlib/terminatorterm.py:1034 +msgid "_Copy Link Address" +msgstr "_Kopier lenkjeadresse" + +#: ../terminatorlib/terminatorterm.py:1044 +msgid "_Send Mail To..." +msgstr "_Send e-post til..." + +#: ../terminatorlib/terminatorterm.py:1045 +msgid "_Copy Email Address" +msgstr "_Kopier e-postadresse" + #: ../terminatorlib/terminatorterm.py:1071 msgid "Show _scrollbar" msgstr "" @@ -338,6 +330,14 @@ msgstr "" msgid "Show _titlebar" msgstr "" +#: ../terminatorlib/terminatorterm.py:1089 +msgid "Split H_orizontally" +msgstr "Splitt H_orisontalt" + +#: ../terminatorlib/terminatorterm.py:1090 +msgid "Split V_ertically" +msgstr "Splitt V_ertikalt" + #: ../terminatorlib/terminatorterm.py:1107 msgid "Open _Tab" msgstr "" diff --git a/po/pl.po b/po/pl.po index de9c2aae..cab53a1d 100644 --- a/po/pl.po +++ b/po/pl.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-05-08 19:11+0000\n" -"Last-Translator: Wacław Jacek \n" +"PO-Revision-Date: 2009-07-16 22:47+0000\n" +"Last-Translator: Marek Spociński \n" "Language-Team: Polish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -22,7 +22,7 @@ msgid "" "You need to install the python bindings for gobject, gtk and pango to run " "Terminator." msgstr "" -"Musisz zainstalować dowiązania pythona dla gobject, gtk i pangoo aby móc " +"Musisz zainstalować dowiązania pythona dla gobject, gtk i pango aby móc " "uruchomić Terminatora." #: ../terminator:129 @@ -250,6 +250,11 @@ msgstr "Czynność" msgid "Keyboard shortcut" msgstr "Skrót klawiaturowy" +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "Zamknij kartę" @@ -263,6 +268,10 @@ msgstr "karta" msgid "Invalid geometry string %r" msgstr "Nieprawidłowy ciąg geometrii %r" +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "Nie można było przypisać klawisza do komendy hide_window" + #: ../terminatorlib/terminator.py:413 msgid "window" msgstr "okno" @@ -422,12 +431,3 @@ msgstr "Wiele terminali w jednym oknie" #: ../data/terminator.desktop.in.h:2 msgid "Terminator" msgstr "Terminator" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "Nie można było przypisać klawisza do komendy hide_window" diff --git a/po/pt.po b/po/pt.po index b47ae97e..44cb2d03 100644 --- a/po/pt.po +++ b/po/pt.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-04-09 23:16+0000\n" -"Last-Translator: Bruno Guerreiro \n" +"PO-Revision-Date: 2009-06-26 22:51+0000\n" +"Last-Translator: Formatado \n" "Language-Team: Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -22,7 +22,7 @@ msgid "" "You need to install the python bindings for gobject, gtk and pango to run " "Terminator." msgstr "" -"Para executar o Terminator necessita de instalas as bibliotecas em python " +"Para executar o Terminator necessita de instalar as bibliotecas em python " "para o gobject, gtk e pango." #: ../terminator:129 diff --git a/po/pt_BR.po b/po/pt_BR.po index 928c3314..8c30b83e 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-05-05 21:27+0000\n" -"Last-Translator: lucio.matema \n" +"PO-Revision-Date: 2009-07-29 14:15+0000\n" +"Last-Translator: Miguel Lima \n" "Language-Team: Brazilian Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -247,6 +247,11 @@ msgstr "Ação" msgid "Keyboard shortcut" msgstr "Atalho de teclado" +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "Fechar Aba" @@ -260,6 +265,10 @@ msgstr "aba" msgid "Invalid geometry string %r" msgstr "String de geometria inválida: %r" +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "Incapaz de vincular hide_windows a uma chave" + #: ../terminatorlib/terminator.py:413 msgid "window" msgstr "janela" @@ -412,15 +421,6 @@ msgstr "Codificações" msgid "Other Encodings" msgstr "Outras Codificações" -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" msgstr "Múltiplos terminais em uma janela" diff --git a/po/ro.po b/po/ro.po index fa336089..2745baab 100644 --- a/po/ro.po +++ b/po/ro.po @@ -2,19 +2,19 @@ # Copyright (c) 2008 Rosetta Contributors and Canonical Ltd 2008 # This file is distributed under the same license as the terminator package. # Cris Grada , 2008. -# +# Lucian Adrian Grijincu , 2009 msgid "" msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-05-30 12:33+0000\n" -"Last-Translator: Cris G \n" +"PO-Revision-Date: 2009-11-10 17:56+0000\n" +"Last-Translator: Lucian Adrian Grijincu \n" "Language-Team: Romanian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -22,7 +22,7 @@ msgid "" "You need to install the python bindings for gobject, gtk and pango to run " "Terminator." msgstr "" -"Pentru a putea rula Terminator este nevoie de dependinţele python pentru " +"Pentru a putea executa Terminator este nevoie de dependenţele python pentru " "gobject, gtk şi pango." #: ../terminator:129 @@ -30,16 +30,16 @@ msgid "" "You need to run terminator in an X environment. Make sure DISPLAY is " "properly set" msgstr "" -"Terminator trebuie rulat în serverul grafic. Asiguraţi-vă ca MONITORUL să " -"fie setat corect" +"Terminator trebuie executat într-un mediu grafic. Asiguraţi-vă ca variabila " +"de mediu DISPLAY este definită corect" #: ../terminatorlib/configfile.py:96 msgid "Unterminated quoted string" -msgstr "Linie cu ghilimele deschise" +msgstr "Șir între ghilimele neterminat" #: ../terminatorlib/configfile.py:147 msgid "Setting without a value" -msgstr "Setare fără nici o valoare" +msgstr "Variabilă de configurare fără nici o valoare" #: ../terminatorlib/configfile.py:152 msgid "Unexpected token" @@ -56,46 +56,47 @@ msgid "" "\n" "%d line(s) have been ignored." msgstr "" -"Eroare setare\n" -"S-au ivit erori la analiza fişierului terminator_config(5):\n" +"Eroare de configurare\n" +"Au apărut erori la analiza fişierului terminator_config(5):\n" "\n" " %s\n" "\n" -"linia(le) %d au fost ignorate." +"%d linii au fost ignorate." #: ../terminatorlib/config.py:258 msgid "Configuration error" -msgstr "Eroare setare" +msgstr "Eroare de configurare" #: ../terminatorlib/config.py:311 #, python-format msgid "Setting %r value %r not a valid colour; ignoring" -msgstr "Setarea %r valorii %r nu e o culoare valabilă; se ignoră" +msgstr "Definirea %r valorii %r nu e o culoare validă; va fi ignorată" #: ../terminatorlib/config.py:316 #, python-format msgid "%s must be one of: top, left, right, bottom" -msgstr "%s trebuie să fie una dintre: sus, stânga, dreapta, jos" +msgstr "%s trebuie să fie una dintre: top, left, right, bottom" #: ../terminatorlib/config.py:323 #, python-format msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" -msgstr "Setare boleană %s aşteptată precum: yes, no, true, false, on, off" +msgstr "" +"Variabila boleană %s aşteptată una din valorile: yes, no, true, false, on, " +"off" #: ../terminatorlib/config.py:329 msgid "" "Reading list values from terminator_config(5) is not currently supported" -msgstr "" -"Citirea valorilor listei din terminator-config(5) momentan nu e disponibilă" +msgstr "Citirea valorilor listei din terminator_config(5) nu este suportată" #: ../terminatorlib/config.py:332 #, python-format msgid "Setting %r should be a section name" -msgstr "Setarea %r ar trebui să fie un nume secţiune valabil" +msgstr "Variabila %r ar trebui să fie un nume secţiune valid" #: ../terminatorlib/encoding.py:29 msgid "Current Locale" -msgstr "Setări locale curente" +msgstr "Configurări locale curente" #: ../terminatorlib/encoding.py:30 ../terminatorlib/encoding.py:43 #: ../terminatorlib/encoding.py:62 ../terminatorlib/encoding.py:85 @@ -163,7 +164,7 @@ msgstr "Română" #: ../terminatorlib/encoding.py:47 ../terminatorlib/encoding.py:48 #: ../terminatorlib/encoding.py:49 msgid "Unicode" -msgstr "Unicode" +msgstr "Unicod" #: ../terminatorlib/encoding.py:50 msgid "Armenian" @@ -195,7 +196,7 @@ msgstr "Chineză simplificată" #: ../terminatorlib/encoding.py:60 msgid "Georgian" -msgstr "Giorgiană" +msgstr "Georgiană" #: ../terminatorlib/encoding.py:73 ../terminatorlib/encoding.py:88 msgid "Cyrillic/Ukrainian" @@ -211,7 +212,7 @@ msgstr "Hindusă" #: ../terminatorlib/encoding.py:79 msgid "Persian" -msgstr "Persiană" +msgstr "Persană" #: ../terminatorlib/encoding.py:81 msgid "Gujarati" @@ -232,7 +233,7 @@ msgstr "Vietnameză" #: ../terminatorlib/encoding.py:91 msgid "Thai" -msgstr "Thailandeză" +msgstr "Tailandeză" #: ../terminatorlib/prefs_profile.py:370 msgid "Name" @@ -246,9 +247,16 @@ msgstr "Acţiune" msgid "Keyboard shortcut" msgstr "Scurtături tastatură" +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" +"Nu s-au găsit dependenţele python pentru deskbar, opţiunea hide_window nu " +"este disponibilă." + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" -msgstr "Închide Tab" +msgstr "Închide tab" #: ../terminatorlib/terminator.py:105 msgid "tab" @@ -257,7 +265,11 @@ msgstr "tab" #: ../terminatorlib/terminator.py:230 #, python-format msgid "Invalid geometry string %r" -msgstr "Stringa necorespunzătoare %r" +msgstr "Șir geometrie necorespunzător %r" + +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "Nu s-a putut asocia tasta rapidă pentru hide_window" #: ../terminatorlib/terminator.py:413 msgid "window" @@ -266,15 +278,15 @@ msgstr "fereastră" #. show dialog #: ../terminatorlib/terminator.py:417 msgid "Close?" -msgstr "Închid?" +msgstr "Închideți?" #: ../terminatorlib/terminator.py:423 msgid "Close _Terminals" -msgstr "Închide _Terminalele" +msgstr "Închide _terminalele" #: ../terminatorlib/terminator.py:425 msgid "Close multiple terminals?" -msgstr "Închid terminalele multiple?" +msgstr "Doriți să închideți multiple terminale?" #: ../terminatorlib/terminator.py:428 #, python-format @@ -282,94 +294,94 @@ msgid "" "This %s has several terminals open. Closing the %s will also close all " "terminals within it." msgstr "" -"Această %s are terminale deschise. Închizând %s deasemenea celelalte " -"terminale vor fi Închise." +"Această %s are mai multe terminale deschise. Închizând %s veți închide și " +"toate aceste terminale deschise." #: ../terminatorlib/terminatorterm.py:39 msgid "" "You need to install python bindings for libvte (\"python-vte\" in " "debian/ubuntu)" msgstr "" -"Trebuie instalată dependinţa \"python\" pentru \"libvte\" (\"python-vte\" " -"în debian/ubuntu)." +"Trebuie instalată dependenţa python pentru libvte („python-vte” în " +"debian/ubuntu)." #: ../terminatorlib/terminatorterm.py:133 msgid "Search:" -msgstr "Căutare:" +msgstr "Caută:" #. Button for the next result. Explicitly not show()n by default. #: ../terminatorlib/terminatorterm.py:150 msgid "Next" -msgstr "Următor" +msgstr "Următorul" #. Give up, we're completely stuck #: ../terminatorlib/terminatorterm.py:518 msgid "Unable to find a shell" -msgstr "Niciun interpretor de comenzi găsit" +msgstr "Nu s-a găsit niciun shell" #: ../terminatorlib/terminatorterm.py:539 msgid "Unable to start shell: " -msgstr "Imposibilitatea pornirii interpretorului de comenzi: " +msgstr "Nu s-a putut porni shellul: " #: ../terminatorlib/terminatorterm.py:1033 msgid "_Open Link" -msgstr "_Deschide link" +msgstr "_Deschide legătura" #: ../terminatorlib/terminatorterm.py:1034 msgid "_Copy Link Address" -msgstr "_Copiază adresa" +msgstr "_Copiază adresa legăturii" #: ../terminatorlib/terminatorterm.py:1044 msgid "_Send Mail To..." -msgstr "_Trimite E-mail la..." +msgstr "_Trimite e-mail către..." #: ../terminatorlib/terminatorterm.py:1045 msgid "_Copy Email Address" -msgstr "_Copiază adresa E-mail" +msgstr "_Copiază adresa e-mail" #: ../terminatorlib/terminatorterm.py:1071 msgid "Show _scrollbar" -msgstr "Arată _scrollbar" +msgstr "Arată _bara de derulare" #: ../terminatorlib/terminatorterm.py:1076 msgid "Show _titlebar" -msgstr "Arată bara _titlu" +msgstr "Arată bara de _titlu" #: ../terminatorlib/terminatorterm.py:1089 msgid "Split H_orizontally" -msgstr "Împarte _Orizontal" +msgstr "Împarte _orizontal" #: ../terminatorlib/terminatorterm.py:1090 msgid "Split V_ertically" -msgstr "Împarte V_ertical" +msgstr "Împarte v_ertical" #: ../terminatorlib/terminatorterm.py:1107 msgid "Open _Tab" -msgstr "Deschide _Tab" +msgstr "Deschide _tab" #: ../terminatorlib/terminatorterm.py:1112 msgid "Open _Debug Tab" -msgstr "Deschide Tab _Depanare" +msgstr "Deschide tab de _depanare" #: ../terminatorlib/terminatorterm.py:1118 msgid "Open Top Level Tab" -msgstr "Deschide Tab primul nivel" +msgstr "Deschide tab de primul nivel" #: ../terminatorlib/terminatorterm.py:1127 msgid "_Zoom terminal" -msgstr "_Zoom-ează terminalul" +msgstr "Panoramea_ză terminalul" #: ../terminatorlib/terminatorterm.py:1131 msgid "Ma_ximise terminal" -msgstr "Măreşte terminalul" +msgstr "Ma_ximizează terminalul" #: ../terminatorlib/terminatorterm.py:1136 msgid "_Unzoom terminal" -msgstr "_De-zoom-ează terminalul" +msgstr "Ad_u terminalul la mărimea inițială" #: ../terminatorlib/terminatorterm.py:1141 msgid "Unma_ximise terminal" -msgstr "De-măreşte terminalul" +msgstr "Dema_ximizează terminalul" #: ../terminatorlib/terminatorterm.py:1148 msgid "Ed_it profile" @@ -385,11 +397,11 @@ msgstr "Nimic" #: ../terminatorlib/terminatorterm.py:1194 msgid "_New group" -msgstr "_Nou grup" +msgstr "Grup _nou" #: ../terminatorlib/terminatorterm.py:1201 msgid "_Group all" -msgstr "_Grupează tot" +msgstr "_Grupează toate" #: ../terminatorlib/terminatorterm.py:1206 msgid "_Ungroup all" @@ -401,7 +413,7 @@ msgstr "Nume grup:" #: ../terminatorlib/terminatorterm.py:1262 msgid "All" -msgstr "Tot" +msgstr "Toate" #: ../terminatorlib/terminatorterm.py:1282 msgid "Encodings" @@ -418,14 +430,3 @@ msgstr "Terminale multiple într-o singură fereastră" #: ../data/terminator.desktop.in.h:2 msgid "Terminator" msgstr "Terminator" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" -"Nu sau găsit dependinţele python pentru panou, opţiunea hide_window nu este " -"disponibilă." - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "Nu s-a putut asocia tasta rapidă pentru hide_window" diff --git a/po/ru.po b/po/ru.po index 184ff75d..39e74fde 100644 --- a/po/ru.po +++ b/po/ru.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-04-11 12:06+0000\n" -"Last-Translator: ehpc \n" +"PO-Revision-Date: 2009-10-18 07:14+0000\n" +"Last-Translator: Alexey Kotlyarov \n" "Language-Team: Russian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -30,8 +30,8 @@ msgid "" "You need to run terminator in an X environment. Make sure DISPLAY is " "properly set" msgstr "" -"Вы должны запускать terminator в X режиме. Пожалуйста убедитесь, в том что " -"переменная DISPLAY установлена." +"Вы должны запускать terminator под X-системой. Убедитесь, что переменная " +"DISPLAY установлена." #: ../terminatorlib/configfile.py:96 msgid "Unterminated quoted string" @@ -58,7 +58,7 @@ msgid "" msgstr "" "Ошибка настройки\n" "\n" -"Ошибка была вызвана при разборе файла terminator_config(5):\n" +"При разборе файла terminator_config(5) произошли ошибки:\n" "\n" " %s\n" "\n" @@ -71,7 +71,7 @@ msgstr "Ошибка настройки" #: ../terminatorlib/config.py:311 #, python-format msgid "Setting %r value %r not a valid colour; ignoring" -msgstr "Устанавливаем %r значение %r не является цветом; игнорируем" +msgstr "Значение параметра %r - %r - не является цветом; игнорируем" #: ../terminatorlib/config.py:316 #, python-format @@ -105,41 +105,41 @@ msgstr "Текущая локаль" #: ../terminatorlib/encoding.py:62 ../terminatorlib/encoding.py:85 #: ../terminatorlib/encoding.py:96 msgid "Western" -msgstr "Западный" +msgstr "Западная" #: ../terminatorlib/encoding.py:31 ../terminatorlib/encoding.py:63 #: ../terminatorlib/encoding.py:75 ../terminatorlib/encoding.py:94 msgid "Central European" -msgstr "Центральноевропейский" +msgstr "Центральноевропейская" #: ../terminatorlib/encoding.py:32 msgid "South European" -msgstr "Южноевропейский" +msgstr "Южноевропейская" #: ../terminatorlib/encoding.py:33 ../terminatorlib/encoding.py:41 #: ../terminatorlib/encoding.py:101 msgid "Baltic" -msgstr "Балтийский" +msgstr "Прибалтийская" #: ../terminatorlib/encoding.py:34 ../terminatorlib/encoding.py:64 #: ../terminatorlib/encoding.py:70 ../terminatorlib/encoding.py:72 #: ../terminatorlib/encoding.py:77 ../terminatorlib/encoding.py:95 msgid "Cyrillic" -msgstr "Кириллический" +msgstr "Кириллическая" #: ../terminatorlib/encoding.py:35 ../terminatorlib/encoding.py:67 #: ../terminatorlib/encoding.py:74 ../terminatorlib/encoding.py:100 msgid "Arabic" -msgstr "Арабский" +msgstr "Арабская" #: ../terminatorlib/encoding.py:36 ../terminatorlib/encoding.py:80 #: ../terminatorlib/encoding.py:97 msgid "Greek" -msgstr "Греческий" +msgstr "Греческая" #: ../terminatorlib/encoding.py:37 msgid "Hebrew Visual" -msgstr "Визуальный иврит" +msgstr "Иврит (Визуальный)" #: ../terminatorlib/encoding.py:38 ../terminatorlib/encoding.py:66 #: ../terminatorlib/encoding.py:83 ../terminatorlib/encoding.py:99 @@ -149,19 +149,19 @@ msgstr "Иврит" #: ../terminatorlib/encoding.py:39 ../terminatorlib/encoding.py:65 #: ../terminatorlib/encoding.py:87 ../terminatorlib/encoding.py:98 msgid "Turkish" -msgstr "Турецкий" +msgstr "Турецкая" #: ../terminatorlib/encoding.py:40 msgid "Nordic" -msgstr "Скандинавский" +msgstr "Скандинавская" #: ../terminatorlib/encoding.py:42 msgid "Celtic" -msgstr "Кельтский" +msgstr "Кельтская" #: ../terminatorlib/encoding.py:44 ../terminatorlib/encoding.py:86 msgid "Romanian" -msgstr "Румынский" +msgstr "Румынская" #: ../terminatorlib/encoding.py:45 ../terminatorlib/encoding.py:46 #: ../terminatorlib/encoding.py:47 ../terminatorlib/encoding.py:48 @@ -171,26 +171,26 @@ msgstr "Юникод" #: ../terminatorlib/encoding.py:50 msgid "Armenian" -msgstr "Армянский" +msgstr "Армянская" #: ../terminatorlib/encoding.py:51 ../terminatorlib/encoding.py:52 #: ../terminatorlib/encoding.py:56 msgid "Chinese Traditional" -msgstr "Китайский Традиционный" +msgstr "Традиционная китайская" #: ../terminatorlib/encoding.py:53 msgid "Cyrillic/Russian" -msgstr "Кириллица/Русский" +msgstr "Кириллица/Россия" #: ../terminatorlib/encoding.py:54 ../terminatorlib/encoding.py:68 #: ../terminatorlib/encoding.py:89 msgid "Japanese" -msgstr "Японский" +msgstr "Японская" #: ../terminatorlib/encoding.py:55 ../terminatorlib/encoding.py:69 #: ../terminatorlib/encoding.py:71 ../terminatorlib/encoding.py:92 msgid "Korean" -msgstr "Корейский" +msgstr "Корейская" #: ../terminatorlib/encoding.py:57 ../terminatorlib/encoding.py:58 #: ../terminatorlib/encoding.py:59 ../terminatorlib/encoding.py:61 @@ -199,15 +199,15 @@ msgstr "Упрощенная китайская" #: ../terminatorlib/encoding.py:60 msgid "Georgian" -msgstr "Грузинский" +msgstr "Грузинская" #: ../terminatorlib/encoding.py:73 ../terminatorlib/encoding.py:88 msgid "Cyrillic/Ukrainian" -msgstr "Кириллица/Украинский" +msgstr "Кириллица/Украина" #: ../terminatorlib/encoding.py:76 msgid "Croatian" -msgstr "Хорватский" +msgstr "Хорватская" #: ../terminatorlib/encoding.py:78 msgid "Hindi" @@ -215,7 +215,7 @@ msgstr "Хинди" #: ../terminatorlib/encoding.py:79 msgid "Persian" -msgstr "Персидский" +msgstr "Персидская" #: ../terminatorlib/encoding.py:81 msgid "Gujarati" @@ -319,11 +319,11 @@ msgstr "Следующее" #. Give up, we're completely stuck #: ../terminatorlib/terminatorterm.py:518 msgid "Unable to find a shell" -msgstr "Не удается найти оболочку(shell)" +msgstr "Не удается найти оболочку (shell)" #: ../terminatorlib/terminatorterm.py:539 msgid "Unable to start shell: " -msgstr "Не удается запустить оболочку " +msgstr "Не удается запустить оболочку: " #: ../terminatorlib/terminatorterm.py:1033 msgid "_Open Link" @@ -343,7 +343,7 @@ msgstr "_Копировать почтовый адрес" #: ../terminatorlib/terminatorterm.py:1071 msgid "Show _scrollbar" -msgstr "Показать прокрутку" +msgstr "Показать полосу прокрутки" #: ../terminatorlib/terminatorterm.py:1076 msgid "Show _titlebar" @@ -367,7 +367,7 @@ msgstr "Открыть отла_дочную вкладку" #: ../terminatorlib/terminatorterm.py:1118 msgid "Open Top Level Tab" -msgstr "Открыть верхнюю вкладку" +msgstr "Открыть вкладку верхнего уровня" #: ../terminatorlib/terminatorterm.py:1127 msgid "_Zoom terminal" diff --git a/po/si.po b/po/si.po index b24f974c..fcdbfb5a 100644 --- a/po/si.po +++ b/po/si.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 diff --git a/po/sk.po b/po/sk.po index d385edab..dbac3af5 100644 --- a/po/sk.po +++ b/po/sk.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-03-30 22:32+0000\n" +"PO-Revision-Date: 2009-06-26 13:09+0000\n" "Last-Translator: helix84 \n" "Language-Team: Slovak \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -250,6 +250,12 @@ msgstr "Operácia" msgid "Keyboard shortcut" msgstr "Klávesová skratka" +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" +"Nepodarilo sa nájsť väzby Pythonu pre deskbar, hide_window nie je dostupné." + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "Zatvoriť záložku" @@ -263,6 +269,10 @@ msgstr "záložka" msgid "Invalid geometry string %r" msgstr "Chybný reťazec geometrie %r" +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "" + #: ../terminatorlib/terminator.py:413 msgid "window" msgstr "okno" @@ -414,19 +424,10 @@ msgstr "Kódovania" msgid "Other Encodings" msgstr "Iné kódovania" -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" -msgstr "" +msgstr "Viaceré terminály v jednom okne" #: ../data/terminator.desktop.in.h:2 msgid "Terminator" -msgstr "" +msgstr "Terminátor" diff --git a/po/sq.po b/po/sq.po index 87c3c09b..5dde5721 100644 --- a/po/sq.po +++ b/po/sq.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2008-08-15 22:28+0000\n" -"Last-Translator: mirena \n" +"PO-Revision-Date: 2009-09-03 21:07+0000\n" +"Last-Translator: Vilson Gjeci \n" "Language-Team: Albanian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -33,6 +33,59 @@ msgstr "" "Terminator duhet ne nje X ambient te startoi. Sigurohuni qe DISPLAY te jete " "korrekt i rregulluar." +#: ../terminatorlib/configfile.py:96 +msgid "Unterminated quoted string" +msgstr "" + +#: ../terminatorlib/configfile.py:147 +msgid "Setting without a value" +msgstr "" + +#: ../terminatorlib/configfile.py:152 +msgid "Unexpected token" +msgstr "" + +#: ../terminatorlib/config.py:250 +#, python-format +msgid "" +"Configuration error\n" +"\n" +"Errors were encountered while parsing terminator_config(5) file:\n" +"\n" +" %s\n" +"\n" +"%d line(s) have been ignored." +msgstr "" + +#: ../terminatorlib/config.py:258 +msgid "Configuration error" +msgstr "" + +#: ../terminatorlib/config.py:311 +#, python-format +msgid "Setting %r value %r not a valid colour; ignoring" +msgstr "" + +#: ../terminatorlib/config.py:316 +#, python-format +msgid "%s must be one of: top, left, right, bottom" +msgstr "" + +#: ../terminatorlib/config.py:323 +#, python-format +msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" +msgstr "" + +#: ../terminatorlib/config.py:329 +msgid "" +"Reading list values from terminator_config(5) is not currently supported" +msgstr "" + +#: ../terminatorlib/config.py:332 +#, python-format +msgid "Setting %r should be a section name" +msgstr "" + #: ../terminatorlib/encoding.py:29 msgid "Current Locale" msgstr "Parametrat lokal te perdorur aktualisht." @@ -41,7 +94,7 @@ msgstr "Parametrat lokal te perdorur aktualisht." #: ../terminatorlib/encoding.py:62 ../terminatorlib/encoding.py:85 #: ../terminatorlib/encoding.py:96 msgid "Western" -msgstr "Westeuropäisch" +msgstr "Perëndimore" #: ../terminatorlib/encoding.py:31 ../terminatorlib/encoding.py:63 #: ../terminatorlib/encoding.py:75 ../terminatorlib/encoding.py:94 @@ -145,6 +198,10 @@ msgstr "Qirilike/Ukraina" msgid "Croatian" msgstr "Kroate" +#: ../terminatorlib/encoding.py:78 +msgid "Hindi" +msgstr "" + #: ../terminatorlib/encoding.py:79 msgid "Persian" msgstr "Persishte" @@ -157,6 +214,10 @@ msgstr "Gujarati" msgid "Gurmukhi" msgstr "Gurmuki" +#: ../terminatorlib/encoding.py:84 +msgid "Icelandic" +msgstr "" + #: ../terminatorlib/encoding.py:90 ../terminatorlib/encoding.py:93 #: ../terminatorlib/encoding.py:102 msgid "Vietnamese" @@ -166,84 +227,6 @@ msgstr "Vietnameze" msgid "Thai" msgstr "Tai" -#: ../terminatorlib/terminator.py:76 -msgid "Close Tab" -msgstr "Mbylle skedën" - -#. show dialog -#: ../terminatorlib/terminator.py:417 -msgid "Close?" -msgstr "Mbylle" - -#: ../terminatorlib/terminatorterm.py:1034 -msgid "_Copy Link Address" -msgstr "Link-Adresse _kopieren" - -#: ../terminatorlib/terminatorterm.py:1044 -msgid "_Send Mail To..." -msgstr "_Dërgo Mail tek..." - -#: ../terminatorlib/configfile.py:96 -msgid "Unterminated quoted string" -msgstr "" - -#: ../terminatorlib/configfile.py:147 -msgid "Setting without a value" -msgstr "" - -#: ../terminatorlib/configfile.py:152 -msgid "Unexpected token" -msgstr "" - -#: ../terminatorlib/config.py:250 -#, python-format -msgid "" -"Configuration error\n" -"\n" -"Errors were encountered while parsing terminator_config(5) file:\n" -"\n" -" %s\n" -"\n" -"%d line(s) have been ignored." -msgstr "" - -#: ../terminatorlib/config.py:258 -msgid "Configuration error" -msgstr "" - -#: ../terminatorlib/config.py:311 -#, python-format -msgid "Setting %r value %r not a valid colour; ignoring" -msgstr "" - -#: ../terminatorlib/config.py:316 -#, python-format -msgid "%s must be one of: top, left, right, bottom" -msgstr "" - -#: ../terminatorlib/config.py:323 -#, python-format -msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" -msgstr "" - -#: ../terminatorlib/config.py:329 -msgid "" -"Reading list values from terminator_config(5) is not currently supported" -msgstr "" - -#: ../terminatorlib/config.py:332 -#, python-format -msgid "Setting %r should be a section name" -msgstr "" - -#: ../terminatorlib/encoding.py:78 -msgid "Hindi" -msgstr "" - -#: ../terminatorlib/encoding.py:84 -msgid "Icelandic" -msgstr "" - #: ../terminatorlib/prefs_profile.py:370 msgid "Name" msgstr "" @@ -261,6 +244,10 @@ msgid "" "Unable to find python bindings for deskbar, hide_window is not available." msgstr "" +#: ../terminatorlib/terminator.py:76 +msgid "Close Tab" +msgstr "Mbylle skedën" + #: ../terminatorlib/terminator.py:105 msgid "tab" msgstr "" @@ -278,6 +265,11 @@ msgstr "" msgid "window" msgstr "" +#. show dialog +#: ../terminatorlib/terminator.py:417 +msgid "Close?" +msgstr "Mbylle" + #: ../terminatorlib/terminator.py:423 msgid "Close _Terminals" msgstr "" @@ -321,6 +313,14 @@ msgstr "" msgid "_Open Link" msgstr "" +#: ../terminatorlib/terminatorterm.py:1034 +msgid "_Copy Link Address" +msgstr "Link-Adresse _kopieren" + +#: ../terminatorlib/terminatorterm.py:1044 +msgid "_Send Mail To..." +msgstr "_Dërgo Mail tek..." + #: ../terminatorlib/terminatorterm.py:1045 msgid "_Copy Email Address" msgstr "" diff --git a/po/sr.po b/po/sr.po index 8f7c01c9..d2e6dcd0 100644 --- a/po/sr.po +++ b/po/sr.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 diff --git a/po/sv.po b/po/sv.po index c497ff6a..414fa172 100644 --- a/po/sv.po +++ b/po/sv.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-05-28 19:35+0000\n" -"Last-Translator: Christian Widell \n" +"PO-Revision-Date: 2009-08-19 09:50+0000\n" +"Last-Translator: Simon Österberg \n" "Language-Team: Swedish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -68,6 +68,11 @@ msgstr "" msgid "Configuration error" msgstr "Konfigurationsfel" +#: ../terminatorlib/config.py:311 +#, python-format +msgid "Setting %r value %r not a valid colour; ignoring" +msgstr "" + #: ../terminatorlib/config.py:316 #, python-format msgid "%s must be one of: top, left, right, bottom" @@ -80,6 +85,16 @@ msgstr "" "Booleansk inställning %s en av följande är förväntad: yes, no, true, false, " "on, off" +#: ../terminatorlib/config.py:329 +msgid "" +"Reading list values from terminator_config(5) is not currently supported" +msgstr "" + +#: ../terminatorlib/config.py:332 +#, python-format +msgid "Setting %r should be a section name" +msgstr "" + #: ../terminatorlib/encoding.py:29 msgid "Current Locale" msgstr "Aktuell lokal" @@ -233,6 +248,11 @@ msgstr "Åtgärd" msgid "Keyboard shortcut" msgstr "Kortkommando" +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "Stäng flik" @@ -241,6 +261,15 @@ msgstr "Stäng flik" msgid "tab" msgstr "flik" +#: ../terminatorlib/terminator.py:230 +#, python-format +msgid "Invalid geometry string %r" +msgstr "Ogiltig geometristräng %r" + +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "" + #: ../terminatorlib/terminator.py:413 msgid "window" msgstr "fönster" @@ -254,6 +283,17 @@ msgstr "Stäng?" msgid "Close _Terminals" msgstr "Stäng _terminaler" +#: ../terminatorlib/terminator.py:425 +msgid "Close multiple terminals?" +msgstr "Stäng flera terminaler?" + +#: ../terminatorlib/terminator.py:428 +#, python-format +msgid "" +"This %s has several terminals open. Closing the %s will also close all " +"terminals within it." +msgstr "" + #: ../terminatorlib/terminatorterm.py:39 msgid "" "You need to install python bindings for libvte (\"python-vte\" in " @@ -328,10 +368,18 @@ msgstr "Öppna toppnivåflik" msgid "_Zoom terminal" msgstr "_Zooma in terminal" +#: ../terminatorlib/terminatorterm.py:1131 +msgid "Ma_ximise terminal" +msgstr "" + #: ../terminatorlib/terminatorterm.py:1136 msgid "_Unzoom terminal" msgstr "Zooma _ut terminal" +#: ../terminatorlib/terminatorterm.py:1141 +msgid "Unma_ximise terminal" +msgstr "" + #: ../terminatorlib/terminatorterm.py:1148 msgid "Ed_it profile" msgstr "Red_igera profil" @@ -352,6 +400,10 @@ msgstr "_Ny grupp" msgid "_Group all" msgstr "_Gruppera alla" +#: ../terminatorlib/terminatorterm.py:1206 +msgid "_Ungroup all" +msgstr "" + #: ../terminatorlib/terminatorterm.py:1216 msgid "Group name:" msgstr "Gruppnamn:" @@ -368,61 +420,9 @@ msgstr "Teckenkodningar" msgid "Other Encodings" msgstr "Övriga teckenkodningar" -#: ../terminatorlib/config.py:311 -#, python-format -msgid "Setting %r value %r not a valid colour; ignoring" -msgstr "" - -#: ../terminatorlib/config.py:329 -msgid "" -"Reading list values from terminator_config(5) is not currently supported" -msgstr "" - -#: ../terminatorlib/config.py:332 -#, python-format -msgid "Setting %r should be a section name" -msgstr "" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:230 -#, python-format -msgid "Invalid geometry string %r" -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - -#: ../terminatorlib/terminator.py:425 -msgid "Close multiple terminals?" -msgstr "" - -#: ../terminatorlib/terminator.py:428 -#, python-format -msgid "" -"This %s has several terminals open. Closing the %s will also close all " -"terminals within it." -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1131 -msgid "Ma_ximise terminal" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1141 -msgid "Unma_ximise terminal" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:1206 -msgid "_Ungroup all" -msgstr "" - #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" -msgstr "" +msgstr "Flera terminaler i ett fönster" #: ../data/terminator.desktop.in.h:2 msgid "Terminator" diff --git a/po/ta.po b/po/ta.po index 37daafd8..28ff74fb 100644 --- a/po/ta.po +++ b/po/ta.po @@ -14,14 +14,9 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" -#: ../terminatorlib/encoding.py:36 ../terminatorlib/encoding.py:80 -#: ../terminatorlib/encoding.py:97 -msgid "Greek" -msgstr "கிரேக்கம்" - #: ../terminator:46 msgid "" "You need to install the python bindings for gobject, gtk and pango to run " @@ -122,6 +117,11 @@ msgstr "" msgid "Arabic" msgstr "" +#: ../terminatorlib/encoding.py:36 ../terminatorlib/encoding.py:80 +#: ../terminatorlib/encoding.py:97 +msgid "Greek" +msgstr "கிரேக்கம்" + #: ../terminatorlib/encoding.py:37 msgid "Hebrew Visual" msgstr "" diff --git a/po/te.po b/po/te.po index ef674b60..47818ac1 100644 --- a/po/te.po +++ b/po/te.po @@ -14,22 +14,9 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" -#. show dialog -#: ../terminatorlib/terminator.py:417 -msgid "Close?" -msgstr "మూసివేయాలా?" - -#: ../terminatorlib/terminatorterm.py:1033 -msgid "_Open Link" -msgstr "_లింకు తెరువు" - -#: ../terminatorlib/terminatorterm.py:1034 -msgid "_Copy Link Address" -msgstr "_లింకు చిరునామా కాపీచెయ్యి" - #: ../terminator:46 msgid "" "You need to install the python bindings for gobject, gtk and pango to run " @@ -274,6 +261,11 @@ msgstr "" msgid "window" msgstr "" +#. show dialog +#: ../terminatorlib/terminator.py:417 +msgid "Close?" +msgstr "మూసివేయాలా?" + #: ../terminatorlib/terminator.py:423 msgid "Close _Terminals" msgstr "" @@ -313,6 +305,14 @@ msgstr "" msgid "Unable to start shell: " msgstr "" +#: ../terminatorlib/terminatorterm.py:1033 +msgid "_Open Link" +msgstr "_లింకు తెరువు" + +#: ../terminatorlib/terminatorterm.py:1034 +msgid "_Copy Link Address" +msgstr "_లింకు చిరునామా కాపీచెయ్యి" + #: ../terminatorlib/terminatorterm.py:1044 msgid "_Send Mail To..." msgstr "" diff --git a/po/tr.po b/po/tr.po index 384aaa22..9dbe5f3f 100644 --- a/po/tr.po +++ b/po/tr.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-04-29 13:04+0000\n" -"Last-Translator: Osman Tosun \n" +"PO-Revision-Date: 2009-10-02 18:27+0000\n" +"Last-Translator: aylincan \n" "Language-Team: Turkish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -33,6 +33,10 @@ msgstr "" "Terminator'u bir X ortamında çalıştırmanız gerekmektedir. DISPLAY " "değişkeninin uygun şekilde ayarlandığına emin olunuz." +#: ../terminatorlib/configfile.py:96 +msgid "Unterminated quoted string" +msgstr "Sınırsız yineleyen dizi" + #: ../terminatorlib/configfile.py:147 msgid "Setting without a value" msgstr "değer atanmamış" @@ -86,6 +90,11 @@ msgid "" msgstr "" "terminator_config(5)'ten liste değerleri okuması şu anda desteklenmiyor" +#: ../terminatorlib/config.py:332 +#, python-format +msgid "Setting %r should be a section name" +msgstr "" + #: ../terminatorlib/encoding.py:29 msgid "Current Locale" msgstr "Geçerli Yerel" @@ -239,6 +248,11 @@ msgstr "Eylem" msgid "Keyboard shortcut" msgstr "Klavye kısayolu" +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "Sekmeyi Kapat" @@ -252,6 +266,10 @@ msgstr "sekme" msgid "Invalid geometry string %r" msgstr "Geçersiz geometri katarı: %r" +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "" + #: ../terminatorlib/terminator.py:413 msgid "window" msgstr "pencere" @@ -269,6 +287,13 @@ msgstr "_Terminalleri Kapat" msgid "Close multiple terminals?" msgstr "Birden çok uçbirim kapatılsın mı?" +#: ../terminatorlib/terminator.py:428 +#, python-format +msgid "" +"This %s has several terminals open. Closing the %s will also close all " +"terminals within it." +msgstr "" + #: ../terminatorlib/terminatorterm.py:39 msgid "" "You need to install python bindings for libvte (\"python-vte\" in " @@ -402,28 +427,3 @@ msgstr "Tek pencerede birden çok uçbirim" #: ../data/terminator.desktop.in.h:2 msgid "Terminator" msgstr "Terminator" - -#: ../terminatorlib/configfile.py:96 -msgid "Unterminated quoted string" -msgstr "" - -#: ../terminatorlib/config.py:332 -#, python-format -msgid "Setting %r should be a section name" -msgstr "" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - -#: ../terminatorlib/terminator.py:428 -#, python-format -msgid "" -"This %s has several terminals open. Closing the %s will also close all " -"terminals within it." -msgstr "" diff --git a/po/uk.po b/po/uk.po index 6f95cead..363f013b 100644 --- a/po/uk.po +++ b/po/uk.po @@ -8,15 +8,94 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2008-07-13 16:15+0000\n" -"Last-Translator: Serhey Kusyumoff (Сергій Кусюмов) \n" +"PO-Revision-Date: 2009-09-30 19:47+0000\n" +"Last-Translator: Сергій Матрунчик (SkyMan) \n" "Language-Team: Ukrainian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" +#: ../terminator:46 +msgid "" +"You need to install the python bindings for gobject, gtk and pango to run " +"Terminator." +msgstr "" +"Вам необхідно встановити прив'язки python до gobject, gtk та pango для " +"того, щоб запустити Terminator." + +#: ../terminator:129 +msgid "" +"You need to run terminator in an X environment. Make sure DISPLAY is " +"properly set" +msgstr "" +"Terminator необхідно запускати у середовищі X. Перевірте, чи змінна DISPLAY " +"встановлена правильно." + +#: ../terminatorlib/configfile.py:96 +msgid "Unterminated quoted string" +msgstr "Незавершений текст у лапках" + +#: ../terminatorlib/configfile.py:147 +msgid "Setting without a value" +msgstr "Параметр без значення" + +#: ../terminatorlib/configfile.py:152 +msgid "Unexpected token" +msgstr "Неочікувана лексема" + +#: ../terminatorlib/config.py:250 +#, python-format +msgid "" +"Configuration error\n" +"\n" +"Errors were encountered while parsing terminator_config(5) file:\n" +"\n" +" %s\n" +"\n" +"%d line(s) have been ignored." +msgstr "" +"Помилка конфігурації\n" +"\n" +"Під час обробки файлу terminator_config(5) трапилася помилка:\n" +"\n" +" %s\n" +"\n" +"%d рядок(ів) було пропущено." + +#: ../terminatorlib/config.py:258 +msgid "Configuration error" +msgstr "Помилка конфігурації" + +#: ../terminatorlib/config.py:311 +#, python-format +msgid "Setting %r value %r not a valid colour; ignoring" +msgstr "Встановлення %r значення %r неможливе; ігнорується" + +#: ../terminatorlib/config.py:316 +#, python-format +msgid "%s must be one of: top, left, right, bottom" +msgstr "%s має бути одним з: зверху, ліворуч, праворуч, знизу" + +#: ../terminatorlib/config.py:323 +#, python-format +msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" +msgstr "" +"Логічний параметр %s повинен мати одне з наступних значень: yes, no, true, " +"false, on, off" + +#: ../terminatorlib/config.py:329 +msgid "" +"Reading list values from terminator_config(5) is not currently supported" +msgstr "" +"Читання параметрів-списків з terminator_config(5) наразі не підтримується" + +#: ../terminatorlib/config.py:332 +#, python-format +msgid "Setting %r should be a section name" +msgstr "Параметр %r має бути назвою розділу" + #: ../terminatorlib/encoding.py:29 msgid "Current Locale" msgstr "Поточна локалізація" @@ -158,15 +237,93 @@ msgstr "В'єтнамська" msgid "Thai" msgstr "Тайська" +#: ../terminatorlib/prefs_profile.py:370 +msgid "Name" +msgstr "Ім'я" + +#: ../terminatorlib/prefs_profile.py:377 +msgid "Action" +msgstr "Дія" + +#: ../terminatorlib/prefs_profile.py:384 +msgid "Keyboard shortcut" +msgstr "Комбінація клавіш" + +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" +"Не вдалось знайти прив'язку deskbar до мови python, hide_window не доступний." + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "Закрити вкладку" +#: ../terminatorlib/terminator.py:105 +msgid "tab" +msgstr "табуляція" + +#: ../terminatorlib/terminator.py:230 +#, python-format +msgid "Invalid geometry string %r" +msgstr "Неправильний рядок геометрії %r" + +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "Не вдалось назначити клавішу для hide_window" + +#: ../terminatorlib/terminator.py:413 +msgid "window" +msgstr "вікно" + #. show dialog #: ../terminatorlib/terminator.py:417 msgid "Close?" msgstr "Закрити?" +#: ../terminatorlib/terminator.py:423 +msgid "Close _Terminals" +msgstr "Закрити _Термінали" + +#: ../terminatorlib/terminator.py:425 +msgid "Close multiple terminals?" +msgstr "Закрити декілька терміналів?" + +#: ../terminatorlib/terminator.py:428 +#, python-format +msgid "" +"This %s has several terminals open. Closing the %s will also close all " +"terminals within it." +msgstr "" +"%s має декілька відкритих терміналів. Закриття %s також закриє всі " +"термінали в ньому." + +#: ../terminatorlib/terminatorterm.py:39 +msgid "" +"You need to install python bindings for libvte (\"python-vte\" in " +"debian/ubuntu)" +msgstr "" +"Необхідно встановити прив'язки python для libvte (\"python-vte\" в " +"debian/ubuntu)" + +#: ../terminatorlib/terminatorterm.py:133 +msgid "Search:" +msgstr "Знайти:" + +#. Button for the next result. Explicitly not show()n by default. +#: ../terminatorlib/terminatorterm.py:150 +msgid "Next" +msgstr "Наступне" + +#. Give up, we're completely stuck +#: ../terminatorlib/terminatorterm.py:518 +msgid "Unable to find a shell" +msgstr "Не вдалося знайти командну оболонку" + +#: ../terminatorlib/terminatorterm.py:539 +msgid "Unable to start shell: " +msgstr "Не вдалося запустити командну оболонку: " + #: ../terminatorlib/terminatorterm.py:1033 msgid "_Open Link" msgstr "_Відкрити посилання" @@ -203,212 +360,74 @@ msgstr "Розділити вертикально" msgid "Open _Tab" msgstr "Відкрити в_кладку" -#: ../terminator:46 -msgid "" -"You need to install the python bindings for gobject, gtk and pango to run " -"Terminator." -msgstr "" - -#: ../terminator:129 -msgid "" -"You need to run terminator in an X environment. Make sure DISPLAY is " -"properly set" -msgstr "" - -#: ../terminatorlib/configfile.py:96 -msgid "Unterminated quoted string" -msgstr "" - -#: ../terminatorlib/configfile.py:147 -msgid "Setting without a value" -msgstr "" - -#: ../terminatorlib/configfile.py:152 -msgid "Unexpected token" -msgstr "" - -#: ../terminatorlib/config.py:250 -#, python-format -msgid "" -"Configuration error\n" -"\n" -"Errors were encountered while parsing terminator_config(5) file:\n" -"\n" -" %s\n" -"\n" -"%d line(s) have been ignored." -msgstr "" - -#: ../terminatorlib/config.py:258 -msgid "Configuration error" -msgstr "" - -#: ../terminatorlib/config.py:311 -#, python-format -msgid "Setting %r value %r not a valid colour; ignoring" -msgstr "" - -#: ../terminatorlib/config.py:316 -#, python-format -msgid "%s must be one of: top, left, right, bottom" -msgstr "" - -#: ../terminatorlib/config.py:323 -#, python-format -msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" -msgstr "" - -#: ../terminatorlib/config.py:329 -msgid "" -"Reading list values from terminator_config(5) is not currently supported" -msgstr "" - -#: ../terminatorlib/config.py:332 -#, python-format -msgid "Setting %r should be a section name" -msgstr "" - -#: ../terminatorlib/prefs_profile.py:370 -msgid "Name" -msgstr "" - -#: ../terminatorlib/prefs_profile.py:377 -msgid "Action" -msgstr "" - -#: ../terminatorlib/prefs_profile.py:384 -msgid "Keyboard shortcut" -msgstr "" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:105 -msgid "tab" -msgstr "" - -#: ../terminatorlib/terminator.py:230 -#, python-format -msgid "Invalid geometry string %r" -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - -#: ../terminatorlib/terminator.py:413 -msgid "window" -msgstr "" - -#: ../terminatorlib/terminator.py:423 -msgid "Close _Terminals" -msgstr "" - -#: ../terminatorlib/terminator.py:425 -msgid "Close multiple terminals?" -msgstr "" - -#: ../terminatorlib/terminator.py:428 -#, python-format -msgid "" -"This %s has several terminals open. Closing the %s will also close all " -"terminals within it." -msgstr "" - -#: ../terminatorlib/terminatorterm.py:39 -msgid "" -"You need to install python bindings for libvte (\"python-vte\" in " -"debian/ubuntu)" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:133 -msgid "Search:" -msgstr "" - -#. Button for the next result. Explicitly not show()n by default. -#: ../terminatorlib/terminatorterm.py:150 -msgid "Next" -msgstr "" - -#. Give up, we're completely stuck -#: ../terminatorlib/terminatorterm.py:518 -msgid "Unable to find a shell" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:539 -msgid "Unable to start shell: " -msgstr "" - #: ../terminatorlib/terminatorterm.py:1112 msgid "Open _Debug Tab" -msgstr "" +msgstr "Відкрити відла_годжувальну вкладку" #: ../terminatorlib/terminatorterm.py:1118 msgid "Open Top Level Tab" -msgstr "" +msgstr "Відкрити вкладку верхнього рівня" #: ../terminatorlib/terminatorterm.py:1127 msgid "_Zoom terminal" -msgstr "" +msgstr "_Збільшити термінал" #: ../terminatorlib/terminatorterm.py:1131 msgid "Ma_ximise terminal" -msgstr "" +msgstr "Розгорнути термінал" #: ../terminatorlib/terminatorterm.py:1136 msgid "_Unzoom terminal" -msgstr "" +msgstr "Віддал_ити термінал" #: ../terminatorlib/terminatorterm.py:1141 msgid "Unma_ximise terminal" -msgstr "" +msgstr "Повернути термінал" #: ../terminatorlib/terminatorterm.py:1148 msgid "Ed_it profile" -msgstr "" +msgstr "Редагувати профіль" #: ../terminatorlib/terminatorterm.py:1155 msgid "_Group" -msgstr "" +msgstr "З_групувати" #: ../terminatorlib/terminatorterm.py:1179 msgid "None" -msgstr "" +msgstr "Немає" #: ../terminatorlib/terminatorterm.py:1194 msgid "_New group" -msgstr "" +msgstr "Нова группа" #: ../terminatorlib/terminatorterm.py:1201 msgid "_Group all" -msgstr "" +msgstr "З_групувати усе" #: ../terminatorlib/terminatorterm.py:1206 msgid "_Ungroup all" -msgstr "" +msgstr "_Розгрупувати усе" #: ../terminatorlib/terminatorterm.py:1216 msgid "Group name:" -msgstr "" +msgstr "Назва групи:" #: ../terminatorlib/terminatorterm.py:1262 msgid "All" -msgstr "" +msgstr "Усе" #: ../terminatorlib/terminatorterm.py:1282 msgid "Encodings" -msgstr "" +msgstr "Кодування" #: ../terminatorlib/terminatorterm.py:1300 msgid "Other Encodings" -msgstr "" +msgstr "Інше кодування" #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" -msgstr "" +msgstr "Множинні термінали в одному вікні" #: ../data/terminator.desktop.in.h:2 msgid "Terminator" -msgstr "" +msgstr "Термінатор" diff --git a/po/zh_CN.po b/po/zh_CN.po index 296009b8..5fcafeba 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: terminator\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-01-26 13:22+0000\n" -"PO-Revision-Date: 2009-06-07 10:30+0000\n" -"Last-Translator: Careone \n" +"PO-Revision-Date: 2009-08-26 13:05+0000\n" +"Last-Translator: Alexey Kotlyarov \n" "Language-Team: Simplified Chinese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -27,7 +27,7 @@ msgstr "你需要安装gobject,gtk和pango的python绑定来运行Terminator msgid "" "You need to run terminator in an X environment. Make sure DISPLAY is " "properly set" -msgstr "你需要在一个X环境中运行terminator。请确保显示器已正确设置" +msgstr "你需要在一个X环境中运行terminator。请确保DISPLAY已正确设置" #: ../terminatorlib/configfile.py:96 msgid "Unterminated quoted string" @@ -239,12 +239,30 @@ msgstr "行为" #: ../terminatorlib/prefs_profile.py:384 msgid "Keyboard shortcut" -msgstr "键盘快捷键" +msgstr "快捷键" + +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "找不到桌面栏的 python 绑定, hide_window 不可用。" #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "关闭标签" +#: ../terminatorlib/terminator.py:105 +msgid "tab" +msgstr "标签页" + +#: ../terminatorlib/terminator.py:230 +#, python-format +msgid "Invalid geometry string %r" +msgstr "无效的几何尺寸 %r" + +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "不能绑定 hide_window 快捷键 (_W)" + #: ../terminatorlib/terminator.py:413 msgid "window" msgstr "窗口" @@ -254,6 +272,21 @@ msgstr "窗口" msgid "Close?" msgstr "确定关闭?" +#: ../terminatorlib/terminator.py:423 +msgid "Close _Terminals" +msgstr "关闭终端 (_T)" + +#: ../terminatorlib/terminator.py:425 +msgid "Close multiple terminals?" +msgstr "关闭多个终端?" + +#: ../terminatorlib/terminator.py:428 +#, python-format +msgid "" +"This %s has several terminals open. Closing the %s will also close all " +"terminals within it." +msgstr "%s 已打开多个终端。关闭 %s 的同时会关闭它打开的全部终端。" + #: ../terminatorlib/terminatorterm.py:39 msgid "" "You need to install python bindings for libvte (\"python-vte\" in " @@ -292,7 +325,7 @@ msgstr "发送邮件给(_S)..." #: ../terminatorlib/terminatorterm.py:1045 msgid "_Copy Email Address" -msgstr "复制Email地址(_C)" +msgstr "复制电子邮件地址(_C)" #: ../terminatorlib/terminatorterm.py:1071 msgid "Show _scrollbar" @@ -346,59 +379,14 @@ msgstr "修改档案(_d)" msgid "_Group" msgstr "编组(_G)" -#: ../terminatorlib/terminatorterm.py:1194 -msgid "_New group" -msgstr "新组(_N)" - -#: ../terminatorlib/terminatorterm.py:1282 -msgid "Encodings" -msgstr "编码" - -#: ../terminatorlib/terminatorterm.py:1300 -msgid "Other Encodings" -msgstr "其他编码" - -#: ../data/terminator.desktop.in.h:2 -msgid "Terminator" -msgstr "Terminator" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "找不到桌面栏的 python 绑定, 隐藏窗口不可用。" - -#: ../terminatorlib/terminator.py:105 -msgid "tab" -msgstr "标签页" - -#: ../terminatorlib/terminator.py:230 -#, python-format -msgid "Invalid geometry string %r" -msgstr "无效的几何尺寸 %r" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "不能绑定 “隐藏窗口” 的快捷键 (_W)" - -#: ../terminatorlib/terminator.py:423 -msgid "Close _Terminals" -msgstr "关闭终端 (_T)" - -#: ../terminatorlib/terminator.py:425 -msgid "Close multiple terminals?" -msgstr "关闭多个终端?" - -#: ../terminatorlib/terminator.py:428 -#, python-format -msgid "" -"This %s has several terminals open. Closing the %s will also close all " -"terminals within it." -msgstr "%s 已打开多个终端。关闭 %s 的同时会关闭它打开的全部终端。" - #: ../terminatorlib/terminatorterm.py:1179 msgid "None" msgstr "无" +#: ../terminatorlib/terminatorterm.py:1194 +msgid "_New group" +msgstr "新组(_N)" + #: ../terminatorlib/terminatorterm.py:1201 msgid "_Group all" msgstr "全部归组(_G)" @@ -415,6 +403,18 @@ msgstr "分组名:" msgid "All" msgstr "全部" +#: ../terminatorlib/terminatorterm.py:1282 +msgid "Encodings" +msgstr "编码" + +#: ../terminatorlib/terminatorterm.py:1300 +msgid "Other Encodings" +msgstr "其他编码" + #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" msgstr "一个窗口里多个终端" + +#: ../data/terminator.desktop.in.h:2 +msgid "Terminator" +msgstr "Terminator" diff --git a/po/zh_HK.po b/po/zh_HK.po index e342cf3e..0a13e68c 100644 --- a/po/zh_HK.po +++ b/po/zh_HK.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -29,6 +29,59 @@ msgid "" "properly set" msgstr "你要在 X 環境下執行 terminator。請確定你的顯示正確設置" +#: ../terminatorlib/configfile.py:96 +msgid "Unterminated quoted string" +msgstr "" + +#: ../terminatorlib/configfile.py:147 +msgid "Setting without a value" +msgstr "" + +#: ../terminatorlib/configfile.py:152 +msgid "Unexpected token" +msgstr "" + +#: ../terminatorlib/config.py:250 +#, python-format +msgid "" +"Configuration error\n" +"\n" +"Errors were encountered while parsing terminator_config(5) file:\n" +"\n" +" %s\n" +"\n" +"%d line(s) have been ignored." +msgstr "" + +#: ../terminatorlib/config.py:258 +msgid "Configuration error" +msgstr "" + +#: ../terminatorlib/config.py:311 +#, python-format +msgid "Setting %r value %r not a valid colour; ignoring" +msgstr "" + +#: ../terminatorlib/config.py:316 +#, python-format +msgid "%s must be one of: top, left, right, bottom" +msgstr "" + +#: ../terminatorlib/config.py:323 +#, python-format +msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" +msgstr "" + +#: ../terminatorlib/config.py:329 +msgid "" +"Reading list values from terminator_config(5) is not currently supported" +msgstr "" + +#: ../terminatorlib/config.py:332 +#, python-format +msgid "Setting %r should be a section name" +msgstr "" + #: ../terminatorlib/encoding.py:29 msgid "Current Locale" msgstr "目前的地區語言" @@ -170,21 +223,88 @@ msgstr "越南語" msgid "Thai" msgstr "泰文" +#: ../terminatorlib/prefs_profile.py:370 +msgid "Name" +msgstr "" + +#: ../terminatorlib/prefs_profile.py:377 +msgid "Action" +msgstr "" + +#: ../terminatorlib/prefs_profile.py:384 +msgid "Keyboard shortcut" +msgstr "" + +#: ../terminatorlib/terminator.py:37 +msgid "" +"Unable to find python bindings for deskbar, hide_window is not available." +msgstr "" + #: ../terminatorlib/terminator.py:76 msgid "Close Tab" msgstr "關閉分頁" +#: ../terminatorlib/terminator.py:105 +msgid "tab" +msgstr "" + +#: ../terminatorlib/terminator.py:230 +#, python-format +msgid "Invalid geometry string %r" +msgstr "" + +#: ../terminatorlib/terminator.py:277 +msgid "Unable to bind hide_window key" +msgstr "" + +#: ../terminatorlib/terminator.py:413 +msgid "window" +msgstr "" + #. show dialog #: ../terminatorlib/terminator.py:417 msgid "Close?" msgstr "關閉?" +#: ../terminatorlib/terminator.py:423 +msgid "Close _Terminals" +msgstr "" + +#: ../terminatorlib/terminator.py:425 +msgid "Close multiple terminals?" +msgstr "" + +#: ../terminatorlib/terminator.py:428 +#, python-format +msgid "" +"This %s has several terminals open. Closing the %s will also close all " +"terminals within it." +msgstr "" + #: ../terminatorlib/terminatorterm.py:39 msgid "" "You need to install python bindings for libvte (\"python-vte\" in " "debian/ubuntu)" msgstr "你需要安裝 python bindings for libvte (在debian/ubuntu 是\"python-vte\")" +#: ../terminatorlib/terminatorterm.py:133 +msgid "Search:" +msgstr "" + +#. Button for the next result. Explicitly not show()n by default. +#: ../terminatorlib/terminatorterm.py:150 +msgid "Next" +msgstr "" + +#. Give up, we're completely stuck +#: ../terminatorlib/terminatorterm.py:518 +msgid "Unable to find a shell" +msgstr "" + +#: ../terminatorlib/terminatorterm.py:539 +msgid "Unable to start shell: " +msgstr "" + #: ../terminatorlib/terminatorterm.py:1033 msgid "_Open Link" msgstr "開啟連結(_O)" @@ -221,142 +341,6 @@ msgstr "垂直分隔(_e)" msgid "Open _Tab" msgstr "開啟分頁(_T)" -#: ../terminatorlib/terminatorterm.py:1127 -msgid "_Zoom terminal" -msgstr "放大終端機(_Z)" - -#: ../terminatorlib/terminatorterm.py:1136 -msgid "_Unzoom terminal" -msgstr "取消放大終端機(&U)" - -#: ../terminatorlib/terminatorterm.py:1282 -msgid "Encodings" -msgstr "編碼" - -#: ../terminatorlib/terminatorterm.py:1300 -msgid "Other Encodings" -msgstr "其他編碼" - -#: ../terminatorlib/configfile.py:96 -msgid "Unterminated quoted string" -msgstr "" - -#: ../terminatorlib/configfile.py:147 -msgid "Setting without a value" -msgstr "" - -#: ../terminatorlib/configfile.py:152 -msgid "Unexpected token" -msgstr "" - -#: ../terminatorlib/config.py:250 -#, python-format -msgid "" -"Configuration error\n" -"\n" -"Errors were encountered while parsing terminator_config(5) file:\n" -"\n" -" %s\n" -"\n" -"%d line(s) have been ignored." -msgstr "" - -#: ../terminatorlib/config.py:258 -msgid "Configuration error" -msgstr "" - -#: ../terminatorlib/config.py:311 -#, python-format -msgid "Setting %r value %r not a valid colour; ignoring" -msgstr "" - -#: ../terminatorlib/config.py:316 -#, python-format -msgid "%s must be one of: top, left, right, bottom" -msgstr "" - -#: ../terminatorlib/config.py:323 -#, python-format -msgid "Boolean setting %s expecting one of: yes, no, true, false, on, off" -msgstr "" - -#: ../terminatorlib/config.py:329 -msgid "" -"Reading list values from terminator_config(5) is not currently supported" -msgstr "" - -#: ../terminatorlib/config.py:332 -#, python-format -msgid "Setting %r should be a section name" -msgstr "" - -#: ../terminatorlib/prefs_profile.py:370 -msgid "Name" -msgstr "" - -#: ../terminatorlib/prefs_profile.py:377 -msgid "Action" -msgstr "" - -#: ../terminatorlib/prefs_profile.py:384 -msgid "Keyboard shortcut" -msgstr "" - -#: ../terminatorlib/terminator.py:37 -msgid "" -"Unable to find python bindings for deskbar, hide_window is not available." -msgstr "" - -#: ../terminatorlib/terminator.py:105 -msgid "tab" -msgstr "" - -#: ../terminatorlib/terminator.py:230 -#, python-format -msgid "Invalid geometry string %r" -msgstr "" - -#: ../terminatorlib/terminator.py:277 -msgid "Unable to bind hide_window key" -msgstr "" - -#: ../terminatorlib/terminator.py:413 -msgid "window" -msgstr "" - -#: ../terminatorlib/terminator.py:423 -msgid "Close _Terminals" -msgstr "" - -#: ../terminatorlib/terminator.py:425 -msgid "Close multiple terminals?" -msgstr "" - -#: ../terminatorlib/terminator.py:428 -#, python-format -msgid "" -"This %s has several terminals open. Closing the %s will also close all " -"terminals within it." -msgstr "" - -#: ../terminatorlib/terminatorterm.py:133 -msgid "Search:" -msgstr "" - -#. Button for the next result. Explicitly not show()n by default. -#: ../terminatorlib/terminatorterm.py:150 -msgid "Next" -msgstr "" - -#. Give up, we're completely stuck -#: ../terminatorlib/terminatorterm.py:518 -msgid "Unable to find a shell" -msgstr "" - -#: ../terminatorlib/terminatorterm.py:539 -msgid "Unable to start shell: " -msgstr "" - #: ../terminatorlib/terminatorterm.py:1112 msgid "Open _Debug Tab" msgstr "" @@ -365,10 +349,18 @@ msgstr "" msgid "Open Top Level Tab" msgstr "" +#: ../terminatorlib/terminatorterm.py:1127 +msgid "_Zoom terminal" +msgstr "放大終端機(_Z)" + #: ../terminatorlib/terminatorterm.py:1131 msgid "Ma_ximise terminal" msgstr "" +#: ../terminatorlib/terminatorterm.py:1136 +msgid "_Unzoom terminal" +msgstr "取消放大終端機(&U)" + #: ../terminatorlib/terminatorterm.py:1141 msgid "Unma_ximise terminal" msgstr "" @@ -405,6 +397,14 @@ msgstr "" msgid "All" msgstr "" +#: ../terminatorlib/terminatorterm.py:1282 +msgid "Encodings" +msgstr "編碼" + +#: ../terminatorlib/terminatorterm.py:1300 +msgid "Other Encodings" +msgstr "其他編碼" + #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" msgstr "" diff --git a/po/zh_TW.po b/po/zh_TW.po index 9d4eb775..5fede5df 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-06-23 19:52+0000\n" +"X-Launchpad-Export-Date: 2009-12-03 13:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../terminator:46 @@ -29,133 +29,6 @@ msgid "" "properly set" msgstr "你要在 X 環境下執行 terminator。請確定你的顯示正確設置" -#: ../terminatorlib/encoding.py:29 -msgid "Current Locale" -msgstr "目前的地區語言" - -#: ../terminatorlib/encoding.py:30 ../terminatorlib/encoding.py:43 -#: ../terminatorlib/encoding.py:62 ../terminatorlib/encoding.py:85 -#: ../terminatorlib/encoding.py:96 -msgid "Western" -msgstr "西歐語系" - -#: ../terminatorlib/encoding.py:31 ../terminatorlib/encoding.py:63 -#: ../terminatorlib/encoding.py:75 ../terminatorlib/encoding.py:94 -msgid "Central European" -msgstr "中歐語系" - -#: ../terminatorlib/encoding.py:32 -msgid "South European" -msgstr "南歐" - -#: ../terminatorlib/encoding.py:33 ../terminatorlib/encoding.py:41 -#: ../terminatorlib/encoding.py:101 -msgid "Baltic" -msgstr "波羅的海語系" - -#: ../terminatorlib/encoding.py:35 ../terminatorlib/encoding.py:67 -#: ../terminatorlib/encoding.py:74 ../terminatorlib/encoding.py:100 -msgid "Arabic" -msgstr "阿拉伯文" - -#: ../terminatorlib/encoding.py:36 ../terminatorlib/encoding.py:80 -#: ../terminatorlib/encoding.py:97 -msgid "Greek" -msgstr "希臘文" - -#: ../terminatorlib/encoding.py:37 -msgid "Hebrew Visual" -msgstr "希伯來文(左至右)" - -#: ../terminatorlib/encoding.py:38 ../terminatorlib/encoding.py:66 -#: ../terminatorlib/encoding.py:83 ../terminatorlib/encoding.py:99 -msgid "Hebrew" -msgstr "希伯來文" - -#: ../terminatorlib/encoding.py:39 ../terminatorlib/encoding.py:65 -#: ../terminatorlib/encoding.py:87 ../terminatorlib/encoding.py:98 -msgid "Turkish" -msgstr "土耳其語" - -#: ../terminatorlib/encoding.py:40 -msgid "Nordic" -msgstr "北歐語系" - -#: ../terminatorlib/encoding.py:42 -msgid "Celtic" -msgstr "塞爾特語系" - -#: ../terminatorlib/encoding.py:44 ../terminatorlib/encoding.py:86 -msgid "Romanian" -msgstr "羅馬尼亞語" - -#: ../terminatorlib/encoding.py:45 ../terminatorlib/encoding.py:46 -#: ../terminatorlib/encoding.py:47 ../terminatorlib/encoding.py:48 -#: ../terminatorlib/encoding.py:49 -msgid "Unicode" -msgstr "萬國碼" - -#: ../terminatorlib/encoding.py:50 -msgid "Armenian" -msgstr "亞美尼亞文" - -#: ../terminatorlib/encoding.py:51 ../terminatorlib/encoding.py:52 -#: ../terminatorlib/encoding.py:56 -msgid "Chinese Traditional" -msgstr "中文(繁體)" - -#: ../terminatorlib/encoding.py:54 ../terminatorlib/encoding.py:68 -#: ../terminatorlib/encoding.py:89 -msgid "Japanese" -msgstr "日文" - -#: ../terminatorlib/encoding.py:55 ../terminatorlib/encoding.py:69 -#: ../terminatorlib/encoding.py:71 ../terminatorlib/encoding.py:92 -msgid "Korean" -msgstr "韓文" - -#: ../terminatorlib/encoding.py:57 ../terminatorlib/encoding.py:58 -#: ../terminatorlib/encoding.py:59 ../terminatorlib/encoding.py:61 -msgid "Chinese Simplified" -msgstr "中文(簡體)" - -#: ../terminatorlib/terminator.py:76 -msgid "Close Tab" -msgstr "關閉分頁" - -#. show dialog -#: ../terminatorlib/terminator.py:417 -msgid "Close?" -msgstr "關閉?" - -#: ../terminatorlib/terminatorterm.py:1033 -msgid "_Open Link" -msgstr "開啟鏈結(_O)" - -#: ../terminatorlib/terminatorterm.py:1034 -msgid "_Copy Link Address" -msgstr "複製鏈結位址(_C)" - -#: ../terminatorlib/terminatorterm.py:1044 -msgid "_Send Mail To..." -msgstr "傳送郵件給(_S)..." - -#: ../terminatorlib/terminatorterm.py:1045 -msgid "_Copy Email Address" -msgstr "複製郵件地址(_C)" - -#: ../terminatorlib/terminatorterm.py:1089 -msgid "Split H_orizontally" -msgstr "橫向分裂(_o)" - -#: ../terminatorlib/terminatorterm.py:1090 -msgid "Split V_ertically" -msgstr "垂直分裂(_e)" - -#: ../terminatorlib/terminatorterm.py:1282 -msgid "Encodings" -msgstr "編碼" - #: ../terminatorlib/configfile.py:96 msgid "Unterminated quoted string" msgstr "" @@ -209,16 +82,106 @@ msgstr "" msgid "Setting %r should be a section name" msgstr "" +#: ../terminatorlib/encoding.py:29 +msgid "Current Locale" +msgstr "目前的地區語言" + +#: ../terminatorlib/encoding.py:30 ../terminatorlib/encoding.py:43 +#: ../terminatorlib/encoding.py:62 ../terminatorlib/encoding.py:85 +#: ../terminatorlib/encoding.py:96 +msgid "Western" +msgstr "西歐語系" + +#: ../terminatorlib/encoding.py:31 ../terminatorlib/encoding.py:63 +#: ../terminatorlib/encoding.py:75 ../terminatorlib/encoding.py:94 +msgid "Central European" +msgstr "中歐語系" + +#: ../terminatorlib/encoding.py:32 +msgid "South European" +msgstr "南歐" + +#: ../terminatorlib/encoding.py:33 ../terminatorlib/encoding.py:41 +#: ../terminatorlib/encoding.py:101 +msgid "Baltic" +msgstr "波羅的海語系" + #: ../terminatorlib/encoding.py:34 ../terminatorlib/encoding.py:64 #: ../terminatorlib/encoding.py:70 ../terminatorlib/encoding.py:72 #: ../terminatorlib/encoding.py:77 ../terminatorlib/encoding.py:95 msgid "Cyrillic" msgstr "" +#: ../terminatorlib/encoding.py:35 ../terminatorlib/encoding.py:67 +#: ../terminatorlib/encoding.py:74 ../terminatorlib/encoding.py:100 +msgid "Arabic" +msgstr "阿拉伯文" + +#: ../terminatorlib/encoding.py:36 ../terminatorlib/encoding.py:80 +#: ../terminatorlib/encoding.py:97 +msgid "Greek" +msgstr "希臘文" + +#: ../terminatorlib/encoding.py:37 +msgid "Hebrew Visual" +msgstr "希伯來文(左至右)" + +#: ../terminatorlib/encoding.py:38 ../terminatorlib/encoding.py:66 +#: ../terminatorlib/encoding.py:83 ../terminatorlib/encoding.py:99 +msgid "Hebrew" +msgstr "希伯來文" + +#: ../terminatorlib/encoding.py:39 ../terminatorlib/encoding.py:65 +#: ../terminatorlib/encoding.py:87 ../terminatorlib/encoding.py:98 +msgid "Turkish" +msgstr "土耳其語" + +#: ../terminatorlib/encoding.py:40 +msgid "Nordic" +msgstr "北歐語系" + +#: ../terminatorlib/encoding.py:42 +msgid "Celtic" +msgstr "塞爾特語系" + +#: ../terminatorlib/encoding.py:44 ../terminatorlib/encoding.py:86 +msgid "Romanian" +msgstr "羅馬尼亞語" + +#: ../terminatorlib/encoding.py:45 ../terminatorlib/encoding.py:46 +#: ../terminatorlib/encoding.py:47 ../terminatorlib/encoding.py:48 +#: ../terminatorlib/encoding.py:49 +msgid "Unicode" +msgstr "萬國碼" + +#: ../terminatorlib/encoding.py:50 +msgid "Armenian" +msgstr "亞美尼亞文" + +#: ../terminatorlib/encoding.py:51 ../terminatorlib/encoding.py:52 +#: ../terminatorlib/encoding.py:56 +msgid "Chinese Traditional" +msgstr "中文(繁體)" + #: ../terminatorlib/encoding.py:53 msgid "Cyrillic/Russian" msgstr "" +#: ../terminatorlib/encoding.py:54 ../terminatorlib/encoding.py:68 +#: ../terminatorlib/encoding.py:89 +msgid "Japanese" +msgstr "日文" + +#: ../terminatorlib/encoding.py:55 ../terminatorlib/encoding.py:69 +#: ../terminatorlib/encoding.py:71 ../terminatorlib/encoding.py:92 +msgid "Korean" +msgstr "韓文" + +#: ../terminatorlib/encoding.py:57 ../terminatorlib/encoding.py:58 +#: ../terminatorlib/encoding.py:59 ../terminatorlib/encoding.py:61 +msgid "Chinese Simplified" +msgstr "中文(簡體)" + #: ../terminatorlib/encoding.py:60 msgid "Georgian" msgstr "" @@ -277,6 +240,10 @@ msgid "" "Unable to find python bindings for deskbar, hide_window is not available." msgstr "" +#: ../terminatorlib/terminator.py:76 +msgid "Close Tab" +msgstr "關閉分頁" + #: ../terminatorlib/terminator.py:105 msgid "tab" msgstr "" @@ -294,6 +261,11 @@ msgstr "" msgid "window" msgstr "" +#. show dialog +#: ../terminatorlib/terminator.py:417 +msgid "Close?" +msgstr "關閉?" + #: ../terminatorlib/terminator.py:423 msgid "Close _Terminals" msgstr "" @@ -333,6 +305,22 @@ msgstr "" msgid "Unable to start shell: " msgstr "" +#: ../terminatorlib/terminatorterm.py:1033 +msgid "_Open Link" +msgstr "開啟鏈結(_O)" + +#: ../terminatorlib/terminatorterm.py:1034 +msgid "_Copy Link Address" +msgstr "複製鏈結位址(_C)" + +#: ../terminatorlib/terminatorterm.py:1044 +msgid "_Send Mail To..." +msgstr "傳送郵件給(_S)..." + +#: ../terminatorlib/terminatorterm.py:1045 +msgid "_Copy Email Address" +msgstr "複製郵件地址(_C)" + #: ../terminatorlib/terminatorterm.py:1071 msgid "Show _scrollbar" msgstr "" @@ -341,6 +329,14 @@ msgstr "" msgid "Show _titlebar" msgstr "" +#: ../terminatorlib/terminatorterm.py:1089 +msgid "Split H_orizontally" +msgstr "橫向分裂(_o)" + +#: ../terminatorlib/terminatorterm.py:1090 +msgid "Split V_ertically" +msgstr "垂直分裂(_e)" + #: ../terminatorlib/terminatorterm.py:1107 msgid "Open _Tab" msgstr "" @@ -401,6 +397,10 @@ msgstr "" msgid "All" msgstr "" +#: ../terminatorlib/terminatorterm.py:1282 +msgid "Encodings" +msgstr "編碼" + #: ../terminatorlib/terminatorterm.py:1300 msgid "Other Encodings" msgstr "" diff --git a/terminator b/terminator index 3ffb1a09..2af3cedb 100755 --- a/terminator +++ b/terminator @@ -97,6 +97,8 @@ if __name__ == '__main__': inside the terminal") parser.add_option ("--working-directory", metavar="DIR", dest="working_directory", help="Set the terminal's working directory") + parser.add_option ("-r", "--role", dest="role", + help="Set custom WM_WINDOW_ROLE property") for item in ['--sm-client-id', '--sm-config-prefix', '--screen']: parser.add_option (item, dest="dummy", action="store", help=SUPPRESS_HELP) @@ -160,7 +162,7 @@ See the following bug report for more details: dbg ('profile_cb: settled on profile: "%s"' % options.profile) term = Terminator (options.profile, command, options.fullscreen, options.maximise, options.borderless, options.no_gconf, - options.geometry, options.hidden, options.forcedtitle) + options.geometry, options.hidden, options.forcedtitle, options.role) term.origcwd = origcwd diff --git a/terminator.spec b/terminator.spec index f5736615..ec51b70f 100644 --- a/terminator.spec +++ b/terminator.spec @@ -1,7 +1,7 @@ %{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} Name: terminator -Version: 0.13 +Version: 0.14 Release: 3%{?dist} Summary: Store and run multiple GNOME terminals in one window diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 9a6b8246..721e7888 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -41,6 +41,7 @@ DEFAULTS = { 'cursor_shape' : 'block', 'cursor_color' : '', 'emulation' : 'xterm', + 'geometry_hinting' : True, 'font' : 'Mono 10', 'foreground_color' : '#AAAAAA', 'scrollbar_position' : "right", diff --git a/terminatorlib/keybindings.py b/terminatorlib/keybindings.py index 6702264d..019e7f11 100644 --- a/terminatorlib/keybindings.py +++ b/terminatorlib/keybindings.py @@ -79,8 +79,7 @@ class Keybindings: # Does much the same, but with poorer error handling. #keyval, mask = gtk.accelerator_parse(binding) except KeymapError, ex: - ex.action = action - raise ex + continue else: if mask & gtk.gdk.SHIFT_MASK: if keyval == gtk.keysyms.Tab: diff --git a/terminatorlib/prefs_profile.py b/terminatorlib/prefs_profile.py index bb991d02..1e9442ca 100644 --- a/terminatorlib/prefs_profile.py +++ b/terminatorlib/prefs_profile.py @@ -2,7 +2,7 @@ from terminatorlib.util import dbg,err from terminatorlib.config import DEFAULTS,TerminatorConfValuestoreRC -from terminatorlib.keybindings import TerminatorKeybindings +from terminatorlib.keybindings import TerminatorKeybindings, KeymapError from terminatorlib.version import APP_NAME, APP_VERSION from terminatorlib import translation @@ -364,7 +364,10 @@ class ProfileEditor: keyval = 0 mask = 0 if value is not None and value != "None": - (keyval, mask) = self.tkbobj._parsebinding(value) + try: + (keyval, mask) = self.tkbobj._parsebinding(value) + except KeymapError: + pass if (row[2], row[3]) != (keyval, mask): changed_keybindings.append ((row[0], accel)) dbg("%s changed from %s to %s" % (row[0], self.term.conf.keybindings[row[0]], accel)) @@ -393,7 +396,10 @@ class ProfileEditor: if isinstance (value, tuple): value = value[0] if value is not None and value != "None": - (keyval, mask) = self.tkbobj._parsebinding (value) + try: + (keyval, mask) = self.tkbobj._parsebinding (value) + except KeymapError: + pass self.liststore.append ([binding, self.source_get_keyname (binding), keyval, mask, True]) dbg("Appended row: %s, %s, %s" % (binding, keyval, mask)) diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index 87c5ba9d..680c89c5 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -130,7 +130,7 @@ class Terminator: def __init__ (self, profile = None, command = None, fullscreen = False, maximise = False, borderless = False, no_gconf = False, - geometry = None, hidden = False, forcedtitle = None): + geometry = None, hidden = False, forcedtitle = None, role=None): self.profile = profile self.command = command @@ -229,6 +229,8 @@ class Terminator: self.set_closebutton_style () self.window = gtk.Window () + if role: + self.window.set_role(role) self.windowtitle = TerminatorWindowTitle (self.window) if forcedtitle: self.windowtitle.force_title (forcedtitle) @@ -288,6 +290,47 @@ class Terminator: if hidden or self.conf.hidden: self.window.iconify() + def on_term_resized(self): + win_total_width, win_total_height = self.window.get_size () + dbg ('Resized window is %dx%d' % (win_total_width, win_total_height)) + + # FIXME: find first terminal + firstidx = 0 + + # Walk terminals across top edge to sum column geometries + prev = -1 + column_sum = 0 + width_extra = 0 + walker = firstidx + while (walker != None): + term = self.term_list[walker] + font_width, font_height, columns, rows = term.get_size_details () + column_sum += columns + dbg ('Geometry hints (term %d) column += %d characters' % (walker, columns)) + prev = walker + walker = self._select_right (walker) + + # Walk terminals down left edge to sum row geometries + prev = -1 + row_sum = 0 + height_extra = 0 + walker = firstidx + while (walker != None): + term = self.term_list[walker] + font_width, font_height, columns, rows = term.get_size_details () + row_sum += rows + dbg ('Geometry hints (term %d) row += %d characters' % (walker, rows)) + prev = walker + walker = self._select_down (walker) + + # adjust... + width_extra = win_total_width - (column_sum * font_width) + height_extra = win_total_height - (row_sum * font_height) + + dbg ('Geometry hints based on font size: %dx%d, columns: %d, rows: %d, extra width: %d, extra height: %d' % (font_width, font_height, column_sum, row_sum, width_extra, height_extra)) + + self.window.set_geometry_hints(self.window, -1, -1, -1, -1, width_extra, height_extra, font_width, font_height, -1.0, -1.0) + def set_handle_size (self, size): if size in xrange (0,6): gtk.rc_parse_string(""" @@ -786,7 +829,7 @@ class Terminator: self.on_destroy_event (parent, gtk.gdk.Event (gtk.gdk.DESTROY)) return True - if isinstance (parent, gtk.Paned): + elif isinstance (parent, gtk.Paned): index = self.term_list.index (widget) grandparent = parent.get_parent () @@ -856,8 +899,22 @@ class Terminator: else: gdparent.remove(parent) gdparent.pack2(sibling) + elif isinstance(gdparent, gtk.Notebook): + # extreme_tabs is on :( + label = gdparent.get_tab_label(parent) + gdparent.remove(parent) + gdparent.insert_page(sibling, None, 0) + gdparent.set_tab_label(sibling, label) + gdparent.set_tab_label_packing(sibling, not self.conf.scroll_tabbar, not self.conf.scroll_tabbar, gtk.PACK_START) + if self._tab_reorderable: + gdparent.set_tab_reorderable(sibling, True) + gdparent.set_current_page(0) + else: + err('Unknown grandparent of %s (parent is a notebook)' % widget) if isinstance(sibling, TerminatorTerm) and sibling.conf.titlebars and sibling.conf.extreme_tabs: sibling._titlebox.show() + else: + err('Attempting to remove terminal from unknown parent: %s' % parent) if self.conf.focus_on_close == 'prev' or ( self.conf.focus_on_close == 'auto' and focus_on_close == 'prev'): if index == 0: index = 1 self.term_list[index - 1]._vte.grab_focus () @@ -907,7 +964,13 @@ class Terminator: term._vte.grab_focus () def _select_direction (self, term, matcher): - current = self.term_list.index (term) + '''Return index of terminal in given direction''' + # Handle either TerminatorTerm or int index + if type(term) == int: + current = term + term = self.term_list[current] + else: + current = self.term_list.index (term) current_geo = term.get_geometry () best_index = None best_geo = None @@ -924,9 +987,13 @@ class Terminator: #print "I saw %d" % (i) #pprint.pprint(possible_geo) - if matcher (current_geo, possible_geo, best_geo): + try: + if matcher (current_geo, possible_geo, best_geo): best_index = i best_geo = possible_geo + except: + # Not being called on a Paned widget + pass #if best_index is None: # print "nothing best" #else: @@ -937,6 +1004,10 @@ class Terminator: '''We want to find terminals that are fully above the top border, but closest in the y direction, breaking ties via the closest cursor x position.''' + if len(possible_geo.keys()) == 0: + dbg('_match_right: no possible geo, bailing') + return False + #print "matching up..." # top edge of the current terminal edge = current_geo['origin_y'] @@ -944,7 +1015,10 @@ class Terminator: new_edge = possible_geo['origin_y']+possible_geo['span_y'] # Width of the horizontal bar that splits terminals - horizontalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0]._titlebox.get_allocation().height + try: + horizontalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0]._titlebox.get_allocation().height + except TypeError: + horizontalBar = 0 # Vertical distance between two terminals distance = current_geo['offset_y'] - (possible_geo['offset_y'] + possible_geo['span_y']) if new_edge < edge: @@ -976,6 +1050,10 @@ class Terminator: '''We want to find terminals that are fully below the bottom border, but closest in the y direction, breaking ties via the closest cursor x position.''' + if len(possible_geo.keys()) == 0: + dbg('_match_right: no possible geo, bailing') + return False + #print "matching down..." # bottom edge of the current terminal edge = current_geo['origin_y']+current_geo['span_y'] @@ -984,7 +1062,10 @@ class Terminator: #print "edge: %d new_edge: %d" % (edge, new_edge) # Width of the horizontal bar that splits terminals - horizontalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0]._titlebox.get_allocation().height + try: + horizontalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0]._titlebox.get_allocation().height + except TypeError: + horizontalBar = 0 # Vertical distance between two terminals distance = possible_geo['offset_y'] - (current_geo['offset_y'] + current_geo['span_y']) if new_edge > edge: @@ -1017,6 +1098,10 @@ class Terminator: '''We want to find terminals that are fully to the left of the left-side border, but closest in the x direction, breaking ties via the closest cursor y position.''' + if len(possible_geo.keys()) == 0: + dbg('_match_right: no possible geo, bailing') + return False + #print "matching left..." # left-side edge of the current terminal edge = current_geo['origin_x'] @@ -1024,12 +1109,21 @@ class Terminator: new_edge = possible_geo['origin_x']+possible_geo['span_x'] # Width of the horizontal bar that splits terminals - horizontalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0]._titlebox.get_allocation().height + try: + horizontalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0]._titlebox.get_allocation().height + except TypeError: + horizontalBar = 0 # Width of the vertical bar that splits terminals if self.term_list[0].is_scrollbar_present(): - verticalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0].get_parent().style_get_property('scroll-arrow-vlength') + try: + verticalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0].get_parent().style_get_property('scroll-arrow-vlength') + except TypeError: + verticalBar = 0 else: - verticalBar = self.term_list[0].get_parent().style_get_property('handle-size') + try: + verticalBar = self.term_list[0].get_parent().style_get_property('handle-size') + except TypeError: + verticalBar = 0 # Horizontal distance between two terminals distance = current_geo['offset_x'] - (possible_geo['offset_x'] + possible_geo['span_x']) if new_edge <= edge: @@ -1058,6 +1152,10 @@ class Terminator: '''We want to find terminals that are fully to the right of the right-side border, but closest in the x direction, breaking ties via the closest cursor y position.''' + if len(possible_geo.keys()) == 0: + dbg('_match_right: no possible geo, bailing') + return False + #print "matching right..." # right-side edge of the current terminal edge = current_geo['origin_x']+current_geo['span_x'] @@ -1066,12 +1164,21 @@ class Terminator: #print "edge: %d new_edge: %d" % (edge, new_edge) # Width of the horizontal bar that splits terminals - horizontalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0]._titlebox.get_allocation().height + try: + horizontalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0]._titlebox.get_allocation().height + except TypeError: + horizontalBar = 0 # Width of the vertical bar that splits terminals if self.term_list[0].is_scrollbar_present(): - verticalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0].get_parent().style_get_property('scroll-arrow-vlength') + try: + verticalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0].get_parent().style_get_property('scroll-arrow-vlength') + except TypeError: + verticalBar = 0 else: - verticalBar = self.term_list[0].get_parent().style_get_property('handle-size') + try: + verticalBar = self.term_list[0].get_parent().style_get_property('handle-size') + except TypeError: + verticalBar = 0 # Horizontal distance between two terminals distance = possible_geo['offset_x'] - (current_geo['offset_x'] + current_geo['span_x']) if new_edge >= edge: diff --git a/terminatorlib/terminatorterm.py b/terminatorlib/terminatorterm.py index 3d136a96..c7378f22 100755 --- a/terminatorlib/terminatorterm.py +++ b/terminatorlib/terminatorterm.py @@ -19,7 +19,7 @@ import pygtk pygtk.require ("2.0") import gobject, gtk, pango -import os, signal, sys, subprocess, pwd, re +import os, signal, sys, subprocess, pwd, re, urllib2 #import version details from terminatorlib.version import * @@ -406,7 +406,11 @@ class TerminatorTerm (gtk.VBox): dbg ('Resize window triggered on %s: %dx%d' % (widget, width, height)) def on_vte_size_allocate(self, widget, allocation): + dbg ('Terminal resized to %dx%d' % (self._vte.get_column_count (), + self._vte.get_row_count ())) self._titlebox.set_terminal_size (self._vte.get_column_count (), self._vte.get_row_count ()) + if self._vte.window != None and (self.conf.geometry_hinting): + self.terminator.on_term_resized () def get_pixbuf(self, maxsize= None): pixmap = self.get_snapshot() @@ -541,7 +545,7 @@ text/plain #print "%s %s" % (selection_data.type, selection_data.target) txt = selection_data.data.strip() if txt[0:7] == "file://": - txt = "'%s'" % txt[7:] + txt = "'%s'" % urllib2.unquote(txt[7:]) for term in self.get_target_terms(): term._vte.feed_child(txt) return @@ -897,6 +901,14 @@ text/plain self._titlebox.update () self._vte.queue_draw () + def get_size_details(self): + font_width = self._vte.get_char_width () + font_height = self._vte.get_char_height () + columns = self._vte.get_column_count () + rows = self._vte.get_row_count () + + return (font_width, font_height, columns, rows) + def on_composited_changed (self, widget): self.reconfigure_vte () @@ -1233,6 +1245,8 @@ text/plain '''Returns Gdk.Window.get_position(), pixel-based cursor position, and Gdk.Window.get_geometry()''' reply = dict() + if not self._vte.window: + return reply x, y = self._vte.window.get_origin () reply.setdefault('origin_x',x) reply.setdefault('origin_y',y) @@ -1415,7 +1429,10 @@ text/plain self.populate_grouping_menu (menu) menu.show_all () - menu.popup (None, None, self.position_popup_group_menu, button, time, widget) + if gtk.gtk_version > (2, 14, 0): + menu.popup (None, None, self.position_popup_group_menu, button, time, widget) + else: + menu.popup (None, None, None, button, time, widget) return True @@ -1753,11 +1770,16 @@ text/plain terms = self.find_all_terms_in_tab(notebook) notebooktablabel = notebook.get_tab_label(notebookchild) - if notebooktablabel.custom is True: + if notebooktablabel._label.custom is True: groupname = notebooktablabel.get_title() if groupname == "": - groupname = "Tab %d" % (pagenum + 1) + tmppagenum = pagenum + while True: + groupname = "Tab %d" % (tmppagenum + 1) + if groupname not in self.terminator.groupings: + break + tmppagenum += 1 self.add_group(groupname) for term in terms: diff --git a/terminatorlib/version.py b/terminatorlib/version.py index 6a8b8209..5d62880f 100644 --- a/terminatorlib/version.py +++ b/terminatorlib/version.py @@ -21,4 +21,4 @@ TerminatorVersion supplies our version number. """ APP_NAME = 'terminator' -APP_VERSION = '0.13' +APP_VERSION = '0.14' From a6a1acd26d171fe33cd4f1da7122a8b5a75ddb28 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 19 Dec 2009 15:07:22 +0000 Subject: [PATCH 189/331] Clear up the position re licencing of the two external sources of code in our refactoring --- terminatorlib/borg.py | 3 +++ terminatorlib/plugin.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/terminatorlib/borg.py b/terminatorlib/borg.py index 7d0b0956..ded16c8b 100755 --- a/terminatorlib/borg.py +++ b/terminatorlib/borg.py @@ -3,6 +3,9 @@ # GPL v2 only """borg.py - We are the borg. Resistance is futile. http://code.activestate.com/recipes/66531/ + ActiveState's policy appears to be that snippets + exist to encourage re-use, but I can not find any + specific licencing terms. >>> obj1 = TestBorg() >>> obj2 = TestBorg() diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py index 9c5feaae..b089239f 100755 --- a/terminatorlib/plugin.py +++ b/terminatorlib/plugin.py @@ -2,6 +2,10 @@ # Terminator by Chris Jones # GPL v2 only """plugin.py - Base plugin system + Inspired by Armin Ronacher's post at + http://lucumr.pocoo.org/2006/7/3/python-plugin-system + Used with permission (the code in that post is to be + considered BSD licenced, per the authors wishes) >>> registry = PluginRegistry() >>> registry.instances From 90c8b7e8c50de3b77b492b4b0ef2ff855845fdf6 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 22 Dec 2009 00:24:03 +0000 Subject: [PATCH 190/331] extend test coverage to ensure borg state is unique between borg types. add some debugging info and support the ability to have multiple borg classes via an ugly parameter to __init__() --- terminatorlib/borg.py | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/terminatorlib/borg.py b/terminatorlib/borg.py index ded16c8b..3e4233cf 100755 --- a/terminatorlib/borg.py +++ b/terminatorlib/borg.py @@ -21,8 +21,19 @@ >>> obj2.attribute = 54321 >>> obj1.attribute 54321 +>>> obj3 = TestBorg2() +>>> obj3.attribute +1 +>>> obj4 = TestBorg2() +>>> obj3.attribute = 98765 +>>> obj4.attribute +98765 +>>> + """ +from util import dbg + # pylint: disable-msg=R0903 # pylint: disable-msg=R0921 class Borg: @@ -35,7 +46,7 @@ class Borg: attribute = None def __init__(self): - Borg.__init__(self) + Borg.__init__(self, self.__class__.__name__) def prepare_attributes(self): if not self.attribute: @@ -51,11 +62,16 @@ class Borg: if necessary.""" __shared_state = {} - def __init__(self): + def __init__(self, borgtype=None): """Class initialiser. Overwrite our class dictionary with the shared state. This makes us identical to every other instance of this class type.""" - self.__dict__ = self.__shared_state + if borgtype is None: + raise TypeError('Borg::__init__: You must pass a borgtype') + if not self.__shared_state.has_key(borgtype): + dbg('Borg::__init__: Preparing borg state for %s' % borgtype) + self.__shared_state[borgtype] = {} + self.__dict__ = self.__shared_state[borgtype] def prepare_attributes(self): """This should be used to prepare any attributes of the borg class.""" @@ -66,13 +82,24 @@ if __name__ == '__main__': attribute = None def __init__(self): - Borg.__init__(self) + Borg.__init__(self, self.__class__.__name__) self.prepare_attributes() def prepare_attributes(self): if not self.attribute: self.attribute = 0 + class TestBorg2(Borg): + attribute = None + + def __init__(self): + Borg.__init__(self, self.__class__.__name__) + self.prepare_attributes() + + def prepare_attributes(self): + if not self.attribute: + self.attribute = 1 + import doctest (failed, attempted) = doctest.testmod() print "%d/%d tests failed" % (failed, attempted) From 4a96bf99d4c9d1e1e0eb25d70b800a4b36470bd3 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 22 Dec 2009 00:24:53 +0000 Subject: [PATCH 191/331] add some test coverate, split the defaults into sections and move the borg element to a new base object, converting it to use the new borg __init__ syntax. --- terminatorlib/config.py | 316 +++++++++++++++++++++++----------------- 1 file changed, 186 insertions(+), 130 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 721e7888..ccab3e50 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -17,154 +17,210 @@ """Terminator by Chris Jones -Classes relating to configuratoin""" +Classes relating to configuration + +>>> config = Config() +>>> config['focus'] +'click' +>>> config['focus'] = 'sloppy' +>>> + +""" import platform from borg import Borg DEFAULTS = { - 'gt_dir' : '/apps/gnome-terminal', - 'profile_dir' : '/apps/gnome-terminal/profiles', - 'titlebars' : True, - 'zoomedtitlebar' : True, - 'allow_bold' : True, - 'audible_bell' : False, - 'visible_bell' : True, - 'urgent_bell' : False, - 'background_color' : '#000000', - 'background_darkness' : 0.5, - 'background_type' : 'solid', - 'background_image' : '', - 'backspace_binding' : 'ascii-del', - 'delete_binding' : 'delete-sequence', - 'cursor_blink' : True, - 'cursor_shape' : 'block', - 'cursor_color' : '', - 'emulation' : 'xterm', - 'geometry_hinting' : True, - 'font' : 'Mono 10', - 'foreground_color' : '#AAAAAA', - 'scrollbar_position' : "right", - 'scroll_background' : True, - 'scroll_on_keystroke' : True, - 'scroll_on_output' : True, - 'scrollback_lines' : 500, - 'focus' : 'click', - 'exit_action' : 'close', - 'palette' : '#000000000000:#CDCD00000000:#0000CDCD0000:\ - #CDCDCDCD0000:#30BF30BFA38E:#A53C212FA53C:\ - #0000CDCDCDCD:#FAFAEBEBD7D7:#404040404040:\ - #FFFF00000000:#0000FFFF0000:#FFFFFFFF0000:\ - #00000000FFFF:#FFFF0000FFFF:#0000FFFFFFFF:\ - #FFFFFFFFFFFF', - 'word_chars' : '-A-Za-z0-9,./?%&#:_', - 'mouse_autohide' : True, - 'update_records' : True, - 'login_shell' : False, - 'use_custom_command' : False, - 'custom_command' : '', - 'use_system_font' : True, - 'use_theme_colors' : False, - 'http_proxy' : '', - 'ignore_hosts' : ['localhost','127.0.0.0/8','*.local'], - 'encoding' : 'UTF-8', - 'active_encodings' : ['UTF-8', 'ISO-8859-1'], - 'fullscreen' : False, - 'borderless' : False, - 'maximise' : False, - 'hidden' : False, - 'handle_size' : -1, - 'focus_on_close' : 'auto', - 'f11_modifier' : False, - 'force_no_bell' : False, - 'cycle_term_tab' : True, - 'copy_on_selection' : False, - 'close_button_on_tab' : True, - 'tab_position' : 'top', - 'enable_real_transparency' : True, - 'title_tx_txt_color' : '#FFFFFF', - 'title_tx_bg_color' : '#C80003', - 'title_rx_txt_color' : '#FFFFFF', - 'title_rx_bg_color' : '#0076C9', - 'title_ia_txt_color' : '#000000', - 'title_ia_bg_color' : '#C0BEBF', - 'try_posix_regexp' : platform.system() != 'Linux', - 'hide_tabbar' : False, - 'scroll_tabbar' : False, - 'alternate_screen_scroll': True, - 'split_to_group' : False, - 'autoclean_groups' : True, - 'keybindings' : { - 'zoom_in' : 'plus', - 'zoom_out' : 'minus', - 'zoom_normal' : '0', - 'new_root_tab' : 'T', - 'new_tab' : 'T', - 'go_next' : ('N','Tab'), - 'go_prev' : ('P','Tab'), - 'go_up' : 'Up', - 'go_down' : 'Down', - 'go_left' : 'Left', - 'go_right' : 'Right', - 'split_horiz' : 'O', - 'split_vert' : 'E', - 'close_term' : 'W', - 'copy' : 'C', - 'paste' : 'V', - 'toggle_scrollbar' : 'S', - 'search' : 'F', - 'close_window' : 'Q', - 'resize_up' : 'Up', - 'resize_down' : 'Down', - 'resize_left' : 'Left', - 'resize_right' : 'Right', - 'move_tab_right' : 'Page_Down', - 'move_tab_left' : 'Page_Up', - 'toggle_zoom' : 'X', - 'scaled_zoom' : 'Z', - 'next_tab' : 'Page_Down', - 'prev_tab' : 'Page_Up', - 'switch_to_tab_1' : None, - 'switch_to_tab_2' : None, - 'switch_to_tab_3' : None, - 'switch_to_tab_4' : None, - 'switch_to_tab_5' : None, - 'switch_to_tab_6' : None, - 'switch_to_tab_7' : None, - 'switch_to_tab_8' : None, - 'switch_to_tab_9' : None, - 'switch_to_tab_10' : None, - 'full_screen' : 'F11', - 'reset' : 'R', - 'reset_clear' : 'G', - 'hide_window' : 'a', - 'group_all' : 'g', - 'ungroup_all' : 'g', - 'group_tab' : 't', - 'ungroup_tab' : 'T', - 'new_window' : 'I', - } + 'global': { + 'focus' : 'click', + 'enable_real_transparency' : True, + 'handle_size' : -1, + 'geometry_hinting' : True, + 'fullscreen' : False, + 'borderless' : False, + 'maximise' : False, + 'hidden' : False, + 'tab_position' : 'top', + 'close_button_on_tab' : True, + 'hide_tabbar' : False, + 'scroll_tabbar' : False, + 'try_posix_regexp' : platform.system() != 'Linux', + }, + 'keybindings': { + 'zoom_in' : 'plus', + 'zoom_out' : 'minus', + 'zoom_normal' : '0', + 'new_root_tab' : 'T', + 'new_tab' : 'T', + 'go_next' : ('N','Tab'), + 'go_prev' : ('P','Tab'), + 'go_up' : 'Up', + 'go_down' : 'Down', + 'go_left' : 'Left', + 'go_right' : 'Right', + 'split_horiz' : 'O', + 'split_vert' : 'E', + 'close_term' : 'W', + 'copy' : 'C', + 'paste' : 'V', + 'toggle_scrollbar' : 'S', + 'search' : 'F', + 'close_window' : 'Q', + 'resize_up' : 'Up', + 'resize_down' : 'Down', + 'resize_left' : 'Left', + 'resize_right' : 'Right', + 'move_tab_right' : 'Page_Down', + 'move_tab_left' : 'Page_Up', + 'toggle_zoom' : 'X', + 'scaled_zoom' : 'Z', + 'next_tab' : 'Page_Down', + 'prev_tab' : 'Page_Up', + 'switch_to_tab_1' : None, + 'switch_to_tab_2' : None, + 'switch_to_tab_3' : None, + 'switch_to_tab_4' : None, + 'switch_to_tab_5' : None, + 'switch_to_tab_6' : None, + 'switch_to_tab_7' : None, + 'switch_to_tab_8' : None, + 'switch_to_tab_9' : None, + 'switch_to_tab_10' : None, + 'full_screen' : 'F11', + 'reset' : 'R', + 'reset_clear' : 'G', + 'hide_window' : 'a', + 'group_all' : 'g', + 'ungroup_all' : 'g', + 'group_tab' : 't', + 'ungroup_tab' : 'T', + 'new_window' : 'I', + }, + 'profiles': { + 'default': { + 'titlebars' : True, + 'zoomedtitlebar' : True, + 'allow_bold' : True, + 'audible_bell' : False, + 'visible_bell' : True, + 'urgent_bell' : False, + 'background_color' : '#000000', + 'background_darkness' : 0.5, + 'background_type' : 'solid', + 'background_image' : '', + 'backspace_binding' : 'ascii-del', + 'delete_binding' : 'delete-sequence', + 'cursor_blink' : True, + 'cursor_shape' : 'block', + 'cursor_color' : '', + 'emulation' : 'xterm', + 'font' : 'Mono 10', + 'foreground_color' : '#AAAAAA', + 'scrollbar_position' : "right", + 'scroll_background' : True, + 'scroll_on_keystroke' : True, + 'scroll_on_output' : True, + 'scrollback_lines' : 500, + 'exit_action' : 'close', + 'palette' :'#000000000000:#CDCD00000000:#0000CDCD0000:\ +#CDCDCDCD0000:#30BF30BFA38E:#A53C212FA53C:\ +#0000CDCDCDCD:#FAFAEBEBD7D7:#404040404040:\ +#FFFF00000000:#0000FFFF0000:#FFFFFFFF0000:\ +#00000000FFFF:#FFFF0000FFFF:#0000FFFFFFFF:\ +#FFFFFFFFFFFF', + 'word_chars' : '-A-Za-z0-9,./?%&#:_', + 'mouse_autohide' : True, + 'update_records' : True, + 'login_shell' : False, + 'use_custom_command' : False, + 'custom_command' : '', + 'use_system_font' : True, + 'use_theme_colors' : False, + 'encoding' : 'UTF-8', + 'active_encodings' : ['UTF-8', 'ISO-8859-1'], + 'focus_on_close' : 'auto', + 'force_no_bell' : False, + 'cycle_term_tab' : True, + 'copy_on_selection' : False, + 'title_tx_txt_color' : '#FFFFFF', + 'title_tx_bg_color' : '#C80003', + 'title_rx_txt_color' : '#FFFFFF', + 'title_rx_bg_color' : '#0076C9', + 'title_ia_txt_color' : '#000000', + 'title_ia_bg_color' : '#C0BEBF', + 'alternate_screen_scroll': True, + 'split_to_group' : False, + 'autoclean_groups' : True, + 'http_proxy' : '', + 'ignore_hosts' : ['localhost','127.0.0.0/8','*.local'], + }, + }, + 'layout': { + }, + 'plugins': { + }, } -class Config(Borg, dict): - """Class to provide access to our user configuration""" +class Config(object): + """Class to provide a slightly richer config API above ConfigBase""" + base = None + profile = None + + def __init__(self, profile='default'): + self.base = ConfigBase() + self.profile = profile - defaults = None + def __getitem__(self, key): + """Look up a configuration item""" + return(self.base.get_item(key, self.profile)) + +class ConfigBase(Borg, dict): + """Class to provide access to our user configuration""" + global_config = None + profiles = None + keybindings = None + plugins = None def __init__(self): """Class initialiser""" - Borg.__init__(self) + Borg.__init__(self, self.__class__.__name__) dict.__init__(self) self.prepare_attributes() + self.load_config() def prepare_attributes(self): """Set up our borg environment""" - if self.defaults is None: - self.defaults = DEFAULTS + if self.global_config is None: + self.global_config = DEFAULTS['global'] + if self.profiles is None: + self.profiles = {} + self.profiles['default'] = DEFAULTS['profiles']['default'] + if self.keybindings is None: + self.keybindings = DEFAULTS['keybindings'] + if self.plugins is None: + self.plugins = {} - def __getitem__(self, key): + def load_config(self): + """Load configuration data from our various sources""" + # FIXME: Load our config from wherever and merge it into the defaults + pass + + def get_item(self, key, profile='default', plugin=None): """Look up a configuration item""" - return(self.defaults[key]) + if self.global_config.has_key(key): + return(self.global_config[key]) + elif self.profiles['default'].has_key(key): + return(self.profiles[profile][key]) + elif key == 'keybindings': + return(self.keybindings) + elif plugin is not None and self.plugins[plugin].has_key(key): + return(self.plugins[plugin][key]) + else: + raise KeyError('ConfigBase::get_item: unknown key %s' % key) +if __name__ == '__main__': + import doctest + (failed, attempted) = doctest.testmod() + print "%d/%d tests failed" % (failed, attempted) From 2c8e2d14e4964f80f27694ddaaa625627b5fc865 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 22 Dec 2009 00:25:05 +0000 Subject: [PATCH 192/331] port to new borg __init__() --- terminatorlib/factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/factory.py b/terminatorlib/factory.py index 2cc6ff5f..54b3a427 100755 --- a/terminatorlib/factory.py +++ b/terminatorlib/factory.py @@ -12,7 +12,7 @@ class Factory(Borg): """Definition of a class that makes other classes""" def __init__(self): """Class initialiser""" - Borg.__init__(self) + Borg.__init__(self, self.__class__.__name__) self.prepare_attributes() def prepare_attributes(self): From 89425256ece47ad4c398615226843ef8b7ffbf57 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 22 Dec 2009 00:25:17 +0000 Subject: [PATCH 193/331] port to new borg __init__() --- terminatorlib/newterminator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index 0fa427cc..d5b1e51d 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -24,7 +24,7 @@ class Terminator(Borg): def __init__(self): """Class initialiser""" - Borg.__init__(self) + Borg.__init__(self, self.__class__.__name__) self.prepare_attributes() def prepare_attributes(self): From 9ce380ef90cb1848092a07e0275ac0c8002e788a Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 22 Dec 2009 00:25:25 +0000 Subject: [PATCH 194/331] port to new borg __init__() --- terminatorlib/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py index b089239f..fbdd4212 100755 --- a/terminatorlib/plugin.py +++ b/terminatorlib/plugin.py @@ -44,7 +44,7 @@ class PluginRegistry(borg.Borg): def __init__(self): """Class initialiser""" - borg.Borg.__init__(self) + borg.Borg.__init__(self, self.__class__.__name__) self.prepare_attributes() def prepare_attributes(self): From 8d3158c152f59f1ab11ad955814f350585569f29 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 22 Dec 2009 00:32:21 +0000 Subject: [PATCH 195/331] Make ConfigBase() do some debugging. Hugely verbose, but potentially very useful for now --- terminatorlib/config.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index ccab3e50..b40af7cc 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -29,6 +29,7 @@ Classes relating to configuration import platform from borg import Borg +from util import dbg DEFAULTS = { 'global': { @@ -209,13 +210,21 @@ class ConfigBase(Borg, dict): def get_item(self, key, profile='default', plugin=None): """Look up a configuration item""" + dbg('ConfigBase::get_item: Lookup %s (profile=%s, plugin=%s)' % (key, + profile, plugin)) if self.global_config.has_key(key): + dbg('ConfigBase::get_item: found in globals: %s' % + self.global_config[key]) return(self.global_config[key]) elif self.profiles['default'].has_key(key): + dbg('ConfigBase::get_item: found in profile: %s' % + self.profiles[profile][key]) return(self.profiles[profile][key]) elif key == 'keybindings': return(self.keybindings) elif plugin is not None and self.plugins[plugin].has_key(key): + dbg('ConfigBase::get_item: found in plugin: %s' % + self.plugins[plugin][key]) return(self.plugins[plugin][key]) else: raise KeyError('ConfigBase::get_item: unknown key %s' % key) From 8068ef656fcca9c6ac093b0e0ace1c20f5a7bc68 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 23 Dec 2009 00:10:57 +0000 Subject: [PATCH 196/331] extend testing, make ConfigBase settable, drop ConfigBase derivation from dict --- terminatorlib/config.py | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index b40af7cc..3177ad7c 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -23,6 +23,8 @@ Classes relating to configuration >>> config['focus'] 'click' >>> config['focus'] = 'sloppy' +>>> config['focus'] +'sloppy' >>> """ @@ -175,7 +177,15 @@ class Config(object): """Look up a configuration item""" return(self.base.get_item(key, self.profile)) -class ConfigBase(Borg, dict): + def __setitem__(self, key, value): + """Set a particular configuration item""" + return(self.base.set_item(key, value, self.profile)) + + def set_profile(self, profile): + """Set our profile (which usually means change it)""" + self.profile = profile + +class ConfigBase(Borg): """Class to provide access to our user configuration""" global_config = None profiles = None @@ -186,7 +196,6 @@ class ConfigBase(Borg, dict): """Class initialiser""" Borg.__init__(self, self.__class__.__name__) - dict.__init__(self) self.prepare_attributes() self.load_config() @@ -216,7 +225,7 @@ class ConfigBase(Borg, dict): dbg('ConfigBase::get_item: found in globals: %s' % self.global_config[key]) return(self.global_config[key]) - elif self.profiles['default'].has_key(key): + elif self.profiles[profile].has_key(key): dbg('ConfigBase::get_item: found in profile: %s' % self.profiles[profile][key]) return(self.profiles[profile][key]) @@ -229,6 +238,24 @@ class ConfigBase(Borg, dict): else: raise KeyError('ConfigBase::get_item: unknown key %s' % key) + def set_item(self, key, value, profile='default', plugin=None): + """Set a configuration item""" + dbg('ConfigBase::set_item: Setting %s=%s (profile=%s, plugin=%s)' % + (key, value, profile, plugin)) + + if self.global_config.has_key(key): + self.global_config[key] = value + elif self.profiles[profile].has_key(key): + self.profiles[profile] = value + elif key == 'keybindings': + self.keybindings = value + elif plugin is not None and self.plugins[plugin].has_key(key): + self.plugins[plugin][key] = value + else: + raise KeyError('ConfigBase::set_item: unknown key %s' % key) + + return(True) + if __name__ == '__main__': import doctest (failed, attempted) = doctest.testmod() From f33dc6f28adbf61e21c93d1610038ed7dbdc6c47 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 23 Dec 2009 10:09:53 +0000 Subject: [PATCH 197/331] Expand Config() test coverage a little --- terminatorlib/config.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 3177ad7c..6a84517f 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -25,6 +25,14 @@ Classes relating to configuration >>> config['focus'] = 'sloppy' >>> config['focus'] 'sloppy' +>>> config2 = Config() +>>> config2['focus'] +'sloppy' +>>> config2['focus'] = 'click' +>>> config2['focus'] +'click' +>>> config['focus'] +'click' >>> """ From df317bd2cd90746e765eef9d7330472109187ce4 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 23 Dec 2009 16:40:54 +0000 Subject: [PATCH 198/331] mildly change the debugging output of objects, and handle ^C a little better --- terminatorlib/container.py | 2 +- terminatorlib/newterminator.py | 12 ++++++++---- terminatorlib/test.py | 5 ++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index 7524c809..e9be2d92 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -68,7 +68,7 @@ class Container(object): if self.cnxids.has_key(widget): for cnxid in self.cnxids[widget]: dbg('Container::disconnect_child: removing handler on %s' % - widget.__class__.__name__) + type(widget)) widget.disconnect(cnxid) del(self.cnxids[widget]) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index d5b1e51d..049a7e7c 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -47,18 +47,21 @@ class Terminator(Borg): def register_window(self, window): """Register a new window widget""" if window not in self.windows: - dbg('Terminator::register_window: registering %s' % window) + dbg('Terminator::register_window: registering %s:%s' % (id(window), + type(window))) self.windows.append(window) def deregister_window(self, window): """de-register a window widget""" - dbg('Terminator::deregister_window: de-registering %s' % window) + dbg('Terminator::deregister_window: de-registering %s:%s' % + (id(window), type(window))) self.windows.remove(window) def register_terminal(self, terminal): """Register a new terminal widget""" if terminal not in self.terminals: - dbg('Terminator::register_terminal: registering %s' % terminal) + dbg('Terminator::register_terminal: registering %s:%s' % + (id(terminal), type(terminal))) self.terminals.append(terminal) terminal.connect('ungroup-all', self.ungroup_all) terminal.connect('navigate', self.navigate_terminal) @@ -66,7 +69,8 @@ class Terminator(Borg): def deregister_terminal(self, terminal): """De-register a terminal widget""" - dbg('Terminator::deregister_terminal: de-registering %s' % terminal) + dbg('Terminator::deregister_terminal: de-registering %s:%s' % + (id(terminal), type(terminal))) self.terminals.remove(terminal) if len(self.terminals) == 0: diff --git a/terminatorlib/test.py b/terminatorlib/test.py index a16e6291..56f058e2 100755 --- a/terminatorlib/test.py +++ b/terminatorlib/test.py @@ -22,4 +22,7 @@ term.spawn_child() window.connect("destroy", on_window_destroyed) -gtk.main() +try: + gtk.main() +except KeyboardInterrupt: + pass From 0a67d73592331ef4d283aca4a61c56b1fd0fdcee Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 23 Dec 2009 17:30:26 +0000 Subject: [PATCH 199/331] Extend the url_handler plugins to include apt: support --- terminatorlib/plugins/url_handlers.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/terminatorlib/plugins/url_handlers.py b/terminatorlib/plugins/url_handlers.py index fbad40be..9cc39806 100644 --- a/terminatorlib/plugins/url_handlers.py +++ b/terminatorlib/plugins/url_handlers.py @@ -5,7 +5,7 @@ import re import plugin # Every plugin you want Terminator to load *must* be listed in 'available' -available = ['LaunchpadURLHandler'] +available = ['LaunchpadURLHandler', 'APTURLHandler'] class URLHandler(plugin.Plugin): """Base class for URL handlers""" @@ -31,3 +31,14 @@ class LaunchpadURLHandler(URLHandler): url = 'https://bugs.launchpad.net/bugs/%s' % item return(url) +class APTURLHandler(URLHandler): + """APT URL handler. If there is a URL that looks like an apturl, handle + it appropriately""" + capabilities = ['url_handler'] + handler_hane = 'apturl' + match = '\\bapt:.*\\b' + + def callback(self, url): + """Actually we don't need to do anything for this to work""" + return(url) + From 60a1b085b4f8e8abfd1426590ac17c36bf1874e7 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 24 Dec 2009 21:35:07 +0000 Subject: [PATCH 200/331] Add a utility function for finding ~/.config/terminator and use it in PLuginRegistry to load plugins from the user's homedir --- terminatorlib/plugin.py | 41 +++++++++++++++++++++++------------------ terminatorlib/util.py | 10 ++++++++++ 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py index fbdd4212..c2399bec 100755 --- a/terminatorlib/plugin.py +++ b/terminatorlib/plugin.py @@ -26,7 +26,7 @@ import sys import os import borg -from util import dbg, err +from util import dbg, err, get_config_dir class Plugin(object): """Definition of our base plugin class""" @@ -52,10 +52,10 @@ class PluginRegistry(borg.Borg): if not self.instances: self.instances = {} if not self.path: + self.path = [] (head, tail) = os.path.split(borg.__file__) - # FIXME: self.path should really be a list so we can have something - # in the users home directory - self.path = os.path.join(head, 'plugins') + self.path.append(os.path.join(head, 'plugins')) + self.path.append(os.path.join(get_config_dir(), 'plugins')) dbg('PluginRegistry::prepare_attributes: Plugin path: %s' % self.path) if not self.done: @@ -67,21 +67,26 @@ class PluginRegistry(borg.Borg): dbg('PluginRegistry::load_plugins: Already loaded') return - sys.path.insert(0, self.path) - files = os.listdir(self.path) - for plugin in files: - pluginpath = os.path.join(self.path, plugin) - if os.path.isfile(pluginpath) and plugin[-3:] == '.py': - dbg('PluginRegistry::load_plugins: Importing plugin %s' % - plugin) - try: - module = __import__(plugin[:-3], None, None, ['']) - for item in getattr(module, 'available'): - if item not in self.instances: - func = getattr(module, item) + for plugindir in self.path: + sys.path.insert(0, plugindir) + try: + files = os.listdir(plugindir) + except OSError: + sys.path.remove(plugindir) + continue + for plugin in files: + pluginpath = os.path.join(plugindir, plugin) + if os.path.isfile(pluginpath) and plugin[-3:] == '.py': + dbg('PluginRegistry::load_plugins: Importing plugin %s' % + plugin) + try: + module = __import__(plugin[:-3], None, None, ['']) + for item in getattr(module, 'available'): + if item not in self.instances: + func = getattr(module, item) self.instances[item] = func() - except Exception as e: - err('PluginRegistry::load_plugins: Importing plugin %s \ + except Exception as e: + err('PluginRegistry::load_plugins: Importing plugin %s \ failed: %s' % (plugin, e)) self.done = True diff --git a/terminatorlib/util.py b/terminatorlib/util.py index 71b77e4f..fb207494 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -123,3 +123,13 @@ def widget_pixbuf(widget, maxsize=None): return(scaledpixbuf) +def get_config_dir(): + """Expand all the messy nonsense for finding where ~/.config/terminator + really is""" + try: + configdir = os.environ['XDG_CONFIG_HOME'] + except KeyError: + configdir = os.path.join(os.path.expanduser('~'), '.config') + + return(os.path.join(configdir, 'terminator')) + From f91d76e9b49014bf13a3e58fa6a89d7fcde5043c Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 25 Dec 2009 21:22:04 +0000 Subject: [PATCH 201/331] Add ConfigObj 4.6.0 from http://www.voidspace.org.uk/python/configobj.html. Add a really simple implementation of config saving --- terminatorlib/config.py | 17 + terminatorlib/configobj.py | 2455 ++++++++++++++++++++++++++++++++++++ 2 files changed, 2472 insertions(+) create mode 100644 terminatorlib/configobj.py diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 6a84517f..dbcb9efe 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -38,6 +38,8 @@ Classes relating to configuration """ import platform +import sys +from configobj import ConfigObj from borg import Borg from util import dbg @@ -193,6 +195,10 @@ class Config(object): """Set our profile (which usually means change it)""" self.profile = profile + def save(self): + """Cause ConfigBase to save our config to file""" + return(self.base.save()) + class ConfigBase(Borg): """Class to provide access to our user configuration""" global_config = None @@ -264,6 +270,17 @@ class ConfigBase(Borg): return(True) + def save(self): + """Save the config to a file""" + sections = ['global_config', 'keybindings', 'profiles', 'plugins'] + parser = ConfigObj() + parser.indent_type = ' ' + for section_name in sections: + section = getattr(self, section_name) + parser[section_name] = section + + parser.write(sys.stdout) + if __name__ == '__main__': import doctest (failed, attempted) = doctest.testmod() diff --git a/terminatorlib/configobj.py b/terminatorlib/configobj.py new file mode 100644 index 00000000..fd61bcd2 --- /dev/null +++ b/terminatorlib/configobj.py @@ -0,0 +1,2455 @@ +# configobj.py +# A config file reader/writer that supports nested sections in config files. +# Copyright (C) 2005-2009 Michael Foord, Nicola Larosa +# E-mail: fuzzyman AT voidspace DOT org DOT uk +# nico AT tekNico DOT net + +# ConfigObj 4 +# http://www.voidspace.org.uk/python/configobj.html + +# Released subject to the BSD License +# Please see http://www.voidspace.org.uk/python/license.shtml + +# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml +# For information about bugfixes, updates and support, please join the +# ConfigObj mailing list: +# http://lists.sourceforge.net/lists/listinfo/configobj-develop +# Comments, suggestions and bug reports welcome. + + +from __future__ import generators + +import sys +import os +import re + +compiler = None +try: + import compiler +except ImportError: + # for IronPython + pass + + +try: + from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE +except ImportError: + # Python 2.2 does not have these + # UTF-8 + BOM_UTF8 = '\xef\xbb\xbf' + # UTF-16, little endian + BOM_UTF16_LE = '\xff\xfe' + # UTF-16, big endian + BOM_UTF16_BE = '\xfe\xff' + if sys.byteorder == 'little': + # UTF-16, native endianness + BOM_UTF16 = BOM_UTF16_LE + else: + # UTF-16, native endianness + BOM_UTF16 = BOM_UTF16_BE + +# A dictionary mapping BOM to +# the encoding to decode with, and what to set the +# encoding attribute to. +BOMS = { + BOM_UTF8: ('utf_8', None), + BOM_UTF16_BE: ('utf16_be', 'utf_16'), + BOM_UTF16_LE: ('utf16_le', 'utf_16'), + BOM_UTF16: ('utf_16', 'utf_16'), + } +# All legal variants of the BOM codecs. +# TODO: the list of aliases is not meant to be exhaustive, is there a +# better way ? +BOM_LIST = { + 'utf_16': 'utf_16', + 'u16': 'utf_16', + 'utf16': 'utf_16', + 'utf-16': 'utf_16', + 'utf16_be': 'utf16_be', + 'utf_16_be': 'utf16_be', + 'utf-16be': 'utf16_be', + 'utf16_le': 'utf16_le', + 'utf_16_le': 'utf16_le', + 'utf-16le': 'utf16_le', + 'utf_8': 'utf_8', + 'u8': 'utf_8', + 'utf': 'utf_8', + 'utf8': 'utf_8', + 'utf-8': 'utf_8', + } + +# Map of encodings to the BOM to write. +BOM_SET = { + 'utf_8': BOM_UTF8, + 'utf_16': BOM_UTF16, + 'utf16_be': BOM_UTF16_BE, + 'utf16_le': BOM_UTF16_LE, + None: BOM_UTF8 + } + + +def match_utf8(encoding): + return BOM_LIST.get(encoding.lower()) == 'utf_8' + + +# Quote strings used for writing values +squot = "'%s'" +dquot = '"%s"' +noquot = "%s" +wspace_plus = ' \r\n\v\t\'"' +tsquot = '"""%s"""' +tdquot = "'''%s'''" + +try: + enumerate +except NameError: + def enumerate(obj): + """enumerate for Python 2.2.""" + i = -1 + for item in obj: + i += 1 + yield i, item + +# Sentinel for use in getattr calls to replace hasattr +MISSING = object() + +__version__ = '4.6.0' + +__revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $' + +__docformat__ = "restructuredtext en" + +__all__ = ( + '__version__', + 'DEFAULT_INDENT_TYPE', + 'DEFAULT_INTERPOLATION', + 'ConfigObjError', + 'NestingError', + 'ParseError', + 'DuplicateError', + 'ConfigspecError', + 'ConfigObj', + 'SimpleVal', + 'InterpolationError', + 'InterpolationLoopError', + 'MissingInterpolationOption', + 'RepeatSectionError', + 'ReloadError', + 'UnreprError', + 'UnknownType', + '__docformat__', + 'flatten_errors', +) + +DEFAULT_INTERPOLATION = 'configparser' +DEFAULT_INDENT_TYPE = ' ' +MAX_INTERPOL_DEPTH = 10 + +OPTION_DEFAULTS = { + 'interpolation': True, + 'raise_errors': False, + 'list_values': True, + 'create_empty': False, + 'file_error': False, + 'configspec': None, + 'stringify': True, + # option may be set to one of ('', ' ', '\t') + 'indent_type': None, + 'encoding': None, + 'default_encoding': None, + 'unrepr': False, + 'write_empty_values': False, +} + + + +def getObj(s): + s = "a=" + s + if compiler is None: + raise ImportError('compiler module not available') + p = compiler.parse(s) + return p.getChildren()[1].getChildren()[0].getChildren()[1] + + +class UnknownType(Exception): + pass + + +class Builder(object): + + def build(self, o): + m = getattr(self, 'build_' + o.__class__.__name__, None) + if m is None: + raise UnknownType(o.__class__.__name__) + return m(o) + + def build_List(self, o): + return map(self.build, o.getChildren()) + + def build_Const(self, o): + return o.value + + def build_Dict(self, o): + d = {} + i = iter(map(self.build, o.getChildren())) + for el in i: + d[el] = i.next() + return d + + def build_Tuple(self, o): + return tuple(self.build_List(o)) + + def build_Name(self, o): + if o.name == 'None': + return None + if o.name == 'True': + return True + if o.name == 'False': + return False + + # An undefined Name + raise UnknownType('Undefined Name') + + def build_Add(self, o): + real, imag = map(self.build_Const, o.getChildren()) + try: + real = float(real) + except TypeError: + raise UnknownType('Add') + if not isinstance(imag, complex) or imag.real != 0.0: + raise UnknownType('Add') + return real+imag + + def build_Getattr(self, o): + parent = self.build(o.expr) + return getattr(parent, o.attrname) + + def build_UnarySub(self, o): + return -self.build_Const(o.getChildren()[0]) + + def build_UnaryAdd(self, o): + return self.build_Const(o.getChildren()[0]) + + +_builder = Builder() + + +def unrepr(s): + if not s: + return s + return _builder.build(getObj(s)) + + + +class ConfigObjError(SyntaxError): + """ + This is the base class for all errors that ConfigObj raises. + It is a subclass of SyntaxError. + """ + def __init__(self, message='', line_number=None, line=''): + self.line = line + self.line_number = line_number + SyntaxError.__init__(self, message) + + +class NestingError(ConfigObjError): + """ + This error indicates a level of nesting that doesn't match. + """ + + +class ParseError(ConfigObjError): + """ + This error indicates that a line is badly written. + It is neither a valid ``key = value`` line, + nor a valid section marker line. + """ + + +class ReloadError(IOError): + """ + A 'reload' operation failed. + This exception is a subclass of ``IOError``. + """ + def __init__(self): + IOError.__init__(self, 'reload failed, filename is not set.') + + +class DuplicateError(ConfigObjError): + """ + The keyword or section specified already exists. + """ + + +class ConfigspecError(ConfigObjError): + """ + An error occured whilst parsing a configspec. + """ + + +class InterpolationError(ConfigObjError): + """Base class for the two interpolation errors.""" + + +class InterpolationLoopError(InterpolationError): + """Maximum interpolation depth exceeded in string interpolation.""" + + def __init__(self, option): + InterpolationError.__init__( + self, + 'interpolation loop detected in value "%s".' % option) + + +class RepeatSectionError(ConfigObjError): + """ + This error indicates additional sections in a section with a + ``__many__`` (repeated) section. + """ + + +class MissingInterpolationOption(InterpolationError): + """A value specified for interpolation was missing.""" + + def __init__(self, option): + InterpolationError.__init__( + self, + 'missing option "%s" in interpolation.' % option) + + +class UnreprError(ConfigObjError): + """An error parsing in unrepr mode.""" + + + +class InterpolationEngine(object): + """ + A helper class to help perform string interpolation. + + This class is an abstract base class; its descendants perform + the actual work. + """ + + # compiled regexp to use in self.interpolate() + _KEYCRE = re.compile(r"%\(([^)]*)\)s") + + def __init__(self, section): + # the Section instance that "owns" this engine + self.section = section + + + def interpolate(self, key, value): + def recursive_interpolate(key, value, section, backtrail): + """The function that does the actual work. + + ``value``: the string we're trying to interpolate. + ``section``: the section in which that string was found + ``backtrail``: a dict to keep track of where we've been, + to detect and prevent infinite recursion loops + + This is similar to a depth-first-search algorithm. + """ + # Have we been here already? + if backtrail.has_key((key, section.name)): + # Yes - infinite loop detected + raise InterpolationLoopError(key) + # Place a marker on our backtrail so we won't come back here again + backtrail[(key, section.name)] = 1 + + # Now start the actual work + match = self._KEYCRE.search(value) + while match: + # The actual parsing of the match is implementation-dependent, + # so delegate to our helper function + k, v, s = self._parse_match(match) + if k is None: + # That's the signal that no further interpolation is needed + replacement = v + else: + # Further interpolation may be needed to obtain final value + replacement = recursive_interpolate(k, v, s, backtrail) + # Replace the matched string with its final value + start, end = match.span() + value = ''.join((value[:start], replacement, value[end:])) + new_search_start = start + len(replacement) + # Pick up the next interpolation key, if any, for next time + # through the while loop + match = self._KEYCRE.search(value, new_search_start) + + # Now safe to come back here again; remove marker from backtrail + del backtrail[(key, section.name)] + + return value + + # Back in interpolate(), all we have to do is kick off the recursive + # function with appropriate starting values + value = recursive_interpolate(key, value, self.section, {}) + return value + + + def _fetch(self, key): + """Helper function to fetch values from owning section. + + Returns a 2-tuple: the value, and the section where it was found. + """ + # switch off interpolation before we try and fetch anything ! + save_interp = self.section.main.interpolation + self.section.main.interpolation = False + + # Start at section that "owns" this InterpolationEngine + current_section = self.section + while True: + # try the current section first + val = current_section.get(key) + if val is not None: + break + # try "DEFAULT" next + val = current_section.get('DEFAULT', {}).get(key) + if val is not None: + break + # move up to parent and try again + # top-level's parent is itself + if current_section.parent is current_section: + # reached top level, time to give up + break + current_section = current_section.parent + + # restore interpolation to previous value before returning + self.section.main.interpolation = save_interp + if val is None: + raise MissingInterpolationOption(key) + return val, current_section + + + def _parse_match(self, match): + """Implementation-dependent helper function. + + Will be passed a match object corresponding to the interpolation + key we just found (e.g., "%(foo)s" or "$foo"). Should look up that + key in the appropriate config file section (using the ``_fetch()`` + helper function) and return a 3-tuple: (key, value, section) + + ``key`` is the name of the key we're looking for + ``value`` is the value found for that key + ``section`` is a reference to the section where it was found + + ``key`` and ``section`` should be None if no further + interpolation should be performed on the resulting value + (e.g., if we interpolated "$$" and returned "$"). + """ + raise NotImplementedError() + + + +class ConfigParserInterpolation(InterpolationEngine): + """Behaves like ConfigParser.""" + _KEYCRE = re.compile(r"%\(([^)]*)\)s") + + def _parse_match(self, match): + key = match.group(1) + value, section = self._fetch(key) + return key, value, section + + + +class TemplateInterpolation(InterpolationEngine): + """Behaves like string.Template.""" + _delimiter = '$' + _KEYCRE = re.compile(r""" + \$(?: + (?P\$) | # Two $ signs + (?P[_a-z][_a-z0-9]*) | # $name format + {(?P[^}]*)} # ${name} format + ) + """, re.IGNORECASE | re.VERBOSE) + + def _parse_match(self, match): + # Valid name (in or out of braces): fetch value from section + key = match.group('named') or match.group('braced') + if key is not None: + value, section = self._fetch(key) + return key, value, section + # Escaped delimiter (e.g., $$): return single delimiter + if match.group('escaped') is not None: + # Return None for key and section to indicate it's time to stop + return None, self._delimiter, None + # Anything else: ignore completely, just return it unchanged + return None, match.group(), None + + +interpolation_engines = { + 'configparser': ConfigParserInterpolation, + 'template': TemplateInterpolation, +} + + +def __newobj__(cls, *args): + # Hack for pickle + return cls.__new__(cls, *args) + +class Section(dict): + """ + A dictionary-like object that represents a section in a config file. + + It does string interpolation if the 'interpolation' attribute + of the 'main' object is set to True. + + Interpolation is tried first from this object, then from the 'DEFAULT' + section of this object, next from the parent and its 'DEFAULT' section, + and so on until the main object is reached. + + A Section will behave like an ordered dictionary - following the + order of the ``scalars`` and ``sections`` attributes. + You can use this to change the order of members. + + Iteration follows the order: scalars, then sections. + """ + + + def __setstate__(self, state): + dict.update(self, state[0]) + self.__dict__.update(state[1]) + + def __reduce__(self): + state = (dict(self), self.__dict__) + return (__newobj__, (self.__class__,), state) + + + def __init__(self, parent, depth, main, indict=None, name=None): + """ + * parent is the section above + * depth is the depth level of this section + * main is the main ConfigObj + * indict is a dictionary to initialise the section with + """ + if indict is None: + indict = {} + dict.__init__(self) + # used for nesting level *and* interpolation + self.parent = parent + # used for the interpolation attribute + self.main = main + # level of nesting depth of this Section + self.depth = depth + # purely for information + self.name = name + # + self._initialise() + # we do this explicitly so that __setitem__ is used properly + # (rather than just passing to ``dict.__init__``) + for entry, value in indict.iteritems(): + self[entry] = value + + + def _initialise(self): + # the sequence of scalar values in this Section + self.scalars = [] + # the sequence of sections in this Section + self.sections = [] + # for comments :-) + self.comments = {} + self.inline_comments = {} + # the configspec + self.configspec = None + # for defaults + self.defaults = [] + self.default_values = {} + + + def _interpolate(self, key, value): + try: + # do we already have an interpolation engine? + engine = self._interpolation_engine + except AttributeError: + # not yet: first time running _interpolate(), so pick the engine + name = self.main.interpolation + if name == True: # note that "if name:" would be incorrect here + # backwards-compatibility: interpolation=True means use default + name = DEFAULT_INTERPOLATION + name = name.lower() # so that "Template", "template", etc. all work + class_ = interpolation_engines.get(name, None) + if class_ is None: + # invalid value for self.main.interpolation + self.main.interpolation = False + return value + else: + # save reference to engine so we don't have to do this again + engine = self._interpolation_engine = class_(self) + # let the engine do the actual work + return engine.interpolate(key, value) + + + def __getitem__(self, key): + """Fetch the item and do string interpolation.""" + val = dict.__getitem__(self, key) + if self.main.interpolation and isinstance(val, basestring): + return self._interpolate(key, val) + return val + + + def __setitem__(self, key, value, unrepr=False): + """ + Correctly set a value. + + Making dictionary values Section instances. + (We have to special case 'Section' instances - which are also dicts) + + Keys must be strings. + Values need only be strings (or lists of strings) if + ``main.stringify`` is set. + + ``unrepr`` must be set when setting a value to a dictionary, without + creating a new sub-section. + """ + if not isinstance(key, basestring): + raise ValueError('The key "%s" is not a string.' % key) + + # add the comment + if not self.comments.has_key(key): + self.comments[key] = [] + self.inline_comments[key] = '' + # remove the entry from defaults + if key in self.defaults: + self.defaults.remove(key) + # + if isinstance(value, Section): + if not self.has_key(key): + self.sections.append(key) + dict.__setitem__(self, key, value) + elif isinstance(value, dict) and not unrepr: + # First create the new depth level, + # then create the section + if not self.has_key(key): + self.sections.append(key) + new_depth = self.depth + 1 + dict.__setitem__( + self, + key, + Section( + self, + new_depth, + self.main, + indict=value, + name=key)) + else: + if not self.has_key(key): + self.scalars.append(key) + if not self.main.stringify: + if isinstance(value, basestring): + pass + elif isinstance(value, (list, tuple)): + for entry in value: + if not isinstance(entry, basestring): + raise TypeError('Value is not a string "%s".' % entry) + else: + raise TypeError('Value is not a string "%s".' % value) + dict.__setitem__(self, key, value) + + + def __delitem__(self, key): + """Remove items from the sequence when deleting.""" + dict. __delitem__(self, key) + if key in self.scalars: + self.scalars.remove(key) + else: + self.sections.remove(key) + del self.comments[key] + del self.inline_comments[key] + + + def get(self, key, default=None): + """A version of ``get`` that doesn't bypass string interpolation.""" + try: + return self[key] + except KeyError: + return default + + + def update(self, indict): + """ + A version of update that uses our ``__setitem__``. + """ + for entry in indict: + self[entry] = indict[entry] + + + def pop(self, key, *args): + """ + 'D.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised' + """ + val = dict.pop(self, key, *args) + if key in self.scalars: + del self.comments[key] + del self.inline_comments[key] + self.scalars.remove(key) + elif key in self.sections: + del self.comments[key] + del self.inline_comments[key] + self.sections.remove(key) + if self.main.interpolation and isinstance(val, basestring): + return self._interpolate(key, val) + return val + + + def popitem(self): + """Pops the first (key,val)""" + sequence = (self.scalars + self.sections) + if not sequence: + raise KeyError(": 'popitem(): dictionary is empty'") + key = sequence[0] + val = self[key] + del self[key] + return key, val + + + def clear(self): + """ + A version of clear that also affects scalars/sections + Also clears comments and configspec. + + Leaves other attributes alone : + depth/main/parent are not affected + """ + dict.clear(self) + self.scalars = [] + self.sections = [] + self.comments = {} + self.inline_comments = {} + self.configspec = None + + + def setdefault(self, key, default=None): + """A version of setdefault that sets sequence if appropriate.""" + try: + return self[key] + except KeyError: + self[key] = default + return self[key] + + + def items(self): + """D.items() -> list of D's (key, value) pairs, as 2-tuples""" + return zip((self.scalars + self.sections), self.values()) + + + def keys(self): + """D.keys() -> list of D's keys""" + return (self.scalars + self.sections) + + + def values(self): + """D.values() -> list of D's values""" + return [self[key] for key in (self.scalars + self.sections)] + + + def iteritems(self): + """D.iteritems() -> an iterator over the (key, value) items of D""" + return iter(self.items()) + + + def iterkeys(self): + """D.iterkeys() -> an iterator over the keys of D""" + return iter((self.scalars + self.sections)) + + __iter__ = iterkeys + + + def itervalues(self): + """D.itervalues() -> an iterator over the values of D""" + return iter(self.values()) + + + def __repr__(self): + """x.__repr__() <==> repr(x)""" + return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key]))) + for key in (self.scalars + self.sections)]) + + __str__ = __repr__ + __str__.__doc__ = "x.__str__() <==> str(x)" + + + # Extra methods - not in a normal dictionary + + def dict(self): + """ + Return a deepcopy of self as a dictionary. + + All members that are ``Section`` instances are recursively turned to + ordinary dictionaries - by calling their ``dict`` method. + + >>> n = a.dict() + >>> n == a + 1 + >>> n is a + 0 + """ + newdict = {} + for entry in self: + this_entry = self[entry] + if isinstance(this_entry, Section): + this_entry = this_entry.dict() + elif isinstance(this_entry, list): + # create a copy rather than a reference + this_entry = list(this_entry) + elif isinstance(this_entry, tuple): + # create a copy rather than a reference + this_entry = tuple(this_entry) + newdict[entry] = this_entry + return newdict + + + def merge(self, indict): + """ + A recursive update - useful for merging config files. + + >>> a = '''[section1] + ... option1 = True + ... [[subsection]] + ... more_options = False + ... # end of file'''.splitlines() + >>> b = '''# File is user.ini + ... [section1] + ... option1 = False + ... # end of file'''.splitlines() + >>> c1 = ConfigObj(b) + >>> c2 = ConfigObj(a) + >>> c2.merge(c1) + >>> c2 + ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}) + """ + for key, val in indict.items(): + if (key in self and isinstance(self[key], dict) and + isinstance(val, dict)): + self[key].merge(val) + else: + self[key] = val + + + def rename(self, oldkey, newkey): + """ + Change a keyname to another, without changing position in sequence. + + Implemented so that transformations can be made on keys, + as well as on values. (used by encode and decode) + + Also renames comments. + """ + if oldkey in self.scalars: + the_list = self.scalars + elif oldkey in self.sections: + the_list = self.sections + else: + raise KeyError('Key "%s" not found.' % oldkey) + pos = the_list.index(oldkey) + # + val = self[oldkey] + dict.__delitem__(self, oldkey) + dict.__setitem__(self, newkey, val) + the_list.remove(oldkey) + the_list.insert(pos, newkey) + comm = self.comments[oldkey] + inline_comment = self.inline_comments[oldkey] + del self.comments[oldkey] + del self.inline_comments[oldkey] + self.comments[newkey] = comm + self.inline_comments[newkey] = inline_comment + + + def walk(self, function, raise_errors=True, + call_on_sections=False, **keywargs): + """ + Walk every member and call a function on the keyword and value. + + Return a dictionary of the return values + + If the function raises an exception, raise the errror + unless ``raise_errors=False``, in which case set the return value to + ``False``. + + Any unrecognised keyword arguments you pass to walk, will be pased on + to the function you pass in. + + Note: if ``call_on_sections`` is ``True`` then - on encountering a + subsection, *first* the function is called for the *whole* subsection, + and then recurses into it's members. This means your function must be + able to handle strings, dictionaries and lists. This allows you + to change the key of subsections as well as for ordinary members. The + return value when called on the whole subsection has to be discarded. + + See the encode and decode methods for examples, including functions. + + .. admonition:: caution + + You can use ``walk`` to transform the names of members of a section + but you mustn't add or delete members. + + >>> config = '''[XXXXsection] + ... XXXXkey = XXXXvalue'''.splitlines() + >>> cfg = ConfigObj(config) + >>> cfg + ConfigObj({'XXXXsection': {'XXXXkey': 'XXXXvalue'}}) + >>> def transform(section, key): + ... val = section[key] + ... newkey = key.replace('XXXX', 'CLIENT1') + ... section.rename(key, newkey) + ... if isinstance(val, (tuple, list, dict)): + ... pass + ... else: + ... val = val.replace('XXXX', 'CLIENT1') + ... section[newkey] = val + >>> cfg.walk(transform, call_on_sections=True) + {'CLIENT1section': {'CLIENT1key': None}} + >>> cfg + ConfigObj({'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}) + """ + out = {} + # scalars first + for i in range(len(self.scalars)): + entry = self.scalars[i] + try: + val = function(self, entry, **keywargs) + # bound again in case name has changed + entry = self.scalars[i] + out[entry] = val + except Exception: + if raise_errors: + raise + else: + entry = self.scalars[i] + out[entry] = False + # then sections + for i in range(len(self.sections)): + entry = self.sections[i] + if call_on_sections: + try: + function(self, entry, **keywargs) + except Exception: + if raise_errors: + raise + else: + entry = self.sections[i] + out[entry] = False + # bound again in case name has changed + entry = self.sections[i] + # previous result is discarded + out[entry] = self[entry].walk( + function, + raise_errors=raise_errors, + call_on_sections=call_on_sections, + **keywargs) + return out + + + def as_bool(self, key): + """ + Accepts a key as input. The corresponding value must be a string or + the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to + retain compatibility with Python 2.2. + + If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns + ``True``. + + If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns + ``False``. + + ``as_bool`` is not case sensitive. + + Any other input will raise a ``ValueError``. + + >>> a = ConfigObj() + >>> a['a'] = 'fish' + >>> a.as_bool('a') + Traceback (most recent call last): + ValueError: Value "fish" is neither True nor False + >>> a['b'] = 'True' + >>> a.as_bool('b') + 1 + >>> a['b'] = 'off' + >>> a.as_bool('b') + 0 + """ + val = self[key] + if val == True: + return True + elif val == False: + return False + else: + try: + if not isinstance(val, basestring): + # TODO: Why do we raise a KeyError here? + raise KeyError() + else: + return self.main._bools[val.lower()] + except KeyError: + raise ValueError('Value "%s" is neither True nor False' % val) + + + def as_int(self, key): + """ + A convenience method which coerces the specified value to an integer. + + If the value is an invalid literal for ``int``, a ``ValueError`` will + be raised. + + >>> a = ConfigObj() + >>> a['a'] = 'fish' + >>> a.as_int('a') + Traceback (most recent call last): + ValueError: invalid literal for int() with base 10: 'fish' + >>> a['b'] = '1' + >>> a.as_int('b') + 1 + >>> a['b'] = '3.2' + >>> a.as_int('b') + Traceback (most recent call last): + ValueError: invalid literal for int() with base 10: '3.2' + """ + return int(self[key]) + + + def as_float(self, key): + """ + A convenience method which coerces the specified value to a float. + + If the value is an invalid literal for ``float``, a ``ValueError`` will + be raised. + + >>> a = ConfigObj() + >>> a['a'] = 'fish' + >>> a.as_float('a') + Traceback (most recent call last): + ValueError: invalid literal for float(): fish + >>> a['b'] = '1' + >>> a.as_float('b') + 1.0 + >>> a['b'] = '3.2' + >>> a.as_float('b') + 3.2000000000000002 + """ + return float(self[key]) + + + def as_list(self, key): + """ + A convenience method which fetches the specified value, guaranteeing + that it is a list. + + >>> a = ConfigObj() + >>> a['a'] = 1 + >>> a.as_list('a') + [1] + >>> a['a'] = (1,) + >>> a.as_list('a') + [1] + >>> a['a'] = [1] + >>> a.as_list('a') + [1] + """ + result = self[key] + if isinstance(result, (tuple, list)): + return list(result) + return [result] + + + def restore_default(self, key): + """ + Restore (and return) default value for the specified key. + + This method will only work for a ConfigObj that was created + with a configspec and has been validated. + + If there is no default value for this key, ``KeyError`` is raised. + """ + default = self.default_values[key] + dict.__setitem__(self, key, default) + if key not in self.defaults: + self.defaults.append(key) + return default + + + def restore_defaults(self): + """ + Recursively restore default values to all members + that have them. + + This method will only work for a ConfigObj that was created + with a configspec and has been validated. + + It doesn't delete or modify entries without default values. + """ + for key in self.default_values: + self.restore_default(key) + + for section in self.sections: + self[section].restore_defaults() + + +class ConfigObj(Section): + """An object to read, create, and write config files.""" + + _keyword = re.compile(r'''^ # line start + (\s*) # indentation + ( # keyword + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'"=].*?) # no quotes + ) + \s*=\s* # divider + (.*) # value (including list values and comments) + $ # line end + ''', + re.VERBOSE) + + _sectionmarker = re.compile(r'''^ + (\s*) # 1: indentation + ((?:\[\s*)+) # 2: section marker open + ( # 3: section name open + (?:"\s*\S.*?\s*")| # at least one non-space with double quotes + (?:'\s*\S.*?\s*')| # at least one non-space with single quotes + (?:[^'"\s].*?) # at least one non-space unquoted + ) # section name close + ((?:\s*\])+) # 4: section marker close + \s*(\#.*)? # 5: optional comment + $''', + re.VERBOSE) + + # this regexp pulls list values out as a single string + # or single values and comments + # FIXME: this regex adds a '' to the end of comma terminated lists + # workaround in ``_handle_value`` + _valueexp = re.compile(r'''^ + (?: + (?: + ( + (?: + (?: + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'",\#][^,\#]*?) # unquoted + ) + \s*,\s* # comma + )* # match all list items ending in a comma (if any) + ) + ( + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'",\#\s][^,]*?)| # unquoted + (?:(? 1: + msg = "Parsing failed with several errors.\nFirst error %s" % info + error = ConfigObjError(msg) + else: + error = self._errors[0] + # set the errors attribute; it's a list of tuples: + # (error_type, message, line_number) + error.errors = self._errors + # set the config attribute + error.config = self + raise error + # delete private attributes + del self._errors + + if configspec is None: + self.configspec = None + else: + self._handle_configspec(configspec) + + + def _initialise(self, options=None): + if options is None: + options = OPTION_DEFAULTS + + # initialise a few variables + self.filename = None + self._errors = [] + self.raise_errors = options['raise_errors'] + self.interpolation = options['interpolation'] + self.list_values = options['list_values'] + self.create_empty = options['create_empty'] + self.file_error = options['file_error'] + self.stringify = options['stringify'] + self.indent_type = options['indent_type'] + self.encoding = options['encoding'] + self.default_encoding = options['default_encoding'] + self.BOM = False + self.newlines = None + self.write_empty_values = options['write_empty_values'] + self.unrepr = options['unrepr'] + + self.initial_comment = [] + self.final_comment = [] + self.configspec = None + + if self._inspec: + self.list_values = False + + # Clear section attributes as well + Section._initialise(self) + + + def __repr__(self): + return ('ConfigObj({%s})' % + ', '.join([('%s: %s' % (repr(key), repr(self[key]))) + for key in (self.scalars + self.sections)])) + + + def _handle_bom(self, infile): + """ + Handle any BOM, and decode if necessary. + + If an encoding is specified, that *must* be used - but the BOM should + still be removed (and the BOM attribute set). + + (If the encoding is wrongly specified, then a BOM for an alternative + encoding won't be discovered or removed.) + + If an encoding is not specified, UTF8 or UTF16 BOM will be detected and + removed. The BOM attribute will be set. UTF16 will be decoded to + unicode. + + NOTE: This method must not be called with an empty ``infile``. + + Specifying the *wrong* encoding is likely to cause a + ``UnicodeDecodeError``. + + ``infile`` must always be returned as a list of lines, but may be + passed in as a single string. + """ + if ((self.encoding is not None) and + (self.encoding.lower() not in BOM_LIST)): + # No need to check for a BOM + # the encoding specified doesn't have one + # just decode + return self._decode(infile, self.encoding) + + if isinstance(infile, (list, tuple)): + line = infile[0] + else: + line = infile + if self.encoding is not None: + # encoding explicitly supplied + # And it could have an associated BOM + # TODO: if encoding is just UTF16 - we ought to check for both + # TODO: big endian and little endian versions. + enc = BOM_LIST[self.encoding.lower()] + if enc == 'utf_16': + # For UTF16 we try big endian and little endian + for BOM, (encoding, final_encoding) in BOMS.items(): + if not final_encoding: + # skip UTF8 + continue + if infile.startswith(BOM): + ### BOM discovered + ##self.BOM = True + # Don't need to remove BOM + return self._decode(infile, encoding) + + # If we get this far, will *probably* raise a DecodeError + # As it doesn't appear to start with a BOM + return self._decode(infile, self.encoding) + + # Must be UTF8 + BOM = BOM_SET[enc] + if not line.startswith(BOM): + return self._decode(infile, self.encoding) + + newline = line[len(BOM):] + + # BOM removed + if isinstance(infile, (list, tuple)): + infile[0] = newline + else: + infile = newline + self.BOM = True + return self._decode(infile, self.encoding) + + # No encoding specified - so we need to check for UTF8/UTF16 + for BOM, (encoding, final_encoding) in BOMS.items(): + if not line.startswith(BOM): + continue + else: + # BOM discovered + self.encoding = final_encoding + if not final_encoding: + self.BOM = True + # UTF8 + # remove BOM + newline = line[len(BOM):] + if isinstance(infile, (list, tuple)): + infile[0] = newline + else: + infile = newline + # UTF8 - don't decode + if isinstance(infile, basestring): + return infile.splitlines(True) + else: + return infile + # UTF16 - have to decode + return self._decode(infile, encoding) + + # No BOM discovered and no encoding specified, just return + if isinstance(infile, basestring): + # infile read from a file will be a single string + return infile.splitlines(True) + return infile + + + def _a_to_u(self, aString): + """Decode ASCII strings to unicode if a self.encoding is specified.""" + if self.encoding: + return aString.decode('ascii') + else: + return aString + + + def _decode(self, infile, encoding): + """ + Decode infile to unicode. Using the specified encoding. + + if is a string, it also needs converting to a list. + """ + if isinstance(infile, basestring): + # can't be unicode + # NOTE: Could raise a ``UnicodeDecodeError`` + return infile.decode(encoding).splitlines(True) + for i, line in enumerate(infile): + if not isinstance(line, unicode): + # NOTE: The isinstance test here handles mixed lists of unicode/string + # NOTE: But the decode will break on any non-string values + # NOTE: Or could raise a ``UnicodeDecodeError`` + infile[i] = line.decode(encoding) + return infile + + + def _decode_element(self, line): + """Decode element to unicode if necessary.""" + if not self.encoding: + return line + if isinstance(line, str) and self.default_encoding: + return line.decode(self.default_encoding) + return line + + + def _str(self, value): + """ + Used by ``stringify`` within validate, to turn non-string values + into strings. + """ + if not isinstance(value, basestring): + return str(value) + else: + return value + + + def _parse(self, infile): + """Actually parse the config file.""" + temp_list_values = self.list_values + if self.unrepr: + self.list_values = False + + comment_list = [] + done_start = False + this_section = self + maxline = len(infile) - 1 + cur_index = -1 + reset_comment = False + + while cur_index < maxline: + if reset_comment: + comment_list = [] + cur_index += 1 + line = infile[cur_index] + sline = line.strip() + # do we have anything on the line ? + if not sline or sline.startswith('#'): + reset_comment = False + comment_list.append(line) + continue + + if not done_start: + # preserve initial comment + self.initial_comment = comment_list + comment_list = [] + done_start = True + + reset_comment = True + # first we check if it's a section marker + mat = self._sectionmarker.match(line) + if mat is not None: + # is a section line + (indent, sect_open, sect_name, sect_close, comment) = mat.groups() + if indent and (self.indent_type is None): + self.indent_type = indent + cur_depth = sect_open.count('[') + if cur_depth != sect_close.count(']'): + self._handle_error("Cannot compute the section depth at line %s.", + NestingError, infile, cur_index) + continue + + if cur_depth < this_section.depth: + # the new section is dropping back to a previous level + try: + parent = self._match_depth(this_section, + cur_depth).parent + except SyntaxError: + self._handle_error("Cannot compute nesting level at line %s.", + NestingError, infile, cur_index) + continue + elif cur_depth == this_section.depth: + # the new section is a sibling of the current section + parent = this_section.parent + elif cur_depth == this_section.depth + 1: + # the new section is a child the current section + parent = this_section + else: + self._handle_error("Section too nested at line %s.", + NestingError, infile, cur_index) + + sect_name = self._unquote(sect_name) + if parent.has_key(sect_name): + self._handle_error('Duplicate section name at line %s.', + DuplicateError, infile, cur_index) + continue + + # create the new section + this_section = Section( + parent, + cur_depth, + self, + name=sect_name) + parent[sect_name] = this_section + parent.inline_comments[sect_name] = comment + parent.comments[sect_name] = comment_list + continue + # + # it's not a section marker, + # so it should be a valid ``key = value`` line + mat = self._keyword.match(line) + if mat is None: + # it neither matched as a keyword + # or a section marker + self._handle_error( + 'Invalid line at line "%s".', + ParseError, infile, cur_index) + else: + # is a keyword value + # value will include any inline comment + (indent, key, value) = mat.groups() + if indent and (self.indent_type is None): + self.indent_type = indent + # check for a multiline value + if value[:3] in ['"""', "'''"]: + try: + (value, comment, cur_index) = self._multiline( + value, infile, cur_index, maxline) + except SyntaxError: + self._handle_error( + 'Parse error in value at line %s.', + ParseError, infile, cur_index) + continue + else: + if self.unrepr: + comment = '' + try: + value = unrepr(value) + except Exception, e: + if type(e) == UnknownType: + msg = 'Unknown name or type in value at line %s.' + else: + msg = 'Parse error in value at line %s.' + self._handle_error(msg, UnreprError, infile, + cur_index) + continue + else: + if self.unrepr: + comment = '' + try: + value = unrepr(value) + except Exception, e: + if isinstance(e, UnknownType): + msg = 'Unknown name or type in value at line %s.' + else: + msg = 'Parse error in value at line %s.' + self._handle_error(msg, UnreprError, infile, + cur_index) + continue + else: + # extract comment and lists + try: + (value, comment) = self._handle_value(value) + except SyntaxError: + self._handle_error( + 'Parse error in value at line %s.', + ParseError, infile, cur_index) + continue + # + key = self._unquote(key) + if this_section.has_key(key): + self._handle_error( + 'Duplicate keyword name at line %s.', + DuplicateError, infile, cur_index) + continue + # add the key. + # we set unrepr because if we have got this far we will never + # be creating a new section + this_section.__setitem__(key, value, unrepr=True) + this_section.inline_comments[key] = comment + this_section.comments[key] = comment_list + continue + # + if self.indent_type is None: + # no indentation used, set the type accordingly + self.indent_type = '' + + # preserve the final comment + if not self and not self.initial_comment: + self.initial_comment = comment_list + elif not reset_comment: + self.final_comment = comment_list + self.list_values = temp_list_values + + + def _match_depth(self, sect, depth): + """ + Given a section and a depth level, walk back through the sections + parents to see if the depth level matches a previous section. + + Return a reference to the right section, + or raise a SyntaxError. + """ + while depth < sect.depth: + if sect is sect.parent: + # we've reached the top level already + raise SyntaxError() + sect = sect.parent + if sect.depth == depth: + return sect + # shouldn't get here + raise SyntaxError() + + + def _handle_error(self, text, ErrorClass, infile, cur_index): + """ + Handle an error according to the error settings. + + Either raise the error or store it. + The error will have occured at ``cur_index`` + """ + line = infile[cur_index] + cur_index += 1 + message = text % cur_index + error = ErrorClass(message, cur_index, line) + if self.raise_errors: + # raise the error - parsing stops here + raise error + # store the error + # reraise when parsing has finished + self._errors.append(error) + + + def _unquote(self, value): + """Return an unquoted version of a value""" + if (value[0] == value[-1]) and (value[0] in ('"', "'")): + value = value[1:-1] + return value + + + def _quote(self, value, multiline=True): + """ + Return a safely quoted version of a value. + + Raise a ConfigObjError if the value cannot be safely quoted. + If multiline is ``True`` (default) then use triple quotes + if necessary. + + * Don't quote values that don't need it. + * Recursively quote members of a list and return a comma joined list. + * Multiline is ``False`` for lists. + * Obey list syntax for empty and single member lists. + + If ``list_values=False`` then the value is only quoted if it contains + a ``\\n`` (is multiline) or '#'. + + If ``write_empty_values`` is set, and the value is an empty string, it + won't be quoted. + """ + if multiline and self.write_empty_values and value == '': + # Only if multiline is set, so that it is used for values not + # keys, and not values that are part of a list + return '' + + if multiline and isinstance(value, (list, tuple)): + if not value: + return ',' + elif len(value) == 1: + return self._quote(value[0], multiline=False) + ',' + return ', '.join([self._quote(val, multiline=False) + for val in value]) + if not isinstance(value, basestring): + if self.stringify: + value = str(value) + else: + raise TypeError('Value "%s" is not a string.' % value) + + if not value: + return '""' + + no_lists_no_quotes = not self.list_values and '\n' not in value and '#' not in value + need_triple = multiline and ((("'" in value) and ('"' in value)) or ('\n' in value )) + hash_triple_quote = multiline and not need_triple and ("'" in value) and ('"' in value) and ('#' in value) + check_for_single = (no_lists_no_quotes or not need_triple) and not hash_triple_quote + + if check_for_single: + if not self.list_values: + # we don't quote if ``list_values=False`` + quot = noquot + # for normal values either single or double quotes will do + elif '\n' in value: + # will only happen if multiline is off - e.g. '\n' in key + raise ConfigObjError('Value "%s" cannot be safely quoted.' % value) + elif ((value[0] not in wspace_plus) and + (value[-1] not in wspace_plus) and + (',' not in value)): + quot = noquot + else: + quot = self._get_single_quote(value) + else: + # if value has '\n' or "'" *and* '"', it will need triple quotes + quot = self._get_triple_quote(value) + + if quot == noquot and '#' in value and self.list_values: + quot = self._get_single_quote(value) + + return quot % value + + + def _get_single_quote(self, value): + if ("'" in value) and ('"' in value): + raise ConfigObjError('Value "%s" cannot be safely quoted.' % value) + elif '"' in value: + quot = squot + else: + quot = dquot + return quot + + + def _get_triple_quote(self, value): + if (value.find('"""') != -1) and (value.find("'''") != -1): + raise ConfigObjError('Value "%s" cannot be safely quoted.' % value) + if value.find('"""') == -1: + quot = tdquot + else: + quot = tsquot + return quot + + + def _handle_value(self, value): + """ + Given a value string, unquote, remove comment, + handle lists. (including empty and single member lists) + """ + if self._inspec: + # Parsing a configspec so don't handle comments + return (value, '') + # do we look for lists in values ? + if not self.list_values: + mat = self._nolistvalue.match(value) + if mat is None: + raise SyntaxError() + # NOTE: we don't unquote here + return mat.groups() + # + mat = self._valueexp.match(value) + if mat is None: + # the value is badly constructed, probably badly quoted, + # or an invalid list + raise SyntaxError() + (list_values, single, empty_list, comment) = mat.groups() + if (list_values == '') and (single is None): + # change this if you want to accept empty values + raise SyntaxError() + # NOTE: note there is no error handling from here if the regex + # is wrong: then incorrect values will slip through + if empty_list is not None: + # the single comma - meaning an empty list + return ([], comment) + if single is not None: + # handle empty values + if list_values and not single: + # FIXME: the '' is a workaround because our regex now matches + # '' at the end of a list if it has a trailing comma + single = None + else: + single = single or '""' + single = self._unquote(single) + if list_values == '': + # not a list value + return (single, comment) + the_list = self._listvalueexp.findall(list_values) + the_list = [self._unquote(val) for val in the_list] + if single is not None: + the_list += [single] + return (the_list, comment) + + + def _multiline(self, value, infile, cur_index, maxline): + """Extract the value, where we are in a multiline situation.""" + quot = value[:3] + newvalue = value[3:] + single_line = self._triple_quote[quot][0] + multi_line = self._triple_quote[quot][1] + mat = single_line.match(value) + if mat is not None: + retval = list(mat.groups()) + retval.append(cur_index) + return retval + elif newvalue.find(quot) != -1: + # somehow the triple quote is missing + raise SyntaxError() + # + while cur_index < maxline: + cur_index += 1 + newvalue += '\n' + line = infile[cur_index] + if line.find(quot) == -1: + newvalue += line + else: + # end of multiline, process it + break + else: + # we've got to the end of the config, oops... + raise SyntaxError() + mat = multi_line.match(line) + if mat is None: + # a badly formed line + raise SyntaxError() + (value, comment) = mat.groups() + return (newvalue + value, comment, cur_index) + + + def _handle_configspec(self, configspec): + """Parse the configspec.""" + # FIXME: Should we check that the configspec was created with the + # correct settings ? (i.e. ``list_values=False``) + if not isinstance(configspec, ConfigObj): + try: + configspec = ConfigObj(configspec, + raise_errors=True, + file_error=True, + _inspec=True) + except ConfigObjError, e: + # FIXME: Should these errors have a reference + # to the already parsed ConfigObj ? + raise ConfigspecError('Parsing configspec failed: %s' % e) + except IOError, e: + raise IOError('Reading configspec failed: %s' % e) + + self.configspec = configspec + + + + def _set_configspec(self, section, copy): + """ + Called by validate. Handles setting the configspec on subsections + including sections to be validated by __many__ + """ + configspec = section.configspec + many = configspec.get('__many__') + if isinstance(many, dict): + for entry in section.sections: + if entry not in configspec: + section[entry].configspec = many + + for entry in configspec.sections: + if entry == '__many__': + continue + if entry not in section: + section[entry] = {} + if copy: + # copy comments + section.comments[entry] = configspec.comments.get(entry, []) + section.inline_comments[entry] = configspec.inline_comments.get(entry, '') + + # Could be a scalar when we expect a section + if isinstance(section[entry], Section): + section[entry].configspec = configspec[entry] + + + def _write_line(self, indent_string, entry, this_entry, comment): + """Write an individual line, for the write method""" + # NOTE: the calls to self._quote here handles non-StringType values. + if not self.unrepr: + val = self._decode_element(self._quote(this_entry)) + else: + val = repr(this_entry) + return '%s%s%s%s%s' % (indent_string, + self._decode_element(self._quote(entry, multiline=False)), + self._a_to_u(' = '), + val, + self._decode_element(comment)) + + + def _write_marker(self, indent_string, depth, entry, comment): + """Write a section marker line""" + return '%s%s%s%s%s' % (indent_string, + self._a_to_u('[' * depth), + self._quote(self._decode_element(entry), multiline=False), + self._a_to_u(']' * depth), + self._decode_element(comment)) + + + def _handle_comment(self, comment): + """Deal with a comment.""" + if not comment: + return '' + start = self.indent_type + if not comment.startswith('#'): + start += self._a_to_u(' # ') + return (start + comment) + + + # Public methods + + def write(self, outfile=None, section=None): + """ + Write the current ConfigObj as a file + + tekNico: FIXME: use StringIO instead of real files + + >>> filename = a.filename + >>> a.filename = 'test.ini' + >>> a.write() + >>> a.filename = filename + >>> a == ConfigObj('test.ini', raise_errors=True) + 1 + """ + if self.indent_type is None: + # this can be true if initialised from a dictionary + self.indent_type = DEFAULT_INDENT_TYPE + + out = [] + cs = self._a_to_u('#') + csp = self._a_to_u('# ') + if section is None: + int_val = self.interpolation + self.interpolation = False + section = self + for line in self.initial_comment: + line = self._decode_element(line) + stripped_line = line.strip() + if stripped_line and not stripped_line.startswith(cs): + line = csp + line + out.append(line) + + indent_string = self.indent_type * section.depth + for entry in (section.scalars + section.sections): + if entry in section.defaults: + # don't write out default values + continue + for comment_line in section.comments[entry]: + comment_line = self._decode_element(comment_line.lstrip()) + if comment_line and not comment_line.startswith(cs): + comment_line = csp + comment_line + out.append(indent_string + comment_line) + this_entry = section[entry] + comment = self._handle_comment(section.inline_comments[entry]) + + if isinstance(this_entry, dict): + # a section + out.append(self._write_marker( + indent_string, + this_entry.depth, + entry, + comment)) + out.extend(self.write(section=this_entry)) + else: + out.append(self._write_line( + indent_string, + entry, + this_entry, + comment)) + + if section is self: + for line in self.final_comment: + line = self._decode_element(line) + stripped_line = line.strip() + if stripped_line and not stripped_line.startswith(cs): + line = csp + line + out.append(line) + self.interpolation = int_val + + if section is not self: + return out + + if (self.filename is None) and (outfile is None): + # output a list of lines + # might need to encode + # NOTE: This will *screw* UTF16, each line will start with the BOM + if self.encoding: + out = [l.encode(self.encoding) for l in out] + if (self.BOM and ((self.encoding is None) or + (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))): + # Add the UTF8 BOM + if not out: + out.append('') + out[0] = BOM_UTF8 + out[0] + return out + + # Turn the list to a string, joined with correct newlines + newline = self.newlines or os.linesep + output = self._a_to_u(newline).join(out) + if self.encoding: + output = output.encode(self.encoding) + if self.BOM and ((self.encoding is None) or match_utf8(self.encoding)): + # Add the UTF8 BOM + output = BOM_UTF8 + output + + if not output.endswith(newline): + output += newline + if outfile is not None: + outfile.write(output) + else: + h = open(self.filename, 'wb') + h.write(output) + h.close() + + + def validate(self, validator, preserve_errors=False, copy=False, + section=None): + """ + Test the ConfigObj against a configspec. + + It uses the ``validator`` object from *validate.py*. + + To run ``validate`` on the current ConfigObj, call: :: + + test = config.validate(validator) + + (Normally having previously passed in the configspec when the ConfigObj + was created - you can dynamically assign a dictionary of checks to the + ``configspec`` attribute of a section though). + + It returns ``True`` if everything passes, or a dictionary of + pass/fails (True/False). If every member of a subsection passes, it + will just have the value ``True``. (It also returns ``False`` if all + members fail). + + In addition, it converts the values from strings to their native + types if their checks pass (and ``stringify`` is set). + + If ``preserve_errors`` is ``True`` (``False`` is default) then instead + of a marking a fail with a ``False``, it will preserve the actual + exception object. This can contain info about the reason for failure. + For example the ``VdtValueTooSmallError`` indicates that the value + supplied was too small. If a value (or section) is missing it will + still be marked as ``False``. + + You must have the validate module to use ``preserve_errors=True``. + + You can then use the ``flatten_errors`` function to turn your nested + results dictionary into a flattened list of failures - useful for + displaying meaningful error messages. + """ + if section is None: + if self.configspec is None: + raise ValueError('No configspec supplied.') + if preserve_errors: + # We do this once to remove a top level dependency on the validate module + # Which makes importing configobj faster + from validate import VdtMissingValue + self._vdtMissingValue = VdtMissingValue + + section = self + + if copy: + section.initial_comment = section.configspec.initial_comment + section.final_comment = section.configspec.final_comment + section.encoding = section.configspec.encoding + section.BOM = section.configspec.BOM + section.newlines = section.configspec.newlines + section.indent_type = section.configspec.indent_type + + # + configspec = section.configspec + self._set_configspec(section, copy) + + def validate_entry(entry, spec, val, missing, ret_true, ret_false): + try: + check = validator.check(spec, + val, + missing=missing + ) + except validator.baseErrorClass, e: + if not preserve_errors or isinstance(e, self._vdtMissingValue): + out[entry] = False + else: + # preserve the error + out[entry] = e + ret_false = False + ret_true = False + else: + try: + section.default_values.pop(entry, None) + except AttributeError: + # For Python 2.2 compatibility + try: + del section.default_values[entry] + except KeyError: + pass + + try: + section.default_values[entry] = validator.get_default_value(configspec[entry]) + except (KeyError, AttributeError): + # No default or validator has no 'get_default_value' (e.g. SimpleVal) + pass + + ret_false = False + out[entry] = True + if self.stringify or missing: + # if we are doing type conversion + # or the value is a supplied default + if not self.stringify: + if isinstance(check, (list, tuple)): + # preserve lists + check = [self._str(item) for item in check] + elif missing and check is None: + # convert the None from a default to a '' + check = '' + else: + check = self._str(check) + if (check != val) or missing: + section[entry] = check + if not copy and missing and entry not in section.defaults: + section.defaults.append(entry) + return ret_true, ret_false + + # + out = {} + ret_true = True + ret_false = True + + unvalidated = [k for k in section.scalars if k not in configspec] + incorrect_sections = [k for k in configspec.sections if k in section.scalars] + incorrect_scalars = [k for k in configspec.scalars if k in section.sections] + + for entry in configspec.scalars: + if entry in ('__many__', '___many___'): + # reserved names + continue + + if (not entry in section.scalars) or (entry in section.defaults): + # missing entries + # or entries from defaults + missing = True + val = None + if copy and not entry in section.scalars: + # copy comments + section.comments[entry] = ( + configspec.comments.get(entry, [])) + section.inline_comments[entry] = ( + configspec.inline_comments.get(entry, '')) + # + else: + missing = False + val = section[entry] + + ret_true, ret_false = validate_entry(entry, configspec[entry], val, + missing, ret_true, ret_false) + + many = None + if '__many__' in configspec.scalars: + many = configspec['__many__'] + elif '___many___' in configspec.scalars: + many = configspec['___many___'] + + if many is not None: + for entry in unvalidated: + val = section[entry] + ret_true, ret_false = validate_entry(entry, many, val, False, + ret_true, ret_false) + + for entry in incorrect_scalars: + ret_true = False + if not preserve_errors: + out[entry] = False + else: + ret_false = False + msg = 'Value %r was provided as a section' % entry + out[entry] = validator.baseErrorClass(msg) + for entry in incorrect_sections: + ret_true = False + if not preserve_errors: + out[entry] = False + else: + ret_false = False + msg = 'Section %r was provided as a single value' % entry + out[entry] = validator.baseErrorClass(msg) + + # Missing sections will have been created as empty ones when the + # configspec was read. + for entry in section.sections: + # FIXME: this means DEFAULT is not copied in copy mode + if section is self and entry == 'DEFAULT': + continue + if section[entry].configspec is None: + continue + if copy: + section.comments[entry] = configspec.comments.get(entry, []) + section.inline_comments[entry] = configspec.inline_comments.get(entry, '') + check = self.validate(validator, preserve_errors=preserve_errors, copy=copy, section=section[entry]) + out[entry] = check + if check == False: + ret_true = False + elif check == True: + ret_false = False + else: + ret_true = False + ret_false = False + # + if ret_true: + return True + elif ret_false: + return False + return out + + + def reset(self): + """Clear ConfigObj instance and restore to 'freshly created' state.""" + self.clear() + self._initialise() + # FIXME: Should be done by '_initialise', but ConfigObj constructor (and reload) + # requires an empty dictionary + self.configspec = None + # Just to be sure ;-) + self._original_configspec = None + + + def reload(self): + """ + Reload a ConfigObj from file. + + This method raises a ``ReloadError`` if the ConfigObj doesn't have + a filename attribute pointing to a file. + """ + if not isinstance(self.filename, basestring): + raise ReloadError() + + filename = self.filename + current_options = {} + for entry in OPTION_DEFAULTS: + if entry == 'configspec': + continue + current_options[entry] = getattr(self, entry) + + configspec = self._original_configspec + current_options['configspec'] = configspec + + self.clear() + self._initialise(current_options) + self._load(filename, configspec) + + + +class SimpleVal(object): + """ + A simple validator. + Can be used to check that all members expected are present. + + To use it, provide a configspec with all your members in (the value given + will be ignored). Pass an instance of ``SimpleVal`` to the ``validate`` + method of your ``ConfigObj``. ``validate`` will return ``True`` if all + members are present, or a dictionary with True/False meaning + present/missing. (Whole missing sections will be replaced with ``False``) + """ + + def __init__(self): + self.baseErrorClass = ConfigObjError + + def check(self, check, member, missing=False): + """A dummy check method, always returns the value unchanged.""" + if missing: + raise self.baseErrorClass() + return member + + +# Check / processing functions for options +def flatten_errors(cfg, res, levels=None, results=None): + """ + An example function that will turn a nested dictionary of results + (as returned by ``ConfigObj.validate``) into a flat list. + + ``cfg`` is the ConfigObj instance being checked, ``res`` is the results + dictionary returned by ``validate``. + + (This is a recursive function, so you shouldn't use the ``levels`` or + ``results`` arguments - they are used by the function.) + + Returns a list of keys that failed. Each member of the list is a tuple : + + :: + + ([list of sections...], key, result) + + If ``validate`` was called with ``preserve_errors=False`` (the default) + then ``result`` will always be ``False``. + + *list of sections* is a flattened list of sections that the key was found + in. + + If the section was missing (or a section was expected and a scalar provided + - or vice-versa) then key will be ``None``. + + If the value (or section) was missing then ``result`` will be ``False``. + + If ``validate`` was called with ``preserve_errors=True`` and a value + was present, but failed the check, then ``result`` will be the exception + object returned. You can use this as a string that describes the failure. + + For example *The value "3" is of the wrong type*. + + >>> import validate + >>> vtor = validate.Validator() + >>> my_ini = ''' + ... option1 = True + ... [section1] + ... option1 = True + ... [section2] + ... another_option = Probably + ... [section3] + ... another_option = True + ... [[section3b]] + ... value = 3 + ... value2 = a + ... value3 = 11 + ... ''' + >>> my_cfg = ''' + ... option1 = boolean() + ... option2 = boolean() + ... option3 = boolean(default=Bad_value) + ... [section1] + ... option1 = boolean() + ... option2 = boolean() + ... option3 = boolean(default=Bad_value) + ... [section2] + ... another_option = boolean() + ... [section3] + ... another_option = boolean() + ... [[section3b]] + ... value = integer + ... value2 = integer + ... value3 = integer(0, 10) + ... [[[section3b-sub]]] + ... value = string + ... [section4] + ... another_option = boolean() + ... ''' + >>> cs = my_cfg.split('\\n') + >>> ini = my_ini.split('\\n') + >>> cfg = ConfigObj(ini, configspec=cs) + >>> res = cfg.validate(vtor, preserve_errors=True) + >>> errors = [] + >>> for entry in flatten_errors(cfg, res): + ... section_list, key, error = entry + ... section_list.insert(0, '[root]') + ... if key is not None: + ... section_list.append(key) + ... else: + ... section_list.append('[missing]') + ... section_string = ', '.join(section_list) + ... errors.append((section_string, ' = ', error)) + >>> errors.sort() + >>> for entry in errors: + ... print entry[0], entry[1], (entry[2] or 0) + [root], option2 = 0 + [root], option3 = the value "Bad_value" is of the wrong type. + [root], section1, option2 = 0 + [root], section1, option3 = the value "Bad_value" is of the wrong type. + [root], section2, another_option = the value "Probably" is of the wrong type. + [root], section3, section3b, section3b-sub, [missing] = 0 + [root], section3, section3b, value2 = the value "a" is of the wrong type. + [root], section3, section3b, value3 = the value "11" is too big. + [root], section4, [missing] = 0 + """ + if levels is None: + # first time called + levels = [] + results = [] + if res is True: + return results + if res is False or isinstance(res, Exception): + results.append((levels[:], None, res)) + if levels: + levels.pop() + return results + for (key, val) in res.items(): + if val == True: + continue + if isinstance(cfg.get(key), dict): + # Go down one level + levels.append(key) + flatten_errors(cfg[key], val, levels, results) + continue + results.append((levels[:], key, val)) + # + # Go up one level + if levels: + levels.pop() + # + return results + + +"""*A programming language is a medium of expression.* - Paul Graham""" From 07f7906f86de5283ff36b4bd8a725661c05be2f8 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 25 Dec 2009 21:25:31 +0000 Subject: [PATCH 202/331] Don't write to stdout, write to a config file, but one with a different name for now --- terminatorlib/config.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index dbcb9efe..18c90b91 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -38,10 +38,11 @@ Classes relating to configuration """ import platform +import os import sys from configobj import ConfigObj from borg import Borg -from util import dbg +from util import dbg, get_config_dir DEFAULTS = { 'global': { @@ -279,7 +280,7 @@ class ConfigBase(Borg): section = getattr(self, section_name) parser[section_name] = section - parser.write(sys.stdout) + parser.write(open(os.path.join(get_config_dir(), 'epic-config'), 'w')) if __name__ == '__main__': import doctest From 71fcada828059f231607a80e4bc905b96974e305 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 25 Dec 2009 21:42:57 +0000 Subject: [PATCH 203/331] add a fixme --- terminatorlib/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 18c90b91..1a5de4a0 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -278,6 +278,8 @@ class ConfigBase(Borg): parser.indent_type = ' ' for section_name in sections: section = getattr(self, section_name) + # FIXME: Instead of just writing out the whole section we should + # only write out things that aren't defaults parser[section_name] = section parser.write(open(os.path.join(get_config_dir(), 'epic-config'), 'w')) From 5bd1b3f9af5c3ccc220dbae1ff4da800f2c07efa Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 26 Dec 2009 01:19:42 +0000 Subject: [PATCH 204/331] Make sure ConfigBase() only uses a copy of DEFAULTS so we never change DEFAULTS, allowing us to add dict_diff() so we can avoid including things in the config file that aren't default --- terminatorlib/config.py | 19 +++++++++++-------- terminatorlib/util.py | 13 +++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 1a5de4a0..e2edc070 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -19,12 +19,16 @@ Classes relating to configuration +>>> DEFAULTS['global_config']['focus'] +'click' >>> config = Config() >>> config['focus'] 'click' >>> config['focus'] = 'sloppy' >>> config['focus'] 'sloppy' +>>> DEFAULTS['global_config']['focus'] +'click' >>> config2 = Config() >>> config2['focus'] 'sloppy' @@ -40,12 +44,13 @@ Classes relating to configuration import platform import os import sys +from copy import copy from configobj import ConfigObj from borg import Borg -from util import dbg, get_config_dir +from util import dbg, get_config_dir, dict_diff DEFAULTS = { - 'global': { + 'global_config': { 'focus' : 'click', 'enable_real_transparency' : True, 'handle_size' : -1, @@ -218,12 +223,12 @@ class ConfigBase(Borg): def prepare_attributes(self): """Set up our borg environment""" if self.global_config is None: - self.global_config = DEFAULTS['global'] + self.global_config = copy(DEFAULTS['global_config']) if self.profiles is None: self.profiles = {} - self.profiles['default'] = DEFAULTS['profiles']['default'] + self.profiles['default'] = copy(DEFAULTS['profiles']['default']) if self.keybindings is None: - self.keybindings = DEFAULTS['keybindings'] + self.keybindings = copy(DEFAULTS['keybindings']) if self.plugins is None: self.plugins = {} @@ -278,9 +283,7 @@ class ConfigBase(Borg): parser.indent_type = ' ' for section_name in sections: section = getattr(self, section_name) - # FIXME: Instead of just writing out the whole section we should - # only write out things that aren't defaults - parser[section_name] = section + parser[section_name] = dict_diff(DEFAULTS[section_name], section) parser.write(open(os.path.join(get_config_dir(), 'epic-config'), 'w')) diff --git a/terminatorlib/util.py b/terminatorlib/util.py index fb207494..0baaa52b 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -133,3 +133,16 @@ def get_config_dir(): return(os.path.join(configdir, 'terminator')) +def dict_diff(reference, working): + """Examine the values in the supplied working set and return a new dict + that only contains those values which are different from those in the + reference dictionary""" + + result = {} + + for key in reference: + if reference[key] != working[key]: + result[key] = working[key] + + return(result) + From 825abbb65845cb69f375a387fc191a920d77b92d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 26 Dec 2009 13:39:14 +0000 Subject: [PATCH 205/331] Flesh out Config.load() so it actually loads a config --- terminatorlib/config.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index e2edc070..820cde0f 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -22,8 +22,6 @@ Classes relating to configuration >>> DEFAULTS['global_config']['focus'] 'click' >>> config = Config() ->>> config['focus'] -'click' >>> config['focus'] = 'sloppy' >>> config['focus'] 'sloppy' @@ -218,7 +216,7 @@ class ConfigBase(Borg): Borg.__init__(self, self.__class__.__name__) self.prepare_attributes() - self.load_config() + self.load() def prepare_attributes(self): """Set up our borg environment""" @@ -232,10 +230,25 @@ class ConfigBase(Borg): if self.plugins is None: self.plugins = {} - def load_config(self): + def load(self): """Load configuration data from our various sources""" - # FIXME: Load our config from wherever and merge it into the defaults - pass + sections = ['global_config', 'keybindings', 'profiles', 'plugins'] + configfile = open(os.path.join(get_config_dir(), 'epic-config'), 'r') + parser = ConfigObj(configfile) + for section_name in sections: + section = getattr(self, section_name) + section.update(parser[section_name]) + + def save(self): + """Save the config to a file""" + sections = ['global_config', 'keybindings', 'profiles', 'plugins'] + parser = ConfigObj() + parser.indent_type = ' ' + for section_name in sections: + section = getattr(self, section_name) + parser[section_name] = dict_diff(DEFAULTS[section_name], section) + + parser.write(open(os.path.join(get_config_dir(), 'epic-config'), 'w')) def get_item(self, key, profile='default', plugin=None): """Look up a configuration item""" @@ -276,17 +289,6 @@ class ConfigBase(Borg): return(True) - def save(self): - """Save the config to a file""" - sections = ['global_config', 'keybindings', 'profiles', 'plugins'] - parser = ConfigObj() - parser.indent_type = ' ' - for section_name in sections: - section = getattr(self, section_name) - parser[section_name] = dict_diff(DEFAULTS[section_name], section) - - parser.write(open(os.path.join(get_config_dir(), 'epic-config'), 'w')) - if __name__ == '__main__': import doctest (failed, attempted) = doctest.testmod() From f09e9947cdfa15a2a7d5b71716430960bdcd4760 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 26 Dec 2009 19:43:01 +0000 Subject: [PATCH 206/331] Make the config loading and saving significantly more functional, but also horrifyingly complex, with very little error handling --- terminatorlib/config.py | 86 ++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 14 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 820cde0f..baa4cf87 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -45,6 +45,7 @@ import sys from copy import copy from configobj import ConfigObj from borg import Borg +from factory import Factory from util import dbg, get_config_dir, dict_diff DEFAULTS = { @@ -172,7 +173,7 @@ DEFAULTS = { 'ignore_hosts' : ['localhost','127.0.0.0/8','*.local'], }, }, - 'layout': { + 'layouts': { }, 'plugins': { }, @@ -198,6 +199,8 @@ class Config(object): def set_profile(self, profile): """Set our profile (which usually means change it)""" self.profile = profile + if not self.base.profiles.has_key(profile): + self.base.profiles[profile] = copy(DEFAULTS['profiles']['default']) def save(self): """Cause ConfigBase to save our config to file""" @@ -205,10 +208,13 @@ class Config(object): class ConfigBase(Borg): """Class to provide access to our user configuration""" + loaded = None + sections = None global_config = None profiles = None keybindings = None plugins = None + layouts = None def __init__(self): """Class initialiser""" @@ -220,6 +226,11 @@ class ConfigBase(Borg): def prepare_attributes(self): """Set up our borg environment""" + if self.loaded is None: + self.loaded = False + if self.sections is None: + self.sections = ['global_config', 'keybindings', 'profiles', + 'layouts', 'plugins'] if self.global_config is None: self.global_config = copy(DEFAULTS['global_config']) if self.profiles is None: @@ -229,44 +240,91 @@ class ConfigBase(Borg): self.keybindings = copy(DEFAULTS['keybindings']) if self.plugins is None: self.plugins = {} + if self.layouts is None: + self.layouts = copy(DEFAULTS['layouts']) def load(self): """Load configuration data from our various sources""" - sections = ['global_config', 'keybindings', 'profiles', 'plugins'] - configfile = open(os.path.join(get_config_dir(), 'epic-config'), 'r') + if self.loaded is True: + dbg('ConfigBase::load: config already loaded') + return + + filename = os.path.join(get_config_dir(), 'epic-config') + try: + configfile = open(filename, 'r') + except Exception, ex: + dbg('ConfigBase::load: Unable to open %s (%s)' % (filename, ex)) + return + parser = ConfigObj(configfile) - for section_name in sections: + for section_name in self.sections: + dbg('ConfigBase::load: Processing section: %s' % section_name) section = getattr(self, section_name) - section.update(parser[section_name]) + if section_name == 'profiles': + for profile in parser[section_name]: + dbg('ConfigBase::load: Processing profile: %s' % profile) + if not section.has_key(section_name): + section[profile] = copy(DEFAULTS['profiles']['default']) + section[profile].update(parser[section_name][profile]) + elif section_name == ['layouts', 'plugins']: + for part in parser[section_name]: + dbg('ConfigBase::load: Processing %s: %s' % (section_name, + part)) + section[part] = parser[section_name][part] + else: + try: + section.update(parser[section_name]) + except KeyError, ex: + dbg('ConfigBase::load: skipping loading missing section %s' % + section_name) + + self.loaded = True def save(self): """Save the config to a file""" - sections = ['global_config', 'keybindings', 'profiles', 'plugins'] + dbg('ConfigBase::save: saving config') parser = ConfigObj() parser.indent_type = ' ' - for section_name in sections: + + for section_name in ['global_config', 'keybindings']: + dbg('ConfigBase::save: Processing section: %s' % section_name) section = getattr(self, section_name) parser[section_name] = dict_diff(DEFAULTS[section_name], section) + parser['profiles'] = {} + for profile in self.profiles: + dbg('ConfigBase::save: Processing profile: %s' % profile) + parser['profiles'][profile] = dict_diff(DEFAULTS['profiles']['default'], + self.profiles[profile]) + + parser['layouts'] = {} + for layout in self.layouts: + dbg('ConfigBase::save: Processing layout: %s' % layout) + parser['layouts'][layout] = self.layouts[layout] + + parser['plugins'] = {} + for plugin in self.plugins: + dbg('ConfigBase::save: Processing plugin: %s' % plugin) + parser['plugins'][plugin] = self.plugins[plugin] + parser.write(open(os.path.join(get_config_dir(), 'epic-config'), 'w')) def get_item(self, key, profile='default', plugin=None): """Look up a configuration item""" - dbg('ConfigBase::get_item: Lookup %s (profile=%s, plugin=%s)' % (key, - profile, plugin)) + dbg('ConfigBase::get_item: %s:%s' % (profile, key)) if self.global_config.has_key(key): dbg('ConfigBase::get_item: found in globals: %s' % self.global_config[key]) return(self.global_config[key]) elif self.profiles[profile].has_key(key): - dbg('ConfigBase::get_item: found in profile: %s' % - self.profiles[profile][key]) + dbg('ConfigBase::get_item: found in profile %s (%s)' % ( + profile, self.profiles[profile][key])) return(self.profiles[profile][key]) elif key == 'keybindings': return(self.keybindings) elif plugin is not None and self.plugins[plugin].has_key(key): - dbg('ConfigBase::get_item: found in plugin: %s' % - self.plugins[plugin][key]) + dbg('ConfigBase::get_item: found in plugin %s (%s)' % ( + plugin, self.plugins[plugin][key])) return(self.plugins[plugin][key]) else: raise KeyError('ConfigBase::get_item: unknown key %s' % key) @@ -279,7 +337,7 @@ class ConfigBase(Borg): if self.global_config.has_key(key): self.global_config[key] = value elif self.profiles[profile].has_key(key): - self.profiles[profile] = value + self.profiles[profile][key] = value elif key == 'keybindings': self.keybindings = value elif plugin is not None and self.plugins[plugin].has_key(key): From 2a56e328ac80f334da50d7bef9689b79889da04c Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 26 Dec 2009 19:52:58 +0000 Subject: [PATCH 207/331] Add methods to Config to list and delete profiles --- terminatorlib/config.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index baa4cf87..2e2ea335 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -173,7 +173,7 @@ DEFAULTS = { 'ignore_hosts' : ['localhost','127.0.0.0/8','*.local'], }, }, - 'layouts': { + 'layouts': { }, 'plugins': { }, @@ -202,6 +202,15 @@ class Config(object): if not self.base.profiles.has_key(profile): self.base.profiles[profile] = copy(DEFAULTS['profiles']['default']) + def del_profile(self, profile): + """Delete a profile""" + if self.base.profiles.has_key(profile): + del(self.base.profiles[profile]) + + def list_profiles(self): + """List all configured profiles""" + return(self.base.profiles.keys()) + def save(self): """Cause ConfigBase to save our config to file""" return(self.base.save()) From ee81bd4f76635eb7464d5af28691a1a72cc586e7 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 26 Dec 2009 20:09:16 +0000 Subject: [PATCH 208/331] Make the terminal context menu list available profiles and switch between them --- terminatorlib/config.py | 2 ++ terminatorlib/terminal.py | 9 +++++++++ terminatorlib/terminal_popup_menu.py | 22 +++++++++++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 2e2ea335..19abb559 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -198,8 +198,10 @@ class Config(object): def set_profile(self, profile): """Set our profile (which usually means change it)""" + dbg('Config::set_profile: Changing profile to %s' % profile) self.profile = profile if not self.base.profiles.has_key(profile): + dbg('Config::set_profile: %s does not exist, creating' % profile) self.base.profiles[profile] = copy(DEFAULTS['profiles']['default']) def del_profile(self, profile): diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 99198293..9bb176c8 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -135,6 +135,15 @@ class Terminal(gtk.VBox): if self.config['http_proxy'] and self.config['http_proxy'] != '': os.putenv('http_proxy', self.config['http_proxy']) + def set_profile(self, widget, profile): + """Set our profile""" + self.config.set_profile(profile) + self.reconfigure() + + def get_profile(self): + """Return our profile name""" + return(self.config.profile) + def close(self): """Close ourselves""" dbg('Terminal::close: emitting close-term') diff --git a/terminatorlib/terminal_popup_menu.py b/terminatorlib/terminal_popup_menu.py index 0500ddad..c0877c69 100755 --- a/terminatorlib/terminal_popup_menu.py +++ b/terminatorlib/terminal_popup_menu.py @@ -10,6 +10,7 @@ from version import APP_NAME from translation import _ from encoding import TerminatorEncoding from util import err +from config import Config import plugin class TerminalPopupMenu(object): @@ -130,10 +131,29 @@ class TerminalPopupMenu(object): item.set_sensitive(False) menu.append(item) + item = gtk.MenuItem(_('Profiles')) + submenu = gtk.Menu() + item.set_submenu(submenu) + menu.append(item) + + config = Config() + current = terminal.get_profile() + + group = None + + for profile in config.list_profiles(): + item = gtk.RadioMenuItem(group, profile.capitalize()) + item.connect('activate', terminal.set_profile, profile) + if profile == current: + item.set_active(True) + submenu.append(item) + + submenu.append(gtk.MenuItem()) + item = gtk.MenuItem(_('Ed_it profile')) item.connect('activate', lambda x: terminal.terminator.edit_profile(terminal)) - menu.append(item) + submenu.append(item) self.add_encoding_items(menu) From 945a5473441f4e4d373aec449f8fa23e5de12a87 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 27 Dec 2009 00:59:29 +0000 Subject: [PATCH 209/331] pluralise --- terminatorlib/terminal_popup_menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/terminal_popup_menu.py b/terminatorlib/terminal_popup_menu.py index c0877c69..b312bf72 100755 --- a/terminatorlib/terminal_popup_menu.py +++ b/terminatorlib/terminal_popup_menu.py @@ -150,7 +150,7 @@ class TerminalPopupMenu(object): submenu.append(gtk.MenuItem()) - item = gtk.MenuItem(_('Ed_it profile')) + item = gtk.MenuItem(_('Ed_it profiles')) item.connect('activate', lambda x: terminal.terminator.edit_profile(terminal)) submenu.append(item) From 896bd1af35c4cded13516b97b5edf0b9325202f0 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 27 Dec 2009 00:59:44 +0000 Subject: [PATCH 210/331] Start the epic refactor of the profile editor --- terminatorlib/prefs_profile.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/terminatorlib/prefs_profile.py b/terminatorlib/prefs_profile.py index 1e9442ca..61048fe4 100644 --- a/terminatorlib/prefs_profile.py +++ b/terminatorlib/prefs_profile.py @@ -1,12 +1,14 @@ #!/usr/bin/python -from terminatorlib.util import dbg,err -from terminatorlib.config import DEFAULTS,TerminatorConfValuestoreRC -from terminatorlib.keybindings import TerminatorKeybindings, KeymapError -from terminatorlib.version import APP_NAME, APP_VERSION -from terminatorlib import translation +import gtk +import gobject + +from util import dbg, err +import config +from keybindings import Keybindings +from version import APP_NAME, APP_VERSION +from translation import _ -import gtk, gobject class ProfileEditor: # lists of which settings to put in which tabs @@ -67,11 +69,6 @@ class ProfileEditor: self.window = gtk.Window () self.notebook = gtk.Notebook() self.box = gtk.VBox() - self.warning = gtk.Label() - - self.warning.set_use_markup (True) - self.warning.set_line_wrap (True) - self.warning.set_markup ("NOTE: These settings will not be saved. See: man terminator_config") self.butbox = gtk.HButtonBox() self.applybut = gtk.Button(stock=gtk.STOCK_APPLY) From 4c025273c93aa981b27c58e52860e179dd2cf16d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 27 Dec 2009 01:01:34 +0000 Subject: [PATCH 211/331] rename and import the profile editor --- terminatorlib/{prefs_profile.py => profileeditor.py} | 0 terminatorlib/terminal.py | 1 + 2 files changed, 1 insertion(+) rename terminatorlib/{prefs_profile.py => profileeditor.py} (100%) diff --git a/terminatorlib/prefs_profile.py b/terminatorlib/profileeditor.py similarity index 100% rename from terminatorlib/prefs_profile.py rename to terminatorlib/profileeditor.py diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 9bb176c8..cc6b5925 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -21,6 +21,7 @@ from newterminator import Terminator from titlebar import Titlebar from terminal_popup_menu import TerminalPopupMenu from searchbar import Searchbar +from profileeditor import ProfileEditor from translation import _ import plugin From f690cd6e5fd45487dfee23abbe7b715502131960 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 27 Dec 2009 02:32:16 +0000 Subject: [PATCH 212/331] Get ProfileEditor to the barest state of functionality possible --- terminatorlib/profileeditor.py | 790 +++++++++++++-------------- terminatorlib/terminal_popup_menu.py | 4 +- 2 files changed, 397 insertions(+), 397 deletions(-) diff --git a/terminatorlib/profileeditor.py b/terminatorlib/profileeditor.py index 61048fe4..87740e88 100644 --- a/terminatorlib/profileeditor.py +++ b/terminatorlib/profileeditor.py @@ -9,432 +9,432 @@ from keybindings import Keybindings from version import APP_NAME, APP_VERSION from translation import _ - class ProfileEditor: - # lists of which settings to put in which tabs - appearance = ['titlebars', 'zoomedtitlebar', 'titletips', 'allow_bold', 'audible_bell', 'visible_bell', 'urgent_bell', 'force_no_bell', 'background_darkness', 'background_type', 'background_image', 'cursor_blink', 'cursor_shape', 'font', 'scrollbar_position', 'scroll_background', 'use_system_font', 'use_theme_colors', 'enable_real_transparency'] - colours = ['foreground_color','background_color', 'cursor_color', 'palette', 'title_tx_txt_color', 'title_tx_bg_color', 'title_rx_txt_color', 'title_rx_bg_color', 'title_ia_txt_color', 'title_ia_bg_color'] - behaviour = ['backspace_binding', 'delete_binding', 'emulation', 'scroll_on_keystroke', 'scroll_on_output', 'alternate_screen_scroll', 'scrollback_lines', 'focus', 'focus_on_close', 'exit_action', 'word_chars', 'mouse_autohide', 'use_custom_command', 'custom_command', 'http_proxy', 'encoding'] - globals = ['fullscreen', 'maximise', 'borderless', 'handle_size', 'cycle_term_tab', 'close_button_on_tab', 'tab_position', 'copy_on_selection', 'extreme_tabs', 'try_posix_regexp'] + # lists of which settings to put in which tabs + appearance = ['titlebars', 'zoomedtitlebar', 'allow_bold', 'audible_bell', 'visible_bell', 'urgent_bell', 'force_no_bell', 'background_darkness', 'background_type', 'background_image', 'cursor_blink', 'cursor_shape', 'font', 'scrollbar_position', 'scroll_background', 'use_system_font', 'use_theme_colors', 'enable_real_transparency'] + colours = ['foreground_color','background_color', 'cursor_color', 'palette', 'title_tx_txt_color', 'title_tx_bg_color', 'title_rx_txt_color', 'title_rx_bg_color', 'title_ia_txt_color', 'title_ia_bg_color'] + behaviour = ['backspace_binding', 'delete_binding', 'emulation', 'scroll_on_keystroke', 'scroll_on_output', 'alternate_screen_scroll', 'scrollback_lines', 'focus', 'focus_on_close', 'exit_action', 'word_chars', 'mouse_autohide', 'use_custom_command', 'custom_command', 'http_proxy', 'encoding'] + globals = ['fullscreen', 'maximise', 'borderless', 'handle_size', 'cycle_term_tab', 'close_button_on_tab', 'tab_position', 'copy_on_selection', 'try_posix_regexp'] - # metadata about the settings - data = {'titlebars': ['Show titlebars', 'This places a bar above each terminal which displays its title.'], - 'zoomedtitlebar': ['Show titlebar when zoomed', 'This places an informative bar above a zoomed terminal to indicate there are hidden terminals.'], - 'titletips': ['Show title tooltips', 'This adds a tooltip to each terminal which contains its title'], - 'allow_bold': ['Allow bold text', 'Controls whether or not the terminals will honour requests for bold text'], - 'silent_bell': ['', 'When enabled, bell events will generate a flash. When disabled, they will generate a beep'], - 'background_darkness': ['', 'Controls how much the background will be tinted'], - 'scroll_background': ['', 'When enabled the background image will scroll with the text'], - 'force_no_bell': ['', 'Disable both the visual and audible bells'], - 'tab_position': ['', 'Controls the placement of the tab bar'], - 'use_theme_colors': ['', 'Take the foreground and background colours from the current GTK theme'], - 'enable_real_transparency': ['', 'If you are running a composited desktop (e.g. compiz), enabling this option will enable "true" transpraency'], - 'handle_size': ['', 'This controls the size of the border between terminals. Values 0 to 5 are in pixels, while -1 means the value will be decided by your normal GTK theme.'], - 'close_window': ['Quit Terminator', ''], - 'toggle_zoom': ['Toggle maximise terminal', ''], - 'scaled_zoom': ['Toggle zoomed terminal', ''], - 'prev_tab': ['Previous tab', ''], - 'split_vert': ['Split vertically', ''], - 'split_horiz': ['Split horizontally', ''], - 'go_prev': ['Focus previous terminal', ''], - 'go_next': ['Focus next terminal', ''], - 'close_term': ['Close terminal', ''], - 'new_root_tab': ['New root tab', ''], - 'zoom_normal': ['Zoom reset', ''], - 'reset': ['Reset terminal state', ''], - 'reset_clear': ['Reset and clear terminal', ''], - 'hide_window': ['Toggle visibility of the window', ''], - 'title_tx_txt_color': ['Tx Title Foreground Color', ''], - 'title_tx_bg_color': ['Tx Title Background Color', ''], - 'title_rx_txt_color': ['Rx Title Foreground Color', ''], - 'title_rx_bg_color': ['Rx Title Background Color', ''], - 'title_ia_txt_color': ['Inactive Title Foreground Color', ''], - 'title_ia_bg_color': ['Inactive Title Background Color', ''], - } + # metadata about the settings + data = {'titlebars': ['Show titlebars', 'This places a bar above each terminal which displays its title.'], + 'zoomedtitlebar': ['Show titlebar when zoomed', 'This places an informative bar above a zoomed terminal to indicate there are hidden terminals.'], + 'allow_bold': ['Allow bold text', 'Controls whether or not the terminals will honour requests for bold text'], + 'silent_bell': ['', 'When enabled, bell events will generate a flash. When disabled, they will generate a beep'], + 'background_darkness': ['', 'Controls how much the background will be tinted'], + 'scroll_background': ['', 'When enabled the background image will scroll with the text'], + 'force_no_bell': ['', 'Disable both the visual and audible bells'], + 'tab_position': ['', 'Controls the placement of the tab bar'], + 'use_theme_colors': ['', 'Take the foreground and background colours from the current GTK theme'], + 'enable_real_transparency': ['', 'If you are running a composited desktop (e.g. compiz), enabling this option will enable "true" transpraency'], + 'handle_size': ['', 'This controls the size of the border between terminals. Values 0 to 5 are in pixels, while -1 means the value will be decided by your normal GTK theme.'], + 'close_window': ['Quit Terminator', ''], + 'toggle_zoom': ['Toggle maximise terminal', ''], + 'scaled_zoom': ['Toggle zoomed terminal', ''], + 'prev_tab': ['Previous tab', ''], + 'split_vert': ['Split vertically', ''], + 'split_horiz': ['Split horizontally', ''], + 'go_prev': ['Focus previous terminal', ''], + 'go_next': ['Focus next terminal', ''], + 'close_term': ['Close terminal', ''], + 'new_root_tab': ['New root tab', ''], + 'zoom_normal': ['Zoom reset', ''], + 'reset': ['Reset terminal state', ''], + 'reset_clear': ['Reset and clear terminal', ''], + 'hide_window': ['Toggle visibility of the window', ''], + 'title_tx_txt_color': ['Tx Title Foreground Color', ''], + 'title_tx_bg_color': ['Tx Title Background Color', ''], + 'title_rx_txt_color': ['Rx Title Foreground Color', ''], + 'title_rx_bg_color': ['Rx Title Background Color', ''], + 'title_ia_txt_color': ['Inactive Title Foreground Color', ''], + 'title_ia_bg_color': ['Inactive Title Background Color', ''], + } - # dictionary for results after setting - widgets = {} + # dictionary for results after setting + widgets = {} - # combobox settings - scrollbar_position = ['left', 'right', 'disabled'] - backspace_del_binding = ['ascii-del', 'control-h', 'escape-sequence', 'delete-sequence'] - focus = ['click', 'sloppy', 'mouse'] - background_type = ['solid', 'image', 'transparent'] - tab_position = ['top', 'bottom', 'left', 'right'] - tab_position_gtk = {'top' : gtk.POS_TOP, 'bottom' : gtk.POS_BOTTOM, 'left' : gtk.POS_LEFT, 'right' : gtk.POS_RIGHT} - cursor_shape = ['block', 'ibeam', 'underline'] + # combobox settings + scrollbar_position = ['left', 'right', 'disabled'] + backspace_del_binding = ['ascii-del', 'control-h', 'escape-sequence', 'delete-sequence'] + focus = ['click', 'sloppy', 'mouse'] + background_type = ['solid', 'image', 'transparent'] + tab_position = ['top', 'bottom', 'left', 'right'] + tab_position_gtk = {'top' : gtk.POS_TOP, 'bottom' : gtk.POS_BOTTOM, 'left' : gtk.POS_LEFT, 'right' : gtk.POS_RIGHT} + cursor_shape = ['block', 'ibeam', 'underline'] - def __init__ (self, term): - self.term = term - self.window = gtk.Window () - self.notebook = gtk.Notebook() - self.box = gtk.VBox() + config = None - self.butbox = gtk.HButtonBox() - self.applybut = gtk.Button(stock=gtk.STOCK_APPLY) - self.applybut.connect ("clicked", self.apply) - self.cancelbut = gtk.Button(stock=gtk.STOCK_CLOSE) - self.cancelbut.connect ("clicked", self.cancel) + def __init__ (self, term): + self.config = config.Config() + self.term = term + self.window = gtk.Window () + self.notebook = gtk.Notebook() + self.box = gtk.VBox() - self.box.pack_start(self.warning, False, False) - self.box.pack_start(self.notebook, False, False) - self.box.pack_end(self.butbox, False, False) + self.butbox = gtk.HButtonBox() + self.applybut = gtk.Button(stock=gtk.STOCK_APPLY) + self.applybut.connect ("clicked", self.apply) + self.cancelbut = gtk.Button(stock=gtk.STOCK_CLOSE) + self.cancelbut.connect ("clicked", self.cancel) - self.butbox.set_layout(gtk.BUTTONBOX_END) - self.butbox.pack_start(self.applybut, False, False) - self.butbox.pack_start(self.cancelbut, False, False) - self.window.add (self.box) + self.box.pack_start(self.notebook, False, False) + self.box.pack_end(self.butbox, False, False) - self.notebook.append_page (self.auto_add (gtk.Table (), self.globals), gtk.Label ("Global Settings")) - self.notebook.append_page (self.prepare_keybindings (), gtk.Label ("Keybindings")) - self.notebook.append_page (self.auto_add (gtk.Table (), self.appearance), gtk.Label ("Appearance")) - self.notebook.append_page (self.auto_add (gtk.Table (), self.colours), gtk.Label ("Colours")) - self.notebook.append_page (self.auto_add (gtk.Table (), self.behaviour), gtk.Label ("Behaviour")) + self.butbox.set_layout(gtk.BUTTONBOX_END) + self.butbox.pack_start(self.applybut, False, False) + self.butbox.pack_start(self.cancelbut, False, False) + self.window.add (self.box) - def go (self): - self.window.show_all () + self.notebook.append_page (self.auto_add (gtk.Table (), self.globals), gtk.Label ("Global Settings")) + self.notebook.append_page (self.prepare_keybindings (), gtk.Label ("Keybindings")) + self.notebook.append_page (self.auto_add (gtk.Table (), self.appearance), gtk.Label ("Appearance")) + self.notebook.append_page (self.auto_add (gtk.Table (), self.colours), gtk.Label ("Colours")) + self.notebook.append_page (self.auto_add (gtk.Table (), self.behaviour), gtk.Label ("Behaviour")) - def source_get_type (self, key): - if DEFAULTS.has_key (key): - return DEFAULTS[key].__class__.__name__ - elif DEFAULTS['keybindings'].has_key (key): - return DEFAULTS['keybindings'][key].__class__.__name__ - else: - raise KeyError + self.window.show_all() - def source_get_value (self, key): - try: - return self.term.conf.__getattr__(key) - except AttributeError: - try: - return self.term.conf.keybindings[key] - except AttributeError: - pass - - def source_get_keyname (self, key): - if self.data.has_key (key) and self.data[key][0] != '': - label_text = self.data[key][0] - else: - label_text = key.replace ('_', ' ').capitalize () - return label_text - - def auto_add (self, table, list): - row = 0 - for key in list: - table.resize (row + 1, 2) - label = gtk.Label (self.source_get_keyname (key)) - wrapperbox = gtk.HBox() - wrapperbox.pack_start(label, False, True) - - type = self.source_get_type (key) - value = self.source_get_value (key) - widget = None - - if key == 'font': - widget = gtk.FontButton(value) - elif key == 'scrollback_lines': - # estimated byte size per line according to g-t: - # sizeof(void *) + sizeof(char *) + sizeof(int) + (80 * (sizeof(int32) + 4) - widget = gtk.SpinButton() - widget.set_digits(0) - widget.set_increments(100, 1000) - widget.set_range(0, 100000) - widget.set_value(value) - elif key == 'scrollbar_position': - if value == 'hidden': - value = 'disabled' - widget = gtk.combo_box_new_text() - for item in self.scrollbar_position: - widget.append_text (item) - if value in self.scrollbar_position: - widget.set_active (self.scrollbar_position.index(value)) - elif key == 'backspace_binding': - widget = gtk.combo_box_new_text() - for item in self.backspace_del_binding: - widget.append_text (item) - if value in self.backspace_del_binding: - widget.set_active (self.backspace_del_binding.index(value)) - elif key == 'delete_binding': - widget = gtk.combo_box_new_text() - for item in self.backspace_del_binding: - widget.append_text (item) - if value in self.backspace_del_binding: - widget.set_active (self.backspace_del_binding.index(value)) - elif key == 'focus': - widget = gtk.combo_box_new_text() - for item in self.focus: - widget.append_text (item) - if value in self.focus: - widget.set_active (self.focus.index(value)) - elif key == 'background_type': - widget = gtk.combo_box_new_text() - for item in self.background_type: - widget.append_text (item) - if value in self.background_type: - widget.set_active (self.background_type.index(value)) - elif key == 'background_darkness': - widget = gtk.HScale () - widget.set_digits (1) - widget.set_draw_value (True) - widget.set_value_pos (gtk.POS_LEFT) - widget.set_range (0, 1) - widget.set_value (value) - elif key == 'handle_size': - widget = gtk.HScale () - widget.set_digits (0) - widget.set_draw_value (True) - widget.set_value_pos (gtk.POS_LEFT) - widget.set_range (-1, 5) - widget.set_value (value) - elif key == 'foreground_color': - widget = gtk.ColorButton (gtk.gdk.color_parse (value)) - elif key == 'background_color': - widget = gtk.ColorButton (gtk.gdk.color_parse (value)) - elif key == 'cursor_color': - if not value: - value = self.source_get_value ('foreground_color') - widget = gtk.ColorButton (gtk.gdk.color_parse (value)) - elif key == 'palette': - colours = value.split (':') - numcolours = len (colours) - widget = gtk.Table (2, numcolours / 2) - x = 0 - y = 0 - for thing in colours: - if x == numcolours / 2: - y += 1 - x = 0 - widget.attach (gtk.ColorButton (gtk.gdk.color_parse (thing)), x, x + 1, y, y + 1) - x += 1 - elif key in ['title_tx_txt_color', 'title_tx_bg_color', 'title_rx_txt_color', 'title_rx_bg_color', 'title_ia_txt_color', 'title_ia_bg_color']: - widget = gtk.ColorButton (gtk.gdk.color_parse (value)) - elif key == 'background_image': - widget = gtk.FileChooserButton('Select a File') - filter = gtk.FileFilter() - filter.add_mime_type ('image/*') - widget.add_filter (filter) - widget.set_local_only (True) - if value: - widget.set_filename (value) - elif key == 'tab_position': - widget = gtk.combo_box_new_text() - for item in self.tab_position: - widget.append_text (item) - if value in self.tab_position: - widget.set_active (self.tab_position.index(value)) - elif key == 'cursor_shape': - widget = gtk.combo_box_new_text() - for item in self.cursor_shape: - widget.append_text (item) - if value in self.cursor_shape: - widget.set_active (self.cursor_shape.index (value)) - else: - if type == "bool": - widget = gtk.CheckButton () - widget.set_active (value) - elif type in ["str", "int", "float"]: - widget = gtk.Entry () - widget.set_text (str(value)) - elif type == "list": - continue + def source_get_type (self, key): + if config.DEFAULTS['global_config'].has_key (key): + print "found %s in global_config" % key + return config.DEFAULTS['global_config'][key].__class__.__name__ + elif config.DEFAULTS['profiles']['default'].has_key (key): + print "found %s in profiles" % key + return config.DEFAULTS['profiles']['default'][key].__class__.__name__ + elif config.DEFAULTS['keybindings'].has_key (key): + print "found %s in keybindings" % key + return config.DEFAULTS['keybindings'][key].__class__.__name__ else: - err("Unknown type: %s for key: %s" % (type, key)) - continue + print "could not find %s" % key + raise KeyError - if hasattr(widget, 'set_tooltip_text') and self.data.has_key (key): - widget.set_tooltip_text (self.data[key][1]) - - widget.set_name(key) - self.widgets[key] = widget - table.attach (wrapperbox, 0, 1, row, row + 1, gtk.EXPAND|gtk.FILL, gtk.FILL) - table.attach (widget, 1, 2, row, row + 1, gtk.EXPAND|gtk.FILL, gtk.FILL) - row += 1 + def source_get_value (self, key): + return self.config[key] - return (table) - - def apply (self, data): - values = {} - for page in [self.appearance, self.behaviour, self.globals, self.colours]: - for property in page: - widget = self.widgets[property] - - if isinstance (widget, gtk.SpinButton): - value = widget.get_value () - elif isinstance (widget, gtk.Entry): - value = widget.get_text() - elif isinstance (widget, gtk.CheckButton): - value = widget.get_active() - elif isinstance (widget, gtk.ComboBox): - if widget.name == 'scrollbar_position': - bucket = self.scrollbar_position - elif widget.name == 'backspace_binding' or widget.name == 'delete_binding': - bucket = self.backspace_del_binding - elif widget.name == 'focus': - bucket = self.focus - elif widget.name == 'background_type': - bucket = self.background_type - elif widget.name == 'tab_position': - bucket = self.tab_position - elif widget.name == 'cursor_shape': - bucket = self.cursor_shape - else: - err("Unknown bucket type for %s" % widget.name) - continue - - value = bucket[widget.get_active()] - elif isinstance (widget, gtk.FontButton): - value = widget.get_font_name() - elif isinstance (widget, gtk.HScale): - value = widget.get_value() - if widget.get_digits() == 0: - value = int(value) - elif isinstance (widget, gtk.ColorButton): - value = widget.get_color().to_string() - elif isinstance (widget, gtk.FileChooserButton): - value = widget.get_filename() - elif widget.get_name() == 'palette': - value = '' - valuebits = [] - children = widget.get_children() - children.reverse() - for child in children: - valuebits.append (child.get_color().to_string()) - value = ':'.join (valuebits) + def source_get_keyname (self, key): + if self.data.has_key (key) and self.data[key][0] != '': + label_text = self.data[key][0] else: - value = None - err("skipping unknown property: %s" % property) + label_text = key.replace ('_', ' ').capitalize () + return label_text - values[property] = value + def auto_add (self, table, list): + row = 0 + for key in list: + table.resize (row + 1, 2) + label = gtk.Label (self.source_get_keyname (key)) + wrapperbox = gtk.HBox() + wrapperbox.pack_start(label, False, True) - has_changed = False - changed = [] - for source in self.term.conf.sources: - if isinstance (source, TerminatorConfValuestoreRC): - for property in values: - try: - if self.source_get_value(property) != values[property]: - dbg("%s changed from %s to %s" % (property, self.source_get_value(property), values[property])) - source.values[property] = values[property] - has_changed = True - changed.append(property) - except KeyError: - pass - if has_changed: - for changer in changed: - if changer == "fullscreen": - self.term.fullscreen_absolute(values[changer]) - elif changer == "maximise": - if values[changer]: - self.term.maximize() - else: - self.term.unmaximize() - elif changer == "borderless": - self.term.window.set_decorated (not values[changer]) - elif changer == "handle_size": - self.term.set_handle_size(values[changer]) - gtk.rc_reset_styles(gtk.settings_get_default()) - elif changer == "tab_position": - notebook = self.term.window.get_child() - new_pos = self.tab_position_gtk[values[changer]] - angle = 0 - if isinstance (notebook, gtk.Notebook): - notebook.set_tab_pos(new_pos) - for i in xrange(0,notebook.get_n_pages()): - notebook.get_tab_label(notebook.get_nth_page(i)).update_angle() - pass - elif changer == "close_button_on_tab": - notebook = self.term.window.get_child() - if isinstance (notebook, gtk.Notebook): - for i in xrange(0,notebook.get_n_pages()): - notebook.get_tab_label(notebook.get_nth_page(i)).update_closebut() - # FIXME: which others? cycle_term_tab, copy_on_selection, extreme_tabs, try_posix_regexp - - self.term.reconfigure_vtes() + type = self.source_get_type (key) + value = self.source_get_value (key) + widget = None - # Check for changed keybindings - changed_keybindings = [] - for row in self.liststore: - accel = gtk.accelerator_name (row[2], row[3]) - value = self.term.conf.keybindings[row[0]] - if isinstance (value, tuple): - value = value[0] - keyval = 0 - mask = 0 - if value is not None and value != "None": - try: - (keyval, mask) = self.tkbobj._parsebinding(value) - except KeymapError: - pass - if (row[2], row[3]) != (keyval, mask): - changed_keybindings.append ((row[0], accel)) - dbg("%s changed from %s to %s" % (row[0], self.term.conf.keybindings[row[0]], accel)) + if key == 'font': + widget = gtk.FontButton(value) + elif key == 'scrollback_lines': + # estimated byte size per line according to g-t: + # sizeof(void *) + sizeof(char *) + sizeof(int) + (80 * (sizeof(int32) + 4) + widget = gtk.SpinButton() + widget.set_digits(0) + widget.set_increments(100, 1000) + widget.set_range(0, 100000) + widget.set_value(value) + elif key == 'scrollbar_position': + if value == 'hidden': + value = 'disabled' + widget = gtk.combo_box_new_text() + for item in self.scrollbar_position: + widget.append_text (item) + if value in self.scrollbar_position: + widget.set_active (self.scrollbar_position.index(value)) + elif key == 'backspace_binding': + widget = gtk.combo_box_new_text() + for item in self.backspace_del_binding: + widget.append_text (item) + if value in self.backspace_del_binding: + widget.set_active (self.backspace_del_binding.index(value)) + elif key == 'delete_binding': + widget = gtk.combo_box_new_text() + for item in self.backspace_del_binding: + widget.append_text (item) + if value in self.backspace_del_binding: + widget.set_active (self.backspace_del_binding.index(value)) + elif key == 'focus': + widget = gtk.combo_box_new_text() + for item in self.focus: + widget.append_text (item) + if value in self.focus: + widget.set_active (self.focus.index(value)) + elif key == 'background_type': + widget = gtk.combo_box_new_text() + for item in self.background_type: + widget.append_text (item) + if value in self.background_type: + widget.set_active (self.background_type.index(value)) + elif key == 'background_darkness': + widget = gtk.HScale () + widget.set_digits (1) + widget.set_draw_value (True) + widget.set_value_pos (gtk.POS_LEFT) + widget.set_range (0, 1) + widget.set_value (value) + elif key == 'handle_size': + widget = gtk.HScale () + widget.set_digits (0) + widget.set_draw_value (True) + widget.set_value_pos (gtk.POS_LEFT) + widget.set_range (-1, 5) + widget.set_value (value) + elif key == 'foreground_color': + widget = gtk.ColorButton (gtk.gdk.color_parse (value)) + elif key == 'background_color': + widget = gtk.ColorButton (gtk.gdk.color_parse (value)) + elif key == 'cursor_color': + if not value: + value = self.source_get_value ('foreground_color') + widget = gtk.ColorButton (gtk.gdk.color_parse (value)) + elif key == 'palette': + colours = value.split (':') + numcolours = len (colours) + widget = gtk.Table (2, numcolours / 2) + x = 0 + y = 0 + for thing in colours: + if x == numcolours / 2: + y += 1 + x = 0 + widget.attach (gtk.ColorButton (gtk.gdk.color_parse (thing)), x, x + 1, y, y + 1) + x += 1 + elif key in ['title_tx_txt_color', 'title_tx_bg_color', 'title_rx_txt_color', 'title_rx_bg_color', 'title_ia_txt_color', 'title_ia_bg_color']: + widget = gtk.ColorButton (gtk.gdk.color_parse (value)) + elif key == 'background_image': + widget = gtk.FileChooserButton('Select a File') + filter = gtk.FileFilter() + filter.add_mime_type ('image/*') + widget.add_filter (filter) + widget.set_local_only (True) + if value: + widget.set_filename (value) + elif key == 'tab_position': + widget = gtk.combo_box_new_text() + for item in self.tab_position: + widget.append_text (item) + if value in self.tab_position: + widget.set_active (self.tab_position.index(value)) + elif key == 'cursor_shape': + widget = gtk.combo_box_new_text() + for item in self.cursor_shape: + widget.append_text (item) + if value in self.cursor_shape: + widget.set_active (self.cursor_shape.index (value)) + else: + print "doing %s automagically" % key + if type == "bool": + widget = gtk.CheckButton () + widget.set_active (value == 'True') + elif type in ["str", "int", "float"]: + widget = gtk.Entry () + widget.set_text (str(value)) + elif type == "list": + continue + else: + err("Unknown type: %s for key: %s" % (type, key)) + continue - newbindings = self.term.conf.keybindings - for binding in changed_keybindings: - newbindings[binding[0]] = binding[1] - self.term.keybindings.configure (newbindings) + if hasattr(widget, 'set_tooltip_text') and self.data.has_key (key): + widget.set_tooltip_text (self.data[key][1]) + + widget.set_name(key) + self.widgets[key] = widget + table.attach (wrapperbox, 0, 1, row, row + 1, gtk.EXPAND|gtk.FILL, gtk.FILL) + table.attach (widget, 1, 2, row, row + 1, gtk.EXPAND|gtk.FILL, gtk.FILL) + row += 1 - def cancel (self, data): - self.window.destroy() - self.term.options = None - del(self) + return (table) - def prepare_keybindings (self): - self.liststore = gtk.ListStore (gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_UINT, gobject.TYPE_UINT, gobject.TYPE_BOOLEAN) - self.liststore.set_sort_column_id (0, gtk.SORT_ASCENDING) - self.tkbobj = TerminatorKeybindings() - keyval = None - mask = None + def apply (self, data): + values = {} + for page in [self.appearance, self.behaviour, self.globals, self.colours]: + for property in page: + widget = self.widgets[property] - for binding in DEFAULTS['keybindings']: - value = self.term.conf.keybindings[binding] - keyval = 0 - mask = 0 - if isinstance (value, tuple): - value = value[0] - if value is not None and value != "None": - try: - (keyval, mask) = self.tkbobj._parsebinding (value) - except KeymapError: - pass - self.liststore.append ([binding, self.source_get_keyname (binding), keyval, mask, True]) - dbg("Appended row: %s, %s, %s" % (binding, keyval, mask)) + if isinstance (widget, gtk.SpinButton): + value = widget.get_value () + elif isinstance (widget, gtk.Entry): + value = widget.get_text() + elif isinstance (widget, gtk.CheckButton): + value = widget.get_active() + elif isinstance (widget, gtk.ComboBox): + if widget.name == 'scrollbar_position': + bucket = self.scrollbar_position + elif widget.name == 'backspace_binding' or widget.name == 'delete_binding': + bucket = self.backspace_del_binding + elif widget.name == 'focus': + bucket = self.focus + elif widget.name == 'background_type': + bucket = self.background_type + elif widget.name == 'tab_position': + bucket = self.tab_position + elif widget.name == 'cursor_shape': + bucket = self.cursor_shape + else: + err("Unknown bucket type for %s" % widget.name) + continue + + value = bucket[widget.get_active()] + elif isinstance (widget, gtk.FontButton): + value = widget.get_font_name() + elif isinstance (widget, gtk.HScale): + value = widget.get_value() + if widget.get_digits() == 0: + value = int(value) + elif isinstance (widget, gtk.ColorButton): + value = widget.get_color().to_string() + elif isinstance (widget, gtk.FileChooserButton): + value = widget.get_filename() + elif widget.get_name() == 'palette': + value = '' + valuebits = [] + children = widget.get_children() + children.reverse() + for child in children: + valuebits.append (child.get_color().to_string()) + value = ':'.join (valuebits) + else: + value = None + err("skipping unknown property: %s" % property) - self.treeview = gtk.TreeView(self.liststore) + values[property] = value - cell = gtk.CellRendererText() - col = gtk.TreeViewColumn(_("Name")) - col.pack_start(cell, True) - col.add_attribute(cell, "text", 0) + has_changed = False + changed = [] + for source in self.config.sources: + if isinstance (source, TerminatorConfValuestoreRC): + for property in values: + try: + if self.source_get_value(property) != values[property]: + dbg("%s changed from %s to %s" % (property, self.source_get_value(property), values[property])) + source.values[property] = values[property] + has_changed = True + changed.append(property) + except KeyError: + pass + if has_changed: + for changer in changed: + if changer == "fullscreen": + self.term.fullscreen_absolute(values[changer]) + elif changer == "maximise": + if values[changer]: + self.term.maximize() + else: + self.term.unmaximize() + elif changer == "borderless": + self.term.window.set_decorated (not values[changer]) + elif changer == "handle_size": + self.term.set_handle_size(values[changer]) + gtk.rc_reset_styles(gtk.settings_get_default()) + elif changer == "tab_position": + notebook = self.term.window.get_child() + new_pos = self.tab_position_gtk[values[changer]] + angle = 0 + if isinstance (notebook, gtk.Notebook): + notebook.set_tab_pos(new_pos) + for i in xrange(0,notebook.get_n_pages()): + notebook.get_tab_label(notebook.get_nth_page(i)).update_angle() + pass + elif changer == "close_button_on_tab": + notebook = self.term.window.get_child() + if isinstance (notebook, gtk.Notebook): + for i in xrange(0,notebook.get_n_pages()): + notebook.get_tab_label(notebook.get_nth_page(i)).update_closebut() + # FIXME: which others? cycle_term_tab, copy_on_selection, try_posix_regexp + + self.term.reconfigure_vtes() - self.treeview.append_column(col) + # Check for changed keybindings + changed_keybindings = [] + for row in self.liststore: + accel = gtk.accelerator_name (row[2], row[3]) + value = self.term.conf.keybindings[row[0]] + if isinstance (value, tuple): + value = value[0] + keyval = 0 + mask = 0 + if value is not None and value != "None": + try: + (keyval, mask) = self.tkbobj._parsebinding(value) + except KeymapError: + pass + if (row[2], row[3]) != (keyval, mask): + changed_keybindings.append ((row[0], accel)) + dbg("%s changed from %s to %s" % (row[0], self.term.conf.keybindings[row[0]], accel)) - cell = gtk.CellRendererText() - col = gtk.TreeViewColumn(_("Action")) - col.pack_start(cell, True) - col.add_attribute(cell, "text", 1) + newbindings = self.term.conf.keybindings + for binding in changed_keybindings: + newbindings[binding[0]] = binding[1] + self.term.keybindings.configure (newbindings) - self.treeview.append_column(col) + def cancel (self, data): + self.window.destroy() + self.term.options = None + del(self) - cell = gtk.CellRendererAccel() - col = gtk.TreeViewColumn(_("Keyboard shortcut")) - col.pack_start(cell, True) - col.set_attributes(cell, accel_key=2, accel_mods=3, editable=4) + def prepare_keybindings (self): + self.liststore = gtk.ListStore (gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_UINT, gobject.TYPE_UINT, gobject.TYPE_BOOLEAN) + self.liststore.set_sort_column_id (0, gtk.SORT_ASCENDING) + self.tkbobj = Keybindings() + keyval = None + mask = None - cell.connect ('accel-edited', self.edited) - cell.connect ('accel-cleared', self.cleared) + for binding in config.DEFAULTS['keybindings']: + value = self.config['keybindings'][binding] + keyval = 0 + mask = 0 + if isinstance (value, tuple): + value = value[0] + if value is not None and value != "None": + try: + (keyval, mask) = self.tkbobj._parsebinding (value) + except KeymapError: + pass + self.liststore.append ([binding, self.source_get_keyname (binding), keyval, mask, True]) + dbg("Appended row: %s, %s, %s" % (binding, keyval, mask)) - self.treeview.append_column(col) + self.treeview = gtk.TreeView(self.liststore) - scrollwin = gtk.ScrolledWindow () - scrollwin.set_policy (gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) - scrollwin.add (self.treeview) - return (scrollwin) + cell = gtk.CellRendererText() + col = gtk.TreeViewColumn(_("Name")) + col.pack_start(cell, True) + col.add_attribute(cell, "text", 0) - def edited (self, obj, path, key, mods, code): - iter = self.liststore.get_iter_from_string(path) - self.liststore.set(iter, 2, key, 3, mods) + self.treeview.append_column(col) - def cleared (self, obj, path): - iter = self.liststore.get_iter_from_string(path) - self.liststore.set(iter, 2, 0, 3, 0) + cell = gtk.CellRendererText() + col = gtk.TreeViewColumn(_("Action")) + col.pack_start(cell, True) + col.add_attribute(cell, "text", 1) + + self.treeview.append_column(col) + + cell = gtk.CellRendererAccel() + col = gtk.TreeViewColumn(_("Keyboard shortcut")) + col.pack_start(cell, True) + col.set_attributes(cell, accel_key=2, accel_mods=3, editable=4) + + cell.connect ('accel-edited', self.edited) + cell.connect ('accel-cleared', self.cleared) + + self.treeview.append_column(col) + + scrollwin = gtk.ScrolledWindow () + scrollwin.set_policy (gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + scrollwin.add (self.treeview) + return (scrollwin) + + def edited (self, obj, path, key, mods, code): + iter = self.liststore.get_iter_from_string(path) + self.liststore.set(iter, 2, key, 3, mods) + + def cleared (self, obj, path): + iter = self.liststore.get_iter_from_string(path) + self.liststore.set(iter, 2, 0, 3, 0) diff --git a/terminatorlib/terminal_popup_menu.py b/terminatorlib/terminal_popup_menu.py index b312bf72..313f02a3 100755 --- a/terminatorlib/terminal_popup_menu.py +++ b/terminatorlib/terminal_popup_menu.py @@ -11,6 +11,7 @@ from translation import _ from encoding import TerminatorEncoding from util import err from config import Config +from profileeditor import ProfileEditor import plugin class TerminalPopupMenu(object): @@ -151,8 +152,7 @@ class TerminalPopupMenu(object): submenu.append(gtk.MenuItem()) item = gtk.MenuItem(_('Ed_it profiles')) - item.connect('activate', lambda x: - terminal.terminator.edit_profile(terminal)) + item.connect('activate', lambda x: ProfileEditor(self.terminal)) submenu.append(item) self.add_encoding_items(menu) From dc9ae3363d747e58f08721f1403fbfacdb5d7d67 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 28 Dec 2009 22:06:23 +0000 Subject: [PATCH 213/331] Add ConfigObj's validate.py and construct a config specification and use it to validate the configuration. Most crucially this causes ConfigObj to know about the correct types it should be converting the different entries into --- terminatorlib/config.py | 70 +- terminatorlib/validate.py | 1465 +++++++++++++++++++++++++++++++++++++ 2 files changed, 1533 insertions(+), 2 deletions(-) create mode 100644 terminatorlib/validate.py diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 19abb559..7ecf1263 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -44,9 +44,10 @@ import os import sys from copy import copy from configobj import ConfigObj +from validate import Validator from borg import Borg from factory import Factory -from util import dbg, get_config_dir, dict_diff +from util import dbg, err, DEBUG, get_config_dir, dict_diff DEFAULTS = { 'global_config': { @@ -254,6 +255,64 @@ class ConfigBase(Borg): if self.layouts is None: self.layouts = copy(DEFAULTS['layouts']) + def defaults_to_configspec(self): + """Convert our tree of default values into a ConfigObj validation + specification""" + configspecdata = {} + + section = {} + for key in DEFAULTS['global_config']: + keytype = DEFAULTS['global_config'][key].__class__.__name__ + value = DEFAULTS['global_config'][key] + if keytype == 'int': + keytype = 'integer' + elif keytype == 'str': + keytype = 'string' + elif keytype == 'bool': + keytype = 'boolean' + elif keytype == 'list': + value = 'list(%s)' % ','.join(value) + + keytype = '%s(default=%s)' % (keytype, value) + + section[key] = keytype + configspecdata['global_config'] = section + + section = {} + for key in DEFAULTS['keybindings']: + value = DEFAULTS['keybindings'][key] + if value is None: + continue + elif isinstance(value, tuple): + value = value[0] + section[key] = 'string(default=%s)' % value + configspecdata['keybindings'] = section + + section = {} + for key in DEFAULTS['profiles']['default']: + keytype = DEFAULTS['profiles']['default'][key].__class__.__name__ + value = DEFAULTS['profiles']['default'][key] + if keytype == 'int': + keytype = 'integer' + elif keytype == 'bool': + keytype = 'boolean' + elif keytype == 'str': + keytype = 'string' + value = '"%s"' % value + elif keytype == 'list': + value = 'list(%s)' % ','.join(value) + + keytype = '%s(default=%s)' % (keytype, value) + + section[key] = keytype + configspecdata['profiles'] = {} + configspecdata['profiles']['__many__'] = section + + configspec = ConfigObj(configspecdata) + if DEBUG == True: + configspec.write(open('/tmp/configspec', 'w')) + return(configspec) + def load(self): """Load configuration data from our various sources""" if self.loaded is True: @@ -267,7 +326,14 @@ class ConfigBase(Borg): dbg('ConfigBase::load: Unable to open %s (%s)' % (filename, ex)) return - parser = ConfigObj(configfile) + configspec = self.defaults_to_configspec() + parser = ConfigObj(configfile, configspec=configspec) + validator = Validator() + result = parser.validate(validator, preserve_errors=True) + + if result != True: + err('ConfigBase::load: config format is not valid') + for section_name in self.sections: dbg('ConfigBase::load: Processing section: %s' % section_name) section = getattr(self, section_name) diff --git a/terminatorlib/validate.py b/terminatorlib/validate.py new file mode 100644 index 00000000..30bdfacb --- /dev/null +++ b/terminatorlib/validate.py @@ -0,0 +1,1465 @@ +# validate.py +# A Validator object +# Copyright (C) 2005 Michael Foord, Mark Andrews, Nicola Larosa +# E-mail: fuzzyman AT voidspace DOT org DOT uk +# mark AT la-la DOT com +# nico AT tekNico DOT net + +# This software is licensed under the terms of the BSD license. +# http://www.voidspace.org.uk/python/license.shtml +# Basically you're free to copy, modify, distribute and relicense it, +# So long as you keep a copy of the license with it. + +# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml +# For information about bugfixes, updates and support, please join the +# ConfigObj mailing list: +# http://lists.sourceforge.net/lists/listinfo/configobj-develop +# Comments, suggestions and bug reports welcome. + +""" + The Validator object is used to check that supplied values + conform to a specification. + + The value can be supplied as a string - e.g. from a config file. + In this case the check will also *convert* the value to + the required type. This allows you to add validation + as a transparent layer to access data stored as strings. + The validation checks that the data is correct *and* + converts it to the expected type. + + Some standard checks are provided for basic data types. + Additional checks are easy to write. They can be + provided when the ``Validator`` is instantiated or + added afterwards. + + The standard functions work with the following basic data types : + + * integers + * floats + * booleans + * strings + * ip_addr + + plus lists of these datatypes + + Adding additional checks is done through coding simple functions. + + The full set of standard checks are : + + * 'integer': matches integer values (including negative) + Takes optional 'min' and 'max' arguments : :: + + integer() + integer(3, 9) # any value from 3 to 9 + integer(min=0) # any positive value + integer(max=9) + + * 'float': matches float values + Has the same parameters as the integer check. + + * 'boolean': matches boolean values - ``True`` or ``False`` + Acceptable string values for True are : + true, on, yes, 1 + Acceptable string values for False are : + false, off, no, 0 + + Any other value raises an error. + + * 'ip_addr': matches an Internet Protocol address, v.4, represented + by a dotted-quad string, i.e. '1.2.3.4'. + + * 'string': matches any string. + Takes optional keyword args 'min' and 'max' + to specify min and max lengths of the string. + + * 'list': matches any list. + Takes optional keyword args 'min', and 'max' to specify min and + max sizes of the list. (Always returns a list.) + + * 'tuple': matches any tuple. + Takes optional keyword args 'min', and 'max' to specify min and + max sizes of the tuple. (Always returns a tuple.) + + * 'int_list': Matches a list of integers. + Takes the same arguments as list. + + * 'float_list': Matches a list of floats. + Takes the same arguments as list. + + * 'bool_list': Matches a list of boolean values. + Takes the same arguments as list. + + * 'ip_addr_list': Matches a list of IP addresses. + Takes the same arguments as list. + + * 'string_list': Matches a list of strings. + Takes the same arguments as list. + + * 'mixed_list': Matches a list with different types in + specific positions. List size must match + the number of arguments. + + Each position can be one of : + 'integer', 'float', 'ip_addr', 'string', 'boolean' + + So to specify a list with two strings followed + by two integers, you write the check as : :: + + mixed_list('string', 'string', 'integer', 'integer') + + * 'pass': This check matches everything ! It never fails + and the value is unchanged. + + It is also the default if no check is specified. + + * 'option': This check matches any from a list of options. + You specify this check with : :: + + option('option 1', 'option 2', 'option 3') + + You can supply a default value (returned if no value is supplied) + using the default keyword argument. + + You specify a list argument for default using a list constructor syntax in + the check : :: + + checkname(arg1, arg2, default=list('val 1', 'val 2', 'val 3')) + + A badly formatted set of arguments will raise a ``VdtParamError``. +""" + +__docformat__ = "restructuredtext en" + +__version__ = '1.0.0' + +__revision__ = '$Id: validate.py 123 2005-09-08 08:54:28Z fuzzyman $' + +__all__ = ( + '__version__', + 'dottedQuadToNum', + 'numToDottedQuad', + 'ValidateError', + 'VdtUnknownCheckError', + 'VdtParamError', + 'VdtTypeError', + 'VdtValueError', + 'VdtValueTooSmallError', + 'VdtValueTooBigError', + 'VdtValueTooShortError', + 'VdtValueTooLongError', + 'VdtMissingValue', + 'Validator', + 'is_integer', + 'is_float', + 'is_boolean', + 'is_list', + 'is_tuple', + 'is_ip_addr', + 'is_string', + 'is_int_list', + 'is_bool_list', + 'is_float_list', + 'is_string_list', + 'is_ip_addr_list', + 'is_mixed_list', + 'is_option', + '__docformat__', +) + + +import re + + +_list_arg = re.compile(r''' + (?: + ([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*list\( + ( + (?: + \s* + (?: + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'",\s\)][^,\)]*?) # unquoted + ) + \s*,\s* + )* + (?: + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'",\s\)][^,\)]*?) # unquoted + )? # last one + ) + \) + ) +''', re.VERBOSE | re.DOTALL) # two groups + +_list_members = re.compile(r''' + ( + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'",\s=][^,=]*?) # unquoted + ) + (?: + (?:\s*,\s*)|(?:\s*$) # comma + ) +''', re.VERBOSE | re.DOTALL) # one group + +_paramstring = r''' + (?: + ( + (?: + [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*list\( + (?: + \s* + (?: + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'",\s\)][^,\)]*?) # unquoted + ) + \s*,\s* + )* + (?: + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'",\s\)][^,\)]*?) # unquoted + )? # last one + \) + )| + (?: + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'",\s=][^,=]*?)| # unquoted + (?: # keyword argument + [a-zA-Z_][a-zA-Z0-9_]*\s*=\s* + (?: + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'",\s=][^,=]*?) # unquoted + ) + ) + ) + ) + (?: + (?:\s*,\s*)|(?:\s*$) # comma + ) + ) + ''' + +_matchstring = '^%s*' % _paramstring + +# Python pre 2.2.1 doesn't have bool +try: + bool +except NameError: + def bool(val): + """Simple boolean equivalent function. """ + if val: + return 1 + else: + return 0 + + +def dottedQuadToNum(ip): + """ + Convert decimal dotted quad string to long integer + + >>> int(dottedQuadToNum('1 ')) + 1 + >>> int(dottedQuadToNum(' 1.2')) + 16777218 + >>> int(dottedQuadToNum(' 1.2.3 ')) + 16908291 + >>> int(dottedQuadToNum('1.2.3.4')) + 16909060 + >>> dottedQuadToNum('1.2.3. 4') + 16909060 + >>> dottedQuadToNum('255.255.255.255') + 4294967295L + >>> dottedQuadToNum('255.255.255.256') + Traceback (most recent call last): + ValueError: Not a good dotted-quad IP: 255.255.255.256 + """ + + # import here to avoid it when ip_addr values are not used + import socket, struct + + try: + return struct.unpack('!L', + socket.inet_aton(ip.strip()))[0] + except socket.error: + # bug in inet_aton, corrected in Python 2.3 + if ip.strip() == '255.255.255.255': + return 0xFFFFFFFFL + else: + raise ValueError('Not a good dotted-quad IP: %s' % ip) + return + + +def numToDottedQuad(num): + """ + Convert long int to dotted quad string + + >>> numToDottedQuad(-1L) + Traceback (most recent call last): + ValueError: Not a good numeric IP: -1 + >>> numToDottedQuad(1L) + '0.0.0.1' + >>> numToDottedQuad(16777218L) + '1.0.0.2' + >>> numToDottedQuad(16908291L) + '1.2.0.3' + >>> numToDottedQuad(16909060L) + '1.2.3.4' + >>> numToDottedQuad(4294967295L) + '255.255.255.255' + >>> numToDottedQuad(4294967296L) + Traceback (most recent call last): + ValueError: Not a good numeric IP: 4294967296 + """ + + # import here to avoid it when ip_addr values are not used + import socket, struct + + # no need to intercept here, 4294967295L is fine + if num > 4294967295L or num < 0: + raise ValueError('Not a good numeric IP: %s' % num) + try: + return socket.inet_ntoa( + struct.pack('!L', long(num))) + except (socket.error, struct.error, OverflowError): + raise ValueError('Not a good numeric IP: %s' % num) + + +class ValidateError(Exception): + """ + This error indicates that the check failed. + It can be the base class for more specific errors. + + Any check function that fails ought to raise this error. + (or a subclass) + + >>> raise ValidateError + Traceback (most recent call last): + ValidateError + """ + + +class VdtMissingValue(ValidateError): + """No value was supplied to a check that needed one.""" + + +class VdtUnknownCheckError(ValidateError): + """An unknown check function was requested""" + + def __init__(self, value): + """ + >>> raise VdtUnknownCheckError('yoda') + Traceback (most recent call last): + VdtUnknownCheckError: the check "yoda" is unknown. + """ + ValidateError.__init__(self, 'the check "%s" is unknown.' % (value,)) + + +class VdtParamError(SyntaxError): + """An incorrect parameter was passed""" + + def __init__(self, name, value): + """ + >>> raise VdtParamError('yoda', 'jedi') + Traceback (most recent call last): + VdtParamError: passed an incorrect value "jedi" for parameter "yoda". + """ + SyntaxError.__init__(self, 'passed an incorrect value "%s" for parameter "%s".' % (value, name)) + + +class VdtTypeError(ValidateError): + """The value supplied was of the wrong type""" + + def __init__(self, value): + """ + >>> raise VdtTypeError('jedi') + Traceback (most recent call last): + VdtTypeError: the value "jedi" is of the wrong type. + """ + ValidateError.__init__(self, 'the value "%s" is of the wrong type.' % (value,)) + + +class VdtValueError(ValidateError): + """The value supplied was of the correct type, but was not an allowed value.""" + + def __init__(self, value): + """ + >>> raise VdtValueError('jedi') + Traceback (most recent call last): + VdtValueError: the value "jedi" is unacceptable. + """ + ValidateError.__init__(self, 'the value "%s" is unacceptable.' % (value,)) + + +class VdtValueTooSmallError(VdtValueError): + """The value supplied was of the correct type, but was too small.""" + + def __init__(self, value): + """ + >>> raise VdtValueTooSmallError('0') + Traceback (most recent call last): + VdtValueTooSmallError: the value "0" is too small. + """ + ValidateError.__init__(self, 'the value "%s" is too small.' % (value,)) + + +class VdtValueTooBigError(VdtValueError): + """The value supplied was of the correct type, but was too big.""" + + def __init__(self, value): + """ + >>> raise VdtValueTooBigError('1') + Traceback (most recent call last): + VdtValueTooBigError: the value "1" is too big. + """ + ValidateError.__init__(self, 'the value "%s" is too big.' % (value,)) + + +class VdtValueTooShortError(VdtValueError): + """The value supplied was of the correct type, but was too short.""" + + def __init__(self, value): + """ + >>> raise VdtValueTooShortError('jed') + Traceback (most recent call last): + VdtValueTooShortError: the value "jed" is too short. + """ + ValidateError.__init__( + self, + 'the value "%s" is too short.' % (value,)) + + +class VdtValueTooLongError(VdtValueError): + """The value supplied was of the correct type, but was too long.""" + + def __init__(self, value): + """ + >>> raise VdtValueTooLongError('jedie') + Traceback (most recent call last): + VdtValueTooLongError: the value "jedie" is too long. + """ + ValidateError.__init__(self, 'the value "%s" is too long.' % (value,)) + + +class Validator(object): + """ + Validator is an object that allows you to register a set of 'checks'. + These checks take input and test that it conforms to the check. + + This can also involve converting the value from a string into + the correct datatype. + + The ``check`` method takes an input string which configures which + check is to be used and applies that check to a supplied value. + + An example input string would be: + 'int_range(param1, param2)' + + You would then provide something like: + + >>> def int_range_check(value, min, max): + ... # turn min and max from strings to integers + ... min = int(min) + ... max = int(max) + ... # check that value is of the correct type. + ... # possible valid inputs are integers or strings + ... # that represent integers + ... if not isinstance(value, (int, long, basestring)): + ... raise VdtTypeError(value) + ... elif isinstance(value, basestring): + ... # if we are given a string + ... # attempt to convert to an integer + ... try: + ... value = int(value) + ... except ValueError: + ... raise VdtValueError(value) + ... # check the value is between our constraints + ... if not min <= value: + ... raise VdtValueTooSmallError(value) + ... if not value <= max: + ... raise VdtValueTooBigError(value) + ... return value + + >>> fdict = {'int_range': int_range_check} + >>> vtr1 = Validator(fdict) + >>> vtr1.check('int_range(20, 40)', '30') + 30 + >>> vtr1.check('int_range(20, 40)', '60') + Traceback (most recent call last): + VdtValueTooBigError: the value "60" is too big. + + New functions can be added with : :: + + >>> vtr2 = Validator() + >>> vtr2.functions['int_range'] = int_range_check + + Or by passing in a dictionary of functions when Validator + is instantiated. + + Your functions *can* use keyword arguments, + but the first argument should always be 'value'. + + If the function doesn't take additional arguments, + the parentheses are optional in the check. + It can be written with either of : :: + + keyword = function_name + keyword = function_name() + + The first program to utilise Validator() was Michael Foord's + ConfigObj, an alternative to ConfigParser which supports lists and + can validate a config file using a config schema. + For more details on using Validator with ConfigObj see: + http://www.voidspace.org.uk/python/configobj.html + """ + + # this regex does the initial parsing of the checks + _func_re = re.compile(r'(.+?)\((.*)\)', re.DOTALL) + + # this regex takes apart keyword arguments + _key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$', re.DOTALL) + + + # this regex finds keyword=list(....) type values + _list_arg = _list_arg + + # this regex takes individual values out of lists - in one pass + _list_members = _list_members + + # These regexes check a set of arguments for validity + # and then pull the members out + _paramfinder = re.compile(_paramstring, re.VERBOSE | re.DOTALL) + _matchfinder = re.compile(_matchstring, re.VERBOSE | re.DOTALL) + + + def __init__(self, functions=None): + """ + >>> vtri = Validator() + """ + self.functions = { + '': self._pass, + 'integer': is_integer, + 'float': is_float, + 'boolean': is_boolean, + 'ip_addr': is_ip_addr, + 'string': is_string, + 'list': is_list, + 'tuple': is_tuple, + 'int_list': is_int_list, + 'float_list': is_float_list, + 'bool_list': is_bool_list, + 'ip_addr_list': is_ip_addr_list, + 'string_list': is_string_list, + 'mixed_list': is_mixed_list, + 'pass': self._pass, + 'option': is_option, + 'force_list': force_list, + } + if functions is not None: + self.functions.update(functions) + # tekNico: for use by ConfigObj + self.baseErrorClass = ValidateError + self._cache = {} + + + def check(self, check, value, missing=False): + """ + Usage: check(check, value) + + Arguments: + check: string representing check to apply (including arguments) + value: object to be checked + Returns value, converted to correct type if necessary + + If the check fails, raises a ``ValidateError`` subclass. + + >>> vtor.check('yoda', '') + Traceback (most recent call last): + VdtUnknownCheckError: the check "yoda" is unknown. + >>> vtor.check('yoda()', '') + Traceback (most recent call last): + VdtUnknownCheckError: the check "yoda" is unknown. + + >>> vtor.check('string(default="")', '', missing=True) + '' + """ + fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check) + + if missing: + if default is None: + # no information needed here - to be handled by caller + raise VdtMissingValue() + value = self._handle_none(default) + + if value is None: + return None + + return self._check_value(value, fun_name, fun_args, fun_kwargs) + + + def _handle_none(self, value): + if value == 'None': + value = None + elif value in ("'None'", '"None"'): + # Special case a quoted None + value = self._unquote(value) + return value + + + def _parse_with_caching(self, check): + if check in self._cache: + fun_name, fun_args, fun_kwargs, default = self._cache[check] + # We call list and dict below to work with *copies* of the data + # rather than the original (which are mutable of course) + fun_args = list(fun_args) + fun_kwargs = dict(fun_kwargs) + else: + fun_name, fun_args, fun_kwargs, default = self._parse_check(check) + fun_kwargs = dict((str(key), value) for (key, value) in fun_kwargs.items()) + self._cache[check] = fun_name, list(fun_args), dict(fun_kwargs), default + return fun_name, fun_args, fun_kwargs, default + + + def _check_value(self, value, fun_name, fun_args, fun_kwargs): + try: + fun = self.functions[fun_name] + except KeyError: + raise VdtUnknownCheckError(fun_name) + else: + return fun(value, *fun_args, **fun_kwargs) + + + def _parse_check(self, check): + fun_match = self._func_re.match(check) + if fun_match: + fun_name = fun_match.group(1) + arg_string = fun_match.group(2) + arg_match = self._matchfinder.match(arg_string) + if arg_match is None: + # Bad syntax + raise VdtParamError('Bad syntax in check "%s".' % check) + fun_args = [] + fun_kwargs = {} + # pull out args of group 2 + for arg in self._paramfinder.findall(arg_string): + # args may need whitespace removing (before removing quotes) + arg = arg.strip() + listmatch = self._list_arg.match(arg) + if listmatch: + key, val = self._list_handle(listmatch) + fun_kwargs[key] = val + continue + keymatch = self._key_arg.match(arg) + if keymatch: + val = keymatch.group(2) + if not val in ("'None'", '"None"'): + # Special case a quoted None + val = self._unquote(val) + fun_kwargs[keymatch.group(1)] = val + continue + + fun_args.append(self._unquote(arg)) + else: + # allows for function names without (args) + return check, (), {}, None + + # Default must be deleted if the value is specified too, + # otherwise the check function will get a spurious "default" keyword arg + try: + default = fun_kwargs.pop('default', None) + except AttributeError: + # Python 2.2 compatibility + default = None + try: + default = fun_kwargs['default'] + del fun_kwargs['default'] + except KeyError: + pass + + return fun_name, fun_args, fun_kwargs, default + + + def _unquote(self, val): + """Unquote a value if necessary.""" + if (len(val) >= 2) and (val[0] in ("'", '"')) and (val[0] == val[-1]): + val = val[1:-1] + return val + + + def _list_handle(self, listmatch): + """Take apart a ``keyword=list('val, 'val')`` type string.""" + out = [] + name = listmatch.group(1) + args = listmatch.group(2) + for arg in self._list_members.findall(args): + out.append(self._unquote(arg)) + return name, out + + + def _pass(self, value): + """ + Dummy check that always passes + + >>> vtor.check('', 0) + 0 + >>> vtor.check('', '0') + '0' + """ + return value + + + def get_default_value(self, check): + """ + Given a check, return the default value for the check + (converted to the right type). + + If the check doesn't specify a default value then a + ``KeyError`` will be raised. + """ + fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check) + if default is None: + raise KeyError('Check "%s" has no default value.' % check) + value = self._handle_none(default) + if value is None: + return value + return self._check_value(value, fun_name, fun_args, fun_kwargs) + + +def _is_num_param(names, values, to_float=False): + """ + Return numbers from inputs or raise VdtParamError. + + Lets ``None`` pass through. + Pass in keyword argument ``to_float=True`` to + use float for the conversion rather than int. + + >>> _is_num_param(('', ''), (0, 1.0)) + [0, 1] + >>> _is_num_param(('', ''), (0, 1.0), to_float=True) + [0.0, 1.0] + >>> _is_num_param(('a'), ('a')) + Traceback (most recent call last): + VdtParamError: passed an incorrect value "a" for parameter "a". + """ + fun = to_float and float or int + out_params = [] + for (name, val) in zip(names, values): + if val is None: + out_params.append(val) + elif isinstance(val, (int, long, float, basestring)): + try: + out_params.append(fun(val)) + except ValueError, e: + raise VdtParamError(name, val) + else: + raise VdtParamError(name, val) + return out_params + + +# built in checks +# you can override these by setting the appropriate name +# in Validator.functions +# note: if the params are specified wrongly in your input string, +# you will also raise errors. + +def is_integer(value, min=None, max=None): + """ + A check that tests that a given value is an integer (int, or long) + and optionally, between bounds. A negative value is accepted, while + a float will fail. + + If the value is a string, then the conversion is done - if possible. + Otherwise a VdtError is raised. + + >>> vtor.check('integer', '-1') + -1 + >>> vtor.check('integer', '0') + 0 + >>> vtor.check('integer', 9) + 9 + >>> vtor.check('integer', 'a') + Traceback (most recent call last): + VdtTypeError: the value "a" is of the wrong type. + >>> vtor.check('integer', '2.2') + Traceback (most recent call last): + VdtTypeError: the value "2.2" is of the wrong type. + >>> vtor.check('integer(10)', '20') + 20 + >>> vtor.check('integer(max=20)', '15') + 15 + >>> vtor.check('integer(10)', '9') + Traceback (most recent call last): + VdtValueTooSmallError: the value "9" is too small. + >>> vtor.check('integer(10)', 9) + Traceback (most recent call last): + VdtValueTooSmallError: the value "9" is too small. + >>> vtor.check('integer(max=20)', '35') + Traceback (most recent call last): + VdtValueTooBigError: the value "35" is too big. + >>> vtor.check('integer(max=20)', 35) + Traceback (most recent call last): + VdtValueTooBigError: the value "35" is too big. + >>> vtor.check('integer(0, 9)', False) + 0 + """ + (min_val, max_val) = _is_num_param(('min', 'max'), (min, max)) + if not isinstance(value, (int, long, basestring)): + raise VdtTypeError(value) + if isinstance(value, basestring): + # if it's a string - does it represent an integer ? + try: + value = int(value) + except ValueError: + raise VdtTypeError(value) + if (min_val is not None) and (value < min_val): + raise VdtValueTooSmallError(value) + if (max_val is not None) and (value > max_val): + raise VdtValueTooBigError(value) + return value + + +def is_float(value, min=None, max=None): + """ + A check that tests that a given value is a float + (an integer will be accepted), and optionally - that it is between bounds. + + If the value is a string, then the conversion is done - if possible. + Otherwise a VdtError is raised. + + This can accept negative values. + + >>> vtor.check('float', '2') + 2.0 + + From now on we multiply the value to avoid comparing decimals + + >>> vtor.check('float', '-6.8') * 10 + -68.0 + >>> vtor.check('float', '12.2') * 10 + 122.0 + >>> vtor.check('float', 8.4) * 10 + 84.0 + >>> vtor.check('float', 'a') + Traceback (most recent call last): + VdtTypeError: the value "a" is of the wrong type. + >>> vtor.check('float(10.1)', '10.2') * 10 + 102.0 + >>> vtor.check('float(max=20.2)', '15.1') * 10 + 151.0 + >>> vtor.check('float(10.0)', '9.0') + Traceback (most recent call last): + VdtValueTooSmallError: the value "9.0" is too small. + >>> vtor.check('float(max=20.0)', '35.0') + Traceback (most recent call last): + VdtValueTooBigError: the value "35.0" is too big. + """ + (min_val, max_val) = _is_num_param( + ('min', 'max'), (min, max), to_float=True) + if not isinstance(value, (int, long, float, basestring)): + raise VdtTypeError(value) + if not isinstance(value, float): + # if it's a string - does it represent a float ? + try: + value = float(value) + except ValueError: + raise VdtTypeError(value) + if (min_val is not None) and (value < min_val): + raise VdtValueTooSmallError(value) + if (max_val is not None) and (value > max_val): + raise VdtValueTooBigError(value) + return value + + +bool_dict = { + True: True, 'on': True, '1': True, 'true': True, 'yes': True, + False: False, 'off': False, '0': False, 'false': False, 'no': False, +} + + +def is_boolean(value): + """ + Check if the value represents a boolean. + + >>> vtor.check('boolean', 0) + 0 + >>> vtor.check('boolean', False) + 0 + >>> vtor.check('boolean', '0') + 0 + >>> vtor.check('boolean', 'off') + 0 + >>> vtor.check('boolean', 'false') + 0 + >>> vtor.check('boolean', 'no') + 0 + >>> vtor.check('boolean', 'nO') + 0 + >>> vtor.check('boolean', 'NO') + 0 + >>> vtor.check('boolean', 1) + 1 + >>> vtor.check('boolean', True) + 1 + >>> vtor.check('boolean', '1') + 1 + >>> vtor.check('boolean', 'on') + 1 + >>> vtor.check('boolean', 'true') + 1 + >>> vtor.check('boolean', 'yes') + 1 + >>> vtor.check('boolean', 'Yes') + 1 + >>> vtor.check('boolean', 'YES') + 1 + >>> vtor.check('boolean', '') + Traceback (most recent call last): + VdtTypeError: the value "" is of the wrong type. + >>> vtor.check('boolean', 'up') + Traceback (most recent call last): + VdtTypeError: the value "up" is of the wrong type. + + """ + if isinstance(value, basestring): + try: + return bool_dict[value.lower()] + except KeyError: + raise VdtTypeError(value) + # we do an equality test rather than an identity test + # this ensures Python 2.2 compatibilty + # and allows 0 and 1 to represent True and False + if value == False: + return False + elif value == True: + return True + else: + raise VdtTypeError(value) + + +def is_ip_addr(value): + """ + Check that the supplied value is an Internet Protocol address, v.4, + represented by a dotted-quad string, i.e. '1.2.3.4'. + + >>> vtor.check('ip_addr', '1 ') + '1' + >>> vtor.check('ip_addr', ' 1.2') + '1.2' + >>> vtor.check('ip_addr', ' 1.2.3 ') + '1.2.3' + >>> vtor.check('ip_addr', '1.2.3.4') + '1.2.3.4' + >>> vtor.check('ip_addr', '0.0.0.0') + '0.0.0.0' + >>> vtor.check('ip_addr', '255.255.255.255') + '255.255.255.255' + >>> vtor.check('ip_addr', '255.255.255.256') + Traceback (most recent call last): + VdtValueError: the value "255.255.255.256" is unacceptable. + >>> vtor.check('ip_addr', '1.2.3.4.5') + Traceback (most recent call last): + VdtValueError: the value "1.2.3.4.5" is unacceptable. + >>> vtor.check('ip_addr', 0) + Traceback (most recent call last): + VdtTypeError: the value "0" is of the wrong type. + """ + if not isinstance(value, basestring): + raise VdtTypeError(value) + value = value.strip() + try: + dottedQuadToNum(value) + except ValueError: + raise VdtValueError(value) + return value + + +def is_list(value, min=None, max=None): + """ + Check that the value is a list of values. + + You can optionally specify the minimum and maximum number of members. + + It does no check on list members. + + >>> vtor.check('list', ()) + [] + >>> vtor.check('list', []) + [] + >>> vtor.check('list', (1, 2)) + [1, 2] + >>> vtor.check('list', [1, 2]) + [1, 2] + >>> vtor.check('list(3)', (1, 2)) + Traceback (most recent call last): + VdtValueTooShortError: the value "(1, 2)" is too short. + >>> vtor.check('list(max=5)', (1, 2, 3, 4, 5, 6)) + Traceback (most recent call last): + VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long. + >>> vtor.check('list(min=3, max=5)', (1, 2, 3, 4)) + [1, 2, 3, 4] + >>> vtor.check('list', 0) + Traceback (most recent call last): + VdtTypeError: the value "0" is of the wrong type. + >>> vtor.check('list', '12') + Traceback (most recent call last): + VdtTypeError: the value "12" is of the wrong type. + """ + (min_len, max_len) = _is_num_param(('min', 'max'), (min, max)) + if isinstance(value, basestring): + raise VdtTypeError(value) + try: + num_members = len(value) + except TypeError: + raise VdtTypeError(value) + if min_len is not None and num_members < min_len: + raise VdtValueTooShortError(value) + if max_len is not None and num_members > max_len: + raise VdtValueTooLongError(value) + return list(value) + + +def is_tuple(value, min=None, max=None): + """ + Check that the value is a tuple of values. + + You can optionally specify the minimum and maximum number of members. + + It does no check on members. + + >>> vtor.check('tuple', ()) + () + >>> vtor.check('tuple', []) + () + >>> vtor.check('tuple', (1, 2)) + (1, 2) + >>> vtor.check('tuple', [1, 2]) + (1, 2) + >>> vtor.check('tuple(3)', (1, 2)) + Traceback (most recent call last): + VdtValueTooShortError: the value "(1, 2)" is too short. + >>> vtor.check('tuple(max=5)', (1, 2, 3, 4, 5, 6)) + Traceback (most recent call last): + VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long. + >>> vtor.check('tuple(min=3, max=5)', (1, 2, 3, 4)) + (1, 2, 3, 4) + >>> vtor.check('tuple', 0) + Traceback (most recent call last): + VdtTypeError: the value "0" is of the wrong type. + >>> vtor.check('tuple', '12') + Traceback (most recent call last): + VdtTypeError: the value "12" is of the wrong type. + """ + return tuple(is_list(value, min, max)) + + +def is_string(value, min=None, max=None): + """ + Check that the supplied value is a string. + + You can optionally specify the minimum and maximum number of members. + + >>> vtor.check('string', '0') + '0' + >>> vtor.check('string', 0) + Traceback (most recent call last): + VdtTypeError: the value "0" is of the wrong type. + >>> vtor.check('string(2)', '12') + '12' + >>> vtor.check('string(2)', '1') + Traceback (most recent call last): + VdtValueTooShortError: the value "1" is too short. + >>> vtor.check('string(min=2, max=3)', '123') + '123' + >>> vtor.check('string(min=2, max=3)', '1234') + Traceback (most recent call last): + VdtValueTooLongError: the value "1234" is too long. + """ + if not isinstance(value, basestring): + raise VdtTypeError(value) + (min_len, max_len) = _is_num_param(('min', 'max'), (min, max)) + try: + num_members = len(value) + except TypeError: + raise VdtTypeError(value) + if min_len is not None and num_members < min_len: + raise VdtValueTooShortError(value) + if max_len is not None and num_members > max_len: + raise VdtValueTooLongError(value) + return value + + +def is_int_list(value, min=None, max=None): + """ + Check that the value is a list of integers. + + You can optionally specify the minimum and maximum number of members. + + Each list member is checked that it is an integer. + + >>> vtor.check('int_list', ()) + [] + >>> vtor.check('int_list', []) + [] + >>> vtor.check('int_list', (1, 2)) + [1, 2] + >>> vtor.check('int_list', [1, 2]) + [1, 2] + >>> vtor.check('int_list', [1, 'a']) + Traceback (most recent call last): + VdtTypeError: the value "a" is of the wrong type. + """ + return [is_integer(mem) for mem in is_list(value, min, max)] + + +def is_bool_list(value, min=None, max=None): + """ + Check that the value is a list of booleans. + + You can optionally specify the minimum and maximum number of members. + + Each list member is checked that it is a boolean. + + >>> vtor.check('bool_list', ()) + [] + >>> vtor.check('bool_list', []) + [] + >>> check_res = vtor.check('bool_list', (True, False)) + >>> check_res == [True, False] + 1 + >>> check_res = vtor.check('bool_list', [True, False]) + >>> check_res == [True, False] + 1 + >>> vtor.check('bool_list', [True, 'a']) + Traceback (most recent call last): + VdtTypeError: the value "a" is of the wrong type. + """ + return [is_boolean(mem) for mem in is_list(value, min, max)] + + +def is_float_list(value, min=None, max=None): + """ + Check that the value is a list of floats. + + You can optionally specify the minimum and maximum number of members. + + Each list member is checked that it is a float. + + >>> vtor.check('float_list', ()) + [] + >>> vtor.check('float_list', []) + [] + >>> vtor.check('float_list', (1, 2.0)) + [1.0, 2.0] + >>> vtor.check('float_list', [1, 2.0]) + [1.0, 2.0] + >>> vtor.check('float_list', [1, 'a']) + Traceback (most recent call last): + VdtTypeError: the value "a" is of the wrong type. + """ + return [is_float(mem) for mem in is_list(value, min, max)] + + +def is_string_list(value, min=None, max=None): + """ + Check that the value is a list of strings. + + You can optionally specify the minimum and maximum number of members. + + Each list member is checked that it is a string. + + >>> vtor.check('string_list', ()) + [] + >>> vtor.check('string_list', []) + [] + >>> vtor.check('string_list', ('a', 'b')) + ['a', 'b'] + >>> vtor.check('string_list', ['a', 1]) + Traceback (most recent call last): + VdtTypeError: the value "1" is of the wrong type. + >>> vtor.check('string_list', 'hello') + Traceback (most recent call last): + VdtTypeError: the value "hello" is of the wrong type. + """ + if isinstance(value, basestring): + raise VdtTypeError(value) + return [is_string(mem) for mem in is_list(value, min, max)] + + +def is_ip_addr_list(value, min=None, max=None): + """ + Check that the value is a list of IP addresses. + + You can optionally specify the minimum and maximum number of members. + + Each list member is checked that it is an IP address. + + >>> vtor.check('ip_addr_list', ()) + [] + >>> vtor.check('ip_addr_list', []) + [] + >>> vtor.check('ip_addr_list', ('1.2.3.4', '5.6.7.8')) + ['1.2.3.4', '5.6.7.8'] + >>> vtor.check('ip_addr_list', ['a']) + Traceback (most recent call last): + VdtValueError: the value "a" is unacceptable. + """ + return [is_ip_addr(mem) for mem in is_list(value, min, max)] + + +def force_list(value, min=None, max=None): + """ + Check that a value is a list, coercing strings into + a list with one member. Useful where users forget the + trailing comma that turns a single value into a list. + + You can optionally specify the minimum and maximum number of members. + A minumum of greater than one will fail if the user only supplies a + string. + + >>> vtor.check('force_list', ()) + [] + >>> vtor.check('force_list', []) + [] + >>> vtor.check('force_list', 'hello') + ['hello'] + """ + if not isinstance(value, (list, tuple)): + value = [value] + return is_list(value, min, max) + + + +fun_dict = { + 'integer': is_integer, + 'float': is_float, + 'ip_addr': is_ip_addr, + 'string': is_string, + 'boolean': is_boolean, +} + + +def is_mixed_list(value, *args): + """ + Check that the value is a list. + Allow specifying the type of each member. + Work on lists of specific lengths. + + You specify each member as a positional argument specifying type + + Each type should be one of the following strings : + 'integer', 'float', 'ip_addr', 'string', 'boolean' + + So you can specify a list of two strings, followed by + two integers as : + + mixed_list('string', 'string', 'integer', 'integer') + + The length of the list must match the number of positional + arguments you supply. + + >>> mix_str = "mixed_list('integer', 'float', 'ip_addr', 'string', 'boolean')" + >>> check_res = vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', True)) + >>> check_res == [1, 2.0, '1.2.3.4', 'a', True] + 1 + >>> check_res = vtor.check(mix_str, ('1', '2.0', '1.2.3.4', 'a', 'True')) + >>> check_res == [1, 2.0, '1.2.3.4', 'a', True] + 1 + >>> vtor.check(mix_str, ('b', 2.0, '1.2.3.4', 'a', True)) + Traceback (most recent call last): + VdtTypeError: the value "b" is of the wrong type. + >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a')) + Traceback (most recent call last): + VdtValueTooShortError: the value "(1, 2.0, '1.2.3.4', 'a')" is too short. + >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', 1, 'b')) + Traceback (most recent call last): + VdtValueTooLongError: the value "(1, 2.0, '1.2.3.4', 'a', 1, 'b')" is too long. + >>> vtor.check(mix_str, 0) + Traceback (most recent call last): + VdtTypeError: the value "0" is of the wrong type. + + This test requires an elaborate setup, because of a change in error string + output from the interpreter between Python 2.2 and 2.3 . + + >>> res_seq = ( + ... 'passed an incorrect value "', + ... 'yoda', + ... '" for parameter "mixed_list".', + ... ) + >>> res_str = "'".join(res_seq) + >>> try: + ... vtor.check('mixed_list("yoda")', ('a')) + ... except VdtParamError, err: + ... str(err) == res_str + 1 + """ + try: + length = len(value) + except TypeError: + raise VdtTypeError(value) + if length < len(args): + raise VdtValueTooShortError(value) + elif length > len(args): + raise VdtValueTooLongError(value) + try: + return [fun_dict[arg](val) for arg, val in zip(args, value)] + except KeyError, e: + raise VdtParamError('mixed_list', e) + + +def is_option(value, *options): + """ + This check matches the value to any of a set of options. + + >>> vtor.check('option("yoda", "jedi")', 'yoda') + 'yoda' + >>> vtor.check('option("yoda", "jedi")', 'jed') + Traceback (most recent call last): + VdtValueError: the value "jed" is unacceptable. + >>> vtor.check('option("yoda", "jedi")', 0) + Traceback (most recent call last): + VdtTypeError: the value "0" is of the wrong type. + """ + if not isinstance(value, basestring): + raise VdtTypeError(value) + if not value in options: + raise VdtValueError(value) + return value + + +def _test(value, *args, **keywargs): + """ + A function that exists for test purposes. + + >>> checks = [ + ... '3, 6, min=1, max=3, test=list(a, b, c)', + ... '3', + ... '3, 6', + ... '3,', + ... 'min=1, test="a b c"', + ... 'min=5, test="a, b, c"', + ... 'min=1, max=3, test="a, b, c"', + ... 'min=-100, test=-99', + ... 'min=1, max=3', + ... '3, 6, test="36"', + ... '3, 6, test="a, b, c"', + ... '3, max=3, test=list("a", "b", "c")', + ... '''3, max=3, test=list("'a'", 'b', "x=(c)")''', + ... "test='x=fish(3)'", + ... ] + >>> v = Validator({'test': _test}) + >>> for entry in checks: + ... print v.check(('test(%s)' % entry), 3) + (3, ('3', '6'), {'test': ['a', 'b', 'c'], 'max': '3', 'min': '1'}) + (3, ('3',), {}) + (3, ('3', '6'), {}) + (3, ('3',), {}) + (3, (), {'test': 'a b c', 'min': '1'}) + (3, (), {'test': 'a, b, c', 'min': '5'}) + (3, (), {'test': 'a, b, c', 'max': '3', 'min': '1'}) + (3, (), {'test': '-99', 'min': '-100'}) + (3, (), {'max': '3', 'min': '1'}) + (3, ('3', '6'), {'test': '36'}) + (3, ('3', '6'), {'test': 'a, b, c'}) + (3, ('3',), {'test': ['a', 'b', 'c'], 'max': '3'}) + (3, ('3',), {'test': ["'a'", 'b', 'x=(c)'], 'max': '3'}) + (3, (), {'test': 'x=fish(3)'}) + + >>> v = Validator() + >>> v.check('integer(default=6)', '3') + 3 + >>> v.check('integer(default=6)', None, True) + 6 + >>> v.get_default_value('integer(default=6)') + 6 + >>> v.get_default_value('float(default=6)') + 6.0 + >>> v.get_default_value('pass(default=None)') + >>> v.get_default_value("string(default='None')") + 'None' + >>> v.get_default_value('pass') + Traceback (most recent call last): + KeyError: 'Check "pass" has no default value.' + >>> v.get_default_value('pass(default=list(1, 2, 3, 4))') + ['1', '2', '3', '4'] + + >>> v = Validator() + >>> v.check("pass(default=None)", None, True) + >>> v.check("pass(default='None')", None, True) + 'None' + >>> v.check('pass(default="None")', None, True) + 'None' + >>> v.check('pass(default=list(1, 2, 3, 4))', None, True) + ['1', '2', '3', '4'] + + Bug test for unicode arguments + >>> v = Validator() + >>> v.check(u'string(min=4)', u'test') + u'test' + + >>> v = Validator() + >>> v.get_default_value(u'string(min=4, default="1234")') + u'1234' + >>> v.check(u'string(min=4, default="1234")', u'test') + u'test' + + >>> v = Validator() + >>> default = v.get_default_value('string(default=None)') + >>> default == None + 1 + """ + return (value, args, keywargs) + + +def _test2(): + """ + >>> + >>> v = Validator() + >>> v.get_default_value('string(default="#ff00dd")') + '#ff00dd' + >>> v.get_default_value('integer(default=3) # comment') + 3 + """ + +def _test3(): + r""" + >>> vtor.check('string(default="")', '', missing=True) + '' + >>> vtor.check('string(default="\n")', '', missing=True) + '\n' + >>> print vtor.check('string(default="\n")', '', missing=True), + + >>> vtor.check('string()', '\n') + '\n' + >>> vtor.check('string(default="\n\n\n")', '', missing=True) + '\n\n\n' + >>> vtor.check('string()', 'random \n text goes here\n\n') + 'random \n text goes here\n\n' + >>> vtor.check('string(default=" \nrandom text\ngoes \n here\n\n ")', + ... '', missing=True) + ' \nrandom text\ngoes \n here\n\n ' + >>> vtor.check("string(default='\n\n\n')", '', missing=True) + '\n\n\n' + >>> vtor.check("option('\n','a','b',default='\n')", '', missing=True) + '\n' + >>> vtor.check("string_list()", ['foo', '\n', 'bar']) + ['foo', '\n', 'bar'] + >>> vtor.check("string_list(default=list('\n'))", '', missing=True) + ['\n'] + """ + + +if __name__ == '__main__': + # run the code tests in doctest format + import sys + import doctest + m = sys.modules.get('__main__') + globs = m.__dict__.copy() + globs.update({ + 'vtor': Validator(), + }) + doctest.testmod(m, globs=globs) From 98015093d853fdf40850628bba224369b470c02c Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 28 Dec 2009 22:16:47 +0000 Subject: [PATCH 214/331] Add a test to make sure that we correctly load boolean types as python bools --- terminatorlib/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 7ecf1263..e4d613da 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -35,6 +35,8 @@ Classes relating to configuration 'click' >>> config['focus'] 'click' +>>> config['fullscreen'].__class__.__name__ +'bool' >>> """ From 1e187b033367c1c9d89e4aa33c86cd97101d62c4 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 29 Dec 2009 01:37:32 +0000 Subject: [PATCH 215/331] make the configspec debug output named better --- terminatorlib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index e4d613da..0d20ee1a 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -312,7 +312,7 @@ class ConfigBase(Borg): configspec = ConfigObj(configspecdata) if DEBUG == True: - configspec.write(open('/tmp/configspec', 'w')) + configspec.write(open('/tmp/terminator_configspec_debug.txt', 'w')) return(configspec) def load(self): From 4cfc1c6fd25c3425ee5be539cc9e1b2cb755693c Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 30 Dec 2009 01:05:51 +0000 Subject: [PATCH 216/331] Improve debugging relating to URL mangler plugins --- terminatorlib/terminal.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index cc6b5925..7610c99b 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -227,7 +227,7 @@ class Terminal(gtk.VBox): match = urlplugin.match self.matches[name] = self.vte.match_add(match) dbg('Terminal::update_matches: added plugin URL handler \ -for %s' % name) +for %s (%s)' % (name, urlplugin.__class__.__name__)) except Exception as ex: err('Terminal::update_url_matches: %s' % ex) @@ -891,6 +891,8 @@ for %s' % name) if match == self.matches[urlplugin.handler_name]: newurl = urlplugin.callback(url) if newurl is not None: + dbg('Terminal::prepare_url: URL prepared by \ +%s plugin' % urlplugin.handler_name) url = newurl break; except Exception as ex: From 714425dfbecb60d51879089d619507552614126d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 30 Dec 2009 01:50:18 +0000 Subject: [PATCH 217/331] Fix apturl handler name and rename LaunchpadURLHandler to LaunchpadBugURLHandler since we are likely to grow a handler for code URLs --- terminatorlib/plugins/url_handlers.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/terminatorlib/plugins/url_handlers.py b/terminatorlib/plugins/url_handlers.py index 9cc39806..b6f6bc2e 100644 --- a/terminatorlib/plugins/url_handlers.py +++ b/terminatorlib/plugins/url_handlers.py @@ -5,7 +5,7 @@ import re import plugin # Every plugin you want Terminator to load *must* be listed in 'available' -available = ['LaunchpadURLHandler', 'APTURLHandler'] +available = ['LaunchpadBugURLHandler', 'APTURLHandler'] class URLHandler(plugin.Plugin): """Base class for URL handlers""" @@ -17,13 +17,13 @@ class URLHandler(plugin.Plugin): """Callback to transform the enclosed URL""" raise NotImplementedError -class LaunchpadURLHandler(URLHandler): - """Launchpad URL handler. If the URL looks like a Launchpad changelog +class LaunchpadBugURLHandler(URLHandler): + """Launchpad Bug URL handler. If the URL looks like a Launchpad changelog closure entry... 'LP: #12345' then it should be transformed into a - Launchpad URL""" + Launchpad Bug URL""" capabilities = ['url_handler'] - handler_name = 'launchpad' - match = '\\bLP:? #?[0-9]+\\b' + handler_name = 'launchpad_bug' + match = '\\b(lp|LP):?\s?#?[0-9]+(,\s*#?[0-9]+)*\\b' def callback(self, url): """Look for the number in the supplied string and return it as a URL""" @@ -35,7 +35,7 @@ class APTURLHandler(URLHandler): """APT URL handler. If there is a URL that looks like an apturl, handle it appropriately""" capabilities = ['url_handler'] - handler_hane = 'apturl' + handler_name = 'apturl' match = '\\bapt:.*\\b' def callback(self, url): From a13581a4c8b65647a584d15c1490b570e387ce4d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 30 Dec 2009 01:50:47 +0000 Subject: [PATCH 218/331] Handle exceptions in a way that doesn't make python 2.5 really angry --- terminatorlib/plugin.py | 2 +- terminatorlib/terminal.py | 4 ++-- terminatorlib/terminal_popup_menu.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py index c2399bec..7c3d1fdf 100755 --- a/terminatorlib/plugin.py +++ b/terminatorlib/plugin.py @@ -85,7 +85,7 @@ class PluginRegistry(borg.Borg): if item not in self.instances: func = getattr(module, item) self.instances[item] = func() - except Exception as e: + except Exception, e: err('PluginRegistry::load_plugins: Importing plugin %s \ failed: %s' % (plugin, e)) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 7610c99b..bf13cbfe 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -228,7 +228,7 @@ class Terminal(gtk.VBox): self.matches[name] = self.vte.match_add(match) dbg('Terminal::update_matches: added plugin URL handler \ for %s (%s)' % (name, urlplugin.__class__.__name__)) - except Exception as ex: + except Exception, ex: err('Terminal::update_url_matches: %s' % ex) def connect_signals(self): @@ -895,7 +895,7 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) %s plugin' % urlplugin.handler_name) url = newurl break; - except Exception as ex: + except Exception, ex: err('Terminal::prepare_url: %s' % ex) return(url) diff --git a/terminatorlib/terminal_popup_menu.py b/terminatorlib/terminal_popup_menu.py index 313f02a3..8800b6ef 100755 --- a/terminatorlib/terminal_popup_menu.py +++ b/terminatorlib/terminal_popup_menu.py @@ -166,7 +166,7 @@ class TerminalPopupMenu(object): menuplugin.callback(menuitems, menu, terminal) for menuitem in menuitems: menu.append(menuitem) - except Exception as ex: + except Exception, ex: err('TerminalPopupMenu::show: %s' % ex) menu.show_all() From 8141a3496d1e415f119ec413625afd0131fa4a1a Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 30 Dec 2009 11:26:22 +0000 Subject: [PATCH 219/331] call sys.exit() with doctest failure results --- terminatorlib/borg.py | 3 ++- terminatorlib/config.py | 2 ++ terminatorlib/plugin.py | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/terminatorlib/borg.py b/terminatorlib/borg.py index 3e4233cf..ec9d3770 100755 --- a/terminatorlib/borg.py +++ b/terminatorlib/borg.py @@ -100,7 +100,8 @@ if __name__ == '__main__': if not self.attribute: self.attribute = 1 + import sys import doctest (failed, attempted) = doctest.testmod() print "%d/%d tests failed" % (failed, attempted) - + sys.exit(failed) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 0d20ee1a..c81470f9 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -427,6 +427,8 @@ class ConfigBase(Borg): return(True) if __name__ == '__main__': + import sys import doctest (failed, attempted) = doctest.testmod() print "%d/%d tests failed" % (failed, attempted) + sys.exit(failed) diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py index 7c3d1fdf..d664f86b 100755 --- a/terminatorlib/plugin.py +++ b/terminatorlib/plugin.py @@ -106,8 +106,9 @@ for %s' % (len(self.instances), capability)) return(self.instances) if __name__ == '__main__': + import sys import doctest sys.path.insert(0, 'plugins') (failed, attempted) = doctest.testmod() print "%d/%d tests failed" % (failed, attempted) - + sys.exit(failed) From 6058727cdb417ea2bd4f51a879d23f0bfed273e4 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 2 Jan 2010 01:40:26 +0000 Subject: [PATCH 220/331] Port RainCT's LP Code URL handler to a plugin --- terminatorlib/plugins/url_handlers.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/terminatorlib/plugins/url_handlers.py b/terminatorlib/plugins/url_handlers.py index b6f6bc2e..c4f728a6 100644 --- a/terminatorlib/plugins/url_handlers.py +++ b/terminatorlib/plugins/url_handlers.py @@ -5,7 +5,7 @@ import re import plugin # Every plugin you want Terminator to load *must* be listed in 'available' -available = ['LaunchpadBugURLHandler', 'APTURLHandler'] +available = ['LaunchpadBugURLHandler', 'LaunchpadCodeURLHandler', 'APTURLHandler'] class URLHandler(plugin.Plugin): """Base class for URL handlers""" @@ -31,6 +31,25 @@ class LaunchpadBugURLHandler(URLHandler): url = 'https://bugs.launchpad.net/bugs/%s' % item return(url) +class LaunchpadCodeURLHandler(URLHandler): + """Launchpad Code URL handler. If the URL looks like a Launchpad project or + branch entry then it should be transformed into a code.launchpad.net URL""" + capabilities = ['url_handler'] + handler_name = 'launchpad_code' + lpfilters = {} + lpfilters['project'] = '[a-z0-9]{1}[a-z0-9\.\-\+]+' + lpfilters['group'] = '~%s' % lpfilters['project'] + lpfilters['series'] = lpfilters['project'] + lpfilters['branch'] = '[a-zA-Z0-9]{1}[a-zA-Z0-9_+@.-]+' + + match = '\\b((lp|LP):%(project)s(/%(series)s)?|(lp|LP):%(group)s/(%(project)s|\+junk)/%(branch)s)\\b' % lpfilters + + def callback(self, url): + """Look for the number in the supplied string and return it as a URL""" + if url.startswith('lp:'): + url = url[3:] + return('https://code.launchpad.net/+branch/%s' % url) + class APTURLHandler(URLHandler): """APT URL handler. If there is a URL that looks like an apturl, handle it appropriately""" From 26f0b2be69c697e0daf5c0904c11866b4ea89b43 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 3 Jan 2010 17:53:25 +0000 Subject: [PATCH 221/331] Rename the Profile editor to a general preferences editor and only show a choice of profiles if there's more than one --- .../{profileeditor.py => prefseditor.py} | 2 +- terminatorlib/terminal.py | 1 - terminatorlib/terminal_popup_menu.py | 33 ++++++++++--------- 3 files changed, 18 insertions(+), 18 deletions(-) rename terminatorlib/{profileeditor.py => prefseditor.py} (99%) diff --git a/terminatorlib/profileeditor.py b/terminatorlib/prefseditor.py similarity index 99% rename from terminatorlib/profileeditor.py rename to terminatorlib/prefseditor.py index 87740e88..e16e60e6 100644 --- a/terminatorlib/profileeditor.py +++ b/terminatorlib/prefseditor.py @@ -9,7 +9,7 @@ from keybindings import Keybindings from version import APP_NAME, APP_VERSION from translation import _ -class ProfileEditor: +class PrefsEditor: # lists of which settings to put in which tabs appearance = ['titlebars', 'zoomedtitlebar', 'allow_bold', 'audible_bell', 'visible_bell', 'urgent_bell', 'force_no_bell', 'background_darkness', 'background_type', 'background_image', 'cursor_blink', 'cursor_shape', 'font', 'scrollbar_position', 'scroll_background', 'use_system_font', 'use_theme_colors', 'enable_real_transparency'] colours = ['foreground_color','background_color', 'cursor_color', 'palette', 'title_tx_txt_color', 'title_tx_bg_color', 'title_rx_txt_color', 'title_rx_bg_color', 'title_ia_txt_color', 'title_ia_bg_color'] diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index bf13cbfe..45c20371 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -21,7 +21,6 @@ from newterminator import Terminator from titlebar import Titlebar from terminal_popup_menu import TerminalPopupMenu from searchbar import Searchbar -from profileeditor import ProfileEditor from translation import _ import plugin diff --git a/terminatorlib/terminal_popup_menu.py b/terminatorlib/terminal_popup_menu.py index 8800b6ef..1a3f2c32 100755 --- a/terminatorlib/terminal_popup_menu.py +++ b/terminatorlib/terminal_popup_menu.py @@ -11,7 +11,7 @@ from translation import _ from encoding import TerminatorEncoding from util import err from config import Config -from profileeditor import ProfileEditor +from prefseditor import PrefsEditor import plugin class TerminalPopupMenu(object): @@ -132,28 +132,29 @@ class TerminalPopupMenu(object): item.set_sensitive(False) menu.append(item) - item = gtk.MenuItem(_('Profiles')) - submenu = gtk.Menu() - item.set_submenu(submenu) + item = gtk.MenuItem(_('_Preferences')) + item.connect('activate', lambda x: PrefsEditor(self.terminal)) menu.append(item) config = Config() - current = terminal.get_profile() + profilelist = config.list_profiles() - group = None + if len(profilelist) > 1: + item = gtk.MenuItem(_('Profiles')) + submenu = gtk.Menu() + item.set_submenu(submenu) + menu.append(item) - for profile in config.list_profiles(): - item = gtk.RadioMenuItem(group, profile.capitalize()) - item.connect('activate', terminal.set_profile, profile) - if profile == current: - item.set_active(True) - submenu.append(item) + current = terminal.get_profile() - submenu.append(gtk.MenuItem()) + group = None - item = gtk.MenuItem(_('Ed_it profiles')) - item.connect('activate', lambda x: ProfileEditor(self.terminal)) - submenu.append(item) + for profile in profilelist(): + item = gtk.RadioMenuItem(group, profile.capitalize()) + item.connect('activate', terminal.set_profile, profile) + if profile == current: + item.set_active(True) + submenu.append(item) self.add_encoding_items(menu) From 137dfe7ef47ce967079fca2dd5a1422563d4fd78 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 4 Jan 2010 12:57:14 +0000 Subject: [PATCH 222/331] prepare for this main terminator script to work with epicrefactor --- terminator | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/terminator b/terminator index 2af3cedb..245e59cf 100755 --- a/terminator +++ b/terminator @@ -19,14 +19,16 @@ """Terminator by Chris Jones """ # import standard python libs -import os, sys +import os +import sys origcwd = os.getcwd() from optparse import OptionParser, SUPPRESS_HELP +from terminatorlib.factory import Factory import terminatorlib.translation from terminatorlib.version import APP_NAME, APP_VERSION -from terminatorlib.config import dbg, err, debug +from terminatorlib.util import dbg, err import terminatorlib.config try: @@ -39,9 +41,13 @@ except ImportError: "gobject, gtk and pango to run Terminator.")) sys.exit(1) -from terminatorlib.terminator import Terminator +from terminatorlib.newterminator import Terminator if __name__ == '__main__': + def on_window_destroyed(window): + """Window has been closed""" + gtk.main_quit() + def execute_cb (option, opt, value, lparser): """Callback for use in parsing Terminator command line options""" assert value is None @@ -160,12 +166,23 @@ See the following bug report for more details: pass dbg ('profile_cb: settled on profile: "%s"' % options.profile) - term = Terminator (options.profile, command, options.fullscreen, - options.maximise, options.borderless, options.no_gconf, - options.geometry, options.hidden, options.forcedtitle, options.role) + term = Terminator() +# term = Terminator (options.profile, command, options.fullscreen, +# options.maximise, options.borderless, options.no_gconf, +# options.geometry, options.hidden, options.forcedtitle, options.role) term.origcwd = origcwd - + + maker = Factory() + window = maker.make('Window') + terminal = maker.make('Terminal') + + window.add(terminal) + window.show() + terminal.spawn_child() + + window.connect('destroy', on_window_destroyed) + if options.debug > 1: import terminatorlib.debugserver as debugserver import threading @@ -174,5 +191,8 @@ See the following bug report for more details: (debugthread, debugsvr) = debugserver.spawn(locals()) term.debugaddress = debugsvr.server_address - gtk.main() + try: + gtk.main() + except KeyboardInterrupt: + pass From 995aa99b2e8bcc8d4a56e67d6f5d3610c80d6755 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 4 Jan 2010 13:04:46 +0000 Subject: [PATCH 223/331] Teach Factory how to make a Window, and have it register terminals windows automagically --- terminatorlib/factory.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/terminatorlib/factory.py b/terminatorlib/factory.py index 54b3a427..19390713 100755 --- a/terminatorlib/factory.py +++ b/terminatorlib/factory.py @@ -46,10 +46,23 @@ class Factory(Borg): dbg('Factory::make: created a %s' % product) return(func(args)) + def make_window(self, *args): + """Make a Window""" + import window + from newterminator import Terminator + terminator = Terminator() + product = window.Window() + terminator.register_window(product) + return(product) + def make_terminal(self, *args): """Make a Terminal""" import terminal - return(terminal.Terminal()) + from newterminator import Terminator + terminator = Terminator() + product = terminal.Terminal() + terminator.register_terminal(product) + return(product) def make_hpaned(self, *args): """Make an HPaned""" From 6d1831824aa4ea71859f04e043fabf09830b8ca3 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 4 Jan 2010 13:05:28 +0000 Subject: [PATCH 224/331] Add a policy that if we have no open windows we should quit --- terminatorlib/newterminator.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index 049a7e7c..bea1fab0 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -3,6 +3,8 @@ # GPL v2 only """terminator.py - class for the master Terminator singleton""" +import gtk + from borg import Borg from config import Config from keybindings import Keybindings @@ -56,6 +58,9 @@ class Terminator(Borg): dbg('Terminator::deregister_window: de-registering %s:%s' % (id(window), type(window))) self.windows.remove(window) + if len(self.windows) == 0: + # We have no windows left, we should exit + gtk.main_quit() def register_terminal(self, terminal): """Register a new terminal widget""" From 293beb233166cb29b1e1a16164739c9350fc768b Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 4 Jan 2010 13:05:58 +0000 Subject: [PATCH 225/331] our test starter can get away with only depending on Factory now --- terminatorlib/test.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/terminatorlib/test.py b/terminatorlib/test.py index 56f058e2..f20b702a 100755 --- a/terminatorlib/test.py +++ b/terminatorlib/test.py @@ -1,27 +1,16 @@ #!/usr/bin/python import gtk - -from newterminator import Terminator -from window import Window from factory import Factory -def on_window_destroyed(widget): - """Window destroyed, so exit""" - gtk.main_quit() - maker = Factory() -window = Window() -foo = Terminator() +window = maker.make('Window') term = maker.make('Terminal') -foo.register_terminal(term) window.add(term) window.show() term.spawn_child() -window.connect("destroy", on_window_destroyed) - try: gtk.main() except KeyboardInterrupt: From be224f301667376bd93d7490fd3e1ee1ecb2104a Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 4 Jan 2010 13:11:16 +0000 Subject: [PATCH 226/331] Undo previous change that had Factory registering windows and terminals. Instead make them self-register since they already have a Terminator reference anyway. Remove calls to register_terminla from elsewhere. --- terminatorlib/factory.py | 12 ++---------- terminatorlib/notebook.py | 4 +--- terminatorlib/paned.py | 1 - terminatorlib/terminal.py | 2 ++ terminatorlib/window.py | 1 - 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/terminatorlib/factory.py b/terminatorlib/factory.py index 19390713..b75832a9 100755 --- a/terminatorlib/factory.py +++ b/terminatorlib/factory.py @@ -49,20 +49,12 @@ class Factory(Borg): def make_window(self, *args): """Make a Window""" import window - from newterminator import Terminator - terminator = Terminator() - product = window.Window() - terminator.register_window(product) - return(product) + return(window.Window()) def make_terminal(self, *args): """Make a Terminal""" import terminal - from newterminator import Terminator - terminator = Terminator() - product = terminal.Terminal() - terminator.register_terminal(product) - return(product) + return(terminal.Terminal()) def make_hpaned(self, *args): """Make an HPaned""" diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index 4eb69e10..85fd0d14 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -68,7 +68,6 @@ class Notebook(Container, gtk.Notebook): if not sibling: sibling = maker.make('terminal') - self.terminator.register_terminal(sibling) sibling.spawn_child() self.insert_page(container, None, page_num) @@ -100,8 +99,7 @@ class Notebook(Container, gtk.Notebook): """Add a new tab, optionally supplying a child widget""" if not widget: maker = Factory() - widget = maker.make('terminal') - self.terminator.register_terminal(widget) + widget = maker.make('Terminal') widget.spawn_child() signals = {'close-term': self.wrapcloseterm, diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index 8a6c9588..47200b7a 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -53,7 +53,6 @@ class Paned(Container): if not sibling: sibling = maker.make('terminal') - self.terminator.register_terminal(sibling) sibling.spawn_child() self.add(container) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 45c20371..5c732567 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -87,6 +87,8 @@ class Terminal(gtk.VBox): self.__gobject_init__() self.terminator = Terminator() + self.terminator.register_terminal(self) + self.connect('enumerate', self.terminator.do_enumerate) self.connect('group-tab', self.terminator.group_tab) self.connect('ungroup-tab', self.terminator.ungroup_tab) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 9a536b06..7e54f0bf 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -237,7 +237,6 @@ class Window(Container, gtk.Window): if not sibling: sibling = maker.make('Terminal') - self.terminator.register_terminal(sibling) self.add(container) container.show_all() From df9abd4523beeb18d3df89c15cd8bcdadb54175c Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 4 Jan 2010 13:24:45 +0000 Subject: [PATCH 227/331] Move the test.py launcher to the top level to start shaking out namespace bugs --- terminatorlib/test.py => test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename terminatorlib/test.py => test.py (83%) diff --git a/terminatorlib/test.py b/test.py similarity index 83% rename from terminatorlib/test.py rename to test.py index f20b702a..4bde29be 100755 --- a/terminatorlib/test.py +++ b/test.py @@ -1,7 +1,7 @@ #!/usr/bin/python import gtk -from factory import Factory +from terminatorlib.factory import Factory maker = Factory() window = maker.make('Window') From 42e022a938dd5c23c01328fd12f56114e51ee156 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 4 Jan 2010 13:46:55 +0000 Subject: [PATCH 228/331] Make the factory uglier, but better able to handle not running from inside the library, and make the plugins import from the library. These changes make it possible to run epicrefactor from outside itself, which is necessary to make the top level terminator script work with it --- terminatorlib/factory.py | 10 +++++++++- terminatorlib/plugins/terminal_menu.py | 2 +- terminatorlib/plugins/testplugin.py | 2 +- terminatorlib/plugins/url_handlers.py | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/terminatorlib/factory.py b/terminatorlib/factory.py index b75832a9..03002ab2 100755 --- a/terminatorlib/factory.py +++ b/terminatorlib/factory.py @@ -29,7 +29,15 @@ class Factory(Borg): 'Container': 'container', 'Window': 'window'} if classtype in types.keys(): - module = __import__(types[classtype], None, None, ['']) + # This is quite ugly, but we're importing from the current + # directory if that makes sense, otherwise falling back to + # terminatorlib. Someone with real Python skills should fix + # this to be less insane. + try: + module = __import__(types[classtype], None, None, ['']) + except ImportError, ex: + module = __import__('terminatorlib.%s' % types[classtype], + None, None, ['']) return(isinstance(product, getattr(module, classtype))) else: err('Factory::isinstance: unknown class type: %s' % classtype) diff --git a/terminatorlib/plugins/terminal_menu.py b/terminatorlib/plugins/terminal_menu.py index e2fcc1a6..575a5c74 100644 --- a/terminatorlib/plugins/terminal_menu.py +++ b/terminatorlib/plugins/terminal_menu.py @@ -2,7 +2,7 @@ # GPL v2 only """terminal_menu.py - Default plugins for the terminal menu""" import gtk -import plugin +import terminatorlib.plugin as plugin # Every plugin you want Terminator to load *must* be listed in 'available' diff --git a/terminatorlib/plugins/testplugin.py b/terminatorlib/plugins/testplugin.py index dda17ce2..a6bd84c3 100644 --- a/terminatorlib/plugins/testplugin.py +++ b/terminatorlib/plugins/testplugin.py @@ -1,4 +1,4 @@ -import plugin +import terminatorlib.plugin as plugin # available must contain a list of all the classes that you want exposed available = ['TestPlugin'] diff --git a/terminatorlib/plugins/url_handlers.py b/terminatorlib/plugins/url_handlers.py index c4f728a6..a7da4393 100644 --- a/terminatorlib/plugins/url_handlers.py +++ b/terminatorlib/plugins/url_handlers.py @@ -2,7 +2,7 @@ # GPL v2 only """url_handlers.py - Default plugins for URL handling""" import re -import plugin +import terminatorlib.plugin as plugin # Every plugin you want Terminator to load *must* be listed in 'available' available = ['LaunchpadBugURLHandler', 'LaunchpadCodeURLHandler', 'APTURLHandler'] From 723dfef697ad31862a3dbb949953af60acb5e057 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 4 Jan 2010 23:51:55 +0000 Subject: [PATCH 229/331] Bump the version number to signify the progress of this branch --- terminatorlib/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/version.py b/terminatorlib/version.py index 5d62880f..f3f3f33a 100644 --- a/terminatorlib/version.py +++ b/terminatorlib/version.py @@ -21,4 +21,4 @@ TerminatorVersion supplies our version number. """ APP_NAME = 'terminator' -APP_VERSION = '0.14' +APP_VERSION = '0.90' From ba63f8fc2b1c7e582cc40e3c4245d64e9ff0b209 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 4 Jan 2010 23:52:39 +0000 Subject: [PATCH 230/331] Merge old terminator option parsing into a new file that parses command line arguments and includes them in the loaded config --- terminatorlib/optionparse.py | 101 +++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100755 terminatorlib/optionparse.py diff --git a/terminatorlib/optionparse.py b/terminatorlib/optionparse.py new file mode 100755 index 00000000..f3981d2f --- /dev/null +++ b/terminatorlib/optionparse.py @@ -0,0 +1,101 @@ +#!/usr/bin/python +# Terminator.optionparse - Parse commandline options +# Copyright (C) 2006-2008 cmsj@tenshu.net +# +# 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, version 2 only. +# +# 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 +"""Terminator.optionparse - Parse commandline options""" + +import sys +import os + +from optparse import OptionParser, SUPPRESS_HELP +from util import dbg, err +import util +import config +import version + +def execute_cb(option, opt, value, lparser): + """Callback for use in parsing execute options""" + assert value is None + value = [] + while lparser.rargs: + arg = lparser.rargs[0] + value.append(arg) + del(lparser.rargs[0]) + setattr(lparser.values, option.dest, value) + +def parse_options(): + """Parse the command line options""" + usage = "usage: %prog [options]" + + configobj = config.Config() + parser = OptionParser(usage) + + parser.add_option('-v', '--version', action='store_true', dest='version', + help='Display program version') + parser.add_option('-d', '--debug', action='count', dest='debug', + help='Enable debugging information (twice for debug server)') + parser.add_option('-m', '--maximise', action='store_true', dest='maximise', + help='Maximise the window') + parser.add_option('-f', '--fullscreen', action='store_true', + dest='fullscreen', help='Make the window fill the screen') + parser.add_option('-b', '--borderless', action='store_true', + dest='borderless', help='Disable window borders') + parser.add_option('-H', '--hidden', action='store_true', dest='hidden', + help='Hide the window at startup') + parser.add_option('-T', '--title', dest='forcedtitle', help='Specify a \ +title for the window') + parser.add_option('--geometry', dest='geometry', type='string', help='Set \ +the preferred size and position of the window (see X man page)') + parser.add_option('-e', '--command', dest='command', help='Specify a \ +command to execute inside the terminal') + parser.add_option('-x', '--execute', dest='execute', action='callback', + callback=execute_cb, help='Use the rest of the command line as a \ +command to execute inside the terminal, and its arguments') + parser.add_option('--working-directory', metavar='DIR', + dest='working_directory', help='Set the working directory') + parser.add_option('-r', '--role', dest='role', help='Set a custom \ +WM_WINDOW_ROLE property on the window') + for item in ['--sm-client-id', '--sm-config-prefix', '--screen']: + parser.add_option(item, dest='dummy', action='store', + help=SUPPRESS_HELP) + + (options, args) = parser.parse_args() + if len(args) != 0: + parser.error('Additional unexpected arguments found: %s' % args) + + if options.version: + print '%s %s' % (version.APP_NAME, version.APP_VERSION) + sys.exit(0) + + if options.debug: + util.DEBUG = True + + if options.working_directory: + if os.path.exists(os.path.expanduser(options.working_directory)): + os.chdir(os.path.expanduser(options.working_directory)) + else: + err('OptionParse::parse_options: %s does not exist' % + options.working_directory) + sys.exit(1) + + if options.maximise: + configobj['maximise'] = True + + # FIXME: Map all the other bits of options to configobj + + if util.DEBUG == True: + dbg('OptionParse::parse_options: options dump: %s' % options) + + return(options) From f4a893a5948d70b243c0450bb671138b5fed639b Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 4 Jan 2010 23:52:57 +0000 Subject: [PATCH 231/331] Remove option parsing code now that is in its own module, and generally refactor/reformat --- terminator | 211 +++++++++++------------------------------------------ 1 file changed, 43 insertions(+), 168 deletions(-) diff --git a/terminator b/terminator index 245e59cf..e39464c1 100755 --- a/terminator +++ b/terminator @@ -18,181 +18,56 @@ """Terminator by Chris Jones """ -# import standard python libs -import os import sys -origcwd = os.getcwd() -from optparse import OptionParser, SUPPRESS_HELP - -from terminatorlib.factory import Factory -import terminatorlib.translation -from terminatorlib.version import APP_NAME, APP_VERSION - -from terminatorlib.util import dbg, err -import terminatorlib.config +# Check we have simple basics like Gtk+ and a valid $DISPLAY try: - import pygtk - pygtk.require ("2.0") + import pygtk + pygtk.require ("2.0") + import gtk + + if gtk.gdk.display_get_default() == None: + print('You need to run terminator in an X environment. ' \ + 'Make sure $DISPLAY is properly set') + sys.exit(1) - import gobject, gtk, pango except ImportError: - err (_("You need to install the python bindings for " \ - "gobject, gtk and pango to run Terminator.")) - sys.exit(1) - -from terminatorlib.newterminator import Terminator - -if __name__ == '__main__': - def on_window_destroyed(window): - """Window has been closed""" - gtk.main_quit() - - def execute_cb (option, opt, value, lparser): - """Callback for use in parsing Terminator command line options""" - assert value is None - value = [] - while lparser.rargs: - arg = lparser.rargs[0] - value.append (arg) - del (lparser.rargs[0]) - setattr(lparser.values, option.dest, value) - - def profile_cb (option, opt, value, lparser): - """Callback for handling the profile name""" - assert value is None - value = '' - while lparser.rargs: - arg = lparser.rargs[0] - if arg[0] != '-': - if len (value) > 0: - value = '%s %s' % (value, arg) - else: - value = '%s' % arg - del (lparser.rargs[0]) - else: - break - setattr (lparser.values, option.dest, value) - - usage = "usage: %prog [options]" - parser = OptionParser (usage) - parser.add_option ("-v", "--version", action="store_true", dest="version", - help="Display program version") - parser.add_option ("-d", "--debug", action="count", dest="debug", - help="Enable debugging information (twice for debug server)") - parser.add_option ("-m", "--maximise", action="store_true", dest="maximise", - help="Open the %s window maximised" % APP_NAME.capitalize()) - parser.add_option ("-f", "--fullscreen", action="store_true", - dest="fullscreen", help="Set the window into fullscreen mode") - parser.add_option ("-b", "--borderless", action="store_true", - dest="borderless", help="Turn off the window's borders") - parser.add_option("-H", "--hidden", action="store_true", dest="hidden", - help="Open the %s window hidden"%APP_NAME.capitalize()) - parser.add_option("-T", "--title", dest="forcedtitle", - help="Specify a title to use for the window") - parser.add_option ("-n", "--no-gconf", dest="no_gconf", action="store_true", - help="ignore gnome-terminal gconf settings") - parser.add_option ("-p", "--profile", dest="profile", action="callback", - callback=profile_cb, help="Specify a GNOME Terminal profile to emulate") - parser.add_option ("--geometry", dest="geometry", type="string", - help="Set the preferred size and position of the window (see X man page)") - parser.add_option ("-e", "--command", dest="command", - help="Execute the argument to this option inside the terminal") - parser.add_option ("-x", "--execute", dest="execute", action="callback", - callback=execute_cb, help="Execute the remainder of the command line \ -inside the terminal") - parser.add_option ("--working-directory", metavar="DIR", - dest="working_directory", help="Set the terminal's working directory") - parser.add_option ("-r", "--role", dest="role", - help="Set custom WM_WINDOW_ROLE property") - for item in ['--sm-client-id', '--sm-config-prefix', '--screen']: - parser.add_option (item, dest="dummy", action="store", help=SUPPRESS_HELP) - - (options, args) = parser.parse_args () - if len (args) != 0: - parser.error("Expecting zero additional arguments, found: %d: %s" % (len (args), args)) - - if options.no_gconf and options.profile: - parser.error("using --no-gconf and defining a profile at the same time \ -does not make sense") - - if options.version: - print "%s %s" % (APP_NAME, APP_VERSION) - sys.exit (0) - - if options.debug: - terminatorlib.config.debug = True - - dbg ("%s starting up, version %s" % (APP_NAME, APP_VERSION)) - - command = None - if (options.command): - command = options.command - if (options.execute): - command = options.execute - - if gtk.gdk.display_get_default() == None: - err (_("You need to run terminator in an X environment. " \ - "Make sure DISPLAY is properly set")) + print('You need to install the python bindings for ' \ + 'gobject, gtk and pango to run Terminator.') sys.exit(1) - if options.working_directory: - if os.path.exists (os.path.expanduser (options.working_directory)): - os.chdir (os.path.expanduser (options.working_directory)) - else: - err (_("The working directory you specified does not exist.")) - sys.exit (1) +import terminatorlib.config +import terminatorlib.optionparse +from terminatorlib.newterminator import Terminator +from terminatorlib.factory import Factory +from terminatorlib.version import APP_NAME, APP_VERSION +from terminatorlib.util import dbg + +if __name__ == '__main__': + dbg ("%s starting up, version %s" % (APP_NAME, APP_VERSION)) + + OPTIONS = terminatorlib.optionparse.parse_options() + + MAKER = Factory() + TERMINATOR = Terminator() + WINDOW = MAKER.make('Window') + TERMINAL = MAKER.make('Terminal') + + WINDOW.add(TERMINAL) + WINDOW.show() + TERMINAL.spawn_child() + + if OPTIONS.debug > 1: + import terminatorlib.debugserver as debugserver + # pylint: disable-msg=W0611 + import threading + + gtk.gdk.threads_init() + (DEBUGTHREAD, DEBUGSVR) = debugserver.spawn(locals()) + TERMINATOR.debugaddress = DEBUGSVR.server_address - try: - open (os.path.expanduser ('~/.config/terminator/config')) - except IOError: try: - open (os.path.expanduser ('~/.terminatorrc')) - error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, - gtk.BUTTONS_OK, ('''You have a configuration file: - - ~/.terminatorrc. - -Please be aware that this file needs to be moved to: - - ~/.config/terminator/config. - -See the following bug report for more details: - - https://bugs.launchpad.net/bugs/238070''')) - error.run () - error.destroy () - except IOError: - pass - - dbg ('profile_cb: settled on profile: "%s"' % options.profile) - term = Terminator() -# term = Terminator (options.profile, command, options.fullscreen, -# options.maximise, options.borderless, options.no_gconf, -# options.geometry, options.hidden, options.forcedtitle, options.role) - - term.origcwd = origcwd - - maker = Factory() - window = maker.make('Window') - terminal = maker.make('Terminal') - - window.add(terminal) - window.show() - terminal.spawn_child() - - window.connect('destroy', on_window_destroyed) - - if options.debug > 1: - import terminatorlib.debugserver as debugserver - import threading - - gtk.gdk.threads_init() - (debugthread, debugsvr) = debugserver.spawn(locals()) - term.debugaddress = debugsvr.server_address - - try: - gtk.main() - except KeyboardInterrupt: - pass + gtk.main() + except KeyboardInterrupt: + pass From dbe683e259551aaca75347eab0550553a240fc8f Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 4 Jan 2010 23:56:28 +0000 Subject: [PATCH 232/331] hey look it's 2010 already --- terminator | 2 +- terminatorlib/__init__.py | 2 +- terminatorlib/config.py | 2 +- terminatorlib/encoding.py | 2 +- terminatorlib/keybindings.py | 2 +- terminatorlib/optionparse.py | 2 +- terminatorlib/translation.py | 2 +- terminatorlib/util.py | 2 +- terminatorlib/version.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/terminator b/terminator index e39464c1..a52f56fc 100755 --- a/terminator +++ b/terminator @@ -1,6 +1,6 @@ #!/usr/bin/env python # Terminator - multiple gnome terminals in one window -# Copyright (C) 2006-2008 cmsj@tenshu.net +# Copyright (C) 2006-2010 cmsj@tenshu.net # # 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 diff --git a/terminatorlib/__init__.py b/terminatorlib/__init__.py index cdcfd509..bb2dee20 100644 --- a/terminatorlib/__init__.py +++ b/terminatorlib/__init__.py @@ -1,6 +1,6 @@ #!/usr/bin/python # Terminator - multiple gnome terminals in one window -# Copyright (C) 2006-2008 cmsj@tenshu.net +# Copyright (C) 2006-2010 cmsj@tenshu.net # # 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 diff --git a/terminatorlib/config.py b/terminatorlib/config.py index c81470f9..75d514ce 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -1,6 +1,6 @@ #!/usr/bin/python # TerminatorConfig - layered config classes -# Copyright (C) 2006-2008 cmsj@tenshu.net +# Copyright (C) 2006-2010 cmsj@tenshu.net # # 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 diff --git a/terminatorlib/encoding.py b/terminatorlib/encoding.py index f2f7920e..b0a5ec20 100644 --- a/terminatorlib/encoding.py +++ b/terminatorlib/encoding.py @@ -1,6 +1,6 @@ #!/usr/bin/python # TerminatorEncoding - charset encoding classes -# Copyright (C) 2006-2008 chantra@debuntu.org +# Copyright (C) 2006-2010 chantra@debuntu.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 diff --git a/terminatorlib/keybindings.py b/terminatorlib/keybindings.py index 019e7f11..4cc1b870 100644 --- a/terminatorlib/keybindings.py +++ b/terminatorlib/keybindings.py @@ -1,6 +1,6 @@ #!/usr/bin/python # Terminator - multiple gnome terminals in one window -# Copyright (C) 2006-2008 cmsj@tenshu.net +# Copyright (C) 2006-2010 cmsj@tenshu.net # # 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 diff --git a/terminatorlib/optionparse.py b/terminatorlib/optionparse.py index f3981d2f..3f213545 100755 --- a/terminatorlib/optionparse.py +++ b/terminatorlib/optionparse.py @@ -1,6 +1,6 @@ #!/usr/bin/python # Terminator.optionparse - Parse commandline options -# Copyright (C) 2006-2008 cmsj@tenshu.net +# Copyright (C) 2006-2010 cmsj@tenshu.net # # 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 diff --git a/terminatorlib/translation.py b/terminatorlib/translation.py index 6262d0b4..ff8bafef 100644 --- a/terminatorlib/translation.py +++ b/terminatorlib/translation.py @@ -1,6 +1,6 @@ #!/usr/bin/python # Terminator - multiple gnome terminals in one window -# Copyright (C) 2006-2008 cmsj@tenshu.net +# Copyright (C) 2006-2010 cmsj@tenshu.net # # 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 diff --git a/terminatorlib/util.py b/terminatorlib/util.py index 0baaa52b..920bd997 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -1,6 +1,6 @@ #!/usr/bin/python # Terminator.util - misc utility functions -# Copyright (C) 2006-2008 cmsj@tenshu.net +# Copyright (C) 2006-2010 cmsj@tenshu.net # # 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 diff --git a/terminatorlib/version.py b/terminatorlib/version.py index f3f3f33a..a0e9e3a6 100644 --- a/terminatorlib/version.py +++ b/terminatorlib/version.py @@ -1,6 +1,6 @@ #!/usr/bin/python # TerminatorVersion - version number -# Copyright (C) 2008 cmsj@tenshu.net +# Copyright (C) 2010 cmsj@tenshu.net # # 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 From b4370235b5ae146fe0a7efe06e85e879d8b3b0bb Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 5 Jan 2010 00:00:47 +0000 Subject: [PATCH 233/331] We don't use this anymore --- terminatorlib/configfile.py | 233 ------------------------------------ 1 file changed, 233 deletions(-) delete mode 100644 terminatorlib/configfile.py diff --git a/terminatorlib/configfile.py b/terminatorlib/configfile.py deleted file mode 100644 index f40b9587..00000000 --- a/terminatorlib/configfile.py +++ /dev/null @@ -1,233 +0,0 @@ -#!/usr/bin/python - -import re -from terminatorlib.util import dbg -from terminatorlib import translation - -def group(*choices): return '(' + '|'.join(choices) + ')' -def any(*choices): return group(*choices) + '*' -def maybe(*choices): return group(*choices) + '?' - -Newline = re.compile(r'[\r\n]+') -Whitespace = r'[ \f\t]*' -Comment = r'#[^\r\n]*' -Ignore = re.compile(Whitespace + maybe(Comment) + maybe(r'[\r\n]+') + '$') - -WhitespaceRE = re.compile(Whitespace) -CommentRE = re.compile(Comment) - -QuotedStrings = {"'": re.compile(r"'([^'\r\n]*)'"), '"': re.compile(r'"([^"\r\n]*)"')} - -Section = re.compile(r"\[([^\r\n\]]+)\][ \f\t]*") -Setting = re.compile(r"(\w+)\s*=\s*") - -PaletteColours = '(?:#[0-9a-fA-F]{12}:){15}#[0-9a-fA-F]{12}' -SingleColour = '#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}' - -Colourvalue = re.compile(group(PaletteColours, SingleColour)) -Barevalue = re.compile(r'((?:[^\r\n# \f\t]+|[^\r\n#]+(?!' + Ignore.pattern +'))+)') - -Tabsize = 8 -HandleIndents = False - -class ConfigSyntaxError(Exception): - def __init__(self, message, cf): - self.single_error = cf.errors_are_fatal - self.message = message - self.file = cf.filename - self.lnum = cf._lnum - self.pos = cf._pos - self.line = cf._line - - def __str__(self): - if self.single_error: - fmt = "File %(file)s line %(lnum)d:\n %(line)s\n %(pad)s^\n%(message)s" - else: - fmt = " * %(message)s, line %(lnum)d:\n %(line)s\n %(pad)s^\n" - return fmt % {'message': self.message, 'file': self.file, 'lnum': self.lnum, - 'line': self.line.rstrip(), 'pad': '-' * self.pos} - -class ConfigIndentError(ConfigSyntaxError): - pass - -class ParsedWithErrors(Exception): - def __init__(self, filename, errors): - self.file = filename - self.errors = errors - - def __str__(self): - return """Errors were encountered while parsing configuration file: - - %r - -Some lines have been ignored. - -%s -""" % (self.file, "\n".join(map(lambda error: str(error), self.errors))) - - -class ConfigFile: - def __init__(self, filename = None, callback = None, errors_are_fatal = False): - self.callback = callback - self.errors_are_fatal = errors_are_fatal - self.filename = filename - self.errors = [] - - def _call_if_match(self, re, callable, group = 0): - if self._pos == self._max: - return False - mo = re.match(self._line, self._pos) - if mo: - if callable: - callable(mo.group(group)) - self._pos = mo.end() - return True - else: - return False - - def _call_if_quoted_string(self, callable): - if self._pos == self._max: - return False - chr = self._line[self._pos] - if chr in '"\'': - string = '' - while True: - mo = QuotedStrings[chr].match(self._line, self._pos) - if mo is None: - raise ConfigSyntaxError(_("Unterminated quoted string"), self) - self._pos = mo.end() - if self._line[self._pos - 2] == '\\': - string += mo.group(1)[0:-1] + chr - self._pos -= 1 - else: - string += mo.group(1) - break - callable(string) - return True - else: - return False - - def parse(self): - file = open(self.filename) - rc = file.readlines() - file.close() - - self._indents = [0] - self._pos = 0 - self._max = 0 - self._lnum = 0 - self._line = '' - - self._sections = {} - self._currsetting = None - self._currvalue = None - self.errors = [] - - for self._line in rc: - try: - self._lnum += 1 - self._pos = 0 - self._max = len(self._line) - dbg("Line %d: %r" % (self._lnum, self._line)) - - if HandleIndents: - self._find_indent() - else: - self._call_if_match(WhitespaceRE, None) - - # [Section] - self._call_if_match(Section, self._section, 1) - # setting = - if self._call_if_match(Setting, self._setting, 1): - # "quoted value" - if not self._call_if_quoted_string(self._value): - # #000000 # colour that would otherwise be a comment - if not self._call_if_match(Colourvalue, self._value, 1): - # bare value - if not self._call_if_match(Barevalue, self._value, 1): - raise ConfigSyntaxError(_("Setting without a value"), self) - - self._call_if_match(Ignore, lambda junk: dbg("Skipping: %r" % junk)) - - if self._line[self._pos:] != '': - raise ConfigSyntaxError(_("Unexpected token"), self) - self._line_ok() - except ConfigSyntaxError, e: - self._line_error(e) - except ConfigIndentError, e: - self.errors.append(e) - break - - if self.errors: - raise ParsedWithErrors(self.filename, self.errors) - - def _find_indent(self): - # Based on tokenizer.py in the base Python standard library - column = 0 - while self._pos < self._max: - chr = self._line[self._pos] - if chr == ' ': column += 1 - elif chr == '\t': column = (column / Tabsize + 1) * Tabsize - elif chr == '\f': column = 0 - else: break - self._pos += 1 - if self._pos == self._max: return - - if column > self._indents[-1]: - self._indents.append(column) - self._indent() # self._line[:self._pos]) - - while column < self._indents[-1]: - if column not in self._indents: - raise ConfigSyntaxError("Unindent does not match a previous indent, config parsing aborted", self) - self._indents.pop() - self._deindent() - - def _indent(self): - dbg(" -> Indent %d" % len(self._indents)) - - def _deindent(self): - dbg(" -> Deindent %d" % len(self._indents)) - - def _get_section(self): - i = 1 - sections = [] - while i <= len(self._indents): - sname = self._sections.get(i, None) - if not sname: - break - sections.append(str(sname)) - i += 1 - return tuple(sections) - - def _section(self, section): - dbg("Section %r" % section) - self._sections[len(self._indents)] = section.lower() - - def _setting(self, setting): - dbg("Setting %r" % setting) - self._currsetting = setting.lower() - - def _value(self, value): - dbg("Value %r" % value) - self._currvalue = value - - def _line_ok(self): - if self._currvalue is None: return - else: - try: # *glares at 2.4 users* - try: - self.callback(self._get_section(), self._currsetting, self._currvalue) - except ValueError, e: - raise ConfigSyntaxError(str(e), self) - finally: - self._currvalue = None - - def _line_error(self, e): - self._currvalue = None - if self.errors_are_fatal: - raise e - else: - self.errors.append(e) - - From 91830e22df7432680dae90c695f8a9a54e8421a4 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 5 Jan 2010 00:03:16 +0000 Subject: [PATCH 234/331] Move configobj into its own subdirectory, since its a separate upstream product --- terminatorlib/config.py | 4 ++-- terminatorlib/configobj/__init__.py | 0 terminatorlib/{ => configobj}/configobj.py | 0 terminatorlib/{ => configobj}/validate.py | 0 4 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 terminatorlib/configobj/__init__.py rename terminatorlib/{ => configobj}/configobj.py (100%) rename terminatorlib/{ => configobj}/validate.py (100%) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 75d514ce..731dd345 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -45,8 +45,8 @@ import platform import os import sys from copy import copy -from configobj import ConfigObj -from validate import Validator +from configobj.configobj import ConfigObj +from configobj.validate import Validator from borg import Borg from factory import Factory from util import dbg, err, DEBUG, get_config_dir, dict_diff diff --git a/terminatorlib/configobj/__init__.py b/terminatorlib/configobj/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/terminatorlib/configobj.py b/terminatorlib/configobj/configobj.py similarity index 100% rename from terminatorlib/configobj.py rename to terminatorlib/configobj/configobj.py diff --git a/terminatorlib/validate.py b/terminatorlib/configobj/validate.py similarity index 100% rename from terminatorlib/validate.py rename to terminatorlib/configobj/validate.py From 4ed920d6256ac331b81f8f44d1e2bb83457ff119 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 5 Jan 2010 00:05:03 +0000 Subject: [PATCH 235/331] Start working on a preferences UI in glade --- data/preferences.glade | 1958 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1958 insertions(+) create mode 100644 data/preferences.glade diff --git a/data/preferences.glade b/data/preferences.glade new file mode 100644 index 00000000..902adcd1 --- /dev/null +++ b/data/preferences.glade @@ -0,0 +1,1958 @@ + + + + + + + + + + + + Default + + + + + + + + + + + + + + + 5 + normal + False + + + True + vertical + 2 + + + True + True + + + True + 6 + 2 + + + True + + + 1 + 2 + + GTK_EXPAND + + + + + True + Mouse focus + + + + + + + + + True + Terminal separator size + + + 1 + 2 + + + + + + + True + True + 0 + left + + + 1 + 2 + 1 + 2 + GTK_EXPAND + 20 + + + + + True + Window geometry hints + + + 2 + 3 + + + + + + + True + True + False + True + True + + + 1 + 2 + 2 + 3 + + GTK_EXPAND + + + + + True + Window state + + + 3 + 4 + + + + + + + True + + + 1 + 2 + 3 + 4 + + GTK_EXPAND + + + + + True + Window borders + + + 4 + 5 + + + + + + + True + True + False + True + True + + + 1 + 2 + 4 + 5 + + GTK_EXPAND + + + + + True + Tab position + + + 5 + 6 + + + + + + + True + + + 1 + 2 + 5 + 6 + + GTK_EXPAND + + + + + + + True + Global + + + False + + + + + True + + + True + True + ProfilesListStore + + + False + 0 + + + + + True + True + 5 + + + True + 12 + vertical + 6 + + + True + 12 + + + True + 0 + 4 + _Profile name: + True + center + profile-name-entry + + + False + False + 0 + + + + + True + True + + + + 1 + + + + + False + False + 0 + + + + + _Use the system fixed width font + True + True + False + True + True + + + False + False + 1 + + + + + True + 12 + + + True + 12 + + + True + 0 + _Font: + True + font-selector + + + False + False + 0 + + + + + True + True + True + False + Choose A Terminal Font + True + True + + + False + False + 1 + + + + + + + False + False + 2 + + + + + _Allow bold text + True + True + False + True + True + + + False + False + 3 + + + + + Show _menubar by default in new terminals + True + True + False + True + True + + + False + False + 4 + + + + + Terminal _bell + True + True + False + True + True + + + False + False + 5 + + + + + True + 12 + + + True + Cursor _shape: + True + cursor-shape-combobox + + + False + False + 0 + + + + + True + + + False + 1 + + + + + False + 6 + + + + + True + 12 + + + True + 0 + Select-by-_word characters: + True + center + word-chars-entry + + + False + False + 0 + + + + + True + True + + + + 1 + + + + + False + 7 + + + + + + + True + General + True + center + + + False + + + + + True + 12 + vertical + 18 + + + True + vertical + 6 + + + True + 0 + <b>Title</b> + True + + + False + False + 0 + + + + + True + 12 + + + True + vertical + 6 + + + True + 12 + + + True + 0 + Initial _title: + True + center + title-entry + + + False + False + 0 + + + + + True + True + + + + 1 + + + + + 0 + + + + + True + 12 + + + True + 0 + When terminal commands set their o_wn titles: + True + center + title-mode-combobox + + + False + False + 0 + + + + + True + + + 1 + + + + + 1 + + + + + + + False + 1 + + + + + False + 0 + + + + + True + vertical + 6 + + + True + 0 + <b>Command</b> + True + + + False + False + 0 + + + + + True + 12 + + + True + vertical + + + True + vertical + 6 + + + _Run command as a login shell + True + True + False + True + True + + + False + False + 0 + + + + + _Update login records when command is launched + True + True + False + True + True + + + False + False + 1 + + + + + Ru_n a custom command instead of my shell + True + True + False + True + True + + + False + False + 2 + + + + + True + 12 + + + True + 12 + + + True + 0 + Custom co_mmand: + True + center + custom-command-entry + + + False + False + 0 + + + + + True + True + + + + 1 + + + + + + + False + 3 + + + + + True + 12 + + + True + 0 + When command _exits: + True + center + exit-action-combobox + + + False + False + 0 + + + + + True + + + 1 + + + + + 4 + + + + + False + 0 + + + + + + + False + 1 + + + + + False + 1 + + + + + 1 + + + + + True + Title and Command + True + center + + + 1 + False + + + + + True + 12 + vertical + 18 + + + True + vertical + 6 + + + True + 0 + <b>Foreground and Background</b> + True + + + False + False + 0 + + + + + True + 12 + + + True + 4 + 2 + 12 + 6 + + + True + 0 + _Text color: + True + center + foreground-colorpicker + + + 2 + 3 + GTK_FILL + + + + + + _Use colors from system theme + True + True + False + True + True + + + 2 + GTK_FILL + + + + + + True + 0 + _Background color: + True + center + background-colorpicker + + + 3 + 4 + GTK_FILL + + + + + + True + 0 + Built-in sche_mes: + True + center + color-scheme-combobox + + + 1 + 2 + GTK_FILL + + + + + + True + + + True + True + True + Choose Terminal Text Color + #000000000000 + + + False + False + 0 + + + + + + + + 1 + 2 + 2 + 3 + GTK_FILL + GTK_FILL + + + + + True + + + True + True + True + Choose Terminal Background Color + #000000000000 + + + False + False + 0 + + + + + + + + 1 + 2 + 3 + 4 + GTK_FILL + GTK_FILL + + + + + True + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + + + False + 1 + + + + + False + 0 + + + + + True + vertical + 6 + + + True + 0 + <b>Palette</b> + True + + + False + False + 0 + + + + + True + 12 + + + True + 3 + 2 + 12 + 6 + + + True + 0 + Built-in _schemes: + True + center + palette-combobox + + + 1 + 2 + GTK_FILL + + + + + + True + 2 + 8 + 6 + 6 + + + True + True + True + #000000000000 + + + + + + + + + True + True + True + #000000000000 + + + 1 + 2 + + + + + + + True + True + True + #000000000000 + + + 2 + 3 + + + + + + + True + True + True + #000000000000 + + + 3 + 4 + + + + + + + True + True + True + #000000000000 + + + 4 + 5 + + + + + + + True + True + True + #000000000000 + + + 5 + 6 + + + + + + + True + True + True + #000000000000 + + + 7 + 8 + + + + + + + True + True + True + #000000000000 + + + 1 + 2 + + + + + + + True + True + True + #000000000000 + + + 1 + 2 + 1 + 2 + + + + + + + True + True + True + #000000000000 + + + 3 + 4 + 1 + 2 + + + + + + + True + True + True + #000000000000 + + + 2 + 3 + 1 + 2 + + + + + + + True + True + True + #000000000000 + + + 4 + 5 + 1 + 2 + + + + + + + True + True + True + #000000000000 + + + 7 + 8 + 1 + 2 + + + + + + + True + True + True + #000000000000 + + + 5 + 6 + 1 + 2 + GTK_FILL + + + + + + True + True + True + #000000000000 + + + 6 + 7 + + + + + + + True + True + True + #000000000000 + + + 6 + 7 + 1 + 2 + GTK_FILL + + + + + + 1 + 2 + 2 + 3 + GTK_FILL + GTK_FILL + + + + + True + 0 + <small><i><b>Note:</b> Terminal applications have these colors available to them.</i></small> + True + center + + + 2 + GTK_FILL + + + + + + True + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + True + 0 + 0 + Color p_alette: + True + center + palette-colorpicker-1 + + + 2 + 3 + GTK_FILL + GTK_FILL + + + + + + + False + 1 + + + + + False + 1 + + + + + 2 + + + + + True + Colors + True + center + + + 2 + False + + + + + True + 12 + vertical + 6 + + + _Solid color + True + True + False + True + True + True + + + False + False + 0 + + + + + True + vertical + 6 + + + _Background image + True + True + False + True + True + solid-radiobutton + + + False + False + 0 + + + + + True + 12 + + + True + vertical + 6 + + + True + 12 + + + True + 0 + Image _file: + True + center + background-image-filechooser + + + False + False + 0 + + + + + True + True + False + Select Background Image + + + 1 + + + + + 0 + + + + + Background image _scrolls + True + True + False + True + True + + + False + False + 1 + + + + + + + False + 1 + + + + + False + 1 + + + + + _Transparent background + True + True + False + True + True + solid-radiobutton + + + False + False + 2 + + + + + True + vertical + 6 + + + True + 0 + S_hade transparent or image background: + True + darken-background-scale + + + False + False + 1 + 0 + + + + + True + + + True + 0 + 6 + <small><i>None</i></small> + True + + + False + False + 0 + + + + + True + True + delayed + 2 + False + bottom + + + 1 + + + + + True + 1 + 6 + <small><i>Maximum</i></small> + True + + + False + False + 2 + + + + + False + False + 1 + 1 + + + + + False + 3 + + + + + 3 + + + + + True + 0 + Background + True + + + 3 + False + + + + + True + 12 + 4 + 2 + 12 + 6 + + + Scroll on _keystroke + True + True + False + True + True + + + 2 + 3 + 4 + + + + + + Scroll on _output + True + True + False + True + True + + + 2 + 2 + 3 + + + + + + True + + + True + + + False + 0 + + + + + 1 + 2 + GTK_FILL + + + + + True + 6 + + + True + True + + 1 + True + + + False + 0 + + + + + True + 0 + lines + center + scrollback-lines-spinbutton + + + False + False + 1 + + + + + True + (about 120kB) + + + False + False + 2 + + + + + 1 + 2 + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + 0 + _Scrollbar is: + True + center + scrollbar-position-combobox + + + GTK_FILL + GTK_FILL + + + + + True + 0 + Scroll_back: + True + center + scrollback-lines-spinbutton + + + 1 + 2 + GTK_FILL + GTK_FILL + + + + + 4 + + + + + True + Scrolling + True + + + 4 + False + + + + + True + 12 + vertical + 12 + + + True + 0 + 7.4505801528346183e-09 + <small><i><b>Note:</b> These options may cause some applications to behave incorrectly. They are only here to allow you to work around certain applications and operating systems that expect different terminal behavior.</i></small> + True + True + + + False + 0 + + + + + True + 2 + 3 + 12 + 6 + + + True + 0 + _Delete key generates: + True + center + delete-binding-combobox + + + 1 + 2 + GTK_FILL + + + + + + True + 0 + _Backspace key generates: + True + center + backspace-binding-combobox + + + GTK_FILL + + + + + + True + + + 1 + 3 + GTK_FILL + GTK_FILL + + + + + True + + + 1 + 3 + 1 + 2 + GTK_FILL + GTK_FILL + + + + + False + 1 + + + + + True + start + + + _Reset Compatibility Options to Defaults + True + True + True + True + + + False + False + 0 + + + + + False + False + 2 + + + + + 5 + + + + + True + Compatibility + True + center + + + 5 + False + + + + + 1 + + + + + 1 + + + + + True + Profiles + + + 1 + False + + + + + True + Not yet implemented + + + 2 + + + + + True + Layouts + + + 2 + False + + + + + True + True + KeybindingsListStore + + + 3 + + + + + True + Keybindings + + + 3 + False + + + + + True + Not yet implemented + + + 4 + + + + + True + Plugins + + + 4 + False + + + + + 1 + + + + + True + end + + + gtk-cancel + True + True + True + True + + + False + False + 0 + + + + + gtk-ok + True + True + True + True + + + False + False + 1 + + + + + False + end + 0 + + + + + + button1 + button2 + + + From 7beb3bf424395d185524743569b61c9cc88224b5 Mon Sep 17 00:00:00 2001 From: Elliot Murphy Date: Mon, 4 Jan 2010 23:47:51 -0500 Subject: [PATCH 236/331] Hook up doctests so they are discoverable via trial: trial terminatorlib --- terminatorlib/tests/__init__.py | 0 terminatorlib/tests/test_doctests.py | 13 +++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 terminatorlib/tests/__init__.py create mode 100644 terminatorlib/tests/test_doctests.py diff --git a/terminatorlib/tests/__init__.py b/terminatorlib/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/terminatorlib/tests/test_doctests.py b/terminatorlib/tests/test_doctests.py new file mode 100644 index 00000000..2b4c6d90 --- /dev/null +++ b/terminatorlib/tests/test_doctests.py @@ -0,0 +1,13 @@ +"""Load up the tests.""" +from unittest import TestSuite +from doctest import DocTestSuite, ELLIPSIS + +def test_suite(): + suite = TestSuite() + for name in ( + 'config', + 'plugin', + 'borg', + ): + suite.addTest(DocTestSuite('terminatorlib.' + name)) + return suite From cb0b68bc5ddcead746114f439535e42751dbfbd9 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 5 Jan 2010 09:24:44 +0000 Subject: [PATCH 237/331] There was a reason we imported these three, I just forgot it until now. It's a rough test that we have sufficient dependencies to run. --- terminator | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/terminator b/terminator index a52f56fc..26fc3502 100755 --- a/terminator +++ b/terminator @@ -24,7 +24,8 @@ import sys try: import pygtk pygtk.require ("2.0") - import gtk + # pylint: disable-msg=W0611 + import gtk, pango, gobject if gtk.gdk.display_get_default() == None: print('You need to run terminator in an X environment. ' \ From a65fa136e8f63606bcc3d680b0567d537f613783 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 5 Jan 2010 10:07:35 +0000 Subject: [PATCH 238/331] Various tidying, version and packaging updates --- INSTALL | 8 +------- README | 2 +- TODO | 6 ------ debian/changelog | 6 ++++++ debian/copyright | 3 ++- doc/terminator.1 | 7 ------- doc/terminator_config.5 | 4 +++- setup.py | 1 + terminator.spec | 7 ++++++- test.py | 17 ----------------- 10 files changed, 20 insertions(+), 41 deletions(-) delete mode 100644 TODO delete mode 100755 test.py diff --git a/INSTALL b/INSTALL index 28857f8c..8776a8f4 100644 --- a/INSTALL +++ b/INSTALL @@ -7,7 +7,7 @@ for many distributions at: If you don't have this option, please make sure you satisfy Terminator's dependencies yourself: - * Python 2.4+, 2.6 recommended: + * Python 2.5+, 2.6 recommended: Debian/Ubuntu: python FreeBSD: lang/python26 @@ -15,12 +15,6 @@ dependencies yourself: Debian/Ubuntu: python-vte FreeBSD: x11-toolkits/py-vte -If you want gnome-terminal profile support, you also need: - - * Python GNOME 2 bindings: - Debian/Ubuntu: python-gnome2 - FreeBSD: x11-toolkits/py-gnome2 - If you don't care about native language support or icons, Terminator should run just fine directly from this directory, just: diff --git a/README b/README index 74c8cd9b..52522fd4 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Terminator 0.14 +Terminator 0.90 by Chris Jones and others. The goal of this project is to produce a useful tool for arranging terminals. diff --git a/TODO b/TODO deleted file mode 100644 index ae46d2fc..00000000 --- a/TODO +++ /dev/null @@ -1,6 +0,0 @@ -* menu entry/keybinding to hightlight a term upon: - * command ending - * new text in window - * when a command exits, "window-title-changed" is emitted - even though the actual title string do not change - * text-modified could be used to spy on outputs from the command diff --git a/debian/changelog b/debian/changelog index 16aea692..9d2112a7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +terminator (0.90~alpha1) lucid; urgency=low + + * New upstream pre-release + + -- Chris Jones Tue, 05 Jan 2010 09:56:27 +0000 + terminator (0.14) karmic; urgency=low * New upstream release diff --git a/debian/copyright b/debian/copyright index bd2183da..26437c9f 100644 --- a/debian/copyright +++ b/debian/copyright @@ -33,13 +33,14 @@ Translations: "Data" Cristian Grada "zhuqin" + and many others. Seriously, thank you very much to the translators. A few minutes of their dedication opens up userbases like nothing else. Copyright: - + License: diff --git a/doc/terminator.1 b/doc/terminator.1 index 500ca2b9..3873ab82 100644 --- a/doc/terminator.1 +++ b/doc/terminator.1 @@ -41,13 +41,6 @@ with the \fBhide_window\fR keyboard shortcut (Ctrl-Shift-Alt-a by default) Force the Terminator window to use a specific name rather than updating it dynamically based on the wishes of the child shell. .TP -.B \-\-no_gconf -Ignore the gconf settings of gnome-terminal -.TP -.B \-p, \-\-profile PROFILE -Loads the GNOME Terminal profile named PROFILE. Note that doing this will override the settings -in your Terminator config file with those from the GNOME Terminal profile. -.TP .B \-\-geometry=GEOMETRY Specifies the preferred size and position of Terminator's window; see X(7). .TP diff --git a/doc/terminator_config.5 b/doc/terminator_config.5 index b2f1ec35..6759a83f 100644 --- a/doc/terminator_config.5 +++ b/doc/terminator_config.5 @@ -3,7 +3,9 @@ ~/.config/terminator/config \- the config file for Terminator terminal emulator. .SH "DESCRIPTION" This manual page documents briefly the -.B Terminator config file. +.B Terminator +config file. +.B IT IS FULL OF LIES. THE CONFIG FILE FORMAT HAS COMPLETELY CHANGED. .PP \fBterminator/config\fP is an optional file to configure the terminator terminal emulator. It is used to control options not in gnome-terminal gconf profiles, or override gconf settings. .SH "FILE LOCATION" diff --git a/setup.py b/setup.py index 38be0b4e..aa3c7bb1 100755 --- a/setup.py +++ b/setup.py @@ -161,6 +161,7 @@ setup(name='Terminator', license='GNU GPL v2', scripts=['terminator'], data_files=[ + ('share/terminator', ['data/preferences.glade']), ('share/applications', ['data/terminator.desktop']), (os.path.join(man_dir, 'man1'), ['doc/terminator.1']), (os.path.join(man_dir, 'man5'), ['doc/terminator_config.5']), diff --git a/terminator.spec b/terminator.spec index ec51b70f..69f7d366 100644 --- a/terminator.spec +++ b/terminator.spec @@ -1,7 +1,7 @@ %{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} Name: terminator -Version: 0.14 +Version: 0.90 Release: 3%{?dist} Summary: Store and run multiple GNOME terminals in one window @@ -52,6 +52,7 @@ rm -rf %{buildroot} %{_mandir}/man5/%{name}_config.* %{_bindir}/%{name} %{python_sitelib}/* +%{_datadir}/terminator/preferences.glade %{_datadir}/applications/%{name}.desktop %{_datadir}/icons/hicolor/*/*/%{name}*.png %{_datadir}/icons/hicolor/*/*/%{name}*.svg @@ -67,6 +68,10 @@ gtk-update-icon-cache -qf %{_datadir}/icons/hicolor &>/dev/null || : %changelog +* Tue Jan 05 2010 Chris Jones 0.90-1 +- Attempt to update for 0.90 pre-release. + Note that this specfile is untested. + * Thu Jan 15 2009 Chris Jones 0.12-1 - Remove patch application since this isn't a fedora build. Note that this specfile is untested. diff --git a/test.py b/test.py deleted file mode 100755 index 4bde29be..00000000 --- a/test.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/python - -import gtk -from terminatorlib.factory import Factory - -maker = Factory() -window = maker.make('Window') -term = maker.make('Terminal') - -window.add(term) -window.show() -term.spawn_child() - -try: - gtk.main() -except KeyboardInterrupt: - pass From 58fd86486d73d0823f41bc9892274049a2a3335b Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 5 Jan 2010 12:49:04 +0000 Subject: [PATCH 239/331] Switch to using **kwargs instead of *args for the added flexibility it brings. We need to support keyword arguments for Window.__init__() --- terminatorlib/factory.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/terminatorlib/factory.py b/terminatorlib/factory.py index 03002ab2..53ab55d7 100755 --- a/terminatorlib/factory.py +++ b/terminatorlib/factory.py @@ -43,7 +43,7 @@ class Factory(Borg): err('Factory::isinstance: unknown class type: %s' % classtype) return(False) - def make(self, product, *args): + def make(self, product, **kwargs): """Make the requested product""" try: func = getattr(self, 'make_%s' % product.lower()) @@ -52,30 +52,30 @@ class Factory(Borg): return(None) dbg('Factory::make: created a %s' % product) - return(func(args)) + return(func(**kwargs)) - def make_window(self, *args): + def make_window(self, **kwargs): """Make a Window""" import window - return(window.Window()) + return(window.Window(**kwargs)) - def make_terminal(self, *args): + def make_terminal(self, **kwargs): """Make a Terminal""" import terminal return(terminal.Terminal()) - def make_hpaned(self, *args): + def make_hpaned(self, **kwargs): """Make an HPaned""" import paned return(paned.HPaned()) - def make_vpaned(self, *args): + def make_vpaned(self, **kwargs): """Make a VPaned""" import paned return(paned.VPaned()) - def make_notebook(self, *args): + def make_notebook(self, **kwargs): """Make a Notebook""" import notebook - return(notebook.Notebook(args[0][0])) + return(notebook.Notebook(kwargs['window'])) From 9744f3ebb64719d6bdd8e6896329addb0b16144f Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 5 Jan 2010 12:49:57 +0000 Subject: [PATCH 240/331] Add keyword arguments to Window.__init__() for command line options --- terminator | 3 ++- terminatorlib/window.py | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/terminator b/terminator index 26fc3502..f79b5577 100755 --- a/terminator +++ b/terminator @@ -51,7 +51,8 @@ if __name__ == '__main__': MAKER = Factory() TERMINATOR = Terminator() - WINDOW = MAKER.make('Window') + WINDOW = MAKER.make('Window', geometry=OPTIONS.geometry, + forcedtitle=OPTIONS.forcedtitle, role=OPTIONS.role) TERMINAL = MAKER.make('Terminal') WINDOW.add(TERMINAL) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 7e54f0bf..de9d6091 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -35,7 +35,7 @@ class Window(Container, gtk.Window): zoom_data = None term_zoomed = gobject.property(type=bool, default=False) - def __init__(self): + def __init__(self, geometry=None, forcedtitle=None, role=None): """Class initialiser""" self.terminator = Terminator() self.terminator.register_window(self) @@ -53,6 +53,8 @@ class Window(Container, gtk.Window): self.title = WindowTitle(self) self.title.update() + if forcedtitle is not None: + self.title.force_title(forcedtitle) def register_callbacks(self): """Connect the GTK+ signals we care about""" @@ -125,7 +127,7 @@ class Window(Container, gtk.Window): """Make a new tab""" maker = Factory() if not maker.isinstance(self.get_child(), 'Notebook'): - notebook = maker.make('Notebook', self) + notebook = maker.make('Notebook', window=self) self.get_child().newtab() def on_delete_event(self, window, event, data=None): @@ -310,7 +312,7 @@ class WindowTitle(object): def force_title(self, newtext): """Force a specific title""" if newtext: - self.set_title(newtext) + self.set_title(None, newtext) self.forced = True else: self.forced = False From fe7e03d00f0a46f5d93730651777e165e2cec74e Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 5 Jan 2010 12:51:53 +0000 Subject: [PATCH 241/331] Add some old command line options as dummies, and make some command line options influence the config object. This is probably a poor long-term strategy as it means supplying command line options and saving the config will cause the command line options to be baked into the config --- terminatorlib/optionparse.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/terminatorlib/optionparse.py b/terminatorlib/optionparse.py index 3f213545..0e33a0a8 100755 --- a/terminatorlib/optionparse.py +++ b/terminatorlib/optionparse.py @@ -67,7 +67,8 @@ command to execute inside the terminal, and its arguments') dest='working_directory', help='Set the working directory') parser.add_option('-r', '--role', dest='role', help='Set a custom \ WM_WINDOW_ROLE property on the window') - for item in ['--sm-client-id', '--sm-config-prefix', '--screen']: + for item in ['--sm-client-id', '--sm-config-prefix', '--screen', '-n', + '--no-gconf', '-p', '--profile' ]: parser.add_option(item, dest='dummy', action='store', help=SUPPRESS_HELP) @@ -93,6 +94,15 @@ WM_WINDOW_ROLE property on the window') if options.maximise: configobj['maximise'] = True + if options.fullscreen: + configobj['fullscreen'] = True + + if options.borderless: + configobj['borderless'] = True + + if options.hidden: + configobj['hidden'] = True + # FIXME: Map all the other bits of options to configobj if util.DEBUG == True: From 1625326838ef2b9ca38662128cad8f415162d609 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 5 Jan 2010 12:55:05 +0000 Subject: [PATCH 242/331] Support setting the window role --- terminatorlib/window.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index de9d6091..d75f689a 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -56,6 +56,9 @@ class Window(Container, gtk.Window): if forcedtitle is not None: self.title.force_title(forcedtitle) + if role is not None: + self.set_role(role) + def register_callbacks(self): """Connect the GTK+ signals we care about""" self.connect('key-press-event', self.on_key_press) From 266b662923eb8f4eb44edb625ef41b4c526c5dcd Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 5 Jan 2010 12:58:05 +0000 Subject: [PATCH 243/331] Support --geometry --- terminatorlib/window.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index d75f689a..10a3a297 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -59,6 +59,10 @@ class Window(Container, gtk.Window): if role is not None: self.set_role(role) + if geometry is not None: + if not self.parse_geometry(geometry): + err('Window::__init__: Unable to parse geometry: %s' % geometry) + def register_callbacks(self): """Connect the GTK+ signals we care about""" self.connect('key-press-event', self.on_key_press) From 08b65f8b2a034bed4a135d8536fb2866c8432aed Mon Sep 17 00:00:00 2001 From: Emmanuel Bretelle Date: Tue, 5 Jan 2010 18:57:46 +0100 Subject: [PATCH 244/331] plugins: * enable custom command using terminal_menu plugin hook --- terminatorlib/plugins/custom_commands.py | 442 +++++++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 terminatorlib/plugins/custom_commands.py diff --git a/terminatorlib/plugins/custom_commands.py b/terminatorlib/plugins/custom_commands.py new file mode 100644 index 00000000..15439012 --- /dev/null +++ b/terminatorlib/plugins/custom_commands.py @@ -0,0 +1,442 @@ +# Terminator by Chris Jones Date: Tue, 5 Jan 2010 21:44:12 +0000 Subject: [PATCH 245/331] Add plugin config API --- terminatorlib/config.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 731dd345..cb62a159 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -37,6 +37,13 @@ Classes relating to configuration 'click' >>> config['fullscreen'].__class__.__name__ 'bool' +>>> plugintest = {} +>>> plugintest['foo'] = 'bar' +>>> config.plugin_set_config('testplugin', plugintest) +>>> config.plugin_get_config('testplugin') +{'foo': 'bar'} +>>> config.plugin_get('testplugin', 'foo') +'bar' >>> """ @@ -220,6 +227,22 @@ class Config(object): """Cause ConfigBase to save our config to file""" return(self.base.save()) + def plugin_get(self, pluginname, key): + """Get a plugin config value""" + return(self.base.get_item(key, plugin=pluginname)) + + def plugin_set(self, pluginname, key, value): + """Set a plugin config value""" + return(self.base.set_item(key, value, plugin=pluginname)) + + def plugin_get_config(self, plugin): + """Return a whole config tree for a given plugin""" + return(self.base.get_plugin(plugin)) + + def plugin_set_config(self, plugin, tree): + """Set a whole config tree for a given plugin""" + return(self.base.set_plugin(plugin, tree)) + class ConfigBase(Borg): """Class to provide access to our user configuration""" loaded = None @@ -419,13 +442,22 @@ class ConfigBase(Borg): self.profiles[profile][key] = value elif key == 'keybindings': self.keybindings = value - elif plugin is not None and self.plugins[plugin].has_key(key): + elif plugin is not None: self.plugins[plugin][key] = value else: raise KeyError('ConfigBase::set_item: unknown key %s' % key) return(True) + def get_plugin(self, plugin): + """Return a whole tree for a plugin""" + if self.plugins.has_key(plugin): + return(self.plugins[plugin]) + + def set_plugin(self, plugin, tree): + """Set a whole tree for a plugin""" + self.plugins[plugin] = tree + if __name__ == '__main__': import sys import doctest From 710b8a4834bb596d28b618bbd83083d444b71c89 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 5 Jan 2010 22:15:56 +0000 Subject: [PATCH 246/331] Plugins that have never given us config before need a dict created for them before they can set values --- terminatorlib/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index cb62a159..b36b0854 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -443,6 +443,8 @@ class ConfigBase(Borg): elif key == 'keybindings': self.keybindings = value elif plugin is not None: + if not self.plugins.has_key(plugin): + self.plugins[plugin] = {} self.plugins[plugin][key] = value else: raise KeyError('ConfigBase::set_item: unknown key %s' % key) From 0b5cf876ba9aadf8633b255248df5fc0abc85f50 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 5 Jan 2010 22:22:13 +0000 Subject: [PATCH 247/331] Move the base plugin classes to plugin.py for cleaner importing in additional plugins --- terminatorlib/plugin.py | 25 +++++++++++++++++++++++++ terminatorlib/plugins/terminal_menu.py | 10 +--------- terminatorlib/plugins/url_handlers.py | 16 +++------------- 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py index d664f86b..f1e0752d 100755 --- a/terminatorlib/plugin.py +++ b/terminatorlib/plugin.py @@ -105,6 +105,31 @@ for %s' % (len(self.instances), capability)) """Return all plugins""" return(self.instances) +# This is where we should define a base class for each type of plugin we +# support + +# URLHandler - This adds a regex match to the Terminal widget and provides a +# callback to turn that into a URL. +class URLHandler(Plugin): + """Base class for URL handlers""" + capabilities = ['url_handler'] + handler_name = None + match = None + + def callback(self, url): + """Callback to transform the enclosed URL""" + raise NotImplementedError + +# MenuItem - This is able to execute code during the construction of the +# context menu of a Terminal. +class MenuItem(Plugin): + """Base class for menu items""" + capabilities = ['terminal_menu'] + + def callback(self, menuitems, menu, terminal): + """Callback to transform the enclosed URL""" + raise NotImplementedError + if __name__ == '__main__': import sys import doctest diff --git a/terminatorlib/plugins/terminal_menu.py b/terminatorlib/plugins/terminal_menu.py index 575a5c74..93b5e62e 100644 --- a/terminatorlib/plugins/terminal_menu.py +++ b/terminatorlib/plugins/terminal_menu.py @@ -10,15 +10,7 @@ import terminatorlib.plugin as plugin #available = ['MyFirstMenuItem'] available = [] -class MenuItem(plugin.Plugin): - """Base class for menu items""" - capabilities = ['terminal_menu'] - - def callback(self, menuitems, menu, terminal): - """Callback to transform the enclosed URL""" - raise NotImplementedError - -class MyFirstMenuItem(MenuItem): +class MyFirstMenuItem(plugin.MenuItem): """Simple proof of concept""" capabilities = ['terminal_menu'] diff --git a/terminatorlib/plugins/url_handlers.py b/terminatorlib/plugins/url_handlers.py index a7da4393..edea9457 100644 --- a/terminatorlib/plugins/url_handlers.py +++ b/terminatorlib/plugins/url_handlers.py @@ -7,17 +7,7 @@ import terminatorlib.plugin as plugin # Every plugin you want Terminator to load *must* be listed in 'available' available = ['LaunchpadBugURLHandler', 'LaunchpadCodeURLHandler', 'APTURLHandler'] -class URLHandler(plugin.Plugin): - """Base class for URL handlers""" - capabilities = ['url_handler'] - handler_name = None - match = None - - def callback(self, url): - """Callback to transform the enclosed URL""" - raise NotImplementedError - -class LaunchpadBugURLHandler(URLHandler): +class LaunchpadBugURLHandler(plugin.URLHandler): """Launchpad Bug URL handler. If the URL looks like a Launchpad changelog closure entry... 'LP: #12345' then it should be transformed into a Launchpad Bug URL""" @@ -31,7 +21,7 @@ class LaunchpadBugURLHandler(URLHandler): url = 'https://bugs.launchpad.net/bugs/%s' % item return(url) -class LaunchpadCodeURLHandler(URLHandler): +class LaunchpadCodeURLHandler(plugin.URLHandler): """Launchpad Code URL handler. If the URL looks like a Launchpad project or branch entry then it should be transformed into a code.launchpad.net URL""" capabilities = ['url_handler'] @@ -50,7 +40,7 @@ class LaunchpadCodeURLHandler(URLHandler): url = url[3:] return('https://code.launchpad.net/+branch/%s' % url) -class APTURLHandler(URLHandler): +class APTURLHandler(plugin.URLHandler): """APT URL handler. If there is a URL that looks like an apturl, handle it appropriately""" capabilities = ['url_handler'] From 7328d9aa4f5b5f9714dff8fd82a87e0b53aa558a Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 5 Jan 2010 22:28:14 +0000 Subject: [PATCH 248/331] If plugins have defined extra menu items, insert a spacer before they start --- terminatorlib/terminal_popup_menu.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/terminatorlib/terminal_popup_menu.py b/terminatorlib/terminal_popup_menu.py index 1a3f2c32..4ba22529 100755 --- a/terminatorlib/terminal_popup_menu.py +++ b/terminatorlib/terminal_popup_menu.py @@ -165,6 +165,10 @@ class TerminalPopupMenu(object): plugins = registry.get_plugins_by_capability('terminal_menu') for menuplugin in plugins: menuplugin.callback(menuitems, menu, terminal) + + if len(menuitems) > 0: + menu.append(gtk.MenuItem()) + for menuitem in menuitems: menu.append(menuitem) except Exception, ex: From 8c117ff6e953d1afad1fbc32730318c8f6522013 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 5 Jan 2010 22:29:02 +0000 Subject: [PATCH 249/331] Tidy custom_commands plugin up and port it to using our shiny new plugin config API --- terminatorlib/plugins/custom_commands.py | 64 +++++++++++------------- 1 file changed, 28 insertions(+), 36 deletions(-) mode change 100644 => 100755 terminatorlib/plugins/custom_commands.py diff --git a/terminatorlib/plugins/custom_commands.py b/terminatorlib/plugins/custom_commands.py old mode 100644 new mode 100755 index 15439012..5fbe31a1 --- a/terminatorlib/plugins/custom_commands.py +++ b/terminatorlib/plugins/custom_commands.py @@ -1,50 +1,44 @@ -# Terminator by Chris Jones # GPL v2 only -"""terminal_menu.py - Default plugins for the terminal menu""" +"""custom_commands.py - Terminator Plugin to add custom command menu entries""" import sys import os -# only ran when testing plugin + +# Fix imports when testing this file directly if __name__ == '__main__': sys.path.append( os.path.join(os.path.dirname(__file__), "../..")) + import gtk import terminatorlib.plugin as plugin +from terminatorlib.config import Config from terminatorlib.translation import _ from terminatorlib.util import get_config_dir -import ConfigParser (CC_COL_ENABLED, CC_COL_NAME, CC_COL_COMMAND) = range(0,3) # Every plugin you want Terminator to load *must* be listed in 'available' - -# This is commented out because we don't want any menu item plugins by default -#available = ['MyFirstMenuItem'] available = ['CustomCommandsMenu'] -class MenuItem(plugin.Plugin): - """Base class for menu items""" - capabilities = ['terminal_menu'] - - def callback(self, menuitems, menu, terminal): - """Callback to transform the enclosed URL""" - raise NotImplementedError - -class CustomCommandsMenu(MenuItem): - """Simple proof of concept""" +class CustomCommandsMenu(plugin.MenuItem): + """Add custom commands to the terminal menu""" capabilities = ['terminal_menu'] cmd_list = [] conf_file = os.path.join(get_config_dir(),"custom_commands") def __init__( self): - config = ConfigParser.ConfigParser() - config.read(self.conf_file) - sections = config.sections() - for s in sections: - if not (config.has_option(s, "name") and config.has_option(s, "command")): + config = Config() + sections = config.plugin_get_config(self.__class__.__name__) + if not isinstance(sections, dict): + return + for part in sections: + s = sections[part] + if not (s.has_key("name") and s.has_key("command")): print "CustomCommandsMenu: Ignoring section %s" % s continue - name = config.get(s, "name") - command = config.get(s, "command") - enabled = config.has_option(s, "enabled") and config.getboolean(s, "enabled") or False + name = s["name"] + command = s["command"] + enabled = s["enabled"] and s["enabled"] or False self.cmd_list.append( {'enabled' : enabled, 'name' : name, @@ -83,25 +77,23 @@ class CustomCommandsMenu(MenuItem): submenu.append(menuitem) def _save_config(self): - config = ConfigParser.ConfigParser() + config = Config() i = 0 length = len(self.cmd_list) while i < length: enabled = self.cmd_list[i]['enabled'] name = self.cmd_list[i]['name'] command = self.cmd_list[i]['command'] - - config.add_section(name) - config.set(name,'enabled', enabled) - config.set(name,'name', name) - config.set(name,'command', command) + + item = {} + item['enabled'] = enabled + item['name'] = name + item['command'] = command + + config.plugin_set(self.__class__.__name__, name, item) + config.save() i = i + 1 - with open(self.conf_file, 'wb') as configfile: - config.write(configfile) - configfile.close() - - def _execute(self, widget, data): command = data['command'] if command[len(command)-1] != '\n': From 68ade515ebcf159d927c154bfee4310c7119e610 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 6 Jan 2010 00:27:58 +0000 Subject: [PATCH 250/331] Add support for disabling modules and add CustomCommandsMenu and TestPlugin to it --- terminatorlib/config.py | 1 + terminatorlib/plugin.py | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index b36b0854..1f3e1d3d 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -73,6 +73,7 @@ DEFAULTS = { 'hide_tabbar' : False, 'scroll_tabbar' : False, 'try_posix_regexp' : platform.system() != 'Linux', + 'disabled_plugins' : ['TestPlugin', 'CustomCommandsMenu'], }, 'keybindings': { 'zoom_in' : 'plus', diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py index f1e0752d..99ab1798 100755 --- a/terminatorlib/plugin.py +++ b/terminatorlib/plugin.py @@ -10,7 +10,7 @@ >>> registry = PluginRegistry() >>> registry.instances {} ->>> registry.load_plugins() +>>> registry.load_plugins(True) >>> plugins = registry.get_plugins_by_capability('test') >>> len(plugins) 1 @@ -26,6 +26,7 @@ import sys import os import borg +from config import Config from util import dbg, err, get_config_dir class Plugin(object): @@ -61,12 +62,14 @@ class PluginRegistry(borg.Borg): if not self.done: self.done = False - def load_plugins(self): + def load_plugins(self, testing=False): """Load all plugins present in the plugins/ directory in our module""" if self.done: dbg('PluginRegistry::load_plugins: Already loaded') return + config = Config() + for plugindir in self.path: sys.path.insert(0, plugindir) try: @@ -82,6 +85,8 @@ class PluginRegistry(borg.Borg): try: module = __import__(plugin[:-3], None, None, ['']) for item in getattr(module, 'available'): + if not testing and item in config['disabled_plugins']: + continue if item not in self.instances: func = getattr(module, item) self.instances[item] = func() From 4aa1b50fe6575b50867bf95d1e407239d62bacb5 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 8 Jan 2010 01:02:49 +0000 Subject: [PATCH 251/331] Change the way window state config works. Initial workings of preferences window. --- data/preferences.glade | 353 ++++++++++++++++++++--------- terminatorlib/config.py | 12 +- terminatorlib/optionparse.py | 6 +- terminatorlib/prefseditor.py | 426 +++++++++++------------------------ terminatorlib/window.py | 8 +- 5 files changed, 389 insertions(+), 416 deletions(-) mode change 100644 => 100755 terminatorlib/prefseditor.py diff --git a/data/preferences.glade b/data/preferences.glade index 902adcd1..bc03ca74 100644 --- a/data/preferences.glade +++ b/data/preferences.glade @@ -7,11 +7,6 @@ - - - Default - - @@ -23,6 +18,97 @@ + + + + + + + + System Default + + + Click to focus + + + Follow mouse pointer + + + + + + + + + + + Normal + + + Hidden + + + Maximised + + + Fullscreen + + + + + + + + + + + Top + + + Bottom + + + Left + + + Right + + + + + + + + + + + Block + + + Underline + + + I-Beam + + + + + + + + + + + Exit the terminal + + + Restart the command + + + Hold the terminal open + + + 5 normal @@ -44,6 +130,14 @@ True + FocusListStore + 0 + + + + 0 + + 1 @@ -78,6 +172,7 @@ True True + adjustment1 0 left @@ -134,6 +229,14 @@ True + WindowStateListStore + 0 + + + + 0 + + 1 @@ -188,6 +291,14 @@ True + TabPositionListStore + 0 + + + + 0 + + 1 @@ -213,13 +324,82 @@ True - + True - True - ProfilesListStore + vertical + + + True + True + ProfilesListStore + adjustment2 + adjustment3 + False + 0 + + + Profile + True + + + True + + + + 0 + + + + + + + 0 + + + + + True + + + + + + gtk-add + True + True + True + True + + + + False + False + 1 + + + + + gtk-remove + True + True + True + True + + + + False + False + 2 + + + + + False + 1 + + - False 0 @@ -234,43 +414,6 @@ 12 vertical 6 - - - True - 12 - - - True - 0 - 4 - _Profile name: - True - center - profile-name-entry - - - False - False - 0 - - - - - True - True - - - - 1 - - - - - False - False - 0 - - _Use the system fixed width font @@ -283,7 +426,7 @@ False False - 1 + 0 @@ -330,7 +473,7 @@ False False - 2 + 1 @@ -345,7 +488,7 @@ False False - 3 + 2 @@ -360,7 +503,7 @@ False False - 4 + 3 @@ -375,7 +518,7 @@ False False - 5 + 4 @@ -398,6 +541,14 @@ True + CursorShapeListStore + 0 + + + + 0 + + False @@ -407,7 +558,7 @@ False - 6 + 5 @@ -442,7 +593,7 @@ False - 7 + 6 @@ -487,71 +638,29 @@ True 12 - + True - vertical - 6 + 12 - + True - 12 - - - True - 0 - Initial _title: - True - center - title-entry - - - False - False - 0 - - - - - True - True - - - - 1 - - + 0 + Initial _title: + True + center + title-entry + False + False 0 - + True - 12 - - - True - 0 - When terminal commands set their o_wn titles: - True - center - title-mode-combobox - - - False - False - 0 - - - - - True - - - 1 - - + True + 1 @@ -710,6 +819,14 @@ True + ChildExitedListStore + 0 + + + + 0 + + 1 @@ -1955,4 +2072,24 @@ button2 + + -1 + -1 + 5 + 1 + 2 + 2 + + + 100 + 1 + 10 + 10 + + + 100 + 1 + 10 + 10 + diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 1f3e1d3d..7efc8f3d 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -35,7 +35,7 @@ Classes relating to configuration 'click' >>> config['focus'] 'click' ->>> config['fullscreen'].__class__.__name__ +>>> config['geometry_hinting'].__class__.__name__ 'bool' >>> plugintest = {} >>> plugintest['foo'] = 'bar' @@ -64,10 +64,8 @@ DEFAULTS = { 'enable_real_transparency' : True, 'handle_size' : -1, 'geometry_hinting' : True, - 'fullscreen' : False, + 'window_state' : 'normal', 'borderless' : False, - 'maximise' : False, - 'hidden' : False, 'tab_position' : 'top', 'close_button_on_tab' : True, 'hide_tabbar' : False, @@ -220,6 +218,12 @@ class Config(object): if self.base.profiles.has_key(profile): del(self.base.profiles[profile]) + def rename_profile(self, profile, newname): + """Rename a profile""" + if self.base.profiles.has_key(profile): + self.base.profiles[newname] = self.base.profiles[profile] + del(self.base.profiles[profile]) + def list_profiles(self): """List all configured profiles""" return(self.base.profiles.keys()) diff --git a/terminatorlib/optionparse.py b/terminatorlib/optionparse.py index 0e33a0a8..6e512fa2 100755 --- a/terminatorlib/optionparse.py +++ b/terminatorlib/optionparse.py @@ -92,16 +92,16 @@ WM_WINDOW_ROLE property on the window') sys.exit(1) if options.maximise: - configobj['maximise'] = True + configobj['window_state'] = 'maximise' if options.fullscreen: - configobj['fullscreen'] = True + configobj['window_state'] = 'fullscreen' if options.borderless: configobj['borderless'] = True if options.hidden: - configobj['hidden'] = True + configobj['window_state'] = 'hidden' # FIXME: Map all the other bits of options to configobj diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py old mode 100644 new mode 100755 index e16e60e6..25d43c03 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -10,13 +10,6 @@ from version import APP_NAME, APP_VERSION from translation import _ class PrefsEditor: - # lists of which settings to put in which tabs - appearance = ['titlebars', 'zoomedtitlebar', 'allow_bold', 'audible_bell', 'visible_bell', 'urgent_bell', 'force_no_bell', 'background_darkness', 'background_type', 'background_image', 'cursor_blink', 'cursor_shape', 'font', 'scrollbar_position', 'scroll_background', 'use_system_font', 'use_theme_colors', 'enable_real_transparency'] - colours = ['foreground_color','background_color', 'cursor_color', 'palette', 'title_tx_txt_color', 'title_tx_bg_color', 'title_rx_txt_color', 'title_rx_bg_color', 'title_ia_txt_color', 'title_ia_bg_color'] - behaviour = ['backspace_binding', 'delete_binding', 'emulation', 'scroll_on_keystroke', 'scroll_on_output', 'alternate_screen_scroll', 'scrollback_lines', 'focus', 'focus_on_close', 'exit_action', 'word_chars', 'mouse_autohide', 'use_custom_command', 'custom_command', 'http_proxy', 'encoding'] - globals = ['fullscreen', 'maximise', 'borderless', 'handle_size', 'cycle_term_tab', 'close_button_on_tab', 'tab_position', 'copy_on_selection', 'try_posix_regexp'] - - # metadata about the settings data = {'titlebars': ['Show titlebars', 'This places a bar above each terminal which displays its title.'], 'zoomedtitlebar': ['Show titlebar when zoomed', 'This places an informative bar above a zoomed terminal to indicate there are hidden terminals.'], 'allow_bold': ['Allow bold text', 'Controls whether or not the terminals will honour requests for bold text'], @@ -50,49 +43,135 @@ class PrefsEditor: 'title_ia_bg_color': ['Inactive Title Background Color', ''], } - # dictionary for results after setting - widgets = {} - - # combobox settings - scrollbar_position = ['left', 'right', 'disabled'] - backspace_del_binding = ['ascii-del', 'control-h', 'escape-sequence', 'delete-sequence'] - focus = ['click', 'sloppy', 'mouse'] - background_type = ['solid', 'image', 'transparent'] - tab_position = ['top', 'bottom', 'left', 'right'] - tab_position_gtk = {'top' : gtk.POS_TOP, 'bottom' : gtk.POS_BOTTOM, 'left' : gtk.POS_LEFT, 'right' : gtk.POS_RIGHT} - cursor_shape = ['block', 'ibeam', 'underline'] - config = None def __init__ (self, term): self.config = config.Config() self.term = term - self.window = gtk.Window () - self.notebook = gtk.Notebook() - self.box = gtk.VBox() - - self.butbox = gtk.HButtonBox() - self.applybut = gtk.Button(stock=gtk.STOCK_APPLY) - self.applybut.connect ("clicked", self.apply) - self.cancelbut = gtk.Button(stock=gtk.STOCK_CLOSE) - self.cancelbut.connect ("clicked", self.cancel) - - self.box.pack_start(self.notebook, False, False) - self.box.pack_end(self.butbox, False, False) - - self.butbox.set_layout(gtk.BUTTONBOX_END) - self.butbox.pack_start(self.applybut, False, False) - self.butbox.pack_start(self.cancelbut, False, False) - self.window.add (self.box) - - self.notebook.append_page (self.auto_add (gtk.Table (), self.globals), gtk.Label ("Global Settings")) - self.notebook.append_page (self.prepare_keybindings (), gtk.Label ("Keybindings")) - self.notebook.append_page (self.auto_add (gtk.Table (), self.appearance), gtk.Label ("Appearance")) - self.notebook.append_page (self.auto_add (gtk.Table (), self.colours), gtk.Label ("Colours")) - self.notebook.append_page (self.auto_add (gtk.Table (), self.behaviour), gtk.Label ("Behaviour")) + self.builder = gtk.Builder() + try: + gladefile = open('/home/cmsj/code/personal/terminator/branches/epicrefactor/data/preferences.glade', 'r') + gladedata = gladefile.read() + except Exception, ex: + print "Failed to find preferences.glade" + print ex + return + self.builder.add_from_string(gladedata) + self.window = self.builder.get_object('prefswin') + self.set_values() + self.builder.connect_signals(self) self.window.show_all() + def set_values(self): + """Update the preferences window with all the configuration from + Config()""" + guiget = self.builder.get_object + + print "SETTING VALUES" + + ## Global tab + + # Mouse focus + # default is 'system', which == 0 + focus = self.config['focus'] + active = 0 + if focus == 'click': + active = 1 + elif focus == 'sloppy': + active = 2 + widget = guiget('focuscombo') + widget.set_active(active) + + # Terminal separator size + # default is -1 + termsepsize = self.config['handle_size'] + widget = guiget('handlesize') + widget.set_value(termsepsize) + + # Window geometry hints + # default is True + geomhint = self.config['geometry_hinting'] + widget = guiget('wingeomcheck') + widget.set_active(geomhint) + + # Window state + # default is not maximised, not fullscreen + option = self.config['window_state'] + if option == 'hidden': + active = 1 + elif option == 'maximise': + active = 2 + elif option == 'fullscreen': + active = 3 + else: + active = 0 + widget = guiget('winstatecombo') + widget.set_active(active) + + # Window borders + # default is True + widget = guiget('winbordercheck') + widget.set_active(not self.config['borderless']) + + # Tab bar position + # default is top + option = self.config['tab_position'] + widget = guiget('tabposcombo') + if option == 'bottom': + active = 1 + elif option == 'left': + active = 2 + elif option == 'right': + active = 3 + else: + active = 0 + widget.set_active(active) + + ## Profile tab + + # Populate the profile list + widget = guiget('profilelist') + liststore = widget.get_model() + profiles = self.config.list_profiles() + self.profileiters = {} + for profile in profiles: + self.profileiters[profile] = liststore.append([profile]) + + selection = widget.get_selection() + selection.connect('changed', self.on_profile_selection_changed) + selection.select_iter(self.profileiters['default']) + print "VALUES ALL SET" + + def on_profile_selection_changed(self, selection): + """A different profile was selected""" + (listmodel, rowiter) = selection.get_selected() + profile = listmodel.get_value(rowiter, 0) + self.update_profile_values(profile) + + def update_profile_values(self, profile): + """Update the profile values for a given profile""" + self.config.set_profile(profile) + guiget = self.builder.get_object + + print "setting profile %s" % profile + widget = guiget('allow-bold-checkbutton') + widget.set_active(self.config['allow_bold']) + + def on_profile_name_edited(self, cell, path, newtext): + """Update a profile name""" + oldname = cell.get_property('text') + if oldname == newtext: + return + dbg('PrefsEditor::on_profile_name_edited: Changing %s to %s' % + (oldname, newtext)) + self.config.rename_profile(oldname, newtext) + + widget = self.builder.get_object('profilelist') + model = widget.get_model() + iter = model.get_iter(path) + model.set_value(iter, 0, newtext) + def source_get_type (self, key): if config.DEFAULTS['global_config'].has_key (key): print "found %s in global_config" % key @@ -117,262 +196,8 @@ class PrefsEditor: label_text = key.replace ('_', ' ').capitalize () return label_text - def auto_add (self, table, list): - row = 0 - for key in list: - table.resize (row + 1, 2) - label = gtk.Label (self.source_get_keyname (key)) - wrapperbox = gtk.HBox() - wrapperbox.pack_start(label, False, True) - - type = self.source_get_type (key) - value = self.source_get_value (key) - widget = None - - if key == 'font': - widget = gtk.FontButton(value) - elif key == 'scrollback_lines': - # estimated byte size per line according to g-t: - # sizeof(void *) + sizeof(char *) + sizeof(int) + (80 * (sizeof(int32) + 4) - widget = gtk.SpinButton() - widget.set_digits(0) - widget.set_increments(100, 1000) - widget.set_range(0, 100000) - widget.set_value(value) - elif key == 'scrollbar_position': - if value == 'hidden': - value = 'disabled' - widget = gtk.combo_box_new_text() - for item in self.scrollbar_position: - widget.append_text (item) - if value in self.scrollbar_position: - widget.set_active (self.scrollbar_position.index(value)) - elif key == 'backspace_binding': - widget = gtk.combo_box_new_text() - for item in self.backspace_del_binding: - widget.append_text (item) - if value in self.backspace_del_binding: - widget.set_active (self.backspace_del_binding.index(value)) - elif key == 'delete_binding': - widget = gtk.combo_box_new_text() - for item in self.backspace_del_binding: - widget.append_text (item) - if value in self.backspace_del_binding: - widget.set_active (self.backspace_del_binding.index(value)) - elif key == 'focus': - widget = gtk.combo_box_new_text() - for item in self.focus: - widget.append_text (item) - if value in self.focus: - widget.set_active (self.focus.index(value)) - elif key == 'background_type': - widget = gtk.combo_box_new_text() - for item in self.background_type: - widget.append_text (item) - if value in self.background_type: - widget.set_active (self.background_type.index(value)) - elif key == 'background_darkness': - widget = gtk.HScale () - widget.set_digits (1) - widget.set_draw_value (True) - widget.set_value_pos (gtk.POS_LEFT) - widget.set_range (0, 1) - widget.set_value (value) - elif key == 'handle_size': - widget = gtk.HScale () - widget.set_digits (0) - widget.set_draw_value (True) - widget.set_value_pos (gtk.POS_LEFT) - widget.set_range (-1, 5) - widget.set_value (value) - elif key == 'foreground_color': - widget = gtk.ColorButton (gtk.gdk.color_parse (value)) - elif key == 'background_color': - widget = gtk.ColorButton (gtk.gdk.color_parse (value)) - elif key == 'cursor_color': - if not value: - value = self.source_get_value ('foreground_color') - widget = gtk.ColorButton (gtk.gdk.color_parse (value)) - elif key == 'palette': - colours = value.split (':') - numcolours = len (colours) - widget = gtk.Table (2, numcolours / 2) - x = 0 - y = 0 - for thing in colours: - if x == numcolours / 2: - y += 1 - x = 0 - widget.attach (gtk.ColorButton (gtk.gdk.color_parse (thing)), x, x + 1, y, y + 1) - x += 1 - elif key in ['title_tx_txt_color', 'title_tx_bg_color', 'title_rx_txt_color', 'title_rx_bg_color', 'title_ia_txt_color', 'title_ia_bg_color']: - widget = gtk.ColorButton (gtk.gdk.color_parse (value)) - elif key == 'background_image': - widget = gtk.FileChooserButton('Select a File') - filter = gtk.FileFilter() - filter.add_mime_type ('image/*') - widget.add_filter (filter) - widget.set_local_only (True) - if value: - widget.set_filename (value) - elif key == 'tab_position': - widget = gtk.combo_box_new_text() - for item in self.tab_position: - widget.append_text (item) - if value in self.tab_position: - widget.set_active (self.tab_position.index(value)) - elif key == 'cursor_shape': - widget = gtk.combo_box_new_text() - for item in self.cursor_shape: - widget.append_text (item) - if value in self.cursor_shape: - widget.set_active (self.cursor_shape.index (value)) - else: - print "doing %s automagically" % key - if type == "bool": - widget = gtk.CheckButton () - widget.set_active (value == 'True') - elif type in ["str", "int", "float"]: - widget = gtk.Entry () - widget.set_text (str(value)) - elif type == "list": - continue - else: - err("Unknown type: %s for key: %s" % (type, key)) - continue - - if hasattr(widget, 'set_tooltip_text') and self.data.has_key (key): - widget.set_tooltip_text (self.data[key][1]) - - widget.set_name(key) - self.widgets[key] = widget - table.attach (wrapperbox, 0, 1, row, row + 1, gtk.EXPAND|gtk.FILL, gtk.FILL) - table.attach (widget, 1, 2, row, row + 1, gtk.EXPAND|gtk.FILL, gtk.FILL) - row += 1 - - return (table) - def apply (self, data): - values = {} - for page in [self.appearance, self.behaviour, self.globals, self.colours]: - for property in page: - widget = self.widgets[property] - - if isinstance (widget, gtk.SpinButton): - value = widget.get_value () - elif isinstance (widget, gtk.Entry): - value = widget.get_text() - elif isinstance (widget, gtk.CheckButton): - value = widget.get_active() - elif isinstance (widget, gtk.ComboBox): - if widget.name == 'scrollbar_position': - bucket = self.scrollbar_position - elif widget.name == 'backspace_binding' or widget.name == 'delete_binding': - bucket = self.backspace_del_binding - elif widget.name == 'focus': - bucket = self.focus - elif widget.name == 'background_type': - bucket = self.background_type - elif widget.name == 'tab_position': - bucket = self.tab_position - elif widget.name == 'cursor_shape': - bucket = self.cursor_shape - else: - err("Unknown bucket type for %s" % widget.name) - continue - - value = bucket[widget.get_active()] - elif isinstance (widget, gtk.FontButton): - value = widget.get_font_name() - elif isinstance (widget, gtk.HScale): - value = widget.get_value() - if widget.get_digits() == 0: - value = int(value) - elif isinstance (widget, gtk.ColorButton): - value = widget.get_color().to_string() - elif isinstance (widget, gtk.FileChooserButton): - value = widget.get_filename() - elif widget.get_name() == 'palette': - value = '' - valuebits = [] - children = widget.get_children() - children.reverse() - for child in children: - valuebits.append (child.get_color().to_string()) - value = ':'.join (valuebits) - else: - value = None - err("skipping unknown property: %s" % property) - - values[property] = value - - has_changed = False - changed = [] - for source in self.config.sources: - if isinstance (source, TerminatorConfValuestoreRC): - for property in values: - try: - if self.source_get_value(property) != values[property]: - dbg("%s changed from %s to %s" % (property, self.source_get_value(property), values[property])) - source.values[property] = values[property] - has_changed = True - changed.append(property) - except KeyError: - pass - if has_changed: - for changer in changed: - if changer == "fullscreen": - self.term.fullscreen_absolute(values[changer]) - elif changer == "maximise": - if values[changer]: - self.term.maximize() - else: - self.term.unmaximize() - elif changer == "borderless": - self.term.window.set_decorated (not values[changer]) - elif changer == "handle_size": - self.term.set_handle_size(values[changer]) - gtk.rc_reset_styles(gtk.settings_get_default()) - elif changer == "tab_position": - notebook = self.term.window.get_child() - new_pos = self.tab_position_gtk[values[changer]] - angle = 0 - if isinstance (notebook, gtk.Notebook): - notebook.set_tab_pos(new_pos) - for i in xrange(0,notebook.get_n_pages()): - notebook.get_tab_label(notebook.get_nth_page(i)).update_angle() - pass - elif changer == "close_button_on_tab": - notebook = self.term.window.get_child() - if isinstance (notebook, gtk.Notebook): - for i in xrange(0,notebook.get_n_pages()): - notebook.get_tab_label(notebook.get_nth_page(i)).update_closebut() - # FIXME: which others? cycle_term_tab, copy_on_selection, try_posix_regexp - - self.term.reconfigure_vtes() - - # Check for changed keybindings - changed_keybindings = [] - for row in self.liststore: - accel = gtk.accelerator_name (row[2], row[3]) - value = self.term.conf.keybindings[row[0]] - if isinstance (value, tuple): - value = value[0] - keyval = 0 - mask = 0 - if value is not None and value != "None": - try: - (keyval, mask) = self.tkbobj._parsebinding(value) - except KeymapError: - pass - if (row[2], row[3]) != (keyval, mask): - changed_keybindings.append ((row[0], accel)) - dbg("%s changed from %s to %s" % (row[0], self.term.conf.keybindings[row[0]], accel)) - - newbindings = self.term.conf.keybindings - for binding in changed_keybindings: - newbindings[binding[0]] = binding[1] - self.term.keybindings.configure (newbindings) + pass def cancel (self, data): self.window.destroy() @@ -438,3 +263,10 @@ class PrefsEditor: def cleared (self, obj, path): iter = self.liststore.get_iter_from_string(path) self.liststore.set(iter, 2, 0, 3, 0) + +if __name__ == '__main__': + import terminal + term = terminal.Terminal() + foo = PrefsEditor(term) + + gtk.main() diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 10a3a297..431a5a36 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -87,14 +87,14 @@ class Window(Container, gtk.Window): def apply_config(self): """Apply various configuration options""" - self.set_fullscreen(self.config['fullscreen']) - self.set_maximised(self.config['maximise']) + self.set_fullscreen(self.config['window_state'] == 'fullscreen') + self.set_maximised(self.config['window_state'] == 'maximise') self.set_borderless(self.config['borderless']) self.set_real_transparency() if self.hidebound: - self.set_hidden(self.config['hidden']) + self.set_hidden(self.config['window_state'] == 'hidden') else: - self.set_iconified(self.config['hidden']) + self.set_iconified(self.config['window_state'] == 'hidden') def apply_icon(self): """Set the window icon""" From c14e955310d83b5ae1ae925816977abb3bd19ae7 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 8 Jan 2010 01:04:06 +0000 Subject: [PATCH 252/331] profilelist is a list, not a callable object --- terminatorlib/terminal_popup_menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/terminal_popup_menu.py b/terminatorlib/terminal_popup_menu.py index 4ba22529..0866b652 100755 --- a/terminatorlib/terminal_popup_menu.py +++ b/terminatorlib/terminal_popup_menu.py @@ -149,7 +149,7 @@ class TerminalPopupMenu(object): group = None - for profile in profilelist(): + for profile in profilelist: item = gtk.RadioMenuItem(group, profile.capitalize()) item.connect('activate', terminal.set_profile, profile) if profile == current: From 1924c2f81a24272a6aaf91413811195d437ded3a Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 8 Jan 2010 08:39:21 +0000 Subject: [PATCH 253/331] fix up the General profile tab in the prefs editor and hook all of its elements up to code --- data/preferences.glade | 27 ++++++++++++++++----- terminatorlib/prefseditor.py | 46 +++++++++++++++++++++++++++++------- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/data/preferences.glade b/data/preferences.glade index bc03ca74..20be3ccd 100644 --- a/data/preferences.glade +++ b/data/preferences.glade @@ -492,8 +492,8 @@ - - Show _menubar by default in new terminals + + Visual terminal _bell True True False @@ -507,8 +507,8 @@ - - Terminal _bell + + Audible terminal _bell True True False @@ -521,6 +521,21 @@ 4 + + + WM_URGENT terminal _bell + True + True + False + True + True + + + False + False + 5 + + True @@ -558,7 +573,7 @@ False - 5 + 6 @@ -593,7 +608,7 @@ False - 6 + 7 diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 25d43c03..b949b69e 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -143,20 +143,50 @@ class PrefsEditor: selection.select_iter(self.profileiters['default']) print "VALUES ALL SET" - def on_profile_selection_changed(self, selection): - """A different profile was selected""" - (listmodel, rowiter) = selection.get_selected() - profile = listmodel.get_value(rowiter, 0) - self.update_profile_values(profile) - - def update_profile_values(self, profile): + def set_profile_values(self, profile): """Update the profile values for a given profile""" self.config.set_profile(profile) guiget = self.builder.get_object - print "setting profile %s" % profile + dbg('PrefsEditor::set_profile_values: Setting profile %s' % profile) + + ## General tab + # Use system font + widget = guiget('system-font-checkbutton') + widget.set_active(self.config['use_system_font']) + # Font selector + widget = guiget('font-selector') + widget.set_font_name(self.config['font']) + # Allow bold text widget = guiget('allow-bold-checkbutton') widget.set_active(self.config['allow_bold']) + # Visual terminal bell + widget = guiget('visual-bell-checkbutton') + widget.set_active(self.config['visible_bell']) + # Audible terminal bell + widget = guiget('audible-bell-checkbutton') + widget.set_active(self.config['audible_bell']) + # WM_URGENT terminal bell + widget = guiget('urgent-bell-checkbutton') + widget.set_active(self.config['urgent_bell']) + # Cursor shape + widget = guiget('cursor-shape-combobox') + if self.config['cursor_shape'] == 'underline': + active = 1 + elif self.config['cursor_shape'] == 'ibeam': + active = 2 + else: + active = 0 + widget.set_active(active) + # Word chars + widget = guiget('word-chars-entry') + widget.set_text(self.config['word_chars']) + + def on_profile_selection_changed(self, selection): + """A different profile was selected""" + (listmodel, rowiter) = selection.get_selected() + profile = listmodel.get_value(rowiter, 0) + self.set_profile_values(profile) def on_profile_name_edited(self, cell, path, newtext): """Update a profile name""" From 594ee479125d89e01bc4eae958decd775d5c018d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 8 Jan 2010 23:51:45 +0000 Subject: [PATCH 254/331] Complete the Terminal Command profile tab. Teach the colour scheme picker how to exist in the config and how to be controlled by the prefs UI --- data/preferences.glade | 119 +++++++++++++---------------------- terminatorlib/config.py | 1 + terminatorlib/prefseditor.py | 116 ++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 74 deletions(-) diff --git a/data/preferences.glade b/data/preferences.glade index 20be3ccd..0e862b32 100644 --- a/data/preferences.glade +++ b/data/preferences.glade @@ -109,6 +109,35 @@ + + + + + + + + Black on light yellow + + + Black on white + + + Grey on black + + + Green on black + + + White on black + + + Orange on black + + + Custom + + + 5 normal @@ -625,76 +654,12 @@ - + True - 12 - vertical - 18 - - - True - vertical - 6 - - - True - 0 - <b>Title</b> - True - - - False - False - 0 - - - - - True - 12 - - - True - 12 - - - True - 0 - Initial _title: - True - center - title-entry - - - False - False - 0 - - - - - True - True - - - - 1 - - - - - - - False - 1 - - - - - False - 0 - - + 6 + 6 + 6 + 6 True @@ -867,10 +832,6 @@ - - False - 1 - @@ -880,7 +841,7 @@ True - Title and Command + Terminal Command True center @@ -948,6 +909,7 @@ False True True + 2 @@ -1050,6 +1012,15 @@ True + ColourSchemeListStore + 2 + + + + + 0 + + 1 diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 7efc8f3d..faaba1a6 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -137,6 +137,7 @@ DEFAULTS = { 'background_image' : '', 'backspace_binding' : 'ascii-del', 'delete_binding' : 'delete-sequence', + 'color_scheme' : 'grey_on_black', 'cursor_blink' : True, 'cursor_shape' : 'block', 'cursor_color' : '', diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index b949b69e..282958fa 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -10,6 +10,14 @@ from version import APP_NAME, APP_VERSION from translation import _ class PrefsEditor: + colorschemevalues = {'black_on_yellow': 0, + 'black_on_white': 1, + 'grey_on_black': 2, + 'green_on_black': 3, + 'white_on_black': 4, + 'orange_on_black': 5, + 'custom': 6} + data = {'titlebars': ['Show titlebars', 'This places a bar above each terminal which displays its title.'], 'zoomedtitlebar': ['Show titlebar when zoomed', 'This places an informative bar above a zoomed terminal to indicate there are hidden terminals.'], 'allow_bold': ['Allow bold text', 'Controls whether or not the terminals will honour requests for bold text'], @@ -182,6 +190,54 @@ class PrefsEditor: widget = guiget('word-chars-entry') widget.set_text(self.config['word_chars']) + ## Teminal Command tab + # Login shell + widget = guiget('login-shell-checkbutton') + widget.set_active(self.config['login_shell']) + # Login records + widget = guiget('update-records-checkbutton') + widget.set_active(self.config['update_records']) + # Use Custom command + widget = guiget('use-custom-command-checkbutton') + widget.set_active(self.config['use_custom_command']) + # Custom Command + widget = guiget('custom-command-entry') + widget.set_text(self.config['custom_command']) + # Exit action + widget = guiget('exit-action-combobox') + if self.config['exit_action'] == 'restart': + widget.set_active(1) + elif self.config['exit_action'] == 'hold': + widget.set_active(2) + else: + # Default is to close the terminal + widget.set_active(0) + + ## Colors tab + # Use system colors + widget = guiget('use-theme-colors-checkbutton') + widget.set_active(self.config['use_theme_colors']) + # Colorscheme + widget = guiget('color-scheme-combobox') + scheme = self.config['color_scheme'] + if scheme not in self.colorschemevalues: + scheme = 'grey_on_black' + widget.set_active(self.colorschemevalues[scheme]) + # Foreground color + widget = guiget('foreground-colorpicker') + widget.set_color(gtk.gdk.Color(self.config['foreground_color'])) + if scheme == 'custom': + widget.set_sensitive(True) + else: + widget.set_sensitive(False) + # Background color + widget = guiget('background-colorpicker') + widget.set_color(gtk.gdk.Color(self.config['background_color'])) + if scheme == 'custom': + widget.set_sensitive(True) + else: + widget.set_sensitive(False) + def on_profile_selection_changed(self, selection): """A different profile was selected""" (listmodel, rowiter) = selection.get_selected() @@ -202,6 +258,66 @@ class PrefsEditor: iter = model.get_iter(path) model.set_value(iter, 0, newtext) + def on_color_scheme_combobox_changed(self, widget): + """Update the fore/background colour pickers""" + value = None + guiget = self.builder.get_object + active = widget.get_active() + for key in self.colorschemevalues.keys(): + if self.colorschemevalues[key] == active: + value = key + + fore = guiget('foreground-colorpicker') + back = guiget('background-colorpicker') + if value == 'custom': + fore.set_sensitive(True) + back.set_sensitive(True) + else: + fore.set_sensitive(False) + back.set_sensitive(False) + + forecol = None + backcol = None + if value == 'grey_on_black': + forecol = '#AAAAAA' + backcol = '#000000' + elif value == 'black_on_yellow': + forecol = '#000000' + backcol = '#FFFFDD' + elif value == 'black_on_white': + forecol = '#000000' + backcol = '#FFFFFF' + elif value == 'white_on_black': + forecol = '#FFFFFF' + backcol = '#000000' + elif value == 'green_on_black': + forecol = '#00FF00' + backcol = '#000000' + elif value == 'orange_on_black': + forecol = '#E53C00' + backcol = '#000000' + + if forecol is not None: + fore.set_color(gtk.gdk.Color(forecol)) + if backcol is not None: + back.set_color(gtk.gdk.Color(backcol)) + + def on_use_theme_colors_checkbutton_toggled(self, widget): + """Update colour pickers""" + guiget = self.builder.get_object + active = widget.get_active() + + scheme = guiget('color-scheme-combobox') + fore = guiget('foreground-colorpicker') + back = guiget('background-colorpicker') + + if active: + for widget in [scheme, fore, back]: + widget.set_sensitive(False) + else: + scheme.set_sensitive(True) + self.on_color_scheme_combobox_changed(scheme) + def source_get_type (self, key): if config.DEFAULTS['global_config'].has_key (key): print "found %s in global_config" % key From dd45d517253b1a0f07c4e1a93391c97d8123298d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 9 Jan 2010 00:47:49 +0000 Subject: [PATCH 255/331] fix up all of the profile apart from the palette --- data/preferences.glade | 105 +++++++++++++++++++++++++++++++++-- terminatorlib/prefseditor.py | 83 +++++++++++++++++++++++++++ 2 files changed, 182 insertions(+), 6 deletions(-) diff --git a/data/preferences.glade b/data/preferences.glade index 0e862b32..520dd134 100644 --- a/data/preferences.glade +++ b/data/preferences.glade @@ -138,6 +138,57 @@ + + + + + + + + On the left side + + + On the right side + + + Disabled + + + + + + + + + + + Control-H + + + ASCII DEL + + + Escape sequence + + + + + + + + + + + Control-H + + + ASCII DEL + + + Escape sequence + + + 5 normal @@ -1431,6 +1482,7 @@ True True True + False @@ -1452,6 +1504,7 @@ True True solid-radiobutton + False @@ -1541,6 +1594,7 @@ True True solid-radiobutton + False @@ -1590,8 +1644,7 @@ True True delayed - 2 - False + background_darkness_scale bottom @@ -1689,6 +1742,14 @@ True + ScrollbarPositionListStore + 0 + + + + 0 + + False @@ -1711,6 +1772,7 @@ True True + ScrollbackAdjustmend 1 True @@ -1860,6 +1922,14 @@ True + BackspaceKeyListStore + 1 + + + + 0 + + 1 @@ -1871,6 +1941,14 @@ True + DeleteKeyListStore + 2 + + + + 0 + + 1 @@ -1898,6 +1976,7 @@ True True True + False @@ -2017,12 +2096,13 @@ True end - + gtk-cancel True True True True + False @@ -2031,12 +2111,13 @@ - + gtk-ok True True True True + False @@ -2054,8 +2135,8 @@ - button1 - button2 + cancelbutton + okbutton @@ -2078,4 +2159,16 @@ 10 10 + + 1 + 0.10000000000000001 + 0.10000000000000001 + 1 + + + 10000000 + 1 + 10 + 10 + diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 282958fa..6b256151 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -237,6 +237,89 @@ class PrefsEditor: widget.set_sensitive(True) else: widget.set_sensitive(False) + # FIXME: Do the Palette schemes and pickers + + ## Background tab + # Radio values + self.update_background_tab() + # Background image file + if self.config['background_image'] != '': + widget = guiget('background-image-filechooser') + widget.set_filename(self.config['background_image']) + # Background image scrolls + widget = guiget('scroll-background-checkbutton') + widget.set_active(self.config['scroll_background']) + # Background shading + widget = guiget('background_darkness_scale') + widget.set_value(self.config['background_darkness']) + + if self.config['background_type'] == 'solid': + guiget('solid-radiobutton').set_active(True) + elif self.config['background_type'] == 'image': + guiget('image-radiobutton').set_active(True) + elif self.config['background_type'] == 'transparent': + guiget('trans-radiobutton').set_active(True) + + ## Scrolling tab + # Scrollbar position + widget = guiget('scrollbar-position-combobox') + value = self.config['scrollbar_position'] + if value == 'left': + widget.set_active(0) + elif value == 'disabled': + widget.set_active(2) + else: + widget.set_active(1) + # Scrollback lines + widget = guiget('scrollback-lines-spinbutton') + widget.set_value(self.config['scrollback_lines']) + # Scroll on outut + widget = guiget('scroll-on-output-checkbutton') + widget.set_active(self.config['scroll_on_output']) + # Scroll on keystroke + widget = guiget('scroll-on-keystroke-checkbutton') + widget.set_active(self.config['scroll_on_keystroke']) + + ## Compatibility tab + # Backspace key + widget = guiget('backspace-binding-combobox') + value = self.config['backspace_binding'] + if value == 'control-h': + widget.set_active(0) + elif value == 'escape-sequence': + widget.set_active(2) + else: + widget.set_active(1) + + def on_background_type_toggled(self, widget): + """The background type was toggled""" + self.update_background_tab() + + def update_background_tab(self): + """Update the background tab""" + guiget = self.builder.get_object + + # Background type + backtype = None + solidwidget = guiget('solid-radiobutton') + imagewidget = guiget('image-radiobutton') + transwidget = guiget('transparent-radiobutton') + if transwidget.get_active() == True: + backtype = 'trans' + elif imagewidget.get_active() == True: + backtype = 'image' + else: + backtype = 'solid' + if backtype == 'image': + guiget('background-image-filechooser').set_sensitive(True) + guiget('scroll-background-checkbutton').set_sensitive(True) + else: + guiget('background-image-filechooser').set_sensitive(False) + guiget('scroll-background-checkbutton').set_sensitive(False) + if backtype == 'trans': + guiget('darken-background-scale').set_sensitive(True) + else: + guiget('darken-background-scale').set_sensitive(False) def on_profile_selection_changed(self, selection): """A different profile was selected""" From 56aab5708d0963934e3cfc11fc5c961302bea9d0 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 9 Jan 2010 01:10:04 +0000 Subject: [PATCH 256/331] Add model for keybindings --- data/preferences.glade | 398 +++++++++++++++++++++++------------------ 1 file changed, 219 insertions(+), 179 deletions(-) diff --git a/data/preferences.glade b/data/preferences.glade index 520dd134..709f99e5 100644 --- a/data/preferences.glade +++ b/data/preferences.glade @@ -203,191 +203,198 @@ True True - + True - 6 - 2 + 6 - + True - FocusListStore - 0 + 6 + 2 + 6 - - - 0 - + + True + FocusListStore + 0 + + + + 0 + + + + + 1 + 2 + + GTK_EXPAND + + + + + True + Mouse focus + + + + + + + + + True + Terminal separator size + + + 1 + 2 + + + + + + + True + True + adjustment1 + 0 + left + + + 1 + 2 + 1 + 2 + GTK_EXPAND + 20 + + + + + True + Window geometry hints + + + 2 + 3 + + + + + + + True + True + False + True + True + + + 1 + 2 + 2 + 3 + + GTK_EXPAND + + + + + True + Window state + + + 3 + 4 + + + + + + + True + WindowStateListStore + 0 + + + + 0 + + + + + 1 + 2 + 3 + 4 + + GTK_EXPAND + + + + + True + Window borders + + + 4 + 5 + + + + + + + True + True + False + True + True + + + 1 + 2 + 4 + 5 + + GTK_EXPAND + + + + + True + Tab position + + + 5 + 6 + + + + + + + True + TabPositionListStore + 0 + + + + 0 + + + + + 1 + 2 + 5 + 6 + + GTK_EXPAND + - - 1 - 2 - - GTK_EXPAND - - - - - True - Mouse focus - - - - - - - - - True - Terminal separator size - - - 1 - 2 - - - - - - - True - True - adjustment1 - 0 - left - - - 1 - 2 - 1 - 2 - GTK_EXPAND - 20 - - - - - True - Window geometry hints - - - 2 - 3 - - - - - - - True - True - False - True - True - - - 1 - 2 - 2 - 3 - - GTK_EXPAND - - - - - True - Window state - - - 3 - 4 - - - - - - - True - WindowStateListStore - 0 - - - - 0 - - - - - 1 - 2 - 3 - 4 - - GTK_EXPAND - - - - - True - Window borders - - - 4 - 5 - - - - - - - True - True - False - True - True - - - 1 - 2 - 4 - 5 - - GTK_EXPAND - - - - - True - Tab position - - - 5 - 6 - - - - - - - True - TabPositionListStore - 0 - - - - 0 - - - - - 1 - 2 - 5 - 6 - - GTK_EXPAND - @@ -2052,6 +2059,39 @@ True True KeybindingsListStore + + + Name + + + + 0 + + + + + + + Action + + + + 1 + + + + + + + Keybinding + + + + 2 + + + + 3 From f273c198c94c7d639e89d7113362ced51dd6407a Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 9 Jan 2010 11:52:30 +0000 Subject: [PATCH 257/331] Finish off the Compatibility tab and the other UI toggles in the profile tabs. --- data/preferences.glade | 15 +++--------- terminatorlib/prefseditor.py | 45 +++++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/data/preferences.glade b/data/preferences.glade index 709f99e5..a1b80c06 100644 --- a/data/preferences.glade +++ b/data/preferences.glade @@ -509,6 +509,7 @@ False True True + False @@ -787,6 +788,7 @@ False True True + False @@ -899,7 +901,7 @@ True - Terminal Command + Command True center @@ -1802,17 +1804,6 @@ 1 - - - True - (about 120kB) - - - False - False - 2 - - 1 diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 6b256151..37a57ff3 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -162,6 +162,7 @@ class PrefsEditor: # Use system font widget = guiget('system-font-checkbutton') widget.set_active(self.config['use_system_font']) + self.on_system_font_checkbutton_toggled(widget) # Font selector widget = guiget('font-selector') widget.set_font_name(self.config['font']) @@ -190,7 +191,7 @@ class PrefsEditor: widget = guiget('word-chars-entry') widget.set_text(self.config['word_chars']) - ## Teminal Command tab + ## Command tab # Login shell widget = guiget('login-shell-checkbutton') widget.set_active(self.config['login_shell']) @@ -200,6 +201,7 @@ class PrefsEditor: # Use Custom command widget = guiget('use-custom-command-checkbutton') widget.set_active(self.config['use_custom_command']) + self.on_use_custom_command_checkbutton_toggled(widget) # Custom Command widget = guiget('custom-command-entry') widget.set_text(self.config['custom_command']) @@ -290,6 +292,47 @@ class PrefsEditor: widget.set_active(2) else: widget.set_active(1) + # Delete key + widget = guiget('delete-binding-combobox') + value = self.config['delete_binding'] + if value == 'control-h': + widget.set_active(0) + elif value == 'escape-sequence': + widget.set_active(2) + else: + widget.set_active(1) + + def on_use_custom_command_checkbutton_toggled(self, checkbox): + """Toggling the use_custom_command checkbox needs to alter the + sensitivity of the custom_command entrybox""" + guiget = self.builder.get_object + + widget = guiget('custom-command-entry') + if checkbox.get_active() == True: + widget.set_sensitive(True) + else: + widget.set_sensitive(False) + + def on_system_font_checkbutton_toggled(self, checkbox): + """Toggling the use_system_font checkbox needs to alter the + sensitivity of the font selector""" + guiget = self.builder.get_object + + widget = guiget('font-selector') + if checkbox.get_active() == True: + widget.set_sensitive(False) + else: + widget.set_sensitive(True) + + def on_reset_compatibility_clicked(self, widget): + """Reset the confusing and annoying backspace/delete options to the + safest values""" + guiget = self.builder.get_object + + widget = guiget('backspace-binding-combobox') + widget.set_active(1) + widget = guiget('delete-binding-combobox') + widget.set_active(2) def on_background_type_toggled(self, widget): """The background type was toggled""" From 5b7e5b431adfd5543c466298ad6539e3c05ff1de Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 9 Jan 2010 12:13:12 +0000 Subject: [PATCH 258/331] failed rearrangement --- data/preferences.glade | 118 ++++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/data/preferences.glade b/data/preferences.glade index a1b80c06..b2acfd48 100644 --- a/data/preferences.glade +++ b/data/preferences.glade @@ -25,7 +25,7 @@ - System Default + GNOME Default Click to focus @@ -212,64 +212,6 @@ 6 2 6 - - - True - FocusListStore - 0 - - - - 0 - - - - - 1 - 2 - - GTK_EXPAND - - - - - True - Mouse focus - - - - - - - - - True - Terminal separator size - - - 1 - 2 - - - - - - - True - True - adjustment1 - 0 - left - - - 1 - 2 - 1 - 2 - GTK_EXPAND - 20 - - True @@ -394,6 +336,64 @@ GTK_EXPAND + + + True + Mouse focus + + + + + + + + + True + FocusListStore + 0 + + + + 0 + + + + + 1 + 2 + + GTK_EXPAND + + + + + True + Terminal separator size + + + 1 + 2 + + + + + + + True + True + adjustment1 + 0 + left + + + 1 + 2 + 1 + 2 + GTK_EXPAND + 20 + + From 92e7eb1dff1995b5f2c7356971a778b675a07c1a Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 9 Jan 2010 15:51:56 +0000 Subject: [PATCH 259/331] support adding and removing profiles, and returning their values to Config() when switching between them --- terminatorlib/config.py | 11 ++ terminatorlib/prefseditor.py | 212 ++++++++++++++++++++++++++++++++++- 2 files changed, 222 insertions(+), 1 deletion(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index faaba1a6..b0e6f87d 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -214,6 +214,10 @@ class Config(object): dbg('Config::set_profile: %s does not exist, creating' % profile) self.base.profiles[profile] = copy(DEFAULTS['profiles']['default']) + def add_profile(self, profile): + """Add a new profile""" + return(self.base.add_profile(profile)) + def del_profile(self, profile): """Delete a profile""" if self.base.profiles.has_key(profile): @@ -466,6 +470,13 @@ class ConfigBase(Borg): """Set a whole tree for a plugin""" self.plugins[plugin] = tree + def add_profile(self, profile): + """Add a new profile""" + if profile in self.profiles: + return(False) + self.profiles[profile] = copy(DEFAULTS['profiles']['default']) + return(True) + if __name__ == '__main__': import sys import doctest diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 37a57ff3..df142dbb 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -10,6 +10,7 @@ from version import APP_NAME, APP_VERSION from translation import _ class PrefsEditor: + previous_selection = None colorschemevalues = {'black_on_yellow': 0, 'black_on_white': 1, 'grey_on_black': 2, @@ -247,7 +248,9 @@ class PrefsEditor: # Background image file if self.config['background_image'] != '': widget = guiget('background-image-filechooser') - widget.set_filename(self.config['background_image']) + if self.config['background_image'] is not None and \ + self.config['background_image'] != '': + widget.set_filename(self.config['background_image']) # Background image scrolls widget = guiget('scroll-background-checkbutton') widget.set_active(self.config['scroll_background']) @@ -302,6 +305,197 @@ class PrefsEditor: else: widget.set_active(1) + def store_profile_values(self, profile): + """Pull out all the settings before switching profile""" + guiget = self.builder.get_object + + ## General tab + # Use system font + widget = guiget('system-font-checkbutton') + self.config['use_system_font'] = widget.get_active() + # Font + widget = guiget('font-selector') + self.config['font'] = widget.get_font_name() + # Allow bold + widget = guiget('allow-bold-checkbutton') + self.config['allow_bold'] = widget.get_active() + # Visual Bell + widget = guiget('visual-bell-checkbutton') + self.config['visible_bell'] = widget.get_active() + # Audible Bell + widget = guiget('audible-bell-checkbutton') + self.config['audible_bell'] = widget.get_active() + # Urgent Bell + widget = guiget('urgent-bell-checkbutton') + self.config['urgent_bell'] = widget.get_active() + # Cursor Shape + widget = guiget('cursor-shape-combobox') + selected = widget.get_active() + if selected == 0: + value = 'block' + elif selected == 1: + value = 'underline' + elif selected == 2: + value = 'ibeam' + self.config['cursor_shape'] = value + # Word chars + widget = guiget('word-chars-entry') + self.config['word_chars'] = widget.get_text() + + ## Command tab + # Login shell + widget = guiget('login-shell-checkbutton') + self.config['login_shell'] = widget.get_active() + # Update records + widget = guiget('update-records-checkbutton') + self.config['update_records'] = widget.get_active() + # Use custom command + widget = guiget('use-custom-command-checkbutton') + self.config['use_custom_command'] = widget.get_active() + # Custom command + widget = guiget('custom-command-entry') + self.config['custom_command'] = widget.get_text() + # Exit action + widget = guiget('exit-action-combobox') + selected = widget.get_active() + if selected == 0: + value = 'close' + elif selected == 1: + value = 'restart' + elif selected == 2: + value = 'hold' + self.config['exit_action'] = value + + ## Colours tab + # Use system colours + widget = guiget('use-theme-colors-checkbutton') + self.config['use_theme_colors'] = widget.get_active() + # Colour scheme + widget = guiget('color-scheme-combobox') + selected = widget.get_active() + if selected == 0: + value = 'black_on_yellow' + elif selected == 1: + value = 'black_on_white' + elif selected == 2: + value = 'grey_on_black' + elif selected == 3: + value = 'green_on_black' + elif selected == 4: + value = 'white_on_black' + elif selected == 5: + value = 'orange_on_black' + elif selected == 6: + value = 'custom' + self.config['color_scheme'] = value + # Foreground colour + widget = guiget('foreground-colorpicker') + self.config['foreground_color'] = widget.get_color().to_string() + # Background colour + widget = guiget('background-colorpicker') + self.config['background_color'] = widget.get_color().to_string() + # FIXME: Do the palette schemes and palette + + ## Background tab + # Background type + widget = guiget('solid-radiobutton') + if widget.get_active() == True: + value = 'solid' + widget = guiget('image-radiobutton') + if widget.get_active() == True: + value = 'image' + widget = guiget('transparent-radiobutton') + if widget.get_active() == True: + value = 'transparent' + # Background image + widget = guiget('background-image-filechooser') + self.config['background_image'] = widget.get_filename() + # Background scrolls + widget = guiget('scroll-background-checkbutton') + self.config['scroll_background'] = widget.get_active() + # Background darkness + widget = guiget('darken-background-scale') + self.config['background_darkness'] = widget.get_value() + + ## Scrolling tab + # Scrollbar + widget = guiget('scrollbar-position-combobox') + selected = widget.get_active() + if selected == 0: + value = 'left' + elif selected == 1: + value = 'right' + elif selected ==2: + value = 'hidden' + self.config['scrollbar_position'] = value + # Scrollback lines + widget = guiget('scrollback-lines-spinbutton') + self.config['scrollback_lines'] = widget.get_value() + # Scroll on output + widget = guiget('scroll-on-output-checkbutton') + self.config['scroll_on_output'] = widget.get_active() + # Scroll on keystroke + widget = guiget('scroll-on-keystroke-checkbutton') + self.config['scroll_on_keystroke'] = widget.get_active() + + ## Compatibility tab + # Backspace key + widget = guiget('backspace-binding-combobox') + selected = widget.get_active() + if selected == 0: + value = 'control-h' + elif selected == 1: + value = 'ascii-del' + elif selected == 2: + value =='escape-sequence' + self.config['backspace_binding'] = value + # Delete key + widget = guiget('delete-binding-combobox') + selected = widget.get_active() + if selected == 0: + value = 'control-h' + elif selected == 1: + value = 'ascii-del' + elif selected == 2: + value = 'escape-sequence' + self.config['delete_binding'] = value + + def on_profileaddbutton_clicked(self, button): + """Add a new profile to the list""" + guiget = self.builder.get_object + + treeview = guiget('profilelist') + model = treeview.get_model() + values = [ r[0] for r in model ] + + newprofile = _('New Profile') + if newprofile in values: + i = 0 + while newprofile in values: + i = i + 1 + newprofile = '%s %d' % (_('New Profile'), i) + + if self.config.add_profile(newprofile): + model.append([newprofile]) + + def on_profileremovebutton_clicked(self, button): + """Remove a profile from the list""" + guiget = self.builder.get_object + + treeview = guiget('profilelist') + selection = treeview.get_selection() + (model, rowiter) = selection.get_selected() + profile = model.get_value(rowiter, 0) + + if profile == 'default': + # We shouldn't let people delete this profile + return + + self.config.del_profile(profile) + model.remove(rowiter) + + selection.select_iter(model.get_iter_first()) + def on_use_custom_command_checkbutton_toggled(self, checkbox): """Toggling the use_custom_command checkbox needs to alter the sensitivity of the custom_command entrybox""" @@ -366,9 +560,25 @@ class PrefsEditor: def on_profile_selection_changed(self, selection): """A different profile was selected""" + if self.previous_selection is not None: + self.store_profile_values(self.previous_selection) + (listmodel, rowiter) = selection.get_selected() + if not rowiter: + # Something is wrong, just jump to the first item in the list + treeview = selection.get_tree_view() + liststore = treeview.get_model() + selection.select_iter(liststore.get_iter_first()) + return profile = listmodel.get_value(rowiter, 0) self.set_profile_values(profile) + self.previous_selection = profile + + widget = self.builder.get_object('profileremovebutton') + if profile == 'default': + widget.set_sensitive(False) + else: + widget.set_sensitive(True) def on_profile_name_edited(self, cell, path, newtext): """Update a profile name""" From 80e0926a058a7ea123ea384bf23962637f4c0442 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 9 Jan 2010 17:42:59 +0000 Subject: [PATCH 260/331] remove some old data, fix up a few defaults that didn't quite match up with reality, and implement the final bits of functionality to make the OK button DTRT --- terminatorlib/config.py | 8 +- terminatorlib/prefseditor.py | 142 +++++++++++++++++++++-------------- 2 files changed, 91 insertions(+), 59 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index b0e6f87d..aff5090b 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -131,19 +131,19 @@ DEFAULTS = { 'audible_bell' : False, 'visible_bell' : True, 'urgent_bell' : False, - 'background_color' : '#000000', + 'background_color' : '#000000000000', 'background_darkness' : 0.5, 'background_type' : 'solid', - 'background_image' : '', + 'background_image' : None, 'backspace_binding' : 'ascii-del', - 'delete_binding' : 'delete-sequence', + 'delete_binding' : 'escape-sequence', 'color_scheme' : 'grey_on_black', 'cursor_blink' : True, 'cursor_shape' : 'block', 'cursor_color' : '', 'emulation' : 'xterm', 'font' : 'Mono 10', - 'foreground_color' : '#AAAAAA', + 'foreground_color' : '#aaaaaaaaaaaa', 'scrollbar_position' : "right", 'scroll_background' : True, 'scroll_on_keystroke' : True, diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index df142dbb..0651019c 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -10,6 +10,9 @@ from version import APP_NAME, APP_VERSION from translation import _ class PrefsEditor: + config = None + window = None + builder = None previous_selection = None colorschemevalues = {'black_on_yellow': 0, 'black_on_white': 1, @@ -19,41 +22,6 @@ class PrefsEditor: 'orange_on_black': 5, 'custom': 6} - data = {'titlebars': ['Show titlebars', 'This places a bar above each terminal which displays its title.'], - 'zoomedtitlebar': ['Show titlebar when zoomed', 'This places an informative bar above a zoomed terminal to indicate there are hidden terminals.'], - 'allow_bold': ['Allow bold text', 'Controls whether or not the terminals will honour requests for bold text'], - 'silent_bell': ['', 'When enabled, bell events will generate a flash. When disabled, they will generate a beep'], - 'background_darkness': ['', 'Controls how much the background will be tinted'], - 'scroll_background': ['', 'When enabled the background image will scroll with the text'], - 'force_no_bell': ['', 'Disable both the visual and audible bells'], - 'tab_position': ['', 'Controls the placement of the tab bar'], - 'use_theme_colors': ['', 'Take the foreground and background colours from the current GTK theme'], - 'enable_real_transparency': ['', 'If you are running a composited desktop (e.g. compiz), enabling this option will enable "true" transpraency'], - 'handle_size': ['', 'This controls the size of the border between terminals. Values 0 to 5 are in pixels, while -1 means the value will be decided by your normal GTK theme.'], - 'close_window': ['Quit Terminator', ''], - 'toggle_zoom': ['Toggle maximise terminal', ''], - 'scaled_zoom': ['Toggle zoomed terminal', ''], - 'prev_tab': ['Previous tab', ''], - 'split_vert': ['Split vertically', ''], - 'split_horiz': ['Split horizontally', ''], - 'go_prev': ['Focus previous terminal', ''], - 'go_next': ['Focus next terminal', ''], - 'close_term': ['Close terminal', ''], - 'new_root_tab': ['New root tab', ''], - 'zoom_normal': ['Zoom reset', ''], - 'reset': ['Reset terminal state', ''], - 'reset_clear': ['Reset and clear terminal', ''], - 'hide_window': ['Toggle visibility of the window', ''], - 'title_tx_txt_color': ['Tx Title Foreground Color', ''], - 'title_tx_bg_color': ['Tx Title Background Color', ''], - 'title_rx_txt_color': ['Rx Title Foreground Color', ''], - 'title_rx_bg_color': ['Rx Title Background Color', ''], - 'title_ia_txt_color': ['Inactive Title Foreground Color', ''], - 'title_ia_bg_color': ['Inactive Title Background Color', ''], - } - - config = None - def __init__ (self, term): self.config = config.Config() self.term = term @@ -72,17 +40,25 @@ class PrefsEditor: self.builder.connect_signals(self) self.window.show_all() + def on_cancelbutton_clicked(self, button): + """Close the window""" + self.window.destroy() + del(self) + + def on_okbutton_clicked(self, button): + """Save the config""" + self.store_values() + self.config.save() + self.window.destroy() + del(self) + def set_values(self): """Update the preferences window with all the configuration from Config()""" guiget = self.builder.get_object - print "SETTING VALUES" - ## Global tab - # Mouse focus - # default is 'system', which == 0 focus = self.config['focus'] active = 0 if focus == 'click': @@ -91,21 +67,15 @@ class PrefsEditor: active = 2 widget = guiget('focuscombo') widget.set_active(active) - # Terminal separator size - # default is -1 termsepsize = self.config['handle_size'] widget = guiget('handlesize') widget.set_value(termsepsize) - # Window geometry hints - # default is True geomhint = self.config['geometry_hinting'] widget = guiget('wingeomcheck') widget.set_active(geomhint) - # Window state - # default is not maximised, not fullscreen option = self.config['window_state'] if option == 'hidden': active = 1 @@ -117,14 +87,10 @@ class PrefsEditor: active = 0 widget = guiget('winstatecombo') widget.set_active(active) - # Window borders - # default is True widget = guiget('winbordercheck') widget.set_active(not self.config['borderless']) - # Tab bar position - # default is top option = self.config['tab_position'] widget = guiget('tabposcombo') if option == 'bottom': @@ -138,7 +104,6 @@ class PrefsEditor: widget.set_active(active) ## Profile tab - # Populate the profile list widget = guiget('profilelist') liststore = widget.get_model() @@ -146,12 +111,79 @@ class PrefsEditor: self.profileiters = {} for profile in profiles: self.profileiters[profile] = liststore.append([profile]) - selection = widget.get_selection() selection.connect('changed', self.on_profile_selection_changed) selection.select_iter(self.profileiters['default']) - print "VALUES ALL SET" + ## Layouts tab + # FIXME: Implement this + + ## Keybindings tab + # FIXME: Implement this + + ## Plugins tab + # FIXME: Implement this + + def store_values(self): + """Store the values from the GUI back into Config()""" + guiget = self.builder.get_object + + ## Global tab + # Focus + widget = guiget('focuscombo') + selected = widget.get_active() + if selected == 0: + value = 'system' + elif selected == 1: + value = 'click' + elif selected == 2: + value = 'mouse' + self.config['focus'] = value + # Handle size + widget = guiget('handlesize') + self.config['handle_size'] = widget.get_value() + # Window geometry + widget = guiget('wingeomcheck') + self.config['geometry_hinting'] = widget.get_active() + # Window state + widget = guiget('winstatecombo') + selected = widget.get_active() + if selected == 0: + value = 'normal' + elif selected == 1: + value = 'hidden' + elif selected == 2: + value = 'maximise' + elif selected == 3: + value = 'fullscreen' + self.config['window_state'] = value + # Window borders + widget = guiget('winbordercheck') + self.config['borderless'] = not widget.get_active() + # Tab position + widget = guiget('tabposcombo') + selected = widget.get_active() + if selected == 0: + value = 'top' + elif selected == 1: + value = 'bottom' + elif selected == 2: + value = 'left' + elif selected == 3: + value = 'right' + self.config['tab_position'] = value + + ## Profile tab + self.store_profile_values(self.previous_selection) + + ## Layouts tab + # FIXME: Implement this + + ## Keybindings tab + # FIXME: Implement this + + ## Plugins tab + # FIXME: Implement this def set_profile_values(self, profile): """Update the profile values for a given profile""" self.config.set_profile(profile) @@ -300,10 +332,10 @@ class PrefsEditor: value = self.config['delete_binding'] if value == 'control-h': widget.set_active(0) - elif value == 'escape-sequence': - widget.set_active(2) - else: + elif value == 'ascii-del': widget.set_active(1) + else: + widget.set_active(2) def store_profile_values(self, profile): """Pull out all the settings before switching profile""" From 2b7be76004435ee08d84ee616a05d6e4002d73b1 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 9 Jan 2010 17:49:14 +0000 Subject: [PATCH 261/331] Move our preferences glade file to terminatorlib/ so we can use the built-in knowledge of where that is on-disk to find the .glade file --- {data => terminatorlib}/preferences.glade | 0 terminatorlib/prefseditor.py | 6 +++++- 2 files changed, 5 insertions(+), 1 deletion(-) rename {data => terminatorlib}/preferences.glade (100%) diff --git a/data/preferences.glade b/terminatorlib/preferences.glade similarity index 100% rename from data/preferences.glade rename to terminatorlib/preferences.glade diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 0651019c..d099de73 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -1,5 +1,6 @@ #!/usr/bin/python +import os import gtk import gobject @@ -27,7 +28,10 @@ class PrefsEditor: self.term = term self.builder = gtk.Builder() try: - gladefile = open('/home/cmsj/code/personal/terminator/branches/epicrefactor/data/preferences.glade', 'r') + # 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, 'preferences.glade') + gladefile = open(librarypath, 'r') gladedata = gladefile.read() except Exception, ex: print "Failed to find preferences.glade" From 2cc76de6099ec80c095fe2005efaa95a7397610c Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sat, 9 Jan 2010 22:27:56 +0000 Subject: [PATCH 262/331] gtkscale insists on getting float values --- terminatorlib/prefseditor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index d099de73..3790d6b2 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -74,7 +74,7 @@ class PrefsEditor: # Terminal separator size termsepsize = self.config['handle_size'] widget = guiget('handlesize') - widget.set_value(termsepsize) + widget.set_value(float(termsepsize)) # Window geometry hints geomhint = self.config['geometry_hinting'] widget = guiget('wingeomcheck') @@ -292,7 +292,7 @@ class PrefsEditor: widget.set_active(self.config['scroll_background']) # Background shading widget = guiget('background_darkness_scale') - widget.set_value(self.config['background_darkness']) + widget.set_value(float(self.config['background_darkness'])) if self.config['background_type'] == 'solid': guiget('solid-radiobutton').set_active(True) From 180ccbbf032781fa9a7fc49a1eb2f548a0e0372c Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 10 Jan 2010 00:20:24 +0000 Subject: [PATCH 263/331] Hook up the keybindings tab in the preferences editor. Loads and saves --- terminatorlib/config.py | 81 ++++++++++++++-------------- terminatorlib/preferences.glade | 96 ++++++++++++++++++++------------- terminatorlib/prefseditor.py | 56 ++++++++++--------- 3 files changed, 130 insertions(+), 103 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index aff5090b..b051b763 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -74,54 +74,53 @@ DEFAULTS = { 'disabled_plugins' : ['TestPlugin', 'CustomCommandsMenu'], }, 'keybindings': { - 'zoom_in' : 'plus', - 'zoom_out' : 'minus', - 'zoom_normal' : '0', - 'new_root_tab' : 'T', - 'new_tab' : 'T', - 'go_next' : ('N','Tab'), - 'go_prev' : ('P','Tab'), + 'zoom_in' : 'plus', + 'zoom_out' : 'minus', + 'zoom_normal' : '0', + 'new_tab' : 't', + 'go_next' : 'n', # FIXME: Define ctrl-tab + 'go_prev' : 'p', #FIXME: ctrl-shift-tab 'go_up' : 'Up', 'go_down' : 'Down', 'go_left' : 'Left', 'go_right' : 'Right', - 'split_horiz' : 'O', - 'split_vert' : 'E', - 'close_term' : 'W', - 'copy' : 'C', - 'paste' : 'V', - 'toggle_scrollbar' : 'S', - 'search' : 'F', - 'close_window' : 'Q', - 'resize_up' : 'Up', - 'resize_down' : 'Down', - 'resize_left' : 'Left', - 'resize_right' : 'Right', - 'move_tab_right' : 'Page_Down', - 'move_tab_left' : 'Page_Up', - 'toggle_zoom' : 'X', - 'scaled_zoom' : 'Z', - 'next_tab' : 'Page_Down', - 'prev_tab' : 'Page_Up', - 'switch_to_tab_1' : None, - 'switch_to_tab_2' : None, - 'switch_to_tab_3' : None, - 'switch_to_tab_4' : None, - 'switch_to_tab_5' : None, - 'switch_to_tab_6' : None, - 'switch_to_tab_7' : None, - 'switch_to_tab_8' : None, - 'switch_to_tab_9' : None, - 'switch_to_tab_10' : None, + 'split_horiz' : 'o', + 'split_vert' : 'e', + 'close_term' : 'w', + 'copy' : 'c', + 'paste' : 'v', + 'toggle_scrollbar' : 's', + 'search' : 'f', + 'close_window' : 'q', + 'resize_up' : 'Up', + 'resize_down' : 'Down', + 'resize_left' : 'Left', + 'resize_right' : 'Right', + 'move_tab_right' : 'Page_Down', + 'move_tab_left' : 'Page_Up', + 'toggle_zoom' : 'x', + 'scaled_zoom' : 'z', + 'next_tab' : 'Page_Down', + 'prev_tab' : 'Page_Up', + 'switch_to_tab_1' : '', + 'switch_to_tab_2' : '', + 'switch_to_tab_3' : '', + 'switch_to_tab_4' : '', + 'switch_to_tab_5' : '', + 'switch_to_tab_6' : '', + 'switch_to_tab_7' : '', + 'switch_to_tab_8' : '', + 'switch_to_tab_9' : '', + 'switch_to_tab_10' : '', 'full_screen' : 'F11', - 'reset' : 'R', - 'reset_clear' : 'G', - 'hide_window' : 'a', + 'reset' : 'r', + 'reset_clear' : 'g', + 'hide_window' : 'a', 'group_all' : 'g', - 'ungroup_all' : 'g', + 'ungroup_all' : 'g', 'group_tab' : 't', - 'ungroup_tab' : 'T', - 'new_window' : 'I', + 'ungroup_tab' : 't', + 'new_window' : 'i', }, 'profiles': { 'default': { diff --git a/terminatorlib/preferences.glade b/terminatorlib/preferences.glade index b2acfd48..8ae6d543 100644 --- a/terminatorlib/preferences.glade +++ b/terminatorlib/preferences.glade @@ -8,16 +8,6 @@ - - - - - - - - - - @@ -189,6 +179,18 @@ + + + + + + + + + + + + 5 normal @@ -2046,40 +2048,56 @@ - + True True - KeybindingsListStore + adjustment4 + never + automatic - - Name + + True + True + KeybindingsListStore + False + 0 - - - 0 - + + Name + + + + 0 + + + - - - - - Action - - - 1 - + + Action + + + + 1 + + + - - - - - Keybinding - - - 2 - + + Keybinding + + + True + + + + + 2 + 3 + + + @@ -2202,4 +2220,10 @@ 10 10 + + 100 + 1 + 10 + 10 + diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 3790d6b2..7e0c1b8e 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -6,12 +6,13 @@ import gobject from util import dbg, err import config -from keybindings import Keybindings +from keybindings import Keybindings, KeymapError from version import APP_NAME, APP_VERSION from translation import _ class PrefsEditor: config = None + keybindings = None window = None builder = None previous_selection = None @@ -27,6 +28,7 @@ class PrefsEditor: 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__) @@ -123,7 +125,20 @@ class PrefsEditor: # FIXME: Implement this ## Keybindings tab - # FIXME: Implement this + widget = guiget('keybindingtreeview') + liststore = widget.get_model() + liststore.set_sort_column_id(0, gtk.SORT_ASCENDING) + keybindings = self.config['keybindings'] + for keybinding in keybindings: + keyval = 0 + mask = 0 + value = keybindings[keybinding] + if value is not None and value != '': + try: + (keyval, mask) = self.keybindings._parsebinding(value) + except KeymapError: + pass + liststore.append([keybinding, 'UNDOCUMENTED', keyval, mask]) ## Plugins tab # FIXME: Implement this @@ -184,10 +199,15 @@ class PrefsEditor: # FIXME: Implement this ## Keybindings tab - # FIXME: Implement this + keybindings = self.config['keybindings'] + liststore = guiget('KeybindingsListStore') + for keybinding in liststore: + accel = gtk.accelerator_name(keybinding[2], keybinding[3]) + keybindings[keybinding[0]] = accel ## Plugins tab # FIXME: Implement this + def set_profile_values(self, profile): """Update the profile values for a given profile""" self.config.set_profile(profile) @@ -690,22 +710,14 @@ class PrefsEditor: scheme.set_sensitive(True) self.on_color_scheme_combobox_changed(scheme) - def source_get_type (self, key): - if config.DEFAULTS['global_config'].has_key (key): - print "found %s in global_config" % key - return config.DEFAULTS['global_config'][key].__class__.__name__ - elif config.DEFAULTS['profiles']['default'].has_key (key): - print "found %s in profiles" % key - return config.DEFAULTS['profiles']['default'][key].__class__.__name__ - elif config.DEFAULTS['keybindings'].has_key (key): - print "found %s in keybindings" % key - return config.DEFAULTS['keybindings'][key].__class__.__name__ - else: - print "could not find %s" % key - raise KeyError + def on_cellrenderer_accel_edited(self, liststore, path, key, mods, code): + """Handle an edited keybinding""" + celliter = liststore.get_iter_from_string(path) + liststore.set(celliter, 2, key, 3, mods) - def source_get_value (self, key): - return self.config[key] + def on_cellrenderer_accel_cleared(self, liststore, path): + celliter = liststore.get_iter_from_string(path) + liststore.set(celliter, 2, 0, 3, 0) def source_get_keyname (self, key): if self.data.has_key (key) and self.data[key][0] != '': @@ -714,14 +726,6 @@ class PrefsEditor: label_text = key.replace ('_', ' ').capitalize () return label_text - def apply (self, data): - pass - - def cancel (self, data): - self.window.destroy() - self.term.options = None - del(self) - def prepare_keybindings (self): self.liststore = gtk.ListStore (gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_UINT, gobject.TYPE_UINT, gobject.TYPE_BOOLEAN) self.liststore.set_sort_column_id (0, gtk.SORT_ASCENDING) From 371aebc06799af205345c2549ce95ada78ff53f8 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 10 Jan 2010 00:35:38 +0000 Subject: [PATCH 264/331] Add the keybinding descriptions --- terminatorlib/prefseditor.py | 53 +++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 7e0c1b8e..fd1c1726 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -10,6 +10,7 @@ from keybindings import Keybindings, KeymapError from version import APP_NAME, APP_VERSION from translation import _ +# FIXME: We need to check that we have represented all of Config() below class PrefsEditor: config = None keybindings = None @@ -24,6 +25,55 @@ class PrefsEditor: 'orange_on_black': 5, 'custom': 6} + keybindingnames = { 'zoom_in' : 'Increase font size', + 'zoom_out' : 'Decrease font size', + 'zoom_normal' : 'Restore original font size', + 'new_tab' : 'Create a new tab', + 'go_next' : 'Focus the next terminal', + 'go_prev' : 'Focus the previous terminal', + 'go_up' : 'Focus the terminal above', + 'go_down' : 'Focus the terminal below', + 'go_left' : 'Focus the terminal left', + 'go_right' : 'Focus the terminal right', + 'split_horiz' : 'Split horizontally', + 'split_vert' : 'Split vertically', + 'close_term' : 'Close terminal', + 'copy' : 'Copy selected text', + 'paste' : 'Paste clipboard', + 'toggle_scrollbar' : 'Show/Hide the scrollbar', + 'search' : 'Search terminal scrollback', + 'close_window' : 'Close window', + 'resize_up' : 'Resize the terminal up', + 'resize_down' : 'Resize the terminal down', + 'resize_left' : 'Resize the terminal left', + 'resize_right' : 'Resize the terminal right', + 'move_tab_right' : 'Move the tab right', + 'move_tab_left' : 'Move the tab left', + 'toggle_zoom' : 'Maximise terminal', + 'scaled_zoom' : 'Zoom terminal', + 'next_tab' : 'Switch to the next tab', + 'prev_tab' : 'Switch to the previous tab', + 'switch_to_tab_1' : 'Switch to the first tab', + 'switch_to_tab_2' : 'Switch to the second tab', + 'switch_to_tab_3' : 'Switch to the third tab', + 'switch_to_tab_4' : 'Switch to the fourth tab', + 'switch_to_tab_5' : 'Switch to the fifth tab', + 'switch_to_tab_6' : 'Switch to the sixth tab', + 'switch_to_tab_7' : 'Switch to the seventh tab', + 'switch_to_tab_8' : 'Switch to the eighth tab', + 'switch_to_tab_9' : 'Switch to the ninth tab', + 'switch_to_tab_10' : 'Switch to the tenth tab', + 'full_screen' : 'Toggle fullscreen', + 'reset' : 'Reset the terminal', + 'reset_clear' : 'Reset and clear the terminal', + 'hide_window' : 'Toggle window visibility', + 'group_all' : 'Group all terminals', + 'ungroup_all' : 'Ungroup all terminals', + 'group_tab' : 'Group terminals in tab', + 'ungroup_tab' : 'Ungroup terminals in tab', + 'new_window' : 'Create a new window', + } + def __init__ (self, term): self.config = config.Config() self.term = term @@ -138,7 +188,8 @@ class PrefsEditor: (keyval, mask) = self.keybindings._parsebinding(value) except KeymapError: pass - liststore.append([keybinding, 'UNDOCUMENTED', keyval, mask]) + liststore.append([keybinding, self.keybindingnames[keybinding], + keyval, mask]) ## Plugins tab # FIXME: Implement this From da7f1a6ea1a51e4ecdb597f6e3e047c3fcaba435 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 10 Jan 2010 00:53:27 +0000 Subject: [PATCH 265/331] renaming the current profile wasn't updating Config.profile with the new value. Now it is. --- terminatorlib/config.py | 2 ++ terminatorlib/prefseditor.py | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index b051b763..f5e22b1b 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -227,6 +227,8 @@ class Config(object): if self.base.profiles.has_key(profile): self.base.profiles[newname] = self.base.profiles[profile] del(self.base.profiles[profile]) + if profile == self.profile: + self.profile = newname def list_profiles(self): """List all configured profiles""" diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index fd1c1726..cdfbd249 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -577,7 +577,7 @@ class PrefsEditor: newprofile = _('New Profile') if newprofile in values: - i = 0 + i = 1 while newprofile in values: i = i + 1 newprofile = '%s %d' % (_('New Profile'), i) @@ -668,6 +668,8 @@ class PrefsEditor: def on_profile_selection_changed(self, selection): """A different profile was selected""" if self.previous_selection is not None: + dbg('PrefsEditor::on_profile_selection_changed: Storing: %s' % + self.previous_selection) self.store_profile_values(self.previous_selection) (listmodel, rowiter) = selection.get_selected() @@ -701,6 +703,9 @@ class PrefsEditor: iter = model.get_iter(path) model.set_value(iter, 0, newtext) + if oldname == self.previous_selection: + self.previous_selection = newtext + def on_color_scheme_combobox_changed(self, widget): """Update the fore/background colour pickers""" value = None From cc11effd3bca9b69b40a05f09265a66781673be2 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 10 Jan 2010 17:15:14 +0000 Subject: [PATCH 266/331] Store a reference to the signal handlers for config-dependent features, so they can be cancelled later --- terminatorlib/terminal.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 5c732567..6905c1ae 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -78,6 +78,8 @@ class Terminal(gtk.VBox): composite_support = None + cnxids = None + # FIXME: These two are old and should be updated to use cnxids[] confcnxid = None zoomcnxid = None @@ -95,6 +97,7 @@ class Terminal(gtk.VBox): self.connect('focus-in', self.terminator.focus_changed) self.matches = {} + self.cnxids = {} self.config = Config() @@ -262,8 +265,9 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) self.on_drag_data_received, self) if self.config['copy_on_selection']: - self.vte.connect('selection-changed', lambda widget: - self.vte.copy_clipboard()) + self.cnxids['copy_on_selection'] = self.vte.connect( + 'selection-changed', + lambda widget: self.vte.copy_clipboard()) if self.composite_support: self.vte.connect('composited-changed', @@ -276,9 +280,11 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) self.vte.connect('size-allocate', self.on_vte_size_allocate) if self.config['exit_action'] == 'restart': - self.vte.connect('child-exited', self.spawn_child) + self.cnxids['child-exited'] = self.vte.connect('child-exited', + self.spawn_child) elif self.config['exit_action'] in ('close', 'left'): - self.vte.connect('child-exited', lambda x: self.emit('close-term')) + self.cnxids['child-exited'] = self.vte.connect('child-exited', + lambda x: self.emit('close-term')) self.vte.add_events(gtk.gdk.ENTER_NOTIFY_MASK) self.vte.connect('enter_notify_event', From be413d3622e6950bc5f58d3560eb6e2b384d88f1 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 10 Jan 2010 17:17:31 +0000 Subject: [PATCH 267/331] Track a couple of pre-existing connection IDs in Terminal.cnxid --- terminatorlib/terminal.py | 15 ++++++--------- terminatorlib/window.py | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 6905c1ae..cc102f5f 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -79,9 +79,6 @@ class Terminal(gtk.VBox): composite_support = None cnxids = None - # FIXME: These two are old and should be updated to use cnxids[] - confcnxid = None - zoomcnxid = None def __init__(self): """Class initialiser""" @@ -290,7 +287,7 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) self.vte.connect('enter_notify_event', self.on_vte_notify_enter) - self.confcnxid = self.vte.connect_after('realize', self.reconfigure) + self.cnxids['conf'] = self.vte.connect_after('realize', self.reconfigure) def create_popup_group_menu(self, widget, event = None): """Pop up a menu for the group widget""" @@ -465,9 +462,9 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) def reconfigure(self, widget=None): """Reconfigure our settings""" dbg('Terminal::reconfigure') - if self.confcnxid: - self.vte.disconnect(self.confcnxid) - self.confcnxid = None + if self.cnxids.has_key('conf'): + self.vte.disconnect(self.cnxids['conf']) + self.cnxids.remove('conf') # FIXME: actually reconfigure our settings pass @@ -772,8 +769,8 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) def zoom_scale(self, widget, allocation, old_data): """Scale our font correctly based on how big we are not vs before""" - self.disconnect(self.zoomcnxid) - self.zoomcnxid = None + self.disconnect(self.cnxids['zoom']) + self.cnxids.remove('zoom') new_columns = self.vte.get_column_count() new_rows = self.vte.get_row_count() diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 431a5a36..55e7a6f1 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -275,7 +275,7 @@ class Window(Container, gtk.Window): self.set_property('term_zoomed', True) if font_scale: - widget.zoomcnxid = widget.connect('size-allocate', + widget.cnxids['zoom'] = widget.connect('size-allocate', widget.zoom_scale, self.zoom_data) widget.grab_focus() From b7fa984a52c9ede4b9a514cffcba2c7e12c6d078 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 10 Jan 2010 17:35:03 +0000 Subject: [PATCH 268/331] config dependent signal handling should be marshalled by Terminal::reconfigure --- terminatorlib/terminal.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index cc102f5f..35a0e125 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -276,13 +276,6 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) self.vte.connect('focus-in-event', self.on_vte_focus_in) self.vte.connect('size-allocate', self.on_vte_size_allocate) - if self.config['exit_action'] == 'restart': - self.cnxids['child-exited'] = self.vte.connect('child-exited', - self.spawn_child) - elif self.config['exit_action'] in ('close', 'left'): - self.cnxids['child-exited'] = self.vte.connect('child-exited', - lambda x: self.emit('close-term')) - self.vte.add_events(gtk.gdk.ENTER_NOTIFY_MASK) self.vte.connect('enter_notify_event', self.on_vte_notify_enter) @@ -464,10 +457,21 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) dbg('Terminal::reconfigure') if self.cnxids.has_key('conf'): self.vte.disconnect(self.cnxids['conf']) - self.cnxids.remove('conf') + del(self.cnxids['conf']) + + # Handle child command exiting + if self.cnxids.has_key('child-exited'): + self.vte.disconnect(self.cnxids['child-exited']) + del(self.cnxids['child-exited']) + + if self.config['exit_action'] == 'restart': + self.cnxids['child-exited'] = self.vte.connect('child-exited', + self.spawn_child) + elif self.config['exit_action'] in ('close', 'left'): + self.cnxids['child-exited'] = self.vte.connect('child-exited', + lambda x: self.emit('close-term')) # FIXME: actually reconfigure our settings - pass def get_window_title(self): """Return the window title""" @@ -770,7 +774,7 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) def zoom_scale(self, widget, allocation, old_data): """Scale our font correctly based on how big we are not vs before""" self.disconnect(self.cnxids['zoom']) - self.cnxids.remove('zoom') + del(self.cnxids['zoom']) new_columns = self.vte.get_column_count() new_rows = self.vte.get_row_count() From 27d0a133302ffae01fa3d0d6894ba36934c7c1a6 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 10 Jan 2010 17:36:55 +0000 Subject: [PATCH 269/331] print more useful errors when the config file format is invalid, and update the configspec generator to know that our undefined default keybindings are now '' instead of None --- terminatorlib/config.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index f5e22b1b..5377f187 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -52,7 +52,7 @@ import platform import os import sys from copy import copy -from configobj.configobj import ConfigObj +from configobj.configobj import ConfigObj, flatten_errors from configobj.validate import Validator from borg import Borg from factory import Factory @@ -317,10 +317,8 @@ class ConfigBase(Borg): section = {} for key in DEFAULTS['keybindings']: value = DEFAULTS['keybindings'][key] - if value is None: + if value is None or value == '': continue - elif isinstance(value, tuple): - value = value[0] section[key] = 'string(default=%s)' % value configspecdata['keybindings'] = section @@ -369,6 +367,11 @@ class ConfigBase(Borg): if result != True: err('ConfigBase::load: config format is not valid') + for (section_list, key, other) in flatten_errors(parser, result): + if key is not None: + print('[%s]: %s is invalid' % (','.join(section_list), key)) + else: + print ('[%s] missing' % ','.join(section_list)) for section_name in self.sections: dbg('ConfigBase::load: Processing section: %s' % section_name) From 976e3124f7e35a9daf91835618ede7ce920a6116 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 10 Jan 2010 23:47:15 +0000 Subject: [PATCH 270/331] two lines of debugging for each Config() lookup is mad, drop it to one --- terminatorlib/config.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 5377f187..036042e5 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -427,20 +427,19 @@ class ConfigBase(Borg): def get_item(self, key, profile='default', plugin=None): """Look up a configuration item""" - dbg('ConfigBase::get_item: %s:%s' % (profile, key)) if self.global_config.has_key(key): - dbg('ConfigBase::get_item: found in globals: %s' % - self.global_config[key]) + dbg('ConfigBase::get_item: %s found in globals: %s' % + (key, self.global_config[key])) return(self.global_config[key]) elif self.profiles[profile].has_key(key): - dbg('ConfigBase::get_item: found in profile %s (%s)' % ( - profile, self.profiles[profile][key])) + dbg('ConfigBase::get_item: %s found in profile %s: %s' % ( + key, profile, self.profiles[profile][key])) return(self.profiles[profile][key]) elif key == 'keybindings': return(self.keybindings) elif plugin is not None and self.plugins[plugin].has_key(key): - dbg('ConfigBase::get_item: found in plugin %s (%s)' % ( - plugin, self.plugins[plugin][key])) + dbg('ConfigBase::get_item: %s found in plugin %s: %s' % ( + key, plugin, self.plugins[plugin][key])) return(self.plugins[plugin][key]) else: raise KeyError('ConfigBase::get_item: unknown key %s' % key) From c5bd22719768fed50c94ee0854dcbb85a7ff4cdf Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 10 Jan 2010 23:47:36 +0000 Subject: [PATCH 271/331] Improve indication of what some debugging is --- terminatorlib/optionparse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/optionparse.py b/terminatorlib/optionparse.py index 6e512fa2..513cb740 100755 --- a/terminatorlib/optionparse.py +++ b/terminatorlib/optionparse.py @@ -106,6 +106,6 @@ WM_WINDOW_ROLE property on the window') # FIXME: Map all the other bits of options to configobj if util.DEBUG == True: - dbg('OptionParse::parse_options: options dump: %s' % options) + dbg('OptionParse::parse_options: command line options: %s' % options) return(options) From e5d52fee52670a788310da35dc98ec007069c3c4 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 10 Jan 2010 23:47:55 +0000 Subject: [PATCH 272/331] Implement most of Terminal::reconfigure --- terminatorlib/terminal.py | 121 +++++++++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 35a0e125..2b648586 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -75,6 +75,8 @@ class Terminal(gtk.VBox): matches = None config = None default_encoding = None + custom_encoding = None + custom_font_size = None composite_support = None @@ -461,6 +463,7 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) # Handle child command exiting if self.cnxids.has_key('child-exited'): + dbg('Terminal::reconfigure: Dropping child-exited handler') self.vte.disconnect(self.cnxids['child-exited']) del(self.cnxids['child-exited']) @@ -471,7 +474,121 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) self.cnxids['child-exited'] = self.vte.connect('child-exited', lambda x: self.emit('close-term')) - # FIXME: actually reconfigure our settings + self.vte.set_emulation(self.config['emulation']) + if self.custom_encoding != True: + self.vte.set_encoding(self.config['encoding']) + self.vte.set_word_chars(self.config['word_chars']) + self.vte.set_mouse_autohide(self.config['mouse_autohide']) + + backspace = self.config['backspace_binding'] + delete = self.config['delete_binding'] + + # FIXME: This doesn't seem like we ever obey control-h or + # escape-sequence + try: + if backspace == 'ascii-del': + backbind = vte.ERASE_ASCII_BACKSPACE + else: + backbind = vte.ERASE_AUTO_BACKSPACE + except AttributeError: + if backspace == 'ascii-del': + backbind = 2 + else: + backbind = 1 + + try: + if delete == 'escape-sequence': + delbind = vte.ERASE_DELETE_SEQUENCE + else: + delbind = vte.ERASE_AUTO + except AttributeError: + if delete == 'escape-sequence': + delbind = 3 + else: + delbind = 0 + + self.vte.set_backspace_binding(backbind) + self.vte.set_delete_binding(delbind) + + if not self.custom_font_size: + try: + self.vte.set_font(pango.FontDescription(self.config['font'])) + except: + pass + self.vte.set_allow_bold(self.config['allow_bold']) + if self.config['use_theme_colors']: + fgcolor = self.vte.get_style().text[gtk.STATE_NORMAL] + bgcolor = self.vte.get_style().base[gtk.STATE_NORMAL] + else: + fgcolor = gtk.gdk.color_parse(self.config['foreground_color']) + bgcolor = gtk.gdk.color_parse(self.config['background_color']) + + colors = self.config['palette'].split(':') + palette = [] + for color in colors: + if color: + palette.append(gtk.gdk.color_parse(color)) + self.vte.set_colors(fgcolor, bgcolor, palette) + if self.config['cursor_color'] != '': + self.vte.set_color_cursor(gtk.gdk.color_parse(self.config['cursor_color'])) + if hasattr(self.vte, 'set_cursor_shape'): + self.vte.set_cursor_shape(getattr(vte, 'CURSOR_SHAPE_' + + self.config['cursor_shape'].upper())) + + background_type = self.config['background_type'] + if background_type == 'image' and \ + self.config['background_image'] is not None and \ + self.config['background_image'] != '': + self.vte.set_background_image_file(self.config['background_image']) + self.vte.set_scroll_background(self.config['scroll_background']) + else: + self.vte.set_background_image_file('') + self.vte.set_scroll_background(False) + + opacity = 65536 + if background_type in ('image', 'transparent'): + self.vte.set_background_tint_color(gtk.gdk.color_parse(self.config['background_color'])) + self.vte.set_background_saturation(1 - + (self.config['background_darkness'])) + opacity = int(self.config['background_darkness'] * 65536) + else: + self.vte.set_background_saturation(1) + + if self.composite_support: + self.vte.set_opacity(opacity) + if self.config['background_type'] == 'transparent': + self.vte.set_background_transparent(True) + + self.vte.set_cursor_blinks(self.config['cursor_blink']) + + if self.config['force_no_bell'] == True: + self.vte.set_audible_bell(False) + self.vte.set_visible_bell(False) + if self.cnxids.has_key('urgent_bell'): + self.vte.disconnect(self.cnxids['urgent_bell']) + del(self.cnxids['urgent_bell']) + else: + self.vte.set_audible_bell(self.config['audible_bell']) + self.vte.set_visible_bell(self.config['visible_bell']) + if self.config['urgent_bell'] == True: + # FIXME: Hook up a signal handler here + pass + else: + if self.cnxids.has_key('urgent_bell'): + self.vte.disconnect(self.cnxids['urgent_bell']) + del(self.cnxids['urgent_bell']) + + self.vte.set_scrollback_lines(self.config['scrollback_lines']) + self.vte.set_scroll_on_keystroke(self.config['scroll_on_keystroke']) + self.vte.set_scroll_on_output(self.config['scroll_on_output']) + + # FIXME: Do subtle scrollbar_position stuff here + + if hasattr(self.vte, 'set_alternate_screen_scroll'): + self.vte.set_alternate_screen_scroll(self.config['alternate_screen_scroll']) + + self.titlebar.update() + self.vte.queue_draw() def get_window_title(self): """Return the window title""" @@ -948,11 +1065,13 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) pangodesc.set_size(fontsize) self.vte.set_font(pangodesc) + self.custom_font_size = fontsize def zoom_orig(self): """Restore original font size""" dbg("Terminal::zoom_orig: restoring font to: %s" % self.config['font']) self.vte.set_font(pango.FontDescription(self.config['font'])) + self.custom_font_size = None # There now begins a great list of keyboard event handlers # FIXME: Probably a bunch of these are wrong. TEST! From 295aeed2e289d670109ef8ec1e140f515e231378 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 10 Jan 2010 23:54:51 +0000 Subject: [PATCH 273/331] Unset the previous profile selection if we just removed it --- terminatorlib/prefseditor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index cdfbd249..ff589100 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -598,9 +598,9 @@ class PrefsEditor: # We shouldn't let people delete this profile return + self.previous_selection = None self.config.del_profile(profile) model.remove(rowiter) - selection.select_iter(model.get_iter_first()) def on_use_custom_command_checkbutton_toggled(self, checkbox): From 5ae299e6da2f34a409ad23f55ec25fbfd42bbd08 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Sun, 10 Jan 2010 23:58:05 +0000 Subject: [PATCH 274/331] Make the OK button in the preferences editor reconfigure all live terminals --- terminatorlib/prefseditor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index ff589100..2e199d70 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -9,6 +9,7 @@ import config from keybindings import Keybindings, KeymapError from version import APP_NAME, APP_VERSION from translation import _ +from newterminator import Terminator # FIXME: We need to check that we have represented all of Config() below class PrefsEditor: @@ -105,6 +106,8 @@ class PrefsEditor: """Save the config""" self.store_values() self.config.save() + terminator = Terminator() + terminator.reconfigure_terminals() self.window.destroy() del(self) From 47453ac40d40ebcea80a36888f2e5d129ebf0a75 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 11 Jan 2010 08:36:19 +0000 Subject: [PATCH 275/331] Ignore meliae --- .bzrignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.bzrignore b/.bzrignore index a8c77bec..03eef862 100644 --- a/.bzrignore +++ b/.bzrignore @@ -2,3 +2,4 @@ terminatorlib/*.pyc .project .pydevproject +terminatorlib/meliae From b8aac8874a06585af775b9df06232137cab335dd Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 11 Jan 2010 10:10:19 +0000 Subject: [PATCH 276/331] Disable DEBUG for now --- terminatorlib/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/util.py b/terminatorlib/util.py index 920bd997..971e8c55 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -22,7 +22,7 @@ import os import pwd # set this to true to enable debugging output -DEBUG = True +DEBUG = False def dbg(log = ""): """Print a message if debugging is enabled""" From 7a06c8631085562cc75205559d930c6c5840109e Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 11 Jan 2010 10:10:35 +0000 Subject: [PATCH 277/331] spinbutton gives us a float, but we should store an int --- terminatorlib/prefseditor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 2e199d70..5e8e3c7e 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -540,7 +540,7 @@ class PrefsEditor: self.config['scrollbar_position'] = value # Scrollback lines widget = guiget('scrollback-lines-spinbutton') - self.config['scrollback_lines'] = widget.get_value() + self.config['scrollback_lines'] = int(widget.get_value()) # Scroll on output widget = guiget('scroll-on-output-checkbutton') self.config['scroll_on_output'] = widget.get_active() From 75b5cd11cfdf6994d68fe1115a1eca772023e7e6 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 11 Jan 2010 10:28:47 +0000 Subject: [PATCH 278/331] Terminator::reconfigure_terminals should just be called reconfigure, and needs to start paying attention to global values --- terminatorlib/newterminator.py | 7 +++++-- terminatorlib/prefseditor.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py index bea1fab0..670b75c9 100755 --- a/terminatorlib/newterminator.py +++ b/terminatorlib/newterminator.py @@ -85,9 +85,12 @@ class Terminator(Borg): dbg('Terminator::deregister_terminal: %d terminals remain' % len(self.terminals)) - def reconfigure_terminals(self): - """Tell all terminals to update their configuration""" + def reconfigure(self): + """Update configuration for the whole application""" + # FIXME: Set handle_size here + + # Cause all the terminals to reconfigure for terminal in self.terminals: terminal.reconfigure() diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 5e8e3c7e..28282afc 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -107,7 +107,7 @@ class PrefsEditor: self.store_values() self.config.save() terminator = Terminator() - terminator.reconfigure_terminals() + terminator.reconfigure() self.window.destroy() del(self) From 949dc81574287347aea7ad0f07ca1fa3e0c2680f Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 11 Jan 2010 20:03:54 +0000 Subject: [PATCH 279/331] Clear up some import errors found by pyflakes --- terminatorlib/__init__.py | 2 -- terminatorlib/config.py | 2 -- terminatorlib/plugin.py | 1 - terminatorlib/prefseditor.py | 3 +-- terminatorlib/searchbar.py | 1 - terminatorlib/terminal.py | 4 ++-- 6 files changed, 3 insertions(+), 10 deletions(-) diff --git a/terminatorlib/__init__.py b/terminatorlib/__init__.py index bb2dee20..314dc331 100644 --- a/terminatorlib/__init__.py +++ b/terminatorlib/__init__.py @@ -16,5 +16,3 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA """Terminator by Chris Jones """ - -from version import APP_NAME, APP_VERSION diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 036042e5..9cd11261 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -55,7 +55,6 @@ from copy import copy from configobj.configobj import ConfigObj, flatten_errors from configobj.validate import Validator from borg import Borg -from factory import Factory from util import dbg, err, DEBUG, get_config_dir, dict_diff DEFAULTS = { @@ -481,7 +480,6 @@ class ConfigBase(Borg): return(True) if __name__ == '__main__': - import sys import doctest (failed, attempted) = doctest.testmod() print "%d/%d tests failed" % (failed, attempted) diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py index 99ab1798..1731f780 100755 --- a/terminatorlib/plugin.py +++ b/terminatorlib/plugin.py @@ -136,7 +136,6 @@ class MenuItem(Plugin): raise NotImplementedError if __name__ == '__main__': - import sys import doctest sys.path.insert(0, 'plugins') (failed, attempted) = doctest.testmod() diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 28282afc..d7299083 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -4,10 +4,9 @@ import os import gtk import gobject -from util import dbg, err +from util import dbg import config from keybindings import Keybindings, KeymapError -from version import APP_NAME, APP_VERSION from translation import _ from newterminator import Terminator diff --git a/terminatorlib/searchbar.py b/terminatorlib/searchbar.py index 9f0da760..46b65900 100755 --- a/terminatorlib/searchbar.py +++ b/terminatorlib/searchbar.py @@ -8,7 +8,6 @@ import gobject from translation import _ from config import Config -from util import dbg # pylint: disable-msg=R0904 class Searchbar(gtk.HBox): diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 2b648586..1d0155c7 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -10,10 +10,10 @@ pygtk.require('2.0') import gtk import gobject import pango -import re import subprocess +import urllib -from util import dbg, err, gerr, widget_pixbuf, get_top_window +from util import dbg, err, gerr, get_top_window import util from config import Config from cwd import get_default_cwd From 257cc3ead6592a92890ff9597022e4a73d4941db Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 11 Jan 2010 20:06:53 +0000 Subject: [PATCH 280/331] Remove the old terminator.py, rename newterminator.py accordingly and update all the references to it --- terminatorlib/newterminator.py | 222 ----- terminatorlib/notebook.py | 2 +- terminatorlib/paned.py | 2 +- terminatorlib/prefseditor.py | 2 +- terminatorlib/terminal.py | 2 +- terminatorlib/terminator.py | 1690 ++++---------------------------- terminatorlib/titlebar.py | 2 +- terminatorlib/window.py | 2 +- 8 files changed, 188 insertions(+), 1736 deletions(-) delete mode 100755 terminatorlib/newterminator.py diff --git a/terminatorlib/newterminator.py b/terminatorlib/newterminator.py deleted file mode 100755 index 670b75c9..00000000 --- a/terminatorlib/newterminator.py +++ /dev/null @@ -1,222 +0,0 @@ -#!/usr/bin/python -# Terminator by Chris Jones -# GPL v2 only -"""terminator.py - class for the master Terminator singleton""" - -import gtk - -from borg import Borg -from config import Config -from keybindings import Keybindings -from util import dbg, get_top_window - -class Terminator(Borg): - """master object for the application""" - - windows = None - windowtitle = None - terminals = None - groups = None - config = None - keybindings = None - - groupsend = None - groupsend_type = {'all':0, 'group':1, 'off':2} - - def __init__(self): - """Class initialiser""" - - Borg.__init__(self, self.__class__.__name__) - self.prepare_attributes() - - def prepare_attributes(self): - """Initialise anything that isn't already""" - - if not self.windows: - self.windows = [] - if not self.terminals: - self.terminals = [] - if not self.groups: - self.groups = [] - if not self.groupsend: - self.groupsend = self.groupsend_type['group'] - if not self.config: - self.config = Config() - if not self.keybindings: - self.keybindings = Keybindings() - self.keybindings.configure(self.config['keybindings']) - - def register_window(self, window): - """Register a new window widget""" - if window not in self.windows: - dbg('Terminator::register_window: registering %s:%s' % (id(window), - type(window))) - self.windows.append(window) - - def deregister_window(self, window): - """de-register a window widget""" - dbg('Terminator::deregister_window: de-registering %s:%s' % - (id(window), type(window))) - self.windows.remove(window) - if len(self.windows) == 0: - # We have no windows left, we should exit - gtk.main_quit() - - def register_terminal(self, terminal): - """Register a new terminal widget""" - if terminal not in self.terminals: - dbg('Terminator::register_terminal: registering %s:%s' % - (id(terminal), type(terminal))) - self.terminals.append(terminal) - terminal.connect('ungroup-all', self.ungroup_all) - terminal.connect('navigate', self.navigate_terminal) - terminal.connect('tab-new', self.tab_new) - - def deregister_terminal(self, terminal): - """De-register a terminal widget""" - dbg('Terminator::deregister_terminal: de-registering %s:%s' % - (id(terminal), type(terminal))) - self.terminals.remove(terminal) - - if len(self.terminals) == 0: - for window in self.windows: - window.destroy() - else: - dbg('Terminator::deregister_terminal: %d terminals remain' % - len(self.terminals)) - - def reconfigure(self): - """Update configuration for the whole application""" - - # FIXME: Set handle_size here - - # Cause all the terminals to reconfigure - for terminal in self.terminals: - terminal.reconfigure() - - def tab_new(self, terminal): - """A terminal asked for a new tab. This function is an indirection - to the Window object""" - window = get_top_window(terminal) - window.tab_new() - - def navigate_terminal(self, terminal, direction): - """Nagivate around the terminals""" - current = self.terminals.index(terminal) - length = len(self.terminals) - next = None - - if length <= 1: - return - - print "Current term: %d" % current - print "Number of terms: %d" % length - - if direction == 'next': - next = current + 1 - if next >= length: - next = 0 - elif direction == 'prev': - next = current - 1 - if next < 0: - next = length - 1 - else: - raise NotImplementedError - # FIXME: Do the directional navigation - - if next is not None: - print "sending focus to term %d" % next - self.terminals[next].grab_focus() - - def create_group(self, name): - """Create a new group""" - if name not in self.groups: - dbg('Terminator::create_group: registering group %s' % name) - self.groups.append(name) - - def ungroup_all(self, widget): - """Remove all groups""" - for terminal in self.terminals: - terminal.set_group(None, None) - self.groups = [] - - def closegroupedterms(self, group): - """Close all terminals in a group""" - for terminal in self.terminals: - if terminal.group == group: - terminal.close() - - def group_hoover(self): - """Clean out unused groups""" - - if self.config['autoclean_groups']: - inuse = [] - todestroy = [] - - for terminal in self.terminals: - if terminal.group: - if not terminal.group in inuse: - inuse.append(terminal.group) - - for group in self.groups: - if not group in inuse: - todestroy.append(group) - - dbg('Terminator::group_hoover: %d groups, hoovering %d' % - (len(self.groups), len(todestroy))) - for group in todestroy: - self.groups.remove(group) - - def group_emit(self, terminal, group, type, event): - """Emit to each terminal in a group""" - dbg('Terminator::group_emit: emitting a keystroke for group %s' % - group) - for term in self.terminals: - if term != terminal and term.group == group: - term.vte.emit(type, event) - - def all_emit(self, terminal, type, event): - """Emit to all terminals""" - for term in self.terminals: - if term != terminal: - term.vte.emit(type, event) - - def do_enumerate(self, widget, pad): - """Insert the number of each terminal in a group, into that terminal""" - if pad: - numstr = '%0'+str(len(str(len(self.terminals))))+'d' - else: - numstr = '%d' - - for term in self.get_target_terms(widget): - idx = self.terminals.index(term) - term.feed(numstr % (idx + 1)) - - def get_target_terms(self, widget): - """Get the terminals we should currently be broadcasting to""" - if self.groupsend == self.groupsend_type['all']: - return(self.terminals) - elif self.groupsend == self.groupsend_type['group']: - termset = [] - for term in self.terminals: - if term == widget or (term.group != None and term.group == - widget.group): - termset.append(term) - return(termset) - else: - return([widget]) - - def group_tab(self, widget): - """Group all the terminals in a tab""" - pass - - def ungroup_tab(self, widget): - """Ungroup all the terminals in a tab""" - pass - - def focus_changed(self, widget): - """We just moved focus to a new terminal""" - for terminal in self.terminals: - terminal.titlebar.update() - return -# vim: set expandtab ts=4 sw=4: diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index 85fd0d14..8b10caee 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -6,7 +6,7 @@ import gobject import gtk -from newterminator import Terminator +from terminator import Terminator from config import Config from factory import Factory from container import Container diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index 47200b7a..cbc5c1c4 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -8,7 +8,7 @@ import gobject import gtk from util import dbg, err, get_top_window -from newterminator import Terminator +from terminator import Terminator from factory import Factory from container import Container diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index d7299083..5c73df57 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -8,7 +8,7 @@ from util import dbg import config from keybindings import Keybindings, KeymapError from translation import _ -from newterminator import Terminator +from terminator import Terminator # FIXME: We need to check that we have represented all of Config() below class PrefsEditor: diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 1d0155c7..a6c11b79 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -17,7 +17,7 @@ from util import dbg, err, gerr, get_top_window import util from config import Config from cwd import get_default_cwd -from newterminator import Terminator +from terminator import Terminator from titlebar import Titlebar from terminal_popup_menu import TerminalPopupMenu from searchbar import Searchbar diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index 680c89c5..670b75c9 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -1,1548 +1,222 @@ #!/usr/bin/python -# Terminator - multiple gnome terminals in one window -# Copyright (C) 2006-2008 cmsj@tenshu.net -# -# 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, version 2 only. -# -# 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 +# Terminator by Chris Jones +# GPL v2 only +"""terminator.py - class for the master Terminator singleton""" -"""Terminator by Chris Jones """ -import time, re, sys, os, platform +import gtk -import pygtk -pygtk.require ("2.0") -import gobject, gtk, pango +from borg import Borg +from config import Config +from keybindings import Keybindings +from util import dbg, get_top_window -from version import APP_NAME, APP_VERSION -import config -from util import dbg, err, debug +class Terminator(Borg): + """master object for the application""" -from keybindings import TerminatorKeybindings -from terminatorterm import TerminatorTerm -from prefs_profile import ProfileEditor -from editablelabel import EditableLabel -import translation + windows = None + windowtitle = None + terminals = None + groups = None + config = None + keybindings = None -# FIXME: Move to notebook.py -class TerminatorNotebookTabLabel(gtk.HBox): - _terminator = None - _notebook = None - _icon = None - _label = None - _button = None - - def __init__(self, title, notebook, terminator): - gtk.HBox.__init__(self, False) - self._notebook = notebook - self._terminator = terminator - - self._label = EditableLabel(title) - self.update_angle() + groupsend = None + groupsend_type = {'all':0, 'group':1, 'off':2} - self.pack_start(self._label, True, True) + def __init__(self): + """Class initialiser""" - self._icon = gtk.Image() - self._icon.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) - - self.update_closebut() + Borg.__init__(self, self.__class__.__name__) + self.prepare_attributes() - self.show_all() + def prepare_attributes(self): + """Initialise anything that isn't already""" - def update_closebut(self): - if self._terminator.conf.close_button_on_tab: - if not self._button: - self._button = gtk.Button() - self._button.set_relief(gtk.RELIEF_NONE) - self._button.set_focus_on_click(False) - self._button.set_relief(gtk.RELIEF_NONE) - self._button.add(self._icon) - self._button.connect('clicked', self.on_close) - self._button.set_name("terminator-tab-close-button") - self._button.connect("style-set", self.on_style_set) - if hasattr(self._button, "set_tooltip_text"): - self._button.set_tooltip_text(_("Close Tab")) - self.pack_start(self._button, False, False) - self.show_all() - else: - if self._button: - self._button.remove(self._icon) - self.remove(self._button) - del(self._button) - self._button = None + if not self.windows: + self.windows = [] + if not self.terminals: + self.terminals = [] + if not self.groups: + self.groups = [] + if not self.groupsend: + self.groupsend = self.groupsend_type['group'] + if not self.config: + self.config = Config() + if not self.keybindings: + self.keybindings = Keybindings() + self.keybindings.configure(self.config['keybindings']) - def update_angle(self): - tab_pos = self._notebook.get_tab_pos() - if tab_pos == gtk.POS_LEFT: - self._label.set_angle(90) - elif tab_pos == gtk.POS_RIGHT: - self._label.set_angle(270) - else: - self._label.set_angle(0) + def register_window(self, window): + """Register a new window widget""" + if window not in self.windows: + dbg('Terminator::register_window: registering %s:%s' % (id(window), + type(window))) + self.windows.append(window) - def on_style_set(self, widget, prevstyle): - x, y = gtk.icon_size_lookup_for_settings( self._button.get_settings(), gtk.ICON_SIZE_MENU) - self._button.set_size_request(x + 2,y + 2) + def deregister_window(self, window): + """de-register a window widget""" + dbg('Terminator::deregister_window: de-registering %s:%s' % + (id(window), type(window))) + self.windows.remove(window) + if len(self.windows) == 0: + # We have no windows left, we should exit + gtk.main_quit() - def on_close(self, widget): - nbpages = self._notebook.get_n_pages() - for i in xrange(0,nbpages): - if self._notebook.get_tab_label(self._notebook.get_nth_page(i)) == self: - #dbg("[Close from tab] Found tab at position [%d]" % i) - if not isinstance (self._notebook.get_nth_page(i), TerminatorTerm): - if self._terminator.confirm_close_multiple (self._terminator.window, _("tab")): - return False - term = self._terminator._notebook_first_term(self._notebook.get_nth_page(i)) - while term: - if term == self._notebook.get_nth_page(i): - self._terminator.closeterm(term) - break - self._terminator.closeterm(term) - term = self._terminator._notebook_first_term(self._notebook.get_nth_page(i)) - break + def register_terminal(self, terminal): + """Register a new terminal widget""" + if terminal not in self.terminals: + dbg('Terminator::register_terminal: registering %s:%s' % + (id(terminal), type(terminal))) + self.terminals.append(terminal) + terminal.connect('ungroup-all', self.ungroup_all) + terminal.connect('navigate', self.navigate_terminal) + terminal.connect('tab-new', self.tab_new) - def set_title(self, title, force=False): - self._label.set_text(title, force) + def deregister_terminal(self, terminal): + """De-register a terminal widget""" + dbg('Terminator::deregister_terminal: de-registering %s:%s' % + (id(terminal), type(terminal))) + self.terminals.remove(terminal) - def get_title(self): - return self._label.get_text() - - def height_request(self): - return self.size_request()[1] - - def width_request(self): - return self.size_request()[0] - - -class Terminator: - options = None - groupings = None - _urgency = False - origcwd = None - - def __init__ (self, profile = None, command = None, fullscreen = False, - maximise = False, borderless = False, no_gconf = False, - geometry = None, hidden = False, forcedtitle = None, role=None): - self.profile = profile - self.command = command - - self._zoomed = False - self._maximised = False - self._fullscreen = False - self._geometry = geometry - self.debugaddress = None - self.start_cwd = os.getcwd() - self._hidden = False - self.term_list = [] - self.gnome_client = None - self.groupsend = 1 # 0 off, 1 group (d), 2 all - self.splittogroup = 0 # 0 no group (d), 1 new takes orginators group - self.autocleangroups = 1 # 0 off, 1 on (d) - stores = [] - self.groupings = [] - - store = config.TerminatorConfValuestoreRC () - store.set_reconfigure_callback (self.reconfigure_vtes) - stores.append (store) - - self._tab_reorderable = True - if not hasattr(gtk.Notebook, "set_tab_reorderable") or not hasattr(gtk.Notebook, "get_tab_reorderable"): - self._tab_reorderable = False - - if not no_gconf: - try: - import gconf - if self.profile: - self.profile = gconf.escape_key (self.profile, -1) - store = config.TerminatorConfValuestoreGConf (self.profile) - store.set_reconfigure_callback (self.reconfigure_vtes) - dbg ('Terminator__init__: comparing %s and %s'%(self.profile, store.profile.split ('/').pop ())) - if self.profile == store.profile.split ('/').pop (): - # If we have been given a profile, and we loaded it, we should be higher priority than RC - dbg ('Terminator__init__: placing GConf before RC') - stores.insert (0, store) + if len(self.terminals) == 0: + for window in self.windows: + window.destroy() else: - stores.append (store) - except Exception, e: - # This should probably be ImportError; what else might it throw? - dbg("GConf setup threw exception %s" % str(e)) + dbg('Terminator::deregister_terminal: %d terminals remain' % + len(self.terminals)) - self.conf = config.TerminatorConfig (stores) + def reconfigure(self): + """Update configuration for the whole application""" - # Sort out cwd detection code, if available - self.pid_get_cwd = lambda pid: None - if platform.system() == 'FreeBSD': - try: - from terminatorlib import freebsd - self.pid_get_cwd = freebsd.get_process_cwd - dbg ('Using FreeBSD self.pid_get_cwd') - except (OSError, NotImplementedError, ImportError): - dbg ('FreeBSD version too old for self.pid_get_cwd') - elif platform.system() == 'Linux': - dbg ('Using Linux self.pid_get_cwd') - self.pid_get_cwd = lambda pid: os.path.realpath ('/proc/%s/cwd' % pid) - elif platform.system() == 'SunOS': - dbg ('Using SunOS self.pid_get_cwd') - self.pid_get_cwd = lambda pid: os.path.realpath ('/proc/%s/path/cwd' % pid) - else: - dbg ('Unable to set a self.pid_get_cwd, unknown system: %s' % platform.system) + # FIXME: Set handle_size here - # import a library for viewing URLs - try: - dbg ('Trying to import gnome for X session and backup URL handling support') - global gnome - import gnome, gnome.ui - self.gnome_program = gnome.init(APP_NAME, APP_VERSION) - self.url_show = gnome.url_show - - # X session saving support - self.gnome_client = gnome.ui.master_client() - self.gnome_client.connect_to_session_manager() - self.gnome_client.connect('save-yourself', self.save_yourself) - self.gnome_client.connect('die', self.die) - except ImportError: - # webbrowser.open() is not really useful, but will do as a fallback - dbg ('gnome not available, no X session support, backup URL handling via webbrowser module') - import webbrowser - self.url_show = webbrowser.open + # Cause all the terminals to reconfigure + for terminal in self.terminals: + terminal.reconfigure() - self.icon_theme = gtk.IconTheme () + def tab_new(self, terminal): + """A terminal asked for a new tab. This function is an indirection + to the Window object""" + window = get_top_window(terminal) + window.tab_new() - self.keybindings = TerminatorKeybindings() - if self.conf.f11_modifier: - config.DEFAULTS['keybindings']['full_screen'] = 'F11' - print "Warning: Config setting f11_modifier is deprecated and will be removed in version 1.0" - print "Please add the following to the end of your terminator config:" - print "[keybindings]" - print "full_screen = F11" - self.keybindings.configure(self.conf.keybindings) + def navigate_terminal(self, terminal, direction): + """Nagivate around the terminals""" + current = self.terminals.index(terminal) + length = len(self.terminals) + next = None - self.set_handle_size (self.conf.handle_size) - self.set_closebutton_style () + if length <= 1: + return - self.window = gtk.Window () - if role: - self.window.set_role(role) - self.windowtitle = TerminatorWindowTitle (self.window) - if forcedtitle: - self.windowtitle.force_title (forcedtitle) - self.windowtitle.update () + print "Current term: %d" % current + print "Number of terms: %d" % length - if self._geometry is not None: - dbg("Geometry=%s" % self._geometry) - if not self.window.parse_geometry(self._geometry): - err(_("Invalid geometry string %r") % self._geometry) - - try: - self.window.set_icon (self.icon_theme.load_icon (APP_NAME, 48, 0)) - except: - self.icon = self.window.render_icon (gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_BUTTON) - self.window.set_icon (self.icon) - - self.window.connect ("key-press-event", self.on_key_press) - self.window.connect ("delete_event", self.on_delete_event) - self.window.connect ("destroy", self.on_destroy_event) - self.window.connect ("window-state-event", self.on_window_state_changed) - - self.window.set_property ('allow-shrink', True) - - if fullscreen or self.conf.fullscreen: - self.fullscreen_toggle () - - if maximise or self.conf.maximise: - self.maximize () - - if borderless or self.conf.borderless: - self.window.set_decorated (False) - - # Set RGBA colormap if possible so VTE can use real alpha - # channels for transparency. - self.enable_rgba(True) - - # Start out with just one terminal - # FIXME: This should be really be decided from some kind of profile - term = (TerminatorTerm (self, self.profile, self.command)) - self.term_list = [term] - - self.window.add (term) - term._titlebox.hide() - self.window.show () - term.spawn_child () - self.save_yourself () - - couldbind = False - try: - couldbind = bindkey.tomboy_keybinder_bind(self.conf.keybindings['hide_window'],self.cbkeyCloak,term) - except: - pass - if couldbind: - if hidden or self.conf.hidden: - self.hide() - else: - if hidden or self.conf.hidden: - self.window.iconify() - - def on_term_resized(self): - win_total_width, win_total_height = self.window.get_size () - dbg ('Resized window is %dx%d' % (win_total_width, win_total_height)) - - # FIXME: find first terminal - firstidx = 0 - - # Walk terminals across top edge to sum column geometries - prev = -1 - column_sum = 0 - width_extra = 0 - walker = firstidx - while (walker != None): - term = self.term_list[walker] - font_width, font_height, columns, rows = term.get_size_details () - column_sum += columns - dbg ('Geometry hints (term %d) column += %d characters' % (walker, columns)) - prev = walker - walker = self._select_right (walker) - - # Walk terminals down left edge to sum row geometries - prev = -1 - row_sum = 0 - height_extra = 0 - walker = firstidx - while (walker != None): - term = self.term_list[walker] - font_width, font_height, columns, rows = term.get_size_details () - row_sum += rows - dbg ('Geometry hints (term %d) row += %d characters' % (walker, rows)) - prev = walker - walker = self._select_down (walker) - - # adjust... - width_extra = win_total_width - (column_sum * font_width) - height_extra = win_total_height - (row_sum * font_height) - - dbg ('Geometry hints based on font size: %dx%d, columns: %d, rows: %d, extra width: %d, extra height: %d' % (font_width, font_height, column_sum, row_sum, width_extra, height_extra)) - - self.window.set_geometry_hints(self.window, -1, -1, -1, -1, width_extra, height_extra, font_width, font_height, -1.0, -1.0) - - def set_handle_size (self, size): - if size in xrange (0,6): - gtk.rc_parse_string(""" - style "terminator-paned-style" { - GtkPaned::handle_size = %s - } + if direction == 'next': + next = current + 1 + if next >= length: + next = 0 + elif direction == 'prev': + next = current - 1 + if next < 0: + next = length - 1 + else: + raise NotImplementedError + # FIXME: Do the directional navigation - class "GtkPaned" style "terminator-paned-style" - """ % self.conf.handle_size) - - def set_closebutton_style (self): - gtk.rc_parse_string(""" - style "terminator-tab-close-button-style" { - GtkWidget::focus-padding = 0 - GtkWidget::focus-line-width = 0 - xthickness = 0 - ythickness = 0 - } - widget "*.terminator-tab-close-button" style "terminator-tab-close-button-style" - """) + if next is not None: + print "sending focus to term %d" % next + self.terminals[next].grab_focus() - def enable_rgba (self, rgba = False): - screen = self.window.get_screen() - if rgba: - colormap = screen.get_rgba_colormap() - else: - colormap = screen.get_rgb_colormap() - if colormap: - self.window.set_colormap(colormap) + def create_group(self, name): + """Create a new group""" + if name not in self.groups: + dbg('Terminator::create_group: registering group %s' % name) + self.groups.append(name) - def die(self, *args): - gtk.main_quit () + def ungroup_all(self, widget): + """Remove all groups""" + for terminal in self.terminals: + terminal.set_group(None, None) + self.groups = [] - def save_yourself (self, *args): - """ Save as much of our state as possible for the X session manager """ - dbg("Saving session for xsm") - args = [sys.argv[0], - ("--geometry=%dx%d" % self.window.get_size()) + ("+%d+%d" % self.window.get_position())] + def closegroupedterms(self, group): + """Close all terminals in a group""" + for terminal in self.terminals: + if terminal.group == group: + terminal.close() - # OptionParser should really help us out here - drop_next_arg = False - geompatt = re.compile(r'^--geometry(=.+)?') - for arg in sys.argv[1:]: - mo = geompatt.match(arg) - if mo: - if not mo.group(1): - drop_next_arg = True - elif not drop_next_arg and arg not in ('--maximise', '-m', '--fullscreen', '-f'): - args.append(arg) - drop_next_arg = False + def group_hoover(self): + """Clean out unused groups""" - if self._maximised: - args.append('--maximise') + if self.config['autoclean_groups']: + inuse = [] + todestroy = [] - if self._fullscreen: - args.append('--fullscreen') + for terminal in self.terminals: + if terminal.group: + if not terminal.group in inuse: + inuse.append(terminal.group) - if self.gnome_client: - # We can't set an interpreter because Gnome unconditionally spams it with - # --sm-foo-bar arguments before our own argument list. *mutter* - # So, hopefully your #! line is correct. If not, we could write out - # a shell script with the interpreter name etc. - c = self.gnome_client - c.set_program(sys.argv[0]) - dbg("Session restart command: %s with args %r in %s" % (sys.argv[0], args, self.start_cwd)) + for group in self.groups: + if not group in inuse: + todestroy.append(group) - c.set_restart_style(gnome.ui.RESTART_IF_RUNNING) - c.set_current_directory(self.start_cwd) - try: - c.set_restart_command(args) - c.set_clone_command(args) - except (TypeError,AttributeError): - # Apparantly needed for some Fedora systems - # see http://trac.nicfit.net/mesk/ticket/137 - dbg("Gnome bindings have weird set_clone/restart_command") - c.set_restart_command(len(args), args) - c.set_clone_command(len(args), args) - return True + dbg('Terminator::group_hoover: %d groups, hoovering %d' % + (len(self.groups), len(todestroy))) + for group in todestroy: + self.groups.remove(group) - def show(self): - """Show the terminator window""" - # restore window position - self.window.move(self.pos[0],self.pos[1]) - #self.window.present() - self.window.show_now() - self._hidden = False + def group_emit(self, terminal, group, type, event): + """Emit to each terminal in a group""" + dbg('Terminator::group_emit: emitting a keystroke for group %s' % + group) + for term in self.terminals: + if term != terminal and term.group == group: + term.vte.emit(type, event) - def hide(self): - """Hide the terminator window""" - # save window position - self.pos = self.window.get_position() - self.window.hide() - self._hidden = True + def all_emit(self, terminal, type, event): + """Emit to all terminals""" + for term in self.terminals: + if term != terminal: + term.vte.emit(type, event) - def cbkeyCloak(self, data): - """Callback event for show/hide keypress""" - if self._hidden: - self.show() - else: - self.hide() - - def maximize (self): - """ Maximize the Terminator window.""" - self.window.maximize () - - def unmaximize (self): - """ Unmaximize the Terminator window.""" - self.window.unmaximize () - - def fullscreen_toggle (self): - """ Toggle the fullscreen state of the window. If it is in - fullscreen state, it will be unfullscreened. If it is not, it - will be set to fullscreen state. - """ - if self._fullscreen: - self.window.unfullscreen () - else: - self.window.fullscreen () - - def fullscreen_absolute (self, fullscreen): - """ Explicitly set the fullscreen state of the window. - """ - if self._fullscreen != fullscreen: - self.fullscreen_toggle () - - def on_window_state_changed (self, window, event): - self._fullscreen = bool (event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN) - self._maximised = bool (event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED) - dbg("window state changed: fullscreen: %s, maximised: %s" % (self._fullscreen, self._maximised)) - return (False) - - def on_delete_event (self, window, event, data=None): - if len (self.term_list) == 1: - return False - return self.confirm_close_multiple (window, _("window")) - - def confirm_close_multiple (self, window, type): - # show dialog - dialog = gtk.Dialog (_("Close?"), window, gtk.DIALOG_MODAL) - dialog.set_has_separator (False) - dialog.set_resizable (False) - - cancel = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT) - close_all = dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT) - label = close_all.get_children()[0].get_children()[0].get_children()[1].set_label(_("Close _Terminals")) - - primairy = gtk.Label (_('Close multiple terminals?')) - primairy.set_use_markup (True) - primairy.set_alignment (0, 0.5) - secundairy = gtk.Label (_("This %s has several terminals open. Closing the %s will also close all terminals within it.") % (type, type)) - secundairy.set_line_wrap(True) - primairy.set_alignment (0, 0.5) - - labels = gtk.VBox () - labels.pack_start (primairy, False, False, 6) - labels.pack_start (secundairy, False, False, 6) - - image = gtk.image_new_from_stock(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_DIALOG) - image.set_alignment (0.5, 0) - - box = gtk.HBox() - box.pack_start (image, False, False, 6) - box.pack_start (labels, False, False, 6) - dialog.vbox.pack_start (box, False, False, 12) - - dialog.show_all () - result = dialog.run () - dialog.destroy () - return not (result == gtk.RESPONSE_ACCEPT) - - def on_destroy_event (self, widget, data=None): - self.die() - - def on_beep (self, terminal): - self.set_urgency (True) - - def set_urgency (self, on): - if on == self._urgency: - return - - self._urgency = on - self.window.set_urgency_hint (on) - - # keybindings for the whole terminal window (affects the main - # windows containing the splited terminals) - def on_key_press (self, window, event): - """ Callback for the window to determine what to do with special - keys. Currently handled key-combo's: - * F11: toggle fullscreen state of the window. - * CTRL - SHIFT - Q: close all terminals - """ - self.set_urgency (False) - mapping = self.keybindings.lookup(event) - - if mapping: - dbg("on_key_press: lookup found %r" % mapping) - if mapping == 'full_screen': - self.fullscreen_toggle () - elif mapping == 'close_window': - if not self.on_delete_event (window, gtk.gdk.Event (gtk.gdk.DELETE)): - self.on_destroy_event (window, gtk.gdk.Event (gtk.gdk.DESTROY)) - else: - return (False) - return (True) - - def set_window_title(self, title): - """ - Modifies Terminator window title - """ - self.windowtitle.set_title(title) - - def add(self, widget, terminal, pos = "bottom"): - """ - Add a term to another at position pos - """ - if pos in ("top", "bottom"): - pane = gtk.VPaned() - vertical = True - elif pos in ("left", "right"): - pane = gtk.HPaned() - vertical = False - else: - err('Terminator.add: massive pos fail: %s' % pos) - return - - # get the parent of the provided terminal - parent = widget.get_parent () - - if isinstance (parent, gtk.Window): - # We have just one term - parent.remove(widget) - if pos in ("top", "left"): - pane.pack1 (terminal, True, True) - pane.pack2 (widget, True, True) - else: - pane.pack1 (widget, True, True) - pane.pack2 (terminal, True, True) - parent.add (pane) - - position = (vertical) and parent.allocation.height \ - or parent.allocation.width - - if (isinstance (parent, gtk.Notebook) or isinstance (parent, gtk.Window)) and widget.conf.titlebars: - #not the only term in the notebook/window anymore, need to reshow the title - widget._titlebox.update() - terminal._titlebox.update() - - if isinstance (parent, gtk.Notebook): - page = -1 - - for i in xrange(0, parent.get_n_pages()): - if parent.get_nth_page(i) == widget: - page = i - break - - label = parent.get_tab_label (widget) - widget.reparent (pane) - if pos in ("top", "left"): - pane.remove(widget) - pane.pack1 (terminal, True, True) - pane.pack2 (widget, True, True) - else: - pane.pack1 (widget, True, True) - pane.pack2 (terminal, True, True) - #parent.remove_page(page) - pane.show() - parent.insert_page(pane, None, page) - parent.set_tab_label(pane,label) - parent.set_tab_label_packing(pane, not self.conf.scroll_tabbar, not self.conf.scroll_tabbar, gtk.PACK_START) - if self._tab_reorderable: - parent.set_tab_reorderable(pane, True) - parent.set_current_page(page) - - position = (vertical) and parent.allocation.height \ - or parent.allocation.width - - if isinstance (parent, gtk.Paned): - # We are inside a split term - position = (vertical) and widget.allocation.height \ - or widget.allocation.width - - if (widget == parent.get_child1 ()): - widget.reparent (pane) - parent.pack1 (pane, True, True) - else: - widget.reparent (pane) - parent.pack2 (pane, True, True) - - if pos in ("top", "left"): - pane.remove(widget) - pane.pack1 (terminal, True, True) - pane.pack2 (widget, True, True) - else: - pane.pack1 (widget, True, True) - pane.pack2 (terminal, True, True) - - pane.pack1 (widget, True, True) - pane.pack2 (terminal, True, True) - - # show all, set position of the divider - pane.show () - pane.set_position (position / 2) - terminal.show () - - # insert the term reference into the list - index = self.term_list.index (widget) - if pos in ('bottom', 'right'): - index = index + 1 - self.term_list.insert (index, terminal) - # make the new terminal grab the focus - terminal._vte.grab_focus () - - return (terminal) - - def on_page_reordered(self, notebook, child, page_num): - #page has been reordered, we need to get the - # first term and last term - dbg ("Reordered: %d"%page_num) - nbpages = notebook.get_n_pages() - if nbpages == 1: - dbg("[ERROR] only one page in on_page_reordered") - - first = self._notebook_first_term(notebook.get_nth_page(page_num)) - last = self._notebook_last_term(notebook.get_nth_page(page_num)) - firstidx = self.term_list.index(first) - lastidx = self.term_list.index(last) - termslice = self.term_list[firstidx:lastidx+1] - #remove them from the list - for term in termslice: - self.term_list.remove(term) - - if page_num == 0: - #first page, we insert before the first term of next page - nexttab = notebook.get_nth_page(1) - sibling = self._notebook_first_term(nexttab) - siblingindex = self.term_list.index(sibling) - for term in termslice: - self.term_list.insert(siblingindex, term) - siblingindex += 1 - else: - #other pages, we insert after the last term of previous page - previoustab = notebook.get_nth_page(page_num - 1) - sibling = self._notebook_last_term(previoustab) - siblingindex = self.term_list.index(sibling) - for term in termslice: - siblingindex += 1 - self.term_list.insert(siblingindex, term) - - #for page reorder, we need to get the first term of a notebook - def notebook_first_term(self, notebook): - return self._notebook_first_term(notebook.get_nth_page(0)) - - def _notebook_first_term(self, child): - if isinstance(child, TerminatorTerm): - return child - elif isinstance(child, gtk.Paned): - return self._notebook_first_term(child.get_child1()) - elif isinstance(child, gtk.Notebook): - return self._notebook_first_term(child.get_nth_page(0)) - - dbg("[ERROR] unsupported class %s in _notebook_first_term" % child.__class__.__name__) - return None - - #for page reorder, we need to get the last term of a notebook - def notebook_last_term(self, notebook): - return self._notebook_last_term(notebook.get_nth_page(notebook.get_n_pages()-1)) - - def _notebook_last_term(self, child): - if isinstance(child, TerminatorTerm): - return child - elif isinstance(child, gtk.Paned): - return self._notebook_last_term(child.get_child2()) - elif isinstance(child, gtk.Notebook): - return self._notebook_last_term(child.get_nth_page(child.get_n_pages()-1)) - - dbg("[ERROR] unsupported class %s in _notebook_last_term" % child.__class__.__name__) - return None - - def newtab(self,widget, toplevel = False, command = None): - if self._zoomed: - # We don't want to add a new tab while we are zoomed in on a terminal - dbg ("newtab function called, but Terminator was in zoomed terminal mode.") - return - - terminal = TerminatorTerm (self, self.profile, command, widget.get_cwd()) - #only one term, we don't show the title - terminal._titlebox.hide() - if self.conf.extreme_tabs and not toplevel: - parent = widget.get_parent () - child = widget - else: - child = self.window.get_children()[0] - parent = child.get_parent() - - if isinstance(parent, gtk.Paned) or (isinstance(parent, gtk.Window) - and - ((self.conf.extreme_tabs and not toplevel) or not isinstance(child, gtk.Notebook))): - #no notebook yet. - notebook = gtk.Notebook() - if self._tab_reorderable: - notebook.connect('page-reordered',self.on_page_reordered) - notebook.set_tab_reorderable(widget, True) - notebook.set_property('homogeneous', not self.conf.scroll_tabbar) - notebook.set_scrollable (self.conf.scroll_tabbar) - # Config validates this. - pos = getattr(gtk, "POS_%s" % self.conf.tab_position.upper()) - notebook.set_tab_pos(pos) - notebook.set_show_tabs (not self.conf.hide_tabbar) - - if isinstance(parent, gtk.Paned): - if parent.get_child1() == child: - child.reparent(notebook) - parent.pack1(notebook) + def do_enumerate(self, widget, pad): + """Insert the number of each terminal in a group, into that terminal""" + if pad: + numstr = '%0'+str(len(str(len(self.terminals))))+'d' else: - child.reparent(notebook) - parent.pack2(notebook) - elif isinstance(parent, gtk.Window): - child.reparent(notebook) - parent.add(notebook) - if self._tab_reorderable: - notebook.set_tab_reorderable(child,True) - notebooklabel = "" - if isinstance(child, TerminatorTerm): - child._titlebox.hide() - if widget.get_window_title() is not None: - notebooklabel = widget.get_window_title() - notebooktablabel = TerminatorNotebookTabLabel(notebooklabel, notebook, self) - notebook.set_tab_label(child, notebooktablabel) - notebook.set_tab_label_packing(child, not self.conf.scroll_tabbar, not self.conf.scroll_tabbar, gtk.PACK_START) + numstr = '%d' - wal = self.window.allocation - if not (self._maximised or self._fullscreen): - self.window.resize(wal.width, - min(wal.height + notebooktablabel.height_request(), gtk.gdk.screen_height())) + for term in self.get_target_terms(widget): + idx = self.terminals.index(term) + term.feed(numstr % (idx + 1)) - notebook.show() - elif isinstance(parent, gtk.Notebook): - notebook = parent - elif isinstance(parent, gtk.Window) and isinstance(child, gtk.Notebook): - notebook = child - else: - return (False) - - ## NOTE - ## Here we need to append to the notebook before we can - ## spawn the terminal (WINDOW_ID needs to be set) - - notebook.append_page(terminal,None) - terminal.show () - terminal.spawn_child () - notebooklabel = terminal.get_window_title() - notebooktablabel = TerminatorNotebookTabLabel(notebooklabel, notebook, self) - notebook.set_tab_label(terminal, notebooktablabel) - notebook.set_tab_label_packing(terminal, not self.conf.scroll_tabbar, not self.conf.scroll_tabbar, gtk.PACK_START) - if self._tab_reorderable: - notebook.set_tab_reorderable(terminal,True) - ## Now, we set focus on the new term - notebook.set_current_page(-1) - terminal._vte.grab_focus () - - #adding a new tab, thus we need to get the - # last term of the previous tab and add - # the new term just after - sibling = self._notebook_last_term(notebook.get_nth_page(notebook.page_num(terminal)-1)) - index = self.term_list.index(sibling) - self.term_list.insert (index + 1, terminal) - return (True) - - def splitaxis (self, widget, vertical=True, command=None): - """ Split the provided widget on the horizontal or vertical axis. """ - if self._zoomed: - # We don't want to split the terminal while we are in zoomed mode - dbg ("splitaxis function called, but Terminator was in zoomed mode.") - return - - # create a new terminal and parent pane. - terminal = TerminatorTerm (self, self.profile, command, widget.get_cwd()) - if self.splittogroup: - terminal.set_group (None, widget._group) - pos = vertical and "right" or "bottom" - self.add(widget, terminal, pos) - terminal.show () - terminal.spawn_child () - - return - - def remove(self, widget, keep = False): - """Remove a TerminatorTerm from the Terminator view and terms list - Returns True on success, False on failure""" - parent = widget.get_parent () - sibling = None - focus_on_close = 'prev' - if isinstance (parent, gtk.Window): - # We are the only term - if not self.on_delete_event (parent, gtk.gdk.Event (gtk.gdk.DELETE)): - self.on_destroy_event (parent, gtk.gdk.Event (gtk.gdk.DESTROY)) - return True - - elif isinstance (parent, gtk.Paned): - index = self.term_list.index (widget) - grandparent = parent.get_parent () - - # Discover sibling while all objects exist - if widget == parent.get_child1 (): - sibling = parent.get_child2 () - focus_on_close = 'next' - if widget == parent.get_child2 (): - sibling = parent.get_child1 () - - if not sibling: - # something is wrong, give up - err ("Error: %s is not a child of %s"%(widget, parent)) - return False - - parent.remove(widget) - if isinstance(grandparent, gtk.Notebook): - page = -1 - for i in xrange(0, grandparent.get_n_pages()): - if grandparent.get_nth_page(i) == parent: - page = i - break - label = grandparent.get_tab_label (parent) - parent.remove(sibling) - grandparent.remove_page(page) - grandparent.insert_page(sibling, None,page) - grandparent.set_tab_label(sibling, label) - grandparent.set_tab_label_packing(sibling, not self.conf.scroll_tabbar, not self.conf.scroll_tabbar, gtk.PACK_START) - if self._tab_reorderable: - grandparent.set_tab_reorderable(sibling, True) - grandparent.set_current_page(page) - else: - grandparent.remove (parent) - sibling.reparent (grandparent) - if not self._zoomed: - grandparent.resize_children() - if isinstance(sibling, TerminatorTerm) and isinstance(sibling.get_parent(), gtk.Notebook): - sibling._titlebox.hide() - - self.term_list.remove (widget) - if not keep: - widget._vte.get_parent().remove(widget._vte) - widget._vte = None - - elif isinstance (parent, gtk.Notebook): - parent.remove(widget) - nbpages = parent.get_n_pages() - index = self.term_list.index (widget) - - self.term_list.remove (widget) - if not keep: - widget._vte.get_parent().remove(widget._vte) - widget._vte = None - if nbpages == 1: - if self.window.allocation.height != gtk.gdk.screen_height(): - self.window.resize(self.window.allocation.width, min(self.window.allocation.height - parent.get_tab_label(parent.get_nth_page(0)).height_request(), gtk.gdk.screen_height())) - sibling = parent.get_nth_page(0) - parent.remove(sibling) - gdparent = parent.get_parent() - if isinstance(gdparent, gtk.Window): - gdparent.remove(parent) - gdparent.add(sibling) - elif isinstance(gdparent, gtk.Paned): - if gdparent.get_child1() == parent: - gdparent.remove(parent) - gdparent.pack1(sibling) - else: - gdparent.remove(parent) - gdparent.pack2(sibling) - elif isinstance(gdparent, gtk.Notebook): - # extreme_tabs is on :( - label = gdparent.get_tab_label(parent) - gdparent.remove(parent) - gdparent.insert_page(sibling, None, 0) - gdparent.set_tab_label(sibling, label) - gdparent.set_tab_label_packing(sibling, not self.conf.scroll_tabbar, not self.conf.scroll_tabbar, gtk.PACK_START) - if self._tab_reorderable: - gdparent.set_tab_reorderable(sibling, True) - gdparent.set_current_page(0) + def get_target_terms(self, widget): + """Get the terminals we should currently be broadcasting to""" + if self.groupsend == self.groupsend_type['all']: + return(self.terminals) + elif self.groupsend == self.groupsend_type['group']: + termset = [] + for term in self.terminals: + if term == widget or (term.group != None and term.group == + widget.group): + termset.append(term) + return(termset) else: - err('Unknown grandparent of %s (parent is a notebook)' % widget) - if isinstance(sibling, TerminatorTerm) and sibling.conf.titlebars and sibling.conf.extreme_tabs: - sibling._titlebox.show() - else: - err('Attempting to remove terminal from unknown parent: %s' % parent) - if self.conf.focus_on_close == 'prev' or ( self.conf.focus_on_close == 'auto' and focus_on_close == 'prev'): - if index == 0: index = 1 - self.term_list[index - 1]._vte.grab_focus () - self._set_current_notebook_page_recursive(self.term_list[index - 1]) - elif self.conf.focus_on_close == 'next' or ( self.conf.focus_on_close == 'auto' and focus_on_close == 'next'): - if index == len(self.term_list): index = index - 1 - self.term_list[index]._vte.grab_focus () - self._set_current_notebook_page_recursive(self.term_list[index]) - - if len(self.term_list) == 1: - self.term_list[0]._titlebox.hide() - - return True - - def closeterm (self, widget): - if self._zoomed: - # We are zoomed, pop back out to normal layout before closing - dbg ("closeterm function called while in zoomed mode. Restoring previous layout before closing.") - self.toggle_zoom(widget, not self._maximised) - - if self.remove(widget): - self.group_hoover() - return True - return False - - def closegroupedterms (self, widget): - if self._zoomed: - # We are zoomed, pop back out to normal layout before closing - dbg ("closeterm function called while in zoomed mode. Restoring previous layout before closing.") - self.toggle_zoom(widget, not self._maximised) - - widget_group = widget._group - all_closed = True - for term in self.term_list[:]: - if term._group == widget_group and not self.remove(term): - all_closed = False - self.group_hoover() - return all_closed - - def go_to (self, term, selector): - current = self.term_list.index (term) - target = selector (term) - if not target is None: - term = self.term_list[target] - ##we need to set the current page of each notebook - self._set_current_notebook_page_recursive(term) - term._vte.grab_focus () - - def _select_direction (self, term, matcher): - '''Return index of terminal in given direction''' - # Handle either TerminatorTerm or int index - if type(term) == int: - current = term - term = self.term_list[current] - else: - current = self.term_list.index (term) - current_geo = term.get_geometry () - best_index = None - best_geo = None - - for i in range(0,len(self.term_list)): - if i == current: - continue - possible = self.term_list[i] - possible_geo = possible.get_geometry () - - #import pprint - #print "I am %d" % (current) - #pprint.pprint(current_geo) - #print "I saw %d" % (i) - #pprint.pprint(possible_geo) - - try: - if matcher (current_geo, possible_geo, best_geo): - best_index = i - best_geo = possible_geo - except: - # Not being called on a Paned widget - pass - #if best_index is None: - # print "nothing best" - #else: - # print "sending %d" % (best_index) - return best_index - - def _match_up (self, current_geo, possible_geo, best_geo): - '''We want to find terminals that are fully above the top - border, but closest in the y direction, breaking ties via - the closest cursor x position.''' - if len(possible_geo.keys()) == 0: - dbg('_match_right: no possible geo, bailing') - return False - - #print "matching up..." - # top edge of the current terminal - edge = current_geo['origin_y'] - # botoom edge of the possible target - new_edge = possible_geo['origin_y']+possible_geo['span_y'] - - # Width of the horizontal bar that splits terminals - try: - horizontalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0]._titlebox.get_allocation().height - except TypeError: - horizontalBar = 0 - # Vertical distance between two terminals - distance = current_geo['offset_y'] - (possible_geo['offset_y'] + possible_geo['span_y']) - if new_edge < edge: - #print "new_edge < edge" - if best_geo is None: - #print "first thing left" - return True - best_edge = best_geo['origin_y']+best_geo['span_y'] - if new_edge > best_edge and distance == horizontalBar: - #print "closer y" - return True - if new_edge == best_edge: - #print "same y" - - cursor = current_geo['origin_x'] + current_geo['cursor_x'] - new_cursor = possible_geo['origin_x'] + possible_geo['cursor_x'] - best_cursor = best_geo['origin_x'] + best_geo['cursor_x'] - - if abs(new_cursor - cursor) < abs(best_cursor - cursor): - #print "closer x" - return True - else: - if distance == horizontalBar: - return True - #print "fail" - return False - - def _match_down (self, current_geo, possible_geo, best_geo): - '''We want to find terminals that are fully below the bottom - border, but closest in the y direction, breaking ties via - the closest cursor x position.''' - if len(possible_geo.keys()) == 0: - dbg('_match_right: no possible geo, bailing') - return False - - #print "matching down..." - # bottom edge of the current terminal - edge = current_geo['origin_y']+current_geo['span_y'] - # top edge of the possible target - new_edge = possible_geo['origin_y'] - #print "edge: %d new_edge: %d" % (edge, new_edge) - - # Width of the horizontal bar that splits terminals - try: - horizontalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0]._titlebox.get_allocation().height - except TypeError: - horizontalBar = 0 - # Vertical distance between two terminals - distance = possible_geo['offset_y'] - (current_geo['offset_y'] + current_geo['span_y']) - if new_edge > edge: - #print "new_edge > edge" - if best_geo is None: - #print "first thing right" - return True - best_edge = best_geo['origin_y'] - #print "best_edge: %d" % (best_edge) - if new_edge < best_edge and distance == horizontalBar: - #print "closer y" - return True - if new_edge == best_edge: - #print "same y" - - cursor = current_geo['origin_x'] + current_geo['cursor_x'] - new_cursor = possible_geo['origin_x'] + possible_geo['cursor_x'] - best_cursor = best_geo['origin_x'] + best_geo['cursor_x'] - - if abs(new_cursor - cursor) < abs(best_cursor - cursor): - #print "closer x" - return True - else: - if distance == horizontalBar: - return True - #print "fail" - return False - - def _match_left (self, current_geo, possible_geo, best_geo): - '''We want to find terminals that are fully to the left of - the left-side border, but closest in the x direction, breaking - ties via the closest cursor y position.''' - if len(possible_geo.keys()) == 0: - dbg('_match_right: no possible geo, bailing') - return False - - #print "matching left..." - # left-side edge of the current terminal - edge = current_geo['origin_x'] - # right-side edge of the possible target - new_edge = possible_geo['origin_x']+possible_geo['span_x'] - - # Width of the horizontal bar that splits terminals - try: - horizontalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0]._titlebox.get_allocation().height - except TypeError: - horizontalBar = 0 - # Width of the vertical bar that splits terminals - if self.term_list[0].is_scrollbar_present(): - try: - verticalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0].get_parent().style_get_property('scroll-arrow-vlength') - except TypeError: - verticalBar = 0 - else: - try: - verticalBar = self.term_list[0].get_parent().style_get_property('handle-size') - except TypeError: - verticalBar = 0 - # Horizontal distance between two terminals - distance = current_geo['offset_x'] - (possible_geo['offset_x'] + possible_geo['span_x']) - if new_edge <= edge: - #print "new_edge(%d) < edge(%d)" % (new_edge, edge) - if best_geo is None: - #print "first thing left" - return True - best_edge = best_geo['origin_x']+best_geo['span_x'] - if new_edge > best_edge and distance == verticalBar: - #print "closer x (new_edge(%d) > best_edge(%d))" % (new_edge, best_edge) - return True - if new_edge == best_edge: - #print "same x" - - cursor = current_geo['origin_y'] + current_geo['cursor_y'] - new_cursor = possible_geo['origin_y'] + possible_geo['cursor_y'] - best_cursor = best_geo['origin_y'] + best_geo['cursor_y'] - - if abs(new_cursor - cursor) < abs(best_cursor - cursor) and distance <> horizontalBar: - #print "closer y" - return True - #print "fail" - return False - - def _match_right (self, current_geo, possible_geo, best_geo): - '''We want to find terminals that are fully to the right of - the right-side border, but closest in the x direction, breaking - ties via the closest cursor y position.''' - if len(possible_geo.keys()) == 0: - dbg('_match_right: no possible geo, bailing') - return False - - #print "matching right..." - # right-side edge of the current terminal - edge = current_geo['origin_x']+current_geo['span_x'] - # left-side edge of the possible target - new_edge = possible_geo['origin_x'] - #print "edge: %d new_edge: %d" % (edge, new_edge) - - # Width of the horizontal bar that splits terminals - try: - horizontalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0]._titlebox.get_allocation().height - except TypeError: - horizontalBar = 0 - # Width of the vertical bar that splits terminals - if self.term_list[0].is_scrollbar_present(): - try: - verticalBar = self.term_list[0].get_parent().style_get_property('handle-size') + self.term_list[0].get_parent().style_get_property('scroll-arrow-vlength') - except TypeError: - verticalBar = 0 - else: - try: - verticalBar = self.term_list[0].get_parent().style_get_property('handle-size') - except TypeError: - verticalBar = 0 - # Horizontal distance between two terminals - distance = possible_geo['offset_x'] - (current_geo['offset_x'] + current_geo['span_x']) - if new_edge >= edge: - #print "new_edge > edge" - if best_geo is None: - #print "first thing right" - return True - best_edge = best_geo['origin_x'] - #print "best_edge: %d" % (best_edge) - if new_edge < best_edge and distance == verticalBar: - #print "closer x" - return True - if new_edge == best_edge: - #print "same x" - - cursor = current_geo['origin_y'] + current_geo['cursor_y'] - new_cursor = possible_geo['origin_y'] + possible_geo['cursor_y'] - best_cursor = best_geo['origin_y'] + best_geo['cursor_y'] - - if abs(new_cursor - cursor) < abs(best_cursor - cursor) and distance <> horizontalBar: - #print "closer y" - return True - #print "fail" - return False - - def _select_up (self, term): - return self._select_direction (term, self._match_up) - - def _select_down (self, term): - return self._select_direction (term, self._match_down) - - def _select_left (self, term): - return self._select_direction (term, self._match_left) - - def _select_right (self, term): - return self._select_direction (term, self._match_right) - - def go_next (self, term): - self.go_to (term, self._select_next) - - def go_prev (self, term): - self.go_to (term, self._select_prev) - - def go_up (self, term): - self.go_to (term, self._select_up) - - def go_down (self, term): - self.go_to (term, self._select_down) - - def go_left (self, term): - self.go_to (term, self._select_left) - - def go_right (self, term): - self.go_to (term, self._select_right) - - def _select_next (self, term): - current = self.term_list.index (term) - next = None - if self.conf.cycle_term_tab: - notebookpage = self.get_first_notebook_page(term) - if notebookpage: - last = self._notebook_last_term(notebookpage[1]) - first = self._notebook_first_term(notebookpage[1]) - if term == last: - next = self.term_list.index(first) - - if next is None: - if current == len (self.term_list) - 1: - next = 0 - else: - next = current + 1 - return next - - def _select_prev (self, term): - current = self.term_list.index (term) - previous = None - if self.conf.cycle_term_tab: - notebookpage = self.get_first_notebook_page(term) - if notebookpage: - last = self._notebook_last_term(notebookpage[1]) - first = self._notebook_first_term(notebookpage[1]) - if term == first: - previous = self.term_list.index(last) - - if previous is None: - if current == 0: - previous = len (self.term_list) - 1 - else: - previous = current - 1 - return previous - - def _set_current_notebook_page_recursive(self, widget): - page = self.get_first_notebook_page(widget) - while page: - child = None - page_num = page[0].page_num(page[1]) - page[0].set_current_page(page_num) - page = self.get_first_notebook_page(page[0]) - - def resizeterm (self, widget, keyname): - if keyname in ('Up', 'Down'): - type = gtk.VPaned - elif keyname in ('Left', 'Right'): - type = gtk.HPaned - else: - err ("Invalid keytype: %s" % type) - return - - parent = self.get_first_parent_widget(widget, type) - if parent is None: - return - - #We have a corresponding parent pane - # - #allocation = parent.get_allocation() - - if keyname in ('Up', 'Down'): - maxi = parent.get_child1().get_allocation().height + parent.get_child2().get_allocation().height - 1 - - else: - maxi = parent.get_child1().get_allocation().width + parent.get_child2().get_allocation().width - 1 - move = 10 - if keyname in ('Up', 'Left'): - move = -10 - - move = max(2, parent.get_position() + move) - move = min(maxi, move) - - parent.set_position(move) - - def previous_tab(self, term): - notebook = self.get_first_parent_notebook(term) - if notebook: - cur = notebook.get_current_page() - pages = notebook.get_n_pages() - if cur == 0: - notebook.set_current_page(pages - 1) - else: - notebook.prev_page() - # This seems to be required in some versions of (py)gtk. - # Without it, the selection changes, but the displayed page doesn't change - # Seen in gtk-2.12.11 and pygtk-2.12.1 at least. - notebook.set_current_page(notebook.get_current_page()) - - def next_tab(self, term): - notebook = self.get_first_parent_notebook(term) - if notebook: - cur = notebook.get_current_page() - pages = notebook.get_n_pages() - if cur == pages - 1: - notebook.set_current_page(0) - else: - notebook.next_page() - notebook.set_current_page(notebook.get_current_page()) - - def switch_to_tab(self, term, index): - notebook = self.get_first_parent_notebook(term) - if notebook: - notebook.set_current_page(index) - notebook.set_current_page(notebook.get_current_page()) - - def move_tab(self, term, direction): - dbg("moving to direction %s" % direction) - data = self.get_first_notebook_page(term) - if data is None: - return False - (notebook, page) = data - page_num = notebook.page_num(page) - nbpages = notebook.get_n_pages() - #dbg ("%s %s %s %s" % (page_num, nbpages,notebook, page)) - if page_num == 0 and direction == 'left': - new_page_num = nbpages - elif page_num == nbpages - 1 and direction == 'right': - new_page_num = 0 - elif direction == 'left': - new_page_num = page_num - 1 - elif direction == 'right': - new_page_num = page_num + 1 - else: - dbg("[ERROR] unhandled combination in move_tab: direction = %s page_num = %d" % (direction, page_num)) - return False - notebook.reorder_child(page, new_page_num) - return True - - def get_first_parent_notebook(self, widget): - if isinstance (widget, gtk.Window): - return None - parent = widget.get_parent() - if isinstance (parent, gtk.Notebook): - return parent - return self.get_first_parent_notebook(parent) - - def get_first_parent_widget (self, widget, type): - """This method searches up through the gtk widget heirarchy - of 'widget' until it finds a parent widget of type 'type'""" - while not isinstance(widget.get_parent(), type): - widget = widget.get_parent() - if widget is None: - return widget - - return widget.get_parent() - - def get_first_notebook_page(self, widget): - if isinstance (widget, gtk.Window) or widget is None: - return None - parent = widget.get_parent() - if isinstance (parent, gtk.Notebook): - page = -1 - for i in xrange(0, parent.get_n_pages()): - if parent.get_nth_page(i) == widget: - return (parent, widget) - return self.get_first_notebook_page(parent) - - def reconfigure_vtes (self): - for term in self.term_list: - term.reconfigure_vte () - - def toggle_zoom(self, widget, fontscale = False): - if not self._zoomed: - widget._titlebars = widget._titlebox.get_property ('visible') - dbg ('toggle_zoom: not zoomed. remembered titlebar setting of %s'%widget._titlebars) - if widget._titlebars: - widget._titlebox.hide() - self.zoom_term (widget, fontscale) - else: - dbg ('toggle_zoom: zoomed. restoring titlebar setting of %s'%widget._titlebars) - self.unzoom_term (widget, True) - if widget._titlebars and \ - len(self.term_list) > 1 \ - and \ - (isinstance(widget, TerminatorTerm) and isinstance(widget.get_parent(),gtk.Paned))\ - : - widget._titlebox.show() - - widget._vte.grab_focus() - widget._titlebox.update() - - def zoom_term (self, widget, fontscale = False): - """Maximize to full window an instance of TerminatorTerm.""" - self.old_font = widget._vte.get_font () - self.old_char_height = widget._vte.get_char_height () - self.old_char_width = widget._vte.get_char_width () - self.old_allocation = widget._vte.get_allocation () - self.old_padding = widget._vte.get_padding () - self.old_columns = widget._vte.get_column_count () - self.old_rows = widget._vte.get_row_count () - self.old_parent = widget.get_parent() - - if isinstance(self.old_parent, gtk.Window): - return - if isinstance(self.old_parent, gtk.Notebook): - self.old_page = self.old_parent.get_current_page() - self.old_label = self.old_parent.get_tab_label (self.old_parent.get_nth_page (self.old_page)) - - self.window_child = self.window.get_children()[0] - self.window.remove(self.window_child) - self.old_parent.remove(widget) - self.window.add(widget) - self._zoomed = True - - if fontscale: - self.cnid = widget.connect ("size-allocate", self.zoom_scale_font) - else: - self._maximised = True - - widget._vte.grab_focus () - - def zoom_scale_font (self, widget, allocation): - # Disconnect ourself so we don't get called again - widget.disconnect (self.cnid) - - new_columns = widget._vte.get_column_count () - new_rows = widget._vte.get_row_count () - new_font = widget._vte.get_font () - new_allocation = widget._vte.get_allocation () - - old_alloc = { 'x': self.old_allocation.width - self.old_padding[0], - 'y': self.old_allocation.height - self.old_padding[1] }; - - dbg ('zoom_scale_font: I just went from %dx%d to %dx%d.'%(self.old_columns, self.old_rows, new_columns, new_rows)) - - if (new_rows == self.old_rows) or (new_columns == self.old_columns): - dbg ('zoom_scale_font: At least one of my axes didn not change size. Refusing to zoom') - return - - old_char_spacing = old_alloc['x'] - (self.old_columns * self.old_char_width) - old_line_spacing = old_alloc['y'] - (self.old_rows * self.old_char_height) - dbg ('zoom_scale_font: char. %d = %d - (%d * %d)' % (old_char_spacing, old_alloc['x'], self.old_columns, self.old_char_width)) - dbg ('zoom_scale_font: lines. %d = %d - (%d * %d)' % (old_line_spacing, old_alloc['y'], self.old_rows, self.old_char_height)) - dbg ('zoom_scale_font: Previously my char spacing was %d and my row spacing was %d' % (old_char_spacing, old_line_spacing)) - - old_area = self.old_columns * self.old_rows - new_area = new_columns * new_rows - area_factor = new_area / old_area - dbg ('zoom_scale_font: My area changed from %d characters to %d characters, a factor of %f.'%(old_area, new_area, area_factor)) - - dbg ('zoom_scale_font: Post-scale-factor, char spacing should be %d and row spacing %d' % (old_char_spacing * (area_factor/2), old_line_spacing * (area_factor/2))) - dbg ('zoom_scale_font: char width should be %d, it was %d' % ((new_allocation.width - (old_char_spacing * (area_factor / 2)))/self.old_columns, self.old_char_width)) - dbg ('zoom_scale_font: char height should be %d, it was %d' % ((new_allocation.height - (old_line_spacing * (area_factor / 2)))/self.old_rows, self.old_char_height)) - - new_char_width = (new_allocation.width - (old_char_spacing * (area_factor / 2)))/self.old_columns - new_char_height = (new_allocation.height - (old_line_spacing * (area_factor / 2)))/self.old_rows - font_scaling_factor = min (float(new_char_width) / float(self.old_char_width), float(new_char_height) / float(self.old_char_height)) - - new_font_size = self.old_font.get_size () * font_scaling_factor * 0.9 - if new_font_size < self.old_font.get_size (): - dbg ('zoom_scale_font: new font size would have been smaller. bailing.') - return - - new_font.set_size (new_font_size) - dbg ('zoom_scale_font: Scaled font from %f to %f'%(self.old_font.get_size () / pango.SCALE, new_font.get_size () / pango.SCALE)) - widget._vte.set_font (new_font) - - def unzoom_term (self, widget, fontscale = False): - """Proof of concept: Go back to previous application - widget structure. - """ - if self._zoomed: - if fontscale: - widget._vte.set_font (self.old_font) - self._zoomed = False - self._maximised = False - - self.window.remove(widget) - self.window.add(self.window_child) - if isinstance(self.old_parent, gtk.Notebook): - self.old_parent.insert_page(widget, None, self.old_page) - self.old_parent.set_tab_label(widget, self.old_label) - self.old_parent.set_tab_label_packing(widget, not self.conf.scroll_tabbar, not self.conf.scroll_tabbar, gtk.PACK_START) - if self._tab_reorderable: - self.old_parent.set_tab_reorderable(widget, True) - self.old_parent.set_current_page(self.old_page) - - else: - self.old_parent.add(widget) - - widget._vte.grab_focus () - - def edit_profile (self, widget): - if not self.options: - self.options = ProfileEditor(self) - self.options.go() - - def group_emit (self, terminatorterm, group, type, event): - for term in self.term_list: - if term != terminatorterm and term._group == group: - term._vte.emit (type, event) - - def all_emit (self, terminatorterm, type, event): - for term in self.term_list: - if term != terminatorterm: - term._vte.emit (type, event) - - def group_hoover (self): - if self.autocleangroups: - destroy = [] - for group in self.groupings: - save = False - for term in self.term_list: - if term._group == group: - save = True - - if not save: - destroy.append (group) - - for group in destroy: - self.groupings.remove (group) + return([widget]) + + def group_tab(self, widget): + """Group all the terminals in a tab""" + pass + + def ungroup_tab(self, widget): + """Ungroup all the terminals in a tab""" + pass + + def focus_changed(self, widget): + """We just moved focus to a new terminal""" + for terminal in self.terminals: + terminal.titlebar.update() + return +# vim: set expandtab ts=4 sw=4: diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index fc47df9c..569f3d74 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -8,7 +8,7 @@ import gobject from version import APP_NAME from util import dbg -from newterminator import Terminator +from terminator import Terminator from editablelabel import EditableLabel # pylint: disable-msg=R0904 diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 55e7a6f1..663303db 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -13,7 +13,7 @@ from translation import _ from version import APP_NAME from container import Container from factory import Factory -from newterminator import Terminator +from terminator import Terminator try: import deskbar.core.keybinder as bindkey From 96ad94126763947273c7044aeadc9eeb2c43cd13 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 11 Jan 2010 20:11:35 +0000 Subject: [PATCH 281/331] Clear up pyflakes errata and switch newterminator to terminator --- terminator | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/terminator b/terminator index f79b5577..631d0c3e 100755 --- a/terminator +++ b/terminator @@ -37,9 +37,8 @@ except ImportError: 'gobject, gtk and pango to run Terminator.') sys.exit(1) -import terminatorlib.config import terminatorlib.optionparse -from terminatorlib.newterminator import Terminator +from terminatorlib.terminator import Terminator from terminatorlib.factory import Factory from terminatorlib.version import APP_NAME, APP_VERSION from terminatorlib.util import dbg From adfaf600faa2def91597cf078963d4bf594446d1 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 11 Jan 2010 20:56:30 +0000 Subject: [PATCH 282/331] Store the command line options in Config rather than overwriting parts of it, and passing them around --- terminator | 3 +-- terminatorlib/config.py | 9 +++++++ terminatorlib/optionparse.py | 13 +--------- terminatorlib/window.py | 46 ++++++++++++++++++++++++++---------- 4 files changed, 44 insertions(+), 27 deletions(-) diff --git a/terminator b/terminator index 631d0c3e..ab5e694f 100755 --- a/terminator +++ b/terminator @@ -50,8 +50,7 @@ if __name__ == '__main__': MAKER = Factory() TERMINATOR = Terminator() - WINDOW = MAKER.make('Window', geometry=OPTIONS.geometry, - forcedtitle=OPTIONS.forcedtitle, role=OPTIONS.role) + WINDOW = MAKER.make('Window') TERMINAL = MAKER.make('Terminal') WINDOW.add(TERMINAL) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 9cd11261..14adcb85 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -237,6 +237,14 @@ class Config(object): """Cause ConfigBase to save our config to file""" return(self.base.save()) + def options_set(self, options): + """Set the command line options""" + self.base.command_line_options = options + + def options_get(self): + """Get the command line options""" + return(self.base.command_line_options) + def plugin_get(self, pluginname, key): """Get a plugin config value""" return(self.base.get_item(key, plugin=pluginname)) @@ -262,6 +270,7 @@ class ConfigBase(Borg): keybindings = None plugins = None layouts = None + command_line_options = None def __init__(self): """Class initialiser""" diff --git a/terminatorlib/optionparse.py b/terminatorlib/optionparse.py index 513cb740..c49575ba 100755 --- a/terminatorlib/optionparse.py +++ b/terminatorlib/optionparse.py @@ -91,18 +91,7 @@ WM_WINDOW_ROLE property on the window') options.working_directory) sys.exit(1) - if options.maximise: - configobj['window_state'] = 'maximise' - - if options.fullscreen: - configobj['window_state'] = 'fullscreen' - - if options.borderless: - configobj['borderless'] = True - - if options.hidden: - configobj['window_state'] = 'hidden' - + configobj.options_set(options) # FIXME: Map all the other bits of options to configobj if util.DEBUG == True: diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 663303db..6b631b5c 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -35,7 +35,7 @@ class Window(Container, gtk.Window): zoom_data = None term_zoomed = gobject.property(type=bool, default=False) - def __init__(self, geometry=None, forcedtitle=None, role=None): + def __init__(self): """Class initialiser""" self.terminator = Terminator() self.terminator.register_window(self) @@ -53,15 +53,19 @@ class Window(Container, gtk.Window): self.title = WindowTitle(self) self.title.update() - if forcedtitle is not None: - self.title.force_title(forcedtitle) - if role is not None: - self.set_role(role) + options = self.config.options_get() + if options: + if options.forcedtitle is not None: + self.title.force_title(options.forcedtitle) - if geometry is not None: - if not self.parse_geometry(geometry): - err('Window::__init__: Unable to parse geometry: %s' % geometry) + if options.role is not None: + self.set_role(options.role) + + if options.geometry is not None: + if not self.parse_geometry(options.geometry): + err('Window::__init__: Unable to parse geometry: %s' % + options.geometry) def register_callbacks(self): """Connect the GTK+ signals we care about""" @@ -87,14 +91,30 @@ class Window(Container, gtk.Window): def apply_config(self): """Apply various configuration options""" - self.set_fullscreen(self.config['window_state'] == 'fullscreen') - self.set_maximised(self.config['window_state'] == 'maximise') - self.set_borderless(self.config['borderless']) + options = self.config.options_get() + maximise = self.config['window_state'] == 'maximise' + fullscreen = self.config['window_state'] == 'fullscreen' + hidden = self.config['window_state'] == 'hidden' + borderless = self.config['borderless'] + + if options: + if options.maximise: + maximise = True + if options.fullscreen: + fullscreen = True + if options.hidden: + hidden = True + if options.borderless: + borderless = True + + self.set_fullscreen(fullscreen) + self.set_maximised(maximise) + self.set_borderless(borderless) self.set_real_transparency() if self.hidebound: - self.set_hidden(self.config['window_state'] == 'hidden') + self.set_hidden(hidden) else: - self.set_iconified(self.config['window_state'] == 'hidden') + self.set_iconified(hidden) def apply_icon(self): """Set the window icon""" From 23f469add37f1fec58ac0c720662196c120fd5a1 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 11 Jan 2010 23:46:18 +0000 Subject: [PATCH 283/331] Fix up handle_size to work properly --- terminator | 1 + terminatorlib/preferences.glade | 2 +- terminatorlib/prefseditor.py | 2 +- terminatorlib/terminator.py | 7 ++++++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/terminator b/terminator index ab5e694f..fa857073 100755 --- a/terminator +++ b/terminator @@ -50,6 +50,7 @@ if __name__ == '__main__': MAKER = Factory() TERMINATOR = Terminator() + TERMINATOR.reconfigure() WINDOW = MAKER.make('Window') TERMINAL = MAKER.make('Terminal') diff --git a/terminatorlib/preferences.glade b/terminatorlib/preferences.glade index 8ae6d543..a9e4ee5d 100644 --- a/terminatorlib/preferences.glade +++ b/terminatorlib/preferences.glade @@ -2191,7 +2191,7 @@ -1 -1 - 5 + 7 1 2 2 diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 5c73df57..9569889d 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -213,7 +213,7 @@ class PrefsEditor: self.config['focus'] = value # Handle size widget = guiget('handlesize') - self.config['handle_size'] = widget.get_value() + self.config['handle_size'] = int(widget.get_value()) # Window geometry widget = guiget('wingeomcheck') self.config['geometry_hinting'] = widget.get_active() diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index 670b75c9..82c23190 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -88,7 +88,12 @@ class Terminator(Borg): def reconfigure(self): """Update configuration for the whole application""" - # FIXME: Set handle_size here + if self.config['handle_size'] in xrange(0, 6): + gtk.rc_parse_string("""style "terminator-paned-style" { + GtkPaned::handle_size = %s } + class "GtkPaned" style "terminator-paned-style" """ % + self.config['handle_size']) + gtk.rc_reset_styles(gtk.settings_get_default()) # Cause all the terminals to reconfigure for terminal in self.terminals: From 88463281377430507de8e4465e1c92923073c5bf Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 12 Jan 2010 00:33:55 +0000 Subject: [PATCH 284/331] scrollbar toggling is now significantly more useful and consistent --- terminatorlib/terminal.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index a6c11b79..26a21c49 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -63,6 +63,7 @@ class Terminal(gtk.VBox): vte = None terminalbox = None scrollbar = None + scrollbar_position = None titlebar = None searchbar = None @@ -158,12 +159,13 @@ class Terminal(gtk.VBox): terminalbox = gtk.HBox() self.scrollbar = gtk.VScrollbar(self.vte.get_adjustment()) - position = self.config['scrollbar_position'] + self.scrollbar.set_no_show_all(True) + self.scrollbar_position = self.config['scrollbar_position'] - if position not in ('hidden', 'disabled'): + if self.scrollbar_position not in ('hidden', 'disabled'): self.scrollbar.show() - if position == 'left': + if self.scrollbar_position == 'left': func = terminalbox.pack_end else: func = terminalbox.pack_start @@ -582,7 +584,16 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) self.vte.set_scroll_on_keystroke(self.config['scroll_on_keystroke']) self.vte.set_scroll_on_output(self.config['scroll_on_output']) - # FIXME: Do subtle scrollbar_position stuff here + if self.scrollbar_position != self.config['scrollbar_position']: + self.scrollbar_position = self.config['scrollbar_position'] + if self.config['scrollbar_position'] == 'disabled': + self.scrollbar.hide() + else: + self.scrollbar.show() + if self.config['scrollbar_position'] == 'left': + self.reorder_child(self.scrollbar, 0) + elif self.config['scrollbar_position'] == 'right': + self.reorder_child(self.vte, 0) if hasattr(self.vte, 'set_alternate_screen_scroll'): self.vte.set_alternate_screen_scroll(self.config['alternate_screen_scroll']) From c257a3abffd056f0b27eec0b54112bc46912a87e Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 12 Jan 2010 00:49:44 +0000 Subject: [PATCH 285/331] remove old file --- terminatorlib/terminatorterm.py | 1925 ------------------------------- 1 file changed, 1925 deletions(-) delete mode 100755 terminatorlib/terminatorterm.py diff --git a/terminatorlib/terminatorterm.py b/terminatorlib/terminatorterm.py deleted file mode 100755 index c7378f22..00000000 --- a/terminatorlib/terminatorterm.py +++ /dev/null @@ -1,1925 +0,0 @@ -#!/usr/bin/python -# Terminator - multiple gnome terminals in one window -# Copyright (C) 2006-2008 cmsj@tenshu.net -# -# 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, version 2 only. -# -# 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 - -"""Terminator by Chris Jones """ -import pygtk -pygtk.require ("2.0") -import gobject, gtk, pango -import os, signal, sys, subprocess, pwd, re, urllib2 - -#import version details -from terminatorlib.version import * - -# import our configuration loader -from terminatorlib import config -from terminatorlib.util import dbg, err, debug - -#import encoding list -from terminatorlib.encoding import TerminatorEncoding -from editablelabel import EditableLabel -# import translation support -from terminatorlib import translation - -# import vte-bindings -try: - import vte -except ImportError: - error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, - _('You need to install python bindings for libvte ("python-vte" in debian/ubuntu)')) - error.run() - sys.exit (1) - -class TerminatorTermTitle (gtk.EventBox): - wanted = None - _title = None - _termtext = "" - _sizetext = "" - _group = None - _separator = None - _hbox = None - _ebox = None - _grouphbox = None - _icon = None - _parent = None - _unzoomed_title = None - _terminal = None - terminator = None - - def __init__ (self, terminal, terminator, configwanted = False): - gtk.EventBox.__init__ (self) - - self._title = EditableLabel() - self._group = gtk.Label () - self._separator = gtk.VSeparator () - self._ebox = gtk.EventBox () - self._grouphbox = gtk.HBox () - self._icon = gtk.Image () - self._hbox = gtk.HBox () - self._terminal = terminal - - self.terminator = terminator - if self.terminator.groupsend == 2: - self.set_from_icon_name (APP_NAME + \ - '_active_broadcast_all', gtk.ICON_SIZE_MENU) - elif self.terminator.groupsend == 1: - self.set_from_icon_name (APP_NAME + \ - '_active_broadcast_group', gtk.ICON_SIZE_MENU) - else: - self.set_from_icon_name (APP_NAME + \ - '_active_broadcast_off', gtk.ICON_SIZE_MENU) - - self._grouphbox.pack_start (self._icon, False, True, 2) - self._grouphbox.pack_start (self._group, False, True, 2) - self._ebox.add (self._grouphbox) - self._ebox.show_all () - - self._hbox.pack_start (self._ebox, False, True, 0) - self._hbox.pack_start (self._separator, False, True, 0) - self._hbox.pack_start (self._title, True, True) - self.add (self._hbox) - - self._title.show_all () - self._hbox.show () - - self.wanted = configwanted - - self.connect ("button-press-event", self.on_clicked) - - def connect_icon (self, func): - self._ebox.connect ("button-release-event", func) - - def on_clicked (self, widget, event): - if self._parent is not None: - self._parent._vte.grab_focus () - - def set_group_label (self, name): - """If 'name' is None, hide the group name object, otherwise set it as the group label""" - if name: - self._group.set_text (name) - self._group.show () - else: - self._group.hide () - self._separator.show () - - def set_terminal_title (self, name): - """Set the title text shown in the titlebar""" - self._termtext = name - self.update_label () - - def set_terminal_size (self, width, height): - """Set the terminal size shown in the titlebar""" - self._sizetext = "%sx%s" % (width, height) - self.update_label () - - def update_label (self): - """Update the gtk label with values previously set""" - self._title.set_text ("%s %s" % (self._termtext, self._sizetext)) - - def get_terminal_title (self): - """Return the text showin in the titlebar""" - return (self._termtext) - - def set_from_icon_name (self, name, size = gtk.ICON_SIZE_MENU): - """Set an icon for the group label""" - if not name: - self._icon.hide () - return - - self._icon.set_from_icon_name (APP_NAME + name, size) - self._icon.show () - - def update_colors(self, source): - """Update terminals titlebar colours based on grouping""" - term = self._terminal - if term != source and term._group != None and term._group == source._group: - # Not active, group is not none, and in active's group - if self.terminator.groupsend == 0: - title_fg = term.conf.title_ia_txt_color - title_bg = term.conf.title_ia_bg_color - icon = '_receive_off' - else: - title_fg = term.conf.title_rx_txt_color - title_bg = term.conf.title_rx_bg_color - icon = '_receive_on' - group_fg = term.conf.title_rx_txt_color - group_bg = term.conf.title_rx_bg_color - elif term != source and term._group == None or term._group != source._group: - # Not active, group is not none, not in active's group - if self.terminator.groupsend == 2: - title_fg = term.conf.title_rx_txt_color - title_bg = term.conf.title_rx_bg_color - icon = '_receive_on' - else: - title_fg = term.conf.title_ia_txt_color - title_bg = term.conf.title_ia_bg_color - icon = '_receive_off' - group_fg = term.conf.title_ia_txt_color - group_bg = term.conf.title_ia_bg_color - else: - title_fg = term.conf.title_tx_txt_color - title_bg = term.conf.title_tx_bg_color - if self.terminator.groupsend == 2: - icon = '_active_broadcast_all' - elif self.terminator.groupsend == 1: - icon = '_active_broadcast_group' - else: - icon = '_active_broadcast_off' - group_fg = term.conf.title_tx_txt_color - group_bg = term.conf.title_tx_bg_color - - self._title.modify_fg (gtk.STATE_NORMAL, gtk.gdk.color_parse (title_fg)) - self._group.modify_fg (gtk.STATE_NORMAL, gtk.gdk.color_parse (group_fg)) - self.modify_bg (gtk.STATE_NORMAL, gtk.gdk.color_parse (title_bg)) - self._ebox.modify_bg (gtk.STATE_NORMAL, gtk.gdk.color_parse (group_bg)) - self.set_from_icon_name(icon, gtk.ICON_SIZE_MENU) - return - - def update (self): - """Update our state""" - if not self._parent: - self._parent = self.get_parent () - - if self._parent.terminator._zoomed and len (self._parent.terminator.term_list): - if not self._unzoomed_title: - self._unzoomed_title = self.get_terminal_title () - if self._parent.conf.zoomedtitlebar: - self.set_terminal_title ("Zoomed/Maximised terminal, %d hidden" % (len (self._parent.terminator.term_list) - 1)) - self.show() - else: - self.hide() - return - else: - if self._unzoomed_title: - self.set_terminal_title (self._unzoomed_title) - self._unzoomed_title = None - - if isinstance (self._parent.get_parent (), gtk.Window): - self.hide() - return - - if (self._parent.conf.titlebars and self.wanted) or self._parent._group: - self.show () - else: - self.hide () - - if self._parent._group: - self.set_group_label (self._parent._group) - else: - self.set_group_label (None) - -class TerminatorTerm (gtk.VBox): - - matches = None - TARGET_TYPE_VTE = 8 - _custom_font_size = None - _custom_encoding = None - _default_encoding = None - _group = None - focus = None - _urgent_bell_cnid = None - - def __init__ (self, terminator, profile = None, command = None, cwd = None): - gtk.VBox.__init__ (self) - self.terminator = terminator - self.conf = terminator.conf - self.command = command - self._oldtitle = "" - self.matches = {} - - self.cwd = cwd or os.getcwd(); - if not os.path.exists(self.cwd) or not os.path.isdir(self.cwd): - self.cwd = pwd.getpwuid(os.getuid ())[5] - - self.clipboard = gtk.clipboard_get (gtk.gdk.SELECTION_CLIPBOARD) - self.scrollbar_position = self.conf.scrollbar_position - - self._composited_support = True - self._vte = vte.Terminal () - self._default_encoding = self._vte.get_encoding() - if not hasattr(self._vte, "set_opacity") or not hasattr(self._vte, "is_composited"): - self._composited_support = False - dbg ('H9TRANS: composited_support: %s' % self._composited_support) - #self._vte.set_double_buffered(True) - self._vte.set_size (80, 24) - self._vte._expose_data = None - self._vte.show () - - self._termbox = gtk.HBox () - self._termbox.show() - - self._titlebox = TerminatorTermTitle (self, self.terminator, self.conf.titlebars) - - self._search_string = None - self._searchbox = gtk.HBox() - self._searchinput = gtk.Entry() - self._searchinput.set_activates_default(True) - self._searchinput.show() - - self._searchinput.connect('activate', self.do_search) - self._searchinput.connect('key-press-event', self.search_keypress) - - slabel = gtk.Label() - slabel.set_text(_("Search:")) - slabel.show() - - sclose = gtk.Button() - sclose.set_relief(gtk.RELIEF_NONE) - sclose.set_focus_on_click(False) - sclose.set_relief(gtk.RELIEF_NONE) - sclose_icon = gtk.Image() - sclose_icon.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) - sclose.add(sclose_icon) - sclose.set_name("terminator-search-close-button") - if hasattr(sclose, "set_tooltip_text"): - sclose.set_tooltip_text("Close Search Bar") - sclose.connect('clicked', self.end_search) - sclose.show_all() - - # Button for the next result. Explicitly not show()n by default. - self._search_next = gtk.Button(_("Next")) - self._search_next.connect('clicked', self.next_search) - - self._searchbox.pack_start(slabel, False) - self._search_result_label = gtk.Label() - self._search_result_label.set_text("") - self._search_result_label.show() - self._searchbox.pack_start(self._searchinput) - self._searchbox.pack_start(self._search_result_label, False) - self._searchbox.pack_start(self._search_next, False, False) - self._searchbox.pack_end(sclose, False, False) - - self.show() - self.pack_start(self._titlebox, False) - self.pack_start(self._termbox) - self.pack_end(self._searchbox) - - self._titlebox.update () - - self._scrollbar = gtk.VScrollbar (self._vte.get_adjustment ()) - if self.scrollbar_position != "hidden" and self.scrollbar_position != "disabled": - self._scrollbar.show () - - if self.scrollbar_position == 'left': - packfunc = self._termbox.pack_end - else: - packfunc = self._termbox.pack_start - - packfunc (self._vte) - packfunc (self._scrollbar, False) - - self._vte.connect ("key-press-event", self.on_vte_key_press) - self._vte.connect ("button-press-event", self.on_vte_button_press) - self._vte.connect ("popup-menu", self.create_popup_menu) - """drag and drop""" - srcvtetargets = [ ( "vte", gtk.TARGET_SAME_APP, self.TARGET_TYPE_VTE ) ] - dsttargets = [ ( "vte", gtk.TARGET_SAME_APP, self.TARGET_TYPE_VTE ), ('text/plain', 0, 0) , ("STRING", 0, 0), ("COMPOUND_TEXT", 0, 0)] - self._vte.drag_source_set( gtk.gdk.CONTROL_MASK | gtk.gdk.BUTTON3_MASK, srcvtetargets, gtk.gdk.ACTION_MOVE) - self._titlebox.drag_source_set( gtk.gdk.BUTTON1_MASK, srcvtetargets, gtk.gdk.ACTION_MOVE) - #self._vte.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_HIGHLIGHT |gtk.DEST_DEFAULT_DROP ,dsttargets, gtk.gdk.ACTION_MOVE) - self._vte.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_HIGHLIGHT |gtk.DEST_DEFAULT_DROP ,dsttargets, gtk.gdk.ACTION_MOVE) - self._vte.connect("drag-begin", self.on_drag_begin, self) - self._titlebox.connect("drag-begin", self.on_drag_begin, self) - self._vte.connect("drag-data-get", self.on_drag_data_get, self) - self._titlebox.connect("drag-data-get", self.on_drag_data_get, self) - #for testing purpose: drag-motion - self._vte.connect("drag-motion", self.on_drag_motion, self) - self._vte.connect("drag-data-received", self.on_drag_data_received, self) - - if self.conf.copy_on_selection: - self._vte.connect ("selection-changed", lambda widget: self._vte.copy_clipboard ()) - if self._composited_support : - self._vte.connect ("composited-changed", self.on_composited_changed) - self._vte.connect ("window-title-changed", self.on_vte_title_change) - self._vte.connect ("grab-focus", self.on_vte_focus) - self._vte.connect ("focus-out-event", self.on_vte_focus_out) - self._vte.connect ("focus-in-event", self.on_vte_focus_in) - self._vte.connect ("resize-window", self.on_resize_window) - self._vte.connect ("size-allocate", self.on_vte_size_allocate) - - self._titlebox.connect_icon (self.on_group_button_press) - - exit_action = self.conf.exit_action - if exit_action == "restart": - self._vte.connect ("child-exited", self.spawn_child) - # We need to support "left" because some buggy versions of gnome-terminal - # set it in some situations - elif exit_action in ("close", "left"): - self._vte.connect ("child-exited", lambda close_term: self.terminator.closeterm (self)) - - self._vte.add_events (gtk.gdk.ENTER_NOTIFY_MASK) - self._vte.connect ("enter_notify_event", self.on_vte_notify_enter) - - self._vte.connect_after ("realize", self.reconfigure_vte) - - self.add_matches(posix = self.conf.try_posix_regexp) - - env_proxy = os.getenv ('http_proxy') - if not env_proxy and self.conf.http_proxy and self.conf.http_proxy != '': - os.putenv ('http_proxy', self.conf.http_proxy) - - os.putenv ('COLORTERM', 'gnome-terminal') - - def prepareurl (self, url, match): - dbg ("prepareurl: Checking '%s' with a match of '%s'" % (url, match)) - if match == self.matches['email'] and url[0:7] != 'mailto:': - url = 'mailto:' + url - elif match == self.matches['addr_only'] and url[0:3] == 'ftp': - url = 'ftp://' + url - elif match == self.matches['addr_only']: - url = 'http://' + url - elif match == self.matches['launchpad']: - for item in re.findall(r'[0-9]+',url): - url = 'https://bugs.launchpad.net/bugs/%s' % item - return url - - return url - - def openurl (self, url): - dbg ('openurl: viewing %s'%url) - try: - dbg ('openurl: calling xdg-open') - subprocess.Popen(["xdg-open", url]) - except: - dbg ('openurl: xdg-open failed') - try: - dbg ('openurl: calling url_show') - self.terminator.url_show (url) - except: - dbg ('openurl: url_show failed. No URL for you') - pass - - def on_resize_window(self, widget, width, height): - dbg ('Resize window triggered on %s: %dx%d' % (widget, width, height)) - - def on_vte_size_allocate(self, widget, allocation): - dbg ('Terminal resized to %dx%d' % (self._vte.get_column_count (), - self._vte.get_row_count ())) - self._titlebox.set_terminal_size (self._vte.get_column_count (), self._vte.get_row_count ()) - if self._vte.window != None and (self.conf.geometry_hinting): - self.terminator.on_term_resized () - - def get_pixbuf(self, maxsize= None): - pixmap = self.get_snapshot() - (width, height) = pixmap.get_size() - pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height) - pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, width, height) - - longest = max(width, height) - - if maxsize is not None: - factor = float(maxsize) / float(longest) - - if not maxsize or (width * factor) > width or (height * factor) > height: - factor = 1 - - scaledpixbuf = pixbuf.scale_simple (int(width * factor), int(height * factor), gtk.gdk.INTERP_BILINEAR) - - return(scaledpixbuf) - - def on_drag_begin(self, widget, drag_context, data): - dbg ('Drag begins') - widget.drag_source_set_icon_pixbuf(self.get_pixbuf (512)) - - def on_drag_data_get(self,widget, drag_context, selection_data, info, time, data): - dbg ("Drag data get") - selection_data.set("vte",info, str(data.terminator.term_list.index (self))) - - def on_expose_event(self, widget, event): - if widget._expose_data is None: - return False - - color = widget._expose_data['color'] - coord = widget._expose_data['coord'] - - context = widget.window.cairo_create() - #leaving those xxx_group as they could be usefull - ##http://macslow.thepimp.net/?p=153 - #context.push_group() - context.set_source_rgba(color.red, color.green, color.blue, 0.5) - if len(coord) > 0 : - context.move_to(coord[len(coord)-1][0],coord[len(coord)-1][1]) - for i in coord: - context.line_to(i[0],i[1]) - - context.fill() - #context.pop_group_to_source() - #context.paint() - return False - - def on_drag_motion(self, widget, drag_context, x, y, time, data): - dbg ("Drag Motion on ") - """ -x-special/gnome-icon-list -text/uri-list -UTF8_STRING -COMPOUND_TEXT -TEXT -STRING -text/plain;charset=utf-8 -text/plain;charset=UTF-8 -text/plain - """ - - if 'text/plain' in drag_context.targets: - #copy text from another widget - return - srcwidget = drag_context.get_source_widget() - if (isinstance(srcwidget, gtk.EventBox) and srcwidget == self._titlebox) or widget == srcwidget: - #on self - return - - alloc = widget.allocation - rect = gtk.gdk.Rectangle(0, 0, alloc.width, alloc.height) - - if self.conf.use_theme_colors: - color = self._vte.get_style ().text[gtk.STATE_NORMAL] - else: - color = gtk.gdk.color_parse (self.conf.foreground_color) - - pos = self.get_location(widget, x, y) - topleft = (0,0) - topright = (alloc.width,0) - topmiddle = (alloc.width/2,0) - bottomleft = (0, alloc.height) - bottomright = (alloc.width,alloc.height) - bottommiddle = (alloc.width/2, alloc.height) - middle = (alloc.width/2, alloc.height/2) - middleleft = (0, alloc.height/2) - middleright = (alloc.width, alloc.height/2) - #print "%f %f %d %d" %(coef1, coef2, b1,b2) - coord = () - if pos == "right": - coord = (topright, topmiddle, bottommiddle, bottomright) - if pos == "top": - coord = (topleft, topright, middleright , middleleft) - if pos == "left": - coord = (topleft, topmiddle, bottommiddle, bottomleft) - if pos == "bottom": - coord = (bottomleft, bottomright, middleright , middleleft) - - - #here, we define some widget internal values - widget._expose_data = { 'color': color, 'coord' : coord } - #redraw by forcing an event - connec = widget.connect_after('expose-event', self.on_expose_event) - widget.window.invalidate_rect(rect, True) - widget.window.process_updates(True) - #finaly reset the values - widget.disconnect(connec) - widget._expose_data = None - - def on_drag_drop(self, widget, drag_context, x, y, time): - parent = widget.get_parent() - dbg ('Drag drop on %s'%parent) - - def get_target_terms(self): - if self.terminator.groupsend == 2: - return self.terminator.term_list - elif self.terminator.groupsend == 1: - term_subset = [] - for term in self.terminator.term_list: - if term == self or (term._group != None and term._group == self._group): - term_subset.append(term) - return term_subset - else: - return [self] - - def on_drag_data_received(self, widget, drag_context, x, y, selection_data, info, time, data): - dbg ("Drag Data Received") - if selection_data.type == 'text/plain': - #copy text to destination - #print "%s %s" % (selection_data.type, selection_data.target) - txt = selection_data.data.strip() - if txt[0:7] == "file://": - txt = "'%s'" % urllib2.unquote(txt[7:]) - for term in self.get_target_terms(): - term._vte.feed_child(txt) - return - - widgetsrc = data.terminator.term_list[int(selection_data.data)] - srcvte = drag_context.get_source_widget() - #check if computation requireds - if (isinstance(srcvte, gtk.EventBox) and srcvte == self._titlebox) or srcvte == widget: - dbg (" on itself") - return - - srchbox = widgetsrc - dsthbox = widget.get_parent().get_parent() - - dstpaned = dsthbox.get_parent() - srcpaned = srchbox.get_parent() - if isinstance(dstpaned, gtk.Window) and isinstance(srcpaned, gtk.Window): - dbg (" Only one terminal") - return - pos = self.get_location(widget, x, y) - - data.terminator.remove(widgetsrc, True) - data.terminator.add(self, widgetsrc,pos) - return - - def get_location(self, vte, x, y): - pos = "" - #get the diagonales function for the receiving widget - coef1 = float(vte.allocation.height)/float(vte.allocation.width) - coef2 = -float(vte.allocation.height)/float(vte.allocation.width) - b1 = 0 - b2 = vte.allocation.height - #determine position in rectangle - """ - -------- - |\ /| - | \ / | - | \/ | - | /\ | - | / \ | - |/ \| - -------- - """ - if (x*coef1 + b1 > y ) and (x*coef2 + b2 < y ): - pos = "right" - if (x*coef1 + b1 > y ) and (x*coef2 + b2 > y ): - pos = "top" - if (x*coef1 + b1 < y ) and (x*coef2 + b2 > y ): - pos = "left" - if (x*coef1 + b1 < y ) and (x*coef2 + b2 < y ): - pos = "bottom" - return pos - - def add_matches (self, posix = True): - userchars = "-A-Za-z0-9" - passchars = "-A-Za-z0-9,?;.:/!%$^*&~\"#'" - hostchars = "-A-Za-z0-9" - pathchars = "-A-Za-z0-9_$.+!*(),;:@&=?/~#%'\"" - schemes = "(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:)" - user = "[" + userchars + "]+(:[" + passchars + "]+)?" - urlpath = "/[" + pathchars + "]*[^]'.}>) \t\r\n,\\\"]" - - if posix: - dbg ('add_matches: Trying POSIX URL regexps. Set try_posix_regexp = False in config to only try GNU if you get (harmless) VTE warnings.') - lboundry = "[[:<:]]" - rboundry = "[[:>:]]" - else: # GNU - dbg ('add_matches: Trying GNU URL regexps. Set try_posix_regexp = True in config if URLs are not detected.') - lboundry = "\\<" - rboundry = "\\>" - - self.matches['full_uri'] = self._vte.match_add(lboundry + schemes + "//(" + user + "@)?[" + hostchars +".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?") - - if self.matches['full_uri'] == -1: - if posix: - err ('add_matches: POSIX match failed, trying GNU') - self.add_matches(posix = False) - else: - err ('add_matches: Failed adding URL match patterns') - else: - self.matches['voip'] = self._vte.match_add(lboundry + '(callto:|h323:|sip:)' + "[" + userchars + "+][" + userchars + ".]*(:[0-9]+)?@?[" + pathchars + "]+" + rboundry) - self.matches['addr_only'] = self._vte.match_add (lboundry + "(www|ftp)[" + hostchars + "]*\.[" + hostchars + ".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?") - self.matches['email'] = self._vte.match_add (lboundry + "(mailto:)?[a-zA-Z0-9][a-zA-Z0-9.+-]*@[a-zA-Z0-9][a-zA-Z0-9-]*\.[a-zA-Z0-9][a-zA-Z0-9-]+[.a-zA-Z0-9-]*" + rboundry) - self.matches['nntp'] = self._vte.match_add (lboundry + '''news:[-A-Z\^_a-z{|}~!"#$%&'()*+,./0-9;:=?`]+@[-A-Za-z0-9.]+(:[0-9]+)?''' + rboundry) - # if the url looks like a Launchpad changelog closure entry LP: #92953 - make it a url to http://bugs.launchpad.net - # the regular expression is similar to the perl one specified in the Ubuntu Policy Manual - /lp:\s+\#\d+(?:,\s*\#\d+)*/i - self.matches['launchpad'] = self._vte.match_add ('\\b(lp|LP):?\s?#?[0-9]+(,\s*#?[0-9]+)*\\b') - - def _path_lookup(self, command): - if os.path.isabs (command): - if os.path.isfile (command): - return command - else: - return None - elif command[:2] == './' and os.path.isfile(command): - dbg('path_lookup: Relative filename "%s" found in cwd' % command) - return command - - try: - paths = os.environ['PATH'].split(':') - if len(paths[0]) == 0: raise (ValueError) - except (ValueError, NameError): - dbg('path_lookup: PATH not set in environment, using fallbacks') - paths = ['/usr/local/bin', '/usr/bin', '/bin'] - - dbg('path_lookup: Using %d paths: %s' % (len(paths), paths)) - - for path in paths: - target = os.path.join (path, command) - if os.path.isfile (target): - dbg('path_lookup: found "%s"' % target) - return target - - dbg('path_lookup: Unable to locate "%s"' % command) - - def _shell_lookup(self): - shells = [os.getenv('SHELL'), pwd.getpwuid(os.getuid())[6], - 'bash', 'zsh', 'tcsh', 'ksh', 'csh', 'sh'] - - for shell in shells: - if shell is None: continue - elif os.path.isfile (shell): - return shell - else: - rshell = self._path_lookup(shell) - if rshell is not None: - dbg('shell_lookup: Found "%s" at "%s"' % (shell, rshell)) - return rshell - - dbg('shell_lookup: Unable to locate a shell') - - def spawn_child (self, event=None): - update_records = self.conf.update_records - login = self.conf.login_shell - args = [] - shell = None - command = None - - if self.command: - dbg ('spawn_child: using self.command: %s' % self.command) - command = self.command - elif self.conf.use_custom_command: - dbg ('spawn_child: using custom command: %s' % self.conf.custom_command) - command = self.conf.custom_command - - if type(command) is list: - # List of arguments from -x - dbg('spawn_child: Bypassing shell and trying to run "%s" directly' % command[0]) - shell = self._path_lookup(command[0]) - args = command - else: - shell = self._shell_lookup() - - if self.conf.login_shell: - args.insert(0, "-%s" % shell) - else: - args.insert(0, shell) - - if command is not None: - args += ['-c', command] - - if shell is None: - # Give up, we're completely stuck - err (_('Unable to find a shell')) - gobject.timeout_add (100, self.terminator.closeterm, self) - return (-1) - - os.putenv ('WINDOWID', '%s' % self._vte.get_parent_window().xid) - - self._pid = self._vte.fork_command (command = shell, argv = args, - envv = [], loglastlog = login, logwtmp = update_records, - logutmp = update_records, directory=self.cwd) - - self.on_vte_title_change(self._vte) # Force an initial update of our titles - self._titlebox.update () - - if self._pid == -1: - err (_('Unable to start shell: ') + shell) - return (-1) - - def get_cwd (self): - """ Return the current working directory of the subprocess. - This function requires OS specific behaviours - """ - try: - cwd = self.terminator.pid_get_cwd (self._pid) - except OSError: - err ('get_cwd: unable to get cwd of %d' % self._pid) - cwd = '~' - pass - dbg ('get_cwd found: %s'%cwd) - return (cwd) - - def reconfigure_vte (self, widget = None): - # Set our emulation - self._vte.set_emulation (self.conf.emulation) - - # Set our charset - if self._custom_encoding == False or self._custom_encoding == None: - self._vte.set_encoding (self.conf.encoding) - - # Set our wordchars - self._vte.set_word_chars (self.conf.word_chars) - - # Set our mouselation - self._vte.set_mouse_autohide (self.conf.mouse_autohide) - - # Set our compatibility - backspace = self.conf.backspace_binding - delete = self.conf.delete_binding - -# Note, each of the 4 following comments should replace the line beneath it, but the python-vte bindings don't appear to support this constant, so the magic values are being assumed from the C enum :/ - if backspace == "ascii-del": -# backbind = vte.ERASE_ASCII_BACKSPACE - backbind = 2 - else: -# backbind = vte.ERASE_AUTO_BACKSPACE - backbind = 1 - - if delete == "escape-sequence": -# delbind = vte.ERASE_DELETE_SEQUENCE - delbind = 3 - else: -# delbind = vte.ERASE_AUTO - delbind = 0 - - self._vte.set_backspace_binding (backbind) - self._vte.set_delete_binding (delbind) - - # Set our font - if not self._custom_font_size: - try: - self._vte.set_font (pango.FontDescription (self.conf.font)) - except: - pass - - # Set our boldness - self._vte.set_allow_bold (self.conf.allow_bold) - - # Set our color scheme - palette = self.conf.palette - if self.conf.use_theme_colors: - fg_color = self._vte.get_style ().text[gtk.STATE_NORMAL] - bg_color = self._vte.get_style ().base[gtk.STATE_NORMAL] - else: - fg_color = gtk.gdk.color_parse (self.conf.foreground_color) - bg_color = gtk.gdk.color_parse (self.conf.background_color) - - colors = palette.split (':') - palette = [] - for color in colors: - if color: - palette.append (gtk.gdk.color_parse (color)) - self._vte.set_colors (fg_color, bg_color, palette) - - cursor_color = self.conf.cursor_color - if cursor_color != '': - self._vte.set_color_cursor (gtk.gdk.color_parse (cursor_color)) - - # Set cursor shape - if hasattr (self._vte, "set_cursor_shape"): - self._vte.set_cursor_shape (getattr (vte, "CURSOR_SHAPE_" + self.conf.cursor_shape.upper ())) - - # Set our background image, transparency and type - # Many thanks to the authors of gnome-terminal, on which this code is based. - background_type = self.conf.background_type - dbg ('H9TRANS: Configuring background type as: %s' % background_type) - - # set background image settings - if background_type == "image" and self.conf.background_image is not None and self.conf.background_image != '': - dbg ('H9TRANS: Setting background image to: %s' % self.conf.background_image) - self._vte.set_background_image_file (self.conf.background_image) - dbg ('H9TRANS: Setting background image scroll to: %s' % self.conf.scroll_background) - self._vte.set_scroll_background (self.conf.scroll_background) - else: - dbg ('H9TRANS: Unsetting background image') - self._vte.set_background_image_file('') - dbg ('H9TRANS: Unsetting background image scrolling') - self._vte.set_scroll_background(False) - - # set transparency for the background (image) - opacity = 65535 - if background_type in ("image", "transparent"): - self._vte.set_background_tint_color (gtk.gdk.color_parse (self.conf.background_color)) - self._vte.set_background_saturation(1 - (self.conf.background_darkness)) - opacity = int(self.conf.background_darkness * 65535) - dbg ('H9TRANS: Set background tint color to: %s' % self.conf.background_color) - dbg ('H9TRANS: Set background saturation to: %s' % (1 - (self.conf.background_darkness))) - else: - dbg ('H9TRANS: Set background saturation to: 1') - self._vte.set_background_saturation(1) - - if self._composited_support: - dbg ('H9TRANS: Set opacity to: %s' % opacity) - self._vte.set_opacity(opacity) - - if background_type == "transparent": - if not self.conf.enable_real_transparency: - self._vte.set_background_transparent (True) - else: - self._vte.set_background_transparent (False) - - # Set our cursor blinkiness - self._vte.set_cursor_blinks (self.conf.cursor_blink) - - if self.conf.force_no_bell: - self._vte.set_audible_bell (False) - self._vte.set_visible_bell (False) - if self._urgent_bell_cnid: - self._vte.disconnect (self._urgent_bell_cnid) - self._urgent_bell_cnid = None - else: - # Set our audible belliness - self._vte.set_audible_bell (self.conf.audible_bell) - - # Set our visual flashiness - self._vte.set_visible_bell (self.conf.visible_bell) - - # Set our urgent belliness - if self.conf.urgent_bell: - try: - self._urgent_bell_cnid = self._vte.connect ("beep", self.terminator.on_beep) - except TypeError: - err ("beep signal not supported by your VTE, urgent handler not available") - elif self._urgent_bell_cnid: - self._vte.disconnect (self._urgent_bell_cnid) - self._urgent_bell_cnid = None - - # Set our scrolliness - self._vte.set_scrollback_lines (self.conf.scrollback_lines) - self._vte.set_scroll_on_keystroke (self.conf.scroll_on_keystroke) - self._vte.set_scroll_on_output (self.conf.scroll_on_output) - - if self.scrollbar_position != self.conf.scrollbar_position: - self.scrollbar_position = self.conf.scrollbar_position - - if self.scrollbar_position == 'hidden' or self.scrollbar_position == 'disabled': - self._scrollbar.hide () - else: - self._scrollbar.show () - if self.scrollbar_position == 'right': - self._termbox.reorder_child (self._vte, 0) - elif self.scrollbar_position == 'left': - self._termbox.reorder_child (self._scrollbar, 0) - - if hasattr (self._vte, "set_alternate_screen_scroll"): - self._vte.set_alternate_screen_scroll (self.conf.alternate_screen_scroll) - - # Set our sloppiness - self.focus = self.conf.focus - - # Sync our titlebar state - self._titlebox.update () - self._vte.queue_draw () - - def get_size_details(self): - font_width = self._vte.get_char_width () - font_height = self._vte.get_char_height () - columns = self._vte.get_column_count () - rows = self._vte.get_row_count () - - return (font_width, font_height, columns, rows) - - def on_composited_changed (self, widget): - self.reconfigure_vte () - - def on_vte_button_press (self, term, event): - # Left mouse button + Ctrl while over a link should open it - mask = gtk.gdk.CONTROL_MASK - if (event.state & mask) == mask: - if event.button == 1: - url = self._vte.match_check (int (event.x / self._vte.get_char_width ()), int (event.y / self._vte.get_char_height ())) - if url: - self.openurl (self.prepareurl (url[0], url[1])) - return False - - # Left mouse button should transfer focus to this vte widget - # we also need to give focus on the widget where the paste occured - if event.button in (1 ,2): - if event.button == 2: - self.paste_clipboard (True) - return True - self._vte.grab_focus () - return False - - # Right mouse button should display a context menu if ctrl not pressed - if event.button == 3 and event.state & gtk.gdk.CONTROL_MASK == 0: - self.create_popup_menu (self._vte, event) - return True - - def on_vte_notify_enter (self, term, event): - if (self.focus == "sloppy" or self.focus == "mouse"): - term.grab_focus () - return False - - def do_autocleangroups_toggle (self): - self.terminator.autocleangroups = not self.terminator.autocleangroups - if self.terminator.autocleangroups: - self.terminator.group_hoover() - - def do_scrollbar_toggle (self): - self.toggle_widget_visibility (self._scrollbar) - - def do_splittogroup_toggle (self): - self.terminator.splittogroup = not self.terminator.splittogroup - - def do_title_toggle (self): - self._titlebox.wanted = not self._titlebox.get_property ('visible') - self.toggle_widget_visibility (self._titlebox) - - def toggle_widget_visibility (self, widget): - if not isinstance (widget, gtk.Widget): - raise TypeError - - if widget.get_property ('visible'): - widget.hide () - else: - widget.show () - - def paste_clipboard(self, primary = False): - for term in self.get_target_terms(): - if primary: - term._vte.paste_primary () - else: - term._vte.paste_clipboard () - self._vte.grab_focus() - - def do_enumerate(self, pad=False): - if pad: - numstr='%0'+str(len(str(len(self.terminator.term_list))))+'d' - else: - numstr='%d' - for term in self.get_target_terms(): - idx=self.terminator.term_list.index(term) - term._vte.feed_child(numstr % (idx+1)) - - #keybindings for the individual splited terminals (affects only the - #the selected terminal) - UnhandledKeybindings = ('close_window', 'full_screen') - def on_vte_key_press (self, term, event): - if not event: - dbg ('on_vte_key_press: Called on %s with no event' % term) - return False - mapping = self.terminator.keybindings.lookup(event) - - if mapping == "hide_window": - return False - - if mapping and mapping not in self.UnhandledKeybindings: - dbg("on_vte_key_press: lookup found %r" % mapping) - # handle the case where user has re-bound copy to ctrl+ - # we only copy if there is a selection otherwise let it fall through to ^ - if (mapping == "copy" and event.state & gtk.gdk.CONTROL_MASK): - if self._vte.get_has_selection (): - getattr(self, "key_" + mapping)() - return True - else: - getattr(self, "key_" + mapping)() - return True - - if self.terminator.groupsend != 0 and self._vte.is_focus (): - if self._group and self.terminator.groupsend == 1: - self.terminator.group_emit (self, self._group, 'key-press-event', event) - if self.terminator.groupsend == 2: - self.terminator.all_emit (self, 'key-press-event', event) - return False - - # Key events - def key_zoom_in(self): - self.zoom (True) - - def key_zoom_out(self): - self.zoom (False) - - def key_copy(self): - self._vte.copy_clipboard () - - def key_paste(self): - self.paste_clipboard () - - def key_toggle_scrollbar(self): - self.do_scrollbar_toggle () - - def key_zoom_normal(self): - self.zoom_orig () - - def key_search(self): - self.start_search() - - # bindings that should be moved to Terminator as they all just call - # a function of Terminator. It would be cleaner if TerminatorTerm - # has absolutely no reference to Terminator. - # N (next) - P (previous) - O (horizontal) - E (vertical) - W (close) - def key_new_root_tab(self): - self.terminator.newtab (self, True) - - def key_go_next(self): - self.terminator.go_next (self) - - def key_go_prev(self): - self.terminator.go_prev (self) - - def key_go_up(self): - self.terminator.go_up (self) - - def key_go_down(self): - self.terminator.go_down (self) - - def key_go_left(self): - self.terminator.go_left (self) - - def key_go_right(self): - self.terminator.go_right (self) - - def key_split_horiz(self): - self.terminator.splitaxis (self, False) - - def key_split_vert(self): - self.terminator.splitaxis (self, True) - - def key_close_term(self): - self.terminator.closeterm (self) - - def key_new_tab(self): - self.terminator.newtab(self) - - def key_resize_up(self): - self.terminator.resizeterm (self, 'Up') - - def key_resize_down(self): - self.terminator.resizeterm (self, 'Down') - - def key_resize_left(self): - self.terminator.resizeterm (self, 'Left') - - def key_resize_right(self): - self.terminator.resizeterm (self, 'Right') - - def key_move_tab_right(self): - self.terminator.move_tab (self, 'right') - - def key_move_tab_left(self): - self.terminator.move_tab (self, 'left') - - def key_toggle_zoom(self): - self.terminator.toggle_zoom (self) - - def key_scaled_zoom(self): - self.terminator.toggle_zoom (self, True) - - def key_next_tab(self): - self.terminator.next_tab (self) - - def key_prev_tab(self): - self.terminator.previous_tab (self) - - def key_switch_to_tab_1(self): - self.terminator.switch_to_tab (self, 0) - - def key_switch_to_tab_2(self): - self.terminator.switch_to_tab (self, 1) - - def key_switch_to_tab_3(self): - self.terminator.switch_to_tab (self, 2) - - def key_switch_to_tab_4(self): - self.terminator.switch_to_tab (self, 3) - - def key_switch_to_tab_5(self): - self.terminator.switch_to_tab (self, 4) - - def key_switch_to_tab_6(self): - self.terminator.switch_to_tab (self, 5) - - def key_switch_to_tab_7(self): - self.terminator.switch_to_tab (self, 6) - - def key_switch_to_tab_8(self): - self.terminator.switch_to_tab (self, 7) - - def key_switch_to_tab_9(self): - self.terminator.switch_to_tab (self, 8) - - def key_switch_to_tab_10(self): - self.terminator.switch_to_tab (self, 9) - - def key_reset(self): - self._vte.reset (True, False) - - def key_reset_clear(self): - self._vte.reset (True, True) - - def key_group_all(self): - self.group_all(self) - - def key_ungroup_all(self): - self.ungroup_all(self) - - def key_group_tab(self): - self.group_tab(self) - - def key_ungroup_tab(self): - self.ungroup_tab(self) - - def key_new_window(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.terminator.origcwd, 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,]) - # End key events - - def zoom_orig (self): - self._custom_font_size = None - self._vte.set_font (pango.FontDescription (self.conf.font)) - - def zoom (self, zoom_in): - pangodesc = self._vte.get_font () - fontsize = pangodesc.get_size () - - if fontsize > pango.SCALE and not zoom_in: - fontsize -= pango.SCALE - elif zoom_in: - fontsize += pango.SCALE - - pangodesc.set_size (fontsize) - self._custom_font_size = fontsize - self._vte.set_font (pangodesc) - - def start_search(self): - self._searchbox.show() - self._searchinput.grab_focus() - - def search_keypress(self, widget, event): - key = gtk.gdk.keyval_name(event.keyval) - if key == 'Escape': - self.end_search() - - def end_search(self, widget = None): - self._search_row = 0 - self._search_string = None - self._search_result_label.set_text("") - self._searchbox.hide() - self._scrollbar.set_value(self._vte.get_cursor_position()[1]) - self._vte.grab_focus() - - def do_search(self, widget): - string = widget.get_text() - dbg("do_search: Looking for %r" % string) - if string == '': - return - - if string != self._search_string: - self._search_row = self._get_vte_buffer_range()[0] - self._search_string = string - - self._search_result_label.set_text("Searching scrollback") - self.next_search() - - # Called by get_text_range, once per character. Argh. - def _search_character(self, widget, col, row, junk): - return True - - def next_search(self, widget=None): - startrow,endrow = self._get_vte_buffer_range() - while True: - if self._search_row == endrow: - self._search_row = startrow - self._search_result_label.set_text("Finished Search") - self._search_next.hide() - return - buffer = self._vte.get_text_range(self._search_row, 0, self._search_row, -1, self._search_character) - - # dbg("Row %d buffer: %r" % (self._search_row, buffer)) - index = buffer.find(self._search_string) - if index != -1: - self._search_result_label.set_text("Found at row %d" % self._search_row) - self._scrollbar.set_value(self._search_row) - self._search_row += 1 - self._search_next.show() - return - self._search_row += 1 - - def _get_vte_buffer_range(self): - column, endrow = self._vte.get_cursor_position() - startrow = max(0, endrow - self.conf.scrollback_lines) - return(startrow, endrow) - - def get_geometry (self): - '''Returns Gdk.Window.get_position(), pixel-based cursor position, - and Gdk.Window.get_geometry()''' - reply = dict() - if not self._vte.window: - return reply - x, y = self._vte.window.get_origin () - reply.setdefault('origin_x',x) - reply.setdefault('origin_y',y) - - column, row = self._vte.get_cursor_position () - cursor_x = column * self._vte.get_char_width () - cursor_y = row * self._vte.get_char_height () - reply.setdefault('cursor_x', cursor_x) - reply.setdefault('cursor_y', cursor_y) - - geometry = self._vte.window.get_geometry() - reply.setdefault('offset_x', geometry[0]) - reply.setdefault('offset_y', geometry[1]) - reply.setdefault('span_x', geometry[2]) - reply.setdefault('span_y', geometry[3]) - reply.setdefault('depth', geometry[4]) - - return reply - - def create_popup_menu (self, widget, event = None): - menu = gtk.Menu () - url = None - address = None - - if event: - url = self._vte.match_check (int (event.x / self._vte.get_char_width ()), int (event.y / self._vte.get_char_height ())) - button = event.button - time = event.time - else: - button = 0 - time = 0 - - if url: - address = self.prepareurl (url[0], url[1]) - - if url[1] == self.matches['email']: - nameopen = _("_Send Mail To...") - namecopy = _("_Copy Email Address") - item = gtk.MenuItem (nameopen) - elif url[1] == self.matches['voip']: - nameopen = _("Ca_ll To...") - namecopy = _("_Copy Call Address") - item = gtk.MenuItem (nameopen) - else: - nameopen = _("_Open Link") - namecopy = _("_Copy Link Address") - iconopen = gtk.image_new_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU) - - item = gtk.ImageMenuItem (nameopen) - item.set_property('image', iconopen) - - item.connect ("activate", lambda menu_item: self.openurl (address)) - menu.append (item) - - item = gtk.MenuItem (namecopy) - item.connect ("activate", lambda menu_item: self.clipboard.set_text (url[0])) - menu.append (item) - - item = gtk.MenuItem () - menu.append (item) - - item = gtk.ImageMenuItem (gtk.STOCK_COPY) - item.connect ("activate", lambda menu_item: self._vte.copy_clipboard ()) - item.set_sensitive (self._vte.get_has_selection ()) - menu.append (item) - - item = gtk.ImageMenuItem (gtk.STOCK_PASTE) - item.connect ("activate", lambda menu_item: self.paste_clipboard ()) - menu.append (item) - - item = gtk.MenuItem () - menu.append (item) - - if not self.terminator._zoomed: - str_horiz = _("Split H_orizontally") - str_vert = _("Split V_ertically") - - item = gtk.ImageMenuItem (str_horiz) - item_image = gtk.Image () - item_image.set_from_icon_name (APP_NAME + '_horiz', gtk.ICON_SIZE_MENU) - item.set_image (item_image) - if hasattr(item, "set_always_show_image"): - item.set_always_show_image (True) - - item.connect ("activate", lambda menu_item: self.terminator.splitaxis (self, False)) - menu.append (item) - item = gtk.ImageMenuItem (str_vert) - item_image = gtk.Image () - item_image.set_from_icon_name (APP_NAME + '_vert', gtk.ICON_SIZE_MENU) - item.set_image (item_image) - if hasattr(item, "set_always_show_image"): - item.set_always_show_image (True) - - item.connect ("activate", lambda menu_item: self.terminator.splitaxis (self, True)) - menu.append (item) - - item = gtk.MenuItem (_("Open _Tab")) - item.connect ("activate", lambda menu_item: self.terminator.newtab (self)) - menu.append (item) - - if self.terminator.debugaddress: - item = gtk.MenuItem (_("Open _Debug Tab")) - item.connect ("activate", lambda menu_item: self.terminator.newtab (self, command = "telnet %s" % ' '.join([str(x) for x in self.terminator.debugaddress]))) - menu.append (item) - - - if self.conf.extreme_tabs: - item = gtk.MenuItem (_("Open Top Level Tab")) - item.connect ("activate", lambda menu_item: self.terminator.newtab (self, True)) - menu.append (item) - - item = gtk.MenuItem () - menu.append (item) - - item = gtk.ImageMenuItem (gtk.STOCK_CLOSE) - item.connect ("activate", lambda menu_item: self.terminator.closeterm (self)) - menu.append (item) - - item = gtk.MenuItem () - menu.append (item) - - if len (self.terminator.term_list) > 1: - if not self.terminator._zoomed: - item = gtk.MenuItem (_("_Zoom terminal")) - item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self, True)) - menu.append (item) - - item = gtk.MenuItem (_("Ma_ximise terminal")) - item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self)) - menu.append (item) - else: - if self.terminator._zoomed and not self.terminator._maximised: - item = gtk.MenuItem (_("_Unzoom terminal")) - item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self, True)) - menu.append (item) - - if self.terminator._zoomed and self.terminator._maximised: - item = gtk.MenuItem (_("Unma_ximise terminal")) - item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self)) - menu.append (item) - - item = gtk.MenuItem () - menu.append (item) - - item = gtk.CheckMenuItem (_("Show _scrollbar")) - item.set_active (self._scrollbar.get_property ('visible')) - item.connect ("toggled", lambda menu_item: self.do_scrollbar_toggle ()) - menu.append (item) - - item = gtk.CheckMenuItem (_("Show _titlebar")) - item.set_active (self._titlebox.get_property ('visible')) - item.connect ("toggled", lambda menu_item: self.do_title_toggle ()) - if self._group: - item.set_sensitive (False) - menu.append (item) - - item = gtk.MenuItem (_("Ed_it profile")) - item.connect ("activate", lambda menu_item: self.terminator.edit_profile (self)) - menu.append (item) - - self._do_encoding_items (menu) - - menu.show_all () - menu.popup (None, None, None, button, time) - - return True - - def create_popup_group_menu (self, widget, event = None): - menu = gtk.Menu () - url = None - - if event: - url = self._vte.match_check (int (event.x / self._vte.get_char_width ()), int (event.y / self._vte.get_char_height ())) - button = event.button - time = event.time - else: - button = 0 - time = 0 - - self.populate_grouping_menu (menu) - - menu.show_all () - if gtk.gtk_version > (2, 14, 0): - menu.popup (None, None, self.position_popup_group_menu, button, time, widget) - else: - menu.popup (None, None, None, button, time, widget) - - return True - - def populate_grouping_menu (self, widget): - groupitem = None - - item = gtk.MenuItem (_("Assign to group...")) - item.connect ("activate", self.create_group) - widget.append (item) - - if len (self.terminator.groupings) > 0: - groupitem = gtk.RadioMenuItem (groupitem, _("None")) - groupitem.set_active (self._group == None) - groupitem.connect ("activate", self.set_group, None) - widget.append (groupitem) - - for group in self.terminator.groupings: - item = gtk.RadioMenuItem (groupitem, group, False) - item.set_active (self._group == group) - item.connect ("toggled", self.set_group, group) - widget.append (item) - groupitem = item - - if self._group != None or len (self.terminator.groupings) > 0: - item = gtk.MenuItem () - widget.append (item) - - if self._group != None: - item = gtk.MenuItem (_("Remove group %s") % (self._group)) - item.connect ("activate", self.ungroup, self._group) - widget.append (item) - - if self.terminator.get_first_parent_widget (self, gtk.Notebook) is not None and \ - not isinstance (self.get_parent(), gtk.Notebook): - item = gtk.MenuItem (_("G_roup all in tab")) - item.connect ("activate", self.group_tab) - widget.append (item) - - if self.terminator.get_first_parent_widget(self, gtk.Notebook) is not None and \ - not isinstance(self.get_parent(), gtk.Notebook) and \ - len(self.terminator.groupings) > 0: - item = gtk.MenuItem(_("Ungr_oup all in tab")) - item.connect("activate", self.ungroup_tab) - widget.append(item) - - if len (self.terminator.groupings) > 0: - item = gtk.MenuItem (_("Remove all groups")) - item.connect ("activate", self.ungroup_all) - widget.append (item) - - if self._group != None: - item = gtk.MenuItem () - widget.append (item) - - item = gtk.ImageMenuItem (_("Close group %s") % (self._group)) - grp_close_img = gtk.Image() - grp_close_img.set_from_stock(gtk.STOCK_CLOSE, 1) - item.set_image (grp_close_img) - item.connect ("activate", lambda menu_item: self.terminator.closegroupedterms (self)) - widget.append (item) - - item = gtk.MenuItem () - widget.append (item) - - groupitem = None - - groupitem = gtk.RadioMenuItem (groupitem, _("Broadcast off")) - groupitem.set_active (self.terminator.groupsend == 0) - groupitem.connect ("activate", self.set_groupsend, 0) - widget.append (groupitem) - - groupitem = gtk.RadioMenuItem (groupitem, _("Broadcast to group")) - groupitem.set_active (self.terminator.groupsend == 1) - groupitem.connect ("activate", self.set_groupsend, 1) - widget.append (groupitem) - - groupitem = gtk.RadioMenuItem (groupitem, _("Broadcast to all")) - groupitem.set_active (self.terminator.groupsend == 2) - groupitem.connect ("activate", self.set_groupsend, 2) - widget.append (groupitem) - - item = gtk.MenuItem () - widget.append (item) - - item = gtk.CheckMenuItem (_("Split to this group")) - item.set_active (self.terminator.splittogroup) - item.connect ("toggled", lambda menu_item: self.do_splittogroup_toggle ()) - if self._group == None: - item.set_sensitive(False) - widget.append (item) - - item = gtk.CheckMenuItem (_("Autoclean groups")) - item.set_active (self.terminator.autocleangroups) - item.connect ("toggled", lambda menu_item: self.do_autocleangroups_toggle ()) - widget.append (item) - - item = gtk.MenuItem () - widget.append (item) - - item = gtk.MenuItem (_("Insert terminal number")) - item.connect ("activate", lambda menu_item: self.do_enumerate ()) - widget.append (item) - - item = gtk.MenuItem (_("Insert padded terminal number")) - item.connect ("activate", lambda menu_item: self.do_enumerate (pad=True)) - widget.append (item) - - def position_popup_group_menu(self, menu, widget): - screen_w = gtk.gdk.screen_width() - screen_h = gtk.gdk.screen_height() - - widget_win = widget.get_window() - widget_x, widget_y = widget_win.get_origin() - widget_w, widget_h = widget_win.get_size() - - menu_w, menu_h = menu.size_request() - - if widget_y + widget_h + menu_h > screen_h: - menu_y = max(widget_y - menu_h, 0) - else: - menu_y = widget_y + widget_h - - return (widget_x, menu_y, 1) - - def create_group (self, item): - self.groupingscope = 0 - grplist=self.terminator.groupings[:] - grplist.sort() - - win = gtk.Window () - vbox = gtk.VBox (False, 6) - vbox.set_border_width(5) - win.add (vbox) - - # Populate the "Assign..." Section - contentvbox = gtk.VBox (False, 6) - selframe = gtk.Frame() - selframe_label = gtk.Label() - selframe_label.set_markup(_("Assign...")) - selframe.set_shadow_type(gtk.SHADOW_NONE) - selframe.set_label_widget(selframe_label) - selframe_align = gtk.Alignment(0, 0, 1, 1) - selframe_align.set_padding(0, 0, 12, 0) - selframevbox = gtk.VBox () - selframehbox = gtk.HBox () - - # Populate the Combo with existing group names (None at the top) - sel_combo = gtk.combo_box_new_text() - sel_combo.append_text(_("Terminals with no group")) - for grp in grplist: - sel_combo.append_text(grp) - sel_combo.set_sensitive(False) - - # Here are the radio buttons - groupitem = None - - groupitem = gtk.RadioButton (groupitem, _("Terminal")) - groupitem.set_active (True) - groupitem.connect ("toggled", self.set_groupingscope, 0, sel_combo) - selframehbox.pack_start (groupitem, False) - - groupitem = gtk.RadioButton (groupitem, _("Group")) - groupitem.connect ("toggled", self.set_groupingscope, 1, sel_combo) - selframehbox.pack_start (groupitem, False) - - groupitem = gtk.RadioButton (groupitem, _("All")) - groupitem.connect ("toggled", self.set_groupingscope, 2, sel_combo) - selframehbox.pack_start (groupitem, False) - - selframevbox.pack_start(selframehbox, True, True) - selframevbox.pack_start(sel_combo, True, True) - selframe_align.add(selframevbox) - selframe.add(selframe_align) - contentvbox.pack_start(selframe) - - # Populate the "To..." Section - tgtframe = gtk.Frame() - tgtframe_label = gtk.Label() - tgtframe_label.set_markup(_("To...")) - tgtframe.set_shadow_type(gtk.SHADOW_NONE) - tgtframe.set_label_widget(tgtframe_label) - tgtframe_align = gtk.Alignment(0, 0, 1, 1) - tgtframe_align.set_padding(0, 0, 12, 0) - tgtframevbox = gtk.VBox () - - # Populate the Combo with existing group names (None not needed) - tgt_comboentry = gtk.combo_box_entry_new_text() - for grp in grplist: - tgt_comboentry.append_text(grp) - - tgtframevbox.pack_start(tgt_comboentry, True, True) - - tgtframe_align.add(tgtframevbox) - tgtframe.add(tgtframe_align) - contentvbox.pack_start(tgtframe) - - okbut = gtk.Button (stock=gtk.STOCK_OK) - canbut = gtk.Button (stock=gtk.STOCK_CANCEL) - hbuttonbox = gtk.HButtonBox() - hbuttonbox.set_layout(gtk.BUTTONBOX_END) - hbuttonbox.pack_start (canbut, True, True) - hbuttonbox.pack_start (okbut, True, True) - - vbox.pack_start (contentvbox, False, True) - vbox.pack_end (hbuttonbox, False, True) - - canbut.connect ("clicked", lambda kill: win.destroy()) - okbut.connect ("clicked", self.do_create_group, win, sel_combo, tgt_comboentry) - tgt_comboentry.child.connect ("activate", self.do_create_group, win, sel_combo, tgt_comboentry) - - tgt_comboentry.grab_focus() - - # Center it over the current terminal (not perfect?!?) - # This could be replaced by a less bothersome dialog, but then that would - # center over the window, not the terminal - screen_w = gtk.gdk.screen_width() - screen_h = gtk.gdk.screen_height() - local_x, local_y = self.allocation.x, self.allocation.y - local_w, local_h = self.allocation.width, self.allocation.height - window_x, window_y = self.get_window().get_origin() - x = window_x + local_x - y = window_y + local_y - win.realize() - new_x = min(max(0, x+(local_w/2)-(win.allocation.width/2)), screen_w-win.allocation.width) - new_y = min(max(0, y+(local_h/2)-(win.allocation.height/2)), screen_h-win.allocation.height) - win.move(new_x, new_y) - - win.show_all () - - def set_groupingscope(self, widget, scope=None, sel_combo=None): - if widget.get_active(): - self.groupingscope = scope - if self.groupingscope == 1: - sel_combo.set_sensitive(True) - else: - sel_combo.set_sensitive(False) - - def do_create_group (self, widget, window, src, tgt): - tgt_name = tgt.child.get_text() - try: - src_name = src.get_active_text() - src_id = src.get_active() - except: - src_name = None - - if tgt_name == "" or (self.groupingscope == 1 and src_name == None): - return False - - if tgt_name not in self.terminator.groupings: - self.terminator.groupings.append (tgt_name) - - if self.groupingscope == 2: - for term in self.terminator.term_list: - term.set_group (None, tgt_name) - elif self.groupingscope == 1: - for term in self.terminator.term_list: - if term._group == src_name or (src_id == 0 and term._group == None): - term.set_group (None, tgt_name) - else: - self.set_group (None, tgt_name) - - window.destroy () - - def add_group (self, groupname): - if not groupname in self.terminator.groupings: - self.terminator.groupings.append(groupname) - - def set_group (self, item, data): - if self._group == data: - # No action needed - return - else: - self._group = data - - self._titlebox.set_group_label (data) - self._titlebox.update () - - if not self._group: - # We were not previously in a group - self._titlebox.show () - self._group = data - else: - # We were previously in a group - self._group = data - if data is None: - # We have been removed from a group - if not self.conf.titlebars and not self._want_titlebar: - self._titlebox.hide () - self.terminator.group_hoover () - - def set_groupsend (self, item, data): - self.terminator.groupsend = data - - def ungroup (self, widget, data): - for term in self.terminator.term_list: - if term._group == data: - term.set_group (None, None) - self.terminator.group_hoover () - - def group_all (self, widget): - allname = _("All") - self.add_group(allname) - for term in self.terminator.term_list: - term.set_group (None, allname) - self.on_vte_focus_in(self._vte, None) - self.terminator.group_hoover () - - def ungroup_all (self, widget): - for term in self.terminator.term_list: - term.set_group (None, None) - self.on_vte_focus_in(self._vte, None) - self.terminator.group_hoover () - - def find_all_terms_in_tab (self, notebook, pagenum=-1): - if pagenum == -1: - pagenum = notebook.get_current_page() - notebookchild = notebook.get_nth_page(pagenum) - - terms = [] - - for term in self.terminator.term_list: - termparent = term.get_parent() - while not isinstance(termparent, gtk.Window): - if termparent == notebookchild: - terms.append(term) - termparent = termparent.get_parent() - - return terms - - def group_tab (self, widget): - groupname = "" - notebook = self.terminator.get_first_parent_widget(self, gtk.Notebook) - pagenum = notebook.get_current_page() - notebookchild = notebook.get_nth_page(pagenum) - terms = self.find_all_terms_in_tab(notebook) - - notebooktablabel = notebook.get_tab_label(notebookchild) - if notebooktablabel._label.custom is True: - groupname = notebooktablabel.get_title() - - if groupname == "": - tmppagenum = pagenum - while True: - groupname = "Tab %d" % (tmppagenum + 1) - if groupname not in self.terminator.groupings: - break - tmppagenum += 1 - - self.add_group(groupname) - for term in terms: - term.set_group(None, groupname) - self.on_vte_focus_in(self._vte, None) - self.terminator.group_hoover() - - def ungroup_tab (self, widget): - notebook = self.terminator.get_first_parent_widget(self, gtk.Notebook) - terms = self.find_all_terms_in_tab (notebook) - - for term in terms: - term.set_group (None, None) - self.on_vte_focus_in(self._vte, None) - self.terminator.group_hoover() - - def on_encoding_change (self, widget, encoding): - current = self._vte.get_encoding () - if current != encoding: - dbg ('Setting Encoding to: %s' % encoding) - if encoding == self.conf.encoding: - self._custom_encoding = False - else: - self._custom_encoding = True - self._vte.set_encoding (encoding) - - def _do_encoding_items (self, menu): - active_encodings = self.conf.active_encodings - item = gtk.MenuItem (_("Encodings")) - menu.append (item) - submenu = gtk.Menu () - item.set_submenu (submenu) - encodings = TerminatorEncoding ().get_list () - encodings.sort (lambda x, y: cmp (x[2].lower (), y[2].lower ())) - - current_encoding = self._vte.get_encoding () - group = None - - if current_encoding not in active_encodings: - active_encodings.insert (0, _(current_encoding)) - - for encoding in active_encodings: - if encoding == self._default_encoding: - extratext = " (%s)" % _("Default") - elif encoding == current_encoding and self._custom_encoding == True: - extratext = " (%s)" % _("User defined") - else: - extratext = "" - - radioitem = gtk.RadioMenuItem (group, _(encoding) + extratext) - - if encoding == current_encoding: - radioitem.set_active (True) - - if group is None: - group = radioitem - - radioitem.connect ('activate', self.on_encoding_change, encoding) - submenu.append (radioitem) - - item = gtk.MenuItem (_("Other Encodings")) - submenu.append (item) - #second level - - submenu = gtk.Menu () - item.set_submenu (submenu) - group = None - - for encoding in encodings: - if encoding[1] in active_encodings: - continue - - if encoding[1] is None: - label = "%s %s"%(encoding[2], self._vte.get_encoding ()) - else: - label = "%s %s"%(encoding[2], encoding[1]) - - radioitem = gtk.RadioMenuItem (group, label) - if group is None: - group = radioitem - - if encoding[1] == current_encoding: - radioitem.set_active (True) - - radioitem.connect ('activate', self.on_encoding_change, encoding[1]) - submenu.append (radioitem) - - def get_window_title(self, vte = None): - if vte is None: - vte = self._vte - title = vte.get_window_title () - if title is None: - title = str(self.command) - return title - - def on_vte_title_change(self, vte): - title = self.get_window_title(vte) - if title == self._oldtitle: - # Title hasn't changed, don't bother doing anything - return - self._oldtitle = title - - if self.conf.titletips: - vte.set_property ("has-tooltip", True) - vte.set_property ("tooltip-text", title) - #set the title anyhow, titlebars setting only show/hide the label - self._titlebox.set_terminal_title (title) - self.terminator.set_window_title (title) - notebookpage = self.terminator.get_first_notebook_page(vte) - while notebookpage != None: - if notebookpage[0].get_tab_label(notebookpage[1]): - label = notebookpage[0].get_tab_label(notebookpage[1]) - label.set_title(title) - # FIXME: Is this necessary? The above line should update the label. LP #369370 might be related - notebookpage[0].set_tab_label(notebookpage[1], label) - notebookpage = self.terminator.get_first_notebook_page(notebookpage[0]) - - def on_vte_focus_in(self, vte, event): - for term in self.terminator.term_list: - term._titlebox.update_colors(self) - return - - def on_vte_focus_out(self, vte, event): - return - - def on_vte_focus(self, vte): - title = self.get_window_title(vte) - self.terminator.set_window_title(title) - notebookpage = self.terminator.get_first_notebook_page(vte) - while notebookpage != None: - if notebookpage[0].get_tab_label(notebookpage[1]): - label = notebookpage[0].get_tab_label(notebookpage[1]) - label.set_title(title) - notebookpage[0].set_tab_label(notebookpage[1], label) - notebookpage = self.terminator.get_first_notebook_page(notebookpage[0]) - - def is_scrollbar_present(self): - return self._scrollbar.get_property('visible') - - def on_group_button_press(self, term, event): - if event.button == 1: - self.create_popup_group_menu(term, event) - return False From 8276a6de77acc355026bf35d1e5b9e1ffc83a743 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 12 Jan 2010 00:58:50 +0000 Subject: [PATCH 286/331] Fix silly focus issue when creating groups - terminals were instantly stealing focus and cancelling the group name creation --- terminatorlib/terminal.py | 5 +++-- terminatorlib/titlebar.py | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 26a21c49..5eb96a47 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -874,8 +874,9 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) def on_vte_notify_enter(self, term, event): """Handle the mouse entering this terminal""" if self.config['focus'] in ['sloppy', 'mouse']: - term.grab_focus() - return(False) + if self.titlebar.creating_group() == False: + term.grab_focus() + return(False) def hide_titlebar(self): """Hide the titlebar""" diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index 569f3d74..e94149b2 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -126,6 +126,10 @@ class Titlebar(gtk.EventBox): """Re-emit an edit-done signal from an EditableLabel""" self.emit('edit-done') + def creating_group(self): + """Determine if we're currently creating a group""" + return(self.groupentry.get_property('visible')) + def create_group(self): """Create a new group""" self.groupentry.show() From 606ca3707b24a4193b902bd6275ea221b97e2358 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 12 Jan 2010 01:05:53 +0000 Subject: [PATCH 287/331] Extend previous commit to cover the titlebar itself, which previously would suffer subtle focus failures --- terminatorlib/editablelabel.py | 6 +++++- terminatorlib/terminal.py | 2 +- terminatorlib/titlebar.py | 6 +++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/terminatorlib/editablelabel.py b/terminatorlib/editablelabel.py index e0216e07..168f3157 100644 --- a/terminatorlib/editablelabel.py +++ b/terminatorlib/editablelabel.py @@ -54,7 +54,11 @@ class EditableLabel(gtk.EventBox): def set_angle(self, angle ): """set angle of the label""" self._label.set_angle( angle ) - + + def editing(self): + """Return if we are currently editing""" + return(self._entry != None) + def set_text(self, text, force=False): """set the text of the label""" self._autotext = text diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 5eb96a47..ee27ebf1 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -874,7 +874,7 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) def on_vte_notify_enter(self, term, event): """Handle the mouse entering this terminal""" if self.config['focus'] in ['sloppy', 'mouse']: - if self.titlebar.creating_group() == False: + if self.titlebar.editing() == False: term.grab_focus() return(False) diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index e94149b2..43f4d3b1 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -126,9 +126,9 @@ class Titlebar(gtk.EventBox): """Re-emit an edit-done signal from an EditableLabel""" self.emit('edit-done') - def creating_group(self): - """Determine if we're currently creating a group""" - return(self.groupentry.get_property('visible')) + def editing(self): + """Determine if we're currently editing a group name or title""" + return(self.groupentry.get_property('visible') or self.label.editing()) def create_group(self): """Create a new group""" From f808a9edc9786d25ec457de60d68a2723c3a2174 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 13 Jan 2010 23:00:42 +0000 Subject: [PATCH 288/331] set_active triggers the 'activate' signal, which makes sense I suppose, but means we were changing profile to the profile we already were set to, which is a waste --- terminatorlib/terminal_popup_menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/terminal_popup_menu.py b/terminatorlib/terminal_popup_menu.py index 0866b652..c147657d 100755 --- a/terminatorlib/terminal_popup_menu.py +++ b/terminatorlib/terminal_popup_menu.py @@ -151,9 +151,9 @@ class TerminalPopupMenu(object): for profile in profilelist: item = gtk.RadioMenuItem(group, profile.capitalize()) - item.connect('activate', terminal.set_profile, profile) if profile == current: item.set_active(True) + item.connect('activate', terminal.set_profile, profile) submenu.append(item) self.add_encoding_items(menu) From 02c1a482df0ed5904f7fa5ba3d3d4d7efcf754f7 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 13 Jan 2010 23:02:17 +0000 Subject: [PATCH 289/331] Add a get_profile() to Config and use it in Terminal::set_profile() to make sure we don't set a profile that already is --- terminatorlib/config.py | 4 ++++ terminatorlib/terminal.py | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 14adcb85..099cd981 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -204,6 +204,10 @@ class Config(object): """Set a particular configuration item""" return(self.base.set_item(key, value, self.profile)) + def get_profile(self): + """Get our profile""" + return(self.profile) + def set_profile(self, profile): """Set our profile (which usually means change it)""" dbg('Config::set_profile: Changing profile to %s' % profile) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index ee27ebf1..f9420cc6 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -142,8 +142,9 @@ class Terminal(gtk.VBox): def set_profile(self, widget, profile): """Set our profile""" - self.config.set_profile(profile) - self.reconfigure() + if profile != self.config.get_profile(): + self.config.set_profile(profile) + self.reconfigure() def get_profile(self): """Return our profile name""" From df7a3b978d2a2a76aa98e146d2d3a219b3ab7972 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 13 Jan 2010 23:06:42 +0000 Subject: [PATCH 290/331] The titlebar is no longer optional, it probably never should have been, it's just too complicated --- terminatorlib/config.py | 2 -- terminatorlib/terminal.py | 11 ----------- terminatorlib/terminal_popup_menu.py | 7 ------- 3 files changed, 20 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 099cd981..abcff535 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -123,8 +123,6 @@ DEFAULTS = { }, 'profiles': { 'default': { - 'titlebars' : True, - 'zoomedtitlebar' : True, 'allow_bold' : True, 'audible_bell' : False, 'visible_bell' : True, diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index f9420cc6..1758b2e0 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -681,9 +681,6 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) def do_scrollbar_toggle(self): self.toggle_widget_visibility(self.scrollbar) - def do_title_toggle(self): - self.toggle_widget_visibility(self.titlebar) - def toggle_widget_visibility(self, widget): if widget.get_property('visible'): widget.hide() @@ -879,14 +876,6 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) term.grab_focus() return(False) - def hide_titlebar(self): - """Hide the titlebar""" - self.titlebar.hide() - - def show_titlebar(self): - """Show the titlebar""" - self.titlebar.show() - def get_zoom_data(self): """Return a dict of information for Window""" data = {} diff --git a/terminatorlib/terminal_popup_menu.py b/terminatorlib/terminal_popup_menu.py index c147657d..db19ae0d 100755 --- a/terminatorlib/terminal_popup_menu.py +++ b/terminatorlib/terminal_popup_menu.py @@ -125,13 +125,6 @@ class TerminalPopupMenu(object): item.connect('toggled', lambda x: terminal.do_scrollbar_toggle()) menu.append(item) - item = gtk.CheckMenuItem(_('Show _titlebar')) - item.set_active(terminal.titlebar.get_property('visible')) - item.connect('toggled', lambda x: terminal.do_title_toggle()) - if terminal.group: - item.set_sensitive(False) - menu.append(item) - item = gtk.MenuItem(_('_Preferences')) item.connect('activate', lambda x: PrefsEditor(self.terminal)) menu.append(item) From 1bcbe94e6aff6f626407e1fad3bba53ef1b97309 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 13 Jan 2010 23:13:19 +0000 Subject: [PATCH 291/331] Fix confusion around focus and scrollbar position settings not being consistent --- terminatorlib/prefseditor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 9569889d..934376e0 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -121,7 +121,7 @@ class PrefsEditor: active = 0 if focus == 'click': active = 1 - elif focus == 'sloppy': + elif focus in ['sloppy', 'mouse']: active = 2 widget = guiget('focuscombo') widget.set_active(active) @@ -380,7 +380,7 @@ class PrefsEditor: value = self.config['scrollbar_position'] if value == 'left': widget.set_active(0) - elif value == 'disabled': + elif value in ['disabled', 'hidden']: widget.set_active(2) else: widget.set_active(1) @@ -534,7 +534,7 @@ class PrefsEditor: value = 'left' elif selected == 1: value = 'right' - elif selected ==2: + elif selected == 2: value = 'hidden' self.config['scrollbar_position'] = value # Scrollback lines From 4d216633fca8ca46fc3b51fdf2b382db93e3ade1 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 14 Jan 2010 13:15:05 +0000 Subject: [PATCH 292/331] -d now automatically infers the Class::method in dbg(), and -d additionally adds a trailing (filename:line) item. debugserver is now moved to -ddd --- terminator | 2 +- terminatorlib/optionparse.py | 2 ++ terminatorlib/util.py | 21 +++++++++++++++++++-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/terminator b/terminator index fa857073..3e766fc1 100755 --- a/terminator +++ b/terminator @@ -58,7 +58,7 @@ if __name__ == '__main__': WINDOW.show() TERMINAL.spawn_child() - if OPTIONS.debug > 1: + if OPTIONS.debug > 2: import terminatorlib.debugserver as debugserver # pylint: disable-msg=W0611 import threading diff --git a/terminatorlib/optionparse.py b/terminatorlib/optionparse.py index c49575ba..08aececb 100755 --- a/terminatorlib/optionparse.py +++ b/terminatorlib/optionparse.py @@ -82,6 +82,8 @@ WM_WINDOW_ROLE property on the window') if options.debug: util.DEBUG = True + if options.debug >1: + util.DEBUGFILES = True if options.working_directory: if os.path.exists(os.path.expanduser(options.working_directory)): diff --git a/terminatorlib/util.py b/terminatorlib/util.py index 971e8c55..ad712b38 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -20,14 +20,32 @@ import sys import gtk import os import pwd +import inspect # set this to true to enable debugging output DEBUG = False +# set this to true to additionally list filenames in debugging +DEBUGFILES = False def dbg(log = ""): """Print a message if debugging is enabled""" if DEBUG: - print >> sys.stderr, log + stackitem = inspect.stack()[1] + parent_frame = stackitem[0] + method = parent_frame.f_code.co_name + names, varargs, keywords, local_vars = inspect.getargvalues(parent_frame) + try: + self_name = names[0] + classname = local_vars[self_name].__class__.__name__ + except IndexError: + classname = "noclass" + if DEBUGFILES: + line = stackitem[2] + filename = parent_frame.f_code.co_filename + extra = " (%s:%s)" % (filename, line) + else: + extra = "" + print >> sys.stderr, "%s::%s: %s%s" % (classname, method, log, extra) def err(log = ""): """Print an error message""" @@ -145,4 +163,3 @@ def dict_diff(reference, working): result[key] = working[key] return(result) - From 8f88537bc5abce7f69e755f950bc94652a81dc3f Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 14 Jan 2010 13:52:48 +0000 Subject: [PATCH 293/331] obvious stupid reason why the email URL matching wasn't working. excess whitespace --- terminatorlib/terminal.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 1758b2e0..18b3bc12 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -215,12 +215,12 @@ class Terminal(gtk.VBox): "(www|ftp)[" + hostchars + "]*\.[" + hostchars + ".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?") self.matches['email'] = self.vte.match_add (lboundry + - "(mailto:)?[a-zA-Z0-9][a-zA-Z0-9.+-]*@[a-zA-Z0-9]\ - [a-zA-Z0-9-]*\.[a-zA-Z0-9][a-zA-Z0-9-]+\ - [.a-zA-Z0-9-]*" + rboundry) + "(mailto:)?[a-zA-Z0-9][a-zA-Z0-9.+-]*@[a-zA-Z0-9]" + + "[a-zA-Z0-9-]*\.[a-zA-Z0-9][a-zA-Z0-9-]+" + "[.a-zA-Z0-9-]*" + rboundry) self.matches['nntp'] = self.vte.match_add (lboundry + - '''news:[-A-Z\^_a-z{|}~!"#$%&'()*+,./0-9;:=?`]+@\ - [-A-Za-z0-9.]+(:[0-9]+)?''' + rboundry) + "news:[-A-Z\^_a-z{|}~!"#$%&'()*+,./0-9;:=?`]+@" + "[-A-Za-z0-9.]+(:[0-9]+)?" + rboundry) # Now add any matches from plugins try: From 375d272ee8f43d6c942469b8367bfd1a6c4dfe05 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 14 Jan 2010 22:58:41 +0000 Subject: [PATCH 294/331] make the config directory if it doesn't exist, and don't explode if we can't write it --- terminatorlib/config.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index abcff535..6d714b23 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -433,7 +433,13 @@ class ConfigBase(Borg): dbg('ConfigBase::save: Processing plugin: %s' % plugin) parser['plugins'][plugin] = self.plugins[plugin] - parser.write(open(os.path.join(get_config_dir(), 'epic-config'), 'w')) + config_dir = get_config_dir() + if not os.isdir(config_dir): + os.makedirs(config_dir) + try: + parser.write(open(os.path.join(config_dir, 'epic-config'), 'w')) + except Exception, ex: + err('ConfigBase::save: Unable to save config: %s' % ex) def get_item(self, key, profile='default', plugin=None): """Look up a configuration item""" From fb0beb42f3400ff5d98b545210d9959df2ae986d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 14 Jan 2010 23:03:47 +0000 Subject: [PATCH 295/331] Split out borg doctests and test classes so they work properly with trial --- .bzrignore | 1 + terminatorlib/borg.py | 52 -------------------------- terminatorlib/testborg.py | 55 ++++++++++++++++++++++++++++ terminatorlib/tests/test_doctests.py | 2 +- 4 files changed, 57 insertions(+), 53 deletions(-) create mode 100755 terminatorlib/testborg.py diff --git a/.bzrignore b/.bzrignore index 03eef862..5c359985 100644 --- a/.bzrignore +++ b/.bzrignore @@ -3,3 +3,4 @@ terminatorlib/*.pyc .project .pydevproject terminatorlib/meliae +_trial_temp diff --git a/terminatorlib/borg.py b/terminatorlib/borg.py index ec9d3770..d61e7283 100755 --- a/terminatorlib/borg.py +++ b/terminatorlib/borg.py @@ -6,30 +6,6 @@ ActiveState's policy appears to be that snippets exist to encourage re-use, but I can not find any specific licencing terms. - ->>> obj1 = TestBorg() ->>> obj2 = TestBorg() ->>> obj1.attribute -0 ->>> obj2.attribute -0 ->>> obj1.attribute = 12345 ->>> obj1.attribute -12345 ->>> obj2.attribute -12345 ->>> obj2.attribute = 54321 ->>> obj1.attribute -54321 ->>> obj3 = TestBorg2() ->>> obj3.attribute -1 ->>> obj4 = TestBorg2() ->>> obj3.attribute = 98765 ->>> obj4.attribute -98765 ->>> - """ from util import dbg @@ -77,31 +53,3 @@ class Borg: """This should be used to prepare any attributes of the borg class.""" raise NotImplementedError('prepare_attributes') -if __name__ == '__main__': - class TestBorg(Borg): - attribute = None - - def __init__(self): - Borg.__init__(self, self.__class__.__name__) - self.prepare_attributes() - - def prepare_attributes(self): - if not self.attribute: - self.attribute = 0 - - class TestBorg2(Borg): - attribute = None - - def __init__(self): - Borg.__init__(self, self.__class__.__name__) - self.prepare_attributes() - - def prepare_attributes(self): - if not self.attribute: - self.attribute = 1 - - import sys - import doctest - (failed, attempted) = doctest.testmod() - print "%d/%d tests failed" % (failed, attempted) - sys.exit(failed) diff --git a/terminatorlib/testborg.py b/terminatorlib/testborg.py new file mode 100755 index 00000000..0dbc5df9 --- /dev/null +++ b/terminatorlib/testborg.py @@ -0,0 +1,55 @@ +#!/usr/bin/python +# Terminator by Chris Jones +# GPL v2 only +"""testborg.py - We are the borg. Resistance is futile. + doctests for borg.py + +>>> obj1 = TestBorg() +>>> obj2 = TestBorg() +>>> obj1.attribute +0 +>>> obj2.attribute +0 +>>> obj1.attribute = 12345 +>>> obj1.attribute +12345 +>>> obj2.attribute +12345 +>>> obj2.attribute = 54321 +>>> obj1.attribute +54321 +>>> obj3 = TestBorg2() +>>> obj3.attribute +1 +>>> obj4 = TestBorg2() +>>> obj3.attribute = 98765 +>>> obj4.attribute +98765 +>>> + +""" + +from borg import Borg + +class TestBorg(Borg): + attribute = None + + def __init__(self): + Borg.__init__(self, self.__class__.__name__) + self.prepare_attributes() + + def prepare_attributes(self): + if not self.attribute: + self.attribute = 0 + +class TestBorg2(Borg): + attribute = None + + def __init__(self): + Borg.__init__(self, self.__class__.__name__) + self.prepare_attributes() + + def prepare_attributes(self): + if not self.attribute: + self.attribute = 1 + diff --git a/terminatorlib/tests/test_doctests.py b/terminatorlib/tests/test_doctests.py index 2b4c6d90..0fb46d30 100644 --- a/terminatorlib/tests/test_doctests.py +++ b/terminatorlib/tests/test_doctests.py @@ -7,7 +7,7 @@ def test_suite(): for name in ( 'config', 'plugin', - 'borg', + 'testborg', ): suite.addTest(DocTestSuite('terminatorlib.' + name)) return suite From ddadf3486f7c97511ea7a933fddc5a971ee6bb2e Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 14 Jan 2010 23:18:41 +0000 Subject: [PATCH 296/331] ignore compiled terminator --- .bzrignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.bzrignore b/.bzrignore index 5c359985..2800e844 100644 --- a/.bzrignore +++ b/.bzrignore @@ -4,3 +4,4 @@ terminatorlib/*.pyc .pydevproject terminatorlib/meliae _trial_temp +terminatorc From 62d5672a889aa2b1f0e8549be1eb9721cbb8018f Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 14 Jan 2010 23:29:47 +0000 Subject: [PATCH 297/331] Extent Config test coverage, and make del_profile() more robust --- terminatorlib/config.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 6d714b23..60f911c4 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -44,7 +44,20 @@ Classes relating to configuration {'foo': 'bar'} >>> config.plugin_get('testplugin', 'foo') 'bar' ->>> +>>> config.get_profile() +'default' +>>> config.set_profile('my_first_new_testing_profile') +>>> config.get_profile() +'my_first_new_testing_profile' +>>> config.del_profile('my_first_new_testing_profile') +>>> config.get_profile() +'default' +>>> config.list_profiles().__class__.__name__ +'list' +>>> config.options_set({}) +>>> config.options_get() +{} +>>> """ @@ -220,6 +233,9 @@ class Config(object): def del_profile(self, profile): """Delete a profile""" + if profile == self.profile: + err('Config::del_profile: Deleting in-use profile %s.' % profile) + self.set_profile('default') if self.base.profiles.has_key(profile): del(self.base.profiles[profile]) From aec2dd5add8e4bdea4004518743e6943c2334a42 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 14 Jan 2010 23:33:06 +0000 Subject: [PATCH 298/331] Add minimal, feeble tests for cwd.py --- terminatorlib/cwd.py | 11 ++++++++++- terminatorlib/tests/test_doctests.py | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/terminatorlib/cwd.py b/terminatorlib/cwd.py index 703a7732..1edb6fb9 100755 --- a/terminatorlib/cwd.py +++ b/terminatorlib/cwd.py @@ -1,7 +1,16 @@ #!/usr/bin/python # Terminator by Chris Jones # GPL v2 only -"""cwd.py - function necessary to get the cwd for a given pid on various OSes""" +"""cwd.py - function necessary to get the cwd for a given pid on various OSes + +>>> cwd = get_default_cwd() +>>> cwd.__class__.__name__ +'str' +>>> func = get_pid_cwd() +>>> func.__class__.__name__ +'function' + +""" import platform import os diff --git a/terminatorlib/tests/test_doctests.py b/terminatorlib/tests/test_doctests.py index 0fb46d30..2d86b1f5 100644 --- a/terminatorlib/tests/test_doctests.py +++ b/terminatorlib/tests/test_doctests.py @@ -8,6 +8,7 @@ def test_suite(): 'config', 'plugin', 'testborg', + 'cwd', ): suite.addTest(DocTestSuite('terminatorlib.' + name)) return suite From 9d5d9a286c14e15210dfc68a9805c006560e7efb Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 14 Jan 2010 23:39:49 +0000 Subject: [PATCH 299/331] Add another possible exception to the tomboy keybinding --- terminatorlib/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 6b631b5c..a141d0fb 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -80,7 +80,7 @@ class Window(Container, gtk.Window): self.hidebound = bindkey.tomboy_keybinder_bind( self.config['keybindings']['hide_window'], self.on_hide_window) - except NameError: + except KeyError,NameError: pass if not self.hidebound: From 32a3933b6cbd1bd9e34c2a0fb0abfdd8d6aadee1 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 14 Jan 2010 23:40:02 +0000 Subject: [PATCH 300/331] Add some basic test coverage of factory.py --- terminatorlib/factory.py | 18 +++++++++++++++++- terminatorlib/tests/test_doctests.py | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/terminatorlib/factory.py b/terminatorlib/factory.py index 53ab55d7..24442eeb 100755 --- a/terminatorlib/factory.py +++ b/terminatorlib/factory.py @@ -1,7 +1,23 @@ #!/usr/bin/python # Terminator by Chris Jones # GPL v2 only -"""factory.py - Maker of objects""" +"""factory.py - Maker of objects + +>>> maker = Factory() +>>> window = maker.make_window() +>>> maker.isinstance(window, 'Window') +True +>>> terminal = maker.make_terminal() +>>> maker.isinstance(terminal, 'Terminal') +True +>>> hpaned = maker.make_hpaned() +>>> maker.isinstance(hpaned, 'HPaned') +True +>>> vpaned = maker.make_vpaned() +>>> maker.isinstance(vpaned, 'VPaned') +True + +""" from borg import Borg from util import dbg, err diff --git a/terminatorlib/tests/test_doctests.py b/terminatorlib/tests/test_doctests.py index 2d86b1f5..86898eaa 100644 --- a/terminatorlib/tests/test_doctests.py +++ b/terminatorlib/tests/test_doctests.py @@ -9,6 +9,7 @@ def test_suite(): 'plugin', 'testborg', 'cwd', + 'factory', ): suite.addTest(DocTestSuite('terminatorlib.' + name)) return suite From 3917ea568b9984e9443c7c82726a8e179d83f438 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 14 Jan 2010 23:48:15 +0000 Subject: [PATCH 301/331] Add tiny test coverage for util.py --- terminatorlib/tests/test_doctests.py | 1 + terminatorlib/util.py | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/terminatorlib/tests/test_doctests.py b/terminatorlib/tests/test_doctests.py index 86898eaa..fd964210 100644 --- a/terminatorlib/tests/test_doctests.py +++ b/terminatorlib/tests/test_doctests.py @@ -10,6 +10,7 @@ def test_suite(): 'testborg', 'cwd', 'factory', + 'util', ): suite.addTest(DocTestSuite('terminatorlib.' + name)) return suite diff --git a/terminatorlib/util.py b/terminatorlib/util.py index ad712b38..143bc6f8 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -14,7 +14,14 @@ # 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 -"""Terminator.util - misc utility functions""" +"""Terminator.util - misc utility functions + +>>> a = {'foo': 'bar', 'baz': 'bjonk'} +>>> b = {'foo': 'far', 'baz': 'bjonk'} +>>> dict_diff(a, b) +{'foo': 'far'} + +""" import sys import gtk From 0e01618dfa54bca7d395066f8203ba23bef1cb85 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 15 Jan 2010 23:32:16 +0000 Subject: [PATCH 302/331] isdir is in os.path, not os --- terminatorlib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 60f911c4..2c199359 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -450,7 +450,7 @@ class ConfigBase(Borg): parser['plugins'][plugin] = self.plugins[plugin] config_dir = get_config_dir() - if not os.isdir(config_dir): + if not os.path.isdir(config_dir): os.makedirs(config_dir) try: parser.write(open(os.path.join(config_dir, 'epic-config'), 'w')) From 8c7218121fa27e655a6998db4ebd2c23a7792925 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 15 Jan 2010 23:32:38 +0000 Subject: [PATCH 303/331] get and set palette values --- terminatorlib/prefseditor.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 934376e0..81a04871 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -349,7 +349,11 @@ class PrefsEditor: widget.set_sensitive(True) else: widget.set_sensitive(False) - # FIXME: Do the Palette schemes and pickers + # Palette + palette = self.config['palette'].split(':') + for i in xrange(1,17): + widget = guiget('palette-colorpicker-%d' % i) + widget.set_color(gtk.gdk.Color(palette[i - 1])) ## Background tab # Radio values @@ -503,7 +507,12 @@ class PrefsEditor: # Background colour widget = guiget('background-colorpicker') self.config['background_color'] = widget.get_color().to_string() - # FIXME: Do the palette schemes and palette + # Palette + palette = [] + for i in xrange(1,17): + widget = guiget('palette-colorpicker-%d' % i) + palette.append(widget.get_color().to_string()) + self.config['palette'] = ':'.join(palette) ## Background tab # Background type From 8ad7ac07b1ba1700894101a012ec329b5c5761ae Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 18 Jan 2010 13:17:35 +0000 Subject: [PATCH 304/331] Create a new class Signalman which is used to track the glib signals on a widget since at least two of our classes were doing this themselves. Also integrated it into one of those classes, Container() where it is only used by VPaned and HPaned. Also add doctests for it --- terminatorlib/container.py | 27 ++++---------- terminatorlib/paned.py | 13 ++----- terminatorlib/signalman.py | 54 +++++++++++++++++++++++++++ terminatorlib/terminal.py | 1 + terminatorlib/tests/test_doctests.py | 1 + terminatorlib/testsignalman.py | 56 ++++++++++++++++++++++++++++ 6 files changed, 123 insertions(+), 29 deletions(-) create mode 100755 terminatorlib/signalman.py create mode 100755 terminatorlib/testsignalman.py diff --git a/terminatorlib/container.py b/terminatorlib/container.py index e9be2d92..a0914bfd 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -10,6 +10,7 @@ from factory import Factory from config import Config from util import dbg, err from translation import _ +from signalman import Signalman # pylint: disable-msg=R0921 class Container(object): @@ -20,13 +21,13 @@ class Container(object): children = None config = None signals = None - cnxids = None + signalman = None def __init__(self): """Class initialiser""" self.children = [] self.signals = [] - self.cnxids = {} + self.cnxids = Signalman() self.config = Config() def register_signals(self, widget): @@ -49,28 +50,14 @@ class Container(object): err('Container:: registering signal for %s on %s failed' % (signal['name'], widget)) - def connect_child(self, widget, signal, handler, data=None): + def connect_child(self, widget, signal, handler, *args): """Register the requested signal and record its connection ID""" - if not self.cnxids.has_key(widget): - self.cnxids[widget] = [] - - if data is not None: - self.cnxids[widget].append(widget.connect(signal, handler, data)) - dbg('Container::connect_child: connecting %s(%s) for %s::%s' % - (handler.__name__, data, widget.__class__.__name__, signal)) - else: - self.cnxids[widget].append(widget.connect(signal, handler)) - dbg('Container::connect_child: registering %s to handle %s::%s' % - (handler.__name__, widget.__class__.__name__, signal)) + self.cnxids.new(widget, signal, handler, *args) + return def disconnect_child(self, widget): """De-register the signals for a child""" - if self.cnxids.has_key(widget): - for cnxid in self.cnxids[widget]: - dbg('Container::disconnect_child: removing handler on %s' % - type(widget)) - widget.disconnect(cnxid) - del(self.cnxids[widget]) + self.cnxids.remove_widget(widget) def get_offspring(self): """Return a list of child widgets, if any""" diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index cbc5c1c4..914acec2 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -37,8 +37,7 @@ class Paned(Container): dbg("Paned::set_initial_position: Setting position to: %d" % position) self.set_position(position) - self.disconnect(self.cnxids['init']) - del(self.cnxids['init']) + self.cnxids.remove_signal(self, 'expose-event') # pylint: disable-msg=W0613 def split_axis(self, widget, vertical=True, sibling=None): @@ -78,7 +77,6 @@ class Paned(Container): else: raise ValueError('Paned widgets can only have two children') - self.cnxids[widget] = [] if maker.isinstance(widget, 'Terminal'): top_window = get_top_window(self) @@ -92,8 +90,7 @@ class Paned(Container): for signal in signals: self.connect_child(widget, signal, signals[signal]) - self.connect_child(widget, 'maximise', top_window.zoom, - False) + self.connect_child(widget, 'maximise', top_window.zoom, False) widget.grab_focus() @@ -176,8 +173,7 @@ class HPaned(Paned, gtk.HPaned): Paned.__init__(self) gtk.HPaned.__init__(self) self.register_signals(HPaned) - self.cnxids['init'] = self.connect('expose-event', - self.set_initial_position) + self.cnxids.new(self, 'expose-event', self.set_initial_position) class VPaned(Paned, gtk.VPaned): """Merge gtk.VPaned into our base Paned Container""" @@ -186,8 +182,7 @@ class VPaned(Paned, gtk.VPaned): Paned.__init__(self) gtk.VPaned.__init__(self) self.register_signals(VPaned) - self.cnxids['init'] = self.connect('expose-event', - self.set_initial_position) + self.cnxids.new(self, 'expose-event', self.set_initial_position) gobject.type_register(HPaned) gobject.type_register(VPaned) diff --git a/terminatorlib/signalman.py b/terminatorlib/signalman.py new file mode 100755 index 00000000..70de7d2a --- /dev/null +++ b/terminatorlib/signalman.py @@ -0,0 +1,54 @@ +#!/usr/bin/python +# Terminator by Chris Jones +# GPL v2 only +"""signalman.py - class providing a glib signal tracker""" + +from util import dbg, err + +class Signalman(object): + """Class providing glib signal tracking and management""" + + cnxids = None + + def __init__(self): + """Class initialiser""" + self.cnxids = {} + + def __del__(self): + """Class destructor""" + if len(self.cnxids.keys()) > 0: + err('Signals remain. This is likely a bug: %s' % self.cnxids) + + def new(self, widget, signal, handler, *args): + """Register a new signal on a widget""" + if not self.cnxids.has_key(widget): + dbg('creating new bucket for %s' % type(widget)) + self.cnxids[widget] = {} + + if self.cnxids[widget].has_key(signal): + err('%s already has a handler for %s' % (id(widget), signal)) + + self.cnxids[widget][signal] = widget.connect(signal, handler, *args) + dbg('connected %s::%s to %s' % (type(widget), signal, handler)) + + def remove_signal(self, widget, signal): + """Remove a signal handler""" + if not self.cnxids[widget].has_key(signal): + err('%s not registered for %s' % (signal, type(widget))) + return + dbg('removing %s::%s' % (type(widget), signal)) + widget.disconnect(self.cnxids[widget][signal]) + del(self.cnxids[widget][signal]) + if len(self.cnxids[widget].keys()) == 0: + dbg('no more signals for widget') + del(self.cnxids[widget]) + + def remove_widget(self, widget): + """Remove all signal handlers for a widget""" + if not self.cnxids.has_key(widget): + dbg('%s not registered' % widget) + return + signals = self.cnxids[widget].keys() + for signal in signals: + self.remove_signal(widget, signal) + diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 18b3bc12..8ae2b018 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -97,6 +97,7 @@ class Terminal(gtk.VBox): self.connect('focus-in', self.terminator.focus_changed) self.matches = {} + # FIXME: Port cnxids to using Signalman self.cnxids = {} self.config = Config() diff --git a/terminatorlib/tests/test_doctests.py b/terminatorlib/tests/test_doctests.py index fd964210..ff5165c4 100644 --- a/terminatorlib/tests/test_doctests.py +++ b/terminatorlib/tests/test_doctests.py @@ -11,6 +11,7 @@ def test_suite(): 'cwd', 'factory', 'util', + 'testsignalman', ): suite.addTest(DocTestSuite('terminatorlib.' + name)) return suite diff --git a/terminatorlib/testsignalman.py b/terminatorlib/testsignalman.py new file mode 100755 index 00000000..8aa6971e --- /dev/null +++ b/terminatorlib/testsignalman.py @@ -0,0 +1,56 @@ +#!/usr/bin/python +# Terminator by Chris Jones +# GPL v2 only +"""testsignalman.py - Test the signalman class + +>>> widget = TestWidget() +>>> signalman = Signalman() +>>> signalman.new(widget, 'test1', handler) +>>> signalman.cnxids[widget].keys() +['test1'] +>>> widget.signals.values() +['test1'] +>>> signalman.remove_widget(widget) +>>> signalman.cnxids.has_key(widget) +False +>>> widget.signals.values() +[] +>>> signalman.new(widget, 'test2', handler) +>>> signalman.new(widget, 'test3', handler) +>>> signalman.remove_signal(widget, 'test2') +>>> signalman.cnxids[widget].keys() +['test3'] +>>> widget.signals.values() +['test3'] +>>> signalman.remove_widget(widget) +>>> + +""" + +from signalman import Signalman + +class TestWidget(): + signals = None + count = None + + def __init__(self): + self.signals = {} + self.count = 0 + + def connect(self, signal, handler, *args): + self.count = self.count + 1 + self.signals[self.count] = signal + return(self.count) + + def disconnect(self, signalid): + del(self.signals[signalid]) + +def handler(): + print "I am a test handler" + +if __name__ == '__main__': + import sys + import doctest + (failed, attempted) = doctest.testmod() + print "%d/%d tests failed" % (failed, attempted) + sys.exit(failed) From f015aaebf4eafa5ce7d5bc52ce15b13b2e7ac2c5 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 18 Jan 2010 13:21:03 +0000 Subject: [PATCH 305/331] Move test-only files into tests/ --- terminatorlib/tests/test_doctests.py | 4 ++-- terminatorlib/{ => tests}/testborg.py | 2 +- terminatorlib/{ => tests}/testsignalman.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename terminatorlib/{ => tests}/testborg.py (97%) rename terminatorlib/{ => tests}/testsignalman.py (97%) diff --git a/terminatorlib/tests/test_doctests.py b/terminatorlib/tests/test_doctests.py index ff5165c4..cb63ece0 100644 --- a/terminatorlib/tests/test_doctests.py +++ b/terminatorlib/tests/test_doctests.py @@ -7,11 +7,11 @@ def test_suite(): for name in ( 'config', 'plugin', - 'testborg', 'cwd', 'factory', 'util', - 'testsignalman', + 'tests.testborg', + 'tests.testsignalman', ): suite.addTest(DocTestSuite('terminatorlib.' + name)) return suite diff --git a/terminatorlib/testborg.py b/terminatorlib/tests/testborg.py similarity index 97% rename from terminatorlib/testborg.py rename to terminatorlib/tests/testborg.py index 0dbc5df9..634f74da 100755 --- a/terminatorlib/testborg.py +++ b/terminatorlib/tests/testborg.py @@ -29,7 +29,7 @@ """ -from borg import Borg +from ..borg import Borg class TestBorg(Borg): attribute = None diff --git a/terminatorlib/testsignalman.py b/terminatorlib/tests/testsignalman.py similarity index 97% rename from terminatorlib/testsignalman.py rename to terminatorlib/tests/testsignalman.py index 8aa6971e..ff6450a2 100755 --- a/terminatorlib/testsignalman.py +++ b/terminatorlib/tests/testsignalman.py @@ -27,7 +27,7 @@ False """ -from signalman import Signalman +from ..signalman import Signalman class TestWidget(): signals = None From f17fab0610642cafad2722c8ab77560a1c80b362 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 18 Jan 2010 13:23:50 +0000 Subject: [PATCH 306/331] Slightly improve docstrings --- terminatorlib/signalman.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terminatorlib/signalman.py b/terminatorlib/signalman.py index 70de7d2a..97615552 100755 --- a/terminatorlib/signalman.py +++ b/terminatorlib/signalman.py @@ -1,7 +1,7 @@ #!/usr/bin/python # Terminator by Chris Jones # GPL v2 only -"""signalman.py - class providing a glib signal tracker""" +"""Simple management of Gtk Widget signal handlers""" from util import dbg, err @@ -15,7 +15,7 @@ class Signalman(object): self.cnxids = {} def __del__(self): - """Class destructor""" + """Class destructor. This is only used to check for stray signals""" if len(self.cnxids.keys()) > 0: err('Signals remain. This is likely a bug: %s' % self.cnxids) From 7d08d790e27a2d7ade0cfea64710548aea53ea7b Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 18 Jan 2010 19:48:24 +0000 Subject: [PATCH 307/331] port Terminal to use Signalman --- terminatorlib/signalman.py | 5 ++++- terminatorlib/terminal.py | 35 ++++++++++++----------------------- terminatorlib/window.py | 2 +- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/terminatorlib/signalman.py b/terminatorlib/signalman.py index 97615552..f32f65d7 100755 --- a/terminatorlib/signalman.py +++ b/terminatorlib/signalman.py @@ -33,8 +33,11 @@ class Signalman(object): def remove_signal(self, widget, signal): """Remove a signal handler""" + if not self.cnxids.has_key(widget): + dbg('%s is not registered' % widget) + return if not self.cnxids[widget].has_key(signal): - err('%s not registered for %s' % (signal, type(widget))) + dbg('%s not registered for %s' % (signal, type(widget))) return dbg('removing %s::%s' % (type(widget), signal)) widget.disconnect(self.cnxids[widget][signal]) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 8ae2b018..7d0274e7 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -22,6 +22,7 @@ from titlebar import Titlebar from terminal_popup_menu import TerminalPopupMenu from searchbar import Searchbar from translation import _ +from signalman import Signalman import plugin try: @@ -97,8 +98,7 @@ class Terminal(gtk.VBox): self.connect('focus-in', self.terminator.focus_changed) self.matches = {} - # FIXME: Port cnxids to using Signalman - self.cnxids = {} + self.cnxids = Signalman() self.config = Config() @@ -267,9 +267,9 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) self.vte.connect('drag-data-received', self.on_drag_data_received, self) + # FIXME: Shouldn't this be in configure()? if self.config['copy_on_selection']: - self.cnxids['copy_on_selection'] = self.vte.connect( - 'selection-changed', + self.cnxids.new(self.vte, 'selection-changed', lambda widget: self.vte.copy_clipboard()) if self.composite_support: @@ -286,7 +286,7 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) self.vte.connect('enter_notify_event', self.on_vte_notify_enter) - self.cnxids['conf'] = self.vte.connect_after('realize', self.reconfigure) + self.cnxids.new(self.vte, 'realize', self.reconfigure) def create_popup_group_menu(self, widget, event = None): """Pop up a menu for the group widget""" @@ -461,21 +461,15 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) def reconfigure(self, widget=None): """Reconfigure our settings""" dbg('Terminal::reconfigure') - if self.cnxids.has_key('conf'): - self.vte.disconnect(self.cnxids['conf']) - del(self.cnxids['conf']) + self.cnxids.remove_signal(self.vte, 'realize') # Handle child command exiting - if self.cnxids.has_key('child-exited'): - dbg('Terminal::reconfigure: Dropping child-exited handler') - self.vte.disconnect(self.cnxids['child-exited']) - del(self.cnxids['child-exited']) + self.cnxids.remove_signal(self.vte, 'child-exited') if self.config['exit_action'] == 'restart': - self.cnxids['child-exited'] = self.vte.connect('child-exited', - self.spawn_child) + self.cnxids.new(self.vte, 'child-exited', self.spawn_child) elif self.config['exit_action'] in ('close', 'left'): - self.cnxids['child-exited'] = self.vte.connect('child-exited', + self.cnxids.new(self.vte, 'child-exited', lambda x: self.emit('close-term')) self.vte.set_emulation(self.config['emulation']) @@ -568,9 +562,7 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) if self.config['force_no_bell'] == True: self.vte.set_audible_bell(False) self.vte.set_visible_bell(False) - if self.cnxids.has_key('urgent_bell'): - self.vte.disconnect(self.cnxids['urgent_bell']) - del(self.cnxids['urgent_bell']) + self.cnxids.remove_signal(self.vte, 'urgent_bell') else: self.vte.set_audible_bell(self.config['audible_bell']) self.vte.set_visible_bell(self.config['visible_bell']) @@ -578,9 +570,7 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) # FIXME: Hook up a signal handler here pass else: - if self.cnxids.has_key('urgent_bell'): - self.vte.disconnect(self.cnxids['urgent_bell']) - del(self.cnxids['urgent_bell']) + self.cnxids.remove_signal(self.vte, 'urgent_bell') self.vte.set_scrollback_lines(self.config['scrollback_lines']) self.vte.set_scroll_on_keystroke(self.config['scroll_on_keystroke']) @@ -893,8 +883,7 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) def zoom_scale(self, widget, allocation, old_data): """Scale our font correctly based on how big we are not vs before""" - self.disconnect(self.cnxids['zoom']) - del(self.cnxids['zoom']) + self.cnxids.remove_signal(self, 'zoom') new_columns = self.vte.get_column_count() new_rows = self.vte.get_row_count() diff --git a/terminatorlib/window.py b/terminatorlib/window.py index a141d0fb..ad334ded 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -295,7 +295,7 @@ class Window(Container, gtk.Window): self.set_property('term_zoomed', True) if font_scale: - widget.cnxids['zoom'] = widget.connect('size-allocate', + widget.cnxids.new(widget, 'size-allocate', widget.zoom_scale, self.zoom_data) widget.grab_focus() From 3f214bf68603be57fddc16b5905f95d9a95f7394 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 18 Jan 2010 20:39:46 +0000 Subject: [PATCH 308/331] Restore ctrl-tab/ctrl-shit-tab --- terminatorlib/config.py | 6 ++++-- terminatorlib/terminal.py | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 2c199359..81328e7f 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -90,8 +90,10 @@ DEFAULTS = { 'zoom_out' : 'minus', 'zoom_normal' : '0', 'new_tab' : 't', - 'go_next' : 'n', # FIXME: Define ctrl-tab - 'go_prev' : 'p', #FIXME: ctrl-shift-tab + 'cycle_next' : 'Tab', + 'cycle_prev' : 'Tab', + 'go_next' : 'n', + 'go_prev' : 'p', 'go_up' : 'Up', 'go_down' : 'Down', 'go_left' : 'Left', diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 7d0274e7..ff19c302 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -1095,6 +1095,12 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) def key_new_root_tab(self): self.terminator.newtab (self, True) + def key_cycle_next(self): + self.key_go_next() + + def key_cycle_prev(self): + self.key_go_prev() + def key_go_next(self): self.emit('navigate', 'next') From e6ad61831d2cab76cea4872eb41cb06b39d39187 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 18 Jan 2010 20:40:52 +0000 Subject: [PATCH 309/331] Remove unnecessary print statements --- terminatorlib/terminator.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index 82c23190..b0de2726 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -114,9 +114,6 @@ class Terminator(Borg): if length <= 1: return - print "Current term: %d" % current - print "Number of terms: %d" % length - if direction == 'next': next = current + 1 if next >= length: @@ -130,7 +127,6 @@ class Terminator(Borg): # FIXME: Do the directional navigation if next is not None: - print "sending focus to term %d" % next self.terminals[next].grab_focus() def create_group(self, name): From bdbe35976771eeb843931e48de0e92697affeda7 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 18 Jan 2010 22:56:43 +0000 Subject: [PATCH 310/331] Restore -e and -x support --- terminatorlib/optionparse.py | 1 - terminatorlib/terminal.py | 28 ++++++++++++++++++++-------- terminatorlib/util.py | 2 +- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/terminatorlib/optionparse.py b/terminatorlib/optionparse.py index 08aececb..8ad7899b 100755 --- a/terminatorlib/optionparse.py +++ b/terminatorlib/optionparse.py @@ -94,7 +94,6 @@ WM_WINDOW_ROLE property on the window') sys.exit(1) configobj.options_set(options) - # FIXME: Map all the other bits of options to configobj if util.DEBUG == True: dbg('OptionParse::parse_options: command line options: %s' % options) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index ff19c302..534fd009 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -948,18 +948,29 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) self.vte.grab_focus() - if self.config['use_custom_command']: + options = self.config.options_get() + if options.command: + command = options.command + options.command = None + elif options.execute: + command = options.execute + options.execute = None + elif self.config['use_custom_command']: command = self.config['custom_command'] - shell = util.shell_lookup() - - if self.config['login_shell']: - args.insert(0, "-%s" % shell) + if type(command) is list: + shell = util.path_lookup(command[0]) + args = command else: - args.insert(0, shell) + shell = util.shell_lookup() - if command is not None: - args += ['-c', command] + if self.config['login_shell']: + args.insert(0, "-%s" % shell) + else: + args.insert(0, shell) + + if command is not None: + args += ['-c', command] if shell is None: self.vte.feed(_('Unable to find a shell')) @@ -970,6 +981,7 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) except AttributeError: pass + dbg('Forking shell: "%s" with args: %s' % (shell, args)) self.pid = self.vte.fork_command(command=shell, argv=args, envv=[], loglastlog=login, logwtmp=update_records, logutmp=update_records, directory=self.cwd) diff --git a/terminatorlib/util.py b/terminatorlib/util.py index 143bc6f8..bb812839 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -101,7 +101,7 @@ def path_lookup(command): dbg('path_lookup: PATH not set in environment, using fallbacks') paths = ['/usr/local/bin', '/usr/bin', '/bin'] - dbg('path_lookup: Using %d paths: %s', (len(paths), paths)) + dbg('path_lookup: Using %d paths: %s' % (len(paths), paths)) for path in paths: target = os.path.join(path, command) From abe9b245180b23b9c4f5c24052e98eb41903fc6d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 18 Jan 2010 23:27:22 +0000 Subject: [PATCH 311/331] Implement titlebar colours --- terminatorlib/config.py | 6 ++++ terminatorlib/terminal.py | 2 +- terminatorlib/terminator.py | 2 +- terminatorlib/titlebar.py | 56 +++++++++++++++++++++++++++++++++++-- 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 81328e7f..08718c9f 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -83,6 +83,12 @@ DEFAULTS = { 'hide_tabbar' : False, 'scroll_tabbar' : False, 'try_posix_regexp' : platform.system() != 'Linux', + 'title_transmit_fg_color' : '#ffffff', + 'title_transmit_bg_color' : '#c80003', + 'title_receive_fg_color' : '#ffffff', + 'title_receive_bg_color' : '#0076c9', + 'title_inactive_fg_color' : '#000000', + 'title_inactive_bg_color' : '#c0bebf', 'disabled_plugins' : ['TestPlugin', 'CustomCommandsMenu'], }, 'keybindings': { diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 534fd009..944bebe0 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -118,7 +118,7 @@ class Terminal(gtk.VBox): self.terminalbox = self.create_terminalbox() - self.titlebar = Titlebar() + self.titlebar = Titlebar(self) self.titlebar.connect_icon(self.on_group_button_press) self.titlebar.connect('edit-done', self.on_edit_done) self.connect('title-change', self.titlebar.set_terminal_title) diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index b0de2726..6c2955d6 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -218,6 +218,6 @@ class Terminator(Borg): def focus_changed(self, widget): """We just moved focus to a new terminal""" for terminal in self.terminals: - terminal.titlebar.update() + terminal.titlebar.update(widget) return # vim: set expandtab ts=4 sw=4: diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index 43f4d3b1..b1bab28f 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -17,6 +17,8 @@ class Titlebar(gtk.EventBox): """Class implementing the Titlebar widget""" terminator = None + terminal = None + config = None oldtitle = None termtext = None sizetext = None @@ -33,12 +35,14 @@ class Titlebar(gtk.EventBox): (gobject.TYPE_STRING,)), } - def __init__(self): + def __init__(self, terminal): """Class initialiser""" gtk.EventBox.__init__(self) self.__gobject_init__() self.terminator = Terminator() + self.terminal = terminal + self.config = self.terminal.config self.label = EditableLabel() self.label.connect('edit-done', self.on_edit_done) @@ -84,10 +88,56 @@ class Titlebar(gtk.EventBox): """Connect the supplied function to clicking on the group icon""" self.ebox.connect('button-release-event', func) - def update(self): + def update(self, other=None): """Update our contents""" self.label.set_text("%s %s" % (self.termtext, self.sizetext)) - # FIXME: Aren't we supposed to be setting a colour here too? + + if other: + term = self.terminal + terminator = self.terminator + if term != other and term.group and term.group == other.group: + if terminator.groupsend == terminator.groupsend_type['off']: + title_fg = self.config['title_inactive_fg_color'] + title_bg = self.config['title_inactive_bg_color'] + icon = '_receive_off' + else: + title_fg = self.config['title_receive_fg_color'] + title_bg = self.config['title_receive_bg_color'] + icon = '_receive_on' + group_fg = self.config['title_receive_fg_color'] + group_bg = self.config['title_receive_bg_color'] + elif term != other and not term.group or term.group != other.group: + if terminator.groupsend == terminator.groupsend_type['all']: + title_fg = self.config['title_receive_fg_color'] + title_bg = self.config['title_receive_bg_color'] + icon = '_receive_on' + else: + title_fg = self.config['title_inactive_fg_color'] + title_bg = self.config['title_inactive_bg_color'] + icon = '_receive_off' + group_fg = self.config['title_inactive_fg_color'] + group_bg = self.config['title_inactive_bg_color'] + else: + title_fg = self.config['title_transmit_fg_color'] + title_bg = self.config['title_transmit_bg_color'] + if terminator.groupsend == terminator.groupsend_type['all']: + icon = '_active_broadcast_all' + elif terminator.groupsend == terminator.groupsend_type['group']: + icon = '_active_broadcast_group' + else: + icon = '_active_broadcast_off' + group_fg = self.config['title_transmit_fg_color'] + group_bg = self.config['title_transmit_bg_color'] + + self.label.modify_fg(gtk.STATE_NORMAL, + gtk.gdk.color_parse(title_fg)) + self.grouplabel.modify_fg(gtk.STATE_NORMAL, + gtk.gdk.color_parse(group_fg)) + self.modify_bg(gtk.STATE_NORMAL, + gtk.gdk.color_parse(title_bg)) + self.ebox.modify_bg(gtk.STATE_NORMAL, + gtk.gdk.color_parse(group_bg)) + self.set_from_icon_name(icon, gtk.ICON_SIZE_MENU) def set_from_icon_name(self, name, size = gtk.ICON_SIZE_MENU): """Set an icon for the group label""" From df6a370bf3dd31e27d6cadd44cde5859c62d9ab0 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 19 Jan 2010 11:14:01 +0000 Subject: [PATCH 312/331] This is a very subtle bug, Multiple exception catches need to be a tuple. We were previously creating an exception object *called* NameError when we caught a KeyError --- terminatorlib/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index ad334ded..e99a6801 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -80,7 +80,7 @@ class Window(Container, gtk.Window): self.hidebound = bindkey.tomboy_keybinder_bind( self.config['keybindings']['hide_window'], self.on_hide_window) - except KeyError,NameError: + except (KeyError, NameError): pass if not self.hidebound: From 5cad06295e4247aa496174cb8dda77883ccef2e7 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 19 Jan 2010 11:22:51 +0000 Subject: [PATCH 313/331] Make the prefs editor not explode on cycle_* --- terminatorlib/prefseditor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 81a04871..035743cc 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -29,6 +29,8 @@ class PrefsEditor: 'zoom_out' : 'Decrease font size', 'zoom_normal' : 'Restore original font size', 'new_tab' : 'Create a new tab', + 'cycle_next' : 'Focus the next terminal', + 'cycle_prev' : 'Focus the previous terminal', 'go_next' : 'Focus the next terminal', 'go_prev' : 'Focus the previous terminal', 'go_up' : 'Focus the terminal above', From ac95dddfbefe9e3fd686f54507cb3177d021cf0e Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 19 Jan 2010 13:06:09 +0000 Subject: [PATCH 314/331] start working on a set of methods to walk the widget tree to find the visible ones, and collect their gtk allocation (i.e. where they are and how big they are). This is expected to form the basis of directional navigation and layout display/saving --- terminatorlib/container.py | 16 ++++++++++++++++ terminatorlib/terminator.py | 6 +++++- terminatorlib/window.py | 24 ++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/terminatorlib/container.py b/terminatorlib/container.py index a0914bfd..175da7c3 100755 --- a/terminatorlib/container.py +++ b/terminatorlib/container.py @@ -173,4 +173,20 @@ the %s will also close all terminals within it.') % (reqtype, reqtype)) if maker.isinstance(parent, 'Container'): parent.propagate_title_change(widget, title) + def get_visible_terminals(self): + """Walk the widget tree to find all of the visible terminals. That is, + any terminals which are not hidden in another Notebook pane""" + maker = Factory() + terminals = {} + + for child in self.get_offspring(): + if maker.isinstance(child, 'Terminal'): + terminals[child] = child.get_allocation() + elif maker.isinstance(child, 'Container'): + terminals.update(child.get_visible_terminals()) + else: + err('Unknown child type %s' % type(child)) + + return(terminals) + # vim: set expandtab ts=4 sw=4: diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index 6c2955d6..9a2614ef 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -123,8 +123,12 @@ class Terminator(Borg): if next < 0: next = length - 1 else: + window = get_top_window(terminal) + layout = window.get_visible_terminals() + # FIXME: Do the directional navigation, don't just print the layout + import pprint + pprint.pprint(layout) raise NotImplementedError - # FIXME: Do the directional navigation if next is not None: self.terminals[next].grab_focus() diff --git a/terminatorlib/window.py b/terminatorlib/window.py index e99a6801..413f0840 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -318,6 +318,30 @@ class Window(Container, gtk.Window): self.zoom_data = None self.set_property('term_zoomed', False) + def get_visible_terminals(self): + """Walk down the widget tree to find all of the visible terminals. + Mostly using Container::get_visible_terminals()""" + maker = Factory() + child = self.get_child() + terminals = {} + + # If our child is a Notebook, reset to work from its visible child + if maker.isinstance(child, 'Notebook'): + pagenum = child.get_current_page() + child = child.get_nth_page(pagenum) + + if maker.isinstance(child, 'Container'): + terminals.update(child.get_visible_terminals()) + elif maker.isnstance(child, 'Terminal'): + # This branch is only possible if we started out as a Notebook. We + # wouldn't have been called if there was really only one Terminal + # child. + terminals[child] = child.get_allocation() + else: + err('Unknown child type %s' % type(child)) + + return(terminals) + class WindowTitle(object): """Class to handle the setting of the window title""" From 35910b7a85e5c35ee898664b6787b362e51cdd3c Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 19 Jan 2010 19:44:23 +0000 Subject: [PATCH 315/331] Tidy up the email match, and fix the nntp one (thanks dutchie) --- terminatorlib/terminal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 944bebe0..0177c7f8 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -217,10 +217,10 @@ class Terminal(gtk.VBox): ".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?") self.matches['email'] = self.vte.match_add (lboundry + "(mailto:)?[a-zA-Z0-9][a-zA-Z0-9.+-]*@[a-zA-Z0-9]" + - "[a-zA-Z0-9-]*\.[a-zA-Z0-9][a-zA-Z0-9-]+" + "[a-zA-Z0-9-]*\.[a-zA-Z0-9][a-zA-Z0-9-]+" + "[.a-zA-Z0-9-]*" + rboundry) self.matches['nntp'] = self.vte.match_add (lboundry + - "news:[-A-Z\^_a-z{|}~!"#$%&'()*+,./0-9;:=?`]+@" + """news:[-A-Z\^_a-z{|}~!"#$%&'()*+,./0-9;:=?`]+@""" + "[-A-Za-z0-9.]+(:[0-9]+)?" + rboundry) # Now add any matches from plugins From 4bad0a6b9641f714409e518daff3ca1fe3967d79 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 19 Jan 2010 19:44:34 +0000 Subject: [PATCH 316/331] temporarily disable setup.py since it probably doesn't work yet --- setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.py b/setup.py index aa3c7bb1..929eaaae 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,10 @@ #!/usr/bin/env python +print "You probably don't want to run this, we're not ready for proper \ +installation yet" +import sys +sys.exit(0) + from distutils.core import setup from distutils.dist import Distribution from distutils.cmd import Command From 4a1a65dfbd3bd8a07638c4cff3a2f60cf270f44f Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 19 Jan 2010 20:03:05 +0000 Subject: [PATCH 317/331] Make icon setting more rubust --- terminatorlib/window.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 413f0840..72807751 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -7,6 +7,7 @@ import pygtk pygtk.require('2.0') import gobject import gtk +import glib from util import dbg, err from translation import _ @@ -122,7 +123,7 @@ class Window(Container, gtk.Window): try: icon = icon_theme.load_icon(APP_NAME, 48, 0) - except NameError: + except (NameError, glib.GError): dbg('Unable to load 48px Terminator icon') icon = self.render_icon(gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_BUTTON) From 4ed18d8a63ef6498185872eb4968707ff3c3e106 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 19 Jan 2010 20:33:35 +0000 Subject: [PATCH 318/331] Make setup.py install configobj, plugins, the preferences dialog and more eloquently handle the uninstall manifest --- setup.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/setup.py b/setup.py index 929eaaae..919cfb04 100755 --- a/setup.py +++ b/setup.py @@ -1,24 +1,20 @@ #!/usr/bin/env python -print "You probably don't want to run this, we're not ready for proper \ -installation yet" -import sys -sys.exit(0) - from distutils.core import setup from distutils.dist import Distribution from distutils.cmd import Command from distutils.command.install_data import install_data from distutils.command.build import build from distutils.dep_util import newer -from distutils.log import warn, info, error, fatal +from distutils.log import warn, info, error +from distutils.errors import DistutilsFileError import glob import os import sys import subprocess import platform -from terminatorlib.version import * +from terminatorlib.version import APP_NAME, APP_VERSION PO_DIR = 'po' MO_DIR = os.path.join('build', 'mo') @@ -88,6 +84,8 @@ class Uninstall(Command): self.ensure_filename('manifest') try: try: + if not self.manifest: + raise DistutilsFileError("Pass manifest with --manifest=file") f = open(self.manifest) files = [file.strip() for file in f] except IOError, e: @@ -157,7 +155,7 @@ if platform.system() == 'FreeBSD': else: man_dir = 'share/man' -setup(name='Terminator', +setup(name=APP_NAME.capitalize(), version=APP_VERSION, description='Terminator, the robot future of terminals', author='Chris Jones', @@ -166,7 +164,6 @@ setup(name='Terminator', license='GNU GPL v2', scripts=['terminator'], data_files=[ - ('share/terminator', ['data/preferences.glade']), ('share/applications', ['data/terminator.desktop']), (os.path.join(man_dir, 'man1'), ['doc/terminator.1']), (os.path.join(man_dir, 'man5'), ['doc/terminator_config.5']), @@ -179,7 +176,9 @@ setup(name='Terminator', ('share/icons/hicolor/48x48/apps', glob.glob('data/icons/48x48/apps/*.png')), ('share/icons/hicolor/16x16/actions', glob.glob('data/icons/16x16/actions/*.png')), ], - packages=['terminatorlib'], + packages=['terminatorlib', 'terminatorlib.configobj', + 'terminatorlib.plugins'], + package_data={'terminatorlib': ['preferences.glade']}, cmdclass={'build': BuildData, 'install_data': InstallData, 'uninstall': Uninstall}, distclass=TerminatorDist ) From 36631b20e44626a567ca7a88617b55290cf6bd90 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 19 Jan 2010 22:44:05 +0000 Subject: [PATCH 319/331] Teach Notebook how to hoover itself since it might be called during drag&drop, and refactor its suicide detection into that function --- terminatorlib/notebook.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index 8b10caee..fb6f17ca 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -134,17 +134,7 @@ class Notebook(Container, gtk.Notebook): dbg('Notebook::wrapcloseterm: called on %s' % widget) if self.closeterm(widget): dbg('Notebook::wrapcloseterm: closeterm succeeded') - if self.get_n_pages() == 1: - dbg('Notebook::wrapcloseterm: last page, removing self') - child = self.get_nth_page(0) - self.remove_page(0) - parent = self.get_parent() - parent.remove(self) - parent.add(child) - del(self) - else: - dbg('Notebook::wrapcloseterm: %d pages remain' % - self.get_n_pages()) + self.hoover() else: dbg('Notebook::wrapcloseterm: closeterm failed') @@ -249,6 +239,25 @@ class Notebook(Container, gtk.Notebook): label.set_label(text) + def hoover(self): + """Clean up any empty tabs and if we only have one tab left, die""" + numpages = self.get_n_pages() + while numpages > 0: + numpages = numpages - 1 + page = self.get_nth_page(numpages) + if not page: + dbg('Removing empty page: %d' % numpages) + self.remove_page(numpages) + + if self.get_n_pages() == 1: + dbg('Last page, removing self') + child = self.get_nth_page(0) + self.remove_page(0) + parent = self.get_parent() + parent.remove(self) + parent.add(child) + del(self) + class TabLabel(gtk.HBox): """Class implementing a label widget for Notebook tabs""" notebook = None From db7b0cc0a0e98ca9db0f880b2c221612d2e65d8f Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 20 Jan 2010 00:54:35 +0000 Subject: [PATCH 320/331] Directional navigation. Only works for going left yet, see included FIXME --- terminatorlib/terminal.py | 7 ++++++ terminatorlib/terminator.py | 48 ++++++++++++++++++++++++++++++++----- terminatorlib/util.py | 32 +++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 6 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 0177c7f8..ea8a9b62 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -1077,6 +1077,13 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) self.vte.set_font(pango.FontDescription(self.config['font'])) self.custom_font_size = None + def get_cursor_position(self): + """Return the co-ordinates of our cursor""" + col, row = self.vte.get_cursor_position() + width = self.vte.get_char_width() + height = self.vte.get_char_height() + return((col * width, row * height)) + # There now begins a great list of keyboard event handlers # FIXME: Probably a bunch of these are wrong. TEST! def key_zoom_in(self): diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index 9a2614ef..ca7469d9 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -8,7 +8,8 @@ import gtk from borg import Borg from config import Config from keybindings import Keybindings -from util import dbg, get_top_window +from util import dbg, err, get_top_window +import util class Terminator(Borg): """master object for the application""" @@ -122,13 +123,48 @@ class Terminator(Borg): next = current - 1 if next < 0: next = length - 1 - else: + elif direction in ['left', 'right', 'up', 'down']: + print direction window = get_top_window(terminal) layout = window.get_visible_terminals() - # FIXME: Do the directional navigation, don't just print the layout - import pprint - pprint.pprint(layout) - raise NotImplementedError + + allocation = terminal.get_allocation() + possibles = [] + + # left + edge = util.get_edge(allocation, direction) + for term in layout: + rect = layout[term] + if util.get_nav_possible(edge, rect, direction): + possibles.append(term) + + if len(possibles) == 0: + return + + # FIXME: Check if the selection of winners and the tie-break need + # helper functions to make them direction agnostic. Likely the + # offset calculation will + offsets = {} + for term in possibles: + rect = layout[term] + offset = edge - (rect.x + rect.width) + offsets[term] = offset + keys = offsets.values() + keys.sort() + winners = [k for k, v in offsets.iteritems() if v == keys[0]] + next = self.terminals.index(winners[0]) + + # Break an n-way tie + cursor_x, cursor_y = terminal.get_cursor_position() + cursor_x = cursor_x + allocation.x + cursor_y = cursor_y + allocation.y + for term in winners: + rect = layout[term] + if cursor_y >= rect.y and cursor_y <= (rect.y + rect.height): + next = self.terminals.index(term) + break; + else: + err('Unknown navigation direction: %s' % direction) if next is not None: self.terminals[next].grab_focus() diff --git a/terminatorlib/util.py b/terminatorlib/util.py index bb812839..aa5997d0 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -170,3 +170,35 @@ def dict_diff(reference, working): result[key] = working[key] return(result) + +# Helper functions for directional navigation +def get_edge(allocation, direction): + """Return the edge of the supplied allocation that we will care about for + directional navigation""" + if direction == 'left': + edge = allocation.x + elif direction == 'up': + edge = allocation.y + elif direction == 'right': + edge = allocation.x + allocation.width + elif direction == 'down': + edge = allocation.y + allocation.height + else: + raise ValueError('unknown direction %s' % direction) + + return(edge) + +def get_nav_possible(edge, allocation, direction): + """Check if the supplied allocation is in the right direction of the + supplied edge""" + if direction == 'left': + return((allocation.x + allocation.width) < edge) + elif direction == 'right': + return(allocation.x > edge) + elif direction == 'up': + return((allocation.y + allocation.height) < edge) + elif direction == 'down': + return(allocation.y > edge) + else: + raise ValueError('Unknown direction: %s' % direction) + From ddce3a862d758d95830dde238e06c38610bdd61f Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 20 Jan 2010 12:11:02 +0000 Subject: [PATCH 321/331] URL dragging was broken because Terminator::get_target_terms() makes no assumptions about which terminal to examine, but it used to --- terminatorlib/terminal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index ea8a9b62..acbb4bcc 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -771,7 +771,7 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) txt = selection_data.data.strip() if txt[0:7] == 'file://': text = "'%s'" % urllib.unquote(txt[7:]) - for term in self.terminator.get_target_terms(): + for term in self.terminator.get_target_terms(self): term.feed(txt) return From ee72ae478feb5113063499b153a469ee6700d6fd Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 20 Jan 2010 13:04:14 +0000 Subject: [PATCH 322/331] refactor out the last two decisions in the directional navigation so instead of being test code that can only move left, they now move in all four directions --- terminatorlib/terminator.py | 6 ++---- terminatorlib/util.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index ca7469d9..790ae03d 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -124,7 +124,6 @@ class Terminator(Borg): if next < 0: next = length - 1 elif direction in ['left', 'right', 'up', 'down']: - print direction window = get_top_window(terminal) layout = window.get_visible_terminals() @@ -147,8 +146,7 @@ class Terminator(Borg): offsets = {} for term in possibles: rect = layout[term] - offset = edge - (rect.x + rect.width) - offsets[term] = offset + offsets[term] = util.get_nav_offset(edge, rect, direction) keys = offsets.values() keys.sort() winners = [k for k, v in offsets.iteritems() if v == keys[0]] @@ -160,7 +158,7 @@ class Terminator(Borg): cursor_y = cursor_y + allocation.y for term in winners: rect = layout[term] - if cursor_y >= rect.y and cursor_y <= (rect.y + rect.height): + if util.get_nav_tiebreak(direction, cursor_x, cursor_y, rect): next = self.terminals.index(term) break; else: diff --git a/terminatorlib/util.py b/terminatorlib/util.py index aa5997d0..bca77884 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -202,3 +202,27 @@ def get_nav_possible(edge, allocation, direction): else: raise ValueError('Unknown direction: %s' % direction) +def get_nav_offset(edge, allocation, direction): + """Work out how far edge is from a particular point on the allocation + rectangle, in the given direction""" + if direction == 'left': + return(edge - (allocation.x + allocation.width)) + elif direction == 'right': + return(edge + allocation.x) + elif direction == 'up': + return(edge - (allocation.y - allocation.height)) + elif direction == 'down': + return(edge + allocation.y) + else: + raise ValueError('Unknown direction: %s' % direction) + +def get_nav_tiebreak(direction, cursor_x, cursor_y, rect): + """We have multiple candidate terminals. Pick the closest by cursor + position""" + if direction in ['left', 'right']: + return(cursor_y >= rect.y and cursor_y <= (rect.y + rect.height)) + elif direction in ['up', 'down']: + return(cursor_x >= rect.x and cursor_x <= (rect.x + rect.width)) + else: + raise ValueError('Unknown direction: %s' % direction) + From 021f5cef98dc65d79adfbd611faf3d8dcb42fd17 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 20 Jan 2010 19:57:16 +0000 Subject: [PATCH 323/331] remove the silly old comments in the directional navigation section and add some better ones describing how it works --- terminatorlib/terminator.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index 790ae03d..da8bb7f8 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -130,8 +130,10 @@ class Terminator(Borg): allocation = terminal.get_allocation() possibles = [] - # left + # Get the co-ordinate of the appropriate edge for this direction edge = util.get_edge(allocation, direction) + # Find all visible terminals which are, in their entirity, in the + # direction we want to move for term in layout: rect = layout[term] if util.get_nav_possible(edge, rect, direction): @@ -140,9 +142,9 @@ class Terminator(Borg): if len(possibles) == 0: return - # FIXME: Check if the selection of winners and the tie-break need - # helper functions to make them direction agnostic. Likely the - # offset calculation will + # Find out how far away each of the possible terminals is, then + # find the smallest distance. The winning terminals are all of + # those who are that distance away. offsets = {} for term in possibles: rect = layout[term] @@ -152,15 +154,16 @@ class Terminator(Borg): winners = [k for k, v in offsets.iteritems() if v == keys[0]] next = self.terminals.index(winners[0]) - # Break an n-way tie - cursor_x, cursor_y = terminal.get_cursor_position() - cursor_x = cursor_x + allocation.x - cursor_y = cursor_y + allocation.y - for term in winners: - rect = layout[term] - if util.get_nav_tiebreak(direction, cursor_x, cursor_y, rect): - next = self.terminals.index(term) - break; + if len(winners) > 1: + # Break an n-way tie with the cursor position + cursor_x, cursor_y = terminal.get_cursor_position() + cursor_x = cursor_x + allocation.x + cursor_y = cursor_y + allocation.y + for term in winners: + rect = layout[term] + if util.get_nav_tiebreak(direction, cursor_x, cursor_y, rect): + next = self.terminals.index(term) + break; else: err('Unknown navigation direction: %s' % direction) From a10babeb5d25af31ea4515a2e7fc83229ffa836f Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 20 Jan 2010 23:36:11 +0000 Subject: [PATCH 324/331] Port geometry hinting from trunk to epic-refactor. HEY THEO. --- terminatorlib/terminal.py | 11 +++++++++++ terminatorlib/window.py | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index acbb4bcc..a74d560c 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -859,6 +859,9 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) def on_vte_size_allocate(self, widget, allocation): self.titlebar.update_terminal_size(self.vte.get_column_count(), self.vte.get_row_count()) + if self.vte.window and self.config['geometry_hinting']: + window = util.get_top_window(self) + window.set_rough_geometry_hints() def on_vte_notify_enter(self, term, event): """Handle the mouse entering this terminal""" @@ -1084,6 +1087,14 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) height = self.vte.get_char_height() return((col * width, row * height)) + def get_font_size(self): + """Return the width/height of our font""" + return((self.vte.get_char_width(), self.vte.get_char_height())) + + def get_size(self): + """Return the column/rows of the terminal""" + return((self.vte.get_column_count(), self.vte.get_row_count())) + # There now begins a great list of keyboard event handlers # FIXME: Probably a bunch of these are wrong. TEST! def key_zoom_in(self): diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 72807751..40a76f84 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -333,16 +333,44 @@ class Window(Container, gtk.Window): if maker.isinstance(child, 'Container'): terminals.update(child.get_visible_terminals()) - elif maker.isnstance(child, 'Terminal'): - # This branch is only possible if we started out as a Notebook. We - # wouldn't have been called if there was really only one Terminal - # child. + elif maker.isinstance(child, 'Terminal'): terminals[child] = child.get_allocation() else: err('Unknown child type %s' % type(child)) return(terminals) + def set_rough_geometry_hints(self): + """Walk all the terminals along the top and left edges to fake up how + many columns/rows we sort of have""" + terminals = self.get_visible_terminals() + column_sum = 0 + row_sum = 0 + + for terminal in terminals: + rect = terminal.get_allocation() + if rect.x == 0: + cols, rows = terminal.get_size() + row_sum = row_sum + rows + if rect.y == 0: + cols, rows = terminal.get_size() + column_sum = column_sum + cols + + # FIXME: I don't think we should just use whatever font size info is on + # the last terminal we inspected. Looking up the default profile font + # size and calculating its character sizes would be rather expensive + # though. + font_width, font_height = terminal.get_font_size() + total_font_width = font_width * column_sum + total_font_height = font_height * row_sum + + win_width, win_height = self.get_size() + extra_width = win_width - total_font_width + extra_height = win_height - total_font_height + + self.set_geometry_hints(self, -1, -1, -1, -1, extra_width, + extra_height, font_width, font_height, -1.0, -1.0) + class WindowTitle(object): """Class to handle the setting of the window title""" From 29d0414a04a60ac0f91563ad843d299b5500ada0 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 20 Jan 2010 23:46:13 +0000 Subject: [PATCH 325/331] Fix inverted groupsend handling and generally make it clearer what we mean here by not using the magic groupsend type values --- terminatorlib/terminal.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index a74d560c..fc417705 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -631,12 +631,14 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) return(True) # FIXME: This is all clearly wrong. We should be doing this better - # FIXMS: maybe we can emit the key event and let Terminator() care? - if self.terminator.groupsend != 0 and self.vte.is_focus(): - if self.group and self.terminator.groupsend == 1: + # maybe we can emit the key event and let Terminator() care? + groupsend = self.terminator.groupsend + groupsend_type = self.terminator.groupsend_type + if groupsend != groupsend_type['off'] and self.vte.is_focus(): + if self.group and groupsend == groupsend_type['group']: self.terminator.group_emit(self, self.group, 'key-press-event', event) - if self.terminator.groupsend == 2: + if groupsend == groupsend_type['all']: self.terminator.all_emit(self, 'key-press-event', event) return(False) From e2db10fc09f0ae1edada5b7f5369d06ad0ccb206 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 21 Jan 2010 00:13:56 +0000 Subject: [PATCH 326/331] Implement urgent bell handler --- terminatorlib/terminal.py | 14 ++++++++++---- terminatorlib/window.py | 3 ++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index fc417705..df9c4c4b 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -566,11 +566,12 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) else: self.vte.set_audible_bell(self.config['audible_bell']) self.vte.set_visible_bell(self.config['visible_bell']) + self.cnxids.remove_signal(self.vte, 'urgent_bell') if self.config['urgent_bell'] == True: - # FIXME: Hook up a signal handler here - pass - else: - self.cnxids.remove_signal(self.vte, 'urgent_bell') + try: + self.cnxids.new(self.vte, 'beep', self.on_beep) + except TypeError: + err('beep signal unavailable with this version of VTE') self.vte.set_scrollback_lines(self.config['scrollback_lines']) self.vte.set_scroll_on_keystroke(self.config['scroll_on_keystroke']) @@ -1097,6 +1098,11 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) """Return the column/rows of the terminal""" return((self.vte.get_column_count(), self.vte.get_row_count())) + def on_beep(self, widget): + """Set the urgency hint for our window""" + window = util.get_top_window(self) + window.set_urgency_hint(True) + # There now begins a great list of keyboard event handlers # FIXME: Probably a bunch of these are wrong. TEST! def key_zoom_in(self): diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 40a76f84..aa296e1c 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -132,7 +132,8 @@ class Window(Container, gtk.Window): def on_key_press(self, window, event): """Handle a keyboard event""" maker = Factory() - # FIXME: We probably want to cancel window urgency here + + self.set_urgency_hint(False) mapping = self.terminator.keybindings.lookup(event) From 0f5b6e97a7818d193cb2e8c7f0cd39ab4c5073bd Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 21 Jan 2010 12:14:12 +0000 Subject: [PATCH 327/331] Fix WM_URGENT removal by using the right signal name --- terminatorlib/terminal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index df9c4c4b..ab5b243e 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -562,11 +562,11 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) if self.config['force_no_bell'] == True: self.vte.set_audible_bell(False) self.vte.set_visible_bell(False) - self.cnxids.remove_signal(self.vte, 'urgent_bell') + self.cnxids.remove_signal(self.vte, 'beep') else: self.vte.set_audible_bell(self.config['audible_bell']) self.vte.set_visible_bell(self.config['visible_bell']) - self.cnxids.remove_signal(self.vte, 'urgent_bell') + self.cnxids.remove_signal(self.vte, 'beep') if self.config['urgent_bell'] == True: try: self.cnxids.new(self.vte, 'beep', self.on_beep) From 9c72b6287d73bb94db6b4e219f9fac3fe43e9ee8 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 21 Jan 2010 12:33:42 +0000 Subject: [PATCH 328/331] Implement tab changing keyboard shortcuts --- terminatorlib/notebook.py | 5 ++++- terminatorlib/paned.py | 1 + terminatorlib/terminal.py | 30 +++++++++++++++--------------- terminatorlib/window.py | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 16 deletions(-) diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index fb6f17ca..9205dd28 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -12,7 +12,7 @@ from factory import Factory from container import Container from editablelabel import EditableLabel from translation import _ -from util import err, dbg +from util import err, dbg, get_top_window class Notebook(Container, gtk.Notebook): """Class implementing a gtk.Notebook container""" @@ -97,6 +97,8 @@ class Notebook(Container, gtk.Notebook): def newtab(self, widget=None): """Add a new tab, optionally supplying a child widget""" + top_window = get_top_window(self) + if not widget: maker = Factory() widget = maker.make('Terminal') @@ -112,6 +114,7 @@ class Notebook(Container, gtk.Notebook): if maker.isinstance(widget, 'Terminal'): for signal in signals: self.connect_child(widget, signal, signals[signal]) + self.connect_child(widget, 'tab-change', top_window.tab_change) self.set_tab_reorderable(widget, True) label = TabLabel(self.window.get_title(), self) diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index 914acec2..ed48190b 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -91,6 +91,7 @@ class Paned(Container): self.connect_child(widget, signal, signals[signal]) self.connect_child(widget, 'maximise', top_window.zoom, False) + self.connect_child(widget, 'tab-change', top_window.tab_change) widget.grab_focus() diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index ab5b243e..a4daf428 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -56,6 +56,8 @@ class Terminal(gtk.VBox): (gobject.TYPE_STRING,)), 'navigate': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), + 'tab-change': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_INT,)), } TARGET_TYPE_VTE = 8 @@ -1197,43 +1199,40 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) self.zoom() def key_next_tab(self): - # FIXME: Implement this - self.terminator.next_tab (self) + self.emit('tab-change', -1) def key_prev_tab(self): - # FIXME: Implement this - self.terminator.previous_tab (self) + self.emit('tab-change', -2) def key_switch_to_tab_1(self): - # FIXME: Implement these - self.terminator.switch_to_tab (self, 0) + self.emit('tab-change', 0) def key_switch_to_tab_2(self): - self.terminator.switch_to_tab (self, 1) + self.emit('tab-change', 1) def key_switch_to_tab_3(self): - self.terminator.switch_to_tab (self, 2) + self.emit('tab-change', 2) def key_switch_to_tab_4(self): - self.terminator.switch_to_tab (self, 3) + self.emit('tab-change', 3) def key_switch_to_tab_5(self): - self.terminator.switch_to_tab (self, 4) + self.emit('tab-change', 4) def key_switch_to_tab_6(self): - self.terminator.switch_to_tab (self, 5) + self.emit('tab-change', 5) def key_switch_to_tab_7(self): - self.terminator.switch_to_tab (self, 6) + self.emit('tab-change', 6) def key_switch_to_tab_8(self): - self.terminator.switch_to_tab (self, 7) + self.emit('tab-change', 7) def key_switch_to_tab_9(self): - self.terminator.switch_to_tab (self, 8) + self.emit('tab-change', 8) def key_switch_to_tab_10(self): - self.terminator.switch_to_tab (self, 9) + self.emit('tab-change', 9) def key_reset(self): self.vte.reset (True, False) @@ -1254,6 +1253,7 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) self.group_tab(self) def key_ungroup_tab(self): + # FIXME: IMplement this self.ungroup_tab(self) def key_new_window(self): diff --git a/terminatorlib/window.py b/terminatorlib/window.py index aa296e1c..230ab430 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -248,6 +248,7 @@ class Window(Container, gtk.Window): for signal in signals: self.connect_child(widget, signal, signals[signal]) + self.connect_child(widget, 'tab-change', self.tab_change) widget.grab_focus() def remove(self, widget): @@ -372,6 +373,38 @@ class Window(Container, gtk.Window): self.set_geometry_hints(self, -1, -1, -1, -1, extra_width, extra_height, font_width, font_height, -1.0, -1.0) + def tab_change(self, widget, num=None): + """Change to a specific tab""" + if num is None: + err('must specify a tab to change to') + + maker = Factory() + child = self.get_child() + + if not maker.isinstance(child, 'Notebook'): + dbg('child is not a notebook, nothing to change to') + return + + if num == -1: + # Go to the next tab + cur = child.get_current_page() + pages = child.get_n_pages() + if cur == pages - 1: + num = 0 + elif num == -2: + # Go to the previous tab + cur = child.get_current_page() + if cur > 0: + num = cur - 1 + else: + num = child.get_n_pages() - 1 + + child.set_current_page(num) + # Work around strange bug in gtk-2.12.11 and pygtk-2.12.1 + # Without it, the selection changes, but the displayed page doesn't + # change + child.set_current_page(child.get_current_page()) + class WindowTitle(object): """Class to handle the setting of the window title""" From 14f98c1b8922b4af392b8b69037b1c9d8a6357a5 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 21 Jan 2010 12:55:57 +0000 Subject: [PATCH 329/331] Implement (un)group_all and (un)group_tab --- terminatorlib/notebook.py | 7 ++++++- terminatorlib/paned.py | 7 ++++++- terminatorlib/terminal.py | 16 +++++++------- terminatorlib/window.py | 44 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index 9205dd28..c256b4f3 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -41,7 +41,12 @@ class Notebook(Container, gtk.Notebook): def configure(self): """Apply widget-wide settings""" - # FIXME: Should all of our widgets have this? + # FIXME: Should all of our widgets have a ::configure()? + + # FIXME: The old reordered handler updated Terminator.terminals with + # the new order of terminals. We probably need to preserve this for + # navigation to next/prev terminals. + #self.connect('page-reordered', self.on_page_reordered) self.set_property('homogeneous', not self.config['scroll_tabbar']) self.set_scrollable(self.config['scroll_tabbar']) diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index ed48190b..42670ad5 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -79,7 +79,6 @@ class Paned(Container): if maker.isinstance(widget, 'Terminal'): top_window = get_top_window(self) - signals = {'close-term': self.wrapcloseterm, 'split-horiz': self.split_horiz, 'split-vert': self.split_vert, @@ -90,8 +89,14 @@ class Paned(Container): for signal in signals: self.connect_child(widget, signal, signals[signal]) + # FIXME: We shouldn't be doing this exact same thing in each + # Container self.connect_child(widget, 'maximise', top_window.zoom, False) self.connect_child(widget, 'tab-change', top_window.tab_change) + self.connect_child(widget, 'group-all', top_window.group_all) + self.connect_child(widget, 'ungroup-all', top_window.ungroup_all) + self.connect_child(widget, 'group-tab', top_window.group_tab) + self.connect_child(widget, 'ungroup-tab', top_window.ungroup_tab) widget.grab_focus() diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index a4daf428..1a6fcdac 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -58,6 +58,10 @@ class Terminal(gtk.VBox): (gobject.TYPE_STRING,)), 'tab-change': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT,)), + 'group-all': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'ungroup-all': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'group-tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'ungroup-tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), } TARGET_TYPE_VTE = 8 @@ -1241,20 +1245,16 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) self.vte.reset (True, True) def key_group_all(self): - # FIXME: Implement this - self.group_all(self) + self.emit('group-all') def key_ungroup_all(self): - # FIXME: Implement this - self.ungroup_all(self) + self.emit('ungroup-all') def key_group_tab(self): - # FIXME: Implement this - self.group_tab(self) + self.emit('group-tab') def key_ungroup_tab(self): - # FIXME: IMplement this - self.ungroup_tab(self) + self.emit('ungroup-tab') def key_new_window(self): cmd = sys.argv[0] diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 230ab430..3f9486f2 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -405,6 +405,50 @@ class Window(Container, gtk.Window): # change child.set_current_page(child.get_current_page()) + # FIXME: All of these (un)group_(all|tab) methods need refactoring work + def group_all(self, widget): + """Group all terminals""" + # FIXME: Why isn't this being done by Terminator() ? + group = _('All') + self.terminator.create_group(group) + for terminal in self.terminator.terminals: + terminal.set_group(None, group) + + def ungroup_all(self, widget): + """Ungroup all terminals""" + for terminal in self.terminator.terminals: + terminal.set_group(None, None) + + def group_tab(self, widget): + """Group all terminals in the current tab""" + maker = Factory() + notebook = self.get_child() + + if not maker.isinstance(notebook, 'Notebook'): + dbg('not in a notebook, refusing to group tab') + return + + pagenum = notebook.get_current_page() + while True: + group = _('Tab %d') % pagenum + if group not in self.terminator.groups: + break + pagenum += 1 + for terminal in self.get_visible_terminals(): + terminal.set_group(None, group) + + def ungroup_tab(self, widget): + """Ungroup all terminals in the current tab""" + maker = Factory() + notebook = self.get_child() + + if not maker.isinstance(notebook, 'Notebook'): + dbg('note in a notebook, refusing to ungroup tab') + return + + for terminal in self.get_visible_terminals(): + terminal.set_group(None, None) + class WindowTitle(object): """Class to handle the setting of the window title""" From 0eb6994620da07343de220ed545259083cd66650 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 21 Jan 2010 13:00:50 +0000 Subject: [PATCH 330/331] Add the (un)group_(all|tab) connections that should have been in the previous commit --- terminatorlib/notebook.py | 4 ++++ terminatorlib/window.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index c256b4f3..76121a3e 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -120,6 +120,10 @@ class Notebook(Container, gtk.Notebook): for signal in signals: self.connect_child(widget, signal, signals[signal]) self.connect_child(widget, 'tab-change', top_window.tab_change) + self.connect_child(widget, 'group-all', top_window.group_all) + self.connect_child(widget, 'ungroup-all', top_window.ungroup_all) + self.connect_child(widget, 'group-tab', top_window.group_tab) + self.connect_child(widget, 'ungroup-tab', top_window.ungroup_tab) self.set_tab_reorderable(widget, True) label = TabLabel(self.window.get_title(), self) diff --git a/terminatorlib/window.py b/terminatorlib/window.py index 3f9486f2..412ef24f 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -249,6 +249,11 @@ class Window(Container, gtk.Window): self.connect_child(widget, signal, signals[signal]) self.connect_child(widget, 'tab-change', self.tab_change) + self.connect_child(widget, 'group-all', self.group_all) + self.connect_child(widget, 'ungroup-all', self.ungroup_all) + self.connect_child(widget, 'group-tab', self.group_tab) + self.connect_child(widget, 'ungroup-tab', self.ungroup_tab) + widget.grab_focus() def remove(self, widget): From 21caae25175f053c43ea17cd9ef437bf1fa47935 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Thu, 21 Jan 2010 13:02:37 +0000 Subject: [PATCH 331/331] Start implementing a tab moving signal. It isn't handled yet. This is a regression from trunk until it is handled --- terminatorlib/terminal.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 1a6fcdac..3e7c0db6 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -62,6 +62,8 @@ class Terminal(gtk.VBox): 'ungroup-all': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'group-tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'ungroup-tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'move-tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_STRING,)), } TARGET_TYPE_VTE = 8 @@ -1184,11 +1186,13 @@ for %s (%s)' % (name, urlplugin.__class__.__name__)) def key_resize_right(self): self.emit('resize-term', 'right') + # FIXME: Nothing currently handles this signal. Make it so something does. def key_move_tab_right(self): - self.terminator.move_tab (self, 'right') + self.emit('move-tab', 'right') + # FIXME: Nothing currently handles this signal. Make it so something does. def key_move_tab_left(self): - self.terminator.move_tab (self, 'left') + self.emit('move-tab', 'left') def key_toggle_zoom(self): if self.is_zoomed():