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