merge
[openerp-client.git] / bin / widget / view / form_gtk / date_widget.py
blob5b4a13d5eb7eecf33eee3036b759ed77bdaf3f3c
2 # Copyright (C) Tiny 2008
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # Lesser General Public License for more details.
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
17 # USA
19 # Author(s): Johan Dahlin (Async Open Source)
20 # Tiny sprl
25 import gobject
26 import pango
27 import gtk
28 import re
30 import time
31 from mx.DateTime import RelativeDateTime
32 from mx.DateTime import DateTime
33 from mx.DateTime import now
34 from mx.DateTime import strptime
36 mapping = {
37 '%y': ('__', '[_0-9][_0-9]'),
38 '%Y': ('____', '[_1-9][_0-9][_0-9][_0-9]'),
39 '%m': ('__', '[_0-1][_0-9]'),
40 '%d': ('__', '[_0-3][_0-9]'),
41 '%H': ('__', '[_0-2][_0-9]'),
42 '%M': ('__', '[_0-6][_0-9]'),
43 '%S': ('__', '[_0-6][_0-9]'),
46 class DateEntry(gtk.Entry):
47 def __init__(self, format, callback=None, callback_process=None):
48 super(DateEntry, self).__init__()
49 self.modify_font(pango.FontDescription("monospace"))
51 self.format = format
52 self.regex = self.initial_value = format
53 for key,val in mapping.items():
54 self.regex = self.regex.replace(key, val[1])
55 self.initial_value = self.initial_value.replace(key, val[0])
57 self.set_text(self.initial_value)
58 self.regex = re.compile(self.regex)
60 assert self.regex.match(self.initial_value), 'Error, the initial value should be validated by regex'
61 self.set_width_chars(len(self.initial_value))
62 self.set_max_length(len(self.initial_value))
64 self.connect('key-press-event', self._on_key_press)
65 self.connect('insert-text', self._on_insert_text)
66 self.connect('delete-text', self._on_delete_text)
68 self.connect('focus-out-event', self._focus_out)
69 self.callback = callback
70 self.callback_process = callback_process
72 self._interactive_input = True
73 self.mode_cmd = False
74 gobject.idle_add(self.set_position, 0)
75 tooltips = gtk.Tooltips()
76 tooltips.set_tip(self, _('''You can use special operation by pressing +, - or =. Plus/minus adds/decrease the variable to the current selected date. Equals set part of selected date. Available variables: 12h = 12 hours, 8d = 8 days, 4w = 4 weeks, 1m = 1 month, 2y = 2 years. Some examples:
77 * +21d : adds 21 days to selected year
78 * =23w : set date to the 23th week of the year
79 * -4m : decrease 4 months to the current date
80 You can also use "=" to set the date to the current date/time and '-' to clear the field.'''))
81 tooltips.enable()
83 def _on_insert_text(self, editable, value, length, position):
84 if not self._interactive_input:
85 return
87 if self.mode_cmd:
88 if self.callback: self.callback(value)
89 self.stop_emission('insert-text')
90 return
92 pos = self.get_position()
93 if length != 1:
94 # TODO: Implement paste
95 self.stop_emission('insert-text')
96 return
98 text = self.get_text()
100 text = text[:pos] + value + text[pos + 1:]
101 if self.regex.match(text):
102 pos += 1
103 while (pos<len(self.initial_value)) and (self.initial_value[pos] not in ['_','0','X']):
104 pos += 1
105 self.set_text(text)
106 gobject.idle_add(self.set_position, pos)
107 self.stop_emission('insert-text')
108 self.show()
109 return
111 def _on_delete_text(self, editable, start, end):
112 if not self._interactive_input:
113 return
115 #if end - start != 1:
116 # #TODO: cut/delete several
117 # self.stop_emission('delete-text')
118 # return
120 while (start>0) and (self.initial_value[start] not in ['_','0','X']):
121 start -= 1
122 text = self.get_text()
123 text = text[:start] + self.initial_value[start:end] + text[end:]
124 self.set_text(text)
125 gobject.idle_add(self.set_position, start)
126 self.stop_emission('delete-text')
127 return
129 def _focus_out(self, args, args2):
130 self.date_get()
131 if self.mode_cmd:
132 self.mode_cmd = False
133 if self.callback_process: self.callback_process(False, self, False)
135 def set_text(self, text):
136 self._interactive_input = False
137 try:
138 gtk.Entry.set_text(self, text)
139 finally:
140 self._interactive_input = True
142 def date_set(self, dt):
143 if dt:
144 self.set_text( dt.strftime(self.format) )
145 else:
146 self.set_text(self.initial_value)
148 def date_get(self):
149 tt = time.strftime(self.format, time.localtime())
150 tc = self.get_text()
151 if tc==self.initial_value:
152 return False
153 for a in range(len(self.initial_value)):
154 if self.initial_value[a] == tc[a]:
155 tc = tc[:a] + tt[a] + tc[a+1:]
156 try:
157 self.set_text(tc)
158 return strptime(tc, self.format)
159 except:
160 tc = tt
161 self.set_text(tc)
162 return strptime(tc, self.format)
164 def delete_text(self, start, end):
165 print 'DELETE TEXT'
166 self._interactive_input = False
167 try:
168 gtk.Entry.delete_text(self, start, end)
169 finally:
170 self._interactive_input = True
172 def insert_text(self, text, position=0):
173 self._interactive_input = False
174 try:
175 gtk.Entry.insert_text(self, text, position)
176 finally:
177 self._interactive_input = True
179 def clear(self):
180 self.set_text(self.initial_value)
182 def _on_key_press(self, editable, event):
183 if event.keyval in (gtk.keysyms.Tab, gtk.keysyms.Escape, gtk.keysyms.Return):
184 if self.mode_cmd:
185 self.mode_cmd = False
186 if self.callback_process: self.callback_process(False, self, event)
187 self.stop_emission("key-press-event")
188 return True
189 elif event.keyval in (ord('+'),ord('-'),ord('=')):
190 self.mode_cmd = True
191 self.date_get()
192 if self.callback_process: self.callback_process(True, self, event)
193 self.stop_emission("key-press-event")
194 return True
195 elif self.mode_cmd:
196 if self.callback: self.callback(event)
197 return True
198 return False
200 class CmdEntry(gtk.Label):
201 pass
203 class ComplexEntry(gtk.HBox):
204 def __init__(self, format, *args, **argv):
205 super(ComplexEntry, self).__init__(*args, **argv)
206 self.widget = DateEntry(
207 format,
208 self._date_cb,
209 self._process_cb
211 self.widget.set_position(0)
212 self.widget.select_region(0, 0)
213 self.widget_cmd = CmdEntry()
214 self.widget_cmd.hide()
215 self.pack_start(self.widget, expand=True, fill=True)
216 self.pack_start(self.widget_cmd, expand=False, fill=True)
218 def _date_cb(self, event):
219 if event.keyval in (gtk.keysyms.BackSpace,):
220 text = self.widget_cmd.get_text()[:-1]
221 self.widget_cmd.set_text(text)
222 return True
223 if event.keyval<250:
224 value = chr(event.keyval)
225 text = self.widget_cmd.get_text()
226 self.widget_cmd.set_text(text+value)
227 return True
229 def _process_cb(self, ok, widget, event=None):
230 if ok:
231 self.widget_cmd.show()
232 self._date_cb(event)
233 else:
234 data = self.widget.get_text()
235 if (not event.keyval == gtk.keysyms.Escape) or not event:
236 lst = {
237 '^=(\d+)w$': lambda dt,r: dt+RelativeDateTime(day=0, month=0, weeks = int(r.group(1))),
238 '^=(\d+)d$': lambda dt,r: dt+RelativeDateTime(day=int(r.group(1))),
239 '^=(\d+)m$': lambda dt,r: dt+RelativeDateTime(day=0, month = int(r.group(1))),
240 '^=(2\d\d\d)y$': lambda dt,r: dt+RelativeDateTime(year = int(r.group(1))),
241 '^=(\d+)h$': lambda dt,r: dt+RelativeDateTime(hour = int(r.group(1))),
242 '^([\\+-]\d+)h$': lambda dt,r: dt+RelativeDateTime(hours = int(r.group(1))),
243 '^([\\+-]\d+)w$': lambda dt,r: dt+RelativeDateTime(days = 7*int(r.group(1))),
244 '^([\\+-]\d+)d$': lambda dt,r: dt+RelativeDateTime(days = int(r.group(1))),
245 '^([\\+-]\d+)m$': lambda dt,r: dt+RelativeDateTime(months = int(r.group(1))),
246 '^([\\+-]\d+)y$': lambda dt,r: dt+RelativeDateTime(years = int(r.group(1))),
247 '^=$': lambda dt,r: now(),
248 '^-$': lambda dt,r: False
250 cmd = self.widget_cmd.get_text()
251 for r,f in lst.items():
252 groups = re.match(r, cmd)
253 if groups:
254 dt = self.widget.date_get()
255 if not dt:
256 dt = time.strftime(self.widget.format, time.localtime())
257 dt = strptime(dt, self.widget.format)
258 self.widget.date_set(f(dt,groups))
259 break
261 # Compute HERE using DATA and setting WIDGET
262 pass
263 self.widget_cmd.set_text('')
264 self.widget_cmd.hide()
266 if __name__ == '__main__':
267 import sys
268 def main(args):
269 win = gtk.Window()
270 win.set_title('gtk.Entry subclass')
271 def cb(window, event):
272 gtk.main_quit()
273 win.connect('delete-event', cb)
275 widget = ComplexEntry('%d/%m/%Y %H:%M:%S')
276 win.add(widget)
278 win.show_all()
279 gtk.main()
281 sys.exit(main(sys.argv))