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