~ubuntu-branches/ubuntu/precise/terminator/precise

« back to all changes in this revision

Viewing changes to terminatorlib/terminatorterm.py

  • Committer: Bazaar Package Importer
  • Author(s): Nicolas Valcárcel
  • Date: 2008-07-08 07:24:49 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20080708072449-n7ey6xc46bo9lo2x
Tags: 0.9-1
New upstream release. (Closes: #489858)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
#    Terminator - multiple gnome terminals in one window
 
3
#    Copyright (C) 2006-2008  cmsj@tenshu.net
 
4
#
 
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.
 
8
#
 
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.
 
13
#
 
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
 
17
 
 
18
"""Terminator by Chris Jones <cmsj@tenshu.net>"""
 
19
import pygtk
 
20
pygtk.require ("2.0")
 
21
import gobject, gtk, pango
 
22
import os, platform, sys
 
23
 
 
24
#import version details
 
25
from terminatorlib.version import *
 
26
 
 
27
# import our configuration loader
 
28
from terminatorlib import config
 
29
from terminatorlib.config import dbg, err, debug
 
30
 
 
31
#import encoding list
 
32
from terminatorlib.encoding import TerminatorEncoding
 
33
 
 
34
# import vte-bindings
 
35
try:
 
36
  import vte
 
37
except:
 
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)'))
 
40
  error.run()
 
41
  sys.exit (1)
 
42
 
 
43
class TerminatorTerm (gtk.VBox):
 
44
 
 
45
  matches = {}
 
46
  TARGET_TYPE_VTE = 8
 
47
 
 
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
 
53
 
 
54
    # Sort out cwd detection code, if available
 
55
    self.pid_get_cwd = lambda pid: None
 
56
    if platform.system() == 'FreeBSD':
 
57
      try:
 
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')
 
61
      except:
 
62
        dbg ('FreeBSD version too old for self.pid_get_cwd')
 
63
        pass
 
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)
 
67
    else:
 
68
      dbg ('Unable to set a self.pid_get_cwd, unknown system: %s'%platform.system)
 
69
 
 
70
    # import a library for viewing URLs
 
71
    try:
 
72
      # gnome.url_show() is really useful
 
73
      dbg ('url_show: importing gnome module')
 
74
      import gnome
 
75
      gnome.init ('terminator', 'terminator')
 
76
      self.url_show = gnome.url_show
 
77
    except:
 
78
      # webbrowser.open() is not really useful, but will do as a fallback
 
79
      dbg ('url_show: gnome module failed, using webbrowser')
 
80
      import webbrowser
 
81
      self.url_show = webbrowser.open
 
82
 
 
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]
 
86
 
 
87
    self.clipboard = gtk.clipboard_get (gtk.gdk.SELECTION_CLIPBOARD)
 
88
    self.scrollbar_position = self.conf.scrollbar_position
 
89
 
 
90
    self._vte = vte.Terminal ()
 
91
    self._vte.set_size (80, 24)
 
92
    self.reconfigure_vte ()
 
93
    self._vte.show ()
 
94
 
 
95
    self._termbox = gtk.HBox ()
 
96
    self._termbox.show()
 
97
    self._title = gtk.Label()
 
98
    self._title.show()
 
99
    self._titlebox =  gtk.EventBox()
 
100
    self._titlebox.add(self._title)
 
101
    self.show()
 
102
    self.pack_start(self._titlebox, False)
 
103
    self.pack_start(self._termbox)
 
104
 
 
105
    if self.conf.titlebars:
 
106
      self._titlebox.show()
 
107
    else:
 
108
      self._titlebox.hide()
 
109
 
 
110
    self._scrollbar = gtk.VScrollbar (self._vte.get_adjustment ())
 
111
    if self.scrollbar_position != "hidden" and self.scrollbar_position != "disabled":
 
112
      self._scrollbar.show ()
 
113
 
 
114
    if self.scrollbar_position == 'left':
 
