Add completion on Many2One, Many2Many and One2Many

review580002
This commit is contained in:
Cédric Krier committed 2012-11-16 20:24:33 +01:00
1 parent bf3a4a05ab
commit 2c6bd741ff
16 files changed
+336 -89

No files matched your search

+1
View File
@@ -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
+16
View File
@@ -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)
+59
View File
@@ -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)
-7
View File
@@ -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
-3
View File
@@ -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)