#!/usr/bin/python
# Terminator by Chris Jones <cmsj@tenshu.net>
# GPL v2 only
"""terminal.py - classes necessary to provide Terminal widgets"""
from __future__ import division
import sys
import os
import signal
import pygtk
pygtk.require('2.0')
import gtk
import gobject
import pango
import subprocess
import urllib
from util import dbg, err, gerr, spawn_new_terminator, make_uuid,  manual_lookup
import util
from config import Config
from cwd import get_default_cwd
from factory import Factory
from terminator import Terminator
from titlebar import Titlebar
from terminal_popup_menu import TerminalPopupMenu
from searchbar import Searchbar
from translation import _
from signalman import Signalman
import plugin
from terminatorlib.layoutlauncher import LayoutLauncher
try:
    import vte
except ImportError:
    gerr('You need to install python bindings for libvte')
    sys.exit(1)
# pylint: disable-msg=R0904
[docs]class Terminal(gtk.VBox):
    """Class implementing the VTE widget and its wrappings"""
    __gsignals__ = {
        '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,)),
        'group-tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
        'group-tab-toggle': (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,
            (gobject.TYPE_STRING,)),
        'split-vert': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            (gobject.TYPE_STRING,)),
        'rotate-cw': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
        'rotate-ccw': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
        'tab-new': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            (gobject.TYPE_BOOLEAN, gobject.TYPE_OBJECT)),
        'tab-top-new': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
        'focus-in': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
        'focus-out': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
        '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,
            (gobject.TYPE_STRING,)),
        'tab-change': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            (gobject.TYPE_INT,)),
        'group-all': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
        'group-all-toggle': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
        'move-tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, 
            (gobject.TYPE_STRING,)),
    }
    TARGET_TYPE_VTE = 8
    terminator = None
    vte = None
    terminalbox = None
    scrollbar = None
    scrollbar_position = None
    titlebar = None
    searchbar = None
    group = None
    cwd = None
    origcwd = None
    command = None
    clipboard = None
    pid = None
    matches = None
    config = None
    default_encoding = None
    custom_encoding = None
    custom_font_size = None
    layout_command = None
    directory = None
    fgcolor_active = None
    fgcolor_inactive = None
    bgcolor = None
    palette_active = None
    palette_inactive = None
    composite_support = None
    cnxids = None
    targets_for_new_group = None
[docs]    def __init__(self):
        """Class initialiser"""
        gtk.VBox.__init__(self)
        self.__gobject_init__()
        self.terminator = Terminator()
        self.terminator.register_terminal(self)
        # 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()
        self.config = Config()
        self.cwd = get_default_cwd()
        self.origcwd = self.terminator.origcwd
        self.clipboard = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD)
        self.pending_on_vte_size_allocate = False
        self.vte = vte.Terminal()
        self.vte._expose_data = None
        if not hasattr(self.vte, "set_opacity") or \
           
not hasattr(self.vte, "is_composited"):
            self.composite_support = False
        else:
            self.composite_support = True
        dbg('composite_support: %s' % self.composite_support)
        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)
        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.titlebar.show_all()
        self.searchbar = Searchbar()
        self.searchbar.connect('end-search', self.on_search_done)
        self.show()
        self.pack_start(self.titlebar, False)
        self.pack_start(self.terminalbox)
        self.pack_end(self.searchbar)
        self.connect_signals()
        os.putenv('TERM', self.config['term'])
        os.putenv('COLORTERM', self.config['colorterm'])
        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'])
        self.reconfigure()
        self.vte.set_size(80, 24)
 
[docs]    def get_vte(self):
        """This simply returns the vte widget we are using"""
        return(self.vte)
 
[docs]    def force_set_profile(self, widget, profile):
        """Forcibly set our profile"""
        self.set_profile(widget, profile, True)
 
[docs]    def set_profile(self, _widget, profile, force=False):
        """Set our profile"""
        if profile != self.config.get_profile():
            self.config.set_profile(profile, force)
            self.reconfigure()
 
[docs]    def get_profile(self):
        """Return our profile name"""
        return(self.config.profile)
 
[docs]    def switch_to_next_profile(self):
        profilelist = self.config.list_profiles()
        list_length = len(profilelist)
        if list_length > 1:
            if profilelist.index(self.get_profile()) + 1 == list_length:
                self.force_set_profile(False, profilelist[0])
            else:
                self.force_set_profile(False, profilelist[profilelist.index(self.get_profile()) + 1])
 
