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