Improve contextual menu
- Actions for all relation fields on list view - Use icons - Add attachments entry review284003
This commit is contained in:
1 parent
82bc90b8ff
commit
ac263a72fa
6 files changed
+164
-120
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user