Gnumed.wxpython.gmDemographicsWidgets

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