[docs]    def switch_to_previous_profile(self):
        profilelist = self.config.list_profiles()
        list_length = len(profilelist)
        if list_length > 1:
            if profilelist.index(self.get_profile()) == 0:
                self.force_set_profile(False, profilelist[list_length - 1])
            else:
                self.force_set_profile(False, profilelist[profilelist.index(self.get_profile()) - 1])
 
[docs]    def get_cwd(self):
        """Return our cwd"""
        return(self.terminator.pid_cwd(self.pid))
 
[docs]    def close(self):
        """Close ourselves"""
        dbg('close: called')
        self.cnxids.remove_signal(self.vte, 'child-exited')
        self.emit('close-term')
        try:
            dbg('close: killing %d' % self.pid)
            os.kill(self.pid, signal.SIGHUP)
        except Exception, ex:
            # We really don't want to care if this failed. Deep OS voodoo is
            # not what we should be doing.
            dbg('os.kill failed: %s' % ex)
            pass
 
[docs]    def create_terminalbox(self):
        """Create a GtkHBox containing the terminal and a scrollbar"""
        terminalbox = gtk.HBox()
        self.scrollbar = gtk.VScrollbar(self.vte.get_adjustment())
        self.scrollbar.set_no_show_all(True)
        self.scrollbar_position = self.config['scrollbar_position']
        if self.scrollbar_position not in ('hidden', 'disabled'):
            self.scrollbar.show()
        if self.scrollbar_position == 'left':
            func = terminalbox.pack_end
        else:
            func = terminalbox.pack_start
        func(self.vte)
        func(self.scrollbar, False)
        terminalbox.show_all()
        return(terminalbox)
 
[docs]    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 ('Terminal::update_url_matches: Trying POSIX URL regexps')
            lboundry = "[[:<:]]"
            rboundry = "[[:>:]]"
        else: # GNU
            dbg ('Terminal::update_url_matches: Trying GNU URL regexps')
            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 ('Terminal::update_url_matches: POSIX failed, trying GNU')
                self.update_url_matches(posix = False)
            else:
                err ('Terminal::update_url_matches: Failed adding URL matches')
        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)
            # 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
                    if name in self.matches:
                        dbg('refusing to add duplicate match %s' % name)
                        continue
                    self.matches[name] = self.vte.match_add(match)
                    dbg('added plugin URL handler for %s (%s) as %d' % 
                        (name, urlplugin.__class__.__name__,
                        self.matches[name]))
            except Exception, ex:
                err('Exception occurred adding plugin URL match: %s' % ex)
 
[docs]    def match_add(self, name, match):
        """Register a URL match"""
        if name in self.matches:
            err('Terminal::match_add: Refusing to create duplicate match %s' % name)
            return
        self.matches[name] = self.vte.match_add(match)
 
[docs]    def match_remove(self, name):
        """Remove a previously registered URL match"""
        if name not in self.matches:
            err('Terminal::match_remove: Unable to remove non-existent match %s' % name)
            return
        self.vte.match_remove(self.matches[name])
        del(self.matches[name])
 
