From ac263a72fa7eb23729cf5092330248ea8c685132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Tue, 20 Mar 2012 19:35:20 +0100 Subject: [PATCH] Improve contextual menu - Actions for all relation fields on list view - Use icons - Add attachments entry review284003 --- CHANGELOG | 4 + tryton/action/main.py | 31 +++++++ tryton/common/popup_menu.py | 92 +++++++++++++++++++ tryton/gui/window/form.py | 25 +---- .../view_form/view/form_gtk/many2one.py | 62 +------------ tryton/gui/window/view_form/view/list.py | 70 +++++++------- 6 files changed, 164 insertions(+), 120 deletions(-) create mode 100644 tryton/common/popup_menu.py diff --git a/CHANGELOG b/CHANGELOG index af80a94784..4b2bd3870d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +* Improve contextual menu: + - Actions for all relation fields on list view + - Use icons + - Add attachments entry * Use RPCProgress almost everywhere * Add a simple search box * New domain parser using shlex diff --git a/tryton/action/main.py b/tryton/action/main.py index a73947c42a..acc65c5dc7 100644 --- a/tryton/action/main.py +++ b/tryton/action/main.py @@ -8,6 +8,7 @@ import gettext import tempfile import os import webbrowser +from tryton.pyson import PYSONEncoder from tryton.common import RPCProgress, RPCExecute, RPCException _ = gettext.gettext @@ -172,3 +173,33 @@ class Action(object): elif not len(keyact) and warning: message(_('No action defined!')) return False + + @staticmethod + def evaluate(action, atype, record): + ''' + Evaluate the action with the record. + ''' + action = action.copy() + if atype in ('print', 'action'): + email = {} + if 'pyson_email' in action: + email = record.expr_eval( action['pyson_email']) + if not email: + email = {} + if 'subject' not in email: + email['subject'] = action['name'].replace('_', '') + action['email'] = email + elif atype == 'relate': + encoder = PYSONEncoder() + if 'pyson_domain' in action: + action['pyson_domain'] = encoder.encode( + record.expr_eval(action['pyson_domain'], check_load=False)) + if 'pyson_context' in action: + action['pyson_context'] = encoder.encode( + record.expr_eval(action['pyson_context'], + check_load=False)) + + else: + raise NotImplementedError("Action type '%s' is not supported" % + atype) + return action diff --git a/tryton/common/popup_menu.py b/tryton/common/popup_menu.py new file mode 100644 index 0000000000..ad418a8c83 --- /dev/null +++ b/tryton/common/popup_menu.py @@ -0,0 +1,92 @@ +#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 gettext +from tryton.common import RPCExecute, RPCException +from tryton.gui.window.view_form.screen import Screen +from tryton.action import Action +from tryton.gui.window import Window +from tryton.gui.window.attachment import Attachment + +_ = gettext.gettext + + + +def populate(menu, model, record_id, title=''): + ''' + Fill menu with the actions of model for the record id. + If title is filled, the actions will be put in a submenu. + ''' + if record_id is None or record_id < 0: + return + try: + toolbar = RPCExecute('model', model, 'view_toolbar_get') + except RPCException: + return + + def activate(menuitem, action, atype): + screen = Screen(model) + screen.load([record_id]) + action = Action.evaluate(action, atype, screen.current_record) + data = { + 'model': model, + 'id': record_id, + 'ids': [record_id], + } + event = gtk.get_current_event() + allow_similar = False + if (event.state & gtk.gdk.CONTROL_MASK + or event.state & gtk.gdk.MOD1_MASK): + allow_similar = True + with Window(hide_current=True, allow_similar=allow_similar): + Action._exec_action(action, data, {}) + + def attachment(menuitem): + Attachment(model, record_id, None) + + if title: + if len(menu): + menu.append(gtk.SeparatorMenuItem()) + title_item = gtk.MenuItem(title) + menu.append(title_item) + submenu = gtk.Menu() + title_item.set_submenu(submenu) + action_menu = submenu + else: + action_menu = menu + + if len(action_menu): + action_menu.append(gtk.SeparatorMenuItem()) + attachment_item = gtk.ImageMenuItem('tryton-attachment') + attachment_item.set_label(_('Attachments...')) + action_menu.append(attachment_item) + attachment_item.connect('activate', attachment) + + for atype, icon, label, flavor in ( + ('action', 'tryton-executable', _('Actions...'), None), + ('relate', 'tryton-go-jump', _('Relate...'), None), + ('print', 'tryton-print-open', _('Report...'), 'open'), + ('print', 'tryton-print-email', _('E-Mail...'), 'email'), + ('print', 'tryton-print', _('Print...'), 'print'), + ): + if len(action_menu): + action_menu.append(gtk.SeparatorMenuItem()) + title_item = gtk.ImageMenuItem(icon) + title_item.set_label(label) + action_menu.append(title_item) + if not toolbar[atype]: + title_item.set_sensitive(False) + continue + submenu = gtk.Menu() + title_item.set_submenu(submenu) + for action in toolbar[atype]: + action = action.copy() + item = gtk.MenuItem(action['name']) + submenu.append(item) + if flavor == 'print': + action['direct_print'] = True + elif flavor == 'email': + action['email_print'] = True + item.connect('activate', activate, action, atype) + + menu.show_all() diff --git a/tryton/gui/window/form.py b/tryton/gui/window/form.py index adf3a814d3..83c9b49dba 100644 --- a/tryton/gui/window/form.py +++ b/tryton/gui/window/form.py @@ -499,30 +499,7 @@ class Form(SignalEvent, TabContent): return record_id = self.screen.id_get() record_ids = self.screen.sel_ids_get() - if atype in ('print', 'action'): - email = {} - if 'pyson_email' in action: - email = self.screen.current_record.expr_eval( - action['pyson_email']) - if not email: - email = {} - if 'subject' not in email: - email['subject'] = action['name'].replace('_', '') - action['email'] = email - self.screen.display() - elif atype == 'relate': - encoder = PYSONEncoder() - if 'pyson_domain' in action: - action['pyson_domain'] = encoder.encode( - self.screen.current_record.expr_eval( - action['pyson_domain'], check_load=False)) - if 'pyson_context' in action: - action['pyson_context'] = encoder.encode( - self.screen.current_record.expr_eval( - action['pyson_context'], check_load=False)) - else: - raise NotImplementedError("Action type '%s' is not supported" % - atype) + action = Action.evaluate(action, atype, self.screen.current_record) data = { 'model': self.screen.model_name, 'id': record_id, diff --git a/tryton/gui/window/view_form/view/form_gtk/many2one.py b/tryton/gui/window/view_form/view/form_gtk/many2one.py index aa2721a393..a8448d2439 100644 --- a/tryton/gui/window/view_form/view/form_gtk/many2one.py +++ b/tryton/gui/window/view_form/view/form_gtk/many2one.py @@ -8,9 +8,8 @@ import tryton.common as common from tryton.gui.window.view_form.screen import Screen from tryton.gui.window.win_search import WinSearch from tryton.gui.window.win_form import WinForm -from tryton.action import Action from tryton.config import CONFIG -from tryton.pyson import PYSONEncoder +from tryton.common.popup_menu import populate from tryton.common import RPCExecute, RPCException _ = gettext.gettext @@ -310,61 +309,6 @@ class Many2One(WidgetInterface): def _populate_popup(self, widget, menu): value = self.field.get(self.record) - try: - relates = RPCExecute('model', 'ir.action.keyword', 'get_keyword', - 'form_relate', (self.attrs['relation'], -1)) - except RPCException: - return False - menu_entries = [] - menu_entries.append((None, None, None)) - menu_entries.append((None, None, None)) - menu_entries.append((_('Actions'), - lambda x: self.click_and_action('form_action'), 0)) - menu_entries.append((_('Reports'), - lambda x: self.click_and_action('form_print'), 0)) - menu_entries.append((None, None, None)) - for relate in relates: - relate['string'] = relate['name'] - fct = lambda action: lambda x: self.click_and_relate(action) - menu_entries.append( - ('... ' + relate['name'], fct(relate), 0)) - - for stock_id, callback, sensitivity in menu_entries: - if stock_id: - item = gtk.ImageMenuItem(stock_id) - if callback: - item.connect("activate", callback) - item.set_sensitive(bool(sensitivity or value)) - else: - item = gtk.SeparatorMenuItem() - item.show() - menu.append(item) + if self.has_target(value): + populate(menu, self.get_model(), self.id_from_value(value)) return True - - def click_and_relate(self, action): - data = {} - context = {} - act = action.copy() - obj_id = self.field.get(self.record) - if not obj_id: - common.message(_('You must select a record to use the relation!')) - return False - screen = Screen(self.attrs['relation']) - screen.load([obj_id]) - encoder = PYSONEncoder() - act['domain'] = encoder.encode(screen.current_record.expr_eval( - act.get('domain', []), check_load=False)) - act['context'] = encoder.encode(screen.current_record.expr_eval( - act.get('context', {}), check_load=False)) - data['model'] = self.attrs['relation'] - data['id'] = obj_id - data['ids'] = [obj_id] - return Action._exec_action(act, data, context) - - def click_and_action(self, atype): - obj_id = self.field.get(self.record) - return Action.exec_keyword(atype, { - 'model': self.attrs['relation'], - 'id': obj_id or False, - 'ids': [obj_id], - }, alwaysask=True) diff --git a/tryton/gui/window/view_form/view/list.py b/tryton/gui/window/view_form/view/list.py index e73ed79aa1..a59f674164 100644 --- a/tryton/gui/window/view_form/view/list.py +++ b/tryton/gui/window/view_form/view/list.py @@ -18,6 +18,7 @@ from tryton.common.cellrenderertoggle import CellRendererToggle from tryton.common.cellrendererbinary import CellRendererBinary from tryton.pyson import PYSONEncoder from tryton.gui.window import Window +from tryton.common.popup_menu import populate from tryton.common import RPCExecute, RPCException _ = gettext.gettext @@ -547,45 +548,40 @@ class ViewList(ParserView): model = selection.get_selected_rows()[0] if (not path) or not path[0]: return False - record = model.group[path[0][0]] + group = model.group + record = group[path[0][0]] - menu_entries = [] - menu_entries.append(('gtk-copy', lambda x: self.on_copy(), 1)) - if hasattr(path[1], '_type') and path[1]._type == 'many2one': - value = record[path[1].name].get(record) - try: - relates = RPCExecute('model', 'ir.action.keyword', - 'get_keyword', 'form_relate', - (self.screen.group.fields[ - path[1].name].attrs['relation'], -1)) - except RPCException: - relates = None - if relates: - menu_entries.append((None, None, None)) - menu_entries.append((_('Actions'), - lambda x: self.click_and_action( - 'form_action', value, path), 0)) - menu_entries.append((_('Reports'), - lambda x: self.click_and_action( - 'form_print', value, path), 0)) - menu_entries.append((None, None, None)) - for relate in relates: - relate['string'] = relate['name'] - fct = lambda action: lambda x: \ - self.click_and_relate(action, value, path) - menu_entries.append( - ('... ' + relate['name'], fct(relate), 0)) menu = gtk.Menu() - for stock_id, callback, sensitivity in menu_entries: - if stock_id: - item = gtk.ImageMenuItem(stock_id) - if callback: - item.connect('activate', callback) - item.set_sensitive(bool(sensitivity or value)) - else: - item = gtk.SeparatorMenuItem() - item.show() - menu.append(item) + copy_item = gtk.ImageMenuItem('gtk-copy') + copy_item.connect('activate', lambda x: self.on_copy()) + menu.append(copy_item) + record_id = record.id + # Don't activate actions if parent is modified + parent = record.parent if record else None + while parent: + if parent.modified: + record_id = -1 + break + parent = parent.parent + populate(menu, group.model_name, record_id) + for col in self.widget_tree.get_columns(): + if not col.get_visible() or not col.name: + continue + field = group.fields[col.name] + model = None + if field.attrs['type'] == 'many2one': + model = field.attrs['relation'] + record_id = field.get(record) + elif field.attrs['type'] == 'reference': + value = field.get(record) + if value: + model, record_id = value.split(',') + record_id = int(record_id) + if not model: + continue + label = field.attrs['string'] + populate(menu, model, record_id, title=label) + menu.show_all() menu.popup(None, None, None, event.button, event.time) elif event.button == 2: event.button = 1