Add drag and drop support for document selection

This commit is contained in:
deeplow 2024-03-20 18:44:21 +00:00 committed by Alexis Métaireau
parent 7744cd55ec
commit d0e1df5546
No known key found for this signature in database
GPG key ID: C65C7A89A8FFC56E
7 changed files with 322 additions and 44 deletions

View file

@ -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

View file

@ -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)_

View file

@ -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()

View file

@ -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)_

View file

@ -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
View 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

View file

@ -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
)