Ejemplo n.º 1
0
def enumerate_descendants(parent):
    """Walk all our children and build up a list of containers and
    terminals"""
    # FIXME: Does having to import this here mean we should move this function
    # back to Container?
    from terminatorlib.factory import Factory

    containerstmp = []
    containers = []
    terminals = []
    maker = Factory()

    if parent is None:
        err('no parent widget specified')
        return

    for descendant in parent.get_children():
        if maker.isinstance(descendant, 'Container'):
            containerstmp.append(descendant)
        elif maker.isinstance(descendant, 'Terminal'):
            terminals.append(descendant)

        while len(containerstmp) > 0:
            child = containerstmp.pop(0)
            for descendant in child.get_children():
                if maker.isinstance(descendant, 'Container'):
                    containerstmp.append(descendant)
                elif maker.isinstance(descendant, 'Terminal'):
                    terminals.append(descendant)
            containers.append(child)

    dbg(f'{len(containers)} containers and {len(terminals)} terminals fall beneath {parent}'
        )
    return containers, terminals
Ejemplo n.º 2
0
    def rotate(self, widget, clockwise):
        """Rotate children in this window"""
        self.set_pos_by_ratio = True
        maker = Factory()
        child = self.get_child()

        # If our child is a Notebook, reset to work from its visible child
        if maker.isinstance(child, 'Notebook'):
            pagenum = child.get_current_page()
            child = child.get_nth_page(pagenum)

        if maker.isinstance(child, 'Paned'):
            parent = child.get_parent()
            # Need to get the allocation before we remove the child,
            # otherwise _sometimes_ we get incorrect values.
            alloc = child.get_allocation()
            parent.remove(child)
            child.rotate_recursive(parent, alloc.width, alloc.height,
                                   clockwise)

            self.show_all()
            while Gtk.events_pending():
                Gtk.main_iteration_do(False)
            widget.grab_focus()

        self.set_pos_by_ratio = False
Ejemplo n.º 3
0
 def get_tab_title(self, uuid=None):
     """Return the title of a parent tab of a given terminal"""
     maker = Factory()
     terminal = self.terminator.find_terminal_by_uuid(uuid)
     window = terminal.get_toplevel()
     root_widget = window.get_children()[0]
     if maker.isinstance(root_widget, "Notebook"):
         for tab_child in root_widget.get_children():
             terms = [tab_child]
             if not maker.isinstance(terms[0], "Terminal"):
                 terms = enumerate_descendants(tab_child)[1]
             if terminal in terms:
                 return root_widget.get_tab_label(tab_child).get_label()
Ejemplo n.º 4
0
 def on_delete_event(self, window, event, data=None):
     """Handle a window close request"""
     maker = Factory()
     if maker.isinstance(self.get_child(), 'Terminal'):
         if self.get_property('term_zoomed') == True:
             return self.confirm_close(window, _('window'))
         else:
             dbg('Window::on_delete_event: Only one child, closing is fine')
             return False
     elif maker.isinstance(self.get_child(), 'Container'):
         return self.confirm_close(window, _('window'))
     else:
         dbg('unknown child: %s' % self.get_child())
Ejemplo n.º 5
0
    def propagate_title_change(self, widget, title):
        """Pass a title change up the widget stack"""
        maker = Factory()
        parent = self.get_parent()
        title = widget.get_window_title()

        if maker.isinstance(self, 'Notebook'):
            self.update_tab_label_text(widget, title)
        elif maker.isinstance(self, 'Window'):
            self.title.set_title(widget, title)

        if maker.isinstance(parent, 'Container'):
            parent.propagate_title_change(widget, title)
Ejemplo n.º 6
0
    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 range(0, nb.get_n_pages() + 1):
            if label == nb.get_tab_label(nb.get_nth_page(i)):
                tabnum = i
                break

        if tabnum is None:
            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')
            del nb.last_active_term[child]
            child.close()
            # FIXME: We only do this del and return here to avoid removing the
            # page below, which child.close() implicitly does
            del(label)
            return
        elif maker.isinstance(child, 'Container'):
            dbg('Notebook::closetab: child is a Container')
            result = self.construct_confirm_close(self.window, _('tab'))

            if result == Gtk.ResponseType.ACCEPT:
                containers = None
                objects = None
                containers, objects = enumerate_descendants(child)

                while len(objects) > 0:
                    descendant = objects.pop()
                    descendant.close()
                    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
Ejemplo n.º 7
0
    def move_tab(self, widget, direction):
        """Handle a keyboard shortcut for moving tab positions"""
        maker = Factory()
        notebook = self.get_child()

        if not maker.isinstance(notebook, 'Notebook'):
            dbg('not in a notebook, refusing to move tab %s' % direction)
            return

        dbg('moving tab %s' % direction)
        numpages = notebook.get_n_pages()
        page = notebook.get_current_page()
        child = notebook.get_nth_page(page)

        if direction == 'left':
            if page == 0:
                page = numpages
            else:
                page = page - 1
        elif direction == 'right':
            if page == numpages - 1:
                page = 0
            else:
                page = page + 1
        else:
            err('unknown direction: %s' % direction)
            return

        notebook.reorder_child(child, page)
