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