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 (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 cCalendarDatePickerDlg(wx.Dialog):
36 """Shows a calendar control from which the user can pick a date."""
37 - def __init__(self, parent):
38 39 wx.Dialog.__init__(self, parent, title = _('Pick a date ...')) 40 panel = wx.Panel(self, -1) 41 42 sizer = wx.BoxSizer(wx.VERTICAL) 43 panel.SetSizer(sizer) 44 45 cal = wx.calendar.CalendarCtrl(panel) 46 47 if sys.platform != 'win32': 48 # gtk truncates the year - this fixes it 49 w, h = cal.Size 50 cal.Size = (w+25, h) 51 cal.MinSize = cal.Size 52 53 sizer.Add(cal, 0) 54 55 button_sizer = wx.BoxSizer(wx.HORIZONTAL) 56 button_sizer.Add((0, 0), 1) 57 btn_ok = wx.Button(panel, wx.ID_OK) 58 btn_ok.SetDefault() 59 button_sizer.Add(btn_ok, 0, wx.ALL, 2) 60 button_sizer.Add((0, 0), 1) 61 btn_can = wx.Button(panel, wx.ID_CANCEL) 62 button_sizer.Add(btn_can, 0, wx.ALL, 2) 63 button_sizer.Add((0, 0), 1) 64 sizer.Add(button_sizer, 1, wx.EXPAND | wx.ALL, 10) 65 sizer.Fit(panel) 66 self.ClientSize = panel.Size 67 68 cal.Bind(wx.EVT_KEY_DOWN, self.__on_key_down) 69 cal.SetFocus() 70 self.cal = cal
71 #-----------------------------------------------------------
72 - def __on_key_down(self, evt):
73 code = evt.KeyCode 74 if code == wx.WXK_TAB: 75 self.cal.Navigate() 76 elif code in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER): 77 self.EndModal(wx.ID_OK) 78 elif code == wx.WXK_ESCAPE: 79 self.EndModal(wx.ID_CANCEL) 80 else: 81 evt.Skip()
82 83 #============================================================
84 -class cDateMatchProvider(gmMatchProvider.cMatchProvider):
85 """Turns strings into candidate dates. 86 87 Matching on "all" (*, '') will pop up a calendar :-) 88 """
89 - def __init__(self):
90 91 gmMatchProvider.cMatchProvider.__init__(self) 92 93 self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999) 94 self.word_separators = None
95 # self.ignored_chars("""[?!."'\\(){}\[\]<>~#*$%^_]+""") 96 #-------------------------------------------------------- 97 # external API 98 #-------------------------------------------------------- 99 #-------------------------------------------------------- 100 # base class API 101 #-------------------------------------------------------- 102 # internal matching algorithms 103 # 104 # if we end up here: 105 # - aFragment will not be "None" 106 # - aFragment will be lower case 107 # - we _do_ deliver matches (whether we find any is a different story) 108 #--------------------------------------------------------
109 - def getMatchesByPhrase(self, aFragment):
110 """Return matches for aFragment at start of phrases.""" 111 matches = gmDateTime.str2pydt_matches(str2parse = aFragment.strip()) 112 if len(matches) > 0: 113 return (True, matches) 114 else: 115 return (False, [])
116 #--------------------------------------------------------
117 - def getMatchesByWord(self, aFragment):
118 """Return matches for aFragment at start of words inside phrases.""" 119 return self.getMatchesByPhrase(aFragment)
120 #--------------------------------------------------------
121 - def getMatchesBySubstr(self, aFragment):
122 """Return matches for aFragment as a true substring.""" 123 return self.getMatchesByPhrase(aFragment)
124 #--------------------------------------------------------
125 - def getAllMatches(self):
126 """Return all items.""" 127 128 matches = (False, []) 129 return matches 130 131 dlg = cCalendarDatePickerDlg(None) 132 # FIXME: show below parent 133 dlg.CentreOnScreen() 134 135 if dlg.ShowModal() == wx.ID_OK: 136 date = dlg.cal.Date 137 if date is not None: 138 if date.IsValid(): 139 date = gmDateTime.wxDate2py_dt(wxDate = date) 140 lbl = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 141 matches = (True, [{'data': date, 'label': lbl}]) 142 dlg.Destroy() 143 144 return matches
145 #============================================================
146 -class cDateInputPhraseWheel(gmPhraseWheel.cPhraseWheel):
147
148 - def __init__(self, *args, **kwargs):
149 150 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 151 152 self.matcher = cDateMatchProvider() 153 self.phrase_separators = None 154 155 self.static_tooltip_extra = _('<ALT-C>: pick from calendar')
156 #-------------------------------------------------------- 157 # internal helpers 158 #--------------------------------------------------------
159 - def __text2timestamp(self, val=None):
160 161 if val is None: 162 val = self.GetValue().strip() 163 164 success, matches = self.matcher.getMatchesByPhrase(val) 165 166 if len(matches) == 1: 167 return matches[0]['data'] 168 169 return None
170 #--------------------------------------------------------
171 - def __pick_from_calendar(self):
172 dlg = cCalendarDatePickerDlg(self) 173 # FIXME: show below parent 174 dlg.CentreOnScreen() 175 decision = dlg.ShowModal() 176 date = dlg.cal.Date 177 dlg.Destroy() 178 179 if decision != wx.ID_OK: 180 return 181 182 if date is None: 183 return 184 185 if not date.IsValid(): 186 return 187 188 date = gmDateTime.wxDate2py_dt(wxDate = date) 189 val = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 190 self.SetText(value = val, data = date, suppress_smarts = True)
191 #-------------------------------------------------------- 192 # phrasewheel internal API 193 #--------------------------------------------------------
194 - def _on_lose_focus(self, event):
195 # are we valid ? 196 if self.data is None: 197 # no, so try 198 self.data = self.__text2timestamp() 199 200 # let the base class do its thing 201 super(self.__class__, self)._on_lose_focus(event)
202 #--------------------------------------------------------
204 data = self._picklist.GetSelectedItemData() 205 if data is not None: 206 return gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 207 return self._picklist.get_selected_item_label()
208 #--------------------------------------------------------
209 - def _on_key_down(self, event):
210 211 # <ALT-C> / <ALT-K> -> calendar 212 if event.AltDown() is True: 213 char = unichr(event.GetUnicodeKey()) 214 if char in u'ckCK': 215 self.__pick_from_calendar() 216 return 217 218 super(self.__class__, self)._on_key_down(event)
219 #--------------------------------------------------------
220 - def _get_data_tooltip(self):
221 if self.data is None: 222 return u'' 223 224 return gmDateTime.pydt_strftime ( 225 self.data, 226 format = '%A, %d. %B %Y (%x)', 227 accuracy = gmDateTime.acc_days 228 )
229 #-------------------------------------------------------- 230 # external API 231 #--------------------------------------------------------
232 - def SetValue(self, value):
233 234 if isinstance(value, pyDT.datetime): 235 self.SetText(data = value, suppress_smarts = True) 236 return 237 238 if value is None: 239 value = u'' 240 241 super(self.__class__, self).SetValue(value)
242 #--------------------------------------------------------
243 - def SetText(self, value=u'', data=None, suppress_smarts=False):
244 245 if data is not None: 246 if isinstance(data, gmDateTime.cFuzzyTimestamp): 247 data = data.timestamp 248 if value.strip() == u'': 249 value = gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 250 251 super(self.__class__, self).SetText(value = value, data = data, suppress_smarts = suppress_smarts)
252 #--------------------------------------------------------
253 - def SetData(self, data=None):
254 if data is None: 255 gmPhraseWheel.cPhraseWheel.SetText(self, u'', None) 256 else: 257 if isinstance(data, gmDateTime.cFuzzyTimestamp): 258 data = data.timestamp 259 val = gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 260 super(self.__class__, self).SetText(value = val, data = data)
261 #--------------------------------------------------------
262 - def GetData(self):
263 if self.data is None: 264 self.data = self.__text2timestamp() 265 266 return super(self.__class__, self).GetData()
267 #--------------------------------------------------------
268 - def is_valid_timestamp(self, allow_empty=True):
269 if self.data is not None: 270 self.display_as_valid(True) 271 return True 272 273 if self.GetValue().strip() == u'': 274 if allow_empty: 275 self.display_as_valid(True) 276 return True 277 else: 278 self.display_as_valid(False) 279 return False 280 281 # skip showing calendar on '*' from here 282 if self.GetValue().strip() == u'*': 283 self.display_as_valid(False) 284 return False 285 286 self.data = self.__text2timestamp() 287 if self.data is None: 288 self.display_as_valid(False) 289 return False 290 291 self.display_as_valid(True) 292 return True
293 #-------------------------------------------------------- 294 # properties 295 #--------------------------------------------------------
296 - def _get_date(self):
297 return self.data
298
299 - def _set_date(self, date):
300 self.data = date
301 302 date = property(_get_date, _set_date)
303 #============================================================
304 -class cMatchProvider_FuzzyTimestamp(gmMatchProvider.cMatchProvider):
305 - def __init__(self):
306 self.__allow_past = 1 307 self.__shifting_base = None 308 309 gmMatchProvider.cMatchProvider.__init__(self) 310 311 self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999) 312 self.word_separators = None
313 # self.ignored_chars("""[?!."'\\(){}\[\]<>~#*$%^_]+""") 314 #-------------------------------------------------------- 315 # external API 316 #-------------------------------------------------------- 317 #-------------------------------------------------------- 318 # base class API 319 #-------------------------------------------------------- 320 # internal matching algorithms 321 # 322 # if we end up here: 323 # - aFragment will not be "None" 324 # - aFragment will be lower case 325 # - we _do_ deliver matches (whether we find any is a different story) 326 #--------------------------------------------------------
327 - def getMatchesByPhrase(self, aFragment):
328 """Return matches for aFragment at start of phrases.""" 329 self.__now = mxDT.now() 330 matches = gmDateTime.str2fuzzy_timestamp_matches(aFragment.strip()) 331 if len(matches) > 0: 332 return (True, matches) 333 else: 334 return (False, [])
335 #--------------------------------------------------------
336 - def getMatchesByWord(self, aFragment):
337 """Return matches for aFragment at start of words inside phrases.""" 338 return self.getMatchesByPhrase(aFragment)
339 #--------------------------------------------------------
340 - def getMatchesBySubstr(self, aFragment):
341 """Return matches for aFragment as a true substring.""" 342 return self.getMatchesByPhrase(aFragment)
343 #--------------------------------------------------------
344 - def getAllMatches(self):
345 """Return all items.""" 346 # FIXME: popup calendar to pick from 347 return (False, [])
348 #==================================================
349 -class cFuzzyTimestampInput(gmPhraseWheel.cPhraseWheel):
350
351 - def __init__(self, *args, **kwargs):
352 353 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 354 355 self.matcher = cMatchProvider_FuzzyTimestamp() 356 self.phrase_separators = None 357 self.selection_only = True 358 self.selection_only_error_msg = _('Cannot interpret input as timestamp.')
359 #-------------------------------------------------------- 360 # internal helpers 361 #--------------------------------------------------------
362 - def __text2timestamp(self, val=None):
363 364 if val is None: 365 val = self.GetValue().strip() 366 367 success, matches = self.matcher.getMatchesByPhrase(val) 368 if len(matches) == 1: 369 return matches[0]['data'] 370 371 return None
372 #-------------------------------------------------------- 373 # phrasewheel internal API 374 #--------------------------------------------------------
375 - def _on_lose_focus(self, event):
376 # are we valid ? 377 if self.data is None: 378 # no, so try 379 self.data = self.__text2timestamp() 380 381 # let the base class do its thing 382 gmPhraseWheel.cPhraseWheel._on_lose_focus(self, event)
383 #--------------------------------------------------------
385 data = self._picklist.GetSelectedItemData() 386 if data is not None: 387 return data.format_accurately() 388 return self._picklist.get_selected_item_label()
389 #-------------------------------------------------------- 390 # external API 391 #--------------------------------------------------------
392 - def SetText(self, value=u'', data=None, suppress_smarts=False):
393 394 if data is not None: 395 if isinstance(data, pyDT.datetime): 396 data = gmDateTime.cFuzzyTimestamp(timestamp=data) 397 if value.strip() == u'': 398 value = data.format_accurately() 399 400 gmPhraseWheel.cPhraseWheel.SetText(self, value = value, data = data, suppress_smarts = suppress_smarts)
401 #--------------------------------------------------------
402 - def SetData(self, data=None):
403 if data is None: 404 gmPhraseWheel.cPhraseWheel.SetText(self, u'', None) 405 else: 406 if isinstance(data, pyDT.datetime): 407 data = gmDateTime.cFuzzyTimestamp(timestamp=data) 408 gmPhraseWheel.cPhraseWheel.SetText(self, value = data.format_accurately(), data = data)
409 #--------------------------------------------------------
410 - def is_valid_timestamp(self):
411 if self.data is not None: 412 return True 413 414 # skip empty value 415 if self.GetValue().strip() == u'': 416 return True 417 418 self.data = self.__text2timestamp() 419 if self.data is None: 420 return False 421 422 return True
423 #==================================================
424 -class cTimeInput(wx.TextCtrl):
425 - def __init__(self, parent, *args, **kwargs):
426 if len(args) < 2: 427 if not kwargs.has_key('value'): 428 kwargs['value'] = _('enter time here') 429 wx.TextCtrl.__init__( 430 self, 431 parent, 432 *args, 433 **kwargs 434 )
435 #================================================== 436 #class cDateInputCtrl(wx.DatePickerCtrl): 437 # 438 # #---------------------------------------------- 439 # def SetValue(self, value): 440 # """Set either datetime.datetime or wx.DateTime""" 441 # 442 # if isinstance(value, (pyDT.date, pyDT.datetime)): 443 # value = gmDateTime.py_dt2wxDate(py_dt = value, wx = wx) 444 # 445 # elif value is None: 446 # value = wx.DefaultDateTime 447 # 448 # wx.DatePickerCtrl.SetValue(self, value) 449 # #---------------------------------------------- 450 # def GetValue(self, as_pydt=False, invalid_as_none=False): 451 # """Returns datetime.datetime values""" 452 # 453 # # datepicker can fail to pick up user changes by keyboard until 454 # # it has lost focus, so do that but also set the focus back to us, 455 # # now, this is a side-effect (after .GetValue focus will be 456 # # here) but at least it is predictable ... 457 # self.Navigate() 458 # self.SetFocus() 459 # value = wx.DatePickerCtrl.GetValue(self) 460 # 461 # if value is None: 462 # return None 463 # 464 # # manage null dates (useful when wx.DP_ALLOWNONE is set) 465 # if not value.IsValid(): 466 # if invalid_as_none: 467 # return None 468 # else: 469 # return value 470 # 471 # self.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 472 # self.Refresh() 473 # 474 # if not as_pydt: 475 # return value 476 # 477 # return gmDateTime.wxDate2py_dt(value) 478 # #---------------------------------------------- 479 # # def convenience wrapper 480 # #---------------------------------------------- 481 # def is_valid_timestamp(self, allow_none=True, invalid_as_none=False): 482 # val = self.GetValue(as_pydt = False, invalid_as_none = invalid_as_none) 483 # 484 # if val is None: 485 # if allow_none: 486 # valid = True 487 # else: 488 # valid = False 489 # else: 490 # valid = val.IsValid() 491 # 492 # if valid: 493 # self.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 494 # else: 495 # self.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 496 # 497 # self.Refresh() 498 # return valid 499 # #---------------------------------------------- 500 # def get_pydt(self): 501 # return self.GetValue(as_pydt = True) 502 # #---------------------------------------------- 503 # def display_as_valid(self, valid=True): 504 # if valid is True: 505 # self.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 506 # else: 507 # self.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 508 # self.Refresh() 509 #================================================== 510 # main 511 #-------------------------------------------------- 512 if __name__ == '__main__': 513 514 if len(sys.argv) < 2: 515 sys.exit() 516 517 if sys.argv[2] != 'test': 518 sys.exit() 519 520 gmI18N.activate_locale() 521 gmI18N.install_domain(domain='gnumed') 522 gmDateTime.init() 523 524 #----------------------------------------------------
525 - def test_cli():
526 mp = cMatchProvider_FuzzyTimestamp() 527 mp.word_separators = None 528 mp.setThresholds(aWord = 998, aSubstring = 999) 529 val = None 530 while val != 'exit': 531 print "************************************" 532 val = raw_input('Enter date fragment: ') 533 found, matches = mp.getMatches(aFragment=val) 534 for match in matches: 535 print match['label'] 536 print match['data'] 537 print "---------------"
538 #--------------------------------------------------------
539 - def test_gui():
540 app = wx.PyWidgetTester(size = (200, 300)) 541 app.SetWidget(cFuzzyTimestampInput, id=-1, size=(180,20), pos=(10,20)) 542 app.MainLoop()
543 # #-------------------------------------------------------- 544 # def test_picker(): 545 # app = wx.PyWidgetTester(size = (200, 300)) 546 # app.SetWidget(cDateInputCtrl, id=-1, size=(180,20), pos=(10,20)) 547 # app.MainLoop() 548 #-------------------------------------------------------- 549 #test_cli() 550 #test_gui() 551 test_picker() 552 553 #================================================== 554