Ejemplo n.º 8
0
    def tab_change(self, widget, num=None):
        """Change to a specific tab"""
        if num is None:
            err('must specify a tab to change to')

        maker = Factory()
        child = self.get_child()

        if not maker.isinstance(child, 'Notebook'):
            dbg('child is not a notebook, nothing to change to')
            return

        if num == -1:
            # Go to the next tab
            cur = child.get_current_page()
            pages = child.get_n_pages()
            if cur == pages - 1:
                num = 0
            else:
                num = cur + 1
        elif num == -2:
            # Go to the previous tab
            cur = child.get_current_page()
            if cur > 0:
                num = cur - 1
            else:
                num = child.get_n_pages() - 1

        child.set_current_page(num)
        # Work around strange bug in gtk-2.12.11 and pygtk-2.12.1
        # Without it, the selection changes, but the displayed page doesn't
        # change
        child.set_current_page(child.get_current_page())
Ejemplo n.º 9
0
 def get_tab(self, uuid=None):
     """Return the UUID of the parent tab of a given terminal"""
     maker = Factory()
     terminal = self.terminator.find_terminal_by_uuid(uuid)
     window = terminal.get_toplevel()
     root_widget = window.get_children()[0]
     if maker.isinstance(root_widget, 'Notebook'):
         #return root_widget.uuid.urn
         for tab_child in root_widget.get_children():
             terms = [tab_child]
             if not maker.isinstance(terms[0], "Terminal"):
                 terms = enumerate_descendants(tab_child)[1]
             if terminal in terms:
                 # FIXME: There are no uuid's assigned to the the notebook, or the actual tabs!
                 # This would fail: return root_widget.uuid.urn
                 return ""
Ejemplo n.º 10
0
    def add(self, widget, metadata=None):
        """Add a widget to the window by way of Gtk.Window.add()"""
        maker = Factory()
        Gtk.Window.add(self, widget)
        if maker.isinstance(widget, 'Terminal'):
            signals = {
                'close-term': self.closeterm,
                'title-change': self.title.set_title,
                'split-horiz': self.split_horiz,
                'split-vert': self.split_vert,
                'unzoom': self.unzoom,
                'tab-change': self.tab_change,
                'group-all': self.group_all,
                'group-all-toggle': self.group_all_toggle,
                'ungroup-all': self.ungroup_all,
                'group-tab': self.group_tab,
                'group-tab-toggle': self.group_tab_toggle,
                'ungroup-tab': self.ungroup_tab,
                'move-tab': self.move_tab,
                'tab-new': [self.tab_new, widget],
                'navigate': self.navigate_terminal
            }

            for signal in signals:
                args = []
                handler = signals[signal]
                if isinstance(handler, list):
                    args = handler[1:]
                    handler = handler[0]
                self.connect_child(widget, signal, handler, *args)

            widget.grab_focus()
Ejemplo n.º 11
0
    def rotate_recursive(self, parent, w, h, clockwise):
        """
        Recursively rotate "self" into a new paned that'll have "w" x "h" size. Attach it to "parent".

        As discussed in LP#1522542, we should build up the new layout (including the separator positions)
        in a single step. We can't rely on Gtk+ computing the allocation sizes yet, so we have to do the
        computation ourselves and carry the resulting paned sizes all the way down the widget tree.
        """
        maker = Factory()
        handle_size = self.get_handlesize()

        if isinstance(self, HPaned):
            container = VPaned()
            reverse = not clockwise
        else:
            container = HPaned()
            reverse = clockwise

        container.ratio = self.ratio
        children = self.get_children()
        if reverse:
            container.ratio = 1 - container.ratio
            children.reverse()

        if isinstance(self, HPaned):
            w1 = w2 = w
            h1 = pos = self.position_by_ratio(h, handle_size, container.ratio)
            h2 = max(h - h1 - handle_size, 0)
        else:
            h1 = h2 = h
            w1 = pos = self.position_by_ratio(w, handle_size, container.ratio)
            w2 = max(w - w1 - handle_size, 0)

        container.set_pos(pos)
        parent.add(container)

        if maker.isinstance(children[0], 'Terminal'):
            children[0].get_parent().remove(children[0])
            container.add(children[0])
        else:
            children[0].rotate_recursive(container, w1, h1, clockwise)

        if maker.isinstance(children[1], 'Terminal'):
            children[1].get_parent().remove(children[1])
            container.add(children[1])
        else:
            children[1].rotate_recursive(container, w2, h2, clockwise)
Ejemplo n.º 12
0
    def ungroup_tab(self, widget):
        """Ungroup all terminals in the current tab"""
        maker = Factory()
        notebook = self.get_child()

        if not maker.isinstance(notebook, 'Notebook'):
            dbg('note in a notebook, refusing to ungroup tab')
            return

        self.set_groups(None, self.get_visible_terminals())
