/*
    This file is part of KCachegrind.

    SPDX-FileCopyrightText: 2002-2016 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>

    SPDX-License-Identifier: GPL-2.0-only
*/

/*
 * QCachegrind top level window
 */

#define TRACE_UPDATES 0

#include "qcgtoplevel.h"

#include <stdlib.h> // for system()

#include <QApplication>
#include <QDebug>
#include <QDockWidget>
#include <QTimer>
#include <QByteArray>
#include <QLabel>
#include <QMenuBar>
#include <QProgressBar>
#include <QFile>
#include <QFileDialog>
#include <QEventLoop>
#include <QToolBar>
#include <QComboBox>
#include <QMessageBox>
#include <QStatusBar>
#include <QWhatsThis>
#include <QWindow>

#ifdef QT_DBUS_SUPPORT
#include <QDBusConnection>
#endif

#include "partselection.h"
#include "functionselection.h"
#include "stackselection.h"
#include "stackbrowser.h"
#include "tracedata.h"
#include "config.h"
#include "globalguiconfig.h"
#include "multiview.h"
#include "callgraphview.h"
#include "configdialog.h"

QCGTopLevel::QCGTopLevel()
{
#ifdef QT_DBUS_SUPPORT
    QDBusConnection con = QDBusConnection::sessionBus();
    con.registerObject("/QCachegrind", this,
                       QDBusConnection::ExportScriptableSlots);
#endif

    _progressBar = nullptr;
    _statusbar = statusBar();
    _statusLabel = new QLabel(_statusbar);
    _statusbar->addWidget(_statusLabel, 1);

    _layoutCount = 1;
    _layoutCurrent = 0;

    resetState();

    GlobalGUIConfig::config()->readOptions();

    createActions();
    createDocks();
    createToolbar();
    createMenu();

    _multiView = new MultiView(this, this);
    _multiView->setObjectName(QStringLiteral("MultiView"));
    setCentralWidget(_multiView);

    // restore current state settings (not configuration options)
    restoreCurrentState(QString());

    // restore docks & toolbars from config
    QByteArray state, geometry;
    ConfigGroup* topConfig = ConfigStorage::group(QStringLiteral("TopWindow"));
    _forcePartDock = topConfig->value(QStringLiteral("ForcePartDockVisible"), false).toBool();
    state = topConfig->value(QStringLiteral("State"), QByteArray()).toByteArray();
    geometry = topConfig->value(QStringLiteral("Geometry"), QByteArray()).toByteArray();
    delete topConfig;

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

    setWindowIcon(QIcon(QStringLiteral(":/app.png")));
    setAttribute(Qt::WA_DeleteOnClose);
}

QCGTopLevel::~QCGTopLevel()
{
#ifdef Q_OS_MAC
    // hacky way to reinstall the dock, since each toplevel has a dock menu,
    // we don't do any global menu stuff on mac beyond what qt gives us, and
    // when the last window that installed the dock menu dies, it goes away.
    // reinstall it into another window if we can.
    auto windowList = QApplication::topLevelWidgets();
    for (int i = 0; i < windowList.size(); i++) {
        QWidget *topLevelRaw = windowList[i];
        if (QCGTopLevel *topLevel = qobject_cast<QCGTopLevel*>(topLevelRaw)) {
            if (topLevel != this) {
                topLevel->reinstallMacDock();
                break;
            }
        }
    }
#endif
    delete _data;
}

void QCGTopLevel::reinstallMacDock()
{
#ifdef Q_OS_MAC
    if (macDockMenu != nullptr) {
        this->macDockMenu->setAsDockMenu();
    }
#endif
}

// reset the visualization state, e.g. before loading new data
void QCGTopLevel::resetState()
{
    _activeParts.clear();
    _hiddenParts.clear();

    _data = nullptr;
    _function = nullptr;
    _eventType = nullptr;
    _eventType2 = nullptr;
    _groupType = ProfileContext::InvalidType;
    _group = nullptr;

    // for delayed slots
    _traceItemDelayed = nullptr;
    _eventTypeDelayed = nullptr;
    _eventType2Delayed = nullptr;
    _groupTypeDelayed = ProfileContext::InvalidType;
    _groupDelayed = nullptr;
    _directionDelayed = TraceItemView::None;
    _lastSender = nullptr;
}


/**
 * This saves the current state of the main window and
 * sub widgets.
 *
 * No positions are saved. These is done automatically for
 * KToolbar, and manually in queryExit() for QT docks.
 */
void QCGTopLevel::saveCurrentState(const QString& postfix)
{
    QString eventType, eventType2;
    if (_eventType) eventType = _eventType->name();
    if (_eventType2) eventType2 = _eventType2->name();

    ConfigGroup* stateConfig = ConfigStorage::group(QLatin1String("CurrentState") + postfix);
    stateConfig->setValue(QStringLiteral("EventType"), eventType);
    stateConfig->setValue(QStringLiteral("EventType2"), eventType2);
    stateConfig->setValue(QStringLiteral("GroupType"), ProfileContext::typeName(_groupType));
    delete stateConfig;

    _partSelection->saveOptions(QStringLiteral("PartOverview"), postfix);
    _multiView->saveLayout(QStringLiteral("MainView"), postfix);
    _multiView->saveOptions(QStringLiteral("MainView"), postfix);
}

/**
 * This function is called when a trace is closed.
 * Save browsing position for later restoring
 */
void QCGTopLevel::saveTraceSettings()
{
    QString key = traceKey();

    ConfigGroup* lConfig = ConfigStorage::group(QStringLiteral("Layouts"));
    lConfig->setValue(QStringLiteral("Count%1").arg(key), _layoutCount);
    lConfig->setValue(QStringLiteral("Current%1").arg(key), _layoutCurrent);
    delete lConfig;

    ConfigGroup* pConfig = ConfigStorage::group(QStringLiteral("TracePositions"));
    QString eventType, eventType2;
    if (_eventType) eventType = _eventType->name();
    if (_eventType2) eventType2 = _eventType2->name();
    pConfig->setValue(QStringLiteral("EventType%1").arg(key), eventType);
    pConfig->setValue(QStringLiteral("EventType2%1").arg(key), eventType2);
    if (_groupType != ProfileContext::InvalidType)
        pConfig->setValue(QStringLiteral("GroupType%1").arg(key),
                          ProfileContext::typeName(_groupType));

    if (_data) {
        if (_group)
            pConfig->setValue(QStringLiteral("Group%1").arg(key), _group->name());
        saveCurrentState(key);
    }
    delete pConfig;
}

/**
 * This restores the current visualization state of the main window and
 * of the profile views.
 */
void QCGTopLevel::restoreCurrentState(const QString& postfix)
{
    _partSelection->restoreOptions(QStringLiteral("PartOverview"), postfix);
    _multiView->restoreLayout(QStringLiteral("MainView"), postfix);
    _multiView->restoreOptions(QStringLiteral("MainView"), postfix);

    _splittedToggleAction->setChecked(_multiView->childCount()>1);
    _splitDirectionToggleAction->setEnabled(_multiView->childCount()>1);
    _splitDirectionToggleAction->setChecked(_multiView->orientation() ==
                                            Qt::Horizontal);
}

void QCGTopLevel::sidebarMenuAboutToShow()
{
    QAction* action;
    QMenu *popup = _sidebarMenuAction->menu();

    popup->clear();

    action = popup->addAction(tr("Parts Overview"));
    action->setCheckable(true);
    action->setChecked(_partDock->isVisible());
    connect(action, &QAction::triggered, this, &QCGTopLevel::togglePartDock);

    action = popup->addAction(tr("Top Cost Call Stack"));
    action->setCheckable(true);
    action->setChecked(_stackDock->isVisible());
    connect(action, &QAction::triggered, this, &QCGTopLevel::toggleStackDock);

    action = popup->addAction(tr("Flat Profile"));
    action->setCheckable(true);
    action->setChecked(_functionDock->isVisible());
    connect(action, &QAction::triggered, this, &QCGTopLevel::toggleFunctionDock);
}

void QCGTopLevel::recentFilesMenuAboutToShow()
{
    QStringList recentFiles;
    QMenu *popup = _recentFilesMenuAction->menu();

    popup->clear();

    ConfigGroup* generalConfig = ConfigStorage::group(QStringLiteral("GeneralSettings"));
    recentFiles = generalConfig->value(QStringLiteral("RecentFiles"),
                                       QStringList()).toStringList();
    delete generalConfig;

    if (recentFiles.isEmpty())
        popup->addAction(tr("(No recent files)"));
    else {
        foreach(const QString& file, recentFiles) {
            // paths shown to user should use OS-native separators
            popup->addAction(QDir::toNativeSeparators(file));
        }
    }
}

void QCGTopLevel::recentFilesTriggered(QAction* action)
{
    if (action)
        load(QStringList(QDir::fromNativeSeparators(action->text())));
}

void QCGTopLevel::primaryAboutToShow()
{
    updateEventTypeMenu(_primaryMenuAction->menu(), false);
}