[docs]    def connect_signals(self):
        """Connect all the gtk signals and drag-n-drop mechanics"""
        self.scrollbar.connect('button-press-event', self.on_buttonpress)
        self.vte.connect('key-press-event', self.on_keypress)
        self.vte.connect('button-press-event', self.on_buttonpress)
        self.vte.connect('scroll-event', self.on_mousewheel)
        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/x-moz-url', 0, 0), 
                      ('_NETSCAPE_URL', 0, 0)]
        dsttargets = gtk.target_list_add_text_targets(dsttargets)
        dsttargets = gtk.target_list_add_uri_targets(dsttargets)
        dbg('Finalised drag targets: %s' % dsttargets)
        for (widget, mask) in [
            (self.vte, gtk.gdk.CONTROL_MASK | gtk.gdk.BUTTON3_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 |
                gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP,
                dsttargets, gtk.gdk.ACTION_COPY | 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)
        # FIXME: Shouldn't this be in configure()?
        if self.config['copy_on_selection']:
            self.cnxids.new(self.vte, 'selection-changed', 
                    lambda widget: self.vte.copy_clipboard())
        if self.composite_support:
            self.vte.connect('composited-changed', self.reconfigure)
        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-in-event', self.on_vte_focus_in)
        self.vte.connect('focus-out-event', self.on_vte_focus_out)
        self.vte.connect('size-allocate', self.deferred_on_vte_size_allocate)
        self.vte.add_events(gtk.gdk.ENTER_NOTIFY_MASK)
        self.vte.connect('enter_notify_event',
            self.on_vte_notify_enter)
        self.cnxids.new(self.vte, 'realize', self.reconfigure)
 
[docs]    def set_group(self, _item, name):
        """Set a particular group"""
        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()
 
[docs]    def create_group(self, _item):
        """Trigger the creation of a group via the titlebar (because popup 
        windows are really lame)"""
        self.titlebar.create_group()
 
[docs]    def really_create_group(self, _widget, groupname):
        """The titlebar has spoken, let a group be created"""
        self.terminator.create_group(groupname)
        self.set_group(None, groupname)
 
[docs]    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()
 
[docs]    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.values():
            dbg('Terminal::set_groupsend: setting groupsend to %s' % value)
            self.terminator.groupsend = value
 
[docs]    def do_splittogroup_toggle(self):
        """Toggle the splittogroup mode"""
        self.config['split_to_group'] = not self.config['split_to_group']
 
[docs]    def do_autocleangroups_toggle(self):
        """Toggle the autocleangroups mode"""
        self.config['autoclean_groups'] = not self.config['autoclean_groups']
 
[docs]    def set_cursor_color(self):
        """Set the cursor color appropriately"""
        if self.config['cursor_color'] == self.config['foreground_color']:
            try:
                self.vte.set_color_cursor(None) 
            except TypeError:
                # FIXME: I think this is only necessary because of
                # https://bugzilla.gnome.org/show_bug.cgi?id=614910
                pass
        elif self.config['cursor_color'] != '':
            self.vte.set_color_cursor(gtk.gdk.color_parse(
                                        self.config['cursor_color']))
  
[docs]    def get_window_title(self):
        """Return the window title"""
        return(self.vte.get_window_title() or str(self.command))
 
[docs]    def on_keypress(self, widget, event):
        """Handler for keyboard events"""
        if not event:
            dbg('Terminal::on_keypress: Called on %s with no event' % widget)
            return(False)
        # Workaround for IBus intefering with broadcast when using dead keys
        # Environment also needs IBUS_DISABLE_SNOOPER=1, or double chars appear
        # in the receivers.
        if (event.state | gtk.gdk.MODIFIER_MASK ) ^ gtk.gdk.MODIFIER_MASK != 0:
            dbg('Terminal::on_keypress: Ingore processed event with event.state %d' % event.state)
            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', 
                                       'new_tab']:
            dbg('Terminal::on_keypress: lookup found: %r' % mapping)
            # handle the case where user has re-bound copy to ctrl+<key>
            # we only copy if there is a selection otherwise let it fall through
            # to ^<key>
            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
        #         maybe we can emit the key event and let Terminator() care?
        groupsend = self.terminator.groupsend
        groupsend_type = self.terminator.groupsend_type
        window_focussed = self.vte.get_toplevel().get_property('has-toplevel-focus')
        if groupsend != groupsend_type['off'] and window_focussed 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 groupsend == groupsend_type['all']:
                self.terminator.all_emit(self, 'key-press-event', event)
        return(False)
 
[docs]    def on_mousewheel(self, widget, event):
        """Handler for modifier + mouse wheel scroll events"""
        if event.state & gtk.gdk.CONTROL_MASK == gtk.gdk.CONTROL_MASK:
            # Ctrl + mouse wheel up/down with Shift and Super additions
            if event.state & gtk.gdk.MOD4_MASK == gtk.gdk.MOD4_MASK:
                targets=self.terminator.terminals
            elif event.state & gtk.gdk.SHIFT_MASK == gtk.gdk.SHIFT_MASK:
                targets=self.terminator.get_target_terms(self)
            else:
                targets=[self]
            if event.direction == gtk.gdk.SCROLL_UP:
                for target in targets:
                    target.zoom_in()
                return (True)
            elif event.direction == gtk.gdk.SCROLL_DOWN:
                for target in targets:
                    target.zoom_out()
                return (True)
        if event.state & gtk.gdk.SHIFT_MASK == gtk.gdk.SHIFT_MASK:
            # Shift + mouse wheel up/down
            if event.direction == gtk.gdk.SCROLL_UP:
                self.scroll_by_page(-1)
                return (True)
            elif event.direction == gtk.gdk.SCROLL_DOWN:
                self.scroll_by_page(1)
                return (True)
        return(False)
 
[docs]    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)
 
[docs]    def on_drag_begin(self, widget, drag_context, _data):
        """Handle the start of a drag event"""
        widget.drag_source_set_icon_pixbuf(util.widget_pixbuf(self, 512))
 
[docs]    def on_drag_data_get(self, _widget, _drag_context, selection_data, info, 
            _time, data):
        """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)))
 
[docs]    def on_drag_motion(self, widget, drag_context, x, y, _time, _data):
        """*shrug*"""
        if not drag_context.targets == ['vte'] and \
           
(gtk.targets_include_text(drag_context.targets) or \
           
gtk.targets_include_uri(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)
        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
 
[docs]    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)
 
[docs]    def on_drag_data_received(self, widget, drag_context, x, y, selection_data,
            _info, _time, data):
        """Something has been dragged into the terminal. Handle it as either a
        URL or another terminal."""
        dbg('drag data received of type: %s' % selection_data.type)
        if gtk.targets_include_text(drag_context.targets) or \
           
gtk.targets_include_uri(drag_context.targets):
            # copy text with no modification yet to destination
            txt = selection_data.data
            txt_lines = txt.split( "\r\n" )
            if txt_lines[-1] == '':
                for line in txt_lines[:-1]:
                    if line[0:7] != 'file://':
                        break
                else:
                    # It is a list of crlf terminated file:// URL. let's
                    # iterate over all elements except the last one.
                    str=''
                    for fname in txt_lines[:-1]:
                        dbg('drag data fname: %s' % fname)
                        fname = "'%s'" % urllib.unquote(fname[7:].replace("'",
                                                                    '\'\\\'\''))
                        str += fname + ' '
                    txt=str
            for term in self.terminator.get_target_terms(self):
                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
        # The widget argument is actually a vte.Terminal(). Turn that into a
        # terminatorlib Terminal()
        maker = Factory()
        while True:
            widget = widget.get_parent()
            if not widget:
                # We've run out of widgets. Something is wrong.
                err('Failed to find Terminal from vte')
                return
            if maker.isinstance(widget, 'Terminal'):
                break
        dsthbox = widget
        dstpaned = dsthbox.get_parent()
        srcpaned = srchbox.get_parent()
        pos = self.get_location(widget, x, y)
        srcpaned.remove(widgetsrc)
        dstpaned.split_axis(dsthbox, pos in ['top', 'bottom'], None, widgetsrc, pos in ['bottom', 'right'])
        srcpaned.hoover()
        widgetsrc.ensure_visible_and_focussed()
 
[docs]    def get_location(self, term, x, y):
        """Get our location within the terminal"""
        pos = ''
        #get the diagonales function for the receiving widget
        coef1 = float(term.allocation.height)/float(term.allocation.width)
        coef2 = -float(term.allocation.height)/float(term.allocation.width)
        b1 = 0
        b2 = term.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
 
[docs]    def grab_focus(self):
        """Steal focus for this terminal"""
        if not self.vte.flags()>k.HAS_FOCUS:
            self.vte.grab_focus()
 
[docs]    def ensure_visible_and_focussed(self):
        """Make sure that we're visible and focussed"""
        window = self.get_toplevel()
        topchild = window.get_child()
        maker = Factory()
        if maker.isinstance(topchild, 'Notebook'):
            # Find which page number this term is on
            tabnum = topchild.page_num_descendant(self)
            # If terms page number is not the current one, switch to it
            current_page = topchild.get_current_page()
            if tabnum != current_page:
                topchild.set_current_page(tabnum)
        self.grab_focus()
 
[docs]    def on_vte_focus(self, _widget):
        """Update our UI when we get focus"""
        self.emit('title-change', self.get_window_title())
 
[docs]    def on_vte_focus_in(self, _widget, _event):
        """Inform other parts of the application when focus is received"""
        self.vte.set_colors(self.fgcolor_active, self.bgcolor,
                            self.palette_active)
        self.set_cursor_color()
        if not self.terminator.doing_layout:
            self.terminator.last_focused_term = self
            if self.get_toplevel().is_child_notebook():
                notebook = self.get_toplevel().get_children()[0]
                notebook.set_last_active_term(self.uuid)
                notebook.clean_last_active_term()
                self.get_toplevel().last_active_term = None
            else:
                self.get_toplevel().last_active_term = self.uuid
        self.emit('focus-in')
 
[docs]    def on_vte_focus_out(self, _widget, _event):
        """Inform other parts of the application when focus is lost"""
        self.vte.set_colors(self.fgcolor_inactive, self.bgcolor,
                            self.palette_inactive)
        self.set_cursor_color()
        self.emit('focus-out')
 
[docs]    def on_window_focus_out(self):
        """Update our UI when the window loses focus"""
        self.titlebar.update('window-focus-out')
 
