Land epic-refactor branch after 5.5 months of work
This commit is contained in:
commit
103428d0e3
|
@ -2,3 +2,6 @@
|
||||||
terminatorlib/*.pyc
|
terminatorlib/*.pyc
|
||||||
.project
|
.project
|
||||||
.pydevproject
|
.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
|
If you don't have this option, please make sure you satisfy Terminator's
|
||||||
dependencies yourself:
|
dependencies yourself:
|
||||||
|
|
||||||
* Python 2.4+, 2.6 recommended:
|
* Python 2.5+, 2.6 recommended:
|
||||||
Debian/Ubuntu: python
|
Debian/Ubuntu: python
|
||||||
FreeBSD: lang/python26
|
FreeBSD: lang/python26
|
||||||
|
|
||||||
|
@ -15,12 +15,6 @@ dependencies yourself:
|
||||||
Debian/Ubuntu: python-vte
|
Debian/Ubuntu: python-vte
|
||||||
FreeBSD: x11-toolkits/py-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
|
If you don't care about native language support or icons, Terminator
|
||||||
should run just fine directly from this directory, just:
|
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.
|
by Chris Jones <cmsj@tenshu.net> and others.
|
||||||
|
|
||||||
The goal of this project is to produce a useful tool for arranging terminals.
|
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
|
|
|
@ -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
|
terminator (0.14) karmic; urgency=low
|
||||||
|
|
||||||
* New upstream release
|
* New upstream release
|
||||||
|
|
|
@ -14,7 +14,7 @@ Homepage: http://www.tenshu.net/terminator/
|
||||||
|
|
||||||
Package: terminator
|
Package: terminator
|
||||||
Architecture: all
|
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}
|
XB-Python-Version: ${python:Versions}
|
||||||
Provides: x-terminal-emulator
|
Provides: x-terminal-emulator
|
||||||
Recommends: xdg-utils, python-xdg, python-gnome2, deskbar-applet
|
Recommends: xdg-utils, python-xdg, python-gnome2, deskbar-applet
|
||||||
|
|
|
@ -33,13 +33,14 @@ Translations:
|
||||||
"Data"
|
"Data"
|
||||||
Cristian Grada
|
Cristian Grada
|
||||||
"zhuqin"
|
"zhuqin"
|
||||||
|
and many others.
|
||||||
|
|
||||||
Seriously, thank you very much to the translators. A few minutes of
|
Seriously, thank you very much to the translators. A few minutes of
|
||||||
their dedication opens up userbases like nothing else.
|
their dedication opens up userbases like nothing else.
|
||||||
|
|
||||||
Copyright:
|
Copyright:
|
||||||
|
|
||||||
<Copyright (C) 2006-2008 Chris Jones>
|
<Copyright (C) 2006-2010 Chris Jones and others>
|
||||||
|
|
||||||
License:
|
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
|
Force the Terminator window to use a specific name rather than updating it dynamically
|
||||||
based on the wishes of the child shell.
|
based on the wishes of the child shell.
|
||||||
.TP
|
.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
|
.B \-\-geometry=GEOMETRY
|
||||||
Specifies the preferred size and position of Terminator's window; see X(7).
|
Specifies the preferred size and position of Terminator's window; see X(7).
|
||||||
.TP
|
.TP
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
~/.config/terminator/config \- the config file for Terminator terminal emulator.
|
~/.config/terminator/config \- the config file for Terminator terminal emulator.
|
||||||
.SH "DESCRIPTION"
|
.SH "DESCRIPTION"
|
||||||
This manual page documents briefly the
|
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
|
.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.
|
\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"
|
.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.
|
If true, a titlebar will be drawn on zoomed/maximised terminals which indicates how many are hidden.
|
||||||
Default value: \fBTrue\fR
|
Default value: \fBTrue\fR
|
||||||
.TP
|
.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
|
.B title_tx_txt_color
|
||||||
Sets the colour of the text shown in the titlebar of the active terminal.
|
Sets the colour of the text shown in the titlebar of the active terminal.
|
||||||
Default value: \fB#FFFFFF\fR
|
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.install_data import install_data
|
||||||
from distutils.command.build import build
|
from distutils.command.build import build
|
||||||
from distutils.dep_util import newer
|
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 glob
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
from terminatorlib.version import *
|
from terminatorlib.version import APP_NAME, APP_VERSION
|
||||||
|
|
||||||
PO_DIR = 'po'
|
PO_DIR = 'po'
|
||||||
MO_DIR = os.path.join('build', 'mo')
|
MO_DIR = os.path.join('build', 'mo')
|
||||||
|
@ -83,6 +84,8 @@ class Uninstall(Command):
|
||||||
self.ensure_filename('manifest')
|
self.ensure_filename('manifest')
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
|
if not self.manifest:
|
||||||
|
raise DistutilsFileError("Pass manifest with --manifest=file")
|
||||||
f = open(self.manifest)
|
f = open(self.manifest)
|
||||||
files = [file.strip() for file in f]
|
files = [file.strip() for file in f]
|
||||||
except IOError, e:
|
except IOError, e:
|
||||||
|
@ -152,7 +155,7 @@ if platform.system() == 'FreeBSD':
|
||||||
else:
|
else:
|
||||||
man_dir = 'share/man'
|
man_dir = 'share/man'
|
||||||
|
|
||||||
setup(name='Terminator',
|
setup(name=APP_NAME.capitalize(),
|
||||||
version=APP_VERSION,
|
version=APP_VERSION,
|
||||||
description='Terminator, the robot future of terminals',
|
description='Terminator, the robot future of terminals',
|
||||||
author='Chris Jones',
|
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/48x48/apps', glob.glob('data/icons/48x48/apps/*.png')),
|
||||||
('share/icons/hicolor/16x16/actions', glob.glob('data/icons/16x16/actions/*.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},
|
cmdclass={'build': BuildData, 'install_data': InstallData, 'uninstall': Uninstall},
|
||||||
distclass=TerminatorDist
|
distclass=TerminatorDist
|
||||||
)
|
)
|
||||||
|
|
170
terminator
170
terminator
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# Terminator - multiple gnome terminals in one window
|
# 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
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,161 +18,57 @@
|
||||||
|
|
||||||
"""Terminator by Chris Jones <cmsj@tenshu.net>"""
|
"""Terminator by Chris Jones <cmsj@tenshu.net>"""
|
||||||
|
|
||||||
# import standard python libs
|
import sys
|
||||||
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
|
|
||||||
|
|
||||||
|
# Check we have simple basics like Gtk+ and a valid $DISPLAY
|
||||||
try:
|
try:
|
||||||
import pygtk
|
import pygtk
|
||||||
pygtk.require ("2.0")
|
pygtk.require ("2.0")
|
||||||
|
# pylint: disable-msg=W0611
|
||||||
import gobject, gtk, pango
|
import gtk, pango, gobject
|
||||||
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:
|
if gtk.gdk.display_get_default() == None:
|
||||||
err (_("You need to run terminator in an X environment. " \
|
print('You need to run terminator in an X environment. ' \
|
||||||
"Make sure DISPLAY is properly set"))
|
'Make sure $DISPLAY is properly set')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if options.working_directory:
|
except ImportError:
|
||||||
if os.path.exists (os.path.expanduser (options.working_directory)):
|
print('You need to install the python bindings for ' \
|
||||||
os.chdir (os.path.expanduser (options.working_directory))
|
'gobject, gtk and pango to run Terminator.')
|
||||||
else:
|
|
||||||
err (_("The working directory you specified does not exist."))
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
try:
|
import terminatorlib.optionparse
|
||||||
open (os.path.expanduser ('~/.config/terminator/config'))
|
from terminatorlib.terminator import Terminator
|
||||||
except IOError:
|
from terminatorlib.factory import Factory
|
||||||
try:
|
from terminatorlib.version import APP_NAME, APP_VERSION
|
||||||
open (os.path.expanduser ('~/.terminatorrc'))
|
from terminatorlib.util import dbg
|
||||||
error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR,
|
|
||||||
gtk.BUTTONS_OK, ('''You have a configuration file:
|
|
||||||
|
|
||||||
~/.terminatorrc.
|
if __name__ == '__main__':
|
||||||
|
dbg ("%s starting up, version %s" % (APP_NAME, APP_VERSION))
|
||||||
|
|
||||||
Please be aware that this file needs to be moved to:
|
OPTIONS = terminatorlib.optionparse.parse_options()
|
||||||
|
|
||||||
~/.config/terminator/config.
|
MAKER = Factory()
|
||||||
|
TERMINATOR = Terminator()
|
||||||
|
TERMINATOR.reconfigure()
|
||||||
|
WINDOW = MAKER.make('Window')
|
||||||
|
TERMINAL = MAKER.make('Terminal')
|
||||||
|
|
||||||
See the following bug report for more details:
|
WINDOW.add(TERMINAL)
|
||||||
|
WINDOW.show()
|
||||||
|
TERMINAL.spawn_child()
|
||||||
|
|
||||||
https://bugs.launchpad.net/bugs/238070'''))
|
if OPTIONS.debug > 2:
|
||||||
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 terminatorlib.debugserver as debugserver
|
||||||
|
# pylint: disable-msg=W0611
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
gtk.gdk.threads_init()
|
gtk.gdk.threads_init()
|
||||||
(debugthread, debugsvr) = debugserver.spawn(locals())
|
(DEBUGTHREAD, DEBUGSVR) = debugserver.spawn(locals())
|
||||||
term.debugaddress = debugsvr.server_address
|
TERMINATOR.debugaddress = DEBUGSVR.server_address
|
||||||
|
|
||||||
|
try:
|
||||||
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()")}
|
%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
|
||||||
|
|
||||||
Name: terminator
|
Name: terminator
|
||||||
Version: 0.14
|
Version: 0.90
|
||||||
Release: 3%{?dist}
|
Release: 3%{?dist}
|
||||||
Summary: Store and run multiple GNOME terminals in one window
|
Summary: Store and run multiple GNOME terminals in one window
|
||||||
|
|
||||||
|
@ -52,6 +52,7 @@ rm -rf %{buildroot}
|
||||||
%{_mandir}/man5/%{name}_config.*
|
%{_mandir}/man5/%{name}_config.*
|
||||||
%{_bindir}/%{name}
|
%{_bindir}/%{name}
|
||||||
%{python_sitelib}/*
|
%{python_sitelib}/*
|
||||||
|
%{_datadir}/terminator/preferences.glade
|
||||||
%{_datadir}/applications/%{name}.desktop
|
%{_datadir}/applications/%{name}.desktop
|
||||||
%{_datadir}/icons/hicolor/*/*/%{name}*.png
|
%{_datadir}/icons/hicolor/*/*/%{name}*.png
|
||||||
%{_datadir}/icons/hicolor/*/*/%{name}*.svg
|
%{_datadir}/icons/hicolor/*/*/%{name}*.svg
|
||||||
|
@ -67,6 +68,10 @@ gtk-update-icon-cache -qf %{_datadir}/icons/hicolor &>/dev/null || :
|
||||||
|
|
||||||
|
|
||||||
%changelog
|
%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
|
* Thu Jan 15 2009 Chris Jones <cmsj@tenshu.net> 0.12-1
|
||||||
- Remove patch application since this isn't a fedora build.
|
- Remove patch application since this isn't a fedora build.
|
||||||
Note that this specfile is untested.
|
Note that this specfile is untested.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# Terminator - multiple gnome terminals in one window
|
# 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
|
# 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
|
# 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
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
"""Terminator by Chris Jones <cmsj@tenshu.net>"""
|
"""Terminator by Chris Jones <cmsj@tenshu.net>"""
|
||||||
|
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# TerminatorConfig - layered config classes
|
# TerminatorConfig - layered config classes
|
||||||
# Copyright (C) 2006-2008 cmsj@tenshu.net
|
# Copyright (C) 2006-2010 cmsj@tenshu.net
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -15,83 +15,164 @@
|
||||||
# along with this program; if not, write to the Free Software
|
# along with this program; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
"""TerminatorConfig by Chris Jones <cmsj@tenshu.net>
|
"""Terminator by Chris Jones <cmsj@tenshu.net>
|
||||||
|
|
||||||
The config scheme works in layers, with defaults at the base,
|
Classes relating to configuration
|
||||||
and a simple/flexible class which can be placed over the top
|
|
||||||
in multiple layers. This was written for Terminator, but
|
>>> DEFAULTS['global_config']['focus']
|
||||||
could be used generically. Its original use is to guarantee
|
'click'
|
||||||
default values for any config item, while allowing them to be
|
>>> config = Config()
|
||||||
overridden by at least two other stores of configuration values.
|
>>> config['focus'] = 'sloppy'
|
||||||
Those being gconf and a plain config file.
|
>>> config['focus']
|
||||||
In addition to the value, the default layer must also provide
|
'sloppy'
|
||||||
the datatype (str, int, float and bool are currently supported).
|
>>> DEFAULTS['global_config']['focus']
|
||||||
values are found as attributes of the TerminatorConfig object.
|
'click'
|
||||||
Trying to read a value that doesn't exist will raise an
|
>>> config2 = Config()
|
||||||
AttributeError. This is by design. If you want to look something
|
>>> config2['focus']
|
||||||
up, set a default for it first."""
|
'sloppy'
|
||||||
|
>>> config2['focus'] = 'click'
|
||||||
|
>>> config2['focus']
|
||||||
|
'click'
|
||||||
|
>>> config['focus']
|
||||||
|
'click'
|
||||||
|
>>> config['geometry_hinting'].__class__.__name__
|
||||||
|
'bool'
|
||||||
|
>>> plugintest = {}
|
||||||
|
>>> plugintest['foo'] = 'bar'
|
||||||
|
>>> config.plugin_set_config('testplugin', plugintest)
|
||||||
|
>>> config.plugin_get_config('testplugin')
|
||||||
|
{'foo': 'bar'}
|
||||||
|
>>> config.plugin_get('testplugin', 'foo')
|
||||||
|
'bar'
|
||||||
|
>>> config.get_profile()
|
||||||
|
'default'
|
||||||
|
>>> config.set_profile('my_first_new_testing_profile')
|
||||||
|
>>> config.get_profile()
|
||||||
|
'my_first_new_testing_profile'
|
||||||
|
>>> config.del_profile('my_first_new_testing_profile')
|
||||||
|
>>> config.get_profile()
|
||||||
|
'default'
|
||||||
|
>>> config.list_profiles().__class__.__name__
|
||||||
|
'list'
|
||||||
|
>>> config.options_set({})
|
||||||
|
>>> config.options_get()
|
||||||
|
{}
|
||||||
|
>>>
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import platform
|
import platform
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import re
|
from copy import copy
|
||||||
import pwd
|
from configobj.configobj import ConfigObj, flatten_errors
|
||||||
import gtk
|
from configobj.validate import Validator
|
||||||
import pango
|
from borg import Borg
|
||||||
|
from util import dbg, err, DEBUG, get_config_dir, dict_diff
|
||||||
try:
|
|
||||||
import gconf
|
|
||||||
except ImportError:
|
|
||||||
gconf = None
|
|
||||||
|
|
||||||
from terminatorlib import translation
|
|
||||||
|
|
||||||
# set this to true to enable debugging output
|
|
||||||
# These should be moved somewhere better.
|
|
||||||
debug = False
|
|
||||||
|
|
||||||
def dbg (log = ""):
|
|
||||||
"""Print a message if debugging is enabled"""
|
|
||||||
if debug:
|
|
||||||
print >> sys.stderr, log
|
|
||||||
|
|
||||||
def err (log = ""):
|
|
||||||
"""Print an error message"""
|
|
||||||
print >> sys.stderr, log
|
|
||||||
|
|
||||||
from terminatorlib.configfile import ConfigFile, ParsedWithErrors
|
|
||||||
|
|
||||||
DEFAULTS = {
|
DEFAULTS = {
|
||||||
'gt_dir' : '/apps/gnome-terminal',
|
'global_config': {
|
||||||
'profile_dir' : '/apps/gnome-terminal/profiles',
|
'focus' : 'click',
|
||||||
'titlebars' : True,
|
'enable_real_transparency' : True,
|
||||||
'zoomedtitlebar' : True,
|
'handle_size' : -1,
|
||||||
'titletips' : False,
|
'geometry_hinting' : True,
|
||||||
|
'window_state' : 'normal',
|
||||||
|
'borderless' : False,
|
||||||
|
'tab_position' : 'top',
|
||||||
|
'close_button_on_tab' : True,
|
||||||
|
'hide_tabbar' : False,
|
||||||
|
'scroll_tabbar' : False,
|
||||||
|
'try_posix_regexp' : platform.system() != 'Linux',
|
||||||
|
'title_transmit_fg_color' : '#ffffff',
|
||||||
|
'title_transmit_bg_color' : '#c80003',
|
||||||
|
'title_receive_fg_color' : '#ffffff',
|
||||||
|
'title_receive_bg_color' : '#0076c9',
|
||||||
|
'title_inactive_fg_color' : '#000000',
|
||||||
|
'title_inactive_bg_color' : '#c0bebf',
|
||||||
|
'disabled_plugins' : ['TestPlugin', 'CustomCommandsMenu'],
|
||||||
|
},
|
||||||
|
'keybindings': {
|
||||||
|
'zoom_in' : '<Control>plus',
|
||||||
|
'zoom_out' : '<Control>minus',
|
||||||
|
'zoom_normal' : '<Control>0',
|
||||||
|
'new_tab' : '<Shift><Control>t',
|
||||||
|
'cycle_next' : '<Control>Tab',
|
||||||
|
'cycle_prev' : '<Shift><Control>Tab',
|
||||||
|
'go_next' : '<Shift><Control>n',
|
||||||
|
'go_prev' : '<Shift><Control>p',
|
||||||
|
'go_up' : '<Alt>Up',
|
||||||
|
'go_down' : '<Alt>Down',
|
||||||
|
'go_left' : '<Alt>Left',
|
||||||
|
'go_right' : '<Alt>Right',
|
||||||
|
'split_horiz' : '<Shift><Control>o',
|
||||||
|
'split_vert' : '<Shift><Control>e',
|
||||||
|
'close_term' : '<Shift><Control>w',
|
||||||
|
'copy' : '<Shift><Control>c',
|
||||||
|
'paste' : '<Shift><Control>v',
|
||||||
|
'toggle_scrollbar' : '<Shift><Control>s',
|
||||||
|
'search' : '<Shift><Control>f',
|
||||||
|
'close_window' : '<Shift><Control>q',
|
||||||
|
'resize_up' : '<Shift><Control>Up',
|
||||||
|
'resize_down' : '<Shift><Control>Down',
|
||||||
|
'resize_left' : '<Shift><Control>Left',
|
||||||
|
'resize_right' : '<Shift><Control>Right',
|
||||||
|
'move_tab_right' : '<Shift><Control>Page_Down',
|
||||||
|
'move_tab_left' : '<Shift><Control>Page_Up',
|
||||||
|
'toggle_zoom' : '<Shift><Control>x',
|
||||||
|
'scaled_zoom' : '<Shift><Control>z',
|
||||||
|
'next_tab' : '<Control>Page_Down',
|
||||||
|
'prev_tab' : '<Control>Page_Up',
|
||||||
|
'switch_to_tab_1' : '',
|
||||||
|
'switch_to_tab_2' : '',
|
||||||
|
'switch_to_tab_3' : '',
|
||||||
|
'switch_to_tab_4' : '',
|
||||||
|
'switch_to_tab_5' : '',
|
||||||
|
'switch_to_tab_6' : '',
|
||||||
|
'switch_to_tab_7' : '',
|
||||||
|
'switch_to_tab_8' : '',
|
||||||
|
'switch_to_tab_9' : '',
|
||||||
|
'switch_to_tab_10' : '',
|
||||||
|
'full_screen' : 'F11',
|
||||||
|
'reset' : '<Shift><Control>r',
|
||||||
|
'reset_clear' : '<Shift><Control>g',
|
||||||
|
'hide_window' : '<Shift><Control><Alt>a',
|
||||||
|
'group_all' : '<Super>g',
|
||||||
|
'ungroup_all' : '<Shift><Super>g',
|
||||||
|
'group_tab' : '<Super>t',
|
||||||
|
'ungroup_tab' : '<Shift><Super>t',
|
||||||
|
'new_window' : '<Shift><Control>i',
|
||||||
|
},
|
||||||
|
'profiles': {
|
||||||
|
'default': {
|
||||||
'allow_bold' : True,
|
'allow_bold' : True,
|
||||||
'audible_bell' : False,
|
'audible_bell' : False,
|
||||||
'visible_bell' : True,
|
'visible_bell' : True,
|
||||||
'urgent_bell' : False,
|
'urgent_bell' : False,
|
||||||
'background_color' : '#000000',
|
'background_color' : '#000000000000',
|
||||||
'background_darkness' : 0.5,
|
'background_darkness' : 0.5,
|
||||||
'background_type' : 'solid',
|
'background_type' : 'solid',
|
||||||
'background_image' : '',
|
'background_image' : None,
|
||||||
'backspace_binding' : 'ascii-del',
|
'backspace_binding' : 'ascii-del',
|
||||||
'delete_binding' : 'delete-sequence',
|
'delete_binding' : 'escape-sequence',
|
||||||
|
'color_scheme' : 'grey_on_black',
|
||||||
'cursor_blink' : True,
|
'cursor_blink' : True,
|
||||||
'cursor_shape' : 'block',
|
'cursor_shape' : 'block',
|
||||||
'cursor_color' : '',
|
'cursor_color' : '',
|
||||||
'emulation' : 'xterm',
|
'emulation' : 'xterm',
|
||||||
'geometry_hinting' : True,
|
|
||||||
'font' : 'Mono 10',
|
'font' : 'Mono 10',
|
||||||
'foreground_color' : '#AAAAAA',
|
'foreground_color' : '#aaaaaaaaaaaa',
|
||||||
'scrollbar_position' : "right",
|
'scrollbar_position' : "right",
|
||||||
'scroll_background' : True,
|
'scroll_background' : True,
|
||||||
'scroll_on_keystroke' : True,
|
'scroll_on_keystroke' : True,
|
||||||
'scroll_on_output' : True,
|
'scroll_on_output' : True,
|
||||||
'scrollback_lines' : 500,
|
'scrollback_lines' : 500,
|
||||||
'focus' : 'click',
|
|
||||||
'exit_action' : 'close',
|
'exit_action' : 'close',
|
||||||
'palette' : '#000000000000:#CDCD00000000:#0000CDCD0000:#CDCDCDCD0000:#30BF30BFA38E:#A53C212FA53C:#0000CDCDCDCD:#FAFAEBEBD7D7:#404040404040:#FFFF00000000:#0000FFFF0000:#FFFFFFFF0000:#00000000FFFF:#FFFF0000FFFF:#0000FFFFFFFF:#FFFFFFFFFFFF',
|
'palette' :'#000000000000:#CDCD00000000:#0000CDCD0000:\
|
||||||
|
#CDCDCDCD0000:#30BF30BFA38E:#A53C212FA53C:\
|
||||||
|
#0000CDCDCDCD:#FAFAEBEBD7D7:#404040404040:\
|
||||||
|
#FFFF00000000:#0000FFFF0000:#FFFFFFFF0000:\
|
||||||
|
#00000000FFFF:#FFFF0000FFFF:#0000FFFFFFFF:\
|
||||||
|
#FFFFFFFFFFFF',
|
||||||
'word_chars' : '-A-Za-z0-9,./?%&#:_',
|
'word_chars' : '-A-Za-z0-9,./?%&#:_',
|
||||||
'mouse_autohide' : True,
|
'mouse_autohide' : True,
|
||||||
'update_records' : True,
|
'update_records' : True,
|
||||||
|
@ -100,447 +181,347 @@ DEFAULTS = {
|
||||||
'custom_command' : '',
|
'custom_command' : '',
|
||||||
'use_system_font' : True,
|
'use_system_font' : True,
|
||||||
'use_theme_colors' : False,
|
'use_theme_colors' : False,
|
||||||
'http_proxy' : '',
|
|
||||||
'ignore_hosts' : ['localhost','127.0.0.0/8','*.local'],
|
|
||||||
'encoding' : 'UTF-8',
|
'encoding' : 'UTF-8',
|
||||||
'active_encodings' : ['UTF-8', 'ISO-8859-1'],
|
'active_encodings' : ['UTF-8', 'ISO-8859-1'],
|
||||||
'extreme_tabs' : False,
|
|
||||||
'fullscreen' : False,
|
|
||||||
'borderless' : False,
|
|
||||||
'maximise' : False,
|
|
||||||
'hidden' : False,
|
|
||||||
'handle_size' : -1,
|
|
||||||
'focus_on_close' : 'auto',
|
'focus_on_close' : 'auto',
|
||||||
'f11_modifier' : False,
|
|
||||||
'force_no_bell' : False,
|
'force_no_bell' : False,
|
||||||
'cycle_term_tab' : True,
|
'cycle_term_tab' : True,
|
||||||
'copy_on_selection' : False,
|
'copy_on_selection' : False,
|
||||||
'close_button_on_tab' : True,
|
|
||||||
'tab_position' : 'top',
|
|
||||||
'enable_real_transparency' : True,
|
|
||||||
'title_tx_txt_color' : '#FFFFFF',
|
'title_tx_txt_color' : '#FFFFFF',
|
||||||
'title_tx_bg_color' : '#C80003',
|
'title_tx_bg_color' : '#C80003',
|
||||||
'title_rx_txt_color' : '#FFFFFF',
|
'title_rx_txt_color' : '#FFFFFF',
|
||||||
'title_rx_bg_color' : '#0076C9',
|
'title_rx_bg_color' : '#0076C9',
|
||||||
'title_ia_txt_color' : '#000000',
|
'title_ia_txt_color' : '#000000',
|
||||||
'title_ia_bg_color' : '#C0BEBF',
|
'title_ia_bg_color' : '#C0BEBF',
|
||||||
'try_posix_regexp' : platform.system() != 'Linux',
|
|
||||||
'hide_tabbar' : False,
|
|
||||||
'scroll_tabbar' : False,
|
|
||||||
'alternate_screen_scroll': True,
|
'alternate_screen_scroll': True,
|
||||||
'keybindings' : {
|
'split_to_group' : False,
|
||||||
'zoom_in' : '<Ctrl>plus',
|
'autoclean_groups' : True,
|
||||||
'zoom_out' : '<Ctrl>minus',
|
'http_proxy' : '',
|
||||||
'zoom_normal' : '<Ctrl>0',
|
'ignore_hosts' : ['localhost','127.0.0.0/8','*.local'],
|
||||||
'new_root_tab' : '<Ctrl><Shift><Alt>T',
|
},
|
||||||
'new_tab' : '<Ctrl><Shift>T',
|
},
|
||||||
'go_next' : ('<Ctrl><Shift>N','<Ctrl>Tab'),
|
'layouts': {
|
||||||
'go_prev' : ('<Ctrl><Shift>P','<Ctrl><Shift>Tab'),
|
},
|
||||||
'go_up' : '<Alt>Up',
|
'plugins': {
|
||||||
'go_down' : '<Alt>Down',
|
},
|
||||||
'go_left' : '<Alt>Left',
|
|
||||||
'go_right' : '<Alt>Right',
|
|
||||||
'split_horiz' : '<Ctrl><Shift>O',
|
|
||||||
'split_vert' : '<Ctrl><Shift>E',
|
|
||||||
'close_term' : '<Ctrl><Shift>W',
|
|
||||||
'copy' : '<Ctrl><Shift>C',
|
|
||||||
'paste' : '<Ctrl><Shift>V',
|
|
||||||
'toggle_scrollbar' : '<Ctrl><Shift>S',
|
|
||||||
'search' : '<Ctrl><Shift>F',
|
|
||||||
'close_window' : '<Ctrl><Shift>Q',
|
|
||||||
'resize_up' : '<Ctrl><Shift>Up',
|
|
||||||
'resize_down' : '<Ctrl><Shift>Down',
|
|
||||||
'resize_left' : '<Ctrl><Shift>Left',
|
|
||||||
'resize_right' : '<Ctrl><Shift>Right',
|
|
||||||
'move_tab_right' : '<Ctrl><Shift>Page_Down',
|
|
||||||
'move_tab_left' : '<Ctrl><Shift>Page_Up',
|
|
||||||
'toggle_zoom' : '<Ctrl><Shift>X',
|
|
||||||
'scaled_zoom' : '<Ctrl><Shift>Z',
|
|
||||||
'next_tab' : '<Ctrl>Page_Down',
|
|
||||||
'prev_tab' : '<Ctrl>Page_Up',
|
|
||||||
'switch_to_tab_1' : None,
|
|
||||||
'switch_to_tab_2' : None,
|
|
||||||
'switch_to_tab_3' : None,
|
|
||||||
'switch_to_tab_4' : None,
|
|
||||||
'switch_to_tab_5' : None,
|
|
||||||
'switch_to_tab_6' : None,
|
|
||||||
'switch_to_tab_7' : None,
|
|
||||||
'switch_to_tab_8' : None,
|
|
||||||
'switch_to_tab_9' : None,
|
|
||||||
'switch_to_tab_10' : None,
|
|
||||||
'full_screen' : 'F11',
|
|
||||||
'reset' : '<Ctrl><Shift>R',
|
|
||||||
'reset_clear' : '<Ctrl><Shift>G',
|
|
||||||
'hide_window' : '<Ctrl><Shift><Alt>a',
|
|
||||||
'group_all' : '<Super>g',
|
|
||||||
'ungroup_all' : '<Super><Shift>g',
|
|
||||||
'group_tab' : '<Super>t',
|
|
||||||
'ungroup_tab' : '<Super><Shift>T',
|
|
||||||
'new_window' : '<Ctrl><Shift>I',
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Config(object):
|
||||||
|
"""Class to provide a slightly richer config API above ConfigBase"""
|
||||||
|
base = None
|
||||||
|
profile = None
|
||||||
|
|
||||||
class TerminatorConfig(object):
|
def __init__(self, profile='default'):
|
||||||
"""This class is used as the base point of the config system"""
|
self.base = ConfigBase()
|
||||||
callback = None
|
self.profile = profile
|
||||||
sources = None
|
|
||||||
_keys = None
|
|
||||||
|
|
||||||
def __init__ (self, sources):
|
def __getitem__(self, key):
|
||||||
self.sources = []
|
"""Look up a configuration item"""
|
||||||
|
return(self.base.get_item(key, self.profile))
|
||||||
|
|
||||||
for source in sources:
|
def __setitem__(self, key, value):
|
||||||
if isinstance(source, TerminatorConfValuestore):
|
"""Set a particular configuration item"""
|
||||||
self.sources.append (source)
|
return(self.base.set_item(key, value, self.profile))
|
||||||
|
|
||||||
# We always add a default valuestore last so no valid config item ever
|
def get_profile(self):
|
||||||
# goes unset
|
"""Get our profile"""
|
||||||
source = TerminatorConfValuestoreDefault ()
|
return(self.profile)
|
||||||
self.sources.append (source)
|
|
||||||
|
|
||||||
def _merge_keybindings(self):
|
def set_profile(self, profile):
|
||||||
if self._keys:
|
"""Set our profile (which usually means change it)"""
|
||||||
return self._keys
|
dbg('Config::set_profile: Changing profile to %s' % profile)
|
||||||
|
self.profile = profile
|
||||||
|
if not self.base.profiles.has_key(profile):
|
||||||
|
dbg('Config::set_profile: %s does not exist, creating' % profile)
|
||||||
|
self.base.profiles[profile] = copy(DEFAULTS['profiles']['default'])
|
||||||
|
|
||||||
self._keys = {}
|
def add_profile(self, profile):
|
||||||
for source in reversed(self.sources):
|
"""Add a new profile"""
|
||||||
try:
|
return(self.base.add_profile(profile))
|
||||||
val = source['keybindings']
|
|
||||||
self._keys.update(val)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return self._keys
|
|
||||||
|
|
||||||
keybindings = property(_merge_keybindings)
|
def del_profile(self, profile):
|
||||||
|
"""Delete a profile"""
|
||||||
|
if profile == self.profile:
|
||||||
|
err('Config::del_profile: Deleting in-use profile %s.' % profile)
|
||||||
|
self.set_profile('default')
|
||||||
|
if self.base.profiles.has_key(profile):
|
||||||
|
del(self.base.profiles[profile])
|
||||||
|
|
||||||
def __getattr__ (self, keyname):
|
def rename_profile(self, profile, newname):
|
||||||
for source in self.sources:
|
"""Rename a profile"""
|
||||||
dbg ("TConfig: Looking for: '%s' in '%s'"%(keyname, source.type))
|
if self.base.profiles.has_key(profile):
|
||||||
try:
|
self.base.profiles[newname] = self.base.profiles[profile]
|
||||||
val = source[keyname]
|
del(self.base.profiles[profile])
|
||||||
dbg (" TConfig: got: '%s' from a '%s'"%(val, source.type))
|
if profile == self.profile:
|
||||||
return (val)
|
self.profile = newname
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
dbg (" TConfig: Out of sources")
|
def list_profiles(self):
|
||||||
raise (AttributeError)
|
"""List all configured profiles"""
|
||||||
|
return(self.base.profiles.keys())
|
||||||
|
|
||||||
class TerminatorConfValuestore(object):
|
def save(self):
|
||||||
type = "Base"
|
"""Cause ConfigBase to save our config to file"""
|
||||||
values = None
|
return(self.base.save())
|
||||||
reconfigure_callback = None
|
|
||||||
|
def options_set(self, options):
|
||||||
|
"""Set the command line options"""
|
||||||
|
self.base.command_line_options = options
|
||||||
|
|
||||||
|
def options_get(self):
|
||||||
|
"""Get the command line options"""
|
||||||
|
return(self.base.command_line_options)
|
||||||
|
|
||||||
|
def plugin_get(self, pluginname, key):
|
||||||
|
"""Get a plugin config value"""
|
||||||
|
return(self.base.get_item(key, plugin=pluginname))
|
||||||
|
|
||||||
|
def plugin_set(self, pluginname, key, value):
|
||||||
|
"""Set a plugin config value"""
|
||||||
|
return(self.base.set_item(key, value, plugin=pluginname))
|
||||||
|
|
||||||
|
def plugin_get_config(self, plugin):
|
||||||
|
"""Return a whole config tree for a given plugin"""
|
||||||
|
return(self.base.get_plugin(plugin))
|
||||||
|
|
||||||
|
def plugin_set_config(self, plugin, tree):
|
||||||
|
"""Set a whole config tree for a given plugin"""
|
||||||
|
return(self.base.set_plugin(plugin, tree))
|
||||||
|
|
||||||
|
class ConfigBase(Borg):
|
||||||
|
"""Class to provide access to our user configuration"""
|
||||||
|
loaded = None
|
||||||
|
sections = None
|
||||||
|
global_config = None
|
||||||
|
profiles = None
|
||||||
|
keybindings = None
|
||||||
|
plugins = None
|
||||||
|
layouts = None
|
||||||
|
command_line_options = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.values = {}
|
"""Class initialiser"""
|
||||||
|
|
||||||
# Our settings
|
Borg.__init__(self, self.__class__.__name__)
|
||||||
def __getitem__ (self, keyname):
|
|
||||||
if self.values.has_key (keyname):
|
|
||||||
value = self.values[keyname]
|
|
||||||
dbg ("Returning '%s':'%s'"%(keyname, value))
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
dbg ("Failed to find '%s'"%keyname)
|
|
||||||
raise (KeyError)
|
|
||||||
|
|
||||||
class TerminatorConfValuestoreDefault (TerminatorConfValuestore):
|
self.prepare_attributes()
|
||||||
def __init__ (self):
|
self.load()
|
||||||
TerminatorConfValuestore.__init__ (self)
|
|
||||||
self.type = "Default"
|
|
||||||
self.values = DEFAULTS
|
|
||||||
|
|
||||||
class TerminatorConfValuestoreRC (TerminatorConfValuestore):
|
def prepare_attributes(self):
|
||||||
rcfilename = ""
|
"""Set up our borg environment"""
|
||||||
type = "RCFile"
|
if self.loaded is None:
|
||||||
def __init__ (self):
|
self.loaded = False
|
||||||
TerminatorConfValuestore.__init__ (self)
|
if self.sections is None:
|
||||||
try:
|
self.sections = ['global_config', 'keybindings', 'profiles',
|
||||||
directory = os.environ['XDG_CONFIG_HOME']
|
'layouts', 'plugins']
|
||||||
except KeyError:
|
if self.global_config is None:
|
||||||
dbg(" VS_RCFile: XDG_CONFIG_HOME not found. defaulting to ~/.config")
|
self.global_config = copy(DEFAULTS['global_config'])
|
||||||
directory = os.path.join (os.path.expanduser("~"), ".config")
|
if self.profiles is None:
|
||||||
self.rcfilename = os.path.join(directory, "terminator/config")
|
self.profiles = {}
|
||||||
dbg(" VS_RCFile: config file located at %s" % self.rcfilename)
|
self.profiles['default'] = copy(DEFAULTS['profiles']['default'])
|
||||||
self.call_parser(True)
|
if self.keybindings is None:
|
||||||
|
self.keybindings = copy(DEFAULTS['keybindings'])
|
||||||
|
if self.plugins is None:
|
||||||
|
self.plugins = {}
|
||||||
|
if self.layouts is None:
|
||||||
|
self.layouts = copy(DEFAULTS['layouts'])
|
||||||
|
|
||||||
def set_reconfigure_callback (self, function):
|
def defaults_to_configspec(self):
|
||||||
dbg (" VS_RCFile: setting callback to: %s"%function)
|
"""Convert our tree of default values into a ConfigObj validation
|
||||||
self.reconfigure_callback = function
|
specification"""
|
||||||
return (True)
|
configspecdata = {}
|
||||||
|
|
||||||
def call_parser (self, is_init = False):
|
section = {}
|
||||||
dbg (" VS_RCFile: parsing config file")
|
for key in DEFAULTS['global_config']:
|
||||||
try:
|
keytype = DEFAULTS['global_config'][key].__class__.__name__
|
||||||
ini = ConfigFile(self.rcfilename, self._rc_set_callback())
|
value = DEFAULTS['global_config'][key]
|
||||||
ini.parse()
|
if keytype == 'int':
|
||||||
except IOError, ex:
|
keytype = 'integer'
|
||||||
dbg (" VS_RCFile: unable to open %s (%r)" % (self.rcfilename, ex))
|
elif keytype == 'str':
|
||||||
except ParsedWithErrors, ex:
|
keytype = 'string'
|
||||||
# We don't really want to produce an error dialog every run
|
elif keytype == 'bool':
|
||||||
if not is_init:
|
keytype = 'boolean'
|
||||||
pass
|
elif keytype == 'list':
|
||||||
msg = _("""<big><b>Configuration error</b></big>
|
value = 'list(%s)' % ','.join(value)
|
||||||
|
|
||||||
Errors were encountered while parsing terminator_config(5) file:
|
keytype = '%s(default=%s)' % (keytype, value)
|
||||||
|
|
||||||
<b>%s</b>
|
section[key] = keytype
|
||||||
|
configspecdata['global_config'] = section
|
||||||
|
|
||||||
%d line(s) have been ignored.""") % (self.rcfilename, len(ex.errors))
|
section = {}
|
||||||
|
for key in DEFAULTS['keybindings']:
|
||||||
|
value = DEFAULTS['keybindings'][key]
|
||||||
|
if value is None or value == '':
|
||||||
|
continue
|
||||||
|
section[key] = 'string(default=%s)' % value
|
||||||
|
configspecdata['keybindings'] = section
|
||||||
|
|
||||||
dialog = gtk.Dialog(_("Configuration error"), None, gtk.DIALOG_MODAL,
|
section = {}
|
||||||
(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
|
for key in DEFAULTS['profiles']['default']:
|
||||||
dialog.set_has_separator(False)
|
keytype = DEFAULTS['profiles']['default'][key].__class__.__name__
|
||||||
dialog.set_resizable(False)
|
value = DEFAULTS['profiles']['default'][key]
|
||||||
|
if keytype == 'int':
|
||||||
|
keytype = 'integer'
|
||||||
|
elif keytype == 'bool':
|
||||||
|
keytype = 'boolean'
|
||||||
|
elif keytype == 'str':
|
||||||
|
keytype = 'string'
|
||||||
|
value = '"%s"' % value
|
||||||
|
elif keytype == 'list':
|
||||||
|
value = 'list(%s)' % ','.join(value)
|
||||||
|
|
||||||
image = gtk.image_new_from_stock(gtk.STOCK_DIALOG_WARNING,
|
keytype = '%s(default=%s)' % (keytype, value)
|
||||||
gtk.ICON_SIZE_DIALOG)
|
|
||||||
image.set_alignment (0.5, 0)
|
|
||||||
dmsg = gtk.Label(msg)
|
|
||||||
dmsg.set_use_markup(True)
|
|
||||||
dmsg.set_alignment(0, 0.5)
|
|
||||||
|
|
||||||
textbuff = gtk.TextBuffer()
|
section[key] = keytype
|
||||||
textbuff.set_text("\n".join(map(lambda ex: str(ex), ex.errors)))
|
configspecdata['profiles'] = {}
|
||||||
textview = gtk.TextView(textbuff)
|
configspecdata['profiles']['__many__'] = section
|
||||||
textview.set_editable(False)
|
|
||||||
|
|
||||||
textview.modify_font(pango.FontDescription(DEFAULTS['font']))
|
configspec = ConfigObj(configspecdata)
|
||||||
textscroll = gtk.ScrolledWindow()
|
if DEBUG == True:
|
||||||
textscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
configspec.write(open('/tmp/terminator_configspec_debug.txt', 'w'))
|
||||||
textscroll.add(textview)
|
return(configspec)
|
||||||
# This should be scaled with the size of the text and font
|
|
||||||
textscroll.set_size_request(600, 200)
|
|
||||||
|
|
||||||
root = gtk.VBox()
|
def load(self):
|
||||||
root.pack_start(dmsg, padding = 6)
|
"""Load configuration data from our various sources"""
|
||||||
root.pack_start(textscroll, padding = 6)
|
if self.loaded is True:
|
||||||
|
dbg('ConfigBase::load: config already loaded')
|
||||||
box = gtk.HBox()
|
|
||||||
box.pack_start (image, False, False, 6)
|
|
||||||
box.pack_start (root, False, False, 6)
|
|
||||||
|
|
||||||
vbox = dialog.get_content_area()
|
|
||||||
vbox.pack_start (box, False, False, 12)
|
|
||||||
dialog.show_all()
|
|
||||||
|
|
||||||
dialog.run()
|
|
||||||
dialog.destroy()
|
|
||||||
|
|
||||||
dbg("ConfigFile settings are: %r" % self.values)
|
|
||||||
|
|
||||||
def _rc_set_callback(self):
|
|
||||||
def callback(sections, key, value):
|
|
||||||
dbg("Setting: section=%r with %r => %r" % (sections, key, value))
|
|
||||||
section = None
|
|
||||||
if len(sections) > 0:
|
|
||||||
section = sections[0]
|
|
||||||
if section is None:
|
|
||||||
# handle some deprecated configs
|
|
||||||
if key == 'silent_bell':
|
|
||||||
err ("silent_bell config option is deprecated, for the new bell related config options, see: man terminator_config")
|
|
||||||
if value:
|
|
||||||
self.values['audible_bell'] = False
|
|
||||||
else:
|
|
||||||
self.values['audible_bell'] = True
|
|
||||||
key = 'visible_bell'
|
|
||||||
|
|
||||||
if not DEFAULTS.has_key (key):
|
|
||||||
raise ValueError("Unknown configuration option %r" % key)
|
|
||||||
deftype = DEFAULTS[key].__class__.__name__
|
|
||||||
if key.endswith('_color'):
|
|
||||||
try:
|
|
||||||
gtk.gdk.color_parse(value)
|
|
||||||
self.values[key] = value
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError(_("Setting %r value %r not a valid colour; ignoring") % (key, value))
|
|
||||||
elif key == 'tab_position':
|
|
||||||
if value.lower() in ('top', 'left', 'bottom', 'right'):
|
|
||||||
self.values[key] = value.lower()
|
|
||||||
else:
|
|
||||||
raise ValueError(_("%s must be one of: top, left, right, bottom") % key)
|
|
||||||
elif deftype == 'bool':
|
|
||||||
if value.lower () in ('true', 'yes', 'on'):
|
|
||||||
self.values[key] = True
|
|
||||||
elif value.lower () in ('false', 'no', 'off'):
|
|
||||||
self.values[key] = False
|
|
||||||
else:
|
|
||||||
raise ValueError(_("Boolean setting %s expecting one of: yes, no, true, false, on, off") % key)
|
|
||||||
elif deftype == 'int':
|
|
||||||
self.values[key] = int (value)
|
|
||||||
elif deftype == 'float':
|
|
||||||
self.values[key] = float (value)
|
|
||||||
elif deftype == 'list':
|
|
||||||
raise ValueError(_("Reading list values from terminator_config(5) is not currently supported"))
|
|
||||||
elif deftype == 'dict':
|
|
||||||
if type(value) != dict:
|
|
||||||
raise ValueError(_("Setting %r should be a section name") % key)
|
|
||||||
self.values[key] = value
|
|
||||||
else:
|
|
||||||
self.values[key] = value
|
|
||||||
|
|
||||||
dbg (" VS_RCFile: Set value %r to %r" % (key, self.values[key]))
|
|
||||||
elif section == 'keybindings':
|
|
||||||
self.values.setdefault(section, {})
|
|
||||||
if not DEFAULTS[section].has_key(key):
|
|
||||||
raise ValueError("Keybinding name %r is unknown" % key)
|
|
||||||
else:
|
|
||||||
self.values[section][key] = value
|
|
||||||
else:
|
|
||||||
raise ValueError("Section name %r is unknown" % section)
|
|
||||||
return callback
|
|
||||||
|
|
||||||
class TerminatorConfValuestoreGConf (TerminatorConfValuestore):
|
|
||||||
profile = ""
|
|
||||||
client = None
|
|
||||||
cache = None
|
|
||||||
notifies = None
|
|
||||||
|
|
||||||
def __init__ (self, profileName = None):
|
|
||||||
TerminatorConfValuestore.__init__ (self)
|
|
||||||
self.type = "GConf"
|
|
||||||
self.inactive = False
|
|
||||||
self.cache = {}
|
|
||||||
self.notifies = {}
|
|
||||||
|
|
||||||
import gconf
|
|
||||||
|
|
||||||
self.client = gconf.client_get_default ()
|
|
||||||
|
|
||||||
# Grab a couple of values from base class to avoid recursing with our __getattr__
|
|
||||||
self._gt_dir = DEFAULTS['gt_dir']
|
|
||||||
self._profile_dir = DEFAULTS['profile_dir']
|
|
||||||
|
|
||||||
dbg ('VSGConf: Profile bet on is: "%s"'%profileName)
|
|
||||||
profiles = self.client.get_list (self._gt_dir + '/global/profile_list','string')
|
|
||||||
dbg ('VSGConf: Found profiles: "%s"'%profiles)
|
|
||||||
|
|
||||||
dbg ('VSGConf: Profile requested is: "%s"'%profileName)
|
|
||||||
if not profileName:
|
|
||||||
profile = self.client.get_string (self._gt_dir + '/global/default_profile')
|
|
||||||
else:
|
|
||||||
profile = profileName
|
|
||||||
# In newer gnome-terminal, the profile keys are named Profile0/1 etc.
|
|
||||||
# We have to match using visible_name instead
|
|
||||||
for p in profiles:
|
|
||||||
profileName2 = self.client.get_string (
|
|
||||||
self._profile_dir + '/' + p + '/visible_name')
|
|
||||||
if profileName == profileName2:
|
|
||||||
profile = p
|
|
||||||
|
|
||||||
#need to handle the list of Gconf.value
|
|
||||||
if profile in profiles:
|
|
||||||
dbg (" VSGConf: Found profile '%s' in profile_list"%profile)
|
|
||||||
self.profile = '%s/%s' % (self._profile_dir, profile)
|
|
||||||
elif "Default" in profiles:
|
|
||||||
dbg (" VSGConf: profile '%s' not found, but 'Default' exists" % profile)
|
|
||||||
self.profile = '%s/%s'%(self._profile_dir, "Default")
|
|
||||||
else:
|
|
||||||
# We're a bit stuck, there is no profile in the list
|
|
||||||
# FIXME: Find a better way to handle this than setting a non-profile
|
|
||||||
dbg ("VSGConf: No profile found, marking inactive")
|
|
||||||
self.inactive = True
|
|
||||||
return
|
return
|
||||||
|
|
||||||
#set up the active encoding list
|
filename = os.path.join(get_config_dir(), 'epic-config')
|
||||||
self.active_encodings = self.client.get_list (self._gt_dir + '/global/active_encodings', 'string')
|
try:
|
||||||
|
configfile = open(filename, 'r')
|
||||||
|
except Exception, ex:
|
||||||
|
dbg('ConfigBase::load: Unable to open %s (%s)' % (filename, ex))
|
||||||
|
return
|
||||||
|
|
||||||
self.client.add_dir (self.profile, gconf.CLIENT_PRELOAD_RECURSIVE)
|
configspec = self.defaults_to_configspec()
|
||||||
if self.on_gconf_notify:
|
parser = ConfigObj(configfile, configspec=configspec)
|
||||||
self.client.notify_add (self.profile, self.on_gconf_notify)
|
validator = Validator()
|
||||||
|
result = parser.validate(validator, preserve_errors=True)
|
||||||
|
|
||||||
self.client.add_dir ('/apps/metacity/general', gconf.CLIENT_PRELOAD_RECURSIVE)
|
if result != True:
|
||||||
self.client.notify_add ('/apps/metacity/general/focus_mode', self.on_gconf_notify)
|
err('ConfigBase::load: config format is not valid')
|
||||||
self.client.add_dir ('/desktop/gnome/interface', gconf.CLIENT_PRELOAD_RECURSIVE)
|
for (section_list, key, other) in flatten_errors(parser, result):
|
||||||
self.client.notify_add ('/desktop/gnome/interface/monospace_font_name', self.on_gconf_notify)
|
if key is not None:
|
||||||
# FIXME: Do we need to watch more non-profile stuff here?
|
print('[%s]: %s is invalid' % (','.join(section_list), key))
|
||||||
|
else:
|
||||||
|
print ('[%s] missing' % ','.join(section_list))
|
||||||
|
|
||||||
|
for section_name in self.sections:
|
||||||
|
dbg('ConfigBase::load: Processing section: %s' % section_name)
|
||||||
|
section = getattr(self, section_name)
|
||||||
|
if section_name == 'profiles':
|
||||||
|
for profile in parser[section_name]:
|
||||||
|
dbg('ConfigBase::load: Processing profile: %s' % profile)
|
||||||
|
if not section.has_key(section_name):
|
||||||
|
section[profile] = copy(DEFAULTS['profiles']['default'])
|
||||||
|
section[profile].update(parser[section_name][profile])
|
||||||
|
elif section_name == ['layouts', 'plugins']:
|
||||||
|
for part in parser[section_name]:
|
||||||
|
dbg('ConfigBase::load: Processing %s: %s' % (section_name,
|
||||||
|
part))
|
||||||
|
section[part] = parser[section_name][part]
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
section.update(parser[section_name])
|
||||||
|
except KeyError, ex:
|
||||||
|
dbg('ConfigBase::load: skipping loading missing section %s' %
|
||||||
|
section_name)
|
||||||
|
|
||||||
|
self.loaded = True
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
"""Save the config to a file"""
|
||||||
|
dbg('ConfigBase::save: saving config')
|
||||||
|
parser = ConfigObj()
|
||||||
|
parser.indent_type = ' '
|
||||||
|
|
||||||
|
for section_name in ['global_config', 'keybindings']:
|
||||||
|
dbg('ConfigBase::save: Processing section: %s' % section_name)
|
||||||
|
section = getattr(self, section_name)
|
||||||
|
parser[section_name] = dict_diff(DEFAULTS[section_name], section)
|
||||||
|
|
||||||
|
parser['profiles'] = {}
|
||||||
|
for profile in self.profiles:
|
||||||
|
dbg('ConfigBase::save: Processing profile: %s' % profile)
|
||||||
|
parser['profiles'][profile] = dict_diff(DEFAULTS['profiles']['default'],
|
||||||
|
self.profiles[profile])
|
||||||
|
|
||||||
|
parser['layouts'] = {}
|
||||||
|
for layout in self.layouts:
|
||||||
|
dbg('ConfigBase::save: Processing layout: %s' % layout)
|
||||||
|
parser['layouts'][layout] = self.layouts[layout]
|
||||||
|
|
||||||
|
parser['plugins'] = {}
|
||||||
|
for plugin in self.plugins:
|
||||||
|
dbg('ConfigBase::save: Processing plugin: %s' % plugin)
|
||||||
|
parser['plugins'][plugin] = self.plugins[plugin]
|
||||||
|
|
||||||
|
config_dir = get_config_dir()
|
||||||
|
if not os.path.isdir(config_dir):
|
||||||
|
os.makedirs(config_dir)
|
||||||
|
try:
|
||||||
|
parser.write(open(os.path.join(config_dir, 'epic-config'), 'w'))
|
||||||
|
except Exception, ex:
|
||||||
|
err('ConfigBase::save: Unable to save config: %s' % ex)
|
||||||
|
|
||||||
|
def get_item(self, key, profile='default', plugin=None):
|
||||||
|
"""Look up a configuration item"""
|
||||||
|
if self.global_config.has_key(key):
|
||||||
|
dbg('ConfigBase::get_item: %s found in globals: %s' %
|
||||||
|
(key, self.global_config[key]))
|
||||||
|
return(self.global_config[key])
|
||||||
|
elif self.profiles[profile].has_key(key):
|
||||||
|
dbg('ConfigBase::get_item: %s found in profile %s: %s' % (
|
||||||
|
key, profile, self.profiles[profile][key]))
|
||||||
|
return(self.profiles[profile][key])
|
||||||
|
elif key == 'keybindings':
|
||||||
|
return(self.keybindings)
|
||||||
|
elif plugin is not None and self.plugins[plugin].has_key(key):
|
||||||
|
dbg('ConfigBase::get_item: %s found in plugin %s: %s' % (
|
||||||
|
key, plugin, self.plugins[plugin][key]))
|
||||||
|
return(self.plugins[plugin][key])
|
||||||
|
else:
|
||||||
|
raise KeyError('ConfigBase::get_item: unknown key %s' % key)
|
||||||
|
|
||||||
|
def set_item(self, key, value, profile='default', plugin=None):
|
||||||
|
"""Set a configuration item"""
|
||||||
|
dbg('ConfigBase::set_item: Setting %s=%s (profile=%s, plugin=%s)' %
|
||||||
|
(key, value, profile, plugin))
|
||||||
|
|
||||||
|
if self.global_config.has_key(key):
|
||||||
|
self.global_config[key] = value
|
||||||
|
elif self.profiles[profile].has_key(key):
|
||||||
|
self.profiles[profile][key] = value
|
||||||
|
elif key == 'keybindings':
|
||||||
|
self.keybindings = value
|
||||||
|
elif plugin is not None:
|
||||||
|
if not self.plugins.has_key(plugin):
|
||||||
|
self.plugins[plugin] = {}
|
||||||
|
self.plugins[plugin][key] = value
|
||||||
|
else:
|
||||||
|
raise KeyError('ConfigBase::set_item: unknown key %s' % key)
|
||||||
|
|
||||||
def set_reconfigure_callback (self, function):
|
|
||||||
dbg (" VSConf: setting callback to: %s"%function)
|
|
||||||
self.reconfigure_callback = function
|
|
||||||
return(True)
|
return(True)
|
||||||
|
|
||||||
def on_gconf_notify (self, client, cnxn_id, entry, what):
|
def get_plugin(self, plugin):
|
||||||
dbg (" VSGConf: invalidating cache")
|
"""Return a whole tree for a plugin"""
|
||||||
self.cache = {}
|
if self.plugins.has_key(plugin):
|
||||||
dbg (" VSGConf: gconf changed, may run a callback. %s, %s"%(entry.key, entry.value))
|
return(self.plugins[plugin])
|
||||||
if entry.key[-12:] == 'visible_name':
|
|
||||||
dbg (" VSGConf: only a visible_name change, ignoring")
|
|
||||||
return False
|
|
||||||
if self.reconfigure_callback:
|
|
||||||
dbg (" VSGConf: callback is: %s"%self.reconfigure_callback)
|
|
||||||
self.reconfigure_callback ()
|
|
||||||
|
|
||||||
def __getitem__ (self, key = ""):
|
def set_plugin(self, plugin, tree):
|
||||||
if self.inactive:
|
"""Set a whole tree for a plugin"""
|
||||||
raise KeyError
|
self.plugins[plugin] = tree
|
||||||
|
|
||||||
if self.cache.has_key (key):
|
def add_profile(self, profile):
|
||||||
dbg (" VSGConf: returning cached value: %s"%self.cache[key])
|
"""Add a new profile"""
|
||||||
return (self.cache[key])
|
if profile in self.profiles:
|
||||||
|
return(False)
|
||||||
ret = None
|
self.profiles[profile] = copy(DEFAULTS['profiles']['default'])
|
||||||
value = None
|
return(True)
|
||||||
|
|
||||||
dbg (' VSGConf: preparing: %s/%s'%(self.profile, key))
|
|
||||||
|
|
||||||
# FIXME: Ugly special cases we should look to fix in some other way.
|
|
||||||
if key == 'font' and self['use_system_font']:
|
|
||||||
value = self.client.get ('/desktop/gnome/interface/monospace_font_name')
|
|
||||||
elif key == 'focus':
|
|
||||||
value = self.client.get ('/apps/metacity/general/focus_mode')
|
|
||||||
elif key == 'http_proxy':
|
|
||||||
if self.client.get_bool ('/system/http_proxy/use_http_proxy'):
|
|
||||||
dbg ('HACK: Mangling http_proxy')
|
|
||||||
|
|
||||||
if self.client.get_bool ('/system/http_proxy/use_authentication'):
|
|
||||||
dbg ('HACK: Using proxy authentication')
|
|
||||||
value = 'http://%s:%s@%s:%s/' % (
|
|
||||||
self.client.get_string ('/system/http_proxy/authentication_user'),
|
|
||||||
self.client.get_string ('/system/http_proxy/authentication_password'),
|
|
||||||
self.client.get_string ('/system/http_proxy/host'),
|
|
||||||
self.client.get_int ('/system/http_proxy/port'))
|
|
||||||
else:
|
|
||||||
dbg ('HACK: Not using proxy authentication')
|
|
||||||
value = 'http://%s:%s/' % (
|
|
||||||
self.client.get_string ('/system/http_proxy/host'),
|
|
||||||
self.client.get_int ('/system/http_proxy/port'))
|
|
||||||
elif key == 'cursor_blink':
|
|
||||||
tmp = self.client.get_string('%s/cursor_blink_mode' % self.profile)
|
|
||||||
if tmp in ['on', 'off'] and self.notifies.has_key ('cursor_blink'):
|
|
||||||
self.client.notify_remove (self.notifies['cursor_blink'])
|
|
||||||
del (self.notifies['cursor_blink'])
|
|
||||||
if tmp == 'on':
|
|
||||||
value = True
|
|
||||||
elif tmp == 'off':
|
|
||||||
value = False
|
|
||||||
elif tmp == 'system':
|
|
||||||
value = self.client.get_bool ('/desktop/gnome/interface/cursor_blink')
|
|
||||||
self.notifies['cursor_blink'] = self.client.notify_add ('/desktop/gnome/interface/cursor_blink', self.on_gconf_notify)
|
|
||||||
else:
|
|
||||||
value = self.client.get ('%s/%s'%(self.profile, key))
|
|
||||||
else:
|
|
||||||
value = self.client.get ('%s/%s'%(self.profile, key))
|
|
||||||
|
|
||||||
if value != None:
|
|
||||||
from types import StringType, BooleanType
|
|
||||||
if type(value) in [StringType, BooleanType]:
|
|
||||||
ret = value
|
|
||||||
else:
|
|
||||||
funcname = "get_" + DEFAULTS[key].__class__.__name__
|
|
||||||
dbg (' GConf: picked function: %s'%funcname)
|
|
||||||
# Special case for str
|
|
||||||
if funcname == "get_str":
|
|
||||||
funcname = "get_string"
|
|
||||||
# Special case for strlist
|
|
||||||
if funcname == "get_strlist":
|
|
||||||
funcname = "get_list"
|
|
||||||
typefunc = getattr (value, funcname)
|
|
||||||
ret = typefunc ()
|
|
||||||
|
|
||||||
self.cache[key] = ret
|
|
||||||
return (ret)
|
|
||||||
else:
|
|
||||||
raise (KeyError)
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import doctest
|
||||||
|
(failed, attempted) = doctest.testmod()
|
||||||
|
print "%d/%d tests failed" % (failed, attempted)
|
||||||
|
sys.exit(failed)
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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:
|
|
@ -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.
|
# 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.
|
# 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
|
from terminatorlib.version import APP_NAME, APP_VERSION
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
|
|
|
@ -17,24 +17,33 @@
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor
|
||||||
# , Boston, MA 02110-1301 USA
|
# , Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
# pylint: disable-msg=W0212
|
""" Editable Label class"""
|
||||||
''' Editable Label class'''
|
|
||||||
import gtk
|
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
|
An eventbox that partialy emulate a gtk.Label
|
||||||
On double-click, the label is editable, entering an empty will revert back to automatic text
|
On double-click, the label is editable, entering an empty will revert back to automatic text
|
||||||
'''
|
"""
|
||||||
_label = None
|
_label = None
|
||||||
_ebox = None
|
_ebox = None
|
||||||
_autotext = None
|
_autotext = None
|
||||||
_custom = None
|
_custom = None
|
||||||
_entry = None
|
_entry = None
|
||||||
_entry_handler_id = None
|
_entry_handler_id = None
|
||||||
|
|
||||||
|
__gsignals__ = {
|
||||||
|
'edit-done': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, text = ""):
|
def __init__(self, text = ""):
|
||||||
''' Class initialiser'''
|
""" Class initialiser"""
|
||||||
gtk.EventBox.__init__(self)
|
gtk.EventBox.__init__(self)
|
||||||
|
self.__gobject_init__()
|
||||||
|
|
||||||
self._entry_handler_id = []
|
self._entry_handler_id = []
|
||||||
self._label = gtk.Label(text)
|
self._label = gtk.Label(text)
|
||||||
self._custom = False
|
self._custom = False
|
||||||
|
@ -43,21 +52,26 @@ class TerminatorEditableLabel( gtk.EventBox ):
|
||||||
self.connect ("button-press-event", self._on_click_text)
|
self.connect ("button-press-event", self._on_click_text)
|
||||||
|
|
||||||
def set_angle(self, angle ):
|
def set_angle(self, angle ):
|
||||||
'''set angle of the label'''
|
"""set angle of the label"""
|
||||||
self._label.set_angle( angle )
|
self._label.set_angle( angle )
|
||||||
|
|
||||||
|
def editing(self):
|
||||||
|
"""Return if we are currently editing"""
|
||||||
|
return(self._entry != None)
|
||||||
|
|
||||||
def set_text(self, text, force=False):
|
def set_text(self, text, force=False):
|
||||||
'''set the text of the label'''
|
"""set the text of the label"""
|
||||||
self._autotext = text
|
self._autotext = text
|
||||||
if not self._custom or force:
|
if not self._custom or force:
|
||||||
self._label.set_text(text)
|
self._label.set_text(text)
|
||||||
|
|
||||||
def get_text(self):
|
def get_text(self):
|
||||||
'''get the text from the label'''
|
"""get the text from the label"""
|
||||||
return self._label.get_text()
|
return(self._label.get_text())
|
||||||
|
|
||||||
def _on_click_text(self, widget, event):
|
def _on_click_text(self, widget, event):
|
||||||
'''event handling text edition'''
|
# pylint: disable-msg=W0613
|
||||||
|
"""event handling text edition"""
|
||||||
if event.type == gtk.gdk._2BUTTON_PRESS:
|
if event.type == gtk.gdk._2BUTTON_PRESS:
|
||||||
self.remove (self._label)
|
self.remove (self._label)
|
||||||
self._entry = gtk.Entry ()
|
self._entry = gtk.Entry ()
|
||||||
|
@ -72,13 +86,12 @@ class TerminatorEditableLabel( gtk.EventBox ):
|
||||||
self._on_entry_keypress)
|
self._on_entry_keypress)
|
||||||
self._entry_handler_id.append(sig)
|
self._entry_handler_id.append(sig)
|
||||||
self._entry.grab_focus ()
|
self._entry.grab_focus ()
|
||||||
return True
|
return(True)
|
||||||
# make pylint happy
|
return(False)
|
||||||
if 1 or widget or event:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _entry_to_label (self, widget, event):
|
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():
|
if self._entry and self._entry in self.get_children():
|
||||||
#disconnect signals to avoid segfault :s
|
#disconnect signals to avoid segfault :s
|
||||||
for sig in self._entry_handler_id:
|
for sig in self._entry_handler_id:
|
||||||
|
@ -89,13 +102,13 @@ class TerminatorEditableLabel( gtk.EventBox ):
|
||||||
self.add (self._label)
|
self.add (self._label)
|
||||||
self._entry = None
|
self._entry = None
|
||||||
self.show_all ()
|
self.show_all ()
|
||||||
return True
|
self.emit('edit-done')
|
||||||
#make pylint happy
|
return(True)
|
||||||
if 1 or widget or event:
|
return(False)
|
||||||
return False
|
|
||||||
|
|
||||||
def _on_entry_activated (self, widget):
|
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 ()
|
entry = self._entry.get_text ()
|
||||||
label = self._label.get_text ()
|
label = self._label.get_text ()
|
||||||
if entry == '':
|
if entry == '':
|
||||||
|
@ -106,20 +119,11 @@ class TerminatorEditableLabel( gtk.EventBox ):
|
||||||
self._label.set_text (entry)
|
self._label.set_text (entry)
|
||||||
self._entry_to_label (None, None)
|
self._entry_to_label (None, None)
|
||||||
|
|
||||||
# make pylint happy
|
|
||||||
if 1 or widget:
|
|
||||||
return
|
|
||||||
|
|
||||||
def _on_entry_keypress (self, widget, event):
|
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)
|
key = gtk.gdk.keyval_name (event.keyval)
|
||||||
if key == 'Escape':
|
if key == 'Escape':
|
||||||
self._entry_to_label (None, None)
|
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
|
#!/usr/bin/python
|
||||||
# TerminatorEncoding - charset encoding classes
|
# 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
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -23,8 +23,9 @@ This list is taken from gnome-terminal's src/terminal-encoding.c
|
||||||
and src/encoding.c
|
and src/encoding.c
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from terminatorlib import translation
|
from translation import _
|
||||||
|
|
||||||
|
#pylint: disable-msg=R0903
|
||||||
class TerminatorEncoding:
|
class TerminatorEncoding:
|
||||||
"""Class to store encoding details"""
|
"""Class to store encoding details"""
|
||||||
|
|
||||||
|
@ -113,5 +114,6 @@ class TerminatorEncoding:
|
||||||
def get_list():
|
def get_list():
|
||||||
"""Return a list of supported encodings"""
|
"""Return a list of supported encodings"""
|
||||||
return TerminatorEncoding.encodings
|
return TerminatorEncoding.encodings
|
||||||
|
|
||||||
get_list = staticmethod(get_list)
|
get_list = staticmethod(get_list)
|
||||||
|
|
||||||
|
|
|
@ -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
|
#!/usr/bin/python
|
||||||
# Terminator - multiple gnome terminals in one window
|
# 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
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -24,7 +24,7 @@ keyboard shortcuts.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import gtk
|
import gtk
|
||||||
from terminatorlib.config import err
|
from util import err
|
||||||
|
|
||||||
class KeymapError(Exception):
|
class KeymapError(Exception):
|
||||||
"""Custom exception for errors in keybinding configurations"""
|
"""Custom exception for errors in keybinding configurations"""
|
||||||
|
@ -37,7 +37,7 @@ class KeymapError(Exception):
|
||||||
return "Keybinding '%s' invalid: %s" % (self.action, self.value)
|
return "Keybinding '%s' invalid: %s" % (self.action, self.value)
|
||||||
|
|
||||||
MODIFIER = re.compile('<([^<]+)>')
|
MODIFIER = re.compile('<([^<]+)>')
|
||||||
class TerminatorKeybindings:
|
class Keybindings:
|
||||||
"""Class to handle loading and lookup of Terminator keybindings"""
|
"""Class to handle loading and lookup of Terminator keybindings"""
|
||||||
|
|
||||||
modifiers = {
|
modifiers = {
|
||||||
|
|
|
@ -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:
|
|
@ -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)
|
|
@ -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:
|
|
@ -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)
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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)
|
||||||
|
|
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)
|
|
|
@ -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()
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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,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
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
@ -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
|
#!/usr/bin/python
|
||||||
# Terminator - multiple gnome terminals in one window
|
# 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
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -17,16 +17,22 @@
|
||||||
|
|
||||||
"""Terminator by Chris Jones <cmsj@tenshu.net>"""
|
"""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:
|
try:
|
||||||
import gettext
|
import gettext
|
||||||
gettext.install (APP_NAME)
|
gettext.textdomain(APP_NAME)
|
||||||
except ImportError:
|
_ = gettext.gettext
|
||||||
print "Using fallback _()"
|
except:
|
||||||
import __builtin__
|
dbg("Using fallback _()")
|
||||||
|
|
||||||
def dummytrans (text):
|
def dummytrans (text):
|
||||||
"""A _ function for systems without gettext. Effectively a NOOP"""
|
"""A _ function for systems without gettext. Effectively a NOOP"""
|
||||||
return text
|
return(text)
|
||||||
__builtin__.__dict__['_'] = dummytrans
|
|
||||||
|
_ = dummytrans
|
||||||
|
|
||||||
|
|
|
@ -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
|
#!/usr/bin/python
|
||||||
# TerminatorVersion - version number
|
# 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
|
# 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
|
# 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_NAME = 'terminator'
|
||||||
APP_VERSION = '0.14'
|
APP_VERSION = '0.90'
|
||||||
|
|
|
@ -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