Package Gnumed :: Package wxpython :: Module gmExportAreaWidgets
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmExportAreaWidgets

   1  """GNUmed patient export area widgets.""" 
   2  #================================================================ 
   3  __author__ = "Karsten.Hilbert@gmx.net" 
   4  __license__ = "GPL v2 or later" 
   5   
   6  # std lib 
   7  import sys 
   8  import logging 
   9  import os.path 
  10  import shutil 
  11   
  12   
  13  # 3rd party 
  14  import wx 
  15   
  16   
  17  # GNUmed libs 
  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  #============================================================ 
  44  from Gnumed.wxGladeWidgets import wxgExportAreaExportToMediaDlg 
  45   
46 -class cExportAreaExportToMediaDlg(wxgExportAreaExportToMediaDlg.wxgExportAreaExportToMediaDlg):
47
48 - def __init__(self, *args, **kwargs):
49 self.__patient = kwargs['patient'] 50 del kwargs['patient'] 51 self.__item_count = kwargs['item_count'] 52 del kwargs['item_count'] 53 super().__init__(*args, **kwargs) 54 55 self.__init_ui()
56 57 #-------------------------------------------------------- 58 # event handling 59 #--------------------------------------------------------
61 event.Skip() 62 wx.BeginBusyCursor() 63 try: 64 self.__update_media_list() 65 finally: 66 wx.EndBusyCursor()
67 68 #--------------------------------------------------------
69 - def _on_media_selected(self, event):
70 self.__update_ui_state()
71 72 #--------------------------------------------------------
73 - def _on_media_deselected(self, event):
74 self.__update_ui_state()
75 76 #--------------------------------------------------------
77 - def _on_use_subdirectory_toggled(self, event):
78 event.Skip() 79 self.__update_ui_state()
80 81 #--------------------------------------------------------
82 - def _on_open_directory_button_pressed(self, event):
83 event.Skip() 84 path = self.__calc_path() 85 if not os.path.isdir(path): 86 return 87 gmMimeLib.call_viewer_on_file(path, block = False)
88 89 #--------------------------------------------------------
90 - def _on_clear_directory_button_pressed(self, event):
91 event.Skip() 92 path = self.__calc_path() 93 if not os.path.isdir(path): 94 return 95 96 delete_data = gmGuiHelpers.gm_show_question ( 97 title = _('Clearing out a directory'), 98 question = _( 99 'Do you really want to delete all existing data\n' 100 'from the following directory ?\n' 101 '\n' 102 ' %s\n' 103 '\n' 104 'Note, this can NOT be reversed without backups.' 105 ) % path, 106 cancel_button = False 107 ) 108 if delete_data: 109 gmTools.rm_dir_content(path) 110 self.__update_ui_state()
111 112 #--------------------------------------------------------
113 - def _on_save2media_button_pressed(self, event):
114 event.Skip() 115 116 path = self.__calc_path() 117 if path is None: 118 return # no media selected, should not happen 119 if path == -2: # not mounted, should not happen 120 return 121 if path == -1: # optical drive 122 if self.__burn_script is None: 123 return # no burn script, should not happen 124 125 self.EndModal(wx.ID_SAVE)
126 127 #-------------------------------------------------------- 128 # internal API 129 #--------------------------------------------------------
130 - def __init_ui(self):
131 msg = _( 132 '\n' 133 'Number of entries to export: %s\n' 134 '\n' 135 'Patient: %s\n' 136 '\n' 137 'Select the media to export onto below.\n' 138 ) % ( 139 self.__item_count, 140 self.__patient['description_gender'] 141 ) 142 self._LBL_header.Label = msg 143 self._LCTRL_removable_media.set_columns([_('Type'), _('Medium'), _('Details')]) 144 self._LCTRL_removable_media.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE]) 145 self._LCTRL_removable_media.set_resize_column() 146 self._LCTRL_removable_media.select_callback = self._on_media_selected 147 self._LCTRL_removable_media.deselect_callback = self._on_media_deselected 148 149 self.__update_media_list() 150 self.__update_ui_state()
151 152 #--------------------------------------------------------
153 - def __calc_path(self):
154 media = self._LCTRL_removable_media.get_selected_item_data(only_one = True) 155 if media is None: 156 return None 157 158 if media['type'] == 'cd': 159 return -1 160 161 if media['is_mounted'] is False: 162 return -2 163 164 mnt_path = media['mountpoint'] 165 if self._CHBOX_use_subdirectory.IsChecked(): 166 return os.path.join(mnt_path, self.__patient.subdir_name) 167 168 return mnt_path
169 170 #--------------------------------------------------------
171 - def __update_media_list(self):
172 173 self._LCTRL_removable_media.remove_items_safely() 174 items = [] 175 data = [] 176 177 found, self.__burn_script = gmShellAPI.detect_external_binary('gm-burn_doc') 178 179 # USB / MMC drives 180 removable_partitions = gmTools.enumerate_removable_partitions() 181 for key in removable_partitions: 182 part = removable_partitions[key] 183 if part['is_mounted'] is False: 184 continue 185 items.append ([ 186 part['bus'].upper(), 187 _('%s (%s %s) - %s free') % ( 188 part['fs_label'], 189 part['vendor'], 190 part['model'], 191 gmTools.size2str(part['bytes_free']) 192 ), 193 _('%s (%s): %s in %s on %s') % ( 194 part['mountpoint'], 195 gmTools.size2str(part['size_in_bytes']), 196 part['fs_type'], 197 part['partition'], 198 part['device'] 199 ) 200 ]) 201 data.append(part) 202 for key in removable_partitions: 203 part = removable_partitions[key] 204 if part['is_mounted'] is True: 205 continue 206 items.append ([ 207 part['bus'].upper(), 208 '%s (%s %s)' % ( 209 part['fs_label'], 210 part['vendor'], 211 part['model'] 212 ), 213 _('%s on %s, not mounted') % ( 214 part['partition'], 215 part['device'] 216 ) 217 ]) 218 data.append(part) 219 220 # optical drives: CD/DVD/BD 221 optical_writers = gmTools.enumerate_optical_writers() 222 for cdrw in optical_writers: 223 items.append ([ 224 cdrw['type'].upper(), 225 cdrw['model'], 226 cdrw['device'] 227 ]) 228 data.append(cdrw) 229 230 self._LCTRL_removable_media.set_string_items(items) 231 self._LCTRL_removable_media.set_data(data) 232 self._LCTRL_removable_media.set_column_widths() 233 234 self._BTN_save2media.Disable()
235 236 #--------------------------------------------------------
237 - def __update_ui_state(self):
238 239 media = self._LCTRL_removable_media.get_selected_item_data(only_one = True) 240 if media is None: 241 self._BTN_save2media.Disable() 242 self._BTN_open_directory.Disable() 243 self._BTN_clear_directory.Disable() 244 self._CHBOX_encrypt.Disable() 245 self._CHBOX_use_subdirectory.Disable() 246 self._LBL_directory.Label = '' 247 self._LBL_dir_is_empty.Label = '' 248 return 249 250 if media['type'] == 'cd': 251 self._BTN_open_directory.Disable() 252 self._BTN_clear_directory.Disable() 253 self._LBL_directory.Label = '' 254 if self.__burn_script is None: 255 self._BTN_save2media.Disable() 256 self._CHBOX_use_subdirectory.Disable() 257 self._CHBOX_encrypt.Disable() 258 self._LBL_dir_is_empty.Label = _('helper <gm-burn_doc(.bat)> not found') 259 else: 260 self._BTN_save2media.Enable() 261 self._CHBOX_use_subdirectory.Enable() 262 self._CHBOX_encrypt.Enable() 263 self._LBL_dir_is_empty.Label = '' 264 return 265 266 if media['is_mounted'] is False: 267 self._BTN_save2media.Disable() 268 self._BTN_open_directory.Disable() 269 self._BTN_clear_directory.Disable() 270 self._CHBOX_use_subdirectory.Disable() 271 self._CHBOX_encrypt.Disable() 272 self._LBL_directory.Label = '' 273 self._LBL_dir_is_empty.Label = _('media not mounted') 274 return 275 276 self._BTN_save2media.Enable() 277 self._CHBOX_use_subdirectory.Enable() 278 self._CHBOX_encrypt.Enable() 279 280 path = self.__calc_path() 281 self._LBL_directory.Label = path + os.sep 282 is_empty = gmTools.dir_is_empty(directory = path) 283 if is_empty is True: 284 self._LBL_dir_is_empty.Label = '' 285 self._BTN_open_directory.Enable() 286 self._BTN_clear_directory.Disable() 287 elif is_empty is False: 288 self._LBL_dir_is_empty.Label = _('directory contains data') 289 self._BTN_open_directory.Enable() 290 self._BTN_clear_directory.Enable() 291 else: # we don't know, say, use_subdir and subdir does not yet exist 292 self._LBL_dir_is_empty.Label = '' 293 self._BTN_open_directory.Disable() 294 self._BTN_clear_directory.Disable()
295 296 #============================================================ 297 from Gnumed.wxGladeWidgets import wxgExportAreaSaveAsDlg 298
299 -class cExportAreaSaveAsDlg(wxgExportAreaSaveAsDlg.wxgExportAreaSaveAsDlg):
300
301 - def __init__(self, *args, **kwargs):
302 self.__patient = kwargs['patient'] 303 del kwargs['patient'] 304 self.__item_count = kwargs['item_count'] 305 del kwargs['item_count'] 306 super().__init__(*args, **kwargs) 307 308 self.__init_ui()
309 310 #-------------------------------------------------------- 311 # event handlers 312 #--------------------------------------------------------
313 - def _on_use_subdirectory_toggled(self, event):
314 event.Skip() 315 self.__update_ui_state()
316 317 #--------------------------------------------------------
318 - def _on_select_directory_button_pressed(self, event):
319 event.Skip() 320 curr_path = self._LBL_directory.Label.rstrip(os.sep).rstrip('/') 321 if not os.path.isdir(curr_path): 322 curr_path = os.path.join(gmTools.gmPaths().home_dir, 'gnumed') 323 msg = _('Select directory where to save the archive or files.') 324 dlg = wx.DirDialog(self, message = msg, defaultPath = curr_path, style = wx.DD_DEFAULT_STYLE)# | wx.DD_DIR_MUST_EXIST) 325 choice = dlg.ShowModal() 326 selected_path = dlg.GetPath().rstrip(os.sep).rstrip('/') 327 dlg.DestroyLater() 328 if choice != wx.ID_OK: 329 return 330 331 self._LBL_directory.Label = selected_path + os.sep 332 self.__update_ui_state()
333 334 #--------------------------------------------------------
335 - def _on_open_directory_button_pressed(self, event):
336 event.Skip() 337 path = self._LBL_directory.Label.strip().rstrip(os.sep).rstrip('/') 338 if not os.path.isdir(path): 339 return 340 gmMimeLib.call_viewer_on_file(path, block = False)
341 342 #--------------------------------------------------------
343 - def _on_clear_directory_button_pressed(self, event):
344 event.Skip() 345 path = self._LBL_directory.Label.strip().rstrip(os.sep).rstrip('/') 346 if not os.path.isdir(path): 347 return 348 delete_data = gmGuiHelpers.gm_show_question ( 349 title = _('Clearing out a directory'), 350 question = _( 351 'Do you really want to delete all existing data\n' 352 'from the following directory ?\n' 353 '\n' 354 ' %s\n' 355 '\n' 356 'Note, this can NOT be reversed without backups.' 357 ) % path, 358 cancel_button = False 359 ) 360 if delete_data: 361 gmTools.rm_dir_content(path) 362 self.__update_ui_state()
363 364 #--------------------------------------------------------
365 - def _on_save_archive_button_pressed(self, event):
366 event.Skip() 367 self.EndModal(wx.ID_SAVE)
368 369 #-------------------------------------------------------- 370 # internal API 371 #--------------------------------------------------------
372 - def __init_ui(self):
373 msg = ('\n' + _('Number of entries to save: %s') + '\n\n' + _('Patient: %s') + '\n') % ( 374 self.__item_count, 375 self.__patient['description_gender'] 376 ) 377 self._LBL_header.Label = msg 378 self._LBL_directory.Label = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', self.__patient.subdir_name) + os.sep 379 self.__update_ui_state()
380 381 #--------------------------------------------------------
382 - def __update_ui_state(self):
383 subdir_name = self.__patient.subdir_name 384 path = self._LBL_directory.Label.strip().rstrip(os.sep).rstrip('/') 385 if self._CHBOX_use_subdirectory.IsChecked(): 386 # add subdir if needed 387 if not path.endswith(subdir_name): 388 path = os.path.join(path, subdir_name) 389 self._LBL_directory.Label = path + os.sep 390 else: 391 # remove subdir if there 392 if path.endswith(subdir_name): 393 path = path[:-len(subdir_name)].rstrip(os.sep).rstrip('/') 394 self._LBL_directory.Label = path + os.sep 395 396 is_empty = gmTools.dir_is_empty(directory = path) 397 if is_empty is True: 398 self._LBL_dir_is_empty.Label = '' 399 self._BTN_open_directory.Disable() 400 self._BTN_clear_directory.Disable() 401 self._BTN_save_files.Enable() 402 self._BTN_save_archive.Enable() 403 elif is_empty is False: 404 self._LBL_dir_is_empty.Label = _('directory contains data') 405 self._BTN_open_directory.Enable() 406 self._BTN_clear_directory.Enable() 407 self._BTN_save_files.Disable() 408 self._BTN_save_archive.Disable() 409 else: # we don't know, say, use_subdir and subdir does not yet exist 410 self._LBL_dir_is_empty.Label = '' 411 self._BTN_open_directory.Disable() 412 self._BTN_clear_directory.Disable() 413 self._BTN_save_files.Enable() 414 self._BTN_save_archive.Enable() 415 416 self.Layout()
417 418 #============================================================ 419 from Gnumed.wxGladeWidgets import wxgExportAreaPluginPnl 420
421 -class cExportAreaPluginPnl(wxgExportAreaPluginPnl.wxgExportAreaPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
422 """Panel holding a number of items for further processing. 423 424 Acts on the current patient. 425 426 Used as notebook page."""
427 - def __init__(self, *args, **kwargs):
428 wxgExportAreaPluginPnl.wxgExportAreaPluginPnl.__init__(self, *args, **kwargs) 429 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 430 self.__init_ui() 431 self.__register_interests()
432 433 #-------------------------------------------------------- 434 # event handling 435 #--------------------------------------------------------
436 - def __register_interests(self):
437 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection) 438 # gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._schedule_data_reget) 439 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_table_mod)
440 441 #--------------------------------------------------------
443 self._LCTRL_items.set_string_items([])
444 445 #--------------------------------------------------------
446 - def _on_table_mod(self, *args, **kwargs):
447 if kwargs['table'] != 'clin.export_item': 448 return 449 pat = gmPerson.gmCurrentPatient() 450 if not pat.connected: 451 return 452 if kwargs['pk_identity'] != pat.ID: 453 return 454 self._schedule_data_reget()
455 456 #--------------------------------------------------------
457 - def _on_list_item_selected(self, event):
458 event.Skip()
459 460 #--------------------------------------------------------
461 - def _on_show_item_button_pressed(self, event):
462 event.Skip() 463 item = self._LCTRL_items.get_selected_item_data(only_one = True) 464 if item is None: 465 return 466 item.display_via_mime(block = False)
467 468 #--------------------------------------------------------
469 - def _on_add_items_button_pressed(self, event):
470 event.Skip() 471 dlg = wx.FileDialog ( 472 parent = self, 473 message = _("Select files to add to the export area"), 474 defaultDir = os.path.expanduser(os.path.join('~', 'gnumed')), 475 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_MULTIPLE | wx.FD_PREVIEW 476 ) 477 choice = dlg.ShowModal() 478 fnames = dlg.GetPaths() 479 dlg.DestroyLater() 480 if choice != wx.ID_OK: 481 return 482 if not gmPerson.gmCurrentPatient().export_area.add_files(fnames): 483 gmGuiHelpers.gm_show_error ( 484 title = _('Adding files to export area'), 485 error = _('Cannot add (some of) the following files to the export area:\n%s ') % '\n '.join(fnames) 486 )
487 488 #--------------------------------------------------------
489 - def _on_add_directory_button_pressed(self, event):
490 event.Skip() 491 dlg = wx.DirDialog ( 492 parent = self, 493 message = _("Select directory to add to the export area"), 494 defaultPath = os.path.expanduser(os.path.join('~', 'gnumed')), 495 style = wx.DD_DEFAULT_STYLE | wx.DD_DIR_MUST_EXIST 496 ) 497 choice = dlg.ShowModal() 498 path = dlg.GetPath() 499 dlg.DestroyLater() 500 if choice != wx.ID_OK: 501 return 502 if not gmPerson.gmCurrentPatient().export_area.add_path(path): 503 gmGuiHelpers.gm_show_error ( 504 title = _('Adding path to export area'), 505 error = _('Cannot add the following path to the export area:\n%s ') % path 506 )
507 508 #--------------------------------------------------------
509 - def _on_add_from_archive_button_pressed(self, event):
510 event.Skip() 511 selected_docs = gmDocumentWidgets.manage_documents ( 512 parent = self, 513 msg = _('Select the documents to be put into the export area:'), 514 single_selection = False 515 ) 516 if selected_docs is None: 517 return 518 gmPerson.gmCurrentPatient().export_area.add_documents(documents = selected_docs)
519 520 #--------------------------------------------------------
521 - def _on_clipboard_items_button_pressed(self, event):
522 event.Skip() 523 clip = gmGuiHelpers.clipboard2file(check_for_filename = True) 524 if clip is None: 525 return 526 if clip is False: 527 return 528 if not gmPerson.gmCurrentPatient().export_area.add_file(filename = clip, hint = _('clipboard')): 529 gmGuiHelpers.gm_show_error ( 530 title = _('Loading clipboard item (saved to file) into export area'), 531 error = _('Cannot add the following clip to the export area:\n%s ') % clip 532 )
533 534 #--------------------------------------------------------
535 - def _on_scan_items_button_pressed(self, event):
536 event.Skip() 537 scans = gmDocumentWidgets.acquire_images_from_capture_device(calling_window = self) 538 if scans is None: 539 return 540 541 if not gmPerson.gmCurrentPatient().export_area.add_files(scans, _('scan')): 542 gmGuiHelpers.gm_show_error ( 543 title = _('Scanning files into export area'), 544 error = _('Cannot add (some of) the following scans to the export area:\n%s ') % '\n '.join(fnames) 545 )
546 547 #--------------------------------------------------------
548 - def _on_remove_items_button_pressed(self, event):
549 event.Skip() 550 items = self._LCTRL_items.get_selected_item_data(only_one = False) 551 if len(items) == 0: 552 return 553 really_delete = gmGuiHelpers.gm_show_question ( 554 title = _('Deleting document from export area.'), 555 question = _('Really remove %s selected document(s)\nfrom the patient export area ?') % len(items) 556 ) 557 if not really_delete: 558 return 559 for item in items: 560 gmPerson.gmCurrentPatient().export_area.remove_item(item)
561 562 #--------------------------------------------------------
563 - def _on_print_items_button_pressed(self, event):
564 event.Skip() 565 items = self._LCTRL_items.get_selected_item_data(only_one = False) 566 if len(items) == 0: 567 return 568 569 files2print = [] 570 for item in items: 571 files2print.append(item.save_to_file()) 572 573 if len(files2print) == 0: 574 return 575 576 jobtype = 'export_area' 577 printed = gmPrinting.print_files(filenames = files2print, jobtype = jobtype, verbose = _cfg.get(option = 'debug')) 578 if not printed: 579 gmGuiHelpers.gm_show_error ( 580 aMessage = _('Error printing documents.'), 581 aTitle = _('Printing [%s]') % jobtype 582 ) 583 return False 584 585 self.__save_soap_note(soap = _('Printed:\n - %s') % '\n - '.join([ i['description'] for i in items ])) 586 return True
587 588 #--------------------------------------------------------
589 - def _on_remote_print_button_pressed(self, event):
590 event.Skip() 591 items = self._LCTRL_items.get_selected_item_data(only_one = False) 592 if len(items) == 0: 593 return 594 for item in items: 595 item.is_print_job = True 596 gmDispatcher.send(signal = 'statustext', msg = _('Item(s) moved to Print Center.'))
597 598 #--------------------------------------------------------
599 - def _on_save_items_button_pressed(self, event):
600 event.Skip() 601 602 items = self.__get_items_to_work_on(_('Saving entries')) 603 if items is None: 604 return 605 606 pat = gmPerson.gmCurrentPatient() 607 dlg = cExportAreaSaveAsDlg(self, -1, patient = pat, item_count = len(items)) 608 choice = dlg.ShowModal() 609 path = dlg._LBL_directory.Label 610 generate_metadata = dlg._CHBOX_generate_metadata.IsChecked() 611 use_subdir = dlg._CHBOX_use_subdirectory.IsChecked() 612 encrypt = dlg._CHBOX_encrypt.IsChecked() 613 dlg.DestroyLater() 614 if choice == wx.ID_CANCEL: 615 return 616 617 if choice == wx.ID_SAVE: 618 create_archive = True 619 elif choice == wx.ID_OK: 620 create_archive = False 621 else: 622 raise Exception('invalid return') 623 624 if create_archive: 625 export_dir = path 626 zip_file = self.__export_as_zip ( 627 gmTools.coalesce(encrypt, _('Saving entries as encrypted ZIP archive'), _('Saving entries as unencrypted ZIP archive')), 628 items = items, 629 encrypt = encrypt 630 ) 631 if zip_file is None: 632 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save: aborted or error.')) 633 return 634 gmTools.mkdir(export_dir) 635 final_zip_file = shutil.move(zip_file, export_dir) 636 gmDispatcher.send(signal = 'statustext', msg = _('Saved entries into [%s]') % final_zip_file) 637 target = final_zip_file 638 else: 639 export_dir = self.__export_as_files ( 640 gmTools.coalesce(encrypt, _('Saving entries as encrypted files'), _('Saving entries as unencrypted files')), 641 base_dir = path, 642 items = items, 643 encrypt = encrypt, 644 with_metadata = generate_metadata 645 ) 646 if export_dir is None: 647 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save: aborted or error.')) 648 return 649 gmDispatcher.send(signal = 'statustext', msg = _('Saved entries into [%s]') % export_dir) 650 target = export_dir 651 652 self.__save_soap_note(soap = _('Saved from export area to [%s]:\n - %s') % ( 653 target, 654 '\n - '.join([ i['description'] for i in items ]) 655 )) 656 self.__browse_patient_data(export_dir) 657 658 # remove_entries ? 659 660 return True
661 662 # cleanup - ask ! 663 # - files corresponding to DIR/DIR CONTENT entries 664 # - entries in export area 665 # remove_items = gmGuiHelpers.gm_show_question ( 666 # title = _('Creating zip archive'), 667 # question = _( 668 # 'Zip archive created as:\n' 669 # '\n' 670 # ' [%s]\n' 671 # '\n' 672 # 'Remove archived entries from export area ?' 673 # ) % zip_file, 674 # cancel_button = False 675 # ) 676 # if remove_items: 677 # exp_area.remove_items(items = items) 678 # return True 679 680 #--------------------------------------------------------
681 - def _on_export_items_button_pressed(self, event):
682 event.Skip() 683 684 items = self.__get_items_to_work_on(_('Exporting entries')) 685 if items is None: 686 return 687 688 # export dialog 689 pat = gmPerson.gmCurrentPatient() 690 dlg = cExportAreaExportToMediaDlg(self, -1, patient = pat, item_count = len(items)) 691 choice = dlg.ShowModal() 692 media = dlg._LCTRL_removable_media.get_selected_item_data(only_one = True) 693 use_subdir = dlg._CHBOX_use_subdirectory.IsChecked() 694 encrypt = dlg._CHBOX_encrypt.IsChecked() 695 dlg.DestroyLater() 696 if choice == wx.ID_CANCEL: 697 return 698 699 # export the files 700 if media['type'] == 'cd': 701 base_dir = gmTools.mk_sandbox_dir(prefix = 'iso-') 702 else: 703 base_dir = media['mountpoint'] 704 if use_subdir: 705 dir2save2 = os.path.join(base_dir, pat.subdir_name) 706 else: 707 dir2save2 = base_dir 708 export_dir = self.__export_as_files ( 709 gmTools.coalesce(encrypt, _('Exporting encrypted entries'), _('Exporting entries')), 710 base_dir = dir2save2, 711 items = items, 712 encrypt = encrypt, 713 with_metadata = True 714 ) 715 if export_dir is None: 716 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export: aborted or error.')) 717 return 718 719 if media['type'] == 'cd': 720 if not self.__burn_dir_to_disk(base_dir = base_dir): 721 return 722 gmDispatcher.send(signal = 'statustext', msg = _('Entries successfully burned to disk.')) 723 self.__save_soap_note(soap = _('Burned onto CD/DVD:\n - %s') % '\n - '.join([ i['description'] for i in items ])) 724 else: 725 gmDispatcher.send(signal = 'statustext', msg = _('Exported entries into [%s]') % export_dir) 726 self.__save_soap_note(soap = _('Exported onto removable media:\n - %s') % '\n - '.join([ i['description'] for i in items ])) 727 728 self.__browse_patient_data(dir2save2, encrypted = encrypt, archive = False, has_metadata = True) 729 730 # remove_entries ? 731 732 return True
733 734 #--------------------------------------------------------
735 - def _on_archive_items_button_pressed(self, event):
736 print("Event handler '_on_archive_items_button_pressed' not implemented!") 737 event.Skip()
738 739 #--------------------------------------------------------
740 - def _on_mail_items_button_pressed(self, event):
741 event.Skip() 742 743 _log.debug('gm-mail_doc(.bat) API: "MAIL-PROGRAM PRAXIS-VCF ZIP-ARCHIVE"') 744 745 found, external_cmd = gmShellAPI.detect_external_binary('gm-mail_doc') 746 if not found: 747 gmDispatcher.send(signal = 'statustext', msg = _('Cannot send e-mail: <gm-mail_doc(.bat)> not found')) 748 return False 749 750 zip_file = self.__export_as_zip ( 751 _('Mailing documents as zip archive'), 752 encrypt = True 753 ) 754 if zip_file is None: 755 gmDispatcher.send(signal = 'statustext', msg = _('Cannot send e-mail: no archive created.')) 756 return False 757 758 prax = gmPraxis.gmCurrentPraxisBranch() 759 args = [external_cmd, prax.vcf, zip_file] 760 success, ret_code, stdout = gmShellAPI.run_process(cmd_line = args, verbose = _cfg.get(option = 'debug')) 761 if not success: 762 gmGuiHelpers.gm_show_error ( 763 aMessage = _('Error mailing documents.'), 764 aTitle = _('Mailing documents') 765 ) 766 return False 767 768 self.__save_soap_note(soap = _('Mailed:\n - %s') % '\n - '.join([ i['description'] for i in items ])) 769 return True
770 771 #--------------------------------------------------------
772 - def _on_fax_items_button_pressed(self, event):
773 event.Skip() 774 775 _log.debug('gm-fax_doc(.bat) API: "FAX-PROGRAM FAXNUMBER-OR-<EMPTY> LIST-OF-FILES-TO-FAX" (any file type !)') 776 777 found, external_cmd = gmShellAPI.detect_external_binary('gm-fax_doc') 778 if not found: 779 gmDispatcher.send(signal = 'statustext', msg = _('Cannot send fax: <gm-fax_doc(.bat)> not found')) 780 return False 781 782 items = self._LCTRL_items.get_selected_item_data(only_one = False) 783 if len(items) == 0: 784 items = self._LCTRL_items.get_item_data() 785 if len(items) == 0: 786 gmDispatcher.send(signal = 'statustext', msg = _('Cannot send fax: no items')) 787 return None 788 if len(items) > 1: 789 # ask, might be a lot 790 process_all = gmGuiHelpers.gm_show_question ( 791 title = _('Faxing documents'), 792 question = _('You have not selected any entries.\n\nSend fax with all %s entries ?') % len(items), 793 cancel_button = False 794 ) 795 if not process_all: 796 return None 797 798 return False 799 800 files2fax = [] 801 for item in items: 802 files2fax.append(item.save_to_file()) 803 fax_number = wx.GetTextFromUser ( 804 _('Please enter the fax number here !\n\n' 805 'It can be left empty if the external\n' 806 'fax software knows how to get the number.'), 807 caption = _('Faxing documents'), 808 parent = self, 809 centre = True 810 ) 811 if fax_number == '': 812 fax_number = 'EMPTY' 813 args = [external_cmd, fax_number, ' '.join(files2fax)] 814 success, ret_code, stdout = gmShellAPI.run_process(cmd_line = args, verbose = _cfg.get(option = 'debug')) 815 if not success: 816 gmGuiHelpers.gm_show_error ( 817 aMessage = _('Error faxing documents to\n\n %s') % fax_number, 818 aTitle = _('Faxing documents') 819 ) 820 return False 821 822 self.__save_soap_note(soap = _('Faxed to [%s]:\n - %s') % (fax_number, '\n - '.join([ i['description'] for i in items ]))) 823 return True
824 825 #--------------------------------------------------------
826 - def repopulate_ui(self):
827 self._populate_with_data()
828 829 #-------------------------------------------------------- 830 # internal API 831 #--------------------------------------------------------
832 - def __init_ui(self):
833 self._LCTRL_items.set_columns([_('By'), _('When'), _('Description')]) 834 835 self._BTN_archive_items.Disable() 836 837 # there's no GetToolTipText() in wx2.8 838 self.__mail_script_exists, path = gmShellAPI.detect_external_binary(binary = r'gm-mail_doc') 839 if not self.__mail_script_exists: 840 self._BTN_mail_items.Disable() 841 tt = self._BTN_mail_items.GetToolTipText() + '\n\n' + _('<gm-mail_doc(.bat) not found>') 842 self._BTN_mail_items.SetToolTip(tt) 843 844 self.__fax_script_exists, path = gmShellAPI.detect_external_binary(binary = r'gm-fax_doc') 845 if not self.__fax_script_exists: 846 self._BTN_fax_items.Disable() 847 tt = self._BTN_fax_items.GetToolTipText() + '\n\n' + _('<gm-fax_doc(.bat) not found>') 848 self._BTN_fax_items.SetToolTip(tt) 849 850 self.__burn_script_exists, path = gmShellAPI.detect_external_binary(binary = r'gm-burn_doc') 851 if not self.__burn_script_exists: 852 self._BTN_burn_items.Disable() 853 tt = self._BTN_burn_items.GetToolTipText() + '\n\n' + _('<gm-burn_doc(.bat) not found>') 854 self._BTN_burn_items.SetToolTip(tt) 855 856 # make me and listctrl file drop targets 857 dt = gmGuiHelpers.cFileDropTarget(target = self) 858 self.SetDropTarget(dt) 859 dt = gmGuiHelpers.cFileDropTarget(on_drop_callback = self._drop_target_consume_filenames) 860 self._LCTRL_items.SetDropTarget(dt)
861 862 #--------------------------------------------------------
863 - def __save_soap_note(self, soap=None):
864 if soap.strip() == '': 865 return 866 emr = gmPerson.gmCurrentPatient().emr 867 epi = emr.add_episode(episode_name = 'administrative', is_open = False) 868 emr.add_clin_narrative ( 869 soap_cat = None, 870 note = soap, 871 episode = epi 872 )
873 874 #--------------------------------------------------------
875 - def __export_as_files(self, msg_title, base_dir=None, encrypt=False, with_metadata=False, items=None):
876 # get password 877 data_pwd = None 878 if encrypt: 879 data_pwd = self.__get_password(msg_title) 880 if data_pwd is None: 881 _log.debug('user aborted by not providing the same password twice') 882 gmDispatcher.send(signal = 'statustext', msg = _('Password not provided twice. Aborting.')) 883 return None 884 # save 885 wx.BeginBusyCursor() 886 try: 887 exp_area = gmPerson.gmCurrentPatient().export_area 888 if with_metadata: 889 export_dir = exp_area.export(base_dir = base_dir, items = items, passphrase = data_pwd) 890 else: 891 export_dir = exp_area.dump_items_to_disk(base_dir = base_dir, items = items, passphrase = data_pwd) 892 finally: 893 wx.EndBusyCursor() 894 if export_dir is None: 895 gmGuiHelpers.gm_show_error ( 896 aMessage = _('Error exporting entries.'), 897 aTitle = msg_title 898 ) 899 return None 900 901 return export_dir
902 903 #--------------------------------------------------------
904 - def __export_as_zip(self, msg_title, encrypt=True, items=None):
905 # get password 906 zip_pwd = None 907 if encrypt: 908 zip_pwd = self.__get_password(msg_title) 909 if zip_pwd is None: 910 _log.debug('user aborted by not providing the same password twice') 911 gmDispatcher.send(signal = 'statustext', msg = _('Password not provided twice. Aborting.')) 912 return None 913 # create archive 914 wx.BeginBusyCursor() 915 zip_file = None 916 try: 917 exp_area = gmPerson.gmCurrentPatient().export_area 918 zip_file = exp_area.export_as_zip(passphrase = zip_pwd, items = items) 919 except Exception: 920 _log.exception('cannot create zip file') 921 wx.EndBusyCursor() 922 if zip_file is None: 923 gmGuiHelpers.gm_show_error ( 924 aMessage = _('Error creating zip file.'), 925 aTitle = msg_title 926 ) 927 return zip_file
928 929 #--------------------------------------------------------
930 - def __get_password(self, msg_title):
931 while True: 932 data_pwd = wx.GetPasswordFromUser ( 933 message = _( 934 'Enter passphrase to protect the data with.\n' 935 '\n' 936 '(minimum length: 5, trailing blanks will be stripped)' 937 ), 938 caption = msg_title 939 ) 940 # minimal weakness check 941 data_pwd = data_pwd.rstrip() 942 if len(data_pwd) > 4: 943 break 944 retry = gmGuiHelpers.gm_show_question ( 945 title = msg_title, 946 question = _( 947 'Insufficient passphrase.\n' 948 '\n' 949 '(minimum length: 5, trailing blanks will be stripped)\n' 950 '\n' 951 'Enter another passphrase ?' 952 ) 953 ) 954 if not retry: 955 # user changed her mind 956 return None 957 # confidentiality 958 gmLog2.add_word2hide(data_pwd) 959 # reget password 960 while True: 961 data_pwd4comparison = wx.GetPasswordFromUser ( 962 message = _( 963 'Once more enter passphrase to protect the data with.\n' 964 '\n' 965 '(this will protect you from typos)\n' 966 '\n' 967 'Abort by leaving empty.' 968 ), 969 caption = msg_title 970 ) 971 data_pwd4comparison = data_pwd4comparison.rstrip() 972 if data_pwd4comparison == '': 973 # user changed her mind ... 974 return None 975 if data_pwd == data_pwd4comparison: 976 break 977 gmGuiHelpers.gm_show_error ( 978 error = _( 979 'Passphrases do not match.\n' 980 '\n' 981 'Retry, or abort with an empty passphrase.' 982 ), 983 title = msg_title 984 ) 985 return data_pwd
986 987 #--------------------------------------------------------
988 - def __get_items_to_work_on(self, msg_title):
989 990 items = self._LCTRL_items.get_selected_item_data(only_one = False) 991 if len(items) > 0: 992 return items 993 994 items = self._LCTRL_items.get_item_data() 995 if len(items) == 0: 996 gmDispatcher.send(signal = 'statustext', msg = _('Export area empty. Nothing to do.')) 997 return None 998 999 if len(items) == 1: 1000 return items 1001 1002 process_all = gmGuiHelpers.gm_show_question ( 1003 title = msg_title, 1004 question = _('You have not selected any entries.\n\nReally use all %s entries ?') % len(items), 1005 cancel_button = False 1006 ) 1007 if process_all: 1008 return items 1009 1010 return None
1011 1012 #--------------------------------------------------------
1013 - def __burn_dir_to_disk(self, base_dir):
1014 1015 _log.debug('gm-burn_doc(.bat) API: "DIRECTORY-TO-BURN-FROM"') 1016 1017 found, burn_cmd = gmShellAPI.detect_external_binary('gm-burn_doc') 1018 if not found: 1019 gmDispatcher.send(signal = 'statustext', msg = _('Cannot burn to disk: Helper not found.')) # should not happen 1020 return False 1021 1022 args = [burn_cmd, base_dir] 1023 wx.BeginBusyCursor() 1024 try: 1025 success, ret_code, stdout = gmShellAPI.run_process(cmd_line = args, verbose = _cfg.get(option = 'debug')) 1026 finally: 1027 wx.EndBusyCursor() 1028 if success: 1029 return True 1030 1031 gmGuiHelpers.gm_show_error ( 1032 aMessage = _('Error burning documents to CD/DVD.'), 1033 aTitle = _('Burning documents') 1034 ) 1035 return False
1036 1037 #--------------------------------------------------------
1038 - def __browse_patient_data(self, base_dir):
1039 1040 msg = _('Documents saved into:\n\n %s') % base_dir 1041 browse_index = gmGuiHelpers.gm_show_question ( 1042 title = _('Browsing patient data excerpt'), 1043 question = msg + '\n\n' + _('Browse saved entries ?'), 1044 cancel_button = False 1045 ) 1046 if not browse_index: 1047 return 1048 1049 if os.path.isfile(os.path.join(base_dir, 'index.html')): 1050 gmNetworkTools.open_url_in_browser(url = 'file://%s' % os.path.join(base_dir, 'index.html')) 1051 return 1052 1053 gmMimeLib.call_viewer_on_file(base_dir, block = False)
1054 1055 #-------------------------------------------------------- 1056 # file drop target API 1057 #--------------------------------------------------------
1058 - def _drop_target_consume_filenames(self, filenames):
1059 pat = gmPerson.gmCurrentPatient() 1060 if not pat.connected: 1061 gmDispatcher.send(signal = 'statustext', msg = _('Cannot accept new documents. No active patient.')) 1062 return 1063 1064 # dive into folders dropped onto us and extract files (one level deep only) 1065 real_filenames = [] 1066 for pathname in filenames: 1067 try: 1068 files = os.listdir(pathname) 1069 gmDispatcher.send(signal='statustext', msg=_('Extracting files from folder [%s] ...') % pathname) 1070 for file in files: 1071 fullname = os.path.join(pathname, file) 1072 if not os.path.isfile(fullname): 1073 continue 1074 real_filenames.append(fullname) 1075 except OSError: 1076 real_filenames.append(pathname) 1077 1078 if not pat.export_area.add_files(real_filenames, hint = _('Drag&Drop')): 1079 gmGuiHelpers.gm_show_error ( 1080 title = _('Adding files to export area'), 1081 error = _('Cannot add (some of) the following files to the export area:\n%s ') % '\n '.join(real_filenames) 1082 )
1083 #-------------------------------------------------------- 1084 # reget mixin API 1085 # 1086 # remember to call 1087 # self._schedule_data_reget() 1088 # whenever you learn of data changes from database 1089 # listener threads, dispatcher signals etc. 1090 #--------------------------------------------------------
1091 - def _populate_with_data(self):
1092 pat = gmPerson.gmCurrentPatient() 1093 if not pat.connected: 1094 return True 1095 1096 items = pat.export_area.items 1097 self._LCTRL_items.set_string_items ([ 1098 [ i['created_by'], 1099 gmDateTime.pydt_strftime(i['created_when'], '%Y %b %d %H:%M'), 1100 i['description'] 1101 ] for i in items 1102 ]) 1103 self._LCTRL_items.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE]) 1104 self._LCTRL_items.set_data(items) 1105 1106 self._LCTRL_items.SetFocus() 1107 1108 return True
1109 1110 #============================================================ 1111 from Gnumed.wxGladeWidgets import wxgPrintMgrPluginPnl 1112
1113 -class cPrintMgrPluginPnl(wxgPrintMgrPluginPnl.wxgPrintMgrPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
1114 """Panel holding print jobs. 1115 1116 Used as notebook page.""" 1117
1118 - def __init__(self, *args, **kwargs):
1119 wxgPrintMgrPluginPnl.wxgPrintMgrPluginPnl.__init__(self, *args, **kwargs) 1120 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1121 self.__init_ui() 1122 self.__register_interests()
1123 #-------------------------------------------------------- 1124 # event handling 1125 #--------------------------------------------------------
1126 - def __register_interests(self):
1127 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection) 1128 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection) 1129 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_table_mod)
1130 #--------------------------------------------------------
1132 self._RBTN_active_patient_only.Enable(False) 1133 self._RBTN_all_patients.Value = True 1134 self._BTN_export_printouts.Enable(False)
1135 #--------------------------------------------------------
1136 - def _on_post_patient_selection(self):
1137 self._RBTN_active_patient_only.Enable(True) 1138 self._BTN_export_printouts.Enable(True)
1139 #--------------------------------------------------------
1140 - def _on_table_mod(self, *args, **kwargs):
1141 if kwargs['table'] != 'clin.export_item': 1142 return 1143 if self._RBTN_all_patients.Value is True: 1144 self._schedule_data_reget() 1145 return 1146 pat = gmPerson.gmCurrentPatient() 1147 if not pat.connected: 1148 return 1149 if kwargs['pk_identity'] != pat.ID: 1150 return 1151 self._schedule_data_reget()
1152 #--------------------------------------------------------
1153 - def _on_all_patients_selected(self, event):
1154 event.Skip() 1155 self._schedule_data_reget()
1156 #--------------------------------------------------------
1157 - def _on_active_patient_only_selected(self, event):
1158 event.Skip() 1159 self._schedule_data_reget()
1160 #--------------------------------------------------------
1161 - def _on_view_button_pressed(self, event):
1162 event.Skip() 1163 printout = self._LCTRL_printouts.get_selected_item_data(only_one = True) 1164 if printout is None: 1165 return 1166 printout.display_via_mime(block = False)
1167 #--------------------------------------------------------
1168 - def _on_print_button_pressed(self, event):
1169 event.Skip() 1170 printouts = self._LCTRL_printouts.get_selected_item_data(only_one = False) 1171 if len(printouts) == 0: 1172 return 1173 1174 files2print = [] 1175 for printout in printouts: 1176 files2print.append(printout.save_to_file()) 1177 1178 if len(files2print) == 0: 1179 return 1180 1181 jobtype = 'print_manager' 1182 printed = gmPrinting.print_files(filenames = files2print, jobtype = jobtype, verbose = _cfg.get(option = 'debug')) 1183 if not printed: 1184 gmGuiHelpers.gm_show_error ( 1185 aMessage = _('Error printing documents.'), 1186 aTitle = _('Printing [%s]') % jobtype 1187 ) 1188 return False 1189 1190 return True
1191 #--------------------------------------------------------
1192 - def _on_export_button_pressed(self, event):
1193 event.Skip() 1194 pat = gmPerson.gmCurrentPatient() 1195 if not pat.connected: 1196 return 1197 printouts = self._LCTRL_printouts.get_selected_item_data(only_one = False) 1198 for printout in printouts: 1199 printout.is_print_job = False
1200 #--------------------------------------------------------
1201 - def _on_delete_button_pressed(self, event):
1202 event.Skip() 1203 printouts = self._LCTRL_printouts.get_selected_item_data(only_one = False) 1204 if len(printouts) == 0: 1205 return 1206 if len(printouts) > 1: 1207 really_delete = gmGuiHelpers.gm_show_question ( 1208 title = _('Deleting document from export area.'), 1209 question = _('Really remove %s selected document(s)\nfrom the patient export area ?') % len(printouts) 1210 ) 1211 if not really_delete: 1212 return 1213 for printout in printouts: 1214 gmExportArea.delete_export_item(pk_export_item = printout['pk_export_item'])
1215 1216 #-------------------------------------------------------- 1217 # internal API 1218 #--------------------------------------------------------
1219 - def __init_ui(self):
1220 self._BTN_export_printouts.Enable(False)
1221 #-------------------------------------------------------- 1222 # reget mixin API 1223 #--------------------------------------------------------
1224 - def _populate_with_data(self):
1225 if self._RBTN_all_patients.Value is True: 1226 columns = [_('Patient'), _('Provider'), _('Description')] 1227 printouts = gmExportArea.get_print_jobs(order_by = 'pk_identity, description') 1228 items = [[ 1229 '%s, %s (%s)' % ( 1230 p['lastnames'], 1231 p['firstnames'], 1232 p['gender'] 1233 ), 1234 p['created_by'], 1235 p['description'] 1236 ] for p in printouts ] 1237 else: 1238 pat = gmPerson.gmCurrentPatient() 1239 if pat.connected: 1240 columns = [_('Provider'), _('Created'), _('Description')] 1241 printouts = pat.export_area.get_printouts(order_by = 'created_when, description') 1242 items = [[ 1243 p['created_by'], 1244 gmDateTime.pydt_strftime(p['created_when'], '%Y %b %d %H:%M'), 1245 p['description'] 1246 ] for p in printouts ] 1247 else: 1248 columns = [_('Patient'), _('Provider'), _('Description')] 1249 printouts = gmExportArea.get_print_jobs(order_by = 'pk_identity, description') 1250 items = [[ 1251 '%s, %s (%s)' % ( 1252 p['lastnames'], 1253 p['firstnames'], 1254 p['gender'] 1255 ), 1256 p['created_by'], 1257 p['description'] 1258 ] for p in printouts ] 1259 self._LCTRL_printouts.set_columns(columns) 1260 self._LCTRL_printouts.set_string_items(items) 1261 self._LCTRL_printouts.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE]) 1262 self._LCTRL_printouts.set_data(printouts) 1263 self._LCTRL_printouts.SetFocus() 1264 return True
1265