/*
    SuperCollider Qt IDE
    Copyright (c) 2012 Jakob Leben & Tim Blechmann
    http://www.audiosynth.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/

#define QT_NO_DEBUG_OUTPUT

#include "cmd_line.hpp"
#include "doc_list.hpp"
#include "documents_dialog.hpp"
#include "find_replace_tool.hpp"
#include "goto_line_tool.hpp"
#include "lookup_dialog.hpp"
#include "main_window.hpp"
#include "multi_editor.hpp"
#include "popup_text_input.hpp"
#include "post_window.hpp"
#include "session_switch_dialog.hpp"
#include "sessions_dialog.hpp"
#include "tool_box.hpp"
#include "audio_status_box.hpp"
#include "lang_status_box.hpp"
#include "../core/main.hpp"
#include "../core/doc_manager.hpp"
#include "../core/session_manager.hpp"
#include "../core/sc_server.hpp"
#include "../core/util/standard_dirs.hpp"
#include "code_editor/sc_editor.hpp"
#include "settings/dialog.hpp"

#ifdef SC_USE_QTWEBENGINE
#    include "help_browser.hpp"
#endif // SC_USE_QTWEBENGINE

#include "QtCollider/hacks/hacks_qt.hpp"

#include "SC_Version.hpp"

#include <QAction>
#include <QApplication>
#include <QDesktopServices>
#include <QStandardPaths>
#include <QFileDialog>
#include <QFileInfo>
#include <QGridLayout>
#include <QInputDialog>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QPointer>
#include <QShortcut>
#include <QStatusBar>
#include <QVBoxLayout>
#include <QUrl>
#include <QMimeData>
#include <QMetaMethod>

namespace ScIDE {

static QWidget* findFirstResponder(QWidget* widget, const char* methodSignature, int& methodIndex) {
    methodIndex = -1;
    while (widget) {
        methodIndex = widget->metaObject()->indexOfMethod(methodSignature);
        if (methodIndex != -1)
            break;
        if (widget->isWindow())
            break;
        widget = widget->parentWidget();
    }
    return widget;
}

static void invokeMethodOnFirstResponder(QByteArray const& signature) {
    int methodIdx = -1;
    QWidget* widget = findFirstResponder(QApplication::focusWidget(), signature.constData(), methodIdx);
    if (widget && methodIdx != -1)
        widget->metaObject()->method(methodIdx).invoke(widget, Qt::DirectConnection);
}

MainWindow* MainWindow::mInstance = 0;

MainWindow::MainWindow(Main* main): mMain(main), mClockLabel(0), mDocDialog(0) {
    Q_ASSERT(!mInstance);
    mInstance = this;

    setAcceptDrops(true);

    // Construct status bar:

    mLangStatus = new LangStatusBox(main->scProcess());
    mServerStatus = new AudioStatusBox(main->scServer());

    mStatusBar = statusBar();
    mStatusBar->addPermanentWidget(new QLabel(tr("Interpreter:")));
    mStatusBar->addPermanentWidget(mLangStatus);
    mStatusBar->addPermanentWidget(new QLabel(tr("Server:")));
    mStatusBar->addPermanentWidget(mServerStatus);

    // Code editor
    mEditors = new MultiEditor(main);

    // Tools

    mCmdLine = new CmdLine(tr("Command Line:"));
    connect(mCmdLine, SIGNAL(invoked(QString, bool)), main->scProcess(), SLOT(evaluateCode(QString, bool)));

    mFindReplaceTool = new TextFindReplacePanel;

    mGoToLineTool = new GoToLineTool();
    connect(mGoToLineTool, SIGNAL(activated(int)), this, SLOT(hideToolBox()));

    mToolBox = new ToolBox;
    mToolBox->addWidget(mCmdLine);
    mToolBox->addWidget(mFindReplaceTool);
    mToolBox->addWidget(mGoToLineTool);
    mToolBox->hide();

    // Docks
    mDocumentsDocklet = new DocumentsDocklet(main->documentManager(), this);
    mDocumentsDocklet->setObjectName("documents-dock");
    addDockWidget(Qt::LeftDockWidgetArea, mDocumentsDocklet->dockWidget());
    mDocumentsDocklet->hide();

#ifdef SC_USE_QTWEBENGINE
    mHelpBrowserDocklet = new HelpBrowserDocklet(this);
    mHelpBrowserDocklet->setObjectName("help-dock");
    addDockWidget(Qt::RightDockWidgetArea, mHelpBrowserDocklet->dockWidget());
    // mHelpBrowserDockable->hide();
#endif // SC_USE_QTWEBENGINE

    mPostDocklet = new PostDocklet(this);
    mPostDocklet->setObjectName("post-dock");
    addDockWidget(Qt::RightDockWidgetArea, mPostDocklet->dockWidget());

    // Layout
    QVBoxLayout* center_box = new QVBoxLayout;
    center_box->setContentsMargins(0, 0, 0, 0);
    center_box->setSpacing(0);
    center_box->addWidget(mEditors);
    center_box->addWidget(mToolBox);

    QWidget* central = new QWidget;
    central->setLayout(center_box);
    setCentralWidget(central);

    // Session management
    connect(main->sessionManager(), SIGNAL(saveSessionRequest(Session*)), this, SLOT(saveSession(Session*)));
    connect(main->sessionManager(), SIGNAL(switchSessionRequest(Session*)), this, SLOT(switchSession(Session*)));
    connect(main->sessionManager(), SIGNAL(currentSessionNameChanged()), this, SLOT(updateWindowTitle()));
    // A system for easy evaluation of pre-defined code:
    connect(this, SIGNAL(evaluateCode(QString, bool)), main->scProcess(), SLOT(evaluateCode(QString, bool)));
    // Interpreter: post output
    connect(main->scProcess(), SIGNAL(scPost(QString)), mPostDocklet->mPostWindow, SLOT(post(QString)));
    // Interpreter: monitor running state
    connect(main->scProcess(), SIGNAL(stateChanged(QProcess::ProcessState)), this,
            SLOT(onInterpreterStateChanged(QProcess::ProcessState)));
    // Interpreter: forward status messages
    connect(main->scProcess(), SIGNAL(statusMessage(const QString&)), this, SLOT(showStatusMessage(const QString&)));

    // Document list interaction
    connect(mDocumentsDocklet->list(), SIGNAL(clicked(Document*)), mEditors, SLOT(setCurrent(Document*)));
    connect(mEditors, SIGNAL(currentDocumentChanged(Document*)), mDocumentsDocklet->list(), SLOT(setCurrent(Document*)),
            Qt::QueuedConnection);
    connect(mDocumentsDocklet->list(), SIGNAL(updateTabsOrder(QList<Document*>)), mEditors,
            SLOT(updateTabsOrder(QList<Document*>)));
    connect(mEditors, SIGNAL(updateDockletOrder(int, int)), mDocumentsDocklet->list(),
            SLOT(updateDockletOrder(int, int)), Qt::QueuedConnection);

    // Update actions on document change
    connect(mEditors, SIGNAL(currentDocumentChanged(Document*)), this, SLOT(onCurrentDocumentChanged(Document*)));
    // Document management
    DocumentManager* docMng = main->documentManager();
    connect(docMng, SIGNAL(changedExternally(Document*)), this, SLOT(onDocumentChangedExternally(Document*)));
    connect(docMng, SIGNAL(recentsChanged()), this, SLOT(updateRecentDocsMenu()));
    connect(docMng, SIGNAL(saved(Document*)), this, SLOT(updateWindowTitle()));
    connect(docMng, SIGNAL(titleChanged(Document*)), this, SLOT(updateWindowTitle()));

    connect(main, SIGNAL(applySettingsRequest(Settings::Manager*)), this, SLOT(applySettings(Settings::Manager*)));
    connect(main, SIGNAL(storeSettingsRequest(Settings::Manager*)), this, SLOT(storeSettings(Settings::Manager*)));

    // ToolBox
    connect(mToolBox->closeButton(), SIGNAL(clicked()), this, SLOT(hideToolBox()));

    createActions();
    createMenus();

    // Must be called after createAtions(), because it accesses an action:
    toggleInterpreterActions(false);

    // Initialize recent documents menu
    updateRecentDocsMenu();

    QIcon icon;
    // Unfortunately, the SVG icon shows up as a tiny dot on some Linux window
    // managers (see #3905, #2646). Best we can do here is PNGs.
    // icon.addFile(":icons/sc-ide-svg");
    icon.addFile(":icons/sc-ide-16");
    icon.addFile(":icons/sc-ide-24");
    icon.addFile(":icons/sc-ide-32");
    icon.addFile(":icons/sc-ide-48");
    icon.addFile(":icons/sc-ide-64");
    icon.addFile(":icons/sc-ide-128");
    icon.addFile(":icons/sc-ide-256");
    icon.addFile(":icons/sc-ide-512");
    icon.addFile(":icons/sc-ide-1024");
    QApplication::setWindowIcon(icon);

    updateWindowTitle();

    applyCursorBlinkingSettings(main->settings());

    // Custom event handling:
    qApp->installEventFilter(this);
}

void MainWindow::createActions() {
    Settings::Manager* settings = mMain->settings();

    QAction* action;
    const QString ideCategory("IDE");
    const QString editorCategory(tr("Text Editor"));
    const QString helpCategory(tr("Help"));

    // File
    mActions[Quit] = action = new QAction(QIcon::fromTheme("application-exit"), tr("&Quit..."), this);
    action->setShortcut(tr("Ctrl+Q", "Quit application"));
    action->setStatusTip(tr("Quit SuperCollider IDE"));
    // explicitly states that this action can be triggered by macOS QUIT events
    // (such as cmd+q or window closing)
    action->setMenuRole(QAction::QuitRole);

    QObject::connect(action, &QAction::triggered, this, &QWidget::close, Qt::QueuedConnection);
    settings->addAction(action, "ide-quit", ideCategory);

    mActions[DocNew] = action = new QAction(QIcon::fromTheme("document-new"), tr("&New"), this);
    action->setShortcut(tr("Ctrl+N", "New document"));
    action->setStatusTip(tr("Create a new document"));
    connect(action, SIGNAL(triggered()), this, SLOT(newDocument()));
    settings->addAction(action, "ide-document-new", ideCategory);

    mActions[DocOpen] = action = new QAction(QIcon::fromTheme("document-open"), tr("&Open..."), this);
    action->setShortcut(tr("Ctrl+O", "Open document"));
    action->setStatusTip(tr("Open an existing file"));
    connect(action, SIGNAL(triggered()), this, SLOT(openDocument()));
    settings->addAction(action, "ide-document-open", ideCategory);

    mActions[DocOpenStartup] = action = new QAction(QIcon::fromTheme("document-open"), tr("Open startup file"), this);
    action->setStatusTip(tr("Open startup file"));
    connect(action, SIGNAL(triggered()), this, SLOT(openStartupFile()));
    settings->addAction(action, "ide-document-open-startup", ideCategory);

    mActions[DocOpenSupportDir] = action =
        new QAction(QIcon::fromTheme("document-open"), tr("Open user support directory"), this);
    action->setStatusTip(tr("Open user support directory"));
    connect(action, SIGNAL(triggered()), this, SLOT(openUserSupportDirectory()));
    settings->addAction(action, "ide-document-open-support-directory", ideCategory);

    mActions[DocSave] = action = new QAction(QIcon::fromTheme("document-save"), tr("&Save"), this);
    action->setShortcut(tr("Ctrl+S", "Save document"));
    action->setStatusTip(tr("Save the current document"));
    connect(action, SIGNAL(triggered()), this, SLOT(saveDocument()));
    settings->addAction(action, "ide-document-save", ideCategory);

    mActions[DocSaveAs] = action = new QAction(QIcon::fromTheme("document-save-as"), tr("Save &As..."), this);
    action->setShortcut(tr("Ctrl+Shift+S", "Save &As..."));
    action->setStatusTip(tr("Save the current document into a different file"));
    connect(action, SIGNAL(triggered()), this, SLOT(saveDocumentAs()));
    settings->addAction(action, "ide-document-save-as", ideCategory);

    mActions[DocSaveAsExtension] = action =
        new QAction(QIcon::fromTheme("document-save-as"), tr("Save As Extension..."), this);
    action->setStatusTip(tr("Save the current document into a different file in the extensions folder"));
    connect(action, SIGNAL(triggered()), this, SLOT(saveDocumentAsExtension()));
    settings->addAction(action, "ide-document-save-as-extension", ideCategory);

    mActions[DocSaveAll] = action = new QAction(QIcon::fromTheme("document-save"), tr("Save All..."), this);
    action->setShortcut(tr("Ctrl+Alt+S", "Save all documents"));
    action->setStatusTip(tr("Save all open documents"));
    connect(action, SIGNAL(triggered()), this, SLOT(saveAllDocuments()));
    settings->addAction(action, "ide-document-save-all", ideCategory);

    mActions[DocCloseAll] = action = new QAction(QIcon::fromTheme("window-close"), tr("Close All..."), this);
    action->setShortcut(tr("Ctrl+Shift+W", "Close all documents"));
    action->setStatusTip(tr("Close all documents"));
    connect(action, SIGNAL(triggered()), this, SLOT(closeAllDocuments()));
    settings->addAction(action, "ide-document-close-all", ideCategory);

    mActions[DocReload] = action = new QAction(QIcon::fromTheme("view-refresh"), tr("&Reload"), this);
    action->setShortcut(tr("F5", "Reload document"));
    action->setStatusTip(tr("Reload the current document"));
    connect(action, SIGNAL(triggered()), this, SLOT(reloadDocument()));
    settings->addAction(action, "ide-document-reload", ideCategory);

    mActions[ClearRecentDocs] = action = new QAction(tr("Clear", "Clear recent documents"), this);
    action->setStatusTip(tr("Clear list of recent documents"));
    connect(action, SIGNAL(triggered()), Main::instance()->documentManager(), SLOT(clearRecents()));
    settings->addAction(action, "ide-clear-recent-documents", ideCategory);

    // Sessions
    mActions[NewSession] = action = new QAction(QIcon::fromTheme("document-new"), tr("&New Session"), this);
    action->setStatusTip(tr("Open a new session"));
    connect(action, SIGNAL(triggered()), this, SLOT(newSession()));
    settings->addAction(action, "ide-session-new", ideCategory);

    mActions[SaveSessionAs] = action =
        new QAction(QIcon::fromTheme("document-save-as"), tr("Save Session &As..."), this);
    action->setStatusTip(tr("Save the current session with a different name"));
    connect(action, SIGNAL(triggered()), this, SLOT(saveCurrentSessionAs()));
    settings->addAction(action, "ide-session-save-as", ideCategory);

    mActions[ManageSessions] = action = new QAction(tr("&Manage Sessions..."), this);
    connect(action, SIGNAL(triggered()), this, SLOT(openSessionsDialog()));
    settings->addAction(action, "ide-session-manage", ideCategory);

    mActions[OpenSessionSwitchDialog] = action = new QAction(tr("&Switch Session..."), this);
    connect(action, SIGNAL(triggered()), this, SLOT(showSwitchSessionDialog()));
    action->setShortcut(tr("Ctrl+Shift+Q", "Switch Session"));
    settings->addAction(action, "ide-session-switch", ideCategory);

    // Edit
    mActions[Find] = action = new QAction(QIcon::fromTheme("edit-find"), tr("&Find..."), this);
    action->setShortcut(tr("Ctrl+F", "Find"));
    action->setStatusTip(tr("Find text in document"));
    connect(action, SIGNAL(triggered()), this, SLOT(showFindTool()));
    settings->addAction(action, "editor-find", editorCategory);

    mActions[Replace] = action = new QAction(QIcon::fromTheme("edit-replace"), tr("&Replace..."), this);
    action->setShortcut(tr("Ctrl+R", "Replace"));
    action->setStatusTip(tr("Find and replace text in document"));
    connect(action, SIGNAL(triggered()), this, SLOT(showReplaceTool()));
    settings->addAction(action, "editor-replace", editorCategory);

    // View
    mActions[ShowCmdLine] = action = new QAction(tr("&Command Line"), this);
    action->setStatusTip(tr("Command line for quick code evaluation"));
    action->setShortcut(tr("Ctrl+E", "Show command line"));
    connect(action, SIGNAL(triggered()), this, SLOT(showCmdLine()));
    settings->addAction(action, "ide-command-line-show", ideCategory);

    mActions[CmdLineForCursor] = action = new QAction(tr("&Command Line from selection"), this);
    action->setShortcut(tr("Ctrl+Shift+E", "Fill command line with current selection"));
    connect(action, SIGNAL(triggered()), this, SLOT(cmdLineForCursor()));
    settings->addAction(action, "ide-command-line-fill", ideCategory);


    mActions[ShowGoToLineTool] = action = new QAction(tr("&Go To Line"), this);
    action->setStatusTip(tr("Tool to jump to a line by number"));
    action->setShortcut(tr("Ctrl+L", "Show go-to-line tool"));
    connect(action, SIGNAL(triggered()), this, SLOT(showGoToLineTool()));
    settings->addAction(action, "editor-go-to-line", editorCategory);

    mActions[CloseToolBox] = action = new QAction(QIcon::fromTheme("window-close"), tr("&Close Tool Panel"), this);
    action->setStatusTip(tr("Close any open tool panel"));
    action->setShortcut(tr("Esc", "Close tool box"));
    connect(action, SIGNAL(triggered()), this, SLOT(hideToolBox()));
    settings->addAction(action, "ide-tool-panel-hide", ideCategory);

    mActions[ShowFullScreen] = action = new QAction(tr("&Full Screen"), this);
    action->setCheckable(false);
    action->setShortcut(tr("Ctrl+Shift+F", "Show ScIDE in Full Screen"));
    connect(action, SIGNAL(triggered()), this, SLOT(toggleFullScreen()));
    settings->addAction(action, "ide-show-fullscreen", ideCategory);

    mActions[FocusPostWindow] = action = new QAction(tr("Focus Post Window"), this);
    action->setStatusTip(tr("Focus post window"));
    action->setShortcut(tr("Ctrl+P", "Focus post window"));
    connect(action, SIGNAL(triggered()), mPostDocklet, SLOT(focus()));
    settings->addAction(action, "post-focus", ideCategory);

    // Language
    mActions[LookupImplementation] = action =
        new QAction(QIcon::fromTheme("window-lookupdefinition"), tr("Look Up Implementations..."), this);
    action->setShortcut(tr("Ctrl+Shift+I", "Look Up Implementations"));
    action->setStatusTip(tr("Open dialog to look up implementations of a class or a method"));
    connect(action, SIGNAL(triggered()), this, SLOT(lookupImplementation()));
    settings->addAction(action, "ide-lookup-implementation", ideCategory);

    mActions[LookupImplementationForCursor] = action = new QAction(tr("Look Up Implementations for Cursor"), this);
    action->setShortcut(tr("Ctrl+I", "Look Up Implementations for Cursor"));
    action->setStatusTip(tr("Look up implementations of class or method under cursor"));
    connect(action, SIGNAL(triggered(bool)), this, SLOT(lookupImplementationForCursor()));
    settings->addAction(action, "ide-lookup-implementation-for-cursor", ideCategory);

    mActions[LookupReferences] = action =
        new QAction(QIcon::fromTheme("window-lookupreferences"), tr("Look Up References..."), this);
    action->setShortcut(tr("Ctrl+Shift+U", "Look Up References"));
    action->setStatusTip(tr("Open dialog to look up references to a class or a method"));
    connect(action, SIGNAL(triggered()), this, SLOT(lookupReferences()));
    settings->addAction(action, "ide-lookup-references", ideCategory);

    mActions[LookupReferencesForCursor] = action = new QAction(tr("Look Up References for Cursor"), this);
    action->setShortcut(tr("Ctrl+U", "Look Up References For Selection"));
    action->setStatusTip(tr("Look up references to class or method under cursor"));
    connect(action, SIGNAL(triggered(bool)), this, SLOT(lookupReferencesForCursor()));
    settings->addAction(action, "ide-lookup-references-for-cursor", ideCategory);

    // Settings
    mActions[ShowSettings] = action = new QAction(tr("Preferences"), this);
#ifdef Q_OS_MAC
    action->setShortcut(tr("Ctrl+,", "Show configuration dialog"));
#endif
    action->setStatusTip(tr("Show configuration dialog"));
    connect(action, SIGNAL(triggered()), this, SLOT(showSettings()));
    settings->addAction(action, "ide-settings-dialog", ideCategory);

    // Help
    mActions[ReportABug] = action = new QAction(QIcon::fromTheme("system-help"), tr("Report a bug..."), this);
    action->setStatusTip(tr("Report a bug"));
    connect(action, SIGNAL(triggered()), this, SLOT(doBugReport()));

#ifdef SC_USE_QTWEBENGINE
    mActions[Help] = action = new QAction(tr("Show &Help Browser"), this);
    action->setStatusTip(tr("Show and focus the Help Browser"));
    connect(action, SIGNAL(triggered()), this, SLOT(openHelp()));
    settings->addAction(action, "help-browser", helpCategory);

    mActions[HelpAboutIDE] = action =
        new QAction(QIcon::fromTheme("system-help"), tr("How to Use SuperCollider IDE"), this);
    action->setStatusTip(tr("Open the SuperCollider IDE guide"));
    connect(action, SIGNAL(triggered()), this, SLOT(openHelpAboutIDE()));

    mActions[LookupDocumentationForCursor] = action = new QAction(tr("Look Up Documentation for Cursor"), this);
    action->setShortcut(tr("Ctrl+D", "Look Up Documentation for Cursor"));
    action->setStatusTip(tr("Look up documentation for text under cursor"));
    connect(action, SIGNAL(triggered()), this, SLOT(lookupDocumentationForCursor()));
    settings->addAction(action, "help-lookup-for-cursor", helpCategory);

    mActions[LookupDocumentation] = action = new QAction(tr("Look Up Documentation..."), this);
    action->setShortcut(tr("Ctrl+Shift+D", "Look Up Documentation"));
    action->setStatusTip(tr("Enter text to look up in documentation"));
    connect(action, SIGNAL(triggered()), this, SLOT(lookupDocumentation()));
    settings->addAction(action, "help-lookup", helpCategory);
#endif // SC_USE_QTWEBENGINE

    mActions[ShowAbout] = action = new QAction(QIcon::fromTheme("help-about"), tr("&About SuperCollider"), this);
    connect(action, SIGNAL(triggered()), this, SLOT(showAbout()));
    settings->addAction(action, "ide-about", ideCategory);

    mActions[ShowAboutQT] = action = new QAction(QIcon::fromTheme("show-about-qt"), tr("About &Qt"), this);
    connect(action, SIGNAL(triggered()), this, SLOT(showAboutQT()));
    settings->addAction(action, "ide-about-qt", ideCategory);

    // Add external actions to settings:
    action = mPostDocklet->toggleViewAction();
    action->setIcon(QIcon::fromTheme("utilities-terminal"));
    action->setStatusTip(tr("Show/hide Post docklet"));
    settings->addAction(mPostDocklet->toggleViewAction(), "ide-docklet-post", ideCategory);

    action = mDocumentsDocklet->toggleViewAction();
    action->setIcon(QIcon::fromTheme("text-x-generic"));
    action->setStatusTip(tr("Show/hide Documents docklet"));
    settings->addAction(mDocumentsDocklet->toggleViewAction(), "ide-docklet-documents", ideCategory);

#ifdef SC_USE_QTWEBENGINE
    action = mHelpBrowserDocklet->toggleViewAction();
    action->setIcon(QIcon::fromTheme("system-help"));
    action->setStatusTip(tr("Show/hide Help browser docklet"));
    settings->addAction(mHelpBrowserDocklet->toggleViewAction(), "ide-docklet-help", ideCategory);
#endif // SC_USE_QTWEBENGINE

    // In Mac OS, all menu item shortcuts need a modifier, so add the action with
    // the "Escape" default shortcut to the main window widget.
    // FIXME: This is not perfect, as any other action customized to "Escape" will
    // still not work.
    addAction(mActions[CloseToolBox]);

    // Add actions to docklets, so shortcuts work when docklets detached:

#ifdef SC_USE_QTWEBENGINE
    mPostDocklet->widget()->addAction(mActions[LookupDocumentation]);
    mPostDocklet->widget()->addAction(mActions[LookupDocumentationForCursor]);
#endif // SC_USE_QTWEBENGINE
    mPostDocklet->widget()->addAction(mActions[LookupImplementation]);
    mPostDocklet->widget()->addAction(mActions[LookupImplementationForCursor]);
    mPostDocklet->widget()->addAction(mActions[LookupReferences]);
    mPostDocklet->widget()->addAction(mActions[LookupReferencesForCursor]);

#ifdef SC_USE_QTWEBENGINE
    mHelpBrowserDocklet->widget()->addAction(mActions[LookupDocumentation]);
    mHelpBrowserDocklet->widget()->addAction(mActions[LookupDocumentationForCursor]);
    mHelpBrowserDocklet->widget()->addAction(mActions[LookupImplementation]);
    mHelpBrowserDocklet->widget()->addAction(mActions[LookupImplementationForCursor]);
    mHelpBrowserDocklet->widget()->addAction(mActions[LookupReferences]);
    mHelpBrowserDocklet->widget()->addAction(mActions[LookupReferencesForCursor]);
#endif // SC_USE_QTWEBENGINE
}

void MainWindow::createMenus() {
    QMenuBar* menuBar;
    QMenu* menu;
    QMenu* submenu;

    // On Mac, create a parent-less menu bar to be shared by all windows:
#ifdef Q_OS_MAC
    menuBar = new QMenuBar(0);
    // icons in menu bars impacts the performance on (intel) macs considerably
    // while switching windows, therefore we de-activate those. (#6523)
    QApplication::instance()->setAttribute(Qt::AA_DontShowIconsInMenus, true);
#else
    menuBar = this->menuBar();
#endif

    menu = new QMenu(tr("&File"), this);
    menu->addAction(mActions[DocNew]);
    menu->addAction(mActions[DocOpen]);
    mRecentDocsMenu = menu->addMenu(tr("Open Recent", "Open a recent document"));
    connect(mRecentDocsMenu, SIGNAL(triggered(QAction*)), this, SLOT(onOpenRecentDocument(QAction*)));
    menu->addAction(mActions[DocOpenStartup]);
    menu->addAction(mActions[DocOpenSupportDir]);
    menu->addAction(mActions[DocSave]);
    menu->addAction(mActions[DocSaveAs]);
    menu->addAction(mActions[DocSaveAsExtension]);
    menu->addAction(mActions[DocSaveAll]);
    menu->addSeparator();
    menu->addAction(mActions[DocReload]);
    menu->addSeparator();
    menu->addAction(mEditors->action(MultiEditor::DocClose));
    menu->addAction(mActions[DocCloseAll]);
    menu->addSeparator();
    menu->addAction(mActions[Quit]);

    menuBar->addMenu(menu);

    menu = new QMenu(tr("&Session"), this);
    menu->addAction(mActions[NewSession]);
    menu->addAction(mActions[SaveSessionAs]);
    submenu = menu->addMenu(tr("&Open Session"));
    connect(submenu, SIGNAL(triggered(QAction*)), this, SLOT(onOpenSessionAction(QAction*)));
    mSessionsMenu = submenu;
    updateSessionsMenu();
    menu->addSeparator();
    menu->addAction(mActions[ManageSessions]);
    menu->addAction(mActions[OpenSessionSwitchDialog]);

    menuBar->addMenu(menu);

    menu = new QMenu(tr("&Edit"), this);
    menu->addAction(mEditors->action(MultiEditor::Undo));
    menu->addAction(mEditors->action(MultiEditor::Redo));
    menu->addSeparator();
    menu->addAction(mEditors->action(MultiEditor::Cut));
    menu->addAction(mEditors->action(MultiEditor::Copy));
    menu->addAction(mEditors->action(MultiEditor::Paste));
    menu->addSeparator();
    menu->addAction(mActions[Find]);
    menu->addAction(mFindReplaceTool->action(TextFindReplacePanel::FindNext));
    menu->addAction(mFindReplaceTool->action(TextFindReplacePanel::FindPrevious));
    menu->addAction(mActions[Replace]);
    menu->addSeparator();
    menu->addAction(mEditors->action(MultiEditor::IndentWithSpaces));
    menu->addAction(mEditors->action(MultiEditor::IndentLineOrRegion));
    menu->addAction(mEditors->action(MultiEditor::ToggleComment));
    menu->addAction(mEditors->action(MultiEditor::ToggleOverwriteMode));
    menu->addAction(mEditors->action(MultiEditor::SelectRegion));
    menu->addAction(mEditors->action(MultiEditor::SelectEnclosingBlock));

    menu->addSeparator();
    menu->addAction(mActions[ShowSettings]);

    menuBar->addMenu(menu);

    menu = new QMenu(tr("&View"), this);
    submenu = new QMenu(tr("&Docklets"), this);
    submenu->addAction(mPostDocklet->toggleViewAction());
    submenu->addAction(mDocumentsDocklet->toggleViewAction());
#ifdef SC_USE_QTWEBENGINE
    submenu->addAction(mHelpBrowserDocklet->toggleViewAction());
#endif // SC_USE_QTWEBENGINE
    menu->addMenu(submenu);
    menu->addSeparator();
    submenu = menu->addMenu(tr("&Tool Panels"));
    submenu->addAction(mActions[Find]);
    submenu->addAction(mActions[Replace]);
    submenu->addAction(mActions[ShowCmdLine]);
    submenu->addAction(mActions[CmdLineForCursor]);
    submenu->addAction(mActions[ShowGoToLineTool]);
    submenu->addSeparator();
    submenu->addAction(mActions[CloseToolBox]);
    menu->addSeparator();
    menu->addAction(mEditors->action(MultiEditor::EnlargeFont));
    menu->addAction(mEditors->action(MultiEditor::ShrinkFont));
    menu->addAction(mEditors->action(MultiEditor::ResetFontSize));
    menu->addSeparator();
    menu->addAction(mEditors->action(MultiEditor::ShowWhitespace));
    menu->addAction(mEditors->action(MultiEditor::ShowLinenumber));
    menu->addSeparator();
    menu->addAction(mEditors->action(MultiEditor::ShowAutocompleteHelp));
    menu->addSeparator();
    menu->addAction(mEditors->action(MultiEditor::NextDocument));
    menu->addAction(mEditors->action(MultiEditor::PreviousDocument));
    menu->addAction(mEditors->action(MultiEditor::SwitchDocument));
    menu->addSeparator();
    menu->addAction(mEditors->action(MultiEditor::SplitHorizontally));
    menu->addAction(mEditors->action(MultiEditor::SplitVertically));
    menu->addAction(mEditors->action(MultiEditor::RemoveCurrentSplit));
    menu->addAction(mEditors->action(MultiEditor::RemoveAllSplits));
    menu->addSeparator();
    menu->addAction(mActions[FocusPostWindow]);

    menuBar->addMenu(menu);

    menu = new QMenu(tr("&Language"), this);
    menu->addAction(mMain->scProcess()->action(ScProcess::ToggleRunning));
    menu->addAction(mMain->scProcess()->action(ScProcess::Restart));
    menu->addAction(mMain->scProcess()->action(ScProcess::RecompileClassLibrary));
    menu->addSeparator();
    menu->addAction(mMain->scProcess()->action(ScProcess::ShowQuarks));
    menu->addSeparator();
    menu->addAction(mEditors->action(MultiEditor::EvaluateCurrentDocument));
    menu->addAction(mEditors->action(MultiEditor::EvaluateRegion));
    menu->addAction(mEditors->action(MultiEditor::EvaluateLine));
    menu->addAction(mMain->scProcess()->action(ScIDE::ScProcess::StopMain));
    menu->addSeparator();
    menu->addAction(mActions[LookupImplementationForCursor]);
    menu->addAction(mActions[LookupImplementation]);
    menu->addAction(mActions[LookupReferencesForCursor]);
    menu->addAction(mActions[LookupReferences]);

    menuBar->addMenu(menu);

    menu = new QMenu(tr("Se&rver"), this);
    menu->addAction(mMain->scServer()->action(ScServer::ToggleRunning));
    menu->addAction(mMain->scServer()->action(ScServer::Reboot));
    menu->addAction(mMain->scServer()->action(ScServer::KillAll));
    menu->addSeparator();
    menu->addAction(mMain->scServer()->action(ScServer::ShowMeters));
    menu->addAction(mMain->scServer()->action(ScServer::ShowScope));
    menu->addAction(mMain->scServer()->action(ScServer::ShowFreqScope));
    menu->addAction(mMain->scServer()->action(ScServer::DumpNodeTree));
    menu->addAction(mMain->scServer()->action(ScServer::DumpNodeTreeWithControls));
    menu->addAction(mMain->scServer()->action(ScServer::PlotTree));
    menu->addAction(mMain->scServer()->action(ScServer::DumpOSC));
    menu->addAction(mMain->scServer()->action(ScServer::Record));
    menu->addAction(mMain->scServer()->action(ScServer::PauseRecord));
    menu->addAction(mMain->scServer()->action(ScServer::VolumeUp));
    menu->addAction(mMain->scServer()->action(ScServer::VolumeDown));
    menu->addAction(mMain->scServer()->action(ScServer::VolumeRestore));
    menu->addAction(mMain->scServer()->action(ScServer::Mute));

    menuBar->addMenu(menu);

    menu = new QMenu(tr("&Help"), this);
#ifdef SC_USE_QTWEBENGINE
    menu->addAction(mActions[HelpAboutIDE]);
#endif
    menu->addAction(mActions[ReportABug]);
#ifdef SC_USE_QTWEBENGINE
    menu->addSeparator();
    menu->addAction(mActions[Help]);
    menu->addAction(mActions[LookupDocumentationForCursor]);
    menu->addAction(mActions[LookupDocumentation]);
#endif // SC_USE_QTWEBENGINE
    menu->addSeparator();
    menu->addAction(mActions[ShowAbout]);
    menu->addAction(mActions[ShowAboutQT]);

    menuBar->addMenu(menu);
}

static void saveDetachedState(Docklet* docklet, QVariantMap& data) {
    data.insert(docklet->objectName(), docklet->saveDetachedState().toBase64());
}

template <class T> void MainWindow::saveWindowState(T* settings) {
    QVariantMap detachedData;
    saveDetachedState(mPostDocklet, detachedData);
    saveDetachedState(mDocumentsDocklet, detachedData);
#ifdef SC_USE_QTWEBENGINE
    saveDetachedState(mHelpBrowserDocklet, detachedData);
#endif // SC_USE_QTWEBENGINE

    settings->beginGroup("mainWindow");
    settings->setValue("geometry", this->saveGeometry().toBase64());
    settings->setValue("state", this->saveState().toBase64());
    settings->setValue("detached", QVariant::fromValue(detachedData));
    settings->endGroup();
}

void MainWindow::saveWindowState() {
    Settings::Manager* settings = Main::settings();
    settings->beginGroup("IDE");
    saveWindowState(settings);
    settings->endGroup();
}

static void restoreDetachedState(Docklet* docklet, const QVariantMap& data) {
    QByteArray base64data = data.value(docklet->objectName()).value<QByteArray>();
    docklet->restoreDetachedState(QByteArray::fromBase64(base64data));
}

template <class T> void MainWindow::restoreWindowState(T* settings) {
    qDebug("------------ restore window state ------------");

    settings->beginGroup("mainWindow");
    QVariant varGeom = settings->value("geometry");
    QVariant varState = settings->value("state");
    QVariant varDetached = settings->value("detached");
    settings->endGroup();

    QByteArray geom = QByteArray::fromBase64(varGeom.value<QByteArray>());
    QByteArray state = QByteArray::fromBase64(varState.value<QByteArray>());
    QVariantMap detachedData = varDetached.value<QVariantMap>();

    if (!geom.isEmpty()) {
        // Workaround for Qt bug 4397:
        setWindowState(Qt::WindowNoState);
        restoreGeometry(geom);
    } else
        setWindowState(windowState() & ~Qt::WindowFullScreen | Qt::WindowMaximized);

    restoreDetachedState(mPostDocklet, detachedData);
    restoreDetachedState(mDocumentsDocklet, detachedData);
#ifdef SC_USE_QTWEBENGINE
    restoreDetachedState(mHelpBrowserDocklet, detachedData);
#endif // SC_USE_QTWEBENGINE

    qDebug("restoring state");

    if (!state.isEmpty())
        restoreState(state);

    qDebug("setting dock area corners");

    setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
    setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
    setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
    setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);

    updateClockWidget(isFullScreen());

    qDebug("------------ END restore window state ------------");
}

void MainWindow::restoreWindowState() {
    Settings::Manager* settings = Main::settings();
    settings->beginGroup("IDE");
    restoreWindowState(settings);
    settings->endGroup();
}

void MainWindow::focusCodeEditor() {
    if (mEditors->currentEditor())
        mEditors->currentEditor()->setFocus();
    else
        mEditors->setFocus();
}

void MainWindow::newSession() { mMain->sessionManager()->newSession(); }

void MainWindow::saveCurrentSessionAs() {
    QString name = QInputDialog::getText(this, tr("Save Current Session"), tr("Enter a name for the session:"));

    if (name.isEmpty())
        return;

    mMain->sessionManager()->saveSessionAs(name);

    updateSessionsMenu();
}

void MainWindow::onOpenSessionAction(QAction* action) { openSession(action->text()); }

void MainWindow::switchSession(Session* session) {
    if (session)
        restoreWindowState(session);

    updateWindowTitle();

    mEditors->switchSession(session);
}

void MainWindow::saveSession(Session* session) {
    saveWindowState(session);

    mEditors->saveSession(session);
}

void MainWindow::openSessionsDialog() {
    QPointer<MainWindow> mainwin(this);
    SessionsDialog dialog(mMain->sessionManager(), this);
    dialog.exec();
    if (mainwin)
        mainwin->updateSessionsMenu();
}

QAction* MainWindow::action(ActionRole role) {
    Q_ASSERT(role < ActionCount);
    return mActions[role];
}

bool MainWindow::quit() {
    if (!promptSaveDocs())
        return false;

    Main::instance()->documentManager()->deleteRestore();

    saveWindowState();

    mMain->quit();

    return true;
}

void MainWindow::onQuit() { quit(); }

void MainWindow::onCurrentDocumentChanged(Document* doc) {
    updateWindowTitle();

    mActions[DocCloseAll]->setEnabled(doc);
    mActions[DocReload]->setEnabled(doc);
    mActions[DocSave]->setEnabled(doc);
    mActions[DocSaveAs]->setEnabled(doc);
    mActions[DocSaveAsExtension]->setEnabled(doc);

    GenericCodeEditor* editor = mEditors->currentEditor();
    mFindReplaceTool->setEditor(editor);
    mGoToLineTool->setEditor(editor);
}

void MainWindow::onDocumentChangedExternally(Document* doc) {
    if (mDocDialog)
        return;

    mDocDialog = new DocumentsDialog(DocumentsDialog::ExternalChange, this);
    mDocDialog->addDocument(doc);
    connect(mDocDialog, SIGNAL(finished(int)), this, SLOT(onDocDialogFinished()));
    mDocDialog->open();
}

void MainWindow::onDocDialogFinished() {
    mDocDialog->deleteLater();
    mDocDialog = 0;
}

void MainWindow::updateRecentDocsMenu() {
    mRecentDocsMenu->clear();

    const QStringList& recent = mMain->documentManager()->recents();

    foreach (const QString& path, recent) {
        QAction* action = mRecentDocsMenu->addAction(path);
        action->setData(QVariant(path));
    }

    if (!recent.isEmpty()) {
        mRecentDocsMenu->addSeparator();
        mRecentDocsMenu->addAction(mActions[ClearRecentDocs]);
    }
}

void MainWindow::onOpenRecentDocument(QAction* action) {
    mMain->documentManager()->open(action->data().value<QString>());
}

void MainWindow::onInterpreterStateChanged(QProcess::ProcessState state) {
    switch (state) {
    case QProcess::NotRunning:
        toggleInterpreterActions(false);

    case QProcess::Starting:
        break;

    case QProcess::Running:
        toggleInterpreterActions(true);
        break;
    }
}

void MainWindow::closeEvent(QCloseEvent* event) {
    if (!quit())
        event->ignore();
}

bool MainWindow::close(Document* doc) {
    if (doc->textDocument()->isModified() && doc->promptsToSave()) {
        QMessageBox::StandardButton ret;
        ret = QMessageBox::warning(mInstance, tr("SuperCollider IDE"),
                                   tr("There are unsaved changes in document '%1'.\n\n"
                                      "Do you want to save it?")
                                       .arg(doc->title()),
                                   QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel,
                                   QMessageBox::Save // the default
        );

        switch (ret) {
        case QMessageBox::Cancel:
            return false;
        case QMessageBox::Save:
            if (!MainWindow::save(doc))
                return false;
            break;
        default:;
        }
    }

    Main::instance()->documentManager()->close(doc);
    return true;
}

bool MainWindow::reload(Document* doc) {
    if (doc->filePath().isEmpty())
        return false;

    if (doc->textDocument()->isModified()) {
        QMessageBox::StandardButton ret;
        ret = QMessageBox::warning(mInstance, tr("SuperCollider IDE"),
                                   tr("There are unsaved changes in document '%1'.\n\n"
                                      "Do you want to reload it?")
                                       .arg(doc->title()),
                                   QMessageBox::Yes | QMessageBox::No,
                                   QMessageBox::No // the default
        );
        if (ret == QMessageBox::No)
            return false;
    }

    return Main::instance()->documentManager()->reload(doc);
}

QString MainWindow::documentSavePath(Document* document) const {
    if (!document->filePath().isEmpty())
        return document->filePath();

    if (!mLastDocumentSavePath.isEmpty())
        return QFileInfo(mLastDocumentSavePath).path();

    QString interpreterWorkingDir = Main::settings()->value("IDE/interpreter/runtimeDir").toString();
    if (!interpreterWorkingDir.isEmpty())
        return interpreterWorkingDir;

    return QStandardPaths::standardLocations(QStandardPaths::HomeLocation)[0];
}

bool MainWindow::save(Document* doc, bool forceChoose, bool saveInExtensionFolder) {
    const bool documentHasPath = !doc->filePath().isEmpty();

    if (!forceChoose && !(doc->isModified()) && documentHasPath)
        return true;

    DocumentManager* documentManager = Main::instance()->documentManager();

    bool fileIsWritable = true;
    if ((!forceChoose) && documentHasPath) {
        QFileInfo fileInfo(doc->filePath());
        fileIsWritable = fileInfo.isWritable();

        if (!fileIsWritable) {
            QMessageBox::warning(instance(), tr("Saving read-only file"),
                                 tr("File is read-only. Please select a new location to save to."), QMessageBox::Ok,
                                 QMessageBox::NoButton);
        }
    }

    if (forceChoose || !documentHasPath || !fileIsWritable) {
        QFileDialog dialog(mInstance);
        dialog.setAcceptMode(QFileDialog::AcceptSave);
        dialog.setFileMode(QFileDialog::AnyFile);

        QStringList filters = QStringList()
            << tr("All Files (*)") << tr("SuperCollider Document (*.scd)") << tr("SuperCollider Class File (*.sc)")
            << tr("SuperCollider Help Source (*.schelp)");

        dialog.setNameFilters(filters);

        if (saveInExtensionFolder) {
            dialog.setDirectory(standardDirectory(ScExtensionUserDir));
        } else {
            QString path = mInstance->documentSavePath(doc);
            QFileInfo path_info(path);

            if (path_info.isDir())
                // FIXME:
                // KDE native file dialog shows parent directory instead (KDE bug 229375)
                dialog.setDirectory(path);
            else
                dialog.selectFile(path);

            // NOTE: do not use QFileDialog::setDefaultSuffix(), because it only adds
            // the suffix after the dialog is closed, without showing a warning if the
            // filepath with added suffix already exists!
        }

#ifdef Q_OS_MAC
        QWidget* last_active_window = QApplication::activeWindow();
#endif

        int result = dialog.exec();

        // FIXME: workaround for Qt bug 25295
        // See SC issue #678
#ifdef Q_OS_MAC
        if (last_active_window)
            last_active_window->activateWindow();
#endif

        QString save_path;

        if (result == QDialog::Accepted) {
            save_path = dialog.selectedFiles()[0];

            if (save_path.indexOf('.') == -1 && !QFile::exists(save_path)) {
                save_path.append(".scd");
                QFileInfo save_path_info(save_path);
                if (save_path_info.exists()) {
                    QString msg = tr("Extension \".scd\" was automatically added to the "
                                     "selected file name, but the file \"%1\" already exists.\n\n"
                                     "Do you wish to overwrite it?")
                                      .arg(save_path_info.fileName());
                    QMessageBox::StandardButton result =
                        QMessageBox::warning(mInstance, tr("Overwrite File?"), msg, QMessageBox::Yes | QMessageBox::No);
                    if (result != QMessageBox::Yes)
                        save_path.clear();
                }
            }
        }

        if (!save_path.isEmpty()) {
            if (!saveInExtensionFolder)
                mInstance->mLastDocumentSavePath = save_path;
            return documentManager->saveAs(doc, save_path);
        } else {
            return false;
        }
    } else
        return documentManager->save(doc);
}

void MainWindow::newDocument() { mMain->documentManager()->create(); }

QString MainWindow::documentOpenPath() const {
    GenericCodeEditor* currentEditor = mEditors->currentEditor();
    if (currentEditor) {
        QString currentEditorPath = currentEditor->document()->filePath();
        if (!currentEditorPath.isEmpty())
            return currentEditorPath;
    }

    const QStringList& recentDocuments = Main::documentManager()->recents();
    if (!recentDocuments.isEmpty())
        return recentDocuments[0];

    QString interpreterWorkingDir = Main::settings()->value("IDE/interpreter/runtimeDir").toString();
    if (!interpreterWorkingDir.isEmpty())
        return interpreterWorkingDir;

    return QStandardPaths::standardLocations(QStandardPaths::HomeLocation)[0];
}

void MainWindow::openDocument() {
    QFileDialog dialog(this, Qt::Dialog);
    dialog.setModal(true);
    dialog.setWindowModality(Qt::ApplicationModal);

    dialog.setFileMode(QFileDialog::ExistingFiles);

    QString path = documentOpenPath();
    QFileInfo path_info(path);
    if (path_info.isDir())
        dialog.setDirectory(path);
    else
        dialog.setDirectory(path_info.dir());

    QStringList filters;
    filters << tr("All Files (*)") << tr("SuperCollider (*.scd *.sc)") << tr("SuperCollider Help Source (*.schelp)");
    dialog.setNameFilters(filters);

#ifdef Q_OS_MAC
    QWidget* last_active_window = QApplication::activeWindow();
#endif

    if (dialog.exec()) {
        QStringList filenames = dialog.selectedFiles();
        foreach (QString filename, filenames)
            mMain->documentManager()->open(filename);
    }

    // FIXME: workaround for Qt bug 25295
    // See SC issue #678
#ifdef Q_OS_MAC
    if (last_active_window)
        last_active_window->activateWindow();
#endif
}

void MainWindow::restoreDocuments() {
    DocumentManager* docMng = Main::instance()->documentManager();

    if (docMng->needRestore()) {
        QString msg = tr("Supercollider didn't quit properly last time\n"
                         "Do you want to restore files saved as temporary backups?");
        QMessageBox::StandardButton restore = QMessageBox::warning(
            mInstance, tr("Restore files?"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
        if (restore == QMessageBox::Yes)
            docMng->restore();
        else
            docMng->deleteRestore();
    }
}

void MainWindow::openStartupFile() {
    QString configDir = standardDirectory(ScConfigUserDir);

    QDir dir;
    // Create the config dir if non existent:
    dir.mkpath(configDir);
    if (!dir.cd(configDir)) {
        qWarning() << "Could not access config dir:" << configDir;
        return;
    }

    QString filePath = dir.filePath("startup.scd");
    // Try creating the file if non-existent:
    if (!QFile::exists(filePath)) {
        QFile file(filePath);
        if (!file.open(QIODevice::WriteOnly)) {
            file.close();
            qWarning() << "Could not create startup file:" << filePath;
            return;
        }
        file.close();
    }

    mMain->documentManager()->open(filePath, -1, 0, false);
}

void MainWindow::openUserSupportDirectory() {
    QUrl dirUrl = QUrl::fromLocalFile(standardDirectory(ScAppDataUserDir));
    QDesktopServices::openUrl(dirUrl);
}

void MainWindow::saveDocument() {
    GenericCodeEditor* editor = mEditors->currentEditor();
    if (!editor)
        return;

    Document* doc = editor->document();
    Q_ASSERT(doc);

    MainWindow::save(doc);
}

void MainWindow::saveDocumentAs() {
    GenericCodeEditor* editor = mEditors->currentEditor();
    if (!editor)
        return;

    Document* doc = editor->document();
    Q_ASSERT(doc);

    MainWindow::save(doc, true);
}

void MainWindow::saveDocumentAsExtension() {
    GenericCodeEditor* editor = mEditors->currentEditor();
    if (!editor)
        return;

    Document* doc = editor->document();
    Q_ASSERT(doc);

    MainWindow::save(doc, true, true);
}

void MainWindow::saveAllDocuments() {
    QList<Document*> docs = mMain->documentManager()->documents();
    foreach (Document* doc, docs)
        if (!MainWindow::save(doc))
            return;
}

void MainWindow::reloadDocument() {
    GenericCodeEditor* editor = mEditors->currentEditor();
    if (!editor)
        return;

    Q_ASSERT(editor->document());
    MainWindow::reload(editor->document());
}

void MainWindow::closeDocument() {
    GenericCodeEditor* editor = mEditors->currentEditor();
    if (!editor)
        return;

    Q_ASSERT(editor->document());
    MainWindow::close(editor->document());
}

void MainWindow::closeAllDocuments() {
    if (promptSaveDocs()) {
        QList<Document*> docs = mMain->documentManager()->documents();
        foreach (Document* doc, docs)
            mMain->documentManager()->close(doc);
    }
}

bool MainWindow::promptSaveDocs() {
    // LATER: maybe this should go to the DocumentManager class?

    QList<Document*> docs = mMain->documentManager()->documents();
    QList<Document*> unsavedDocs;
    foreach (Document* doc, docs)
        if (doc->textDocument()->isModified() && doc->promptsToSave())
            unsavedDocs.append(doc);

    if (!unsavedDocs.isEmpty()) {
        DocumentsDialog dialog(unsavedDocs, DocumentsDialog::Quit, this);

        if (!dialog.exec())
            return false;
    }

    return true;
}

void MainWindow::updateWindowTitle() {
    Session* session = mMain->sessionManager()->currentSession();
    GenericCodeEditor* editor = mEditors->currentEditor();
    Document* doc = editor ? editor->document() : 0;

    QString title;

    if (session) {
        title.append(session->name());
        if (doc)
            title.append(": ");
    }

    if (doc) {
        if (!doc->filePath().isEmpty()) {
            QFileInfo info = QFileInfo(doc->filePath());
            QString pathString = info.dir().path();

            QString homePath = QDir::homePath();
            if (pathString.startsWith(homePath))
                pathString.replace(0, homePath.size(), QStringLiteral("~"));

            QString titleString = QStringLiteral("%1 (%2)").arg(info.fileName(), pathString);

            title.append(titleString);

            setWindowFilePath(doc->filePath());
        } else {
            title.append(tr("Untitled"));
            setWindowFilePath("");
        }
    } else {
        setWindowFilePath("");
    }

    if (!title.isEmpty())
        title.append(" - ");

    title.append("SuperCollider IDE");

    setWindowTitle(title);
}

void MainWindow::toggleFullScreen() {
    if (isFullScreen()) {
        setWindowState(windowState() & ~Qt::WindowFullScreen);

        updateClockWidget(false);
    } else {
        setWindowState(windowState() | Qt::WindowFullScreen);

        updateClockWidget(true);
    }
}

void MainWindow::updateClockWidget(bool isFullScreen) {
    if (!isFullScreen) {
        if (mClockLabel) {
            delete mClockLabel;
            mClockLabel = NULL;
        }
    } else {
        if (mClockLabel == NULL) {
            mClockLabel = new ClockStatusBox(this);
            statusBar()->insertWidget(0, mClockLabel);
        }
    }
}

void MainWindow::openSession(const QString& sessionName) { mMain->sessionManager()->openSession(sessionName); }

void MainWindow::lookupImplementationForCursor() {
    static const QByteArray signature = QMetaObject::normalizedSignature("openDefinition()");

    invokeMethodOnFirstResponder(signature);
}

void MainWindow::lookupImplementation() { Main::openDefinition(QString(), QApplication::activeWindow()); }

void MainWindow::lookupReferencesForCursor() {
    static const QByteArray signature = QMetaObject::normalizedSignature("findReferences()");

    invokeMethodOnFirstResponder(signature);
}

void MainWindow::lookupReferences() { Main::findReferences(QString(), QApplication::activeWindow()); }

void MainWindow::showStatusMessage(QString const& string) { mStatusBar->showMessage(string, 3000); }

void MainWindow::applySettings(Settings::Manager* settings) {
    applyCursorBlinkingSettings(settings);

    mPostDocklet->mPostWindow->applySettings(settings);
#ifdef SC_USE_QTWEBENGINE
    mHelpBrowserDocklet->browser()->applySettings(settings);
#endif // SC_USE_QTWEBENGINE
    mCmdLine->applySettings(settings);
}

void MainWindow::applyCursorBlinkingSettings(Settings::Manager* settings) {
    const bool disableBlinkingCursor = settings->value("IDE/editor/disableBlinkingCursor").toBool();
    const int defaultCursorFlashTime = settings->defaultCursorFlashTime();
    QApplication::setCursorFlashTime(disableBlinkingCursor ? 0 : defaultCursorFlashTime);
}

void MainWindow::storeSettings(Settings::Manager* settings) { mPostDocklet->mPostWindow->storeSettings(settings); }

void MainWindow::updateSessionsMenu() {
    mSessionsMenu->clear();
    QStringList sessions = mMain->sessionManager()->availableSessions();
    foreach (const QString& session, sessions)
        mSessionsMenu->addAction(session);
}

void MainWindow::showSwitchSessionDialog() {
    SessionSwitchDialog* dialog = new SessionSwitchDialog(this);
    int result = dialog->exec();

    if (result == QDialog::Accepted)
        openSession(dialog->activeElement());

    delete dialog;
}

void MainWindow::showAbout() {
    QString aboutString = "<h3>SuperCollider %1</h3>"
                          "<p>%2</p>"
                          "&copy; James McCartney and others.<br>"
                          "<h3>SuperCollider IDE</h3>"
                          "&copy; Jakob Leben, Tim Blechmann and others.<br>";
    aboutString = aboutString.arg(SC_VersionString().c_str()).arg(SC_BuildString().c_str());

    QMessageBox::about(this, tr("About SuperCollider IDE"), aboutString);
}

void MainWindow::showAboutQT() { QMessageBox::aboutQt(this); }

void MainWindow::toggleInterpreterActions(bool enabled) {
    mEditors->action(MultiEditor::EvaluateCurrentDocument)->setEnabled(enabled);
    mEditors->action(MultiEditor::EvaluateLine)->setEnabled(enabled);
    mEditors->action(MultiEditor::EvaluateRegion)->setEnabled(enabled);
}


void MainWindow::showCmdLine() {
    mToolBox->setCurrentWidget(mCmdLine);
    mToolBox->show();

    mCmdLine->setFocus(Qt::OtherFocusReason);
}

void MainWindow::showCmdLine(const QString& cmd) {
    mCmdLine->setText(cmd);
    showCmdLine();
}

void MainWindow::cmdLineForCursor() {
    static const QByteArray signature = QMetaObject::normalizedSignature("openCommandLine()");

    invokeMethodOnFirstResponder(signature);
}

void MainWindow::showGoToLineTool() {
    GenericCodeEditor* editor = mEditors->currentEditor();
    mGoToLineTool->setValue(editor ? editor->textCursor().blockNumber() + 1 : 0);

    mToolBox->setCurrentWidget(mGoToLineTool);
    mToolBox->show();

    mGoToLineTool->setFocus();
}

void MainWindow::showFindTool() {
    mFindReplaceTool->setMode(TextFindReplacePanel::Find);
    mFindReplaceTool->initiate();

    mToolBox->setCurrentWidget(mFindReplaceTool);
    mToolBox->show();

    mFindReplaceTool->setFocus(Qt::OtherFocusReason);
}

void MainWindow::showReplaceTool() {
    mFindReplaceTool->setMode(TextFindReplacePanel::Replace);
    mFindReplaceTool->initiate();

    mToolBox->setCurrentWidget(mFindReplaceTool);
    mToolBox->show();

    mFindReplaceTool->setFocus(Qt::OtherFocusReason);
}

void MainWindow::hideToolBox() {
    GenericCodeEditor* editor = mEditors->currentEditor();
    if (editor) {
        // This slot is mapped to Escape, so also clear highlighting
        // whenever invoked:
        editor->clearSearchHighlighting();
        if (!editor->hasFocus())
            editor->setFocus(Qt::OtherFocusReason);
    }

    mToolBox->hide();
}

void MainWindow::showSettings() {
    static std::atomic<bool> showingSettings { false };

    if (showingSettings.load())
        return;

    showingSettings = true;
    try {
        Settings::Dialog dialog(mMain->settings());
        dialog.resize(700, 400);
        int result = dialog.exec();
        if (result == QDialog::Accepted)
            mMain->applySettings();
    } catch (std::exception const& e) { qWarning() << "Error while executing settings dialog:" << e.what(); }
    showingSettings = false;
}


void MainWindow::lookupDocumentation() {
    PopupTextInput* dialog = new PopupTextInput(tr("Look up Documentation For"), QApplication::activeWindow());

    bool success = dialog->exec();
    if (success)
        Main::openDocumentation(dialog->textValue());

    delete dialog;
}

void MainWindow::lookupDocumentationForCursor() {
    static const QByteArray signature = QMetaObject::normalizedSignature("openDocumentation()");

    bool documentationOpened = false;
    QWidget* widget = QApplication::focusWidget();
    int methodIdx = -1;

    widget = findFirstResponder(widget, signature.constData(), methodIdx);

    if (widget && methodIdx != -1) {
        widget->metaObject()->method(methodIdx).invoke(widget, Qt::DirectConnection,
                                                       Q_RETURN_ARG(bool, documentationOpened));
    };

    if (!documentationOpened)
        openHelp();
}

void MainWindow::openHelp() {
#ifdef SC_USE_QTWEBENGINE
    if (mHelpBrowserDocklet->browser()->url().isEmpty())
        mHelpBrowserDocklet->browser()->goHome();
    mHelpBrowserDocklet->focus();
#endif // SC_USE_QTWEBENGINE
}

void MainWindow::openHelpAboutIDE() {
#ifdef SC_USE_QTWEBENGINE
    mHelpBrowserDocklet->browser()->gotoHelpFor("Guides/SCIde");
    mHelpBrowserDocklet->focus();
#endif // SC_USE_QTWEBENGINE
}

void MainWindow::doBugReport() {
    Settings::Manager* settings = mMain->settings();
    bool useGitHubBugReport = false;

    if (settings->contains("IDE/useGitHubBugReport")) {
        useGitHubBugReport = settings->value("IDE/useGitHubBugReport").toBool();

    } else {
        QMessageBox* dialog = new QMessageBox();
        dialog->setText("Do you want to submit bugs using <a href=\"https://www.github.com\">GitHub</a>?");
        dialog->setInformativeText("This requires a GitHub account.");
        dialog->addButton("Submit using GitHub", QMessageBox::YesRole);
        dialog->addButton("Submit anonymously", QMessageBox::NoRole);
        dialog->addButton("Cancel", QMessageBox::RejectRole);
        dialog->exec();
        QMessageBox::ButtonRole clicked = dialog->buttonRole(dialog->clickedButton());

        if (clicked == QMessageBox::YesRole || clicked == QMessageBox::NoRole) {
            useGitHubBugReport = (clicked == QMessageBox::YesRole);
            settings->setValue("IDE/useGitHubBugReport", useGitHubBugReport);
        } else {
            // Dialog was cancelled, so bail
            return;
        }
    }

    if (useGitHubBugReport) {
        QString url("https://github.com/supercollider/supercollider/issues/new");
        QString formData("?labels=bug&body=Bug%20description%3A%0A%0ASteps%20to%20reproduce%3A%0A1.%0A2.%0A3.%0A%"
                         "0AActual%20result%3A%0A%0AExpected%20result%3A%0A");
        QDesktopServices::openUrl(url + formData);
    } else {
        QDesktopServices::openUrl(QStringLiteral("https://gitreports.com/issue/supercollider/supercollider"));
    }
}

void MainWindow::dragEnterEvent(QDragEnterEvent* event) {
    if (event->mimeData()->hasUrls()) {
        foreach (QUrl url, event->mimeData()->urls()) {
            if (QURL_IS_LOCAL_FILE(url)) {
                // LATER: check mime type ?
                event->acceptProposedAction();
                return;
            }
        }
    }
}

bool MainWindow::checkFileExtension(const QString& fpath) {
    if (fpath.endsWith(".sc") || fpath.endsWith(".scd") || fpath.endsWith(".txt") || fpath.endsWith(".schelp")) {
        return true;
    }
    int ret = QMessageBox::question(this, tr("Open binary file?"),
                                    fpath
                                        + tr("\n\nThe file has an unrecognized extension. It may be a binary file. "
                                             "Would you still like to open it?"),
                                    QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel);
    if (ret != QMessageBox::Ok)
        return false;

    return true;
}

void MainWindow::dropEvent(QDropEvent* event) {
    const QMimeData* data = event->mimeData();
    if (data->hasUrls()) {
        foreach (QUrl url, data->urls()) {
            if (QURL_IS_LOCAL_FILE(url)) {
                QString fpath = url.toLocalFile();
                if (MainWindow::checkFileExtension(fpath))
                    Main::documentManager()->open(fpath);
            }
        }
    }
}

bool MainWindow::eventFilter(QObject* object, QEvent* event) {
    switch (event->type()) {
    case QEvent::ShortcutOverride: {
        QKeyEvent* key_event = static_cast<QKeyEvent*>(event);
        if (key_event->key() == 0) {
            // FIXME:
            // On Mac OS, for some global menu items, there is a  ShortcutOverride event with
            // key == 0, which seems like a Qt bug.
            // Text widgets override all events with key < Qt::Key_Escape, which includes 0.
            // Instead, prevent overriding such events:
            event->ignore();
            return true;
        }
        break;
    }
    default:
        break;
    }

    return QMainWindow::eventFilter(object, event);
}

//////////////////////////// ClockStatusBox ////////////////////////////

ClockStatusBox::ClockStatusBox(QWidget* parent): StatusLabel(parent) {
    setTextColor(Qt::green);
    mTimerId = startTimer(1000);
    updateTime();
}

ClockStatusBox::~ClockStatusBox() { killTimer(mTimerId); }

void ClockStatusBox::timerEvent(QTimerEvent* e) {
    if (e->timerId() == mTimerId)
        updateTime();
}

void ClockStatusBox::updateTime() { setText(QTime::currentTime().toString()); }

} // namespace ScIDE
