1 """GNUmed patient export area widgets."""
2
3 __author__ = "Karsten.Hilbert@gmx.net"
4 __license__ = "GPL v2 or later"
5
6
7 import sys
8 import logging
9 import os.path
10 import shutil
11
12
13
14 import wx
15
16
17
18 if __name__ == '__main__':
19 sys.path.insert(0, '../../')
20
21 from Gnumed.pycommon import gmDispatcher
22 from Gnumed.pycommon import gmTools
23 from Gnumed.pycommon import gmMimeLib
24 from Gnumed.pycommon import gmDateTime
25 from Gnumed.pycommon import gmPrinting
26 from Gnumed.pycommon import gmShellAPI
27 from Gnumed.pycommon import gmNetworkTools
28 from Gnumed.pycommon import gmCfg2
29 from Gnumed.pycommon import gmLog2
30
31 from Gnumed.business import gmPerson
32 from Gnumed.business import gmExportArea
33 from Gnumed.business import gmPraxis
34
35 from Gnumed.wxpython import gmRegetMixin
36 from Gnumed.wxpython import gmGuiHelpers
37 from Gnumed.wxpython import gmDocumentWidgets
38
39
40 _log = logging.getLogger('gm.ui')
41 _cfg = gmCfg2.gmCfgData()
42
43
45 try:
46 del kwargs['signal']
47 del kwargs['sender']
48 except KeyError:
49 pass
50 wx.CallAfter(add_file_to_export_area, **kwargs)
51
53 try:
54 del kwargs['signal']
55 del kwargs['sender']
56 except KeyError:
57 pass
58 wx.CallAfter(add_files_to_export_area, **kwargs)
59
60
68
69
71 pat = gmPerson.gmCurrentPatient()
72 if not pat.connected:
73 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add files to export area. No patient.'), beep = True)
74 return False
75
76 wx.BeginBusyCursor()
77 if parent is None:
78 parent = wx.GetApp().GetTopWindow()
79 if not pat.export_area.add_files(filenames = filenames, hint = hint):
80 wx.EndBusyCursor()
81 gmGuiHelpers.gm_show_error (
82 aMessage = _('Cannot import files into export area.'),
83 aTitle = _('Export area')
84 )
85 return False
86
87 wx.EndBusyCursor()
88
89 tmp_dir = gmTools.gmPaths().tmp_dir
90 files2remove = [ f for f in filenames if not f.startswith(tmp_dir) ]
91 if len(files2remove) > 0:
92 do_delete = gmGuiHelpers.gm_show_question (
93 _( 'Successfully imported files into export area.\n'
94 '\n'
95 'Do you want to delete imported files from the filesystem ?\n'
96 '\n'
97 ' %s'
98 ) % '\n '.join(files2remove),
99 _('Removing files')
100 )
101 if do_delete:
102 for fname in files2remove:
103 gmTools.remove_file(fname)
104 else:
105 gmDispatcher.send(signal = 'statustext', msg = _('Imported files into export area.'), beep = True)
106 return True
107
108
109
110
111
112 gmDispatcher.connect(signal = 'add_file_to_export_area', receiver = _add_file_to_export_area)
113 gmDispatcher.connect(signal = 'add_files_to_export_area', receiver = _add_files_to_export_area)
114
115
116 from Gnumed.wxGladeWidgets import wxgExportAreaExportToMediaDlg
117
367
368
369 from Gnumed.wxGladeWidgets import wxgExportAreaSaveAsDlg
370
372
374 self.__patient = kwargs['patient']
375 del kwargs['patient']
376 self.__item_count = kwargs['item_count']
377 del kwargs['item_count']
378 super().__init__(*args, **kwargs)
379
380 self.__init_ui()
381
382
383
384
386 event.Skip()
387 self.__update_ui_state()
388
389
405
406
413
414
435
436
440
441
442
443
445 msg = ('\n' + _('Number of entries to save: %s') + '\n\n' + _('Patient: %s') + '\n') % (
446 self.__item_count,
447 self.__patient['description_gender']
448 )
449 self._LBL_header.Label = msg
450 self._LBL_directory.Label = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', self.__patient.subdir_name) + os.sep
451 self.__update_ui_state()
452
453
455 subdir_name = self.__patient.subdir_name
456 path = self._LBL_directory.Label.strip().rstrip(os.sep).rstrip('/')
457 if self._CHBOX_use_subdirectory.IsChecked():
458
459 if not path.endswith(subdir_name):
460 path = os.path.join(path, subdir_name)
461 self._LBL_directory.Label = path + os.sep
462 else:
463
464 if path.endswith(subdir_name):
465 path = path[:-len(subdir_name)].rstrip(os.sep).rstrip('/')
466 self._LBL_directory.Label = path + os.sep
467
468 is_empty = gmTools.dir_is_empty(directory = path)
469 if is_empty is True:
470 self._LBL_dir_is_empty.Label = ''
471 self._BTN_open_directory.Disable()
472 self._BTN_clear_directory.Disable()
473 self._BTN_save_files.Enable()
474 self._BTN_save_archive.Enable()
475 elif is_empty is False:
476 self._LBL_dir_is_empty.Label = _('directory contains data')
477 self._BTN_open_directory.Enable()
478 self._BTN_clear_directory.Enable()
479 self._BTN_save_files.Disable()
480 self._BTN_save_archive.Disable()
481 else:
482 self._LBL_dir_is_empty.Label = ''
483 self._BTN_open_directory.Disable()
484 self._BTN_clear_directory.Disable()
485 self._BTN_save_files.Enable()
486 self._BTN_save_archive.Enable()
487
488 self.Layout()
489
490
491 from Gnumed.wxGladeWidgets import wxgExportAreaPluginPnl
492
493 -class cExportAreaPluginPnl(wxgExportAreaPluginPnl.wxgExportAreaPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
494 """Panel holding a number of items for further processing.
495
496 Acts on the current patient.
497
498 Used as notebook page."""
504
505
506
507
509 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
510
511 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_table_mod)
512
513
516
517
519 if kwargs['table'] != 'clin.export_item':
520 return
521 pat = gmPerson.gmCurrentPatient()
522 if not pat.connected:
523 return
524 if kwargs['pk_identity'] != pat.ID:
525 return
526 self._schedule_data_reget()
527
528
531
532
539
540
559
560
579
580
591
592
605
606
618
619
633
634
659
660
669
670
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
805
806
810
811
843
844
897
898
900 self._populate_with_data()
901
902
903
904
906 self._LCTRL_items.set_columns([_('By'), _('When'), _('Description')])
907
908 self._BTN_archive_items.Disable()
909
910
911 self.__mail_script_exists, path = gmShellAPI.detect_external_binary(binary = r'gm-mail_doc')
912 if not self.__mail_script_exists:
913 self._BTN_mail_items.Disable()
914 tt = self._BTN_mail_items.GetToolTipText() + '\n\n' + _('<gm-mail_doc(.bat) not found>')
915 self._BTN_mail_items.SetToolTip(tt)
916
917 self.__fax_script_exists, path = gmShellAPI.detect_external_binary(binary = r'gm-fax_doc')
918 if not self.__fax_script_exists:
919 self._BTN_fax_items.Disable()
920 tt = self._BTN_fax_items.GetToolTipText() + '\n\n' + _('<gm-fax_doc(.bat) not found>')
921 self._BTN_fax_items.SetToolTip(tt)
922
923 self.__burn_script_exists, path = gmShellAPI.detect_external_binary(binary = r'gm-burn_doc')
924 if not self.__burn_script_exists:
925 self._BTN_burn_items.Disable()
926 tt = self._BTN_burn_items.GetToolTipText() + '\n\n' + _('<gm-burn_doc(.bat) not found>')
927 self._BTN_burn_items.SetToolTip(tt)
928
929
930 dt = gmGuiHelpers.cFileDropTarget(target = self)
931 self.SetDropTarget(dt)
932 dt = gmGuiHelpers.cFileDropTarget(on_drop_callback = self._drop_target_consume_filenames)
933 self._LCTRL_items.SetDropTarget(dt)
934
935
946
947
948 - def __export_as_files(self, msg_title, base_dir=None, encrypt=False, with_metadata=False, items=None):
949
950 data_pwd = None
951 if encrypt:
952 data_pwd = self.__get_password(msg_title)
953 if data_pwd is None:
954 _log.debug('user aborted by not providing the same password twice')
955 gmDispatcher.send(signal = 'statustext', msg = _('Password not provided twice. Aborting.'))
956 return None
957
958 wx.BeginBusyCursor()
959 try:
960 exp_area = gmPerson.gmCurrentPatient().export_area
961 if with_metadata:
962 export_dir = exp_area.export(base_dir = base_dir, items = items, passphrase = data_pwd)
963 else:
964 export_dir = exp_area.dump_items_to_disk(base_dir = base_dir, items = items, passphrase = data_pwd)
965 finally:
966 wx.EndBusyCursor()
967 if export_dir is None:
968 gmGuiHelpers.gm_show_error (
969 aMessage = _('Error exporting entries.'),
970 aTitle = msg_title
971 )
972 return None
973
974 return export_dir
975
976
978
979 zip_pwd = None
980 if encrypt:
981 zip_pwd = self.__get_password(msg_title)
982 if zip_pwd is None:
983 _log.debug('user aborted by not providing the same password twice')
984 gmDispatcher.send(signal = 'statustext', msg = _('Password not provided twice. Aborting.'))
985 return None
986
987 wx.BeginBusyCursor()
988 zip_file = None
989 try:
990 exp_area = gmPerson.gmCurrentPatient().export_area
991 zip_file = exp_area.export_as_zip(passphrase = zip_pwd, items = items)
992 except Exception:
993 _log.exception('cannot create zip file')
994 wx.EndBusyCursor()
995 if zip_file is None:
996 gmGuiHelpers.gm_show_error (
997 aMessage = _('Error creating zip file.'),
998 aTitle = msg_title
999 )
1000 return zip_file
1001
1002
1004 while True:
1005 data_pwd = wx.GetPasswordFromUser (
1006 message = _(
1007 'Enter passphrase to protect the data with.\n'
1008 '\n'
1009 '(minimum length: 5, trailing blanks will be stripped)'
1010 ),
1011 caption = msg_title
1012 )
1013
1014 data_pwd = data_pwd.rstrip()
1015 if len(data_pwd) > 4:
1016 break
1017 retry = gmGuiHelpers.gm_show_question (
1018 title = msg_title,
1019 question = _(
1020 'Insufficient passphrase.\n'
1021 '\n'
1022 '(minimum length: 5, trailing blanks will be stripped)\n'
1023 '\n'
1024 'Enter another passphrase ?'
1025 )
1026 )
1027 if not retry:
1028
1029 return None
1030
1031 gmLog2.add_word2hide(data_pwd)
1032
1033 while True:
1034 data_pwd4comparison = wx.GetPasswordFromUser (
1035 message = _(
1036 'Once more enter passphrase to protect the data with.\n'
1037 '\n'
1038 '(this will protect you from typos)\n'
1039 '\n'
1040 'Abort by leaving empty.'
1041 ),
1042 caption = msg_title
1043 )
1044 data_pwd4comparison = data_pwd4comparison.rstrip()
1045 if data_pwd4comparison == '':
1046
1047 return None
1048 if data_pwd == data_pwd4comparison:
1049 break
1050 gmGuiHelpers.gm_show_error (
1051 error = _(
1052 'Passphrases do not match.\n'
1053 '\n'
1054 'Retry, or abort with an empty passphrase.'
1055 ),
1056 title = msg_title
1057 )
1058 return data_pwd
1059
1060
1062
1063 items = self._LCTRL_items.get_selected_item_data(only_one = False)
1064 if len(items) > 0:
1065 return items
1066
1067 items = self._LCTRL_items.get_item_data()
1068 if len(items) == 0:
1069 gmDispatcher.send(signal = 'statustext', msg = _('Export area empty. Nothing to do.'))
1070 return None
1071
1072 if len(items) == 1:
1073 return items
1074
1075 process_all = gmGuiHelpers.gm_show_question (
1076 title = msg_title,
1077 question = _('You have not selected any entries.\n\nReally use all %s entries ?') % len(items),
1078 cancel_button = False
1079 )
1080 if process_all:
1081 return items
1082
1083 return None
1084
1085
1087
1088 _log.debug('gm-burn_doc(.bat) API: "DIRECTORY-TO-BURN-FROM"')
1089
1090 found, burn_cmd = gmShellAPI.detect_external_binary('gm-burn_doc')
1091 if not found:
1092 gmDispatcher.send(signal = 'statustext', msg = _('Cannot burn to disk: Helper not found.'))
1093 return False
1094
1095 args = [burn_cmd, base_dir]
1096 wx.BeginBusyCursor()
1097 try:
1098 success, ret_code, stdout = gmShellAPI.run_process(cmd_line = args, verbose = _cfg.get(option = 'debug'))
1099 finally:
1100 wx.EndBusyCursor()
1101 if success:
1102 return True
1103
1104 gmGuiHelpers.gm_show_error (
1105 aMessage = _('Error burning documents to CD/DVD.'),
1106 aTitle = _('Burning documents')
1107 )
1108 return False
1109
1110
1112
1113 msg = _('Documents saved into:\n\n %s') % base_dir
1114 browse_index = gmGuiHelpers.gm_show_question (
1115 title = _('Browsing patient data excerpt'),
1116 question = msg + '\n\n' + _('Browse saved entries ?'),
1117 cancel_button = False
1118 )
1119 if not browse_index:
1120 return
1121
1122 if os.path.isfile(os.path.join(base_dir, 'index.html')):
1123 gmNetworkTools.open_url_in_browser(url = 'file://%s' % os.path.join(base_dir, 'index.html'))
1124 return
1125
1126 gmMimeLib.call_viewer_on_file(base_dir, block = False)
1127
1128
1129
1130
1132 pat = gmPerson.gmCurrentPatient()
1133 if not pat.connected:
1134 gmDispatcher.send(signal = 'statustext', msg = _('Cannot accept new documents. No active patient.'))
1135 return
1136
1137
1138 real_filenames = []
1139 for pathname in filenames:
1140 try:
1141 files = os.listdir(pathname)
1142 gmDispatcher.send(signal='statustext', msg=_('Extracting files from folder [%s] ...') % pathname)
1143 for file in files:
1144 fullname = os.path.join(pathname, file)
1145 if not os.path.isfile(fullname):
1146 continue
1147 real_filenames.append(fullname)
1148 except OSError:
1149 real_filenames.append(pathname)
1150
1151 if not pat.export_area.add_files(real_filenames, hint = _('Drag&Drop')):
1152 gmGuiHelpers.gm_show_error (
1153 title = _('Adding files to export area'),
1154 error = _('Cannot add (some of) the following files to the export area:\n%s ') % '\n '.join(real_filenames)
1155 )
1156
1157
1158
1159
1160
1161
1162
1163
1182
1183
1184 from Gnumed.wxGladeWidgets import wxgPrintMgrPluginPnl
1185
1186 -class cPrintMgrPluginPnl(wxgPrintMgrPluginPnl.wxgPrintMgrPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
1187 """Panel holding print jobs.
1188
1189 Used as notebook page."""
1190
1196
1197
1198
1200 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1201 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1202 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_table_mod)
1203
1205 self._RBTN_active_patient_only.Enable(False)
1206 self._RBTN_all_patients.Value = True
1207 self._BTN_export_printouts.Enable(False)
1208
1210 self._RBTN_active_patient_only.Enable(True)
1211 self._BTN_export_printouts.Enable(True)
1212
1214 if kwargs['table'] != 'clin.export_item':
1215 return
1216 if self._RBTN_all_patients.Value is True:
1217 self._schedule_data_reget()
1218 return
1219 pat = gmPerson.gmCurrentPatient()
1220 if not pat.connected:
1221 return
1222 if kwargs['pk_identity'] != pat.ID:
1223 return
1224 self._schedule_data_reget()
1225
1227 event.Skip()
1228 self._schedule_data_reget()
1229
1231 event.Skip()
1232 self._schedule_data_reget()
1233
1240
1264
1273
1288
1289
1290
1291
1293 self._BTN_export_printouts.Enable(False)
1294
1295
1296
1298 if self._RBTN_all_patients.Value is True:
1299 columns = [_('Patient'), _('Provider'), _('Description')]
1300 printouts = gmExportArea.get_print_jobs(order_by = 'pk_identity, description')
1301 items = [[
1302 '%s, %s (%s)' % (
1303 p['lastnames'],
1304 p['firstnames'],
1305 p['gender']
1306 ),
1307 p['created_by'],
1308 p['description']
1309 ] for p in printouts ]
1310 else:
1311 pat = gmPerson.gmCurrentPatient()
1312 if pat.connected:
1313 columns = [_('Provider'), _('Created'), _('Description')]
1314 printouts = pat.export_area.get_printouts(order_by = 'created_when, description')
1315 items = [[
1316 p['created_by'],
1317 gmDateTime.pydt_strftime(p['created_when'], '%Y %b %d %H:%M'),
1318 p['description']
1319 ] for p in printouts ]
1320 else:
1321 columns = [_('Patient'), _('Provider'), _('Description')]
1322 printouts = gmExportArea.get_print_jobs(order_by = 'pk_identity, description')
1323 items = [[
1324 '%s, %s (%s)' % (
1325 p['lastnames'],
1326 p['firstnames'],
1327 p['gender']
1328 ),
1329 p['created_by'],
1330 p['description']
1331 ] for p in printouts ]
1332 self._LCTRL_printouts.set_columns(columns)
1333 self._LCTRL_printouts.set_string_items(items)
1334 self._LCTRL_printouts.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1335 self._LCTRL_printouts.set_data(printouts)
1336 self._LCTRL_printouts.SetFocus()
1337 return True
1338