Skip to content

Commit d7aadbd

Browse files
committed
Save dialogs: Avoid weird behaviour with extensions on macOS
1 parent c807a41 commit d7aadbd

File tree

4 files changed

+28
-81
lines changed

4 files changed

+28
-81
lines changed

Orange/widgets/data/owsave.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,13 @@ def migrate_to_version_2():
128128

129129
def initial_start_dir(self):
130130
if self.filename and os.path.exists(os.path.split(self.filename)[0]):
131-
return self.filename
131+
return os.path.splitext(self.filename)[0]
132132
else:
133133
data_name = getattr(self.data, 'name', '')
134134
if data_name:
135135
if self.writer is None:
136136
self.filter = self.default_filter()
137137
assert self.writer is not None
138-
data_name += self.writer.EXTENSIONS[0]
139138
return os.path.join(self.last_dir or _userhome, data_name)
140139

141140
def valid_filters(self):

Orange/widgets/data/tests/test_owsave.py

Lines changed: 10 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,19 @@ def test_initial_start_dir(self):
7373

7474
with patch("os.path.exists", return_value=True):
7575
widget.filename = _w("/usr/foo/bar.csv")
76-
self.assertEqual(widget.initial_start_dir(), widget.filename)
76+
self.assertEqual(widget.initial_start_dir(), widget.filename[:-4])
7777

7878
widget.filename = ""
7979
widget.last_dir = _w("/usr/bar")
8080
self.assertEqual(widget.initial_start_dir(), _w("/usr/bar/"))
8181

8282
widget.last_dir = _w("/usr/bar")
8383
self.send_signal(widget.Inputs.data, self.iris)
84-
self.assertEqual(widget.initial_start_dir(),
85-
_w("/usr/bar/iris.csv"))
84+
self.assertEqual(widget.initial_start_dir(), _w("/usr/bar/iris"))
8685

8786
widget.last_dir = ""
8887
self.assertEqual(widget.initial_start_dir(),
89-
os.path.expanduser(_w("~/iris.csv")))
88+
os.path.expanduser(_w("~/iris")))
9089

9190
@patch("Orange.widgets.utils.save.owsavebase.QFileDialog.getSaveFileName")
9291
def test_save_file_sets_name(self, _filedialog):
@@ -521,15 +520,6 @@ def test_save_file_dialog_uses_valid_filters_linux(self):
521520
@unittest.skipUnless(sys.platform in ("darwin", "win32"),
522521
"Test for native dialog on Windows and macOS")
523522
class TestOWSaveDarwinDialog(OWSaveTestBase): # pragma: no cover
524-
if sys.platform == "darwin":
525-
@staticmethod
526-
def remove_star(filt):
527-
return filt.replace(" (*.", " (.")
528-
else:
529-
@staticmethod
530-
def remove_star(filt):
531-
return filt
532-
533523
@patch("Orange.widgets.utils.save.owsavebase.QFileDialog")
534524
def test_get_save_filename_darwin(self, dlg):
535525
widget = self.widget
@@ -540,14 +530,11 @@ def test_get_save_filename_darwin(self, dlg):
540530
instance = dlg.return_value
541531
instance.exec.return_value = dlg.Accepted = QFileDialog.Accepted
542532
instance.selectedFiles.return_value = ["foo"]
543-
instance.selectedNameFilter.return_value = self.remove_star("aa (*.a)")
533+
instance.selectedNameFilter.return_value = "aa (*.a)"
544534
self.assertEqual(widget.get_save_filename(), ("foo.a", "aa (*.a)"))
545535
self.assertEqual(dlg.call_args[0][2], "baz")
546-
self.assertEqual(
547-
dlg.call_args[0][3],
548-
self.remove_star("aa (*.a);;bb (*.b);;cc (*.c)"))
549-
instance.selectNameFilter.assert_called_with(
550-
self.remove_star("bb (*.b)"))
536+
self.assertEqual(dlg.call_args[0][3], "aa (*.a);;bb (*.b);;cc (*.c)")
537+
instance.selectNameFilter.assert_called_with("bb (*.b)")
551538

552539
instance.exec.return_value = dlg.Rejected = QFileDialog.Rejected
553540
self.assertEqual(widget.get_save_filename(), ("", ""))
@@ -567,7 +554,7 @@ def test_save_file_dialog_enforces_extension_darwin(self, dlg):
567554
instance = dlg.return_value
568555
instance.exec.return_value = QFileDialog.Accepted
569556

570-
instance.selectedNameFilter.return_value = self.remove_star(filter1)
557+
instance.selectedNameFilter.return_value = filter1
571558
instance.selectedFiles.return_value = ["foo"]
572559
self.assertEqual(widget.get_save_filename()[0], "foo.tab")
573560
instance.selectedFiles.return_value = ["foo.pkl"]
@@ -579,7 +566,7 @@ def test_save_file_dialog_enforces_extension_darwin(self, dlg):
579566
instance.selectedFiles.return_value = ["foo.bar"]
580567
self.assertEqual(widget.get_save_filename()[0], "foo.bar.tab")
581568

582-
instance.selectedNameFilter.return_value = self.remove_star(filter2)
569+
instance.selectedNameFilter.return_value = filter2
583570
instance.selectedFiles.return_value = ["foo"]
584571
self.assertEqual(widget.get_save_filename()[0], "foo.csv.gz")
585572
instance.selectedFiles.return_value = ["foo.pkl"]
@@ -591,36 +578,6 @@ def test_save_file_dialog_enforces_extension_darwin(self, dlg):
591578
instance.selectedFiles.return_value = ["foo.bar"]
592579
self.assertEqual(widget.get_save_filename()[0], "foo.bar.csv.gz")
593580

594-
@patch("Orange.widgets.utils.save.owsavebase.QFileDialog")
595-
@patch("os.path.exists", new=lambda x: x == "old.tab")
596-
@patch("Orange.widgets.utils.save.owsavebase.QMessageBox")
597-
def test_save_file_dialog_asks_for_overwrite_darwin(self, msgbox, dlg):
598-
def selected_files():
599-
nonlocal attempts
600-
attempts += 1
601-
return [["old.tab", "new.tab"][attempts]]
602-
603-
widget = self.widget
604-
widget.initial_start_dir = lambda: "baz"
605-
filter1 = "" # prevent pylint warning 'undefined-loop-variable'
606-
for filter1 in widget.get_filters():
607-
if OWSaveBase._extension_from_filter(filter1) == ".tab":
608-
break
609-
610-
widget.filter = filter1
611-
instance = dlg.return_value
612-
instance.exec.return_value = QFileDialog.Accepted
613-
instance.selectedFiles = selected_files
614-
instance.selectedNameFilter.return_value = self.remove_star(filter1)
615-
616-
attempts = -1
617-
msgbox.question.return_value = msgbox.Yes = 1
618-
self.assertEqual(widget.get_save_filename()[0], "old.tab")
619-
620-
attempts = -1
621-
msgbox.question.return_value = msgbox.No = 0
622-
self.assertEqual(widget.get_save_filename()[0], "new.tab")
623-
624581
@patch("Orange.widgets.utils.save.owsavebase.QFileDialog")
625582
def test_save_file_dialog_uses_valid_filters_darwin(self, dlg):
626583
widget = self.widget
@@ -629,10 +586,8 @@ def test_save_file_dialog_uses_valid_filters_darwin(self, dlg):
629586
instance = dlg.return_value
630587
instance.exec.return_value = dlg.Rejected = QFileDialog.Rejected
631588
widget.get_save_filename()
632-
self.assertEqual(
633-
dlg.call_args[0][3], self.remove_star("aa (*.a);;bb (*.b)"))
634-
instance.selectNameFilter.assert_called_with(
635-
self.remove_star("aa (*.a)"))
589+
self.assertEqual(dlg.call_args[0][3], "aa (*.a);;bb (*.b)")
590+
instance.selectNameFilter.assert_called_with("aa (*.a)")
636591

637592

638593
if __name__ == "__main__":

Orange/widgets/utils/save/owsavebase.py

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
_userhome = os.path.expanduser(f"~{os.sep}")
1212

13+
_IS_DARWIN = sys.platform == "darwin"
14+
_IS_WIN32 = sys.platform == "win32"
1315

1416
class OWSaveBase(widget.OWWidget, openclass=True):
1517
"""
@@ -300,7 +302,7 @@ def initial_start_dir(self):
300302
Return either the current file's path, the last directory or home.
301303
"""
302304
if self.filename and os.path.exists(os.path.split(self.filename)[0]):
303-
return self.filename
305+
return os.path.splitext(self.filename)[0]
304306
else:
305307
return self.last_dir or _userhome
306308

@@ -353,39 +355,30 @@ def migrate_settings(cls, settings, version):
353355

354356
# As of Qt 5.9, QFileDialog.setDefaultSuffix does not support double
355357
# suffixes, not even in non-native dialogs. We handle each OS separately.
356-
if sys.platform in ("darwin", "win32"):
357-
# macOS and Windows native dialogs do not correctly handle double
358-
# extensions. We thus don't pass any suffixes to the dialog and add
359-
# the correct suffix after closing the dialog and only then check
360-
# if the file exists and ask whether to override.
361-
# It is a bit confusing that the user does not see the final name in the
362-
# dialog, but I see no better solution.
358+
if _IS_DARWIN or _IS_WIN32:
363359
def get_save_filename(self): # pragma: no cover
364-
if sys.platform == "darwin":
365-
def remove_star(filt):
366-
return filt.replace(" (*.", " (.")
367-
else:
368-
def remove_star(filt):
369-
return filt
370-
371-
no_ext_filters = {remove_star(f): f for f in self.valid_filters()}
372360
filename = self.initial_start_dir()
373361
while True:
374362
dlg = QFileDialog(
375-
None, "Save File", filename, ";;".join(no_ext_filters))
363+
None, "Save File", filename, ";;".join(self.valid_filters()))
376364
dlg.setAcceptMode(dlg.AcceptSave)
377-
dlg.selectNameFilter(remove_star(self.default_valid_filter()))
378-
dlg.setOption(QFileDialog.DontConfirmOverwrite)
365+
dlg.selectNameFilter(self.default_valid_filter())
366+
# MacOs (currently) ignores DontConfirmOverwrite
367+
# Let us not set it, so we know it's not set in the future
368+
if _IS_WIN32:
369+
dlg.setOption(QFileDialog.DontConfirmOverwrite)
379370
if dlg.exec() == QFileDialog.Rejected:
380371
return "", ""
381372
filename = dlg.selectedFiles()[0]
382-
selected_filter = no_ext_filters[dlg.selectedNameFilter()]
373+
selected_filter = dlg.selectedNameFilter()
383374
filename = self._replace_extension(
384375
filename, self._extension_from_filter(selected_filter))
385-
if not os.path.exists(filename) or QMessageBox.question(
376+
if (not os.path.exists(filename)
377+
or _IS_DARWIN # MacOs already asked for confirmation
378+
or QMessageBox.question(
386379
self, "Overwrite file?",
387380
f"File {os.path.split(filename)[1]} already exists.\n"
388-
"Overwrite?") == QMessageBox.Yes:
381+
"Overwrite?") == QMessageBox.Yes):
389382
return filename, selected_filter
390383

391384
else: # Linux and any unknown platforms

i18n/si/msgs.jaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13642,6 +13642,8 @@ widgets/utils/plot/owplotgui_obsolete.py:
1364213642
Dark: false
1364313643
widgets/utils/save/owsavebase.py:
1364413644
~{os.sep}: false
13645+
darwin: false
13646+
win32: false
1364513647
class `OWSaveBase`:
1364613648
class `Information`:
1364713649
Empty input; nothing was saved.: Ni podatkov.
@@ -13678,8 +13680,6 @@ widgets/utils/save/owsavebase.py:
1367813680
stored_path: false
1367913681
filename: false
1368013682
stored_name: false
13681-
darwin: false
13682-
win32: false
1368313683
def `get_save_filename`:
1368413684
Save File: Shrani datoteko
1368513685
;;: false

0 commit comments

Comments
 (0)