Ejemplo n.º 13
0
    def do_redistribute(self, recurse_up=False, recurse_down=False):
        """Evenly divide available space between sibling panes"""
        maker = Factory()
        # 1 Find highest ancestor of the same type => ha
        highest_ancestor = self
        while isinstance(highest_ancestor.get_parent(), type(highest_ancestor)):
            highest_ancestor = highest_ancestor.get_parent()

        highest_ancestor.set_autoresize(False)

        # (1b) If Super modifier, redistribute higher sections too
        if recurse_up:
            grandfather = highest_ancestor.get_parent()
            if maker.isinstance(grandfather, 'VPaned') or \
                    maker.isinstance(grandfather, 'HPaned'):
                grandfather.do_redistribute(recurse_up, recurse_down)

        highest_ancestor._do_redistribute(recurse_up, recurse_down)

        GLib.idle_add(highest_ancestor.set_autoresize, True)
Ejemplo n.º 14
0
    def _do_redistribute(self, recurse_up=False, recurse_down=False):
        maker = Factory()
        # 2 Make a list of self + all children of same type
        tree = [self, [], 0, None]
        toproc = [tree]
        number_splits = 1
        while toproc:
            curr = toproc.pop(0)
            for child in curr[0].get_children():
                if isinstance(child, type(curr[0])):
                    childset = [child, [], 0, curr]
                    curr[1].append(childset)
                    toproc.append(childset)
                    number_splits += 1
                else:
                    curr[1].append([None, [], 1, None])
                    p = curr
                    while p:
                        p[2] += 1
                        p = p[3]
                    # (1c) If Shift modifier, redistribute lower sections too
                    if recurse_down and (maker.isinstance(child, 'VPaned') or maker.isinstance(child, 'HPaned')):
                        child.do_redistribute(False, True)

        # 3 Get ancestor x/y => a, and handle size => hs
        avail_pixels = self.get_length()
        handle_size = self.get_handlesize()
        # 4 Math! eek (a - (n * hs)) / (n + 1) = single size => s
        single_size = (avail_pixels - (number_splits * handle_size)) / (number_splits + 1)
        arr_sizes = [single_size] * (number_splits + 1)
        for i in range(avail_pixels % (number_splits + 1)):
            arr_sizes[i] += 1
        # 5 Descend down setting the handle position to s
        #  (Has to handle nesting properly)
        toproc = [tree]
        while toproc:
            curr = toproc.pop(0)
            for child in curr[1]:
                toproc.append(child)
                if curr[1].index(child) == 0:
                    curr[0].set_position((child[2] * single_size) + ((child[2] - 1) * handle_size))
Ejemplo n.º 15
0
    def group_tab(self, widget):
        """Group all terminals in the current tab"""
        maker = Factory()
        notebook = self.get_child()

        if not maker.isinstance(notebook, 'Notebook'):
            dbg('not in a notebook, refusing to group tab')
            return

        pagenum = notebook.get_current_page()
        while True:
            group = _('Tab %d') % pagenum
            if group not in self.terminator.groups:
                break
            pagenum += 1
        self.set_groups(group, self.get_visible_terminals())
