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
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
99
146
147 gmDispatcher.connect(signal = u'import_document_from_file', receiver = _save_file_as_new_document)
148
193
194
195
197
198 if parent is None:
199 parent = wx.GetApp().GetTopWindow()
200
201
202 dlg = cEditDocumentTypesDlg(parent = parent)
203 dlg.ShowModal()
204
205 from Gnumed.wxGladeWidgets import wxgEditDocumentTypesDlg
206
208 """A dialog showing a cEditDocumentTypesPnl."""
209
212
213
214 from Gnumed.wxGladeWidgets import wxgEditDocumentTypesPnl
215
217 """A panel grouping together fields to edit the list of document types."""
218
224
228
231
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
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
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
383 - def GetData(self, can_create=False):
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
411
413
414
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
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()
474 if len(revs) == 0:
475 return True
476
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
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
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
604 -class cScanIdxDocsPnl(wxgScanIdxPnl.wxgScanIdxPnl, gmPlugin.cPatientChange_PluginMixin):
625
626
627
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
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
658
662
663 - def _post_patient_selection(self, **kwds):
664 self.__init_ui_data()
665
666
667
669
670 self._PhWheel_episode.SetText('')
671 self._PhWheel_doc_type.SetText('')
672
673
674 fts = gmDateTime.cFuzzyTimestamp()
675 self._PhWheel_doc_date.SetText(fts.strftime('%Y-%m-%d'), fts)
676 self._PRW_doc_comment.SetText('')
677
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
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
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
739
740
741
742
743
744
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
786
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
794
795
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
810 return device[0]
811
812
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
825
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:
848 return True
849
850 self.acquired_pages.extend(fnames)
851 self.__reload_LBOX_doc_pages()
852
853 return True
854
856
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
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
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
905 self.acquired_pages[page_idx:(page_idx+1)] = []
906
907
908 self.__reload_LBOX_doc_pages()
909
910
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
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
980
981 new_doc['clin_when'] = self._PhWheel_doc_date.GetData().get_pydt()
982
983 ref = gmDocuments.get_ext_ref()
984 if ref is not None:
985 new_doc['ext_ref'] = ref
986
987 comment = self._PRW_doc_comment.GetLineText(0).strip()
988 if comment != u'':
989 new_doc['comment'] = comment
990
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
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
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
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
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
1064 self.__init_ui_data()
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
1079 """A panel with a document tree which can be sorted."""
1080
1081
1082
1087
1092
1097
1102
1103 -class cDocTree(wx.TreeCtrl, gmRegetMixin.cRegetOnPaintMixin):
1104
1105 """This wx.TreeCtrl derivative displays a tree view of stored medical documents.
1106
1107 It listens to document and patient changes and updated itself accordingly.
1108 """
1109 _sort_modes = ['age', 'review', 'episode', 'type']
1110 _root_node_labels = None
1111
1112 - def __init__(self, parent, id, *args, **kwds):
1113 """Set up our specialised tree.
1114 """
1115 kwds['style'] = wx.TR_NO_BUTTONS | wx.NO_BORDER
1116 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds)
1117
1118 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1119
1120 tmp = _('available documents (%s)')
1121 unsigned = _('unsigned (%s) on top') % u'\u270D'
1122 cDocTree._root_node_labels = {
1123 'age': tmp % _('most recent on top'),
1124 'review': tmp % unsigned,
1125 'episode': tmp % _('sorted by episode'),
1126 'type': tmp % _('sorted by type')
1127 }
1128
1129 self.root = None
1130 self.__sort_mode = 'age'
1131
1132 self.__build_context_menus()
1133 self.__register_interests()
1134 self._schedule_data_reget()
1135
1136
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
1150
1152 return self.__sort_mode
1153
1171
1172 sort_mode = property(_get_sort_mode, _set_sort_mode)
1173
1174
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
1188
1190
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
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
1202
1203
1204 self.__part_context_menu = wx.Menu(title = _('part menu'))
1205
1206 ID = wx.NewId()
1207 self.__part_context_menu.Append(ID, _('Display part'))
1208 wx.EVT_MENU(self.__part_context_menu, ID, self.__display_curr_part)
1209
1210 ID = wx.NewId()
1211 self.__part_context_menu.Append(ID, _('%s Sign/Edit properties') % u'\u270D')
1212 wx.EVT_MENU(self.__part_context_menu, ID, self.__review_curr_part)
1213
1214 self.__part_context_menu.AppendSeparator()
1215
1216 ID = wx.NewId()
1217 self.__part_context_menu.Append(ID, _('Print part'))
1218 wx.EVT_MENU(self.__part_context_menu, ID, self.__print_part)
1219
1220 ID = wx.NewId()
1221 self.__part_context_menu.Append(ID, _('Fax part'))
1222 wx.EVT_MENU(self.__part_context_menu, ID, self.__fax_part)
1223
1224 ID = wx.NewId()
1225 self.__part_context_menu.Append(ID, _('Mail part'))
1226 wx.EVT_MENU(self.__part_context_menu, ID, self.__mail_part)
1227
1228 self.__part_context_menu.AppendSeparator()
1229
1230
1231 self.__doc_context_menu = wx.Menu(title = _('document menu'))
1232
1233 ID = wx.NewId()
1234 self.__doc_context_menu.Append(ID, _('%s Sign/Edit properties') % u'\u270D')
1235 wx.EVT_MENU(self.__doc_context_menu, ID, self.__review_curr_part)
1236
1237 self.__doc_context_menu.AppendSeparator()
1238
1239 ID = wx.NewId()
1240 self.__doc_context_menu.Append(ID, _('Print all parts'))
1241 wx.EVT_MENU(self.__doc_context_menu, ID, self.__print_doc)
1242
1243 ID = wx.NewId()
1244 self.__doc_context_menu.Append(ID, _('Fax all parts'))
1245 wx.EVT_MENU(self.__doc_context_menu, ID, self.__fax_doc)
1246
1247 ID = wx.NewId()
1248 self.__doc_context_menu.Append(ID, _('Mail all parts'))
1249 wx.EVT_MENU(self.__doc_context_menu, ID, self.__mail_doc)
1250
1251 ID = wx.NewId()
1252 self.__doc_context_menu.Append(ID, _('Export all parts'))
1253 wx.EVT_MENU(self.__doc_context_menu, ID, self.__export_doc_to_disk)
1254
1255 self.__doc_context_menu.AppendSeparator()
1256
1257 ID = wx.NewId()
1258 self.__doc_context_menu.Append(ID, _('Delete document'))
1259 wx.EVT_MENU(self.__doc_context_menu, ID, self.__delete_document)
1260
1261 ID = wx.NewId()
1262 self.__doc_context_menu.Append(ID, _('Access external original'))
1263 wx.EVT_MENU(self.__doc_context_menu, ID, self.__access_external_original)
1264
1265 ID = wx.NewId()
1266 self.__doc_context_menu.Append(ID, _('Edit corresponding encounter'))
1267 wx.EVT_MENU(self.__doc_context_menu, ID, self.__edit_encounter_details)
1268
1269
1270
1271
1272 ID = wx.NewId()
1273 self.__doc_context_menu.Append(ID, _('Manage descriptions'))
1274 wx.EVT_MENU(self.__doc_context_menu, ID, self.__manage_document_descriptions)
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1292
1293 wx.BeginBusyCursor()
1294
1295
1296 if self.root is not None:
1297 self.DeleteAllItems()
1298
1299
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
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
1315 wx.EndBusyCursor()
1316 return True
1317
1318 if len(docs) == 0:
1319 wx.EndBusyCursor()
1320 return True
1321
1322
1323 self.SetItemHasChildren(self.root, True)
1324
1325
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
1341 if self.__sort_mode == 'episode':
1342 lbl = doc['episode']
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
1359 self.SetPyData(doc_node, doc)
1360 if len(parts) > 0:
1361 self.SetItemHasChildren(doc_node, True)
1362
1363
1364 for part in parts:
1365
1366
1367
1368
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
1391
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
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
1420 if isinstance(data1, gmDocuments.cMedDoc):
1421
1422 date_field = 'clin_when'
1423
1424
1425 if self.__sort_mode == 'age':
1426
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
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
1447 if data1.has_unreviewed_parts == data2.has_unreviewed_parts:
1448
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
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
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
1480 if isinstance(data1, gmDocuments.cMedDocPart):
1481
1482
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
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
1505
1507
1508 wx.CallAfter(self._schedule_data_reget)
1509
1510 - def _on_doc_page_mod_db(self, *args, **kwargs):
1511
1512 wx.CallAfter(self._schedule_data_reget)
1513
1515
1516
1517
1518 if self.root is not None:
1519 self.DeleteAllItems()
1520 self.root = None
1521
1522 - def _on_post_patient_selection(self, *args, **kwargs):
1523
1524 self._schedule_data_reget()
1525
1527 node = event.GetItem()
1528 node_data = self.GetPyData(node)
1529
1530
1531 if node_data is None:
1532 return None
1533
1534
1535 if isinstance(node_data, gmDocuments.cMedDoc):
1536 self.Toggle(node)
1537 return True
1538
1539
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
1553 if self.__curr_node_data is None:
1554 return None
1555
1556
1557 if isinstance(self.__curr_node_data, gmDocuments.cMedDoc):
1558 self.__handle_doc_context()
1559
1560
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
1569
1571 self.__display_part(part = self.__curr_node_data)
1572
1574 self.__review_part(part = self.__curr_node_data)
1575
1578
1579
1580
1582
1583 if start_node is None:
1584 start_node = self.GetRootItem()
1585
1586
1587
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
1601 self.PopupMenu(self.__doc_context_menu, wx.DefaultPosition)
1602
1604
1605
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
1619
1621 """Display document part."""
1622
1623
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
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
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
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
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
1681
1682
1683
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:
1691 self.__review_part(part=part)
1692 elif review_after_display == 2:
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
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
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
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
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
1791
1795
1797
1798 gmHooks.run_hook_script(hook = u'before_%s_doc' % action)
1799
1800 wx.BeginBusyCursor()
1801
1802
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
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
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
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
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
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
1983
1984
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
1995 pass
1996
1997
1998