[docs]    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()
 
[docs]    def on_edit_done(self, _widget):
        """A child widget is done editing a label, return focus to VTE"""
        self.vte.grab_focus()
 
[docs]    def deferred_on_vte_size_allocate(self, widget, allocation):
        # widget & allocation are not used in on_vte_size_allocate, so we
        # can use the on_vte_size_allocate instead of duplicating the code
        if self.pending_on_vte_size_allocate == True:
            return
        self.pending_on_vte_size_allocate = True
        gobject.idle_add(self.do_deferred_on_vte_size_allocate, widget, allocation)
 
[docs]    def do_deferred_on_vte_size_allocate(self, widget, allocation):
        self.pending_on_vte_size_allocate = False
        self.on_vte_size_allocate(widget, allocation)
 
[docs]    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 = self.get_toplevel()
            window.deferred_set_rough_geometry_hints()
 
[docs]    def on_vte_notify_enter(self, term, event):
        """Handle the mouse entering this terminal"""
        # FIXME: This shouldn't be looking up all these values every time
        sloppy = False
        if self.config['focus'] == 'system':
            sloppy = self.config.get_system_focus() in ['sloppy', 'mouse']
        elif self.config['focus'] in ['sloppy', 'mouse']:
            sloppy = True
        if sloppy == True and self.titlebar.editing() == False:
            term.grab_focus()
            return(False)
 
[docs]    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_columns'] = self.vte.get_column_count()
        data['old_rows'] = self.vte.get_row_count()
        data['old_parent'] = self.get_parent()
        return(data)
 
[docs]    def zoom_scale(self, widget, allocation, old_data):
        """Scale our font correctly based on how big we are not vs before"""
        self.cnxids.remove_signal(self, 'size-allocate')
        # FIXME: Is a zoom signal actualy used anywhere?
        self.cnxids.remove_signal(self, 'zoom')
        new_columns = self.vte.get_column_count()
        new_rows = self.vte.get_row_count()
        new_font = self.vte.get_font()
        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
        scale_factor = min ( (new_columns / old_data['old_columns'] * 0.97),
                             (new_rows / old_data['old_rows'] * 1.05) )
        new_size = int(old_data['old_font'].get_size() * scale_factor)
        if new_size == 0:
            err('refusing to set a zero sized font')
            return
        new_font.set_size(new_size)
        dbg('setting new font: %s' % new_font)
        self.set_font(new_font)
 
[docs]    def is_zoomed(self):
        """Determine if we are a zoomed terminal"""
        prop = None
        window = self.get_toplevel()
        try:
            prop = window.get_property('term-zoomed')
        except TypeError:
            prop = False
        return(prop)
 
[docs]    def zoom(self, widget=None):
        """Zoom ourself to fill the window"""
        self.emit('zoom')
 
[docs]    def maximise(self, widget=None):
        """Maximise ourself to fill the window"""
        self.emit('maximise')
 
[docs]    def unzoom(self, widget=None):
        """Restore normal layout"""
        self.emit('unzoom')
 
[docs]    def set_cwd(self, cwd=None):
        """Set our cwd"""
        if cwd is not None:
            self.cwd = cwd
 
[docs]    def spawn_child(self, widget=None, respawn=False, debugserver=False):
        update_records = self.config['update_records']
        login = self.config['login_shell']
        args = []
        shell = None
        command = None
        if self.terminator.doing_layout == True:
            dbg('still laying out, refusing to spawn a child')
            return
        if respawn == False:
            self.vte.grab_focus()
        options = self.config.options_get()
        if options and options.command:
            command = options.command
            options.command = None
        elif options and options.execute:
            command = options.execute
            options.execute = None
        elif self.config['use_custom_command']:
            command = self.config['custom_command']
        elif self.layout_command:
            command = self.layout_command
        elif debugserver is True:
            details = self.terminator.debug_address
            dbg('spawning debug session with: %s:%s' % (details[0],
                details[1]))
            command = 'telnet %s %s' % (details[0], details[1])
        # working directory set in layout config
        if self.directory:
            self.set_cwd(self.directory)
        # working directory given as argument
        elif options and options.working_directory and \
           
