Package Gnumed :: Package wxpython :: Module gmDateTimeInput
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmDateTimeInput

  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  #============================================================ 
74 -class cIntervalPhraseWheel(gmPhraseWheel.cPhraseWheel):
75
76 - def __init__(self, *args, **kwargs):
77 78 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 79 self.phrase_separators = None 80 self.display_accuracy = None
81 #-------------------------------------------------------- 82 # phrasewheel internal API 83 #--------------------------------------------------------
84 - def _update_candidates_in_picklist(self, val):
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 #--------------------------------------------------------
104 - def _picklist_item2display_string(self, item=None):
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 #--------------------------------------------------------
113 - def _get_data_tooltip(self):
114 intv = self.GetData() 115 print intv 116 if intv is None: 117 return u'' 118 return gmDateTime.format_interval ( 119 interval = intv, 120 accuracy_wanted = self.display_accuracy 121 )
122 #-------------------------------------------------------- 123 # external API 124 #--------------------------------------------------------
125 - def SetValue(self, value):
126 127 if isinstance(value, pyDT.timedelta): 128 self.SetText(data = value, suppress_smarts = True) 129 return 130 131 if value is None: 132 value = u'' 133 134 super(cIntervalPhraseWheel, self).SetValue(value)
135 #--------------------------------------------------------
136 - def SetText(self, value=u'', data=None, suppress_smarts=False):
137 138 if data is not None: 139 if value.strip() == u'': 140 value = gmDateTime.format_interval ( 141 interval = data, 142 accuracy_wanted = self.display_accuracy 143 ) 144 145 super(cIntervalPhraseWheel, self).SetText(value = value, data = data, suppress_smarts = suppress_smarts)
146 #--------------------------------------------------------
147 - def SetData(self, data=None):
148 if data is None: 149 super(cIntervalPhraseWheel, self).SetText(u'', None) 150 return 151 152 value = gmDateTime.format_interval ( 153 interval = data, 154 accuracy_wanted = self.display_accuracy 155 ) 156 super(cIntervalPhraseWheel, self).SetText(value = value, data = data)
157 #--------------------------------------------------------
158 - def GetData(self):
159 if len(self._data) == 0: 160 self._set_data_to_first_match() 161 162 return super(cIntervalPhraseWheel, self).GetData()
163 #============================================================
164 -class cCalendarDatePickerDlg(wx.Dialog):
165 """Shows a calendar control from which the user can pick a date."""
166 - def __init__(self, parent):
167 168 wx.Dialog.__init__(self, parent, title = _('Pick a date ...')) 169 panel = wx.Panel(self, -1) 170 171 sizer = wx.BoxSizer(wx.VERTICAL) 172 panel.SetSizer(sizer) 173 174 cal = wx.calendar.CalendarCtrl(panel) 175 176 if sys.platform != 'win32': 177 # gtk truncates the year - this fixes it 178 w, h = cal.Size 179 cal.Size = (w+25, h) 180 cal.MinSize = cal.Size 181 182 sizer.Add(cal, 0) 183 184 button_sizer = wx.BoxSizer(wx.HORIZONTAL) 185 button_sizer.Add((0, 0), 1) 186 btn_ok = wx.Button(panel, wx.ID_OK) 187 btn_ok.SetDefault() 188 button_sizer.Add(btn_ok, 0, wx.ALL, 2) 189 button_sizer.Add((0, 0), 1) 190 btn_can = wx.Button(panel, wx.ID_CANCEL) 191 button_sizer.Add(btn_can, 0, wx.ALL, 2) 192 button_sizer.Add((0, 0), 1) 193 sizer.Add(button_sizer, 1, wx.EXPAND | wx.ALL, 10) 194 sizer.Fit(panel) 195 self.ClientSize = panel.Size 196 197 cal.Bind(wx.EVT_KEY_DOWN, self.__on_key_down) 198 cal.SetFocus() 199 self.cal = cal
200 #-----------------------------------------------------------
201 - def __on_key_down(self, evt):
202 code = evt.KeyCode 203 if code == wx.WXK_TAB: 204 self.cal.Navigate() 205 elif code in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER): 206 self.EndModal(wx.ID_OK) 207 elif code == wx.WXK_ESCAPE: 208 self.EndModal(wx.ID_CANCEL) 209 else: 210 evt.Skip()
211 212 #============================================================
213 -class cDateMatchProvider(gmMatchProvider.cMatchProvider):
214 """Turns strings into candidate dates. 215 216 Matching on "all" (*, '') will pop up a calendar :-) 217 """
218 - def __init__(self):
219 220 gmMatchProvider.cMatchProvider.__init__(self) 221 222 self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999) 223 self.word_separators = None
224 # self.ignored_chars("""[?!."'\\(){}\[\]<>~#*$%^_]+""") 225 #-------------------------------------------------------- 226 # external API 227 #-------------------------------------------------------- 228 #-------------------------------------------------------- 229 # base class API 230 #-------------------------------------------------------- 231 # internal matching algorithms 232 # 233 # if we end up here: 234 # - aFragment will not be "None" 235 # - aFragment will be lower case 236 # - we _do_ deliver matches (whether we find any is a different story) 237 #--------------------------------------------------------
238 - def getMatchesByPhrase(self, aFragment):
239 """Return matches for aFragment at start of phrases.""" 240 matches = gmDateTime.str2pydt_matches(str2parse = aFragment.strip()) 241 242 if len(matches) == 0: 243 return (False, []) 244 245 items = [] 246 for match in matches: 247 if match['data'] is None: 248 list_label = match['label'] 249 data = None 250 else: 251 data = match['data'].replace ( 252 hour = 11, 253 minute = 11, 254 second = 11, 255 microsecond = 111111 256 ) 257 list_label = gmDateTime.pydt_strftime ( 258 data, 259 format = '%A, %d. %B %Y (%x)', 260 accuracy = gmDateTime.acc_days 261 ) 262 items.append ({ 263 'data': data, 264 'field_label': match['label'], 265 'list_label': list_label 266 }) 267 268 return (True, items)
269 #--------------------------------------------------------
270 - def getMatchesByWord(self, aFragment):
271 """Return matches for aFragment at start of words inside phrases.""" 272 return self.getMatchesByPhrase(aFragment)
273 #--------------------------------------------------------
274 - def getMatchesBySubstr(self, aFragment):
275 """Return matches for aFragment as a true substring.""" 276 return self.getMatchesByPhrase(aFragment)
277 #--------------------------------------------------------
278 - def getAllMatches(self):
279 """Return all items.""" 280 281 matches = (False, []) 282 return matches
283 284 # # consider this: 285 # dlg = cCalendarDatePickerDlg(None) 286 # # FIXME: show below parent 287 # dlg.CentreOnScreen() 288 # 289 # if dlg.ShowModal() == wx.ID_OK: 290 # date = dlg.cal.Date 291 # if date is not None: 292 # if date.IsValid(): 293 # date = gmDateTime.wxDate2py_dt(wxDate = date).replace ( 294 # hour = 11, 295 # minute = 11, 296 # second = 11, 297 # microsecond = 111111 298 # ) 299 # lbl = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 300 # matches = (True, [{'data': date, 'label': lbl}]) 301 # dlg.Destroy() 302 # 303 # return matches 304 #============================================================
305 -class cDateInputPhraseWheel(gmPhraseWheel.cPhraseWheel):
306
307 - def __init__(self, *args, **kwargs):
308 309 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 310 311 self.matcher = cDateMatchProvider() 312 self.phrase_separators = None 313 314 self.static_tooltip_extra = _('<ALT-C/K>: pick from (c/k)alendar')
315 #-------------------------------------------------------- 316 # internal helpers 317 #-------------------------------------------------------- 318 # def __text2timestamp(self): 319 # 320 # self._update_candidates_in_picklist(val = self.GetValue().strip()) 321 # 322 # if len(self._current_match_candidates) == 1: 323 # return self._current_match_candidates[0]['data'] 324 # 325 # return None 326 #--------------------------------------------------------
327 - def __pick_from_calendar(self):
328 dlg = cCalendarDatePickerDlg(self) 329 # FIXME: show below parent 330 dlg.CentreOnScreen() 331 decision = dlg.ShowModal() 332 date = dlg.cal.Date 333 dlg.Destroy() 334 335 if decision != wx.ID_OK: 336 return 337 338 if date is None: 339 return 340 341 if not date.IsValid(): 342 return 343 344 date = gmDateTime.wxDate2py_dt(wxDate = date).replace ( 345 hour = 11, 346 minute = 11, 347 second = 11, 348 microsecond = 111111 349 ) 350 val = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 351 self.SetText(value = val, data = date, suppress_smarts = True)
352 #-------------------------------------------------------- 353 # phrasewheel internal API 354 #--------------------------------------------------------
355 - def _on_lose_focus(self, event):
356 # are we valid ? 357 if len(self._data) == 0: 358 self._set_data_to_first_match() 359 360 # let the base class do its thing 361 super(cDateInputPhraseWheel, self)._on_lose_focus(event)
362 #--------------------------------------------------------
363 - def _picklist_item2display_string(self, item=None):
364 data = item['data'] 365 if data is not None: 366 return gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 367 return item['field_label']
368 #--------------------------------------------------------
369 - def _on_key_down(self, event):
370 371 # <ALT-C> / <ALT-K> -> calendar 372 if event.AltDown() is True: 373 char = unichr(event.GetUnicodeKey()) 374 if char in u'ckCK': 375 self.__pick_from_calendar() 376 return 377 378 super(cDateInputPhraseWheel, self)._on_key_down(event)
379 #--------------------------------------------------------
380 - def _get_data_tooltip(self):
381 if len(self._data) == 0: 382 return u'' 383 384 date = self.GetData() 385 # if match provider only provided completions 386 # but not a full date with it 387 if date is None: 388 return u'' 389 390 return gmDateTime.pydt_strftime ( 391 date, 392 format = '%A, %d. %B %Y (%x)', 393 accuracy = gmDateTime.acc_days 394 )
395 #-------------------------------------------------------- 396 # external API 397 #--------------------------------------------------------
398 - def SetValue(self, value):
399 400 if isinstance(value, pyDT.datetime): 401 date = value.replace ( 402 hour = 11, 403 minute = 11, 404 second = 11, 405 microsecond = 111111 406 ) 407 self.SetText(data = date, suppress_smarts = True) 408 return 409 410 if value is None: 411 value = u'' 412 413 super(self.__class__, self).SetValue(value)
414 #--------------------------------------------------------
415 - def SetText(self, value=u'', data=None, suppress_smarts=False):
416 417 if data is not None: 418 if isinstance(data, gmDateTime.cFuzzyTimestamp): 419 data = data.timestamp.replace ( 420 hour = 11, 421 minute = 11, 422 second = 11, 423 microsecond = 111111 424 ) 425 if value.strip() == u'': 426 value = gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 427 428 super(self.__class__, self).SetText(value = value, data = data, suppress_smarts = suppress_smarts)
429 #--------------------------------------------------------
430 - def SetData(self, data=None):
431 if data is None: 432 gmPhraseWheel.cPhraseWheel.SetText(self, u'', None) 433 else: 434 if isinstance(data, gmDateTime.cFuzzyTimestamp): 435 data = data.timestamp.replace ( 436 hour = 11, 437 minute = 11, 438 second = 11, 439 microsecond = 111111 440 ) 441 val = gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 442 super(self.__class__, self).SetText(value = val, data = data)
443 #--------------------------------------------------------
444 - def GetData(self):
445 if len(self._data) == 0: 446 self._set_data_to_first_match() 447 448 return super(self.__class__, self).GetData()
449 #--------------------------------------------------------
450 - def is_valid_timestamp(self, allow_empty=True):
451 if len(self._data) > 0: 452 self.display_as_valid(True) 453 return True 454 455 if self.GetValue().strip() == u'': 456 if allow_empty: 457 self.display_as_valid(True) 458 return True 459 else: 460 self.display_as_valid(False) 461 return False 462 463 # skip showing calendar on '*' from here 464 if self.GetValue().strip() == u'*': 465 self.display_as_valid(False) 466 return False 467 468 self._set_data_to_first_match() 469 if len(self._data) == 0: 470 self.display_as_valid(False) 471 return False 472 473 self.display_as_valid(True) 474 return True
475 #-------------------------------------------------------- 476 # properties 477 #--------------------------------------------------------
478 - def _get_date(self):
479 return self.GetData()
480
481 - def _set_date(self, date):
482 raise AttributeError('._set_date not implemented')
483 # val = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 484 # self.data = date.replace ( 485 # hour = 11, 486 # minute = 11, 487 # second = 11, 488 # microsecond = 111111 489 # ) 490 491 date = property(_get_date, _set_date)
492 #============================================================
493 -class cMatchProvider_FuzzyTimestamp(gmMatchProvider.cMatchProvider):
494 - def __init__(self):
495 self.__allow_past = 1 496 self.__shifting_base = None 497 498 gmMatchProvider.cMatchProvider.__init__(self) 499 500 self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999) 501 self.word_separators = None
502 # self.ignored_chars("""[?!."'\\(){}\[\]<>~#*$%^_]+""") 503 #-------------------------------------------------------- 504 # external API 505 #-------------------------------------------------------- 506 #-------------------------------------------------------- 507 # base class API 508 #-------------------------------------------------------- 509 # internal matching algorithms 510 # 511 # if we end up here: 512 # - aFragment will not be "None" 513 # - aFragment will be lower case 514 # - we _do_ deliver matches (whether we find any is a different story) 515 #--------------------------------------------------------
516 - def getMatchesByPhrase(self, aFragment):
517 """Return matches for aFragment at start of phrases.""" 518 # self.__now = mxDT.now() 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 # if match['data'] is None: 527 # list_label = match['label'] 528 # else: 529 # list_label = gmDateTime.pydt_strftime ( 530 # match['data'].timestamp.format_accurately(), 531 # format = '%A, %d. %B %Y (%x)', 532 # accuracy = gmDateTime.acc_days 533 # ) 534 items.append ({ 535 'data': match['data'], 536 'field_label': match['label'], 537 'list_label': match['label'] 538 }) 539 540 return (True, items)
541 #--------------------------------------------------------
542 - def getMatchesByWord(self, aFragment):
543 """Return matches for aFragment at start of words inside phrases.""" 544 return self.getMatchesByPhrase(aFragment)
545 #--------------------------------------------------------
546 - def getMatchesBySubstr(self, aFragment):
547 """Return matches for aFragment as a true substring.""" 548 return self.getMatchesByPhrase(aFragment)
549 #--------------------------------------------------------
550 - def getAllMatches(self):
551 """Return all items.""" 552 return (False, [])
553 #==================================================
554 -class cFuzzyTimestampInput(gmPhraseWheel.cPhraseWheel):
555
556 - def __init__(self, *args, **kwargs):
557 558 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 559 560 self.matcher = cMatchProvider_FuzzyTimestamp() 561 self.phrase_separators = None 562 self.selection_only = True 563 self.selection_only_error_msg = _('Cannot interpret input as timestamp.')
564 #-------------------------------------------------------- 565 # internal helpers 566 #--------------------------------------------------------
567 - def __text2timestamp(self, val=None):
568 569 if val is None: 570 val = self.GetValue().strip() 571 572 success, matches = self.matcher.getMatchesByPhrase(val) 573 if len(matches) == 1: 574 return matches[0]['data'] 575 576 return None
577 #-------------------------------------------------------- 578 # phrasewheel internal API 579 #--------------------------------------------------------
580 - def _on_lose_focus(self, event):
581 # are we valid ? 582 if self.data is None: 583 # no, so try 584 self.data = self.__text2timestamp() 585 586 # let the base class do its thing 587 gmPhraseWheel.cPhraseWheel._on_lose_focus(self, event)
588 #--------------------------------------------------------
589 - def _picklist_item2display_string(self, item=None):
590 data = item['data'] 591 if data is not None: 592 return data.format_accurately() 593 return item['field_label']
594 #-------------------------------------------------------- 595 # external API 596 #--------------------------------------------------------
597 - def SetText(self, value=u'', data=None, suppress_smarts=False):
598 599 if data is not None: 600 if isinstance(data, pyDT.datetime): 601 data = gmDateTime.cFuzzyTimestamp(timestamp=data) 602 if value.strip() == u'': 603 value = data.format_accurately() 604 605 gmPhraseWheel.cPhraseWheel.SetText(self, value = value, data = data, suppress_smarts = suppress_smarts)
606 #--------------------------------------------------------
607 - def SetData(self, data=None):
608 if data is None: 609 gmPhraseWheel.cPhraseWheel.SetText(self, u'', None) 610 else: 611 if isinstance(data, pyDT.datetime): 612 data = gmDateTime.cFuzzyTimestamp(timestamp=data) 613 gmPhraseWheel.cPhraseWheel.SetText(self, value = data.format_accurately(), data = data)
614 #--------------------------------------------------------
615 - def is_valid_timestamp(self):
616 if self.data is not None: 617 return True 618 619 # skip empty value 620 if self.GetValue().strip() == u'': 621 return True 622 623 self.data = self.__text2timestamp() 624 if self.data is None: 625 return False 626 627 return True
628 #================================================== 629 # main 630 #-------------------------------------------------- 631 if __name__ == '__main__': 632 633 if len(sys.argv) < 2: 634 sys.exit() 635 636 if sys.argv[1] != 'test': 637 sys.exit() 638 639 gmI18N.activate_locale() 640 gmI18N.install_domain(domain='gnumed') 641 gmDateTime.init() 642 643 #----------------------------------------------------
644 - def test_cli():
645 mp = cMatchProvider_FuzzyTimestamp() 646 mp.word_separators = None 647 mp.setThresholds(aWord = 998, aSubstring = 999) 648 val = None 649 while val != 'exit': 650 print "************************************" 651 val = raw_input('Enter date fragment ("exit" to quit): ') 652 found, matches = mp.getMatches(aFragment=val) 653 for match in matches: 654 #print match 655 print match['label'] 656 print match['data'] 657 print "---------------"
658 #--------------------------------------------------------
659 - def test_fuzzy_picker():
660 app = wx.PyWidgetTester(size = (300, 40)) 661 app.SetWidget(cFuzzyTimestampInput, id=-1, size=(180,20), pos=(10,20)) 662 app.MainLoop()
663 #--------------------------------------------------------
664 - def test_picker():
665 app = wx.PyWidgetTester(size = (300, 40)) 666 app.SetWidget(cDateInputPhraseWheel, id=-1, size=(180,20), pos=(10,20)) 667 app.MainLoop()
668 #-------------------------------------------------------- 669 #test_cli() 670 test_fuzzy_picker() 671 #test_picker() 672 673 #================================================== 674