void QCGTopLevel::secondaryAboutToShow()
{
    updateEventTypeMenu(_secondaryMenuAction->menu(), true);
}

void QCGTopLevel::groupingAboutToShow()
{
    if (!_functionSelection) return;
    _functionSelection->updateGroupingMenu(_groupingMenuAction->menu());
}



void QCGTopLevel::createDocks()
{
    // part visualization/selection side bar
    _partDock = new QDockWidget(this);
    _partDock->setObjectName(QStringLiteral("part-dock"));
    _partDock->setWindowTitle(tr("Parts Overview"));
    _partSelection = new PartSelection(this, _partDock);
    _partDock->setWidget(_partSelection);

    connect(_partSelection, &PartSelection::partsHideSelected,
            this, &QCGTopLevel::partsHideSelectedSlotDelayed);
    connect(_partSelection, &PartSelection::partsUnhideAll,
            this, &QCGTopLevel::partsUnhideAllSlotDelayed);

    // stack selection side bar
    _stackDock = new QDockWidget(this);
    _stackDock->setObjectName(QStringLiteral("stack-dock"));
    _stackSelection = new StackSelection(_stackDock);
    _stackDock->setWidget(_stackSelection);
    _stackDock->setWindowTitle(tr("Top Cost Call Stack"));
    _stackSelection->setWhatsThis( tr(
                                       "<b>The Top Cost Call Stack</b>"
                                       "<p>This is a purely fictional 'most probable' call stack. "
                                       "It is built up by starting with the current selected "
                                       "function and adds the callers/callees with highest cost "
                                       "at the top and to bottom.</p>"
                                       "<p>The <b>Cost</b> and <b>Calls</b> columns show the "
                                       "cost used for all calls from the function in the line "
                                       "above.</p>"));
    connect(_stackSelection, SIGNAL(functionSelected(CostItem*)),
            this, SLOT(setTraceItemDelayed(CostItem*)));
    // actions are already created
    connect(_upAction, &QAction::triggered,
            _stackSelection, &StackSelection::browserUp );
    connect(_backAction, &QAction::triggered,
            _stackSelection, &StackSelection::browserBack );
    connect(_forwardAction, &QAction::triggered,
            _stackSelection, &StackSelection::browserForward);

    // flat function profile side bar
    _functionDock = new QDockWidget(this);
    _functionDock->setObjectName(QStringLiteral("function-dock"));
    _functionDock->setWindowTitle(tr("Flat Profile"));
    _functionSelection = new FunctionSelection(this, _functionDock);
    _functionDock->setWidget(_functionSelection);
    // functionDock needs call to updateView() when getting visible
    connect(_functionDock, &QDockWidget::visibilityChanged,
            this, &QCGTopLevel::functionVisibilityChanged);

    // defaults (later to be adjusted from stored state in config)
    addDockWidget(Qt::LeftDockWidgetArea, _partDock );
    addDockWidget(Qt::LeftDockWidgetArea, _stackDock );
    addDockWidget(Qt::LeftDockWidgetArea, _functionDock );
    _stackDock->hide();
    _partDock->hide();
}