options.working_directory != '':
            self.set_cwd(options.working_directory)
            options.working_directory = ''
        if type(command) is list:
            shell = util.path_lookup(command[0])
            args = command
        else:
            shell = util.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)
        try:
            os.putenv('WINDOWID', '%s' % self.vte.get_parent_window().xid)
        except AttributeError:
            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)
        if self.terminator.dbus_path:
            envv.append('TERMINATOR_DBUS_PATH=%s' % self.terminator.dbus_path)
        dbg('Forking shell: "%s" with args: %s' % (shell, args))
        self.pid = self.vte.fork_command(command=shell, argv=args, envv=envv,
                                         loglastlog=login, 
                                         logwtmp=update_records,
                                         logutmp=update_records, 
                                         directory=self.cwd)
        self.command = shell
        self.titlebar.update()
        if self.pid == -1:
            self.vte.feed(_('Unable to start shell:') + shell)
            return(-1)
 
[docs]    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())))
 
[docs]    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 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:
                            dbg('Terminal::prepare_url: URL prepared by \
%s plugin' % urlplugin.handler_name)
                            url = newurl
                        break
            except Exception, ex:
                err('Exception occurred preparing URL: %s' % ex)
        return(url)
 
[docs]    def open_url(self, url, prepare=False):
        """Open a given URL, conditionally unpacking it from a VTE match"""
        oldstyle = False
        if prepare == True:
            url = self.prepare_url(url)
        dbg('open_url: URL: %s (prepared: %s)' % (url, prepare))
        if self.config['use_custom_url_handler']:
            dbg("Using custom URL handler: %s" %
                self.config['custom_url_handler'])
            try:
                subprocess.Popen([self.config['custom_url_handler'], url])
                return
            except:
                dbg('custom url handler did not work, falling back to defaults')
        if gtk.gtk_version < (2, 14, 0) or \
           
not hasattr(gtk, 'show_uri') or \
           
not hasattr(gtk.gdk, 'CURRENT_TIME'):
            oldstyle = True
        if oldstyle == False:
            try:
                gtk.show_uri(None, url, gtk.gdk.CURRENT_TIME)
            except:
                oldstyle = True
        if oldstyle == True:
            dbg('Old gtk (%s,%s,%s), calling xdg-open' % gtk.gtk_version)
            try:
                subprocess.Popen(["xdg-open", url])
            except:
                dbg('xdg-open did not work, falling back to webbrowser.open')
                import webbrowser
                webbrowser.open(url)
 
[docs]    def paste_clipboard(self, primary=False):
        """Paste one of the two clipboards"""
        for term in self.terminator.get_target_terms(self):
            if primary:
                term.vte.paste_primary()
            else:
                term.vte.paste_clipboard()
        self.vte.grab_focus()
 
[docs]    def feed(self, text):
        """Feed the supplied text to VTE"""
        self.vte.feed_child(text)
 
[docs]    def zoom_in(self):
        """Increase the font size"""
        self.zoom_font(True)
 
[docs]    def zoom_out(self):
        """Decrease the font size"""
        self.zoom_font(False)
 
[docs]    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.set_font(pangodesc)
        self.custom_font_size = fontsize
 
[docs]    def zoom_orig(self):
        """Restore original font size"""
        if self.config['use_system_font'] == True:
            font = self.config.get_system_mono_font()
        else:
            font = self.config['font']
        dbg("Terminal::zoom_orig: restoring font to: %s" % font)
        self.set_font(pango.FontDescription(font))
        self.custom_font_size = None
 
[docs]    def set_font(self, fontdesc):
        """Set the font we want in VTE"""
        antialias = self.config['antialias']
        if antialias:
            try:
                antialias = vte.ANTI_ALIAS_FORCE_ENABLE
            except AttributeError:
                antialias = 1
        else:
            try:
                antialias = vte.ANTI_ALIAS_FORCE_DISABLE
            except AttributeError:
                antialias = 2
        self.vte.set_font_full(fontdesc, antialias)
 
[docs]    def get_cursor_position(self):
        """Return the co-ordinates of our cursor"""
        # FIXME: THIS METHOD IS DEPRECATED AND UNUSED
        col, row = self.vte.get_cursor_position()
        width = self.vte.get_char_width()
        height = self.vte.get_char_height()
        return((col * width, row * height))
 
[docs]    def get_font_size(self):
        """Return the width/height of our font"""
        return((self.vte.get_char_width(), self.vte.get_char_height()))
 
[docs]    def get_size(self):
        """Return the column/rows of the terminal"""
        return((self.vte.get_column_count(), self.vte.get_row_count()))
 
[docs]    def on_beep(self, widget):
        """Set the urgency hint for our window"""
        if self.config['urgent_bell'] == True:
            window = self.get_toplevel()
            if window.flags() & gtk.TOPLEVEL:
                window.set_urgency_hint(True)
        if self.config['icon_bell'] == True:
            self.titlebar.icon_bell()
 