115
      packfunc = self._termbox.pack_end
 
116
    else:
 
117
      packfunc = self._termbox.pack_start
 
118
 
 
119
    packfunc (self._vte)
 
120
    packfunc (self._scrollbar, False)
 
121
 
 
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)
 
125
    
 
126
    """drag and drop"""
 
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)
 
140
 
 
141
    if self.conf.copy_on_selection:
 
142
      self._vte.connect ("selection-changed", lambda widget: self._vte.copy_clipboard ())
 
143
 
 
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)
 
149
 
 
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))
 
157
 
 
158
    self._vte.add_events (gtk.gdk.ENTER_NOTIFY_MASK)
 
159
    self._vte.connect ("enter_notify_event", self.on_vte_notify_enter)
 
160
 
 
161
    self.add_matches()
 
162
 
 
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)
 
167
 
 
168
    dbg ('SEGBUG: Setting COLORTERM')
 
169
    os.putenv ('COLORTERM', 'gnome-terminal')
 
170
    dbg ('SEGBUG: TerminatorTerm __init__ complete')
 
171
 
 
172
  def openurl (self, url):
 
173
    dbg ('openurl: viewing %s'%url)
 
174
    try:
 
175
      if subprocess.call(["xdg-open", url]) != 0:
 
176
        dbg ('openurl: xdg-open failed')
 
177
        raise
 
178
    except:
 
179
      try:
 
180
        dbg ('openurl: calling url_show')
 
181
        self.url_show (url)
 
182
      except:
 
183
        dbg ('openurl: url_show failed. No URL for you')
 
184
        pass
 
185
 
 
186
  def on_drag_begin(self, widget, drag_context, data):
 
187
    dbg ('Drag begins')
 
188
    widget.drag_source_set_icon_pixbuf(self.terminator.icon_theme.load_icon (APP_NAME, 48, 0))
 
189
    
 
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)))
 
193
 
 
194
  def on_drag_motion(self, widget, drag_context, x, y, time, data): 
 
195
    dbg ("Drag Motion on ")
 
196
    """
 
197
x-special/gnome-icon-list
 
198
text/uri-list
 
199
UTF8_STRING
 
200
COMPOUND_TEXT
 
201
TEXT
 
202
STRING
 
203
text/plain;charset=utf-8
 
204
text/plain;charset=UTF-8
 
205
text/plain
 
206
    """
 
207
      
 
208
    if 'text/plain' in drag_context.targets:
 
209
      #copy text from another widget
 
210
      return
 
211
    srcwidget = drag_context.get_source_widget()
 
212
    if (isinstance(srcwidget, gtk.EventBox) and srcwidget == self._titlebox) or widget == srcwidget:
 
213
      #on self
 
214
      return
 
215
 
 
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)
 
220
    
 
221
    context = widget.window.cairo_create()
 
222
    if self.conf.use_theme_colors:
 
223
      color = self._vte.get_style ().text[gtk.STATE_NORMAL]
 
224
    else:
 
225
      color = gtk.gdk.color_parse (self.conf.foreground_color)
 
226
 
 
227
    context.set_source_rgba(color.red, color.green, color.blue, 0.5)
 
228
     
 
229
    pos = self.get_location(widget, x, y)
 
230
    topleft = (0,0)
 
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)
 
240
    coord = ()
 
241
    if pos == "right":
 
242
      coord = (topright, topmiddle, bottommiddle, bottomright)
 
243
    if pos == "top":
 
244
      coord = (topleft, topright, middleright , middleleft) 
 
245
    if pos == "left":
 
246
      coord = (topleft, topmiddle, bottommiddle, bottomleft)
 
247
    if pos == "bottom":
 
248
      coord = (bottomleft, bottomright, middleright , middleleft) 
 
249
     
 
250
    if len(coord) > 0 :
 
251
      context.move_to(coord[len(coord)-1][0],coord[len(coord)-1][1])
 