void QCGTopLevel::createActions()
{
    QString hint;
    QIcon icon;

    // file menu actions
    _newAction = new QAction(tr("&New"), this);
    _newAction->setShortcuts(QKeySequence::New);
    _newAction->setStatusTip(tr("Open new empty window"));
    connect(_newAction, &QAction::triggered, this, &QCGTopLevel::newWindow);

    icon = QApplication::style()->standardIcon(QStyle::SP_DialogOpenButton);
    _openAction = new QAction(icon, tr("&Open..."), this);
    _openAction->setShortcuts(QKeySequence::Open);
    _openAction->setStatusTip(tr("Open profile data file"));
    connect(_openAction, SIGNAL(triggered()), this, SLOT(load()));

    _closeAction = new QAction(tr("&Close"), this);
    _closeAction->setShortcuts(QKeySequence::Close);
    _closeAction->setStatusTip(tr("Close the current window"));
    connect(_closeAction, SIGNAL(triggered()), this, SLOT(close()));

    _addAction = new QAction(tr( "&Add..." ), this);
    _addAction->setStatusTip(tr("Add profile data to current window"));
    connect(_addAction, SIGNAL(triggered(bool)), SLOT(add()));

    _exportAction = new QAction(tr("Export Graph"), this);
    _exportAction->setStatusTip(tr("Generate GraphViz file 'callgraph.dot'"));
    connect(_exportAction, &QAction::triggered, this, &QCGTopLevel::exportGraph);

    _recentFilesMenuAction = new QAction(tr("Open &Recent"), this);
    _recentFilesMenuAction->setMenu(new QMenu(this));
    connect(_recentFilesMenuAction->menu(), &QMenu::aboutToShow,
            this, &QCGTopLevel::recentFilesMenuAboutToShow);
    connect(_recentFilesMenuAction->menu(), &QMenu::triggered,
            this, &QCGTopLevel::recentFilesTriggered);

    _exitAction = new QAction(tr("E&xit"), this);
    _exitAction->setMenuRole(QAction::QuitRole);
    _exitAction->setShortcut(tr("Ctrl+Q"));
    _exitAction->setStatusTip(tr("Exit the application"));
    connect(_exitAction, &QAction::triggered, this, &QApplication::closeAllWindows);

    // view menu actions

    _primaryMenuAction = new QAction(tr( "Primary Event Type" ), this );
    _primaryMenuAction->setMenu(new QMenu(this));
    connect(_primaryMenuAction->menu(), &QMenu::aboutToShow,
            this, &QCGTopLevel::primaryAboutToShow );
    _secondaryMenuAction = new QAction(tr( "Secondary Event Type" ), this );
    _secondaryMenuAction->setMenu(new QMenu(this));
    connect(_secondaryMenuAction->menu(), &QMenu::aboutToShow,
            this, &QCGTopLevel::secondaryAboutToShow );
    _groupingMenuAction = new QAction(tr( "Grouping" ), this );
    _groupingMenuAction->setMenu(new QMenu(this));
    connect(_groupingMenuAction->menu(), &QMenu::aboutToShow,
            this, &QCGTopLevel::groupingAboutToShow );

    icon = QApplication::style()->standardIcon(QStyle::SP_BrowserReload);
    _cyclesToggleAction = new QAction(icon, tr("Detect Cycles"), this);
    _cyclesToggleAction->setCheckable(true);
    _cyclesToggleAction->setStatusTip(tr("Do Cycle Detection"));
    hint = tr("<b>Detect recursive cycles</b>"
              "<p>If this is switched off, the treemap drawing will show "
              "black areas when a recursive call is made instead of drawing "
              "the recursion ad infinitum. Note that "
              "the size of black areas often will be wrong, as inside "
              "recursive cycles the cost of calls cannot be determined; "
              "the error is small, "
              "however, for false cycles (see documentation).</p>"
              "<p>The correct handling for cycles is to detect them and "
              "collapse all functions of a cycle into an artificial "
              "function, which is done when this option is selected. "
              "Unfortunately, with GUI applications, this often will "
              "lead to huge false cycles, making the analysis impossible; "
              "therefore, there is the option to switch this off.</p>");
    _cyclesToggleAction->setWhatsThis(hint);
    connect(_cyclesToggleAction, &QAction::triggered,
            this, &QCGTopLevel::toggleCycles);
    _cyclesToggleAction->setChecked(GlobalConfig::showCycles());

    _percentageToggleAction = new QAction(QIcon(QStringLiteral(":/percent.png")),
                                          tr("Relative Cost"), this);
    _percentageToggleAction->setCheckable(true);
    _percentageToggleAction->setStatusTip(tr("Show Relative Costs"));
    connect(_percentageToggleAction, &QAction::triggered,
            this, &QCGTopLevel::togglePercentage);
    _percentageToggleAction->setChecked(GlobalConfig::showPercentage());

    _hideTemplatesToggleAction = new QAction(QIcon(QStringLiteral(":/hidetemplates.png")),
                                             tr("Shorten Templates"), this);
    _hideTemplatesToggleAction->setCheckable(true);
    _hideTemplatesToggleAction->setStatusTip(tr("Hide Template Parameters "
                                                "in C++ Symbols"));
    connect(_hideTemplatesToggleAction, &QAction::triggered,
            this, &QCGTopLevel::toggleHideTemplates);
    _hideTemplatesToggleAction->setChecked(GlobalConfig::hideTemplates());
    hint = tr("<b>Hide Template Parameters in C++ Symbols</b>"
              "<p>If this is switched on, every symbol displayed will have "
              "any C++ template parameters hidden, just showing &lt;&gt; "
              "instead of a potentially nested template parameter.</p>"
              "<p>In this mode, you can hover the mouse pointer over the "
              "activated symbol label to show a tooltip with the "
              "unabbreviated symbol.</p>");
    _hideTemplatesToggleAction->setWhatsThis(hint);

    _expandedToggleAction = new QAction(QIcon(QStringLiteral(":/move.png")),
                                        tr("Relative to Parent"), this);
    _expandedToggleAction->setCheckable(true);
    _expandedToggleAction->setStatusTip(
                tr("Show Percentage relative to Parent"));
    hint = tr("<b>Show percentage costs relative to parent</b>"
              "<p>If this is switched off, percentage costs are always "
              "shown relative to the total cost of the profile part(s) "
              "that are currently browsed. By turning on this option, "
              "percentage cost of shown cost items will be relative "
              "to the parent cost item.</p>"
              "<ul><table>"
              "<tr><td><b>Cost Type</b></td><td><b>Parent Cost</b></td></tr>"
              "<tr><td>Function Inclusive</td><td>Total</td></tr>"
              "<tr><td>Function Self</td><td>Function Group (*)/Total</td></tr>"
              "<tr><td>Call</td><td>Function Inclusive</td></tr>"
              "<tr><td>Source Line</td><td>Function Inclusive</td></tr>"
              "</table></ul>"
              "<p>(*) Only if function grouping is switched on "
              "(e.g. ELF object grouping).</p>");
    _expandedToggleAction->setWhatsThis( hint );
    connect(_expandedToggleAction, &QAction::triggered,
            this, &QCGTopLevel::toggleExpanded);
    _expandedToggleAction->setChecked(GlobalConfig::showExpanded());

    _splittedToggleAction = new QAction(tr("Split Visualization"), this);
    _splittedToggleAction->setCheckable(true);
    _splittedToggleAction->setStatusTip(
                tr("Show visualization of two cost items"));
    connect(_splittedToggleAction, &QAction::triggered,
            this, &QCGTopLevel::toggleSplitted);

    _splitDirectionToggleAction = new QAction(tr("Split Horizontal"), this);
    _splitDirectionToggleAction->setCheckable(true);
    _splitDirectionToggleAction->setStatusTip(
                tr("Split visualization area horizontally"));
    connect(_splitDirectionToggleAction, &QAction::triggered,
            this, &QCGTopLevel::toggleSplitDirection);

    _sidebarMenuAction = new QAction(tr("Sidebars"), this);
    _sidebarMenuAction->setMenu(new QMenu(this));
    connect( _sidebarMenuAction->menu(), &QMenu::aboutToShow,
             this, &QCGTopLevel::sidebarMenuAboutToShow);

    _layoutDup = new QAction(tr("&Duplicate"), this);
    connect(_layoutDup, &QAction::triggered, this, &QCGTopLevel::layoutDuplicate);
    _layoutDup->setShortcut(Qt::CTRL | Qt::Key_Plus);
    _layoutDup->setStatusTip(tr("Duplicate current layout"));

    _layoutRemove = new QAction(tr("&Remove"), this);
    connect(_layoutRemove, &QAction::triggered, this, &QCGTopLevel::layoutRemove);
    _layoutRemove->setStatusTip(tr("Remove current layout"));

    _layoutNext = new QAction(tr("Go to &Next"), this);
    connect(_layoutNext, &QAction::triggered, this, &QCGTopLevel::layoutNext);
    _layoutNext->setShortcut(Qt::CTRL | Qt::Key_Right);
    _layoutNext->setStatusTip(tr("Switch to next layout"));

    _layoutPrev = new QAction(tr("Go to &Previous"), this);
    connect(_layoutPrev, &QAction::triggered, this, &QCGTopLevel::layoutPrevious);
    _layoutPrev->setShortcut(Qt::CTRL | Qt::Key_Left);
    _layoutPrev->setStatusTip(tr("Switch to previous layout"));

    _layoutRestore = new QAction(tr("&Restore to Default"), this);
    connect(_layoutRestore, &QAction::triggered, this, &QCGTopLevel::layoutRestore);
    _layoutRestore->setStatusTip(tr("Restore layouts to default"));

    _layoutSave = new QAction(tr("&Save as Default"), this);
    connect(_layoutSave, &QAction::triggered, this, &QCGTopLevel::layoutSave);
    _layoutSave->setStatusTip(tr("Save layouts as default"));

    // go menu actions
    icon = QApplication::style()->standardIcon(QStyle::SP_ArrowUp);
    _upAction = new QAction(icon, tr( "Up" ), this );
    _upAction->setShortcut( QKeySequence(Qt::ALT | Qt::Key_Up) );
    _upAction->setStatusTip(tr("Go Up in Call Stack"));
    _upAction->setMenu(new QMenu(this));
    connect(_upAction->menu(), &QMenu::aboutToShow,
            this, &QCGTopLevel::upAboutToShow );
    connect(_upAction->menu(), &QMenu::triggered,
            this, &QCGTopLevel::upTriggered );
    hint = tr("Go to last selected caller of current function");
    _upAction->setToolTip(hint);

    icon = QApplication::style()->standardIcon(QStyle::SP_ArrowBack);
    _backAction = new QAction(icon, tr("Back"), this);
    _backAction->setShortcut( QKeySequence(Qt::ALT | Qt::Key_Left) );
    _backAction->setStatusTip(tr("Go Back"));
    _backAction->setMenu(new QMenu(this));
    connect(_backAction->menu(), &QMenu::aboutToShow,
            this, &QCGTopLevel::backAboutToShow );
    connect(_backAction->menu(), &QMenu::triggered,
            this, &QCGTopLevel::backTriggered );
    hint = tr("Go back in function selection history");
    _backAction->setToolTip(hint);

    icon = QApplication::style()->standardIcon(QStyle::SP_ArrowForward);
    _forwardAction = new QAction(icon, tr("Forward"), this);
    _forwardAction->setShortcut( QKeySequence(Qt::ALT | Qt::Key_Right) );
    _forwardAction->setStatusTip(tr("Go Forward"));
    _forwardAction->setMenu(new QMenu(this));
    connect(_forwardAction->menu(), &QMenu::aboutToShow,
            this, &QCGTopLevel::forwardAboutToShow );
    connect(_forwardAction->menu(), &QMenu::triggered,
            this, &QCGTopLevel::forwardTriggered );
    hint = tr("Go forward in function selection history");
    _forwardAction->setToolTip( hint );

    // settings menu actions
    _configureAction = new QAction(tr("&Configure..."), this);
    _configureAction->setMenuRole(QAction::PreferencesRole);
    _configureAction->setStatusTip(tr("Configure QCachegrind"));
    connect(_configureAction, SIGNAL(triggered()), this, SLOT(configure()));

    // window menu actions
    _minimizeAction = new QAction(tr("&Minimize"), this);
    _minimizeAction->setShortcut(Qt::CTRL | Qt::Key_M);
    connect(_minimizeAction, &QAction::triggered, this, &QWidget::showMinimized);

    _zoomAction = new QAction(tr("&Zoom"), this);
    // on macOS, zoom doesn't have exactly the same semantics as maximize
    // (it creates the best fit), but maximize is the usual copout answer
    connect(_zoomAction, &QAction::triggered, this, &QWidget::showMaximized);

    // help menu actions
    _aboutAction = new QAction(tr("&About QCachegrind..."), this);
    _aboutAction->setMenuRole(QAction::AboutRole);
    _aboutAction->setStatusTip(tr("Show the application's About box"));
    connect(_aboutAction, &QAction::triggered, this, &QCGTopLevel::about);

    _aboutQtAction = new QAction(tr("About Qt..."), this);
    _aboutQtAction->setMenuRole(QAction::AboutQtRole);
    connect(_aboutQtAction, &QAction::triggered, qApp, &QApplication::aboutQt);

    // toolbar actions
    _eventTypeBox = new QComboBox(this);
    _eventTypeBox->setMinimumContentsLength(25);
    hint = tr("Select primary event type of costs");
    _eventTypeBox->setToolTip( hint );
    connect( _eventTypeBox, &QComboBox::textActivated,
             this, &QCGTopLevel::eventTypeSelected);
}

