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:
Cédric Krier committed 2015-01-26 11:31:24 +01:00
1 parent adff4c21bd
commit 4e360524f4
19 files changed
+902 -692

No files matched your search

+2
View File
@@ -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
+3
View File
@@ -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)
-119
View File
@@ -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)
+1 -1
View File
@@ -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):
-340
View File
@@ -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()
+561
View File
@@ -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()
+39 -30
View File
@@ -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: !""'
+6 -5
View File
@@ -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:
+47 -19
View File
@@ -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
+30 -38
View File
@@ -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,
+2 -2
View File
@@ -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(
+1 -2
View File
@@ -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())
-12
View File
@@ -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