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