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

# Source Code for Module Gnumed.wxpython.gmMeasurementWidgets

       1  """GNUmed measurement widgets."""
       2  #================================================================
       3  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
       4  __license__ = "GPL"
       5
       6
       7  import sys
       8  import logging
       9  import datetime as pyDT
      10  import decimal
      11  import os
      12  import subprocess
      13  import io
      14  import os.path
      15
      16
      17  import wx
      18  import wx.grid
      19  import wx.adv as wxh
      20
      21
      22  if __name__ == '__main__':
      23          sys.path.insert(0, '../../')
      24  from Gnumed.pycommon import gmTools
      25  from Gnumed.pycommon import gmNetworkTools
      26  from Gnumed.pycommon import gmI18N
      27  from Gnumed.pycommon import gmShellAPI
      28  from Gnumed.pycommon import gmCfg
      29  from Gnumed.pycommon import gmDateTime
      30  from Gnumed.pycommon import gmMatchProvider
      31  from Gnumed.pycommon import gmDispatcher
      32  from Gnumed.pycommon import gmMimeLib
      33
      34  from Gnumed.business import gmPerson
      35  from Gnumed.business import gmStaff
      36  from Gnumed.business import gmPathLab
      37  from Gnumed.business import gmPraxis
      38  from Gnumed.business import gmLOINC
      39  from Gnumed.business import gmForms
      40  from Gnumed.business import gmPersonSearch
      41  from Gnumed.business import gmOrganization
      42  from Gnumed.business import gmHL7
      43  from Gnumed.business import gmIncomingData
      44  from Gnumed.business import gmDocuments
      45
      46  from Gnumed.wxpython import gmRegetMixin
      47  from Gnumed.wxpython import gmPlugin
      48  from Gnumed.wxpython import gmEditArea
      49  from Gnumed.wxpython import gmPhraseWheel
      50  from Gnumed.wxpython import gmListWidgets
      51  from Gnumed.wxpython import gmGuiHelpers
      52  from Gnumed.wxpython import gmAuthWidgets
      53  from Gnumed.wxpython import gmOrganizationWidgets
      54  from Gnumed.wxpython import gmEMRStructWidgets
      55  from Gnumed.wxpython import gmCfgWidgets
      56  from Gnumed.wxpython import gmDocumentWidgets
      57
      58
      59  _log = logging.getLogger('gm.ui')
      60
      61  #================================================================
      62  # HL7 related widgets
      63  #================================================================
      64 -def show_hl7_file(parent=None):
      65
      66          if parent is None:
      67                  parent = wx.GetApp().GetTopWindow()
      68
      69          # select file
      70          paths = gmTools.gmPaths()
      71          dlg = wx.FileDialog (
      72                  parent = parent,
      73                  message = _('Show HL7 file:'),
      74                  # make configurable:
      75                  defaultDir = os.path.join(paths.home_dir, 'gnumed'),
      76                  wildcard = "hl7 files|*.hl7|HL7 files|*.HL7|all files|*",
      77                  style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
      78          )
      79          choice = dlg.ShowModal()
      80          hl7_name = dlg.GetPath()
      81          dlg.DestroyLater()
      82          if choice != wx.ID_OK:
      83                  return False
      84
      85          formatted_name = gmHL7.format_hl7_file (
      86                  hl7_name,
      87                  skip_empty_fields = True,
      88                  return_filename = True,
      89                  fix_hl7 = True
      90          )
      91          gmMimeLib.call_viewer_on_file(aFile = formatted_name, block = False)
      92          return True
      93
      94  #================================================================
      95 -def unwrap_HL7_from_XML(parent=None):
      96
      97          if parent is None:
      98                  parent = wx.GetApp().GetTopWindow()
      99
     100          # select file
     101          paths = gmTools.gmPaths()
     102          dlg = wx.FileDialog (
     103                  parent = parent,
     104                  message = _('Extract HL7 from XML file:'),
     105                  # make configurable:
     106                  defaultDir = os.path.join(paths.home_dir, 'gnumed'),
     107                  wildcard = "xml files|*.xml|XML files|*.XML|all files|*",
     108                  style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
     109          )
     110          choice = dlg.ShowModal()
     111          xml_name = dlg.GetPath()
     112          dlg.DestroyLater()
     113          if choice != wx.ID_OK:
     114                  return False
     115
     116          target_dir = os.path.split(xml_name)[0]
     117          xml_path = './/Message'
     118          hl7_name = gmHL7.extract_HL7_from_XML_CDATA(xml_name, xml_path, target_dir = target_dir)
     119          if hl7_name is None:
     120                  gmGuiHelpers.gm_show_error (
     121                          title = _('Extracting HL7 from XML file'),
     122                          error = (
     123                          'Cannot unwrap HL7 data from XML file\n'
     124                          '\n'
     125                          ' [%s]\n'
     126                          '\n'
     127                          '(CDATA of [%s] nodes)'
     128                          ) % (
     129                                  xml_name,
     130                                  xml_path
     131                          )
     132                  )
     133                  return False
     134
     135          gmDispatcher.send(signal = 'statustext', msg = _('Unwrapped HL7 into [%s] from [%s].') % (hl7_name, xml_name), beep = False)
     136          return True
     137
     138  #================================================================
     139 -def stage_hl7_file(parent=None):
     140
     141          if parent is None:
     142                  parent = wx.GetApp().GetTopWindow()
     143
     144          paths = gmTools.gmPaths()
     145          dlg = wx.FileDialog (
     146                  parent = parent,
     147                  message = _('Select HL7 file for staging:'),
     148                  # make configurable:
     149                  defaultDir = os.path.join(paths.home_dir, 'gnumed'),
     150                  wildcard = ".hl7 files|*.hl7|.HL7 files|*.HL7|all files|*",
     151                  style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
     152          )
     153          choice = dlg.ShowModal()
     154          hl7_name = dlg.GetPath()
     155          dlg.DestroyLater()
     156          if choice != wx.ID_OK:
     157                  return False
     158
     159          target_dir = os.path.join(paths.home_dir, '.gnumed', 'hl7')
     160          success, PID_names = gmHL7.split_hl7_file(hl7_name, target_dir = target_dir, encoding = 'utf8')
     161          if not success:
     162                  gmGuiHelpers.gm_show_error (
     163                          title = _('Staging HL7 file'),
     164                          error = _(
     165                                  'There was a problem with splitting the HL7 file\n'
     166                                  '\n'
     167                                  ' %s'
     168                          ) % hl7_name
     169                  )
     170                  return False
     171
     172          failed_files = []
     173          for PID_name in PID_names:
     174                  if not gmHL7.stage_single_PID_hl7_file(PID_name, source = _('generic'), encoding = 'utf8'):
     175                          failed_files.append(PID_name)
     176          if len(failed_files) > 0:
     177                  gmGuiHelpers.gm_show_error (
     178                          title = _('Staging HL7 file'),
     179                          error = _(
     180                                  'There was a problem with staging the following files\n'
     181                                  '\n'
     182                                  ' %s'
     183                          ) % '\n '.join(failed_files)
     184                  )
     185                  return False
     186
     187          gmDispatcher.send(signal = 'statustext', msg = _('Staged HL7 from [%s].') % hl7_name, beep = False)
     188          return True
     189
     190  #================================================================
     191 -def browse_incoming_unmatched(parent=None):
     192
     193          if parent is None:
     194                  parent = wx.GetApp().GetTopWindow()
     195          #------------------------------------------------------------
     196          def show_hl7(staged_item):
     197                  if staged_item is None:
     198                          return False
     199                  if 'HL7' not in staged_item['data_type']:
     200                          return False
     201                  filename = staged_item.save_to_file()
     202                  if filename is None:
     203                          filename = gmTools.get_unique_filename()
     204                  tmp_file = io.open(filename, mode = 'at', encoding = 'utf8')
     205                  tmp_file.write('\n')
     206                  tmp_file.write('-' * 80)
     207                  tmp_file.write('\n')
     208                  tmp_file.write(gmTools.coalesce(staged_item['comment'], ''))
     209                  tmp_file.close()
     210                  gmMimeLib.call_viewer_on_file(aFile = filename, block = False)
     211                  return False
     212          #------------------------------------------------------------
     213          def import_hl7(staged_item):
     214                  if staged_item is None:
     215                          return False
     216                  if 'HL7' not in staged_item['data_type']:
     217                          return False
     218                  unset_identity_on_error = False
     219                  if staged_item['pk_identity_disambiguated'] is None:
     220                          pat = gmPerson.gmCurrentPatient()
     221                          if pat.connected:
     222                                  answer = gmGuiHelpers.gm_show_question (
     223                                          title = _('Importing HL7 data'),
     224                                          question = _(
     225                                                  'There has not been a patient explicitely associated\n'
     226                                                  'with this chunk of HL7 data. However, the data file\n'
     227                                                  'contains the following patient identification information:\n'
     228                                                  '\n'
     229                                                  ' %s\n'
     230                                                  '\n'
     231                                                  'Do you want to import the HL7 under the current patient ?\n'
     232                                                  '\n'
     233                                                  ' %s\n'
     234                                                  '\n'
     235                                                  'Selecting [NO] makes GNUmed try to find a patient matching the HL7 data.\n'
     236                                          ) % (
     237                                                  staged_item.patient_identification,
     238                                                  pat['description_gender']
     239                                          ),
     240                                          cancel_button = True
     241                                  )
     242                                  if answer is None:
     243                                          return False
     244                                  if answer is True:
     245                                          unset_identity_on_error = True
     246                                          staged_item['pk_identity_disambiguated'] = pat.ID
     247
     248                  success, log_name = gmHL7.process_staged_single_PID_hl7_file(staged_item)
     249                  if success:
     250                          return True
     251
     252                  if unset_identity_on_error:
     253                          staged_item['pk_identity_disambiguated'] = None
     254                          staged_item.save()
     255
     256                  gmGuiHelpers.gm_show_error (
     257                          error = _('Error processing HL7 data.'),
     258                          title = _('Processing staged HL7 data.')
     259                  )
     260                  return False
     261
     262          #------------------------------------------------------------
     263          def delete(staged_item):
     264                  if staged_item is None:
     265                          return False
     266                  do_delete = gmGuiHelpers.gm_show_question (
     267                          title = _('Deleting incoming data'),
     268                          question = _(
     269                                  'Do you really want to delete the incoming data ?\n'
     270                                  '\n'
     271                                  'Note that deletion is not reversible.'
     272                          )
     273                  )
     274                  if not do_delete:
     275                          return False
     276                  return gmIncomingData.delete_incoming_data(pk_incoming_data = staged_item['pk_incoming_data_unmatched'])
     277          #------------------------------------------------------------
     278          def refresh(lctrl):
     279                  incoming = gmIncomingData.get_incoming_data()
     280                  items = [ [
     281                          gmTools.coalesce(i['data_type'], ''),
     282                          '%s, %s (%s) %s' % (
     283                                  gmTools.coalesce(i['lastnames'], ''),
     284                                  gmTools.coalesce(i['firstnames'], ''),
     285                                  gmDateTime.pydt_strftime(dt = i['dob'], format = '%Y %b %d', accuracy = gmDateTime.acc_days, none_str = _('unknown DOB')),
     286                                  gmTools.coalesce(i['gender'], '')
     287                          ),
     288                          gmTools.coalesce(i['external_data_id'], ''),
     289                          i['pk_incoming_data_unmatched']
     290                  ] for i in incoming ]
     291                  lctrl.set_string_items(items)
     292                  lctrl.set_data(incoming)
     293          #------------------------------------------------------------
     294          gmListWidgets.get_choices_from_list (
     295                  parent = parent,
     296                  msg = None,
     297                  caption = _('Showing unmatched incoming data'),
     298                  columns = [ _('Type'), _('Identification'), _('Reference'), '#' ],
     299                  single_selection = True,
     300                  can_return_empty = False,
     301                  ignore_OK_button = True,
     302                  refresh_callback = refresh,
     303  #               edit_callback=None,
     304  #               new_callback=None,
     305                  delete_callback = delete,
     306                  left_extra_button = [_('Show'), _('Show formatted HL7'), show_hl7],
     307                  middle_extra_button = [_('Import'), _('Import HL7 data into patient chart'), import_hl7]
     308  #               right_extra_button=None
     309          )
     310
     311  #================================================================
     312  # convenience functions
     313  #================================================================
     314 -def call_browser_on_measurement_type(measurement_type=None):
     315
     316          dbcfg = gmCfg.cCfgSQL()
     317
     318          url = dbcfg.get2 (
     319                  option = 'external.urls.measurements_search',
     320                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     321                  bias = 'user',
     322                  default = gmPathLab.URL_test_result_information_search
     323          )
     324
     325          base_url = dbcfg.get2 (
     326                  option = 'external.urls.measurements_encyclopedia',
     327                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     328                  bias = 'user',
     329                  default = gmPathLab.URL_test_result_information
     330          )
     331
     332          if measurement_type is None:
     333                  url = base_url
     334
     335          measurement_type = measurement_type.strip()
     336
     337          if measurement_type == '':
     338                  url = base_url
     339
     340          url = url % {'search_term': measurement_type}
     341
     342          gmNetworkTools.open_url_in_browser(url = url)
     343
     344  #----------------------------------------------------------------
     345 -def edit_measurement(parent=None, measurement=None, single_entry=False, presets=None):
     346          ea = cMeasurementEditAreaPnl(parent, -1)
     347          ea.data = measurement
     348          ea.mode = gmTools.coalesce(measurement, 'new', 'edit')
     349          dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
     350          dlg.SetTitle(gmTools.coalesce(measurement, _('Adding new measurement'), _('Editing measurement')))
     351          if presets is not None:
     352                  ea.set_fields(presets)
     353          if dlg.ShowModal() == wx.ID_OK:
     354                  dlg.DestroyLater()
     355                  return True
     356          dlg.DestroyLater()
     357          return False
     358
     359  #----------------------------------------------------------------
     360 -def manage_measurements(parent=None, single_selection=False, emr=None):
     361
     362          if parent is None:
     363                  parent = wx.GetApp().GetTopWindow()
     364
     365          if emr is None:
     366                  emr = gmPerson.gmCurrentPatient().emr
     367
     368          #------------------------------------------------------------
     369          def edit(measurement=None):
     370                  return edit_measurement(parent = parent, measurement = measurement, single_entry = True)
     371          #------------------------------------------------------------
     372          def delete(measurement):
     373                  gmPathLab.delete_test_result(result = measurement)
     374                  return True
     375          #------------------------------------------------------------
     376          def do_review(lctrl):
     377                  data = lctrl.get_selected_item_data()
     378                  if len(data) == 0:
     379                          return
     380                  return review_tests(parent = parent, tests = data)
     381          #------------------------------------------------------------
     382          def do_plot(lctrl):
     383                  data = lctrl.get_selected_item_data()
     384                  if len(data) == 0:
     385                          return
     386                  return plot_measurements(parent = parent, tests = data)
     387          #------------------------------------------------------------
     388          def get_tooltip(measurement):
     389                  return measurement.format(with_review=True, with_evaluation=True, with_ranges=True)
     390          #------------------------------------------------------------
     391          def refresh(lctrl):
     392                  results = emr.get_test_results(order_by = 'clin_when DESC, unified_abbrev, unified_name')
     393                  items = [ [
     394                          gmDateTime.pydt_strftime (
     395                                  r['clin_when'],
     396                                  '%Y %b %d %H:%M',
     397                                  accuracy = gmDateTime.acc_minutes
     398                          ),
     399                          r['unified_abbrev'],
     400                          '%s%s%s%s' % (
     401                                  gmTools.bool2subst (
     402                                          boolean = (not r['reviewed'] or (not r['review_by_you'] and r['you_are_responsible'])),
     403                                          true_return = 'u' + gmTools.u_writing_hand,
     404                                          false_return = ''
     405                                  ),
     406                                  r['unified_val'],
     407                                  gmTools.coalesce(r['val_unit'], '', ' %s'),
     408                                  gmTools.coalesce(r['abnormality_indicator'], '', ' %s')
     409                          ),
     410                          r['unified_name'],
     411  #                       u'%s%s' % (
     412  #                               gmTools.bool2subst (
     413  #                                       boolean = not r['reviewed'],
     414  #                                       true_return = _('no review at all'),
     415  #                                       false_return = gmTools.bool2subst (
     416  #                                               boolean = (r['you_are_responsible'] and not r['review_by_you']),
     417  #                                               true_return = _('no review by you (you are responsible)'),
     418  #                                               false_return = _('reviewed')
     419  #                                       )
     420  #                               ),
     421  #                               gmTools.coalesce(r['comment'], u'', u' / %s')
     422  #                       ),
     423                          gmTools.coalesce(r['comment'], ''),
     424                          r['pk_test_result']
     425                  ] for r in results ]
     426                  lctrl.set_string_items(items)
     427                  lctrl.set_data(results)
     428
     429          #------------------------------------------------------------
     430          msg = _('Test results (ordered reverse-chronologically)')
     431
     432          return gmListWidgets.get_choices_from_list (
     433                  parent = parent,
     434                  msg = msg,
     435                  caption = _('Showing test results.'),
     436                  columns = [ _('When'), _('Abbrev'), _('Value'), _('Name'), _('Comment'), '#' ],
     437                  single_selection = single_selection,
     438                  can_return_empty = False,
     439                  refresh_callback = refresh,
     440                  edit_callback = edit,
     441                  new_callback = edit,
     442                  delete_callback = delete,
     443                  list_tooltip_callback = get_tooltip,
     444                  left_extra_button = (_('Review'), _('Review current selection'), do_review, True),
     445                  middle_extra_button = (_('Plot'), _('Plot current selection'), do_plot, True)
     446          )
     447
     448  #================================================================
     449 -def configure_default_top_lab_panel(parent=None):
     450
     451          if parent is None:
     452                  parent = wx.GetApp().GetTopWindow()
     453
     454          panels = gmPathLab.get_test_panels(order_by = 'description')
     455          gmCfgWidgets.configure_string_from_list_option (
     456                  parent = parent,
     457                  message = _('Select the measurements panel to show in the top pane for continuous monitoring.'),
     458                  option = 'horstspace.top_panel.lab_panel',
     459                  bias = 'user',
     460                  default_value = None,
     461                  choices = [ '%s%s' % (p['description'], gmTools.coalesce(p['comment'], '', ' (%s)')) for p in panels ],
     462                  columns = [_('Lab panel')],
     463                  data = [ p['pk_test_panel'] for p in panels ],
     464                  caption = _('Configuring continuous monitoring measurements panel')
     465          )
     466
     467  #================================================================
     468 -def configure_default_gnuplot_template(parent=None):
     469
     470          from Gnumed.wxpython import gmFormWidgets
     471
     472          if parent is None:
     473                  parent = wx.GetApp().GetTopWindow()
     474
     475          template = gmFormWidgets.manage_form_templates (
     476                  parent = parent,
     477                  active_only = True,
     478                  template_types = ['gnuplot script']
     479          )
     480
     481          option = 'form_templates.default_gnuplot_template'
     482
     483          if template is None:
     484                  gmDispatcher.send(signal = 'statustext', msg = _('No default Gnuplot script template selected.'), beep = True)
     485                  return None
     486
     487          if template['engine'] != 'G':
     488                  gmDispatcher.send(signal = 'statustext', msg = _('No default Gnuplot script template selected.'), beep = True)
     489                  return None
     490
     491          dbcfg = gmCfg.cCfgSQL()
     492          dbcfg.set (
     493                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     494                  option = option,
     495                  value = '%s - %s' % (template['name_long'], template['external_version'])
     496          )
     497          return template
     498
     499  #============================================================
     500 -def get_default_gnuplot_template(parent = None):
     501
     502          option = 'form_templates.default_gnuplot_template'
     503
     504          dbcfg = gmCfg.cCfgSQL()
     505
     506          # load from option
     507          default_template_name = dbcfg.get2 (
     508                  option = option,
     509                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     510                  bias = 'user'
     511          )
     512
     513          # not configured -> try to configure
     514          if default_template_name is None:
     515                  gmDispatcher.send('statustext', msg = _('No default Gnuplot template configured.'), beep = False)
     516                  default_template = configure_default_gnuplot_template(parent = parent)
     517                  # still not configured -> return
     518                  if default_template is None:
     519                          gmGuiHelpers.gm_show_error (
     520                                  aMessage = _('There is no default Gnuplot one-type script template configured.'),
     521                                  aTitle = _('Plotting test results')
     522                          )
     523                          return None
     524                  return default_template
     525
     526          # now it MUST be configured (either newly or previously)
     527          # but also *validly* ?
     528          try:
     529                  name, ver = default_template_name.split(' - ')
     530          except:
     531                  # not valid
     532                  _log.exception('problem splitting Gnuplot script template name [%s]', default_template_name)
     533                  gmDispatcher.send(signal = 'statustext', msg = _('Problem loading Gnuplot script template.'), beep = True)
     534                  return None
     535
     536          default_template = gmForms.get_form_template(name_long = name, external_version = ver)
     537          if default_template is None:
     538                  default_template = configure_default_gnuplot_template(parent = parent)
     539                  # still not configured -> return
     540                  if default_template is None:
     541                          gmGuiHelpers.gm_show_error (
     542                                  aMessage = _('Cannot load default Gnuplot script template [%s - %s]') % (name, ver),
     543                                  aTitle = _('Plotting test results')
     544                          )
     545                          return None
     546
     547          return default_template
     548
     549  #----------------------------------------------------------------
     550 -def plot_measurements(parent=None, tests=None, format=None, show_year = True, use_default_template=False):
     551
     552          from Gnumed.wxpython import gmFormWidgets
     553
     554          # only valid for one-type plotting
     555          if use_default_template:
     556                  template = get_default_gnuplot_template()
     557          else:
     558                  template = gmFormWidgets.manage_form_templates (
     559                          parent = parent,
     560                          active_only = True,
     561                          template_types = ['gnuplot script']
     562                  )
     563
     564          if template is None:
     565                  gmGuiHelpers.gm_show_error (
     566                          aMessage = _('Cannot plot without a plot script.'),
     567                          aTitle = _('Plotting test results')
     568                  )
     569                  return False
     570
     571          fname_data = gmPathLab.export_results_for_gnuplot(results = tests, show_year = show_year)
     572
     573          script = template.instantiate()
     574          script.data_filename = fname_data
     575          script.generate_output(format = format)                 # Gnuplot output terminal, wxt = wxWidgets window
     576
     577  #----------------------------------------------------------------
     578 -def plot_adjacent_measurements(parent=None, test=None, format=None, show_year=True, plot_singular_result=True, use_default_template=False):
     579
     580          earlier, later = test.get_adjacent_results(desired_earlier_results = 2, desired_later_results = 2)
     581          results2plot = []
     582          if earlier is not None:
     583                  results2plot.extend(earlier)
     584          results2plot.append(test)
     585          if later is not None:
     586                  results2plot.extend(later)
     587          if len(results2plot) == 1:
     588                  if not plot_singular_result:
     589                          return
     590          plot_measurements (
     591                  parent = parent,
     592                  tests = results2plot,
     593                  format = format,
     594                  show_year = show_year,
     595                  use_default_template = use_default_template
     596          )
     597
     598  #================================================================
     599  #from Gnumed.wxGladeWidgets import wxgPrimaryCareVitalsInputPnl
     600  #
     601  # Taillenumfang: Mitte zwischen unterster Rippe und
     602  # hoechstem Teil des Beckenkamms
     603  # Maenner: maessig: 94-102, deutlich: > 102  .. erhoeht
     604  # Frauen:  maessig: 80-88,  deutlich: > 88   .. erhoeht
     605  #
     606  #================================================================
     607  # display widgets
     608  #================================================================
     609  from Gnumed.wxGladeWidgets import wxgLabRelatedDocumentsPnl
     610
     611 -class cLabRelatedDocumentsPnl(wxgLabRelatedDocumentsPnl.wxgLabRelatedDocumentsPnl):
     612          """This panel handles documents related to the lab result it is handed.
     613          """
     614 -        def __init__(self, *args, **kwargs):
     615                  wxgLabRelatedDocumentsPnl.wxgLabRelatedDocumentsPnl.__init__(self, *args, **kwargs)
     616
     617                  self.__reference = None
     618
     619                  self.__init_ui()
     620                  self.__register_events()
     621
     622          #------------------------------------------------------------
     623          # internal helpers
     624          #------------------------------------------------------------
     625 -        def __init_ui(self):
     626                  self.__repopulate_ui()
     627
     628          #------------------------------------------------------------
     629 -        def __register_events(self):
     630                  gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
     631
     632          #------------------------------------------------------------
     633 -        def __repopulate_ui(self):
     634                  self._BTN_list_documents.Disable()
     635                  self._LBL_no_of_docs.SetLabel(_('no related documents'))
     636                  self._LBL_no_of_docs.ContainingSizer.Layout()
     637
     638                  if self.__reference is None:
     639                          self._LBL_no_of_docs.SetToolTip(_('There is no lab reference to find related documents for.'))
     640                          return
     641
     642                  dbcfg = gmCfg.cCfgSQL()
     643                  lab_doc_types = dbcfg.get2 (
     644                          option = 'horstspace.lab_doc_types',
     645                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     646                          bias = 'user'
     647                  )
     648                  if lab_doc_types is None:
     649                          self._LBL_no_of_docs.SetToolTip(_('No document types declared to contain lab results.'))
     650                          return
     651
     652                  if len(lab_doc_types) == 0:
     653                          self._LBL_no_of_docs.SetToolTip(_('No document types declared to contain lab results.'))
     654                          return
     655
     656                  pks_doc_types = gmDocuments.map_types2pk(lab_doc_types)
     657                  if len(pks_doc_types) == 0:
     658                          self._LBL_no_of_docs.SetToolTip(_('No valid document types declared to contain lab results.'))
     659                          return
     660
     661                  txt = _('Document types assumed to contain lab results:')
     662                  txt += '\n '
     663                  txt += '\n '.join(lab_doc_types)
     664                  self._LBL_no_of_docs.SetToolTip(txt)
     665                  if isinstance(self.__reference, gmPathLab.cTestResult):
     666                          pk_current_episode = self.__reference['pk_episode']
     667                  else:
     668                          pk_current_episode = self.__reference
     669                  docs = gmDocuments.search_for_documents (
     670                          pk_episode = pk_current_episode,
     671                          pk_types = [ dt['pk_doc_type'] for dt in pks_doc_types ]
     672                  )
     673                  if len(docs) == 0:
     674                          return
     675
     676                  self._LBL_no_of_docs.SetLabel(_('Related documents: %s') % len(docs))
     677                  self._LBL_no_of_docs.ContainingSizer.Layout()
     678                  self._BTN_list_documents.Enable()
     679
     680          #------------------------------------------------------------
     681          # event handlers
     682          #------------------------------------------------------------
     683 -        def _on_database_signal(self, **kwds):
     684                  if self.__reference is None:
     685                          return True
     686
     687                  if kwds['table'] not in ['clin.test_result', 'blobs.doc_med']:
     688                          return True
     689
     690                  if isinstance(self.__reference, gmPathLab.cTestResult):
     691                          if kwds['pk_of_row'] != self.__reference['pk_test_result']:
     692                                  return True
     693
     694                  self.__repopulate_ui()
     695                  return True
     696
     697          #------------------------------------------------------------
     698 -        def _on_select_lab_doc_types_button_pressed(self, event):
     699                  event.Skip()
     700                  doc_types = gmDocuments.get_document_types()
     701                  gmCfgWidgets.configure_list_from_list_option (
     702                          parent = self,
     703                          message = _('Select the document types which are assumed to contain lab results.'),
     704                          option = 'horstspace.lab_doc_types',
     705                          bias = 'user',
     706                          choices = [ dt['l10n_type'] for dt in doc_types ],
     707                          columns = [_('Document types')]#,
     708                          #data = None,
     709                          #caption = None,
     710                          #picks = None
     711                  )
     712                  self.__repopulate_ui()
     713
     714          #------------------------------------------------------------
     715 -        def _on_list_documents_button_pressed(self, event):
     716                  event.Skip()
     717                  dbcfg = gmCfg.cCfgSQL()
     718                  lab_doc_types = dbcfg.get2 (
     719                          option = 'horstspace.lab_doc_types',
     720                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     721                          bias = 'user'
     722                  )
     723                  d_types = gmDocuments.map_types2pk(lab_doc_types)
     724                  if isinstance(self.__reference, gmPathLab.cTestResult):
     725                          pk_current_episode = self.__reference['pk_episode']
     726                  else:
     727                          pk_current_episode = self.__reference
     728                  gmDocumentWidgets.manage_documents (
     729                          parent = self,
     730                          msg = _('Documents possibly related to this episode'),
     731                          pk_types = [ dt['pk_doc_type'] for dt in d_types ],
     732                          pk_episodes = [ pk_current_episode ]
     733                  )
     734
     735          #------------------------------------------------------------
     736          # properties
     737          #------------------------------------------------------------
     738 -        def _set_lab_reference(self, value):
     739                  """Either a test result or an episode PK."""
     740                  if isinstance(self.__reference, gmPathLab.cTestResult):
     741                          pk_old_episode = self.__reference['pk_episode']
     742                  else:
     743                          pk_old_episode = self.__reference
     744                  if isinstance(value, gmPathLab.cTestResult):
     745                          pk_new_episode = value['pk_episode']
     746                  else:
     747                          pk_new_episode = value
     748                  self.__reference = value
     749                  if pk_new_episode != pk_old_episode:
     750                          self.__repopulate_ui()
     751                  return
     752
     753          lab_reference = property(lambda x:x, _set_lab_reference)
     754
     755  #================================================================
     756  from Gnumed.wxGladeWidgets import wxgMeasurementsAsListPnl
     757
     758 -class cMeasurementsAsListPnl(wxgMeasurementsAsListPnl.wxgMeasurementsAsListPnl, gmRegetMixin.cRegetOnPaintMixin):
     759          """A class for displaying all measurement results as a simple list.
     760
     761          - operates on a cPatient instance handed to it and NOT on the currently active patient
     762          """
     763 -        def __init__(self, *args, **kwargs):
     764                  wxgMeasurementsAsListPnl.wxgMeasurementsAsListPnl.__init__(self, *args, **kwargs)
     765
     766                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
     767
     768                  self.__patient = None
     769
     770                  self.__init_ui()
     771                  self.__register_events()
     772
     773          #------------------------------------------------------------
     774          # internal helpers
     775          #------------------------------------------------------------
     776 -        def __init_ui(self):
     777                  self._LCTRL_results.set_columns([_('When'), _('Test'), _('Result'), _('Reference')])
     778                  self._LCTRL_results.edit_callback = self._on_edit
     779                  self._PNL_related_documents.lab_reference = None
     780
     781          #------------------------------------------------------------
     782 -        def __register_events(self):
     783                  gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
     784
     785          #------------------------------------------------------------
     786 -        def __repopulate_ui(self):
     787                  if self.__patient is None:
     788                          self._LCTRL_results.set_string_items([])
     789                          self._TCTRL_measurements.SetValue('')
     790                          self._PNL_related_documents.lab_reference = None
     791                          return
     792
     793                  results = self.__patient.emr.get_test_results(order_by = 'clin_when DESC, unified_abbrev, unified_name')
     794                  items = []
     795                  data = []
     796                  for r in results:
     797                          range_info = gmTools.coalesce (
     798                                  r.formatted_clinical_range,
     799                                  r.formatted_normal_range
     800                          )
     801                          review = gmTools.bool2subst (
     802                                  r['reviewed'],
     803                                  '',
     804                                  ' ' + gmTools.u_writing_hand,
     805                                  ' ' + gmTools.u_writing_hand
     806                          )
     807                          items.append ([
     808                                  gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d  %H:%M', accuracy = gmDateTime.acc_minutes),
     809                                  r['abbrev_tt'],
     810                                  '%s%s%s%s' % (
     811                                          gmTools.strip_empty_lines(text = r['unified_val'])[0],
     812                                          gmTools.coalesce(r['val_unit'], '', ' %s'),
     813                                          gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
     814                                          review
     815                                  ),
     816                                  gmTools.coalesce(range_info, '')
     817                          ])
     818                          data.append({'data': r, 'formatted': r.format(with_source_data = True)})
     819
     820                  self._LCTRL_results.set_string_items(items)
     821                  self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
     822                  self._LCTRL_results.set_data(data)
     823                  if len(items) > 0:
     824                          self._LCTRL_results.Select(idx = 0, on = 1)
     825                          self._TCTRL_measurements.SetValue(self._LCTRL_results.get_item_data(item_idx = 0)['formatted'])
     826
     827                  self._LCTRL_results.SetFocus()
     828
     829          #------------------------------------------------------------
     830 -        def _on_edit(self):
     831                  item_data = self._LCTRL_results.get_selected_item_data(only_one = True)
     832                  if item_data is None:
     833                          return
     834                  if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True):
     835                          self.__repopulate_ui()
     836
     837          #------------------------------------------------------------
     838          # event handlers
     839          #------------------------------------------------------------
     840 -        def _on_database_signal(self, **kwds):
     841                  if self.__patient is None:
     842                          return True
     843
     844                  if kwds['pk_identity'] is not None:                             # review table doesn't have pk_identity yet
     845                          if kwds['pk_identity'] != self.__patient.ID:
     846                                  return True
     847
     848                  if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
     849                          return True
     850
     851                  self._schedule_data_reget()
     852                  return True
     853
     854          #------------------------------------------------------------
     855 -        def _on_result_selected(self, event):
     856                  event.Skip()
     857                  item_data = self._LCTRL_results.get_item_data(item_idx = event.Index)
     858                  self._TCTRL_measurements.SetValue(item_data['formatted'])
     859                  self._PNL_related_documents.lab_reference = item_data['data']
     860
     861          #------------------------------------------------------------
     862          # reget mixin API
     863          #------------------------------------------------------------
     864 -        def _populate_with_data(self):
     865                  self.__repopulate_ui()
     866                  return True
     867
     868          #------------------------------------------------------------
     869          # properties
     870          #------------------------------------------------------------
     871 -        def _get_patient(self):
     872                  return self.__patient
     873
     874 -        def _set_patient(self, patient):
     875                  if (self.__patient is None) and (patient is None):
     876                          return
     877                  if (self.__patient is None) or (patient is None):
     878                          self.__patient = patient
     879                          self._schedule_data_reget()
     880                          return
     881                  if self.__patient.ID == patient.ID:
     882                          return
     883                  self.__patient = patient
     884                  self._schedule_data_reget()
     885
     886          patient = property(_get_patient, _set_patient)
     887
     888  #================================================================
     889  from Gnumed.wxGladeWidgets import wxgMeasurementsByDayPnl
     890
     891 -class cMeasurementsByDayPnl(wxgMeasurementsByDayPnl.wxgMeasurementsByDayPnl, gmRegetMixin.cRegetOnPaintMixin):
     892          """A class for displaying measurement results as a list partitioned by day.
     893
     894          - operates on a cPatient instance handed to it and NOT on the currently active patient
     895          """
     896 -        def __init__(self, *args, **kwargs):
     897                  wxgMeasurementsByDayPnl.wxgMeasurementsByDayPnl.__init__(self, *args, **kwargs)
     898
     899                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
     900
     901                  self.__patient = None
     902                  self.__date_format = str('%Y %b %d')
     903
     904                  self.__init_ui()
     905                  self.__register_events()
     906
     907          #------------------------------------------------------------
     908          # internal helpers
     909          #------------------------------------------------------------
     910 -        def __init_ui(self):
     911                  self._LCTRL_days.set_columns([_('Day')])
     912                  self._LCTRL_results.set_columns([_('Time'), _('Test'), _('Result'), _('Reference')])
     913                  self._LCTRL_results.edit_callback = self._on_edit
     914                  self._PNL_related_documents.lab_reference = None
     915
     916          #------------------------------------------------------------
     917 -        def __register_events(self):
     918                  gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
     919
     920          #------------------------------------------------------------
     921 -        def __clear(self):
     922                  self._LCTRL_days.set_string_items()
     923                  self._LCTRL_results.set_string_items()
     924                  self._TCTRL_measurements.SetValue('')
     925                  self._PNL_related_documents.lab_reference = None
     926
     927          #------------------------------------------------------------
     928 -        def __repopulate_ui(self):
     929                  if self.__patient is None:
     930                          self.__clear()
     931                          return
     932
     933                  dates = self.__patient.emr.get_dates_for_results(reverse_chronological = True)
     934                  items = [ ['%s%s' % (
     935                                          gmDateTime.pydt_strftime(d['clin_when_day'], self.__date_format),
     936                                          gmTools.bool2subst(d['is_reviewed'], '', gmTools.u_writing_hand, gmTools.u_writing_hand)
     937                                  )]
     938                          for d in dates
     939                  ]
     940
     941                  self._LCTRL_days.set_string_items(items)
     942                  self._LCTRL_days.set_data(dates)
     943                  if len(items) > 0:
     944                          self._LCTRL_days.Select(idx = 0, on = 1)
     945                          self._LCTRL_days.SetFocus()
     946
     947          #------------------------------------------------------------
     948 -        def _on_edit(self):
     949                  item_data = self._LCTRL_results.get_selected_item_data(only_one = True)
     950                  if item_data is None:
     951                          return
     952                  if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True):
     953                          self.__repopulate_ui()
     954
     955          #------------------------------------------------------------
     956          # event handlers
     957          #------------------------------------------------------------
     958 -        def _on_database_signal(self, **kwds):
     959                  if self.__patient is None:
     960                          return True
     961
     962                  if kwds['pk_identity'] is not None:                             # review table doesn't have pk_identity yet
     963                          if kwds['pk_identity'] != self.__patient.ID:
     964                                  return True
     965
     966                  if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
     967                          return True
     968
     969                  self._schedule_data_reget()
     970                  return True
     971
     972          #------------------------------------------------------------
     973 -        def _on_day_selected(self, event):
     974                  event.Skip()
     975
     976                  day = self._LCTRL_days.get_item_data(item_idx = event.Index)['clin_when_day']
     977                  results = self.__patient.emr.get_results_for_day(timestamp = day)
     978                  items = []
     979                  data = []
     980                  for r in results:
     981                          range_info = gmTools.coalesce (
     982                                  r.formatted_clinical_range,
     983                                  r.formatted_normal_range
     984                          )
     985                          review = gmTools.bool2subst (
     986                                  r['reviewed'],
     987                                  '',
     988                                  ' ' + gmTools.u_writing_hand,
     989                                  ' ' + gmTools.u_writing_hand
     990                          )
     991                          items.append ([
     992                                  gmDateTime.pydt_strftime(r['clin_when'], '%H:%M'),
     993                                  r['abbrev_tt'],
     994                                  '%s%s%s%s' % (
     995                                          gmTools.strip_empty_lines(text = r['unified_val'])[0],
     996                                          gmTools.coalesce(r['val_unit'], '', ' %s'),
     997                                          gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
     998                                          review
     999                                  ),
    1000                                  gmTools.coalesce(range_info, '')
    1001                          ])
    1002                          data.append({'data': r, 'formatted': r.format(with_source_data = True)})
    1003
    1004                  self._LCTRL_results.set_string_items(items)
    1005                  self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
    1006                  self._LCTRL_results.set_data(data)
    1007                  self._LCTRL_results.Select(idx = 0, on = 1)
    1008
    1009          #------------------------------------------------------------
    1010 -        def _on_result_selected(self, event):
    1011                  event.Skip()
    1012                  item_data = self._LCTRL_results.get_item_data(item_idx = event.Index)
    1013                  self._TCTRL_measurements.SetValue(item_data['formatted'])
    1014                  self._PNL_related_documents.lab_reference = item_data['data']
    1015
    1016          #------------------------------------------------------------
    1017          # reget mixin API
    1018          #------------------------------------------------------------
    1019 -        def _populate_with_data(self):
    1020                  self.__repopulate_ui()
    1021                  return True
    1022
    1023          #------------------------------------------------------------
    1024          # properties
    1025          #------------------------------------------------------------
    1026 -        def _get_patient(self):
    1027                  return self.__patient
    1028
    1029 -        def _set_patient(self, patient):
    1030                  if (self.__patient is None) and (patient is None):
    1031                          return
    1032                  if patient is None:
    1033                          self.__patient = None
    1034                          self.__clear()
    1035                          return
    1036                  if self.__patient is None:
    1037                          self.__patient = patient
    1038                          self._schedule_data_reget()
    1039                          return
    1040                  if self.__patient.ID == patient.ID:
    1041                          return
    1042                  self.__patient = patient
    1043                  self._schedule_data_reget()
    1044
    1045          patient = property(_get_patient, _set_patient)
    1046
    1047  #================================================================
    1048  from Gnumed.wxGladeWidgets import wxgMeasurementsByIssuePnl
    1049
    1050 -class cMeasurementsByIssuePnl(wxgMeasurementsByIssuePnl.wxgMeasurementsByIssuePnl, gmRegetMixin.cRegetOnPaintMixin):
    1051          """A class for displaying measurement results as a list partitioned by issue/episode.
    1052
    1053          - operates on a cPatient instance handed to it and NOT on the currently active patient
    1054          """
    1055 -        def __init__(self, *args, **kwargs):
    1056                  wxgMeasurementsByIssuePnl.wxgMeasurementsByIssuePnl.__init__(self, *args, **kwargs)
    1057
    1058                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    1059
    1060                  self.__patient = None
    1061
    1062                  self.__init_ui()
    1063                  self.__register_events()
    1064
    1065          #------------------------------------------------------------
    1066          # internal helpers
    1067          #------------------------------------------------------------
    1068 -        def __init_ui(self):
    1069                  self._LCTRL_issues.set_columns([_('Problem')])
    1070                  self._LCTRL_results.set_columns([_('When'), _('Test'), _('Result'), _('Reference')])
    1071                  self._PNL_related_documents.lab_reference = None
    1072
    1073          #------------------------------------------------------------
    1074 -        def __register_events(self):
    1075                  gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
    1076                  self._LCTRL_issues.select_callback = self._on_problem_selected
    1077                  self._LCTRL_results.edit_callback = self._on_edit
    1078                  self._LCTRL_results.select_callback = self._on_result_selected
    1079
    1080          #------------------------------------------------------------
    1081 -        def __clear(self):
    1082                  self._LCTRL_issues.set_string_items()
    1083                  self._LCTRL_results.set_string_items()
    1084                  self._TCTRL_measurements.SetValue('')
    1085                  self._PNL_related_documents.lab_reference = None
    1086
    1087          #------------------------------------------------------------
    1088 -        def __repopulate_ui(self):
    1089                  if self.__patient is None:
    1090                          self.__clear()
    1091                          return
    1092
    1093                  probs = self.__patient.emr.get_issues_or_episodes_for_results()
    1094                  items = [ ['%s%s' % (
    1095                          gmTools.coalesce(p['pk_health_issue'], gmTools.u_diameter + ':', ''),
    1096                          gmTools.shorten_words_in_line(text = p['problem'], min_word_length = 5, max_length = 30)
    1097                  )] for p in probs ]
    1098                  self._LCTRL_issues.set_string_items(items)
    1099                  self._LCTRL_issues.set_data([ {'pk_issue': p['pk_health_issue'], 'pk_episode': p['pk_episode']} for p in probs ])
    1100                  if len(items) > 0:
    1101                          self._LCTRL_issues.Select(idx = 0, on = 1)
    1102                          self._LCTRL_issues.SetFocus()
    1103
    1104          #------------------------------------------------------------
    1105 -        def _on_edit(self):
    1106                  item_data = self._LCTRL_results.get_selected_item_data(only_one = True)
    1107                  if item_data is None:
    1108                          return
    1109                  if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True):
    1110                          self.__repopulate_ui()
    1111
    1112          #------------------------------------------------------------
    1113          # event handlers
    1114          #------------------------------------------------------------
    1115 -        def _on_database_signal(self, **kwds):
    1116                  if self.__patient is None:
    1117                          return True
    1118
    1119                  if kwds['pk_identity'] is not None:                             # review table doesn't have pk_identity yet
    1120                          if kwds['pk_identity'] != self.__patient.ID:
    1121                                  return True
    1122
    1123                  if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
    1124                          return True
    1125
    1126                  self._schedule_data_reget()
    1127                  return True
    1128
    1129          #------------------------------------------------------------
    1130 -        def _on_problem_selected(self, event):
    1131                  event.Skip()
    1132
    1133                  pk_issue = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_issue']
    1134                  if pk_issue is None:
    1135                          pk_episode = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_episode']
    1136                          results = self.__patient.emr.get_results_for_episode(pk_episode = pk_episode)
    1137                  else:
    1138                          results = self.__patient.emr.get_results_for_issue(pk_health_issue = pk_issue)
    1139                  items = []
    1140                  data = []
    1141                  for r in results:
    1142                          range_info = gmTools.coalesce (
    1143                                  r.formatted_clinical_range,
    1144                                  r.formatted_normal_range
    1145                          )
    1146                          review = gmTools.bool2subst (
    1147                                  r['reviewed'],
    1148                                  '',
    1149                                  ' ' + gmTools.u_writing_hand,
    1150                                  ' ' + gmTools.u_writing_hand
    1151                          )
    1152                          items.append ([
    1153                                  gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d  %H:%M'),
    1154                                  r['abbrev_tt'],
    1155                                  '%s%s%s%s' % (
    1156                                          gmTools.strip_empty_lines(text = r['unified_val'])[0],
    1157                                          gmTools.coalesce(r['val_unit'], '', ' %s'),
    1158                                          gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
    1159                                          review
    1160                                  ),
    1161                                  gmTools.coalesce(range_info, '')
    1162                          ])
    1163                          data.append({'data': r, 'formatted': r.format(with_source_data = True)})
    1164
    1165                  self._LCTRL_results.set_string_items(items)
    1166                  self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
    1167                  self._LCTRL_results.set_data(data)
    1168                  self._LCTRL_results.Select(idx = 0, on = 1)
    1169                  self._TCTRL_measurements.SetValue(self._LCTRL_results.get_item_data(item_idx = 0)['formatted'])
    1170
    1171          #------------------------------------------------------------
    1172 -        def _on_result_selected(self, event):
    1173                  event.Skip()
    1174                  item_data = self._LCTRL_results.get_item_data(item_idx = event.Index)
    1175                  self._TCTRL_measurements.SetValue(item_data['formatted'])
    1176                  self._PNL_related_documents.lab_reference = item_data['data']
    1177
    1178          #------------------------------------------------------------
    1179          # reget mixin API
    1180          #------------------------------------------------------------
    1181 -        def _populate_with_data(self):
    1182                  self.__repopulate_ui()
    1183                  return True
    1184
    1185          #------------------------------------------------------------
    1186          # properties
    1187          #------------------------------------------------------------
    1188 -        def _get_patient(self):
    1189                  return self.__patient
    1190
    1191 -        def _set_patient(self, patient):
    1192                  if (self.__patient is None) and (patient is None):
    1193                          return
    1194                  if patient is None:
    1195                          self.__patient = None
    1196                          self.__clear()
    1197                          return
    1198                  if self.__patient is None:
    1199                          self.__patient = patient
    1200                          self._schedule_data_reget()
    1201                          return
    1202                  if self.__patient.ID == patient.ID:
    1203                          return
    1204                  self.__patient = patient
    1205                  self._schedule_data_reget()
    1206
    1207          patient = property(_get_patient, _set_patient)
    1208
    1209  #================================================================
    1210  from Gnumed.wxGladeWidgets import wxgMeasurementsByBatteryPnl
    1211
    1212 -class cMeasurementsByBatteryPnl(wxgMeasurementsByBatteryPnl.wxgMeasurementsByBatteryPnl, gmRegetMixin.cRegetOnPaintMixin):
    1213          """A grid class for displaying measurement results filtered by battery/panel.
    1214
    1215          - operates on a cPatient instance handed to it and NOT on the currently active patient
    1216          """
    1217 -        def __init__(self, *args, **kwargs):
    1218                  wxgMeasurementsByBatteryPnl.wxgMeasurementsByBatteryPnl.__init__(self, *args, **kwargs)
    1219
    1220                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    1221
    1222                  self.__patient = None
    1223
    1224                  self.__init_ui()
    1225                  self.__register_events()
    1226
    1227          #------------------------------------------------------------
    1228          # internal helpers
    1229          #------------------------------------------------------------
    1230 -        def __init_ui(self):
    1231                  self._GRID_results_battery.show_by_panel = True
    1232
    1233          #------------------------------------------------------------
    1234 -        def __register_events(self):
    1235                  gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
    1236
    1237                  self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
    1238                  self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
    1239
    1240          #------------------------------------------------------------
    1241 -        def __repopulate_ui(self):
    1242                  self._GRID_results_battery.patient = self.__patient
    1243                  return True
    1244
    1245          #--------------------------------------------------------
    1246 -        def __on_panel_selected(self, panel):
    1247                  if panel is None:
    1248                          self._TCTRL_panel_comment.SetValue('')
    1249                          self._GRID_results_battery.panel_to_show = None
    1250                  else:
    1251                          pnl = self._PRW_panel.GetData(as_instance = True)
    1252                          self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
    1253                                  pnl['comment'],
    1254                                  ''
    1255                          ))
    1256                          self._GRID_results_battery.panel_to_show = pnl
    1257  #               self.Layout()
    1258
    1259          #--------------------------------------------------------
    1260 -        def __on_panel_selection_modified(self):
    1261                  self._TCTRL_panel_comment.SetValue('')
    1262                  if self._PRW_panel.GetValue().strip() == '':
    1263                          self._GRID_results_battery.panel_to_show = None
    1264  #                       self.Layout()
    1265
    1266          #------------------------------------------------------------
    1267          # event handlers
    1268          #------------------------------------------------------------
    1269 -        def _on_database_signal(self, **kwds):
    1270                  if self.__patient is None:
    1271                          return True
    1272
    1273                  if kwds['pk_identity'] is not None:                             # review table doesn't have pk_identity yet
    1274                          if kwds['pk_identity'] != self.__patient.ID:
    1275                                  return True
    1276
    1277                  if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
    1278                          return True
    1279
    1280                  self._schedule_data_reget()
    1281                  return True
    1282
    1283          #------------------------------------------------------------
    1284 -        def _on_manage_panels_button_pressed(self, event):
    1285                  manage_test_panels(parent = self)
    1286
    1287          #--------------------------------------------------------
    1288 -        def _on_panel_selected(self, panel):
    1289                  wx.CallAfter(self.__on_panel_selected, panel=panel)
    1290
    1291          #--------------------------------------------------------
    1292 -        def _on_panel_selection_modified(self):
    1293                  wx.CallAfter(self.__on_panel_selection_modified)
    1294
    1295          #------------------------------------------------------------
    1296          # reget mixin API
    1297          #------------------------------------------------------------
    1298 -        def _populate_with_data(self):
    1299                  self.__repopulate_ui()
    1300                  return True
    1301
    1302          #------------------------------------------------------------
    1303          # properties
    1304          #------------------------------------------------------------
    1305 -        def _get_patient(self):
    1306                  return self.__patient
    1307
    1308 -        def _set_patient(self, patient):
    1309                  if (self.__patient is None) and (patient is None):
    1310                          return
    1311                  if (self.__patient is None) or (patient is None):
    1312                          self.__patient = patient
    1313                          self._schedule_data_reget()
    1314                          return
    1315                  if self.__patient.ID == patient.ID:
    1316                          return
    1317                  self.__patient = patient
    1318                  self._schedule_data_reget()
    1319
    1320          patient = property(_get_patient, _set_patient)
    1321
    1322  #================================================================
    1323  from Gnumed.wxGladeWidgets import wxgMeasurementsAsMostRecentListPnl
    1324
    1325 -class cMeasurementsAsMostRecentListPnl(wxgMeasurementsAsMostRecentListPnl.wxgMeasurementsAsMostRecentListPnl, gmRegetMixin.cRegetOnPaintMixin):
    1326          """A list ctrl class for displaying measurement results.
    1327
    1328                  - most recent results
    1329                  - possibly filtered by battery/panel
    1330
    1331          - operates on a cPatient instance handed to it and NOT on the currently active patient
    1332          """
    1333 -        def __init__(self, *args, **kwargs):
    1334                  wxgMeasurementsAsMostRecentListPnl.wxgMeasurementsAsMostRecentListPnl.__init__(self, *args, **kwargs)
    1335
    1336                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    1337
    1338                  self.__patient = None
    1339
    1340                  self.__init_ui()
    1341                  self.__register_events()
    1342
    1343          #------------------------------------------------------------
    1344          # internal helpers
    1345          #------------------------------------------------------------
    1346 -        def __init_ui(self):
    1347                  self._LCTRL_results.set_columns([_('Test'), _('Result'), _('When'), _('Range')])
    1348                  self._CHBOX_show_missing.Disable()
    1349                  self._PNL_related_documents.lab_reference = None
    1350
    1351          #------------------------------------------------------------
    1352 -        def __register_events(self):
    1353                  gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
    1354
    1355                  self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
    1356                  self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
    1357
    1358                  self._LCTRL_results.select_callback = self._on_result_selected
    1359                  self._LCTRL_results.edit_callback = self._on_edit
    1360
    1361          #------------------------------------------------------------
    1362 -        def __repopulate_ui(self):
    1363
    1364                  self._TCTRL_details.SetValue('')
    1365                  self._PNL_related_documents.lab_reference = None
    1366
    1367                  pnl = self._PRW_panel.GetData(as_instance = True)
    1368                  if pnl is None:
    1369                          results = gmPathLab.get_most_recent_result_for_test_types (
    1370                                  pk_patient = self.__patient.ID,
    1371                                  consider_meta_type = True
    1372                          )
    1373                  else:
    1374                          results = pnl.get_most_recent_results (
    1375                                  pk_patient = self.__patient.ID,
    1376                                  #order_by = ,
    1377                                  group_by_meta_type = True,
    1378                                  include_missing = self._CHBOX_show_missing.IsChecked()
    1379                          )
    1380                  items = []
    1381                  data = []
    1382                  for r in results:
    1383                          if isinstance(r, gmPathLab.cTestResult):
    1384                                  result_type = gmTools.coalesce (
    1385                                          value2test = r['pk_meta_test_type'],
    1386                                          return_instead = r['abbrev_tt'],
    1387                                          value2return = '%s%s' % (gmTools.u_sum, r['abbrev_meta'])
    1388                                  )
    1389                                  review = gmTools.bool2subst (
    1390                                          r['reviewed'],
    1391                                          '',
    1392                                          ' ' + gmTools.u_writing_hand,
    1393                                          ' ' + gmTools.u_writing_hand
    1394                                  )
    1395                                  result_val = '%s%s%s%s' % (
    1396                                          gmTools.strip_empty_lines(text = r['unified_val'])[0],
    1397                                          gmTools.coalesce(r['val_unit'], '', ' %s'),
    1398                                          gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
    1399                                          review
    1400                                  )
    1401                                  result_when = _('%s ago (%s)') % (
    1402                                          gmDateTime.format_interval_medically(interval = gmDateTime.pydt_now_here() - r['clin_when']),
    1403                                          gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d  %H:%M', accuracy = gmDateTime.acc_minutes)
    1404                                  )
    1405                                  range_info = gmTools.coalesce (
    1406                                          r.formatted_clinical_range,
    1407                                          r.formatted_normal_range
    1408                                  )
    1409                                  tt = r.format(with_source_data = True)
    1410                          else:
    1411                                  result_type = r
    1412                                  result_val = _('missing')
    1413                                  loinc_data = gmLOINC.loinc2data(r)
    1414                                  if loinc_data is None:
    1415                                          result_when = _('LOINC not found')
    1416                                          tt = u''
    1417                                  else:
    1418                                          result_when = loinc_data['term']
    1419                                          tt = gmLOINC.format_loinc(r)
    1420                                  range_info = None
    1421                          items.append([result_type, result_val, result_when, gmTools.coalesce(range_info, '')])
    1422                          data.append({'data': r, 'formatted': tt})
    1423
    1424                  self._LCTRL_results.set_string_items(items)
    1425                  self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
    1426                  self._LCTRL_results.set_data(data)
    1427
    1428                  if len(items) > 0:
    1429                          self._LCTRL_results.Select(idx = 0, on = 1)
    1430                  self._LCTRL_results.SetFocus()
    1431
    1432                  return True
    1433
    1434          #--------------------------------------------------------
    1435 -        def __on_panel_selected(self, panel):
    1436                  if panel is None:
    1437                          self._TCTRL_panel_comment.SetValue('')
    1438                          self._CHBOX_show_missing.Disable()
    1439                  else:
    1440                          pnl = self._PRW_panel.GetData(as_instance = True)
    1441                          self._TCTRL_panel_comment.SetValue(gmTools.coalesce(pnl['comment'], ''))
    1442                  self.__repopulate_ui()
    1443                  self._CHBOX_show_missing.Enable()
    1444
    1445          #--------------------------------------------------------
    1446 -        def __on_panel_selection_modified(self):
    1447                  self._TCTRL_panel_comment.SetValue('')
    1448                  if self._PRW_panel.Value.strip() == u'':
    1449                          self.__repopulate_ui()
    1450                          self._CHBOX_show_missing.Disable()
    1451
    1452          #------------------------------------------------------------
    1453          # event handlers
    1454          #------------------------------------------------------------
    1455 -        def _on_database_signal(self, **kwds):
    1456                  if self.__patient is None:
    1457                          return True
    1458
    1459                  if kwds['pk_identity'] is not None:                             # review table doesn't have pk_identity yet
    1460                          if kwds['pk_identity'] != self.__patient.ID:
    1461                                  return True
    1462
    1463                  if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results', 'clin.test_panel']:
    1464                          return True
    1465
    1466                  self._schedule_data_reget()
    1467                  return True
    1468
    1469          #------------------------------------------------------------
    1470 -        def _on_manage_panels_button_pressed(self, event):
    1471                  manage_test_panels(parent = self)
    1472
    1473          #--------------------------------------------------------
    1474 -        def _on_panel_selected(self, panel):
    1475                  wx.CallAfter(self.__on_panel_selected, panel = panel)
    1476
    1477          #--------------------------------------------------------
    1478 -        def _on_panel_selection_modified(self):
    1479                  wx.CallAfter(self.__on_panel_selection_modified)
    1480
    1481          #------------------------------------------------------------
    1482 -        def _on_result_selected(self, event):
    1483                  event.Skip()
    1484                  item_data = self._LCTRL_results.get_item_data(item_idx = event.Index)
    1485                  self._TCTRL_details.SetValue(item_data['formatted'])
    1486                  if isinstance(item_data['data'], gmPathLab.cTestResult):
    1487                          self._PNL_related_documents.lab_reference = item_data['data']
    1488                  else:
    1489                          self._PNL_related_documents.lab_reference = None
    1490
    1491          #------------------------------------------------------------
    1492 -        def _on_edit(self):
    1493                  item_data = self._LCTRL_results.get_selected_item_data(only_one = True)
    1494                  if item_data is None:
    1495                          return
    1496                  if isinstance(item_data['data'], gmPathLab.cTestResult):
    1497                          if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True):
    1498                                  self.__repopulate_ui()
    1499
    1500          #------------------------------------------------------------
    1501 -        def _on_show_missing_toggled(self, event):
    1502                  event.Skip()
    1503                  # should not happen
    1504                  if self._PRW_panel.GetData(as_instance = False) is None:
    1505                          return
    1506                  self.__repopulate_ui()
    1507
    1508          #------------------------------------------------------------
    1509          # reget mixin API
    1510          #------------------------------------------------------------
    1511 -        def _populate_with_data(self):
    1512                  self.__repopulate_ui()
    1513                  return True
    1514
    1515          #------------------------------------------------------------
    1516          # properties
    1517          #------------------------------------------------------------
    1518 -        def _get_patient(self):
    1519                  return self.__patient
    1520
    1521 -        def _set_patient(self, patient):
    1522                  if (self.__patient is None) and (patient is None):
    1523                          return
    1524                  if (self.__patient is None) or (patient is None):
    1525                          self.__patient = patient
    1526                          self._schedule_data_reget()
    1527                          return
    1528                  if self.__patient.ID == patient.ID:
    1529                          return
    1530                  self.__patient = patient
    1531                  self._schedule_data_reget()
    1532
    1533          patient = property(_get_patient, _set_patient)
    1534
    1535  #================================================================
    1536  from Gnumed.wxGladeWidgets import wxgMeasurementsAsTablePnl
    1537
    1538 -class cMeasurementsAsTablePnl(wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl, gmRegetMixin.cRegetOnPaintMixin):
    1539          """A panel for holding a grid displaying all measurement results.
    1540
    1541          - operates on a cPatient instance handed to it and NOT on the currently active patient
    1542          """
    1543 -        def __init__(self, *args, **kwargs):
    1544                  wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl.__init__(self, *args, **kwargs)
    1545
    1546                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    1547
    1548                  self.__patient = None
    1549
    1550                  self.__init_ui()
    1551                  self.__register_events()
    1552
    1553          #------------------------------------------------------------
    1554          # internal helpers
    1555          #------------------------------------------------------------
    1556 -        def __init_ui(self):
    1557                  self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
    1558
    1559                  item = self.__action_button_popup.Append(-1, _('Review and &sign'))
    1560                  self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
    1561
    1562                  item = self.__action_button_popup.Append(-1, _('Plot'))
    1563                  self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
    1564
    1565                  #item = self.__action_button_popup.Append(-1, _('Export to &file'))
    1566                  #self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item)
    1567                  #self.__action_button_popup.Enable(id = item.Id, enable = False)
    1568
    1569                  #item = self.__action_button_popup.Append(-1, _('Export to &clipboard'))
    1570                  #self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item)
    1571                  #self.__action_button_popup.Enable(id = item.Id, enable = False)
    1572
    1573                  item = self.__action_button_popup.Append(-1, _('&Delete'))
    1574                  self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
    1575
    1576                  # FIXME: create inbox message to staff to phone patient to come in
    1577                  # FIXME: generate and let edit a SOAP narrative and include the values
    1578
    1579                  self._GRID_results_all.show_by_panel = False
    1580
    1581          #------------------------------------------------------------
    1582 -        def __register_events(self):
    1583                  gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
    1584
    1585          #------------------------------------------------------------
    1586 -        def __repopulate_ui(self):
    1587                  self._GRID_results_all.patient = self.__patient
    1588                  #self._GRID_results_battery.Fit()
    1589                  self.Layout()
    1590                  return True
    1591
    1592          #------------------------------------------------------------
    1593 -        def __on_sign_current_selection(self, evt):
    1594                  self._GRID_results_all.sign_current_selection()
    1595
    1596          #------------------------------------------------------------
    1597 -        def __on_plot_current_selection(self, evt):
    1598                  self._GRID_results_all.plot_current_selection()
    1599
    1600          #------------------------------------------------------------
    1601 -        def __on_delete_current_selection(self, evt):
    1602                  self._GRID_results_all.delete_current_selection()
    1603
    1604          #------------------------------------------------------------
    1605          # event handlers
    1606          #------------------------------------------------------------
    1607 -        def _on_database_signal(self, **kwds):
    1608                  if self.__patient is None:
    1609                          return True
    1610
    1611                  if kwds['pk_identity'] is not None:                             # review table doesn't have pk_identity yet
    1612                          if kwds['pk_identity'] != self.__patient.ID:
    1613                                  return True
    1614
    1615                  if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
    1616                          return True
    1617
    1618                  self._schedule_data_reget()
    1619                  return True
    1620
    1621          #--------------------------------------------------------
    1622 -        def _on_add_button_pressed(self, event):
    1623                  edit_measurement(parent = self, measurement = None)
    1624
    1625          #--------------------------------------------------------
    1626 -        def _on_manage_types_button_pressed(self, event):
    1627                  event.Skip()
    1628                  manage_measurement_types(parent = self)
    1629
    1630          #--------------------------------------------------------
    1631 -        def _on_review_button_pressed(self, evt):
    1632                  self.PopupMenu(self.__action_button_popup)
    1633
    1634          #--------------------------------------------------------
    1635 -        def _on_select_button_pressed(self, evt):
    1636                  if self._RBTN_my_unsigned.GetValue() is True:
    1637                          self._GRID_results_all.select_cells(unsigned_only = True, accountables_only = True, keep_preselections = False)
    1638                  elif self._RBTN_all_unsigned.GetValue() is True:
    1639                          self._GRID_results_all.select_cells(unsigned_only = True, accountables_only = False, keep_preselections = False)
    1640
    1641          #------------------------------------------------------------
    1642          # reget mixin API
    1643          #------------------------------------------------------------
    1644 -        def _populate_with_data(self):
    1645                  self.__repopulate_ui()
    1646                  return True
    1647
    1648          #------------------------------------------------------------
    1649          # properties
    1650          #------------------------------------------------------------
    1651 -        def _get_patient(self):
    1652                  return self.__patient
    1653
    1654 -        def _set_patient(self, patient):
    1655                  if (self.__patient is None) and (patient is None):
    1656                          return
    1657                  if (self.__patient is None) or (patient is None):
    1658                          self.__patient = patient
    1659                          self._schedule_data_reget()
    1660                          return
    1661                  if self.__patient.ID == patient.ID:
    1662                          return
    1663                  self.__patient = patient
    1664                  self._schedule_data_reget()
    1665
    1666          patient = property(_get_patient, _set_patient)
    1667
    1668  #================================================================
    1669  # notebook based measurements plugin
    1670  #================================================================
    1671 -class cMeasurementsNb(wx.Notebook, gmPlugin.cPatientChange_PluginMixin):
    1672          """Notebook displaying measurements pages:
    1673
    1674                  - by test battery
    1675                  - by day
    1676                  - by issue/episode
    1677                  - most-recent list, perhaps by panel
    1678                  - full grid
    1679                  - full list
    1680
    1681          Used as a main notebook plugin page.
    1682
    1683          Operates on the active patient.
    1684          """
    1685          #--------------------------------------------------------
    1686 -        def __init__(self, parent, id):
    1687
    1688                  wx.Notebook.__init__ (
    1689                          self,
    1690                          parent = parent,
    1691                          id = id,
    1692                          style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
    1693                          name = self.__class__.__name__
    1694                  )
    1695                  _log.debug('created wx.Notebook: %s with ID %s', self.__class__.__name__, self.Id)
    1696                  gmPlugin.cPatientChange_PluginMixin.__init__(self)
    1697                  self.__patient = gmPerson.gmCurrentPatient()
    1698                  self.__init_ui()
    1699                  self.SetSelection(0)
    1700
    1701          #--------------------------------------------------------
    1702          # patient change plugin API
    1703          #--------------------------------------------------------
    1704 -        def _on_current_patient_unset(self, **kwds):
    1705                  for page_idx in range(self.GetPageCount()):
    1706                          page = self.GetPage(page_idx)
    1707                          page.patient = None
    1708
    1709          #--------------------------------------------------------
    1710 -        def _post_patient_selection(self, **kwds):
    1711                  for page_idx in range(self.GetPageCount()):
    1712                          page = self.GetPage(page_idx)
    1713                          page.patient = self.__patient.patient
    1714
    1715          #--------------------------------------------------------
    1716          # notebook plugin API
    1717          #--------------------------------------------------------
    1718 -        def repopulate_ui(self):
    1719                  if self.__patient.connected:
    1720                          pat = self.__patient.patient
    1721                  else:
    1722                          pat = None
    1723                  for page_idx in range(self.GetPageCount()):
    1724                          page = self.GetPage(page_idx)
    1725                          page.patient = pat
    1726
    1727                  return True
    1728
    1729          #--------------------------------------------------------
    1730          # internal API
    1731          #--------------------------------------------------------
    1732 -        def __init_ui(self):
    1733
    1734                  # by day
    1735                  new_page = cMeasurementsByDayPnl(self, -1)
    1736                  new_page.patient = None
    1737                  self.AddPage (
    1738                          page = new_page,
    1739                          text = _('Days'),
    1740                          select = True
    1741                  )
    1742
    1743                  # by issue
    1744                  new_page = cMeasurementsByIssuePnl(self, -1)
    1745                  new_page.patient = None
    1746                  self.AddPage (
    1747                          page = new_page,
    1748                          text = _('Problems'),
    1749                          select = False
    1750                  )
    1751
    1752                  # by test panel
    1753                  new_page = cMeasurementsByBatteryPnl(self, -1)
    1754                  new_page.patient = None
    1755                  self.AddPage (
    1756                          page = new_page,
    1757                          text = _('Panels'),
    1758                          select = False
    1759                  )
    1760
    1761                  # most-recent, by panel
    1762                  new_page = cMeasurementsAsMostRecentListPnl(self, -1)
    1763                  new_page.patient = None
    1764                  self.AddPage (
    1765                          page = new_page,
    1766                          text = _('Most recent'),
    1767                          select = False
    1768                  )
    1769
    1770                  # full grid
    1771                  new_page = cMeasurementsAsTablePnl(self, -1)
    1772                  new_page.patient = None
    1773                  self.AddPage (
    1774                          page = new_page,
    1775                          text = _('Table'),
    1776                          select = False
    1777                  )
    1778
    1779                  # full list
    1780                  new_page = cMeasurementsAsListPnl(self, -1)
    1781                  new_page.patient = None
    1782                  self.AddPage (
    1783                          page = new_page,
    1784                          text = _('List'),
    1785                          select = False
    1786                  )
    1787
    1788          #--------------------------------------------------------
    1789          # properties
    1790          #--------------------------------------------------------
    1791 -        def _get_patient(self):
    1792                  return self.__patient
    1793
    1794 -        def _set_patient(self, patient):
    1795                  self.__patient = patient
    1796                  if self.__patient.connected:
    1797                          pat = self.__patient.patient
    1798                  else:
    1799                          pat = None
    1800                  for page_idx in range(self.GetPageCount()):
    1801                          page = self.GetPage(page_idx)
    1802                          page.patient = pat
    1803
    1804          patient = property(_get_patient, _set_patient)
    1805
    1806  #================================================================
    1807 -class cMeasurementsGrid(wx.grid.Grid):
    1808          """A grid class for displaying measurement results.
    1809
    1810          - operates on a cPatient instance handed to it
    1811          - does NOT listen to the currently active patient
    1812          - thereby it can display any patient at any time
    1813          """
    1814          # FIXME: sort-by-battery
    1815          # FIXME: filter out empty
    1816          # FIXME: filter by tests of a selected date
    1817          # FIXME: dates DESC/ASC by cfg
    1818          # FIXME: mouse over column header: display date info
    1819 -        def __init__(self, *args, **kwargs):
    1820
    1821                  wx.grid.Grid.__init__(self, *args, **kwargs)
    1822
    1823                  self.__patient = None
    1824                  self.__panel_to_show = None
    1825                  self.__show_by_panel = False
    1826                  self.__cell_data = {}
    1827                  self.__row_label_data = []
    1828                  self.__col_label_data = []
    1829
    1830                  self.__prev_row = None
    1831                  self.__prev_col = None
    1832                  self.__prev_label_row = None
    1833                  self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::'))
    1834
    1835                  self.__init_ui()
    1836                  self.__register_events()
    1837
    1838          #------------------------------------------------------------
    1839          # external API
    1840          #------------------------------------------------------------
    1841 -        def delete_current_selection(self):
    1842                  if not self.IsSelection():
    1843                          gmDispatcher.send(signal = 'statustext', msg = _('No results selected for deletion.'))
    1844                          return True
    1845
    1846                  selected_cells = self.get_selected_cells()
    1847                  if len(selected_cells) > 20:
    1848                          results = None
    1849                          msg = _(
    1850                                  'There are %s results marked for deletion.\n'
    1851                                  '\n'
    1852                                  'Are you sure you want to delete these results ?'
    1853                          ) % len(selected_cells)
    1854                  else:
    1855                          results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
    1856                          txt = '\n'.join([ '%s %s (%s): %s %s%s' % (
    1857                                          r['clin_when'].strftime('%x %H:%M'),
    1858                                          r['unified_abbrev'],
    1859                                          r['unified_name'],
    1860                                          r['unified_val'],
    1861                                          r['val_unit'],
    1862                                          gmTools.coalesce(r['abnormality_indicator'], '', ' (%s)')
    1863                                  ) for r in results
    1864                          ])
    1865                          msg = _(
    1866                                  'The following results are marked for deletion:\n'
    1867                                  '\n'
    1868                                  '%s\n'
    1869                                  '\n'
    1870                                  'Are you sure you want to delete these results ?'
    1871                          ) % txt
    1872
    1873                  dlg = gmGuiHelpers.c2ButtonQuestionDlg (
    1874                          self,
    1875                          -1,
    1876                          caption = _('Deleting test results'),
    1877                          question = msg,
    1878                          button_defs = [
    1879                                  {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False},
    1880                                  {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True}
    1881                          ]
    1882                  )
    1883                  decision = dlg.ShowModal()
    1884
    1885                  if decision == wx.ID_YES:
    1886                          if results is None:
    1887                                  results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
    1888                          for result in results:
    1889                                  gmPathLab.delete_test_result(result)
    1890
    1891          #------------------------------------------------------------
    1892 -        def sign_current_selection(self):
    1893                  if not self.IsSelection():
    1894                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot sign results. No results selected.'))
    1895                          return True
    1896
    1897                  selected_cells = self.get_selected_cells()
    1898                  tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
    1899
    1900                  return review_tests(parent = self, tests = tests)
    1901
    1902          #------------------------------------------------------------
    1903 -        def plot_current_selection(self):
    1904
    1905                  if not self.IsSelection():
    1906                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot plot results. No results selected.'))
    1907                          return True
    1908
    1909                  tests = self.__cells_to_data (
    1910                          cells = self.get_selected_cells(),
    1911                          exclude_multi_cells = False,
    1912                          auto_include_multi_cells = True
    1913                  )
    1914
    1915                  plot_measurements(parent = self, tests = tests)
    1916          #------------------------------------------------------------
    1917 -        def get_selected_cells(self):
    1918
    1919                  sel_block_top_left = self.GetSelectionBlockTopLeft()
    1920                  sel_block_bottom_right = self.GetSelectionBlockBottomRight()
    1921                  sel_cols = self.GetSelectedCols()
    1922                  sel_rows = self.GetSelectedRows()
    1923
    1924                  selected_cells = []
    1925
    1926                  # individually selected cells (ctrl-click)
    1927                  selected_cells += self.GetSelectedCells()
    1928
    1929                  # selected rows
    1930                  selected_cells += list (
    1931                          (row, col)
    1932                                  for row in sel_rows
    1933                                  for col in range(self.GetNumberCols())
    1934                  )
    1935
    1936                  # selected columns
    1937                  selected_cells += list (
    1938                          (row, col)
    1939                                  for row in range(self.GetNumberRows())
    1940                                  for col in sel_cols
    1941                  )
    1942
    1943                  # selection blocks
    1944                  for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()):
    1945                          selected_cells += [
    1946                                  (row, col)
    1947                                          for row in range(top_left[0], bottom_right[0] + 1)
    1948                                          for col in range(top_left[1], bottom_right[1] + 1)
    1949                          ]
    1950
    1951                  return set(selected_cells)
    1952          #------------------------------------------------------------
    1953 -        def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
    1954                  """Select a range of cells according to criteria.
    1955
    1956                  unsigned_only: include only those which are not signed at all yet
    1957                  accountable_only: include only those for which the current user is responsible
    1958                  keep_preselections: broaden (rather than replace) the range of selected cells
    1959
    1960                  Combinations are powerful !
    1961                  """
    1962                  wx.BeginBusyCursor()
    1963                  self.BeginBatch()
    1964
    1965                  if not keep_preselections:
    1966                          self.ClearSelection()
    1967
    1968                  for col_idx in self.__cell_data.keys():
    1969                          for row_idx in self.__cell_data[col_idx].keys():
    1970                                  # loop over results in cell and only include
    1971                                  # those multi-value cells that are not ambiguous
    1972                                  do_not_include = False
    1973                                  for result in self.__cell_data[col_idx][row_idx]:
    1974                                          if unsigned_only:
    1975                                                  if result['reviewed']:
    1976                                                          do_not_include = True
    1977                                                          break
    1978                                          if accountables_only:
    1979                                                  if not result['you_are_responsible']:
    1980                                                          do_not_include = True
    1981                                                          break
    1982                                  if do_not_include:
    1983                                          continue
    1984
    1985                                  self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True)
    1986
    1987                  self.EndBatch()
    1988                  wx.EndBusyCursor()
    1989
    1990          #------------------------------------------------------------
    1991 -        def repopulate_grid(self):
    1992                  self.empty_grid()
    1993                  if self.__patient is None:
    1994                          return
    1995
    1996                  if self.__show_by_panel:
    1997                          if self.__panel_to_show is None:
    1998                                  return
    1999                          tests = self.__panel_to_show.get_test_types_for_results (
    2000                                  self.__patient.ID,
    2001                                  order_by = 'unified_abbrev',
    2002                                  unique_meta_types = True
    2003                          )
    2004                          self.__repopulate_grid (
    2005                                  tests4rows = tests,
    2006                                  test_pks2show = [ tt['pk_test_type'] for tt in self.__panel_to_show['test_types'] ]
    2007                          )
    2008                          return
    2009
    2010                  emr = self.__patient.emr
    2011                  tests = emr.get_test_types_for_results(order_by = 'unified_abbrev', unique_meta_types = True)
    2012                  self.__repopulate_grid(tests4rows = tests)
    2013
    2014          #------------------------------------------------------------
    2015 -        def __repopulate_grid(self, tests4rows=None, test_pks2show=None):
    2016
    2017                  if len(tests4rows) == 0:
    2018                          return
    2019
    2020                  emr = self.__patient.emr
    2021
    2022                  self.__row_label_data = tests4rows
    2023                  row_labels = [ '%s%s' % (
    2024                                  gmTools.bool2subst(test_type['is_fake_meta_type'], '', gmTools.u_sum, ''),
    2025                                  test_type['unified_abbrev']
    2026                          ) for test_type in self.__row_label_data
    2027                  ]
    2028
    2029                  self.__col_label_data = [ d['clin_when_day'] for d in emr.get_dates_for_results (
    2030                          tests = test_pks2show,
    2031                          reverse_chronological = True
    2032                  )]
    2033                  col_labels = [ gmDateTime.pydt_strftime(date, self.__date_format, accuracy = gmDateTime.acc_days) for date in self.__col_label_data ]
    2034
    2035                  results = emr.get_test_results_by_date (
    2036                          tests = test_pks2show,
    2037                          reverse_chronological = True
    2038                  )
    2039
    2040                  self.BeginBatch()
    2041
    2042                  # rows
    2043                  self.AppendRows(numRows = len(row_labels))
    2044                  for row_idx in range(len(row_labels)):
    2045                          self.SetRowLabelValue(row_idx, row_labels[row_idx])
    2046
    2047                  # columns
    2048                  self.AppendCols(numCols = len(col_labels))
    2049                  for col_idx in range(len(col_labels)):
    2050                          self.SetColLabelValue(col_idx, col_labels[col_idx])
    2051
    2052                  # cell values (list of test results)
    2053                  for result in results:
    2054                          row_idx = row_labels.index('%s%s' % (
    2055                                  gmTools.bool2subst(result['is_fake_meta_type'], '', gmTools.u_sum, ''),
    2056                                  result['unified_abbrev']
    2057                          ))
    2058                          col_idx = col_labels.index(gmDateTime.pydt_strftime(result['clin_when'], self.__date_format, accuracy = gmDateTime.acc_days))
    2059
    2060                          try:
    2061                                  self.__cell_data[col_idx]
    2062                          except KeyError:
    2063                                  self.__cell_data[col_idx] = {}
    2064
    2065                          # the tooltip always shows the youngest sub result details
    2066                          if row_idx in self.__cell_data[col_idx]:
    2067                                  self.__cell_data[col_idx][row_idx].append(result)
    2068                                  self.__cell_data[col_idx][row_idx].sort(key = lambda x: x['clin_when'], reverse = True)
    2069                          else:
    2070                                  self.__cell_data[col_idx][row_idx] = [result]
    2071
    2072                          # rebuild cell display string
    2073                          vals2display = []
    2074                          cell_has_out_of_bounds_value = False
    2075                          for sub_result in self.__cell_data[col_idx][row_idx]:
    2076
    2077                                  if sub_result.is_considered_abnormal:
    2078                                          cell_has_out_of_bounds_value = True
    2079
    2080                                  abnormality_indicator = sub_result.formatted_abnormality_indicator
    2081                                  if abnormality_indicator is None:
    2082                                          abnormality_indicator = ''
    2083                                  if abnormality_indicator != '':
    2084                                          abnormality_indicator = ' (%s)' % abnormality_indicator[:3]
    2085
    2086                                  missing_review = False
    2087                                  # warn on missing review if
    2088                                  # a) no review at all exists or
    2089                                  if not sub_result['reviewed']:
    2090                                          missing_review = True
    2091                                  # b) there is a review but
    2092                                  else:
    2093                                          # current user is reviewer and hasn't reviewed
    2094                                          if sub_result['you_are_responsible'] and not sub_result['review_by_you']:
    2095                                                  missing_review = True
    2096
    2097                                  needs_superscript = False
    2098
    2099                                  # can we display the full sub_result length ?
    2100                                  if sub_result.is_long_text:
    2101                                          lines = gmTools.strip_empty_lines (
    2102                                                  text = sub_result['unified_val'],
    2103                                                  eol = '\n',
    2104                                                  return_list = True
    2105                                          )
    2106                                          needs_superscript = True
    2107                                          tmp = lines[0][:7]
    2108                                  else:
    2109                                          val = gmTools.strip_empty_lines (
    2110                                                  text = sub_result['unified_val'],
    2111                                                  eol = '\n',
    2112                                                  return_list = False
    2113                                          ).replace('\n', '//')
    2114                                          if len(val) > 8:
    2115                                                  needs_superscript = True
    2116                                                  tmp = val[:7]
    2117                                          else:
    2118                                                  tmp = '%.8s' % val[:8]
    2119
    2120                                  # abnormal ?
    2121                                  tmp = '%s%.6s' % (tmp, abnormality_indicator)
    2122
    2123                                  # is there a comment ?
    2124                                  has_sub_result_comment = gmTools.coalesce (
    2125                                          gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']),
    2126                                          ''
    2127                                  ).strip() != ''
    2128                                  if has_sub_result_comment:
    2129                                          needs_superscript = True
    2130
    2131                                  if needs_superscript:
    2132                                          tmp = '%s%s' % (tmp, gmTools.u_superscript_one)
    2133
    2134                                  # lacking a review ?
    2135                                  if missing_review:
    2136                                          tmp = '%s %s' % (tmp, gmTools.u_writing_hand)
    2137                                  else:
    2138                                          if sub_result['is_clinically_relevant']:
    2139                                                  tmp += ' !'
    2140
    2141                                  # part of a multi-result cell ?
    2142                                  if len(self.__cell_data[col_idx][row_idx]) > 1:
    2143                                          tmp = '%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp)
    2144
    2145                                  vals2display.append(tmp)
    2146
    2147                          self.SetCellValue(row_idx, col_idx, '\n'.join(vals2display))
    2148                          self.SetCellAlignment(row_idx, col_idx, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
    2149                          # We used to color text in cells holding abnormals
    2150                          # in firebrick red but that would color ALL text (including
    2151                          # normals) and not only the abnormals within that
    2152                          # cell. Shading, however, only says that *something*
    2153                          # inside that cell is worthy of attention.
    2154                          #if sub_result_relevant:
    2155                          #       font = self.GetCellFont(row_idx, col_idx)
    2156                          #       self.SetCellTextColour(row_idx, col_idx, 'firebrick')
    2157                          #       font.SetWeight(wx.FONTWEIGHT_BOLD)
    2158                          #       self.SetCellFont(row_idx, col_idx, font)
    2159                          if cell_has_out_of_bounds_value:
    2160                                  #self.SetCellBackgroundColour(row_idx, col_idx, 'cornflower blue')
    2161                                  self.SetCellBackgroundColour(row_idx, col_idx, 'PALE TURQUOISE')
    2162
    2163                  self.EndBatch()
    2164
    2165                  self.AutoSize()
    2166                  self.AdjustScrollbars()
    2167                  self.ForceRefresh()
    2168
    2169                  #self.Fit()
    2170
    2171                  return
    2172
    2173          #------------------------------------------------------------
    2174 -        def empty_grid(self):
    2175                  self.BeginBatch()
    2176                  self.ClearGrid()
    2177                  # Windows cannot do nothing, it rather decides to assert()
    2178                  # on thinking it is supposed to do nothing
    2179                  if self.GetNumberRows() > 0:
    2180                          self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
    2181                  if self.GetNumberCols() > 0:
    2182                          self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
    2183                  self.EndBatch()
    2184                  self.__cell_data = {}
    2185                  self.__row_label_data = []
    2186                  self.__col_label_data = []
    2187
    2188          #------------------------------------------------------------
    2189 -        def get_row_tooltip(self, row=None):
    2190                  # include details about test types included ?
    2191
    2192                  # sometimes, for some reason, there is no row and
    2193                  # wxPython still tries to find a tooltip for it
    2194                  try:
    2195                          tt = self.__row_label_data[row]
    2196                  except IndexError:
    2197                          return ' '
    2198
    2199                  if tt['is_fake_meta_type']:
    2200                          return tt.format(patient = self.__patient.ID)
    2201
    2202                  meta_tt = tt.meta_test_type
    2203                  txt = meta_tt.format(with_tests = True, patient = self.__patient.ID)
    2204
    2205                  return txt
    2206
    2207          #------------------------------------------------------------
    2208 -        def get_cell_tooltip(self, col=None, row=None):
    2209                  try:
    2210                          cell_results = self.__cell_data[col][row]
    2211                  except KeyError:
    2212                          # FIXME: maybe display the most recent or when the most recent was ?
    2213                          cell_results = None
    2214
    2215                  if cell_results is None:
    2216                          return ' '
    2217
    2218                  is_multi_cell = False
    2219                  if len(cell_results) > 1:
    2220                          is_multi_cell = True
    2221                  result = cell_results[0]
    2222
    2223                  tt = ''
    2224                  # header
    2225                  if is_multi_cell:
    2226                          tt += _('Details of most recent (topmost) result !               \n')
    2227                  if result.is_long_text:
    2228                          tt += gmTools.strip_empty_lines(text = result['val_alpha'], eol = '\n', return_list = False)
    2229                          return tt
    2230
    2231                  tt += result.format(with_review = True, with_evaluation = True, with_ranges = True)
    2232                  return tt
    2233
    2234          #------------------------------------------------------------
    2235          # internal helpers
    2236          #------------------------------------------------------------
    2237 -        def __init_ui(self):
    2238                  #self.SetMinSize(wx.DefaultSize)
    2239                  self.SetMinSize((10, 10))
    2240
    2241                  self.CreateGrid(0, 1)
    2242                  self.EnableEditing(0)
    2243                  self.EnableDragGridSize(1)
    2244
    2245                  # column labels
    2246                  # setting this screws up the labels: they are cut off and displaced
    2247                  #self.SetColLabelAlignment(wx.ALIGN_CENTER, wx.ALIGN_BOTTOM)
    2248
    2249                  # row labels
    2250                  self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE)             # starting with 2.8.8
    2251                  #self.SetRowLabelSize(150)
    2252                  self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE)
    2253                  font = self.GetLabelFont()
    2254                  font.SetWeight(wx.FONTWEIGHT_LIGHT)
    2255                  self.SetLabelFont(font)
    2256
    2257                  # add link to left upper corner
    2258                  dbcfg = gmCfg.cCfgSQL()
    2259                  url = dbcfg.get2 (
    2260                          option = 'external.urls.measurements_encyclopedia',
    2261                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    2262                          bias = 'user',
    2263                          default = gmPathLab.URL_test_result_information
    2264                  )
    2265
    2266                  self.__WIN_corner = self.GetGridCornerLabelWindow()             # a wx.Window instance
    2267
    2268                  LNK_lab = wxh.HyperlinkCtrl (
    2269                          self.__WIN_corner,
    2270                          -1,
    2271                          label = _('Tests'),
    2272                          style = wxh.HL_DEFAULT_STYLE                    # wx.TE_READONLY|wx.TE_CENTRE| wx.NO_BORDER |
    2273                  )
    2274                  LNK_lab.SetURL(url)
    2275                  LNK_lab.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND))
    2276                  LNK_lab.SetToolTip(_(
    2277                          'Navigate to an encyclopedia of measurements\n'
    2278                          'and test methods on the web.\n'
    2279                          '\n'
    2280                          ' <%s>'
    2281                  ) % url)
    2282
    2283                  SZR_inner = wx.BoxSizer(wx.HORIZONTAL)
    2284                  SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)                # spacer
    2285                  SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0)          #wx.ALIGN_CENTER wx.EXPAND
    2286                  SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)                # spacer
    2287
    2288                  SZR_corner = wx.BoxSizer(wx.VERTICAL)
    2289                  SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)               # spacer
    2290                  SZR_corner.Add(SZR_inner, 0, wx.EXPAND)                 # inner sizer with centered hyperlink
    2291                  SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)               # spacer
    2292
    2293                  self.__WIN_corner.SetSizer(SZR_corner)
    2294                  SZR_corner.Fit(self.__WIN_corner)
    2295
    2296          #------------------------------------------------------------
    2297 -        def __resize_corner_window(self, evt):
    2298                  self.__WIN_corner.Layout()
    2299
    2300          #------------------------------------------------------------
    2301 -        def __cells_to_data(self, cells=None, exclude_multi_cells=False, auto_include_multi_cells=False):
    2302                  """List of <cells> must be in row / col order."""
    2303                  data = []
    2304                  for row, col in cells:
    2305                          try:
    2306                                  # cell data is stored col / row
    2307                                  data_list = self.__cell_data[col][row]
    2308                          except KeyError:
    2309                                  continue
    2310
    2311                          if len(data_list) == 1:
    2312                                  data.append(data_list[0])
    2313                                  continue
    2314
    2315                          if exclude_multi_cells:
    2316                                  gmDispatcher.send(signal = 'statustext', msg = _('Excluding multi-result field from further processing.'))
    2317                                  continue
    2318
    2319                          if auto_include_multi_cells:
    2320                                  data.extend(data_list)
    2321                                  continue
    2322
    2323                          data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list)
    2324                          if data_to_include is None:
    2325                                  continue
    2326                          data.extend(data_to_include)
    2327
    2328                  return data
    2329
    2330          #------------------------------------------------------------
    2331 -        def __get_choices_from_multi_cell(self, cell_data=None, single_selection=False):
    2332                  data = gmListWidgets.get_choices_from_list (
    2333                          parent = self,
    2334                          msg = _(
    2335                                  'Your selection includes a field with multiple results.\n'
    2336                                  '\n'
    2337                                  'Please select the individual results you want to work on:'
    2338                          ),
    2339                          caption = _('Selecting test results'),
    2340                          choices = [ [d['clin_when'], '%s: %s' % (d['abbrev_tt'], d['name_tt']), d['unified_val']] for d in cell_data ],
    2341                          columns = [ _('Date / Time'), _('Test'), _('Result') ],
    2342                          data = cell_data,
    2343                          single_selection = single_selection
    2344                  )
    2345                  return data
    2346
    2347          #------------------------------------------------------------
    2348          # event handling
    2349          #------------------------------------------------------------
    2350 -        def __register_events(self):
    2351                  # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow
    2352                  self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
    2353                  self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels)
    2354                  #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels)
    2355
    2356                  # sizing left upper corner window
    2357                  self.Bind(wx.EVT_SIZE, self.__resize_corner_window)
    2358
    2359                  # editing cells
    2360                  self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
    2361
    2362          #------------------------------------------------------------
    2363 -        def __on_cell_left_dclicked(self, evt):
    2364                  col = evt.GetCol()
    2365                  row = evt.GetRow()
    2366
    2367                  try:
    2368                          self.__cell_data[col][row]
    2369                  except KeyError:                # empty cell
    2370                          presets = {}
    2371                          col_date = self.__col_label_data[col]
    2372                          presets['clin_when'] = {'data': col_date}
    2373                          test_type = self.__row_label_data[row]
    2374                          temporally_closest_result_of_row_type = test_type.meta_test_type.get_temporally_closest_result(col_date, self.__patient.ID)
    2375                          if temporally_closest_result_of_row_type is not None:
    2376                                  presets['pk_test_type'] = {'data': temporally_closest_result_of_row_type['pk_test_type']}
    2377                          same_day_results = gmPathLab.get_results_for_day (
    2378                                  timestamp = col_date,
    2379                                  patient = self.__patient.ID,
    2380                                  order_by = None
    2381                          )
    2382                          if len(same_day_results) > 0:
    2383                                  presets['pk_episode'] = {'data': same_day_results[0]['pk_episode']}
    2384                                  # maybe ['comment'] as in "medical context" ? - not thought through yet
    2385                          # no need to set because because setting pk_test_type will do so:
    2386                          #       presets['val_unit']
    2387                          #       presets['val_normal_min']
    2388                          #       presets['val_normal_max']
    2389                          #       presets['val_normal_range']
    2390                          #       presets['val_target_min']
    2391                          #       presets['val_target_max']
    2392                          #       presets['val_target_range']
    2393                          edit_measurement (
    2394                                  parent = self,
    2395                                  measurement = None,
    2396                                  single_entry = True,
    2397                                  presets = presets
    2398                          )
    2399                          return
    2400
    2401                  if len(self.__cell_data[col][row]) > 1:
    2402                          data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True)
    2403                  else:
    2404                          data = self.__cell_data[col][row][0]
    2405
    2406                  if data is None:
    2407                          return
    2408
    2409                  edit_measurement(parent = self, measurement = data, single_entry = True)
    2410
    2411          #------------------------------------------------------------
    2412  #     def OnMouseMotionRowLabel(self, evt):
    2413  #         x, y = self.CalcUnscrolledPosition(evt.GetPosition())
    2414  #         row = self.YToRow(y)
    2415  #         label = self.table().GetRowHelpValue(row)
    2416  #         self.GetGridRowLabelWindow().SetToolTip(label or "")
    2417  #         evt.Skip()
    2418 -        def __on_mouse_over_row_labels(self, evt):
    2419
    2420                  # Use CalcUnscrolledPosition() to get the mouse position within the
    2421                  # entire grid including what's offscreen
    2422                  x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
    2423
    2424                  row = self.YToRow(y)
    2425
    2426                  if self.__prev_label_row == row:
    2427                          return
    2428
    2429                  self.__prev_label_row == row
    2430
    2431                  evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row))
    2432          #------------------------------------------------------------
    2433  #     def OnMouseMotionColLabel(self, evt):
    2434  #         x, y = self.CalcUnscrolledPosition(evt.GetPosition())
    2435  #         col = self.XToCol(x)
    2436  #         label = self.table().GetColHelpValue(col)
    2437  #         self.GetGridColLabelWindow().SetToolTip(label or "")
    2438  #         evt.Skip()
    2439          #------------------------------------------------------------
    2440 -        def __on_mouse_over_cells(self, evt):
    2441                  """Calculate where the mouse is and set the tooltip dynamically."""
    2442
    2443                  # Use CalcUnscrolledPosition() to get the mouse position within the
    2444                  # entire grid including what's offscreen
    2445                  x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
    2446
    2447                  # use this logic to prevent tooltips outside the actual cells
    2448                  # apply to GetRowSize, too
    2449  #        tot = 0
    2450  #        for col in range(self.NumberCols):
    2451  #            tot += self.GetColSize(col)
    2452  #            if xpos <= tot:
    2453  #                self.tool_tip.Tip = 'Tool tip for Column %s' % (
    2454  #                    self.GetColLabelValue(col))
    2455  #                break
    2456  #            else:  # mouse is in label area beyond the right-most column
    2457  #            self.tool_tip.Tip = ''
    2458
    2459                  row, col = self.XYToCell(x, y)
    2460
    2461                  if (row == self.__prev_row) and (col == self.__prev_col):
    2462                          return
    2463
    2464                  self.__prev_row = row
    2465                  self.__prev_col = col
    2466
    2467                  evt.GetEventObject().SetToolTip(self.get_cell_tooltip(col=col, row=row))
    2468
    2469          #------------------------------------------------------------
    2470          # properties
    2471          #------------------------------------------------------------
    2472 -        def _get_patient(self):
    2473                  return self.__patient
    2474
    2475 -        def _set_patient(self, patient):
    2476                  self.__patient = patient
    2477                  self.repopulate_grid()
    2478
    2479          patient = property(_get_patient, _set_patient)
    2480          #------------------------------------------------------------
    2481 -        def _set_panel_to_show(self, panel):
    2482                  self.__panel_to_show = panel
    2483                  self.repopulate_grid()
    2484
    2485          panel_to_show = property(lambda x:x, _set_panel_to_show)
    2486          #------------------------------------------------------------
    2487 -        def _set_show_by_panel(self, show_by_panel):
    2488                  self.__show_by_panel = show_by_panel
    2489                  self.repopulate_grid()
    2490
    2491          show_by_panel = property(lambda x:x, _set_show_by_panel)
    2492
    2493  #================================================================
    2494  # integrated measurements plugin
    2495  #================================================================
    2496  from Gnumed.wxGladeWidgets import wxgMeasurementsPnl
    2497
    2498 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
    2499          """Panel holding a grid with lab data. Used as notebook page."""
    2500
    2501 -        def __init__(self, *args, **kwargs):
    2502
    2503                  wxgMeasurementsPnl.wxgMeasurementsPnl.__init__(self, *args, **kwargs)
    2504                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    2505                  self.__display_mode = 'grid'
    2506                  self.__init_ui()
    2507                  self.__register_interests()
    2508          #--------------------------------------------------------
    2509          # event handling
    2510          #--------------------------------------------------------
    2511 -        def __register_interests(self):
    2512                  gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
    2513                  gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
    2514                  gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._schedule_data_reget)
    2515                  gmDispatcher.connect(signal = 'clin.reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
    2516          #--------------------------------------------------------
    2517 -        def _on_post_patient_selection(self):
    2518                  self._schedule_data_reget()
    2519          #--------------------------------------------------------
    2520 -        def _on_pre_patient_unselection(self):
    2521                  self._GRID_results_all.patient = None
    2522                  self._GRID_results_battery.patient = None
    2523          #--------------------------------------------------------
    2524 -        def _on_add_button_pressed(self, event):
    2525                  edit_measurement(parent = self, measurement = None)
    2526          #--------------------------------------------------------
    2527 -        def _on_manage_types_button_pressed(self, event):
    2528                  event.Skip()
    2529                  manage_measurement_types(parent = self)
    2530          #--------------------------------------------------------
    2531 -        def _on_list_button_pressed(self, event):
    2532                  event.Skip()
    2533                  manage_measurements(parent = self, single_selection = True)#, emr = pat.emr)
    2534          #--------------------------------------------------------
    2535 -        def _on_review_button_pressed(self, evt):
    2536                  self.PopupMenu(self.__action_button_popup)
    2537          #--------------------------------------------------------
    2538 -        def _on_select_button_pressed(self, evt):
    2539                  if self._RBTN_my_unsigned.GetValue() is True:
    2540                          self._GRID_results_all.select_cells(unsigned_only = True, accountables_only = True, keep_preselections = False)
    2541                  elif self._RBTN_all_unsigned.GetValue() is True:
    2542                          self._GRID_results_all.select_cells(unsigned_only = True, accountables_only = False, keep_preselections = False)
    2543          #--------------------------------------------------------
    2544 -        def _on_manage_panels_button_pressed(self, event):
    2545                  manage_test_panels(parent = self)
    2546          #--------------------------------------------------------
    2547 -        def _on_display_mode_button_pressed(self, event):
    2548                  event.Skip()
    2549                  if self.__display_mode == 'grid':
    2550                          self._BTN_display_mode.SetLabel(_('All: as &Grid'))
    2551                          self.__display_mode = 'day'
    2552                          #self._GRID_results_all.Hide()
    2553                          self._PNL_results_all_grid.Hide()
    2554                          if self._PNL_results_all_listed.patient is None:
    2555                                  self._PNL_results_all_listed.patient = self._GRID_results_all.patient
    2556                          self._PNL_results_all_listed.Show()
    2557                  else:
    2558                          self._BTN_display_mode.SetLabel(_('All: by &Day'))
    2559                          self.__display_mode = 'grid'
    2560                          self._PNL_results_all_listed.Hide()
    2561                          if self._GRID_results_all.patient is None:
    2562                                  self._GRID_results_all.patient = self._PNL_results_all_listed.patient
    2563                          #self._GRID_results_all.Show()
    2564                          self._PNL_results_all_grid.Show()
    2565                  self.Layout()
    2566          #--------------------------------------------------------
    2567 -        def __on_sign_current_selection(self, evt):
    2568                  self._GRID_results_all.sign_current_selection()
    2569          #--------------------------------------------------------
    2570 -        def __on_plot_current_selection(self, evt):
    2571                  self._GRID_results_all.plot_current_selection()
    2572          #--------------------------------------------------------
    2573 -        def __on_delete_current_selection(self, evt):
    2574                  self._GRID_results_all.delete_current_selection()
    2575          #--------------------------------------------------------
    2576 -        def _on_panel_selected(self, panel):
    2577                  wx.CallAfter(self.__on_panel_selected, panel=panel)
    2578          #--------------------------------------------------------
    2579 -        def __on_panel_selected(self, panel):
    2580                  if panel is None:
    2581                          self._TCTRL_panel_comment.SetValue('')
    2582                          self._GRID_results_battery.panel_to_show = None
    2583                          #self._GRID_results_battery.Hide()
    2584                          self._PNL_results_battery_grid.Hide()
    2585                  else:
    2586                          pnl = self._PRW_panel.GetData(as_instance = True)
    2587                          self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
    2588                                  pnl['comment'],
    2589                                  ''
    2590                          ))
    2591                          self._GRID_results_battery.panel_to_show = pnl
    2592                          #self._GRID_results_battery.Show()
    2593                          self._PNL_results_battery_grid.Show()
    2594                  self._GRID_results_battery.Fit()
    2595                  self._GRID_results_all.Fit()
    2596                  self.Layout()
    2597          #--------------------------------------------------------
    2598 -        def _on_panel_selection_modified(self):
    2599                  wx.CallAfter(self.__on_panel_selection_modified)
    2600          #--------------------------------------------------------
    2601 -        def __on_panel_selection_modified(self):
    2602                  self._TCTRL_panel_comment.SetValue('')
    2603                  if self._PRW_panel.GetValue().strip() == '':
    2604                          self._GRID_results_battery.panel_to_show = None
    2605                          #self._GRID_results_battery.Hide()
    2606                          self._PNL_results_battery_grid.Hide()
    2607                          self.Layout()
    2608          #--------------------------------------------------------
    2609          # internal API
    2610          #--------------------------------------------------------
    2611 -        def __init_ui(self):
    2612                  self.SetMinSize((10, 10))
    2613
    2614                  self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
    2615
    2616                  item = self.__action_button_popup.Append(-1, _('Review and &sign'))
    2617                  self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
    2618
    2619                  item = self.__action_button_popup.Append(-1, _('Plot'))
    2620                  self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
    2621
    2622                  item = self.__action_button_popup.Append(-1, _('Export to &file'))
    2623                  self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item)
    2624                  self.__action_button_popup.Enable(id = menu_id, enable = False)
    2625
    2626                  item = self.__action_button_popup.Append(-1, _('Export to &clipboard'))
    2627                  self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item)
    2628                  self.__action_button_popup.Enable(id = menu_id, enable = False)
    2629
    2630                  item = self.__action_button_popup.Append(-1, _('&Delete'))
    2631                  self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
    2632
    2633                  # FIXME: create inbox message to staff to phone patient to come in
    2634                  # FIXME: generate and let edit a SOAP narrative and include the values
    2635
    2636                  self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
    2637                  self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
    2638
    2639                  self._GRID_results_battery.show_by_panel = True
    2640                  self._GRID_results_battery.panel_to_show = None
    2641                  #self._GRID_results_battery.Hide()
    2642                  self._PNL_results_battery_grid.Hide()
    2643                  self._BTN_display_mode.SetLabel(_('All: by &Day'))
    2644                  #self._GRID_results_all.Show()
    2645                  self._PNL_results_all_grid.Show()
    2646                  self._PNL_results_all_listed.Hide()
    2647                  self.Layout()
    2648
    2649                  self._PRW_panel.SetFocus()
    2650          #--------------------------------------------------------
    2651          # reget mixin API
    2652          #--------------------------------------------------------
    2653 -        def _populate_with_data(self):
    2654                  pat = gmPerson.gmCurrentPatient()
    2655                  if pat.connected:
    2656                          self._GRID_results_battery.patient = pat
    2657                          if self.__display_mode == 'grid':
    2658                                  self._GRID_results_all.patient = pat
    2659                                  self._PNL_results_all_listed.patient = None
    2660                          else:
    2661                                  self._GRID_results_all.patient = None
    2662                                  self._PNL_results_all_listed.patient = pat
    2663                  else:
    2664                          self._GRID_results_battery.patient = None
    2665                          self._GRID_results_all.patient = None
    2666                          self._PNL_results_all_listed.patient = None
    2667                  return True
    2668
    2669  #================================================================
    2670  # editing widgets
    2671  #================================================================
    2672 -def review_tests(parent=None, tests=None):
    2673
    2674          if tests is None:
    2675                  return True
    2676
    2677          if len(tests) == 0:
    2678                  return True
    2679
    2680          if parent is None:
    2681                  parent = wx.GetApp().GetTopWindow()
    2682
    2683          if len(tests) > 10:
    2684                  test_count = len(tests)
    2685                  tests2show = None
    2686          else:
    2687                  test_count = None
    2688                  tests2show = tests
    2689                  if len(tests) == 0:
    2690                          return True
    2691
    2692          dlg = cMeasurementsReviewDlg(parent, -1, tests = tests, test_count = test_count)
    2693          decision = dlg.ShowModal()
    2694          if decision != wx.ID_APPLY:
    2695                  return True
    2696
    2697          wx.BeginBusyCursor()
    2698          if dlg._RBTN_confirm_abnormal.GetValue():
    2699                  abnormal = None
    2700          elif dlg._RBTN_results_normal.GetValue():
    2701                  abnormal = False
    2702          else:
    2703                  abnormal = True
    2704
    2705          if dlg._RBTN_confirm_relevance.GetValue():
    2706                  relevant = None
    2707          elif dlg._RBTN_results_not_relevant.GetValue():
    2708                  relevant = False
    2709          else:
    2710                  relevant = True
    2711
    2712          comment = None
    2713          if len(tests) == 1:
    2714                  comment = dlg._TCTRL_comment.GetValue()
    2715
    2716          make_responsible = dlg._CHBOX_responsible.IsChecked()
    2717          dlg.DestroyLater()
    2718
    2719          for test in tests:
    2720                  test.set_review (
    2721                          technically_abnormal = abnormal,
    2722                          clinically_relevant = relevant,
    2723                          comment = comment,
    2724                          make_me_responsible = make_responsible
    2725                  )
    2726          wx.EndBusyCursor()
    2727
    2728          return True
    2729
    2730  #----------------------------------------------------------------
    2731  from Gnumed.wxGladeWidgets import wxgMeasurementsReviewDlg
    2732
    2733 -class cMeasurementsReviewDlg(wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg):
    2734
    2735 -        def __init__(self, *args, **kwargs):
    2736
    2737                  try:
    2738                          tests = kwargs['tests']
    2739                          del kwargs['tests']
    2740                          test_count = len(tests)
    2741                          try: del kwargs['test_count']
    2742                          except KeyError: pass
    2743                  except KeyError:
    2744                          tests = None
    2745                          test_count = kwargs['test_count']
    2746                          del kwargs['test_count']
    2747
    2748                  wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs)
    2749
    2750                  if tests is None:
    2751                          msg = _('%s results selected. Too many to list individually.') % test_count
    2752                  else:
    2753                          msg = '\n'.join (
    2754                                  [       '%s: %s %s (%s)' % (
    2755                                                  t['unified_abbrev'],
    2756                                                  t['unified_val'],
    2757                                                  t['val_unit'],
    2758                                                  gmDateTime.pydt_strftime(t['clin_when'], '%Y %b %d')
    2759                                          ) for t in tests
    2760                                  ]
    2761                          )
    2762
    2763                  self._LBL_tests.SetLabel(msg)
    2764
    2765                  if test_count == 1:
    2766                          self._TCTRL_comment.Enable(True)
    2767                          self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], ''))
    2768                          if tests[0]['you_are_responsible']:
    2769                                  self._CHBOX_responsible.Enable(False)
    2770
    2771                  self.Fit()
    2772          #--------------------------------------------------------
    2773          # event handling
    2774          #--------------------------------------------------------
    2775 -        def _on_signoff_button_pressed(self, evt):
    2776                  if self.IsModal():
    2777                          self.EndModal(wx.ID_APPLY)
    2778                  else:
    2779                          self.Close()
    2780
    2781  #================================================================
    2782  from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl
    2783
    2784 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
    2785          """This edit area saves *new* measurements into the active patient only."""
    2786
    2787 -        def __init__(self, *args, **kwargs):
    2788
    2789                  try:
    2790                          self.__default_date = kwargs['date']
    2791                          del kwargs['date']
    2792                  except KeyError:
    2793                          self.__default_date = None
    2794
    2795                  wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl.__init__(self, *args, **kwargs)
    2796                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    2797
    2798                  self.__register_interests()
    2799
    2800                  self.successful_save_msg = _('Successfully saved measurement.')
    2801
    2802                  self._DPRW_evaluated.display_accuracy = gmDateTime.acc_minutes
    2803
    2804          #--------------------------------------------------------
    2805          # generic edit area mixin API
    2806          #----------------------------------------------------------------
    2807 -        def set_fields(self, fields):
    2808                  try:
    2809                          self._PRW_test.SetData(data = fields['pk_test_type']['data'])
    2810                  except KeyError:
    2811                          pass
    2812                  try:
    2813                          self._DPRW_evaluated.SetData(data = fields['clin_when']['data'])
    2814                  except KeyError:
    2815                          pass
    2816                  try:
    2817                          self._PRW_problem.SetData(data = fields['pk_episode']['data'])
    2818                  except KeyError:
    2819                          pass
    2820                  try:
    2821                          self._PRW_units.SetText(fields['val_unit']['data'], fields['val_unit']['data'], True)
    2822                  except KeyError:
    2823                          pass
    2824                  try:
    2825                          self._TCTRL_normal_min.SetValue(fields['val_normal_min']['data'])
    2826                  except KeyError:
    2827                          pass
    2828                  try:
    2829                          self._TCTRL_normal_max.SetValue(fields['val_normal_max']['data'])
    2830                  except KeyError:
    2831                          pass
    2832                  try:
    2833                          self._TCTRL_normal_range.SetValue(fields['val_normal_range']['data'])
    2834                  except KeyError:
    2835                          pass
    2836                  try:
    2837                          self._TCTRL_target_min.SetValue(fields['val_target_min']['data'])
    2838                  except KeyError:
    2839                          pass
    2840                  try:
    2841                          self._TCTRL_target_max.SetValue(fields['val_target_max']['data'])
    2842                  except KeyError:
    2843                          pass
    2844                  try:
    2845                          self._TCTRL_target_range.SetValue(fields['val_target_range']['data'])
    2846                  except KeyError:
    2847                          pass
    2848
    2849                  self._TCTRL_result.SetFocus()
    2850
    2851          #--------------------------------------------------------
    2852 -        def _refresh_as_new(self):
    2853                  self._PRW_test.SetText('', None, True)
    2854                  self.__refresh_loinc_info()
    2855                  self.__refresh_previous_value()
    2856                  self.__update_units_context()
    2857                  self._TCTRL_result.SetValue('')
    2858                  self._PRW_units.SetText('', None, True)
    2859                  self._PRW_abnormality_indicator.SetText('', None, True)
    2860                  if self.__default_date is None:
    2861                          self._DPRW_evaluated.SetData(data = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone))
    2862                  else:
    2863                          self._DPRW_evaluated.SetData(data =     None)
    2864                  self._TCTRL_note_test_org.SetValue('')
    2865                  self._PRW_intended_reviewer.SetData(gmStaff.gmCurrentProvider()['pk_staff'])
    2866                  self._PRW_problem.SetData()
    2867                  self._TCTRL_narrative.SetValue('')
    2868                  self._CHBOX_review.SetValue(False)
    2869                  self._CHBOX_abnormal.SetValue(False)
    2870                  self._CHBOX_relevant.SetValue(False)
    2871                  self._CHBOX_abnormal.Enable(False)
    2872                  self._CHBOX_relevant.Enable(False)
    2873                  self._TCTRL_review_comment.SetValue('')
    2874                  self._TCTRL_normal_min.SetValue('')
    2875                  self._TCTRL_normal_max.SetValue('')
    2876                  self._TCTRL_normal_range.SetValue('')
    2877                  self._TCTRL_target_min.SetValue('')
    2878                  self._TCTRL_target_max.SetValue('')
    2879                  self._TCTRL_target_range.SetValue('')
    2880                  self._TCTRL_norm_ref_group.SetValue('')
    2881
    2882                  self._PRW_test.SetFocus()
    2883          #--------------------------------------------------------
    2884 -        def _refresh_from_existing(self):
    2885                  self._PRW_test.SetData(data = self.data['pk_test_type'])
    2886                  self.__refresh_loinc_info()
    2887                  self.__refresh_previous_value()
    2888                  self.__update_units_context()
    2889                  self._TCTRL_result.SetValue(self.data['unified_val'])
    2890                  self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True)
    2891                  self._PRW_abnormality_indicator.SetText (
    2892                          gmTools.coalesce(self.data['abnormality_indicator'], ''),
    2893                          gmTools.coalesce(self.data['abnormality_indicator'], ''),
    2894                          True
    2895                  )
    2896                  self._DPRW_evaluated.SetData(data = self.data['clin_when'])
    2897                  self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], ''))
    2898                  self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
    2899                  self._PRW_problem.SetData(self.data['pk_episode'])
    2900                  self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], ''))
    2901                  self._CHBOX_review.SetValue(False)
    2902                  self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False))
    2903                  self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False))
    2904                  self._CHBOX_abnormal.Enable(False)
    2905                  self._CHBOX_relevant.Enable(False)
    2906                  self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], ''))
    2907                  self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(self.data['val_normal_min'], '')))
    2908                  self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(self.data['val_normal_max'], '')))
    2909                  self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], ''))
    2910                  self._TCTRL_target_min.SetValue(str(gmTools.coalesce(self.data['val_target_min'], '')))
    2911                  self._TCTRL_target_max.SetValue(str(gmTools.coalesce(self.data['val_target_max'], '')))
    2912                  self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], ''))
    2913                  self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], ''))
    2914
    2915                  self._TCTRL_result.SetFocus()
    2916          #--------------------------------------------------------
    2917 -        def _refresh_as_new_from_existing(self):
    2918                  self._PRW_test.SetText('', None, True)
    2919                  self.__refresh_loinc_info()
    2920                  self.__refresh_previous_value()
    2921                  self.__update_units_context()
    2922                  self._TCTRL_result.SetValue('')
    2923                  self._PRW_units.SetText('', None, True)
    2924                  self._PRW_abnormality_indicator.SetText('', None, True)
    2925                  self._DPRW_evaluated.SetData(data = self.data['clin_when'])
    2926                  self._TCTRL_note_test_org.SetValue('')
    2927                  self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
    2928                  self._PRW_problem.SetData(self.data['pk_episode'])
    2929                  self._TCTRL_narrative.SetValue('')
    2930                  self._CHBOX_review.SetValue(False)
    2931                  self._CHBOX_abnormal.SetValue(False)
    2932                  self._CHBOX_relevant.SetValue(False)
    2933                  self._CHBOX_abnormal.Enable(False)
    2934                  self._CHBOX_relevant.Enable(False)
    2935                  self._TCTRL_review_comment.SetValue('')
    2936                  self._TCTRL_normal_min.SetValue('')
    2937                  self._TCTRL_normal_max.SetValue('')
    2938                  self._TCTRL_normal_range.SetValue('')
    2939                  self._TCTRL_target_min.SetValue('')
    2940                  self._TCTRL_target_max.SetValue('')
    2941                  self._TCTRL_target_range.SetValue('')
    2942                  self._TCTRL_norm_ref_group.SetValue('')
    2943
    2944                  self._PRW_test.SetFocus()
    2945          #--------------------------------------------------------
    2946 -        def _valid_for_save(self):
    2947
    2948                  validity = True
    2949
    2950                  if not self._DPRW_evaluated.is_valid_timestamp():
    2951                          self._DPRW_evaluated.display_as_valid(False)
    2952                          validity = False
    2953                  else:
    2954                          self._DPRW_evaluated.display_as_valid(True)
    2955
    2956                  val = self._TCTRL_result.GetValue().strip()
    2957                  if val == '':
    2958                          validity = False
    2959                          self.display_ctrl_as_valid(self._TCTRL_result, False)
    2960                  else:
    2961                          self.display_ctrl_as_valid(self._TCTRL_result, True)
    2962                          numeric, val = gmTools.input2decimal(val)
    2963                          if numeric:
    2964                                  if self._PRW_units.GetValue().strip() == '':
    2965                                          self._PRW_units.display_as_valid(False)
    2966                                          validity = False
    2967                                  else:
    2968                                          self._PRW_units.display_as_valid(True)
    2969                          else:
    2970                                  self._PRW_units.display_as_valid(True)
    2971
    2972                  if self._PRW_problem.GetValue().strip() == '':
    2973                          self._PRW_problem.display_as_valid(False)
    2974                          validity = False
    2975                  else:
    2976                          self._PRW_problem.display_as_valid(True)
    2977
    2978                  if self._PRW_test.GetValue().strip() == '':
    2979                          self._PRW_test.display_as_valid(False)
    2980                          validity = False
    2981                  else:
    2982                          self._PRW_test.display_as_valid(True)
    2983
    2984                  if self._PRW_intended_reviewer.GetData() is None:
    2985                          self._PRW_intended_reviewer.display_as_valid(False)
    2986                          validity = False
    2987                  else:
    2988                          self._PRW_intended_reviewer.display_as_valid(True)
    2989
    2990                  ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max]
    2991                  for widget in ctrls:
    2992                          val = widget.GetValue().strip()
    2993                          if val == '':
    2994                                  continue
    2995                          try:
    2996                                  decimal.Decimal(val.replace(',', '.', 1))
    2997                                  self.display_ctrl_as_valid(widget, True)
    2998                          except:
    2999                                  validity = False
    3000                                  self.display_ctrl_as_valid(widget, False)
    3001
    3002                  if validity is False:
    3003                          self.StatusText = _('Cannot save result. Invalid or missing essential input.')
    3004
    3005                  return validity
    3006          #--------------------------------------------------------
    3007 -        def _save_as_new(self):
    3008
    3009                  emr = gmPerson.gmCurrentPatient().emr
    3010
    3011                  success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
    3012                  if success:
    3013                          v_num = result
    3014                          v_al = None
    3015                  else:
    3016                          v_al = self._TCTRL_result.GetValue().strip()
    3017                          v_num = None
    3018
    3019                  pk_type = self._PRW_test.GetData()
    3020                  if pk_type is None:
    3021                          abbrev = self._PRW_test.GetValue().strip()
    3022                          name = self._PRW_test.GetValue().strip()
    3023                          unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
    3024                          lab = manage_measurement_orgs (
    3025                                  parent = self,
    3026                                  msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
    3027                          )
    3028                          if lab is not None:
    3029                                  lab = lab['pk_test_org']
    3030                          tt = gmPathLab.create_measurement_type (
    3031                                  lab = lab,
    3032                                  abbrev = abbrev,
    3033                                  name = name,
    3034                                  unit = unit
    3035                          )
    3036                          pk_type = tt['pk_test_type']
    3037
    3038                  tr = emr.add_test_result (
    3039                          episode = self._PRW_problem.GetData(can_create=True, is_open=False),
    3040                          type = pk_type,
    3041                          intended_reviewer = self._PRW_intended_reviewer.GetData(),
    3042                          val_num = v_num,
    3043                          val_alpha = v_al,
    3044                          unit = self._PRW_units.GetValue()
    3045                  )
    3046
    3047                  tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
    3048
    3049                  ctrls = [
    3050                          ('abnormality_indicator', self._PRW_abnormality_indicator),
    3051                          ('note_test_org', self._TCTRL_note_test_org),
    3052                          ('comment', self._TCTRL_narrative),
    3053                          ('val_normal_range', self._TCTRL_normal_range),
    3054                          ('val_target_range', self._TCTRL_target_range),
    3055                          ('norm_ref_group', self._TCTRL_norm_ref_group)
    3056                  ]
    3057                  for field, widget in ctrls:
    3058                          tr[field] = widget.GetValue().strip()
    3059
    3060                  ctrls = [
    3061                          ('val_normal_min', self._TCTRL_normal_min),
    3062                          ('val_normal_max', self._TCTRL_normal_max),
    3063                          ('val_target_min', self._TCTRL_target_min),
    3064                          ('val_target_max', self._TCTRL_target_max)
    3065                  ]
    3066                  for field, widget in ctrls:
    3067                          val = widget.GetValue().strip()
    3068                          if val == '':
    3069                                  tr[field] = None
    3070                          else:
    3071                                  tr[field] = decimal.Decimal(val.replace(',', '.', 1))
    3072
    3073                  tr.save_payload()
    3074
    3075                  if self._CHBOX_review.GetValue() is True:
    3076                          tr.set_review (
    3077                                  technically_abnormal = self._CHBOX_abnormal.GetValue(),
    3078                                  clinically_relevant = self._CHBOX_relevant.GetValue(),
    3079                                  comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
    3080                                  make_me_responsible = False
    3081                          )
    3082
    3083                  self.data = tr
    3084
    3085  #               wx.CallAfter (
    3086  #                       plot_adjacent_measurements,
    3087  #                       test = self.data,
    3088  #                       plot_singular_result = False,
    3089  #                       use_default_template = True
    3090  #               )
    3091
    3092                  return True
    3093          #--------------------------------------------------------
    3094 -        def _save_as_update(self):
    3095
    3096                  success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
    3097                  if success:
    3098                          v_num = result
    3099                          v_al = None
    3100                  else:
    3101                          v_num = None
    3102                          v_al = self._TCTRL_result.GetValue().strip()
    3103
    3104                  pk_type = self._PRW_test.GetData()
    3105                  if pk_type is None:
    3106                          abbrev = self._PRW_test.GetValue().strip()
    3107                          name = self._PRW_test.GetValue().strip()
    3108                          unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
    3109                          lab = manage_measurement_orgs (
    3110                                  parent = self,
    3111                                  msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
    3112                          )
    3113                          if lab is not None:
    3114                                  lab = lab['pk_test_org']
    3115                          tt = gmPathLab.create_measurement_type (
    3116                                  lab = None,
    3117                                  abbrev = abbrev,
    3118                                  name = name,
    3119                                  unit = unit
    3120                          )
    3121                          pk_type = tt['pk_test_type']
    3122
    3123                  tr = self.data
    3124
    3125                  tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False)
    3126                  tr['pk_test_type'] = pk_type
    3127                  tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData()
    3128                  tr['val_num'] = v_num
    3129                  tr['val_alpha'] = v_al
    3130                  tr['val_unit'] = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
    3131                  tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
    3132
    3133                  ctrls = [
    3134                          ('abnormality_indicator', self._PRW_abnormality_indicator),
    3135                          ('note_test_org', self._TCTRL_note_test_org),
    3136                          ('comment', self._TCTRL_narrative),
    3137                          ('val_normal_range', self._TCTRL_normal_range),
    3138                          ('val_target_range', self._TCTRL_target_range),
    3139                          ('norm_ref_group', self._TCTRL_norm_ref_group)
    3140                  ]
    3141                  for field, widget in ctrls:
    3142                          tr[field] = widget.GetValue().strip()
    3143
    3144                  ctrls = [
    3145                          ('val_normal_min', self._TCTRL_normal_min),
    3146                          ('val_normal_max', self._TCTRL_normal_max),
    3147                          ('val_target_min', self._TCTRL_target_min),
    3148                          ('val_target_max', self._TCTRL_target_max)
    3149                  ]
    3150                  for field, widget in ctrls:
    3151                          val = widget.GetValue().strip()
    3152                          if val == '':
    3153                                  tr[field] = None
    3154                          else:
    3155                                  tr[field] = decimal.Decimal(val.replace(',', '.', 1))
    3156
    3157                  tr.save_payload()
    3158
    3159                  if self._CHBOX_review.GetValue() is True:
    3160                          tr.set_review (
    3161                                  technically_abnormal = self._CHBOX_abnormal.GetValue(),
    3162                                  clinically_relevant = self._CHBOX_relevant.GetValue(),
    3163                                  comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
    3164                                  make_me_responsible = False
    3165                          )
    3166
    3167  #               wx.CallAfter (
    3168  #                       plot_adjacent_measurements,
    3169  #                       test = self.data,
    3170  #                       plot_singular_result = False,
    3171  #                       use_default_template = True
    3172  #               )
    3173
    3174                  return True
    3175          #--------------------------------------------------------
    3176          # event handling
    3177          #--------------------------------------------------------
    3178 -        def __register_interests(self):
    3179                  self._PRW_test.add_callback_on_lose_focus(self._on_leave_test_prw)
    3180                  self._PRW_abnormality_indicator.add_callback_on_lose_focus(self._on_leave_indicator_prw)
    3181                  self._PRW_units.add_callback_on_lose_focus(self._on_leave_unit_prw)
    3182          #--------------------------------------------------------
    3183 -        def _on_leave_test_prw(self):
    3184                  self.__refresh_loinc_info()
    3185                  self.__refresh_previous_value()
    3186                  self.__update_units_context()
    3187                  # only works if we've got a unit set
    3188                  self.__update_normal_range()
    3189                  self.__update_clinical_range()
    3190          #--------------------------------------------------------
    3191 -        def _on_leave_unit_prw(self):
    3192                  # maybe we've got a unit now ?
    3193                  self.__update_normal_range()
    3194                  self.__update_clinical_range()
    3195          #--------------------------------------------------------
    3196 -        def _on_leave_indicator_prw(self):
    3197                  # if the user hasn't explicitly enabled reviewing
    3198                  if not self._CHBOX_review.GetValue():
    3199                          self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != '')
    3200          #--------------------------------------------------------
    3201 -        def _on_review_box_checked(self, evt):
    3202                  self._CHBOX_abnormal.Enable(self._CHBOX_review.GetValue())
    3203                  self._CHBOX_relevant.Enable(self._CHBOX_review.GetValue())
    3204                  self._TCTRL_review_comment.Enable(self._CHBOX_review.GetValue())
    3205          #--------------------------------------------------------
    3206 -        def _on_test_info_button_pressed(self, event):
    3207                  pk = self._PRW_test.GetData()
    3208                  if pk is not None:
    3209                          tt = gmPathLab.cMeasurementType(aPK_obj = pk)
    3210                          search_term = '%s %s %s' % (
    3211                                  tt['name'],
    3212                                  tt['abbrev'],
    3213                                  gmTools.coalesce(tt['loinc'], '')
    3214                          )
    3215                  else:
    3216                          search_term = self._PRW_test.GetValue()
    3217
    3218                  search_term = search_term.replace(' ', '+')
    3219
    3220                  call_browser_on_measurement_type(measurement_type = search_term)
    3221          #--------------------------------------------------------
    3222 -        def _on_manage_episodes_button_pressed(self, event):
    3223                  event.Skip()
    3224                  gmEMRStructWidgets.manage_episodes(parent = self)
    3225          #--------------------------------------------------------
    3226          # internal helpers
    3227          #--------------------------------------------------------
    3228 -        def __update_units_context(self):
    3229
    3230                  if self._PRW_test.GetData() is None:
    3231                          self._PRW_units.unset_context(context = 'pk_type')
    3232                          self._PRW_units.unset_context(context = 'loinc')
    3233                          if self._PRW_test.GetValue().strip() == '':
    3234                                  self._PRW_units.unset_context(context = 'test_name')
    3235                          else:
    3236                                  self._PRW_units.set_context(context = 'test_name', val = self._PRW_test.GetValue().strip())
    3237                          return
    3238
    3239                  tt = self._PRW_test.GetData(as_instance = True)
    3240
    3241                  self._PRW_units.set_context(context = 'pk_type', val = tt['pk_test_type'])
    3242                  self._PRW_units.set_context(context = 'test_name', val = tt['name'])
    3243
    3244                  if tt['loinc'] is not None:
    3245                          self._PRW_units.set_context(context = 'loinc', val = tt['loinc'])
    3246
    3247                  # closest unit
    3248                  if self._PRW_units.GetValue().strip() == '':
    3249                          clin_when = self._DPRW_evaluated.GetData()
    3250                          if clin_when is None:
    3251                                  unit = tt.temporally_closest_unit
    3252                          else:
    3253                                  clin_when = clin_when.get_pydt()
    3254                                  unit = tt.get_temporally_closest_unit(timestamp = clin_when)
    3255                          if unit is None:
    3256                                  self._PRW_units.SetText('', unit, True)
    3257                          else:
    3258                                  self._PRW_units.SetText(unit, unit, True)
    3259
    3260          #--------------------------------------------------------
    3261 -        def __update_normal_range(self):
    3262                  unit = self._PRW_units.GetValue().strip()
    3263                  if unit == '':
    3264                          return
    3265                  if self._PRW_test.GetData() is None:
    3266                          return
    3267                  for ctrl in [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_normal_range, self._TCTRL_norm_ref_group]:
    3268                          if ctrl.GetValue().strip() != '':
    3269                                  return
    3270                  tt = self._PRW_test.GetData(as_instance = True)
    3271                  test_w_range = tt.get_temporally_closest_normal_range (
    3272                          unit,
    3273                          timestamp = self._DPRW_evaluated.GetData().get_pydt()
    3274                  )
    3275                  if test_w_range is None:
    3276                          return
    3277                  self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(test_w_range['val_normal_min'], '')))
    3278                  self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(test_w_range['val_normal_max'], '')))
    3279                  self._TCTRL_normal_range.SetValue(gmTools.coalesce(test_w_range['val_normal_range'], ''))
    3280                  self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(test_w_range['norm_ref_group'], ''))
    3281
    3282          #--------------------------------------------------------
    3283 -        def __update_clinical_range(self):
    3284                  unit = self._PRW_units.GetValue().strip()
    3285                  if unit == '':
    3286                          return
    3287                  if self._PRW_test.GetData() is None:
    3288                          return
    3289                  for ctrl in [self._TCTRL_target_min, self._TCTRL_target_max, self._TCTRL_target_range]:
    3290                          if ctrl.GetValue().strip() != '':
    3291                                  return
    3292                  tt = self._PRW_test.GetData(as_instance = True)
    3293                  test_w_range = tt.get_temporally_closest_target_range (
    3294                          unit,
    3295                          gmPerson.gmCurrentPatient().ID,
    3296                          timestamp = self._DPRW_evaluated.GetData().get_pydt()
    3297                  )
    3298                  if test_w_range is None:
    3299                          return
    3300                  self._TCTRL_target_min.SetValue(str(gmTools.coalesce(test_w_range['val_target_min'], '')))
    3301                  self._TCTRL_target_max.SetValue(str(gmTools.coalesce(test_w_range['val_target_max'], '')))
    3302                  self._TCTRL_target_range.SetValue(gmTools.coalesce(test_w_range['val_target_range'], ''))
    3303
    3304          #--------------------------------------------------------
    3305 -        def __refresh_loinc_info(self):
    3306
    3307                  self._TCTRL_loinc.SetValue('')
    3308
    3309                  if self._PRW_test.GetData() is None:
    3310                          return
    3311
    3312                  tt = self._PRW_test.GetData(as_instance = True)
    3313
    3314                  if tt['loinc'] is None:
    3315                          return
    3316
    3317                  info = gmLOINC.loinc2term(loinc = tt['loinc'])
    3318                  if len(info) == 0:
    3319                          self._TCTRL_loinc.SetValue('')
    3320                          return
    3321
    3322                  self._TCTRL_loinc.SetValue('%s: %s' % (tt['loinc'], info[0]))
    3323          #--------------------------------------------------------
    3324 -        def __refresh_previous_value(self):
    3325                  self._TCTRL_previous_value.SetValue('')
    3326                  # it doesn't make much sense to show the most
    3327                  # recent value when editing an existing one
    3328                  if self.data is not None:
    3329                          return
    3330                  if self._PRW_test.GetData() is None:
    3331                          return
    3332                  tt = self._PRW_test.GetData(as_instance = True)
    3333                  most_recent = tt.get_most_recent_results (
    3334                          no_of_results = 1,
    3335                          patient = gmPerson.gmCurrentPatient().ID
    3336                  )
    3337                  if most_recent is None:
    3338                          return
    3339                  self._TCTRL_previous_value.SetValue(_('%s ago: %s%s%s - %s%s') % (
    3340                          gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - most_recent['clin_when']),
    3341                          most_recent['unified_val'],
    3342                          most_recent['val_unit'],
    3343                          gmTools.coalesce(most_recent['abnormality_indicator'], '', ' (%s)'),
    3344                          most_recent['abbrev_tt'],
    3345                          gmTools.coalesce(most_recent.formatted_range, '', ' [%s]')
    3346                  ))
    3347                  self._TCTRL_previous_value.SetToolTip(most_recent.format (
    3348                          with_review = True,
    3349                          with_evaluation = False,
    3350                          with_ranges = True,
    3351                          with_episode = True,
    3352                          with_type_details=True
    3353                  ))
    3354
    3355  #================================================================
    3356  # measurement type handling
    3357  #================================================================
    3358 -def pick_measurement_types(parent=None, msg=None, right_column=None, picks=None):
    3359
    3360          if parent is None:
    3361                  parent = wx.GetApp().GetTopWindow()
    3362
    3363          if msg is None:
    3364                  msg = _('Pick the relevant measurement types.')
    3365
    3366          if right_column is None:
    3367                  right_columns = [_('Picked')]
    3368          else:
    3369                  right_columns = [right_column]
    3370
    3371          picker = gmListWidgets.cItemPickerDlg(parent, -1, msg = msg)
    3372          picker.set_columns(columns = [_('Known measurement types')], columns_right = right_columns)
    3373          types = gmPathLab.get_measurement_types(order_by = 'unified_abbrev')
    3374          picker.set_choices (
    3375                  choices = [
    3376                          '%s: %s%s' % (
    3377                                  t['unified_abbrev'],
    3378                                  t['unified_name'],
    3379                                  gmTools.coalesce(t['name_org'], '', ' (%s)')
    3380                          )
    3381                          for t in types
    3382                  ],
    3383                  data = types
    3384          )
    3385          if picks is not None:
    3386                  picker.set_picks (
    3387                          picks = [
    3388                                  '%s: %s%s' % (
    3389                                          p['unified_abbrev'],
    3390                                          p['unified_name'],
    3391                                          gmTools.coalesce(p['name_org'], '', ' (%s)')
    3392                                  )
    3393                                  for p in picks
    3394                          ],
    3395                          data = picks
    3396                  )
    3397          result = picker.ShowModal()
    3398
    3399          if result == wx.ID_CANCEL:
    3400                  picker.DestroyLater()
    3401                  return None
    3402
    3403          picks = picker.picks
    3404          picker.DestroyLater()
    3405          return picks
    3406
    3407  #----------------------------------------------------------------
    3408 -def manage_measurement_types(parent=None):
    3409
    3410          if parent is None:
    3411                  parent = wx.GetApp().GetTopWindow()
    3412
    3413          #------------------------------------------------------------
    3414          def edit(test_type=None):
    3415                  ea = cMeasurementTypeEAPnl(parent, -1, type = test_type)
    3416                  dlg = gmEditArea.cGenericEditAreaDlg2 (
    3417                          parent = parent,
    3418                          id = -1,
    3419                          edit_area = ea,
    3420                          single_entry = gmTools.bool2subst((test_type is None), False, True)
    3421                  )
    3422                  dlg.SetTitle(gmTools.coalesce(test_type, _('Adding measurement type'), _('Editing measurement type')))
    3423
    3424                  if dlg.ShowModal() == wx.ID_OK:
    3425                          dlg.DestroyLater()
    3426                          return True
    3427
    3428                  dlg.DestroyLater()
    3429                  return False
    3430
    3431          #------------------------------------------------------------
    3432          def delete(measurement_type):
    3433                  if measurement_type.in_use:
    3434                          gmDispatcher.send (
    3435                                  signal = 'statustext',
    3436                                  beep = True,
    3437                                  msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev'])
    3438                          )
    3439                          return False
    3440                  gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type'])
    3441                  return True
    3442
    3443          #------------------------------------------------------------
    3444          def get_tooltip(test_type):
    3445                  return test_type.format()
    3446
    3447          #------------------------------------------------------------
    3448          def manage_aggregates(test_type):
    3449                  manage_meta_test_types(parent = parent)
    3450                  return False
    3451
    3452          #------------------------------------------------------------
    3453          def manage_panels_of_type(test_type):
    3454                  if test_type['loinc'] is None:
    3455                          return False
    3456                  all_panels = gmPathLab.get_test_panels(order_by = 'description')
    3457                  curr_panels = test_type.test_panels
    3458                  if curr_panels is None:
    3459                          curr_panels = []
    3460                  panel_candidates = [ p for p in all_panels if p['pk_test_panel'] not in [
    3461                          c_pnl['pk_test_panel'] for c_pnl in curr_panels
    3462                  ] ]
    3463                  picker = gmListWidgets.cItemPickerDlg(parent, -1, title = 'Panels with [%s]' % test_type['abbrev'])
    3464                  picker.set_columns(['Panels available'], ['Panels [%s] is to be on' % test_type['abbrev']])
    3465                  picker.set_choices (
    3466                          choices = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in panel_candidates ],
    3467                          data = panel_candidates
    3468                  )
    3469                  picker.set_picks (
    3470                          picks = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in curr_panels ],
    3471                          data = curr_panels
    3472                  )
    3473                  exit_type = picker.ShowModal()
    3474                  if exit_type == wx.ID_CANCEL:
    3475                          return False
    3476
    3477                  # add picked panels which aren't currently in the panel list
    3478                  panels2add = [ p for p in picker.picks if p['pk_test_panel'] not in [
    3479                          c_pnl['pk_test_panel'] for c_pnl in curr_panels
    3480                  ] ]
    3481                  # remove unpicked panels off the current panel list
    3482                  panels2remove = [ p for p in curr_panels if p['pk_test_panel'] not in [
    3483                          picked_pnl['pk_test_panel'] for picked_pnl in picker.picks
    3484                  ] ]
    3485                  for new_panel in panels2add:
    3486                          new_panel.add_loinc(test_type['loinc'])
    3487                  for stale_panel in panels2remove:
    3488                          stale_panel.remove_loinc(test_type['loinc'])
    3489
    3490                  return True
    3491
    3492          #------------------------------------------------------------
    3493          def refresh(lctrl):
    3494                  mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev')
    3495                  items = [ [
    3496                          m['abbrev'],
    3497                          m['name'],
    3498                          gmTools.coalesce(m['reference_unit'], ''),
    3499                          gmTools.coalesce(m['loinc'], ''),
    3500                          gmTools.coalesce(m['comment_type'], ''),
    3501                          gmTools.coalesce(m['name_org'], '?'),
    3502                          gmTools.coalesce(m['comment_org'], ''),
    3503                          m['pk_test_type']
    3504                  ] for m in mtypes ]
    3505                  lctrl.set_string_items(items)
    3506                  lctrl.set_data(mtypes)
    3507
    3508          #------------------------------------------------------------
    3509          gmListWidgets.get_choices_from_list (
    3510                  parent = parent,
    3511                  caption = _('Measurement types.'),
    3512                  columns = [ _('Abbrev'), _('Name'), _('Unit'), _('LOINC'), _('Comment'), _('Org'), _('Comment'), '#' ],
    3513                  single_selection = True,
    3514                  refresh_callback = refresh,
    3515                  edit_callback = edit,
    3516                  new_callback = edit,
    3517                  delete_callback = delete,
    3518                  list_tooltip_callback = get_tooltip,
    3519                  left_extra_button = (_('%s &Aggregate') % gmTools.u_sum, _('Manage aggregations (%s) of tests into groups.') % gmTools.u_sum, manage_aggregates),
    3520                  middle_extra_button = (_('Select panels'), _('Select panels the focussed test type is to belong to.'), manage_panels_of_type)
    3521          )
    3522
    3523  #----------------------------------------------------------------
    3524 -class cMeasurementTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
    3525
    3526 -        def __init__(self, *args, **kwargs):
    3527
    3528                  query = """
    3529  SELECT DISTINCT ON (field_label)
    3530          pk_test_type AS data,
    3531          name
    3532                  || ' ('
    3533                  || coalesce (
    3534                          (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
    3535                          '%(in_house)s'
    3536                          )
    3537                  || ')'
    3538          AS field_label,
    3539          name
    3540                  || ' ('
    3541                  || abbrev || ', '
    3542                  || coalesce(abbrev_meta || ': ' || name_meta || ', ', '')
    3543                  || coalesce (
    3544                          (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
    3545                          '%(in_house)s'
    3546                          )
    3547                  || ')'
    3548          AS list_label
    3549  FROM
    3550          clin.v_test_types c_vtt
    3551  WHERE
    3552          abbrev_meta %%(fragment_condition)s
    3553                  OR
    3554          name_meta %%(fragment_condition)s
    3555                  OR
    3556          abbrev %%(fragment_condition)s
    3557                  OR
    3558          name %%(fragment_condition)s
    3559  ORDER BY field_label
    3560  LIMIT 50""" % {'in_house': _('generic / in house lab')}
    3561
    3562                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    3563                  mp.setThresholds(1, 2, 4)
    3564                  mp.word_separators = '[ \t:@]+'
    3565                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    3566                  self.matcher = mp
    3567                  self.SetToolTip(_('Select the type of measurement.'))
    3568                  self.selection_only = False
    3569
    3570          #------------------------------------------------------------
    3571 -        def _data2instance(self):
    3572                  if self.GetData() is None:
    3573                          return None
    3574
    3575                  return gmPathLab.cMeasurementType(aPK_obj = self.GetData())
    3576
    3577  #----------------------------------------------------------------
    3578  from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl
    3579
    3580 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
    3581
    3582 -        def __init__(self, *args, **kwargs):
    3583
    3584                  try:
    3585                          data = kwargs['type']
    3586                          del kwargs['type']
    3587                  except KeyError:
    3588                          data = None
    3589
    3590                  wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl.__init__(self, *args, **kwargs)
    3591                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    3592                  self.mode = 'new'
    3593                  self.data = data
    3594                  if data is not None:
    3595                          self.mode = 'edit'
    3596
    3597                  self.__init_ui()
    3598
    3599          #----------------------------------------------------------------
    3600 -        def __init_ui(self):
    3601
    3602                  # name phraseweel
    3603                  query = """
    3604  select distinct on (name)
    3605          pk,
    3606          name
    3607  from clin.test_type
    3608  where
    3609          name %(fragment_condition)s
    3610  order by name
    3611  limit 50"""
    3612                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    3613                  mp.setThresholds(1, 2, 4)
    3614                  self._PRW_name.matcher = mp
    3615                  self._PRW_name.selection_only = False
    3616                  self._PRW_name.add_callback_on_lose_focus(callback = self._on_name_lost_focus)
    3617
    3618                  # abbreviation
    3619                  query = """
    3620  select distinct on (abbrev)
    3621          pk,
    3622          abbrev
    3623  from clin.test_type
    3624  where
    3625          abbrev %(fragment_condition)s
    3626  order by abbrev
    3627  limit 50"""
    3628                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    3629                  mp.setThresholds(1, 2, 3)
    3630                  self._PRW_abbrev.matcher = mp
    3631                  self._PRW_abbrev.selection_only = False
    3632
    3633                  # unit
    3634                  self._PRW_reference_unit.selection_only = False
    3635
    3636                  # loinc
    3637                  mp = gmLOINC.cLOINCMatchProvider()
    3638                  mp.setThresholds(1, 2, 4)
    3639                  #mp.print_queries = True
    3640                  #mp.word_separators = '[ \t:@]+'
    3641                  self._PRW_loinc.matcher = mp
    3642                  self._PRW_loinc.selection_only = False
    3643                  self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
    3644
    3645          #----------------------------------------------------------------
    3646 -        def _on_name_lost_focus(self):
    3647
    3648                  test = self._PRW_name.GetValue().strip()
    3649
    3650                  if test == '':
    3651                          self._PRW_reference_unit.unset_context(context = 'test_name')
    3652                          return
    3653
    3654                  self._PRW_reference_unit.set_context(context = 'test_name', val = test)
    3655
    3656          #----------------------------------------------------------------
    3657 -        def _on_loinc_lost_focus(self):
    3658                  loinc = self._PRW_loinc.GetData()
    3659
    3660                  if loinc is None:
    3661                          self._TCTRL_loinc_info.SetValue('')
    3662                          self._PRW_reference_unit.unset_context(context = 'loinc')
    3663                          return
    3664
    3665                  self._PRW_reference_unit.set_context(context = 'loinc', val = loinc)
    3666
    3667                  info = gmLOINC.loinc2term(loinc = loinc)
    3668                  if len(info) == 0:
    3669                          self._TCTRL_loinc_info.SetValue('')
    3670                          return
    3671
    3672                  self._TCTRL_loinc_info.SetValue(info[0])
    3673
    3674          #----------------------------------------------------------------
    3675          # generic Edit Area mixin API
    3676          #----------------------------------------------------------------
    3677 -        def _valid_for_save(self):
    3678
    3679                  has_errors = False
    3680                  for field in [self._PRW_name, self._PRW_abbrev, self._PRW_reference_unit]:
    3681                          if field.GetValue().strip() in ['', None]:
    3682                                  has_errors = True
    3683                                  field.display_as_valid(valid = False)
    3684                          else:
    3685                                  field.display_as_valid(valid = True)
    3686                          field.Refresh()
    3687
    3688                  return (not has_errors)
    3689
    3690          #----------------------------------------------------------------
    3691 -        def _save_as_new(self):
    3692
    3693                  pk_org = self._PRW_test_org.GetData()
    3694                  if pk_org is None:
    3695                          pk_org = gmPathLab.create_test_org (
    3696                                  name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), '')
    3697                          )['pk_test_org']
    3698
    3699                  tt = gmPathLab.create_measurement_type (
    3700                          lab = pk_org,
    3701                          abbrev = self._PRW_abbrev.GetValue().strip(),
    3702                          name = self._PRW_name.GetValue().strip(),
    3703                          unit = gmTools.coalesce (
    3704                                  self._PRW_reference_unit.GetData(),
    3705                                  self._PRW_reference_unit.GetValue()
    3706                          ).strip()
    3707                  )
    3708                  if self._PRW_loinc.GetData() is not None:
    3709                          tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), '')
    3710                  else:
    3711                          tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), '')
    3712                  tt['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), '')
    3713                  tt['pk_meta_test_type'] = self._PRW_meta_type.GetData()
    3714
    3715                  tt.save()
    3716
    3717                  self.data = tt
    3718
    3719                  return True
    3720          #----------------------------------------------------------------
    3721 -        def _save_as_update(self):
    3722
    3723                  pk_org = self._PRW_test_org.GetData()
    3724                  if pk_org is None:
    3725                          pk_org = gmPathLab.create_test_org (
    3726                                  name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), '')
    3727                          )['pk_test_org']
    3728
    3729                  self.data['pk_test_org'] = pk_org
    3730                  self.data['abbrev'] = self._PRW_abbrev.GetValue().strip()
    3731                  self.data['name'] = self._PRW_name.GetValue().strip()
    3732                  self.data['reference_unit'] = gmTools.coalesce (
    3733                          self._PRW_reference_unit.GetData(),
    3734                          self._PRW_reference_unit.GetValue()
    3735                  ).strip()
    3736                  old_loinc = self.data['loinc']
    3737                  if self._PRW_loinc.GetData() is not None:
    3738                          self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), '')
    3739                  else:
    3740                          self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), '')
    3741                  new_loinc = self.data['loinc']
    3742                  self.data['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), '')
    3743                  self.data['pk_meta_test_type'] = self._PRW_meta_type.GetData()
    3744                  self.data.save()
    3745
    3746                  # was it, AND can it be, on any panel ?
    3747                  if None not in [old_loinc, new_loinc]:
    3748                          # would it risk being dropped from any panel ?
    3749                          if new_loinc != old_loinc:
    3750                                  for panel in gmPathLab.get_test_panels(loincs = [old_loinc]):
    3751                                          pnl_loincs = panel.included_loincs
    3752                                          if new_loinc not in pnl_loincs:
    3753                                                  pnl_loincs.append(new_loinc)
    3754                                                  panel.included_loincs = pnl_loincs
    3755                                          # do not remove old_loinc as it may sit on another
    3756                                          # test type which we haven't removed it from yet
    3757
    3758                  return True
    3759
    3760          #----------------------------------------------------------------
    3761 -        def _refresh_as_new(self):
    3762                  self._PRW_name.SetText('', None, True)
    3763                  self._on_name_lost_focus()
    3764                  self._PRW_abbrev.SetText('', None, True)
    3765                  self._PRW_reference_unit.SetText('', None, True)
    3766                  self._PRW_loinc.SetText('', None, True)
    3767                  self._on_loinc_lost_focus()
    3768                  self._TCTRL_comment_type.SetValue('')
    3769                  self._PRW_test_org.SetText('', None, True)
    3770                  self._PRW_meta_type.SetText('', None, True)
    3771
    3772                  self._PRW_name.SetFocus()
    3773          #----------------------------------------------------------------
    3774 -        def _refresh_from_existing(self):
    3775                  self._PRW_name.SetText(self.data['name'], self.data['name'], True)
    3776                  self._on_name_lost_focus()
    3777                  self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True)
    3778                  self._PRW_reference_unit.SetText (
    3779                          gmTools.coalesce(self.data['reference_unit'], ''),
    3780                          self.data['reference_unit'],
    3781                          True
    3782                  )
    3783                  self._PRW_loinc.SetText (
    3784                          gmTools.coalesce(self.data['loinc'], ''),
    3785                          self.data['loinc'],
    3786                          True
    3787                  )
    3788                  self._on_loinc_lost_focus()
    3789                  self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], ''))
    3790                  self._PRW_test_org.SetText (
    3791                          gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
    3792                          self.data['pk_test_org'],
    3793                          True
    3794                  )
    3795                  if self.data['pk_meta_test_type'] is None:
    3796                          self._PRW_meta_type.SetText('', None, True)
    3797                  else:
    3798                          self._PRW_meta_type.SetText('%s: %s' % (self.data['abbrev_meta'], self.data['name_meta']), self.data['pk_meta_test_type'], True)
    3799
    3800                  self._PRW_name.SetFocus()
    3801          #----------------------------------------------------------------
    3802 -        def _refresh_as_new_from_existing(self):
    3803                  self._refresh_as_new()
    3804                  self._PRW_test_org.SetText (
    3805                          gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
    3806                          self.data['pk_test_org'],
    3807                          True
    3808                  )
    3809                  self._PRW_name.SetFocus()
    3810
    3811  #================================================================
    3812  _SQL_units_from_test_results = """
    3813          -- via clin.v_test_results.pk_type (for types already used in results)
    3814          SELECT
    3815                  val_unit AS data,
    3816                  val_unit AS field_label,
    3817                  val_unit || ' (' || name_tt || ')' AS list_label,
    3818                  1 AS rank
    3819          FROM
    3820                  clin.v_test_results
    3821          WHERE
    3822                  (
    3823                          val_unit %(fragment_condition)s
    3824                                  OR
    3825                          reference_unit %(fragment_condition)s
    3826                  )
    3827                  %(ctxt_type_pk)s
    3828                  %(ctxt_test_name)s
    3829  """
    3830
    3831  _SQL_units_from_test_types = """
    3832          -- via clin.test_type (for types not yet used in results)
    3833          SELECT
    3834                  reference_unit AS data,
    3835                  reference_unit AS field_label,
    3836                  reference_unit || ' (' || name || ')' AS list_label,
    3837                  2 AS rank
    3838          FROM
    3839                  clin.test_type
    3840          WHERE
    3841                  reference_unit %(fragment_condition)s
    3842                  %(ctxt_ctt)s
    3843  """
    3844
    3845  _SQL_units_from_loinc_ipcc = """
    3846          -- via ref.loinc.ipcc_units
    3847          SELECT
    3848                  ipcc_units AS data,
    3849                  ipcc_units AS field_label,
    3850                  ipcc_units || ' (LOINC.ipcc: ' || term || ')' AS list_label,
    3851                  3 AS rank
    3852          FROM
    3853                  ref.loinc
    3854          WHERE
    3855                  ipcc_units %(fragment_condition)s
    3856                  %(ctxt_loinc)s
    3857                  %(ctxt_loinc_term)s
    3858  """
    3859
    3860  _SQL_units_from_loinc_submitted = """
    3861          -- via ref.loinc.submitted_units
    3862          SELECT
    3863                  submitted_units AS data,
    3864                  submitted_units AS field_label,
    3865                  submitted_units || ' (LOINC.submitted:' || term || ')' AS list_label,
    3866                  3 AS rank
    3867          FROM
    3868                  ref.loinc
    3869          WHERE
    3870                  submitted_units %(fragment_condition)s
    3871                  %(ctxt_loinc)s
    3872                  %(ctxt_loinc_term)s
    3873  """
    3874
    3875  _SQL_units_from_loinc_example = """
    3876          -- via ref.loinc.example_units
    3877          SELECT
    3878                  example_units AS data,
    3879                  example_units AS field_label,
    3880                  example_units || ' (LOINC.example: ' || term || ')' AS list_label,
    3881                  3 AS rank
    3882          FROM
    3883                  ref.loinc
    3884          WHERE
    3885                  example_units %(fragment_condition)s
    3886                  %(ctxt_loinc)s
    3887                  %(ctxt_loinc_term)s
    3888  """
    3889
    3890  _SQL_units_from_substance_doses = """
    3891          -- via ref.v_substance_doses.unit
    3892          SELECT
    3893                  unit AS data,
    3894                  unit AS field_label,
    3895                  unit || ' (' || substance || ')' AS list_label,
    3896                  2 AS rank
    3897          FROM
    3898                  ref.v_substance_doses
    3899          WHERE
    3900                  unit %(fragment_condition)s
    3901                  %(ctxt_substance)s
    3902  """
    3903
    3904  _SQL_units_from_substance_doses2 = """
    3905          -- via ref.v_substance_doses.dose_unit
    3906          SELECT
    3907                  dose_unit AS data,
    3908                  dose_unit AS field_label,
    3909                  dose_unit || ' (' || substance || ')' AS list_label,
    3910                  2 AS rank
    3911          FROM
    3912                  ref.v_substance_doses
    3913          WHERE
    3914                  dose_unit %(fragment_condition)s
    3915                  %(ctxt_substance)s
    3916  """
    3917
    3918  #----------------------------------------------------------------
    3919 -class cUnitPhraseWheel(gmPhraseWheel.cPhraseWheel):
    3920
    3921 -        def __init__(self, *args, **kwargs):
    3922
    3923                  query = """
    3924  SELECT DISTINCT ON (data)
    3925          data,
    3926          field_label,
    3927          list_label
    3928  FROM (
    3929
    3930          SELECT
    3931                  data,
    3932                  field_label,
    3933                  list_label,
    3934                  rank
    3935          FROM (
    3936                  (%s) UNION ALL
    3937                  (%s) UNION ALL
    3938                  (%s) UNION ALL
    3939                  (%s) UNION ALL
    3940                  (%s) UNION ALL
    3941                  (%s) UNION ALL
    3942                  (%s)
    3943          ) AS all_matching_units
    3944          WHERE data IS NOT NULL
    3945          ORDER BY rank, list_label
    3946
    3947  ) AS ranked_matching_units
    3948  LIMIT 50""" % (
    3949                          _SQL_units_from_test_results,
    3950                          _SQL_units_from_test_types,
    3951                          _SQL_units_from_loinc_ipcc,
    3952                          _SQL_units_from_loinc_submitted,
    3953                          _SQL_units_from_loinc_example,
    3954                          _SQL_units_from_substance_doses,
    3955                          _SQL_units_from_substance_doses2
    3956                  )
    3957
    3958                  ctxt = {
    3959                          'ctxt_type_pk': {
    3960                                  'where_part': 'AND pk_test_type = %(pk_type)s',
    3961                                  'placeholder': 'pk_type'
    3962                          },
    3963                          'ctxt_test_name': {
    3964                                  'where_part': 'AND %(test_name)s IN (name_tt, name_meta, abbrev_meta)',
    3965                                  'placeholder': 'test_name'
    3966                          },
    3967                          'ctxt_ctt': {
    3968                                  'where_part': 'AND %(test_name)s IN (name, abbrev)',
    3969                                  'placeholder': 'test_name'
    3970                          },
    3971                          'ctxt_loinc': {
    3972                                  'where_part': 'AND code = %(loinc)s',
    3973                                  'placeholder': 'loinc'
    3974                          },
    3975                          'ctxt_loinc_term': {
    3976                                  'where_part': 'AND term ~* %(test_name)s',
    3977                                  'placeholder': 'test_name'
    3978                          },
    3979                          'ctxt_substance': {
    3980                                  'where_part': 'AND description ~* %(substance)s',
    3981                                  'placeholder': 'substance'
    3982                          }
    3983                  }
    3984
    3985                  mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = ctxt)
    3986                  mp.setThresholds(1, 2, 4)
    3987                  #mp.print_queries = True
    3988                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    3989                  self.matcher = mp
    3990                  self.SetToolTip(_('Select the desired unit for the amount or measurement.'))
    3991                  self.selection_only = False
    3992                  self.phrase_separators = '[;|]+'
    3993
    3994  #================================================================
    3995
    3996  #================================================================
    3997 -class cTestResultIndicatorPhraseWheel(gmPhraseWheel.cPhraseWheel):
    3998
    3999 -        def __init__(self, *args, **kwargs):
    4000
    4001                  query = """
    4002  select distinct abnormality_indicator,
    4003          abnormality_indicator, abnormality_indicator
    4004  from clin.v_test_results
    4005  where
    4006          abnormality_indicator %(fragment_condition)s
    4007  order by abnormality_indicator
    4008  limit 25"""
    4009
    4010                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    4011                  mp.setThresholds(1, 1, 2)
    4012                  mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"'
    4013                  mp.word_separators = '[ \t&:]+'
    4014                  gmPhraseWheel.cPhraseWheel.__init__ (
    4015                          self,
    4016                          *args,
    4017                          **kwargs
    4018                  )
    4019                  self.matcher = mp
    4020                  self.SetToolTip(_('Select an indicator for the level of abnormality.'))
    4021                  self.selection_only = False
    4022
    4023  #================================================================
    4024  # measurement org widgets / functions
    4025  #----------------------------------------------------------------
    4026 -def edit_measurement_org(parent=None, org=None):
    4027          ea = cMeasurementOrgEAPnl(parent, -1)
    4028          ea.data = org
    4029          ea.mode = gmTools.coalesce(org, 'new', 'edit')
    4030          dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea)
    4031          dlg.SetTitle(gmTools.coalesce(org, _('Adding new diagnostic org'), _('Editing diagnostic org')))
    4032          if dlg.ShowModal() == wx.ID_OK:
    4033                  dlg.DestroyLater()
    4034                  return True
    4035          dlg.DestroyLater()
    4036          return False
    4037  #----------------------------------------------------------------
    4038 -def manage_measurement_orgs(parent=None, msg=None):
    4039
    4040          if parent is None:
    4041                  parent = wx.GetApp().GetTopWindow()
    4042
    4043          #------------------------------------------------------------
    4044          def edit(org=None):
    4045                  return edit_measurement_org(parent = parent, org = org)
    4046          #------------------------------------------------------------
    4047          def refresh(lctrl):
    4048                  orgs = gmPathLab.get_test_orgs()
    4049                  lctrl.set_string_items ([
    4050                          (o['unit'], o['organization'], gmTools.coalesce(o['test_org_contact'], ''), gmTools.coalesce(o['comment'], ''), o['pk_test_org'])
    4051                          for o in orgs
    4052                  ])
    4053                  lctrl.set_data(orgs)
    4054          #------------------------------------------------------------
    4055          def delete(test_org):
    4056                  gmPathLab.delete_test_org(test_org = test_org['pk_test_org'])
    4057                  return True
    4058          #------------------------------------------------------------
    4059          if msg is None:
    4060                  msg = _('\nThese are the diagnostic orgs (path labs etc) currently defined in GNUmed.\n\n')
    4061
    4062          return gmListWidgets.get_choices_from_list (
    4063                  parent = parent,
    4064                  msg = msg,
    4065                  caption = _('Showing diagnostic orgs.'),
    4066                  columns = [_('Name'), _('Organization'), _('Contact'), _('Comment'), '#'],
    4067                  single_selection = True,
    4068                  refresh_callback = refresh,
    4069                  edit_callback = edit,
    4070                  new_callback = edit,
    4071                  delete_callback = delete
    4072          )
    4073
    4074  #----------------------------------------------------------------
    4075  from Gnumed.wxGladeWidgets import wxgMeasurementOrgEAPnl
    4076
    4077 -class cMeasurementOrgEAPnl(wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl, gmEditArea.cGenericEditAreaMixin):
    4078
    4079 -        def __init__(self, *args, **kwargs):
    4080
    4081                  try:
    4082                          data = kwargs['org']
    4083                          del kwargs['org']
    4084                  except KeyError:
    4085                          data = None
    4086
    4087                  wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl.__init__(self, *args, **kwargs)
    4088                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    4089
    4090                  self.mode = 'new'
    4091                  self.data = data
    4092                  if data is not None:
    4093                          self.mode = 'edit'
    4094
    4095                  #self.__init_ui()
    4096          #----------------------------------------------------------------
    4097  #       def __init_ui(self):
    4098  #               # adjust phrasewheels etc
    4099          #----------------------------------------------------------------
    4100          # generic Edit Area mixin API
    4101          #----------------------------------------------------------------
    4102 -        def _valid_for_save(self):
    4103                  has_errors = False
    4104                  if self._PRW_org_unit.GetData() is None:
    4105                          if self._PRW_org_unit.GetValue().strip() == '':
    4106                                  has_errors = True
    4107                                  self._PRW_org_unit.display_as_valid(valid = False)
    4108                          else:
    4109                                  self._PRW_org_unit.display_as_valid(valid = True)
    4110                  else:
    4111                          self._PRW_org_unit.display_as_valid(valid = True)
    4112
    4113                  return (not has_errors)
    4114          #----------------------------------------------------------------
    4115 -        def _save_as_new(self):
    4116                  data = gmPathLab.create_test_org (
    4117                          name = self._PRW_org_unit.GetValue().strip(),
    4118                          comment = self._TCTRL_comment.GetValue().strip(),
    4119                          pk_org_unit = self._PRW_org_unit.GetData()
    4120                  )
    4121                  data['test_org_contact'] = self._TCTRL_contact.GetValue().strip()
    4122                  data.save()
    4123                  self.data = data
    4124                  return True
    4125          #----------------------------------------------------------------
    4126 -        def _save_as_update(self):
    4127                  # get or create the org unit
    4128                  name = self._PRW_org_unit.GetValue().strip()
    4129                  org = gmOrganization.org_exists(organization = name)
    4130                  if org is None:
    4131                          org = gmOrganization.create_org (
    4132                                  organization = name,
    4133                                  category = 'Laboratory'
    4134                          )
    4135                  org_unit = gmOrganization.create_org_unit (
    4136                          pk_organization = org['pk_org'],
    4137                          unit = name
    4138                  )
    4139                  # update test_org fields
    4140                  self.data['pk_org_unit'] = org_unit['pk_org_unit']
    4141                  self.data['test_org_contact'] = self._TCTRL_contact.GetValue().strip()
    4142                  self.data['comment'] = self._TCTRL_comment.GetValue().strip()
    4143                  self.data.save()
    4144                  return True
    4145          #----------------------------------------------------------------
    4146 -        def _refresh_as_new(self):
    4147                  self._PRW_org_unit.SetText(value = '', data = None)
    4148                  self._TCTRL_contact.SetValue('')
    4149                  self._TCTRL_comment.SetValue('')
    4150          #----------------------------------------------------------------
    4151 -        def _refresh_from_existing(self):
    4152                  self._PRW_org_unit.SetText(value = self.data['unit'], data = self.data['pk_org_unit'])
    4153                  self._TCTRL_contact.SetValue(gmTools.coalesce(self.data['test_org_contact'], ''))
    4154                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
    4155          #----------------------------------------------------------------
    4156 -        def _refresh_as_new_from_existing(self):
    4157                  self._refresh_as_new()
    4158          #----------------------------------------------------------------
    4159 -        def _on_manage_orgs_button_pressed(self, event):
    4160                  gmOrganizationWidgets.manage_orgs(parent = self)
    4161
    4162  #----------------------------------------------------------------
    4163 -class cMeasurementOrgPhraseWheel(gmPhraseWheel.cPhraseWheel):
    4164
    4165 -        def __init__(self, *args, **kwargs):
    4166
    4167                  query = """
    4168  SELECT DISTINCT ON (list_label)
    4169          pk_test_org AS data,
    4170          unit || ' (' || organization || ')' AS field_label,
    4171          unit || ' @ ' || organization AS list_label
    4172  FROM clin.v_test_orgs
    4173  WHERE
    4174          unit %(fragment_condition)s
    4175                  OR
    4176          organization %(fragment_condition)s
    4177  ORDER BY list_label
    4178  LIMIT 50"""
    4179                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    4180                  mp.setThresholds(1, 2, 4)
    4181                  #mp.word_separators = '[ \t:@]+'
    4182                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    4183                  self.matcher = mp
    4184                  self.SetToolTip(_('The name of the path lab/diagnostic organisation.'))
    4185                  self.selection_only = False
    4186          #------------------------------------------------------------
    4187 -        def _create_data(self):
    4188                  if self.GetData() is not None:
    4189                          _log.debug('data already set, not creating')
    4190                          return
    4191
    4192                  if self.GetValue().strip() == '':
    4193                          _log.debug('cannot create new lab, missing name')
    4194                          return
    4195
    4196                  lab = gmPathLab.create_test_org(name = self.GetValue().strip())
    4197                  self.SetText(value = lab['unit'], data = lab['pk_test_org'])
    4198                  return
    4199          #------------------------------------------------------------
    4200 -        def _data2instance(self):
    4201                  return gmPathLab.cTestOrg(aPK_obj = self.GetData())
    4202
    4203  #================================================================
    4204  # Meta test type widgets
    4205  #----------------------------------------------------------------
    4206 -def edit_meta_test_type(parent=None, meta_test_type=None):
    4207          ea = cMetaTestTypeEAPnl(parent, -1)
    4208          ea.data = meta_test_type
    4209          ea.mode = gmTools.coalesce(meta_test_type, 'new', 'edit')
    4210          dlg = gmEditArea.cGenericEditAreaDlg2 (
    4211                  parent = parent,
    4212                  id = -1,
    4213                  edit_area = ea,
    4214                  single_entry = gmTools.bool2subst((meta_test_type is None), False, True)
    4215          )
    4216          dlg.SetTitle(gmTools.coalesce(meta_test_type, _('Adding new meta test type'), _('Editing meta test type')))
    4217          if dlg.ShowModal() == wx.ID_OK:
    4218                  dlg.DestroyLater()
    4219                  return True
    4220          dlg.DestroyLater()
    4221          return False
    4222
    4223  #----------------------------------------------------------------
    4224 -def manage_meta_test_types(parent=None):
    4225
    4226          if parent is None:
    4227                  parent = wx.GetApp().GetTopWindow()
    4228
    4229          #------------------------------------------------------------
    4230          def edit(meta_test_type=None):
    4231                  return edit_meta_test_type(parent = parent, meta_test_type = meta_test_type)
    4232          #------------------------------------------------------------
    4233          def delete(meta_test_type):
    4234                  gmPathLab.delete_meta_type(meta_type = meta_test_type['pk'])
    4235                  return True
    4236          #----------------------------------------
    4237          def get_tooltip(data):
    4238                  if data is None:
    4239                          return None
    4240                  return data.format(with_tests = True)
    4241          #------------------------------------------------------------
    4242          def refresh(lctrl):
    4243                  mtts = gmPathLab.get_meta_test_types()
    4244                  items = [ [
    4245                          m['abbrev'],
    4246                          m['name'],
    4247                          gmTools.coalesce(m['loinc'], ''),
    4248                          gmTools.coalesce(m['comment'], ''),
    4249                          m['pk']
    4250                  ] for m in mtts ]
    4251                  lctrl.set_string_items(items)
    4252                  lctrl.set_data(mtts)
    4253          #----------------------------------------
    4254
    4255          msg = _(
    4256                  '\n'
    4257                  'These are the meta test types currently defined in GNUmed.\n'
    4258                  '\n'
    4259                  'Meta test types allow you to aggregate several actual test types used\n'
    4260                  'by pathology labs into one logical type.\n'
    4261                  '\n'
    4262                  'This is useful for grouping together results of tests which come under\n'
    4263                  'different names but really are the same thing. This often happens when\n'
    4264                  'you switch labs or the lab starts using another test method.\n'
    4265          )
    4266
    4267          gmListWidgets.get_choices_from_list (
    4268                  parent = parent,
    4269                  msg = msg,
    4270                  caption = _('Showing meta test types.'),
    4271                  columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), '#'],
    4272                  single_selection = True,
    4273                  list_tooltip_callback = get_tooltip,
    4274                  edit_callback = edit,
    4275                  new_callback = edit,
    4276                  delete_callback = delete,
    4277                  refresh_callback = refresh
    4278          )
    4279
    4280  #----------------------------------------------------------------
    4281 -class cMetaTestTypePRW(gmPhraseWheel.cPhraseWheel):
    4282
    4283 -        def __init__(self, *args, **kwargs):
    4284
    4285                  query = """
    4286  SELECT DISTINCT ON (field_label)
    4287          c_mtt.pk
    4288                  AS data,
    4289          c_mtt.abbrev || ': ' || name
    4290                  AS field_label,
    4291          c_mtt.abbrev || ': ' || name
    4292                  ||      coalesce (
    4293                                  ' (' || c_mtt.comment || ')',
    4294                                  ''
    4295                          )
    4296                  ||      coalesce (
    4297                                  ', LOINC: ' || c_mtt.loinc,
    4298                                  ''
    4299                          )
    4300          AS list_label
    4301  FROM
    4302          clin.meta_test_type c_mtt
    4303  WHERE
    4304          abbrev %(fragment_condition)s
    4305                  OR
    4306          name %(fragment_condition)s
    4307                  OR
    4308          loinc %(fragment_condition)s
    4309  ORDER BY field_label
    4310  LIMIT 50"""
    4311
    4312                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    4313                  mp.setThresholds(1, 2, 4)
    4314                  mp.word_separators = '[ \t:@]+'
    4315                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    4316                  self.matcher = mp
    4317                  self.SetToolTip(_('Select the meta test type.'))
    4318                  self.selection_only = True
    4319          #------------------------------------------------------------
    4320 -        def _data2instance(self):
    4321                  if self.GetData() is None:
    4322                          return None
    4323
    4324                  return gmPathLab.cMetaTestType(aPK_obj = self.GetData())
    4325
    4326  #----------------------------------------------------------------
    4327  from Gnumed.wxGladeWidgets import wxgMetaTestTypeEAPnl
    4328
    4329 -class cMetaTestTypeEAPnl(wxgMetaTestTypeEAPnl.wxgMetaTestTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
    4330
    4331 -        def __init__(self, *args, **kwargs):
    4332
    4333                  try:
    4334                          data = kwargs['meta_test_type']
    4335                          del kwargs['meta_test_type']
    4336                  except KeyError:
    4337                          data = None
    4338
    4339                  wxgMetaTestTypeEAPnl.wxgMetaTestTypeEAPnl.__init__(self, *args, **kwargs)
    4340                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    4341
    4342                  # Code using this mixin should set mode and data
    4343                  # after instantiating the class:
    4344                  self.mode = 'new'
    4345                  self.data = data
    4346                  if data is not None:
    4347                          self.mode = 'edit'
    4348
    4349                  self.__init_ui()
    4350          #----------------------------------------------------------------
    4351 -        def __init_ui(self):
    4352                  # loinc
    4353                  mp = gmLOINC.cLOINCMatchProvider()
    4354                  mp.setThresholds(1, 2, 4)
    4355                  #mp.print_queries = True
    4356                  #mp.word_separators = '[ \t:@]+'
    4357                  self._PRW_loinc.matcher = mp
    4358                  self._PRW_loinc.selection_only = False
    4359                  self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
    4360
    4361          #----------------------------------------------------------------
    4362          # generic Edit Area mixin API
    4363          #----------------------------------------------------------------
    4364 -        def _valid_for_save(self):
    4365
    4366                  validity = True
    4367
    4368                  if self._PRW_abbreviation.GetValue().strip() == '':
    4369                          validity = False
    4370                          self._PRW_abbreviation.display_as_valid(False)
    4371                          self.StatusText = _('Missing abbreviation for meta test type.')
    4372                          self._PRW_abbreviation.SetFocus()
    4373                  else:
    4374                          self._PRW_abbreviation.display_as_valid(True)
    4375
    4376                  if self._PRW_name.GetValue().strip() == '':
    4377                          validity = False
    4378                          self._PRW_name.display_as_valid(False)
    4379                          self.StatusText = _('Missing name for meta test type.')
    4380                          self._PRW_name.SetFocus()
    4381                  else:
    4382                          self._PRW_name.display_as_valid(True)
    4383
    4384                  return validity
    4385          #----------------------------------------------------------------
    4386 -        def _save_as_new(self):
    4387
    4388                  # save the data as a new instance
    4389                  data = gmPathLab.create_meta_type (
    4390                          name = self._PRW_name.GetValue().strip(),
    4391                          abbreviation = self._PRW_abbreviation.GetValue().strip(),
    4392                          return_existing = False
    4393                  )
    4394                  if data is None:
    4395                          self.StatusText = _('This meta test type already exists.')
    4396                          return False
    4397                  data['loinc'] = self._PRW_loinc.GetData()
    4398                  data['comment'] = self._TCTRL_comment.GetValue().strip()
    4399                  data.save()
    4400                  self.data = data
    4401                  return True
    4402          #----------------------------------------------------------------
    4403 -        def _save_as_update(self):
    4404                  self.data['name'] = self._PRW_name.GetValue().strip()
    4405                  self.data['abbrev'] = self._PRW_abbreviation.GetValue().strip()
    4406                  self.data['loinc'] = self._PRW_loinc.GetData()
    4407                  self.data['comment'] = self._TCTRL_comment.GetValue().strip()
    4408                  self.data.save()
    4409                  return True
    4410          #----------------------------------------------------------------
    4411 -        def _refresh_as_new(self):
    4412                  self._PRW_name.SetText('', None)
    4413                  self._PRW_abbreviation.SetText('', None)
    4414                  self._PRW_loinc.SetText('', None)
    4415                  self._TCTRL_loinc_info.SetValue('')
    4416                  self._TCTRL_comment.SetValue('')
    4417                  self._LBL_member_detail.SetLabel('')
    4418
    4419                  self._PRW_name.SetFocus()
    4420          #----------------------------------------------------------------
    4421 -        def _refresh_as_new_from_existing(self):
    4422                  self._refresh_as_new()
    4423          #----------------------------------------------------------------
    4424 -        def _refresh_from_existing(self):
    4425                  self._PRW_name.SetText(self.data['name'], self.data['pk'])
    4426                  self._PRW_abbreviation.SetText(self.data['abbrev'], self.data['abbrev'])
    4427                  self._PRW_loinc.SetText(gmTools.coalesce(self.data['loinc'], ''), self.data['loinc'])
    4428                  self.__refresh_loinc_info()
    4429                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
    4430                  self.__refresh_members()
    4431
    4432                  self._PRW_name.SetFocus()
    4433          #----------------------------------------------------------------
    4434          # event handlers
    4435          #----------------------------------------------------------------
    4436 -        def _on_loinc_lost_focus(self):
    4437                  self.__refresh_loinc_info()
    4438          #----------------------------------------------------------------
    4439          # internal helpers
    4440          #----------------------------------------------------------------
    4441 -        def __refresh_loinc_info(self):
    4442                  loinc = self._PRW_loinc.GetData()
    4443
    4444                  if loinc is None:
    4445                          self._TCTRL_loinc_info.SetValue('')
    4446                          return
    4447
    4448                  info = gmLOINC.loinc2term(loinc = loinc)
    4449                  if len(info) == 0:
    4450                          self._TCTRL_loinc_info.SetValue('')
    4451                          return
    4452
    4453                  self._TCTRL_loinc_info.SetValue(info[0])
    4454          #----------------------------------------------------------------
    4455 -        def __refresh_members(self):
    4456                  if self.data is None:
    4457                          self._LBL_member_detail.SetLabel('')
    4458                          return
    4459
    4460                  types = self.data.included_test_types
    4461                  if len(types) == 0:
    4462                          self._LBL_member_detail.SetLabel('')
    4463                          return
    4464
    4465                  lines = []
    4466                  for tt in types:
    4467                          lines.append('%s (%s%s) [#%s] @ %s' % (
    4468                                  tt['name'],
    4469                                  tt['abbrev'],
    4470                                  gmTools.coalesce(tt['loinc'], '', ', LOINC: %s'),
    4471                                  tt['pk_test_type'],
    4472                                  tt['name_org']
    4473                          ))
    4474                  self._LBL_member_detail.SetLabel('\n'.join(lines))
    4475
    4476  #================================================================
    4477  # test panel handling
    4478  #================================================================
    4479 -def edit_test_panel(parent=None, test_panel=None):
    4480          ea = cTestPanelEAPnl(parent, -1)
    4481          ea.data = test_panel
    4482          ea.mode = gmTools.coalesce(test_panel, 'new', 'edit')
    4483          dlg = gmEditArea.cGenericEditAreaDlg2 (
    4484                  parent = parent,
    4485                  id = -1,
    4486                  edit_area = ea,
    4487                  single_entry = gmTools.bool2subst((test_panel is None), False, True)
    4488          )
    4489          dlg.SetTitle(gmTools.coalesce(test_panel, _('Adding new test panel'), _('Editing test panel')))
    4490          if dlg.ShowModal() == wx.ID_OK:
    4491                  dlg.DestroyLater()
    4492                  return True
    4493          dlg.DestroyLater()
    4494          return False
    4495
    4496  #----------------------------------------------------------------
    4497 -def manage_test_panels(parent=None):
    4498
    4499          if parent is None:
    4500                  parent = wx.GetApp().GetTopWindow()
    4501
    4502          #------------------------------------------------------------
    4503          def edit(test_panel=None):
    4504                  return edit_test_panel(parent = parent, test_panel = test_panel)
    4505          #------------------------------------------------------------
    4506          def delete(test_panel):
    4507                  gmPathLab.delete_test_panel(pk = test_panel['pk_test_panel'])
    4508                  return True
    4509          #------------------------------------------------------------
    4510          def get_tooltip(test_panel):
    4511                  return test_panel.format()
    4512          #------------------------------------------------------------
    4513          def refresh(lctrl):
    4514                  panels = gmPathLab.get_test_panels(order_by = 'description')
    4515                  items = [ [
    4516                          p['description'],
    4517                          gmTools.coalesce(p['comment'], ''),
    4518                          p['pk_test_panel']
    4519                  ] for p in panels ]
    4520                  lctrl.set_string_items(items)
    4521                  lctrl.set_data(panels)
    4522          #------------------------------------------------------------
    4523          gmListWidgets.get_choices_from_list (
    4524                  parent = parent,
    4525                  caption = 'GNUmed: ' + _('Test panels list'),
    4526                  columns = [ _('Name'), _('Comment'), '#' ],
    4527                  single_selection = True,
    4528                  refresh_callback = refresh,
    4529                  edit_callback = edit,
    4530                  new_callback = edit,
    4531                  delete_callback = delete,
    4532                  list_tooltip_callback = get_tooltip
    4533          )
    4534
    4535  #----------------------------------------------------------------
    4536 -class cTestPanelPRW(gmPhraseWheel.cPhraseWheel):
    4537
    4538 -        def __init__(self, *args, **kwargs):
    4539                  query = """
    4540  SELECT
    4541          pk_test_panel
    4542                  AS data,
    4543          description
    4544                  AS field_label,
    4545          description
    4546                  AS list_label
    4547  FROM
    4548          clin.v_test_panels
    4549  WHERE
    4550          description %(fragment_condition)s
    4551  ORDER BY field_label
    4552  LIMIT 30"""
    4553                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    4554                  mp.setThresholds(1, 2, 4)
    4555                  #mp.word_separators = '[ \t:@]+'
    4556                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    4557                  self.matcher = mp
    4558                  self.SetToolTip(_('Select a test panel.'))
    4559                  self.selection_only = True
    4560          #------------------------------------------------------------
    4561 -        def _data2instance(self):
    4562                  if self.GetData() is None:
    4563                          return None
    4564                  return gmPathLab.cTestPanel(aPK_obj = self.GetData())
    4565          #------------------------------------------------------------
    4566 -        def _get_data_tooltip(self):
    4567                  if self.GetData() is None:
    4568                          return None
    4569                  return gmPathLab.cTestPanel(aPK_obj = self.GetData()).format()
    4570
    4571  #====================================================================
    4572  from Gnumed.wxGladeWidgets import wxgTestPanelEAPnl
    4573
    4574 -class cTestPanelEAPnl(wxgTestPanelEAPnl.wxgTestPanelEAPnl, gmEditArea.cGenericEditAreaMixin):
    4575
    4576 -        def __init__(self, *args, **kwargs):
    4577
    4578                  try:
    4579                          data = kwargs['panel']
    4580                          del kwargs['panel']
    4581                  except KeyError:
    4582                          data = None
    4583
    4584                  wxgTestPanelEAPnl.wxgTestPanelEAPnl.__init__(self, *args, **kwargs)
    4585                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    4586
    4587                  self.__loincs = None
    4588
    4589                  self.mode = 'new'
    4590                  self.data = data
    4591                  if data is not None:
    4592                          self.mode = 'edit'
    4593
    4594                  self.__init_ui()
    4595
    4596          #----------------------------------------------------------------
    4597 -        def __init_ui(self):
    4598                  self._LCTRL_loincs.set_columns([_('LOINC'), _('Term'), _('Units')])
    4599                  self._LCTRL_loincs.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
    4600                  #self._LCTRL_loincs.set_resize_column(column = 2)
    4601                  self._LCTRL_loincs.delete_callback = self._remove_loincs_from_list
    4602                  self.__refresh_loinc_list()
    4603
    4604                  self._PRW_loinc.final_regex = r'.*'
    4605                  self._PRW_loinc.add_callback_on_selection(callback = self._on_loinc_selected)
    4606
    4607          #----------------------------------------------------------------
    4608 -        def __refresh_loinc_list(self):
    4609                  self._LCTRL_loincs.remove_items_safely()
    4610                  if self.__loincs is None:
    4611                          if self.data is None:
    4612                                  return
    4613                          self.__loincs = self.data['loincs']
    4614
    4615                  items = []
    4616                  for loinc in self.__loincs:
    4617                          loinc_detail = gmLOINC.loinc2data(loinc = loinc)
    4618                          if loinc_detail is None:
    4619                                  # check for test type with this pseudo loinc
    4620                                  ttypes = gmPathLab.get_measurement_types(loincs = [loinc])
    4621                                  if len(ttypes) == 0:
    4622                                          items.append([loinc, _('LOINC not found'), ''])
    4623                                  else:
    4624                                          for tt in ttypes:
    4625                                                  items.append([loinc, _('not a LOINC') + u'; %(name)s @ %(name_org)s [#%(pk_test_type)s]' % tt, ''])
    4626                                  continue
    4627                          items.append ([
    4628                                  loinc,
    4629                                  loinc_detail['term'],
    4630                                  gmTools.coalesce(loinc_detail['example_units'], '', '%s')
    4631                          ])
    4632
    4633                  self._LCTRL_loincs.set_string_items(items)
    4634                  self._LCTRL_loincs.set_column_widths()
    4635
    4636          #----------------------------------------------------------------
    4637          # generic Edit Area mixin API
    4638          #----------------------------------------------------------------
    4639 -        def _valid_for_save(self):
    4640                  validity = True
    4641
    4642                  if self.__loincs is None:
    4643                          if self.data is not None:
    4644                                  self.__loincs = self.data['loincs']
    4645
    4646                  if self.__loincs is None:
    4647                          # not fatal despite panel being useless
    4648                          self.StatusText = _('No LOINC codes selected.')
    4649                          self._PRW_loinc.SetFocus()
    4650
    4651                  if self._TCTRL_description.GetValue().strip() == '':
    4652                          validity = False
    4653                          self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False)
    4654                          self._TCTRL_description.SetFocus()
    4655                  else:
    4656                          self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True)
    4657
    4658                  return validity
    4659
    4660          #----------------------------------------------------------------
    4661 -        def _save_as_new(self):
    4662                  data = gmPathLab.create_test_panel(description = self._TCTRL_description.GetValue().strip())
    4663                  data['comment'] = self._TCTRL_comment.GetValue().strip()
    4664                  data.save()
    4665                  if self.__loincs is not None:
    4666                          data.included_loincs = self.__loincs
    4667                  self.data = data
    4668                  return True
    4669
    4670          #----------------------------------------------------------------
    4671 -        def _save_as_update(self):
    4672                  self.data['description'] = self._TCTRL_description.GetValue().strip()
    4673                  self.data['comment'] = self._TCTRL_comment.GetValue().strip()
    4674                  self.data.save()
    4675                  if self.__loincs is not None:
    4676                          self.data.included_loincs = self.__loincs
    4677                  return True
    4678
    4679          #----------------------------------------------------------------
    4680 -        def _refresh_as_new(self):
    4681                  self._TCTRL_description.SetValue('')
    4682                  self._TCTRL_comment.SetValue('')
    4683                  self._PRW_loinc.SetText('', None)
    4684                  self._LBL_loinc.SetLabel('')
    4685                  self.__loincs = None
    4686                  self.__refresh_loinc_list()
    4687
    4688                  self._TCTRL_description.SetFocus()
    4689
    4690          #----------------------------------------------------------------
    4691 -        def _refresh_as_new_from_existing(self):
    4692                  self._refresh_as_new()
    4693
    4694          #----------------------------------------------------------------
    4695 -        def _refresh_from_existing(self):
    4696                  self._TCTRL_description.SetValue(self.data['description'])
    4697                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
    4698                  self._PRW_loinc.SetText('', None)
    4699                  self._LBL_loinc.SetLabel('')
    4700                  self.__loincs = self.data['loincs']
    4701                  self.__refresh_loinc_list()
    4702
    4703                  self._PRW_loinc.SetFocus()
    4704
    4705          #----------------------------------------------------------------
    4706          # event handlers
    4707          #----------------------------------------------------------------
    4708 -        def _on_loinc_selected(self, loinc):
    4709                  loinc = self._PRW_loinc.GetData()
    4710                  if loinc is None:
    4711                          self._LBL_loinc.SetLabel('')
    4712                          return
    4713                  loinc_detail = gmLOINC.loinc2data(loinc = loinc)
    4714                  if loinc_detail is None:
    4715                          loinc_str = _('no LOINC details found')
    4716                  else:
    4717                          loinc_str = '%s: %s%s' % (
    4718                                  loinc,
    4719                                  loinc_detail['term'],
    4720                                  gmTools.coalesce(loinc_detail['example_units'], '', ' (%s)')
    4721                          )
    4722                  self._LBL_loinc.SetLabel(loinc_str)
    4723
    4724          #----------------------------------------------------------------
    4725 -        def _on_add_loinc_button_pressed(self, event):
    4726                  event.Skip()
    4727
    4728                  loinc = self._PRW_loinc.GetData()
    4729                  if loinc is None:
    4730                          loinc = self._PRW_loinc.GetValue().strip()
    4731                  if loinc.strip() == '':
    4732                          return
    4733
    4734                  if self.__loincs is None:
    4735                          self.__loincs = [loinc]
    4736                  else:
    4737                          if loinc in self.__loincs:
    4738                                  return
    4739                          self.__loincs.append(loinc)
    4740
    4741                  self.__refresh_loinc_list()
    4742                  self._PRW_loinc.SetText('', None)
    4743                  self._LBL_loinc.SetLabel('')
    4744
    4745                  self._PRW_loinc.SetFocus()
    4746
    4747          #----------------------------------------------------------------
    4748 -        def _on_remove_loinc_button_pressed(self, event):
    4749                  event.Skip()
    4750                  self._remove_loincs_from_list()
    4751
    4752          #----------------------------------------------------------------
    4753 -        def _remove_loincs_from_list(self):
    4754                  loincs2remove = self._LCTRL_loincs.selected_item_data
    4755                  if loincs2remove is None:
    4756                          return
    4757                  for loinc in loincs2remove:
    4758                          try:
    4759                                  while True:
    4760                                          self.__loincs.remove(loinc[0])
    4761                          except ValueError:
    4762                                  pass
    4763                  self.__refresh_loinc_list()
    4764
    4765  #================================================================
    4766  # main
    4767  #----------------------------------------------------------------
    4768  if __name__ == '__main__':
    4769
    4770          from Gnumed.pycommon import gmLog2
    4771          from Gnumed.wxpython import gmPatSearchWidgets
    4772
    4773          gmI18N.activate_locale()
    4774          gmI18N.install_domain()
    4775          gmDateTime.init()
    4776
    4777          #------------------------------------------------------------
    4778 -        def test_grid():
    4779                  pat = gmPersonSearch.ask_for_patient()
    4780                  app = wx.PyWidgetTester(size = (500, 300))
    4781                  lab_grid = cMeasurementsGrid(app.frame, -1)
    4782                  lab_grid.patient = pat
    4783                  app.frame.Show()
    4784                  app.MainLoop()
    4785          #------------------------------------------------------------
    4786 -        def test_test_ea_pnl():
    4787                  pat = gmPersonSearch.ask_for_patient()
    4788                  gmPatSearchWidgets.set_active_patient(patient=pat)
    4789                  app = wx.PyWidgetTester(size = (500, 300))
    4790                  ea = cMeasurementEditAreaPnl(app.frame, -1)
    4791                  app.frame.Show()
    4792                  app.MainLoop()
    4793          #------------------------------------------------------------
    4794  #       def test_primary_care_vitals_pnl():
    4795  #               app = wx.PyWidgetTester(size = (500, 300))
    4796  #               pnl = wxgPrimaryCareVitalsInputPnl.wxgPrimaryCareVitalsInputPnl(app.frame, -1)
    4797  #               app.frame.Show()
    4798  #               app.MainLoop()
    4799          #------------------------------------------------------------
    4800          if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
    4801                  #test_grid()
    4802                  test_test_ea_pnl()
    4803                  #test_primary_care_vitals_pnl()
    4804
    4805  #================================================================
    4806