Ejemplo n.º 16
0
    def reconfigure(self):
        """Update configuration for the whole application"""

        if self.style_providers != []:
            for style_provider in self.style_providers:
                Gtk.StyleContext.remove_provider_for_screen(
                    Gdk.Screen.get_default(), style_provider)
        self.style_providers = []

        # Force the window background to be transparent for newer versions of
        # GTK3. We then have to fix all the widget backgrounds because the
        # widgets theming may not render it's own background.
        css = """
            .terminator-terminal-window {
                background-color: alpha(@theme_bg_color,0); }

            .terminator-terminal-window .notebook.header,
            .terminator-terminal-window notebook header {
                background-color: @theme_bg_color; }

            .terminator-terminal-window .pane-separator {
                background-color: @theme_bg_color; }

            .terminator-terminal-window .terminator-terminal-searchbar {
                background-color: @theme_bg_color; }
            """

        # Fix several themes that put a borders, corners, or backgrounds around
        # viewports, making the titlebar look bad.
        css += """
            .terminator-terminal-window GtkViewport,
            .terminator-terminal-window viewport {
                border-width: 0px;
                border-radius: 0px;
                background-color: transparent; }
            """

        # Add per profile snippets for setting the background of the HBox
        template = """
            .terminator-profile-%s {
                background-color: alpha(%s, %s); }
            """
        profiles = self.config.base.profiles
        for profile in profiles.keys():
            if profiles[profile]['use_theme_colors']:
                # Create a dummy window/vte and realise it so it has correct
                # values to read from
                tmp_win = Gtk.Window()
                tmp_vte = Vte.Terminal()
                tmp_win.add(tmp_vte)
                tmp_win.realize()
                bgcolor = tmp_vte.get_style_context().get_background_color(
                    Gtk.StateType.NORMAL)
                bgcolor = "#{0:02x}{1:02x}{2:02x}".format(
                    int(bgcolor.red * 255), int(bgcolor.green * 255),
                    int(bgcolor.blue * 255))
                tmp_win.remove(tmp_vte)
                del tmp_vte
                del tmp_win
            else:
                bgcolor = Gdk.RGBA()
                bgcolor = profiles[profile]['background_color']
            if profiles[profile]['background_type'] == 'transparent':
                bgalpha = profiles[profile]['background_darkness']
            else:
                bgalpha = "1"

            munged_profile = "".join(
                [c if c.isalnum() else "-" for c in profile])
            css += template % (munged_profile, bgcolor, bgalpha)

        style_provider = Gtk.CssProvider()
        style_provider.load_from_data(css.encode('utf8'))
        self.style_providers.append(style_provider)

        # Attempt to load some theme specific stylistic tweaks for appearances
        usr_theme_dir = os.path.expanduser('~/.local/share/themes')
        (head, _tail) = os.path.split(borg.__file__)
        app_theme_dir = os.path.join(head, 'themes')

        theme_name = self.gtk_settings.get_property('gtk-theme-name')

        theme_part_list = ['terminator.css']
        if self.config[
                'extra_styling']:  # checkbox_style - needs adding to prefs
            theme_part_list.append('terminator_styling.css')
        for theme_part_file in theme_part_list:
            for theme_dir in [usr_theme_dir, app_theme_dir]:
                path_to_theme_specific_css = os.path.join(
                    theme_dir, theme_name, 'gtk-3.0/apps', theme_part_file)
                if os.path.isfile(path_to_theme_specific_css):
                    style_provider = Gtk.CssProvider()
                    style_provider.connect('parsing-error',
                                           self.on_css_parsing_error)
                    try:
                        style_provider.load_from_path(
                            path_to_theme_specific_css)
                    except GError:
                        # Hmmm. Should we try to provide GTK version specific files here on failure?
                        gtk_version_string = '.'.join([
                            str(Gtk.get_major_version()),
                            str(Gtk.get_minor_version()),
                            str(Gtk.get_micro_version())
                        ])
                        err('Error(s) loading css from %s into Gtk %s' %
                            (path_to_theme_specific_css, gtk_version_string))
                    self.style_providers.append(style_provider)
                    break

        # Size the GtkPaned splitter handle size.
        if self.config['handle_size'] in range(0, 21):
            css = '''.terminator-terminal-window GtkPaned,
            .terminator-terminal-window paned {{
            min-width: {0}px;
            min-height: {0}px; }}
            '''.format(self.config['handle_size'])
            style_provider = Gtk.CssProvider()
            style_provider.load_from_data(css.encode())
            self.style_providers.append(style_provider)

        # Apply the providers, incrementing priority so they don't cancel out
        # each other
        for idx in range(0, len(self.style_providers)):
            Gtk.StyleContext.add_provider_for_screen(
                Gdk.Screen.get_default(), self.style_providers[idx],
                Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + idx)

        # Cause all the terminals to reconfigure
        for terminal in self.terminals:
            terminal.reconfigure()

        # Reparse our keybindings
        self.keybindings.configure(self.config['keybindings'])

        # Update tab position if appropriate
        maker = Factory()
        for window in self.windows:
            child = window.get_child()
            if maker.isinstance(child, 'Notebook'):
                child.configure()
Ejemplo n.º 17
0
    def layout_done(self):
        """Layout operations have finished, record that fact"""
        self.doing_layout = False
        maker = Factory()

        window_last_active_term_mapping = {}
        for window in self.windows:
            if window.is_child_notebook():
                source = window.get_toplevel().get_children()[0]
            else:
                source = window
            window_last_active_term_mapping[window] = copy.copy(
                source.last_active_term)

        for terminal in self.terminals:
            if not terminal.pid:
                terminal.spawn_child()

        for window in self.windows:
            if window.is_child_notebook():
                # For windows with a notebook
                notebook = window.get_toplevel().get_children()[0]
                # Cycle through pages by number
                for page in range(0, notebook.get_n_pages()):
                    # Try and get the entry in the previously saved mapping
                    mapping = window_last_active_term_mapping[window]
                    page_last_active_term = mapping.get(
                        notebook.get_nth_page(page), None)
                    if page_last_active_term is None:
                        # Couldn't find entry, so we find the first child of type Terminal
                        children = notebook.get_nth_page(page).get_children()
                        for page_last_active_term in children:
                            if maker.isinstance(page_last_active_term,
                                                'Terminal'):
                                page_last_active_term = page_last_active_term.uuid
                                break
                        else:
                            err('Should never reach here!')
                            page_last_active_term = None
                    if page_last_active_term is None:
                        # Bail on this tab as we're having no luck here, continue with the next
                        continue
                    # Set the notebook entry, then ensure Terminal is visible and focussed
                    urn = page_last_active_term.urn
                    notebook.last_active_term[notebook.get_nth_page(
                        page)] = page_last_active_term
                    if urn:
                        term = self.find_terminal_by_uuid(urn)
                        if term:
                            term.ensure_visible_and_focussed()
            else:
                # For windows without a notebook ensure Terminal is visible and focussed
                if window_last_active_term_mapping[window]:
                    term = self.find_terminal_by_uuid(
                        window_last_active_term_mapping[window].urn)
                    term.ensure_visible_and_focussed()

        # Build list of new windows using prelayout list
        new_win_list = []
        for window in self.windows:
            if window not in self.prelayout_windows:
                new_win_list.append(window)

        # Make sure all new windows get bumped to the top
        for window in new_win_list:
            window.show()
            window.grab_focus()
            try:
                t = GdkX11.x11_get_server_time(window.get_window())
            except (TypeError, AttributeError):
                t = 0
            window.get_window().focus(t)

        # Awful workaround to be sure that the last focused window is actually the one focused.
        # Don't ask, don't tell policy on this. Even this is not 100%
        if self.last_active_window:
            window = self.find_window_by_uuid(self.last_active_window.urn)
            count = 0
            while count < 1000 and Gtk.events_pending():
                count += 1
                Gtk.main_iteration_do(False)
                window.show()
                window.grab_focus()
                try:
                    t = GdkX11.x11_get_server_time(window.get_window())
                except (TypeError, AttributeError):
                    t = 0
                window.get_window().focus(t)

        self.prelayout_windows = None
