| 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 return
440 self.SetText(data = data)
441 #--------------------------------------------------------
443 if len(self._data) == 0:
444 self._set_data_to_first_match()
445
446 return super(self.__class__, self).GetData()
447 #--------------------------------------------------------
449 if len(self._data) > 0:
450 self.display_as_valid(True)
451 return True
452
453 if self.GetValue().strip() == u'':
454 if allow_empty:
455 self.display_as_valid(True)
456 return True
457 else:
458 self.display_as_valid(False)
459 return False
460
461 # skip showing calendar on '*' from here
462 if self.GetValue().strip() == u'*':
463 self.display_as_valid(False)
464 return False
465
466 # try to auto-snap to first match
467 self._set_data_to_first_match()
468 if len(self._data) == 0:
469 self.display_as_valid(False)
470 return False
471
472 date = self.GetData()
473 self.SetValue(gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days))
474 self.display_as_valid(True)
475 return True
476 #--------------------------------------------------------
477 # properties
478 #--------------------------------------------------------
480 return self.GetData()
481
484 # val = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days)
485 # self.data = date.replace (
486 # hour = 11,
487 # minute = 11,
488 # second = 11,
489 # microsecond = 111111
490 # )
491
492 date = property(_get_date, _set_date)
493 #============================================================
496 self.__allow_past = 1
497 self.__shifting_base = None
498
499 gmMatchProvider.cMatchProvider.__init__(self)
500
501 self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999)
502 self.word_separators = None
503 # self.ignored_chars("""[?!."'\\(){}\[\]<>~#*$%^_]+""")
504 #--------------------------------------------------------
505 # external API
506 #--------------------------------------------------------
507 #--------------------------------------------------------
508 # base class API
509 #--------------------------------------------------------
510 # internal matching algorithms
511 #
512 # if we end up here:
513 # - aFragment will not be "None"
514 # - aFragment will be lower case
515 # - we _do_ deliver matches (whether we find any is a different story)
516 #--------------------------------------------------------
518 """Return matches for aFragment at start of phrases."""
519 matches = gmDateTime.str2fuzzy_timestamp_matches(aFragment.strip())
520
521 if len(matches) == 0:
522 return (False, [])
523
524 items = []
525 for match in matches:
526 items.append ({
527 'data': match['data'],
528 'field_label': match['label'],
529 'list_label': match['label']
530 })
531
532 return (True, items)
533 #--------------------------------------------------------
535 """Return matches for aFragment at start of words inside phrases."""
536 return self.getMatchesByPhrase(aFragment)
537 #--------------------------------------------------------
539 """Return matches for aFragment as a true substring."""
540 return self.getMatchesByPhrase(aFragment)
541 #--------------------------------------------------------
545 #==================================================
547
549
550 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
551
552 self.matcher = cMatchProvider_FuzzyTimestamp()
553 self.phrase_separators = None
554 self.selection_only = True
555 self.selection_only_error_msg = _('Cannot interpret input as timestamp.')
556 self.display_accuracy = None
557 #--------------------------------------------------------
558 # internal helpers
559 #--------------------------------------------------------
561 if val is None:
562 val = self.GetValue()
563 val = val.strip()
564 if val == u'':
565 return None
566 success, matches = self.matcher.getMatchesByPhrase(val)
567 if len(matches) == 1:
568 return matches[0]['data']
569 return None
570 #--------------------------------------------------------
571 # phrasewheel internal API
572 #--------------------------------------------------------
574 # are we valid ?
575 if self.data is None:
576 # no, so try
577 date = self.__text2timestamp()
578 if date is not None:
579 self.SetValue(value = date.format_accurately(accuracy = self.display_accuracy))
580 self.data = date
581
582 # let the base class do its thing
583 gmPhraseWheel.cPhraseWheel._on_lose_focus(self, event)
584 #--------------------------------------------------------
586 data = item['data']
587 if data is not None:
588 return data.format_accurately(accuracy = self.display_accuracy)
589 return item['field_label']
590 #--------------------------------------------------------
591 # external API
592 #--------------------------------------------------------
594
595 if data is not None:
596 if isinstance(data, pyDT.datetime):
597 data = gmDateTime.cFuzzyTimestamp(timestamp=data)
598 if value.strip() == u'':
599 value = data.format_accurately(accuracy = self.display_accuracy)
600
601 gmPhraseWheel.cPhraseWheel.SetText(self, value = value, data = data, suppress_smarts = suppress_smarts)
602 #--------------------------------------------------------
604 if data is None:
605 gmPhraseWheel.cPhraseWheel.SetText(self, u'', None)
606 else:
607 if isinstance(data, pyDT.datetime):
608 data = gmDateTime.cFuzzyTimestamp(timestamp=data)
609 gmPhraseWheel.cPhraseWheel.SetText(self, value = data.format_accurately(accuracy = self.display_accuracy), data = data)
610 #--------------------------------------------------------
612 if self.GetData() is not None:
613 return True
614
615 # skip empty value
616 if self.GetValue().strip() == u'':
617 if empty_is_valid:
618 return True
619 return False
620
621 date = self.__text2timestamp()
622 if date is None:
623 return False
624
625 self.SetText (
626 value = date.format_accurately(accuracy = self.display_accuracy),
627 data = date,
628 suppress_smarts = True
629 )
630
631 return True
632 #==================================================
633 # main
634 #--------------------------------------------------
635 if __name__ == '__main__':
636
637 if len(sys.argv) < 2:
638 sys.exit()
639
640 if sys.argv[1] != 'test':
641 sys.exit()
642
643 gmI18N.activate_locale()
644 gmI18N.install_domain(domain='gnumed')
645 gmDateTime.init()
646
647 #----------------------------------------------------
649 mp = cMatchProvider_FuzzyTimestamp()
650 mp.word_separators = None
651 mp.setThresholds(aWord = 998, aSubstring = 999)
652 val = None
653 while val != 'exit':
654 print "************************************"
655 val = raw_input('Enter date fragment ("exit" to quit): ')
656 found, matches = mp.getMatches(aFragment=val)
657 for match in matches:
658 #print match
659 print match['label']
660 print match['data']
661 print "---------------"
662 #--------------------------------------------------------
664 app = wx.PyWidgetTester(size = (300, 40))
665 app.SetWidget(cFuzzyTimestampInput, id=-1, size=(180,20), pos=(10,20))
666 app.MainLoop()
667 #--------------------------------------------------------
669 app = wx.PyWidgetTester(size = (300, 40))
670 app.SetWidget(cDateInputPhraseWheel, id=-1, size=(180,20), pos=(10,20))
671 app.MainLoop()
672 #--------------------------------------------------------
673 #test_cli()
674 #test_fuzzy_picker()
675 test_picker()
676
677 #==================================================
678
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Oct 5 03:57:03 2013 | http://epydoc.sourceforge.net |