[docs]    def describe_layout(self, count, parent, global_layout, child_order):
        """Describe our layout"""
        layout = {}
        layout['type'] = 'Terminal'
        layout['parent'] = parent
        layout['order'] = child_order
        if self.group:
            layout['group'] = self.group
        profile = self.get_profile()
        if layout != "default":
            # There's no point explicitly noting default profiles
            layout['profile'] = profile
        title = self.titlebar.get_custom_string()
        if title:
            layout['title'] = title
        layout['uuid'] = self.uuid
        name = 'terminal%d' % count
        count = count + 1
        global_layout[name] = layout
        return(count)
 
[docs]    def create_layout(self, layout):
        """Apply our layout"""
        dbg('Setting layout')
        if layout.has_key('command') and layout['command'] != '':
            self.layout_command = layout['command']
        if layout.has_key('profile') and layout['profile'] != '':
            if layout['profile'] in self.config.list_profiles():
                self.set_profile(self, layout['profile'])
        if layout.has_key('group') and layout['group'] != '':
            # This doesn't need/use self.titlebar, but it's safer than sending
            # None
            self.really_create_group(self.titlebar, layout['group'])
        if layout.has_key('title') and layout['title'] != '':
            self.titlebar.set_custom_string(layout['title'])
        if layout.has_key('directory') and layout['directory'] != '':
            self.directory = layout['directory']
        if layout.has_key('uuid') and layout['uuid'] != '':
            self.uuid = make_uuid(layout['uuid'])
 
[docs]    def scroll_by_page(self, pages):
        """Scroll up or down in pages"""
        amount = pages * self.vte.get_adjustment().get_page_increment()
        self.scroll_by(int(amount))
 
[docs]    def key_zoom_in(self):
        self.zoom_in()
 
[docs]    def key_next_profile(self):
        self.switch_to_next_profile()
 
[docs]    def key_previous_profile(self):
        self.switch_to_previous_profile()
 
[docs]    def key_zoom_out(self):
        self.zoom_out()
 
[docs]    def key_copy(self):
        self.vte.copy_clipboard()
 
[docs]    def key_paste(self):
        self.paste_clipboard()
 
[docs]    def key_zoom_normal(self):
        self.zoom_orig ()
 
[docs]    def key_search(self):
        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
    # has absolutely no reference to Terminator.
    # N (next) - P (previous) - O (horizontal) - E (vertical) - W (close) 
[docs]    def key_cycle_next(self):
        self.key_go_next()
 
[docs]    def key_cycle_prev(self):
        self.key_go_prev()
 
[docs]    def key_go_next(self):
        self.emit('navigate', 'next')
 
[docs]    def key_go_prev(self):
        self.emit('navigate', 'prev')
 
[docs]    def key_go_up(self):
        self.emit('navigate', 'up')
 
[docs]    def key_go_down(self):
        self.emit('navigate', 'down')
 
[docs]    def key_go_left(self):
        self.emit('navigate', 'left')
 
[docs]    def key_go_right(self):
        self.emit('navigate', 'right')
 
[docs]    def key_split_horiz(self):
        self.emit('split-horiz', self.terminator.pid_cwd(self.pid))
 
[docs]    def key_split_vert(self):
        self.emit('split-vert', self.terminator.pid_cwd(self.pid))
 
[docs]    def key_rotate_cw(self):
        self.emit('rotate-cw')
 
[docs]    def key_rotate_ccw(self):
        self.emit('rotate-ccw')
 
[docs]    def key_close_term(self):
        self.close()
 
[docs]    def key_resize_up(self):
        self.emit('resize-term', 'up')
 
[docs]    def key_resize_down(self):
        self.emit('resize-term', 'down')
 
[docs]    def key_resize_left(self):
        self.emit('resize-term', 'left')
 
[docs]    def key_resize_right(self):
        self.emit('resize-term', 'right')
 
[docs]    def key_move_tab_right(self):
        self.emit('move-tab', 'right')
 
[docs]    def key_move_tab_left(self):
        self.emit('move-tab', 'left')
 
[docs]    def key_toggle_zoom(self):
        if self.is_zoomed():
            self.unzoom()
        else:
            self.maximise()
 
[docs]    def key_scaled_zoom(self):
        if self.is_zoomed():
            self.unzoom()
        else:
            self.zoom()
 
[docs]    def key_next_tab(self):
        self.emit('tab-change', -1)
 
[docs]    def key_prev_tab(self):
        self.emit('tab-change', -2)
 
