Simplify date, time and datetime widgets
- Use parse of dateutil - Remove datetime widget in list view - Allow to use widget attribute for date/time issue4457 review12311003
This commit is contained in:
1 parent
adff4c21bd
commit
4e360524f4
19 files changed
+902
-692
No files matched your search
@@ -1,3 +1,5 @@
|
||||
* Improve date/time widgets
|
||||
* Remove datetime widget on list/tree view
|
||||
* Allow to put many times the same field on tree view
|
||||
* Add search completion on dictionary widget
|
||||
* Merge host and port field in profile editor
|
||||
|
||||
@@ -114,6 +114,9 @@ class Action(object):
|
||||
ctx.update(action_ctx)
|
||||
ctx.update(context)
|
||||
action_ctx.update(context)
|
||||
if 'date_format' not in action_ctx:
|
||||
action_ctx['date_format'] = rpc.CONTEXT.get(
|
||||
'locale', {}).get('date', '%x')
|
||||
|
||||
ctx['context'] = ctx
|
||||
decoder = PYSONDecoder(ctx)
|
||||
|
||||
@@ -1,119 +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 gobject
|
||||
import pango
|
||||
|
||||
from date_widget import DateEntry
|
||||
|
||||
|
||||
class CellRendererDate(gtk.GenericCellRenderer):
|
||||
__gproperties__ = {
|
||||
'text': (gobject.TYPE_STRING, None, 'Text',
|
||||
'Text', gobject.PARAM_READWRITE),
|
||||
'foreground': (gobject.TYPE_STRING, None, 'Foreground',
|
||||
'Foreground', gobject.PARAM_WRITABLE),
|
||||
'foreground-set': (gobject.TYPE_INT, 'Foreground Set',
|
||||
'Foreground Set', 0, 10, 0, gobject.PARAM_READWRITE),
|
||||
'background': (gobject.TYPE_STRING, None, 'Background',
|
||||
'Background', gobject.PARAM_WRITABLE),
|
||||
'background-set': (gobject.TYPE_INT, 'Background Set',
|
||||
'Background Set', 0, 10, 0, gobject.PARAM_READWRITE),
|
||||
'editable': (gobject.TYPE_INT, 'Editable',
|
||||
'Editable', 0, 10, 0, gobject.PARAM_READWRITE),
|
||||
'visible': (gobject.TYPE_INT, 'Visible',
|
||||
'Visible', 0, 10, 0, gobject.PARAM_READWRITE),
|
||||
'strikethrough': (gobject.TYPE_BOOLEAN, 'Strikethrough',
|
||||
'Strikethrough', False, gobject.PARAM_WRITABLE),
|
||||
}
|
||||
|
||||
def __init__(self, format):
|
||||
self.__gobject_init__()
|
||||
self._renderer = gtk.CellRendererText()
|
||||
self.set_property("mode", self._renderer.get_property("mode"))
|
||||
|
||||
self.format = format
|
||||
self.cmd = ''
|
||||
self.text = self._renderer.get_property('text')
|
||||
self.editable = self._renderer.get_property('editable')
|
||||
self.visible = True
|
||||
|
||||
def set_sensitive(self, value):
|
||||
if hasattr(self._renderer, 'set_sensitive'):
|
||||
return self._renderer.set_sensitive(value)
|
||||
return self._renderer.set_property('sensitive', value)
|
||||
|
||||
def do_set_property(self, pspec, value):
|
||||
setattr(self, pspec.name, value)
|
||||
if pspec.name == 'visible':
|
||||
return
|
||||
self._renderer.set_property(pspec.name, value)
|
||||
self.set_property("mode", self._renderer.get_property("mode"))
|
||||
|
||||
def do_get_property(self, pspec):
|
||||
return getattr(self, pspec.name)
|
||||
|
||||
def on_get_size(self, widget, cell_area):
|
||||
return self._renderer.get_size(widget, cell_area)
|
||||
|
||||
def on_render(self, window, widget, background_area, cell_area,
|
||||
expose_area, flags):
|
||||
if not self.visible:
|
||||
return
|
||||
# Handle Pixmap window as pygtk failed
|
||||
if type(window) == gtk.gdk.Pixmap:
|
||||
layout = widget.create_pango_layout(self.text)
|
||||
layout.set_font_description(widget.style.font_desc)
|
||||
w, h = layout.get_size()
|
||||
xalign = self._renderer.get_property('xalign')
|
||||
x = int(cell_area.x + (cell_area.width - w / pango.SCALE) * xalign)
|
||||
y = int(cell_area.y + (cell_area.height - h / pango.SCALE) / 2)
|
||||
window.draw_layout(widget.style.text_gc[0], x, y, layout)
|
||||
return
|
||||
return self._renderer.render(window, widget, background_area,
|
||||
cell_area, expose_area, flags)
|
||||
|
||||
def on_activate(self, event, widget, path, background_area, cell_area,
|
||||
flags):
|
||||
if not self.visible:
|
||||
return
|
||||
return self._renderer.activate(event, widget, path, background_area,
|
||||
cell_area, flags)
|
||||
|
||||
def on_start_editing(self, event, widget, path, background_area,
|
||||
cell_area, flags):
|
||||
if not self.visible:
|
||||
return
|
||||
editable = DateEntry(self.format)
|
||||
editable.set_property('shadow-type', gtk.SHADOW_NONE)
|
||||
|
||||
colormap = editable.get_colormap()
|
||||
style = editable.get_style()
|
||||
if hasattr(self, 'background') \
|
||||
and getattr(self, 'background') != 'white':
|
||||
bg_color = colormap.alloc_color(getattr(self, 'background'))
|
||||
fg_color = gtk.gdk.color_parse("black")
|
||||
editable.modify_bg(gtk.STATE_ACTIVE, bg_color)
|
||||
editable.modify_base(gtk.STATE_NORMAL, bg_color)
|
||||
editable.modify_fg(gtk.STATE_NORMAL, fg_color)
|
||||
editable.modify_text(gtk.STATE_NORMAL, fg_color)
|
||||
editable.modify_text(gtk.STATE_INSENSITIVE, fg_color)
|
||||
else:
|
||||
editable.modify_bg(gtk.STATE_ACTIVE, style.bg[gtk.STATE_ACTIVE])
|
||||
editable.modify_base(gtk.STATE_NORMAL,
|
||||
style.base[gtk.STATE_NORMAL])
|
||||
editable.modify_fg(gtk.STATE_NORMAL, style.fg[gtk.STATE_NORMAL])
|
||||
editable.modify_text(gtk.STATE_NORMAL,
|
||||
style.text[gtk.STATE_NORMAL])
|
||||
editable.modify_text(gtk.STATE_INSENSITIVE,
|
||||
style.text[gtk.STATE_INSENSITIVE])
|
||||
|
||||
if self.text:
|
||||
editable.set_text(self.text)
|
||||
else:
|
||||
editable.clear()
|
||||
editable.grab_focus()
|
||||
editable.show()
|
||||
return editable
|
||||
|
||||
gobject.type_register(CellRendererDate)
|
||||
@@ -1501,7 +1501,7 @@ def timezoned_date(date, reverse=False):
|
||||
szone = dateutil.tz.tzutc()
|
||||
if reverse:
|
||||
lzone, szone = szone, lzone
|
||||
return date.replace(tzinfo=szone).astimezone(lzone)
|
||||
return date.replace(tzinfo=szone).astimezone(lzone).replace(tzinfo=None)
|
||||
|
||||
|
||||
def untimezoned_date(date):
|
||||
|
||||
@@ -1,340 +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.
|
||||
"Date Widget"
|
||||
|
||||
import gobject
|
||||
import gtk
|
||||
import re
|
||||
import time
|
||||
import datetime
|
||||
import gettext
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from datetime_strftime import datetime_strftime
|
||||
from common import TRYTON_ICON
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
__all__ = ['DateEntry']
|
||||
|
||||
MAPPING = {
|
||||
'%y': ('__', '([_ 0-9][_ 0-9])'),
|
||||
'%Y': ('____', '([_ 0-9][_ 0-9][_ 0-9][_ 0-9])'),
|
||||
'%m': ('__', '([_ 0-9][_ 0-9])'),
|
||||
'%d': ('__', '([_ 0-9][_ 0-9])'),
|
||||
'%H': ('__', '([_ 0-9][_ 0-9])'),
|
||||
'%I': ('__', '([_ 0-9][_ 0-9])'),
|
||||
'%M': ('__', '([_ 0-9][_ 0-9])'),
|
||||
'%S': ('__', '([_ 0-9][_ 0-9])'),
|
||||
'%p': ('__', '([_ AP][_ M])'),
|
||||
'%f': ('______', '([_0-9][_0-9][_0-9][_0-9][_0-9][_0-9])'),
|
||||
}
|
||||
OPERATORS = {
|
||||
gtk.keysyms.S: relativedelta(seconds=-1),
|
||||
gtk.keysyms.s: relativedelta(seconds=1),
|
||||
gtk.keysyms.I: relativedelta(minutes=-1),
|
||||
gtk.keysyms.i: relativedelta(minutes=1),
|
||||
gtk.keysyms.H: relativedelta(hours=-1),
|
||||
gtk.keysyms.h: relativedelta(hours=1),
|
||||
gtk.keysyms.D: relativedelta(days=-1),
|
||||
gtk.keysyms.d: relativedelta(days=1),
|
||||
gtk.keysyms.W: relativedelta(weeks=-1),
|
||||
gtk.keysyms.w: relativedelta(weeks=1),
|
||||
gtk.keysyms.M: relativedelta(months=-1),
|
||||
gtk.keysyms.m: relativedelta(months=1),
|
||||
gtk.keysyms.Y: relativedelta(years=-1),
|
||||
gtk.keysyms.y: relativedelta(years=1),
|
||||
}
|
||||
|
||||
|
||||
class DateEntry(gtk.Entry):
|
||||
|
||||
def __init__(self, format):
|
||||
super(DateEntry, self).__init__()
|
||||
|
||||
self.set_format(format)
|
||||
|
||||
self.connect('key-press-event', self._on_key_press)
|
||||
self.connect('insert-text', self._on_insert_text)
|
||||
self.connect('delete-text', self._on_delete_text)
|
||||
|
||||
self.connect('focus-in-event', self._focus_in)
|
||||
self.connect('focus-out-event', self._focus_out)
|
||||
|
||||
self.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, 'tryton-find')
|
||||
self.set_icon_tooltip_text(gtk.ENTRY_ICON_SECONDARY,
|
||||
_('Open the calendar <F2>'))
|
||||
self.connect('icon-press', DateEntry.cal_open)
|
||||
|
||||
self._interactive_input = True
|
||||
self.idle_set_position(0)
|
||||
|
||||
def set_format(self, format):
|
||||
self.format = format
|
||||
self.regex = self.initial_value = format
|
||||
for key, val in MAPPING.items():
|
||||
self.regex = self.regex.replace(key, val[1])
|
||||
self.initial_value = self.initial_value.replace(key, val[0])
|
||||
|
||||
self.regex = re.compile(self.regex)
|
||||
|
||||
assert self.regex.match(self.initial_value), \
|
||||
'Error, the initial value should be validated by regex'
|
||||
self.set_width_chars(len(self.initial_value) + 3) # space for icon
|
||||
self.set_max_length(len(self.initial_value))
|
||||
|
||||
def idle_set_position(self, value):
|
||||
def idle_func():
|
||||
with gtk.gdk.lock:
|
||||
self.set_position(value)
|
||||
return False
|
||||
gobject.idle_add(idle_func)
|
||||
|
||||
def _on_insert_text(self, editable, value, length, position):
|
||||
if not self._interactive_input:
|
||||
return
|
||||
|
||||
pos = self.get_position()
|
||||
|
||||
text = self.get_text()
|
||||
if not text:
|
||||
text = self.initial_value
|
||||
if text == self.initial_value and pos >= len(self.initial_value):
|
||||
pos = 0
|
||||
|
||||
for char in value:
|
||||
if pos >= len(self.initial_value):
|
||||
continue
|
||||
if char not in ('_', ' ') and char in self.initial_value[pos:]:
|
||||
pos += self.initial_value[pos:].index(char)
|
||||
else:
|
||||
while self.initial_value[pos] not in ('_', ' '):
|
||||
pos += 1
|
||||
text = text[:pos] + char + text[pos + 1:]
|
||||
pos += 1
|
||||
|
||||
if self.regex.match(text) and self.test_date(text):
|
||||
self.set_text(text)
|
||||
self.idle_set_position(pos)
|
||||
self.stop_emission('insert-text')
|
||||
self.show()
|
||||
return
|
||||
|
||||
def _on_delete_text(self, editable, start, end):
|
||||
if not self._interactive_input:
|
||||
return
|
||||
|
||||
if start > len(self.initial_value):
|
||||
start = len(self.initial_value)
|
||||
if start < 0:
|
||||
start = 0
|
||||
if end > len(self.initial_value):
|
||||
end = len(self.initial_value)
|
||||
if end < 0:
|
||||
end = 0
|
||||
while (start > 0
|
||||
and self.initial_value[start] not in ['_', ' ', '0', 'X']):
|
||||
start -= 1
|
||||
text = self.get_text()
|
||||
text = text[:start] + self.initial_value[start:end] + text[end:]
|
||||
self.set_text(text)
|
||||
self.idle_set_position(start)
|
||||
self.stop_emission('delete-text')
|
||||
return
|
||||
|
||||
def _focus_in(self, editable, event):
|
||||
if not self.get_text():
|
||||
self.set_text(self.initial_value)
|
||||
|
||||
def _focus_out(self, editable, event):
|
||||
if self.get_text() == self.initial_value or not self.date_get():
|
||||
self.set_text('')
|
||||
|
||||
def set_text(self, text):
|
||||
self._interactive_input = False
|
||||
try:
|
||||
# Force position as _on_insert_text use it
|
||||
position = self.get_position()
|
||||
self.set_position(0)
|
||||
gtk.Entry.set_text(self, text)
|
||||
self.idle_set_position(position)
|
||||
finally:
|
||||
self._interactive_input = True
|
||||
|
||||
def date_set(self, dt):
|
||||
if dt:
|
||||
self.set_text(datetime_strftime(dt, self.format))
|
||||
else:
|
||||
if self.is_focus():
|
||||
self.set_text(self.initial_value)
|
||||
else:
|
||||
self.set_text('')
|
||||
|
||||
def compute_date(self, text, default=None, year=None):
|
||||
if default is None:
|
||||
default = datetime_strftime(datetime.datetime.now(), self.format)
|
||||
if text == self.initial_value or not text:
|
||||
return ''
|
||||
|
||||
match = self.regex.match(text)
|
||||
if not match:
|
||||
return ''
|
||||
for i in range(len(match.groups())):
|
||||
val = match.group(i + 1)
|
||||
n = len(val)
|
||||
val = val.strip()
|
||||
val = val.strip('_')
|
||||
if n != 4:
|
||||
val = val.lstrip('0')
|
||||
if not val:
|
||||
continue
|
||||
fchar = '0'
|
||||
if n == 4:
|
||||
fchar = '_'
|
||||
if len(val) == 1:
|
||||
val = '0' + val
|
||||
if year and n == 4 and len(val) != 4:
|
||||
val = year
|
||||
val = (fchar * (n - len(val))) + val
|
||||
start = match.start(i + 1)
|
||||
end = match.end(i + 1)
|
||||
text = text[:start] + val + text[end:]
|
||||
|
||||
for a in range(len(self.initial_value)):
|
||||
if self.initial_value[a] == text[a]:
|
||||
text = text[:a] + default[a] + text[a + 1:]
|
||||
return text
|
||||
|
||||
def test_date(self, text):
|
||||
default = datetime_strftime(datetime.datetime(2000, 1, 1),
|
||||
self.format)
|
||||
try:
|
||||
time.strptime(
|
||||
self.compute_date(text, default=default, year='2000'),
|
||||
self.format)
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def date_get(self, set_text=True):
|
||||
res = None
|
||||
date = self.compute_date(self.get_text())
|
||||
try:
|
||||
res = datetime.datetime.strptime(date, self.format)
|
||||
except ValueError:
|
||||
return None
|
||||
if set_text:
|
||||
self.set_text(date)
|
||||
return res
|
||||
|
||||
def delete_text(self, start, end):
|
||||
self._interactive_input = False
|
||||
try:
|
||||
gtk.Entry.delete_text(self, start, end)
|
||||
finally:
|
||||
self._interactive_input = True
|
||||
|
||||
def insert_text(self, text, position=0):
|
||||
self._interactive_input = False
|
||||
try:
|
||||
gtk.Entry.insert_text(self, text, position)
|
||||
finally:
|
||||
self._interactive_input = True
|
||||
|
||||
def clear(self):
|
||||
if self.is_focus():
|
||||
self.set_text(self.initial_value)
|
||||
else:
|
||||
self.set_text('')
|
||||
|
||||
def _on_key_press(self, editable, event):
|
||||
if not self.get_editable():
|
||||
return False
|
||||
if event.keyval in (gtk.keysyms.KP_Equal, gtk.keysyms.equal):
|
||||
now = datetime.datetime.now()
|
||||
self.date_set(now)
|
||||
self.stop_emission("key-press-event")
|
||||
return True
|
||||
elif event.keyval == gtk.keysyms.F2:
|
||||
DateEntry.cal_open(editable)
|
||||
self.stop_emission("key-press-event")
|
||||
return True
|
||||
elif event.keyval in OPERATORS:
|
||||
date = self.date_get()
|
||||
if date:
|
||||
self.date_set(date + OPERATORS[event.keyval])
|
||||
self.stop_emission("key-press-event")
|
||||
return True
|
||||
return False
|
||||
|
||||
def cal_open(self, *args):
|
||||
if not self.get_editable():
|
||||
return False
|
||||
parent = self.get_toplevel()
|
||||
dialog = 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))
|
||||
dialog.set_has_separator(True)
|
||||
dialog.set_icon(TRYTON_ICON)
|
||||
dialog.set_default_response(gtk.RESPONSE_OK)
|
||||
|
||||
calendar = gtk.Calendar()
|
||||
calendar.set_display_options(
|
||||
gtk.CALENDAR_SHOW_HEADING |
|
||||
gtk.CALENDAR_SHOW_DAY_NAMES |
|
||||
gtk.CALENDAR_SHOW_WEEK_NUMBERS |
|
||||
gtk.CALENDAR_WEEK_START_MONDAY)
|
||||
calendar.connect('day-selected-double-click',
|
||||
lambda *x: dialog.response(gtk.RESPONSE_OK))
|
||||
dialog.vbox.pack_start(calendar, expand=True, fill=True)
|
||||
|
||||
date = self.date_get()
|
||||
if date:
|
||||
calendar.select_month(date.month - 1, date.year)
|
||||
calendar.select_day(date.day)
|
||||
else:
|
||||
date = datetime.datetime.now()
|
||||
|
||||
x, y = self.window.get_origin()
|
||||
allocation = self.get_allocation()
|
||||
dialog.move(x, y + allocation.height)
|
||||
|
||||
dialog.show_all()
|
||||
|
||||
response = dialog.run()
|
||||
if response == gtk.RESPONSE_OK:
|
||||
year, month, day = calendar.get_date()
|
||||
date += relativedelta(year=year, month=month + 1, day=day)
|
||||
self.date_set(date)
|
||||
|
||||
parent.present()
|
||||
self.grab_focus()
|
||||
dialog.destroy()
|
||||
|
||||
if __name__ == '__main__':
|
||||
win = gtk.Window()
|
||||
win.set_title('DateEntry')
|
||||
|
||||
def cb(window, event):
|
||||
gtk.main_quit()
|
||||
win.connect('delete-event', cb)
|
||||
vbox = gtk.VBox()
|
||||
win.add(vbox)
|
||||
|
||||
for format in (
|
||||
'%d.%m.%Y %H:%M:%S',
|
||||
'%m/%d/%Y %H:%M:%S',
|
||||
'%d/%m/%Y %H:%M:%S',
|
||||
'%Y-%m-%d %H:%M%S',
|
||||
'%d.%m.%Y',
|
||||
'%m/%d/%Y',
|
||||
'%d/%m/%Y',
|
||||
'%Y-%m-%d',
|
||||
):
|
||||
widget = DateEntry(format)
|
||||
vbox.pack_start(widget, False, False)
|
||||
|
||||
win.show_all()
|
||||
gtk.main()
|
||||
@@ -0,0 +1,561 @@
|
||||
# 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
|
||||
import datetime
|
||||
|
||||
import pygtk
|
||||
|
||||
pygtk.require('2.0')
|
||||
|
||||
import gobject
|
||||
import gtk
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from dateutil.parser import parse
|
||||
|
||||
from datetime_strftime import datetime_strftime as strftime
|
||||
|
||||
__all__ = ['Date', 'CellRendererDate', 'Time', 'CellRendererTime', 'DateTime']
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
def date_parse(text, format_='%x'):
|
||||
dayfirst = datetime.date(1988, 8, 16).strftime(format_).index('16') == 0
|
||||
yearfirst = datetime.date(1988, 8, 16).strftime(format_).index('88') <= 2
|
||||
return parse(text, dayfirst=dayfirst, yearfirst=yearfirst, ignoretz=True)
|
||||
|
||||
|
||||
class Date(gtk.Entry):
|
||||
__gtype_name__ = 'Date'
|
||||
__gproperties__ = {
|
||||
'value': (gobject.TYPE_PYOBJECT,
|
||||
_('Value'),
|
||||
_('Displayed value'),
|
||||
gobject.PARAM_READABLE | gobject.PARAM_WRITABLE),
|
||||
'format': (gobject.TYPE_STRING,
|
||||
'%x',
|
||||
_('Format'),
|
||||
_('Display format'),
|
||||
gobject.PARAM_READABLE | gobject.PARAM_WRITABLE),
|
||||
}
|
||||
__gsignals__ = {
|
||||
'date-changed': (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
|
||||
gobject.TYPE_NONE, ()),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.__date = None
|
||||
self.__format = '%x'
|
||||
|
||||
gtk.Entry.__init__(self)
|
||||
|
||||
self.set_width_chars(12)
|
||||
|
||||
self.connect('focus-out-event', self.focus_out)
|
||||
self.connect('activate', self.activate)
|
||||
|
||||
# Calendar Popup
|
||||
self.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, 'tryton-find')
|
||||
self.set_icon_tooltip_text(gtk.ENTRY_ICON_SECONDARY,
|
||||
_('Open the calendar'))
|
||||
self.connect('icon-press', self.icon_press)
|
||||
|
||||
self.__cal_popup = gtk.Window(gtk.WINDOW_POPUP)
|
||||
self.__cal_popup.set_events(
|
||||
self.__cal_popup.get_events() | gtk.gdk.KEY_PRESS_MASK)
|
||||
self.__cal_popup.set_resizable(False)
|
||||
self.__cal_popup.connect('delete-event', self.cal_popup_closed)
|
||||
self.__cal_popup.connect('key-press-event', self.cal_popup_key_pressed)
|
||||
self.__cal_popup.connect('button-press-event',
|
||||
self.cal_popup_button_pressed)
|
||||
|
||||
self.__calendar = gtk.Calendar()
|
||||
cal_options = gtk.CALENDAR_SHOW_DAY_NAMES | gtk.CALENDAR_SHOW_HEADING
|
||||
self.__calendar.set_display_options(cal_options)
|
||||
self.__cal_popup.add(self.__calendar)
|
||||
self.__calendar.connect('day-selected', self.cal_popup_changed)
|
||||
self.__calendar.connect('day-selected-double-click',
|
||||
self.cal_popup_double_click)
|
||||
self.__calendar.show()
|
||||
|
||||
def parse(self):
|
||||
text = self.get_text()
|
||||
date = None
|
||||
if text:
|
||||
try:
|
||||
date = date_parse(text, self.__format).date()
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
self.__date = date
|
||||
|
||||
def update_label(self):
|
||||
if not self.__date:
|
||||
self.set_text('')
|
||||
return
|
||||
self.set_text(strftime(self.__date, self.__format))
|
||||
|
||||
def icon_press(self, entry, icon_pos, event):
|
||||
if icon_pos == gtk.ENTRY_ICON_SECONDARY:
|
||||
self.cal_popup_open()
|
||||
|
||||
def cal_popup_open(self):
|
||||
self.parse()
|
||||
if self.__date:
|
||||
self.__calendar.select_month(
|
||||
self.__date.month - 1, self.__date.year)
|
||||
self.__calendar.select_day(self.__date.day)
|
||||
popup_position(self, self.__cal_popup)
|
||||
popup_show(self.__cal_popup)
|
||||
|
||||
def cal_popup_changed(self, calendar):
|
||||
year, month, day = self.__calendar.get_date()
|
||||
self.__date = datetime.date(year, month + 1, day)
|
||||
|
||||
self.update_label()
|
||||
|
||||
self.emit('date-changed')
|
||||
|
||||
def cal_popup_double_click(self, calendar):
|
||||
self.cal_popup_hide()
|
||||
|
||||
def cal_popup_key_pressed(self, calendar, event):
|
||||
if event.keyval != gtk.keysyms.Escape:
|
||||
return False
|
||||
|
||||
self.stop_emission('key-press-event')
|
||||
self.cal_popup_hide()
|
||||
return True
|
||||
|
||||
def cal_popup_button_pressed(self, calendar, event):
|
||||
child = event.window
|
||||
if child != calendar.window:
|
||||
while child:
|
||||
if child == calendar.window:
|
||||
return False
|
||||
child = child.get_parent()
|
||||
self.cal_popup_hide()
|
||||
return True
|
||||
|
||||
def cal_popup_closed(self, popup):
|
||||
self.cal_popup_hide()
|
||||
return True
|
||||
|
||||
def cal_popup_hide(self):
|
||||
popup_hide(self.__cal_popup)
|
||||
self.grab_focus()
|
||||
self.emit('date-changed')
|
||||
|
||||
def focus_out(self, entry, event):
|
||||
self.parse()
|
||||
self.update_label()
|
||||
self.emit('date-changed')
|
||||
return False
|
||||
|
||||
def activate(self, entry):
|
||||
self.parse()
|
||||
self.update_label()
|
||||
self.emit('date-changed')
|
||||
return False
|
||||
|
||||
def do_set_property(self, prop, value):
|
||||
if prop.name == 'value':
|
||||
if isinstance(value, basestring):
|
||||
self.set_text(value)
|
||||
self.parse()
|
||||
value = self.__date
|
||||
if value:
|
||||
if isinstance(value, datetime.datetime):
|
||||
value = value.date()
|
||||
assert isinstance(value, datetime.date), value
|
||||
self.__date = value
|
||||
self.update_label()
|
||||
elif prop.name == 'format':
|
||||
self.__format = value
|
||||
self.update_label()
|
||||
|
||||
def do_get_property(self, prop):
|
||||
if prop.name == 'value':
|
||||
return self.__date
|
||||
elif prop.name == 'format':
|
||||
return self.__format
|
||||
|
||||
gobject.type_register(Date)
|
||||
|
||||
|
||||
class CellRendererDate(gtk.CellRendererText):
|
||||
__gproperties__ = {
|
||||
'format': (gobject.TYPE_STRING,
|
||||
_('Format'),
|
||||
_('Display format'),
|
||||
'%x',
|
||||
gobject.PARAM_READABLE | gobject.PARAM_WRITABLE),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.__format = '%x'
|
||||
|
||||
gtk.CellRendererText.__init__(self)
|
||||
|
||||
def do_set_property(self, prop, value):
|
||||
if prop.name == 'format':
|
||||
self.__format = value
|
||||
return
|
||||
gtk.CellRendererText.set_property(self, prop, value)
|
||||
|
||||
def do_get_property(self, prop):
|
||||
if prop.name == 'format':
|
||||
return self.__format
|
||||
return gtk.CellRendererText.get_property(self, prop)
|
||||
|
||||
def do_start_editing(
|
||||
self, event, widget, path, background_area, cell_area, flags):
|
||||
widget = add_operators(Date()) # TODO add_operators has option
|
||||
widget.props.format = self.props.format
|
||||
widget.props.value = self.props.text
|
||||
if self.props.background_set:
|
||||
widget.modify_base(gtk.STATE_NORMAL, self.props.background_gdk)
|
||||
if self.props.foreground_set:
|
||||
widget.modify_fg(gtk.STATE_NORMAL, self.props.foreground_gdk)
|
||||
widget.grab_focus()
|
||||
widget.show()
|
||||
return widget
|
||||
|
||||
gobject.type_register(CellRendererDate)
|
||||
|
||||
|
||||
class Time(gtk.ComboBoxEntry):
|
||||
__gtype_name__ = 'Time'
|
||||
__gproperties__ = {
|
||||
'value': (gobject.TYPE_PYOBJECT,
|
||||
_('Value'),
|
||||
_('Displayed value'),
|
||||
gobject.PARAM_READABLE | gobject.PARAM_WRITABLE),
|
||||
'format': (gobject.TYPE_STRING,
|
||||
_('Format'),
|
||||
_('Display format'),
|
||||
'%X',
|
||||
gobject.PARAM_READABLE | gobject.PARAM_WRITABLE),
|
||||
}
|
||||
__gsignals__ = {
|
||||
'time-changed': (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
|
||||
gobject.TYPE_NONE, ()),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.__time = None
|
||||
self.__format = '%X'
|
||||
|
||||
gtk.ComboBoxEntry.__init__(self)
|
||||
|
||||
self.__entry = self.get_child()
|
||||
self.__entry.set_width_chars(10)
|
||||
|
||||
self.__entry.connect('focus-out-event', self.focus_out)
|
||||
self.__entry.connect('activate', self.activate)
|
||||
self.connect('changed', self.changed)
|
||||
|
||||
self.__model = gtk.ListStore(
|
||||
gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
|
||||
self.update_model()
|
||||
self.set_model(self.__model)
|
||||
self.set_text_column(0)
|
||||
|
||||
def parse(self):
|
||||
text = self.__entry.get_text()
|
||||
time = None
|
||||
if text:
|
||||
try:
|
||||
time = date_parse(text).time()
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
self.__time = time
|
||||
|
||||
def update_label(self):
|
||||
if not self.__time:
|
||||
self.__entry.set_text('')
|
||||
return
|
||||
|
||||
self.__entry.set_text(self.__time.strftime(self.__format))
|
||||
|
||||
def update_model(self):
|
||||
self.__model.clear()
|
||||
timelist_set_list(
|
||||
self.__model, datetime.time(0, 0), datetime.time(23, 59),
|
||||
self.__format)
|
||||
|
||||
def focus_out(self, entry, event):
|
||||
self.parse()
|
||||
self.update_label()
|
||||
self.emit('time-changed')
|
||||
return False
|
||||
|
||||
def activate(self, entry):
|
||||
self.parse()
|
||||
self.update_label()
|
||||
self.emit('time-changed')
|
||||
return False
|
||||
|
||||
def changed(self, combobox):
|
||||
# "changed" signal is also triggered by text editing
|
||||
# so only parse when a row is active
|
||||
if combobox.get_active_iter():
|
||||
self.parse()
|
||||
self.update_label()
|
||||
self.emit('time-changed')
|
||||
return False
|
||||
|
||||
def do_set_property(self, prop, value):
|
||||
if prop.name == 'value':
|
||||
if isinstance(value, basestring):
|
||||
self.__entry.set_text(value)
|
||||
self.parse()
|
||||
value = self.__time
|
||||
if value:
|
||||
if isinstance(value, datetime.datetime):
|
||||
value = value.time()
|
||||
self.__time = value
|
||||
self.update_label()
|
||||
elif prop.name == 'format':
|
||||
self.__format = value
|
||||
self.update_label()
|
||||
self.update_model()
|
||||
|
||||
def do_get_property(self, prop):
|
||||
if prop.name == 'value':
|
||||
return self.__time
|
||||
elif prop.name == 'format':
|
||||
return self.__format
|
||||
|
||||
gobject.type_register(Time)
|
||||
|
||||
|
||||
class CellRendererTime(gtk.CellRendererText):
|
||||
__gproperties__ = {
|
||||
'format': (gobject.TYPE_STRING,
|
||||
'%X',
|
||||
_('Format'),
|
||||
_('Display format'),
|
||||
gobject.PARAM_READABLE | gobject.PARAM_WRITABLE),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.__format = '%X'
|
||||
|
||||
gtk.CellRendererText.__init__(self)
|
||||
|
||||
def do_set_property(self, prop, value):
|
||||
if prop.name == 'format':
|
||||
self.__format = value
|
||||
return
|
||||
gtk.CellRendererText.set_property(self, prop, value)
|
||||
|
||||
def do_get_property(self, prop):
|
||||
if prop.name == 'format':
|
||||
return self.__format
|
||||
return gtk.CellRendererText.get_property(self, prop)
|
||||
|
||||
def do_start_editing(
|
||||
self, event, widget, path, background_area, cell_area, flags):
|
||||
if not self.props.editable:
|
||||
return
|
||||
widget = add_operators(Time()) # TODO add_operators has option
|
||||
widget.props.format = self.props.format
|
||||
widget.props.value = self.props.text
|
||||
if self.props.background_set:
|
||||
widget.modify_base(gtk.STATE_NORMAL, self.props.background_gdk)
|
||||
if self.props.foreground_set:
|
||||
widget.modify_fg(gtk.STATE_NORMAL, self.props.foreground_gdk)
|
||||
widget.grab_focus()
|
||||
widget.show()
|
||||
return widget
|
||||
|
||||
gobject.type_register(CellRendererTime)
|
||||
|
||||
|
||||
class DateTime(gtk.HBox):
|
||||
__gtype_name__ = 'DateTime'
|
||||
__gproperties__ = {
|
||||
'value': (gobject.TYPE_PYOBJECT,
|
||||
_('Value'),
|
||||
_('Displayed value'),
|
||||
gobject.PARAM_READABLE | gobject.PARAM_WRITABLE),
|
||||
'date-format': (gobject.TYPE_STRING,
|
||||
'%x',
|
||||
_('Date Format'),
|
||||
_('Displayed date format'),
|
||||
gobject.PARAM_READABLE | gobject.PARAM_WRITABLE),
|
||||
'time-format': (gobject.TYPE_STRING,
|
||||
'%X',
|
||||
_('Date Format'),
|
||||
_('Displayed date format'),
|
||||
gobject.PARAM_READABLE | gobject.PARAM_WRITABLE),
|
||||
}
|
||||
__gsignals__ = {
|
||||
'datetime-changed': (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
|
||||
gobject.TYPE_NONE, ()),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
gtk.HBox.__init__(self, spacing=4)
|
||||
|
||||
self.__date = Date()
|
||||
self.pack_start(self.__date, True, True, 0)
|
||||
self.__date.show()
|
||||
self.__date.connect('date-changed',
|
||||
lambda e: self.emit('datetime-changed'))
|
||||
|
||||
self.__time = Time()
|
||||
self.pack_start(self.__time, True, True, 0)
|
||||
self.__time.show()
|
||||
self.__time.connect('time-changed',
|
||||
lambda e: self.emit('datetime-changed'))
|
||||
|
||||
def do_set_property(self, prop, value):
|
||||
if prop.name == 'value':
|
||||
self.__date.props.value = value
|
||||
self.__time.props.value = value
|
||||
elif prop.name == 'date-format':
|
||||
self.__date.props.format = value
|
||||
elif prop.name == 'time-format':
|
||||
self.__time.props.format = value
|
||||
|
||||
def do_get_property(self, prop):
|
||||
if prop.name == 'value':
|
||||
date = self.__date.props.value
|
||||
time = self.__time.props.value or datetime.time()
|
||||
if date:
|
||||
return datetime.datetime.combine(date, time)
|
||||
else:
|
||||
return
|
||||
elif prop.name == 'date-format':
|
||||
return self.__date.props.format
|
||||
elif prop.name == 'time-format':
|
||||
return self.__time.props.format
|
||||
|
||||
gobject.type_register(DateTime)
|
||||
|
||||
|
||||
def popup_position(widget, popup):
|
||||
|
||||
req_width, req_height = popup.size_request()
|
||||
x, y = widget.window.get_origin()
|
||||
allocation = widget.get_allocation()
|
||||
width = allocation.width
|
||||
height = allocation.height
|
||||
|
||||
x += width - req_width
|
||||
y += height
|
||||
|
||||
x = max(x, 0)
|
||||
y = max(y, 0)
|
||||
|
||||
popup.move(x, y)
|
||||
|
||||
|
||||
def popup_show(popup):
|
||||
popup.show()
|
||||
popup.grab_focus()
|
||||
popup.grab_add()
|
||||
|
||||
cursor = gtk.gdk.Cursor(gtk.gdk.ARROW)
|
||||
gtk.gdk.pointer_grab(popup.window, True,
|
||||
(gtk.gdk.BUTTON_PRESS_MASK
|
||||
| gtk.gdk.BUTTON_RELEASE_MASK
|
||||
| gtk.gdk.POINTER_MOTION_MASK),
|
||||
None, cursor, 0L)
|
||||
|
||||
|
||||
def popup_hide(popup):
|
||||
popup.hide()
|
||||
popup.grab_remove()
|
||||
gtk.gdk.pointer_ungrab(0L)
|
||||
|
||||
|
||||
def timelist_set_list(model, min_, max_, format_):
|
||||
time = min_
|
||||
delta = 30
|
||||
while time < max_:
|
||||
model.append((time.strftime(format_), time))
|
||||
hour = time.hour
|
||||
minute = time.minute + delta
|
||||
hour += minute / 60
|
||||
if hour >= 24:
|
||||
break
|
||||
minute = minute % 60
|
||||
time = datetime.time(hour, minute)
|
||||
|
||||
|
||||
def add_operators(widget):
|
||||
def key_press(editable, event):
|
||||
if event.keyval in OPERATORS:
|
||||
value = widget.props.value
|
||||
if value:
|
||||
if isinstance(value, datetime.time):
|
||||
value = datetime.datetime.combine(
|
||||
datetime.date.today(), value)
|
||||
try:
|
||||
widget.props.value = value + OPERATORS[event.keyval]
|
||||
except TypeError:
|
||||
return False
|
||||
return True
|
||||
elif event.keyval in (gtk.keysyms.KP_Equal, gtk.keysyms.equal):
|
||||
widget.props.value = datetime.datetime.now()
|
||||
return True
|
||||
return False
|
||||
|
||||
if isinstance(widget, gtk.ComboBoxEntry):
|
||||
editable = widget.get_child()
|
||||
else:
|
||||
editable = widget
|
||||
editable.connect('key-press-event', key_press)
|
||||
return widget
|
||||
|
||||
OPERATORS = {
|
||||
gtk.keysyms.S: relativedelta(seconds=-1),
|
||||
gtk.keysyms.s: relativedelta(seconds=1),
|
||||
gtk.keysyms.I: relativedelta(minutes=-1),
|
||||
gtk.keysyms.i: relativedelta(minutes=1),
|
||||
gtk.keysyms.H: relativedelta(hours=-1),
|
||||
gtk.keysyms.h: relativedelta(hours=1),
|
||||
gtk.keysyms.D: relativedelta(days=-1),
|
||||
gtk.keysyms.d: relativedelta(days=1),
|
||||
gtk.keysyms.W: relativedelta(weeks=-1),
|
||||
gtk.keysyms.w: relativedelta(weeks=1),
|
||||
gtk.keysyms.M: relativedelta(months=-1),
|
||||
gtk.keysyms.m: relativedelta(months=1),
|
||||
gtk.keysyms.Y: relativedelta(years=-1),
|
||||
gtk.keysyms.y: relativedelta(years=1),
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
win = gtk.Window()
|
||||
win.connect('delete-event', gtk.main_quit)
|
||||
|
||||
v = gtk.VBox()
|
||||
v.show()
|
||||
|
||||
d = add_operators(Date())
|
||||
d.show()
|
||||
v.pack_start(d, False, False)
|
||||
|
||||
t = add_operators(Time())
|
||||
t.show()
|
||||
v.pack_start(t, False, False)
|
||||
|
||||
t = add_operators(Time())
|
||||
t.props.format = '%H:%M'
|
||||
t.show()
|
||||
v.pack_start(t, False, False)
|
||||
|
||||
dt = DateTime()
|
||||
[add_operators(c) for c in dt.get_children()]
|
||||
dt.show()
|
||||
v.pack_start(dt, False, False)
|
||||
|
||||
win.add(v)
|
||||
win.show()
|
||||
|
||||
gtk.main()
|
||||
@@ -7,12 +7,11 @@ import locale
|
||||
import decimal
|
||||
from decimal import Decimal
|
||||
import datetime
|
||||
import time
|
||||
import io
|
||||
from collections import OrderedDict
|
||||
|
||||
from tryton.translate import date_format
|
||||
from tryton.common import untimezoned_date, timezoned_date, datetime_strftime
|
||||
from tryton.common.datetime_ import date_parse
|
||||
from tryton.pyson import PYSONDecoder
|
||||
|
||||
__all__ = ['DomainParser']
|
||||
@@ -220,8 +219,10 @@ def test_split_target_value():
|
||||
assert split_target_value(field, value) == result
|
||||
|
||||
|
||||
def convert_value(field, value):
|
||||
def convert_value(field, value, context=None):
|
||||
"Convert value for field"
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
def convert_boolean():
|
||||
if isinstance(value, basestring):
|
||||
@@ -256,28 +257,31 @@ def convert_value(field, value):
|
||||
return value
|
||||
|
||||
def convert_datetime():
|
||||
if not value:
|
||||
return
|
||||
format_ = context.get('date_format', '%X') + ' %x'
|
||||
try:
|
||||
return untimezoned_date(datetime.datetime(*time.strptime(value,
|
||||
date_format() + ' ' + time_format(field))[:6]))
|
||||
dt = date_parse(value, format_)
|
||||
if dt.time() == datetime.time.min:
|
||||
return dt
|
||||
return untimezoned_date(dt)
|
||||
except ValueError:
|
||||
try:
|
||||
return datetime.datetime(*time.strptime(value,
|
||||
date_format())[:6])
|
||||
except ValueError:
|
||||
return
|
||||
except TypeError:
|
||||
return
|
||||
|
||||
def convert_date():
|
||||
if not value:
|
||||
return
|
||||
format_ = context.get('date_format', '%x')
|
||||
try:
|
||||
return datetime.date(*time.strptime(value, date_format())[:3])
|
||||
return date_parse(value, format_).date()
|
||||
except (ValueError, TypeError):
|
||||
return
|
||||
|
||||
def convert_time():
|
||||
if not value:
|
||||
return
|
||||
try:
|
||||
return datetime.time(*time.strptime(value,
|
||||
time_format(field))[3:6])
|
||||
return date_parse(value).time()
|
||||
except (ValueError, TypeError):
|
||||
return
|
||||
|
||||
@@ -423,8 +427,10 @@ def test_convert_time():
|
||||
assert convert_value(field, value) == result
|
||||
|
||||
|
||||
def format_value(field, value, target=None):
|
||||
def format_value(field, value, target=None, context=None):
|
||||
"Format value for field"
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
def format_boolean():
|
||||
return _('True') if value else _('False')
|
||||
@@ -458,10 +464,10 @@ def format_value(field, value, target=None):
|
||||
def format_datetime():
|
||||
if not value:
|
||||
return ''
|
||||
format_ = date_format() + ' ' + time_format(field)
|
||||
format_ = context.get('date_format', '%x') + ' ' + time_format(field)
|
||||
if (not isinstance(value, datetime.datetime)
|
||||
or value.time() == datetime.time.min):
|
||||
format_ = date_format()
|
||||
format_ = '%x'
|
||||
time = value
|
||||
else:
|
||||
time = timezoned_date(value)
|
||||
@@ -470,7 +476,8 @@ def format_value(field, value, target=None):
|
||||
def format_date():
|
||||
if not value:
|
||||
return ''
|
||||
return datetime_strftime(value, date_format())
|
||||
format_ = context.get('date_format', '%x')
|
||||
return datetime_strftime(value, format_)
|
||||
|
||||
def format_time():
|
||||
if not value:
|
||||
@@ -495,7 +502,7 @@ def format_value(field, value, target=None):
|
||||
'many2one': format_many2one,
|
||||
}
|
||||
if isinstance(value, (list, tuple)):
|
||||
return ';'.join(format_value(field, x) for x in value)
|
||||
return ';'.join(format_value(field, x, context=context) for x in value)
|
||||
return quote(converts.get(field['type'],
|
||||
lambda: value if value is not None else '')())
|
||||
|
||||
@@ -589,10 +596,10 @@ def test_format_datetime():
|
||||
'format': '"%H:%M:%S"',
|
||||
}
|
||||
for value, result in (
|
||||
(datetime.date(2002, 12, 4), '12/04/2002'),
|
||||
(datetime.datetime(2002, 12, 4), '12/04/2002'),
|
||||
(datetime.date(2002, 12, 4), '12/04/02'),
|
||||
(datetime.datetime(2002, 12, 4), '12/04/02'),
|
||||
(untimezoned_date(datetime.datetime(2002, 12, 4, 12, 30)),
|
||||
'"12/04/2002 12:30:00"'),
|
||||
'"12/04/02 12:30:00"'),
|
||||
(False, ''),
|
||||
(None, ''),
|
||||
):
|
||||
@@ -604,7 +611,7 @@ def test_format_date():
|
||||
'type': 'date',
|
||||
}
|
||||
for value, result in (
|
||||
(datetime.date(2002, 12, 4), '12/04/2002'),
|
||||
(datetime.date(2002, 12, 4), '12/04/02'),
|
||||
(False, ''),
|
||||
(None, ''),
|
||||
):
|
||||
@@ -807,13 +814,14 @@ def test_operatorize():
|
||||
class DomainParser(object):
|
||||
"A parser for domain"
|
||||
|
||||
def __init__(self, fields):
|
||||
def __init__(self, fields, context=None):
|
||||
self.fields = OrderedDict((name, f)
|
||||
for name, f in fields.iteritems()
|
||||
if f.get('searchable', True))
|
||||
self.strings = dict((f['string'].lower(), f)
|
||||
for f in fields.itervalues()
|
||||
if f.get('searchable', True))
|
||||
self.context = context
|
||||
|
||||
def parse(self, input_):
|
||||
"Return domain for the input string"
|
||||
@@ -895,7 +903,7 @@ class DomainParser(object):
|
||||
operator = '!'
|
||||
else:
|
||||
operator = ''
|
||||
formatted_value = format_value(field, value, target)
|
||||
formatted_value = format_value(field, value, target, self.context)
|
||||
if (operator in OPERATORS and
|
||||
field['type'] in ('char', 'text', 'selection')
|
||||
and value == ''):
|
||||
@@ -1090,20 +1098,21 @@ class DomainParser(object):
|
||||
'datetime', 'date', 'time'):
|
||||
if value and '..' in value:
|
||||
lvalue, rvalue = value.split('..', 1)
|
||||
lvalue = convert_value(field, lvalue)
|
||||
rvalue = convert_value(field, rvalue)
|
||||
lvalue = convert_value(field, lvalue, self.context)
|
||||
rvalue = convert_value(field, rvalue, self.context)
|
||||
yield iter([
|
||||
(field_name, '>=', lvalue),
|
||||
(field_name, '<=', rvalue),
|
||||
])
|
||||
continue
|
||||
if isinstance(value, list):
|
||||
value = [convert_value(field, v) for v in value]
|
||||
value = [convert_value(field, v, self.context)
|
||||
for v in value]
|
||||
if field['type'] in ('many2one', 'one2many',
|
||||
'many2many', 'one2one'):
|
||||
field_name += '.rec_name'
|
||||
else:
|
||||
value = convert_value(field, value)
|
||||
value = convert_value(field, value, self.context)
|
||||
if 'like' in operator:
|
||||
value = likify(value)
|
||||
if target:
|
||||
@@ -1196,7 +1205,7 @@ def test_string():
|
||||
assert dom.string([]) == ''
|
||||
assert dom.string([('surname', 'ilike', '%Doe%')]) == '"(Sur)Name": Doe'
|
||||
assert dom.string([('date', '>=', datetime.date(2012, 10, 24))]) == \
|
||||
'Date: >=10/24/2012'
|
||||
'Date: >=10/24/12'
|
||||
assert dom.string([('selection', '=', '')]) == 'Selection: '
|
||||
assert dom.string([('selection', '=', None)]) == 'Selection: '
|
||||
assert dom.string([('selection', '!=', '')]) == 'Selection: !""'
|
||||
|
||||
@@ -15,7 +15,6 @@ from tryton.gui.window.revision import Revision
|
||||
from tryton.signal_event import SignalEvent
|
||||
from tryton.common import message, sur, sur_3b, COLOR_SCHEMES, timezoned_date
|
||||
import tryton.common as common
|
||||
from tryton.translate import date_format
|
||||
from tryton.common import RPCExecute, RPCException
|
||||
from tryton.common.datetime_strftime import datetime_strftime
|
||||
from tryton import plugins
|
||||
@@ -253,9 +252,8 @@ class Form(SignalEvent, TabContent):
|
||||
value = str(line.get(key, False) or '/')
|
||||
if line.get(key, False) \
|
||||
and key in ('create_date', 'write_date'):
|
||||
display_format = date_format() + ' %H:%M:%S'
|
||||
date = timezoned_date(line[key])
|
||||
value = common.datetime_strftime(date, display_format)
|
||||
value = common.datetime_strftime(date, '%X')
|
||||
message_str += val + ' ' + value + '\n'
|
||||
message_str += _('Model:') + ' ' + self.model
|
||||
message(message_str)
|
||||
@@ -272,7 +270,9 @@ class Form(SignalEvent, TabContent):
|
||||
except RPCException:
|
||||
return
|
||||
revision = self.screen.context.get('_datetime')
|
||||
revision = Revision(revisions, revision).run()
|
||||
format_ = self.screen.context.get('date_format', '%x')
|
||||
format_ += ' %X.%f'
|
||||
revision = Revision(revisions, revision, format_).run()
|
||||
# Prevent too old revision in form view
|
||||
if (self.screen.current_view.view_type == 'form'
|
||||
and revision
|
||||
@@ -294,7 +294,8 @@ class Form(SignalEvent, TabContent):
|
||||
def update_revision(self):
|
||||
revision = self.screen.context.get('_datetime')
|
||||
if revision:
|
||||
format_ = date_format() + ' %H:%M:%S.%f'
|
||||
format_ = self.screen.context.get('date_format', '%x')
|
||||
format_ += ' %X.%f'
|
||||
revision = datetime_strftime(revision, format_)
|
||||
self.title.set_label('%s @ %s' % (self.name, revision))
|
||||
else:
|
||||
|
||||
@@ -5,9 +5,8 @@ import gettext
|
||||
|
||||
from tryton.config import TRYTON_ICON
|
||||
from tryton.common import get_toplevel_window
|
||||
from tryton.common.date_widget import DateEntry
|
||||
from tryton.common.datetime_strftime import datetime_strftime
|
||||
from tryton.translate import date_format
|
||||
from tryton.common.datetime_ import date_parse
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
@@ -15,7 +14,7 @@ _ = gettext.gettext
|
||||
class Revision(object):
|
||||
'Ask revision'
|
||||
|
||||
def __init__(self, revisions, revision=None):
|
||||
def __init__(self, revisions, revision=None, format_='%x %X.%f'):
|
||||
self.parent = get_toplevel_window()
|
||||
self.win = gtk.Dialog(_('Revision'), self.parent,
|
||||
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||
@@ -30,45 +29,74 @@ class Revision(object):
|
||||
self.win.vbox.pack_start(gtk.HSeparator())
|
||||
hbox = gtk.HBox(spacing=3)
|
||||
hbox.pack_start(gtk.Label(_('Revision:')), expand=True, fill=True)
|
||||
format_ = date_format() + ' %H:%M:%S.%f'
|
||||
self.entry = DateEntry(format_)
|
||||
list_store = gtk.ListStore(str, str)
|
||||
combobox = gtk.ComboBoxEntry(list_store)
|
||||
self.entry = combobox.child
|
||||
self.entry.connect('focus-out-event', self.focus_out)
|
||||
self.entry.connect('activate', self.activate)
|
||||
combobox.connect('changed', self.changed)
|
||||
self.entry.set_property('activates_default', True)
|
||||
self._format = format_
|
||||
if revision:
|
||||
self.entry.set_text(datetime_strftime(revision, format_))
|
||||
self.entry.set_text(datetime_strftime(revision, self._format))
|
||||
self._value = revision
|
||||
active = -1
|
||||
else:
|
||||
self._value = None
|
||||
active = 0
|
||||
list_store = gtk.ListStore(str, str)
|
||||
list_store.append(('', ''))
|
||||
for i, (rev, id_, name) in enumerate(revisions, 1):
|
||||
list_store.append((datetime_strftime(rev, format_), name))
|
||||
list_store.append((datetime_strftime(rev, self._format), name))
|
||||
if rev == revision:
|
||||
active = i
|
||||
combobox = gtk.ComboBoxEntry(list_store)
|
||||
combobox.add(self.entry)
|
||||
combobox.set_active(active)
|
||||
cell = gtk.CellRendererText()
|
||||
combobox.pack_start(cell, True)
|
||||
combobox.add_attribute(cell, 'text', 1)
|
||||
hbox.pack_start(combobox, expand=True, fill=True)
|
||||
combobox.set_entry_text_column(0)
|
||||
combobox.connect('changed', self.changed)
|
||||
self.entry.set_property('activates_default', True)
|
||||
self.win.vbox.pack_start(hbox, expand=True, fill=True)
|
||||
self.win.show_all()
|
||||
|
||||
def focus_out(self, entry, event):
|
||||
self.parse()
|
||||
self.update()
|
||||
return False
|
||||
|
||||
def activate(self, entry):
|
||||
self.parse()
|
||||
self.update()
|
||||
return False
|
||||
|
||||
def changed(self, combobox):
|
||||
# set_text must be called because DateEntry doesn't work correctly with
|
||||
# the default insert/delete text behavior of combobox
|
||||
model = combobox.get_model()
|
||||
idx = combobox.get_active()
|
||||
if idx >= 0:
|
||||
self.entry.set_text(model[idx][0])
|
||||
# "changed" signal is also triggered by text editing
|
||||
# so only parse when a row is active
|
||||
if combobox.get_active_iter():
|
||||
self.parse()
|
||||
self.update()
|
||||
return False
|
||||
|
||||
def parse(self):
|
||||
text = self.entry.get_text()
|
||||
value = None
|
||||
if text:
|
||||
try:
|
||||
value = date_parse(text, self._format)
|
||||
except ValueError:
|
||||
pass
|
||||
self._value = value
|
||||
|
||||
def update(self):
|
||||
if not self._value:
|
||||
self.entry.set_text('')
|
||||
else:
|
||||
self.entry.set_text(datetime_strftime(self._value, self._format))
|
||||
|
||||
def run(self):
|
||||
response = self.win.run()
|
||||
revision = None
|
||||
if response == gtk.RESPONSE_OK:
|
||||
revision = self.entry.date_get()
|
||||
revision = self._value
|
||||
self.parent.present()
|
||||
self.win.destroy()
|
||||
return revision
|
||||
@@ -4,16 +4,14 @@ import os
|
||||
from itertools import chain
|
||||
import tempfile
|
||||
import locale
|
||||
from tryton.common import datetime_strftime, \
|
||||
from tryton.common import \
|
||||
domain_inversion, eval_domain, localize_domain, \
|
||||
merge, inverse_leaf, concat, simplify, EvalEnvironment
|
||||
import tryton.common as common
|
||||
import time
|
||||
import datetime
|
||||
import decimal
|
||||
from decimal import Decimal
|
||||
import math
|
||||
from tryton.translate import date_format
|
||||
from tryton.common import RPCExecute, RPCException
|
||||
|
||||
|
||||
@@ -202,12 +200,18 @@ class DateTimeField(Field):
|
||||
_default = None
|
||||
|
||||
def set_client(self, record, value, force_change=False):
|
||||
if isinstance(value, basestring):
|
||||
try:
|
||||
value = datetime.datetime(*time.strptime(value,
|
||||
date_format() + ' ' + self.time_format(record))[:6])
|
||||
except ValueError:
|
||||
value = self._default
|
||||
if isinstance(value, datetime.time):
|
||||
current_value = self.get_client(record)
|
||||
if current_value:
|
||||
value = datetime.datetime.combine(
|
||||
current_value.date(), value)
|
||||
else:
|
||||
value = None
|
||||
elif value and not isinstance(value, datetime.datetime):
|
||||
current_value = self.get_client(record)
|
||||
if current_value:
|
||||
value = datetime.datetime.combine(
|
||||
value, current_value.time())
|
||||
if value:
|
||||
value = common.untimezoned_date(value)
|
||||
super(DateTimeField, self).set_client(record, value,
|
||||
@@ -216,10 +220,11 @@ class DateTimeField(Field):
|
||||
def get_client(self, record):
|
||||
value = super(DateTimeField, self).get_client(record)
|
||||
if value:
|
||||
value = common.timezoned_date(value)
|
||||
return datetime_strftime(value, date_format() + ' ' +
|
||||
self.time_format(record))
|
||||
return ''
|
||||
return common.timezoned_date(value)
|
||||
|
||||
def date_format(self, record):
|
||||
context = self.context_get(record)
|
||||
return context.get('date_format', '%x')
|
||||
|
||||
def time_format(self, record):
|
||||
return record.expr_eval(self.attrs['format'])
|
||||
@@ -230,23 +235,15 @@ class DateField(Field):
|
||||
_default = None
|
||||
|
||||
def set_client(self, record, value, force_change=False):
|
||||
if isinstance(value, basestring):
|
||||
try:
|
||||
value = datetime.date(*time.strptime(value,
|
||||
date_format())[:3])
|
||||
except ValueError:
|
||||
value = self._default
|
||||
elif isinstance(value, datetime.datetime):
|
||||
if isinstance(value, datetime.datetime):
|
||||
assert(value.time() == datetime.time())
|
||||
value = value.date()
|
||||
super(DateField, self).set_client(record, value,
|
||||
force_change=force_change)
|
||||
|
||||
def get_client(self, record):
|
||||
value = super(DateField, self).get_client(record)
|
||||
if value:
|
||||
return datetime_strftime(value, date_format())
|
||||
return ''
|
||||
def date_format(self, record):
|
||||
context = self.context_get(record)
|
||||
return context.get('date_format', '%x')
|
||||
|
||||
|
||||
class TimeField(Field):
|
||||
@@ -254,23 +251,11 @@ class TimeField(Field):
|
||||
_default = None
|
||||
|
||||
def set_client(self, record, value, force_change=False):
|
||||
if isinstance(value, basestring):
|
||||
try:
|
||||
value = datetime.time(*time.strptime(value,
|
||||
self.time_format(record))[3:6])
|
||||
except ValueError:
|
||||
value = self._default
|
||||
elif isinstance(value, datetime.datetime):
|
||||
if isinstance(value, datetime.datetime):
|
||||
value = value.time()
|
||||
super(TimeField, self).set_client(record, value,
|
||||
force_change=force_change)
|
||||
|
||||
def get_client(self, record):
|
||||
value = super(TimeField, self).get_client(record)
|
||||
if value is not None:
|
||||
return value.strftime(self.time_format(record))
|
||||
return ''
|
||||
|
||||
def time_format(self, record):
|
||||
return record.expr_eval(self.attrs['format'])
|
||||
|
||||
@@ -902,6 +887,13 @@ class DictField(Field):
|
||||
return concat(localize_domain(inverse_leaf(screen_domain)),
|
||||
attr_domain)
|
||||
|
||||
def date_format(self, record):
|
||||
context = self.context_get(record)
|
||||
return context.get('date_format', '%x')
|
||||
|
||||
def time_format(self, record):
|
||||
return '%X'
|
||||
|
||||
TYPES = {
|
||||
'char': CharField,
|
||||
'float_time': FloatField,
|
||||
|
||||
@@ -11,7 +11,7 @@ from tryton.common.focus import (get_invisible_ancestor, find_focused_child,
|
||||
from tryton.common import Tooltips, node_attributes, ICONFACTORY
|
||||
from tryton.common.button import Button
|
||||
from tryton.config import CONFIG
|
||||
from .form_gtk.calendar import Calendar, DateTime, Time
|
||||
from .form_gtk.calendar import Date, Time, DateTime
|
||||
from .form_gtk.float import Float
|
||||
from .form_gtk.integer import Integer
|
||||
from .form_gtk.selection import Selection
|
||||
@@ -364,7 +364,7 @@ class ViewForm(View):
|
||||
pack(container.table, resize=True, shrink=True)
|
||||
|
||||
WIDGETS = {
|
||||
'date': Calendar,
|
||||
'date': Date,
|
||||
'datetime': DateTime,
|
||||
'time': Time,
|
||||
'float': Float,
|
||||
|
||||
@@ -4,32 +4,39 @@ import gtk
|
||||
import gettext
|
||||
|
||||
from .widget import Widget
|
||||
from tryton.common.date_widget import DateEntry
|
||||
from tryton.translate import date_format
|
||||
from tryton.common.datetime_ import (Date as DateEntry, Time as TimeEntry,
|
||||
DateTime as DateTimeEntry, add_operators)
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class Calendar(Widget):
|
||||
"Calendar"
|
||||
class Date(Widget):
|
||||
|
||||
def __init__(self, view, attrs):
|
||||
super(Calendar, self).__init__(view, attrs)
|
||||
def __init__(self, view, attrs, _entry=DateEntry):
|
||||
super(Date, self).__init__(view, attrs)
|
||||
|
||||
self.widget = gtk.HBox()
|
||||
self.entry = DateEntry('')
|
||||
self.entry.set_property('activates_default', True)
|
||||
self.entry.connect('key_press_event', self.sig_key_press)
|
||||
self.entry.connect('activate', self.sig_activate)
|
||||
self.entry.connect('changed', lambda _: self.send_modified())
|
||||
self.entry.connect('focus-out-event', lambda x, y: self._focus_out())
|
||||
self.entry = add_operators(_entry())
|
||||
self.real_entry.set_property('activates_default', True)
|
||||
self.real_entry.connect('key_press_event', self.sig_key_press)
|
||||
self.real_entry.connect('activate', self.sig_activate)
|
||||
self.real_entry.connect('changed', lambda _: self.send_modified())
|
||||
self.real_entry.connect('focus-out-event',
|
||||
lambda x, y: self._focus_out())
|
||||
self.widget.pack_start(self.entry, expand=False, fill=False)
|
||||
|
||||
@property
|
||||
def real_entry(self):
|
||||
return self.entry
|
||||
|
||||
def _color_widget(self):
|
||||
return self.entry
|
||||
|
||||
def _set_editable(self, value):
|
||||
self.real_entry.set_editable(value)
|
||||
|
||||
def _readonly_set(self, value):
|
||||
self.entry.set_editable(not value)
|
||||
self._set_editable(not value)
|
||||
if value:
|
||||
self.widget.set_focus_chain([])
|
||||
else:
|
||||
@@ -38,8 +45,7 @@ class Calendar(Widget):
|
||||
@property
|
||||
def modified(self):
|
||||
if self.record and self.field:
|
||||
value = self.entry.compute_date(self.entry.get_text())
|
||||
return self.field.get_client(self.record) != value
|
||||
return self.field.get_client(self.record) != self.entry.props.value
|
||||
return False
|
||||
|
||||
def sig_key_press(self, widget, event):
|
||||
@@ -49,41 +55,78 @@ class Calendar(Widget):
|
||||
field.set_client(record, self.get_value())
|
||||
|
||||
def get_value(self):
|
||||
return self.entry.date_get(set_text=False)
|
||||
return self.entry.props.value
|
||||
|
||||
def get_format(self, record, field):
|
||||
return date_format()
|
||||
def set_format(self, record, field):
|
||||
if field and record:
|
||||
format_ = field.date_format(record)
|
||||
else:
|
||||
format_ = '%x'
|
||||
self.entry.props.format = format_
|
||||
|
||||
def display(self, record, field):
|
||||
if not field:
|
||||
self.entry.set_format('')
|
||||
self.entry.clear()
|
||||
return False
|
||||
self.entry.set_format(self.get_format(record, field))
|
||||
super(Calendar, self).display(record, field)
|
||||
value = field.get_client(record)
|
||||
if not value:
|
||||
self.entry.clear()
|
||||
super(Date, self).display(record, field)
|
||||
if field and record:
|
||||
value = field.get_client(record)
|
||||
else:
|
||||
if len(value) > self.entry.get_width_chars():
|
||||
self.entry.set_width_chars(len(value))
|
||||
self.entry.set_text(value)
|
||||
return True
|
||||
value = ''
|
||||
self.entry.props.value = value
|
||||
self.set_format(record, field)
|
||||
|
||||
|
||||
class DateTime(Calendar):
|
||||
"DateTime"
|
||||
|
||||
def get_format(self, record, field):
|
||||
return date_format() + ' ' + field.time_format(record)
|
||||
|
||||
|
||||
class Time(Calendar):
|
||||
"Time"
|
||||
|
||||
class Time(Date):
|
||||
def __init__(self, view, attrs):
|
||||
super(Time, self).__init__(view, attrs)
|
||||
self.entry.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, None)
|
||||
super(Time, self).__init__(view, attrs, _entry=TimeEntry)
|
||||
self.entry.set_focus_chain([self.entry.get_child()])
|
||||
|
||||
def get_format(self, record, field):
|
||||
return field.time_format(record)
|
||||
@property
|
||||
def real_entry(self):
|
||||
return self.entry.get_child()
|
||||
|
||||
def display(self, record, field):
|
||||
super(Time, self).display(record, field)
|
||||
|
||||
def set_format(self, record, field):
|
||||
if field and record:
|
||||
format_ = field.time_format(record)
|
||||
else:
|
||||
format_ = '%X'
|
||||
self.entry.props.format = format_
|
||||
|
||||
|
||||
class DateTime(Date):
|
||||
def __init__(self, view, attrs):
|
||||
Widget.__init__(self, view, attrs)
|
||||
|
||||
self.widget = gtk.HBox()
|
||||
self.entry = DateTimeEntry()
|
||||
for child in self.entry.get_children():
|
||||
add_operators(child)
|
||||
if isinstance(child, gtk.ComboBoxEntry):
|
||||
child.set_focus_chain([child.get_child()])
|
||||
child = child.get_child()
|
||||
child.set_property('activates_default', True)
|
||||
child.connect('key_press_event', self.sig_key_press)
|
||||
child.connect('activate', self.sig_activate)
|
||||
child.connect('changed', lambda _: self.send_modified())
|
||||
child.connect('focus-out-event', lambda x, y: self._focus_out())
|
||||
self.widget.pack_start(self.entry, expand=False, fill=False)
|
||||
|
||||
def _color_widget(self):
|
||||
return self.entry.get_children()[0] # XXX not always sure to have Date
|
||||
|
||||
def _set_editable(self, value):
|
||||
for child in self.entry.get_children():
|
||||
if not hasattr(child, 'set_editable'):
|
||||
child = child.child
|
||||
child.set_editable(value)
|
||||
|
||||
def set_format(self, record, field):
|
||||
if field and record:
|
||||
date_format = field.date_format(record)
|
||||
time_format = field.time_format(record)
|
||||
else:
|
||||
date_format = '%x'
|
||||
time_format = '%X'
|
||||
self.entry.props.date_format = date_format
|
||||
self.entry.props.time_format = time_format
|
||||
@@ -12,13 +12,12 @@ from decimal import Decimal
|
||||
from .widget import Widget
|
||||
from tryton.config import CONFIG
|
||||
from tryton.gui.window.win_search import WinSearch
|
||||
from tryton.common import RPCExecute, RPCException, timezoned_date, \
|
||||
datetime_strftime, Tooltips
|
||||
from tryton.common.date_widget import DateEntry
|
||||
from tryton.common import RPCExecute, RPCException, Tooltips, \
|
||||
timezoned_date, untimezoned_date
|
||||
from tryton.common.placeholder_entry import PlaceholderEntry
|
||||
from tryton.common.selection import selection_shortcuts
|
||||
from tryton.common.completion import get_completion, update_completion
|
||||
from tryton.translate import date_format
|
||||
from tryton.common.datetime_ import Date, DateTime
|
||||
from tryton.common.domain_parser import quote
|
||||
|
||||
_ = gettext.gettext
|
||||
@@ -250,50 +249,45 @@ class DictDateTimeEntry(DictEntry):
|
||||
fill = False
|
||||
|
||||
def create_widget(self):
|
||||
widget = DateEntry('')
|
||||
widget.set_format(self.get_format())
|
||||
widget = DateTime()
|
||||
record = self.parent_widget.record
|
||||
field = self.parent_widget.field
|
||||
if record and field:
|
||||
format_ = field.time_format(record)
|
||||
widget.props.format = format_
|
||||
widget.connect('key_press_event', self.parent_widget.send_modified)
|
||||
widget.connect('focus-out-event', lambda w, e:
|
||||
self.parent_widget._focus_out())
|
||||
return widget
|
||||
|
||||
def modified(self, value):
|
||||
if value.get(self.name):
|
||||
text = datetime_strftime(timezoned_date(value[self.name]),
|
||||
self.get_format())
|
||||
else:
|
||||
text = ''
|
||||
return self.widget.compute_date(self.widget.get_text()) != text
|
||||
|
||||
def get_format(self):
|
||||
return date_format() + ' %H:%M:%S' # got to find a way
|
||||
|
||||
def get_value(self):
|
||||
return self.widget.date_get()
|
||||
return untimezoned_date(self.widget.props.value)
|
||||
|
||||
def set_value(self, value):
|
||||
self.widget.date_set(value)
|
||||
txt = self.widget.get_text()
|
||||
if txt:
|
||||
if len(txt) > self.widget.get_width_chars():
|
||||
self.widget.set_width_chars(len(txt))
|
||||
self.widget.props.value = timezoned_date(value)
|
||||
|
||||
|
||||
class DictDateEntry(DictDateTimeEntry):
|
||||
class DictDateEntry(DictEntry):
|
||||
expand = False
|
||||
fill = False
|
||||
|
||||
def modified(self, value):
|
||||
if value.get(self.name):
|
||||
text = datetime_strftime(value[self.name], self.get_format())
|
||||
else:
|
||||
text = ''
|
||||
return self.widget.compute_date(self.widget.get_text()) != text
|
||||
|
||||
def get_format(self):
|
||||
return date_format()
|
||||
def create_widget(self):
|
||||
widget = Date()
|
||||
record = self.parent_widget.record
|
||||
field = self.parent_widget.field
|
||||
if record and field:
|
||||
format_ = field.date_format(record)
|
||||
widget.props.format = format_
|
||||
widget.connect('key_press_event', self.parent_widget.send_modified)
|
||||
widget.connect('focus-out-event', lambda w, e:
|
||||
self.parent_widget._focus_out())
|
||||
return widget
|
||||
|
||||
def get_value(self):
|
||||
dt = super(DictDateEntry, self).get_value()
|
||||
return dt.date() if dt else None
|
||||
return self.widget.props.value
|
||||
|
||||
def set_value(self, value):
|
||||
self.widget.props.value = value
|
||||
|
||||
|
||||
DICT_ENTRIES = {
|
||||
|
||||
@@ -15,7 +15,6 @@ import tryton.rpc as rpc
|
||||
import cairo
|
||||
from tryton.action import Action
|
||||
from tryton.gui.window import Window
|
||||
from tryton.translate import date_format
|
||||
|
||||
|
||||
class Popup(object):
|
||||
@@ -378,10 +377,12 @@ class Graph(gtk.DrawingArea):
|
||||
else:
|
||||
self.datas[x][key] += \
|
||||
float(model[yfield['name']].get(model) or 0)
|
||||
date_format = self.view.screen.context.get('date_format', '%x')
|
||||
datetime_format = date_format + ' %X'
|
||||
if isinstance(minx, datetime.datetime):
|
||||
date = minx
|
||||
while date <= maxx:
|
||||
self.labels[date] = datetime_strftime(date, date_format())
|
||||
self.labels[date] = datetime_strftime(date, datetime_format)
|
||||
self.datas.setdefault(date, {})
|
||||
for yfield in self.yfields:
|
||||
self.datas[date].setdefault(
|
||||
@@ -390,7 +391,7 @@ class Graph(gtk.DrawingArea):
|
||||
elif isinstance(minx, datetime.date):
|
||||
date = minx
|
||||
while date <= maxx:
|
||||
self.labels[date] = datetime_strftime(date, date_format())
|
||||
self.labels[date] = datetime_strftime(date, date_format)
|
||||
self.datas.setdefault(date, {})
|
||||
for yfield in self.yfields:
|
||||
self.datas[date].setdefault(
|
||||
|
||||
@@ -20,7 +20,7 @@ from tryton.common.popup_menu import populate
|
||||
from tryton.common import RPCExecute, RPCException, node_attributes, Tooltips
|
||||
from . import View
|
||||
from .list_gtk.editabletree import EditableTreeView, TreeView
|
||||
from .list_gtk.widget import (Affix, Char, Int, Boolean, URL, Date, Datetime,
|
||||
from .list_gtk.widget import (Affix, Char, Int, Boolean, URL, Date,
|
||||
Time, Float, FloatTime, Binary, M2O, O2O, O2M, M2M, Selection, Reference,
|
||||
ProgressBar, Button, Image)
|
||||
|
||||
@@ -426,7 +426,6 @@ class ViewTree(View):
|
||||
'float_time': FloatTime,
|
||||
'integer': Int,
|
||||
'biginteger': Int,
|
||||
'datetime': Datetime,
|
||||
'time': Time,
|
||||
'boolean': Boolean,
|
||||
'text': Char,
|
||||
|
||||
@@ -6,7 +6,7 @@ import gobject
|
||||
from itertools import islice, cycle
|
||||
|
||||
from tryton.common import MODELACCESS
|
||||
from tryton.common.date_widget import DateEntry
|
||||
from tryton.common.datetime_ import Date, Time
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
@@ -110,6 +110,8 @@ class EditableTreeView(TreeView):
|
||||
field = record[column.name]
|
||||
if hasattr(field, 'editabletree_entry'):
|
||||
entry = field.editabletree_entry
|
||||
if isinstance(entry, (Date, Time)):
|
||||
txt = entry.props.value
|
||||
if isinstance(entry, gtk.Entry):
|
||||
txt = entry.get_text()
|
||||
else:
|
||||
@@ -141,9 +143,9 @@ class EditableTreeView(TreeView):
|
||||
leaving = True
|
||||
|
||||
if event.keyval in self.leaving_events or leaving:
|
||||
if isinstance(entry, gtk.Entry):
|
||||
if isinstance(entry, DateEntry):
|
||||
entry.date_get()
|
||||
if isinstance(entry, (Date, Time)):
|
||||
txt = entry.props.value
|
||||
elif isinstance(entry, gtk.Entry):
|
||||
txt = entry.get_text()
|
||||
else:
|
||||
txt = entry.get_active_text()
|
||||
@@ -260,9 +262,9 @@ class EditableTreeView(TreeView):
|
||||
return True
|
||||
model = self.get_model()
|
||||
record = model.get_value(model.get_iter(path), 0)
|
||||
if isinstance(entry, gtk.Entry):
|
||||
if isinstance(entry, DateEntry):
|
||||
entry.date_get()
|
||||
if isinstance(entry, (Date, Time)):
|
||||
self.on_quit_cell(record, column, entry.props.value)
|
||||
elif isinstance(entry, gtk.Entry):
|
||||
self.on_quit_cell(record, column, entry.get_text())
|
||||
elif isinstance(entry, (gtk.ComboBoxEntry, gtk.ComboBox)):
|
||||
self.on_quit_cell(record, column, entry.get_active_text())
|
||||
@@ -17,7 +17,6 @@ import tryton.rpc as rpc
|
||||
from tryton.common import COLORS, file_selection, file_open, slugify
|
||||
import tryton.common as common
|
||||
from tryton.common.cellrendererbutton import CellRendererButton
|
||||
from tryton.common.cellrendererdate import CellRendererDate
|
||||
from tryton.common.cellrenderertext import CellRendererText, \
|
||||
CellRendererTextCompletion
|
||||
from tryton.common.cellrenderertoggle import CellRendererToggle
|
||||
@@ -27,10 +26,11 @@ from tryton.common.cellrendererfloat import CellRendererFloat
|
||||
from tryton.common.cellrendererbinary import CellRendererBinary
|
||||
from tryton.common.cellrendererclickablepixbuf import \
|
||||
CellRendererClickablePixbuf
|
||||
from tryton.translate import date_format
|
||||
from tryton.common import data2pixbuf
|
||||
from tryton.common.completion import get_completion, update_completion
|
||||
from tryton.common.selection import SelectionMixin, PopdownMixin
|
||||
from tryton.common.datetime_ import CellRendererDate, CellRendererTime
|
||||
from tryton.common.datetime_strftime import datetime_strftime
|
||||
from tryton.common.domain_parser import quote
|
||||
|
||||
_ = gettext.gettext
|
||||
@@ -343,31 +343,53 @@ class Date(Char):
|
||||
|
||||
def __init__(self, view, attrs, renderer=None):
|
||||
if renderer is None:
|
||||
renderer = partial(CellRendererDate, date_format())
|
||||
renderer = CellRendererDate
|
||||
super(Date, self).__init__(view, attrs, renderer=renderer)
|
||||
self.renderer.connect('editing-started', self.editing_started)
|
||||
|
||||
|
||||
class Datetime(Date):
|
||||
|
||||
@realized
|
||||
def setter(self, column, cell, store, iter):
|
||||
super(Datetime, self).setter(column, cell, store, iter)
|
||||
record = store.get_value(iter, 0)
|
||||
field = record[self.attrs['name']]
|
||||
time_format = field.time_format(record)
|
||||
self.renderer.format = date_format() + ' ' + time_format
|
||||
self.renderer.props.format = self.get_format(record, field)
|
||||
super(Date, self).setter(column, cell, store, iter)
|
||||
|
||||
def get_format(self, record, field):
|
||||
if field and record:
|
||||
return field.date_format(record)
|
||||
else:
|
||||
return '%x'
|
||||
|
||||
def get_textual_value(self, record):
|
||||
if not record:
|
||||
return ''
|
||||
value = record[self.attrs['name']].get_client(record)
|
||||
if value:
|
||||
return datetime_strftime(value, self.renderer.props.format)
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
class Time(Date):
|
||||
|
||||
@realized
|
||||
def setter(self, column, cell, store, iter):
|
||||
super(Time, self).setter(column, cell, store, iter)
|
||||
record = store.get_value(iter, 0)
|
||||
field = record[self.attrs['name']]
|
||||
time_format = field.time_format(record)
|
||||
self.renderer.format = time_format
|
||||
def __init__(self, view, attrs, renderer=None):
|
||||
if renderer is None:
|
||||
renderer = CellRendererTime
|
||||
super(Time, self).__init__(view, attrs, renderer=renderer)
|
||||
|
||||
def get_format(self, record, field):
|
||||
if field and record:
|
||||
return field.time_format(record)
|
||||
else:
|
||||
return '%X'
|
||||
|
||||
def get_textual_value(self, record):
|
||||
if not record:
|
||||
return ''
|
||||
value = record[self.attrs['name']].get_client(record)
|
||||
if value:
|
||||
return value.strftime(self.renderer.props.format)
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
class Float(Int):
|
||||
|
||||
@@ -8,7 +8,7 @@ import tryton.common as common
|
||||
from tryton.common.domain_parser import quote
|
||||
from tryton.common.placeholder_entry import PlaceholderEntry
|
||||
from tryton.common.treeviewcontrol import TreeViewControl
|
||||
from tryton.translate import date_format
|
||||
from tryton.common.datetime_ import Date, Time, DateTime, add_operators
|
||||
from tryton.config import TRYTON_ICON
|
||||
from tryton.pyson import PYSONDecoder
|
||||
|
||||
@@ -17,17 +17,20 @@ _ = gettext.gettext
|
||||
|
||||
class Dates(gtk.HBox):
|
||||
|
||||
def __init__(self, format_):
|
||||
def __init__(self, format_=None, _entry=Date):
|
||||
super(Dates, self).__init__()
|
||||
self.from_ = common.date_widget.DateEntry(format_)
|
||||
self.from_ = add_operators(_entry())
|
||||
self.pack_start(self.from_, expand=True, fill=True)
|
||||
self.pack_start(gtk.Label(_('..')), expand=False, fill=False)
|
||||
self.to = common.date_widget.DateEntry(format_)
|
||||
self.to = add_operators(_entry())
|
||||
self.pack_start(self.to, expand=True, fill=True)
|
||||
if format_:
|
||||
self.from_.props.format = format_
|
||||
self.to.props.format = format_
|
||||
|
||||
def get_value(self):
|
||||
from_ = self.from_.get_text()
|
||||
to = self.to.get_text()
|
||||
from_ = self.from_.props.value
|
||||
to = self.to.props.value
|
||||
if from_ and to:
|
||||
if from_ != to:
|
||||
return '%s..%s' % (quote(from_), quote(to))
|
||||
@@ -38,6 +41,30 @@ class Dates(gtk.HBox):
|
||||
elif to:
|
||||
return '<=%s' % quote(to)
|
||||
|
||||
def connect_activate(self, callback):
|
||||
self.from_.connect('activate', callback)
|
||||
self.to.connect('activate', callback)
|
||||
|
||||
|
||||
class Times(Dates):
|
||||
|
||||
def __init__(self, format_, _entry=Time):
|
||||
super(Times, self).__init__(_entry=_entry)
|
||||
|
||||
|
||||
class DateTimes(Dates):
|
||||
|
||||
def __init__(self, date_format, time_format, _entry=DateTime):
|
||||
super(DateTimes, self).__init__(_entry=_entry)
|
||||
self.from_.props.date_format = date_format
|
||||
self.to.props.date_format = date_format
|
||||
self.from_.props.time_format = time_format
|
||||
self.to.props.time_format = time_format
|
||||
|
||||
def connect_activate(self, callback):
|
||||
for widget in self.from_.get_children() + self.to.get_children():
|
||||
widget.connect('activate', callback)
|
||||
|
||||
|
||||
class Selection(gtk.ScrolledWindow):
|
||||
|
||||
@@ -423,10 +450,6 @@ class ScreenContainer(object):
|
||||
self.last_search_text = self.get_text()
|
||||
self.do_search()
|
||||
|
||||
def date_activate(entry):
|
||||
entry._focus_out(entry, None)
|
||||
search()
|
||||
|
||||
if not self.search_window:
|
||||
self.search_window = gtk.Window()
|
||||
self.search_window.set_transient_for(button.get_toplevel())
|
||||
@@ -469,15 +492,16 @@ class ScreenContainer(object):
|
||||
entry = Selection(selections)
|
||||
yoptions = gtk.FILL | gtk.EXPAND
|
||||
elif field['type'] in ('date', 'datetime', 'time'):
|
||||
date_format = self.screen.context.get('date_format', '%x')
|
||||
if field['type'] == 'date':
|
||||
format_ = date_format()
|
||||
entry = Dates(date_format)
|
||||
elif field['type'] in ('datetime', 'time'):
|
||||
format_ = PYSONDecoder({}).decode(field['format'])
|
||||
if field['type'] == 'datetime':
|
||||
format_ = date_format() + ' ' + format_
|
||||
entry = Dates(format_)
|
||||
entry.from_.connect('activate', date_activate)
|
||||
entry.to.connect('activate', date_activate)
|
||||
time_format = PYSONDecoder({}).decode(field['format'])
|
||||
if field['type'] == 'time':
|
||||
entry = Times(time_format)
|
||||
elif field['type'] == 'datetime':
|
||||
entry = DateTimes(date_format, time_format)
|
||||
entry.connect_activate(lambda *a: search())
|
||||
else:
|
||||
entry = gtk.Entry()
|
||||
entry.connect('activate', lambda *a: search())
|
||||
|
||||
@@ -150,8 +150,6 @@ _LOCALE2WIN32 = {
|
||||
'zh_TW': 'Chinese_Taiwan',
|
||||
}
|
||||
|
||||
_DATE_FORMAT = '%m/%d/%Y'
|
||||
|
||||
|
||||
def setlang(lang=None, locale_dict=None):
|
||||
"Set language"
|
||||
@@ -191,8 +189,6 @@ def setlang(lang=None, locale_dict=None):
|
||||
conv = locale.localeconv()
|
||||
for field in locale_dict.keys():
|
||||
if field == 'date':
|
||||
global _DATE_FORMAT
|
||||
_DATE_FORMAT = str(locale_dict[field]).replace('%-', '%')
|
||||
continue
|
||||
conv[field] = locale_dict[field]
|
||||
locale.localeconv = lambda: conv
|
||||
@@ -203,11 +199,3 @@ def set_language_direction(direction):
|
||||
gtk.widget_set_default_direction(gtk.TEXT_DIR_RTL)
|
||||
else:
|
||||
gtk.widget_set_default_direction(gtk.TEXT_DIR_LTR)
|
||||
|
||||
|
||||
def date_format():
|
||||
'''
|
||||
Return the locale date format
|
||||
'''
|
||||
global _DATE_FORMAT
|
||||
return _DATE_FORMAT
|
||||
Reference in new issue
Block a user