Always use internal value to match selection
The widget must only rely on the active element instead of searching on the string values which could not be unique. issue3698 review3711002
This commit is contained in:
1 parent
4ca7d9b11a
commit
c74d0ef859
4 files changed
+129
-149
No files matched your search
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user