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 self.__burn_script_exists, path = gmShellAPI.detect_external_binary(binary = r'gm-burn_doc')
945 if not self.__burn_script_exists:
946 self._BTN_burn_items.Disable()
947 tt = self._BTN_burn_items.GetToolTipText() + '\n\n' + _('<gm-burn_doc(.bat) not found>')
948 self._BTN_burn_items.SetToolTip(tt)
949
950
951 dt = gmGuiHelpers.cFileDropTarget(target = self)
952 self.SetDropTarget(dt)
953 dt = gmGuiHelpers.cFileDropTarget(on_drop_callback = self._drop_target_consume_filenames)
954 self._LCTRL_items.SetDropTarget(dt)
955
956
967
968
969 - def __export_as_files(self, msg_title, base_dir=None, encrypt=False, with_metadata=False, items=None, convert2pdf=False):
970 data_pwd = None
971 if encrypt:
972 data_pwd = self.__get_password(msg_title)
973 if data_pwd is None:
974 _log.debug('user aborted by not providing the same password twice')
975 gmDispatcher.send(signal = 'statustext', msg = _('Password not provided twice. Aborting.'))
976 return None
977
978 wx.BeginBusyCursor()
979 try:
980 exp_area = gmPerson.gmCurrentPatient().export_area
981 if with_metadata:
982 export_dir = exp_area.export(base_dir = base_dir, items = items, passphrase = data_pwd)
983 else:
984 export_dir = exp_area.dump_items_to_disk(base_dir = base_dir, items = items, passphrase = data_pwd, convert2pdf = convert2pdf)
985 finally:
986 wx.EndBusyCursor()
987 if export_dir is None:
988 gmGuiHelpers.gm_show_error (
989 aMessage = _('Error exporting entries.'),
990 aTitle = msg_title
991 )
992 return None
993
994 return export_dir
995
996
998
999 zip_pwd = None
1000 if encrypt:
1001 zip_pwd = self.__get_password(msg_title)
1002 if zip_pwd is None:
1003 _log.debug('user aborted by not providing the same password twice')
1004 gmDispatcher.send(signal = 'statustext', msg = _('Password not provided twice. Aborting.'))
1005 return None
1006
1007 wx.BeginBusyCursor()
1008 zip_file = None
1009 try:
1010 exp_area = gmPerson.gmCurrentPatient().export_area
1011 zip_file = exp_area.export_as_zip(passphrase = zip_pwd, items = items)
1012 except Exception:
1013 _log.exception('cannot create zip file')
1014 wx.EndBusyCursor()
1015 if zip_file is None:
1016 gmGuiHelpers.gm_show_error (
1017 aMessage = _('Error creating zip file.'),
1018 aTitle = msg_title
1019 )
1020 return zip_file
1021
1022
1024 while True:
1025 data_pwd = wx.GetPasswordFromUser (
1026 message = _(
1027 'Enter passphrase to protect the data with.\n'
1028 '\n'
1029 '(minimum length: 5, trailing blanks will be stripped)'
1030 ),
1031 caption = msg_title
1032 )
1033
1034 data_pwd = data_pwd.rstrip()
1035 if len(data_pwd) > 4:
1036 break
1037 retry = gmGuiHelpers.gm_show_question (
1038 title = msg_title,
1039 question = _(
1040 'Insufficient passphrase.\n'
1041 '\n'
1042 '(minimum length: 5, trailing blanks will be stripped)\n'
1043 '\n'
1044 'Enter another passphrase ?'
1045 )
1046 )
1047 if not retry:
1048
1049 return None
1050
1051 gmLog2.add_word2hide(data_pwd)
1052
1053 while True:
1054 data_pwd4comparison = wx.GetPasswordFromUser (
1055 message = _(
1056 'Once more enter passphrase to protect the data with.\n'
1057 '\n'
1058 '(this will protect you from typos)\n'
1059 '\n'
1060 'Abort by leaving empty.'
1061 ),
1062 caption = msg_title
1063 )
1064 data_pwd4comparison = data_pwd4comparison.rstrip()
1065 if data_pwd4comparison == '':
1066
1067 return None
1068 if data_pwd == data_pwd4comparison:
1069 break
1070 gmGuiHelpers.gm_show_error (
1071 error = _(
1072 'Passphrases do not match.\n'
1073 '\n'
1074 'Retry, or abort with an empty passphrase.'
1075 ),
1076 title = msg_title
1077 )
1078 return data_pwd
1079
1080
1082
1083 items = self._LCTRL_items.get_selected_item_data(only_one = False)
1084 if len(items) > 0:
1085 return items
1086
1087 items = self._LCTRL_items.get_item_data()
1088 if len(items) == 0:
1089 gmDispatcher.send(signal = 'statustext', msg = _('Export area empty. Nothing to do.'))
1090 return None
1091
1092 if len(items) == 1:
1093 return items
1094
1095 process_all = gmGuiHelpers.gm_show_question (
1096 title = msg_title,
1097 question = _('You have not selected any entries.\n\nReally use all %s entries ?') % len(items),
1098 cancel_button = False
1099 )
1100 if process_all:
1101 return items
1102
1103 return None
1104
1105
1107
1108 _log.debug('gm-burn_doc(.bat) API: "DIRECTORY-TO-BURN-FROM"')
1109
1110 found, burn_cmd = gmShellAPI.detect_external_binary('gm-burn_doc')
1111 if not found:
1112 gmDispatcher.send(signal = 'statustext', msg = _('Cannot burn to disk: Helper not found.'))
1113 return False
1114
1115 args = [burn_cmd, base_dir]
1116 wx.BeginBusyCursor()
1117 try:
1118 success, ret_code, stdout = gmShellAPI.run_process(cmd_line = args, verbose = _cfg.get(option = 'debug'))
1119 finally:
1120 wx.EndBusyCursor()
1121 if success:
1122 return True
1123
1124 gmGuiHelpers.gm_show_error (
1125 aMessage = _('Error burning documents to CD/DVD.'),
1126 aTitle = _('Burning documents')
1127 )
1128 return False
1129
1130
1132
1133 msg = _('Documents saved into:\n\n %s') % base_dir
1134 browse_index = gmGuiHelpers.gm_show_question (
1135 title = _('Browsing patient data excerpt'),
1136 question = msg + '\n\n' + _('Browse saved entries ?'),
1137 cancel_button = False
1138 )
1139 if not browse_index:
1140 return
1141
1142 if os.path.isfile(os.path.join(base_dir, 'index.html')):
1143 gmNetworkTools.open_url_in_browser(url = 'file://%s' % os.path.join(base_dir, 'index.html'))
1144 return
1145
1146 gmMimeLib.call_viewer_on_file(base_dir, block = False)
1147
1148
1149
1150
1152 pat = gmPerson.gmCurrentPatient()
1153 if not pat.connected:
1154 gmDispatcher.send(signal = 'statustext', msg = _('Cannot accept new documents. No active patient.'))
1155 return
1156
1157
1158 real_filenames = []
1159 for pathname in filenames:
1160 try:
1161 files = os.listdir(pathname)
1162 gmDispatcher.send(signal='statustext', msg=_('Extracting files from folder [%s] ...') % pathname)
1163 for file in files:
1164 fullname = os.path.join(pathname, file)
1165 if not os.path.isfile(fullname):
1166 continue
1167 real_filenames.append(fullname)
1168 except OSError:
1169 real_filenames.append(pathname)
1170
1171 if not pat.export_area.add_files(real_filenames, hint = _('Drag&Drop')):
1172 gmGuiHelpers.gm_show_error (
1173 title = _('Adding files to export area'),
1174 error = _('Cannot add (some of) the following files to the export area:\n%s ') % '\n '.join(real_filenames)
1175 )
1176
1177
1178
1179
1180
1181
1182
1183
1202
1203
1204 from Gnumed.wxGladeWidgets import wxgPrintMgrPluginPnl
1205
1206 -class cPrintMgrPluginPnl(wxgPrintMgrPluginPnl.wxgPrintMgrPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
1207 """Panel holding print jobs.
1208
1209 Used as notebook page."""
1210
1216
1217
1218
1220 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1221 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1222 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_table_mod)
1223
1225 self._RBTN_active_patient_only.Enable(False)
1226 self._RBTN_all_patients.Value = True
1227 self._BTN_export_printouts.Enable(False)
1228
1230 self._RBTN_active_patient_only.Enable(True)
1231 self._BTN_export_printouts.Enable(True)
1232
1234 if kwargs['table'] != 'clin.export_item':
1235 return
1236 if self._RBTN_all_patients.Value is True:
1237 self._schedule_data_reget()
1238 return
1239 pat = gmPerson.gmCurrentPatient()
1240 if not pat.connected:
1241 return
1242 if kwargs['pk_identity'] != pat.ID:
1243 return
1244 self._schedule_data_reget()
1245
1247 event.Skip()
1248 self._schedule_data_reget()
1249
1251 event.Skip()
1252 self._schedule_data_reget()
1253
1260
1284
1293
1308
1309
1310
1311
1313 self._BTN_export_printouts.Enable(False)
1314
1315
1316
1318 if self._RBTN_all_patients.Value is True:
1319 columns = [_('Patient'), _('Provider'), _('Description')]
1320 printouts = gmExportArea.get_print_jobs(order_by = 'pk_identity, description')
1321 items = [[
1322 '%s, %s (%s)' % (
1323 p['lastnames'],
1324 p['firstnames'],
1325 p['gender']
1326 ),
1327 p['created_by'],
1328 p['description']
1329 ] for p in printouts ]
1330 else:
1331 pat = gmPerson.gmCurrentPatient()
1332 if pat.connected:
1333 columns = [_('Provider'), _('Created'), _('Description')]
1334 printouts = pat.export_area.get_printouts(order_by = 'created_when, description')
1335 items = [[
1336 p['created_by'],
1337 gmDateTime.pydt_strftime(p['created_when'], '%Y %b %d %H:%M'),
1338 p['description']
1339 ] for p in printouts ]
1340 else:
1341 columns = [_('Patient'), _('Provider'), _('Description')]
1342 printouts = gmExportArea.get_print_jobs(order_by = 'pk_identity, description')
1343 items = [[
1344 '%s, %s (%s)' % (
1345 p['lastnames'],
1346 p['firstnames'],
1347 p['gender']
1348 ),
1349 p['created_by'],
1350 p['description']
1351 ] for p in printouts ]
1352 self._LCTRL_printouts.set_columns(columns)
1353 self._LCTRL_printouts.set_string_items(items)
1354 self._LCTRL_printouts.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1355 self._LCTRL_printouts.set_data(printouts)
1356 self._LCTRL_printouts.SetFocus()
1357 return True
1358