Gnumed.wxpython.gmDateTimeInput

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  __author__  = "K. Hilbert <Karsten.Hilbert@gmx.net>"
     10  __licence__ = "GPL v2 or later (details at http://www.gnu.org)"
     11
     12  # standard libary
     13  import re, string, sys, time, datetime as pyDT, logging
     14
     15
     16  # 3rd party
     17  import wx
     18  try:
     19          import wx.calendar as wxcal
     20  except ImportError:
     21          # Phoenix
     22          import wx.adv as wxcal
     23
     24
     25  # GNUmed specific
     26  if __name__ == '__main__':
     27          sys.path.insert(0, '../../')
     28  from Gnumed.pycommon import gmMatchProvider
     29  from Gnumed.pycommon import gmDateTime
     30  from Gnumed.pycommon import gmI18N
     31  from Gnumed.wxpython import gmPhraseWheel
     32  from Gnumed.wxpython import gmGuiHelpers
     33
     34  _log = logging.getLogger('gm.ui')
     35
     36  #============================================================
     37  #class cIntervalMatchProvider(gmMatchProvider.cMatchProvider):
     38  #       """Turns strings into candidate intervals."""
     39  #       def __init__(self):
     40  #
     41  #               gmMatchProvider.cMatchProvider.__init__(self)
     42  #
     43  #               self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999)
     44  #               self.word_separators = None
     45  ##              self.ignored_chars("""[?!."'\\(){}\[\]<>~#*$%^_]+""")
     46  #       #--------------------------------------------------------
     47  #       # external API
     48  #       #--------------------------------------------------------
     49  #       #--------------------------------------------------------
     50  #       # base class API
     51  #       #--------------------------------------------------------
     52  #       def getMatchesByPhrase(self, aFragment):
     53  #               intv = gmDateTime.str2interval(str_interval = aFragment)
     54  #
     55  #               if intv is None:
     56  #                       return (False, [])
     57  #
     58  #               items = [{
     59  #                       'data': intv,
     60  #                       'field_label': gmDateTime.format_interval(intv, gmDateTime.acc_minutes),
     61  #                       'list_label': gmDateTime.format_interval(intv, gmDateTime.acc_minutes)
     62  #               }]
     63  #
     64  #               return (True, items)
     65  #       #--------------------------------------------------------
     66  #       def getMatchesByWord(self, aFragment):
     67  #               return self.getMatchesByPhrase(aFragment)
     68  #       #--------------------------------------------------------
     69  #       def getMatchesBySubstr(self, aFragment):
     70  #               return self.getMatchesByPhrase(aFragment)
     71  #       #--------------------------------------------------------
     72  #       def getAllMatches(self):
     73  #               matches = (False, [])
     74  #               return matches
     75
     76  #============================================================
     77 -class cIntervalPhraseWheel(gmPhraseWheel.cPhraseWheel):
     78
     79 -        def __init__(self, *args, **kwargs):
     80
     81                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
     82                  self.phrase_separators = None
     83                  self.display_accuracy = None
     84          #--------------------------------------------------------
     85          # phrasewheel internal API
     86          #--------------------------------------------------------
     87 -        def _update_candidates_in_picklist(self, val):
     88                  intv = gmDateTime.str2interval(str_interval = val)
     89                  if intv is None:
     90                          self._current_match_candidates = []
     91                  else:
     92                          self._current_match_candidates = [{
     93                                  'data': intv,
     94                                  'field_label': gmDateTime.format_interval(intv, gmDateTime.acc_minutes),
     95                                  'list_label': gmDateTime.format_interval(intv, gmDateTime.acc_minutes)
     96                          }]
     97                  self._picklist.SetItems(self._current_match_candidates)
     98          #---------------------------------------------------------
     99  #       def _on_lose_focus(self, event):
    100  #               # are we valid ?
    101  #               if len(self._data) == 0:
    102  #                       self._set_data_to_first_match()
    103  #
    104  #               # let the base class do its thing
    105  #               super(cIntervalPhraseWheel, self)._on_lose_focus(event)
    106          #--------------------------------------------------------
    107 -        def _picklist_item2display_string(self, item=None):
    108                  intv = item['data']
    109                  if intv is not None:
    110                          return gmDateTime.format_interval (
    111                                  interval = intv,
    112                                  accuracy_wanted = self.display_accuracy
    113                          )
    114                  return item['field_label']
    115          #--------------------------------------------------------
    116 -        def _get_data_tooltip(self):
    117                  intv = self.GetData()
    118                  if intv is None:
    119                          return ''
    120                  return gmDateTime.format_interval (
    121                          interval = intv,
    122                          accuracy_wanted = self.display_accuracy
    123                  )
    124          #--------------------------------------------------------
    125          # external API
    126          #--------------------------------------------------------
    127 -        def SetValue(self, value):
    128
    129                  if isinstance(value, pyDT.timedelta):
    130                          self.SetText(data = value, suppress_smarts = True)
    131                          return
    132
    133                  if value is None:
    134                          value = ''
    135
    136                  super(cIntervalPhraseWheel, self).SetValue(value)
    137          #--------------------------------------------------------
    138 -        def SetText(self, value='', data=None, suppress_smarts=False):
    139
    140                  if data is not None:
    141                          if value.strip() == '':
    142                                  value = gmDateTime.format_interval (
    143                                          interval = data,
    144                                          accuracy_wanted = self.display_accuracy
    145                                  )
    146
    147                  super(cIntervalPhraseWheel, self).SetText(value = value, data = data, suppress_smarts = suppress_smarts)
    148          #--------------------------------------------------------
    149 -        def SetData(self, data=None):
    150                  if data is None:
    151                          super(cIntervalPhraseWheel, self).SetText('', None)
    152                          return
    153
    154                  value = gmDateTime.format_interval (
    155                          interval = data,
    156                          accuracy_wanted = self.display_accuracy
    157                  )
    158                  super(cIntervalPhraseWheel, self).SetText(value = value, data = data)
    159          #--------------------------------------------------------
    160 -        def GetData(self):
    161                  if len(self._data) == 0:
    162                          self._set_data_to_first_match()
    163
    164                  return super(cIntervalPhraseWheel, self).GetData()
    165
    166  #============================================================
    167 -class cCalendarDatePickerDlg(wx.Dialog):
    168          """Shows a calendar control from which the user can pick a date."""
    169 -        def __init__(self, parent):
    170
    171                  wx.Dialog.__init__(self, parent, title = _('Pick a date ...'))
    172                  panel = wx.Panel(self, -1)
    173
    174                  sizer = wx.BoxSizer(wx.VERTICAL)
    175                  panel.SetSizer(sizer)
    176
    177                  cal = wxcal.CalendarCtrl(panel)
    178
    179                  if sys.platform != 'win32':
    180                          # gtk truncates the year - this fixes it
    181                          w, h = cal.Size
    182                          cal.Size = (w+25, h)
    183                          cal.MinSize = cal.Size
    184
    185                  sizer.Add(cal, 0)
    186
    187                  button_sizer = wx.BoxSizer(wx.HORIZONTAL)
    188                  button_sizer.Add((0, 0), 1)
    189                  btn_ok = wx.Button(panel, wx.ID_OK)
    190                  btn_ok.SetDefault()
    191                  button_sizer.Add(btn_ok, 0, wx.ALL, 2)
    192                  button_sizer.Add((0, 0), 1)
    193                  btn_can = wx.Button(panel, wx.ID_CANCEL)
    194                  button_sizer.Add(btn_can, 0, wx.ALL, 2)
    195                  button_sizer.Add((0, 0), 1)
    196                  sizer.Add(button_sizer, 1, wx.EXPAND | wx.ALL, 10)
    197                  sizer.Fit(panel)
    198                  self.ClientSize = panel.Size
    199
    200                  cal.Bind(wx.EVT_KEY_DOWN, self.__on_key_down)
    201                  cal.SetFocus()
    202                  self.cal = cal
    203
    204          #-----------------------------------------------------------
    205 -        def __on_key_down(self, evt):
    206                  code = evt.KeyCode
    207                  if code == wx.WXK_TAB:
    208                          self.cal.Navigate()
    209                  elif code in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER):
    210                          self.EndModal(wx.ID_OK)
    211                  elif code == wx.WXK_ESCAPE:
    212                          self.EndModal(wx.ID_CANCEL)
    213                  else:
    214                          evt.Skip()
    215
    216  #============================================================
    217 -class cDateMatchProvider(gmMatchProvider.cMatchProvider):
    218          """Turns strings into candidate dates.
    219
    220          Matching on "all" (*, '') will pop up a calendar :-)
    221          """
    222 -        def __init__(self):
    223
    224                  gmMatchProvider.cMatchProvider.__init__(self)
    225
    226                  self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999)
    227                  self.word_separators = None
    228  #               self.ignored_chars("""[?!."'\\(){}\[\]<>~#*$%^_]+""")
    229          #--------------------------------------------------------
    230          # external API
    231          #--------------------------------------------------------
    232          #--------------------------------------------------------
    233          # base class API
    234          #--------------------------------------------------------
    235          # internal matching algorithms
    236          #
    237          # if we end up here:
    238          #       - aFragment will not be "None"
    239          #   - aFragment will be lower case
    240          #       - we _do_ deliver matches (whether we find any is a different story)
    241          #--------------------------------------------------------
    242 -        def getMatchesByPhrase(self, aFragment):
    243                  """Return matches for aFragment at start of phrases."""
    244                  matches = gmDateTime.str2pydt_matches(str2parse = aFragment.strip())
    245
    246                  if len(matches) == 0:
    247                          return (False, [])
    248
    249                  items = []
    250                  for match in matches:
    251                          if match['data'] is None:
    252                                  items.append ({
    253                                          'data': None,
    254                                          'field_label': match['label'],
    255                                          'list_label': match['label']
    256                                  })
    257                                  continue
    258
    259                          data = match['data'].replace (
    260                                  hour = 11,
    261                                  minute = 11,
    262                                  second = 11,
    263                                  microsecond = 111111
    264                          )
    265                          list_label = gmDateTime.pydt_strftime (
    266                                  data,
    267                                  format = '%A, %d. %B %Y (%x)',
    268                                  accuracy = gmDateTime.acc_days
    269                          )
    270                          items.append ({
    271                                  'data': data,
    272                                  'field_label': match['label'],
    273                                  'list_label': list_label
    274                          })
    275
    276                  return (True, items)
    277          #--------------------------------------------------------
    278 -        def getMatchesByWord(self, aFragment):
    279                  """Return matches for aFragment at start of words inside phrases."""
    280                  return self.getMatchesByPhrase(aFragment)
    281          #--------------------------------------------------------
    282 -        def getMatchesBySubstr(self, aFragment):
    283                  """Return matches for aFragment as a true substring."""
    284                  return self.getMatchesByPhrase(aFragment)
    285          #--------------------------------------------------------
    286 -        def getAllMatches(self):
    287                  """Return all items."""
    288
    289                  matches = (False, [])
    290                  return matches
    291
    292  #               # consider this:
    293  #               dlg = cCalendarDatePickerDlg(None)
    294  #               # FIXME: show below parent
    295  #               dlg.CentreOnScreen()
    296  #
    297  #               if dlg.ShowModal() == wx.ID_OK:
    298  #                       date = dlg.cal.Date
    299  #                       if date is not None:
    300  #                               if date.IsValid():
    301  #                                       date = gmDateTime.wxDate2py_dt(wxDate = date).replace (
    302  #                                               hour = 11,
    303  #                                               minute = 11,
    304  #                                               second = 11,
    305  #                                               microsecond = 111111
    306  #                                       )
    307  #                                       lbl = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days)
    308  #                                       matches = (True, [{'data': date, 'label': lbl}])
    309  #               dlg.DestroyLater()
    310  #
    311  #               return matches
    312
    313  #============================================================
    314 -class cDateInputPhraseWheel(gmPhraseWheel.cPhraseWheel):
    315
    316 -        def __init__(self, *args, **kwargs):
    317
    318                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    319
    320                  self.matcher = cDateMatchProvider()
    321                  self.phrase_separators = None
    322
    323                  self.static_tooltip_extra = _('<ALT-C/K>: pick from (c/k)alendar')
    324
    325                  self.__weekday_keys = [wx.WXK_F1, wx.WXK_F2, wx.WXK_F3, wx.WXK_F4, wx.WXK_F5, wx.WXK_F6, wx.WXK_F7]
    326
    327          #--------------------------------------------------------
    328          # internal helpers
    329          #--------------------------------------------------------
    330  #       def __text2timestamp(self):
    331  #
    332  #               self._update_candidates_in_picklist(val = self.GetValue().strip())
    333  #
    334  #               if len(self._current_match_candidates) == 1:
    335  #                       return self._current_match_candidates[0]['data']
    336  #
    337  #               return None
    338          #--------------------------------------------------------
    339 -        def __pick_from_calendar(self):
    340                  dlg = cCalendarDatePickerDlg(self)
    341                  # FIXME: show below parent
    342                  dlg.CentreOnScreen()
    343                  decision = dlg.ShowModal()
    344                  date = dlg.cal.Date
    345                  dlg.DestroyLater()
    346
    347                  if decision != wx.ID_OK:
    348                          return
    349
    350                  if date is None:
    351                          return
    352
    353                  if not date.IsValid():
    354                          return
    355
    356                  date = gmDateTime.wxDate2py_dt(wxDate = date).replace (
    357                          hour = 11,
    358                          minute = 11,
    359                          second = 11,
    360                          microsecond = 111111
    361                  )
    362                  val = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days)
    363                  self.SetText(value = val, data = date, suppress_smarts = True)
    364
    365          #--------------------------------------------------------
    366 -        def __pick_from_weekday(self, weekday):
    367                  self.is_valid_timestamp(empty_is_valid = True)
    368                  target_date = gmDateTime.get_date_of_weekday_in_week_of_date(weekday, base_dt = self.date)
    369                  val = gmDateTime.pydt_strftime(target_date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days)
    370                  self.SetText(value = val, data = target_date, suppress_smarts = True)
    371
    372          #--------------------------------------------------------
    373          # phrasewheel internal API
    374          #--------------------------------------------------------
    375 -        def _on_lose_focus(self, event):
    376                  # no valid date yet ?
    377                  if len(self._data) == 0:
    378                          self._set_data_to_first_match()
    379                          date = self.GetData()
    380                          if date is not None:
    381                                  self.SetValue(gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days))
    382
    383                  # let the base class do its thing
    384                  super(cDateInputPhraseWheel, self)._on_lose_focus(event)
    385
    386          #--------------------------------------------------------
    387 -        def _picklist_item2display_string(self, item=None):
    388                  data = item['data']
    389                  if data is not None:
    390                          return gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days)
    391                  return item['field_label']
    392
    393          #--------------------------------------------------------
    394 -        def _on_key_down(self, event):
    395
    396                  if event.GetUnicodeKey() == wx.WXK_NONE:
    397                          key = event.GetKeyCode()
    398                          if key in self.__weekday_keys:
    399                                  self.__pick_from_weekday(self.__weekday_keys.index(key))
    400                                  return
    401
    402                  # <ALT-C> / <ALT-K> -> calendar
    403                  if event.AltDown() is True:
    404                          char = chr(event.GetUnicodeKey())
    405                          if char in 'ckCK':
    406                                  self.__pick_from_calendar()
    407                                  return
    408
    409                  super()._on_key_down(event)
    410
    411          #--------------------------------------------------------
    412 -        def _get_data_tooltip(self):
    413                  if len(self._data) == 0:
    414                          return ''
    415
    416                  date = self.GetData()
    417                  # if match provider only provided completions
    418                  # but not a full date with it
    419                  if date is None:
    420                          return ''
    421
    422                  if isinstance(date, pyDT.datetime):
    423                          date = pyDT.date(date.year, date.month, date.day)
    424                  today = pyDT.date.today()
    425                  if date > today:
    426                          intv = date - today
    427                          return _('%s\n (a %s in %s)') % (
    428                                  date.strftime(format = '%B %d %Y -- %x'),
    429                                  date.strftime(format = '%A'),
    430                                  gmDateTime.format_interval(interval = intv, accuracy_wanted = gmDateTime.acc_days, verbose = True)
    431                          )
    432
    433                  if date < today:
    434                          intv = today - date
    435                          return _('%s\n (a %s %s ago)') % (
    436                                  gmDateTime.pydt_strftime(date, format = '%B %d %Y -- %x'),
    437                                  gmDateTime.pydt_strftime(date, format = '%A'),
    438                                  gmDateTime.format_interval(interval = intv, accuracy_wanted = gmDateTime.acc_days, verbose = True)
    439                          )
    440
    441                  return _('today (%s): %s') % (
    442                          gmDateTime.pydt_strftime(date, format = '%A'),
    443                          gmDateTime.pydt_strftime(date, format = '%B %d %Y -- %x')
    444                  )
    445
    446          #--------------------------------------------------------
    447          # external API
    448          #--------------------------------------------------------
    449 -        def SetValue(self, value):
    450
    451                  if isinstance(value, pyDT.datetime):
    452                          date = value.replace (
    453                                  hour = 11,
    454                                  minute = 11,
    455                                  second = 11,
    456                                  microsecond = 111111
    457                          )
    458                          self.SetText(data = date, suppress_smarts = True)
    459                          return
    460
    461                  if value is None:
    462                          value = ''
    463
    464                  super().SetValue(value)
    465
    466          #--------------------------------------------------------
    467 -        def SetText(self, value='', data=None, suppress_smarts=False):
    468
    469                  if data is not None:
    470                          if isinstance(data, gmDateTime.cFuzzyTimestamp):
    471                                  data = data.timestamp.replace (
    472                                          hour = 11,
    473                                          minute = 11,
    474                                          second = 11,
    475                                          microsecond = 111111
    476                                  )
    477                          if value.strip() == '':
    478                                  value = gmDateTime.pydt_strftime(data, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days)
    479
    480                  super().SetText(value = value, data = data, suppress_smarts = suppress_smarts)
    481
    482          #--------------------------------------------------------
    483 -        def SetData(self, data=None):
    484                  if data is None:
    485                          gmPhraseWheel.cPhraseWheel.SetText(self, '', None)
    486                          return
    487                  self.SetText(data = data)
    488
    489          #--------------------------------------------------------
    490 -        def GetData(self):
    491                  if len(self._data) == 0:
    492                          self._set_data_to_first_match()
    493
    494                  return super(self.__class__, self).GetData()
    495
    496          #--------------------------------------------------------
    497 -        def is_valid_timestamp(self, empty_is_valid=True):
    498                  if len(self._data) > 0:
    499                          self.display_as_valid(True)
    500                          return True
    501
    502                  if self.GetValue().strip() == '':
    503                          if empty_is_valid:
    504                                  self.display_as_valid(True)
    505                                  return True
    506                          else:
    507                                  self.display_as_valid(False)
    508                                  return False
    509
    510                  # skip showing calendar on '*' from here
    511                  if self.GetValue().strip() == '*':
    512                          self.display_as_valid(False)
    513                          return False
    514
    515                  # try to auto-snap to first match
    516                  self._set_data_to_first_match()
    517                  if len(self._data) == 0:
    518                          self.display_as_valid(False)
    519                          return False
    520
    521                  date = self.GetData()
    522                  self.SetValue(gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days))#, none_str = u'')
    523                  self.display_as_valid(True)
    524                  return True
    525
    526          #--------------------------------------------------------
    527          # properties
    528          #--------------------------------------------------------
    529 -        def _get_date(self):
    530                  return self.GetData()
    531
    532 -        def _set_date(self, date):
    533                  raise AttributeError('._set_date not implemented')
    534  #               val = gmDateTime.pydt_strftime(date, format = '%Y-%m-%d', accuracy = gmDateTime.acc_days)
    535  #               self.data = date.replace (
    536  #                       hour = 11,
    537  #                       minute = 11,
    538  #                       second = 11,
    539  #                       microsecond = 111111
    540  #               )
    541
    542          date = property(_get_date, _set_date)
    543
    544  #============================================================
    545 -class cMatchProvider_FuzzyTimestamp(gmMatchProvider.cMatchProvider):
    546 -        def __init__(self):
    547                  self.__allow_past = 1
    548                  self.__shifting_base = None
    549
    550                  gmMatchProvider.cMatchProvider.__init__(self)
    551
    552                  self.setThresholds(aPhrase = 1, aWord = 998, aSubstring = 999)
    553                  self.word_separators = None
    554  #               self.ignored_chars("""[?!."'\\(){}\[\]<>~#*$%^_]+""")
    555          #--------------------------------------------------------
    556          # external API
    557          #--------------------------------------------------------
    558          #--------------------------------------------------------
    559          # base class API
    560          #--------------------------------------------------------
    561          # internal matching algorithms
    562          #
    563          # if we end up here:
    564          #       - aFragment will not be "None"
    565          #   - aFragment will be lower case
    566          #       - we _do_ deliver matches (whether we find any is a different story)
    567          #--------------------------------------------------------
    568 -        def getMatchesByPhrase(self, aFragment):
    569                  """Return matches for aFragment at start of phrases."""
    570                  matches = gmDateTime.str2fuzzy_timestamp_matches(aFragment.strip())
    571
    572                  if len(matches) == 0:
    573                          return (False, [])
    574
    575                  items = []
    576                  for match in matches:
    577                          items.append ({
    578                                  'data': match['data'],
    579                                  'field_label': match['label'],
    580                                  'list_label': match['label']
    581                          })
    582
    583                  return (True, items)
    584          #--------------------------------------------------------
    585 -        def getMatchesByWord(self, aFragment):
    586                  """Return matches for aFragment at start of words inside phrases."""
    587                  return self.getMatchesByPhrase(aFragment)
    588          #--------------------------------------------------------
    589 -        def getMatchesBySubstr(self, aFragment):
    590                  """Return matches for aFragment as a true substring."""
    591                  return self.getMatchesByPhrase(aFragment)
    592          #--------------------------------------------------------
    593 -        def getAllMatches(self):
    594                  """Return all items."""
    595                  return (False, [])
    596
    597  #==================================================
    598 -class cFuzzyTimestampInput(gmPhraseWheel.cPhraseWheel):
    599
    600 -        def __init__(self, *args, **kwargs):
    601
    602                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    603
    604                  self.matcher = cMatchProvider_FuzzyTimestamp()
    605                  self.phrase_separators = None
    606                  self.selection_only = True
    607                  self.selection_only_error_msg = _('Cannot interpret input as timestamp.')
    608                  self.display_accuracy = None
    609
    610                  self.__weekday_keys = [wx.WXK_F1, wx.WXK_F2, wx.WXK_F3, wx.WXK_F4, wx.WXK_F5, wx.WXK_F6, wx.WXK_F7]
    611
    612          #--------------------------------------------------------
    613          # internal helpers
    614          #--------------------------------------------------------
    615 -        def __text2timestamp(self, val=None):
    616                  if val is None:
    617                          val = self.GetValue()
    618                  val = val.strip()
    619                  if val == '':
    620                          return None
    621                  success, matches = self.matcher.getMatchesByPhrase(val)
    622                  if len(matches) == 1:
    623                          return matches[0]['data']
    624                  return None
    625
    626          #--------------------------------------------------------
    627 -        def __pick_from_weekday(self, weekday):
    628                  self.is_valid_timestamp(empty_is_valid = True)
    629                  dt = self.GetData()
    630                  if dt is not None:
    631                          dt = dt.timestamp
    632                  target_date = gmDateTime.get_date_of_weekday_in_week_of_date(weekday, base_dt = dt)
    633                  self.SetText(data = target_date, suppress_smarts = True)
    634
    635          #--------------------------------------------------------
    636          # phrasewheel internal API
    637          #--------------------------------------------------------
    638 -        def _on_lose_focus(self, event):
    639                  # are we valid ?
    640                  if self.data is None:
    641                          # no, so try
    642                          date = self.__text2timestamp()
    643                          if date is not None:
    644                                  self.SetValue(value = date.format_accurately(accuracy = self.display_accuracy))
    645                                  self.data = date
    646
    647                  # let the base class do its thing
    648                  gmPhraseWheel.cPhraseWheel._on_lose_focus(self, event)
    649
    650          #--------------------------------------------------------
    651 -        def _picklist_item2display_string(self, item=None):
    652                  data = item['data']
    653                  if data is not None:
    654                          return data.format_accurately(accuracy = self.display_accuracy)
    655                  return item['field_label']
    656
    657          #--------------------------------------------------------
    658 -        def _on_key_down(self, event):
    659
    660                  if event.GetUnicodeKey() == wx.WXK_NONE:
    661                          key = event.GetKeyCode()
    662                          if key in self.__weekday_keys:
    663                                  self.__pick_from_weekday(self.__weekday_keys.index(key))
    664                                  return
    665
    666  #               # <ALT-C> / <ALT-K> -> calendar
    667  #               if event.AltDown() is True:
    668  #                       char = chr(event.GetUnicodeKey())
    669  #                       if char in 'ckCK':
    670  #                               self.__pick_from_calendar()
    671  #                               return
    672
    673                  super()._on_key_down(event)
    674
    675          #--------------------------------------------------------
    676 -        def _get_data_tooltip(self):
    677                  if len(self._data) == 0:
    678                          return ''
    679
    680                  date = self.GetData()
    681                  # if match provider only provided completions
    682                  # but not a full date with it
    683                  if date is None:
    684                          return ''
    685                  ts = date.timestamp
    686                  now = gmDateTime.pydt_now_here()
    687                  if ts > now:
    688                          intv = ts - now
    689                          template = _('%s\n %s\n in %s')
    690                  else:
    691                          intv = now - ts
    692                          template = _('%s\n%s\n%s ago')
    693                  txt = template % (
    694                          date.format_accurately(self.display_accuracy),
    695                          gmDateTime.pydt_strftime (
    696                                  ts,
    697                                  format = '%A, %B-%d %Y (%c)',
    698                          ),
    699                          gmDateTime.format_interval (
    700                                  interval = intv,
    701                                  accuracy_wanted = gmDateTime.acc_days,
    702                                  verbose = True
    703                          )
    704                  )
    705                  return txt
    706
    707          #--------------------------------------------------------
    708          # external API
    709          #--------------------------------------------------------
    710 -        def SetText(self, value='', data=None, suppress_smarts=False):
    711
    712                  if data is not None:
    713                          if isinstance(data, pyDT.datetime):
    714                                  data = gmDateTime.cFuzzyTimestamp(timestamp = data)
    715                          if value.strip() == '':
    716                                  value = data.format_accurately(accuracy = self.display_accuracy)
    717
    718                  super().SetText(value = value, data = data, suppress_smarts = suppress_smarts)
    719
    720          #--------------------------------------------------------
    721 -        def SetData(self, data=None):
    722                  if data is None:
    723                          gmPhraseWheel.cPhraseWheel.SetText(self, '', None)
    724                  else:
    725                          if isinstance(data, pyDT.datetime):
    726                                  data = gmDateTime.cFuzzyTimestamp(timestamp=data)
    727                          gmPhraseWheel.cPhraseWheel.SetText(self, value = data.format_accurately(accuracy = self.display_accuracy), data = data)
    728
    729          #--------------------------------------------------------
    730 -        def is_valid_timestamp(self, empty_is_valid=True):
    731                  if self.GetData() is not None:
    732                          return True
    733
    734                  # skip empty value
    735                  if self.GetValue().strip() == '':
    736                          if empty_is_valid:
    737                                  return True
    738                          return False
    739
    740                  date = self.__text2timestamp()
    741                  if date is None:
    742                          return False
    743
    744                  self.SetText (
    745                          value = date.format_accurately(accuracy = self.display_accuracy),
    746                          data = date,
    747                          suppress_smarts = True
    748                  )
    749
    750                  return True
    751
    752  #==================================================
    753  # main
    754  #--------------------------------------------------
    755  if __name__ == '__main__':
    756
    757          if len(sys.argv) < 2:
    758                  sys.exit()
    759
    760          if sys.argv[1] != 'test':
    761                  sys.exit()
    762
    763          gmI18N.activate_locale()
    764          gmI18N.install_domain(domain='gnumed')
    765          gmDateTime.init()
    766
    767          #----------------------------------------------------
    768 -        def test_cli():
    769                  mp = cMatchProvider_FuzzyTimestamp()
    770                  mp.word_separators = None
    771                  mp.setThresholds(aWord = 998, aSubstring = 999)
    772                  val = None
    773                  while val != 'exit':
    774                          print("************************************")
    775                          val = input('Enter date fragment ("exit" to quit): ')
    776                          found, matches = mp.getMatches(aFragment=val)
    777                          for match in matches:
    778                                  #print match
    779                                  print(match['label'])
    780                                  print(match['data'])
    781                                  print("---------------")
    782          #--------------------------------------------------------
    783 -        def test_fuzzy_picker():
    784                  app = wx.PyWidgetTester(size = (300, 40))
    785                  app.SetWidget(cFuzzyTimestampInput, id=-1, size=(180,20), pos=(10,20))
    786                  app.MainLoop()
    787          #--------------------------------------------------------
    788 -        def test_picker():
    789                  app = wx.PyWidgetTester(size = (300, 40))
    790                  app.SetWidget(cDateInputPhraseWheel, id=-1, size=(180,20), pos=(10,20))
    791                  app.MainLoop()
    792          #--------------------------------------------------------
    793          #test_cli()
    794          #test_fuzzy_picker()
    795          test_picker()
    796
    797  #==================================================
    798