# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)

"""
Spyder, the Scientific Python Development Environment
=====================================================

Developed and maintained by the Spyder Project
Contributors

Copyright © Spyder Project Contributors
Licensed under the terms of the MIT License
(see spyder/__init__.py for details)
"""

# =============================================================================
# Stdlib imports
# =============================================================================
from __future__ import print_function
from collections import OrderedDict
from enum import Enum
import errno
import gc
import logging
import os
import os.path as osp
import shutil
import signal
import socket
import glob
import sys
import threading
import traceback

#==============================================================================
# Check requirements before proceeding
#==============================================================================
from spyder import requirements
requirements.check_path()
requirements.check_qt()
requirements.check_spyder_kernels()

#==============================================================================
# Third-party imports
#==============================================================================
from qtpy.compat import from_qvariant
from qtpy.QtCore import (QCoreApplication, Qt, QTimer, Signal, Slot,
                         qInstallMessageHandler)
from qtpy.QtGui import QColor, QIcon, QKeySequence
from qtpy.QtWidgets import (QAction, QApplication, QMainWindow, QMenu,
                            QMessageBox, QShortcut, QStyleFactory, QCheckBox)

# Avoid a "Cannot mix incompatible Qt library" error on Windows platforms
from qtpy import QtSvg  # analysis:ignore

# Avoid a bug in Qt: https://bugreports.qt.io/browse/QTBUG-46720
from qtpy import QtWebEngineWidgets  # analysis:ignore

from qtawesome.iconic_font import FontError

#==============================================================================
# Local imports
# NOTE: Move (if possible) import's of widgets and plugins exactly where they
# are needed in MainWindow to speed up perceived startup time (i.e. the time
# from clicking the Spyder icon to showing the splash screen).
#==============================================================================
from spyder import __version__
from spyder import dependencies
from spyder.app import tour
from spyder.app.utils import (create_splash_screen, delete_lsp_log_files,
                              qt_message_handler, set_links_color,
                              setup_logging, set_opengl_implementation, Spy)
from spyder.config.base import (_, DEV, get_conf_path, get_debug_level,
                                get_home_dir, get_module_source_path,
                                get_safe_mode, is_pynsist, running_in_mac_app,
                                running_under_pytest, STDERR)
from spyder.utils.image_path_manager import get_image_path
from spyder.config.gui import is_dark_font_color
from spyder.config.main import OPEN_FILES_PORT
from spyder.config.manager import CONF
from spyder.config.utils import IMPORT_EXT, is_gtk_desktop
from spyder.otherplugins import get_spyderplugins_mods
from spyder.py3compat import configparser as cp, PY3, to_text_string
from spyder.utils import encoding, programs
from spyder.utils.icon_manager import ima
from spyder.utils.misc import (select_port, getcwd_or_home,
                               get_python_executable)
from spyder.utils.palette import QStylePalette
from spyder.utils.qthelpers import (create_action, add_actions, file_uri,
                                    qapplication, start_file)
from spyder.utils.stylesheet import APP_STYLESHEET
from spyder.app.solver import (
    find_external_plugins, find_internal_plugins, solve_plugin_dependencies)

# Spyder API Imports
from spyder.api.exceptions import SpyderAPIError
from spyder.api.plugins import Plugins, SpyderPluginV2, SpyderDockablePlugin

#==============================================================================
# Windows only local imports
#==============================================================================
set_attached_console_visible = None
is_attached_console_visible = None
set_windows_appusermodelid = None
if os.name == 'nt':
    from spyder.utils.windows import (set_attached_console_visible,
                                      set_windows_appusermodelid)

#==============================================================================
# Constants
#==============================================================================
# Module logger
logger = logging.getLogger(__name__)

# Keeping a reference to the original sys.exit before patching it
ORIGINAL_SYS_EXIT = sys.exit

# Get the cwd before initializing WorkingDirectory, which sets it to the one
# used in the last session
CWD = getcwd_or_home()

# Set the index for the default tour
DEFAULT_TOUR = 0

#==============================================================================
# Install Qt messaage handler
#==============================================================================
qInstallMessageHandler(qt_message_handler)

