Land epic-refactor branch after 5.5 months of work
This commit is contained in:
commit
103428d0e3
@ -2,3 +2,6 @@
|
||||
terminatorlib/*.pyc
|
||||
.project
|
||||
.pydevproject
|
||||
terminatorlib/meliae
|
||||
_trial_temp
|
||||
terminatorc
|
||||
|
8
INSTALL
8
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:
|
||||
|
||||
|
2
README
2
README
@ -1,4 +1,4 @@
|
||||
Terminator 0.14
|
||||
Terminator 0.90
|
||||
by Chris Jones <cmsj@tenshu.net> and others.
|
||||
|
||||
The goal of this project is to produce a useful tool for arranging terminals.
|
||||
|
6
TODO
6
TODO
@ -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
|
6
debian/changelog
vendored
6
debian/changelog
vendored
@ -1,3 +1,9 @@
|
||||
terminator (0.90~alpha1) lucid; urgency=low
|
||||
|
||||
* New upstream pre-release
|
||||
|
||||
-- Chris Jones <cmsj@tenshu.net> Tue, 05 Jan 2010 09:56:27 +0000
|
||||
|
||||
terminator (0.14) karmic; urgency=low
|
||||
|
||||
* New upstream release
|
||||
|
2
debian/control
vendored
2
debian/control
vendored
@ -14,7 +14,7 @@ Homepage: http://www.tenshu.net/terminator/
|
||||
|
||||
Package: terminator
|
||||
Architecture: all
|
||||
Depends: ${python:Depends}, ${misc:Depends}, python-vte, python-gobject, python-gtk2, 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, python-gnome2, deskbar-applet
|
||||
|
3
debian/copyright
vendored
3
debian/copyright
vendored
@ -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:
|
||||
|
||||
<Copyright (C) 2006-2008 Chris Jones>
|
||||
<Copyright (C) 2006-2010 Chris Jones and others>
|
||||
|
||||
License:
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
@ -119,10 +121,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
|
||||
|
13
setup.py
13
setup.py
@ -6,14 +6,15 @@ 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')
|
||||
@ -83,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:
|
||||
@ -152,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',
|
||||
@ -173,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
|
||||
)
|
||||
|
196
terminator
196
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
|
||||
@ -18,161 +18,57 @@
|
||||
|
||||
"""Terminator by Chris Jones <cmsj@tenshu.net>"""
|
||||
|
||||
# import standard python libs
|
||||
import os, sys
|
||||
origcwd = os.getcwd()
|
||||
from optparse import OptionParser, SUPPRESS_HELP
|
||||
|
||||
import terminatorlib.translation
|
||||
from terminatorlib.version import APP_NAME, APP_VERSION
|
||||
|
||||
from terminatorlib.config import dbg, err, debug
|
||||
import terminatorlib.config
|
||||
import sys
|
||||
|
||||
# Check we have simple basics like Gtk+ and a valid $DISPLAY
|
||||
try:
|
||||
import pygtk
|
||||
pygtk.require ("2.0")
|
||||
import pygtk
|
||||
pygtk.require ("2.0")
|
||||
# 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. ' \
|
||||
'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.terminator import Terminator
|
||||
|
||||
if __name__ == '__main__':
|
||||
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.optionparse
|
||||
from terminatorlib.terminator 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()
|
||||
TERMINATOR.reconfigure()
|
||||
WINDOW = MAKER.make('Window')
|
||||
TERMINAL = MAKER.make('Terminal')
|
||||
|
||||
WINDOW.add(TERMINAL)
|
||||
WINDOW.show()
|
||||
TERMINAL.spawn_child()
|
||||
|
||||
if OPTIONS.debug > 2:
|
||||
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 (options.profile, command, options.fullscreen,
|
||||
options.maximise, options.borderless, options.no_gconf,
|
||||
options.geometry, options.hidden, options.forcedtitle, options.role)
|
||||
|
||||
term.origcwd = origcwd
|
||||
|
||||
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
|
||||
|
||||
gtk.main()
|
||||
gtk.main()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
|
@ -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 <cmsj@tenshu.net> 0.90-1
|
||||
- Attempt to update for 0.90 pre-release.
|
||||
Note that this specfile is untested.
|
||||
|
||||
* Thu Jan 15 2009 Chris Jones <cmsj@tenshu.net> 0.12-1
|
||||
- Remove patch application since this isn't a fedora build.
|
||||
Note that this specfile is untested.
|
||||
|
@ -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
|
||||
@ -16,4 +16,3 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
"""Terminator by Chris Jones <cmsj@tenshu.net>"""
|
||||
|
||||
|
55
terminatorlib/borg.py
Executable file
55
terminatorlib/borg.py
Executable file
@ -0,0 +1,55 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net>
|
||||
# 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.
|
||||
"""
|
||||
|
||||
from util import dbg
|
||||
|
||||
# pylint: disable-msg=R0903
|
||||
# pylint: disable-msg=R0921
|
||||
class Borg:
|
||||
"""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, self.__class__.__name__)
|
||||
|
||||
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, borgtype=None):
|
||||
"""Class initialiser. Overwrite our class dictionary with the shared
|
||||
state. This makes us identical to every other instance of this class
|
||||
type."""
|
||||
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."""
|
||||
raise NotImplementedError('prepare_attributes')
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,233 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import re
|
||||
from terminatorlib.config import dbg, debug
|
||||
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)
|
||||
|
||||
|
0
terminatorlib/configobj/__init__.py
Normal file
0
terminatorlib/configobj/__init__.py
Normal file
2455
terminatorlib/configobj/configobj.py
Normal file
2455
terminatorlib/configobj/configobj.py
Normal file
File diff suppressed because it is too large
Load Diff
1465
terminatorlib/configobj/validate.py
Normal file
1465
terminatorlib/configobj/validate.py
Normal file
File diff suppressed because it is too large
Load Diff
192
terminatorlib/container.py
Executable file
192
terminatorlib/container.py
Executable file
@ -0,0 +1,192 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net>
|
||||
# GPL v2 only
|
||||
"""container.py - classes necessary to contain Terminal widgets"""
|
||||
|
||||
import gobject
|
||||
import gtk
|
||||
|
||||
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):
|
||||
"""Base class for Terminator Containers"""
|
||||
|
||||
terminator = None
|
||||
immutable = None
|
||||
children = None
|
||||
config = None
|
||||
signals = None
|
||||
signalman = None
|
||||
|
||||
def __init__(self):
|
||||
"""Class initialiser"""
|
||||
self.children = []
|
||||
self.signals = []
|
||||
self.cnxids = Signalman()
|
||||
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, *args):
|
||||
"""Register the requested signal and record its connection ID"""
|
||||
self.cnxids.new(widget, signal, handler, *args)
|
||||
return
|
||||
|
||||
def disconnect_child(self, widget):
|
||||
"""De-register the signals for a child"""
|
||||
self.cnxids.remove_widget(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):
|
||||
dbg('Container::closeterm: self.remove() failed for %s' % 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, 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)
|
||||
|
||||
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(_('<big><b>Close multiple terminals?</b></big>'))
|
||||
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.') % (reqtype, reqtype))
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
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:
|
52
terminatorlib/cwd.py
Executable file
52
terminatorlib/cwd.py
Executable file
@ -0,0 +1,52 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net>
|
||||
# GPL v2 only
|
||||
"""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
|
||||
import pwd
|
||||
from util import dbg
|
||||
|
||||
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]
|
||||
|
||||
return(cwd)
|
||||
|
||||
def get_pid_cwd():
|
||||
"""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:
|
@ -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
|
||||
|
@ -17,24 +17,33 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor
|
||||
# , Boston, MA 02110-1301 USA
|
||||
|
||||
# pylint: disable-msg=W0212
|
||||
''' Editable Label class'''
|
||||
""" Editable Label class"""
|
||||
import gtk
|
||||
import gobject
|
||||
|
||||
class TerminatorEditableLabel( gtk.EventBox ):
|
||||
'''
|
||||
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
|
||||
'''
|
||||
"""
|
||||
_label = None
|
||||
_ebox = None
|
||||
_autotext = None
|
||||
_custom = None
|
||||
_entry = None
|
||||
_entry_handler_id = None
|
||||
|
||||
__gsignals__ = {
|
||||
'edit-done': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
|
||||
}
|
||||
|
||||
def __init__(self, text = ""):
|
||||
''' Class initialiser'''
|
||||
""" Class initialiser"""
|
||||
gtk.EventBox.__init__(self)
|
||||
self.__gobject_init__()
|
||||
|
||||
self._entry_handler_id = []
|
||||
self._label = gtk.Label(text)
|
||||
self._custom = False
|
||||
@ -43,22 +52,27 @@ 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'''
|
||||
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
|
||||
if not self._custom or force:
|
||||
self._label.set_text(text)
|
||||
|
||||
def get_text( self ):
|
||||
'''get the text from the label'''
|
||||
return self._label.get_text()
|
||||
def get_text(self):
|
||||
"""get the text from the label"""
|
||||
return(self._label.get_text())
|
||||
|
||||
def _on_click_text(self, widget, event):
|
||||
'''event handling text edition'''
|
||||
if event.type == gtk.gdk._2BUTTON_PRESS :
|
||||
# pylint: disable-msg=W0613
|
||||
"""event handling text edition"""
|
||||
if event.type == gtk.gdk._2BUTTON_PRESS:
|
||||
self.remove (self._label)
|
||||
self._entry = gtk.Entry ()
|
||||
self._entry.set_text (self._label.get_text ())
|
||||
@ -72,13 +86,12 @@ class TerminatorEditableLabel( 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):
|
||||
'''replace gtk.Entry by the gtk.Label'''
|
||||
# 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
|
||||
for sig in self._entry_handler_id:
|
||||
@ -89,13 +102,13 @@ class TerminatorEditableLabel( gtk.EventBox ):
|
||||
self.add (self._label)
|
||||
self._entry = None
|
||||
self.show_all ()
|
||||
return True
|
||||
#make pylint happy
|
||||
if 1 or widget or event:
|
||||
return False
|
||||
self.emit('edit-done')
|
||||
return(True)
|
||||
return(False)
|
||||
|
||||
def _on_entry_activated (self, widget):
|
||||
'''get the text entered in gtk.Entry'''
|
||||
# pylint: disable-msg=W0613
|
||||
"""get the text entered in gtk.Entry"""
|
||||
entry = self._entry.get_text ()
|
||||
label = self._label.get_text ()
|
||||
if entry == '':
|
||||
@ -106,20 +119,11 @@ class TerminatorEditableLabel( 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):
|
||||
'''handle keypressed in gtk.Entry'''
|
||||
# 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
|
||||
|
||||
def modify_fg (self, state, color):
|
||||
'''modify the foreground of our label'''
|
||||
self._label.modify_fg(state, color)
|
||||
|
||||
gobject.type_register(EditableLabel)
|
@ -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
|
||||
@ -23,95 +23,97 @@ This list is taken from gnome-terminal's src/terminal-encoding.c
|
||||
and src/encoding.c
|
||||
"""
|
||||
|
||||
from terminatorlib import translation
|
||||
from translation import _
|
||||
|
||||
#pylint: disable-msg=R0903
|
||||
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)
|
||||
|
||||
|
97
terminatorlib/factory.py
Executable file
97
terminatorlib/factory.py
Executable file
@ -0,0 +1,97 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net>
|
||||
# GPL v2 only
|
||||
"""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
|
||||
|
||||
# pylint: disable-msg=R0201
|
||||
# pylint: disable-msg=W0613
|
||||
class Factory(Borg):
|
||||
"""Definition of a class that makes other classes"""
|
||||
def __init__(self):
|
||||
"""Class initialiser"""
|
||||
Borg.__init__(self, self.__class__.__name__)
|
||||
self.prepare_attributes()
|
||||
|
||||
def prepare_attributes(self):
|
||||
"""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"""
|
||||
types = {'Terminal': 'terminal',
|
||||
'VPaned': 'paned',
|
||||
'HPaned': 'paned',
|
||||
'Paned': 'paned',
|
||||
'Notebook': 'notebook',
|
||||
'Container': 'container',
|
||||
'Window': 'window'}
|
||||
if classtype in types.keys():
|
||||
# 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)
|
||||
return(False)
|
||||
|
||||
def make(self, product, **kwargs):
|
||||
"""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(**kwargs))
|
||||
|
||||
def make_window(self, **kwargs):
|
||||
"""Make a Window"""
|
||||
import window
|
||||
return(window.Window(**kwargs))
|
||||
|
||||
def make_terminal(self, **kwargs):
|
||||
"""Make a Terminal"""
|
||||
import terminal
|
||||
return(terminal.Terminal())
|
||||
|
||||
def make_hpaned(self, **kwargs):
|
||||
"""Make an HPaned"""
|
||||
import paned
|
||||
return(paned.HPaned())
|
||||
|
||||
def make_vpaned(self, **kwargs):
|
||||
"""Make a VPaned"""
|
||||
import paned
|
||||
return(paned.VPaned())
|
||||
|
||||
def make_notebook(self, **kwargs):
|
||||
"""Make a Notebook"""
|
||||
import notebook
|
||||
return(notebook.Notebook(kwargs['window']))
|
||||
|
@ -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-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
|
||||
@ -24,111 +24,111 @@ 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:
|
||||
continue
|
||||
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:
|
||||
continue
|
||||
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)
|
||||
|
||||
|
360
terminatorlib/notebook.py
Executable file
360
terminatorlib/notebook.py
Executable file
@ -0,0 +1,360 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net>
|
||||
# GPL v2 only
|
||||
"""notebook.py - classes for the notebook widget"""
|
||||
|
||||
import gobject
|
||||
import gtk
|
||||
|
||||
from terminator import Terminator
|
||||
from config import Config
|
||||
from factory import Factory
|
||||
from container import Container
|
||||
from editablelabel import EditableLabel
|
||||
from translation import _
|
||||
from util import err, dbg, get_top_window
|
||||
|
||||
class Notebook(Container, gtk.Notebook):
|
||||
"""Class implementing a gtk.Notebook container"""
|
||||
window = None
|
||||
|
||||
def __init__(self, window):
|
||||
"""Class initialiser"""
|
||||
if isinstance(window.get_child(), gtk.Notebook):
|
||||
err('There is already a Notebook at the top of this window')
|
||||
raise(ValueError)
|
||||
|
||||
Container.__init__(self)
|
||||
gtk.Notebook.__init__(self)
|
||||
self.terminator = Terminator()
|
||||
self.window = window
|
||||
gobject.type_register(Notebook)
|
||||
self.register_signals(Notebook)
|
||||
self.configure()
|
||||
|
||||
child = window.get_child()
|
||||
window.remove(child)
|
||||
window.add(self)
|
||||
self.newtab(child)
|
||||
|
||||
self.show_all()
|
||||
|
||||
def configure(self):
|
||||
"""Apply widget-wide settings"""
|
||||
# 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'])
|
||||
|
||||
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):
|
||||
"""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
|
||||
|
||||
label = self.get_tab_label(widget)
|
||||
self.remove(widget)
|
||||
|
||||
maker = Factory()
|
||||
if vertical:
|
||||
container = maker.make('vpaned')
|
||||
else:
|
||||
container = maker.make('hpaned')
|
||||
|
||||
if not sibling:
|
||||
sibling = maker.make('terminal')
|
||||
sibling.spawn_child()
|
||||
|
||||
self.insert_page(container, None, page_num)
|
||||
self.set_tab_label(container, label)
|
||||
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"""
|
||||
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. 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):
|
||||
"""Add a new tab, optionally supplying a child widget"""
|
||||
top_window = get_top_window(self)
|
||||
|
||||
if not widget:
|
||||
maker = Factory()
|
||||
widget = maker.make('Terminal')
|
||||
widget.spawn_child()
|
||||
|
||||
signals = {'close-term': self.wrapcloseterm,
|
||||
'split-horiz': self.split_horiz,
|
||||
'split-vert': self.split_vert,
|
||||
'title-change': self.propagate_title_change,
|
||||
'unzoom': self.unzoom}
|
||||
|
||||
maker = Factory()
|
||||
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.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)
|
||||
label.connect('close-clicked', self.closetab)
|
||||
|
||||
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.set_current_page(-1)
|
||||
widget.grab_focus()
|
||||
|
||||
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')
|
||||
self.hoover()
|
||||
else:
|
||||
dbg('Notebook::wrapcloseterm: closeterm failed')
|
||||
|
||||
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. Bailing.' % (label, nb))
|
||||
return
|
||||
|
||||
maker = Factory()
|
||||
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'):
|
||||
dbg('Notebook::closetab: child is a Container')
|
||||
dialog = self.construct_confirm_close(self.window, _('tab'))
|
||||
result = dialog.run()
|
||||
dialog.destroy()
|
||||
|
||||
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)
|
||||
|
||||
while len(objects) > 0:
|
||||
descendant = objects.pop()
|
||||
descendant.close()
|
||||
# FIXME: Is this mainloop iterations stuff necessary?
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration()
|
||||
return
|
||||
else:
|
||||
dbg('Notebook::closetab: user cancelled request')
|
||||
return
|
||||
else:
|
||||
err('Notebook::closetab: child is unknown type %s' % child)
|
||||
return
|
||||
|
||||
nb.remove_page(tabnum)
|
||||
del(label)
|
||||
|
||||
def resizeterm(self, widget, keyname):
|
||||
"""Handle a keyboard event requesting a terminal resize"""
|
||||
raise NotImplementedError('resizeterm')
|
||||
|
||||
def zoom(self, widget, fontscale = False):
|
||||
"""Zoom a terminal"""
|
||||
raise NotImplementedError('zoom')
|
||||
|
||||
def unzoom(self, widget):
|
||||
"""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"""
|
||||
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
|
||||
|
||||
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
|
||||
terminator = None
|
||||
config = None
|
||||
label = None
|
||||
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()
|
||||
|
||||
self.label = EditableLabel(title)
|
||||
self.update_angle()
|
||||
|
||||
self.pack_start(self.label, True, True)
|
||||
|
||||
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']:
|
||||
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"""
|
||||
self.emit('close-clicked', self)
|
||||
|
||||
# vim: set expandtab ts=4 sw=4:
|
101
terminatorlib/optionparse.py
Executable file
101
terminatorlib/optionparse.py
Executable file
@ -0,0 +1,101 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator.optionparse - Parse commandline options
|
||||
# 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
|
||||
# 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', '-n',
|
||||
'--no-gconf', '-p', '--profile' ]:
|
||||
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.debug >1:
|
||||
util.DEBUGFILES = 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)
|
||||
|
||||
configobj.options_set(options)
|
||||
|
||||
if util.DEBUG == True:
|
||||
dbg('OptionParse::parse_options: command line options: %s' % options)
|
||||
|
||||
return(options)
|
195
terminatorlib/paned.py
Executable file
195
terminatorlib/paned.py
Executable file
@ -0,0 +1,195 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net>
|
||||
# GPL v2 only
|
||||
"""paned.py - a base Paned container class and the vertical/horizontal
|
||||
variants"""
|
||||
|
||||
import gobject
|
||||
import gtk
|
||||
|
||||
from util import dbg, err, get_top_window
|
||||
from terminator import Terminator
|
||||
from factory import Factory
|
||||
from container import Container
|
||||
|
||||
# pylint: disable-msg=R0921
|
||||
# pylint: disable-msg=E1101
|
||||
class Paned(Container):
|
||||
"""Base class for Paned Containers"""
|
||||
|
||||
def __init__(self):
|
||||
"""Class initialiser"""
|
||||
self.terminator = Terminator()
|
||||
Container.__init__(self)
|
||||
self.signals.append({'name': 'resize-term',
|
||||
'flags': gobject.SIGNAL_RUN_LAST,
|
||||
'return_type': gobject.TYPE_NONE,
|
||||
'param_types': (gobject.TYPE_STRING,)})
|
||||
|
||||
|
||||
# pylint: disable-msg=W0613
|
||||
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
|
||||
|
||||
dbg("Paned::set_initial_position: Setting position to: %d" % position)
|
||||
self.set_position(position)
|
||||
self.cnxids.remove_signal(self, 'expose-event')
|
||||
|
||||
# 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()
|
||||
else:
|
||||
container = HPaned()
|
||||
|
||||
if not sibling:
|
||||
sibling = maker.make('terminal')
|
||||
sibling.spawn_child()
|
||||
|
||||
self.add(container)
|
||||
self.show_all()
|
||||
|
||||
container.add(widget)
|
||||
container.add(sibling)
|
||||
|
||||
self.show_all()
|
||||
|
||||
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)
|
||||
elif len(self.children) == 1:
|
||||
if self.get_child1():
|
||||
self.pack2(widget, True, True)
|
||||
else:
|
||||
self.pack1(widget, True, True)
|
||||
self.children.append(widget)
|
||||
else:
|
||||
raise ValueError('Paned widgets can only have two children')
|
||||
|
||||
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,
|
||||
'title-change': self.propagate_title_change,
|
||||
'resize-term': self.resizeterm,
|
||||
'zoom': top_window.zoom}
|
||||
|
||||
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()
|
||||
|
||||
elif isinstance(widget, gtk.Paned):
|
||||
try:
|
||||
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)
|
||||
self.disconnect_child(widget)
|
||||
self.children.remove(widget)
|
||||
return(True)
|
||||
|
||||
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()
|
||||
parent.remove(self)
|
||||
parent.add(sibling)
|
||||
del(self)
|
||||
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"""
|
||||
maker = Factory()
|
||||
if keyname in ['up', 'down'] and isinstance(self, gtk.VPaned):
|
||||
# This is a key we can handle
|
||||
position = self.get_position()
|
||||
|
||||
if maker.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 maker.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"""
|
||||
def __init__(self):
|
||||
"""Class initialiser"""
|
||||
Paned.__init__(self)
|
||||
gtk.HPaned.__init__(self)
|
||||
self.register_signals(HPaned)
|
||||
self.cnxids.new(self, 'expose-event', self.set_initial_position)
|
||||
|
||||
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)
|
||||
self.register_signals(VPaned)
|
||||
self.cnxids.new(self, 'expose-event', self.set_initial_position)
|
||||
|
||||
gobject.type_register(HPaned)
|
||||
gobject.type_register(VPaned)
|
||||
# vim: set expandtab ts=4 sw=4:
|
143
terminatorlib/plugin.py
Executable file
143
terminatorlib/plugin.py
Executable file
@ -0,0 +1,143 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net>
|
||||
# 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
|
||||
{}
|
||||
>>> registry.load_plugins(True)
|
||||
>>> plugins = registry.get_plugins_by_capability('test')
|
||||
>>> len(plugins)
|
||||
1
|
||||
>>> plugins[0] #doctest: +ELLIPSIS
|
||||
<testplugin.TestPlugin object at 0x...>
|
||||
>>> registry.get_plugins_by_capability('this_should_not_ever_exist')
|
||||
[]
|
||||
>>> plugins[0].do_test()
|
||||
'TestPluginWin'
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import borg
|
||||
from config import Config
|
||||
from util import dbg, err, get_config_dir
|
||||
|
||||
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
|
||||
done = None
|
||||
|
||||
def __init__(self):
|
||||
"""Class initialiser"""
|
||||
borg.Borg.__init__(self, self.__class__.__name__)
|
||||
self.prepare_attributes()
|
||||
|
||||
def prepare_attributes(self):
|
||||
"""Prepare our attributes"""
|
||||
if not self.instances:
|
||||
self.instances = {}
|
||||
if not self.path:
|
||||
self.path = []
|
||||
(head, tail) = os.path.split(borg.__file__)
|
||||
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:
|
||||
self.done = False
|
||||
|
||||
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:
|
||||
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 not testing and item in config['disabled_plugins']:
|
||||
continue
|
||||
if item not in self.instances:
|
||||
func = getattr(module, item)
|
||||
self.instances[item] = func()
|
||||
except Exception, e:
|
||||
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 = []
|
||||
dbg('PluginRegistry::get_plugins_by_capability: searching %d plugins \
|
||||
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)
|
||||
|
||||
# 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 doctest
|
||||
sys.path.insert(0, 'plugins')
|
||||
(failed, attempted) = doctest.testmod()
|
||||
print "%d/%d tests failed" % (failed, attempted)
|
||||
sys.exit(failed)
|
434
terminatorlib/plugins/custom_commands.py
Executable file
434
terminatorlib/plugins/custom_commands.py
Executable file
@ -0,0 +1,434 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net>
|
||||
# GPL v2 only
|
||||
"""custom_commands.py - Terminator Plugin to add custom command menu entries"""
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 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
|
||||
|
||||
(CC_COL_ENABLED, CC_COL_NAME, CC_COL_COMMAND) = range(0,3)
|
||||
|
||||
# Every plugin you want Terminator to load *must* be listed in 'available'
|
||||
available = ['CustomCommandsMenu']
|
||||
|
||||
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 = 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 = s["name"]
|
||||
command = s["command"]
|
||||
enabled = s["enabled"] and s["enabled"] or False
|
||||
self.cmd_list.append(
|
||||
{'enabled' : enabled,
|
||||
'name' : name,
|
||||
'command' : command
|
||||
}
|
||||
)
|
||||
def callback(self, menuitems, menu, terminal):
|
||||
"""Add our menu items to the menu"""
|
||||
item = gtk.MenuItem(_('Custom Commands'))
|
||||
menuitems.append(item)
|
||||
|
||||
submenu = gtk.Menu()
|
||||
item.set_submenu(submenu)
|
||||
|
||||
menuitem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
|
||||
menuitem.connect("activate", self.configure)
|
||||
submenu.append(menuitem)
|
||||
|
||||
menuitem = gtk.SeparatorMenuItem()
|
||||
submenu.append(menuitem)
|
||||
|
||||
theme = gtk.IconTheme()
|
||||
for command in self.cmd_list:
|
||||
if not command['enabled']:
|
||||
continue
|
||||
exe = command['command'].split(' ')[0]
|
||||
iconinfo = theme.choose_icon([exe], gtk.ICON_SIZE_MENU, gtk.ICON_LOOKUP_USE_BUILTIN)
|
||||
if iconinfo:
|
||||
image = gtk.Image()
|
||||
image.set_from_icon_name(exe, gtk.ICON_SIZE_MENU)
|
||||
menuitem = gtk.ImageMenuItem(command['name'])
|
||||
menuitem.set_image(image)
|
||||
else:
|
||||
menuitem = gtk.MenuItem(command["name"])
|
||||
menuitem.connect("activate", self._execute, {'terminal' : terminal, 'command' : command['command'] })
|
||||
submenu.append(menuitem)
|
||||
|
||||
def _save_config(self):
|
||||
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']
|
||||
|
||||
item = {}
|
||||
item['enabled'] = enabled
|
||||
item['name'] = name
|
||||
item['command'] = command
|
||||
|
||||
config.plugin_set(self.__class__.__name__, name, item)
|
||||
config.save()
|
||||
i = i + 1
|
||||
|
||||
def _execute(self, widget, data):
|
||||
command = data['command']
|
||||
if command[len(command)-1] != '\n':
|
||||
command = command + '\n'
|
||||
data['terminal'].vte.feed_child(command)
|
||||
|
||||
def configure(self, widget, data = None):
|
||||
ui = {}
|
||||
dbox = gtk.Dialog(
|
||||
_("Custom Commands Configuration"),
|
||||
None,
|
||||
gtk.DIALOG_MODAL,
|
||||
(
|
||||
gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
|
||||
gtk.STOCK_OK, gtk.RESPONSE_ACCEPT
|
||||
)
|
||||
)
|
||||
store = gtk.ListStore(bool, str, str)
|
||||
|
||||
for command in self.cmd_list:
|
||||
store.append([command['enabled'], command['name'], command['command']])
|
||||
|
||||
treeview = gtk.TreeView(store)
|
||||
#treeview.connect("cursor-changed", self.on_cursor_changed, ui)
|
||||
selection = treeview.get_selection()
|
||||
selection.set_mode(gtk.SELECTION_SINGLE)
|
||||
selection.connect("changed", self.on_selection_changed, ui)
|
||||
ui['treeview'] = treeview
|
||||
|
||||
renderer = gtk.CellRendererToggle()
|
||||
renderer.connect('toggled', self.on_toggled, ui)
|
||||
column = gtk.TreeViewColumn("Enabled", renderer, active=CC_COL_ENABLED)
|
||||
treeview.append_column(column)
|
||||
|
||||
renderer = gtk.CellRendererText()
|
||||
column = gtk.TreeViewColumn("Name", renderer, text=CC_COL_NAME)
|
||||
treeview.append_column(column)
|
||||
|
||||
renderer = gtk.CellRendererText()
|
||||
column = gtk.TreeViewColumn("Command", renderer, text=CC_COL_COMMAND)
|
||||
treeview.append_column(column)
|
||||
|
||||
hbox = gtk.HBox()
|
||||
hbox.pack_start(treeview)
|
||||
dbox.vbox.pack_start(hbox)
|
||||
|
||||
button_box = gtk.VBox()
|
||||
|
||||
button = gtk.Button(stock=gtk.STOCK_GOTO_TOP)
|
||||
button_box.pack_start(button, False, True)
|
||||
button.connect("clicked", self.on_goto_top, ui)
|
||||
button.set_sensitive(False)
|
||||
ui['button_top'] = button
|
||||
|
||||
button = gtk.Button(stock=gtk.STOCK_GO_UP)
|
||||
button_box.pack_start(button, False, True)
|
||||
button.connect("clicked", self.on_go_up, ui)
|
||||
button.set_sensitive(False)
|
||||
ui['button_up'] = button
|
||||
|
||||
button = gtk.Button(stock=gtk.STOCK_GO_DOWN)
|
||||
button_box.pack_start(button, False, True)
|
||||
button.connect("clicked", self.on_go_down, ui)
|
||||
button.set_sensitive(False)
|
||||
ui['button_down'] = button
|
||||
|
||||
button = gtk.Button(stock=gtk.STOCK_GOTO_LAST)
|
||||
button_box.pack_start(button, False, True)
|
||||
button.connect("clicked", self.on_goto_last, ui)
|
||||
button.set_sensitive(False)
|
||||
ui['button_last'] = button
|
||||
|
||||
button = gtk.Button(stock=gtk.STOCK_NEW)
|
||||
button_box.pack_start(button, False, True)
|
||||
button.connect("clicked", self.on_new, ui)
|
||||
ui['button_new'] = button
|
||||
|
||||
button = gtk.Button(stock=gtk.STOCK_EDIT)
|
||||
button_box.pack_start(button, False, True)
|
||||
button.set_sensitive(False)
|
||||
button.connect("clicked", self.on_edit, ui)
|
||||
ui['button_edit'] = button
|
||||
|
||||
button = gtk.Button(stock=gtk.STOCK_DELETE)
|
||||
button_box.pack_start(button, False, True)
|
||||
button.connect("clicked", self.on_delete, ui)
|
||||
button.set_sensitive(False)
|
||||
ui['button_delete'] = button
|
||||
|
||||
|
||||
|
||||
hbox.pack_start(button_box)
|
||||
dbox.show_all()
|
||||
res = dbox.run()
|
||||
if res == gtk.RESPONSE_ACCEPT:
|
||||
#we save the config
|
||||
iter = store.get_iter_first()
|
||||
self.cmd_list = []
|
||||
while iter:
|
||||
(enabled, name, command) = store.get(iter,
|
||||
CC_COL_ENABLED,
|
||||
CC_COL_NAME,
|
||||
CC_COL_COMMAND)
|
||||
self.cmd_list.append(
|
||||
{'enabled' : enabled,
|
||||
'name': name,
|
||||
'command' : command}
|
||||
)
|
||||
iter = store.iter_next(iter)
|
||||
self._save_config()
|
||||
|
||||
dbox.destroy()
|
||||
return
|
||||
|
||||
def on_toggled(self, widget, path, data):
|
||||
treeview = data['treeview']
|
||||
store = treeview.get_model()
|
||||
iter = store.get_iter(path)
|
||||
(enabled, name, command) = store.get(iter,
|
||||
CC_COL_ENABLED,
|
||||
CC_COL_NAME,
|
||||
CC_COL_COMMAND
|
||||
)
|
||||
store.set_value(iter, CC_COL_ENABLED, not enabled)
|
||||
for cmd in self.cmd_list:
|
||||
if cmd['name'] == name:
|
||||
cmd['enabled'] = not enabled
|
||||
break
|
||||
|
||||
def on_selection_changed(self,selection, data=None):
|
||||
treeview = selection.get_tree_view()
|
||||
(model, iter) = selection.get_selected()
|
||||
data['button_top'].set_sensitive(iter is not None)
|
||||
data['button_up'].set_sensitive(iter is not None)
|
||||
data['button_down'].set_sensitive(iter is not None)
|
||||
data['button_last'].set_sensitive(iter is not None)
|
||||
data['button_edit'].set_sensitive(iter is not None)
|
||||
data['button_delete'].set_sensitive(iter is not None)
|
||||
|
||||
def _create_command_dialog(self, enabled_var = False, name_var = "", command_var = ""):
|
||||
dialog = gtk.Dialog(
|
||||
_("New Command"),
|
||||
None,
|
||||
gtk.DIALOG_MODAL,
|
||||
(
|
||||
gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
|
||||
gtk.STOCK_OK, gtk.RESPONSE_ACCEPT
|
||||
)
|
||||
)
|
||||
table = gtk.Table(3, 2)
|
||||
|
||||
label = gtk.Label(_("Enabled:"))
|
||||
table.attach(label, 0, 1, 0, 1)
|
||||
enabled = gtk.CheckButton()
|
||||
enabled.set_active(enabled_var)
|
||||
table.attach(enabled, 1, 2, 0, 1)
|
||||
|
||||
label = gtk.Label(_("Name:"))
|
||||
table.attach(label, 0, 1, 1, 2)
|
||||
name = gtk.Entry()
|
||||
name.set_text(name_var)
|
||||
table.attach(name, 1, 2, 1, 2)
|
||||
|
||||
label = gtk.Label(_("Command:"))
|
||||
table.attach(label, 0, 1, 2, 3)
|
||||
command = gtk.Entry()
|
||||
command.set_text(command_var)
|
||||
table.attach(command, 1, 2, 2, 3)
|
||||
|
||||
dialog.vbox.pack_start(table)
|
||||
dialog.show_all()
|
||||
return (dialog,enabled,name,command)
|
||||
|
||||
def _error(self, msg):
|
||||
err = gtk.MessageDialog(dialog,
|
||||
gtk.DIALOG_MODAL,
|
||||
gtk.MESSAGE_ERROR,
|
||||
gtk.BUTTONS_CLOSE,
|
||||
msg
|
||||
)
|
||||
err.run()
|
||||
err.destroy()
|
||||
|
||||
|
||||
|
||||
|
||||
def on_new(self, button, data):
|
||||
(dialog,enabled,name,command) = self._create_command_dialog()
|
||||
res = dialog.run()
|
||||
item = {}
|
||||
if res == gtk.RESPONSE_ACCEPT:
|
||||
item['enabled'] = enabled.get_active()
|
||||
item['name'] = name.get_text()
|
||||
item['command'] = command.get_text()
|
||||
if item['name'] == '' or item['command'] == '':
|
||||
err = gtk.MessageDialog(dialog,
|
||||
gtk.DIALOG_MODAL,
|
||||
gtk.MESSAGE_ERROR,
|
||||
gtk.BUTTONS_CLOSE,
|
||||
_("You need to define a name and command")
|
||||
)
|
||||
err.run()
|
||||
err.destroy()
|
||||
else:
|
||||
# we have a new command
|
||||
store = data['treeview'].get_model()
|
||||
iter = store.get_iter_first()
|
||||
name_exist = False
|
||||
while iter != None:
|
||||
if store.get_value(iter,CC_COL_NAME) == item['name']:
|
||||
name_exist = True
|
||||
break
|
||||
iter = store.iter_next(iter)
|
||||
if not name_exist:
|
||||
store.append((item['enabled'], item['name'], item['command']))
|
||||
else:
|
||||
self._err(_("Name *%s* already exist") % item['name'])
|
||||
dialog.destroy()
|
||||
|
||||
def on_goto_top(self, button, data):
|
||||
treeview = data['treeview']
|
||||
selection = treeview.get_selection()
|
||||
(store, iter) = selection.get_selected()
|
||||
|
||||
if not iter:
|
||||
return
|
||||
firstiter = store.get_iter_first()
|
||||
store.move_before(iter, firstiter)
|
||||
|
||||
def on_go_up(self, button, data):
|
||||
treeview = data['treeview']
|
||||
selection = treeview.get_selection()
|
||||
(store, iter) = selection.get_selected()
|
||||
|
||||
if not iter:
|
||||
return
|
||||
|
||||
tmpiter = store.get_iter_first()
|
||||
|
||||
if(store.get_path(tmpiter) == store.get_path(iter)):
|
||||
return
|
||||
|
||||
while tmpiter:
|
||||
next = store.iter_next(tmpiter)
|
||||
if(store.get_path(next) == store.get_path(iter)):
|
||||
store.swap(iter, tmpiter)
|
||||
break
|
||||
tmpiter = next
|
||||
|
||||
def on_go_down(self, button, data):
|
||||
treeview = data['treeview']
|
||||
selection = treeview.get_selection()
|
||||
(store, iter) = selection.get_selected()
|
||||
|
||||
if not iter:
|
||||
return
|
||||
next = store.iter_next(iter)
|
||||
if next:
|
||||
store.swap(iter, next)
|
||||
|
||||
def on_goto_last(self, button, data):
|
||||
treeview = data['treeview']
|
||||
selection = treeview.get_selection()
|
||||
(store, iter) = selection.get_selected()
|
||||
|
||||
if not iter:
|
||||
return
|
||||
lastiter = iter
|
||||
tmpiter = store.get_iter_first()
|
||||
while tmpiter:
|
||||
lastiter = tmpiter
|
||||
tmpiter = store.iter_next(tmpiter)
|
||||
|
||||
store.move_after(iter, lastiter)
|
||||
|
||||
|
||||
def on_delete(self, button, data):
|
||||
treeview = data['treeview']
|
||||
selection = treeview.get_selection()
|
||||
(store, iter) = selection.get_selected()
|
||||
if iter:
|
||||
store.remove(iter)
|
||||
|
||||
return
|
||||
|
||||
def on_edit(self, button, data):
|
||||
treeview = data['treeview']
|
||||
selection = treeview.get_selection()
|
||||
(store, iter) = selection.get_selected()
|
||||
|
||||
if not iter:
|
||||
return
|
||||
|
||||
(dialog,enabled,name,command) = self._create_command_dialog(
|
||||
enabled_var = store.get_value(iter, CC_COL_ENABLED),
|
||||
name_var = store.get_value(iter, CC_COL_NAME),
|
||||
command_var = store.get_value(iter, CC_COL_COMMAND)
|
||||
)
|
||||
res = dialog.run()
|
||||
item = {}
|
||||
if res == gtk.RESPONSE_ACCEPT:
|
||||
item['enabled'] = enabled.get_active()
|
||||
item['name'] = name.get_text()
|
||||
item['command'] = command.get_text()
|
||||
if item['name'] == '' or item['command'] == '':
|
||||
err = gtk.MessageDialog(dialog,
|
||||
gtk.DIALOG_MODAL,
|
||||
gtk.MESSAGE_ERROR,
|
||||
gtk.BUTTONS_CLOSE,
|
||||
_("You need to define a name and command")
|
||||
)
|
||||
err.run()
|
||||
err.destroy()
|
||||
else:
|
||||
tmpiter = store.get_iter_first()
|
||||
name_exist = False
|
||||
while tmpiter != None:
|
||||
if store.get_path(tmpiter) != store.get_path(iter) and store.get_value(tmpiter,CC_COL_NAME) == item['name']:
|
||||
name_exist = True
|
||||
break
|
||||
tmpiter = store.iter_next(tmpiter)
|
||||
if not name_exist:
|
||||
store.set(iter,
|
||||
CC_COL_ENABLED,item['enabled'],
|
||||
CC_COL_NAME, item['name'],
|
||||
CC_COL_COMMAND, item['command']
|
||||
)
|
||||
else:
|
||||
self._err(_("Name *%s* already exist") % item['name'])
|
||||
|
||||
dialog.destroy()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
c = CustomCommandsMenu()
|
||||
c.configure(None, None)
|
||||
gtk.main()
|
||||
|
21
terminatorlib/plugins/terminal_menu.py
Normal file
21
terminatorlib/plugins/terminal_menu.py
Normal file
@ -0,0 +1,21 @@
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net?
|
||||
# GPL v2 only
|
||||
"""terminal_menu.py - Default plugins for the terminal menu"""
|
||||
import gtk
|
||||
import terminatorlib.plugin as plugin
|
||||
|
||||
# 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 = []
|
||||
|
||||
class MyFirstMenuItem(plugin.MenuItem):
|
||||
"""Simple proof of concept"""
|
||||
capabilities = ['terminal_menu']
|
||||
|
||||
def callback(self, menuitems, menu, terminal):
|
||||
"""Add our menu items to the menu"""
|
||||
item = gtk.MenuItem('Some Menu Text')
|
||||
menuitems.append(item)
|
||||
|
11
terminatorlib/plugins/testplugin.py
Normal file
11
terminatorlib/plugins/testplugin.py
Normal file
@ -0,0 +1,11 @@
|
||||
import terminatorlib.plugin as plugin
|
||||
|
||||
# available must contain a list of all the classes that you want exposed
|
||||
available = ['TestPlugin']
|
||||
|
||||
class TestPlugin(plugin.Plugin):
|
||||
capabilities = ['test']
|
||||
|
||||
def do_test(self):
|
||||
return('TestPluginWin')
|
||||
|
53
terminatorlib/plugins/url_handlers.py
Normal file
53
terminatorlib/plugins/url_handlers.py
Normal file
@ -0,0 +1,53 @@
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net?
|
||||
# GPL v2 only
|
||||
"""url_handlers.py - Default plugins for URL handling"""
|
||||
import re
|
||||
import terminatorlib.plugin as plugin
|
||||
|
||||
# Every plugin you want Terminator to load *must* be listed in 'available'
|
||||
available = ['LaunchpadBugURLHandler', 'LaunchpadCodeURLHandler', 'APTURLHandler']
|
||||
|
||||
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"""
|
||||
capabilities = ['url_handler']
|
||||
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"""
|
||||
for item in re.findall(r'[0-9]+', url):
|
||||
url = 'https://bugs.launchpad.net/bugs/%s' % item
|
||||
return(url)
|
||||
|
||||
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']
|
||||
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(plugin.URLHandler):
|
||||
"""APT URL handler. If there is a URL that looks like an apturl, handle
|
||||
it appropriately"""
|
||||
capabilities = ['url_handler']
|
||||
handler_name = 'apturl'
|
||||
match = '\\bapt:.*\\b'
|
||||
|
||||
def callback(self, url):
|
||||
"""Actually we don't need to do anything for this to work"""
|
||||
return(url)
|
||||
|
2229
terminatorlib/preferences.glade
Normal file
2229
terminatorlib/preferences.glade
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,442 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
from terminatorlib.config import dbg,err,DEFAULTS,TerminatorConfValuestoreRC
|
||||
from terminatorlib.keybindings import TerminatorKeybindings, KeymapError
|
||||
from terminatorlib.version import APP_NAME, APP_VERSION
|
||||
from terminatorlib import translation
|
||||
|
||||
import gtk, gobject
|
||||
|
||||
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']
|
||||
|
||||
# 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', ''],
|
||||
}
|
||||
|
||||
# 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']
|
||||
|
||||
def __init__ (self, term):
|
||||
self.term = term
|
||||
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 ("<i><b>NOTE:</b> These settings will not be saved. See:</i> <tt>man terminator_config</tt>")
|
||||
|
||||
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.warning, False, False)
|
||||
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"))
|
||||
|
||||
def go (self):
|
||||
self.window.show_all ()
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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.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()
|
||||
|
||||
# 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)
|
||||
|
||||
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)
|
||||
self.tkbobj = TerminatorKeybindings()
|
||||
keyval = None
|
||||
mask = None
|
||||
|
||||
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))
|
||||
|
||||
self.treeview = gtk.TreeView(self.liststore)
|
||||
|
||||
cell = gtk.CellRendererText()
|
||||
col = gtk.TreeViewColumn(_("Name"))
|
||||
col.pack_start(cell, True)
|
||||
col.add_attribute(cell, "text", 0)
|
||||
|
||||
self.treeview.append_column(col)
|
||||
|
||||
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)
|
863
terminatorlib/prefseditor.py
Executable file
863
terminatorlib/prefseditor.py
Executable file
@ -0,0 +1,863 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import os
|
||||
import gtk
|
||||
import gobject
|
||||
|
||||
from util import dbg
|
||||
import config
|
||||
from keybindings import Keybindings, KeymapError
|
||||
from translation import _
|
||||
from terminator import Terminator
|
||||
|
||||
# FIXME: We need to check that we have represented all of Config() below
|
||||
class PrefsEditor:
|
||||
config = None
|
||||
keybindings = None
|
||||
window = None
|
||||
builder = None
|
||||
previous_selection = None
|
||||
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}
|
||||
|
||||
keybindingnames = { 'zoom_in' : 'Increase font size',
|
||||
'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',
|
||||
'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
|
||||
self.builder = gtk.Builder()
|
||||
self.keybindings = Keybindings()
|
||||
try:
|
||||
# Figure out where our library is on-disk so we can open our
|
||||
(head, tail) = os.path.split(config.__file__)
|
||||
librarypath = os.path.join(head, 'preferences.glade')
|
||||
gladefile = open(librarypath, '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 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()
|
||||
terminator = Terminator()
|
||||
terminator.reconfigure()
|
||||
self.window.destroy()
|
||||
del(self)
|
||||
|
||||
def set_values(self):
|
||||
"""Update the preferences window with all the configuration from
|
||||
Config()"""
|
||||
guiget = self.builder.get_object
|
||||
|
||||
## Global tab
|
||||
# Mouse focus
|
||||
focus = self.config['focus']
|
||||
active = 0
|
||||
if focus == 'click':
|
||||
active = 1
|
||||
elif focus in ['sloppy', 'mouse']:
|
||||
active = 2
|
||||
widget = guiget('focuscombo')
|
||||
widget.set_active(active)
|
||||
# Terminal separator size
|
||||
termsepsize = self.config['handle_size']
|
||||
widget = guiget('handlesize')
|
||||
widget.set_value(float(termsepsize))
|
||||
# Window geometry hints
|
||||
geomhint = self.config['geometry_hinting']
|
||||
widget = guiget('wingeomcheck')
|
||||
widget.set_active(geomhint)
|
||||
# Window state
|
||||
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
|
||||
widget = guiget('winbordercheck')
|
||||
widget.set_active(not self.config['borderless'])
|
||||
# Tab bar position
|
||||
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'])
|
||||
|
||||
## Layouts tab
|
||||
# FIXME: Implement this
|
||||
|
||||
## Keybindings tab
|
||||
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, self.keybindingnames[keybinding],
|
||||
keyval, mask])
|
||||
|
||||
## 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'] = int(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
|
||||
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)
|
||||
guiget = self.builder.get_object
|
||||
|
||||
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'])
|
||||
self.on_system_font_checkbutton_toggled(widget)
|
||||
# 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'])
|
||||
|
||||
## 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'])
|
||||
self.on_use_custom_command_checkbutton_toggled(widget)
|
||||
# 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)
|
||||
# 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
|
||||
self.update_background_tab()
|
||||
# Background image file
|
||||
if self.config['background_image'] != '':
|
||||
widget = guiget('background-image-filechooser')
|
||||
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'])
|
||||
# Background shading
|
||||
widget = guiget('background_darkness_scale')
|
||||
widget.set_value(float(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 in ['disabled', 'hidden']:
|
||||
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)
|
||||
# Delete key
|
||||
widget = guiget('delete-binding-combobox')
|
||||
value = self.config['delete_binding']
|
||||
if value == 'control-h':
|
||||
widget.set_active(0)
|
||||
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"""
|
||||
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()
|
||||
# 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
|
||||
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'] = int(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 = 1
|
||||
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.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):
|
||||
"""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"""
|
||||
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"""
|
||||
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()
|
||||
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"""
|
||||
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)
|
||||
|
||||
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
|
||||
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 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 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] != '':
|
||||
label_text = self.data[key][0]
|
||||
else:
|
||||
label_text = key.replace ('_', ' ').capitalize ()
|
||||
return label_text
|
||||
|
||||
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
|
||||
|
||||
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 = gtk.TreeView(self.liststore)
|
||||
|
||||
cell = gtk.CellRendererText()
|
||||
col = gtk.TreeViewColumn(_("Name"))
|
||||
col.pack_start(cell, True)
|
||||
col.add_attribute(cell, "text", 0)
|
||||
|
||||
self.treeview.append_column(col)
|
||||
|
||||
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)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import terminal
|
||||
term = terminal.Terminal()
|
||||
foo = PrefsEditor(term)
|
||||
|
||||
gtk.main()
|
11
terminatorlib/pylint.sh
Executable file
11
terminatorlib/pylint.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
for file in *.py; do
|
||||
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
|
194
terminatorlib/searchbar.py
Executable file
194
terminatorlib/searchbar.py
Executable file
@ -0,0 +1,194 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net>
|
||||
# GPL v2 only
|
||||
"""searchbar.py - classes necessary to provide a terminal search bar"""
|
||||
|
||||
import gtk
|
||||
import gobject
|
||||
|
||||
from translation import _
|
||||
from config import Config
|
||||
|
||||
# pylint: disable-msg=R0904
|
||||
class Searchbar(gtk.HBox):
|
||||
"""Class implementing the Searchbar widget"""
|
||||
|
||||
__gsignals__ = {
|
||||
'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)
|
||||
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.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)
|
||||
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()
|
||||
self.set_no_show_all(True)
|
||||
|
||||
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"""
|
||||
key = gtk.gdk.keyval_name(event.keyval)
|
||||
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"""
|
||||
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.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"""
|
||||
startrow,endrow = self.get_vte_buffer_range()
|
||||
while True:
|
||||
if self.searchrow == endrow:
|
||||
self.searchrow = startrow
|
||||
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
|
||||
|
||||
# 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"""
|
||||
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"""
|
||||
self.reslabel.set_text("%s %d" % (_('Found at row'), row))
|
||||
self.get_parent().scrollbar_jump(row)
|
||||
self.next.show()
|
||||
self.prev.show()
|
||||
|
||||
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.searchrow = 0
|
||||
self.searchstring = None
|
||||
self.reslabel.set_text('')
|
||||
self.emit('end-search')
|
||||
|
||||
def get_search_term(self):
|
||||
"""Return the currently set search term"""
|
||||
return(self.entry.get_text())
|
||||
|
||||
gobject.type_register(Searchbar)
|
57
terminatorlib/signalman.py
Executable file
57
terminatorlib/signalman.py
Executable file
@ -0,0 +1,57 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net>
|
||||
# GPL v2 only
|
||||
"""Simple management of Gtk Widget signal handlers"""
|
||||
|
||||
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. 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)
|
||||
|
||||
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.has_key(widget):
|
||||
dbg('%s is not registered' % widget)
|
||||
return
|
||||
if not self.cnxids[widget].has_key(signal):
|
||||
dbg('%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)
|
||||
|
1279
terminatorlib/terminal.py
Executable file
1279
terminatorlib/terminal.py
Executable file
File diff suppressed because it is too large
Load Diff
241
terminatorlib/terminal_popup_menu.py
Executable file
241
terminatorlib/terminal_popup_menu.py
Executable file
@ -0,0 +1,241 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net>
|
||||
# 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 _
|
||||
from encoding import TerminatorEncoding
|
||||
from util import err
|
||||
from config import Config
|
||||
from prefseditor import PrefsEditor
|
||||
import plugin
|
||||
|
||||
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())
|
||||
else:
|
||||
item = gtk.MenuItem(_('_Restore all terminals'))
|
||||
item.connect('activate', terminal.unzoom)
|
||||
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.MenuItem(_('_Preferences'))
|
||||
item.connect('activate', lambda x: PrefsEditor(self.terminal))
|
||||
menu.append(item)
|
||||
|
||||
config = Config()
|
||||
profilelist = config.list_profiles()
|
||||
|
||||
if len(profilelist) > 1:
|
||||
item = gtk.MenuItem(_('Profiles'))
|
||||
submenu = gtk.Menu()
|
||||
item.set_submenu(submenu)
|
||||
menu.append(item)
|
||||
|
||||
current = terminal.get_profile()
|
||||
|
||||
group = None
|
||||
|
||||
for profile in profilelist:
|
||||
item = gtk.RadioMenuItem(group, profile.capitalize())
|
||||
if profile == current:
|
||||
item.set_active(True)
|
||||
item.connect('activate', terminal.set_profile, profile)
|
||||
submenu.append(item)
|
||||
|
||||
self.add_encoding_items(menu)
|
||||
|
||||
try:
|
||||
menuitems = []
|
||||
registry = plugin.PluginRegistry()
|
||||
registry.load_plugins()
|
||||
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:
|
||||
err('TerminalPopupMenu::show: %s' % ex)
|
||||
|
||||
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)
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
0
terminatorlib/tests/__init__.py
Normal file
0
terminatorlib/tests/__init__.py
Normal file
17
terminatorlib/tests/test_doctests.py
Normal file
17
terminatorlib/tests/test_doctests.py
Normal file
@ -0,0 +1,17 @@
|
||||
"""Load up the tests."""
|
||||
from unittest import TestSuite
|
||||
from doctest import DocTestSuite, ELLIPSIS
|
||||
|
||||
def test_suite():
|
||||
suite = TestSuite()
|
||||
for name in (
|
||||
'config',
|
||||
'plugin',
|
||||
'cwd',
|
||||
'factory',
|
||||
'util',
|
||||
'tests.testborg',
|
||||
'tests.testsignalman',
|
||||
):
|
||||
suite.addTest(DocTestSuite('terminatorlib.' + name))
|
||||
return suite
|
55
terminatorlib/tests/testborg.py
Executable file
55
terminatorlib/tests/testborg.py
Executable file
@ -0,0 +1,55 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net>
|
||||
# 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
|
||||
|
56
terminatorlib/tests/testsignalman.py
Executable file
56
terminatorlib/tests/testsignalman.py
Executable file
@ -0,0 +1,56 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net>
|
||||
# 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)
|
207
terminatorlib/titlebar.py
Executable file
207
terminatorlib/titlebar.py
Executable file
@ -0,0 +1,207 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net>
|
||||
# GPL v2 only
|
||||
"""titlebar.py - classes necessary to provide a terminal title bar"""
|
||||
|
||||
import gtk
|
||||
import gobject
|
||||
|
||||
from version import APP_NAME
|
||||
from util import dbg
|
||||
from terminator import Terminator
|
||||
from editablelabel import EditableLabel
|
||||
|
||||
# pylint: disable-msg=R0904
|
||||
# pylint: disable-msg=W0613
|
||||
class Titlebar(gtk.EventBox):
|
||||
"""Class implementing the Titlebar widget"""
|
||||
|
||||
terminator = None
|
||||
terminal = None
|
||||
config = None
|
||||
oldtitle = None
|
||||
termtext = None
|
||||
sizetext = None
|
||||
label = None
|
||||
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, 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)
|
||||
self.ebox = gtk.EventBox()
|
||||
grouphbox = gtk.HBox()
|
||||
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'
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
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(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"""
|
||||
self.ebox.connect('button-release-event', func)
|
||||
|
||||
def update(self, other=None):
|
||||
"""Update our contents"""
|
||||
self.label.set_text("%s %s" % (self.termtext, self.sizetext))
|
||||
|
||||
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"""
|
||||
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)
|
||||
self.update()
|
||||
|
||||
def set_terminal_title(self, widget, title):
|
||||
"""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"""
|
||||
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"""
|
||||
self.emit('clicked')
|
||||
|
||||
def on_edit_done(self, widget):
|
||||
"""Re-emit an edit-done signal from an EditableLabel"""
|
||||
self.emit('edit-done')
|
||||
|
||||
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"""
|
||||
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()
|
||||
self.get_parent().grab_focus()
|
||||
|
||||
def groupentry_activate(self, widget):
|
||||
"""Actually cause a group to be created"""
|
||||
groupname = self.groupentry.get_text()
|
||||
dbg('Titlebar::groupentry_activate: 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)
|
@ -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
|
||||
@ -17,16 +17,22 @@
|
||||
|
||||
"""Terminator by Chris Jones <cmsj@tenshu.net>"""
|
||||
|
||||
from terminatorlib.version import APP_NAME
|
||||
from version import APP_NAME
|
||||
from util import dbg
|
||||
|
||||
_ = None
|
||||
|
||||
# pylint: disable-msg=W0702
|
||||
try:
|
||||
import gettext
|
||||
gettext.install (APP_NAME)
|
||||
except ImportError:
|
||||
print "Using fallback _()"
|
||||
import __builtin__
|
||||
def dummytrans (text):
|
||||
"""A _ function for systems without gettext. Effectively a NOOP"""
|
||||
return text
|
||||
__builtin__.__dict__['_'] = dummytrans
|
||||
gettext.textdomain(APP_NAME)
|
||||
_ = gettext.gettext
|
||||
except:
|
||||
dbg("Using fallback _()")
|
||||
|
||||
def dummytrans (text):
|
||||
"""A _ function for systems without gettext. Effectively a NOOP"""
|
||||
return(text)
|
||||
|
||||
_ = dummytrans
|
||||
|
||||
|
228
terminatorlib/util.py
Executable file
228
terminatorlib/util.py
Executable file
@ -0,0 +1,228 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator.util - misc utility functions
|
||||
# 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
|
||||
# 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.util - misc utility functions
|
||||
|
||||
>>> a = {'foo': 'bar', 'baz': 'bjonk'}
|
||||
>>> b = {'foo': 'far', 'baz': 'bjonk'}
|
||||
>>> dict_diff(a, b)
|
||||
{'foo': 'far'}
|
||||
|
||||
"""
|
||||
|
||||
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:
|
||||
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"""
|
||||
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"""
|
||||
|
||||
dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL,
|
||||
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message)
|
||||
dialog.run()
|
||||
|
||||
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, 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):
|
||||
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')
|
||||
|
||||
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)
|
||||
|
||||
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'))
|
||||
|
||||
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)
|
||||
|
||||
# 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)
|
||||
|
||||
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)
|
||||
|
@ -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
|
||||
@ -21,4 +21,4 @@ TerminatorVersion supplies our version number.
|
||||
"""
|
||||
|
||||
APP_NAME = 'terminator'
|
||||
APP_VERSION = '0.14'
|
||||
APP_VERSION = '0.90'
|
||||
|
495
terminatorlib/window.py
Executable file
495
terminatorlib/window.py
Executable file
@ -0,0 +1,495 @@
|
||||
#!/usr/bin/python
|
||||
# Terminator by Chris Jones <cmsj@tenshu.net>
|
||||
# GPL v2 only
|
||||
"""window.py - class for the main Terminator window"""
|
||||
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
import gobject
|
||||
import gtk
|
||||
import glib
|
||||
|
||||
from util import dbg, err
|
||||
from translation import _
|
||||
from version import APP_NAME
|
||||
from container import Container
|
||||
from factory import Factory
|
||||
from terminator import Terminator
|
||||
|
||||
try:
|
||||
import deskbar.core.keybinder as bindkey
|
||||
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"""
|
||||
|
||||
terminator = None
|
||||
title = None
|
||||
isfullscreen = None
|
||||
ismaximised = None
|
||||
hidebound = None
|
||||
hidefunc = None
|
||||
|
||||
zoom_data = None
|
||||
term_zoomed = gobject.property(type=bool, default=False)
|
||||
|
||||
def __init__(self):
|
||||
"""Class initialiser"""
|
||||
self.terminator = Terminator()
|
||||
self.terminator.register_window(self)
|
||||
|
||||
Container.__init__(self)
|
||||
gtk.Window.__init__(self)
|
||||
gobject.type_register(Window)
|
||||
self.register_signals(Window)
|
||||
|
||||
self.set_property('allow-shrink', True)
|
||||
self.apply_icon()
|
||||
|
||||
self.register_callbacks()
|
||||
self.apply_config()
|
||||
|
||||
self.title = WindowTitle(self)
|
||||
self.title.update()
|
||||
|
||||
options = self.config.options_get()
|
||||
if options:
|
||||
if options.forcedtitle is not None:
|
||||
self.title.force_title(options.forcedtitle)
|
||||
|
||||
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"""
|
||||
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)
|
||||
|
||||
# Attempt to grab a global hotkey for hiding the window.
|
||||
# If we fail, we'll never hide the window, iconifying instead.
|
||||
try:
|
||||
self.hidebound = bindkey.tomboy_keybinder_bind(
|
||||
self.config['keybindings']['hide_window'],
|
||||
self.on_hide_window)
|
||||
except (KeyError, NameError):
|
||||
pass
|
||||
|
||||
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"""
|
||||
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(hidden)
|
||||
else:
|
||||
self.set_iconified(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, glib.GError):
|
||||
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"""
|
||||
maker = Factory()
|
||||
|
||||
self.set_urgency_hint(False)
|
||||
|
||||
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))
|
||||
elif mapping == 'new_tab':
|
||||
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', window=self)
|
||||
self.get_child().newtab()
|
||||
|
||||
def on_delete_event(self, window, event, data=None):
|
||||
"""Handle a window close request"""
|
||||
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')))
|
||||
|
||||
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)
|
||||
self.destroy()
|
||||
del(self)
|
||||
|
||||
def on_hide_window(self, data):
|
||||
"""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 &
|
||||
gtk.gdk.WINDOW_STATE_FULLSCREEN)
|
||||
self.ismaximised = bool(event.new_window_state &
|
||||
gtk.gdk.WINDOW_STATE_MAXIMIZED)
|
||||
dbg('Window::on_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=True):
|
||||
"""Enable RGBA if supported on the current screen"""
|
||||
screen = self.get_screen()
|
||||
if value:
|
||||
colormap = screen.get_rgba_colormap()
|
||||
else:
|
||||
colormap = screen.get_rgb_colormap()
|
||||
|
||||
if colormap:
|
||||
self.set_colormap(colormap)
|
||||
|
||||
def add(self, widget):
|
||||
"""Add a widget to the window by way of gtk.Window.add()"""
|
||||
maker = Factory()
|
||||
gtk.Window.add(self, widget)
|
||||
if maker.isinstance(widget, 'Terminal'):
|
||||
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.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):
|
||||
"""Remove our child widget by way of gtk.Window.remove()"""
|
||||
gtk.Window.remove(self, widget)
|
||||
self.disconnect_child(widget)
|
||||
return(True)
|
||||
|
||||
def split_axis(self, widget, vertical=True, sibling=None):
|
||||
"""Split the window"""
|
||||
maker = Factory()
|
||||
self.remove(widget)
|
||||
|
||||
if vertical:
|
||||
container = maker.make('VPaned')
|
||||
else:
|
||||
container = maker.make('HPaned')
|
||||
|
||||
if not sibling:
|
||||
sibling = maker.make('Terminal')
|
||||
self.add(container)
|
||||
container.show_all()
|
||||
|
||||
for term in [widget, sibling]:
|
||||
container.add(term)
|
||||
container.show_all()
|
||||
|
||||
sibling.spawn_child()
|
||||
|
||||
def zoom(self, widget, font_scale=True):
|
||||
"""Zoom a terminal widget"""
|
||||
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.cnxids.new(widget, 'size-allocate',
|
||||
widget.zoom_scale, self.zoom_data)
|
||||
|
||||
widget.grab_focus()
|
||||
|
||||
def unzoom(self, widget):
|
||||
"""Restore normal terminal layout"""
|
||||
if not self.get_property('term_zoomed'):
|
||||
# We're not zoomed anyway
|
||||
dbg('Window::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)
|
||||
|
||||
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.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)
|
||||
|
||||
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())
|
||||
|
||||
# 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"""
|
||||
|
||||
window = None
|
||||
text = None
|
||||
forced = None
|
||||
|
||||
def __init__(self, window):
|
||||
"""Class initialiser"""
|
||||
self.window = window
|
||||
self.forced = False
|
||||
|
||||
def set_title(self, widget, text):
|
||||
"""Set the title"""
|
||||
if not self.forced:
|
||||
self.text = text
|
||||
self.update()
|
||||
|
||||
def force_title(self, newtext):
|
||||
"""Force a specific title"""
|
||||
if newtext:
|
||||
self.set_title(None, 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)
|
||||
|
||||
# vim: set expandtab ts=4 sw=4:
|
Loading…
Reference in New Issue
Block a user