From 9180bed90cefd915da83f240bf16ce43fdd6b6a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Wed, 20 Feb 2019 12:14:12 +0100 Subject: [PATCH] Migrate cellrenderer to introspection and store editable In order to limit issue with vfunc, it is better to follow the best practice by using introspection without pygtkcompat. Also vfunc may have issue with object created by it and returned, so it is safer to keep a Python reference to on the Python class. issue8110 review47811002 --- tryton/__init__.py | 2 - tryton/common/cellrendererbinary.py | 90 +++++++++----------- tryton/common/cellrendererbutton.py | 82 +++++++----------- tryton/common/cellrendererclickablepixbuf.py | 25 +++--- tryton/common/datetime_.py | 76 ++++++++++++----- 5 files changed, 140 insertions(+), 135 deletions(-) diff --git a/tryton/__init__.py b/tryton/__init__.py index 130a69ece8..f7f7ce27cc 100644 --- a/tryton/__init__.py +++ b/tryton/__init__.py @@ -96,8 +96,6 @@ def get_active_text(self): return model[active][index] Gtk.ComboBox.get_active_text = get_active_text -Gtk.GenericCellRenderer.__gobject_init__ = Gtk.GenericCellRenderer.__init__ - from gi.repository import Pango diff --git a/tryton/common/cellrendererbinary.py b/tryton/common/cellrendererbinary.py index ec37bb355e..b7b3bdff0b 100644 --- a/tryton/common/cellrendererbinary.py +++ b/tryton/common/cellrendererbinary.py @@ -1,8 +1,6 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -import gtk -import gobject -import pango +from gi.repository import Gtk, Gdk, GObject, Pango from .common import IconFactory @@ -10,31 +8,31 @@ BUTTON_BORDER = 2 BUTTON_SPACING = 1 -class CellRendererBinary(gtk.GenericCellRenderer): +class CellRendererBinary(Gtk.CellRenderer): __gproperties__ = { - 'visible': (gobject.TYPE_BOOLEAN, 'Visible', 'Visible', True, - gobject.ParamFlags.READWRITE), - 'editable': (gobject.TYPE_BOOLEAN, 'Editable', 'Editable', False, - gobject.ParamFlags.READWRITE), - 'size': (gobject.TYPE_STRING, 'Size', 'Size', '', - gobject.ParamFlags.READWRITE), - } + 'visible': (GObject.TYPE_BOOLEAN, 'Visible', 'Visible', True, + GObject.ParamFlags.READWRITE), + 'editable': (GObject.TYPE_BOOLEAN, 'Editable', 'Editable', False, + GObject.ParamFlags.READWRITE), + 'size': (GObject.TYPE_STRING, 'Size', 'Size', '', + GObject.ParamFlags.READWRITE), + } __gsignals__ = { - 'select': (gobject.SignalFlags.RUN_LAST, gobject.TYPE_NONE, - (gobject.TYPE_STRING,)), - 'open': (gobject.SignalFlags.RUN_LAST, gobject.TYPE_NONE, - (gobject.TYPE_STRING,)), - 'save': (gobject.SignalFlags.RUN_LAST, gobject.TYPE_NONE, - (gobject.TYPE_STRING,)), - 'clear': (gobject.SignalFlags.RUN_LAST, gobject.TYPE_NONE, - (gobject.TYPE_STRING,)), - } + 'select': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, + (GObject.TYPE_STRING,)), + 'open': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, + (GObject.TYPE_STRING,)), + 'save': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, + (GObject.TYPE_STRING,)), + 'clear': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, + (GObject.TYPE_STRING,)), + } def __init__(self, use_filename): - self.__gobject_init__() + Gtk.CellRenderer.__init__(self) self.visible = True self.editable = False - self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE) + self.set_property('mode', Gtk.CellRendererMode.ACTIVATABLE) self.use_filename = use_filename self.images = {} for key, icon in ( @@ -42,9 +40,8 @@ class CellRendererBinary(gtk.GenericCellRenderer): ('open', 'tryton-open'), ('save', 'tryton-save'), ('clear', 'tryton-clear')): - # hack to get gtk.gdk.Image from stock icon img_sensitive = IconFactory.get_pixbuf( - icon, gtk.ICON_SIZE_SMALL_TOOLBAR) + icon, Gtk.IconSize.SMALL_TOOLBAR) img_insensitive = img_sensitive.copy() img_sensitive.saturate_and_pixelate(img_insensitive, 0, False) width = img_sensitive.get_width() @@ -75,18 +72,17 @@ class CellRendererBinary(gtk.GenericCellRenderer): + (2 * (BUTTON_BORDER + BUTTON_SPACING) * len(self.buttons)) - 2 * BUTTON_SPACING) - def on_get_size(self, widget, cell_area=None): + def do_get_size(self, widget, cell_area=None): if cell_area is None: return (0, 0, 30, 18) else: return (cell_area.x, cell_area.y, cell_area.width, cell_area.height) - do_get_size = on_get_size - def on_activate(self, event, widget, path, background_area, - cell_area, flags): + def do_activate( + self, event, widget, path, background_area, cell_area, flags): if event is None: - return True + return button_width = self.button_width() for index, button_name in enumerate(self.buttons): @@ -104,15 +100,13 @@ class CellRendererBinary(gtk.GenericCellRenderer): else: button_name = None if not self.visible or not button_name: - return True - if not self.editable and button_name in ('select', 'clear'): - return True - if not self.size and button_name in {'open', 'save'}: - return True - if event.type == gtk.gdk.BUTTON_PRESS: - self.emit(button_name, path) + return + elif not self.editable and button_name in ('select', 'clear'): + return + elif not self.size and button_name in {'open', 'save'}: + return + self.emit(button_name, path) return True - do_activate = on_activate def do_render(self, cr, widget, background_area, cell_area, flags): if not self.visible: @@ -137,15 +131,15 @@ class CellRendererBinary(gtk.GenericCellRenderer): lwidth = w - button_width - padding.left - padding.right if lwidth < 0: lwidth = 0 - layout.set_width(lwidth * pango.SCALE) - layout.set_ellipsize(pango.ELLIPSIZE_END) - layout.set_wrap(pango.WRAP_CHAR) - layout.set_alignment(pango.ALIGN_RIGHT) + layout.set_width(lwidth * Pango.SCALE) + layout.set_ellipsize(Pango.ELLIPSIZE_END) + layout.set_wrap(Pango.WRAP_CHAR) + layout.set_alignment(Pango.ALIGN_RIGHT) if lwidth > 0: lw, lh = layout.get_size() # Can not use get_pixel_extents - lw /= pango.SCALE - lh /= pango.SCALE + lw /= Pango.SCALE + lh /= Pango.SCALE lx = x + padding.left if self.buttons and self.buttons[0] == 'open': @@ -154,7 +148,7 @@ class CellRendererBinary(gtk.GenericCellRenderer): ly = y + padding.top + 0.5 * ( h - padding.top - padding.bottom - lh) - gtk.render_layout(context, cr, lx, ly, layout) + Gtk.render_layout(context, cr, lx, ly, layout) for index, button_name in enumerate(self.buttons): pxbf_sens, pxbf_insens, pxbf_width, pxbf_height = \ @@ -177,13 +171,13 @@ class CellRendererBinary(gtk.GenericCellRenderer): by = cell_area.y bw = pxbf_width + (2 * BUTTON_BORDER) - gtk.render_background(context, cr, bx, by, bw, h) - gtk.render_frame(context, cr, bx, by, bw, h) + Gtk.render_background(context, cr, bx, by, bw, h) + Gtk.render_frame(context, cr, bx, by, bw, h) - gtk.gdk.cairo_set_source_pixbuf( + Gdk.cairo_set_source_pixbuf( cr, pixbuf, bx + BUTTON_BORDER, by + (h - pxbf_height) / 2) cr.paint() context.restore() -gobject.type_register(CellRendererBinary) +GObject.type_register(CellRendererBinary) diff --git a/tryton/common/cellrendererbutton.py b/tryton/common/cellrendererbutton.py index 88dd377be7..a93bd6a529 100644 --- a/tryton/common/cellrendererbutton.py +++ b/tryton/common/cellrendererbutton.py @@ -1,31 +1,27 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -import gtk -import gobject -import pango +from gi.repository import Gtk, GObject, Pango -class CellRendererButton(gtk.GenericCellRenderer): - # TODO Add keyboard editing +class CellRendererButton(Gtk.CellRenderer): __gproperties__ = { - "text": (gobject.TYPE_STRING, None, "Text", - "Displayed text", gobject.ParamFlags.READWRITE), - 'visible': (gobject.TYPE_INT, 'Visible', - 'Visible', 0, 10, 0, gobject.ParamFlags.READWRITE), - 'sensitive': (gobject.TYPE_INT, 'Sensitive', - 'Sensitive', 0, 10, 0, gobject.ParamFlags.READWRITE), - } + "text": (GObject.TYPE_STRING, None, "Text", + "Displayed text", GObject.ParamFlags.READWRITE), + 'visible': (GObject.TYPE_INT, 'Visible', + 'Visible', 0, 10, 0, GObject.ParamFlags.READWRITE), + 'sensitive': (GObject.TYPE_INT, 'Sensitive', + 'Sensitive', 0, 10, 0, GObject.ParamFlags.READWRITE), + } __gsignals__ = { - 'clicked': (gobject.SignalFlags.RUN_LAST, gobject.TYPE_NONE, - (gobject.TYPE_STRING, )), - } + 'clicked': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, + (GObject.TYPE_STRING,)), + } def __init__(self, text=""): - self.__gobject_init__() + Gtk.CellRenderer.__init__(self) self.text = text - self.set_property('mode', gtk.CELL_RENDERER_MODE_EDITABLE) - self.clicking = False + self.set_property('mode', Gtk.CellRendererMode.EDITABLE) self.visible = True self.sensitive = True @@ -39,15 +35,14 @@ class CellRendererButton(gtk.GenericCellRenderer): if not self.visible: return - state = gtk.StateFlags.NORMAL - if self.clicking and flags & gtk.CELL_RENDERER_SELECTED: - state = gtk.StateFlags.ACTIVE - elif not self.sensitive: - state = gtk.StateFlags.INSENSITIVE + if not self.sensitive: + state = Gtk.StateFlags.INSENSITIVE + else: + state = Gtk.StateFlags.NORMAL context = widget.get_style_context() context.save() - context.add_class('button') + context.add_class('text-button') context.set_state(state) xpad, ypad = self.get_padding() @@ -56,18 +51,18 @@ class CellRendererButton(gtk.GenericCellRenderer): w = cell_area.width - 2 * xpad h = cell_area.height - 2 * ypad - gtk.render_background(context, cr, x, y, w, h) - gtk.render_frame(context, cr, x, y, w, h) + Gtk.render_background(context, cr, x, y, w, h) + Gtk.render_frame(context, cr, x, y, w, h) padding = context.get_padding(state) layout = widget.create_pango_layout(self.text) - layout.set_width((w - padding.left - padding.right) * pango.SCALE) - layout.set_ellipsize(pango.ELLIPSIZE_END) - layout.set_wrap(pango.WRAP_CHAR) + layout.set_width((w - padding.left - padding.right) * Pango.SCALE) + layout.set_ellipsize(Pango.EllipsizeMode.END) + layout.set_wrap(Pango.WrapMode.CHAR) lw, lh = layout.get_size() # Can not use get_pixel_extents - lw /= pango.SCALE - lh /= pango.SCALE + lw /= Pango.SCALE + lh /= Pango.SCALE if w < lw: x = x + padding.left else: @@ -75,34 +70,21 @@ class CellRendererButton(gtk.GenericCellRenderer): w - padding.left - padding.right - lw) y = y + padding.top + 0.5 * (h - padding.top - padding.bottom - lh) - gtk.render_layout(context, cr, x, y, layout) + Gtk.render_layout(context, cr, x, y, layout) context.restore() - def on_get_size(self, widget, cell_area=None): + def do_get_size(self, widget, cell_area=None): if cell_area is None: return (0, 0, 30, 18) else: return (cell_area.x, cell_area.y, cell_area.width, cell_area.height) - do_get_size = on_get_size - def on_start_editing(self, event, widget, path, background_area, - cell_area, flags): + def do_start_editing( + self, event, widget, path, background_area, cell_area, flags): if not self.visible or not self.sensitive: return - if (event is None) or ((event.type == gtk.gdk.BUTTON_PRESS) - or (event.type == gtk.gdk.KEY_PRESS - and event.keyval == gtk.keysyms.space)): - self.clicking = True - widget.queue_draw() - while gtk.events_pending(): - gtk.main_iteration() - self.emit("clicked", path) + self.emit("clicked", path) - def timeout(self, widget): - self.clicking = False - widget.queue_draw() - gobject.timeout_add(60, timeout, self, widget) - do_start_editing = on_start_editing -gobject.type_register(CellRendererButton) +GObject.type_register(CellRendererButton) diff --git a/tryton/common/cellrendererclickablepixbuf.py b/tryton/common/cellrendererclickablepixbuf.py index 11e6b2a7dc..d26b36eb53 100644 --- a/tryton/common/cellrendererclickablepixbuf.py +++ b/tryton/common/cellrendererclickablepixbuf.py @@ -1,24 +1,21 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -import gtk -import gobject +from gi.repository import Gtk, GObject -class CellRendererClickablePixbuf(gtk.CellRendererPixbuf): +class CellRendererClickablePixbuf(Gtk.CellRendererPixbuf): __gsignals__ = { - 'clicked': (gobject.SignalFlags.RUN_LAST, gobject.TYPE_NONE, - (gobject.TYPE_STRING, )), + 'clicked': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, + (GObject.TYPE_STRING, )), } def __init__(self): - super(CellRendererClickablePixbuf, self).__init__() - self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE) + Gtk.CellRendererPixbuf.__init__(self) + self.set_property('mode', Gtk.CellRendererMode.ACTIVATABLE) - def do_activate(self, event, widget, path, background_area, cell_area, - flags): - if (event - and cell_area.x <= event.x <= cell_area.x + cell_area.width - and cell_area.y <= event.y <= cell_area.y + cell_area.height): - self.emit('clicked', path) + def do_activate( + self, event, widget, path, background_area, cell_area, flags): + self.emit('clicked', path) -gobject.type_register(CellRendererClickablePixbuf) + +GObject.type_register(CellRendererClickablePixbuf) diff --git a/tryton/common/datetime_.py b/tryton/common/datetime_.py index 749eb56442..f39ee01569 100644 --- a/tryton/common/datetime_.py +++ b/tryton/common/datetime_.py @@ -199,6 +199,8 @@ class CellRendererDate(gtk.CellRendererText): def __init__(self): self.__format = '%x' + self.__entry = None + self.__focus_out_id = 0 gtk.CellRendererText.__init__(self) @@ -215,16 +217,34 @@ class CellRendererDate(gtk.CellRendererText): def do_start_editing( self, event, widget, path, background_area, cell_area, flags): - widget = add_operators(Date()) # TODO add_operators has option - widget.props.format = self.props.format - widget.props.value = self.props.text - if self.props.background_set: - widget.modify_base(gtk.STATE_NORMAL, self.props.background_gdk) - if self.props.foreground_set: - widget.modify_fg(gtk.STATE_NORMAL, self.props.foreground_gdk) - widget.grab_focus() - widget.show() - return widget + if not self.props.editable: + return + self.__entry = add_operators(Date()) # TODO add_operators has option + self.__entry.props.format = self.props.format + self.__entry.props.value = self.props.text + self.__entry.set_has_frame(False) + self.__entry.connect('editing-done', self.__editing_done) + self.__focus_out_id = self.__entry.connect( + 'focus-out-event', self.__focus_out_event) + # XXX focus-out-event + self.__entry.show() + return self.__entry + + def __editing_done(self, entry): + self.__entry = None + if self.__focus_out_id: + entry.disconnect(self.__focus_out_id) + canceled = entry.props.editing_canceled + self.stop_editing(canceled) + if canceled: + return + # TODO emit edited + + def __focus_out_event(self, entry, event): + entry.props.editing_canceled = True + entry.editing_done() + entry.remove_widget() + return False gobject.type_register(CellRendererDate) @@ -339,7 +359,7 @@ class Time(gtk.ComboBoxEntry): gobject.type_register(Time) -class CellRendererTime(gtk.CellRendererText): +class CellRendererTime(gtk.CellRendererCombo): __gproperties__ = { 'format': (gobject.TYPE_STRING, '%X', @@ -350,6 +370,8 @@ class CellRendererTime(gtk.CellRendererText): def __init__(self): self.__format = '%X' + self.__combo = None + self.__focus_out_id = 0 gtk.CellRendererText.__init__(self) @@ -368,16 +390,28 @@ class CellRendererTime(gtk.CellRendererText): self, event, widget, path, background_area, cell_area, flags): if not self.props.editable: return - widget = add_operators(Time()) # TODO add_operators has option - widget.props.format = self.props.format - widget.props.value = self.props.text - if self.props.background_set: - widget.modify_base(gtk.STATE_NORMAL, self.props.background_gdk) - if self.props.foreground_set: - widget.modify_fg(gtk.STATE_NORMAL, self.props.foreground_gdk) - widget.grab_focus() - widget.show() - return widget + self.__combo = add_operators(Time()) # TODO add_operators has option + self.__combo.props.format = self.props.format + self.__combo.props.value = self.props.text + self.__combo.props.has_frame = False + self.__combo.connect('editing-done', self.__editing_done) + # TODO: connect to changed + self.__focus_out_id = self.__combo.connect( + 'focus-out-event', self.__focus_out_event) + self.__combo.show() + return self.__combo + + def __editing_done(self, combo): + self.__combo = None + canceled = combo.props.editing_canceled + self.stop_editing(canceled) + if canceled: + return + # TODO emit edited + + def __focus_out_event(self, combo, event): + self.__editing_done(combo) + return False gobject.type_register(CellRendererTime)