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 codecs
      14  import os.path
      15
      16
      17  import wx
      18  import wx.grid
      19  import wx.lib.hyperlink
      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
      43  from Gnumed.wxpython import gmRegetMixin
      44  from Gnumed.wxpython import gmEditArea
      45  from Gnumed.wxpython import gmPhraseWheel
      46  from Gnumed.wxpython import gmListWidgets
      47  from Gnumed.wxpython import gmGuiHelpers
      48  from Gnumed.wxpython import gmAuthWidgets
      49  from Gnumed.wxpython import gmOrganizationWidgets
      50
      51
      52  _log = logging.getLogger('gm.ui')
      53
      54  #================================================================
      55  # HL7 related widgets
      56  #================================================================
      57 -def import_Excelleris_HL7(parent=None):
      58
      59          if parent is None:
      60                  parent = wx.GetApp().GetTopWindow()
      61
      62          # select file
      63          dlg = wx.FileDialog (
      64                  parent = parent,
      65                  message = 'Import Excelleris HL7 from XML file:',
      66  #               defaultDir = aDefDir,
      67  #               defaultFile = fname,
      68                  wildcard = "xml files|*.xml|XML files|*.XML|all files|*",
      69                  style = wx.OPEN | wx.FILE_MUST_EXIST
      70          )
      71          choice = dlg.ShowModal()
      72          xml_name = dlg.GetPath()
      73          dlg.Destroy()
      74          if choice != wx.ID_OK:
      75                  return False
      76
      77          # for now, localize gmHL7 import
      78          from Gnumed.business import gmHL7
      79
      80          hl7 = gmHL7.extract_HL7_from_CDATA(xml_name, u'.//Message')
      81          if hl7 is None:
      82                  gmGuiHelpers.gm_show_info (
      83                          u'File [%s]\ndoes not seem to contain HL7 wrapped in XML.' % xml_name,
      84                          u'Extracting HL7 from XML'
      85                  )
      86                  return False
      87          fixed_hl7 = gmHL7.fix_HL7_stupidities(hl7)
      88          PID_names = gmHL7.split_HL7_by_PID(fixed_hl7)
      89          for name in PID_names:
      90                  gmHL7.stage_MSH_as_incoming_data(name, source = u'Excelleris')
      91
      92  #================================================================
      93 -def import_HL7(parent=None):
      94
      95          if parent is None:
      96                  parent = wx.GetApp().GetTopWindow()
      97
      98          # select file
      99          dlg = wx.FileDialog (
     100                  parent = parent,
     101                  message = 'Import HL7 from file:',
     102  #               defaultDir = aDefDir,
     103  #               defaultFile = fname,
     104                  wildcard = "*.hl7|*.hl7|*.HL7|*.HL7|all files|*",
     105                  style = wx.OPEN | wx.FILE_MUST_EXIST
     106          )
     107          choice = dlg.ShowModal()
     108          hl7_name = dlg.GetPath()
     109          dlg.Destroy()
     110          if choice != wx.ID_OK:
     111                  return False
     112
     113          # for now, localize gmHL7 import
     114          from Gnumed.business import gmHL7
     115
     116          fixed_hl7 = gmHL7.fix_HL7_stupidities(hl7_name)
     117          PID_names = gmHL7.split_HL7_by_PID(fixed_hl7)
     118          for name in PID_names:
     119                  gmHL7.stage_MSH_as_incoming_data(name, source = u'generic')
     120
     121  #================================================================
     122 -def browse_incoming_unmatched(parent=None):
     123
     124          # for now, localize gmHL7 import
     125          from Gnumed.business import gmHL7
     126
     127          if parent is None:
     128                  parent = wx.GetApp().GetTopWindow()
     129          #------------------------------------------------------------
     130          def show_hl7(data=None):
     131                  if data is None:
     132                          return False
     133                  filename = data.export_to_file()
     134                  if filename is None:
     135                          return False
     136                  formatted_hl7 = gmHL7.format_hl7_file(filename, return_filename = True)
     137                  gmMimeLib.call_viewer_on_file(aFile = formatted_hl7, block = False)
     138
     139                  return False
     140          #------------------------------------------------------------
     141          def refresh(lctrl):
     142                  incoming = gmHL7.get_incoming_data()
     143                  items = [ [
     144                          i['data_type'],
     145                          u'%s, %s (%s) %s' % (
     146                                  i['lastnames'],
     147                                  i['firstnames'],
     148                                  i['dob'],
     149                                  i['gender']
     150                          ),
     151                          i['external_data_id'],
     152                          i['pk_incoming_data_unmatched']
     153                  ] for i in incoming ]
     154                  lctrl.set_string_items(items)
     155                  lctrl.set_data(incoming)
     156          #------------------------------------------------------------
     157          gmListWidgets.get_choices_from_list (
     158                  parent = parent,
     159                  msg = None,
     160                  caption = _('Showing unmatched incoming data'),
     161                  columns = [ _('Type'), _('Patient'), _('Data ID'), '#' ],
     162                  single_selection = True,
     163                  can_return_empty = False,
     164                  ignore_OK_button = True,
     165                  refresh_callback = refresh,
     166  #               edit_callback=None,
     167  #               new_callback=None,
     168  #               delete_callback=None,
     169                  left_extra_button = [_('Show'), _('Show formatted HL7'), show_hl7]
     170  #               middle_extra_button=None,
     171  #               right_extra_button=None
     172          )
     173
     174  #================================================================
     175  # LOINC related widgets
     176  #================================================================
     177 -def update_loinc_reference_data():
     178
     179          wx.BeginBusyCursor()
     180
     181          gmDispatcher.send(signal = 'statustext', msg = _('Updating LOINC data can take quite a while...'), beep = True)
     182
     183          # download
     184          loinc_zip = gmNetworkTools.download_file(url = 'http://www.gnumed.de/downloads/data/loinc/loinctab.zip', suffix = '.zip')
     185          if loinc_zip is None:
     186                  wx.EndBusyCursor()
     187                  gmGuiHelpers.gm_show_warning (
     188                          aTitle = _('Downloading LOINC'),
     189                          aMessage = _('Error downloading the latest LOINC data.\n')
     190                  )
     191                  return False
     192
     193          _log.debug('downloaded zipped LOINC data into [%s]', loinc_zip)
     194
     195          loinc_dir = gmNetworkTools.unzip_data_pack(filename = loinc_zip)
     196
     197          # split master data file
     198          data_fname, license_fname = gmLOINC.split_LOINCDBTXT(input_fname = os.path.join(loinc_dir, 'LOINCDB.TXT'))
     199
     200          wx.EndBusyCursor()
     201
     202          conn = gmAuthWidgets.get_dbowner_connection(procedure = _('importing LOINC reference data'))
     203          if conn is None:
     204                  return False
     205
     206          wx.BeginBusyCursor()
     207
     208          # import data
     209          if gmLOINC.loinc_import(data_fname = data_fname, license_fname = license_fname, conn = conn):
     210                  gmDispatcher.send(signal = 'statustext', msg = _('Successfully imported LOINC reference data.'))
     211          else:
     212                  gmDispatcher.send(signal = 'statustext', msg = _('Importing LOINC reference data failed.'), beep = True)
     213
     214          wx.EndBusyCursor()
     215          return True
     216
     217  #================================================================
     218  # convenience functions
     219  #================================================================
     220 -def call_browser_on_measurement_type(measurement_type=None):
     221
     222          dbcfg = gmCfg.cCfgSQL()
     223
     224          url = dbcfg.get2 (
     225                  option = u'external.urls.measurements_search',
     226                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     227                  bias = 'user',
     228                  default = u"http://www.google.de/search?as_oq=%(search_term)s&num=10&as_sitesearch=laborlexikon.de"
     229          )
     230
     231          base_url = dbcfg.get2 (
     232                  option = u'external.urls.measurements_encyclopedia',
     233                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     234                  bias = 'user',
     235                  default = u'http://www.laborlexikon.de'
     236          )
     237
     238          if measurement_type is None:
     239                  url = base_url
     240
     241          measurement_type = measurement_type.strip()
     242
     243          if measurement_type == u'':
     244                  url = base_url
     245
     246          url = url % {'search_term': measurement_type}
     247
     248          gmNetworkTools.open_url_in_browser(url = url)
     249
     250  #----------------------------------------------------------------
     251 -def edit_measurement(parent=None, measurement=None, single_entry=False):
     252          ea = cMeasurementEditAreaPnl(parent = parent, id = -1)
     253          ea.data = measurement
     254          ea.mode = gmTools.coalesce(measurement, 'new', 'edit')
     255          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
     256          dlg.SetTitle(gmTools.coalesce(measurement, _('Adding new measurement'), _('Editing measurement')))
     257          if dlg.ShowModal() == wx.ID_OK:
     258                  dlg.Destroy()
     259                  return True
     260          dlg.Destroy()
     261          return False
     262
     263  #----------------------------------------------------------------
     264 -def manage_measurements(parent=None, single_selection=False, emr=None):
     265
     266          if parent is None:
     267                  parent = wx.GetApp().GetTopWindow()
     268
     269          if emr is None:
     270                  emr = gmPerson.gmCurrentPatient().emr
     271
     272          #------------------------------------------------------------
     273          def edit(measurement=None):
     274                  return edit_measurement(parent = parent, measurement = measurement, single_entry = True)
     275          #------------------------------------------------------------
     276          def delete(measurement):
     277                  gmPathLab.delete_test_result(result = measurement)
     278                  return True
     279          #------------------------------------------------------------
     280          def do_review(lctrl):
     281                  data = lctrl.get_selected_item_data()
     282                  if len(data) == 0:
     283                          return
     284                  return review_tests(parent = parent, tests = data)
     285          #------------------------------------------------------------
     286          def do_plot(lctrl):
     287                  data = lctrl.get_selected_item_data()
     288                  if len(data) == 0:
     289                          return
     290                  return plot_measurements(parent = parent, tests = data)
     291          #------------------------------------------------------------
     292          def get_tooltip(measurement):
     293                  return measurement.format(with_review=True, with_evaluation=True, with_ranges=True)
     294          #------------------------------------------------------------
     295          def refresh(lctrl):
     296                  results = emr.get_test_results(order_by = 'clin_when DESC, unified_abbrev, unified_name')
     297                  items = [ [
     298                          gmDateTime.pydt_strftime (
     299                                  r['clin_when'],
     300                                  '%Y %b %d %H:%M',
     301                                  accuracy = gmDateTime.acc_minutes
     302                          ),
     303                          r['unified_abbrev'],
     304                          u'%s%s%s' % (
     305                                  r['unified_val'],
     306                                  gmTools.coalesce(r['val_unit'], u'', u' %s'),
     307                                  gmTools.coalesce(r['abnormality_indicator'], u'', u' %s')
     308                          ),
     309                          r['unified_name'],
     310                          gmTools.coalesce(r['comment'], u''),
     311                          r['pk_test_result']
     312                  ] for r in results ]
     313                  lctrl.set_string_items(items)
     314                  lctrl.set_data(results)
     315          #------------------------------------------------------------
     316          msg = _('Test results (ordered reverse-chronologically)')
     317
     318          return gmListWidgets.get_choices_from_list (
     319                  parent = parent,
     320                  msg = msg,
     321                  caption = _('Showing test results.'),
     322                  columns = [ _('When'), _('Abbrev'), _('Value'), _('Name'), _('Comment'), u'#' ],
     323                  single_selection = single_selection,
     324                  can_return_empty = False,
     325                  refresh_callback = refresh,
     326                  edit_callback = edit,
     327                  new_callback = edit,
     328                  delete_callback = delete,
     329                  list_tooltip_callback = get_tooltip,
     330                  left_extra_button = (_('Review'), _('Review current selection'), do_review, True),
     331                  middle_extra_button = (_('Plot'), _('Plot current selection'), do_plot, True)
     332          )
     333
     334  #================================================================
     335 -def configure_default_gnuplot_template(parent=None):
     336
     337          from Gnumed.wxpython import gmFormWidgets
     338
     339          if parent is None:
     340                  parent = wx.GetApp().GetTopWindow()
     341
     342          template = gmFormWidgets.manage_form_templates (
     343                  parent = parent,
     344                  active_only = True,
     345                  template_types = [u'gnuplot script']
     346          )
     347
     348          option = u'form_templates.default_gnuplot_template'
     349
     350          if template is None:
     351                  gmDispatcher.send(signal = 'statustext', msg = _('No default Gnuplot script template selected.'), beep = True)
     352                  return None
     353
     354          if template['engine'] != u'G':
     355                  gmDispatcher.send(signal = 'statustext', msg = _('No default Gnuplot script template selected.'), beep = True)
     356                  return None
     357
     358          dbcfg = gmCfg.cCfgSQL()
     359          dbcfg.set (
     360                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     361                  option = option,
     362                  value = u'%s - %s' % (template['name_long'], template['external_version'])
     363          )
     364          return template
     365
     366  #============================================================
     367 -def get_default_gnuplot_template(parent = None):
     368
     369          option = u'form_templates.default_gnuplot_template'
     370
     371          dbcfg = gmCfg.cCfgSQL()
     372
     373          # load from option
     374          default_template_name = dbcfg.get2 (
     375                  option = option,
     376                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     377                  bias = 'user'
     378          )
     379
     380          # not configured -> try to configure
     381          if default_template_name is None:
     382                  gmDispatcher.send('statustext', msg = _('No default Gnuplot template configured.'), beep = False)
     383                  default_template = configure_default_gnuplot_template(parent = parent)
     384                  # still not configured -> return
     385                  if default_template is None:
     386                          gmGuiHelpers.gm_show_error (
     387                                  aMessage = _('There is no default Gnuplot one-type script template configured.'),
     388                                  aTitle = _('Plotting test results')
     389                          )
     390                          return None
     391                  return default_template
     392
     393          # now it MUST be configured (either newly or previously)
     394          # but also *validly* ?
     395          try:
     396                  name, ver = default_template_name.split(u' - ')
     397          except:
     398                  # not valid
     399                  _log.exception('problem splitting Gnuplot script template name [%s]', default_template_name)
     400                  gmDispatcher.send(signal = 'statustext', msg = _('Problem loading Gnuplot script template.'), beep = True)
     401                  return None
     402
     403          default_template = gmForms.get_form_template(name_long = name, external_version = ver)
     404          if default_template is None:
     405                  default_template = configure_default_gnuplot_template(parent = parent)
     406                  # still not configured -> return
     407                  if default_template is None:
     408                          gmGuiHelpers.gm_show_error (
     409                                  aMessage = _('Cannot load default Gnuplot script template [%s - %s]') % (name, ver),
     410                                  aTitle = _('Plotting test results')
     411                          )
     412                          return None
     413
     414          return default_template
     415
     416  #----------------------------------------------------------------
     417 -def plot_measurements(parent=None, tests=None, format=None, show_year = True, use_default_template=False):
     418
     419          from Gnumed.wxpython import gmFormWidgets
     420
     421          # only valid for one-type plotting
     422          if use_default_template:
     423                  template = get_default_gnuplot_template()
     424          else:
     425                  template = gmFormWidgets.manage_form_templates (
     426                          parent = parent,
     427                          active_only = True,
     428                          template_types = [u'gnuplot script']
     429                  )
     430
     431          if template is None:
     432                  gmGuiHelpers.gm_show_error (
     433                          aMessage = _('Cannot plot without a plot script.'),
     434                          aTitle = _('Plotting test results')
     435                  )
     436                  return False
     437
     438          fname_data = gmPathLab.export_results_for_gnuplot(results = tests, show_year = show_year)
     439
     440          script = template.instantiate()
     441          script.data_filename = fname_data
     442          script.generate_output(format = format)                 # Gnuplot output terminal, wxt = wxWidgets window
     443
     444  #----------------------------------------------------------------
     445 -def plot_adjacent_measurements(parent=None, test=None, format=None, show_year=True, plot_singular_result=True, use_default_template=False):
     446
     447          earlier, later = test.get_adjacent_results(desired_earlier_results = 2, desired_later_results = 2)
     448          results2plot = []
     449          if earlier is not None:
     450                  results2plot.extend(earlier)
     451          results2plot.append(test)
     452          if later is not None:
     453                  results2plot.extend(later)
     454          if len(results2plot) == 1:
     455                  if not plot_singular_result:
     456                          return
     457          plot_measurements (
     458                  parent = parent,
     459                  tests = results2plot,
     460                  format = format,
     461                  show_year = show_year,
     462                  use_default_template = use_default_template
     463          )
     464  #================================================================
     465  #from Gnumed.wxGladeWidgets import wxgPrimaryCareVitalsInputPnl
     466
     467  # Taillenumfang: Mitte zwischen unterster Rippe und
     468  # hoechstem Teil des Beckenkamms
     469  # Maenner: maessig: 94-102, deutlich: > 102  .. erhoeht
     470  # Frauen:  maessig: 80-88,  deutlich: > 88   .. erhoeht
     471
     472  #================================================================
     473  # display widgets
     474  #================================================================
     475 -class cMeasurementsGrid(wx.grid.Grid):
     476          """A grid class for displaying measurment results.
     477
     478          - does NOT listen to the currently active patient
     479          - thereby it can display any patient at any time
     480          """
     481          # FIXME: sort-by-battery
     482          # FIXME: filter-by-battery
     483          # FIXME: filter out empty
     484          # FIXME: filter by tests of a selected date
     485          # FIXME: dates DESC/ASC by cfg
     486          # FIXME: mouse over column header: display date info
     487 -        def __init__(self, *args, **kwargs):
     488
     489                  wx.grid.Grid.__init__(self, *args, **kwargs)
     490
     491                  self.__patient = None
     492                  self.__panel_to_show = None
     493                  self.__show_by_panel = False
     494                  self.__cell_data = {}
     495                  self.__row_label_data = []
     496
     497                  self.__prev_row = None
     498                  self.__prev_col = None
     499                  self.__prev_label_row = None
     500                  self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::'))
     501
     502                  self.__init_ui()
     503                  self.__register_events()
     504          #------------------------------------------------------------
     505          # external API
     506          #------------------------------------------------------------
     507 -        def delete_current_selection(self):
     508                  if not self.IsSelection():
     509                          gmDispatcher.send(signal = u'statustext', msg = _('No results selected for deletion.'))
     510                          return True
     511
     512                  selected_cells = self.get_selected_cells()
     513                  if len(selected_cells) > 20:
     514                          results = None
     515                          msg = _(
     516                                  'There are %s results marked for deletion.\n'
     517                                  '\n'
     518                                  'Are you sure you want to delete these results ?'
     519                          ) % len(selected_cells)
     520                  else:
     521                          results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
     522                          txt = u'\n'.join([ u'%s %s (%s): %s %s%s' % (
     523                                          r['clin_when'].strftime('%x %H:%M').decode(gmI18N.get_encoding()),
     524                                          r['unified_abbrev'],
     525                                          r['unified_name'],
     526                                          r['unified_val'],
     527                                          r['val_unit'],
     528                                          gmTools.coalesce(r['abnormality_indicator'], u'', u' (%s)')
     529                                  ) for r in results
     530                          ])
     531                          msg = _(
     532                                  'The following results are marked for deletion:\n'
     533                                  '\n'
     534                                  '%s\n'
     535                                  '\n'
     536                                  'Are you sure you want to delete these results ?'
     537                          ) % txt
     538
     539                  dlg = gmGuiHelpers.c2ButtonQuestionDlg (
     540                          self,
     541                          -1,
     542                          caption = _('Deleting test results'),
     543                          question = msg,
     544                          button_defs = [
     545                                  {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False},
     546                                  {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True}
     547                          ]
     548                  )
     549                  decision = dlg.ShowModal()
     550
     551                  if decision == wx.ID_YES:
     552                          if results is None:
     553                                  results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
     554                          for result in results:
     555                                  gmPathLab.delete_test_result(result)
     556          #------------------------------------------------------------
     557 -        def sign_current_selection(self):
     558                  if not self.IsSelection():
     559                          gmDispatcher.send(signal = u'statustext', msg = _('Cannot sign results. No results selected.'))
     560                          return True
     561
     562                  selected_cells = self.get_selected_cells()
     563                  tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
     564
     565                  return review_tests(parent = self, tests = tests)
     566          #------------------------------------------------------------
     567 -        def plot_current_selection(self):
     568
     569                  if not self.IsSelection():
     570                          gmDispatcher.send(signal = u'statustext', msg = _('Cannot plot results. No results selected.'))
     571                          return True
     572
     573                  tests = self.__cells_to_data (
     574                          cells = self.get_selected_cells(),
     575                          exclude_multi_cells = False,
     576                          auto_include_multi_cells = True
     577                  )
     578
     579                  plot_measurements(parent = self, tests = tests)
     580          #------------------------------------------------------------
     581 -        def get_selected_cells(self):
     582
     583                  sel_block_top_left = self.GetSelectionBlockTopLeft()
     584                  sel_block_bottom_right = self.GetSelectionBlockBottomRight()
     585                  sel_cols = self.GetSelectedCols()
     586                  sel_rows = self.GetSelectedRows()
     587
     588                  selected_cells = []
     589
     590                  # individually selected cells (ctrl-click)
     591                  selected_cells += self.GetSelectedCells()
     592
     593                  # selected rows
     594                  selected_cells += list (
     595                          (row, col)
     596                                  for row in sel_rows
     597                                  for col in xrange(self.GetNumberCols())
     598                  )
     599
     600                  # selected columns
     601                  selected_cells += list (
     602                          (row, col)
     603                                  for row in xrange(self.GetNumberRows())
     604                                  for col in sel_cols
     605                  )
     606
     607                  # selection blocks
     608                  for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()):
     609                          selected_cells += [
     610                                  (row, col)
     611                                          for row in xrange(top_left[0], bottom_right[0] + 1)
     612                                          for col in xrange(top_left[1], bottom_right[1] + 1)
     613                          ]
     614
     615                  return set(selected_cells)
     616          #------------------------------------------------------------
     617 -        def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
     618                  """Select a range of cells according to criteria.
     619
     620                  unsigned_only: include only those which are not signed at all yet
     621                  accountable_only: include only those for which the current user is responsible
     622                  keep_preselections: broaden (rather than replace) the range of selected cells
     623
     624                  Combinations are powerful !
     625                  """
     626                  wx.BeginBusyCursor()
     627                  self.BeginBatch()
     628
     629                  if not keep_preselections:
     630                          self.ClearSelection()
     631
     632                  for col_idx in self.__cell_data.keys():
     633                          for row_idx in self.__cell_data[col_idx].keys():
     634                                  # loop over results in cell and only include
     635                                  # those multi-value cells that are not ambiguous
     636                                  do_not_include = False
     637                                  for result in self.__cell_data[col_idx][row_idx]:
     638                                          if unsigned_only:
     639                                                  if result['reviewed']:
     640                                                          do_not_include = True
     641                                                          break
     642                                          if accountables_only:
     643                                                  if not result['you_are_responsible']:
     644                                                          do_not_include = True
     645                                                          break
     646                                  if do_not_include:
     647                                          continue
     648
     649                                  self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True)
     650
     651                  self.EndBatch()
     652                  wx.EndBusyCursor()
     653          #------------------------------------------------------------
     654 -        def repopulate_grid(self):
     655                  self.empty_grid()
     656                  if self.__patient is None:
     657                          return
     658
     659                  if self.__show_by_panel:
     660                          self.__repopulate_grid_by_panel()
     661                          return
     662
     663                  self.__repopulate_grid_all_results()
     664          #------------------------------------------------------------
     665 -        def __repopulate_grid_by_panel(self):
     666
     667                  if self.__panel_to_show is None:
     668                          return
     669
     670                  emr = self.__patient.get_emr()
     671
     672                  # rows
     673                  self.__row_label_data = self.__panel_to_show.test_types
     674                  row_labels = [ u'%s%s' % (
     675                                  gmTools.bool2subst(test['is_fake_meta_type'], u'', gmTools.u_sum, u''),
     676                                  test['unified_abbrev']
     677                          ) for test in self.__row_label_data
     678                  ]
     679                  if len(row_labels) == 0:
     680                          return
     681
     682                  # columns
     683                  column_labels = [
     684                          date[0].strftime(self.__date_format) for date in emr.get_dates_for_results (
     685                                  tests = self.__panel_to_show['pk_test_types'],
     686                                  # FIXME: make configurable
     687                                  reverse_chronological = True
     688                          )
     689                  ]
     690                  results = emr.get_test_results_by_date (
     691                          tests = self.__panel_to_show['pk_test_types'],
     692                          # FIXME: make configurable
     693                          reverse_chronological = True
     694                  )
     695
     696                  self.BeginBatch()
     697
     698                  # rows
     699                  self.AppendRows(numRows = len(row_labels))
     700                  for row_idx in range(len(row_labels)):
     701                          self.SetRowLabelValue(row_idx, row_labels[row_idx])
     702
     703                  # columns
     704                  self.AppendCols(numCols = len(column_labels))
     705                  for date_idx in range(len(column_labels)):
     706                          self.SetColLabelValue(date_idx, column_labels[date_idx])
     707
     708                  # cell values (list of test results)
     709                  for result in results:
     710                          row = row_labels.index(u'%s%s' % (
     711                                  gmTools.bool2subst(result['is_fake_meta_type'], u'', gmTools.u_sum, u''),
     712                                  result['unified_abbrev']
     713                          ))
     714                          col = column_labels.index(result['clin_when'].strftime(self.__date_format))
     715
     716                          try:
     717                                  self.__cell_data[col]
     718                          except KeyError:
     719                                  self.__cell_data[col] = {}
     720
     721                          # the tooltip always shows the youngest sub result details
     722                          if self.__cell_data[col].has_key(row):
     723                                  self.__cell_data[col][row].append(result)
     724                                  self.__cell_data[col][row].sort(key = lambda x: x['clin_when'], reverse = True)
     725                          else:
     726                                  self.__cell_data[col][row] = [result]
     727
     728                          # rebuild cell display string
     729                          vals2display = []
     730                          cell_has_out_of_bounds_value = False
     731                          for sub_result in self.__cell_data[col][row]:
     732
     733                                  if sub_result.is_considered_abnormal:
     734                                          cell_has_out_of_bounds_value = True
     735
     736                                  abnormality_indicator = sub_result.formatted_abnormality_indicator
     737                                  if abnormality_indicator is None:
     738                                          abnormality_indicator = u''
     739                                  if abnormality_indicator != u'':
     740                                          abnormality_indicator = u' (%s)' % abnormality_indicator[:3]
     741
     742                                  missing_review = False
     743                                  # warn on missing review if
     744                                  # a) no review at all exists or
     745                                  if not sub_result['reviewed']:
     746                                          missing_review = True
     747                                  # b) there is a review but
     748                                  else:
     749                                          # current user is reviewer and hasn't reviewed
     750                                          if sub_result['you_are_responsible'] and not sub_result['review_by_you']:
     751                                                  missing_review = True
     752
     753                                  # can we display the full sub_result length ?
     754                                  if sub_result.is_long_text:
     755                                          lines = gmTools.strip_empty_lines (
     756                                                  text = sub_result['unified_val'],
     757                                                  eol = u'\n',
     758                                                  return_list = True
     759                                          )
     760                                          tmp = u'%.7s%s' % (lines[0][:7], gmTools.u_ellipsis)
     761                                  else:
     762                                          val = gmTools.strip_empty_lines (
     763                                                  text = sub_result['unified_val'],
     764                                                  eol = u'\n',
     765                                                  return_list = False
     766                                          ).replace(u'\n', u'//')
     767                                          if len(val) > 8:
     768                                                  tmp = u'%.7s%s' % (val[:7], gmTools.u_ellipsis)
     769                                          else:
     770                                                  tmp = u'%.8s' % val[:8]
     771
     772                                  # abnormal ?
     773                                  tmp = u'%s%.6s' % (tmp, abnormality_indicator)
     774
     775                                  # is there a comment ?
     776                                  has_sub_result_comment = gmTools.coalesce (
     777                                          gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']),
     778                                          u''
     779                                  ).strip() != u''
     780                                  if has_sub_result_comment:
     781                                          tmp = u'%s %s' % (tmp, gmTools.u_ellipsis)
     782
     783                                  # lacking a review ?
     784                                  if missing_review:
     785                                          tmp = u'%s %s' % (tmp, gmTools.u_writing_hand)
     786                                  else:
     787                                          if sub_result['is_clinically_relevant']:
     788                                                  tmp += u' !'
     789
     790                                  # part of a multi-result cell ?
     791                                  if len(self.__cell_data[col][row]) > 1:
     792                                          tmp = u'%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp)
     793
     794                                  vals2display.append(tmp)
     795
     796                          self.SetCellValue(row, col, u'\n'.join(vals2display))
     797                          self.SetCellAlignment(row, col, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
     798                          # We used to color text in cells holding abnormals
     799                          # in firebrick red but that would color ALL text (including
     800                          # normals) and not only the abnormals within that
     801                          # cell. Shading, however, only says that *something*
     802                          # inside that cell is worthy of attention.
     803                          #if sub_result_relevant:
     804                          #       font = self.GetCellFont(row, col)
     805                          #       self.SetCellTextColour(row, col, 'firebrick')
     806                          #       font.SetWeight(wx.FONTWEIGHT_BOLD)
     807                          #       self.SetCellFont(row, col, font)
     808                          if cell_has_out_of_bounds_value:
     809                                  self.SetCellBackgroundColour(row, col, 'cornflower blue')
     810
     811                  self.AutoSize()
     812                  self.EndBatch()
     813                  return
     814          #------------------------------------------------------------
     815 -        def __repopulate_grid_all_results(self):
     816                  emr = self.__patient.get_emr()
     817
     818                  self.__row_label_data = emr.get_test_types_for_results()
     819                  test_type_labels = [ u'%s%s' % (
     820                                  gmTools.bool2subst(test['is_fake_meta_type'], u'', gmTools.u_sum, u''),
     821                                  test['unified_abbrev']
     822                          ) for test in self.__row_label_data
     823                  ]
     824                  if len(test_type_labels) == 0:
     825                          return
     826
     827                  test_date_labels = [ date[0].strftime(self.__date_format) for date in emr.get_dates_for_results() ]
     828                  results = emr.get_test_results_by_date()
     829
     830                  self.BeginBatch()
     831
     832                  # rows
     833                  self.AppendRows(numRows = len(test_type_labels))
     834                  for row_idx in range(len(test_type_labels)):
     835                          self.SetRowLabelValue(row_idx, test_type_labels[row_idx])
     836
     837                  # columns
     838                  self.AppendCols(numCols = len(test_date_labels))
     839                  for date_idx in range(len(test_date_labels)):
     840                          self.SetColLabelValue(date_idx, test_date_labels[date_idx])
     841
     842                  # cell values (list of test results)
     843                  for result in results:
     844                          row = test_type_labels.index(u'%s%s' % (
     845                                  gmTools.bool2subst(result['is_fake_meta_type'], u'', gmTools.u_sum, u''),
     846                                  result['unified_abbrev']
     847                          ))
     848                          col = test_date_labels.index(result['clin_when'].strftime(self.__date_format))
     849
     850                          try:
     851                                  self.__cell_data[col]
     852                          except KeyError:
     853                                  self.__cell_data[col] = {}
     854
     855                          # the tooltip always shows the youngest sub result details
     856                          if self.__cell_data[col].has_key(row):
     857                                  self.__cell_data[col][row].append(result)
     858                                  self.__cell_data[col][row].sort(key = lambda x: x['clin_when'], reverse = True)
     859                          else:
     860                                  self.__cell_data[col][row] = [result]
     861
     862                          # rebuild cell display string
     863                          vals2display = []
     864                          cell_has_out_of_bounds_value = False
     865                          for sub_result in self.__cell_data[col][row]:
     866
     867                                  #if (sub_result.is_considered_lowered is True) or (sub_result.is_considered_elevated is True):
     868                                  if sub_result.is_considered_abnormal:
     869                                          cell_has_out_of_bounds_value = True
     870
     871                                  abnormality_indicator = sub_result.formatted_abnormality_indicator
     872                                  if abnormality_indicator is None:
     873                                          abnormality_indicator = u''
     874                                  if abnormality_indicator != u'':
     875                                          abnormality_indicator = u' (%s)' % abnormality_indicator[:3]
     876
     877                                  # is the sub_result relevant clinically ?
     878                                  # FIXME: take into account primary_GP once we support that
     879                                  sub_result_relevant = sub_result['is_clinically_relevant']
     880                                  if sub_result_relevant is None:
     881                                          # FIXME: calculate from clinical range
     882                                          sub_result_relevant = False
     883
     884                                  missing_review = False
     885                                  # warn on missing review if
     886                                  # a) no review at all exists or
     887                                  if not sub_result['reviewed']:
     888                                          missing_review = True
     889                                  # b) there is a review but
     890                                  else:
     891                                          # current user is reviewer and hasn't reviewed
     892                                          if sub_result['you_are_responsible'] and not sub_result['review_by_you']:
     893                                                  missing_review = True
     894
     895                                  # can we display the full sub_result length ?
     896                                  if sub_result.is_long_text:
     897                                          lines = gmTools.strip_empty_lines (
     898                                                  text = sub_result['unified_val'],
     899                                                  eol = u'\n',
     900                                                  return_list = True
     901                                          )
     902                                          tmp = u'%.7s%s' % (lines[0][:7], gmTools.u_ellipsis)
     903                                  else:
     904                                          val = gmTools.strip_empty_lines (
     905                                                  text = sub_result['unified_val'],
     906                                                  eol = u'\n',
     907                                                  return_list = False
     908                                          ).replace(u'\n', u'//')
     909                                          if len(val) > 8:
     910                                                  tmp = u'%.7s%s' % (val[:7], gmTools.u_ellipsis)
     911                                          else:
     912                                                  tmp = u'%.8s' % val[:8]
     913
     914                                  # abnormal ?
     915                                  tmp = u'%s%.6s' % (tmp, abnormality_indicator)
     916
     917                                  # is there a comment ?
     918                                  has_sub_result_comment = gmTools.coalesce (
     919                                          gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']),
     920                                          u''
     921                                  ).strip() != u''
     922                                  if has_sub_result_comment:
     923                                          tmp = u'%s %s' % (tmp, gmTools.u_ellipsis)
     924
     925                                  # lacking a review ?
     926                                  if missing_review:
     927                                          tmp = u'%s %s' % (tmp, gmTools.u_writing_hand)
     928
     929                                  # part of a multi-result cell ?
     930                                  if len(self.__cell_data[col][row]) > 1:
     931                                          tmp = u'%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp)
     932
     933                                  vals2display.append(tmp)
     934
     935                          self.SetCellValue(row, col, u'\n'.join(vals2display))
     936                          self.SetCellAlignment(row, col, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
     937                          # FIXME: what about partial sub results being relevant ??
     938                          if sub_result_relevant:
     939                                  font = self.GetCellFont(row, col)
     940                                  self.SetCellTextColour(row, col, 'firebrick')
     941                                  font.SetWeight(wx.FONTWEIGHT_BOLD)
     942                                  self.SetCellFont(row, col, font)
     943                          if cell_has_out_of_bounds_value:
     944                                  self.SetCellBackgroundColour(row, col, 'cornflower blue')
     945
     946                  self.AutoSize()
     947                  self.EndBatch()
     948                  return
     949          #------------------------------------------------------------
     950 -        def empty_grid(self):
     951                  self.BeginBatch()
     952                  self.ClearGrid()
     953                  # Windows cannot do nothing, it rather decides to assert()
     954                  # on thinking it is supposed to do nothing
     955                  if self.GetNumberRows() > 0:
     956                          self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
     957                  if self.GetNumberCols() > 0:
     958                          self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
     959                  self.EndBatch()
     960                  self.__cell_data = {}
     961                  self.__row_label_data = []
     962          #------------------------------------------------------------
     963 -        def get_row_tooltip(self, row=None):
     964                  # include details about test types included ?
     965
     966                  # sometimes, for some reason, there is no row and
     967                  # wxPython still tries to find a tooltip for it
     968                  try:
     969                          tt = self.__row_label_data[row]
     970                  except IndexError:
     971                          return u' '
     972
     973                  if tt['is_fake_meta_type']:
     974                          return tt.format(patient = self.__patient.ID)
     975
     976                  meta_tt = tt.meta_test_type
     977                  txt = meta_tt.format(with_tests = True, patient = self.__patient.ID)
     978
     979                  return txt
     980          #------------------------------------------------------------
     981 -        def get_cell_tooltip(self, col=None, row=None):
     982                  try:
     983                          cell_results = self.__cell_data[col][row]
     984                  except KeyError:
     985                          # FIXME: maybe display the most recent or when the most recent was ?
     986                          cell_results = None
     987
     988                  if cell_results is None:
     989                          return u' '
     990
     991                  is_multi_cell = False
     992                  if len(cell_results) > 1:
     993                          is_multi_cell = True
     994                  result = cell_results[0]
     995
     996                  tt = u''
     997                  # header
     998                  if is_multi_cell:
     999                          tt += _(u'Details of most recent (topmost) result !               \n')
    1000                  if result.is_long_text:
    1001                          tt += gmTools.strip_empty_lines(text = result['val_alpha'], eol = u'\n', return_list = False)
    1002                          return tt
    1003
    1004                  tt += result.format(with_review = True, with_evaluation = True, with_ranges = True)
    1005                  return tt
    1006          #------------------------------------------------------------
    1007          # internal helpers
    1008          #------------------------------------------------------------
    1009 -        def __init_ui(self):
    1010                  self.CreateGrid(0, 1)
    1011                  self.EnableEditing(0)
    1012                  self.EnableDragGridSize(1)
    1013                  self.SetMinSize(wx.DefaultSize)
    1014
    1015                  # column labels
    1016                  # setting this screws up the labels: they are cut off and displaced
    1017                  #self.SetColLabelAlignment(wx.ALIGN_CENTER, wx.ALIGN_BOTTOM)
    1018
    1019                  # row labels
    1020                  self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE)             # starting with 2.8.8
    1021                  #self.SetRowLabelSize(150)
    1022                  self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE)
    1023                  font = self.GetLabelFont()
    1024                  font.SetWeight(wx.FONTWEIGHT_LIGHT)
    1025                  self.SetLabelFont(font)
    1026
    1027                  # add link to left upper corner
    1028                  dbcfg = gmCfg.cCfgSQL()
    1029                  url = dbcfg.get2 (
    1030                          option = u'external.urls.measurements_encyclopedia',
    1031                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1032                          bias = 'user',
    1033                          default = u'http://www.laborlexikon.de'
    1034                  )
    1035
    1036                  self.__WIN_corner = self.GetGridCornerLabelWindow()             # a wx.Window instance
    1037
    1038                  LNK_lab = wx.lib.hyperlink.HyperLinkCtrl (
    1039                          self.__WIN_corner,
    1040                          -1,
    1041                          label = _('Tests'),
    1042                          style = wx.HL_DEFAULT_STYLE                     # wx.TE_READONLY|wx.TE_CENTRE| wx.NO_BORDER |
    1043                  )
    1044                  LNK_lab.SetURL(url)
    1045                  LNK_lab.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BACKGROUND))
    1046                  LNK_lab.SetToolTipString(_(
    1047                          'Navigate to an encyclopedia of measurements\n'
    1048                          'and test methods on the web.\n'
    1049                          '\n'
    1050                          ' <%s>'
    1051                  ) % url)
    1052
    1053                  SZR_inner = wx.BoxSizer(wx.HORIZONTAL)
    1054                  SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)                # spacer
    1055                  SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0)          #wx.ALIGN_CENTER wx.EXPAND
    1056                  SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)                # spacer
    1057
    1058                  SZR_corner = wx.BoxSizer(wx.VERTICAL)
    1059                  SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)               # spacer
    1060                  SZR_corner.AddWindow(SZR_inner, 0, wx.EXPAND)   # inner sizer with centered hyperlink
    1061                  SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)               # spacer
    1062
    1063                  self.__WIN_corner.SetSizer(SZR_corner)
    1064                  SZR_corner.Fit(self.__WIN_corner)
    1065          #------------------------------------------------------------
    1066 -        def __resize_corner_window(self, evt):
    1067                  self.__WIN_corner.Layout()
    1068          #------------------------------------------------------------
    1069 -        def __cells_to_data(self, cells=None, exclude_multi_cells=False, auto_include_multi_cells=False):
    1070                  """List of <cells> must be in row / col order."""
    1071                  data = []
    1072                  for row, col in cells:
    1073                          try:
    1074                                  # cell data is stored col / row
    1075                                  data_list = self.__cell_data[col][row]
    1076                          except KeyError:
    1077                                  continue
    1078
    1079                          if len(data_list) == 1:
    1080                                  data.append(data_list[0])
    1081                                  continue
    1082
    1083                          if exclude_multi_cells:
    1084                                  gmDispatcher.send(signal = u'statustext', msg = _('Excluding multi-result field from further processing.'))
    1085                                  continue
    1086
    1087                          if auto_include_multi_cells:
    1088                                  data.extend(data_list)
    1089                                  continue
    1090
    1091                          data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list)
    1092                          if data_to_include is None:
    1093                                  continue
    1094                          data.extend(data_to_include)
    1095
    1096                  return data
    1097          #------------------------------------------------------------
    1098 -        def __get_choices_from_multi_cell(self, cell_data=None, single_selection=False):
    1099                  data = gmListWidgets.get_choices_from_list (
    1100                          parent = self,
    1101                          msg = _(
    1102                                  'Your selection includes a field with multiple results.\n'
    1103                                  '\n'
    1104                                  'Please select the individual results you want to work on:'
    1105                          ),
    1106                          caption = _('Selecting test results'),
    1107                          choices = [ [d['clin_when'], u'%s: %s' % (d['abbrev_tt'], d['name_tt']), d['unified_val']] for d in cell_data ],
    1108                          columns = [ _('Date / Time'), _('Test'), _('Result') ],
    1109                          data = cell_data,
    1110                          single_selection = single_selection
    1111                  )
    1112                  return data
    1113          #------------------------------------------------------------
    1114          # event handling
    1115          #------------------------------------------------------------
    1116 -        def __register_events(self):
    1117                  # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow
    1118                  self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
    1119                  self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels)
    1120                  #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels)
    1121
    1122                  # sizing left upper corner window
    1123                  self.Bind(wx.EVT_SIZE, self.__resize_corner_window)
    1124
    1125                  # editing cells
    1126                  self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
    1127          #------------------------------------------------------------
    1128 -        def __on_cell_left_dclicked(self, evt):
    1129                  col = evt.GetCol()
    1130                  row = evt.GetRow()
    1131
    1132                  # empty cell, perhaps ?
    1133                  try:
    1134                          self.__cell_data[col][row]
    1135                  except KeyError:
    1136                          # FIXME: invoke editor for adding value for day of that column
    1137                          # FIMXE: and test of that row
    1138                          return
    1139
    1140                  if len(self.__cell_data[col][row]) > 1:
    1141                          data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True)
    1142                  else:
    1143                          data = self.__cell_data[col][row][0]
    1144
    1145                  if data is None:
    1146                          return
    1147
    1148                  edit_measurement(parent = self, measurement = data, single_entry = True)
    1149          #------------------------------------------------------------
    1150  #     def OnMouseMotionRowLabel(self, evt):
    1151  #         x, y = self.CalcUnscrolledPosition(evt.GetPosition())
    1152  #         row = self.YToRow(y)
    1153  #         label = self.table().GetRowHelpValue(row)
    1154  #         self.GetGridRowLabelWindow().SetToolTipString(label or "")
    1155  #         evt.Skip()
    1156 -        def __on_mouse_over_row_labels(self, evt):
    1157
    1158                  # Use CalcUnscrolledPosition() to get the mouse position within the
    1159                  # entire grid including what's offscreen
    1160                  x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
    1161
    1162                  row = self.YToRow(y)
    1163
    1164                  if self.__prev_label_row == row:
    1165                          return
    1166
    1167                  self.__prev_label_row == row
    1168
    1169                  evt.GetEventObject().SetToolTipString(self.get_row_tooltip(row = row))
    1170          #------------------------------------------------------------
    1171  #     def OnMouseMotionColLabel(self, evt):
    1172  #         x, y = self.CalcUnscrolledPosition(evt.GetPosition())
    1173  #         col = self.XToCol(x)
    1174  #         label = self.table().GetColHelpValue(col)
    1175  #         self.GetGridColLabelWindow().SetToolTipString(label or "")
    1176  #         evt.Skip()
    1177          #------------------------------------------------------------
    1178 -        def __on_mouse_over_cells(self, evt):
    1179                  """Calculate where the mouse is and set the tooltip dynamically."""
    1180
    1181                  # Use CalcUnscrolledPosition() to get the mouse position within the
    1182                  # entire grid including what's offscreen
    1183                  x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
    1184
    1185                  # use this logic to prevent tooltips outside the actual cells
    1186                  # apply to GetRowSize, too
    1187  #        tot = 0
    1188  #        for col in xrange(self.NumberCols):
    1189  #            tot += self.GetColSize(col)
    1190  #            if xpos <= tot:
    1191  #                self.tool_tip.Tip = 'Tool tip for Column %s' % (
    1192  #                    self.GetColLabelValue(col))
    1193  #                break
    1194  #            else:  # mouse is in label area beyond the right-most column
    1195  #            self.tool_tip.Tip = ''
    1196
    1197                  row, col = self.XYToCell(x, y)
    1198
    1199                  if (row == self.__prev_row) and (col == self.__prev_col):
    1200                          return
    1201
    1202                  self.__prev_row = row
    1203                  self.__prev_col = col
    1204
    1205                  evt.GetEventObject().SetToolTipString(self.get_cell_tooltip(col=col, row=row))
    1206          #------------------------------------------------------------
    1207          # properties
    1208          #------------------------------------------------------------
    1209 -        def _set_patient(self, patient):
    1210                  self.__patient = patient
    1211                  self.repopulate_grid()
    1212
    1213          patient = property(lambda x:x, _set_patient)
    1214          #------------------------------------------------------------
    1215 -        def _set_panel_to_show(self, panel):
    1216                  self.__panel_to_show = panel
    1217                  self.repopulate_grid()
    1218
    1219          panel_to_show = property(lambda x:x, _set_panel_to_show)
    1220          #------------------------------------------------------------
    1221 -        def _set_show_by_panel(self, show_by_panel):
    1222                  self.__show_by_panel = show_by_panel
    1223                  self.repopulate_grid()
    1224
    1225          show_by_panel = property(lambda x:x, _set_show_by_panel)
    1226  #================================================================
    1227  from Gnumed.wxGladeWidgets import wxgMeasurementsPnl
    1228
    1229 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
    1230          """Panel holding a grid with lab data. Used as notebook page."""
    1231
    1232 -        def __init__(self, *args, **kwargs):
    1233
    1234                  wxgMeasurementsPnl.wxgMeasurementsPnl.__init__(self, *args, **kwargs)
    1235                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    1236                  self.__init_ui()
    1237                  self.__register_interests()
    1238          #--------------------------------------------------------
    1239          # event handling
    1240          #--------------------------------------------------------
    1241 -        def __register_interests(self):
    1242                  gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
    1243                  gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
    1244                  gmDispatcher.connect(signal = u'clin.test_result_mod_db', receiver = self._schedule_data_reget)
    1245                  gmDispatcher.connect(signal = u'clin.reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
    1246          #--------------------------------------------------------
    1247 -        def _on_post_patient_selection(self):
    1248                  wx.CallAfter(self.__on_post_patient_selection)
    1249          #--------------------------------------------------------
    1250 -        def __on_post_patient_selection(self):
    1251                  self._schedule_data_reget()
    1252          #--------------------------------------------------------
    1253 -        def _on_pre_patient_selection(self):
    1254                  wx.CallAfter(self.__on_pre_patient_selection)
    1255          #--------------------------------------------------------
    1256 -        def __on_pre_patient_selection(self):
    1257                  self.data_grid.patient = None
    1258                  self.panel_data_grid.patient = None
    1259          #--------------------------------------------------------
    1260 -        def _on_add_button_pressed(self, event):
    1261                  edit_measurement(parent = self, measurement = None)
    1262          #--------------------------------------------------------
    1263 -        def _on_list_button_pressed(self, event):
    1264                  event.Skip()
    1265                  manage_measurements(parent = self, single_selection = True)#, emr = pat.emr)
    1266          #--------------------------------------------------------
    1267 -        def _on_review_button_pressed(self, evt):
    1268                  self.PopupMenu(self.__action_button_popup)
    1269          #--------------------------------------------------------
    1270 -        def _on_select_button_pressed(self, evt):
    1271                  if self._RBTN_my_unsigned.GetValue() is True:
    1272                          self.data_grid.select_cells(unsigned_only = True, accountables_only = True, keep_preselections = False)
    1273                  elif self._RBTN_all_unsigned.GetValue() is True:
    1274                          self.data_grid.select_cells(unsigned_only = True, accountables_only = False, keep_preselections = False)
    1275          #--------------------------------------------------------
    1276 -        def _on_manage_panels_button_pressed(self, event):
    1277                  manage_test_panels(parent = self)
    1278          #--------------------------------------------------------
    1279 -        def __on_sign_current_selection(self, evt):
    1280                  self.data_grid.sign_current_selection()
    1281          #--------------------------------------------------------
    1282 -        def __on_plot_current_selection(self, evt):
    1283                  self.data_grid.plot_current_selection()
    1284          #--------------------------------------------------------
    1285 -        def __on_delete_current_selection(self, evt):
    1286                  self.data_grid.delete_current_selection()
    1287          #--------------------------------------------------------
    1288 -        def _on_panel_selected(self, panel):
    1289                  wx.CallAfter(self.__on_panel_selected, panel=panel)
    1290          #--------------------------------------------------------
    1291 -        def __on_panel_selected(self, panel):
    1292                  if panel is None:
    1293                          self._TCTRL_panel_comment.SetValue(u'')
    1294                          self.panel_data_grid.panel_to_show = None
    1295                          self.panel_data_grid.Hide()
    1296                  else:
    1297                          pnl = self._PRW_panel.GetData(as_instance = True)
    1298                          self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
    1299                                  pnl['comment'],
    1300                                  u''
    1301                          ))
    1302                          self.panel_data_grid.panel_to_show = pnl
    1303                          self.panel_data_grid.Show()
    1304                  self.Layout()
    1305                  #self.Refresh()
    1306          #--------------------------------------------------------
    1307 -        def _on_panel_selection_modified(self):
    1308                  wx.CallAfter(self.__on_panel_selection_modified)
    1309          #--------------------------------------------------------
    1310 -        def __on_panel_selection_modified(self):
    1311                  self._TCTRL_panel_comment.SetValue(u'')
    1312                  if self._PRW_panel.GetValue().strip() == u'':
    1313                          self.panel_data_grid.panel_to_show = None
    1314                          self.panel_data_grid.Hide()
    1315                          self.Layout()
    1316          #--------------------------------------------------------
    1317          # internal API
    1318          #--------------------------------------------------------
    1319 -        def __init_ui(self):
    1320                  self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
    1321
    1322                  menu_id = wx.NewId()
    1323                  self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Review and &sign')))
    1324                  wx.EVT_MENU(self.__action_button_popup, menu_id, self.__on_sign_current_selection)
    1325
    1326                  menu_id = wx.NewId()
    1327                  self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Plot')))
    1328                  wx.EVT_MENU(self.__action_button_popup, menu_id, self.__on_plot_current_selection)
    1329
    1330                  menu_id = wx.NewId()
    1331                  self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Export to &file')))
    1332                  #wx.EVT_MENU(self.__action_button_popup, menu_id, self.data_grid.current_selection_to_file)
    1333                  self.__action_button_popup.Enable(id = menu_id, enable = False)
    1334
    1335                  menu_id = wx.NewId()
    1336                  self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Export to &clipboard')))
    1337                  #wx.EVT_MENU(self.__action_button_popup, menu_id, self.data_grid.current_selection_to_clipboard)
    1338                  self.__action_button_popup.Enable(id = menu_id, enable = False)
    1339
    1340                  menu_id = wx.NewId()
    1341                  self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('&Delete')))
    1342                  wx.EVT_MENU(self.__action_button_popup, menu_id, self.__on_delete_current_selection)
    1343
    1344                  # FIXME: create inbox message to staff to phone patient to come in
    1345                  # FIXME: generate and let edit a SOAP narrative and include the values
    1346
    1347                  self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
    1348                  self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
    1349
    1350                  self.panel_data_grid.show_by_panel = True
    1351                  self.panel_data_grid.panel_to_show = None
    1352                  self.panel_data_grid.Hide()
    1353                  self.Layout()
    1354
    1355                  self._PRW_panel.SetFocus()
    1356          #--------------------------------------------------------
    1357          # reget mixin API
    1358          #--------------------------------------------------------
    1359 -        def _populate_with_data(self):
    1360                  """Populate fields in pages with data from model."""
    1361                  pat = gmPerson.gmCurrentPatient()
    1362                  if pat.connected:
    1363                          self.data_grid.patient = pat
    1364                          self.panel_data_grid.patient = pat
    1365                  else:
    1366                          self.data_grid.patient = None
    1367                          self.panel_data_grid.patient = None
    1368                  return True
    1369
    1370  #================================================================
    1371  # editing widgets
    1372  #================================================================
    1373 -def review_tests(parent=None, tests=None):
    1374
    1375          if tests is None:
    1376                  return True
    1377
    1378          if len(tests) == 0:
    1379                  return True
    1380
    1381          if parent is None:
    1382                  parent = wx.GetApp().GetTopWindow()
    1383
    1384          if len(tests) > 10:
    1385                  test_count = len(tests)
    1386                  tests2show = None
    1387          else:
    1388                  test_count = None
    1389                  tests2show = tests
    1390                  if len(tests) == 0:
    1391                          return True
    1392
    1393          dlg = cMeasurementsReviewDlg(parent, -1, tests = tests, test_count = test_count)
    1394          decision = dlg.ShowModal()
    1395          if decision != wx.ID_APPLY:
    1396                  return True
    1397
    1398          wx.BeginBusyCursor()
    1399          if dlg._RBTN_confirm_abnormal.GetValue():
    1400                  abnormal = None
    1401          elif dlg._RBTN_results_normal.GetValue():
    1402                  abnormal = False
    1403          else:
    1404                  abnormal = True
    1405
    1406          if dlg._RBTN_confirm_relevance.GetValue():
    1407                  relevant = None
    1408          elif dlg._RBTN_results_not_relevant.GetValue():
    1409                  relevant = False
    1410          else:
    1411                  relevant = True
    1412
    1413          comment = None
    1414          if len(tests) == 1:
    1415                  comment = dlg._TCTRL_comment.GetValue()
    1416
    1417          make_responsible = dlg._CHBOX_responsible.IsChecked()
    1418          dlg.Destroy()
    1419
    1420          for test in tests:
    1421                  test.set_review (
    1422                          technically_abnormal = abnormal,
    1423                          clinically_relevant = relevant,
    1424                          comment = comment,
    1425                          make_me_responsible = make_responsible
    1426                  )
    1427          wx.EndBusyCursor()
    1428
    1429          return True
    1430  #----------------------------------------------------------------
    1431  from Gnumed.wxGladeWidgets import wxgMeasurementsReviewDlg
    1432
    1433 -class cMeasurementsReviewDlg(wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg):
    1434
    1435 -        def __init__(self, *args, **kwargs):
    1436
    1437                  try:
    1438                          tests = kwargs['tests']
    1439                          del kwargs['tests']
    1440                          test_count = len(tests)
    1441                          try: del kwargs['test_count']
    1442                          except KeyError: pass
    1443                  except KeyError:
    1444                          tests = None
    1445                          test_count = kwargs['test_count']
    1446                          del kwargs['test_count']
    1447
    1448                  wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs)
    1449
    1450                  if tests is None:
    1451                          msg = _('%s results selected. Too many to list individually.') % test_count
    1452                  else:
    1453                          msg = '\n'.join (
    1454                                  [       u'%s: %s %s (%s)' % (
    1455                                                  t['unified_abbrev'],
    1456                                                  t['unified_val'],
    1457                                                  t['val_unit'],
    1458                                                  gmDateTime.pydt_strftime(t['clin_when'], '%Y %b %d')
    1459                                          ) for t in tests
    1460                                  ]
    1461                          )
    1462
    1463                  self._LBL_tests.SetLabel(msg)
    1464
    1465                  if test_count == 1:
    1466                          self._TCTRL_comment.Enable(True)
    1467                          self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], u''))
    1468                          if tests[0]['you_are_responsible']:
    1469                                  self._CHBOX_responsible.Enable(False)
    1470
    1471                  self.Fit()
    1472          #--------------------------------------------------------
    1473          # event handling
    1474          #--------------------------------------------------------
    1475 -        def _on_signoff_button_pressed(self, evt):
    1476                  if self.IsModal():
    1477                          self.EndModal(wx.ID_APPLY)
    1478                  else:
    1479                          self.Close()
    1480  #================================================================
    1481  from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl
    1482
    1483 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
    1484          """This edit area saves *new* measurements into the active patient only."""
    1485
    1486 -        def __init__(self, *args, **kwargs):
    1487
    1488                  try:
    1489                          self.__default_date = kwargs['date']
    1490                          del kwargs['date']
    1491                  except KeyError:
    1492                          self.__default_date = None
    1493
    1494                  wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl.__init__(self, *args, **kwargs)
    1495                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    1496
    1497                  self.__register_interests()
    1498
    1499                  self.successful_save_msg = _('Successfully saved measurement.')
    1500
    1501                  self._DPRW_evaluated.display_accuracy = gmDateTime.acc_minutes
    1502          #--------------------------------------------------------
    1503          # generic edit area mixin API
    1504          #--------------------------------------------------------
    1505 -        def _refresh_as_new(self):
    1506                  self._PRW_test.SetText(u'', None, True)
    1507                  self.__refresh_loinc_info()
    1508                  self.__refresh_previous_value()
    1509                  self.__update_units_context()
    1510                  self._TCTRL_result.SetValue(u'')
    1511                  self._PRW_units.SetText(u'', None, True)
    1512                  self._PRW_abnormality_indicator.SetText(u'', None, True)
    1513                  if self.__default_date is None:
    1514                          self._DPRW_evaluated.SetData(data = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone))
    1515                  else:
    1516                          self._DPRW_evaluated.SetData(data =     None)
    1517                  self._TCTRL_note_test_org.SetValue(u'')
    1518                  self._PRW_intended_reviewer.SetData(gmStaff.gmCurrentProvider()['pk_staff'])
    1519                  self._PRW_problem.SetData()
    1520                  self._TCTRL_narrative.SetValue(u'')
    1521                  self._CHBOX_review.SetValue(False)
    1522                  self._CHBOX_abnormal.SetValue(False)
    1523                  self._CHBOX_relevant.SetValue(False)
    1524                  self._CHBOX_abnormal.Enable(False)
    1525                  self._CHBOX_relevant.Enable(False)
    1526                  self._TCTRL_review_comment.SetValue(u'')
    1527                  self._TCTRL_normal_min.SetValue(u'')
    1528                  self._TCTRL_normal_max.SetValue(u'')
    1529                  self._TCTRL_normal_range.SetValue(u'')
    1530                  self._TCTRL_target_min.SetValue(u'')
    1531                  self._TCTRL_target_max.SetValue(u'')
    1532                  self._TCTRL_target_range.SetValue(u'')
    1533                  self._TCTRL_norm_ref_group.SetValue(u'')
    1534
    1535                  self._PRW_test.SetFocus()
    1536          #--------------------------------------------------------
    1537 -        def _refresh_from_existing(self):
    1538                  self._PRW_test.SetData(data = self.data['pk_test_type'])
    1539                  self.__refresh_loinc_info()
    1540                  self.__refresh_previous_value()
    1541                  self.__update_units_context()
    1542                  self._TCTRL_result.SetValue(self.data['unified_val'])
    1543                  self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True)
    1544                  self._PRW_abnormality_indicator.SetText (
    1545                          gmTools.coalesce(self.data['abnormality_indicator'], u''),
    1546                          gmTools.coalesce(self.data['abnormality_indicator'], u''),
    1547                          True
    1548                  )
    1549                  self._DPRW_evaluated.SetData(data = self.data['clin_when'])
    1550                  self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], u''))
    1551                  self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
    1552                  self._PRW_problem.SetData(self.data['pk_episode'])
    1553                  self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], u''))
    1554                  self._CHBOX_review.SetValue(False)
    1555                  self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False))
    1556                  self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False))
    1557                  self._CHBOX_abnormal.Enable(False)
    1558                  self._CHBOX_relevant.Enable(False)
    1559                  self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], u''))
    1560                  self._TCTRL_normal_min.SetValue(unicode(gmTools.coalesce(self.data['val_normal_min'], u'')))
    1561                  self._TCTRL_normal_max.SetValue(unicode(gmTools.coalesce(self.data['val_normal_max'], u'')))
    1562                  self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], u''))
    1563                  self._TCTRL_target_min.SetValue(unicode(gmTools.coalesce(self.data['val_target_min'], u'')))
    1564                  self._TCTRL_target_max.SetValue(unicode(gmTools.coalesce(self.data['val_target_max'], u'')))
    1565                  self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], u''))
    1566                  self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], u''))
    1567
    1568                  self._TCTRL_result.SetFocus()
    1569          #--------------------------------------------------------
    1570 -        def _refresh_as_new_from_existing(self):
    1571                  self._PRW_test.SetText(u'', None, True)
    1572                  self.__refresh_loinc_info()
    1573                  self.__refresh_previous_value()
    1574                  self.__update_units_context()
    1575                  self._TCTRL_result.SetValue(u'')
    1576                  self._PRW_units.SetText(u'', None, True)
    1577                  self._PRW_abnormality_indicator.SetText(u'', None, True)
    1578                  self._DPRW_evaluated.SetData(data = self.data['clin_when'])
    1579                  self._TCTRL_note_test_org.SetValue(u'')
    1580                  self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
    1581                  self._PRW_problem.SetData(self.data['pk_episode'])
    1582                  self._TCTRL_narrative.SetValue(u'')
    1583                  self._CHBOX_review.SetValue(False)
    1584                  self._CHBOX_abnormal.SetValue(False)
    1585                  self._CHBOX_relevant.SetValue(False)
    1586                  self._CHBOX_abnormal.Enable(False)
    1587                  self._CHBOX_relevant.Enable(False)
    1588                  self._TCTRL_review_comment.SetValue(u'')
    1589                  self._TCTRL_normal_min.SetValue(u'')
    1590                  self._TCTRL_normal_max.SetValue(u'')
    1591                  self._TCTRL_normal_range.SetValue(u'')
    1592                  self._TCTRL_target_min.SetValue(u'')
    1593                  self._TCTRL_target_max.SetValue(u'')
    1594                  self._TCTRL_target_range.SetValue(u'')
    1595                  self._TCTRL_norm_ref_group.SetValue(u'')
    1596
    1597                  self._PRW_test.SetFocus()
    1598          #--------------------------------------------------------
    1599 -        def _valid_for_save(self):
    1600
    1601                  validity = True
    1602
    1603                  if not self._DPRW_evaluated.is_valid_timestamp():
    1604                          self._DPRW_evaluated.display_as_valid(False)
    1605                          validity = False
    1606                  else:
    1607                          self._DPRW_evaluated.display_as_valid(True)
    1608
    1609                  val = self._TCTRL_result.GetValue().strip()
    1610                  if val == u'':
    1611                          validity = False
    1612                          self.display_ctrl_as_valid(self._TCTRL_result, False)
    1613                  else:
    1614                          self.display_ctrl_as_valid(self._TCTRL_result, True)
    1615                          numeric, val = gmTools.input2decimal(val)
    1616                          if numeric:
    1617                                  if self._PRW_units.GetValue().strip() == u'':
    1618                                          self._PRW_units.display_as_valid(False)
    1619                                          validity = False
    1620                                  else:
    1621                                          self._PRW_units.display_as_valid(True)
    1622                          else:
    1623                                  self._PRW_units.display_as_valid(True)
    1624
    1625                  if self._PRW_problem.GetValue().strip() == u'':
    1626                          self._PRW_problem.display_as_valid(False)
    1627                          validity = False
    1628                  else:
    1629                          self._PRW_problem.display_as_valid(True)
    1630
    1631                  if self._PRW_test.GetValue().strip() == u'':
    1632                          self._PRW_test.display_as_valid(False)
    1633                          validity = False
    1634                  else:
    1635                          self._PRW_test.display_as_valid(True)
    1636
    1637                  if self._PRW_intended_reviewer.GetData() is None:
    1638                          self._PRW_intended_reviewer.display_as_valid(False)
    1639                          validity = False
    1640                  else:
    1641                          self._PRW_intended_reviewer.display_as_valid(True)
    1642
    1643                  ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max]
    1644                  for widget in ctrls:
    1645                          val = widget.GetValue().strip()
    1646                          if val == u'':
    1647                                  continue
    1648                          try:
    1649                                  decimal.Decimal(val.replace(',', u'.', 1))
    1650                                  self.display_ctrl_as_valid(widget, True)
    1651                          except:
    1652                                  validity = False
    1653                                  self.display_ctrl_as_valid(widget, False)
    1654
    1655                  if validity is False:
    1656                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot save result. Invalid or missing essential input.'))
    1657
    1658                  return validity
    1659          #--------------------------------------------------------
    1660 -        def _save_as_new(self):
    1661
    1662                  emr = gmPerson.gmCurrentPatient().get_emr()
    1663
    1664                  success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
    1665                  if success:
    1666                          v_num = result
    1667                          v_al = None
    1668                  else:
    1669                          v_al = self._TCTRL_result.GetValue().strip()
    1670                          v_num = None
    1671
    1672                  pk_type = self._PRW_test.GetData()
    1673                  if pk_type is None:
    1674                          tt = gmPathLab.create_measurement_type (
    1675                                  lab = None,
    1676                                  abbrev = self._PRW_test.GetValue().strip(),
    1677                                  name = self._PRW_test.GetValue().strip(),
    1678                                  unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
    1679                          )
    1680                          pk_type = tt['pk_test_type']
    1681
    1682                  tr = emr.add_test_result (
    1683                          episode = self._PRW_problem.GetData(can_create=True, is_open=False),
    1684                          type = pk_type,
    1685                          intended_reviewer = self._PRW_intended_reviewer.GetData(),
    1686                          val_num = v_num,
    1687                          val_alpha = v_al,
    1688                          unit = self._PRW_units.GetValue()
    1689                  )
    1690
    1691                  tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
    1692
    1693                  ctrls = [
    1694                          ('abnormality_indicator', self._PRW_abnormality_indicator),
    1695                          ('note_test_org', self._TCTRL_note_test_org),
    1696                          ('comment', self._TCTRL_narrative),
    1697                          ('val_normal_range', self._TCTRL_normal_range),
    1698                          ('val_target_range', self._TCTRL_target_range),
    1699                          ('norm_ref_group', self._TCTRL_norm_ref_group)
    1700                  ]
    1701                  for field, widget in ctrls:
    1702                          tr[field] = widget.GetValue().strip()
    1703
    1704                  ctrls = [
    1705                          ('val_normal_min', self._TCTRL_normal_min),
    1706                          ('val_normal_max', self._TCTRL_normal_max),
    1707                          ('val_target_min', self._TCTRL_target_min),
    1708                          ('val_target_max', self._TCTRL_target_max)
    1709                  ]
    1710                  for field, widget in ctrls:
    1711                          val = widget.GetValue().strip()
    1712                          if val == u'':
    1713                                  tr[field] = None
    1714                          else:
    1715                                  tr[field] = decimal.Decimal(val.replace(',', u'.', 1))
    1716
    1717                  tr.save_payload()
    1718
    1719                  if self._CHBOX_review.GetValue() is True:
    1720                          tr.set_review (
    1721                                  technically_abnormal = self._CHBOX_abnormal.GetValue(),
    1722                                  clinically_relevant = self._CHBOX_relevant.GetValue(),
    1723                                  comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), u''),
    1724                                  make_me_responsible = False
    1725                          )
    1726
    1727                  self.data = tr
    1728
    1729                  wx.CallAfter (
    1730                          plot_adjacent_measurements,
    1731                          test = self.data,
    1732                          plot_singular_result = False,
    1733                          use_default_template = True
    1734                  )
    1735
    1736                  return True
    1737          #--------------------------------------------------------
    1738 -        def _save_as_update(self):
    1739
    1740                  success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
    1741                  if success:
    1742                          v_num = result
    1743                          v_al = None
    1744                  else:
    1745                          v_num = None
    1746                          v_al = self._TCTRL_result.GetValue().strip()
    1747
    1748                  pk_type = self._PRW_test.GetData()
    1749                  if pk_type is None:
    1750                          tt = gmPathLab.create_measurement_type (
    1751                                  lab = None,
    1752                                  abbrev = self._PRW_test.GetValue().strip(),
    1753                                  name = self._PRW_test.GetValue().strip(),
    1754                                  unit = gmTools.none_if(self._PRW_units.GetValue().strip(), u'')
    1755                          )
    1756                          pk_type = tt['pk_test_type']
    1757
    1758                  tr = self.data
    1759
    1760                  tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False)
    1761                  tr['pk_test_type'] = pk_type
    1762                  tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData()
    1763                  tr['val_num'] = v_num
    1764                  tr['val_alpha'] = v_al
    1765                  tr['val_unit'] = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
    1766                  tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
    1767
    1768                  ctrls = [
    1769                          ('abnormality_indicator', self._PRW_abnormality_indicator),
    1770                          ('note_test_org', self._TCTRL_note_test_org),
    1771                          ('comment', self._TCTRL_narrative),
    1772                          ('val_normal_range', self._TCTRL_normal_range),
    1773                          ('val_target_range', self._TCTRL_target_range),
    1774                          ('norm_ref_group', self._TCTRL_norm_ref_group)
    1775                  ]
    1776                  for field, widget in ctrls:
    1777                          tr[field] = widget.GetValue().strip()
    1778
    1779                  ctrls = [
    1780                          ('val_normal_min', self._TCTRL_normal_min),
    1781                          ('val_normal_max', self._TCTRL_normal_max),
    1782                          ('val_target_min', self._TCTRL_target_min),
    1783                          ('val_target_max', self._TCTRL_target_max)
    1784                  ]
    1785                  for field, widget in ctrls:
    1786                          val = widget.GetValue().strip()
    1787                          if val == u'':
    1788                                  tr[field] = None
    1789                          else:
    1790                                  tr[field] = decimal.Decimal(val.replace(',', u'.', 1))
    1791
    1792                  tr.save_payload()
    1793
    1794                  if self._CHBOX_review.GetValue() is True:
    1795                          tr.set_review (
    1796                                  technically_abnormal = self._CHBOX_abnormal.GetValue(),
    1797                                  clinically_relevant = self._CHBOX_relevant.GetValue(),
    1798                                  comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), u''),
    1799                                  make_me_responsible = False
    1800                          )
    1801
    1802                  wx.CallAfter (
    1803                          plot_adjacent_measurements,
    1804                          test = self.data,
    1805                          plot_singular_result = False,
    1806                          use_default_template = True
    1807                  )
    1808
    1809                  return True
    1810          #--------------------------------------------------------
    1811          # event handling
    1812          #--------------------------------------------------------
    1813 -        def __register_interests(self):
    1814                  self._PRW_test.add_callback_on_lose_focus(self._on_leave_test_prw)
    1815                  self._PRW_abnormality_indicator.add_callback_on_lose_focus(self._on_leave_indicator_prw)
    1816                  self._PRW_units.add_callback_on_lose_focus(self._on_leave_unit_prw)
    1817          #--------------------------------------------------------
    1818 -        def _on_leave_test_prw(self):
    1819                  self.__refresh_loinc_info()
    1820                  self.__refresh_previous_value()
    1821                  self.__update_units_context()
    1822                  # only works if we've got a unit set
    1823                  self.__update_normal_range()
    1824                  self.__update_clinical_range()
    1825          #--------------------------------------------------------
    1826 -        def _on_leave_unit_prw(self):
    1827                  # maybe we've got a unit now ?
    1828                  self.__update_normal_range()
    1829                  self.__update_clinical_range()
    1830          #--------------------------------------------------------
    1831 -        def _on_leave_indicator_prw(self):
    1832                  # if the user hasn't explicitly enabled reviewing
    1833                  if not self._CHBOX_review.GetValue():
    1834                          self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != u'')
    1835          #--------------------------------------------------------
    1836 -        def _on_review_box_checked(self, evt):
    1837                  self._CHBOX_abnormal.Enable(self._CHBOX_review.GetValue())
    1838                  self._CHBOX_relevant.Enable(self._CHBOX_review.GetValue())
    1839                  self._TCTRL_review_comment.Enable(self._CHBOX_review.GetValue())
    1840          #--------------------------------------------------------
    1841 -        def _on_test_info_button_pressed(self, event):
    1842
    1843                  pk = self._PRW_test.GetData()
    1844                  if pk is not None:
    1845                          tt = gmPathLab.cMeasurementType(aPK_obj = pk)
    1846                          search_term = u'%s %s %s' % (
    1847                                  tt['name'],
    1848                                  tt['abbrev'],
    1849                                  gmTools.coalesce(tt['loinc'], u'')
    1850                          )
    1851                  else:
    1852                          search_term = self._PRW_test.GetValue()
    1853
    1854                  search_term = search_term.replace(' ', u'+')
    1855
    1856                  call_browser_on_measurement_type(measurement_type = search_term)
    1857          #--------------------------------------------------------
    1858          # internal helpers
    1859          #--------------------------------------------------------
    1860 -        def __update_units_context(self):
    1861
    1862                  if self._PRW_test.GetData() is None:
    1863                          self._PRW_units.unset_context(context = u'pk_type')
    1864                          self._PRW_units.unset_context(context = u'loinc')
    1865                          if self._PRW_test.GetValue().strip() == u'':
    1866                                  self._PRW_units.unset_context(context = u'test_name')
    1867                          else:
    1868                                  self._PRW_units.set_context(context = u'test_name', val = self._PRW_test.GetValue().strip())
    1869                          return
    1870
    1871                  tt = self._PRW_test.GetData(as_instance = True)
    1872
    1873                  self._PRW_units.set_context(context = u'pk_type', val = tt['pk_test_type'])
    1874                  self._PRW_units.set_context(context = u'test_name', val = tt['name'])
    1875
    1876                  if tt['loinc'] is not None:
    1877                          self._PRW_units.set_context(context = u'loinc', val = tt['loinc'])
    1878
    1879                  # closest unit
    1880                  if self._PRW_units.GetValue().strip() == u'':
    1881                          clin_when = self._DPRW_evaluated.GetData().get_pydt()
    1882                          if clin_when is None:
    1883                                  unit = tt.temporally_closest_unit
    1884                          else:
    1885                                  unit = tt.get_temporally_closest_unit(timestamp = clin_when)
    1886                          if unit is None:
    1887                                  self._PRW_units.SetText(u'', unit, True)
    1888                          else:
    1889                                  self._PRW_units.SetText(unit, unit, True)
    1890
    1891          #--------------------------------------------------------
    1892 -        def __update_normal_range(self):
    1893                  unit = self._PRW_units.GetValue().strip()
    1894                  if unit == u'':
    1895                          return
    1896                  if self._PRW_test.GetData() is None:
    1897                          return
    1898                  for ctrl in [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_normal_range, self._TCTRL_norm_ref_group]:
    1899                          if ctrl.GetValue().strip() != u'':
    1900                                  return
    1901                  tt = self._PRW_test.GetData(as_instance = True)
    1902                  test_w_range = tt.get_temporally_closest_normal_range (
    1903                          unit,
    1904                          timestamp = self._DPRW_evaluated.GetData().get_pydt()
    1905                  )
    1906                  if test_w_range is None:
    1907                          return
    1908                  self._TCTRL_normal_min.SetValue(unicode(gmTools.coalesce(test_w_range['val_normal_min'], u'')))
    1909                  self._TCTRL_normal_max.SetValue(unicode(gmTools.coalesce(test_w_range['val_normal_max'], u'')))
    1910                  self._TCTRL_normal_range.SetValue(gmTools.coalesce(test_w_range['val_normal_range'], u''))
    1911                  self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(test_w_range['norm_ref_group'], u''))
    1912
    1913          #--------------------------------------------------------
    1914 -        def __update_clinical_range(self):
    1915                  unit = self._PRW_units.GetValue().strip()
    1916                  if unit == u'':
    1917                          return
    1918                  if self._PRW_test.GetData() is None:
    1919                          return
    1920                  for ctrl in [self._TCTRL_target_min, self._TCTRL_target_max, self._TCTRL_target_range]:
    1921                          if ctrl.GetValue().strip() != u'':
    1922                                  return
    1923                  tt = self._PRW_test.GetData(as_instance = True)
    1924                  test_w_range = tt.get_temporally_closest_target_range (
    1925                          unit,
    1926                          gmPerson.gmCurrentPatient().ID,
    1927                          timestamp = self._DPRW_evaluated.GetData().get_pydt()
    1928                  )
    1929                  if test_w_range is None:
    1930                          return
    1931                  self._TCTRL_target_min.SetValue(unicode(gmTools.coalesce(test_w_range['val_target_min'], u'')))
    1932                  self._TCTRL_target_max.SetValue(unicode(gmTools.coalesce(test_w_range['val_target_max'], u'')))
    1933                  self._TCTRL_target_range.SetValue(gmTools.coalesce(test_w_range['val_target_range'], u''))
    1934
    1935          #--------------------------------------------------------
    1936 -        def __refresh_loinc_info(self):
    1937
    1938                  self._TCTRL_loinc.SetValue(u'')
    1939
    1940                  if self._PRW_test.GetData() is None:
    1941                          return
    1942
    1943                  tt = self._PRW_test.GetData(as_instance = True)
    1944
    1945                  if tt['loinc'] is None:
    1946                          return
    1947
    1948                  info = gmLOINC.loinc2term(loinc = tt['loinc'])
    1949                  if len(info) == 0:
    1950                          self._TCTRL_loinc.SetValue(u'')
    1951                          return
    1952
    1953                  self._TCTRL_loinc.SetValue(u'%s: %s' % (tt['loinc'], info[0]))
    1954          #--------------------------------------------------------
    1955 -        def __refresh_previous_value(self):
    1956                  self._TCTRL_previous_value.SetValue(u'')
    1957                  # it doesn't make much sense to show the most
    1958                  # recent value when editing an existing one
    1959                  if self.data is not None:
    1960                          return
    1961                  if self._PRW_test.GetData() is None:
    1962                          return
    1963                  tt = self._PRW_test.GetData(as_instance = True)
    1964                  most_recent = tt.get_most_recent_results (
    1965                          no_of_results = 1,
    1966                          patient = gmPerson.gmCurrentPatient().ID
    1967                  )
    1968                  if most_recent is None:
    1969                          return
    1970                  self._TCTRL_previous_value.SetValue(_('%s ago: %s%s%s - %s%s') % (
    1971                          gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - most_recent['clin_when']),
    1972                          most_recent['unified_val'],
    1973                          most_recent['val_unit'],
    1974                          gmTools.coalesce(most_recent['abnormality_indicator'], u'', u' (%s)'),
    1975                          most_recent['abbrev_tt'],
    1976                          gmTools.coalesce(most_recent.formatted_range, u'', u' [%s]')
    1977                  ))
    1978                  self._TCTRL_previous_value.SetToolTipString(most_recent.format (
    1979                          with_review = True,
    1980                          with_evaluation = False,
    1981                          with_ranges = True,
    1982                          with_episode = True,
    1983                          with_type_details=True
    1984                  ))
    1985
    1986  #================================================================
    1987  # measurement type handling
    1988  #================================================================
    1989 -def pick_measurement_types(parent=None, msg=None, right_column=None, picks=None):
    1990
    1991          if parent is None:
    1992                  parent = wx.GetApp().GetTopWindow()
    1993
    1994          if msg is None:
    1995                  msg = _('Pick the relevant measurement types.')
    1996
    1997          if right_column is None:
    1998                  right_columns = [_('Picked')]
    1999          else:
    2000                  right_columns = [right_column]
    2001
    2002          picker = gmListWidgets.cItemPickerDlg(parent, -1, msg = msg)
    2003          picker.set_columns(columns = [_('Known measurement types')], columns_right = right_columns)
    2004          types = gmPathLab.get_measurement_types(order_by = 'unified_abbrev')
    2005          picker.set_choices (
    2006                  choices = [
    2007                          u'%s: %s%s' % (
    2008                                  t['unified_abbrev'],
    2009                                  t['unified_name'],
    2010                                  gmTools.coalesce(t['name_org'], u'', u' (%s)')
    2011                          )
    2012                          for t in types
    2013                  ],
    2014                  data = types
    2015          )
    2016          if picks is not None:
    2017                  picker.set_picks (
    2018                          picks = [
    2019                                  u'%s: %s%s' % (
    2020                                          p['unified_abbrev'],
    2021                                          p['unified_name'],
    2022                                          gmTools.coalesce(p['name_org'], u'', u' (%s)')
    2023                                  )
    2024                                  for p in picks
    2025                          ],
    2026                          data = picks
    2027                  )
    2028          result = picker.ShowModal()
    2029
    2030          if result == wx.ID_CANCEL:
    2031                  picker.Destroy()
    2032                  return None
    2033
    2034          picks = picker.picks
    2035          picker.Destroy()
    2036          return picks
    2037
    2038  #----------------------------------------------------------------
    2039 -def manage_measurement_types(parent=None):
    2040
    2041          if parent is None:
    2042                  parent = wx.GetApp().GetTopWindow()
    2043
    2044          #------------------------------------------------------------
    2045          def edit(test_type=None):
    2046                  ea = cMeasurementTypeEAPnl(parent = parent, id = -1, type = test_type)
    2047                  dlg = gmEditArea.cGenericEditAreaDlg2 (
    2048                          parent = parent,
    2049                          id = -1,
    2050                          edit_area = ea,
    2051                          single_entry = gmTools.bool2subst((test_type is None), False, True)
    2052                  )
    2053                  dlg.SetTitle(gmTools.coalesce(test_type, _('Adding measurement type'), _('Editing measurement type')))
    2054
    2055                  if dlg.ShowModal() == wx.ID_OK:
    2056                          dlg.Destroy()
    2057                          return True
    2058
    2059                  dlg.Destroy()
    2060                  return False
    2061          #------------------------------------------------------------
    2062          def delete(measurement_type):
    2063                  if measurement_type.in_use:
    2064                          gmDispatcher.send (
    2065                                  signal = 'statustext',
    2066                                  beep = True,
    2067                                  msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev'])
    2068                          )
    2069                          return False
    2070                  gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type'])
    2071                  return True
    2072          #------------------------------------------------------------
    2073          def get_tooltip(test_type):
    2074                  return test_type.format()
    2075          #------------------------------------------------------------
    2076          def refresh(lctrl):
    2077                  mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev')
    2078                  items = [ [
    2079                          m['abbrev'],
    2080                          m['name'],
    2081                          gmTools.coalesce(m['conversion_unit'], u''),
    2082                          gmTools.coalesce(m['loinc'], u''),
    2083                          gmTools.coalesce(m['comment_type'], u''),
    2084                          gmTools.coalesce(m['name_org'], u'?'),
    2085                          gmTools.coalesce(m['comment_org'], u''),
    2086                          m['pk_test_type']
    2087                  ] for m in mtypes ]
    2088                  lctrl.set_string_items(items)
    2089                  lctrl.set_data(mtypes)
    2090          #------------------------------------------------------------
    2091          msg = _(
    2092                  '\n'
    2093                  'These are the measurement types currently defined in GNUmed.\n'
    2094                  '\n'
    2095          )
    2096
    2097          gmListWidgets.get_choices_from_list (
    2098                  parent = parent,
    2099                  msg = msg,
    2100                  caption = _('Showing measurement types.'),
    2101                  columns = [ _('Abbrev'), _('Name'), _('Unit'), _('LOINC'), _('Comment'), _('Org'), _('Comment'), u'#' ],
    2102                  single_selection = True,
    2103                  refresh_callback = refresh,
    2104                  edit_callback = edit,
    2105                  new_callback = edit,
    2106                  delete_callback = delete,
    2107                  list_tooltip_callback = get_tooltip
    2108          )
    2109
    2110  #----------------------------------------------------------------
    2111 -class cMeasurementTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
    2112
    2113 -        def __init__(self, *args, **kwargs):
    2114
    2115                  query = u"""
    2116  SELECT DISTINCT ON (field_label)
    2117          pk_test_type AS data,
    2118          name
    2119                  || ' ('
    2120                  || coalesce (
    2121                          (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
    2122                          '%(in_house)s'
    2123                          )
    2124                  || ')'
    2125          AS field_label,
    2126          name
    2127                  || ' ('
    2128                  || abbrev || ', '
    2129                  || coalesce(abbrev_meta || ': ' || name_meta || ', ', '')
    2130                  || coalesce (
    2131                          (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
    2132                          '%(in_house)s'
    2133                          )
    2134                  || ')'
    2135          AS list_label
    2136  FROM
    2137          clin.v_test_types c_vtt
    2138  WHERE
    2139          abbrev_meta %%(fragment_condition)s
    2140                  OR
    2141          name_meta %%(fragment_condition)s
    2142                  OR
    2143          abbrev %%(fragment_condition)s
    2144                  OR
    2145          name %%(fragment_condition)s
    2146  ORDER BY field_label
    2147  LIMIT 50""" % {'in_house': _('generic / in house lab')}
    2148
    2149                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    2150                  mp.setThresholds(1, 2, 4)
    2151                  mp.word_separators = '[ \t:@]+'
    2152                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    2153                  self.matcher = mp
    2154                  self.SetToolTipString(_('Select the type of measurement.'))
    2155                  self.selection_only = False
    2156          #------------------------------------------------------------
    2157 -        def _data2instance(self):
    2158                  if self.GetData() is None:
    2159                          return None
    2160
    2161                  return gmPathLab.cMeasurementType(aPK_obj = self.GetData())
    2162  #----------------------------------------------------------------
    2163  from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl
    2164
    2165 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
    2166
    2167 -        def __init__(self, *args, **kwargs):
    2168
    2169                  try:
    2170                          data = kwargs['type']
    2171                          del kwargs['type']
    2172                  except KeyError:
    2173                          data = None
    2174
    2175                  wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl.__init__(self, *args, **kwargs)
    2176                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    2177                  self.mode = 'new'
    2178                  self.data = data
    2179                  if data is not None:
    2180                          self.mode = 'edit'
    2181
    2182                  self.__init_ui()
    2183
    2184          #----------------------------------------------------------------
    2185 -        def __init_ui(self):
    2186
    2187                  # name phraseweel
    2188                  query = u"""
    2189  select distinct on (name)
    2190          pk,
    2191          name
    2192  from clin.test_type
    2193  where
    2194          name %(fragment_condition)s
    2195  order by name
    2196  limit 50"""
    2197                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    2198                  mp.setThresholds(1, 2, 4)
    2199                  self._PRW_name.matcher = mp
    2200                  self._PRW_name.selection_only = False
    2201                  self._PRW_name.add_callback_on_lose_focus(callback = self._on_name_lost_focus)
    2202
    2203                  # abbreviation
    2204                  query = u"""
    2205  select distinct on (abbrev)
    2206          pk,
    2207          abbrev
    2208  from clin.test_type
    2209  where
    2210          abbrev %(fragment_condition)s
    2211  order by abbrev
    2212  limit 50"""
    2213                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    2214                  mp.setThresholds(1, 2, 3)
    2215                  self._PRW_abbrev.matcher = mp
    2216                  self._PRW_abbrev.selection_only = False
    2217
    2218                  # unit
    2219                  self._PRW_conversion_unit.selection_only = False
    2220
    2221                  # loinc
    2222                  mp = gmLOINC.cLOINCMatchProvider()
    2223                  mp.setThresholds(1, 2, 4)
    2224                  #mp.print_queries = True
    2225                  #mp.word_separators = '[ \t:@]+'
    2226                  self._PRW_loinc.matcher = mp
    2227                  self._PRW_loinc.selection_only = False
    2228                  self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
    2229          #----------------------------------------------------------------
    2230 -        def _on_name_lost_focus(self):
    2231
    2232                  test = self._PRW_name.GetValue().strip()
    2233
    2234                  if test == u'':
    2235                          self._PRW_conversion_unit.unset_context(context = u'test_name')
    2236                          return
    2237
    2238                  self._PRW_conversion_unit.set_context(context = u'test_name', val = test)
    2239          #----------------------------------------------------------------
    2240 -        def _on_loinc_lost_focus(self):
    2241                  loinc = self._PRW_loinc.GetData()
    2242
    2243                  if loinc is None:
    2244                          self._TCTRL_loinc_info.SetValue(u'')
    2245                          self._PRW_conversion_unit.unset_context(context = u'loinc')
    2246                          return
    2247
    2248                  self._PRW_conversion_unit.set_context(context = u'loinc', val = loinc)
    2249
    2250                  info = gmLOINC.loinc2term(loinc = loinc)
    2251                  if len(info) == 0:
    2252                          self._TCTRL_loinc_info.SetValue(u'')
    2253                          return
    2254
    2255                  self._TCTRL_loinc_info.SetValue(info[0])
    2256          #----------------------------------------------------------------
    2257          # generic Edit Area mixin API
    2258          #----------------------------------------------------------------
    2259 -        def _valid_for_save(self):
    2260
    2261                  has_errors = False
    2262                  for field in [self._PRW_name, self._PRW_abbrev, self._PRW_conversion_unit]:
    2263                          if field.GetValue().strip() in [u'', None]:
    2264                                  has_errors = True
    2265                                  field.display_as_valid(valid = False)
    2266                          else:
    2267                                  field.display_as_valid(valid = True)
    2268                          field.Refresh()
    2269
    2270                  return (not has_errors)
    2271          #----------------------------------------------------------------
    2272 -        def _save_as_new(self):
    2273
    2274                  pk_org = self._PRW_test_org.GetData()
    2275                  if pk_org is None:
    2276                          pk_org = gmPathLab.create_test_org (
    2277                                  name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), u'')
    2278                          )['pk_test_org']
    2279
    2280                  tt = gmPathLab.create_measurement_type (
    2281                          lab = pk_org,
    2282                          abbrev = self._PRW_abbrev.GetValue().strip(),
    2283                          name = self._PRW_name.GetValue().strip(),
    2284                          unit = gmTools.coalesce (
    2285                                  self._PRW_conversion_unit.GetData(),
    2286                                  self._PRW_conversion_unit.GetValue()
    2287                          ).strip()
    2288                  )
    2289                  if self._PRW_loinc.GetData() is not None:
    2290                          tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), u'')
    2291                  else:
    2292                          tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), u'')
    2293                  tt['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), u'')
    2294                  tt['pk_meta_test_type'] = self._PRW_meta_type.GetData()
    2295
    2296                  tt.save()
    2297
    2298                  self.data = tt
    2299
    2300                  return True
    2301          #----------------------------------------------------------------
    2302 -        def _save_as_update(self):
    2303
    2304                  pk_org = self._PRW_test_org.GetData()
    2305                  if pk_org is None:
    2306                          pk_org = gmPathLab.create_test_org (
    2307                                  name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), u'')
    2308                          )['pk_test_org']
    2309
    2310                  self.data['pk_test_org'] = pk_org
    2311                  self.data['abbrev'] = self._PRW_abbrev.GetValue().strip()
    2312                  self.data['name'] = self._PRW_name.GetValue().strip()
    2313                  self.data['conversion_unit'] = gmTools.coalesce (
    2314                          self._PRW_conversion_unit.GetData(),
    2315                          self._PRW_conversion_unit.GetValue()
    2316                  ).strip()
    2317                  if self._PRW_loinc.GetData() is not None:
    2318                          self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), u'')
    2319                  if self._PRW_loinc.GetData() is not None:
    2320                          self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), u'')
    2321                  else:
    2322                          self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), u'')
    2323                  self.data['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), u'')
    2324                  self.data['pk_meta_test_type'] = self._PRW_meta_type.GetData()
    2325                  self.data.save()
    2326
    2327                  return True
    2328          #----------------------------------------------------------------
    2329 -        def _refresh_as_new(self):
    2330                  self._PRW_name.SetText(u'', None, True)
    2331                  self._on_name_lost_focus()
    2332                  self._PRW_abbrev.SetText(u'', None, True)
    2333                  self._PRW_conversion_unit.SetText(u'', None, True)
    2334                  self._PRW_loinc.SetText(u'', None, True)
    2335                  self._on_loinc_lost_focus()
    2336                  self._TCTRL_comment_type.SetValue(u'')
    2337                  self._PRW_test_org.SetText(u'', None, True)
    2338                  self._PRW_meta_type.SetText(u'', None, True)
    2339
    2340                  self._PRW_name.SetFocus()
    2341          #----------------------------------------------------------------
    2342 -        def _refresh_from_existing(self):
    2343                  self._PRW_name.SetText(self.data['name'], self.data['name'], True)
    2344                  self._on_name_lost_focus()
    2345                  self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True)
    2346                  self._PRW_conversion_unit.SetText (
    2347                          gmTools.coalesce(self.data['conversion_unit'], u''),
    2348                          self.data['conversion_unit'],
    2349                          True
    2350                  )
    2351                  self._PRW_loinc.SetText (
    2352                          gmTools.coalesce(self.data['loinc'], u''),
    2353                          self.data['loinc'],
    2354                          True
    2355                  )
    2356                  self._on_loinc_lost_focus()
    2357                  self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], u''))
    2358                  self._PRW_test_org.SetText (
    2359                          gmTools.coalesce(self.data['pk_test_org'], u'', self.data['name_org']),
    2360                          self.data['pk_test_org'],
    2361                          True
    2362                  )
    2363                  if self.data['pk_meta_test_type'] is None:
    2364                          self._PRW_meta_type.SetText(u'', None, True)
    2365                  else:
    2366                          self._PRW_meta_type.SetText(u'%s: %s' % (self.data['abbrev_meta'], self.data['name_meta']), self.data['pk_meta_test_type'], True)
    2367
    2368                  self._PRW_name.SetFocus()
    2369          #----------------------------------------------------------------
    2370 -        def _refresh_as_new_from_existing(self):
    2371                  self._refresh_as_new()
    2372                  self._PRW_test_org.SetText (
    2373                          gmTools.coalesce(self.data['pk_test_org'], u'', self.data['name_org']),
    2374                          self.data['pk_test_org'],
    2375                          True
    2376                  )
    2377                  self._PRW_name.SetFocus()
    2378
    2379  #================================================================
    2380  _SQL_units_from_test_results = u"""
    2381          -- via clin.v_test_results.pk_type (for types already used in results)
    2382          SELECT
    2383                  val_unit AS data,
    2384                  val_unit AS field_label,
    2385                  val_unit || ' (' || name_tt || ')' AS list_label,
    2386                  1 AS rank
    2387          FROM
    2388                  clin.v_test_results
    2389          WHERE
    2390                  (
    2391                          val_unit %(fragment_condition)s
    2392                                  OR
    2393                          conversion_unit %(fragment_condition)s
    2394                  )
    2395                  %(ctxt_type_pk)s
    2396                  %(ctxt_test_name)s
    2397  """
    2398
    2399  _SQL_units_from_test_types = u"""
    2400          -- via clin.test_type (for types not yet used in results)
    2401          SELECT
    2402                  conversion_unit AS data,
    2403                  conversion_unit AS field_label,
    2404                  conversion_unit || ' (' || name || ')' AS list_label,
    2405                  2 AS rank
    2406          FROM
    2407                  clin.test_type
    2408          WHERE
    2409                  conversion_unit %(fragment_condition)s
    2410                  %(ctxt_ctt)s
    2411  """
    2412
    2413  _SQL_units_from_loinc_ipcc = u"""
    2414          -- via ref.loinc.ipcc_units
    2415          SELECT
    2416                  ipcc_units AS data,
    2417                  ipcc_units AS field_label,
    2418                  ipcc_units || ' (LOINC.ipcc: ' || term || ')' AS list_label,
    2419                  3 AS rank
    2420          FROM
    2421                  ref.loinc
    2422          WHERE
    2423                  ipcc_units %(fragment_condition)s
    2424                  %(ctxt_loinc)s
    2425                  %(ctxt_loinc_term)s
    2426  """
    2427
    2428  _SQL_units_from_loinc_submitted = u"""
    2429          -- via ref.loinc.submitted_units
    2430          SELECT
    2431                  submitted_units AS data,
    2432                  submitted_units AS field_label,
    2433                  submitted_units || ' (LOINC.submitted:' || term || ')' AS list_label,
    2434                  3 AS rank
    2435          FROM
    2436                  ref.loinc
    2437          WHERE
    2438                  submitted_units %(fragment_condition)s
    2439                  %(ctxt_loinc)s
    2440                  %(ctxt_loinc_term)s
    2441  """
    2442
    2443  _SQL_units_from_loinc_example = u"""
    2444          -- via ref.loinc.example_units
    2445          SELECT
    2446                  example_units AS data,
    2447                  example_units AS field_label,
    2448                  example_units || ' (LOINC.example: ' || term || ')' AS list_label,
    2449                  3 AS rank
    2450          FROM
    2451                  ref.loinc
    2452          WHERE
    2453                  example_units %(fragment_condition)s
    2454                  %(ctxt_loinc)s
    2455                  %(ctxt_loinc_term)s
    2456  """
    2457
    2458  _SQL_units_from_atc = u"""
    2459          -- via ref.atc.unit
    2460          SELECT
    2461                  unit AS data,
    2462                  unit AS field_label,
    2463                  unit || ' (ATC: ' || term || ')' AS list_label,
    2464                  2 AS rank
    2465          FROM
    2466                  ref.atc
    2467          WHERE
    2468                  unit IS NOT NULL
    2469                          AND
    2470                  unit %(fragment_condition)s
    2471  """
    2472
    2473  _SQL_units_from_consumable_substance = u"""
    2474          -- via ref.consumable_substance.unit
    2475          SELECT
    2476                  unit AS data,
    2477                  unit AS field_label,
    2478                  unit || ' (' || description || ')' AS list_label,
    2479                  2 AS rank
    2480          FROM
    2481                  ref.consumable_substance
    2482          WHERE
    2483                  unit %(fragment_condition)s
    2484                  %(ctxt_substance)s
    2485  """
    2486
    2487  #----------------------------------------------------------------
    2488 -class cUnitPhraseWheel(gmPhraseWheel.cPhraseWheel):
    2489
    2490 -        def __init__(self, *args, **kwargs):
    2491
    2492                  query = u"""
    2493  SELECT DISTINCT ON (data)
    2494          data,
    2495          field_label,
    2496          list_label
    2497  FROM (
    2498
    2499          SELECT
    2500                  data,
    2501                  field_label,
    2502                  list_label,
    2503                  rank
    2504          FROM (
    2505                  (%s) UNION ALL
    2506                  (%s) UNION ALL
    2507                  (%s) UNION ALL
    2508                  (%s) UNION ALL
    2509                  (%s) UNION ALL
    2510                  (%s) UNION ALL
    2511                  (%s)
    2512          ) AS all_matching_units
    2513          WHERE data IS NOT NULL
    2514          ORDER BY rank, list_label
    2515
    2516  ) AS ranked_matching_units
    2517  LIMIT 50""" % (
    2518                          _SQL_units_from_test_results,
    2519                          _SQL_units_from_test_types,
    2520                          _SQL_units_from_loinc_ipcc,
    2521                          _SQL_units_from_loinc_submitted,
    2522                          _SQL_units_from_loinc_example,
    2523                          _SQL_units_from_atc,
    2524                          _SQL_units_from_consumable_substance
    2525                  )
    2526
    2527                  ctxt = {
    2528                          'ctxt_type_pk': {
    2529                                  'where_part': u'AND pk_test_type = %(pk_type)s',
    2530                                  'placeholder': u'pk_type'
    2531                          },
    2532                          'ctxt_test_name': {
    2533                                  'where_part': u'AND %(test_name)s IN (name_tt, name_meta, abbrev_meta)',
    2534                                  'placeholder': u'test_name'
    2535                          },
    2536                          'ctxt_ctt': {
    2537                                  'where_part': u'AND %(test_name)s IN (name, abbrev)',
    2538                                  'placeholder': u'test_name'
    2539                          },
    2540                          'ctxt_loinc': {
    2541                                  'where_part': u'AND code = %(loinc)s',
    2542                                  'placeholder': u'loinc'
    2543                          },
    2544                          'ctxt_loinc_term': {
    2545                                  'where_part': u'AND term ~* %(test_name)s',
    2546                                  'placeholder': u'test_name'
    2547                          },
    2548                          'ctxt_substance': {
    2549                                  'where_part': u'AND description ~* %(substance)s',
    2550                                  'placeholder': u'substance'
    2551                          }
    2552                  }
    2553
    2554                  mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = ctxt)
    2555                  mp.setThresholds(1, 2, 4)
    2556                  #mp.print_queries = True
    2557                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    2558                  self.matcher = mp
    2559                  self.SetToolTipString(_('Select the desired unit for the amount or measurement.'))
    2560                  self.selection_only = False
    2561                  self.phrase_separators = u'[;|]+'
    2562  #================================================================
    2563
    2564  #================================================================
    2565 -class cTestResultIndicatorPhraseWheel(gmPhraseWheel.cPhraseWheel):
    2566
    2567 -        def __init__(self, *args, **kwargs):
    2568
    2569                  query = u"""
    2570  select distinct abnormality_indicator,
    2571          abnormality_indicator, abnormality_indicator
    2572  from clin.v_test_results
    2573  where
    2574          abnormality_indicator %(fragment_condition)s
    2575  order by abnormality_indicator
    2576  limit 25"""
    2577
    2578                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    2579                  mp.setThresholds(1, 1, 2)
    2580                  mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"'
    2581                  mp.word_separators = '[ \t&:]+'
    2582                  gmPhraseWheel.cPhraseWheel.__init__ (
    2583                          self,
    2584                          *args,
    2585                          **kwargs
    2586                  )
    2587                  self.matcher = mp
    2588                  self.SetToolTipString(_('Select an indicator for the level of abnormality.'))
    2589                  self.selection_only = False
    2590
    2591  #================================================================
    2592  # measurement org widgets / functions
    2593  #----------------------------------------------------------------
    2594 -def edit_measurement_org(parent=None, org=None):
    2595          ea = cMeasurementOrgEAPnl(parent = parent, id = -1)
    2596          ea.data = org
    2597          ea.mode = gmTools.coalesce(org, 'new', 'edit')
    2598          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea)
    2599          dlg.SetTitle(gmTools.coalesce(org, _('Adding new diagnostic org'), _('Editing diagnostic org')))
    2600          if dlg.ShowModal() == wx.ID_OK:
    2601                  dlg.Destroy()
    2602                  return True
    2603          dlg.Destroy()
    2604          return False
    2605  #----------------------------------------------------------------
    2606 -def manage_measurement_orgs(parent=None):
    2607
    2608          if parent is None:
    2609                  parent = wx.GetApp().GetTopWindow()
    2610
    2611          #------------------------------------------------------------
    2612          def edit(org=None):
    2613                  return edit_measurement_org(parent = parent, org = org)
    2614          #------------------------------------------------------------
    2615          def refresh(lctrl):
    2616                  orgs = gmPathLab.get_test_orgs()
    2617                  lctrl.set_string_items ([
    2618                          (o['unit'], o['organization'], gmTools.coalesce(o['test_org_contact'], u''), gmTools.coalesce(o['comment'], u''), o['pk_test_org'])
    2619                          for o in orgs
    2620                  ])
    2621                  lctrl.set_data(orgs)
    2622          #------------------------------------------------------------
    2623          def delete(test_org):
    2624                  gmPathLab.delete_test_org(test_org = test_org['pk_test_org'])
    2625                  return True
    2626          #------------------------------------------------------------
    2627          gmListWidgets.get_choices_from_list (
    2628                  parent = parent,
    2629                  msg = _('\nThese are the diagnostic orgs (path labs etc) currently defined in GNUmed.\n\n'),
    2630                  caption = _('Showing diagnostic orgs.'),
    2631                  columns = [_('Name'), _('Organization'), _('Contact'), _('Comment'), u'#'],
    2632                  single_selection = True,
    2633                  refresh_callback = refresh,
    2634                  edit_callback = edit,
    2635                  new_callback = edit,
    2636                  delete_callback = delete
    2637          )
    2638
    2639  #----------------------------------------------------------------
    2640  from Gnumed.wxGladeWidgets import wxgMeasurementOrgEAPnl
    2641
    2642 -class cMeasurementOrgEAPnl(wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl, gmEditArea.cGenericEditAreaMixin):
    2643
    2644 -        def __init__(self, *args, **kwargs):
    2645
    2646                  try:
    2647                          data = kwargs['org']
    2648                          del kwargs['org']
    2649                  except KeyError:
    2650                          data = None
    2651
    2652                  wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl.__init__(self, *args, **kwargs)
    2653                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    2654
    2655                  self.mode = 'new'
    2656                  self.data = data
    2657                  if data is not None:
    2658                          self.mode = 'edit'
    2659
    2660                  #self.__init_ui()
    2661          #----------------------------------------------------------------
    2662  #       def __init_ui(self):
    2663  #               # adjust phrasewheels etc
    2664          #----------------------------------------------------------------
    2665          # generic Edit Area mixin API
    2666          #----------------------------------------------------------------
    2667 -        def _valid_for_save(self):
    2668                  has_errors = False
    2669                  if self._PRW_org_unit.GetData() is None:
    2670                          if self._PRW_org_unit.GetValue().strip() == u'':
    2671                                  has_errors = True
    2672                                  self._PRW_org_unit.display_as_valid(valid = False)
    2673                          else:
    2674                                  self._PRW_org_unit.display_as_valid(valid = True)
    2675                  else:
    2676                          self._PRW_org_unit.display_as_valid(valid = True)
    2677
    2678                  return (not has_errors)
    2679          #----------------------------------------------------------------
    2680 -        def _save_as_new(self):
    2681                  data = gmPathLab.create_test_org (
    2682                          name = self._PRW_org_unit.GetValue().strip(),
    2683                          comment = self._TCTRL_comment.GetValue().strip(),
    2684                          pk_org_unit = self._PRW_org_unit.GetData()
    2685                  )
    2686                  data['test_org_contact'] = self._TCTRL_contact.GetValue().strip()
    2687                  data.save()
    2688                  self.data = data
    2689                  return True
    2690          #----------------------------------------------------------------
    2691 -        def _save_as_update(self):
    2692                  # get or create the org unit
    2693                  name = self._PRW_org_unit.GetValue().strip()
    2694                  org = gmOrganization.org_exists(organization = name)
    2695                  if org is None:
    2696                          org = gmOrganization.create_org (
    2697                                  organization = name,
    2698                                  category = u'Laboratory'
    2699                          )
    2700                  org_unit = gmOrganization.create_org_unit (
    2701                          pk_organization = org['pk_org'],
    2702                          unit = name
    2703                  )
    2704                  # update test_org fields
    2705                  self.data['pk_org_unit'] = org_unit['pk_org_unit']
    2706                  self.data['test_org_contact'] = self._TCTRL_contact.GetValue().strip()
    2707                  self.data['comment'] = self._TCTRL_comment.GetValue().strip()
    2708                  self.data.save()
    2709                  return True
    2710          #----------------------------------------------------------------
    2711 -        def _refresh_as_new(self):
    2712                  self._PRW_org_unit.SetText(value = u'', data = None)
    2713                  self._TCTRL_contact.SetValue(u'')
    2714                  self._TCTRL_comment.SetValue(u'')
    2715          #----------------------------------------------------------------
    2716 -        def _refresh_from_existing(self):
    2717                  self._PRW_org_unit.SetText(value = self.data['unit'], data = self.data['pk_org_unit'])
    2718                  self._TCTRL_contact.SetValue(gmTools.coalesce(self.data['test_org_contact'], u''))
    2719                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
    2720          #----------------------------------------------------------------
    2721 -        def _refresh_as_new_from_existing(self):
    2722                  self._refresh_as_new()
    2723          #----------------------------------------------------------------
    2724 -        def _on_manage_orgs_button_pressed(self, event):
    2725                  gmOrganizationWidgets.manage_orgs(parent = self)
    2726
    2727  #----------------------------------------------------------------
    2728 -class cMeasurementOrgPhraseWheel(gmPhraseWheel.cPhraseWheel):
    2729
    2730 -        def __init__(self, *args, **kwargs):
    2731
    2732                  query = u"""
    2733  SELECT DISTINCT ON (list_label)
    2734          pk_test_org AS data,
    2735          unit || ' (' || organization || ')' AS field_label,
    2736          unit || ' @ ' || organization AS list_label
    2737  FROM clin.v_test_orgs
    2738  WHERE
    2739          unit %(fragment_condition)s
    2740                  OR
    2741          organization %(fragment_condition)s
    2742  ORDER BY list_label
    2743  LIMIT 50"""
    2744                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    2745                  mp.setThresholds(1, 2, 4)
    2746                  #mp.word_separators = '[ \t:@]+'
    2747                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    2748                  self.matcher = mp
    2749                  self.SetToolTipString(_('The name of the path lab/diagnostic organisation.'))
    2750                  self.selection_only = False
    2751          #------------------------------------------------------------
    2752 -        def _create_data(self):
    2753                  if self.GetData() is not None:
    2754                          _log.debug('data already set, not creating')
    2755                          return
    2756
    2757                  if self.GetValue().strip() == u'':
    2758                          _log.debug('cannot create new lab, missing name')
    2759                          return
    2760
    2761                  lab = gmPathLab.create_test_org(name = self.GetValue().strip())
    2762                  self.SetText(value = lab['unit'], data = lab['pk_test_org'])
    2763                  return
    2764          #------------------------------------------------------------
    2765 -        def _data2instance(self):
    2766                  return gmPathLab.cTestOrg(aPK_obj = self.GetData())
    2767
    2768  #================================================================
    2769  # Meta test type widgets
    2770  #----------------------------------------------------------------
    2771 -def edit_meta_test_type(parent=None, meta_test_type=None):
    2772          ea = cMetaTestTypeEAPnl(parent = parent, id = -1)
    2773          ea.data = meta_test_type
    2774          ea.mode = gmTools.coalesce(meta_test_type, 'new', 'edit')
    2775          dlg = gmEditArea.cGenericEditAreaDlg2 (
    2776                  parent = parent,
    2777                  id = -1,
    2778                  edit_area = ea,
    2779                  single_entry = gmTools.bool2subst((meta_test_type is None), False, True)
    2780          )
    2781          dlg.SetTitle(gmTools.coalesce(meta_test_type, _('Adding new meta test type'), _('Editing meta test type')))
    2782          if dlg.ShowModal() == wx.ID_OK:
    2783                  dlg.Destroy()
    2784                  return True
    2785          dlg.Destroy()
    2786          return False
    2787
    2788  #----------------------------------------------------------------
    2789 -def manage_meta_test_types(parent=None):
    2790
    2791          if parent is None:
    2792                  parent = wx.GetApp().GetTopWindow()
    2793
    2794          #------------------------------------------------------------
    2795          def edit(meta_test_type=None):
    2796                  return edit_meta_test_type(parent = parent, meta_test_type = meta_test_type)
    2797          #------------------------------------------------------------
    2798          def delete(meta_test_type):
    2799                  gmPathLab.delete_meta_type(meta_type = meta_test_type['pk'])
    2800                  return True
    2801          #----------------------------------------
    2802          def get_tooltip(data):
    2803                  if data is None:
    2804                          return None
    2805                  return data.format(with_tests = True)
    2806          #------------------------------------------------------------
    2807          def refresh(lctrl):
    2808                  mtts = gmPathLab.get_meta_test_types()
    2809                  items = [ [
    2810                          m['abbrev'],
    2811                          m['name'],
    2812                          gmTools.coalesce(m['loinc'], u''),
    2813                          gmTools.coalesce(m['comment'], u''),
    2814                          m['pk']
    2815                  ] for m in mtts ]
    2816                  lctrl.set_string_items(items)
    2817                  lctrl.set_data(mtts)
    2818          #----------------------------------------
    2819
    2820          msg = _(
    2821                  '\n'
    2822                  'These are the meta test types currently defined in GNUmed.\n'
    2823                  '\n'
    2824                  'Meta test types allow you to aggregate several actual test types used\n'
    2825                  'by pathology labs into one logical type.\n'
    2826                  '\n'
    2827                  'This is useful for grouping together results of tests which come under\n'
    2828                  'different names but really are the same thing. This often happens when\n'
    2829                  'you switch labs or the lab starts using another test method.\n'
    2830          )
    2831
    2832          gmListWidgets.get_choices_from_list (
    2833                  parent = parent,
    2834                  msg = msg,
    2835                  caption = _('Showing meta test types.'),
    2836                  columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), u'#'],
    2837                  single_selection = True,
    2838                  list_tooltip_callback = get_tooltip,
    2839                  edit_callback = edit,
    2840                  new_callback = edit,
    2841                  delete_callback = delete,
    2842                  refresh_callback = refresh
    2843          )
    2844
    2845  #----------------------------------------------------------------
    2846 -class cMetaTestTypePRW(gmPhraseWheel.cPhraseWheel):
    2847
    2848 -        def __init__(self, *args, **kwargs):
    2849
    2850                  query = u"""
    2851  SELECT DISTINCT ON (field_label)
    2852          c_mtt.pk
    2853                  AS data,
    2854          c_mtt.abbrev || ': ' || name
    2855                  AS field_label,
    2856          c_mtt.abbrev || ': ' || name
    2857                  ||      coalesce (
    2858                                  ' (' || c_mtt.comment || ')',
    2859                                  ''
    2860                          )
    2861                  ||      coalesce (
    2862                                  ', LOINC: ' || c_mtt.loinc,
    2863                                  ''
    2864                          )
    2865          AS list_label
    2866  FROM
    2867          clin.meta_test_type c_mtt
    2868  WHERE
    2869          abbrev %(fragment_condition)s
    2870                  OR
    2871          name %(fragment_condition)s
    2872                  OR
    2873          loinc %(fragment_condition)s
    2874  ORDER BY field_label
    2875  LIMIT 50"""
    2876
    2877                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    2878                  mp.setThresholds(1, 2, 4)
    2879                  mp.word_separators = '[ \t:@]+'
    2880                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    2881                  self.matcher = mp
    2882                  self.SetToolTipString(_('Select the meta test type.'))
    2883                  self.selection_only = True
    2884          #------------------------------------------------------------
    2885 -        def _data2instance(self):
    2886                  if self.GetData() is None:
    2887                          return None
    2888
    2889                  return gmPathLab.cMetaTestType(aPK_obj = self.GetData())
    2890
    2891  #----------------------------------------------------------------
    2892  from Gnumed.wxGladeWidgets import wxgMetaTestTypeEAPnl
    2893
    2894 -class cMetaTestTypeEAPnl(wxgMetaTestTypeEAPnl.wxgMetaTestTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
    2895
    2896 -        def __init__(self, *args, **kwargs):
    2897
    2898                  try:
    2899                          data = kwargs['meta_test_type']
    2900                          del kwargs['meta_test_type']
    2901                  except KeyError:
    2902                          data = None
    2903
    2904                  wxgMetaTestTypeEAPnl.wxgMetaTestTypeEAPnl.__init__(self, *args, **kwargs)
    2905                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    2906
    2907                  # Code using this mixin should set mode and data
    2908                  # after instantiating the class:
    2909                  self.mode = 'new'
    2910                  self.data = data
    2911                  if data is not None:
    2912                          self.mode = 'edit'
    2913
    2914                  self.__init_ui()
    2915          #----------------------------------------------------------------
    2916 -        def __init_ui(self):
    2917                  # loinc
    2918                  mp = gmLOINC.cLOINCMatchProvider()
    2919                  mp.setThresholds(1, 2, 4)
    2920                  #mp.print_queries = True
    2921                  #mp.word_separators = '[ \t:@]+'
    2922                  self._PRW_loinc.matcher = mp
    2923                  self._PRW_loinc.selection_only = False
    2924                  self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
    2925          #----------------------------------------------------------------
    2926          # generic Edit Area mixin API
    2927          #----------------------------------------------------------------
    2928 -        def _valid_for_save(self):
    2929
    2930                  validity = True
    2931
    2932                  if self._PRW_abbreviation.GetValue().strip() == u'':
    2933                          validity = False
    2934                          self._PRW_abbreviation.display_as_valid(False)
    2935                          self.status_message = _('Missing abbreviation for meta test type.')
    2936                          self._PRW_abbreviation.SetFocus()
    2937                  else:
    2938                          self._PRW_abbreviation.display_as_valid(True)
    2939
    2940                  if self._PRW_name.GetValue().strip() == u'':
    2941                          validity = False
    2942                          self._PRW_name.display_as_valid(False)
    2943                          self.status_message = _('Missing name for meta test type.')
    2944                          self._PRW_name.SetFocus()
    2945                  else:
    2946                          self._PRW_name.display_as_valid(True)
    2947
    2948                  return validity
    2949          #----------------------------------------------------------------
    2950 -        def _save_as_new(self):
    2951
    2952                  # save the data as a new instance
    2953                  data = gmPathLab.create_meta_type (
    2954                          name = self._PRW_name.GetValue().strip(),
    2955                          abbreviation = self._PRW_abbreviation.GetValue().strip(),
    2956                          return_existing = False
    2957                  )
    2958                  if data is None:
    2959                          self.status_message = _('This meta test type already exists.')
    2960                          return False
    2961                  data['loinc'] = self._PRW_loinc.GetData()
    2962                  data['comment'] = self._TCTRL_comment.GetValue().strip()
    2963                  data.save()
    2964                  self.data = data
    2965                  return True
    2966          #----------------------------------------------------------------
    2967 -        def _save_as_update(self):
    2968                  self.data['name'] = self._PRW_name.GetValue().strip()
    2969                  self.data['abbrev'] = self._PRW_abbreviation.GetValue().strip()
    2970                  self.data['loinc'] = self._PRW_loinc.GetData()
    2971                  self.data['comment'] = self._TCTRL_comment.GetValue().strip()
    2972                  self.data.save()
    2973                  return True
    2974          #----------------------------------------------------------------
    2975 -        def _refresh_as_new(self):
    2976                  self._PRW_name.SetText(u'', None)
    2977                  self._PRW_abbreviation.SetText(u'', None)
    2978                  self._PRW_loinc.SetText(u'', None)
    2979                  self._TCTRL_loinc_info.SetValue(u'')
    2980                  self._TCTRL_comment.SetValue(u'')
    2981                  self._LBL_member_detail.SetLabel(u'')
    2982
    2983                  self._PRW_name.SetFocus()
    2984          #----------------------------------------------------------------
    2985 -        def _refresh_as_new_from_existing(self):
    2986                  self._refresh_as_new()
    2987          #----------------------------------------------------------------
    2988 -        def _refresh_from_existing(self):
    2989                  self._PRW_name.SetText(self.data['name'], self.data['pk'])
    2990                  self._PRW_abbreviation.SetText(self.data['abbrev'], self.data['abbrev'])
    2991                  self._PRW_loinc.SetText(gmTools.coalesce(self.data['loinc'], u''), self.data['loinc'])
    2992                  self.__refresh_loinc_info()
    2993                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
    2994                  self.__refresh_members()
    2995
    2996                  self._PRW_name.SetFocus()
    2997          #----------------------------------------------------------------
    2998          # event handlers
    2999          #----------------------------------------------------------------
    3000 -        def _on_loinc_lost_focus(self):
    3001                  self.__refresh_loinc_info()
    3002          #----------------------------------------------------------------
    3003          # internal helpers
    3004          #----------------------------------------------------------------
    3005 -        def __refresh_loinc_info(self):
    3006                  loinc = self._PRW_loinc.GetData()
    3007
    3008                  if loinc is None:
    3009                          self._TCTRL_loinc_info.SetValue(u'')
    3010                          return
    3011
    3012                  info = gmLOINC.loinc2term(loinc = loinc)
    3013                  if len(info) == 0:
    3014                          self._TCTRL_loinc_info.SetValue(u'')
    3015                          return
    3016
    3017                  self._TCTRL_loinc_info.SetValue(info[0])
    3018          #----------------------------------------------------------------
    3019 -        def __refresh_members(self):
    3020                  if self.data is None:
    3021                          self._LBL_member_detail.SetLabel(u'')
    3022                          return
    3023
    3024                  types = self.data.included_test_types
    3025                  if len(types) == 0:
    3026                          self._LBL_member_detail.SetLabel(u'')
    3027                          return
    3028
    3029                  lines = []
    3030                  for tt in types:
    3031                          lines.append(u'%s (%s%s) [#%s] @ %s' % (
    3032                                  tt['name'],
    3033                                  tt['abbrev'],
    3034                                  gmTools.coalesce(tt['loinc'], u'', u', LOINC: %s'),
    3035                                  tt['pk_test_type'],
    3036                                  tt['name_org']
    3037                          ))
    3038                  self._LBL_member_detail.SetLabel(u'\n'.join(lines))
    3039
    3040  #================================================================
    3041  # test panel handling
    3042  #================================================================
    3043 -def edit_test_panel(parent=None, test_panel=None):
    3044          ea = cTestPanelEAPnl(parent = parent, id = -1)
    3045          ea.data = test_panel
    3046          ea.mode = gmTools.coalesce(test_panel, 'new', 'edit')
    3047          dlg = gmEditArea.cGenericEditAreaDlg2 (
    3048                  parent = parent,
    3049                  id = -1,
    3050                  edit_area = ea,
    3051                  single_entry = gmTools.bool2subst((test_panel is None), False, True)
    3052          )
    3053          dlg.SetTitle(gmTools.coalesce(test_panel, _('Adding new test panel'), _('Editing test panel')))
    3054          if dlg.ShowModal() == wx.ID_OK:
    3055                  dlg.Destroy()
    3056                  return True
    3057          dlg.Destroy()
    3058          return False
    3059
    3060  #----------------------------------------------------------------
    3061 -def manage_test_panels(parent=None):
    3062
    3063          if parent is None:
    3064                  parent = wx.GetApp().GetTopWindow()
    3065
    3066          #------------------------------------------------------------
    3067          def edit(test_panel=None):
    3068                  return edit_test_panel(parent = parent, test_panel = test_panel)
    3069          #------------------------------------------------------------
    3070          def delete(test_panel):
    3071                  gmPathLab.delete_test_panel(pk = test_panel['pk_test_panel'])
    3072                  return True
    3073          #------------------------------------------------------------
    3074          def get_tooltip(test_panel):
    3075                  return test_panel.format()
    3076          #------------------------------------------------------------
    3077          def refresh(lctrl):
    3078                  panels = gmPathLab.get_test_panels(order_by = 'description')
    3079                  items = [ [
    3080                          p['description'],
    3081                          gmTools.coalesce(p['comment'], u''),
    3082                          p['pk_test_panel']
    3083                  ] for p in panels ]
    3084                  lctrl.set_string_items(items)
    3085                  lctrl.set_data(panels)
    3086          #------------------------------------------------------------
    3087          msg = _(
    3088                  '\n'
    3089                  'Test panels as defined in GNUmed.\n'
    3090          )
    3091
    3092          gmListWidgets.get_choices_from_list (
    3093                  parent = parent,
    3094                  msg = msg,
    3095                  caption = _('Showing test panels.'),
    3096                  columns = [ _('Name'), _('Comment'), u'#' ],
    3097                  single_selection = True,
    3098                  refresh_callback = refresh,
    3099                  edit_callback = edit,
    3100                  new_callback = edit,
    3101                  delete_callback = delete,
    3102                  list_tooltip_callback = get_tooltip
    3103          )
    3104
    3105  #----------------------------------------------------------------
    3106 -class cTestPanelPRW(gmPhraseWheel.cPhraseWheel):
    3107
    3108 -        def __init__(self, *args, **kwargs):
    3109                  query = u"""
    3110  SELECT
    3111          pk_test_panel
    3112                  AS data,
    3113          description
    3114                  AS field_label,
    3115          description
    3116                  AS list_label
    3117  FROM
    3118          clin.v_test_panels
    3119  WHERE
    3120          description %(fragment_condition)s
    3121  ORDER BY field_label
    3122  LIMIT 30"""
    3123                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    3124                  mp.setThresholds(1, 2, 4)
    3125                  #mp.word_separators = '[ \t:@]+'
    3126                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    3127                  self.matcher = mp
    3128                  self.SetToolTipString(_('Select a test panel.'))
    3129                  self.selection_only = True
    3130          #------------------------------------------------------------
    3131 -        def _data2instance(self):
    3132                  if self.GetData() is None:
    3133                          return None
    3134                  return gmPathLab.cTestPanel(aPK_obj = self.GetData())
    3135          #------------------------------------------------------------
    3136 -        def _get_data_tooltip(self):
    3137                  if self.GetData() is None:
    3138                          return None
    3139                  return gmPathLab.cTestPanel(aPK_obj = self.GetData()).format()
    3140
    3141  #====================================================================
    3142  from Gnumed.wxGladeWidgets import wxgTestPanelEAPnl
    3143
    3144 -class cTestPanelEAPnl(wxgTestPanelEAPnl.wxgTestPanelEAPnl, gmEditArea.cGenericEditAreaMixin):
    3145
    3146 -        def __init__(self, *args, **kwargs):
    3147
    3148                  try:
    3149                          data = kwargs['panel']
    3150                          del kwargs['panel']
    3151                  except KeyError:
    3152                          data = None
    3153
    3154                  wxgTestPanelEAPnl.wxgTestPanelEAPnl.__init__(self, *args, **kwargs)
    3155                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    3156
    3157                  self._test_types = None
    3158
    3159                  self.mode = 'new'
    3160                  self.data = data
    3161                  if data is not None:
    3162                          self.mode = 'edit'
    3163
    3164                  #self.__init_ui()
    3165          #----------------------------------------------------------------
    3166  #       def __init_ui(self):
    3167  #               # adjust phrasewheels etc
    3168          #----------------------------------------------------------------
    3169          # generic Edit Area mixin API
    3170          #----------------------------------------------------------------
    3171 -        def _valid_for_save(self):
    3172                  validity = True
    3173
    3174                  if self._test_types is None:
    3175                          validity = False
    3176                          gmDispatcher.send(signal = 'statustext', msg = _('No test types selected.'))
    3177                          self._BTN_select_tests.SetFocus()
    3178
    3179                  if self._TCTRL_description.GetValue().strip() == u'':
    3180                          validity = False
    3181                          self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False)
    3182                          self._TCTRL_description.SetFocus()
    3183                  else:
    3184                          self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True)
    3185
    3186                  return validity
    3187          #----------------------------------------------------------------
    3188 -        def _save_as_new(self):
    3189                  data = gmPathLab.create_test_panel(description = self._TCTRL_description.GetValue().strip())
    3190                  data['comment'] = self._TCTRL_comment.GetValue().strip()
    3191                  data['pk_test_types'] = [ tt['pk_test_type'] for tt in self._test_types ]
    3192                  data.save()
    3193                  data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
    3194                  self.data = data
    3195                  return True
    3196          #----------------------------------------------------------------
    3197 -        def _save_as_update(self):
    3198                  self.data['description'] = self._TCTRL_description.GetValue().strip()
    3199                  self.data['comment'] = self._TCTRL_comment.GetValue().strip()
    3200                  self.data['pk_test_types'] = [ tt['pk_test_type'] for tt in self._test_types ]
    3201                  self.data.save()
    3202                  self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
    3203                  return True
    3204          #----------------------------------------------------------------
    3205 -        def __refresh_test_types_field(self, test_types=None):
    3206                  self._TCTRL_tests.SetValue(u'')
    3207                  self._test_types = test_types
    3208                  if self._test_types is None:
    3209                          return
    3210                  tmp = u';\n'.join ([
    3211                          u'%s: %s%s' % (
    3212                                  t['unified_abbrev'],
    3213                                  t['unified_name'],
    3214                                  gmTools.coalesce(t['name_org'], u'', u' (%s)')
    3215                          )
    3216                          for t in self._test_types
    3217                  ])
    3218                  self._TCTRL_tests.SetValue(tmp)
    3219          #----------------------------------------------------------------
    3220 -        def _refresh_as_new(self):
    3221                  self._TCTRL_description.SetValue(u'')
    3222                  self._TCTRL_comment.SetValue(u'')
    3223                  self.__refresh_test_types_field()
    3224                  self._PRW_codes.SetText()
    3225
    3226                  self._TCTRL_description.SetFocus()
    3227          #----------------------------------------------------------------
    3228 -        def _refresh_as_new_from_existing(self):
    3229                  self._refresh_as_new()
    3230                  self.__refresh_test_types_field(test_types = self.data.test_types)
    3231          #----------------------------------------------------------------
    3232 -        def _refresh_from_existing(self):
    3233                  self._TCTRL_description.SetValue(self.data['description'])
    3234                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
    3235                  self.__refresh_test_types_field(test_types = self.data.test_types)
    3236                  val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
    3237                  self._PRW_codes.SetText(val, data)
    3238
    3239                  self._BTN_select_tests.SetFocus()
    3240          #----------------------------------------------------------------
    3241 -        def _on_select_tests_button_pressed(self, event):
    3242                  desc = self._TCTRL_description.GetValue().strip()
    3243                  if desc == u'':
    3244                          desc = None
    3245                  picked = pick_measurement_types (
    3246                          parent = self,
    3247                          msg = _('Pick the measurement types for this panel.'),
    3248                          right_column = desc,
    3249                          picks = self._test_types
    3250                  )
    3251                  if picked is None:
    3252                          return
    3253                  if len(picked) == 0:
    3254                          picked = None
    3255                  self.__refresh_test_types_field(test_types = picked)
    3256
    3257  #================================================================
    3258  # main
    3259  #----------------------------------------------------------------
    3260  if __name__ == '__main__':
    3261
    3262          from Gnumed.pycommon import gmLog2
    3263          from Gnumed.wxpython import gmPatSearchWidgets
    3264
    3265          gmI18N.activate_locale()
    3266          gmI18N.install_domain()
    3267          gmDateTime.init()
    3268
    3269          #------------------------------------------------------------
    3270 -        def test_grid():
    3271                  pat = gmPersonSearch.ask_for_patient()
    3272                  app = wx.PyWidgetTester(size = (500, 300))
    3273                  lab_grid = cMeasurementsGrid(parent = app.frame, id = -1)
    3274                  lab_grid.patient = pat
    3275                  app.frame.Show()
    3276                  app.MainLoop()
    3277          #------------------------------------------------------------
    3278 -        def test_test_ea_pnl():
    3279                  pat = gmPersonSearch.ask_for_patient()
    3280                  gmPatSearchWidgets.set_active_patient(patient=pat)
    3281                  app = wx.PyWidgetTester(size = (500, 300))
    3282                  ea = cMeasurementEditAreaPnl(parent = app.frame, id = -1)
    3283                  app.frame.Show()
    3284                  app.MainLoop()
    3285          #------------------------------------------------------------
    3286  #       def test_primary_care_vitals_pnl():
    3287  #               app = wx.PyWidgetTester(size = (500, 300))
    3288  #               pnl = wxgPrimaryCareVitalsInputPnl.wxgPrimaryCareVitalsInputPnl(parent = app.frame, id = -1)
    3289  #               app.frame.Show()
    3290  #               app.MainLoop()
    3291          #------------------------------------------------------------
    3292          if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
    3293                  #test_grid()
    3294                  test_test_ea_pnl()
    3295                  #test_primary_care_vitals_pnl()
    3296
    3297  #================================================================
    3298