Merge branch 'treeview_perf2-try2' of https://github.com/godiard/sugar-toolkit-gtk3 into godiard-treeview_perf2-try2

This commit is contained in:
Gonzalo Odiard
2015-05-15 18:12:58 -03:00
7 changed files with 292 additions and 196 deletions
+1
View File
@@ -18,6 +18,7 @@ sugar_PYTHON = \
panel.py \
radiopalette.py \
radiotoolbutton.py \
scrollingdetector.py \
style.py \
toggletoolbutton.py \
toolbarbox.py \
+67 -88
View File
@@ -825,9 +825,11 @@ 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
if treeview is not None:
logging.warning('CellRendererIcon: treeview parameter in '
'constructor is deprecated')
self._buffer = _IconBuffer()
self._buffer.cache = True
self._xo_color = None
@@ -836,36 +838,28 @@ 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._is_scrolling = False
self._palette_invoker.attach_cell_renderer(tree_view, self)
def connect_to_scroller(self, scrolled):
scrolled.connect('scroll-start', self._scroll_start_cb)
scrolled.connect('scroll-end', self._scroll_end_cb)
def __del__(self):
self._palette_invoker.detach()
def _scroll_start_cb(self, event):
self._is_scrolling = True
def __button_press_event_cb(self, widget, event):
if self._point_in_cell_renderer(widget, event.x, event.y):
self._active_state = True
def _scroll_end_cb(self, event):
self._is_scrolling = False
def __button_release_event_cb(self, widget, event):
self._active_state = False
def is_scrolling(self):
return self._is_scrolling
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
@@ -964,81 +958,66 @@ 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):
context = widget.get_style_context()
context.save()
context.add_class("sugar-icon-cell")
if not self._is_scrolling:
def is_pointer_inside():
# widget is the treeview
x, y = widget.get_pointer()
x, y = widget.convert_widget_to_bin_window_coords(x, y)
return ((cell_area.x <= x <= cell_area.x + cell_area.width)
and (cell_area.y <= y <= cell_area.y + cell_area.height))
context = widget.get_style_context()
context.save()
context.add_class("sugar-icon-cell")
pointer_inside = is_pointer_inside()
def is_pointer_inside():
# widget is the treeview
x, y = widget.get_pointer()
x, y = widget.convert_widget_to_bin_window_coords(x, y)
return ((cell_area.x <= x <= cell_area.x + cell_area.width)
and
(cell_area.y <= y <= cell_area.y + cell_area.height))
# The context will have prelight state if the mouse pointer is
# in the entire row, but we want that state if the pointer is
# in this cell only:
if flags & Gtk.CellRendererState.PRELIT:
if pointer_inside:
if self._active_state:
context.set_state(Gtk.StateFlags.ACTIVE)
pointer_inside = is_pointer_inside()
# The context will have prelight state if the mouse pointer is
# in the entire row, but we want that state if the pointer is
# in this cell only:
if flags & Gtk.CellRendererState.PRELIT:
if pointer_inside:
if self._active_state:
context.set_state(Gtk.StateFlags.ACTIVE)
else:
context.set_state(Gtk.StateFlags.NORMAL)
Gtk.render_background(
context, cr, background_area.x, background_area.y,
background_area.width, background_area.height)
if self._xo_color is not None:
stroke_color = self._xo_color.get_stroke_color()
fill_color = self._xo_color.get_fill_color()
prelit_fill_color = None
prelit_stroke_color = None
else:
context.set_state(Gtk.StateFlags.NORMAL)
stroke_color = self._stroke_color
fill_color = self._fill_color
prelit_fill_color = self._prelit_fill_color
prelit_stroke_color = self._prelit_stroke_color
Gtk.render_background(
context, cr, background_area.x, background_area.y,
background_area.width, background_area.height)
has_prelit_colors = None not in [prelit_fill_color,
prelit_stroke_color]
if self._xo_color is not None:
stroke_color = self._xo_color.get_stroke_color()
fill_color = self._xo_color.get_fill_color()
prelit_fill_color = None
prelit_stroke_color = None
if flags & Gtk.CellRendererState.PRELIT and has_prelit_colors and \
pointer_inside:
self._buffer.fill_color = prelit_fill_color
self._buffer.stroke_color = prelit_stroke_color
else:
self._buffer.fill_color = fill_color
self._buffer.stroke_color = stroke_color
else:
stroke_color = self._stroke_color
fill_color = self._fill_color
prelit_fill_color = self._prelit_fill_color
prelit_stroke_color = self._prelit_stroke_color
has_prelit_colors = None not in [prelit_fill_color,
prelit_stroke_color]
if flags & Gtk.CellRendererState.PRELIT and has_prelit_colors and \
pointer_inside:
self._buffer.fill_color = prelit_fill_color
self._buffer.stroke_color = prelit_stroke_color
else:
self._buffer.fill_color = fill_color
self._buffer.stroke_color = stroke_color
if self._xo_color is not None:
self._buffer.fill_color = self._xo_color.get_fill_color()
self._buffer.stroke_color = self._xo_color.get_stroke_color()
else:
self._buffer.fill_color = self._fill_color
self._buffer.stroke_color = self._stroke_color
surface = self._buffer.get_surface()
if surface is None:
+5 -3
View File
@@ -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()
+86 -104
View File
@@ -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)
@@ -1372,28 +1373,34 @@ class ToolInvoker(WidgetInvoker):
self._widget.emit('clicked')
class CellRendererInvoker(Invoker):
class TreeViewInvoker(Invoker):
def __init__(self):
Invoker.__init__(self)
self._position_hint = self.AT_CURSOR
self._tree_view = None
self._cell_renderer = None
self._motion_hid = None
self._leave_hid = None
self._release_hid = None
self._long_pressed_hid = None
self.path = None
self._position_hint = self.AT_CURSOR
self._long_pressed_controller = SugarGestures.LongPressController()
def attach_cell_renderer(self, tree_view, cell_renderer):
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._cell_renderer = cell_renderer
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',
@@ -1403,129 +1410,104 @@ class CellRendererInvoker(Invoker):
self._long_pressed_controller.attach(
tree_view,
SugarGestures.EventControllerFlags.NONE)
Invoker.attach(self, cell_renderer)
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):
allocation = self._tree_view.get_allocation()
window = self._tree_view.get_window()
if window is not None:
res, x, y = window.get_origin()
else:
logging.warning(
"Trying to position palette with invoker that's not realized.")
x = 0
y = 0
return self._tree_view.get_background_area(self._path, self._column)
rect = Gdk.Rectangle()
rect.x = x + allocation.x
rect.y = y + allocation.y
rect.width = allocation.width
rect.height = allocation.height
return rect
def get_toplevel(self):
return self._tree_view.get_toplevel()
def __motion_notify_event_cb(self, widget, event):
if event.window != widget.get_bin_window():
return
if self.point_in_cell_renderer(event.x, event.y):
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
tree_view = self._tree_view
path, column_, x_, y_ = tree_view.get_path_at_pos(int(event.x),
int(event.y))
if path != self.path:
if self.path is not None:
self._redraw_path(self.path)
if path is not None:
self._redraw_path(path)
if self.palette is not None:
self.palette.popdown(immediate=True)
self.palette = None
self.path = path
if event.get_source_device().get_source() == \
Gdk.InputSource.TOUCHSCREEN:
return False
self.notify_mouse_enter()
else:
if self.path is not None:
self._redraw_path(self.path)
self.path = None
self._mouse_detector.start()
except TypeError:
# tree_view.get_path_at_pos() fail if x,y poition is over
# a empty area
pass
if event.get_source_device().get_source() == \
Gdk.InputSource.TOUCHSCREEN:
return False
self.notify_mouse_leave()
def _redraw_path(self, path):
column = None
for column in self._tree_view.get_columns():
if self._cell_renderer in column.get_cells():
break
assert column is not None
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):
if event.mode == Gdk.CrossingMode.NORMAL:
self.notify_mouse_leave()
return False
self._mouse_detector.stop()
def __button_release_event_cb(self, widget, event):
if event.button == 1 and self.point_in_cell_renderer(event.x,
event.y):
tree_view = self._tree_view
path, column_, x_, y_ = tree_view.get_path_at_pos(int(event.x),
int(event.y))
self._cell_renderer.emit('clicked', path)
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 and self.point_in_cell_renderer(event.x,
event.y):
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):
if self.point_in_cell_renderer(x, y):
self.notify_right_click()
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 point_in_cell_renderer(self, event_x, event_y):
pos = self._tree_view.get_path_at_pos(int(event_x), int(event_y))
if pos is None:
return False
def __mouse_slow_cb(self, widget):
self._mouse_detector.stop()
self._change_palette()
self.emit('mouse-enter')
path_, column, x, y_ = pos
for cell_renderer in column.get_cells():
if cell_renderer == self._cell_renderer:
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 get_toplevel(self):
return self._tree_view.get_toplevel()
def notify_popup(self):
Invoker.notify_popup(self)
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
def get_default_position(self):
return self.AT_CURSOR
+61
View File
@@ -0,0 +1,61 @@
# Copyright (C) 2014, Sugarlabs
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
from gi.repository import GObject
from gi.repository import GLib
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_end_signal = GObject.Signal('scroll-end')
def __init__(self, scrolled_window, timeout=100):
self._scrolled_window = scrolled_window
self._timeout = timeout
self.is_scrolling = False
self._prev_value = 0
self.connect_scrolled_window()
GObject.GObject.__init__(self)
def connect_scrolled_window(self):
adj = self._scrolled_window.get_vadjustment()
adj.connect('value-changed', self._value_changed_cb)
def _check_scroll_cb(self, adj):
if (adj.props.value == self._prev_value):
self.is_scrolling = False
self.scroll_end_signal.emit()
return False
self._prev_value = adj.props.value
return True
def _value_changed_cb(self, adj):
if (self.is_scrolling):
return
self.is_scrolling = True
self.scroll_start_signal.emit()
self._prev_value = adj.props.value
GLib.timeout_add(self._timeout, self._check_scroll_cb, adj)