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 OSError:
819 _log.exception('problem acquiring image from source')
820 gmGuiHelpers.gm_show_error (
821 aMessage = _(
822 'No pages could be acquired from the source.\n\n'
823 'This may mean the scanner driver is not properly installed\n\n'
824 'On Windows you must install the TWAIN Python module\n'
825 'while on Linux and MacOSX it is recommended to install\n'
826 'the XSane package.'
827 ),
828 aTitle = _('acquiring page')
829 )
830 return None
831
832 if len(fnames) == 0:
833 return True
834
835 self.acquired_pages.extend(fnames)
836 self.__reload_LBOX_doc_pages()
837
838 return True
839
841
842 dlg = wx.FileDialog (
843 parent = None,
844 message = _('Choose a file'),
845 defaultDir = os.path.expanduser(os.path.join('~', 'gnumed')),
846 defaultFile = '',
847 wildcard = "%s (*)|*|TIFFs (*.tif)|*.tif|JPEGs (*.jpg)|*.jpg|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')),
848 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST | wx.MULTIPLE
849 )
850 result = dlg.ShowModal()
851 if result != wx.ID_CANCEL:
852 files = dlg.GetPaths()
853 for file in files:
854 self.acquired_pages.append(file)
855 self.__reload_LBOX_doc_pages()
856 dlg.Destroy()
857
859
860 page_idx = self._LBOX_doc_pages.GetSelection()
861 if page_idx == -1:
862 gmGuiHelpers.gm_show_info (
863 aMessage = _('You must select a part before you can view it.'),
864 aTitle = _('displaying part')
865 )
866 return None
867
868 page_fname = self._LBOX_doc_pages.GetClientData(page_idx)
869
870 (result, msg) = gmMimeLib.call_viewer_on_file(page_fname)
871 if not result:
872 gmGuiHelpers.gm_show_warning (
873 aMessage = _('Cannot display document part:\n%s') % msg,
874 aTitle = _('displaying part')
875 )
876 return None
877 return 1
878
880 page_idx = self._LBOX_doc_pages.GetSelection()
881 if page_idx == -1:
882 gmGuiHelpers.gm_show_info (
883 aMessage = _('You must select a part before you can delete it.'),
884 aTitle = _('deleting part')
885 )
886 return None
887 page_fname = self._LBOX_doc_pages.GetClientData(page_idx)
888
889
890 self.acquired_pages[page_idx:(page_idx+1)] = []
891
892
893 self.__reload_LBOX_doc_pages()
894
895
896 do_delete = gmGuiHelpers.gm_show_question (
897 _('The part has successfully been removed from the document.\n'
898 '\n'
899 'Do you also want to permanently delete the file\n'
900 '\n'
901 ' [%s]\n'
902 '\n'
903 'from which this document part was loaded ?\n'
904 '\n'
905 'If it is a temporary file for a page you just scanned\n'
906 'this makes a lot of sense. In other cases you may not\n'
907 'want to lose the file.\n'
908 '\n'
909 'Pressing [YES] will permanently remove the file\n'
910 'from your computer.\n'
911 ) % page_fname,
912 _('Removing document part')
913 )
914 if do_delete:
915 try:
916 os.remove(page_fname)
917 except:
918 _log.exception('Error deleting file.')
919 gmGuiHelpers.gm_show_error (
920 aMessage = _('Cannot delete part in file [%s].\n\nYou may not have write access to it.') % page_fname,
921 aTitle = _('deleting part')
922 )
923
924 return 1
925
927
928 if not self.__valid_for_save():
929 return False
930
931 wx.BeginBusyCursor()
932
933 pat = gmPerson.gmCurrentPatient()
934 doc_folder = pat.get_document_folder()
935 emr = pat.get_emr()
936
937
938 pk_episode = self._PhWheel_episode.GetData()
939 if pk_episode is None:
940 episode = emr.add_episode (
941 episode_name = self._PhWheel_episode.GetValue().strip(),
942 is_open = True
943 )
944 if episode is None:
945 wx.EndBusyCursor()
946 gmGuiHelpers.gm_show_error (
947 aMessage = _('Cannot start episode [%s].') % self._PhWheel_episode.GetValue().strip(),
948 aTitle = _('saving document')
949 )
950 return False
951 pk_episode = episode['pk_episode']
952
953 encounter = emr.active_encounter['pk_encounter']
954 document_type = self._PhWheel_doc_type.GetData()
955 new_doc = doc_folder.add_document(document_type, encounter, pk_episode)
956 if new_doc is None:
957 wx.EndBusyCursor()
958 gmGuiHelpers.gm_show_error (
959 aMessage = _('Cannot create new document.'),
960 aTitle = _('saving document')
961 )
962 return False
963
964
965
966 new_doc['clin_when'] = self._PhWheel_doc_date.GetData().get_pydt()
967
968 ref = gmDocuments.get_ext_ref()
969 if ref is not None:
970 new_doc['ext_ref'] = ref
971
972 comment = self._PRW_doc_comment.GetLineText(0).strip()
973 if comment != u'':
974 new_doc['comment'] = comment
975
976 if not new_doc.save_payload():
977 wx.EndBusyCursor()
978 gmGuiHelpers.gm_show_error (
979 aMessage = _('Cannot update document metadata.'),
980 aTitle = _('saving document')
981 )
982 return False
983
984 description = self._TBOX_description.GetValue().strip()
985 if description != '':
986 if not new_doc.add_description(description):
987 wx.EndBusyCursor()
988 gmGuiHelpers.gm_show_error (
989 aMessage = _('Cannot add document description.'),
990 aTitle = _('saving document')
991 )
992 return False
993
994
995 success, msg, filename = new_doc.add_parts_from_files (
996 files = self.acquired_pages,
997 reviewer = self._PhWheel_reviewer.GetData()
998 )
999 if not success:
1000 wx.EndBusyCursor()
1001 gmGuiHelpers.gm_show_error (
1002 aMessage = msg,
1003 aTitle = _('saving document')
1004 )
1005 return False
1006
1007
1008 if self._ChBOX_reviewed.GetValue():
1009 if not new_doc.set_reviewed (
1010 technically_abnormal = self._ChBOX_abnormal.GetValue(),
1011 clinically_relevant = self._ChBOX_relevant.GetValue()
1012 ):
1013 msg = _('Error setting "reviewed" status of new document.')
1014
1015 gmHooks.run_hook_script(hook = u'after_new_doc_created')
1016
1017
1018 cfg = gmCfg.cCfgSQL()
1019 show_id = bool (
1020 cfg.get2 (
1021 option = 'horstspace.scan_index.show_doc_id',
1022 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1023 bias = 'user'
1024 )
1025 )
1026 wx.EndBusyCursor()
1027 if show_id and (ref is not None):
1028 msg = _(
1029 """The reference ID for the new document is:
1030
1031 <%s>
1032
1033 You probably want to write it down on the
1034 original documents.
1035
1036 If you don't care about the ID you can switch
1037 off this message in the GNUmed configuration.""") % ref
1038 gmGuiHelpers.gm_show_info (
1039 aMessage = msg,
1040 aTitle = _('saving document')
1041 )
1042 else:
1043 gmDispatcher.send(signal='statustext', msg=_('Successfully saved new document.'))
1044
1045 self.__init_ui_data()
1046 return True
1047
1049 self.__init_ui_data()
1050
1052 self._ChBOX_abnormal.Enable(enable = self._ChBOX_reviewed.GetValue())
1053 self._ChBOX_relevant.Enable(enable = self._ChBOX_reviewed.GetValue())
1054
1056 pk_doc_type = self._PhWheel_doc_type.GetData()
1057 if pk_doc_type is None:
1058 self._PRW_doc_comment.unset_context(context = 'pk_doc_type')
1059 else:
1060 self._PRW_doc_comment.set_context(context = 'pk_doc_type', val = pk_doc_type)
1061 return True
1062
1064 """A panel with a document tree which can be sorted."""
1065
1066
1067
1072
1077
1082
1087
1088 -class cDocTree(wx.TreeCtrl, gmRegetMixin.cRegetOnPaintMixin):
1089
1090 """This wx.TreeCtrl derivative displays a tree view of stored medical documents.
1091
1092 It listens to document and patient changes and updated itself accordingly.
1093 """
1094 _sort_modes = ['age', 'review', 'episode', 'type']
1095 _root_node_labels = None
1096
1097 - def __init__(self, parent, id, *args, **kwds):
1098 """Set up our specialised tree.
1099 """
1100 kwds['style'] = wx.TR_NO_BUTTONS | wx.NO_BORDER
1101 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds)
1102
1103 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1104
1105 tmp = _('available documents (%s)')
1106 unsigned = _('unsigned (%s) on top') % u'\u270D'
1107 cDocTree._root_node_labels = {
1108 'age': tmp % _('most recent on top'),
1109 'review': tmp % unsigned,
1110 'episode': tmp % _('sorted by episode'),
1111 'type': tmp % _('sorted by type')
1112 }
1113
1114 self.root = None
1115 self.__sort_mode = 'age'
1116
1117 self.__build_context_menus()
1118 self.__register_interests()
1119 self._schedule_data_reget()
1120
1121
1122
1124
1125 node = self.GetSelection()
1126 node_data = self.GetPyData(node)
1127
1128 if not isinstance(node_data, gmDocuments.cMedDocPart):
1129 return True
1130
1131 self.__display_part(part = node_data)
1132 return True
1133
1134
1135
1137 return self.__sort_mode
1138
1156
1157 sort_mode = property(_get_sort_mode, _set_sort_mode)
1158
1159
1160
1162 curr_pat = gmPerson.gmCurrentPatient()
1163 if not curr_pat.connected:
1164 gmDispatcher.send(signal = 'statustext', msg = _('Cannot load documents. No active patient.'))
1165 return False
1166
1167 if not self.__populate_tree():
1168 return False
1169
1170 return True
1171
1172
1173
1175
1176 wx.EVT_TREE_ITEM_ACTIVATED (self, self.GetId(), self._on_activate)
1177 wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self.__on_right_click)
1178
1179
1180
1181 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
1182 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1183 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_doc_mod_db)
1184 gmDispatcher.connect(signal = u'doc_page_mod_db', receiver = self._on_doc_page_mod_db)
1185
1187
1188
1189 self.__part_context_menu = wx.Menu(title = _('part menu'))
1190
1191 ID = wx.NewId()
1192 self.__part_context_menu.Append(ID, _('Display part'))
1193 wx.EVT_MENU(self.__part_context_menu, ID, self.__display_curr_part)
1194
1195 ID = wx.NewId()
1196 self.__part_context_menu.Append(ID, _('%s Sign/Edit properties') % u'\u270D')
1197 wx.EVT_MENU(self.__part_context_menu, ID, self.__review_curr_part)
1198
1199 self.__part_context_menu.AppendSeparator()
1200
1201 ID = wx.NewId()
1202 self.__part_context_menu.Append(ID, _('Print part'))
1203 wx.EVT_MENU(self.__part_context_menu, ID, self.__print_part)
1204
1205 ID = wx.NewId()
1206 self.__part_context_menu.Append(ID, _('Fax part'))
1207 wx.EVT_MENU(self.__part_context_menu, ID, self.__fax_part)
1208
1209 ID = wx.NewId()
1210 self.__part_context_menu.Append(ID, _('Mail part'))
1211 wx.EVT_MENU(self.__part_context_menu, ID, self.__mail_part)
1212
1213 self.__part_context_menu.AppendSeparator()
1214
1215
1216 self.__doc_context_menu = wx.Menu(title = _('document menu'))
1217
1218 ID = wx.NewId()
1219 self.__doc_context_menu.Append(ID, _('%s Sign/Edit properties') % u'\u270D')
1220 wx.EVT_MENU(self.__doc_context_menu, ID, self.__review_curr_part)
1221
1222 self.__doc_context_menu.AppendSeparator()
1223
1224 ID = wx.NewId()
1225 self.__doc_context_menu.Append(ID, _('Print all parts'))
1226 wx.EVT_MENU(self.__doc_context_menu, ID, self.__print_doc)
1227
1228 ID = wx.NewId()
1229 self.__doc_context_menu.Append(ID, _('Fax all parts'))
1230 wx.EVT_MENU(self.__doc_context_menu, ID, self.__fax_doc)
1231
1232 ID = wx.NewId()
1233 self.__doc_context_menu.Append(ID, _('Mail all parts'))
1234 wx.EVT_MENU(self.__doc_context_menu, ID, self.__mail_doc)
1235
1236 ID = wx.NewId()
1237 self.__doc_context_menu.Append(ID, _('Export all parts'))
1238 wx.EVT_MENU(self.__doc_context_menu, ID, self.__export_doc_to_disk)
1239
1240 self.__doc_context_menu.AppendSeparator()
1241
1242 ID = wx.NewId()
1243 self.__doc_context_menu.Append(ID, _('Delete document'))
1244 wx.EVT_MENU(self.__doc_context_menu, ID, self.__delete_document)
1245
1246 ID = wx.NewId()
1247 self.__doc_context_menu.Append(ID, _('Access external original'))
1248 wx.EVT_MENU(self.__doc_context_menu, ID, self.__access_external_original)
1249
1250 ID = wx.NewId()
1251 self.__doc_context_menu.Append(ID, _('Edit corresponding encounter'))
1252 wx.EVT_MENU(self.__doc_context_menu, ID, self.__edit_encounter_details)
1253
1254
1255
1256
1257 ID = wx.NewId()
1258 self.__doc_context_menu.Append(ID, _('Manage descriptions'))
1259 wx.EVT_MENU(self.__doc_context_menu, ID, self.__manage_document_descriptions)
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1277
1278 wx.BeginBusyCursor()
1279
1280
1281 if self.root is not None:
1282 self.DeleteAllItems()
1283
1284
1285 self.root = self.AddRoot(cDocTree._root_node_labels[self.__sort_mode], -1, -1)
1286 self.SetPyData(self.root, None)
1287 self.SetItemHasChildren(self.root, False)
1288
1289
1290 curr_pat = gmPerson.gmCurrentPatient()
1291 docs_folder = curr_pat.get_document_folder()
1292 docs = docs_folder.get_documents()
1293
1294 if docs is None:
1295 gmGuiHelpers.gm_show_error (
1296 aMessage = _('Error searching documents.'),
1297 aTitle = _('loading document list')
1298 )
1299
1300 wx.EndBusyCursor()
1301 return True
1302
1303 if len(docs) == 0:
1304 wx.EndBusyCursor()
1305 return True
1306
1307
1308 self.SetItemHasChildren(self.root, True)
1309
1310
1311 intermediate_nodes = {}
1312 for doc in docs:
1313
1314 parts = doc.parts
1315
1316 label = _('%s%7s %s:%s (%s part(s)%s)') % (
1317 gmTools.bool2subst(doc.has_unreviewed_parts, gmTools.u_writing_hand, u'', u'?'),
1318 doc['clin_when'].strftime('%m/%Y'),
1319 doc['l10n_type'][:26],
1320 gmTools.coalesce(initial = doc['comment'], instead = u'', template_initial = u' %s'),
1321 len(parts),
1322 gmTools.coalesce(initial = doc['ext_ref'], instead = u'', template_initial = u', \u00BB%s\u00AB')
1323 )
1324
1325
1326 if self.__sort_mode == 'episode':
1327 lbl = doc['episode']
1328 if not intermediate_nodes.has_key(lbl):
1329 intermediate_nodes[lbl] = self.AppendItem(parent = self.root, text = lbl)
1330 self.SetItemBold(intermediate_nodes[lbl], bold = True)
1331 self.SetPyData(intermediate_nodes[lbl], None)
1332 parent = intermediate_nodes[lbl]
1333 elif self.__sort_mode == 'type':
1334 if not intermediate_nodes.has_key(doc['l10n_type']):
1335 intermediate_nodes[doc['l10n_type']] = self.AppendItem(parent = self.root, text = doc['l10n_type'])
1336 self.SetItemBold(intermediate_nodes[doc['l10n_type']], bold = True)
1337 self.SetPyData(intermediate_nodes[doc['l10n_type']], None)
1338 parent = intermediate_nodes[doc['l10n_type']]
1339 else:
1340 parent = self.root
1341
1342 doc_node = self.AppendItem(parent = parent, text = label)
1343
1344 self.SetPyData(doc_node, doc)
1345 if len(parts) > 0:
1346 self.SetItemHasChildren(doc_node, True)
1347
1348
1349 for part in parts:
1350
1351
1352
1353
1354 label = '%s%s (%s)%s' % (
1355 gmTools.bool2str (
1356 boolean = part['reviewed'] or part['reviewed_by_you'] or part['reviewed_by_intended_reviewer'],
1357 true_str = u'',
1358 false_str = gmTools.u_writing_hand
1359 ),
1360 _('part %2s') % part['seq_idx'],
1361 gmTools.size2str(part['size']),
1362 gmTools.coalesce (
1363 part['obj_comment'],
1364 u'',
1365 u': %s%%s%s' % (gmTools.u_left_double_angle_quote, gmTools.u_right_double_angle_quote)
1366 )
1367 )
1368
1369 part_node = self.AppendItem(parent = doc_node, text = label)
1370 self.SetPyData(part_node, part)
1371
1372 self.__sort_nodes()
1373 self.SelectItem(self.root)
1374
1375
1376
1377 self.Expand(self.root)
1378 if self.__sort_mode in ['episode', 'type']:
1379 for key in intermediate_nodes.keys():
1380 self.Expand(intermediate_nodes[key])
1381
1382 wx.EndBusyCursor()
1383
1384 return True
1385
1387 """Used in sorting items.
1388
1389 -1: 1 < 2
1390 0: 1 = 2
1391 1: 1 > 2
1392 """
1393
1394 if not node1.IsOk():
1395 _log.debug('no data on node 1')
1396 return 0
1397 if not node2.IsOk():
1398 _log.debug('no data on node 2')
1399 return 0
1400
1401 data1 = self.GetPyData(node1)
1402 data2 = self.GetPyData(node2)
1403
1404
1405 if isinstance(data1, gmDocuments.cMedDoc):
1406
1407 date_field = 'clin_when'
1408
1409
1410 if self.__sort_mode == 'age':
1411
1412 if data1[date_field] > data2[date_field]:
1413 return -1
1414 if data1[date_field] == data2[date_field]:
1415 return 0
1416 return 1
1417
1418 elif self.__sort_mode == 'episode':
1419 if data1['episode'] < data2['episode']:
1420 return -1
1421 if data1['episode'] == data2['episode']:
1422
1423 if data1[date_field] > data2[date_field]:
1424 return -1
1425 if data1[date_field] == data2[date_field]:
1426 return 0
1427 return 1
1428 return 1
1429
1430 elif self.__sort_mode == 'review':
1431
1432 if data1.has_unreviewed_parts == data2.has_unreviewed_parts:
1433
1434 if data1[date_field] > data2[date_field]:
1435 return -1
1436 if data1[date_field] == data2[date_field]:
1437 return 0
1438 return 1
1439 if data1.has_unreviewed_parts:
1440 return -1
1441 return 1
1442
1443 elif self.__sort_mode == 'type':
1444 if data1['l10n_type'] < data2['l10n_type']:
1445 return -1
1446 if data1['l10n_type'] == data2['l10n_type']:
1447
1448 if data1[date_field] > data2[date_field]:
1449 return -1
1450 if data1[date_field] == data2[date_field]:
1451 return 0
1452 return 1
1453 return 1
1454
1455 else:
1456 _log.error('unknown document sort mode [%s], reverse-sorting by age', self.__sort_mode)
1457
1458 if data1[date_field] > data2[date_field]:
1459 return -1
1460 if data1[date_field] == data2[date_field]:
1461 return 0
1462 return 1
1463
1464
1465 if isinstance(data1, gmDocuments.cMedDocPart):
1466
1467
1468 if data1['seq_idx'] < data2['seq_idx']:
1469 return -1
1470 if data1['seq_idx'] == data2['seq_idx']:
1471 return 0
1472 return 1
1473
1474
1475 if None in [data1, data2]:
1476 l1 = self.GetItemText(node1)
1477 l2 = self.GetItemText(node2)
1478 if l1 < l2:
1479 return -1
1480 if l1 == l2:
1481 return 0
1482 else:
1483 if data1 < data2:
1484 return -1
1485 if data1 == data2:
1486 return 0
1487 return 1
1488
1489
1490
1492
1493 wx.CallAfter(self._schedule_data_reget)
1494
1495 - def _on_doc_page_mod_db(self, *args, **kwargs):
1496
1497 wx.CallAfter(self._schedule_data_reget)
1498
1500
1501
1502
1503 if self.root is not None:
1504 self.DeleteAllItems()
1505 self.root = None
1506
1507 - def _on_post_patient_selection(self, *args, **kwargs):
1508
1509 self._schedule_data_reget()
1510
1512 node = event.GetItem()
1513 node_data = self.GetPyData(node)
1514
1515
1516 if node_data is None:
1517 return None
1518
1519
1520 if isinstance(node_data, gmDocuments.cMedDoc):
1521 self.Toggle(node)
1522 return True
1523
1524
1525 if type(node_data) == type('string'):
1526 self.Toggle(node)
1527 return True
1528
1529 self.__display_part(part = node_data)
1530 return True
1531
1533
1534 node = evt.GetItem()
1535 self.__curr_node_data = self.GetPyData(node)
1536
1537
1538 if self.__curr_node_data is None:
1539 return None
1540
1541
1542 if isinstance(self.__curr_node_data, gmDocuments.cMedDoc):
1543 self.__handle_doc_context()
1544
1545
1546 if isinstance(self.__curr_node_data, gmDocuments.cMedDocPart):
1547 self.__handle_part_context()
1548
1549 del self.__curr_node_data
1550 evt.Skip()
1551
1554
1556 self.__display_part(part = self.__curr_node_data)
1557
1559 self.__review_part(part = self.__curr_node_data)
1560
1563
1564
1565
1567
1568 if start_node is None:
1569 start_node = self.GetRootItem()
1570
1571
1572
1573 if not start_node.IsOk():
1574 return True
1575
1576 self.SortChildren(start_node)
1577
1578 child_node, cookie = self.GetFirstChild(start_node)
1579 while child_node.IsOk():
1580 self.__sort_nodes(start_node = child_node)
1581 child_node, cookie = self.GetNextChild(start_node, cookie)
1582
1583 return
1584
1586 self.PopupMenu(self.__doc_context_menu, wx.DefaultPosition)
1587
1589
1590
1591 if self.__curr_node_data['type'] == 'patient photograph':
1592 ID = wx.NewId()
1593 self.__part_context_menu.Append(ID, _('Activate as current photo'))
1594 wx.EVT_MENU(self.__part_context_menu, ID, self.__activate_as_current_photo)
1595 else:
1596 ID = None
1597
1598 self.PopupMenu(self.__part_context_menu, wx.DefaultPosition)
1599
1600 if ID is not None:
1601 self.__part_context_menu.Delete(ID)
1602
1603
1604
1606 """Display document part."""
1607
1608
1609 if part['size'] == 0:
1610 _log.debug('cannot display part [%s] - 0 bytes', part['pk_obj'])
1611 gmGuiHelpers.gm_show_error (
1612 aMessage = _('Document part does not seem to exist in database !'),
1613 aTitle = _('showing document')
1614 )
1615 return None
1616
1617 wx.BeginBusyCursor()
1618
1619 cfg = gmCfg.cCfgSQL()
1620
1621
1622 tmp_dir = gmTools.coalesce (
1623 cfg.get2 (
1624 option = "horstspace.tmp_dir",
1625 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1626 bias = 'workplace'
1627 ),
1628 os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
1629 )
1630 _log.debug("temporary directory [%s]", tmp_dir)
1631
1632
1633 chunksize = int(
1634 cfg.get2 (
1635 option = "horstspace.blob_export_chunk_size",
1636 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1637 bias = 'workplace',
1638 default = default_chunksize
1639 ))
1640
1641
1642 block_during_view = bool( cfg.get2 (
1643 option = 'horstspace.document_viewer.block_during_view',
1644 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1645 bias = 'user',
1646 default = None
1647 ))
1648
1649
1650 successful, msg = part.display_via_mime (
1651 tmpdir = tmp_dir,
1652 chunksize = chunksize,
1653 block = block_during_view
1654 )
1655
1656 wx.EndBusyCursor()
1657
1658 if not successful:
1659 gmGuiHelpers.gm_show_error (
1660 aMessage = _('Cannot display document part:\n%s') % msg,
1661 aTitle = _('showing document')
1662 )
1663 return None
1664
1665
1666
1667
1668
1669 review_after_display = int(cfg.get2 (
1670 option = 'horstspace.document_viewer.review_after_display',
1671 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1672 bias = 'user',
1673 default = 2
1674 ))
1675 if review_after_display == 1:
1676 self.__review_part(part=part)
1677 elif review_after_display == 2:
1678 review_by_me = filter(lambda rev: rev['is_your_review'], part.get_reviews())
1679 if len(review_by_me) == 0:
1680 self.__review_part(part=part)
1681
1682 return True
1683
1685 dlg = cReviewDocPartDlg (
1686 parent = self,
1687 id = -1,
1688 part = part
1689 )
1690 dlg.ShowModal()
1691 dlg.Destroy()
1692
1694
1695 gmHooks.run_hook_script(hook = u'before_%s_doc_part' % action)
1696
1697 wx.BeginBusyCursor()
1698
1699
1700 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc' % action)
1701 if not found:
1702 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc.bat' % action)
1703 if not found:
1704 _log.error('neither of gm-%s_doc or gm-%s_doc.bat found', action, action)
1705 wx.EndBusyCursor()
1706 gmGuiHelpers.gm_show_error (
1707 _('Cannot %(l10n_action)s document part - %(l10n_action)s command not found.\n'
1708 '\n'
1709 'Either of gm_%(action)s_doc.sh or gm_%(action)s_doc.bat\n'
1710 'must be in the execution path. The command will\n'
1711 'be passed the filename to %(l10n_action)s.'
1712 ) % {'action': action, 'l10n_action': l10n_action},
1713 _('Processing document part: %s') % l10n_action
1714 )
1715 return
1716
1717 cfg = gmCfg.cCfgSQL()
1718
1719
1720 tmp_dir = gmTools.coalesce (
1721 cfg.get2 (
1722 option = "horstspace.tmp_dir",
1723 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1724 bias = 'workplace'
1725 ),
1726 os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
1727 )
1728 _log.debug("temporary directory [%s]", tmp_dir)
1729
1730
1731 chunksize = int(cfg.get2 (
1732 option = "horstspace.blob_export_chunk_size",
1733 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1734 bias = 'workplace',
1735 default = default_chunksize
1736 ))
1737
1738 part_file = self.__curr_node_data.export_to_file (
1739 aTempDir = tmp_dir,
1740 aChunkSize = chunksize
1741 )
1742
1743 cmd = u'%s %s' % (external_cmd, part_file)
1744 success = gmShellAPI.run_command_in_shell (
1745 command = cmd,
1746 blocking = False
1747 )
1748
1749 wx.EndBusyCursor()
1750
1751 if not success:
1752 _log.error('%s command failed: [%s]', action, cmd)
1753 gmGuiHelpers.gm_show_error (
1754 _('Cannot %(l10n_action)s document part - %(l10n_action)s command failed.\n'
1755 '\n'
1756 'You may need to check and fix either of\n'
1757 ' gm_%(action)s_doc.sh (Unix/Mac) or\n'
1758 ' gm_%(action)s_doc.bat (Windows)\n'
1759 '\n'
1760 'The command is passed the filename to %(l10n_action)s.'
1761 ) % {'action': action, 'l10n_action': l10n_action},
1762 _('Processing document part: %s') % l10n_action
1763 )
1764
1765
1767 self.__process_part(action = u'print', l10n_action = _('print'))
1768
1770 self.__process_part(action = u'fax', l10n_action = _('fax'))
1771
1773 self.__process_part(action = u'mail', l10n_action = _('mail'))
1774
1775
1776
1781
1783
1784 gmHooks.run_hook_script(hook = u'before_%s_doc' % action)
1785
1786 wx.BeginBusyCursor()
1787
1788
1789 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc' % action)
1790 if not found:
1791 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc.bat' % action)
1792 if not found:
1793 _log.error('neither of gm-%s_doc or gm-%s_doc.bat found', action, action)
1794 wx.EndBusyCursor()
1795 gmGuiHelpers.gm_show_error (
1796 _('Cannot %(l10n_action)s document - %(l10n_action)s command not found.\n'
1797 '\n'
1798 'Either of gm_%(action)s_doc.sh or gm_%(action)s_doc.bat\n'
1799 'must be in the execution path. The command will\n'
1800 'be passed a list of filenames to %(l10n_action)s.'
1801 ) % {'action': action, 'l10n_action': l10n_action},
1802 _('Processing document: %s') % l10n_action
1803 )
1804 return
1805
1806 cfg = gmCfg.cCfgSQL()
1807
1808
1809 tmp_dir = gmTools.coalesce (
1810 cfg.get2 (
1811 option = "horstspace.tmp_dir",
1812 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1813 bias = 'workplace'
1814 ),
1815 os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
1816 )
1817 _log.debug("temporary directory [%s]", tmp_dir)
1818
1819
1820 chunksize = int(cfg.get2 (
1821 option = "horstspace.blob_export_chunk_size",
1822 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1823 bias = 'workplace',
1824 default = default_chunksize
1825 ))
1826
1827 part_files = self.__curr_node_data.export_parts_to_files (
1828 export_dir = tmp_dir,
1829 chunksize = chunksize
1830 )
1831
1832 cmd = external_cmd + u' ' + u' '.join(part_files)
1833 success = gmShellAPI.run_command_in_shell (
1834 command = cmd,
1835 blocking = False
1836 )
1837
1838 wx.EndBusyCursor()
1839
1840 if not success:
1841 _log.error('%s command failed: [%s]', action, cmd)
1842 gmGuiHelpers.gm_show_error (
1843 _('Cannot %(l10n_action)s document - %(l10n_action)s command failed.\n'
1844 '\n'
1845 'You may need to check and fix either of\n'
1846 ' gm_%(action)s_doc.sh (Unix/Mac) or\n'
1847 ' gm_%(action)s_doc.bat (Windows)\n'
1848 '\n'
1849 'The command is passed a list of filenames to %(l10n_action)s.'
1850 ) % {'action': action, 'l10n_action': l10n_action},
1851 _('Processing document: %s') % l10n_action
1852 )
1853
1854
1856 self.__process_doc(action = u'print', l10n_action = _('print'))
1857
1859 self.__process_doc(action = u'fax', l10n_action = _('fax'))
1860
1862 self.__process_doc(action = u'mail', l10n_action = _('mail'))
1863
1865
1866 gmHooks.run_hook_script(hook = u'before_external_doc_access')
1867
1868 wx.BeginBusyCursor()
1869
1870
1871 found, external_cmd = gmShellAPI.detect_external_binary(u'gm_access_external_doc.sh')
1872 if not found:
1873 found, external_cmd = gmShellAPI.detect_external_binary(u'gm_access_external_doc.bat')
1874 if not found:
1875 _log.error('neither of gm_access_external_doc.sh or .bat found')
1876 wx.EndBusyCursor()
1877 gmGuiHelpers.gm_show_error (
1878 _('Cannot access external document - access command not found.\n'
1879 '\n'
1880 'Either of gm_access_external_doc.sh or *.bat must be\n'
1881 'in the execution path. The command will be passed the\n'
1882 'document type and the reference URL for processing.'
1883 ),
1884 _('Accessing external document')
1885 )
1886 return
1887
1888 cmd = u'%s "%s" "%s"' % (external_cmd, self.__curr_node_data['type'], self.__curr_node_data['ext_ref'])
1889 success = gmShellAPI.run_command_in_shell (
1890 command = cmd,
1891 blocking = False
1892 )
1893
1894 wx.EndBusyCursor()
1895
1896 if not success:
1897 _log.error('External access command failed: [%s]', cmd)
1898 gmGuiHelpers.gm_show_error (
1899 _('Cannot access external document - access command failed.\n'
1900 '\n'
1901 'You may need to check and fix either of\n'
1902 ' gm_access_external_doc.sh (Unix/Mac) or\n'
1903 ' gm_access_external_doc.bat (Windows)\n'
1904 '\n'
1905 'The command is passed the document type and the\n'
1906 'external reference URL on the command line.'
1907 ),
1908 _('Accessing external document')
1909 )
1910
1912 """Export document into directory.
1913
1914 - one file per object
1915 - into subdirectory named after patient
1916 """
1917 pat = gmPerson.gmCurrentPatient()
1918 dname = '%s-%s%s' % (
1919 self.__curr_node_data['l10n_type'],
1920 self.__curr_node_data['clin_when'].strftime('%Y-%m-%d'),
1921 gmTools.coalesce(self.__curr_node_data['ext_ref'], '', '-%s').replace(' ', '_')
1922 )
1923 def_dir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'docs', pat['dirname'], dname))
1924 gmTools.mkdir(def_dir)
1925
1926 dlg = wx.DirDialog (
1927 parent = self,
1928 message = _('Save document into directory ...'),
1929 defaultPath = def_dir,
1930 style = wx.DD_DEFAULT_STYLE
1931 )
1932 result = dlg.ShowModal()
1933 dirname = dlg.GetPath()
1934 dlg.Destroy()
1935
1936 if result != wx.ID_OK:
1937 return True
1938
1939 wx.BeginBusyCursor()
1940
1941 cfg = gmCfg.cCfgSQL()
1942
1943
1944 chunksize = int(cfg.get2 (
1945 option = "horstspace.blob_export_chunk_size",
1946 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1947 bias = 'workplace',
1948 default = default_chunksize
1949 ))
1950
1951 fnames = self.__curr_node_data.export_parts_to_files(export_dir = dirname, chunksize = chunksize)
1952
1953 wx.EndBusyCursor()
1954
1955 gmDispatcher.send(signal='statustext', msg=_('Successfully exported %s parts into the directory [%s].') % (len(fnames), dirname))
1956
1957 return True
1958
1969
1970
1971
1972 if __name__ == '__main__':
1973
1974 gmI18N.activate_locale()
1975 gmI18N.install_domain(domain = 'gnumed')
1976
1977
1978
1979 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
1980
1981 pass
1982
1983
1984