252
      for i in coord:
 
253
        context.line_to(i[0],i[1])
 
254
      
 
255
      context.fill()
 
256
 
 
257
  def on_drag_drop(self, widget, drag_context, x, y, time):
 
258
    parent = widget.get_parent()
 
259
    dbg ('Drag drop on %s'%parent)
 
260
    
 
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)
 
270
      return
 
271
      
 
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:
 
276
      dbg ("  on itself")
 
277
      return
 
278
    
 
279
    srchbox = widgetsrc
 
280
    dsthbox = widget.get_parent().get_parent()
 
281
    
 
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")
 
286
      return
 
287
    pos = self.get_location(widget, x, y)
 
288
    
 
289
    data.terminator.remove(widgetsrc)
 
290
    data.terminator.add(self, widgetsrc,pos)
 
291
    return
 
292
 
 
293
  def get_location(self, vte, x, y):
 
294
    pos = ""
 
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)
 
298
    b1 = 0
 
299
    b2 = vte.allocation.height
 
300
    #determine position in rectangle
 
301
    """
 
302
    --------
 
303
    |\    /|
 
304
    | \  / |
 
305
    |  \/  |
 
306
    |  /\  |
 
307
    | /  \ |
 
308
    |/    \|
 
309
    --------
 
310
    """
 
311
    if (x*coef1 + b1 > y ) and (x*coef2 + b2 < y ):
 
312
      pos =  "right"
 
313
    if (x*coef1 + b1 > y ) and (x*coef2 + b2 > y ):
 
314
      pos = "top"
 
315
    if (x*coef1 + b1 < y ) and (x*coef2 + b2 > y ):
 
316
      pos = "left"
 
317
    if (x*coef1 + b1 < y ) and (x*coef2 + b2 < y ):
 
318
      pos = "bottom"
 
319
    return pos
 
320
 
 
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,\\\"]"
 
329
    
 
330
    self.matches['full_uri'] = self._vte.match_add(lboundry + schemes + "//(" + user + "@)?[" + hostchars  +".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?")
 
331
 
 
332
    # FreeBSD works with [[:<:]], Linux works with \<
 
333
    if self.matches['full_uri'] == -1:
 
334
      if lboundry != "\\<":
 
335
        self.add_matches(lboundry = "\\<", rboundry = "\\>")
 
336
    else:
 
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)
 
340
 
 
341
  def spawn_child (self, event=None):
 
342
    update_records = self.conf.update_records
 
343
    login = self.conf.login_shell
 
344
    args = []
 
345
    shell = ''
 
346
 
 
347
    if self.command:
 
348
      dbg ('spawn_child: using self.command: %s'%self.command)
 
349
      args = 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 ()
 
354
      shell = args[0]
 
355
 
 
356
    try:
 
357
      if os.environ['PATH'] == "":
 
358
        raise (ValueError)
 
359
      paths = os.environ['PATH'].split(':')
 
360
    except:
 
361
      paths = ['/usr/local/bin', '/usr/bin', '/bin']
 
362
    dbg ('spawn_child: found paths: "%s"'%paths)
 
363
 
 
364
    if self.conf.use_custom_command and shell[0] != '/':
 
365
     for path in paths:
 
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)
 
369
         break
 
370
 
 
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 ''
 
374
      args = []
 
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']:
 
380
            for p in paths:
 
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)
 
385
                continue
 
386
              else:
 
387
                dbg ('spawn_child: %s does exist'%shell)
 
388
                break
 
389
            if os.path.exists (shell):
 
390
              break
 
391
 
 
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)
 
396
      return (-1)
 
397
 
 
398
    if not args:
 
399
      args.append (shell)
 
400
 
 
401
    if self.conf.login_shell:
 
402
      args[0] = "-%s"%args[0]
 
403
 
 
404
    dbg ('SEGBUG: Setting WINDOWID')
 
