Repository: spyder-ide/spyder

Issue:
"Create new project" dialog box button focus is "Cancel" not "Create" 
When creating a new project in Spyder the default button highlighted is "Cancel", not "Create".

So the user (me) enters the new project information, hits the "Enter" key on the keyboard, and the dialog box disappears without creating the project, because the "Cancel" button is highlighted by default.  So I have to re-enter the information.  

I have trained myself to not use the mouse, and rely on well-designed GUIs to have the proper defaults keyboard shortcuts for the operation at hand, in this case "Create" project.  

Please cleanup this minor usability issue in the next release.  

Thank you. 

Rich
<!--- **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

* [ ] Searched the [issues page](https://github.com/spyder-ide/spyder/issues?q=is%3Aissue) for similar reports
* [ ] Read the relevant sections of the [Spyder Troubleshooting Guide](https://github.com/spyder-ide/spyder/wiki/Troubleshooting-Guide-and-FAQ) and followed its advice
* [ ] Reproduced the issue after updating with ``conda update spyder`` (or ``pip``, if not using Anaconda)
* [ ] Could not reproduce inside ``jupyter qtconsole`` (if console-related)
* [ ] Tried basic troubleshooting (if a bug/error)
    * [ ] Restarted Spyder
    * [ ] Reset preferences with ``spyder --reset``
    * [ ] Reinstalled the latest version of [Anaconda](https://www.anaconda.com/download/)
    * [ ] Tried the other applicable steps from the Troubleshooting Guide
* [ ] Completed the **Problem Description**, **Steps to Reproduce** and **Version** sections below


## Problem Description



### What steps reproduce the problem?

1. 
2. 
3. 

### What is the expected output? What do you see instead?



### Paste Traceback/Error Below (if applicable)
<!--- Copy from error dialog or View > Panes > Internal Console --->

```python-traceback

PASTE TRACEBACK HERE

```

## 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: 
* Python version: 
* Qt version: 
* PyQt version: 
* Operating System name/version: 

### Dependencies
<!--- Please go to the menu entry Help > Dependencies,
press the Copy to clipboard button and paste below --->

```
PASTE DEPENDENCIES HERE
```



Discussion:

Resolving patch:
diff --git a/spyder/plugins/projects/api.py b/spyder/plugins/projects/api.py
--- a/spyder/plugins/projects/api.py
+++ b/spyder/plugins/projects/api.py
@@ -9,21 +9,17 @@
 """
 
 # Standard library imports
-import os
 import os.path as osp
 from collections import OrderedDict
 
 # Local imports
-from spyder.api.exceptions import SpyderAPIError
 from spyder.api.translations import get_translation
 from spyder.config.base import get_project_config_folder
 from spyder.plugins.projects.utils.config import (ProjectMultiConfig,
                                                   PROJECT_NAME_MAP,
                                                   PROJECT_DEFAULTS,
                                                   PROJECT_CONF_VERSION,
-                                                  WORKSPACE, CODESTYLE,
-                                                  ENCODING, VCS)
-
+                                                  WORKSPACE)
 
 # Localization
 _ = get_translation("spyder")
@@ -43,7 +39,7 @@ def __init__(self, root_path, parent_plugin=None):
         self.root_path = root_path
         self.open_project_files = []
         self.open_non_project_files = []
-        path = os.path.join(root_path, get_project_config_folder(), 'config')
+        path = osp.join(root_path, get_project_config_folder(), 'config')
         self.config = ProjectMultiConfig(
             PROJECT_NAME_MAP,
             path=path,
@@ -72,9 +68,9 @@ def set_recent_files(self, recent_files):
         """Set a list of files opened by the project."""
         processed_recent_files = []
         for recent_file in recent_files:
-            if os.path.isfile(recent_file):
+            if osp.isfile(recent_file):
                 try:
-                    relative_recent_file = os.path.relpath(
+                    relative_recent_file = osp.relpath(
                         recent_file, self.root_path)
                     processed_recent_files.append(relative_recent_file)
                 except ValueError:
@@ -95,11 +91,11 @@ def get_recent_files(self):
         else:
             recent_files = self.get_option("recent_files", default=[])
 
-        recent_files = [recent_file if os.path.isabs(recent_file)
-                        else os.path.join(self.root_path, recent_file)
+        recent_files = [recent_file if osp.isabs(recent_file)
+                        else osp.join(self.root_path, recent_file)
                         for recent_file in recent_files]
         for recent_file in recent_files[:]:
-            if not os.path.isfile(recent_file):
+            if not osp.isfile(recent_file):
                 recent_files.remove(recent_file)
 
         return list(OrderedDict.fromkeys(recent_files))
diff --git a/spyder/plugins/projects/widgets/projectdialog.py b/spyder/plugins/projects/widgets/projectdialog.py
--- a/spyder/plugins/projects/widgets/projectdialog.py
+++ b/spyder/plugins/projects/widgets/projectdialog.py
@@ -7,11 +7,8 @@
 # -----------------------------------------------------------------------------
 """Project creation dialog."""
 
-from __future__ import print_function
-
 # Standard library imports
 import errno
-import os
 import os.path as osp
 import sys
 import tempfile
@@ -27,7 +24,6 @@
 from spyder.config.base import _, get_home_dir
 from spyder.utils.icon_manager import ima
 from spyder.utils.qthelpers import create_toolbutton
-from spyder.py3compat import to_text_string
 
 
 def is_writable(path):
@@ -69,19 +65,19 @@ def __init__(self, parent, project_types):
         self.setWindowFlags(
             self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
 
-        # Variables
-        current_python_version = '.'.join(
-            [to_text_string(sys.version_info[0]),
-             to_text_string(sys.version_info[1])])
-        python_versions = ['2.7', '3.4', '3.5']
-        if current_python_version not in python_versions:
-            python_versions.append(current_python_version)
-            python_versions = sorted(python_versions)
-
         self.project_name = None
         self.location = get_home_dir()
 
         # Widgets
+        projects_url = "http://docs.spyder-ide.org/current/panes/projects.html"
+        self.description_label = QLabel(
+            _(f"Select a new or existing directory to create a new Spyder "
+              f"project in it. To learn more about projects, take a look at "
+              f"our <a href=\"{projects_url}\">documentation</a>.")
+        )
+        self.description_label.setOpenExternalLinks(True)
+        self.description_label.setWordWrap(True)
+
         self.groupbox = QGroupBox()
         self.radio_new_dir = QRadioButton(_("New directory"))
         self.radio_from_dir = QRadioButton(_("Existing directory"))
@@ -89,20 +85,20 @@ def __init__(self, parent, project_types):
         self.label_project_name = QLabel(_('Project name'))
         self.label_location = QLabel(_('Location'))
         self.label_project_type = QLabel(_('Project type'))
-        self.label_python_version = QLabel(_('Python version'))
 
         self.text_project_name = QLineEdit()
         self.text_location = QLineEdit(get_home_dir())
         self.combo_project_type = QComboBox()
-        self.combo_python_version = QComboBox()
 
         self.label_information = QLabel("")
+        self.label_information.hide()
 
         self.button_select_location = create_toolbutton(
             self,
             triggered=self.select_location,
             icon=ima.icon('DirOpenIcon'),
-            tip=_("Select directory"))
+            tip=_("Select directory")
+        )
         self.button_cancel = QPushButton(_('Cancel'))
         self.button_create = QPushButton(_('Create'))
 
@@ -111,7 +107,6 @@ def __init__(self, parent, project_types):
         self.bbox.addButton(self.button_create, QDialogButtonBox.ActionRole)
 
         # Widget setup
-        self.combo_python_version.addItems(python_versions)
         self.radio_new_dir.setChecked(True)
         self.text_location.setEnabled(True)
         self.text_location.setReadOnly(True)
@@ -122,18 +117,14 @@ def __init__(self, parent, project_types):
                             in project_types.items()]:
             self.combo_project_type.addItem(name, id_)
 
-        self.combo_python_version.setCurrentIndex(
-            python_versions.index(current_python_version))
         self.setWindowTitle(_('Create new project'))
-        self.setFixedWidth(500)
-        self.label_python_version.setVisible(False)
-        self.combo_python_version.setVisible(False)
 
         # Layouts
         layout_top = QHBoxLayout()
         layout_top.addWidget(self.radio_new_dir)
+        layout_top.addSpacing(15)
         layout_top.addWidget(self.radio_from_dir)
-        layout_top.addStretch(1)
+        layout_top.addSpacing(200)
         self.groupbox.setLayout(layout_top)
 
         layout_grid = QGridLayout()
@@ -144,17 +135,17 @@ def __init__(self, parent, project_types):
         layout_grid.addWidget(self.button_select_location, 1, 2)
         layout_grid.addWidget(self.label_project_type, 2, 0)
         layout_grid.addWidget(self.combo_project_type, 2, 1, 1, 2)
-        layout_grid.addWidget(self.label_python_version, 3, 0)
-        layout_grid.addWidget(self.combo_python_version, 3, 1, 1, 2)
-        layout_grid.addWidget(self.label_information, 4, 0, 1, 3)
+        layout_grid.addWidget(self.label_information, 3, 0, 1, 3)
 
         layout = QVBoxLayout()
+        layout.addWidget(self.description_label)
+        layout.addSpacing(3)
         layout.addWidget(self.groupbox)
-        layout.addSpacing(10)
+        layout.addSpacing(8)
         layout.addLayout(layout_grid)
-        layout.addStretch()
-        layout.addSpacing(20)
+        layout.addSpacing(8)
         layout.addWidget(self.bbox)
+        layout.setSizeConstraint(layout.SetFixedSize)
 
         self.setLayout(layout)
 
@@ -175,36 +166,70 @@ def select_location(self):
             )
         )
 
-        if location:
+        if location and location != '.':
             if is_writable(location):
                 self.location = location
+                self.text_project_name.setText(osp.basename(location))
                 self.update_location()
 
     def update_location(self, text=''):
-        """Update text of location."""
-        self.text_project_name.setEnabled(self.radio_new_dir.isChecked())
+        """Update text of location and validate it."""
+        msg = ''
+        path_validation = False
+        path = self.location
         name = self.text_project_name.text().strip()
 
+        # Setup
+        self.text_project_name.setEnabled(self.radio_new_dir.isChecked())
+        self.label_information.setText('')
+        self.label_information.hide()
+
         if name and self.radio_new_dir.isChecked():
+            # Allow to create projects only on new directories.
             path = osp.join(self.location, name)
-            self.button_create.setDisabled(os.path.isdir(path))
+            path_validation = not osp.isdir(path)
+            if not path_validation:
+                msg = _("This directory already exists!")
         elif self.radio_from_dir.isChecked():
-            self.button_create.setEnabled(True)
-            path = self.location
-        else:
-            self.button_create.setEnabled(False)
+            # Allow to create projects in current directories that are not
+            # Spyder projects.
             path = self.location
+            path_validation = not osp.isdir(osp.join(path, '.spyproject'))
+            if not path_validation:
+                msg = _("This directory is already a Spyder project!")
 
+        # Set path in text_location
         self.text_location.setText(path)
 
-        # Validate name with the method from the currently selected project
+        # Validate project name with the method from the currently selected
+        # project.
         project_type_id = self.combo_project_type.currentData()
         validate_func = self._project_types[project_type_id].validate_name
-        validated, msg = validate_func(path, name)
-        msg = "" if validated else msg
-        self.label_information.setText(msg)
+        project_name_validation, project_msg = validate_func(path, name)
+        if not project_name_validation:
+            if msg:
+                msg = msg + '\n\n' + project_msg
+            else:
+                msg = project_msg
+
+        # Set message
+        if msg:
+            self.label_information.show()
+            self.label_information.setText('\n' + msg)
+
+        # Allow to create project if validation was successful
+        validated = path_validation and project_name_validation
         self.button_create.setEnabled(validated)
 
+        # Set default state of buttons according to validation
+        # Fixes spyder-ide/spyder#16745
+        if validated:
+            self.button_create.setDefault(True)
+            self.button_create.setAutoDefault(True)
+        else:
+            self.button_cancel.setDefault(True)
+            self.button_cancel.setAutoDefault(True)
+
     def create_project(self):
         """Create project."""
         self.project_data = {
@@ -234,7 +259,6 @@ def get_name():
         def validate_name(path, name):
             return False, "BOOM!"
 
-
     app = qapplication()
     dlg = ProjectDialog(None, {"empty": MockProjectType})
     dlg.show()


Test patch:
diff --git a/spyder/plugins/projects/widgets/tests/test_projectdialog.py b/spyder/plugins/projects/widgets/tests/test_projectdialog.py
--- a/spyder/plugins/projects/widgets/tests/test_projectdialog.py
+++ b/spyder/plugins/projects/widgets/tests/test_projectdialog.py
@@ -25,15 +25,10 @@ def projects_dialog(qtbot):
     """Set up ProjectDialog."""
     dlg = ProjectDialog(None, {'Empty project': EmptyProject})
     qtbot.addWidget(dlg)
+    dlg.show()
     return dlg
 
 
-def test_project_dialog(projects_dialog):
-    """Run project dialog."""
-    projects_dialog.show()
-    assert projects_dialog
-
-
 @pytest.mark.skipif(os.name != 'nt', reason="Specific to Windows platform")
 def test_projectdialog_location(monkeypatch):
     """Test that select_location normalizes delimiters and updates the path."""
@@ -71,5 +66,55 @@ def test_projectdialog_location(monkeypatch):
     assert dlg.location == r"c:\a_a_1\Bbbb\2345\d-6D"
 
 
+def test_directory_validations(projects_dialog, monkeypatch, tmpdir):
+    """
+    Test that we perform appropiate validations before allowing users to
+    create a project in a directory.
+    """
+    dlg = projects_dialog
+
+    # Assert button_create is disabled by default
+    assert not dlg.button_create.isEnabled()
+    assert not dlg.button_create.isDefault()
+
+    # Set location to tmpdir root
+    dlg.location = str(tmpdir)
+    dlg.text_location.setText(str(tmpdir))
+
+    # Check that we don't allow to create projects in existing directories when
+    # 'New directory' is selected.
+    dlg.radio_new_dir.click()
+    tmpdir.mkdir('foo')
+    dlg.text_project_name.setText('foo')
+    assert not dlg.button_create.isEnabled()
+    assert not dlg.button_create.isDefault()
+    assert dlg.label_information.text() == '\nThis directory already exists!'
+
+    # Selecting 'Existing directory' should allow to create a project there
+    dlg.radio_from_dir.click()
+    assert dlg.button_create.isEnabled()
+    assert dlg.button_create.isDefault()
+    assert dlg.label_information.text() == ''
+
+    # Create a Spyder project
+    folder = tmpdir.mkdir('bar')
+    folder.mkdir('.spyproject')
+
+    # Mock selecting a directory
+    mock_getexistingdirectory = Mock()
+    monkeypatch.setattr('spyder.plugins.projects.widgets.projectdialog' +
+                        '.getexistingdirectory', mock_getexistingdirectory)
+    mock_getexistingdirectory.return_value = str(folder)
+
+    # Check that we don't allow to create projects in existing directories when
+    # 'Existing directory' is selected and there's already a Spyder project
+    # there.
+    dlg.select_location()
+    assert not dlg.button_create.isEnabled()
+    assert not dlg.button_create.isDefault()
+    msg = '\nThis directory is already a Spyder project!'
+    assert dlg.label_information.text() == msg
+
+
 if __name__ == "__main__":
     pytest.main()