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.DestroyLater()
      78                  return True
      79          dlg.DestroyLater()
      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                                  self.StatusText = _('Must pick an image file for a new tag.')
     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                                  self.StatusText = _('Cannot open the image file [%s].')
     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.DestroyLater()
     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                          self.StatusText = _('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                          gmGuiHelpers.gm_show_error (
    1131                                  aTitle = _('Adding name'),
    1132                                  aMessage = _(
    1133                                          'Cannot add this name to the patient !\n'
    1134                                          '\n'
    1135                                          ' %s'
    1136                                  ) % exc.pgerror
    1137                          )
    1138                          return False
    1139
    1140                  old_nick = self.__identity['active_name']['preferred']
    1141                  new_nick = gmTools.none_if(self._PRW_nick.GetValue().strip(), '')
    1142                  if active:
    1143                          data['preferred'] = gmTools.coalesce(new_nick, old_nick)
    1144                  else:
    1145                          data['preferred'] = new_nick
    1146                  data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
    1147                  data.save()
    1148
    1149                  self.data = data
    1150                  return True
    1151          #----------------------------------------------------------------
    1152 -        def _save_as_update(self):
    1153                  """The knack here is that we can only update a few fields.
    1154
    1155                  Otherwise we need to clone the name and update that.
    1156                  """
    1157                  first = self._PRW_firstname.GetValue().strip()
    1158                  last = self._PRW_lastname.GetValue().strip()
    1159                  active = self._CHBOX_active.GetValue()
    1160
    1161                  current_name = self.data['firstnames'].strip() + self.data['lastnames'].strip()
    1162                  new_name = first + last
    1163
    1164                  # editable fields only ?
    1165                  if new_name == current_name:
    1166                          self.data['active_name'] = self._CHBOX_active.GetValue()
    1167                          self.data['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), '')
    1168                          self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
    1169                          self.data.save()
    1170                  # else clone name and update that
    1171                  else:
    1172                          try:
    1173                                  name = self.__identity.add_name(first, last, active)
    1174                          except gmPG2.dbapi.IntegrityError as exc:
    1175                                  _log.exception('cannot clone name when editing existing name')
    1176                                  gmGuiHelpers.gm_show_error (
    1177                                          aTitle = _('Editing name'),
    1178                                          aMessage = _(
    1179                                                  'Cannot clone a copy of this name !\n'
    1180                                                  '\n'
    1181                                                  ' %s'
    1182                                          ) % exc.pgerror
    1183                                  )
    1184                                  return False
    1185                          name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), '')
    1186                          name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
    1187                          name.save()
    1188                          self.data = name
    1189
    1190                  return True
    1191          #----------------------------------------------------------------
    1192 -        def _refresh_as_new(self):
    1193                  self._PRW_firstname.SetText(value = '', data = None)
    1194                  self._PRW_lastname.SetText(value = '', data = None)
    1195                  self._PRW_nick.SetText(value = '', data = None)
    1196                  self._TCTRL_comment.SetValue('')
    1197                  self._CHBOX_active.SetValue(False)
    1198
    1199                  self._PRW_firstname.SetFocus()
    1200          #----------------------------------------------------------------
    1201 -        def _refresh_as_new_from_existing(self):
    1202                  self._refresh_as_new()
    1203                  self._PRW_firstname.SetText(value = '', data = None)
    1204                  self._PRW_nick.SetText(gmTools.coalesce(self.data['preferred'], ''))
    1205
    1206                  self._PRW_lastname.SetFocus()
    1207          #----------------------------------------------------------------
    1208 -        def _refresh_from_existing(self):
    1209                  self._PRW_firstname.SetText(self.data['firstnames'])
    1210                  self._PRW_lastname.SetText(self.data['lastnames'])
    1211                  self._PRW_nick.SetText(gmTools.coalesce(self.data['preferred'], ''))
    1212                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
    1213                  self._CHBOX_active.SetValue(self.data['active_name'])
    1214
    1215                  self._TCTRL_comment.SetFocus()
    1216  #------------------------------------------------------------
    1217  # list manager
    1218  #------------------------------------------------------------
    1219 -class cPersonNamesManagerPnl(gmListWidgets.cGenericListManagerPnl):
    1220          """A list for managing a person's names.
    1221
    1222          Does NOT act on/listen to the current patient.
    1223          """
    1224 -        def __init__(self, *args, **kwargs):
    1225
    1226                  try:
    1227                          self.__identity = kwargs['identity']
    1228                          del kwargs['identity']
    1229                  except KeyError:
    1230                          self.__identity = None
    1231
    1232                  gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs)
    1233
    1234                  self.refresh_callback = self.refresh
    1235                  self.new_callback = self._add_name
    1236                  self.edit_callback = self._edit_name
    1237                  self.delete_callback = self._del_name
    1238
    1239                  self.__init_ui()
    1240                  self.refresh()
    1241          #--------------------------------------------------------
    1242          # external API
    1243          #--------------------------------------------------------
    1244 -        def refresh(self, *args, **kwargs):
    1245                  if self.__identity is None:
    1246                          self._LCTRL_items.set_string_items()
    1247                          return
    1248
    1249                  names = self.__identity.get_names()
    1250                  self._LCTRL_items.set_string_items (
    1251                          items = [ [
    1252                                          gmTools.bool2str(n['active_name'], 'X', ''),
    1253                                          n['lastnames'],
    1254                                          n['firstnames'],
    1255                                          gmTools.coalesce(n['preferred'], ''),
    1256                                          gmTools.coalesce(n['comment'], '')
    1257                                  ] for n in names ]
    1258                  )
    1259                  self._LCTRL_items.set_column_widths()
    1260                  self._LCTRL_items.set_data(data = names)
    1261          #--------------------------------------------------------
    1262          # internal helpers
    1263          #--------------------------------------------------------
    1264 -        def __init_ui(self):
    1265                  self._LCTRL_items.set_columns(columns = [
    1266                          _('Active'),
    1267                          _('Lastname'),
    1268                          _('Firstname(s)'),
    1269                          _('Preferred Name'),
    1270                          _('Comment')
    1271                  ])
    1272          #--------------------------------------------------------
    1273 -        def _add_name(self):
    1274                  #ea = cPersonNameEAPnl(self, -1, name = self.__identity.get_active_name())
    1275                  ea = cPersonNameEAPnl(self, -1, identity = self.__identity)
    1276                  dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True)
    1277                  dlg.SetTitle(_('Adding new name'))
    1278                  if dlg.ShowModal() == wx.ID_OK:
    1279                          dlg.DestroyLater()
    1280                          return True
    1281                  dlg.DestroyLater()
    1282                  return False
    1283          #--------------------------------------------------------
    1284 -        def _edit_name(self, name):
    1285                  ea = cPersonNameEAPnl(self, -1, name = name)
    1286                  dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True)
    1287                  dlg.SetTitle(_('Editing name'))
    1288                  if dlg.ShowModal() == wx.ID_OK:
    1289                          dlg.DestroyLater()
    1290                          return True
    1291                  dlg.DestroyLater()
    1292                  return False
    1293          #--------------------------------------------------------
    1294 -        def _del_name(self, name):
    1295
    1296                  if len(self.__identity.get_names()) == 1:
    1297                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete the only name of a person.'), beep = True)
    1298                          return False
    1299
    1300                  if name['active_name']:
    1301                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete the active name of a person.'), beep = True)
    1302                          return False
    1303
    1304                  go_ahead = gmGuiHelpers.gm_show_question (
    1305                          _(      'It is often advisable to keep old names around and\n'
    1306                                  'just create a new "currently active" name.\n'
    1307                                  '\n'
    1308                                  'This allows finding the patient by both the old\n'
    1309                                  'and the new name (think before/after marriage).\n'
    1310                                  '\n'
    1311                                  'Do you still want to really delete\n'
    1312                                  "this name from the patient ?"
    1313                          ),
    1314                          _('Deleting name')
    1315                  )
    1316                  if not go_ahead:
    1317                          return False
    1318
    1319                  self.__identity.delete_name(name = name)
    1320                  return True
    1321          #--------------------------------------------------------
    1322          # properties
    1323          #--------------------------------------------------------
    1324 -        def _get_identity(self):
    1325                  return self.__identity
    1326
    1327 -        def _set_identity(self, identity):
    1328                  self.__identity = identity
    1329                  self.refresh()
    1330
    1331          identity = property(_get_identity, _set_identity)
    1332
    1333  #------------------------------------------------------------
    1334 -class cPersonIDsManagerPnl(gmListWidgets.cGenericListManagerPnl):
    1335          """A list for managing a person's external IDs.
    1336
    1337          Does NOT act on/listen to the current patient.
    1338          """
    1339 -        def __init__(self, *args, **kwargs):
    1340
    1341                  try:
    1342                          self.__identity = kwargs['identity']
    1343                          del kwargs['identity']
    1344                  except KeyError:
    1345                          self.__identity = None
    1346
    1347                  gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs)
    1348
    1349                  self.refresh_callback = self.refresh
    1350                  self.new_callback = self._add_id
    1351                  self.edit_callback = self._edit_id
    1352                  self.delete_callback = self._del_id
    1353
    1354                  self.__init_ui()
    1355                  self.refresh()
    1356          #--------------------------------------------------------
    1357          # external API
    1358          #--------------------------------------------------------
    1359 -        def refresh(self, *args, **kwargs):
    1360                  if self.__identity is None:
    1361                          self._LCTRL_items.set_string_items()
    1362                          return
    1363
    1364                  ids = self.__identity.get_external_ids()
    1365                  self._LCTRL_items.set_string_items (
    1366                          items = [ [
    1367                                          i['name'],
    1368                                          i['value'],
    1369                                          gmTools.coalesce(i['issuer'], ''),
    1370                                          gmTools.coalesce(i['comment'], '')
    1371                                  ] for i in ids
    1372                          ]
    1373                  )
    1374                  self._LCTRL_items.set_column_widths()
    1375                  self._LCTRL_items.set_data(data = ids)
    1376          #--------------------------------------------------------
    1377          # internal helpers
    1378          #--------------------------------------------------------
    1379 -        def __init_ui(self):
    1380                  self._LCTRL_items.set_columns(columns = [
    1381                          _('ID type'),
    1382                          _('Value'),
    1383                          _('Issuer'),
    1384                          _('Comment')
    1385                  ])
    1386          #--------------------------------------------------------
    1387 -        def _add_id(self):
    1388                  ea = cExternalIDEditAreaPnl(self, -1)
    1389                  ea.id_holder = self.__identity
    1390                  dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea)
    1391                  dlg.SetTitle(_('Adding new external ID'))
    1392                  if dlg.ShowModal() == wx.ID_OK:
    1393                          dlg.DestroyLater()
    1394                          return True
    1395                  dlg.DestroyLater()
    1396                  return False
    1397          #--------------------------------------------------------
    1398 -        def _edit_id(self, ext_id):
    1399                  ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id)
    1400                  ea.id_holder = self.__identity
    1401                  dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True)
    1402                  dlg.SetTitle(_('Editing external ID'))
    1403                  if dlg.ShowModal() == wx.ID_OK:
    1404                          dlg.DestroyLater()
    1405                          return True
    1406                  dlg.DestroyLater()
    1407                  return False
    1408          #--------------------------------------------------------
    1409 -        def _del_id(self, ext_id):
    1410                  go_ahead = gmGuiHelpers.gm_show_question (
    1411                          _(      'Do you really want to delete this\n'
    1412                                  'external ID from the patient ?'),
    1413                          _('Deleting external ID')
    1414                  )
    1415                  if not go_ahead:
    1416                          return False
    1417                  self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
    1418                  return True
    1419          #--------------------------------------------------------
    1420          # properties
    1421          #--------------------------------------------------------
    1422 -        def _get_identity(self):
    1423                  return self.__identity
    1424
    1425 -        def _set_identity(self, identity):
    1426                  self.__identity = identity
    1427                  self.refresh()
    1428
    1429          identity = property(_get_identity, _set_identity)
    1430  #------------------------------------------------------------
    1431  # integrated panels
    1432  #------------------------------------------------------------
    1433  from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl
    1434
    1435 -class cPersonIdentityManagerPnl(wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl):
    1436          """A panel for editing identity data for a person.
    1437
    1438          - provides access to:
    1439            - identity EA
    1440            - name list manager
    1441            - external IDs list manager
    1442
    1443          Does NOT act on/listen to the current patient.
    1444          """
    1445 -        def __init__(self, *args, **kwargs):
    1446
    1447                  wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl.__init__(self, *args, **kwargs)
    1448
    1449                  self.__identity = None
    1450                  self.refresh()
    1451          #--------------------------------------------------------
    1452          # external API
    1453          #--------------------------------------------------------
    1454 -        def refresh(self):
    1455                  self._PNL_names.identity = self.__identity
    1456                  self._PNL_ids.identity = self.__identity
    1457                  # this is an Edit Area:
    1458                  self._PNL_identity.mode = 'new'
    1459                  self._PNL_identity.data = self.__identity
    1460                  if self.__identity is not None:
    1461                          self._PNL_identity.mode = 'edit'
    1462                          self._PNL_identity._refresh_from_existing()
    1463          #--------------------------------------------------------
    1464          # properties
    1465          #--------------------------------------------------------
    1466 -        def _get_identity(self):
    1467                  return self.__identity
    1468
    1469 -        def _set_identity(self, identity):
    1470                  self.__identity = identity
    1471                  self.refresh()
    1472
    1473          identity = property(_get_identity, _set_identity)
    1474          #--------------------------------------------------------
    1475          # event handlers
    1476          #--------------------------------------------------------
    1477 -        def _on_save_identity_details_button_pressed(self, event):
    1478                  if not self._PNL_identity.save():
    1479                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot save identity. Incomplete information.'), beep = True)
    1480                  #self._PNL_identity.refresh()
    1481          #--------------------------------------------------------
    1482 -        def _on_reload_identity_button_pressed(self, event):
    1483                  self._PNL_identity.refresh()
    1484
    1485  #============================================================
    1486  from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl
    1487
    1488 -class cPersonSocialNetworkManagerPnl(wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl):
    1489 -        def __init__(self, *args, **kwargs):
    1490
    1491                  wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl.__init__(self, *args, **kwargs)
    1492
    1493                  self.__identity = None
    1494                  self._PRW_provider.selection_only = False
    1495                  self.refresh()
    1496          #--------------------------------------------------------
    1497          # external API
    1498          #--------------------------------------------------------
    1499 -        def refresh(self):
    1500
    1501                  tt = _('Link another person in this database as the emergency contact:\n\nEnter person name part or identifier and hit <enter>.')
    1502
    1503                  if self.__identity is None:
    1504                          self._TCTRL_er_contact.SetValue('')
    1505                          self._TCTRL_person.person = None
    1506                          self._TCTRL_person.SetToolTip(tt)
    1507
    1508                          self._PRW_provider.SetText(value = '', data = None)
    1509                          return
    1510
    1511                  self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], ''))
    1512                  if self.__identity['pk_emergency_contact'] is not None:
    1513                          ident = gmPerson.cPerson(aPK_obj = self.__identity['pk_emergency_contact'])
    1514                          self._TCTRL_person.person = ident
    1515                          tt = '%s\n\n%s\n\n%s' % (
    1516                                  tt,
    1517                                  ident['description_gender'],
    1518                                  '\n'.join([
    1519                                          '%s: %s%s' % (
    1520                                                  c['l10n_comm_type'],
    1521                                                  c['url'],
    1522                                                  gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), '', '')
    1523                                          )
    1524                                          for c in ident.get_comm_channels()
    1525                                  ])
    1526                          )
    1527                  else:
    1528                          self._TCTRL_person.person = None
    1529
    1530                  self._TCTRL_person.SetToolTip(tt)
    1531
    1532                  if self.__identity['pk_primary_provider'] is None:
    1533                          self._PRW_provider.SetText(value = '', data = None)
    1534                  else:
    1535                          self._PRW_provider.SetData(data = self.__identity['pk_primary_provider'])
    1536
    1537                  self._PNL_external_care.identity = self.__identity
    1538          #--------------------------------------------------------
    1539          # properties
    1540          #--------------------------------------------------------
    1541 -        def _get_identity(self):
    1542                  return self.__identity
    1543
    1544 -        def _set_identity(self, identity):
    1545                  self.__identity = identity
    1546                  self.refresh()
    1547
    1548          identity = property(_get_identity, _set_identity)
    1549          #--------------------------------------------------------
    1550          # event handlers
    1551          #--------------------------------------------------------
    1552 -        def _on_save_button_pressed(self, event):
    1553                  if self.__identity is not None:
    1554                          self.__identity['emergency_contact'] = self._TCTRL_er_contact.GetValue().strip()
    1555                          if self._TCTRL_person.person is not None:
    1556                                  self.__identity['pk_emergency_contact'] = self._TCTRL_person.person.ID
    1557                          if self._PRW_provider.GetValue().strip == '':
    1558                                  self.__identity['pk_primary_provider'] = None
    1559                          else:
    1560                                  self.__identity['pk_primary_provider'] = self._PRW_provider.GetData()
    1561
    1562                          self.__identity.save()
    1563                          gmDispatcher.send(signal = 'statustext', msg = _('Emergency data and primary provider saved.'), beep = False)
    1564
    1565                  event.Skip()
    1566          #--------------------------------------------------------
    1567 -        def _on_reload_button_pressed(self, event):
    1568                  self.refresh()
    1569          #--------------------------------------------------------
    1570 -        def _on_remove_contact_button_pressed(self, event):
    1571                  event.Skip()
    1572
    1573                  if self.__identity is None:
    1574                          return
    1575
    1576                  self._TCTRL_person.person = None
    1577
    1578                  self.__identity['pk_emergency_contact'] = None
    1579                  self.__identity.save()
    1580          #--------------------------------------------------------
    1581 -        def _on_button_activate_contact_pressed(self, event):
    1582                  ident = self._TCTRL_person.person
    1583                  if ident is not None:
    1584                          from Gnumed.wxpython.gmPatSearchWidgets import set_active_patient
    1585                          wx.CallAfter(set_active_patient, patient = ident)
    1586
    1587                  event.Skip()
    1588
    1589  #============================================================
    1590  # patient demographics editing classes
    1591  #============================================================
    1592 -class cPersonDemographicsEditorNb(wx.Notebook):
    1593          """Notebook displaying demographics editing pages:
    1594
    1595                  - Identity (as per Jim/Rogerio 12/2011)
    1596                  - Contacts (addresses, phone numbers, etc)
    1597                  - Social network (significant others, GP, etc)
    1598
    1599          Does NOT act on/listen to the current patient.
    1600          """
    1601          #--------------------------------------------------------
    1602 -        def __init__(self, parent, id):
    1603
    1604                  wx.Notebook.__init__ (
    1605                          self,
    1606                          parent = parent,
    1607                          id = id,
    1608                          style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
    1609                          name = self.__class__.__name__
    1610                  )
    1611                  _log.debug('created wx.Notebook: %s with ID %s', self.__class__.__name__, self.Id)
    1612
    1613                  self.__identity = None
    1614                  self.__do_layout()
    1615                  self.SetSelection(0)
    1616          #--------------------------------------------------------
    1617          # public API
    1618          #--------------------------------------------------------
    1619 -        def refresh(self):
    1620                  """Populate fields in pages with data from model."""
    1621                  for page_idx in range(self.GetPageCount()):
    1622                          page = self.GetPage(page_idx)
    1623                          page.identity = self.__identity
    1624
    1625                  return True
    1626          #--------------------------------------------------------
    1627          # internal API
    1628          #--------------------------------------------------------
    1629 -        def __do_layout(self):
    1630                  """Build patient edition notebook pages."""
    1631
    1632                  # identity page
    1633                  new_page = cPersonIdentityManagerPnl(self, -1)
    1634                  new_page.identity = self.__identity
    1635                  self.AddPage (
    1636                          page = new_page,
    1637                          text = _('Identity'),
    1638                          select = False
    1639                  )
    1640
    1641                  # contacts page
    1642                  new_page = gmPersonContactWidgets.cPersonContactsManagerPnl(self, -1)
    1643                  new_page.identity = self.__identity
    1644                  self.AddPage (
    1645                          page = new_page,
    1646                          text = _('Contacts'),
    1647                          select = True
    1648                  )
    1649
    1650                  # social network page
    1651                  new_page = cPersonSocialNetworkManagerPnl(self, -1)
    1652                  new_page.identity = self.__identity
    1653                  self.AddPage (
    1654                          page = new_page,
    1655                          text = _('Social network'),
    1656                          select = False
    1657                  )
    1658          #--------------------------------------------------------
    1659          # properties
    1660          #--------------------------------------------------------
    1661 -        def _get_identity(self):
    1662                  return self.__identity
    1663
    1664 -        def _set_identity(self, identity):
    1665                  self.__identity = identity
    1666
    1667          identity = property(_get_identity, _set_identity)
    1668
    1669  #============================================================
    1670  # old occupation widgets
    1671  #============================================================
    1672  # FIXME: support multiple occupations
    1673  # FIXME: redo with wxGlade
    1674
    1675 -class cPatOccupationsPanel(wx.Panel):
    1676          """Page containing patient occupations edition fields.
    1677          """
    1678 -        def __init__(self, parent, id, ident=None):
    1679                  """
    1680                  Creates a new instance of BasicPatDetailsPage
    1681                  @param parent - The parent widget
    1682                  @type parent - A wx.Window instance
    1683                  @param id - The widget id
    1684                  @type id - An integer
    1685                  """
    1686                  wx.Panel.__init__(self, parent, id)
    1687                  self.__ident = ident
    1688                  self.__do_layout()
    1689          #--------------------------------------------------------
    1690 -        def __do_layout(self):
    1691                  PNL_form = wx.Panel(self, -1)
    1692                  # occupation
    1693                  STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
    1694                  self.PRW_occupation = cOccupationPhraseWheel(PNL_form, -1)
    1695                  self.PRW_occupation.SetToolTip(_("primary occupation of the patient"))
    1696                  # known since
    1697                  STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
    1698                  self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
    1699
    1700                  # layout input widgets
    1701                  SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
    1702                  SZR_input.AddGrowableCol(1)
    1703                  SZR_input.Add(STT_occupation, 0, wx.SHAPED)
    1704                  SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
    1705                  SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
    1706                  SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
    1707                  PNL_form.SetSizerAndFit(SZR_input)
    1708
    1709                  # layout page
    1710                  SZR_main = wx.BoxSizer(wx.VERTICAL)
    1711                  SZR_main.Add(PNL_form, 1, wx.EXPAND)
    1712                  self.SetSizer(SZR_main)
    1713          #--------------------------------------------------------
    1714 -        def set_identity(self, identity):
    1715                  return self.refresh(identity=identity)
    1716          #--------------------------------------------------------
    1717 -        def refresh(self, identity=None):
    1718                  if identity is not None:
    1719                          self.__ident = identity
    1720                  jobs = self.__ident.get_occupations()
    1721                  if len(jobs) > 0:
    1722                          self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
    1723                          self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
    1724                  return True
    1725          #--------------------------------------------------------
    1726 -        def save(self):
    1727                  if self.PRW_occupation.IsModified():
    1728                          new_job = self.PRW_occupation.GetValue().strip()
    1729                          jobs = self.__ident.get_occupations()
    1730                          for job in jobs:
    1731                                  if job['l10n_occupation'] == new_job:
    1732                                          continue
    1733                                  self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
    1734                          self.__ident.link_occupation(occupation = new_job)
    1735                  return True
    1736
    1737  #============================================================
    1738 -class cNotebookedPatEditionPanel(wx.Panel, gmRegetMixin.cRegetOnPaintMixin):
    1739          """Patient demographics plugin for main notebook.
    1740
    1741          Hosts another notebook with pages for Identity, Contacts, etc.
    1742
    1743          Acts on/listens to the currently active patient.
    1744          """
    1745          #--------------------------------------------------------
    1746 -        def __init__(self, parent, id):
    1747                  wx.Panel.__init__ (self, parent = parent, id = id, style = wx.NO_BORDER)
    1748                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    1749                  self.__do_layout()
    1750                  self.__register_interests()
    1751          #--------------------------------------------------------
    1752          # public API
    1753          #--------------------------------------------------------
    1754          #--------------------------------------------------------
    1755          # internal helpers
    1756          #--------------------------------------------------------
    1757 -        def __do_layout(self):
    1758                  """Arrange widgets."""
    1759                  self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
    1760
    1761                  szr_main = wx.BoxSizer(wx.VERTICAL)
    1762                  szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
    1763                  self.SetSizerAndFit(szr_main)
    1764          #--------------------------------------------------------
    1765          # event handling
    1766          #--------------------------------------------------------
    1767 -        def __register_interests(self):
    1768                  gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
    1769                  gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
    1770          #--------------------------------------------------------
    1771 -        def _on_pre_patient_unselection(self):
    1772                  self.__patient_notebook.identity = None
    1773                  self.__patient_notebook.refresh()
    1774          #--------------------------------------------------------
    1775 -        def _on_post_patient_selection(self):
    1776                  self._schedule_data_reget()
    1777          #--------------------------------------------------------
    1778          # reget mixin API
    1779          #--------------------------------------------------------
    1780 -        def _populate_with_data(self):
    1781                  """Populate fields in pages with data from model."""
    1782                  pat = gmPerson.gmCurrentPatient()
    1783                  if pat.connected:
    1784                          self.__patient_notebook.identity = pat
    1785                  else:
    1786                          self.__patient_notebook.identity = None
    1787                  self.__patient_notebook.refresh()
    1788                  return True
    1789
    1790  #============================================================
    1791  #============================================================
    1792  if __name__ == "__main__":
    1793
    1794          #--------------------------------------------------------
    1795 -        def test_organizer_pnl():
    1796                  app = wx.PyWidgetTester(size = (600, 400))
    1797                  app.SetWidget(cKOrganizerSchedulePnl)
    1798                  app.MainLoop()
    1799          #--------------------------------------------------------
    1800 -        def test_person_names_pnl():
    1801                  app = wx.PyWidgetTester(size = (600, 400))
    1802                  widget = cPersonNamesManagerPnl(app.frame, -1)
    1803                  widget.identity = activate_patient()
    1804                  app.frame.Show(True)
    1805                  app.MainLoop()
    1806          #--------------------------------------------------------
    1807 -        def test_person_ids_pnl():
    1808                  app = wx.PyWidgetTester(size = (600, 400))
    1809                  widget = cPersonIDsManagerPnl(app.frame, -1)
    1810                  widget.identity = activate_patient()
    1811                  app.frame.Show(True)
    1812                  app.MainLoop()
    1813          #--------------------------------------------------------
    1814 -        def test_pat_ids_pnl():
    1815                  app = wx.PyWidgetTester(size = (600, 400))
    1816                  widget = cPersonIdentityManagerPnl(app.frame, -1)
    1817                  widget.identity = activate_patient()
    1818                  app.frame.Show(True)
    1819                  app.MainLoop()
    1820          #--------------------------------------------------------
    1821 -        def test_name_ea_pnl():
    1822                  app = wx.PyWidgetTester(size = (600, 400))
    1823                  app.SetWidget(cPersonNameEAPnl, name = activate_patient().get_active_name())
    1824                  app.MainLoop()
    1825          #--------------------------------------------------------
    1826 -        def test_cPersonDemographicsEditorNb():
    1827                  app = wx.PyWidgetTester(size = (600, 400))
    1828                  widget = cPersonDemographicsEditorNb(app.frame, -1)
    1829                  widget.identity = activate_patient()
    1830                  widget.refresh()
    1831                  app.frame.Show(True)
    1832                  app.MainLoop()
    1833          #--------------------------------------------------------
    1834 -        def activate_patient():
    1835                  patient = gmPersonSearch.ask_for_patient()
    1836                  if patient is None:
    1837                          print("No patient. Exiting gracefully...")
    1838                          sys.exit(0)
    1839                  from Gnumed.wxpython.gmPatSearchWidgets import set_active_patient
    1840                  set_active_patient(patient = patient)
    1841                  return patient
    1842          #--------------------------------------------------------
    1843          if len(sys.argv) > 1 and sys.argv[1] == 'test':
    1844
    1845                  gmI18N.activate_locale()
    1846                  gmI18N.install_domain(domain='gnumed')
    1847                  gmPG2.get_connection()
    1848
    1849  #               app = wx.PyWidgetTester(size = (400, 300))
    1850  #               app.SetWidget(cNotebookedPatEditionPanel, -1)
    1851  #               app.frame.Show(True)
    1852  #               app.MainLoop()
    1853
    1854                  # phrasewheels
    1855  #               test_organizer_pnl()
    1856
    1857                  # identity related widgets
    1858                  #test_person_names_pnl()
    1859                  test_person_ids_pnl()
    1860                  #test_pat_ids_pnl()
    1861                  #test_name_ea_pnl()
    1862
    1863                  #test_cPersonDemographicsEditorNb()
    1864
    1865  #============================================================
    1866