Esempio n. 1
0
class TableViewWidget(InsertedObjectWidget):
    def __init__(self, model):
        InsertedObjectWidget.__init__(self)
        self.textarea_width = 0
        self.model = model

        # used in pageview
        self._has_cursor = False  # Skip table object, if someone moves cursor around in textview

        # used here
        self._timer = None  # NONE or number of current GObject.timer, which is running
        self._keep_toolbar_open = False  # a cell is currently edited, toolbar should not be hidden
        self._cellinput_canceled = None  # cell changes should be skipped
        self._toolbar_enabled = True  # sets if toolbar should be shown beneath a selected table

        # Toolbar for table actions
        self.toolbar = self.create_toolbar()
        self.toolbar.show_all()
        self.toolbar.set_no_show_all(True)
        self.toolbar.hide()

        # Create treeview
        self._init_treeview(model)

        # package gui elements
        self.vbox = Gtk.VBox()
        self.add(self.vbox)
        self.vbox.pack_end(self.toolbar, True, True, 0)
        self.scroll_win = ScrolledWindow(self.treeview, Gtk.PolicyType.NEVER,
                                         Gtk.PolicyType.NEVER,
                                         Gtk.ShadowType.NONE)
        self.vbox.pack_start(self.scroll_win, True, True, 0)

        # signals
        model.connect('model-changed', self.on_model_changed)

    def _init_treeview(self, model):
        # Actual gtk table object
        self.treeview = self.create_treeview(model)

        # Hook up signals & set options
        self.treeview.connect('button-press-event', self.on_button_press_event)
        self.treeview.connect('focus-in-event', self.on_focus_in, self.toolbar)
        self.treeview.connect('focus-out-event', self.on_focus_out,
                              self.toolbar)
        self.treeview.connect('move-cursor', self.on_move_cursor)

        # Set options
        self.treeview.set_grid_lines(Gtk.TreeViewGridLines.BOTH)
        self.treeview.set_receives_default(True)
        self.treeview.set_size_request(-1, -1)
        self.treeview.set_border_width(2)

        # disable interactive column search
        self.treeview.set_enable_search(False)
        #Gtk.binding_entry_remove(Gtk.TreeView, Gdk.KEY_f, Gdk.ModifierType.CONTROL_MASK)
        self.treeview.set_search_column(-1)

    def on_model_changed(self, model):
        self.scroll_win.remove(self.treeview)
        self.treeview.destroy()
        self._init_treeview(model)
        self.scroll_win.add(self.treeview)
        self.scroll_win.show_all()

    def old_do_size_request(self, requisition):  # TODO - FIX this behavior
        model = self.get_model()
        wraps = model.get_wraps()
        if not any(wraps):
            return InsertedObjectWidget.do_size_request(self, requisition)

        # Negotiate how to wrap ..
        for col in self.treeview.get_columns():
            cr = col.get_cell_renderers()[0]
            cr.set_property('wrap-width', -1)  # reset size

            #~ col.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)  # allow column shrinks
            #~ col.set_max_width(0)	 # shrink column
            #~ col.set_max_width(-1)  # reset value
            #~ col.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY)  # reset value

        InsertedObjectWidget.do_size_request(self, requisition)

        #~ print("Widget requests: %i textview: %i" % (requisition.width, self._textview_width))
        if requisition.width > self._textview_width:
            # Figure out width of fixed cols
            fixed = 0
            for col, wrap in zip(self.treeview.get_columns(), wraps):
                if not wrap:
                    fixed += col.get_width()

            nwrap = sum(wraps)
            wrap_size = (self._textview_width - fixed) // nwrap

            # Set width for wrappable cols
            #~ print("Fixed, nwrap, wrap_size", (fixed, nwrap, wrap_size))
            for col, wrap in zip(self.treeview.get_columns(), wraps):
                if wrap:
                    cr = col.get_cell_renderers()[0]
                    cr.set_property('wrap-width', wrap_size)  # reset size

            # Update request
            InsertedObjectWidget.do_size_request(self, requisition)
        else:
            pass

    def on_focus_in(self, treeview, event, toolbar):
        '''After a table is selected, this function will be triggered'''

        self._keep_toolbar_open = False
        if self._timer:
            GObject.source_remove(self._timer)
        if self._toolbar_enabled:
            toolbar.show()

    def on_focus_out(self, treeview, event, toolbar):
        '''After a table is deselected, this function will be triggered'''
        def receive_alarm():
            if self._keep_toolbar_open:
                self._timer = None
            if self._timer:
                self._timer = None
                treeview.get_selection().unselect_all()
                if self._toolbar_enabled:
                    toolbar.hide()
            return False

        self._timer = GObject.timeout_add(500, receive_alarm)

    def create_toolbar(self):
        '''This function creates a toolbar which is displayed next to the table'''
        toolbar = Gtk.Toolbar()
        toolbar.set_orientation(Gtk.Orientation.HORIZONTAL)
        toolbar.set_style(Gtk.ToolbarStyle.ICONS)
        toolbar.set_border_width(1)

        for pos, stock, handler, data, tooltip in (
            (0, Gtk.STOCK_ADD, self.on_add_row, None,
             _('Add row')),  # T: tooltip on mouse hover
            (1, Gtk.STOCK_DELETE, self.on_delete_row, None,
             _('Remove row')),  # T: tooltip on mouse hover
            (2, Gtk.STOCK_COPY, self.on_clone_row, None,
             _('Clone row')),  # T: tooltip on mouse hover
            (3, None, None, None, None),
            (4, Gtk.STOCK_GO_UP, self.on_move_row, -1,
             _('Row up')),  # T: tooltip on mouse hover
            (5, Gtk.STOCK_GO_DOWN, self.on_move_row, 1,
             _('Row down')),  # T: tooltip on mouse hover
            (6, None, None, None, None),
            (7, Gtk.STOCK_PREFERENCES, self.on_change_columns, None,
             _('Change columns')),  # T: tooltip on mouse hover
            (8, None, None, None, None),
            (9, Gtk.STOCK_HELP, self.on_open_help, None,
             _('Open help')),  # T: tooltip on mouse hover
        ):
            if stock is None:
                toolbar.insert(Gtk.SeparatorToolItem(), pos)
            else:
                button = Gtk.ToolButton(stock)
                if data:
                    button.connect('clicked', handler, data)
                else:
                    button.connect('clicked', handler)
                button.set_tooltip_text(tooltip)
                toolbar.insert(button, pos)

        toolbar.set_size_request(300, -1)
        toolbar.set_icon_size(Gtk.IconSize.MENU)

        return toolbar

    def _column_alignment(self, aligntext):
        ''' The column alignment must be converted from numeric to keywords '''
        if aligntext == 'left':
            align = 0.0
        elif aligntext == 'center':
            align = 0.5
        elif aligntext == 'right':
            align = 1.0
        else:
            align = None
        return align

    def create_treeview(self, model):
        '''Initializes a treeview with its model (liststore) and all its columns'''
        treeview = Gtk.TreeView(model.liststore)

        # Set default sorting function.
        model.liststore.set_default_sort_func(lambda *a: 0)

        aligns = model.get_aligns()
        wraps = model.get_wraps()
        for i, headcol in enumerate(model.headers):
            cell = Gtk.CellRendererText()
            tview_column = Gtk.TreeViewColumn(headcol, cell)
            tview_column.set_sizing(
                Gtk.TreeViewColumnSizing.AUTOSIZE)  # allow column shrinks
            treeview.append_column(tview_column)

            # set title as label
            header_label = self.create_headerlabel(headcol)
            tview_column.set_widget(header_label)

            # set properties of column
            tview_column.set_attributes(cell, markup=i)
            cell.set_property('editable', True)
            cell.set_property(
                'yalign', 0.0)  # no vertical alignment, text starts on the top
            tview_column.set_sort_column_id(i)
            # set sort function
            model.liststore.set_sort_func(i, self.sort_by_number_or_string, i)
            # set alignment - left center right
            align = self._column_alignment(aligns[i])
            if align:
                tview_column.set_alignment(align)
                cell.set_alignment(align, 0.0)

            # set wrap mode, wrap-size is set elsewhere
            if wraps[i]:
                cell.set_property('wrap-mode', Pango.WrapMode.WORD)

            # callbacks after an action
            cell.connect('edited', self.on_cell_changed, treeview.get_model(),
                         i)
            cell.connect('editing-started', self.on_cell_editing_started,
                         treeview.get_model(), i)
            cell.connect('editing-canceled', self.on_cell_editing_canceled)

        return treeview

    def create_headerlabel(self, title):
        return TableViewWidget.create_headerlabel(title)

    @staticmethod
    def create_headerlabel(title):
        ''' Sets options for the treeview header'''
        col_widget = Gtk.VBox()
        col_widget.show()

        col_label = Gtk.Label(label='<u>' + title + '</u>')
        col_label.set_use_markup(True)
        col_label.show()
        col_widget.pack_start(col_label, True, True, 0)
        #col_align.add(col_label)
        '''col_entry = InputEntry()
		col_entry.set_name('treeview-header-entry')
		col_entry.show()
		col_widget.pack_start(col_entry, True, True, 0)'''

        return col_widget

    def get_treeview(self):
        # treeview of current table
        return self.treeview

    def set_preferences(self, preferences):
        self._toolbar_enabled = preferences.get('show_helper_toolbar', True)
        self.treeview.set_grid_lines(GTK_GRIDLINES[preferences.get(
            'grid_lines', LINES_BOTH)])

    def on_move_cursor(self, view, step_size, count):
        ''' If you try to move the cursor out of the tableditor release the cursor to the parent textview '''
        return None  # let parent handle this signal

    def fetch_cell_by_event(self, event, treeview):
        '''	Looks for the cell where the mouse clicked on it '''
        liststore = treeview.get_model()
        (xpos, ypos) = event.get_coords()
        (treepath, treecol, xrel,
         yrel) = treeview.get_path_at_pos(int(xpos), int(ypos))
        treeiter = liststore.get_iter(treepath)
        cellvalue = liststore.get_value(treeiter,
                                        treeview.get_columns().index(treecol))
        return cellvalue

    def get_linkurl(self, celltext):
        '''	Checks a cellvalue if it contains a link and returns only the link value '''
        linkregex = r'<span foreground="blue">.*?<span.*?>(.*?)</span></span>'
        matches = re.match(linkregex, celltext)
        linkvalue = matches.group(1) if matches else None
        return linkvalue

    def on_button_press_event(self, treeview, event):
        '''
		Displays a context-menu on right button click
		Opens the link of a tablecell on CTRL pressed and left button click
		'''
        if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 1 and event.get_state(
        ) & Gdk.ModifierType.CONTROL_MASK:
            # With CTRL + LEFT-Mouse-Click link of cell is opened
            cellvalue = self.fetch_cell_by_event(event, treeview)
            linkvalue = self.get_linkurl(cellvalue)
            if linkvalue:
                self.emit('link-clicked', {'href': str(linkvalue)})
            return

        if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
            # Right button opens context menu
            self._keep_toolbar_open = True
            cellvalue = self.fetch_cell_by_event(event, treeview)
            linkvalue = self.get_linkurl(cellvalue)
            linkitem_is_activated = (linkvalue is not None)

            menu = Gtk.Menu()

            for stock, handler, data, tooltip in (
                (Gtk.STOCK_ADD, self.on_add_row, None,
                 _('Add row')),  # T: menu item
                (Gtk.STOCK_DELETE, self.on_delete_row, None,
                 _('Delete row')),  # T: menu item
                (Gtk.STOCK_COPY, self.on_clone_row, None,
                 _('Clone row')),  # T: menu item
                (None, None, None, None),  # T: menu item
                (Gtk.STOCK_JUMP_TO, self.on_open_link, linkvalue,
                 _('Open cell content link')),  # T: menu item
                (None, None, None, None),
                (Gtk.STOCK_GO_UP, self.on_move_row, -1,
                 _('Row up')),  # T: menu item
                (Gtk.STOCK_GO_DOWN, self.on_move_row, 1,
                 _('Row down')),  # T: menu item
                (None, None, None, None),
                (Gtk.STOCK_PREFERENCES, self.on_change_columns, None,
                 _('Change columns'))  # T: menu item
            ):

                if stock is None:
                    menu.append(Gtk.SeparatorMenuItem())
                else:
                    item = Gtk.ImageMenuItem(stock)
                    item.set_always_show_image(True)
                    item.set_label(_(tooltip))
                    if data:
                        item.connect_after('activate', handler, data)
                    else:
                        item.connect_after('activate', handler)
                    if handler == self.on_open_link:
                        item.set_sensitive(linkitem_is_activated)
                    menu.append(item)

            menu.show_all()
            gtk_popup_at_pointer(menu, event)

    def on_add_row(self, action):
        ''' Context menu: Add a row '''
        selection = self.treeview.get_selection()
        model, treeiter = selection.get_selected()
        if not treeiter:  # no selected item
            self.selection_info()
            return

        # Set default sorting.
        model.set_sort_column_id(-1, Gtk.SortType.ASCENDING)

        row = len(self.treeview.get_columns()) * ['']
        path = model.insert_after(treeiter, row)

    def on_clone_row(self, action):
        ''' Context menu: Clone a row '''
        selection = self.treeview.get_selection()
        model, treeiter = selection.get_selected()
        if not treeiter:  # no selected item
            self.selection_info()
            return

        path = model.get_path(treeiter)
        row = list(model[path[0]])  # copy
        model.insert_after(treeiter, row)

    def on_delete_row(self, action):
        ''' Context menu: Delete a row '''
        selection = self.treeview.get_selection()
        model, treeiter = selection.get_selected()
        if not treeiter:  # no selected item
            self.selection_info()
            return

        if len(model) > 1:
            model.remove(treeiter)
        else:
            md = Gtk.MessageDialog(
                None, Gtk.DialogFlags.DESTROY_WITH_PARENT,
                Gtk.MessageType.WARNING, Gtk.ButtonsType.CLOSE,
                _("The table must consist of at least on row!\n No deletion done."
                  ))
            # T: Popup dialog
            md.run()
            md.destroy()

    def on_move_row(self, action, direction):
        ''' Trigger for moving a row one position up/down '''
        selection = self.treeview.get_selection()
        model, treeiter = selection.get_selected()
        if not treeiter:  # no selected item
            self.selection_info()
            return

        path = model.get_path(treeiter)
        newpos = path[0] + direction
        if 0 > newpos or newpos >= len(
                model
        ):  # first item cannot be pushed forward, last not backwards
            return
        newiter = model.get_iter((newpos, ))

        # Set default sorting.
        model.set_sort_column_id(-1, Gtk.SortType.ASCENDING)

        # Change values of two rows.
        for col in range(model.get_n_columns()):
            value = model.get_value(treeiter, col)
            newvalue = model.get_value(newiter, col)
            model.set_value(newiter, col, value)
            model.set_value(treeiter, col, newvalue)

    def on_open_link(self, action, link):
        ''' Context menu: Open a link, which is written in a cell '''
        self.emit('link-clicked', {'href': str(link)})

    def on_open_help(self, action):
        ''' Context menu: Open help '''
        ZIM_APPLICATION.run('--manual', 'Plugins:Table Editor')

    def on_change_columns(self, action):
        ''' Context menu: Edit table, run the EditTableDialog '''
        aligns = self.model.get_aligns()
        wraps = self.model.get_wraps()
        headers = [col.get_title() for col in self.treeview.get_columns()]
        ids = [i for i in range(len(headers))]
        definition = ids, headers, wraps, aligns
        newdefinition = EditTableDialog(self.get_toplevel(), definition).run()
        if newdefinition:
            self.model.change_model(
                newdefinition)  # Will call back to change our treeview

    def on_cell_changed(self, cellrenderer, path, text, liststore, colid):
        ''' Trigger after cell-editing, to transform displayed table cell into right format '''
        self._keep_toolbar_open = False
        markup = CellFormatReplacer.input_to_cell(text)
        liststore[path][colid] = markup
        self._cellinput_canceled = False

    def on_cell_editing_started(self, cellrenderer, editable, path, liststore,
                                colid):
        ''' Trigger before cell-editing, to transform text-field data into right format '''
        self._keep_toolbar_open = True

        editable.connect('focus-out-event', self.on_cell_focus_out,
                         cellrenderer, path, liststore, colid)
        markup = liststore[path][colid]
        markup = CellFormatReplacer.cell_to_input(markup)
        editable.set_text(markup)
        self._cellinput_canceled = False

    def on_cell_focus_out(self, editable, event, cellrenderer, path, liststore,
                          colid):
        if not self._cellinput_canceled:
            self.on_cell_changed(cellrenderer, path, editable.get_text(),
                                 liststore, colid)

    def on_cell_editing_canceled(self, renderer):
        ''' Trigger after a cell is edited but any change is skipped '''
        self._cellinput_canceled = True

    def sort_by_number_or_string(self, liststore, treeiter1, treeiter2, colid):
        '''
		Sort algorithm for sorting numbers correctly and putting 10 after 3.
		This part can be improved in future to support also currencies, dates, floats, etc.
		:param liststore: model of treeview
		:param treeiter1: treeiter 1
		:param treeiter2: treeiter 2
		:param colid: a column number
		:return: -1 / first data is smaller than second, 0 / equality, 1 / else
		'''
        data1 = natural_sort_key(liststore.get_value(treeiter1, colid))
        data2 = natural_sort_key(liststore.get_value(treeiter2, colid))
        return (data1 > data2) - (data1 < data2)  # python3 jargon for "cmp()"

    def selection_info(self):
        ''' Info-Popup for selecting a cell before this action can be done '''
        md = Gtk.MessageDialog(
            None, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.WARNING,
            Gtk.ButtonsType.CLOSE,
            _("Please select a row, before you push the button."))
        # T:
        md.run()
        md.destroy()
