2009-11-22 03:53:38 +00:00
|
|
|
# Terminator by Chris Jones <cmsj@tenshu.net>
|
|
|
|
# GPL v2 only
|
2009-11-22 04:28:39 +00:00
|
|
|
"""notebook.py - classes for the notebook widget"""
|
2009-11-22 03:53:38 +00:00
|
|
|
|
2019-02-28 20:15:11 +00:00
|
|
|
from functools import cmp_to_key
|
2014-09-19 14:08:08 +00:00
|
|
|
from gi.repository import GObject
|
|
|
|
from gi.repository import Gtk
|
2017-02-06 03:37:29 +00:00
|
|
|
from gi.repository import Gdk
|
2015-08-03 18:22:15 +00:00
|
|
|
from gi.repository import Gio
|
2009-11-22 03:53:38 +00:00
|
|
|
|
2018-04-24 18:22:10 +00:00
|
|
|
from .terminator import Terminator
|
|
|
|
from .config import Config
|
|
|
|
from .factory import Factory
|
|
|
|
from .container import Container
|
|
|
|
from .editablelabel import EditableLabel
|
|
|
|
from .translation import _
|
|
|
|
from .util import err, dbg, enumerate_descendants, make_uuid
|
2009-11-22 03:53:38 +00:00
|
|
|
|
2014-09-19 14:08:08 +00:00
|
|
|
class Notebook(Container, Gtk.Notebook):
|
|
|
|
"""Class implementing a Gtk.Notebook container"""
|
2009-11-23 15:17:33 +00:00
|
|
|
window = None
|
2013-12-18 17:06:59 +00:00
|
|
|
last_active_term = None
|
|
|
|
pending_on_tab_switch = None
|
|
|
|
pending_on_tab_switch_args = None
|
2009-11-22 03:53:38 +00:00
|
|
|
|
2009-11-22 04:28:39 +00:00
|
|
|
def __init__(self, window):
|
2009-11-22 03:53:38 +00:00
|
|
|
"""Class initialiser"""
|
2014-09-19 14:08:08 +00:00
|
|
|
if isinstance(window.get_child(), Gtk.Notebook):
|
2009-11-22 04:28:39 +00:00
|
|
|
err('There is already a Notebook at the top of this window')
|
|
|
|
raise(ValueError)
|
|
|
|
|
|
|
|
Container.__init__(self)
|
2014-09-19 14:08:08 +00:00
|
|
|
GObject.GObject.__init__(self)
|
2009-11-22 04:28:39 +00:00
|
|
|
self.terminator = Terminator()
|
2009-11-23 15:17:33 +00:00
|
|
|
self.window = window
|
2014-09-19 14:08:08 +00:00
|
|
|
GObject.type_register(Notebook)
|
2009-11-22 04:28:39 +00:00
|
|
|
self.register_signals(Notebook)
|
2013-12-18 17:06:59 +00:00
|
|
|
self.connect('switch-page', self.deferred_on_tab_switch)
|
2017-02-06 03:37:29 +00:00
|
|
|
self.connect('scroll-event', self.on_scroll_event)
|
2009-11-22 04:28:39 +00:00
|
|
|
self.configure()
|
|
|
|
|
|
|
|
child = window.get_child()
|
|
|
|
window.remove(child)
|
|
|
|
window.add(self)
|
2015-11-30 20:39:15 +00:00
|
|
|
window_last_active_term = window.last_active_term
|
2010-03-19 22:16:08 +00:00
|
|
|
self.newtab(widget=child)
|
2015-11-30 20:39:15 +00:00
|
|
|
if window_last_active_term:
|
|
|
|
self.set_last_active_term(window_last_active_term)
|
2013-12-18 17:06:59 +00:00
|
|
|
window.last_active_term = None
|
2009-11-23 15:17:33 +00:00
|
|
|
|
2009-11-22 04:28:39 +00:00
|
|
|
self.show_all()
|
|
|
|
|
|
|
|
def configure(self):
|
|
|
|
"""Apply widget-wide settings"""
|
2010-01-21 12:55:57 +00:00
|
|
|
# FIXME: The old reordered handler updated Terminator.terminals with
|
|
|
|
# the new order of terminals. We probably need to preserve this for
|
|
|
|
# navigation to next/prev terminals.
|
2009-11-22 04:28:39 +00:00
|
|
|
#self.connect('page-reordered', self.on_page_reordered)
|
|
|
|
self.set_scrollable(self.config['scroll_tabbar'])
|
|
|
|
|
2010-07-03 19:42:33 +00:00
|
|
|
if self.config['tab_position'] == 'hidden' or self.config['hide_tabbar']:
|
|
|
|
self.set_show_tabs(False)
|
|
|
|
else:
|
|
|
|
self.set_show_tabs(True)
|
2014-09-19 14:10:43 +00:00
|
|
|
pos = getattr(Gtk.PositionType, self.config['tab_position'].upper())
|
2010-07-03 19:42:33 +00:00
|
|
|
self.set_tab_pos(pos)
|
2009-11-22 03:53:38 +00:00
|
|
|
|
2018-04-24 18:22:10 +00:00
|
|
|
for tab in range(0, self.get_n_pages()):
|
2010-04-01 22:15:42 +00:00
|
|
|
label = self.get_tab_label(self.get_nth_page(tab))
|
|
|
|
label.update_angle()
|
|
|
|
|
2014-09-19 14:10:43 +00:00
|
|
|
# style = Gtk.RcStyle() # FIXME FOR GTK3 how to do it there? actually do we really want to override the theme?
|
|
|
|
# style.xthickness = 0
|
|
|
|
# style.ythickness = 0
|
|
|
|
# self.modify_style(style)
|
2013-12-18 17:06:59 +00:00
|
|
|
self.last_active_term = {}
|
2013-04-21 23:01:31 +00:00
|
|
|
|
2010-02-27 13:04:15 +00:00
|
|
|
def create_layout(self, layout):
|
|
|
|
"""Apply layout configuration"""
|
2011-08-21 00:01:59 +00:00
|
|
|
def child_compare(a, b):
|
|
|
|
order_a = children[a]['order']
|
|
|
|
order_b = children[b]['order']
|
|
|
|
|
|
|
|
if (order_a == order_b):
|
|
|
|
return 0
|
|
|
|
if (order_a < order_b):
|
|
|
|
return -1
|
|
|
|
if (order_a > order_b):
|
|
|
|
return 1
|
|
|
|
|
2018-04-24 18:22:10 +00:00
|
|
|
if 'children' not in layout:
|
2010-02-27 13:04:15 +00:00
|
|
|
err('layout specifies no children: %s' % layout)
|
|
|
|
return
|
|
|
|
|
|
|
|
children = layout['children']
|
|
|
|
if len(children) <= 1:
|
|
|
|
#Notebooks should have two or more children
|
|
|
|
err('incorrect number of children for Notebook: %s' % layout)
|
|
|
|
return
|
|
|
|
|
|
|
|
num = 0
|
2018-04-24 18:22:10 +00:00
|
|
|
keys = list(children.keys())
|
2019-02-28 20:15:11 +00:00
|
|
|
keys = sorted(keys, key=cmp_to_key(child_compare))
|
2010-02-27 13:04:15 +00:00
|
|
|
|
|
|
|
for child_key in keys:
|
|
|
|
child = children[child_key]
|
2010-06-15 14:19:05 +00:00
|
|
|
dbg('Making a child of type: %s' % child['type'])
|
2010-02-27 13:04:15 +00:00
|
|
|
if child['type'] == 'Terminal':
|
2011-08-21 00:01:59 +00:00
|
|
|
pass
|
2010-02-27 13:04:15 +00:00
|
|
|
elif child['type'] == 'VPaned':
|
|
|
|
page = self.get_nth_page(num)
|
|
|
|
self.split_axis(page, True)
|
|
|
|
elif child['type'] == 'HPaned':
|
|
|
|
page = self.get_nth_page(num)
|
|
|
|
self.split_axis(page, False)
|
|
|
|
num = num + 1
|
|
|
|
|
|
|
|
num = 0
|
|
|
|
for child_key in keys:
|
|
|
|
page = self.get_nth_page(num)
|
2010-05-25 18:08:20 +00:00
|
|
|
if not page:
|
|
|
|
# This page does not yet exist, so make it
|
|
|
|
self.newtab(children[child_key])
|
|
|
|
page = self.get_nth_page(num)
|
2018-04-24 18:22:10 +00:00
|
|
|
if 'labels' in layout:
|
2010-07-04 16:22:39 +00:00
|
|
|
labeltext = layout['labels'][num]
|
|
|
|
if labeltext and labeltext != "None":
|
|
|
|
label = self.get_tab_label(page)
|
|
|
|
label.set_custom_label(labeltext)
|
2010-02-27 13:04:15 +00:00
|
|
|
page.create_layout(children[child_key])
|
2013-12-18 17:06:59 +00:00
|
|
|
|
|
|
|
if layout.get('last_active_term', None):
|
|
|
|
self.last_active_term[page] = make_uuid(layout['last_active_term'][num])
|
2010-02-27 13:04:15 +00:00
|
|
|
num = num + 1
|
|
|
|
|
2018-04-24 18:22:10 +00:00
|
|
|
if 'active_page' in layout:
|
2013-12-18 17:06:59 +00:00
|
|
|
# Need to do it later, or layout changes result
|
2014-09-19 14:08:08 +00:00
|
|
|
GObject.idle_add(self.set_current_page, int(layout['active_page']))
|
2013-09-04 22:50:12 +00:00
|
|
|
else:
|
|
|
|
self.set_current_page(0)
|
|
|
|
|
2010-03-05 22:44:38 +00:00
|
|
|
def split_axis(self, widget, vertical=True, cwd=None, sibling=None, widgetfirst=True):
|
2009-12-08 13:57:29 +00:00
|
|
|
"""Split the axis of a terminal inside us"""
|
2011-08-20 23:38:50 +00:00
|
|
|
dbg('called for widget: %s' % widget)
|
2010-02-27 12:30:38 +00:00
|
|
|
order = None
|
2009-11-25 00:37:29 +00:00
|
|
|
page_num = self.page_num(widget)
|
|
|
|
if page_num == -1:
|
|
|
|
err('Notebook::split_axis: %s not found in Notebook' % widget)
|
|
|
|
return
|
|
|
|
|
2009-12-08 13:57:29 +00:00
|
|
|
label = self.get_tab_label(widget)
|
|
|
|
self.remove(widget)
|
2009-11-25 00:37:29 +00:00
|
|
|
|
|
|
|
maker = Factory()
|
|
|
|
if vertical:
|
|
|
|
container = maker.make('vpaned')
|
|
|
|
else:
|
|
|
|
container = maker.make('hpaned')
|
|
|
|
|
2013-10-25 14:55:26 +00:00
|
|
|
self.get_toplevel().set_pos_by_ratio = True
|
|
|
|
|
2009-11-25 00:37:29 +00:00
|
|
|
if not sibling:
|
|
|
|
sibling = maker.make('terminal')
|
2010-03-05 22:44:38 +00:00
|
|
|
sibling.set_cwd(cwd)
|
2017-02-13 16:30:37 +00:00
|
|
|
if self.config['always_split_with_profile']:
|
|
|
|
sibling.force_set_profile(None, widget.get_profile())
|
2010-02-04 00:59:11 +00:00
|
|
|
sibling.spawn_child()
|
2012-10-19 16:57:23 +00:00
|
|
|
if widget.group and self.config['split_to_group']:
|
|
|
|
sibling.set_group(None, widget.group)
|
2017-02-13 16:30:37 +00:00
|
|
|
elif self.config['always_split_with_profile']:
|
2011-11-05 15:58:39 +00:00
|
|
|
sibling.force_set_profile(None, widget.get_profile())
|
2009-11-25 00:37:29 +00:00
|
|
|
|
|
|
|
self.insert_page(container, None, page_num)
|
2014-09-19 14:10:43 +00:00
|
|
|
self.child_set_property(container, 'tab-expand', True)
|
|
|
|
self.child_set_property(container, 'tab-fill', True)
|
2010-04-20 11:38:49 +00:00
|
|
|
self.set_tab_reorderable(container, True)
|
2009-12-08 13:57:29 +00:00
|
|
|
self.set_tab_label(container, label)
|
2009-11-25 00:37:29 +00:00
|
|
|
self.show_all()
|
|
|
|
|
2010-03-02 20:38:28 +00:00
|
|
|
order = [widget, sibling]
|
|
|
|
if widgetfirst is False:
|
2010-03-02 12:39:47 +00:00
|
|
|
order.reverse()
|
2010-02-27 12:30:38 +00:00
|
|
|
|
|
|
|
for terminal in order:
|
|
|
|
container.add(terminal)
|
2009-11-25 00:37:29 +00:00
|
|
|
self.set_current_page(page_num)
|
|
|
|
|
|
|
|
self.show_all()
|
2009-11-22 03:53:38 +00:00
|
|
|
|
2014-09-19 14:08:08 +00:00
|
|
|
while Gtk.events_pending():
|
|
|
|
Gtk.main_iteration_do(False)
|
2013-10-25 14:55:26 +00:00
|
|
|
self.get_toplevel().set_pos_by_ratio = False
|
|
|
|
|
2014-09-19 14:08:08 +00:00
|
|
|
GObject.idle_add(terminal.ensure_visible_and_focussed)
|
2013-12-18 17:06:59 +00:00
|
|
|
|
2011-08-22 20:05:38 +00:00
|
|
|
def add(self, widget, metadata=None):
|
2009-11-22 03:53:38 +00:00
|
|
|
"""Add a widget to the container"""
|
2011-08-20 23:38:50 +00:00
|
|
|
dbg('adding a new tab')
|
2011-08-22 20:05:38 +00:00
|
|
|
self.newtab(widget=widget, metadata=metadata)
|
2009-11-22 03:53:38 +00:00
|
|
|
|
|
|
|
def remove(self, widget):
|
|
|
|
"""Remove a widget from the container"""
|
2009-11-25 00:37:29 +00:00
|
|
|
page_num = self.page_num(widget)
|
|
|
|
if page_num == -1:
|
2010-01-22 19:03:58 +00:00
|
|
|
err('%s not found in Notebook. Actual parent is: %s' %
|
2009-12-08 13:57:29 +00:00
|
|
|
(widget, widget.get_parent()))
|
2009-11-25 00:37:29 +00:00
|
|
|
return(False)
|
|
|
|
self.remove_page(page_num)
|
2009-12-08 13:57:29 +00:00
|
|
|
self.disconnect_child(widget)
|
2009-11-25 12:51:14 +00:00
|
|
|
return(True)
|
2009-11-22 03:53:38 +00:00
|
|
|
|
2010-04-12 20:35:24 +00:00
|
|
|
def replace(self, oldwidget, newwidget):
|
|
|
|
"""Replace a tab's contents with a new widget"""
|
|
|
|
page_num = self.page_num(oldwidget)
|
|
|
|
self.remove(oldwidget)
|
|
|
|
self.add(newwidget)
|
|
|
|
self.reorder_child(newwidget, page_num)
|
|
|
|
|
2011-08-22 20:05:38 +00:00
|
|
|
def get_child_metadata(self, widget):
|
|
|
|
"""Fetch the relevant metadata for a widget which we'd need
|
|
|
|
to recreate it when it's readded"""
|
|
|
|
metadata = {}
|
|
|
|
metadata['tabnum'] = self.page_num(widget)
|
|
|
|
label = self.get_tab_label(widget)
|
|
|
|
if not label:
|
|
|
|
dbg('unable to find label for widget: %s' % widget)
|
2016-11-23 04:55:36 +00:00
|
|
|
elif label.get_custom_label():
|
|
|
|
metadata['label'] = label.get_custom_label()
|
2011-08-22 20:05:38 +00:00
|
|
|
else:
|
2016-11-23 04:55:36 +00:00
|
|
|
dbg('don\'t grab the label as it was not customised')
|
2011-08-22 20:05:38 +00:00
|
|
|
return metadata
|
|
|
|
|
2010-03-10 22:51:33 +00:00
|
|
|
def get_children(self):
|
|
|
|
"""Return an ordered list of our children"""
|
|
|
|
children = []
|
2018-04-24 18:22:10 +00:00
|
|
|
for page in range(0,self.get_n_pages()):
|
2010-03-10 22:51:33 +00:00
|
|
|
children.append(self.get_nth_page(page))
|
|
|
|
return(children)
|
|
|
|
|
2011-11-05 15:49:01 +00:00
|
|
|
def newtab(self, debugtab=False, widget=None, cwd=None, metadata=None, profile=None):
|
2009-11-22 04:28:39 +00:00
|
|
|
"""Add a new tab, optionally supplying a child widget"""
|
2011-08-20 23:38:50 +00:00
|
|
|
dbg('making a new tab')
|
2010-01-24 12:55:03 +00:00
|
|
|
maker = Factory()
|
2011-02-23 21:35:10 +00:00
|
|
|
top_window = self.get_toplevel()
|
2010-01-21 12:33:42 +00:00
|
|
|
|
2009-11-22 04:28:39 +00:00
|
|
|
if not widget:
|
2010-01-04 13:11:16 +00:00
|
|
|
widget = maker.make('Terminal')
|
2010-04-02 15:45:32 +00:00
|
|
|
if cwd:
|
|
|
|
widget.set_cwd(cwd)
|
2017-02-13 16:30:37 +00:00
|
|
|
if profile and self.config['always_split_with_profile']:
|
|
|
|
widget.force_set_profile(None, profile)
|
2010-03-19 22:16:08 +00:00
|
|
|
widget.spawn_child(debugserver=debugtab)
|
2017-02-13 16:30:37 +00:00
|
|
|
elif profile and self.config['always_split_with_profile']:
|
2011-11-16 20:38:24 +00:00
|
|
|
widget.force_set_profile(None, profile)
|
2009-11-22 04:28:39 +00:00
|
|
|
|
2009-11-25 12:51:14 +00:00
|
|
|
signals = {'close-term': self.wrapcloseterm,
|
2009-11-25 00:37:29 +00:00
|
|
|
'split-horiz': self.split_horiz,
|
|
|
|
'split-vert': self.split_vert,
|
2009-12-10 13:20:03 +00:00
|
|
|
'title-change': self.propagate_title_change,
|
2010-01-24 12:55:03 +00:00
|
|
|
'unzoom': self.unzoom,
|
|
|
|
'tab-change': top_window.tab_change,
|
|
|
|
'group-all': top_window.group_all,
|
2014-01-24 22:29:07 +00:00
|
|
|
'group-all-toggle': top_window.group_all_toggle,
|
2010-01-24 12:55:03 +00:00
|
|
|
'ungroup-all': top_window.ungroup_all,
|
|
|
|
'group-tab': top_window.group_tab,
|
2014-01-24 22:29:07 +00:00
|
|
|
'group-tab-toggle': top_window.group_tab_toggle,
|
2010-01-24 22:15:54 +00:00
|
|
|
'ungroup-tab': top_window.ungroup_tab,
|
2010-01-28 12:49:38 +00:00
|
|
|
'move-tab': top_window.move_tab,
|
2010-04-02 15:45:32 +00:00
|
|
|
'tab-new': [top_window.tab_new, widget],
|
2010-01-29 13:12:33 +00:00
|
|
|
'navigate': top_window.navigate_terminal}
|
2009-11-25 00:37:29 +00:00
|
|
|
|
2009-11-25 09:07:48 +00:00
|
|
|
if maker.isinstance(widget, 'Terminal'):
|
|
|
|
for signal in signals:
|
2010-04-02 15:45:32 +00:00
|
|
|
args = []
|
|
|
|
handler = signals[signal]
|
|
|
|
if isinstance(handler, list):
|
|
|
|
args = handler[1:]
|
|
|
|
handler = handler[0]
|
|
|
|
self.connect_child(widget, signal, handler, *args)
|
2009-11-25 00:37:29 +00:00
|
|
|
|
2018-04-24 18:22:10 +00:00
|
|
|
if metadata and 'tabnum' in metadata:
|
2011-08-22 20:05:38 +00:00
|
|
|
tabpos = metadata['tabnum']
|
|
|
|
else:
|
|
|
|
tabpos = -1
|
|
|
|
|
2009-11-23 15:17:33 +00:00
|
|
|
label = TabLabel(self.window.get_title(), self)
|
2018-04-24 18:22:10 +00:00
|
|
|
if metadata and 'label' in metadata:
|
2011-08-22 20:05:38 +00:00
|
|
|
dbg('creating TabLabel with text: %s' % metadata['label'])
|
|
|
|
label.set_custom_label(metadata['label'])
|
2009-12-08 09:10:39 +00:00
|
|
|
label.connect('close-clicked', self.closetab)
|
2009-11-23 15:17:33 +00:00
|
|
|
|
|
|
|
label.show_all()
|
|
|
|
widget.show_all()
|
|
|
|
|
2011-08-22 20:05:38 +00:00
|
|
|
dbg('inserting page at position: %s' % tabpos)
|
|
|
|
self.insert_page(widget, None, tabpos)
|
2015-08-09 02:34:05 +00:00
|
|
|
|
|
|
|
if maker.isinstance(widget, 'Terminal'):
|
|
|
|
containers, objects = ([], [widget])
|
|
|
|
else:
|
|
|
|
containers, objects = enumerate_descendants(widget)
|
|
|
|
|
2013-12-18 17:06:59 +00:00
|
|
|
term_widget = None
|
2015-08-09 02:34:05 +00:00
|
|
|
for term_widget in objects:
|
2013-12-18 17:06:59 +00:00
|
|
|
if maker.isinstance(term_widget, 'Terminal'):
|
|
|
|
self.set_last_active_term(term_widget.uuid)
|
|
|
|
break
|
2009-11-23 15:17:33 +00:00
|
|
|
|
2015-08-09 02:34:05 +00:00
|
|
|
self.set_tab_label(widget, label)
|
2014-09-19 14:10:43 +00:00
|
|
|
self.child_set_property(widget, 'tab-expand', True)
|
|
|
|
self.child_set_property(widget, 'tab-fill', True)
|
2015-08-09 02:34:05 +00:00
|
|
|
|
2010-04-11 22:08:00 +00:00
|
|
|
self.set_tab_reorderable(widget, True)
|
2011-08-22 20:05:38 +00:00
|
|
|
self.set_current_page(tabpos)
|
2010-04-09 11:58:46 +00:00
|
|
|
self.show_all()
|
2013-12-18 17:06:59 +00:00
|
|
|
if maker.isinstance(term_widget, 'Terminal'):
|
2010-04-09 11:58:46 +00:00
|
|
|
widget.grab_focus()
|
2009-11-25 12:51:14 +00:00
|
|
|
|
|
|
|
def wrapcloseterm(self, widget):
|
|
|
|
"""A child terminal has closed"""
|
2009-12-09 13:02:13 +00:00
|
|
|
dbg('Notebook::wrapcloseterm: called on %s' % widget)
|
2009-11-25 12:51:14 +00:00
|
|
|
if self.closeterm(widget):
|
2009-12-08 13:57:29 +00:00
|
|
|
dbg('Notebook::wrapcloseterm: closeterm succeeded')
|
2010-01-19 22:44:05 +00:00
|
|
|
self.hoover()
|
2009-12-08 13:57:29 +00:00
|
|
|
else:
|
|
|
|
dbg('Notebook::wrapcloseterm: closeterm failed')
|
2009-11-25 12:51:14 +00:00
|
|
|
|
2009-12-08 09:10:39 +00:00
|
|
|
def closetab(self, widget, label):
|
|
|
|
"""Close a tab"""
|
|
|
|
tabnum = None
|
|
|
|
try:
|
|
|
|
nb = widget.notebook
|
|
|
|
except AttributeError:
|
|
|
|
err('TabLabel::closetab: called on non-Notebook: %s' % widget)
|
|
|
|
return
|
|
|
|
|
2018-04-24 18:22:10 +00:00
|
|
|
for i in range(0, nb.get_n_pages() + 1):
|
2009-12-08 09:10:39 +00:00
|
|
|
if label == nb.get_tab_label(nb.get_nth_page(i)):
|
|
|
|
tabnum = i
|
|
|
|
break
|
|
|
|
|
2010-04-06 22:45:05 +00:00
|
|
|
if tabnum is None:
|
2009-12-09 13:02:13 +00:00
|
|
|
err('TabLabel::closetab: %s not in %s. Bailing.' % (label, nb))
|
2009-12-08 09:10:39 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
maker = Factory()
|
|
|
|
child = nb.get_nth_page(tabnum)
|
|
|
|
|
|
|
|
if maker.isinstance(child, 'Terminal'):
|
2009-12-08 13:57:29 +00:00
|
|
|
dbg('Notebook::closetab: child is a single Terminal')
|
2013-12-18 17:06:59 +00:00
|
|
|
del nb.last_active_term[child]
|
2009-12-08 09:10:39 +00:00
|
|
|
child.close()
|
2010-04-06 22:45:05 +00:00
|
|
|
# FIXME: We only do this del and return here to avoid removing the
|
|
|
|
# page below, which child.close() implicitly does
|
|
|
|
del(label)
|
|
|
|
return
|
2009-12-08 09:10:39 +00:00
|
|
|
elif maker.isinstance(child, 'Container'):
|
2009-12-08 13:57:29 +00:00
|
|
|
dbg('Notebook::closetab: child is a Container')
|
2013-12-18 17:06:59 +00:00
|
|
|
result = self.construct_confirm_close(self.window, _('tab'))
|
2009-12-08 13:01:13 +00:00
|
|
|
|
2014-09-19 14:08:08 +00:00
|
|
|
if result == Gtk.ResponseType.ACCEPT:
|
2010-01-28 13:41:44 +00:00
|
|
|
containers = None
|
|
|
|
objects = None
|
|
|
|
containers, objects = enumerate_descendants(child)
|
2009-12-08 13:57:29 +00:00
|
|
|
|
2009-12-09 13:02:13 +00:00
|
|
|
while len(objects) > 0:
|
|
|
|
descendant = objects.pop()
|
2009-12-08 13:57:29 +00:00
|
|
|
descendant.close()
|
2014-09-19 14:08:08 +00:00
|
|
|
while Gtk.events_pending():
|
|
|
|
Gtk.main_iteration()
|
2009-12-08 13:57:29 +00:00
|
|
|
return
|
|
|
|
else:
|
|
|
|
dbg('Notebook::closetab: user cancelled request')
|
|
|
|
return
|
2009-12-08 09:10:39 +00:00
|
|
|
else:
|
2009-12-08 13:57:29 +00:00
|
|
|
err('Notebook::closetab: child is unknown type %s' % child)
|
|
|
|
return
|
2009-12-08 09:10:39 +00:00
|
|
|
|
2020-04-22 08:14:16 +00:00
|
|
|
def resizeterm(self, widget, keyname, fast = False):
|
2009-11-22 03:53:38 +00:00
|
|
|
"""Handle a keyboard event requesting a terminal resize"""
|
|
|
|
raise NotImplementedError('resizeterm')
|
|
|
|
|
|
|
|
def zoom(self, widget, fontscale = False):
|
|
|
|
"""Zoom a terminal"""
|
|
|
|
raise NotImplementedError('zoom')
|
|
|
|
|
|
|
|
def unzoom(self, widget):
|
|
|
|
"""Unzoom a terminal"""
|
|
|
|
raise NotImplementedError('unzoom')
|
|
|
|
|
2009-12-10 23:25:52 +00:00
|
|
|
def find_tab_root(self, widget):
|
|
|
|
"""Look for the tab child which is or ultimately contains the supplied
|
|
|
|
widget"""
|
|
|
|
parent = widget.get_parent()
|
|
|
|
previous = parent
|
|
|
|
|
|
|
|
while parent is not None and parent is not self:
|
|
|
|
previous = parent
|
|
|
|
parent = parent.get_parent()
|
|
|
|
|
|
|
|
if previous == self:
|
|
|
|
return(widget)
|
|
|
|
else:
|
|
|
|
return(previous)
|
|
|
|
|
2009-12-10 13:20:03 +00:00
|
|
|
def update_tab_label_text(self, widget, text):
|
|
|
|
"""Update the text of a tab label"""
|
2009-12-10 23:25:52 +00:00
|
|
|
notebook = self.find_tab_root(widget)
|
|
|
|
label = self.get_tab_label(notebook)
|
2009-12-10 13:20:03 +00:00
|
|
|
if not label:
|
|
|
|
err('Notebook::update_tab_label_text: %s not found' % widget)
|
|
|
|
return
|
|
|
|
|
|
|
|
label.set_label(text)
|
|
|
|
|
2010-01-19 22:44:05 +00:00
|
|
|
def hoover(self):
|
|
|
|
"""Clean up any empty tabs and if we only have one tab left, die"""
|
|
|
|
numpages = self.get_n_pages()
|
|
|
|
while numpages > 0:
|
|
|
|
numpages = numpages - 1
|
|
|
|
page = self.get_nth_page(numpages)
|
|
|
|
if not page:
|
|
|
|
dbg('Removing empty page: %d' % numpages)
|
|
|
|
self.remove_page(numpages)
|
|
|
|
|
|
|
|
if self.get_n_pages() == 1:
|
|
|
|
dbg('Last page, removing self')
|
|
|
|
child = self.get_nth_page(0)
|
|
|
|
self.remove_page(0)
|
|
|
|
parent = self.get_parent()
|
|
|
|
parent.remove(self)
|
2010-01-25 12:55:38 +00:00
|
|
|
self.cnxids.remove_all()
|
2010-01-19 22:44:05 +00:00
|
|
|
parent.add(child)
|
|
|
|
del(self)
|
2010-04-11 14:31:42 +00:00
|
|
|
# Find the last terminal in the new parent and give it focus
|
|
|
|
terms = parent.get_visible_terminals()
|
2018-04-24 18:22:10 +00:00
|
|
|
list(terms.keys())[-1].grab_focus()
|
2010-01-19 22:44:05 +00:00
|
|
|
|
2013-12-18 17:06:59 +00:00
|
|
|
def page_num_descendant(self, widget):
|
|
|
|
"""Find the tabnum of the tab containing a widget at any level"""
|
|
|
|
tabnum = self.page_num(widget)
|
|
|
|
dbg("widget is direct child if not equal -1 - tabnum: %d" % tabnum)
|
|
|
|
while tabnum == -1 and widget.get_parent():
|
|
|
|
widget = widget.get_parent()
|
|
|
|
tabnum = self.page_num(widget)
|
|
|
|
dbg("found tabnum containing widget: %d" % tabnum)
|
|
|
|
return tabnum
|
|
|
|
|
|
|
|
def set_last_active_term(self, uuid):
|
|
|
|
"""Set the last active term for uuid"""
|
|
|
|
widget = self.terminator.find_terminal_by_uuid(uuid.urn)
|
|
|
|
if not widget:
|
|
|
|
err("Cannot find terminal with uuid: %s, so cannot make it active" % (uuid.urn))
|
|
|
|
return
|
|
|
|
tabnum = self.page_num_descendant(widget)
|
|
|
|
if tabnum == -1:
|
|
|
|
err("No tabnum found for terminal with uuid: %s" % (uuid.urn))
|
|
|
|
return
|
|
|
|
nth_page = self.get_nth_page(tabnum)
|
|
|
|
self.last_active_term[nth_page] = uuid
|
|
|
|
|
|
|
|
def clean_last_active_term(self):
|
|
|
|
"""Clean up old entries in last_active_term"""
|
|
|
|
if self.terminator.doing_layout == True:
|
|
|
|
return
|
|
|
|
last_active_term = {}
|
2018-04-24 18:22:10 +00:00
|
|
|
for tabnum in range(0, self.get_n_pages()):
|
2013-12-18 17:06:59 +00:00
|
|
|
nth_page = self.get_nth_page(tabnum)
|
|
|
|
if nth_page in self.last_active_term:
|
|
|
|
last_active_term[nth_page] = self.last_active_term[nth_page]
|
|
|
|
self.last_active_term = last_active_term
|
|
|
|
|
|
|
|
def deferred_on_tab_switch(self, notebook, page, page_num, data=None):
|
|
|
|
"""Prime a single idle tab switch signal, using the most recent set of params"""
|
|
|
|
tabs_last_active_term = self.last_active_term.get(self.get_nth_page(page_num), None)
|
|
|
|
data = {'tabs_last_active_term':tabs_last_active_term}
|
|
|
|
|
|
|
|
self.pending_on_tab_switch_args = (notebook, page, page_num, data)
|
|
|
|
if self.pending_on_tab_switch == True:
|
|
|
|
return
|
2014-09-19 14:08:08 +00:00
|
|
|
GObject.idle_add(self.do_deferred_on_tab_switch)
|
2013-12-18 17:06:59 +00:00
|
|
|
self.pending_on_tab_switch = True
|
|
|
|
|
|
|
|
def do_deferred_on_tab_switch(self):
|
|
|
|
"""Perform the latest tab switch signal, and resetting the pending flag"""
|
|
|
|
self.on_tab_switch(*self.pending_on_tab_switch_args)
|
|
|
|
self.pending_on_tab_switch = False
|
|
|
|
self.pending_on_tab_switch_args = None
|
|
|
|
|
|
|
|
def on_tab_switch(self, notebook, page, page_num, data=None):
|
|
|
|
"""Do the real work for a tab switch"""
|
|
|
|
tabs_last_active_term = data['tabs_last_active_term']
|
|
|
|
if tabs_last_active_term:
|
|
|
|
term = self.terminator.find_terminal_by_uuid(tabs_last_active_term.urn)
|
2014-09-19 14:08:08 +00:00
|
|
|
GObject.idle_add(term.ensure_visible_and_focussed)
|
2013-12-18 17:06:59 +00:00
|
|
|
return True
|
|
|
|
|
2017-02-06 03:37:29 +00:00
|
|
|
def on_scroll_event(self, notebook, event):
|
|
|
|
'''Handle scroll events for scrolling through tabs'''
|
|
|
|
#print "self: %s" % self
|
|
|
|
#print "event: %s" % event
|
|
|
|
child = self.get_nth_page(self.get_current_page())
|
|
|
|
if child == None:
|
2018-04-24 18:22:10 +00:00
|
|
|
print("Child = None, return false")
|
2017-02-06 03:37:29 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
event_widget = Gtk.get_event_widget(event)
|
|
|
|
|
|
|
|
if event_widget == None or \
|
|
|
|
event_widget == child or \
|
|
|
|
event_widget.is_ancestor(child):
|
2018-04-24 18:22:10 +00:00
|
|
|
print("event_widget is wrong one, return false")
|
2017-02-06 03:37:29 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
# Not sure if we need these. I don't think wehave any action widgets
|
|
|
|
# at this point.
|
|
|
|
action_widget = self.get_action_widget(Gtk.PackType.START)
|
|
|
|
if event_widget == action_widget or \
|
|
|
|
(action_widget != None and event_widget.is_ancestor(action_widget)):
|
|
|
|
return False
|
|
|
|
action_widget = self.get_action_widget(Gtk.PackType.END)
|
|
|
|
if event_widget == action_widget or \
|
|
|
|
(action_widget != None and event_widget.is_ancestor(action_widget)):
|
|
|
|
return False
|
|
|
|
|
|
|
|
if event.direction in [Gdk.ScrollDirection.RIGHT,
|
|
|
|
Gdk.ScrollDirection.DOWN]:
|
|
|
|
self.next_page()
|
|
|
|
elif event.direction in [Gdk.ScrollDirection.LEFT,
|
|
|
|
Gdk.ScrollDirection.UP]:
|
|
|
|
self.prev_page()
|
|
|
|
elif event.direction == Gdk.ScrollDirection.SMOOTH:
|
|
|
|
if self.get_tab_pos() in [Gtk.PositionType.LEFT,
|
|
|
|
Gtk.PositionType.RIGHT]:
|
|
|
|
if event.delta_y > 0:
|
|
|
|
self.next_page()
|
|
|
|
elif event.delta_y < 0:
|
|
|
|
self.prev_page()
|
|
|
|
elif self.get_tab_pos() in [Gtk.PositionType.TOP,
|
|
|
|
Gtk.PositionType.BOTTOM]:
|
|
|
|
if event.delta_x > 0:
|
|
|
|
self.next_page()
|
|
|
|
elif event.delta_x < 0:
|
|
|
|
self.prev_page()
|
|
|
|
return True
|
|
|
|
|
2014-09-19 14:08:08 +00:00
|
|
|
class TabLabel(Gtk.HBox):
|
2009-11-23 15:17:33 +00:00
|
|
|
"""Class implementing a label widget for Notebook tabs"""
|
|
|
|
notebook = None
|
|
|
|
terminator = None
|
|
|
|
config = None
|
|
|
|
label = None
|
|
|
|
icon = None
|
|
|
|
button = None
|
|
|
|
|
2009-12-08 09:10:39 +00:00
|
|
|
__gsignals__ = {
|
2014-09-19 14:08:08 +00:00
|
|
|
'close-clicked': (GObject.SignalFlags.RUN_LAST, None,
|
|
|
|
(GObject.TYPE_OBJECT,)),
|
2009-12-08 09:10:39 +00:00
|
|
|
}
|
|
|
|
|
2009-11-23 15:17:33 +00:00
|
|
|
def __init__(self, title, notebook):
|
|
|
|
"""Class initialiser"""
|
2014-09-19 14:08:08 +00:00
|
|
|
GObject.GObject.__init__(self)
|
2009-12-08 09:10:39 +00:00
|
|
|
|
2009-11-23 15:17:33 +00:00
|
|
|
self.notebook = notebook
|
|
|
|
self.terminator = Terminator()
|
|
|
|
self.config = Config()
|
|
|
|
|
|
|
|
self.label = EditableLabel(title)
|
|
|
|
self.update_angle()
|
|
|
|
|
2014-09-19 14:10:43 +00:00
|
|
|
self.pack_start(self.label, True, True, 0)
|
2009-11-23 15:17:33 +00:00
|
|
|
|
|
|
|
self.update_button()
|
|
|
|
self.show_all()
|
|
|
|
|
2009-12-10 13:20:03 +00:00
|
|
|
def set_label(self, text):
|
|
|
|
"""Update the text of our label"""
|
|
|
|
self.label.set_text(text)
|
|
|
|
|
2011-08-22 20:05:38 +00:00
|
|
|
def get_label(self):
|
|
|
|
return self.label.get_text()
|
|
|
|
|
2010-07-04 16:22:39 +00:00
|
|
|
def set_custom_label(self, text):
|
|
|
|
"""Set a permanent label as if the user had edited it"""
|
|
|
|
self.label.set_text(text)
|
|
|
|
self.label.set_custom()
|
|
|
|
|
|
|
|
def get_custom_label(self):
|
|
|
|
"""Return a custom label if we have one, otherwise None"""
|
|
|
|
if self.label.is_custom():
|
|
|
|
return(self.label.get_text())
|
|
|
|
else:
|
|
|
|
return(None)
|
|
|
|
|
2015-11-30 23:57:18 +00:00
|
|
|
def edit(self):
|
|
|
|
self.label.edit()
|
|
|
|
|
2009-11-23 15:17:33 +00:00
|
|
|
def update_button(self):
|
|
|
|
"""Update the state of our close button"""
|
|
|
|
if not self.config['close_button_on_tab']:
|
|
|
|
if self.button:
|
|
|
|
self.button.remove(self.icon)
|
|
|
|
self.remove(self.button)
|
|
|
|
del(self.button)
|
|
|
|
del(self.icon)
|
|
|
|
self.button = None
|
|
|
|
self.icon = None
|
|
|
|
return
|
|
|
|
|
|
|
|
if not self.button:
|
2014-09-19 14:08:08 +00:00
|
|
|
self.button = Gtk.Button()
|
2009-11-23 15:17:33 +00:00
|
|
|
if not self.icon:
|
2015-08-03 18:22:15 +00:00
|
|
|
self.icon = Gio.ThemedIcon.new_with_default_fallbacks("window-close-symbolic")
|
|
|
|
self.icon = Gtk.Image.new_from_gicon(self.icon, Gtk.IconSize.MENU)
|
|
|
|
|
2009-11-23 15:17:33 +00:00
|
|
|
self.button.set_focus_on_click(False)
|
2014-09-19 14:08:08 +00:00
|
|
|
self.button.set_relief(Gtk.ReliefStyle.NONE)
|
2014-09-19 14:10:43 +00:00
|
|
|
# style = Gtk.RcStyle() # FIXME FOR GTK3 how to do it there? actually do we really want to override the theme?
|
|
|
|
# style.xthickness = 0
|
|
|
|
# style.ythickness = 0
|
|
|
|
# self.button.modify_style(style)
|
2009-11-23 15:17:33 +00:00
|
|
|
self.button.add(self.icon)
|
|
|
|
self.button.connect('clicked', self.on_close)
|
|
|
|
self.button.set_name('terminator-tab-close-button')
|
|
|
|
if hasattr(self.button, 'set_tooltip_text'):
|
|
|
|
self.button.set_tooltip_text(_('Close Tab'))
|
2014-09-19 14:10:43 +00:00
|
|
|
self.pack_start(self.button, False, False, 0)
|
2009-11-23 15:17:33 +00:00
|
|
|
self.show_all()
|
|
|
|
|
|
|
|
def update_angle(self):
|
|
|
|
"""Update the angle of a label"""
|
|
|
|
position = self.notebook.get_tab_pos()
|
2014-09-19 14:08:08 +00:00
|
|
|
if position == Gtk.PositionType.LEFT:
|
2010-05-13 07:17:30 +00:00
|
|
|
if hasattr(self, 'set_orientation'):
|
2014-09-19 14:08:08 +00:00
|
|
|
self.set_orientation(Gtk.Orientation.VERTICAL)
|
2009-11-23 15:17:33 +00:00
|
|
|
self.label.set_angle(90)
|
2014-09-19 14:08:08 +00:00
|
|
|
elif position == Gtk.PositionType.RIGHT:
|
2012-10-18 22:03:48 +00:00
|
|
|
if hasattr(self, 'set_orientation'):
|
2014-09-19 14:08:08 +00:00
|
|
|
self.set_orientation(Gtk.Orientation.VERTICAL)
|
2009-11-23 15:17:33 +00:00
|
|
|
self.label.set_angle(270)
|
|
|
|
else:
|
2010-05-13 07:17:30 +00:00
|
|
|
if hasattr(self, 'set_orientation'):
|
2014-09-19 14:08:08 +00:00
|
|
|
self.set_orientation(Gtk.Orientation.HORIZONTAL)
|
2009-11-23 15:17:33 +00:00
|
|
|
self.label.set_angle(0)
|
|
|
|
|
2010-01-22 19:03:58 +00:00
|
|
|
def on_close(self, _widget):
|
2009-11-23 15:17:33 +00:00
|
|
|
"""The close button has been clicked. Destroy the tab"""
|
2009-12-08 09:10:39 +00:00
|
|
|
self.emit('close-clicked', self)
|
|
|
|
|
2009-11-22 03:53:38 +00:00
|
|
|
# vim: set expandtab ts=4 sw=4:
|