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

Source Code for Module Gnumed.wxpython.gmMedDocWidgets

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