terminator/terminatorlib/notebook.py

313 lines
10 KiB
Python
Executable File

#!/usr/bin/python
# Terminator by Chris Jones <cmsj@tenshu.net>
# GPL v2 only
"""notebook.py - classes for the notebook widget"""
import gobject
import gtk
from newterminator 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
class Notebook(Container, gtk.Notebook):
"""Class implementing a gtk.Notebook container"""
window = None
def __init__(self, window):
"""Class initialiser"""
if isinstance(window.get_child(), gtk.Notebook):
err('There is already a Notebook at the top of this window')
raise(ValueError)
Container.__init__(self)
gtk.Notebook.__init__(self)
self.terminator = Terminator()
self.window = window
gobject.type_register(Notebook)
self.register_signals(Notebook)
self.configure()
child = window.get_child()
window.remove(child)
window.add(self)
self.newtab(child)
self.show_all()
def configure(self):
"""Apply widget-wide settings"""
# FIXME: Should all of our widgets have this?
#self.connect('page-reordered', self.on_page_reordered)
self.set_property('homogeneous', not self.config['scroll_tabbar'])
self.set_scrollable(self.config['scroll_tabbar'])
pos = getattr(gtk, 'POS_%s' % self.config['tab_position'].upper())
self.set_tab_pos(pos)
self.set_show_tabs(not self.config['hide_tabbar'])
def split_axis(self, widget, vertical=True, sibling=None):
"""Split the axis of a terminal inside us"""
page_num = self.page_num(widget)
if page_num == -1:
err('Notebook::split_axis: %s not found in Notebook' % widget)
return
label = self.get_tab_label(widget)
self.remove(widget)
maker = Factory()
if vertical:
container = maker.make('vpaned')
else:
container = maker.make('hpaned')
if not sibling:
sibling = maker.make('terminal')
self.terminator.register_terminal(sibling)
sibling.spawn_child()
self.insert_page(container, None, page_num)
self.set_tab_label(container, label)
self.show_all()
container.add(widget)
container.add(sibling)
self.set_current_page(page_num)
self.show_all()
def add(self, widget):
"""Add a widget to the container"""
self.newtab(widget)
def remove(self, widget):
"""Remove a widget from the container"""
page_num = self.page_num(widget)
if page_num == -1:
err('Notebook::remove: %s not found in Notebook. Actual parent is: %s' %
(widget, widget.get_parent()))
return(False)
self.remove_page(page_num)
self.disconnect_child(widget)
return(True)
def newtab(self, widget=None):
"""Add a new tab, optionally supplying a child widget"""
if not widget:
maker = Factory()
widget = maker.make('terminal')
self.terminator.register_terminal(widget)
widget.spawn_child()
signals = {'close-term': self.wrapcloseterm,
#'title-change': self.title.set_title,
'split-horiz': self.split_horiz,
'split-vert': self.split_vert,
'unzoom': self.unzoom}
maker = Factory()
if maker.isinstance(widget, 'Terminal'):
for signal in signals:
self.connect_child(widget, signal, signals[signal])
self.set_tab_reorderable(widget, True)
label = TabLabel(self.window.get_title(), self)
label.connect('close-clicked', self.closetab)
label.show_all()
widget.show_all()
self.append_page(widget, None)
self.set_tab_label(widget, label)
self.set_tab_label_packing(widget, not self.config['scroll_tabbar'],
not self.config['scroll_tabbar'],
gtk.PACK_START)
self.set_current_page(-1)
widget.grab_focus()
def wrapcloseterm(self, widget):
"""A child terminal has closed"""
dbg('Notebook::wrapcloseterm: called on %s' % widget)
if self.closeterm(widget):
dbg('Notebook::wrapcloseterm: closeterm succeeded')
if self.get_n_pages() == 1:
dbg('Notebook::wrapcloseterm: last page, removing self')
child = self.get_nth_page(0)
self.remove_page(0)
parent = self.get_parent()
parent.remove(self)
parent.add(child)
del(self)
else:
dbg('Notebook::wrapcloseterm: %d pages remain' %
self.get_n_pages())
else:
dbg('Notebook::wrapcloseterm: closeterm failed')
def closetab(self, widget, label):
"""Close a tab"""
tabnum = None
try:
nb = widget.notebook
except AttributeError:
err('TabLabel::closetab: called on non-Notebook: %s' % widget)
return
for i in xrange(0, nb.get_n_pages()):
if label == nb.get_tab_label(nb.get_nth_page(i)):
tabnum = i
break
if not tabnum:
err('TabLabel::closetab: %s not in %s. Bailing.' % (label, nb))
return
maker = Factory()
child = nb.get_nth_page(tabnum)
if maker.isinstance(child, 'Terminal'):
dbg('Notebook::closetab: child is a single Terminal')
child.close()
elif maker.isinstance(child, 'Container'):
dbg('Notebook::closetab: child is a Container')
dialog = self.construct_confirm_close(self.window, _('tab'))
result = dialog.run()
dialog.destroy()
if result == gtk.RESPONSE_ACCEPT:
containers = []
objects = []
for descendant in child.get_children():
if maker.isinstance(descendant, 'Container'):
containers.append(descendant)
elif maker.isinstance(descendant, 'Terminal'):
objects.append(descendant)
while len(containers) > 0:
child = containers.pop()
for descendant in child.get_children():
if maker.isinstance(descendant, 'Container'):
containers.append(descendant)
elif maker.isinstance(descendant, 'Terminal'):
objects.append(descendant)
while len(objects) > 0:
descendant = objects.pop()
descendant.close()
# FIXME: Is this mainloop iterations stuff necessary?
while gtk.events_pending():
gtk.main_iteration()
return
else:
dbg('Notebook::closetab: user cancelled request')
return
else:
err('Notebook::closetab: child is unknown type %s' % child)
return
nb.remove_page(tabnum)
del(label)
def resizeterm(self, widget, keyname):
"""Handle a keyboard event requesting a terminal resize"""
raise NotImplementedError('resizeterm')
def zoom(self, widget, fontscale = False):
"""Zoom a terminal"""
raise NotImplementedError('zoom')
def unzoom(self, widget):
"""Unzoom a terminal"""
raise NotImplementedError('unzoom')
class TabLabel(gtk.HBox):
"""Class implementing a label widget for Notebook tabs"""
notebook = None
terminator = None
config = None
label = None
icon = None
button = None
__gsignals__ = {
'close-clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_OBJECT,)),
}
def __init__(self, title, notebook):
"""Class initialiser"""
gtk.HBox.__init__(self)
self.__gobject_init__()
self.notebook = notebook
self.terminator = Terminator()
self.config = Config()
self.label = EditableLabel(title)
self.update_angle()
self.pack_start(self.label, True, True)
self.update_button()
self.show_all()
def update_button(self):
"""Update the state of our close button"""
if not self.config['close_button_on_tab']:
if self.button:
self.button.remove(self.icon)
self.remove(self.button)
del(self.button)
del(self.icon)
self.button = None
self.icon = None
return
if not self.button:
self.button = gtk.Button()
if not self.icon:
self.icon = gtk.Image()
self.icon.set_from_stock(gtk.STOCK_CLOSE,
gtk.ICON_SIZE_MENU)
self.button.set_relief(gtk.RELIEF_NONE)
self.button.set_focus_on_click(False)
# FIXME: Why on earth are we doing this twice?
self.button.set_relief(gtk.RELIEF_NONE)
self.button.add(self.icon)
self.button.connect('clicked', self.on_close)
self.button.set_name('terminator-tab-close-button')
self.button.connect('style-set', self.on_style_set)
if hasattr(self.button, 'set_tooltip_text'):
self.button.set_tooltip_text(_('Close Tab'))
self.pack_start(self.button, False, False)
self.show_all()
def update_angle(self):
"""Update the angle of a label"""
position = self.notebook.get_tab_pos()
if position == gtk.POS_LEFT:
self.label.set_angle(90)
elif position == gtk.POS_RIGHT:
self.label.set_angle(270)
else:
self.label.set_angle(0)
def on_style_set(self, widget, prevstyle):
"""Style changed, recalculate icon size"""
x, y = gtk.icon_size_lookup_for_settings(self.button.get_settings(),
gtk.ICON_SIZE_MENU)
self.button.set_size_request(x + 2, y + 2)
def on_close(self, widget):
"""The close button has been clicked. Destroy the tab"""
self.emit('close-clicked', self)
# vim: set expandtab ts=4 sw=4: