mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-04-28 09:52:37 +02:00
Add drag and drop support for document selection
This commit is contained in:
parent
7744cd55ec
commit
d0e1df5546
7 changed files with 322 additions and 44 deletions
|
@ -14,6 +14,7 @@ since 0.4.1, and this project adheres to [Semantic Versioning](https://semver.or
|
||||||
* Prevent attacker from becoming root within the container ([#224](https://github.com/freedomofpress/dangerzone/issues/224))
|
* Prevent attacker from becoming root within the container ([#224](https://github.com/freedomofpress/dangerzone/issues/224))
|
||||||
* Use a restricted seccomp profile ([#225](https://github.com/freedomofpress/dangerzone/issues/225))
|
* Use a restricted seccomp profile ([#225](https://github.com/freedomofpress/dangerzone/issues/225))
|
||||||
* Make use of user namespaces ([#228](https://github.com/freedomofpress/dangerzone/issues/228))
|
* Make use of user namespaces ([#228](https://github.com/freedomofpress/dangerzone/issues/228))
|
||||||
|
- Files can now be drag-n-dropped to Dangerzone ([issue #409](https://github.com/freedomofpress/dangerzone/issues/409))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
11
RELEASE.md
11
RELEASE.md
|
@ -209,21 +209,26 @@ Run Dangerzone against a list of documents, and tick all options. Ensure that:
|
||||||
location.
|
location.
|
||||||
* The original files have been saved in the `unsafe/` directory.
|
* The original files have been saved in the `unsafe/` directory.
|
||||||
|
|
||||||
#### 8. Dangerzone CLI succeeds in converting multiple documents
|
#### 8. Dangerzone is able to handle drag-n-drop
|
||||||
|
|
||||||
|
Run Dangerzone against a set of documents that you drag-n-drop. Files should be
|
||||||
|
added and conversion should run without issue.
|
||||||
|
|
||||||
|
#### 9. Dangerzone CLI succeeds in converting multiple documents
|
||||||
|
|
||||||
_(Only for Windows and Linux)_
|
_(Only for Windows and Linux)_
|
||||||
|
|
||||||
Run Dangerzone CLI against a list of documents. Ensure that conversions happen
|
Run Dangerzone CLI against a list of documents. Ensure that conversions happen
|
||||||
sequentially, are completed successfully, and we see their progress.
|
sequentially, are completed successfully, and we see their progress.
|
||||||
|
|
||||||
#### 9. Dangerzone can open a document for conversion via right-click -> "Open With"
|
#### 10. Dangerzone can open a document for conversion via right-click -> "Open With"
|
||||||
|
|
||||||
_(Only for Windows, MacOS and Qubes)_
|
_(Only for Windows, MacOS and Qubes)_
|
||||||
|
|
||||||
Go to a directory with office documents, right-click on one, and click on "Open
|
Go to a directory with office documents, right-click on one, and click on "Open
|
||||||
With". We should be able to open the file with Dangerzone, and then convert it.
|
With". We should be able to open the file with Dangerzone, and then convert it.
|
||||||
|
|
||||||
#### 10. Dangerzone shows helpful errors for setup issues on Qubes
|
#### 11. Dangerzone shows helpful errors for setup issues on Qubes
|
||||||
|
|
||||||
_(Only for Qubes)_
|
_(Only for Qubes)_
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,60 @@ about updates.</p>
|
||||||
HAMBURGER_MENU_SIZE = 30
|
HAMBURGER_MENU_SIZE = 30
|
||||||
|
|
||||||
|
|
||||||
|
def load_svg_image(filename: str, width: int, height: int) -> QtGui.QPixmap:
|
||||||
|
"""Load an SVG image from a filename.
|
||||||
|
|
||||||
|
This answer is basically taken from: https://stackoverflow.com/a/25689790
|
||||||
|
"""
|
||||||
|
path = get_resource_path(filename)
|
||||||
|
svg_renderer = QtSvg.QSvgRenderer(path)
|
||||||
|
image = QtGui.QImage(width, height, QtGui.QImage.Format_ARGB32)
|
||||||
|
# Set the ARGB to 0 to prevent rendering artifacts
|
||||||
|
image.fill(0x00000000)
|
||||||
|
svg_renderer.render(QtGui.QPainter(image))
|
||||||
|
pixmap = QtGui.QPixmap.fromImage(image)
|
||||||
|
return pixmap
|
||||||
|
|
||||||
|
|
||||||
|
def get_supported_extensions() -> List[str]:
|
||||||
|
supported_ext = [
|
||||||
|
".pdf",
|
||||||
|
".docx",
|
||||||
|
".doc",
|
||||||
|
".docm",
|
||||||
|
".xlsx",
|
||||||
|
".xls",
|
||||||
|
".pptx",
|
||||||
|
".ppt",
|
||||||
|
".odt",
|
||||||
|
".odg",
|
||||||
|
".odp",
|
||||||
|
".ods",
|
||||||
|
".epub",
|
||||||
|
".jpg",
|
||||||
|
".jpeg",
|
||||||
|
".gif",
|
||||||
|
".png",
|
||||||
|
".tif",
|
||||||
|
".tiff",
|
||||||
|
".bmp",
|
||||||
|
".pnm",
|
||||||
|
".pbm",
|
||||||
|
".ppm",
|
||||||
|
".svg",
|
||||||
|
]
|
||||||
|
|
||||||
|
# XXX: We disable loading HWP/HWPX files on Qubes, because H2ORestart does not work there.
|
||||||
|
# See:
|
||||||
|
#
|
||||||
|
# https://github.com/freedomofpress/dangerzone/issues/494
|
||||||
|
hwp_filters = [".hwp", ".hwpx"]
|
||||||
|
if is_qubes_native_conversion():
|
||||||
|
supported_ext += hwp_filters
|
||||||
|
|
||||||
|
return supported_ext
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QtWidgets.QMainWindow):
|
class MainWindow(QtWidgets.QMainWindow):
|
||||||
def __init__(self, dangerzone: DangerzoneGui) -> None:
|
def __init__(self, dangerzone: DangerzoneGui) -> None:
|
||||||
super(MainWindow, self).__init__()
|
super(MainWindow, self).__init__()
|
||||||
|
@ -87,7 +141,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
self.hamburger_button = QtWidgets.QToolButton()
|
self.hamburger_button = QtWidgets.QToolButton()
|
||||||
self.hamburger_button.setPopupMode(QtWidgets.QToolButton.InstantPopup)
|
self.hamburger_button.setPopupMode(QtWidgets.QToolButton.InstantPopup)
|
||||||
self.hamburger_button.setIcon(
|
self.hamburger_button.setIcon(
|
||||||
QtGui.QIcon(self.load_svg_image("hamburger_menu.svg"))
|
QtGui.QIcon(load_svg_image("hamburger_menu.svg", width=64, height=64))
|
||||||
)
|
)
|
||||||
self.hamburger_button.setFixedSize(HAMBURGER_MENU_SIZE, HAMBURGER_MENU_SIZE)
|
self.hamburger_button.setFixedSize(HAMBURGER_MENU_SIZE, HAMBURGER_MENU_SIZE)
|
||||||
self.hamburger_button.setIconSize(
|
self.hamburger_button.setIconSize(
|
||||||
|
@ -168,20 +222,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
|
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
def load_svg_image(self, filename: str) -> QtGui.QPixmap:
|
|
||||||
"""Load an SVG image from a filename.
|
|
||||||
|
|
||||||
This answer is basically taken from: https://stackoverflow.com/a/25689790
|
|
||||||
"""
|
|
||||||
path = get_resource_path(filename)
|
|
||||||
svg_renderer = QtSvg.QSvgRenderer(path)
|
|
||||||
image = QtGui.QImage(64, 64, QtGui.QImage.Format_ARGB32)
|
|
||||||
# Set the ARGB to 0 to prevent rendering artifacts
|
|
||||||
image.fill(0x00000000)
|
|
||||||
svg_renderer.render(QtGui.QPainter(image))
|
|
||||||
pixmap = QtGui.QPixmap.fromImage(image)
|
|
||||||
return pixmap
|
|
||||||
|
|
||||||
def show_update_success(self) -> None:
|
def show_update_success(self) -> None:
|
||||||
"""Inform the user about a new Dangerzone release."""
|
"""Inform the user about a new Dangerzone release."""
|
||||||
version = self.dangerzone.settings.get("updater_latest_version")
|
version = self.dangerzone.settings.get("updater_latest_version")
|
||||||
|
@ -262,13 +302,21 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.hamburger_button.setIcon(
|
self.hamburger_button.setIcon(
|
||||||
QtGui.QIcon(self.load_svg_image("hamburger_menu_update_error.svg"))
|
QtGui.QIcon(
|
||||||
|
load_svg_image(
|
||||||
|
"hamburger_menu_update_error.svg", width=64, height=64
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
sep = hamburger_menu.insertSeparator(hamburger_menu.actions()[0])
|
sep = hamburger_menu.insertSeparator(hamburger_menu.actions()[0])
|
||||||
# FIXME: Add red bubble next to the text.
|
# FIXME: Add red bubble next to the text.
|
||||||
error_action = QAction("Update error", hamburger_menu)
|
error_action = QAction("Update error", hamburger_menu)
|
||||||
error_action.setIcon(
|
error_action.setIcon(
|
||||||
QtGui.QIcon(self.load_svg_image("hamburger_menu_update_dot_error.svg"))
|
QtGui.QIcon(
|
||||||
|
load_svg_image(
|
||||||
|
"hamburger_menu_update_dot_error.svg", width=64, height=64
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
error_action.triggered.connect(self.show_update_error)
|
error_action.triggered.connect(self.show_update_error)
|
||||||
hamburger_menu.insertAction(sep, error_action)
|
hamburger_menu.insertAction(sep, error_action)
|
||||||
|
@ -283,14 +331,20 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
self.dangerzone.settings.save()
|
self.dangerzone.settings.save()
|
||||||
|
|
||||||
self.hamburger_button.setIcon(
|
self.hamburger_button.setIcon(
|
||||||
QtGui.QIcon(self.load_svg_image("hamburger_menu_update_success.svg"))
|
QtGui.QIcon(
|
||||||
|
load_svg_image(
|
||||||
|
"hamburger_menu_update_success.svg", width=64, height=64
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
sep = hamburger_menu.insertSeparator(hamburger_menu.actions()[0])
|
sep = hamburger_menu.insertSeparator(hamburger_menu.actions()[0])
|
||||||
success_action = QAction("New version available", hamburger_menu)
|
success_action = QAction("New version available", hamburger_menu)
|
||||||
success_action.setIcon(
|
success_action.setIcon(
|
||||||
QtGui.QIcon(
|
QtGui.QIcon(
|
||||||
self.load_svg_image("hamburger_menu_update_dot_available.svg")
|
load_svg_image(
|
||||||
|
"hamburger_menu_update_dot_available.svg", width=64, height=64
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
success_action.triggered.connect(self.show_update_success)
|
success_action.triggered.connect(self.show_update_success)
|
||||||
|
@ -455,6 +509,10 @@ class ContentWidget(QtWidgets.QWidget):
|
||||||
# Doc selection widget
|
# Doc selection widget
|
||||||
self.doc_selection_widget = DocSelectionWidget(self.dangerzone)
|
self.doc_selection_widget = DocSelectionWidget(self.dangerzone)
|
||||||
self.doc_selection_widget.documents_selected.connect(self.documents_selected)
|
self.doc_selection_widget.documents_selected.connect(self.documents_selected)
|
||||||
|
self.doc_selection_wrapper = DocSelectionDropFrame(
|
||||||
|
self.dangerzone, self.doc_selection_widget
|
||||||
|
)
|
||||||
|
self.doc_selection_wrapper.documents_selected.connect(self.documents_selected)
|
||||||
|
|
||||||
# Settings
|
# Settings
|
||||||
self.settings_widget = SettingsWidget(self.dangerzone)
|
self.settings_widget = SettingsWidget(self.dangerzone)
|
||||||
|
@ -475,7 +533,7 @@ class ContentWidget(QtWidgets.QWidget):
|
||||||
layout = QtWidgets.QVBoxLayout()
|
layout = QtWidgets.QVBoxLayout()
|
||||||
layout.addWidget(self.settings_widget, stretch=1)
|
layout.addWidget(self.settings_widget, stretch=1)
|
||||||
layout.addWidget(self.documents_list, stretch=1)
|
layout.addWidget(self.documents_list, stretch=1)
|
||||||
layout.addWidget(self.doc_selection_widget, stretch=1)
|
layout.addWidget(self.doc_selection_wrapper, stretch=1)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
def documents_selected(self, docs: List[Document]) -> None:
|
def documents_selected(self, docs: List[Document]) -> None:
|
||||||
|
@ -505,7 +563,7 @@ class ContentWidget(QtWidgets.QWidget):
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
self.dangerzone.add_document(doc)
|
self.dangerzone.add_document(doc)
|
||||||
|
|
||||||
self.doc_selection_widget.hide()
|
self.doc_selection_wrapper.hide()
|
||||||
self.settings_widget.show()
|
self.settings_widget.show()
|
||||||
|
|
||||||
if len(docs) > 0:
|
if len(docs) > 0:
|
||||||
|
@ -551,20 +609,8 @@ class DocSelectionWidget(QtWidgets.QWidget):
|
||||||
self.file_dialog = QtWidgets.QFileDialog()
|
self.file_dialog = QtWidgets.QFileDialog()
|
||||||
self.file_dialog.setWindowTitle("Open Documents")
|
self.file_dialog.setWindowTitle("Open Documents")
|
||||||
self.file_dialog.setFileMode(QtWidgets.QFileDialog.ExistingFiles)
|
self.file_dialog.setFileMode(QtWidgets.QFileDialog.ExistingFiles)
|
||||||
|
|
||||||
# XXX: We disable loading HWP/HWPX files on Qubes, because H2ORestart does not work there.
|
|
||||||
# See:
|
|
||||||
#
|
|
||||||
# https://github.com/freedomofpress/dangerzone/issues/494
|
|
||||||
hwp_filters = "*.hwp *.hwpx"
|
|
||||||
if is_qubes_native_conversion():
|
|
||||||
hwp_filters = ""
|
|
||||||
self.file_dialog.setNameFilters(
|
self.file_dialog.setNameFilters(
|
||||||
[
|
["Documents (*" + " *".join(get_supported_extensions()) + ")"]
|
||||||
"Documents (*.pdf *.docx *.doc *.docm *.xlsx *.xls *.pptx *.ppt *.odt"
|
|
||||||
f" *.odg *.odp *.ods {hwp_filters} *.epub *.jpg *.jpeg *.gif *.png"
|
|
||||||
" *.tif *.tiff *.bmp *.pnm *.pbm *.ppm *.svg)"
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def dangerous_doc_button_clicked(self) -> None:
|
def dangerous_doc_button_clicked(self) -> None:
|
||||||
|
@ -584,6 +630,103 @@ class DocSelectionWidget(QtWidgets.QWidget):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DocSelectionDropFrame(QtWidgets.QFrame):
|
||||||
|
"""
|
||||||
|
HACK Docs selecting widget "drag-n-drop" border widget
|
||||||
|
The border frame doesn't show around the whole widget
|
||||||
|
unless there is another widget wrapping it
|
||||||
|
"""
|
||||||
|
|
||||||
|
documents_selected = QtCore.Signal(list)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, dangerzone: DangerzoneGui, docs_selection_widget: DocSelectionWidget
|
||||||
|
) -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.dangerzone = dangerzone
|
||||||
|
self.docs_selection_widget = docs_selection_widget
|
||||||
|
|
||||||
|
# Drag and drop functionality
|
||||||
|
self.setAcceptDrops(True)
|
||||||
|
|
||||||
|
self.document_image_text = QtWidgets.QLabel(
|
||||||
|
"Drag and drop\ndocuments here\n\nor"
|
||||||
|
)
|
||||||
|
self.document_image_text.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
self.document_image = QtWidgets.QLabel()
|
||||||
|
self.document_image.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
self.document_image.setPixmap(
|
||||||
|
load_svg_image("document.svg", width=20, height=24)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.center_layout = QtWidgets.QVBoxLayout()
|
||||||
|
self.center_layout.addWidget(self.document_image)
|
||||||
|
self.center_layout.addWidget(self.document_image_text)
|
||||||
|
self.center_layout.addWidget(self.docs_selection_widget)
|
||||||
|
|
||||||
|
self.drop_layout = QtWidgets.QVBoxLayout()
|
||||||
|
self.drop_layout.addStretch()
|
||||||
|
self.drop_layout.addLayout(self.center_layout)
|
||||||
|
self.drop_layout.addStretch()
|
||||||
|
|
||||||
|
self.setLayout(self.drop_layout)
|
||||||
|
|
||||||
|
def dragEnterEvent(self, ev: QtGui.QDragEnterEvent) -> None:
|
||||||
|
ev.accept()
|
||||||
|
|
||||||
|
def dragLeaveEvent(self, ev: QtGui.QDragLeaveEvent) -> None:
|
||||||
|
ev.accept()
|
||||||
|
|
||||||
|
def dropEvent(self, ev: QtGui.QDropEvent) -> None:
|
||||||
|
ev.setDropAction(QtCore.Qt.CopyAction)
|
||||||
|
documents = []
|
||||||
|
supported_exts = get_supported_extensions()
|
||||||
|
for url_path in ev.mimeData().urls():
|
||||||
|
doc_path = url_path.toLocalFile()
|
||||||
|
doc_ext = os.path.splitext(doc_path)[1]
|
||||||
|
if doc_ext in supported_exts:
|
||||||
|
documents += [Document(doc_path)]
|
||||||
|
|
||||||
|
# Ignore anything dropped that's not a file (e.g. text)
|
||||||
|
if len(documents) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Ignore when all dropped files are unsupported
|
||||||
|
total_dragged_docs = len(ev.mimeData().urls())
|
||||||
|
num_unsupported_docs = total_dragged_docs - len(documents)
|
||||||
|
|
||||||
|
if num_unsupported_docs == total_dragged_docs:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Confirm with user when _some_ docs were ignored
|
||||||
|
if num_unsupported_docs > 0:
|
||||||
|
if not self.prompt_continue_without(num_unsupported_docs):
|
||||||
|
return
|
||||||
|
self.documents_selected.emit(documents)
|
||||||
|
|
||||||
|
def prompt_continue_without(self, num_unsupported_docs: int) -> int:
|
||||||
|
"""
|
||||||
|
Prompt the user if they want to convert even though some files are not
|
||||||
|
supported.
|
||||||
|
"""
|
||||||
|
if num_unsupported_docs == 1:
|
||||||
|
text = "1 file is not supported."
|
||||||
|
ok_text = "Continue without this file"
|
||||||
|
else: # plural
|
||||||
|
text = f"{num_unsupported_docs} files are not supported."
|
||||||
|
ok_text = "Continue without these files"
|
||||||
|
|
||||||
|
alert_widget = Alert(
|
||||||
|
self.dangerzone,
|
||||||
|
message=f"{text}\nThe supported extensions are: "
|
||||||
|
+ ", ".join(get_supported_extensions()),
|
||||||
|
ok_text=ok_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
return alert_widget.exec_()
|
||||||
|
|
||||||
|
|
||||||
class SettingsWidget(QtWidgets.QWidget):
|
class SettingsWidget(QtWidgets.QWidget):
|
||||||
start_clicked = QtCore.Signal()
|
start_clicked = QtCore.Signal()
|
||||||
change_docs_clicked = QtCore.Signal()
|
change_docs_clicked = QtCore.Signal()
|
||||||
|
|
|
@ -153,21 +153,26 @@ Run Dangerzone against a list of documents, and tick all options. Ensure that:
|
||||||
location.
|
location.
|
||||||
* The original files have been saved in the `unsafe/` directory.
|
* The original files have been saved in the `unsafe/` directory.
|
||||||
|
|
||||||
#### 8. Dangerzone CLI succeeds in converting multiple documents
|
#### 8. Dangerzone is able to handle drag-n-drop
|
||||||
|
|
||||||
|
Run Dangerzone against a set of documents that you drag-n-drop. Files should be
|
||||||
|
added and conversion should run without issue.
|
||||||
|
|
||||||
|
#### 9. Dangerzone CLI succeeds in converting multiple documents
|
||||||
|
|
||||||
_(Only for Windows and Linux)_
|
_(Only for Windows and Linux)_
|
||||||
|
|
||||||
Run Dangerzone CLI against a list of documents. Ensure that conversions happen
|
Run Dangerzone CLI against a list of documents. Ensure that conversions happen
|
||||||
sequentially, are completed successfully, and we see their progress.
|
sequentially, are completed successfully, and we see their progress.
|
||||||
|
|
||||||
#### 9. Dangerzone can open a document for conversion via right-click -> "Open With"
|
#### 10. Dangerzone can open a document for conversion via right-click -> "Open With"
|
||||||
|
|
||||||
_(Only for Windows, MacOS and Qubes)_
|
_(Only for Windows, MacOS and Qubes)_
|
||||||
|
|
||||||
Go to a directory with office documents, right-click on one, and click on "Open
|
Go to a directory with office documents, right-click on one, and click on "Open
|
||||||
With". We should be able to open the file with Dangerzone, and then convert it.
|
With". We should be able to open the file with Dangerzone, and then convert it.
|
||||||
|
|
||||||
#### 10. Dangerzone shows helpful errors for setup issues on Qubes
|
#### 11. Dangerzone shows helpful errors for setup issues on Qubes
|
||||||
|
|
||||||
_(Only for Qubes)_
|
_(Only for Qubes)_
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,11 @@ QDialog[OSColorMode="light"] QWidget {
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DocSelectionDropFrame{
|
||||||
|
border: 2px dashed rgb(193, 193, 193);
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* QLabel left-adjacent to a QLineEdit to give the illusion
|
* QLabel left-adjacent to a QLineEdit to give the illusion
|
||||||
* that it is part of it, but just not editable
|
* that it is part of it, but just not editable
|
||||||
|
|
1
share/document.svg
Normal file
1
share/document.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="20.308" xmlns="http://www.w3.org/2000/svg" height="24" id="screenshot-629d13e7-3095-8022-8003-fd3e19dbffbc" viewBox="10023.346 5693.5 20.308 24" style="-webkit-print-color-adjust: exact;" fill="none" version="1.1"><g id="shape-629d13e7-3095-8022-8003-fd3e19dbffbc"><g class="fills" id="fills-629d13e7-3095-8022-8003-fd3e19dbffbc"><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" rx="0" ry="0" d="M10026.115,5695.346C10025.606,5695.346,10025.192,5695.759,10025.192,5696.269L10025.192,5714.731C10025.192,5715.240,10025.606,5715.654,10026.115,5715.654L10040.885,5715.654C10041.394,5715.654,10041.808,5715.240,10041.808,5714.731L10041.808,5702.731L10035.346,5702.731C10034.836,5702.731,10034.423,5702.317,10034.423,5701.808L10034.423,5695.346L10026.115,5695.346ZZM10036.269,5696.652L10040.502,5700.885L10036.269,5700.885L10036.269,5696.652ZZM10023.346,5696.269C10023.346,5694.740,10024.586,5693.500,10026.115,5693.500L10035.346,5693.500C10035.591,5693.500,10035.826,5693.597,10035.999,5693.770L10043.384,5701.155C10043.557,5701.328,10043.654,5701.563,10043.654,5701.808L10043.654,5714.731C10043.654,5716.260,10042.414,5717.500,10040.885,5717.500L10026.115,5717.500C10024.586,5717.500,10023.346,5716.260,10023.346,5714.731L10023.346,5696.269ZZ" style="fill: rgb(153, 153, 153); fill-opacity: 1;"/></g></g></svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -2,17 +2,22 @@ import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from PySide6 import QtCore
|
|
||||||
from pytest import MonkeyPatch, fixture
|
from pytest import MonkeyPatch, fixture
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from pytestqt.qtbot import QtBot
|
from pytestqt.qtbot import QtBot
|
||||||
|
|
||||||
|
from dangerzone.document import Document
|
||||||
from dangerzone.gui import MainWindow
|
from dangerzone.gui import MainWindow
|
||||||
from dangerzone.gui import main_window as main_window_module
|
from dangerzone.gui import main_window as main_window_module
|
||||||
from dangerzone.gui import updater as updater_module
|
from dangerzone.gui import updater as updater_module
|
||||||
from dangerzone.gui.logic import DangerzoneGui
|
from dangerzone.gui.logic import DangerzoneGui
|
||||||
from dangerzone.gui.main_window import ContentWidget
|
from dangerzone.gui.main_window import ( # import Pyside related objects from here to avoid duplicating import logic.
|
||||||
|
ContentWidget,
|
||||||
|
QtCore,
|
||||||
|
QtGui,
|
||||||
|
)
|
||||||
from dangerzone.gui.updater import UpdateReport, UpdaterThread
|
from dangerzone.gui.updater import UpdateReport, UpdaterThread
|
||||||
|
|
||||||
from .test_updater import assert_report_equal, default_updater_settings
|
from .test_updater import assert_report_equal, default_updater_settings
|
||||||
|
@ -33,6 +38,51 @@ def content_widget(qtbot: QtBot, mocker: MockerFixture) -> ContentWidget:
|
||||||
return w
|
return w
|
||||||
|
|
||||||
|
|
||||||
|
def drag_files_event(mocker: MockerFixture, files: List[str]) -> QtGui.QDropEvent:
|
||||||
|
ev = mocker.MagicMock(spec=QtGui.QDropEvent)
|
||||||
|
ev.accept.return_value = True
|
||||||
|
|
||||||
|
urls = [QtCore.QUrl.fromLocalFile(x) for x in files]
|
||||||
|
ev.mimeData.return_value.has_urls.return_value = True
|
||||||
|
ev.mimeData.return_value.urls.return_value = urls
|
||||||
|
return ev
|
||||||
|
|
||||||
|
|
||||||
|
@fixture
|
||||||
|
def drag_valid_files_event(
|
||||||
|
mocker: MockerFixture, sample_doc: str, sample_pdf: str
|
||||||
|
) -> QtGui.QDropEvent:
|
||||||
|
return drag_files_event(mocker, [sample_doc, sample_pdf])
|
||||||
|
|
||||||
|
|
||||||
|
@fixture
|
||||||
|
def drag_1_invalid_file_event(
|
||||||
|
mocker: MockerFixture, sample_doc: str, tmp_path: pathlib.Path
|
||||||
|
) -> QtGui.QDropEvent:
|
||||||
|
unsupported_file_path = tmp_path / "file.unsupported"
|
||||||
|
shutil.copy(sample_doc, unsupported_file_path)
|
||||||
|
return drag_files_event(mocker, [str(unsupported_file_path)])
|
||||||
|
|
||||||
|
|
||||||
|
@fixture
|
||||||
|
def drag_1_invalid_and_2_valid_files_event(
|
||||||
|
mocker: MockerFixture, tmp_path: pathlib.Path, sample_doc: str, sample_pdf: str
|
||||||
|
) -> QtGui.QDropEvent:
|
||||||
|
unsupported_file_path = tmp_path / "file.unsupported"
|
||||||
|
shutil.copy(sample_doc, unsupported_file_path)
|
||||||
|
return drag_files_event(
|
||||||
|
mocker, [sample_doc, sample_pdf, str(unsupported_file_path)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@fixture
|
||||||
|
def drag_text_event(mocker: MockerFixture) -> QtGui.QDropEvent:
|
||||||
|
ev = mocker.MagicMock()
|
||||||
|
ev.accept.return_value = True
|
||||||
|
ev.mimeData.return_value.has_urls.return_value = False
|
||||||
|
return ev
|
||||||
|
|
||||||
|
|
||||||
def test_default_menu(
|
def test_default_menu(
|
||||||
qtbot: QtBot,
|
qtbot: QtBot,
|
||||||
updater: UpdaterThread,
|
updater: UpdaterThread,
|
||||||
|
@ -121,7 +171,7 @@ def test_update_detected(
|
||||||
window = MainWindow(qt_updater.dangerzone)
|
window = MainWindow(qt_updater.dangerzone)
|
||||||
window.register_update_handler(qt_updater.finished)
|
window.register_update_handler(qt_updater.finished)
|
||||||
handle_updates_spy = mocker.spy(window, "handle_updates")
|
handle_updates_spy = mocker.spy(window, "handle_updates")
|
||||||
load_svg_spy = mocker.spy(window, "load_svg_image")
|
load_svg_spy = mocker.spy(main_window_module, "load_svg_image")
|
||||||
|
|
||||||
menu_actions_before = window.hamburger_button.menu().actions()
|
menu_actions_before = window.hamburger_button.menu().actions()
|
||||||
|
|
||||||
|
@ -231,7 +281,7 @@ def test_update_error(
|
||||||
window = MainWindow(qt_updater.dangerzone)
|
window = MainWindow(qt_updater.dangerzone)
|
||||||
window.register_update_handler(qt_updater.finished)
|
window.register_update_handler(qt_updater.finished)
|
||||||
handle_updates_spy = mocker.spy(window, "handle_updates")
|
handle_updates_spy = mocker.spy(window, "handle_updates")
|
||||||
load_svg_spy = mocker.spy(window, "load_svg_image")
|
load_svg_spy = mocker.spy(main_window_module, "load_svg_image")
|
||||||
|
|
||||||
menu_actions_before = window.hamburger_button.menu().actions()
|
menu_actions_before = window.hamburger_button.menu().actions()
|
||||||
|
|
||||||
|
@ -374,3 +424,71 @@ def test_change_document_button(
|
||||||
]
|
]
|
||||||
assert len(docs) == 1
|
assert len(docs) == 1
|
||||||
assert docs[0] == str(tmp_sample_doc)
|
assert docs[0] == str(tmp_sample_doc)
|
||||||
|
|
||||||
|
|
||||||
|
def test_drop_valid_documents(
|
||||||
|
content_widget: ContentWidget,
|
||||||
|
drag_valid_files_event: QtGui.QDropEvent,
|
||||||
|
qtbot: QtBot,
|
||||||
|
) -> None:
|
||||||
|
with qtbot.waitSignal(
|
||||||
|
content_widget.doc_selection_wrapper.documents_selected,
|
||||||
|
check_params_cb=lambda x: len(x) == 2 and isinstance(x[0], Document),
|
||||||
|
):
|
||||||
|
content_widget.doc_selection_wrapper.dropEvent(drag_valid_files_event)
|
||||||
|
|
||||||
|
|
||||||
|
def test_drop_text(
|
||||||
|
content_widget: ContentWidget,
|
||||||
|
drag_text_event: QtGui.QDropEvent,
|
||||||
|
qtbot: QtBot,
|
||||||
|
) -> None:
|
||||||
|
with qtbot.assertNotEmitted(
|
||||||
|
content_widget.doc_selection_wrapper.documents_selected
|
||||||
|
):
|
||||||
|
content_widget.doc_selection_wrapper.dropEvent(drag_text_event)
|
||||||
|
|
||||||
|
|
||||||
|
def test_drop_1_invalid_doc(
|
||||||
|
content_widget: ContentWidget,
|
||||||
|
drag_1_invalid_file_event: QtGui.QDropEvent,
|
||||||
|
qtbot: QtBot,
|
||||||
|
) -> None:
|
||||||
|
with qtbot.assertNotEmitted(
|
||||||
|
content_widget.doc_selection_wrapper.documents_selected
|
||||||
|
):
|
||||||
|
content_widget.doc_selection_wrapper.dropEvent(drag_1_invalid_file_event)
|
||||||
|
|
||||||
|
|
||||||
|
def test_drop_1_invalid_2_valid_documents(
|
||||||
|
content_widget: ContentWidget,
|
||||||
|
drag_1_invalid_and_2_valid_files_event: QtGui.QDropEvent,
|
||||||
|
qtbot: QtBot,
|
||||||
|
monkeypatch: MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
# If we accept to continue
|
||||||
|
monkeypatch.setattr(
|
||||||
|
content_widget.doc_selection_wrapper, "prompt_continue_without", lambda x: True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Then the 2 valid docs will be selected
|
||||||
|
with qtbot.waitSignal(
|
||||||
|
content_widget.doc_selection_wrapper.documents_selected,
|
||||||
|
check_params_cb=lambda x: len(x) == 2 and isinstance(x[0], Document),
|
||||||
|
):
|
||||||
|
content_widget.doc_selection_wrapper.dropEvent(
|
||||||
|
drag_1_invalid_and_2_valid_files_event
|
||||||
|
)
|
||||||
|
|
||||||
|
# If we refuse to continue
|
||||||
|
monkeypatch.setattr(
|
||||||
|
content_widget.doc_selection_wrapper, "prompt_continue_without", lambda x: False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Then no docs will be selected
|
||||||
|
with qtbot.assertNotEmitted(
|
||||||
|
content_widget.doc_selection_wrapper.documents_selected,
|
||||||
|
):
|
||||||
|
content_widget.doc_selection_wrapper.dropEvent(
|
||||||
|
drag_1_invalid_and_2_valid_files_event
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in a new issue