diff --git a/tryton/action/main.py b/tryton/action/main.py index 9ede0112af..3dc76fb4de 100644 --- a/tryton/action/main.py +++ b/tryton/action/main.py @@ -54,23 +54,31 @@ class Action(object): @staticmethod def execute(act_id, data, action_type=None, context=None): - if not action_type: - res = False + def get_action_type(actions): try: - res, = RPCExecute('model', 'ir.action', 'read', [act_id], - ['type'], context=context) + action, = actions() except RPCException: return - if not res: - raise Exception('ActionNotFound') - action_type = res['type'] - try: - res, = RPCExecute('model', action_type, 'search_read', + action_type = action['type'] + exec_action(action_type) + + def exec_action(action_type): + def callback(actions): + try: + action, = actions() + except RPCException: + return + Action._exec_action(action, data) + + RPCExecute('model', action_type, 'search_read', [('action', '=', act_id)], 0, 1, None, None, - context=context) - except RPCException: - return - Action._exec_action(res, data) + context=context, callback=callback) + + if not action_type: + RPCExecute('model', 'ir.action', 'read', [act_id], + ['type'], context=context, callback=get_action_type) + else: + exec_action(action_type) @staticmethod def _exec_action(action, data=None, context=None): diff --git a/tryton/common/common.py b/tryton/common/common.py index 83fefe9e7a..9fe4e685b6 100644 --- a/tryton/common/common.py +++ b/tryton/common/common.py @@ -14,7 +14,6 @@ import unicodedata from functools import partial from tryton.config import CONFIG from tryton.config import TRYTON_ICON, PIXMAPS_DIR -import time import sys import xmlrpclib try: @@ -1276,42 +1275,59 @@ class RPCProgress(object): self.error = True self.res = False self.exception = exception - return True - if not self.res: - self.error = True + else: + if not self.res: + self.error = True + if self.callback: + # Post to GTK queue to be run by the main thread + gobject.idle_add(self.process) return True - def run(self, process_exception_p=True, main_iteration_p=True): - session = rpc._SESSION - thread.start_new_thread(self.start, ()) + def run(self, process_exception_p=True, callback=None): + self.process_exception_p = process_exception_p + self.callback = callback + self.session = rpc._SESSION + if self.parent.window: + watch = gtk.gdk.Cursor(gtk.gdk.WATCH) + self.parent.window.set_cursor(watch) - watch = None - i = 0 - while (not self.res) and (not self.error): - i += 1 - if i > 1: - watch = gtk.gdk.Cursor(gtk.gdk.WATCH) - self.parent.window.set_cursor(watch) - if main_iteration_p: - with gtk.gdk.lock: - while gtk.events_pending(): - gtk.main_iteration() - time.sleep(0.1) + if callback: + thread.start_new_thread(self.start, ()) + return + else: + self.start() + return self.process() + + def process(self): if self.parent.window: self.parent.window.set_cursor(None) if self.exception: - if process_exception_p: + if self.process_exception_p: def rpc_execute(*args): return RPCProgress('execute', - args).run(process_exception_p, main_iteration_p) + args).run(self.process_exception_p, self.callback) result = process_exception(self.exception, *self.args, - rpc_execute=rpc_execute, session=session) + rpc_execute=rpc_execute, session=self.session) + if self.callback: + return if result is False: - raise RPCException(self.exception) - return result + self.exception = RPCException(self.exception) + else: + self.exception = None + self.res = result else: + self.exception + + def return_(): + if self.exception: raise self.exception - return self.res + else: + return self.res + + if self.callback: + self.callback(return_) + else: + return return_() def RPCExecute(*args, **kwargs): @@ -1320,8 +1336,17 @@ def RPCExecute(*args, **kwargs): rpc_context.update(kwargs['context']) args = args + (rpc_context,) process_exception = kwargs.get('process_exception', True) - main_iteration = kwargs.get('main_iteration', True) - return RPCProgress('execute', args).run(process_exception, main_iteration) + callback = kwargs.get('callback') + return RPCProgress('execute', args).run(process_exception, callback) + + +def RPCContextReload(callback=None): + def update(context): + rpc.CONTEXT.clear() + rpc.CONTEXT.update(context()) + if callback: + callback() + RPCExecute('model', 'res.user', 'get_preferences', True, callback=update) class Tooltips(object): diff --git a/tryton/common/completion.py b/tryton/common/completion.py index 441d422fdd..307b68ecf9 100644 --- a/tryton/common/completion.py +++ b/tryton/common/completion.py @@ -39,20 +39,23 @@ def update_completion(entry, record, field, model, domain=None): domain = field.domain_get(record) context = field.context_get(record) domain = [('rec_name', 'ilike', '%' + search_text + '%'), domain] - try: - results = RPCExecute('model', model, 'search_read', domain, 0, - CONFIG['client.limit'], None, ['rec_name'], context=context, - process_exception=False) - except (TrytonError, TrytonServerError): - results = [] - if search_text != entry.get_text().decode('utf-8'): - return False - completion_model.clear() - for result in results: - completion_model.append([result['rec_name'], result['id']]) - completion_model.search_text = search_text - # Force display of popup - entry.emit('changed') + + def callback(results): + try: + results = results() + except (TrytonError, TrytonServerError): + results = [] + if search_text != entry.get_text().decode('utf-8'): + return False + completion_model.clear() + for result in results: + completion_model.append([result['rec_name'], result['id']]) + completion_model.search_text = search_text + # Force display of popup + entry.emit('changed') + RPCExecute('model', model, 'search_read', domain, 0, + CONFIG['client.limit'], None, ['rec_name'], context=context, + process_exception=False, callback=callback) return False search_text = entry.get_text().decode('utf-8') gobject.timeout_add(300, update, search_text, domain) diff --git a/tryton/common/popup_menu.py b/tryton/common/popup_menu.py index 4b4e743c1c..6743b7b0f1 100644 --- a/tryton/common/popup_menu.py +++ b/tryton/common/popup_menu.py @@ -23,10 +23,6 @@ def populate(menu, model, record, title=''): return elif record.id < 0: return - try: - toolbar = RPCExecute('model', model, 'view_toolbar_get') - except RPCException: - return def load(record): if isinstance(record, (int, long)): @@ -72,31 +68,37 @@ def populate(menu, model, record, title=''): 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) - + def set_toolbar(toolbar): + try: + toolbar = toolbar() + except RPCException: + return + 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() + RPCExecute('model', model, 'view_toolbar_get', callback=set_toolbar) menu.show_all() diff --git a/tryton/gui/main.py b/tryton/gui/main.py index d0106e7448..bb7997a434 100644 --- a/tryton/gui/main.py +++ b/tryton/gui/main.py @@ -16,7 +16,7 @@ import webbrowser import threading import tryton.rpc as rpc -from tryton.common import RPCExecute, RPCException +from tryton.common import RPCExecute, RPCException, RPCContextReload from tryton.config import CONFIG, TRYTON_ICON, get_config_dir import tryton.common as common from tryton.pyson import PYSONDecoder @@ -325,31 +325,35 @@ class Main(object): return False if getattr(gmodel, 'search_text', None) == search_text: return False - try: - result = RPCExecute('model', 'ir.model', 'global_search', + + def callback(result): + try: + result = result() + except RPCException: + result = [] + if search_text != widget.get_text().decode('utf-8'): + return False + gmodel.clear() + for r in result: + _, model, model_name, record_id, record_name, icon = r + if icon: + text = common.to_xml(record_name) + common.ICONFACTORY.register_icon(icon) + pixbuf = widget.render_icon(stock_id=icon, + size=gtk.ICON_SIZE_BUTTON, detail=None) + else: + text = '%s:\n %s' % ( + common.to_xml(model_name), + common.to_xml(record_name)) + pixbuf = None + gmodel.append([pixbuf, text, model, record_id]) + gmodel.search_text = search_text + # Force display of popup + widget.emit('changed') + + RPCExecute('model', 'ir.model', 'global_search', search_text, CONFIG['client.limit'], - self.menu_screen.model_name) - except RPCException: - result = [] - if search_text != widget.get_text().decode('utf-8'): - return False - gmodel.clear() - for r in result: - _, model, model_name, record_id, record_name, icon = r - if icon: - text = common.to_xml(record_name) - common.ICONFACTORY.register_icon(icon) - pixbuf = widget.render_icon(stock_id=icon, - size=gtk.ICON_SIZE_BUTTON, detail=None) - else: - text = '%s:\n %s' % ( - common.to_xml(model_name), - common.to_xml(record_name)) - pixbuf = None - gmodel.append([pixbuf, text, model, record_id]) - gmodel.search_text = search_text - # Force display of popup - widget.emit('changed') + self.menu_screen.model_name, callback=callback) return False def changed(widget): @@ -842,38 +846,43 @@ class Main(object): self.notebook.set_current_page(page - 1) def get_preferences(self): - rpc.context_reload() - try: - prefs = RPCExecute('model', 'res.user', 'get_preferences', - False) - except RPCException: - prefs = {} - threads = [] - for target in ( - common.ICONFACTORY.load_icons, - common.MODELACCESS.load_models, - common.VIEW_SEARCH.load_searches, - ): - t = threading.Thread(target=target) - threads.append(t) - t.start() - for t in threads: - t.join() - if prefs and 'language_direction' in prefs: - translate.set_language_direction(prefs['language_direction']) - CONFIG['client.language_direction'] = \ - prefs['language_direction'] - self.sig_win_menu(prefs=prefs) - for action_id in prefs.get('actions', []): - Action.execute(action_id, {}) - self.set_title(prefs.get('status_bar', '')) - if prefs and 'language' in prefs: - translate.setlang(prefs['language'], prefs.get('locale')) - if CONFIG['client.lang'] != prefs['language']: - self.set_menubar() - self.favorite_unset() - CONFIG['client.lang'] = prefs['language'] - CONFIG.save() + def _set_preferences(prefs): + try: + prefs = prefs() + except RPCException: + prefs = {} + threads = [] + for target in ( + common.ICONFACTORY.load_icons, + common.MODELACCESS.load_models, + common.VIEW_SEARCH.load_searches, + ): + t = threading.Thread(target=target) + threads.append(t) + t.start() + for t in threads: + t.join() + if prefs and 'language_direction' in prefs: + translate.set_language_direction(prefs['language_direction']) + CONFIG['client.language_direction'] = \ + prefs['language_direction'] + self.sig_win_menu(prefs=prefs) + for action_id in prefs.get('actions', []): + Action.execute(action_id, {}) + self.set_title(prefs.get('status_bar', '')) + if prefs and 'language' in prefs: + translate.setlang(prefs['language'], prefs.get('locale')) + if CONFIG['client.lang'] != prefs['language']: + self.set_menubar() + self.favorite_unset() + CONFIG['client.lang'] = prefs['language'] + CONFIG.save() + + def _get_preferences(): + RPCExecute('model', 'res.user', 'get_preferences', False, + callback=_set_preferences) + + RPCContextReload(_get_preferences) def sig_user_preferences(self, widget): if not self.close_pages(): diff --git a/tryton/gui/window/view_form/model/field.py b/tryton/gui/window/view_form/model/field.py index 82eaa32170..e3bc13a16a 100644 --- a/tryton/gui/window/view_form/model/field.py +++ b/tryton/gui/window/view_form/model/field.py @@ -410,7 +410,7 @@ class M2OField(CharField): if not rec_name and value >= 0: try: result, = RPCExecute('model', self.attrs['relation'], 'read', - [value], ['rec_name'], main_iteration=False) + [value], ['rec_name']) except RPCException: return False rec_name = result['rec_name'] or '' @@ -603,8 +603,7 @@ class O2MField(CharField): if field_names: try: fields.update(RPCExecute('model', self.attrs['relation'], - 'fields_get', list(field_names), - main_iteration=False, context=context)) + 'fields_get', list(field_names), context=context)) except RPCException: return @@ -681,8 +680,7 @@ class O2MField(CharField): if field_names: try: fields = RPCExecute('model', self.attrs['relation'], - 'fields_get', list(field_names), main_iteration=False, - context=context) + 'fields_get', list(field_names), context=context) except RPCException: return False else: @@ -855,7 +853,7 @@ class ReferenceField(CharField): if not rec_name and ref_id >= 0: try: result, = RPCExecute('model', ref_model, 'read', [ref_id], - ['rec_name'], main_iteration=False) + ['rec_name']) except RPCException: return rec_name = result['rec_name'] @@ -910,8 +908,7 @@ class BinaryField(CharField): context = record.context_get() try: values, = RPCExecute('model', record.model_name, 'read', - [record.id], [self.name], main_iteration=False, - context=context) + [record.id], [self.name], context=context) except RPCException: return '' _, filename = tempfile.mkstemp(prefix='tryton_') diff --git a/tryton/gui/window/view_form/model/group.py b/tryton/gui/window/view_form/model/group.py index 15861205c7..645de68219 100644 --- a/tryton/gui/window/view_form/model/group.py +++ b/tryton/gui/window/view_form/model/group.py @@ -203,7 +203,7 @@ class Group(SignalEvent, list): for fnct in self.on_write: try: res += RPCExecute('model', self.model_name, fnct, ids, - main_iteration=False, context=self.context) + context=self.context) except RPCException: return [] return list({}.fromkeys(res)) @@ -392,7 +392,7 @@ class Group(SignalEvent, list): if len(new) and len(to_add): try: values = RPCExecute('model', self.model_name, 'default_get', - to_add.keys(), main_iteration=False, context=self.context) + to_add.keys(), context=self.context) except RPCException: return False for record in new: diff --git a/tryton/gui/window/view_form/model/record.py b/tryton/gui/window/view_form/model/record.py index 208a1759c1..ad1914290c 100644 --- a/tryton/gui/window/view_form/model/record.py +++ b/tryton/gui/window/view_form/model/record.py @@ -118,8 +118,7 @@ class Record(SignalEvent): exception = None try: values = RPCExecute('model', self.model_name, 'read', - id2record.keys(), fnames, context=ctx, - main_iteration=False) + id2record.keys(), fnames, context=ctx) except RPCException, exception: values = [{'id': x} for x in id2record] default_values = dict((f, None) for f in fnames) @@ -286,7 +285,7 @@ class Record(SignalEvent): values = self._get_on_change_args(self.modified_fields) try: RPCExecute('model', self.model_name, 'pre_validate', values, - main_iteration=False, context=self.context_get()) + context=self.context_get()) except RPCException: return False return True @@ -297,8 +296,7 @@ class Record(SignalEvent): value = self.get() try: res, = RPCExecute('model', self.model_name, 'create', - [value], main_iteration=False, - context=self.context_get()) + [value], context=self.context_get()) except RPCException: return False old_id = self.id @@ -312,8 +310,7 @@ class Record(SignalEvent): context['_timestamp'] = self.get_timestamp() try: RPCExecute('model', self.model_name, 'write', - [self.id], value, main_iteration=False, - context=context) + [self.id], value, context=context) except RPCException: return False self._loaded.clear() @@ -346,7 +343,7 @@ class Record(SignalEvent): reload_ids = list(reload_ids) try: RPCExecute('model', record.model_name, 'delete', list(record_ids), - main_iteration=False, context=ctx) + context=ctx) except RPCException: return False if reload_ids: @@ -357,8 +354,7 @@ class Record(SignalEvent): if len(self.group.fields): try: vals = RPCExecute('model', self.model_name, 'default_get', - self.group.fields.keys(), main_iteration=False, - context=self.context_get()) + self.group.fields.keys(), context=self.context_get()) except RPCException: return if (self.parent @@ -380,8 +376,7 @@ class Record(SignalEvent): def rec_name(self): try: return RPCExecute('model', self.model_name, 'read', [self.id], - ['rec_name'], main_iteration=False, - context=self.context_get())[0]['rec_name'] + ['rec_name'], context=self.context_get())[0]['rec_name'] except RPCException: return '' @@ -532,8 +527,7 @@ class Record(SignalEvent): args = self._get_on_change_args(attr) try: res = RPCExecute('model', self.model_name, 'on_change_' + - fieldname, args, main_iteration=False, - context=self.context_get()) + fieldname, args, context=self.context_get()) except RPCException: return self.set_on_change(res) @@ -564,8 +558,7 @@ class Record(SignalEvent): if fieldnames: try: result = RPCExecute('model', self.model_name, 'on_change_with', - values, list(fieldnames), main_iteration=False, - context=self.context_get()) + values, list(fieldnames), context=self.context_get()) except RPCException: return self.set_on_change(result) @@ -576,7 +569,7 @@ class Record(SignalEvent): try: result = RPCExecute('model', self.model_name, 'on_change_with_' + fieldname, values, - main_iteration=False, context=self.context_get()) + context=self.context_get()) except RPCException: return self.group.fields[fieldname].set_on_change(self, result) @@ -594,8 +587,7 @@ class Record(SignalEvent): args = self._get_on_change_args(autocomplete) try: res = RPCExecute('model', self.model_name, 'autocomplete_' + - fieldname, args, main_iteration=False, - context=self.context_get()) + fieldname, args, context=self.context_get()) except RPCException: # ensure res is a list res = [] @@ -610,7 +602,7 @@ class Record(SignalEvent): 'search_count', [ ('resource', '=', '%s,%s' % (self.model_name, self.id)), - ], main_iteration=False) + ]) except RPCException: return 0 return self.attachment_count diff --git a/tryton/gui/window/wizard.py b/tryton/gui/window/wizard.py index c76664acf5..9a22c60216 100644 --- a/tryton/gui/window/wizard.py +++ b/tryton/gui/window/wizard.py @@ -3,17 +3,16 @@ import gtk import pango import gettext -import socket from tryton.signal_event import SignalEvent -import tryton.rpc as rpc import tryton.common as common from tryton.gui.window.view_form.screen import Screen from tryton.gui import Main -from tryton.exceptions import TrytonServerError, TrytonServerUnavailable +from tryton.exceptions import TrytonServerError from tryton.gui.window.nomodal import NoModal from tryton.common.button import Button -from tryton.common import RPCExecute, RPCException, TRYTON_ICON +from tryton.common import RPCExecute, RPCException, RPCContextReload +from tryton.common import TRYTON_ICON _ = gettext.gettext @@ -55,83 +54,85 @@ class Wizard(object): self.email_print = email_print self.email = email self.context = context - try: - result = RPCExecute('wizard', action, 'create') - except RPCException: - return - self.session_id, self.start_state, self.end_state = result - self.state = self.start_state - self.process() + + def callback(result): + try: + result = result() + except RPCException: + return + self.session_id, self.start_state, self.end_state = result + self.state = self.start_state + self.process() + RPCExecute('wizard', action, 'create', callback=callback) def process(self): from tryton.action import Action if self.__processing or self.__waiting_response: return - try: - self.__processing = True - while self.state != self.end_state: - ctx = self.context.copy() - ctx['active_id'] = self.id - ctx['active_ids'] = self.ids - ctx['active_model'] = self.model - ctx['action_id'] = self.action_id - if self.screen: - data = { - self.screen_state: self.screen.get_on_change_value(), - } - else: - data = {} - try: - result = RPCExecute('wizard', self.action, 'execute', - self.session_id, data, self.state, context=ctx) - except RPCException, rpc_exception: - if (not isinstance(rpc_exception.exception, - TrytonServerError) - or not self.screen): - self.state = self.end_state - break + self.__processing = True - if 'view' in result: - self.clean() - view = result['view'] - self.update(view['fields_view'], view['defaults'], - view['buttons']) - self.screen_state = view['state'] - self.__waiting_response = True - else: + ctx = self.context.copy() + ctx['active_id'] = self.id + ctx['active_ids'] = self.ids + ctx['active_model'] = self.model + ctx['action_id'] = self.action_id + if self.screen: + data = { + self.screen_state: self.screen.get_on_change_value(), + } + else: + data = {} + + def callback(result): + try: + result = result() + except RPCException, rpc_exception: + if (not isinstance(rpc_exception.exception, + TrytonServerError) + or not self.screen): self.state = self.end_state + self.end() + self.__processing = False + return - if 'actions' in result: - sensitive_widget = self.widget.get_toplevel() - if not 'view' in result: - self.end() - for action in result['actions']: - Action._exec_action(*action, context=ctx) - if (not 'view' in result - or (action[0]['type'] == 'ir.action.wizard' - and not sensitive_widget.props.sensitive)): - return - if self.__waiting_response: - break + if 'view' in result: + self.clean() + view = result['view'] + self.update(view['fields_view'], view['defaults'], + view['buttons']) + self.screen_state = view['state'] + self.__waiting_response = True + else: + self.state = self.end_state + + if 'actions' in result: + sensitive_widget = self.widget.get_toplevel() + if not 'view' in result: + self.end() + for action in result['actions']: + Action._exec_action(*action, context=ctx) + if (not 'view' in result + or (action[0]['type'] == 'ir.action.wizard' + and not sensitive_widget.props.sensitive)): + self.__processing = False + return if self.state == self.end_state: self.end() - finally: self.__processing = False + RPCExecute('wizard', self.action, 'execute', self.session_id, data, + self.state, context=ctx, callback=callback) + def destroy(self): if self.screen: self.screen.destroy() - def end(self): - try: - return RPCExecute('wizard', self.action, 'delete', self.session_id, - process_exception=False) - if self.action == 'ir.module.module.config_wizard': - rpc.context_reload() - Main.get_main().sig_win_menu() - except (TrytonServerError, socket.error, TrytonServerUnavailable): - pass + def end(self, callback=None): + RPCExecute('wizard', self.action, 'delete', self.session_id, + process_exception=False, callback=callback) + if self.action == 'ir.module.module.config_wizard': + RPCContextReload(Main.get_main().sig_win_menu) def clean(self): for widget in self.widget.get_children(): @@ -360,8 +361,9 @@ class WizardDialog(Wizard, NoModal): dialog.screen.client_action(action) def end(self): - action = super(WizardDialog, self).end() - self.destroy(action=action) + def callback(action): + self.destroy(action=action()) + super(WizardDialog, self).end(callback=callback) def close(self, widget, event=None): if self.end_state in self.states: diff --git a/tryton/rpc.py b/tryton/rpc.py index 555dd7b927..b0af1a44f4 100644 --- a/tryton/rpc.py +++ b/tryton/rpc.py @@ -130,16 +130,6 @@ def logout(): _KEYWORD_CACHE = {} -def context_reload(): - global CONTEXT - try: - context = execute('model', 'res.user', 'get_preferences', True, {}) - except Fault: - return - CONTEXT = {} - CONTEXT.update(context) - - def _execute(blocking, *args): global CONNECTION, _USER, _SESSION if CONNECTION is None: