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