#==============================================================================
# Main Window
#==============================================================================
class MainWindow(QMainWindow):
    """Spyder main window"""
    DOCKOPTIONS = (
        QMainWindow.AllowTabbedDocks | QMainWindow.AllowNestedDocks |
        QMainWindow.AnimatedDocks
    )
    SPYDER_PATH = get_conf_path('path')
    SPYDER_NOT_ACTIVE_PATH = get_conf_path('not_active_path')
    DEFAULT_LAYOUTS = 4

    # Signals
    restore_scrollbar_position = Signal()
    sig_setup_finished = Signal()
    all_actions_defined = Signal()
    # type: (OrderedDict, OrderedDict)
    sig_pythonpath_changed = Signal(object, object)
    sig_main_interpreter_changed = Signal()
    sig_open_external_file = Signal(str)
    sig_resized = Signal("QResizeEvent")     # Related to interactive tour
    sig_moved = Signal("QMoveEvent")         # Related to interactive tour
    sig_layout_setup_ready = Signal(object)  # Related to default layouts

    # --- Plugin handling methods
    # ------------------------------------------------------------------------
    def get_plugin(self, plugin_name, error=True):
        """
        Return a plugin instance by providing the plugin class.
        """
        for name, plugin in self._PLUGINS.items():
            if plugin_name == name:
                return plugin
        else:
            if error:
                raise SpyderAPIError(
                    'Plugin "{}" not found!'.format(plugin_name))
            else:
                return None

    def show_status_message(self, message, timeout):
        """
        Show a status message in Spyder Main Window.
        """
        status_bar = self.statusBar()
        if status_bar.isVisible():
            status_bar.showMessage(message, timeout)

    def show_plugin_compatibility_message(self, message):
        """
        Show a compatibility message.
        """
        messageBox = QMessageBox(self)
        messageBox.setWindowModality(Qt.NonModal)
        messageBox.setAttribute(Qt.WA_DeleteOnClose)
        messageBox.setWindowTitle(_('Compatibility Check'))
        messageBox.setText(message)
        messageBox.setStandardButtons(QMessageBox.Ok)
        messageBox.show()

    def add_plugin(self, plugin, external=False):
        """
        Add plugin to plugins dictionary.
        """
        self._PLUGINS[plugin.NAME] = plugin
        if external:
            self._EXTERNAL_PLUGINS[plugin.NAME] = plugin
        else:
            self._INTERNAL_PLUGINS[plugin.NAME] = plugin

    def register_plugin(self, plugin, external=False, omit_conf=False):
        """
        Register a plugin in Spyder Main Window.
        """
        self.set_splash(_("Loading {}...").format(plugin.get_name()))
        logger.info("Loading {}...".format(plugin.NAME))

        # Check plugin compatibility
        is_compatible, message = plugin.check_compatibility()
        plugin.is_compatible = is_compatible
        plugin.get_description()

        if not is_compatible:
            self.show_compatibility_message(message)
            return

        # Signals
        plugin.sig_exception_occurred.connect(self.handle_exception)
        plugin.sig_free_memory_requested.connect(self.free_memory)
        plugin.sig_quit_requested.connect(self.close)
        plugin.sig_restart_requested.connect(self.restart)
        plugin.sig_redirect_stdio_requested.connect(
            self.redirect_internalshell_stdio)
        plugin.sig_status_message_requested.connect(self.show_status_message)

        if isinstance(plugin, SpyderDockablePlugin):
            plugin.sig_focus_changed.connect(self.plugin_focus_changed)
            plugin.sig_switch_to_plugin_requested.connect(
                self.switch_to_plugin)
            plugin.sig_update_ancestor_requested.connect(
                lambda: plugin.set_ancestor(self))

        # Register plugin
        plugin._register(omit_conf=omit_conf)
        plugin.register()

        if isinstance(plugin, SpyderDockablePlugin):
            # Add dockwidget
            self.add_dockwidget(plugin)

            # Update margins
            margin = 0
            if CONF.get('main', 'use_custom_margin'):
                margin = CONF.get('main', 'custom_margin')
            plugin.update_margins(margin)

        self.add_plugin(plugin, external=external)

        logger.info("Registering shortcuts for {}...".format(plugin.NAME))
        for action_name, action in plugin.get_actions().items():
            context = (getattr(action, 'shortcut_context', plugin.NAME)
                       or plugin.NAME)

            if getattr(action, 'register_shortcut', True):
                if isinstance(action_name, Enum):
                    action_name = action_name.value

                self.register_shortcut(action, context, action_name)

        if isinstance(plugin, SpyderDockablePlugin):
            try:
                context = '_'
                name = 'switch to {}'.format(plugin.CONF_SECTION)
                shortcut = CONF.get_shortcut(context, name,
                                             plugin_name=plugin.CONF_SECTION)
            except (cp.NoSectionError, cp.NoOptionError):
                shortcut = None

            sc = QShortcut(QKeySequence(), self,
                           lambda: self.switch_to_plugin(plugin))
            sc.setContext(Qt.ApplicationShortcut)
            plugin._shortcut = sc

            self.register_shortcut(sc, context, name)
            self.register_shortcut(plugin.toggle_view_action, context, name)

    def unregister_plugin(self, plugin):
        """
        Unregister a plugin from the Spyder Main Window.
        """
        logger.info("Unloading {}...".format(plugin.NAME))

        # Disconnect all slots
        signals = [
            plugin.sig_quit_requested,
            plugin.sig_redirect_stdio_requested,
            plugin.sig_status_message_requested,
        ]

        for sig in signals:
            try:
                sig.disconnect()
            except TypeError:
                pass

        # Unregister shortcuts for actions
        logger.info("Unregistering shortcuts for {}...".format(plugin.NAME))
        for action_name, action in plugin.get_actions().items():
            context = (getattr(action, 'shortcut_context', plugin.NAME)
                       or plugin.NAME)
            self.shortcuts.unregister_shortcut(action, context, action_name)

        # Unregister switch to shortcut
        shortcut = None
        try:
            context = '_'
            name = 'switch to {}'.format(plugin.CONF_SECTION)
            shortcut = CONF.get_shortcut(context, name,
                                         plugin_name=plugin.CONF_SECTION)
        except Exception:
            pass

        if shortcut is not None:
            self.shortcuts.unregister_shortcut(
                plugin._shortcut,
                context,
                "Switch to {}".format(plugin.CONF_SECTION),
            )

        # Remove dockwidget
        logger.info("Removing {} dockwidget...".format(plugin.NAME))
        self.remove_dockwidget(plugin)

        plugin.unregister()
        plugin._unregister()

    def create_plugin_conf_widget(self, plugin):
        """
        Create configuration dialog box page widget.
        """
        config_dialog = self.prefs_dialog_instance
        if plugin.CONF_WIDGET_CLASS is not None and config_dialog is not None:
            conf_widget = plugin.CONF_WIDGET_CLASS(plugin, config_dialog)
            conf_widget.initialize()
            return conf_widget

    @property
    def last_plugin(self):
        """
        Get last plugin with focus if it is a dockable widget.

        If a non-dockable plugin has the focus this will return by default
        the Editor plugin.
        """
        # Needed to prevent errors with the old API at
        # spyder/plugins/base::_switch_to_plugin
        return self.layouts.get_last_plugin()

    def maximize_dockwidget(self, restore=False):
        """
        This is needed to prevent errors with the old API at
        spyder/plugins/base::_switch_to_plugin.

        See spyder-ide/spyder#15164

        Parameters
        ----------
        restore : bool, optional
            If the current dockwidget needs to be restored to its unmaximized
            state. The default is False.
        """
        self.layouts.maximize_dockwidget(restore=restore)

    def switch_to_plugin(self, plugin, force_focus=None):
        """
        Switch to this plugin.

        Notes
        -----
        This operation unmaximizes the current plugin (if any), raises
        this plugin to view (if it's hidden) and gives it focus (if
        possible).
        """
        last_plugin = self.last_plugin
        try:
            # New API
            if (last_plugin is not None
                    and last_plugin.get_widget().is_maximized
                    and last_plugin is not plugin):
                self.layouts.maximize_dockwidget()
        except AttributeError:
            # Old API
            if (last_plugin is not None and self.last_plugin._ismaximized
                    and last_plugin is not plugin):
                self.layouts.maximize_dockwidget()

        try:
            # New API
            if not plugin.toggle_view_action.isChecked():
                plugin.toggle_view_action.setChecked(True)
                plugin.get_widget().is_visible = False
        except AttributeError:
            # Old API
            if not plugin._toggle_view_action.isChecked():
                plugin._toggle_view_action.setChecked(True)
                plugin._widget._is_visible = False

        plugin.change_visibility(True, force_focus=force_focus)

    def remove_dockwidget(self, plugin):
        """
        Remove a plugin QDockWidget from the main window.
        """
        self.removeDockWidget(plugin.dockwidget)
        try:
            self.widgetlist.remove(plugin)
        except ValueError:
            pass

    def tabify_plugins(self, first, second):
        """Tabify plugin dockwigdets."""
        self.tabifyDockWidget(first.dockwidget, second.dockwidget)

    def tabify_plugin(self, plugin, default=None):
        """
        Tabify the plugin using the list of possible TABIFY options.

        Only do this if the dockwidget does not have more dockwidgets
        in the same position and if the plugin is using the New API.
        """
        def tabify_helper(plugin, next_to_plugins):
            for next_to_plugin in next_to_plugins:
                try:
                    self.tabify_plugins(next_to_plugin, plugin)
                    break
                except SpyderAPIError as err:
                    logger.error(err)

        # If TABIFY not defined use the [default]
        tabify = getattr(plugin, 'TABIFY', [default])
        if not isinstance(tabify, list):
            next_to_plugins = [tabify]
        else:
            next_to_plugins = tabify

        # Check if TABIFY is not a list with None as unique value or a default
        # list
        if tabify in [[None], []]:
            return False

        # Get the actual plugins from the names
        next_to_plugins = [self.get_plugin(p) for p in next_to_plugins]

        # First time plugin starts
        if plugin.get_conf('first_time', True):
            if (isinstance(plugin, SpyderDockablePlugin)
                    and plugin.NAME != Plugins.Console):
                logger.info(
                    "Tabify {} dockwidget for the first time...".format(
                        plugin.NAME))
                tabify_helper(plugin, next_to_plugins)

            plugin.set_conf('enable', True)
            plugin.set_conf('first_time', False)
        else:
            # This is needed to ensure new plugins are placed correctly
            # without the need for a layout reset.
            logger.info("Tabify {} dockwidget...".format(plugin.NAME))
            # Check if plugin has no other dockwidgets in the same position
            if not bool(self.tabifiedDockWidgets(plugin.dockwidget)):
                tabify_helper(plugin, next_to_plugins)

        return True

    def handle_exception(self, error_data):
        """
        This method will call the handle exception method of the Console
        plugin. It is provided as a signal on the Plugin API for convenience,
        so that plugin do not need to explicitly call the Console plugin.

        Parameters
        ----------
        error_data: dict
            The dictionary containing error data. The expected keys are:
            >>> error_data= {
                "text": str,
                "is_traceback": bool,
                "repo": str,
                "title": str,
                "label": str,
                "steps": str,
            }

        Notes
        -----
        The `is_traceback` key indicates if `text` contains plain text or a
        Python error traceback.

        The `title` and `repo` keys indicate how the error data should
        customize the report dialog and Github error submission.

        The `label` and `steps` keys allow customizing the content of the
        error dialog.
        """
        if self.console:
            self.console.handle_exception(error_data)

    def __init__(self, splash=None, options=None):
        QMainWindow.__init__(self)
        qapp = QApplication.instance()

        if running_under_pytest():
            self._proxy_style = None
        else:
            from spyder.utils.qthelpers import SpyderProxyStyle
            # None is needed, see: https://bugreports.qt.io/browse/PYSIDE-922
            self._proxy_style = SpyderProxyStyle(None)

        # Enabling scaling for high dpi
        qapp.setAttribute(Qt.AA_UseHighDpiPixmaps)

        self.default_style = str(qapp.style().objectName())

        self.init_workdir = options.working_directory
        self.profile = options.profile
        self.multithreaded = options.multithreaded
        self.new_instance = options.new_instance
        if options.project is not None and not running_in_mac_app():
            self.open_project = osp.normpath(osp.join(CWD, options.project))
        else:
            self.open_project = None
        self.window_title = options.window_title

        logger.info("Start of MainWindow constructor")

        def signal_handler(signum, frame=None):
            """Handler for signals."""
            sys.stdout.write('Handling signal: %s\n' % signum)
            sys.stdout.flush()
            QApplication.quit()

        if os.name == "nt":
            try:
                import win32api
                win32api.SetConsoleCtrlHandler(signal_handler, True)
            except ImportError:
                pass
        else:
            signal.signal(signal.SIGTERM, signal_handler)
            if not DEV:
                # Make spyder quit when presing ctrl+C in the console
                # In DEV Ctrl+C doesn't quit, because it helps to
                # capture the traceback when spyder freezes
                signal.signal(signal.SIGINT, signal_handler)

        # Use a custom Qt stylesheet
        if sys.platform == 'darwin':
            spy_path = get_module_source_path('spyder')
            img_path = osp.join(spy_path, 'images')
            mac_style = open(osp.join(spy_path, 'app', 'mac_stylesheet.qss')).read()
            mac_style = mac_style.replace('$IMAGE_PATH', img_path)
            self.setStyleSheet(mac_style)

        # Shortcut management data
        self.shortcut_data = []

        # Handle Spyder path
        self.path = ()
        self.not_active_path = ()
        self.project_path = ()

        # New API
        self._APPLICATION_TOOLBARS = OrderedDict()
        self._STATUS_WIDGETS = OrderedDict()
        self._PLUGINS = OrderedDict()
        self._EXTERNAL_PLUGINS = OrderedDict()
        self._INTERNAL_PLUGINS = OrderedDict()
        # Mapping of new plugin identifiers vs old attributtes
        # names given for plugins or to prevent collisions with other
        # attributes, i.e layout (Qt) vs layout (SpyderPluginV2)
        self._INTERNAL_PLUGINS_MAPPING = {
            'console': Plugins.Console,
            'maininterpreter': Plugins.MainInterpreter,
            'outlineexplorer': Plugins.OutlineExplorer,
            'variableexplorer': Plugins.VariableExplorer,
            'ipyconsole': Plugins.IPythonConsole,
            'workingdirectory': Plugins.WorkingDirectory,
            'projects': Plugins.Projects,
            'findinfiles': Plugins.Find,
            'layouts': Plugins.Layout,
            }

        self.thirdparty_plugins = []

        # Tour
        # TODO: Should be a plugin
        self.tour = None
        self.tours_available = None
        self.tour_dialog = None

        # File switcher
        self.switcher = None

        # Preferences
        self.prefs_dialog_size = None
        self.prefs_dialog_instance = None

        # Actions
        self.undo_action = None
        self.redo_action = None
        self.copy_action = None
        self.cut_action = None
        self.paste_action = None
        self.selectall_action = None

        # Menu bars
        self.edit_menu = None
        self.edit_menu_actions = []
        self.search_menu = None
        self.search_menu_actions = []
        self.source_menu = None
        self.source_menu_actions = []
        self.run_menu = None
        self.run_menu_actions = []
        self.debug_menu = None
        self.debug_menu_actions = []
        self.projects_menu = None
        self.projects_menu_actions = []

        # TODO: Move to corresponding Plugins
        self.main_toolbar = None
        self.main_toolbar_actions = []
        self.file_toolbar = None
        self.file_toolbar_actions = []
        self.run_toolbar = None
        self.run_toolbar_actions = []
        self.debug_toolbar = None
        self.debug_toolbar_actions = []

        self.menus = []

        if running_under_pytest():
            # Show errors in internal console when testing.
            CONF.set('main', 'show_internal_errors', False)

        self.CURSORBLINK_OSDEFAULT = QApplication.cursorFlashTime()

        if set_windows_appusermodelid != None:
            res = set_windows_appusermodelid()
            logger.info("appusermodelid: %s", res)

        # Setting QTimer if running in travis
        test_app = os.environ.get('TEST_CI_APP')
        if test_app is not None:
            app = qapplication()
            timer_shutdown_time = 30000
            self.timer_shutdown = QTimer(self)
            self.timer_shutdown.timeout.connect(app.quit)
            self.timer_shutdown.start(timer_shutdown_time)

        # Showing splash screen
        self.splash = splash
        if CONF.get('main', 'current_version', '') != __version__:
            CONF.set('main', 'current_version', __version__)
            # Execute here the actions to be performed only once after
            # each update (there is nothing there for now, but it could
            # be useful some day...)

        # List of satellite widgets (registered in add_dockwidget):
        self.widgetlist = []

        # Flags used if closing() is called by the exit() shell command
        self.already_closed = False
        self.is_starting_up = True
        self.is_setting_up = True

        self.floating_dockwidgets = []
        self.window_size = None
        self.window_position = None

        # To keep track of the last focused widget
        self.last_focused_widget = None
        self.previous_focused_widget = None

        # Keep track of dpi message
        self.show_dpi_message = True

        # Server to open external files on a single instance
        # This is needed in order to handle socket creation problems.
        # See spyder-ide/spyder#4132.
        if os.name == 'nt':
            try:
                self.open_files_server = socket.socket(socket.AF_INET,
                                                       socket.SOCK_STREAM,
                                                       socket.IPPROTO_TCP)
            except OSError:
                self.open_files_server = None
                QMessageBox.warning(None, "Spyder",
                         _("An error occurred while creating a socket needed "
                           "by Spyder. Please, try to run as an Administrator "
                           "from cmd.exe the following command and then "
                           "restart your computer: <br><br><span "
                           "style=\'color: {color}\'><b>netsh winsock reset "
                           "</b></span><br>").format(
                               color=QStylePalette.COLOR_BACKGROUND_4))
        else:
            self.open_files_server = socket.socket(socket.AF_INET,
                                                   socket.SOCK_STREAM,
                                                   socket.IPPROTO_TCP)

        # To show the message about starting the tour
        self.sig_setup_finished.connect(self.show_tour_message)

        # Apply main window settings
        self.apply_settings()

        # To set all dockwidgets tabs to be on top (in case we want to do it
        # in the future)
        # self.setTabPosition(Qt.AllDockWidgetAreas, QTabWidget.North)

        logger.info("End of MainWindow constructor")

    # --- Window setup
    def _update_shortcuts_in_panes_menu(self, show=True):
        """
        Display the shortcut for the "Switch to plugin..." on the toggle view
        action of the plugins displayed in the Help/Panes menu.

        Notes
        -----
        SpyderDockablePlugins provide two actions that function as a single
        action. The `Switch to Plugin...` action has an assignable shortcut
        via the shortcut preferences. The `Plugin toggle View` in the `View`
        application menu, uses a custom `Toggle view action` that displays the
        shortcut assigned to the `Switch to Plugin...` action, but is not
        triggered by that shortcut.
        """
        for plugin_id, plugin in self._PLUGINS.items():
            if isinstance(plugin, SpyderDockablePlugin):
                try:
                    # New API
                    action = plugin.toggle_view_action
                except AttributeError:
                    # Old API
                    action = plugin._toggle_view_action

                if show:
                    section = plugin.CONF_SECTION
                    try:
                        context = '_'
                        name = 'switch to {}'.format(section)
                        shortcut = CONF.get_shortcut(
                            context, name, plugin_name=section)
                    except (cp.NoSectionError, cp.NoOptionError):
                        shortcut = QKeySequence()
                else:
                    shortcut = QKeySequence()

                action.setShortcut(shortcut)

    def setup(self):
        """Setup main window."""
        # TODO: Remove circular dependency between help and ipython console
        # and remove this import. Help plugin should take care of it
        from spyder.plugins.help.utils.sphinxify import CSS_PATH, DARK_CSS_PATH
        logger.info("*** Start of MainWindow setup ***")
        logger.info("Updating PYTHONPATH")
        path_dict = self.get_spyder_pythonpath_dict()
        self.update_python_path(path_dict)

        logger.info("Applying theme configuration...")
        ui_theme = CONF.get('appearance', 'ui_theme')
        color_scheme = CONF.get('appearance', 'selected')

        if ui_theme == 'dark':
            if not running_under_pytest():
                # Set style proxy to fix combobox popup on mac and qdark
                qapp = QApplication.instance()
                qapp.setStyle(self._proxy_style)
            dark_qss = str(APP_STYLESHEET)
            self.setStyleSheet(dark_qss)
            self.statusBar().setStyleSheet(dark_qss)
            css_path = DARK_CSS_PATH

        elif ui_theme == 'light':
            if not running_under_pytest():
                # Set style proxy to fix combobox popup on mac and qdark
                qapp = QApplication.instance()
                qapp.setStyle(self._proxy_style)
            light_qss = str(APP_STYLESHEET)
            self.setStyleSheet(light_qss)
            self.statusBar().setStyleSheet(light_qss)
            css_path = CSS_PATH

        elif ui_theme == 'automatic':
            if not is_dark_font_color(color_scheme):
                if not running_under_pytest():
                    # Set style proxy to fix combobox popup on mac and qdark
                    qapp = QApplication.instance()
                    qapp.setStyle(self._proxy_style)
                dark_qss = str(APP_STYLESHEET)
                self.setStyleSheet(dark_qss)
                self.statusBar().setStyleSheet(dark_qss)
                css_path = DARK_CSS_PATH
            else:
                light_qss = str(APP_STYLESHEET)
                self.setStyleSheet(light_qss)
                self.statusBar().setStyleSheet(light_qss)
                css_path = CSS_PATH

        # Set css_path as a configuration to be used by the plugins
        CONF.set('appearance', 'css_path', css_path)

        # Status bar
        status = self.statusBar()
        status.setObjectName("StatusBar")
        status.showMessage(_("Welcome to Spyder!"), 5000)

        # Switcher instance
        logger.info("Loading switcher...")
        self.create_switcher()

        message = _(
            "Spyder Internal Console\n\n"
            "This console is used to report application\n"
            "internal errors and to inspect Spyder\n"
            "internals with the following commands:\n"
            "  spy.app, spy.window, dir(spy)\n\n"
            "Please don't use it to run your code\n\n"
        )
        CONF.set('internal_console', 'message', message)
        CONF.set('internal_console', 'multithreaded', self.multithreaded)
        CONF.set('internal_console', 'profile', self.profile)
        CONF.set('internal_console', 'commands', [])
        CONF.set('internal_console', 'namespace', {})
        CONF.set('internal_console', 'show_internal_errors', True)

        # Working directory initialization
        CONF.set('workingdir', 'init_workdir', self.init_workdir)

        # Load and register internal and external plugins
        external_plugins = find_external_plugins()
        internal_plugins = find_internal_plugins()
        all_plugins = external_plugins.copy()
        all_plugins.update(internal_plugins.copy())

        # Determine 'enable' config for the plugins that have it
        enabled_plugins = {}
        for plugin in all_plugins.values():
            plugin_name = plugin.NAME
            plugin_main_attribute_name = (
                self._INTERNAL_PLUGINS_MAPPING[plugin_name]
                if plugin_name in self._INTERNAL_PLUGINS_MAPPING
                else plugin_name)
            try:
                if CONF.get(plugin_main_attribute_name, "enable"):
                    enabled_plugins[plugin_name] = plugin
            except (cp.NoOptionError, cp.NoSectionError):
                enabled_plugins[plugin_name] = plugin

        # Get ordered list of plugins classes and instantiate them
        plugin_deps = solve_plugin_dependencies(list(enabled_plugins.values()))
        for plugin_class in plugin_deps:
            plugin_name = plugin_class.NAME
            # Non-migrated plugins
            if plugin_name in [
                    Plugins.Editor,
                    Plugins.IPythonConsole,
                    Plugins.Projects]:
                if plugin_name == Plugins.IPythonConsole:
                    plugin_instance = plugin_class(self)
                    plugin_instance.sig_exception_occurred.connect(
                        self.handle_exception)
                else:
                    plugin_instance = plugin_class(self)
                plugin_instance.register_plugin()
                self.add_plugin(plugin_instance)
                if plugin_name == Plugins.Projects:
                    self.project_path = plugin_instance.get_pythonpath(
                        at_start=True)
                else:
                    self.preferences.register_plugin_preferences(
                        plugin_instance)
            # Migrated or new plugins
            elif plugin_name in [
                    Plugins.MainMenu,
                    Plugins.OnlineHelp,
                    Plugins.Toolbar,
                    Plugins.Preferences,
                    Plugins.Appearance,
                    Plugins.Run,
                    Plugins.Shortcuts,
                    Plugins.StatusBar,
                    Plugins.Completions,
                    Plugins.OutlineExplorer,
                    Plugins.Console,
                    Plugins.MainInterpreter,
                    Plugins.Breakpoints,
                    Plugins.History,
                    Plugins.Profiler,
                    Plugins.Explorer,
                    Plugins.Help,
                    Plugins.Plots,
                    Plugins.VariableExplorer,
                    Plugins.Application,
                    Plugins.Find,
                    Plugins.Pylint,
                    Plugins.WorkingDirectory,
                    Plugins.Layout]:
                plugin_instance = plugin_class(self, configuration=CONF)
                self.register_plugin(plugin_instance)
                # TODO: Check thirdparty attribute usage
                # For now append plugins to the thirdparty attribute as was
                # being done
                if plugin_name in [
                        Plugins.Breakpoints,
                        Plugins.Profiler,
                        Plugins.Pylint]:
                    self.thirdparty_plugins.append(plugin_instance)
            # Load external_plugins adding their dependencies
            elif (issubclass(plugin_class, SpyderPluginV2) and
                  plugin_class.NAME in external_plugins):
                try:
                    if plugin_class.CONF_FILE:
                        CONF.register_plugin(plugin_class)
                    plugin_instance = plugin_class(
                        self,
                        configuration=CONF,
                    )
                    self.register_plugin(plugin_instance, external=True,
                                         omit_conf=plugin_class.CONF_FILE)

                    # These attributes come from spyder.app.solver to add
                    # plugins to the dependencies dialog
                    if not running_under_pytest():
                        module = plugin_class._spyder_module_name
                        package_name = plugin_class._spyder_package_name
                        version = plugin_class._spyder_version
                        description = plugin_instance.get_description()
                        dependencies.add(
                            module, package_name, description, version, None,
                            kind=dependencies.PLUGIN)
                except Exception as error:
                    print("%s: %s" % (plugin_class, str(error)), file=STDERR)
                    traceback.print_exc(file=STDERR)

        self.set_splash(_("Loading old third-party plugins..."))
        for mod in get_spyderplugins_mods():
            try:
                plugin = mod.PLUGIN_CLASS(self)
                if plugin.check_compatibility()[0]:
                    if hasattr(plugin, 'CONFIGWIDGET_CLASS'):
                        self.preferences.register_plugin_preferences(plugin)

                    if hasattr(plugin, 'COMPLETION_PROVIDER_NAME'):
                        self.completions.register_completion_plugin(plugin)
                    else:
                        self.thirdparty_plugins.append(plugin)
                        plugin.register_plugin()

                    # Add to dependencies dialog
                    module = mod.__name__
                    name = module.replace('_', '-')
                    if plugin.DESCRIPTION:
                        description = plugin.DESCRIPTION
                    else:
                        description = plugin.get_plugin_title()

                    dependencies.add(module, name, description,
                                     '', None, kind=dependencies.PLUGIN)
            except TypeError:
                # Fixes spyder-ide/spyder#13977
                pass
            except Exception as error:
                print("%s: %s" % (mod, str(error)), file=STDERR)
                traceback.print_exc(file=STDERR)

        # Set window title
        self.set_window_title()

        # Menus
        # TODO: Remove when all menus are migrated to use the Main Menu Plugin
        logger.info("Creating Menus...")
        from spyder.api.widgets.menus import SpyderMenu
        from spyder.plugins.mainmenu.api import (
            ApplicationMenus, HelpMenuSections, ToolsMenuSections,
            FileMenuSections)
        mainmenu = self.mainmenu
        self.edit_menu = mainmenu.get_application_menu("edit_menu")
        self.search_menu = mainmenu.get_application_menu("search_menu")
        self.source_menu = mainmenu.get_application_menu("source_menu")
        self.source_menu.aboutToShow.connect(self.update_source_menu)
        self.run_menu = mainmenu.get_application_menu("run_menu")
        self.debug_menu = mainmenu.get_application_menu("debug_menu")
        self.projects_menu = mainmenu.get_application_menu("projects_menu")
        self.projects_menu.aboutToShow.connect(self.valid_project)

        # Switcher shortcuts
        self.file_switcher_action = create_action(
                                    self,
                                    _('File switcher...'),
                                    icon=ima.icon('filelist'),
                                    tip=_('Fast switch between files'),
                                    triggered=self.open_switcher,
                                    context=Qt.ApplicationShortcut)
        self.register_shortcut(self.file_switcher_action, context="_",
                               name="File switcher")
        self.symbol_finder_action = create_action(
                                    self, _('Symbol finder...'),
                                    icon=ima.icon('symbol_find'),
                                    tip=_('Fast symbol search in file'),
                                    triggered=self.open_symbolfinder,
                                    context=Qt.ApplicationShortcut)
        self.register_shortcut(self.symbol_finder_action, context="_",
                               name="symbol finder", add_shortcut_to_tip=True)

        def create_edit_action(text, tr_text, icon):
            textseq = text.split(' ')
            method_name = textseq[0].lower()+"".join(textseq[1:])
            action = create_action(self, tr_text,
                                   icon=icon,
                                   triggered=self.global_callback,
                                   data=method_name,
                                   context=Qt.WidgetShortcut)
            self.register_shortcut(action, "Editor", text)
            return action

        self.undo_action = create_edit_action('Undo', _('Undo'),
                                              ima.icon('undo'))
        self.redo_action = create_edit_action('Redo', _('Redo'),
                                              ima.icon('redo'))
        self.copy_action = create_edit_action('Copy', _('Copy'),
                                              ima.icon('editcopy'))
        self.cut_action = create_edit_action('Cut', _('Cut'),
                                             ima.icon('editcut'))
        self.paste_action = create_edit_action('Paste', _('Paste'),
                                               ima.icon('editpaste'))
        self.selectall_action = create_edit_action("Select All",
                                                   _("Select All"),
                                                   ima.icon('selectall'))

        self.edit_menu_actions += [self.undo_action, self.redo_action,
                                   None, self.cut_action, self.copy_action,
                                   self.paste_action, self.selectall_action,
                                   None] + self.editor.edit_menu_actions
        switcher_actions = [
            self.file_switcher_action,
            self.symbol_finder_action
        ]
        for switcher_action in switcher_actions:
            mainmenu.add_item_to_application_menu(
                    switcher_action,
                    menu_id=ApplicationMenus.File,
                    section=FileMenuSections.Switcher,
                    before_section=FileMenuSections.Restart)
        self.set_splash("")

        # Toolbars
        # TODO: Remove after finishing the migration
        logger.info("Creating toolbars...")
        toolbar = self.toolbar
        self.file_toolbar = toolbar.get_application_toolbar("file_toolbar")
        self.run_toolbar = toolbar.get_application_toolbar("run_toolbar")
        self.debug_toolbar = toolbar.get_application_toolbar("debug_toolbar")
        self.main_toolbar = toolbar.get_application_toolbar("main_toolbar")

        # Tools + External Tools (some of this depends on the Application
        # plugin)
        logger.info("Creating Tools menu...")

        spyder_path_action = create_action(
            self,
            _("PYTHONPATH manager"),
            None, icon=ima.icon('pythonpath'),
            triggered=self.show_path_manager,
            tip=_("PYTHONPATH manager"),
            menurole=QAction.ApplicationSpecificRole)
        from spyder.plugins.application.plugin import (
            ApplicationActions, WinUserEnvDialog)
        winenv_action = None
        if WinUserEnvDialog:
            winenv_action = self.application.get_action(
                ApplicationActions.SpyderWindowsEnvVariables)
        mainmenu.add_item_to_application_menu(
            spyder_path_action,
            menu_id=ApplicationMenus.Tools,
            section=ToolsMenuSections.Tools,
            before=winenv_action
        )
        if get_debug_level() >= 3:
            self.menu_lsp_logs = QMenu(_("LSP logs"))
            self.menu_lsp_logs.aboutToShow.connect(self.update_lsp_logs)
            mainmenu.add_item_to_application_menu(
                self.menu_lsp_logs,
                menu_id=ApplicationMenus.Tools)

        # Main toolbar
        from spyder.plugins.toolbar.api import (
            ApplicationToolbars, MainToolbarSections)
        self.toolbar.add_item_to_application_toolbar(
            spyder_path_action,
            toolbar_id=ApplicationToolbars.Main,
            section=MainToolbarSections.ApplicationSection
        )

        self.set_splash(_("Setting up main window..."))

        #----- Tours
        # TODO: Move tours to a plugin structure
        self.tour = tour.AnimatedTour(self)
        # self.tours_menu = QMenu(_("Interactive tours"), self)
        # self.tour_menu_actions = []
        # # TODO: Only show intro tour for now. When we are close to finish
        # # 3.0, we will finish and show the other tour
        self.tours_available = tour.get_tours(DEFAULT_TOUR)

        for i, tour_available in enumerate(self.tours_available):
            self.tours_available[i]['last'] = 0
            tour_name = tour_available['name']

        #     def trigger(i=i, self=self):  # closure needed!
        #         return lambda: self.show_tour(i)

        #     temp_action = create_action(self, tour_name, tip="",
        #                                 triggered=trigger())
        #     self.tour_menu_actions += [temp_action]

        # self.tours_menu.addActions(self.tour_menu_actions)
        self.tour_action = create_action(
            self,
            self.tours_available[DEFAULT_TOUR]['name'],
            tip=_("Interactive tour introducing Spyder's panes and features"),
            triggered=lambda: self.show_tour(DEFAULT_TOUR))
        mainmenu.add_item_to_application_menu(
            self.tour_action,
            menu_id=ApplicationMenus.Help,
            section=HelpMenuSections.Documentation)

        # TODO: Migrate to use the MainMenu Plugin instead of list of actions
        # Filling out menu/toolbar entries:
        add_actions(self.edit_menu, self.edit_menu_actions)
        add_actions(self.search_menu, self.search_menu_actions)
        add_actions(self.source_menu, self.source_menu_actions)
        add_actions(self.run_menu, self.run_menu_actions)
        add_actions(self.debug_menu, self.debug_menu_actions)
        add_actions(self.projects_menu, self.projects_menu_actions)

        # Emitting the signal notifying plugins that main window menu and
        # toolbar actions are all defined:
        self.all_actions_defined.emit()

    def __getattr__(self, attr):
        """
        Redefinition of __getattr__ to enable access to plugins.

        Loaded plugins can be accessed as attributes of the mainwindow
        as before, e.g self.console or self.main.console, preserving the
        same accessor as before.
        """
        # Mapping of new plugin identifiers vs old attributtes
        # names given for plugins
        if attr in self._INTERNAL_PLUGINS_MAPPING.keys():
            return self.get_plugin(self._INTERNAL_PLUGINS_MAPPING[attr])
        try:
            return self.get_plugin(attr)
        except SpyderAPIError:
            pass
        return super().__getattr__(attr)

    def update_lsp_logs(self):
        """Create an action for each lsp log file."""
        self.menu_lsp_logs.clear()
        lsp_logs = []
        files = glob.glob(osp.join(get_conf_path('lsp_logs'), '*.log'))
        for f in files:
            action = create_action(self, f, triggered=self.editor.load)
            action.setData(f)
            lsp_logs.append(action)
        add_actions(self.menu_lsp_logs, lsp_logs)

    def pre_visible_setup(self):
        """
        Actions to be performed before the main window is visible.

        The actions here are related with setting up the main window.
        """
        logger.info("Setting up window...")
        # Create external plugins before loading the layout to include them in
        # the window restore state after restarts.
        for plugin, plugin_instance in self._EXTERNAL_PLUGINS.items():
            self.tabify_plugin(plugin_instance, Plugins.Console)
            if isinstance(plugin_instance, SpyderDockablePlugin):
                plugin_instance.get_widget().toggle_view(False)

        for plugin_id, plugin_instance in self._PLUGINS.items():
            try:
                plugin_instance.before_mainwindow_visible()
            except AttributeError:
                pass

        if self.splash is not None:
            self.splash.hide()

        # Menu about to show
        for child in self.menuBar().children():
            if isinstance(child, QMenu):
                try:
                    child.aboutToShow.connect(self.update_edit_menu)
                    child.aboutToShow.connect(self.update_search_menu)
                except TypeError:
                    pass

        # Register custom layouts
        for plugin, plugin_instance in self._PLUGINS.items():
            if hasattr(plugin_instance, 'CUSTOM_LAYOUTS'):
                if isinstance(plugin_instance.CUSTOM_LAYOUTS, list):
                    for custom_layout in plugin_instance.CUSTOM_LAYOUTS:
                        self.layouts.register_layout(
                            self, custom_layout)
                else:
                    logger.info(
                        'Unable to load custom layouts for {}. '
                        'Expecting a list of layout classes but got {}'
                        .format(plugin, plugin_instance.CUSTOM_LAYOUTS)
                    )
        self.layouts.update_layout_menu_actions()

        logger.info("*** End of MainWindow setup ***")
        self.is_starting_up = False


    def post_visible_setup(self):
        """Actions to be performed only after the main window's `show` method
        was triggered"""
        for __, plugin in self._PLUGINS.items():
            try:
                plugin.on_mainwindow_visible()
            except AttributeError:
                pass

        self.restore_scrollbar_position.emit()

        logger.info('Deleting previous Spyder instance LSP logs...')
        delete_lsp_log_files()

        # Workaround for spyder-ide/spyder#880.
        # QDockWidget objects are not painted if restored as floating
        # windows, so we must dock them before showing the mainwindow,
        # then set them again as floating windows here.
        for widget in self.floating_dockwidgets:
            widget.setFloating(True)

        # Server to maintain just one Spyder instance and open files in it if
        # the user tries to start other instances with
        # $ spyder foo.py
        if (CONF.get('main', 'single_instance') and not self.new_instance
                and self.open_files_server):
            t = threading.Thread(target=self.start_open_files_server)
            t.setDaemon(True)
            t.start()

            # Connect the window to the signal emitted by the previous server
            # when it gets a client connected to it
            self.sig_open_external_file.connect(self.open_external_file)

        # Hide Internal Console so that people don't use it instead of
        # the External or IPython ones
        if self.console.dockwidget.isVisible() and DEV is None:
            self.console.toggle_view_action.setChecked(False)
            self.console.dockwidget.hide()

        # Show Help and Consoles by default
        plugins_to_show = [self.ipyconsole]
        if self.help is not None:
            plugins_to_show.append(self.help)
        for plugin in plugins_to_show:
            if plugin.dockwidget.isVisible():
                plugin.dockwidget.raise_()

        # Update plugins toggle actions to show the "Switch to" plugin shortcut
        self._update_shortcuts_in_panes_menu()

        # Process pending events and hide splash before loading the
        # previous session.
        QApplication.processEvents()
        if self.splash is not None:
            self.splash.hide()

        if self.open_project:
            if not running_in_mac_app():
                self.projects.open_project(
                    self.open_project, workdir=self.init_workdir
                )
        else:
            # Load last project if a project was active when Spyder
            # was closed
            self.projects.reopen_last_project()

            # If no project is active, load last session
            if self.projects.get_active_project() is None:
                self.editor.setup_open_files(close_previous_files=False)

        # Raise the menuBar to the top of the main window widget's stack
        # Fixes spyder-ide/spyder#3887.
        self.menuBar().raise_()

        # Handle DPI scale and window changes to show a restart message.
        # Don't activate this functionality on macOS because it's being
        # triggered in the wrong situations.
        # See spyder-ide/spyder#11846
        if not sys.platform == 'darwin':
            window = self.window().windowHandle()
            window.screenChanged.connect(self.handle_new_screen)
            screen = self.window().windowHandle().screen()
            self.current_dpi = screen.logicalDotsPerInch()
            screen.logicalDotsPerInchChanged.connect(
                self.show_dpi_change_message)

        # Notify that the setup of the mainwindow was finished
        self.is_setting_up = False
        self.sig_setup_finished.emit()

    def handle_new_screen(self, new_screen):
        """Connect DPI signals for new screen."""
        if new_screen is not None:
            new_screen_dpi = new_screen.logicalDotsPerInch()
            if self.current_dpi != new_screen_dpi:
                self.show_dpi_change_message(new_screen_dpi)
            else:
                new_screen.logicalDotsPerInchChanged.connect(
                    self.show_dpi_change_message)

    def handle_dpi_change_response(self, result, dpi):
        """Handle dpi change message dialog result."""
        if self.dpi_change_dismiss_box.isChecked():
            self.show_dpi_message = False
            self.dpi_change_dismiss_box = None
        if result == 0:  # Restart button was clicked
            # Activate HDPI auto-scaling option since is needed for a
            # proper display when using OS scaling
            CONF.set('main', 'normal_screen_resolution', False)
            CONF.set('main', 'high_dpi_scaling', True)
            CONF.set('main', 'high_dpi_custom_scale_factor', False)
            self.restart()
        else:
            # Update current dpi for future checks
            self.current_dpi = dpi

    def show_dpi_change_message(self, dpi):
        """Show message to restart Spyder since the DPI scale changed."""
        if not self.show_dpi_message:
            return

        if self.current_dpi != dpi:
            # Check the window state to not show the message if the window
            # is in fullscreen mode.
            window = self.window().windowHandle()
            if (window.windowState() == Qt.WindowFullScreen and
                    sys.platform == 'darwin'):
                return

            self.dpi_change_dismiss_box = QCheckBox(
                _("Hide this message during the current session"),
                self
            )

            msgbox = QMessageBox(self)
            msgbox.setIcon(QMessageBox.Warning)
            msgbox.setText(
                _
                ("A monitor scale change was detected. <br><br>"
                 "We recommend restarting Spyder to ensure that it's properly "
                 "displayed. If you don't want to do that, please be sure to "
                 "activate the option<br><br><tt>Enable auto high DPI scaling"
                 "</tt><br><br>in <tt>Preferences > Application > "
                 "Interface</tt>, in case Spyder is not displayed "
                 "correctly.<br><br>"
                 "Do you want to restart Spyder?"))
            msgbox.addButton(_('Restart now'), QMessageBox.NoRole)
            dismiss_button = msgbox.addButton(
                _('Dismiss'), QMessageBox.NoRole)
            msgbox.setCheckBox(self.dpi_change_dismiss_box)
            msgbox.setDefaultButton(dismiss_button)
            msgbox.finished.connect(
                lambda result: self.handle_dpi_change_response(result, dpi))
            msgbox.open()

    def set_window_title(self):
        """Set window title."""
        if DEV is not None:
            title = u"Spyder %s (Python %s.%s)" % (__version__,
                                                   sys.version_info[0],
                                                   sys.version_info[1])
        elif running_in_mac_app() or is_pynsist():
            title = "Spyder"
        else:
            title = u"Spyder (Python %s.%s)" % (sys.version_info[0],
                                                sys.version_info[1])

        if get_debug_level():
            title += u" [DEBUG MODE %d]" % get_debug_level()

        if self.window_title is not None:
            title += u' -- ' + to_text_string(self.window_title)

        if self.projects is not None:
            path = self.projects.get_active_project_path()
            if path:
                path = path.replace(get_home_dir(), u'~')
                title = u'{0} - {1}'.format(path, title)

        self.base_title = title
        self.setWindowTitle(self.base_title)

    # TODO: To be removed after all actions are moved to their corresponding
    # plugins
    def register_shortcut(self, qaction_or_qshortcut, context, name,
                          add_shortcut_to_tip=True, plugin_name=None):
        self.shortcuts.register_shortcut(
            qaction_or_qshortcut,
            context,
            name,
            add_shortcut_to_tip=add_shortcut_to_tip,
            plugin_name=plugin_name,
        )

    # --- Other
    def valid_project(self):
        """Handle an invalid active project."""
        try:
            path = self.projects.get_active_project_path()
        except AttributeError:
            return

        if bool(path):
            if not self.projects.is_valid_project(path):
                if path:
                    QMessageBox.critical(
                        self,
                        _('Error'),
                        _("<b>{}</b> is no longer a valid Spyder project! "
                          "Since it is the current active project, it will "
                          "be closed automatically.").format(path))
                self.projects.close_project()

    def update_source_menu(self):
        """Update source menu options that vary dynamically."""
        # This is necessary to avoid an error at startup.
        # Fixes spyder-ide/spyder#14901
        try:
            self.editor.refresh_formatter_name()
        except AttributeError:
            pass

    def free_memory(self):
        """Free memory after event."""
        gc.collect()

    def plugin_focus_changed(self):
        """Focus has changed from one plugin to another"""
        self.update_edit_menu()
        self.update_search_menu()

    def show_shortcuts(self, menu):
        """Show action shortcuts in menu."""
        menu_actions = menu.actions()
        for action in menu_actions:
            if getattr(action, '_shown_shortcut', False):
                # This is a SpyderAction
                if action._shown_shortcut is not None:
                    action.setShortcut(action._shown_shortcut)
            elif action.menu() is not None:
                # This is submenu, so we need to call this again
                self.show_shortcuts(action.menu())
            else:
                # We don't need to do anything for other elements
                continue

    def hide_shortcuts(self, menu):
        """Hide action shortcuts in menu."""
        menu_actions = menu.actions()
        for action in menu_actions:
            if getattr(action, '_shown_shortcut', False):
                # This is a SpyderAction
                if action._shown_shortcut is not None:
                    action.setShortcut(QKeySequence())
            elif action.menu() is not None:
                # This is submenu, so we need to call this again
                self.hide_shortcuts(action.menu())
            else:
                # We don't need to do anything for other elements
                continue

    def hide_options_menus(self):
        """Hide options menu when menubar is pressed in macOS."""
        for plugin in self.widgetlist + self.thirdparty_plugins:
            if plugin.CONF_SECTION == 'editor':
                editorstack = self.editor.get_current_editorstack()
                editorstack.menu.hide()
            else:
                try:
                    # New API
                    plugin.options_menu.hide()
                except AttributeError:
                    # Old API
                    plugin._options_menu.hide()

    def get_focus_widget_properties(self):
        """Get properties of focus widget
        Returns tuple (widget, properties) where properties is a tuple of
        booleans: (is_console, not_readonly, readwrite_editor)"""
        from spyder.plugins.editor.widgets.base import TextEditBaseWidget
        from spyder.plugins.ipythonconsole.widgets import ControlWidget
        widget = QApplication.focusWidget()

        textedit_properties = None
        if isinstance(widget, (TextEditBaseWidget, ControlWidget)):
            console = isinstance(widget, ControlWidget)
            not_readonly = not widget.isReadOnly()
            readwrite_editor = not_readonly and not console
            textedit_properties = (console, not_readonly, readwrite_editor)
        return widget, textedit_properties

    def update_edit_menu(self):
        """Update edit menu"""
        widget, textedit_properties = self.get_focus_widget_properties()
        if textedit_properties is None: # widget is not an editor/console
            return
        # !!! Below this line, widget is expected to be a QPlainTextEdit
        #     instance
        console, not_readonly, readwrite_editor = textedit_properties

        # Editor has focus and there is no file opened in it
        if (not console and not_readonly and self.editor
                and not self.editor.is_file_opened()):
            return

        # Disabling all actions to begin with
        for child in self.edit_menu.actions():
            child.setEnabled(False)

        self.selectall_action.setEnabled(True)

        # Undo, redo
        self.undo_action.setEnabled( readwrite_editor \
                                     and widget.document().isUndoAvailable() )
        self.redo_action.setEnabled( readwrite_editor \
                                     and widget.document().isRedoAvailable() )

        # Copy, cut, paste, delete
        has_selection = widget.has_selected_text()
        self.copy_action.setEnabled(has_selection)
        self.cut_action.setEnabled(has_selection and not_readonly)
        self.paste_action.setEnabled(not_readonly)

        # Comment, uncomment, indent, unindent...
        if not console and not_readonly:
            # This is the editor and current file is writable
            if self.editor:
                for action in self.editor.edit_menu_actions:
                    action.setEnabled(True)

    def update_search_menu(self):
        """Update search menu"""
        # Disabling all actions except the last one
        # (which is Find in files) to begin with
        for child in self.search_menu.actions()[:-1]:
            child.setEnabled(False)

        widget, textedit_properties = self.get_focus_widget_properties()
        if textedit_properties is None: # widget is not an editor/console
            return

        # !!! Below this line, widget is expected to be a QPlainTextEdit
        #     instance
        console, not_readonly, readwrite_editor = textedit_properties

        # Find actions only trigger an effect in the Editor
        if not console:
            for action in self.search_menu.actions():
                try:
                    action.setEnabled(True)
                except RuntimeError:
                    pass

        # Disable the replace action for read-only files
        if len(self.search_menu_actions) > 3:
            self.search_menu_actions[3].setEnabled(readwrite_editor)

    def createPopupMenu(self):
        return self.application.get_application_context_menu(parent=self)

    def set_splash(self, message):
        """Set splash message"""
        if self.splash is None:
            return
        if message:
            logger.info(message)
        self.splash.show()
        self.splash.showMessage(message,
                                int(Qt.AlignBottom | Qt.AlignCenter |
                                    Qt.AlignAbsolute),
                                QColor(Qt.white))
        QApplication.processEvents()

    def closeEvent(self, event):
        """closeEvent reimplementation"""
        if self.closing(True):
            event.accept()
        else:
            event.ignore()

    def resizeEvent(self, event):
        """Reimplement Qt method"""
        if not self.isMaximized() and not self.layouts.get_fullscreen_flag():
            self.window_size = self.size()
        QMainWindow.resizeEvent(self, event)

        # To be used by the tour to be able to resize
        self.sig_resized.emit(event)

    def moveEvent(self, event):
        """Reimplement Qt method"""
        if not self.isMaximized() and not self.layouts.get_fullscreen_flag():
            self.window_position = self.pos()
        QMainWindow.moveEvent(self, event)

        # To be used by the tour to be able to move
        self.sig_moved.emit(event)

    def hideEvent(self, event):
        """Reimplement Qt method"""
        try:
            for plugin in (self.widgetlist + self.thirdparty_plugins):
                # TODO: Remove old API
                try:
                    # New API
                    if plugin.get_widget().isAncestorOf(
                            self.last_focused_widget):
                        plugin.change_visibility(True)
                except AttributeError:
                    # Old API
                    if plugin.isAncestorOf(self.last_focused_widget):
                        plugin._visibility_changed(True)

            QMainWindow.hideEvent(self, event)
        except RuntimeError:
            QMainWindow.hideEvent(self, event)

    def change_last_focused_widget(self, old, now):
        """To keep track of to the last focused widget"""
        if (now is None and QApplication.activeWindow() is not None):
            QApplication.activeWindow().setFocus()
            self.last_focused_widget = QApplication.focusWidget()
        elif now is not None:
            self.last_focused_widget = now

        self.previous_focused_widget =  old

    def closing(self, cancelable=False):
        """Exit tasks"""
        if self.already_closed or self.is_starting_up:
            return True

        if cancelable and CONF.get('main', 'prompt_on_exit'):
            reply = QMessageBox.critical(self, 'Spyder',
                                         'Do you really want to exit?',
                                         QMessageBox.Yes, QMessageBox.No)
            if reply == QMessageBox.No:
                return False

        if CONF.get('main', 'single_instance') and self.open_files_server:
            self.open_files_server.close()

        # Internal plugins
        for plugin in (self.widgetlist + self.thirdparty_plugins):
            # New API
            try:
                if isinstance(plugin, SpyderDockablePlugin):
                    plugin.close_window()
                if not plugin.on_close(cancelable):
                    return False
            except AttributeError:
                pass

            # Old API
            try:
                plugin._close_window()
                if not plugin.closing_plugin(cancelable):
                    return False
            except AttributeError:
                pass

        # New API: External plugins
        for plugin_name, plugin in self._EXTERNAL_PLUGINS.items():
            try:
                if isinstance(plugin, SpyderDockablePlugin):
                    plugin.close_window()

                if not plugin.on_close(cancelable):
                    return False
            except AttributeError as e:
                logger.error(str(e))

        # Save window settings *after* closing all plugin windows, in order
        # to show them in their previous locations in the next session.
        # Fixes spyder-ide/spyder#12139
        prefix = 'window' + '/'
        self.layouts.save_current_window_settings(prefix)

        self.already_closed = True
        return True

    def add_dockwidget(self, plugin):
        """
        Add a plugin QDockWidget to the main window.
        """
        try:
            # New API
            if plugin.is_compatible:
                dockwidget, location = plugin.create_dockwidget(self)
                self.addDockWidget(location, dockwidget)
                self.widgetlist.append(plugin)
        except AttributeError:
            # Old API
            if plugin._is_compatible:
                dockwidget, location = plugin._create_dockwidget()
                self.addDockWidget(location, dockwidget)
                self.widgetlist.append(plugin)

    @Slot()
    def global_callback(self):
        """Global callback"""
        widget = QApplication.focusWidget()
        action = self.sender()
        callback = from_qvariant(action.data(), to_text_string)
        from spyder.plugins.editor.widgets.base import TextEditBaseWidget
        from spyder.plugins.ipythonconsole.widgets import ControlWidget

        if isinstance(widget, (TextEditBaseWidget, ControlWidget)):
            getattr(widget, callback)()
        else:
            return

    def redirect_internalshell_stdio(self, state):
        if state:
            self.console.redirect_stds()
        else:
            self.console.restore_stds()

    def open_external_console(self, fname, wdir, args, interact, debug, python,
                              python_args, systerm, post_mortem=False):
        """Open external console"""
        if systerm:
            # Running script in an external system terminal
            try:
                if CONF.get('main_interpreter', 'default'):
                    executable = get_python_executable()
                else:
                    executable = CONF.get('main_interpreter', 'executable')
                programs.run_python_script_in_terminal(
                        fname, wdir, args, interact, debug, python_args,
                        executable)
            except NotImplementedError:
                QMessageBox.critical(self, _("Run"),
                                     _("Running an external system terminal "
                                       "is not supported on platform %s."
                                       ) % os.name)

    def open_file(self, fname, external=False):
        """
        Open filename with the appropriate application
        Redirect to the right widget (txt -> editor, spydata -> workspace, ...)
        or open file outside Spyder (if extension is not supported)
        """
        fname = to_text_string(fname)
        ext = osp.splitext(fname)[1]
        if encoding.is_text_file(fname):
            self.editor.load(fname)
        elif self.variableexplorer is not None and ext in IMPORT_EXT:
            self.variableexplorer.import_data(fname)
        elif not external:
            fname = file_uri(fname)
            start_file(fname)

    def open_external_file(self, fname):
        """
        Open external files that can be handled either by the Editor or the
        variable explorer inside Spyder.
        """
        # Check that file exists
        fname = encoding.to_unicode_from_fs(fname)
        if osp.exists(osp.join(CWD, fname)):
            fpath = osp.join(CWD, fname)
        elif osp.exists(fname):
            fpath = fname
        else:
            return

        # Don't open script that starts Spyder at startup.
        # Fixes issue spyder-ide/spyder#14483
        if sys.platform == 'darwin' and 'bin/spyder' in fname:
            return

        if osp.isfile(fpath):
            self.open_file(fpath, external=True)
        elif osp.isdir(fpath):
            QMessageBox.warning(
                self, _("Error"),
                _('To open <code>{fpath}</code> as a project with Spyder, '
                  'please use <code>spyder -p "{fname}"</code>.')
                .format(fpath=osp.normpath(fpath), fname=fname)
            )

    # --- Path Manager
    # ------------------------------------------------------------------------
    def load_python_path(self):
        """Load path stored in Spyder configuration folder."""
        if osp.isfile(self.SPYDER_PATH):
            with open(self.SPYDER_PATH, 'r', encoding='utf-8') as f:
                path = f.read().splitlines()
            self.path = tuple(name for name in path if osp.isdir(name))

        if osp.isfile(self.SPYDER_NOT_ACTIVE_PATH):
            with open(self.SPYDER_NOT_ACTIVE_PATH, 'r',
                      encoding='utf-8') as f:
                not_active_path = f.read().splitlines()
            self.not_active_path = tuple(name for name in not_active_path
                                         if osp.isdir(name))

    def save_python_path(self, new_path_dict):
        """
        Save path in Spyder configuration folder.

        `new_path_dict` is an OrderedDict that has the new paths as keys and
        the state as values. The state is `True` for active and `False` for
        inactive.
        """
        path = [p for p in new_path_dict]
        not_active_path = [p for p in new_path_dict if not new_path_dict[p]]
        try:
            encoding.writelines(path, self.SPYDER_PATH)
            encoding.writelines(not_active_path, self.SPYDER_NOT_ACTIVE_PATH)
        except EnvironmentError as e:
            logger.error(str(e))
        CONF.set('main', 'spyder_pythonpath', self.get_spyder_pythonpath())

    def get_spyder_pythonpath_dict(self):
        """
        Return Spyder PYTHONPATH.

        The returned ordered dictionary has the paths as keys and the state
        as values. The state is `True` for active and `False` for inactive.

        Example:
            OrderedDict([('/some/path, True), ('/some/other/path, False)])
        """
        self.load_python_path()

        path_dict = OrderedDict()
        for path in self.path:
            path_dict[path] = path not in self.not_active_path

        for path in self.project_path:
            path_dict[path] = True

        return path_dict

    def get_spyder_pythonpath(self):
        """
        Return Spyder PYTHONPATH.
        """
        path_dict = self.get_spyder_pythonpath_dict()
        path = [k for k, v in path_dict.items() if v]
        return path

    def update_python_path(self, new_path_dict):
        """Update python path on Spyder interpreter and kernels."""
        # Load previous path
        path_dict = self.get_spyder_pythonpath_dict()

        # Save path
        if path_dict != new_path_dict:
            # It doesn't include the project_path
            self.save_python_path(new_path_dict)

        # Load new path
        new_path_dict_p = self.get_spyder_pythonpath_dict()  # Includes project

        # Update Spyder interpreter
        for path in path_dict:
            while path in sys.path:
                sys.path.remove(path)

        for path, active in reversed(new_path_dict_p.items()):
            if active:
                sys.path.insert(1, path)

        # Any plugin that needs to do some work based on this signal should
        # connect to it on plugin registration
        self.sig_pythonpath_changed.emit(path_dict, new_path_dict_p)

    @Slot()
    def show_path_manager(self):
        """Show path manager dialog."""
        from spyder.widgets.pathmanager import PathManager
        read_only_path = tuple(self.projects.get_pythonpath())
        dialog = PathManager(self, self.path, read_only_path,
                             self.not_active_path, sync=True)
        self._path_manager = dialog
        dialog.sig_path_changed.connect(self.update_python_path)
        dialog.redirect_stdio.connect(self.redirect_internalshell_stdio)
        dialog.show()

    def pythonpath_changed(self):
        """Project's PYTHONPATH contribution has changed."""
        self.project_path = tuple(self.projects.get_pythonpath())
        path_dict = self.get_spyder_pythonpath_dict()
        self.update_python_path(path_dict)

    #---- Preferences
    def apply_settings(self):
        """Apply main window settings."""
        qapp = QApplication.instance()

        # Set 'gtk+' as the default theme in Gtk-based desktops
        # Fixes spyder-ide/spyder#2036.
        if is_gtk_desktop() and ('GTK+' in QStyleFactory.keys()):
            try:
                qapp.setStyle('gtk+')
            except:
                pass

        default = self.DOCKOPTIONS
        if CONF.get('main', 'vertical_tabs'):
            default = default|QMainWindow.VerticalTabs
        self.setDockOptions(default)

        self.apply_panes_settings()

        if CONF.get('main', 'use_custom_cursor_blinking'):
            qapp.setCursorFlashTime(
                CONF.get('main', 'custom_cursor_blinking'))
        else:
            qapp.setCursorFlashTime(self.CURSORBLINK_OSDEFAULT)

    def apply_panes_settings(self):
        """Update dockwidgets features settings."""
        for plugin in (self.widgetlist + self.thirdparty_plugins):
            features = plugin.dockwidget.FEATURES

            plugin.dockwidget.setFeatures(features)

            try:
                # New API
                margin = 0
                if CONF.get('main', 'use_custom_margin'):
                    margin = CONF.get('main', 'custom_margin')
                plugin.update_margins(margin)
            except AttributeError:
                # Old API
                plugin._update_margins()

    @Slot()
    def show_preferences(self):
        """Edit Spyder preferences."""
        self.preferences.open_dialog(self.prefs_dialog_size)

    def set_prefs_size(self, size):
        """Save preferences dialog size."""
        self.prefs_dialog_size = size

    # -- Open files server
    def start_open_files_server(self):
        self.open_files_server.setsockopt(socket.SOL_SOCKET,
                                          socket.SO_REUSEADDR, 1)
        port = select_port(default_port=OPEN_FILES_PORT)
        CONF.set('main', 'open_files_port', port)
        self.open_files_server.bind(('127.0.0.1', port))
        self.open_files_server.listen(20)
        while 1:  # 1 is faster than True
            try:
                req, dummy = self.open_files_server.accept()
            except socket.error as e:
                # See spyder-ide/spyder#1275 for details on why errno EINTR is
                # silently ignored here.
                eintr = errno.WSAEINTR if os.name == 'nt' else errno.EINTR
                # To avoid a traceback after closing on Windows
                if e.args[0] == eintr:
                    continue
                # handle a connection abort on close error
                enotsock = (errno.WSAENOTSOCK if os.name == 'nt'
                            else errno.ENOTSOCK)
                if e.args[0] in [errno.ECONNABORTED, enotsock]:
                    return
                raise
            fname = req.recv(1024)
            fname = fname.decode('utf-8')
            self.sig_open_external_file.emit(fname)
            req.sendall(b' ')

    # ---- Quit and restart, and reset spyder defaults
    @Slot()
    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)

    @Slot()
    def restart(self, reset=False):
        """Wrapper to handle plugins request to restart Spyder."""
        self.application.restart(reset=reset)

    # ---- Interactive Tours
    def show_tour(self, index):
        """Show interactive tour."""
        self.layouts.maximize_dockwidget(restore=True)
        frames = self.tours_available[index]
        self.tour.set_tour(index, frames, self)
        self.tour.start_tour()

    # ---- Global Switcher
    def open_switcher(self, symbol=False):
        """Open switcher dialog box."""
        if self.switcher is not None and self.switcher.isVisible():
            self.switcher.clear()
            self.switcher.hide()
            return
        if symbol:
            self.switcher.set_search_text('@')
        else:
            self.switcher.set_search_text('')
            self.switcher.setup()
        self.switcher.show()

        # Note: The +6 pixel on the top makes it look better
        # FIXME: Why is this using the toolbars menu? A: To not be on top of
        # the toolbars.
        # Probably toolbars should be taken into account for this 'delta' only
        # when are visible
        delta_top = (self.toolbar.toolbars_menu.geometry().height() +
                     self.menuBar().geometry().height() + 6)

        self.switcher.set_position(delta_top)

    def open_symbolfinder(self):
        """Open symbol list management dialog box."""
        self.open_switcher(symbol=True)

    def create_switcher(self):
        """Create switcher dialog instance."""
        if self.switcher is None:
            from spyder.widgets.switcher import Switcher
            self.switcher = Switcher(self)

        return self.switcher

    @Slot()
    def show_tour_message(self, force=False):
        """
        Show message about starting the tour the first time Spyder starts.
        """
        should_show_tour = CONF.get('main', 'show_tour_message')
        if force or (should_show_tour and not running_under_pytest()
                     and not get_safe_mode()):
            CONF.set('main', 'show_tour_message', False)
            self.tour_dialog = tour.OpenTourDialog(
                self, lambda: self.show_tour(DEFAULT_TOUR))
            self.tour_dialog.show()

    # --- For OpenGL
    def _test_setting_opengl(self, option):
        """Get the current OpenGL implementation in use"""
        if option == 'software':
            return QCoreApplication.testAttribute(Qt.AA_UseSoftwareOpenGL)
        elif option == 'desktop':
            return QCoreApplication.testAttribute(Qt.AA_UseDesktopOpenGL)
        elif option == 'gles':
            return QCoreApplication.testAttribute(Qt.AA_UseOpenGLES)


