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