New search widget
review103001
This commit is contained in:
1 parent
8bcc90c2c7
commit
6ab08fec91
19 files changed
+1697
-1281
No files matched your search
@@ -1,3 +1,4 @@
|
||||
* New search widget
|
||||
* Improve memory management of Binary fields
|
||||
* Support buffer for Binary fields
|
||||
* Remove delete on Escape in editable tree
|
||||
|
||||
+98
-3
@@ -92,9 +92,9 @@ Figure: Tryton client application::
|
||||
| |-+ | |-----------------------------------------------||
|
||||
Tool bar | | |- | | New Save Switch Reload | Prev Next | Attach v ||
|
||||
| | |- | |-----------------------------------------------||
|
||||
| + | | ||
|
||||
| |-+ | | ||
|
||||
| | |- | | ||
|
||||
| + | | ____________________ ||
|
||||
Search widget | |-+ | | Search | | <- -> Find Clear ||
|
||||
| | |- | |-----------------------------------------------||
|
||||
| | |- | | ||
|
||||
| + | | ||
|
||||
View | |-+ | | ||
|
||||
@@ -874,6 +874,101 @@ Attachment:
|
||||
On click it opens the attachments :term:`dialog`. The default dialog
|
||||
shows a list view of the attached files and links.
|
||||
|
||||
Search Widget
|
||||
*************
|
||||
|
||||
The search widget adds the ability to easily search for records on the current
|
||||
tab. This widget is visible only on :term:`tree view`.
|
||||
|
||||
The Syntax
|
||||
^^^^^^^^^^
|
||||
|
||||
A query is composed of search clauses.
|
||||
A clause is composed of a field name (with `:` at the end), an operator and a value.
|
||||
The field name is optional and defaults to the record name.
|
||||
The operator is also optional and defaults to `like` or `equal` depending on
|
||||
the type of the field. The default operator is `=` except for fields of type
|
||||
`char`, `text` and `many2one` which is `ilike`.
|
||||
|
||||
Field Names
|
||||
^^^^^^^^^^^
|
||||
|
||||
All field names shown in the :term:`tree view` can be searched. Field names
|
||||
must be followed by a `:`
|
||||
|
||||
For example: ``Name:``
|
||||
|
||||
If the field name contains spaces, it is possible to
|
||||
escape it using double quotes.
|
||||
|
||||
For example: ``"Receivable Today":``
|
||||
|
||||
Operators
|
||||
^^^^^^^^^
|
||||
|
||||
The following operators can be used:
|
||||
|
||||
* `=`: equal to
|
||||
* `<`: less then
|
||||
* `<=`: less then or equal to
|
||||
* `>`: greater then
|
||||
* `>=`: greater then or equal to
|
||||
* `!=`: not equal
|
||||
* `!`: not equal or not like (depending of the type of field)
|
||||
|
||||
For example: ``Name: != Dwight``
|
||||
|
||||
.. note:: The `ilike` operator is never explicit and `%` is appended to the
|
||||
value to make it behaves like `starts with`
|
||||
|
||||
Values
|
||||
^^^^^^
|
||||
|
||||
The format of the value depends on the type of the field.
|
||||
A list of values can be set using `;` as separator.
|
||||
|
||||
For example: ``Name: Michael; Pam``
|
||||
|
||||
It will find all records having the `Name` starting with `Michael` or
|
||||
`Pam`.
|
||||
|
||||
A range of number values can be set using `..`.
|
||||
|
||||
For example: ``Amount: 100..500``
|
||||
|
||||
It will find all records with `Amount` between `100` and `500` (left
|
||||
included and right excluded)
|
||||
|
||||
There are two wildcards:
|
||||
|
||||
* `%`: matches any string of zero or more characters.
|
||||
* `_`: matches any single character.
|
||||
|
||||
It is possible to escape special characters in values by using double quotes.
|
||||
|
||||
For example: ``Name: "Michael:Scott"``
|
||||
|
||||
Here it will search with the value `Michael:Scott`.
|
||||
|
||||
Clause composition
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The clauses can be composed using the two boolean operators `and` and `or`.
|
||||
By default, there is an implicit `and` between each clause if no operator is
|
||||
specified.
|
||||
|
||||
For example: ``Name: Michael Amount: 100``
|
||||
|
||||
is the same as ``Name: Michael and Amount: 100``
|
||||
|
||||
The `and` operator has a highest precedence than `or` but you can change it by
|
||||
using parenthesis.
|
||||
|
||||
For example: ``(Name: Michael or Name: Pam) and Amount: 100``
|
||||
|
||||
is different than ``Name: Michael or Name: Pam and Amount: 100``
|
||||
|
||||
which is evaluated as ``Name: Michael or (Name: Pam and Amount: 100)``
|
||||
|
||||
Appendix
|
||||
********
|
||||
|
||||
@@ -161,6 +161,8 @@ class DateEntry(gtk.Entry):
|
||||
return False
|
||||
|
||||
match = self.regex.match(text)
|
||||
if not match:
|
||||
return False
|
||||
for i in range(len(match.groups())):
|
||||
val = match.group(i + 1)
|
||||
n = len(val)
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -101,8 +101,6 @@ class Main(object):
|
||||
gtk.gdk.CONTROL_MASK|gtk.gdk.SHIFT_MASK)
|
||||
gtk.accel_map_add_entry('<tryton>/Form/Delete', gtk.keysyms.D,
|
||||
gtk.gdk.CONTROL_MASK)
|
||||
gtk.accel_map_add_entry('<tryton>/Form/Find', gtk.keysyms.F,
|
||||
gtk.gdk.CONTROL_MASK)
|
||||
gtk.accel_map_add_entry('<tryton>/Form/Next', gtk.keysyms.Page_Down,
|
||||
0)
|
||||
gtk.accel_map_add_entry('<tryton>/Form/Previous', gtk.keysyms.Page_Up,
|
||||
|
||||
@@ -11,7 +11,6 @@ from tryton.action import Action
|
||||
from tryton.config import CONFIG
|
||||
from tryton.gui import Main
|
||||
from tryton.gui.window import Window
|
||||
from tryton.gui.window.win_search import WinSearch
|
||||
from tryton.gui.window.preference import Preference
|
||||
from tryton.gui.window.win_export import WinExport
|
||||
from tryton.gui.window.win_import import WinImport
|
||||
@@ -59,7 +58,6 @@ class Form(SignalEvent, TabContent):
|
||||
(_('_Delete...'), 'tryton-delete', 'sig_remove',
|
||||
'<tryton>/Form/Delete'),
|
||||
(None,) * 4,
|
||||
(_('_Find...'), 'tryton-find', 'sig_search', '<tryton>/Form/Find'),
|
||||
(_('_Next'), 'tryton-go-next', 'sig_next', '<tryton>/Form/Next'),
|
||||
(_('_Previous'), 'tryton-go-previous', 'sig_previous',
|
||||
'<tryton>/Form/Previous'),
|
||||
@@ -442,16 +440,6 @@ class Form(SignalEvent, TabContent):
|
||||
menu.show_all()
|
||||
menu.popup(None, None, menu_position, 0, 0)
|
||||
|
||||
def sig_search(self, widget=None):
|
||||
if not self.modified_save():
|
||||
return
|
||||
def callback(result):
|
||||
if result:
|
||||
self.screen.clear()
|
||||
self.screen.load(result)
|
||||
WinSearch(self.model, callback, domain=self.domain,
|
||||
context=self.context)
|
||||
|
||||
def message_info(self, message, color='red'):
|
||||
if message:
|
||||
self.info_label.set_label(message)
|
||||
|
||||
@@ -9,13 +9,13 @@ import xml.dom.minidom
|
||||
import tryton.rpc as rpc
|
||||
from tryton.gui.window.view_form.model.group import Group
|
||||
from tryton.gui.window.view_form.view.screen_container import ScreenContainer
|
||||
from tryton.gui.window.view_form.widget_search import Form
|
||||
from tryton.signal_event import SignalEvent
|
||||
from tryton.common import node_attributes
|
||||
from tryton.config import CONFIG
|
||||
import tryton.common as common
|
||||
from tryton.exceptions import TrytonServerError
|
||||
from tryton.jsonrpc import JSONEncoder
|
||||
from tryton.common.tdp import DomainParser
|
||||
|
||||
|
||||
class Screen(SignalEvent):
|
||||
@@ -36,6 +36,8 @@ class Screen(SignalEvent):
|
||||
if domain is None:
|
||||
domain = []
|
||||
|
||||
self.limit = limit or 1000
|
||||
self.offset = 0
|
||||
super(Screen, self).__init__()
|
||||
|
||||
self.readonly = readonly
|
||||
@@ -62,13 +64,13 @@ class Screen(SignalEvent):
|
||||
self.screen_container.alternate_view = alternate_view
|
||||
self.widget = self.screen_container.widget_get()
|
||||
self.__current_view = 0
|
||||
self.limit = limit
|
||||
self.search_value = search_value
|
||||
self.fields_view_tree = None
|
||||
self.sort = sort
|
||||
self.view_to_load = []
|
||||
self.expanded_nodes = collections.defaultdict(
|
||||
lambda: collections.defaultdict(lambda: None))
|
||||
self.domain_parser = None
|
||||
|
||||
if mode:
|
||||
self.view_to_load = mode[1:]
|
||||
@@ -84,68 +86,86 @@ class Screen(SignalEvent):
|
||||
|
||||
def search_active(self, active=True):
|
||||
if active and not self.parent:
|
||||
if not self.filter_widget:
|
||||
if not self.fields_view_tree:
|
||||
ctx = {}
|
||||
ctx.update(rpc.CONTEXT)
|
||||
ctx.update(self.context)
|
||||
try:
|
||||
self.fields_view_tree = rpc.execute('model',
|
||||
self.model_name, 'fields_view_get', False,
|
||||
'tree', ctx)
|
||||
except TrytonServerError:
|
||||
return
|
||||
self.filter_widget = Form(self.fields_view_tree,
|
||||
self.model_name, self.domain, (self, self.search_filter),
|
||||
self.context)
|
||||
self.screen_container.add_filter(self.filter_widget.widget,
|
||||
self.search_filter, self.search_clear,
|
||||
self.search_prev, self.search_next)
|
||||
self.filter_widget.set_limit(self.limit)
|
||||
self.filter_widget.value = self.search_value
|
||||
if not self.fields_view_tree:
|
||||
ctx = {}
|
||||
ctx.update(rpc.CONTEXT)
|
||||
ctx.update(self.context)
|
||||
try:
|
||||
self.fields_view_tree = rpc.execute('model',
|
||||
self.model_name, 'fields_view_get', False,
|
||||
'tree', ctx)
|
||||
except TrytonServerError:
|
||||
return
|
||||
|
||||
fields = self.fields_view_tree['fields']
|
||||
for name, props in fields.iteritems():
|
||||
if props['type'] not in ('selection', 'reference'):
|
||||
continue
|
||||
if isinstance(props['selection'], (tuple, list)):
|
||||
continue
|
||||
props['selection'] = self.get_selection(props)
|
||||
|
||||
self.domain_parser = DomainParser(
|
||||
self.fields_view_tree['fields'])
|
||||
|
||||
self.screen_container.set_screen(self)
|
||||
self.screen_container.show_filter()
|
||||
else:
|
||||
self.screen_container.hide_filter()
|
||||
|
||||
def search_prev(self, widget=None):
|
||||
self.filter_widget.prev()
|
||||
self.search_filter()
|
||||
def get_selection(self, props):
|
||||
try:
|
||||
selection = rpc.execute('model',
|
||||
self.model_name, props['selection'], rpc.CONTEXT)
|
||||
except TrytonServerError, exception:
|
||||
common.process_exception(exception, None)
|
||||
selection = []
|
||||
selection.sort(lambda x, y: cmp(x[1], y[1]))
|
||||
return selection
|
||||
|
||||
def search_next(self, widget=None):
|
||||
self.filter_widget.next()
|
||||
self.search_filter()
|
||||
def search_prev(self, search_string):
|
||||
self.offset -= self.limit
|
||||
self.search_filter(search_string=search_string)
|
||||
|
||||
def search_clear(self, widget=None):
|
||||
self.filter_widget.clear()
|
||||
self.clear()
|
||||
def search_next(self, search_string):
|
||||
self.offset += self.limit
|
||||
self.search_filter(search_string=search_string)
|
||||
|
||||
def search_filter(self, widget=None, only_ids=False):
|
||||
limit = None
|
||||
offset = 0
|
||||
values = []
|
||||
if self.filter_widget:
|
||||
limit = self.filter_widget.get_limit()
|
||||
offset = self.filter_widget.get_offset()
|
||||
values = self.filter_widget.value
|
||||
def search_complete(self, search_string):
|
||||
parsed_tree = self.domain_parser.parse(search_string)
|
||||
res = list(r for r in parsed_tree.complete() \
|
||||
if r.strip() != search_string)
|
||||
return res
|
||||
|
||||
def search_filter(self, search_string=None, only_ids=False):
|
||||
domain = []
|
||||
|
||||
if self.domain_parser:
|
||||
if search_string is not None:
|
||||
domain = self.domain_parser.parse(search_string or '').domain()
|
||||
else:
|
||||
domain = self.search_value
|
||||
self.screen_container.set_text(self.domain_parser.string(domain))
|
||||
else:
|
||||
values = [('id', 'in', [x.id for x in self.group])]
|
||||
domain = [('id', 'in', [x.id for x in self.group])]
|
||||
|
||||
ctx = {}
|
||||
ctx.update(rpc.CONTEXT)
|
||||
ctx.update(self.context)
|
||||
if values:
|
||||
if domain:
|
||||
if self.domain:
|
||||
values = ['AND', values, self.domain]
|
||||
domain = ['AND', domain, self.domain]
|
||||
else:
|
||||
values = self.domain
|
||||
rpc_args = ('model', self.model_name, 'search', values, offset, limit,
|
||||
self.sort, ctx)
|
||||
domain = self.domain
|
||||
rpc_args = ('model', self.model_name, 'search', domain, self.offset,
|
||||
self.limit, self.sort, ctx)
|
||||
try:
|
||||
ids = rpc.execute(*rpc_args)
|
||||
except TrytonServerError, exception:
|
||||
ids = (common.process_exception(exception, *rpc_args) or [])
|
||||
if not only_ids:
|
||||
if len(ids) == limit:
|
||||
rpc_args = ('model', self.model_name, 'search_count', values,
|
||||
if len(ids) == self.limit:
|
||||
rpc_args = ('model', self.model_name, 'search_count', domain,
|
||||
ctx)
|
||||
try:
|
||||
self.search_count = rpc.execute(*rpc_args)
|
||||
@@ -154,9 +174,9 @@ class Screen(SignalEvent):
|
||||
*rpc_args) or 0)
|
||||
else:
|
||||
self.search_count = len(ids)
|
||||
self.screen_container.but_prev.set_sensitive(bool(offset))
|
||||
if (len(ids) == limit
|
||||
and self.search_count > limit + offset):
|
||||
self.screen_container.but_prev.set_sensitive(bool(self.offset))
|
||||
if (len(ids) == self.limit
|
||||
and self.search_count > self.limit + self.offset):
|
||||
self.screen_container.but_next.set_sensitive(True)
|
||||
else:
|
||||
self.screen_container.but_next.set_sensitive(False)
|
||||
@@ -220,12 +240,8 @@ class Screen(SignalEvent):
|
||||
|
||||
def __set_current_record(self, record):
|
||||
self.__current_record = record
|
||||
if self.filter_widget:
|
||||
offset = int(self.filter_widget.get_offset())
|
||||
else:
|
||||
offset = 0
|
||||
try:
|
||||
pos = self.group.index(record) + offset + 1
|
||||
pos = self.group.index(record) + self.offset + 1
|
||||
except ValueError:
|
||||
pos = []
|
||||
i = record
|
||||
@@ -234,7 +250,7 @@ class Screen(SignalEvent):
|
||||
i = i.parent
|
||||
pos.reverse()
|
||||
pos = tuple(pos)
|
||||
self.signal('record-message', (pos or 0, len(self.group) + offset,
|
||||
self.signal('record-message', (pos or 0, len(self.group) + self.offset,
|
||||
self.search_count, record and record.id))
|
||||
attachment_count = 0
|
||||
if record and record.attachment_count > 0:
|
||||
|
||||
@@ -16,6 +16,7 @@ class ScreenContainer(object):
|
||||
self.filter_vbox = None
|
||||
self.but_prev = None
|
||||
self.but_next = None
|
||||
self.current_text = ''
|
||||
self.alternate_viewport = gtk.Viewport()
|
||||
self.alternate_viewport.set_shadow_type(gtk.SHADOW_NONE)
|
||||
self.alternate_view = False
|
||||
@@ -23,20 +24,35 @@ class ScreenContainer(object):
|
||||
def widget_get(self):
|
||||
return self.vbox
|
||||
|
||||
def add_filter(self, widget, fnct, clear_fnct, prev_fnct, next_fnct):
|
||||
tooltips = common.Tooltips()
|
||||
def set_screen(self, screen):
|
||||
self.screen = screen
|
||||
|
||||
tooltips = common.Tooltips()
|
||||
if self.filter_vbox is not None:
|
||||
return
|
||||
self.filter_vbox = gtk.VBox(spacing=0)
|
||||
self.filter_vbox.set_border_width(0)
|
||||
hbox = gtk.HBox(homogeneous=False, spacing=0)
|
||||
label = gtk.Label(_('Search'))
|
||||
label.set_alignment(0.0, 0.5)
|
||||
hbox.pack_start(label, expand=True, fill=True)
|
||||
hbox.pack_start(label, expand=False, fill=False, padding=5)
|
||||
|
||||
self.search_entry = gtk.Entry()
|
||||
self.search_entry.set_alignment(0.0)
|
||||
self.completion = gtk.EntryCompletion()
|
||||
self.completion.set_model(gtk.ListStore(str))
|
||||
self.completion.set_text_column(0)
|
||||
self.completion.set_match_func(lambda *a: True, None)
|
||||
self.search_entry.connect_after('activate', self.do_search)
|
||||
self.search_entry.set_completion(self.completion)
|
||||
self.search_entry.connect('changed', self.changed)
|
||||
|
||||
hbox.pack_start(self.search_entry, expand=True, fill=True, padding=5)
|
||||
|
||||
but_prev = gtk.Button()
|
||||
self.but_prev = but_prev
|
||||
tooltips.set_tip(but_prev, _('Previous'))
|
||||
but_prev.connect('clicked', prev_fnct)
|
||||
but_prev.connect('clicked', self.search_prev)
|
||||
img_prev = gtk.Image()
|
||||
img_prev.set_from_stock('tryton-go-previous',
|
||||
gtk.ICON_SIZE_SMALL_TOOLBAR)
|
||||
@@ -48,7 +64,7 @@ class ScreenContainer(object):
|
||||
but_next = gtk.Button()
|
||||
self.but_next = but_next
|
||||
tooltips.set_tip(but_next, _('Next'))
|
||||
but_next.connect('clicked', next_fnct)
|
||||
but_next.connect('clicked', self.search_next)
|
||||
img_next = gtk.Image()
|
||||
img_next.set_from_stock('tryton-go-next',
|
||||
gtk.ICON_SIZE_SMALL_TOOLBAR)
|
||||
@@ -59,7 +75,7 @@ class ScreenContainer(object):
|
||||
|
||||
but_find = gtk.Button()
|
||||
tooltips.set_tip(but_find, _('Find'))
|
||||
but_find.connect('clicked', fnct)
|
||||
but_find.connect('clicked', self.do_search)
|
||||
img_find = gtk.Image()
|
||||
img_find.set_from_stock('tryton-find', gtk.ICON_SIZE_SMALL_TOOLBAR)
|
||||
img_find.set_alignment(0.5, 0.5)
|
||||
@@ -69,7 +85,7 @@ class ScreenContainer(object):
|
||||
|
||||
but_clear = gtk.Button()
|
||||
tooltips.set_tip(but_clear, _('Clear'))
|
||||
but_clear.connect('clicked', clear_fnct)
|
||||
but_clear.connect('clicked', self.clear)
|
||||
img_clear = gtk.Image()
|
||||
img_clear.set_from_stock('tryton-clear', gtk.ICON_SIZE_SMALL_TOOLBAR)
|
||||
img_clear.set_alignment(0.5, 0.5)
|
||||
@@ -83,7 +99,6 @@ class ScreenContainer(object):
|
||||
hseparator = gtk.HSeparator()
|
||||
hseparator.show()
|
||||
self.filter_vbox.pack_start(hseparator, expand=True, fill=False)
|
||||
self.filter_vbox.pack_start(widget, expand=True, fill=True)
|
||||
|
||||
self.vbox.pack_start(self.filter_vbox, expand=False, fill=True)
|
||||
|
||||
@@ -114,3 +129,29 @@ class ScreenContainer(object):
|
||||
self.viewport.remove(self.viewport.get_child())
|
||||
self.viewport.add(widget)
|
||||
self.viewport.show_all()
|
||||
|
||||
def changed(self, editable):
|
||||
res = self.screen.search_complete(self.get_text())
|
||||
self.current_text = self.get_text()
|
||||
model = self.completion.get_model()
|
||||
model.clear()
|
||||
for r in res:
|
||||
model.append([r.strip()])
|
||||
|
||||
def clear(self, widget=None):
|
||||
self.search_entry.set_text('');
|
||||
|
||||
def get_text(self):
|
||||
return self.search_entry.get_text().strip().decode('utf-8')
|
||||
|
||||
def set_text(self, value):
|
||||
return self.search_entry.set_text(value)
|
||||
|
||||
def search_next(self, widget=None):
|
||||
self.screen.search_next(self.get_text())
|
||||
|
||||
def search_prev(self, widget=None):
|
||||
self.screen.search_prev(self.get_text())
|
||||
|
||||
def do_search(self, widget=None):
|
||||
self.screen.search_filter(self.get_text())
|
||||
@@ -1,3 +0,0 @@
|
||||
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
from form import *
|
||||
@@ -1,203 +0,0 @@
|
||||
#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 time
|
||||
import datetime
|
||||
import gtk
|
||||
import gettext
|
||||
import locale
|
||||
from interface import Interface
|
||||
from tryton.common import DT_FORMAT, DHM_FORMAT, TRYTON_ICON
|
||||
from tryton.common import date_widget, Tooltips, datetime_strftime
|
||||
from tryton.common import get_toplevel_window
|
||||
from tryton.translate import date_format
|
||||
import gobject
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class Calendar(Interface):
|
||||
|
||||
def __init__(self, name, attrs=None, context=None,
|
||||
on_change=None):
|
||||
super(Calendar, self).__init__(name, attrs=attrs, context=context,
|
||||
on_change=on_change)
|
||||
|
||||
tooltips = Tooltips()
|
||||
self.widget = gtk.HBox(spacing=3)
|
||||
|
||||
self.format = date_format()
|
||||
|
||||
self.liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
|
||||
self.combo = gtk.ComboBox(self.liststore)
|
||||
cell = gtk.CellRendererText()
|
||||
self.combo.pack_start(cell, True)
|
||||
self.combo.add_attribute(cell, 'text', 1)
|
||||
for oper in (['=', _('is')],
|
||||
['between', _('is between')],
|
||||
['not between', _('is not between')],
|
||||
['!=', _('is not')],
|
||||
):
|
||||
self.liststore.append(oper)
|
||||
self.combo.set_active(0)
|
||||
self.widget.pack_start(self.combo, False, False)
|
||||
self.combo.connect('changed', self._changed)
|
||||
self.combo.connect('changed', self.on_change)
|
||||
|
||||
self.widget1 = date_widget.ComplexEntry(self.format, spacing=3)
|
||||
self.widget1.show()
|
||||
self.entry1 = self.widget1.widget
|
||||
self.entry1.set_property('width-chars', 10)
|
||||
self.entry1.set_property('activates_default', True)
|
||||
self.entry1.connect('key_press_event', self.on_change)
|
||||
tooltips.set_tip(self.entry1, _('Start date'))
|
||||
self.widget.pack_start(self.widget1, expand=False, fill=True)
|
||||
|
||||
self.eb1 = gtk.EventBox()
|
||||
tooltips.set_tip(self.eb1, _('Open the calendar'))
|
||||
self.eb1.set_events(gtk.gdk.BUTTON_PRESS)
|
||||
self.eb1.connect('button_press_event', self.cal_open, self.entry1)
|
||||
img = gtk.Image()
|
||||
img.set_from_stock('tryton-find', gtk.ICON_SIZE_BUTTON)
|
||||
img.set_alignment(0.5, 0.5)
|
||||
self.eb1.add(img)
|
||||
self.widget.pack_start(self.eb1, expand=False, fill=False)
|
||||
|
||||
self.separator = gtk.Label('-')
|
||||
self.widget.pack_start(self.separator, expand=False, fill=False)
|
||||
|
||||
self.widget2 = date_widget.ComplexEntry(self.format, spacing=3)
|
||||
self.entry2 = self.widget2.widget
|
||||
self.entry2.set_property('width-chars', 10)
|
||||
self.entry2.set_property('activates_default', True)
|
||||
self.entry2.connect('key_press_event', self.on_change)
|
||||
tooltips.set_tip(self.entry2, _('End date'))
|
||||
self.widget.pack_start(self.widget2, expand=False, fill=True)
|
||||
|
||||
self.eb2 = gtk.EventBox()
|
||||
tooltips.set_tip(self.eb2, _('Open the calendar'))
|
||||
self.eb2.set_events(gtk.gdk.BUTTON_PRESS)
|
||||
self.eb2.connect('button_press_event', self.cal_open, self.entry2)
|
||||
img = gtk.Image()
|
||||
img.set_from_stock('tryton-find', gtk.ICON_SIZE_BUTTON)
|
||||
img.set_alignment(0.5, 0.5)
|
||||
self.eb2.add(img)
|
||||
self.widget.pack_start(self.eb2, expand=False, fill=False)
|
||||
|
||||
self.widget.show_all()
|
||||
self._changed(self.combo)
|
||||
|
||||
tooltips.enable()
|
||||
|
||||
def _changed(self, widget):
|
||||
oper = self.liststore.get_value(self.combo.get_active_iter(), 0)
|
||||
if oper in ('=', '!='):
|
||||
self.entry2.hide()
|
||||
self.separator.hide()
|
||||
self.eb2.hide()
|
||||
else:
|
||||
self.entry2.show()
|
||||
self.separator.show()
|
||||
self.eb2.show()
|
||||
|
||||
def _date_get(self, value):
|
||||
try:
|
||||
date = datetime.date(*time.strptime(value, self.format)[:3])
|
||||
except ValueError:
|
||||
return False
|
||||
if self.attrs.get('type', 'date') == 'datetime':
|
||||
return datetime_strftime(datetime.datetime.combine(date,
|
||||
datetime.time.min), DHM_FORMAT)
|
||||
return datetime_strftime(date, DT_FORMAT)
|
||||
|
||||
def _value_get(self):
|
||||
oper = self.liststore.get_value(self.combo.get_active_iter(), 0)
|
||||
if oper in ('=', '!='):
|
||||
value = self._date_get(self.entry1.get_text())
|
||||
if value:
|
||||
return [(self.name, oper, value)]
|
||||
else:
|
||||
return []
|
||||
else:
|
||||
res = []
|
||||
if oper == 'between':
|
||||
clause = 'AND'
|
||||
oper1 = '>='
|
||||
oper2 = '<='
|
||||
else:
|
||||
clause = 'OR'
|
||||
oper1 = '<='
|
||||
oper2 = '>='
|
||||
res.append(clause)
|
||||
val = self._date_get(self.entry1.get_text())
|
||||
if val:
|
||||
res.append((self.name, oper1, val))
|
||||
val = self._date_get(self.entry2.get_text())
|
||||
if val:
|
||||
res.append((self.name, oper2, val))
|
||||
return [res]
|
||||
|
||||
def _value_set(self, value):
|
||||
def conv(value):
|
||||
if not value:
|
||||
return ''
|
||||
try:
|
||||
return datetime_strftime(value, self.format)
|
||||
except ValueError:
|
||||
return ''
|
||||
|
||||
i = self.liststore.get_iter_root()
|
||||
while i:
|
||||
if self.liststore.get_value(i, 0) == value[0]:
|
||||
self.combo.set_active_iter(i)
|
||||
break
|
||||
i = self.liststore.iter_next(i)
|
||||
|
||||
self.entry1.set_text(conv(value[1]))
|
||||
if len(value) == 2:
|
||||
self.entry2.clear()
|
||||
else:
|
||||
self.entry2.set_text(conv(value[2]))
|
||||
|
||||
value = property(_value_get, _value_set)
|
||||
|
||||
def cal_open(self, widget, event, dest):
|
||||
parent = get_toplevel_window()
|
||||
win = gtk.Dialog(_('Date selection'), parent,
|
||||
gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
|
||||
gtk.STOCK_OK, gtk.RESPONSE_OK))
|
||||
win.set_has_separator(True)
|
||||
win.set_icon(TRYTON_ICON)
|
||||
|
||||
cal = gtk.Calendar()
|
||||
cal.display_options(gtk.CALENDAR_SHOW_HEADING | \
|
||||
gtk.CALENDAR_SHOW_DAY_NAMES | \
|
||||
gtk.CALENDAR_SHOW_WEEK_NUMBERS)
|
||||
cal.connect('day-selected-double-click',
|
||||
lambda *x: win.response(gtk.RESPONSE_OK))
|
||||
win.vbox.pack_start(cal, expand=True, fill=True)
|
||||
win.show_all()
|
||||
|
||||
try:
|
||||
val = self._date_get(dest.get_text())
|
||||
if val:
|
||||
cal.select_month(int(val[5:7])-1, int(val[0:4]))
|
||||
cal.select_day(int(val[8:10]))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
response = win.run()
|
||||
if response == gtk.RESPONSE_OK:
|
||||
year, month, day = cal.get_date()
|
||||
date = datetime.date(year, month + 1, day)
|
||||
dest.set_text(datetime_strftime(date, self.format))
|
||||
self.on_change()
|
||||
win.destroy()
|
||||
parent.present()
|
||||
|
||||
def clear(self):
|
||||
self.value = ('=', '')
|
||||
|
||||
def sig_activate(self, fct):
|
||||
self.entry1.connect_after('activate', fct)
|
||||
self.entry2.connect_after('activate', fct)
|
||||
@@ -1,82 +0,0 @@
|
||||
#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
|
||||
from interface import Interface
|
||||
import gobject
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class Char(Interface):
|
||||
|
||||
def __init__(self, name, attrs=None, context=None, on_change=None):
|
||||
super(Char, self).__init__(name, attrs=attrs, context=context,
|
||||
on_change=on_change)
|
||||
|
||||
self.widget = gtk.HBox()
|
||||
|
||||
self.liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
|
||||
self.combo = gtk.ComboBox(self.liststore)
|
||||
cell = gtk.CellRendererText()
|
||||
self.combo.pack_start(cell, True)
|
||||
self.combo.add_attribute(cell, 'text', 1)
|
||||
for oper in (
|
||||
['ilike', _('contains')],
|
||||
['not ilike', _('does not contain')],
|
||||
['=', _('equals')],
|
||||
['!=', _('does not equal')],
|
||||
['starts', _('starts with')],
|
||||
['ends', _('ends with')],
|
||||
):
|
||||
self.liststore.append(oper)
|
||||
self.combo.set_active(0)
|
||||
self.combo.connect('changed', self.on_change)
|
||||
self.widget.pack_start(self.combo, False, False)
|
||||
|
||||
self.entry = gtk.Entry()
|
||||
self.entry.set_max_length(int(self.attrs.get('size', 0)))
|
||||
self.entry.set_width_chars(5)
|
||||
self.entry.set_property('activates_default', True)
|
||||
self.entry.connect('key_press_event', self.on_change)
|
||||
self.widget.pack_start(self.entry, True, True)
|
||||
self.widget.show_all()
|
||||
|
||||
def _value_get(self):
|
||||
value = self.entry.get_text()
|
||||
oper = self.liststore.get_value(self.combo.get_active_iter(), 0)
|
||||
if value or oper != 'ilike':
|
||||
if oper == '=' and not value:
|
||||
value = False
|
||||
if oper in ('ilike', 'not ilike') \
|
||||
and '%' not in value:
|
||||
return [(self.name, oper, '%' + value + '%')]
|
||||
elif oper == 'starts':
|
||||
return [(self.name, 'ilike', value + '%')]
|
||||
elif oper == 'ends':
|
||||
return [(self.name, 'ilike', '%' + value)]
|
||||
return [(self.name, oper, value)]
|
||||
else:
|
||||
return []
|
||||
|
||||
def _value_set(self, value):
|
||||
i = self.liststore.get_iter_root()
|
||||
while i:
|
||||
if self.liststore.get_value(i, 0) == value[0]:
|
||||
self.combo.set_active_iter(i)
|
||||
break
|
||||
i = self.liststore.iter_next(i)
|
||||
self.entry.set_text(value[1] or '')
|
||||
|
||||
value = property(_value_get, _value_set)
|
||||
|
||||
def clear(self):
|
||||
self.value = ['ilike', '']
|
||||
|
||||
def _readonly_set(self, value):
|
||||
self.combo.set_sensitive(not value)
|
||||
self.entry.set_editable(not value)
|
||||
self.entry.set_sensitive(not value)
|
||||
|
||||
def sig_activate(self, fct):
|
||||
self.entry.connect_after('activate', fct)
|
||||
@@ -1,77 +0,0 @@
|
||||
#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
|
||||
from interface import Interface
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class CheckBox(Interface):
|
||||
|
||||
def __init__(self, name, attrs=None, context=None, on_change=None):
|
||||
super(CheckBox, self).__init__(name, attrs=attrs, context=context,
|
||||
on_change=on_change)
|
||||
|
||||
self.widget = gtk.combo_box_entry_new_text()
|
||||
self.widget.child.set_editable(True)
|
||||
self.widget.child.set_property('activates_default', True)
|
||||
self.widget.child.connect('key_press_event', self.sig_key_press)
|
||||
self.widget.child.connect('key_press_event', self.on_change)
|
||||
self.widget.set_focus_chain([self.widget.child])
|
||||
|
||||
if self.name != 'active' or \
|
||||
not (self.name == 'active' \
|
||||
and self.context.get('active_test', True)):
|
||||
self.widget.append_text('')
|
||||
self.widget.child.set_text('')
|
||||
else:
|
||||
self.widget.child.set_text(_('Yes'))
|
||||
self.widget.append_text(_('Yes'))
|
||||
self.widget.append_text(_('No'))
|
||||
|
||||
completion = gtk.EntryCompletion()
|
||||
#Only available in PyGTK 2.6 and above.
|
||||
if hasattr(completion, 'set_inline_selection'):
|
||||
completion.set_inline_selection(True)
|
||||
completion.set_model(self.widget.get_model())
|
||||
self.widget.child.set_completion(completion)
|
||||
completion.set_text_column(0)
|
||||
self.widget.show()
|
||||
|
||||
def sig_key_press(self, widget, event):
|
||||
if event.type == gtk.gdk.KEY_PRESS \
|
||||
and event.state & gtk.gdk.CONTROL_MASK \
|
||||
and event.keyval == gtk.keysyms.space:
|
||||
self.widget.popup()
|
||||
|
||||
def _value_get(self):
|
||||
val = self.widget.child.get_text()
|
||||
if not val \
|
||||
and self.name == 'active' \
|
||||
and self.context.get('active_test', True):
|
||||
val = _('Yes')
|
||||
self.widget.child.set_text(val)
|
||||
if val:
|
||||
return [(self.name, '=', int(val == _('Yes')))]
|
||||
self.widget.child.set_text('')
|
||||
return []
|
||||
|
||||
def _value_set(self, value):
|
||||
if value == '':
|
||||
if self.name != 'active' or \
|
||||
(self.name == 'active' \
|
||||
and self.context.get('active_test', True)):
|
||||
self.widget.child.set_text('')
|
||||
else:
|
||||
self.widget.child.set_text(_('Yes'))
|
||||
return
|
||||
if value:
|
||||
self.widget.child.set_text(_('Yes'))
|
||||
else:
|
||||
self.widget.child.set_text(_('No'))
|
||||
|
||||
value = property(_value_get, _value_set)
|
||||
|
||||
def sig_activate(self, fct):
|
||||
self.widget.child.connect_after('activate', fct)
|
||||
@@ -1,88 +0,0 @@
|
||||
#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 locale
|
||||
from decimal import Decimal
|
||||
from integer import Integer
|
||||
import gettext
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class Float(Integer):
|
||||
|
||||
def __init__(self, name, attrs=None, context=None, on_change=None):
|
||||
super(Float, self).__init__(name, attrs=attrs, context=context,
|
||||
on_change=on_change)
|
||||
if isinstance(self.attrs.get('digits'), basestring):
|
||||
self.digits = (16, 2)
|
||||
else:
|
||||
self.digits = self.attrs.get('digits', (16, 2))
|
||||
|
||||
def _value_get(self):
|
||||
try:
|
||||
value1 = locale.atof(self.entry1.get_text())
|
||||
except ValueError:
|
||||
value1 = False
|
||||
try:
|
||||
value2 = locale.atof(self.entry2.get_text())
|
||||
except ValueError:
|
||||
value2 = False
|
||||
return self._get_clause(value1, value2)
|
||||
|
||||
def _value_set(self, value):
|
||||
def conv(value):
|
||||
if value == False:
|
||||
return ''
|
||||
else:
|
||||
return locale.format('%.' + str(self.digits[1]) + 'f',
|
||||
value or 0.0, True)
|
||||
|
||||
i = self.liststore.get_iter_root()
|
||||
while i:
|
||||
if self.liststore.get_value(i, 0) == value[0]:
|
||||
self.combo.set_active_iter(i)
|
||||
break
|
||||
i = self.liststore.iter_next(i)
|
||||
|
||||
self.entry1.set_text(conv(value[1]))
|
||||
if len(value) == 2:
|
||||
self.entry2.set_text('')
|
||||
else:
|
||||
self.entry2.set_text(conv(value[2]))
|
||||
|
||||
value = property(_value_get, _value_set)
|
||||
|
||||
def clear(self):
|
||||
self.value = ('=', False, False)
|
||||
|
||||
def sig_insert_text(self, widget, new_text, new_text_length, position):
|
||||
value = widget.get_text()
|
||||
position = widget.get_position()
|
||||
new_value = value[:position] + new_text + value[position:]
|
||||
if new_value == '-':
|
||||
return
|
||||
try:
|
||||
if len(str(int(locale.atof(new_value)))) > self.digits[0]:
|
||||
widget.stop_emission('insert-text')
|
||||
except ValueError:
|
||||
widget.stop_emission('insert-text')
|
||||
|
||||
|
||||
class Numeric(Float):
|
||||
|
||||
def _value_get(self):
|
||||
try:
|
||||
value1 = Decimal(str(locale.atof(self.entry1.get_text())))
|
||||
except ValueError:
|
||||
value1 = False
|
||||
try:
|
||||
value2 = Decimal(str(locale.atof(self.entry2.get_text())))
|
||||
except ValueError:
|
||||
value2 = False
|
||||
res = self._get_clause(value1, value2)
|
||||
return res
|
||||
|
||||
def _value_set(self, value):
|
||||
super(Numeric, self)._value_set(value)
|
||||
|
||||
value = property(_value_get, _value_set)
|
||||
@@ -1,307 +0,0 @@
|
||||
#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
|
||||
from xml.parsers import expat
|
||||
import sys
|
||||
import gettext
|
||||
from tryton.config import CONFIG
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class _container(object):
|
||||
|
||||
def __init__(self):
|
||||
self.cont = []
|
||||
self.count = 0
|
||||
self.col = 0
|
||||
|
||||
def new(self, col=6):
|
||||
self.col = col+1
|
||||
table = gtk.Table(1, col)
|
||||
table.show()
|
||||
table.set_homogeneous(False)
|
||||
table.set_col_spacings(3)
|
||||
table.set_row_spacings(0)
|
||||
table.set_border_width(1)
|
||||
self.cont.append( (table, 1, 0) )
|
||||
|
||||
def get(self):
|
||||
return self.cont[-1][0]
|
||||
|
||||
def pop(self):
|
||||
return self.cont.pop()[0]
|
||||
|
||||
def newline(self):
|
||||
(table, i, j) = self.cont[-1]
|
||||
if i > 0:
|
||||
self.cont[-1] = (table, 1, j+1)
|
||||
table.resize(j + 1, self.col)
|
||||
|
||||
def wid_add(self, widget, length=1, name=None, expand=False, ypadding=0):
|
||||
self.count += 1
|
||||
(table, i, j) = self.cont[-1]
|
||||
if length > self.col:
|
||||
length = self.col
|
||||
if length + i > self.col:
|
||||
self.newline()
|
||||
(table, i, j) = self.cont[-1]
|
||||
if name:
|
||||
vbox = gtk.VBox(homogeneous=False, spacing=1)
|
||||
vbox.show()
|
||||
label = gtk.Label(name)
|
||||
label.show()
|
||||
label.set_alignment(0.0, 0.5)
|
||||
vbox.pack_start(label, expand=False)
|
||||
vbox.pack_start(widget, expand=expand, fill=True)
|
||||
wid = vbox
|
||||
else:
|
||||
wid = widget
|
||||
yopt = False
|
||||
if expand:
|
||||
yopt = yopt | gtk.EXPAND |gtk.FILL
|
||||
table.attach(wid, i, i+length, j, j+1,
|
||||
yoptions=yopt, xoptions=gtk.FILL|gtk.EXPAND,
|
||||
ypadding=ypadding, xpadding=5)
|
||||
self.cont[-1] = (table, i+length, j)
|
||||
return wid
|
||||
|
||||
|
||||
class Parse(object):
|
||||
|
||||
def __init__(self, fields, model='', context=None, on_change=None):
|
||||
self.fields = fields
|
||||
self.model = model
|
||||
self.col = CONFIG['client.modepda'] and 1 or 6
|
||||
self.focusable = None
|
||||
self.add_widget_end = []
|
||||
self.container = None
|
||||
self.spin_limit = gtk.SpinButton(climb_rate=1, digits=0)
|
||||
self.spin_limit.show()
|
||||
self.spin_offset = gtk.SpinButton(climb_rate=1, digits=0)
|
||||
self.spin_offset.show()
|
||||
self.title = 'Form'
|
||||
self.notebooks = []
|
||||
self.dict_widget = {}
|
||||
self.context = context or {}
|
||||
self.on_change = on_change
|
||||
|
||||
def _psr_start(self, name, attrs):
|
||||
|
||||
if name in ('form','tree'):
|
||||
self.title = attrs.get('string', self.title)
|
||||
self.container.new(self.col)
|
||||
elif name == 'field':
|
||||
if attrs['name'] in self.fields:
|
||||
val = attrs.get('select', None)
|
||||
if val is None:
|
||||
val = self.fields[attrs['name']].get('select', 0)
|
||||
val = int(val)
|
||||
if val:
|
||||
if val <= 1:
|
||||
self.add_widget(attrs, val)
|
||||
else:
|
||||
self.add_widget_end.append((attrs, val))
|
||||
|
||||
def add_widget(self, attrs, val):
|
||||
ftype = attrs.get('widget', self.fields[str(attrs['name'])]['type'])
|
||||
self.fields[str(attrs['name'])].update(attrs)
|
||||
self.fields[str(attrs['name'])]['model']=self.model
|
||||
if ftype not in WIDGETS_TYPE:
|
||||
return False
|
||||
widget_act = WIDGETS_TYPE[ftype][0](str(attrs['name']),
|
||||
attrs=self.fields[attrs['name']], context=self.context,
|
||||
on_change=self.on_change)
|
||||
if 'string' in self.fields[str(attrs['name'])]:
|
||||
label = self.fields[str(attrs['name'])]['string'] + _(':')
|
||||
else:
|
||||
label = None
|
||||
size = WIDGETS_TYPE[ftype][1]
|
||||
if not self.focusable:
|
||||
self.focusable = widget_act.widget
|
||||
wid = self.container.wid_add(widget_act.widget, size, label,
|
||||
int(self.fields[str(attrs['name'])].get('expand',0)))
|
||||
if int(val) <= 1:
|
||||
wid.show()
|
||||
self.dict_widget[str(attrs['name'])] = (widget_act, wid, int(val))
|
||||
|
||||
def add_parameters(self):
|
||||
hb_param = gtk.HBox(spacing=3)
|
||||
hb_param.pack_start(gtk.Label(_('Limit:')), expand=False, fill=False)
|
||||
|
||||
self.spin_limit.set_numeric(False)
|
||||
self.spin_limit.set_adjustment(gtk.Adjustment(value=CONFIG['client.limit'],
|
||||
lower=1, upper=sys.maxint, step_incr=10, page_incr=100))
|
||||
self.spin_limit.set_property('visible', True)
|
||||
|
||||
hb_param.pack_start(self.spin_limit, expand=False, fill=False)
|
||||
|
||||
hb_param.pack_start(gtk.Label(_('Offset:')), expand=False, fill=False)
|
||||
|
||||
self.spin_offset.set_numeric(False)
|
||||
self.spin_offset.set_adjustment(gtk.Adjustment(value=0, lower=0,
|
||||
upper=sys.maxint, step_incr=CONFIG['client.limit'], page_incr=100))
|
||||
|
||||
hb_param.pack_start(self.spin_offset, expand=False, fill=False)
|
||||
|
||||
hb_param.show_all()
|
||||
return hb_param
|
||||
|
||||
def _psr_end(self, name):
|
||||
pass
|
||||
|
||||
def _psr_char(self, name):
|
||||
pass
|
||||
|
||||
def parse(self, xml_data):
|
||||
psr = expat.ParserCreate()
|
||||
psr.StartElementHandler = self._psr_start
|
||||
psr.EndElementHandler = self._psr_end
|
||||
psr.CharacterDataHandler = self._psr_char
|
||||
self.container = _container()
|
||||
|
||||
psr.Parse(xml_data)
|
||||
|
||||
vbox = gtk.VBox()
|
||||
vbox.show()
|
||||
vbox.pack_start(self.container.pop())
|
||||
|
||||
expander = gtk.Expander(_('Advanced Search'))
|
||||
expander.show()
|
||||
vbox2 = gtk.VBox()
|
||||
vbox2.show()
|
||||
|
||||
self.container = _container()
|
||||
self.container.new(self.col)
|
||||
for i in self.add_widget_end:
|
||||
self.add_widget(*i)
|
||||
if ('active' in self.fields) and ('active' not in self.dict_widget):
|
||||
val = self.fields['active'].get('select', 0)
|
||||
val = int(val)
|
||||
self.add_widget(self.fields['active'], val)
|
||||
|
||||
self.add_widget_end = []
|
||||
|
||||
vbox2.pack_start(self.container.pop())
|
||||
|
||||
vbox2.pack_start(self.add_parameters())
|
||||
expander.add(vbox2)
|
||||
vbox.pack_start(expander)
|
||||
|
||||
return (self.dict_widget, vbox)
|
||||
|
||||
|
||||
class Form(object):
|
||||
|
||||
def __init__(self, view, model=None, domain=None, call=None, context=None):
|
||||
if domain is None:
|
||||
domain = []
|
||||
if context is None:
|
||||
context = {}
|
||||
parser = Parse(view['fields'], model=model, context=context,
|
||||
on_change=self.on_change)
|
||||
self.fields = view['fields']
|
||||
self.model = model
|
||||
self.parser = parser
|
||||
self.call = call
|
||||
self.context = context
|
||||
(self.widgets, self.widget) = parser.parse(view['arch'])
|
||||
self.widget.show()
|
||||
self.spin_limit = parser.spin_limit
|
||||
self.spin_limit.connect('value-changed', self.limit_changed)
|
||||
self.spin_limit.set_activates_default(True)
|
||||
self.spin_offset = parser.spin_offset
|
||||
self.spin_offset.set_activates_default(True)
|
||||
self.focusable = parser.focusable
|
||||
self.id = 0
|
||||
self.name = parser.title
|
||||
for i in self.widgets.itervalues():
|
||||
i[0].sig_activate(self.sig_activate)
|
||||
self.spin_limit.connect_after('activate', self.sig_activate)
|
||||
self.spin_offset.connect_after('activate', self.sig_activate)
|
||||
|
||||
def clear(self):
|
||||
self.id = 0
|
||||
for i in self.widgets.itervalues():
|
||||
i[0].clear()
|
||||
self.spin_offset.set_value(0)
|
||||
|
||||
def next(self):
|
||||
self.spin_offset.set_value(self.spin_offset.get_value()
|
||||
+ self.spin_limit.get_value())
|
||||
|
||||
def prev(self):
|
||||
self.spin_offset.set_value(max(self.spin_offset.get_value()
|
||||
- self.spin_limit.get_value(), 0))
|
||||
|
||||
def on_change(self, *args):
|
||||
self.spin_offset.set_value(0)
|
||||
|
||||
def limit_changed(self, widget):
|
||||
self.spin_offset.set_increments(step=self.spin_limit.get_value(),
|
||||
page=100)
|
||||
|
||||
def set_limit(self, value):
|
||||
if not value:
|
||||
value = CONFIG['client.limit']
|
||||
return self.spin_limit.set_value(value)
|
||||
|
||||
def get_limit(self):
|
||||
return self.spin_limit.get_value()
|
||||
|
||||
def get_offset(self):
|
||||
return self.spin_offset.get_value()
|
||||
|
||||
def sig_activate(self, *args):
|
||||
if self.call:
|
||||
obj, fct = self.call
|
||||
fct(obj)
|
||||
|
||||
def _value_get(self):
|
||||
res = []
|
||||
for i in self.widgets:
|
||||
res += self.widgets[i][0].value
|
||||
return res
|
||||
|
||||
def _value_set(self, value):
|
||||
if not value:
|
||||
return
|
||||
for i in value:
|
||||
if i in self.widgets:
|
||||
self.widgets[i][0].value = value[i]
|
||||
|
||||
value = property(_value_get, _value_set)
|
||||
|
||||
import calendar
|
||||
import float
|
||||
import integer
|
||||
import selection
|
||||
import char
|
||||
import checkbox
|
||||
import reference
|
||||
|
||||
WIDGETS_TYPE = {
|
||||
'date': (calendar.Calendar, 2),
|
||||
'datetime': (calendar.Calendar, 2),
|
||||
'float': (float.Float, 2),
|
||||
'numeric': (float.Numeric, 2),
|
||||
'integer': (integer.Integer, 2),
|
||||
'biginteger': (integer.Integer, 2),
|
||||
'selection': (selection.Selection, 2),
|
||||
'many2one_selection': (selection.Selection, 2),
|
||||
'char': (char.Char, 2),
|
||||
'boolean': (checkbox.CheckBox, 2),
|
||||
'reference': (reference.Reference, 2),
|
||||
'text': (char.Char, 2),
|
||||
'email': (char.Char, 2),
|
||||
'url': (char.Char, 2),
|
||||
'many2one': (char.Char, 2),
|
||||
'one2many': (char.Char, 2),
|
||||
'one2many_form': (char.Char, 2),
|
||||
'one2many_list': (char.Char, 2),
|
||||
'many2many_edit': (char.Char, 2),
|
||||
'many2many': (char.Char, 2),
|
||||
'callto': (char.Char, 2),
|
||||
'sip': (char.Char, 2),
|
||||
'one2one': (char.Char, 2),
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
#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
|
||||
from interface import Interface
|
||||
import locale
|
||||
import gettext
|
||||
import gobject
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class Integer(Interface):
|
||||
|
||||
def __init__(self, name, attrs=None, context=None, on_change=None):
|
||||
super(Integer, self).__init__(name, attrs=attrs, context=context,
|
||||
on_change=on_change)
|
||||
self.widget = gtk.HBox(spacing=3)
|
||||
|
||||
self.liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
|
||||
self.combo = gtk.ComboBox(self.liststore)
|
||||
cell = gtk.CellRendererText()
|
||||
self.combo.pack_start(cell, True)
|
||||
self.combo.add_attribute(cell, 'text', 1)
|
||||
for oper in (['=', _('equals')],
|
||||
['between', _('is between')],
|
||||
['not between', _('is not between')],
|
||||
['!=', _('is different')],
|
||||
):
|
||||
self.liststore.append(oper)
|
||||
self.combo.set_active(0)
|
||||
self.widget.pack_start(self.combo, False, False)
|
||||
self.combo.connect('changed', self._changed)
|
||||
self.combo.connect('changed', self.on_change)
|
||||
|
||||
self.entry1 = gtk.Entry()
|
||||
self.entry1.set_max_length(0)
|
||||
self.entry1.set_width_chars(5)
|
||||
self.entry1.set_activates_default(True)
|
||||
self.entry1.set_alignment(1.0)
|
||||
self.entry1.connect('insert_text', self.sig_insert_text)
|
||||
self.entry1.connect('key_press_event', self.on_change)
|
||||
self.widget.pack_start(self.entry1, expand=True, fill=True)
|
||||
self.separator = gtk.Label('-')
|
||||
self.widget.pack_start(self.separator, expand=False, fill=False)
|
||||
self.entry2 = gtk.Entry()
|
||||
self.entry2.set_max_length(0)
|
||||
self.entry2.set_width_chars(5)
|
||||
self.entry2.set_activates_default(True)
|
||||
self.entry2.set_alignment(1.0)
|
||||
self.entry2.connect('insert_text', self.sig_insert_text)
|
||||
self.entry2.connect('key_press_event', self.on_change)
|
||||
self.widget.pack_start(self.entry2, expand=True, fill=True)
|
||||
|
||||
self.widget.show_all()
|
||||
self._changed(self.combo)
|
||||
|
||||
def _changed(self, widget):
|
||||
oper = self.liststore.get_value(self.combo.get_active_iter(), 0)
|
||||
if oper in ('=', '!='):
|
||||
self.entry2.hide()
|
||||
self.separator.hide()
|
||||
else:
|
||||
self.entry2.show()
|
||||
self.separator.show()
|
||||
|
||||
def _value_get(self):
|
||||
try:
|
||||
value1 = locale.atoi(self.entry1.get_text())
|
||||
except ValueError:
|
||||
value1 = False
|
||||
try:
|
||||
value2 = locale.atoi(self.entry2.get_text())
|
||||
except ValueError:
|
||||
value2 = False
|
||||
return self._get_clause(value1, value2)
|
||||
|
||||
def _get_clause(self, value1, value2):
|
||||
oper = self.liststore.get_value(self.combo.get_active_iter(), 0)
|
||||
if oper in ('=', '!='):
|
||||
if self.entry1.get_text():
|
||||
return [(self.name, oper, value1 or 0)]
|
||||
else:
|
||||
return []
|
||||
else:
|
||||
res = []
|
||||
if oper == 'between':
|
||||
clause = 'AND'
|
||||
oper1 = '>='
|
||||
oper2 = '<='
|
||||
else:
|
||||
clause = 'OR'
|
||||
oper1 = '<='
|
||||
oper2 = '>='
|
||||
res.append(clause)
|
||||
if value1 is not False:
|
||||
res.append((self.name, oper1, value1))
|
||||
if value2 is not False:
|
||||
res.append((self.name, oper2, value2))
|
||||
return [res]
|
||||
|
||||
def _value_set(self, value):
|
||||
def conv(value):
|
||||
if value == False:
|
||||
return ''
|
||||
else:
|
||||
return locale.format('%d', value or 0, True)
|
||||
|
||||
i = self.liststore.get_iter_root()
|
||||
while i:
|
||||
if self.liststore.get_value(i, 0) == value[0]:
|
||||
self.combo.set_active_iter(i)
|
||||
break
|
||||
i = self.liststore.iter_next(i)
|
||||
|
||||
self.entry1.set_text(conv(value[1]))
|
||||
if len(value) == 2:
|
||||
self.entry2.set_text('')
|
||||
else:
|
||||
self.entry2.set_text(conv(value[2]))
|
||||
|
||||
value = property(_value_get, _value_set)
|
||||
|
||||
def clear(self):
|
||||
self.value = ('=', False, False)
|
||||
|
||||
def sig_activate(self, fct):
|
||||
self.entry1.connect_after('activate', fct)
|
||||
self.entry2.connect_after('activate', fct)
|
||||
|
||||
def sig_insert_text(self, widget, new_text, new_text_length, position):
|
||||
value = widget.get_text()
|
||||
position = widget.get_position()
|
||||
new_value = value[:position] + new_text + value[position:]
|
||||
if new_value == '-':
|
||||
return
|
||||
try:
|
||||
locale.atoi(new_value)
|
||||
except ValueError:
|
||||
widget.stop_emission('insert-text')
|
||||
@@ -1,36 +0,0 @@
|
||||
#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 gettext
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class Interface(object):
|
||||
"Interface for search widget"
|
||||
|
||||
def __init__(self, name, attrs=None, context=None, on_change=None):
|
||||
if attrs is None:
|
||||
attrs = {}
|
||||
self._value = None
|
||||
self.name = name
|
||||
self.model = attrs.get('model', None)
|
||||
self.attrs = attrs or {}
|
||||
self.context = context or {}
|
||||
self.on_change = on_change
|
||||
|
||||
def clear(self):
|
||||
self.value = ''
|
||||
|
||||
def _value_get(self):
|
||||
return self._value
|
||||
|
||||
def _value_set(self, value):
|
||||
self._value = value
|
||||
|
||||
value = property(_value_get, _value_set)
|
||||
|
||||
def _readonly_set(self, value):
|
||||
pass
|
||||
|
||||
def sig_activate(self, fct):
|
||||
pass
|
||||
@@ -1,130 +0,0 @@
|
||||
#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
|
||||
from interface import Interface
|
||||
import tryton.rpc as rpc
|
||||
import tryton.common as common
|
||||
import gobject
|
||||
from tryton.exceptions import TrytonServerError
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class Reference(Interface):
|
||||
|
||||
def __init__(self, name, attrs=None, context=None, on_change=None):
|
||||
super(Reference, self).__init__(name, attrs=attrs, context=context,
|
||||
on_change=on_change)
|
||||
|
||||
self.widget = gtk.HBox()
|
||||
|
||||
self.liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
|
||||
self.combo = gtk.ComboBox(self.liststore)
|
||||
cell = gtk.CellRendererText()
|
||||
self.combo.pack_start(cell, True)
|
||||
self.combo.add_attribute(cell, 'text', 1)
|
||||
for oper in (['like', _('equals')],
|
||||
['not like', _('is different')],
|
||||
):
|
||||
self.liststore.append(oper)
|
||||
self.combo.set_active(0)
|
||||
self.combo.connect('changed', self.on_change)
|
||||
self.widget.pack_start(self.combo, False, False)
|
||||
|
||||
self.entry = gtk.combo_box_entry_new_text()
|
||||
self.entry.child.set_editable(True)
|
||||
self.entry.child.set_property('activates_default', True)
|
||||
self.entry.child.connect('key_press_event', self.sig_key_press)
|
||||
self.entry.child.connect('key_press_event', self.on_change)
|
||||
self.entry.set_focus_chain([self.entry.child])
|
||||
self._selection = {}
|
||||
selection = self.attrs.get('selection', [])
|
||||
if 'relation' in self.attrs:
|
||||
try:
|
||||
result = rpc.execute('model',
|
||||
self.attrs['relation'], 'search_read',
|
||||
self.attrs.get('domain', []),
|
||||
0, None, None, ['rec_name'], rpc.CONTEXT)
|
||||
selection = [(x['id'], x['rec_name']) for x in result]
|
||||
except TrytonServerError, exception:
|
||||
common.process_exception(exception)
|
||||
selection = []
|
||||
else:
|
||||
if not isinstance(selection, (list, tuple)):
|
||||
try:
|
||||
selection = rpc.execute('model',
|
||||
self.attrs['model'], selection, rpc.CONTEXT)
|
||||
except TrytonServerError, exception:
|
||||
common.process_exception(exception)
|
||||
selection = []
|
||||
selection.sort(lambda x, y: cmp(x[1], y[1]))
|
||||
self.attrs['selection'] = selection
|
||||
self.set_popdown(selection)
|
||||
self.widget.pack_start(self.entry, True, True)
|
||||
self.widget.show_all()
|
||||
|
||||
def set_popdown(self, selection):
|
||||
model = self.entry.get_model()
|
||||
model.clear()
|
||||
lst = []
|
||||
for (i, j) in selection:
|
||||
name = str(j)
|
||||
if type(i) == type(1):
|
||||
name += ' (' + str(i) + ')'
|
||||
lst.append(name)
|
||||
self._selection[name] = i
|
||||
self.entry.append_text('')
|
||||
for name in lst:
|
||||
self.entry.append_text(name)
|
||||
completion = gtk.EntryCompletion()
|
||||
#Only available in PyGTK 2.6 and above.
|
||||
if hasattr(completion, 'set_inline_selection'):
|
||||
completion.set_inline_selection(True)
|
||||
completion.set_model(model)
|
||||
self.entry.child.set_completion(completion)
|
||||
completion.set_text_column(0)
|
||||
return lst
|
||||
|
||||
def sig_key_press(self, widget, event):
|
||||
if event.type == gtk.gdk.KEY_PRESS \
|
||||
and event.state & gtk.gdk.CONTROL_MASK \
|
||||
and event.keyval == gtk.keysyms.space:
|
||||
self.entry.popup()
|
||||
|
||||
def _value_get(self):
|
||||
value = self._selection.get(self.entry.child.get_text(), False)
|
||||
oper = self.liststore.get_value(self.combo.get_active_iter(), 0)
|
||||
if value or oper != 'like':
|
||||
return [(self.name, oper, value + ',%')]
|
||||
else:
|
||||
self.entry.child.set_text('')
|
||||
return []
|
||||
|
||||
def _value_set(self, value):
|
||||
i = self.liststore.get_iter_root()
|
||||
while i:
|
||||
if self.liststore.get_value(i, 0) == value[0]:
|
||||
self.combo.set_active_iter(i)
|
||||
break
|
||||
i = self.liststore.iter_next(i)
|
||||
if value[1] == False:
|
||||
self.entry.child.set_text('')
|
||||
return
|
||||
for long_text, sel_value in self._selection.items():
|
||||
if sel_value == value[1]:
|
||||
self.entry.child.set_text(long_text)
|
||||
break
|
||||
|
||||
value = property(_value_get, _value_set, None,
|
||||
'The content of the widget or ValueError if not valid')
|
||||
|
||||
def clear(self):
|
||||
self.value = ['like', False]
|
||||
|
||||
def _readonly_set(self, value):
|
||||
self.combo.set_sensitive(not value)
|
||||
self.entry.set_sensitive(not value)
|
||||
|
||||
def sig_activate(self, fct):
|
||||
self.entry.child.connect_after('activate', fct)
|
||||
@@ -1,135 +0,0 @@
|
||||
#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 operator
|
||||
from interface import Interface
|
||||
import tryton.rpc as rpc
|
||||
import tryton.common as common
|
||||
import gobject
|
||||
import gettext
|
||||
from tryton.pyson import PYSONDecoder
|
||||
from tryton.exceptions import TrytonServerError
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
class Selection(Interface):
|
||||
|
||||
def __init__(self, name, attrs=None, context=None, on_change=None):
|
||||
super(Selection, self).__init__(name, attrs=attrs, context=context,
|
||||
on_change=on_change)
|
||||
|
||||
self.widget = gtk.HBox()
|
||||
|
||||
self.liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
|
||||
self.combo = gtk.ComboBox(self.liststore)
|
||||
cell = gtk.CellRendererText()
|
||||
self.combo.pack_start(cell, True)
|
||||
self.combo.add_attribute(cell, 'text', 1)
|
||||
for oper in (['=', _('equals')],
|
||||
['!=', _('is different')],
|
||||
):
|
||||
self.liststore.append(oper)
|
||||
self.combo.set_active(0)
|
||||
self.combo.connect('changed', self.on_change)
|
||||
self.widget.pack_start(self.combo, False, False)
|
||||
|
||||
self.entry = gtk.combo_box_entry_new_text()
|
||||
self.entry.child.set_editable(True)
|
||||
self.entry.child.set_property('activates_default', True)
|
||||
self.entry.child.connect('key_press_event', self.sig_key_press)
|
||||
self.entry.child.connect('key_press_event', self.on_change)
|
||||
self.entry.set_focus_chain([self.entry.child])
|
||||
self._selection = {}
|
||||
selection = self.attrs.get('selection', [])
|
||||
if 'relation' in self.attrs:
|
||||
if not self.attrs.get('domain'):
|
||||
domain = []
|
||||
else:
|
||||
domain = PYSONDecoder(rpc.CONTEXT).decode(self.attrs['domain'])
|
||||
try:
|
||||
result = rpc.execute('model', self.attrs['relation'],
|
||||
'search_read', domain, 0, None, None,
|
||||
['rec_name'], rpc.CONTEXT)
|
||||
selection = [(x['id'], x['rec_name']) for x in result]
|
||||
except TrytonServerError, exception:
|
||||
common.process_exception(exception)
|
||||
selection = []
|
||||
else:
|
||||
if not isinstance(selection, (list, tuple)):
|
||||
try:
|
||||
selection = rpc.execute('model',
|
||||
self.attrs['model'], selection, rpc.CONTEXT)
|
||||
except TrytonServerError, exception:
|
||||
common.process_exception(exception)
|
||||
selection = []
|
||||
selection.sort(key=operator.itemgetter(1))
|
||||
self.attrs['selection'] = selection
|
||||
self.set_popdown(selection)
|
||||
self.widget.pack_start(self.entry, True, True)
|
||||
self.widget.show_all()
|
||||
|
||||
def set_popdown(self, selection):
|
||||
model = self.entry.get_model()
|
||||
model.clear()
|
||||
self._selection = {}
|
||||
lst = []
|
||||
for (i, j) in selection:
|
||||
name = str(j)
|
||||
if type(i) == type(1):
|
||||
name += ' (' + str(i) + ')'
|
||||
lst.append(name)
|
||||
self._selection[name] = i
|
||||
self.entry.append_text('')
|
||||
for name in lst:
|
||||
self.entry.append_text(name)
|
||||
completion = gtk.EntryCompletion()
|
||||
#Only available in PyGTK 2.6 and above.
|
||||
if hasattr(completion, 'set_inline_selection'):
|
||||
completion.set_inline_selection(True)
|
||||
completion.set_model(model)
|
||||
self.entry.child.set_completion(completion)
|
||||
completion.set_text_column(0)
|
||||
return lst
|
||||
|
||||
def sig_key_press(self, widget, event):
|
||||
if event.type == gtk.gdk.KEY_PRESS \
|
||||
and event.state & gtk.gdk.CONTROL_MASK \
|
||||
and event.keyval == gtk.keysyms.space:
|
||||
self.entry.popup()
|
||||
|
||||
def _value_get(self):
|
||||
value = self._selection.get(self.entry.child.get_text(), False)
|
||||
oper = self.liststore.get_value(self.combo.get_active_iter(), 0)
|
||||
if value or oper != '=':
|
||||
return [(self.name, oper, value)]
|
||||
else:
|
||||
self.entry.child.set_text('')
|
||||
return []
|
||||
|
||||
def _value_set(self, value):
|
||||
i = self.liststore.get_iter_root()
|
||||
while i:
|
||||
if self.liststore.get_value(i, 0) == value[0]:
|
||||
self.combo.set_active_iter(i)
|
||||
break
|
||||
i = self.liststore.iter_next(i)
|
||||
if value[1] == False:
|
||||
self.entry.child.set_text('')
|
||||
return
|
||||
for long_text, sel_value in self._selection.items():
|
||||
if sel_value == value[1]:
|
||||
self.entry.child.set_text(long_text)
|
||||
break
|
||||
|
||||
def clear(self):
|
||||
self.value = ['=', False]
|
||||
|
||||
value = property(_value_get, _value_set, None,
|
||||
'The content of the widget or ValueError if not valid')
|
||||
|
||||
def _readonly_set(self, value):
|
||||
self.combo.set_sensitive(not value)
|
||||
self.entry.set_sensitive(not value)
|
||||
|
||||
def sig_activate(self, fct):
|
||||
self.entry.child.connect_after('activate', fct)
|
||||
@@ -4,7 +4,6 @@ import gtk
|
||||
import gettext
|
||||
import tryton.rpc as rpc
|
||||
from tryton.gui.window.view_form.screen import Screen
|
||||
import tryton.gui.window.view_form.widget_search as widget_search
|
||||
from tryton.config import TRYTON_ICON
|
||||
from tryton.gui.window.win_form import WinForm
|
||||
from tryton.gui.window.nomodal import NoModal
|
||||
|
||||
Reference in new issue
Block a user