Package Gnumed :: Package wxpython :: Module gmMeasurementWidgets |
[frames] | no frames]

# Source Code for Module Gnumed.wxpython.gmMeasurementWidgets

       1  """GNUmed measurement widgets."""
       2  #================================================================
       3  __version__ = "$Revision: 1.66 $"
       4  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
       5  __license__ = "GPL"
       6
       7
       8  import sys, logging, datetime as pyDT, decimal, os, subprocess, codecs
       9  import os.path
      10
      11
      12  import wx, wx.grid, wx.lib.hyperlink
      13
      14
      15  if __name__ == '__main__':
      16          sys.path.insert(0, '../../')
      17  from Gnumed.business import gmPerson
      18  from Gnumed.business import gmStaff
      19  from Gnumed.business import gmPathLab
      20  from Gnumed.business import gmSurgery
      21  from Gnumed.business import gmLOINC
      22  from Gnumed.business import gmForms
      23  from Gnumed.business import gmPersonSearch
      24  from Gnumed.business import gmOrganization
      25
      26  from Gnumed.pycommon import gmTools
      27  from Gnumed.pycommon import gmNetworkTools
      28  from Gnumed.pycommon import gmI18N
      29  from Gnumed.pycommon import gmShellAPI
      30  from Gnumed.pycommon import gmCfg
      31  from Gnumed.pycommon import gmDateTime
      32  from Gnumed.pycommon import gmMatchProvider
      33  from Gnumed.pycommon import gmDispatcher
      34
      35  from Gnumed.wxpython import gmRegetMixin
      36  from Gnumed.wxpython import gmPhraseWheel
      37  from Gnumed.wxpython import gmEditArea
      38  from Gnumed.wxpython import gmGuiHelpers
      39  from Gnumed.wxpython import gmListWidgets
      40  from Gnumed.wxpython import gmAuthWidgets
      41  from Gnumed.wxpython import gmFormWidgets
      42  from Gnumed.wxpython import gmPatSearchWidgets
      43  from Gnumed.wxpython import gmOrganizationWidgets
      44
      45
      46  _log = logging.getLogger('gm.ui')
      47  _log.info(__version__)
      48
      49  #================================================================
      50  # LOINC related widgets
      51  #================================================================
      52 -def update_loinc_reference_data():
      53
      54          wx.BeginBusyCursor()
      55
      56          gmDispatcher.send(signal = 'statustext', msg = _('Updating LOINC data can take quite a while...'), beep = True)
      57
      58          # download
      59          downloaded, loinc_dir = gmNetworkTools.download_data_pack(url = 'http://www.gnumed.de/downloads/data/loinc/loinctab.zip')
      60          if not downloaded:
      61                  wx.EndBusyCursor()
      62                  gmGuiHelpers.gm_show_warning (
      63                          aTitle = _('Downloading LOINC'),
      64                          aMessage = _('Error downloading the latest LOINC data.\n')
      65                  )
      66                  return False
      67
      68          # split master data file
      69          data_fname, license_fname = gmLOINC.split_LOINCDBTXT(input_fname = os.path.join(loinc_dir, 'LOINCDB.TXT'))
      70
      71          wx.EndBusyCursor()
      72
      73          conn = gmAuthWidgets.get_dbowner_connection(procedure = _('importing LOINC reference data'))
      74          if conn is None:
      75                  return False
      76
      77          wx.BeginBusyCursor()
      78
      79          # import data
      80          if gmLOINC.loinc_import(data_fname = data_fname, license_fname = license_fname, conn = conn):
      81                  gmDispatcher.send(signal = 'statustext', msg = _('Successfully imported LOINC reference data.'))
      82          else:
      83                  gmDispatcher.send(signal = 'statustext', msg = _('Importing LOINC reference data failed.'), beep = True)
      84
      85          wx.EndBusyCursor()
      86          return True
      87  #================================================================
      88  # convenience functions
      89  #================================================================
      90 -def call_browser_on_measurement_type(measurement_type=None):
      91
      92          dbcfg = gmCfg.cCfgSQL()
      93
      94          url = dbcfg.get (
      95                  option = u'external.urls.measurements_search',
      96                  workplace = gmSurgery.gmCurrentPractice().active_workplace,
      97                  bias = 'user',
      98                  default = u"http://www.google.de/search?as_oq=%(search_term)s&num=10&as_sitesearch=laborlexikon.de"
      99          )
     100
     101          base_url = dbcfg.get2 (
     102                  option = u'external.urls.measurements_encyclopedia',
     103                  workplace = gmSurgery.gmCurrentPractice().active_workplace,
     104                  bias = 'user',
     105                  default = u'http://www.laborlexikon.de'
     106          )
     107
     108          if measurement_type is None:
     109                  url = base_url
     110
     111          measurement_type = measurement_type.strip()
     112
     113          if measurement_type == u'':
     114                  url = base_url
     115
     116          url = url % {'search_term': measurement_type}
     117
     118          gmNetworkTools.open_url_in_browser(url = url)
     119  #----------------------------------------------------------------
     120 -def edit_measurement(parent=None, measurement=None, single_entry=False):
     121          ea = cMeasurementEditAreaPnl(parent = parent, id = -1)
     122          ea.data = measurement
     123          ea.mode = gmTools.coalesce(measurement, 'new', 'edit')
     124          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
     125          dlg.SetTitle(gmTools.coalesce(measurement, _('Adding new measurement'), _('Editing measurement')))
     126          if dlg.ShowModal() == wx.ID_OK:
     127                  dlg.Destroy()
     128                  return True
     129          dlg.Destroy()
     130          return False
     131  #================================================================
     132 -def plot_measurements(parent=None, tests=None):
     133
     134          template = gmFormWidgets.manage_form_templates (
     135                  parent = parent,
     136                  active_only = True,
     137                  template_types = [u'gnuplot script']
     138          )
     139
     140          if template is None:
     141                  gmGuiHelpers.gm_show_error (
     142                          aMessage = _('Cannot plot without a plot script.'),
     143                          aTitle = _('Plotting test results')
     144                  )
     145                  return False
     146
     147          fname_data = gmPathLab.export_results_for_gnuplot(results = tests)
     148
     149          script = template.instantiate()
     150          script.data_filename = fname_data
     151          script.generate_output(format = 'wxp')          # Gnuplot output terminal
     152
     153  #================================================================
     154  #from Gnumed.wxGladeWidgets import wxgPrimaryCareVitalsInputPnl
     155
     156  # Taillenumfang: Mitte zwischen unterster Rippe und
     157  # hoechstem Teil des Beckenkamms
     158  # Maenner: maessig: 94-102, deutlich: > 102  .. erhoeht
     159  # Frauen:  maessig: 80-88,  deutlich: > 88   .. erhoeht
     160
     161  #================================================================
     162  # display widgets
     163  #================================================================
     164 -class cMeasurementsGrid(wx.grid.Grid):
     165          """A grid class for displaying measurment results.
     166
     167          - does NOT listen to the currently active patient
     168          - thereby it can display any patient at any time
     169          """
     170          # FIXME: sort-by-battery
     171          # FIXME: filter-by-battery
     172          # FIXME: filter out empty
     173          # FIXME: filter by tests of a selected date
     174          # FIXME: dates DESC/ASC by cfg
     175          # FIXME: mouse over column header: display date info
     176 -        def __init__(self, *args, **kwargs):
     177
     178                  wx.grid.Grid.__init__(self, *args, **kwargs)
     179
     180                  self.__patient = None
     181                  self.__cell_data = {}
     182                  self.__row_label_data = []
     183
     184                  self.__prev_row = None
     185                  self.__prev_col = None
     186                  self.__prev_label_row = None
     187                  self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::'))
     188
     189                  self.__init_ui()
     190                  self.__register_events()
     191          #------------------------------------------------------------
     192          # external API
     193          #------------------------------------------------------------
     194 -        def delete_current_selection(self):
     195                  if not self.IsSelection():
     196                          gmDispatcher.send(signal = u'statustext', msg = _('No results selected for deletion.'))
     197                          return True
     198
     199                  selected_cells = self.get_selected_cells()
     200                  if len(selected_cells) > 20:
     201                          results = None
     202                          msg = _(
     203                                  'There are %s results marked for deletion.\n'
     204                                  '\n'
     205                                  'Are you sure you want to delete these results ?'
     206                          ) % len(selected_cells)
     207                  else:
     208                          results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
     209                          txt = u'\n'.join([ u'%s %s (%s): %s %s%s' % (
     210                                          r['clin_when'].strftime('%x %H:%M').decode(gmI18N.get_encoding()),
     211                                          r['unified_abbrev'],
     212                                          r['unified_name'],
     213                                          r['unified_val'],
     214                                          r['val_unit'],
     215                                          gmTools.coalesce(r['abnormality_indicator'], u'', u' (%s)')
     216                                  ) for r in results
     217                          ])
     218                          msg = _(
     219                                  'The following results are marked for deletion:\n'
     220                                  '\n'
     221                                  '%s\n'
     222                                  '\n'
     223                                  'Are you sure you want to delete these results ?'
     224                          ) % txt
     225
     226                  dlg = gmGuiHelpers.c2ButtonQuestionDlg (
     227                          self,
     228                          -1,
     229                          caption = _('Deleting test results'),
     230                          question = msg,
     231                          button_defs = [
     232                                  {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False},
     233                                  {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True}
     234                          ]
     235                  )
     236                  decision = dlg.ShowModal()
     237
     238                  if decision == wx.ID_YES:
     239                          if results is None:
     240                                  results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
     241                          for result in results:
     242                                  gmPathLab.delete_test_result(result)
     243          #------------------------------------------------------------
     244 -        def sign_current_selection(self):
     245                  if not self.IsSelection():
     246                          gmDispatcher.send(signal = u'statustext', msg = _('Cannot sign results. No results selected.'))
     247                          return True
     248
     249                  selected_cells = self.get_selected_cells()
     250                  if len(selected_cells) > 10:
     251                          test_count = len(selected_cells)
     252                          tests = None
     253                  else:
     254                          test_count = None
     255                          tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
     256                          if len(tests) == 0:
     257                                  return True
     258
     259                  dlg = cMeasurementsReviewDlg (
     260                          self,
     261                          -1,
     262                          tests = tests,
     263                          test_count = test_count
     264                  )
     265                  decision = dlg.ShowModal()
     266
     267                  if decision == wx.ID_APPLY:
     268                          wx.BeginBusyCursor()
     269
     270                          if dlg._RBTN_confirm_abnormal.GetValue():
     271                                  abnormal = None
     272                          elif dlg._RBTN_results_normal.GetValue():
     273                                  abnormal = False
     274                          else:
     275                                  abnormal = True
     276
     277                          if dlg._RBTN_confirm_relevance.GetValue():
     278                                  relevant = None
     279                          elif dlg._RBTN_results_not_relevant.GetValue():
     280                                  relevant = False
     281                          else:
     282                                  relevant = True
     283
     284                          if tests is None:
     285                                  tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
     286
     287                          comment = None
     288                          if len(tests) == 1:
     289                                  comment = dlg._TCTRL_comment.GetValue()
     290
     291                          for test in tests:
     292                                  test.set_review (
     293                                          technically_abnormal = abnormal,
     294                                          clinically_relevant = relevant,
     295                                          comment = comment,
     296                                          make_me_responsible = dlg._CHBOX_responsible.IsChecked()
     297                                  )
     298
     299                          wx.EndBusyCursor()
     300
     301                  dlg.Destroy()
     302          #------------------------------------------------------------
     303 -        def plot_current_selection(self):
     304
     305                  if not self.IsSelection():
     306                          gmDispatcher.send(signal = u'statustext', msg = _('Cannot plot results. No results selected.'))
     307                          return True
     308
     309                  tests = self.__cells_to_data (
     310                          cells = self.get_selected_cells(),
     311                          exclude_multi_cells = False,
     312                          auto_include_multi_cells = True
     313                  )
     314
     315                  plot_measurements(parent = self, tests = tests)
     316          #------------------------------------------------------------
     317 -        def get_selected_cells(self):
     318
     319                  sel_block_top_left = self.GetSelectionBlockTopLeft()
     320                  sel_block_bottom_right = self.GetSelectionBlockBottomRight()
     321                  sel_cols = self.GetSelectedCols()
     322                  sel_rows = self.GetSelectedRows()
     323
     324                  selected_cells = []
     325
     326                  # individually selected cells (ctrl-click)
     327                  selected_cells += self.GetSelectedCells()
     328
     329                  # selected rows
     330                  selected_cells += list (
     331                          (row, col)
     332                                  for row in sel_rows
     333                                  for col in xrange(self.GetNumberCols())
     334                  )
     335
     336                  # selected columns
     337                  selected_cells += list (
     338                          (row, col)
     339                                  for row in xrange(self.GetNumberRows())
     340                                  for col in sel_cols
     341                  )
     342
     343                  # selection blocks
     344                  for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()):
     345                          selected_cells += [
     346                                  (row, col)
     347                                          for row in xrange(top_left[0], bottom_right[0] + 1)
     348                                          for col in xrange(top_left[1], bottom_right[1] + 1)
     349                          ]
     350
     351                  return set(selected_cells)
     352          #------------------------------------------------------------
     353 -        def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
     354                  """Select a range of cells according to criteria.
     355
     356                  unsigned_only: include only those which are not signed at all yet
     357                  accountable_only: include only those for which the current user is responsible
     358                  keep_preselections: broaden (rather than replace) the range of selected cells
     359
     360                  Combinations are powerful !
     361                  """
     362                  wx.BeginBusyCursor()
     363                  self.BeginBatch()
     364
     365                  if not keep_preselections:
     366                          self.ClearSelection()
     367
     368                  for col_idx in self.__cell_data.keys():
     369                          for row_idx in self.__cell_data[col_idx].keys():
     370                                  # loop over results in cell and only include
     371                                  # those multi-value cells that are not ambiguous
     372                                  do_not_include = False
     373                                  for result in self.__cell_data[col_idx][row_idx]:
     374                                          if unsigned_only:
     375                                                  if result['reviewed']:
     376                                                          do_not_include = True
     377                                                          break
     378                                          if accountables_only:
     379                                                  if not result['you_are_responsible']:
     380                                                          do_not_include = True
     381                                                          break
     382                                  if do_not_include:
     383                                          continue
     384
     385                                  self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True)
     386
     387                  self.EndBatch()
     388                  wx.EndBusyCursor()
     389          #------------------------------------------------------------
     390 -        def repopulate_grid(self):
     391
     392                  self.empty_grid()
     393                  if self.__patient is None:
     394                          return
     395
     396                  emr = self.__patient.get_emr()
     397
     398                  self.__row_label_data = emr.get_test_types_for_results()
     399                  test_type_labels = [ u'%s (%s)' % (test['unified_abbrev'], test['unified_name']) for test in self.__row_label_data ]
     400                  if len(test_type_labels) == 0:
     401                          return
     402
     403                  test_date_labels = [ date[0].strftime(self.__date_format) for date in emr.get_dates_for_results() ]
     404                  results = emr.get_test_results_by_date()
     405
     406                  self.BeginBatch()
     407
     408                  # rows
     409                  self.AppendRows(numRows = len(test_type_labels))
     410                  for row_idx in range(len(test_type_labels)):
     411                          self.SetRowLabelValue(row_idx, test_type_labels[row_idx])
     412
     413                  # columns
     414                  self.AppendCols(numCols = len(test_date_labels))
     415                  for date_idx in range(len(test_date_labels)):
     416                          self.SetColLabelValue(date_idx, test_date_labels[date_idx])
     417
     418                  # cell values (list of test results)
     419                  for result in results:
     420                          row = test_type_labels.index(u'%s (%s)' % (result['unified_abbrev'], result['unified_name']))
     421                          col = test_date_labels.index(result['clin_when'].strftime(self.__date_format))
     422
     423                          try:
     424                                  self.__cell_data[col]
     425                          except KeyError:
     426                                  self.__cell_data[col] = {}
     427
     428                          # the tooltip always shows the youngest sub result details
     429                          if self.__cell_data[col].has_key(row):
     430                                  self.__cell_data[col][row].append(result)
     431                                  self.__cell_data[col][row].sort(key = lambda x: x['clin_when'], reverse = True)
     432                          else:
     433                                  self.__cell_data[col][row] = [result]
     434
     435                          # rebuild cell display string
     436                          vals2display = []
     437                          for sub_result in self.__cell_data[col][row]:
     438
     439                                  # is the sub_result technically abnormal ?
     440                                  ind = gmTools.coalesce(sub_result['abnormality_indicator'], u'').strip()
     441                                  if ind != u'':
     442                                          lab_abnormality_indicator = u' (%s)' % ind[:3]
     443                                  else:
     444                                          lab_abnormality_indicator = u''
     445                                  # - if noone reviewed - use what the lab thinks
     446                                  if sub_result['is_technically_abnormal'] is None:
     447                                          abnormality_indicator = lab_abnormality_indicator
     448                                  # - if someone reviewed and decreed normality - use that
     449                                  elif sub_result['is_technically_abnormal'] is False:
     450                                          abnormality_indicator = u''
     451                                  # - if someone reviewed and decreed abnormality ...
     452                                  else:
     453                                          # ... invent indicator if the lab did't use one
     454                                          if lab_abnormality_indicator == u'':
     455                                                  # FIXME: calculate from min/max/range
     456                                                  abnormality_indicator = u' (%s)' % gmTools.u_plus_minus
     457                                          # ... else use indicator the lab used
     458                                          else:
     459                                                  abnormality_indicator = lab_abnormality_indicator
     460
     461                                  # is the sub_result relevant clinically ?
     462                                  # FIXME: take into account primary_GP once we support that
     463                                  sub_result_relevant = sub_result['is_clinically_relevant']
     464                                  if sub_result_relevant is None:
     465                                          # FIXME: calculate from clinical range
     466                                          sub_result_relevant = False
     467
     468                                  missing_review = False
     469                                  # warn on missing review if
     470                                  # a) no review at all exists or
     471                                  if not sub_result['reviewed']:
     472                                          missing_review = True
     473                                  # b) there is a review but
     474                                  else:
     475                                          # current user is reviewer and hasn't reviewed
     476                                          if sub_result['you_are_responsible'] and not sub_result['review_by_you']:
     477                                                  missing_review = True
     478
     479                                  # can we display the full sub_result length ?
     480                                  if len(sub_result['unified_val']) > 8:
     481                                          tmp = u'%.7s%s' % (sub_result['unified_val'][:7], gmTools.u_ellipsis)
     482                                  else:
     483                                          tmp = u'%.8s' % sub_result['unified_val'][:8]
     484
     485                                  # abnormal ?
     486                                  tmp = u'%s%.6s' % (tmp, abnormality_indicator)
     487
     488                                  # is there a comment ?
     489                                  has_sub_result_comment = gmTools.coalesce (
     490                                          gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']),
     491                                          u''
     492                                  ).strip() != u''
     493                                  if has_sub_result_comment:
     494                                          tmp = u'%s %s' % (tmp, gmTools.u_ellipsis)
     495
     496                                  # lacking a review ?
     497                                  if missing_review:
     498                                          tmp = u'%s %s' % (tmp, gmTools.u_writing_hand)
     499
     500                                  # part of a multi-result cell ?
     501                                  if len(self.__cell_data[col][row]) > 1:
     502                                          tmp = u'%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp)
     503
     504                                  vals2display.append(tmp)
     505
     506                          self.SetCellValue(row, col, u'\n'.join(vals2display))
     507                          self.SetCellAlignment(row, col, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
     508  #                       font = self.GetCellFont(row, col)
     509  #                       if not font.IsFixedWidth():
     510  #                               font.SetFamily(family = wx.FONTFAMILY_MODERN)
     511                          # FIXME: what about partial sub results being relevant ??
     512                          if sub_result_relevant:
     513                                  font = self.GetCellFont(row, col)
     514                                  self.SetCellTextColour(row, col, 'firebrick')
     515                                  font.SetWeight(wx.FONTWEIGHT_BOLD)
     516                                  self.SetCellFont(row, col, font)
     517  #                       self.SetCellFont(row, col, font)
     518
     519                  self.AutoSize()
     520                  self.EndBatch()
     521                  return
     522          #------------------------------------------------------------
     523 -        def empty_grid(self):
     524                  self.BeginBatch()
     525                  self.ClearGrid()
     526                  # Windows cannot do nothing, it rather decides to assert()
     527                  # on thinking it is supposed to do nothing
     528                  if self.GetNumberRows() > 0:
     529                          self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
     530                  if self.GetNumberCols() > 0:
     531                          self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
     532                  self.EndBatch()
     533                  self.__cell_data = {}
     534                  self.__row_label_data = []
     535          #------------------------------------------------------------
     536 -        def get_row_tooltip(self, row=None):
     537                  # display test info (unified, which tests are grouped, which panels they belong to
     538                  # include details about test types included,
     539                  # most recent value in this row, etc
     540  #               test_details, td_idx = emr.get_test_types_details()
     541
     542                  # sometimes, for some reason, there is no row and
     543                  # wxPython still tries to find a tooltip for it
     544                  try:
     545                          tt = self.__row_label_data[row]
     546                  except IndexError:
     547                          return u' '
     548
     549                  tip = u''
     550                  tip += _('Details about %s (%s)%s\n') % (tt['unified_name'], tt['unified_abbrev'], gmTools.coalesce(tt['unified_loinc'], u'', u' [%s]'))
     551                  tip += u'\n'
     552                  tip += _('Meta type:\n')
     553                  tip += _(' Name: %s (%s)%s #%s\n') % (tt['name_meta'], tt['abbrev_meta'], gmTools.coalesce(tt['loinc_meta'], u'', u' [%s]'), tt['pk_meta_test_type'])
     554                  tip += gmTools.coalesce(tt['conversion_unit'], u'', _(' Conversion unit: %s\n'))
     555                  tip += gmTools.coalesce(tt['comment_meta'], u'', _(' Comment: %s\n'))
     556                  tip += u'\n'
     557                  tip += _('Test type:\n')
     558                  tip += _(' Name: %s (%s)%s #%s\n') % (tt['name_tt'], tt['abbrev_tt'], gmTools.coalesce(tt['loinc_tt'], u'', u' [%s]'), tt['pk_test_type'])
     559                  tip += gmTools.coalesce(tt['comment_tt'], u'', _(' Comment: %s\n'))
     560                  tip += gmTools.coalesce(tt['code_tt'], u'', _(' Code: %s\n'))
     561                  tip += gmTools.coalesce(tt['coding_system_tt'], u'', _(' Code: %s\n'))
     562                  result = tt.get_most_recent_result(pk_patient = self.__patient.ID)
     563                  if result is not None:
     564                          tip += u'\n'
     565                          tip += _('Most recent result:\n')
     566                          tip += _(' %s: %s%s%s') % (
     567                                  result['clin_when'].strftime('%Y-%m-%d'),
     568                                  result['unified_val'],
     569                                  gmTools.coalesce(result['val_unit'], u'', u' %s'),
     570                                  gmTools.coalesce(result['abnormality_indicator'], u'', u' (%s)')
     571                          )
     572
     573                  return tip
     574          #------------------------------------------------------------
     575 -        def get_cell_tooltip(self, col=None, row=None):
     576                  # FIXME: add panel/battery, request details
     577
     578                  try:
     579                          d = self.__cell_data[col][row]
     580                  except KeyError:
     581                          # FIXME: maybe display the most recent or when the most recent was ?
     582                          d = None
     583
     584                  if d is None:
     585                          return u' '
     586
     587                  is_multi_cell = False
     588                  if len(d) > 1:
     589                          is_multi_cell = True
     590
     591                  d = d[0]
     592
     593                  has_normal_min_or_max = (d['val_normal_min'] is not None) or (d['val_normal_max'] is not None)
     594                  if has_normal_min_or_max:
     595                          normal_min_max = u'%s - %s' % (
     596                                  gmTools.coalesce(d['val_normal_min'], u'?'),
     597                                  gmTools.coalesce(d['val_normal_max'], u'?')
     598                          )
     599                  else:
     600                          normal_min_max = u''
     601
     602                  has_clinical_min_or_max = (d['val_target_min'] is not None) or (d['val_target_max'] is not None)
     603                  if has_clinical_min_or_max:
     604                          clinical_min_max = u'%s - %s' % (
     605                                  gmTools.coalesce(d['val_target_min'], u'?'),
     606                                  gmTools.coalesce(d['val_target_max'], u'?')
     607                          )
     608                  else:
     609                          clinical_min_max = u''
     610
     611                  # header
     612                  if is_multi_cell:
     613                          tt = _(u'Measurement details of most recent (topmost) result:               \n')
     614                  else:
     615                          tt = _(u'Measurement details:                                               \n')
     616
     617                  # basics
     618                  tt += u' ' + _(u'Date: %s\n') % d['clin_when'].strftime('%c').decode(gmI18N.get_encoding())
     619                  tt += u' ' + _(u'Type: "%(name)s" (%(code)s)  [#%(pk_type)s]\n') % ({
     620                          'name': d['name_tt'],
     621                          'code': d['code_tt'],
     622                          'pk_type': d['pk_test_type']
     623                  })
     624                  tt += u' ' + _(u'Result: %(val)s%(unit)s%(ind)s  [#%(pk_result)s]\n') % ({
     625                          'val': d['unified_val'],
     626                          'unit': gmTools.coalesce(d['val_unit'], u'', u' %s'),
     627                          'ind': gmTools.coalesce(d['abnormality_indicator'], u'', u' (%s)'),
     628                          'pk_result': d['pk_test_result']
     629                  })
     630                  tmp = (u'%s%s' % (
     631                          gmTools.coalesce(d['name_test_org'], u''),
     632                          gmTools.coalesce(d['contact_test_org'], u'', u' (%s)'),
     633                  )).strip()
     634                  if tmp != u'':
     635                          tt += u' ' + _(u'Source: %s\n') % tmp
     636                  tt += u'\n'
     637
     638                  # clinical evaluation
     639                  norm_eval = None
     640                  if d['val_num'] is not None:
     641                          # 1) normal range
     642                          # lowered ?
     643                          if (d['val_normal_min'] is not None) and (d['val_num'] < d['val_normal_min']):
     644                                  try:
     645                                          percent = (d['val_num'] * 100) / d['val_normal_min']
     646                                  except ZeroDivisionError:
     647                                          percent = None
     648                                  if percent is not None:
     649                                          if percent < 6:
     650                                                  norm_eval = _(u'%.1f %% of the normal lower limit') % percent
     651                                          else:
     652                                                  norm_eval = _(u'%.0f %% of the normal lower limit') % percent
     653                          # raised ?
     654                          if (d['val_normal_max'] is not None) and (d['val_num'] > d['val_normal_max']):
     655                                  try:
     656                                          x_times = d['val_num'] / d['val_normal_max']
     657                                  except ZeroDivisionError:
     658                                          x_times = None
     659                                  if x_times is not None:
     660                                          if x_times < 10:
     661                                                  norm_eval = _(u'%.1f times the normal upper limit') % x_times
     662                                          else:
     663                                                  norm_eval = _(u'%.0f times the normal upper limit') % x_times
     664                          if norm_eval is not None:
     665                                  tt += u'  (%s)\n' % norm_eval
     666  #                       #-------------------------------------
     667  #                       # this idea was shot down on the list
     668  #                       #-------------------------------------
     669  #                       # bandwidth of deviation
     670  #                       if None not in [d['val_normal_min'], d['val_normal_max']]:
     671  #                               normal_width = d['val_normal_max'] - d['val_normal_min']
     672  #                               deviation_from_normal_range = None
     673  #                               # below ?
     674  #                               if d['val_num'] < d['val_normal_min']:
     675  #                                       deviation_from_normal_range = d['val_normal_min'] - d['val_num']
     676  #                               # above ?
     677  #                               elif d['val_num'] > d['val_normal_max']:
     678  #                                       deviation_from_normal_range = d['val_num'] - d['val_normal_max']
     679  #                               if deviation_from_normal_range is None:
     680  #                                       try:
     681  #                                               times_deviation = deviation_from_normal_range / normal_width
     682  #                                       except ZeroDivisionError:
     683  #                                               times_deviation = None
     684  #                                       if times_deviation is not None:
     685  #                                               if times_deviation < 10:
     686  #                                                       tt += u'  (%s)\n' % _(u'deviates by %.1f times of the normal range') % times_deviation
     687  #                                               else:
     688  #                                                       tt += u'  (%s)\n' % _(u'deviates by %.0f times of the normal range') % times_deviation
     689  #                       #-------------------------------------
     690
     691                          # 2) clinical target range
     692                          norm_eval = None
     693                          # lowered ?
     694                          if (d['val_target_min'] is not None) and (d['val_num'] < d['val_target_min']):
     695                                  try:
     696                                          percent = (d['val_num'] * 100) / d['val_target_min']
     697                                  except ZeroDivisionError:
     698                                          percent = None
     699                                  if percent is not None:
     700                                          if percent < 6:
     701                                                  norm_eval = _(u'%.1f %% of the target lower limit') % percent
     702                                          else:
     703                                                  norm_eval = _(u'%.0f %% of the target lower limit') % percent
     704                          # raised ?
     705                          if (d['val_target_max'] is not None) and (d['val_num'] > d['val_target_max']):
     706                                  try:
     707                                          x_times = d['val_num'] / d['val_target_max']
     708                                  except ZeroDivisionError:
     709                                          x_times = None
     710                                  if x_times is not None:
     711                                          if x_times < 10:
     712                                                  norm_eval = _(u'%.1f times the target upper limit') % x_times
     713                                          else:
     714                                                  norm_eval = _(u'%.0f times the target upper limit') % x_times
     715                          if norm_eval is not None:
     716                                  tt += u' (%s)\n' % norm_eval
     717  #                       #-------------------------------------
     718  #                       # this idea was shot down on the list
     719  #                       #-------------------------------------
     720  #                       # bandwidth of deviation
     721  #                       if None not in [d['val_target_min'], d['val_target_max']]:
     722  #                               normal_width = d['val_target_max'] - d['val_target_min']
     723  #                               deviation_from_target_range = None
     724  #                               # below ?
     725  #                               if d['val_num'] < d['val_target_min']:
     726  #                                       deviation_from_target_range = d['val_target_min'] - d['val_num']
     727  #                               # above ?
     728  #                               elif d['val_num'] > d['val_target_max']:
     729  #                                       deviation_from_target_range = d['val_num'] - d['val_target_max']
     730  #                               if deviation_from_target_range is None:
     731  #                                       try:
     732  #                                               times_deviation = deviation_from_target_range / normal_width
     733  #                                       except ZeroDivisionError:
     734  #                                               times_deviation = None
     735  #                               if times_deviation is not None:
     736  #                                       if times_deviation < 10:
     737  #                                               tt += u'  (%s)\n' % _(u'deviates by %.1f times of the target range') % times_deviation
     738  #                                       else:
     739  #                                               tt += u'  (%s)\n' % _(u'deviates by %.0f times of the target range') % times_deviation
     740  #                       #-------------------------------------
     741
     742                  # ranges
     743                  tt += u' ' + _(u'Standard normal range: %(norm_min_max)s%(norm_range)s  \n') % ({
     744                          'norm_min_max': normal_min_max,
     745                          'norm_range': gmTools.coalesce (
     746                                  d['val_normal_range'],
     747                                  u'',
     748                                  gmTools.bool2subst (
     749                                          has_normal_min_or_max,
     750                                          u' / %s',
     751                                          u'%s'
     752                                  )
     753                          )
     754                  })
     755                  if d['norm_ref_group'] is not None:
     756                          tt += u' ' + _(u'Reference group: %s\n') % d['norm_ref_group']
     757                  tt += u' ' + _(u'Clinical target range: %(clin_min_max)s%(clin_range)s  \n') % ({
     758                          'clin_min_max': clinical_min_max,
     759                          'clin_range': gmTools.coalesce (
     760                                  d['val_target_range'],
     761                                  u'',
     762                                  gmTools.bool2subst (
     763                                          has_clinical_min_or_max,
     764                                          u' / %s',
     765                                          u'%s'
     766                                  )
     767                          )
     768                  })
     769
     770                  # metadata
     771                  if d['comment'] is not None:
     772                          tt += u' ' + _(u'Doc: %s\n') % _(u'\n Doc: ').join(d['comment'].split(u'\n'))
     773                  if d['note_test_org'] is not None:
     774                          tt += u' ' + _(u'Lab: %s\n') % _(u'\n Lab: ').join(d['note_test_org'].split(u'\n'))
     775                  tt += u' ' + _(u'Episode: %s\n') % d['episode']
     776                  if d['health_issue'] is not None:
     777                          tt += u' ' + _(u'Issue: %s\n') % d['health_issue']
     778                  if d['material'] is not None:
     779                          tt += u' ' + _(u'Material: %s\n') % d['material']
     780                  if d['material_detail'] is not None:
     781                          tt += u' ' + _(u'Details: %s\n') % d['material_detail']
     782                  tt += u'\n'
     783
     784                  # review
     785                  if d['reviewed']:
     786                          review = d['last_reviewed'].strftime('%c').decode(gmI18N.get_encoding())
     787                  else:
     788                          review = _('not yet')
     789                  tt += _(u'Signed (%(sig_hand)s): %(reviewed)s\n') % ({
     790                          'sig_hand': gmTools.u_writing_hand,
     791                          'reviewed': review
     792                  })
     793                  tt += u' ' + _(u'Responsible clinician: %s\n') % gmTools.bool2subst(d['you_are_responsible'], _('you'), d['responsible_reviewer'])
     794                  if d['reviewed']:
     795                          tt += u' ' + _(u'Last reviewer: %(reviewer)s\n') % ({'reviewer': gmTools.bool2subst(d['review_by_you'], _('you'), gmTools.coalesce(d['last_reviewer'], u'?'))})
     796                          tt += u' ' + _(u' Technically abnormal: %(abnormal)s\n') % ({'abnormal': gmTools.bool2subst(d['is_technically_abnormal'], _('yes'), _('no'), u'?')})
     797                          tt += u' ' + _(u' Clinically relevant: %(relevant)s\n') % ({'relevant': gmTools.bool2subst(d['is_clinically_relevant'], _('yes'), _('no'), u'?')})
     798                  if d['review_comment'] is not None:
     799                          tt += u' ' + _(u' Comment: %s\n') % d['review_comment'].strip()
     800                  tt += u'\n'
     801
     802                  # type
     803                  tt += _(u'Test type details:\n')
     804                  tt += u' ' + _(u'Grouped under "%(name_meta)s" (%(abbrev_meta)s)  [#%(pk_u_type)s]\n') % ({
     805                          'name_meta': gmTools.coalesce(d['name_meta'], u''),
     806                          'abbrev_meta': gmTools.coalesce(d['abbrev_meta'], u''),
     807                          'pk_u_type': d['pk_meta_test_type']
     808                  })
     809                  if d['comment_tt'] is not None:
     810                          tt += u' ' + _(u'Type comment: %s\n') % _(u'\n Type comment:').join(d['comment_tt'].split(u'\n'))
     811                  if d['comment_meta'] is not None:
     812                          tt += u' ' + _(u'Group comment: %s\n') % _(u'\n Group comment: ').join(d['comment_meta'].split(u'\n'))
     813                  tt += u'\n'
     814
     815                  tt += _(u'Revisions: %(row_ver)s, last %(mod_when)s by %(mod_by)s.') % ({
     816                          'row_ver': d['row_version'],
     817                          'mod_when': d['modified_when'].strftime('%c').decode(gmI18N.get_encoding()),
     818                          'mod_by': d['modified_by']
     819                  })
     820
     821                  return tt
     822          #------------------------------------------------------------
     823          # internal helpers
     824          #------------------------------------------------------------
     825 -        def __init_ui(self):
     826                  self.CreateGrid(0, 1)
     827                  self.EnableEditing(0)
     828                  self.EnableDragGridSize(1)
     829
     830                  # setting this screws up the labels: they are cut off and displaced
     831                  #self.SetColLabelAlignment(wx.ALIGN_CENTER, wx.ALIGN_BOTTOM)
     832
     833                  #self.SetRowLabelSize(wx.GRID_AUTOSIZE)         # starting with 2.8.8
     834                  self.SetRowLabelSize(150)
     835                  self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE)
     836
     837                  # add link to left upper corner
     838                  dbcfg = gmCfg.cCfgSQL()
     839                  url = dbcfg.get2 (
     840                          option = u'external.urls.measurements_encyclopedia',
     841                          workplace = gmSurgery.gmCurrentPractice().active_workplace,
     842                          bias = 'user',
     843                          default = u'http://www.laborlexikon.de'
     844                  )
     845
     846                  self.__WIN_corner = self.GetGridCornerLabelWindow()             # a wx.Window instance
     847
     848                  LNK_lab = wx.lib.hyperlink.HyperLinkCtrl (
     849                          self.__WIN_corner,
     850                          -1,
     851                          label = _('Reference'),
     852                          style = wx.HL_DEFAULT_STYLE                     # wx.TE_READONLY|wx.TE_CENTRE| wx.NO_BORDER |
     853                  )
     854                  LNK_lab.SetURL(url)
     855                  LNK_lab.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BACKGROUND))
     856                  LNK_lab.SetToolTipString(_(
     857                          'Navigate to an encyclopedia of measurements\n'
     858                          'and test methods on the web.\n'
     859                          '\n'
     860                          ' <%s>'
     861                  ) % url)
     862
     863                  SZR_inner = wx.BoxSizer(wx.HORIZONTAL)
     864                  SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)                # spacer
     865                  SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0)          #wx.ALIGN_CENTER wx.EXPAND
     866                  SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)                # spacer
     867
     868                  SZR_corner = wx.BoxSizer(wx.VERTICAL)
     869                  SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)               # spacer
     870                  SZR_corner.AddWindow(SZR_inner, 0, wx.EXPAND)   # inner sizer with centered hyperlink
     871                  SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)               # spacer
     872
     873                  self.__WIN_corner.SetSizer(SZR_corner)
     874                  SZR_corner.Fit(self.__WIN_corner)
     875          #------------------------------------------------------------
     876 -        def __resize_corner_window(self, evt):
     877                  self.__WIN_corner.Layout()
     878          #------------------------------------------------------------
     879 -        def __cells_to_data(self, cells=None, exclude_multi_cells=False, auto_include_multi_cells=False):
     880                  """List of <cells> must be in row / col order."""
     881                  data = []
     882                  for row, col in cells:
     883                          try:
     884                                  # cell data is stored col / row
     885                                  data_list = self.__cell_data[col][row]
     886                          except KeyError:
     887                                  continue
     888
     889                          if len(data_list) == 1:
     890                                  data.append(data_list[0])
     891                                  continue
     892
     893                          if exclude_multi_cells:
     894                                  gmDispatcher.send(signal = u'statustext', msg = _('Excluding multi-result field from further processing.'))
     895                                  continue
     896
     897                          if auto_include_multi_cells:
     898                                  data.extend(data_list)
     899                                  continue
     900
     901                          data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list)
     902                          if data_to_include is None:
     903                                  continue
     904                          data.extend(data_to_include)
     905
     906                  return data
     907          #------------------------------------------------------------
     908 -        def __get_choices_from_multi_cell(self, cell_data=None, single_selection=False):
     909                  data = gmListWidgets.get_choices_from_list (
     910                          parent = self,
     911                          msg = _(
     912                                  'Your selection includes a field with multiple results.\n'
     913                                  '\n'
     914                                  'Please select the individual results you want to work on:'
     915                          ),
     916                          caption = _('Selecting test results'),
     917                          choices = [ [d['clin_when'], d['unified_abbrev'], d['unified_name'], d['unified_val']] for d in cell_data ],
     918                          columns = [_('Date / Time'), _('Code'), _('Test'), _('Result')],
     919                          data = cell_data,
     920                          single_selection = single_selection
     921                  )
     922                  return data
     923          #------------------------------------------------------------
     924          # event handling
     925          #------------------------------------------------------------
     926 -        def __register_events(self):
     927                  # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow
     928                  self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
     929                  self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels)
     930                  #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels)
     931
     932                  # sizing left upper corner window
     933                  self.Bind(wx.EVT_SIZE, self.__resize_corner_window)
     934
     935                  # editing cells
     936                  self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
     937          #------------------------------------------------------------
     938 -        def __on_cell_left_dclicked(self, evt):
     939                  col = evt.GetCol()
     940                  row = evt.GetRow()
     941
     942                  # empty cell, perhaps ?
     943                  try:
     944                          self.__cell_data[col][row]
     945                  except KeyError:
     946                          # FIXME: invoke editor for adding value for day of that column
     947                          # FIMXE: and test of that row
     948                          return
     949
     950                  if len(self.__cell_data[col][row]) > 1:
     951                          data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True)
     952                  else:
     953                          data = self.__cell_data[col][row][0]
     954
     955                  if data is None:
     956                          return
     957
     958                  edit_measurement(parent = self, measurement = data, single_entry = True)
     959          #------------------------------------------------------------
     960  #     def OnMouseMotionRowLabel(self, evt):
     961  #         x, y = self.CalcUnscrolledPosition(evt.GetPosition())
     962  #         row = self.YToRow(y)
     963  #         label = self.table().GetRowHelpValue(row)
     964  #         self.GetGridRowLabelWindow().SetToolTipString(label or "")
     965  #         evt.Skip()
     966 -        def __on_mouse_over_row_labels(self, evt):
     967
     968                  # Use CalcUnscrolledPosition() to get the mouse position within the
     969                  # entire grid including what's offscreen
     970                  x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
     971
     972                  row = self.YToRow(y)
     973
     974                  if self.__prev_label_row == row:
     975                          return
     976
     977                  self.__prev_label_row == row
     978
     979                  evt.GetEventObject().SetToolTipString(self.get_row_tooltip(row = row))
     980          #------------------------------------------------------------
     981  #     def OnMouseMotionColLabel(self, evt):
     982  #         x, y = self.CalcUnscrolledPosition(evt.GetPosition())
     983  #         col = self.XToCol(x)
     984  #         label = self.table().GetColHelpValue(col)
     985  #         self.GetGridColLabelWindow().SetToolTipString(label or "")
     986  #         evt.Skip()
     987          #------------------------------------------------------------
     988 -        def __on_mouse_over_cells(self, evt):
     989                  """Calculate where the mouse is and set the tooltip dynamically."""
     990
     991                  # Use CalcUnscrolledPosition() to get the mouse position within the
     992                  # entire grid including what's offscreen
     993                  x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
     994
     995                  # use this logic to prevent tooltips outside the actual cells
     996                  # apply to GetRowSize, too
     997  #        tot = 0
     998  #        for col in xrange(self.NumberCols):
     999  #            tot += self.GetColSize(col)
    1000  #            if xpos <= tot:
    1001  #                self.tool_tip.Tip = 'Tool tip for Column %s' % (
    1002  #                    self.GetColLabelValue(col))
    1003  #                break
    1004  #            else:  # mouse is in label area beyond the right-most column
    1005  #            self.tool_tip.Tip = ''
    1006
    1007                  row, col = self.XYToCell(x, y)
    1008
    1009                  if (row == self.__prev_row) and (col == self.__prev_col):
    1010                          return
    1011
    1012                  self.__prev_row = row
    1013                  self.__prev_col = col
    1014
    1015                  evt.GetEventObject().SetToolTipString(self.get_cell_tooltip(col=col, row=row))
    1016          #------------------------------------------------------------
    1017          # properties
    1018          #------------------------------------------------------------
    1019 -        def _set_patient(self, patient):
    1020                  self.__patient = patient
    1021                  self.repopulate_grid()
    1022
    1023          patient = property(lambda x:x, _set_patient)
    1024  #================================================================
    1025  from Gnumed.wxGladeWidgets import wxgMeasurementsPnl
    1026
    1027 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
    1028          """Panel holding a grid with lab data. Used as notebook page."""
    1029
    1030 -        def __init__(self, *args, **kwargs):
    1031
    1032                  wxgMeasurementsPnl.wxgMeasurementsPnl.__init__(self, *args, **kwargs)
    1033                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    1034                  self.__init_ui()
    1035                  self.__register_interests()
    1036          #--------------------------------------------------------
    1037          # event handling
    1038          #--------------------------------------------------------
    1039 -        def __register_interests(self):
    1040                  gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
    1041                  gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
    1042                  gmDispatcher.connect(signal = u'test_result_mod_db', receiver = self._schedule_data_reget)
    1043                  gmDispatcher.connect(signal = u'reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
    1044          #--------------------------------------------------------
    1045 -        def _on_post_patient_selection(self):
    1046                  wx.CallAfter(self.__on_post_patient_selection)
    1047          #--------------------------------------------------------
    1048 -        def __on_post_patient_selection(self):
    1049                  self._schedule_data_reget()
    1050          #--------------------------------------------------------
    1051 -        def _on_pre_patient_selection(self):
    1052                  wx.CallAfter(self.__on_pre_patient_selection)
    1053          #--------------------------------------------------------
    1054 -        def __on_pre_patient_selection(self):
    1055                  self.data_grid.patient = None
    1056          #--------------------------------------------------------
    1057 -        def _on_add_button_pressed(self, event):
    1058                  edit_measurement(parent = self, measurement = None)
    1059          #--------------------------------------------------------
    1060 -        def _on_review_button_pressed(self, evt):
    1061                  self.PopupMenu(self.__action_button_popup)
    1062          #--------------------------------------------------------
    1063 -        def _on_select_button_pressed(self, evt):
    1064                  if self._RBTN_my_unsigned.GetValue() is True:
    1065                          self.data_grid.select_cells(unsigned_only = True, accountables_only = True, keep_preselections = False)
    1066                  elif self._RBTN_all_unsigned.GetValue() is True:
    1067                          self.data_grid.select_cells(unsigned_only = True, accountables_only = False, keep_preselections = False)
    1068          #--------------------------------------------------------
    1069 -        def __on_sign_current_selection(self, evt):
    1070                  self.data_grid.sign_current_selection()
    1071          #--------------------------------------------------------
    1072 -        def __on_plot_current_selection(self, evt):
    1073                  self.data_grid.plot_current_selection()
    1074          #--------------------------------------------------------
    1075 -        def __on_delete_current_selection(self, evt):
    1076                  self.data_grid.delete_current_selection()
    1077          #--------------------------------------------------------
    1078          # internal API
    1079          #--------------------------------------------------------
    1080 -        def __init_ui(self):
    1081                  self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
    1082
    1083                  menu_id = wx.NewId()
    1084                  self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Review and &sign')))
    1085                  wx.EVT_MENU(self.__action_button_popup, menu_id, self.__on_sign_current_selection)
    1086
    1087                  menu_id = wx.NewId()
    1088                  self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Plot')))
    1089                  wx.EVT_MENU(self.__action_button_popup, menu_id, self.__on_plot_current_selection)
    1090
    1091                  menu_id = wx.NewId()
    1092                  self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Export to &file')))
    1093                  #wx.EVT_MENU(self.__action_button_popup, menu_id, self.data_grid.current_selection_to_file)
    1094                  self.__action_button_popup.Enable(id = menu_id, enable = False)
    1095
    1096                  menu_id = wx.NewId()
    1097                  self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Export to &clipboard')))
    1098                  #wx.EVT_MENU(self.__action_button_popup, menu_id, self.data_grid.current_selection_to_clipboard)
    1099                  self.__action_button_popup.Enable(id = menu_id, enable = False)
    1100
    1101                  menu_id = wx.NewId()
    1102                  self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('&Delete')))
    1103                  wx.EVT_MENU(self.__action_button_popup, menu_id, self.__on_delete_current_selection)
    1104
    1105                  # FIXME: create inbox message to staff to phone patient to come in
    1106                  # FIXME: generate and let edit a SOAP narrative and include the values
    1107
    1108          #--------------------------------------------------------
    1109          # reget mixin API
    1110          #--------------------------------------------------------
    1111 -        def _populate_with_data(self):
    1112                  """Populate fields in pages with data from model."""
    1113                  pat = gmPerson.gmCurrentPatient()
    1114                  if pat.connected:
    1115                          self.data_grid.patient = pat
    1116                  else:
    1117                          self.data_grid.patient = None
    1118                  return True
    1119  #================================================================
    1120  # editing widgets
    1121  #================================================================
    1122  from Gnumed.wxGladeWidgets import wxgMeasurementsReviewDlg
    1123
    1124 -class cMeasurementsReviewDlg(wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg):
    1125
    1126 -        def __init__(self, *args, **kwargs):
    1127
    1128                  try:
    1129                          tests = kwargs['tests']
    1130                          del kwargs['tests']
    1131                          test_count = len(tests)
    1132                          try: del kwargs['test_count']
    1133                          except KeyError: pass
    1134                  except KeyError:
    1135                          tests = None
    1136                          test_count = kwargs['test_count']
    1137                          del kwargs['test_count']
    1138
    1139                  wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs)
    1140
    1141                  if tests is None:
    1142                          msg = _('%s results selected. Too many to list individually.') % test_count
    1143                  else:
    1144                          msg = ' // '.join (
    1145                                  [       u'%s: %s %s (%s)' % (
    1146                                                  t['unified_abbrev'],
    1147                                                  t['unified_val'],
    1148                                                  t['val_unit'],
    1149                                                  t['clin_when'].strftime('%x').decode(gmI18N.get_encoding())
    1150                                          ) for t in tests
    1151                                  ]
    1152                          )
    1153
    1154                  self._LBL_tests.SetLabel(msg)
    1155
    1156                  if test_count == 1:
    1157                          self._TCTRL_comment.Enable(True)
    1158                          self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], u''))
    1159                          if tests[0]['you_are_responsible']:
    1160                                  self._CHBOX_responsible.Enable(False)
    1161
    1162                  self.Fit()
    1163          #--------------------------------------------------------
    1164          # event handling
    1165          #--------------------------------------------------------
    1166 -        def _on_signoff_button_pressed(self, evt):
    1167                  if self.IsModal():
    1168                          self.EndModal(wx.ID_APPLY)
    1169                  else:
    1170                          self.Close()
    1171  #================================================================
    1172  from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl
    1173
    1174 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
    1175          """This edit area saves *new* measurements into the active patient only."""
    1176
    1177 -        def __init__(self, *args, **kwargs):
    1178
    1179                  try:
    1180                          self.__default_date = kwargs['date']
    1181                          del kwargs['date']
    1182                  except KeyError:
    1183                          self.__default_date = None
    1184
    1185                  wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl.__init__(self, *args, **kwargs)
    1186                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    1187
    1188                  self.__register_interests()
    1189
    1190                  self.successful_save_msg = _('Successfully saved measurement.')
    1191
    1192                  self._DPRW_evaluated.display_accuracy = gmDateTime.acc_minutes
    1193          #--------------------------------------------------------
    1194          # generic edit area mixin API
    1195          #--------------------------------------------------------
    1196 -        def _refresh_as_new(self):
    1197                  self._PRW_test.SetText(u'', None, True)
    1198                  self.__refresh_loinc_info()
    1199                  self.__update_units_context()
    1200                  self._TCTRL_result.SetValue(u'')
    1201                  self._PRW_units.SetText(u'', None, True)
    1202                  self._PRW_abnormality_indicator.SetText(u'', None, True)
    1203                  if self.__default_date is None:
    1204                          self._DPRW_evaluated.SetData(data = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone))
    1205                  else:
    1206                          self._DPRW_evaluated.SetData(data =     None)
    1207                  self._TCTRL_note_test_org.SetValue(u'')
    1208                  self._PRW_intended_reviewer.SetData(gmStaff.gmCurrentProvider()['pk_staff'])
    1209                  self._PRW_problem.SetData()
    1210                  self._TCTRL_narrative.SetValue(u'')
    1211                  self._CHBOX_review.SetValue(False)
    1212                  self._CHBOX_abnormal.SetValue(False)
    1213                  self._CHBOX_relevant.SetValue(False)
    1214                  self._CHBOX_abnormal.Enable(False)
    1215                  self._CHBOX_relevant.Enable(False)
    1216                  self._TCTRL_review_comment.SetValue(u'')
    1217                  self._TCTRL_normal_min.SetValue(u'')
    1218                  self._TCTRL_normal_max.SetValue(u'')
    1219                  self._TCTRL_normal_range.SetValue(u'')
    1220                  self._TCTRL_target_min.SetValue(u'')
    1221                  self._TCTRL_target_max.SetValue(u'')
    1222                  self._TCTRL_target_range.SetValue(u'')
    1223                  self._TCTRL_norm_ref_group.SetValue(u'')
    1224
    1225                  self._PRW_test.SetFocus()
    1226          #--------------------------------------------------------
    1227 -        def _refresh_from_existing(self):
    1228                  self._PRW_test.SetData(data = self.data['pk_test_type'])
    1229                  self.__refresh_loinc_info()
    1230                  self.__update_units_context()
    1231                  self._TCTRL_result.SetValue(self.data['unified_val'])
    1232                  self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True)
    1233                  self._PRW_abnormality_indicator.SetText (
    1234                          gmTools.coalesce(self.data['abnormality_indicator'], u''),
    1235                          gmTools.coalesce(self.data['abnormality_indicator'], u''),
    1236                          True
    1237                  )
    1238                  self._DPRW_evaluated.SetData(data = self.data['clin_when'])
    1239                  self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], u''))
    1240                  self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
    1241                  self._PRW_problem.SetData(self.data['pk_episode'])
    1242                  self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], u''))
    1243                  self._CHBOX_review.SetValue(False)
    1244                  self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False))
    1245                  self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False))
    1246                  self._CHBOX_abnormal.Enable(False)
    1247                  self._CHBOX_relevant.Enable(False)
    1248                  self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], u''))
    1249                  self._TCTRL_normal_min.SetValue(unicode(gmTools.coalesce(self.data['val_normal_min'], u'')))
    1250                  self._TCTRL_normal_max.SetValue(unicode(gmTools.coalesce(self.data['val_normal_max'], u'')))
    1251                  self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], u''))
    1252                  self._TCTRL_target_min.SetValue(unicode(gmTools.coalesce(self.data['val_target_min'], u'')))
    1253                  self._TCTRL_target_max.SetValue(unicode(gmTools.coalesce(self.data['val_target_max'], u'')))
    1254                  self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], u''))
    1255                  self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], u''))
    1256
    1257                  self._TCTRL_result.SetFocus()
    1258          #--------------------------------------------------------
    1259 -        def _refresh_as_new_from_existing(self):
    1260                  self._refresh_from_existing()
    1261
    1262                  self._PRW_test.SetText(u'', None, True)
    1263                  self.__refresh_loinc_info()
    1264                  self.__update_units_context()
    1265                  self._TCTRL_result.SetValue(u'')
    1266                  self._PRW_units.SetText(u'', None, True)
    1267                  self._PRW_abnormality_indicator.SetText(u'', None, True)
    1268                  # self._DPRW_evaluated
    1269                  self._TCTRL_note_test_org.SetValue(u'')
    1270                  self._TCTRL_narrative.SetValue(u'')
    1271                  self._CHBOX_review.SetValue(False)
    1272                  self._CHBOX_abnormal.SetValue(False)
    1273                  self._CHBOX_relevant.SetValue(False)
    1274                  self._CHBOX_abnormal.Enable(False)
    1275                  self._CHBOX_relevant.Enable(False)
    1276                  self._TCTRL_review_comment.SetValue(u'')
    1277                  self._TCTRL_normal_min.SetValue(u'')
    1278                  self._TCTRL_normal_max.SetValue(u'')
    1279                  self._TCTRL_normal_range.SetValue(u'')
    1280                  self._TCTRL_target_min.SetValue(u'')
    1281                  self._TCTRL_target_max.SetValue(u'')
    1282                  self._TCTRL_target_range.SetValue(u'')
    1283                  self._TCTRL_norm_ref_group.SetValue(u'')
    1284
    1285                  self._PRW_test.SetFocus()
    1286          #--------------------------------------------------------
    1287 -        def _valid_for_save(self):
    1288
    1289                  validity = True
    1290
    1291                  if not self._DPRW_evaluated.is_valid_timestamp():
    1292                          self._DPRW_evaluated.display_as_valid(False)
    1293                          validity = False
    1294                  else:
    1295                          self._DPRW_evaluated.display_as_valid(True)
    1296
    1297                  if self._TCTRL_result.GetValue().strip() == u'':
    1298                          validity = False
    1299                          self.display_ctrl_as_valid(self._TCTRL_result, False)
    1300                  else:
    1301                          self.display_ctrl_as_valid(self._TCTRL_result, True)
    1302
    1303                  if self._PRW_problem.GetValue().strip() == u'':
    1304                          self._PRW_problem.display_as_valid(False)
    1305                          validity = False
    1306                  else:
    1307                          self._PRW_problem.display_as_valid(True)
    1308
    1309                  if self._PRW_test.GetValue().strip() == u'':
    1310                          self._PRW_test.display_as_valid(False)
    1311                          validity = False
    1312                  else:
    1313                          self._PRW_test.display_as_valid(True)
    1314
    1315                  if self._PRW_intended_reviewer.GetData() is None:
    1316                          self._PRW_intended_reviewer.display_as_valid(False)
    1317                          validity = False
    1318                  else:
    1319                          self._PRW_intended_reviewer.display_as_valid(True)
    1320
    1321                  if self._PRW_units.GetValue().strip() == u'':
    1322                          self._PRW_units.display_as_valid(False)
    1323                          validity = False
    1324                  else:
    1325                          self._PRW_units.display_as_valid(True)
    1326
    1327                  ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max]
    1328                  for widget in ctrls:
    1329                          val = widget.GetValue().strip()
    1330                          if val == u'':
    1331                                  continue
    1332                          try:
    1333                                  decimal.Decimal(val.replace(',', u'.', 1))
    1334                                  self.display_ctrl_as_valid(widget, True)
    1335                          except:
    1336                                  validity = False
    1337                                  self.display_ctrl_as_valid(widget, False)
    1338
    1339                  if validity is False:
    1340                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot save result. Invalid or missing essential input.'))
    1341
    1342                  return validity
    1343          #--------------------------------------------------------
    1344 -        def _save_as_new(self):
    1345
    1346                  emr = gmPerson.gmCurrentPatient().get_emr()
    1347
    1348                  success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
    1349                  if success:
    1350                          v_num = result
    1351                          v_al = None
    1352                  else:
    1353                          v_al = self._TCTRL_result.GetValue().strip()
    1354                          v_num = None
    1355
    1356                  pk_type = self._PRW_test.GetData()
    1357                  if pk_type is None:
    1358                          tt = gmPathLab.create_measurement_type (
    1359                                  lab = None,
    1360                                  abbrev = self._PRW_test.GetValue().strip(),
    1361                                  name = self._PRW_test.GetValue().strip(),
    1362                                  unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
    1363                          )
    1364                          pk_type = tt['pk_test_type']
    1365
    1366                  tr = emr.add_test_result (
    1367                          episode = self._PRW_problem.GetData(can_create=True, is_open=False),
    1368                          type = pk_type,
    1369                          intended_reviewer = self._PRW_intended_reviewer.GetData(),
    1370                          val_num = v_num,
    1371                          val_alpha = v_al,
    1372                          unit = self._PRW_units.GetValue()
    1373                  )
    1374
    1375                  tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
    1376
    1377                  ctrls = [
    1378                          ('abnormality_indicator', self._PRW_abnormality_indicator),
    1379                          ('note_test_org', self._TCTRL_note_test_org),
    1380                          ('comment', self._TCTRL_narrative),
    1381                          ('val_normal_range', self._TCTRL_normal_range),
    1382                          ('val_target_range', self._TCTRL_target_range),
    1383                          ('norm_ref_group', self._TCTRL_norm_ref_group)
    1384                  ]
    1385                  for field, widget in ctrls:
    1386                          tr[field] = widget.GetValue().strip()
    1387
    1388                  ctrls = [
    1389                          ('val_normal_min', self._TCTRL_normal_min),
    1390                          ('val_normal_max', self._TCTRL_normal_max),
    1391                          ('val_target_min', self._TCTRL_target_min),
    1392                          ('val_target_max', self._TCTRL_target_max)
    1393                  ]
    1394                  for field, widget in ctrls:
    1395                          val = widget.GetValue().strip()
    1396                          if val == u'':
    1397                                  tr[field] = None
    1398                          else:
    1399                                  tr[field] = decimal.Decimal(val.replace(',', u'.', 1))
    1400
    1401                  tr.save_payload()
    1402
    1403                  if self._CHBOX_review.GetValue() is True:
    1404                          tr.set_review (
    1405                                  technically_abnormal = self._CHBOX_abnormal.GetValue(),
    1406                                  clinically_relevant = self._CHBOX_relevant.GetValue(),
    1407                                  comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), u''),
    1408                                  make_me_responsible = False
    1409                          )
    1410
    1411                  self.data = tr
    1412
    1413                  return True
    1414          #--------------------------------------------------------
    1415 -        def _save_as_update(self):
    1416
    1417                  success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
    1418                  if success:
    1419                          v_num = result
    1420                          v_al = None
    1421                  else:
    1422                          v_num = None
    1423                          v_al = self._TCTRL_result.GetValue().strip()
    1424
    1425                  pk_type = self._PRW_test.GetData()
    1426                  if pk_type is None:
    1427                          tt = gmPathLab.create_measurement_type (
    1428                                  lab = None,
    1429                                  abbrev = self._PRW_test.GetValue().strip(),
    1430                                  name = self._PRW_test.GetValue().strip(),
    1431                                  unit = gmTools.none_if(self._PRW_units.GetValue().strip(), u'')
    1432                          )
    1433                          pk_type = tt['pk_test_type']
    1434
    1435                  tr = self.data
    1436
    1437                  tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False)
    1438                  tr['pk_test_type'] = pk_type
    1439                  tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData()
    1440                  tr['val_num'] = v_num
    1441                  tr['val_alpha'] = v_al
    1442                  tr['val_unit'] = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
    1443                  tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
    1444
    1445                  ctrls = [
    1446                          ('abnormality_indicator', self._PRW_abnormality_indicator),
    1447                          ('note_test_org', self._TCTRL_note_test_org),
    1448                          ('comment', self._TCTRL_narrative),
    1449                          ('val_normal_range', self._TCTRL_normal_range),
    1450                          ('val_target_range', self._TCTRL_target_range),
    1451                          ('norm_ref_group', self._TCTRL_norm_ref_group)
    1452                  ]
    1453                  for field, widget in ctrls:
    1454                          tr[field] = widget.GetValue().strip()
    1455
    1456                  ctrls = [
    1457                          ('val_normal_min', self._TCTRL_normal_min),
    1458                          ('val_normal_max', self._TCTRL_normal_max),
    1459                          ('val_target_min', self._TCTRL_target_min),
    1460                          ('val_target_max', self._TCTRL_target_max)
    1461                  ]
    1462                  for field, widget in ctrls:
    1463                          val = widget.GetValue().strip()
    1464                          if val == u'':
    1465                                  tr[field] = None
    1466                          else:
    1467                                  tr[field] = decimal.Decimal(val.replace(',', u'.', 1))
    1468
    1469                  tr.save_payload()
    1470
    1471                  if self._CHBOX_review.GetValue() is True:
    1472                          tr.set_review (
    1473                                  technically_abnormal = self._CHBOX_abnormal.GetValue(),
    1474                                  clinically_relevant = self._CHBOX_relevant.GetValue(),
    1475                                  comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), u''),
    1476                                  make_me_responsible = False
    1477                          )
    1478
    1479                  return True
    1480          #--------------------------------------------------------
    1481          # event handling
    1482          #--------------------------------------------------------
    1483 -        def __register_interests(self):
    1484                  self._PRW_test.add_callback_on_lose_focus(self._on_leave_test_prw)
    1485                  self._PRW_abnormality_indicator.add_callback_on_lose_focus(self._on_leave_indicator_prw)
    1486          #--------------------------------------------------------
    1487 -        def _on_leave_test_prw(self):
    1488                  self.__refresh_loinc_info()
    1489                  self.__update_units_context()
    1490          #--------------------------------------------------------
    1491 -        def _on_leave_indicator_prw(self):
    1492                  # if the user hasn't explicitly enabled reviewing
    1493                  if not self._CHBOX_review.GetValue():
    1494                          self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != u'')
    1495          #--------------------------------------------------------
    1496 -        def _on_review_box_checked(self, evt):
    1497                  self._CHBOX_abnormal.Enable(self._CHBOX_review.GetValue())
    1498                  self._CHBOX_relevant.Enable(self._CHBOX_review.GetValue())
    1499                  self._TCTRL_review_comment.Enable(self._CHBOX_review.GetValue())
    1500          #--------------------------------------------------------
    1501 -        def _on_test_info_button_pressed(self, event):
    1502
    1503                  pk = self._PRW_test.GetData()
    1504                  if pk is not None:
    1505                          tt = gmPathLab.cMeasurementType(aPK_obj = pk)
    1506                          search_term = u'%s %s %s' % (
    1507                                  tt['name'],
    1508                                  tt['abbrev'],
    1509                                  gmTools.coalesce(tt['loinc'], u'')
    1510                          )
    1511                  else:
    1512                          search_term = self._PRW_test.GetValue()
    1513
    1514                  search_term = search_term.replace(' ', u'+')
    1515
    1516                  call_browser_on_measurement_type(measurement_type = search_term)
    1517          #--------------------------------------------------------
    1518          # internal helpers
    1519          #--------------------------------------------------------
    1520 -        def __update_units_context(self):
    1521
    1522                  self._PRW_units.unset_context(context = u'loinc')
    1523
    1524                  tt = self._PRW_test.GetData(as_instance = True)
    1525
    1526                  if tt is None:
    1527                          self._PRW_units.unset_context(context = u'pk_type')
    1528                          if self._PRW_test.GetValue().strip() == u'':
    1529                                  self._PRW_units.unset_context(context = u'test_name')
    1530                          else:
    1531                                  self._PRW_units.set_context(context = u'test_name', val = self._PRW_test.GetValue().strip())
    1532                          return
    1533
    1534                  self._PRW_units.set_context(context = u'pk_type', val = tt['pk_test_type'])
    1535                  self._PRW_units.set_context(context = u'test_name', val = tt['name'])
    1536
    1537                  if tt['loinc'] is None:
    1538                          return
    1539
    1540                  self._PRW_units.set_context(context = u'loinc', val = tt['loinc'])
    1541          #--------------------------------------------------------
    1542 -        def __refresh_loinc_info(self):
    1543
    1544                  self._TCTRL_loinc.SetValue(u'')
    1545
    1546                  if self._PRW_test.GetData() is None:
    1547                          return
    1548
    1549                  tt = self._PRW_test.GetData(as_instance = True)
    1550
    1551                  if tt['loinc'] is None:
    1552                          return
    1553
    1554                  info = gmLOINC.loinc2term(loinc = tt['loinc'])
    1555                  if len(info) == 0:
    1556                          self._TCTRL_loinc.SetValue(u'')
    1557                          return
    1558
    1559                  self._TCTRL_loinc.SetValue(u'%s: %s' % (tt['loinc'], info[0]))
    1560  #================================================================
    1561  # measurement type handling
    1562  #================================================================
    1563 -def manage_measurement_types(parent=None):
    1564
    1565          if parent is None:
    1566                  parent = wx.GetApp().GetTopWindow()
    1567
    1568          #------------------------------------------------------------
    1569          def edit(test_type=None):
    1570                  ea = cMeasurementTypeEAPnl(parent = parent, id = -1, type = test_type)
    1571                  dlg = gmEditArea.cGenericEditAreaDlg2 (
    1572                          parent = parent,
    1573                          id = -1,
    1574                          edit_area = ea,
    1575                          single_entry = gmTools.bool2subst((test_type is None), False, True)
    1576                  )
    1577                  dlg.SetTitle(gmTools.coalesce(test_type, _('Adding measurement type'), _('Editing measurement type')))
    1578
    1579                  if dlg.ShowModal() == wx.ID_OK:
    1580                          dlg.Destroy()
    1581                          return True
    1582
    1583                  dlg.Destroy()
    1584                  return False
    1585          #------------------------------------------------------------
    1586          def refresh(lctrl):
    1587                  mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev')
    1588                  items = [ [
    1589                          m['abbrev'],
    1590                          m['name'],
    1591                          gmTools.coalesce(m['loinc'], u''),
    1592                          gmTools.coalesce(m['conversion_unit'], u''),
    1593                          gmTools.coalesce(m['comment_type'], u''),
    1594                          gmTools.coalesce(m['name_org'], u'?'),
    1595                          gmTools.coalesce(m['comment_org'], u''),
    1596                          m['pk_test_type']
    1597                  ] for m in mtypes ]
    1598                  lctrl.set_string_items(items)
    1599                  lctrl.set_data(mtypes)
    1600          #------------------------------------------------------------
    1601          def delete(measurement_type):
    1602                  if measurement_type.in_use:
    1603                          gmDispatcher.send (
    1604                                  signal = 'statustext',
    1605                                  beep = True,
    1606                                  msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev'])
    1607                          )
    1608                          return False
    1609                  gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type'])
    1610                  return True
    1611          #------------------------------------------------------------
    1612          msg = _(
    1613                  '\n'
    1614                  'These are the measurement types currently defined in GNUmed.\n'
    1615                  '\n'
    1616          )
    1617
    1618          gmListWidgets.get_choices_from_list (
    1619                  parent = parent,
    1620                  msg = msg,
    1621                  caption = _('Showing measurement types.'),
    1622                  columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Base unit'), _('Comment'), _('Org'), _('Comment'), u'#'],
    1623                  single_selection = True,
    1624                  refresh_callback = refresh,
    1625                  edit_callback = edit,
    1626                  new_callback = edit,
    1627                  delete_callback = delete
    1628          )
    1629  #----------------------------------------------------------------
    1630 -class cMeasurementTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
    1631
    1632 -        def __init__(self, *args, **kwargs):
    1633
    1634                  query = u"""
    1635  SELECT DISTINCT ON (field_label)
    1636          pk_test_type AS data,
    1637          name_tt
    1638                  || ' ('
    1639                  || coalesce (
    1640                          (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = vcutt.pk_test_org),
    1641                          '%(in_house)s'
    1642                          )
    1643                  || ')'
    1644          AS field_label,
    1645          name_tt
    1646                  || ' ('
    1647                  || coalesce(code_tt || ', ', '')
    1648                  || abbrev_tt || ', '
    1649                  || coalesce(abbrev_meta || ': ' || name_meta || ', ', '')
    1650                  || coalesce (
    1651                          (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = vcutt.pk_test_org),
    1652                          '%(in_house)s'
    1653                          )
    1654                  || ')'
    1655          AS list_label
    1656  FROM
    1657          clin.v_unified_test_types vcutt
    1658  WHERE
    1659          abbrev_meta %%(fragment_condition)s
    1660                  OR
    1661          name_meta %%(fragment_condition)s
    1662                  OR
    1663          abbrev_tt %%(fragment_condition)s
    1664                  OR
    1665          name_tt %%(fragment_condition)s
    1666                  OR
    1667          code_tt %%(fragment_condition)s
    1668  ORDER BY field_label
    1669  LIMIT 50""" % {'in_house': _('generic / in house lab')}
    1670
    1671                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    1672                  mp.setThresholds(1, 2, 4)
    1673                  mp.word_separators = '[ \t:@]+'
    1674                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    1675                  self.matcher = mp
    1676                  self.SetToolTipString(_('Select the type of measurement.'))
    1677                  self.selection_only = False
    1678          #------------------------------------------------------------
    1679 -        def _data2instance(self):
    1680                  if self.GetData() is None:
    1681                          return None
    1682
    1683                  return gmPathLab.cMeasurementType(aPK_obj = self.GetData())
    1684  #----------------------------------------------------------------
    1685  from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl
    1686
    1687 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
    1688
    1689 -        def __init__(self, *args, **kwargs):
    1690
    1691                  try:
    1692                          data = kwargs['type']
    1693                          del kwargs['type']
    1694                  except KeyError:
    1695                          data = None
    1696
    1697                  wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl.__init__(self, *args, **kwargs)
    1698                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    1699                  self.mode = 'new'
    1700                  self.data = data
    1701                  if data is not None:
    1702                          self.mode = 'edit'
    1703
    1704                  self.__init_ui()
    1705
    1706          #----------------------------------------------------------------
    1707 -        def __init_ui(self):
    1708
    1709                  # name phraseweel
    1710                  query = u"""
    1711  select distinct on (name)
    1712          pk,
    1713          name
    1714  from clin.test_type
    1715  where
    1716          name %(fragment_condition)s
    1717  order by name
    1718  limit 50"""
    1719                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    1720                  mp.setThresholds(1, 2, 4)
    1721                  self._PRW_name.matcher = mp
    1722                  self._PRW_name.selection_only = False
    1723                  self._PRW_name.add_callback_on_lose_focus(callback = self._on_name_lost_focus)
    1724
    1725                  # abbreviation
    1726                  query = u"""
    1727  select distinct on (abbrev)
    1728          pk,
    1729          abbrev
    1730  from clin.test_type
    1731  where
    1732          abbrev %(fragment_condition)s
    1733  order by abbrev
    1734  limit 50"""
    1735                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    1736                  mp.setThresholds(1, 2, 3)
    1737                  self._PRW_abbrev.matcher = mp
    1738                  self._PRW_abbrev.selection_only = False
    1739
    1740                  # unit
    1741                  self._PRW_conversion_unit.selection_only = False
    1742
    1743                  # loinc
    1744                  query = u"""
    1745  SELECT DISTINCT ON (list_label)
    1746          data,
    1747          field_label,
    1748          list_label
    1749  FROM ((
    1750
    1751                  SELECT
    1752                          loinc AS data,
    1753                          loinc AS field_label,
    1754                          (loinc || ': ' || abbrev || ' (' || name || ')') AS list_label
    1755                  FROM clin.test_type
    1756                  WHERE loinc %(fragment_condition)s
    1757                  LIMIT 50
    1758
    1759          ) UNION ALL (
    1760
    1761                  SELECT
    1762                          code AS data,
    1763                          code AS field_label,
    1764                          (code || ': ' || term) AS list_label
    1765                  FROM ref.v_coded_terms
    1766                  WHERE
    1767                          coding_system = 'LOINC'
    1768                                  AND
    1769                          lang = i18n.get_curr_lang()
    1770                                  AND
    1771                          (code %(fragment_condition)s
    1772                                  OR
    1773                          term %(fragment_condition)s)
    1774                  LIMIT 50
    1775
    1776          ) UNION ALL (
    1777
    1778                  SELECT
    1779                          code AS data,
    1780                          code AS field_label,
    1781                          (code || ': ' || term) AS list_label
    1782                  FROM ref.v_coded_terms
    1783                  WHERE
    1784                          coding_system = 'LOINC'
    1785                                  AND
    1786                          lang = 'en_EN'
    1787                                  AND
    1788                          (code %(fragment_condition)s
    1789                                  OR
    1790                          term %(fragment_condition)s)
    1791                  LIMIT 50
    1792
    1793          ) UNION ALL (
    1794
    1795                  SELECT
    1796                          code AS data,
    1797                          code AS field_label,
    1798                          (code || ': ' || term) AS list_label
    1799                  FROM ref.v_coded_terms
    1800                  WHERE
    1801                          coding_system = 'LOINC'
    1802                                  AND
    1803                          (code %(fragment_condition)s
    1804                                  OR
    1805                          term %(fragment_condition)s)
    1806                  LIMIT 50
    1807          )
    1808  ) AS all_known_loinc
    1809
    1810  ORDER BY list_label
    1811  LIMIT 50"""
    1812                  mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
    1813                  mp.setThresholds(1, 2, 4)
    1814                  self._PRW_loinc.matcher = mp
    1815                  self._PRW_loinc.selection_only = False
    1816                  self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
    1817          #----------------------------------------------------------------
    1818 -        def _on_name_lost_focus(self):
    1819
    1820                  test = self._PRW_name.GetValue().strip()
    1821
    1822                  if test == u'':
    1823                          self._PRW_conversion_unit.unset_context(context = u'test_name')
    1824                          return
    1825
    1826                  self._PRW_conversion_unit.set_context(context = u'test_name', val = test)
    1827          #----------------------------------------------------------------
    1828 -        def _on_loinc_lost_focus(self):
    1829                  loinc = self._PRW_loinc.GetData()
    1830
    1831                  if loinc is None:
    1832                          self._TCTRL_loinc_info.SetValue(u'')
    1833                          self._PRW_conversion_unit.unset_context(context = u'loinc')
    1834                          return
    1835
    1836                  self._PRW_conversion_unit.set_context(context = u'loinc', val = loinc)
    1837
    1838                  info = gmLOINC.loinc2term(loinc = loinc)
    1839                  if len(info) == 0:
    1840                          self._TCTRL_loinc_info.SetValue(u'')
    1841                          return
    1842
    1843                  self._TCTRL_loinc_info.SetValue(info[0])
    1844          #----------------------------------------------------------------
    1845          # generic Edit Area mixin API
    1846          #----------------------------------------------------------------
    1847 -        def _valid_for_save(self):
    1848
    1849                  has_errors = False
    1850                  for field in [self._PRW_name, self._PRW_abbrev, self._PRW_conversion_unit]:
    1851                          if field.GetValue().strip() in [u'', None]:
    1852                                  has_errors = True
    1853                                  field.display_as_valid(valid = False)
    1854                          else:
    1855                                  field.display_as_valid(valid = True)
    1856                          field.Refresh()
    1857
    1858                  return (not has_errors)
    1859          #----------------------------------------------------------------
    1860 -        def _save_as_new(self):
    1861
    1862                  pk_org = self._PRW_test_org.GetData()
    1863                  if pk_org is None:
    1864                          pk_org = gmPathLab.create_test_org (
    1865                                  name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), u''),
    1866                                  comment = gmTools.none_if(self._TCTRL_comment_org.GetValue().strip(), u'')
    1867                          )['pk_test_org']
    1868
    1869                  tt = gmPathLab.create_measurement_type (
    1870                          lab = pk_org,
    1871                          abbrev = self._PRW_abbrev.GetValue().strip(),
    1872                          name = self._PRW_name.GetValue().strip(),
    1873                          unit = gmTools.coalesce (
    1874                                  self._PRW_conversion_unit.GetData(),
    1875                                  self._PRW_conversion_unit.GetValue()
    1876                          ).strip()
    1877                  )
    1878                  if self._PRW_loinc.GetData() is not None:
    1879                          tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), u'')
    1880                  else:
    1881                          tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), u'')
    1882                  tt['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), u'')
    1883                  tt.save()
    1884
    1885                  self.data = tt
    1886
    1887                  return True
    1888          #----------------------------------------------------------------
    1889 -        def _save_as_update(self):
    1890
    1891                  pk_org = self._PRW_test_org.GetData()
    1892                  if pk_org is None:
    1893                          pk_org = gmPathLab.create_test_org (
    1894                                  name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), u''),
    1895                                  comment = gmTools.none_if(self._TCTRL_comment_org.GetValue().strip(), u'')
    1896                          )['pk_test_org']
    1897
    1898                  self.data['pk_test_org'] = pk_org
    1899                  self.data['abbrev'] = self._PRW_abbrev.GetValue().strip()
    1900                  self.data['name'] = self._PRW_name.GetValue().strip()
    1901                  self.data['conversion_unit'] = gmTools.coalesce (
    1902                          self._PRW_conversion_unit.GetData(),
    1903                          self._PRW_conversion_unit.GetValue()
    1904                  ).strip()
    1905                  if self._PRW_loinc.GetData() is not None:
    1906                          self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), u'')
    1907                  if self._PRW_loinc.GetData() is not None:
    1908                          self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), u'')
    1909                  else:
    1910                          self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), u'')
    1911                  self.data['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), u'')
    1912                  self.data.save()
    1913
    1914                  return True
    1915          #----------------------------------------------------------------
    1916 -        def _refresh_as_new(self):
    1917                  self._PRW_name.SetText(u'', None, True)
    1918                  self._on_name_lost_focus()
    1919                  self._PRW_abbrev.SetText(u'', None, True)
    1920                  self._PRW_conversion_unit.SetText(u'', None, True)
    1921                  self._PRW_loinc.SetText(u'', None, True)
    1922                  self._on_loinc_lost_focus()
    1923                  self._TCTRL_comment_type.SetValue(u'')
    1924                  self._PRW_test_org.SetText(u'', None, True)
    1925                  self._TCTRL_comment_org.SetValue(u'')
    1926
    1927                  self._PRW_name.SetFocus()
    1928          #----------------------------------------------------------------
    1929 -        def _refresh_from_existing(self):
    1930                  self._PRW_name.SetText(self.data['name'], self.data['name'], True)
    1931                  self._on_name_lost_focus()
    1932                  self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True)
    1933                  self._PRW_conversion_unit.SetText (
    1934                          gmTools.coalesce(self.data['conversion_unit'], u''),
    1935                          self.data['conversion_unit'],
    1936                          True
    1937                  )
    1938                  self._PRW_loinc.SetText (
    1939                          gmTools.coalesce(self.data['loinc'], u''),
    1940                          self.data['loinc'],
    1941                          True
    1942                  )
    1943                  self._on_loinc_lost_focus()
    1944                  self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], u''))
    1945                  self._PRW_test_org.SetText (
    1946                          gmTools.coalesce(self.data['pk_test_org'], u'', self.data['name_org']),
    1947                          self.data['pk_test_org'],
    1948                          True
    1949                  )
    1950                  self._TCTRL_comment_org.SetValue(gmTools.coalesce(self.data['comment_org'], u''))
    1951
    1952                  self._PRW_name.SetFocus()
    1953          #----------------------------------------------------------------
    1954 -        def _refresh_as_new_from_existing(self):
    1955                  self._refresh_as_new()
    1956                  self._PRW_test_org.SetText (
    1957                          gmTools.coalesce(self.data['pk_test_org'], u'', self.data['name_org']),
    1958                          self.data['pk_test_org'],
    1959                          True
    1960                  )
    1961                  self._TCTRL_comment_org.SetValue(gmTools.coalesce(self.data['comment_org'], u''))
    1962
    1963                  self._PRW_name.SetFocus()
    1964  #================================================================
    1965  _SQL_units_from_test_results = u"""
    1966          -- via clin.v_test_results.pk_type (for types already used in results)
    1967          SELECT
    1968                  val_unit AS data,
    1969                  val_unit AS field_label,
    1970                  val_unit || ' (' || name_tt || ')' AS list_label,
    1971                  1 AS rank
    1972          FROM
    1973                  clin.v_test_results
    1974          WHERE
    1975                  (
    1976                          val_unit %(fragment_condition)s
    1977                                  OR
    1978                          conversion_unit %(fragment_condition)s
    1979                  )
    1980                  %(ctxt_type_pk)s
    1981                  %(ctxt_test_name)s
    1982  """
    1983
    1984  _SQL_units_from_test_types = u"""
    1985          -- via clin.test_type (for types not yet used in results)
    1986          SELECT
    1987                  conversion_unit AS data,
    1988                  conversion_unit AS field_label,
    1989                  conversion_unit || ' (' || name || ')' AS list_label,
    1990                  2 AS rank
    1991          FROM
    1992                  clin.test_type
    1993          WHERE
    1994                  conversion_unit %(fragment_condition)s
    1995                  %(ctxt_ctt)s
    1996  """
    1997
    1998  _SQL_units_from_loinc_ipcc = u"""
    1999          -- via ref.loinc.ipcc_units
    2000          SELECT
    2001                  ipcc_units AS data,
    2002                  ipcc_units AS field_label,
    2003                  ipcc_units || ' (LOINC.ipcc: ' || term || ')' AS list_label,
    2004                  3 AS rank
    2005          FROM
    2006                  ref.loinc
    2007          WHERE
    2008                  ipcc_units %(fragment_condition)s
    2009                  %(ctxt_loinc)s
    2010                  %(ctxt_loinc_term)s
    2011  """
    2012
    2013  _SQL_units_from_loinc_submitted = u"""
    2014          -- via ref.loinc.submitted_units
    2015          SELECT
    2016                  submitted_units AS data,
    2017                  submitted_units AS field_label,
    2018                  submitted_units || ' (LOINC.submitted:' || term || ')' AS list_label,
    2019                  3 AS rank
    2020          FROM
    2021                  ref.loinc
    2022          WHERE
    2023                  submitted_units %(fragment_condition)s
    2024                  %(ctxt_loinc)s
    2025                  %(ctxt_loinc_term)s
    2026  """
    2027
    2028  _SQL_units_from_loinc_example = u"""
    2029          -- via ref.loinc.example_units
    2030          SELECT
    2031                  example_units AS data,
    2032                  example_units AS field_label,
    2033                  example_units || ' (LOINC.example: ' || term || ')' AS list_label,
    2034                  3 AS rank
    2035          FROM
    2036                  ref.loinc
    2037          WHERE
    2038                  example_units %(fragment_condition)s
    2039                  %(ctxt_loinc)s
    2040                  %(ctxt_loinc_term)s
    2041  """
    2042
    2043  _SQL_units_from_atc = u"""
    2044          -- via rev.atc.unit
    2045          SELECT
    2046                  unit AS data,
    2047                  unit AS field_label,
    2048                  unit || ' (ATC: ' || term || ')' AS list_label,
    2049                  2 AS rank
    2050          FROM
    2051                  ref.atc
    2052          WHERE
    2053                  unit IS NOT NULL
    2054                          AND
    2055                  unit %(fragment_condition)s
    2056  """
    2057
    2058  _SQL_units_from_consumable_substance = u"""
    2059          -- via ref.consumable_substance.unit
    2060          SELECT
    2061                  unit AS data,
    2062                  unit AS field_label,
    2063                  unit || ' (' || description || ')' AS list_label,
    2064                  2 AS rank
    2065          FROM
    2066                  ref.consumable_substance
    2067          WHERE
    2068                  unit %(fragment_condition)s
    2069                  %(ctxt_substance)s
    2070  """
    2071  #================================================================
    2072 -class cUnitPhraseWheel(gmPhraseWheel.cPhraseWheel):
    2073
    2074 -        def __init__(self, *args, **kwargs):
    2075
    2076                  query = u"""
    2077  SELECT DISTINCT ON (data)
    2078          data,
    2079          field_label,
    2080          list_label
    2081  FROM (
    2082
    2083          SELECT
    2084                  data,
    2085                  field_label,
    2086                  list_label,
    2087                  rank
    2088          FROM (
    2089                  (%s) UNION ALL
    2090                  (%s) UNION ALL
    2091                  (%s) UNION ALL
    2092                  (%s) UNION ALL
    2093                  (%s) UNION ALL
    2094                  (%s) UNION ALL
    2095                  (%s)
    2096          ) AS all_matching_units
    2097          WHERE data IS NOT NULL
    2098          ORDER BY rank
    2099
    2100  ) AS ranked_matching_units
    2101  LIMIT 50""" % (
    2102                          _SQL_units_from_test_results,
    2103                          _SQL_units_from_test_types,
    2104                          _SQL_units_from_loinc_ipcc,
    2105                          _SQL_units_from_loinc_submitted,
    2106                          _SQL_units_from_loinc_example,
    2107                          _SQL_units_from_atc,
    2108                          _SQL_units_from_consumable_substance
    2109                  )
    2110
    2111                  ctxt = {
    2112                          'ctxt_type_pk': {
    2113                                  'where_part': u'AND pk_test_type = %(pk_type)s',
    2114                                  'placeholder': u'pk_type'
    2115                          },
    2116                          'ctxt_test_name': {
    2117                                  'where_part': u'AND %(test_name)s IN (name_tt, name_meta, code_tt, abbrev_meta)',
    2118                                  'placeholder': u'test_name'
    2119                          },
    2120                          'ctxt_ctt': {
    2121                                  'where_part': u'AND %(test_name)s IN (name, code, abbrev)',
    2122                                  'placeholder': u'test_name'
    2123                          },
    2124                          'ctxt_loinc': {
    2125                                  'where_part': u'AND code = %(loinc)s',
    2126                                  'placeholder': u'loinc'
    2127                          },
    2128                          'ctxt_loinc_term': {
    2129                                  'where_part': u'AND term ~* %(test_name)s',
    2130                                  'placeholder': u'test_name'
    2131                          },
    2132                          'ctxt_substance': {
    2133                                  'where_part': u'AND description ~* %(substance)s',
    2134                                  'placeholder': u'substance'
    2135                          }
    2136                  }
    2137
    2138                  mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = ctxt)
    2139                  mp.setThresholds(1, 2, 4)
    2140                  #mp.print_queries = True
    2141                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    2142                  self.matcher = mp
    2143                  self.SetToolTipString(_('Select the desired unit for the amount or measurement.'))
    2144                  self.selection_only = False
    2145                  self.phrase_separators = u'[;|]+'
    2146  #================================================================
    2147
    2148  #================================================================
    2149 -class cTestResultIndicatorPhraseWheel(gmPhraseWheel.cPhraseWheel):
    2150
    2151 -        def __init__(self, *args, **kwargs):
    2152
    2153                  query = u"""
    2154  select distinct abnormality_indicator,
    2155          abnormality_indicator, abnormality_indicator
    2156  from clin.v_test_results
    2157  where
    2158          abnormality_indicator %(fragment_condition)s
    2159  order by abnormality_indicator
    2160  limit 25"""
    2161
    2162                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    2163                  mp.setThresholds(1, 1, 2)
    2164                  mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"'
    2165                  mp.word_separators = '[ \t&:]+'
    2166                  gmPhraseWheel.cPhraseWheel.__init__ (
    2167                          self,
    2168                          *args,
    2169                          **kwargs
    2170                  )
    2171                  self.matcher = mp
    2172                  self.SetToolTipString(_('Select an indicator for the level of abnormality.'))
    2173                  self.selection_only = False
    2174  #================================================================
    2175  # measurement org widgets / functions
    2176  #----------------------------------------------------------------
    2177 -def edit_measurement_org(parent=None, org=None):
    2178          ea = cMeasurementOrgEAPnl(parent = parent, id = -1)
    2179          ea.data = org
    2180          ea.mode = gmTools.coalesce(org, 'new', 'edit')
    2181          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea)
    2182          dlg.SetTitle(gmTools.coalesce(org, _('Adding new diagnostic org'), _('Editing diagnostic org')))
    2183          if dlg.ShowModal() == wx.ID_OK:
    2184                  dlg.Destroy()
    2185                  return True
    2186          dlg.Destroy()
    2187          return False
    2188  #----------------------------------------------------------------
    2189 -def manage_measurement_orgs(parent=None):
    2190
    2191          if parent is None:
    2192                  parent = wx.GetApp().GetTopWindow()
    2193
    2194          #------------------------------------------------------------
    2195          def edit(org=None):
    2196                  return edit_measurement_org(parent = parent, org = org)
    2197          #------------------------------------------------------------
    2198          def refresh(lctrl):
    2199                  orgs = gmPathLab.get_test_orgs()
    2200                  lctrl.set_string_items ([
    2201                          (o['unit'], o['organization'], gmTools.coalesce(o['test_org_contact'], u''), gmTools.coalesce(o['comment'], u''), o['pk_test_org'])
    2202                          for o in orgs
    2203                  ])
    2204                  lctrl.set_data(orgs)
    2205          #------------------------------------------------------------
    2206          def delete(test_org):
    2207                  gmPathLab.delete_test_org(test_org = test_org['pk_test_org'])
    2208                  return True
    2209          #------------------------------------------------------------
    2210          gmListWidgets.get_choices_from_list (
    2211                  parent = parent,
    2212                  msg = _('\nThese are the diagnostic orgs (path labs etc) currently defined in GNUmed.\n\n'),
    2213                  caption = _('Showing diagnostic orgs.'),
    2214                  columns = [_('Name'), _('Organization'), _('Contact'), _('Comment'), u'#'],
    2215                  single_selection = True,
    2216                  refresh_callback = refresh,
    2217                  edit_callback = edit,
    2218                  new_callback = edit,
    2219                  delete_callback = delete
    2220          )
    2221
    2222  #----------------------------------------------------------------
    2223  from Gnumed.wxGladeWidgets import wxgMeasurementOrgEAPnl
    2224
    2225 -class cMeasurementOrgEAPnl(wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl, gmEditArea.cGenericEditAreaMixin):
    2226
    2227 -        def __init__(self, *args, **kwargs):
    2228
    2229                  try:
    2230                          data = kwargs['org']
    2231                          del kwargs['org']
    2232                  except KeyError:
    2233                          data = None
    2234
    2235                  wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl.__init__(self, *args, **kwargs)
    2236                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    2237
    2238                  self.mode = 'new'
    2239                  self.data = data
    2240                  if data is not None:
    2241                          self.mode = 'edit'
    2242
    2243                  #self.__init_ui()
    2244          #----------------------------------------------------------------
    2245  #       def __init_ui(self):
    2246  #               # adjust phrasewheels etc
    2247          #----------------------------------------------------------------
    2248          # generic Edit Area mixin API
    2249          #----------------------------------------------------------------
    2250 -        def _valid_for_save(self):
    2251                  has_errors = False
    2252                  if self._PRW_org_unit.GetData() is None:
    2253                          if self._PRW_org_unit.GetValue().strip() == u'':
    2254                                  has_errors = True
    2255                                  self._PRW_org_unit.display_as_valid(valid = False)
    2256                          else:
    2257                                  self._PRW_org_unit.display_as_valid(valid = True)
    2258                  else:
    2259                          self._PRW_org_unit.display_as_valid(valid = True)
    2260
    2261                  return (not has_errors)
    2262          #----------------------------------------------------------------
    2263 -        def _save_as_new(self):
    2264                  data = gmPathLab.create_test_org (
    2265                          name = self._PRW_org_unit.GetValue().strip(),
    2266                          comment = self._TCTRL_comment.GetValue().strip(),
    2267                          pk_org_unit = self._PRW_org_unit.GetData()
    2268                  )
    2269                  data['test_org_contact'] = self._TCTRL_contact.GetValue().strip()
    2270                  data.save()
    2271                  self.data = data
    2272                  return True
    2273          #----------------------------------------------------------------
    2274 -        def _save_as_update(self):
    2275                  # get or create the org unit
    2276                  name = self._PRW_org_unit.GetValue().strip()
    2277                  org = gmOrganization.org_exists(organization = name)
    2278                  if org is None:
    2279                          org = gmOrganization.create_org (
    2280                                  organization = name,
    2281                                  category = u'Laboratory'
    2282                          )
    2283                  org_unit = gmOrganization.create_org_unit (
    2284                          pk_organization = org['pk_org'],
    2285                          unit = name
    2286                  )
    2287                  # update test_org fields
    2288                  self.data['pk_org_unit'] = org_unit['pk_org_unit']
    2289                  self.data['test_org_contact'] = self._TCTRL_contact.GetValue().strip()
    2290                  self.data['comment'] = self._TCTRL_comment.GetValue().strip()
    2291                  self.data.save()
    2292                  return True
    2293          #----------------------------------------------------------------
    2294 -        def _refresh_as_new(self):
    2295                  self._PRW_org_unit.SetText(value = u'', data = None)
    2296                  self._TCTRL_contact.SetValue(u'')
    2297                  self._TCTRL_comment.SetValue(u'')
    2298          #----------------------------------------------------------------
    2299 -        def _refresh_from_existing(self):
    2300                  self._PRW_org_unit.SetText(value = self.data['unit'], data = self.data['pk_org_unit'])
    2301                  self._TCTRL_contact.SetValue(gmTools.coalesce(self.data['test_org_contact'], u''))
    2302                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
    2303          #----------------------------------------------------------------
    2304 -        def _refresh_as_new_from_existing(self):
    2305                  self._refresh_as_new()
    2306          #----------------------------------------------------------------
    2307 -        def _on_manage_orgs_button_pressed(self, event):
    2308                  gmOrganizationWidgets.manage_orgs(parent = self)
    2309  #----------------------------------------------------------------
    2310 -class cMeasurementOrgPhraseWheel(gmPhraseWheel.cPhraseWheel):
    2311
    2312 -        def __init__(self, *args, **kwargs):
    2313
    2314                  query = u"""
    2315  SELECT DISTINCT ON (list_label)
    2316          pk AS data,
    2317          unit || ' (' || organization || ')' AS field_label,
    2318          unit || ' @ ' || organization AS list_label
    2319  FROM clin.v_test_orgs
    2320  WHERE
    2321          unit %(fragment_condition)s
    2322                  OR
    2323          organization %(fragment_condition)s
    2324  ORDER BY list_label
    2325  LIMIT 50"""
    2326                  mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
    2327                  mp.setThresholds(1, 2, 4)
    2328                  #mp.word_separators = '[ \t:@]+'
    2329                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    2330                  self.matcher = mp
    2331                  self.SetToolTipString(_('The name of the path lab/diagnostic organisation.'))
    2332                  self.selection_only = False
    2333          #------------------------------------------------------------
    2334 -        def _create_data(self):
    2335                  if self.GetData() is not None:
    2336                          _log.debug('data already set, not creating')
    2337                          return
    2338
    2339                  if self.GetValue().strip() == u'':
    2340                          _log.debug('cannot create new lab, missing name')
    2341                          return
    2342
    2343                  lab = gmPathLab.create_test_org(name = self.GetValue().strip())
    2344                  self.SetText(value = lab['unit'], data = lab['pk_test_org'])
    2345                  return
    2346          #------------------------------------------------------------
    2347 -        def _data2instance(self):
    2348                  return gmPathLab.cTestOrg(aPK_obj = self.GetData())
    2349  #================================================================
    2350 -def manage_meta_test_types(parent=None):
    2351
    2352          if parent is None:
    2353                  parent = wx.GetApp().GetTopWindow()
    2354
    2355          msg = _(
    2356                  '\n'
    2357                  'These are the meta test types currently defined in GNUmed.\n'
    2358                  '\n'
    2359                  'Meta test types allow you to aggregate several actual test types used\n'
    2360                  'by pathology labs into one logical type.\n'
    2361                  '\n'
    2362                  'This is useful for grouping together results of tests which come under\n'
    2363                  'different names but really are the same thing. This often happens when\n'
    2364                  'you switch labs or the lab starts using another test method.\n'
    2365          )
    2366
    2367          mtts = gmPathLab.get_meta_test_types()
    2368
    2369          gmListWidgets.get_choices_from_list (
    2370                  parent = parent,
    2371                  msg = msg,
    2372                  caption = _('Showing meta test types.'),
    2373                  columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), u'#'],
    2374                  choices = [ [
    2375                          m['abbrev'],
    2376                          m['name'],
    2377                          gmTools.coalesce(m['loinc'], u''),
    2378                          gmTools.coalesce(m['comment'], u''),
    2379                          m['pk']
    2380                  ] for m in mtts ],
    2381                  data = mtts,
    2382                  single_selection = True,
    2383                  #edit_callback = edit,
    2384                  #new_callback = edit,
    2385                  #delete_callback = delete,
    2386                  #refresh_callback = refresh
    2387          )
    2388  #================================================================
    2389  # main
    2390  #----------------------------------------------------------------
    2391  if __name__ == '__main__':
    2392
    2393          from Gnumed.pycommon import gmLog2
    2394
    2395          gmI18N.activate_locale()
    2396          gmI18N.install_domain()
    2397          gmDateTime.init()
    2398
    2399          #------------------------------------------------------------
    2400 -        def test_grid():
    2401                  pat = gmPersonSearch.ask_for_patient()
    2402                  app = wx.PyWidgetTester(size = (500, 300))
    2403                  lab_grid = cMeasurementsGrid(parent = app.frame, id = -1)
    2404                  lab_grid.patient = pat
    2405                  app.frame.Show()
    2406                  app.MainLoop()
    2407          #------------------------------------------------------------
    2408 -        def test_test_ea_pnl():
    2409                  pat = gmPersonSearch.ask_for_patient()
    2410                  gmPatSearchWidgets.set_active_patient(patient=pat)
    2411                  app = wx.PyWidgetTester(size = (500, 300))
    2412                  ea = cMeasurementEditAreaPnl(parent = app.frame, id = -1)
    2413                  app.frame.Show()
    2414                  app.MainLoop()
    2415          #------------------------------------------------------------
    2416  #       def test_primary_care_vitals_pnl():
    2417  #               app = wx.PyWidgetTester(size = (500, 300))
    2418  #               pnl = wxgPrimaryCareVitalsInputPnl.wxgPrimaryCareVitalsInputPnl(parent = app.frame, id = -1)
    2419  #               app.frame.Show()
    2420  #               app.MainLoop()
    2421          #------------------------------------------------------------
    2422          if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
    2423                  #test_grid()
    2424                  test_test_ea_pnl()
    2425                  #test_primary_care_vitals_pnl()
    2426
    2427  #================================================================
    2428