diff --git a/tryton/common/selection.py b/tryton/common/selection.py index 63811380e8..ddb5297a97 100644 --- a/tryton/common/selection.py +++ b/tryton/common/selection.py @@ -1,7 +1,10 @@ #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 operator +import math + import gtk +import gobject from tryton.common import RPCExecute, RPCException @@ -102,3 +105,72 @@ def selection_shortcuts(entry): widget.popup() entry.connect('key_press_event', key_press) return entry + + +class PopdownMixin(object): + + def set_popdown(self, selection, entry): + if not entry.child: # entry is destroyed + return + model, lengths = self.get_model(selection) + entry.set_model(model) + # GTK 2.24 and above use a ComboBox instead of a ComboBoxEntry + if hasattr(entry, 'set_text_column'): + entry.set_text_column(0) + else: + entry.set_entry_text_column(0) + completion = gtk.EntryCompletion() + completion.set_inline_selection(True) + completion.set_model(model) + entry.child.set_completion(completion) + if lengths: + pop = sorted(lengths, reverse=True) + average = sum(pop) / len(pop) + deviation = int(math.sqrt(sum((x - average) ** 2 for x in pop) / + len(pop))) + width = max(next((x for x in pop if x < (deviation * 4)), 10), 10) + else: + width = 10 + entry.child.set_width_chars(width) + if lengths: + entry.child.set_max_length(max(lengths)) + completion.set_text_column(0) + completion.connect('match-selected', self.match_selected, entry) + + def get_model(self, selection): + model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT) + lengths = [] + for (value, name) in selection: + name = str(name) + model.append((name, value)) + lengths.append(len(name)) + return model, lengths + + def match_selected(self, completion, model, iter_, entry): + value, = model.get(iter_, 1) + model = entry.get_model() + for i, values in enumerate(model): + if values[1] == value: + entry.set_active(i) + break + + def get_popdown_value(self, entry): + active = entry.get_active() + if active < 0: + return None + else: + model = entry.get_model() + return model[active][1] + + def set_popdown_value(self, entry, value): + active = -1 + model = entry.get_model() + for i, selection in enumerate(model): + if selection[1] == value: + active = i + break + else: + if value: + return False + entry.set_active(active) + return True diff --git a/tryton/gui/window/view_form/view/form_gtk/reference.py b/tryton/gui/window/view_form/view/form_gtk/reference.py index 8bb6f925ce..1a9381c7ee 100644 --- a/tryton/gui/window/view_form/view/form_gtk/reference.py +++ b/tryton/gui/window/view_form/view/form_gtk/reference.py @@ -4,8 +4,7 @@ import gtk import gettext from many2one import Many2One -from .selection import PopdownMixin -from tryton.common.selection import SelectionMixin +from tryton.common.selection import SelectionMixin, PopdownMixin _ = gettext.gettext @@ -32,9 +31,12 @@ class Reference(Many2One, SelectionMixin, PopdownMixin): return self.widget_combo.grab_focus() def get_model(self): - child = self.widget_combo.get_child() - res = child.get_text() - return self._selection.get(res, '') + active = self.widget_combo.get_active() + if active < 0: + return '' + else: + model = self.widget_combo.get_model() + return model[active][1] def _readonly_set(self, value): super(Reference, self)._readonly_set(value) @@ -116,15 +118,14 @@ class Reference(Many2One, SelectionMixin, PopdownMixin): else: model, value = None, None super(Reference, self).set_text(value) - child = self.widget_combo.get_child() - reverse_selection = dict((v, k) - for k, v in self._selection.iteritems()) - if model: - child.set_text(reverse_selection[model]) - child.set_position(len(reverse_selection[model])) - else: - child.set_text('') - child.set_position(0) + + active = -1 + combo_model = self.widget_combo.get_model() + for i, selection in enumerate(combo_model): + if selection[1] == model: + active = i + break + self.widget_combo.set_active(active) def display(self, record, field): self.update_selection(record, field) diff --git a/tryton/gui/window/view_form/view/form_gtk/selection.py b/tryton/gui/window/view_form/view/form_gtk/selection.py index 33dc37bf83..ff66e37659 100644 --- a/tryton/gui/window/view_form/view/form_gtk/selection.py +++ b/tryton/gui/window/view_form/view/form_gtk/selection.py @@ -1,51 +1,9 @@ #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 math from interface import WidgetInterface -from tryton.common.selection import SelectionMixin, selection_shortcuts - - -class PopdownMixin(object): - - def __init__(self, *args, **kwargs): - super(PopdownMixin, self).__init__() - self._selection = {} - - def set_popdown(self, selection, entry): - if not entry.child: # entry is destroyed - return - # Don't update popdown if not changed - if (list(x[0] for x in entry.get_model() or []) - == [x[1] for x in selection]): - return - model = gtk.ListStore(gobject.TYPE_STRING) - self._selection.clear() - for (value, name) in selection: - name = str(name) - self._selection[name] = value - model.append((name,)) - entry.set_model(model) - entry.set_text_column(0) - completion = gtk.EntryCompletion() - completion.set_inline_selection(True) - completion.set_model(model) - entry.child.set_completion(completion) - if self._selection: - pop = sorted((len(x) for x in self._selection), reverse=True) - average = sum(pop) / len(pop) - deviation = int(math.sqrt(sum((x - average) ** 2 for x in pop) / - len(pop))) - width = max(next((x for x in pop if x < (deviation * 4)), 10), 10) - else: - width = 10 - entry.child.set_width_chars(width) - if self._selection: - entry.child.set_max_length( - max(len(x) for x in self._selection)) - completion.set_text_column(0) - completion.connect('match-selected', lambda *a: self._focus_out()) +from tryton.common.selection import SelectionMixin, selection_shortcuts, \ + PopdownMixin class Selection(WidgetInterface, SelectionMixin, PopdownMixin): @@ -61,15 +19,12 @@ class Selection(WidgetInterface, SelectionMixin, PopdownMixin): child.set_width_chars(10) selection_shortcuts(self.entry) - child.connect('key_press_event', self.sig_key_press) - child.connect('activate', self.sig_activate) - child.connect_after('focus-out-event', self.sig_activate) + child.connect('activate', lambda *a: self._focus_out()) + child.connect_after('focus-out-event', lambda *a: self._focus_out()) child.connect('changed', self.send_modified) - self.entry.connect('notify::active', lambda *a: self._focus_out()) self.widget.pack_start(self.entry) self.widget.set_focus_chain([child]) - self._selection = {} self.selection = attrs.get('selection', [])[:] self.attrs = attrs self.init_selection() @@ -88,30 +43,7 @@ class Selection(WidgetInterface, SelectionMixin, PopdownMixin): def get_value(self): if not self.entry.child: # entry is destroyed return - text = self.entry.child.get_text() - value = None - if text: - for txt, val in self._selection.items(): - if not val: - continue - if txt[:len(text)].lower() == text.lower(): - value = val - if len(txt) == len(text): - break - if not value: - for val, txt in self.inactive_selection: - if txt == text: - value = val - break - return value - - def sig_key_press(self, widget, event): - self.send_modified() - - def sig_activate(self, widget, event=None): - if not self.field: - return - self.field.set_client(self.record, self.get_value()) + return self.get_popdown_value(self.entry) @property def modified(self): @@ -120,43 +52,23 @@ class Selection(WidgetInterface, SelectionMixin, PopdownMixin): return False def set_value(self, record, field): - field.set_client(record, self.get_value()) + value = self.get_value() + if 'relation' in self.attrs and value: + value = (value, self.entry.get_active_text()) + field.set_client(record, value) def display(self, record, field): self.update_selection(record, field) self.set_popdown(self.selection, self.entry) - child = self.entry.child if not field: - child.set_text('') - return False + self.entry.set_active(-1) + return super(Selection, self).display(record, field) value = field.get(record) if isinstance(value, (list, tuple)): # Compatibility with Many2One value = value[0] - if not value: - child.set_text('') - else: - found = False - for long_text, sel_value in self._selection.items(): - if str(sel_value) == str(value): - child.set_text(long_text) - found = True - break - if not found: - for sel_value, long_text in self.selection: - if str(sel_value) == str(value): - self._selection[long_text] = sel_value - model = self.entry.get_model() - i = model.append() - model.set(i, 0, long_text) - child.set_text(long_text) - found = True - break - if not found: - text = self.get_inactive_selection(value) - if text: - child.set_text(text) - found = True - if not found: - child.set_text('') + + if not self.set_popdown_value(self.entry, value): + self.get_inactive_selection(value) + self.set_popdown_value(self.entry, value) 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 71fd7f0351..fc637f715b 100644 --- a/tryton/gui/window/view_form/view/list_gtk/parser.py +++ b/tryton/gui/window/view_form/view/list_gtk/parser.py @@ -2,7 +2,6 @@ #this repository contains the full copyright notices and license terms. import os -import gobject import tempfile import gtk import locale @@ -34,7 +33,7 @@ from tryton.common.cellrendererclickablepixbuf import \ from tryton.translate import date_format from tryton.common import RPCExecute, RPCException from tryton.common.completion import get_completion, update_completion -from tryton.common.selection import SelectionMixin +from tryton.common.selection import SelectionMixin, PopdownMixin _ = gettext.gettext @@ -923,40 +922,26 @@ class M2M(O2M): context=context) -class Selection(Char, SelectionMixin): +class Selection(Char, SelectionMixin, PopdownMixin): def __init__(self, *args): super(Selection, self).__init__(*args) self.renderer = CellRendererCombo() self.renderer.connect('editing-started', self.editing_started) self.init_selection() - self.renderer.set_property('model', self.get_model(self.selection)) + self.renderer.set_property('model', self.get_model(self.selection)[0]) self.renderer.set_property('text-column', 0) - def get_model(self, selection): - model = gtk.ListStore(gobject.TYPE_STRING) - self._selection = {} - lst = [] - for (value, name) in selection: - name = str(name) - lst.append(name) - self._selection[name] = value - i = model.append() - model.set(i, 0, name) - return model - def get_textual_value(self, record): field = record[self.field_name] self.update_selection(record, field) - value = record[self.field_name].get(record) + value = field.get(record) text = dict(self.selection).get(value, '') if value and not text: text = self.get_inactive_selection(value) return text def value_from_text(self, record, text, callback=None): - field = record[self.field_name] - field.set_client(record, self._selection.get(text, False)) if callback: callback() @@ -965,17 +950,26 @@ class Selection(Char, SelectionMixin): store = self.treeview.get_model() record = store.get_value(store.get_iter(path), 0) field = record[self.field_name] + + set_value = lambda *a: self.set_value(editable, record, field) + editable.child.connect('activate', set_value) + editable.child.connect('focus-out-event', set_value) + editable.connect('changed', set_value) + self.update_selection(record, field) - model = self.get_model(self.selection) - editable.set_model(model) - # GTK 2.24 and above use a ComboBox instead of a ComboBoxEntry - if hasattr(editable, 'set_text_column'): - editable.set_text_column(0) - completion = gtk.EntryCompletion() - completion.set_inline_selection(True) - completion.set_model(model) - editable.get_child().set_completion(completion) - completion.set_text_column(0) + self.set_popdown(self.selection, editable) + + value = field.get(record) + if not self.set_popdown_value(editable, value): + self.get_inactive_selection(value) + self.set_popdown_value(editable, value) + return False + + def set_value(self, editable, record, field): + value = self.get_popdown_value(editable) + if 'relation' in self.attrs and value: + value = (value, editable.get_active_text()) + field.set_client(record, value) return False @@ -986,16 +980,17 @@ class Reference(Char, SelectionMixin): super(Reference, self).__init__(field_name, model_name, treeview, attrs=attrs, renderer=renderer) self.init_selection() - self._selection = dict(self.selection) def get_textual_value(self, record): - value = record[self.field_name].get_client(record) + field = record[self.field_name] + self.update_selection(record, field) + value = field.get_client(record) if not value: model, name = '', '' else: model, name = value if model: - return self._selection.get(model, model) + ',' + name + return dict(self.selection).get(model, model) + ',' + name else: return name