diff --git a/CHANGELOG b/CHANGELOG
index 15d66736dd..7ed50bb905 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,4 @@
+* Add completion on Many2One, Many2Many and One2Many
* Add bookmark on search widget
* Manage domains on Action Window
* Use range for Date/Time fields in filter box
diff --git a/tryton/common/cellrenderertext.py b/tryton/common/cellrenderertext.py
index a704a5aab0..743221eb14 100644
--- a/tryton/common/cellrenderertext.py
+++ b/tryton/common/cellrenderertext.py
@@ -113,4 +113,20 @@ class CellRendererText(gtk.GenericCellRenderer):
editable.show()
return editable
+
+class CellRendererTextCompletion(CellRendererText):
+
+ def __init__(self, set_completion):
+ super(CellRendererTextCompletion, self).__init__()
+ self.set_completion = set_completion
+
+ def on_start_editing(self, event, widget, path, background_area, cell_area,
+ flags):
+ editable = super(CellRendererTextCompletion,
+ self).on_start_editing(event, widget, path, background_area,
+ cell_area, flags)
+ self.set_completion(editable, path)
+ return editable
+
gobject.type_register(CellRendererText)
+gobject.type_register(CellRendererTextCompletion)
diff --git a/tryton/common/completion.py b/tryton/common/completion.py
new file mode 100644
index 0000000000..2f5b3d702b
--- /dev/null
+++ b/tryton/common/completion.py
@@ -0,0 +1,59 @@
+#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 gettext
+import gobject
+
+from tryton.config import CONFIG
+from tryton.common import RPCExecute
+from tryton.exceptions import TrytonServerError, TrytonError
+
+_ = gettext.gettext
+
+
+def get_completion():
+ "Return a EntryCompletion"
+ completion = gtk.EntryCompletion()
+ completion.set_match_func(lambda *a: True)
+ completion.set_model(gtk.ListStore(str, int))
+ completion.set_text_column(0)
+ if hasattr(completion.props, 'popup_set_width'):
+ completion.props.popup_set_width = False
+ completion.insert_action_markup(0, _('Search...'))
+ completion.insert_action_markup(1, _('Create...'))
+ return completion
+
+
+def update_completion(entry, record, field, model, domain=None):
+ "Update entry completion"
+ def update(search_text, domain):
+ if search_text != entry.get_text().decode('utf-8'):
+ return False
+ completion_model = entry.get_completion().get_model()
+ if not search_text or not model:
+ completion_model.clear()
+ completion_model.search_text = search_text
+ return False
+ if getattr(completion_model, 'search_text', None) == search_text:
+ return False
+ if domain is 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')
+ return False
+ search_text = entry.get_text().decode('utf-8')
+ gobject.timeout_add(300, update, search_text, domain)
diff --git a/tryton/gui/window/view_form/view/form.py b/tryton/gui/window/view_form/view/form.py
index c6653491ec..835d396531 100644
--- a/tryton/gui/window/view_form/view/form.py
+++ b/tryton/gui/window/view_form/view/form.py
@@ -4,7 +4,6 @@ import operator
from functools import reduce
import gtk
import gettext
-import gobject
from interface import ParserView
_ = gettext.gettext
@@ -21,7 +20,6 @@ class ViewForm(ParserView):
for widget in self.state_widgets:
if isinstance(widget, gtk.Button):
widget.connect('clicked', self.button_clicked)
- widget.set_focus_on_click(False)
# Force to display the first time it switches on a page
# This avoids glitch in position of widgets
@@ -42,7 +40,6 @@ class ViewForm(ParserView):
vbox = gtk.VBox()
vp = gtk.Viewport()
vp.set_shadow_type(gtk.SHADOW_NONE)
- vp.connect('leave-notify-event', self.leave)
vp.add(self.widget)
scroll = gtk.ScrolledWindow()
scroll.add(vp)
@@ -171,10 +168,6 @@ class ViewForm(ParserView):
notebook.set_current_page(i)
focus_widget.grab_focus()
- def leave(self, widget, event):
- # leave could be called during event process
- gobject.idle_add(self.set_value, True)
-
def button_clicked(self, widget):
record = self.screen.current_record
fields = self.get_fields()
diff --git a/tryton/gui/window/view_form/view/form_gtk/binary.py b/tryton/gui/window/view_form/view/form_gtk/binary.py
index 66df1bc664..d9d1593d98 100644
--- a/tryton/gui/window/view_form/view/form_gtk/binary.py
+++ b/tryton/gui/window/view_form/view/form_gtk/binary.py
@@ -46,7 +46,6 @@ class Binary(WidgetInterface):
self.but_new.set_image(img_new)
self.but_new.set_relief(gtk.RELIEF_NONE)
self.but_new.connect('clicked', self.sig_new)
- self.but_new.connect('enter-notify-event', self.enter)
self.tooltips.set_tip(self.but_new, _('Select a File...'))
self.widget.pack_start(self.but_new, expand=False, fill=False)
@@ -57,7 +56,6 @@ class Binary(WidgetInterface):
self.but_open.set_image(img_open)
self.but_open.set_relief(gtk.RELIEF_NONE)
self.but_open.connect('clicked', self.sig_open)
- self.but_open.connect('enter-notify-event', self.enter)
self.tooltips.set_tip(self.but_open, _('Open...'))
self.widget.pack_start(self.but_open, expand=False, fill=False)
else:
@@ -70,7 +68,6 @@ class Binary(WidgetInterface):
self.but_save_as.set_image(img_save_as)
self.but_save_as.set_relief(gtk.RELIEF_NONE)
self.but_save_as.connect('clicked', self.sig_save_as)
- self.but_save_as.connect('enter-notify-event', self.enter)
self.tooltips.set_tip(self.but_save_as, _('Save As...'))
self.widget.pack_start(self.but_save_as, expand=False, fill=False)
@@ -80,7 +77,6 @@ class Binary(WidgetInterface):
self.but_remove.set_image(img_remove)
self.but_remove.set_relief(gtk.RELIEF_NONE)
self.but_remove.connect('clicked', self.sig_remove)
- self.but_remove.connect('enter-notify-event', self.enter)
self.tooltips.set_tip(self.but_remove, _('Clear'))
self.widget.pack_start(self.but_remove, expand=False, fill=False)
diff --git a/tryton/gui/window/view_form/view/form_gtk/image.py b/tryton/gui/window/view_form/view/form_gtk/image.py
index 7c1f8a2053..dc30c039b9 100644
--- a/tryton/gui/window/view_form/view/form_gtk/image.py
+++ b/tryton/gui/window/view_form/view/form_gtk/image.py
@@ -48,7 +48,6 @@ class Image(WidgetInterface):
self.but_add.set_image(img_add)
self.but_add.set_relief(gtk.RELIEF_NONE)
self.but_add.connect('clicked', self.sig_add)
- self.but_add.connect('enter-notify-event', self.enter)
self.tooltips.set_tip(self.but_add, _('Select an Image...'))
hbox.pack_start(self.but_add, expand=False, fill=False)
@@ -60,7 +59,6 @@ class Image(WidgetInterface):
self.but_open.set_image(img_open)
self.but_open.set_relief(gtk.RELIEF_NONE)
self.but_open.connect('clicked', self.sig_open)
- self.but_open.connect('enter-notify-event', self.enter)
self.tooltips.set_tip(self.but_open, _('Open...'))
hbox.pack_start(self.but_open, expand=False, fill=False)
else:
@@ -73,7 +71,6 @@ class Image(WidgetInterface):
self.but_save_as.set_image(img_save_as)
self.but_save_as.set_relief(gtk.RELIEF_NONE)
self.but_save_as.connect('clicked', self.sig_save_as)
- self.but_save_as.connect('enter-notify-event', self.enter)
self.tooltips.set_tip(self.but_save_as, _('Save As...'))
hbox.pack_start(self.but_save_as, expand=False, fill=False)
@@ -84,7 +81,6 @@ class Image(WidgetInterface):
self.but_remove.set_image(img_remove)
self.but_remove.set_relief(gtk.RELIEF_NONE)
self.but_remove.connect('clicked', self.sig_remove)
- self.but_remove.connect('enter-notify-event', self.enter)
self.tooltips.set_tip(self.but_remove, _('Clear'))
hbox.pack_start(self.but_remove, expand=False, fill=False)
diff --git a/tryton/gui/window/view_form/view/form_gtk/interface.py b/tryton/gui/window/view_form/view/form_gtk/interface.py
index b8f158d1c1..70a7191b9e 100644
--- a/tryton/gui/window/view_form/view/form_gtk/interface.py
+++ b/tryton/gui/window/view_form/view/form_gtk/interface.py
@@ -164,10 +164,6 @@ class WidgetInterface(object):
def cancel(self):
pass
- def enter(self, widget, event):
- if self.view:
- self.view.set_value(focused_widget=True)
-
class TranslateDialog(NoModal):
@@ -299,7 +295,6 @@ class TranslateMixin:
button.set_image(img)
button.set_relief(gtk.RELIEF_NONE)
button.connect('clicked', self.translate)
- button.connect('enter-notify-event', self.enter)
return button
def translate(self, widget):
diff --git a/tryton/gui/window/view_form/view/form_gtk/many2many.py b/tryton/gui/window/view_form/view/form_gtk/many2many.py
index 89c6eac132..81d9929b91 100644
--- a/tryton/gui/window/view_form/view/form_gtk/many2many.py
+++ b/tryton/gui/window/view_form/view/form_gtk/many2many.py
@@ -1,6 +1,8 @@
#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
+
from tryton.gui.window.view_form.screen import Screen
from interface import WidgetInterface
from tryton.gui.window.win_search import WinSearch
@@ -10,6 +12,7 @@ import tryton.common as common
import gettext
from tryton.common import RPCExecute, RPCException
from tryton.common.placeholder_entry import PlaceholderEntry
+from tryton.common.completion import get_completion, update_completion
_ = gettext.gettext
@@ -37,15 +40,24 @@ class Many2Many(WidgetInterface):
self.wid_text = PlaceholderEntry()
self.wid_text.set_placeholder_text(_('Search'))
self.wid_text.set_property('width_chars', 13)
- self.wid_text.connect('activate', self._sig_activate)
- self.wid_text.connect('focus-out-event', self._focus_out)
+ self.wid_text.connect('focus-out-event', lambda *a: self._focus_out())
self.focus_out = True
hbox.pack_start(self.wid_text, expand=True, fill=True)
+ if int(self.attrs.get('completion', 1)):
+ self.wid_completion = get_completion()
+ self.wid_completion.connect('match-selected',
+ self._completion_match_selected)
+ self.wid_completion.connect('action-activated',
+ self._completion_action_activated)
+ self.wid_text.set_completion(self.wid_completion)
+ self.wid_text.connect('changed', self._update_completion)
+ else:
+ self.wid_completion = None
+
self.but_add = gtk.Button()
tooltips.set_tip(self.but_add, _('Add existing record'))
self.but_add.connect('clicked', self._sig_add)
- self.but_add.connect('enter-notify-event', self.enter)
img_add = gtk.Image()
img_add.set_from_stock('tryton-list-add',
gtk.ICON_SIZE_SMALL_TOOLBAR)
@@ -57,7 +69,6 @@ class Many2Many(WidgetInterface):
self.but_remove = gtk.Button()
tooltips.set_tip(self.but_remove, _('Remove selected record '))
self.but_remove.connect('clicked', self._sig_remove)
- self.but_remove.connect('enter-notify-event', self.enter)
img_remove = gtk.Image()
img_remove.set_from_stock('tryton-list-remove',
gtk.ICON_SIZE_SMALL_TOOLBAR)
@@ -95,6 +106,10 @@ class Many2Many(WidgetInterface):
return self.wid_text.grab_focus()
def on_keypress(self, widget, event):
+ editable = self.wid_text.get_editable()
+ activate_keys = [gtk.keysyms.Tab, gtk.keysyms.ISO_Left_Tab]
+ if not self.wid_completion:
+ activate_keys.append(gtk.keysyms.Return)
if event.keyval == gtk.keysyms.F3:
self._sig_add()
return False
@@ -105,6 +120,13 @@ class Many2Many(WidgetInterface):
and widget == self.screen.widget:
self._sig_remove()
return False
+ if (widget == self.wid_text
+ and event.keyval in activate_keys
+ and editable
+ and self.wid_text.get_text()):
+ self._sig_add()
+ self.wid_text.grab_focus()
+ return False
def destroy(self):
self.screen.destroy()
@@ -120,11 +142,7 @@ class Many2Many(WidgetInterface):
widget.modify_text(gtk.STATE_INSENSITIVE,
self.colors['text_color_insensitive'])
- def _focus_out(self, *args):
- if self.wid_text.get_text():
- self._sig_add()
-
- def _sig_add(self, *args):
+ def _sig_add(self, *args, **kwargs):
if not self.focus_out:
return
domain = self.field.domain_get(self.record)
@@ -151,7 +169,7 @@ class Many2Many(WidgetInterface):
self.screen.display(res_id=ids[0])
self.screen.set_cursor()
self.wid_text.set_text('')
- if len(ids) != 1 or not value:
+ if len(ids) != 1 or not value or kwargs.get('win_search', False):
WinSearch(self.attrs['relation'], callback, sel_multi=True,
ids=ids, context=context, domain=domain,
view_ids=self.attrs.get('view_ids', '').split(','),
@@ -163,10 +181,6 @@ class Many2Many(WidgetInterface):
def _sig_remove(self, *args):
self.screen.remove(remove=True)
- def _sig_activate(self, *args):
- self._sig_add()
- self.wid_text.grab_focus()
-
def _on_activate(self):
self._sig_edit()
@@ -222,3 +236,44 @@ class Many2Many(WidgetInterface):
self.screen.save_tree_state()
self.screen.current_view.set_value()
return True
+
+ def _completion_match_selected(self, completion, model, iter_):
+ record_id, = model.get(iter_, 1)
+ self.screen.load([record_id], modified=True)
+ self.wid_text.set_text('')
+ self.wid_text.grab_focus()
+
+ completion_model = self.wid_completion.get_model()
+ completion_model.clear()
+ completion_model.search_text = self.wid_text.get_text()
+ return True
+
+ def _update_completion(self, widget):
+ if self._readonly:
+ return
+ if not self.record:
+ return
+ model = self.attrs['relation']
+ update_completion(self.wid_text, self.record, self.field, model)
+
+ def _completion_action_activated(self, completion, index):
+ if index == 0:
+ self._sig_add(win_search=True)
+ self.wid_text.grab_focus()
+ elif index == 1:
+ model = self.attrs['relation']
+ domain = self.field.domain_get(self.record)
+ context = self.field.context_get(self.record)
+
+ screen = Screen(model, domain, context=context, mode=['form'])
+
+ def callback(result):
+ self.focus_out = True
+ if result:
+ record = screen.current_record
+ self.screen.load([record.id], modified=True)
+ self.wid_text.set_text('')
+ self.wid_text.grab_focus()
+
+ self.focus_out = False
+ WinForm(screen, callback, new=True, save_current=True)
diff --git a/tryton/gui/window/view_form/view/form_gtk/many2one.py b/tryton/gui/window/view_form/view/form_gtk/many2one.py
index a5fc0d20c3..774cc3b258 100644
--- a/tryton/gui/window/view_form/view/form_gtk/many2one.py
+++ b/tryton/gui/window/view_form/view/form_gtk/many2one.py
@@ -11,6 +11,7 @@ from tryton.gui.window.win_form import WinForm
from tryton.config import CONFIG
from tryton.common.popup_menu import populate
from tryton.common import RPCExecute, RPCException
+from tryton.common.completion import get_completion, update_completion
_ = gettext.gettext
@@ -34,17 +35,25 @@ class Many2One(WidgetInterface):
lambda x, y: self._focus_out())
self.wid_text.connect_after('changed', self.sig_changed)
self.changed = True
- self.wid_text.connect('activate', self.sig_activate)
- self.wid_text.connect_after('focus-out-event', self.sig_activate)
self.focus_out = True
+ if int(self.attrs.get('completion', 1)):
+ self.wid_completion = get_completion()
+ self.wid_completion.connect('match-selected',
+ self._completion_match_selected)
+ self.wid_completion.connect('action-activated',
+ self._completion_action_activated)
+ self.wid_text.set_completion(self.wid_completion)
+ self.wid_text.connect('changed', self._update_completion)
+ else:
+ self.wid_completion = None
+
self.but_open = gtk.Button()
img_find = gtk.Image()
img_find.set_from_stock('tryton-find', gtk.ICON_SIZE_SMALL_TOOLBAR)
self.but_open.set_image(img_find)
self.but_open.set_relief(gtk.RELIEF_NONE)
self.but_open.connect('clicked', self.sig_edit)
- self.but_open.connect('enter-notify-event', self.enter)
self.but_open.set_alignment(0.5, 0.5)
self.but_new = gtk.Button()
@@ -53,7 +62,6 @@ class Many2One(WidgetInterface):
self.but_new.set_image(img_new)
self.but_new.set_relief(gtk.RELIEF_NONE)
self.but_new.connect('clicked', self.sig_new)
- self.but_new.connect('enter-notify-event', self.enter)
self.but_new.set_alignment(0.5, 0.5)
self.widget.pack_end(self.but_new, expand=False, fill=False)
@@ -124,7 +132,7 @@ class Many2One(WidgetInterface):
def id_from_value(value):
return value
- def sig_activate(self, widget=None, event=None, key_press=False):
+ def sig_activate(self):
model = self.get_model()
if not model or not common.MODELACCESS[model]['read']:
return
@@ -136,13 +144,10 @@ class Many2One(WidgetInterface):
self.focus_out = False
if model and not self.has_target(value):
- if not key_press and not event and widget:
- widget.emit_stop_by_name('activate')
if (not self._readonly
and (self.wid_text.get_text()
- or (self.field.get_state_attrs(
- self.record)['required'])
- and key_press)):
+ or self.field.get_state_attrs(
+ self.record)['required'])):
domain = self.field.domain_get(self.record)
context = self.field.context_get(self.record)
self.wid_text.grab_focus()
@@ -208,7 +213,7 @@ class Many2One(WidgetInterface):
self.focus_out = True
WinForm(screen, callback, new=True, save_current=True)
- def sig_edit(self, widget):
+ def sig_edit(self, *args):
model = self.get_model()
if not model or not common.MODELACCESS[model]['read']:
return
@@ -275,6 +280,9 @@ class Many2One(WidgetInterface):
def sig_key_press(self, widget, event, *args):
editable = self.wid_text.get_editable()
+ activate_keys = [gtk.keysyms.Tab, gtk.keysyms.ISO_Left_Tab]
+ if not self.wid_completion:
+ activate_keys.append(gtk.keysyms.Return)
if (event.keyval == gtk.keysyms.F3
and editable
and self.but_new.get_property('sensitive')):
@@ -284,9 +292,9 @@ class Many2One(WidgetInterface):
and self.but_open.get_property('sensitive')):
self.sig_edit(widget)
return True
- elif (event.keyval in (gtk.keysyms.Tab, gtk.keysyms.Return)
+ elif (event.keyval in activate_keys
and editable):
- self.sig_activate(widget, event, key_press=True)
+ self.sig_activate()
elif (self.has_target(self.field.get(self.record))
and editable
and event.keyval in (gtk.keysyms.Delete,
@@ -311,8 +319,9 @@ class Many2One(WidgetInterface):
return False
def set_value(self, record, field):
- # Simulate a focus-out
- self.sig_activate()
+ if field.get_client(record) != self.wid_text.get_text():
+ field.set_client(record, self.value_from_id(None, ''))
+ self.wid_text.set_text('')
def set_text(self, value):
if not value:
@@ -350,3 +359,29 @@ class Many2One(WidgetInterface):
gobject.idle_add(populate, menu, self.get_model(),
self.id_from_value(value))
return True
+
+ def _completion_match_selected(self, completion, model, iter_):
+ rec_name, record_id = model.get(iter_, 0, 1)
+ self.field.set_client(self.record,
+ self.value_from_id(record_id, rec_name), force_change=True)
+
+ completion_model = self.wid_completion.get_model()
+ completion_model.clear()
+ completion_model.search_text = self.wid_text.get_text()
+ return True
+
+ def _update_completion(self, widget):
+ if self._readonly:
+ return
+ if not self.record:
+ return
+ if self.field.get(self.record) is not None:
+ return
+ model = self.get_model()
+ update_completion(self.wid_text, self.record, self.field, model)
+
+ def _completion_action_activated(self, completion, index):
+ if index == 0:
+ self.sig_edit()
+ elif index == 1:
+ self.sig_new()
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 22d78e210a..0611d022bd 100644
--- a/tryton/gui/window/view_form/view/form_gtk/one2many.py
+++ b/tryton/gui/window/view_form/view/form_gtk/one2many.py
@@ -2,6 +2,8 @@
#this repository contains the full copyright notices and license terms.
import gtk
import gettext
+import gobject
+
from interface import WidgetInterface
from tryton.gui.window.view_form.screen import Screen
from tryton.gui.window.win_search import WinSearch
@@ -10,6 +12,7 @@ from tryton.config import CONFIG
import tryton.common as common
from tryton.common import RPCExecute, RPCException
from tryton.common.placeholder_entry import PlaceholderEntry
+from tryton.common.completion import get_completion, update_completion
_ = gettext.gettext
@@ -36,19 +39,28 @@ class One2Many(WidgetInterface):
tooltips = common.Tooltips()
self.focus_out = True
+ self.wid_completion = None
if attrs.get('add_remove'):
self.wid_text = PlaceholderEntry()
self.wid_text.set_placeholder_text(_('Search'))
self.wid_text.set_property('width_chars', 13)
- self.wid_text.connect('activate', self._sig_activate)
- self.wid_text.connect('focus-out-event', self._focus_out)
+ self.wid_text.connect('focus-out-event',
+ lambda *a: self._focus_out())
hbox.pack_start(self.wid_text, expand=True, fill=True)
+ if int(self.attrs.get('completion', 1)):
+ self.wid_completion = get_completion()
+ self.wid_completion.connect('match-selected',
+ self._completion_match_selected)
+ self.wid_completion.connect('action-activated',
+ self._completion_action_activated)
+ self.wid_text.set_completion(self.wid_completion)
+ self.wid_text.connect('changed', self._update_completion)
+
self.but_add = gtk.Button()
tooltips.set_tip(self.but_add, _('Add existing record'))
self.but_add.connect('clicked', self._sig_add)
- self.but_add.connect('enter-notify-event', self.enter)
img_add = gtk.Image()
img_add.set_from_stock('tryton-list-add',
gtk.ICON_SIZE_SMALL_TOOLBAR)
@@ -61,7 +73,6 @@ class One2Many(WidgetInterface):
tooltips.set_tip(self.but_remove,
_('Remove selected record'))
self.but_remove.connect('clicked', self._sig_remove, True)
- self.but_remove.connect('enter-notify-event', self.enter)
img_remove = gtk.Image()
img_remove.set_from_stock('tryton-list-remove',
gtk.ICON_SIZE_SMALL_TOOLBAR)
@@ -75,7 +86,6 @@ class One2Many(WidgetInterface):
self.but_new = gtk.Button()
tooltips.set_tip(self.but_new, _('Create a new record '))
self.but_new.connect('clicked', self._sig_new)
- self.but_new.connect('enter-notify-event', self.enter)
img_new = gtk.Image()
img_new.set_from_stock('tryton-new', gtk.ICON_SIZE_SMALL_TOOLBAR)
img_new.set_alignment(0.5, 0.5)
@@ -86,7 +96,6 @@ class One2Many(WidgetInterface):
self.but_open = gtk.Button()
tooltips.set_tip(self.but_open, _('Edit selected record '))
self.but_open.connect('clicked', self._sig_edit)
- self.but_open.connect('enter-notify-event', self.enter)
img_open = gtk.Image()
img_open.set_from_stock('tryton-open', gtk.ICON_SIZE_SMALL_TOOLBAR)
img_open.set_alignment(0.5, 0.5)
@@ -97,7 +106,6 @@ class One2Many(WidgetInterface):
self.but_del = gtk.Button()
tooltips.set_tip(self.but_del, _('Delete selected record '))
self.but_del.connect('clicked', self._sig_remove, False)
- self.but_del.connect('enter-notify-event', self.enter)
img_del = gtk.Image()
img_del.set_from_stock('tryton-delete', gtk.ICON_SIZE_SMALL_TOOLBAR)
img_del.set_alignment(0.5, 0.5)
@@ -108,7 +116,6 @@ class One2Many(WidgetInterface):
self.but_undel = gtk.Button()
tooltips.set_tip(self.but_undel, _('Undelete selected record '))
self.but_undel.connect('clicked', self._sig_undelete)
- self.but_undel.connect('enter-notify-event', self.enter)
img_undel = gtk.Image()
img_undel.set_from_stock('tryton-undo', gtk.ICON_SIZE_SMALL_TOOLBAR)
img_undel.set_alignment(0.5, 0.5)
@@ -121,7 +128,6 @@ class One2Many(WidgetInterface):
self.but_pre = gtk.Button()
tooltips.set_tip(self.but_pre, _('Previous'))
self.but_pre.connect('clicked', self._sig_previous)
- self.but_pre.connect('enter-notify-event', self.enter)
img_pre = gtk.Image()
img_pre.set_from_stock('tryton-go-previous',
gtk.ICON_SIZE_SMALL_TOOLBAR)
@@ -136,7 +142,6 @@ class One2Many(WidgetInterface):
self.but_next = gtk.Button()
tooltips.set_tip(self.but_next, _('Next'))
self.but_next.connect('clicked', self._sig_next)
- self.but_next.connect('enter-notify-event', self.enter)
img_next = gtk.Image()
img_next.set_from_stock('tryton-go-next', gtk.ICON_SIZE_SMALL_TOOLBAR)
img_next.set_alignment(0.5, 0.5)
@@ -149,7 +154,6 @@ class One2Many(WidgetInterface):
but_switch = gtk.Button()
tooltips.set_tip(but_switch, _('Switch'))
but_switch.connect('clicked', self.switch_view)
- but_switch.connect('enter-notify-event', self.enter)
img_switch = gtk.Image()
img_switch.set_from_stock('tryton-fullscreen',
gtk.ICON_SIZE_SMALL_TOOLBAR)
@@ -212,6 +216,18 @@ class One2Many(WidgetInterface):
if event.keyval == gtk.keysyms.Insert and widget == self.screen.widget:
self._sig_undelete(widget)
return False
+ if self.attrs.get('add_remove'):
+ editable = self.wid_text.get_editable()
+ activate_keys = [gtk.keysyms.Tab, gtk.keysyms.ISO_Left_Tab]
+ if not self.wid_completion:
+ activate_keys.append(gtk.keysyms.Return)
+ if (widget == self.wid_text
+ and event.keyval in activate_keys
+ and editable
+ and self.wid_text.get_text()):
+ self._sig_add()
+ self.wid_text.grab_focus()
+ return False
def destroy(self):
self.screen.destroy()
@@ -298,7 +314,7 @@ class One2Many(WidgetInterface):
return False
return True
- def _sig_new(self, widget):
+ def _sig_new(self, widget=None):
if not common.MODELACCESS[self.screen.model_name]['create']:
return
if not self._validate():
@@ -356,15 +372,7 @@ class One2Many(WidgetInterface):
def _sig_undelete(self, button):
self.screen.unremove()
- def _sig_activate(self, *args):
- self._sig_add()
- self.wid_text.grab_focus()
-
- def _focus_out(self, *args):
- if self.wid_text.get_text():
- self._sig_add()
-
- def _sig_add(self, *args):
+ def _sig_add(self, *args, **kwargs):
if not self.focus_out:
return
access = common.MODELACCESS[self.screen.model_name]
@@ -399,7 +407,7 @@ class One2Many(WidgetInterface):
self.screen.display(res_id=ids[0])
self.screen.set_cursor()
self.wid_text.set_text('')
- if len(ids) != 1:
+ if len(ids) != 1 or kwargs.get('win_search', False):
WinSearch(self.attrs['relation'], callback, sel_multi=True,
ids=ids, context=context, domain=domain,
view_ids=self.attrs.get('view_ids', '').split(','),
@@ -456,3 +464,35 @@ class One2Many(WidgetInterface):
record.modified_fields.setdefault(field.name)
record.signal('record-modified')
return True
+
+ def _completion_match_selected(self, completion, model, iter_):
+ record_id, = model.get(iter_, 1)
+ self.screen.load([record_id], modified=True)
+ self.wid_text.set_text('')
+ self.wid_text.grab_focus()
+
+ completion_model = self.wid_completion.get_model()
+ completion_model.clear()
+ completion_model.search_text = self.wid_text.get_text()
+ return True
+
+ def _update_completion(self, widget):
+ if self._readonly:
+ return
+ if not self.record:
+ return
+ model = self.attrs['relation']
+ domain = self.field.domain_get(self.record)
+ domain = domain[:]
+ domain.extend(self.record.expr_eval(self.attrs.get('add_remove')))
+ removed_ids = self.field.get_removed_ids(self.record)
+ domain = ['OR', domain, ('id', 'in', removed_ids)]
+ update_completion(self.wid_text, self.record, self.field, model,
+ domain=domain)
+
+ def _completion_action_activated(self, completion, index):
+ if index == 0:
+ self._sig_add(win_search=True)
+ self.wid_text.grab_focus()
+ elif index == 1:
+ self._sig_new()
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 b546c750ac..8d08de6356 100644
--- a/tryton/gui/window/view_form/view/form_gtk/reference.py
+++ b/tryton/gui/window/view_form/view/form_gtk/reference.py
@@ -122,8 +122,17 @@ class Reference(Many2One):
if not value:
field.set_client(record, None)
else:
- return field.set_client(record, ('', value))
- return super(Reference, self).set_value(record, field)
+ field.set_client(record, ('', value))
+ return
+ else:
+ try:
+ model, name = field.get_client(record)
+ except (ValueError, TypeError):
+ model, name = '', ''
+ if (model != self.get_model()
+ or name != self.wid_text.get_text()):
+ field.set_client(record, None)
+ self.wid_text.set_text('')
def set_text(self, value):
if value:
diff --git a/tryton/gui/window/view_form/view/form_gtk/richtextbox.py b/tryton/gui/window/view_form/view/form_gtk/richtextbox.py
index b2b355339e..b6f24e102d 100644
--- a/tryton/gui/window/view_form/view/form_gtk/richtextbox.py
+++ b/tryton/gui/window/view_form/view/form_gtk/richtextbox.py
@@ -180,7 +180,6 @@ class RichTextBox(TextBox):
for local in (3, 6, 11, 14):
self.tool_bar.insert(separator(), local)
self.widget.pack_start(self.tool_bar, False)
- self.widget.connect('enter-notify-event', self.enter)
def get_value_markup(self):
if self.tools['markup'].get_active():
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 0f7f58a73a..67eaf1e3d8 100644
--- a/tryton/gui/window/view_form/view/form_gtk/selection.py
+++ b/tryton/gui/window/view_form/view/form_gtk/selection.py
@@ -14,8 +14,6 @@ class Selection(WidgetInterface):
super(Selection, self).__init__(field_name, model_name, attrs=attrs)
self.widget = gtk.HBox(spacing=3)
- ev_box = gtk.EventBox()
- ev_box.connect('enter-notify-event', self.enter)
self.entry = gtk.ComboBoxEntry()
child = self.entry.child
child.set_property('activates_default', True)
@@ -27,8 +25,7 @@ class Selection(WidgetInterface):
child.connect_after('focus-out-event', self.sig_activate)
child.connect('changed', self.send_modified)
self.entry.connect('notify::active', lambda *a: self._focus_out())
- ev_box.add(self.entry)
- self.widget.pack_start(ev_box)
+ self.widget.pack_start(self.entry)
self.widget.set_focus_chain([child])
self._selection = {}
diff --git a/tryton/gui/window/view_form/view/form_gtk/url.py b/tryton/gui/window/view_form/view/form_gtk/url.py
index d28540618d..4e4651a022 100644
--- a/tryton/gui/window/view_form/view/form_gtk/url.py
+++ b/tryton/gui/window/view_form/view/form_gtk/url.py
@@ -19,7 +19,6 @@ class URL(Char):
self.button.set_image(img)
self.button.set_relief(gtk.RELIEF_NONE)
self.button.connect('clicked', self.button_clicked)
- self.button.connect('enter-notify-event', self.enter)
self.button.set_alignment(0.5, 0.5)
self.widget.pack_start(self.button, expand=False, fill=False)
self.widget.set_focus_chain([self.entry])
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 b003f0045d..1479aa1a9e 100644
--- a/tryton/gui/window/view_form/view/list_gtk/parser.py
+++ b/tryton/gui/window/view_form/view/list_gtk/parser.py
@@ -22,7 +22,8 @@ from tryton.common import COLORS, node_attributes, \
import tryton.common as common
from tryton.common.cellrendererbutton import CellRendererButton
from tryton.common.cellrendererdate import CellRendererDate
-from tryton.common.cellrenderertext import CellRendererText
+from tryton.common.cellrenderertext import CellRendererText, \
+ CellRendererTextCompletion
from tryton.common.cellrenderertoggle import CellRendererToggle
from tryton.common.cellrenderercombo import CellRendererCombo
from tryton.common.cellrendererinteger import CellRendererInteger
@@ -30,6 +31,7 @@ from tryton.common.cellrendererfloat import CellRendererFloat
from tryton.common.cellrendererbinary import CellRendererBinary
from tryton.translate import date_format
from tryton.common import RPCExecute, RPCException
+from tryton.common.completion import get_completion, update_completion
_ = gettext.gettext
@@ -391,6 +393,12 @@ class Char(object):
def editing_started(self, cell, editable, path):
return False
+ def _get_record_field(self, path):
+ store = self.treeview.get_model()
+ record = store.get_value(store.get_iter(path), 0)
+ field = record.group.fields[self.field_name]
+ return record, field
+
class Int(Char):
@@ -523,12 +531,6 @@ class Binary(Char):
size = record[self.field_name].get_size(record)
cell.set_property('size', common.humanize(size) if size else '')
- def _get_record_field(self, path):
- store = self.treeview.get_model()
- record = store.get_value(store.get_iter(path), 0)
- field = record.group.fields[self.field_name]
- return record, field
-
def new_binary(self, renderer, path):
filename = file_selection(_('Open...'))
record, field = self._get_record_field(path)
@@ -575,6 +577,13 @@ class Binary(Char):
class M2O(Char):
+ def __init__(self, field_name, model_name, treeview, attrs=None):
+ super(M2O, self).__init__(field_name, model_name, treeview,
+ attrs=attrs)
+ if int(attrs.get('completion', 1)):
+ self.renderer = CellRendererTextCompletion(self.set_completion)
+ self.renderer.connect('editing-started', self.editing_started)
+
def value_from_text(self, record, text, callback=None):
field = record.group.fields[self.field_name]
if not text:
@@ -671,6 +680,57 @@ class M2O(Char):
WinSearch(relation, search_callback, sel_multi=False, ids=ids,
context=context, domain=domain)
+ def set_completion(self, entry, path):
+ if entry.get_completion():
+ entry.set_completion(None)
+ completion = get_completion()
+ completion.connect('match-selected', self._completion_match_selected,
+ path)
+ completion.connect('action-activated',
+ self._completion_action_activated, path)
+ entry.set_completion(completion)
+ entry.connect('key-press-event', self._key_press, path)
+ entry.connect('changed', self._update_completion, path)
+
+ def _key_press(self, entry, event, path):
+ record, field = self._get_record_field(path)
+ if (field.get(record) is not None
+ and event.keyval in (gtk.keysyms.Delete,
+ gtk.keysyms.BackSpace)):
+ entry.set_text('')
+ field.set_client(record, None)
+ return False
+
+ def _completion_match_selected(self, completion, model, iter_, path):
+ record, field = self._get_record_field(path)
+ rec_name, record_id = model.get(iter_, 0, 1)
+ field.set_client(record, (record_id, rec_name))
+
+ completion.get_entry().set_text(rec_name)
+ completion_model = completion.get_model()
+ completion_model.clear()
+ completion_model.search_text = rec_name
+ return True
+
+ def _update_completion(self, entry, path):
+ record, field = self._get_record_field(path)
+ if field.get(record) is not None:
+ return
+ model = field.attrs['relation']
+ update_completion(entry, record, field, model)
+
+ def _completion_action_activated(self, completion, index, path):
+ record, field = self._get_record_field(path)
+ entry = completion.get_entry()
+
+ def callback():
+ entry.set_text(field.get_client(record))
+ if index == 0:
+ self.open_remote(record, create=False, changed=True,
+ text=entry.get_text(), callback=callback)
+ elif index == 1:
+ self.open_remote(record, create=True, callback=callback)
+
class O2O(M2O):
pass
diff --git a/tryton/gui/window/win_form.py b/tryton/gui/window/win_form.py
index 5d5f4ff0a7..a0896840a7 100644
--- a/tryton/gui/window/win_form.py
+++ b/tryton/gui/window/win_form.py
@@ -57,12 +57,10 @@ class WinForm(NoModal):
icon_cancel = gtk.STOCK_CANCEL
self.but_cancel = self.win.add_button(icon_cancel,
gtk.RESPONSE_CANCEL)
- self.but_cancel.set_focus_on_click(False)
if new and self.many:
self.but_new = self.win.add_button(gtk.STOCK_NEW,
gtk.RESPONSE_ACCEPT)
- self.but_new.set_focus_on_click(False)
self.but_new.set_accel_path('/Form/New', self.accel_group)
if self.save_current:
@@ -77,7 +75,6 @@ class WinForm(NoModal):
else:
self.but_ok = self.win.add_button(gtk.STOCK_OK,
gtk.RESPONSE_OK)
- self.but_ok.set_focus_on_click(False)
self.but_ok.add_accelerator('clicked', self.accel_group,
gtk.keysyms.Return, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
self.win.set_default_response(gtk.RESPONSE_OK)