2
# Terminator - multiple gnome terminals in one window
3
# Copyright (C) 2006-2008 cmsj@tenshu.net
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation, version 2 only.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
"""Terminator by Chris Jones <cmsj@tenshu.net>"""
21
import gobject, gtk, pango
22
import os, platform, sys
24
#import version details
25
from terminatorlib.version import *
27
# import our configuration loader
28
from terminatorlib import config
29
from terminatorlib.config import dbg, err, debug
32
from terminatorlib.encoding import TerminatorEncoding
38
error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
39
_('You need to install python bindings for libvte ("python-vte" in debian/ubuntu)'))
43
class TerminatorTerm (gtk.VBox):
48
def __init__ (self, terminator, profile = None, command = None, cwd = None):
49
gtk.VBox.__init__ (self)
50
self.terminator = terminator
51
self.conf = terminator.conf
52
self.command = command
54
# Sort out cwd detection code, if available
55
self.pid_get_cwd = lambda pid: None
56
if platform.system() == 'FreeBSD':
58
from terminatorlib import freebsd
59
self.pid_get_cwd = lambda pid: freebsd.get_process_cwd(pid)
60
dbg ('Using FreeBSD self.pid_get_cwd')
62
dbg ('FreeBSD version too old for self.pid_get_cwd')
64
elif platform.system() == 'Linux':
65
dbg ('Using Linux self.pid_get_cwd')
66
self.pid_get_cwd = lambda pid: os.path.realpath ('/proc/%s/cwd' % pid)
68
dbg ('Unable to set a self.pid_get_cwd, unknown system: %s'%platform.system)
70
# import a library for viewing URLs
72
# gnome.url_show() is really useful
73
dbg ('url_show: importing gnome module')
75
gnome.init ('terminator', 'terminator')
76
self.url_show = gnome.url_show
78
# webbrowser.open() is not really useful, but will do as a fallback
79
dbg ('url_show: gnome module failed, using webbrowser')
81
self.url_show = webbrowser.open
83
self.cwd = cwd or os.getcwd();
84
if not os.path.exists(self.cwd) or not os.path.isdir(self.cwd):
85
self.cwd = pwd.getpwuid(os.getuid ())[5]
87
self.clipboard = gtk.clipboard_get (gtk.gdk.SELECTION_CLIPBOARD)
88
self.scrollbar_position = self.conf.scrollbar_position
90
self._vte = vte.Terminal ()
91
self._vte.set_size (80, 24)
92
self.reconfigure_vte ()
95
self._termbox = gtk.HBox ()
97
self._title = gtk.Label()
99
self._titlebox = gtk.EventBox()
100
self._titlebox.add(self._title)
102
self.pack_start(self._titlebox, False)
103
self.pack_start(self._termbox)
105
if self.conf.titlebars:
106
self._titlebox.show()
108
self._titlebox.hide()
110
self._scrollbar = gtk.VScrollbar (self._vte.get_adjustment ())
111
if self.scrollbar_position != "hidden" and self.scrollbar_position != "disabled":
112
self._scrollbar.show ()
114
if self.scrollbar_position == 'left':
115
packfunc = self._termbox.pack_end
117
packfunc = self._termbox.pack_start
120
packfunc (self._scrollbar, False)
122
self._vte.connect ("key-press-event", self.on_vte_key_press)
123
self._vte.connect ("button-press-event", self.on_vte_button_press)
124
self._vte.connect ("popup-menu", self.on_vte_popup_menu)
127
srcvtetargets = [ ( "vte", gtk.TARGET_SAME_APP, self.TARGET_TYPE_VTE ) ]
128
dsttargets = [ ( "vte", gtk.TARGET_SAME_APP, self.TARGET_TYPE_VTE ), ('text/plain', 0, 0) , ("STRING", 0, 0), ("COMPOUND_TEXT", 0, 0)]
129
self._vte.drag_source_set( gtk.gdk.CONTROL_MASK | gtk.gdk.BUTTON3_MASK, srcvtetargets, gtk.gdk.ACTION_MOVE)
130
self._titlebox.drag_source_set( gtk.gdk.BUTTON1_MASK, srcvtetargets, gtk.gdk.ACTION_MOVE)
131
#self._vte.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_HIGHLIGHT |gtk.DEST_DEFAULT_DROP ,dsttargets, gtk.gdk.ACTION_MOVE)
132
self._vte.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_HIGHLIGHT |gtk.DEST_DEFAULT_DROP ,dsttargets, gtk.gdk.ACTION_MOVE)
133
self._vte.connect("drag-begin", self.on_drag_begin, self)
134
self._titlebox.connect("drag-begin", self.on_drag_begin, self)
135
self._vte.connect("drag-data-get", self.on_drag_data_get, self)
136
self._titlebox.connect("drag-data-get", self.on_drag_data_get, self)
137
#for testing purpose: drag-motion
138
self._vte.connect("drag-motion", self.on_drag_motion, self)
139
self._vte.connect("drag-data-received", self.on_drag_data_received, self)
141
if self.conf.copy_on_selection:
142
self._vte.connect ("selection-changed", lambda widget: self._vte.copy_clipboard ())
144
self._vte.connect ("composited-changed", self.on_composited_changed)
145
self._vte.connect ("window-title-changed", self.on_vte_title_change)
146
self._vte.connect ("grab-focus", self.on_vte_focus)
147
self._vte.connect ("focus-out-event", self.on_vte_focus_out)
148
self._vte.connect ("focus-in-event", self.on_vte_focus_in)
150
exit_action = self.conf.exit_action
151
if exit_action == "restart":
152
self._vte.connect ("child-exited", self.spawn_child)
153
# We need to support "left" because some buggy versions of gnome-terminal
154
# set it in some situations
155
elif exit_action in ("close", "left"):
156
self._vte.connect ("child-exited", lambda close_term: self.terminator.closeterm (self))
158
self._vte.add_events (gtk.gdk.ENTER_NOTIFY_MASK)
159
self._vte.connect ("enter_notify_event", self.on_vte_notify_enter)
163
dbg ('SEGBUG: Setting http_proxy')
164
env_proxy = os.getenv ('http_proxy')
165
if not env_proxy and self.conf.http_proxy and self.conf.http_proxy != '':
166
os.putenv ('http_proxy', self.conf.http_proxy)
168
dbg ('SEGBUG: Setting COLORTERM')
169
os.putenv ('COLORTERM', 'gnome-terminal')
170
dbg ('SEGBUG: TerminatorTerm __init__ complete')
172
def openurl (self, url):
173
dbg ('openurl: viewing %s'%url)
175
if subprocess.call(["xdg-open", url]) != 0:
176
dbg ('openurl: xdg-open failed')
180
dbg ('openurl: calling url_show')
183
dbg ('openurl: url_show failed. No URL for you')
186
def on_drag_begin(self, widget, drag_context, data):
188
widget.drag_source_set_icon_pixbuf(self.terminator.icon_theme.load_icon (APP_NAME, 48, 0))
190
def on_drag_data_get(self,widget, drag_context, selection_data, info, time, data):
191
dbg ("Drag data get")
192
selection_data.set("vte",info, str(data.terminator.term_list.index (self)))
194
def on_drag_motion(self, widget, drag_context, x, y, time, data):
195
dbg ("Drag Motion on ")
197
x-special/gnome-icon-list
203
text/plain;charset=utf-8
204
text/plain;charset=UTF-8
208
if 'text/plain' in drag_context.targets:
209
#copy text from another widget
211
srcwidget = drag_context.get_source_widget()
212
if (isinstance(srcwidget, gtk.EventBox) and srcwidget == self._titlebox) or widget == srcwidget:
216
alloc = widget.allocation
217
rect = gtk.gdk.Rectangle(0, 0, alloc.width, alloc.height)
218
widget.window.invalidate_rect(rect, True)
219
widget.window.process_updates(True)
221
context = widget.window.cairo_create()
222
if self.conf.use_theme_colors:
223
color = self._vte.get_style ().text[gtk.STATE_NORMAL]
225
color = gtk.gdk.color_parse (self.conf.foreground_color)
227
context.set_source_rgba(color.red, color.green, color.blue, 0.5)
229
pos = self.get_location(widget, x, y)
231
topright = (alloc.width,0)
232
topmiddle = (alloc.width/2,0)
233
bottomleft = (0, alloc.height)
234
bottomright = (alloc.width,alloc.height)
235
bottommiddle = (alloc.width/2, alloc.height)
236
middle = (alloc.width/2, alloc.height/2)
237
middleleft = (0, alloc.height/2)
238
middleright = (alloc.width, alloc.height/2)
239
#print "%f %f %d %d" %(coef1, coef2, b1,b2)
242
coord = (topright, topmiddle, bottommiddle, bottomright)
244
coord = (topleft, topright, middleright , middleleft)
246
coord = (topleft, topmiddle, bottommiddle, bottomleft)
248
coord = (bottomleft, bottomright, middleright , middleleft)
251
context.move_to(coord[len(coord)-1][0],coord[len(coord)-1][1])
253
context.line_to(i[0],i[1])
257
def on_drag_drop(self, widget, drag_context, x, y, time):
258
parent = widget.get_parent()
259
dbg ('Drag drop on %s'%parent)
261
def on_drag_data_received(self, widget, drag_context, x, y, selection_data, info, time, data):
262
dbg ("Drag Data Received")
263
if selection_data.type == 'text/plain':
264
#copy text to destination
265
#print "%s %s" % (selection_data.type, selection_data.target)
266
txt = selection_data.data.strip()
267
if txt[0:7] == "file://":
268
txt = "'%s'" % txt[7:]
269
self._vte.feed_child(txt)
272
widgetsrc = data.terminator.term_list[int(selection_data.data)]
273
srcvte = drag_context.get_source_widget()
274
#check if computation requireds
275
if (isinstance(srcvte, gtk.EventBox) and srcvte == self._titlebox) or srcvte == widget:
280
dsthbox = widget.get_parent().get_parent()
282
dstpaned = dsthbox.get_parent()
283
srcpaned = srchbox.get_parent()
284
if isinstance(dstpaned, gtk.Window) and isinstance(srcpaned, gtk.Window):
285
dbg (" Only one terminal")
287
pos = self.get_location(widget, x, y)
289
data.terminator.remove(widgetsrc)
290
data.terminator.add(self, widgetsrc,pos)
293
def get_location(self, vte, x, y):
295
#get the diagonales function for the receiving widget
296
coef1 = float(vte.allocation.height)/float(vte.allocation.width)
297
coef2 = -float(vte.allocation.height)/float(vte.allocation.width)
299
b2 = vte.allocation.height
300
#determine position in rectangle
311
if (x*coef1 + b1 > y ) and (x*coef2 + b2 < y ):
313
if (x*coef1 + b1 > y ) and (x*coef2 + b2 > y ):
315
if (x*coef1 + b1 < y ) and (x*coef2 + b2 > y ):
317
if (x*coef1 + b1 < y ) and (x*coef2 + b2 < y ):
321
def add_matches (self, lboundry="[[:<:]]", rboundry="[[:>:]]"):
322
userchars = "-A-Za-z0-9"
323
passchars = "-A-Za-z0-9,?;.:/!%$^*&~\"#'"
324
hostchars = "-A-Za-z0-9"
325
pathchars = "-A-Za-z0-9_$.+!*(),;:@&=?/~#%'"
326
schemes = "(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:)"
327
user = "[" + userchars + "]+(:[" + passchars + "]+)?"
328
urlpath = "/[" + pathchars + "]*[^]'.}>) \t\r\n,\\\"]"
330
self.matches['full_uri'] = self._vte.match_add(lboundry + schemes + "//(" + user + "@)?[" + hostchars +".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?")
332
# FreeBSD works with [[:<:]], Linux works with \<
333
if self.matches['full_uri'] == -1:
334
if lboundry != "\\<":
335
self.add_matches(lboundry = "\\<", rboundry = "\\>")
337
self.matches['addr_only'] = self._vte.match_add (lboundry + "(www|ftp)[" + hostchars + "]*\.[" + hostchars + ".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?")
338
self.matches['email'] = self._vte.match_add (lboundry + "(mailto:)?[a-zA-Z0-9][a-zA-Z0-9.+-]*@[a-zA-Z0-9][a-zA-Z0-9-]*\.[a-zA-Z0-9][a-zA-Z0-9-]+[.a-zA-Z0-9-]*" + rboundry)
339
self.matches['nntp'] = self._vte.match_add (lboundry + '''news:[-A-Z\^_a-z{|}~!"#$%&'()*+,./0-9;:=?`]+@[-A-Za-z0-9.]+(:[0-9]+)?''' + rboundry)
341
def spawn_child (self, event=None):
342
update_records = self.conf.update_records
343
login = self.conf.login_shell
348
dbg ('spawn_child: using self.command: %s'%self.command)
350
shell = self.command[0]
351
elif self.conf.use_custom_command:
352
dbg ('spawn_child: using custom command: %s'%self.conf.custom_command)
353
args = self.conf.custom_command.split ()
357
if os.environ['PATH'] == "":
359
paths = os.environ['PATH'].split(':')
361
paths = ['/usr/local/bin', '/usr/bin', '/bin']
362
dbg ('spawn_child: found paths: "%s"'%paths)
364
if self.conf.use_custom_command and shell[0] != '/':
366
dbg ('spawn_child: looking for pathless custom command "%s"'%os.path.join (path, shell))
367
if os.path.exists (os.path.join (path, shell)):
368
shell = os.path.join (path, shell)
371
if not self.command and not os.path.exists (shell):
372
dbg ('spawn_child: hunting for a command')
373
shell = os.getenv ('SHELL') or ''
375
if not os.path.exists (shell):
376
dbg ('spawn_child: No usable shell in $SHELL (%s)'%os.getenv('SHELL'))
377
shell = pwd.getpwuid (os.getuid ())[6] or ''
378
if not os.path.exists (shell):
379
for i in ['bash','zsh','tcsh','ksh','csh','sh']:
381
shell = os.path.join(p, i)
382
dbg ('spawn_child: Checking if "%s" exists'%shell)
383
if not os.path.exists (shell):
384
dbg ('spawn_child: %s does not exist'%shell)
387
dbg ('spawn_child: %s does exist'%shell)
389
if os.path.exists (shell):
392
if not self.command and not os.path.exists (shell):
393
# Give up, we're completely stuck
394
err (_('Unable to find a shell'))
395
gobject.timeout_add (100, self.terminator.closeterm, self)
401
if self.conf.login_shell:
402
args[0] = "-%s"%args[0]
404
dbg ('SEGBUG: Setting WINDOWID')
405
os.putenv ('WINDOWID', '%s'%self._vte.get_parent_window().xid)
407
dbg ('SEGBUG: Forking command: "%s" with args "%s", loglastlog = "%s", logwtmp = "%s", logutmp = "%s" and cwd "%s"'%(shell, args, login, update_records, update_records, self.cwd))
408
self._pid = self._vte.fork_command (command = shell, argv = args, envv = [], loglastlog = login, logwtmp = update_records, logutmp = update_records, directory=self.cwd)
410
dbg ('SEGBUG: Forked command')
412
err (_('Unable to start shell: ') + shell)
416
""" Return the current working directory of the subprocess.
417
This function requires OS specific behaviours
419
cwd = self.pid_get_cwd (self._pid)
420
dbg ('get_cwd found: %s'%cwd)
423
def reconfigure_vte (self):
425
self._vte.set_emulation (self.conf.emulation)
428
self._vte.set_word_chars (self.conf.word_chars)
430
# Set our mouselation
431
self._vte.set_mouse_autohide (self.conf.mouse_autohide)
433
# Set our compatibility
434
backspace = self.conf.backspace_binding
435
delete = self.conf.delete_binding
437
# Note, each of the 4 following comments should replace the line beneath it, but the python-vte bindings don't appear to support this constant, so the magic values are being assumed from the C enum :/
438
if backspace == "ascii-del":
439
# backbind = vte.ERASE_ASCII_BACKSPACE
442
# backbind = vte.ERASE_AUTO_BACKSPACE
445
if delete == "escape-sequence":
446
# delbind = vte.ERASE_DELETE_SEQUENCE
449
# delbind = vte.ERASE_AUTO
452
self._vte.set_backspace_binding (backbind)
453
self._vte.set_delete_binding (delbind)
457
self._vte.set_font (pango.FontDescription (self.conf.font))
462
self._vte.set_allow_bold (self.conf.allow_bold)
464
# Set our color scheme
465
palette = self.conf.palette
466
if self.conf.use_theme_colors:
467
fg_color = self._vte.get_style ().text[gtk.STATE_NORMAL]
468
bg_color = self._vte.get_style ().base[gtk.STATE_NORMAL]
470
fg_color = gtk.gdk.color_parse (self.conf.foreground_color)
471
bg_color = gtk.gdk.color_parse (self.conf.background_color)
473
colors = palette.split (':')
477
palette.append (gtk.gdk.color_parse (color))
478
self._vte.set_colors (fg_color, bg_color, palette)
480
# Set our background image, transparency and type
481
# Many thanks to the authors of gnome-terminal, on which this code is based.
482
background_type = self.conf.background_type
484
# set background image settings
485
if background_type == "image":
486
self._vte.set_background_image_file (self.conf.background_image)
487
self._vte.set_scroll_background (self.conf.scroll_background)
489
self._vte.set_background_image_file('')
490
self._vte.set_scroll_background(False)
492
# set transparency for the background (image)
493
if background_type in ("image", "transparent"):
494
self._vte.set_background_tint_color (bg_color)
495
self._vte.set_background_saturation(1 - (self.conf.background_darkness))
496
self._vte.set_opacity(int(self.conf.background_darkness * 65535))
498
self._vte.set_background_saturation(1)
499
self._vte.set_opacity(65535)
501
if not self._vte.is_composited():
502
self._vte.set_background_transparent (background_type == "transparent")
504
self._vte.set_background_transparent (False)
506
# Set our cursor blinkiness
507
self._vte.set_cursor_blinks = (self.conf.cursor_blink)
509
# Set our audible belliness
510
silent_bell = self.conf.silent_bell
511
self._vte.set_audible_bell (not silent_bell)
513
# Set our visual flashiness
514
self._vte.set_visible_bell (silent_bell)
516
# Override our flashybelliness
517
if self.conf.force_no_bell:
518
self._vte.set_visible_bell (False)
519
self._vte.set_audible_bell (False)
521
# Set our scrolliness
522
self._vte.set_scrollback_lines (self.conf.scrollback_lines)
523
self._vte.set_scroll_on_keystroke (self.conf.scroll_on_keystroke)
524
self._vte.set_scroll_on_output (self.conf.scroll_on_output)
526
if self.scrollbar_position != self.conf.scrollbar_position:
527
self.scrollbar_position = self.conf.scrollbar_position
529
if self.scrollbar_position == 'hidden' or self.scrollbar_position == 'disabled':
530
self._scrollbar.hide ()
532
self._scrollbar.show ()
533
if self.scrollbar_position == 'right':
534
self._termbox.reorder_child (self._vte, 0)
535
elif self.scrollbar_position == 'left':
536
self._termbox.reorder_child (self._scrollbar, 0)
539
self.focus = self.conf.focus
541
self._vte.queue_draw ()
543
def on_composited_changed (self, widget):
544
self.reconfigure_vte ()
546
def on_vte_button_press (self, term, event):
547
# Left mouse button + Ctrl while over a link should open it
548
mask = gtk.gdk.CONTROL_MASK
549
if (event.state & mask) == mask:
550
if event.button == 1:
551
url = self._vte.match_check (int (event.x / self._vte.get_char_width ()), int (event.y / self._vte.get_char_height ()))
553
if (url[0][0:7] != "mailto:") & (url[1] == self.matches['email']):
554
address = "mailto:" + url[0]
557
self.openurl ( address )
560
# Left mouse button should transfer focus to this vte widget
562
# we also need to give focus on the widget where the paste occured
563
if event.button in (1 ,2):
564
self._vte.grab_focus ()
567
# Right mouse button should display a context menu if ctrl not pressed
568
if event.button == 3 and event.state & gtk.gdk.CONTROL_MASK == 0:
569
self.do_popup (event)
572
def on_vte_notify_enter (self, term, event):
573
if (self.focus == "sloppy" or self.focus == "mouse"):
577
def do_scrollbar_toggle (self):
578
self.toggle_widget_visibility (self._scrollbar)
580
def do_title_toggle (self):
581
self.toggle_widget_visibility (self._titlebox)
583
def toggle_widget_visibility (self, widget):
584
if not isinstance (widget, gtk.Widget):
587
if widget.get_property ('visible'):
592
def paste_clipboard(self):
593
self._vte.paste_clipboard()
594
self._vte.grab_focus()
596
#keybindings for the individual splited terminals (affects only the
597
#the selected terminal)
598
def on_vte_key_press (self, term, event):
599
keyname = gtk.gdk.keyval_name (event.keyval)
601
mask = gtk.gdk.CONTROL_MASK
602
if (event.state & mask) == mask:
603
if keyname == 'plus':
606
elif keyname == 'minus':
609
elif keyname == 'equal':
613
mask = gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK | gtk.gdk.MOD1_MASK
614
if (event.state & mask) == mask:
617
self.terminator.newtab (self, True)
619
# bindings that should be moved to Terminator as they all just call
620
# a function of Terminator. It would be cleaner is TerminatorTerm
621
# has absolutely no reference to Terminator.
622
# N (next) - P (previous) - O (horizontal) - E (vertical) - W (close)
624
mask = gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK
625
if (event.state & mask) == mask:
627
self.terminator.go_next (self)
630
self.terminator.go_prev (self)
633
self.terminator.splitaxis (self, False)
636
self.terminator.splitaxis (self, True)
639
self.terminator.closeterm (self)
642
self._vte.copy_clipboard ()
645
self.paste_clipboard ()
648
self.do_scrollbar_toggle ()
651
self.terminator.newtab(self)
653
elif keyname in ('Up', 'Down', 'Left', 'Right'):
654
self.terminator.resizeterm (self, keyname)
656
elif keyname == 'Page_Down':
657
self.terminator.move_tab(self, 'right')
659
elif keyname == 'Page_Up':
660
self.terminator.move_tab(self, 'left')
663
self.terminator.toggle_zoom (self, True)
666
self.terminator.toggle_zoom (self)
669
mask = gtk.gdk.CONTROL_MASK
670
if (event.state & mask) == mask:
671
if keyname == 'Page_Down':
672
self.terminator.next_tab(self)
674
elif keyname == 'Page_Up':
675
self.terminator.previous_tab(self)
678
if keyname and (keyname == 'Tab' or keyname.endswith('_Tab')):
679
mask = gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK
680
if (event.state & mask) == mask:
681
self.terminator.go_prev (self)
683
mask = gtk.gdk.CONTROL_MASK
684
if (event.state & mask) == mask:
685
self.terminator.go_next (self)
687
# Warning, mask value is either gtk.gdk.CONTROL_MASK or gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK
688
# if you intend to use it, reinit it
691
def zoom_orig (self):
692
self._vte.set_font (pango.FontDescription (self.conf.font))
694
def zoom (self, zoom_in):
695
pangodesc = self._vte.get_font ()
696
fontsize = pangodesc.get_size ()
698
if fontsize > pango.SCALE and not zoom_in:
699
fontsize -= pango.SCALE
701
fontsize += pango.SCALE
703
pangodesc.set_size (fontsize)
704
self._vte.set_font (pangodesc)
706
def on_vte_popup_menu (self, term, event):
707
self.do_popup (event)
709
def do_popup (self, event = None):
710
menu = self.create_popup_menu (event)
711
menu.popup (None, None, None, event.button, event.time)
713
def create_popup_menu (self, event):
718
url = self._vte.match_check (int (event.x / self._vte.get_char_width ()), int (event.y / self._vte.get_char_height ()))
721
if url[1] != self.matches['email']:
723
nameopen = _("_Open Link")
724
namecopy = _("_Copy Link Address")
725
iconopen = gtk.image_new_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
727
item = gtk.ImageMenuItem (nameopen)
728
item.set_property('image', iconopen)
730
if url[0][0:7] != "mailto:":
731
address = "mailto:" + url[0]
734
nameopen = _("_Send Mail To...")
735
namecopy = _("_Copy Email Address")
737
item = gtk.MenuItem (nameopen)
739
item.connect ("activate", lambda menu_item: self.openurl (address))
742
item = gtk.MenuItem (namecopy)
743
item.connect ("activate", lambda menu_item: self.clipboard.set_text (url[0]))
746
item = gtk.MenuItem ()
749
item = gtk.ImageMenuItem (gtk.STOCK_COPY)
750
item.connect ("activate", lambda menu_item: self._vte.copy_clipboard ())
751
item.set_sensitive (self._vte.get_has_selection ())
754
item = gtk.ImageMenuItem (gtk.STOCK_PASTE)
755
item.connect ("activate", lambda menu_item: self.paste_clipboard ())
758
item = gtk.MenuItem ()
761
item = gtk.CheckMenuItem (_("Show _scrollbar"))
762
item.set_active (self._scrollbar.get_property ('visible'))
763
item.connect ("toggled", lambda menu_item: self.do_scrollbar_toggle ())
766
item = gtk.CheckMenuItem (_("Show _titlebar"))
767
item.set_active (self._titlebox.get_property ('visible'))
768
item.connect ("toggled", lambda menu_item: self.do_title_toggle ())
771
self._do_encoding_items (menu)
773
item = gtk.MenuItem ()
776
if not self.terminator._zoomed:
777
str_horiz = _("Split H_orizontally")
778
str_vert = _("Split V_ertically")
780
item = gtk.ImageMenuItem (str_horiz)
781
item_image = gtk.Image ()
782
item_image.set_from_icon_name (APP_NAME + '_horiz', gtk.ICON_SIZE_MENU)
783
item.set_image (item_image)
785
item.connect ("activate", lambda menu_item: self.terminator.splitaxis (self, False))
788
item = gtk.ImageMenuItem (str_vert)
789
item_image = gtk.Image ()
790
item_image.set_from_icon_name (APP_NAME + '_vert', gtk.ICON_SIZE_MENU)
791
item.set_image (item_image)
793
item.connect ("activate", lambda menu_item: self.terminator.splitaxis (self, True))
796
item = gtk.MenuItem (_("Open _Tab"))
797
item.connect ("activate", lambda menu_item: self.terminator.newtab (self))
800
if self.conf.extreme_tabs:
801
item = gtk.MenuItem (_("Open Top Level Tab"))
802
item.connect ("activate", lambda menu_item: self.terminator.newtab (self, True))
805
item = gtk.MenuItem ()
808
if len (self.terminator.term_list) > 1:
809
if not self.terminator._zoomed:
810
item = gtk.MenuItem (_("_Zoom terminal"))
811
item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self, True))
814
item = gtk.MenuItem (_("_Maximise terminal"))
815
item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self))
818
if self.terminator._zoomed and not self.terminator._maximised:
819
item = gtk.MenuItem (_("_Unzoom terminal"))
820
item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self, True))
823
if self.terminator._zoomed and self.terminator._maximised:
824
item = gtk.MenuItem (_("U_nmaximise terminal"))
825
item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self))
828
item = gtk.MenuItem ()
831
item = gtk.ImageMenuItem (gtk.STOCK_CLOSE)
832
item.connect ("activate", lambda menu_item: self.terminator.closeterm (self))
838
def on_encoding_change (self, widget, encoding):
839
current = self._vte.get_encoding ()
840
if current != encoding:
841
dbg ('Setting Encoding to: %s'%encoding)
842
self._vte.set_encoding (encoding)
844
def _do_encoding_items (self, menu):
845
active_encodings = self.conf.active_encodings
846
item = gtk.MenuItem (_("Encodings"))
848
submenu = gtk.Menu ()
849
item.set_submenu (submenu)
851
current_encoding = self._vte.get_encoding ()
853
for encoding in active_encodings:
854
radioitem = gtk.RadioMenuItem (group, _(encoding))
858
if encoding == current_encoding:
859
radioitem.set_active (True)
861
radioitem.connect ('activate', self.on_encoding_change, encoding)
862
submenu.append (radioitem)
864
item = gtk.MenuItem (_("Other Encodings"))
865
submenu.append (item)
868
submenu = gtk.Menu ()
869
item.set_submenu (submenu)
870
encodings = TerminatorEncoding ().get_list ()
871
encodings.sort (lambda x, y: cmp (x[2].lower (), y[2].lower ()))
874
for encoding in encodings:
875
if encoding[1] in active_encodings:
878
if encoding[1] is None:
879
label = "%s %s"%(encoding[2], self._vte.get_encoding ())
881
label = "%s %s"%(encoding[2], encoding[1])
883
radioitem = gtk.RadioMenuItem (group, label)
887
if encoding[1] == current_encoding:
888
radioitem.set_active (True)
890
radioitem.connect ('activate', self.on_encoding_change, encoding[1])
891
submenu.append (radioitem)
893
def on_vte_title_change(self, vte):
894
if self.conf.titletips:
895
vte.set_property ("has-tooltip", True)
896
vte.set_property ("tooltip-text", vte.get_window_title ())
897
#set the title anyhow, titlebars setting only show/hide the label
898
self._title.set_text(vte.get_window_title ())
899
self.terminator.set_window_title("%s: %s" %(APP_NAME.capitalize(), vte.get_window_title ()))
900
notebookpage = self.terminator.get_first_notebook_page(vte)
901
while notebookpage != None:
902
if notebookpage[0].get_tab_label(notebookpage[1]):
903
label = notebookpage[0].get_tab_label(notebookpage[1])
904
label.set_title(vte.get_window_title ())
905
notebookpage[0].set_tab_label(notebookpage[1], label)
906
notebookpage = self.terminator.get_first_notebook_page(notebookpage[0])
908
def on_vte_focus_in(self, vte, event):
909
self._titlebox.modify_bg(gtk.STATE_NORMAL,self.terminator.window.get_style().bg[gtk.STATE_SELECTED])
910
self._title.modify_fg(gtk.STATE_NORMAL, self.terminator.window.get_style().fg[gtk.STATE_SELECTED])
913
def on_vte_focus_out(self, vte, event):
914
self._titlebox.modify_bg(gtk.STATE_NORMAL, self.terminator.window.get_style().bg[gtk.STATE_NORMAL])
915
self._title.modify_fg(gtk.STATE_NORMAL, self.terminator.window.get_style().fg[gtk.STATE_NORMAL])
918
def on_vte_focus(self, vte):
919
if vte.get_window_title ():
920
self.terminator.set_window_title("%s: %s" %(APP_NAME.capitalize(), vte.get_window_title ()))
921
notebookpage = self.terminator.get_first_notebook_page(vte)
922
while notebookpage != None:
923
if notebookpage[0].get_tab_label(notebookpage[1]):
924
label = notebookpage[0].get_tab_label(notebookpage[1])
925
label.set_title(vte.get_window_title ())
926
notebookpage[0].set_tab_label(notebookpage[1], label)
927
notebookpage = self.terminator.get_first_notebook_page(notebookpage[0])