Repository: spyder-ide/spyder

Issue:
Unable to reset settings from the Main Window
Hi,

I have tried reseting the User interface i.e. windows layout from view but I cannot get back to original. 
I have removed the Spyder-Py2.app and reinstalled it but it doesn't restore to default. Also, I have tried using Anaconda but still the same problem. 
![screen shot 2015-05-04 at 6 39 11 pm](https://cloud.githubusercontent.com/assets/10142508/7457520/59bcde9a-f28d-11e4-8195-37f2049c153e.png)

Any suggestion on proper removal of Spyder or setting it to default. Thanks.



Discussion:
Have you tried running from a console

`spyder --reset` 

?

Yes, I did give a try from iPython console and python console and termina,. 

It states, 'name spyder not defined.'

It should be run from a shell, not from python. Alternatively you can delete manually `~/.spyder`, this is exactly what the command above does.

Thanks @Nodd . 

rm -f ~/.spyder worked perfectly. 

This is a complaint I've heard several times lately, so I think it's something we should solve for 3.0.

The idea is to provide a menu entry (under `Tools`?) to let users easily come back to factory settings after a restart.

@goanpeca, would you mind to take a look at it?

Sure :+1:

So... the plan would be.

The restart method in `spyder.py` should have a named parameter by default `reset=False`, so that it can pass an extra argument to `restart_app.py` where we can first reset and then restart spyder.

Also I think we could have a soft reset and a hard reset, because some people cannot get spyder fixed when running reset, sometimes they need to fully erase `.spyder`

> The restart method in spyder.py should have a named parameter by default reset=False, so that it can pass an extra argument to restart_app.py where we can first reset and then restart spyder.

+1

> Also I think we could have a soft reset and a hard reset

Nop, the problem @dezember was having is that he couldn't call `spyder --reset` because our Mac app can't be called from the console like that. So he needed to remove `~/.spyder2`by hand.

ok

I would really like to start working on some PRs, but I really would like to get the `.gitattributes` issue solved soon....

We're basically waiting for @SylvainCorlay, who asked not to do it before we merge the FontAwesome PR :-)


Resolving patch:
diff --git a/spyderlib/plugins/configdialog.py b/spyderlib/plugins/configdialog.py
--- a/spyderlib/plugins/configdialog.py
+++ b/spyderlib/plugins/configdialog.py
@@ -124,36 +124,37 @@ class ConfigDialog(QDialog):
     
     def __init__(self, parent=None):
         QDialog.__init__(self, parent)
-        
+
+        self.main = parent
+
+        # Widgets
+        self.pages_widget = QStackedWidget()
+        self.contents_widget = QListWidget()
+        self.button_reset = QPushButton(_('Reset to defaults'))
+
+        bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Apply |
+                                QDialogButtonBox.Cancel)
+        self.apply_btn = bbox.button(QDialogButtonBox.Apply)
+
+        # Widgets setup
         # Destroying the C++ object right after closing the dialog box,
         # otherwise it may be garbage-collected in another QThread
         # (e.g. the editor's analysis thread in Spyder), thus leading to
         # a segmentation fault on UNIX or an application crash on Windows
         self.setAttribute(Qt.WA_DeleteOnClose)
-
-        self.contents_widget = QListWidget()
+        self.setWindowTitle(_('Preferences'))
+        self.setWindowIcon(ima.icon('configure'))
         self.contents_widget.setMovement(QListView.Static)
         self.contents_widget.setSpacing(1)
-
-        bbox = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Apply
-                                |QDialogButtonBox.Cancel)
-        self.apply_btn = bbox.button(QDialogButtonBox.Apply)
-        bbox.accepted.connect(self.accept)
-        bbox.rejected.connect(self.reject)
-        bbox.clicked.connect(self.button_clicked)
-        
-        self.pages_widget = QStackedWidget()
-        self.pages_widget.currentChanged.connect(self.current_page_changed)
-
-        self.contents_widget.currentRowChanged.connect(
-                                             self.pages_widget.setCurrentIndex)
         self.contents_widget.setCurrentRow(0)
 
+        # Layout
         hsplitter = QSplitter()
         hsplitter.addWidget(self.contents_widget)
         hsplitter.addWidget(self.pages_widget)
 
         btnlayout = QHBoxLayout()
+        btnlayout.addWidget(self.button_reset)
         btnlayout.addStretch(1)
         btnlayout.addWidget(bbox)
 
