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

# Source Code for Module Gnumed.wxpython.gmLabWidgets

      1  """This widgets lets you manage laboratory requests
      2
      3   - add requests
      4   - keep track of pending requests
      5   - see import errors
      6   - review newly imported lab results
      7  """
      8  #============================================================================
      9  # $Source: /home/ncq/Projekte/cvs2git/vcs-mirror/gnumed/gnumed/client/wxpython/gmLabWidgets.py,v $
     10  __version__ = "$Revision: 1.33 $"
     11  __author__ = "Sebastian Hilbert <Sebastian.Hilbert@gmx.net>"
     12
     13  import os.path, sys, os, re as regex, random, logging
     14
     15
     16  # 3rd party
     17  import wx
     18  import wx.lib.mixins.listctrl as listmixins
     19
     20
     21  from Gnumed.pycommon import gmI18N, gmPG2, gmCfg, gmExceptions, gmMatchProvider, gmDispatcher
     22  from Gnumed.business import gmPerson, gmClinicalRecord, gmPathLab, gmStaff
     23  from Gnumed.wxpython import gmGuiHelpers, gmPhraseWheel
     24
     25  _log = gmLog.gmDefLog
     26  if __name__ == '__main__':
     27          _log.SetAllLogLevels(gmLog.lData)
     28  _log.Log(gmLog.lInfo, __version__)
     29
     30  [       wx.ID_LAB_GRID,
     31          wx.ID_NB_LabJournal,
     32          wx.ID_LBOX_pending_results,
     33          wx.ID_PHRWH_labs,
     34          wx.ID_TextCtrl_req_id,
     35          wx.ID_BTN_save_request_ID,
     36          wx.ID_BTN_select_all,
     37          wx.ID_BTN_mark_reviewed,
     38          wx.ID_pending_requests,
     39          wx.ID_lbox_errors,
     40          wx.ID_grid_unreviewed_results
     41  ] = map(lambda _init_ctrls: wx.NewId(), range(11))
     42  #=========================================================
     43 -class cLabDataGridCellRenderer(wxPyGridCellRenderer):
     44 -    def __init__(self):
     45          wxPyGridCellRenderer.__init__(self)
     46
     47 -    def Draw(self, grid, attr, dc, rect, row, col, isSelected):
     48          dc.SetBackgroundMode(wx.SOLID)
     49          dc.SetBrush(wx.Brush(wx.BLACK, wx.SOLID))
     50          dc.SetPen(wx.TRANSPARENT_PEN)
     51          dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)
     52
     53          dc.SetBackgroundMode(wx.TRANSPARENT)
     54          dc.SetFont(attr.GetFont())
     55
     56          text = grid.GetCellValue(row, col)
     57          colors = [wxRED, wx.WHITE, wx.CYAN]
     58          x = rect.x + 1
     59          y = rect.y + 1
     60          for ch in text:
     61              dc.SetTextForeground(random.choice(colors))
     62              dc.DrawText(ch, x, y)
     63              w, h = dc.GetTextExtent(ch)
     64              x = x + w
     65              if x > rect.right - 5:
     66                  break
     67
     68
     69 -    def GetBestSize(self, grid, attr, dc, row, col):
     70          text = grid.GetCellValue(row, col)
     71          dc.SetFont(attr.GetFont())
     72          w, h = dc.GetTextExtent(text)
     73          return wx.Size(w, h)
     74
     75
     76 -    def Clone(self):
     77          return cLabDataGridCellRenderer()
     78  #=========================================================
     79 -class cLabJournalCellRenderer(wxPyGridCellRenderer):
     80 -        def __init__(self):
     81                  wxPyGridCellRenderer.__init__(self)
     82
     83 -        def Draw(self, grid, attr, dc, rect, row, col, isSelected):
     84                  dc.SetBackgroundMode(wx.SOLID)
     85                  dc.SetBrush(wx.Brush(wx.BLACK, wx.SOLID))
     86                  dc.SetPen(wx.TRANSPARENT_PEN)
     87                  dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)
     88                  dc.SetBackgroundMode(wx.TRANSPARENT)
     89                  dc.SetFont(attr.GetFont())
     90
     91                  text = grid.GetCellValue(row, col)
     92                  colors = [wxRED, wx.WHITE, wx.CYAN]
     93                  x = rect.x + 1
     94                  y = rect.y + 1
     95                  for ch in text:
     96                          dc.SetTextForeground(random.choice(colors))
     97                          dc.DrawText(ch, x, y)
     98                          w, h = dc.GetTextExtent(ch)
     99                          x = x + w
    100                          if x > rect.right - 5:
    101                                  break
    102
    103  #=========================================================
    104 -class cLabReviewGrid(wx.Grid):
    105          """This wx.Grid derivative displays lab data that has not yet been reviewed by a clinician.
    106          """
    107 -        def __init__(self, parent, id):
    108                  """Set up our specialised grid.
    109                  """
    110                  wx.Grid.__init__(
    111                          self,
    112                          parent,
    113                          id,
    114                          pos = wx.DefaultPosition,
    115                          size = wx.DefaultSize,
    116                          style= wx.WANTS_CHARS
    117                  )
    118  #=========================================================
    119 -class cLabWheel(gmPhraseWheel.cPhraseWheel):
    120 -        def __init__(self, parent):
    121                  query = """
    122                          select pk, internal_OBSOLETE_name
    123                          from test_org
    124                          """
    125                  mp = gmMatchProvider.cMatchProvider_SQL2([query])
    126                  mp.setThresholds(aWord=2, aSubstring=4)
    127
    128                  gmPhraseWheel.cPhraseWheel.__init__(
    129                          self,
    130                          parent = parent,
    131                          id = -1,
    132                          size = wx.DefaultSize,
    133                          pos = wx.DefaultPosition
    134                  )
    135                  self.SetToolTipString(_('choose which lab will process the probe with the specified ID'))
    136                  self.matcher = mp
    137  #=========================================================
    138  # FIXME: is this really lab specific ?
    139 -class cLabIDListCtrl(wx.ListCtrl, wx.ListCtrlAutoWidthMixin):
    140 -        def __init__(self, parent, id, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):
    141                  wx.ListCtrl.__init__(self, parent, id, pos, size, style)
    142                  wx.ListCtrlAutoWidthMixin.__init__(self)
    143
    144  #=========================================================
    145 -class cLabJournalNB(wx.Notebook):
    146          """This wx.Notebook derivative displays 'records still due' and lab-import related errors.
    147          """
    148 -        def __init__(self, parent, id):
    149                  """Set up our specialised notebook.
    150                  """
    151                  wx.Notebook.__init__(
    152                          self,
    153                          parent,
    154                          id,
    155                          wx.DefaultPosition,
    156                          wx.DefaultSize,
    157                          0
    158                  )
    159
    160                  self.__pat = gmPerson.gmCurrentPatient()
    161
    162                  self.__do_layout_requests_page()
    163                  self.__do_layout_errors_page()
    164                  self.__do_layout_review_page()
    165                  self.__do_layout_config_page()
    166
    167                  self.__register_events()
    168          #------------------------------------------------------------------------
    169 -        def __do_layout_config_page(self):
    170                  pnl_page = wx.Panel(self, -1)
    171
    172
    173
    174                  szr_page = wx.BoxSizer(wx.VERTICAL)
    175  #               szr_page.Add(hbszr,0, wxALIGN_LEFT | wxALL, 5)
    176  #               szr_page.Add(self.lbox_pending, 1, wxEXPAND | wxALIGN_CENTER | wxALL, 5)
    177
    178                  pnl_page.SetAutoLayout(True)
    179                  pnl_page.SetSizer(szr_page)
    180                  szr_page.Fit(pnl_page)
    181                  szr_page.SetSizeHints(pnl_page)
    182
    183                  self.AddPage(pnl_page, _("lab config"))
    184          #------------------------------------------------------------------------
    185 -        def __do_layout_requests_page(self):
    186                  # notebook tab with pending requests
    187                  pnl_page = wx.Panel(self, -1)
    188
    189                  # -- add request area --
    190                  hbszr = wx.StaticBoxSizer(
    191                          wx.StaticBox(
    192                                  pnl_page,
    193                                  -1,
    194                                  _("add new request for current patient")
    195                          ),
    196                          wx.HORIZONTAL
    197                  )
    198                  # label
    199                  lab_label = wx.StaticText(
    200                          name = 'lablabel',
    201                          parent = pnl_page,
    202                          id = -1,
    203                          label = _('Lab')
    204                  )
    205                  # phrase wheel
    206                  self.lab_wheel = cLabWheel(pnl_page)
    207                  self.lab_wheel.on_resize (None)
    208                  self.lab_wheel.add_callback_on_selection(self.on_lab_selected)
    209                  # label
    210                  req_id_label = wx.StaticText(
    211                          name = 'req_id_label',
    212                          parent = pnl_page,
    213                          id = -1,
    214                          label = _("Specimen ID")
    215                  )
    216                  # request_id field
    217                  self.fld_request_id = wx.TextCtrl (
    218                          pnl_page,
    219                          wx.ID_TextCtrl_req_id,
    220                          "",
    221                          wx.DefaultPosition,
    222                          wx.Size(80,-1),
    223                          0
    224                  )
    225                  # "save request id" button
    226                  self.BTN_save_request_ID = wx.Button(
    227                          name = 'BTN_save_request_ID',
    228                          parent = pnl_page,
    229                          id = wx.ID_BTN_save_request_ID,
    230                          label = _("save lab request")
    231                  )
    232                  self.BTN_save_request_ID.SetToolTipString(_('associate chosen lab and ID with current patient'))
    233
    234                  hbszr.Add(lab_label, 0, wx.ALIGN_CENTER | wx.ALL, 5)
    235                  hbszr.Add(self.lab_wheel, 0, wx.ALIGN_CENTER | wx.ALL, 5)
    236                  hbszr.Add(req_id_label, 0, wx.ALIGN_CENTER | wx.ALL, 5)
    237                  hbszr.Add(self.fld_request_id, 0, wx.ALIGN_CENTER| wx.ALL, 5)
    238                  hbszr.Add(self.BTN_save_request_ID, 0, wx.ALIGN_CENTER | wx.ALL, 5)
    239
    240                  # -- add list of pending requests --
    241                  self.lbox_pending = cLabIDListCtrl(
    242                          pnl_page,
    243                          wx.ID_pending_requests,
    244                          size = wx.DefaultSize,
    245                          style = wx.LC_REPORT | wx.SUNKEN_BORDER | wx.LC_VRULES
    246                  )
    247
    248                  self.lbox_pending.InsertColumn(0, _("date"))
    249                  self.lbox_pending.InsertColumn(1, _("lab"))
    250                  self.lbox_pending.InsertColumn(2, _("sample id"))
    251                  self.lbox_pending.InsertColumn(3, _("patient"))
    252                  self.lbox_pending.InsertColumn(4, _("status"))
    253
    254                  szr_page = wx.BoxSizer(wx.VERTICAL)
    255                  szr_page.Add(hbszr,0, wx.ALIGN_LEFT | wx.ALL, 5)
    256                  szr_page.Add(self.lbox_pending, 1, wxEXPAND | wx.ALIGN_CENTER | wx.ALL, 5)
    257  #               szr_page.Add(self.lbox_pending, 1, wxEXPAND | wxALIGN_CENTER | wxALL, 5)
    258
    259                  pnl_page.SetAutoLayout(True)
    260                  pnl_page.SetSizer(szr_page)
    261                  szr_page.Fit(pnl_page)
    262                  szr_page.SetSizeHints(pnl_page)
    263
    264                  self.AddPage(pnl_page, _("pending requests"))
    265          #------------------------------------------------------------------------
    266 -        def __do_layout_errors_page(self):
    267                  pnl_page = wx.Panel( self, -1)
    268
    269                  self.lbox_errors = cLabIDListCtrl (
    270                          parent = pnl_page,
    271                          id = wx.ID_lbox_errors,
    272                          size = wx.DefaultSize,
    273                          style = wx.LC_REPORT | wx.SUNKEN_BORDER | wx.LC_VRULES
    274                  )
    275                  self.lbox_errors.InsertColumn(0, _("noticed when"))
    276                  self.lbox_errors.InsertColumn(1, _("problem"))
    277                  self.lbox_errors.InsertColumn(2, _("solution"))
    278                  self.lbox_errors.InsertColumn(3, _("context"))
    279
    280                  szr_page = wx.BoxSizer(wx.VERTICAL)
    281                  szr_page.Add(self.lbox_errors, 1, wxEXPAND| wx.ALIGN_CENTER | wx.ALL, 5)
    282  #               szr_page.Add(self.lbox_errors, 1, wxEXPAND| wxALIGN_CENTER | wxALL, 5)
    283
    284                  pnl_page.SetAutoLayout(True)
    285                  pnl_page.SetSizer(szr_page)
    286                  szr_page.Fit(pnl_page)
    287                  szr_page.SetSizeHints(pnl_page)
    288
    289                  self.AddPage(pnl_page, _("lab errors"))
    290          #------------------------------------------------------------------------
    291 -        def __do_layout_review_page(self):
    292                  pnl_page = wx.Panel( self, -1)
    293
    294                  # -- create new grid --
    295                  self.__grid_unreviewed_results = cLabReviewGrid(
    296                          pnl_page,
    297                          wx.ID_grid_unreviewed_results
    298                  )
    299                  self.__grid_unreviewed_results.CreateGrid(0, 8, wx.Grid.wx.GridSelectCells)
    300                  self.__grid_unreviewed_results.SetDefaultCellAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTRE)
    301                  # there is a bug in wxGTK for this method...
    302                  self.__grid_unreviewed_results.AutoSizeColumns(True)
    303                  self.__grid_unreviewed_results.AutoSizeRows(True)
    304                  # what is this supposed to do ?!?
    305                  renderer = apply(cLabJournalCellRenderer, ())
    306                  self.__grid_unreviewed_results.SetDefaultRenderer(renderer)
    307                  # attribute objects let you keep a set of formatting values
    308                  # in one spot, and reuse them if needed
    309  #               font = self.GetFont()
    310  #               font.SetWeight(wxNORMAL)
    311  #               attr = wxGridCellAttr()
    312  #               attr.SetFont(font)
    313                  #attr.SetBackgroundColour(wx.LIGHT_GREY)
    314  #               attr.SetReadOnly(True)
    315                  #attr.SetAlignment(wxRIGHT, -1)
    316  #               self.__grid_unreviewed_results.SetLabelFont(font)
    317                  # layout review grid
    318                  self.__grid_unreviewed_results.SetColLabelValue(0, _('reviewed'))
    319                  self.__grid_unreviewed_results.SetColLabelValue(1, _('relevant'))
    320                  self.__grid_unreviewed_results.SetColLabelValue(2, _('patient'))
    321                  self.__grid_unreviewed_results.SetColLabelValue(3, _('facility'))
    322                  self.__grid_unreviewed_results.SetColLabelValue(4, _('analysis'))
    323                  self.__grid_unreviewed_results.SetColLabelValue(5, _('result'))
    324                  self.__grid_unreviewed_results.SetColLabelValue(6, _('range'))
    325                  self.__grid_unreviewed_results.SetColLabelValue(7, _('info provided by lab'))
    326                  # turn row labels off
    327                  self.__grid_unreviewed_results.SetRowLabelSize(0)
    328                  self.__grid_unreviewed_results.AutoSize()
    329
    330                  # -- add buttons --
    331                  # "select all requests"
    332                  self.BTN_select_all = wx.Button(
    333                          name = 'BTN_select_all',
    334                          parent = pnl_page,
    335                          id = wx.ID_BTN_select_all,
    336                          label = _("select all requests")
    337                  )
    338                  self.BTN_select_all.SetToolTipString(_('select all requests'))
    339                  # "mark selected as reviewed"
    340                  self.BTN_mark_reviewed = wx.Button(
    341                          name = 'BTN_mark_reviewed',
    342                          parent = pnl_page,
    343                          id = wx.ID_BTN_mark_reviewed,
    344                          label = _("mark selected requests as reviewed")
    345                  )
    346                  self.BTN_mark_reviewed.SetToolTipString(_('mark selected requests as reviewed'))
    347
    348                  szr_buttons = wx.BoxSizer(wx.HORIZONTAL)
    349                  szr_buttons.Add(self.BTN_select_all, 0, wx.ALIGN_CENTER_VERTICAL, 1)
    350                  szr_buttons.Add(self.BTN_mark_reviewed, 0, wx.ALIGN_CENTER_VERTICAL, 1)
    351
    352                  # -- do layout --
    353                  szr_page = wx.BoxSizer(wx.VERTICAL)
    354                  szr_page.Add(self.__grid_unreviewed_results, 1, wxEXPAND | wx.ALIGN_CENTER | wx.ALL, 5)
    355                  szr_page.Add(szr_buttons, 0, wxEXPAND | wx.ALIGN_CENTER | wx.ALL, 5)
    356
    357                  pnl_page.SetAutoLayout(True)
    358                  pnl_page.SetSizer(szr_page)
    359                  szr_page.Fit(pnl_page)
    360                  szr_page.SetSizeHints(pnl_page)
    361
    362                  self.AddPage(pnl_page, _("unreviewed results"))
    363          #------------------------------------------------------------------------
    364 -        def __register_events(self):
    365                  wx.EVT_BUTTON(self.BTN_save_request_ID, wx.ID_BTN_save_request_ID, self.on_save_request_ID)
    366                  wx.EVT_BUTTON(self.BTN_select_all, wx.ID_BTN_select_all, self.on_select_all)
    367                  wx.EVT_BUTTON(self.BTN_mark_reviewed, wx.ID_BTN_mark_reviewed, self._on_mark_reviewed)
    368
    369                  wx.EVT_GRID_CELL_LEFT_CLICK(self.__grid_unreviewed_results, self.OnLeftSClick)
    370                  wx.EVT_GRID_CELL_LEFT_DCLICK(self.__grid_unreviewed_results, self.OnLeftDClick)
    371                  #wx.EVT_GRID_SELECT_CELL(self.__grid_unreviewed_results, self.OnSelectCell)
    372                  wx.EVT_KEY_UP(self.__grid_unreviewed_results, self.OnKeyPressed)
    373          #------------------------------------------------------------------------
    374 -        def update(self):
    375                  if self.__pat['pk'] is None:
    376                          gmGuiHelpers.gm_show_error(
    377                                  aMessage = _('Cannot load lab journal.\nYou first need to select a patient.'),
    378                                  aTitle = _('loading lab journal')
    379                          )
    380                          return None
    381
    382                  if self.__populate_notebook() is None:
    383                          return None
    384                  return 1
    385          #------------------------------------------------------------------------
    386 -        def __populate_notebook(self):
    387
    388                  self.fld_request_id.Clear()
    389                  self.lab_wheel.Clear()
    390
    391                  #------ due PNL ------------------------------------
    392                  # FIXME: make limit configurable
    393                  too_many, pending_requests = gmPathLab.get_pending_requests(limit=250)
    394                  # clear list
    395                  self.lbox_pending.DeleteAllItems()
    396                  # FIXME: make use of too_many
    397                  for request in pending_requests:
    398                          item_idx = self.lbox_pending.InsertItem(info=wx.ListItem())
    399                          # request date
    400                          self.lbox_pending.SetStringItem(index = item_idx, col=0, label=request['sampled_when'].date)
    401                          # request lab
    402                          lab = self.__get_labname(request['pk_test_org'])
    403                          self.lbox_pending.SetStringItem(index = item_idx, col=1, label=lab[0][0])
    404                          # request id
    405                          self.lbox_pending.SetStringItem(index = item_idx, col=2, label=request['request_id'])
    406                          # patient
    407                          pat = request.get_patient()
    408                          self.lbox_pending.SetStringItem(index = item_idx, col=3, label="%s %s (%s)" % (pat[2], pat[3], pat[4].date))
    409                          self.lbox_pending.SetStringItem(index = item_idx, col=4, label=_('pending'))
    410                          # FIXME: make use of rest data in patient via mouse over context
    411
    412                  #----- import errors PNL -----------------------
    413                  lab_errors = self.__get_import_errors()
    414                  # clear list
    415                  self.lbox_errors.DeleteAllItems()
    416                  # populate list
    417                  for lab_error in lab_errors:
    418                          item_idx = self.lbox_errors.InsertItem(info=wx.ListItem())
    419                          # when was error reported
    420                          self.lbox_errors.SetStringItem(index = item_idx, col=0, label=lab_error[1].date)
    421                          # error
    422                          self.lbox_errors.SetStringItem(index = item_idx, col=1, label=lab_error[4])
    423                          # solution
    424                          self.lbox_errors.SetStringItem(index = item_idx, col=2, label=lab_error[5])
    425                          # context
    426                          self.lbox_errors.SetStringItem(index = item_idx, col=3, label=lab_error[6])
    427
    428                  #------ unreviewed lab results PNL ------------------------------------
    429                  # FIXME: make configurable, make use of count visible lines func of wxlistctrl
    430                  more_avail, self.dict_unreviewed_results = gmPathLab.get_unreviewed_results(limit=50)
    431
    432                  # FIXME: react to errors
    433
    434                  # clear grid
    435                  self.__grid_unreviewed_results.ClearGrid()
    436                  # set number of rows
    437                  if self.__grid_unreviewed_results.GetNumberRows() == 0:
    438                          self.__grid_unreviewed_results.AppendRows(len(self.dict_unreviewed_results))
    439                  # populate grid
    440                  for item_idx in range(len(self.dict_unreviewed_results)):
    441                          result = self.dict_unreviewed_results[item_idx]
    442
    443                          # boolean renderer for first and second column
    444                          renderer = apply(wx.GridCellBoolRenderer, ())
    445                          self.__grid_unreviewed_results.SetCellRenderer(item_idx, 0 , renderer)
    446                          self.__grid_unreviewed_results.SetCellRenderer(item_idx, 1 , renderer)
    447                          # set all cells read only
    448                          self.__grid_unreviewed_results.SetReadOnly(item_idx, 0, 1)
    449                          self.__grid_unreviewed_results.SetReadOnly(item_idx, 1, 1)
    450                          #self.__grid_unreviewed_results.SetReadOnly(item_idx, 2, True)
    451                          self.__grid_unreviewed_results.EnableGridLines(0)
    452                          # "reviewed" checkbox in first column
    453                          try:
    454                                  self.__grid_unreviewed_results.SetColSize(0, self.__grid_unreviewed_results.GetColMinimalAcceptableWidth())
    455                          except AttributeError:
    456                                  pass
    457                          self.__grid_unreviewed_results.SetCellValue(item_idx, 0, '0')
    458                          # "relevant" checkbox in second column
    459                          try:
    460                                  self.__grid_unreviewed_results.SetColSize(1, self.__grid_unreviewed_results.GetColMinimalAcceptableWidth())
    461                          except AttributeError:
    462                                  pass
    463                          self.__grid_unreviewed_results.SetCellValue(item_idx, 1, '0')
    464                          # abnormal ? -> display in red
    465                          if (result['abnormal'] is not None) and (result['abnormal'].strip() != ''):
    466                                  self.__grid_unreviewed_results.SetCellTextColour(item_idx,2,wx.RED)
    467                                  self.__grid_unreviewed_results.SetCellTextColour(item_idx,3,wx.RED)
    468                                  self.__grid_unreviewed_results.SetCellTextColour(item_idx,4,wx.RED)
    469                                  self.__grid_unreviewed_results.SetCellTextColour(item_idx,5,wx.RED)
    470                                  self.__grid_unreviewed_results.SetCellTextColour(item_idx,6,wx.RED)
    471                                  self.__grid_unreviewed_results.SetCellTextColour(item_idx,7,wx.RED)
    472                                  # abnormal status from lab
    473                                  info = '(%s)' % result['abnormal']
    474                                  # technically abnormal -> defaults to relevant = true
    475                                  self.__grid_unreviewed_results.SetCellValue(item_idx, 1, '1')
    476                          else:
    477                                  info = ''
    478                                  # technically normal -> defaults to relevant = False
    479                                  self.__grid_unreviewed_results.SetCellValue(item_idx, 1, '0')
    480                          # patient
    481                          pat = result.get_patient()
    482                          self.__grid_unreviewed_results.SetCellValue(item_idx, 2, "%s %s (%s)" % (pat[2], pat[3], pat[4].date))
    483                          self.__grid_unreviewed_results.SetColSize(2,200)
    484                          # rxd when
    485                          self.__grid_unreviewed_results.SetCellValue(item_idx, 3, result['lab_rxd_when'].date)
    486                          self.__grid_unreviewed_results.SetColSize(3,80)
    487                          # test name
    488                          self.__grid_unreviewed_results.SetCellValue(item_idx, 4, result['unified_name'])
    489                          self.__grid_unreviewed_results.SetColSize(4,100)
    490                          # result including unit
    491                          # FIXME: what about val_unit empty ?
    492                          self.__grid_unreviewed_results.SetCellValue(item_idx, 5, '%s %s' % (result['unified_val'], info))
    493                          self.__grid_unreviewed_results.SetColSize(5,80)
    494                          # normal range
    495                          if result['val_normal_range'] is None:
    496                                  self.__grid_unreviewed_results.SetCellValue(item_idx, 6, '')
    497                          else:
    498                                  self.__grid_unreviewed_results.SetCellValue(item_idx, 6, '%s %s' % (result['val_normal_range'], result['val_unit']))
    499                          self.__grid_unreviewed_results.SetColSize(6,80)
    500                          # FIXME: target range
    501                          # notes from provider
    502                          if result['note_provider'] is None:
    503                                  self.__grid_unreviewed_results.SetCellValue(item_idx, 7, '')
    504                          else:
    505                                  self.__grid_unreviewed_results.SetCellValue(item_idx, 7, result['note_provider'])
    506
    507                  # we show 50 items at once , notify user if there are more
    508                  if more_avail:
    509                          gmDispatcher.send(signal = 'statustext', msg =_('More unreviewed results available. Review some to see more.'))
    510          #------------------------------------------------------------------------
    511 -        def __get_import_errors(self):
    512                  query = """select * from housekeeping_todo where category='lab'"""
    513                  import_errors = gmPG.run_ro_query('historica', query)
    514                  return import_errors
    515          #------------------------------------------------------------------------
    516 -        def __get_labname(self, data):
    517                  # FIXME: eventually, this will be done via a cOrg value object class
    518                  query= """select internal_OBSOLETE_name from test_org where pk=%s"""
    519                  labs = gmPG.run_ro_query('historica', query, None, data)
    520                  return labs
    521
    522          #-----------------------------------
    523          # event handlers
    524          #------------------------------------------------------------------------
    525 -        def OnLeftSClick(self, event):
    526                  self.OnSelectCell(event, selector='LSClick')
    527                  event.Skip()
    528          #------------------------------------------------------------------------
    529 -        def OnLeftDClick(self, event):
    530                  self.OnSelectCell(event, selector='LDClick')
    531                  event.Skip()
    532          #------------------------------------------------------------------------
    533 -        def CrosscheckRelevant(self):
    534                  # reviewed checked -> check relevant if result is abnormal
    535                  #if (result['abnormal'] is not None) and (result['abnormal'].strip() != ''):
    536                  #       self.__grid_unreviewed_results.SetCellValue(row, col, '1')
    537                  print "only stub for Crosscheck - please fix"
    538          #------------------------------------------------------------------------
    539 -        def OnSelectCell(self, event, selector=None):
    540                  if selector is None:
    541  #                       event.Skip()
    542                          return None
    543
    544                  if selector in ['SelKEY', 'LDClick']:
    545                          #print 'key pressed %s' %selector
    546                          col = self.__grid_unreviewed_results.GetGridCursorCol()
    547                          row = self.__grid_unreviewed_results.GetGridCursorRow()
    548                  if selector in ['LSClick']:
    549                          #print 'key pressed %s' %selector
    550                          col = event.GetCol()
    551                          row = event.GetRow()
    552
    553                  if col in [0,1]:
    554                          if self.__grid_unreviewed_results.GetCellValue(row,col) == '1':         # if set
    555                                  self.__grid_unreviewed_results.SetCellValue(row,col, '0')               # then unset
    556                          else:                                                                                                                           # if unset
    557                                  self.__grid_unreviewed_results.SetCellValue(row,col,'1')                # then set
    558                                  self.CrosscheckRelevant()
    559                          event.Skip()
    560          #-------------------------------------------------------
    561 -        def OnKeyPressed (self, key):
    562                  """Is called when a key is pressed."""
    563                  #key.Skip()
    564
    565                  # user moved down
    566                  if key.GetKeyCode() == WXK_DOWN:
    567                          key.Skip()
    568                          #self.__on_down_arrow(key)
    569                          return
    570                  # user moved up
    571                  if key.GetKeyCode() == wx.WXK_UP:
    572                          key.Skip()
    573                          #self.__on_up_arrow(key)
    574                          return
    575
    576                  # FIXME: need PAGE UP/DOWN//POS1/END here
    577
    578                  #user pressed <SPACE>
    579                  if key.GetKeyCode() == WXK_SPACE:
    580                          self.OnSelectCell(key,selector='SelKEY')
    581                          return
    582          # -------------------------------------------------
    583 -        def on_save_request_ID(self, event):
    584                  req_id = self.fld_request_id.GetValue()
    585                  if (req_id is None) or (req_id.strip() == ''):
    586                          gmGuiHelpers.gm_show_error (
    587                                  _('You must type in a request ID !\n\nUsually you will find the request ID written on\nthe barcode sticker on your probe container.'),
    588                                  _('saving request id')
    589                          )
    590                          return None
    591                  emr = self.__pat.get_emr()
    592                  request = emr.add_lab_request(lab=int(self.lab), req_id = req_id)
    593                  if request is None:
    594                          gmDispatcher.send(signal = 'statustext', msg =_('Cannot save lab request.'))
    595                          return None
    596
    597                  # FIXME: maybe populate request list only ?
    598                  # btw, we can make the sub-notebook tabs load data on-demand just
    599                  # like the main notebook tabs :-)
    600                  self.__populate_notebook()
    601          #------------------------------------------------
    602 -        def on_select_all(self, event):
    603                  for item_idx in range(self.__grid_unreviewed_results.GetNumberRows()):
    604                          self.__grid_unreviewed_results.SetCellValue(item_idx, 0, '1')
    605          #------------------------------------------------
    606 -        def _on_mark_reviewed(self, event):
    607                  reviewed_results = []
    608                  for row in range(self.__grid_unreviewed_results.GetNumberRows()):
    609                          if self.__grid_unreviewed_results.GetCellValue(row, 0) == '1':
    610                                  # look up associated request
    611                                  result = self.dict_unreviewed_results[row]
    612                                  reviewed_results.append(result)
    613                                  # update "relevant" status
    614                                  relevant = self.__grid_unreviewed_results.GetCellValue(row, 1)
    615                                  if relevant == '1':
    616                                          result['relevant'] = 'true'
    617                                  else:
    618                                          result['relevant'] = 'false'
    619
    620                  if len(reviewed_results) == 0:
    621                          gmGuiHelpers.beep_status_text(_('No results marked as reviewed.'))
    622                          event.Skip()
    623                          return None
    624
    625                  for result in reviewed_results:
    626                          result['reviewed'] = 'true'
    627                          result['pk_reviewer'] = gmStaff.gmCurrentProvider()['pk_staff']
    628                          if not result['abnormal']:
    629                                  result['abnormal'] = ''
    630                          successfull, error = result.save_payload()
    631                          # repopulate
    632                          if successfull:
    633                                  self.__populate_notebook()
    634                          else:
    635                                  _log.Log(gmLog.lErr, 'setting result status to reviewed failed %s' % error)
    636                                  gmGuiHelpers.gm_show_error (
    637                                          aMessage = _('Cannot mark results as "reviewed":\n%s') % error,
    638                                          aTitle = _('update result status')
    639                                  )
    640                                  return None
    641
    642                  event.Skip()
    643          #--------------------------------------------------------
    644 -        def __on_right_click(self, evt):
    645                  event.Skip()
    646          #-------------------------------------------------------
    647 -        def on_lab_selected(self,data):
    648                  if data is None:
    649                          self.fld_request_id.SetValue('')
    650                          return None
    651                  # propose new request id
    652                  nID = gmPathLab.get_next_request_ID(int(data))
    653                  if not nID is None:
    654                          # set field to that
    655                          self.fld_request_id.SetValue(nID)
    656                  # FIXME : this is needed so save_request_ID knows about the lab
    657                  self.lab =  data
    658
    659  #=========================================================
    660 -class cLabDataGrid(wx.Grid):
    661          """This wx.Grid derivative displays a grid view of stored lab data.
    662          """
    663 -        def __init__(self, parent, id):
    664                  """Set up our specialised grid.
    665                  """
    666                  # get connection
    667                  self.__backend = gmPG.ConnectionPool()
    668                  self.__defconn = self.__backend.GetConnection('blobs')
    669                  if self.__defconn is None:
    670                          _log.Log(gmLog.lErr, "Cannot retrieve lab data without database connection !")
    671                          raise gmExceptions.ConstructorError, "cLabDataGrid.__init__(): need db conn"
    672
    673                  # connect to config database
    674                  self.__dbcfg = gmCfg.cCfgSQL(
    675                          aConn = self.__backend.GetConnection('default'),
    676                          aDBAPI = gmPG.dbapi
    677                  )
    678
    679                  wx.Grid.__init__(
    680                          self,
    681                          parent,
    682                          id,
    683                          pos = wx.DefaultPosition,
    684                          size = wx.DefaultSize,
    685                          style= wx.WANTS_CHARS
    686                          )
    687
    688                  self.__pat = gmPerson.gmCurrentPatient()
    689
    690                  #wx.EVT_GRID_CELL_LEFT_DCLICK(self, self.OnLeftDClick)
    691
    692                  # create new grid
    693                  self.__grid_unreviewed_results = self.CreateGrid(0, 0, wx.Grid.wx.GridSelectCells )
    694                  self.SetDefaultCellAlignment(wx.ALIGN_RIGHT,wx.ALIGN_CENTRE)
    695                  #renderer = apply(wxGridCellStringRenderer, ())
    696                  renderer = apply(cLabDataGridCellRenderer, ())
    697                  self.SetDefaultRenderer(renderer)
    698
    699                  # There is a bug in wxGTK for this method...
    700                  self.AutoSizeColumns(True)
    701                  self.AutoSizeRows(True)
    702                  # attribute objects let you keep a set of formatting values
    703                  # in one spot, and reuse them if needed
    704                  font = self.GetFont()
    705                  #font.SetWeight(wx.BOLD)
    706                  attr = wx.GridCellAttr()
    707                  attr.SetFont(font)
    708                  #attr.SetBackgroundColour(wx.LIGHT_GREY)
    709                  attr.SetReadOnly(True)
    710                  #attr.SetAlignment(wxRIGHT, -1)
    711                  #attr.IncRef()
    712                  #self.SetLabelFont(font)
    713
    714
    715          # I do this because I don't like the default behaviour of not starting the
    716          # cell editor on double clicks, but only a second click.
    717 -        def OnLeftDClick(self, evt):
    718                  if self.CanEnableCellControl():
    719                          self.EnableCellEditControl()
    720
    721          #------------------------------------------------------------------------
    722 -        def update(self):
    723                  if self.__pat['pk'] is None:
    724                          _log.Log(gmLog.lErr, 'need patient for update')
    725                          gmGuiHelpers.gm_show_error(
    726                                  aMessage = _('Cannot load lab data.\nYou first need to select a patient.'),
    727                                  aTitle = _('loading lab data')
    728                          )
    729                          return None
    730
    731                  if self.__populate_grid() is None:
    732                          return None
    733
    734                  return 1
    735
    736          #------------------------------------------------------------------------
    737 -        def __populate_grid(self):
    738                  """Fill grid with data.
    739
    740                  sorting:
    741                          1) check user's preferred way of sorting
    742                                  none defaults to smart sorting
    743                          2) check if user defined lab profiles
    744                                  - add a notebook tab for each profile
    745                                  - postpone profile dependent stats until tab is selected
    746                          sort modes :
    747                                  1: no profiles -> smart sorting only
    748                                  2: profile -> smart sorting first
    749                                  3: profile -> user defined profile order
    750                  """
    751                  emr = self.__pat.get_emr()
    752                  # FIXME: there might be too many results to handle in memory
    753                  results = emr.get_lab_results()
    754                  if results is None:
    755                          name = self.__pat.get_names()
    756                          gmGuiHelpers.gm_show_error (
    757                                  aMessage = _('Error loading lab data for patient\n[%s %s].') % (name['firstnames'], name['lastnames']),
    758                                  aTitle = _('loading lab data')
    759                          )
    760                          return None
    761                  if len(results) == 0:
    762                          gmDispatcher.send(signal = 'statustext', msg =_('No lab data available.'))
    763                          return None
    764
    765                  dates, test_names = self.__compile_stats(results)
    766                  # sort tests before pushing onto the grid
    767                  #sort_mode = gmPerson.getsort_mode() # yet to be written
    768                  sort_mode = 1 # get real here :-)
    769
    770                  if sort_mode == 1:
    771                          """
    772                          2) look at the the most recent date a test was performed on
    773                                  move these tests to the top
    774                          3) sort by runs starting with most recent date
    775                                  a run is a series of consecutive dates a particular test was done on
    776                                  sort by length of the runs
    777                                  longest run will move to the top
    778                          """
    779                          pass
    780
    781                  # clear grid
    782                  self.ClearGrid()
    783                  # add columns
    784                  if self.GetNumberCols() == 0:
    785                          self.AppendCols(len(dates))
    786                          # set column labels
    787                          for i in range(len(dates)):
    788                                  self.SetColLabelValue(i, dates[i])
    789                  # add rows
    790                  if self.GetNumberRows() == 0:
    791                          self.AppendRows(len(test_names))
    792                          # add labels
    793                          for i in range(len(test_names)):
    794                                  self.SetRowLabelValue(i, test_names[i])
    795                  # push data onto grid
    796                  cells = []
    797                  for result in results:
    798                          # get  x,y position for result
    799                          x = dates.index(result['val_when'].date)
    800                          y = test_names.index(result['unified_name'])
    801                          cell_data = self.GetCellValue(x, y)
    802                          if cell_data == '':
    803                                  self.SetCellValue(x, y, '%s %s' % (result['unified_val'], result['val_unit']))
    804                          else:
    805                                  self.SetCellValue(x, y, '%s\n%s %s' % (cell_data, result['unified_val'], result['val_unit']))
    806                          # you can set cell attributes for the whole row (or column)
    807                          #self.SetRowAttr(int(y), attr)
    808                          #self.SetColAttr(int(x), attr)
    809                          #self.SetCellRenderer(int(x), int(y), renderer)
    810
    811                  self.AutoSize()
    812                  return 1
    813          #------------------------------------------------------------------------
    814 -        def __compile_stats(self, lab_results=None):
    815                  # parse record for dates and tests
    816                  dates = []
    817                  test_names = []
    818                  for result in lab_results:
    819                          if result['val_when'].date not in dates:
    820                                  dates.append(result['val_when'].date)
    821                          if result['unified_name'] not in test_names:
    822                                  test_names.append(result['unified_name'])
    823                  dates.sort()
    824
    825                  return dates, test_names
    826          #------------------------------------------------------------------------
    827          #def sort_by_value(self, d=None):
    828          #    """ Returns the keys of dictionary d sorted by their values """
    829          #    items=d.items()
    830          #    backitems=[ [v[1],v[0]] for v in items]
    831          #    backitems.sort()
    832          #    return [ backitems[i][1] for i in range(0,len(backitems))]
    833
    834          #--------------------------------------------------------
    835 -        def __on_right_click(self, evt):
    836                  pass
    837                  #evt.Skip()
    838
    839  #=========================================================
    840  # MAIN
    841  #---------------------------------------------------------
    842  if __name__ == '__main__':
    843          _log.Log (gmLog.lInfo, "starting lab journal")
    844
    845          # catch all remaining exceptions
    846          try:
    847                  application = wxPyWidgetTester(size=(640,480))
    848                  application.SetWidget(cStandalonePanel,-1)
    849                  application.MainLoop()
    850          except:
    851                  _log.LogException("unhandled exception caught !", sys.exc_info(), 1)
    852                  # but re-raise them
    853                  raise
    854          #gmPG.StopListeners()
    855          _log.Log (gmLog.lInfo, "closing lab journal")
    856  #=========================================================
    857