diff --git a/tryton/common/cellrendererbutton.py b/tryton/common/cellrendererbutton.py new file mode 100644 index 0000000000..c7f6ec821b --- /dev/null +++ b/tryton/common/cellrendererbutton.py @@ -0,0 +1,76 @@ +#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 + + +class CellRendererButton(gtk.GenericCellRenderer): + #TODO Add keyboard editing + __gproperties__ = { + "text": (gobject.TYPE_STRING, None, "Text", + "Displayed text", gobject.PARAM_READWRITE), + } + + __gsignals__ = { + 'clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_STRING, )), + } + + def __init__(self, text=""): + self.__gobject_init__() + self.text = text + self.set_property('mode', gtk.CELL_RENDERER_MODE_EDITABLE) + self.clicking = False + + def do_set_property(self, pspec, value): + setattr(self, pspec.name, value) + + def do_get_property(self, pspec): + return getattr(self, pspec.name) + + def on_render(self, window, widget, background_area, cell_area, + expose_area, flags): + state = gtk.STATE_NORMAL + shadow = gtk.SHADOW_OUT + if self.clicking and flags & gtk.CELL_RENDERER_SELECTED: + state = gtk.STATE_ACTIVE + shadow = gtk.SHADOW_IN + widget.style.paint_box(window, state, shadow, + None, widget, "button", + cell_area.x, cell_area.y, + cell_area.width, cell_area.height) + layout = widget.create_pango_layout('') + layout.set_font_description(widget.style.font_desc) + w, h = layout.get_size() + x = cell_area.x + y = int(cell_area.y + (cell_area.height - h / pango.SCALE) / 2) + window.draw_layout(widget.style.text_gc[0], x, y, layout) + + layout = widget.create_pango_layout(self.text) + layout.set_font_description(widget.style.font_desc) + w, h = layout.get_size() + x = cell_area.x + 3 + y = int(cell_area.y + (cell_area.height - h / pango.SCALE) / 2) + window.draw_layout(widget.style.text_gc[0], x, y, layout) + + def on_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) + + def on_start_editing(self, event, widget, path, background_area, + cell_area, flags): + if event is not None and ((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() + gtk.main_iteration() + self.emit("clicked", path) + def timeout(self, widget): + self.clicking = False + widget.queue_draw() + gobject.timeout_add(60, timeout, self, widget) + +gobject.type_register(CellRendererButton) diff --git a/tryton/gui/window/view_form/view/list.py b/tryton/gui/window/view_form/view/list.py index db24968633..a2b7b01d40 100644 --- a/tryton/gui/window/view_form/view/list.py +++ b/tryton/gui/window/view_form/view/list.py @@ -10,6 +10,7 @@ from tryton.common import message import gettext import tryton.common as common from tryton.config import CONFIG +from tryton.common.cellrendererbutton import CellRendererButton _ = gettext.gettext @@ -507,7 +508,8 @@ class ViewList(ParserView): renderer = renderers[0] if isinstance(renderer, gtk.CellRendererToggle): editable = renderer.get_property('activatable') - elif isinstance(renderer, gtk.CellRendererProgress): + elif isinstance(renderer, + (gtk.CellRendererProgress, CellRendererButton)): editable = False else: editable = renderer.get_property('editable') @@ -540,7 +542,8 @@ class ViewList(ParserView): for renderer in col.get_cell_renderers(): if isinstance(renderer, gtk.CellRendererToggle): renderer.set_property('activatable', False) - elif isinstance(renderer, gtk.CellRendererProgress): + elif isinstance(renderer, + (gtk.CellRendererProgress, CellRendererButton)): pass else: renderer.set_property('editable', False) diff --git a/tryton/gui/window/view_form/view/list_gtk/editabletree.py b/tryton/gui/window/view_form/view/list_gtk/editabletree.py index c0e9032738..215ddccf34 100644 --- a/tryton/gui/window/view_form/view/list_gtk/editabletree.py +++ b/tryton/gui/window/view_form/view/list_gtk/editabletree.py @@ -2,6 +2,7 @@ import gtk import parser import gettext +from tryton.common.cellrendererbutton import CellRendererButton _ = gettext.gettext @@ -74,7 +75,8 @@ class EditableTreeView(gtk.TreeView): renderer = cols[idx].get_cell_renderers()[0] if isinstance(renderer, gtk.CellRendererToggle): editable = renderer.get_property('activatable') - elif isinstance(renderer, gtk.CellRendererProgress): + elif isinstance(renderer, + (gtk.CellRendererProgress, CellRendererButton)): editable = False else: editable = renderer.get_property('editable') @@ -90,7 +92,8 @@ class EditableTreeView(gtk.TreeView): renderer = cols[idx].get_cell_renderers()[0] if isinstance(renderer, gtk.CellRendererToggle): editable = renderer.get_property('activatable') - elif isinstance(renderer, gtk.CellRendererProgress): + elif isinstance(renderer, + (gtk.CellRendererProgress, CellRendererButton)): editable = False else: editable = renderer.get_property('editable') @@ -187,7 +190,8 @@ class EditableTreeView(gtk.TreeView): renderer = column.get_cell_renderers()[0] if isinstance(renderer, gtk.CellRendererToggle): editable = renderer.get_property('activatable') - elif isinstance(renderer, gtk.CellRendererProgress): + elif isinstance(renderer, + (gtk.CellRendererProgress, CellRendererButton)): editable = False else: editable = renderer.get_property('editable') diff --git a/tryton/gui/window/view_form/view/list_gtk/parser.py b/tryton/gui/window/view_form/view/list_gtk/parser.py index a2bda0f763..6d840d9286 100644 --- a/tryton/gui/window/view_form/view/list_gtk/parser.py +++ b/tryton/gui/window/view_form/view/list_gtk/parser.py @@ -20,6 +20,8 @@ import datetime as DT from tryton.common import DT_FORMAT, DHM_FORMAT, COLORS, node_attributes, TRYTON_ICON import tryton.common as common from _strptime import LocaleTime +from tryton.common.cellrendererbutton import CellRendererButton +from tryton.action import Action def send_keys(renderer, editable, position, treeview): editable.connect('key_press_event', treeview.on_keypressed) @@ -61,6 +63,7 @@ class ParserTree(ParserInterface): def parse(self, model, root_node, fields): dict_widget = {} + button_list = [] attrs = node_attributes(root_node) on_write = attrs.get('on_write', '') editable = attrs.get('editable', False) @@ -101,7 +104,8 @@ class ParserTree(ParserInterface): if editable and not node_attrs.get('readonly', False): if isinstance(renderer, gtk.CellRendererToggle): renderer.set_property('activatable', True) - elif isinstance(renderer, gtk.CellRendererProgress): + elif isinstance(renderer, + (gtk.CellRendererProgress, CellRendererButton)): pass else: renderer.set_property('editable', True) @@ -179,6 +183,38 @@ class ParserTree(ParserInterface): digits = fields.get('digits', (16, 2))[1] dict_widget[i] = (fname, label, label_sum, digits, label_bold) + elif node.localName == 'button': + #TODO add shortcut + cell = Button(treeview, node_attrs, self.window, self.screen) + button_list.append(cell) + renderer = cell.renderer + string = node_attrs.get('string', _('Unknown')) + col = gtk.TreeViewColumn(string, renderer) + col.name = None + + label = gtk.Label(string) + label.show() + help = string + if node_attrs.get('help'): + help += '\n' + node_attrs['help'] + tooltips.set_tip(label, help) + tooltips.enable() + arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_IN) + col.arrow = arrow + col.arrow_show = False + col.set_widget(label) + + col._type = 'button' + if 'width' in node_attrs: + width = int(node_attrs['width']) + else: + width = 80 + col.width = width + if width > 0: + col.set_fixed_width(width) + col.set_resizable(True) + col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) + i = treeview.append_column(col) if not bool(int(attrs.get('fill', '0'))): col = gtk.TreeViewColumn() col.name = None @@ -188,7 +224,7 @@ class ParserTree(ParserInterface): col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) treeview.append_column(col) treeview.set_fixed_height_mode(True) - return treeview, dict_widget, [], on_write, [], None + return treeview, dict_widget, button_list, on_write, [], None class Char(object): @@ -633,6 +669,79 @@ class ProgressBar(object): def value_from_text(self, model, text): return float(text) + +class Button(object): + + def __init__(self, treeview=None, attrs=None, window=None, screen=None): + super(Button, self).__init__() + self.attrs = attrs or {} + self.renderer = CellRendererButton(attrs.get('string', _('Unknown'))) + self.treeview = treeview + self.window = window + self.screen = screen + + self.renderer.connect('clicked', self.button_clicked) + + def button_clicked(self, widget, path): + if not path: + return True + store = self.treeview.get_model() + model = store.get_value(store.get_iter(path), 0) + + state_changes = self.attrs.get('states', {}) + if isinstance(state_changes, basestring): + state_changes = eval(state_changes) + if 'invisible' in state_changes: + if model.expr_eval(state_changes['invisible'], check_load=False): + return True + if 'readonly' in state_changes: + if model.expr_eval(state_changes['readonly'], check_load=False): + return True + + self.screen.current_model = model + obj_id = self.screen.save_current() + if obj_id: + if not self.attrs.get('confirm', False) or \ + common.sur(self.attrs['confirm'], self.window): + button_type = self.attrs.get('type', 'workflow') + if button_type == 'workflow': + args = ('object', 'exec_workflow', self.screen.name, + self.attrs['name'], obj_id) + try: + rpc.execute(*args) + except Exception, exception: + common.process_exception(exception, self.window, *args) + elif button_type == 'object': + args = ('object', 'execute', self.screen.name, + self.attrs['name'], [obj_id], model.context_get()) + try: + rpc.execute(*args) + except Exception, exception: + common.process_exception(exception, self.window, *args) + elif button_type == 'action': + action_id = None + args = ('object', 'execute', 'ir.action', 'get_action_id', + int(self.attrs['name']), rpc.CONTEXT) + try: + action_id = rpc.execute(*args) + except Exception, exception: + action_id = common.process_exception(exception, + self.window, *args) + if action_id: + Action.execute(action_id, { + 'model': self.screen.name, + 'id': obj_id, + 'ids': [obj_id], + }, self.window) + else: + raise Exception('Unallowed button type') + self.screen.reload() + else: + if self.screen.form: + self.screen.form.message_info( + _('Invalid Form, correct red fields!')) + self.screen.display() + CELLTYPES = { 'char': Char, 'many2one': M2O, diff --git a/tryton/gui/window/view_tree/parse.py b/tryton/gui/window/view_tree/parse.py index c5db6ec0a7..76566d6301 100644 --- a/tryton/gui/window/view_tree/parse.py +++ b/tryton/gui/window/view_tree/parse.py @@ -61,6 +61,8 @@ class Parse(object): column.set_resizable(True) column.set_visible(not attrs.get('tree_invisible', False)) self.tree.append_column(column) + elif name == 'button': + pass else: import logging log = logging.getLogger('view')