Gnumed.wxpython.gmMeasurementWidgets

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.Destroy()
      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.Destroy()
     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.Destroy()
     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, fields=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 fields is not None:
     352                  ea.set_fields(fields)
     353          if dlg.ShowModal() == wx.ID_OK:
     354                  dlg.Destroy()
     355                  return True
     356          dlg.Destroy()
     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 most recent measurement results, possibly filtered by panel/battery.
    1327
    1328          - operates on a cPatient instance handed to it and NOT on the currently active patient
    1329          """
    1330 -        def __init__(self, *args, **kwargs):
    1331                  wxgMeasurementsAsMostRecentListPnl.wxgMeasurementsAsMostRecentListPnl.__init__(self, *args, **kwargs)
    1332
    1333                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    1334
    1335                  self.__patient = None
    1336
    1337                  self.__init_ui()
    1338                  self.__register_events()
    1339
    1340          #------------------------------------------------------------
    1341          # internal helpers
    1342          #------------------------------------------------------------
    1343 -        def __init_ui(self):
    1344                  self._LCTRL_results.set_columns([_('Test'), _('Result'), _('When'), _('Range')])
    1345                  self._CHBOX_show_missing.Disable()
    1346                  self._PNL_related_documents.lab_reference = None
    1347
    1348          #------------------------------------------------------------
    1349 -        def __register_events(self):
    1350                  gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
    1351
    1352                  self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
    1353                  self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
    1354
    1355                  self._LCTRL_results.select_callback = self._on_result_selected
    1356                  self._LCTRL_results.edit_callback = self._on_edit
    1357
    1358          #------------------------------------------------------------
    1359 -        def __repopulate_ui(self):
    1360
    1361                  self._TCTRL_details.SetValue('')
    1362                  self._PNL_related_documents.lab_reference = None
    1363
    1364                  pnl = self._PRW_panel.GetData(as_instance = True)
    1365                  if pnl is None:
    1366                          results = gmPathLab.get_most_recent_result_for_test_types(pk_patient = self.__patient.ID)
    1367                  else:
    1368                          results = pnl.get_most_recent_results (
    1369                                  pk_patient = self.__patient.ID,
    1370                                  #order_by = ,
    1371                                  group_by_meta_type = True,
    1372                                  include_missing = self._CHBOX_show_missing.IsChecked()
    1373                          )
    1374                  items = []
    1375                  data = []
    1376                  for r in results:
    1377                          if isinstance(r, gmPathLab.cTestResult):
    1378                                  result_type = r['abbrev_tt']
    1379                                  review = gmTools.bool2subst (
    1380                                          r['reviewed'],
    1381                                          '',
    1382                                          ' ' + gmTools.u_writing_hand,
    1383                                          ' ' + gmTools.u_writing_hand
    1384                                  )
    1385                                  result_val = '%s%s%s%s' % (
    1386                                          gmTools.strip_empty_lines(text = r['unified_val'])[0],
    1387                                          gmTools.coalesce(r['val_unit'], '', ' %s'),
    1388                                          gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
    1389                                          review
    1390                                  )
    1391                                  result_when = _('%s ago (%s)') % (
    1392                                          gmDateTime.format_interval_medically(interval = gmDateTime.pydt_now_here() - r['clin_when']),
    1393                                          gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d  %H:%M', accuracy = gmDateTime.acc_minutes)
    1394                                  )
    1395                                  range_info = gmTools.coalesce (
    1396                                          r.formatted_clinical_range,
    1397                                          r.formatted_normal_range
    1398                                  )
    1399                                  tt = r.format(with_source_data = True)
    1400                          else:
    1401                                  result_type = r
    1402                                  result_val = _('missing')
    1403                                  loinc_data = gmLOINC.loinc2data(r)
    1404                                  if loinc_data is None:
    1405                                          result_when = _('LOINC not found')
    1406                                          tt = u''
    1407                                  else:
    1408                                          result_when = loinc_data['term']
    1409                                          tt = gmLOINC.format_loinc(r)
    1410                                  range_info = None
    1411                          items.append([result_type, result_val, result_when, gmTools.coalesce(range_info, '')])
    1412                          data.append({'data': r, 'formatted': tt})
    1413
    1414                  self._LCTRL_results.set_string_items(items)
    1415                  self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
    1416                  self._LCTRL_results.set_data(data)
    1417
    1418                  if len(items) > 0:
    1419                          self._LCTRL_results.Select(idx = 0, on = 1)
    1420                  self._LCTRL_results.SetFocus()
    1421
    1422                  return True
    1423
    1424          #--------------------------------------------------------
    1425 -        def __on_panel_selected(self, panel):
    1426                  if panel is None:
    1427                          self._TCTRL_panel_comment.SetValue('')
    1428                          self._CHBOX_show_missing.Disable()
    1429                  else:
    1430                          pnl = self._PRW_panel.GetData(as_instance = True)
    1431                          self._TCTRL_panel_comment.SetValue(gmTools.coalesce(pnl['comment'], ''))
    1432                  self.__repopulate_ui()
    1433                  self._CHBOX_show_missing.Enable()
    1434
    1435          #--------------------------------------------------------
    1436 -        def __on_panel_selection_modified(self):
    1437                  self._TCTRL_panel_comment.SetValue('')
    1438                  if self._PRW_panel.Value.strip() == u'':
    1439                          self.__repopulate_ui()
    1440                          self._CHBOX_show_missing.Disable()
    1441
    1442          #------------------------------------------------------------
    1443          # event handlers
    1444          #------------------------------------------------------------
    1445 -        def _on_database_signal(self, **kwds):
    1446                  if self.__patient is None:
    1447                          return True
    1448
    1449                  if kwds['pk_identity'] is not None:                             # review table doesn't have pk_identity yet
    1450                          if kwds['pk_identity'] != self.__patient.ID:
    1451                                  return True
    1452
    1453                  if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results', 'clin.test_panel']:
    1454                          return True
    1455
    1456                  self._schedule_data_reget()
    1457                  return True
    1458
    1459          #------------------------------------------------------------
    1460 -        def _on_manage_panels_button_pressed(self, event):
    1461                  manage_test_panels(parent = self)
    1462
    1463          #--------------------------------------------------------
    1464 -        def _on_panel_selected(self, panel):
    1465                  wx.CallAfter(self.__on_panel_selected, panel = panel)
    1466
    1467          #--------------------------------------------------------
    1468 -        def _on_panel_selection_modified(self):
    1469                  wx.CallAfter(self.__on_panel_selection_modified)
    1470
    1471          #------------------------------------------------------------
    1472 -        def _on_result_selected(self, event):
    1473                  event.Skip()
    1474                  item_data = self._LCTRL_results.get_item_data(item_idx = event.Index)
    1475                  self._TCTRL_details.SetValue(item_data['formatted'])
    1476                  if isinstance(item_data['data'], gmPathLab.cTestResult):
    1477                          self._PNL_related_documents.lab_reference = item_data['data']
    1478                  else:
    1479                          self._PNL_related_documents.lab_reference = None
    1480
    1481          #------------------------------------------------------------
    1482 -        def _on_edit(self):
    1483                  item_data = self._LCTRL_results.get_selected_item_data(only_one = True)
    1484                  if item_data is None:
    1485                          return
    1486                  if isinstance(item_data['data'], gmPathLab.cTestResult):
    1487                          if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True):
    1488                                  self.__repopulate_ui()
    1489
    1490          #------------------------------------------------------------
    1491 -        def _on_show_missing_toggled(self, event):
    1492                  event.Skip()
    1493                  # should not happen
    1494                  if self._PRW_panel.GetData(as_instance = False) is None:
    1495                          return
    1496                  self.__repopulate_ui()
    1497
    1498          #------------------------------------------------------------
    1499          # reget mixin API
    1500          #------------------------------------------------------------
    1501 -        def _populate_with_data(self):
    1502                  self.__repopulate_ui()
    1503                  return True
    1504
    1505          #------------------------------------------------------------
    1506          # properties
    1507          #------------------------------------------------------------
    1508 -        def _get_patient(self):
    1509                  return self.__patient
    1510
    1511 -        def _set_patient(self, patient):
    1512                  if (self.__patient is None) and (patient is None):
    1513                          return
    1514                  if (self.__patient is None) or (patient is None):
    1515                          self.__patient = patient
    1516                          self._schedule_data_reget()
    1517                          return
    1518                  if self.__patient.ID == patient.ID:
    1519                          return
    1520                  self.__patient = patient
    1521                  self._schedule_data_reget()
    1522
    1523          patient = property(_get_patient, _set_patient)
    1524
    1525  #================================================================
    1526  from Gnumed.wxGladeWidgets import wxgMeasurementsAsTablePnl
    1527
    1528 -class cMeasurementsAsTablePnl(wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl, gmRegetMixin.cRegetOnPaintMixin):
    1529          """A panel for holding a grid displaying all measurement results.
    1530
    1531          - operates on a cPatient instance handed to it and NOT on the currently active patient
    1532          """
    1533 -        def __init__(self, *args, **kwargs):
    1534                  wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl.__init__(self, *args, **kwargs)
    1535
    1536                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    1537
    1538                  self.__patient = None
    1539
    1540                  self.__init_ui()
    1541                  self.__register_events()
    1542
    1543          #------------------------------------------------------------
    1544          # internal helpers
    1545          #------------------------------------------------------------
    1546 -        def __init_ui(self):
    1547                  self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
    1548
    1549                  item = self.__action_button_popup.Append(-1, _('Review and &sign'))
    1550                  self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
    1551
    1552                  item = self.__action_button_popup.Append(-1, _('Plot'))
    1553                  self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
    1554
    1555                  #item = self.__action_button_popup.Append(-1, _('Export to &file'))
    1556                  #self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item)
    1557                  #self.__action_button_popup.Enable(id = item.Id, enable = False)
    1558
    1559                  #item = self.__action_button_popup.Append(-1, _('Export to &clipboard'))
    1560                  #self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item)
    1561                  #self.__action_button_popup.Enable(id = item.Id, enable = False)
    1562
    1563                  item = self.__action_button_popup.Append(-1, _('&Delete'))
    1564                  self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
    1565
    1566                  # FIXME: create inbox message to staff to phone patient to come in
    1567                  # FIXME: generate and let edit a SOAP narrative and include the values
    1568
    1569                  self._GRID_results_all.show_by_panel = False
    1570
    1571          #------------------------------------------------------------
    1572 -        def __register_events(self):
    1573                  gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
    1574
    1575          #------------------------------------------------------------
    1576 -        def __repopulate_ui(self):
    1577                  self._GRID_results_all.patient = self.__patient
    1578                  #self._GRID_results_battery.Fit()
    1579                  self.Layout()
    1580                  return True
    1581
    1582          #------------------------------------------------------------
    1583 -        def __on_sign_current_selection(self, evt):
    1584                  self._GRID_results_all.sign_current_selection()
    1585
    1586          #------------------------------------------------------------
    1587 -        def __on_plot_current_selection(self, evt):
    1588                  self._GRID_results_all.plot_current_selection()
    1589
    1590          #------------------------------------------------------------
    1591 -        def __on_delete_current_selection(self, evt):
    1592                  self._GRID_results_all.delete_current_selection()
    1593
    1594          #------------------------------------------------------------
    1595          # event handlers
    1596          #------------------------------------------------------------
    1597 -        def _on_database_signal(self, **kwds):
    1598                  if self.__patient is None:
    1599                          return True
    1600
    1601                  if kwds['pk_identity'] is not None:                             # review table doesn't have pk_identity yet
    1602                          if kwds['pk_identity'] != self.__patient.ID:
    1603                                  return True
    1604
    1605                  if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
    1606                          return True
    1607
    1608                  self._schedule_data_reget()
    1609                  return True
    1610
    1611          #--------------------------------------------------------
    1612 -        def _on_add_button_pressed(self, event):
    1613                  edit_measurement(parent = self, measurement = None)
    1614
    1615          #--------------------------------------------------------
    1616 -        def _on_manage_types_button_pressed(self, event):
    1617                  event.Skip()
    1618                  manage_measurement_types(parent = self)
    1619
    1620          #--------------------------------------------------------
    1621 -        def _on_review_button_pressed(self, evt):
    1622                  self.PopupMenu(self.__action_button_popup)
    1623
    1624          #--------------------------------------------------------
    1625 -        def _on_select_button_pressed(self, evt):
    1626                  if self._RBTN_my_unsigned.GetValue() is True:
    1627                          self._GRID_results_all.select_cells(unsigned_only = True, accountables_only = True, keep_preselections = False)
    1628                  elif self._RBTN_all_unsigned.GetValue() is True:
    1629                          self._GRID_results_all.select_cells(unsigned_only = True, accountables_only = False, keep_preselections = False)
    1630
    1631          #------------------------------------------------------------
    1632          # reget mixin API
    1633          #------------------------------------------------------------
    1634 -        def _populate_with_data(self):
    1635                  self.__repopulate_ui()
    1636                  return True
    1637
    1638          #------------------------------------------------------------
    1639          # properties
    1640          #------------------------------------------------------------
    1641 -        def _get_patient(self):
    1642                  return self.__patient
    1643
    1644 -        def _set_patient(self, patient):
    1645                  if (self.__patient is None) and (patient is None):
    1646                          return
    1647                  if (self.__patient is None) or (patient is None):
    1648                          self.__patient = patient
    1649                          self._schedule_data_reget()
    1650                          return
    1651                  if self.__patient.ID == patient.ID:
    1652                          return
    1653                  self.__patient = patient
    1654                  self._schedule_data_reget()
    1655
    1656          patient = property(_get_patient, _set_patient)
    1657
    1658  #================================================================
    1659  # notebook based measurements plugin
    1660  #================================================================
    1661 -class cMeasurementsNb(wx.Notebook, gmPlugin.cPatientChange_PluginMixin):
    1662          """Notebook displaying measurements pages:
    1663
    1664                  - by test battery
    1665                  - by day
    1666                  - by issue/episode
    1667                  - most-recent list, perhaps by panel
    1668                  - full grid
    1669                  - full list
    1670
    1671          Used as a main notebook plugin page.
    1672
    1673          Operates on the active patient.
    1674          """
    1675          #--------------------------------------------------------
    1676 -        def __init__(self, parent, id):
    1677
    1678                  wx.Notebook.__init__ (
    1679                          self,
    1680                          parent = parent,
    1681                          id = id,
    1682                          style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
    1683                          name = self.__class__.__name__
    1684                  )
    1685                  _log.debug('created wx.Notebook: %s with ID %s', self.__class__.__name__, self.Id)
    1686                  gmPlugin.cPatientChange_PluginMixin.__init__(self)
    1687                  self.__patient = gmPerson.gmCurrentPatient()
    1688                  self.__init_ui()
    1689                  self.SetSelection(0)
    1690
    1691          #--------------------------------------------------------
    1692          # patient change plugin API
    1693          #--------------------------------------------------------
    1694 -        def _on_current_patient_unset(self, **kwds):
    1695                  for page_idx in range(self.GetPageCount()):
    1696                          page = self.GetPage(page_idx)
    1697                          page.patient = None
    1698
    1699          #--------------------------------------------------------
    1700 -        def _post_patient_selection(self, **kwds):
    1701                  for page_idx in range(self.GetPageCount()):
    1702                          page = self.GetPage(page_idx)
    1703                          page.patient = self.__patient.patient
    1704
    1705          #--------------------------------------------------------
    1706          # notebook plugin API
    1707          #--------------------------------------------------------
    1708 -        def repopulate_ui(self):
    1709                  if self.__patient.connected:
    1710                          pat = self.__patient.patient
    1711                  else:
    1712                          pat = None
    1713                  for page_idx in range(self.GetPageCount()):
    1714                          page = self.GetPage(page_idx)
    1715                          page.patient = pat
    1716
    1717                  return True
    1718
    1719          #--------------------------------------------------------
    1720          # internal API
    1721          #--------------------------------------------------------
    1722 -        def __init_ui(self):
    1723
    1724                  # by day
    1725                  new_page = cMeasurementsByDayPnl(self, -1)
    1726                  new_page.patient = None
    1727                  self.AddPage (
    1728                          page = new_page,
    1729                          text = _('Days'),
    1730                          select = True
    1731                  )
    1732
    1733                  # by issue
    1734                  new_page = cMeasurementsByIssuePnl(self, -1)
    1735                  new_page.patient = None
    1736                  self.AddPage (
    1737                          page = new_page,
    1738                          text = _('Problems'),
    1739                          select = False
    1740                  )
    1741
    1742                  # by test panel
    1743                  new_page = cMeasurementsByBatteryPnl(self, -1)
    1744                  new_page.patient = None
    1745                  self.AddPage (
    1746                          page = new_page,
    1747                          text = _('Panels'),
    1748                          select = False
    1749                  )
    1750
    1751                  # most-recent, by panel
    1752                  new_page = cMeasurementsAsMostRecentListPnl(self, -1)
    1753                  new_page.patient = None
    1754                  self.AddPage (
    1755                          page = new_page,
    1756                          text = _('Most recent'),
    1757                          select = False
    1758                  )
    1759
    1760                  # full grid
    1761                  new_page = cMeasurementsAsTablePnl(self, -1)
    1762                  new_page.patient = None
    1763                  self.AddPage (
    1764                          page = new_page,
    1765                          text = _('Table'),
    1766                          select = False
    1767                  )
    1768
    1769                  # full list
    1770                  new_page = cMeasurementsAsListPnl(self, -1)
    1771                  new_page.patient = None
    1772                  self.AddPage (
    1773                          page = new_page,
    1774                          text = _('List'),
    1775                          select = False
    1776                  )
    1777
    1778          #--------------------------------------------------------
    1779          # properties
    1780          #--------------------------------------------------------
    1781 -        def _get_patient(self):
    1782                  return self.__patient
    1783
    1784 -        def _set_patient(self, patient):
    1785                  self.__patient = patient
    1786                  if self.__patient.connected:
    1787                          pat = self.__patient.patient
    1788                  else:
    1789                          pat = None
    1790                  for page_idx in range(self.GetPageCount()):
    1791                          page = self.GetPage(page_idx)
    1792                          page.patient = pat
    1793
    1794          patient = property(_get_patient, _set_patient)
    1795
    1796  #================================================================
    1797 -class cMeasurementsGrid(wx.grid.Grid):
    1798          """A grid class for displaying measurement results.
    1799
    1800          - operates on a cPatient instance handed to it
    1801          - does NOT listen to the currently active patient
    1802          - thereby it can display any patient at any time
    1803          """
    1804          # FIXME: sort-by-battery
    1805          # FIXME: filter out empty
    1806          # FIXME: filter by tests of a selected date
    1807          # FIXME: dates DESC/ASC by cfg
    1808          # FIXME: mouse over column header: display date info
    1809 -        def __init__(self, *args, **kwargs):
    1810
    1811                  wx.grid.Grid.__init__(self, *args, **kwargs)
    1812
    1813                  self.__patient = None
    1814                  self.__panel_to_show = None
    1815                  self.__show_by_panel = False
    1816                  self.__cell_data = {}
    1817                  self.__row_label_data = []
    1818                  self.__col_label_data = []
    1819
    1820                  self.__prev_row = None
    1821                  self.__prev_col = None
    1822                  self.__prev_label_row = None
    1823                  self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::'))
    1824
    1825                  self.__init_ui()
    1826                  self.__register_events()
    1827
    1828          #------------------------------------------------------------
    1829          # external API
    1830          #------------------------------------------------------------
    1831 -        def delete_current_selection(self):
    1832                  if not self.IsSelection():
    1833                          gmDispatcher.send(signal = 'statustext', msg = _('No results selected for deletion.'))
    1834                          return True
    1835
    1836                  selected_cells = self.get_selected_cells()
    1837                  if len(selected_cells) > 20:
    1838                          results = None
    1839                          msg = _(
    1840                                  'There are %s results marked for deletion.\n'
    1841                                  '\n'
    1842                                  'Are you sure you want to delete these results ?'
    1843                          ) % len(selected_cells)
    1844                  else:
    1845                          results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
    1846                          txt = '\n'.join([ '%s %s (%s): %s %s%s' % (
    1847                                          r['clin_when'].strftime('%x %H:%M'),
    1848                                          r['unified_abbrev'],
    1849                                          r['unified_name'],
    1850                                          r['unified_val'],
    1851                                          r['val_unit'],
    1852                                          gmTools.coalesce(r['abnormality_indicator'], '', ' (%s)')
    1853                                  ) for r in results
    1854                          ])
    1855                          msg = _(
    1856                                  'The following results are marked for deletion:\n'
    1857                                  '\n'
    1858                                  '%s\n'
    1859                                  '\n'
    1860                                  'Are you sure you want to delete these results ?'
    1861                          ) % txt
    1862
    1863                  dlg = gmGuiHelpers.c2ButtonQuestionDlg (
    1864                          self,
    1865                          -1,
    1866                          caption = _('Deleting test results'),
    1867                          question = msg,
    1868                          button_defs = [
    1869                                  {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False},
    1870                                  {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True}
    1871                          ]
    1872                  )
    1873                  decision = dlg.ShowModal()
    1874
    1875                  if decision == wx.ID_YES:
    1876                          if results is None:
    1877                                  results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
    1878                          for result in results:
    1879                                  gmPathLab.delete_test_result(result)
    1880
    1881          #------------------------------------------------------------
    1882 -        def sign_current_selection(self):
    1883                  if not self.IsSelection():
    1884                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot sign results. No results selected.'))
    1885                          return True
    1886
    1887                  selected_cells = self.get_selected_cells()
    1888                  tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
    1889
    1890                  return review_tests(parent = self, tests = tests)
    1891
    1892          #------------------------------------------------------------
    1893 -        def plot_current_selection(self):
    1894
    1895                  if not self.IsSelection():
    1896                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot plot results. No results selected.'))
    1897                          return True
    1898
    1899                  tests = self.__cells_to_data (
    1900                          cells = self.get_selected_cells(),
    1901                          exclude_multi_cells = False,
    1902                          auto_include_multi_cells = True
    1903                  )
    1904
    1905                  plot_measurements(parent = self, tests = tests)
    1906          #------------------------------------------------------------
    1907 -        def get_selected_cells(self):
    1908
    1909                  sel_block_top_left = self.GetSelectionBlockTopLeft()
    1910                  sel_block_bottom_right = self.GetSelectionBlockBottomRight()
    1911                  sel_cols = self.GetSelectedCols()
    1912                  sel_rows = self.GetSelectedRows()
    1913
    1914                  selected_cells = []
    1915
    1916                  # individually selected cells (ctrl-click)
    1917                  selected_cells += self.GetSelectedCells()
    1918
    1919                  # selected rows
    1920                  selected_cells += list (
    1921                          (row, col)
    1922                                  for row in sel_rows
    1923                                  for col in range(self.GetNumberCols())
    1924                  )
    1925
    1926                  # selected columns
    1927                  selected_cells += list (
    1928                          (row, col)
    1929                                  for row in range(self.GetNumberRows())
    1930                                  for col in sel_cols
    1931                  )
    1932
    1933                  # selection blocks
    1934                  for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()):
    1935                          selected_cells += [
    1936                                  (row, col)
    1937                                          for row in range(top_left[0], bottom_right[0] + 1)
    1938                                          for col in range(top_left[1], bottom_right[1] + 1)
    1939                          ]
    1940
    1941                  return set(selected_cells)
    1942          #------------------------------------------------------------
    1943 -        def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
    1944                  """Select a range of cells according to criteria.
    1945
    1946                  unsigned_only: include only those which are not signed at all yet
    1947                  accountable_only: include only those for which the current user is responsible
    1948                  keep_preselections: broaden (rather than replace) the range of selected cells
    1949
    1950                  Combinations are powerful !
    1951                  """
    1952                  wx.BeginBusyCursor()
    1953                  self.BeginBatch()
    1954
    1955                  if not keep_preselections:
    1956                          self.ClearSelection()
    1957
    1958                  for col_idx in self.__cell_data.keys():
    1959                          for row_idx in self.__cell_data[col_idx].keys():
    1960                                  # loop over results in cell and only include
    1961                                  # those multi-value cells that are not ambiguous
    1962                                  do_not_include = False
    1963                                  for result in self.__cell_data[col_idx][row_idx]:
    1964                                          if unsigned_only:
    1965                                                  if result['reviewed']:
    1966                                                          do_not_include = True
    1967                                                          break
    1968                                          if accountables_only:
    1969                                                  if not result['you_are_responsible']:
    1970                                                          do_not_include = True
    1971                                                          break
    1972                                  if do_not_include:
    1973                                          continue
    1974
    1975                                  self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True)
    1976
    1977                  self.EndBatch()
    1978                  wx.EndBusyCursor()
    1979
    1980          #------------------------------------------------------------
    1981 -        def repopulate_grid(self):
    1982                  self.empty_grid()
    1983                  if self.__patient is None:
    1984                          return
    1985
    1986                  if self.__show_by_panel:
    1987                          if self.__panel_to_show is None:
    1988                                  return
    1989                          tests = self.__panel_to_show.get_test_types_for_results (
    1990                                  self.__patient.ID,
    1991                                  order_by = 'unified_abbrev',
    1992                                  unique_meta_types = True
    1993                          )
    1994                          self.__repopulate_grid (
    1995                                  tests4rows = tests,
    1996                                  test_pks2show = [ tt['pk_test_type'] for tt in self.__panel_to_show['test_types'] ]
    1997                          )
    1998                          return
    1999
    2000                  emr = self.__patient.emr
    2001                  tests = emr.get_test_types_for_results(order_by = 'unified_abbrev', unique_meta_types = True)
    2002                  self.__repopulate_grid(tests4rows = tests)
    2003
    2004          #------------------------------------------------------------
    2005 -        def __repopulate_grid(self, tests4rows=None, test_pks2show=None):
    2006
    2007                  if len(tests4rows) == 0:
    2008                          return
    2009
    2010                  emr = self.__patient.emr
    2011
    2012                  self.__row_label_data = tests4rows
    2013                  row_labels = [ '%s%s' % (
    2014                                  gmTools.bool2subst(test_type['is_fake_meta_type'], '', gmTools.u_sum, ''),
    2015                                  test_type['unified_abbrev']
    2016                          ) for test_type in self.__row_label_data
    2017                  ]
    2018
    2019                  self.__col_label_data = [ d['clin_when_day'] for d in emr.get_dates_for_results (
    2020                          tests = test_pks2show,
    2021                          reverse_chronological = True
    2022                  )]
    2023                  col_labels = [ gmDateTime.pydt_strftime(date, self.__date_format, accuracy = gmDateTime.acc_days) for date in self.__col_label_data ]
    2024
    2025                  results = emr.get_test_results_by_date (
    2026                          tests = test_pks2show,
    2027                          reverse_chronological = True
    2028                  )
    2029
    2030                  self.BeginBatch()
    2031
    2032                  # rows
    2033                  self.AppendRows(numRows = len(row_labels))
    2034                  for row_idx in range(len(row_labels)):
    2035                          self.SetRowLabelValue(row_idx, row_labels[row_idx])
    2036
    2037                  # columns
    2038                  self.AppendCols(numCols = len(col_labels))
    2039                  for col_idx in range(len(col_labels)):
    2040                          self.SetColLabelValue(col_idx, col_labels[col_idx])
    2041
    2042                  # cell values (list of test results)
    2043                  for result in results:
    2044                          row_idx = row_labels.index('%s%s' % (
    2045                                  gmTools.bool2subst(result['is_fake_meta_type'], '', gmTools.u_sum, ''),
    2046                                  result['unified_abbrev']
    2047                          ))
    2048                          col_idx = col_labels.index(gmDateTime.pydt_strftime(result['clin_when'], self.__date_format, accuracy = gmDateTime.acc_days))
    2049
    2050                          try:
    2051                                  self.__cell_data[col_idx]
    2052                          except KeyError:
    2053                                  self.__cell_data[col_idx] = {}
    2054
    2055                          # the tooltip always shows the youngest sub result details
    2056                          if row_idx in self.__cell_data[col_idx]:
    2057                                  self.__cell_data[col_idx][row_idx].append(result)
    2058                                  self.__cell_data[col_idx][row_idx].sort(key = lambda x: x['clin_when'], reverse = True)
    2059                          else:
    2060                                  self.__cell_data[col_idx][row_idx] = [result]
    2061
    2062                          # rebuild cell display string
    2063                          vals2display = []
    2064                          cell_has_out_of_bounds_value = False
    2065                          for sub_result in self.__cell_data[col_idx][row_idx]:
    2066
    2067                                  if sub_result.is_considered_abnormal:
    2068                                          cell_has_out_of_bounds_value = True
    2069
    2070                                  abnormality_indicator = sub_result.formatted_abnormality_indicator
    2071                                  if abnormality_indicator is None:
    2072                                          abnormality_indicator = ''
    2073                                  if abnormality_indicator != '':
    2074                                          abnormality_indicator = ' (%s)' % abnormality_indicator[:3]
    2075
    2076                                  missing_review = False
    2077                                  # warn on missing review if
    2078                                  # a) no review at all exists or
    2079                                  if not sub_result['reviewed']:
    2080                                          missing_review = True
    2081                                  # b) there is a review but
    2082                                  else:
    2083                                          # current user is reviewer and hasn't reviewed
    2084                                          if sub_result['you_are_responsible'] and not sub_result['review_by_you']:
    2085                                                  missing_review = True
    2086
    2087                                  needs_superscript = False
    2088
    2089                                  # can we display the full sub_result length ?
    2090                                  if sub_result.is_long_text:
    2091                                          lines = gmTools.strip_empty_lines (
    2092                                                  text = sub_result['unified_val'],
    2093                                                  eol = '\n',
    2094                                                  return_list = True
    2095                                          )
    2096                                          needs_superscript = True
    2097                                          tmp = lines[0][:7]
    2098                                  else:
    2099                                          val = gmTools.strip_empty_lines (
    2100                                                  text = sub_result['unified_val'],
    2101                                                  eol = '\n',
    2102                                                  return_list = False
    2103                                          ).replace('\n', '//')
    2104                                          if len(val) > 8:
    2105                                                  needs_superscript = True
    2106                                                  tmp = val[:7]
    2107                                          else:
    2108                                                  tmp = '%.8s' % val[:8]
    2109
    2110                                  # abnormal ?
    2111                                  tmp = '%s%.6s' % (tmp, abnormality_indicator)
    2112
    2113                                  # is there a comment ?
    2114                                  has_sub_result_comment = gmTools.coalesce (
    2115                                          gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']),
    2116                                          ''
    2117                                  ).strip() != ''
    2118                                  if has_sub_result_comment:
    2119                                          needs_superscript = True
    2120
    2121                                  if needs_superscript:
    2122                                          tmp = '%s%s' % (tmp, gmTools.u_superscript_one)
    2123
    2124                                  # lacking a review ?
    2125                                  if missing_review:
    2126                                          tmp = '%s %s' % (tmp, gmTools.u_writing_hand)
    2127                                  else:
    2128                                          if sub_result['is_clinically_relevant']:
    2129                                                  tmp += ' !'
    2130
    2131                                  # part of a multi-result cell ?
    2132                                  if len(self.__cell_data[col_idx][row_idx]) > 1:
    2133                                          tmp = '%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp)
    2134
    2135                                  vals2display.append(tmp)
    2136
    2137                          self.SetCellValue(row_idx, col_idx, '\n'.join(vals2display))
    2138                          self.SetCellAlignment(row_idx, col_idx, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
    2139                          # We used to color text in cells holding abnormals
    2140                          # in firebrick red but that would color ALL text (including
    2141                          # normals) and not only the abnormals within that
    2142                          # cell. Shading, however, only says that *something*
    2143                          # inside that cell is worthy of attention.
    2144                          #if sub_result_relevant:
    2145                          #       font = self.GetCellFont(row_idx, col_idx)
    2146                          #       self.SetCellTextColour(row_idx, col_idx, 'firebrick')
    2147                          #       font.SetWeight(wx.FONTWEIGHT_BOLD)
    2148                          #       self.SetCellFont(row_idx, col_idx, font)
    2149                          if cell_has_out_of_bounds_value:
    2150                                  #self.SetCellBackgroundColour(row_idx, col_idx, 'cornflower blue')
    2151                                  self.SetCellBackgroundColour(row_idx, col_idx, 'PALE TURQUOISE')
    2152
    2153                  self.EndBatch()
    2154
    2155                  self.AutoSize()
    2156                  self.AdjustScrollbars()
    2157                  self.ForceRefresh()
    2158
    2159                  #self.Fit()
    2160
    2161                  return
    2162
    2163          #------------------------------------------------------------
    2164 -        def empty_grid(self):
    2165                  self.BeginBatch()
    2166                  self.ClearGrid()
    2167                  # Windows cannot do nothing, it rather decides to assert()
    2168                  # on thinking it is supposed to do nothing
    2169                  if self.GetNumberRows() > 0:
    2170                          self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
    2171                  if self.GetNumberCols() > 0:
    2172                          self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
    2173                  self.EndBatch()
    2174                  self.__cell_data = {}
    2175                  self.__row_label_data = []
    2176                  self.__col_label_data = []
    2177
    2178          #------------------------------------------------------------
    2179 -        def get_row_tooltip(self, row=None):
    2180                  # include details about test types included ?
    2181
    2182                  # sometimes, for some reason, there is no row and
    2183                  # wxPython still tries to find a tooltip for it
    2184                  try:
    2185                          tt = self.__row_label_data[row]
    2186                  except IndexError:
    2187                          return ' '
    2188
    2189                  if tt['is_fake_meta_type']:
    2190                          return tt.format(patient = self.__patient.ID)
    2191
    2192                  meta_tt = tt.meta_test_type
    2193                  txt = meta_tt.format(with_tests = True, patient = self.__patient.ID)
    2194
    2195                  return txt
    2196
    2197          #------------------------------------------------------------
    2198 -        def get_cell_tooltip(self, col=None, row=None):
    2199                  try:
    2200                          cell_results = self.__cell_data[col][row]
    2201                  except KeyError:
    2202                          # FIXME: maybe display the most recent or when the most recent was ?
    2203                          cell_results = None
    2204
    2205                  if cell_results is None:
    2206                          return ' '
    2207
    2208                  is_multi_cell = False
    2209                  if len(cell_results) > 1:
    2210                          is_multi_cell = True
    2211                  result = cell_results[0]
    2212
    2213                  tt = ''
    2214                  # header
    2215                  if is_multi_cell:
    2216                          tt += _('Details of most recent (topmost) result !               \n')
    2217                  if result.is_long_text:
    2218                          tt += gmTools.strip_empty_lines(text = result['val_alpha'], eol = '\n', return_list = False)
    2219                          return tt
    2220
    2221                  tt += result.format(with_review = True, with_evaluation = True, with_ranges = True)
    2222                  return tt
    2223
    2224          #------------------------------------------------------------
    2225          # internal helpers
    2226          #------------------------------------------------------------
    2227 -        def __init_ui(self):
    2228                  #self.SetMinSize(wx.DefaultSize)
    2229                  self.SetMinSize((10, 10))
    2230
    2231                  self.CreateGrid(0, 1)
    2232                  self.EnableEditing(0)
    2233                  self.EnableDragGridSize(1)
    2234
    2235                  # column labels
    2236                  # setting this screws up the labels: they are cut off and displaced
    2237                  #self.SetColLabelAlignment(wx.ALIGN_CENTER, wx.ALIGN_BOTTOM)
    2238
    2239                  # row labels
    2240                  self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE)             # starting with 2.8.8
    2241                  #self.SetRowLabelSize(150)
    2242                  self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE)
    2243                  font = self.GetLabelFont()
    2244                  font.SetWeight(wx.FONTWEIGHT_LIGHT)
    2245                  self.SetLabelFont(font)
    2246
    2247                  # add link to left upper corner
    2248                  dbcfg = gmCfg.cCfgSQL()
    2249                  url = dbcfg.get2 (
    2250                          option = 'external.urls.measurements_encyclopedia',
    2251                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    2252                          bias = 'user',
    2253                          default = gmPathLab.URL_test_result_information
    2254                  )
    2255
    2256                  self.__WIN_corner = self.GetGridCornerLabelWindow()             # a wx.Window instance
    2257
    2258                  LNK_lab = wxh.HyperlinkCtrl (
    2259                          self.__WIN_corner,
    2260                          -1,
    2261                          label = _('Tests'),
    2262                          style = wxh.HL_DEFAULT_STYLE                    # wx.TE_READONLY|wx.TE_CENTRE| wx.NO_BORDER |
    2263                  )
    2264                  LNK_lab.SetURL(url)
    2265                  LNK_lab.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND))
    2266                  LNK_lab.SetToolTip(_(
    2267                          'Navigate to an encyclopedia of measurements\n'
    2268                          'and test methods on the web.\n'
    2269                          '\n'
    2270                          ' <%s>'
    2271                  ) % url)
    2272
    2273                  SZR_inner = wx.BoxSizer(wx.HORIZONTAL)
    2274                  SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)                # spacer
    2275                  SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0)          #wx.ALIGN_CENTER wx.EXPAND
    2276                  SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)                # spacer
    2277
    2278                  SZR_corner = wx.BoxSizer(wx.VERTICAL)
    2279                  SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)               # spacer
    2280                  SZR_corner.Add(SZR_inner, 0, wx.EXPAND)                 # inner sizer with centered hyperlink
    2281                  SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)               # spacer
    2282
    2283                  self.__WIN_corner.SetSizer(SZR_corner)
    2284                  SZR_corner.Fit(self.__WIN_corner)
    2285
    2286          #------------------------------------------------------------
    2287 -        def __resize_corner_window(self, evt):
    2288                  self.__WIN_corner.Layout()
    2289
    2290          #------------------------------------------------------------
    2291 -        def __cells_to_data(self, cells=None, exclude_multi_cells=False, auto_include_multi_cells=False):
    2292                  """List of <cells> must be in row / col order."""
    2293                  data = []
    2294                  for row, col in cells:
    2295                          try:
    2296                                  # cell data is stored col / row
    2297                                  data_list = self.__cell_data[col][row]
    2298                          except KeyError:
    2299                                  continue
    2300
    2301                          if len(data_list) == 1:
    2302                                  data.append(data_list[0])
    2303                                  continue
    2304
    2305                          if exclude_multi_cells:
    2306                                  gmDispatcher.send(signal = 'statustext', msg = _('Excluding multi-result field from further processing.'))
    2307                                  continue
    2308
    2309                          if auto_include_multi_cells:
    2310                                  data.extend(data_list)
    2311                                  continue
    2312
    2313                          data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list)
    2314                          if data_to_include is None:
    2315                                  continue
    2316                          data.extend(data_to_include)
    2317
    2318                  return data
    2319
    2320          #------------------------------------------------------------
    2321 -        def __get_choices_from_multi_cell(self, cell_data=None, single_selection=False):
    2322                  data = gmListWidgets.get_choices_from_list (
    2323                          parent = self,
    2324                          msg = _(
    2325                                  'Your selection includes a field with multiple results.\n'
    2326                                  '\n'
    2327                                  'Please select the individual results you want to work on:'
    2328                          ),
    2329                          caption = _('Selecting test results'),
    2330                          choices = [ [d['clin_when'], '%s: %s' % (d['abbrev_tt'], d['name_tt']), d['unified_val']] for d in cell_data ],
    2331                          columns = [ _('Date / Time'), _('Test'), _('Result') ],
    2332                          data = cell_data,
    2333                          single_selection = single_selection
    2334                  )
    2335                  return data
    2336
    2337          #------------------------------------------------------------
    2338          # event handling
    2339          #------------------------------------------------------------
    2340 -        def __register_events(self):
    2341                  # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow
    2342                  self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
    2343                  self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels)
    2344                  #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels)
    2345
    2346                  # sizing left upper corner window
    2347                  self.Bind(wx.EVT_SIZE, self.__resize_corner_window)
    2348
    2349                  # editing cells
    2350                  self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
    2351
    2352          #------------------------------------------------------------
    2353 -        def __on_cell_left_dclicked(self, evt):
    2354                  col = evt.GetCol()
    2355                  row = evt.GetRow()
    2356
    2357                  try:
    2358                          self.__cell_data[col][row]
    2359                  except KeyError:                # empty cell
    2360                          fields = {}
    2361                          col_date = self.__col_label_data[col]
    2362                          fields['clin_when'] = {'data': col_date}
    2363                          test_type = self.__row_label_data[row]
    2364                          temporally_closest_result_of_row_type = test_type.meta_test_type.get_temporally_closest_result(col_date, self.__patient.ID)
    2365                          if temporally_closest_result_of_row_type is not None:
    2366                                  fields['pk_test_type'] = {'data': temporally_closest_result_of_row_type['pk_test_type']}
    2367                          same_day_results = gmPathLab.get_results_for_day (
    2368                                  timestamp = col_date,
    2369                                  patient = self.__patient.ID,
    2370                                  order_by = None
    2371                          )
    2372                          if len(same_day_results) > 0:
    2373                                  fields['pk_episode'] = {'data': same_day_results[0]['pk_episode']}
    2374                                  # maybe ['comment'] as in "medical context" ? - not thought through yet
    2375                          # no need to set because because setting pk_test_type will do so:
    2376                          #       fields['val_unit']
    2377                          #       fields['val_normal_min']
    2378                          #       fields['val_normal_max']
    2379                          #       fields['val_normal_range']
    2380                          #       fields['val_target_min']
    2381                          #       fields['val_target_max']
    2382                          #       fields['val_target_range']
    2383                          edit_measurement (
    2384                                  parent = self,
    2385                                  measurement = None,
    2386                                  single_entry = True,
    2387                                  fields = fields
    2388                          )
    2389                          return
    2390
    2391                  if len(self.__cell_data[col][row]) > 1:
    2392                          data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True)
    2393                  else:
    2394                          data = self.__cell_data[col][row][0]
    2395
    2396                  if data is None:
    2397                          return
    2398
    2399                  edit_measurement(parent = self, measurement = data, single_entry = True)
    2400
    2401          #------------------------------------------------------------
    2402  #     def OnMouseMotionRowLabel(self, evt):
    2403  #         x, y = self.CalcUnscrolledPosition(evt.GetPosition())
    2404  #         row = self.YToRow(y)
    2405  #         label = self.table().GetRowHelpValue(row)
    2406  #         self.GetGridRowLabelWindow().SetToolTip(label or "")
    2407  #         evt.Skip()
    2408 -        def __on_mouse_over_row_labels(self, evt):
    2409
    2410                  # Use CalcUnscrolledPosition() to get the mouse position within the
    2411                  # entire grid including what's offscreen
    2412                  x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
    2413
    2414                  row = self.YToRow(y)
    2415
    2416                  if self.__prev_label_row == row:
    2417                          return
    2418
    2419                  self.__prev_label_row == row
    2420
    2421                  evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row))
    2422          #------------------------------------------------------------
    2423  #     def OnMouseMotionColLabel(self, evt):
    2424  #         x, y = self.CalcUnscrolledPosition(evt.GetPosition())
    2425  #         col = self.XToCol(x)
    2426  #         label = self.table().GetColHelpValue(col)
    2427  #         self.GetGridColLabelWindow().SetToolTip(label or "")
    2428  #         evt.Skip()
    2429          #------------------------------------------------------------
    2430 -        def __on_mouse_over_cells(self, evt):
    2431                  """Calculate where the mouse is and set the tooltip dynamically."""
    2432
    2433                  # Use CalcUnscrolledPosition() to get the mouse position within the
    2434                  # entire grid including what's offscreen
    2435                  x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
    2436
    2437                  # use this logic to prevent tooltips outside the actual cells
    2438                  # apply to GetRowSize, too
    2439  #        tot = 0
    2440  #        for col in range(self.NumberCols):
    2441  #            tot += self.GetColSize(col)
    2442  #            if xpos <= tot:
    2443  #                self.tool_tip.Tip = 'Tool tip for Column %s' % (
    2444  #                    self.GetColLabelValue(col))
    2445  #                break
    2446  #            else:  # mouse is in label area beyond the right-most column
    2447  #            self.tool_tip.Tip = ''
    2448
    2449                  row, col = self.XYToCell(x, y)
    2450
    2451                  if (row == self.__prev_row) and (col == self.__prev_col):
    2452                          return
    2453
    2454                  self.__prev_row = row
    2455                  self.__prev_col = col
    2456
    2457                  evt.GetEventObject().SetToolTip(self.get_cell_tooltip(col=col, row=row))
    2458
    2459          #------------------------------------------------------------
    2460          # properties
    2461          #------------------------------------------------------------
    2462 -        def _get_patient(self):
    2463                  return self.__patient
    2464
    2465 -        def _set_patient(self, patient):
    2466                  self.__patient = patient
    2467                  self.repopulate_grid()
    2468
    2469          patient = property(_get_patient, _set_patient)
    2470          #------------------------------------------------------------
    2471 -        def _set_panel_to_show(self, panel):
    2472                  self.__panel_to_show = panel
    2473                  self.repopulate_grid()
    2474
    2475          panel_to_show = property(lambda x:x, _set_panel_to_show)
    2476          #------------------------------------------------------------
    2477 -        def _set_show_by_panel(self, show_by_panel):
    2478                  self.__show_by_panel = show_by_panel
    2479                  self.repopulate_grid()
    2480
    2481          show_by_panel = property(lambda x:x, _set_show_by_panel)
    2482
    2483  #================================================================
    2484  # integrated measurements plugin
    2485  #================================================================
    2486  from Gnumed.wxGladeWidgets import wxgMeasurementsPnl
    2487
    2488 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
    2489          """Panel holding a grid with lab data. Used as notebook page."""
    2490
    2491 -        def __init__(self, *args, **kwargs):
    2492
    2493                  wxgMeasurementsPnl.wxgMeasurementsPnl.__init__(self, *args, **kwargs)
    2494                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    2495                  self.__display_mode = 'grid'
    2496                  self.__init_ui()
    2497                  self.__register_interests()
    2498          #--------------------------------------------------------
    2499          # event handling
    2500          #--------------------------------------------------------
    2501 -        def __register_interests(self):
    2502                  gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
    2503                  gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
    2504                  gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._schedule_data_reget)
    2505                  gmDispatcher.connect(signal = 'clin.reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
    2506          #--------------------------------------------------------
    2507 -        def _on_post_patient_selection(self):
    2508                  self._schedule_data_reget()
    2509          #--------------------------------------------------------
    2510 -        def _on_pre_patient_unselection(self):
    2511                  self._GRID_results_all.patient = None
    2512                  self._GRID_results_battery.patient = None
    2513          #--------------------------------------------------------
    2514 -        def _on_add_button_pressed(self, event):
    2515                  edit_measurement(parent = self, measurement = None)
    2516          #--------------------------------------------------------
    2517 -        def _on_manage_types_button_pressed(self, event):
    2518                  event.Skip()
    2519                  manage_measurement_types(parent = self)
    2520          #--------------------------------------------------------
    2521 -        def _on_list_button_pressed(self, event):
    2522                  event.Skip()
    2523                  manage_measurements(parent = self, single_selection = True)#, emr = pat.emr)
    2524          #--------------------------------------------------------
    2525 -        def _on_review_button_pressed(self, evt):
    2526                  self.PopupMenu(self.__action_button_popup)
    2527          #--------------------------------------------------------
    2528 -        def _on_select_button_pressed(self, evt):
    2529                  if self._RBTN_my_unsigned.GetValue() is True:
    2530                          self._GRID_results_all.select_cells(unsigned_only = True, accountables_only = True, keep_preselections = False)
    2531                  elif self._RBTN_all_unsigned.GetValue() is True:
    2532                          self._GRID_results_all.select_cells(unsigned_only = True, accountables_only = False, keep_preselections = False)
    2533          #--------------------------------------------------------
    2534 -        def _on_manage_panels_button_pressed(self, event):
    2535                  manage_test_panels(parent = self)
    2536          #--------------------------------------------------------
    2537 -        def _on_display_mode_button_pressed(self, event):
    2538                  event.Skip()
    2539                  if self.__display_mode == 'grid':
    2540                          self._BTN_display_mode.SetLabel(_('All: as &Grid'))
    2541                          self.__display_mode = 'day'
    2542                          #self._GRID_results_all.Hide()
    2543                          self._PNL_results_all_grid.Hide()
    2544                          if self._PNL_results_all_listed.patient is None:
    2545                                  self._PNL_results_all_listed.patient = self._GRID_results_all.patient
    2546                          self._PNL_results_all_listed.Show()
    2547                  else:
    2548                          self._BTN_display_mode.SetLabel(_('All: by &Day'))
    2549                          self.__display_mode = 'grid'
    2550                          self._PNL_results_all_listed.Hide()
    2551                          if self._GRID_results_all.patient is None:
    2552                                  self._GRID_results_all.patient = self._PNL_results_all_listed.patient
    2553                          #self._GRID_results_all.Show()
    2554                          self._PNL_results_all_grid.Show()
    2555                  self.Layout()
    2556          #--------------------------------------------------------
    2557 -        def __on_sign_current_selection(self, evt):
    2558                  self._GRID_results_all.sign_current_selection()
    2559          #--------------------------------------------------------
    2560 -        def __on_plot_current_selection(self, evt):
    2561                  self._GRID_results_all.plot_current_selection()
    2562          #--------------------------------------------------------
    2563 -        def __on_delete_current_selection(self, evt):
    2564                  self._GRID_results_all.delete_current_selection()
    2565          #--------------------------------------------------------
    2566 -        def _on_panel_selected(self, panel):
    2567                  wx.CallAfter(self.__on_panel_selected, panel=panel)
    2568          #--------------------------------------------------------
    2569 -        def __on_panel_selected(self, panel):
    2570                  if panel is None:
    2571                          self._TCTRL_panel_comment.SetValue('')
    2572                          self._GRID_results_battery.panel_to_show = None
    2573                          #self._GRID_results_battery.Hide()
    2574                          self._PNL_results_battery_grid.Hide()
    2575                  else:
    2576                          pnl = self._PRW_panel.GetData(as_instance = True)
    2577                          self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
    2578                                  pnl['comment'],
    2579                                  ''
    2580                          ))
    2581                          self._GRID_results_battery.panel_to_show = pnl
    2582                          #self._GRID_results_battery.Show()
    2583                          self._PNL_results_battery_grid.Show()
    2584                  self._GRID_results_battery.Fit()
    2585                  self._GRID_results_all.Fit()
    2586                  self.Layout()
    2587          #--------------------------------------------------------
    2588 -        def _on_panel_selection_modified(self):
    2589                  wx.CallAfter(self.__on_panel_selection_modified)
    2590          #--------------------------------------------------------
    2591 -        def __on_panel_selection_modified(self):
    2592                  self._TCTRL_panel_comment.SetValue('')
    2593                  if self._PRW_panel.GetValue().strip() == '':
    2594                          self._GRID_results_battery.panel_to_show = None
    2595                          #self._GRID_results_battery.Hide()
    2596                          self._PNL_results_battery_grid.Hide()
    2597                          self.Layout()
    2598          #--------------------------------------------------------
    2599          # internal API
    2600          #--------------------------------------------------------
    2601 -        def __init_ui(self):
    2602                  self.SetMinSize((10, 10))
    2603
    2604                  self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
    2605
    2606                  item = self.__action_button_popup.Append(-1, _('Review and &sign'))
    2607                  self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
    2608
    2609                  item = self.__action_button_popup.Append(-1, _('Plot'))
    2610                  self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
    2611
    2612                  item = self.__action_button_popup.Append(-1, _('Export to &file'))
    2613                  self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item)
    2614                  self.__action_button_popup.Enable(id = menu_id, enable = False)
    2615
    2616                  item = self.__action_button_popup.Append(-1, _('Export to &clipboard'))
    2617                  self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item)
    2618                  self.__action_button_popup.Enable(id = menu_id, enable = False)
    2619
    2620                  item = self.__action_button_popup.Append(-1, _('&Delete'))
    2621                  self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
    2622
    2623                  # FIXME: create inbox message to staff to phone patient to come in
    2624                  # FIXME: generate and let edit a SOAP narrative and include the values
    2625
    2626                  self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
    2627                  self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
    2628
    2629                  self._GRID_results_battery.show_by_panel = True
    2630                  self._GRID_results_battery.panel_to_show = None
    2631                  #self._GRID_results_battery.Hide()
    2632                  self._PNL_results_battery_grid.Hide()
    2633                  self._BTN_display_mode.SetLabel(_('All: by &Day'))
    2634                  #self._GRID_results_all.Show()
    2635                  self._PNL_results_all_grid.Show()
    2636                  self._PNL_results_all_listed.Hide()
    2637                  self.Layout()
    2638
    2639                  self._PRW_panel.SetFocus()
    2640          #--------------------------------------------------------
    2641          # reget mixin API
    2642          #--------------------------------------------------------
    2643 -        def _populate_with_data(self):
    2644                  pat = gmPerson.gmCurrentPatient()
    2645                  if pat.connected:
    2646                          self._GRID_results_battery.patient = pat
    2647                          if self.__display_mode == 'grid':
    2648                                  self._GRID_results_all.patient = pat
    2649                                  self._PNL_results_all_listed.patient = None
    2650                          else:
    2651                                  self._GRID_results_all.patient = None
    2652                                  self._PNL_results_all_listed.patient = pat
    2653                  else:
    2654                          self._GRID_results_battery.patient = None
    2655                          self._GRID_results_all.patient = None
    2656                          self._PNL_results_all_listed.patient = None
    2657                  return True
    2658
    2659  #================================================================
    2660  # editing widgets
    2661  #================================================================
    2662 -def review_tests(parent=None, tests=None):
    2663
    2664          if tests is None:
    2665                  return True
    2666
    2667          if len(tests) == 0:
    2668                  return True
    2669
    2670          if parent is None:
    2671                  parent = wx.GetApp().GetTopWindow()
    2672
    2673          if len(tests) > 10:
    2674                  test_count = len(tests)
    2675                  tests2show = None
    2676          else:
    2677                  test_count = None
    2678                  tests2show = tests
    2679                  if len(tests) == 0:
    2680                          return True
    2681
    2682          dlg = cMeasurementsReviewDlg(parent, -1, tests = tests, test_count = test_count)
    2683          decision = dlg.ShowModal()
    2684          if decision != wx.ID_APPLY:
    2685                  return True
    2686
    2687          wx.BeginBusyCursor()
    2688          if dlg._RBTN_confirm_abnormal.GetValue():
    2689                  abnormal = None
    2690          elif dlg._RBTN_results_normal.GetValue():
    2691                  abnormal = False
    2692          else:
    2693                  abnormal = True
    2694
    2695          if dlg._RBTN_confirm_relevance.GetValue():
    2696                  relevant = None
    2697          elif dlg._RBTN_results_not_relevant.GetValue():
    2698                  relevant = False
    2699          else:
    2700                  relevant = True
    2701
    2702          comment = None
    2703          if len(tests) == 1:
    2704                  comment = dlg._TCTRL_comment.GetValue()
    2705
    2706          make_responsible = dlg._CHBOX_responsible.IsChecked()
    2707          dlg.Destroy()
    2708
    2709          for test in tests:
    2710                  test.set_review (
    2711                          technically_abnormal = abnormal,
    2712                          clinically_relevant = relevant,
    2713                          comment = comment,
    2714                          make_me_responsible = make_responsible
    2715                  )
    2716          wx.EndBusyCursor()
    2717
    2718          return True
    2719
    2720  #----------------------------------------------------------------
    2721  from Gnumed.wxGladeWidgets import wxgMeasurementsReviewDlg
    2722
    2723 -class cMeasurementsReviewDlg(wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg):
    2724
    2725 -        def __init__(self, *args, **kwargs):
    2726
    2727                  try:
    2728                          tests = kwargs['tests']
    2729                          del kwargs['tests']
    2730                          test_count = len(tests)
    2731                          try: del kwargs['test_count']
    2732                          except KeyError: pass
    2733                  except KeyError:
    2734                          tests = None
    2735                          test_count = kwargs['test_count']
    2736                          del kwargs['test_count']
    2737
    2738                  wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs)
    2739
    2740                  if tests is None:
    2741                          msg = _('%s results selected. Too many to list individually.') % test_count
    2742                  else:
    2743                          msg = '\n'.join (
    2744                                  [       '%s: %s %s (%s)' % (
    2745                                                  t['unified_abbrev'],
    2746                                                  t['unified_val'],
    2747                                                  t['val_unit'],
    2748                                                  gmDateTime.pydt_strftime(t['clin_when'], '%Y %b %d')
    2749                                          ) for t in tests
    2750                                  ]
    2751                          )
    2752
    2753                  self._LBL_tests.SetLabel(msg)
    2754
    2755                  if test_count == 1:
    2756                          self._TCTRL_comment.Enable(True)
    2757                          self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], ''))
    2758                          if tests[0]['you_are_responsible']:
    2759                                  self._CHBOX_responsible.Enable(False)
    2760
    2761                  self.Fit()
    2762          #--------------------------------------------------------
    2763          # event handling
    2764          #--------------------------------------------------------
    2765 -        def _on_signoff_button_pressed(self, evt):
    2766                  if self.IsModal():
    2767                          self.EndModal(wx.ID_APPLY)
    2768                  else:
    2769                          self.Close()
    2770
    2771  #================================================================
    2772  from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl
    2773
    2774 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
    2775          """This edit area saves *new* measurements into the active patient only."""
    2776
    2777 -        def __init__(self, *args, **kwargs):
    2778
    2779                  try:
    2780                          self.__default_date = kwargs['date']
    2781                          del kwargs['date']
    2782                  except KeyError:
    2783                          self.__default_date = None
    2784
    2785                  wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl.__init__(self, *args, **kwargs)
    2786                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    2787
    2788                  self.__register_interests()
    2789
    2790                  self.successful_save_msg = _('Successfully saved measurement.')
    2791
    2792                  self._DPRW_evaluated.display_accuracy = gmDateTime.acc_minutes
    2793
    2794          #--------------------------------------------------------
    2795          # generic edit area mixin API
    2796          #----------------------------------------------------------------
    2797 -        def set_fields(self, fields):
    2798                  try:
    2799                          self._PRW_test.SetData(data = fields['pk_test_type']['data'])
    2800                  except KeyError:
    2801                          pass
    2802                  try:
    2803                          self._DPRW_evaluated.SetData(data = fields['clin_when']['data'])
    2804                  except KeyError:
    2805                          pass
    2806                  try:
    2807                          self._PRW_problem.SetData(data = fields['pk_episode']['data'])
    2808                  except KeyError:
    2809                          pass
    2810                  try:
    2811                          self._PRW_units.SetText(fields['val_unit']['data'], fields['val_unit']['data'], True)
    2812                  except KeyError:
    2813                          pass
    2814                  try:
    2815                          self._TCTRL_normal_min.SetValue(fields['val_normal_min']['data'])
    2816                  except KeyError:
    2817                          pass
    2818                  try:
    2819                          self._TCTRL_normal_max.SetValue(fields['val_normal_max']['data'])
    2820                  except KeyError:
    2821                          pass
    2822                  try:
    2823                          self._TCTRL_normal_range.SetValue(fields['val_normal_range']['data'])
    2824                  except KeyError:
    2825                          pass
    2826                  try:
    2827                          self._TCTRL_target_min.SetValue(fields['val_target_min']['data'])
    2828                  except KeyError:
    2829                          pass
    2830                  try:
    2831                          self._TCTRL_target_max.SetValue(fields['val_target_max']['data'])
    2832                  except KeyError:
    2833                          pass
    2834                  try:
    2835                          self._TCTRL_target_range.SetValue(fields['val_target_range']['data'])
    2836                  except KeyError:
    2837                          pass
    2838
    2839                  self._TCTRL_result.SetFocus()
    2840
    2841          #--------------------------------------------------------
    2842 -        def _refresh_as_new(self):
    2843                  self._PRW_test.SetText('', None, True)
    2844                  self.__refresh_loinc_info()
    2845                  self.__refresh_previous_value()
    2846                  self.__update_units_context()
    2847                  self._TCTRL_result.SetValue('')
    2848                  self._PRW_units.SetText('', None, True)
    2849                  self._PRW_abnormality_indicator.SetText('', None, True)
    2850                  if self.__default_date is None:
    2851                          self._DPRW_evaluated.SetData(data = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone))
    2852                  else:
    2853                          self._DPRW_evaluated.SetData(data =     None)
    2854                  self._TCTRL_note_test_org.SetValue('')
    2855                  self._PRW_intended_reviewer.SetData(gmStaff.gmCurrentProvider()['pk_staff'])
    2856                  self._PRW_problem.SetData()
    2857                  self._TCTRL_narrative.SetValue('')
    2858                  self._CHBOX_review.SetValue(False)
    2859                  self._CHBOX_abnormal.SetValue(False)
    2860                  self._CHBOX_relevant.SetValue(False)
    2861                  self._CHBOX_abnormal.Enable(False)
    2862                  self._CHBOX_relevant.Enable(False)
    2863                  self._TCTRL_review_comment.SetValue('')
    2864                  self._TCTRL_normal_min.SetValue('')
    2865                  self._TCTRL_normal_max.SetValue('')
    2866                  self._TCTRL_normal_range.SetValue('')
    2867                  self._TCTRL_target_min.SetValue('')
    2868                  self._TCTRL_target_max.SetValue('')
    2869                  self._TCTRL_target_range.SetValue('')
    2870                  self._TCTRL_norm_ref_group.SetValue('')
    2871
    2872                  self._PRW_test.SetFocus()
    2873          #--------------------------------------------------------
    2874 -        def _refresh_from_existing(self):
    2875                  self._PRW_test.SetData(data = self.data['pk_test_type'])
    2876                  self.__refresh_loinc_info()
    2877                  self.__refresh_previous_value()
    2878                  self.__update_units_context()
    2879                  self._TCTRL_result.SetValue(self.data['unified_val'])
    2880                  self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True)
    2881                  self._PRW_abnormality_indicator.SetText (
    2882                          gmTools.coalesce(self.data['abnormality_indicator'], ''),
    2883                          gmTools.coalesce(self.data['abnormality_indicator'], ''),
    2884                          True
    2885                  )
    2886                  self._DPRW_evaluated.SetData(data = self.data['clin_when'])
    2887                  self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], ''))
    2888                  self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
    2889                  self._PRW_problem.SetData(self.data['pk_episode'])
    2890                  self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], ''))
    2891                  self._CHBOX_review.SetValue(False)
    2892                  self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False))
    2893                  self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False))
    2894                  self._CHBOX_abnormal.Enable(False)
    2895                  self._CHBOX_relevant.Enable(False)
    2896                  self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], ''))
    2897                  self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(self.data['val_normal_min'], '')))
    2898                  self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(self.data['val_normal_max'], '')))
    2899                  self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], ''))
    2900                  self._TCTRL_target_min.SetValue(str(gmTools.coalesce(self.data['val_target_min'], '')))
    2901                  self._TCTRL_target_max.SetValue(str(gmTools.coalesce(self.data['val_target_max'], '')))
    2902                  self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], ''))
    2903                  self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], ''))
    2904
    2905                  self._TCTRL_result.SetFocus()
    2906          #--------------------------------------------------------
    2907 -        def _refresh_as_new_from_existing(self):
    2908                  self._PRW_test.SetText('', None, True)
    2909                  self.__refresh_loinc_info()
    2910                  self.__refresh_previous_value()
    2911                  self.__update_units_context()
    2912                  self._TCTRL_result.SetValue('')
    2913                  self._PRW_units.SetText('', None, True)
    2914                  self._PRW_abnormality_indicator.SetText('', None, True)
    2915                  self._DPRW_evaluated.SetData(data = self.data['clin_when'])
    2916                  self._TCTRL_note_test_org.SetValue('')
    2917                  self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
    2918                  self._PRW_problem.SetData(self.data['pk_episode'])
    2919                  self._TCTRL_narrative.SetValue('')
    2920                  self._CHBOX_review.SetValue(False)
    2921                  self._CHBOX_abnormal.SetValue(False)
    2922                  self._CHBOX_relevant.SetValue(False)
    2923                  self._CHBOX_abnormal.Enable(False)
    2924                  self._CHBOX_relevant.Enable(False)
    2925                  self._TCTRL_review_comment.SetValue('')
    2926                  self._TCTRL_normal_min.SetValue('')
    2927                  self._TCTRL_normal_max.SetValue('')
    2928                  self._TCTRL_normal_range.SetValue('')
    2929                  self._TCTRL_target_min.SetValue('')
    2930                  self._TCTRL_target_max.SetValue('')
    2931                  self._TCTRL_target_range.SetValue('')
    2932                  self._TCTRL_norm_ref_group.SetValue('')
    2933
    2934                  self._PRW_test.SetFocus()
    2935          #--------------------------------------------------------
    2936 -        def _valid_for_save(self):
    2937
    2938                  validity = True
    2939
    2940                  if not self._DPRW_evaluated.is_valid_timestamp():
    2941                          self._DPRW_evaluated.display_as_valid(False)
    2942                          validity = False
    2943                  else:
    2944                          self._DPRW_evaluated.display_as_valid(True)
    2945
    2946                  val = self._TCTRL_result.GetValue().strip()
    2947                  if val == '':
    2948                          validity = False
    2949                          self.display_ctrl_as_valid(self._TCTRL_result, False)
    2950                  else:
    2951                          self.display_ctrl_as_valid(self._TCTRL_result, True)
    2952                          numeric, val = gmTools.input2decimal(val)
    2953                          if numeric:
    2954                                  if self._PRW_units.GetValue().strip() == '':
    2955                                          self._PRW_units.display_as_valid(False)
    2956                                          validity = False
    2957                                  else:
    2958                                          self._PRW_units.display_as_valid(True)
    2959                          else:
    2960                                  self._PRW_units.display_as_valid(True)
    2961
    2962                  if self._PRW_problem.GetValue().strip() == '':
    2963                          self._PRW_problem.display_as_valid(False)
    2964                          validity = False
    2965                  else:
    2966                          self._PRW_problem.display_as_valid(True)
    2967
    2968                  if self._PRW_test.GetValue().strip() == '':
    2969                          self._PRW_test.display_as_valid(False)
    2970                          validity = False
    2971                  else:
    2972                          self._PRW_test.display_as_valid(True)
    2973
    2974                  if self._PRW_intended_reviewer.GetData() is None:
    2975                          self._PRW_intended_reviewer.display_as_valid(False)
    2976                          validity = False
    2977                  else:
    2978                          self._PRW_intended_reviewer.display_as_valid(True)
    2979
    2980                  ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max]
    2981                  for widget in ctrls:
    2982                          val = widget.GetValue().strip()
    2983                          if val == '':
    2984                                  continue
    2985                          try:
    2986                                  decimal.Decimal(val.replace(',', '.', 1))
    2987                                  self.display_ctrl_as_valid(widget, True)
    2988                          except:
    2989                                  validity = False
    2990                                  self.display_ctrl_as_valid(widget, False)
    2991
    2992                  if validity is False:
    2993                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot save result. Invalid or missing essential input.'))
    2994
    2995                  return validity
    2996          #--------------------------------------------------------
    2997 -        def _save_as_new(self):
    2998
    2999                  emr = gmPerson.gmCurrentPatient().emr
    3000
    3001                  success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
    3002                  if success:
    3003                          v_num = result
    3004                          v_al = None
    3005                  else:
    3006                          v_al = self._TCTRL_result.GetValue().strip()
    3007                          v_num = None
    3008
    3009                  pk_type = self._PRW_test.GetData()
    3010                  if pk_type is None:
    3011                          abbrev = self._PRW_test.GetValue().strip()
    3012                          name = self._PRW_test.GetValue().strip()
    3013                          unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
    3014                          lab = manage_measurement_orgs (
    3015                                  parent = self,
    3016                                  msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
    3017                          )
    3018                          if lab is not None:
    3019                                  lab = lab['pk_test_org']
    3020                          tt = gmPathLab.create_measurement_type (
    3021                                  lab = lab,
    3022                                  abbrev = abbrev,
    3023                                  name = name,
    3024                                  unit = unit
    3025                          )
    3026                          pk_type = tt['pk_test_type']
    3027
    3028                  tr = emr.add_test_result (
    3029                          episode = self._PRW_problem.GetData(can_create=True, is_open=False),
    3030                          type = pk_type,
    3031                          intended_reviewer = self._PRW_intended_reviewer.GetData(),
    3032                          val_num = v_num,
    3033                          val_alpha = v_al,
    3034                          unit = self._PRW_units.GetValue()
    3035                  )
    3036
    3037                  tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
    3038
    3039                  ctrls = [
    3040                          ('abnormality_indicator', self._PRW_abnormality_indicator),
    3041                          ('note_test_org', self._TCTRL_note_test_org),
    3042                          ('comment', self._TCTRL_narrative),
    3043                          ('val_normal_range', self._TCTRL_normal_range),
    3044                          ('val_target_range', self._TCTRL_target_range),
    3045                          ('norm_ref_group', self._TCTRL_norm_ref_group)
    3046                  ]
    3047                  for field, widget in ctrls:
    3048                          tr[field] = widget.GetValue().strip()
    3049
    3050                  ctrls = [
    3051                          ('val_normal_min', self._TCTRL_normal_min),
    3052                          ('val_normal_max', self._TCTRL_normal_max),
    3053                          ('val_target_min', self._TCTRL_target_min),
    3054                          ('val_target_max', self._TCTRL_target_max)
    3055                  ]
    3056                  for field, widget in ctrls:
    3057                          val = widget.GetValue().strip()
    3058                          if val == '':
    3059                                  tr[field] = None
    3060                          else:
    3061                                  tr[field] = decimal.Decimal(val.replace(',', '.', 1))
    3062
    3063                  tr.save_payload()
    3064
    3065                  if self._CHBOX_review.GetValue() is True:
    3066                          tr.set_review (
    3067                                  technically_abnormal = self._CHBOX_abnormal.GetValue(),
    3068                                  clinically_relevant = self._CHBOX_relevant.GetValue(),
    3069                                  comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
    3070                                  make_me_responsible = False
    3071                          )
    3072
    3073                  self.data = tr
    3074
    3075  #               wx.CallAfter (
    3076  #                       plot_adjacent_measurements,
    3077  #                       test = self.data,
    3078  #                       plot_singular_result = False,
    3079  #                       use_default_template = True
    3080  #               )
    3081
    3082                  return True
    3083          #--------------------------------------------------------
    3084 -        def _save_as_update(self):
    3085
    3086                  success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
    3087                  if success:
    3088                          v_num = result
    3089                          v_al = None
    3090                  else:
    3091                          v_num = None
    3092                          v_al = self._TCTRL_result.GetValue().strip()
    3093
    3094                  pk_type = self._PRW_test.GetData()
    3095                  if pk_type is None:
    3096                          abbrev = self._PRW_test.GetValue().strip()
    3097                          name = self._PRW_test.GetValue().strip()
    3098                          unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
    3099                          lab = manage_measurement_orgs (
    3100                                  parent = self,
    3101                                  msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
    3102                          )
    3103                          if lab is not None:
    3104                                  lab = lab['pk_test_org']
    3105                          tt = gmPathLab.create_measurement_type (
    3106                                  lab = None,
    3107                                  abbrev = abbrev,
    3108                                  name = name,
    3109                                  unit = unit
    3110                          )
    3111                          pk_type = tt['pk_test_type']
    3112
    3113                  tr = self.data
    3114
    3115                  tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False)
    3116                  tr['pk_test_type'] = pk_type
    3117                  tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData()
    3118                  tr['val_num'] = v_num
    3119                  tr['val_alpha'] = v_al
    3120                  tr['val_unit'] = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
    3121                  tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
    3122
    3123                  ctrls = [
    3124                          ('abnormality_indicator', self._PRW_abnormality_indicator),
    3125                          ('note_test_org', self._TCTRL_note_test_org),
    3126                          ('comment', self._TCTRL_narrative),
    3127                          ('val_normal_range', self._TCTRL_normal_range),
    3128                          ('val_target_range', self._TCTRL_target_range),
    3129                          ('norm_ref_group', self._TCTRL_norm_ref_group)
    3130                  ]
    3131                  for field, widget in ctrls:
    3132                          tr[field] = widget.GetValue().strip()
    3133
    3134                  ctrls = [
    3135                          ('val_normal_min', self._TCTRL_normal_min),
    3136                          ('val_normal_max', self._TCTRL_normal_max),
    3137                          ('val_target_min', self._TCTRL_target_min),
    3138                          ('val_target_max', self._TCTRL_target_max)
    3139                  ]
    3140                  for field, widget in ctrls:
    3141                          val = widget.GetValue().strip()
    3142                          if val == '':
    3143                                  tr[field] = None
    3144                          else:
    3145                                  tr[field] = decimal.Decimal(val.replace(',', '.', 1))
    3146
    3147                  tr.save_payload()
    3148
    3149                  if self._CHBOX_review.GetValue() is True:
    3150                          tr.set_review (
    3151                                  technically_abnormal = self._CHBOX_abnormal.GetValue(),
    3152                                  clinically_relevant = self._CHBOX_relevant.GetValue(),
    3153                                  comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
    3154                                  make_me_responsible = False
    3155                          )
    3156
    3157  #               wx.CallAfter (
    3158  #                       plot_adjacent_measurements,
    3159  #                       test = self.data,
    3160  #                       plot_singular_result = False,
    3161  #                       use_default_template = True
    3162  #               )
    3163
    3164                  return True
    3165          #--------------------------------------------------------
    3166          # event handling
    3167          #--------------------------------------------------------
    3168 -        def __register_interests(self):
    3169                  self._PRW_test.add_callback_on_lose_focus(self._on_leave_test_prw)
    3170                  self._PRW_abnormality_indicator.add_callback_on_lose_focus(self._on_leave_indicator_prw)
    3171                  self._PRW_units.add_callback_on_lose_focus(self._on_leave_unit_prw)
    3172          #--------------------------------------------------------
    3173 -        def _on_leave_test_prw(self):
    3174                  self.__refresh_loinc_info()
    3175                  self.__refresh_previous_value()
    3176                  self.__update_units_context()
    3177                  # only works if we've got a unit set
    3178                  self.__update_normal_range()
    3179                  self.__update_clinical_range()
    3180          #--------------------------------------------------------
    3181 -        def _on_leave_unit_prw(self):
    3182                  # maybe we've got a unit now ?
    3183                  self.__update_normal_range()
    3184                  self.__update_clinical_range()
    3185          #--------------------------------------------------------
    3186 -        def _on_leave_indicator_prw(self):
    3187                  # if the user hasn't explicitly enabled reviewing
    3188                  if not self._CHBOX_review.GetValue():
    3189                          self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != '')
    3190          #--------------------------------------------------------
    3191 -        def _on_review_box_checked(self, evt):
    3192                  self._CHBOX_abnormal.Enable(self._CHBOX_review.GetValue())
    3193                  self._CHBOX_relevant.Enable(self._CHBOX_review.GetValue())
    3194                  self._TCTRL_review_comment.Enable(self._CHBOX_review.GetValue())
    3195          #--------------------------------------------------------
    3196 -        def _on_test_info_button_pressed(self, event):
    3197                  pk = self._PRW_test.GetData()
    3198                  if pk is not None:
    3199                          tt = gmPathLab.cMeasurementType(aPK_obj = pk)
    3200                          search_term = '%s %s %s' % (
    3201                                  tt['name'],
    3202                                  tt['abbrev'],
    3203                                  gmTools.coalesce(tt['loinc'], '')
    3204                          )
    3205                  else:
    3206                          search_term = self._PRW_test.GetValue()
    3207
    3208                  search_term = search_term.replace(' ', '+')
    3209
    3210                  call_browser_on_measurement_type(measurement_type = search_term)
    3211          #--------------------------------------------------------
    3212 -        def _on_manage_episodes_button_pressed(self, event):
    3213                  event.Skip()
    3214                  gmEMRStructWidgets.manage_episodes(parent = self)
    3215          #--------------------------------------------------------
    3216          # internal helpers
    3217          #--------------------------------------------------------
    3218 -        def __update_units_context(self):
    3219
    3220                  if self._PRW_test.GetData() is None:
    3221                          self._PRW_units.unset_context(context = 'pk_type')
    3222                          self._PRW_units.unset_context(context = 'loinc')
    3223                          if self._PRW_test.GetValue().strip() == '':
    3224                                  self._PRW_units.unset_context(context = 'test_name')
    3225                          else:
    3226                                  self._PRW_units.set_context(context = 'test_name', val = self._PRW_test.GetValue().strip())
    3227                          return
    3228
    3229                  tt = self._PRW_test.GetData(as_instance = True)
    3230
    3231                  self._PRW_units.set_context(context = 'pk_type', val = tt['pk_test_type'])
    3232                  self._PRW_units.set_context(context = 'test_name', val = tt['name'])
    3233
    3234                  if tt['loinc'] is not None:
    3235                          self._PRW_units.set_context(context = 'loinc', val = tt['loinc'])
    3236
    3237                  # closest unit
    3238                  if self._PRW_units.GetValue().strip() == '':
    3239                          clin_when = self._DPRW_evaluated.GetData()
    3240                          if clin_when is None:
    3241                                  unit = tt.temporally_closest_unit
    3242                          else:
    3243                                  clin_when = clin_when.get_pydt()
    3244                                  unit = tt.get_temporally_closest_unit(timestamp = clin_when)
    3245                          if unit is None:
    3246                                  self._PRW_units.SetText('', unit, True)
    3247                          else:
    3248                                  self._PRW_units.SetText(unit, unit, True)
    3249
    3250          #--------------------------------------------------------
    3251 -        def __update_normal_range(self):
    3252                  unit = self._PRW_units.GetValue().strip()
    3253                  if unit == '':
    3254                          return
    3255                  if self._PRW_test.GetData() is None:
    3256                          return
    3257                  for ctrl in [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_normal_range, self._TCTRL_norm_ref_group]:
    3258                          if ctrl.GetValue().strip() != '':
    3259                                  return
    3260                  tt = self._PRW_test.GetData(as_instance = True)
    3261                  test_w_range = tt.get_temporally_closest_normal_range (
    3262                          unit,
    3263                          timestamp = self._DPRW_evaluated.GetData().get_pydt()
    3264                  )
    3265                  if test_w_range is None:
    3266                          return
    3267                  self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(test_w_range['val_normal_min'], '')))
    3268                  self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(test_w_range['val_normal_max'], '')))
    3269                  self._TCTRL_normal_range.SetValue(gmTools.coalesce(test_w_range['val_normal_range'], ''))
    3270                  self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(test_w_range['norm_ref_group'], ''))
    3271
    3272          #--------------------------------------------------------
    3273 -        def __update_clinical_range(self):
    3274                  unit = self._PRW_units.GetValue().strip()
    3275                  if unit == '':
    3276                          return
    3277                  if self._PRW_test.GetData() is None:
    3278                          return
    3279                  for ctrl in [self._TCTRL_target_min, self._TCTRL_target_max, self._TCTRL_target_range]:
    3280                          if ctrl.GetValue().strip() != '':
    3281                                  return
    3282                  tt = self._PRW_test.GetData(as_instance = True)
    3283                  test_w_range = tt.get_temporally_closest_target_range (
    3284                          unit,
    3285                          gmPerson.gmCurrentPatient().ID,
    3286                          timestamp = self._DPRW_evaluated.GetData().get_pydt()
    3287                  )
    3288                  if test_w_range is None:
    3289                          return
    3290                  self._TCTRL_target_min.SetValue(str(gmTools.coalesce(test_w_range['val_target_min'], '')))
    3291                  self._TCTRL_target_max.SetValue(str(gmTools.coalesce(test_w_range['val_target_max'], '')))
    3292                  self._TCTRL_target_range.SetValue(gmTools.coalesce(test_w_range['val_target_range'], ''))
    3293
    3294          #--------------------------------------------------------
    3295 -        def __refresh_loinc_info(self):
    3296
    3297                  self._TCTRL_loinc.SetValue('')
    3298
    3299                  if self._PRW_test.GetData() is None:
    3300                          return
    3301
    3302                  tt = self._PRW_test.GetData(as_instance = True)
    3303
    3304                  if tt['loinc'] is None:
    3305                          return
    3306
    3307                  info = gmLOINC.loinc2term(loinc = tt['loinc'])
    3308                  if len(info) == 0:
    3309                          self._TCTRL_loinc.SetValue('')
    3310                          return
    3311
    3312                  self._TCTRL_loinc.SetValue('%s: %s' % (tt['loinc'], info[0]))
    3313          #--------------------------------------------------------
    3314 -        def __refresh_previous_value(self):
    3315                  self._TCTRL_previous_value.SetValue('')
    3316                  # it doesn't make much sense to show the most
    3317                  # recent value when editing an existing one
    3318                  if self.data is not None:
    3319                          return
    3320                  if self._PRW_test.GetData() is None:
    3321                          return
    3322                  tt = self._PRW_test.GetData(as_instance = True)
    3323                  most_recent = tt.get_most_recent_results (
    3324                          no_of_results = 1,
    3325                          patient = gmPerson.gmCurrentPatient().ID
    3326                  )
    3327                  if most_recent is None:
    3328                          return
    3329                  self._TCTRL_previous_value.SetValue(_('%s ago: %s%s%s - %s%s') % (
    3330                          gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - most_recent['clin_when']),
    3331                          most_recent['unified_val'],
    3332                          most_recent['val_unit'],
    3333                          gmTools.coalesce(most_recent['abnormality_indicator'], '', ' (%s)'),
    3334                          most_recent['abbrev_tt'],
    3335                          gmTools.coalesce(most_recent.formatted_range, '', ' [%s]')
    3336                  ))
    3337                  self._TCTRL_previous_value.SetToolTip(most_recent.format (
    3338                          with_review = True,
    3339                          with_evaluation = False,
    3340                          with_ranges = True,
    3341                          with_episode = True,
    3342                          with_type_details=True
    3343                  ))
    3344
    3345  #================================================================
    3346  # measurement type handling
    3347  #================================================================
    3348 -def pick_measurement_types(parent=None, msg=None, right_column=None, picks=None):
    3349
    3350          if parent is None:
    3351                  parent = wx.GetApp().GetTopWindow()
    3352
    3353          if msg is None:
    3354                  msg = _('Pick the relevant measurement types.')
    3355
    3356          if right_column is None:
    3357                  right_columns = [_('Picked')]
    3358          else:
    3359                  right_columns = [right_column]
    3360
    3361          picker = gmListWidgets.cItemPickerDlg(parent, -1, msg = msg)
    3362          picker.set_columns(columns = [_('Known measurement types')], columns_right = right_columns)
    3363          types = gmPathLab.get_measurement_types(order_by = 'unified_abbrev')
    3364          picker.set_choices (
    3365                  choices = [
    3366                          '%s: %s%s' % (
    3367                                  t['unified_abbrev'],
    3368                                  t['unified_name'],
    3369                                  gmTools.coalesce(t['name_org'], '', ' (%s)')
    3370                          )
    3371                          for t in types
    3372                  ],
    3373                  data = types
    3374          )
    3375          if picks is not None:
    3376                  picker.set_picks (
    3377                          picks = [
    3378                                  '%s: %s%s' % (
    3379                                          p['unified_abbrev'],
    3380                                          p['unified_name'],
    3381                                          gmTools.coalesce(p['name_org'], '', ' (%s)')
    3382                                  )
    3383                                  for p in picks
    3384                          ],
    3385                          data = picks
    3386                  )
    3387          result = picker.ShowModal()
    3388
    3389          if result == wx.ID_CANCEL:
    3390                  picker.Destroy()
    3391                  return None
    3392
    3393          picks = picker.picks
    3394          picker.Destroy()
    3395          return picks
    3396
    3397  #----------------------------------------------------------------
    3398 -def manage_measurement_types(parent=None):
    3399
    3400          if parent is None:
    3401                  parent = wx.GetApp().GetTopWindow()
    3402
    3403          #------------------------------------------------------------
    3404          def edit(test_type=None):
    3405                  ea = cMeasurementTypeEAPnl(parent, -1, type = test_type)
    3406                  dlg = gmEditArea.cGenericEditAreaDlg2 (
    3407                          parent = parent,
    3408                          id = -1,
    3409                          edit_area = ea,
    3410                          single_entry = gmTools.bool2subst((test_type is None), False, True)
    3411                  )
    3412                  dlg.SetTitle(gmTools.coalesce(test_type, _('Adding measurement type'), _('Editing measurement type')))
    3413
    3414                  if dlg.ShowModal() == wx.ID_OK:
    3415                          dlg.Destroy()
    3416                          return True
    3417
    3418                  dlg.Destroy()
    3419                  return False
    3420
    3421          #------------------------------------------------------------
    3422          def delete(measurement_type):
    3423                  if measurement_type.in_use:
    3424                          gmDispatcher.send (
    3425                                  signal = 'statustext',
    3426                                  beep = True,
    3427                                  msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev'])
    3428                          )
    3429                          return False
    3430                  gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type'])
    3431                  return True
    3432
    3433          #------------------------------------------------------------
    3434          def get_tooltip(test_type):
    3435                  return test_type.format()
    3436
    3437          #------------------------------------------------------------
    3438          def manage_aggregates(test_type):
    3439                  manage_meta_test_types(parent = parent)
    3440                  return False
    3441
    3442          #------------------------------------------------------------
    3443          def manage_panels_of_type(test_type):
    3444                  if test_type['loinc'] is None:
    3445                          return False
    3446                  all_panels = gmPathLab.get_test_panels(order_by = 'description')
    3447                  curr_panels = test_type.test_panels
    3448                  if curr_panels is None:
    3449                          curr_panels = []
    3450                  panel_candidates = [ p for p in all_panels if p['pk_test_panel'] not in [
    3451                          c_pnl['pk_test_panel'] for c_pnl in curr_panels
    3452                  ] ]
    3453                  picker = gmListWidgets.cItemPickerDlg(parent, -1, title = 'Panels with [%s]' % test_type['abbrev'])
    3454                  picker.set_columns(['Panels available'], ['Panels [%s] is to be on' % test_type['abbrev']])
    3455                  picker.set_choices (
    3456                          choices = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in panel_candidates ],
    3457                          data = panel_candidates
    3458                  )
    3459                  picker.set_picks (
    3460                          picks = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in curr_panels ],
    3461                          data = curr_panels
    3462                  )
    3463                  exit_type = picker.ShowModal()
    3464                  if exit_type == wx.ID_CANCEL:
    3465                          return False
    3466
    3467                  # add picked panels which aren't currently in the panel list
    3468                  panels2add = [ p for p in picker.picks if p['pk_test_panel'] not in [
    3469                          c_pnl['pk_test_panel'] for c_pnl in curr_panels
    3470                  ] ]
    3471                  # remove unpicked panels off the current panel list
    3472                  panels2remove = [ p for p in curr_panels if p['pk_test_panel'] not in [
    3473                          picked_pnl['pk_test_panel'] for picked_pnl in picker.picks
    3474                  ] ]
    3475                  for new_panel in panels2add:
    3476                          new_panel.add_loinc(test_type['loinc'])
    3477                  for stale_panel in panels2remove:
    3478                          stale_panel.remove_loinc(test_type['loinc'])
    3479
    3480                  return True
    3481
    3482          #------------------------------------------------------------
    3483          def refresh(lctrl):
    3484                  mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev')
    3485                  items = [ [
    3486                          m['abbrev'],
    3487                          m['name'],
    3488                          gmTools.coalesce(m['reference_unit'], ''),
    3489                          gmTools.coalesce(m['loinc'], ''),
    3490                          gmTools.coalesce(m['comment_type'], ''),
    3491                          gmTools.coalesce(m['name_org'], '?'),
    3492                          gmTools.coalesce(m['comment_org'], ''),
    3493                          m['pk_test_type']
    3494                  ] for m in mtypes ]
    3495                  lctrl.set_string_items(items)
    3496                  lctrl.set_data(mtypes)
    3497
    3498          #------------------------------------------------------------
    3499          gmListWidgets.get_choices_from_list (
    3500                  parent = parent,
    3501                  caption = _('Measurement types.'),
    3502                  columns = [ _('Abbrev'), _('Name'), _('Unit'), _('LOINC'), _('Comment'), _('Org'), _('Comment'), '#' ],
    3503                  single_selection = True,
    3504                  refresh_callback = refresh,
    3505                  edit_callback = edit,
    3506                  new_callback = edit,
    3507                  delete_callback = delete,
    3508                  list_tooltip_callback = get_tooltip,
    3509                  left_extra_button = (_('%s &Aggregate') % gmTools.u_sum, _('Manage aggregations (%s) of tests into groups.') % gmTools.u_sum, manage_aggregates),
    3510                  middle_extra_button = (_('Select panels'), _('Select panels the focussed test type is to belong to.'), manage_panels_of_type)
    3511          )
    3512
    3513  #----------------------------------------------------------------
    3514 -class cMeasurementTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
    3515
    3516 -        def __init__(self, *args, **kwargs):
    3517
    3518                  query = """
    3519  SELECT DISTINCT ON (field_label)
    3520          pk_test_type AS data,
    3521          name
    3522                  || ' ('
    3523                  || coalesce (
    3524                          (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
    3525                          '%(in_house)s'
    3526                          )
    3527                  || ')'
    3528          AS field_label,
    3529          name
    3530                  || ' ('
    3531                  || abbrev || ', '
    3532                  || coalesce(abbrev_meta || ': ' || name_meta || ', ', '')
    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 list_label
    3539  FROM
    3540          clin.v_test_types c_vtt
    3541  WHERE
    3542          abbrev_meta %%(fragment_condition)s
    3543                  OR
    3544          name_meta %%(fragment_condition)s
    3545                  OR
    3546          abbrev %%(fragment_condition)s
    3547                  OR
    3548          name %%(fragment_condition)s
    3549  ORDER BY field_label
    3550  LIMIT 50""" % {'in_house': _('generic / in house lab')}
    3551
    3552                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    3553                  mp.setThresholds(1, 2, 4)
    3554                  mp.word_separators = '[ \t:@]+'
    3555                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    3556                  self.matcher = mp
    3557                  self.SetToolTip(_('Select the type of measurement.'))
    3558                  self.selection_only = False
    3559
    3560          #------------------------------------------------------------
    3561 -        def _data2instance(self):
    3562                  if self.GetData() is None:
    3563                          return None
    3564
    3565                  return gmPathLab.cMeasurementType(aPK_obj = self.GetData())
    3566
    3567  #----------------------------------------------------------------
    3568  from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl
    3569
    3570 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
    3571
    3572 -        def __init__(self, *args, **kwargs):
    3573
    3574                  try:
    3575                          data = kwargs['type']
    3576                          del kwargs['type']
    3577                  except KeyError:
    3578                          data = None
    3579
    3580                  wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl.__init__(self, *args, **kwargs)
    3581                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    3582                  self.mode = 'new'
    3583                  self.data = data
    3584                  if data is not None:
    3585                          self.mode = 'edit'
    3586
    3587                  self.__init_ui()
    3588
    3589          #----------------------------------------------------------------
    3590 -        def __init_ui(self):
    3591
    3592                  # name phraseweel
    3593                  query = """
    3594  select distinct on (name)
    3595          pk,
    3596          name
    3597  from clin.test_type
    3598  where
    3599          name %(fragment_condition)s
    3600  order by name
    3601  limit 50"""
    3602                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    3603                  mp.setThresholds(1, 2, 4)
    3604                  self._PRW_name.matcher = mp
    3605                  self._PRW_name.selection_only = False
    3606                  self._PRW_name.add_callback_on_lose_focus(callback = self._on_name_lost_focus)
    3607
    3608                  # abbreviation
    3609                  query = """
    3610  select distinct on (abbrev)
    3611          pk,
    3612          abbrev
    3613  from clin.test_type
    3614  where
    3615          abbrev %(fragment_condition)s
    3616  order by abbrev
    3617  limit 50"""
    3618                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    3619                  mp.setThresholds(1, 2, 3)
    3620                  self._PRW_abbrev.matcher = mp
    3621                  self._PRW_abbrev.selection_only = False
    3622
    3623                  # unit
    3624                  self._PRW_reference_unit.selection_only = False
    3625
    3626                  # loinc
    3627                  mp = gmLOINC.cLOINCMatchProvider()
    3628                  mp.setThresholds(1, 2, 4)
    3629                  #mp.print_queries = True
    3630                  #mp.word_separators = '[ \t:@]+'
    3631                  self._PRW_loinc.matcher = mp
    3632                  self._PRW_loinc.selection_only = False
    3633                  self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
    3634
    3635          #----------------------------------------------------------------
    3636 -        def _on_name_lost_focus(self):
    3637
    3638                  test = self._PRW_name.GetValue().strip()
    3639
    3640                  if test == '':
    3641                          self._PRW_reference_unit.unset_context(context = 'test_name')
    3642                          return
    3643
    3644                  self._PRW_reference_unit.set_context(context = 'test_name', val = test)
    3645
    3646          #----------------------------------------------------------------
    3647 -        def _on_loinc_lost_focus(self):
    3648                  loinc = self._PRW_loinc.GetData()
    3649
    3650                  if loinc is None:
    3651                          self._TCTRL_loinc_info.SetValue('')
    3652                          self._PRW_reference_unit.unset_context(context = 'loinc')
    3653                          return
    3654
    3655                  self._PRW_reference_unit.set_context(context = 'loinc', val = loinc)
    3656
    3657                  info = gmLOINC.loinc2term(loinc = loinc)
    3658                  if len(info) == 0:
    3659                          self._TCTRL_loinc_info.SetValue('')
    3660                          return
    3661
    3662                  self._TCTRL_loinc_info.SetValue(info[0])
    3663
    3664          #----------------------------------------------------------------
    3665          # generic Edit Area mixin API
    3666          #----------------------------------------------------------------
    3667 -        def _valid_for_save(self):
    3668
    3669                  has_errors = False
    3670                  for field in [self._PRW_name, self._PRW_abbrev, self._PRW_reference_unit]:
    3671                          if field.GetValue().strip() in ['', None]:
    3672                                  has_errors = True
    3673                                  field.display_as_valid(valid = False)
    3674                          else:
    3675                                  field.display_as_valid(valid = True)
    3676                          field.Refresh()
    3677
    3678                  return (not has_errors)
    3679
    3680          #----------------------------------------------------------------
    3681 -        def _save_as_new(self):
    3682
    3683                  pk_org = self._PRW_test_org.GetData()
    3684                  if pk_org is None:
    3685                          pk_org = gmPathLab.create_test_org (
    3686                                  name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), '')
    3687                          )['pk_test_org']
    3688
    3689                  tt = gmPathLab.create_measurement_type (
    3690                          lab = pk_org,
    3691                          abbrev = self._PRW_abbrev.GetValue().strip(),
    3692                          name = self._PRW_name.GetValue().strip(),
    3693                          unit = gmTools.coalesce (
    3694                                  self._PRW_reference_unit.GetData(),
    3695                                  self._PRW_reference_unit.GetValue()
    3696                          ).strip()
    3697                  )
    3698                  if self._PRW_loinc.GetData() is not None:
    3699                          tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), '')
    3700                  else:
    3701                          tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), '')
    3702                  tt['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), '')
    3703                  tt['pk_meta_test_type'] = self._PRW_meta_type.GetData()
    3704
    3705                  tt.save()
    3706
    3707                  self.data = tt
    3708
    3709                  return True
    3710          #----------------------------------------------------------------
    3711 -        def _save_as_update(self):
    3712
    3713                  pk_org = self._PRW_test_org.GetData()
    3714                  if pk_org is None:
    3715                          pk_org = gmPathLab.create_test_org (
    3716                                  name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), '')
    3717                          )['pk_test_org']
    3718
    3719                  self.data['pk_test_org'] = pk_org
    3720                  self.data['abbrev'] = self._PRW_abbrev.GetValue().strip()
    3721                  self.data['name'] = self._PRW_name.GetValue().strip()
    3722                  self.data['reference_unit'] = gmTools.coalesce (
    3723                          self._PRW_reference_unit.GetData(),
    3724                          self._PRW_reference_unit.GetValue()
    3725                  ).strip()
    3726                  old_loinc = self.data['loinc']
    3727                  if self._PRW_loinc.GetData() is not None:
    3728                          self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), '')
    3729                  else:
    3730                          self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), '')
    3731                  new_loinc = self.data['loinc']
    3732                  self.data['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), '')
    3733                  self.data['pk_meta_test_type'] = self._PRW_meta_type.GetData()
    3734                  self.data.save()
    3735
    3736                  # was it, AND can it be, on any panel ?
    3737                  if None not in [old_loinc, new_loinc]:
    3738                          # would it risk being dropped from any panel ?
    3739                          if new_loinc != old_loinc:
    3740                                  for panel in gmPathLab.get_test_panels(loincs = [old_loinc]):
    3741                                          pnl_loincs = panel.included_loincs
    3742                                          if new_loinc not in pnl_loincs:
    3743                                                  pnl_loincs.append(new_loinc)
    3744                                                  panel.included_loincs = pnl_loincs
    3745                                          # do not remove old_loinc as it may sit on another
    3746                                          # test type which we haven't removed it from yet
    3747
    3748                  return True
    3749
    3750          #----------------------------------------------------------------
    3751 -        def _refresh_as_new(self):
    3752                  self._PRW_name.SetText('', None, True)
    3753                  self._on_name_lost_focus()
    3754                  self._PRW_abbrev.SetText('', None, True)
    3755                  self._PRW_reference_unit.SetText('', None, True)
    3756                  self._PRW_loinc.SetText('', None, True)
    3757                  self._on_loinc_lost_focus()
    3758                  self._TCTRL_comment_type.SetValue('')
    3759                  self._PRW_test_org.SetText('', None, True)
    3760                  self._PRW_meta_type.SetText('', None, True)
    3761
    3762                  self._PRW_name.SetFocus()
    3763          #----------------------------------------------------------------
    3764 -        def _refresh_from_existing(self):
    3765                  self._PRW_name.SetText(self.data['name'], self.data['name'], True)
    3766                  self._on_name_lost_focus()
    3767                  self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True)
    3768                  self._PRW_reference_unit.SetText (
    3769                          gmTools.coalesce(self.data['reference_unit'], ''),
    3770                          self.data['reference_unit'],
    3771                          True
    3772                  )
    3773                  self._PRW_loinc.SetText (
    3774                          gmTools.coalesce(self.data['loinc'], ''),
    3775                          self.data['loinc'],
    3776                          True
    3777                  )
    3778                  self._on_loinc_lost_focus()
    3779                  self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], ''))
    3780                  self._PRW_test_org.SetText (
    3781                          gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
    3782                          self.data['pk_test_org'],
    3783                          True
    3784                  )
    3785                  if self.data['pk_meta_test_type'] is None:
    3786                          self._PRW_meta_type.SetText('', None, True)
    3787                  else:
    3788                          self._PRW_meta_type.SetText('%s: %s' % (self.data['abbrev_meta'], self.data['name_meta']), self.data['pk_meta_test_type'], True)
    3789
    3790                  self._PRW_name.SetFocus()
    3791          #----------------------------------------------------------------
    3792 -        def _refresh_as_new_from_existing(self):
    3793                  self._refresh_as_new()
    3794                  self._PRW_test_org.SetText (
    3795                          gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
    3796                          self.data['pk_test_org'],
    3797                          True
    3798                  )
    3799                  self._PRW_name.SetFocus()
    3800
    3801  #================================================================
    3802  _SQL_units_from_test_results = """
    3803          -- via clin.v_test_results.pk_type (for types already used in results)
    3804          SELECT
    3805                  val_unit AS data,
    3806                  val_unit AS field_label,
    3807                  val_unit || ' (' || name_tt || ')' AS list_label,
    3808                  1 AS rank
    3809          FROM
    3810                  clin.v_test_results
    3811          WHERE
    3812                  (
    3813                          val_unit %(fragment_condition)s
    3814                                  OR
    3815                          reference_unit %(fragment_condition)s
    3816                  )
    3817                  %(ctxt_type_pk)s
    3818                  %(ctxt_test_name)s
    3819  """
    3820
    3821  _SQL_units_from_test_types = """
    3822          -- via clin.test_type (for types not yet used in results)
    3823          SELECT
    3824                  reference_unit AS data,
    3825                  reference_unit AS field_label,
    3826                  reference_unit || ' (' || name || ')' AS list_label,
    3827                  2 AS rank
    3828          FROM
    3829                  clin.test_type
    3830          WHERE
    3831                  reference_unit %(fragment_condition)s
    3832                  %(ctxt_ctt)s
    3833  """
    3834
    3835  _SQL_units_from_loinc_ipcc = """
    3836          -- via ref.loinc.ipcc_units
    3837          SELECT
    3838                  ipcc_units AS data,
    3839                  ipcc_units AS field_label,
    3840                  ipcc_units || ' (LOINC.ipcc: ' || term || ')' AS list_label,
    3841                  3 AS rank
    3842          FROM
    3843                  ref.loinc
    3844          WHERE
    3845                  ipcc_units %(fragment_condition)s
    3846                  %(ctxt_loinc)s
    3847                  %(ctxt_loinc_term)s
    3848  """
    3849
    3850  _SQL_units_from_loinc_submitted = """
    3851          -- via ref.loinc.submitted_units
    3852          SELECT
    3853                  submitted_units AS data,
    3854                  submitted_units AS field_label,
    3855                  submitted_units || ' (LOINC.submitted:' || term || ')' AS list_label,
    3856                  3 AS rank
    3857          FROM
    3858                  ref.loinc
    3859          WHERE
    3860                  submitted_units %(fragment_condition)s
    3861                  %(ctxt_loinc)s
    3862                  %(ctxt_loinc_term)s
    3863  """
    3864
    3865  _SQL_units_from_loinc_example = """
    3866          -- via ref.loinc.example_units
    3867          SELECT
    3868                  example_units AS data,
    3869                  example_units AS field_label,
    3870                  example_units || ' (LOINC.example: ' || term || ')' AS list_label,
    3871                  3 AS rank
    3872          FROM
    3873                  ref.loinc
    3874          WHERE
    3875                  example_units %(fragment_condition)s
    3876                  %(ctxt_loinc)s
    3877                  %(ctxt_loinc_term)s
    3878  """
    3879
    3880  _SQL_units_from_substance_doses = """
    3881          -- via ref.v_substance_doses.unit
    3882          SELECT
    3883                  unit AS data,
    3884                  unit AS field_label,
    3885                  unit || ' (' || substance || ')' AS list_label,
    3886                  2 AS rank
    3887          FROM
    3888                  ref.v_substance_doses
    3889          WHERE
    3890                  unit %(fragment_condition)s
    3891                  %(ctxt_substance)s
    3892  """
    3893
    3894  _SQL_units_from_substance_doses2 = """
    3895          -- via ref.v_substance_doses.dose_unit
    3896          SELECT
    3897                  dose_unit AS data,
    3898                  dose_unit AS field_label,
    3899                  dose_unit || ' (' || substance || ')' AS list_label,
    3900                  2 AS rank
    3901          FROM
    3902                  ref.v_substance_doses
    3903          WHERE
    3904                  dose_unit %(fragment_condition)s
    3905                  %(ctxt_substance)s
    3906  """
    3907
    3908  #----------------------------------------------------------------
    3909 -class cUnitPhraseWheel(gmPhraseWheel.cPhraseWheel):
    3910
    3911 -        def __init__(self, *args, **kwargs):
    3912
    3913                  query = """
    3914  SELECT DISTINCT ON (data)
    3915          data,
    3916          field_label,
    3917          list_label
    3918  FROM (
    3919
    3920          SELECT
    3921                  data,
    3922                  field_label,
    3923                  list_label,
    3924                  rank
    3925          FROM (
    3926                  (%s) UNION ALL
    3927                  (%s) UNION ALL
    3928                  (%s) UNION ALL
    3929                  (%s) UNION ALL
    3930                  (%s) UNION ALL
    3931                  (%s) UNION ALL
    3932                  (%s)
    3933          ) AS all_matching_units
    3934          WHERE data IS NOT NULL
    3935          ORDER BY rank, list_label
    3936
    3937  ) AS ranked_matching_units
    3938  LIMIT 50""" % (
    3939                          _SQL_units_from_test_results,
    3940                          _SQL_units_from_test_types,
    3941                          _SQL_units_from_loinc_ipcc,
    3942                          _SQL_units_from_loinc_submitted,
    3943                          _SQL_units_from_loinc_example,
    3944                          _SQL_units_from_substance_doses,
    3945                          _SQL_units_from_substance_doses2
    3946                  )
    3947
    3948                  ctxt = {
    3949                          'ctxt_type_pk': {
    3950                                  'where_part': 'AND pk_test_type = %(pk_type)s',
    3951                                  'placeholder': 'pk_type'
    3952                          },
    3953                          'ctxt_test_name': {
    3954                                  'where_part': 'AND %(test_name)s IN (name_tt, name_meta, abbrev_meta)',
    3955                                  'placeholder': 'test_name'
    3956                          },
    3957                          'ctxt_ctt': {
    3958                                  'where_part': 'AND %(test_name)s IN (name, abbrev)',
    3959                                  'placeholder': 'test_name'
    3960                          },
    3961                          'ctxt_loinc': {
    3962                                  'where_part': 'AND code = %(loinc)s',
    3963                                  'placeholder': 'loinc'
    3964                          },
    3965                          'ctxt_loinc_term': {
    3966                                  'where_part': 'AND term ~* %(test_name)s',
    3967                                  'placeholder': 'test_name'
    3968                          },
    3969                          'ctxt_substance': {
    3970                                  'where_part': 'AND description ~* %(substance)s',
    3971                                  'placeholder': 'substance'
    3972                          }
    3973                  }
    3974
    3975                  mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = ctxt)
    3976                  mp.setThresholds(1, 2, 4)
    3977                  #mp.print_queries = True
    3978                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    3979                  self.matcher = mp
    3980                  self.SetToolTip(_('Select the desired unit for the amount or measurement.'))
    3981                  self.selection_only = False
    3982                  self.phrase_separators = '[;|]+'
    3983
    3984  #================================================================
    3985
    3986  #================================================================
    3987 -class cTestResultIndicatorPhraseWheel(gmPhraseWheel.cPhraseWheel):
    3988
    3989 -        def __init__(self, *args, **kwargs):
    3990
    3991                  query = """
    3992  select distinct abnormality_indicator,
    3993          abnormality_indicator, abnormality_indicator
    3994  from clin.v_test_results
    3995  where
    3996          abnormality_indicator %(fragment_condition)s
    3997  order by abnormality_indicator
    3998  limit 25"""
    3999
    4000                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    4001                  mp.setThresholds(1, 1, 2)
    4002                  mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"'
    4003                  mp.word_separators = '[ \t&:]+'
    4004                  gmPhraseWheel.cPhraseWheel.__init__ (
    4005                          self,
    4006                          *args,
    4007                          **kwargs
    4008                  )
    4009                  self.matcher = mp
    4010                  self.SetToolTip(_('Select an indicator for the level of abnormality.'))
    4011                  self.selection_only = False
    4012
    4013  #================================================================
    4014  # measurement org widgets / functions
    4015  #----------------------------------------------------------------
    4016 -def edit_measurement_org(parent=None, org=None):
    4017          ea = cMeasurementOrgEAPnl(parent, -1)
    4018          ea.data = org
    4019          ea.mode = gmTools.coalesce(org, 'new', 'edit')
    4020          dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea)
    4021          dlg.SetTitle(gmTools.coalesce(org, _('Adding new diagnostic org'), _('Editing diagnostic org')))
    4022          if dlg.ShowModal() == wx.ID_OK:
    4023                  dlg.Destroy()
    4024                  return True
    4025          dlg.Destroy()
    4026          return False
    4027  #----------------------------------------------------------------
    4028 -def manage_measurement_orgs(parent=None, msg=None):
    4029
    4030          if parent is None:
    4031                  parent = wx.GetApp().GetTopWindow()
    4032
    4033          #------------------------------------------------------------
    4034          def edit(org=None):
    4035                  return edit_measurement_org(parent = parent, org = org)
    4036          #------------------------------------------------------------
    4037          def refresh(lctrl):
    4038                  orgs = gmPathLab.get_test_orgs()
    4039                  lctrl.set_string_items ([
    4040                          (o['unit'], o['organization'], gmTools.coalesce(o['test_org_contact'], ''), gmTools.coalesce(o['comment'], ''), o['pk_test_org'])
    4041                          for o in orgs
    4042                  ])
    4043                  lctrl.set_data(orgs)
    4044          #------------------------------------------------------------
    4045          def delete(test_org):
    4046                  gmPathLab.delete_test_org(test_org = test_org['pk_test_org'])
    4047                  return True
    4048          #------------------------------------------------------------
    4049          if msg is None:
    4050                  msg = _('\nThese are the diagnostic orgs (path labs etc) currently defined in GNUmed.\n\n')
    4051
    4052          return gmListWidgets.get_choices_from_list (
    4053                  parent = parent,
    4054                  msg = msg,
    4055                  caption = _('Showing diagnostic orgs.'),
    4056                  columns = [_('Name'), _('Organization'), _('Contact'), _('Comment'), '#'],
    4057                  single_selection = True,
    4058                  refresh_callback = refresh,
    4059                  edit_callback = edit,
    4060                  new_callback = edit,
    4061                  delete_callback = delete
    4062          )
    4063
    4064  #----------------------------------------------------------------
    4065  from Gnumed.wxGladeWidgets import wxgMeasurementOrgEAPnl
    4066
    4067 -class cMeasurementOrgEAPnl(wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl, gmEditArea.cGenericEditAreaMixin):
    4068
    4069 -        def __init__(self, *args, **kwargs):
    4070
    4071                  try:
    4072                          data = kwargs['org']
    4073                          del kwargs['org']
    4074                  except KeyError:
    4075                          data = None
    4076
    4077                  wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl.__init__(self, *args, **kwargs)
    4078                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    4079
    4080                  self.mode = 'new'
    4081                  self.data = data
    4082                  if data is not None:
    4083                          self.mode = 'edit'
    4084
    4085                  #self.__init_ui()
    4086          #----------------------------------------------------------------
    4087  #       def __init_ui(self):
    4088  #               # adjust phrasewheels etc
    4089          #----------------------------------------------------------------
    4090          # generic Edit Area mixin API
    4091          #----------------------------------------------------------------
    4092 -        def _valid_for_save(self):
    4093                  has_errors = False
    4094                  if self._PRW_org_unit.GetData() is None:
    4095                          if self._PRW_org_unit.GetValue().strip() == '':
    4096                                  has_errors = True
    4097                                  self._PRW_org_unit.display_as_valid(valid = False)
    4098                          else:
    4099                                  self._PRW_org_unit.display_as_valid(valid = True)
    4100                  else:
    4101                          self._PRW_org_unit.display_as_valid(valid = True)
    4102
    4103                  return (not has_errors)
    4104          #----------------------------------------------------------------
    4105 -        def _save_as_new(self):
    4106                  data = gmPathLab.create_test_org (
    4107                          name = self._PRW_org_unit.GetValue().strip(),
    4108                          comment = self._TCTRL_comment.GetValue().strip(),
    4109                          pk_org_unit = self._PRW_org_unit.GetData()
    4110                  )
    4111                  data['test_org_contact'] = self._TCTRL_contact.GetValue().strip()
    4112                  data.save()
    4113                  self.data = data
    4114                  return True
    4115          #----------------------------------------------------------------
    4116 -        def _save_as_update(self):
    4117                  # get or create the org unit
    4118                  name = self._PRW_org_unit.GetValue().strip()
    4119                  org = gmOrganization.org_exists(organization = name)
    4120                  if org is None:
    4121                          org = gmOrganization.create_org (
    4122                                  organization = name,
    4123                                  category = 'Laboratory'
    4124                          )
    4125                  org_unit = gmOrganization.create_org_unit (
    4126                          pk_organization = org['pk_org'],
    4127                          unit = name
    4128                  )
    4129                  # update test_org fields
    4130                  self.data['pk_org_unit'] = org_unit['pk_org_unit']
    4131                  self.data['test_org_contact'] = self._TCTRL_contact.GetValue().strip()
    4132                  self.data['comment'] = self._TCTRL_comment.GetValue().strip()
    4133                  self.data.save()
    4134                  return True
    4135          #----------------------------------------------------------------
    4136 -        def _refresh_as_new(self):
    4137                  self._PRW_org_unit.SetText(value = '', data = None)
    4138                  self._TCTRL_contact.SetValue('')
    4139                  self._TCTRL_comment.SetValue('')
    4140          #----------------------------------------------------------------
    4141 -        def _refresh_from_existing(self):
    4142                  self._PRW_org_unit.SetText(value = self.data['unit'], data = self.data['pk_org_unit'])
    4143                  self._TCTRL_contact.SetValue(gmTools.coalesce(self.data['test_org_contact'], ''))
    4144                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
    4145          #----------------------------------------------------------------
    4146 -        def _refresh_as_new_from_existing(self):
    4147                  self._refresh_as_new()
    4148          #----------------------------------------------------------------
    4149 -        def _on_manage_orgs_button_pressed(self, event):
    4150                  gmOrganizationWidgets.manage_orgs(parent = self)
    4151
    4152  #----------------------------------------------------------------
    4153 -class cMeasurementOrgPhraseWheel(gmPhraseWheel.cPhraseWheel):
    4154
    4155 -        def __init__(self, *args, **kwargs):
    4156
    4157                  query = """
    4158  SELECT DISTINCT ON (list_label)
    4159          pk_test_org AS data,
    4160          unit || ' (' || organization || ')' AS field_label,
    4161          unit || ' @ ' || organization AS list_label
    4162  FROM clin.v_test_orgs
    4163  WHERE
    4164          unit %(fragment_condition)s
    4165                  OR
    4166          organization %(fragment_condition)s
    4167  ORDER BY list_label
    4168  LIMIT 50"""
    4169                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    4170                  mp.setThresholds(1, 2, 4)
    4171                  #mp.word_separators = '[ \t:@]+'
    4172                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    4173                  self.matcher = mp
    4174                  self.SetToolTip(_('The name of the path lab/diagnostic organisation.'))
    4175                  self.selection_only = False
    4176          #------------------------------------------------------------
    4177 -        def _create_data(self):
    4178                  if self.GetData() is not None:
    4179                          _log.debug('data already set, not creating')
    4180                          return
    4181
    4182                  if self.GetValue().strip() == '':
    4183                          _log.debug('cannot create new lab, missing name')
    4184                          return
    4185
    4186                  lab = gmPathLab.create_test_org(name = self.GetValue().strip())
    4187                  self.SetText(value = lab['unit'], data = lab['pk_test_org'])
    4188                  return
    4189          #------------------------------------------------------------
    4190 -        def _data2instance(self):
    4191                  return gmPathLab.cTestOrg(aPK_obj = self.GetData())
    4192
    4193  #================================================================
    4194  # Meta test type widgets
    4195  #----------------------------------------------------------------
    4196 -def edit_meta_test_type(parent=None, meta_test_type=None):
    4197          ea = cMetaTestTypeEAPnl(parent, -1)
    4198          ea.data = meta_test_type
    4199          ea.mode = gmTools.coalesce(meta_test_type, 'new', 'edit')
    4200          dlg = gmEditArea.cGenericEditAreaDlg2 (
    4201                  parent = parent,
    4202                  id = -1,
    4203                  edit_area = ea,
    4204                  single_entry = gmTools.bool2subst((meta_test_type is None), False, True)
    4205          )
    4206          dlg.SetTitle(gmTools.coalesce(meta_test_type, _('Adding new meta test type'), _('Editing meta test type')))
    4207          if dlg.ShowModal() == wx.ID_OK:
    4208                  dlg.Destroy()
    4209                  return True
    4210          dlg.Destroy()
    4211          return False
    4212
    4213  #----------------------------------------------------------------
    4214 -def manage_meta_test_types(parent=None):
    4215
    4216          if parent is None:
    4217                  parent = wx.GetApp().GetTopWindow()
    4218
    4219          #------------------------------------------------------------
    4220          def edit(meta_test_type=None):
    4221                  return edit_meta_test_type(parent = parent, meta_test_type = meta_test_type)
    4222          #------------------------------------------------------------
    4223          def delete(meta_test_type):
    4224                  gmPathLab.delete_meta_type(meta_type = meta_test_type['pk'])
    4225                  return True
    4226          #----------------------------------------
    4227          def get_tooltip(data):
    4228                  if data is None:
    4229                          return None
    4230                  return data.format(with_tests = True)
    4231          #------------------------------------------------------------
    4232          def refresh(lctrl):
    4233                  mtts = gmPathLab.get_meta_test_types()
    4234                  items = [ [
    4235                          m['abbrev'],
    4236                          m['name'],
    4237                          gmTools.coalesce(m['loinc'], ''),
    4238                          gmTools.coalesce(m['comment'], ''),
    4239                          m['pk']
    4240                  ] for m in mtts ]
    4241                  lctrl.set_string_items(items)
    4242                  lctrl.set_data(mtts)
    4243          #----------------------------------------
    4244
    4245          msg = _(
    4246                  '\n'
    4247                  'These are the meta test types currently defined in GNUmed.\n'
    4248                  '\n'
    4249                  'Meta test types allow you to aggregate several actual test types used\n'
    4250                  'by pathology labs into one logical type.\n'
    4251                  '\n'
    4252                  'This is useful for grouping together results of tests which come under\n'
    4253                  'different names but really are the same thing. This often happens when\n'
    4254                  'you switch labs or the lab starts using another test method.\n'
    4255          )
    4256
    4257          gmListWidgets.get_choices_from_list (
    4258                  parent = parent,
    4259                  msg = msg,
    4260                  caption = _('Showing meta test types.'),
    4261                  columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), '#'],
    4262                  single_selection = True,
    4263                  list_tooltip_callback = get_tooltip,
    4264                  edit_callback = edit,
    4265                  new_callback = edit,
    4266                  delete_callback = delete,
    4267                  refresh_callback = refresh
    4268          )
    4269
    4270  #----------------------------------------------------------------
    4271 -class cMetaTestTypePRW(gmPhraseWheel.cPhraseWheel):
    4272
    4273 -        def __init__(self, *args, **kwargs):
    4274
    4275                  query = """
    4276  SELECT DISTINCT ON (field_label)
    4277          c_mtt.pk
    4278                  AS data,
    4279          c_mtt.abbrev || ': ' || name
    4280                  AS field_label,
    4281          c_mtt.abbrev || ': ' || name
    4282                  ||      coalesce (
    4283                                  ' (' || c_mtt.comment || ')',
    4284                                  ''
    4285                          )
    4286                  ||      coalesce (
    4287                                  ', LOINC: ' || c_mtt.loinc,
    4288                                  ''
    4289                          )
    4290          AS list_label
    4291  FROM
    4292          clin.meta_test_type c_mtt
    4293  WHERE
    4294          abbrev %(fragment_condition)s
    4295                  OR
    4296          name %(fragment_condition)s
    4297                  OR
    4298          loinc %(fragment_condition)s
    4299  ORDER BY field_label
    4300  LIMIT 50"""
    4301
    4302                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    4303                  mp.setThresholds(1, 2, 4)
    4304                  mp.word_separators = '[ \t:@]+'
    4305                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    4306                  self.matcher = mp
    4307                  self.SetToolTip(_('Select the meta test type.'))
    4308                  self.selection_only = True
    4309          #------------------------------------------------------------
    4310 -        def _data2instance(self):
    4311                  if self.GetData() is None:
    4312                          return None
    4313
    4314                  return gmPathLab.cMetaTestType(aPK_obj = self.GetData())
    4315
    4316  #----------------------------------------------------------------
    4317  from Gnumed.wxGladeWidgets import wxgMetaTestTypeEAPnl
    4318
    4319 -class cMetaTestTypeEAPnl(wxgMetaTestTypeEAPnl.wxgMetaTestTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
    4320
    4321 -        def __init__(self, *args, **kwargs):
    4322
    4323                  try:
    4324                          data = kwargs['meta_test_type']
    4325                          del kwargs['meta_test_type']
    4326                  except KeyError:
    4327                          data = None
    4328
    4329                  wxgMetaTestTypeEAPnl.wxgMetaTestTypeEAPnl.__init__(self, *args, **kwargs)
    4330                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    4331
    4332                  # Code using this mixin should set mode and data
    4333                  # after instantiating the class:
    4334                  self.mode = 'new'
    4335                  self.data = data
    4336                  if data is not None:
    4337                          self.mode = 'edit'
    4338
    4339                  self.__init_ui()
    4340          #----------------------------------------------------------------
    4341 -        def __init_ui(self):
    4342                  # loinc
    4343                  mp = gmLOINC.cLOINCMatchProvider()
    4344                  mp.setThresholds(1, 2, 4)
    4345                  #mp.print_queries = True
    4346                  #mp.word_separators = '[ \t:@]+'
    4347                  self._PRW_loinc.matcher = mp
    4348                  self._PRW_loinc.selection_only = False
    4349                  self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
    4350
    4351          #----------------------------------------------------------------
    4352          # generic Edit Area mixin API
    4353          #----------------------------------------------------------------
    4354 -        def _valid_for_save(self):
    4355
    4356                  validity = True
    4357
    4358                  if self._PRW_abbreviation.GetValue().strip() == '':
    4359                          validity = False
    4360                          self._PRW_abbreviation.display_as_valid(False)
    4361                          self.status_message = _('Missing abbreviation for meta test type.')
    4362                          self._PRW_abbreviation.SetFocus()
    4363                  else:
    4364                          self._PRW_abbreviation.display_as_valid(True)
    4365
    4366                  if self._PRW_name.GetValue().strip() == '':
    4367                          validity = False
    4368                          self._PRW_name.display_as_valid(False)
    4369                          self.status_message = _('Missing name for meta test type.')
    4370                          self._PRW_name.SetFocus()
    4371                  else:
    4372                          self._PRW_name.display_as_valid(True)
    4373
    4374                  return validity
    4375          #----------------------------------------------------------------
    4376 -        def _save_as_new(self):
    4377
    4378                  # save the data as a new instance
    4379                  data = gmPathLab.create_meta_type (
    4380                          name = self._PRW_name.GetValue().strip(),
    4381                          abbreviation = self._PRW_abbreviation.GetValue().strip(),
    4382                          return_existing = False
    4383                  )
    4384                  if data is None:
    4385                          self.status_message = _('This meta test type already exists.')
    4386                          return False
    4387                  data['loinc'] = self._PRW_loinc.GetData()
    4388                  data['comment'] = self._TCTRL_comment.GetValue().strip()
    4389                  data.save()
    4390                  self.data = data
    4391                  return True
    4392          #----------------------------------------------------------------
    4393 -        def _save_as_update(self):
    4394                  self.data['name'] = self._PRW_name.GetValue().strip()
    4395                  self.data['abbrev'] = self._PRW_abbreviation.GetValue().strip()
    4396                  self.data['loinc'] = self._PRW_loinc.GetData()
    4397                  self.data['comment'] = self._TCTRL_comment.GetValue().strip()
    4398                  self.data.save()
    4399                  return True
    4400          #----------------------------------------------------------------
    4401 -        def _refresh_as_new(self):
    4402                  self._PRW_name.SetText('', None)
    4403                  self._PRW_abbreviation.SetText('', None)
    4404                  self._PRW_loinc.SetText('', None)
    4405                  self._TCTRL_loinc_info.SetValue('')
    4406                  self._TCTRL_comment.SetValue('')
    4407                  self._LBL_member_detail.SetLabel('')
    4408
    4409                  self._PRW_name.SetFocus()
    4410          #----------------------------------------------------------------
    4411 -        def _refresh_as_new_from_existing(self):
    4412                  self._refresh_as_new()
    4413          #----------------------------------------------------------------
    4414 -        def _refresh_from_existing(self):
    4415                  self._PRW_name.SetText(self.data['name'], self.data['pk'])
    4416                  self._PRW_abbreviation.SetText(self.data['abbrev'], self.data['abbrev'])
    4417                  self._PRW_loinc.SetText(gmTools.coalesce(self.data['loinc'], ''), self.data['loinc'])
    4418                  self.__refresh_loinc_info()
    4419                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
    4420                  self.__refresh_members()
    4421
    4422                  self._PRW_name.SetFocus()
    4423          #----------------------------------------------------------------
    4424          # event handlers
    4425          #----------------------------------------------------------------
    4426 -        def _on_loinc_lost_focus(self):
    4427                  self.__refresh_loinc_info()
    4428          #----------------------------------------------------------------
    4429          # internal helpers
    4430          #----------------------------------------------------------------
    4431 -        def __refresh_loinc_info(self):
    4432                  loinc = self._PRW_loinc.GetData()
    4433
    4434                  if loinc is None:
    4435                          self._TCTRL_loinc_info.SetValue('')
    4436                          return
    4437
    4438                  info = gmLOINC.loinc2term(loinc = loinc)
    4439                  if len(info) == 0:
    4440                          self._TCTRL_loinc_info.SetValue('')
    4441                          return
    4442
    4443                  self._TCTRL_loinc_info.SetValue(info[0])
    4444          #----------------------------------------------------------------
    4445 -        def __refresh_members(self):
    4446                  if self.data is None:
    4447                          self._LBL_member_detail.SetLabel('')
    4448                          return
    4449
    4450                  types = self.data.included_test_types
    4451                  if len(types) == 0:
    4452                          self._LBL_member_detail.SetLabel('')
    4453                          return
    4454
    4455                  lines = []
    4456                  for tt in types:
    4457                          lines.append('%s (%s%s) [#%s] @ %s' % (
    4458                                  tt['name'],
    4459                                  tt['abbrev'],
    4460                                  gmTools.coalesce(tt['loinc'], '', ', LOINC: %s'),
    4461                                  tt['pk_test_type'],
    4462                                  tt['name_org']
    4463                          ))
    4464                  self._LBL_member_detail.SetLabel('\n'.join(lines))
    4465
    4466  #================================================================
    4467  # test panel handling
    4468  #================================================================
    4469 -def edit_test_panel(parent=None, test_panel=None):
    4470          ea = cTestPanelEAPnl(parent, -1)
    4471          ea.data = test_panel
    4472          ea.mode = gmTools.coalesce(test_panel, 'new', 'edit')
    4473          dlg = gmEditArea.cGenericEditAreaDlg2 (
    4474                  parent = parent,
    4475                  id = -1,
    4476                  edit_area = ea,
    4477                  single_entry = gmTools.bool2subst((test_panel is None), False, True)
    4478          )
    4479          dlg.SetTitle(gmTools.coalesce(test_panel, _('Adding new test panel'), _('Editing test panel')))
    4480          if dlg.ShowModal() == wx.ID_OK:
    4481                  dlg.Destroy()
    4482                  return True
    4483          dlg.Destroy()
    4484          return False
    4485
    4486  #----------------------------------------------------------------
    4487 -def manage_test_panels(parent=None):
    4488
    4489          if parent is None:
    4490                  parent = wx.GetApp().GetTopWindow()
    4491
    4492          #------------------------------------------------------------
    4493          def edit(test_panel=None):
    4494                  return edit_test_panel(parent = parent, test_panel = test_panel)
    4495          #------------------------------------------------------------
    4496          def delete(test_panel):
    4497                  gmPathLab.delete_test_panel(pk = test_panel['pk_test_panel'])
    4498                  return True
    4499          #------------------------------------------------------------
    4500          def get_tooltip(test_panel):
    4501                  return test_panel.format()
    4502          #------------------------------------------------------------
    4503          def refresh(lctrl):
    4504                  panels = gmPathLab.get_test_panels(order_by = 'description')
    4505                  items = [ [
    4506                          p['description'],
    4507                          gmTools.coalesce(p['comment'], ''),
    4508                          p['pk_test_panel']
    4509                  ] for p in panels ]
    4510                  lctrl.set_string_items(items)
    4511                  lctrl.set_data(panels)
    4512          #------------------------------------------------------------
    4513          gmListWidgets.get_choices_from_list (
    4514                  parent = parent,
    4515                  caption = 'GNUmed: ' + _('Test panels list'),
    4516                  columns = [ _('Name'), _('Comment'), '#' ],
    4517                  single_selection = True,
    4518                  refresh_callback = refresh,
    4519                  edit_callback = edit,
    4520                  new_callback = edit,
    4521                  delete_callback = delete,
    4522                  list_tooltip_callback = get_tooltip
    4523          )
    4524
    4525  #----------------------------------------------------------------
    4526 -class cTestPanelPRW(gmPhraseWheel.cPhraseWheel):
    4527
    4528 -        def __init__(self, *args, **kwargs):
    4529                  query = """
    4530  SELECT
    4531          pk_test_panel
    4532                  AS data,
    4533          description
    4534                  AS field_label,
    4535          description
    4536                  AS list_label
    4537  FROM
    4538          clin.v_test_panels
    4539  WHERE
    4540          description %(fragment_condition)s
    4541  ORDER BY field_label
    4542  LIMIT 30"""
    4543                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    4544                  mp.setThresholds(1, 2, 4)
    4545                  #mp.word_separators = '[ \t:@]+'
    4546                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    4547                  self.matcher = mp
    4548                  self.SetToolTip(_('Select a test panel.'))
    4549                  self.selection_only = True
    4550          #------------------------------------------------------------
    4551 -        def _data2instance(self):
    4552                  if self.GetData() is None:
    4553                          return None
    4554                  return gmPathLab.cTestPanel(aPK_obj = self.GetData())
    4555          #------------------------------------------------------------
    4556 -        def _get_data_tooltip(self):
    4557                  if self.GetData() is None:
    4558                          return None
    4559                  return gmPathLab.cTestPanel(aPK_obj = self.GetData()).format()
    4560
    4561  #====================================================================
    4562  from Gnumed.wxGladeWidgets import wxgTestPanelEAPnl
    4563
    4564 -class cTestPanelEAPnl(wxgTestPanelEAPnl.wxgTestPanelEAPnl, gmEditArea.cGenericEditAreaMixin):
    4565
    4566 -        def __init__(self, *args, **kwargs):
    4567
    4568                  try:
    4569                          data = kwargs['panel']
    4570                          del kwargs['panel']
    4571                  except KeyError:
    4572                          data = None
    4573
    4574                  wxgTestPanelEAPnl.wxgTestPanelEAPnl.__init__(self, *args, **kwargs)
    4575                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    4576
    4577                  self.__loincs = None
    4578
    4579                  self.mode = 'new'
    4580                  self.data = data
    4581                  if data is not None:
    4582                          self.mode = 'edit'
    4583
    4584                  self.__init_ui()
    4585
    4586          #----------------------------------------------------------------
    4587 -        def __init_ui(self):
    4588                  self._LCTRL_loincs.set_columns([_('LOINC'), _('Term'), _('Units')])
    4589                  self._LCTRL_loincs.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
    4590                  #self._LCTRL_loincs.set_resize_column(column = 2)
    4591                  self._LCTRL_loincs.delete_callback = self._remove_loincs_from_list
    4592                  self.__refresh_loinc_list()
    4593
    4594                  self._PRW_loinc.final_regex = r'.*'
    4595                  self._PRW_loinc.add_callback_on_selection(callback = self._on_loinc_selected)
    4596
    4597          #----------------------------------------------------------------
    4598 -        def __refresh_loinc_list(self):
    4599                  self._LCTRL_loincs.remove_items_safely()
    4600                  if self.__loincs is None:
    4601                          if self.data is None:
    4602                                  return
    4603                          self.__loincs = self.data['loincs']
    4604
    4605                  items = []
    4606                  for loinc in self.__loincs:
    4607                          loinc_detail = gmLOINC.loinc2data(loinc = loinc)
    4608                          if loinc_detail is None:
    4609                                  # check for test type with this pseudo loinc
    4610                                  ttypes = gmPathLab.get_measurement_types(loincs = [loinc])
    4611                                  if len(ttypes) == 0:
    4612                                          items.append([loinc, _('LOINC not found'), ''])
    4613                                  else:
    4614                                          for tt in ttypes:
    4615                                                  items.append([loinc, _('not a LOINC') + u'; %(name)s @ %(name_org)s [#%(pk_test_type)s]' % tt, ''])
    4616                                  continue
    4617                          items.append ([
    4618                                  loinc,
    4619                                  loinc_detail['term'],
    4620                                  gmTools.coalesce(loinc_detail['example_units'], '', '%s')
    4621                          ])
    4622
    4623                  self._LCTRL_loincs.set_string_items(items)
    4624                  self._LCTRL_loincs.set_column_widths()
    4625
    4626          #----------------------------------------------------------------
    4627          # generic Edit Area mixin API
    4628          #----------------------------------------------------------------
    4629 -        def _valid_for_save(self):
    4630                  validity = True
    4631
    4632                  if self.__loincs is None:
    4633                          if self.data is not None:
    4634                                  self.__loincs = self.data['loincs']
    4635
    4636                  if self.__loincs is None:
    4637                          # not fatal despite panel being useless
    4638                          self.status_message = _('No LOINC codes selected.')
    4639                          self._PRW_loinc.SetFocus()
    4640
    4641                  if self._TCTRL_description.GetValue().strip() == '':
    4642                          validity = False
    4643                          self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False)
    4644                          self._TCTRL_description.SetFocus()
    4645                  else:
    4646                          self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True)
    4647
    4648                  return validity
    4649
    4650          #----------------------------------------------------------------
    4651 -        def _save_as_new(self):
    4652                  data = gmPathLab.create_test_panel(description = self._TCTRL_description.GetValue().strip())
    4653                  data['comment'] = self._TCTRL_comment.GetValue().strip()
    4654                  data.save()
    4655                  if self.__loincs is not None:
    4656                          data.included_loincs = self.__loincs
    4657                  self.data = data
    4658                  return True
    4659
    4660          #----------------------------------------------------------------
    4661 -        def _save_as_update(self):
    4662                  self.data['description'] = self._TCTRL_description.GetValue().strip()
    4663                  self.data['comment'] = self._TCTRL_comment.GetValue().strip()
    4664                  self.data.save()
    4665                  if self.__loincs is not None:
    4666                          self.data.included_loincs = self.__loincs
    4667                  return True
    4668
    4669          #----------------------------------------------------------------
    4670 -        def _refresh_as_new(self):
    4671                  self._TCTRL_description.SetValue('')
    4672                  self._TCTRL_comment.SetValue('')
    4673                  self._PRW_loinc.SetText('', None)
    4674                  self._LBL_loinc.SetLabel('')
    4675                  self.__loincs = None
    4676                  self.__refresh_loinc_list()
    4677
    4678                  self._TCTRL_description.SetFocus()
    4679
    4680          #----------------------------------------------------------------
    4681 -        def _refresh_as_new_from_existing(self):
    4682                  self._refresh_as_new()
    4683
    4684          #----------------------------------------------------------------
    4685 -        def _refresh_from_existing(self):
    4686                  self._TCTRL_description.SetValue(self.data['description'])
    4687                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
    4688                  self._PRW_loinc.SetText('', None)
    4689                  self._LBL_loinc.SetLabel('')
    4690                  self.__loincs = self.data['loincs']
    4691                  self.__refresh_loinc_list()
    4692
    4693                  self._PRW_loinc.SetFocus()
    4694
    4695          #----------------------------------------------------------------
    4696          # event handlers
    4697          #----------------------------------------------------------------
    4698 -        def _on_loinc_selected(self, loinc):
    4699                  loinc = self._PRW_loinc.GetData()
    4700                  if loinc is None:
    4701                          self._LBL_loinc.SetLabel('')
    4702                          return
    4703                  loinc_detail = gmLOINC.loinc2data(loinc = loinc)
    4704                  if loinc_detail is None:
    4705                          loinc_str = _('no LOINC details found')
    4706                  else:
    4707                          loinc_str = '%s: %s%s' % (
    4708                                  loinc,
    4709                                  loinc_detail['term'],
    4710                                  gmTools.coalesce(loinc_detail['example_units'], '', ' (%s)')
    4711                          )
    4712                  self._LBL_loinc.SetLabel(loinc_str)
    4713
    4714          #----------------------------------------------------------------
    4715 -        def _on_add_loinc_button_pressed(self, event):
    4716                  event.Skip()
    4717
    4718                  loinc = self._PRW_loinc.GetData()
    4719                  if loinc is None:
    4720                          loinc = self._PRW_loinc.GetValue().strip()
    4721                  if loinc.strip() == '':
    4722                          return
    4723
    4724                  if self.__loincs is None:
    4725                          self.__loincs = [loinc]
    4726                  else:
    4727                          if loinc in self.__loincs:
    4728                                  return
    4729                          self.__loincs.append(loinc)
    4730
    4731                  self.__refresh_loinc_list()
    4732                  self._PRW_loinc.SetText('', None)
    4733                  self._LBL_loinc.SetLabel('')
    4734
    4735                  self._PRW_loinc.SetFocus()
    4736
    4737          #----------------------------------------------------------------
    4738 -        def _on_remove_loinc_button_pressed(self, event):
    4739                  event.Skip()
    4740                  self._remove_loincs_from_list()
    4741
    4742          #----------------------------------------------------------------
    4743 -        def _remove_loincs_from_list(self):
    4744                  loincs2remove = self._LCTRL_loincs.selected_item_data
    4745                  if loincs2remove is None:
    4746                          return
    4747                  for loinc in loincs2remove:
    4748                          try:
    4749                                  while True:
    4750                                          self.__loincs.remove(loinc[0])
    4751                          except ValueError:
    4752                                  pass
    4753                  self.__refresh_loinc_list()
    4754
    4755  #================================================================
    4756  # main
    4757  #----------------------------------------------------------------
    4758  if __name__ == '__main__':
    4759
    4760          from Gnumed.pycommon import gmLog2
    4761          from Gnumed.wxpython import gmPatSearchWidgets
    4762
    4763          gmI18N.activate_locale()
    4764          gmI18N.install_domain()
    4765          gmDateTime.init()
    4766
    4767          #------------------------------------------------------------
    4768 -        def test_grid():
    4769                  pat = gmPersonSearch.ask_for_patient()
    4770                  app = wx.PyWidgetTester(size = (500, 300))
    4771                  lab_grid = cMeasurementsGrid(app.frame, -1)
    4772                  lab_grid.patient = pat
    4773                  app.frame.Show()
    4774                  app.MainLoop()
    4775          #------------------------------------------------------------
    4776 -        def test_test_ea_pnl():
    4777                  pat = gmPersonSearch.ask_for_patient()
    4778                  gmPatSearchWidgets.set_active_patient(patient=pat)
    4779                  app = wx.PyWidgetTester(size = (500, 300))
    4780                  ea = cMeasurementEditAreaPnl(app.frame, -1)
    4781                  app.frame.Show()
    4782                  app.MainLoop()
    4783          #------------------------------------------------------------
    4784  #       def test_primary_care_vitals_pnl():
    4785  #               app = wx.PyWidgetTester(size = (500, 300))
    4786  #               pnl = wxgPrimaryCareVitalsInputPnl.wxgPrimaryCareVitalsInputPnl(app.frame, -1)
    4787  #               app.frame.Show()
    4788  #               app.MainLoop()
    4789          #------------------------------------------------------------
    4790          if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
    4791                  #test_grid()
    4792                  test_test_ea_pnl()
    4793                  #test_primary_care_vitals_pnl()
    4794
    4795  #================================================================
    4796