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