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