#==============================================================================
# Utilities for the 'main' function below
#==============================================================================
def create_application():
    """Create application and patch sys.exit."""
    # Our QApplication
    app = qapplication()

    # --- Set application icon
    app_icon = QIcon(get_image_path("spyder"))
    app.setWindowIcon(app_icon)

    # Required for correct icon on GNOME/Wayland:
    if hasattr(app, 'setDesktopFileName'):
        app.setDesktopFileName('spyder')

    #----Monkey patching QApplication
    class FakeQApplication(QApplication):
        """Spyder's fake QApplication"""
        def __init__(self, args):
            self = app  # analysis:ignore
        @staticmethod
        def exec_():
            """Do nothing because the Qt mainloop is already running"""
            pass
    from qtpy import QtWidgets
    QtWidgets.QApplication = FakeQApplication

    # ----Monkey patching sys.exit
    def fake_sys_exit(arg=[]):
        pass
    sys.exit = fake_sys_exit

    # ----Monkey patching sys.excepthook to avoid crashes in PyQt 5.5+
    def spy_excepthook(type_, value, tback):
        sys.__excepthook__(type_, value, tback)
    sys.excepthook = spy_excepthook

    # Removing arguments from sys.argv as in standard Python interpreter
    sys.argv = ['']

    return app