void QCGTopLevel::createMenu()
{
    QMenuBar* mBar = menuBar();

    QMenu* fileMenu = mBar->addMenu(tr("&File"));
    fileMenu->addAction(_newAction);
    fileMenu->addAction(_openAction);
    fileMenu->addAction(_recentFilesMenuAction);
    fileMenu->addAction(_addAction);
    fileMenu->addSeparator();
    fileMenu->addAction(_exportAction);
    fileMenu->addSeparator();
    fileMenu->addAction(_closeAction);
    fileMenu->addSeparator();
    fileMenu->addAction(_exitAction);

    QMenu* layoutMenu = new QMenu(tr("&Layout"), this);
    layoutMenu->addAction(_layoutDup);
    layoutMenu->addAction(_layoutRemove);
    layoutMenu->addSeparator();
    layoutMenu->addAction(_layoutPrev);
    layoutMenu->addAction(_layoutNext);
    layoutMenu->addSeparator();
    layoutMenu->addAction(_layoutSave);
    layoutMenu->addAction(_layoutRestore);

    QMenu* viewMenu = mBar->addMenu(tr("&View"));
    viewMenu->addAction(_primaryMenuAction);
    viewMenu->addAction(_secondaryMenuAction);
    viewMenu->addAction(_groupingMenuAction);
    viewMenu->addSeparator();
    viewMenu->addAction(tb->toggleViewAction());
    viewMenu->addMenu(layoutMenu);
    viewMenu->addAction(_sidebarMenuAction);
    viewMenu->addAction(_splittedToggleAction);
    viewMenu->addAction(_splitDirectionToggleAction);
    viewMenu->addSeparator();
    viewMenu->addAction(_cyclesToggleAction);
    viewMenu->addAction(_percentageToggleAction);
    viewMenu->addAction(_expandedToggleAction);
    viewMenu->addAction(_hideTemplatesToggleAction);
    viewMenu->addSeparator();
    viewMenu->addAction(_configureAction);

    QMenu* goMenu = mBar->addMenu(tr("&Go"));
    goMenu->addAction(_backAction);
    goMenu->addAction(_forwardAction);
    goMenu->addAction(_upAction);
    fileMenu->addAction(_exitAction);

#ifdef Q_OS_MAC
    // class level for ease of manipulation
    this->windowMenu = mBar->addMenu(tr("&Window"));
    this->windowMenu->addAction(_minimizeAction);
    this->windowMenu->addAction(_zoomAction);
    connect(this->windowMenu, &QMenu::aboutToShow, this, &QCGTopLevel::windowListAboutToShow);
    connect(this->windowMenu, &QMenu::triggered, this, &QCGTopLevel::windowListTriggered);

    // right-clicking the dock icon should be a window list
    this->macDockMenu = new QMenu(this);
    connect(this->macDockMenu, &QMenu::aboutToShow, this, &QCGTopLevel::macDockMenuAboutToShow);
    // it can reuse the same events, it just needs a diff menu structure
    connect(this->macDockMenu, &QMenu::triggered, this, &QCGTopLevel::windowListTriggered);
    reinstallMacDock();
#endif

    QMenu* helpMenu = mBar->addMenu(tr("&Help"));
    helpMenu->addAction(QWhatsThis::createAction(this));
    helpMenu->addSeparator();
    helpMenu->addAction(_aboutAction);
    helpMenu->addAction(_aboutQtAction);
}

void QCGTopLevel::createToolbar()
{
    tb = new QToolBar(tr("Main Toolbar"), this);
    tb->setObjectName(QStringLiteral("main-toolbar"));
    addToolBar(Qt::TopToolBarArea, tb);

    tb->addAction(_openAction);
    tb->addSeparator();

    tb->addAction(_cyclesToggleAction);
    tb->addAction(_percentageToggleAction);
    tb->addAction(_expandedToggleAction);
    tb->addAction(_hideTemplatesToggleAction);
    tb->addSeparator();

    tb->addAction(_backAction);
    tb->addAction(_forwardAction);
    tb->addAction(_upAction);
    tb->addSeparator();

    tb->addWidget(_eventTypeBox);
}


void QCGTopLevel::about()
{
    QString text, version;
    version = QStringLiteral("0.8.0kde");
    text = QStringLiteral("<h3>QCachegrind %1</h3>").arg(version);
    text += tr("<p>QCachegrind is a graphical user interface for analysing "
               "profiling data, which helps in the performance optimization "
               "phase of developing a computer program. "
               "QCachegrind is open-source, and it is distributed under the "
               "terms of the GPL v2. For details and source code, see the "
               "<a href=\"https://kcachegrind.github.io\">homepage</a> of the "
               "KCachegrind project.</p>"
               "Main author and maintainer: "
               "<a href=\"mailto:Josef.Weidendorfer@gmx.de\">"
               "Josef Weidendorfer</a><br>"
               "(with lots of bug fixes/porting help by the KDE community)");
    QMessageBox::about(this, tr("About QCachegrind"), text);
}

void QCGTopLevel::configure(QString s)
{
    static QString lastPage;

    // if no specific config item should be focused, use last page
    if (s.isEmpty()) s = lastPage;
    ConfigDialog d(_data, this, s);

    if (d.exec() == QDialog::Accepted) {
        GlobalConfig::config()->saveOptions();
        configChanged();
    }
    lastPage = d.currentPage();
}

void QCGTopLevel::togglePartDock()
{
    if (!_partDock->isVisible())
        _partDock->show();
    else
        _partDock->hide();
}

void QCGTopLevel::toggleStackDock()
{
    if (!_stackDock->isVisible())
        _stackDock->show();
    else
        _stackDock->hide();
}

void QCGTopLevel::toggleFunctionDock()
{
    if (!_functionDock->isVisible())
        _functionDock->show();
    else
        _functionDock->hide();
}

void QCGTopLevel::togglePercentage()
{
    setPercentage(_percentageToggleAction->isChecked());
}


void QCGTopLevel::setAbsoluteCost()
{
    setPercentage(false);
}

void QCGTopLevel::setRelativeCost()
{
    setPercentage(true);
}

void QCGTopLevel::setPercentage(bool show)
{
    if (GlobalConfig::showPercentage() == show) return;
    if (_percentageToggleAction->isChecked() != show)
        _percentageToggleAction->setChecked(show);
    _expandedToggleAction->setEnabled(show);
    GlobalConfig::setShowPercentage(show);

    _partSelection->notifyChange(TraceItemView::configChanged);
    _stackSelection->refresh();
    _functionSelection->notifyChange(TraceItemView::configChanged);
    _multiView->notifyChange(TraceItemView::configChanged);
}

void QCGTopLevel::toggleHideTemplates()
{
    bool show = _hideTemplatesToggleAction->isChecked();
    if (GlobalConfig::hideTemplates() == show) return;
    GlobalConfig::setHideTemplates(show);

    _partSelection->notifyChange(TraceItemView::configChanged);
    _stackSelection->refresh();
    _functionSelection->notifyChange(TraceItemView::configChanged);
    _multiView->notifyChange(TraceItemView::configChanged);
}

void QCGTopLevel::toggleExpanded()
{
    bool show = _expandedToggleAction->isChecked();
    if (GlobalConfig::showExpanded() == show) return;
    GlobalConfig::setShowExpanded(show);

    _partSelection->notifyChange(TraceItemView::configChanged);
    _stackSelection->refresh();
    _functionSelection->notifyChange(TraceItemView::configChanged);
    _multiView->notifyChange(TraceItemView::configChanged);
}

void QCGTopLevel::toggleCycles()
{
    bool show = _cyclesToggleAction->isChecked();
    if (GlobalConfig::showCycles() == show) return;
    GlobalConfig::setShowCycles(show);

    if (!_data) return;

    _data->invalidateDynamicCost();
    _data->updateFunctionCycles();

    _partSelection->notifyChange(TraceItemView::configChanged);
    _stackSelection->rebuildStackList();
    _functionSelection->notifyChange(TraceItemView::configChanged);
    _multiView->notifyChange(TraceItemView::configChanged);
}


void QCGTopLevel::functionVisibilityChanged(bool v)
{
    if (v)
        _functionSelection->updateView();
}


void QCGTopLevel::newWindow()
{
    QCGTopLevel* t = new QCGTopLevel();
    t->show();
}


void QCGTopLevel::load()
{
    QStringList files;
    files = QFileDialog::getOpenFileNames(this,
                                          tr("Open Callgrind Data"),
                                          _lastFile,
                                          tr("Callgrind Files (callgrind.* cachegrind.*);;All Files (*)"));
    load(files);
}

void QCGTopLevel::load(QStringList files, bool addToRecentFiles)
{
    if (files.isEmpty()) return;
    _lastFile = files[0];

    if (_data && _data->parts().count()>0) {

        // In new window
        QCGTopLevel* t = new QCGTopLevel();
        t->show();
        t->loadDelayed(files, addToRecentFiles);
        return;
    }

    // this constructor enables progress bar callbacks
    TraceData* d = new TraceData(this);
    int filesLoaded = d->load(files);
    if (filesLoaded >0)
        setData(d);

    if (!addToRecentFiles) return;

    // add to recent file list in config
    QStringList recentFiles;
    ConfigGroup* generalConfig = ConfigStorage::group(QStringLiteral("GeneralSettings"));
    recentFiles = generalConfig->value(QStringLiteral("RecentFiles"),
                                       QStringList()).toStringList();
    foreach(const QString& file, files) {
        recentFiles.removeAll(file);
        if (filesLoaded >0)
            recentFiles.prepend(file);
        if (recentFiles.count() >5)
            recentFiles.removeLast();
    }
    generalConfig->setValue(QStringLiteral("RecentFiles"), recentFiles);
    delete generalConfig;
}


void QCGTopLevel::add()
{
    QStringList files;
    files = QFileDialog::getOpenFileNames(this,
                                          tr("Add Callgrind Data"),
                                          _lastFile,
                                          tr("Callgrind Files (callgrind.*);;All Files (*)"));
    add(files);
}


