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: 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 ImportError: 819 gmGuiHelpers.gm_show_error ( 820 aMessage = _( 821 'No pages could be acquired from the source.\n\n' 822 'This may mean the scanner driver is not properly installed\n\n' 823 'On Windows you must install the TWAIN Python module\n' 824 'while on Linux and MacOSX it is recommended to install\n' 825 'the XSane package.' 826 ), 827 aTitle = _('acquiring page') 828 ) 829 return None 830 831 if len(fnames) == 0: # no pages scanned 832 return True 833 834 self.acquired_pages.extend(fnames) 835 self.__reload_LBOX_doc_pages() 836 837 return True
838 #--------------------------------------------------------
839 - def _load_btn_pressed(self, evt):
840 # patient file chooser 841 dlg = wx.FileDialog ( 842 parent = None, 843 message = _('Choose a file'), 844 defaultDir = os.path.expanduser(os.path.join('~', 'gnumed')), 845 defaultFile = '', 846 wildcard = "%s (*)|*|TIFFs (*.tif)|*.tif|JPEGs (*.jpg)|*.jpg|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')), 847 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST | wx.MULTIPLE 848 ) 849 result = dlg.ShowModal() 850 if result != wx.ID_CANCEL: 851 files = dlg.GetPaths() 852 for file in files: 853 self.acquired_pages.append(file) 854 self.__reload_LBOX_doc_pages() 855 dlg.Destroy()
856 #--------------------------------------------------------
857 - def _show_btn_pressed(self, evt):
858 # did user select a page ? 859 page_idx = self._LBOX_doc_pages.GetSelection() 860 if page_idx == -1: 861 gmGuiHelpers.gm_show_info ( 862 aMessage = _('You must select a part before you can view it.'), 863 aTitle = _('displaying part') 864 ) 865 return None 866 # now, which file was that again ? 867 page_fname = self._LBOX_doc_pages.GetClientData(page_idx) 868 869 (result, msg) = gmMimeLib.call_viewer_on_file(page_fname) 870 if not result: 871 gmGuiHelpers.gm_show_warning ( 872 aMessage = _('Cannot display document part:\n%s') % msg, 873 aTitle = _('displaying part') 874 ) 875 return None 876 return 1
877 #--------------------------------------------------------
878 - def _del_btn_pressed(self, event):
879 page_idx = self._LBOX_doc_pages.GetSelection() 880 if page_idx == -1: 881 gmGuiHelpers.gm_show_info ( 882 aMessage = _('You must select a part before you can delete it.'), 883 aTitle = _('deleting part') 884 ) 885 return None 886 page_fname = self._LBOX_doc_pages.GetClientData(page_idx) 887 888 # 1) del item from self.acquired_pages 889 self.acquired_pages[page_idx:(page_idx+1)] = [] 890 891 # 2) reload list box 892 self.__reload_LBOX_doc_pages() 893 894 # 3) optionally kill file in the file system 895 do_delete = gmGuiHelpers.gm_show_question ( 896 _('The part has successfully been removed from the document.\n' 897 '\n' 898 'Do you also want to permanently delete the file\n' 899 '\n' 900 ' [%s]\n' 901 '\n' 902 'from which this document part was loaded ?\n' 903 '\n' 904 'If it is a temporary file for a page you just scanned\n' 905 'this makes a lot of sense. In other cases you may not\n' 906 'want to lose the file.\n' 907 '\n' 908 'Pressing [YES] will permanently remove the file\n' 909 'from your computer.\n' 910 ) % page_fname, 911 _('Removing document part') 912 ) 913 if do_delete: 914 try: 915 os.remove(page_fname) 916 except: 917 _log.exception('Error deleting file.') 918 gmGuiHelpers.gm_show_error ( 919 aMessage = _('Cannot delete part in file [%s].\n\nYou may not have write access to it.') % page_fname, 920 aTitle = _('deleting part') 921 ) 922 923 return 1
924 #--------------------------------------------------------
925 - def _save_btn_pressed(self, evt):
926 927 if not self.__valid_for_save(): 928 return False 929 930 wx.BeginBusyCursor() 931 932 pat = gmPerson.gmCurrentPatient() 933 doc_folder = pat.get_document_folder() 934 emr = pat.get_emr() 935 936 # create new document 937 pk_episode = self._PhWheel_episode.GetData() 938 if pk_episode is None: 939 episode = emr.add_episode ( 940 episode_name = self._PhWheel_episode.GetValue().strip(), 941 is_open = True 942 ) 943 if episode is None: 944 wx.EndBusyCursor() 945 gmGuiHelpers.gm_show_error ( 946 aMessage = _('Cannot start episode [%s].') % self._PhWheel_episode.GetValue().strip(), 947 aTitle = _('saving document') 948 ) 949 return False 950 pk_episode = episode['pk_episode'] 951 952 encounter = emr.active_encounter['pk_encounter'] 953 document_type = self._PhWheel_doc_type.GetData() 954 new_doc = doc_folder.add_document(document_type, encounter, pk_episode) 955 if new_doc is None: 956 wx.EndBusyCursor() 957 gmGuiHelpers.gm_show_error ( 958 aMessage = _('Cannot create new document.'), 959 aTitle = _('saving document') 960 ) 961 return False 962 963 # update business object with metadata 964 # - date of generation 965 new_doc['clin_when'] = self._PhWheel_doc_date.GetData().get_pydt() 966 # - external reference 967 ref = gmDocuments.get_ext_ref() 968 if ref is not None: 969 new_doc['ext_ref'] = ref 970 # - comment 971 comment = self._PRW_doc_comment.GetLineText(0).strip() 972 if comment != u'': 973 new_doc['comment'] = comment 974 # - save it 975 if not new_doc.save_payload(): 976 wx.EndBusyCursor() 977 gmGuiHelpers.gm_show_error ( 978 aMessage = _('Cannot update document metadata.'), 979 aTitle = _('saving document') 980 ) 981 return False 982 # - long description 983 description = self._TBOX_description.GetValue().strip() 984 if description != '': 985 if not new_doc.add_description(description): 986 wx.EndBusyCursor() 987 gmGuiHelpers.gm_show_error ( 988 aMessage = _('Cannot add document description.'), 989 aTitle = _('saving document') 990 ) 991 return False 992 993 # add document parts from files 994 success, msg, filename = new_doc.add_parts_from_files ( 995 files = self.acquired_pages, 996 reviewer = self._PhWheel_reviewer.GetData() 997 ) 998 if not success: 999 wx.EndBusyCursor() 1000 gmGuiHelpers.gm_show_error ( 1001 aMessage = msg, 1002 aTitle = _('saving document') 1003 ) 1004 return False 1005 1006 # set reviewed status 1007 if self._ChBOX_reviewed.GetValue(): 1008 if not new_doc.set_reviewed ( 1009 technically_abnormal = self._ChBOX_abnormal.GetValue(), 1010 clinically_relevant = self._ChBOX_relevant.GetValue() 1011 ): 1012 msg = _('Error setting "reviewed" status of new document.') 1013 1014 gmHooks.run_hook_script(hook = u'after_new_doc_created') 1015 1016 # inform user 1017 cfg = gmCfg.cCfgSQL() 1018 show_id = bool ( 1019 cfg.get2 ( 1020 option = 'horstspace.scan_index.show_doc_id', 1021 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1022 bias = 'user' 1023 ) 1024 ) 1025 wx.EndBusyCursor() 1026 if show_id and (ref is not None): 1027 msg = _( 1028 """The reference ID for the new document is: 1029 1030 <%s> 1031 1032 You probably want to write it down on the 1033 original documents. 1034 1035 If you don't care about the ID you can switch 1036 off this message in the GNUmed configuration.""") % ref 1037 gmGuiHelpers.gm_show_info ( 1038 aMessage = msg, 1039 aTitle = _('saving document') 1040 ) 1041 else: 1042 gmDispatcher.send(signal='statustext', msg=_('Successfully saved new document.')) 1043 1044 self.__init_ui_data() 1045 return True
1046 #--------------------------------------------------------
1047 - def _startover_btn_pressed(self, evt):
1048 self.__init_ui_data()
1049 #--------------------------------------------------------
1050 - def _reviewed_box_checked(self, evt):
1051 self._ChBOX_abnormal.Enable(enable = self._ChBOX_reviewed.GetValue()) 1052 self._ChBOX_relevant.Enable(enable = self._ChBOX_reviewed.GetValue())
1053 #--------------------------------------------------------
1054 - def _on_doc_type_loses_focus(self):
1055 pk_doc_type = self._PhWheel_doc_type.GetData() 1056 if pk_doc_type is None: 1057 self._PRW_doc_comment.unset_context(context = 'pk_doc_type') 1058 else: 1059 self._PRW_doc_comment.set_context(context = 'pk_doc_type', val = pk_doc_type) 1060 return True
1061 #============================================================
1062 -class cSelectablySortedDocTreePnl(wxgSelectablySortedDocTreePnl.wxgSelectablySortedDocTreePnl):
1063 """A panel with a document tree which can be sorted.""" 1064 #-------------------------------------------------------- 1065 # inherited event handlers 1066 #--------------------------------------------------------
1067 - def _on_sort_by_age_selected(self, evt):
1068 self._doc_tree.sort_mode = 'age' 1069 self._doc_tree.SetFocus() 1070 self._rbtn_sort_by_age.SetValue(True)
1071 #--------------------------------------------------------
1072 - def _on_sort_by_review_selected(self, evt):
1073 self._doc_tree.sort_mode = 'review' 1074 self._doc_tree.SetFocus() 1075 self._rbtn_sort_by_review.SetValue(True)
1076 #--------------------------------------------------------
1077 - def _on_sort_by_episode_selected(self, evt):
1078 self._doc_tree.sort_mode = 'episode' 1079 self._doc_tree.SetFocus() 1080 self._rbtn_sort_by_episode.SetValue(True)
1081 #--------------------------------------------------------
1082 - def _on_sort_by_type_selected(self, evt):
1083 self._doc_tree.sort_mode = 'type' 1084 self._doc_tree.SetFocus() 1085 self._rbtn_sort_by_type.SetValue(True)
1086 #============================================================
1087 -class cDocTree(wx.TreeCtrl, gmRegetMixin.cRegetOnPaintMixin):
1088 # FIXME: handle expansion state 1089 """This wx.TreeCtrl derivative displays a tree view of stored medical documents. 1090 1091 It listens to document and patient changes and updated itself accordingly. 1092 """ 1093 _sort_modes = ['age', 'review', 'episode', 'type'] 1094 _root_node_labels = None 1095 #--------------------------------------------------------
1096 - def __init__(self, parent, id, *args, **kwds):
1097 """Set up our specialised tree. 1098 """ 1099 kwds['style'] = wx.TR_NO_BUTTONS | wx.NO_BORDER 1100 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds) 1101 1102 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1103 1104 tmp = _('available documents (%s)') 1105 unsigned = _('unsigned (%s) on top') % u'\u270D' 1106 cDocTree._root_node_labels = { 1107 'age': tmp % _('most recent on top'), 1108 'review': tmp % unsigned, 1109 'episode': tmp % _('sorted by episode'), 1110 'type': tmp % _('sorted by type') 1111 } 1112 1113 self.root = None 1114 self.__sort_mode = 'age' 1115 1116 self.__build_context_menus() 1117 self.__register_interests() 1118 self._schedule_data_reget()
1119 #-------------------------------------------------------- 1120 # external API 1121 #--------------------------------------------------------
1122 - def display_selected_part(self, *args, **kwargs):
1123 1124 node = self.GetSelection() 1125 node_data = self.GetPyData(node) 1126 1127 if not isinstance(node_data, gmDocuments.cMedDocPart): 1128 return True 1129 1130 self.__display_part(part = node_data) 1131 return True
1132 #-------------------------------------------------------- 1133 # properties 1134 #--------------------------------------------------------
1135 - def _get_sort_mode(self):
1136 return self.__sort_mode
1137 #-----
1138 - def _set_sort_mode(self, mode):
1139 if mode is None: 1140 mode = 'age' 1141 1142 if mode == self.__sort_mode: 1143 return 1144 1145 if mode not in cDocTree._sort_modes: 1146 raise ValueError('invalid document tree sort mode [%s], valid modes: %s' % (mode, cDocTree._sort_modes)) 1147 1148 self.__sort_mode = mode 1149 1150 curr_pat = gmPerson.gmCurrentPatient() 1151 if not curr_pat.connected: 1152 return 1153 1154 self._schedule_data_reget()
1155 #----- 1156 sort_mode = property(_get_sort_mode, _set_sort_mode) 1157 #-------------------------------------------------------- 1158 # reget-on-paint API 1159 #--------------------------------------------------------
1160 - def _populate_with_data(self):
1161 curr_pat = gmPerson.gmCurrentPatient() 1162 if not curr_pat.connected: 1163 gmDispatcher.send(signal = 'statustext', msg = _('Cannot load documents. No active patient.')) 1164 return False 1165 1166 if not self.__populate_tree(): 1167 return False 1168 1169 return True
1170 #-------------------------------------------------------- 1171 # internal helpers 1172 #--------------------------------------------------------
1173 - def __register_interests(self):
1174 # connect handlers 1175 wx.EVT_TREE_ITEM_ACTIVATED (self, self.GetId(), self._on_activate) 1176 wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self.__on_right_click) 1177 1178 # wx.EVT_LEFT_DCLICK(self.tree, self.OnLeftDClick) 1179 1180 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 1181 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 1182 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_doc_mod_db) 1183 gmDispatcher.connect(signal = u'doc_page_mod_db', receiver = self._on_doc_page_mod_db)
1184 #--------------------------------------------------------
1185 - def __build_context_menus(self):
1186 1187 # --- part context menu --- 1188 self.__part_context_menu = wx.Menu(title = _('part menu')) 1189 1190 ID = wx.NewId() 1191 self.__part_context_menu.Append(ID, _('Display part')) 1192 wx.EVT_MENU(self.__part_context_menu, ID, self.__display_curr_part) 1193 1194 ID = wx.NewId() 1195 self.__part_context_menu.Append(ID, _('%s Sign/Edit properties') % u'\u270D') 1196 wx.EVT_MENU(self.__part_context_menu, ID, self.__review_curr_part) 1197 1198 self.__part_context_menu.AppendSeparator() 1199 1200 ID = wx.NewId() 1201 self.__part_context_menu.Append(ID, _('Print part')) 1202 wx.EVT_MENU(self.__part_context_menu, ID, self.__print_part) 1203 1204 ID = wx.NewId() 1205 self.__part_context_menu.Append(ID, _('Fax part')) 1206 wx.EVT_MENU(self.__part_context_menu, ID, self.__fax_part) 1207 1208 ID = wx.NewId() 1209 self.__part_context_menu.Append(ID, _('Mail part')) 1210 wx.EVT_MENU(self.__part_context_menu, ID, self.__mail_part) 1211 1212 self.__part_context_menu.AppendSeparator() # so we can append some items 1213 1214 # --- doc context menu --- 1215 self.__doc_context_menu = wx.Menu(title = _('document menu')) 1216 1217 ID = wx.NewId() 1218 self.__doc_context_menu.Append(ID, _('%s Sign/Edit properties') % u'\u270D') 1219 wx.EVT_MENU(self.__doc_context_menu, ID, self.__review_curr_part) 1220 1221 self.__doc_context_menu.AppendSeparator() 1222 1223 ID = wx.NewId() 1224 self.__doc_context_menu.Append(ID, _('Print all parts')) 1225 wx.EVT_MENU(self.__doc_context_menu, ID, self.__print_doc) 1226 1227 ID = wx.NewId() 1228 self.__doc_context_menu.Append(ID, _('Fax all parts')) 1229 wx.EVT_MENU(self.__doc_context_menu, ID, self.__fax_doc) 1230 1231 ID = wx.NewId() 1232 self.__doc_context_menu.Append(ID, _('Mail all parts')) 1233 wx.EVT_MENU(self.__doc_context_menu, ID, self.__mail_doc) 1234 1235 ID = wx.NewId() 1236 self.__doc_context_menu.Append(ID, _('Export all parts')) 1237 wx.EVT_MENU(self.__doc_context_menu, ID, self.__export_doc_to_disk) 1238 1239 self.__doc_context_menu.AppendSeparator() 1240 1241 ID = wx.NewId() 1242 self.__doc_context_menu.Append(ID, _('Delete document')) 1243 wx.EVT_MENU(self.__doc_context_menu, ID, self.__delete_document) 1244 1245 ID = wx.NewId() 1246 self.__doc_context_menu.Append(ID, _('Access external original')) 1247 wx.EVT_MENU(self.__doc_context_menu, ID, self.__access_external_original) 1248 1249 ID = wx.NewId() 1250 self.__doc_context_menu.Append(ID, _('Edit corresponding encounter')) 1251 wx.EVT_MENU(self.__doc_context_menu, ID, self.__edit_encounter_details) 1252 1253 1254 # self.__doc_context_menu.AppendSeparator() 1255 1256 ID = wx.NewId() 1257 self.__doc_context_menu.Append(ID, _('Manage descriptions')) 1258 wx.EVT_MENU(self.__doc_context_menu, ID, self.__manage_document_descriptions)
1259 1260 # document / description 1261 # self.__desc_menu = wx.Menu() 1262 # ID = wx.NewId() 1263 # self.__doc_context_menu.AppendMenu(ID, _('Descriptions ...'), self.__desc_menu) 1264 1265 # ID = wx.NewId() 1266 # self.__desc_menu.Append(ID, _('Add new description')) 1267 # wx.EVT_MENU(self.__desc_menu, ID, self.__add_doc_desc) 1268 1269 # ID = wx.NewId() 1270 # self.__desc_menu.Append(ID, _('Delete description')) 1271 # wx.EVT_MENU(self.__desc_menu, ID, self.__del_doc_desc) 1272 1273 # self.__desc_menu.AppendSeparator() 1274 #--------------------------------------------------------
1275 - def __populate_tree(self):
1276 1277 wx.BeginBusyCursor() 1278 1279 # clean old tree 1280 if self.root is not None: 1281 self.DeleteAllItems() 1282 1283 # init new tree 1284 self.root = self.AddRoot(cDocTree._root_node_labels[self.__sort_mode], -1, -1) 1285 self.SetPyData(self.root, None) 1286 self.SetItemHasChildren(self.root, False) 1287 1288 # read documents from database 1289 curr_pat = gmPerson.gmCurrentPatient() 1290 docs_folder = curr_pat.get_document_folder() 1291 docs = docs_folder.get_documents() 1292 1293 if docs is None: 1294 gmGuiHelpers.gm_show_error ( 1295 aMessage = _('Error searching documents.'), 1296 aTitle = _('loading document list') 1297 ) 1298 # avoid recursion of GUI updating 1299 wx.EndBusyCursor() 1300 return True 1301 1302 if len(docs) == 0: 1303 wx.EndBusyCursor() 1304 return True 1305 1306 # fill new tree from document list 1307 self.SetItemHasChildren(self.root, True) 1308 1309 # add our documents as first level nodes 1310 intermediate_nodes = {} 1311 for doc in docs: 1312 1313 parts = doc.parts 1314 1315 label = _('%s%7s %s:%s (%s part(s)%s)') % ( 1316 gmTools.bool2subst(doc.has_unreviewed_parts, gmTools.u_writing_hand, u'', u'?'), 1317 doc['clin_when'].strftime('%m/%Y'), 1318 doc['l10n_type'][:26], 1319 gmTools.coalesce(initial = doc['comment'], instead = u'', template_initial = u' %s'), 1320 len(parts), 1321 gmTools.coalesce(initial = doc['ext_ref'], instead = u'', template_initial = u', \u00BB%s\u00AB') 1322 ) 1323 1324 # need intermediate branch level ? 1325 if self.__sort_mode == 'episode': 1326 lbl = doc['episode'] # it'd be nice to also show the issue but we don't have that 1327 if not intermediate_nodes.has_key(lbl): 1328 intermediate_nodes[lbl] = self.AppendItem(parent = self.root, text = lbl) 1329 self.SetItemBold(intermediate_nodes[lbl], bold = True) 1330 self.SetPyData(intermediate_nodes[lbl], None) 1331 parent = intermediate_nodes[lbl] 1332 elif self.__sort_mode == 'type': 1333 if not intermediate_nodes.has_key(doc['l10n_type']): 1334 intermediate_nodes[doc['l10n_type']] = self.AppendItem(parent = self.root, text = doc['l10n_type']) 1335 self.SetItemBold(intermediate_nodes[doc['l10n_type']], bold = True) 1336 self.SetPyData(intermediate_nodes[doc['l10n_type']], None) 1337 parent = intermediate_nodes[doc['l10n_type']] 1338 else: 1339 parent = self.root 1340 1341 doc_node = self.AppendItem(parent = parent, text = label) 1342 #self.SetItemBold(doc_node, bold = True) 1343 self.SetPyData(doc_node, doc) 1344 if len(parts) > 0: 1345 self.SetItemHasChildren(doc_node, True) 1346 1347 # now add parts as child nodes 1348 for part in parts: 1349 # if part['clinically_relevant']: 1350 # rel = ' [%s]' % _('Cave') 1351 # else: 1352 # rel = '' 1353 label = '%s%s (%s)%s' % ( 1354 gmTools.bool2str ( 1355 boolean = part['reviewed'] or part['reviewed_by_you'] or part['reviewed_by_intended_reviewer'], 1356 true_str = u'', 1357 false_str = gmTools.u_writing_hand 1358 ), 1359 _('part %2s') % part['seq_idx'], 1360 gmTools.size2str(part['size']), 1361 gmTools.coalesce ( 1362 part['obj_comment'], 1363 u'', 1364 u': %s%%s%s' % (gmTools.u_left_double_angle_quote, gmTools.u_right_double_angle_quote) 1365 ) 1366 ) 1367 1368 part_node = self.AppendItem(parent = doc_node, text = label) 1369 self.SetPyData(part_node, part) 1370 1371 self.__sort_nodes() 1372 self.SelectItem(self.root) 1373 1374 # FIXME: apply expansion state if available or else ... 1375 # FIXME: ... uncollapse to default state 1376 self.Expand(self.root) 1377 if self.__sort_mode in ['episode', 'type']: 1378 for key in intermediate_nodes.keys(): 1379 self.Expand(intermediate_nodes[key]) 1380 1381 wx.EndBusyCursor() 1382 1383 return True
1384 #------------------------------------------------------------------------
1385 - def OnCompareItems (self, node1=None, node2=None):
1386 """Used in sorting items. 1387 1388 -1: 1 < 2 1389 0: 1 = 2 1390 1: 1 > 2 1391 """ 1392 # Windows can send bogus events so ignore that 1393 if not node1.IsOk(): 1394 _log.debug('no data on node 1') 1395 return 0 1396 if not node2.IsOk(): 1397 _log.debug('no data on node 2') 1398 return 0 1399 1400 data1 = self.GetPyData(node1) 1401 data2 = self.GetPyData(node2) 1402 1403 # doc node 1404 if isinstance(data1, gmDocuments.cMedDoc): 1405 1406 date_field = 'clin_when' 1407 #date_field = 'modified_when' 1408 1409 if self.__sort_mode == 'age': 1410 # reverse sort by date 1411 if data1[date_field] > data2[date_field]: 1412 return -1 1413 if data1[date_field] == data2[date_field]: 1414 return 0 1415 return 1 1416 1417 elif self.__sort_mode == 'episode': 1418 if data1['episode'] < data2['episode']: 1419 return -1 1420 if data1['episode'] == data2['episode']: 1421 # inner sort: reverse by date 1422 if data1[date_field] > data2[date_field]: 1423 return -1 1424 if data1[date_field] == data2[date_field]: 1425 return 0 1426 return 1 1427 return 1 1428 1429 elif self.__sort_mode == 'review': 1430 # equality 1431 if data1.has_unreviewed_parts == data2.has_unreviewed_parts: 1432 # inner sort: reverse by date 1433 if data1[date_field] > data2[date_field]: 1434 return -1 1435 if data1[date_field] == data2[date_field]: 1436 return 0 1437 return 1 1438 if data1.has_unreviewed_parts: 1439 return -1 1440 return 1 1441 1442 elif self.__sort_mode == 'type': 1443 if data1['l10n_type'] < data2['l10n_type']: 1444 return -1 1445 if data1['l10n_type'] == data2['l10n_type']: 1446 # inner sort: reverse by date 1447 if data1[date_field] > data2[date_field]: 1448 return -1 1449 if data1[date_field] == data2[date_field]: 1450 return 0 1451 return 1 1452 return 1 1453 1454 else: 1455 _log.error('unknown document sort mode [%s], reverse-sorting by age', self.__sort_mode) 1456 # reverse sort by date 1457 if data1[date_field] > data2[date_field]: 1458 return -1 1459 if data1[date_field] == data2[date_field]: 1460 return 0 1461 return 1 1462 1463 # part node 1464 if isinstance(data1, gmDocuments.cMedDocPart): 1465 # compare sequence IDs (= "page" numbers) 1466 # FIXME: wrong order ? 1467 if data1['seq_idx'] < data2['seq_idx']: 1468 return -1 1469 if data1['seq_idx'] == data2['seq_idx']: 1470 return 0 1471 return 1 1472 1473 # else sort alphabetically 1474 if None in [data1, data2]: 1475 l1 = self.GetItemText(node1) 1476 l2 = self.GetItemText(node2) 1477 if l1 < l2: 1478 return -1 1479 if l1 == l2: 1480 return 0 1481 else: 1482 if data1 < data2: 1483 return -1 1484 if data1 == data2: 1485 return 0 1486 return 1
1487 #------------------------------------------------------------------------ 1488 # event handlers 1489 #------------------------------------------------------------------------
1490 - def _on_doc_mod_db(self, *args, **kwargs):
1491 # FIXME: remember current expansion state 1492 wx.CallAfter(self._schedule_data_reget)
1493 #------------------------------------------------------------------------
1494 - def _on_doc_page_mod_db(self, *args, **kwargs):
1495 # FIXME: remember current expansion state 1496 wx.CallAfter(self._schedule_data_reget)
1497 #------------------------------------------------------------------------
1498 - def _on_pre_patient_selection(self, *args, **kwargs):
1499 # FIXME: self.__store_expansion_history_in_db 1500 1501 # empty out tree 1502 if self.root is not None: 1503 self.DeleteAllItems() 1504 self.root = None
1505 #------------------------------------------------------------------------
1506 - def _on_post_patient_selection(self, *args, **kwargs):
1507 # FIXME: self.__load_expansion_history_from_db (but not apply it !) 1508 self._schedule_data_reget()
1509 #------------------------------------------------------------------------
1510 - def _on_activate(self, event):
1511 node = event.GetItem() 1512 node_data = self.GetPyData(node) 1513 1514 # exclude pseudo root node 1515 if node_data is None: 1516 return None 1517 1518 # expand/collapse documents on activation 1519 if isinstance(node_data, gmDocuments.cMedDoc): 1520 self.Toggle(node) 1521 return True 1522 1523 # string nodes are labels such as episodes which may or may not have children 1524 if type(node_data) == type('string'): 1525 self.Toggle(node) 1526 return True 1527 1528 self.__display_part(part = node_data) 1529 return True
1530 #--------------------------------------------------------
1531 - def __on_right_click(self, evt):
1532 1533 node = evt.GetItem() 1534 self.__curr_node_data = self.GetPyData(node) 1535 1536 # exclude pseudo root node 1537 if self.__curr_node_data is None: 1538 return None 1539 1540 # documents 1541 if isinstance(self.__curr_node_data, gmDocuments.cMedDoc): 1542 self.__handle_doc_context() 1543 1544 # parts 1545 if isinstance(self.__curr_node_data, gmDocuments.cMedDocPart): 1546 self.__handle_part_context() 1547 1548 del self.__curr_node_data 1549 evt.Skip()
1550 #--------------------------------------------------------
1551 - def __activate_as_current_photo(self, evt):
1552 self.__curr_node_data.set_as_active_photograph()
1553 #--------------------------------------------------------
1554 - def __display_curr_part(self, evt):
1555 self.__display_part(part = self.__curr_node_data)
1556 #--------------------------------------------------------
1557 - def __review_curr_part(self, evt):
1558 self.__review_part(part = self.__curr_node_data)
1559 #--------------------------------------------------------
1560 - def __manage_document_descriptions(self, evt):
1561 manage_document_descriptions(parent = self, document = self.__curr_node_data)
1562 #-------------------------------------------------------- 1563 # internal API 1564 #--------------------------------------------------------
1565 - def __sort_nodes(self, start_node=None):
1566 1567 if start_node is None: 1568 start_node = self.GetRootItem() 1569 1570 # protect against empty tree where not even 1571 # a root node exists 1572 if not start_node.IsOk(): 1573 return True 1574 1575 self.SortChildren(start_node) 1576 1577 child_node, cookie = self.GetFirstChild(start_node) 1578 while child_node.IsOk(): 1579 self.__sort_nodes(start_node = child_node) 1580 child_node, cookie = self.GetNextChild(start_node, cookie) 1581 1582 return
1583 #--------------------------------------------------------
1584 - def __handle_doc_context(self):
1585 self.PopupMenu(self.__doc_context_menu, wx.DefaultPosition)
1586 #--------------------------------------------------------
1587 - def __handle_part_context(self):
1588 1589 # make active patient photograph 1590 if self.__curr_node_data['type'] == 'patient photograph': 1591 ID = wx.NewId() 1592 self.__part_context_menu.Append(ID, _('Activate as current photo')) 1593 wx.EVT_MENU(self.__part_context_menu, ID, self.__activate_as_current_photo) 1594 else: 1595 ID = None 1596 1597 self.PopupMenu(self.__part_context_menu, wx.DefaultPosition) 1598 1599 if ID is not None: 1600 self.__part_context_menu.Delete(ID)
1601 #-------------------------------------------------------- 1602 # part level context menu handlers 1603 #--------------------------------------------------------
1604 - def __display_part(self, part):
1605 """Display document part.""" 1606 1607 # sanity check 1608 if part['size'] == 0: 1609 _log.debug('cannot display part [%s] - 0 bytes', part['pk_obj']) 1610 gmGuiHelpers.gm_show_error ( 1611 aMessage = _('Document part does not seem to exist in database !'), 1612 aTitle = _('showing document') 1613 ) 1614 return None 1615 1616 wx.BeginBusyCursor() 1617 1618 cfg = gmCfg.cCfgSQL() 1619 1620 # get export directory for temporary files 1621 tmp_dir = gmTools.coalesce ( 1622 cfg.get2 ( 1623 option = "horstspace.tmp_dir", 1624 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1625 bias = 'workplace' 1626 ), 1627 os.path.expanduser(os.path.join('~', '.gnumed', 'tmp')) 1628 ) 1629 _log.debug("temporary directory [%s]", tmp_dir) 1630 1631 # determine database export chunk size 1632 chunksize = int( 1633 cfg.get2 ( 1634 option = "horstspace.blob_export_chunk_size", 1635 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1636 bias = 'workplace', 1637 default = default_chunksize 1638 )) 1639 1640 # shall we force blocking during view ? 1641 block_during_view = bool( cfg.get2 ( 1642 option = 'horstspace.document_viewer.block_during_view', 1643 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1644 bias = 'user', 1645 default = None 1646 )) 1647 1648 # display it 1649 successful, msg = part.display_via_mime ( 1650 tmpdir = tmp_dir, 1651 chunksize = chunksize, 1652 block = block_during_view 1653 ) 1654 1655 wx.EndBusyCursor() 1656 1657 if not successful: 1658 gmGuiHelpers.gm_show_error ( 1659 aMessage = _('Cannot display document part:\n%s') % msg, 1660 aTitle = _('showing document') 1661 ) 1662 return None 1663 1664 # handle review after display 1665 # 0: never 1666 # 1: always 1667 # 2: if no review by myself exists yet 1668 review_after_display = int(cfg.get2 ( 1669 option = 'horstspace.document_viewer.review_after_display', 1670 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1671 bias = 'user', 1672 default = 2 1673 )) 1674 if review_after_display == 1: # always review 1675 self.__review_part(part=part) 1676 elif review_after_display == 2: # review if no review by me exists 1677 review_by_me = filter(lambda rev: rev['is_your_review'], part.get_reviews()) 1678 if len(review_by_me) == 0: 1679 self.__review_part(part=part) 1680 1681 return True
1682 #--------------------------------------------------------
1683 - def __review_part(self, part=None):
1684 dlg = cReviewDocPartDlg ( 1685 parent = self, 1686 id = -1, 1687 part = part 1688 ) 1689 dlg.ShowModal() 1690 dlg.Destroy()
1691 #--------------------------------------------------------
1692 - def __process_part(self, action=None, l10n_action=None):
1693 1694 gmHooks.run_hook_script(hook = u'before_%s_doc_part' % action) 1695 1696 wx.BeginBusyCursor() 1697 1698 # detect wrapper 1699 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc' % action) 1700 if not found: 1701 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc.bat' % action) 1702 if not found: 1703 _log.error('neither of gm-%s_doc or gm-%s_doc.bat found', action, action) 1704 wx.EndBusyCursor() 1705 gmGuiHelpers.gm_show_error ( 1706 _('Cannot %(l10n_action)s document part - %(l10n_action)s command not found.\n' 1707 '\n' 1708 'Either of gm_%(action)s_doc.sh or gm_%(action)s_doc.bat\n' 1709 'must be in the execution path. The command will\n' 1710 'be passed the filename to %(l10n_action)s.' 1711 ) % {'action': action, 'l10n_action': l10n_action}, 1712 _('Processing document part: %s') % l10n_action 1713 ) 1714 return 1715 1716 cfg = gmCfg.cCfgSQL() 1717 1718 # get export directory for temporary files 1719 tmp_dir = gmTools.coalesce ( 1720 cfg.get2 ( 1721 option = "horstspace.tmp_dir", 1722 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1723 bias = 'workplace' 1724 ), 1725 os.path.expanduser(os.path.join('~', '.gnumed', 'tmp')) 1726 ) 1727 _log.debug("temporary directory [%s]", tmp_dir) 1728 1729 # determine database export chunk size 1730 chunksize = int(cfg.get2 ( 1731 option = "horstspace.blob_export_chunk_size", 1732 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1733 bias = 'workplace', 1734 default = default_chunksize 1735 )) 1736 1737 part_file = self.__curr_node_data.export_to_file ( 1738 aTempDir = tmp_dir, 1739 aChunkSize = chunksize 1740 ) 1741 1742 cmd = u'%s %s' % (external_cmd, part_file) 1743 success = gmShellAPI.run_command_in_shell ( 1744 command = cmd, 1745 blocking = False 1746 ) 1747 1748 wx.EndBusyCursor() 1749 1750 if not success: 1751 _log.error('%s command failed: [%s]', action, cmd) 1752 gmGuiHelpers.gm_show_error ( 1753 _('Cannot %(l10n_action)s document part - %(l10n_action)s command failed.\n' 1754 '\n' 1755 'You may need to check and fix either of\n' 1756 ' gm_%(action)s_doc.sh (Unix/Mac) or\n' 1757 ' gm_%(action)s_doc.bat (Windows)\n' 1758 '\n' 1759 'The command is passed the filename to %(l10n_action)s.' 1760 ) % {'action': action, 'l10n_action': l10n_action}, 1761 _('Processing document part: %s') % l10n_action 1762 )
1763 #-------------------------------------------------------- 1764 # FIXME: icons in the plugin toolbar
1765 - def __print_part(self, evt):
1766 self.__process_part(action = u'print', l10n_action = _('print'))
1767 #--------------------------------------------------------
1768 - def __fax_part(self, evt):
1769 self.__process_part(action = u'fax', l10n_action = _('fax'))
1770 #--------------------------------------------------------
1771 - def __mail_part(self, evt):
1772 self.__process_part(action = u'mail', l10n_action = _('mail'))
1773 #-------------------------------------------------------- 1774 # document level context menu handlers 1775 #--------------------------------------------------------
1776 - def __edit_encounter_details(self, evt):
1777 enc = gmEMRStructItems.cEncounter(aPK_obj=self.__curr_node_data['pk_encounter']) 1778 dlg = gmEMRStructWidgets.cEncounterEditAreaDlg(parent=self, encounter=enc) 1779 dlg.ShowModal()
1780 #--------------------------------------------------------
1781 - def __process_doc(self, action=None, l10n_action=None):
1782 1783 gmHooks.run_hook_script(hook = u'before_%s_doc' % action) 1784 1785 wx.BeginBusyCursor() 1786 1787 # detect wrapper 1788 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc' % action) 1789 if not found: 1790 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc.bat' % action) 1791 if not found: 1792 _log.error('neither of gm-%s_doc or gm-%s_doc.bat found', action, action) 1793 wx.EndBusyCursor() 1794 gmGuiHelpers.gm_show_error ( 1795 _('Cannot %(l10n_action)s document - %(l10n_action)s command not found.\n' 1796 '\n' 1797 'Either of gm_%(action)s_doc.sh or gm_%(action)s_doc.bat\n' 1798 'must be in the execution path. The command will\n' 1799 'be passed a list of filenames to %(l10n_action)s.' 1800 ) % {'action': action, 'l10n_action': l10n_action}, 1801 _('Processing document: %s') % l10n_action 1802 ) 1803 return 1804 1805 cfg = gmCfg.cCfgSQL() 1806 1807 # get export directory for temporary files 1808 tmp_dir = gmTools.coalesce ( 1809 cfg.get2 ( 1810 option = "horstspace.tmp_dir", 1811 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1812 bias = 'workplace' 1813 ), 1814 os.path.expanduser(os.path.join('~', '.gnumed', 'tmp')) 1815 ) 1816 _log.debug("temporary directory [%s]", tmp_dir) 1817 1818 # determine database export chunk size 1819 chunksize = int(cfg.get2 ( 1820 option = "horstspace.blob_export_chunk_size", 1821 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1822 bias = 'workplace', 1823 default = default_chunksize 1824 )) 1825 1826 part_files = self.__curr_node_data.export_parts_to_files ( 1827 export_dir = tmp_dir, 1828 chunksize = chunksize 1829 ) 1830 1831 cmd = external_cmd + u' ' + u' '.join(part_files) 1832 success = gmShellAPI.run_command_in_shell ( 1833 command = cmd, 1834 blocking = False 1835 ) 1836 1837 wx.EndBusyCursor() 1838 1839 if not success: 1840 _log.error('%s command failed: [%s]', action, cmd) 1841 gmGuiHelpers.gm_show_error ( 1842 _('Cannot %(l10n_action)s document - %(l10n_action)s command failed.\n' 1843 '\n' 1844 'You may need to check and fix either of\n' 1845 ' gm_%(action)s_doc.sh (Unix/Mac) or\n' 1846 ' gm_%(action)s_doc.bat (Windows)\n' 1847 '\n' 1848 'The command is passed a list of filenames to %(l10n_action)s.' 1849 ) % {'action': action, 'l10n_action': l10n_action}, 1850 _('Processing document: %s') % l10n_action 1851 )
1852 #-------------------------------------------------------- 1853 # FIXME: icons in the plugin toolbar
1854 - def __print_doc(self, evt):
1855 self.__process_doc(action = u'print', l10n_action = _('print'))
1856 #--------------------------------------------------------
1857 - def __fax_doc(self, evt):
1858 self.__process_doc(action = u'fax', l10n_action = _('fax'))
1859 #--------------------------------------------------------
1860 - def __mail_doc(self, evt):
1861 self.__process_doc(action = u'mail', l10n_action = _('mail'))
1862 #--------------------------------------------------------
1863 - def __access_external_original(self, evt):
1864 1865 gmHooks.run_hook_script(hook = u'before_external_doc_access') 1866 1867 wx.BeginBusyCursor() 1868 1869 # detect wrapper 1870 found, external_cmd = gmShellAPI.detect_external_binary(u'gm_access_external_doc.sh') 1871 if not found: 1872 found, external_cmd = gmShellAPI.detect_external_binary(u'gm_access_external_doc.bat') 1873 if not found: 1874 _log.error('neither of gm_access_external_doc.sh or .bat found') 1875 wx.EndBusyCursor() 1876 gmGuiHelpers.gm_show_error ( 1877 _('Cannot access external document - access command not found.\n' 1878 '\n' 1879 'Either of gm_access_external_doc.sh or *.bat must be\n' 1880 'in the execution path. The command will be passed the\n' 1881 'document type and the reference URL for processing.' 1882 ), 1883 _('Accessing external document') 1884 ) 1885 return 1886 1887 cmd = u'%s "%s" "%s"' % (external_cmd, self.__curr_node_data['type'], self.__curr_node_data['ext_ref']) 1888 success = gmShellAPI.run_command_in_shell ( 1889 command = cmd, 1890 blocking = False 1891 ) 1892 1893 wx.EndBusyCursor() 1894 1895 if not success: 1896 _log.error('External access command failed: [%s]', cmd) 1897 gmGuiHelpers.gm_show_error ( 1898 _('Cannot access external document - access command failed.\n' 1899 '\n' 1900 'You may need to check and fix either of\n' 1901 ' gm_access_external_doc.sh (Unix/Mac) or\n' 1902 ' gm_access_external_doc.bat (Windows)\n' 1903 '\n' 1904 'The command is passed the document type and the\n' 1905 'external reference URL on the command line.' 1906 ), 1907 _('Accessing external document') 1908 )
1909 #--------------------------------------------------------
1910 - def __export_doc_to_disk(self, evt):
1911 """Export document into directory. 1912 1913 - one file per object 1914 - into subdirectory named after patient 1915 """ 1916 pat = gmPerson.gmCurrentPatient() 1917 dname = '%s-%s%s' % ( 1918 self.__curr_node_data['l10n_type'], 1919 self.__curr_node_data['clin_when'].strftime('%Y-%m-%d'), 1920 gmTools.coalesce(self.__curr_node_data['ext_ref'], '', '-%s').replace(' ', '_') 1921 ) 1922 def_dir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'docs', pat['dirname'], dname)) 1923 gmTools.mkdir(def_dir) 1924 1925 dlg = wx.DirDialog ( 1926 parent = self, 1927 message = _('Save document into directory ...'), 1928 defaultPath = def_dir, 1929 style = wx.DD_DEFAULT_STYLE 1930 ) 1931 result = dlg.ShowModal() 1932 dirname = dlg.GetPath() 1933 dlg.Destroy() 1934 1935 if result != wx.ID_OK: 1936 return True 1937 1938 wx.BeginBusyCursor() 1939 1940 cfg = gmCfg.cCfgSQL() 1941 1942 # determine database export chunk size 1943 chunksize = int(cfg.get2 ( 1944 option = "horstspace.blob_export_chunk_size", 1945 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1946 bias = 'workplace', 1947 default = default_chunksize 1948 )) 1949 1950 fnames = self.__curr_node_data.export_parts_to_files(export_dir = dirname, chunksize = chunksize) 1951 1952 wx.EndBusyCursor() 1953 1954 gmDispatcher.send(signal='statustext', msg=_('Successfully exported %s parts into the directory [%s].') % (len(fnames), dirname)) 1955 1956 return True
1957 #--------------------------------------------------------
1958 - def __delete_document(self, evt):
1959 result = gmGuiHelpers.gm_show_question ( 1960 aMessage = _('Are you sure you want to delete the document ?'), 1961 aTitle = _('Deleting document') 1962 ) 1963 if result is True: 1964 curr_pat = gmPerson.gmCurrentPatient() 1965 emr = curr_pat.get_emr() 1966 enc = emr.active_encounter 1967 gmDocuments.delete_document(document_id = self.__curr_node_data['pk_doc'], encounter_id = enc['pk_encounter'])
1968 #============================================================ 1969 # main 1970 #------------------------------------------------------------ 1971 if __name__ == '__main__': 1972 1973 gmI18N.activate_locale() 1974 gmI18N.install_domain(domain = 'gnumed') 1975 1976 #---------------------------------------- 1977 #---------------------------------------- 1978 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'): 1979 # test_*() 1980 pass 1981 1982 #============================================================ 1983