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)