[docs]    def key_switch_to_tab_1(self):
        self.emit('tab-change', 0)
 
[docs]    def key_switch_to_tab_2(self):
        self.emit('tab-change', 1)
 
[docs]    def key_switch_to_tab_3(self):
        self.emit('tab-change', 2)
 
[docs]    def key_switch_to_tab_4(self):
        self.emit('tab-change', 3)
 
[docs]    def key_switch_to_tab_5(self):
        self.emit('tab-change', 4)
 
[docs]    def key_switch_to_tab_6(self):
        self.emit('tab-change', 5)
 
[docs]    def key_switch_to_tab_7(self):
        self.emit('tab-change', 6)
 
[docs]    def key_switch_to_tab_8(self):
        self.emit('tab-change', 7)
 
[docs]    def key_switch_to_tab_9(self):
        self.emit('tab-change', 8)
 
[docs]    def key_switch_to_tab_10(self):
        self.emit('tab-change', 9)
 
[docs]    def key_reset(self):
        self.vte.reset (True, False)
 
[docs]    def key_reset_clear(self):
        self.vte.reset (True, True)
 
[docs]    def key_group_all(self):
        self.emit('group-all')
 
[docs]    def key_group_all_toggle(self):
        self.emit('group-all-toggle')
 
[docs]    def key_ungroup_all(self):
        self.emit('ungroup-all')
 
[docs]    def key_group_tab(self):
        self.emit('group-tab')
 
[docs]    def key_group_tab_toggle(self):
        self.emit('group-tab-toggle')
 
[docs]    def key_ungroup_tab(self):
        self.emit('ungroup-tab')
 
[docs]    def key_new_window(self):
        self.terminator.new_window(self.terminator.pid_cwd(self.pid))
 
[docs]    def key_new_terminator(self):
        spawn_new_terminator(self.origcwd, ['-u'])
 
[docs]    def key_broadcast_off(self):
        self.set_groupsend(None, self.terminator.groupsend_type['off'])
        self.terminator.focus_changed(self)
 
[docs]    def key_broadcast_group(self):
        self.set_groupsend(None, self.terminator.groupsend_type['group'])
        self.terminator.focus_changed(self)
 
[docs]    def key_broadcast_all(self):
        self.set_groupsend(None, self.terminator.groupsend_type['all'])
        self.terminator.focus_changed(self)
 
[docs]    def key_insert_number(self):
        self.emit('enumerate', False)
     
[docs]    def key_insert_padded(self):
        self.emit('enumerate', True)
 
[docs]    def key_edit_window_title(self):
        window = self.get_toplevel()
        dialog = gtk.Dialog(_('Rename Window'), window,
                        gtk.DIALOG_MODAL,
                        ( gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
                          gtk.STOCK_OK, gtk.RESPONSE_ACCEPT ))
        dialog.set_default_response(gtk.RESPONSE_ACCEPT)
        dialog.set_has_separator(False)
        dialog.set_resizable(False)
        dialog.set_border_width(8)
        
        label = gtk.Label(_('Enter a new title for the Terminator window...'))
        name = gtk.Entry()
        name.set_activates_default(True)
        if window.title.text != self.vte.get_window_title():
            name.set_text(self.get_toplevel().title.text)
        
        dialog.vbox.pack_start(label, False, False, 6)
        dialog.vbox.pack_start(name, False, False, 6)
        dialog.show_all()
        res = dialog.run()
        if res == gtk.RESPONSE_ACCEPT:
            if name.get_text():
                window.title.force_title(None)
                window.title.force_title(name.get_text())
            else:
                window.title.force_title(None)
        dialog.destroy()
        return
 
[docs]    def key_layout_launcher(self):
        LAYOUTLAUNCHER=LayoutLauncher()
 
[docs]    def key_page_up(self):
        self.scroll_by_page(-1)
 
[docs]    def key_page_down(self):
        self.scroll_by_page(1)
 
[docs]    def key_page_up_half(self):
        self.scroll_by_page(-0.5)
 
[docs]    def key_page_down_half(self):
        self.scroll_by_page(0.5)
 
[docs]    def key_line_up(self):
        self.scroll_by_line(-1)
 
[docs]    def key_line_down(self):
        self.scroll_by_line(1)
 
[docs]    def key_help(self):
        manual_index_page = manual_lookup()
        if manual_index_page:
            self.open_url('file://%s' % (manual_index_page))
# End key events
  
gobject.type_register(Terminal)
# vim: set expandtab ts=4 sw=4: