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:
parent
5626a6c182
commit
49365e132c
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user