void QCGTopLevel::add(QStringList files)
{
    if (files.isEmpty()) return;
    _lastFile = files[0];

    if (_data) {
        _data->load(files);

        // GUI update for added data
        configChanged();
        return;
    }

    // this constructor enables progress bar callbacks
    TraceData* d = new TraceData(this);
    int filesLoaded = d->load(files);
    if (filesLoaded >0)
        setData(d);
}

void QCGTopLevel::loadDelayed(QString file, bool addToRecentFiles)
{
    _loadFilesDelayed << file;

    _addToRecentFiles = addToRecentFiles;
    QTimer::singleShot(0, this, &QCGTopLevel::loadFilesDelayed);
}

void QCGTopLevel::loadDelayed(QStringList files, bool addToRecentFiles)
{
    _loadFilesDelayed << files;

    _addToRecentFiles = addToRecentFiles;
    QTimer::singleShot(0, this, &QCGTopLevel::loadFilesDelayed);
}

void QCGTopLevel::loadFilesDelayed()
{
    if (_loadFilesDelayed.isEmpty()) return;

    load(_loadFilesDelayed, _addToRecentFiles);
    _loadFilesDelayed.clear();
}


void QCGTopLevel::exportGraph()
{
    if (!_data || !_function) return;

    GraphExporter::savePrompt(this, _data, _function, _eventType, _groupType, nullptr);
}


void QCGTopLevel::setEventType(QString s)
{
    EventType* ct;

    ct = (_data) ? _data->eventTypes()->type(s) : nullptr;

    // if costtype with given name not found, use first available
    if (!ct && _data) ct = _data->eventTypes()->type(0);

    setEventType(ct);
}

void QCGTopLevel::setEventType2(QString s)
{
    EventType* ct;

    // Special type tr("(Hidden)") gives 0
    ct = (_data) ? _data->eventTypes()->type(s) : nullptr;

    setEventType2(ct);
}

void QCGTopLevel::eventTypeSelected(const QString& s)
{
    EventType* ct;

    ct = (_data) ? _data->eventTypes()->typeForLong(s) : nullptr;
    setEventType(ct);
}

void QCGTopLevel::eventType2Selected(const QString& s)
{
    EventType* ct;

    ct = (_data) ? _data->eventTypes()->typeForLong(s) : nullptr;
    setEventType2(ct);
}

void QCGTopLevel::setEventType(EventType* ct)
{
    if (_eventType == ct) return;

    _eventType = ct;

    if (ct) {
        int idx = _eventTypeBox->findText(ct->longName());
        if (idx >=0) _eventTypeBox->setCurrentIndex(idx);
    }

    _partSelection->setEventType(_eventType);
    _stackSelection->setEventType(_eventType);
    _functionSelection->setEventType(_eventType);
    _multiView->setEventType(_eventType);

    updateStatusBar();
}

void QCGTopLevel::setEventType2(EventType* ct)
{
    if (_eventType2 == ct) return;

    _eventType2 = ct;

    _partSelection->setEventType2(_eventType2);
    _stackSelection->setEventType2(_eventType2);
    _functionSelection->setEventType2(_eventType2);
    _multiView->setEventType2(_eventType2);

    updateStatusBar();
}


void QCGTopLevel::groupTypeSelected(int cg)
{
    switch(cg) {
    case 0: setGroupType( ProfileContext::Function ); break;
    case 1: setGroupType( ProfileContext::Object ); break;
    case 2: setGroupType( ProfileContext::File ); break;
    case 3: setGroupType( ProfileContext::Class ); break;
    case 4: setGroupType( ProfileContext::FunctionCycle ); break;
    default: break;
    }
}

bool QCGTopLevel::setGroupType(QString s)
{
    ProfileContext::Type gt;

    gt = ProfileContext::type(s);
    // only allow Function/Object/File/Class as grouptype
    switch(gt) {
    case ProfileContext::Object:
    case ProfileContext::File:
    case ProfileContext::Class:
    case ProfileContext::FunctionCycle:
        break;
    default:
        gt = ProfileContext::Function;
    }

    return setGroupType(gt);
}

bool QCGTopLevel::setGroupType(ProfileContext::Type gt)
{
    if (_groupType == gt) return false;
    _groupType = gt;

    int idx = -1;
    switch(gt) {
    case ProfileContext::Function: idx = 0; break;
    case ProfileContext::Object: idx = 1; break;
    case ProfileContext::File: idx = 2; break;
    case ProfileContext::Class: idx = 3; break;
    case ProfileContext::FunctionCycle: idx = 4; break;
    default:
        break;
    }

    if (idx==-1) return false;

#if 0
    if (saGroup->currentItem() != idx)
        saGroup->setCurrentItem(idx);
#endif

    _stackSelection->setGroupType(_groupType);

    _partSelection->set(_groupType);
    _functionSelection->set(_groupType);
    _multiView->set(_groupType);

    updateStatusBar();

    return true;
}

bool QCGTopLevel::setGroup(QString s)
{
    TraceCostItem* ci = _functionSelection->group(s);
    if (!ci)
        return false;

    return setGroup(ci);
}


bool QCGTopLevel::setGroup(TraceCostItem* g)
{
    if (_group == g) return false;
    _group = g;

    _functionSelection->setGroup(g);
    updateStatusBar();

    return true;
}

bool QCGTopLevel::setFunction(QString s)
{
    if (!_data) return false;

    ProfileCostArray* f = _data->search(ProfileContext::Function, s, _eventType);
    if (!f) return false;

    return setFunction((TraceFunction*)f);
}

bool QCGTopLevel::setFunction(TraceFunction* f)
{
    if (_function == f) return false;
    _function = f;

    _multiView->activate(f);
    _functionSelection->activate(f);
    _partSelection->activate(f);
    _stackSelection->setFunction(_function);

    StackBrowser* b = _stackSelection->browser();
    if (b) {
        // do not disable up: a press forces stack-up extending...
        _forwardAction->setEnabled(b->canGoForward());
        _backAction->setEnabled(b->canGoBack());
    }

#if TRACE_UPDATES
    qDebug("QCGTopLevel::setFunction(%s), lastSender %s",
           f ? f->prettyName().toAscii() : "0",
           _lastSender ? _lastSender->name() :"0" );
#endif

    return true;
}


/**
 * Delayed versions.
 * We always have a pair of slots: One receiver to start the
 * delay with a singleShot Timer. It stores the parameter into a
 * temporary variable. And one parameterless slot for
 * forwarding, using this temporary.
 */
void QCGTopLevel::setEventTypeDelayed(EventType* ct)
{
    _eventTypeDelayed = ct;
    QTimer::singleShot (0, this, SLOT(setEventTypeDelayed()));
}

void QCGTopLevel::setEventType2Delayed(EventType* ct)
{
    _eventType2Delayed = ct;
    QTimer::singleShot (0, this, SLOT(setEventType2Delayed()));
}

void QCGTopLevel::setEventTypeDelayed()
{
    setEventType(_eventTypeDelayed);
}

void QCGTopLevel::setEventType2Delayed()
{
    setEventType2(_eventType2Delayed);
}

void QCGTopLevel::setGroupTypeDelayed(ProfileContext::Type gt)
{
    _groupTypeDelayed = gt;
    QTimer::singleShot (0, this, SLOT(setGroupTypeDelayed()));
}

void QCGTopLevel::setGroupTypeDelayed()
{
    setGroupType(_groupTypeDelayed);
}

void QCGTopLevel::setGroupDelayed(TraceCostItem* g)
{
#if TRACE_UPDATES
    qDebug("QCGTopLevel::setGroupDelayed(%s), sender %s",
           g ? g->prettyName().toAscii() : "0",
           _lastSender ? _lastSender->name() :"0" );
#endif

    _groupDelayed = g;
    QTimer::singleShot (0, this, SLOT(setGroupDelayed()));
}

void QCGTopLevel::setGroupDelayed()
{
    setGroup(_groupDelayed);
}

void QCGTopLevel::setDirectionDelayed(TraceItemView::Direction d)
{
    _directionDelayed = d;
    QTimer::singleShot (0, this, SLOT(setDirectionDelayed()));
}

void QCGTopLevel::setDirectionDelayed()
{
    switch(_directionDelayed) {
    case TraceItemView::Back:
        _stackSelection->browserBack();
        break;

    case TraceItemView::Forward:
        _stackSelection->browserForward();
        break;

    case TraceItemView::Up:
    {
        StackBrowser* b = _stackSelection ? _stackSelection->browser() : nullptr;
        HistoryItem* hi = b ? b->current() : nullptr;
        TraceFunction* f = hi ? hi->function() : nullptr;

        if (!f) break;
        f = hi->stack()->caller(f, false);
        if (f) setFunction(f);
    }
        break;

    default: break;
    }

    _directionDelayed = TraceItemView::None;
}