def create_window(app, splash, options, args):
    """
    Create and show Spyder's main window and start QApplication event loop.
    """
    # Main window
    main = MainWindow(splash, options)
    try:
        main.setup()
    except BaseException:
        if main.console is not None:
            try:
                main.console.exit_interpreter()
            except BaseException:
                pass
        raise

    main.pre_visible_setup()
    main.show()
    main.post_visible_setup()

    if main.console:
        namespace = CONF.get('internal_console', 'namespace', {})
        main.console.start_interpreter(namespace)
        main.console.set_namespace_item('spy', Spy(app=app, window=main))

    # Propagate current configurations to all configuration observers
    CONF.notify_all_observers()

    # Don't show icons in menus for Mac
    if sys.platform == 'darwin':
        QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, True)

    # Open external files with our Mac app
    if running_in_mac_app():
        app.sig_open_external_file.connect(main.open_external_file)
        app._has_started = True
        if hasattr(app, '_pending_file_open'):
            if args:
                args = app._pending_file_open + args
            else:
                args = app._pending_file_open


    # Open external files passed as args
    if args:
        for a in args:
            main.open_external_file(a)

    # To give focus again to the last focused widget after restoring
    # the window
    app.focusChanged.connect(main.change_last_focused_widget)

    if not running_under_pytest():
        app.exec_()
    return main


