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