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:
Cédric Krier committed 2014-03-04 11:45:38 +01:00
1 parent 4ca7d9b11a
commit c74d0ef859
4 files changed
+129 -149

No files matched your search

+72
View File
@@ -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