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

# Source Code for Module Gnumed.wxpython.gmDemographicsWidgets

       1  """Widgets dealing with patient demographics."""
       2  #============================================================
       3  __author__ = "R.Terry, SJ Tan, I Haywood, Carlos Moro <cfmoro1976@yahoo.es>"
       4  __license__ = 'GPL v2 or later (details at http://www.gnu.org)'
       5
       6  # standard library
       7  import sys
       8  import sys
       9  import codecs
      10  import re as regex
      11  import logging
      12  import os
      13
      14
      15  import wx
      16  import wx.wizard
      17  import wx.lib.imagebrowser as wx_imagebrowser
      18  import wx.lib.statbmp as wx_genstatbmp
      19
      20
      21  # GNUmed specific
      22  if __name__ == '__main__':
      23          sys.path.insert(0, '../../')
      24  from Gnumed.pycommon import gmDispatcher
      25  from Gnumed.pycommon import gmI18N
      26  from Gnumed.pycommon import gmMatchProvider
      27  from Gnumed.pycommon import gmPG2
      28  from Gnumed.pycommon import gmTools
      29  from Gnumed.pycommon import gmCfg
      30  from Gnumed.pycommon import gmDateTime
      31  from Gnumed.pycommon import gmShellAPI
      32  from Gnumed.pycommon import gmNetworkTools
      33
      34  from Gnumed.business import gmDemographicRecord
      35  from Gnumed.business import gmPersonSearch
      36  from Gnumed.business import gmSurgery
      37  from Gnumed.business import gmPerson
      38
      39  from Gnumed.wxpython import gmPhraseWheel
      40  from Gnumed.wxpython import gmRegetMixin
      41  from Gnumed.wxpython import gmAuthWidgets
      42  from Gnumed.wxpython import gmPersonContactWidgets
      43  from Gnumed.wxpython import gmEditArea
      44  from Gnumed.wxpython import gmListWidgets
      45  from Gnumed.wxpython import gmDateTimeInput
      46  from Gnumed.wxpython import gmDataMiningWidgets
      47  from Gnumed.wxpython import gmGuiHelpers
      48
      49
      50  # constant defs
      51  _log = logging.getLogger('gm.ui')
      52
      53
      54  try:
      55          _('dummy-no-need-to-translate-but-make-epydoc-happy')
      56  except NameError:
      57          _ = lambda x:x
      58
      59  #============================================================
      60  # image tags related widgets
      61  #------------------------------------------------------------
      62 -def edit_tag_image(parent=None, tag_image=None, single_entry=False):
      63          if tag_image is not None:
      64                  if tag_image['is_in_use']:
      65                          gmGuiHelpers.gm_show_info (
      66                                  aTitle = _('Editing tag'),
      67                                  aMessage = _(
      68                                          'Cannot edit the image tag\n'
      69                                          '\n'
      70                                          ' "%s"\n'
      71                                          '\n'
      72                                          'because it is currently in use.\n'
      73                                  ) % tag_image['l10n_description']
      74                          )
      75                          return False
      76
      77          ea = cTagImageEAPnl(parent = parent, id = -1)
      78          ea.data = tag_image
      79          ea.mode = gmTools.coalesce(tag_image, 'new', 'edit')
      80          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
      81          dlg.SetTitle(gmTools.coalesce(tag_image, _('Adding new tag'), _('Editing tag')))
      82          if dlg.ShowModal() == wx.ID_OK:
      83                  dlg.Destroy()
      84                  return True
      85          dlg.Destroy()
      86          return False
      87  #------------------------------------------------------------
      88 -def manage_tag_images(parent=None):
      89
      90          if parent is None:
      91                  parent = wx.GetApp().GetTopWindow()
      92          #------------------------------------------------------------
      93          def go_to_openclipart_org(tag_image):
      94                  gmNetworkTools.open_url_in_browser(url = u'http://www.openclipart.org')
      95                  gmNetworkTools.open_url_in_browser(url = u'http://www.google.com')
      96                  return True
      97          #------------------------------------------------------------
      98          def edit(tag_image=None):
      99                  return edit_tag_image(parent = parent, tag_image = tag_image, single_entry = (tag_image is not None))
     100          #------------------------------------------------------------
     101          def delete(tag):
     102                  if tag['is_in_use']:
     103                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this tag. It is in use.'), beep = True)
     104                          return False
     105
     106                  return gmDemographicRecord.delete_tag_image(tag_image = tag['pk_tag_image'])
     107          #------------------------------------------------------------
     108          def refresh(lctrl):
     109                  tags = gmDemographicRecord.get_tag_images(order_by = u'l10n_description')
     110                  items = [ [
     111                          t['l10n_description'],
     112                          gmTools.bool2subst(t['is_in_use'], u'X', u''),
     113                          u'%s' % t['size'],
     114                          t['pk_tag_image']
     115                  ] for t in tags ]
     116                  lctrl.set_string_items(items)
     117                  lctrl.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE])
     118                  lctrl.set_data(tags)
     119          #------------------------------------------------------------
     120          msg = _('\nTags with images registered with GNUmed.\n')
     121
     122          tag = gmListWidgets.get_choices_from_list (
     123                  parent = parent,
     124                  msg = msg,
     125                  caption = _('Showing tags with images.'),
     126                  columns = [_('Tag name'), _('In use'), _('Image size'), u'#'],
     127                  single_selection = True,
     128                  new_callback = edit,
     129                  edit_callback = edit,
     130                  delete_callback = delete,
     131                  refresh_callback = refresh,
     132                  left_extra_button = (_('WWW'), _('Go to www.openclipart.org for images.'), go_to_openclipart_org)
     133          )
     134
     135          return tag
     136  #------------------------------------------------------------
     137  from Gnumed.wxGladeWidgets import wxgTagImageEAPnl
     138
     139 -class cTagImageEAPnl(wxgTagImageEAPnl.wxgTagImageEAPnl, gmEditArea.cGenericEditAreaMixin):
     140
     141 -        def __init__(self, *args, **kwargs):
     142
     143                  try:
     144                          data = kwargs['tag_image']
     145                          del kwargs['tag_image']
     146                  except KeyError:
     147                          data = None
     148
     149                  wxgTagImageEAPnl.wxgTagImageEAPnl.__init__(self, *args, **kwargs)
     150                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     151
     152                  self.mode = 'new'
     153                  self.data = data
     154                  if data is not None:
     155                          self.mode = 'edit'
     156
     157                  self.__selected_image_file = None
     158          #----------------------------------------------------------------
     159          # generic Edit Area mixin API
     160          #----------------------------------------------------------------
     161 -        def _valid_for_save(self):
     162
     163                  valid = True
     164
     165                  if self.mode == u'new':
     166                          if self.__selected_image_file is None:
     167                                  valid = False
     168                                  gmDispatcher.send(signal = 'statustext', msg = _('Must pick an image file for a new tag.'), beep = True)
     169                                  self._BTN_pick_image.SetFocus()
     170
     171                  if self.__selected_image_file is not None:
     172                          try:
     173                                  open(self.__selected_image_file).close()
     174                          except StandardError:
     175                                  valid = False
     176                                  self.__selected_image_file = None
     177                                  gmDispatcher.send(signal = 'statustext', msg = _('Cannot open the image file [%s].') % self.__selected_image_file, beep = True)
     178                                  self._BTN_pick_image.SetFocus()
     179
     180                  if self._TCTRL_description.GetValue().strip() == u'':
     181                          valid = False
     182                          self.display_tctrl_as_valid(self._TCTRL_description, False)
     183                          self._TCTRL_description.SetFocus()
     184                  else:
     185                          self.display_tctrl_as_valid(self._TCTRL_description, True)
     186
     187                  return (valid is True)
     188          #----------------------------------------------------------------
     189 -        def _save_as_new(self):
     190
     191                  dbo_conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Creating tag with image'))
     192                  if dbo_conn is None:
     193                          return False
     194
     195                  data = gmDemographicRecord.create_tag_image(description = self._TCTRL_description.GetValue().strip(), link_obj = dbo_conn)
     196                  dbo_conn.close()
     197
     198                  data['filename'] = self._TCTRL_filename.GetValue().strip()
     199                  data.save()
     200                  data.update_image_from_file(filename = self.__selected_image_file)
     201
     202                  # must be done very late or else the property access
     203                  # will refresh the display such that later field
     204                  # access will return empty values
     205                  self.data = data
     206                  return True
     207          #----------------------------------------------------------------
     208 -        def _save_as_update(self):
     209
     210                  # this is somewhat fake as it never actually uses the gm-dbo conn
     211                  # (although it does verify it)
     212                  dbo_conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Updating tag with image'))
     213                  if dbo_conn is None:
     214                          return False
     215                  dbo_conn.close()
     216
     217                  self.data['description'] = self._TCTRL_description.GetValue().strip()
     218                  self.data['filename'] = self._TCTRL_filename.GetValue().strip()
     219                  self.data.save()
     220
     221                  if self.__selected_image_file is not None:
     222                          open(self.__selected_image_file).close()
     223                          self.data.update_image_from_file(filename = self.__selected_image_file)
     224                          self.__selected_image_file = None
     225
     226                  return True
     227          #----------------------------------------------------------------
     228 -        def _refresh_as_new(self):
     229                  self._TCTRL_description.SetValue(u'')
     230                  self._TCTRL_filename.SetValue(u'')
     231                  self._BMP_image.SetBitmap(bitmap = wx.EmptyBitmap(100, 100))
     232
     233                  self.__selected_image_file = None
     234
     235                  self._TCTRL_description.SetFocus()
     236          #----------------------------------------------------------------
     237 -        def _refresh_as_new_from_existing(self):
     238                  self._refresh_as_new()
     239          #----------------------------------------------------------------
     240 -        def _refresh_from_existing(self):
     241                  self._TCTRL_description.SetValue(self.data['l10n_description'])
     242                  self._TCTRL_filename.SetValue(gmTools.coalesce(self.data['filename'], u''))
     243                  fname = self.data.export_image2file()
     244                  if fname is None:
     245                          self._BMP_image.SetBitmap(bitmap = wx.EmptyBitmap(100, 100))
     246                  else:
     247                          self._BMP_image.SetBitmap(bitmap = gmGuiHelpers.file2scaled_image(filename = fname, height = 100))
     248
     249                  self.__selected_image_file = None
     250
     251                  self._TCTRL_description.SetFocus()
     252          #----------------------------------------------------------------
     253          # event handlers
     254          #----------------------------------------------------------------
     255 -        def _on_pick_image_button_pressed(self, event):
     256                  paths = gmTools.gmPaths()
     257                  img_dlg = wx_imagebrowser.ImageDialog(parent = self, set_dir = paths.home_dir)
     258                  img_dlg.Centre()
     259                  if img_dlg.ShowModal() != wx.ID_OK:
     260                          return
     261
     262                  self.__selected_image_file = img_dlg.GetFile()
     263                  self._BMP_image.SetBitmap(bitmap = gmGuiHelpers.file2scaled_image(filename = self.__selected_image_file, height = 100))
     264                  fdir, fname = os.path.split(self.__selected_image_file)
     265                  self._TCTRL_filename.SetValue(fname)
     266
     267  #============================================================
     268  from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl
     269
     270 -class cImageTagPresenterPnl(wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl):
     271
     272 -        def __init__(self, *args, **kwargs):
     273                  wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl.__init__(self, *args, **kwargs)
     274                  self._SZR_bitmaps = self.GetSizer()
     275                  self.__bitmaps = []
     276
     277                  self.__context_popup = wx.Menu()
     278
     279                  item = self.__context_popup.Append(-1, _('&Edit comment'))
     280                  self.Bind(wx.EVT_MENU, self.__edit_tag, item)
     281
     282                  item = self.__context_popup.Append(-1, _('&Remove tag'))
     283                  self.Bind(wx.EVT_MENU, self.__remove_tag, item)
     284          #--------------------------------------------------------
     285          # external API
     286          #--------------------------------------------------------
     287 -        def refresh(self, patient):
     288
     289                  self.clear()
     290
     291                  for tag in patient.get_tags(order_by = u'l10n_description'):
     292                          fname = tag.export_image2file()
     293                          if fname is None:
     294                                  _log.warning('cannot export image data of tag [%s]', tag['l10n_description'])
     295                                  continue
     296                          img = gmGuiHelpers.file2scaled_image(filename = fname, height = 20)
     297                          bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER)
     298                          bmp.SetToolTipString(u'%s%s' % (
     299                                  tag['l10n_description'],
     300                                  gmTools.coalesce(tag['comment'], u'', u'\n\n%s')
     301                          ))
     302                          bmp.tag = tag
     303                          bmp.Bind(wx.EVT_RIGHT_UP, self._on_bitmap_rightclicked)
     304                          # FIXME: add context menu for Delete/Clone/Add/Configure
     305                          self._SZR_bitmaps.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, 1)               # | wx.EXPAND
     306                          self.__bitmaps.append(bmp)
     307
     308                  self.GetParent().Layout()
     309          #--------------------------------------------------------
     310 -        def clear(self):
     311                  while len(self._SZR_bitmaps.GetChildren()) > 0:
     312                          self._SZR_bitmaps.Detach(0)
     313  #               for child_idx in range(len(self._SZR_bitmaps.GetChildren())):
     314  #                       self._SZR_bitmaps.Detach(child_idx)
     315                  for bmp in self.__bitmaps:
     316                          bmp.Destroy()
     317                  self.__bitmaps = []
     318          #--------------------------------------------------------
     319          # internal helpers
     320          #--------------------------------------------------------
     321 -        def __remove_tag(self, evt):
     322                  if self.__current_tag is None:
     323                          return
     324                  pat = gmPerson.gmCurrentPatient()
     325                  if not pat.connected:
     326                          return
     327                  pat.remove_tag(tag = self.__current_tag['pk_identity_tag'])
     328          #--------------------------------------------------------
     329 -        def __edit_tag(self, evt):
     330                  if self.__current_tag is None:
     331                          return
     332
     333                  msg = _('Edit the comment on tag [%s]') % self.__current_tag['l10n_description']
     334                  comment = wx.GetTextFromUser (
     335                          message = msg,
     336                          caption = _('Editing tag comment'),
     337                          default_value = gmTools.coalesce(self.__current_tag['comment'], u''),
     338                          parent = self
     339                  )
     340
     341                  if comment == u'':
     342                          return
     343
     344                  if comment.strip() == self.__current_tag['comment']:
     345                          return
     346
     347                  if comment == u' ':
     348                          self.__current_tag['comment'] = None
     349                  else:
     350                          self.__current_tag['comment'] = comment.strip()
     351
     352                  self.__current_tag.save()
     353          #--------------------------------------------------------
     354          # event handlers
     355          #--------------------------------------------------------
     356 -        def _on_bitmap_rightclicked(self, evt):
     357                  self.__current_tag = evt.GetEventObject().tag
     358                  self.PopupMenu(self.__context_popup, pos = wx.DefaultPosition)
     359                  self.__current_tag = None
     360  #============================================================
     361  #============================================================
     362 -class cKOrganizerSchedulePnl(gmDataMiningWidgets.cPatientListingPnl):
     363
     364 -        def __init__(self, *args, **kwargs):
     365
     366                  kwargs['message'] = _("Today's KOrganizer appointments ...")
     367                  kwargs['button_defs'] = [
     368                          {'label': _('Reload'), 'tooltip': _('Reload appointments from KOrganizer')},
     369                          {'label': u''},
     370                          {'label': u''},
     371                          {'label': u''},
     372                          {'label': u'KOrganizer', 'tooltip': _('Launch KOrganizer')}
     373                  ]
     374                  gmDataMiningWidgets.cPatientListingPnl.__init__(self, *args, **kwargs)
     375
     376                  self.fname = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp', 'korganizer2gnumed.csv'))
     377                  self.reload_cmd = 'konsolekalendar --view --export-type csv --export-file %s' % self.fname
     378
     379          #--------------------------------------------------------
     380 -        def _on_BTN_1_pressed(self, event):
     381                  """Reload appointments from KOrganizer."""
     382                  self.reload_appointments()
     383          #--------------------------------------------------------
     384 -        def _on_BTN_5_pressed(self, event):
     385                  """Reload appointments from KOrganizer."""
     386                  found, cmd = gmShellAPI.detect_external_binary(binary = 'korganizer')
     387
     388                  if not found:
     389                          gmDispatcher.send(signal = 'statustext', msg = _('KOrganizer is not installed.'), beep = True)
     390                          return
     391
     392                  gmShellAPI.run_command_in_shell(command = cmd, blocking = False)
     393          #--------------------------------------------------------
     394 -        def reload_appointments(self):
     395                  try: os.remove(self.fname)
     396                  except OSError: pass
     397                  gmShellAPI.run_command_in_shell(command=self.reload_cmd, blocking=True)
     398                  try:
     399                          csv_file = codecs.open(self.fname , mode = 'rU', encoding = 'utf8', errors = 'replace')
     400                  except IOError:
     401                          gmDispatcher.send(signal = u'statustext', msg = _('Cannot access KOrganizer transfer file [%s]') % self.fname, beep = True)
     402                          return
     403
     404                  csv_lines = gmTools.unicode_csv_reader (
     405                          csv_file,
     406                          delimiter = ','
     407                  )
     408                  # start_date, start_time, end_date, end_time, title (patient), ort, comment, UID
     409                  self._LCTRL_items.set_columns ([
     410                          _('Place'),
     411                          _('Start'),
     412                          u'',
     413                          u'',
     414                          _('Patient'),
     415                          _('Comment')
     416                  ])
     417                  items = []
     418                  data = []
     419                  for line in csv_lines:
     420                          items.append([line[5], line[0], line[1], line[3], line[4], line[6]])
     421                          data.append([line[4], line[7]])
     422
     423                  self._LCTRL_items.set_string_items(items = items)
     424                  self._LCTRL_items.set_column_widths()
     425                  self._LCTRL_items.set_data(data = data)
     426                  self._LCTRL_items.patient_key = 0
     427          #--------------------------------------------------------
     428          # notebook plugins API
     429          #--------------------------------------------------------
     430 -        def repopulate_ui(self):
     431                  self.reload_appointments()
     432  #============================================================
     433  # occupation related widgets / functions
     434  #============================================================
     435 -def edit_occupation():
     436
     437          pat = gmPerson.gmCurrentPatient()
     438          curr_jobs = pat.get_occupations()
     439          if len(curr_jobs) > 0:
     440                  old_job = curr_jobs[0]['l10n_occupation']
     441                  update = curr_jobs[0]['modified_when'].strftime('%m/%Y')
     442          else:
     443                  old_job = u''
     444                  update = u''
     445
     446          msg = _(
     447                  'Please enter the primary occupation of the patient.\n'
     448                  '\n'
     449                  'Currently recorded:\n'
     450                  '\n'
     451                  ' %s (last updated %s)'
     452          ) % (old_job, update)
     453
     454          new_job = wx.GetTextFromUser (
     455                  message = msg,
     456                  caption = _('Editing primary occupation'),
     457                  default_value = old_job,
     458                  parent = None
     459          )
     460          if new_job.strip() == u'':
     461                  return
     462
     463          for job in curr_jobs:
     464                  # unlink all but the new job
     465                  if job['l10n_occupation'] != new_job:
     466                          pat.unlink_occupation(occupation = job['l10n_occupation'])
     467          # and link the new one
     468          pat.link_occupation(occupation = new_job)
     469
     470  #------------------------------------------------------------
     471 -class cOccupationPhraseWheel(gmPhraseWheel.cPhraseWheel):
     472
     473 -        def __init__(self, *args, **kwargs):
     474                  query = u"SELECT distinct name, _(name) from dem.occupation where _(name) %(fragment_condition)s"
     475                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
     476                  mp.setThresholds(1, 3, 5)
     477                  gmPhraseWheel.cPhraseWheel.__init__ (
     478                          self,
     479                          *args,
     480                          **kwargs
     481                  )
     482                  self.SetToolTipString(_("Type or select an occupation."))
     483                  self.capitalisation_mode = gmTools.CAPS_FIRST
     484                  self.matcher = mp
     485
     486  #============================================================
     487  # identity widgets / functions
     488  #============================================================
     489 -def disable_identity(identity=None):
     490          # ask user for assurance
     491          go_ahead = gmGuiHelpers.gm_show_question (
     492                  _('Are you sure you really, positively want\n'
     493                    'to disable the following person ?\n'
     494                    '\n'
     495                    ' %s %s %s\n'
     496                    ' born %s\n'
     497                    '\n'
     498                    '%s\n'
     499                  ) % (
     500                          identity['firstnames'],
     501                          identity['lastnames'],
     502                          identity['gender'],
     503                          identity['dob'],
     504                          gmTools.bool2subst (
     505                                  identity.is_patient,
     506                                  _('This patient DID receive care.'),
     507                                  _('This person did NOT receive care.')
     508                          )
     509                  ),
     510                  _('Disabling person')
     511          )
     512          if not go_ahead:
     513                  return True
     514
     515          # get admin connection
     516          conn = gmAuthWidgets.get_dbowner_connection (
     517                  procedure = _('Disabling patient')
     518          )
     519          # - user cancelled
     520          if conn is False:
     521                  return True
     522          # - error
     523          if conn is None:
     524                  return False
     525
     526          # now disable patient
     527          gmPG2.run_rw_queries(queries = [{'cmd': u"update dem.identity set deleted=True where pk=%s", 'args': [identity['pk_identity']]}])
     528
     529          return True
     530
     531  #------------------------------------------------------------
     532  # phrasewheels
     533  #------------------------------------------------------------
     534 -class cLastnamePhraseWheel(gmPhraseWheel.cPhraseWheel):
     535
     536 -        def __init__(self, *args, **kwargs):
     537                  query = u"SELECT distinct lastnames, lastnames from dem.names where lastnames %(fragment_condition)s order by lastnames limit 25"
     538                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
     539                  mp.setThresholds(3, 5, 9)
     540                  gmPhraseWheel.cPhraseWheel.__init__ (
     541                          self,
     542                          *args,
     543                          **kwargs
     544                  )
     545                  self.SetToolTipString(_("Type or select a last name (family name/surname)."))
     546                  self.capitalisation_mode = gmTools.CAPS_NAMES
     547                  self.matcher = mp
     548  #------------------------------------------------------------
     549 -class cFirstnamePhraseWheel(gmPhraseWheel.cPhraseWheel):
     550
     551 -        def __init__(self, *args, **kwargs):
     552                  query = u"""
     553                          (SELECT distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
     554                                  union
     555                          (SELECT distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
     556                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
     557                  mp.setThresholds(3, 5, 9)
     558                  gmPhraseWheel.cPhraseWheel.__init__ (
     559                          self,
     560                          *args,
     561                          **kwargs
     562                  )
     563                  self.SetToolTipString(_("Type or select a first name (forename/Christian name/given name)."))
     564                  self.capitalisation_mode = gmTools.CAPS_NAMES
     565                  self.matcher = mp
     566  #------------------------------------------------------------
     567 -class cNicknamePhraseWheel(gmPhraseWheel.cPhraseWheel):
     568
     569 -        def __init__(self, *args, **kwargs):
     570                  query = u"""
     571                          (SELECT distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20)
     572                                  union
     573                          (SELECT distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
     574                                  union
     575                          (SELECT distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
     576                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
     577                  mp.setThresholds(3, 5, 9)
     578                  gmPhraseWheel.cPhraseWheel.__init__ (
     579                          self,
     580                          *args,
     581                          **kwargs
     582                  )
     583                  self.SetToolTipString(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name)."))
     584                  # nicknames CAN start with lower case !
     585                  #self.capitalisation_mode = gmTools.CAPS_NAMES
     586                  self.matcher = mp
     587  #------------------------------------------------------------
     588 -class cTitlePhraseWheel(gmPhraseWheel.cPhraseWheel):
     589
     590 -        def __init__(self, *args, **kwargs):
     591                  query = u"SELECT distinct title, title from dem.identity where title %(fragment_condition)s"
     592                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
     593                  mp.setThresholds(1, 3, 9)
     594                  gmPhraseWheel.cPhraseWheel.__init__ (
     595                          self,
     596                          *args,
     597                          **kwargs
     598                  )
     599                  self.SetToolTipString(_("Type or select a title. Note that the title applies to the person, not to a particular name !"))
     600                  self.matcher = mp
     601  #------------------------------------------------------------
     602 -class cGenderSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
     603          """Let user select a gender."""
     604
     605          _gender_map = None
     606
     607 -        def __init__(self, *args, **kwargs):
     608
     609                  if cGenderSelectionPhraseWheel._gender_map is None:
     610                          cmd = u"""
     611                                  SELECT tag, l10n_label, sort_weight
     612                                  from dem.v_gender_labels
     613                                  order by sort_weight desc"""
     614                          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True)
     615                          cGenderSelectionPhraseWheel._gender_map = {}
     616                          for gender in rows:
     617                                  cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = {
     618                                          'data': gender[idx['tag']],
     619                                          'field_label': gender[idx['l10n_label']],
     620                                          'list_label': gender[idx['l10n_label']],
     621                                          'weight': gender[idx['sort_weight']]
     622                                  }
     623
     624                  mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = cGenderSelectionPhraseWheel._gender_map.values())
     625                  mp.setThresholds(1, 1, 3)
     626
     627                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
     628                  self.selection_only = True
     629                  self.matcher = mp
     630                  self.picklist_delay = 50
     631  #------------------------------------------------------------
     632 -class cExternalIDTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
     633
     634 -        def __init__(self, *args, **kwargs):
     635                  query = u"""
     636                          SELECT DISTINCT ON (list_label)
     637                                  pk AS data,
     638                                  name AS field_label,
     639                                  name || coalesce(' (' || issuer || ')', '') as list_label
     640                          FROM dem.enum_ext_id_types
     641                          WHERE name %(fragment_condition)s
     642                          ORDER BY list_label
     643                          LIMIT 25
     644                  """
     645                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
     646                  mp.setThresholds(1, 3, 5)
     647                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
     648                  self.SetToolTipString(_("Enter or select a type for the external ID."))
     649                  self.matcher = mp
     650          #--------------------------------------------------------
     651 -        def _get_data_tooltip(self):
     652                  if self.GetData() is None:
     653                          return None
     654                  return self._data.values()[0]['list_label']
     655  #------------------------------------------------------------
     656 -class cExternalIDIssuerPhraseWheel(gmPhraseWheel.cPhraseWheel):
     657
     658 -        def __init__(self, *args, **kwargs):
     659                  query = u"""
     660  SELECT distinct issuer, issuer
     661  from dem.enum_ext_id_types
     662  where issuer %(fragment_condition)s
     663  order by issuer limit 25"""
     664                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
     665                  mp.setThresholds(1, 3, 5)
     666                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
     667                  self.SetToolTipString(_("Type or select an ID issuer."))
     668                  self.capitalisation_mode = gmTools.CAPS_FIRST
     669                  self.matcher = mp
     670  #------------------------------------------------------------
     671  # edit areas
     672  #------------------------------------------------------------
     673  from Gnumed.wxGladeWidgets import wxgExternalIDEditAreaPnl
     674
     675 -class cExternalIDEditAreaPnl(wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
     676          """An edit area for editing/creating external IDs.
     677
     678          Does NOT act on/listen to the current patient.
     679          """
     680 -        def __init__(self, *args, **kwargs):
     681
     682                  try:
     683                          data = kwargs['external_id']
     684                          del kwargs['external_id']
     685                  except:
     686                          data = None
     687
     688                  wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl.__init__(self, *args, **kwargs)
     689                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     690
     691                  self.identity = None
     692
     693                  self.mode = 'new'
     694                  self.data = data
     695                  if data is not None:
     696                          self.mode = 'edit'
     697
     698                  self.__init_ui()
     699          #--------------------------------------------------------
     700 -        def __init_ui(self):
     701                  self._PRW_type.add_callback_on_lose_focus(self._on_type_set)
     702          #----------------------------------------------------------------
     703          # generic Edit Area mixin API
     704          #----------------------------------------------------------------
     705 -        def _valid_for_save(self):
     706                  validity = True
     707
     708                  # do not test .GetData() because adding external
     709                  # IDs will create types as necessary
     710                  #if self._PRW_type.GetData() is None:
     711                  if self._PRW_type.GetValue().strip() == u'':
     712                          validity = False
     713                          self._PRW_type.display_as_valid(False)
     714                          self._PRW_type.SetFocus()
     715                  else:
     716                          self._PRW_type.display_as_valid(True)
     717
     718                  if self._TCTRL_value.GetValue().strip() == u'':
     719                          validity = False
     720                          self.display_tctrl_as_valid(tctrl = self._TCTRL_value, valid = False)
     721                  else:
     722                          self.display_tctrl_as_valid(tctrl = self._TCTRL_value, valid = True)
     723
     724                  return validity
     725          #----------------------------------------------------------------
     726 -        def _save_as_new(self):
     727                  data = {}
     728                  data['pk_type'] = None
     729                  data['name'] = self._PRW_type.GetValue().strip()
     730                  data['value'] = self._TCTRL_value.GetValue().strip()
     731                  data['issuer'] = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u'')
     732                  data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
     733
     734                  self.identity.add_external_id (
     735                          type_name = data['name'],
     736                          value = data['value'],
     737                          issuer = data['issuer'],
     738                          comment = data['comment']
     739                  )
     740
     741                  self.data = data
     742                  return True
     743          #----------------------------------------------------------------
     744 -        def _save_as_update(self):
     745                  self.data['name'] = self._PRW_type.GetValue().strip()
     746                  self.data['value'] = self._TCTRL_value.GetValue().strip()
     747                  self.data['issuer'] = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u'')
     748                  self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
     749
     750                  self.identity.update_external_id (
     751                          pk_id = self.data['pk_id'],
     752                          type = self.data['name'],
     753                          value = self.data['value'],
     754                          issuer = self.data['issuer'],
     755                          comment = self.data['comment']
     756                  )
     757
     758                  return True
     759          #----------------------------------------------------------------
     760 -        def _refresh_as_new(self):
     761                  self._PRW_type.SetText(value = u'', data = None)
     762                  self._TCTRL_value.SetValue(u'')
     763                  self._PRW_issuer.SetText(value = u'', data = None)
     764                  self._TCTRL_comment.SetValue(u'')
     765          #----------------------------------------------------------------
     766 -        def _refresh_as_new_from_existing(self):
     767                  self._refresh_as_new()
     768                  self._PRW_issuer.SetText(self.data['issuer'])
     769          #----------------------------------------------------------------
     770 -        def _refresh_from_existing(self):
     771                  self._PRW_type.SetText(value = self.data['name'], data = self.data['pk_type'])
     772                  self._TCTRL_value.SetValue(self.data['value'])
     773                  self._PRW_issuer.SetText(self.data['issuer'])
     774                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
     775          #----------------------------------------------------------------
     776          # internal helpers
     777          #----------------------------------------------------------------
     778 -        def _on_type_set(self):
     779                  """Set the issuer according to the selected type.
     780
     781                  Matches are fetched from existing records in backend.
     782                  """
     783                  pk_curr_type = self._PRW_type.GetData()
     784                  if pk_curr_type is None:
     785                          return True
     786                  rows, idx = gmPG2.run_ro_queries(queries = [{
     787                          'cmd': u"SELECT issuer from dem.enum_ext_id_types where pk = %s",
     788                          'args': [pk_curr_type]
     789                  }])
     790                  if len(rows) == 0:
     791                          return True
     792                  wx.CallAfter(self._PRW_issuer.SetText, rows[0][0])
     793                  return True
     794
     795  #============================================================
     796  # identity widgets
     797  #------------------------------------------------------------
     798 -def _empty_dob_allowed():
     799          allow_empty_dob = gmGuiHelpers.gm_show_question (
     800                  _(
     801                          'Are you sure you want to leave this person\n'
     802                          'without a valid date of birth ?\n'
     803                          '\n'
     804                          'This can be useful for temporary staff members\n'
     805                          'but will provoke nag screens if this person\n'
     806                          'becomes a patient.\n'
     807                  ),
     808                  _('Validating date of birth')
     809          )
     810          return allow_empty_dob
     811  #------------------------------------------------------------
     812 -def _validate_dob_field(dob_prw):
     813
     814          # valid timestamp ?
     815          if dob_prw.is_valid_timestamp(allow_empty = False):                     # properly colors the field
     816                  dob = dob_prw.date
     817                  # but year also usable ?
     818                  if (dob.year > 1899) and (dob < gmDateTime.pydt_now_here()):
     819                          return True
     820
     821                  if dob.year < 1900:
     822                          msg = _(
     823                                  'DOB: %s\n'
     824                                  '\n'
     825                                  'While this is a valid point in time Python does\n'
     826                                  'not know how to deal with it.\n'
     827                                  '\n'
     828                                  'We suggest using January 1st 1901 instead and adding\n'
     829                                  'the true date of birth to the patient comment.\n'
     830                                  '\n'
     831                                  'Sorry for the inconvenience %s'
     832                          ) % (dob, gmTools.u_frowning_face)
     833                  else:
     834                          msg = _(
     835                                  'DOB: %s\n'
     836                                  '\n'
     837                                  'Date of birth in the future !'
     838                          ) % dob
     839                  gmGuiHelpers.gm_show_error (
     840                          msg,
     841                          _('Validating date of birth')
     842                  )
     843                  dob_prw.display_as_valid(False)
     844                  dob_prw.SetFocus()
     845                  return False
     846
     847          # invalid timestamp but not empty
     848          if dob_prw.GetValue().strip() != u'':
     849                  dob_prw.display_as_valid(False)
     850                  gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of birth.'))
     851                  dob_prw.SetFocus()
     852                  return False
     853
     854          # empty DOB field
     855          dob_prw.display_as_valid(False)
     856          return True
     857  #------------------------------------------------------------
     858  from Gnumed.wxGladeWidgets import wxgIdentityEAPnl
     859
     860 -class cIdentityEAPnl(wxgIdentityEAPnl.wxgIdentityEAPnl, gmEditArea.cGenericEditAreaMixin):
     861          """An edit area for editing/creating title/gender/dob/dod etc."""
     862
     863 -        def __init__(self, *args, **kwargs):
     864
     865                  try:
     866                          data = kwargs['identity']
     867                          del kwargs['identity']
     868                  except KeyError:
     869                          data = None
     870
     871                  wxgIdentityEAPnl.wxgIdentityEAPnl.__init__(self, *args, **kwargs)
     872                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     873
     874                  self.mode = 'new'
     875                  self.data = data
     876                  if data is not None:
     877                          self.mode = 'edit'
     878
     879  #               self.__init_ui()
     880          #----------------------------------------------------------------
     881  #       def __init_ui(self):
     882  #               # adjust phrasewheels etc
     883          #----------------------------------------------------------------
     884          # generic Edit Area mixin API
     885          #----------------------------------------------------------------
     886 -        def _valid_for_save(self):
     887
     888                  has_error = False
     889
     890                  if self._PRW_gender.GetData() is None:
     891                          self._PRW_gender.SetFocus()
     892                          has_error = True
     893
     894                  if self.data is not None:
     895                          if not _validate_dob_field(self._PRW_dob):
     896                                  has_error = True
     897
     898                  if not self._PRW_dod.is_valid_timestamp(allow_empty = True):
     899                          gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.'))
     900                          self._PRW_dod.SetFocus()
     901                          has_error = True
     902
     903                  return (has_error is False)
     904          #----------------------------------------------------------------
     905 -        def _save_as_new(self):
     906                  # not used yet
     907                  return False
     908          #----------------------------------------------------------------
     909 -        def _save_as_update(self):
     910
     911                  if self._PRW_dob.GetValue().strip() == u'':
     912                          if not _empty_dob_allowed():
     913                                  return False
     914                          self.data['dob'] = None
     915                  else:
     916                          self.data['dob'] = self._PRW_dob.GetData()
     917
     918                  self.data['gender'] = self._PRW_gender.GetData()
     919                  self.data['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'')
     920                  self.data['deceased'] = self._PRW_dod.GetData()
     921                  self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
     922
     923                  self.data.save()
     924                  return True
     925          #----------------------------------------------------------------
     926 -        def _refresh_as_new(self):
     927                  pass
     928          #----------------------------------------------------------------
     929 -        def _refresh_from_existing(self):
     930
     931                  self._LBL_info.SetLabel(u'ID: #%s' % (
     932                          self.data.ID
     933                          # FIXME: add 'deleted' status
     934                  ))
     935                  if self.data['dob'] is None:
     936                          val = u''
     937                  else:
     938                          val = gmDateTime.pydt_strftime (
     939                                  self.data['dob'],
     940                                  format = '%Y-%m-%d %H:%M',
     941                                  accuracy = gmDateTime.acc_minutes
     942                          )
     943                  self._PRW_dob.SetText(value = val, data = self.data['dob'])
     944                  if self.data['deceased'] is None:
     945                          val = u''
     946                  else:
     947                          val = gmDateTime.pydt_strftime (
     948                                  self.data['deceased'],
     949                                  format = '%Y-%m-%d %H:%M',
     950                                  accuracy = gmDateTime.acc_minutes
     951                          )
     952                  self._PRW_dod.SetText(value = val, data = self.data['deceased'])
     953                  self._PRW_gender.SetData(self.data['gender'])
     954                  #self._PRW_ethnicity.SetValue()
     955                  self._PRW_title.SetText(gmTools.coalesce(self.data['title'], u''))
     956                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
     957          #----------------------------------------------------------------
     958 -        def _refresh_as_new_from_existing(self):
     959                  pass
     960  #------------------------------------------------------------
     961  from Gnumed.wxGladeWidgets import wxgPersonNameEAPnl
     962
     963 -class cPersonNameEAPnl(wxgPersonNameEAPnl.wxgPersonNameEAPnl, gmEditArea.cGenericEditAreaMixin):
     964          """An edit area for editing/creating names of people.
     965
     966          Does NOT act on/listen to the current patient.
     967          """
     968 -        def __init__(self, *args, **kwargs):
     969
     970                  try:
     971                          data = kwargs['name']
     972                          identity = gmPerson.cIdentity(aPK_obj = data['pk_identity'])
     973                          del kwargs['name']
     974                  except KeyError:
     975                          data = None
     976                          identity = kwargs['identity']
     977                          del kwargs['identity']
     978
     979                  wxgPersonNameEAPnl.wxgPersonNameEAPnl.__init__(self, *args, **kwargs)
     980                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     981
     982                  self.__identity = identity
     983
     984                  self.mode = 'new'
     985                  self.data = data
     986                  if data is not None:
     987                          self.mode = 'edit'
     988
     989                  #self.__init_ui()
     990          #----------------------------------------------------------------
     991  #       def __init_ui(self):
     992  #               # adjust phrasewheels etc
     993          #----------------------------------------------------------------
     994          # generic Edit Area mixin API
     995          #----------------------------------------------------------------
     996 -        def _valid_for_save(self):
     997                  validity = True
     998
     999                  if self._PRW_lastname.GetValue().strip() == u'':
    1000                          validity = False
    1001                          self._PRW_lastname.display_as_valid(False)
    1002                          self._PRW_lastname.SetFocus()
    1003                  else:
    1004                          self._PRW_lastname.display_as_valid(True)
    1005
    1006                  if self._PRW_firstname.GetValue().strip() == u'':
    1007                          validity = False
    1008                          self._PRW_firstname.display_as_valid(False)
    1009                          self._PRW_firstname.SetFocus()
    1010                  else:
    1011                          self._PRW_firstname.display_as_valid(True)
    1012
    1013                  return validity
    1014          #----------------------------------------------------------------
    1015 -        def _save_as_new(self):
    1016
    1017                  first = self._PRW_firstname.GetValue().strip()
    1018                  last = self._PRW_lastname.GetValue().strip()
    1019                  active = self._CHBOX_active.GetValue()
    1020
    1021                  data = self.__identity.add_name(first, last, active)
    1022
    1023                  old_nick = self.__identity['active_name']['preferred']
    1024                  new_nick = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'')
    1025                  if active:
    1026                          data['preferred'] = gmTools.coalesce(new_nick, old_nick)
    1027                  else:
    1028                          data['preferred'] = new_nick
    1029                  data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
    1030                  data.save()
    1031
    1032                  self.data = data
    1033                  return True
    1034          #----------------------------------------------------------------
    1035 -        def _save_as_update(self):
    1036                  """The knack here is that we can only update a few fields.
    1037
    1038                  Otherwise we need to clone the name and update that.
    1039                  """
    1040                  first = self._PRW_firstname.GetValue().strip()
    1041                  last = self._PRW_lastname.GetValue().strip()
    1042                  active = self._CHBOX_active.GetValue()
    1043
    1044                  current_name = self.data['firstnames'].strip() + self.data['lastnames'].strip()
    1045                  new_name = first + last
    1046
    1047                  # editable fields only ?
    1048                  if new_name == current_name:
    1049                          self.data['active_name'] = self._CHBOX_active.GetValue()
    1050                          self.data['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'')
    1051                          self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
    1052                          self.data.save()
    1053                  # else clone name and update that
    1054                  else:
    1055                          name = self.__identity.add_name(first, last, active)
    1056                          name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'')
    1057                          name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
    1058                          name.save()
    1059                          self.data = name
    1060
    1061                  return True
    1062          #----------------------------------------------------------------
    1063 -        def _refresh_as_new(self):
    1064                  self._PRW_firstname.SetText(value = u'', data = None)
    1065                  self._PRW_lastname.SetText(value = u'', data = None)
    1066                  self._PRW_nick.SetText(value = u'', data = None)
    1067                  self._TCTRL_comment.SetValue(u'')
    1068                  self._CHBOX_active.SetValue(False)
    1069
    1070                  self._PRW_firstname.SetFocus()
    1071          #----------------------------------------------------------------
    1072 -        def _refresh_as_new_from_existing(self):
    1073                  self._refresh_as_new()
    1074                  self._PRW_firstname.SetText(value = u'', data = None)
    1075                  self._PRW_nick.SetText(gmTools.coalesce(self.data['preferred'], u''))
    1076
    1077                  self._PRW_lastname.SetFocus()
    1078          #----------------------------------------------------------------
    1079 -        def _refresh_from_existing(self):
    1080                  self._PRW_firstname.SetText(self.data['firstnames'])
    1081                  self._PRW_lastname.SetText(self.data['lastnames'])
    1082                  self._PRW_nick.SetText(gmTools.coalesce(self.data['preferred'], u''))
    1083                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
    1084                  self._CHBOX_active.SetValue(self.data['active_name'])
    1085
    1086                  self._TCTRL_comment.SetFocus()
    1087  #------------------------------------------------------------
    1088  # list manager
    1089  #------------------------------------------------------------
    1090 -class cPersonNamesManagerPnl(gmListWidgets.cGenericListManagerPnl):
    1091          """A list for managing a person's names.
    1092
    1093          Does NOT act on/listen to the current patient.
    1094          """
    1095 -        def __init__(self, *args, **kwargs):
    1096
    1097                  try:
    1098                          self.__identity = kwargs['identity']
    1099                          del kwargs['identity']
    1100                  except KeyError:
    1101                          self.__identity = None
    1102
    1103                  gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs)
    1104
    1105                  self.new_callback = self._add_name
    1106                  self.edit_callback = self._edit_name
    1107                  self.delete_callback = self._del_name
    1108                  self.refresh_callback = self.refresh
    1109
    1110                  self.__init_ui()
    1111                  self.refresh()
    1112          #--------------------------------------------------------
    1113          # external API
    1114          #--------------------------------------------------------
    1115 -        def refresh(self, *args, **kwargs):
    1116                  if self.__identity is None:
    1117                          self._LCTRL_items.set_string_items()
    1118                          return
    1119
    1120                  names = self.__identity.get_names()
    1121                  self._LCTRL_items.set_string_items (
    1122                          items = [ [
    1123                                          gmTools.bool2str(n['active_name'], 'X', ''),
    1124                                          n['lastnames'],
    1125                                          n['firstnames'],
    1126                                          gmTools.coalesce(n['preferred'], u''),
    1127                                          gmTools.coalesce(n['comment'], u'')
    1128                                  ] for n in names ]
    1129                  )
    1130                  self._LCTRL_items.set_column_widths()
    1131                  self._LCTRL_items.set_data(data = names)
    1132          #--------------------------------------------------------
    1133          # internal helpers
    1134          #--------------------------------------------------------
    1135 -        def __init_ui(self):
    1136                  self._LCTRL_items.set_columns(columns = [
    1137                          _('Active'),
    1138                          _('Lastname'),
    1139                          _('Firstname(s)'),
    1140                          _('Preferred Name'),
    1141                          _('Comment')
    1142                  ])
    1143                  self._BTN_edit.SetLabel(_('Clone and &edit'))
    1144          #--------------------------------------------------------
    1145 -        def _add_name(self):
    1146                  #ea = cPersonNameEAPnl(self, -1, name = self.__identity.get_active_name())
    1147                  ea = cPersonNameEAPnl(self, -1, identity = self.__identity)
    1148                  dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True)
    1149                  dlg.SetTitle(_('Adding new name'))
    1150                  if dlg.ShowModal() == wx.ID_OK:
    1151                          dlg.Destroy()
    1152                          return True
    1153                  dlg.Destroy()
    1154                  return False
    1155          #--------------------------------------------------------
    1156 -        def _edit_name(self, name):
    1157                  ea = cPersonNameEAPnl(self, -1, name = name)
    1158                  dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True)
    1159                  dlg.SetTitle(_('Cloning name'))
    1160                  if dlg.ShowModal() == wx.ID_OK:
    1161                          dlg.Destroy()
    1162                          return True
    1163                  dlg.Destroy()
    1164                  return False
    1165          #--------------------------------------------------------
    1166 -        def _del_name(self, name):
    1167
    1168                  if len(self.__identity.get_names()) == 1:
    1169                          gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True)
    1170                          return False
    1171
    1172                  if name['active_name']:
    1173                          gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the active name of a person.'), beep = True)
    1174                          return False
    1175
    1176                  go_ahead = gmGuiHelpers.gm_show_question (
    1177                          _(      'It is often advisable to keep old names around and\n'
    1178                                  'just create a new "currently active" name.\n'
    1179                                  '\n'
    1180                                  'This allows finding the patient by both the old\n'
    1181                                  'and the new name (think before/after marriage).\n'
    1182                                  '\n'
    1183                                  'Do you still want to really delete\n'
    1184                                  "this name from the patient ?"
    1185                          ),
    1186                          _('Deleting name')
    1187                  )
    1188                  if not go_ahead:
    1189                          return False
    1190
    1191                  self.__identity.delete_name(name = name)
    1192                  return True
    1193          #--------------------------------------------------------
    1194          # properties
    1195          #--------------------------------------------------------
    1196 -        def _get_identity(self):
    1197                  return self.__identity
    1198
    1199 -        def _set_identity(self, identity):
    1200                  self.__identity = identity
    1201                  self.refresh()
    1202
    1203          identity = property(_get_identity, _set_identity)
    1204  #------------------------------------------------------------
    1205 -class cPersonIDsManagerPnl(gmListWidgets.cGenericListManagerPnl):
    1206          """A list for managing a person's external IDs.
    1207
    1208          Does NOT act on/listen to the current patient.
    1209          """
    1210 -        def __init__(self, *args, **kwargs):
    1211
    1212                  try:
    1213                          self.__identity = kwargs['identity']
    1214                          del kwargs['identity']
    1215                  except KeyError:
    1216                          self.__identity = None
    1217
    1218                  gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs)
    1219
    1220                  self.new_callback = self._add_id
    1221                  self.edit_callback = self._edit_id
    1222                  self.delete_callback = self._del_id
    1223                  self.refresh_callback = self.refresh
    1224
    1225                  self.__init_ui()
    1226                  self.refresh()
    1227          #--------------------------------------------------------
    1228          # external API
    1229          #--------------------------------------------------------
    1230 -        def refresh(self, *args, **kwargs):
    1231                  if self.__identity is None:
    1232                          self._LCTRL_items.set_string_items()
    1233                          return
    1234
    1235                  ids = self.__identity.get_external_ids()
    1236                  self._LCTRL_items.set_string_items (
    1237                          items = [ [
    1238                                          i['name'],
    1239                                          i['value'],
    1240                                          gmTools.coalesce(i['issuer'], u''),
    1241                                          gmTools.coalesce(i['comment'], u'')
    1242                                  ] for i in ids
    1243                          ]
    1244                  )
    1245                  self._LCTRL_items.set_column_widths()
    1246                  self._LCTRL_items.set_data(data = ids)
    1247          #--------------------------------------------------------
    1248          # internal helpers
    1249          #--------------------------------------------------------
    1250 -        def __init_ui(self):
    1251                  self._LCTRL_items.set_columns(columns = [
    1252                          _('ID type'),
    1253                          _('Value'),
    1254                          _('Issuer'),
    1255                          _('Comment')
    1256                  ])
    1257          #--------------------------------------------------------
    1258 -        def _add_id(self):
    1259                  ea = cExternalIDEditAreaPnl(self, -1)
    1260                  ea.identity = self.__identity
    1261                  dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea)
    1262                  dlg.SetTitle(_('Adding new external ID'))
    1263                  if dlg.ShowModal() == wx.ID_OK:
    1264                          dlg.Destroy()
    1265                          return True
    1266                  dlg.Destroy()
    1267                  return False
    1268          #--------------------------------------------------------
    1269 -        def _edit_id(self, ext_id):
    1270                  ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id)
    1271                  ea.identity = self.__identity
    1272                  dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True)
    1273                  dlg.SetTitle(_('Editing external ID'))
    1274                  if dlg.ShowModal() == wx.ID_OK:
    1275                          dlg.Destroy()
    1276                          return True
    1277                  dlg.Destroy()
    1278                  return False
    1279          #--------------------------------------------------------
    1280 -        def _del_id(self, ext_id):
    1281                  go_ahead = gmGuiHelpers.gm_show_question (
    1282                          _(      'Do you really want to delete this\n'
    1283                                  'external ID from the patient ?'),
    1284                          _('Deleting external ID')
    1285                  )
    1286                  if not go_ahead:
    1287                          return False
    1288                  self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
    1289                  return True
    1290          #--------------------------------------------------------
    1291          # properties
    1292          #--------------------------------------------------------
    1293 -        def _get_identity(self):
    1294                  return self.__identity
    1295
    1296 -        def _set_identity(self, identity):
    1297                  self.__identity = identity
    1298                  self.refresh()
    1299
    1300          identity = property(_get_identity, _set_identity)
    1301  #------------------------------------------------------------
    1302  # integrated panels
    1303  #------------------------------------------------------------
    1304  from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl
    1305
    1306 -class cPersonIdentityManagerPnl(wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl):
    1307          """A panel for editing identity data for a person.
    1308
    1309          - provides access to:
    1310            - identity EA
    1311            - name list manager
    1312            - external IDs list manager
    1313
    1314          Does NOT act on/listen to the current patient.
    1315          """
    1316 -        def __init__(self, *args, **kwargs):
    1317
    1318                  wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl.__init__(self, *args, **kwargs)
    1319
    1320                  self.__identity = None
    1321                  self.refresh()
    1322          #--------------------------------------------------------
    1323          # external API
    1324          #--------------------------------------------------------
    1325 -        def refresh(self):
    1326                  self._PNL_names.identity = self.__identity
    1327                  self._PNL_ids.identity = self.__identity
    1328                  # this is an Edit Area:
    1329                  self._PNL_identity.mode = 'new'
    1330                  self._PNL_identity.data = self.__identity
    1331                  if self.__identity is not None:
    1332                          self._PNL_identity.mode = 'edit'
    1333                          self._PNL_identity._refresh_from_existing()
    1334          #--------------------------------------------------------
    1335          # properties
    1336          #--------------------------------------------------------
    1337 -        def _get_identity(self):
    1338                  return self.__identity
    1339
    1340 -        def _set_identity(self, identity):
    1341                  self.__identity = identity
    1342                  self.refresh()
    1343
    1344          identity = property(_get_identity, _set_identity)
    1345          #--------------------------------------------------------
    1346          # event handlers
    1347          #--------------------------------------------------------
    1348 -        def _on_save_identity_details_button_pressed(self, event):
    1349                  if not self._PNL_identity.save():
    1350                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot save identity. Incomplete information.'), beep = True)
    1351                  #self._PNL_identity.refresh()
    1352          #--------------------------------------------------------
    1353 -        def _on_reload_identity_button_pressed(self, event):
    1354                  self._PNL_identity.refresh()
    1355
    1356  #============================================================
    1357  from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl
    1358
    1359 -class cPersonSocialNetworkManagerPnl(wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl):
    1360 -        def __init__(self, *args, **kwargs):
    1361
    1362                  wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl.__init__(self, *args, **kwargs)
    1363
    1364                  self.__identity = None
    1365                  self._PRW_provider.selection_only = False
    1366                  self.refresh()
    1367          #--------------------------------------------------------
    1368          # external API
    1369          #--------------------------------------------------------
    1370 -        def refresh(self):
    1371
    1372                  tt = _('Link another person in this database as the emergency contact:\n\nEnter person name part or identifier and hit <enter>.')
    1373
    1374                  if self.__identity is None:
    1375                          self._TCTRL_er_contact.SetValue(u'')
    1376                          self._TCTRL_person.person = None
    1377                          self._TCTRL_person.SetToolTipString(tt)
    1378
    1379                          self._PRW_provider.SetText(value = u'', data = None)
    1380                          return
    1381
    1382                  self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], u''))
    1383                  if self.__identity['pk_emergency_contact'] is not None:
    1384                          ident = gmPerson.cIdentity(aPK_obj = self.__identity['pk_emergency_contact'])
    1385                          self._TCTRL_person.person = ident
    1386                          tt = u'%s\n\n%s\n\n%s' % (
    1387                                  tt,
    1388                                  ident['description_gender'],
    1389                                  u'\n'.join([
    1390                                          u'%s: %s%s' % (
    1391                                                  c['l10n_comm_type'],
    1392                                                  c['url'],
    1393                                                  gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), u'', u'')
    1394                                          )
    1395                                          for c in ident.get_comm_channels()
    1396                                  ])
    1397                          )
    1398                  else:
    1399                          self._TCTRL_person.person = None
    1400
    1401                  self._TCTRL_person.SetToolTipString(tt)
    1402
    1403                  if self.__identity['pk_primary_provider'] is None:
    1404                          self._PRW_provider.SetText(value = u'', data = None)
    1405                  else:
    1406                          self._PRW_provider.SetData(data = self.__identity['pk_primary_provider'])
    1407          #--------------------------------------------------------
    1408          # properties
    1409          #--------------------------------------------------------
    1410 -        def _get_identity(self):
    1411                  return self.__identity
    1412
    1413 -        def _set_identity(self, identity):
    1414                  self.__identity = identity
    1415                  self.refresh()
    1416
    1417          identity = property(_get_identity, _set_identity)
    1418          #--------------------------------------------------------
    1419          # event handlers
    1420          #--------------------------------------------------------
    1421 -        def _on_save_button_pressed(self, event):
    1422                  if self.__identity is not None:
    1423                          self.__identity['emergency_contact'] = self._TCTRL_er_contact.GetValue().strip()
    1424                          if self._TCTRL_person.person is not None:
    1425                                  self.__identity['pk_emergency_contact'] = self._TCTRL_person.person.ID
    1426                          if self._PRW_provider.GetValue().strip == u'':
    1427                                  self.__identity['pk_primary_provider'] = None
    1428                          else:
    1429                                  self.__identity['pk_primary_provider'] = self._PRW_provider.GetData()
    1430
    1431                          self.__identity.save()
    1432                          gmDispatcher.send(signal = 'statustext', msg = _('Emergency data and primary provider saved.'), beep = False)
    1433
    1434                  event.Skip()
    1435          #--------------------------------------------------------
    1436 -        def _on_reload_button_pressed(self, event):
    1437                  self.refresh()
    1438          #--------------------------------------------------------
    1439 -        def _on_remove_contact_button_pressed(self, event):
    1440                  event.Skip()
    1441
    1442                  if self.__identity is None:
    1443                          return
    1444
    1445                  self._TCTRL_person.person = None
    1446
    1447                  self.__identity['pk_emergency_contact'] = None
    1448                  self.__identity.save()
    1449          #--------------------------------------------------------
    1450 -        def _on_button_activate_contact_pressed(self, event):
    1451                  ident = self._TCTRL_person.person
    1452                  if ident is not None:
    1453                          from Gnumed.wxpython import gmPatSearchWidgets
    1454                          gmPatSearchWidgets.set_active_patient(patient = ident, forced_reload = False)
    1455
    1456                  event.Skip()
    1457  #============================================================
    1458  # new-patient widgets
    1459  #============================================================
    1460 -def create_new_person(parent=None, activate=False):
    1461
    1462          dbcfg = gmCfg.cCfgSQL()
    1463
    1464          def_region = dbcfg.get2 (
    1465                  option = u'person.create.default_region',
    1466                  workplace = gmSurgery.gmCurrentPractice().active_workplace,
    1467                  bias = u'user'
    1468          )
    1469          def_country = None
    1470
    1471          if def_region is None:
    1472                  def_country = dbcfg.get2 (
    1473                          option = u'person.create.default_country',
    1474                          workplace = gmSurgery.gmCurrentPractice().active_workplace,
    1475                          bias = u'user'
    1476                  )
    1477          else:
    1478                  countries = gmDemographicRecord.get_country_for_region(region = def_region)
    1479                  if len(countries) == 1:
    1480                          def_country = countries[0]['code_country']
    1481
    1482          if parent is None:
    1483                  parent = wx.GetApp().GetTopWindow()
    1484
    1485          ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region)
    1486          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
    1487          dlg.SetTitle(_('Adding new person'))
    1488          ea._PRW_lastname.SetFocus()
    1489          result = dlg.ShowModal()
    1490          pat = ea.data
    1491          dlg.Destroy()
    1492
    1493          if result != wx.ID_OK:
    1494                  return False
    1495
    1496          _log.debug('created new person [%s]', pat.ID)
    1497
    1498          if activate:
    1499                  from Gnumed.wxpython import gmPatSearchWidgets
    1500                  gmPatSearchWidgets.set_active_patient(patient = pat)
    1501
    1502          gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin')
    1503
    1504          return True
    1505  #============================================================
    1506  from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl
    1507
    1508 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
    1509
    1510 -        def __init__(self, *args, **kwargs):
    1511
    1512                  try:
    1513                          self.default_region = kwargs['region']
    1514                          del kwargs['region']
    1515                  except KeyError:
    1516                          self.default_region = None
    1517
    1518                  try:
    1519                          self.default_country = kwargs['country']
    1520                          del kwargs['country']
    1521                  except KeyError:
    1522                          self.default_country = None
    1523
    1524                  wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs)
    1525                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    1526
    1527                  self.mode = 'new'
    1528                  self.data = None
    1529                  self._address = None
    1530
    1531                  self.__init_ui()
    1532                  self.__register_interests()
    1533          #----------------------------------------------------------------
    1534          # internal helpers
    1535          #----------------------------------------------------------------
    1536 -        def __init_ui(self):
    1537                  self._PRW_lastname.final_regex = '.+'
    1538                  self._PRW_firstnames.final_regex = '.+'
    1539                  self._PRW_address_searcher.selection_only = False
    1540
    1541                  # only if we would support None on selection_only's:
    1542  #               self._PRW_external_id_type.selection_only = True
    1543
    1544                  if self.default_country is not None:
    1545                          match = self._PRW_country._data2match(data = self.default_country)
    1546                          if match is not None:
    1547                                  self._PRW_country.SetText(value = match['field_label'], data = match['data'])
    1548
    1549                  if self.default_region is not None:
    1550                          self._PRW_region.SetText(value = self.default_region)
    1551          #----------------------------------------------------------------
    1552 -        def __perhaps_invalidate_address_searcher(self, ctrl=None, field=None):
    1553
    1554                  adr = self._PRW_address_searcher.address
    1555                  if adr is None:
    1556                          return True
    1557
    1558                  if ctrl.GetValue().strip() != adr[field]:
    1559                          wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None)
    1560                          return True
    1561
    1562                  return False
    1563          #----------------------------------------------------------------
    1564 -        def __set_fields_from_address_searcher(self):
    1565                  adr = self._PRW_address_searcher.address
    1566                  if adr is None:
    1567                          return True
    1568
    1569                  self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode'])
    1570
    1571                  self._PRW_street.SetText(value = adr['street'], data = adr['street'])
    1572                  self._PRW_street.set_context(context = u'zip', val = adr['postcode'])
    1573
    1574                  self._PRW_urb.SetText(value = adr['urb'], data = adr['urb'])
    1575                  self._PRW_urb.set_context(context = u'zip', val = adr['postcode'])
    1576
    1577                  self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state'])
    1578                  self._PRW_region.set_context(context = u'zip', val = adr['postcode'])
    1579
    1580                  self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country'])
    1581                  self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
    1582          #----------------------------------------------------------------
    1583 -        def __identity_valid_for_save(self):
    1584                  error = False
    1585
    1586                  # name fields
    1587                  if self._PRW_lastname.GetValue().strip() == u'':
    1588                          error = True
    1589                          gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
    1590                          self._PRW_lastname.display_as_valid(False)
    1591                  else:
    1592                          self._PRW_lastname.display_as_valid(True)
    1593
    1594                  if self._PRW_firstnames.GetValue().strip() == '':
    1595                          error = True
    1596                          gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
    1597                          self._PRW_firstnames.display_as_valid(False)
    1598                  else:
    1599                          self._PRW_firstnames.display_as_valid(True)
    1600
    1601                  # gender
    1602                  if self._PRW_gender.GetData() is None:
    1603                          error = True
    1604                          gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
    1605                          self._PRW_gender.display_as_valid(False)
    1606                  else:
    1607                          self._PRW_gender.display_as_valid(True)
    1608
    1609                  # dob validation
    1610                  if not _validate_dob_field(self._PRW_dob):
    1611                          error = True
    1612
    1613                  # TOB validation if non-empty
    1614  #               if self._TCTRL_tob.GetValue().strip() != u'':
    1615
    1616                  return (not error)
    1617          #----------------------------------------------------------------
    1618 -        def __address_valid_for_save(self, empty_address_is_valid=False):
    1619
    1620                  # existing address ? if so set other fields
    1621                  if self._PRW_address_searcher.GetData() is not None:
    1622                          wx.CallAfter(self.__set_fields_from_address_searcher)
    1623                          return True
    1624
    1625                  # must either all contain something or none of them
    1626                  fields_to_fill = (
    1627                          self._TCTRL_number,
    1628                          self._PRW_zip,
    1629                          self._PRW_street,
    1630                          self._PRW_urb,
    1631                          self._PRW_type
    1632                  )
    1633                  no_of_filled_fields = 0
    1634
    1635                  for field in fields_to_fill:
    1636                          if field.GetValue().strip() != u'':
    1637                                  no_of_filled_fields += 1
    1638                                  field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
    1639                                  field.Refresh()
    1640
    1641                  # empty address ?
    1642                  if no_of_filled_fields == 0:
    1643                          if empty_address_is_valid:
    1644                                  return True
    1645                          else:
    1646                                  return None
    1647
    1648                  # incompletely filled address ?
    1649                  if no_of_filled_fields != len(fields_to_fill):
    1650                          for field in fields_to_fill:
    1651                                  if field.GetValue().strip() == u'':
    1652                                          field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
    1653                                          field.SetFocus()
    1654                                          field.Refresh()
    1655                          msg = _('To properly create an address, all the related fields must be filled in.')
    1656                          gmGuiHelpers.gm_show_error(msg, _('Required fields'))
    1657                          return False
    1658
    1659                  # fields which must contain a selected item
    1660                  # FIXME: they must also contain an *acceptable combination* which
    1661                  # FIXME: can only be tested against the database itself ...
    1662                  strict_fields = (
    1663                          self._PRW_type,
    1664                          self._PRW_region,
    1665                          self._PRW_country
    1666                  )
    1667                  error = False
    1668                  for field in strict_fields:
    1669                          if field.GetData() is None:
    1670                                  error = True
    1671                                  field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
    1672                                  field.SetFocus()
    1673                          else:
    1674                                  field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
    1675                          field.Refresh()
    1676
    1677                  if error:
    1678                          msg = _('This field must contain an item selected from the dropdown list.')
    1679                          gmGuiHelpers.gm_show_error(msg, _('Required fields'))
    1680                          return False
    1681
    1682                  return True
    1683          #----------------------------------------------------------------
    1684 -        def __register_interests(self):
    1685
    1686                  # identity
    1687                  self._PRW_firstnames.add_callback_on_lose_focus(self._on_leaving_firstname)
    1688
    1689                  # address
    1690                  self._PRW_address_searcher.add_callback_on_lose_focus(self._on_leaving_adress_searcher)
    1691
    1692                  # invalidate address searcher when any field edited
    1693                  self._PRW_street.add_callback_on_lose_focus(self._invalidate_address_searcher)
    1694                  wx.EVT_KILL_FOCUS(self._TCTRL_number, self._on_leaving_number)
    1695                  wx.EVT_KILL_FOCUS(self._TCTRL_unit, self._on_leaving_unit)
    1696                  self._PRW_urb.add_callback_on_lose_focus(self._invalidate_address_searcher)
    1697                  self._PRW_region.add_callback_on_lose_focus(self._invalidate_address_searcher)
    1698
    1699                  self._PRW_zip.add_callback_on_lose_focus(self._on_leaving_zip)
    1700                  self._PRW_country.add_callback_on_lose_focus(self._on_leaving_country)
    1701          #----------------------------------------------------------------
    1702          # event handlers
    1703          #----------------------------------------------------------------
    1704 -        def _on_leaving_firstname(self):
    1705                  """Set the gender according to entered firstname.
    1706
    1707                  Matches are fetched from existing records in backend.
    1708                  """
    1709                  # only set if not already set so as to not
    1710                  # overwrite a change by the user
    1711                  if self._PRW_gender.GetData() is not None:
    1712                          return True
    1713
    1714                  firstname = self._PRW_firstnames.GetValue().strip()
    1715                  if firstname == u'':
    1716                          return True
    1717
    1718                  gender = gmPerson.map_firstnames2gender(firstnames = firstname)
    1719                  if gender is None:
    1720                          return True
    1721
    1722                  wx.CallAfter(self._PRW_gender.SetData, gender)
    1723                  return True
    1724          #----------------------------------------------------------------
    1725 -        def _on_leaving_zip(self):
    1726                  self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode')
    1727
    1728                  zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'')
    1729                  self._PRW_street.set_context(context = u'zip', val = zip_code)
    1730                  self._PRW_urb.set_context(context = u'zip', val = zip_code)
    1731                  self._PRW_region.set_context(context = u'zip', val = zip_code)
    1732                  self._PRW_country.set_context(context = u'zip', val = zip_code)
    1733
    1734                  return True
    1735          #----------------------------------------------------------------
    1736 -        def _on_leaving_country(self):
    1737                  self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country')
    1738
    1739                  country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'')
    1740                  self._PRW_region.set_context(context = u'country', val = country)
    1741
    1742                  return True
    1743          #----------------------------------------------------------------
    1744 -        def _on_leaving_number(self, evt):
    1745                  if self._TCTRL_number.GetValue().strip() == u'':
    1746                          adr = self._PRW_address_searcher.address
    1747                          if adr is None:
    1748                                  return True
    1749                          self._TCTRL_number.SetValue(adr['number'])
    1750                          return True
    1751
    1752                  self.__perhaps_invalidate_address_searcher(self._TCTRL_number, 'number')
    1753                  return True
    1754          #----------------------------------------------------------------
    1755 -        def _on_leaving_unit(self, evt):
    1756                  if self._TCTRL_unit.GetValue().strip() == u'':
    1757                          adr = self._PRW_address_searcher.address
    1758                          if adr is None:
    1759                                  return True
    1760                          self._TCTRL_unit.SetValue(gmTools.coalesce(adr['subunit'], u''))
    1761                          return True
    1762
    1763                  self.__perhaps_invalidate_address_searcher(self._TCTRL_unit, 'subunit')
    1764                  return True
    1765          #----------------------------------------------------------------
    1766 -        def _invalidate_address_searcher(self, *args, **kwargs):
    1767                  mapping = [
    1768                          (self._PRW_street, 'street'),
    1769                          (self._PRW_urb, 'urb'),
    1770                          (self._PRW_region, 'l10n_state')
    1771                  ]
    1772                  # loop through fields and invalidate address searcher if different
    1773                  for ctrl, field in mapping:
    1774                          if self.__perhaps_invalidate_address_searcher(ctrl, field):
    1775                                  return True
    1776
    1777                  return True
    1778          #----------------------------------------------------------------
    1779 -        def _on_leaving_adress_searcher(self):
    1780                  if self._PRW_address_searcher.address is None:
    1781                          return True
    1782
    1783                  wx.CallAfter(self.__set_fields_from_address_searcher)
    1784                  return True
    1785          #----------------------------------------------------------------
    1786          # generic Edit Area mixin API
    1787          #----------------------------------------------------------------
    1788 -        def _valid_for_save(self):
    1789                  if self._PRW_primary_provider.GetValue().strip() == u'':
    1790                          self._PRW_primary_provider.display_as_valid(True)
    1791                  else:
    1792                          if self._PRW_primary_provider.GetData() is None:
    1793                                  self._PRW_primary_provider.display_as_valid(False)
    1794                          else:
    1795                                  self._PRW_primary_provider.display_as_valid(True)
    1796                  return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
    1797          #----------------------------------------------------------------
    1798 -        def _save_as_new(self):
    1799
    1800                  if self._PRW_dob.GetValue().strip() == u'':
    1801                          if not _empty_dob_allowed():
    1802                                  self._PRW_dob.display_as_valid(False)
    1803                                  self._PRW_dob.SetFocus()
    1804                                  return False
    1805
    1806                  # identity
    1807                  new_identity = gmPerson.create_identity (
    1808                          gender = self._PRW_gender.GetData(),
    1809                          dob = self._PRW_dob.GetData(),
    1810                          lastnames = self._PRW_lastname.GetValue().strip(),
    1811                          firstnames = self._PRW_firstnames.GetValue().strip()
    1812                  )
    1813                  _log.debug('identity created: %s' % new_identity)
    1814
    1815                  new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip())
    1816                  new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u''))
    1817                  #TOB
    1818                  prov = self._PRW_primary_provider.GetData()
    1819                  if prov is not None:
    1820                          new_identity['pk_primary_provider'] = prov
    1821                  new_identity['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
    1822                  new_identity.save()
    1823
    1824                  # address
    1825                  # if we reach this the address cannot be completely empty
    1826                  is_valid = self.__address_valid_for_save(empty_address_is_valid = False)
    1827                  if is_valid is True:
    1828                          # because we currently only check for non-emptiness
    1829                          # we must still deal with database errors
    1830                          try:
    1831                                  new_identity.link_address (
    1832                                          number = self._TCTRL_number.GetValue().strip(),
    1833                                          street = self._PRW_street.GetValue().strip(),
    1834                                          postcode = self._PRW_zip.GetValue().strip(),
    1835                                          urb = self._PRW_urb.GetValue().strip(),
    1836                                          state = self._PRW_region.GetData(),
    1837                                          country = self._PRW_country.GetData(),
    1838                                          subunit = gmTools.none_if(self._TCTRL_unit.GetValue().strip(), u''),
    1839                                          id_type = self._PRW_type.GetData()
    1840                                  )
    1841                          except gmPG2.dbapi.InternalError:
    1842                                  _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip())
    1843                                  _log.debug('(sub)unit: >>%s<<', self._TCTRL_unit.GetValue().strip())
    1844                                  _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip())
    1845                                  _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip())
    1846                                  _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip())
    1847                                  _log.debug('state: >>%s<<', self._PRW_region.GetData().strip())
    1848                                  _log.debug('country: >>%s<<', self._PRW_country.GetData().strip())
    1849                                  _log.exception('cannot link address')
    1850                                  gmGuiHelpers.gm_show_error (
    1851                                          aTitle = _('Saving address'),
    1852                                          aMessage = _(
    1853                                                  'Cannot save this address.\n'
    1854                                                  '\n'
    1855                                                  'You will have to add it via the Demographics plugin.\n'
    1856                                          )
    1857                                  )
    1858                  elif is_valid is False:
    1859                          gmGuiHelpers.gm_show_error (
    1860                                  aTitle = _('Saving address'),
    1861                                  aMessage = _(
    1862                                          'Address not saved.\n'
    1863                                          '\n'
    1864                                          'You will have to add it via the Demographics plugin.\n'
    1865                                  )
    1866                          )
    1867                  # else it is None which means empty address which we ignore
    1868
    1869                  # phone
    1870                  channel_name = self._PRW_channel_type.GetValue().strip()
    1871                  pk_channel_type = self._PRW_channel_type.GetData()
    1872                  if pk_channel_type is None:
    1873                          if channel_name == u'':
    1874                                  channel_name = u'homephone'
    1875                  new_identity.link_comm_channel (
    1876                          comm_medium = channel_name,
    1877                          pk_channel_type = pk_channel_type,
    1878                          url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''),
    1879                          is_confidential = False
    1880                  )
    1881
    1882                  # external ID
    1883                  pk_type = self._PRW_external_id_type.GetData()
    1884                  id_value = self._TCTRL_external_id_value.GetValue().strip()
    1885                  if (pk_type is not None) and (id_value != u''):
    1886                          new_identity.add_external_id(value = id_value, pk_type = pk_type)
    1887
    1888                  # occupation
    1889                  new_identity.link_occupation (
    1890                          occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'')
    1891                  )
    1892
    1893                  self.data = new_identity
    1894                  return True
    1895          #----------------------------------------------------------------
    1896 -        def _save_as_update(self):
    1897                  raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
    1898          #----------------------------------------------------------------
    1899 -        def _refresh_as_new(self):
    1900                  # FIXME: button "empty out"
    1901                  return
    1902          #----------------------------------------------------------------
    1903 -        def _refresh_from_existing(self):
    1904                  return          # there is no forward button so nothing to do here
    1905          #----------------------------------------------------------------
    1906 -        def _refresh_as_new_from_existing(self):
    1907                  raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
    1908
    1909  #============================================================
    1910  # patient demographics editing classes
    1911  #============================================================
    1912 -class cPersonDemographicsEditorNb(wx.Notebook):
    1913          """Notebook displaying demographics editing pages:
    1914
    1915                  - Identity (as per Jim/Rogerio 12/2011)
    1916                  - Contacts (addresses, phone numbers, etc)
    1917                  - Social network (significant others, GP, etc)
    1918
    1919          Does NOT act on/listen to the current patient.
    1920          """
    1921          #--------------------------------------------------------
    1922 -        def __init__(self, parent, id):
    1923
    1924                  wx.Notebook.__init__ (
    1925                          self,
    1926                          parent = parent,
    1927                          id = id,
    1928                          style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
    1929                          name = self.__class__.__name__
    1930                  )
    1931
    1932                  self.__identity = None
    1933                  self.__do_layout()
    1934                  self.SetSelection(0)
    1935          #--------------------------------------------------------
    1936          # public API
    1937          #--------------------------------------------------------
    1938 -        def refresh(self):
    1939                  """Populate fields in pages with data from model."""
    1940                  for page_idx in range(self.GetPageCount()):
    1941                          page = self.GetPage(page_idx)
    1942                          page.identity = self.__identity
    1943
    1944                  return True
    1945          #--------------------------------------------------------
    1946          # internal API
    1947          #--------------------------------------------------------
    1948 -        def __do_layout(self):
    1949                  """Build patient edition notebook pages."""
    1950
    1951                  # identity page
    1952                  new_page = cPersonIdentityManagerPnl(self, -1)
    1953                  new_page.identity = self.__identity
    1954                  self.AddPage (
    1955                          page = new_page,
    1956                          text = _('Identity'),
    1957                          select = False
    1958                  )
    1959
    1960                  # contacts page
    1961                  new_page = gmPersonContactWidgets.cPersonContactsManagerPnl(self, -1)
    1962                  new_page.identity = self.__identity
    1963                  self.AddPage (
    1964                          page = new_page,
    1965                          text = _('Contacts'),
    1966                          select = True
    1967                  )
    1968
    1969                  # social network page
    1970                  new_page = cPersonSocialNetworkManagerPnl(self, -1)
    1971                  new_page.identity = self.__identity
    1972                  self.AddPage (
    1973                          page = new_page,
    1974                          text = _('Social network'),
    1975                          select = False
    1976                  )
    1977          #--------------------------------------------------------
    1978          # properties
    1979          #--------------------------------------------------------
    1980 -        def _get_identity(self):
    1981                  return self.__identity
    1982
    1983 -        def _set_identity(self, identity):
    1984                  self.__identity = identity
    1985
    1986          identity = property(_get_identity, _set_identity)
    1987  #============================================================
    1988  # old occupation widgets
    1989  #============================================================
    1990  # FIXME: support multiple occupations
    1991  # FIXME: redo with wxGlade
    1992
    1993 -class cPatOccupationsPanel(wx.Panel):
    1994          """Page containing patient occupations edition fields.
    1995          """
    1996 -        def __init__(self, parent, id, ident=None):
    1997                  """
    1998                  Creates a new instance of BasicPatDetailsPage
    1999                  @param parent - The parent widget
    2000                  @type parent - A wx.Window instance
    2001                  @param id - The widget id
    2002                  @type id - An integer
    2003                  """
    2004                  wx.Panel.__init__(self, parent, id)
    2005                  self.__ident = ident
    2006                  self.__do_layout()
    2007          #--------------------------------------------------------
    2008 -        def __do_layout(self):
    2009                  PNL_form = wx.Panel(self, -1)
    2010                  # occupation
    2011                  STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
    2012                  self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
    2013                  self.PRW_occupation.SetToolTipString(_("primary occupation of the patient"))
    2014                  # known since
    2015                  STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
    2016                  self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
    2017
    2018                  # layout input widgets
    2019                  SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
    2020                  SZR_input.AddGrowableCol(1)
    2021                  SZR_input.Add(STT_occupation, 0, wx.SHAPED)
    2022                  SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
    2023                  SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
    2024                  SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
    2025                  PNL_form.SetSizerAndFit(SZR_input)
    2026
    2027                  # layout page
    2028                  SZR_main = wx.BoxSizer(wx.VERTICAL)
    2029                  SZR_main.Add(PNL_form, 1, wx.EXPAND)
    2030                  self.SetSizer(SZR_main)
    2031          #--------------------------------------------------------
    2032 -        def set_identity(self, identity):
    2033                  return self.refresh(identity=identity)
    2034          #--------------------------------------------------------
    2035 -        def refresh(self, identity=None):
    2036                  if identity is not None:
    2037                          self.__ident = identity
    2038                  jobs = self.__ident.get_occupations()
    2039                  if len(jobs) > 0:
    2040                          self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
    2041                          self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
    2042                  return True
    2043          #--------------------------------------------------------
    2044 -        def save(self):
    2045                  if self.PRW_occupation.IsModified():
    2046                          new_job = self.PRW_occupation.GetValue().strip()
    2047                          jobs = self.__ident.get_occupations()
    2048                          for job in jobs:
    2049                                  if job['l10n_occupation'] == new_job:
    2050                                          continue
    2051                                  self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
    2052                          self.__ident.link_occupation(occupation = new_job)
    2053                  return True
    2054  #============================================================
    2055 -class cNotebookedPatEditionPanel(wx.Panel, gmRegetMixin.cRegetOnPaintMixin):
    2056          """Patient demographics plugin for main notebook.
    2057
    2058          Hosts another notebook with pages for Identity, Contacts, etc.
    2059
    2060          Acts on/listens to the currently active patient.
    2061          """
    2062          #--------------------------------------------------------
    2063 -        def __init__(self, parent, id):
    2064                  wx.Panel.__init__ (self, parent = parent, id = id, style = wx.NO_BORDER)
    2065                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    2066                  self.__do_layout()
    2067                  self.__register_interests()
    2068          #--------------------------------------------------------
    2069          # public API
    2070          #--------------------------------------------------------
    2071          #--------------------------------------------------------
    2072          # internal helpers
    2073          #--------------------------------------------------------
    2074 -        def __do_layout(self):
    2075                  """Arrange widgets."""
    2076                  self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
    2077
    2078                  szr_main = wx.BoxSizer(wx.VERTICAL)
    2079                  szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
    2080                  self.SetSizerAndFit(szr_main)
    2081          #--------------------------------------------------------
    2082          # event handling
    2083          #--------------------------------------------------------
    2084 -        def __register_interests(self):
    2085                  gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
    2086                  gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
    2087          #--------------------------------------------------------
    2088 -        def _on_pre_patient_selection(self):
    2089                  self._schedule_data_reget()
    2090          #--------------------------------------------------------
    2091 -        def _on_post_patient_selection(self):
    2092                  self._schedule_data_reget()
    2093          # reget mixin API
    2094          #--------------------------------------------------------
    2095 -        def _populate_with_data(self):
    2096                  """Populate fields in pages with data from model."""
    2097                  pat = gmPerson.gmCurrentPatient()
    2098                  if pat.connected:
    2099                          self.__patient_notebook.identity = pat
    2100                  else:
    2101                          self.__patient_notebook.identity = None
    2102                  self.__patient_notebook.refresh()
    2103                  return True
    2104  #============================================================
    2105  #============================================================
    2106  if __name__ == "__main__":
    2107
    2108          #--------------------------------------------------------
    2109 -        def test_organizer_pnl():
    2110                  app = wx.PyWidgetTester(size = (600, 400))
    2111                  app.SetWidget(cKOrganizerSchedulePnl)
    2112                  app.MainLoop()
    2113          #--------------------------------------------------------
    2114 -        def test_person_names_pnl():
    2115                  app = wx.PyWidgetTester(size = (600, 400))
    2116                  widget = cPersonNamesManagerPnl(app.frame, -1)
    2117                  widget.identity = activate_patient()
    2118                  app.frame.Show(True)
    2119                  app.MainLoop()
    2120          #--------------------------------------------------------
    2121 -        def test_person_ids_pnl():
    2122                  app = wx.PyWidgetTester(size = (600, 400))
    2123                  widget = cPersonIDsManagerPnl(app.frame, -1)
    2124                  widget.identity = activate_patient()
    2125                  app.frame.Show(True)
    2126                  app.MainLoop()
    2127          #--------------------------------------------------------
    2128 -        def test_pat_ids_pnl():
    2129                  app = wx.PyWidgetTester(size = (600, 400))
    2130                  widget = cPersonIdentityManagerPnl(app.frame, -1)
    2131                  widget.identity = activate_patient()
    2132                  app.frame.Show(True)
    2133                  app.MainLoop()
    2134          #--------------------------------------------------------
    2135 -        def test_name_ea_pnl():
    2136                  app = wx.PyWidgetTester(size = (600, 400))
    2137                  app.SetWidget(cPersonNameEAPnl, name = activate_patient().get_active_name())
    2138                  app.MainLoop()
    2139          #--------------------------------------------------------
    2140 -        def test_cPersonDemographicsEditorNb():
    2141                  app = wx.PyWidgetTester(size = (600, 400))
    2142                  widget = cPersonDemographicsEditorNb(app.frame, -1)
    2143                  widget.identity = activate_patient()
    2144                  widget.refresh()
    2145                  app.frame.Show(True)
    2146                  app.MainLoop()
    2147          #--------------------------------------------------------
    2148 -        def activate_patient():
    2149                  patient = gmPersonSearch.ask_for_patient()
    2150                  if patient is None:
    2151                          print "No patient. Exiting gracefully..."
    2152                          sys.exit(0)
    2153                  from Gnumed.wxpython import gmPatSearchWidgets
    2154                  gmPatSearchWidgets.set_active_patient(patient=patient)
    2155                  return patient
    2156          #--------------------------------------------------------
    2157          if len(sys.argv) > 1 and sys.argv[1] == 'test':
    2158
    2159                  gmI18N.activate_locale()
    2160                  gmI18N.install_domain(domain='gnumed')
    2161                  gmPG2.get_connection()
    2162
    2163  #               app = wx.PyWidgetTester(size = (400, 300))
    2164  #               app.SetWidget(cNotebookedPatEditionPanel, -1)
    2165  #               app.frame.Show(True)
    2166  #               app.MainLoop()
    2167
    2168                  # phrasewheels
    2169  #               test_organizer_pnl()
    2170
    2171                  # identity related widgets
    2172                  #test_person_names_pnl()
    2173                  test_person_ids_pnl()
    2174                  #test_pat_ids_pnl()
    2175                  #test_name_ea_pnl()
    2176
    2177                  #test_cPersonDemographicsEditorNb()
    2178
    2179  #============================================================
    2180