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
8 import sys
9 import re as regex
10 import logging
11
12
13 import wx
14
15
16 if __name__ == '__main__':
17 sys.path.insert(0, '../../')
18 from Gnumed.pycommon import gmI18N, gmCfg, gmPG2, gmMimeLib, gmExceptions, gmMatchProvider, gmDispatcher, gmDateTime, gmTools, gmShellAPI, gmHooks
19 from Gnumed.business import gmPerson
20 from Gnumed.business import gmDocuments
21 from Gnumed.business import gmEMRStructItems
22 from Gnumed.business import gmSurgery
23
24 from Gnumed.wxpython import gmGuiHelpers
25 from Gnumed.wxpython import gmRegetMixin
26 from Gnumed.wxpython import gmPhraseWheel
27 from Gnumed.wxpython import gmPlugin
28 from Gnumed.wxpython import gmEMRStructWidgets
29 from Gnumed.wxpython import gmListWidgets
30
31
32 _log = logging.getLogger('gm.ui')
33 _log.info(__version__)
34
35
36 default_chunksize = 1 * 1024 * 1024
37
39
40
41 def delete_item(item):
42 doit = gmGuiHelpers.gm_show_question (
43 _( 'Are you sure you want to delete this\n'
44 'description from the document ?\n'
45 ),
46 _('Deleting document description')
47 )
48 if not doit:
49 return True
50
51 document.delete_description(pk = item[0])
52 return True
53
54 def add_item():
55 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
56 parent,
57 -1,
58 title = _('Adding document description'),
59 msg = _('Below you can add a document description.\n')
60 )
61 result = dlg.ShowModal()
62 if result == wx.ID_SAVE:
63 document.add_description(dlg.value)
64
65 dlg.Destroy()
66 return True
67
68 def edit_item(item):
69 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
70 parent,
71 -1,
72 title = _('Editing document description'),
73 msg = _('Below you can edit the document description.\n'),
74 text = item[1]
75 )
76 result = dlg.ShowModal()
77 if result == wx.ID_SAVE:
78 document.update_description(pk = item[0], description = dlg.value)
79
80 dlg.Destroy()
81 return True
82
83 def refresh_list(lctrl):
84 descriptions = document.get_descriptions()
85
86 lctrl.set_string_items(items = [
87 u'%s%s' % ( (u' '.join(regex.split('\r\n+|\r+|\n+|\t+', desc[1])))[:30], gmTools.u_ellipsis )
88 for desc in descriptions
89 ])
90 lctrl.set_data(data = descriptions)
91
92
93 gmListWidgets.get_choices_from_list (
94 parent = parent,
95 msg = _('Select the description you are interested in.\n'),
96 caption = _('Managing document descriptions'),
97 columns = [_('Description')],
98 edit_callback = edit_item,
99 new_callback = add_item,
100 delete_callback = delete_item,
101 refresh_callback = refresh_list,
102 single_selection = True,
103 can_return_empty = True
104 )
105
106 return True
107
110
113
114 -def save_file_as_new_document(parent=None, filename=None, document_type=None, unlock_patient=False, episode=None, review_as_normal=False):
123
124 -def save_files_as_new_document(parent=None, filenames=None, document_type=None, unlock_patient=False, episode=None, review_as_normal=False):
171
172 gmDispatcher.connect(signal = u'import_document_from_file', receiver = _save_file_as_new_document)
173 gmDispatcher.connect(signal = u'import_document_from_files', receiver = _save_files_as_new_document)
174
219
220
221
223
224 if parent is None:
225 parent = wx.GetApp().GetTopWindow()
226
227
228 dlg = cEditDocumentTypesDlg(parent = parent)
229 dlg.ShowModal()
230
231 from Gnumed.wxGladeWidgets import wxgEditDocumentTypesDlg
232
234 """A dialog showing a cEditDocumentTypesPnl."""
235
238
239
240 from Gnumed.wxGladeWidgets import wxgEditDocumentTypesPnl
241
243 """A panel grouping together fields to edit the list of document types."""
244
250
254
257
260
262
263 self._LCTRL_doc_type.DeleteAllItems()
264
265 doc_types = gmDocuments.get_document_types()
266 pos = len(doc_types) + 1
267
268 for doc_type in doc_types:
269 row_num = self._LCTRL_doc_type.InsertStringItem(pos, label = doc_type['type'])
270 self._LCTRL_doc_type.SetStringItem(index = row_num, col = 1, label = doc_type['l10n_type'])
271 if doc_type['is_user_defined']:
272 self._LCTRL_doc_type.SetStringItem(index = row_num, col = 2, label = ' X ')
273 if doc_type['is_in_use']:
274 self._LCTRL_doc_type.SetStringItem(index = row_num, col = 3, label = ' X ')
275
276 if len(doc_types) > 0:
277 self._LCTRL_doc_type.set_data(data = doc_types)
278 self._LCTRL_doc_type.SetColumnWidth(col=0, width=wx.LIST_AUTOSIZE)
279 self._LCTRL_doc_type.SetColumnWidth(col=1, width=wx.LIST_AUTOSIZE)
280 self._LCTRL_doc_type.SetColumnWidth(col=2, width=wx.LIST_AUTOSIZE_USEHEADER)
281 self._LCTRL_doc_type.SetColumnWidth(col=3, width=wx.LIST_AUTOSIZE_USEHEADER)
282
283 self._TCTRL_type.SetValue('')
284 self._TCTRL_l10n_type.SetValue('')
285
286 self._BTN_set_translation.Enable(False)
287 self._BTN_delete.Enable(False)
288 self._BTN_add.Enable(False)
289 self._BTN_reassign.Enable(False)
290
291 self._LCTRL_doc_type.SetFocus()
292
293
294
296 doc_type = self._LCTRL_doc_type.get_selected_item_data()
297
298 self._TCTRL_type.SetValue(doc_type['type'])
299 self._TCTRL_l10n_type.SetValue(doc_type['l10n_type'])
300
301 self._BTN_set_translation.Enable(True)
302 self._BTN_delete.Enable(not bool(doc_type['is_in_use']))
303 self._BTN_add.Enable(False)
304 self._BTN_reassign.Enable(True)
305
306 return
307
309 self._BTN_set_translation.Enable(False)
310 self._BTN_delete.Enable(False)
311 self._BTN_reassign.Enable(False)
312
313 self._BTN_add.Enable(True)
314
315 return
316
323
340
350
382
384 """Let user select a document type."""
386
387 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
388
389 mp = gmMatchProvider.cMatchProvider_SQL2 (
390 queries = [
391 u"""SELECT * FROM ((
392 SELECT
393 pk_doc_type AS data,
394 l10n_type AS field_label,
395 l10n_type AS list_label,
396 1 AS rank
397 FROM blobs.v_doc_type
398 WHERE
399 is_user_defined IS True
400 AND
401 l10n_type %(fragment_condition)s
402 ) UNION (
403 SELECT
404 pk_doc_type AS data,
405 l10n_type AS field_label,
406 l10n_type AS list_label,
407 2 AS rank
408 FROM blobs.v_doc_type
409 WHERE
410 is_user_defined IS False
411 AND
412 l10n_type %(fragment_condition)s
413 )) AS q1
414 ORDER BY q1.rank, q1.field_label"""]
415 )
416 mp.setThresholds(2, 4, 6)
417
418 self.matcher = mp
419 self.picklist_delay = 50
420
421 self.SetToolTipString(_('Select the document type.'))
422
424
425 doc_type = self.GetValue().strip()
426 if doc_type == u'':
427 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create document type without name.'), beep = True)
428 _log.debug('cannot create document type without name')
429 return
430
431 pk = gmDocuments.create_document_type(doc_type)['pk_doc_type']
432 if pk is None:
433 self.data = {}
434 else:
435 self.SetText (
436 value = doc_type,
437 data = pk
438 )
439
440 from Gnumed.wxGladeWidgets import wxgReviewDocPartDlg
441
444 """Support parts and docs now.
445 """
446 part = kwds['part']
447 del kwds['part']
448 wxgReviewDocPartDlg.wxgReviewDocPartDlg.__init__(self, *args, **kwds)
449
450 if isinstance(part, gmDocuments.cDocumentPart):
451 self.__part = part
452 self.__doc = self.__part.get_containing_document()
453 self.__reviewing_doc = False
454 elif isinstance(part, gmDocuments.cDocument):
455 self.__doc = part
456 self.__part = self.__doc.parts[0]
457 self.__reviewing_doc = True
458 else:
459 raise ValueError('<part> must be gmDocuments.cDocument or gmDocuments.cDocumentPart instance, got <%s>' % type(part))
460
461 self.__init_ui_data()
462
463
464
466
467
468 self._PhWheel_episode.SetText('%s ' % self.__part['episode'], self.__part['pk_episode'])
469 self._PhWheel_doc_type.SetText(value = self.__part['l10n_type'], data = self.__part['pk_type'])
470 self._PhWheel_doc_type.add_callback_on_set_focus(self._on_doc_type_gets_focus)
471 self._PhWheel_doc_type.add_callback_on_lose_focus(self._on_doc_type_loses_focus)
472
473 if self.__reviewing_doc:
474 self._PRW_doc_comment.SetText(gmTools.coalesce(self.__part['doc_comment'], ''))
475 self._PRW_doc_comment.set_context(context = 'pk_doc_type', val = self.__part['pk_type'])
476 else:
477 self._PRW_doc_comment.SetText(gmTools.coalesce(self.__part['obj_comment'], ''))
478
479 fts = gmDateTime.cFuzzyTimestamp(timestamp = self.__part['date_generated'])
480 self._PhWheel_doc_date.SetText(fts.strftime('%Y-%m-%d'), fts)
481 self._TCTRL_reference.SetValue(gmTools.coalesce(self.__part['ext_ref'], ''))
482 if self.__reviewing_doc:
483 self._TCTRL_filename.Enable(False)
484 self._SPINCTRL_seq_idx.Enable(False)
485 else:
486 self._TCTRL_filename.SetValue(gmTools.coalesce(self.__part['filename'], ''))
487 self._SPINCTRL_seq_idx.SetValue(gmTools.coalesce(self.__part['seq_idx'], 0))
488
489 self._LCTRL_existing_reviews.InsertColumn(0, _('who'))
490 self._LCTRL_existing_reviews.InsertColumn(1, _('when'))
491 self._LCTRL_existing_reviews.InsertColumn(2, _('+/-'))
492 self._LCTRL_existing_reviews.InsertColumn(3, _('!'))
493 self._LCTRL_existing_reviews.InsertColumn(4, _('comment'))
494
495 self.__reload_existing_reviews()
496
497 if self._LCTRL_existing_reviews.GetItemCount() > 0:
498 self._LCTRL_existing_reviews.SetColumnWidth(col=0, width=wx.LIST_AUTOSIZE)
499 self._LCTRL_existing_reviews.SetColumnWidth(col=1, width=wx.LIST_AUTOSIZE)
500 self._LCTRL_existing_reviews.SetColumnWidth(col=2, width=wx.LIST_AUTOSIZE_USEHEADER)
501 self._LCTRL_existing_reviews.SetColumnWidth(col=3, width=wx.LIST_AUTOSIZE_USEHEADER)
502 self._LCTRL_existing_reviews.SetColumnWidth(col=4, width=wx.LIST_AUTOSIZE)
503
504 me = gmPerson.gmCurrentProvider()
505 if self.__part['pk_intended_reviewer'] == me['pk_staff']:
506 msg = _('(you are the primary reviewer)')
507 else:
508 msg = _('(someone else is the primary reviewer)')
509 self._TCTRL_responsible.SetValue(msg)
510
511
512 if self.__part['reviewed_by_you']:
513 revs = self.__part.get_reviews()
514 for rev in revs:
515 if rev['is_your_review']:
516 self._ChBOX_abnormal.SetValue(bool(rev[2]))
517 self._ChBOX_relevant.SetValue(bool(rev[3]))
518 break
519
520 self._ChBOX_sign_all_pages.SetValue(self.__reviewing_doc)
521
522 return True
523
525 self._LCTRL_existing_reviews.DeleteAllItems()
526 revs = self.__part.get_reviews()
527 if len(revs) == 0:
528 return True
529
530 review_by_responsible_doc = None
531 reviews_by_others = []
532 for rev in revs:
533 if rev['is_review_by_responsible_reviewer'] and not rev['is_your_review']:
534 review_by_responsible_doc = rev
535 if not (rev['is_review_by_responsible_reviewer'] or rev['is_your_review']):
536 reviews_by_others.append(rev)
537
538 if review_by_responsible_doc is not None:
539 row_num = self._LCTRL_existing_reviews.InsertStringItem(sys.maxint, label=review_by_responsible_doc[0])
540 self._LCTRL_existing_reviews.SetItemTextColour(row_num, col=wx.BLUE)
541 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=0, label=review_by_responsible_doc[0])
542 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=1, label=review_by_responsible_doc[1].strftime('%x %H:%M'))
543 if review_by_responsible_doc['is_technically_abnormal']:
544 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=2, label=u'X')
545 if review_by_responsible_doc['clinically_relevant']:
546 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=3, label=u'X')
547 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=4, label=review_by_responsible_doc[6])
548 row_num += 1
549 for rev in reviews_by_others:
550 row_num = self._LCTRL_existing_reviews.InsertStringItem(sys.maxint, label=rev[0])
551 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=0, label=rev[0])
552 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=1, label=rev[1].strftime('%x %H:%M'))
553 if rev['is_technically_abnormal']:
554 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=2, label=u'X')
555 if rev['clinically_relevant']:
556 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=3, label=u'X')
557 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=4, label=rev[6])
558 return True
559
560
561
649
651 state = self._ChBOX_review.GetValue()
652 self._ChBOX_abnormal.Enable(enable = state)
653 self._ChBOX_relevant.Enable(enable = state)
654 self._ChBOX_responsible.Enable(enable = state)
655
657 """Per Jim: Changing the doc type happens a lot more often
658 then correcting spelling, hence select-all on getting focus.
659 """
660 self._PhWheel_doc_type.SetSelection(-1, -1)
661
663 pk_doc_type = self._PhWheel_doc_type.GetData()
664 if pk_doc_type is None:
665 self._PRW_doc_comment.unset_context(context = 'pk_doc_type')
666 else:
667 self._PRW_doc_comment.set_context(context = 'pk_doc_type', val = pk_doc_type)
668 return True
669
671
672 _log.debug('acquiring images from [%s]', device)
673
674
675
676 from Gnumed.pycommon import gmScanBackend
677 try:
678 fnames = gmScanBackend.acquire_pages_into_files (
679 device = device,
680 delay = 5,
681 calling_window = calling_window
682 )
683 except OSError:
684 _log.exception('problem acquiring image from source')
685 gmGuiHelpers.gm_show_error (
686 aMessage = _(
687 'No images could be acquired from the source.\n\n'
688 'This may mean the scanner driver is not properly installed.\n\n'
689 'On Windows you must install the TWAIN Python module\n'
690 'while on Linux and MacOSX it is recommended to install\n'
691 'the XSane package.'
692 ),
693 aTitle = _('Acquiring images')
694 )
695 return None
696
697 _log.debug('acquired %s images', len(fnames))
698
699 return fnames
700
701 from Gnumed.wxGladeWidgets import wxgScanIdxPnl
702
703 -class cScanIdxDocsPnl(wxgScanIdxPnl.wxgScanIdxPnl, gmPlugin.cPatientChange_PluginMixin):
724
725
726
729
731 pat = gmPerson.gmCurrentPatient()
732 if not pat.connected:
733 gmDispatcher.send(signal='statustext', msg=_('Cannot accept new documents. No active patient.'))
734 return
735
736
737 real_filenames = []
738 for pathname in filenames:
739 try:
740 files = os.listdir(pathname)
741 gmDispatcher.send(signal='statustext', msg=_('Extracting files from folder [%s] ...') % pathname)
742 for file in files:
743 fullname = os.path.join(pathname, file)
744 if not os.path.isfile(fullname):
745 continue
746 real_filenames.append(fullname)
747 except OSError:
748 real_filenames.append(pathname)
749
750 self.acquired_pages.extend(real_filenames)
751 self.__reload_LBOX_doc_pages()
752
755
756
757
761
762 - def _post_patient_selection(self, **kwds):
763 self.__init_ui_data()
764
765
766
768
769 self._PhWheel_episode.SetText('')
770 self._PhWheel_doc_type.SetText('')
771
772
773 fts = gmDateTime.cFuzzyTimestamp()
774 self._PhWheel_doc_date.SetText(fts.strftime('%Y-%m-%d'), fts)
775 self._PRW_doc_comment.SetText('')
776
777 self._PhWheel_reviewer.selection_only = True
778 me = gmPerson.gmCurrentProvider()
779 self._PhWheel_reviewer.SetText (
780 value = u'%s (%s%s %s)' % (me['short_alias'], me['title'], me['firstnames'], me['lastnames']),
781 data = me['pk_staff']
782 )
783
784
785 self._ChBOX_reviewed.SetValue(False)
786 self._ChBOX_abnormal.Disable()
787 self._ChBOX_abnormal.SetValue(False)
788 self._ChBOX_relevant.Disable()
789 self._ChBOX_relevant.SetValue(False)
790
791 self._TBOX_description.SetValue('')
792
793
794 self._LBOX_doc_pages.Clear()
795 self.acquired_pages = []
796
798 self._LBOX_doc_pages.Clear()
799 if len(self.acquired_pages) > 0:
800 for i in range(len(self.acquired_pages)):
801 fname = self.acquired_pages[i]
802 self._LBOX_doc_pages.Append(_('part %s: %s') % (i+1, fname), fname)
803
805 title = _('saving document')
806
807 if self.acquired_pages is None or len(self.acquired_pages) == 0:
808 dbcfg = gmCfg.cCfgSQL()
809 allow_empty = bool(dbcfg.get2 (
810 option = u'horstspace.scan_index.allow_partless_documents',
811 workplace = gmSurgery.gmCurrentPractice().active_workplace,
812 bias = 'user',
813 default = False
814 ))
815 if allow_empty:
816 save_empty = gmGuiHelpers.gm_show_question (
817 aMessage = _('No parts to save. Really save an empty document as a reference ?'),
818 aTitle = title
819 )
820 if not save_empty:
821 return False
822 else:
823 gmGuiHelpers.gm_show_error (
824 aMessage = _('No parts to save. Aquire some parts first.'),
825 aTitle = title
826 )
827 return False
828
829 doc_type_pk = self._PhWheel_doc_type.GetData(can_create = True)
830 if doc_type_pk is None:
831 gmGuiHelpers.gm_show_error (
832 aMessage = _('No document type applied. Choose a document type'),
833 aTitle = title
834 )
835 return False
836
837
838
839
840
841
842
843
844
845 if self._PhWheel_episode.GetValue().strip() == '':
846 gmGuiHelpers.gm_show_error (
847 aMessage = _('You must select an episode to save this document under.'),
848 aTitle = title
849 )
850 return False
851
852 if self._PhWheel_reviewer.GetData() is None:
853 gmGuiHelpers.gm_show_error (
854 aMessage = _('You need to select from the list of staff members the doctor who is intended to sign the document.'),
855 aTitle = title
856 )
857 return False
858
859 return True
860
862
863 if not reconfigure:
864 dbcfg = gmCfg.cCfgSQL()
865 device = dbcfg.get2 (
866 option = 'external.xsane.default_device',
867 workplace = gmSurgery.gmCurrentPractice().active_workplace,
868 bias = 'workplace',
869 default = ''
870 )
871 if device.strip() == u'':
872 device = None
873 if device is not None:
874 return device
875
876 try:
877 devices = self.scan_module.get_devices()
878 except:
879 _log.exception('cannot retrieve list of image sources')
880 gmDispatcher.send(signal = 'statustext', msg = _('There is no scanner support installed on this machine.'))
881 return None
882
883 if devices is None:
884
885
886 return None
887
888 if len(devices) == 0:
889 gmDispatcher.send(signal = 'statustext', msg = _('Cannot find an active scanner.'))
890 return None
891
892
893
894
895
896 device = gmListWidgets.get_choices_from_list (
897 parent = self,
898 msg = _('Select an image capture device'),
899 caption = _('device selection'),
900 choices = [ '%s (%s)' % (d[2], d[0]) for d in devices ],
901 columns = [_('Device')],
902 data = devices,
903 single_selection = True
904 )
905 if device is None:
906 return None
907
908
909 return device[0]
910
911
912
914
915 chosen_device = self.get_device_to_use()
916
917 tmpdir = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
918 try:
919 gmTools.mkdir(tmpdir)
920 except:
921 tmpdir = None
922
923
924
925 try:
926 fnames = self.scan_module.acquire_pages_into_files (
927 device = chosen_device,
928 delay = 5,
929 tmpdir = tmpdir,
930 calling_window = self
931 )
932 except OSError:
933 _log.exception('problem acquiring image from source')
934 gmGuiHelpers.gm_show_error (
935 aMessage = _(
936 'No pages could be acquired from the source.\n\n'
937 'This may mean the scanner driver is not properly installed.\n\n'
938 'On Windows you must install the TWAIN Python module\n'
939 'while on Linux and MacOSX it is recommended to install\n'
940 'the XSane package.'
941 ),
942 aTitle = _('acquiring page')
943 )
944 return None
945
946 if len(fnames) == 0:
947 return True
948
949 self.acquired_pages.extend(fnames)
950 self.__reload_LBOX_doc_pages()
951
952 return True
953
955
956 dlg = wx.FileDialog (
957 parent = None,
958 message = _('Choose a file'),
959 defaultDir = os.path.expanduser(os.path.join('~', 'gnumed')),
960 defaultFile = '',
961 wildcard = "%s (*)|*|TIFFs (*.tif)|*.tif|JPEGs (*.jpg)|*.jpg|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')),
962 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST | wx.MULTIPLE
963 )
964 result = dlg.ShowModal()
965 if result != wx.ID_CANCEL:
966 files = dlg.GetPaths()
967 for file in files:
968 self.acquired_pages.append(file)
969 self.__reload_LBOX_doc_pages()
970 dlg.Destroy()
971
973
974 page_idx = self._LBOX_doc_pages.GetSelection()
975 if page_idx == -1:
976 gmGuiHelpers.gm_show_info (
977 aMessage = _('You must select a part before you can view it.'),
978 aTitle = _('displaying part')
979 )
980 return None
981
982 page_fname = self._LBOX_doc_pages.GetClientData(page_idx)
983
984 (result, msg) = gmMimeLib.call_viewer_on_file(page_fname)
985 if not result:
986 gmGuiHelpers.gm_show_warning (
987 aMessage = _('Cannot display document part:\n%s') % msg,
988 aTitle = _('displaying part')
989 )
990 return None
991 return 1
992
994 page_idx = self._LBOX_doc_pages.GetSelection()
995 if page_idx == -1:
996 gmGuiHelpers.gm_show_info (
997 aMessage = _('You must select a part before you can delete it.'),
998 aTitle = _('deleting part')
999 )
1000 return None
1001 page_fname = self._LBOX_doc_pages.GetClientData(page_idx)
1002
1003
1004 self.acquired_pages[page_idx:(page_idx+1)] = []
1005
1006
1007 self.__reload_LBOX_doc_pages()
1008
1009
1010 do_delete = gmGuiHelpers.gm_show_question (
1011 _('The part has successfully been removed from the document.\n'
1012 '\n'
1013 'Do you also want to permanently delete the file\n'
1014 '\n'
1015 ' [%s]\n'
1016 '\n'
1017 'from which this document part was loaded ?\n'
1018 '\n'
1019 'If it is a temporary file for a page you just scanned\n'
1020 'this makes a lot of sense. In other cases you may not\n'
1021 'want to lose the file.\n'
1022 '\n'
1023 'Pressing [YES] will permanently remove the file\n'
1024 'from your computer.\n'
1025 ) % page_fname,
1026 _('Removing document part')
1027 )
1028 if do_delete:
1029 try:
1030 os.remove(page_fname)
1031 except:
1032 _log.exception('Error deleting file.')
1033 gmGuiHelpers.gm_show_error (
1034 aMessage = _('Cannot delete part in file [%s].\n\nYou may not have write access to it.') % page_fname,
1035 aTitle = _('deleting part')
1036 )
1037
1038 return 1
1039
1041
1042 if not self.__valid_for_save():
1043 return False
1044
1045 wx.BeginBusyCursor()
1046
1047 pat = gmPerson.gmCurrentPatient()
1048 doc_folder = pat.get_document_folder()
1049 emr = pat.get_emr()
1050
1051
1052 pk_episode = self._PhWheel_episode.GetData()
1053 if pk_episode is None:
1054 episode = emr.add_episode (
1055 episode_name = self._PhWheel_episode.GetValue().strip(),
1056 is_open = True
1057 )
1058 if episode is None:
1059 wx.EndBusyCursor()
1060 gmGuiHelpers.gm_show_error (
1061 aMessage = _('Cannot start episode [%s].') % self._PhWheel_episode.GetValue().strip(),
1062 aTitle = _('saving document')
1063 )
1064 return False
1065 pk_episode = episode['pk_episode']
1066
1067 encounter = emr.active_encounter['pk_encounter']
1068 document_type = self._PhWheel_doc_type.GetData()
1069 new_doc = doc_folder.add_document(document_type, encounter, pk_episode)
1070 if new_doc is None:
1071 wx.EndBusyCursor()
1072 gmGuiHelpers.gm_show_error (
1073 aMessage = _('Cannot create new document.'),
1074 aTitle = _('saving document')
1075 )
1076 return False
1077
1078
1079
1080 new_doc['clin_when'] = self._PhWheel_doc_date.GetData().get_pydt()
1081
1082 cfg = gmCfg.cCfgSQL()
1083 generate_uuid = bool (
1084 cfg.get2 (
1085 option = 'horstspace.scan_index.generate_doc_uuid',
1086 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1087 bias = 'user',
1088 default = False
1089 )
1090 )
1091 ref = None
1092 if generate_uuid:
1093 ref = gmDocuments.get_ext_ref()
1094 if ref is not None:
1095 new_doc['ext_ref'] = ref
1096
1097 comment = self._PRW_doc_comment.GetLineText(0).strip()
1098 if comment != u'':
1099 new_doc['comment'] = comment
1100
1101 if not new_doc.save_payload():
1102 wx.EndBusyCursor()
1103 gmGuiHelpers.gm_show_error (
1104 aMessage = _('Cannot update document metadata.'),
1105 aTitle = _('saving document')
1106 )
1107 return False
1108
1109 description = self._TBOX_description.GetValue().strip()
1110 if description != '':
1111 if not new_doc.add_description(description):
1112 wx.EndBusyCursor()
1113 gmGuiHelpers.gm_show_error (
1114 aMessage = _('Cannot add document description.'),
1115 aTitle = _('saving document')
1116 )
1117 return False
1118
1119
1120 success, msg, filename = new_doc.add_parts_from_files (
1121 files = self.acquired_pages,
1122 reviewer = self._PhWheel_reviewer.GetData()
1123 )
1124 if not success:
1125 wx.EndBusyCursor()
1126 gmGuiHelpers.gm_show_error (
1127 aMessage = msg,
1128 aTitle = _('saving document')
1129 )
1130 return False
1131
1132
1133 if self._ChBOX_reviewed.GetValue():
1134 if not new_doc.set_reviewed (
1135 technically_abnormal = self._ChBOX_abnormal.GetValue(),
1136 clinically_relevant = self._ChBOX_relevant.GetValue()
1137 ):
1138 msg = _('Error setting "reviewed" status of new document.')
1139
1140 gmHooks.run_hook_script(hook = u'after_new_doc_created')
1141
1142
1143 show_id = bool (
1144 cfg.get2 (
1145 option = 'horstspace.scan_index.show_doc_id',
1146 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1147 bias = 'user'
1148 )
1149 )
1150 wx.EndBusyCursor()
1151 if show_id:
1152 if ref is None:
1153 msg = _('Successfully saved the new document.')
1154 else:
1155 msg = _(
1156 """The reference ID for the new document is:
1157
1158 <%s>
1159
1160 You probably want to write it down on the
1161 original documents.
1162
1163 If you don't care about the ID you can switch
1164 off this message in the GNUmed configuration.""") % ref
1165 gmGuiHelpers.gm_show_info (
1166 aMessage = msg,
1167 aTitle = _('Saving document')
1168 )
1169 else:
1170 gmDispatcher.send(signal='statustext', msg=_('Successfully saved new document.'))
1171
1172 self.__init_ui_data()
1173 return True
1174
1176 self.__init_ui_data()
1177
1179 self._ChBOX_abnormal.Enable(enable = self._ChBOX_reviewed.GetValue())
1180 self._ChBOX_relevant.Enable(enable = self._ChBOX_reviewed.GetValue())
1181
1183 pk_doc_type = self._PhWheel_doc_type.GetData()
1184 if pk_doc_type is None:
1185 self._PRW_doc_comment.unset_context(context = 'pk_doc_type')
1186 else:
1187 self._PRW_doc_comment.set_context(context = 'pk_doc_type', val = pk_doc_type)
1188 return True
1189
1190 from Gnumed.wxGladeWidgets import wxgSelectablySortedDocTreePnl
1191
1193 """A panel with a document tree which can be sorted."""
1194
1195
1196
1201
1206
1211
1216
1217 -class cDocTree(wx.TreeCtrl, gmRegetMixin.cRegetOnPaintMixin):
1218
1219 """This wx.TreeCtrl derivative displays a tree view of stored medical documents.
1220
1221 It listens to document and patient changes and updated itself accordingly.
1222
1223 This acts on the current patient.
1224 """
1225 _sort_modes = ['age', 'review', 'episode', 'type']
1226 _root_node_labels = None
1227
1228 - def __init__(self, parent, id, *args, **kwds):
1229 """Set up our specialised tree.
1230 """
1231 kwds['style'] = wx.TR_NO_BUTTONS | wx.NO_BORDER | wx.TR_SINGLE
1232 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds)
1233
1234 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1235
1236 tmp = _('available documents (%s)')
1237 unsigned = _('unsigned (%s) on top') % u'\u270D'
1238 cDocTree._root_node_labels = {
1239 'age': tmp % _('most recent on top'),
1240 'review': tmp % unsigned,
1241 'episode': tmp % _('sorted by episode'),
1242 'type': tmp % _('sorted by type')
1243 }
1244
1245 self.root = None
1246 self.__sort_mode = 'age'
1247
1248 self.__build_context_menus()
1249 self.__register_interests()
1250 self._schedule_data_reget()
1251
1252
1253
1255
1256 node = self.GetSelection()
1257 node_data = self.GetPyData(node)
1258
1259 if not isinstance(node_data, gmDocuments.cDocumentPart):
1260 return True
1261
1262 self.__display_part(part = node_data)
1263 return True
1264
1265
1266
1268 return self.__sort_mode
1269
1287
1288 sort_mode = property(_get_sort_mode, _set_sort_mode)
1289
1290
1291
1293 curr_pat = gmPerson.gmCurrentPatient()
1294 if not curr_pat.connected:
1295 gmDispatcher.send(signal = 'statustext', msg = _('Cannot load documents. No active patient.'))
1296 return False
1297
1298 if not self.__populate_tree():
1299 return False
1300
1301 return True
1302
1303
1304
1306
1307 wx.EVT_TREE_ITEM_ACTIVATED (self, self.GetId(), self._on_activate)
1308 wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self.__on_right_click)
1309
1310
1311
1312 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
1313 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1314 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_doc_mod_db)
1315 gmDispatcher.connect(signal = u'doc_page_mod_db', receiver = self._on_doc_page_mod_db)
1316
1318
1319
1320 self.__part_context_menu = wx.Menu(title = _('Part Actions:'))
1321
1322 ID = wx.NewId()
1323 self.__part_context_menu.Append(ID, _('Display part'))
1324 wx.EVT_MENU(self.__part_context_menu, ID, self.__display_curr_part)
1325
1326 ID = wx.NewId()
1327 self.__part_context_menu.Append(ID, _('%s Sign/Edit properties') % u'\u270D')
1328 wx.EVT_MENU(self.__part_context_menu, ID, self.__review_curr_part)
1329
1330 self.__part_context_menu.AppendSeparator()
1331
1332 ID = wx.NewId()
1333 self.__part_context_menu.Append(ID, _('Print part'))
1334 wx.EVT_MENU(self.__part_context_menu, ID, self.__print_part)
1335
1336 ID = wx.NewId()
1337 self.__part_context_menu.Append(ID, _('Fax part'))
1338 wx.EVT_MENU(self.__part_context_menu, ID, self.__fax_part)
1339
1340 ID = wx.NewId()
1341 self.__part_context_menu.Append(ID, _('Mail part'))
1342 wx.EVT_MENU(self.__part_context_menu, ID, self.__mail_part)
1343
1344 self.__part_context_menu.AppendSeparator()
1345
1346
1347 self.__doc_context_menu = wx.Menu(title = _('Document Actions:'))
1348
1349 ID = wx.NewId()
1350 self.__doc_context_menu.Append(ID, _('%s Sign/Edit properties') % u'\u270D')
1351 wx.EVT_MENU(self.__doc_context_menu, ID, self.__review_curr_part)
1352
1353 self.__doc_context_menu.AppendSeparator()
1354
1355 ID = wx.NewId()
1356 self.__doc_context_menu.Append(ID, _('Print all parts'))
1357 wx.EVT_MENU(self.__doc_context_menu, ID, self.__print_doc)
1358
1359 ID = wx.NewId()
1360 self.__doc_context_menu.Append(ID, _('Fax all parts'))
1361 wx.EVT_MENU(self.__doc_context_menu, ID, self.__fax_doc)
1362
1363 ID = wx.NewId()
1364 self.__doc_context_menu.Append(ID, _('Mail all parts'))
1365 wx.EVT_MENU(self.__doc_context_menu, ID, self.__mail_doc)
1366
1367 ID = wx.NewId()
1368 self.__doc_context_menu.Append(ID, _('Export all parts'))
1369 wx.EVT_MENU(self.__doc_context_menu, ID, self.__export_doc_to_disk)
1370
1371 self.__doc_context_menu.AppendSeparator()
1372
1373 ID = wx.NewId()
1374 self.__doc_context_menu.Append(ID, _('Delete document'))
1375 wx.EVT_MENU(self.__doc_context_menu, ID, self.__delete_document)
1376
1377 ID = wx.NewId()
1378 self.__doc_context_menu.Append(ID, _('Access external original'))
1379 wx.EVT_MENU(self.__doc_context_menu, ID, self.__access_external_original)
1380
1381 ID = wx.NewId()
1382 self.__doc_context_menu.Append(ID, _('Edit corresponding encounter'))
1383 wx.EVT_MENU(self.__doc_context_menu, ID, self.__edit_encounter_details)
1384
1385 ID = wx.NewId()
1386 self.__doc_context_menu.Append(ID, _('Select corresponding encounter'))
1387 wx.EVT_MENU(self.__doc_context_menu, ID, self.__select_encounter)
1388
1389
1390
1391 ID = wx.NewId()
1392 self.__doc_context_menu.Append(ID, _('Manage descriptions'))
1393 wx.EVT_MENU(self.__doc_context_menu, ID, self.__manage_document_descriptions)
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1411
1412 wx.BeginBusyCursor()
1413
1414
1415 if self.root is not None:
1416 self.DeleteAllItems()
1417
1418
1419 self.root = self.AddRoot(cDocTree._root_node_labels[self.__sort_mode], -1, -1)
1420 self.SetPyData(self.root, None)
1421 self.SetItemHasChildren(self.root, False)
1422
1423
1424 curr_pat = gmPerson.gmCurrentPatient()
1425 docs_folder = curr_pat.get_document_folder()
1426 docs = docs_folder.get_documents()
1427
1428 if docs is None:
1429 gmGuiHelpers.gm_show_error (
1430 aMessage = _('Error searching documents.'),
1431 aTitle = _('loading document list')
1432 )
1433
1434 wx.EndBusyCursor()
1435 return True
1436
1437 if len(docs) == 0:
1438 wx.EndBusyCursor()
1439 return True
1440
1441
1442 self.SetItemHasChildren(self.root, True)
1443
1444
1445 intermediate_nodes = {}
1446 for doc in docs:
1447
1448 parts = doc.parts
1449
1450 label = _('%s%7s %s:%s (%s part(s)%s)') % (
1451 gmTools.bool2subst(doc.has_unreviewed_parts, gmTools.u_writing_hand, u'', u'?'),
1452 doc['clin_when'].strftime('%m/%Y'),
1453 doc['l10n_type'][:26],
1454 gmTools.coalesce(initial = doc['comment'], instead = u'', template_initial = u' %s'),
1455 len(parts),
1456 gmTools.coalesce(initial = doc['ext_ref'], instead = u'', template_initial = u', \u00BB%s\u00AB')
1457 )
1458
1459
1460 if self.__sort_mode == 'episode':
1461 lbl = doc['episode']
1462 if not intermediate_nodes.has_key(lbl):
1463 intermediate_nodes[lbl] = self.AppendItem(parent = self.root, text = lbl)
1464 self.SetItemBold(intermediate_nodes[lbl], bold = True)
1465 self.SetPyData(intermediate_nodes[lbl], None)
1466 parent = intermediate_nodes[lbl]
1467 elif self.__sort_mode == 'type':
1468 if not intermediate_nodes.has_key(doc['l10n_type']):
1469 intermediate_nodes[doc['l10n_type']] = self.AppendItem(parent = self.root, text = doc['l10n_type'])
1470 self.SetItemBold(intermediate_nodes[doc['l10n_type']], bold = True)
1471 self.SetPyData(intermediate_nodes[doc['l10n_type']], None)
1472 parent = intermediate_nodes[doc['l10n_type']]
1473 else:
1474 parent = self.root
1475
1476 doc_node = self.AppendItem(parent = parent, text = label)
1477
1478 self.SetPyData(doc_node, doc)
1479 if len(parts) > 0:
1480 self.SetItemHasChildren(doc_node, True)
1481
1482
1483 for part in parts:
1484
1485
1486
1487
1488 f_ext = u''
1489 if part['filename'] is not None:
1490 f_ext = os.path.splitext(part['filename'])[1].strip('.').strip()
1491 if f_ext != u'':
1492 f_ext = u' .' + f_ext.upper()
1493 label = '%s%s (%s%s)%s' % (
1494 gmTools.bool2str (
1495 boolean = part['reviewed'] or part['reviewed_by_you'] or part['reviewed_by_intended_reviewer'],
1496 true_str = u'',
1497 false_str = gmTools.u_writing_hand
1498 ),
1499 _('part %2s') % part['seq_idx'],
1500 gmTools.size2str(part['size']),
1501 f_ext,
1502 gmTools.coalesce (
1503 part['obj_comment'],
1504 u'',
1505 u': %s%%s%s' % (gmTools.u_left_double_angle_quote, gmTools.u_right_double_angle_quote)
1506 )
1507 )
1508
1509 part_node = self.AppendItem(parent = doc_node, text = label)
1510 self.SetPyData(part_node, part)
1511
1512 self.__sort_nodes()
1513 self.SelectItem(self.root)
1514
1515
1516
1517 self.Expand(self.root)
1518 if self.__sort_mode in ['episode', 'type']:
1519 for key in intermediate_nodes.keys():
1520 self.Expand(intermediate_nodes[key])
1521
1522 wx.EndBusyCursor()
1523
1524 return True
1525
1527 """Used in sorting items.
1528
1529 -1: 1 < 2
1530 0: 1 = 2
1531 1: 1 > 2
1532 """
1533
1534 if not node1.IsOk():
1535 _log.debug('no data on node 1')
1536 return 0
1537 if not node2.IsOk():
1538 _log.debug('no data on node 2')
1539 return 0
1540
1541 data1 = self.GetPyData(node1)
1542 data2 = self.GetPyData(node2)
1543
1544
1545 if isinstance(data1, gmDocuments.cDocument):
1546
1547 date_field = 'clin_when'
1548
1549
1550 if self.__sort_mode == 'age':
1551
1552 if data1[date_field] > data2[date_field]:
1553 return -1
1554 if data1[date_field] == data2[date_field]:
1555 return 0
1556 return 1
1557
1558 elif self.__sort_mode == 'episode':
1559 if data1['episode'] < data2['episode']:
1560 return -1
1561 if data1['episode'] == data2['episode']:
1562
1563 if data1[date_field] > data2[date_field]:
1564 return -1
1565 if data1[date_field] == data2[date_field]:
1566 return 0
1567 return 1
1568 return 1
1569
1570 elif self.__sort_mode == 'review':
1571
1572 if data1.has_unreviewed_parts == data2.has_unreviewed_parts:
1573
1574 if data1[date_field] > data2[date_field]:
1575 return -1
1576 if data1[date_field] == data2[date_field]:
1577 return 0
1578 return 1
1579 if data1.has_unreviewed_parts:
1580 return -1
1581 return 1
1582
1583 elif self.__sort_mode == 'type':
1584 if data1['l10n_type'] < data2['l10n_type']:
1585 return -1
1586 if data1['l10n_type'] == data2['l10n_type']:
1587
1588 if data1[date_field] > data2[date_field]:
1589 return -1
1590 if data1[date_field] == data2[date_field]:
1591 return 0
1592 return 1
1593 return 1
1594
1595 else:
1596 _log.error('unknown document sort mode [%s], reverse-sorting by age', self.__sort_mode)
1597
1598 if data1[date_field] > data2[date_field]:
1599 return -1
1600 if data1[date_field] == data2[date_field]:
1601 return 0
1602 return 1
1603
1604
1605 if isinstance(data1, gmDocuments.cDocumentPart):
1606
1607
1608 if data1['seq_idx'] < data2['seq_idx']:
1609 return -1
1610 if data1['seq_idx'] == data2['seq_idx']:
1611 return 0
1612 return 1
1613
1614
1615 if None in [data1, data2]:
1616 l1 = self.GetItemText(node1)
1617 l2 = self.GetItemText(node2)
1618 if l1 < l2:
1619 return -1
1620 if l1 == l2:
1621 return 0
1622 else:
1623 if data1 < data2:
1624 return -1
1625 if data1 == data2:
1626 return 0
1627 return 1
1628
1629
1630
1632
1633 wx.CallAfter(self._schedule_data_reget)
1634
1635 - def _on_doc_page_mod_db(self, *args, **kwargs):
1636
1637 wx.CallAfter(self._schedule_data_reget)
1638
1640
1641
1642
1643 if self.root is not None:
1644 self.DeleteAllItems()
1645 self.root = None
1646
1647 - def _on_post_patient_selection(self, *args, **kwargs):
1648
1649 self._schedule_data_reget()
1650
1652 node = event.GetItem()
1653 node_data = self.GetPyData(node)
1654
1655
1656 if node_data is None:
1657 return None
1658
1659
1660 if isinstance(node_data, gmDocuments.cDocument):
1661 self.Toggle(node)
1662 return True
1663
1664
1665 if type(node_data) == type('string'):
1666 self.Toggle(node)
1667 return True
1668
1669 self.__display_part(part = node_data)
1670 return True
1671
1673
1674 node = evt.GetItem()
1675 self.__curr_node_data = self.GetPyData(node)
1676
1677
1678 if self.__curr_node_data is None:
1679 return None
1680
1681
1682 if isinstance(self.__curr_node_data, gmDocuments.cDocument):
1683 self.__handle_doc_context()
1684
1685
1686 if isinstance(self.__curr_node_data, gmDocuments.cDocumentPart):
1687 self.__handle_part_context()
1688
1689 del self.__curr_node_data
1690 evt.Skip()
1691
1694
1696 self.__display_part(part = self.__curr_node_data)
1697
1699 self.__review_part(part = self.__curr_node_data)
1700
1703
1704
1705
1707
1708 if start_node is None:
1709 start_node = self.GetRootItem()
1710
1711
1712
1713 if not start_node.IsOk():
1714 return True
1715
1716 self.SortChildren(start_node)
1717
1718 child_node, cookie = self.GetFirstChild(start_node)
1719 while child_node.IsOk():
1720 self.__sort_nodes(start_node = child_node)
1721 child_node, cookie = self.GetNextChild(start_node, cookie)
1722
1723 return
1724
1726 self.PopupMenu(self.__doc_context_menu, wx.DefaultPosition)
1727
1729
1730
1731 if self.__curr_node_data['type'] == 'patient photograph':
1732 ID = wx.NewId()
1733 self.__part_context_menu.Append(ID, _('Activate as current photo'))
1734 wx.EVT_MENU(self.__part_context_menu, ID, self.__activate_as_current_photo)
1735 else:
1736 ID = None
1737
1738 self.PopupMenu(self.__part_context_menu, wx.DefaultPosition)
1739
1740 if ID is not None:
1741 self.__part_context_menu.Delete(ID)
1742
1743
1744
1746 """Display document part."""
1747
1748
1749 if part['size'] == 0:
1750 _log.debug('cannot display part [%s] - 0 bytes', part['pk_obj'])
1751 gmGuiHelpers.gm_show_error (
1752 aMessage = _('Document part does not seem to exist in database !'),
1753 aTitle = _('showing document')
1754 )
1755 return None
1756
1757 wx.BeginBusyCursor()
1758
1759 cfg = gmCfg.cCfgSQL()
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773 chunksize = int(
1774 cfg.get2 (
1775 option = "horstspace.blob_export_chunk_size",
1776 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1777 bias = 'workplace',
1778 default = default_chunksize
1779 ))
1780
1781
1782 block_during_view = bool( cfg.get2 (
1783 option = 'horstspace.document_viewer.block_during_view',
1784 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1785 bias = 'user',
1786 default = None
1787 ))
1788
1789
1790 successful, msg = part.display_via_mime (
1791
1792 chunksize = chunksize,
1793 block = block_during_view
1794 )
1795
1796 wx.EndBusyCursor()
1797
1798 if not successful:
1799 gmGuiHelpers.gm_show_error (
1800 aMessage = _('Cannot display document part:\n%s') % msg,
1801 aTitle = _('showing document')
1802 )
1803 return None
1804
1805
1806
1807
1808
1809
1810
1811 review_after_display = int(cfg.get2 (
1812 option = 'horstspace.document_viewer.review_after_display',
1813 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1814 bias = 'user',
1815 default = 3
1816 ))
1817 if review_after_display == 1:
1818 self.__review_part(part=part)
1819 elif review_after_display == 2:
1820 review_by_me = filter(lambda rev: rev['is_your_review'], part.get_reviews())
1821 if len(review_by_me) == 0:
1822 self.__review_part(part = part)
1823 elif review_after_display == 3:
1824 if len(part.get_reviews()) == 0:
1825 self.__review_part(part = part)
1826 elif review_after_display == 4:
1827 reviewed_by_responsible = filter(lambda rev: rev['is_review_by_responsible_reviewer'], part.get_reviews())
1828 if len(reviewed_by_responsible) == 0:
1829 self.__review_part(part = part)
1830
1831 return True
1832
1834 dlg = cReviewDocPartDlg (
1835 parent = self,
1836 id = -1,
1837 part = part
1838 )
1839 dlg.ShowModal()
1840 dlg.Destroy()
1841
1843
1844 gmHooks.run_hook_script(hook = u'before_%s_doc_part' % action)
1845
1846 wx.BeginBusyCursor()
1847
1848
1849 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc' % action)
1850 if not found:
1851 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc.bat' % action)
1852 if not found:
1853 _log.error('neither of gm-%s_doc or gm-%s_doc.bat found', action, action)
1854 wx.EndBusyCursor()
1855 gmGuiHelpers.gm_show_error (
1856 _('Cannot %(l10n_action)s document part - %(l10n_action)s command not found.\n'
1857 '\n'
1858 'Either of gm_%(action)s_doc.sh or gm_%(action)s_doc.bat\n'
1859 'must be in the execution path. The command will\n'
1860 'be passed the filename to %(l10n_action)s.'
1861 ) % {'action': action, 'l10n_action': l10n_action},
1862 _('Processing document part: %s') % l10n_action
1863 )
1864 return
1865
1866 cfg = gmCfg.cCfgSQL()
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880 chunksize = int(cfg.get2 (
1881 option = "horstspace.blob_export_chunk_size",
1882 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1883 bias = 'workplace',
1884 default = default_chunksize
1885 ))
1886
1887 part_file = self.__curr_node_data.export_to_file (
1888
1889 aChunkSize = chunksize
1890 )
1891
1892 cmd = u'%s %s' % (external_cmd, part_file)
1893 success = gmShellAPI.run_command_in_shell (
1894 command = cmd,
1895 blocking = False
1896 )
1897
1898 wx.EndBusyCursor()
1899
1900 if not success:
1901 _log.error('%s command failed: [%s]', action, cmd)
1902 gmGuiHelpers.gm_show_error (
1903 _('Cannot %(l10n_action)s document part - %(l10n_action)s command failed.\n'
1904 '\n'
1905 'You may need to check and fix either of\n'
1906 ' gm_%(action)s_doc.sh (Unix/Mac) or\n'
1907 ' gm_%(action)s_doc.bat (Windows)\n'
1908 '\n'
1909 'The command is passed the filename to %(l10n_action)s.'
1910 ) % {'action': action, 'l10n_action': l10n_action},
1911 _('Processing document part: %s') % l10n_action
1912 )
1913
1914
1916 self.__process_part(action = u'print', l10n_action = _('print'))
1917
1919 self.__process_part(action = u'fax', l10n_action = _('fax'))
1920
1922 self.__process_part(action = u'mail', l10n_action = _('mail'))
1923
1924
1925
1935
1939
1941
1942 gmHooks.run_hook_script(hook = u'before_%s_doc' % action)
1943
1944 wx.BeginBusyCursor()
1945
1946
1947 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc' % action)
1948 if not found:
1949 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc.bat' % action)
1950 if not found:
1951 _log.error('neither of gm-%s_doc or gm-%s_doc.bat found', action, action)
1952 wx.EndBusyCursor()
1953 gmGuiHelpers.gm_show_error (
1954 _('Cannot %(l10n_action)s document - %(l10n_action)s command not found.\n'
1955 '\n'
1956 'Either of gm_%(action)s_doc.sh or gm_%(action)s_doc.bat\n'
1957 'must be in the execution path. The command will\n'
1958 'be passed a list of filenames to %(l10n_action)s.'
1959 ) % {'action': action, 'l10n_action': l10n_action},
1960 _('Processing document: %s') % l10n_action
1961 )
1962 return
1963
1964 cfg = gmCfg.cCfgSQL()
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978 chunksize = int(cfg.get2 (
1979 option = "horstspace.blob_export_chunk_size",
1980 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1981 bias = 'workplace',
1982 default = default_chunksize
1983 ))
1984
1985 part_files = self.__curr_node_data.export_parts_to_files (
1986
1987 chunksize = chunksize
1988 )
1989
1990 cmd = external_cmd + u' ' + u' '.join(part_files)
1991 success = gmShellAPI.run_command_in_shell (
1992 command = cmd,
1993 blocking = False
1994 )
1995
1996 wx.EndBusyCursor()
1997
1998 if not success:
1999 _log.error('%s command failed: [%s]', action, cmd)
2000 gmGuiHelpers.gm_show_error (
2001 _('Cannot %(l10n_action)s document - %(l10n_action)s command failed.\n'
2002 '\n'
2003 'You may need to check and fix either of\n'
2004 ' gm_%(action)s_doc.sh (Unix/Mac) or\n'
2005 ' gm_%(action)s_doc.bat (Windows)\n'
2006 '\n'
2007 'The command is passed a list of filenames to %(l10n_action)s.'
2008 ) % {'action': action, 'l10n_action': l10n_action},
2009 _('Processing document: %s') % l10n_action
2010 )
2011
2012
2014 self.__process_doc(action = u'print', l10n_action = _('print'))
2015
2017 self.__process_doc(action = u'fax', l10n_action = _('fax'))
2018
2020 self.__process_doc(action = u'mail', l10n_action = _('mail'))
2021
2023
2024 gmHooks.run_hook_script(hook = u'before_external_doc_access')
2025
2026 wx.BeginBusyCursor()
2027
2028
2029 found, external_cmd = gmShellAPI.detect_external_binary(u'gm_access_external_doc.sh')
2030 if not found:
2031 found, external_cmd = gmShellAPI.detect_external_binary(u'gm_access_external_doc.bat')
2032 if not found:
2033 _log.error('neither of gm_access_external_doc.sh or .bat found')
2034 wx.EndBusyCursor()
2035 gmGuiHelpers.gm_show_error (
2036 _('Cannot access external document - access command not found.\n'
2037 '\n'
2038 'Either of gm_access_external_doc.sh or *.bat must be\n'
2039 'in the execution path. The command will be passed the\n'
2040 'document type and the reference URL for processing.'
2041 ),
2042 _('Accessing external document')
2043 )
2044 return
2045
2046 cmd = u'%s "%s" "%s"' % (external_cmd, self.__curr_node_data['type'], self.__curr_node_data['ext_ref'])
2047 success = gmShellAPI.run_command_in_shell (
2048 command = cmd,
2049 blocking = False
2050 )
2051
2052 wx.EndBusyCursor()
2053
2054 if not success:
2055 _log.error('External access command failed: [%s]', cmd)
2056 gmGuiHelpers.gm_show_error (
2057 _('Cannot access external document - access command failed.\n'
2058 '\n'
2059 'You may need to check and fix either of\n'
2060 ' gm_access_external_doc.sh (Unix/Mac) or\n'
2061 ' gm_access_external_doc.bat (Windows)\n'
2062 '\n'
2063 'The command is passed the document type and the\n'
2064 'external reference URL on the command line.'
2065 ),
2066 _('Accessing external document')
2067 )
2068
2070 """Export document into directory.
2071
2072 - one file per object
2073 - into subdirectory named after patient
2074 """
2075 pat = gmPerson.gmCurrentPatient()
2076 dname = '%s-%s%s' % (
2077 self.__curr_node_data['l10n_type'],
2078 self.__curr_node_data['clin_when'].strftime('%Y-%m-%d'),
2079 gmTools.coalesce(self.__curr_node_data['ext_ref'], '', '-%s').replace(' ', '_')
2080 )
2081 def_dir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'docs', pat['dirname'], dname))
2082 gmTools.mkdir(def_dir)
2083
2084 dlg = wx.DirDialog (
2085 parent = self,
2086 message = _('Save document into directory ...'),
2087 defaultPath = def_dir,
2088 style = wx.DD_DEFAULT_STYLE
2089 )
2090 result = dlg.ShowModal()
2091 dirname = dlg.GetPath()
2092 dlg.Destroy()
2093
2094 if result != wx.ID_OK:
2095 return True
2096
2097 wx.BeginBusyCursor()
2098
2099 cfg = gmCfg.cCfgSQL()
2100
2101
2102 chunksize = int(cfg.get2 (
2103 option = "horstspace.blob_export_chunk_size",
2104 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2105 bias = 'workplace',
2106 default = default_chunksize
2107 ))
2108
2109 fnames = self.__curr_node_data.export_parts_to_files(export_dir = dirname, chunksize = chunksize)
2110
2111 wx.EndBusyCursor()
2112
2113 gmDispatcher.send(signal='statustext', msg=_('Successfully exported %s parts into the directory [%s].') % (len(fnames), dirname))
2114
2115 return True
2116
2127
2128
2129
2130 if __name__ == '__main__':
2131
2132 gmI18N.activate_locale()
2133 gmI18N.install_domain(domain = 'gnumed')
2134
2135
2136
2137 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2138
2139 pass
2140
2141
2142