Repository: spyder-ide/spyder

Issue:
When closing a modified file, the text on the popup's buttons could be clarified
<!--- **PLEASE READ:** When submitting here, please ensure you've completed the following checklist and checked the boxes to confirm. Issue reports without it may be closed. Thanks! --->

### Issue Report Checklist

* [x] Searched the [issues page](https://github.com/spyder-ide/spyder/issues?q=is%3Aissue) for similar reports
* [x] Read the relevant sections of the [Spyder Troubleshooting Guide](https://github.com/spyder-ide/spyder/wiki/Troubleshooting-Guide-and-FAQ) and followed its advice
* [x] Reproduced the issue after updating with ``conda update spyder`` (or ``pip``, if not using Anaconda)
* [x] Could not reproduce inside ``jupyter qtconsole`` (if console-related)
* [x] Tried basic troubleshooting (if a bug/error)
    * [x] Restarted Spyder
    * [x] Reset preferences with ``spyder --reset``
    * [x] Reinstalled the latest version of [Anaconda](https://www.anaconda.com/download/)
    * [x] Tried the other applicable steps from the Troubleshooting Guide
* [x] Completed the **Problem Description**, **Steps to Reproduce** and **Version** sections below


## Problem Description
When closing a file that has been modified, Spyder opens a popup with this text:
"**filename** has been modified. Do you want to save changes?"
Buttons: [Cance] [No] [Yes]
![image](https://github.com/spyder-ide/spyder/assets/3538115/6f98e12a-224a-4f27-aab4-b883af53221c)


### What steps reproduce the problem?

1. Edit a file
2. Close the file without saving first

### What is the expected output? What do you see instead?
The wording of the popup could be improved as such:
Button "Cance" ➡️ "Cancel"
Button "No" ➡️ "Discard changes"
Button "Yes" ➡️ "Save changes"


## Versions
<!--- You can get this information from Help > About Spyder...
or (if Spyder won't launch) the "conda list" command
from the Anaconda Prompt/Terminal/command line. --->

* Spyder version: 5.4.4
* Python version: 3.11
* Qt version: 5.15.2
* PyQt version: 5.15.7
* Operating System name/version: Linux 5.15.0-88-generic (x86_64) 

### Dependencies
<!--- Please go to the menu entry Help > Dependencies,
press the Copy to clipboard button and paste below --->

```
# Mandatory:
atomicwrites >=1.2.0                                                        :  1.4.1 (OK)
chardet >=2.0.0                                                             :  5.2.0 (OK)
cloudpickle >=0.5.0                                                         :  2.2.1 (OK)
cookiecutter >=1.6.0                                                        :  2.3.0 (OK)
diff_match_patch >=20181111                                                 :  20230430 (OK)
intervaltree >=3.0.2                                                        :  3.1.0 (OK)
IPython >=7.31.1,<9.0.0,!=8.8.0,!=8.9.0,!=8.10.0,!=8.11.0,!=8.12.0,!=8.12.1 :  8.14.0 (OK)
jedi >=0.17.2,<0.19.0                                                       :  0.18.2 (OK)
jellyfish >=0.7                                                             :  1.0.0 (OK)
jsonschema >=3.2.0                                                          :  4.19.0 (OK)
keyring >=17.0.0                                                            :  24.2.0 (OK)
nbconvert >=4.0                                                             :  7.8.0 (OK)
numpydoc >=0.6.0                                                            :  1.5.0 (OK)
parso >=0.7.0,<0.9.0                                                        :  0.8.3 (OK)
pexpect >=4.4.0                                                             :  4.8.0 (OK)
pickleshare >=0.4                                                           :  0.7.5 (OK)
psutil >=5.3                                                                :  5.9.5 (OK)
pygments >=2.0                                                              :  2.16.1 (OK)
pylint >=2.5.0,<3.0                                                         :  2.17.5 (OK)
pylint_venv >=3.0.2                                                         :  3.0.2 (OK)
pyls_spyder >=0.4.0                                                         :  0.4.0 (OK)
pylsp >=1.7.4,<1.8.0                                                        :  1.7.4 (OK)
pylsp_black >=1.2.0,<3.0.0                                                  :  1.3.0 (OK)
qdarkstyle >=3.0.2,<3.2.0                                                   :  3.1 (OK)
qstylizer >=0.2.2                                                           :  0.2.2 (OK)
qtawesome >=1.2.1                                                           :  1.2.3 (OK)
qtconsole >=5.4.2,<5.5.0                                                    :  5.4.3 (OK)
qtpy >=2.1.0                                                                :  2.3.1 (OK)
rtree >=0.9.7                                                               :  1.0.1 (OK)
setuptools >=49.6.0                                                         :  68.1.2 (OK)
sphinx >=0.6.6                                                              :  7.2.4 (OK)
spyder_kernels >=2.4.4,<2.5.0                                               :  2.4.4 (OK)
textdistance >=4.2.0                                                        :  4.5.0 (OK)
three_merge >=0.1.1                                                         :  0.1.1 (OK)
watchdog >=0.10.3                                                           :  3.0.0 (OK)
xdg >=0.26                                                                  :  0.28 (OK)
zmq >=22.1.0                                                                :  25.1.1 (OK)

# Optional:
cython >=0.21                                                               :  None (NOK)
matplotlib >=3.0.0                                                          :  None (NOK)
numpy >=1.7                                                                 :  None (NOK)
pandas >=1.1.1                                                              :  None (NOK)
scipy >=0.17.0                                                              :  None (NOK)
sympy >=0.7.3                                                               :  None (NOK)
```



Discussion:
If someone could point me to where those strings are located, I might be able to fix them myself.
Hi @narFnarF thank you for the feedback and willingness to implement your suggestion! I think we are using there the standard Qt buttons and probably the `Cancel` button text is being cut due to the dialog size :thinking: Anyhow, regarding your text suggestions, what do you think @spyder-ide/core-developers ?

> If someone could point me to where those strings are located, I might be able to fix them myself.

I think that dialog is located at https://github.com/spyder-ide/spyder/blob/ec13a2547de6d158f4d366a693e190447cae2769/spyder/plugins/editor/widgets/editorstack/editorstack.py#L1668-L1676

To change the buttons text most probably you will need to manually add the button by calling [`QMessageBox.addButton`](https://doc.qt.io/qt-5/qmessagebox.html#addButton-1) from the message box widget that is being created instead of passing the buttons and accommodate the validations that check for what button was selected

I think the dialog is clear enough if you read the text on it. But I agree that by doing the changes suggested by @narFnarF, you would have a better idea of what kind of action Spyder is requiring from you.

@CAM-Gerlach, what do you think?
One issue I have with making custom button text is that the button text becomes cramped and difficult to read, or the buttons have to be made larger and become non-uniform in size in the message box. This is aesthetically distracting and does not look professional.

I would recommend keeping the standard buttons, but perhaps add vertical space above "Do you want to save the changes?", which might help give it more attention.

If we decide to change the button text, then I would recommend keeping the words short, e.g. "Cancel", "Discard", "Save", in order to maintain button size uniformity without crowding the text in the button.
I would be ok with the shorter text proposed by @mrclary 
@mrclary, could you take care of this one? Thanks!
To be clear, we have agreed to modify the button text to "Cancel", "Discard", and "Save", correct?
Yep, that's correct.
Overall, this would make the actions a lot clearer and avoid confusion, cognitive load and possible (desctructive) mistakes parsing it incorrectly, and would also follow the lead of what I'm seen from other applications. I do agree with the short forms, as otherwise you're likely to just have the text cut off as it is for the Cancel button in the OP's screenshot, due to their system's rather unusual and wonky theming I presume.

If possible, I'd suggest changing the order as well. Given the question is "Do you want to save changes?", the conventional and natural order to me in English would be "Save" "Discard" "Cancel" / "Yes" "No" "Cancel", which is also what I see testing a couple other editors.
> If possible, I'd suggest changing the order as well. Given the question is "Do you want to save changes?", the conventional and natural order to me in English would be "Save" "Discard" "Cancel" / "Yes" "No" "Cancel", which is also what I see testing a couple other editors.

I believe this is actually platform dependent and should be respected as such.
On macOS:
<img width="401" alt="Screenshot 2023-12-18 at 1 40 22 PM" src="https://github.com/spyder-ide/spyder/assets/9618975/7984aefb-0e38-47a8-9e09-c632d1d09bcd">
On Windows:
<img width="279" alt="Screenshot 2023-12-18 at 1 45 03 PM" src="https://github.com/spyder-ide/spyder/assets/9618975/2b728f0e-6af2-4c6c-8b50-65c210db4140">
On Ubuntu:
<img width="297" alt="Screenshot 2023-12-18 at 2 00 10 PM" src="https://github.com/spyder-ide/spyder/assets/9618975/3df00cee-feab-4a14-adb6-15d0c942bec5">


Resolving patch:
diff --git a/spyder/plugins/editor/widgets/editorstack/editorstack.py b/spyder/plugins/editor/widgets/editorstack/editorstack.py
--- a/spyder/plugins/editor/widgets/editorstack/editorstack.py
+++ b/spyder/plugins/editor/widgets/editorstack/editorstack.py
@@ -57,7 +57,6 @@
 from spyder.utils.stylesheet import PANES_TABBAR_STYLESHEET
 from spyder.widgets.tabs import BaseTabs
 
-
 logger = logging.getLogger(__name__)
 
 
@@ -1643,8 +1642,6 @@ def save_if_changed(self, cancelable=False, index=None):
         if not unsaved_nb:
             # No file to save
             return True
-        if unsaved_nb > 1:
-            buttons |= int(QMessageBox.YesToAll | QMessageBox.NoToAll)
         yes_all = no_all = False
         for index in indexes:
             self.set_stack_index(index)
@@ -1662,19 +1659,33 @@ def save_if_changed(self, cancelable=False, index=None):
                     return False
             elif no_all:
                 self.autosave.remove_autosave_file(finfo)
-            elif (finfo.editor.document().isModified() and
-                  self.save_dialog_on_tests):
+            elif (
+                finfo.editor.document().isModified()
+                and self.save_dialog_on_tests
+            ):
+                if unsaved_nb > 1:
+                    buttons |= QMessageBox.YesToAll | QMessageBox.NoToAll
 
                 self.msgbox = QMessageBox(
                     QMessageBox.Question,
                     self.title,
                     _("<b>%s</b> has been modified."
-                      "<br>Do you want to save changes?"
+                      "<br><br>Do you want to save changes?"
                       ) % osp.basename(finfo.filename),
                     buttons,
                     parent=self
                 )
 
+                self.msgbox.button(QMessageBox.Yes).setText(_("Save"))
+                self.msgbox.button(QMessageBox.No).setText(_("Discard"))
+                yta = self.msgbox.button(QMessageBox.YesToAll)
+                nta = self.msgbox.button(QMessageBox.NoToAll)
+
+                if yta:
+                    yta.setText(_("Save all"))
+                if nta:
+                    nta.setText(_("Discard all"))
+
                 answer = self.msgbox.exec_()
                 if answer == QMessageBox.Yes:
                     if not self.save(index):


Test patch: