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

Source Code for Module Gnumed.wxpython.gmDocumentWidgets

   1  """GNUmed medical document handling widgets. 
   2  """ 
   3  #================================================================ 
   4  __version__ = "$Revision: 1.187 $" 
   5  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>" 
   6   
   7  import os.path 
   8  import os 
   9  import sys 
  10  import re as regex 
  11  import logging 
  12   
  13   
  14  import wx 
  15   
  16   
  17  if __name__ == '__main__': 
  18          sys.path.insert(0, '../../') 
  19  from Gnumed.pycommon import gmI18N 
  20  from Gnumed.pycommon import gmCfg 
  21  from Gnumed.pycommon import gmPG2 
  22  from Gnumed.pycommon import gmMimeLib 
  23  from Gnumed.pycommon import gmMatchProvider 
  24  from Gnumed.pycommon import gmDispatcher 
  25  from Gnumed.pycommon import gmDateTime 
  26  from Gnumed.pycommon import gmTools 
  27  from Gnumed.pycommon import gmShellAPI 
  28  from Gnumed.pycommon import gmHooks 
  29   
  30  from Gnumed.business import gmPerson 
  31  from Gnumed.business import gmStaff 
  32  from Gnumed.business import gmDocuments 
  33  from Gnumed.business import gmEMRStructItems 
  34  from Gnumed.business import gmPraxis 
  35   
  36  from Gnumed.wxpython import gmGuiHelpers 
  37  from Gnumed.wxpython import gmRegetMixin 
  38  from Gnumed.wxpython import gmPhraseWheel 
  39  from Gnumed.wxpython import gmPlugin 
  40  from Gnumed.wxpython import gmEMRStructWidgets 
  41  from Gnumed.wxpython import gmListWidgets 
  42   
  43   
  44  _log = logging.getLogger('gm.ui') 
  45  _log.info(__version__) 
  46   
  47   
  48  default_chunksize = 1 * 1024 * 1024             # 1 MB 
  49  #============================================================ 
50 -def manage_document_descriptions(parent=None, document=None):
51 52 #----------------------------------- 53 def delete_item(item): 54 doit = gmGuiHelpers.gm_show_question ( 55 _( 'Are you sure you want to delete this\n' 56 'description from the document ?\n' 57 ), 58 _('Deleting document description') 59 ) 60 if not doit: 61 return True 62 63 document.delete_description(pk = item[0]) 64 return True
65 #----------------------------------- 66 def add_item(): 67 dlg = gmGuiHelpers.cMultilineTextEntryDlg ( 68 parent, 69 -1, 70 title = _('Adding document description'), 71 msg = _('Below you can add a document description.\n') 72 ) 73 result = dlg.ShowModal() 74 if result == wx.ID_SAVE: 75 document.add_description(dlg.value) 76 77 dlg.Destroy() 78 return True 79 #----------------------------------- 80 def edit_item(item): 81 dlg = gmGuiHelpers.cMultilineTextEntryDlg ( 82 parent, 83 -1, 84 title = _('Editing document description'), 85 msg = _('Below you can edit the document description.\n'), 86 text = item[1] 87 ) 88 result = dlg.ShowModal() 89 if result == wx.ID_SAVE: 90 document.update_description(pk = item[0], description = dlg.value) 91 92 dlg.Destroy() 93 return True 94 #----------------------------------- 95 def refresh_list(lctrl): 96 descriptions = document.get_descriptions() 97 98 lctrl.set_string_items(items = [ 99 u'%s%s' % ( (u' '.join(regex.split('\r\n+|\r+|\n+|\t+', desc[1])))[:30], gmTools.u_ellipsis ) 100 for desc in descriptions 101 ]) 102 lctrl.set_data(data = descriptions) 103 #----------------------------------- 104 105 gmListWidgets.get_choices_from_list ( 106 parent = parent, 107 msg = _('Select the description you are interested in.\n'), 108 caption = _('Managing document descriptions'), 109 columns = [_('Description')], 110 edit_callback = edit_item, 111 new_callback = add_item, 112 delete_callback = delete_item, 113 refresh_callback = refresh_list, 114 single_selection = True, 115 can_return_empty = True 116 ) 117 118 return True 119 #============================================================
120 -def _save_file_as_new_document(**kwargs):
121 try: 122 del kwargs['signal'] 123 del kwargs['sender'] 124 except KeyError: 125 pass 126 wx.CallAfter(save_file_as_new_document, **kwargs)
127
128 -def _save_files_as_new_document(**kwargs):
129 try: 130 del kwargs['signal'] 131 del kwargs['sender'] 132 except KeyError: 133 pass 134 wx.CallAfter(save_files_as_new_document, **kwargs)
135 #----------------------
136 -def save_file_as_new_document(parent=None, filename=None, document_type=None, unlock_patient=False, episode=None, review_as_normal=False):
137 return save_files_as_new_document ( 138 parent = parent, 139 filenames = [filename], 140 document_type = document_type, 141 unlock_patient = unlock_patient, 142 episode = episode, 143 review_as_normal = review_as_normal 144 )
145 #----------------------
146 -def save_files_as_new_document(parent=None, filenames=None, document_type=None, unlock_patient=False, episode=None, review_as_normal=False, reference=None):
147 148 pat = gmPerson.gmCurrentPatient() 149 if not pat.connected: 150 return None 151 152 emr = pat.get_emr() 153 154 if parent is None: 155 parent = wx.GetApp().GetTopWindow() 156 157 if episode is None: 158 all_epis = emr.get_episodes() 159 # FIXME: what to do here ? probably create dummy episode 160 if len(all_epis) == 0: 161 episode = emr.add_episode(episode_name = _('Documents'), is_open = False) 162 else: 163 dlg = gmEMRStructWidgets.cEpisodeListSelectorDlg(parent = parent, id = -1, episodes = all_epis) 164 dlg.SetTitle(_('Select the episode under which to file the document ...')) 165 btn_pressed = dlg.ShowModal() 166 episode = dlg.get_selected_item_data(only_one = True) 167 dlg.Destroy() 168 169 if (btn_pressed == wx.ID_CANCEL) or (episode is None): 170 if unlock_patient: 171 pat.locked = False 172 return None 173 174 doc_type = gmDocuments.create_document_type(document_type = document_type) 175 176 docs_folder = pat.get_document_folder() 177 doc = docs_folder.add_document ( 178 document_type = doc_type['pk_doc_type'], 179 encounter = emr.active_encounter['pk_encounter'], 180 episode = episode['pk_episode'] 181 ) 182 if reference is not None: 183 doc['ext_ref'] = reference 184 doc.save() 185 doc.add_parts_from_files(files = filenames) 186 187 if review_as_normal: 188 doc.set_reviewed(technically_abnormal = False, clinically_relevant = False) 189 190 if unlock_patient: 191 pat.locked = False 192 193 gmDispatcher.send(signal = 'statustext', msg = _('Imported new document from %s.') % filenames, beep = True) 194 195 return doc
196 #---------------------- 197 gmDispatcher.connect(signal = u'import_document_from_file', receiver = _save_file_as_new_document) 198 gmDispatcher.connect(signal = u'import_document_from_files', receiver = _save_files_as_new_document) 199 #============================================================
200 -class cDocumentCommentPhraseWheel(gmPhraseWheel.cPhraseWheel):
201 """Let user select a document comment from all existing comments."""
202 - def __init__(self, *args, **kwargs):
203 204 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 205 206 context = { 207 u'ctxt_doc_type': { 208 u'where_part': u'and fk_type = %(pk_doc_type)s', 209 u'placeholder': u'pk_doc_type' 210 } 211 } 212 213 mp = gmMatchProvider.cMatchProvider_SQL2 ( 214 queries = [u""" 215 SELECT 216 data, 217 field_label, 218 list_label 219 FROM ( 220 SELECT DISTINCT ON (field_label) * 221 FROM ( 222 -- constrained by doc type 223 SELECT 224 comment AS data, 225 comment AS field_label, 226 comment AS list_label, 227 1 AS rank 228 FROM blobs.doc_med 229 WHERE 230 comment %(fragment_condition)s 231 %(ctxt_doc_type)s 232 233 UNION ALL 234 235 SELECT 236 comment AS data, 237 comment AS field_label, 238 comment AS list_label, 239 2 AS rank 240 FROM blobs.doc_med 241 WHERE 242 comment %(fragment_condition)s 243 ) AS q_union 244 ) AS q_distinct 245 ORDER BY rank, list_label 246 LIMIT 25"""], 247 context = context 248 ) 249 mp.setThresholds(3, 5, 7) 250 mp.unset_context(u'pk_doc_type') 251 252 self.matcher = mp 253 self.picklist_delay = 50 254 255 self.SetToolTipString(_('Enter a comment on the document.'))
256 #============================================================ 257 # document type widgets 258 #============================================================
259 -def manage_document_types(parent=None):
260 261 if parent is None: 262 parent = wx.GetApp().GetTopWindow() 263 264 dlg = cEditDocumentTypesDlg(parent = parent) 265 dlg.ShowModal()
266 #============================================================ 267 from Gnumed.wxGladeWidgets import wxgEditDocumentTypesDlg 268
269 -class cEditDocumentTypesDlg(wxgEditDocumentTypesDlg.wxgEditDocumentTypesDlg):
270 """A dialog showing a cEditDocumentTypesPnl.""" 271
272 - def __init__(self, *args, **kwargs):
274 275 #============================================================ 276 from Gnumed.wxGladeWidgets import wxgEditDocumentTypesPnl 277
278 -class cEditDocumentTypesPnl(wxgEditDocumentTypesPnl.wxgEditDocumentTypesPnl):
279 """A panel grouping together fields to edit the list of document types.""" 280
281 - def __init__(self, *args, **kwargs):
282 wxgEditDocumentTypesPnl.wxgEditDocumentTypesPnl.__init__(self, *args, **kwargs) 283 self.__init_ui() 284 self.__register_interests() 285 self.repopulate_ui()
286 #--------------------------------------------------------
287 - def __init_ui(self):
288 self._LCTRL_doc_type.set_columns([_('Type'), _('Translation'), _('User defined'), _('In use')]) 289 self._LCTRL_doc_type.set_column_widths()
290 #--------------------------------------------------------
291 - def __register_interests(self):
292 gmDispatcher.connect(signal = u'blobs.doc_type_mod_db', receiver = self._on_doc_type_mod_db)
293 #--------------------------------------------------------
294 - def _on_doc_type_mod_db(self):
295 wx.CallAfter(self.repopulate_ui)
296 #--------------------------------------------------------
297 - def repopulate_ui(self):
298 299 self._LCTRL_doc_type.DeleteAllItems() 300 301 doc_types = gmDocuments.get_document_types() 302 pos = len(doc_types) + 1 303 304 for doc_type in doc_types: 305 row_num = self._LCTRL_doc_type.InsertStringItem(pos, label = doc_type['type']) 306 self._LCTRL_doc_type.SetStringItem(index = row_num, col = 1, label = doc_type['l10n_type']) 307 if doc_type['is_user_defined']: 308 self._LCTRL_doc_type.SetStringItem(index = row_num, col = 2, label = ' X ') 309 if doc_type['is_in_use']: 310 self._LCTRL_doc_type.SetStringItem(index = row_num, col = 3, label = ' X ') 311 312 if len(doc_types) > 0: 313 self._LCTRL_doc_type.set_data(data = doc_types) 314 self._LCTRL_doc_type.SetColumnWidth(col=0, width=wx.LIST_AUTOSIZE) 315 self._LCTRL_doc_type.SetColumnWidth(col=1, width=wx.LIST_AUTOSIZE) 316 self._LCTRL_doc_type.SetColumnWidth(col=2, width=wx.LIST_AUTOSIZE_USEHEADER) 317 self._LCTRL_doc_type.SetColumnWidth(col=3, width=wx.LIST_AUTOSIZE_USEHEADER) 318 319 self._TCTRL_type.SetValue('') 320 self._TCTRL_l10n_type.SetValue('') 321 322 self._BTN_set_translation.Enable(False) 323 self._BTN_delete.Enable(False) 324 self._BTN_add.Enable(False) 325 self._BTN_reassign.Enable(False) 326 327 self._LCTRL_doc_type.SetFocus()
328 #-------------------------------------------------------- 329 # event handlers 330 #--------------------------------------------------------
331 - def _on_list_item_selected(self, evt):
332 doc_type = self._LCTRL_doc_type.get_selected_item_data() 333 334 self._TCTRL_type.SetValue(doc_type['type']) 335 self._TCTRL_l10n_type.SetValue(doc_type['l10n_type']) 336 337 self._BTN_set_translation.Enable(True) 338 self._BTN_delete.Enable(not bool(doc_type['is_in_use'])) 339 self._BTN_add.Enable(False) 340 self._BTN_reassign.Enable(True) 341 342 return
343 #--------------------------------------------------------
344 - def _on_type_modified(self, event):
345 self._BTN_set_translation.Enable(False) 346 self._BTN_delete.Enable(False) 347 self._BTN_reassign.Enable(False) 348 349 self._BTN_add.Enable(True) 350 # self._LCTRL_doc_type.deselect_selected_item() 351 return
352 #--------------------------------------------------------
353 - def _on_set_translation_button_pressed(self, event):
354 doc_type = self._LCTRL_doc_type.get_selected_item_data() 355 if doc_type.set_translation(translation = self._TCTRL_l10n_type.GetValue().strip()): 356 wx.CallAfter(self.repopulate_ui) 357 358 return
359 #--------------------------------------------------------
360 - def _on_delete_button_pressed(self, event):
361 doc_type = self._LCTRL_doc_type.get_selected_item_data() 362 if doc_type['is_in_use']: 363 gmGuiHelpers.gm_show_info ( 364 _( 365 'Cannot delete document type\n' 366 ' [%s]\n' 367 'because it is currently in use.' 368 ) % doc_type['l10n_type'], 369 _('deleting document type') 370 ) 371 return 372 373 gmDocuments.delete_document_type(document_type = doc_type) 374 375 return
376 #--------------------------------------------------------
377 - def _on_add_button_pressed(self, event):
378 desc = self._TCTRL_type.GetValue().strip() 379 if desc != '': 380 doc_type = gmDocuments.create_document_type(document_type = desc) # does not create dupes 381 l10n_desc = self._TCTRL_l10n_type.GetValue().strip() 382 if (l10n_desc != '') and (l10n_desc != doc_type['l10n_type']): 383 doc_type.set_translation(translation = l10n_desc) 384 385 return
386 #--------------------------------------------------------
387 - def _on_reassign_button_pressed(self, event):
388 389 orig_type = self._LCTRL_doc_type.get_selected_item_data() 390 doc_types = gmDocuments.get_document_types() 391 392 new_type = gmListWidgets.get_choices_from_list ( 393 parent = self, 394 msg = _( 395 'From the list below select the document type you want\n' 396 'all documents currently classified as:\n\n' 397 ' "%s"\n\n' 398 'to be changed to.\n\n' 399 'Be aware that this change will be applied to ALL such documents. If there\n' 400 'are many documents to change it can take quite a while.\n\n' 401 'Make sure this is what you want to happen !\n' 402 ) % orig_type['l10n_type'], 403 caption = _('Reassigning document type'), 404 choices = [ [gmTools.bool2subst(dt['is_user_defined'], u'X', u''), dt['type'], dt['l10n_type']] for dt in doc_types ], 405 columns = [_('User defined'), _('Type'), _('Translation')], 406 data = doc_types, 407 single_selection = True 408 ) 409 410 if new_type is None: 411 return 412 413 wx.BeginBusyCursor() 414 gmDocuments.reclassify_documents_by_type(original_type = orig_type, target_type = new_type) 415 wx.EndBusyCursor() 416 417 return
418 #============================================================
419 -class cDocumentTypeSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
420 """Let user select a document type."""
421 - def __init__(self, *args, **kwargs):
422 423 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 424 425 mp = gmMatchProvider.cMatchProvider_SQL2 ( 426 queries = [ 427 u"""SELECT 428 data, 429 field_label, 430 list_label 431 FROM (( 432 SELECT 433 pk_doc_type AS data, 434 l10n_type AS field_label, 435 l10n_type AS list_label, 436 1 AS rank 437 FROM blobs.v_doc_type 438 WHERE 439 is_user_defined IS True 440 AND 441 l10n_type %(fragment_condition)s 442 ) UNION ( 443 SELECT 444 pk_doc_type AS data, 445 l10n_type AS field_label, 446 l10n_type AS list_label, 447 2 AS rank 448 FROM blobs.v_doc_type 449 WHERE 450 is_user_defined IS False 451 AND 452 l10n_type %(fragment_condition)s 453 )) AS q1 454 ORDER BY q1.rank, q1.list_label"""] 455 ) 456 mp.setThresholds(2, 4, 6) 457 458 self.matcher = mp 459 self.picklist_delay = 50 460 461 self.SetToolTipString(_('Select the document type.'))
462 #--------------------------------------------------------
463 - def _create_data(self):
464 465 doc_type = self.GetValue().strip() 466 if doc_type == u'': 467 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create document type without name.'), beep = True) 468 _log.debug('cannot create document type without name') 469 return 470 471 pk = gmDocuments.create_document_type(doc_type)['pk_doc_type'] 472 if pk is None: 473 self.data = {} 474 else: 475 self.SetText ( 476 value = doc_type, 477 data = pk 478 )
479 #============================================================ 480 # document review widgets 481 #============================================================
482 -def review_document_part(parent=None, part=None):
483 if parent is None: 484 parent = wx.GetApp().GetTopWindow() 485 dlg = cReviewDocPartDlg ( 486 parent = parent, 487 id = -1, 488 part = part 489 ) 490 dlg.ShowModal() 491 dlg.Destroy()
492 #------------------------------------------------------------
493 -def review_document(parent=None, document=None):
494 return review_document_part(parent = parent, part = document)
495 #------------------------------------------------------------ 496 from Gnumed.wxGladeWidgets import wxgReviewDocPartDlg 497
498 -class cReviewDocPartDlg(wxgReviewDocPartDlg.wxgReviewDocPartDlg):
499 - def __init__(self, *args, **kwds):
500 """Support parts and docs now. 501 """ 502 part = kwds['part'] 503 del kwds['part'] 504 wxgReviewDocPartDlg.wxgReviewDocPartDlg.__init__(self, *args, **kwds) 505 506 if isinstance(part, gmDocuments.cDocumentPart): 507 self.__part = part 508 self.__doc = self.__part.get_containing_document() 509 self.__reviewing_doc = False 510 elif isinstance(part, gmDocuments.cDocument): 511 self.__doc = part 512 if len(self.__doc.parts) == 0: 513 self.__part = None 514 else: 515 self.__part = self.__doc.parts[0] 516 self.__reviewing_doc = True 517 else: 518 raise ValueError('<part> must be gmDocuments.cDocument or gmDocuments.cDocumentPart instance, got <%s>' % type(part)) 519 520 self.__init_ui_data()
521 #-------------------------------------------------------- 522 # internal API 523 #--------------------------------------------------------
524 - def __init_ui_data(self):
525 # FIXME: fix this 526 # associated episode (add " " to avoid popping up pick list) 527 self._PhWheel_episode.SetText('%s ' % self.__doc['episode'], self.__doc['pk_episode']) 528 self._PhWheel_doc_type.SetText(value = self.__doc['l10n_type'], data = self.__doc['pk_type']) 529 self._PhWheel_doc_type.add_callback_on_set_focus(self._on_doc_type_gets_focus) 530 self._PhWheel_doc_type.add_callback_on_lose_focus(self._on_doc_type_loses_focus) 531 532 if self.__reviewing_doc: 533 self._PRW_doc_comment.SetText(gmTools.coalesce(self.__doc['comment'], '')) 534 self._PRW_doc_comment.set_context(context = 'pk_doc_type', val = self.__doc['pk_type']) 535 else: 536 self._PRW_doc_comment.SetText(gmTools.coalesce(self.__part['obj_comment'], '')) 537 538 fts = gmDateTime.cFuzzyTimestamp(timestamp = self.__doc['clin_when']) 539 self._PhWheel_doc_date.SetText(fts.strftime('%Y-%m-%d'), fts) 540 self._TCTRL_reference.SetValue(gmTools.coalesce(self.__doc['ext_ref'], '')) 541 if self.__reviewing_doc: 542 self._TCTRL_filename.Enable(False) 543 self._SPINCTRL_seq_idx.Enable(False) 544 else: 545 self._TCTRL_filename.SetValue(gmTools.coalesce(self.__part['filename'], '')) 546 self._SPINCTRL_seq_idx.SetValue(gmTools.coalesce(self.__part['seq_idx'], 0)) 547 548 self._LCTRL_existing_reviews.InsertColumn(0, _('who')) 549 self._LCTRL_existing_reviews.InsertColumn(1, _('when')) 550 self._LCTRL_existing_reviews.InsertColumn(2, _('+/-')) 551 self._LCTRL_existing_reviews.InsertColumn(3, _('!')) 552 self._LCTRL_existing_reviews.InsertColumn(4, _('comment')) 553 554 self.__reload_existing_reviews() 555 556 if self._LCTRL_existing_reviews.GetItemCount() > 0: 557 self._LCTRL_existing_reviews.SetColumnWidth(col=0, width=wx.LIST_AUTOSIZE) 558 self._LCTRL_existing_reviews.SetColumnWidth(col=1, width=wx.LIST_AUTOSIZE) 559 self._LCTRL_existing_reviews.SetColumnWidth(col=2, width=wx.LIST_AUTOSIZE_USEHEADER) 560 self._LCTRL_existing_reviews.SetColumnWidth(col=3, width=wx.LIST_AUTOSIZE_USEHEADER) 561 self._LCTRL_existing_reviews.SetColumnWidth(col=4, width=wx.LIST_AUTOSIZE) 562 563 if self.__part is None: 564 self._ChBOX_review.SetValue(False) 565 self._ChBOX_review.Enable(False) 566 self._ChBOX_abnormal.Enable(False) 567 self._ChBOX_relevant.Enable(False) 568 self._ChBOX_sign_all_pages.Enable(False) 569 else: 570 me = gmStaff.gmCurrentProvider() 571 if self.__part['pk_intended_reviewer'] == me['pk_staff']: 572 msg = _('(you are the primary reviewer)') 573 else: 574 other = gmStaff.cStaff(aPK_obj = self.__part['pk_intended_reviewer']) 575 msg = _('(someone else is the intended reviewer: %s)') % other['short_alias'] 576 self._TCTRL_responsible.SetValue(msg) 577 # init my review if any 578 if self.__part['reviewed_by_you']: 579 revs = self.__part.get_reviews() 580 for rev in revs: 581 if rev['is_your_review']: 582 self._ChBOX_abnormal.SetValue(bool(rev[2])) 583 self._ChBOX_relevant.SetValue(bool(rev[3])) 584 break 585 586 self._ChBOX_sign_all_pages.SetValue(self.__reviewing_doc) 587 588 return True
589 #--------------------------------------------------------
590 - def __reload_existing_reviews(self):
591 self._LCTRL_existing_reviews.DeleteAllItems() 592 if self.__part is None: 593 return True 594 revs = self.__part.get_reviews() # FIXME: this is ugly as sin, it should be dicts, not lists 595 if len(revs) == 0: 596 return True 597 # find special reviews 598 review_by_responsible_doc = None 599 reviews_by_others = [] 600 for rev in revs: 601 if rev['is_review_by_responsible_reviewer'] and not rev['is_your_review']: 602 review_by_responsible_doc = rev 603 if not (rev['is_review_by_responsible_reviewer'] or rev['is_your_review']): 604 reviews_by_others.append(rev) 605 # display them 606 if review_by_responsible_doc is not None: 607 row_num = self._LCTRL_existing_reviews.InsertStringItem(sys.maxint, label=review_by_responsible_doc[0]) 608 self._LCTRL_existing_reviews.SetItemTextColour(row_num, col=wx.BLUE) 609 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=0, label=review_by_responsible_doc[0]) 610 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=1, label=review_by_responsible_doc[1].strftime('%x %H:%M')) 611 if review_by_responsible_doc['is_technically_abnormal']: 612 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=2, label=u'X') 613 if review_by_responsible_doc['clinically_relevant']: 614 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=3, label=u'X') 615 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=4, label=review_by_responsible_doc[6]) 616 row_num += 1 617 for rev in reviews_by_others: 618 row_num = self._LCTRL_existing_reviews.InsertStringItem(sys.maxint, label=rev[0]) 619 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=0, label=rev[0]) 620 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=1, label=rev[1].strftime('%x %H:%M')) 621 if rev['is_technically_abnormal']: 622 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=2, label=u'X') 623 if rev['clinically_relevant']: 624 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=3, label=u'X') 625 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=4, label=rev[6]) 626 return True
627 #-------------------------------------------------------- 628 # event handlers 629 #--------------------------------------------------------
630 - def _on_save_button_pressed(self, evt):
631 """Save the metadata to the backend.""" 632 633 evt.Skip() 634 635 # 1) handle associated episode 636 pk_episode = self._PhWheel_episode.GetData(can_create=True, is_open=True) 637 if pk_episode is None: 638 gmGuiHelpers.gm_show_error ( 639 _('Cannot create episode\n [%s]'), 640 _('Editing document properties') 641 ) 642 return False 643 644 doc_type = self._PhWheel_doc_type.GetData(can_create = True) 645 if doc_type is None: 646 gmDispatcher.send(signal='statustext', msg=_('Cannot change document type to [%s].') % self._PhWheel_doc_type.GetValue().strip()) 647 return False 648 649 # since the phrasewheel operates on the active 650 # patient all episodes really should belong 651 # to it so we don't check patient change 652 self.__doc['pk_episode'] = pk_episode 653 self.__doc['pk_type'] = doc_type 654 if self.__reviewing_doc: 655 self.__doc['comment'] = self._PRW_doc_comment.GetValue().strip() 656 # FIXME: a rather crude way of error checking: 657 if self._PhWheel_doc_date.GetData() is not None: 658 self.__doc['clin_when'] = self._PhWheel_doc_date.GetData().get_pydt() 659 self.__doc['ext_ref'] = self._TCTRL_reference.GetValue().strip() 660 661 success, data = self.__doc.save_payload() 662 if not success: 663 gmGuiHelpers.gm_show_error ( 664 _('Cannot link the document to episode\n\n [%s]') % epi_name, 665 _('Editing document properties') 666 ) 667 return False 668 669 # 2) handle review 670 if self._ChBOX_review.GetValue(): 671 provider = gmStaff.gmCurrentProvider() 672 abnormal = self._ChBOX_abnormal.GetValue() 673 relevant = self._ChBOX_relevant.GetValue() 674 msg = None 675 if self.__reviewing_doc: # - on all pages 676 if not self.__doc.set_reviewed(technically_abnormal = abnormal, clinically_relevant = relevant): 677 msg = _('Error setting "reviewed" status of this document.') 678 if self._ChBOX_responsible.GetValue(): 679 if not self.__doc.set_primary_reviewer(reviewer = provider['pk_staff']): 680 msg = _('Error setting responsible clinician for this document.') 681 else: # - just on this page 682 if not self.__part.set_reviewed(technically_abnormal = abnormal, clinically_relevant = relevant): 683 msg = _('Error setting "reviewed" status of this part.') 684 if self._ChBOX_responsible.GetValue(): 685 self.__part['pk_intended_reviewer'] = provider['pk_staff'] 686 if msg is not None: 687 gmGuiHelpers.gm_show_error(msg, _('Editing document properties')) 688 return False 689 690 # 3) handle "page" specific parts 691 if not self.__reviewing_doc: 692 self.__part['filename'] = gmTools.none_if(self._TCTRL_filename.GetValue().strip(), u'') 693 new_idx = gmTools.none_if(self._SPINCTRL_seq_idx.GetValue(), 0) 694 if self.__part['seq_idx'] != new_idx: 695 if new_idx in self.__doc['seq_idx_list']: 696 msg = _( 697 'Cannot set page number to [%s] because\n' 698 'another page with this number exists.\n' 699 '\n' 700 'Page numbers in use:\n' 701 '\n' 702 ' %s' 703 ) % ( 704 new_idx, 705 self.__doc['seq_idx_list'] 706 ) 707 gmGuiHelpers.gm_show_error(msg, _('Editing document part properties')) 708 else: 709 self.__part['seq_idx'] = new_idx 710 self.__part['obj_comment'] = self._PRW_doc_comment.GetValue().strip() 711 success, data = self.__part.save_payload() 712 if not success: 713 gmGuiHelpers.gm_show_error ( 714 _('Error saving part properties.'), 715 _('Editing document part properties') 716 ) 717 return False 718 719 return True
720 #--------------------------------------------------------
721 - def _on_reviewed_box_checked(self, evt):
722 state = self._ChBOX_review.GetValue() 723 self._ChBOX_abnormal.Enable(enable = state) 724 self._ChBOX_relevant.Enable(enable = state) 725 self._ChBOX_responsible.Enable(enable = state)
726 #--------------------------------------------------------
727 - def _on_doc_type_gets_focus(self):
728 """Per Jim: Changing the doc type happens a lot more often 729 then correcting spelling, hence select-all on getting focus. 730 """ 731 self._PhWheel_doc_type.SetSelection(-1, -1)
732 #--------------------------------------------------------
733 - def _on_doc_type_loses_focus(self):
734 pk_doc_type = self._PhWheel_doc_type.GetData() 735 if pk_doc_type is None: 736 self._PRW_doc_comment.unset_context(context = 'pk_doc_type') 737 else: 738 self._PRW_doc_comment.set_context(context = 'pk_doc_type', val = pk_doc_type) 739 return True
740 #============================================================
741 -def acquire_images_from_capture_device(device=None, calling_window=None):
742 743 _log.debug('acquiring images from [%s]', device) 744 745 # do not import globally since we might want to use 746 # this module without requiring any scanner to be available 747 from Gnumed.pycommon import gmScanBackend 748 try: 749 fnames = gmScanBackend.acquire_pages_into_files ( 750 device = device, 751 delay = 5, 752 calling_window = calling_window 753 ) 754 except OSError: 755 _log.exception('problem acquiring image from source') 756 gmGuiHelpers.gm_show_error ( 757 aMessage = _( 758 'No images could be acquired from the source.\n\n' 759 'This may mean the scanner driver is not properly installed.\n\n' 760 'On Windows you must install the TWAIN Python module\n' 761 'while on Linux and MacOSX it is recommended to install\n' 762 'the XSane package.' 763 ), 764 aTitle = _('Acquiring images') 765 ) 766 return None 767 768 _log.debug('acquired %s images', len(fnames)) 769 770 return fnames
771 #------------------------------------------------------------ 772 from Gnumed.wxGladeWidgets import wxgScanIdxPnl 773
774 -class cScanIdxDocsPnl(wxgScanIdxPnl.wxgScanIdxPnl, gmPlugin.cPatientChange_PluginMixin):
775 - def __init__(self, *args, **kwds):
776 wxgScanIdxPnl.wxgScanIdxPnl.__init__(self, *args, **kwds) 777 gmPlugin.cPatientChange_PluginMixin.__init__(self) 778 779 self._PhWheel_reviewer.matcher = gmPerson.cMatchProvider_Provider() 780 781 self.__init_ui_data() 782 self._PhWheel_doc_type.add_callback_on_lose_focus(self._on_doc_type_loses_focus) 783 784 # make me and listctrl a file drop target 785 dt = gmGuiHelpers.cFileDropTarget(self) 786 self.SetDropTarget(dt) 787 dt = gmGuiHelpers.cFileDropTarget(self._LBOX_doc_pages) 788 self._LBOX_doc_pages.SetDropTarget(dt) 789 self._LBOX_doc_pages.add_filenames = self.add_filenames_to_listbox 790 791 # do not import globally since we might want to use 792 # this module without requiring any scanner to be available 793 from Gnumed.pycommon import gmScanBackend 794 self.scan_module = gmScanBackend
795 #-------------------------------------------------------- 796 # file drop target API 797 #--------------------------------------------------------
798 - def add_filenames_to_listbox(self, filenames):
800 #--------------------------------------------------------
801 - def add_filenames(self, filenames):
802 pat = gmPerson.gmCurrentPatient() 803 if not pat.connected: 804 gmDispatcher.send(signal='statustext', msg=_('Cannot accept new documents. No active patient.')) 805 return 806 807 # dive into folders dropped onto us and extract files (one level deep only) 808 real_filenames = [] 809 for pathname in filenames: 810 try: 811 files = os.listdir(pathname) 812 gmDispatcher.send(signal='statustext', msg=_('Extracting files from folder [%s] ...') % pathname) 813 for file in files: 814 fullname = os.path.join(pathname, file) 815 if not os.path.isfile(fullname): 816 continue 817 real_filenames.append(fullname) 818 except OSError: 819 real_filenames.append(pathname) 820 821 self.acquired_pages.extend(real_filenames) 822 self.__reload_LBOX_doc_pages()
823 #--------------------------------------------------------
824 - def repopulate_ui(self):
825 pass
826 #-------------------------------------------------------- 827 # patient change plugin API 828 #--------------------------------------------------------
829 - def _pre_patient_selection(self, **kwds):
830 # FIXME: persist pending data from here 831 pass
832 #--------------------------------------------------------
833 - def _post_patient_selection(self, **kwds):
834 self.__init_ui_data()
835 #-------------------------------------------------------- 836 # internal API 837 #--------------------------------------------------------
838 - def __init_ui_data(self):
839 # ----------------------------- 840 self._PhWheel_episode.SetText(value = _('other documents'), suppress_smarts = True) 841 self._PhWheel_doc_type.SetText('') 842 # ----------------------------- 843 # FIXME: make this configurable: either now() or last_date() 844 fts = gmDateTime.cFuzzyTimestamp() 845 self._PhWheel_doc_date.SetText(fts.strftime('%Y-%m-%d'), fts) 846 self._PRW_doc_comment.SetText('') 847 # FIXME: should be set to patient's primary doc 848 self._PhWheel_reviewer.selection_only = True 849 me = gmStaff.gmCurrentProvider() 850 self._PhWheel_reviewer.SetText ( 851 value = u'%s (%s%s %s)' % (me['short_alias'], me['title'], me['firstnames'], me['lastnames']), 852 data = me['pk_staff'] 853 ) 854 # ----------------------------- 855 # FIXME: set from config item 856 self._ChBOX_reviewed.SetValue(False) 857 self._ChBOX_abnormal.Disable() 858 self._ChBOX_abnormal.SetValue(False) 859 self._ChBOX_relevant.Disable() 860 self._ChBOX_relevant.SetValue(False) 861 # ----------------------------- 862 self._TBOX_description.SetValue('') 863 # ----------------------------- 864 # the list holding our page files 865 self._LBOX_doc_pages.Clear() 866 self.acquired_pages = [] 867 868 self._PhWheel_doc_type.SetFocus()
869 #--------------------------------------------------------
870 - def __reload_LBOX_doc_pages(self):
871 self._LBOX_doc_pages.Clear() 872 if len(self.acquired_pages) > 0: 873 for i in range(len(self.acquired_pages)): 874 fname = self.acquired_pages[i] 875 self._LBOX_doc_pages.Append(_('part %s: %s') % (i+1, fname), fname)
876 #--------------------------------------------------------
877 - def __valid_for_save(self):
878 title = _('saving document') 879 880 if self.acquired_pages is None or len(self.acquired_pages) == 0: 881 dbcfg = gmCfg.cCfgSQL() 882 allow_empty = bool(dbcfg.get2 ( 883 option = u'horstspace.scan_index.allow_partless_documents', 884 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 885 bias = 'user', 886 default = False 887 )) 888 if allow_empty: 889 save_empty = gmGuiHelpers.gm_show_question ( 890 aMessage = _('No parts to save. Really save an empty document as a reference ?'), 891 aTitle = title 892 ) 893 if not save_empty: 894 return False 895 else: 896 gmGuiHelpers.gm_show_error ( 897 aMessage = _('No parts to save. Aquire some parts first.'), 898 aTitle = title 899 ) 900 return False 901 902 doc_type_pk = self._PhWheel_doc_type.GetData(can_create = True) 903 if doc_type_pk is None: 904 gmGuiHelpers.gm_show_error ( 905 aMessage = _('No document type applied. Choose a document type'), 906 aTitle = title 907 ) 908 return False 909 910 # this should be optional, actually 911 # if self._PRW_doc_comment.GetValue().strip() == '': 912 # gmGuiHelpers.gm_show_error ( 913 # aMessage = _('No document comment supplied. Add a comment for this document.'), 914 # aTitle = title 915 # ) 916 # return False 917 918 if self._PhWheel_episode.GetValue().strip() == '': 919 gmGuiHelpers.gm_show_error ( 920 aMessage = _('You must select an episode to save this document under.'), 921 aTitle = title 922 ) 923 return False 924 925 if self._PhWheel_reviewer.GetData() is None: 926 gmGuiHelpers.gm_show_error ( 927 aMessage = _('You need to select from the list of staff members the doctor who is intended to sign the document.'), 928 aTitle = title 929 ) 930 return False 931 932 return True
933 #--------------------------------------------------------
934 - def get_device_to_use(self, reconfigure=False):
935 936 if not reconfigure: 937 dbcfg = gmCfg.cCfgSQL() 938 device = dbcfg.get2 ( 939 option = 'external.xsane.default_device', 940 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 941 bias = 'workplace', 942 default = '' 943 ) 944 if device.strip() == u'': 945 device = None 946 if device is not None: 947 return device 948 949 try: 950 devices = self.scan_module.get_devices() 951 except: 952 _log.exception('cannot retrieve list of image sources') 953 gmDispatcher.send(signal = 'statustext', msg = _('There is no scanner support installed on this machine.')) 954 return None 955 956 if devices is None: 957 # get_devices() not implemented for TWAIN yet 958 # XSane has its own chooser (so does TWAIN) 959 return None 960 961 if len(devices) == 0: 962 gmDispatcher.send(signal = 'statustext', msg = _('Cannot find an active scanner.')) 963 return None 964 965 # device_names = [] 966 # for device in devices: 967 # device_names.append('%s (%s)' % (device[2], device[0])) 968 969 device = gmListWidgets.get_choices_from_list ( 970 parent = self, 971 msg = _('Select an image capture device'), 972 caption = _('device selection'), 973 choices = [ '%s (%s)' % (d[2], d[0]) for d in devices ], 974 columns = [_('Device')], 975 data = devices, 976 single_selection = True 977 ) 978 if device is None: 979 return None 980 981 # FIXME: add support for actually reconfiguring 982 return device[0]
983 #-------------------------------------------------------- 984 # event handling API 985 #--------------------------------------------------------
986 - def _scan_btn_pressed(self, evt):
987 988 chosen_device = self.get_device_to_use() 989 990 # FIXME: configure whether to use XSane or sane directly 991 # FIXME: add support for xsane_device_settings argument 992 try: 993 fnames = self.scan_module.acquire_pages_into_files ( 994 device = chosen_device, 995 delay = 5, 996 calling_window = self 997 ) 998 except OSError: 999 _log.exception('problem acquiring image from source') 1000 gmGuiHelpers.gm_show_error ( 1001 aMessage = _( 1002 'No pages could be acquired from the source.\n\n' 1003 'This may mean the scanner driver is not properly installed.\n\n' 1004 'On Windows you must install the TWAIN Python module\n' 1005 'while on Linux and MacOSX it is recommended to install\n' 1006 'the XSane package.' 1007 ), 1008 aTitle = _('acquiring page') 1009 ) 1010 return None 1011 1012 if len(fnames) == 0: # no pages scanned 1013 return True 1014 1015 self.acquired_pages.extend(fnames) 1016 self.__reload_LBOX_doc_pages() 1017 1018 return True
1019 #--------------------------------------------------------
1020 - def _load_btn_pressed(self, evt):
1021 # patient file chooser 1022 dlg = wx.FileDialog ( 1023 parent = None, 1024 message = _('Choose a file'), 1025 defaultDir = os.path.expanduser(os.path.join('~', 'gnumed')), 1026 defaultFile = '', 1027 wildcard = "%s (*)|*|TIFFs (*.tif)|*.tif|JPEGs (*.jpg)|*.jpg|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')), 1028 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST | wx.MULTIPLE 1029 ) 1030 result = dlg.ShowModal() 1031 if result != wx.ID_CANCEL: 1032 files = dlg.GetPaths() 1033 for file in files: 1034 self.acquired_pages.append(file) 1035 self.__reload_LBOX_doc_pages() 1036 dlg.Destroy()
1037 #--------------------------------------------------------
1038 - def _show_btn_pressed(self, evt):
1039 # did user select a page ? 1040 page_idx = self._LBOX_doc_pages.GetSelection() 1041 if page_idx == -1: 1042 gmGuiHelpers.gm_show_info ( 1043 aMessage = _('You must select a part before you can view it.'), 1044 aTitle = _('displaying part') 1045 ) 1046 return None 1047 # now, which file was that again ? 1048 page_fname = self._LBOX_doc_pages.GetClientData(page_idx) 1049 1050 (result, msg) = gmMimeLib.call_viewer_on_file(page_fname) 1051 if not result: 1052 gmGuiHelpers.gm_show_warning ( 1053 aMessage = _('Cannot display document part:\n%s') % msg, 1054 aTitle = _('displaying part') 1055 ) 1056 return None 1057 return 1
1058 #--------------------------------------------------------
1059 - def _del_btn_pressed(self, event):
1060 page_idx = self._LBOX_doc_pages.GetSelection() 1061 if page_idx == -1: 1062 gmGuiHelpers.gm_show_info ( 1063 aMessage = _('You must select a part before you can delete it.'), 1064 aTitle = _('deleting part') 1065 ) 1066 return None 1067 page_fname = self._LBOX_doc_pages.GetClientData(page_idx) 1068 1069 # 1) del item from self.acquired_pages 1070 self.acquired_pages[page_idx:(page_idx+1)] = [] 1071 1072 # 2) reload list box 1073 self.__reload_LBOX_doc_pages() 1074 1075 # 3) optionally kill file in the file system 1076 do_delete = gmGuiHelpers.gm_show_question ( 1077 _('The part has successfully been removed from the document.\n' 1078 '\n' 1079 'Do you also want to permanently delete the file\n' 1080 '\n' 1081 ' [%s]\n' 1082 '\n' 1083 'from which this document part was loaded ?\n' 1084 '\n' 1085 'If it is a temporary file for a page you just scanned\n' 1086 'this makes a lot of sense. In other cases you may not\n' 1087 'want to lose the file.\n' 1088 '\n' 1089 'Pressing [YES] will permanently remove the file\n' 1090 'from your computer.\n' 1091 ) % page_fname, 1092 _('Removing document part') 1093 ) 1094 if do_delete: 1095 try: 1096 os.remove(page_fname) 1097 except: 1098 _log.exception('Error deleting file.') 1099 gmGuiHelpers.gm_show_error ( 1100 aMessage = _('Cannot delete part in file [%s].\n\nYou may not have write access to it.') % page_fname, 1101 aTitle = _('deleting part') 1102 ) 1103 1104 return 1
1105 #--------------------------------------------------------
1106 - def _save_btn_pressed(self, evt):
1107 1108 if not self.__valid_for_save(): 1109 return False 1110 1111 wx.BeginBusyCursor() 1112 1113 pat = gmPerson.gmCurrentPatient() 1114 doc_folder = pat.get_document_folder() 1115 emr = pat.get_emr() 1116 1117 # create new document 1118 pk_episode = self._PhWheel_episode.GetData() 1119 if pk_episode is None: 1120 episode = emr.add_episode ( 1121 episode_name = self._PhWheel_episode.GetValue().strip(), 1122 is_open = True 1123 ) 1124 if episode is None: 1125 wx.EndBusyCursor() 1126 gmGuiHelpers.gm_show_error ( 1127 aMessage = _('Cannot start episode [%s].') % self._PhWheel_episode.GetValue().strip(), 1128 aTitle = _('saving document') 1129 ) 1130 return False 1131 pk_episode = episode['pk_episode'] 1132 1133 encounter = emr.active_encounter['pk_encounter'] 1134 document_type = self._PhWheel_doc_type.GetData() 1135 new_doc = doc_folder.add_document(document_type, encounter, pk_episode) 1136 if new_doc is None: 1137 wx.EndBusyCursor() 1138 gmGuiHelpers.gm_show_error ( 1139 aMessage = _('Cannot create new document.'), 1140 aTitle = _('saving document') 1141 ) 1142 return False 1143 1144 # update business object with metadata 1145 # - date of generation 1146 new_doc['clin_when'] = self._PhWheel_doc_date.GetData().get_pydt() 1147 # - external reference 1148 cfg = gmCfg.cCfgSQL() 1149 generate_uuid = bool ( 1150 cfg.get2 ( 1151 option = 'horstspace.scan_index.generate_doc_uuid', 1152 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 1153 bias = 'user', 1154 default = False 1155 ) 1156 ) 1157 ref = None 1158 if generate_uuid: 1159 ref = gmDocuments.get_ext_ref() 1160 if ref is not None: 1161 new_doc['ext_ref'] = ref 1162 # - comment 1163 comment = self._PRW_doc_comment.GetLineText(0).strip() 1164 if comment != u'': 1165 new_doc['comment'] = comment 1166 # - save it 1167 if not new_doc.save_payload(): 1168 wx.EndBusyCursor() 1169 gmGuiHelpers.gm_show_error ( 1170 aMessage = _('Cannot update document metadata.'), 1171 aTitle = _('saving document') 1172 ) 1173 return False 1174 # - long description 1175 description = self._TBOX_description.GetValue().strip() 1176 if description != '': 1177 if not new_doc.add_description(description): 1178 wx.EndBusyCursor() 1179 gmGuiHelpers.gm_show_error ( 1180 aMessage = _('Cannot add document description.'), 1181 aTitle = _('saving document') 1182 ) 1183 return False 1184 1185 # add document parts from files 1186 success, msg, filename = new_doc.add_parts_from_files ( 1187 files = self.acquired_pages, 1188 reviewer = self._PhWheel_reviewer.GetData() 1189 ) 1190 if not success: 1191 wx.EndBusyCursor() 1192 gmGuiHelpers.gm_show_error ( 1193 aMessage = msg, 1194 aTitle = _('saving document') 1195 ) 1196 return False 1197 1198 # set reviewed status 1199 if self._ChBOX_reviewed.GetValue(): 1200 if not new_doc.set_reviewed ( 1201 technically_abnormal = self._ChBOX_abnormal.GetValue(), 1202 clinically_relevant = self._ChBOX_relevant.GetValue() 1203 ): 1204 msg = _('Error setting "reviewed" status of new document.') 1205 1206 gmHooks.run_hook_script(hook = u'after_new_doc_created') 1207 1208 # inform user 1209 show_id = bool ( 1210 cfg.get2 ( 1211 option = 'horstspace.scan_index.show_doc_id', 1212 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 1213 bias = 'user' 1214 ) 1215 ) 1216 wx.EndBusyCursor() 1217 if show_id: 1218 if ref is None: 1219 msg = _('Successfully saved the new document.') 1220 else: 1221 msg = _( 1222 """The reference ID for the new document is: 1223 1224 <%s> 1225 1226 You probably want to write it down on the 1227 original documents. 1228 1229 If you don't care about the ID you can switch 1230 off this message in the GNUmed configuration.""") % ref 1231 gmGuiHelpers.gm_show_info ( 1232 aMessage = msg, 1233 aTitle = _('Saving document') 1234 ) 1235 else: 1236 gmDispatcher.send(signal='statustext', msg=_('Successfully saved new document.')) 1237 1238 self.__init_ui_data() 1239 return True
1240 #--------------------------------------------------------
1241 - def _startover_btn_pressed(self, evt):
1242 self.__init_ui_data()
1243 #--------------------------------------------------------
1244 - def _reviewed_box_checked(self, evt):
1245 self._ChBOX_abnormal.Enable(enable = self._ChBOX_reviewed.GetValue()) 1246 self._ChBOX_relevant.Enable(enable = self._ChBOX_reviewed.GetValue())
1247 #--------------------------------------------------------
1248 - def _on_doc_type_loses_focus(self):
1249 pk_doc_type = self._PhWheel_doc_type.GetData() 1250 if pk_doc_type is None: 1251 self._PRW_doc_comment.unset_context(context = 'pk_doc_type') 1252 else: 1253 self._PRW_doc_comment.set_context(context = 'pk_doc_type', val = pk_doc_type) 1254 return True
1255 #============================================================
1256 -def display_document_part(parent=None, part=None):
1257 1258 if parent is None: 1259 parent = wx.GetApp().GetTopWindow() 1260 1261 # sanity check 1262 if part['size'] == 0: 1263 _log.debug('cannot display part [%s] - 0 bytes', part['pk_obj']) 1264 gmGuiHelpers.gm_show_error ( 1265 aMessage = _('Document part does not seem to exist in database !'), 1266 aTitle = _('showing document') 1267 ) 1268 return None 1269 1270 wx.BeginBusyCursor() 1271 cfg = gmCfg.cCfgSQL() 1272 1273 # determine database export chunk size 1274 chunksize = int( 1275 cfg.get2 ( 1276 option = "horstspace.blob_export_chunk_size", 1277 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 1278 bias = 'workplace', 1279 default = 2048 1280 )) 1281 1282 # shall we force blocking during view ? 1283 block_during_view = bool( cfg.get2 ( 1284 option = 'horstspace.document_viewer.block_during_view', 1285 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 1286 bias = 'user', 1287 default = None 1288 )) 1289 1290 wx.EndBusyCursor() 1291 1292 # display it 1293 successful, msg = part.display_via_mime ( 1294 chunksize = chunksize, 1295 block = block_during_view 1296 ) 1297 if not successful: 1298 gmGuiHelpers.gm_show_error ( 1299 aMessage = _('Cannot display document part:\n%s') % msg, 1300 aTitle = _('showing document') 1301 ) 1302 return None 1303 1304 # handle review after display 1305 # 0: never 1306 # 1: always 1307 # 2: if no review by myself exists yet 1308 # 3: if no review at all exists yet 1309 # 4: if no review by responsible reviewer 1310 review_after_display = int(cfg.get2 ( 1311 option = 'horstspace.document_viewer.review_after_display', 1312 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 1313 bias = 'user', 1314 default = 3 1315 )) 1316 if review_after_display == 1: # always review 1317 review_document_part(parent = parent, part = part) 1318 elif review_after_display == 2: # review if no review by me exists 1319 review_by_me = filter(lambda rev: rev['is_your_review'], part.get_reviews()) 1320 if len(review_by_me) == 0: 1321 review_document_part(parent = parent, part = part) 1322 elif review_after_display == 3: 1323 if len(part.get_reviews()) == 0: 1324 review_document_part(parent = parent, part = part) 1325 elif review_after_display == 4: 1326 reviewed_by_responsible = filter(lambda rev: rev['is_review_by_responsible_reviewer'], part.get_reviews()) 1327 if len(reviewed_by_responsible) == 0: 1328 review_document_part(parent = parent, part = part) 1329 1330 return True
1331 #============================================================
1332 -def manage_documents(parent=None, msg=None, single_selection=True):
1333 1334 pat = gmPerson.gmCurrentPatient() 1335 1336 if parent is None: 1337 parent = wx.GetApp().GetTopWindow() 1338 #-------------------------------------------------------- 1339 def edit(document=None): 1340 return
1341 #return edit_consumable_substance(parent = parent, substance = substance, single_entry = (substance is not None)) 1342 #-------------------------------------------------------- 1343 def delete(document): 1344 return 1345 # if substance.is_in_use_by_patients: 1346 # gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this substance. It is in use.'), beep = True) 1347 # return False 1348 # 1349 # return gmMedication.delete_consumable_substance(substance = substance['pk']) 1350 #------------------------------------------------------------ 1351 def refresh(lctrl): 1352 docs = pat.document_folder.get_documents() 1353 items = [ [ 1354 gmDateTime.pydt_strftime(d['clin_when'], u'%Y %b %d', accuracy = gmDateTime.acc_days), 1355 d['l10n_type'], 1356 gmTools.coalesce(d['comment'], u''), 1357 gmTools.coalesce(d['ext_ref'], u''), 1358 d['pk_doc'] 1359 ] for d in docs ] 1360 lctrl.set_string_items(items) 1361 lctrl.set_data(docs) 1362 #------------------------------------------------------------ 1363 if msg is None: 1364 msg = _('Document list for this patient.') 1365 return gmListWidgets.get_choices_from_list ( 1366 parent = parent, 1367 msg = msg, 1368 caption = _('Showing documents.'), 1369 columns = [_('Generated'), _('Type'), _('Comment'), _('Ref #'), u'#'], 1370 single_selection = single_selection, 1371 #new_callback = edit, 1372 #edit_callback = edit, 1373 #delete_callback = delete, 1374 refresh_callback = refresh 1375 #,left_extra_button = (_('Import'), _('Import consumable substances from a drug database.'), add_from_db) 1376 ) 1377 #============================================================ 1378 from Gnumed.wxGladeWidgets import wxgSelectablySortedDocTreePnl 1379
1380 -class cSelectablySortedDocTreePnl(wxgSelectablySortedDocTreePnl.wxgSelectablySortedDocTreePnl):
1381 """A panel with a document tree which can be sorted.""" 1382 #-------------------------------------------------------- 1383 # inherited event handlers 1384 #--------------------------------------------------------
1385 - def _on_sort_by_age_selected(self, evt):
1386 self._doc_tree.sort_mode = 'age' 1387 self._doc_tree.SetFocus() 1388 self._rbtn_sort_by_age.SetValue(True)
1389 #--------------------------------------------------------
1390 - def _on_sort_by_review_selected(self, evt):
1391 self._doc_tree.sort_mode = 'review' 1392 self._doc_tree.SetFocus() 1393 self._rbtn_sort_by_review.SetValue(True)
1394 #--------------------------------------------------------
1395 - def _on_sort_by_episode_selected(self, evt):
1396 self._doc_tree.sort_mode = 'episode' 1397 self._doc_tree.SetFocus() 1398 self._rbtn_sort_by_episode.SetValue(True)
1399 #--------------------------------------------------------
1400 - def _on_sort_by_issue_selected(self, event):
1401 self._doc_tree.sort_mode = 'issue' 1402 self._doc_tree.SetFocus() 1403 self._rbtn_sort_by_issue.SetValue(True)
1404 #--------------------------------------------------------
1405 - def _on_sort_by_type_selected(self, evt):
1406 self._doc_tree.sort_mode = 'type' 1407 self._doc_tree.SetFocus() 1408 self._rbtn_sort_by_type.SetValue(True)
1409 #============================================================
1410 -class cDocTree(wx.TreeCtrl, gmRegetMixin.cRegetOnPaintMixin):
1411 # FIXME: handle expansion state 1412 """This wx.TreeCtrl derivative displays a tree view of stored medical documents. 1413 1414 It listens to document and patient changes and updates itself accordingly. 1415 1416 This acts on the current patient. 1417 """ 1418 _sort_modes = ['age', 'review', 'episode', 'type', 'issue'] 1419 _root_node_labels = None 1420 #--------------------------------------------------------
1421 - def __init__(self, parent, id, *args, **kwds):
1422 """Set up our specialised tree. 1423 """ 1424 kwds['style'] = wx.TR_NO_BUTTONS | wx.NO_BORDER | wx.TR_SINGLE 1425 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds) 1426 1427 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1428 1429 tmp = _('available documents (%s)') 1430 unsigned = _('unsigned (%s) on top') % u'\u270D' 1431 cDocTree._root_node_labels = { 1432 'age': tmp % _('most recent on top'), 1433 'review': tmp % unsigned, 1434 'episode': tmp % _('sorted by episode'), 1435 'issue': tmp % _('sorted by health issue'), 1436 'type': tmp % _('sorted by type') 1437 } 1438 1439 self.root = None 1440 self.__sort_mode = 'age' 1441 1442 self.__build_context_menus() 1443 self.__register_interests() 1444 self._schedule_data_reget()
1445 #-------------------------------------------------------- 1446 # external API 1447 #--------------------------------------------------------
1448 - def display_selected_part(self, *args, **kwargs):
1449 1450 node = self.GetSelection() 1451 node_data = self.GetPyData(node) 1452 1453 if not isinstance(node_data, gmDocuments.cDocumentPart): 1454 return True 1455 1456 self.__display_part(part = node_data) 1457 return True
1458 #-------------------------------------------------------- 1459 # properties 1460 #--------------------------------------------------------
1461 - def _get_sort_mode(self):
1462 return self.__sort_mode
1463 #-----
1464 - def _set_sort_mode(self, mode):
1465 if mode is None: 1466 mode = 'age' 1467 1468 if mode == self.__sort_mode: 1469 return 1470 1471 if mode not in cDocTree._sort_modes: 1472 raise ValueError('invalid document tree sort mode [%s], valid modes: %s' % (mode, cDocTree._sort_modes)) 1473 1474 self.__sort_mode = mode 1475 1476 curr_pat = gmPerson.gmCurrentPatient() 1477 if not curr_pat.connected: 1478 return 1479 1480 self._schedule_data_reget()
1481 #----- 1482 sort_mode = property(_get_sort_mode, _set_sort_mode) 1483 #-------------------------------------------------------- 1484 # reget-on-paint API 1485 #--------------------------------------------------------
1486 - def _populate_with_data(self):
1487 curr_pat = gmPerson.gmCurrentPatient() 1488 if not curr_pat.connected: 1489 gmDispatcher.send(signal = 'statustext', msg = _('Cannot load documents. No active patient.')) 1490 return False 1491 1492 if not self.__populate_tree(): 1493 return False 1494 1495 return True
1496 #-------------------------------------------------------- 1497 # internal helpers 1498 #--------------------------------------------------------
1499 - def __register_interests(self):
1500 # connect handlers 1501 wx.EVT_TREE_ITEM_ACTIVATED (self, self.GetId(), self._on_activate) 1502 wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self.__on_right_click) 1503 wx.EVT_TREE_ITEM_GETTOOLTIP(self, -1, self._on_tree_item_gettooltip) 1504 1505 # wx.EVT_LEFT_DCLICK(self.tree, self.OnLeftDClick) 1506 1507 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 1508 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 1509 gmDispatcher.connect(signal = u'blobs.doc_med_mod_db', receiver = self._on_doc_mod_db) 1510 gmDispatcher.connect(signal = u'blobs.doc_obj_mod_db', receiver = self._on_doc_page_mod_db)
1511 #--------------------------------------------------------
1512 - def __build_context_menus(self):
1513 1514 # --- part context menu --- 1515 self.__part_context_menu = wx.Menu(title = _('Part Actions:')) 1516 1517 ID = wx.NewId() 1518 self.__part_context_menu.Append(ID, _('Display part')) 1519 wx.EVT_MENU(self.__part_context_menu, ID, self.__display_curr_part) 1520 1521 ID = wx.NewId() 1522 self.__part_context_menu.Append(ID, _('%s Sign/Edit properties') % u'\u270D') 1523 wx.EVT_MENU(self.__part_context_menu, ID, self.__review_curr_part) 1524 1525 self.__part_context_menu.AppendSeparator() 1526 1527 item = self.__part_context_menu.Append(-1, _('Delete part')) 1528 self.Bind(wx.EVT_MENU, self.__delete_part, item) 1529 1530 item = self.__part_context_menu.Append(-1, _('Move part')) 1531 self.Bind(wx.EVT_MENU, self.__move_part, item) 1532 1533 ID = wx.NewId() 1534 self.__part_context_menu.Append(ID, _('Print part')) 1535 wx.EVT_MENU(self.__part_context_menu, ID, self.__print_part) 1536 1537 ID = wx.NewId() 1538 self.__part_context_menu.Append(ID, _('Fax part')) 1539 wx.EVT_MENU(self.__part_context_menu, ID, self.__fax_part) 1540 1541 ID = wx.NewId() 1542 self.__part_context_menu.Append(ID, _('Mail part')) 1543 wx.EVT_MENU(self.__part_context_menu, ID, self.__mail_part) 1544 1545 self.__part_context_menu.AppendSeparator() # so we can append some items 1546 1547 # --- doc context menu --- 1548 self.__doc_context_menu = wx.Menu(title = _('Document Actions:')) 1549 1550 ID = wx.NewId() 1551 self.__doc_context_menu.Append(ID, _('%s Sign/Edit properties') % u'\u270D') 1552 wx.EVT_MENU(self.__doc_context_menu, ID, self.__review_curr_part) 1553 1554 self.__doc_context_menu.AppendSeparator() 1555 1556 item = self.__doc_context_menu.Append(-1, _('Add parts')) 1557 self.Bind(wx.EVT_MENU, self.__add_part, item) 1558 1559 ID = wx.NewId() 1560 self.__doc_context_menu.Append(ID, _('Print all parts')) 1561 wx.EVT_MENU(self.__doc_context_menu, ID, self.__print_doc) 1562 1563 ID = wx.NewId() 1564 self.__doc_context_menu.Append(ID, _('Fax all parts')) 1565 wx.EVT_MENU(self.__doc_context_menu, ID, self.__fax_doc) 1566 1567 ID = wx.NewId() 1568 self.__doc_context_menu.Append(ID, _('Mail all parts')) 1569 wx.EVT_MENU(self.__doc_context_menu, ID, self.__mail_doc) 1570 1571 ID = wx.NewId() 1572 self.__doc_context_menu.Append(ID, _('Export all parts')) 1573 wx.EVT_MENU(self.__doc_context_menu, ID, self.__export_doc_to_disk) 1574 1575 self.__doc_context_menu.AppendSeparator() 1576 1577 ID = wx.NewId() 1578 self.__doc_context_menu.Append(ID, _('Delete document')) 1579 wx.EVT_MENU(self.__doc_context_menu, ID, self.__delete_document) 1580 1581 ID = wx.NewId() 1582 self.__doc_context_menu.Append(ID, _('Access external original')) 1583 wx.EVT_MENU(self.__doc_context_menu, ID, self.__access_external_original) 1584 1585 ID = wx.NewId() 1586 self.__doc_context_menu.Append(ID, _('Edit corresponding encounter')) 1587 wx.EVT_MENU(self.__doc_context_menu, ID, self.__edit_encounter_details) 1588 1589 ID = wx.NewId() 1590 self.__doc_context_menu.Append(ID, _('Select corresponding encounter')) 1591 wx.EVT_MENU(self.__doc_context_menu, ID, self.__select_encounter) 1592 1593 # self.__doc_context_menu.AppendSeparator() 1594 1595 ID = wx.NewId() 1596 self.__doc_context_menu.Append(ID, _('Manage descriptions')) 1597 wx.EVT_MENU(self.__doc_context_menu, ID, self.__manage_document_descriptions)
1598 1599 # document / description 1600 # self.__desc_menu = wx.Menu() 1601 # ID = wx.NewId() 1602 # self.__doc_context_menu.AppendMenu(ID, _('Descriptions ...'), self.__desc_menu) 1603 1604 # ID = wx.NewId() 1605 # self.__desc_menu.Append(ID, _('Add new description')) 1606 # wx.EVT_MENU(self.__desc_menu, ID, self.__add_doc_desc) 1607 1608 # ID = wx.NewId() 1609 # self.__desc_menu.Append(ID, _('Delete description')) 1610 # wx.EVT_MENU(self.__desc_menu, ID, self.__del_doc_desc) 1611 1612 # self.__desc_menu.AppendSeparator() 1613 #--------------------------------------------------------
1614 - def __populate_tree(self):
1615 1616 wx.BeginBusyCursor() 1617 1618 # clean old tree 1619 if self.root is not None: 1620 self.DeleteAllItems() 1621 1622 # init new tree 1623 self.root = self.AddRoot(cDocTree._root_node_labels[self.__sort_mode], -1, -1) 1624 self.SetItemPyData(self.root, None) 1625 self.SetItemHasChildren(self.root, False) 1626 1627 # read documents from database 1628 curr_pat = gmPerson.gmCurrentPatient() 1629 docs_folder = curr_pat.get_document_folder() 1630 docs = docs_folder.get_documents() 1631 1632 if docs is None: 1633 gmGuiHelpers.gm_show_error ( 1634 aMessage = _('Error searching documents.'), 1635 aTitle = _('loading document list') 1636 ) 1637 # avoid recursion of GUI updating 1638 wx.EndBusyCursor() 1639 return True 1640 1641 if len(docs) == 0: 1642 wx.EndBusyCursor() 1643 return True 1644 1645 # fill new tree from document list 1646 self.SetItemHasChildren(self.root, True) 1647 1648 # add our documents as first level nodes 1649 intermediate_nodes = {} 1650 for doc in docs: 1651 1652 parts = doc.parts 1653 1654 if len(parts) == 0: 1655 no_parts = _('no parts') 1656 elif len(parts) == 1: 1657 no_parts = _('1 part') 1658 else: 1659 no_parts = _('%s parts') % len(parts) 1660 1661 # need intermediate branch level ? 1662 if self.__sort_mode == 'episode': 1663 inter_label = u'%s%s' % (doc['episode'], gmTools.coalesce(doc['health_issue'], u'', u' (%s)')) 1664 doc_label = _('%s%7s %s:%s (%s)') % ( 1665 gmTools.bool2subst(doc.has_unreviewed_parts, gmTools.u_writing_hand, u'', u'?'), 1666 doc['clin_when'].strftime('%m/%Y'), 1667 doc['l10n_type'][:26], 1668 gmTools.coalesce(initial = doc['comment'], instead = u'', template_initial = u' %s'), 1669 no_parts 1670 ) 1671 if not intermediate_nodes.has_key(inter_label): 1672 intermediate_nodes[inter_label] = self.AppendItem(parent = self.root, text = inter_label) 1673 self.SetItemBold(intermediate_nodes[inter_label], bold = True) 1674 self.SetItemPyData(intermediate_nodes[inter_label], None) 1675 self.SetItemHasChildren(intermediate_nodes[inter_label], True) 1676 parent = intermediate_nodes[inter_label] 1677 1678 elif self.__sort_mode == 'type': 1679 inter_label = doc['l10n_type'] 1680 doc_label = _('%s%7s (%s):%s (%s)') % ( 1681 gmTools.bool2subst(doc.has_unreviewed_parts, gmTools.u_writing_hand, u'', u'?'), 1682 doc['clin_when'].strftime('%m/%Y'), 1683 no_parts, 1684 gmTools.coalesce(initial = doc['comment'], instead = u'', template_initial = u' %s'), 1685 u'%s%s' % (doc['episode'], gmTools.coalesce(doc['health_issue'], u'', u' %s %%s' % gmTools.u_right_arrow)) 1686 ) 1687 if not intermediate_nodes.has_key(inter_label): 1688 intermediate_nodes[inter_label] = self.AppendItem(parent = self.root, text = inter_label) 1689 self.SetItemBold(intermediate_nodes[inter_label], bold = True) 1690 self.SetItemPyData(intermediate_nodes[inter_label], None) 1691 self.SetItemHasChildren(intermediate_nodes[inter_label], True) 1692 parent = intermediate_nodes[inter_label] 1693 1694 elif self.__sort_mode == 'issue': 1695 if doc['health_issue'] is None: 1696 inter_label = _('%s (unattributed episode)') % doc['episode'] 1697 else: 1698 inter_label = doc['health_issue'] 1699 doc_label = _('%s%7s %s:%s (%s)') % ( 1700 gmTools.bool2subst(doc.has_unreviewed_parts, gmTools.u_writing_hand, u'', u'?'), 1701 doc['clin_when'].strftime('%m/%Y'), 1702 doc['l10n_type'][:26], 1703 gmTools.coalesce(initial = doc['comment'], instead = u'', template_initial = u' %s'), 1704 no_parts 1705 ) 1706 if not intermediate_nodes.has_key(inter_label): 1707 intermediate_nodes[inter_label] = self.AppendItem(parent = self.root, text = inter_label) 1708 self.SetItemBold(intermediate_nodes[inter_label], bold = True) 1709 self.SetItemPyData(intermediate_nodes[inter_label], None) 1710 self.SetItemHasChildren(intermediate_nodes[inter_label], True) 1711 parent = intermediate_nodes[inter_label] 1712 1713 else: 1714 doc_label = _('%s%7s %s:%s (%s)') % ( 1715 gmTools.bool2subst(doc.has_unreviewed_parts, gmTools.u_writing_hand, u'', u'?'), 1716 doc['clin_when'].strftime('%m/%Y'), 1717 doc['l10n_type'][:26], 1718 gmTools.coalesce(initial = doc['comment'], instead = u'', template_initial = u' %s'), 1719 no_parts 1720 ) 1721 parent = self.root 1722 1723 doc_node = self.AppendItem(parent = parent, text = doc_label) 1724 #self.SetItemBold(doc_node, bold = True) 1725 self.SetItemPyData(doc_node, doc) 1726 if len(parts) == 0: 1727 self.SetItemHasChildren(doc_node, False) 1728 else: 1729 self.SetItemHasChildren(doc_node, True) 1730 1731 # now add parts as child nodes 1732 for part in parts: 1733 # if part['clinically_relevant']: 1734 # rel = ' [%s]' % _('Cave') 1735 # else: 1736 # rel = '' 1737 f_ext = u'' 1738 if part['filename'] is not None: 1739 f_ext = os.path.splitext(part['filename'])[1].strip('.').strip() 1740 if f_ext != u'': 1741 f_ext = u' .' + f_ext.upper() 1742 label = '%s%s (%s%s)%s' % ( 1743 gmTools.bool2str ( 1744 boolean = part['reviewed'] or part['reviewed_by_you'] or part['reviewed_by_intended_reviewer'], 1745 true_str = u'', 1746 false_str = gmTools.u_writing_hand 1747 ), 1748 _('part %2s') % part['seq_idx'], 1749 gmTools.size2str(part['size']), 1750 f_ext, 1751 gmTools.coalesce ( 1752 part['obj_comment'], 1753 u'', 1754 u': %s%%s%s' % (gmTools.u_left_double_angle_quote, gmTools.u_right_double_angle_quote) 1755 ) 1756 ) 1757 1758 part_node = self.AppendItem(parent = doc_node, text = label) 1759 self.SetItemPyData(part_node, part) 1760 self.SetItemHasChildren(part_node, False) 1761 1762 self.__sort_nodes() 1763 self.SelectItem(self.root) 1764 1765 # FIXME: apply expansion state if available or else ... 1766 # FIXME: ... uncollapse to default state 1767 self.Expand(self.root) 1768 if self.__sort_mode in ['episode', 'type', 'issue']: 1769 for key in intermediate_nodes.keys(): 1770 self.Expand(intermediate_nodes[key]) 1771 1772 wx.EndBusyCursor() 1773 1774 return True
1775 #------------------------------------------------------------------------
1776 - def OnCompareItems (self, node1=None, node2=None):
1777 """Used in sorting items. 1778 1779 -1: 1 < 2 1780 0: 1 = 2 1781 1: 1 > 2 1782 """ 1783 # Windows can send bogus events so ignore that 1784 if not node1: 1785 _log.debug('invalid node 1') 1786 return 0 1787 if not node2: 1788 _log.debug('invalid node 2') 1789 return 0 1790 if not node1.IsOk(): 1791 _log.debug('no data on node 1') 1792 return 0 1793 if not node2.IsOk(): 1794 _log.debug('no data on node 2') 1795 return 0 1796 1797 data1 = self.GetPyData(node1) 1798 data2 = self.GetPyData(node2) 1799 1800 # doc node 1801 if isinstance(data1, gmDocuments.cDocument): 1802 1803 date_field = 'clin_when' 1804 #date_field = 'modified_when' 1805 1806 if self.__sort_mode == 'age': 1807 # reverse sort by date 1808 if data1[date_field] > data2[date_field]: 1809 return -1 1810 if data1[date_field] == data2[date_field]: 1811 return 0 1812 return 1 1813 1814 elif self.__sort_mode == 'episode': 1815 if data1['episode'] < data2['episode']: 1816 return -1 1817 if data1['episode'] == data2['episode']: 1818 # inner sort: reverse by date 1819 if data1[date_field] > data2[date_field]: 1820 return -1 1821 if data1[date_field] == data2[date_field]: 1822 return 0 1823 return 1 1824 return 1 1825 1826 elif self.__sort_mode == 'issue': 1827 if data1['health_issue'] < data2['health_issue']: 1828 return -1 1829 if data1['health_issue'] == data2['health_issue']: 1830 # inner sort: reverse by date 1831 if data1[date_field] > data2[date_field]: 1832 return -1 1833 if data1[date_field] == data2[date_field]: 1834 return 0 1835 return 1 1836 return 1 1837 1838 elif self.__sort_mode == 'review': 1839 # equality 1840 if data1.has_unreviewed_parts == data2.has_unreviewed_parts: 1841 # inner sort: reverse by date 1842 if data1[date_field] > data2[date_field]: 1843 return -1 1844 if data1[date_field] == data2[date_field]: 1845 return 0 1846 return 1 1847 if data1.has_unreviewed_parts: 1848 return -1 1849 return 1 1850 1851 elif self.__sort_mode == 'type': 1852 if data1['l10n_type'] < data2['l10n_type']: 1853 return -1 1854 if data1['l10n_type'] == data2['l10n_type']: 1855 # inner sort: reverse by date 1856 if data1[date_field] > data2[date_field]: 1857 return -1 1858 if data1[date_field] == data2[date_field]: 1859 return 0 1860 return 1 1861 return 1 1862 1863 else: 1864 _log.error('unknown document sort mode [%s], reverse-sorting by age', self.__sort_mode) 1865 # reverse sort by date 1866 if data1[date_field] > data2[date_field]: 1867 return -1 1868 if data1[date_field] == data2[date_field]: 1869 return 0 1870 return 1 1871 1872 # part node 1873 if isinstance(data1, gmDocuments.cDocumentPart): 1874 # compare sequence IDs (= "page" numbers) 1875 # FIXME: wrong order ? 1876 if data1['seq_idx'] < data2['seq_idx']: 1877 return -1 1878 if data1['seq_idx'] == data2['seq_idx']: 1879 return 0 1880 return 1 1881 1882 # else sort alphabetically 1883 if None in [data1, data2]: 1884 l1 = self.GetItemText(node1) 1885 l2 = self.GetItemText(node2) 1886 if l1 < l2: 1887 return -1 1888 if l1 == l2: 1889 return 0 1890 else: 1891 if data1 < data2: 1892 return -1 1893 if data1 == data2: 1894 return 0 1895 return 1
1896 #------------------------------------------------------------------------ 1897 # event handlers 1898 #------------------------------------------------------------------------
1899 - def _on_doc_mod_db(self, *args, **kwargs):
1900 # FIXME: remember current expansion state 1901 wx.CallAfter(self._schedule_data_reget)
1902 #------------------------------------------------------------------------
1903 - def _on_doc_page_mod_db(self, *args, **kwargs):
1904 # FIXME: remember current expansion state 1905 wx.CallAfter(self._schedule_data_reget)
1906 #------------------------------------------------------------------------
1907 - def _on_pre_patient_selection(self, *args, **kwargs):
1908 # FIXME: self.__store_expansion_history_in_db 1909 1910 # empty out tree 1911 if self.root is not None: 1912 self.DeleteAllItems() 1913 self.root = None
1914 #------------------------------------------------------------------------
1915 - def _on_post_patient_selection(self, *args, **kwargs):
1916 # FIXME: self.__load_expansion_history_from_db (but not apply it !) 1917 self._schedule_data_reget()
1918 #------------------------------------------------------------------------
1919 - def _on_activate(self, event):
1920 node = event.GetItem() 1921 node_data = self.GetPyData(node) 1922 1923 # exclude pseudo root node 1924 if node_data is None: 1925 return None 1926 1927 # expand/collapse documents on activation 1928 if isinstance(node_data, gmDocuments.cDocument): 1929 self.Toggle(node) 1930 return True 1931 1932 # string nodes are labels such as episodes which may or may not have children 1933 if type(node_data) == type('string'): 1934 self.Toggle(node) 1935 return True 1936 1937 self.__display_part(part = node_data) 1938 return True
1939 #--------------------------------------------------------
1940 - def __on_right_click(self, evt):
1941 1942 node = evt.GetItem() 1943 self.__curr_node_data = self.GetPyData(node) 1944 1945 # exclude pseudo root node 1946 if self.__curr_node_data is None: 1947 return None 1948 1949 # documents 1950 if isinstance(self.__curr_node_data, gmDocuments.cDocument): 1951 self.__handle_doc_context() 1952 1953 # parts 1954 if isinstance(self.__curr_node_data, gmDocuments.cDocumentPart): 1955 self.__handle_part_context() 1956 1957 del self.__curr_node_data 1958 evt.Skip()
1959 #--------------------------------------------------------
1960 - def __activate_as_current_photo(self, evt):
1961 self.__curr_node_data.set_as_active_photograph()
1962 #--------------------------------------------------------
1963 - def __display_curr_part(self, evt):
1964 self.__display_part(part = self.__curr_node_data)
1965 #--------------------------------------------------------
1966 - def __review_curr_part(self, evt):
1967 self.__review_part(part = self.__curr_node_data)
1968 #--------------------------------------------------------
1969 - def __manage_document_descriptions(self, evt):
1970 manage_document_descriptions(parent = self, document = self.__curr_node_data)
1971 #--------------------------------------------------------
1972 - def _on_tree_item_gettooltip(self, event):
1973 1974 item = event.GetItem() 1975 1976 if not item.IsOk(): 1977 event.SetToolTip(u' ') 1978 return 1979 1980 data = self.GetPyData(item) 1981 1982 # documents 1983 if isinstance(data, gmDocuments.cDocument): 1984 tt = data.format() 1985 # parts 1986 elif isinstance(data, gmDocuments.cDocumentPart): 1987 tt = data.format() 1988 # other (root, intermediate nodes) 1989 else: 1990 tt = u' ' 1991 1992 event.SetToolTip(tt)
1993 #-------------------------------------------------------- 1994 # internal API 1995 #--------------------------------------------------------
1996 - def __sort_nodes(self, start_node=None):
1997 1998 if start_node is None: 1999 start_node = self.GetRootItem() 2000 2001 # protect against empty tree where not even 2002 # a root node exists 2003 if not start_node.IsOk(): 2004 return True 2005 2006 self.SortChildren(start_node) 2007 2008 child_node, cookie = self.GetFirstChild(start_node) 2009 while child_node.IsOk(): 2010 self.__sort_nodes(start_node = child_node) 2011 child_node, cookie = self.GetNextChild(start_node, cookie) 2012 2013 return
2014 #--------------------------------------------------------
2015 - def __handle_doc_context(self):
2016 self.PopupMenu(self.__doc_context_menu, wx.DefaultPosition)
2017 #--------------------------------------------------------
2018 - def __handle_part_context(self):
2019 # make active patient photograph 2020 if self.__curr_node_data['type'] == 'patient photograph': 2021 ID = wx.NewId() 2022 self.__part_context_menu.Append(ID, _('Activate as current photo')) 2023 wx.EVT_MENU(self.__part_context_menu, ID, self.__activate_as_current_photo) 2024 else: 2025 ID = None 2026 2027 self.PopupMenu(self.__part_context_menu, wx.DefaultPosition) 2028 2029 if ID is not None: 2030 self.__part_context_menu.Delete(ID)
2031 #-------------------------------------------------------- 2032 # part level context menu handlers 2033 #--------------------------------------------------------
2034 - def __display_part(self, part):
2035 """Display document part.""" 2036 2037 # sanity check 2038 if part['size'] == 0: 2039 _log.debug('cannot display part [%s] - 0 bytes', part['pk_obj']) 2040 gmGuiHelpers.gm_show_error ( 2041 aMessage = _('Document part does not seem to exist in database !'), 2042 aTitle = _('showing document') 2043 ) 2044 return None 2045 2046 wx.BeginBusyCursor() 2047 2048 cfg = gmCfg.cCfgSQL() 2049 2050 # determine database export chunk size 2051 chunksize = int( 2052 cfg.get2 ( 2053 option = "horstspace.blob_export_chunk_size", 2054 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2055 bias = 'workplace', 2056 default = default_chunksize 2057 )) 2058 2059 # shall we force blocking during view ? 2060 block_during_view = bool( cfg.get2 ( 2061 option = 'horstspace.document_viewer.block_during_view', 2062 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2063 bias = 'user', 2064 default = None 2065 )) 2066 2067 # display it 2068 successful, msg = part.display_via_mime ( 2069 chunksize = chunksize, 2070 block = block_during_view 2071 ) 2072 2073 wx.EndBusyCursor() 2074 2075 if not successful: 2076 gmGuiHelpers.gm_show_error ( 2077 aMessage = _('Cannot display document part:\n%s') % msg, 2078 aTitle = _('showing document') 2079 ) 2080 return None 2081 2082 # handle review after display 2083 # 0: never 2084 # 1: always 2085 # 2: if no review by myself exists yet 2086 # 3: if no review at all exists yet 2087 # 4: if no review by responsible reviewer 2088 review_after_display = int(cfg.get2 ( 2089 option = 'horstspace.document_viewer.review_after_display', 2090 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2091 bias = 'user', 2092 default = 3 2093 )) 2094 if review_after_display == 1: # always review 2095 self.__review_part(part=part) 2096 elif review_after_display == 2: # review if no review by me exists 2097 review_by_me = filter(lambda rev: rev['is_your_review'], part.get_reviews()) 2098 if len(review_by_me) == 0: 2099 self.__review_part(part = part) 2100 elif review_after_display == 3: 2101 if len(part.get_reviews()) == 0: 2102 self.__review_part(part = part) 2103 elif review_after_display == 4: 2104 reviewed_by_responsible = filter(lambda rev: rev['is_review_by_responsible_reviewer'], part.get_reviews()) 2105 if len(reviewed_by_responsible) == 0: 2106 self.__review_part(part = part) 2107 2108 return True
2109 #--------------------------------------------------------
2110 - def __review_part(self, part=None):
2111 dlg = cReviewDocPartDlg ( 2112 parent = self, 2113 id = -1, 2114 part = part 2115 ) 2116 dlg.ShowModal() 2117 dlg.Destroy()
2118 #--------------------------------------------------------
2119 - def __move_part(self, evt):
2120 target_doc = manage_documents ( 2121 parent = self, 2122 msg = _('\nSelect the document into which to move the selected part !\n') 2123 ) 2124 if target_doc is None: 2125 return 2126 self.__curr_node_data['pk_doc'] = target_doc['pk_doc'] 2127 self.__curr_node_data.save()
2128 #--------------------------------------------------------
2129 - def __delete_part(self, evt):
2130 delete_it = gmGuiHelpers.gm_show_question ( 2131 cancel_button = True, 2132 title = _('Deleting document part'), 2133 question = _( 2134 'Are you sure you want to delete the %s part #%s\n' 2135 '\n' 2136 '%s' 2137 'from the following document\n' 2138 '\n' 2139 ' %s (%s)\n' 2140 '%s' 2141 '\n' 2142 'Really delete ?\n' 2143 '\n' 2144 '(this action cannot be reversed)' 2145 ) % ( 2146 gmTools.size2str(self.__curr_node_data['size']), 2147 self.__curr_node_data['seq_idx'], 2148 gmTools.coalesce(self.__curr_node_data['obj_comment'], u'', u' "%s"\n\n'), 2149 self.__curr_node_data['l10n_type'], 2150 gmDateTime.pydt_strftime(self.__curr_node_data['date_generated'], format = '%Y-%m-%d', accuracy = gmDateTime.acc_days), 2151 gmTools.coalesce(self.__curr_node_data['doc_comment'], u'', u' "%s"\n') 2152 ) 2153 ) 2154 if not delete_it: 2155 return 2156 2157 gmDocuments.delete_document_part ( 2158 part_pk = self.__curr_node_data['pk_obj'], 2159 encounter_pk = gmPerson.gmCurrentPatient().emr.active_encounter['pk_encounter'] 2160 )
2161 #--------------------------------------------------------
2162 - def __process_part(self, action=None, l10n_action=None):
2163 2164 gmHooks.run_hook_script(hook = u'before_%s_doc_part' % action) 2165 2166 wx.BeginBusyCursor() 2167 2168 # detect wrapper 2169 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc' % action) 2170 if not found: 2171 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc.bat' % action) 2172 if not found: 2173 _log.error('neither of gm-%s_doc or gm-%s_doc.bat found', action, action) 2174 wx.EndBusyCursor() 2175 gmGuiHelpers.gm_show_error ( 2176 _('Cannot %(l10n_action)s document part - %(l10n_action)s command not found.\n' 2177 '\n' 2178 'Either of gm-%(action)s_doc or gm-%(action)s_doc.bat\n' 2179 'must be in the execution path. The command will\n' 2180 'be passed the filename to %(l10n_action)s.' 2181 ) % {'action': action, 'l10n_action': l10n_action}, 2182 _('Processing document part: %s') % l10n_action 2183 ) 2184 return 2185 2186 cfg = gmCfg.cCfgSQL() 2187 2188 # determine database export chunk size 2189 chunksize = int(cfg.get2 ( 2190 option = "horstspace.blob_export_chunk_size", 2191 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2192 bias = 'workplace', 2193 default = default_chunksize 2194 )) 2195 2196 part_file = self.__curr_node_data.export_to_file(aChunkSize = chunksize) 2197 2198 cmd = u'%s %s' % (external_cmd, part_file) 2199 if os.name == 'nt': 2200 blocking = True 2201 else: 2202 blocking = False 2203 success = gmShellAPI.run_command_in_shell ( 2204 command = cmd, 2205 blocking = blocking 2206 ) 2207 2208 wx.EndBusyCursor() 2209 2210 if not success: 2211 _log.error('%s command failed: [%s]', action, cmd) 2212 gmGuiHelpers.gm_show_error ( 2213 _('Cannot %(l10n_action)s document part - %(l10n_action)s command failed.\n' 2214 '\n' 2215 'You may need to check and fix either of\n' 2216 ' gm-%(action)s_doc (Unix/Mac) or\n' 2217 ' gm-%(action)s_doc.bat (Windows)\n' 2218 '\n' 2219 'The command is passed the filename to %(l10n_action)s.' 2220 ) % {'action': action, 'l10n_action': l10n_action}, 2221 _('Processing document part: %s') % l10n_action 2222 ) 2223 else: 2224 if action == 'mail': 2225 curr_pat = gmPerson.gmCurrentPatient() 2226 emr = curr_pat.emr 2227 emr.add_clin_narrative ( 2228 soap_cat = None, 2229 note = _('document part handed over to email program: %s') % self.__curr_node_data.format(single_line = True), 2230 episode = self.__curr_node_data['pk_episode'] 2231 )
2232 #--------------------------------------------------------
2233 - def __print_part(self, evt):
2234 self.__process_part(action = u'print', l10n_action = _('print'))
2235 #--------------------------------------------------------
2236 - def __fax_part(self, evt):
2237 self.__process_part(action = u'fax', l10n_action = _('fax'))
2238 #--------------------------------------------------------
2239 - def __mail_part(self, evt):
2240 self.__process_part(action = u'mail', l10n_action = _('mail'))
2241 #-------------------------------------------------------- 2242 # document level context menu handlers 2243 #--------------------------------------------------------
2244 - def __select_encounter(self, evt):
2245 enc = gmEMRStructWidgets.select_encounters ( 2246 parent = self, 2247 patient = gmPerson.gmCurrentPatient() 2248 ) 2249 if not enc: 2250 return 2251 self.__curr_node_data['pk_encounter'] = enc['pk_encounter'] 2252 self.__curr_node_data.save()
2253 #--------------------------------------------------------
2254 - def __edit_encounter_details(self, evt):
2255 enc = gmEMRStructItems.cEncounter(aPK_obj = self.__curr_node_data['pk_encounter']) 2256 gmEMRStructWidgets.edit_encounter(parent = self, encounter = enc)
2257 #--------------------------------------------------------
2258 - def __process_doc(self, action=None, l10n_action=None):
2259 2260 gmHooks.run_hook_script(hook = u'before_%s_doc' % action) 2261 2262 wx.BeginBusyCursor() 2263 2264 # detect wrapper 2265 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc' % action) 2266 if not found: 2267 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc.bat' % action) 2268 if not found: 2269 _log.error('neither of gm-%s_doc or gm-%s_doc.bat found', action, action) 2270 wx.EndBusyCursor() 2271 gmGuiHelpers.gm_show_error ( 2272 _('Cannot %(l10n_action)s document - %(l10n_action)s command not found.\n' 2273 '\n' 2274 'Either of gm-%(action)s_doc or gm-%(action)s_doc.bat\n' 2275 'must be in the execution path. The command will\n' 2276 'be passed a list of filenames to %(l10n_action)s.' 2277 ) % {'action': action, 'l10n_action': l10n_action}, 2278 _('Processing document: %s') % l10n_action 2279 ) 2280 return 2281 2282 cfg = gmCfg.cCfgSQL() 2283 2284 # determine database export chunk size 2285 chunksize = int(cfg.get2 ( 2286 option = "horstspace.blob_export_chunk_size", 2287 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2288 bias = 'workplace', 2289 default = default_chunksize 2290 )) 2291 2292 part_files = self.__curr_node_data.export_parts_to_files(chunksize = chunksize) 2293 2294 if os.name == 'nt': 2295 blocking = True 2296 else: 2297 blocking = False 2298 cmd = external_cmd + u' ' + u' '.join(part_files) 2299 success = gmShellAPI.run_command_in_shell ( 2300 command = cmd, 2301 blocking = blocking 2302 ) 2303 2304 wx.EndBusyCursor() 2305 2306 if not success: 2307 _log.error('%s command failed: [%s]', action, cmd) 2308 gmGuiHelpers.gm_show_error ( 2309 _('Cannot %(l10n_action)s document - %(l10n_action)s command failed.\n' 2310 '\n' 2311 'You may need to check and fix either of\n' 2312 ' gm-%(action)s_doc (Unix/Mac) or\n' 2313 ' gm-%(action)s_doc.bat (Windows)\n' 2314 '\n' 2315 'The command is passed a list of filenames to %(l10n_action)s.' 2316 ) % {'action': action, 'l10n_action': l10n_action}, 2317 _('Processing document: %s') % l10n_action 2318 )
2319 #-------------------------------------------------------- 2320 # FIXME: icons in the plugin toolbar
2321 - def __print_doc(self, evt):
2322 self.__process_doc(action = u'print', l10n_action = _('print'))
2323 #--------------------------------------------------------
2324 - def __fax_doc(self, evt):
2325 self.__process_doc(action = u'fax', l10n_action = _('fax'))
2326 #--------------------------------------------------------
2327 - def __mail_doc(self, evt):
2328 self.__process_doc(action = u'mail', l10n_action = _('mail'))
2329 #--------------------------------------------------------
2330 - def __add_part(self, evt):
2331 dlg = wx.FileDialog ( 2332 parent = self, 2333 message = _('Choose a file'), 2334 defaultDir = os.path.expanduser(os.path.join('~', 'gnumed')), 2335 defaultFile = '', 2336 wildcard = "%s (*)|*|PNGs (*.png)|*.png|PDFs (*.pdf)|*.pdf|TIFFs (*.tif)|*.tif|JPEGs (*.jpg)|*.jpg|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')), 2337 style = wx.OPEN | wx.FILE_MUST_EXIST | wx.MULTIPLE 2338 ) 2339 result = dlg.ShowModal() 2340 if result != wx.ID_CANCEL: 2341 self.__curr_node_data.add_parts_from_files(files = dlg.GetPaths(), reviewer = gmStaff.gmCurrentProvider()['pk_staff']) 2342 dlg.Destroy()
2343 #--------------------------------------------------------
2344 - def __access_external_original(self, evt):
2345 2346 gmHooks.run_hook_script(hook = u'before_external_doc_access') 2347 2348 wx.BeginBusyCursor() 2349 2350 # detect wrapper 2351 found, external_cmd = gmShellAPI.detect_external_binary(u'gm_access_external_doc.sh') 2352 if not found: 2353 found, external_cmd = gmShellAPI.detect_external_binary(u'gm_access_external_doc.bat') 2354 if not found: 2355 _log.error('neither of gm_access_external_doc.sh or .bat found') 2356 wx.EndBusyCursor() 2357 gmGuiHelpers.gm_show_error ( 2358 _('Cannot access external document - access command not found.\n' 2359 '\n' 2360 'Either of gm_access_external_doc.sh or *.bat must be\n' 2361 'in the execution path. The command will be passed the\n' 2362 'document type and the reference URL for processing.' 2363 ), 2364 _('Accessing external document') 2365 ) 2366 return 2367 2368 cmd = u'%s "%s" "%s"' % (external_cmd, self.__curr_node_data['type'], self.__curr_node_data['ext_ref']) 2369 if os.name == 'nt': 2370 blocking = True 2371 else: 2372 blocking = False 2373 success = gmShellAPI.run_command_in_shell ( 2374 command = cmd, 2375 blocking = blocking 2376 ) 2377 2378 wx.EndBusyCursor() 2379 2380 if not success: 2381 _log.error('External access command failed: [%s]', cmd) 2382 gmGuiHelpers.gm_show_error ( 2383 _('Cannot access external document - access command failed.\n' 2384 '\n' 2385 'You may need to check and fix either of\n' 2386 ' gm_access_external_doc.sh (Unix/Mac) or\n' 2387 ' gm_access_external_doc.bat (Windows)\n' 2388 '\n' 2389 'The command is passed the document type and the\n' 2390 'external reference URL on the command line.' 2391 ), 2392 _('Accessing external document') 2393 )
2394 #--------------------------------------------------------
2395 - def __export_doc_to_disk(self, evt):
2396 """Export document into directory. 2397 2398 - one file per object 2399 - into subdirectory named after patient 2400 """ 2401 pat = gmPerson.gmCurrentPatient() 2402 dname = '%s-%s%s' % ( 2403 self.__curr_node_data['l10n_type'], 2404 self.__curr_node_data['clin_when'].strftime('%Y-%m-%d'), 2405 gmTools.coalesce(self.__curr_node_data['ext_ref'], '', '-%s').replace(' ', '_') 2406 ) 2407 def_dir = os.path.expanduser(os.path.join('~', 'gnumed', pat['dirname'], dname)) 2408 gmTools.mkdir(def_dir) 2409 2410 dlg = wx.DirDialog ( 2411 parent = self, 2412 message = _('Save document into directory ...'), 2413 defaultPath = def_dir, 2414 style = wx.DD_DEFAULT_STYLE 2415 ) 2416 result = dlg.ShowModal() 2417 dirname = dlg.GetPath() 2418 dlg.Destroy() 2419 2420 if result != wx.ID_OK: 2421 return True 2422 2423 wx.BeginBusyCursor() 2424 2425 cfg = gmCfg.cCfgSQL() 2426 2427 # determine database export chunk size 2428 chunksize = int(cfg.get2 ( 2429 option = "horstspace.blob_export_chunk_size", 2430 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2431 bias = 'workplace', 2432 default = default_chunksize 2433 )) 2434 2435 fnames = self.__curr_node_data.export_parts_to_files(export_dir = dirname, chunksize = chunksize) 2436 2437 wx.EndBusyCursor() 2438 2439 gmDispatcher.send(signal='statustext', msg=_('Successfully exported %s parts into the directory [%s].') % (len(fnames), dirname)) 2440 2441 return True
2442 #--------------------------------------------------------
2443 - def __delete_document(self, evt):
2444 result = gmGuiHelpers.gm_show_question ( 2445 aMessage = _('Are you sure you want to delete the document ?'), 2446 aTitle = _('Deleting document') 2447 ) 2448 if result is True: 2449 curr_pat = gmPerson.gmCurrentPatient() 2450 emr = curr_pat.get_emr() 2451 enc = emr.active_encounter 2452 gmDocuments.delete_document(document_id = self.__curr_node_data['pk_doc'], encounter_id = enc['pk_encounter'])
2453 #============================================================ 2454 # main 2455 #------------------------------------------------------------ 2456 if __name__ == '__main__': 2457 2458 gmI18N.activate_locale() 2459 gmI18N.install_domain(domain = 'gnumed') 2460 2461 #---------------------------------------- 2462 #---------------------------------------- 2463 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'): 2464 # test_*() 2465 pass 2466 2467 #============================================================ 2468