diff options
author | Jesús Eduardo <heckyel@hyperbola.info> | 2017-09-11 17:47:17 -0500 |
---|---|---|
committer | Jesús Eduardo <heckyel@hyperbola.info> | 2017-09-11 17:47:17 -0500 |
commit | 14738704ede6dfa6ac79f362a9c1f7f40f470cdc (patch) | |
tree | 31c83bdd188ae7b64d7169974d6f066ccfe95367 /mvc/widgets/gtk/tableview.py | |
parent | eb1896583afbbb622cadcde1a24e17173f61904f (diff) | |
download | librevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.tar.lz librevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.tar.xz librevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.zip |
rename mvc at lvc
Diffstat (limited to 'mvc/widgets/gtk/tableview.py')
-rw-r--r-- | mvc/widgets/gtk/tableview.py | 1557 |
1 files changed, 0 insertions, 1557 deletions
diff --git a/mvc/widgets/gtk/tableview.py b/mvc/widgets/gtk/tableview.py deleted file mode 100644 index 930270c..0000000 --- a/mvc/widgets/gtk/tableview.py +++ /dev/null @@ -1,1557 +0,0 @@ -# @Base: Miro - an RSS based video player application -# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 -# Participatory Culture Foundation -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. - -"""tableview.py -- Wrapper for the GTKTreeView widget. It's used for the tab -list and the item list (AKA almost all of the miro). -""" - -import logging - -import itertools -import gobject -import gtk -from collections import namedtuple - -# These are probably wrong, and are placeholders for now, until custom headers -# are also implemented for GTK. -CUSTOM_HEADER_HEIGHT = 25 -HEADER_HEIGHT = 25 - -from mvc import signals -from mvc.errors import (WidgetActionError, WidgetDomainError, - WidgetRangeError, WidgetNotReadyError) -from mvc.widgets.tableselection import SelectionOwnerMixin -from mvc.widgets.tablescroll import ScrollbarOwnerMixin -import drawing -import wrappermap -from .base import Widget -from .simple import Image -from .layoutmanager import LayoutManager -from .weakconnect import weak_connect -from .tableviewcells import GTKCustomCellRenderer - - -PathInfo = namedtuple('PathInfo', 'path column x y') -Rect = namedtuple('Rect', 'x y width height') -_album_view_gtkrc_installed = False - -def _install_album_view_gtkrc(): - """Hack for styling GTKTreeView for the album view widget. - - We do a couple things: - - Remove the focus ring - - Remove any separator space. - - We do this so that we don't draw a box through the album view column for - selected rows. - """ - global _album_view_gtkrc_installed - if _album_view_gtkrc_installed: - return - rc_string = ('style "album-view-style"\n' - '{ \n' - ' GtkTreeView::vertical-separator = 0\n' - ' GtkTreeView::horizontal-separator = 0\n' - ' GtkWidget::focus-line-width = 0 \n' - '}\n' - 'widget "*.miro-album-view" style "album-view-style"\n') - gtk.rc_parse_string(rc_string) - _album_view_gtkrc_installed = True - -def rect_contains_point(rect, x, y): - return ((rect.x <= x < rect.x + rect.width) and - (rect.y <= y < rect.y + rect.height)) - -class TreeViewScrolling(object): - def __init__(self): - self.scrollbars = [] - self.scroll_positions = None, None - self.restoring_scroll = None - self.connect('parent-set', self.on_parent_set) - self.scroller = None - # hack necessary because of our weird widget hierarchy (GTK doesn't deal - # well with the Scroller's widget not being the direct parent of the - # TableView's widget.) - self._coords_working = False - - def scroll_range_changed(self): - """Faux-signal; this should all be integrated into - GTKScrollbarOwnerMixin, making this unnecessary. - """ - - @property - def manually_scrolled(self): - """Return whether the view has been scrolled explicitly by the user - since the last time it was set automatically. - """ - auto_pos = self.scroll_positions[1] - if auto_pos is None: - # if we don't have any position yet, user can't have manually - # scrolled - return False - real_pos = self.scrollbars[1].get_value() - return abs(auto_pos - real_pos) > 5 # allowing some fuzziness - - @property - def position_set(self): - """Return whether the scroll position has been set in any way.""" - return any(x is not None for x in self.scroll_positions) - - def on_parent_set(self, widget, old_parent): - """We have parent window now; we need to control its scrollbars.""" - self.set_scroller(widget.get_parent()) - - def set_scroller(self, window): - """Take control of the scrollbars of window.""" - if not isinstance(window, gtk.ScrolledWindow): - return - self.scroller = window - scrollbars = tuple(bar.get_adjustment() - for bar in (window.get_hscrollbar(), window.get_vscrollbar())) - self.scrollbars = scrollbars - for i, bar in enumerate(scrollbars): - weak_connect(bar, 'changed', self.on_scroll_range_changed, i) - if self.restoring_scroll: - self.set_scroll_position(self.restoring_scroll) - - def on_scroll_range_changed(self, adjustment, bar): - """The scrollbar might have a range now. Set its initial position if - we haven't already. - """ - self._coords_working = True - if self.restoring_scroll: - self.set_scroll_position(self.restoring_scroll) - # our wrapper handles the same thing for iters - self.scroll_range_changed() - - def set_scroll_position(self, scroll_position): - """Restore the scrollbars to a remembered state.""" - try: - self.scroll_positions = tuple(self._clip_pos(adj, x) - for adj, x in zip(self.scrollbars, scroll_position)) - except WidgetActionError, error: - logging.debug("can't scroll yet: %s", error.reason) - # try again later - self.restoring_scroll = scroll_position - else: - for adj, pos in zip(self.scrollbars, self.scroll_positions): - adj.set_value(pos) - self.restoring_scroll = None - - def _clip_pos(self, adj, pos): - lower = adj.get_lower() - upper = adj.get_upper() - adj.get_page_size() - # currently, StandardView gets an upper of 2.0 when it's not ready - # FIXME: don't count on that - if pos > upper and upper < 5: - raise WidgetRangeError("scrollable area", pos, lower, upper) - return min(max(pos, lower), upper) - - def get_path_rect(self, path): - """Return the Rect for the given item, in tree coords.""" - if not self._coords_working: - # part of solution to #17405; widget_to_tree_coords tends to return - # y=8 before the first scroll-range-changed signal. ugh. - raise WidgetNotReadyError('_coords_working') - rect = self.get_background_area(path, self.get_columns()[0]) - x, y = self.widget_to_tree_coords(rect.x, rect.y) - return Rect(x, y, rect.width, rect.height) - - @property - def _scrollbars(self): - if not self.scrollbars: - raise WidgetNotReadyError - return self.scrollbars - - def scroll_ancestor(self, newly_selected, down): - # Try to figure out what just became selected. If multiple things - # somehow became selected, select the outermost one - if len(newly_selected) == 0: - raise WidgetActionError("need at an item to scroll to") - if down: - path_to_show = max(newly_selected) - else: - path_to_show = min(newly_selected) - - if not self.scrollbars: - return - vadjustment = self.scrollbars[1] - - rect = self.get_background_area(path_to_show, self.get_columns()[0]) - _, top = self.translate_coordinates(self.scroller, 0, rect.y) - top += vadjustment.value - bottom = top + rect.height - if down: - if bottom > vadjustment.value + vadjustment.page_size: - bottom_value = min(bottom, vadjustment.upper) - vadjustment.set_value(bottom_value - vadjustment.page_size) - else: - if top < vadjustment.value: - vadjustment.set_value(max(vadjustment.lower, top)) - -class MiroTreeView(gtk.TreeView, TreeViewScrolling): - """Extends the GTK TreeView widget to help implement TableView - https://develop.participatoryculture.org/index.php/WidgetAPITableView""" - # Add a tiny bit of padding so that the user can drag feeds below - # the table, i.e. to the bottom row, as a top-level - PAD_BOTTOM = 3 - def __init__(self): - gtk.TreeView.__init__(self) - TreeViewScrolling.__init__(self) - self.height_without_pad_bottom = -1 - self.set_enable_search(False) - self.horizontal_separator = self.style_get_property("horizontal-separator") - self.expander_size = self.style_get_property("expander-size") - self.group_lines_enabled = False - self.group_line_color = (0, 0, 0) - self.group_line_width = 1 - - def do_size_request(self, req): - gtk.TreeView.do_size_request(self, req) - self.height_without_pad_bottom = req.height - req.height += self.PAD_BOTTOM - - def do_move_cursor(self, step, count): - if step == gtk.MOVEMENT_VISUAL_POSITIONS: - # GTK is asking us to move left/right. Since our TableViews don't - # support this, return False to let the key press propagate. See - # #15646 for more info. - return False - if isinstance(self.get_parent(), gtk.ScrolledWindow): - # If our parent is a ScrolledWindow, let GTK take care of this - handled = gtk.TreeView.do_move_cursor(self, step, count) - return handled - else: - # Otherwise, we have to search up the widget tree for a - # ScrolledWindow to take care of it - selection = self.get_selection() - model, start_selection = selection.get_selected_rows() - gtk.TreeView.do_move_cursor(self, step, count) - - model, end_selection = selection.get_selected_rows() - newly_selected = set(end_selection) - set(start_selection) - down = (count > 0) - - try: - self.scroll_ancestor(newly_selected, down) - except WidgetActionError: - # not possible - return False - return True - - def get_position_info(self, x, y): - """Wrapper for get_path_at_pos that converts the path_info to a named - tuple and handles rounding the coordinates. - """ - path_info = self.get_path_at_pos(int(round(x)), int(round(y))) - if path_info: - return PathInfo(*path_info) - -gobject.type_register(MiroTreeView) - -class HotspotTracker(object): - """Handles tracking hotspots. - https://develop.participatoryculture.org/index.php/WidgetAPITableView""" - - def __init__(self, treeview, event): - self.treeview = treeview - self.treeview_wrapper = wrappermap.wrapper(treeview) - self.hit = False - self.button = event.button - path_info = treeview.get_position_info(event.x, event.y) - if path_info is None: - return - self.path, self.column, background_x, background_y = path_info - # We always pack 1 renderer for each column - gtk_renderer = self.column.get_cell_renderers()[0] - if not isinstance(gtk_renderer, GTKCustomCellRenderer): - return - self.renderer = wrappermap.wrapper(gtk_renderer) - self.attr_map = self.treeview_wrapper.attr_map_for_column[self.column] - if not rect_contains_point(self.calc_cell_area(), event.x, event.y): - # Mouse is in the padding around the actual cell area - return - self.update_position(event) - self.iter = treeview.get_model().get_iter(self.path) - self.name = self.calc_hotspot() - if self.name is not None: - self.hit = True - - def is_for_context_menu(self): - return self.name == "#show-context-menu" - - def calc_cell_area(self): - cell_area = self.treeview.get_cell_area(self.path, self.column) - xpad = self.renderer._renderer.props.xpad - ypad = self.renderer._renderer.props.ypad - cell_area.x += xpad - cell_area.y += ypad - cell_area.width -= xpad * 2 - cell_area.height -= ypad * 2 - return cell_area - - def update_position(self, event): - self.x, self.y = int(event.x), int(event.y) - - def calc_cell_state(self): - if self.treeview.get_selection().path_is_selected(self.path): - if self.treeview.flags() & gtk.HAS_FOCUS: - return gtk.STATE_SELECTED - else: - return gtk.STATE_ACTIVE - else: - return gtk.STATE_NORMAL - - def calc_hotspot(self): - cell_area = self.calc_cell_area() - if rect_contains_point(cell_area, self.x, self.y): - model = self.treeview.get_model() - self.renderer.cell_data_func(self.column, self.renderer._renderer, - model, self.iter, self.attr_map) - style = drawing.DrawingStyle(self.treeview_wrapper, - use_base_color=True, state=self.calc_cell_state()) - x = self.x - cell_area.x - y = self.y - cell_area.y - return self.renderer.hotspot_test(style, - self.treeview_wrapper.layout_manager, - x, y, cell_area.width, cell_area.height) - else: - return None - - def update_hit(self): - if self.is_for_context_menu(): - return # we always keep hit = True for this one - old_hit = self.hit - self.hit = (self.calc_hotspot() == self.name) - if self.hit != old_hit: - self.redraw_cell() - - def redraw_cell(self): - # Check that the treeview is still around. We might have switched - # views in response to a hotspot being clicked. - if self.treeview.flags() & gtk.REALIZED: - cell_area = self.treeview.get_cell_area(self.path, self.column) - x, y = self.treeview.tree_to_widget_coords(cell_area.x, - cell_area.y) - self.treeview.queue_draw_area(x, y, - cell_area.width, cell_area.height) - -class TableColumn(signals.SignalEmitter): - """A single column of a TableView. - - Signals: - - clicked (table_column) -- The header for this column was clicked. - """ - # GTK hard-codes 4px of padding for each column - FIXED_PADDING = 4 - def __init__(self, title, renderer, header=None, **attrs): - # header widget not used yet in GTK (#15800) - signals.SignalEmitter.__init__(self) - self.create_signal('clicked') - self._column = gtk.TreeViewColumn(title, renderer._renderer) - self._column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) - self._column.set_clickable(True) - self.attrs = attrs - renderer.setup_attributes(self._column, attrs) - self.renderer = renderer - weak_connect(self._column, 'clicked', self._header_clicked) - self.do_horizontal_padding = True - - def set_right_aligned(self, right_aligned): - """Horizontal alignment of the header label.""" - if right_aligned: - self._column.set_alignment(1.0) - else: - self._column.set_alignment(0.0) - - def set_min_width(self, width): - self._column.props.min_width = width + TableColumn.FIXED_PADDING - - def set_max_width(self, width): - self._column.props.max_width = width - - def set_width(self, width): - self._column.set_fixed_width(width + TableColumn.FIXED_PADDING) - - def get_width(self): - return self._column.get_width() - - def _header_clicked(self, tablecolumn): - self.emit('clicked') - - def set_resizable(self, resizable): - """Set if the user can resize the column.""" - self._column.set_resizable(resizable) - - def set_do_horizontal_padding(self, horizontal_padding): - self.do_horizontal_padding = False - - def set_sort_indicator_visible(self, visible): - """Show/Hide the sort indicator for this column.""" - self._column.set_sort_indicator(visible) - - def get_sort_indicator_visible(self): - return self._column.get_sort_indicator() - - def set_sort_order(self, ascending): - """Display a sort indicator on the column header. Ascending can be - either True or False which affects the direction of the indicator. - """ - if ascending: - self._column.set_sort_order(gtk.SORT_ASCENDING) - else: - self._column.set_sort_order(gtk.SORT_DESCENDING) - - def get_sort_order_ascending(self): - """Returns if the sort indicator is displaying that the sort is - ascending. - """ - return self._column.get_sort_order() == gtk.SORT_ASCENDING - -class GTKSelectionOwnerMixin(SelectionOwnerMixin): - """GTK-specific methods for selection management. - - This subclass should not define any behavior. Methods that cannot be - completed in this widget state should raise WidgetActionError. - """ - def __init__(self): - SelectionOwnerMixin.__init__(self) - self.selection = self._widget.get_selection() - weak_connect(self.selection, 'changed', self.on_selection_changed) - - def _set_allow_multiple_select(self, allow): - if allow: - mode = gtk.SELECTION_MULTIPLE - else: - mode = gtk.SELECTION_SINGLE - self.selection.set_mode(mode) - - def _get_allow_multiple_select(self): - return self.selection.get_mode() == gtk.SELECTION_MULTIPLE - - def _get_selected_iters(self): - iters = [] - def collect(treemodel, path, iter_): - iters.append(iter_) - self.selection.selected_foreach(collect) - return iters - - def _get_selected_iter(self): - model, iter_ = self.selection.get_selected() - return iter_ - - @property - def num_rows_selected(self): - return self.selection.count_selected_rows() - - def _is_selected(self, iter_): - return self.selection.iter_is_selected(iter_) - - def _select(self, iter_): - self.selection.select_iter(iter_) - - def _unselect(self, iter_): - self.selection.unselect_iter(iter_) - - def _unselect_all(self): - self.selection.unselect_all() - - def _iter_to_string(self, iter_): - return self._model.get_string_from_iter(iter_) - - def _iter_from_string(self, string): - try: - return self._model.get_iter_from_string(string) - except ValueError: - raise WidgetDomainError( - "model iters", string, "%s other iters" % len(self.model)) - - def select_path(self, path): - self.selection.select_path(path) - - def _validate_iter(self, iter_): - if self.get_path(iter_) is None: - raise WidgetDomainError( - "model iters", iter_, "%s other iters" % len(self.model)) - real_model = self._widget.get_model() - if not real_model: - raise WidgetActionError("no model") - elif real_model != self._model: - raise WidgetActionError("wrong model?") - - def get_cursor(self): - """Return the path of the 'focused' item.""" - path, column = self._widget.get_cursor() - return path - - def set_cursor(self, path): - """Set the path of the 'focused' item.""" - if path is None: - # XXX: is there a way to clear the cursor? - return - path_as_string = ':'.join(str(component) for component in path) - with self.preserving_selection(): # set_cursor() messes up the selection - self._widget.set_cursor(path_as_string) - -class DNDHandlerMixin(object): - """TableView row DnD. - - Depends on arbitrary TableView methods; otherwise self-contained except: - on_button_press: may call start_drag - on_button_release: may unset drag_button_down - on_motion_notify: may call potential_drag_motion - """ - def __init__(self): - self.drag_button_down = False - self.drag_data = {} - self.drag_source = self.drag_dest = None - self.drag_start_x, self.drag_start_y = None, None - self.wrapped_widget_connect('drag-data-get', self.on_drag_data_get) - self.wrapped_widget_connect('drag-end', self.on_drag_end) - self.wrapped_widget_connect('drag-motion', self.on_drag_motion) - self.wrapped_widget_connect('drag-leave', self.on_drag_leave) - self.wrapped_widget_connect('drag-drop', self.on_drag_drop) - self.wrapped_widget_connect('drag-data-received', - self.on_drag_data_received) - self.wrapped_widget_connect('unrealize', self.on_drag_unrealize) - - def set_drag_source(self, drag_source): - self.drag_source = drag_source - # XXX: the following note no longer seems accurate: - # No need to call enable_model_drag_source() here, we handle it - # ourselves in on_motion_notify() - - def set_drag_dest(self, drag_dest): - """Set the drop handler.""" - self.drag_dest = drag_dest - if drag_dest is not None: - targets = self._gtk_target_list(drag_dest.allowed_types()) - self._widget.enable_model_drag_dest(targets, - drag_dest.allowed_actions()) - self._widget.drag_dest_set(0, targets, - drag_dest.allowed_actions()) - else: - self._widget.unset_rows_drag_dest() - self._widget.drag_dest_unset() - - def start_drag(self, treeview, event, path_info): - """Check whether the event is a drag event; return whether handled - here. - """ - if event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK): - return False - model, row_paths = treeview.get_selection().get_selected_rows() - - if path_info.path not in row_paths: - # something outside the selection is being dragged. - # make it the new selection. - self.unselect_all(signal=False) - self.select_path(path_info.path) - row_paths = [path_info.path] - rows = self.model.get_rows(row_paths) - self.drag_data = rows and self.drag_source.begin_drag(self, rows) - self.drag_button_down = bool(self.drag_data) - if self.drag_button_down: - self.drag_start_x = int(event.x) - self.drag_start_y = int(event.y) - - if len(row_paths) > 1 and path_info.path in row_paths: - # handle multiple selection. If the current row is already - # selected, stop propagating the signal. We will only change - # the selection if the user doesn't start a DnD operation. - # This makes it more natural for the user to drag a block of - # selected items. - renderer = path_info.column.get_cell_renderers()[0] - if (not self._x_coord_in_expander(treeview, path_info) - and not isinstance(renderer, GTKCheckboxCellRenderer)): - self.delaying_press = True - # grab keyboard focus since we handled the event - self.focus() - return True - - def on_drag_data_get(self, treeview, context, selection, info, timestamp): - for typ, data in self.drag_data.items(): - selection.set(typ, 8, repr(data)) - - def on_drag_end(self, treeview, context): - self.drag_data = {} - - def find_type(self, drag_context): - return self._widget.drag_dest_find_target(drag_context, - self._widget.drag_dest_get_target_list()) - - def calc_positions(self, x, y): - """Given x and y coordinates, generate a list of drop positions to - try. The values are tuples in the form of (parent_path, position, - gtk_path, gtk_position), where parent_path and position is the - position to send to the Miro code, and gtk_path and gtk_position is an - equivalent position to send to the GTK code if the drag_dest validates - the drop. - """ - model = self._model - try: - gtk_path, gtk_position = self._widget.get_dest_row_at_pos(x, y) - except TypeError: - # Below the last row - yield (None, len(model), None, None) - return - - iter_ = model.get_iter(gtk_path) - if gtk_position in (gtk.TREE_VIEW_DROP_INTO_OR_BEFORE, - gtk.TREE_VIEW_DROP_INTO_OR_AFTER): - yield (iter_, -1, gtk_path, gtk_position) - - if hasattr(model, 'iter_is_valid'): - # tablist has this; item list does not - assert model.iter_is_valid(iter_) - parent_iter = model.iter_parent(iter_) - position = gtk_path[-1] - if gtk_position in (gtk.TREE_VIEW_DROP_BEFORE, - gtk.TREE_VIEW_DROP_INTO_OR_BEFORE): - # gtk gave us a "before" position, no need to change it - yield (parent_iter, position, gtk_path, gtk.TREE_VIEW_DROP_BEFORE) - else: - # gtk gave us an "after" position, translate that to before the - # next row for miro. - if (self._widget.row_expanded(gtk_path) and - model.iter_has_child(iter_)): - child_path = gtk_path + (0,) - yield (iter_, 0, child_path, gtk.TREE_VIEW_DROP_BEFORE) - else: - yield (parent_iter, position+1, gtk_path, - gtk.TREE_VIEW_DROP_AFTER) - - def on_drag_motion(self, treeview, drag_context, x, y, timestamp): - if not self.drag_dest: - return True - type = self.find_type(drag_context) - if type == "NONE": - drag_context.drag_status(0, timestamp) - return True - drop_action = 0 - for pos_info in self.calc_positions(x, y): - drop_action = self.drag_dest.validate_drop(self, self.model, type, - drag_context.actions, pos_info[0], pos_info[1]) - if isinstance(drop_action, (list, tuple)): - drop_action, iter = drop_action - path = self.model.get_path(iter) - pos = gtk.TREE_VIEW_DROP_INTO_OR_BEFORE - else: - path, pos = pos_info[2:4] - - if drop_action: - self.set_drag_dest_row(path, pos) - break - else: - self.unset_drag_dest_row() - drag_context.drag_status(drop_action, timestamp) - return True - - def set_drag_dest_row(self, path, position): - self._widget.set_drag_dest_row(path, position) - - def unset_drag_dest_row(self): - self._widget.unset_drag_dest_row() - - def on_drag_leave(self, treeview, drag_context, timestamp): - treeview.unset_drag_dest_row() - - def on_drag_drop(self, treeview, drag_context, x, y, timestamp): - # prevent the default handler - treeview.emit_stop_by_name('drag-drop') - target = self.find_type(drag_context) - if target == "NONE": - return False - treeview.drag_get_data(drag_context, target, timestamp) - treeview.unset_drag_dest_row() - - def on_drag_data_received(self, - treeview, drag_context, x, y, selection, info, timestamp): - # prevent the default handler - treeview.emit_stop_by_name('drag-data-received') - if not self.drag_dest: - return - type = self.find_type(drag_context) - if type == "NONE": - return - if selection.data is None: - return - drop_action = 0 - for pos_info in self.calc_positions(x, y): - drop_action = self.drag_dest.validate_drop(self, self.model, type, - drag_context.actions, pos_info[0], pos_info[1]) - if drop_action: - self.drag_dest.accept_drop(self, self.model, type, - drag_context.actions, pos_info[0], pos_info[1], - eval(selection.data)) - return True - return False - - def on_drag_unrealize(self, treeview): - self.drag_button_down = False - - def potential_drag_motion(self, treeview, event): - """A motion event has occurred and did not hit a hotspot; start a drag - if applicable. - """ - if (self.drag_data and self.drag_button_down and - treeview.drag_check_threshold(self.drag_start_x, - self.drag_start_y, int(event.x), int(event.y))): - self.delaying_press = False - treeview.drag_begin(self._gtk_target_list(self.drag_data.keys()), - self.drag_source.allowed_actions(), 1, event) - - @staticmethod - def _gtk_target_list(types): - count = itertools.count() - return [(type, gtk.TARGET_SAME_APP, count.next()) for type in types] - -class HotspotTrackingMixin(object): - def __init__(self): - self.hotspot_tracker = None - self.create_signal('hotspot-clicked') - self._hotspot_callback_handles = [] - self._connect_hotspot_signals() - self.wrapped_widget_connect('unrealize', self.on_hotspot_unrealize) - - def _connect_hotspot_signals(self): - SIGNALS = { - 'row-inserted': self.on_row_inserted, - 'row-deleted': self.on_row_deleted, - 'row-changed': self.on_row_changed, - } - self._hotspot_callback_handles.extend( - weak_connect(self._model, signal, handler) - for signal, handler in SIGNALS.iteritems()) - - def _disconnect_hotspot_signals(self): - for handle in self._hotspot_callback_handles: - self._model.disconnect(handle) - self._hotspot_callback_handles = [] - - def on_row_inserted(self, model, path, iter_): - if self.hotspot_tracker: - self.hotspot_tracker.redraw_cell() - self.hotspot_tracker = None - - def on_row_deleted(self, model, path): - if self.hotspot_tracker: - self.hotspot_tracker.redraw_cell() - self.hotspot_tracker = None - - def on_row_changed(self, model, path, iter_): - if self.hotspot_tracker: - self.hotspot_tracker.update_hit() - - def handle_hotspot_hit(self, treeview, event): - """Check whether the event is a hotspot event; return whether handled - here. - """ - if self.hotspot_tracker: - return - hotspot_tracker = HotspotTracker(treeview, event) - if hotspot_tracker.hit: - self.hotspot_tracker = hotspot_tracker - hotspot_tracker.redraw_cell() - if hotspot_tracker.is_for_context_menu(): - menu = self._popup_context_menu(self.hotspot_tracker.path, event) - if menu: - menu.connect('selection-done', - self._on_hotspot_context_menu_selection_done) - # grab keyboard focus since we handled the event - self.focus() - return True - - def _on_hotspot_context_menu_selection_done(self, menu): - # context menu is closed, we won't get the button-release-event in - # this case, but we can unset hotspot tracker here. - if self.hotspot_tracker: - self.hotspot_tracker.redraw_cell() - self.hotspot_tracker = None - - def on_hotspot_unrealize(self, treeview): - self.hotspot_tracker = None - - def release_on_hotspot(self, event): - """A button_release occurred; return whether it has been handled as a - hotspot hit. - """ - hotspot_tracker = self.hotspot_tracker - if hotspot_tracker and event.button == hotspot_tracker.button: - hotspot_tracker.update_position(event) - hotspot_tracker.update_hit() - if (hotspot_tracker.hit and - not hotspot_tracker.is_for_context_menu()): - self.emit('hotspot-clicked', hotspot_tracker.name, - hotspot_tracker.iter) - hotspot_tracker.redraw_cell() - self.hotspot_tracker = None - return True - - def hotspot_model_changed(self): - """A bulk change has ended; reconnect signals and update hotspots.""" - self._connect_hotspot_signals() - if self.hotspot_tracker: - self.hotspot_tracker.redraw_cell() - self.hotspot_tracker.update_hit() - -class ColumnOwnerMixin(object): - """Keeps track of the table's columns - including the list of columns, and - properties that we set for a table but need to apply to each column. - - This manages: - columns - attr_map_for_column - gtk_column_to_wrapper - for use throughout tableview. - """ - def __init__(self): - self._columns_draggable = False - self._renderer_xpad = self._renderer_ypad = 0 - self.columns = [] - self.attr_map_for_column = {} - self.gtk_column_to_wrapper = {} - self.create_signal('reallocate-columns') # not emitted on GTK - - def remove_column(self, index): - """Remove a column from the display and forget it from the column lists. - """ - column = self.columns.pop(index) - del self.attr_map_for_column[column._column] - del self.gtk_column_to_wrapper[column._column] - self._widget.remove_column(column._column) - - def get_columns(self): - """Returns the current columns, in order, by title.""" - # FIXME: this should probably return column objects, and really should - # not be keeping track of columns by title at all - titles = [column.get_title().decode('utf-8') - for column in self._widget.get_columns()] - return titles - - def add_column(self, column): - """Append a column to this table; setup all necessary mappings, and - setup the new column's properties to match the table's settings. - """ - self.model.check_new_column(column) - self._widget.append_column(column._column) - self.columns.append(column) - self.attr_map_for_column[column._column] = column.attrs - self.gtk_column_to_wrapper[column._column] = column - self.setup_new_column(column) - - def setup_new_column(self, column): - """Apply properties that we keep track of at the table level to a - newly-created column. - """ - if self.background_color: - column.renderer._renderer.set_property('cell-background-gdk', - self.background_color) - column._column.set_reorderable(self._columns_draggable) - if column.do_horizontal_padding: - column.renderer._renderer.set_property('xpad', self._renderer_xpad) - column.renderer._renderer.set_property('ypad', self._renderer_ypad) - - def set_column_spacing(self, space): - """Set the amount of space between columns.""" - self._renderer_xpad = space / 2 - for column in self.columns: - if column.do_horizontal_padding: - column.renderer._renderer.set_property('xpad', - self._renderer_xpad) - - def set_row_spacing(self, space): - """Set the amount of space between columns.""" - self._renderer_ypad = space / 2 - for column in self.columns: - column.renderer._renderer.set_property('ypad', self._renderer_ypad) - - def set_columns_draggable(self, setting): - """Set the draggability of existing and future columns.""" - self._columns_draggable = setting - for column in self.columns: - column._column.set_reorderable(setting) - - def set_column_background_color(self): - """Set the background color of existing columns to the table's - background_color. - """ - for column in self.columns: - column.renderer._renderer.set_property('cell-background-gdk', - self.background_color) - - def set_auto_resizes(self, setting): - # FIXME: to be implemented. - # At this point, GTK somehow does the right thing anyway in terms of - # auto-resizing. I'm not sure exactly what's happening, but I believe - # that if the column widths don't add up to the total width, - # gtk.TreeView allocates extra width for the last column. This works - # well enough for the tab list and item list, since there's only one - # column. - pass - -class HoverTrackingMixin(object): - """Handle mouse hover events - tooltips for some cells and hover events for - renderers which support them. - """ - def __init__(self): - self.hover_info = None - self.hover_pos = None - if hasattr(self, 'get_tooltip'): - # this should probably be something like self.set_tooltip_source - self._widget.set_property('has-tooltip', True) - self.wrapped_widget_connect('query-tooltip', self.on_tooltip) - self._last_tooltip_place = None - - def on_tooltip(self, treeview, x, y, keyboard_mode, tooltip): - # x, y are relative to the entire widget, but we want them to be - # relative to our bin window. The bin window doesn't include things - # like the column headers. - origin = treeview.window.get_origin() - bin_origin = treeview.get_bin_window().get_origin() - x += origin[0] - bin_origin[0] - y += origin[1] - bin_origin[1] - path_info = treeview.get_position_info(x, y) - if path_info is None: - self._last_tooltip_place = None - return False - if (self._last_tooltip_place is not None and - path_info[:2] != self._last_tooltip_place): - # the default GTK behavior is to keep the tooltip in the same - # position, but this is looks bad when we move to a different row. - # So return False once to stop this. - self._last_tooltip_place = None - return False - self._last_tooltip_place = path_info[:2] - iter_ = treeview.get_model().get_iter(path_info.path) - column = self.gtk_column_to_wrapper[path_info.column] - text = self.get_tooltip(iter_, column) - if text is None: - return False - pygtkhacks.set_tooltip_text(tooltip, text) - return True - - def _update_hover(self, treeview, event): - old_hover_info, old_hover_pos = self.hover_info, self.hover_pos - path_info = treeview.get_position_info(event.x, event.y) - if (path_info and - self.gtk_column_to_wrapper[path_info.column].renderer.want_hover): - self.hover_info = path_info.path, path_info.column - self.hover_pos = path_info.x, path_info.y - else: - self.hover_info = None - self.hover_pos = None - if (old_hover_info != self.hover_info or - old_hover_pos != self.hover_pos): - if (old_hover_info != self.hover_info and - old_hover_info is not None): - self._redraw_cell(treeview, *old_hover_info) - if self.hover_info is not None: - self._redraw_cell(treeview, *self.hover_info) - -class GTKScrollbarOwnerMixin(ScrollbarOwnerMixin): - # XXX this is half a wrapper for TreeViewScrolling. A lot of things will - # become much simpler when we integrate TVS into this - def __init__(self): - ScrollbarOwnerMixin.__init__(self) - # super uses this for postponed scroll_to_iter - # it's a faux-signal from our _widget; this hack is only necessary until - # we integrate TVS - self._widget.scroll_range_changed = (lambda *a: - self.emit('scroll-range-changed')) - - def set_scroller(self, scroller): - """Set the Scroller object for this widget, if its ScrolledWindow is - not a direct ancestor of the object. Standard View needs this. - """ - self._widget.set_scroller(scroller._widget) - - def _set_scroll_position(self, scroll_pos): - self._widget.set_scroll_position(scroll_pos) - - def _get_item_area(self, iter_): - return self._widget.get_path_rect(self.get_path(iter_)) - - @property - def _manually_scrolled(self): - return self._widget.manually_scrolled - - @property - def _position_set(self): - return self._widget.position_set - - def _get_visible_area(self): - """Return the Rect of the visible area, in tree coords. - - get_visible_rect gets this wrong for StandardView, always returning an - origin of (0, 0) - this is because our ScrolledWindow is not our direct - parent. - """ - bars = self._widget._scrollbars - x, y = (int(adj.get_value()) for adj in bars) - width, height = (int(adj.get_page_size()) for adj in bars) - if height == 0: - # this happens even after _widget._coords_working - raise WidgetNotReadyError('visible height') - return Rect(x, y, width, height) - - def _get_scroll_position(self): - """Get the current position of both scrollbars, to restore later.""" - try: - return tuple(int(bar.get_value()) for bar in self._widget._scrollbars) - except WidgetNotReadyError: - return None - -class TableView(Widget, GTKSelectionOwnerMixin, DNDHandlerMixin, - HotspotTrackingMixin, ColumnOwnerMixin, HoverTrackingMixin, - GTKScrollbarOwnerMixin): - """https://develop.participatoryculture.org/index.php/WidgetAPITableView""" - - draws_selection = True - - def __init__(self, model, custom_headers=False): - Widget.__init__(self) - self.set_widget(MiroTreeView()) - self.model = model - self.model.add_to_tableview(self._widget) - self._model = self._widget.get_model() - wrappermap.add(self._model, model) - self._setup_colors() - self.background_color = None - self.context_menu_callback = None - self.in_bulk_change = False - self.delaying_press = False - self._use_custom_headers = False - self.layout_manager = LayoutManager(self._widget) - self.height_changed = None # 17178 hack - self._connect_signals() - # setting up mixins after general TableView init - GTKSelectionOwnerMixin.__init__(self) - DNDHandlerMixin.__init__(self) - HotspotTrackingMixin.__init__(self) - ColumnOwnerMixin.__init__(self) - HoverTrackingMixin.__init__(self) - GTKScrollbarOwnerMixin.__init__(self) - if custom_headers: - self._enable_custom_headers() - - # FIXME: should implement set_model() and make None a special case. - def unset_model(self): - """Disconnect our model from this table view. - - This should be called when you want to destroy a TableView and - there's a new TableView sharing its model. - """ - self._widget.set_model(None) - self.model = None - - def _connect_signals(self): - self.create_signal('row-expanded') - self.create_signal('row-collapsed') - self.create_signal('row-clicked') - self.create_signal('row-activated') - self.wrapped_widget_connect('row-activated', self.on_row_activated) - self.wrapped_widget_connect('row-expanded', self.on_row_expanded) - self.wrapped_widget_connect('row-collapsed', self.on_row_collapsed) - self.wrapped_widget_connect('button-press-event', self.on_button_press) - self.wrapped_widget_connect('button-release-event', - self.on_button_release) - self.wrapped_widget_connect('motion-notify-event', - self.on_motion_notify) - - def set_gradient_highlight(self, gradient): - # This is just an OS X thing. - pass - - def set_background_color(self, color): - self.background_color = self.make_color(color) - self.modify_style('base', gtk.STATE_NORMAL, self.background_color) - if not self.draws_selection: - self.modify_style('base', gtk.STATE_SELECTED, - self.background_color) - self.modify_style('base', gtk.STATE_ACTIVE, self.background_color) - if self.use_custom_style: - self.set_column_background_color() - - def set_group_lines_enabled(self, enabled): - """Enable/Disable group lines. - - This only has an effect if our model is an InfoListModel and it has a - grouping set. - - If group lines are enabled, we will draw a line below the last item in - the group. Use set_group_line_style() to change the look of the line. - """ - self._widget.group_lines_enabled = enabled - self.queue_redraw() - - def set_group_line_style(self, color, width): - self._widget.group_line_color = color - self._widget.group_line_width = width - self.queue_redraw() - - def handle_custom_style_change(self): - if self.background_color is not None: - if self.use_custom_style: - self.set_column_background_color() - else: - for column in self.columns: - column.renderer._renderer.set_property( - 'cell-background-set', False) - - def set_alternate_row_backgrounds(self, setting): - self._widget.set_rules_hint(setting) - - def set_grid_lines(self, horizontal, vertical): - if horizontal and vertical: - setting = gtk.TREE_VIEW_GRID_LINES_BOTH - elif horizontal: - setting = gtk.TREE_VIEW_GRID_LINES_HORIZONTAL - elif vertical: - setting = gtk.TREE_VIEW_GRID_LINES_VERTICAL - else: - setting = gtk.TREE_VIEW_GRID_LINES_NONE - self._widget.set_grid_lines(setting) - - def width_for_columns(self, total_width): - """Given the width allocated for the TableView, return how much of that - is available to column contents. Note that this depends on the number of - columns. - """ - column_spacing = TableColumn.FIXED_PADDING * len(self.columns) - return total_width - column_spacing - - def enable_album_view_focus_hack(self): - _install_album_view_gtkrc() - self._widget.set_name("miro-album-view") - - def focus(self): - self._widget.grab_focus() - - def _enable_custom_headers(self): - # NB: this is currently not used because the GTK tableview does not - # support custom headers. - self._use_custom_headers = True - - def set_show_headers(self, show): - self._widget.set_headers_visible(show) - self._widget.set_headers_clickable(show) - - def _setup_colors(self): - style = self._widget.style - if not self.draws_selection: - # if we don't want to draw selection, make the selected/active - # colors the same as the normal ones - self.modify_style('base', gtk.STATE_SELECTED, - style.base[gtk.STATE_NORMAL]) - self.modify_style('base', gtk.STATE_ACTIVE, - style.base[gtk.STATE_NORMAL]) - - def set_search_column(self, model_index): - self._widget.set_search_column(model_index) - - def set_fixed_height(self, fixed_height): - self._widget.set_fixed_height_mode(fixed_height) - - def set_row_expanded(self, iter_, expanded): - """Expand or collapse the row specified by iter_. Succeeds or raises - WidgetActionError. Causes row-expanded or row-collapsed to be emitted - when successful. - """ - path = self.get_path(iter_) - if expanded: - self._widget.expand_row(path, False) - else: - self._widget.collapse_row(path) - if bool(self._widget.row_expanded(path)) != bool(expanded): - raise WidgetActionError("cannot expand the given item - it " - "probably has no children.") - - def is_row_expanded(self, iter_): - path = self.get_path(iter_) - return self._widget.row_expanded(path) - - def set_context_menu_callback(self, callback): - self.context_menu_callback = callback - - # GTK is really good and it is safe to operate on table even when - # cells may be constantly changing in flux. - def set_volatile(self, volatile): - return - - def on_row_expanded(self, _widget, iter_, path): - self.emit('row-expanded', iter_, path) - - def on_row_collapsed(self, _widget, iter_, path): - self.emit('row-collapsed', iter_, path) - - def on_button_press(self, treeview, event): - """Handle a mouse button press""" - if event.type == gtk.gdk._2BUTTON_PRESS: - # already handled as row-activated - return False - - path_info = treeview.get_position_info(event.x, event.y) - if not path_info: - # no item was clicked, so it's not going to be a hotspot, drag, or - # context menu - return False - if event.type == gtk.gdk.BUTTON_PRESS: - # single click; emit the event but keep on running so we can handle - # stuff like drag and drop. - if not self._x_coord_in_expander(treeview, path_info): - iter_ = treeview.get_model().get_iter(path_info.path) - self.emit('row-clicked', iter_) - - if (event.button == 1 and self.handle_hotspot_hit(treeview, event)): - return True - if event.window != treeview.get_bin_window(): - # click is outside the content area, don't try to handle this. - # In particular, our DnD code messes up resizing table columns. - return False - if (event.button == 1 and self.drag_source and - not self._x_coord_in_expander(treeview, path_info)): - return self.start_drag(treeview, event, path_info) - elif event.button == 3 and self.context_menu_callback: - self.show_context_menu(treeview, event, path_info) - return True - - # FALLTHROUGH - return False - - def show_context_menu(self, treeview, event, path_info): - """Pop up a context menu for the given click event (which is a - right-click on a row). - """ - # hack for album view - if (treeview.group_lines_enabled and - path_info.column == treeview.get_columns()[0]): - self._select_all_rows_in_group(treeview, path_info.path) - self._popup_context_menu(path_info.path, event) - # grab keyboard focus since we handled the event - self.focus() - - def _select_all_rows_in_group(self, treeview, path): - """Select all items in the group """ - - # FIXME: this is very tightly coupled with the portable code. - - infolist = self.model - gtk_model = treeview.get_model() - if (not isinstance(infolist, InfoListModel) or - infolist.get_grouping() is None): - return - it = gtk_model.get_iter(path) - info, attrs, group_info = infolist.row_for_iter(it) - start_row = path[0] - group_info[0] - total_rows = group_info[1] - - with self._ignoring_changes(): - self.unselect_all() - for row in xrange(start_row, start_row + total_rows): - self.select_path((row,)) - self.emit('selection-changed') - - def _popup_context_menu(self, path, event): - if not self.selection.path_is_selected(path): - self.unselect_all(signal=False) - self.select_path(path) - menu = self.make_context_menu() - if menu: - menu.popup(None, None, None, event.button, event.time) - return menu - else: - return None - - # XXX treeview.get_cell_area handles what we're trying to use this for - def _x_coord_in_expander(self, treeview, path_info): - """Calculate if an x coordinate is over the expander triangle - - :param treeview: Gtk.TreeView - :param path_info: PathInfo( - tree path for the cell, - Gtk.TreeColumn, - x coordinate relative to column's cell area, - y coordinate relative to column's cell area (ignored), - ) - """ - if path_info.column != treeview.get_expander_column(): - return False - model = treeview.get_model() - if not model.iter_has_child(model.get_iter(path_info.path)): - return False - # GTK allocateds an extra 4px to the right of the expanders. This - # seems to be hardcoded as EXPANDER_EXTRA_PADDING in the source code. - total_exander_size = treeview.expander_size + 4 - # include horizontal_separator - # XXX: should this value be included in total_exander_size ? - offset = treeview.horizontal_separator / 2 - # allocate space for expanders for parent nodes - expander_start = total_exander_size * (len(path_info.path) - 1) + offset - expander_end = expander_start + total_exander_size + offset - return expander_start <= path_info.x < expander_end - - def on_row_activated(self, treeview, path, view_column): - iter_ = treeview.get_model().get_iter(path) - self.emit('row-activated', iter_) - - def make_context_menu(self): - def gen_menu(menu_items): - menu = gtk.Menu() - for menu_item_info in menu_items: - if menu_item_info is None: - item = gtk.SeparatorMenuItem() - else: - label, callback = menu_item_info - - if isinstance(label, tuple) and len(label) == 2: - text_label, icon_path = label - pixbuf = gtk.gdk.pixbuf_new_from_file(icon_path) - image = gtk.Image() - image.set_from_pixbuf(pixbuf) - item = gtk.ImageMenuItem(text_label) - item.set_image(image) - else: - item = gtk.MenuItem(label) - - if callback is None: - item.set_sensitive(False) - elif isinstance(callback, list): - item.set_submenu(gen_menu(callback)) - else: - item.connect('activate', self.on_context_menu_activate, - callback) - menu.append(item) - item.show() - return menu - - items = self.context_menu_callback(self) - if items: - return gen_menu(items) - else: - return None - - def on_context_menu_activate(self, item, callback): - callback() - - def on_button_release(self, treeview, event): - if self.release_on_hotspot(event): - return True - if event.button == 1: - self.drag_button_down = False - - if self.delaying_press: - # if dragging did not happen, unselect other rows and - # select current row - path_info = treeview.get_position_info(event.x, event.y) - if path_info is not None: - self.unselect_all(signal=False) - self.select_path(path_info.path) - self.delaying_press = False - - def _redraw_cell(self, treeview, path, column): - cell_area = treeview.get_cell_area(path, column) - x, y = treeview.convert_bin_window_to_widget_coords(cell_area.x, - cell_area.y) - treeview.queue_draw_area(x, y, cell_area.width, cell_area.height) - - def on_motion_notify(self, treeview, event): - self._update_hover(treeview, event) - - if self.hotspot_tracker: - self.hotspot_tracker.update_position(event) - self.hotspot_tracker.update_hit() - return True - - self.potential_drag_motion(treeview, event) - return None # XXX: used to fall through; not sure what retval does here - - def start_bulk_change(self): - self._widget.freeze_child_notify() - self._widget.set_model(None) - self._disconnect_hotspot_signals() - self.in_bulk_change = True - - def model_changed(self): - if self.in_bulk_change: - self._widget.set_model(self._model) - self._widget.thaw_child_notify() - self.hotspot_model_changed() - self.in_bulk_change = False - - def get_path(self, iter_): - """Always use this rather than the model's get_path directly - - if the iter isn't valid, a GTK assertion causes us to exit - without warning; this wrapper changes that to a much more useful - AssertionError. Example related bug: #17362. - """ - assert self.model.iter_is_valid(iter_) - return self._model.get_path(iter_) - -class TableModel(object): - """https://develop.participatoryculture.org/index.php/WidgetAPITableView""" - MODEL_CLASS = gtk.ListStore - - def __init__(self, *column_types): - self._model = self.MODEL_CLASS(*self.map_types(column_types)) - self._column_types = column_types - if 'image' in self._column_types: - self.convert_row_for_gtk = self.convert_row_for_gtk_slow - self.convert_value_for_gtk = self.convert_value_for_gtk_slow - else: - self.convert_row_for_gtk = self.convert_row_for_gtk_fast - self.convert_value_for_gtk = self.convert_value_for_gtk_fast - - def add_to_tableview(self, widget): - widget.set_model(self._model) - - def map_types(self, miro_column_types): - type_map = { - 'boolean': bool, - 'numeric': float, - 'integer': int, - 'text': str, - 'image': gtk.gdk.Pixbuf, - 'datetime': object, - 'object': object, - } - try: - return [type_map[type] for type in miro_column_types] - except KeyError, e: - raise ValueError("Unknown column type: %s" % e[0]) - - # If we store image data, we need to do some work to convert row data to - # send to GTK - def convert_value_for_gtk_slow(self, column_value): - if isinstance(column_value, Image): - return column_value.pixbuf - else: - return column_value - - def convert_row_for_gtk_slow(self, column_values): - return tuple(self.convert_value_for_gtk(c) for c in column_values) - - def check_new_column(self, column): - for value in column.attrs.values(): - if not isinstance(value, int): - msg = "Attribute values must be integers, not %r" % value - raise TypeError(msg) - if value < 0 or value >= len(self._column_types): - raise ValueError("Attribute index out of range: %s" % value) - - # If we don't store image data, we can don't need to do any work to - # convert row data to gtk - def convert_value_for_gtk_fast(self, value): - return value - - def convert_row_for_gtk_fast(self, column_values): - return column_values - - def append(self, *column_values): - return self._model.append(self.convert_row_for_gtk(column_values)) - - def update_value(self, iter_, index, value): - assert self._model.iter_is_valid(iter_) - self._model.set(iter_, index, self.convert_value_for_gtk(value)) - - def update(self, iter_, *column_values): - self._model[iter_] = self.convert_value_for_gtk(column_values) - - def remove(self, iter_): - if self._model.remove(iter_): - return iter_ - else: - return None - - def insert_before(self, iter_, *column_values): - row = self.convert_row_for_gtk(column_values) - return self._model.insert_before(iter_, row) - - def first_iter(self): - return self._model.get_iter_first() - - def next_iter(self, iter_): - return self._model.iter_next(iter_) - - def nth_iter(self, index): - assert index >= 0 - return self._model.iter_nth_child(None, index) - - def __iter__(self): - return iter(self._model) - - def __len__(self): - return len(self._model) - - def __getitem__(self, iter_): - return self._model[iter_] - - def get_rows(self, row_paths): - return [self._model[path] for path in row_paths] - - def get_path(self, iter_): - return self._model.get_path(iter_) - - def iter_is_valid(self, iter_): - return self._model.iter_is_valid(iter_) - -class TreeTableModel(TableModel): - """https://develop.participatoryculture.org/index.php/WidgetAPITableView""" - MODEL_CLASS = gtk.TreeStore - - def append(self, *column_values): - return self._model.append(None, self.convert_row_for_gtk( - column_values)) - - def insert_before(self, iter_, *column_values): - parent = self.parent_iter(iter_) - row = self.convert_row_for_gtk(column_values) - return self._model.insert_before(parent, iter_, row) - - def append_child(self, iter_, *column_values): - return self._model.append(iter_, self.convert_row_for_gtk( - column_values)) - - def child_iter(self, iter_): - return self._model.iter_children(iter_) - - def nth_child_iter(self, iter_, index): - assert index >= 0 - return self._model.iter_nth_child(iter_, index) - - def has_child(self, iter_): - return self._model.iter_has_child(iter_) - - def children_count(self, iter_): - return self._model.iter_n_children(iter_) - - def parent_iter(self, iter_): - assert self._model.iter_is_valid(iter_) - return self._model.iter_parent(iter_) |