Ejemplo n.º 18
0
 def is_child_notebook(self):
     """Returns True if this Window's child is a Notebook"""
     maker = Factory()
     return maker.isinstance(self.get_child(), 'Notebook')
Ejemplo n.º 19
0
    def newtab(self, debugtab=False, widget=None, cwd=None, metadata=None, profile=None):
        """Add a new tab, optionally supplying a child widget"""
        dbg('making a new tab')
        maker = Factory()
        top_window = self.get_toplevel()

        if not widget:
            widget = maker.make('Terminal')
            if cwd:
                widget.set_cwd(cwd)
            if profile and self.config['always_split_with_profile']:
                widget.force_set_profile(None, profile)
            widget.spawn_child(debugserver=debugtab)
        elif profile and self.config['always_split_with_profile']:
            widget.force_set_profile(None, profile)

        signals = {'close-term': self.wrapcloseterm,
                   'split-horiz': self.split_horiz,
                   'split-vert': self.split_vert,
                   'title-change': self.propagate_title_change,
                   'unzoom': self.unzoom,
                   'tab-change': top_window.tab_change,
                   'group-all': top_window.group_all,
                   'group-all-toggle': top_window.group_all_toggle,
                   'ungroup-all': top_window.ungroup_all,
                   'group-tab': top_window.group_tab,
                   'group-tab-toggle': top_window.group_tab_toggle,
                   'ungroup-tab': top_window.ungroup_tab,
                   'move-tab': top_window.move_tab,
                   'tab-new': [top_window.tab_new, widget],
                   'navigate': top_window.navigate_terminal}

        if maker.isinstance(widget, 'Terminal'):
            for signal in signals:
                args = []
                handler = signals[signal]
                if isinstance(handler, list):
                    args = handler[1:]
                    handler = handler[0]
                self.connect_child(widget, signal, handler, *args)

        if metadata and metadata.has_key('tabnum'):
            tabpos = metadata['tabnum']
        else:
            tabpos = -1

        label = TabLabel(self.window.get_title(), self)
        if metadata and metadata.has_key('label'):
            dbg('creating TabLabel with text: %s' % metadata['label'])
            label.set_custom_label(metadata['label'])
        label.connect('close-clicked', self.closetab)

        label.show_all()
        widget.show_all()

        dbg('inserting page at position: %s' % tabpos)
        self.insert_page(widget, None, tabpos)

        if maker.isinstance(widget, 'Terminal'):
            containers, objects = ([], [widget])
        else:
            containers, objects = enumerate_descendants(widget)

        term_widget = None
        for term_widget in objects:
            if maker.isinstance(term_widget, 'Terminal'):
                self.set_last_active_term(term_widget.uuid)
                break

        self.set_tab_label(widget, label)
        self.child_set_property(widget, 'tab-expand', True)
        self.child_set_property(widget, 'tab-fill', True)

        self.set_tab_reorderable(widget, True)
        self.set_current_page(tabpos)
        self.show_all()
        if maker.isinstance(term_widget, 'Terminal'):
            widget.grab_focus()