#==============================================================================
# Main
#==============================================================================
def main(options, args):
    """Main function"""
    # **** For Pytest ****
    if running_under_pytest():
        if CONF.get('main', 'opengl') != 'automatic':
            option = CONF.get('main', 'opengl')
            set_opengl_implementation(option)

        app = create_application()
        window = create_window(app, None, options, None)
        return window

    # **** Handle hide_console option ****
    if options.show_console:
        print("(Deprecated) --show console does nothing, now the default "
              " behavior is to show the console, use --hide-console if you "
              "want to hide it")

    if set_attached_console_visible is not None:
        set_attached_console_visible(not options.hide_console
                                     or options.reset_config_files
                                     or options.reset_to_defaults
                                     or options.optimize
                                     or bool(get_debug_level()))

    # **** Set OpenGL implementation to use ****
    # This attribute must be set before creating the application.
    # See spyder-ide/spyder#11227
    if options.opengl_implementation:
        option = options.opengl_implementation
        set_opengl_implementation(option)
    else:
        if CONF.get('main', 'opengl') != 'automatic':
            option = CONF.get('main', 'opengl')
            set_opengl_implementation(option)

    # **** Set high DPI scaling ****
    # This attribute must be set before creating the application.
    if hasattr(Qt, 'AA_EnableHighDpiScaling'):
        QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling,
                                      CONF.get('main', 'high_dpi_scaling'))

    # **** Set debugging info ****
    setup_logging(options)

    # **** Create the application ****
    app = create_application()

    # **** Create splash screen ****
    splash = create_splash_screen()
    if splash is not None:
        splash.show()
        splash.showMessage(
            _("Initializing..."),
            int(Qt.AlignBottom | Qt.AlignCenter | Qt.AlignAbsolute),
            QColor(Qt.white)
        )
        QApplication.processEvents()

    if options.reset_to_defaults:
        # Reset Spyder settings to defaults
        CONF.reset_to_defaults()
        return
    elif options.optimize:
        # Optimize the whole Spyder's source code directory
        import spyder
        programs.run_python_script(module="compileall",
                                   args=[spyder.__path__[0]], p_args=['-O'])
        return

    # **** Read faulthandler log file ****
    faulthandler_file = get_conf_path('faulthandler.log')
    previous_crash = ''
    if osp.exists(faulthandler_file):
        with open(faulthandler_file, 'r') as f:
            previous_crash = f.read()

        # Remove file to not pick it up for next time.
        try:
            dst = get_conf_path('faulthandler.log.old')
            shutil.move(faulthandler_file, dst)
        except Exception:
            pass
    CONF.set('main', 'previous_crash', previous_crash)

    # **** Set color for links ****
    set_links_color(app)

    # **** Create main window ****
    mainwindow = None
    try:
        if PY3 and options.report_segfault:
            import faulthandler
            with open(faulthandler_file, 'w') as f:
                faulthandler.enable(file=f)
                mainwindow = create_window(app, splash, options, args)
        else:
            mainwindow = create_window(app, splash, options, args)
    except FontError:
        QMessageBox.information(None, "Spyder",
                "Spyder was unable to load the <i>Spyder 3</i> "
                "icon theme. That's why it's going to fallback to the "
                "theme used in Spyder 2.<br><br>"
                "For that, please close this window and start Spyder again.")
        CONF.set('appearance', 'icon_theme', 'spyder 2')
    if mainwindow is None:
        # An exception occurred
        if splash is not None:
            splash.hide()
        return

    ORIGINAL_SYS_EXIT()


if __name__ == "__main__":
    main()