@@ -163,12 +164,18 @@ def __init__(self, parent=None):
 
         self.setLayout(vlayout)
 
-        self.setWindowTitle(_('Preferences'))
-        self.setWindowIcon(ima.icon('configure'))
+        # Signals and slots
+        self.button_reset.clicked.connect(self.main.reset_spyder)
+        self.pages_widget.currentChanged.connect(self.current_page_changed)
+        self.contents_widget.currentRowChanged.connect(
+                                             self.pages_widget.setCurrentIndex)
+        bbox.accepted.connect(self.accept)
+        bbox.rejected.connect(self.reject)
+        bbox.clicked.connect(self.button_clicked)
 
         # Ensures that the config is present on spyder first run
         CONF.set('main', 'interface_language', load_lang_conf())
-        
+
     def get_current_index(self):
         """Return current page index"""
         return self.contents_widget.currentRow()
diff --git a/spyderlib/restart_app.py b/spyderlib/restart_app.py
--- a/spyderlib/restart_app.py
+++ b/spyderlib/restart_app.py
@@ -8,7 +8,8 @@
 """
 Restart Spyder
 
-A helper script that allows Spyder to restart from within the application.
+A helper script that allows to restart (and also reset) Spyder from within the
+running application.
 """
 
 import ast
@@ -19,7 +20,18 @@
 import time
 
 
+from spyderlib.baseconfig import _, get_image_path
+from spyderlib.py3compat import to_text_string
+from spyderlib.qt.QtCore import Qt, QTimer
+from spyderlib.qt.QtGui import (QColor, QMessageBox, QPixmap, QSplashScreen,
+                                QWidget, QApplication)
+from spyderlib.utils import icon_manager as ima
+from spyderlib.utils.qthelpers import qapplication
+
 PY2 = sys.version[0] == '2'
+IS_WINDOWS = os.name == 'nt'
+SLEEP_TIME = 0.2  # Seconds for throttling control
+CLOSE_ERROR, RESET_ERROR, RESTART_ERROR = [1, 2, 3]  # Spyder error codes
 
 
 def _is_pid_running_on_windows(pid):
@@ -63,55 +75,128 @@ def is_pid_running(pid):
         return _is_pid_running_on_unix(pid)
 
 
-# Note: Copied from py3compat because we can't rely on Spyder
-# being installed when using bootstrap.py
-def to_text_string(obj, encoding=None):
-    """Convert `obj` to (unicode) text string"""
-    if PY2:
-        # Python 2
-        if encoding is None:
-            return unicode(obj)
-        else:
-            return unicode(obj, encoding)
-    else:
-        # Python 3
-        if encoding is None:
-            return str(obj)
-        elif isinstance(obj, str):
-            # In case this function is not used properly, this could happen
-            return obj
+class Restarter(QWidget):
+    """Widget in charge of displaying the splash information screen and the
+       error messages.
+    """
+    def __init__(self):
+        super(Restarter, self).__init__()
+        self.ellipsis = ['', '.', '..', '...', '..', '.']
+
+        # Widgets
+        self.timer_ellipsis = QTimer(self)
+        self.splash = QSplashScreen(QPixmap(get_image_path('splash.png'),
+                                    'png'))
+
+        # Widget setup
+        self.setVisible(False)
+
+        font = self.splash.font()
+        font.setPixelSize(10)
+        self.splash.setFont(font)
+        self.splash.show()
+
+        self.timer_ellipsis.timeout.connect(self.animate_ellipsis)
+
+    def _show_message(self, text):
+        """Show message on splash screen."""
+        self.splash.showMessage(text, Qt.AlignBottom | Qt.AlignCenter |
+                                Qt.AlignAbsolute, QColor(Qt.white))
+
+    def animate_ellipsis(self):
+        """Animate dots at the end of the splash screen message."""
+        ellipsis = self.ellipsis.pop(0)
+        text = ' '*len(ellipsis) + self.splash_text + ellipsis
+        self.ellipsis.append(ellipsis)
+        self._show_message(text)
+
+    def set_splash_message(self, text):
+        """Sets the text in the bottom of the Splash screen."""
+        self.splash_text = text
+        self._show_message(text)
+        self.timer_ellipsis.start(500)
+
+    def launch_error_message(self, error_type, error=None):
+        """Launch a message box with a predefined error message.
+
+        Parameters
+        ----------
+        error_type : int [CLOSE_ERROR, RESET_ERROR, RESTART_ERROR]
+            Possible error codes when restarting/reseting spyder.
+        error : Exception
+            Actual Python exception error caught.
+        """
+        messages = {CLOSE_ERROR: _("It was not possible to close the previous "
+                                   "Spyder instance.\nRestart aborted."),
+                    RESET_ERROR: _("Spyder could not reset to factory "
+                                   "defaults.\nRestart aborted."),
+                    RESTART_ERROR: _("It was not possible to restart Spyder.\n"
+                                     "Operation aborted.")}
+        titles = {CLOSE_ERROR: _("Spyder exit error"),
+                  RESET_ERROR: _("Spyder reset error"),
+                  RESTART_ERROR: _("Spyder restart error")}
+
+        if error:
+            e = error.__repr__()
+            message = messages[error_type] + _("\n\n{0}".format(e))
         else:
