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