void QCGTopLevel::setTraceItemDelayed(CostItem* i)
{
    // no need to select same item a 2nd time...
    if (_traceItemDelayed == i) return;
    _traceItemDelayed = i;
    _lastSender = sender();

    qDebug() << "Selected " << (i ? i->fullName() : QStringLiteral("(none)"));

#if TRACE_UPDATES
    qDebug("QCGTopLevel::setTraceItemDelayed(%s), sender %s",
           i ? i->prettyName().toAscii() : "0",
           _lastSender ? _lastSender->name() :"0" );
#endif

    QTimer::singleShot (0, this, SLOT(setTraceItemDelayed()));
}

void QCGTopLevel::setTraceItemDelayed()
{
    if (!_traceItemDelayed) return;

    switch(_traceItemDelayed->type()) {
    case ProfileContext::Function:
    case ProfileContext::FunctionCycle:
        setFunction((TraceFunction*)_traceItemDelayed);
        break;

    case ProfileContext::Object:
    case ProfileContext::File:
    case ProfileContext::Class:
        _multiView->activate(_traceItemDelayed);
        break;

    case ProfileContext::Instr:
    case ProfileContext::Line:
        // only for multiview
        _multiView->activate(_traceItemDelayed);
        break;

    default: break;
    }

    _traceItemDelayed = nullptr;
    _lastSender = nullptr;
}

/**
 * A TraceData object cannot be viewed many times in different
 * toplevel windows. Thus, this toplevel window takes ownership
 * of the TraceData object: on closing the window or opening
 * another trace, the object is destroyed.
 */
void QCGTopLevel::setData(TraceData* data)
{
    if (data == _data) return;

    _lastSender = nullptr;

    saveTraceSettings();

    if (_data) {
        _partSelection->setData(nullptr);
        _stackSelection->setData(nullptr);
        _functionSelection->setData(nullptr);
        _multiView->setData(nullptr);
        _multiView->updateView(true);

        // we are the owner...
        delete _data;
    }

    // reset members
    resetState();

    _data = data;

    // fill cost type list
    QStringList types;

    if (_data) {
        /* add all supported virtual types */
        EventTypeSet* m = _data->eventTypes();
        m->addKnownDerivedTypes();

        /* first, fill selection list with available cost types */
        for (int i=0;i<m->realCount();i++)
            types << m->realType(i)->longName();
        for (int i=0;i<m->derivedCount();i++)
            types << m->derivedType(i)->longName();
    }
    _eventTypes = types;
    _eventTypeBox->addItems(types);

    _stackSelection->setData(_data);
    _partSelection->setData(_data);
    _functionSelection->setData(_data);
    _multiView->setData(_data);
    // Force update of _data in all children of _multiView
    // This is needed to make restoring of activeItem work!
    _multiView->updateView(true);

    /* this is needed to let the other widgets know the types */
    restoreTraceTypes();

    restoreTraceSettings();

    QString caption;
    if (_data) {
        caption = QDir::toNativeSeparators(_data->traceName());
        if (!_data->command().isEmpty())
            caption += " [" + _data->command() + ']';
    }
    setWindowFilePath(QDir::toNativeSeparators(_data->traceName()));
    setWindowTitle(caption);

    if (!_data || (!_forcePartDock && _data->parts().count()<2))
        _partDock->hide();
    else
        _partDock->show();

    updateStatusBar();
}

// Clears and repopulates the given menu with dynamic items for event types.
// Menu item handlers for setting the types are installed.
void QCGTopLevel::updateEventTypeMenu(QMenu* m, bool secondary)
{
    QAction* action;

    if (!m) return;
    m->clear();

    if (!_data) {
        // no data loaded yet
        m->addAction(tr("(None)"));
        return;
    }

    if (secondary) {
        connect(m, &QMenu::triggered,
                this, qOverload<QAction*>(&QCGTopLevel::setEventType2), Qt::UniqueConnection);

        if (_eventType2 != nullptr) {
            action = m->addAction(tr("Hide"));
            action->setData(199);
            m->addSeparator();
        }
    }
    else {
        connect(m, &QMenu::triggered,
                this, qOverload<QAction*>(&QCGTopLevel::setEventType), Qt::UniqueConnection);
    }

    EventTypeSet* ets = _data->eventTypes();
    EventType* et;
    EventType* selected = secondary ? _eventType2 : _eventType;
    for (int i = 0; i < ets->realCount(); i++) {
        et = ets->realType(i);

        action = m->addAction(et->longName());
        action->setCheckable(true);
        action->setData(100+i);
        if (et == selected) action->setChecked(true);
    }
    for (int i = 0; i < ets->derivedCount(); i++) {
        et = ets->derivedType(i);

        action = m->addAction(et->longName());
        action->setCheckable(true);
        action->setData(200+i);
        if (et == selected) action->setChecked(true);
    }
}

void QCGTopLevel::addEventTypeMenu(QMenu* popup, bool withCost2)
{
    if (_data) {
        QMenu* menu = popup->addMenu(tr("Primary Event Type"));
        updateEventTypeMenu(menu, false);

        if (withCost2) {
            QMenu* menu = popup->addMenu(tr("Secondary Event Type"));
            updateEventTypeMenu(menu, true);
        }
    }

    if (GlobalConfig::showPercentage())
        popup->addAction(tr("Show Absolute Cost"),
                         this, SLOT(setAbsoluteCost()));
    else
        popup->addAction(tr("Show Relative Cost"),
                         this, SLOT(setRelativeCost()));
}

void QCGTopLevel::setEventType(QAction* action)
{
    if (!_data) return;

    int id = action->data().toInt(nullptr);

    EventTypeSet* m = _data->eventTypes();
    EventType* ct=nullptr;
    if (id >=100 && id<199) ct = m->realType(id-100);
    if (id >=200 && id<299) ct = m->derivedType(id-200);

    if (ct)
        setEventType(ct);
}

void QCGTopLevel::setEventType2(QAction* action)
{
    if (!_data) return;

    int id = action->data().toInt(nullptr);

    EventTypeSet* m = _data->eventTypes();
    EventType* ct=nullptr;
    if (id >=100 && id<199) ct = m->realType(id-100);
    if (id >=200 && id<299) ct = m->derivedType(id-200);

    setEventType2(ct);
}

void QCGTopLevel::addGoMenu(QMenu* popup)
{
    StackBrowser* b = _stackSelection->browser();
    if (b) {
        if (b->canGoBack())
            popup->addAction(tr("Go Back"), this, SLOT(goBack()));
        if (b->canGoForward())
            popup->addAction(tr("Go Forward"), this, SLOT(goForward()));
    }
    // do not disable up: a press forces stack-up extending...
    popup->addAction(tr("Go Up"), this, SLOT(goUp()));
}

void QCGTopLevel::goBack()
{
    setDirectionDelayed(TraceItemView::Back);
}

void QCGTopLevel::goForward()
{
    setDirectionDelayed(TraceItemView::Forward);
}

void QCGTopLevel::goUp()
{
    setDirectionDelayed(TraceItemView::Up);
}

QString QCGTopLevel::traceKey()
{
    if (!_data || _data->command().isEmpty()) return QString();

    QString name = _data->command();
    QString key;
    for (int l=0;l<name.length();l++)
        if (name[l].isLetterOrNumber()) key += name[l];

    return QStringLiteral("-") + key;
}


void QCGTopLevel::restoreTraceTypes()
{
    QString key = traceKey();
    QString groupType, eventType, eventType2;

    ConfigGroup* pConfig = ConfigStorage::group(QStringLiteral("TracePositions"));
    groupType = pConfig->value(QStringLiteral("GroupType%1").arg(key),QString()).toString();
    eventType = pConfig->value(QStringLiteral("EventType%1").arg(key),QString()).toString();
    eventType2 = pConfig->value(QStringLiteral("EventType2%1").arg(key),QString()).toString();
    delete pConfig;

    ConfigGroup* cConfig = ConfigStorage::group(QStringLiteral("CurrentState"));
    if (groupType.isEmpty())
        groupType = cConfig->value(QStringLiteral("GroupType"),QString()).toString();
    if (eventType.isEmpty())
        eventType = cConfig->value(QStringLiteral("EventType"),QString()).toString();
    if (eventType2.isEmpty())
        eventType2 = cConfig->value(QStringLiteral("EventType2"),QString()).toString();
    delete cConfig;

    setGroupType(groupType);
    setEventType(eventType);
    setEventType2(eventType2);

    // if still no event type set, use first available
    if (!_eventType && !_eventTypes.isEmpty())
        eventTypeSelected(_eventTypes.first());

    ConfigGroup* aConfig = ConfigStorage::group(QStringLiteral("Layouts"));
    _layoutCount = aConfig->value(QStringLiteral("Count%1").arg(key), 0).toInt();
    _layoutCurrent = aConfig->value(QStringLiteral("Current%1").arg(key), 0).toInt();
    delete aConfig;

    if (_layoutCount == 0) layoutRestore();
    updateLayoutActions();
}


