Skip to content

Commit 1d0cbce

Browse files
committed
OWSave: Use the same dialog for Windows and macOS
1 parent 02d49df commit 1d0cbce

File tree

2 files changed

+42
-53
lines changed

2 files changed

+42
-53
lines changed

Orange/widgets/data/owsave.py

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -215,31 +215,33 @@ def _default_valid_filter(self):
215215

216216
# As of Qt 5.9, QFileDialog.setDefaultSuffix does not support double
217217
# suffixes, not even in non-native dialogs. We handle each OS separately.
218-
if sys.platform == "darwin":
219-
# On macOS, is double suffixes are passed to the dialog, they are
220-
# appended multiple times even if already present (QTBUG-44227).
221-
# The only known workaround with native dialog is to use suffix *.*.
222-
# We add the correct suffix after closing the dialog and only then check
218+
if sys.platform in ("darwin", "win32"):
219+
# macOS and Windows native dialogs do not correctly handle double
220+
# extensions. We thus don't pass any suffixes to the dialog and add
221+
# the correct suffix after closing the dialog and only then check
223222
# if the file exists and ask whether to override.
224223
# It is a bit confusing that the user does not see the final name in the
225224
# dialog, but I see no better solution.
226225
def get_save_filename(self): # pragma: no cover
227-
def no_suffix(filt):
228-
return filt.split("(")[0] + "(*.*)"
226+
if sys.platform == "darwin":
227+
def remove_star(filt):
228+
return filt.replace(" (*.", " (.")
229+
else:
230+
def remove_star(filt):
231+
return filt
229232

230-
mac_filters = {no_suffix(f): f for f in self._valid_filters()}
233+
no_ext_filters = {remove_star(f): f for f in self._valid_filters()}
231234
filename = self._initial_start_dir()
232235
while True:
233236
dlg = QFileDialog(
234-
None, "Save File", filename, ";;".join(mac_filters))
237+
None, "Save File", filename, ";;".join(no_ext_filters))
235238
dlg.setAcceptMode(dlg.AcceptSave)
236-
dlg.selectNameFilter(no_suffix(self._default_valid_filter()))
237-
dlg.setOption(QFileDialog.HideNameFilterDetails)
239+
dlg.selectNameFilter(remove_star(self._default_valid_filter()))
238240
dlg.setOption(QFileDialog.DontConfirmOverwrite)
239241
if dlg.exec() == QFileDialog.Rejected:
240242
return "", ""
241243
filename = dlg.selectedFiles()[0]
242-
selected_filter = mac_filters[dlg.selectedNameFilter()]
244+
selected_filter = no_ext_filters[dlg.selectedNameFilter()]
243245
filename = self._replace_extension(
244246
filename, self._extension_from_filter(selected_filter))
245247
if not os.path.exists(filename) or QMessageBox.question(
@@ -248,15 +250,6 @@ def no_suffix(filt):
248250
"Overwrite?") == QMessageBox.Yes:
249251
return filename, selected_filter
250252

251-
elif sys.platform == "win32":
252-
# TODO: Behaviour of getSaveFileName on Windows is not tested!!!
253-
# Windows native dialog may work correctly; if not, we may do the same
254-
# as for macOS?
255-
def get_save_filename(self): # pragma: no cover
256-
return QFileDialog.getSaveFileName(
257-
self, "Save File", self._initial_start_dir(),
258-
";;".join(self._valid_filters()), self._default_valid_filter())
259-
260253
else: # Linux and any unknown platforms
261254
# Qt does not use a native dialog on Linux, so we can connect to
262255
# filterSelected and to overload selectFile to change the extension

Orange/widgets/data/tests/test_owsave.py

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from unittest.mock import patch, Mock
44
import os
55
import sys
6-
import re
76

87
import scipy.sparse as sp
98
from AnyQt.QtWidgets import QFileDialog
@@ -444,26 +443,35 @@ def test_save_file_dialog_uses_valid_filters_linux(self):
444443
instance.selectNameFilter.assert_called_with("a (*.a)")
445444

446445

447-
@unittest.skipUnless(sys.platform == "darwin", "Test for dialog for macOS")
446+
@unittest.skipUnless(sys.platform in ("darwin", "win32"),
447+
"Test for native dialog on Windows and macOS")
448448
class TestOWSaveDarwinDialog(TestOWSaveBase): # pragma: no cover
449-
@staticmethod
450-
def _mac_filter(filt):
451-
return re.sub(r"\(\*\..*\)", "(*.*)", filt)
449+
if sys.platform == "darwin":
450+
@staticmethod
451+
def remove_star(filt):
452+
return filt.replace(" (*.", " (.")
453+
else:
454+
@staticmethod
455+
def remove_star(filt):
456+
return filt
452457

453458
@patch("Orange.widgets.data.owsave.QFileDialog")
454459
def test_get_save_filename_darwin(self, dlg):
455460
widget = self.widget
456461
widget._initial_start_dir = lambda: "baz"
457-
widget.filters = dict.fromkeys(("a (*.a)", "b (*.b)", "c (*.c)"))
458-
widget.filter = "b (*.b)"
462+
widget.filters = dict.fromkeys(("aa (*.a)", "bb (*.b)", "cc (*.c)"))
463+
widget.filter = "bb (*.b)"
459464
instance = dlg.return_value
460465
instance.exec.return_value = dlg.Accepted = QFileDialog.Accepted
461466
instance.selectedFiles.return_value = ["foo"]
462-
instance.selectedNameFilter.return_value = "a (*.*)"
463-
self.assertEqual(widget.get_save_filename(), ("foo.a", "a (*.a)"))
467+
instance.selectedNameFilter.return_value = self.remove_star("aa (*.a)")
468+
self.assertEqual(widget.get_save_filename(), ("foo.a", "aa (*.a)"))
464469
self.assertEqual(dlg.call_args[0][2], "baz")
465-
self.assertEqual(dlg.call_args[0][3], "a (*.*);;b (*.*);;c (*.*)")
466-
instance.selectNameFilter.assert_called_with("b (*.*)")
470+
self.assertEqual(
471+
dlg.call_args[0][3],
472+
self.remove_star("aa (*.a);;bb (*.b);;cc (*.c)"))
473+
instance.selectNameFilter.assert_called_with(
474+
self.remove_star("bb (*.b)"))
467475

468476
instance.exec.return_value = dlg.Rejected = QFileDialog.Rejected
469477
self.assertEqual(widget.get_save_filename(), ("", ""))
@@ -482,7 +490,7 @@ def test_save_file_dialog_enforces_extension_darwin(self, dlg):
482490
instance = dlg.return_value
483491
instance.exec.return_value = QFileDialog.Accepted
484492

485-
instance.selectedNameFilter.return_value = self._mac_filter(filter1)
493+
instance.selectedNameFilter.return_value = self.remove_star(filter1)
486494
instance.selectedFiles.return_value = ["foo"]
487495
self.assertEqual(widget.get_save_filename()[0], "foo.tab")
488496
instance.selectedFiles.return_value = ["foo.pkl"]
@@ -494,7 +502,7 @@ def test_save_file_dialog_enforces_extension_darwin(self, dlg):
494502
instance.selectedFiles.return_value = ["foo.bar"]
495503
self.assertEqual(widget.get_save_filename()[0], "foo.bar.tab")
496504

497-
instance.selectedNameFilter.return_value = self._mac_filter(filter2)
505+
instance.selectedNameFilter.return_value = self.remove_star(filter2)
498506
instance.selectedFiles.return_value = ["foo"]
499507
self.assertEqual(widget.get_save_filename()[0], "foo.csv.gz")
500508
instance.selectedFiles.return_value = ["foo.pkl"]
@@ -525,7 +533,7 @@ def selected_files():
525533
instance = dlg.return_value
526534
instance.exec.return_value = QFileDialog.Accepted
527535
instance.selectedFiles = selected_files
528-
instance.selectedNameFilter.return_value = self._mac_filter(filter1)
536+
instance.selectedNameFilter.return_value = self.remove_star(filter1)
529537

530538
attempts = -1
531539
msgbox.question.return_value = msgbox.Yes = 1
@@ -538,26 +546,15 @@ def selected_files():
538546
@patch("Orange.widgets.data.owsave.QFileDialog")
539547
def test_save_file_dialog_uses_valid_filters_darwin(self, dlg):
540548
widget = self.widget
541-
widget._valid_filters = lambda: ["a (*.a)", "b (*.b)"]
542-
widget._default_valid_filter = lambda: "a (*.a)"
549+
widget._valid_filters = lambda: ["aa (*.a)", "bb (*.b)"]
550+
widget._default_valid_filter = lambda: "aa (*.a)"
543551
instance = dlg.return_value
544552
instance.exec.return_value = dlg.Rejected = QFileDialog.Rejected
545553
widget.get_save_filename()
546-
self.assertEqual(dlg.call_args[0][3], "a (*.*);;b (*.*)")
547-
instance.selectNameFilter.assert_called_with("a (*.*)")
548-
549-
550-
@unittest.skipUnless(sys.platform == "win32", "Test for dialog for Windows")
551-
class TestOWSaveWindowsDialog(TestOWSaveBase): # pragma: no cover
552-
@patch("Orange.widgets.data.owsave.QFileDialog.getSaveFileName")
553-
def test_save_file_dialog_uses_valid_filters_windows(self, dlg):
554-
widget = self.widget
555-
widget._valid_filters = lambda: ["a (*.a)", "b (*.b)"]
556-
widget._default_valid_filter = lambda: "a (*.a)"
557-
widget.get_save_filename()
558-
call_args = dlg.call_args
559-
self.assertEqual(call_args[0][3], "a (*.a);;b (*.b)")
560-
self.assertEqual(call_args[0][4], "a (*.a)")
554+
self.assertEqual(
555+
dlg.call_args[0][3], self.remove_star("aa (*.a);;bb (*.b)"))
556+
instance.selectNameFilter.assert_called_with(
557+
self.remove_star("aa (*.a)"))
561558

562559

563560
class TestOWSaveUtils(unittest.TestCase):
@@ -592,6 +589,5 @@ def test_extension_from_filter(self):
592589
OWSave._extension_from_filter("Description (.foo.bar)"), ".foo.bar")
593590

594591

595-
596592
if __name__ == "__main__":
597593
unittest.main()

0 commit comments

Comments
 (0)