Add completion on Many2One, Many2Many and One2Many
review580002
This commit is contained in:
1 parent
bf3a4a05ab
commit
2c6bd741ff
16 files changed
+336
-89
No files matched your search
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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, _('<i>Search...</i>'))
|
||||
completion.insert_action_markup(1, _('<i>Create...</i>'))
|
||||
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)
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 <Del>'))
|
||||
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)
|
||||
@@ -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()
|
||||
@@ -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 <F3>'))
|
||||
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 <F2>'))
|
||||
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 <Del>'))
|
||||
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 <Ins>'))
|
||||
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()
|
||||
@@ -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:
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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('<tryton>/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)
|
||||
|
||||
Reference in new issue
Block a user