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 #============================================================78165 166 #============================================================80 81 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 82 self.phrase_separators = None 83 self.display_accuracy = None84 #-------------------------------------------------------- 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()168 """Shows a calendar control from which the user can pick a date."""215 216 #============================================================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 = cal203 204 #-----------------------------------------------------------206 code = evt.KeyCode 207 if code == wx.WXK_TAB: 208 self.cal.Navigate() 209 elif code in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER): 210 self.EndModal(wx.ID_OK) 211 elif code == wx.WXK_ESCAPE: 212 self.EndModal(wx.ID_CANCEL) 213 else: 214 evt.Skip()218 """Turns strings into candidate dates. 219 220 Matching on "all" (*, '') will pop up a calendar :-) 221 """291 292 # # consider this: 293 # dlg = cCalendarDatePickerDlg(None) 294 # # FIXME: show below parent 295 # dlg.CentreOnScreen() 296 # 297 # if dlg.ShowModal() == wx.ID_OK: 298 # date = dlg.cal.Date 299 # if date is not None: 300 # if date.IsValid(): 301 # date = gmDateTime.wxDate2py_dt(wxDate = date).replace ( 302 # hour = 11, 303 # minute = 11, 304 # second = 11, 305 # microsecond = 111111 306 # ) 307 # lbl = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 308 # matches = (True, [{'data': date, 'label': lbl}]) 309 # dlg.DestroyLater() 310 # 311 # return matches 312 313 #============================================================223 224 gmMatchProvider.cMatchProvider.__init__(self) 225 226 self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999) 227 self.word_separators = None228 # self.ignored_chars("""[?!."'\\(){}\[\]<>~#*$%^_]+""") 229 #-------------------------------------------------------- 230 # external API 231 #-------------------------------------------------------- 232 #-------------------------------------------------------- 233 # base class API 234 #-------------------------------------------------------- 235 # internal matching algorithms 236 # 237 # if we end up here: 238 # - aFragment will not be "None" 239 # - aFragment will be lower case 240 # - we _do_ deliver matches (whether we find any is a different story) 241 #--------------------------------------------------------243 """Return matches for aFragment at start of phrases.""" 244 matches = gmDateTime.str2pydt_matches(str2parse = aFragment.strip()) 245 246 if len(matches) == 0: 247 return (False, []) 248 249 items = [] 250 for match in matches: 251 if match['data'] is None: 252 items.append ({ 253 'data': None, 254 'field_label': match['label'], 255 'list_label': match['label'] 256 }) 257 continue 258 259 data = match['data'].replace ( 260 hour = 11, 261 minute = 11, 262 second = 11, 263 microsecond = 111111 264 ) 265 list_label = gmDateTime.pydt_strftime ( 266 data, 267 format = '%A, %d. %B %Y (%x)', 268 accuracy = gmDateTime.acc_days 269 ) 270 items.append ({ 271 'data': data, 272 'field_label': match['label'], 273 'list_label': list_label 274 }) 275 276 return (True, items)277 #--------------------------------------------------------279 """Return matches for aFragment at start of words inside phrases.""" 280 return self.getMatchesByPhrase(aFragment)281 #--------------------------------------------------------283 """Return matches for aFragment as a true substring.""" 284 return self.getMatchesByPhrase(aFragment)285 #--------------------------------------------------------315532 533 #============================================================317 318 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 319 320 self.matcher = cDateMatchProvider() 321 self.phrase_separators = None 322 323 self.static_tooltip_extra = _('<ALT-C/K>: pick from (c/k)alendar') 324 325 self.__weekday_keys = [wx.WXK_F1, wx.WXK_F2, wx.WXK_F3, wx.WXK_F4, wx.WXK_F5, wx.WXK_F6, wx.WXK_F7]326 327 #-------------------------------------------------------- 328 # internal helpers 329 #-------------------------------------------------------- 330 # def __text2timestamp(self): 331 # 332 # self._update_candidates_in_picklist(val = self.GetValue().strip()) 333 # 334 # if len(self._current_match_candidates) == 1: 335 # return self._current_match_candidates[0]['data'] 336 # 337 # return None 338 #--------------------------------------------------------340 dlg = cCalendarDatePickerDlg(self) 341 # FIXME: show below parent 342 dlg.CentreOnScreen() 343 decision = dlg.ShowModal() 344 date = dlg.cal.Date 345 dlg.DestroyLater() 346 347 if decision != wx.ID_OK: 348 return 349 350 if date is None: 351 return 352 353 if not date.IsValid(): 354 return 355 356 date = gmDateTime.wxDate2py_dt(wxDate = date).replace ( 357 hour = 11, 358 minute = 11, 359 second = 11, 360 microsecond = 111111 361 ) 362 val = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 363 self.SetText(value = val, data = date, suppress_smarts = True)364 365 #--------------------------------------------------------367 self.is_valid_timestamp(empty_is_valid = True) 368 target_date = gmDateTime.get_date_of_weekday_in_week_of_date(weekday, base_dt = self.date) 369 val = gmDateTime.pydt_strftime(target_date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 370 self.SetText(value = val, data = target_date, suppress_smarts = True)371 372 #-------------------------------------------------------- 373 # phrasewheel internal API 374 #--------------------------------------------------------376 # no valid date yet ? 377 if len(self._data) == 0: 378 self._set_data_to_first_match() 379 date = self.GetData() 380 if date is not None: 381 self.SetValue(gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days)) 382 383 # let the base class do its thing 384 super(cDateInputPhraseWheel, self)._on_lose_focus(event)385 386 #--------------------------------------------------------388 data = item['data'] 389 if data is not None: 390 return gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 391 return item['field_label']392 393 #--------------------------------------------------------395 396 if event.GetUnicodeKey() == wx.WXK_NONE: 397 key = event.GetKeyCode() 398 if key in self.__weekday_keys: 399 self.__pick_from_weekday(self.__weekday_keys.index(key)) 400 return 401 402 # <ALT-C> / <ALT-K> -> calendar 403 if event.AltDown() is True: 404 char = chr(event.GetUnicodeKey()) 405 if char in 'ckCK': 406 self.__pick_from_calendar() 407 return 408 409 super()._on_key_down(event)410 411 #--------------------------------------------------------413 if len(self._data) == 0: 414 return '' 415 416 date = self.GetData() 417 # if match provider only provided completions 418 # but not a full date with it 419 if date is None: 420 return '' 421 422 now = gmDateTime.pydt_now_here() 423 if date > now: 424 intv = date - now 425 template = _('%s\n (a %s in %s)') 426 else: 427 intv = now - date 428 template = _('%s\n (a %s %s ago)') 429 return template % ( 430 gmDateTime.pydt_strftime(date, format = '%B %d %Y -- %c', accuracy = gmDateTime.acc_days), 431 gmDateTime.pydt_strftime(date, format = '%A', accuracy = gmDateTime.acc_days), 432 gmDateTime.format_interval(interval = intv, accuracy_wanted = gmDateTime.acc_days, verbose = True) 433 )434 435 #-------------------------------------------------------- 436 # external API 437 #--------------------------------------------------------439 440 if isinstance(value, pyDT.datetime): 441 date = value.replace ( 442 hour = 11, 443 minute = 11, 444 second = 11, 445 microsecond = 111111 446 ) 447 self.SetText(data = date, suppress_smarts = True) 448 return 449 450 if value is None: 451 value = '' 452 453 super().SetValue(value)454 455 #--------------------------------------------------------457 458 if data is not None: 459 if isinstance(data, gmDateTime.cFuzzyTimestamp): 460 data = data.timestamp.replace ( 461 hour = 11, 462 minute = 11, 463 second = 11, 464 microsecond = 111111 465 ) 466 if value.strip() == '': 467 value = gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 468 469 super().SetText(value = value, data = data, suppress_smarts = suppress_smarts)470 471 #--------------------------------------------------------473 if data is None: 474 gmPhraseWheel.cPhraseWheel.SetText(self, '', None) 475 return 476 self.SetText(data = data)477 478 #--------------------------------------------------------480 if len(self._data) == 0: 481 self._set_data_to_first_match() 482 483 return super(self.__class__, self).GetData()484 485 #--------------------------------------------------------487 if len(self._data) > 0: 488 self.display_as_valid(True) 489 return True 490 491 if self.GetValue().strip() == '': 492 if empty_is_valid: 493 self.display_as_valid(True) 494 return True 495 else: 496 self.display_as_valid(False) 497 return False 498 499 # skip showing calendar on '*' from here 500 if self.GetValue().strip() == '*': 501 self.display_as_valid(False) 502 return False 503 504 # try to auto-snap to first match 505 self._set_data_to_first_match() 506 if len(self._data) == 0: 507 self.display_as_valid(False) 508 return False 509 510 date = self.GetData() 511 self.SetValue(gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days))#, none_str = u'') 512 self.display_as_valid(True) 513 return True514 515 #-------------------------------------------------------- 516 # properties 517 #--------------------------------------------------------519 return self.GetData()520 523 # val = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 524 # self.data = date.replace ( 525 # hour = 11, 526 # minute = 11, 527 # second = 11, 528 # microsecond = 111111 529 # ) 530 531 date = property(_get_date, _set_date)585 586 #==================================================536 self.__allow_past = 1 537 self.__shifting_base = None 538 539 gmMatchProvider.cMatchProvider.__init__(self) 540 541 self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999) 542 self.word_separators = None543 # self.ignored_chars("""[?!."'\\(){}\[\]<>~#*$%^_]+""") 544 #-------------------------------------------------------- 545 # external API 546 #-------------------------------------------------------- 547 #-------------------------------------------------------- 548 # base class API 549 #-------------------------------------------------------- 550 # internal matching algorithms 551 # 552 # if we end up here: 553 # - aFragment will not be "None" 554 # - aFragment will be lower case 555 # - we _do_ deliver matches (whether we find any is a different story) 556 #--------------------------------------------------------558 """Return matches for aFragment at start of phrases.""" 559 matches = gmDateTime.str2fuzzy_timestamp_matches(aFragment.strip()) 560 561 if len(matches) == 0: 562 return (False, []) 563 564 items = [] 565 for match in matches: 566 items.append ({ 567 'data': match['data'], 568 'field_label': match['label'], 569 'list_label': match['label'] 570 }) 571 572 return (True, items)573 #--------------------------------------------------------575 """Return matches for aFragment at start of words inside phrases.""" 576 return self.getMatchesByPhrase(aFragment)577 #--------------------------------------------------------579 """Return matches for aFragment as a true substring.""" 580 return self.getMatchesByPhrase(aFragment)581 #--------------------------------------------------------588740 741 #================================================== 742 # main 743 #-------------------------------------------------- 744 if __name__ == '__main__': 745 746 if len(sys.argv) < 2: 747 sys.exit() 748 749 if sys.argv[1] != 'test': 750 sys.exit() 751 752 gmI18N.activate_locale() 753 gmI18N.install_domain(domain='gnumed') 754 gmDateTime.init() 755 756 #----------------------------------------------------590 591 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 592 593 self.matcher = cMatchProvider_FuzzyTimestamp() 594 self.phrase_separators = None 595 self.selection_only = True 596 self.selection_only_error_msg = _('Cannot interpret input as timestamp.') 597 self.display_accuracy = None 598 599 self.__weekday_keys = [wx.WXK_F1, wx.WXK_F2, wx.WXK_F3, wx.WXK_F4, wx.WXK_F5, wx.WXK_F6, wx.WXK_F7]600 601 #-------------------------------------------------------- 602 # internal helpers 603 #--------------------------------------------------------605 if val is None: 606 val = self.GetValue() 607 val = val.strip() 608 if val == '': 609 return None 610 success, matches = self.matcher.getMatchesByPhrase(val) 611 if len(matches) == 1: 612 return matches[0]['data'] 613 return None614 615 #--------------------------------------------------------617 self.is_valid_timestamp(empty_is_valid = True) 618 dt = self.GetData() 619 if dt is not None: 620 dt = dt.timestamp 621 target_date = gmDateTime.get_date_of_weekday_in_week_of_date(weekday, base_dt = dt) 622 self.SetText(data = target_date, suppress_smarts = True)623 624 #-------------------------------------------------------- 625 # phrasewheel internal API 626 #--------------------------------------------------------628 # are we valid ? 629 if self.data is None: 630 # no, so try 631 date = self.__text2timestamp() 632 if date is not None: 633 self.SetValue(value = date.format_accurately(accuracy = self.display_accuracy)) 634 self.data = date 635 636 # let the base class do its thing 637 gmPhraseWheel.cPhraseWheel._on_lose_focus(self, event)638 639 #--------------------------------------------------------641 data = item['data'] 642 if data is not None: 643 return data.format_accurately(accuracy = self.display_accuracy) 644 return item['field_label']645 646 #--------------------------------------------------------648 649 if event.GetUnicodeKey() == wx.WXK_NONE: 650 key = event.GetKeyCode() 651 if key in self.__weekday_keys: 652 self.__pick_from_weekday(self.__weekday_keys.index(key)) 653 return 654 655 # # <ALT-C> / <ALT-K> -> calendar 656 # if event.AltDown() is True: 657 # char = chr(event.GetUnicodeKey()) 658 # if char in 'ckCK': 659 # self.__pick_from_calendar() 660 # return 661 662 super()._on_key_down(event)663 664 #--------------------------------------------------------666 if len(self._data) == 0: 667 return '' 668 669 date = self.GetData() 670 # if match provider only provided completions 671 # but not a full date with it 672 if date is None: 673 return '' 674 ts = date.timestamp 675 now = gmDateTime.pydt_now_here() 676 if ts > now: 677 intv = ts - now 678 template = _('%s\n %s\n in %s') 679 else: 680 intv = now - ts 681 template = _('%s\n%s\n%s ago') 682 txt = template % ( 683 date.format_accurately(self.display_accuracy), 684 gmDateTime.pydt_strftime ( 685 ts, 686 format = '%A, %B-%d %Y (%c)', 687 ), 688 gmDateTime.format_interval ( 689 interval = intv, 690 accuracy_wanted = gmDateTime.acc_days, 691 verbose = True 692 ) 693 ) 694 return txt695 696 #-------------------------------------------------------- 697 # external API 698 #--------------------------------------------------------700 701 if data is not None: 702 if isinstance(data, pyDT.datetime): 703 data = gmDateTime.cFuzzyTimestamp(timestamp = data) 704 if value.strip() == '': 705 value = data.format_accurately(accuracy = self.display_accuracy) 706 707 super().SetText(value = value, data = data, suppress_smarts = suppress_smarts)708 709 #--------------------------------------------------------711 if data is None: 712 gmPhraseWheel.cPhraseWheel.SetText(self, '', None) 713 else: 714 if isinstance(data, pyDT.datetime): 715 data = gmDateTime.cFuzzyTimestamp(timestamp=data) 716 gmPhraseWheel.cPhraseWheel.SetText(self, value = data.format_accurately(accuracy = self.display_accuracy), data = data)717 718 #--------------------------------------------------------720 if self.GetData() is not None: 721 return True 722 723 # skip empty value 724 if self.GetValue().strip() == '': 725 if empty_is_valid: 726 return True 727 return False 728 729 date = self.__text2timestamp() 730 if date is None: 731 return False 732 733 self.SetText ( 734 value = date.format_accurately(accuracy = self.display_accuracy), 735 data = date, 736 suppress_smarts = True 737 ) 738 739 return True758 mp = cMatchProvider_FuzzyTimestamp() 759 mp.word_separators = None 760 mp.setThresholds(aWord = 998, aSubstring = 999) 761 val = None 762 while val != 'exit': 763 print("************************************") 764 val = input('Enter date fragment ("exit" to quit): ') 765 found, matches = mp.getMatches(aFragment=val) 766 for match in matches: 767 #print match 768 print(match['label']) 769 print(match['data']) 770 print("---------------")771 #--------------------------------------------------------773 app = wx.PyWidgetTester(size = (300, 40)) 774 app.SetWidget(cFuzzyTimestampInput, id=-1, size=(180,20), pos=(10,20)) 775 app.MainLoop()776 #--------------------------------------------------------778 app = wx.PyWidgetTester(size = (300, 40)) 779 app.SetWidget(cDateInputPhraseWheel, id=-1, size=(180,20), pos=(10,20)) 780 app.MainLoop()781 #-------------------------------------------------------- 782 #test_cli() 783 #test_fuzzy_picker() 784 test_picker() 785 786 #================================================== 787
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Sun Jul 28 01:55:29 2019 | http://epydoc.sourceforge.net |