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.cDocumentPart): 398 self.__part = part 399 self.__doc = self.__part.get_containing_document() 400 self.__reviewing_doc = False 401 elif isinstance(part, gmDocuments.cDocument): 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.cDocument or gmDocuments.cDocumentPart 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 This acts on the current patient. 1110 """ 1111 _sort_modes = ['age', 'review', 'episode', 'type'] 1112 _root_node_labels = None 1113 #--------------------------------------------------------
1114 - def __init__(self, parent, id, *args, **kwds):
1115 """Set up our specialised tree. 1116 """ 1117 kwds['style'] = wx.TR_NO_BUTTONS | wx.NO_BORDER 1118 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds) 1119 1120 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1121 1122 tmp = _('available documents (%s)') 1123 unsigned = _('unsigned (%s) on top') % u'\u270D' 1124 cDocTree._root_node_labels = { 1125 'age': tmp % _('most recent on top'), 1126 'review': tmp % unsigned, 1127 'episode': tmp % _('sorted by episode'), 1128 'type': tmp % _('sorted by type') 1129 } 1130 1131 self.root = None 1132 self.__sort_mode = 'age' 1133 1134 self.__build_context_menus() 1135 self.__register_interests() 1136 self._schedule_data_reget()
1137 #-------------------------------------------------------- 1138 # external API 1139 #--------------------------------------------------------
1140 - def display_selected_part(self, *args, **kwargs):
1141 1142 node = self.GetSelection() 1143 node_data = self.GetPyData(node) 1144 1145 if not isinstance(node_data, gmDocuments.cDocumentPart): 1146 return True 1147 1148 self.__display_part(part = node_data) 1149 return True
1150 #-------------------------------------------------------- 1151 # properties 1152 #--------------------------------------------------------
1153 - def _get_sort_mode(self):
1154 return self.__sort_mode
1155 #-----
1156 - def _set_sort_mode(self, mode):
1157 if mode is None: 1158 mode = 'age' 1159 1160 if mode == self.__sort_mode: 1161 return 1162 1163 if mode not in cDocTree._sort_modes: 1164 raise ValueError('invalid document tree sort mode [%s], valid modes: %s' % (mode, cDocTree._sort_modes)) 1165 1166 self.__sort_mode = mode 1167 1168 curr_pat = gmPerson.gmCurrentPatient() 1169 if not curr_pat.connected: 1170 return 1171 1172 self._schedule_data_reget()
1173 #----- 1174 sort_mode = property(_get_sort_mode, _set_sort_mode) 1175 #-------------------------------------------------------- 1176 # reget-on-paint API 1177 #--------------------------------------------------------
1178 - def _populate_with_data(self):
1179 curr_pat = gmPerson.gmCurrentPatient() 1180 if not curr_pat.connected: 1181 gmDispatcher.send(signal = 'statustext', msg = _('Cannot load documents. No active patient.')) 1182 return False 1183 1184 if not self.__populate_tree(): 1185 return False 1186 1187 return True
1188 #-------------------------------------------------------- 1189 # internal helpers 1190 #--------------------------------------------------------
1191 - def __register_interests(self):
1192 # connect handlers 1193 wx.EVT_TREE_ITEM_ACTIVATED (self, self.GetId(), self._on_activate) 1194 wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self.__on_right_click) 1195 1196 # wx.EVT_LEFT_DCLICK(self.tree, self.OnLeftDClick) 1197 1198 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 1199 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 1200 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_doc_mod_db) 1201 gmDispatcher.connect(signal = u'doc_page_mod_db', receiver = self._on_doc_page_mod_db)
1202 #--------------------------------------------------------
1203 - def __build_context_menus(self):
1204 1205 # --- part context menu --- 1206 self.__part_context_menu = wx.Menu(title = _('part menu')) 1207 1208 ID = wx.NewId() 1209 self.__part_context_menu.Append(ID, _('Display part')) 1210 wx.EVT_MENU(self.__part_context_menu, ID, self.__display_curr_part) 1211 1212 ID = wx.NewId() 1213 self.__part_context_menu.Append(ID, _('%s Sign/Edit properties') % u'\u270D') 1214 wx.EVT_MENU(self.__part_context_menu, ID, self.__review_curr_part) 1215 1216 self.__part_context_menu.AppendSeparator() 1217 1218 ID = wx.NewId() 1219 self.__part_context_menu.Append(ID, _('Print part')) 1220 wx.EVT_MENU(self.__part_context_menu, ID, self.__print_part) 1221 1222 ID = wx.NewId() 1223 self.__part_context_menu.Append(ID, _('Fax part')) 1224 wx.EVT_MENU(self.__part_context_menu, ID, self.__fax_part) 1225 1226 ID = wx.NewId() 1227 self.__part_context_menu.Append(ID, _('Mail part')) 1228 wx.EVT_MENU(self.__part_context_menu, ID, self.__mail_part) 1229 1230 self.__part_context_menu.AppendSeparator() # so we can append some items 1231 1232 # --- doc context menu --- 1233 self.__doc_context_menu = wx.Menu(title = _('document menu')) 1234 1235 ID = wx.NewId() 1236 self.__doc_context_menu.Append(ID, _('%s Sign/Edit properties') % u'\u270D') 1237 wx.EVT_MENU(self.__doc_context_menu, ID, self.__review_curr_part) 1238 1239 self.__doc_context_menu.AppendSeparator() 1240 1241 ID = wx.NewId() 1242 self.__doc_context_menu.Append(ID, _('Print all parts')) 1243 wx.EVT_MENU(self.__doc_context_menu, ID, self.__print_doc) 1244 1245 ID = wx.NewId() 1246 self.__doc_context_menu.Append(ID, _('Fax all parts')) 1247 wx.EVT_MENU(self.__doc_context_menu, ID, self.__fax_doc) 1248 1249 ID = wx.NewId() 1250 self.__doc_context_menu.Append(ID, _('Mail all parts')) 1251 wx.EVT_MENU(self.__doc_context_menu, ID, self.__mail_doc) 1252 1253 ID = wx.NewId() 1254 self.__doc_context_menu.Append(ID, _('Export all parts')) 1255 wx.EVT_MENU(self.__doc_context_menu, ID, self.__export_doc_to_disk) 1256 1257 self.__doc_context_menu.AppendSeparator() 1258 1259 ID = wx.NewId() 1260 self.__doc_context_menu.Append(ID, _('Delete document')) 1261 wx.EVT_MENU(self.__doc_context_menu, ID, self.__delete_document) 1262 1263 ID = wx.NewId() 1264 self.__doc_context_menu.Append(ID, _('Access external original')) 1265 wx.EVT_MENU(self.__doc_context_menu, ID, self.__access_external_original) 1266 1267 ID = wx.NewId() 1268 self.__doc_context_menu.Append(ID, _('Edit corresponding encounter')) 1269 wx.EVT_MENU(self.__doc_context_menu, ID, self.__edit_encounter_details) 1270 1271 ID = wx.NewId() 1272 self.__doc_context_menu.Append(ID, _('Select corresponding encounter')) 1273 wx.EVT_MENU(self.__doc_context_menu, ID, self.__select_encounter) 1274 1275 # self.__doc_context_menu.AppendSeparator() 1276 1277 ID = wx.NewId() 1278 self.__doc_context_menu.Append(ID, _('Manage descriptions')) 1279 wx.EVT_MENU(self.__doc_context_menu, ID, self.__manage_document_descriptions)
1280 1281 # document / description 1282 # self.__desc_menu = wx.Menu() 1283 # ID = wx.NewId() 1284 # self.__doc_context_menu.AppendMenu(ID, _('Descriptions ...'), self.__desc_menu) 1285 1286 # ID = wx.NewId() 1287 # self.__desc_menu.Append(ID, _('Add new description')) 1288 # wx.EVT_MENU(self.__desc_menu, ID, self.__add_doc_desc) 1289 1290 # ID = wx.NewId() 1291 # self.__desc_menu.Append(ID, _('Delete description')) 1292 # wx.EVT_MENU(self.__desc_menu, ID, self.__del_doc_desc) 1293 1294 # self.__desc_menu.AppendSeparator() 1295 #--------------------------------------------------------
1296 - def __populate_tree(self):
1297 1298 wx.BeginBusyCursor() 1299 1300 # clean old tree 1301 if self.root is not None: 1302 self.DeleteAllItems() 1303 1304 # init new tree 1305 self.root = self.AddRoot(cDocTree._root_node_labels[self.__sort_mode], -1, -1) 1306 self.SetPyData(self.root, None) 1307 self.SetItemHasChildren(self.root, False) 1308 1309 # read documents from database 1310 curr_pat = gmPerson.gmCurrentPatient() 1311 docs_folder = curr_pat.get_document_folder() 1312 docs = docs_folder.get_documents() 1313 1314 if docs is None: 1315 gmGuiHelpers.gm_show_error ( 1316 aMessage = _('Error searching documents.'), 1317 aTitle = _('loading document list') 1318 ) 1319 # avoid recursion of GUI updating 1320 wx.EndBusyCursor() 1321 return True 1322 1323 if len(docs) == 0: 1324 wx.EndBusyCursor() 1325 return True 1326 1327 # fill new tree from document list 1328 self.SetItemHasChildren(self.root, True) 1329 1330 # add our documents as first level nodes 1331 intermediate_nodes = {} 1332 for doc in docs: 1333 1334 parts = doc.parts 1335 1336 label = _('%s%7s %s:%s (%s part(s)%s)') % ( 1337 gmTools.bool2subst(doc.has_unreviewed_parts, gmTools.u_writing_hand, u'', u'?'), 1338 doc['clin_when'].strftime('%m/%Y'), 1339 doc['l10n_type'][:26], 1340 gmTools.coalesce(initial = doc['comment'], instead = u'', template_initial = u' %s'), 1341 len(parts), 1342 gmTools.coalesce(initial = doc['ext_ref'], instead = u'', template_initial = u', \u00BB%s\u00AB') 1343 ) 1344 1345 # need intermediate branch level ? 1346 if self.__sort_mode == 'episode': 1347 lbl = doc['episode'] # it'd be nice to also show the issue but we don't have that 1348 if not intermediate_nodes.has_key(lbl): 1349 intermediate_nodes[lbl] = self.AppendItem(parent = self.root, text = lbl) 1350 self.SetItemBold(intermediate_nodes[lbl], bold = True) 1351 self.SetPyData(intermediate_nodes[lbl], None) 1352 parent = intermediate_nodes[lbl] 1353 elif self.__sort_mode == 'type': 1354 if not intermediate_nodes.has_key(doc['l10n_type']): 1355 intermediate_nodes[doc['l10n_type']] = self.AppendItem(parent = self.root, text = doc['l10n_type']) 1356 self.SetItemBold(intermediate_nodes[doc['l10n_type']], bold = True) 1357 self.SetPyData(intermediate_nodes[doc['l10n_type']], None) 1358 parent = intermediate_nodes[doc['l10n_type']] 1359 else: 1360 parent = self.root 1361 1362 doc_node = self.AppendItem(parent = parent, text = label) 1363 #self.SetItemBold(doc_node, bold = True) 1364 self.SetPyData(doc_node, doc) 1365 if len(parts) > 0: 1366 self.SetItemHasChildren(doc_node, True) 1367 1368 # now add parts as child nodes 1369 for part in parts: 1370 # if part['clinically_relevant']: 1371 # rel = ' [%s]' % _('Cave') 1372 # else: 1373 # rel = '' 1374 label = '%s%s (%s)%s' % ( 1375 gmTools.bool2str ( 1376 boolean = part['reviewed'] or part['reviewed_by_you'] or part['reviewed_by_intended_reviewer'], 1377 true_str = u'', 1378 false_str = gmTools.u_writing_hand 1379 ), 1380 _('part %2s') % part['seq_idx'], 1381 gmTools.size2str(part['size']), 1382 gmTools.coalesce ( 1383 part['obj_comment'], 1384 u'', 1385 u': %s%%s%s' % (gmTools.u_left_double_angle_quote, gmTools.u_right_double_angle_quote) 1386 ) 1387 ) 1388 1389 part_node = self.AppendItem(parent = doc_node, text = label) 1390 self.SetPyData(part_node, part) 1391 1392 self.__sort_nodes() 1393 self.SelectItem(self.root) 1394 1395 # FIXME: apply expansion state if available or else ... 1396 # FIXME: ... uncollapse to default state 1397 self.Expand(self.root) 1398 if self.__sort_mode in ['episode', 'type']: 1399 for key in intermediate_nodes.keys(): 1400 self.Expand(intermediate_nodes[key]) 1401 1402 wx.EndBusyCursor() 1403 1404 return True
1405 #------------------------------------------------------------------------
1406 - def OnCompareItems (self, node1=None, node2=None):
1407 """Used in sorting items. 1408 1409 -1: 1 < 2 1410 0: 1 = 2 1411 1: 1 > 2 1412 """ 1413 # Windows can send bogus events so ignore that 1414 if not node1.IsOk(): 1415 _log.debug('no data on node 1') 1416 return 0 1417 if not node2.IsOk(): 1418 _log.debug('no data on node 2') 1419 return 0 1420 1421 data1 = self.GetPyData(node1) 1422 data2 = self.GetPyData(node2) 1423 1424 # doc node 1425 if isinstance(data1, gmDocuments.cDocument): 1426 1427 date_field = 'clin_when' 1428 #date_field = 'modified_when' 1429 1430 if self.__sort_mode == 'age': 1431 # reverse sort by date 1432 if data1[date_field] > data2[date_field]: 1433 return -1 1434 if data1[date_field] == data2[date_field]: 1435 return 0 1436 return 1 1437 1438 elif self.__sort_mode == 'episode': 1439 if data1['episode'] < data2['episode']: 1440 return -1 1441 if data1['episode'] == data2['episode']: 1442 # inner sort: reverse by date 1443 if data1[date_field] > data2[date_field]: 1444 return -1 1445 if data1[date_field] == data2[date_field]: 1446 return 0 1447 return 1 1448 return 1 1449 1450 elif self.__sort_mode == 'review': 1451 # equality 1452 if data1.has_unreviewed_parts == data2.has_unreviewed_parts: 1453 # inner sort: reverse by date 1454 if data1[date_field] > data2[date_field]: 1455 return -1 1456 if data1[date_field] == data2[date_field]: 1457 return 0 1458 return 1 1459 if data1.has_unreviewed_parts: 1460 return -1 1461 return 1 1462 1463 elif self.__sort_mode == 'type': 1464 if data1['l10n_type'] < data2['l10n_type']: 1465 return -1 1466 if data1['l10n_type'] == data2['l10n_type']: 1467 # inner sort: reverse by date 1468 if data1[date_field] > data2[date_field]: 1469 return -1 1470 if data1[date_field] == data2[date_field]: 1471 return 0 1472 return 1 1473 return 1 1474 1475 else: 1476 _log.error('unknown document sort mode [%s], reverse-sorting by age', self.__sort_mode) 1477 # reverse sort by date 1478 if data1[date_field] > data2[date_field]: 1479 return -1 1480 if data1[date_field] == data2[date_field]: 1481 return 0 1482 return 1 1483 1484 # part node 1485 if isinstance(data1, gmDocuments.cDocumentPart): 1486 # compare sequence IDs (= "page" numbers) 1487 # FIXME: wrong order ? 1488 if data1['seq_idx'] < data2['seq_idx']: 1489 return -1 1490 if data1['seq_idx'] == data2['seq_idx']: 1491 return 0 1492 return 1 1493 1494 # else sort alphabetically 1495 if None in [data1, data2]: 1496 l1 = self.GetItemText(node1) 1497 l2 = self.GetItemText(node2) 1498 if l1 < l2: 1499 return -1 1500 if l1 == l2: 1501 return 0 1502 else: 1503 if data1 < data2: 1504 return -1 1505 if data1 == data2: 1506 return 0 1507 return 1
1508 #------------------------------------------------------------------------ 1509 # event handlers 1510 #------------------------------------------------------------------------
1511 - def _on_doc_mod_db(self, *args, **kwargs):
1512 # FIXME: remember current expansion state 1513 wx.CallAfter(self._schedule_data_reget)
1514 #------------------------------------------------------------------------
1515 - def _on_doc_page_mod_db(self, *args, **kwargs):
1516 # FIXME: remember current expansion state 1517 wx.CallAfter(self._schedule_data_reget)
1518 #------------------------------------------------------------------------
1519 - def _on_pre_patient_selection(self, *args, **kwargs):
1520 # FIXME: self.__store_expansion_history_in_db 1521 1522 # empty out tree 1523 if self.root is not None: 1524 self.DeleteAllItems() 1525 self.root = None
1526 #------------------------------------------------------------------------
1527 - def _on_post_patient_selection(self, *args, **kwargs):
1528 # FIXME: self.__load_expansion_history_from_db (but not apply it !) 1529 self._schedule_data_reget()
1530 #------------------------------------------------------------------------
1531 - def _on_activate(self, event):
1532 node = event.GetItem() 1533 node_data = self.GetPyData(node) 1534 1535 # exclude pseudo root node 1536 if node_data is None: 1537 return None 1538 1539 # expand/collapse documents on activation 1540 if isinstance(node_data, gmDocuments.cDocument): 1541 self.Toggle(node) 1542 return True 1543 1544 # string nodes are labels such as episodes which may or may not have children 1545 if type(node_data) == type('string'): 1546 self.Toggle(node) 1547 return True 1548 1549 self.__display_part(part = node_data) 1550 return True
1551 #--------------------------------------------------------
1552 - def __on_right_click(self, evt):
1553 1554 node = evt.GetItem() 1555 self.__curr_node_data = self.GetPyData(node) 1556 1557 # exclude pseudo root node 1558 if self.__curr_node_data is None: 1559 return None 1560 1561 # documents 1562 if isinstance(self.__curr_node_data, gmDocuments.cDocument): 1563 self.__handle_doc_context() 1564 1565 # parts 1566 if isinstance(self.__curr_node_data, gmDocuments.cDocumentPart): 1567 self.__handle_part_context() 1568 1569 del self.__curr_node_data 1570 evt.Skip()
1571 #--------------------------------------------------------
1572 - def __activate_as_current_photo(self, evt):
1573 self.__curr_node_data.set_as_active_photograph()
1574 #--------------------------------------------------------
1575 - def __display_curr_part(self, evt):
1576 self.__display_part(part = self.__curr_node_data)
1577 #--------------------------------------------------------
1578 - def __review_curr_part(self, evt):
1579 self.__review_part(part = self.__curr_node_data)
1580 #--------------------------------------------------------
1581 - def __manage_document_descriptions(self, evt):
1582 manage_document_descriptions(parent = self, document = self.__curr_node_data)
1583 #-------------------------------------------------------- 1584 # internal API 1585 #--------------------------------------------------------
1586 - def __sort_nodes(self, start_node=None):
1587 1588 if start_node is None: 1589 start_node = self.GetRootItem() 1590 1591 # protect against empty tree where not even 1592 # a root node exists 1593 if not start_node.IsOk(): 1594 return True 1595 1596 self.SortChildren(start_node) 1597 1598 child_node, cookie = self.GetFirstChild(start_node) 1599 while child_node.IsOk(): 1600 self.__sort_nodes(start_node = child_node) 1601 child_node, cookie = self.GetNextChild(start_node, cookie) 1602 1603 return
1604 #--------------------------------------------------------
1605 - def __handle_doc_context(self):
1606 self.PopupMenu(self.__doc_context_menu, wx.DefaultPosition)
1607 #--------------------------------------------------------
1608 - def __handle_part_context(self):
1609 1610 # make active patient photograph 1611 if self.__curr_node_data['type'] == 'patient photograph': 1612 ID = wx.NewId() 1613 self.__part_context_menu.Append(ID, _('Activate as current photo')) 1614 wx.EVT_MENU(self.__part_context_menu, ID, self.__activate_as_current_photo) 1615 else: 1616 ID = None 1617 1618 self.PopupMenu(self.__part_context_menu, wx.DefaultPosition) 1619 1620 if ID is not None: 1621 self.__part_context_menu.Delete(ID)
1622 #-------------------------------------------------------- 1623 # part level context menu handlers 1624 #--------------------------------------------------------
1625 - def __display_part(self, part):
1626 """Display document part.""" 1627 1628 # sanity check 1629 if part['size'] == 0: 1630 _log.debug('cannot display part [%s] - 0 bytes', part['pk_obj']) 1631 gmGuiHelpers.gm_show_error ( 1632 aMessage = _('Document part does not seem to exist in database !'), 1633 aTitle = _('showing document') 1634 ) 1635 return None 1636 1637 wx.BeginBusyCursor() 1638 1639 cfg = gmCfg.cCfgSQL() 1640 1641 # # get export directory for temporary files 1642 # tmp_dir = gmTools.coalesce ( 1643 # cfg.get2 ( 1644 # option = "horstspace.tmp_dir", 1645 # workplace = gmSurgery.gmCurrentPractice().active_workplace, 1646 # bias = 'workplace' 1647 # ), 1648 # os.path.expanduser(os.path.join('~', '.gnumed', 'tmp')) 1649 # ) 1650 # _log.debug("temporary directory [%s]", tmp_dir) 1651 1652 # determine database export chunk size 1653 chunksize = int( 1654 cfg.get2 ( 1655 option = "horstspace.blob_export_chunk_size", 1656 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1657 bias = 'workplace', 1658 default = default_chunksize 1659 )) 1660 1661 # shall we force blocking during view ? 1662 block_during_view = bool( cfg.get2 ( 1663 option = 'horstspace.document_viewer.block_during_view', 1664 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1665 bias = 'user', 1666 default = None 1667 )) 1668 1669 # display it 1670 successful, msg = part.display_via_mime ( 1671 # tmpdir = tmp_dir, 1672 chunksize = chunksize, 1673 block = block_during_view 1674 ) 1675 1676 wx.EndBusyCursor() 1677 1678 if not successful: 1679 gmGuiHelpers.gm_show_error ( 1680 aMessage = _('Cannot display document part:\n%s') % msg, 1681 aTitle = _('showing document') 1682 ) 1683 return None 1684 1685 # handle review after display 1686 # 0: never 1687 # 1: always 1688 # 2: if no review by myself exists yet 1689 review_after_display = int(cfg.get2 ( 1690 option = 'horstspace.document_viewer.review_after_display', 1691 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1692 bias = 'user', 1693 default = 2 1694 )) 1695 if review_after_display == 1: # always review 1696 self.__review_part(part=part) 1697 elif review_after_display == 2: # review if no review by me exists 1698 review_by_me = filter(lambda rev: rev['is_your_review'], part.get_reviews()) 1699 if len(review_by_me) == 0: 1700 self.__review_part(part=part) 1701 1702 return True
1703 #--------------------------------------------------------
1704 - def __review_part(self, part=None):
1705 dlg = cReviewDocPartDlg ( 1706 parent = self, 1707 id = -1, 1708 part = part 1709 ) 1710 dlg.ShowModal() 1711 dlg.Destroy()
1712 #--------------------------------------------------------
1713 - def __process_part(self, action=None, l10n_action=None):
1714 1715 gmHooks.run_hook_script(hook = u'before_%s_doc_part' % action) 1716 1717 wx.BeginBusyCursor() 1718 1719 # detect wrapper 1720 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc' % action) 1721 if not found: 1722 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc.bat' % action) 1723 if not found: 1724 _log.error('neither of gm-%s_doc or gm-%s_doc.bat found', action, action) 1725 wx.EndBusyCursor() 1726 gmGuiHelpers.gm_show_error ( 1727 _('Cannot %(l10n_action)s document part - %(l10n_action)s command not found.\n' 1728 '\n' 1729 'Either of gm_%(action)s_doc.sh or gm_%(action)s_doc.bat\n' 1730 'must be in the execution path. The command will\n' 1731 'be passed the filename to %(l10n_action)s.' 1732 ) % {'action': action, 'l10n_action': l10n_action}, 1733 _('Processing document part: %s') % l10n_action 1734 ) 1735 return 1736 1737 cfg = gmCfg.cCfgSQL() 1738 1739 # # get export directory for temporary files 1740 # tmp_dir = gmTools.coalesce ( 1741 # cfg.get2 ( 1742 # option = "horstspace.tmp_dir", 1743 # workplace = gmSurgery.gmCurrentPractice().active_workplace, 1744 # bias = 'workplace' 1745 # ), 1746 # os.path.expanduser(os.path.join('~', '.gnumed', 'tmp')) 1747 # ) 1748 # _log.debug("temporary directory [%s]", tmp_dir) 1749 1750 # determine database export chunk size 1751 chunksize = int(cfg.get2 ( 1752 option = "horstspace.blob_export_chunk_size", 1753 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1754 bias = 'workplace', 1755 default = default_chunksize 1756 )) 1757 1758 part_file = self.__curr_node_data.export_to_file ( 1759 # aTempDir = tmp_dir, 1760 aChunkSize = chunksize 1761 ) 1762 1763 cmd = u'%s %s' % (external_cmd, part_file) 1764 success = gmShellAPI.run_command_in_shell ( 1765 command = cmd, 1766 blocking = False 1767 ) 1768 1769 wx.EndBusyCursor() 1770 1771 if not success: 1772 _log.error('%s command failed: [%s]', action, cmd) 1773 gmGuiHelpers.gm_show_error ( 1774 _('Cannot %(l10n_action)s document part - %(l10n_action)s command failed.\n' 1775 '\n' 1776 'You may need to check and fix either of\n' 1777 ' gm_%(action)s_doc.sh (Unix/Mac) or\n' 1778 ' gm_%(action)s_doc.bat (Windows)\n' 1779 '\n' 1780 'The command is passed the filename to %(l10n_action)s.' 1781 ) % {'action': action, 'l10n_action': l10n_action}, 1782 _('Processing document part: %s') % l10n_action 1783 )
1784 #-------------------------------------------------------- 1785 # FIXME: icons in the plugin toolbar
1786 - def __print_part(self, evt):
1787 self.__process_part(action = u'print', l10n_action = _('print'))
1788 #--------------------------------------------------------
1789 - def __fax_part(self, evt):
1790 self.__process_part(action = u'fax', l10n_action = _('fax'))
1791 #--------------------------------------------------------
1792 - def __mail_part(self, evt):
1793 self.__process_part(action = u'mail', l10n_action = _('mail'))
1794 #-------------------------------------------------------- 1795 # document level context menu handlers 1796 #--------------------------------------------------------
1797 - def __select_encounter(self, evt):
1798 enc = gmEMRStructWidgets.select_encounters ( 1799 parent = self, 1800 patient = gmPerson.gmCurrentPatient() 1801 ) 1802 if enc is None: 1803 return 1804 self.__curr_node_data['pk_encounter'] = enc['pk_encounter'] 1805 self.__curr_node_data.save()
1806 #--------------------------------------------------------
1807 - def __edit_encounter_details(self, evt):
1808 enc = gmEMRStructItems.cEncounter(aPK_obj = self.__curr_node_data['pk_encounter']) 1809 gmEMRStructWidgets.edit_encounter(parent = self, encounter = enc)
1810 #--------------------------------------------------------
1811 - def __process_doc(self, action=None, l10n_action=None):
1812 1813 gmHooks.run_hook_script(hook = u'before_%s_doc' % action) 1814 1815 wx.BeginBusyCursor() 1816 1817 # detect wrapper 1818 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc' % action) 1819 if not found: 1820 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc.bat' % action) 1821 if not found: 1822 _log.error('neither of gm-%s_doc or gm-%s_doc.bat found', action, action) 1823 wx.EndBusyCursor() 1824 gmGuiHelpers.gm_show_error ( 1825 _('Cannot %(l10n_action)s document - %(l10n_action)s command not found.\n' 1826 '\n' 1827 'Either of gm_%(action)s_doc.sh or gm_%(action)s_doc.bat\n' 1828 'must be in the execution path. The command will\n' 1829 'be passed a list of filenames to %(l10n_action)s.' 1830 ) % {'action': action, 'l10n_action': l10n_action}, 1831 _('Processing document: %s') % l10n_action 1832 ) 1833 return 1834 1835 cfg = gmCfg.cCfgSQL() 1836 1837 # # get export directory for temporary files 1838 # tmp_dir = gmTools.coalesce ( 1839 # cfg.get2 ( 1840 # option = "horstspace.tmp_dir", 1841 # workplace = gmSurgery.gmCurrentPractice().active_workplace, 1842 # bias = 'workplace' 1843 # ), 1844 # os.path.expanduser(os.path.join('~', '.gnumed', 'tmp')) 1845 # ) 1846 # _log.debug("temporary directory [%s]", tmp_dir) 1847 1848 # determine database export chunk size 1849 chunksize = int(cfg.get2 ( 1850 option = "horstspace.blob_export_chunk_size", 1851 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1852 bias = 'workplace', 1853 default = default_chunksize 1854 )) 1855 1856 part_files = self.__curr_node_data.export_parts_to_files ( 1857 # export_dir = tmp_dir, 1858 chunksize = chunksize 1859 ) 1860 1861 cmd = external_cmd + u' ' + u' '.join(part_files) 1862 success = gmShellAPI.run_command_in_shell ( 1863 command = cmd, 1864 blocking = False 1865 ) 1866 1867 wx.EndBusyCursor() 1868 1869 if not success: 1870 _log.error('%s command failed: [%s]', action, cmd) 1871 gmGuiHelpers.gm_show_error ( 1872 _('Cannot %(l10n_action)s document - %(l10n_action)s command failed.\n' 1873 '\n' 1874 'You may need to check and fix either of\n' 1875 ' gm_%(action)s_doc.sh (Unix/Mac) or\n' 1876 ' gm_%(action)s_doc.bat (Windows)\n' 1877 '\n' 1878 'The command is passed a list of filenames to %(l10n_action)s.' 1879 ) % {'action': action, 'l10n_action': l10n_action}, 1880 _('Processing document: %s') % l10n_action 1881 )
1882 #-------------------------------------------------------- 1883 # FIXME: icons in the plugin toolbar
1884 - def __print_doc(self, evt):
1885 self.__process_doc(action = u'print', l10n_action = _('print'))
1886 #--------------------------------------------------------
1887 - def __fax_doc(self, evt):
1888 self.__process_doc(action = u'fax', l10n_action = _('fax'))
1889 #--------------------------------------------------------
1890 - def __mail_doc(self, evt):
1891 self.__process_doc(action = u'mail', l10n_action = _('mail'))
1892 #--------------------------------------------------------
1893 - def __access_external_original(self, evt):
1894 1895 gmHooks.run_hook_script(hook = u'before_external_doc_access') 1896 1897 wx.BeginBusyCursor() 1898 1899 # detect wrapper 1900 found, external_cmd = gmShellAPI.detect_external_binary(u'gm_access_external_doc.sh') 1901 if not found: 1902 found, external_cmd = gmShellAPI.detect_external_binary(u'gm_access_external_doc.bat') 1903 if not found: 1904 _log.error('neither of gm_access_external_doc.sh or .bat found') 1905 wx.EndBusyCursor() 1906 gmGuiHelpers.gm_show_error ( 1907 _('Cannot access external document - access command not found.\n' 1908 '\n' 1909 'Either of gm_access_external_doc.sh or *.bat must be\n' 1910 'in the execution path. The command will be passed the\n' 1911 'document type and the reference URL for processing.' 1912 ), 1913 _('Accessing external document') 1914 ) 1915 return 1916 1917 cmd = u'%s "%s" "%s"' % (external_cmd, self.__curr_node_data['type'], self.__curr_node_data['ext_ref']) 1918 success = gmShellAPI.run_command_in_shell ( 1919 command = cmd, 1920 blocking = False 1921 ) 1922 1923 wx.EndBusyCursor() 1924 1925 if not success: 1926 _log.error('External access command failed: [%s]', cmd) 1927 gmGuiHelpers.gm_show_error ( 1928 _('Cannot access external document - access command failed.\n' 1929 '\n' 1930 'You may need to check and fix either of\n' 1931 ' gm_access_external_doc.sh (Unix/Mac) or\n' 1932 ' gm_access_external_doc.bat (Windows)\n' 1933 '\n' 1934 'The command is passed the document type and the\n' 1935 'external reference URL on the command line.' 1936 ), 1937 _('Accessing external document') 1938 )
1939 #--------------------------------------------------------
1940 - def __export_doc_to_disk(self, evt):
1941 """Export document into directory. 1942 1943 - one file per object 1944 - into subdirectory named after patient 1945 """ 1946 pat = gmPerson.gmCurrentPatient() 1947 dname = '%s-%s%s' % ( 1948 self.__curr_node_data['l10n_type'], 1949 self.__curr_node_data['clin_when'].strftime('%Y-%m-%d'), 1950 gmTools.coalesce(self.__curr_node_data['ext_ref'], '', '-%s').replace(' ', '_') 1951 ) 1952 def_dir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'docs', pat['dirname'], dname)) 1953 gmTools.mkdir(def_dir) 1954 1955 dlg = wx.DirDialog ( 1956 parent = self, 1957 message = _('Save document into directory ...'), 1958 defaultPath = def_dir, 1959 style = wx.DD_DEFAULT_STYLE 1960 ) 1961 result = dlg.ShowModal() 1962 dirname = dlg.GetPath() 1963 dlg.Destroy() 1964 1965 if result != wx.ID_OK: 1966 return True 1967 1968 wx.BeginBusyCursor() 1969 1970 cfg = gmCfg.cCfgSQL() 1971 1972 # determine database export chunk size 1973 chunksize = int(cfg.get2 ( 1974 option = "horstspace.blob_export_chunk_size", 1975 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1976 bias = 'workplace', 1977 default = default_chunksize 1978 )) 1979 1980 fnames = self.__curr_node_data.export_parts_to_files(export_dir = dirname, chunksize = chunksize) 1981 1982 wx.EndBusyCursor() 1983 1984 gmDispatcher.send(signal='statustext', msg=_('Successfully exported %s parts into the directory [%s].') % (len(fnames), dirname)) 1985 1986 return True
1987 #--------------------------------------------------------
1988 - def __delete_document(self, evt):
1989 result = gmGuiHelpers.gm_show_question ( 1990 aMessage = _('Are you sure you want to delete the document ?'), 1991 aTitle = _('Deleting document') 1992 ) 1993 if result is True: 1994 curr_pat = gmPerson.gmCurrentPatient() 1995 emr = curr_pat.get_emr() 1996 enc = emr.active_encounter 1997 gmDocuments.delete_document(document_id = self.__curr_node_data['pk_doc'], encounter_id = enc['pk_encounter'])
1998 #============================================================ 1999 # main 2000 #------------------------------------------------------------ 2001 if __name__ == '__main__': 2002 2003 gmI18N.activate_locale() 2004 gmI18N.install_domain(domain = 'gnumed') 2005 2006 #---------------------------------------- 2007 #---------------------------------------- 2008 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'): 2009 # test_*() 2010 pass 2011 2012 #============================================================ 2013