Skip to content

Commit

Permalink
OWSave: Use the same dialog for Windows and macOS
Browse files Browse the repository at this point in the history
  • Loading branch information
janezd committed Mar 1, 2019
1 parent c83cb1b commit 64cc2b9
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 52 deletions.
31 changes: 10 additions & 21 deletions Orange/widgets/data/owsave.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,31 +215,29 @@ def _default_valid_filter(self):

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

mac_filters = {no_suffix(f): f for f in self._valid_filters()}
no_ext_filters = {remove_star(f): f for f in self._valid_filters()}
filename = self._initial_start_dir()
while True:
dlg = QFileDialog(
None, "Save File", filename, ";;".join(mac_filters))
None, "Save File", filename, ";;".join(no_ext_filters))
dlg.setAcceptMode(dlg.AcceptSave)
dlg.selectNameFilter(no_suffix(self._default_valid_filter()))
dlg.setOption(QFileDialog.HideNameFilterDetails)
dlg.selectNameFilter(remove_star(self._default_valid_filter()))
dlg.setOption(QFileDialog.DontConfirmOverwrite)
if dlg.exec() == QFileDialog.Rejected:
return "", ""
filename = dlg.selectedFiles()[0]
selected_filter = mac_filters[dlg.selectedNameFilter()]
selected_filter = no_ext_filters[dlg.selectedNameFilter()]
filename = self._replace_extension(
filename, self._extension_from_filter(selected_filter))
if not os.path.exists(filename) or QMessageBox.question(
Expand All @@ -248,15 +246,6 @@ def no_suffix(filt):
"Overwrite?") == QMessageBox.Yes:
return filename, selected_filter

elif sys.platform == "win32":
# TODO: Behaviour of getSaveFileName on Windows is not tested!!!
# Windows native dialog may work correctly; if not, we may do the same
# as for macOS?
def get_save_filename(self): # pragma: no cover
return QFileDialog.getSaveFileName(
self, "Save File", self._initial_start_dir(),
";;".join(self._valid_filters()), self._default_valid_filter())

else: # Linux and any unknown platforms
# Qt does not use a native dialog on Linux, so we can connect to
# filterSelected and to overload selectFile to change the extension
Expand Down
48 changes: 17 additions & 31 deletions Orange/widgets/data/tests/test_owsave.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from unittest.mock import patch, Mock
import os
import sys
import re

import scipy.sparse as sp
from AnyQt.QtWidgets import QFileDialog
Expand Down Expand Up @@ -444,26 +443,27 @@ def test_save_file_dialog_uses_valid_filters_linux(self):
instance.selectNameFilter.assert_called_with("a (*.a)")


@unittest.skipUnless(sys.platform == "darwin", "Test for dialog for macOS")
@unittest.skipUnless(sys.platform in ("darwin", "win32"),
"Test for native dialog on Windows and macOS")
class TestOWSaveDarwinDialog(TestOWSaveBase): # pragma: no cover
@staticmethod
def _mac_filter(filt):
return re.sub(r"\(\*\..*\)", "(*.*)", filt)
def remove_star(filt):
return filt.replace(" (*.", " (.")

@patch("Orange.widgets.data.owsave.QFileDialog")
def test_get_save_filename_darwin(self, dlg):
widget = self.widget
widget._initial_start_dir = lambda: "baz"
widget.filters = dict.fromkeys(("a (*.a)", "b (*.b)", "c (*.c)"))
widget.filter = "b (*.b)"
widget.filters = dict.fromkeys(("aa (*.a)", "bb (*.b)", "cc (*.c)"))
widget.filter = "bb (*.b)"
instance = dlg.return_value
instance.exec.return_value = dlg.Accepted = QFileDialog.Accepted
instance.selectedFiles.return_value = ["foo"]
instance.selectedNameFilter.return_value = "a (*.*)"
self.assertEqual(widget.get_save_filename(), ("foo.a", "a (*.a)"))
instance.selectedNameFilter.return_value = "aa (.a)"
self.assertEqual(widget.get_save_filename(), ("foo.a", "aa (*.a)"))
self.assertEqual(dlg.call_args[0][2], "baz")
self.assertEqual(dlg.call_args[0][3], "a (*.*);;b (*.*);;c (*.*)")
instance.selectNameFilter.assert_called_with("b (*.*)")
self.assertEqual(dlg.call_args[0][3], "aa (.a);;bb (.b);;cc (.c)")
instance.selectNameFilter.assert_called_with("bb (.b)")

instance.exec.return_value = dlg.Rejected = QFileDialog.Rejected
self.assertEqual(widget.get_save_filename(), ("", ""))
Expand All @@ -482,7 +482,7 @@ def test_save_file_dialog_enforces_extension_darwin(self, dlg):
instance = dlg.return_value
instance.exec.return_value = QFileDialog.Accepted

instance.selectedNameFilter.return_value = self._mac_filter(filter1)
instance.selectedNameFilter.return_value = self.remove_star(filter1)
instance.selectedFiles.return_value = ["foo"]
self.assertEqual(widget.get_save_filename()[0], "foo.tab")
instance.selectedFiles.return_value = ["foo.pkl"]
Expand All @@ -494,7 +494,7 @@ def test_save_file_dialog_enforces_extension_darwin(self, dlg):
instance.selectedFiles.return_value = ["foo.bar"]
self.assertEqual(widget.get_save_filename()[0], "foo.bar.tab")

instance.selectedNameFilter.return_value = self._mac_filter(filter2)
instance.selectedNameFilter.return_value = self.remove_star(filter2)
instance.selectedFiles.return_value = ["foo"]
self.assertEqual(widget.get_save_filename()[0], "foo.csv.gz")
instance.selectedFiles.return_value = ["foo.pkl"]
Expand Down Expand Up @@ -525,7 +525,7 @@ def selected_files():
instance = dlg.return_value
instance.exec.return_value = QFileDialog.Accepted
instance.selectedFiles = selected_files
instance.selectedNameFilter.return_value = self._mac_filter(filter1)
instance.selectedNameFilter.return_value = self.remove_star(filter1)

attempts = -1
msgbox.question.return_value = msgbox.Yes = 1
Expand All @@ -538,26 +538,13 @@ def selected_files():
@patch("Orange.widgets.data.owsave.QFileDialog")
def test_save_file_dialog_uses_valid_filters_darwin(self, dlg):
widget = self.widget
widget._valid_filters = lambda: ["a (*.a)", "b (*.b)"]
widget._default_valid_filter = lambda: "a (*.a)"
widget._valid_filters = lambda: ["aa (*.a)", "bb (*.b)"]
widget._default_valid_filter = lambda: "aa (*.a)"
instance = dlg.return_value
instance.exec.return_value = dlg.Rejected = QFileDialog.Rejected
widget.get_save_filename()
self.assertEqual(dlg.call_args[0][3], "a (*.*);;b (*.*)")
instance.selectNameFilter.assert_called_with("a (*.*)")


@unittest.skipUnless(sys.platform == "win32", "Test for dialog for Windows")
class TestOWSaveWindowsDialog(TestOWSaveBase): # pragma: no cover
@patch("Orange.widgets.data.owsave.QFileDialog.getSaveFileName")
def test_save_file_dialog_uses_valid_filters_windows(self, dlg):
widget = self.widget
widget._valid_filters = lambda: ["a (*.a)", "b (*.b)"]
widget._default_valid_filter = lambda: "a (*.a)"
widget.get_save_filename()
call_args = dlg.call_args
self.assertEqual(call_args[0][3], "a (*.a);;b (*.b)")
self.assertEqual(call_args[0][4], "a (*.a)")
self.assertEqual(dlg.call_args[0][3], "aa (.a);;bb (.b)")
instance.selectNameFilter.assert_called_with("aa (.a)")


class TestOWSaveUtils(unittest.TestCase):
Expand Down Expand Up @@ -592,6 +579,5 @@ def test_extension_from_filter(self):
OWSave._extension_from_filter("Description (.foo.bar)"), ".foo.bar")



if __name__ == "__main__":
unittest.main()

0 comments on commit 64cc2b9

Please sign in to comment.