Gnumed.wxpython.gmPatSearchWidgets

Package Gnumed :: Package wxpython :: Module gmPatSearchWidgets |
[frames] | no frames]

# Source Code for Module Gnumed.wxpython.gmPatSearchWidgets

       1  #  coding: latin-1
       2  """GNUmed quick person search widgets.
       3
       4  This widget allows to search for persons based on the
       5  critera name, date of birth and person ID. It goes to
       6  considerable lengths to understand the user's intent from
       7  her input. For that to work well we need per-culture
       8  query generators. However, there's always the fallback
       9  generator.
      10  """
      11  #============================================================
      12  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
      13  __license__ = 'GPL v2 or later (for details see http://www.gnu.org/)'
      14
      15  import sys
      16  import os.path
      17  import glob
      18  import re as regex
      19  import logging
      20
      21
      22  import wx
      23
      24
      25  if __name__ == '__main__':
      26          sys.path.insert(0, '../../')
      27          from Gnumed.pycommon import gmLog2
      28  from Gnumed.pycommon import gmDispatcher
      29  from Gnumed.pycommon import gmDateTime
      30  from Gnumed.pycommon import gmTools
      31  from Gnumed.pycommon import gmPG2
      32  from Gnumed.pycommon import gmI18N
      33  from Gnumed.pycommon import gmCfg
      34  from Gnumed.pycommon import gmMatchProvider
      35  from Gnumed.pycommon import gmCfg2
      36  from Gnumed.pycommon import gmNetworkTools
      37
      38  from Gnumed.business import gmPerson
      39  from Gnumed.business import gmStaff
      40  from Gnumed.business import gmKVK
      41  from Gnumed.business import gmPraxis
      42  from Gnumed.business import gmCA_MSVA
      43  from Gnumed.business import gmPersonSearch
      44  from Gnumed.business import gmProviderInbox
      45
      46  from Gnumed.wxpython import gmGuiHelpers
      47  from Gnumed.wxpython import gmAuthWidgets
      48  from Gnumed.wxpython import gmRegetMixin
      49  from Gnumed.wxpython import gmEditArea
      50  from Gnumed.wxpython.gmPersonCreationWidgets import create_new_person
      51
      52
      53  _log = logging.getLogger('gm.person')
      54
      55  _cfg = gmCfg2.gmCfgData()
      56
      57  ID_PatPickList = wx.NewId()
      58  ID_BTN_AddNew = wx.NewId()
      59
      60  #============================================================
      61 -def merge_patients(parent=None):
      62          dlg = cMergePatientsDlg(parent, -1)
      63          result = dlg.ShowModal()
      64
      65  #============================================================
      66  from Gnumed.wxGladeWidgets import wxgMergePatientsDlg
      67
      68 -class cMergePatientsDlg(wxgMergePatientsDlg.wxgMergePatientsDlg):
      69
      70 -        def __init__(self, *args, **kwargs):
      71                  wxgMergePatientsDlg.wxgMergePatientsDlg.__init__(self, *args, **kwargs)
      72
      73                  curr_pat = gmPerson.gmCurrentPatient()
      74                  if curr_pat.connected:
      75                          self._TCTRL_patient1.person = curr_pat
      76                          self._TCTRL_patient1._display_name()
      77                          self._RBTN_patient1.SetValue(True)
      78          #--------------------------------------------------------
      79 -        def _on_merge_button_pressed(self, event):
      80
      81                  if self._TCTRL_patient1.person is None:
      82                          gmDispatcher.send(signal = 'statustext', msg = _('No patient selected on the left.'), beep = True)
      83                          return
      84
      85                  if self._TCTRL_patient2.person is None:
      86                          gmDispatcher.send(signal = 'statustext', msg = _('No patient selected on the right.'), beep = True)
      87                          return
      88
      89                  if self._RBTN_patient1.GetValue():
      90                          patient2keep = self._TCTRL_patient1.person
      91                          patient2merge = self._TCTRL_patient2.person
      92                  else:
      93                          patient2keep = self._TCTRL_patient2.person
      94                          patient2merge = self._TCTRL_patient1.person
      95
      96                  if patient2merge['lastnames'] == 'Kirk':
      97                          if _cfg.get(option = 'debug'):
      98                                  gmNetworkTools.open_url_in_browser(url = 'http://en.wikipedia.org/wiki/File:Picard_as_Locutus.jpg')
      99                                  gmGuiHelpers.gm_show_info(_('\n\nYou will be assimilated.\n\n'), _('The Borg'))
     100                                  return
     101                          else:
     102                                  gmDispatcher.send(signal = 'statustext', msg = _('Cannot merge Kirk into another patient.'), beep = True)
     103                                  return
     104
     105                  doit = gmGuiHelpers.gm_show_question (
     106                          aMessage = _(
     107                                  'Are you positively sure you want to merge patient\n\n'
     108                                  ' #%s: %s (%s, %s)\n\n'
     109                                  'into patient\n\n'
     110                                  ' #%s: %s (%s, %s) ?\n\n'
     111                                  'Note that this action can ONLY be reversed by a laborious\n'
     112                                  'manual process requiring in-depth knowledge about databases\n'
     113                                  'and the patients in question !\n'
     114                          ) % (
     115                                  patient2merge.ID,
     116                                  patient2merge['description_gender'],
     117                                  patient2merge['gender'],
     118                                  patient2merge.get_formatted_dob(format = '%Y %b %d'),
     119                                  patient2keep.ID,
     120                                  patient2keep['description_gender'],
     121                                  patient2keep['gender'],
     122                                  patient2keep.get_formatted_dob(format = '%Y %b %d')
     123                          ),
     124                          aTitle = _('Merging patients: confirmation'),
     125                          cancel_button = False
     126                  )
     127                  if not doit:
     128                          return
     129
     130                  conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Merging patients'))
     131                  if conn is None:
     132                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot merge patients without admin access.'), beep = True)
     133                          return
     134
     135                  success, msg = patient2keep.assimilate_identity(other_identity = patient2merge, link_obj = conn)
     136                  conn.close()
     137                  if not success:
     138                          gmDispatcher.send(signal = 'statustext', msg = msg, beep = True)
     139                          return
     140
     141                  msg = _(
     142                          'The patient\n'
     143                          '\n'
     144                          ' #%s: %s (%s, %s)\n'
     145                          '\n'
     146                          'has successfully been merged into\n'
     147                          '\n'
     148                          ' #%s: %s (%s, %s)'
     149                  ) % (
     150                          patient2merge.ID,
     151                          patient2merge['description_gender'],
     152                          patient2merge['gender'],
     153                          patient2merge.get_formatted_dob(format = '%Y %b %d'),
     154                          patient2keep.ID,
     155                          patient2keep['description_gender'],
     156                          patient2keep['gender'],
     157                          patient2keep.get_formatted_dob(format = '%Y %b %d')
     158                  )
     159                  title = _('Merging patients: success')
     160
     161                  curr_pat = gmPerson.gmCurrentPatient()
     162                  # announce success
     163                  if (curr_pat.connected) and (patient2keep.ID == curr_pat.ID):
     164                          gmGuiHelpers.gm_show_info(aMessage = msg, aTitle = title)
     165                  # and offer to activate kept patient if not active
     166                  else:
     167                          msg = msg + (
     168                          '\n\n\n'
     169                          'Do you want to activate that patient\n'
     170                          'now for further modifications ?\n'
     171                          )
     172                          doit = gmGuiHelpers.gm_show_question (
     173                                  aMessage = msg,
     174                                  aTitle = title,
     175                                  cancel_button = False
     176                          )
     177                          if doit:
     178                                  wx.CallAfter(set_active_patient, patient = patient2keep)
     179
     180                  if self.IsModal():
     181                          self.EndModal(wx.ID_OK)
     182                  else:
     183                          self.Close()
     184
     185  #============================================================
     186  from Gnumed.wxGladeWidgets import wxgSelectPersonFromListDlg
     187
     188 -class cSelectPersonFromListDlg(wxgSelectPersonFromListDlg.wxgSelectPersonFromListDlg):
     189
     190 -        def __init__(self, *args, **kwargs):
     191                  wxgSelectPersonFromListDlg.wxgSelectPersonFromListDlg.__init__(self, *args, **kwargs)
     192
     193                  self.__cols = [
     194                          _('Lastname'),
     195                          _('Firstname'),
     196                          _('DOB'),
     197                          '%s%s' % (gmTools.u_female, gmTools.u_male),
     198                          _('last visit'),
     199                          _('Nickname / Comment'),
     200                          _('found via')
     201                  ]
     202                  self.__init_ui()
     203
     204          #--------------------------------------------------------
     205 -        def __init_ui(self):
     206                  for col in range(len(self.__cols)):
     207                          self._LCTRL_persons.InsertColumn(col, self.__cols[col])
     208
     209          #--------------------------------------------------------
     210 -        def set_persons(self, persons=None):
     211                  self._LCTRL_persons.DeleteAllItems()
     212
     213                  pos = len(persons) + 1
     214                  if pos == 1:
     215                          return False
     216
     217                  for person in persons:
     218                          row_num = self._LCTRL_persons.InsertItem(pos, label = gmTools.coalesce(person['title'], person['lastnames'], '%s, %%s' % person['lastnames']))
     219                          self._LCTRL_persons.SetItem(index = row_num, column = 1, label = person['firstnames'])
     220                          self._LCTRL_persons.SetItem(index = row_num, column = 2, label = person.get_formatted_dob(format = '%Y %b %d'))
     221                          self._LCTRL_persons.SetItem(index = row_num, column = 3, label = gmTools.coalesce(person['l10n_gender'], '?'))
     222
     223                          label = ''
     224                          if person.is_patient:
     225                                  enc = person.get_last_encounter()
     226                                  if enc is not None:
     227                                          label = '%s (%s)' % (gmDateTime.pydt_strftime(enc['started'], '%Y %b %d'), enc['l10n_type'])
     228                          self._LCTRL_persons.SetItem(index = row_num, column = 4, label = label)
     229
     230                          parts = []
     231                          if person['preferred'] is not None:
     232                                  parts.append(person['preferred'])
     233                          if person['comment'] is not None:
     234                                  parts.append(person['comment'])
     235                          self._LCTRL_persons.SetItem(index = row_num, column = 5, label = ' / '.join(parts))
     236
     237                          try:
     238                                  self._LCTRL_persons.SetItem(index = row_num, column = 6, label = person['match_type'])
     239                          except KeyError:
     240                                  _log.warning('cannot set match_type field')
     241                                  self._LCTRL_persons.SetItem(index = row_num, column = 6, label = '??')
     242
     243                  for col in range(len(self.__cols)):
     244                          self._LCTRL_persons.SetColumnWidth(col, wx.LIST_AUTOSIZE)
     245
     246                  self._BTN_select.Enable(False)
     247                  self._LCTRL_persons.SetFocus()
     248                  self._LCTRL_persons.Select(0)
     249
     250                  self._LCTRL_persons.set_data(data = persons)
     251
     252          #--------------------------------------------------------
     253 -        def get_selected_person(self):
     254                  return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
     255          #--------------------------------------------------------
     256          # event handlers
     257          #--------------------------------------------------------
     258 -        def _on_list_item_selected(self, evt):
     259                  self._BTN_select.Enable(True)
     260                  return
     261          #--------------------------------------------------------
     262 -        def _on_list_item_activated(self, evt):
     263                  self._BTN_select.Enable(True)
     264                  if self.IsModal():
     265                          self.EndModal(wx.ID_OK)
     266                  else:
     267                          self.Close()
     268          #--------------------------------------------------------
     269 -        def _on_new_patient_button_pressed(self, event):
     270                  event.Skip()
     271                  success = create_new_person(activate = True)
     272                  if success:
     273                          self.person = gmPerson.gmCurrentPatient()
     274                  else:
     275                          self.person = None
     276                  if self.IsModal():
     277                          self.EndModal(wx.ID_CANCEL)
     278                  else:
     279                          self.Close()
     280
     281  #============================================================
     282  from Gnumed.wxGladeWidgets import wxgSelectPersonDTOFromListDlg
     283
     284 -class cSelectPersonDTOFromListDlg(wxgSelectPersonDTOFromListDlg.wxgSelectPersonDTOFromListDlg):
     285
     286 -        def __init__(self, *args, **kwargs):
     287                  wxgSelectPersonDTOFromListDlg.wxgSelectPersonDTOFromListDlg.__init__(self, *args, **kwargs)
     288
     289                  self.__cols = [
     290                          _('Source'),
     291                          _('Lastname'),
     292                          _('Firstname'),
     293                          _('DOB'),
     294                          _('Gender')
     295                  ]
     296                  self.__init_ui()
     297          #--------------------------------------------------------
     298 -        def __init_ui(self):
     299                  for col in range(len(self.__cols)):
     300                          self._LCTRL_persons.InsertColumn(col, self.__cols[col])
     301          #--------------------------------------------------------
     302 -        def set_dtos(self, dtos=None):
     303                  self._LCTRL_persons.DeleteAllItems()
     304
     305                  pos = len(dtos) + 1
     306                  if pos == 1:
     307                          return False
     308
     309                  for rec in dtos:
     310                          row_num = self._LCTRL_persons.InsertItem(pos, label = rec['source'])
     311                          dto = rec['dto']
     312                          self._LCTRL_persons.SetItem(index = row_num, column = 1, label = dto.lastnames)
     313                          self._LCTRL_persons.SetItem(index = row_num, column = 2, label = dto.firstnames)
     314                          if dto.dob is None:
     315                                  self._LCTRL_persons.SetItem(index = row_num, column = 3, label = '')
     316                          else:
     317                                  if dto.dob_is_estimated:
     318                                          self._LCTRL_persons.SetItem(index = row_num, column = 3, label = gmTools.u_almost_equal_to + gmDateTime.pydt_strftime(dto.dob, '%Y %b %d'))
     319                                  else:
     320                                          self._LCTRL_persons.SetItem(index = row_num, column = 3, label = gmDateTime.pydt_strftime(dto.dob, '%Y %b %d'))
     321                          self._LCTRL_persons.SetItem(index = row_num, column = 4, label = gmTools.coalesce(dto.gender, ''))
     322
     323                  for col in range(len(self.__cols)):
     324                          self._LCTRL_persons.SetColumnWidth(col, wx.LIST_AUTOSIZE)
     325
     326                  self._BTN_select.Enable(False)
     327                  self._LCTRL_persons.SetFocus()
     328                  self._LCTRL_persons.Select(0)
     329
     330                  self._LCTRL_persons.set_data(data=dtos)
     331          #--------------------------------------------------------
     332 -        def get_selected_dto(self):
     333                  return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
     334          #--------------------------------------------------------
     335          # event handlers
     336          #--------------------------------------------------------
     337 -        def _on_list_item_selected(self, evt):
     338                  self._BTN_select.Enable(True)
     339                  return
     340          #--------------------------------------------------------
     341 -        def _on_list_item_activated(self, evt):
     342                  self._BTN_select.Enable(True)
     343                  if self.IsModal():
     344                          self.EndModal(wx.ID_OK)
     345                  else:
     346                          self.Close()
     347
     348  #============================================================
     349 -def load_persons_from_ca_msva():
     350
     351          group = 'CA Medical Manager MSVA'
     352
     353          src_order = [
     354                  ('explicit', 'append'),
     355                  ('workbase', 'append'),
     356                  ('local', 'append'),
     357                  ('user', 'append'),
     358                  ('system', 'append')
     359          ]
     360          msva_files = _cfg.get (
     361                  group = group,
     362                  option = 'filename',
     363                  source_order = src_order
     364          )
     365          if msva_files is None:
     366                  return []
     367
     368          dtos = []
     369          for msva_file in msva_files:
     370                  try:
     371                          # FIXME: potentially return several persons per file
     372                          msva_dtos = gmCA_MSVA.read_persons_from_msva_file(filename = msva_file)
     373                  except Exception:
     374  #                       gmGuiHelpers.gm_show_error (
     375  #                               _(
     376  #                               'Cannot load patient from Medical Manager MSVA file\n\n'
     377  #                               ' [%s]'
     378  #                               ) % msva_file,
     379  #                               _('Activating MSVA patient')
     380  #                       )
     381                          _log.exception('cannot read patient from MSVA file [%s]' % msva_file)
     382                          continue
     383
     384                  dtos.extend([ {'dto': dto, 'source': dto.source} for dto in msva_dtos ])
     385                  #dtos.extend([ {'dto': dto} for dto in msva_dtos ])
     386
     387          return dtos
     388
     389  #============================================================
     390
     391 -def load_persons_from_xdt():
     392
     393          bdt_files = []
     394
     395          # some can be auto-detected
     396          # MCS/Isynet: $DRIVE:\Winacs\TEMP\BDTxx.tmp where xx is the workplace
     397          candidates = []
     398          drives = 'cdefghijklmnopqrstuvwxyz'
     399          for drive in drives:
     400                  candidate = drive + ':\Winacs\TEMP\BDT*.tmp'
     401                  candidates.extend(glob.glob(candidate))
     402          for candidate in candidates:
     403                  path, filename = os.path.split(candidate)
     404                  # FIXME: add encoding !
     405                  bdt_files.append({'file': candidate, 'source': 'MCS/Isynet %s' % filename[-6:-4]})
     406
     407          # some need to be configured
     408          # aggregate sources
     409          src_order = [
     410                  ('explicit', 'return'),
     411                  ('workbase', 'append'),
     412                  ('local', 'append'),
     413                  ('user', 'append'),
     414                  ('system', 'append')
     415          ]
     416          xdt_profiles = _cfg.get (
     417                  group = 'workplace',
     418                  option = 'XDT profiles',
     419                  source_order = src_order
     420          )
     421          if xdt_profiles is None:
     422                  return []
     423
     424          # first come first serve
     425          src_order = [
     426                  ('explicit', 'return'),
     427                  ('workbase', 'return'),
     428                  ('local', 'return'),
     429                  ('user', 'return'),
     430                  ('system', 'return')
     431          ]
     432          for profile in xdt_profiles:
     433                  name = _cfg.get (
     434                          group = 'XDT profile %s' % profile,
     435                          option = 'filename',
     436                          source_order = src_order
     437                  )
     438                  if name is None:
     439                          _log.error('XDT profile [%s] does not define a <filename>' % profile)
     440                          continue
     441                  encoding = _cfg.get (
     442                          group = 'XDT profile %s' % profile,
     443                          option = 'encoding',
     444                          source_order = src_order
     445                  )
     446                  if encoding is None:
     447                          _log.warning('xDT source profile [%s] does not specify an <encoding> for BDT file [%s]' % (profile, name))
     448                  source = _cfg.get (
     449                          group = 'XDT profile %s' % profile,
     450                          option = 'source',
     451                          source_order = src_order
     452                  )
     453                  dob_format = _cfg.get (
     454                          group = 'XDT profile %s' % profile,
     455                          option = 'DOB format',
     456                          source_order = src_order
     457                  )
     458                  if dob_format is None:
     459                          _log.warning('XDT profile [%s] does not define a date of birth format in <DOB format>' % profile)
     460                  bdt_files.append({'file': name, 'source': source, 'encoding': encoding, 'dob_format': dob_format})
     461
     462          dtos = []
     463          for bdt_file in bdt_files:
     464                  try:
     465                          # FIXME: potentially return several persons per file
     466                          dto = gmPerson.get_person_from_xdt (
     467                                  filename = bdt_file['file'],
     468                                  encoding = bdt_file['encoding'],
     469                                  dob_format = bdt_file['dob_format']
     470                          )
     471
     472                  except IOError:
     473                          gmGuiHelpers.gm_show_info (
     474                                  _(
     475                                  'Cannot access BDT file\n\n'
     476                                  ' [%s]\n\n'
     477                                  'to import patient.\n\n'
     478                                  'Please check your configuration.'
     479                                  ) % bdt_file,
     480                                  _('Activating xDT patient')
     481                          )
     482                          _log.exception('cannot access xDT file [%s]' % bdt_file['file'])
     483                          continue
     484                  except Exception:
     485                          gmGuiHelpers.gm_show_error (
     486                                  _(
     487                                  'Cannot load patient from BDT file\n\n'
     488                                  ' [%s]'
     489                                  ) % bdt_file,
     490                                  _('Activating xDT patient')
     491                          )
     492                          _log.exception('cannot read patient from xDT file [%s]' % bdt_file['file'])
     493                          continue
     494
     495                  dtos.append({'dto': dto, 'source': gmTools.coalesce(bdt_file['source'], dto.source)})
     496
     497          return dtos
     498
     499  #============================================================
     500
     501 -def load_persons_from_pracsoft_au():
     502
     503          pracsoft_files = []
     504
     505          # try detecting PATIENTS.IN files
     506          candidates = []
     507          drives = 'cdefghijklmnopqrstuvwxyz'
     508          for drive in drives:
     509                  candidate = drive + ':\MDW2\PATIENTS.IN'
     510                  candidates.extend(glob.glob(candidate))
     511          for candidate in candidates:
     512                  drive, filename = os.path.splitdrive(candidate)
     513                  pracsoft_files.append({'file': candidate, 'source': 'PracSoft (AU): drive %s' % drive})
     514
     515          # add configured one(s)
     516          src_order = [
     517                  ('explicit', 'append'),
     518                  ('workbase', 'append'),
     519                  ('local', 'append'),
     520                  ('user', 'append'),
     521                  ('system', 'append')
     522          ]
     523          fnames = _cfg.get (
     524                  group = 'AU PracSoft PATIENTS.IN',
     525                  option = 'filename',
     526                  source_order = src_order
     527          )
     528
     529          src_order = [
     530                  ('explicit', 'return'),
     531                  ('user', 'return'),
     532                  ('system', 'return'),
     533                  ('local', 'return'),
     534                  ('workbase', 'return')
     535          ]
     536          source = _cfg.get (
     537                  group = 'AU PracSoft PATIENTS.IN',
     538                  option = 'source',
     539                  source_order = src_order
     540          )
     541
     542          if source is not None:
     543                  for fname in fnames:
     544                          fname = os.path.abspath(os.path.expanduser(fname))
     545                          if os.access(fname, os.R_OK):
     546                                  pracsoft_files.append({'file': os.path.expanduser(fname), 'source': source})
     547                          else:
     548                                  _log.error('cannot read [%s] in AU PracSoft profile' % fname)
     549
     550          # and parse them
     551          dtos = []
     552          for pracsoft_file in pracsoft_files:
     553                  try:
     554                          tmp = gmPerson.get_persons_from_pracsoft_file(filename = pracsoft_file['file'])
     555                  except Exception:
     556                          _log.exception('cannot parse PracSoft file [%s]' % pracsoft_file['file'])
     557                          continue
     558                  for dto in tmp:
     559                          dtos.append({'dto': dto, 'source': pracsoft_file['source']})
     560
     561          return dtos
     562
     563  #============================================================
     564 -def load_persons_from_kvks():
     565
     566          dbcfg = gmCfg.cCfgSQL()
     567          kvk_dir = os.path.abspath(os.path.expanduser(dbcfg.get2 (
     568                  option = 'DE.KVK.spool_dir',
     569                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     570                  bias = 'workplace',
     571                  default = '/var/spool/kvkd/'
     572                  #default = u'/home/ncq/gnumed/'
     573          )))
     574          dtos = []
     575          for dto in gmKVK.get_available_kvks_as_dtos(spool_dir = kvk_dir):
     576                  dtos.append({'dto': dto, 'source': 'KVK'})
     577  #       for dto in gmKVK.get_available_CCRdr_files_as_dtos(spool_dir = kvk_dir):
     578  #               dtos.append({'dto': dto, 'source': dto.source})
     579
     580          return dtos
     581
     582  #============================================================
     583 -def load_person_from_vcard_file():
     584
     585          wildcards = '|'.join ([
     586                  '%s (*.vcf)|*.vcf' % _('vcf files'),
     587                  '%s (*.VCF)|*.VCF' % _('VCF files'),
     588                  '%s (*)|*' % _('all files'),
     589                  '%s (*.*)|*.*' % _('all files (Windows)')
     590          ])
     591
     592          dlg = wx.FileDialog (
     593                  parent = wx.GetApp().GetTopWindow(),
     594                  message = _('Choose a vCard file:'),
     595                  defaultDir = os.path.join(gmTools.gmPaths().home_dir, 'gnumed'),
     596                  wildcard = wildcards,
     597                  style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
     598          )
     599          result = dlg.ShowModal()
     600          fname = dlg.GetPath()
     601          dlg.DestroyLater()
     602          if result == wx.ID_CANCEL:
     603                  return
     604
     605          from Gnumed.business import gmVCard
     606          dto = gmVCard.parse_vcard2dto(filename = fname)
     607          if dto is None:
     608                  gmDispatcher.send(signal='statustext', msg=_('[%s] does not seem to contain a vCard.') % fname)
     609                  return
     610
     611          idents = dto.get_candidate_identities(can_create = True)
     612          if len(idents) == 1:
     613                  ident = idents[0]
     614                  if not set_active_patient(patient = ident):
     615                          gmGuiHelpers.gm_show_info (_(
     616                                  'Cannot activate patient:\n\n'
     617                                  '%s %s (%s)\n'
     618                                  '%s'
     619                                  ) % (
     620                                          dto.firstnames, dto.lastnames, dto.gender, gmDateTime.pydt_strftime(dto.dob, '%Y %b %d')
     621                                  ),
     622                                  _('Activating external patient')
     623                          )
     624                  return
     625
     626          dlg = cSelectPersonFromListDlg(wx.GetApp().GetTopWindow(), -1)
     627          dlg.set_persons(persons = idents)
     628          result = dlg.ShowModal()
     629          ident = dlg.get_selected_person()
     630          dlg.DestroyLater()
     631          if result == wx.ID_CANCEL:
     632                  return
     633          if not set_active_patient(patient = ident):
     634                  gmGuiHelpers.gm_show_info (_(
     635                          'Cannot activate patient:\n\n'
     636                          '%s %s (%s)\n'
     637                          '%s'
     638                          ) % (
     639                                  dto.firstnames, dto.lastnames, dto.gender, gmDateTime.pydt_strftime(dto.dob, '%Y %b %d')
     640                          ),
     641                          _('Activating external patient')
     642                  )
     643
     644  #============================================================
     645 -def load_person_from_vcard_via_clipboard():
     646
     647          fname = gmGuiHelpers.clipboard2file()
     648          if fname in [None, False]:
     649                  gmGuiHelpers.gm_show_info (
     650                          info = _('No patient in clipboard.'),
     651                          title = _('Activating external patient')
     652                  )
     653                  return
     654
     655          from Gnumed.business import gmVCard
     656          dto = gmVCard.parse_vcard2dto(filename = fname)
     657          if dto is None:
     658                  gmDispatcher.send(signal='statustext', msg=_('Clipboard does not seem to contain a vCard.'))
     659                  return
     660
     661          idents = dto.get_candidate_identities(can_create = True)
     662          if len(idents) == 1:
     663                  ident = idents[0]
     664                  if not set_active_patient(patient = ident):
     665                          gmGuiHelpers.gm_show_info (_(
     666                                  'Cannot activate patient:\n\n'
     667                                  '%s %s (%s)\n'
     668                                  '%s'
     669                                  ) % (
     670                                          dto.firstnames, dto.lastnames, dto.gender, gmDateTime.pydt_strftime(dto.dob, '%Y %b %d')
     671                                  ),
     672                                  _('Activating external patient')
     673                          )
     674                  return
     675
     676          dlg = cSelectPersonFromListDlg(wx.GetApp().GetTopWindow(), -1)
     677          dlg.set_persons(persons = idents)
     678          result = dlg.ShowModal()
     679          ident = dlg.get_selected_person()
     680          dlg.DestroyLater()
     681          if result == wx.ID_CANCEL:
     682                  return
     683          if not set_active_patient(patient = ident):
     684                  gmGuiHelpers.gm_show_info (_(
     685                          'Cannot activate patient:\n\n'
     686                          '%s %s (%s)\n'
     687                          '%s'
     688                          ) % (
     689                                  dto.firstnames, dto.lastnames, dto.gender, gmDateTime.pydt_strftime(dto.dob, '%Y %b %d')
     690                          ),
     691                          _('Activating external patient')
     692                  )
     693
     694  #============================================================
     695 -def load_person_from_xml_linuxmednews_via_clipboard():
     696
     697          fname = gmGuiHelpers.clipboard2file()
     698          if fname in [None, False]:
     699                  gmGuiHelpers.gm_show_info (
     700                          info = _('No patient in clipboard.'),
     701                          title = _('Activating external patient')
     702                  )
     703                  return
     704
     705          from Gnumed.business import gmLinuxMedNewsXML
     706          dto = gmLinuxMedNewsXML.parse_xml_linuxmednews(filename = fname)
     707          if dto is None:
     708                  gmDispatcher.send(signal='statustext', msg=_('Clipboard does not seem to contain LinuxMedNews XML.'))
     709                  return
     710
     711          idents = dto.get_candidate_identities(can_create = True)
     712          if len(idents) == 1:
     713                  ident = idents[0]
     714                  if not set_active_patient(patient = ident):
     715                          gmGuiHelpers.gm_show_info (_(
     716                                  'Cannot activate patient:\n\n'
     717                                  '%s %s (%s)\n'
     718                                  '%s'
     719                                  ) % (
     720                                          dto.firstnames, dto.lastnames, dto.gender, gmDateTime.pydt_strftime(dto.dob, '%Y %b %d')
     721                                  ),
     722                                  _('Activating external patient')
     723                          )
     724                  return
     725
     726          dlg = cSelectPersonFromListDlg(wx.GetApp().GetTopWindow(), -1)
     727          dlg.set_persons(persons = idents)
     728          result = dlg.ShowModal()
     729          ident = dlg.get_selected_person()
     730          dlg.DestroyLater()
     731          if result == wx.ID_CANCEL:
     732                  return None
     733          if not set_active_patient(patient = ident):
     734                  gmGuiHelpers.gm_show_info (_(
     735                          'Cannot activate patient:\n\n'
     736                          '%s %s (%s)\n'
     737                          '%s'
     738                          ) % (
     739                                  dto.firstnames, dto.lastnames, dto.gender, gmDateTime.pydt_strftime(dto.dob, '%Y %b %d')
     740                          ),
     741                          _('Activating external patient')
     742                  )
     743
     744  #============================================================
     745 -def get_person_from_external_sources(parent=None, search_immediately=False, activate_immediately=False):
     746          """Load patient from external source.
     747
     748          - scan external sources for candidates
     749          - let user select source
     750            - if > 1 available: always
     751            - if only 1 available: depending on search_immediately
     752          - search for patients matching info from external source
     753          - if more than one match:
     754            - let user select patient
     755          - if no match:
     756            - create patient
     757          - activate patient
     758          """
     759          # get DTOs from interfaces
     760          dtos = []
     761          dtos.extend(load_persons_from_xdt())
     762          dtos.extend(load_persons_from_pracsoft_au())
     763          dtos.extend(load_persons_from_kvks())
     764          dtos.extend(load_persons_from_ca_msva())
     765
     766          # no external persons
     767          if len(dtos) == 0:
     768                  gmDispatcher.send(signal='statustext', msg=_('No patients found in external sources.'))
     769                  return None
     770
     771          # one external patient with DOB - already active ?
     772          if (len(dtos) == 1) and (dtos[0]['dto'].dob is not None):
     773                  dto = dtos[0]['dto']
     774                  # is it already the current patient ?
     775                  curr_pat = gmPerson.gmCurrentPatient()
     776                  if curr_pat.connected:
     777                          key_dto = dto.firstnames + dto.lastnames + dto.dob.strftime('%Y-%m-%d') + dto.gender
     778                          names = curr_pat.get_active_name()
     779                          key_pat = names['firstnames'] + names['lastnames'] + curr_pat.get_formatted_dob(format = '%Y-%m-%d') + curr_pat['gender']
     780                          _log.debug('current patient: %s' % key_pat)
     781                          _log.debug('dto patient    : %s' % key_dto)
     782                          if key_dto == key_pat:
     783                                  gmDispatcher.send(signal='statustext', msg=_('The only external patient is already active in GNUmed.'), beep=False)
     784                                  return None
     785
     786          # one external person - look for internal match immediately ?
     787          if (len(dtos) == 1) and search_immediately:
     788                  dto = dtos[0]['dto']
     789
     790          # several external persons
     791          else:
     792                  if parent is None:
     793                          parent = wx.GetApp().GetTopWindow()
     794                  dlg = cSelectPersonDTOFromListDlg(parent=parent, id=-1)
     795                  dlg.set_dtos(dtos=dtos)
     796                  result = dlg.ShowModal()
     797                  if result == wx.ID_CANCEL:
     798                          return None
     799                  dto = dlg.get_selected_dto()['dto']
     800                  dlg.DestroyLater()
     801
     802          # search
     803          idents = dto.get_candidate_identities(can_create=True)
     804          if idents is None:
     805                  gmGuiHelpers.gm_show_info (_(
     806                          'Cannot create new patient:\n\n'
     807                          ' [%s %s (%s), %s]'
     808                          ) % (
     809                                  dto.firstnames, dto.lastnames, dto.gender, gmDateTime.pydt_strftime(dto.dob, '%Y %b %d')
     810                          ),
     811                          _('Activating external patient')
     812                  )
     813                  return None
     814
     815          if len(idents) == 1:
     816                  ident = idents[0]
     817
     818          if len(idents) > 1:
     819                  if parent is None:
     820                          parent = wx.GetApp().GetTopWindow()
     821                  dlg = cSelectPersonFromListDlg(parent=parent, id=-1)
     822                  dlg.set_persons(persons=idents)
     823                  result = dlg.ShowModal()
     824                  ident = dlg.get_selected_person()
     825                  dlg.DestroyLater()
     826                  if result == wx.ID_CANCEL:
     827                          return None
     828
     829          if activate_immediately:
     830                  if not set_active_patient(patient = ident):
     831                          gmGuiHelpers.gm_show_info (_(
     832                                  'Cannot activate patient:\n\n'
     833                                  '%s %s (%s)\n'
     834                                  '%s'
     835                                  ) % (
     836                                          dto.firstnames, dto.lastnames, dto.gender, gmDateTime.pydt_strftime(dto.dob, '%Y %b %d')
     837                                  ),
     838                                  _('Activating external patient')
     839                          )
     840                          return None
     841
     842          dto.import_extra_data(identity = ident)
     843          dto.delete_from_source()
     844
     845          return ident
     846
     847  #============================================================
     848 -class cPersonSearchCtrl(wx.TextCtrl):
     849          """Widget for smart search for persons."""
     850
     851 -        def __init__(self, *args, **kwargs):
     852
     853                  try:
     854                          kwargs['style'] = kwargs['style'] | wx.TE_PROCESS_ENTER
     855                  except KeyError:
     856                          kwargs['style'] = wx.TE_PROCESS_ENTER
     857
     858                  # need to explicitly process ENTER events to avoid
     859                  # them being handed over to the next control
     860                  wx.TextCtrl.__init__(self, *args, **kwargs)
     861
     862                  self.person = None
     863
     864                  self._tt_search_hints = _(
     865                          'To search for a person, type any of:                   \n'
     866                          '\n'
     867                          ' - fragment(s) of last and/or first name(s)\n'
     868                          " - GNUmed ID of person (can start with '#')\n"
     869                          ' - any external ID of person\n'
     870                          " - date of birth (can start with '$' or '*')\n"
     871                          '\n'
     872                          'and hit <ENTER>.\n'
     873                          '\n'
     874                          'Shortcuts:\n'
     875                          ' <F2>\n'
     876                          '  - scan external sources for persons\n'
     877                          ' <CURSOR-UP>\n'
     878                          '  - recall most recently used search term\n'
     879                          ' <CURSOR-DOWN>\n'
     880                          '  - list 10 most recently found persons\n'
     881                  )
     882                  self.SetToolTip(self._tt_search_hints)
     883
     884                  # FIXME: set query generator
     885                  self.__person_searcher = gmPersonSearch.cPatientSearcher_SQL()
     886
     887                  self._prev_search_term = None
     888                  self.__prev_idents = []
     889                  self._lclick_count = 0
     890
     891                  self.__register_events()
     892          #--------------------------------------------------------
     893          # properties
     894          #--------------------------------------------------------
     895 -        def _set_person(self, person):
     896                  self.__person = person
     897                  wx.CallAfter(self._display_name)
     898
     899 -        def _get_person(self):
     900                  return self.__person
     901
     902          person = property(_get_person, _set_person)
     903          #--------------------------------------------------------
     904          # utility methods
     905          #--------------------------------------------------------
     906 -        def _display_name(self):
     907                  name = ''
     908
     909                  if self.person is not None:
     910                          name = self.person['description']
     911
     912                  self.SetValue(name)
     913          #--------------------------------------------------------
     914 -        def _remember_ident(self, ident=None):
     915
     916                  if not isinstance(ident, gmPerson.cPerson):
     917                          return False
     918
     919                  # only unique identities
     920                  for known_ident in self.__prev_idents:
     921                          if known_ident['pk_identity'] == ident['pk_identity']:
     922                                  return True
     923
     924                  self.__prev_idents.append(ident)
     925
     926                  # and only 10 of them
     927                  if len(self.__prev_idents) > 10:
     928                          self.__prev_idents.pop(0)
     929
     930                  return True
     931          #--------------------------------------------------------
     932          # event handling
     933          #--------------------------------------------------------
     934 -        def __register_events(self):
     935                  self.Bind(wx.EVT_CHAR, self.__on_char)
     936                  self.Bind(wx.EVT_SET_FOCUS, self._on_get_focus)
     937                  self.Bind(wx.EVT_KILL_FOCUS, self._on_loose_focus)
     938                  self.Bind(wx.EVT_TEXT_ENTER, self.__on_enter)
     939
     940          #--------------------------------------------------------
     941 -        def _on_get_focus(self, evt):
     942                  """upon tabbing in
     943
     944                  - select all text in the field so that the next
     945                    character typed will delete it
     946                  """
     947                  wx.CallAfter(self.SetSelection, -1, -1)
     948                  evt.Skip()
     949
     950          #--------------------------------------------------------
     951 -        def _on_loose_focus(self, evt):
     952                  # - redraw the currently active name upon losing focus
     953                  #
     954                  # if we use wx.EVT_KILL_FOCUS we will also receive this event
     955                  # when closing our application or loosing focus to another
     956                  # application which is NOT what we intend to achieve,
     957                  # however, this is the least ugly way of doing this due to
     958                  # certain vagaries of wxPython (see the Wiki)
     959                  evt.Skip()
     960                  wx.CallAfter(self.__on_lost_focus)
     961          #--------------------------------------------------------
     962 -        def __on_lost_focus(self):
     963                  # just for good measure
     964                  self.SetSelection(0, 0)
     965                  self._display_name()
     966                  self._remember_ident(self.person)
     967          #--------------------------------------------------------
     968 -        def __on_char(self, evt):
     969                  self._on_char(evt)
     970
     971 -        def _on_char(self, evt):
     972                  """True: patient was selected.
     973                     False: no patient was selected.
     974                  """
     975                  keycode = evt.GetKeyCode()
     976
     977                  # list of previously active patients
     978                  if keycode == wx.WXK_DOWN:
     979                          evt.Skip()
     980                          if len(self.__prev_idents) == 0:
     981                                  return False
     982
     983                          dlg = cSelectPersonFromListDlg(wx.GetTopLevelParent(self), -1)
     984                          dlg.set_persons(persons = self.__prev_idents)
     985                          result = dlg.ShowModal()
     986                          if result == wx.ID_OK:
     987                                  wx.BeginBusyCursor()
     988                                  self.person = dlg.get_selected_person()
     989                                  dlg.DestroyLater()
     990                                  wx.EndBusyCursor()
     991                                  return True
     992
     993                          dlg.DestroyLater()
     994                          return False
     995
     996                  # recall previous search fragment
     997                  if keycode == wx.WXK_UP:
     998                          evt.Skip()
     999                          # FIXME: cycling through previous fragments
    1000                          if self._prev_search_term is not None:
    1001                                  self.SetValue(self._prev_search_term)
    1002                          return False
    1003
    1004                  # invoke external patient sources
    1005                  if keycode == wx.WXK_F2:
    1006                          evt.Skip()
    1007                          dbcfg = gmCfg.cCfgSQL()
    1008                          search_immediately = bool(dbcfg.get2 (
    1009                                  option = 'patient_search.external_sources.immediately_search_if_single_source',
    1010                                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1011                                  bias = 'user',
    1012                                  default = 0
    1013                          ))
    1014                          p = get_person_from_external_sources (
    1015                                  parent = wx.GetTopLevelParent(self),
    1016                                  search_immediately = search_immediately
    1017                          )
    1018                          if p is not None:
    1019                                  self.person = p
    1020                                  return True
    1021                          return False
    1022
    1023                  # FIXME: invoke add new person
    1024                  # FIXME: add popup menu apart from system one
    1025
    1026                  evt.Skip()
    1027          #--------------------------------------------------------
    1028 -        def __on_enter(self, evt):
    1029                  """This is called from the ENTER handler."""
    1030
    1031                  # ENTER but no search term ?
    1032                  curr_search_term = self.GetValue().strip()
    1033                  if curr_search_term == '':
    1034                          return None
    1035
    1036                  # same person anyways ?
    1037                  if self.person is not None:
    1038                          if curr_search_term == self.person['description']:
    1039                                  return None
    1040
    1041                  # remember search fragment
    1042                  if self.IsModified():
    1043                          self._prev_search_term = curr_search_term
    1044
    1045                  self._on_enter(search_term = curr_search_term)
    1046          #--------------------------------------------------------
    1047 -        def _on_enter(self, search_term=None):
    1048                  """This can be overridden in child classes."""
    1049
    1050                  wx.BeginBusyCursor()
    1051
    1052                  # get list of matching ids
    1053                  idents = self.__person_searcher.get_identities(search_term)
    1054
    1055                  if idents is None:
    1056                          wx.EndBusyCursor()
    1057                          gmGuiHelpers.gm_show_info (
    1058                                  _('Error searching for matching persons.\n\n'
    1059                                    'Search term: "%s"'
    1060                                  ) % search_term,
    1061                                  _('selecting person')
    1062                          )
    1063                          return None
    1064
    1065                  _log.info("%s matching person(s) found", len(idents))
    1066
    1067                  if len(idents) == 0:
    1068                          wx.EndBusyCursor()
    1069
    1070                          dlg = gmGuiHelpers.c2ButtonQuestionDlg (
    1071                                  wx.GetTopLevelParent(self),
    1072                                  -1,
    1073                                  caption = _('Selecting patient'),
    1074                                  question = _(
    1075                                          'Cannot find any matching patients for the search term\n\n'
    1076                                          ' "%s"\n\n'
    1077                                          'You may want to try a shorter search term.\n'
    1078                                  ) % search_term,
    1079                                  button_defs = [
    1080                                          {'label': _('Go back'), 'tooltip': _('Go back and search again.'), 'default': True},
    1081                                          {'label': _('Create new'), 'tooltip': _('Create new patient.')}
    1082                                  ]
    1083                          )
    1084                          if dlg.ShowModal() != wx.ID_NO:
    1085                                  return
    1086
    1087                          success = create_new_person(activate = True)
    1088                          if success:
    1089                                  self.person = gmPerson.gmCurrentPatient()
    1090                          else:
    1091                                  self.person = None
    1092                          return None
    1093
    1094                  # only one matching identity
    1095                  if len(idents) == 1:
    1096                          self.person = idents[0]
    1097                          wx.EndBusyCursor()
    1098                          return None
    1099
    1100                  # more than one matching identity: let user select from pick list
    1101                  dlg = cSelectPersonFromListDlg(parent=wx.GetTopLevelParent(self), id=-1)
    1102                  dlg.set_persons(persons=idents)
    1103                  wx.EndBusyCursor()
    1104                  result = dlg.ShowModal()
    1105                  if result == wx.ID_CANCEL:
    1106                          dlg.DestroyLater()
    1107                          return None
    1108
    1109                  wx.BeginBusyCursor()
    1110                  self.person = dlg.get_selected_person()
    1111                  dlg.DestroyLater()
    1112                  wx.EndBusyCursor()
    1113
    1114                  return None
    1115
    1116  #============================================================
    1117 -def _verify_staff_chart_access(patient=None):
    1118
    1119          if patient is None:
    1120                  return True
    1121
    1122          # staff ?
    1123          if patient.ID not in [ s['pk_identity'] for s in gmStaff.get_staff_list() ]:
    1124                  return True
    1125
    1126          curr_prov = gmStaff.gmCurrentProvider()
    1127
    1128          # can view my own chart
    1129          if patient.ID == curr_prov['pk_identity']:
    1130                  return True
    1131
    1132          # primary provider can view patient
    1133          if patient['pk_primary_provider'] == curr_prov['pk_staff']:
    1134                  return True
    1135
    1136          proceed = gmGuiHelpers.gm_show_question (
    1137                  aTitle = _('Privacy check'),
    1138                  aMessage = _(
    1139                          'You have selected the chart of a member of staff,\n'
    1140                          'for whom privacy is especially important:\n'
    1141                          '\n'
    1142                          '  %s, %s\n'
    1143                          '\n'
    1144                          'This may be OK depending on circumstances.\n'
    1145                          '\n'
    1146                          'Please be aware that accessing patient charts is\n'
    1147                          'logged and that %s%s will be\n'
    1148                          'notified of the access if you choose to proceed.\n'
    1149                          '\n'
    1150                          'Are you sure you want to draw this chart ?'
    1151                  ) % (
    1152                          patient.get_description_gender(),
    1153                          patient.get_formatted_dob(),
    1154                          gmTools.coalesce(patient['title'], '', '%s '),
    1155                          patient['lastnames']
    1156                  )
    1157          )
    1158
    1159          if proceed:
    1160                  prov = '%s (%s%s %s)' % (
    1161                          curr_prov['short_alias'],
    1162                          gmTools.coalesce(curr_prov['title'], '', '%s '),
    1163                          curr_prov['firstnames'],
    1164                          curr_prov['lastnames']
    1165                  )
    1166                  pat = '%s%s %s' % (
    1167                          gmTools.coalesce(patient['title'], '', '%s '),
    1168                          patient['firstnames'],
    1169                          patient['lastnames']
    1170                  )
    1171                  # notify the staff member
    1172                  gmProviderInbox.create_inbox_message (
    1173                          staff = patient.staff_id,
    1174                          message_type = _('Privacy notice'),
    1175                          message_category = 'administrative',
    1176                          subject = _('%s: Your chart has been accessed by %s.') % (pat, prov),
    1177                          patient = patient.ID
    1178                  )
    1179                  # notify /me about the staff member notification
    1180                  gmProviderInbox.create_inbox_message (
    1181                          staff = curr_prov['pk_staff'],
    1182                          message_type = _('Privacy notice'),
    1183                          message_category = 'administrative',
    1184                          subject = _('%s: Staff member %s has been notified of your chart access.') % (prov, pat)
    1185                  )
    1186
    1187          return proceed
    1188
    1189  #------------------------------------------------------------
    1190 -def _check_has_dob(patient=None):
    1191
    1192          if patient is None:
    1193                  return
    1194
    1195          if patient['dob'] is None:
    1196                  gmGuiHelpers.gm_show_warning (
    1197                          aTitle = _('Checking date of birth'),
    1198                          aMessage = _(
    1199                                  '\n'
    1200                                  ' %s\n'
    1201                                  '\n'
    1202                                  'The date of birth for this patient is not known !\n'
    1203                                  '\n'
    1204                                  'You can proceed to work on the patient but\n'
    1205                                  'GNUmed will be unable to assist you with\n'
    1206                                  'age-related decisions.\n'
    1207                          ) % patient['description_gender']
    1208                  )
    1209
    1210  #------------------------------------------------------------
    1211 -def _check_birthday(patient=None):
    1212
    1213          if patient['dob'] is None:
    1214                  return
    1215
    1216          dbcfg = gmCfg.cCfgSQL()
    1217          dob_distance = dbcfg.get2 (
    1218                  option = 'patient_search.dob_warn_interval',
    1219                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1220                  bias = 'user',
    1221                  default = '1 week'
    1222          )
    1223
    1224          if not patient.dob_in_range(dob_distance, dob_distance):
    1225                  return
    1226
    1227          now = gmDateTime.pydt_now_here()
    1228          enc = gmI18N.get_encoding()
    1229          msg = _('%(pat)s turns %(age)s on %(month)s %(day)s ! (today is %(month_now)s %(day_now)s)') % {
    1230                  'pat': patient.get_description_gender(),
    1231                  'age': patient.get_medical_age().strip('y'),
    1232                  'month': patient.get_formatted_dob(format = '%B'),
    1233                  'day': patient.get_formatted_dob(format = '%d'),
    1234                  'month_now': gmDateTime.pydt_strftime(now, '%B', gmDateTime.acc_months),
    1235                  'day_now': gmDateTime.pydt_strftime(now, '%d', gmDateTime.acc_days)
    1236          }
    1237          gmDispatcher.send(signal = 'statustext', msg = msg)
    1238
    1239  #------------------------------------------------------------
    1240 -def _do_after_setting_active_patient(patient=None):
    1241          _check_has_dob(patient = patient)
    1242          _check_birthday(patient = patient)
    1243
    1244  #------------------------------------------------------------
    1245 -def set_active_patient(patient=None, forced_reload=False):
    1246
    1247          # already active ?
    1248          if isinstance(patient, gmPerson.gmCurrentPatient):
    1249                  return True
    1250
    1251          if isinstance(patient, gmPerson.cPatient):
    1252                  if patient['is_deleted']:
    1253                          _log.error('patient is disabled, will not use as active patient: %s', patient)
    1254                          return False
    1255          elif isinstance(patient, gmPerson.cPerson):
    1256                  if patient['is_deleted']:
    1257                          _log.error('patient is disabled, will not use as active patient: %s', patient)
    1258                          return False
    1259                  patient = patient.as_patient
    1260          elif patient == -1:
    1261                  pass
    1262          else:
    1263                  # maybe integer ?
    1264                  success, pk = gmTools.input2int(initial = patient, minval = 1)
    1265                  if not success:
    1266                          raise ValueError('<patient> must be either -1, >0, or a cPatient, cPerson or gmCurrentPatient instance, is: %s' % patient)
    1267                  # but also valid patient ID ?
    1268                  try:
    1269                          patient = gmPerson.cPatient(aPK_obj = pk)
    1270                  except Exception:
    1271                          _log.exception('error changing active patient to [%s]' % patient)
    1272                          return False
    1273
    1274          if not _verify_staff_chart_access(patient = patient):
    1275                  return False
    1276
    1277          success = gmPerson.set_active_patient(patient = patient, forced_reload = forced_reload)
    1278
    1279          if not success:
    1280                  return False
    1281
    1282          wx.CallAfter(_do_after_setting_active_patient, patient)
    1283          return True
    1284
    1285  #------------------------------------------------------------
    1286 -class cActivePatientSelector(cPersonSearchCtrl):
    1287
    1288 -        def __init__ (self, *args, **kwargs):
    1289
    1290                  cPersonSearchCtrl.__init__(self, *args, **kwargs)
    1291
    1292                  # get configuration
    1293                  cfg = gmCfg.cCfgSQL()
    1294
    1295                  self.__always_dismiss_on_search = bool (
    1296                          cfg.get2 (
    1297                                  option = 'patient_search.always_dismiss_previous_patient',
    1298                                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1299                                  bias = 'user',
    1300                                  default = 0
    1301                          )
    1302                  )
    1303
    1304                  self.__always_reload_after_search = bool (
    1305                          cfg.get2 (
    1306                                  option = 'patient_search.always_reload_new_patient',
    1307                                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1308                                  bias = 'user',
    1309                                  default = 0
    1310                          )
    1311                  )
    1312
    1313                  self.__register_events()
    1314          #--------------------------------------------------------
    1315          # utility methods
    1316          #--------------------------------------------------------
    1317 -        def _display_name(self):
    1318
    1319                  curr_pat = gmPerson.gmCurrentPatient()
    1320                  if curr_pat.connected:
    1321                          name = curr_pat['description']
    1322                          if curr_pat.locked:
    1323                                  name = _('%(name)s (locked)') % {'name': name}
    1324                  else:
    1325                          if curr_pat.locked:
    1326                                  name = _('<patient search locked>')
    1327                          else:
    1328                                  name = _('<type here to search patient>')
    1329
    1330                  self.SetValue(name)
    1331
    1332                  # adjust tooltip
    1333                  if self.person is None:
    1334                          self.SetToolTip(self._tt_search_hints)
    1335                          return
    1336
    1337                  if (self.person['emergency_contact'] is None) and (self.person['comment'] is None):
    1338                          separator = ''
    1339                  else:
    1340                          separator = '%s\n' % (gmTools.u_box_horiz_single * 40)
    1341
    1342                  tt = '%s%s%s%s' % (
    1343                          gmTools.coalesce(self.person['emergency_contact'], '', '%s\n %%s\n' % _('In case of emergency contact:')),
    1344                          gmTools.coalesce(self.person['comment'], '', '\n%s\n'),
    1345                          separator,
    1346                          self._tt_search_hints
    1347                  )
    1348                  self.SetToolTip(tt)
    1349          #--------------------------------------------------------
    1350 -        def _set_person_as_active_patient(self, pat):
    1351                  if not set_active_patient(patient=pat, forced_reload = self.__always_reload_after_search):
    1352                          _log.error('cannot change active patient')
    1353                          return None
    1354
    1355                  self._remember_ident(pat)
    1356
    1357                  return True
    1358          #--------------------------------------------------------
    1359          # event handling
    1360          #--------------------------------------------------------
    1361 -        def __register_events(self):
    1362                  # client internal signals
    1363                  gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
    1364                  gmDispatcher.connect(signal = 'dem.names_mod_db', receiver = self._on_name_identity_change)
    1365                  gmDispatcher.connect(signal = 'dem.identity_mod_db', receiver = self._on_name_identity_change)
    1366
    1367                  gmDispatcher.connect(signal = 'patient_locked', receiver = self._on_post_patient_selection)
    1368                  gmDispatcher.connect(signal = 'patient_unlocked', receiver = self._on_post_patient_selection)
    1369          #----------------------------------------------
    1370 -        def _on_name_identity_change(self, **kwargs):
    1371                  wx.CallAfter(self._display_name)
    1372          #----------------------------------------------
    1373 -        def _on_post_patient_selection(self, **kwargs):
    1374                  if gmPerson.gmCurrentPatient().connected:
    1375                          self.person = gmPerson.gmCurrentPatient().patient
    1376                  else:
    1377                          self.person = None
    1378          #----------------------------------------------
    1379 -        def _on_enter(self, search_term = None):
    1380
    1381                  if self.__always_dismiss_on_search:
    1382                          _log.warning("dismissing patient before patient search")
    1383                          self._set_person_as_active_patient(-1)
    1384
    1385                  super(self.__class__, self)._on_enter(search_term=search_term)
    1386
    1387                  if self.person is None:
    1388                          return
    1389
    1390                  self._set_person_as_active_patient(self.person)
    1391          #----------------------------------------------
    1392 -        def _on_char(self, evt):
    1393
    1394                  success = super(self.__class__, self)._on_char(evt)
    1395                  if success:
    1396                          self._set_person_as_active_patient(self.person)
    1397
    1398  #============================================================
    1399  # main
    1400  #------------------------------------------------------------
    1401  if __name__ == "__main__":
    1402
    1403          if len(sys.argv) > 1:
    1404                  if sys.argv[1] == 'test':
    1405                          gmI18N.activate_locale()
    1406                          gmI18N.install_domain()
    1407
    1408                          app = wx.PyWidgetTester(size = (200, 40))
    1409  #                       app.SetWidget(cSelectPersonFromListDlg, -1)
    1410                          app.SetWidget(cPersonSearchCtrl, -1)
    1411  #                       app.SetWidget(cActivePatientSelector, -1)
    1412                          app.MainLoop()
    1413
    1414  #============================================================
    1415  # docs
    1416  #------------------------------------------------------------
    1417  # functionality
    1418  # -------------
    1419  # - hitting ENTER on non-empty field (and more than threshold chars)
    1420  #   - start search
    1421  #   - display results in a list, prefixed with numbers
    1422  #   - last name
    1423  #   - first name
    1424  #   - gender
    1425  #   - age
    1426  #   - city + street (no ZIP, no number)
    1427  #   - last visit (highlighted if within a certain interval)
    1428  #   - arbitrary marker (e.g. office attendance this quartal, missing KVK, appointments, due dates)
    1429  #   - if none found -> go to entry of new patient
    1430  #   - scrolling in this list
    1431  #   - ENTER selects patient
    1432  #   - ESC cancels selection
    1433  #   - number selects patient
    1434  #
    1435  # - hitting cursor-up/-down
    1436  #   - cycle through history of last 10 search fragments
    1437  #
    1438  # - hitting alt-L = List, alt-P = previous
    1439  #   - show list of previous ten patients prefixed with numbers
    1440  #   - scrolling in list
    1441  #   - ENTER selects patient
    1442  #   - ESC cancels selection
    1443  #   - number selects patient
    1444  #
    1445  # - hitting ALT-N
    1446  #   - immediately goes to entry of new patient
    1447  #
    1448  # - hitting cursor-right in a patient selection list
    1449  #   - pops up more detail about the patient
    1450  #   - ESC/cursor-left goes back to list
    1451  #
    1452  # - hitting TAB
    1453  #   - makes sure the currently active patient is displayed
    1454
    1455  #------------------------------------------------------------
    1456  # samples
    1457  # -------
    1458  # working:
    1459  #  Ian Haywood
    1460  #  Haywood Ian
    1461  #  Haywood
    1462  #  Amador Jimenez (yes, two last names but no hyphen: Spain, for example)
    1463  #  Ian Haywood 19/12/1977
    1464  #  19/12/1977
    1465  #  19-12-1977
    1466  #  19.12.1977
    1467  #  19771219
    1468  #  $dob
    1469  #  *dob
    1470  #  #ID
    1471  #  ID
    1472  #  HIlbert, karsten
    1473  #  karsten, hilbert
    1474  #  kars, hilb
    1475  #
    1476  # non-working:
    1477  #  Haywood, Ian <40
    1478  #  ?, Ian 1977
    1479  #  Ian Haywood, 19/12/77
    1480  # "hilb; karsten, 23.10.74"
    1481
    1482  #------------------------------------------------------------
    1483  # notes
    1484  # -----
    1485  # >> 3. There are countries in which people have more than one
    1486  # >> (significant) lastname (spanish-speaking countries are one case :), some
    1487  # >> asian countries might be another one).
    1488  # -> we need per-country query generators ...
    1489
    1490  # search case sensitive by default, switch to insensitive if not found ?
    1491
    1492  # accent insensitive search:
    1493  #  select * from * where to_ascii(column, 'encoding') like '%test%';
    1494  # may not work with Unicode
    1495
    1496  # phrase wheel is most likely too slow
    1497
    1498  # extend search fragment history
    1499
    1500  # ask user whether to send off level 3 queries - or thread them
    1501
    1502  # we don't expect patient IDs in complicated patterns, hence any digits signify a date
    1503
    1504  # FIXME: make list window fit list size ...
    1505
    1506  # clear search field upon get-focus ?
    1507
    1508  # F1 -> context help with hotkey listing
    1509
    1510  # th -> th|t
    1511  # v/f/ph -> f|v|ph
    1512  # maybe don't do umlaut translation in the first 2-3 letters
    1513  # such that not to defeat index use for the first level query ?
    1514
    1515  # user defined function key to start search
    1516