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
391 event.Skip()
392 self.__update_ui_state()
393
394
410
411
418
419
440
441
445
446
447
448
450 msg = ('\n' + _('Number of entries to save: %s') + '\n\n' + _('Patient: %s') + '\n') % (
451 self.__item_count,
452 self.__patient['description_gender']
453 )
454 self._LBL_header.Label = msg
455 self._LBL_directory.Label = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', self.__patient.subdir_name) + os.sep
456 self.__update_ui_state()
457
458
460 subdir_name = self.__patient.subdir_name
461 path = self._LBL_directory.Label.strip().rstrip(os.sep).rstrip('/')
462 if self._CHBOX_use_subdirectory.IsChecked():
463
464 if not path.endswith(subdir_name):
465 path = os.path.join(path, subdir_name)
466 self._LBL_directory.Label = path + os.sep
467 else:
468
469 if path.endswith(subdir_name):
470 path = path[:-len(subdir_name)].rstrip(os.sep).rstrip('/')
471 self._LBL_directory.Label = path + os.sep
472
473 if self._CHBOX_encrypt.IsChecked():
474 self._CHBOX_convert2pdf.Enable()
475 else:
476 self._CHBOX_convert2pdf.Disable()
477
478 is_empty = gmTools.dir_is_empty(directory = path)
479 if is_empty is True:
480 self._LBL_dir_is_empty.Label = ''
481 self._BTN_open_directory.Disable()
482 self._BTN_clear_directory.Disable()
483 self._BTN_save_files.Enable()
484 self._BTN_save_archive.Enable()
485 elif is_empty is False:
486 self._LBL_dir_is_empty.Label = _('directory contains data')
487 self._BTN_open_directory.Enable()
488 self._BTN_clear_directory.Enable()
489 self._BTN_save_files.Disable()
490 self._BTN_save_archive.Disable()
491 else:
492 self._LBL_dir_is_empty.Label = ''
493 self._BTN_open_directory.Disable()
494 self._BTN_clear_directory.Disable()
495 self._BTN_save_files.Enable()
496 self._BTN_save_archive.Enable()
497
498 self.Layout()
499
500
501 from Gnumed.wxGladeWidgets import wxgExportAreaPluginPnl
502
503 -class cExportAreaPluginPnl(wxgExportAreaPluginPnl.wxgExportAreaPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
504 """Panel holding a number of items for further processing.
505
506 Acts on the current patient.
507
508 Used as notebook page."""
514
515
516
517
519 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
520
521 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_table_mod)
522
523
526
527
529 if kwargs['table'] != 'clin.export_item':
530 return
531 pat = gmPerson.gmCurrentPatient()
532 if not pat.connected:
533 return
534 if kwargs['pk_identity'] != pat.ID:
535 return
536 self._schedule_data_reget()
537
538
541
542
549
550
569
570
589
590
601
602
615
616
628
629
643
644
669
670
679
680
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
826
827
831
832
864
865
918
919
921 self._populate_with_data()
922
923
924
925
927 self._LCTRL_items.set_columns([_('By'), _('When'), _('Description')])
928
929 self._BTN_archive_items.Disable()
930
931
932 self.__mail_script_exists, path = gmShellAPI.detect_external_binary(binary = r'gm-mail_doc')
933 if not self.__mail_script_exists:
934 self._BTN_mail_items.Disable()
935 tt = self._BTN_mail_items.GetToolTipText() + '\n\n' + _('<gm-mail_doc(.bat) not found>')
936 self._BTN_mail_items.SetToolTip(tt)
937
938 self.__fax_script_exists, path = gmShellAPI.detect_external_binary(binary = r'gm-fax_doc')
939 if not self.__fax_script_exists:
940 self._BTN_fax_items.Disable()
941 tt = self._BTN_fax_items.GetToolTipText() + '\n\n' + _('<gm-fax_doc(.bat) not found>')
942 self._BTN_fax_items.SetToolTip(tt)
943
944
945
946
947
948
949
950
951
952 dt = gmGuiHelpers.cFileDropTarget(target = self)
953 self.SetDropTarget(dt)
954 dt = gmGuiHelpers.cFileDropTarget(on_drop_callback = self._drop_target_consume_filenames)
955 self._LCTRL_items.SetDropTarget(dt)
956
957
968
969
970 - def __export_as_files(self, msg_title, base_dir=None, encrypt=False, with_metadata=False, items=None, convert2pdf=False):
971 data_pwd = None
972 if encrypt:
973 data_pwd = self.__get_password(msg_title)
974 if data_pwd is None:
975 _log.debug('user aborted by not providing the same password twice')
976 gmDispatcher.send(signal = 'statustext', msg = _('Password not provided twice. Aborting.'))
977 return None
978
979 wx.BeginBusyCursor()
980 try:
981 exp_area = gmPerson.gmCurrentPatient().export_area
982 if with_metadata:
983 export_dir = exp_area.export(base_dir = base_dir, items = items, passphrase = data_pwd)
984 else:
985 export_dir = exp_area.dump_items_to_disk(base_dir = base_dir, items = items, passphrase = data_pwd, convert2pdf = convert2pdf)
986 finally:
987 wx.EndBusyCursor()
988 if export_dir is None:
989 gmGuiHelpers.gm_show_error (
990 aMessage = _('Error exporting entries.'),
991 aTitle = msg_title
992 )
993 return None
994
995 return export_dir
996
997
999
1000 zip_pwd = None
1001 if encrypt:
1002 zip_pwd = self.__get_password(msg_title)
1003 if zip_pwd is None:
1004 _log.debug('user aborted by not providing the same password twice')
1005 gmDispatcher.send(signal = 'statustext', msg = _('Password not provided twice. Aborting.'))
1006 return None
1007
1008 wx.BeginBusyCursor()
1009 zip_file = None
1010 try:
1011 exp_area = gmPerson.gmCurrentPatient().export_area
1012 zip_file = exp_area.export_as_zip(passphrase = zip_pwd, items = items)
1013 except Exception:
1014 _log.exception('cannot create zip file')
1015 wx.EndBusyCursor()
1016 if zip_file is None:
1017 gmGuiHelpers.gm_show_error (
1018 aMessage = _('Error creating zip file.'),
1019 aTitle = msg_title
1020 )
1021 return zip_file
1022
1023
1025 while True:
1026 data_pwd = wx.GetPasswordFromUser (
1027 message = _(
1028 'Enter passphrase to protect the data with.\n'
1029 '\n'
1030 '(minimum length: 5, trailing blanks will be stripped)'
1031 ),
1032 caption = msg_title
1033 )
1034
1035 data_pwd = data_pwd.rstrip()
1036 if len(data_pwd) > 4:
1037 break
1038 retry = gmGuiHelpers.gm_show_question (
1039 title = msg_title,
1040 question = _(
1041 'Insufficient passphrase.\n'
1042 '\n'
1043 '(minimum length: 5, trailing blanks will be stripped)\n'
1044 '\n'
1045 'Enter another passphrase ?'
1046 )
1047 )
1048 if not retry:
1049
1050 return None
1051
1052 gmLog2.add_word2hide(data_pwd)
1053
1054 while True:
1055 data_pwd4comparison = wx.GetPasswordFromUser (
1056 message = _(
1057 'Once more enter passphrase to protect the data with.\n'
1058 '\n'
1059 '(this will protect you from typos)\n'
1060 '\n'
1061 'Abort by leaving empty.'
1062 ),
1063 caption = msg_title
1064 )
1065 data_pwd4comparison = data_pwd4comparison.rstrip()
1066 if data_pwd4comparison == '':
1067
1068 return None
1069 if data_pwd == data_pwd4comparison:
1070 break
1071 gmGuiHelpers.gm_show_error (
1072 error = _(
1073 'Passphrases do not match.\n'
1074 '\n'
1075 'Retry, or abort with an empty passphrase.'
1076 ),
1077 title = msg_title
1078 )
1079 return data_pwd
1080
1081
1083
1084 items = self._LCTRL_items.get_selected_item_data(only_one = False)
1085 if len(items) > 0:
1086 return items
1087
1088 items = self._LCTRL_items.get_item_data()
1089 if len(items) == 0:
1090 gmDispatcher.send(signal = 'statustext', msg = _('Export area empty. Nothing to do.'))
1091 return None
1092
1093 if len(items) == 1:
1094 return items
1095
1096 process_all = gmGuiHelpers.gm_show_question (
1097 title = msg_title,
1098 question = _('You have not selected any entries.\n\nReally use all %s entries ?') % len(items),
1099 cancel_button = False
1100 )
1101 if process_all:
1102 return items
1103
1104 return None
1105
1106
1108
1109 _log.debug('gm-burn_doc(.bat) API: "DIRECTORY-TO-BURN-FROM"')
1110
1111 found, burn_cmd = gmShellAPI.detect_external_binary('gm-burn_doc')
1112 if not found:
1113 gmDispatcher.send(signal = 'statustext', msg = _('Cannot burn to disk: Helper not found.'))
1114 return False
1115
1116 args = [burn_cmd, base_dir]
1117 wx.BeginBusyCursor()
1118 try:
1119 success, ret_code, stdout = gmShellAPI.run_process(cmd_line = args, verbose = _cfg.get(option = 'debug'))
1120 finally:
1121 wx.EndBusyCursor()
1122 if success:
1123 return True
1124
1125 gmGuiHelpers.gm_show_error (
1126 aMessage = _('Error burning documents to CD/DVD.'),
1127 aTitle = _('Burning documents')
1128 )
1129 return False
1130
1131
1133
1134 msg = _('Documents saved into:\n\n %s') % base_dir
1135 browse_index = gmGuiHelpers.gm_show_question (
1136 title = _('Browsing patient data excerpt'),
1137 question = msg + '\n\n' + _('Browse saved entries ?'),
1138 cancel_button = False
1139 )
1140 if not browse_index:
1141 return
1142
1143 if os.path.isfile(os.path.join(base_dir, 'index.html')):
1144 gmNetworkTools.open_url_in_browser(url = 'file://%s' % os.path.join(base_dir, 'index.html'))
1145 return
1146
1147 gmMimeLib.call_viewer_on_file(base_dir, block = False)
1148
1149
1150
1151
1153 pat = gmPerson.gmCurrentPatient()
1154 if not pat.connected:
1155 gmDispatcher.send(signal = 'statustext', msg = _('Cannot accept new documents. No active patient.'))
1156 return
1157
1158
1159 real_filenames = []
1160 for pathname in filenames:
1161 try:
1162 files = os.listdir(pathname)
1163 gmDispatcher.send(signal='statustext', msg=_('Extracting files from folder [%s] ...') % pathname)
1164 for file in files:
1165 fullname = os.path.join(pathname, file)
1166 if not os.path.isfile(fullname):
1167 continue
1168 real_filenames.append(fullname)
1169 except OSError:
1170 real_filenames.append(pathname)
1171
1172 if not pat.export_area.add_files(real_filenames, hint = _('Drag&Drop')):
1173 gmGuiHelpers.gm_show_error (
1174 title = _('Adding files to export area'),
1175 error = _('Cannot add (some of) the following files to the export area:\n%s ') % '\n '.join(real_filenames)
1176 )
1177
1178
1179
1180
1181
1182
1183
1184
1203
1204
1205 from Gnumed.wxGladeWidgets import wxgPrintMgrPluginPnl
1206
1207 -class cPrintMgrPluginPnl(wxgPrintMgrPluginPnl.wxgPrintMgrPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
1208 """Panel holding print jobs.
1209
1210 Used as notebook page."""
1211
1217
1218
1219
1221 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1222 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1223 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_table_mod)
1224
1226 self._RBTN_active_patient_only.Enable(False)
1227 self._RBTN_all_patients.Value = True
1228 self._BTN_export_printouts.Enable(False)
1229
1231 self._RBTN_active_patient_only.Enable(True)
1232 self._BTN_export_printouts.Enable(True)
1233
1235 if kwargs['table'] != 'clin.export_item':
1236 return
1237 if self._RBTN_all_patients.Value is True:
1238 self._schedule_data_reget()
1239 return
1240 pat = gmPerson.gmCurrentPatient()
1241 if not pat.connected:
1242 return
1243 if kwargs['pk_identity'] != pat.ID:
1244 return
1245 self._schedule_data_reget()
1246
1248 event.Skip()
1249 self._schedule_data_reget()
1250
1252 event.Skip()
1253 self._schedule_data_reget()
1254
1261
1285
1294
1309
1310
1311
1312
1314 self._BTN_export_printouts.Enable(False)
1315
1316
1317
1319 if self._RBTN_all_patients.Value is True:
1320 columns = [_('Patient'), _('Provider'), _('Description')]
1321 printouts = gmExportArea.get_print_jobs(order_by = 'pk_identity, description')
1322 items = [[
1323 '%s, %s (%s)' % (
1324 p['lastnames'],
1325 p['firstnames'],
1326 p['gender']
1327 ),
1328 p['created_by'],
1329 p['description']
1330 ] for p in printouts ]
1331 else:
1332 pat = gmPerson.gmCurrentPatient()
1333 if pat.connected:
1334 columns = [_('Provider'), _('Created'), _('Description')]
1335 printouts = pat.export_area.get_printouts(order_by = 'created_when, description')
1336 items = [[
1337 p['created_by'],
1338 gmDateTime.pydt_strftime(p['created_when'], '%Y %b %d %H:%M'),
1339 p['description']
1340 ] for p in printouts ]
1341 else:
1342 columns = [_('Patient'), _('Provider'), _('Description')]
1343 printouts = gmExportArea.get_print_jobs(order_by = 'pk_identity, description')
1344 items = [[
1345 '%s, %s (%s)' % (
1346 p['lastnames'],
1347 p['firstnames'],
1348 p['gender']
1349 ),
1350 p['created_by'],
1351 p['description']
1352 ] for p in printouts ]
1353 self._LCTRL_printouts.set_columns(columns)
1354 self._LCTRL_printouts.set_string_items(items)
1355 self._LCTRL_printouts.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1356 self._LCTRL_printouts.set_data(printouts)
1357 self._LCTRL_printouts.SetFocus()
1358 return True
1359