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

Source Code for Module Gnumed.wxpython.gmDocumentWidgets

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