405
    os.putenv ('WINDOWID', '%s'%self._vte.get_parent_window().xid)
 
406
 
 
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)
 
409
 
 
410
    dbg ('SEGBUG: Forked command') 
 
411
    if self._pid == -1:
 
412
      err (_('Unable to start shell: ') + shell)
 
413
      return (-1)
 
414
 
 
415
  def get_cwd (self):
 
416
    """ Return the current working directory of the subprocess.
 
417
        This function requires OS specific behaviours
 
418
    """
 
419
    cwd = self.pid_get_cwd (self._pid)
 
420
    dbg ('get_cwd found: %s'%cwd)
 
421
    return (cwd)
 
422
 
 
423
  def reconfigure_vte (self):
 
424
    # Set our emulation
 
425
    self._vte.set_emulation (self.conf.emulation)
 
426
 
 
427
    # Set our wordchars
 
428
    self._vte.set_word_chars (self.conf.word_chars)
 
429
 
 
430
    # Set our mouselation
 
431
    self._vte.set_mouse_autohide (self.conf.mouse_autohide)
 
432
 
 
433
    # Set our compatibility
 
434
    backspace = self.conf.backspace_binding
 
435
    delete = self.conf.delete_binding
 
436
 
 
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
 
440
      backbind = 2
 
441
    else:
 
442
#      backbind = vte.ERASE_AUTO_BACKSPACE
 
443
      backbind = 1
 
444
 
 
445
    if delete == "escape-sequence":
 
446
#      delbind = vte.ERASE_DELETE_SEQUENCE
 
447
      delbind = 3
 
448
    else:
 
449
#      delbind = vte.ERASE_AUTO
 
450
      delbind = 0
 
451
 
 
452
    self._vte.set_backspace_binding (backbind)
 
453
    self._vte.set_delete_binding (delbind)
 
454
 
 
455
    # Set our font
 
456
    try:
 
457
      self._vte.set_font (pango.FontDescription (self.conf.font))
 
458
    except:
 
459
      pass
 
460
 
 
461
    # Set our boldness
 
462
    self._vte.set_allow_bold (self.conf.allow_bold)
 
463
 
 
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]
 
469
    else:
 
470
      fg_color = gtk.gdk.color_parse (self.conf.foreground_color)
 
471
      bg_color = gtk.gdk.color_parse (self.conf.background_color)
 
472
      
 
473
    colors = palette.split (':')
 
474
    palette = []
 
475
    for color in colors:
 
476
      if color:
 
477
        palette.append (gtk.gdk.color_parse (color))
 
478
    self._vte.set_colors (fg_color, bg_color, palette)
 
479
 
 
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
 
483
 
 
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)
 
488
    else:
 
489
      self._vte.set_background_image_file('')
 
490
      self._vte.set_scroll_background(False)
 
491
 
 
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))
 
497
    else:
 
498
      self._vte.set_background_saturation(1)
 
499
      self._vte.set_opacity(65535)
 
500
 
 
501
    if not self._vte.is_composited():
 
502
      self._vte.set_background_transparent (background_type == "transparent")
 
503
    else:
 
504
      self._vte.set_background_transparent (False)
 
505
 
 
506
    # Set our cursor blinkiness
 
507
    self._vte.set_cursor_blinks = (self.conf.cursor_blink)
 
508
 
 
509
    # Set our audible belliness
 
510
    silent_bell = self.conf.silent_bell
 
511
    self._vte.set_audible_bell (not silent_bell)
 
512
 
 
513
    # Set our visual flashiness
 
514
    self._vte.set_visible_bell (silent_bell)
 
515
 
 
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)
 
520
 
 
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)
 
525
 
 
526
    if self.scrollbar_position != self.conf.scrollbar_position:
 
527
      self.scrollbar_position = self.conf.scrollbar_position
 
528
 
 
529
      if self.scrollbar_position == 'hidden' or self.scrollbar_position == 'disabled':
 
530
        self._scrollbar.hide ()
 