-            return str(obj, encoding)
+            message = messages[error_type]
+
+        title = titles[error_type]
+        self.splash.hide()
+        QMessageBox.warning(self, title, message, QMessageBox.Ok)
+        raise RuntimeError(message)
 
 
 def main():
+    # Splash screen
+    # -------------------------------------------------------------------------
+    # Start Qt Splash to inform the user of the current status
+    app = qapplication()
+    restarter = Restarter()
+    resample = not IS_WINDOWS
+    # Resampling SVG icon only on non-Windows platforms (see Issue 1314):
+    icon = ima.icon('spyder', resample=resample)
+    app.setWindowIcon(icon)
+    restarter.set_splash_message(_('Closing Spyder'))
+
+    # Get variables
     # Note: Variables defined in spyderlib\spyder.py 'restart()' method
     spyder_args = os.environ.pop('SPYDER_ARGS', None)
     pid = os.environ.pop('SPYDER_PID', None)
     is_bootstrap = os.environ.pop('SPYDER_IS_BOOTSTRAP', None)
+    reset = os.environ.pop('SPYDER_RESET', None)
 
     # Get the spyder base folder based on this file
     spyder_folder = osp.split(osp.dirname(osp.abspath(__file__)))[0]
 
-    if any([not spyder_args, not pid, not is_bootstrap]):
+    if not any([spyder_args, pid, is_bootstrap, reset]):
         error = "This script can only be called from within a Spyder instance"
         raise RuntimeError(error)
 
     # Variables were stored as string literals in the environment, so to use
     # them we need to parse them in a safe manner.
     is_bootstrap = ast.literal_eval(is_bootstrap)
-    pid = int(pid)
+    pid = ast.literal_eval(pid)
     args = ast.literal_eval(spyder_args)
+    reset = ast.literal_eval(reset)
 
     # Enforce the --new-instance flag when running spyder
     if '--new-instance' not in args:
-        if is_bootstrap and not '--' in args:
+        if is_bootstrap and '--' not in args:
             args = args + ['--', '--new-instance']
         else:
             args.append('--new-instance')
 
-    # Arrange arguments to be passed to the restarter subprocess
+    # Create the arguments needed for reseting
+    if '--' in args:
+        args_reset = ['--', '--reset']
+    else:
+        args_reset = ['--reset']
+
+    # Arrange arguments to be passed to the restarter and reset subprocess
     args = ' '.join(args)
+    args_reset = ' '.join(args_reset)
 
     # Get python excutable running this script
     python = sys.executable
@@ -126,21 +211,63 @@ def main():
     command = '"{0}" "{1}" {2}'.format(python, spyder, args)
 
     # Adjust the command and/or arguments to subprocess depending on the OS
-    shell = os.name != 'nt'
+    shell = not IS_WINDOWS
 
-    # Wait for original process to end before launching the new instance
-    while True:
+    # Before launching a new Spyder instance we need to make sure that the
+    # previous one has closed. We wait for a fixed and "reasonable" amount of
+    # time and check, otherwise an error is launched
+    wait_time = 90 if IS_WINDOWS else 30  # Seconds
+    for counter in range(int(wait_time/SLEEP_TIME)):
         if not is_pid_running(pid):
             break
-        time.sleep(0.2)  # Throttling control
+        time.sleep(SLEEP_TIME)  # Throttling control
+        QApplication.processEvents()  # Needed to refresh the splash
+    else:
+        # The old spyder instance took too long to close and restart aborts
+        restarter.launch_error_message(error_type=CLOSE_ERROR)
 
     env = os.environ.copy()
