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