531
      else:
 
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)
 
537
 
 
538
    # Set our sloppiness
 
539
    self.focus = self.conf.focus
 
540
 
 
541
    self._vte.queue_draw ()
 
542
 
 
543
  def on_composited_changed (self, widget):
 
544
    self.reconfigure_vte ()
 
545
 
 
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 ()))
 
552
        if url:
 
553
          if (url[0][0:7] != "mailto:") & (url[1] == self.matches['email']):
 
554
            address = "mailto:" + url[0]
 
555
          else:
 
556
            address = url[0]
 
557
          self.openurl ( address )
 
558
      return False
 
559
 
 
560
    # Left mouse button should transfer focus to this vte widget
 
561
    #LP#242612:
 
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 ()
 
565
      return False
 
566
 
 
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)
 
570
      return True
 
571
 
 
572
  def on_vte_notify_enter (self, term, event):
 
573
    if (self.focus == "sloppy" or self.focus == "mouse"):
 
574
      term.grab_focus ()
 
575
      return False
 
576
 
 
577
  def do_scrollbar_toggle (self):
 
578
    self.toggle_widget_visibility (self._scrollbar)
 
579
 
 
580
  def do_title_toggle (self):
 
581
    self.toggle_widget_visibility (self._titlebox)
 
582
 
 
583
  def toggle_widget_visibility (self, widget):
 
584
    if not isinstance (widget, gtk.Widget):
 
585
      raise TypeError
 
586
 
 
587
    if widget.get_property ('visible'):
 
588
      widget.hide ()
 
589
    else:
 
590
      widget.show ()
 
591
 
 
592
  def paste_clipboard(self):
 
593
    self._vte.paste_clipboard()
 
594
    self._vte.grab_focus()
 
595
 
 
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)
 
600
 
 
601
    mask = gtk.gdk.CONTROL_MASK
 
602
    if (event.state & mask) == mask:
 
603
      if keyname == 'plus':
 
604
        self.zoom (True)
 
605
        return (True)
 
606
      elif keyname == 'minus':
 
607
        self.zoom (False)
 
608
        return (True)
 
609
      elif keyname == 'equal':
 
610
        self.zoom_orig ()
 
611
        return (True)
 
612
 
 
613
    mask = gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK | gtk.gdk.MOD1_MASK
 
614
    if (event.state & mask) == mask:
 
615
      #Top level tab
 
616
      if keyname == 'T':
 
617
        self.terminator.newtab (self, True)
 
618
        return (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)
 
623
 
 
624
    mask = gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK
 
625
    if (event.state & mask) == mask:
 
626
      if keyname == 'N':
 
627
        self.terminator.go_next (self)
 
628
        return (True)
 
629
      elif keyname == "P":
 
630
        self.terminator.go_prev (self)
 
631
        return (True)
 
632
      elif keyname == 'O':
 
633
        self.terminator.splitaxis (self, False)
 
634
        return (True)
 
635
      elif keyname == 'E':
 
636
        self.terminator.splitaxis (self, True)
 
637
        return (True)
 
638
      elif keyname == 'W':
 
639
        self.terminator.closeterm (self)
 
640
        return (True)
 
641
      elif keyname == 'C':
 
642
        self._vte.copy_clipboard ()
 
643
        return (True)
 
644
      elif keyname == 'V':
 
645
        self.paste_clipboard ()
 
646
        return (True)
 
647
      elif keyname == 'S':
 
648
        self.do_scrollbar_toggle ()
 
649
        return (True)
 
650
      elif keyname == 'T':
 
651
        self.terminator.newtab(self)
 
652
        return (True)
 
653
      elif keyname in ('Up', 'Down', 'Left', 'Right'):
 
654
          self.terminator.resizeterm (self, keyname)
 
655
          return (True)
 
656
      elif keyname  == 'Page_Down':
 
657
          self.terminator.move_tab(self, 'right')
 