+
+    # Reset Spyder (if required)
+    # -------------------------------------------------------------------------
+    if reset:
+        restarter.set_splash_message(_('Resetting Spyder to defaults'))
+        command_reset = '"{0}" "{1}" {2}'.format(python, spyder, args_reset)
+
+        try:
+            p = subprocess.Popen(command_reset, shell=shell, env=env)
+        except Exception as error:
+            restarter.launch_error_message(error_type=RESET_ERROR, error=error)
+        else:
+            p.communicate()
+            pid_reset = p.pid
+
+        # Before launching a new Spyder instance we need to make sure that the
+        # reset subprocess has closed. We wait for a fixed and "reasonable"
+        # amount of time and check, otherwise an error is launched.
+        wait_time = 20  # Seconds
+        for counter in range(int(wait_time/SLEEP_TIME)):
+            if not is_pid_running(pid_reset):
+                break
+            time.sleep(SLEEP_TIME)  # Throttling control
+            QApplication.processEvents()  # Needed to refresh the splash
+        else:
+            # The reset subprocess took too long and it is killed
+            try:
+                p.kill()
+            except OSError as error:
+                restarter.launch_error_message(error_type=RESET_ERROR,
+                                               error=error)
+            else:
+                restarter.launch_error_message(error_type=RESET_ERROR)
+
+    # Restart
+    # -------------------------------------------------------------------------
+    restarter.set_splash_message(_('Restarting'))
     try:
         subprocess.Popen(command, shell=shell, env=env)
     except Exception as error:
-        print(command)
-        print(error)
-        time.sleep(15)
+        restarter.launch_error_message(error_type=RESTART_ERROR, error=error)
 
 
 if __name__ == '__main__':
diff --git a/spyderlib/spyder.py b/spyderlib/spyder.py
--- a/spyderlib/spyder.py
+++ b/spyderlib/spyder.py
@@ -677,6 +677,9 @@ def create_edit_action(text, tr_text, icon):
                                                   module_completion.reset(),
                                         tip=_("Refresh list of module names "
                                               "available in PYTHONPATH"))
+            reset_spyder_action = create_action(
+                self, _("Reset Spyder to factory defaults"),
+                triggered=self.reset_spyder)
             self.tools_menu_actions = [prefs_action, spyder_path_action]
             if WinUserEnvDialog is not None:
                 winenv_action = create_action(self,
@@ -687,7 +690,8 @@ def create_edit_action(text, tr_text, icon):
                               "(i.e. for all sessions)"),
                         triggered=self.win_env)
                 self.tools_menu_actions.append(winenv_action)
-            self.tools_menu_actions += [None, update_modules_action]
+            self.tools_menu_actions += [reset_spyder_action, None,
+                                        update_modules_action]
 
             # External Tools submenu
             self.external_tools_menu = QMenu(_("External Tools"))
@@ -2707,11 +2711,26 @@ def start_open_files_server(self):
                 self.sig_open_external_file.emit(fname)
             req.sendall(b' ')
 
-    # ---- Quit and restart
-    def restart(self):
-        """Quit and Restart Spyder application"""
+    # ---- Quit and restart, and reset spyder defaults
+    def reset_spyder(self):
+        """
+        Quit and reset Spyder and then Restart application.
+        """
+        answer = QMessageBox.warning(self, _("Warning"),
+             _("Spyder will restart and reset to default settings: <br><br>"
+               "Do you want to continue?"),
+             QMessageBox.Yes | QMessageBox.No)
+        if answer == QMessageBox.Yes:
+            self.restart(reset=True)
+
+    def restart(self, reset=False):
+        """
+        Quit and Restart Spyder application.
+
+        If reset True it allows to reset spyder on restart.
+        """
         # Get start path to use in restart script
-        spyder_start_directory  = get_module_path('spyderlib')
+        spyder_start_directory = get_module_path('spyderlib')
         restart_script = osp.join(spyder_start_directory, 'restart_app.py')
 
         # Get any initial argument passed when spyder was started
@@ -2735,6 +2754,13 @@ def restart(self):
         env['SPYDER_ARGS'] = spyder_args
         env['SPYDER_PID'] = str(pid)
         env['SPYDER_IS_BOOTSTRAP'] = str(is_bootstrap)
+        env['SPYDER_RESET'] = str(reset)
+
+        if DEV:
+            if os.name == 'nt':
+                env['PYTHONPATH'] = ';'.join(sys.path)
+            else:
+                env['PYTHONPATH'] = ':'.join(sys.path)
 
         # Build the command and popen arguments depending on the OS
         if os.name == 'nt':


Test patch: