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."""214 215 #============================================================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 #-----------------------------------------------------------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()217 """Turns strings into candidate dates. 218 219 Matching on "all" (*, '') will pop up a calendar :-) 220 """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 #============================================================222 223 gmMatchProvider.cMatchProvider.__init__(self) 224 225 self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999) 226 self.word_separators = None227 # 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 #--------------------------------------------------------314508 509 #============================================================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 True490 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)561 562 #==================================================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 = None519 # 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 #--------------------------------------------------------564655 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 #----------------------------------------------------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 = None574 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 None588 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 True673 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 |