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):
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 #------------------------------------------------------------------------
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 results = None 753 if results is None: 754 name = self.__pat.get_names() 755 gmGuiHelpers.gm_show_error ( 756 aMessage = _('Error loading lab data for patient\n[%s %s].') % (name['firstnames'], name['lastnames']), 757 aTitle = _('loading lab data') 758 ) 759 return None 760 if len(results) == 0: 761 gmDispatcher.send(signal = 'statustext', msg =_('No lab data available.')) 762 return None 763 764 dates, test_names = self.__compile_stats(results) 765 # sort tests before pushing onto the grid 766 #sort_mode = gmPerson.getsort_mode() # yet to be written 767 sort_mode = 1 # get real here :-) 768 769 if sort_mode == 1: 770 """ 771 2) look at the the most recent date a test was performed on 772 move these tests to the top 773 3) sort by runs starting with most recent date 774 a run is a series of consecutive dates a particular test was done on 775 sort by length of the runs 776 longest run will move to the top 777 """ 778 pass 779 780 # clear grid 781 self.ClearGrid() 782 # add columns 783 if self.GetNumberCols() == 0: 784 self.AppendCols(len(dates)) 785 # set column labels 786 for i in range(len(dates)): 787 self.SetColLabelValue(i, dates[i]) 788 # add rows 789 if self.GetNumberRows() == 0: 790 self.AppendRows(len(test_names)) 791 # add labels 792 for i in range(len(test_names)): 793 self.SetRowLabelValue(i, test_names[i]) 794 # push data onto grid 795 cells = [] 796 for result in results: 797 # get x,y position for result 798 x = dates.index(result['val_when'].date) 799 y = test_names.index(result['unified_name']) 800 cell_data = self.GetCellValue(x, y) 801 if cell_data == '': 802 self.SetCellValue(x, y, '%s %s' % (result['unified_val'], result['val_unit'])) 803 else: 804 self.SetCellValue(x, y, '%s\n%s %s' % (cell_data, result['unified_val'], result['val_unit'])) 805 # you can set cell attributes for the whole row (or column) 806 #self.SetRowAttr(int(y), attr) 807 #self.SetColAttr(int(x), attr) 808 #self.SetCellRenderer(int(x), int(y), renderer) 809 810 self.AutoSize() 811 return 1
812 #------------------------------------------------------------------------
813 - def __compile_stats(self, lab_results=None):
814 # parse record for dates and tests 815 dates = [] 816 test_names = [] 817 for result in lab_results: 818 if result['val_when'].date not in dates: 819 dates.append(result['val_when'].date) 820 if result['unified_name'] not in test_names: 821 test_names.append(result['unified_name']) 822 dates.sort() 823 824 return dates, test_names
825 #------------------------------------------------------------------------ 826 #def sort_by_value(self, d=None): 827 # """ Returns the keys of dictionary d sorted by their values """ 828 # items=d.items() 829 # backitems=[ [v[1],v[0]] for v in items] 830 # backitems.sort() 831 # return [ backitems[i][1] for i in range(0,len(backitems))] 832 833 #--------------------------------------------------------
834 - def __on_right_click(self, evt):
835 pass
836 #evt.Skip() 837 838 #========================================================= 839 # MAIN 840 #--------------------------------------------------------- 841 if __name__ == '__main__': 842 _log.Log (gmLog.lInfo, "starting lab journal") 843 844 # catch all remaining exceptions 845 try: 846 application = wxPyWidgetTester(size=(640,480)) 847 application.SetWidget(cStandalonePanel,-1) 848 application.MainLoop() 849 except: 850 _log.LogException("unhandled exception caught !", sys.exc_info(), 1) 851 # but re-raise them 852 raise 853 #gmPG.StopListeners() 854 _log.Log (gmLog.lInfo, "closing lab journal") 855 #========================================================= 856