#!/usr/bin/python # Terminator - multiple gnome terminals in one window # Copyright (C) 2006-2008 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 by Chris Jones """ import gobject, gtk, pango import os, platform, sys #import version details from terminatorlib.version import * # import our configuration loader from terminatorlib import config from terminatorlib.config import dbg, err #import encoding list from terminatorlib.encoding import TerminatorEncoding # import vte-bindings try: import vte except: error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _('You need to install python bindings for libvte ("python-vte" in debian/ubuntu)')) error.run() sys.exit (1) TARGET_TYPE_VTE = 8 # Sort out cwd detection code, if available pid_get_cwd = lambda pid: None if platform.system() == 'FreeBSD': try: from terminatorlib import freebsd pid_get_cwd = lambda pid: freebsd.get_process_cwd(pid) dbg ('Using FreeBSD pid_get_cwd') except: dbg ('FreeBSD version too old for pid_get_cwd') pass elif platform.system() == 'Linux': dbg ('Using Linux pid_get_cwd') pid_get_cwd = lambda pid: os.path.realpath ('/proc/%s/cwd' % pid) else: dbg ('Unable to set a pid_get_cwd, unknown system: %s'%platform.system) # import a library for viewing URLs try: # gnome.url_show() is really useful import gnome url_show = gnome.url_show except: # webbrowser.open() is not really useful, but will do as a fallback import webbrowser url_show = webbrowser.open def openurl (url): try: if subprocess.call(["xdg-open", url]) != 0: raise except: try: url_show (url) except: pass class TerminatorTerm (gtk.VBox): matches = {} def __init__ (self, terminator, profile = None, command = None, cwd = None): gtk.VBox.__init__ (self) self.terminator = terminator self.conf = terminator.conf self.command = command self.cwd = cwd or os.getcwd(); if not os.path.exists(self.cwd) or not os.path.isdir(self.cwd): self.cwd = pwd.getpwuid(os.getuid ())[5] self.clipboard = gtk.clipboard_get (gtk.gdk.SELECTION_CLIPBOARD) self.scrollbar_position = self.conf.scrollbar_position self._vte = vte.Terminal () self._vte.set_size (80, 24) self.reconfigure_vte () self._vte.show () self._termbox = gtk.HBox () self._termbox.show() self._title = gtk.Label() self._title.show() self._titlebox = gtk.EventBox() self._titlebox.add(self._title) self.show() self.pack_start(self._titlebox, False) self.pack_start(self._termbox) if self.conf.titlebars: self._titlebox.show() else: self._titlebox.hide() self._scrollbar = gtk.VScrollbar (self._vte.get_adjustment ()) if self.scrollbar_position != "hidden" and self.scrollbar_position != "disabled": self._scrollbar.show () if self.scrollbar_position == 'left': packfunc = self._termbox.pack_end else: packfunc = self._termbox.pack_start packfunc (self._vte) packfunc (self._scrollbar, False) self._vte.connect ("key-press-event", self.on_vte_key_press) self._vte.connect ("button-press-event", self.on_vte_button_press) self._vte.connect ("popup-menu", self.on_vte_popup_menu) """drag and drop""" srcvtetargets = [ ( "vte", gtk.TARGET_SAME_APP, TARGET_TYPE_VTE ) ] dsttargets = [ ( "vte", gtk.TARGET_SAME_APP, TARGET_TYPE_VTE ), ('text/plain', 0, 0) , ("STRING", 0, 0), ("COMPOUND_TEXT", 0, 0)] self._vte.drag_source_set( gtk.gdk.CONTROL_MASK | gtk.gdk.BUTTON3_MASK, srcvtetargets, gtk.gdk.ACTION_MOVE) self._titlebox.drag_source_set( gtk.gdk.BUTTON1_MASK, srcvtetargets, gtk.gdk.ACTION_MOVE) #self._vte.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_HIGHLIGHT |gtk.DEST_DEFAULT_DROP ,dsttargets, gtk.gdk.ACTION_MOVE) self._vte.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_HIGHLIGHT |gtk.DEST_DEFAULT_DROP ,dsttargets, gtk.gdk.ACTION_MOVE) self._vte.connect("drag-begin", self.on_drag_begin, self) self._titlebox.connect("drag-begin", self.on_drag_begin, self) self._vte.connect("drag-data-get", self.on_drag_data_get, self) self._titlebox.connect("drag-data-get", self.on_drag_data_get, self) #for testing purpose: drag-motion self._vte.connect("drag-motion", self.on_drag_motion, self) self._vte.connect("drag-data-received", self.on_drag_data_received, self) if self.conf.copy_on_selection: self._vte.connect ("selection-changed", lambda widget: self._vte.copy_clipboard ()) self._vte.connect ("composited-changed", self.on_composited_changed) self._vte.connect ("window-title-changed", self.on_vte_title_change) self._vte.connect ("grab-focus", self.on_vte_focus) self._vte.connect ("focus-out-event", self.on_vte_focus_out) self._vte.connect ("focus-in-event", self.on_vte_focus_in) exit_action = self.conf.exit_action if exit_action == "restart": self._vte.connect ("child-exited", self.spawn_child) # We need to support "left" because some buggy versions of gnome-terminal # set it in some situations elif exit_action in ("close", "left"): self._vte.connect ("child-exited", lambda close_term: self.terminator.closeterm (self)) self._vte.add_events (gtk.gdk.ENTER_NOTIFY_MASK) self._vte.connect ("enter_notify_event", self.on_vte_notify_enter) self.add_matches() dbg ('SEGBUG: Setting http_proxy') env_proxy = os.getenv ('http_proxy') if not env_proxy and self.conf.http_proxy and self.conf.http_proxy != '': os.putenv ('http_proxy', self.conf.http_proxy) dbg ('SEGBUG: Setting COLORTERM') os.putenv ('COLORTERM', 'gnome-terminal') dbg ('SEGBUG: TerminatorTerm __init__ complete') def on_drag_begin(self, widget, drag_context, data): dbg ('Drag begins') widget.drag_source_set_icon_pixbuf(self.terminator.icon_theme.load_icon (APP_NAME, 48, 0)) def on_drag_data_get(self,widget, drag_context, selection_data, info, time, data): dbg ("Drag data get") selection_data.set("vte",info, str(data.terminator.term_list.index (self))) def on_drag_motion(self, widget, drag_context, x, y, time, data): dbg ("Drag Motion on ") """ x-special/gnome-icon-list text/uri-list UTF8_STRING COMPOUND_TEXT TEXT STRING text/plain;charset=utf-8 text/plain;charset=UTF-8 text/plain """ if 'text/plain' in drag_context.targets: #copy text from another widget return srcwidget = drag_context.get_source_widget() if (isinstance(srcwidget, gtk.EventBox) and srcwidget == self._titlebox) or widget == srcwidget: #on self return alloc = widget.allocation rect = gtk.gdk.Rectangle(0, 0, alloc.width, alloc.height) widget.window.invalidate_rect(rect, True) widget.window.process_updates(True) context = widget.window.cairo_create() if self.conf.use_theme_colors: color = self._vte.get_style ().text[gtk.STATE_NORMAL] else: color = gtk.gdk.color_parse (self.conf.foreground_color) context.set_source_rgba(color.red, color.green, color.blue, 0.5) pos = self.get_location(widget, x, y) topleft = (0,0) topright = (alloc.width,0) topmiddle = (alloc.width/2,0) bottomleft = (0, alloc.height) bottomright = (alloc.width,alloc.height) bottommiddle = (alloc.width/2, alloc.height) middle = (alloc.width/2, alloc.height/2) middleleft = (0, alloc.height/2) middleright = (alloc.width, alloc.height/2) #print "%f %f %d %d" %(coef1, coef2, b1,b2) coord = () if pos == "right": coord = (topright, topmiddle, bottommiddle, bottomright) if pos == "top": coord = (topleft, topright, middleright , middleleft) if pos == "left": coord = (topleft, topmiddle, bottommiddle, bottomleft) if pos == "bottom": coord = (bottomleft, bottomright, middleright , middleleft) if len(coord) > 0 : context.move_to(coord[len(coord)-1][0],coord[len(coord)-1][1]) for i in coord: context.line_to(i[0],i[1]) context.fill() def on_drag_drop(self, widget, drag_context, x, y, time): parent = widget.get_parent() dbg ('Drag drop on %s'%parent) def on_drag_data_received(self, widget, drag_context, x, y, selection_data, info, time, data): dbg ("Drag Data Received") if selection_data.type == 'text/plain': #copy text to destination #print "%s %s" % (selection_data.type, selection_data.target) txt = selection_data.data.strip() if txt[0:7] == "file://": txt = "'%s'" % txt[7:] self._vte.feed_child(txt) return widgetsrc = data.terminator.term_list[int(selection_data.data)] srcvte = drag_context.get_source_widget() #check if computation requireds if (isinstance(srcvte, gtk.EventBox) and srcvte == self._titlebox) or srcvte == widget: dbg (" on itself") return srchbox = widgetsrc dsthbox = widget.get_parent().get_parent() dstpaned = dsthbox.get_parent() srcpaned = srchbox.get_parent() if isinstance(dstpaned, gtk.Window) and isinstance(srcpaned, gtk.Window): dbg (" Only one terminal") return pos = self.get_location(widget, x, y) data.terminator.remove(widgetsrc) data.terminator.add(self, widgetsrc,pos) return def get_location(self, vte, x, y): pos = "" #get the diagonales function for the receiving widget coef1 = float(vte.allocation.height)/float(vte.allocation.width) coef2 = -float(vte.allocation.height)/float(vte.allocation.width) b1 = 0 b2 = vte.allocation.height #determine position in rectangle """ -------- |\ /| | \ / | | \/ | | /\ | | / \ | |/ \| -------- """ if (x*coef1 + b1 > y ) and (x*coef2 + b2 < y ): pos = "right" if (x*coef1 + b1 > y ) and (x*coef2 + b2 > y ): pos = "top" if (x*coef1 + b1 < y ) and (x*coef2 + b2 > y ): pos = "left" if (x*coef1 + b1 < y ) and (x*coef2 + b2 < y ): pos = "bottom" return pos def add_matches (self, lboundry="[[:<:]]", rboundry="[[:>:]]"): userchars = "-A-Za-z0-9" passchars = "-A-Za-z0-9,?;.:/!%$^*&~\"#'" hostchars = "-A-Za-z0-9" pathchars = "-A-Za-z0-9_$.+!*(),;:@&=?/~#%'" schemes = "(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:)" user = "[" + userchars + "]+(:[" + passchars + "]+)?" urlpath = "/[" + pathchars + "]*[^]'.}>) \t\r\n,\\\"]" self.matches['full_uri'] = self._vte.match_add(lboundry + schemes + "//(" + user + "@)?[" + hostchars +".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?") # FreeBSD works with [[:<:]], Linux works with \< if self.matches['full_uri'] == -1: if lboundry != "\\<": self.add_matches(lboundry = "\\<", rboundry = "\\>") else: self.matches['addr_only'] = self._vte.match_add (lboundry + "(www|ftp)[" + hostchars + "]*\.[" + hostchars + ".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?") self.matches['email'] = self._vte.match_add (lboundry + "(mailto:)?[a-zA-Z0-9][a-zA-Z0-9.+-]*@[a-zA-Z0-9][a-zA-Z0-9-]*\.[a-zA-Z0-9][a-zA-Z0-9-]+[.a-zA-Z0-9-]*" + rboundry) self.matches['nntp'] = self._vte.match_add (lboundry + '''news:[-A-Z\^_a-z{|}~!"#$%&'()*+,./0-9;:=?`]+@[-A-Za-z0-9.]+(:[0-9]+)?''' + rboundry) def spawn_child (self, event=None): update_records = self.conf.update_records login = self.conf.login_shell args = [] shell = '' if self.command: dbg ('spawn_child: using self.command: %s'%self.command) args = self.command shell = self.command[0] elif self.conf.use_custom_command: dbg ('spawn_child: using custom command: %s'%self.conf.custom_command) args = self.conf.custom_command.split () shell = args[0] try: if os.environ['PATH'] == "": raise (ValueError) paths = os.environ['PATH'].split(':') except: paths = ['/usr/local/bin', '/usr/bin', '/bin'] dbg ('spawn_child: found paths: "%s"'%paths) if self.conf.use_custom_command and shell[0] != '/': for path in paths: dbg ('spawn_child: looking for pathless custom command "%s"'%os.path.join (path, shell)) if os.path.exists (os.path.join (path, shell)): shell = os.path.join (path, shell) break if not self.command and not os.path.exists (shell): dbg ('spawn_child: hunting for a command') shell = os.getenv ('SHELL') or '' args = [] if not os.path.exists (shell): dbg ('spawn_child: No usable shell in $SHELL (%s)'%os.getenv('SHELL')) shell = pwd.getpwuid (os.getuid ())[6] or '' if not os.path.exists (shell): for i in ['bash','zsh','tcsh','ksh','csh','sh']: for p in paths: shell = os.path.join(p, i) dbg ('spawn_child: Checking if "%s" exists'%shell) if not os.path.exists (shell): dbg ('spawn_child: %s does not exist'%shell) continue else: dbg ('spawn_child: %s does exist'%shell) break if os.path.exists (shell): break if not self.command and not os.path.exists (shell): # Give up, we're completely stuck err (_('Unable to find a shell')) gobject.timeout_add (100, self.terminator.closeterm, self) return (-1) if not args: args.append (shell) if self.conf.login_shell: args[0] = "-%s"%args[0] dbg ('SEGBUG: Setting WINDOWID') os.putenv ('WINDOWID', '%s'%self._vte.get_parent_window().xid) 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)) self._pid = self._vte.fork_command (command = shell, argv = args, envv = [], loglastlog = login, logwtmp = update_records, logutmp = update_records, directory=self.cwd) dbg ('SEGBUG: Forked command') if self._pid == -1: err (_('Unable to start shell: ') + shell) return (-1) def get_cwd (self): """ Return the current working directory of the subprocess. This function requires OS specific behaviours """ cwd = pid_get_cwd (self._pid) dbg ('get_cwd found: %s'%cwd) return (cwd) def reconfigure_vte (self): # Set our emulation self._vte.set_emulation (self.conf.emulation) # Set our wordchars self._vte.set_word_chars (self.conf.word_chars) # Set our mouselation self._vte.set_mouse_autohide (self.conf.mouse_autohide) # Set our compatibility backspace = self.conf.backspace_binding delete = self.conf.delete_binding # 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 :/ if backspace == "ascii-del": # backbind = vte.ERASE_ASCII_BACKSPACE backbind = 2 else: # backbind = vte.ERASE_AUTO_BACKSPACE backbind = 1 if delete == "escape-sequence": # delbind = vte.ERASE_DELETE_SEQUENCE delbind = 3 else: # delbind = vte.ERASE_AUTO delbind = 0 self._vte.set_backspace_binding (backbind) self._vte.set_delete_binding (delbind) # Set our font try: self._vte.set_font (pango.FontDescription (self.conf.font)) except: pass # Set our boldness self._vte.set_allow_bold (self.conf.allow_bold) # Set our color scheme palette = self.conf.palette if self.conf.use_theme_colors: fg_color = self._vte.get_style ().text[gtk.STATE_NORMAL] bg_color = self._vte.get_style ().base[gtk.STATE_NORMAL] else: fg_color = gtk.gdk.color_parse (self.conf.foreground_color) bg_color = gtk.gdk.color_parse (self.conf.background_color) colors = palette.split (':') palette = [] for color in colors: if color: palette.append (gtk.gdk.color_parse (color)) self._vte.set_colors (fg_color, bg_color, palette) # Set our background image, transparency and type # Many thanks to the authors of gnome-terminal, on which this code is based. background_type = self.conf.background_type # set background image settings if background_type == "image": self._vte.set_background_image_file (self.conf.background_image) self._vte.set_scroll_background (self.conf.scroll_background) else: self._vte.set_background_image_file('') self._vte.set_scroll_background(False) # set transparency for the background (image) if background_type in ("image", "transparent"): self._vte.set_background_tint_color (bg_color) self._vte.set_background_saturation(1 - (self.conf.background_darkness)) self._vte.set_opacity(int(self.conf.background_darkness * 65535)) else: self._vte.set_background_saturation(1) self._vte.set_opacity(65535) if not self._vte.is_composited(): self._vte.set_background_transparent (background_type == "transparent") else: self._vte.set_background_transparent (False) # Set our cursor blinkiness self._vte.set_cursor_blinks = (self.conf.cursor_blink) # Set our audible belliness silent_bell = self.conf.silent_bell self._vte.set_audible_bell (not silent_bell) # Set our visual flashiness self._vte.set_visible_bell (silent_bell) # Override our flashybelliness if self.conf.force_no_bell: self._vte.set_visible_bell (False) self._vte.set_audible_bell (False) # Set our scrolliness self._vte.set_scrollback_lines (self.conf.scrollback_lines) self._vte.set_scroll_on_keystroke (self.conf.scroll_on_keystroke) self._vte.set_scroll_on_output (self.conf.scroll_on_output) if self.scrollbar_position != self.conf.scrollbar_position: self.scrollbar_position = self.conf.scrollbar_position if self.scrollbar_position == 'hidden' or self.scrollbar_position == 'disabled': self._scrollbar.hide () else: self._scrollbar.show () if self.scrollbar_position == 'right': self._termbox.reorder_child (self._vte, 0) elif self.scrollbar_position == 'left': self._termbox.reorder_child (self._scrollbar, 0) # Set our sloppiness self.focus = self.conf.focus self._vte.queue_draw () def on_composited_changed (self, widget): self.reconfigure_vte () def on_vte_button_press (self, term, event): # Left mouse button + Ctrl while over a link should open it mask = gtk.gdk.CONTROL_MASK if (event.state & mask) == mask: if event.button == 1: url = self._vte.match_check (int (event.x / self._vte.get_char_width ()), int (event.y / self._vte.get_char_height ())) if url: if (url[0][0:7] != "mailto:") & (url[1] == self.matches['email']): address = "mailto:" + url[0] else: address = url[0] openurl ( address ) return False # Left mouse button should transfer focus to this vte widget #LP#242612: # we also need to give focus on the widget where the paste occured if event.button in (1 ,2): self._vte.grab_focus () return False # Right mouse button should display a context menu if ctrl not pressed if event.button == 3 and event.state & gtk.gdk.CONTROL_MASK == 0: self.do_popup (event) return True def on_vte_notify_enter (self, term, event): if (self.focus == "sloppy" or self.focus == "mouse"): term.grab_focus () return False def do_scrollbar_toggle (self): self.toggle_widget_visibility (self._scrollbar) def do_title_toggle (self): self.toggle_widget_visibility (self._titlebox) def toggle_widget_visibility (self, widget): if not isinstance (widget, gtk.Widget): raise TypeError if widget.get_property ('visible'): widget.hide () else: widget.show () def paste_clipboard(self): self._vte.paste_clipboard() self._vte.grab_focus() #keybindings for the individual splited terminals (affects only the #the selected terminal) def on_vte_key_press (self, term, event): keyname = gtk.gdk.keyval_name (event.keyval) mask = gtk.gdk.CONTROL_MASK if (event.state & mask) == mask: if keyname == 'plus': self.zoom (True) return (True) elif keyname == 'minus': self.zoom (False) return (True) elif keyname == 'equal': self.zoom_orig () return (True) mask = gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK | gtk.gdk.MOD1_MASK if (event.state & mask) == mask: #Top level tab if keyname == 'T': self.terminator.newtab (self, True) return (True) # bindings that should be moved to Terminator as they all just call # a function of Terminator. It would be cleaner is TerminatorTerm # has absolutely no reference to Terminator. # N (next) - P (previous) - O (horizontal) - E (vertical) - W (close) mask = gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK if (event.state & mask) == mask: if keyname == 'N': self.terminator.go_next (self) return (True) elif keyname == "P": self.terminator.go_prev (self) return (True) elif keyname == 'O': self.terminator.splitaxis (self, False) return (True) elif keyname == 'E': self.terminator.splitaxis (self, True) return (True) elif keyname == 'W': self.terminator.closeterm (self) return (True) elif keyname == 'C': self._vte.copy_clipboard () return (True) elif keyname == 'V': self.paste_clipboard () return (True) elif keyname == 'S': self.do_scrollbar_toggle () return (True) elif keyname == 'T': self.terminator.newtab(self) return (True) elif keyname in ('Up', 'Down', 'Left', 'Right'): self.terminator.resizeterm (self, keyname) return (True) elif keyname == 'Page_Down': self.terminator.move_tab(self, 'right') return (True) elif keyname == 'Page_Up': self.terminator.move_tab(self, 'left') return (True) elif keyname == 'Z': self.terminator.toggle_zoom (self, True) return (True) elif keyname == 'X': self.terminator.toggle_zoom (self) return (True) mask = gtk.gdk.CONTROL_MASK if (event.state & mask) == mask: if keyname == 'Page_Down': self.terminator.next_tab(self) return (True) elif keyname == 'Page_Up': self.terminator.previous_tab(self) return (True) if keyname and (keyname == 'Tab' or keyname.endswith('_Tab')): mask = gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK if (event.state & mask) == mask: self.terminator.go_prev (self) return (True) mask = gtk.gdk.CONTROL_MASK if (event.state & mask) == mask: self.terminator.go_next (self) return (True) # Warning, mask value is either gtk.gdk.CONTROL_MASK or gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK # if you intend to use it, reinit it return (False) def zoom_orig (self): self._vte.set_font (pango.FontDescription (self.conf.font)) def zoom (self, zoom_in): pangodesc = self._vte.get_font () fontsize = pangodesc.get_size () if fontsize > pango.SCALE and not zoom_in: fontsize -= pango.SCALE elif zoom_in: fontsize += pango.SCALE pangodesc.set_size (fontsize) self._vte.set_font (pangodesc) def on_vte_popup_menu (self, term, event): self.do_popup (event) def do_popup (self, event = None): menu = self.create_popup_menu (event) menu.popup (None, None, None, event.button, event.time) def create_popup_menu (self, event): menu = gtk.Menu () url = None if event: url = self._vte.match_check (int (event.x / self._vte.get_char_width ()), int (event.y / self._vte.get_char_height ())) if url: if url[1] != self.matches['email']: address = url[0] nameopen = _("_Open Link") namecopy = _("_Copy Link Address") iconopen = gtk.image_new_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU) item = gtk.ImageMenuItem (nameopen) item.set_property('image', iconopen) else: if url[0][0:7] != "mailto:": address = "mailto:" + url[0] else: address = url[0] nameopen = _("_Send Mail To...") namecopy = _("_Copy Email Address") item = gtk.MenuItem (nameopen) item.connect ("activate", lambda menu_item: openurl (address)) menu.append (item) item = gtk.MenuItem (namecopy) item.connect ("activate", lambda menu_item: self.clipboard.set_text (url[0])) menu.append (item) item = gtk.MenuItem () menu.append (item) item = gtk.ImageMenuItem (gtk.STOCK_COPY) item.connect ("activate", lambda menu_item: self._vte.copy_clipboard ()) item.set_sensitive (self._vte.get_has_selection ()) menu.append (item) item = gtk.ImageMenuItem (gtk.STOCK_PASTE) item.connect ("activate", lambda menu_item: self.paste_clipboard ()) menu.append (item) item = gtk.MenuItem () menu.append (item) item = gtk.CheckMenuItem (_("Show _scrollbar")) item.set_active (self._scrollbar.get_property ('visible')) item.connect ("toggled", lambda menu_item: self.do_scrollbar_toggle ()) menu.append (item) item = gtk.CheckMenuItem (_("Show _titlebar")) item.set_active (self._titlebox.get_property ('visible')) item.connect ("toggled", lambda menu_item: self.do_title_toggle ()) menu.append (item) self._do_encoding_items (menu) item = gtk.MenuItem () menu.append (item) if not self.terminator._zoomed: str_horiz = _("Split H_orizontally") str_vert = _("Split V_ertically") item = gtk.ImageMenuItem (str_horiz) item_image = gtk.Image () item_image.set_from_icon_name (APP_NAME + '_horiz', gtk.ICON_SIZE_MENU) item.set_image (item_image) item.connect ("activate", lambda menu_item: self.terminator.splitaxis (self, False)) menu.append (item) item = gtk.ImageMenuItem (str_vert) item_image = gtk.Image () item_image.set_from_icon_name (APP_NAME + '_vert', gtk.ICON_SIZE_MENU) item.set_image (item_image) item.connect ("activate", lambda menu_item: self.terminator.splitaxis (self, True)) menu.append (item) item = gtk.MenuItem (_("Open _Tab")) item.connect ("activate", lambda menu_item: self.terminator.newtab (self)) menu.append (item) if self.conf.extreme_tabs: item = gtk.MenuItem (_("Open Top Level Tab")) item.connect ("activate", lambda menu_item: self.terminator.newtab (self, True)) menu.append (item) item = gtk.MenuItem () menu.append (item) if len (self.terminator.term_list) > 1: if not self.terminator._zoomed: item = gtk.MenuItem (_("_Zoom terminal")) item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self, True)) menu.append (item) item = gtk.MenuItem (_("_Maximise terminal")) item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self)) menu.append (item) else: if self.terminator._zoomed and not self.terminator._maximised: item = gtk.MenuItem (_("_Unzoom terminal")) item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self, True)) menu.append (item) if self.terminator._zoomed and self.terminator._maximised: item = gtk.MenuItem (_("U_nmaximise terminal")) item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self)) menu.append (item) item = gtk.MenuItem () menu.append (item) item = gtk.ImageMenuItem (gtk.STOCK_CLOSE) item.connect ("activate", lambda menu_item: self.terminator.closeterm (self)) menu.append (item) menu.show_all () return menu def on_encoding_change (self, widget, encoding): current = self._vte.get_encoding () if current != encoding: dbg ('Setting Encoding to: %s'%encoding) self._vte.set_encoding (encoding) def _do_encoding_items (self, menu): active_encodings = self.conf.active_encodings item = gtk.MenuItem (_("Encodings")) menu.append (item) submenu = gtk.Menu () item.set_submenu (submenu) current_encoding = self._vte.get_encoding () group = None for encoding in active_encodings: radioitem = gtk.RadioMenuItem (group, _(encoding)) if group is None: group = radioitem if encoding == current_encoding: radioitem.set_active (True) radioitem.connect ('activate', self.on_encoding_change, encoding) submenu.append (radioitem) item = gtk.MenuItem (_("Other Encodings")) submenu.append (item) #second level submenu = gtk.Menu () item.set_submenu (submenu) encodings = TerminatorEncoding ().get_list () encodings.sort (lambda x, y: cmp (x[2].lower (), y[2].lower ())) group = None for encoding in encodings: if encoding[1] in active_encodings: continue if encoding[1] is None: label = "%s %s"%(encoding[2], self._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', self.on_encoding_change, encoding[1]) submenu.append (radioitem) def on_vte_title_change(self, vte): if self.conf.titletips: vte.set_property ("has-tooltip", True) vte.set_property ("tooltip-text", vte.get_window_title ()) #set the title anyhow, titlebars setting only show/hide the label self._title.set_text(vte.get_window_title ()) self.terminator.set_window_title("%s: %s" %(APP_NAME.capitalize(), vte.get_window_title ())) notebookpage = self.terminator.get_first_notebook_page(vte) while notebookpage != None: notebookpage[0].set_tab_label_text(notebookpage[1], vte.get_window_title ()) notebookpage = self.terminator.get_first_notebook_page(notebookpage[0]) def on_vte_focus_in(self, vte, event): self._titlebox.modify_bg(gtk.STATE_NORMAL,self.terminator.window.get_style().bg[gtk.STATE_SELECTED]) self._title.modify_fg(gtk.STATE_NORMAL, self.terminator.window.get_style().fg[gtk.STATE_SELECTED]) return def on_vte_focus_out(self, vte, event): self._titlebox.modify_bg(gtk.STATE_NORMAL, self.terminator.window.get_style().bg[gtk.STATE_NORMAL]) self._title.modify_fg(gtk.STATE_NORMAL, self.terminator.window.get_style().fg[gtk.STATE_NORMAL]) return def on_vte_focus(self, vte): if vte.get_window_title (): self.terminator.set_window_title("%s: %s" %(APP_NAME.capitalize(), vte.get_window_title ())) notebookpage = self.terminator.get_first_notebook_page(vte) while notebookpage != None: notebookpage[0].set_tab_label_text(notebookpage[1], vte.get_window_title ()) notebookpage = self.terminator.get_first_notebook_page(notebookpage[0]) def destroy(self): self._vte.destroy() class Terminator: def __init__ (self, profile = None, command = None, fullscreen = False, maximise = False, borderless = False): self.profile = profile self.command = command self._zoomed = False self._maximised = False self._fullscreen = False self._f11_modifier = False self.term_list = [] stores = [] stores.append (config.TerminatorConfValuestoreRC ()) try: import gconf if self.profile: self.profile = gconf.escape_key (self.profile, -1) store = config.TerminatorConfValuestoreGConf (self.profile) store.set_reconfigure_callback (self.reconfigure_vtes) dbg ('Terminator__init__: comparing %s and %s'%(self.profile, store.profile.split ('/').pop ())) if self.profile == store.profile.split ('/').pop (): # If we have been given a profile, and we loaded it, we should be higher priority than RC dbg ('Terminator__init__: placing GConf before RC') stores.insert (0, store) else: stores.append (store) except: pass self.conf = config.TerminatorConfig (stores) self.icon_theme = gtk.IconTheme () if self.conf.f11_modifier: self._f11_modifier = True if self.conf.handle_size in range (0,6): gtk.rc_parse_string(""" style "terminator-paned-style" { GtkPaned::handle_size = %s } class "GtkPaned" style "terminator-paned-style" """ % self.conf.handle_size) self.window = gtk.Window () self.window.set_title (APP_NAME.capitalize()) try: self.window.set_icon (self.icon_theme.load_icon (APP_NAME, 48, 0)) except: self.icon = self.window.render_icon (gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_BUTTON) self.window.set_icon (self.icon) self.window.connect ("key-press-event", self.on_key_press) self.window.connect ("delete_event", self.on_delete_event) self.window.connect ("destroy", self.on_destroy_event) self.window.connect ("window-state-event", self.on_window_state_changed) self.window.set_property ('allow-shrink', True) if fullscreen or self.conf.fullscreen: self.fullscreen_toggle () if maximise or self.conf.maximise: self.maximize () if borderless or self.conf.borderless: self.window.set_decorated (False) # Set RGBA colormap if possible so VTE can use real alpha # channels for transparency. screen = self.window.get_screen() colormap = screen.get_rgba_colormap() if colormap: self.window.set_colormap(colormap) # Start out with just one terminal # FIXME: This should be really be decided from some kind of profile term = (TerminatorTerm (self, self.profile, self.command)) self.term_list = [term] self.window.add (term) term._titlebox.hide() self.window.show () term.spawn_child () def maximize (self): """ Maximize the Terminator window.""" self.window.maximize () def fullscreen_toggle (self): """ Toggle the fullscreen state of the window. If it is in fullscreen state, it will be unfullscreened. If it is not, it will be set to fullscreen state. """ if self._fullscreen: self.window.unfullscreen () else: self.window.fullscreen () def on_window_state_changed (self, window, event): state = event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN self._fullscreen = bool (state) return (False) def on_delete_event (self, window, event, data=None): if len (self.term_list) == 1: return False # show dialog dialog = gtk.Dialog (_("Close?"), window, gtk.DIALOG_MODAL, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT)) dialog.set_has_separator (False) dialog.set_resizable (False) primairy = gtk.Label (_('Close all terminals?')) primairy.set_use_markup (True) primairy.set_alignment (0, 0.5) secundairy = gtk.Label (_("This window has %s terminals open. Closing the window will also close all terminals.") % len(self.term_list)) secundairy.set_line_wrap(True) primairy.set_alignment (0, 0.5) labels = gtk.VBox () labels.pack_start (primairy, False, False, 6) labels.pack_start (secundairy, 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 () result = dialog.run () dialog.destroy () return not (result == gtk.RESPONSE_ACCEPT) def on_destroy_event (self, widget, data=None): gtk.main_quit () # keybindings for the whole terminal window (affects the main # windows containing the splited terminals) def on_key_press (self, window, event): """ Callback for the window to determine what to do with special keys. Currently handled key-combo's: * F11: toggle fullscreen state of the window. * CTRL - SHIFT - Q: close all terminals """ keyname = gtk.gdk.keyval_name (event.keyval) mask = gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK if (keyname == 'F11' and (self._f11_modifier == False or event.state & mask)): self.fullscreen_toggle () return (True) if (event.state & mask) == mask: if keyname == 'Q': if not self.on_delete_event (window, gtk.gdk.Event (gtk.gdk.DELETE)): self.on_destroy_event (window, gtk.gdk.Event (gtk.gdk.DESTROY)) def set_window_title(self, title): """ Modifies Terminator window title """ self.window.set_title(title) def add(self, widget, terminal, pos = "bottom"): """ Add a term to another at position pos """ vertical = pos in ("top", "bottom") pane = (vertical) and gtk.VPaned () or gtk.HPaned () # get the parent of the provided terminal parent = widget.get_parent () dbg ('SEGBUG: add() Got parent') if isinstance (parent, gtk.Window): dbg ('SEGBUG: parent is a gtk.Window') # We have just one term parent.remove(widget) dbg ('SEGBUG: removed widget from window') if pos in ("top", "left"): dbg ('SEGBUG: pos is in top/left') pane.pack1 (terminal, True, True) dbg ('SEGBUG: packed terminal') pane.pack2 (widget, True, True) dbg ('SEGBUG: packed widget') else: dbg ('SEGBUG: pos is not in top/left') pane.pack1 (widget, True, True) dbg ('SEGBUG: packed widget') pane.pack2 (terminal, True, True) dbg ('SEGBUG: packed terminal') parent.add (pane) dbg ('SEGBUG: added pane to parent') position = (vertical) and parent.allocation.height \ or parent.allocation.width if (isinstance (parent, gtk.Notebook) or isinstance (parent, gtk.Window)) and widget.conf.titlebars: #not the only term in the notebook/window anymore, need to reshow the title dbg ('SEGBUG: Showing _titlebox') widget._titlebox.show() if isinstance (parent, gtk.Notebook): dbg ('SEGBUG: Parent is a notebook') page = -1 for i in range(0, parent.get_n_pages()): if parent.get_nth_page(i) == widget: page = i break widget.reparent (pane) if pos in ("top", "left"): pane.remove(widget) pane.pack1 (terminal, True, True) pane.pack2 (widget, True, True) else: pane.pack1 (widget, True, True) pane.pack2 (terminal, True, True) #parent.remove_page(page) pane.show() parent.insert_page(pane, None, page) parent.set_tab_label_text(pane, widget._vte.get_window_title()) parent.set_tab_label_packing(pane, True, True, gtk.PACK_START) parent.set_tab_reorderable(pane, True) parent.set_current_page(page) position = (vertical) and parent.allocation.height \ or parent.allocation.width if isinstance (parent, gtk.Paned): dbg ('SEGBUG: parent is a Paned') # We are inside a split term position = (vertical) and widget.allocation.height \ or widget.allocation.width dbg ('SEGBUG: Preparing to reparent sibling') if (widget == parent.get_child1 ()): widget.reparent (pane) parent.pack1 (pane, True, True) else: widget.reparent (pane) parent.pack2 (pane, True, True) if pos in ("top", "left"): dbg ('SEGBUG: pos is in top/left. Removing and re-ordering') pane.remove(widget) pane.pack1 (terminal, True, True) pane.pack2 (widget, True, True) else: dbg ('SEGBUG: pos is not in top/left. Packing') pane.pack1 (widget, True, True) pane.pack2 (terminal, True, True) dbg ('SEGBUG: packing widget and terminal') pane.pack1 (widget, True, True) pane.pack2 (terminal, True, True) dbg ('SEGBUG: packed widget and terminal') # show all, set position of the divider dbg ('SEGBUG: Showing pane') pane.show () dbg ('SEGBUG: Showed pane') pane.set_position (position / 2) dbg ('SEGBUG: Set position') terminal.show () # insert the term reference into the list index = self.term_list.index (widget) if pos in ('bottom', 'right'): index = index + 1 self.term_list.insert (index, terminal) # make the new terminal grab the focus terminal._vte.grab_focus () return (terminal) def on_page_reordered(self, notebook, child, page_num): #page has been reordered, we need to get the # first term and last term dbg ("Reordered: %d"%page_num) nbpages = notebook.get_n_pages() if nbpages == 1: dbg("[ERROR] only one page in on_page_reordered") first = self._notebook_first_term(notebook.get_nth_page(page_num)) last = self._notebook_last_term(notebook.get_nth_page(page_num)) firstidx = self.term_list.index(first) lastidx = self.term_list.index(last) termslice = self.term_list[firstidx:lastidx+1] #remove them from the list for term in termslice: self.term_list.remove(term) if page_num == 0: #first page, we insert before the first term of next page nexttab = notebook.get_nth_page(1) sibling = self._notebook_first_term(nexttab) siblingindex = self.term_list.index(sibling) for term in termslice: self.term_list.insert(siblingindex, term) siblingindex += 1 else: #other pages, we insert after the last term of previous page previoustab = notebook.get_nth_page(page_num - 1) sibling = self._notebook_last_term(previoustab) siblingindex = self.term_list.index(sibling) for term in termslice: siblingindex += 1 self.term_list.insert(siblingindex, term) #for page reorder, we need to get the first term of a notebook def notebook_first_term(self, notebook): return self._notebook_first_term(notebook.get_nth_page(0)) def _notebook_first_term(self, child): if isinstance(child, TerminatorTerm): return child elif isinstance(child, gtk.Paned): return self._notebook_first_term(child.get_child1()) elif isinstance(child, gtk.Notebook): return self._notebook_first_term(child.get_nth_page(0)) dbg("[ERROR] unsupported class %s in _notebook_first_term" % child.__class__.__name__) return None #for page reorder, we need to get the last term of a notebook def notebook_last_term(self, notebook): return self._notebook_last_term(notebook.get_nth_page(notebook.get_n_pages()-1)) def _notebook_last_term(self, child): if isinstance(child, TerminatorTerm): return child elif isinstance(child, gtk.Paned): return self._notebook_last_term(child.get_child2()) elif isinstance(child, gtk.Notebook): return self._notebook_last_term(child.get_nth_page(child.get_n_pages()-1)) dbg("[ERROR] unsupported class %s in _notebook_last_term" % child.__class__.__name__) return None def newtab(self,widget, toplevel = False): if self._zoomed: # We don't want to add a new tab while we are zoomed in on a terminal dbg ("newtab function called, but Terminator was in zoomed terminal mode.") return terminal = TerminatorTerm (self, self.profile, None, widget.get_cwd()) #only one term, we don't show the title terminal._titlebox.hide() if self.conf.extreme_tabs and not toplevel: parent = widget.get_parent () child = widget else: child = self.window.get_children()[0] parent = child.get_parent() if isinstance(parent, gtk.Paned) or (isinstance(parent, gtk.Window) and ((self.conf.extreme_tabs and not toplevel) or not isinstance(child, gtk.Notebook))): #no notebook yet. notebook = gtk.Notebook() notebook.set_tab_pos(gtk.POS_TOP) notebook.connect('page-reordered',self.on_page_reordered) notebook.set_property('homogeneous', True) notebook.set_tab_reorderable(widget, True) if isinstance(parent, gtk.Paned): if parent.get_child1() == child: child.reparent(notebook) parent.pack1(notebook) else: child.reparent(notebook) parent.pack2(notebook) elif isinstance(parent, gtk.Window): child.reparent(notebook) parent.add(notebook) notebook.set_tab_reorderable(child,True) notebooklabel = "" if isinstance(child, TerminatorTerm): child._titlebox.hide() if widget._vte.get_window_title() is not None: notebooklabel = widget._vte.get_window_title() notebook.set_tab_label_text(child, notebooklabel) notebook. set_tab_label_packing(child, True, True, gtk.PACK_START) notebook.show() elif isinstance(parent, gtk.Notebook): notebook = parent elif isinstance(parent, gtk.Window) and isinstance(child, gtk.Notebook): notebook = child else: return (False) ## NOTE ## Here we need to append to the notebook before we can ## spawn the terminal (WINDOW_ID needs to be set) notebook.append_page(terminal,None) terminal.show () terminal.spawn_child () ## Some gtk/vte weirdness ## If we don't use this silly test, ## terminal._vte.get_window_title() might return ## bogus values notebooklabel = "" if terminal._vte.get_window_title() is not None: notebooklabel = terminal._vte.get_window_title() notebook.set_tab_label_text(terminal, notebooklabel) notebook.set_tab_label_packing(terminal, True, True, gtk.PACK_START) notebook.set_tab_reorderable(terminal,True) ## Now, we set focus on the new term notebook.set_current_page(-1) terminal._vte.grab_focus () #adding a new tab, thus we need to get the # last term of the previous tab and add # the new term just after sibling = self._notebook_last_term(notebook.get_nth_page(notebook.page_num(terminal)-1)) index = self.term_list.index(sibling) self.term_list.insert (index + 1, terminal) return (True) return terminal def splitaxis (self, widget, vertical=True): """ Split the provided widget on the horizontal or vertical axis. """ if self._zoomed: # We don't want to split the terminal while we are in zoomed mode dbg ("splitaxis function called, but Terminator was in zoomed mode.") return # create a new terminal and parent pane. dbg ('SEGBUG: Creating TerminatorTerm') terminal = TerminatorTerm (self, self.profile, None, widget.get_cwd()) dbg ('SEGBUG: Created TerminatorTerm') pos = vertical and "right" or "bottom" dbg ('SEGBUG: Position is: %s'%pos) self.add(widget, terminal, pos) dbg ('SEGBUG: added TerminatorTerm to container') terminal.show () dbg ('SEGBUG: showed TerminatorTerm') terminal.spawn_child () dbg ('SEGBUG: spawned child') return terminal def remove(self, widget): """Remove a TerminatorTerm from the Terminator view and terms list Returns True on success, False on failure""" parent = widget.get_parent () sibling = None focus_on_close = 'prev' if isinstance (parent, gtk.Window): # We are the only term if not self.on_delete_event (parent, gtk.gdk.Event (gtk.gdk.DELETE)): self.on_destroy_event (parent, gtk.gdk.Event (gtk.gdk.DESTROY)) return if isinstance (parent, gtk.Paned): index = self.term_list.index (widget) grandparent = parent.get_parent () # Discover sibling while all objects exist if widget == parent.get_child1 (): sibling = parent.get_child2 () focus_on_close = 'next' if widget == parent.get_child2 (): sibling = parent.get_child1 () if not sibling: # something is wrong, give up err ("Error: %s is not a child of %s"%(widget, parent)) return False parent.remove(widget) if isinstance(grandparent, gtk.Notebook): page = -1 for i in range(0, grandparent.get_n_pages()): if grandparent.get_nth_page(i) == parent: page = i break parent.remove(sibling) grandparent.remove_page(page) grandparent.insert_page(sibling, None,page) grandparent.set_tab_label_packing(sibling, True, True, gtk.PACK_START) grandparent.set_tab_reorderable(sibling, True) grandparent.set_current_page(page) else: grandparent.remove (parent) sibling.reparent (grandparent) if not self._zoomed: grandparent.resize_children() parent.destroy () if isinstance(sibling, TerminatorTerm) and isinstance(sibling.get_parent(), gtk.Notebook): sibling._titlebox.hide() self.term_list.remove (widget) elif isinstance (parent, gtk.Notebook): parent.remove(widget) nbpages = parent.get_n_pages() index = self.term_list.index (widget) self.term_list.remove (widget) if nbpages == 1: sibling = parent.get_nth_page(0) parent.remove(sibling) gdparent = parent.get_parent() if isinstance(gdparent, gtk.Window): gdparent.remove(parent) gdparent.add(sibling) elif isinstance(gdparent, gtk.Paned): if gdparent.get_child1() == parent: gdparent.remove(parent) gdparent.pack1(sibling) else: gdparent.remove(parent) gdparent.pack2(sibling) if isinstance(sibling, TerminatorTerm) and sibling.conf.titlebars and sibling.conf.extreme_tabs: sibling._titlebox.show() parent.destroy() if self.conf.focus_on_close == 'prev' or ( self.conf.focus_on_close == 'auto' and focus_on_close == 'prev'): if index == 0: index = 1 self.term_list[index - 1]._vte.grab_focus () self._set_current_notebook_page_recursive(self.term_list[index - 1]) elif self.conf.focus_on_close == 'next' or ( self.conf.focus_on_close == 'auto' and focus_on_close == 'next'): if index == len(self.term_list): index = index - 1 self.term_list[index]._vte.grab_focus () self._set_current_notebook_page_recursive(self.term_list[index]) if len(self.term_list) == 1: self.term_list[0]._titlebox.hide() return True def closeterm (self, widget): if self._zoomed: # We are zoomed, pop back out to normal layout before closing dbg ("closeterm function called while in zoomed mode. Restoring previous layout before closing.") self.toggle_zoom(widget, not self._maximised) if self.remove(widget): widget.destroy () return True return False def go_next (self, term): current = self.term_list.index (term) next = None if self.conf.cycle_term_tab: notebookpage = self.get_first_notebook_page(term) if notebookpage: last = self._notebook_last_term(notebookpage[1]) first = self._notebook_first_term(notebookpage[1]) if term == last: next = self.term_list.index(first) if next is None: if current == len (self.term_list) - 1: next = 0 else: next = current + 1 nextterm = self.term_list[next] ##we need to set the current page of each notebook self._set_current_notebook_page_recursive(nextterm) nextterm._vte.grab_focus () def go_prev (self, term): current = self.term_list.index (term) previous = None if self.conf.cycle_term_tab: notebookpage = self.get_first_notebook_page(term) if notebookpage: last = self._notebook_last_term(notebookpage[1]) first = self._notebook_first_term(notebookpage[1]) if term == first: previous = self.term_list.index(last) if previous is None: if current == 0: previous = len (self.term_list) - 1 else: previous = current - 1 #self.window.set_title(self.term_list[previous]._vte.get_window_title()) previousterm = self.term_list[previous] ##we need to set the current page of each notebook self._set_current_notebook_page_recursive(previousterm) previousterm._vte.grab_focus () def _set_current_notebook_page_recursive(self, widget): page = self.get_first_notebook_page(widget) while page: child = None page_num = page[0].page_num(page[1]) page[0].set_current_page(page_num) page = self.get_first_notebook_page(page[0]) def resizeterm (self, widget, keyname): vertical = False if keyname in ('Up', 'Down'): vertical = True elif keyname in ('Left', 'Right'): vertical = False else: return parent = self.get_first_parent_paned(widget,vertical) if parent == None: return #We have a corresponding parent pane # #allocation = parent.get_allocation() if keyname in ('Up', 'Down'): maxi = parent.get_child1().get_allocation().height + parent.get_child2().get_allocation().height - 1 else: maxi = parent.get_child1().get_allocation().width + parent.get_child2().get_allocation().width - 1 move = 10 if keyname in ('Up', 'Left'): move = -10 move = max(2, parent.get_position() + move) move = min(maxi, move) parent.set_position(move) def previous_tab(self, term): notebook = self.get_first_parent_notebook(term) notebook.prev_page() return def next_tab(self, term): notebook = self.get_first_parent_notebook(term) notebook.next_page() return def move_tab(self, term, direction): dbg("moving to direction %s" % direction) (notebook, page) = self.get_first_notebook_page(term) page_num = notebook.page_num(page) nbpages = notebook.get_n_pages() #dbg ("%s %s %s %s" % (page_num, nbpages,notebook, page)) if page_num == 0 and direction == 'left': new_page_num = nbpages elif page_num == nbpages - 1 and direction == 'right': new_page_num = 0 elif direction == 'left': new_page_num = page_num - 1 elif direction == 'right': new_page_num = page_num + 1 else: dbg("[ERROR] unhandled combination in move_tab: direction = %s page_num = %d" % (direction, page_num)) return False notebook.reorder_child(page, new_page_num) return True def get_first_parent_notebook(self, widget): if isinstance (widget, gtk.Window): return None parent = widget.get_parent() if isinstance (parent, gtk.Notebook): return parent return self.get_first_parent_notebook(parent) def get_first_parent_paned (self, widget, vertical = None): """This method returns the first parent pane of a widget. if vertical is True returns the first VPaned if vertical is False return the first Hpaned if is None return the First Paned""" if isinstance (widget, gtk.Window): return None parent = widget.get_parent() if isinstance (parent, gtk.Paned) and vertical is None: return parent if isinstance (parent, gtk.VPaned) and vertical: return parent elif isinstance (parent, gtk.HPaned) and not vertical: return parent return self.get_first_parent_paned(parent, vertical) def get_first_notebook_page(self, widget): if isinstance (widget, gtk.Window): return None parent = widget.get_parent() if isinstance (parent, gtk.Notebook): page = -1 for i in range(0, parent.get_n_pages()): if parent.get_nth_page(i) == widget: return (parent, widget) return self.get_first_notebook_page(parent) def reconfigure_vtes (self): for term in self.term_list: term.reconfigure_vte () def toggle_zoom(self, widget, fontscale = False): if not self._zoomed: widget._titlebars = widget._titlebox.get_property ('visible') dbg ('toggle_zoom: not zoomed. remembered titlebar setting of %s'%widget._titlebars) if widget._titlebars: widget._titlebox.hide() self.zoom_term (widget, fontscale) else: dbg ('toggle_zoom: zoomed. restoring titlebar setting of %s'%widget._titlebars) self.unzoom_term (widget, True) if widget._titlebars and \ len(self.term_list) > 1 \ and \ (isinstance(widget, TerminatorTerm) and isinstance(widget.get_parent(),gtk.Paned))\ : widget._titlebox.show() widget._vte.grab_focus() def zoom_term (self, widget, fontscale = False): """Maximize to full window an instance of TerminatorTerm.""" self.old_font = widget._vte.get_font () self.old_columns = widget._vte.get_column_count () self.old_rows = widget._vte.get_row_count () self.old_parent = widget.get_parent() if isinstance(self.old_parent, gtk.Window): return if isinstance(self.old_parent, gtk.Notebook): self.old_page = self.old_parent.get_current_page() self.window_child = self.window.get_children()[0] self.window.remove(self.window_child) self.old_parent.remove(widget) self.window.add(widget) self._zoomed = True if fontscale: self.cnid = widget.connect ("size-allocate", self.zoom_scale_font) else: self._maximised = True widget._vte.grab_focus () def zoom_scale_font (self, widget, allocation): new_columns = widget._vte.get_column_count () new_rows = widget._vte.get_row_count () new_font = widget._vte.get_font () dbg ('zoom_scale_font: I just went from %dx%d to %dx%d. Raa!'%(self.old_columns, self.old_rows, new_columns, new_rows)) if new_rows != self.old_rows: titleheight = widget._titlebox.get_allocation().height vtecharheight = widget._vte.get_char_height() rowdiff = new_rows - self.old_rows + 2 dbg ('zoom_scale_font: titlebox height is %d, char_height is %d'%(titleheight, vtecharheight)) dbg ('zoom_scale_font: lhs: %d, rhs: %f'%((titleheight / vtecharheight), rowdiff)) care_height = (rowdiff <= vtecharheight / rowdiff) dbg ('zoom_scale_font: caring about height difference: %s'%care_height) else: care_height = False if (new_rows <= self.old_rows) or care_height or (new_columns <= self.old_columns): dbg ('zoom_scale_font: Which means I didnt scale on one axis (col: %s, row: %s). Bailing'%((new_columns <= self.old_columns), (new_rows <= self.old_rows))) return old_area = self.old_columns * self.old_rows new_area = new_columns * new_rows area_factor = new_area / old_area dbg ('zoom_scale_font: My area changed from %d characters to %d characters, a factor of %f.'%(old_area, new_area, area_factor)) new_font.set_size (self.old_font.get_size() * (area_factor / 2)) dbg ('zoom_scale_font: Scaled font from %f to %f'%(self.old_font.get_size () / pango.SCALE, new_font.get_size () / pango.SCALE)) widget._vte.set_font (new_font) widget.disconnect (self.cnid) def unzoom_term (self, widget, fontscale = False): """Proof of concept: Go back to previous application widget structure. """ if self._zoomed: if fontscale: widget._vte.set_font (self.old_font) self._zoomed = False self._maximised = False self.window.remove(widget) self.window.add(self.window_child) self.old_parent.add(widget) if isinstance(self.old_parent, gtk.Notebook): self.old_parent.set_current_page(self.old_page) widget._vte.grab_focus ()