diff --git a/securedrop_client/gui/widgets.py b/securedrop_client/gui/widgets.py index e85dd3feb3..d5e8b861e2 100644 --- a/securedrop_client/gui/widgets.py +++ b/securedrop_client/gui/widgets.py @@ -27,9 +27,9 @@ from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal, QEvent, QTimer, QSize, pyqtBoundSignal, \ QObject, QPoint from PyQt5.QtGui import QIcon, QPalette, QBrush, QColor, QFont, QLinearGradient -from PyQt5.QtWidgets import QListWidget, QLabel, QWidget, QListWidgetItem, QHBoxLayout, \ - QPushButton, QVBoxLayout, QLineEdit, QScrollArea, QDialog, QAction, QMenu, QMessageBox, \ - QToolButton, QSizePolicy, QPlainTextEdit, QStatusBar, QGraphicsDropShadowEffect, \ +from PyQt5.QtWidgets import QApplication, QListWidget, QLabel, QWidget, QListWidgetItem, \ + QHBoxLayout, QVBoxLayout, QLineEdit, QScrollArea, QDialog, QAction, QMenu, QMessageBox, \ + QToolButton, QSizePolicy, QPlainTextEdit, QStatusBar, QGraphicsDropShadowEffect, QPushButton, \ QDialogButtonBox from securedrop_client.db import DraftReply, Source, Message, File, Reply, User @@ -625,14 +625,6 @@ def __init__(self): self.setStyleSheet(self.CSS) self.setFixedHeight(40) - # Set drop shadow effect - effect = QGraphicsDropShadowEffect(self) - effect.setOffset(0, 1) - effect.setBlurRadius(8) - effect.setColor(QColor('#aa000000')) - self.setGraphicsEffect(effect) - self.update() - # Set click handler self.clicked.connect(self._on_clicked) @@ -1915,7 +1907,6 @@ def _on_export_clicked(self): return dialog = ExportDialog(self.controller, self.file.uuid, self.file.original_filename) - dialog.show() dialog.exec() @pyqtSlot() @@ -1928,7 +1919,6 @@ def _on_print_clicked(self): return dialog = PrintDialog(self.controller, self.file.uuid, self.file.original_filename) - dialog.show() dialog.exec() def _on_left_click(self): @@ -1947,25 +1937,129 @@ def _on_left_click(self): self.controller.on_submission_download(File, self.file.uuid) -class PrintDialog(QDialog): +class FramelessModal(QDialog): - CSS_FOR_DIALOG_WITH_ERROR = ''' - #print_dialog { - min-width: 830; - min-height: 430; + CSS = ''' + #frameless_modal { + min-width: 800; + max-width: 800; + min-height: 400; + background-color: #fff; border: 1px solid #2a319d; } - ''' - - CSS = ''' - #print_dialog { - min-width: 400; - max-width: 400; - min-height: 200; - max-height: 200; + #close_button { + border: none; + font-family: 'Source Sans Pro'; + font-weight: 600; + font-size: 12px; + color: #2a319d; + } + #header { + font-family: 'Montserrat'; + font-weight: 500; + font-size: 24px; + color: #2a319d; + padding-bottom: 20px; + } + #body { + font-family: 'Montserrat'; + font-weight: 400; + font-size: 16px; + line-height: 27px; + color: #302aa3; + padding-bottom: 20px; + padding-left: 2px; + } + #window_buttons QPushButton { + height: 40px; + margin: 0px 0px 0px 12px; + padding-left: 20px; + padding-right: 20px; + border: 2px solid #2a319d; + font-family: 'Montserrat'; + font-weight: 500; + font-size: 15px; + line-height: 1.83px; + color: #2a319d; + } + #window_buttons QPushButton::disabled { + border: 2px solid rgba(42, 49, 157, 0.4); + color: rgba(42, 49, 157, 0.4); } ''' + def __init__(self): + parent = QApplication.activeWindow() + super().__init__(parent) + self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + + self.setObjectName('frameless_modal') + self.setStyleSheet(self.CSS) + self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint) + self.setWindowModality(Qt.WindowModal) + + # Always display center of the application window + application_window_size = parent.geometry() + dialog_size = self.geometry() + x_center = (application_window_size.width() - dialog_size.width()) / 2 + y_center = (application_window_size.height() - dialog_size.height()) / 2 + self.move(x_center, y_center) + + # Set drop shadow effect + effect = QGraphicsDropShadowEffect(self) + effect.setOffset(0, 1) + effect.setBlurRadius(8) + effect.setColor(QColor('#aa000000')) + self.setGraphicsEffect(effect) + self.update() + + # Custom titlebar for close button + titlebar = QWidget() + titlebar_layout = QVBoxLayout() + titlebar.setLayout(titlebar_layout) + close_button = SvgPushButton('delete_close.svg', svg_size=QSize(10, 10)) + close_button.setObjectName('close_button') + close_button.setText('CLOSE') + close_button.clicked.connect(self.close) + titlebar_layout.addWidget(close_button, alignment=Qt.AlignRight) + + # Content including: header, body, help menu, and buttons + content = QWidget() + content.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + content_layout = QVBoxLayout() + content_layout.setContentsMargins(40, 20, 40, 40) + content.setLayout(content_layout) + self.header = QLabel() + self.header.setObjectName('header') + self.header.setWordWrap(True) + self.body = QLabel() + self.body.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self.body.setObjectName('body') + self.body.setWordWrap(True) + self.body_layout = QVBoxLayout() + self.body.setLayout(self.body_layout) + cancel_button = QPushButton(_('CANCEL')) + cancel_button.setAutoDefault(False) + cancel_button.clicked.connect(self.close) + self.continue_button = QPushButton(_('CONTINUE')) + window_buttons = QDialogButtonBox(Qt.Horizontal) + window_buttons.setObjectName('window_buttons') + window_buttons.addButton(cancel_button, QDialogButtonBox.ActionRole) + window_buttons.addButton(self.continue_button, QDialogButtonBox.ActionRole) + content_layout.addWidget(self.header) + content_layout.addWidget(self.body) + content_layout.addStretch() + content_layout.addWidget(window_buttons, alignment=Qt.AlignRight) + + # Layout + layout = QVBoxLayout(self) + self.setLayout(layout) + layout.addWidget(titlebar, alignment=Qt.AlignTop) + layout.addWidget(content) + + +class PrintDialog(FramelessModal): + def __init__(self, controller: Controller, file_uuid: str, file_name: str): super().__init__() @@ -1973,105 +2067,75 @@ def __init__(self, controller: Controller, file_uuid: str, file_name: str): self.file_uuid = file_uuid self.file_name = file_name - self.setObjectName('print_dialog') - self.setStyleSheet(self.CSS) - self.setWindowFlags( - Qt.Dialog | - Qt.FramelessWindowHint | - Qt.WindowStaysOnTopHint | - Qt.X11BypassWindowManagerHint) - self.setWindowModality(Qt.ApplicationModal) + # Connect controller signals to slots + self.controller.export.start_export_vm_success.connect(self._on_start_export_vm_success) + self.controller.export.start_export_vm_failure.connect(self._on_start_export_vm_failure) + self.controller.export.print_call_success.connect(self._on_print_success) + self.controller.export.print_call_failure.connect(self._on_print_failure) + + # Connect parent signals to slots + self.continue_button.clicked.connect(self._on_continue_button_clicked) + self.continue_button.setEnabled(False) + + # Dialog content + self.starting_header = _('Preparing to print: {}'.format(self.file_name)) + self.insert_usb_header = _('Insert USB printer') + self.error_header = _('Unable to print') + self.starting_message = _( + 'Proceed with caution when printing files.\n\n' + 'Anonymity\n' + 'Documents submitted by sources may contain information that identifies who they are. ' + 'To protect your sources, please consider redacting documents before printing them.\n\n' + 'The CONTINUE button will be disabled until the Export VM is ready.') + self.insert_usb_message = _('Please connect your printer to a USB port.') + self.generic_error_message = _('See your administrator for help.') + self.usb_error_message = _( + 'Please try reconnecting your printer, or see your administrator for help.') + + self._show_starting_instructions() + self.controller.start_export_vm() - layout = QVBoxLayout(self) - self.setLayout(layout) + def _show_starting_instructions(self): + self.header.setText(self.starting_header) + self.body.setText(self.starting_message) - # Starting print - self.starting = QWidget() - starting_layout = QVBoxLayout() - self.starting.setLayout(starting_layout) - starting_message = SecureQLabel(_('Preparing to print:\n' + self.file_name)) - starting_message.setWordWrap(True) - starting_cancel_button = QPushButton(_('CANCEL')) - starting_cancel_button.setAutoDefault(False) - starting_continue_button = QPushButton(_('CONTINUE')) - starting_layout.addWidget(starting_message) - starting_buttons = QDialogButtonBox(Qt.Horizontal) - starting_buttons.addButton(starting_cancel_button, QDialogButtonBox.ActionRole) - starting_buttons.addButton(starting_continue_button, QDialogButtonBox.ActionRole) - starting_layout.addWidget(starting_buttons) - starting_cancel_button.clicked.connect(self._cancel) - starting_continue_button.clicked.connect(self._print) - - # Widget to show error messages that occur during print - self.generic_error = QWidget() - self.generic_error.setObjectName('generic_error') - generic_error_layout = QHBoxLayout() - self.generic_error.setLayout(generic_error_layout) - self.error_status_code = SecureQLabel() - generic_error_message = SecureQLabel(_('See your administrator for help.')) - generic_error_message.setWordWrap(True) - generic_error_layout.addWidget(self.error_status_code) - generic_error_layout.addWidget(generic_error_message) - - # Insert USB Device Form - self.insert_usb_form = QWidget() - self.insert_usb_form.setObjectName('insert_usb_form') - usb_form_layout = QVBoxLayout() - self.insert_usb_form.setLayout(usb_form_layout) - self.usb_error_message = SecureQLabel(_( - 'Please try reconnecting your printer, or see your administrator for help.')) - self.usb_error_message.setWordWrap(True) - usb_instructions = SecureQLabel(_('Please connect your printer to a USB port.')) - usb_instructions.setWordWrap(True) - buttons = QWidget() - buttons_layout = QHBoxLayout() - buttons.setLayout(buttons_layout) - cancel_button = QPushButton(_('CANCEL')) - cancel_button.setAutoDefault(False) - retry_button = QPushButton(_('CONTINUE')) - buttons_layout.addWidget(cancel_button) - buttons_layout.addWidget(retry_button) - usb_form_layout.addWidget(self.usb_error_message) - usb_form_layout.addWidget(usb_instructions) - usb_form_layout.addWidget(buttons, alignment=Qt.AlignRight) - - # Printing message - self.printing_message = SecureQLabel(_('Printing:\n' + self.file_name)) - self.printing_message.setWordWrap(True) - - layout.addWidget(self.starting) - layout.addWidget(self.printing_message) - layout.addWidget(self.generic_error) - layout.addWidget(self.insert_usb_form) - - self.starting.show() - self.printing_message.hide() - self.generic_error.hide() - self.insert_usb_form.hide() - - cancel_button.clicked.connect(self._cancel) - retry_button.clicked.connect(self._on_retry_button_clicked) - - self.controller.export.print_call_failure.connect( - self._on_print_failure, type=Qt.QueuedConnection) - self.controller.export.print_call_success.connect( - self._on_print_success, type=Qt.QueuedConnection) + def _show_insert_usb_message(self): + self.header.setText(self.insert_usb_header) + self.body.setText(self.insert_usb_message) - self.controller.start_export_vm() + def _show_generic_error_message(self, error_code: str): + self.header.setText(self.error_header) + if not error_code: + self.body.setText(self.generic_error_message) + else: + message = error_code + '\n' + self.generic_error_message.getText() + self.body.setText(message) - def _cancel(self): - self.close() + def _show_usb_error_message(self, error_code: str): + self.header.setText(self.error_header) + if not error_code: + self.body.setText(self.usb_error_message) + else: + message = error_code + '\n' + self.usb_error_message.getText() + self.body.setText(message) - def _print(self): - self.starting.hide() - self.printing_message.show() - self.generic_error.hide() - self.insert_usb_form.hide() + def _update_content(self, status: str): + if status == ExportStatus.PRINTER_NOT_FOUND.value: + self._show_insert_usb_message() + else: + self._show_generic_error_message(status) + + @pyqtSlot() + def _on_continue_button_clicked(self): self.controller.print_file(self.file_uuid) @pyqtSlot() - def _on_retry_button_clicked(self): - self._print() + def _on_start_export_vm_success(self): + self.continue_button.setEnabled(True) + + @pyqtSlot(object) + def _on_start_export_vm_failure(self, error: ExportError): + self._update(error.status) @pyqtSlot() def _on_print_success(self): @@ -2081,49 +2145,10 @@ def _on_print_success(self): def _on_print_failure(self, error: ExportError): self._update(error.status) - def _update(self, status): - logger.debug('updating status... ') - if status == ExportStatus.PRINTER_NOT_FOUND.value: - self._request_to_insert_usb_device() - else: - self.error_status_code.setText(_(status)) - self.starting.hide() - self.printing_message.hide() - self.generic_error.show() - self.insert_usb_form.hide() - - def _request_to_insert_usb_device(self): - self.starting.hide() - self.printing_message.hide() - self.generic_error.hide() - self.insert_usb_form.show() - - -class ExportDialog(QDialog): - CSS = ''' - #export_dialog { - min-width: 830; - min-height: 330; - border: 1px solid #2a319d; - } - ''' +class ExportDialog(FramelessModal): - CSS_FOR_DIALOG_WITH_ERROR = ''' - #export_dialog { - min-width: 830; - min-height: 430; - border: 1px solid #2a319d; - } - ''' - - CSS = ''' - #export_dialog { - min-width: 400; - max-width: 400; - min-height: 200; - max-height: 200; - } + PASSPHRASE_FORM_CSS = ''' #passphrase_label { font-family: 'Montserrat'; font-weight: 500; @@ -2143,209 +2168,134 @@ def __init__(self, controller: Controller, file_uuid: str, file_name: str): self.file_uuid = file_uuid self.file_name = file_name - self.setObjectName('export_dialog') - self.setStyleSheet(self.CSS) - self.setWindowFlags( - Qt.Dialog | - Qt.FramelessWindowHint | - Qt.WindowStaysOnTopHint | - Qt.X11BypassWindowManagerHint) - self.setWindowModality(Qt.ApplicationModal) - - layout = QVBoxLayout(self) - self.setLayout(layout) - - # Starting export - starting_message = SecureQLabel(_('Preparing to export:\n' + self.file_name)) - starting_message.setWordWrap(True) - starting_cancel_button = QPushButton(_('CANCEL')) - starting_cancel_button.setAutoDefault(False) - starting_continue_button = QPushButton(_('CONTINUE')) - starting_buttons = QDialogButtonBox(Qt.Horizontal) - starting_buttons.addButton(starting_cancel_button, QDialogButtonBox.ActionRole) - starting_buttons.addButton(starting_continue_button, QDialogButtonBox.ActionRole) - self.starting = QWidget() - starting_layout = QVBoxLayout() - self.starting.setLayout(starting_layout) - starting_layout.addWidget(starting_message) - starting_layout.addWidget(starting_buttons) - starting_cancel_button.clicked.connect(self._cancel) - starting_continue_button.clicked.connect(self._export) - - # Widget to show error messages that occur during an export - self.generic_error = QWidget() - self.generic_error.setObjectName('gener_error') - generic_error_layout = QHBoxLayout() - self.generic_error.setLayout(generic_error_layout) - self.error_status_code = SecureQLabel() - generic_error_message = SecureQLabel(_('See your administrator for help.')) - generic_error_message.setWordWrap(True) - generic_error_layout.addWidget(self.error_status_code) - generic_error_layout.addWidget(generic_error_message) - - # Insert USB Device Form - self.insert_usb_form = QWidget() - self.insert_usb_form.setObjectName('insert_usb_form') - usb_form_layout = QVBoxLayout() - self.insert_usb_form.setLayout(usb_form_layout) - self.usb_error_message = SecureQLabel(_( - 'Either the drive is not LUKS-encrypted, or there is something ' - 'else wrong with it.')) - self.usb_error_message.setWordWrap(True) - usb_instructions = SecureQLabel(_( + # Connect controller signals to slots + self.controller.export.start_export_vm_success.connect(self._on_start_export_vm_success) + self.controller.export.start_export_vm_failure.connect(self._on_start_export_vm_failure) + self.controller.export.preflight_check_call_success.connect(self._on_preflight_success) + self.controller.export.preflight_check_call_failure.connect(self._on_preflight_failure) + self.controller.export.export_usb_call_success.connect(self._on_export_success) + self.controller.export.export_usb_call_failure.connect(self._on_export_failure) + + # Connect parent signals to slots + self.continue_button.clicked.connect(self._on_continue_button_clicked) + self.continue_button.setEnabled(False) + + # Dialog content + self.starting_header = _('Preparing to export: {}'.format(self.file_name)) + self.insert_usb_header = _('Insert encrypted USB drive') + self.passphrase_header = _('Enter passphrase for USB drive') + self.error_header = _('Unable to export') + self.starting_message = _( + 'Proceed with caution when exporting files.\n\n' + 'Malware\n' + 'This workstation lets you open documents securely. If you open documents on another ' + 'computer, any embedded malware may spread to your computer or network. If you are ' + 'unsure how to manage this risk, please print the document, or contact your ' + 'administrator.\n\n' + 'Anonymity\n' + 'Documents submitted by sources may contain information or hidden metadata that ' + 'identifies who they are. To protect your sources, please consider redacting documents ' + 'before working with them on network-connected computers.\n\n' + 'The CONTINUE button will be disabled until the Export VM is ready.') + self.exporting_message = _('Exporting: {}'.format(self.file_name)) + self.insert_usb_message = _( 'Please insert one of the export drives provisioned specifically ' - 'for the SecureDrop Workstation.')) - usb_instructions.setWordWrap(True) - buttons = QWidget() - buttons_layout = QHBoxLayout() - buttons.setLayout(buttons_layout) - usb_cancel_button = QPushButton(_('CANCEL')) - usb_cancel_button.setAutoDefault(False) - retry_export_button = QPushButton(_('OK')) - buttons_layout.addWidget(usb_cancel_button) - buttons_layout.addWidget(retry_export_button) - usb_form_layout.addWidget(self.usb_error_message) - usb_form_layout.addWidget(usb_instructions) - usb_form_layout.addWidget(buttons, alignment=Qt.AlignRight) + 'for the SecureDrop Workstation.') + self.usb_error_message = _( + 'Either the drive is not encrypted or there is something else wrong with it. Please ' + 'try another drive, or see your administrator for help.') + self.passphrase_error_message = _('The passphrase provided did not work. Please try again.') + self.generic_error_message = _('See your administrator for help.') # Passphrase Form - self.passphrase_form = QWidget() - self.passphrase_form.setObjectName('passphrase_form') - passphrase_form_layout = QVBoxLayout() - self.passphrase_form.setLayout(passphrase_form_layout) - self.passphrase_error_message = SecureQLabel(_( - 'The passphrase provided did not work. Please try again.')) - self.passphrase_error_message.setWordWrap(True) - self.passphrase_instructions = SecureQLabel(_('Enter the passphrase for this drive')) - self.passphrase_instructions.setWordWrap(True) passphrase_label = SecureQLabel(_('Passphrase')) passphrase_label.setObjectName('passphrase_label') self.passphrase_field = QLineEdit() self.passphrase_field.setEchoMode(QLineEdit.Password) - buttons = QWidget() - buttons_layout = QHBoxLayout() - buttons.setLayout(buttons_layout) - passphrase_cancel_button = QPushButton(_('CANCEL')) - passphrase_cancel_button.setAutoDefault(False) - unlock_disk_button = QPushButton(_('SUBMIT')) - buttons_layout.addWidget(passphrase_cancel_button) - buttons_layout.addWidget(unlock_disk_button) - passphrase_form_layout.addWidget(self.passphrase_error_message) - passphrase_form_layout.addWidget(self.passphrase_instructions) + self.passphrase_form = QWidget() + self.passphrase_form.setStyleSheet(self.PASSPHRASE_FORM_CSS) + self.passphrase_form.setObjectName('passphrase_form') + passphrase_form_layout = QVBoxLayout() + self.passphrase_form.setLayout(passphrase_form_layout) passphrase_form_layout.addWidget(passphrase_label) passphrase_form_layout.addWidget(self.passphrase_field) - passphrase_form_layout.addWidget(buttons, alignment=Qt.AlignRight) - self.passphrase_error_message.hide() - - # Starting export message - self.exporting_message = SecureQLabel(_('File export in progress:\n' + self.file_name)) - self.exporting_message.setWordWrap(True) - - layout.addWidget(self.starting) - layout.addWidget(self.exporting_message) - layout.addWidget(self.generic_error) - layout.addWidget(self.insert_usb_form) - layout.addWidget(self.passphrase_form) - - self.starting.show() - self.exporting_message.hide() - self.generic_error.hide() - self.insert_usb_form.hide() + self.body_layout.addWidget(self.passphrase_form) self.passphrase_form.hide() - usb_cancel_button.clicked.connect(self._cancel) - passphrase_cancel_button.clicked.connect(self._cancel) - retry_export_button.clicked.connect(self._on_retry_export_button_clicked) - unlock_disk_button.clicked.connect(self._on_unlock_disk_clicked) - - self.controller.export.preflight_check_call_failure.connect( - self._on_preflight_check_call_failure, type=Qt.QueuedConnection) - self.controller.export.export_usb_call_failure.connect( - self._on_export_usb_call_failure, type=Qt.QueuedConnection) - self.controller.export.preflight_check_call_success.connect( - self._request_passphrase, type=Qt.QueuedConnection) - self.controller.export.export_usb_call_success.connect( - self._on_export_success, type=Qt.QueuedConnection) - - def _cancel(self): - self.close() - - def _export(self): - self.controller.run_export_preflight_checks() + self._show_starting_instructions() + self.controller.start_export_vm() - @pyqtSlot() - def _on_retry_export_button_clicked(self): - self.starting.hide() - self.controller.run_export_preflight_checks() + def _show_starting_instructions(self): + self.header.setText(self.starting_header) + self.body.setText(self.starting_message) - @pyqtSlot() - def _on_unlock_disk_clicked(self): - self.passphrase_form.hide() - self.exporting_message.show() - passphrase = self.passphrase_field.text() - self.controller.export_file_to_usb_drive(self.file_uuid, passphrase) + def _show_passphrase_request_message(self): + self.header.setText(self.passphrase_header) + self.body.setText('') + self.passphrase_form.show() - @pyqtSlot() - def _on_export_success(self): - self.close() + def _show_passphrase_request_message_again(self): + self.header.setText(self.passphrase_header) + self.body.setText(self.passphrase_message.getText()) + self.passphrase_form.show() - def _request_to_insert_usb_device(self, encryption_not_supported: bool = False): - self.starting.hide() + def _show_insert_usb_message(self): + self.header.setText(self.insert_usb_header) + self.body.setText(self.insert_usb_message) self.passphrase_form.hide() - self.insert_usb_form.show() - if encryption_not_supported: - self.usb_error_message.show() - else: - self.usb_error_message.hide() - - @pyqtSlot() - def _request_passphrase(self, bad_passphrase: bool = False): - logger.debug('requesting passphrase... ') - self.starting.hide() - self.exporting_message.hide() - self.insert_usb_form.hide() - self.passphrase_form.show() + def _show_insert_encrypted_usb_message(self): + self.header.setText(self.error_header) + self.body.setText( + '{}\n\n{}'.format(self.usb_error_message.getText(), self.insert_usb_message.getText())) + self.passphrase_form.hide() - if bad_passphrase: - self.passphrase_instructions.hide() - self.passphrase_error_message.show() - else: - self.passphrase_error_message.hide() - self.passphrase_instructions.show() + def _show_generic_error_message(self, error_code: str): + self.header.setText(self.error_header) + self.body.setText('{}\n\n{}'.format(error_code, self.generic_error_message.getText())) + self.passphrase_form.hide() def _update(self, status): - logger.debug('updating status... ') if status == ExportStatus.USB_NOT_CONNECTED.value: - self._request_to_insert_usb_device() + self._show_insert_usb_message() elif status == ExportStatus.BAD_PASSPHRASE.value: - self._request_passphrase(True) + self._show_passphrase_request_message_again() elif status == ExportStatus.DISK_ENCRYPTION_NOT_SUPPORTED_ERROR.value: - self._request_to_insert_usb_device(True) + self._show_insert_encrypted_usb_message() else: - self.error_status_code.setText(_(status)) - self.generic_error.show() - self.starting.hide() - self.passphrase_form.hide() - self.insert_usb_form.hide() + self._show_generic_error_message(_(status)) + + @pyqtSlot() + def _on_continue_button_clicked(self): + self.controller.run_export_preflight_checks() + + @pyqtSlot() + def _on_start_export_vm_success(self): + self.continue_button.setEnabled(True) @pyqtSlot(object) - def _on_preflight_check_call_failure(self, error: ExportError): - # The first time we see a CALLED_PROCESS_ERROR, tell the user to insert the USB device - # in case the issue is that the Export VM cannot start due to a USB device being - # unavailable for attachment. According to the Qubes docs: - # - # "If the device is unavailable (physically missing or sourceVM not started), booting - # the targetVM fails." - # - # For information, see https://www.qubes-os.org/doc/device-handling - if error.status == ExportStatus.CALLED_PROCESS_ERROR.value: - self._request_to_insert_usb_device() - else: - self._update(error.status) + def _on_start_export_vm_failure(self, error: ExportError): + self._update(error.status) + + @pyqtSlot() + def _on_preflight_success(self): + self._show_passphrase_request_message() + + @pyqtSlot(object) + def _on_preflight_failure(self, error: ExportError): + self._update(error.status) + + @pyqtSlot() + def _on_unlock_disk_clicked(self): + passphrase = self.passphrase_field.text() + self.controller.export_file_to_usb_drive(self.file_uuid, passphrase) + + @pyqtSlot() + def _on_export_success(self): + self.close() @pyqtSlot(object) - def _on_export_usb_call_failure(self, error: ExportError): + def _on_export_failure(self, error: ExportError): self._update(error.status) diff --git a/securedrop_client/resources/images/delete.png b/securedrop_client/resources/images/delete.png deleted file mode 100644 index 6c9656c98a..0000000000 Binary files a/securedrop_client/resources/images/delete.png and /dev/null differ diff --git a/securedrop_client/resources/images/delete_close.svg b/securedrop_client/resources/images/delete_close.svg new file mode 100644 index 0000000000..cbf8fedf77 --- /dev/null +++ b/securedrop_client/resources/images/delete_close.svg @@ -0,0 +1,11 @@ + + + + Delete-X + Created with Sketch. + + + + + + \ No newline at end of file