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. Initially this is just a plain text box 
  5  but using this throughout GNUmed will allow us to 
  6  transparently add features. 
  7   
  8  @copyright: author(s) 
  9  """ 
 10  #============================================================================== 
 11  __version__ = "$Revision: 1.66 $" 
 12  __author__  = "K. Hilbert <Karsten.Hilbert@gmx.net>" 
 13  __licence__ = "GPL (details at http://www.gnu.org)" 
 14   
 15  # standard libary 
 16  import re, string, sys, time, datetime as pyDT, logging 
 17   
 18   
 19  # 3rd party 
 20  import mx.DateTime as mxDT 
 21  import wx 
 22  import wx.calendar 
 23   
 24   
 25  # GNUmed specific 
 26  if __name__ == '__main__': 
 27          sys.path.insert(0, '../../') 
 28  from Gnumed.pycommon import gmMatchProvider, gmDateTime 
 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 cDateInputCtrl2(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 if event.AltDown() is False: 205 keycode = event.GetKeyCode() 206 if keycode == wx.WXK_F4: 207 self.__pick_from_calendar() 208 return 209 210 if self.GetValue().strip() != u'': 211 super(self.__class__, self)._on_key_down(event)
212 #-------------------------------------------------------- 213 # external API 214 #--------------------------------------------------------
215 - def SetValue(self, value):
216 217 if isinstance(value, pyDT.datetime): 218 self.SetText(data = value, suppress_smarts = True) 219 return 220 221 if value is None: 222 value = u'' 223 224 super(self.__class__, self).SetValue(value)
225 #--------------------------------------------------------
226 - def SetText(self, value=u'', data=None, suppress_smarts=False):
227 228 if data is not None: 229 if isinstance(data, gmDateTime.cFuzzyTimestamp): 230 data = data.timestamp 231 if value.strip() == u'': 232 value = data.strftime('%Y-%m-%d') 233 234 super(self.__class__, self).SetText(value = value, data = data, suppress_smarts = suppress_smarts)
235 #--------------------------------------------------------
236 - def SetData(self, data=None):
237 if data is None: 238 gmPhraseWheel.cPhraseWheel.SetText(self, u'', None) 239 else: 240 if isinstance(data, gmDateTime.cFuzzyTimestamp): 241 data = data.timestamp 242 super(self.__class__, self).SetText(value = data.strftime('%Y-%m-%d'), data = data)
243 #--------------------------------------------------------
244 - def is_valid_timestamp(self):
245 if self.data is not None: 246 return True 247 248 # skip empty value 249 if self.GetValue().strip() == u'': 250 return True 251 252 self.data = self.__text2timestamp() 253 if self.data is None: 254 return False 255 256 return True
257 258 #============================================================
259 -class cMatchProvider_FuzzyTimestamp(gmMatchProvider.cMatchProvider):
260 - def __init__(self):
261 self.__allow_past = 1 262 self.__shifting_base = None 263 264 gmMatchProvider.cMatchProvider.__init__(self) 265 266 self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999) 267 self.word_separators = None
268 # self.ignored_chars("""[?!."'\\(){}\[\]<>~#*$%^_]+""") 269 #-------------------------------------------------------- 270 # external API 271 #-------------------------------------------------------- 272 #-------------------------------------------------------- 273 # base class API 274 #-------------------------------------------------------- 275 # internal matching algorithms 276 # 277 # if we end up here: 278 # - aFragment will not be "None" 279 # - aFragment will be lower case 280 # - we _do_ deliver matches (whether we find any is a different story) 281 #--------------------------------------------------------
282 - def getMatchesByPhrase(self, aFragment):
283 """Return matches for aFragment at start of phrases.""" 284 self.__now = mxDT.now() 285 matches = gmDateTime.str2fuzzy_timestamp_matches(aFragment.strip()) 286 if len(matches) > 0: 287 return (True, matches) 288 else: 289 return (False, [])
290 #--------------------------------------------------------
291 - def getMatchesByWord(self, aFragment):
292 """Return matches for aFragment at start of words inside phrases.""" 293 return self.getMatchesByPhrase(aFragment)
294 #--------------------------------------------------------
295 - def getMatchesBySubstr(self, aFragment):
296 """Return matches for aFragment as a true substring.""" 297 return self.getMatchesByPhrase(aFragment)
298 #--------------------------------------------------------
299 - def getAllMatches(self):
300 """Return all items.""" 301 # FIXME: popup calendar to pick from 302 return (False, [])
303 #==================================================
304 -class cFuzzyTimestampInput(gmPhraseWheel.cPhraseWheel):
305
306 - def __init__(self, *args, **kwargs):
307 308 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 309 310 self.matcher = cMatchProvider_FuzzyTimestamp() 311 self.phrase_separators = None 312 self.selection_only = True 313 self.selection_only_error_msg = _('Cannot interpret input as timestamp.')
314 #-------------------------------------------------------- 315 # internal helpers 316 #--------------------------------------------------------
317 - def __text2timestamp(self, val=None):
318 319 if val is None: 320 val = self.GetValue().strip() 321 322 success, matches = self.matcher.getMatchesByPhrase(val) 323 if len(matches) == 1: 324 return matches[0]['data'] 325 326 return None
327 #-------------------------------------------------------- 328 # phrasewheel internal API 329 #--------------------------------------------------------
330 - def _on_lose_focus(self, event):
331 # are we valid ? 332 if self.data is None: 333 # no, so try 334 self.data = self.__text2timestamp() 335 336 # let the base class do its thing 337 gmPhraseWheel.cPhraseWheel._on_lose_focus(self, event)
338 #--------------------------------------------------------
340 data = self._picklist.GetSelectedItemData() 341 if data is not None: 342 return data.format_accurately() 343 return self._picklist.get_selected_item_label()
344 #-------------------------------------------------------- 345 # external API 346 #--------------------------------------------------------
347 - def SetText(self, value=u'', data=None, suppress_smarts=False):
348 349 if data is not None: 350 if isinstance(data, pyDT.datetime): 351 data = gmDateTime.cFuzzyTimestamp(timestamp=data) 352 if value.strip() == u'': 353 value = data.format_accurately() 354 355 gmPhraseWheel.cPhraseWheel.SetText(self, value = value, data = data, suppress_smarts = suppress_smarts)
356 #--------------------------------------------------------
357 - def SetData(self, data=None):
358 if data is None: 359 gmPhraseWheel.cPhraseWheel.SetText(self, u'', None) 360 else: 361 if isinstance(data, pyDT.datetime): 362 data = gmDateTime.cFuzzyTimestamp(timestamp=data) 363 gmPhraseWheel.cPhraseWheel.SetText(self, value = data.format_accurately(), data = data)
364 #--------------------------------------------------------
365 - def is_valid_timestamp(self):
366 if self.data is not None: 367 return True 368 369 # skip empty value 370 if self.GetValue().strip() == u'': 371 return True 372 373 self.data = self.__text2timestamp() 374 if self.data is None: 375 return False 376 377 return True
378 #==================================================
379 -class cTimeInput(wx.TextCtrl):
380 - def __init__(self, parent, *args, **kwargs):
381 if len(args) < 2: 382 if not kwargs.has_key('value'): 383 kwargs['value'] = _('enter time here') 384 wx.TextCtrl.__init__( 385 self, 386 parent, 387 *args, 388 **kwargs 389 )
390 #==================================================
391 -class cDateInputCtrl(wx.DatePickerCtrl):
392 393 #----------------------------------------------
394 - def SetValue(self, value):
395 """Set either datetime.datetime or wx.DateTime""" 396 397 if isinstance(value, (pyDT.date, pyDT.datetime)): 398 value = gmDateTime.py_dt2wxDate(py_dt = value, wx = wx) 399 400 elif value is None: 401 value = wx.DefaultDateTime 402 403 wx.DatePickerCtrl.SetValue(self, value)
404 #----------------------------------------------
405 - def GetValue(self, as_pydt=False, invalid_as_none=False):
406 """Returns datetime.datetime values""" 407 408 # datepicker can fail to pick up user changes by keyboard until 409 # it has lost focus, so do that but also set the focus back to 410 # us, now this is a side-effect (after GetValue() focus will be 411 # here) but at least it is predictable ... 412 self.Navigate() 413 self.SetFocus() 414 value = wx.DatePickerCtrl.GetValue(self) 415 416 if value is None: 417 return None 418 419 # manage null dates (useful when wx.DP_ALLOWNONE is set) 420 if not value.IsValid(): 421 if invalid_as_none: 422 return None 423 else: 424 return value 425 426 self.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 427 self.Refresh() 428 429 if not as_pydt: 430 return value 431 432 return gmDateTime.wxDate2py_dt(value)
433 #---------------------------------------------- 434 # def convenience wrapper 435 #----------------------------------------------
436 - def is_valid_timestamp(self, allow_none=True, invalid_as_none=False):
437 val = self.GetValue(as_pydt = False, invalid_as_none = invalid_as_none) 438 439 if val is None: 440 if allow_none: 441 valid = True 442 else: 443 valid = False 444 else: 445 valid = val.IsValid() 446 447 if valid: 448 self.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 449 else: 450 self.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 451 452 self.Refresh() 453 return valid
454 #----------------------------------------------
455 - def get_pydt(self):
456 return self.GetValue(as_pydt = True)
457 #----------------------------------------------
458 - def display_as_valid(self, valid=True):
459 if valid is True: 460 self.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 461 else: 462 self.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 463 self.Refresh()
464 #================================================== 465 # main 466 #-------------------------------------------------- 467 if __name__ == '__main__': 468 469 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'): 470 from Gnumed.pycommon import gmI18N 471 gmI18N.activate_locale() 472 gmI18N.install_domain(domain='gnumed') 473 gmDateTime.init() 474 475 #----------------------------------------------------
476 - def test_cli():
477 mp = cMatchProvider_FuzzyTimestamp() 478 mp.word_separators = None 479 mp.setThresholds(aWord = 998, aSubstring = 999) 480 val = None 481 while val != 'exit': 482 print "************************************" 483 val = raw_input('Enter date fragment: ') 484 found, matches = mp.getMatches(aFragment=val) 485 for match in matches: 486 print match['label'] 487 print match['data'] 488 print "---------------"
489 #--------------------------------------------------------
490 - def test_gui():
491 app = wx.PyWidgetTester(size = (200, 300)) 492 app.SetWidget(cFuzzyTimestampInput, id=-1, size=(180,20), pos=(10,20)) 493 app.MainLoop()
494 #--------------------------------------------------------
495 - def test_picker():
496 app = wx.PyWidgetTester(size = (200, 300)) 497 app.SetWidget(cDateInputCtrl, id=-1, size=(180,20), pos=(10,20)) 498 app.MainLoop()
499 #-------------------------------------------------------- 500 #test_cli() 501 #test_gui() 502 test_picker() 503 504 #================================================== 505 # - free text input: start string with " 506 #================================================== 507