Ejemplo n.º 20
0
class Paned(Container):
    """Base class for Paned Containers"""

    position = None
    maker = None
    ratio = 0.5
    last_balance_time = 0
    last_balance_args = None

    def __init__(self):
        """Class initialiser"""
        self.terminator = Terminator()
        self.maker = Factory()
        Container.__init__(self)
        self.signals.append({
            'name': 'resize-term',
            'flags': GObject.SignalFlags.RUN_LAST,
            'return_type': None,
            'param_types': (GObject.TYPE_STRING, )
        })

    # pylint: disable-msg=W0613
    def split_axis(self,
                   widget,
                   vertical=True,
                   cwd=None,
                   sibling=None,
                   widgetfirst=True):
        """Default axis splitter. This should be implemented by subclasses"""
        order = None

        self.remove(widget)
        if vertical:
            container = VPaned()
        else:
            container = HPaned()

        self.get_toplevel().set_pos_by_ratio = True

        if not sibling:
            sibling = self.maker.make('terminal')
            sibling.set_cwd(cwd)
            if self.config['always_split_with_profile']:
                sibling.force_set_profile(None, widget.get_profile())
            sibling.spawn_child()
            if widget.group and self.config['split_to_group']:
                sibling.set_group(None, widget.group)
        elif self.config['always_split_with_profile']:
            sibling.force_set_profile(None, widget.get_profile())

        self.add(container)
        self.show_all()

        order = [widget, sibling]
        if widgetfirst is False:
            order.reverse()

        for terminal in order:
            container.add(terminal)

        self.show_all()
        sibling.grab_focus()

        while Gtk.events_pending():
            Gtk.main_iteration_do(False)
        self.get_toplevel().set_pos_by_ratio = False

    def add(self, widget, metadata=None):
        """Add a widget to the container"""
        if len(self.children) == 0:
            self.pack1(widget, False, True)
            self.children.append(widget)
        elif len(self.children) == 1:
            if self.get_child1():
                self.pack2(widget, False, True)
            else:
                self.pack1(widget, False, True)
            self.children.append(widget)
        else:
            raise ValueError('Paned widgets can only have two children')

        if self.maker.isinstance(widget, 'Terminal'):
            top_window = self.get_toplevel()
            signals = {
                'close-term': self.wrapcloseterm,
                'split-horiz': self.split_horiz,
                'split-vert': self.split_vert,
                'title-change': self.propagate_title_change,
                'resize-term': self.resizeterm,
                'size-allocate': self.new_size,
                'zoom': top_window.zoom,
                'tab-change': top_window.tab_change,
                'group-all': top_window.group_all,
                'group-all-toggle': top_window.group_all_toggle,
                'ungroup-all': top_window.ungroup_all,
                'group-tab': top_window.group_tab,
                'group-tab-toggle': top_window.group_tab_toggle,
                'ungroup-tab': top_window.ungroup_tab,
                'move-tab': top_window.move_tab,
                'maximise': [top_window.zoom, False],
                'tab-new': [top_window.tab_new, widget],
                'navigate': top_window.navigate_terminal,
                'rotate-cw': [top_window.rotate, True],
                'rotate-ccw': [top_window.rotate, False]
            }

            for signal in signals:
                args = []
                handler = signals[signal]
                if isinstance(handler, list):
                    args = handler[1:]
                    handler = handler[0]
                self.connect_child(widget, signal, handler, *args)

            if metadata and \
               'had_focus' in metadata and \
               metadata['had_focus'] == True:
                widget.grab_focus()

        elif isinstance(widget, Gtk.Paned):
            try:
                self.connect_child(widget, 'resize-term', self.resizeterm)
                self.connect_child(widget, 'size-allocate', self.new_size)
            except TypeError:
                err('Paned::add: %s has no signal resize-term' % widget)

    def on_button_press(self, widget, event):
        """Handle button presses on a Pane"""
        if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
            if event.get_state(
            ) & Gdk.ModifierType.MOD4_MASK == Gdk.ModifierType.MOD4_MASK:
                recurse_up = True
            else:
                recurse_up = False

            if event.get_state(
            ) & Gdk.ModifierType.SHIFT_MASK == Gdk.ModifierType.SHIFT_MASK:
                recurse_down = True
            else:
                recurse_down = False

            self.last_balance_time = time.time()
            self.last_balance_args = (recurse_up, recurse_down)
            return True
        else:
            return False

    def on_button_release(self, widget, event):
        """Handle button presses on a Pane"""
        if event.button == 1:
            if self.last_balance_time > (time.time() - 1):
                # Dumb loop still needed, or some terms get squished on a Super rebalance
                for i in range(3):
                    while Gtk.events_pending():
                        Gtk.main_iteration_do(False)
                    self.do_redistribute(*self.last_balance_args)
        return False

    def set_autoresize(self, autoresize):
        """Must be called on the highest ancestor in one given orientation"""
        """TODO write some better doc :)"""
        maker = Factory()
        children = self.get_children()
        self.child_set_property(children[0], 'resize', False)
        self.child_set_property(children[1], 'resize', not autoresize)
        for child in children:
            if maker.type(child) == maker.type(self):
                child.set_autoresize(autoresize)

    def do_redistribute(self, recurse_up=False, recurse_down=False):
        """Evenly divide available space between sibling panes"""
        maker = Factory()
        #1 Find highest ancestor of the same type => ha
        highest_ancestor = self
        while type(highest_ancestor.get_parent()) == type(highest_ancestor):
            highest_ancestor = highest_ancestor.get_parent()

        highest_ancestor.set_autoresize(False)

        # (1b) If Super modifier, redistribute higher sections too
        if recurse_up:
            grandfather = highest_ancestor.get_parent()
            if maker.isinstance(grandfather, 'VPaned') or \
               maker.isinstance(grandfather, 'HPaned') :
                grandfather.do_redistribute(recurse_up, recurse_down)

        highest_ancestor._do_redistribute(recurse_up, recurse_down)

        GLib.idle_add(highest_ancestor.set_autoresize, True)

    def _do_redistribute(self, recurse_up=False, recurse_down=False):
        maker = Factory()
        #2 Make a list of self + all children of same type
        tree = [self, [], 0, None]
        toproc = [tree]
        number_splits = 1
        while toproc:
            curr = toproc.pop(0)
            for child in curr[0].get_children():
                if type(child) == type(curr[0]):
                    childset = [child, [], 0, curr]
                    curr[1].append(childset)
                    toproc.append(childset)
                    number_splits = number_splits + 1
                else:
                    curr[1].append([None, [], 1, None])
                    p = curr
                    while p:
                        p[2] = p[2] + 1
                        p = p[3]
                    # (1c) If Shift modifier, redistribute lower sections too
                    if recurse_down and \
                      (maker.isinstance(child, 'VPaned') or \
                       maker.isinstance(child, 'HPaned')):
                        child.do_redistribute(False, True)

        #3 Get ancestor x/y => a, and handle size => hs
        avail_pixels = self.get_length()
        handle_size = self.get_handlesize()
        #4 Math! eek (a - (n * hs)) / (n + 1) = single size => s
        single_size = (avail_pixels -
                       (number_splits * handle_size)) / (number_splits + 1)
        arr_sizes = [single_size] * (number_splits + 1)
        for i in range(avail_pixels % (number_splits + 1)):
            arr_sizes[i] = arr_sizes[i] + 1
        #5 Descend down setting the handle position to s
        #  (Has to handle nesting properly)
        toproc = [tree]
        while toproc:
            curr = toproc.pop(0)
            for child in curr[1]:
                toproc.append(child)
                if curr[1].index(child) == 0:
                    curr[0].set_position((child[2] * single_size) +
                                         ((child[2] - 1) * handle_size))

    def remove(self, widget):
        """Remove a widget from the container"""
        Gtk.Paned.remove(self, widget)
        self.disconnect_child(widget)
        self.children.remove(widget)
        return True

    def get_children(self):
        """Return an ordered list of our children"""
        children = []
        children.append(self.get_child1())
        children.append(self.get_child2())
        return children

    def get_child_metadata(self, widget):
        """Return metadata about a child"""
        metadata = {}
        metadata['had_focus'] = widget.has_focus()

    def get_handlesize(self):
        """Why oh why, gtk3?"""
        try:
            value = GObject.Value(int)
            self.style_get_property('handle-size', value)
            return value.get_int()
        except:
            return 0

    def wrapcloseterm(self, widget):
        """A child terminal has closed, so this container must die"""
        dbg('Paned::wrapcloseterm: Called on %s' % widget)

        if self.closeterm(widget):
            # At this point we only have one child, which is the surviving term
            sibling = self.children[0]
            first_term_sibling = sibling
            cur_tabnum = None

            focus_sibling = True
            if self.get_toplevel().is_child_notebook():
                notebook = self.get_toplevel().get_children()[0]
                cur_tabnum = notebook.get_current_page()
                tabnum = notebook.page_num_descendant(self)
                nth_page = notebook.get_nth_page(tabnum)
                exiting_term_was_last_active = (
                    notebook.last_active_term[nth_page] == widget.uuid)
                if exiting_term_was_last_active:
                    first_term_sibling = enumerate_descendants(self)[1][0]
                    notebook.set_last_active_term(first_term_sibling.uuid)
                    notebook.clean_last_active_term()
                    self.get_toplevel().last_active_term = None
                if cur_tabnum != tabnum:
                    focus_sibling = False
            elif self.get_toplevel().last_active_term != widget.uuid:
                focus_sibling = False

            self.remove(sibling)

            metadata = None
            parent = self.get_parent()
            metadata = parent.get_child_metadata(self)
            dbg('metadata obtained for %s: %s' % (self, metadata))
            parent.remove(self)
            self.cnxids.remove_all()
            parent.add(sibling, metadata)
            if cur_tabnum:
                notebook.set_current_page(cur_tabnum)
            if focus_sibling:
                first_term_sibling.grab_focus()
            elif not sibling.get_toplevel().is_child_notebook():
                Terminator().find_terminal_by_uuid(
                    sibling.get_toplevel().last_active_term.urn).grab_focus()
        else:
            dbg("Paned::wrapcloseterm: self.closeterm failed")

    def hoover(self):
        """Check that we still have a reason to exist"""
        if len(self.children) == 1:
            dbg('Paned::hoover: We only have one child, die')
            parent = self.get_parent()
            child = self.children[0]
            self.remove(child)
            parent.replace(self, child)
            del self

    def resizeterm(self, widget, keyname):
        """Handle a keyboard event requesting a terminal resize"""
        if keyname in ['up', 'down'] and isinstance(self, Gtk.VPaned):
            # This is a key we can handle
            position = self.get_position()

            if self.maker.isinstance(widget, 'Terminal'):
                fontheight = widget.vte.get_char_height()
            else:
                fontheight = 10

            if keyname == 'up':
                self.set_position(position - fontheight)
            else:
                self.set_position(position + fontheight)
        elif keyname in ['left', 'right'] and isinstance(self, Gtk.HPaned):
            # This is a key we can handle
            position = self.get_position()

            if self.maker.isinstance(widget, 'Terminal'):
                fontwidth = widget.vte.get_char_width()
            else:
                fontwidth = 10

            if keyname == 'left':
                self.set_position(position - fontwidth)
            else:
                self.set_position(position + fontwidth)
        else:
            # This is not a key we can handle
            self.emit('resize-term', keyname)

    def create_layout(self, layout):
        """Apply layout configuration"""
        if 'children' not in layout:
            err('layout specifies no children: %s' % layout)
            return

        children = layout['children']
        if len(children) != 2:
            # Paned widgets can only have two children
            err('incorrect number of children for Paned: %s' % layout)
            return

        keys = []

        # FIXME: This seems kinda ugly. All we want here is to know the order
        # of children based on child['order']
        try:
            child_order_map = {}
            for child in children:
                key = children[child]['order']
                child_order_map[key] = child
            map_keys = child_order_map.keys()
            map_keys.sort()
            for map_key in map_keys:
                keys.append(child_order_map[map_key])
        except KeyError:
            # We've failed to figure out the order. At least give the terminals
            # in the wrong order
            keys = children.keys()

        num = 0
        for child_key in keys:
            child = children[child_key]
            dbg('Making a child of type: %s' % child['type'])
            if child['type'] == 'Terminal':
                pass
            elif child['type'] == 'VPaned':
                if num == 0:
                    terminal = self.get_child1()
                else:
                    terminal = self.get_child2()
                self.split_axis(terminal, True)
            elif child['type'] == 'HPaned':
                if num == 0:
                    terminal = self.get_child1()
                else:
                    terminal = self.get_child2()
                self.split_axis(terminal, False)
            else:
                err('unknown child type: %s' % child['type'])
            num = num + 1

        self.get_child1().create_layout(children[keys[0]])
        self.get_child2().create_layout(children[keys[1]])

        # Set the position with ratio. For some reason more reliable than by pos.
        if 'ratio' in layout:
            self.ratio = float(layout['ratio'])
            self.set_position_by_ratio()

    def grab_focus(self):
        """We don't want focus, we want a Terminal to have it"""
        self.get_child1().grab_focus()

    def rotate_recursive(self, parent, w, h, clockwise):
        """
        Recursively rotate "self" into a new paned that'll have "w" x "h" size. Attach it to "parent".

        As discussed in LP#1522542, we should build up the new layout (including the separator positions)
        in a single step. We can't rely on Gtk+ computing the allocation sizes yet, so we have to do the
        computation ourselves and carry the resulting paned sizes all the way down the widget tree.
        """
        maker = Factory()
        handle_size = self.get_handlesize()

        if isinstance(self, HPaned):
            container = VPaned()
            reverse = not clockwise
        else:
            container = HPaned()
            reverse = clockwise

        container.ratio = self.ratio
        children = self.get_children()
        if reverse:
            container.ratio = 1 - container.ratio
            children.reverse()

        if isinstance(self, HPaned):
            w1 = w2 = w
            h1 = pos = self.position_by_ratio(h, handle_size, container.ratio)
            h2 = max(h - h1 - handle_size, 0)
        else:
            h1 = h2 = h
            w1 = pos = self.position_by_ratio(w, handle_size, container.ratio)
            w2 = max(w - w1 - handle_size, 0)

        container.set_pos(pos)
        parent.add(container)

        if maker.isinstance(children[0], 'Terminal'):
            children[0].get_parent().remove(children[0])
            container.add(children[0])
        else:
            children[0].rotate_recursive(container, w1, h1, clockwise)

        if maker.isinstance(children[1], 'Terminal'):
            children[1].get_parent().remove(children[1])
            container.add(children[1])
        else:
            children[1].rotate_recursive(container, w2, h2, clockwise)

    def new_size(self, widget, allocation):
        if self.get_toplevel().set_pos_by_ratio:
            self.set_position_by_ratio()
        else:
            self.set_position(self.get_position())

    def position_by_ratio(self, total_size, handle_size, ratio):
        non_separator_size = max(total_size - handle_size, 0)
        ratio = min(max(ratio, 0.0), 1.0)
        return int(round(non_separator_size * ratio))

    def ratio_by_position(self, total_size, handle_size, position):
        non_separator_size = max(total_size - handle_size, 0)
        if non_separator_size == 0:
            return None
        position = min(max(position, 0), non_separator_size)
        return float(position) / float(non_separator_size)

    def set_position_by_ratio(self):
        # Fix for strange race condition where every so often get_length returns 1. (LP:1655027)
        while self.terminator.doing_layout and self.get_length() == 1:
            while Gtk.events_pending():
                Gtk.main_iteration()

        self.set_pos(
            self.position_by_ratio(self.get_length(), self.get_handlesize(),
                                   self.ratio))

    def set_position(self, pos):
        newratio = self.ratio_by_position(self.get_length(),
                                          self.get_handlesize(), pos)
        if newratio is not None:
            self.ratio = newratio
        self.set_pos(pos)