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
This commit is contained in:
Gonzalo Odiard 2014-11-28 08:45:22 -03:00
parent 5626a6c182
commit 49365e132c
6 changed files with 179 additions and 86 deletions

View File

@ -21,7 +21,7 @@ treeview.show()
col = Gtk.TreeViewColumn() col = Gtk.TreeViewColumn()
treeview.append_column(col) treeview.append_column(col)
cell_icon = CellRendererIcon(treeview) cell_icon = CellRendererIcon()
cell_icon.props.width = style.GRID_CELL_SIZE cell_icon.props.width = style.GRID_CELL_SIZE
cell_icon.props.height = style.GRID_CELL_SIZE cell_icon.props.height = style.GRID_CELL_SIZE
cell_icon.props.size = style.SMALL_ICON_SIZE cell_icon.props.size = style.SMALL_ICON_SIZE

View File

@ -7,9 +7,18 @@ from sugar3.graphics import style
from sugar3.graphics.icon import CellRendererIcon from sugar3.graphics.icon import CellRendererIcon
from sugar3.graphics.xocolor import XoColor from sugar3.graphics.xocolor import XoColor
from sugar3.graphics.scrollingdetector import ScrollingDetector from sugar3.graphics.scrollingdetector import ScrollingDetector
from sugar3.graphics.palettewindow import TreeViewInvoker
import common 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 = common.Test()
test.show() test.show()
@ -38,7 +47,7 @@ col = Gtk.TreeViewColumn()
treeview.append_column(col) treeview.append_column(col)
xo_color = XoColor('#FF0000,#00FF00') xo_color = XoColor('#FF0000,#00FF00')
cell_icon = CellRendererIcon(treeview) cell_icon = CellRendererIcon()
cell_icon.props.width = style.GRID_CELL_SIZE cell_icon.props.width = style.GRID_CELL_SIZE
cell_icon.props.height = style.GRID_CELL_SIZE cell_icon.props.height = style.GRID_CELL_SIZE
cell_icon.props.size = style.STANDARD_ICON_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.pack_start(cell_text, expand=True)
col.add_attribute(cell_text, 'text', 0) col.add_attribute(cell_text, 'text', 0)
invoker = TreeViewInvoker()
invoker.attach_treeview(treeview)
detector = ScrollingDetector(scrolled) 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__': if __name__ == '__main__':
time_ini = time.time() time_ini = time.time()

View File

@ -825,9 +825,8 @@ class CellRendererIcon(Gtk.CellRenderer):
'clicked': (GObject.SignalFlags.RUN_FIRST, None, [object]), 'clicked': (GObject.SignalFlags.RUN_FIRST, None, [object]),
} }
def __init__(self, tree_view): def __init__(self, treeview=None):
from sugar3.graphics.palette import CellRendererInvoker # treeview is not used anymore, is here just to not break the API
self._buffer = _IconBuffer() self._buffer = _IconBuffer()
self._buffer.cache = True self._buffer.cache = True
self._xo_color = None self._xo_color = None
@ -836,19 +835,10 @@ class CellRendererIcon(Gtk.CellRenderer):
self._prelit_fill_color = None self._prelit_fill_color = None
self._prelit_stroke_color = None self._prelit_stroke_color = None
self._active_state = False self._active_state = False
self._palette_invoker = CellRendererInvoker()
self._cached_offsets = None self._cached_offsets = None
Gtk.CellRenderer.__init__(self) 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 self._is_scrolling = False
def connect_to_scroller(self, scrolled): def connect_to_scroller(self, scrolled):
@ -856,34 +846,17 @@ class CellRendererIcon(Gtk.CellRenderer):
scrolled.connect('scroll-end', self._scroll_end_cb) scrolled.connect('scroll-end', self._scroll_end_cb)
def _scroll_start_cb(self, event): def _scroll_start_cb(self, event):
self._palette_invoker.detach()
self._is_scrolling = True self._is_scrolling = True
def _scroll_end_cb(self, event): def _scroll_end_cb(self, event):
self._palette_invoker.attach_cell_renderer(self._tree_view, self)
self._is_scrolling = False self._is_scrolling = False
def is_scrolling(self): def is_scrolling(self):
return self._is_scrolling 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): def create_palette(self):
return None 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): def set_file_name(self, value):
if self._buffer.file_name != value: if self._buffer.file_name != value:
self._buffer.file_name = value self._buffer.file_name = value
@ -982,31 +955,6 @@ class CellRendererIcon(Gtk.CellRenderer):
flags): flags):
pass 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): def do_render(self, cr, widget, background_area, cell_area, flags):
if not self._is_scrolling: if not self._is_scrolling:

View File

@ -38,13 +38,14 @@ from sugar3.graphics.palettewindow import PaletteWindow, \
from sugar3.graphics.palettemenu import PaletteMenuItem from sugar3.graphics.palettemenu import PaletteMenuItem
from sugar3.graphics.palettewindow import MouseSpeedDetector, Invoker, \ from sugar3.graphics.palettewindow import MouseSpeedDetector, Invoker, \
WidgetInvoker, CursorInvoker, ToolInvoker, CellRendererInvoker WidgetInvoker, CursorInvoker, ToolInvoker, TreeViewInvoker
assert MouseSpeedDetector assert MouseSpeedDetector
assert Invoker assert Invoker
assert WidgetInvoker assert WidgetInvoker
assert CursorInvoker assert CursorInvoker
assert ToolInvoker assert ToolInvoker
assert CellRendererInvoker assert TreeViewInvoker
class _HeaderItem(Gtk.MenuItem): class _HeaderItem(Gtk.MenuItem):
@ -418,6 +419,7 @@ class Palette(PaletteWindow):
if self._palette_state == self.PRIMARY: if self._palette_state == self.PRIMARY:
self._secondary_box.show() self._secondary_box.show()
if self._widget is not None:
self._full_request = self._widget.size_request() self._full_request = self._widget.size_request()
if self._palette_state == self.PRIMARY: if self._palette_state == self.PRIMARY:

View File

@ -35,6 +35,7 @@ from gi.repository import SugarGestures
from sugar3.graphics import palettegroup from sugar3.graphics import palettegroup
from sugar3.graphics import animator from sugar3.graphics import animator
from sugar3.graphics import style from sugar3.graphics import style
from sugar3.graphics.icon import CellRendererIcon
def _calculate_gap(a, b): def _calculate_gap(a, b):
@ -223,9 +224,6 @@ class _PaletteMenuWidget(Gtk.Menu):
x = event.x_root x = event.x_root
y = event.y_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() rect = self._invoker.get_rect()
in_invoker = x >= rect.x and x < (rect.x + rect.width) \ in_invoker = x >= rect.x and x < (rect.x + rect.width) \
and y >= rect.y and y < (rect.y + rect.height) and y >= rect.y and y < (rect.y + rect.height)
@ -238,9 +236,6 @@ class _PaletteMenuWidget(Gtk.Menu):
x = event.x_root x = event.x_root
y = event.y_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() rect = self._invoker.get_rect()
in_invoker = x >= rect.x and x < (rect.x + rect.width) \ in_invoker = x >= rect.x and x < (rect.x + rect.width) \
and y >= rect.y and y < (rect.y + rect.height) and y >= rect.y and y < (rect.y + rect.height)
@ -399,6 +394,7 @@ class _PaletteWindowWidget(Gtk.Window):
def popup(self, invoker): def popup(self, invoker):
if self.get_visible(): if self.get_visible():
logging.error('PaletteWindowWidget popup get_visible True')
return return
self.connect('enter-notify-event', self.__enter_notify_event_cb) self.connect('enter-notify-event', self.__enter_notify_event_cb)
self.connect('leave-notify-event', self.__leave_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.') logging.error('Cannot update the palette position.')
return return
if self._widget is None:
return
req = self._widget.size_request() req = self._widget.size_request()
# on Gtk 3.10, menu at the bottom of the screen are resized # on Gtk 3.10, menu at the bottom of the screen are resized
# to not fall out, and report a wrong size. # to not fall out, and report a wrong size.
@ -642,6 +641,8 @@ class PaletteWindow(GObject.GObject):
return self._widget.size_request() return self._widget.size_request()
def popup(self, immediate=False): def popup(self, immediate=False):
if self._widget is None:
return
if self._invoker is not None: if self._invoker is not None:
full_size_request = self.get_full_size_request() full_size_request = self.get_full_size_request()
self._alignment = self._invoker.get_alignment(full_size_request) self._alignment = self._invoker.get_alignment(full_size_request)
@ -1529,3 +1530,143 @@ class CellRendererInvoker(Invoker):
def get_default_position(self): def get_default_position(self):
return self.AT_CURSOR 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

View File

@ -18,11 +18,12 @@
from gi.repository import GObject from gi.repository import GObject
from gi.repository import GLib from gi.repository import GLib
from sugar3.graphics.icon import CellRendererIcon
class ScrollingDetector(GObject.GObject): 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') scroll_start_signal = GObject.Signal('scroll-start')
@ -58,15 +59,3 @@ class ScrollingDetector(GObject.GObject):
self.scroll_start_signal.emit() self.scroll_start_signal.emit()
self._prev_value = adj.props.value self._prev_value = adj.props.value
GLib.timeout_add(self._timeout, self._check_scroll_cb, adj) 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)