658
          return (True)
 
659
      elif keyname == 'Page_Up':
 
660
          self.terminator.move_tab(self, 'left')
 
661
          return (True)
 
662
      elif keyname == 'Z':
 
663
        self.terminator.toggle_zoom (self, True)
 
664
        return (True)
 
665
      elif keyname == 'X':
 
666
        self.terminator.toggle_zoom (self)
 
667
        return (True)
 
668
      
 
669
    mask = gtk.gdk.CONTROL_MASK
 
670
    if (event.state & mask) == mask:
 
671
      if keyname  == 'Page_Down':
 
672
          self.terminator.next_tab(self)
 
673
          return (True)
 
674
      elif keyname == 'Page_Up':
 
675
          self.terminator.previous_tab(self)
 
676
          return (True)
 
677
    
 
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)
 
682
            return (True)
 
683
        mask = gtk.gdk.CONTROL_MASK
 
684
        if (event.state & mask) == mask:
 
685
            self.terminator.go_next (self)
 
686
            return (True)
 
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
 
689
    return (False)
 
690
 
 
691
  def zoom_orig (self):
 
692
    self._vte.set_font (pango.FontDescription (self.conf.font))
 
693
 
 
694
  def zoom (self, zoom_in):
 
695
    pangodesc = self._vte.get_font ()
 
696
    fontsize = pangodesc.get_size ()
 
697
 
 
698
    if fontsize > pango.SCALE and not zoom_in:
 
699
      fontsize -= pango.SCALE
 
700
    elif zoom_in:
 
701
      fontsize += pango.SCALE
 
702
 
 
703
    pangodesc.set_size (fontsize)
 
704
    self._vte.set_font (pangodesc)
 
705
 
 
706
  def on_vte_popup_menu (self, term, event):
 
707
    self.do_popup (event)
 
708
 
 
709
  def do_popup (self, event = None):
 
710
    menu = self.create_popup_menu (event)
 
711
    menu.popup (None, None, None, event.button, event.time)
 
712
 
 
713
  def create_popup_menu (self, event):
 
714
    menu = gtk.Menu ()
 
715
    url = None
 
716
 
 
717
    if event:
 
718
      url = self._vte.match_check (int (event.x / self._vte.get_char_width ()), int (event.y / self._vte.get_char_height ()))
 
719
 
 
720
    if url:
 
721
      if url[1] != self.matches['email']:
 
722
        address = url[0]
 
723
        nameopen = _("_Open Link")
 
724
        namecopy = _("_Copy Link Address")
 
725
        iconopen = gtk.image_new_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
 
726
 
 
727
        item = gtk.ImageMenuItem (nameopen)
 
728
        item.set_property('image', iconopen)
 
729
      else:
 
730
        if url[0][0:7] != "mailto:":
 
731
          address = "mailto:" + url[0]
 
732
        else:
 
733
          address = url[0]
 
734
        nameopen = _("_Send Mail To...")
 
735
        namecopy = _("_Copy Email Address")
 
736
 
 
737
        item = gtk.MenuItem (nameopen)
 
738
 
 
739
      item.connect ("activate", lambda menu_item: self.openurl (address))
 
740
      menu.append (item)
 
741
 
 
742
      item = gtk.MenuItem (namecopy)
 
743
      item.connect ("activate", lambda menu_item: self.clipboard.set_text (url[0]))
 
744
      menu.append (item)
 
745
 
 
746
      item = gtk.MenuItem ()
 
747
      menu.append (item)
 
748
 
 
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 ())
 
752
    menu.append (item)
 
753
 
 
754
    item = gtk.ImageMenuItem (gtk.STOCK_PASTE)
 
755
    item.connect ("activate", lambda menu_item: self.paste_clipboard ())
 
756
    menu.append (item)
 
757
 
 
758
    item = gtk.MenuItem ()
 
759
    menu.append (item)
 
760
 
 
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 ())
 
