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 #--------------------------------------------------------315543 544 #============================================================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 if isinstance(date, pyDT.datetime): 423 date = pyDT.date(date.year, date.month, date.day) 424 today = pyDT.date.today() 425 if date > today: 426 intv = date - today 427 return _('%s\n (a %s in %s)') % ( 428 date.strftime(format = '%B %d %Y -- %x'), 429 date.strftime(format = '%A'), 430 gmDateTime.format_interval(interval = intv, accuracy_wanted = gmDateTime.acc_days, verbose = True) 431 ) 432 433 if date < today: 434 intv = today - date 435 return _('%s\n (a %s %s ago)') % ( 436 gmDateTime.pydt_strftime(date, format = '%B %d %Y -- %x'), 437 gmDateTime.pydt_strftime(date, format = '%A'), 438 gmDateTime.format_interval(interval = intv, accuracy_wanted = gmDateTime.acc_days, verbose = True) 439 ) 440 441 return _('today (%s): %s') % ( 442 gmDateTime.pydt_strftime(date, format = '%A'), 443 gmDateTime.pydt_strftime(date, format = '%B %d %Y -- %x') 444 )445 446 #-------------------------------------------------------- 447 # external API 448 #--------------------------------------------------------450 451 if isinstance(value, pyDT.datetime): 452 date = value.replace ( 453 hour = 11, 454 minute = 11, 455 second = 11, 456 microsecond = 111111 457 ) 458 self.SetText(data = date, suppress_smarts = True) 459 return 460 461 if value is None: 462 value = '' 463 464 super().SetValue(value)465 466 #--------------------------------------------------------468 469 if data is not None: 470 if isinstance(data, gmDateTime.cFuzzyTimestamp): 471 data = data.timestamp.replace ( 472 hour = 11, 473 minute = 11, 474 second = 11, 475 microsecond = 111111 476 ) 477 if value.strip() == '': 478 value = gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 479 480 super().SetText(value = value, data = data, suppress_smarts = suppress_smarts)481 482 #--------------------------------------------------------484 if data is None: 485 gmPhraseWheel.cPhraseWheel.SetText(self, '', None) 486 return 487 self.SetText(data = data)488 489 #--------------------------------------------------------491 if len(self._data) == 0: 492 self._set_data_to_first_match() 493 494 return super(self.__class__, self).GetData()495 496 #--------------------------------------------------------498 if len(self._data) > 0: 499 self.display_as_valid(True) 500 return True 501 502 if self.GetValue().strip() == '': 503 if empty_is_valid: 504 self.display_as_valid(True) 505 return True 506 else: 507 self.display_as_valid(False) 508 return False 509 510 # skip showing calendar on '*' from here 511 if self.GetValue().strip() == '*': 512 self.display_as_valid(False) 513 return False 514 515 # try to auto-snap to first match 516 self._set_data_to_first_match() 517 if len(self._data) == 0: 518 self.display_as_valid(False) 519 return False 520 521 date = self.GetData() 522 self.SetValue(gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days))#, none_str = u'') 523 self.display_as_valid(True) 524 return True525 526 #-------------------------------------------------------- 527 # properties 528 #--------------------------------------------------------530 return self.GetData()531 534 # val = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 535 # self.data = date.replace ( 536 # hour = 11, 537 # minute = 11, 538 # second = 11, 539 # microsecond = 111111 540 # ) 541 542 date = property(_get_date, _set_date)596 597 #==================================================547 self.__allow_past = 1 548 self.__shifting_base = None 549 550 gmMatchProvider.cMatchProvider.__init__(self) 551 552 self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999) 553 self.word_separators = None554 # self.ignored_chars("""[?!."'\\(){}\[\]<>~#*$%^_]+""") 555 #-------------------------------------------------------- 556 # external API 557 #-------------------------------------------------------- 558 #-------------------------------------------------------- 559 # base class API 560 #-------------------------------------------------------- 561 # internal matching algorithms 562 # 563 # if we end up here: 564 # - aFragment will not be "None" 565 # - aFragment will be lower case 566 # - we _do_ deliver matches (whether we find any is a different story) 567 #--------------------------------------------------------569 """Return matches for aFragment at start of phrases.""" 570 matches = gmDateTime.str2fuzzy_timestamp_matches(aFragment.strip()) 571 572 if len(matches) == 0: 573 return (False, []) 574 575 items = [] 576 for match in matches: 577 items.append ({ 578 'data': match['data'], 579 'field_label': match['label'], 580 'list_label': match['label'] 581 }) 582 583 return (True, items)584 #--------------------------------------------------------586 """Return matches for aFragment at start of words inside phrases.""" 587 return self.getMatchesByPhrase(aFragment)588 #--------------------------------------------------------590 """Return matches for aFragment as a true substring.""" 591 return self.getMatchesByPhrase(aFragment)592 #--------------------------------------------------------599751 752 #================================================== 753 # main 754 #-------------------------------------------------- 755 if __name__ == '__main__': 756 757 if len(sys.argv) < 2: 758 sys.exit() 759 760 if sys.argv[1] != 'test': 761 sys.exit() 762 763 gmI18N.activate_locale() 764 gmI18N.install_domain(domain='gnumed') 765 gmDateTime.init() 766 767 #----------------------------------------------------601 602 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 603 604 self.matcher = cMatchProvider_FuzzyTimestamp() 605 self.phrase_separators = None 606 self.selection_only = True 607 self.selection_only_error_msg = _('Cannot interpret input as timestamp.') 608 self.display_accuracy = None 609 610 self.__weekday_keys = [wx.WXK_F1, wx.WXK_F2, wx.WXK_F3, wx.WXK_F4, wx.WXK_F5, wx.WXK_F6, wx.WXK_F7]611 612 #-------------------------------------------------------- 613 # internal helpers 614 #--------------------------------------------------------616 if val is None: 617 val = self.GetValue() 618 val = val.strip() 619 if val == '': 620 return None 621 success, matches = self.matcher.getMatchesByPhrase(val) 622 if len(matches) == 1: 623 return matches[0]['data'] 624 return None625 626 #--------------------------------------------------------628 self.is_valid_timestamp(empty_is_valid = True) 629 dt = self.GetData() 630 if dt is not None: 631 dt = dt.timestamp 632 target_date = gmDateTime.get_date_of_weekday_in_week_of_date(weekday, base_dt = dt) 633 self.SetText(data = target_date, suppress_smarts = True)634 635 #-------------------------------------------------------- 636 # phrasewheel internal API 637 #--------------------------------------------------------639 # are we valid ? 640 if self.data is None: 641 # no, so try 642 date = self.__text2timestamp() 643 if date is not None: 644 self.SetValue(value = date.format_accurately(accuracy = self.display_accuracy)) 645 self.data = date 646 647 # let the base class do its thing 648 gmPhraseWheel.cPhraseWheel._on_lose_focus(self, event)649 650 #--------------------------------------------------------652 data = item['data'] 653 if data is not None: 654 return data.format_accurately(accuracy = self.display_accuracy) 655 return item['field_label']656 657 #--------------------------------------------------------659 660 if event.GetUnicodeKey() == wx.WXK_NONE: 661 key = event.GetKeyCode() 662 if key in self.__weekday_keys: 663 self.__pick_from_weekday(self.__weekday_keys.index(key)) 664 return 665 666 # # <ALT-C> / <ALT-K> -> calendar 667 # if event.AltDown() is True: 668 # char = chr(event.GetUnicodeKey()) 669 # if char in 'ckCK': 670 # self.__pick_from_calendar() 671 # return 672 673 super()._on_key_down(event)674 675 #--------------------------------------------------------677 if len(self._data) == 0: 678 return '' 679 680 date = self.GetData() 681 # if match provider only provided completions 682 # but not a full date with it 683 if date is None: 684 return '' 685 ts = date.timestamp 686 now = gmDateTime.pydt_now_here() 687 if ts > now: 688 intv = ts - now 689 template = _('%s\n %s\n in %s') 690 else: 691 intv = now - ts 692 template = _('%s\n%s\n%s ago') 693 txt = template % ( 694 date.format_accurately(self.display_accuracy), 695 gmDateTime.pydt_strftime ( 696 ts, 697 format = '%A, %B-%d %Y (%c)', 698 ), 699 gmDateTime.format_interval ( 700 interval = intv, 701 accuracy_wanted = gmDateTime.acc_days, 702 verbose = True 703 ) 704 ) 705 return txt706 707 #-------------------------------------------------------- 708 # external API 709 #--------------------------------------------------------711 712 if data is not None: 713 if isinstance(data, pyDT.datetime): 714 data = gmDateTime.cFuzzyTimestamp(timestamp = data) 715 if value.strip() == '': 716 value = data.format_accurately(accuracy = self.display_accuracy) 717 718 super().SetText(value = value, data = data, suppress_smarts = suppress_smarts)719 720 #--------------------------------------------------------722 if data is None: 723 gmPhraseWheel.cPhraseWheel.SetText(self, '', None) 724 else: 725 if isinstance(data, pyDT.datetime): 726 data = gmDateTime.cFuzzyTimestamp(timestamp=data) 727 gmPhraseWheel.cPhraseWheel.SetText(self, value = data.format_accurately(accuracy = self.display_accuracy), data = data)728 729 #--------------------------------------------------------731 if self.GetData() is not None: 732 return True 733 734 # skip empty value 735 if self.GetValue().strip() == '': 736 if empty_is_valid: 737 return True 738 return False 739 740 date = self.__text2timestamp() 741 if date is None: 742 return False 743 744 self.SetText ( 745 value = date.format_accurately(accuracy = self.display_accuracy), 746 data = date, 747 suppress_smarts = True 748 ) 749 750 return True769 mp = cMatchProvider_FuzzyTimestamp() 770 mp.word_separators = None 771 mp.setThresholds(aWord = 998, aSubstring = 999) 772 val = None 773 while val != 'exit': 774 print("************************************") 775 val = input('Enter date fragment ("exit" to quit): ') 776 found, matches = mp.getMatches(aFragment=val) 777 for match in matches: 778 #print match 779 print(match['label']) 780 print(match['data']) 781 print("---------------")782 #--------------------------------------------------------784 app = wx.PyWidgetTester(size = (300, 40)) 785 app.SetWidget(cFuzzyTimestampInput, id=-1, size=(180,20), pos=(10,20)) 786 app.MainLoop()787 #--------------------------------------------------------789 app = wx.PyWidgetTester(size = (300, 40)) 790 app.SetWidget(cDateInputPhraseWheel, id=-1, size=(180,20), pos=(10,20)) 791 app.MainLoop()792 #-------------------------------------------------------- 793 #test_cli() 794 #test_fuzzy_picker() 795 test_picker() 796 797 #================================================== 798
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Thu Jul 23 01:55:31 2020 | http://epydoc.sourceforge.net |