diff --git a/tryton/gui/window/view_form/model/field.py b/tryton/gui/window/view_form/model/field.py index 7e15b8a862..7ca1b8dc9b 100644 --- a/tryton/gui/window/view_form/model/field.py +++ b/tryton/gui/window/view_form/model/field.py @@ -336,6 +336,8 @@ class O2MField(CharField): result.append(('create', model2.get(check_load=check_load, get_readonly=readonly))) for rm_id in model.value[self.name].model_removed: + result.append(('remove', rm_id)) + for rm_id in model.value[self.name].model_unlinked: result.append(('unlink', rm_id)) return result diff --git a/tryton/gui/window/view_form/model/group.py b/tryton/gui/window/view_form/model/group.py index fc51f6d316..e9657a2d8a 100644 --- a/tryton/gui/window/view_form/model/group.py +++ b/tryton/gui/window/view_form/model/group.py @@ -87,6 +87,7 @@ class ModelRecordGroup(SignalEvent): self.current_idx = None self.load(ids) self.model_removed = [] + self.model_unlinked = [] self.on_write = '' self.readonly = readonly @@ -138,7 +139,7 @@ class ModelRecordGroup(SignalEvent): self.models.lock_signal = False self.signal('record-cleared') - def load(self, ids, display=True): + def load(self, ids, display=True, modified=False): if not ids: return True @@ -150,11 +151,17 @@ class ModelRecordGroup(SignalEvent): if len(ids) > 10: self.models.lock_signal = True newmod = None + newmods = [] for id in ids: newmod = ModelRecord(self.resource, id, self.window, parent=self.parent, group=self) self.models.append(newmod) + newmods.append(newmod) newmod.signal_connect(self, 'record-changed', self._record_changed) + if id in self.model_removed: + del self.model_removed[id] + if id in self.model_unlinked: + del self.model_unlinked[id] if len(ids) > 10: self.models.lock_signal = False self.signal('record-cleared') @@ -173,12 +180,20 @@ class ModelRecordGroup(SignalEvent): if newmod and display: self.signal('model-changed', newmod) + + if modified: + for newmod in newmods: + newmod.modified = True + if newmod.parent: + newmod.signal('record-changed', newmod.parent) + self.current_idx = 0 return True def clear(self): self.models.clear() self.model_removed = [] + self.model_unlinked = [] def _get_context(self): ctx = {} @@ -267,10 +282,13 @@ class ModelRecordGroup(SignalEvent): return None return self.models[self.current_idx] - def remove(self, model): + def remove(self, model, remove=False): idx = self.models.index(model) if self.models[idx].id: - self.model_removed.append(self.models[idx].id) + if remove: + self.model_removed.append(self.models[idx].id) + else: + self.model_unlinked.append(self.models[idx].id) if model.parent: model.parent.modified = True self.models.remove(self.models[idx]) diff --git a/tryton/gui/window/view_form/screen/screen.py b/tryton/gui/window/view_form/screen/screen.py index d3ffcf73ae..e5f75181b8 100644 --- a/tryton/gui/window/view_form/screen/screen.py +++ b/tryton/gui/window/view_form/screen/screen.py @@ -453,7 +453,7 @@ class Screen(SignalEvent): self.parent.reload() self.display() - def remove(self, unlink=False): + def remove(self, unlink=False, remove=False): res = False reload_ids = [] if self.current_view.view_type == 'form' and self.current_model: @@ -470,7 +470,7 @@ class Screen(SignalEvent): return False self.current_view.set_cursor() idx = self.models.models.index(self.current_model) - self.models.remove(self.current_model) + self.models.remove(self.current_model, remove=remove) if self.models.models: idx = min(idx, len(self.models.models)-1) self.current_model = self.models.models[idx] @@ -497,7 +497,7 @@ class Screen(SignalEvent): idx = self.models.models.index( self.current_view.sel_models_get()[0]) for model in self.current_view.sel_models_get(): - self.models.remove(model) + self.models.remove(model, remove=remove) if self.models.models: idx = min(idx, len(self.models.models)-1) self.current_model = self.models.models[idx] @@ -510,8 +510,8 @@ class Screen(SignalEvent): res = ids return res - def load(self, ids, set_cursor=True): - self.models.load(ids, display=False) + def load(self, ids, set_cursor=True, modified=False): + self.models.load(ids, display=False, modified=modified) self.current_view.reset() if set_cursor: self.current_view.set_cursor() diff --git a/tryton/gui/window/view_form/view/form_gtk/one2many.py b/tryton/gui/window/view_form/view/form_gtk/one2many.py index c3d91eb8e1..dbbcf94c2a 100644 --- a/tryton/gui/window/view_form/view/form_gtk/one2many.py +++ b/tryton/gui/window/view_form/view/form_gtk/one2many.py @@ -5,10 +5,14 @@ from tryton.common import TRYTON_ICON from interface import WidgetInterface from tryton.gui.window.view_form.screen import Screen from tryton.gui.window.view_form.model.group import ModelRecordGroup +from tryton.gui.window.win_search import WinSearch +from tryton.gui.window.view_form.widget_search.form import _LIMIT +import tryton.common as common +import tryton.rpc as rpc _ = gettext.gettext -def _create_menu(self): +def _create_menu(self, attrs): hbox = gtk.HBox(homogeneous=False, spacing=5) menubar = gtk.MenuBar() if hasattr(menubar, 'set_pack_direction') and \ @@ -34,6 +38,35 @@ def _create_menu(self): tooltips = gtk.Tooltips() + if attrs.get('add_remove'): + + self.wid_text = gtk.Entry() + self.wid_text.set_property('width_chars', 13) + self.wid_text.connect('activate', self._sig_activate) + hbox.pack_start(self.wid_text, expand=True, fill=True) + + self.eb_add = gtk.EventBox() + tooltips.set_tip(self.eb_add, _('Add')) + self.eb_add.set_events(gtk.gdk.BUTTON_PRESS) + self.eb_add.connect('button_press_event', self._sig_add) + img_add = gtk.Image() + img_add.set_from_stock('tryton-list-add', gtk.ICON_SIZE_BUTTON) + img_add.set_alignment(0.5, 0.5) + self.eb_add.add(img_add) + hbox.pack_start(self.eb_add, expand=False, fill=False) + + self.eb_remove = gtk.EventBox() + tooltips.set_tip(self.eb_remove, _('Remove')) + self.eb_remove.set_events(gtk.gdk.BUTTON_PRESS) + self.eb_remove.connect('button_press_event', self._sig_remove, True) + img_remove = gtk.Image() + img_remove.set_from_stock('tryton-list-remove', gtk.ICON_SIZE_BUTTON) + img_remove.set_alignment(0.5, 0.5) + self.eb_remove.add(img_remove) + hbox.pack_start(self.eb_remove, expand=False, fill=False) + + hbox.pack_start(gtk.VSeparator(), expand=False, fill=True) + self.eb_new = gtk.EventBox() tooltips.set_tip(self.eb_new, _('Create a new entry')) self.eb_new.set_events(gtk.gdk.BUTTON_PRESS) @@ -45,7 +78,7 @@ def _create_menu(self): hbox.pack_start(self.eb_new, expand=False, fill=False) self.eb_open = gtk.EventBox() - tooltips.set_tip(self.eb_open, _('Edit this entry')) + tooltips.set_tip(self.eb_open, _('Edit selected entry')) self.eb_open.set_events(gtk.gdk.BUTTON_PRESS) self.eb_open.connect('button_press_event', self._sig_edit) img_open = gtk.Image() @@ -55,7 +88,7 @@ def _create_menu(self): hbox.pack_start(self.eb_open, expand=False, fill=False) self.eb_del = gtk.EventBox() - tooltips.set_tip(self.eb_del, _('Remove this entry')) + tooltips.set_tip(self.eb_del, _('Delete selected entry')) self.eb_del.set_events(gtk.gdk.BUTTON_PRESS) self.eb_del.connect('button_press_event', self._sig_remove) img_del = gtk.Image() @@ -152,7 +185,7 @@ class Dialog(object): menuitem_title = None if isinstance(model, ModelRecordGroup): - hbox, menuitem_title = _create_menu(self) + hbox, menuitem_title = _create_menu(self, attrs) self.dia.vbox.pack_start(hbox, expand=False, fill=True) scroll = gtk.ScrolledWindow() @@ -211,6 +244,10 @@ class Dialog(object): self.screen.current_view.set_cursor() def on_keypress(self, widget, event): + if self.attrs.get('add_remove') \ + and event.keyval == gtk.keysyms.F3: + self._sig_add() + return False if (event.keyval in (gtk.keysyms.N, gtk.keysyms.n) \ and event.state & gtk.gdk.CONTROL_MASK) \ or event.keyval == gtk.keysyms.F3: @@ -264,12 +301,42 @@ class Dialog(object): if event.type == gtk.gdk.BUTTON_PRESS: self.screen.display_prev() - def _sig_remove(self, widget, event): + def _sig_remove(self, widget, event, remove=False): if event.type == gtk.gdk.BUTTON_PRESS: - self.screen.remove() + self.screen.remove(remove=remove) if not self.screen.models.models: self.screen.current_view.widget.set_sensitive(False) + def _sig_activate(self, *args): + self._sig_add() + self.wid_text.grab_focus() + + def _sig_add(self, *args): + domain = [] + + try: + ids = rpc.execute('object', 'execute', self.attrs['relation'], + 'name_search', self.wid_text.get_text(), domain, 'ilike', + context, _LIMIT) + except Exception, exception: + common.process_exception(exception, self._window) + return False + ids = [x[0] for x in ids] + if len(ids) != 1: + win = WinSearch(self.attrs['relation'], sel_multi=True, ids=ids, + context=context, domain=domain, parent=self._window, + views_preload=self.attrs.get('views', {})) + ids = win.run() + + res_id = None + if ids: + res_id = ids[0] + self.screen.load(ids, modified=True) + self.screen.display(res_id=res_id) + if self.screen.current_view: + self.screen.current_view.set_cursor() + self.wid_text.set_text('') + def _sig_label(self, screen, signal_data): name = '_' if signal_data[0] >= 0: @@ -320,7 +387,7 @@ class One2Many(WidgetInterface): self.widget = gtk.VBox(homogeneous=False, spacing=5) - hbox, menuitem_title = _create_menu(self) + hbox, menuitem_title = _create_menu(self, attrs) self.widget.pack_start(hbox, expand=False, fill=True) @@ -346,6 +413,10 @@ class One2Many(WidgetInterface): return self.screen.widget.grab_focus() def on_keypress(self, widget, event): + if self.attrs.get('add_remove') \ + and event.keyval == gtk.keysyms.F3: + self._sig_add() + return False if (event.keyval == gtk.keysyms.N \ and event.state & gtk.gdk.CONTROL_MASK \ and event.state & gtk.gdk.SHIFT_MASK) \ @@ -368,6 +439,10 @@ class One2Many(WidgetInterface): def _readonly_set(self, value): self.eb_new.set_sensitive(not value) self.eb_del.set_sensitive(not value) + if self.attrs.get('add_remove'): + self.wid_text.set_sensitive(not value) + self.eb_add.set_sensitive(not value) + self.eb_remove.set_sensitive(not value) def _sig_new(self, widget, event): ctx = self._view.model.expr_eval(self.screen.default_get) @@ -426,12 +501,46 @@ class One2Many(WidgetInterface): if event.type == gtk.gdk.BUTTON_PRESS: self.screen.display_prev() - def _sig_remove(self, widget, event): + def _sig_remove(self, widget, event, remove=False): if event.type == gtk.gdk.BUTTON_PRESS: - self.screen.remove() + self.screen.remove(remove=remove) if not self.screen.models.models: self.screen.current_view.widget.set_sensitive(False) + def _sig_activate(self, *args): + self._sig_add() + self.wid_text.grab_focus() + + def _sig_add(self, *args): + domain = self._view.modelfield.domain_get(self._view.model) + context = self._view.modelfield.context_get(self._view.model) + domain = domain[:] + domain.extend(self._view.model.expr_eval(self.attrs.get('add_remove'), + context)) + + try: + ids = rpc.execute('object', 'execute', self.attrs['relation'], + 'name_search', self.wid_text.get_text(), domain, 'ilike', + context, _LIMIT) + except Exception, exception: + common.process_exception(exception, self._window) + return False + ids = [x[0] for x in ids] + if len(ids) != 1: + win = WinSearch(self.attrs['relation'], sel_multi=True, ids=ids, + context=context, domain=domain, parent=self._window, + views_preload=self.attrs.get('views', {})) + ids = win.run() + + res_id = None + if ids: + res_id = ids[0] + self.screen.load(ids, modified=True) + self.screen.display(res_id=res_id) + if self.screen.current_view: + self.screen.current_view.set_cursor() + self.wid_text.set_text('') + def _sig_label(self, screen, signal_data): name = '_' if signal_data[0] >= 0: