Skip to content

Commit 685c287

Browse files
committed
Enable Save action only if notebook is dirty
Enable the File > Save menu item and the Save button in the Spyder toolbar only if there the current notebook is dirty (i.e., if there is something to save). Enable the Save All action only if any notebook is dirty. If the user triggers this action, then only save notebooks that are dirty.
1 parent 472c567 commit 685c287

File tree

3 files changed

+90
-3
lines changed

3 files changed

+90
-3
lines changed

spyder_notebook/notebookplugin.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ def on_application_available(self) -> None:
8484
)
8585
widget = self.get_widget()
8686
widget.sig_new_recent_file.connect(application.add_recent_file)
87+
widget.sig_enable_save_requested.connect(self._enable_save_actions)
8788

8889
@on_plugin_available(plugin=Plugins.Preferences)
8990
def on_preferences_available(self):
@@ -106,6 +107,7 @@ def on_application_teardown(self) -> None:
106107
application = self.get_plugin(Plugins.Application)
107108
widget = self.get_widget()
108109
widget.sig_new_recent_file.disconnect(application.add_recent_file)
110+
widget.sig_file_action_enabled.connect(self._enable_file_action)
109111

110112
@on_plugin_teardown(plugin=Plugins.Preferences)
111113
def on_preferences_teardown(self):
@@ -261,3 +263,28 @@ def _handle_switcher_selection(self, item, mode, search_text):
261263
self.switch_to_plugin()
262264
switcher = self.get_plugin(Plugins.Switcher)
263265
switcher.hide()
266+
267+
def _enable_save_actions(
268+
self,
269+
save_enabled: bool,
270+
save_all_enabled: bool
271+
) -> None:
272+
"""
273+
Enable or disable file action for this plugin.
274+
"""
275+
# Moving this import to the top of the file interferes with the
276+
# async loop in Jupyter
277+
from spyder.plugins.application.api import ApplicationActions
278+
279+
application = self.get_plugin(Plugins.Application, error=False)
280+
if application:
281+
application.enable_file_action(
282+
ApplicationActions.SaveFile,
283+
save_enabled,
284+
self.NAME
285+
)
286+
application.enable_file_action(
287+
ApplicationActions.SaveAll,
288+
save_all_enabled,
289+
self.NAME
290+
)

spyder_notebook/widgets/main_widget.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@ class NotebookMainWidgetRecentNotebooksMenuSections:
5454

5555
class NotebookMainWidget(PluginMainWidget):
5656

57+
sig_enable_save_requested = Signal(bool, bool)
58+
"""
59+
Request to enable or disable the Save and Save All actions.
60+
61+
Parameters
62+
----------
63+
save_enabled: bool
64+
True if the Save action should be enabled, False if it should disabled.
65+
save_all_enabled: bool
66+
True if the Save All action should be enabled, False if it should
67+
disabled.
68+
"""
69+
5770
sig_new_recent_file = Signal(str)
5871
"""
5972
This signal is emitted when a file is opened or got a new name.
@@ -90,6 +103,9 @@ def __init__(self, name, plugin, parent):
90103
dark_theme=self.dark_theme
91104
)
92105
self.tabwidget.currentChanged.connect(self.refresh_plugin)
106+
self.tabwidget.sig_refresh_save_actions_requested.connect(
107+
self.refresh_save_actions
108+
)
93109

94110
# Widget layout
95111
layout = QVBoxLayout()
@@ -223,6 +239,7 @@ def open_previous_session(self):
223239
self.tabwidget.maybe_create_welcome_client()
224240
self.create_new_client()
225241
self.tabwidget.setCurrentIndex(0) # bring welcome tab to top
242+
self.refresh_save_actions()
226243

227244
def open_notebook(self, filenames=None):
228245
"""Open a notebook from file."""
@@ -255,7 +272,8 @@ def save_all(self) -> None:
255272
"""
256273
for client_index in range(self.tabwidget.count()):
257274
client = self.tabwidget.widget(client_index)
258-
self.tabwidget.save_notebook(client)
275+
if self.tabwidget.can_save_client(client):
276+
self.tabwidget.save_notebook(client)
259277

260278
def save_as(self, close_after_save=True):
261279
"""
@@ -272,6 +290,22 @@ def save_as(self, close_after_save=True):
272290
if old_filename != new_filename:
273291
self.sig_new_recent_file.emit(new_filename)
274292

293+
def refresh_save_actions(self):
294+
"""
295+
Enable or disable 'Save' and 'Save All' actions.
296+
297+
The 'Save' action is enabled if the current notebook can be saved.
298+
The 'Save all' action is enabled if any notebook can be saved.
299+
"""
300+
current_index = self.tabwidget.currentIndex()
301+
current_client = self.tabwidget.widget(current_index)
302+
save_enabled = self.tabwidget.can_save_client(current_client)
303+
save_all_enabled = any(
304+
self.tabwidget.can_save_client(self.tabwidget.widget(index))
305+
for index in range(self.tabwidget.count())
306+
)
307+
self.sig_enable_save_requested.emit(save_enabled, save_all_enabled)
308+
275309
def close_notebook(self) -> None:
276310
"""
277311
Close current notebook.

spyder_notebook/widgets/notebooktabwidget.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
# Qt imports
1616
from qtpy.compat import getopenfilenames, getsavefilename
17-
from qtpy.QtCore import QEventLoop, QTimer
17+
from qtpy.QtCore import QEventLoop, QTimer, Signal
1818
from qtpy.QtWidgets import QMessageBox
1919

2020
# Third-party imports
@@ -103,6 +103,11 @@ class NotebookTabWidget(Tabs, SpyderConfigurationAccessor):
103103
most recently closed one listed last.
104104
"""
105105

106+
sig_refresh_save_actions_requested = Signal()
107+
"""
108+
This signal is emitted when the save actions should be refreshed.
109+
"""
110+
106111
def __init__(self, parent, server_manager, actions=None, menu=None,
107112
corner_widgets=None, dark_theme=False):
108113
"""
@@ -478,6 +483,25 @@ def is_welcome_client(client):
478483
"""
479484
return client.get_filename() in [WELCOME, WELCOME_DARK]
480485

486+
@classmethod
487+
def can_save_client(cls, client: NotebookClient) -> bool:
488+
"""
489+
Return whether some client can be saved.
490+
491+
A client can be saved if it contains a notebook (i.e., it is not a
492+
welcome client) and it is dirty.
493+
494+
Parameters
495+
----------
496+
client : NotebookClient
497+
Client under consideration.
498+
499+
Returns
500+
-------
501+
True if `client` can be saved, False otherwise.
502+
"""
503+
return not cls.is_welcome_client(client) and client.dirty
504+
481505
def add_tab(self, widget):
482506
"""
483507
Add tab containing some notebook widget to the tabbed widget.
@@ -496,7 +520,8 @@ def handle_dirty_changed(self, new_value: bool) -> None:
496520
Handle signal that a notebook became dirty or not.
497521
498522
Append a `*` to the filename of the notebook in the tab title if the
499-
notebook is dirty.
523+
notebook is dirty. Then signal that the save actions should be
524+
refreshed.
500525
501526
Parameters
502527
----------
@@ -511,6 +536,7 @@ def handle_dirty_changed(self, new_value: bool) -> None:
511536
suffix = '*' if new_value else ''
512537
self.setTabText(index, notebook_client.get_short_name() + suffix)
513538
self.setTabToolTip(index, notebook_client.get_filename() + suffix)
539+
self.sig_refresh_save_actions_requested.emit()
514540

515541
def handle_server_started(self, process):
516542
"""

0 commit comments

Comments
 (0)