diff --git a/ChangeLog b/ChangeLog index 6a11c2e8..02dd90ee 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,9 +1,24 @@ -terminator 0.97: +terminator 1.0: * Allow font dimming in inactive terminals * Allow URL handler plugins to override label text for URL context menus * When copying a URL, run it through the URL handler first so the resulting URL is copied, rather than the original text + * Allow users to configure a custom URL handler, since the + default GTK library option is failing a lot of users in non-GNOME + environments. + * Allow rotation of a group of terminals (Andre Hilsendeger) + * Add a keyboard shortcut to insert a terminal's number (Stephen J + Boddy) + * Add a keyboard shortcut to edit the window title (Stephen J Boddy) + * Add an easy way to balance terminals by double clicking on their + separator (Stephen J Boddy) + * Add a plugin by Sinan Nalkaya to log the contents of terminals. + * Support configuration of TERM and COLORTERM, via a patch from + John Feuerstein + * Support reading configuration from alternate files, via a patch + from Pavel Khlebovich + * Bug fixes terminator 0.96: * Unity support for opening new windows (Lucian Adrian Grijincu) diff --git a/debian/changelog b/debian/changelog index 7bcca422..28776b87 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +terminator (1.0) precise; urgency=low + + * New upstream release of 1.0 + + -- Chris Jones Fri, 19 Oct 2012 12:57:42 -0700 + terminator (0.96ppa6) oneiric; urgency=low * No-change rebuild for oneiric PPA diff --git a/doc/terminator_config.5 b/doc/terminator_config.5 index 677383ca..d29b4892 100644 --- a/doc/terminator_config.5 +++ b/doc/terminator_config.5 @@ -115,6 +115,11 @@ Controls how much to reduce the colour values of fonts in terminals that do not factor. A font colour that was RGB(200,200,200) with an inactive_color_offset of 0.5 would set inactive terminals to RGB(100,100,100). .TP +.B always_split_with_profile +Controls whether splits/tabs will continue to use the profile of their peer terminal. If set to False, they will always use +the default profile. +Default value: \fBFalse\fR +.TP .B enabled_plugins A list of plugins which should be loaded by default. All other plugin classes will be ignored. The default value includes two plugins related to Launchpad, which are enabled by default to provide continuity with earlier releases where these were the @@ -377,6 +382,14 @@ Default value: \fBblock\fR Sets what type of terminal should be emulated. Default value: \fBxterm\fR .TP +.B xterm +This translates into the value that will be set for TERM in the environment of your terminals. +Default value: \fBxterm\fR +.TP +.B colorterm +This translates into the value that will be set for COLORTERM in the environment of your terminals. +Default value: \fBgnome-terminal\fR +.TP .B use_system_font Whether or not to use the GNOME default monospace font for terminals. Default value: \fBTrue\fR @@ -423,7 +436,7 @@ Default value: \fBFalse\fR .TP .B focus_on_close Sets which terminal should get the focus when another terminal is closed. Values can be "prev", "next" or "auto". -Using "auto", if the closed terminal is within a splitted window, the focus will be on the sibling terminal rather than another tab. +Using "auto", if the closed terminal is within a split window, the focus will be on the sibling terminal rather than another tab. Default value: \fBauto\fR .TP .B exit_action diff --git a/po/es.po b/po/es.po index 1e311526..1044ec43 100644 --- a/po/es.po +++ b/po/es.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-08-21 01:31+0100\n" -"PO-Revision-Date: 2012-05-04 22:28+0000\n" -"Last-Translator: Juan Pablo \n" +"PO-Revision-Date: 2012-10-05 03:56+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: 2012-05-24 10:30+0000\n" -"X-Generator: Launchpad (build 15288)\n" +"X-Launchpad-Export-Date: 2012-10-06 04:33+0000\n" +"X-Generator: Launchpad (build 16061)\n" #: ../data/terminator.desktop.in.h:1 msgid "Multiple terminals in one window" @@ -333,7 +333,7 @@ msgstr "Captura de terminal" #: ../terminatorlib/prefseditor.py:942 ../terminatorlib/prefseditor.py:947 msgid "New Profile" -msgstr "Nuevo Perfil" +msgstr "Perfil nuevo" #: ../terminatorlib/prefseditor.py:987 ../terminatorlib/prefseditor.py:992 msgid "New Layout" diff --git a/remotinator b/remotinator index f720851d..d464ee54 100755 --- a/remotinator +++ b/remotinator @@ -24,7 +24,7 @@ import sys from terminatorlib.util import dbg, err try: from terminatorlib import ipc -except ImportErrror: +except ImportError: err('Unable to initialise Terminator remote library. This probably means dbus is not available') sys.exit(1) @@ -35,6 +35,8 @@ COMMANDS={ 'hsplit': ['terminal_hsplit', 'Split the current terminal horizontally'], 'vsplit': ['terminal_vsplit', 'Split the current terminal vertically'], 'terminals': ['get_terminals', 'Get a list of all terminals'], + 'terminal_tab': ['get_terminal_tab', 'Get the UUID of a parent tab'], + 'terminal_tab_title': ['get_terminal_tab_title', 'Get the title of a parent tab'], } if __name__ == '__main__': diff --git a/terminator b/terminator index 1fd95ccd..f0f8a41a 100755 --- a/terminator +++ b/terminator @@ -62,11 +62,23 @@ if __name__ == '__main__': dbg('dbus disabled by command line') raise ImportError from terminatorlib import ipc + import dbus try: dbus_service = ipc.DBusService() except ipc.DBusException: dbg('Unable to become master process, requesting a new window') - ipc.new_window(OPTIONS.layout) + # get rid of the None and True types so dbus can handle them (empty + # and 'True' strings are used instead), also arrays are joined + # (the -x argument for example) + optionslist = {} + for opt, val in OPTIONS.__dict__.items(): + if type(val) == type([]): + val = ' '.join(val) + if val == True: + val = 'True' + optionslist[opt] = val and '%s'%val or '' + optionslist = dbus.Dictionary(optionslist, signature='ss') + ipc.new_window(optionslist) sys.exit() except ImportError: dbg('dbus not imported') diff --git a/terminator.spec b/terminator.spec index 0692fb06..0e127cae 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.96 +Version: 1.0 Release: 1%{?dist} Summary: Store and run multiple GNOME terminals in one window diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 2c05db27..0ff372c4 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -105,6 +105,7 @@ DEFAULTS = { 'LaunchpadCodeURLHandler', 'APTURLHandler'], 'suppress_multiple_term_dialog': False, + 'always_split_with_profile': False, }, 'keybindings': { 'zoom_in' : 'plus', @@ -185,6 +186,8 @@ DEFAULTS = { 'cursor_shape' : 'block', 'cursor_color' : '#aaaaaa', 'emulation' : 'xterm', + 'term' : 'xterm', + 'colorterm' : 'gnome-terminal', 'font' : 'Mono 10', 'foreground_color' : '#aaaaaa', 'show_titlebar' : True, @@ -351,9 +354,10 @@ class Config(object): self.gconf = gconf.client_get_default() value = self.gconf.get('/apps/metacity/general/focus_mode') - self.system_focus = value.get_string() - self.gconf.notify_add('/apps/metacity/general/focus_mode', - self.on_gconf_notify) + if value: + self.system_focus = value.get_string() + self.gconf.notify_add('/apps/metacity/general/focus_mode', + self.on_gconf_notify) return(self.system_focus) def on_gconf_notify(self, _client, _cnxn_id, _entry, _what): @@ -427,6 +431,8 @@ class ConfigBase(Borg): Borg.__init__(self, self.__class__.__name__) self.prepare_attributes() + import optionparse + self.command_line_options = optionparse.options self.load() def prepare_attributes(self): @@ -474,6 +480,9 @@ class ConfigBase(Borg): keytype = '%s(default=%s)' % (keytype, value) + if key == 'custom_url_handler': + keytype = 'string(default="")' + section[key] = keytype configspecdata['global_config'] = section @@ -528,7 +537,10 @@ class ConfigBase(Borg): dbg('ConfigBase::load: config already loaded') return - filename = os.path.join(get_config_dir(), 'config') + if not self.command_line_options.config: + self.command_line_options.config = os.path.join(get_config_dir(), 'config') + filename = self.command_line_options.config + dbg('looking for config file: %s' % filename) try: configfile = open(filename, 'r') @@ -624,7 +636,7 @@ class ConfigBase(Borg): if not os.path.isdir(config_dir): os.makedirs(config_dir) try: - parser.write(open(os.path.join(config_dir, 'config'), 'w')) + parser.write(open(self.command_line_options.config, 'w')) except Exception, ex: err('ConfigBase::save: Unable to save config: %s' % ex) diff --git a/terminatorlib/factory.py b/terminatorlib/factory.py index d868ed1c..2ecaa2f7 100755 --- a/terminatorlib/factory.py +++ b/terminatorlib/factory.py @@ -20,7 +20,7 @@ True """ from borg import Borg -from util import dbg, err +from util import dbg, err, inject_uuid # pylint: disable-msg=R0201 # pylint: disable-msg=W0613 @@ -91,7 +91,9 @@ class Factory(Borg): return(None) dbg('Factory::make: created a %s' % product) - return(func(**kwargs)) + output = func(**kwargs) + inject_uuid(output) + return(output) def make_window(self, **kwargs): """Make a Window""" diff --git a/terminatorlib/ipc.py b/terminatorlib/ipc.py index d413be84..c7a3f0da 100644 --- a/terminatorlib/ipc.py +++ b/terminatorlib/ipc.py @@ -10,6 +10,7 @@ import dbus.glib from borg import Borg from terminator import Terminator from config import Config +from factory import Factory from util import dbg CONFIG = Config() @@ -58,12 +59,16 @@ class DBusService(Borg, dbus.service.Object): if not self.terminator: self.terminator = Terminator() - @dbus.service.method(BUS_NAME) - def new_window(self, layout='default'): + @dbus.service.method(BUS_NAME, in_signature='a{ss}') + def new_window(self, options=dbus.Dictionary()): """Create a new Window""" - dbg('dbus method called: new_window') - self.terminator.create_layout(layout) + dbg('dbus method called: new_window with parameters %s'%(options)) + oldopts = self.terminator.config.options_get() + oldopts.__dict__ = options + self.terminator.config.options_set(oldopts) + self.terminator.create_layout(oldopts.layout) self.terminator.layout_done() + @dbus.service.method(BUS_NAME) def terminal_hsplit(self, uuid=None): @@ -93,6 +98,26 @@ class DBusService(Borg, dbus.service.Object): """Return a list of all the terminals""" return [x.uuid.urn for x in self.terminator.terminals] + @dbus.service.method(BUS_NAME) + def get_terminal_tab(self, uuid): + """Return the UUID of the parent tab of a given terminal""" + maker = Factory() + terminal = self.terminator.find_terminal_by_uuid(uuid) + window = terminal.get_toplevel() + root_widget = window.get_children()[0] + if maker.isinstance(root_widget, 'Notebook'): + return root_widget.uuid.urn + + @dbus.service.method(BUS_NAME) + def get_terminal_tab_title(self, uuid): + """Return the title of a parent tab of a given terminal""" + maker = Factory() + terminal = self.terminator.find_terminal_by_uuid(uuid) + window = terminal.get_toplevel() + root_widget = window.get_children()[0] + if maker.isinstance(root_widget, "Notebook"): + return root_widget.get_tab_label(terminal).get_label() + def with_proxy(func): """Decorator function to connect to the session dbus bus""" dbg('dbus client call: %s' % func.func_name) @@ -103,9 +128,9 @@ def with_proxy(func): return _exec @with_proxy -def new_window(session, layout='default'): +def new_window(session, options): """Call the dbus method to open a new window""" - session.new_window(layout) + session.new_window(options) @with_proxy def terminal_hsplit(session, uuid): @@ -122,3 +147,13 @@ def get_terminals(session, uuid): """Call the dbus method to return a list of all terminals""" print '\n'.join(session.get_terminals(uuid)) +@with_proxy +def get_terminal_tab(session, uuid): + """Call the dbus method to return the toplevel tab for a terminal""" + print session.get_terminal_tab(uuid) + +@with_proxy +def get_terminal_tab_title(session, uuid): + """Call the dbus method to return the title of a tab""" + print session.get_terminal_tab_title(uuid) + diff --git a/terminatorlib/notebook.py b/terminatorlib/notebook.py index bef0caf9..6fd653a4 100755 --- a/terminatorlib/notebook.py +++ b/terminatorlib/notebook.py @@ -136,6 +136,10 @@ class Notebook(Container, gtk.Notebook): sibling = maker.make('terminal') sibling.set_cwd(cwd) sibling.spawn_child() + if widget.group and self.config['split_to_group']: + sibling.set_group(None, widget.group) + if self.config['always_split_with_profile']: + sibling.force_set_profile(None, widget.get_profile()) self.insert_page(container, None, page_num) self.set_tab_reorderable(container, True) @@ -195,7 +199,7 @@ class Notebook(Container, gtk.Notebook): children.append(self.get_nth_page(page)) return(children) - def newtab(self, debugtab=False, widget=None, cwd=None, metadata=None): + def newtab(self, debugtab=False, widget=None, cwd=None, metadata=None, profile=None): """Add a new tab, optionally supplying a child widget""" dbg('making a new tab') maker = Factory() @@ -206,6 +210,8 @@ class Notebook(Container, gtk.Notebook): if cwd: widget.set_cwd(cwd) widget.spawn_child(debugserver=debugtab) + if profile and self.config['always_split_with_profile']: + widget.force_set_profile(None, profile) signals = {'close-term': self.wrapcloseterm, 'split-horiz': self.split_horiz, @@ -472,6 +478,8 @@ class TabLabel(gtk.HBox): self.set_orientation(gtk.ORIENTATION_VERTICAL) self.label.set_angle(90) elif position == gtk.POS_RIGHT: + if hasattr(self, 'set_orientation'): + self.set_orientation(gtk.ORIENTATION_VERTICAL) self.label.set_angle(270) else: if hasattr(self, 'set_orientation'): diff --git a/terminatorlib/optionparse.py b/terminatorlib/optionparse.py index 7d4b3bff..9cdabc23 100755 --- a/terminatorlib/optionparse.py +++ b/terminatorlib/optionparse.py @@ -26,6 +26,8 @@ import config import version from translation import _ +options = None + def execute_cb(option, opt, value, lparser): """Callback for use in parsing execute options""" assert value is None @@ -40,7 +42,6 @@ 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', @@ -53,26 +54,31 @@ def parse_options(): 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('-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('-g', '--config', dest='config', + help=_('Specify a config file')) 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')) + callback=execute_cb, + help=_('Use the rest of the command line as a command to execute' + 'nside 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')) parser.add_option('-c', '--classname', dest='classname', help=_('Set a \ custom name (WM_CLASS) property on the window')) - parser.add_option('-l', '--layout', dest='layout', help=_('Select a layout')) - parser.add_option('-p', '--profile', dest='profile', help=_('Use a \ -different profile as the default')) parser.add_option('-i', '--icon', dest='forcedicon', help=_('Set a custom \ icon for the window (by file or name)')) + parser.add_option('-r', '--role', dest='role', + help=_('Set a custom WM_WINDOW_ROLE property on the window')) + parser.add_option('-l', '--layout', dest='layout', + help=_('Select a layout')) + parser.add_option('-p', '--profile', dest='profile', + help=_('Use a different profile as the default')) parser.add_option('-u', '--no-dbus', action='store_true', dest='nodbus', help=_('Disable DBus')) parser.add_option('-d', '--debug', action='count', dest='debug', @@ -82,10 +88,11 @@ icon for the window (by file or name)')) parser.add_option('--debug-methods', action='store', dest='debug_methods', help=_('Comma separated list of methods to limit debugging to')) for item in ['--sm-client-id', '--sm-config-prefix', '--screen', '-n', - '--no-gconf' ]: + '--no-gconf' ]: parser.add_option(item, dest='dummy', action='store', help=SUPPRESS_HELP) + global options (options, args) = parser.parse_args() if len(args) != 0: parser.error('Additional unexpected arguments found: %s' % args) @@ -122,6 +129,7 @@ icon for the window (by file or name)')) if options.layout is None: options.layout = 'default' + configobj = config.Config() if options.profile and options.profile not in configobj.list_profiles(): options.profile = None diff --git a/terminatorlib/paned.py b/terminatorlib/paned.py index 01b297e6..22259b4f 100755 --- a/terminatorlib/paned.py +++ b/terminatorlib/paned.py @@ -50,6 +50,10 @@ class Paned(Container): sibling = self.maker.make('terminal') sibling.set_cwd(cwd) sibling.spawn_child() + if widget.group and self.config['split_to_group']: + sibling.set_group(None, widget.group) + if self.config['always_split_with_profile']: + sibling.force_set_profile(None, widget.get_profile()) self.add(container) self.show_all() @@ -111,7 +115,10 @@ class Paned(Container): handler = handler[0] self.connect_child(widget, signal, handler, *args) - widget.grab_focus() + if metadata and \ + metadata.has_key('had_focus') and \ + metadata['had_focus'] == True: + widget.grab_focus() elif isinstance(widget, gtk.Paned): try: @@ -220,9 +227,15 @@ class Paned(Container): children.append(self.get_child2()) return(children) + def get_child_metadata(self, widget): + """Return metadata about a child""" + metadata = {} + metadata['had_focus'] = widget.has_focus() + 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] @@ -235,7 +248,6 @@ class Paned(Container): parent.remove(self) self.cnxids.remove_all() parent.add(sibling, metadata) - sibling.grab_focus() del(self) else: dbg("Paned::wrapcloseterm: self.closeterm failed") diff --git a/terminatorlib/plugins/logger.py b/terminatorlib/plugins/logger.py new file mode 100644 index 00000000..a02407fa --- /dev/null +++ b/terminatorlib/plugins/logger.py @@ -0,0 +1,108 @@ +#!/usr/bin/python + +# Plugin by Sinan Nalkaya +# See LICENSE of Terminator package. + +""" logger.py - Terminator Plugin to log 'content' of individual +terminals """ + +import os +import sys +import gtk +import terminatorlib.plugin as plugin +from terminatorlib.translation import _ + +AVAILABLE = ['Logger'] + +class Logger(plugin.MenuItem): + """ Add custom command to the terminal menu""" + capabilities = ['terminal_menu'] + loggers = None + dialog_action = gtk.FILE_CHOOSER_ACTION_SAVE + dialog_buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_SAVE, gtk.RESPONSE_OK) + + def __init__(self): + plugin.MenuItem.__init__(self) + if not self.loggers: + self.loggers = {} + + def callback(self, menuitems, menu, terminal): + """ Add save menu item to the menu""" + vte_terminal = terminal.get_vte() + if not self.loggers.has_key(vte_terminal): + item = gtk.MenuItem(_('Start Logger')) + item.connect("activate", self.start_logger, terminal) + else: + item = gtk.MenuItem(_('Stop Logger')) + item.connect("activate", self.stop_logger, terminal) + item.set_has_tooltip(True) + item.set_tooltip_text("Saving at '" + self.loggers[vte_terminal]["filepath"] + "'") + menuitems.append(item) + + def write_content(self, terminal, row_start, col_start, row_end, col_end): + """ Final function to write a file """ + content = terminal.get_text_range(row_start, col_start, row_end, col_end, + lambda *a: True) + fd = self.loggers[terminal]["fd"] + # Don't write the last char which is always '\n' + fd.write(content[:-1]) + self.loggers[terminal]["col"] = col_end + self.loggers[terminal]["row"] = row_end + + def save(self, terminal): + """ 'contents-changed' callback """ + last_saved_col = self.loggers[terminal]["col"] + last_saved_row = self.loggers[terminal]["row"] + (col, row) = terminal.get_cursor_position() + # Save only when buffer is nearly full, + # for the sake of efficiency + if row - last_saved_row < terminal.get_row_count(): + return + self.write_content(terminal, last_saved_row, last_saved_col, row, col) + + def start_logger(self, _widget, Terminal): + """ Handle menu item callback by saving text to a file""" + savedialog = gtk.FileChooserDialog(title="Save Log File As", + action=self.dialog_action, + buttons=self.dialog_buttons) + savedialog.set_do_overwrite_confirmation(True) + savedialog.set_local_only(True) + savedialog.show_all() + response = savedialog.run() + if response == gtk.RESPONSE_OK: + try: + logfile = os.path.join(savedialog.get_current_folder(), + savedialog.get_filename()) + fd = open(logfile, 'w+') + # Save log file path, + # associated file descriptor, signal handler id + # and last saved col,row positions respectively. + vte_terminal = Terminal.get_vte() + (col, row) = vte_terminal.get_cursor_position() + + self.loggers[vte_terminal] = {"filepath":logfile, + "handler_id":0, "fd":fd, + "col":col, "row":row} + # Add contents-changed callback + self.loggers[vte_terminal]["handler_id"] = vte_terminal.connect('contents-changed', self.save) + except: + e = sys.exc_info()[1] + error = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, + gtk.BUTTONS_OK, e.strerror) + error.run() + error.destroy() + savedialog.destroy() + + def stop_logger(self, _widget, terminal): + vte_terminal = terminal.get_vte() + last_saved_col = self.loggers[vte_terminal]["col"] + last_saved_row = self.loggers[vte_terminal]["row"] + (col, row) = vte_terminal.get_cursor_position() + if last_saved_col != col or last_saved_row != row: + # Save unwritten bufer to the file + self.write_content(vte_terminal, last_saved_row, last_saved_col, row, col) + fd = self.loggers[vte_terminal]["fd"] + fd.close() + vte_terminal.disconnect(self.loggers[vte_terminal]["handler_id"]) + del(self.loggers[vte_terminal]) diff --git a/terminatorlib/preferences.glade b/terminatorlib/preferences.glade index a037824a..7b0bf05d 100644 --- a/terminatorlib/preferences.glade +++ b/terminatorlib/preferences.glade @@ -382,7 +382,7 @@ True False - 15 + 16 2 6 @@ -857,6 +857,36 @@ 15 + + + True + False + Re-use profiles for new terminals + + + 15 + 16 + + + + + True + True + False + False + 0 + True + + + + 1 + 2 + 15 + 16 + GTK_EXPAND + GTK_EXPAND + + True diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 6036c316..863b970d 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -238,6 +238,9 @@ class PrefsEditor: #Hide size text from the title bar widget = guiget('title_hide_sizetextcheck') widget.set_active(self.config['title_hide_sizetext']) + #Always split with profile + widget = guiget('always_split_with_profile') + widget.set_active(self.config['always_split_with_profile']) ## Profile tab # Populate the profile list @@ -610,6 +613,11 @@ class PrefsEditor: self.config['title_hide_sizetext'] = widget.get_active() self.config.save() + def on_always_split_with_profile_toggled(self, widget): + """Always split with profile setting changed""" + self.config['always_split_with_profile'] = widget.get_active() + self.config.save() + def on_allow_bold_checkbutton_toggled(self, widget): """Allow bold setting changed""" self.config['allow_bold'] = widget.get_active() diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 7a9c2ff4..3a0edf78 100755 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -14,7 +14,6 @@ import gobject import pango import subprocess import urllib -import uuid from util import dbg, err, gerr import util @@ -89,7 +88,6 @@ class Terminal(gtk.VBox): command = None clipboard = None pid = None - uuid = None matches = None config = None @@ -107,6 +105,7 @@ class Terminal(gtk.VBox): composite_support = None cnxids = None + targets_for_new_group = None def __init__(self): """Class initialiser""" @@ -119,6 +118,7 @@ class Terminal(gtk.VBox): # FIXME: Surely these should happen in Terminator::register_terminal()? self.connect('enumerate', self.terminator.do_enumerate) self.connect('focus-in', self.terminator.focus_changed) + self.connect('focus-out', self.terminator.focus_left) self.matches = {} self.cnxids = Signalman() @@ -131,9 +131,6 @@ class Terminal(gtk.VBox): self.pending_on_vte_size_allocate = False - self.uuid = uuid.uuid4() - dbg('assigning Terminal a TERMINATOR_UUID of: %s' % self.uuid.urn) - self.vte = vte.Terminal() self.vte._expose_data = None if not hasattr(self.vte, "set_opacity") or \ @@ -167,8 +164,8 @@ class Terminal(gtk.VBox): self.connect_signals() - os.putenv('TERM', 'xterm') - os.putenv('COLORTERM', 'gnome-terminal') + os.putenv('TERM', self.config['term']) + os.putenv('COLORTERM', self.config['colorterm']) env_proxy = os.getenv('http_proxy') if not env_proxy: @@ -770,7 +767,40 @@ class Terminal(gtk.VBox): def on_group_button_press(self, widget, event): """Handler for the group button""" if event.button == 1: - self.create_popup_group_menu(widget, event) + if event.type == gtk.gdk._2BUTTON_PRESS or \ + event.type == gtk.gdk._3BUTTON_PRESS: + # Ignore these, or they make the interaction bad + return False + # Super key applies interaction to all terms in group + include_siblings=event.state & gtk.gdk.MOD4_MASK == gtk.gdk.MOD4_MASK + if include_siblings: + targets=self.terminator.get_sibling_terms(self) + else: + targets=[self] + if event.state & gtk.gdk.CONTROL_MASK == gtk.gdk.CONTROL_MASK: + dbg('on_group_button_press: toggle terminal to focused terminals group') + focused=self.get_toplevel().get_focussed_terminal() + if focused in targets: targets.remove(focused) + if self != focused: + if self.group==focused.group: + new_group=None + else: + new_group=focused.group + [term.set_group(None, new_group) for term in targets] + [term.titlebar.update(focused) for term in targets] + return True + elif event.state & gtk.gdk.SHIFT_MASK == gtk.gdk.SHIFT_MASK: + dbg('on_group_button_press: rename of terminals group') + self.targets_for_new_group = targets + self.titlebar.create_group() + return True + elif event.type == gtk.gdk.BUTTON_PRESS: + # Single Click gives popup + dbg('on_group_button_press: group menu popup') + self.create_popup_group_menu(widget, event) + return True + else: + dbg('on_group_button_press: unknown group button interaction') return(False) def on_keypress(self, widget, event): @@ -947,9 +977,10 @@ class Terminal(gtk.VBox): if gtk.targets_include_text(drag_context.targets) or \ gtk.targets_include_uri(drag_context.targets): # copy text to destination - txt = selection_data.data.strip() + txt = selection_data.data.strip(' ') if txt[0:7] == 'file://': txt = "'%s'" % urllib.unquote(txt[7:]) + txt = selection_data.data.strip('\n') for term in self.terminator.get_target_terms(self): term.feed(txt) return @@ -1247,6 +1278,8 @@ class Terminal(gtk.VBox): pass envv = [] + envv.append('TERM=%s' % self.config['term']) + envv.append('COLORTERM=%s' % self.config['colorterm']) envv.append('TERMINATOR_UUID=%s' % self.uuid.urn) if self.terminator.dbus_name: envv.append('TERMINATOR_DBUS_NAME=%s' % self.terminator.dbus_name) diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index 54226838..5bea899a 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -349,19 +349,21 @@ class Terminator(Borg): idx = terminals.index(term) term.feed(numstr % (idx + 1)) + def get_sibling_terms(self, widget): + termset = [] + for term in self.terminals: + if term.group == widget.group: + termset.append(term) + return(termset) + 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]) + if widget.group != None: + return(self.get_sibling_terms(widget)) + return([widget]) def get_focussed_terminal(self): """iterate over all the terminals to find which, if any, has focus""" @@ -376,6 +378,9 @@ class Terminator(Borg): terminal.titlebar.update(widget) return + def focus_left(self, widget): + self.last_focused_term=widget + def describe_layout(self): """Describe our current layout""" layout = {} diff --git a/terminatorlib/titlebar.py b/terminatorlib/titlebar.py index c8bf192f..e743087a 100755 --- a/terminatorlib/titlebar.py +++ b/terminatorlib/titlebar.py @@ -94,7 +94,7 @@ class Titlebar(gtk.EventBox): def connect_icon(self, func): """Connect the supplied function to clicking on the group icon""" - self.ebox.connect('button-release-event', func) + self.ebox.connect('button-press-event', func) def update(self, other=None): """Update our contents""" @@ -247,6 +247,7 @@ class Titlebar(gtk.EventBox): if self.groupentry.get_text()=='' and freegroups: self.groupentry.set_text(freegroups.pop()) self.groupentry.show() + self.grouplabel.hide() self.groupentry.grab_focus() self.update_visibility() @@ -254,6 +255,7 @@ class Titlebar(gtk.EventBox): """Hide the group name entry""" self.groupentry.set_text('') self.groupentry.hide() + self.grouplabel.show() self.get_parent().grab_focus() def groupentry_activate(self, widget): @@ -261,7 +263,14 @@ class Titlebar(gtk.EventBox): groupname = self.groupentry.get_text() dbg('Titlebar::groupentry_activate: creating group: %s' % groupname) self.groupentry_cancel(None, None) - self.emit('create-group', groupname) + last_focused_term=self.terminator.last_focused_term + if self.terminal.targets_for_new_group: + [term.titlebar.emit('create-group', groupname) for term in self.terminal.targets_for_new_group] + self.terminal.targets_for_new_group = None + else: + self.emit('create-group', groupname) + last_focused_term.grab_focus() + self.terminator.focus_changed(last_focused_term) def groupentry_keypress(self, widget, event): """Handle keypresses on the entry widget""" diff --git a/terminatorlib/util.py b/terminatorlib/util.py index bd5c2fa8..1592cc7f 100755 --- a/terminatorlib/util.py +++ b/terminatorlib/util.py @@ -28,6 +28,7 @@ import gtk import os import pwd import inspect +import uuid # set this to true to enable debugging output DEBUG = False @@ -276,3 +277,16 @@ def enumerate_descendants(parent): len(terminals), parent)) return(containers, terminals) +def make_uuid(): + """Generate a UUID for an object""" + return uuid.uuid4() + +def inject_uuid(target): + """Inject a UUID into an existing object""" + uuid = make_uuid() + if not hasattr(target, "uuid") or target.uuid == None: + dbg("Injecting UUID %s into: %s" % (uuid, target)) + target.uuid = uuid + else: + dbg("Object already has a UUID: %s" % target) + diff --git a/terminatorlib/version.py b/terminatorlib/version.py index 60e01b93..eccb891d 100644 --- a/terminatorlib/version.py +++ b/terminatorlib/version.py @@ -21,4 +21,4 @@ TerminatorVersion supplies our version number. """ APP_NAME = 'terminator' -APP_VERSION = '0.96' +APP_VERSION = '1.0' diff --git a/terminatorlib/window.py b/terminatorlib/window.py index bd2f52d6..7031400a 100755 --- a/terminatorlib/window.py +++ b/terminatorlib/window.py @@ -71,10 +71,10 @@ class Window(Container, gtk.Window): options = self.config.options_get() if options: - if options.forcedtitle is not None: + if options.forcedtitle: self.title.force_title(options.forcedtitle) - if options.role is not None: + if options.role: self.set_role(options.role) if options.classname is not None: @@ -83,7 +83,7 @@ class Window(Container, gtk.Window): if options.forcedicon is not None: icon_to_apply = options.forcedicon - if options.geometry is not None: + if options.geometry: if not self.parse_geometry(options.geometry): err('Window::__init__: Unable to parse geometry: %s' % options.geometry) @@ -248,6 +248,7 @@ class Window(Container, gtk.Window): def tab_new(self, widget=None, debugtab=False, _param1=None, _param2=None): """Make a new tab""" cwd = None + profile = None if self.get_property('term_zoomed') == True: err("You can't create a tab while a terminal is maximised/zoomed") @@ -255,11 +256,13 @@ class Window(Container, gtk.Window): if widget: cwd = widget.get_cwd() + profile = widget.get_profile() + maker = Factory() if not self.is_child_notebook(): dbg('Making a new Notebook') notebook = maker.make('Notebook', window=self) - self.get_child().newtab(debugtab, cwd=cwd) + self.get_child().newtab(debugtab, cwd=cwd, profile=profile) def on_delete_event(self, window, event, data=None): """Handle a window close request""" @@ -373,8 +376,10 @@ class Window(Container, gtk.Window): def show(self, startup=False): """Undo the startup show request if started in hidden mode""" - gtk.Window.show(self) - #Present is necessary to grab focus when window is hidden from taskbar + #Present is necessary to grab focus when window is hidden from taskbar. + #It is important to call present() before show(), otherwise the window + #won't be brought to front if an another application has the focus. + #Last note: present() will implicitly call gtk.Window.show() self.present() #Window must be shown, then hidden for the hotkeys to be registered @@ -454,6 +459,11 @@ class Window(Container, gtk.Window): sibling = maker.make('Terminal') sibling.set_cwd(cwd) sibling.spawn_child() + if widget.group and self.config['split_to_group']: + sibling.set_group(None, widget.group) + if self.config['always_split_with_profile']: + sibling.force_set_profile(None, widget.get_profile()) + self.add(container) container.show_all()