/**
 * This must be called after setting group/cost types in the function
 * selection widget, because the group/function choosing depends on
 * filled lists in the function selection widget
 */
void QCGTopLevel::restoreTraceSettings()
{
    if (!_data) return;

    QString key = traceKey();

    restoreCurrentState(key);

    ConfigGroup* pConfig = ConfigStorage::group(QStringLiteral("TracePositions"));
    QString group = pConfig->value(QStringLiteral("Group%1").arg(key),QString()).toString();
    delete pConfig;
    if (!group.isEmpty()) setGroup(group);

    // restoreCurrentState() usually leads to a call to setTraceItemDelayed()
    // to restore last active item...
    if (!_traceItemDelayed) {
        // function not available any more.. try with "main"
        if (!setFunction(QStringLiteral("main"))) {
#if 1
            _functionSelection->selectTopFunction();
#else
            HighestCostList hc;
            hc.clear(50);
            TraceFunctionMap::Iterator it;
            for ( it = _data->functionMap().begin();
                  it != _data->functionMap().end(); ++it )
                hc.addCost(&(*it), (*it).inclusive()->subCost(_eventType));

            setFunction( (TraceFunction*) hc[0]);
#endif
        }
    }
}


/* Layout */

void QCGTopLevel::layoutDuplicate()
{
    // save current and allocate a new slot
    _multiView->saveLayout(QStringLiteral("Layout%1-MainView").arg(_layoutCurrent),
                           traceKey());
    _layoutCurrent = _layoutCount;
    _layoutCount++;

    updateLayoutActions();

    qDebug() << "QCGTopLevel::layoutDuplicate: count " << _layoutCount;
}

void QCGTopLevel::layoutRemove()
{
    if (_layoutCount <2) return;

    int from = _layoutCount-1;
    if (_layoutCurrent == from) { _layoutCurrent--; from--; }

    // restore from last and decrement count
    _multiView->restoreLayout(QStringLiteral("Layout%1-MainView").arg(from),
                              traceKey());
    _layoutCount--;

    updateLayoutActions();

    qDebug() << "QCGTopLevel::layoutRemove: count " << _layoutCount;
}

void QCGTopLevel::layoutNext()
{
    if (_layoutCount <2) return;

    QString key = traceKey();
    QString layoutPrefix = QStringLiteral("Layout%1-MainView");

    _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key);
    _layoutCurrent++;
    if (_layoutCurrent == _layoutCount) _layoutCurrent = 0;
    _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key);

    qDebug() << "QCGTopLevel::layoutNext: current " << _layoutCurrent;
}

void QCGTopLevel::layoutPrevious()
{
    if (_layoutCount <2) return;

    QString key = traceKey();
    QString layoutPrefix = QStringLiteral("Layout%1-MainView");

    _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key);
    _layoutCurrent--;
    if (_layoutCurrent <0) _layoutCurrent = _layoutCount-1;
    _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key);

    qDebug() << "QCGTopLevel::layoutPrevious: current " << _layoutCurrent;
}

void QCGTopLevel::layoutSave()
{
    QString key = traceKey();
    QString layoutPrefix = QStringLiteral("Layout%1-MainView");

    _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key);

    // save all layouts as defaults (ie. without any group name postfix)
    for(int i=0;i<_layoutCount;i++) {
        _multiView->restoreLayout(layoutPrefix.arg(i), key);
        _multiView->saveLayout(layoutPrefix.arg(i), QString());
    }
    // restore the previously saved current layout
    _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key);

    ConfigGroup* layoutConfig = ConfigStorage::group(QStringLiteral("Layouts"));
    layoutConfig->setValue(QStringLiteral("DefaultCount"), _layoutCount);
    layoutConfig->setValue(QStringLiteral("DefaultCurrent"), _layoutCurrent);
    delete layoutConfig;
}

void QCGTopLevel::layoutRestore()
{
    ConfigGroup* layoutConfig = ConfigStorage::group(QStringLiteral("Layouts"));
    _layoutCount = layoutConfig->value(QStringLiteral("DefaultCount"), 0).toInt();
    _layoutCurrent = layoutConfig->value(QStringLiteral("DefaultCurrent"), 0).toInt();
    delete layoutConfig;

    if (_layoutCount == 0) {
        _layoutCount++;
        return;
    }

    QString layoutPrefix = QStringLiteral("Layout%1-MainView");
    _multiView->restoreLayout( layoutPrefix.arg(_layoutCurrent), traceKey());

    updateLayoutActions();
}


void QCGTopLevel::updateLayoutActions()
{
    if (_layoutNext)
        _layoutNext->setEnabled(_layoutCount>1);

    if (_layoutPrev)
        _layoutPrev->setEnabled(_layoutCount>1);

    if (_layoutRemove)
        _layoutRemove->setEnabled(_layoutCount>1);

    if (_statusbar)
        _statusbar->showMessage(tr("Layout Count: %1").arg(_layoutCount),
                                1000);
}


void QCGTopLevel::updateStatusBar()
{
    if (!_data || _data->parts().isEmpty()) {
        _statusLabel->setText(tr("No profile data file loaded."));
        return;
    }

    QString status = QStringLiteral("%1 [%2] - ")
                     .arg(_data->shortTraceName())
                     .arg(_data->activePartRange());

    if (_eventType) {
        status += tr("Total %1 Cost: %2")
                  .arg(_eventType->longName())
                  .arg(_data->prettySubCost(_eventType));

        /* this gets too long...
    if (_eventType2 && (_eventType2 != _eventType))
      status += tr(", %1 Cost: %2")
    .arg(_eventType2->longName())
    .arg(_data->prettySubCost(_eventType2));
    */
    }
    else
        status += tr("No event type selected");

    /* Not working... should give group of selected function

  if (_groupType != ProfileContext::Function) {
    status += QString(" - %1 '%2'")
              .arg(ProfileContext::trTypeName(_groupType))
              .arg(_group ? _group->prettyName() : tr("(None)"));
  }
  */

    _statusLabel->setText(status);
}


void QCGTopLevel::closeEvent(QCloseEvent* event)
{
    GlobalConfig::config()->saveOptions();

    saveTraceSettings();
    saveCurrentState(QString());

    // if part dock was chosen visible even for only 1 part loaded,
    // keep this choice...
    _forcePartDock = false;
    if (_data && (_data->parts().count()<2) && _partDock->isVisible())
        _forcePartDock=true;

    ConfigGroup* topConfig = ConfigStorage::group(QStringLiteral("TopWindow"));
    topConfig->setValue(QStringLiteral("ForcePartDockVisible"), _forcePartDock, false);
    topConfig->setValue(QStringLiteral("State"), saveState());
    topConfig->setValue(QStringLiteral("Geometry"), saveGeometry());
    delete topConfig;

    event->accept();
}


void QCGTopLevel::toggleSplitted()
{
    int count = _multiView->childCount();
    if (count<1) count = 1;
    if (count>2) count = 2;
    count = 3-count;
    _multiView->setChildCount(count);

    _splittedToggleAction->setChecked(count>1);
    _splitDirectionToggleAction->setEnabled(count>1);
    _splitDirectionToggleAction->setChecked(_multiView->orientation() ==
                                            Qt::Horizontal);
}

void QCGTopLevel::toggleSplitDirection()
{
    _multiView->setOrientation( _splitDirectionToggleAction->isChecked() ?
                                    Qt::Horizontal : Qt::Vertical );
}



// this is called after a config change in the dialog
void QCGTopLevel::configChanged()
{
    // invalidate found/cached dirs of source files
    if (_data)
        _data->resetSourceDirs();

    _partSelection->notifyChange(TraceItemView::configChanged);
    _stackSelection->refresh();
    _functionSelection->notifyChange(TraceItemView::configChanged);
    _multiView->notifyChange(TraceItemView::configChanged);
}



void QCGTopLevel::activePartsChangedSlot(const TracePartList& list)
{
    if (!_data) return;

    if (!_data->activateParts(list)) {
        //    qDebug("QCGTopLevel::activePartsChangedSlot: No Change!");
        return;
    }
    _activeParts = list;

    _partSelection->set(list);
    _stackSelection->refresh();
    _functionSelection->set(list);
    _multiView->set(list);

    updateStatusBar();
}

void QCGTopLevel::partsHideSelectedSlotDelayed()
{
    QTimer::singleShot( 0, this, &QCGTopLevel::partsHideSelectedSlot );
}

// this puts selected parts into hidden list,
// deselects them and makes the remaining parts selected
void QCGTopLevel::partsHideSelectedSlot()
{
    if (!_data) return;

    TracePartList newHidden, newActive;
    foreach(TracePart* part, _data->parts()) {
        if (_activeParts.contains(part) ||
            _hiddenParts.contains(part))
            newHidden.append(part);
        else
            newActive.append(part);
    }

    _hiddenParts = newHidden;
    _partSelection->hiddenPartsChangedSlot(_hiddenParts);

#if 0
    _mainWidget1->hiddenPartsChangedSlot(_hiddenParts);
    _mainWidget2->hiddenPartsChangedSlot(_hiddenParts);
#endif

    activePartsChangedSlot(newActive);
}

