From 49365e132c3a35b51c1700b3be5d2364d0200d98 Mon Sep 17 00:00:00 2001 From: Gonzalo Odiard Date: Fri, 28 Nov 2014 08:45:22 -0300 Subject: [PATCH] New invoker, TreeViewInvoker This new invoker is able to handle all the palettes on a treeview, removing the need of one invoker per cell renderer (CellRendererInvoker). This simplifies the code and makes it more efficient. Also removes the logic from the CellRendererIcon, which should only care about drawing itself. [1] Is important to note than in Gtk3 a CellRenderer is not a GtkWidget then can't know when the mouse is over it or have the usual events used by other invokers, making the implementation CellRendererInvoker very complicate. This commit also removes the invoker of CellRendererIcon. The ScrollingDetector logic is simplified too, because now there are only one invoker instead of one by renderer and the example code updated. [1] https://developer.gnome.org/gtk3/stable/GtkCellRenderer.html --- examples/cellrenderericon.py | 2 +- examples/scrollingdetector.py | 17 ++- src/sugar3/graphics/icon.py | 56 +------- src/sugar3/graphics/palette.py | 8 +- src/sugar3/graphics/palettewindow.py | 165 +++++++++++++++++++++-- src/sugar3/graphics/scrollingdetector.py | 17 +-- 6 files changed, 179 insertions(+), 86 deletions(-) diff --git a/examples/cellrenderericon.py b/examples/cellrenderericon.py index 1f875520..e2802045 100644 --- a/examples/cellrenderericon.py +++ b/examples/cellrenderericon.py @@ -21,7 +21,7 @@ treeview.show() col = Gtk.TreeViewColumn() treeview.append_column(col) -cell_icon = CellRendererIcon(treeview) +cell_icon = CellRendererIcon() cell_icon.props.width = style.GRID_CELL_SIZE cell_icon.props.height = style.GRID_CELL_SIZE cell_icon.props.size = style.SMALL_ICON_SIZE diff --git a/examples/scrollingdetector.py b/examples/scrollingdetector.py index f6845223..8f7243a0 100644 --- a/examples/scrollingdetector.py +++ b/examples/scrollingdetector.py @@ -7,9 +7,18 @@ from sugar3.graphics import style from sugar3.graphics.icon import CellRendererIcon from sugar3.graphics.xocolor import XoColor from sugar3.graphics.scrollingdetector import ScrollingDetector +from sugar3.graphics.palettewindow import TreeViewInvoker import common +def _scroll_start_cb(event, treeview, invoker): + print "Scroll starts" + invoker.detach() + +def _scroll_end_cb(event, treeview, invoker): + print "Scroll ends" + invoker.attach_treeview(treeview) + test = common.Test() test.show() @@ -38,7 +47,7 @@ col = Gtk.TreeViewColumn() treeview.append_column(col) xo_color = XoColor('#FF0000,#00FF00') -cell_icon = CellRendererIcon(treeview) +cell_icon = CellRendererIcon() cell_icon.props.width = style.GRID_CELL_SIZE cell_icon.props.height = style.GRID_CELL_SIZE cell_icon.props.size = style.STANDARD_ICON_SIZE @@ -50,8 +59,12 @@ cell_text = Gtk.CellRendererText() col.pack_start(cell_text, expand=True) col.add_attribute(cell_text, 'text', 0) +invoker = TreeViewInvoker() +invoker.attach_treeview(treeview) + detector = ScrollingDetector(scrolled) -detector.connect_treeview(treeview) +detector.connect('scroll-start', _scroll_start_cb, treeview, invoker) +detector.connect('scroll-end', _scroll_end_cb, treeview, invoker) if __name__ == '__main__': time_ini = time.time() diff --git a/src/sugar3/graphics/icon.py b/src/sugar3/graphics/icon.py index 329b4b15..69e481a6 100644 --- a/src/sugar3/graphics/icon.py +++ b/src/sugar3/graphics/icon.py @@ -825,9 +825,8 @@ class CellRendererIcon(Gtk.CellRenderer): 'clicked': (GObject.SignalFlags.RUN_FIRST, None, [object]), } - def __init__(self, tree_view): - from sugar3.graphics.palette import CellRendererInvoker - + def __init__(self, treeview=None): + # treeview is not used anymore, is here just to not break the API self._buffer = _IconBuffer() self._buffer.cache = True self._xo_color = None @@ -836,19 +835,10 @@ class CellRendererIcon(Gtk.CellRenderer): self._prelit_fill_color = None self._prelit_stroke_color = None self._active_state = False - self._palette_invoker = CellRendererInvoker() self._cached_offsets = None Gtk.CellRenderer.__init__(self) - tree_view.connect('button-press-event', - self.__button_press_event_cb) - tree_view.connect('button-release-event', - self.__button_release_event_cb) - - self._palette_invoker.attach_cell_renderer(tree_view, self) - - self._tree_view = tree_view self._is_scrolling = False def connect_to_scroller(self, scrolled): @@ -856,34 +846,17 @@ class CellRendererIcon(Gtk.CellRenderer): scrolled.connect('scroll-end', self._scroll_end_cb) def _scroll_start_cb(self, event): - self._palette_invoker.detach() self._is_scrolling = True def _scroll_end_cb(self, event): - self._palette_invoker.attach_cell_renderer(self._tree_view, self) self._is_scrolling = False def is_scrolling(self): return self._is_scrolling - def __del__(self): - self._palette_invoker.detach() - - def __button_press_event_cb(self, widget, event): - if self._point_in_cell_renderer(widget, event.x, event.y): - self._active_state = True - - def __button_release_event_cb(self, widget, event): - self._active_state = False - def create_palette(self): return None - def get_palette_invoker(self): - return self._palette_invoker - - palette_invoker = GObject.property(type=object, getter=get_palette_invoker) - def set_file_name(self, value): if self._buffer.file_name != value: self._buffer.file_name = value @@ -982,31 +955,6 @@ class CellRendererIcon(Gtk.CellRenderer): flags): pass - def _point_in_cell_renderer(self, tree_view, x=None, y=None): - """Check if the point with coordinates x, y is inside this icon. - - If the x, y coordinates are not given, they are taken from the - pointer current position. - - """ - if x is None and y is None: - x, y = tree_view.get_pointer() - x, y = tree_view.convert_widget_to_bin_window_coords(x, y) - pos = tree_view.get_path_at_pos(int(x), int(y)) - if pos is None: - return False - - path_, column, x, y_ = pos - - for cell_renderer in column.get_cells(): - if cell_renderer == self: - cell_x, cell_width = column.cell_get_position(cell_renderer) - if x > cell_x and x < (cell_x + cell_width): - return True - return False - - return False - def do_render(self, cr, widget, background_area, cell_area, flags): if not self._is_scrolling: diff --git a/src/sugar3/graphics/palette.py b/src/sugar3/graphics/palette.py index e50ac392..c7092c23 100644 --- a/src/sugar3/graphics/palette.py +++ b/src/sugar3/graphics/palette.py @@ -38,13 +38,14 @@ from sugar3.graphics.palettewindow import PaletteWindow, \ from sugar3.graphics.palettemenu import PaletteMenuItem from sugar3.graphics.palettewindow import MouseSpeedDetector, Invoker, \ - WidgetInvoker, CursorInvoker, ToolInvoker, CellRendererInvoker + WidgetInvoker, CursorInvoker, ToolInvoker, TreeViewInvoker + assert MouseSpeedDetector assert Invoker assert WidgetInvoker assert CursorInvoker assert ToolInvoker -assert CellRendererInvoker +assert TreeViewInvoker class _HeaderItem(Gtk.MenuItem): @@ -418,7 +419,8 @@ class Palette(PaletteWindow): if self._palette_state == self.PRIMARY: self._secondary_box.show() - self._full_request = self._widget.size_request() + if self._widget is not None: + self._full_request = self._widget.size_request() if self._palette_state == self.PRIMARY: self._secondary_box.hide() diff --git a/src/sugar3/graphics/palettewindow.py b/src/sugar3/graphics/palettewindow.py index 1b43b65c..a19f2f3a 100644 --- a/src/sugar3/graphics/palettewindow.py +++ b/src/sugar3/graphics/palettewindow.py @@ -35,6 +35,7 @@ from gi.repository import SugarGestures from sugar3.graphics import palettegroup from sugar3.graphics import animator from sugar3.graphics import style +from sugar3.graphics.icon import CellRendererIcon def _calculate_gap(a, b): @@ -223,12 +224,9 @@ class _PaletteMenuWidget(Gtk.Menu): x = event.x_root y = event.y_root - if type(self._invoker) is CellRendererInvoker: - in_invoker = self._invoker.point_in_cell_renderer(x, y) - else: - rect = self._invoker.get_rect() - in_invoker = x >= rect.x and x < (rect.x + rect.width) \ - and y >= rect.y and y < (rect.y + rect.height) + rect = self._invoker.get_rect() + in_invoker = x >= rect.x and x < (rect.x + rect.width) \ + and y >= rect.y and y < (rect.y + rect.height) if in_invoker != self._mouse_in_invoker: self._mouse_in_invoker = in_invoker @@ -238,12 +236,9 @@ class _PaletteMenuWidget(Gtk.Menu): x = event.x_root y = event.y_root - if type(self._invoker) is CellRendererInvoker: - in_invoker = self._invoker.point_in_cell_renderer(x, y) - else: - rect = self._invoker.get_rect() - in_invoker = x >= rect.x and x < (rect.x + rect.width) \ - and y >= rect.y and y < (rect.y + rect.height) + rect = self._invoker.get_rect() + in_invoker = x >= rect.x and x < (rect.x + rect.width) \ + and y >= rect.y and y < (rect.y + rect.height) if in_invoker: return True @@ -399,6 +394,7 @@ class _PaletteWindowWidget(Gtk.Window): def popup(self, invoker): if self.get_visible(): + logging.error('PaletteWindowWidget popup get_visible True') return self.connect('enter-notify-event', self.__enter_notify_event_cb) self.connect('leave-notify-event', self.__leave_notify_event_cb) @@ -618,6 +614,9 @@ class PaletteWindow(GObject.GObject): logging.error('Cannot update the palette position.') return + if self._widget is None: + return + req = self._widget.size_request() # on Gtk 3.10, menu at the bottom of the screen are resized # to not fall out, and report a wrong size. @@ -642,6 +641,8 @@ class PaletteWindow(GObject.GObject): return self._widget.size_request() def popup(self, immediate=False): + if self._widget is None: + return if self._invoker is not None: full_size_request = self.get_full_size_request() self._alignment = self._invoker.get_alignment(full_size_request) @@ -1529,3 +1530,143 @@ class CellRendererInvoker(Invoker): def get_default_position(self): return self.AT_CURSOR + + +class TreeViewInvoker(Invoker): + def __init__(self): + Invoker.__init__(self) + + self._tree_view = None + self._motion_hid = None + self._leave_hid = None + self._release_hid = None + self._long_pressed_hid = None + self._position_hint = self.AT_CURSOR + + self._long_pressed_controller = SugarGestures.LongPressController() + + self._mouse_detector = MouseSpeedDetector(200, 5) + + self._tree_view = None + self._path = None + self._column = None + + self.palette = None + + def attach_treeview(self, tree_view): + self._tree_view = tree_view + + self._motion_hid = tree_view.connect('motion-notify-event', + self.__motion_notify_event_cb) + self._enter_hid = tree_view.connect('enter-notify-event', + self.__enter_notify_event_cb) + self._leave_hid = tree_view.connect('leave-notify-event', + self.__leave_notify_event_cb) + self._release_hid = tree_view.connect('button-release-event', + self.__button_release_event_cb) + self._long_pressed_hid = self._long_pressed_controller.connect( + 'pressed', self.__long_pressed_event_cb, tree_view) + self._long_pressed_controller.attach( + tree_view, + SugarGestures.EventControllerFlags.NONE) + + self._mouse_detector.connect('motion-slow', self.__mouse_slow_cb) + self._mouse_detector.parent = tree_view + Invoker.attach(self, tree_view) + + def detach(self): + Invoker.detach(self) + self._tree_view.disconnect(self._motion_hid) + self._tree_view.disconnect(self._enter_hid) + self._tree_view.disconnect(self._leave_hid) + self._tree_view.disconnect(self._release_hid) + self._long_pressed_controller.detach(self._tree_view) + self._long_pressed_controller.disconnect(self._long_pressed_hid) + self._mouse_detector.disconnect_by_func(self.__mouse_slow_cb) + + def get_rect(self): + return self._tree_view.get_background_area(self._path, self._column) + + def get_toplevel(self): + return self._tree_view.get_toplevel() + + def __motion_notify_event_cb(self, widget, event): + try: + path, column, x_, y_ = self._tree_view.get_path_at_pos( + int(event.x), int(event.y)) + if path != self._path or column != self._column: + self._redraw_cell(self._path, self._column) + self._redraw_cell(path, column) + + self._path = path + self._column = column + + if self.palette is not None: + self.palette.popdown(immediate=True) + self.palette = None + + self._mouse_detector.start() + except TypeError: + # tree_view.get_path_at_pos() fail if x,y poition is over + # a empty area + pass + + def _redraw_cell(self, path, column): + area = self._tree_view.get_background_area(path, column) + x, y = \ + self._tree_view.convert_bin_window_to_widget_coords(area.x, area.y) + self._tree_view.queue_draw_area(x, y, area.width, area.height) + + def __enter_notify_event_cb(self, widget, event): + self._mouse_detector.start() + + def __leave_notify_event_cb(self, widget, event): + self._mouse_detector.stop() + + def __button_release_event_cb(self, widget, event): + x, y = int(event.x), int(event.y) + path, column, cell_x, cell_y = self._tree_view.get_path_at_pos(x, y) + self._path = path + self._column = column + if event.button == 1: + # left mouse button + if self.palette is not None: + self.palette.popdown(immediate=True) + # NOTE: we don't use columns with more than one cell + cellrenderer = column.get_cells()[0] + if cellrenderer is not None and \ + isinstance(cellrenderer, CellRendererIcon): + cellrenderer.emit('clicked', path) + # So the treeview receives it and knows a drag isn't going on + return False + if event.button == 3: + # right mouse button + self._mouse_detector.stop() + self._change_palette() + self.notify_right_click() + return True + else: + return False + + def __long_pressed_event_cb(self, controller, x, y, widget): + path, column, x_, y_ = self._tree_view.get_path_at_pos(x, y) + self._path = path + self._column = column + self._change_palette() + self.notify_right_click() + + def __mouse_slow_cb(self, widget): + self._mouse_detector.stop() + self._change_palette() + self.emit('mouse-enter') + + def _change_palette(self): + if hasattr(self._tree_view, 'create_palette'): + self.palette = self._tree_view.create_palette( + self._path, self._column) + else: + self.palette = None + + def notify_popdown(self): + Invoker.notify_popdown(self) + self.palette = None diff --git a/src/sugar3/graphics/scrollingdetector.py b/src/sugar3/graphics/scrollingdetector.py index b44d35f8..e56dda75 100644 --- a/src/sugar3/graphics/scrollingdetector.py +++ b/src/sugar3/graphics/scrollingdetector.py @@ -18,11 +18,12 @@ from gi.repository import GObject from gi.repository import GLib -from sugar3.graphics.icon import CellRendererIcon - class ScrollingDetector(GObject.GObject): """ + ScollingDetector emit signals when a ScrolledWindow starts and + finish scrolling. Other widets can use that information to + avoid do performance expensive operations. """ scroll_start_signal = GObject.Signal('scroll-start') @@ -58,15 +59,3 @@ class ScrollingDetector(GObject.GObject): self.scroll_start_signal.emit() self._prev_value = adj.props.value GLib.timeout_add(self._timeout, self._check_scroll_cb, adj) - - def connect_treeview(self, treeview): - """ - treeview -- Gtk.TreeView: a TreeView with CellRendererIcons already - added. - This method need be executed after all the columns and renderers - were added to the treeview. - """ - for column in treeview.get_columns(): - for cell_renderer in column.get_cells(): - if isinstance(cell_renderer, CellRendererIcon): - cell_renderer.connect_to_scroller(self)