764
    menu.append (item)
 
765
    
 
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 ())
 
769
    menu.append (item)
 
770
 
 
771
    self._do_encoding_items (menu)
 
772
        
 
773
    item = gtk.MenuItem ()
 
774
    menu.append (item)
 
775
 
 
776
    if not self.terminator._zoomed:
 
777
      str_horiz = _("Split H_orizontally")
 
778
      str_vert = _("Split V_ertically")
 
779
 
 
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)
 
784
 
 
785
      item.connect ("activate", lambda menu_item: self.terminator.splitaxis (self, False))
 
786
      menu.append (item)
 
787
 
 
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)
 
792
 
 
793
      item.connect ("activate", lambda menu_item: self.terminator.splitaxis (self, True))
 
794
      menu.append (item)
 
795
    
 
796
      item = gtk.MenuItem (_("Open _Tab"))
 
797
      item.connect ("activate", lambda menu_item: self.terminator.newtab (self))
 
798
      menu.append (item)
 
799
 
 
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))
 
803
        menu.append (item)
 
804
      
 
805
      item = gtk.MenuItem ()
 
806
      menu.append (item)
 
807
 
 
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))
 
812
        menu.append (item)
 
813
 
 
814
        item = gtk.MenuItem (_("_Maximise terminal"))
 
815
        item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self))
 
816
        menu.append (item)
 
817
      else:
 
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))
 
821
          menu.append (item)
 
822
 
 
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))
 
826
          menu.append (item)
 
827
 
 
828
      item = gtk.MenuItem ()
 
829
      menu.append (item)
 
830
 
 
831
    item = gtk.ImageMenuItem (gtk.STOCK_CLOSE)
 
832
    item.connect ("activate", lambda menu_item: self.terminator.closeterm (self))
 
833
    menu.append (item)
 
834
 
 
835
    menu.show_all ()
 
836
    return menu
 
837
 
 
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)
 
843
      
 
844
  def _do_encoding_items (self, menu):
 
845
    active_encodings = self.conf.active_encodings
 
846
    item = gtk.MenuItem (_("Encodings"))
 
847
    menu.append (item)
 
848
    submenu = gtk.Menu ()
 
849
    item.set_submenu (submenu)
 
850
    
 
851
    current_encoding = self._vte.get_encoding ()
 
852
    group = None
 
853
    for encoding in active_encodings:
 
854
      radioitem = gtk.RadioMenuItem (group, _(encoding))
 
855
      if group is None:
 
856
        group = radioitem
 
857
        
 
858
      if encoding == current_encoding:
 
859
        radioitem.set_active (True)
 
860
      
 
861
      radioitem.connect ('activate', self.on_encoding_change, encoding)
 
862
      submenu.append (radioitem)
 
863
      
 
864
    item = gtk.MenuItem (_("Other Encodings"))
 
865
    submenu.append (item)
 
866
    #second level
 
867
 
 
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 ()))
 
872
    group = None
 
873
 
 
874
    for encoding in encodings:
 
875
      if encoding[1] in active_encodings:
 
876
        continue
 
877
 
 
878
      if encoding[1] is None:
 
879
        label = "%s %s"%(encoding[2], self._vte.get_encoding ())
 
880
      else:
 
881
        label = "%s %s"%(encoding[2], encoding[1])
 
882
        
 
883
      radioitem = gtk.RadioMenuItem (group, label)
 
884
      if group is None:
 
885
        group = radioitem
 
886
        
 
887
      if encoding[1] == current_encoding:
 
888
        radioitem.set_active (True)
 
889
      
 
890
      radioitem.connect ('activate', self.on_encoding_change, encoding[1])
 
891
      submenu.append (radioitem)
 
892
   
 
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])
 
907
 
 
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])
 
911
    return
 
912
    
 
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])
 
916
    return
 
917
 
 
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])
 
928
 
 
929
  def destroy(self):
 
930
    self._vte.destroy()
 
931