| Home | Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed date input widget
2
3 All GNUmed date input should happen via classes in
4 this module.
5
6 @copyright: author(s)
7 """
8 #==============================================================================
9 __version__ = "$Revision: 1.66 $"
10 __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
11 __licence__ = "GPL v2 or later (details at http://www.gnu.org)"
12
13 # standard libary
14 import re, string, sys, time, datetime as pyDT, logging
15
16
17 # 3rd party
18 import mx.DateTime as mxDT
19 import wx
20 import wx.calendar
21
22
23 # GNUmed specific
24 if __name__ == '__main__':
25 sys.path.insert(0, '../../')
26 from Gnumed.pycommon import gmMatchProvider
27 from Gnumed.pycommon import gmDateTime
28 from Gnumed.pycommon import gmI18N
29 from Gnumed.wxpython import gmPhraseWheel
30 from Gnumed.wxpython import gmGuiHelpers
31
32 _log = logging.getLogger('gm.ui')
33
34 #============================================================
35 #class cIntervalMatchProvider(gmMatchProvider.cMatchProvider):
36 # """Turns strings into candidate intervals."""
37 # def __init__(self):
38 #
39 # gmMatchProvider.cMatchProvider.__init__(self)
40 #
41 # self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999)
42 # self.word_separators = None
43 ## self.ignored_chars("""[?!."'\\(){}\[\]<>~#*$%^_]+""")
44 # #--------------------------------------------------------
45 # # external API
46 # #--------------------------------------------------------
47 # #--------------------------------------------------------
48 # # base class API
49 # #--------------------------------------------------------
50 # def getMatchesByPhrase(self, aFragment):
51 # intv = gmDateTime.str2interval(str_interval = aFragment)
52 #
53 # if intv is None:
54 # return (False, [])
55 #
56 # items = [{
57 # 'data': intv,
58 # 'field_label': gmDateTime.format_interval(intv, gmDateTime.acc_minutes),
59 # 'list_label': gmDateTime.format_interval(intv, gmDateTime.acc_minutes)
60 # }]
61 #
62 # return (True, items)
63 # #--------------------------------------------------------
64 # def getMatchesByWord(self, aFragment):
65 # return self.getMatchesByPhrase(aFragment)
66 # #--------------------------------------------------------
67 # def getMatchesBySubstr(self, aFragment):
68 # return self.getMatchesByPhrase(aFragment)
69 # #--------------------------------------------------------
70 # def getAllMatches(self):
71 # matches = (False, [])
72 # return matches
73 #============================================================
75
77
78 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
79 self.phrase_separators = None
80 self.display_accuracy = None
81 #--------------------------------------------------------
82 # phrasewheel internal API
83 #--------------------------------------------------------
85 intv = gmDateTime.str2interval(str_interval = val)
86 if intv is None:
87 self._current_match_candidates = []
88 else:
89 self._current_match_candidates = [{
90 'data': intv,
91 'field_label': gmDateTime.format_interval(intv, gmDateTime.acc_minutes),
92 'list_label': gmDateTime.format_interval(intv, gmDateTime.acc_minutes)
93 }]
94 self._picklist.SetItems(self._current_match_candidates)
95 #---------------------------------------------------------
96 # def _on_lose_focus(self, event):
97 # # are we valid ?
98 # if len(self._data) == 0:
99 # self._set_data_to_first_match()
100 #
101 # # let the base class do its thing
102 # super(cIntervalPhraseWheel, self)._on_lose_focus(event)
103 #--------------------------------------------------------
105 intv = item['data']
106 if intv is not None:
107 return gmDateTime.format_interval (
108 interval = intv,
109 accuracy_wanted = self.display_accuracy
110 )
111 return item['field_label']
112 #--------------------------------------------------------
114 intv = self.GetData()
115 if intv is None:
116 return u''
117 return gmDateTime.format_interval (
118 interval = intv,
119 accuracy_wanted = self.display_accuracy
120 )
121 #--------------------------------------------------------
122 # external API
123 #--------------------------------------------------------
125
126 if isinstance(value, pyDT.timedelta):
127 self.SetText(data = value, suppress_smarts = True)
128 return
129
130 if value is None:
131 value = u''
132
133 super(cIntervalPhraseWheel, self).SetValue(value)
134 #--------------------------------------------------------
136
137 if data is not None:
138 if value.strip() == u'':
139 value = gmDateTime.format_interval (
140 interval = data,
141 accuracy_wanted = self.display_accuracy
142 )
143
144 super(cIntervalPhraseWheel, self).SetText(value = value, data = data, suppress_smarts = suppress_smarts)
145 #--------------------------------------------------------
147 if data is None:
148 super(cIntervalPhraseWheel, self).SetText(u'', None)
149 return
150
151 value = gmDateTime.format_interval (
152 interval = data,
153 accuracy_wanted = self.display_accuracy
154 )
155 super(cIntervalPhraseWheel, self).SetText(value = value, data = data)
156 #--------------------------------------------------------
158 if len(self._data) == 0:
159 self._set_data_to_first_match()
160
161 return super(cIntervalPhraseWheel, self).GetData()
162 #============================================================
164 """Shows a calendar control from which the user can pick a date."""
166
167 wx.Dialog.__init__(self, parent, title = _('Pick a date ...'))
168 panel = wx.Panel(self, -1)
169
170 sizer = wx.BoxSizer(wx.VERTICAL)
171 panel.SetSizer(sizer)
172
173 cal = wx.calendar.CalendarCtrl(panel)
174
175 if sys.platform != 'win32':
176 # gtk truncates the year - this fixes it
177 w, h = cal.Size
178 cal.Size = (w+25, h)
179 cal.MinSize = cal.Size
180
181 sizer.Add(cal, 0)
182
183 button_sizer = wx.BoxSizer(wx.HORIZONTAL)
184 button_sizer.Add((0, 0), 1)
185 btn_ok = wx.Button(panel, wx.ID_OK)
186 btn_ok.SetDefault()
187 button_sizer.Add(btn_ok, 0, wx.ALL, 2)
188 button_sizer.Add((0, 0), 1)
189 btn_can = wx.Button(panel, wx.ID_CANCEL)
190 button_sizer.Add(btn_can, 0, wx.ALL, 2)
191 button_sizer.Add((0, 0), 1)
192 sizer.Add(button_sizer, 1, wx.EXPAND | wx.ALL, 10)
193 sizer.Fit(panel)
194 self.ClientSize = panel.Size
195
196 cal.Bind(wx.EVT_KEY_DOWN, self.__on_key_down)
197 cal.SetFocus()
198 self.cal = cal
199 #-----------------------------------------------------------
210
211 #============================================================
213 """Turns strings into candidate dates.
214
215 Matching on "all" (*, '') will pop up a calendar :-)
216 """
218
219 gmMatchProvider.cMatchProvider.__init__(self)
220
221 self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999)
222 self.word_separators = None
223 # self.ignored_chars("""[?!."'\\(){}\[\]<>~#*$%^_]+""")
224 #--------------------------------------------------------
225 # external API
226 #--------------------------------------------------------
227 #--------------------------------------------------------
228 # base class API
229 #--------------------------------------------------------
230 # internal matching algorithms
231 #
232 # if we end up here:
233 # - aFragment will not be "None"
234 # - aFragment will be lower case
235 # - we _do_ deliver matches (whether we find any is a different story)
236 #--------------------------------------------------------
238 """Return matches for aFragment at start of phrases."""
239 matches = gmDateTime.str2pydt_matches(str2parse = aFragment.strip())
240
241 if len(matches) == 0:
242 return (False, [])
243
244 items = []
245 for match in matches:
246 if match['data'] is None:
247 items.append ({
248 'data': None,
249 'field_label': match['label'],
250 'list_label': match['label']
251 })
252 continue
253
254 data = match['data'].replace (
255 hour = 11,
256 minute = 11,
257 second = 11,
258 microsecond = 111111
259 )
260 list_label = gmDateTime.pydt_strftime (
261 data,
262 format = '%A, %d. %B %Y (%x)',
263 accuracy = gmDateTime.acc_days
264 )
265 items.append ({
266 'data': data,
267 'field_label': match['label'],
268 'list_label': list_label
269 })
270
271 return (True, items)
272 #--------------------------------------------------------
274 """Return matches for aFragment at start of words inside phrases."""
275 return self.getMatchesByPhrase(aFragment)
276 #--------------------------------------------------------
278 """Return matches for aFragment as a true substring."""
279 return self.getMatchesByPhrase(aFragment)
280 #--------------------------------------------------------
286
287 # # consider this:
288 # dlg = cCalendarDatePickerDlg(None)
289 # # FIXME: show below parent
290 # dlg.CentreOnScreen()
291 #
292 # if dlg.ShowModal() == wx.ID_OK:
293 # date = dlg.cal.Date
294 # if date is not None:
295 # if date.IsValid():
296 # date = gmDateTime.wxDate2py_dt(wxDate = date).replace (
297 # hour = 11,
298 # minute = 11,
299 # second = 11,
300 # microsecond = 111111
301 # )
302 # lbl = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days)
303 # matches = (True, [{'data': date, 'label': lbl}])
304 # dlg.Destroy()
305 #
306 # return matches
307 #============================================================
309
311
312 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
313
314 self.matcher = cDateMatchProvider()
315 self.phrase_separators = None
316
317 self.static_tooltip_extra = _('<ALT-C/K>: pick from (c/k)alendar')
318 #--------------------------------------------------------
319 # internal helpers
320 #--------------------------------------------------------
321 # def __text2timestamp(self):
322 #
323 # self._update_candidates_in_picklist(val = self.GetValue().strip())
324 #
325 # if len(self._current_match_candidates) == 1:
326 # return self._current_match_candidates[0]['data']
327 #
328 # return None
329 #--------------------------------------------------------
331 dlg = cCalendarDatePickerDlg(self)
332 # FIXME: show below parent
333 dlg.CentreOnScreen()
334 decision = dlg.ShowModal()
335 date = dlg.cal.Date
336 dlg.Destroy()
337
338 if decision != wx.ID_OK:
339 return
340
341 if date is None:
342 return
343
344 if not date.IsValid():
345 return
346
347 date = gmDateTime.wxDate2py_dt(wxDate = date).replace (
348 hour = 11,
349 minute = 11,
350 second = 11,
351 microsecond = 111111
352 )
353 val = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days)
354 self.SetText(value = val, data = date, suppress_smarts = True)
355 #--------------------------------------------------------
356 # phrasewheel internal API
357 #--------------------------------------------------------
359 # no valid date yet ?
360 if len(self._data) == 0:
361 self._set_data_to_first_match()
362 date = self.GetData()
363 if date is not None:
364 self.SetValue(gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days))
365
366 # let the base class do its thing
367 super(cDateInputPhraseWheel, self)._on_lose_focus(event)
368 #--------------------------------------------------------
370 data = item['data']
371 if data is not None:
372 return gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days)
373 return item['field_label']
374 #--------------------------------------------------------
376
377 # <ALT-C> / <ALT-K> -> calendar
378 if event.AltDown() is True:
379 char = unichr(event.GetUnicodeKey())
380 if char in u'ckCK':
381 self.__pick_from_calendar()
382 return
383
384 super(cDateInputPhraseWheel, self)._on_key_down(event)
385 #--------------------------------------------------------
387 if len(self._data) == 0:
388 return u''
389
390 date = self.GetData()
391 # if match provider only provided completions
392 # but not a full date with it
393 if date is None:
394 return u''
395
396 return gmDateTime.pydt_strftime (
397 date,
398 format = '%A, %d. %B %Y (%x)',
399 accuracy = gmDateTime.acc_days
400 )
401 #--------------------------------------------------------
402 # external API
403 #--------------------------------------------------------
405
406 if isinstance(value, pyDT.datetime):
407 date = value.replace (
408 hour = 11,
409 minute = 11,
410 second = 11,
411 microsecond = 111111
412 )
413 self.SetText(data = date, suppress_smarts = True)
414 return
415
416 if value is None:
417 value = u''
418
419 super(self.__class__, self).SetValue(value)
420 #--------------------------------------------------------
422
423 if data is not None:
424 if isinstance(data, gmDateTime.cFuzzyTimestamp):
425 data = data.timestamp.replace (
426 hour = 11,
427 minute = 11,
428 second = 11,
429 microsecond = 111111
430 )
431 if value.strip() == u'':
432 value = gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days)
433
434 super(self.__class__, self).SetText(value = value, data = data, suppress_smarts = suppress_smarts)
435 #--------------------------------------------------------
437 if data is None:
438 gmPhraseWheel.cPhraseWheel.SetText(self, u'', None)
439 else:
440 if isinstance(data, gmDateTime.cFuzzyTimestamp):
441 data = data.timestamp.replace (
442 hour = 11,
443 minute = 11,
444 second = 11,
445 microsecond = 111111
446 )
447 val = gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days)
448 super(self.__class__, self).SetText(value = val, data = data)
449 #--------------------------------------------------------
451 if len(self._data) == 0:
452 self._set_data_to_first_match()
453
454 return super(self.__class__, self).GetData()
455 #--------------------------------------------------------
457 if len(self._data) > 0:
458 self.display_as_valid(True)
459 return True
460
461 if self.GetValue().strip() == u'':
462 if allow_empty:
463 self.display_as_valid(True)
464 return True
465 else:
466 self.display_as_valid(False)
467 return False
468
469 # skip showing calendar on '*' from here
470 if self.GetValue().strip() == u'*':
471 self.display_as_valid(False)
472 return False
473
474 # try to auto-snap to first match
475 self._set_data_to_first_match()
476 if len(self._data) == 0:
477 self.display_as_valid(False)
478 return False
479
480 date = self.GetData()
481 self.SetValue(gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days))
482 self.display_as_valid(True)
483 return True
484 #--------------------------------------------------------
485 # properties
486 #--------------------------------------------------------
488 return self.GetData()
489
492 # val = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days)
493 # self.data = date.replace (
494 # hour = 11,
495 # minute = 11,
496 # second = 11,
497 # microsecond = 111111
498 # )
499
500 date = property(_get_date, _set_date)
501 #============================================================
504 self.__allow_past = 1
505 self.__shifting_base = None
506
507 gmMatchProvider.cMatchProvider.__init__(self)
508
509 self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999)
510 self.word_separators = None
511 # self.ignored_chars("""[?!."'\\(){}\[\]<>~#*$%^_]+""")
512 #--------------------------------------------------------
513 # external API
514 #--------------------------------------------------------
515 #--------------------------------------------------------
516 # base class API
517 #--------------------------------------------------------
518 # internal matching algorithms
519 #
520 # if we end up here:
521 # - aFragment will not be "None"
522 # - aFragment will be lower case
523 # - we _do_ deliver matches (whether we find any is a different story)
524 #--------------------------------------------------------
526 """Return matches for aFragment at start of phrases."""
527 matches = gmDateTime.str2fuzzy_timestamp_matches(aFragment.strip())
528
529 if len(matches) == 0:
530 return (False, [])
531
532 items = []
533 for match in matches:
534 items.append ({
535 'data': match['data'],
536 'field_label': match['label'],
537 'list_label': match['label']
538 })
539
540 return (True, items)
541 #--------------------------------------------------------
543 """Return matches for aFragment at start of words inside phrases."""
544 return self.getMatchesByPhrase(aFragment)
545 #--------------------------------------------------------
547 """Return matches for aFragment as a true substring."""
548 return self.getMatchesByPhrase(aFragment)
549 #--------------------------------------------------------
553 #==================================================
555
557
558 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
559
560 self.matcher = cMatchProvider_FuzzyTimestamp()
561 self.phrase_separators = None
562 self.selection_only = True
563 self.selection_only_error_msg = _('Cannot interpret input as timestamp.')
564 self.display_accuracy = None
565 #--------------------------------------------------------
566 # internal helpers
567 #--------------------------------------------------------
569
570 if val is None:
571 val = self.GetValue().strip()
572
573 success, matches = self.matcher.getMatchesByPhrase(val)
574 if len(matches) == 1:
575 return matches[0]['data']
576
577 return None
578 #--------------------------------------------------------
579 # phrasewheel internal API
580 #--------------------------------------------------------
582 # are we valid ?
583 if self.data is None:
584 # no, so try
585 date = self.__text2timestamp()
586 if date is not None:
587 self.SetValue(value = date.format_accurately(accuracy = self.display_accuracy))
588 self.data = date
589
590 # let the base class do its thing
591 gmPhraseWheel.cPhraseWheel._on_lose_focus(self, event)
592 #--------------------------------------------------------
594 data = item['data']
595 if data is not None:
596 return data.format_accurately(accuracy = self.display_accuracy)
597 return item['field_label']
598 #--------------------------------------------------------
599 # external API
600 #--------------------------------------------------------
602
603 if data is not None:
604 if isinstance(data, pyDT.datetime):
605 data = gmDateTime.cFuzzyTimestamp(timestamp=data)
606 if value.strip() == u'':
607 value = data.format_accurately(accuracy = self.display_accuracy)
608
609 gmPhraseWheel.cPhraseWheel.SetText(self, value = value, data = data, suppress_smarts = suppress_smarts)
610 #--------------------------------------------------------
612 if data is None:
613 gmPhraseWheel.cPhraseWheel.SetText(self, u'', None)
614 else:
615 if isinstance(data, pyDT.datetime):
616 data = gmDateTime.cFuzzyTimestamp(timestamp=data)
617 gmPhraseWheel.cPhraseWheel.SetText(self, value = data.format_accurately(accuracy = self.display_accuracy), data = data)
618 #--------------------------------------------------------
620 if self.data is not None:
621 return True
622
623 # skip empty value
624 if self.GetValue().strip() == u'':
625 return True
626
627 date = self.__text2timestamp()
628 if date is None:
629 return False
630
631 self.SetText (
632 value = date.format_accurately(accuracy = self.display_accuracy),
633 data = date,
634 suppress_smarts = True
635 )
636
637 return True
638 #==================================================
639 # main
640 #--------------------------------------------------
641 if __name__ == '__main__':
642
643 if len(sys.argv) < 2:
644 sys.exit()
645
646 if sys.argv[1] != 'test':
647 sys.exit()
648
649 gmI18N.activate_locale()
650 gmI18N.install_domain(domain='gnumed')
651 gmDateTime.init()
652
653 #----------------------------------------------------
655 mp = cMatchProvider_FuzzyTimestamp()
656 mp.word_separators = None
657 mp.setThresholds(aWord = 998, aSubstring = 999)
658 val = None
659 while val != 'exit':
660 print "************************************"
661 val = raw_input('Enter date fragment ("exit" to quit): ')
662 found, matches = mp.getMatches(aFragment=val)
663 for match in matches:
664 #print match
665 print match['label']
666 print match['data']
667 print "---------------"
668 #--------------------------------------------------------
670 app = wx.PyWidgetTester(size = (300, 40))
671 app.SetWidget(cFuzzyTimestampInput, id=-1, size=(180,20), pos=(10,20))
672 app.MainLoop()
673 #--------------------------------------------------------
675 app = wx.PyWidgetTester(size = (300, 40))
676 app.SetWidget(cDateInputPhraseWheel, id=-1, size=(180,20), pos=(10,20))
677 app.MainLoop()
678 #--------------------------------------------------------
679 #test_cli()
680 #test_fuzzy_picker()
681 test_picker()
682
683 #==================================================
684
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Dec 5 04:00:02 2011 | http://epydoc.sourceforge.net |