void QCGTopLevel::partsUnhideAllSlotDelayed()
{
    QTimer::singleShot( 0, this, &QCGTopLevel::partsUnhideAllSlot );
}

// this unhides all hidden parts. Does NOT change selection
void QCGTopLevel::partsUnhideAllSlot()
{
    if (!_data) return;

    _hiddenParts.clear();
    _partSelection->hiddenPartsChangedSlot(_hiddenParts);

#if 0
    _mainWidget1->hiddenPartsChangedSlot(_hiddenParts);
    _mainWidget2->hiddenPartsChangedSlot(_hiddenParts);
#endif
}

void QCGTopLevel::insertWindowList(QMenu* menu)
{
    auto windowList = QApplication::topLevelWidgets();
    auto activeWindow = QApplication::activeWindow();
    // dock menu this corresponds to (IIRC) last window that installed one,
    // so using it unless we have to is a bad idea
    if (activeWindow == nullptr) {
        activeWindow = this;
    }
    for (int i = 0; i < windowList.size(); i++) {
        QWidget *topLevelRaw = windowList[i];
        if (QCGTopLevel *topLevel = qobject_cast<QCGTopLevel*>(topLevelRaw)) {
            QString windowTitle = topLevel->windowTitle();
            QAction *windowItem = menu->addAction(windowTitle);
            windowItem->setData(QVariant::fromValue(topLevel));
            if (topLevel == activeWindow) {
                windowItem->setCheckable(true);
                windowItem->setChecked(true);
            }
        }
    }
}

void QCGTopLevel::windowListAboutToShow()
{
    windowMenu->clear();

    windowMenu->addAction(_minimizeAction);
    windowMenu->addAction(_zoomAction);
    windowMenu->addSeparator();

    insertWindowList(windowMenu);
}

void QCGTopLevel::macDockMenuAboutToShow()
{
    macDockMenu-> clear();

    insertWindowList(macDockMenu);

    macDockMenu->addSeparator();
    macDockMenu->addAction(_newAction);
}

void QCGTopLevel::forwardAboutToShow()
{
    QMenu *popup = _forwardAction->menu();

    popup->clear();
    StackBrowser* b = _stackSelection ? _stackSelection->browser() : nullptr;
    HistoryItem* hi = b ? b->current() : nullptr;
    TraceFunction* f;
    QAction* action;

    if (!hi) {
        popup->addAction(tr("(No Stack)"));
        return;
    }

    hi = hi->next();
    if (!hi) {
        popup->addAction(tr("(No next function)"));
        return;
    }

    int count = 1;
    while (count<GlobalConfig::maxSymbolCount() && hi) {
        f = hi->function();
        if (!f) break;

        QString name = GlobalConfig::shortenSymbol(f->prettyName());

        //qDebug("forward: Adding %s", name.toAscii());
        action = popup->addAction(name);
        action->setData(count);

        hi = hi->next();
        count++;
    }
}

void QCGTopLevel::backAboutToShow()
{
    QMenu *popup = _backAction->menu();

    popup->clear();
    StackBrowser* b = _stackSelection ? _stackSelection->browser() : nullptr;
    HistoryItem* hi = b ? b->current() : nullptr;
    TraceFunction* f;
    QAction* action;

    if (!hi) {
        popup->addAction(tr("(No Stack)"));
        return;
    }

    hi = hi->last();
    if (!hi) {
        popup->addAction(tr("(No previous function)"));
        return;
    }

    int count = 1;
    while (count<GlobalConfig::maxSymbolCount() && hi) {
        f = hi->function();
        if (!f) break;

        QString name = GlobalConfig::shortenSymbol(f->prettyName());

        //qDebug("back: Adding %s", name.toAscii());
        action = popup->addAction(name);
        action->setData(count);

        hi = hi->last();
        count++;
    }
}

void QCGTopLevel::upAboutToShow()
{
    QMenu *popup = _upAction->menu();

    popup->clear();
    StackBrowser* b = _stackSelection ? _stackSelection->browser() : nullptr;
    HistoryItem* hi = b ? b->current() : nullptr;
    TraceFunction* f = hi ? hi->function() : nullptr;
    QAction* action;

    if (!f) {
        popup->addAction(tr("(No Stack)"));
        return;
    }
    f = hi->stack()->caller(f, false);
    if (!f) {
        popup->addAction(tr("(No Function Up)"));
        return;
    }

    int count = 1;
    while (count<GlobalConfig::maxSymbolCount() && f) {
        QString name = GlobalConfig::shortenSymbol(f->prettyName());

        action = popup->addAction(name);
        action->setData(count);

        f = hi->stack()->caller(f, false);
        count++;
    }
}

void QCGTopLevel::windowListTriggered(QAction* action)
{
    if (action == _minimizeAction || action == _zoomAction) {
        // these are always in the menu for macOS
        return;
    }

    QVariant data = action->data();
    if (QCGTopLevel* tl = qvariant_cast<QCGTopLevel*>(data)) {
        tl->activateWindow();
        tl->raise();
    }
}

void QCGTopLevel::forwardTriggered(QAction* action)
{
    int count = action->data().toInt(nullptr);
    //qDebug("forwardTriggered: %d", count);
    if( count <= 0)
        return;

    StackBrowser* b = _stackSelection ? _stackSelection->browser() : nullptr;
    if (!b) return;

    while (count>1) {
        b->goForward();
        count--;
    }
    _stackSelection->browserForward();
}

void QCGTopLevel::backTriggered(QAction* action)
{
    int count = action->data().toInt(nullptr);
    //qDebug("backTriggered: %d", count);
    if( count <= 0)
        return;

    StackBrowser* b = _stackSelection ? _stackSelection->browser() : nullptr;
    if (!b) return;

    while (count>1) {
        b->goBack();
        count--;
    }
    _stackSelection->browserBack();
}

void QCGTopLevel::upTriggered(QAction* action)
{
    int count = action->data().toInt(nullptr);
    //qDebug("upTriggered: %d", count);
    if( count <= 0)
        return;

    StackBrowser* b = _stackSelection ? _stackSelection->browser() : nullptr;
    HistoryItem* hi = b ? b->current() : nullptr;
    if (!hi) return;

    TraceFunction* f = hi->function();

    while (count>0 && f) {
        f = hi->stack()->caller(f, false);
        count--;
    }

    //qDebug("upActivated: %s", f ? f->prettyName().toAscii() : "??" );
    if (f)
        setFunction(f);
}

void QCGTopLevel::showMessage(const QString& msg, int ms)
{
    if (_statusbar)
        _statusbar->showMessage(msg, ms);
}

void QCGTopLevel::showStatus(const QString& msg, int progress)
{
    static bool msgUpdateNeeded = true;

    if (!_statusbar) return;

    if (msg.isEmpty()) {
        //reset status
        if (_progressBar) {
            _statusbar->removeWidget(_progressBar);
            delete _progressBar;
            _progressBar = nullptr;
        }
        _statusbar->clearMessage();
        _progressMsg = msg;
        return;
    }

    if (_progressMsg.isEmpty())
        _progressStart.start();

    if (msg != _progressMsg) {
        _progressMsg = msg;
        msgUpdateNeeded = true;
    }

    // do nothing if last change was less than 0.5 seconds ago
    if (_progressStart.elapsed() < 500)
        return;

    if (!_progressBar) {
        _progressBar = new QProgressBar(_statusbar);
        _progressBar->setMaximumSize(200, _statusbar->height()-4);
        _statusbar->addPermanentWidget(_progressBar, 1);
        _progressBar->show();
        msgUpdateNeeded = true;
    }

    _progressStart.restart();

    if (msgUpdateNeeded) {
        _statusbar->showMessage(msg);
        msgUpdateNeeded = false;
    }
    _progressBar->setValue(progress);

    // let the progress bar update itself
    qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
}

void QCGTopLevel::loadStart(const QString& filename)
{
    showStatus(QStringLiteral("Loading %1").arg(filename), 0);
    Logger::_filename = filename;
}

void QCGTopLevel::loadFinished(const QString& msg)
{
    showStatus(QString(), 0);
    if (!msg.isEmpty())
        showMessage(QStringLiteral("Error loading %1: %2").arg(_filename).arg(msg),
                    2000);
}

void QCGTopLevel::loadProgress(int progress)
{
    showStatus(QStringLiteral("Loading %1").arg(_filename), progress);
}

void QCGTopLevel::loadError(int line, const QString& msg)
{
    qCritical() << "Loading" << _filename
                << ":" << line << ": " << msg;
}

void QCGTopLevel::loadWarning(int line, const QString& msg)
{
    qWarning() << "Loading" << _filename
               << ":" << line << ": " << msg;
}

#include "moc_qcgtoplevel.cpp"