Esempio n. 2
0
class TableViewWidget(CustomObjectWidget):

    __gsignals__ = {
        'size-request': 'override',
    }

    def __init__(self, obj, liststore, headers, attrs):
        '''
		This is a group of GTK Gui elements which are directly displayed within the wiki textarea
		On initilizing also some signals are registered and a toolbar is initialized
		:param obj: a Table-View-Object
		:param liststore: a gtk.ListStore object
		:param headers: list of titles
		:param attrs: table settings, like alignment and wrapping
		:return:
		'''
        CustomObjectWidget.__init__(self)
        self.textarea_width = 0

        # used in pageview
        self._has_cursor = False  # Skip table object, if someone moves cursor around in textview

        # used here
        self.obj = obj
        self._timer = None  # NONE or number of current gobject.timer, which is running
        self._keep_toolbar_open = False  # a cell is currently edited, toolbar should not be hidden
        self._cellinput_canceled = None  # cell changes should be skipped
        self._toolbar_enabled = True  # sets if toolbar should be shown beneath a selected table

        # Toolbar for table actions
        self.toolbar = self.create_toolbar()
        self.toolbar.show_all()
        self.toolbar.set_no_show_all(True)
        self.toolbar.hide()

        # Create treeview
        self._init_treeview(liststore, headers, attrs)

        # package gui elements
        self.vbox.pack_end(self.toolbar)
        self.scroll_win = ScrolledWindow(self.treeview, gtk.POLICY_NEVER,
                                         gtk.POLICY_NEVER, gtk.SHADOW_NONE)
        self.vbox.pack_start(self.scroll_win)

    def _init_treeview(self, liststore, headers, attrs):
        # Actual gtk table object
        self.treeview = self.create_treeview(liststore, headers, attrs)

        # Hook up signals & set options
        self.treeview.connect('button-press-event', self.on_button_press_event)
        self.treeview.connect('focus-in-event', self.on_focus_in, self.toolbar)
        self.treeview.connect('focus-out-event', self.on_focus_out,
                              self.toolbar)
        self.treeview.connect('move-cursor', self.on_move_cursor)

        # Set options
        self.treeview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)
        self.treeview.set_receives_default(True)
        self.treeview.set_size_request(-1, -1)
        self.treeview.set_border_width(2)

        # disable interactive column search
        self.treeview.set_enable_search(False)
        gtk.binding_entry_remove(gtk.TreeView, gtk.keysyms.f,
                                 gtk.gdk.CONTROL_MASK)
        self.treeview.set_search_column(-1)

    def on_model_changed(self, liststore, headers, attrs):
        '''Called by TableViewObject when columns changed, replaces the
		treeview idget with a new one for the new model
		'''
        self.scroll_win.remove(self.treeview)
        self._init_treeview(liststore, headers, attrs)
        self.scroll_win.add(self.treeview)
        self.scroll_win.show_all()

    def do_size_request(self, requisition):
        wraps = self.obj.get_wraps()
        if not any(wraps):
            return CustomObjectWidget.do_size_request(self, requisition)

        # Negotiate how to wrap ..
        for col in self.treeview.get_columns():
            cr = col.get_cell_renderers()[0]
            cr.set_property('wrap-width', -1)  # reset size

            #~ col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)  # allow column shrinks
            #~ col.set_max_width(0)	 # shrink column
            #~ col.set_max_width(-1)  # reset value
            #~ col.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)  # reset value

        CustomObjectWidget.do_size_request(self, requisition)

        #~ print "Widget requests: %i textview: %i" % (requisition.width, self._textview_width)
        if requisition.width > self._textview_width:
            # Figure out width of fixed cols
            fixed = 0
            for col, wrap in zip(self.treeview.get_columns(), wraps):
                if not wrap:
                    fixed += col.get_width()

            nwrap = sum(wraps)
            wrap_size = (self._textview_width - fixed) // nwrap

            # Set width for wrappable cols
            #~ print "Fixed, nwrap, wrap_size", (fixed, nwrap, wrap_size)
            for col, wrap in zip(self.treeview.get_columns(), wraps):
                if wrap:
                    cr = col.get_cell_renderers()[0]
                    cr.set_property('wrap-width', wrap_size)  # reset size

            # Update request
            CustomObjectWidget.do_size_request(self, requisition)
        else:
            pass

    def on_focus_in(self, treeview, event, toolbar):
        '''After a table is selected, this function will be triggered'''

        self._keep_toolbar_open = False
        if self._timer:
            gobject.source_remove(self._timer)
        if self._toolbar_enabled:
            toolbar.show()

    def on_focus_out(self, treeview, event, toolbar):
        '''After a table is deselected, this function will be triggered'''
        def receive_alarm():
            if self._keep_toolbar_open:
                self._timer = None
            if self._timer:
                self._timer = None
                treeview.get_selection().unselect_all()
                if self._toolbar_enabled:
                    toolbar.hide()
            return False

        self._timer = gobject.timeout_add(500, receive_alarm)

    def create_toolbar(self):
        '''This function creates a toolbar which is displayed next to the table'''
        toolbar = gtk.Toolbar()
        toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL)
        toolbar.set_style(gtk.TOOLBAR_ICONS)
        toolbar.set_border_width(1)

        tooltips = gtk.Tooltips()
        for pos, stock, handler, data, tooltip in (
            (0, gtk.STOCK_ADD, self.on_add_row, None,
             _('Add row')),  # T: tooltip on mouse hover
            (1, gtk.STOCK_DELETE, self.on_delete_row, None,
             _('Remove row')),  # T: tooltip on mouse hover
            (2, gtk.STOCK_COPY, self.on_clone_row, None,
             _('Clone row')),  # T: tooltip on mouse hover
            (3, None, None, None, None),
            (4, gtk.STOCK_GO_UP, self.on_move_row, -1,
             _('Row up')),  # T: tooltip on mouse hover
            (5, gtk.STOCK_GO_DOWN, self.on_move_row, 1,
             _('Row down')),  # T: tooltip on mouse hover
            (6, None, None, None, None),
            (7, gtk.STOCK_PREFERENCES, self.on_change_columns, None,
             _('Change columns')),  # T: tooltip on mouse hover
            (8, None, None, None, None),
            (9, gtk.STOCK_HELP, self.on_open_help, None,
             _('Open help')),  # T: tooltip on mouse hover
        ):
            if stock is None:
                toolbar.insert(gtk.SeparatorToolItem(), pos)
            else:
                button = gtk.ToolButton(stock)
                if data:
                    button.connect('clicked', handler, data)
                else:
                    button.connect('clicked', handler)
                tooltips.set_tip(button, tooltip)
                toolbar.insert(button, pos)

        toolbar.set_size_request(300, -1)
        toolbar.set_icon_size(gtk.ICON_SIZE_MENU)

        return toolbar

    def _column_alignment(self, aligntext):
        ''' The column alignment must be converted from numeric to keywords '''
        if aligntext == 'left':
            align = 0.0
        elif aligntext == 'center':
            align = 0.5
        elif aligntext == 'right':
            align = 1.0
        else:
            align = None
        return align

    def create_treeview(self, liststore, headers, attrs):
        '''
		Initializes a treeview with its model (liststore) and all its columns
		:param headers: a list of title values for the column-headers
		:param rows: a list of list of cells, for the table body
		:param attrs: some more attributes, which define the layout of a column
		:return: gtk.treeview
		'''
        treeview = gtk.TreeView(liststore)

        for i, headcol in enumerate(headers):
            cell = gtk.CellRendererText()
            tview_column = gtk.TreeViewColumn(headcol, cell)
            tview_column.set_sizing(
                gtk.TREE_VIEW_COLUMN_AUTOSIZE)  # allow column shrinks
            treeview.append_column(tview_column)

            # set title as label
            header_label = self.create_headerlabel(headcol)
            tview_column.set_widget(header_label)

            # set properties of column
            tview_column.set_attributes(cell, markup=i)
            cell.set_property('editable', True)
            cell.set_property(
                'yalign', 0.0)  # no vertical alignment, text starts on the top
            tview_column.set_sort_column_id(i)
            # set sort function
            liststore.set_sort_func(i, self.sort_by_number_or_string, i)
            # set alignment - left center right
            align = self._column_alignment(attrs['aligns'][i])
            if align:
                tview_column.set_alignment(align)
                cell.set_alignment(align, 0.0)

            # set wrap mode, wrap-size is set elsewhere
            if attrs['wraps'][i]:
                cell.set_property('wrap-mode', pango.WRAP_WORD)

            # callbacks after an action
            cell.connect('edited', self.on_cell_changed, treeview.get_model(),
                         i)
            cell.connect('editing-started', self.on_cell_editing_started,
                         treeview.get_model(), i)
            cell.connect('editing-canceled', self.on_cell_editing_canceled)

        return treeview

    def create_headerlabel(self, title):
        return TableViewWidget.create_headerlabel(title)

    @staticmethod
    def create_headerlabel(title):
        ''' Sets options for the treeview header'''
        col_widget = gtk.VBox()
        col_widget.show()

        col_label = gtk.Label('<u>' + title + '</u>')
        col_label.set_use_markup(True)
        col_label.show()
        col_widget.pack_start(col_label)
        #col_align.add(col_label)
        '''col_entry = InputEntry()
		col_entry.set_name('treeview-header-entry')
		col_entry.show()
		col_widget.pack_start(col_entry)'''

        return col_widget

    def get_treeview(self):
        # treeview of current table
        return self.treeview

    def set_preferences(self, preferences):
        self._toolbar_enabled = preferences.get('show_helper_toolbar', True)
        self.treeview.set_grid_lines(GTK_GRIDLINES[preferences.get(
            'grid_lines', LINES_BOTH)])

    def on_move_cursor(self, view, step_size, count):
        ''' If you try to move the cursor out of the tableditor release the cursor to the parent textview '''
        return None  # let parent handle this signal

    def fetch_cell_by_event(self, event, treeview):
        '''	Looks for the cell where the mouse clicked on it '''
        liststore = treeview.get_model()
        (xpos, ypos) = event.get_coords()
        (treepath, treecol, xrel,
         yrel) = treeview.get_path_at_pos(int(xpos), int(ypos))
        treeiter = liststore.get_iter(treepath)
        cellvalue = liststore.get_value(treeiter,
                                        treeview.get_columns().index(treecol))
        return cellvalue

    def get_linkurl(self, celltext):
        '''	Checks a cellvalue if it contains a link and returns only the link value '''
        linkregex = r'<span foreground="blue">.*?<span.*?>(.*?)</span></span>'
        matches = re.match(linkregex, celltext)
        linkvalue = matches.group(1) if matches else None
        return linkvalue

    def on_button_press_event(self, treeview, event):
        '''
		Displays a context-menu on right button click
		Opens the link of a tablecell on CTRL pressed and left button click
		'''
        if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1 and event.get_state(
        ) & gtk.gdk.CONTROL_MASK:
            # With CTRL + LEFT-Mouse-Click link of cell is opened
            cellvalue = self.fetch_cell_by_event(event, treeview)
            linkvalue = self.get_linkurl(cellvalue)
            if linkvalue:
                self.obj.emit('link-clicked', {'href': linkvalue})
            return

        if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
            # Right button opens context menu
            self._keep_toolbar_open = True
            cellvalue = self.fetch_cell_by_event(event, treeview)
            linkvalue = self.get_linkurl(cellvalue)
            linkitem_is_activated = (linkvalue is not None)

            menu = gtk.Menu()

            for stock, handler, data, tooltip in (
                (gtk.STOCK_ADD, self.on_add_row, None,
                 _('Add row')),  # T: menu item
                (gtk.STOCK_DELETE, self.on_delete_row, None,
                 _('Delete row')),  # T: menu item
                (gtk.STOCK_COPY, self.on_clone_row, None,
                 _('Clone row')),  # T: menu item
                (None, None, None, None),  # T: menu item
                (gtk.STOCK_JUMP_TO, self.on_open_link, linkvalue,
                 _('Open cell content link')),  # T: menu item
                (None, None, None, None),
                (gtk.STOCK_GO_UP, self.on_move_row, -1,
                 _('Row up')),  # T: menu item
                (gtk.STOCK_GO_DOWN, self.on_move_row, 1,
                 _('Row down')),  # T: menu item
                (None, None, None, None),
                (gtk.STOCK_PREFERENCES, self.on_change_columns, None,
                 _('Change columns'))  # T: menu item
            ):

                if stock is None:
                    menu.append(gtk.SeparatorMenuItem())
                else:
                    item = gtk.ImageMenuItem(stock)
                    item.set_always_show_image(True)
                    item.set_label(_(tooltip))
                    if data:
                        item.connect_after('activate', handler, data)
                    else:
                        item.connect_after('activate', handler)
                    if handler == self.on_open_link:
                        item.set_sensitive(linkitem_is_activated)
                    menu.append(item)

            menu.show_all()
            menu.popup(None, None, None, event.button, event.time)

    def on_add_row(self, action):
        ''' Context menu: Add a row '''
        selection = self.treeview.get_selection()
        model, treeiter = selection.get_selected()
        if not treeiter:  # no selected item
            self.selection_info()
            return

        row = len(self.treeview.get_columns()) * ['']
        path = model.insert_after(treeiter, row)
        self.obj.set_modified(True)

    def on_clone_row(self, action):
        ''' Context menu: Clone a row '''
        selection = self.treeview.get_selection()
        model, treeiter = selection.get_selected()
        if not treeiter:  # no selected item
            self.selection_info()
            return

        path = model.get_path(treeiter)
        row = model[path[0]]
        model.insert_after(treeiter, row)
        self.obj.set_modified(True)

    def on_delete_row(self, action):
        ''' Context menu: Delete a row '''
        selection = self.treeview.get_selection()
        model, treeiter = selection.get_selected()
        if not treeiter:  # no selected item
            self.selection_info()
            return

        if len(model) > 1:
            model.remove(treeiter)
            self.obj.set_modified(True)
        else:
            md = gtk.MessageDialog(
                None, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING,
                gtk.BUTTONS_CLOSE,
                _("The table must consist of at least on row!\n No deletion done."
                  ))
            # T: Popup dialog
            md.run()
            md.destroy()

    def on_move_row(self, action, direction):
        ''' Trigger for moving a row one position up/down '''
        selection = self.treeview.get_selection()
        model, treeiter = selection.get_selected()
        if not treeiter:  # no selected item
            self.selection_info()
            return

        path = model.get_path(treeiter)
        newpos = path[0] + direction
        if 0 > newpos or newpos >= len(
                model
        ):  # first item cannot be pushed forward, last not backwards
            return
        newiter = model.get_iter((newpos, ))

        model.swap(treeiter, newiter)
        self.obj.set_modified(True)

    def on_open_link(self, action, link):
        ''' Context menu: Open a link, which is written in a cell '''
        self.obj.emit('link-clicked', {'href': link})

    def on_open_help(self, action):
        ''' Context menu: Open help '''
        get_zim_application('--manual', 'Plugins:Table Editor').spawn()
        pass

    def on_change_columns(self, action):
        ''' Context menu: Edit table, run the EditTableDialog '''
        aligns = self.obj.get_aligns()
        wraps = self.obj.get_wraps()
        titles = [col.get_title() for col in self.treeview.get_columns()]
        old_model = []
        for i in range(len(titles)):
            old_model.append([i, titles[i], aligns[i], wraps[i]])

        new_model = EditTableDialog(self.get_toplevel(), old_model).run()

        if new_model:
            self.obj.change_model(
                new_model)  # Will call back to change our treeview

    def on_cell_changed(self, cellrenderer, path, text, liststore, colid):
        ''' Trigger after cell-editing, to transform displayed table cell into right format '''
        self._keep_toolbar_open = False
        markup = CellFormatReplacer.input_to_cell(text, True)
        liststore[path][colid] = markup
        self._cellinput_canceled = False

    def on_cell_editing_started(self, cellrenderer, editable, path, liststore,
                                colid):
        ''' Trigger before cell-editing, to transform text-field data into right format '''
        self._keep_toolbar_open = True

        editable.connect('focus-out-event', self.on_cell_focus_out,
                         cellrenderer, path, liststore, colid)
        markup = liststore[path][colid]
        markup = CellFormatReplacer.cell_to_input(markup, True)
        editable.set_text(markup)
        self._cellinput_canceled = False

    def on_cell_focus_out(self, editable, event, cellrenderer, path, liststore,
                          colid):
        if not self._cellinput_canceled:
            self.on_cell_changed(cellrenderer, path, editable.get_text(),
                                 liststore, colid)

    def on_cell_editing_canceled(self, renderer):
        ''' Trigger after a cell is edited but any change is skipped '''
        self._cellinput_canceled = True

    def sort_by_number_or_string(self, liststore, treeiter1, treeiter2, colid):
        '''
		Sort algorithm for sorting numbers correctly and putting 10 after 3.
		This part can be improved in future to support also currencies, dates, floats, etc.
		:param liststore: model of treeview
		:param treeiter1: treeiter 1
		:param treeiter2: treeiter 2
		:param colid: a column number
		:return: -1 / first data is smaller than second, 0 / equality, 1 / else
		'''
        data1 = liststore.get_value(treeiter1, colid)
        data2 = liststore.get_value(treeiter2, colid)
        if data1.isdigit() and data2.isdigit():
            data1 = int(data1)
            data2 = int(data2)
        self.obj.set_modified(True)
        return cmp(data1, data2)

    def selection_info(self):
        ''' Info-Popup for selecting a cell before this action can be done '''
        md = gtk.MessageDialog(
            None, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING,
            gtk.BUTTONS_CLOSE,
            _("Please select a row, before you push the button."))
        # T:
        md.run()
        md.destroy()