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 113 if len(matches) == 0: 114 return (False, []) 115 116 items = [] 117 for match in matches: 118 if match['data'] is None: 119 list_label = match['label'] 120 else: 121 list_label = gmDateTime.pydt_strftime ( 122 match['data'], 123 format = '%A, %d. %B %Y (%x)', 124 accuracy = gmDateTime.acc_days 125 ) 126 items.append ({ 127 'data': match['data'], 128 'field_label': match['label'], 129 'list_label': list_label 130 }) 131 132 return (True, items)
133 #--------------------------------------------------------
134 - def getMatchesByWord(self, aFragment):
135 """Return matches for aFragment at start of words inside phrases.""" 136 return self.getMatchesByPhrase(aFragment)
137 #--------------------------------------------------------
138 - def getMatchesBySubstr(self, aFragment):
139 """Return matches for aFragment as a true substring.""" 140 return self.getMatchesByPhrase(aFragment)
141 #--------------------------------------------------------
142 - def getAllMatches(self):
143 """Return all items.""" 144 145 matches = (False, []) 146 return matches
147 148 # # consider this: 149 # dlg = cCalendarDatePickerDlg(None) 150 # # FIXME: show below parent 151 # dlg.CentreOnScreen() 152 # 153 # if dlg.ShowModal() == wx.ID_OK: 154 # date = dlg.cal.Date 155 # if date is not None: 156 # if date.IsValid(): 157 # date = gmDateTime.wxDate2py_dt(wxDate = date) 158 # lbl = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 159 # matches = (True, [{'data': date, 'label': lbl}]) 160 # dlg.Destroy() 161 # 162 # return matches 163 #============================================================
164 -class cDateInputPhraseWheel(gmPhraseWheel.cPhraseWheel):
165
166 - def __init__(self, *args, **kwargs):
167 168 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 169 170 self.matcher = cDateMatchProvider() 171 self.phrase_separators = None 172 173 self.static_tooltip_extra = _('<ALT-C/K>: pick from (c/k)alendar')
174 #-------------------------------------------------------- 175 # internal helpers 176 #-------------------------------------------------------- 177 # def __text2timestamp(self): 178 # 179 # self._update_candidates_in_picklist(val = self.GetValue().strip()) 180 # 181 # if len(self._current_match_candidates) == 1: 182 # return self._current_match_candidates[0]['data'] 183 # 184 # return None 185 #--------------------------------------------------------
186 - def __pick_from_calendar(self):
187 dlg = cCalendarDatePickerDlg(self) 188 # FIXME: show below parent 189 dlg.CentreOnScreen() 190 decision = dlg.ShowModal() 191 date = dlg.cal.Date 192 dlg.Destroy() 193 194 if decision != wx.ID_OK: 195 return 196 197 if date is None: 198 return 199 200 if not date.IsValid(): 201 return 202 203 date = gmDateTime.wxDate2py_dt(wxDate = date) 204 val = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 205 self.SetText(value = val, data = date, suppress_smarts = True)
206 #-------------------------------------------------------- 207 # phrasewheel internal API 208 #--------------------------------------------------------
209 - def _on_lose_focus(self, event):
210 # are we valid ? 211 if len(self._data) == 0: 212 self._set_data_to_first_match() 213 214 # let the base class do its thing 215 super(cDateInputPhraseWheel, self)._on_lose_focus(event)
216 #--------------------------------------------------------
217 - def _picklist_item2display_string(self, item=None):
218 data = item['data'] 219 if data is not None: 220 return gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 221 return item['field_label']
222 #--------------------------------------------------------
223 - def _on_key_down(self, event):
224 225 # <ALT-C> / <ALT-K> -> calendar 226 if event.AltDown() is True: 227 char = unichr(event.GetUnicodeKey()) 228 if char in u'ckCK': 229 self.__pick_from_calendar() 230 return 231 232 super(cDateInputPhraseWheel, self)._on_key_down(event)
233 #--------------------------------------------------------
234 - def _get_data_tooltip(self):
235 if len(self._data) == 0: 236 return u'' 237 238 date = self.GetData() 239 # if match provider only provided completions 240 # but not a full date with it 241 if date is None: 242 return u'' 243 244 return gmDateTime.pydt_strftime ( 245 date, 246 format = '%A, %d. %B %Y (%x)', 247 accuracy = gmDateTime.acc_days 248 )
249 #-------------------------------------------------------- 250 # external API 251 #--------------------------------------------------------
252 - def SetValue(self, value):
253 254 if isinstance(value, pyDT.datetime): 255 self.SetText(data = value, suppress_smarts = True) 256 return 257 258 if value is None: 259 value = u'' 260 261 super(self.__class__, self).SetValue(value)
262 #--------------------------------------------------------
263 - def SetText(self, value=u'', data=None, suppress_smarts=False):
264 265 if data is not None: 266 if isinstance(data, gmDateTime.cFuzzyTimestamp): 267 data = data.timestamp 268 if value.strip() == u'': 269 value = gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 270 271 super(self.__class__, self).SetText(value = value, data = data, suppress_smarts = suppress_smarts)
272 #--------------------------------------------------------
273 - def SetData(self, data=None):
274 if data is None: 275 gmPhraseWheel.cPhraseWheel.SetText(self, u'', None) 276 else: 277 if isinstance(data, gmDateTime.cFuzzyTimestamp): 278 data = data.timestamp 279 val = gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 280 super(self.__class__, self).SetText(value = val, data = data)
281 #--------------------------------------------------------
282 - def GetData(self):
283 if len(self._data) == 0: 284 self._set_data_to_first_match() 285 286 return super(self.__class__, self).GetData()
287 #--------------------------------------------------------
288 - def is_valid_timestamp(self, allow_empty=True):
289 if len(self._data) > 0: 290 self.display_as_valid(True) 291 return True 292 293 if self.GetValue().strip() == u'': 294 if allow_empty: 295 self.display_as_valid(True) 296 return True 297 else: 298 self.display_as_valid(False) 299 return False 300 301 # skip showing calendar on '*' from here 302 if self.GetValue().strip() == u'*': 303 self.display_as_valid(False) 304 return False 305 306 self._set_data_to_first_match() 307 if len(self._data) == 0: 308 self.display_as_valid(False) 309 return False 310 311 self.display_as_valid(True) 312 return True
313 #-------------------------------------------------------- 314 # properties 315 #--------------------------------------------------------
316 - def _get_date(self):
317 return self.GetData()
318
319 - def _set_date(self, date):
320 raise AttributeError('._set_date not implemented')
321 # val = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days) 322 # self.data = date 323 324 date = property(_get_date, _set_date)
325 #============================================================
326 -class cMatchProvider_FuzzyTimestamp(gmMatchProvider.cMatchProvider):
327 - def __init__(self):
328 self.__allow_past = 1 329 self.__shifting_base = None 330 331 gmMatchProvider.cMatchProvider.__init__(self) 332 333 self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999) 334 self.word_separators = None
335 # self.ignored_chars("""[?!."'\\(){}\[\]<>~#*$%^_]+""") 336 #-------------------------------------------------------- 337 # external API 338 #-------------------------------------------------------- 339 #-------------------------------------------------------- 340 # base class API 341 #-------------------------------------------------------- 342 # internal matching algorithms 343 # 344 # if we end up here: 345 # - aFragment will not be "None" 346 # - aFragment will be lower case 347 # - we _do_ deliver matches (whether we find any is a different story) 348 #--------------------------------------------------------
349 - def getMatchesByPhrase(self, aFragment):
350 """Return matches for aFragment at start of phrases.""" 351 # self.__now = mxDT.now() 352 matches = gmDateTime.str2fuzzy_timestamp_matches(aFragment.strip()) 353 354 if len(matches) == 0: 355 return (False, []) 356 357 items = [] 358 for match in matches: 359 # if match['data'] is None: 360 # list_label = match['label'] 361 # else: 362 # list_label = gmDateTime.pydt_strftime ( 363 # match['data'].timestamp.format_accurately(), 364 # format = '%A, %d. %B %Y (%x)', 365 # accuracy = gmDateTime.acc_days 366 # ) 367 items.append ({ 368 'data': match['data'], 369 'field_label': match['label'], 370 'list_label': match['label'] 371 }) 372 373 return (True, items)
374 #--------------------------------------------------------
375 - def getMatchesByWord(self, aFragment):
376 """Return matches for aFragment at start of words inside phrases.""" 377 return self.getMatchesByPhrase(aFragment)
378 #--------------------------------------------------------
379 - def getMatchesBySubstr(self, aFragment):
380 """Return matches for aFragment as a true substring.""" 381 return self.getMatchesByPhrase(aFragment)
382 #--------------------------------------------------------
383 - def getAllMatches(self):
384 """Return all items.""" 385 return (False, [])
386 #==================================================
387 -class cFuzzyTimestampInput(gmPhraseWheel.cPhraseWheel):
388
389 - def __init__(self, *args, **kwargs):
390 391 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 392 393 self.matcher = cMatchProvider_FuzzyTimestamp() 394 self.phrase_separators = None 395 self.selection_only = True 396 self.selection_only_error_msg = _('Cannot interpret input as timestamp.')
397 #-------------------------------------------------------- 398 # internal helpers 399 #--------------------------------------------------------
400 - def __text2timestamp(self, val=None):
401 402 if val is None: 403 val = self.GetValue().strip() 404 405 success, matches = self.matcher.getMatchesByPhrase(val) 406 if len(matches) == 1: 407 return matches[0]['data'] 408 409 return None
410 #-------------------------------------------------------- 411 # phrasewheel internal API 412 #--------------------------------------------------------
413 - def _on_lose_focus(self, event):
414 # are we valid ? 415 if self.data is None: 416 # no, so try 417 self.data = self.__text2timestamp() 418 419 # let the base class do its thing 420 gmPhraseWheel.cPhraseWheel._on_lose_focus(self, event)
421 #--------------------------------------------------------
422 - def _picklist_item2display_string(self, item=None):
423 data = item['data'] 424 if data is not None: 425 return data.format_accurately() 426 return item['field_label']
427 #-------------------------------------------------------- 428 # external API 429 #--------------------------------------------------------
430 - def SetText(self, value=u'', data=None, suppress_smarts=False):
431 432 if data is not None: 433 if isinstance(data, pyDT.datetime): 434 data = gmDateTime.cFuzzyTimestamp(timestamp=data) 435 if value.strip() == u'': 436 value = data.format_accurately() 437 438 gmPhraseWheel.cPhraseWheel.SetText(self, value = value, data = data, suppress_smarts = suppress_smarts)
439 #--------------------------------------------------------
440 - def SetData(self, data=None):
441 if data is None: 442 gmPhraseWheel.cPhraseWheel.SetText(self, u'', None) 443 else: 444 if isinstance(data, pyDT.datetime): 445 data = gmDateTime.cFuzzyTimestamp(timestamp=data) 446 gmPhraseWheel.cPhraseWheel.SetText(self, value = data.format_accurately(), data = data)
447 #--------------------------------------------------------
448 - def is_valid_timestamp(self):
449 if self.data is not None: 450 return True 451 452 # skip empty value 453 if self.GetValue().strip() == u'': 454 return True 455 456 self.data = self.__text2timestamp() 457 if self.data is None: 458 return False 459 460 return True
461 #================================================== 462 # main 463 #-------------------------------------------------- 464 if __name__ == '__main__': 465 466 if len(sys.argv) < 2: 467 sys.exit() 468 469 if sys.argv[1] != 'test': 470 sys.exit() 471 472 gmI18N.activate_locale() 473 gmI18N.install_domain(domain='gnumed') 474 gmDateTime.init() 475 476 #----------------------------------------------------
477 - def test_cli():
478 mp = cMatchProvider_FuzzyTimestamp() 479 mp.word_separators = None 480 mp.setThresholds(aWord = 998, aSubstring = 999) 481 val = None 482 while val != 'exit': 483 print "************************************" 484 val = raw_input('Enter date fragment ("exit" to quit): ') 485 found, matches = mp.getMatches(aFragment=val) 486 for match in matches: 487 #print match 488 print match['label'] 489 print match['data'] 490 print "---------------"
491 #--------------------------------------------------------
492 - def test_fuzzy_picker():
493 app = wx.PyWidgetTester(size = (300, 40)) 494 app.SetWidget(cFuzzyTimestampInput, id=-1, size=(180,20), pos=(10,20)) 495 app.MainLoop()
496 #--------------------------------------------------------
497 - def test_picker():
498 app = wx.PyWidgetTester(size = (300, 40)) 499 app.SetWidget(cDateInputPhraseWheel, id=-1, size=(180,20), pos=(10,20)) 500 app.MainLoop()
501 #-------------------------------------------------------- 502 #test_cli() 503 test_fuzzy_picker() 504 #test_picker() 505 506 #================================================== 507