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