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