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.cDocumentPart):
398 self.__part = part
399 self.__doc = self.__part.get_containing_document()
400 self.__reviewing_doc = False
401 elif isinstance(part, gmDocuments.cDocument):
402 self.__doc = part
403 self.__part = self.__doc.parts[0]
404 self.__reviewing_doc = True
405 else:
406 raise ValueError('<part> must be gmDocuments.cDocument or gmDocuments.cDocumentPart instance, got <%s>' % type(part))
407
408 self.__init_ui_data()
409
410
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 This acts on the current patient.
1110 """
1111 _sort_modes = ['age', 'review', 'episode', 'type']
1112 _root_node_labels = None
1113
1114 - def __init__(self, parent, id, *args, **kwds):
1115 """Set up our specialised tree.
1116 """
1117 kwds['style'] = wx.TR_NO_BUTTONS | wx.NO_BORDER
1118 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds)
1119
1120 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1121
1122 tmp = _('available documents (%s)')
1123 unsigned = _('unsigned (%s) on top') % u'\u270D'
1124 cDocTree._root_node_labels = {
1125 'age': tmp % _('most recent on top'),
1126 'review': tmp % unsigned,
1127 'episode': tmp % _('sorted by episode'),
1128 'type': tmp % _('sorted by type')
1129 }
1130
1131 self.root = None
1132 self.__sort_mode = 'age'
1133
1134 self.__build_context_menus()
1135 self.__register_interests()
1136 self._schedule_data_reget()
1137
1138
1139
1141
1142 node = self.GetSelection()
1143 node_data = self.GetPyData(node)
1144
1145 if not isinstance(node_data, gmDocuments.cDocumentPart):
1146 return True
1147
1148 self.__display_part(part = node_data)
1149 return True
1150
1151
1152
1154 return self.__sort_mode
1155
1173
1174 sort_mode = property(_get_sort_mode, _set_sort_mode)
1175
1176
1177
1179 curr_pat = gmPerson.gmCurrentPatient()
1180 if not curr_pat.connected:
1181 gmDispatcher.send(signal = 'statustext', msg = _('Cannot load documents. No active patient.'))
1182 return False
1183
1184 if not self.__populate_tree():
1185 return False
1186
1187 return True
1188
1189
1190
1192
1193 wx.EVT_TREE_ITEM_ACTIVATED (self, self.GetId(), self._on_activate)
1194 wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self.__on_right_click)
1195
1196
1197
1198 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
1199 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1200 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_doc_mod_db)
1201 gmDispatcher.connect(signal = u'doc_page_mod_db', receiver = self._on_doc_page_mod_db)
1202
1204
1205
1206 self.__part_context_menu = wx.Menu(title = _('part menu'))
1207
1208 ID = wx.NewId()
1209 self.__part_context_menu.Append(ID, _('Display part'))
1210 wx.EVT_MENU(self.__part_context_menu, ID, self.__display_curr_part)
1211
1212 ID = wx.NewId()
1213 self.__part_context_menu.Append(ID, _('%s Sign/Edit properties') % u'\u270D')
1214 wx.EVT_MENU(self.__part_context_menu, ID, self.__review_curr_part)
1215
1216 self.__part_context_menu.AppendSeparator()
1217
1218 ID = wx.NewId()
1219 self.__part_context_menu.Append(ID, _('Print part'))
1220 wx.EVT_MENU(self.__part_context_menu, ID, self.__print_part)
1221
1222 ID = wx.NewId()
1223 self.__part_context_menu.Append(ID, _('Fax part'))
1224 wx.EVT_MENU(self.__part_context_menu, ID, self.__fax_part)
1225
1226 ID = wx.NewId()
1227 self.__part_context_menu.Append(ID, _('Mail part'))
1228 wx.EVT_MENU(self.__part_context_menu, ID, self.__mail_part)
1229
1230 self.__part_context_menu.AppendSeparator()
1231
1232
1233 self.__doc_context_menu = wx.Menu(title = _('document menu'))
1234
1235 ID = wx.NewId()
1236 self.__doc_context_menu.Append(ID, _('%s Sign/Edit properties') % u'\u270D')
1237 wx.EVT_MENU(self.__doc_context_menu, ID, self.__review_curr_part)
1238
1239 self.__doc_context_menu.AppendSeparator()
1240
1241 ID = wx.NewId()
1242 self.__doc_context_menu.Append(ID, _('Print all parts'))
1243 wx.EVT_MENU(self.__doc_context_menu, ID, self.__print_doc)
1244
1245 ID = wx.NewId()
1246 self.__doc_context_menu.Append(ID, _('Fax all parts'))
1247 wx.EVT_MENU(self.__doc_context_menu, ID, self.__fax_doc)
1248
1249 ID = wx.NewId()
1250 self.__doc_context_menu.Append(ID, _('Mail all parts'))
1251 wx.EVT_MENU(self.__doc_context_menu, ID, self.__mail_doc)
1252
1253 ID = wx.NewId()
1254 self.__doc_context_menu.Append(ID, _('Export all parts'))
1255 wx.EVT_MENU(self.__doc_context_menu, ID, self.__export_doc_to_disk)
1256
1257 self.__doc_context_menu.AppendSeparator()
1258
1259 ID = wx.NewId()
1260 self.__doc_context_menu.Append(ID, _('Delete document'))
1261 wx.EVT_MENU(self.__doc_context_menu, ID, self.__delete_document)
1262
1263 ID = wx.NewId()
1264 self.__doc_context_menu.Append(ID, _('Access external original'))
1265 wx.EVT_MENU(self.__doc_context_menu, ID, self.__access_external_original)
1266
1267 ID = wx.NewId()
1268 self.__doc_context_menu.Append(ID, _('Edit corresponding encounter'))
1269 wx.EVT_MENU(self.__doc_context_menu, ID, self.__edit_encounter_details)
1270
1271 ID = wx.NewId()
1272 self.__doc_context_menu.Append(ID, _('Select corresponding encounter'))
1273 wx.EVT_MENU(self.__doc_context_menu, ID, self.__select_encounter)
1274
1275
1276
1277 ID = wx.NewId()
1278 self.__doc_context_menu.Append(ID, _('Manage descriptions'))
1279 wx.EVT_MENU(self.__doc_context_menu, ID, self.__manage_document_descriptions)
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1297
1298 wx.BeginBusyCursor()
1299
1300
1301 if self.root is not None:
1302 self.DeleteAllItems()
1303
1304
1305 self.root = self.AddRoot(cDocTree._root_node_labels[self.__sort_mode], -1, -1)
1306 self.SetPyData(self.root, None)
1307 self.SetItemHasChildren(self.root, False)
1308
1309
1310 curr_pat = gmPerson.gmCurrentPatient()
1311 docs_folder = curr_pat.get_document_folder()
1312 docs = docs_folder.get_documents()
1313
1314 if docs is None:
1315 gmGuiHelpers.gm_show_error (
1316 aMessage = _('Error searching documents.'),
1317 aTitle = _('loading document list')
1318 )
1319
1320 wx.EndBusyCursor()
1321 return True
1322
1323 if len(docs) == 0:
1324 wx.EndBusyCursor()
1325 return True
1326
1327
1328 self.SetItemHasChildren(self.root, True)
1329
1330
1331 intermediate_nodes = {}
1332 for doc in docs:
1333
1334 parts = doc.parts
1335
1336 label = _('%s%7s %s:%s (%s part(s)%s)') % (
1337 gmTools.bool2subst(doc.has_unreviewed_parts, gmTools.u_writing_hand, u'', u'?'),
1338 doc['clin_when'].strftime('%m/%Y'),
1339 doc['l10n_type'][:26],
1340 gmTools.coalesce(initial = doc['comment'], instead = u'', template_initial = u' %s'),
1341 len(parts),
1342 gmTools.coalesce(initial = doc['ext_ref'], instead = u'', template_initial = u', \u00BB%s\u00AB')
1343 )
1344
1345
1346 if self.__sort_mode == 'episode':
1347 lbl = doc['episode']
1348 if not intermediate_nodes.has_key(lbl):
1349 intermediate_nodes[lbl] = self.AppendItem(parent = self.root, text = lbl)
1350 self.SetItemBold(intermediate_nodes[lbl], bold = True)
1351 self.SetPyData(intermediate_nodes[lbl], None)
1352 parent = intermediate_nodes[lbl]
1353 elif self.__sort_mode == 'type':
1354 if not intermediate_nodes.has_key(doc['l10n_type']):
1355 intermediate_nodes[doc['l10n_type']] = self.AppendItem(parent = self.root, text = doc['l10n_type'])
1356 self.SetItemBold(intermediate_nodes[doc['l10n_type']], bold = True)
1357 self.SetPyData(intermediate_nodes[doc['l10n_type']], None)
1358 parent = intermediate_nodes[doc['l10n_type']]
1359 else:
1360 parent = self.root
1361
1362 doc_node = self.AppendItem(parent = parent, text = label)
1363
1364 self.SetPyData(doc_node, doc)
1365 if len(parts) > 0:
1366 self.SetItemHasChildren(doc_node, True)
1367
1368
1369 for part in parts:
1370
1371
1372
1373
1374 label = '%s%s (%s)%s' % (
1375 gmTools.bool2str (
1376 boolean = part['reviewed'] or part['reviewed_by_you'] or part['reviewed_by_intended_reviewer'],
1377 true_str = u'',
1378 false_str = gmTools.u_writing_hand
1379 ),
1380 _('part %2s') % part['seq_idx'],
1381 gmTools.size2str(part['size']),
1382 gmTools.coalesce (
1383 part['obj_comment'],
1384 u'',
1385 u': %s%%s%s' % (gmTools.u_left_double_angle_quote, gmTools.u_right_double_angle_quote)
1386 )
1387 )
1388
1389 part_node = self.AppendItem(parent = doc_node, text = label)
1390 self.SetPyData(part_node, part)
1391
1392 self.__sort_nodes()
1393 self.SelectItem(self.root)
1394
1395
1396
1397 self.Expand(self.root)
1398 if self.__sort_mode in ['episode', 'type']:
1399 for key in intermediate_nodes.keys():
1400 self.Expand(intermediate_nodes[key])
1401
1402 wx.EndBusyCursor()
1403
1404 return True
1405
1407 """Used in sorting items.
1408
1409 -1: 1 < 2
1410 0: 1 = 2
1411 1: 1 > 2
1412 """
1413
1414 if not node1.IsOk():
1415 _log.debug('no data on node 1')
1416 return 0
1417 if not node2.IsOk():
1418 _log.debug('no data on node 2')
1419 return 0
1420
1421 data1 = self.GetPyData(node1)
1422 data2 = self.GetPyData(node2)
1423
1424
1425 if isinstance(data1, gmDocuments.cDocument):
1426
1427 date_field = 'clin_when'
1428
1429
1430 if self.__sort_mode == 'age':
1431
1432 if data1[date_field] > data2[date_field]:
1433 return -1
1434 if data1[date_field] == data2[date_field]:
1435 return 0
1436 return 1
1437
1438 elif self.__sort_mode == 'episode':
1439 if data1['episode'] < data2['episode']:
1440 return -1
1441 if data1['episode'] == data2['episode']:
1442
1443 if data1[date_field] > data2[date_field]:
1444 return -1
1445 if data1[date_field] == data2[date_field]:
1446 return 0
1447 return 1
1448 return 1
1449
1450 elif self.__sort_mode == 'review':
1451
1452 if data1.has_unreviewed_parts == data2.has_unreviewed_parts:
1453
1454 if data1[date_field] > data2[date_field]:
1455 return -1
1456 if data1[date_field] == data2[date_field]:
1457 return 0
1458 return 1
1459 if data1.has_unreviewed_parts:
1460 return -1
1461 return 1
1462
1463 elif self.__sort_mode == 'type':
1464 if data1['l10n_type'] < data2['l10n_type']:
1465 return -1
1466 if data1['l10n_type'] == data2['l10n_type']:
1467
1468 if data1[date_field] > data2[date_field]:
1469 return -1
1470 if data1[date_field] == data2[date_field]:
1471 return 0
1472 return 1
1473 return 1
1474
1475 else:
1476 _log.error('unknown document sort mode [%s], reverse-sorting by age', self.__sort_mode)
1477
1478 if data1[date_field] > data2[date_field]:
1479 return -1
1480 if data1[date_field] == data2[date_field]:
1481 return 0
1482 return 1
1483
1484
1485 if isinstance(data1, gmDocuments.cDocumentPart):
1486
1487
1488 if data1['seq_idx'] < data2['seq_idx']:
1489 return -1
1490 if data1['seq_idx'] == data2['seq_idx']:
1491 return 0
1492 return 1
1493
1494
1495 if None in [data1, data2]:
1496 l1 = self.GetItemText(node1)
1497 l2 = self.GetItemText(node2)
1498 if l1 < l2:
1499 return -1
1500 if l1 == l2:
1501 return 0
1502 else:
1503 if data1 < data2:
1504 return -1
1505 if data1 == data2:
1506 return 0
1507 return 1
1508
1509
1510
1512
1513 wx.CallAfter(self._schedule_data_reget)
1514
1515 - def _on_doc_page_mod_db(self, *args, **kwargs):
1516
1517 wx.CallAfter(self._schedule_data_reget)
1518
1520
1521
1522
1523 if self.root is not None:
1524 self.DeleteAllItems()
1525 self.root = None
1526
1527 - def _on_post_patient_selection(self, *args, **kwargs):
1528
1529 self._schedule_data_reget()
1530
1532 node = event.GetItem()
1533 node_data = self.GetPyData(node)
1534
1535
1536 if node_data is None:
1537 return None
1538
1539
1540 if isinstance(node_data, gmDocuments.cDocument):
1541 self.Toggle(node)
1542 return True
1543
1544
1545 if type(node_data) == type('string'):
1546 self.Toggle(node)
1547 return True
1548
1549 self.__display_part(part = node_data)
1550 return True
1551
1553
1554 node = evt.GetItem()
1555 self.__curr_node_data = self.GetPyData(node)
1556
1557
1558 if self.__curr_node_data is None:
1559 return None
1560
1561
1562 if isinstance(self.__curr_node_data, gmDocuments.cDocument):
1563 self.__handle_doc_context()
1564
1565
1566 if isinstance(self.__curr_node_data, gmDocuments.cDocumentPart):
1567 self.__handle_part_context()
1568
1569 del self.__curr_node_data
1570 evt.Skip()
1571
1574
1576 self.__display_part(part = self.__curr_node_data)
1577
1579 self.__review_part(part = self.__curr_node_data)
1580
1583
1584
1585
1587
1588 if start_node is None:
1589 start_node = self.GetRootItem()
1590
1591
1592
1593 if not start_node.IsOk():
1594 return True
1595
1596 self.SortChildren(start_node)
1597
1598 child_node, cookie = self.GetFirstChild(start_node)
1599 while child_node.IsOk():
1600 self.__sort_nodes(start_node = child_node)
1601 child_node, cookie = self.GetNextChild(start_node, cookie)
1602
1603 return
1604
1606 self.PopupMenu(self.__doc_context_menu, wx.DefaultPosition)
1607
1609
1610
1611 if self.__curr_node_data['type'] == 'patient photograph':
1612 ID = wx.NewId()
1613 self.__part_context_menu.Append(ID, _('Activate as current photo'))
1614 wx.EVT_MENU(self.__part_context_menu, ID, self.__activate_as_current_photo)
1615 else:
1616 ID = None
1617
1618 self.PopupMenu(self.__part_context_menu, wx.DefaultPosition)
1619
1620 if ID is not None:
1621 self.__part_context_menu.Delete(ID)
1622
1623
1624
1626 """Display document part."""
1627
1628
1629 if part['size'] == 0:
1630 _log.debug('cannot display part [%s] - 0 bytes', part['pk_obj'])
1631 gmGuiHelpers.gm_show_error (
1632 aMessage = _('Document part does not seem to exist in database !'),
1633 aTitle = _('showing document')
1634 )
1635 return None
1636
1637 wx.BeginBusyCursor()
1638
1639 cfg = gmCfg.cCfgSQL()
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653 chunksize = int(
1654 cfg.get2 (
1655 option = "horstspace.blob_export_chunk_size",
1656 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1657 bias = 'workplace',
1658 default = default_chunksize
1659 ))
1660
1661
1662 block_during_view = bool( cfg.get2 (
1663 option = 'horstspace.document_viewer.block_during_view',
1664 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1665 bias = 'user',
1666 default = None
1667 ))
1668
1669
1670 successful, msg = part.display_via_mime (
1671
1672 chunksize = chunksize,
1673 block = block_during_view
1674 )
1675
1676 wx.EndBusyCursor()
1677
1678 if not successful:
1679 gmGuiHelpers.gm_show_error (
1680 aMessage = _('Cannot display document part:\n%s') % msg,
1681 aTitle = _('showing document')
1682 )
1683 return None
1684
1685
1686
1687
1688
1689 review_after_display = int(cfg.get2 (
1690 option = 'horstspace.document_viewer.review_after_display',
1691 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1692 bias = 'user',
1693 default = 2
1694 ))
1695 if review_after_display == 1:
1696 self.__review_part(part=part)
1697 elif review_after_display == 2:
1698 review_by_me = filter(lambda rev: rev['is_your_review'], part.get_reviews())
1699 if len(review_by_me) == 0:
1700 self.__review_part(part=part)
1701
1702 return True
1703
1705 dlg = cReviewDocPartDlg (
1706 parent = self,
1707 id = -1,
1708 part = part
1709 )
1710 dlg.ShowModal()
1711 dlg.Destroy()
1712
1714
1715 gmHooks.run_hook_script(hook = u'before_%s_doc_part' % action)
1716
1717 wx.BeginBusyCursor()
1718
1719
1720 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc' % action)
1721 if not found:
1722 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc.bat' % action)
1723 if not found:
1724 _log.error('neither of gm-%s_doc or gm-%s_doc.bat found', action, action)
1725 wx.EndBusyCursor()
1726 gmGuiHelpers.gm_show_error (
1727 _('Cannot %(l10n_action)s document part - %(l10n_action)s command not found.\n'
1728 '\n'
1729 'Either of gm_%(action)s_doc.sh or gm_%(action)s_doc.bat\n'
1730 'must be in the execution path. The command will\n'
1731 'be passed the filename to %(l10n_action)s.'
1732 ) % {'action': action, 'l10n_action': l10n_action},
1733 _('Processing document part: %s') % l10n_action
1734 )
1735 return
1736
1737 cfg = gmCfg.cCfgSQL()
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751 chunksize = int(cfg.get2 (
1752 option = "horstspace.blob_export_chunk_size",
1753 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1754 bias = 'workplace',
1755 default = default_chunksize
1756 ))
1757
1758 part_file = self.__curr_node_data.export_to_file (
1759
1760 aChunkSize = chunksize
1761 )
1762
1763 cmd = u'%s %s' % (external_cmd, part_file)
1764 success = gmShellAPI.run_command_in_shell (
1765 command = cmd,
1766 blocking = False
1767 )
1768
1769 wx.EndBusyCursor()
1770
1771 if not success:
1772 _log.error('%s command failed: [%s]', action, cmd)
1773 gmGuiHelpers.gm_show_error (
1774 _('Cannot %(l10n_action)s document part - %(l10n_action)s command failed.\n'
1775 '\n'
1776 'You may need to check and fix either of\n'
1777 ' gm_%(action)s_doc.sh (Unix/Mac) or\n'
1778 ' gm_%(action)s_doc.bat (Windows)\n'
1779 '\n'
1780 'The command is passed the filename to %(l10n_action)s.'
1781 ) % {'action': action, 'l10n_action': l10n_action},
1782 _('Processing document part: %s') % l10n_action
1783 )
1784
1785
1787 self.__process_part(action = u'print', l10n_action = _('print'))
1788
1790 self.__process_part(action = u'fax', l10n_action = _('fax'))
1791
1793 self.__process_part(action = u'mail', l10n_action = _('mail'))
1794
1795
1796
1806
1810
1812
1813 gmHooks.run_hook_script(hook = u'before_%s_doc' % action)
1814
1815 wx.BeginBusyCursor()
1816
1817
1818 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc' % action)
1819 if not found:
1820 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc.bat' % action)
1821 if not found:
1822 _log.error('neither of gm-%s_doc or gm-%s_doc.bat found', action, action)
1823 wx.EndBusyCursor()
1824 gmGuiHelpers.gm_show_error (
1825 _('Cannot %(l10n_action)s document - %(l10n_action)s command not found.\n'
1826 '\n'
1827 'Either of gm_%(action)s_doc.sh or gm_%(action)s_doc.bat\n'
1828 'must be in the execution path. The command will\n'
1829 'be passed a list of filenames to %(l10n_action)s.'
1830 ) % {'action': action, 'l10n_action': l10n_action},
1831 _('Processing document: %s') % l10n_action
1832 )
1833 return
1834
1835 cfg = gmCfg.cCfgSQL()
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849 chunksize = int(cfg.get2 (
1850 option = "horstspace.blob_export_chunk_size",
1851 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1852 bias = 'workplace',
1853 default = default_chunksize
1854 ))
1855
1856 part_files = self.__curr_node_data.export_parts_to_files (
1857
1858 chunksize = chunksize
1859 )
1860
1861 cmd = external_cmd + u' ' + u' '.join(part_files)
1862 success = gmShellAPI.run_command_in_shell (
1863 command = cmd,
1864 blocking = False
1865 )
1866
1867 wx.EndBusyCursor()
1868
1869 if not success:
1870 _log.error('%s command failed: [%s]', action, cmd)
1871 gmGuiHelpers.gm_show_error (
1872 _('Cannot %(l10n_action)s document - %(l10n_action)s command failed.\n'
1873 '\n'
1874 'You may need to check and fix either of\n'
1875 ' gm_%(action)s_doc.sh (Unix/Mac) or\n'
1876 ' gm_%(action)s_doc.bat (Windows)\n'
1877 '\n'
1878 'The command is passed a list of filenames to %(l10n_action)s.'
1879 ) % {'action': action, 'l10n_action': l10n_action},
1880 _('Processing document: %s') % l10n_action
1881 )
1882
1883
1885 self.__process_doc(action = u'print', l10n_action = _('print'))
1886
1888 self.__process_doc(action = u'fax', l10n_action = _('fax'))
1889
1891 self.__process_doc(action = u'mail', l10n_action = _('mail'))
1892
1894
1895 gmHooks.run_hook_script(hook = u'before_external_doc_access')
1896
1897 wx.BeginBusyCursor()
1898
1899
1900 found, external_cmd = gmShellAPI.detect_external_binary(u'gm_access_external_doc.sh')
1901 if not found:
1902 found, external_cmd = gmShellAPI.detect_external_binary(u'gm_access_external_doc.bat')
1903 if not found:
1904 _log.error('neither of gm_access_external_doc.sh or .bat found')
1905 wx.EndBusyCursor()
1906 gmGuiHelpers.gm_show_error (
1907 _('Cannot access external document - access command not found.\n'
1908 '\n'
1909 'Either of gm_access_external_doc.sh or *.bat must be\n'
1910 'in the execution path. The command will be passed the\n'
1911 'document type and the reference URL for processing.'
1912 ),
1913 _('Accessing external document')
1914 )
1915 return
1916
1917 cmd = u'%s "%s" "%s"' % (external_cmd, self.__curr_node_data['type'], self.__curr_node_data['ext_ref'])
1918 success = gmShellAPI.run_command_in_shell (
1919 command = cmd,
1920 blocking = False
1921 )
1922
1923 wx.EndBusyCursor()
1924
1925 if not success:
1926 _log.error('External access command failed: [%s]', cmd)
1927 gmGuiHelpers.gm_show_error (
1928 _('Cannot access external document - access command failed.\n'
1929 '\n'
1930 'You may need to check and fix either of\n'
1931 ' gm_access_external_doc.sh (Unix/Mac) or\n'
1932 ' gm_access_external_doc.bat (Windows)\n'
1933 '\n'
1934 'The command is passed the document type and the\n'
1935 'external reference URL on the command line.'
1936 ),
1937 _('Accessing external document')
1938 )
1939
1941 """Export document into directory.
1942
1943 - one file per object
1944 - into subdirectory named after patient
1945 """
1946 pat = gmPerson.gmCurrentPatient()
1947 dname = '%s-%s%s' % (
1948 self.__curr_node_data['l10n_type'],
1949 self.__curr_node_data['clin_when'].strftime('%Y-%m-%d'),
1950 gmTools.coalesce(self.__curr_node_data['ext_ref'], '', '-%s').replace(' ', '_')
1951 )
1952 def_dir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'docs', pat['dirname'], dname))
1953 gmTools.mkdir(def_dir)
1954
1955 dlg = wx.DirDialog (
1956 parent = self,
1957 message = _('Save document into directory ...'),
1958 defaultPath = def_dir,
1959 style = wx.DD_DEFAULT_STYLE
1960 )
1961 result = dlg.ShowModal()
1962 dirname = dlg.GetPath()
1963 dlg.Destroy()
1964
1965 if result != wx.ID_OK:
1966 return True
1967
1968 wx.BeginBusyCursor()
1969
1970 cfg = gmCfg.cCfgSQL()
1971
1972
1973 chunksize = int(cfg.get2 (
1974 option = "horstspace.blob_export_chunk_size",
1975 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1976 bias = 'workplace',
1977 default = default_chunksize
1978 ))
1979
1980 fnames = self.__curr_node_data.export_parts_to_files(export_dir = dirname, chunksize = chunksize)
1981
1982 wx.EndBusyCursor()
1983
1984 gmDispatcher.send(signal='statustext', msg=_('Successfully exported %s parts into the directory [%s].') % (len(fnames), dirname))
1985
1986 return True
1987
1998
1999
2000
2001 if __name__ == '__main__':
2002
2003 gmI18N.activate_locale()
2004 gmI18N.install_domain(domain = 'gnumed')
2005
2006
2007
2008 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2009
2010 pass
2011
2012
2013