forked from qt-creator/qt-creator
Task-number: QTCREATORBUG-27464 Change-Id: I0d0257bbd46f3eec9b11b386d1987156e83a9dac Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Eike Ziller <eike.ziller@qt.io>
1026 lines
35 KiB
C++
1026 lines
35 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of Qt Creator.
|
|
**
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "outputpanemanager.h"
|
|
#include "outputpane.h"
|
|
#include "findplaceholder.h"
|
|
|
|
#include "icore.h"
|
|
#include "ioutputpane.h"
|
|
#include "modemanager.h"
|
|
#include "statusbarmanager.h"
|
|
|
|
#include <coreplugin/actionmanager/actionmanager.h>
|
|
#include <coreplugin/actionmanager/actioncontainer.h>
|
|
#include <coreplugin/actionmanager/command.h>
|
|
#include <coreplugin/actionmanager/commandbutton.h>
|
|
#include <coreplugin/editormanager/editormanager.h>
|
|
#include <coreplugin/editormanager/ieditor.h>
|
|
#include <coreplugin/find/optionspopup.h>
|
|
|
|
#include <utils/algorithm.h>
|
|
#include <utils/hostosinfo.h>
|
|
#include <utils/styledbar.h>
|
|
#include <utils/stylehelper.h>
|
|
#include <utils/proxyaction.h>
|
|
#include <utils/qtcassert.h>
|
|
#include <utils/theme/theme.h>
|
|
#include <utils/utilsicons.h>
|
|
|
|
#include <QDebug>
|
|
|
|
#include <QAction>
|
|
#include <QApplication>
|
|
#include <QComboBox>
|
|
#include <QFocusEvent>
|
|
#include <QHBoxLayout>
|
|
#include <QLabel>
|
|
#include <QMenu>
|
|
#include <QPainter>
|
|
#include <QStyle>
|
|
#include <QStackedWidget>
|
|
#include <QToolButton>
|
|
#include <QTimeLine>
|
|
|
|
using namespace Utils;
|
|
using namespace Core::Internal;
|
|
|
|
namespace Core {
|
|
namespace Internal {
|
|
|
|
class OutputPaneData
|
|
{
|
|
public:
|
|
OutputPaneData(IOutputPane *pane = nullptr) : pane(pane) {}
|
|
|
|
IOutputPane *pane = nullptr;
|
|
Id id;
|
|
OutputPaneToggleButton *button = nullptr;
|
|
QAction *action = nullptr;
|
|
};
|
|
|
|
static QVector<OutputPaneData> g_outputPanes;
|
|
static bool g_managerConstructed = false; // For debugging reasons.
|
|
|
|
} // Internal
|
|
|
|
// OutputPane
|
|
|
|
IOutputPane::IOutputPane(QObject *parent)
|
|
: QObject(parent),
|
|
m_zoomInButton(new Core::CommandButton),
|
|
m_zoomOutButton(new Core::CommandButton)
|
|
{
|
|
// We need all pages first. Ignore latecomers and shout.
|
|
QTC_ASSERT(!g_managerConstructed, return);
|
|
g_outputPanes.append(OutputPaneData(this));
|
|
|
|
m_zoomInButton->setIcon(Utils::Icons::PLUS_TOOLBAR.icon());
|
|
m_zoomInButton->setCommandId(Constants::ZOOM_IN);
|
|
connect(m_zoomInButton, &QToolButton::clicked, this, [this] { emit zoomInRequested(1); });
|
|
|
|
m_zoomOutButton->setIcon(Utils::Icons::MINUS.icon());
|
|
m_zoomOutButton->setCommandId(Constants::ZOOM_OUT);
|
|
connect(m_zoomOutButton, &QToolButton::clicked, this, [this] { emit zoomOutRequested(1); });
|
|
}
|
|
|
|
IOutputPane::~IOutputPane()
|
|
{
|
|
const int i = Utils::indexOf(g_outputPanes, Utils::equal(&OutputPaneData::pane, this));
|
|
QTC_ASSERT(i >= 0, return);
|
|
delete g_outputPanes.at(i).button;
|
|
g_outputPanes.removeAt(i);
|
|
|
|
delete m_zoomInButton;
|
|
delete m_zoomOutButton;
|
|
}
|
|
|
|
QList<QWidget *> IOutputPane::toolBarWidgets() const
|
|
{
|
|
QList<QWidget *> widgets;
|
|
if (m_filterOutputLineEdit)
|
|
widgets << m_filterOutputLineEdit;
|
|
return widgets << m_zoomInButton << m_zoomOutButton;
|
|
}
|
|
|
|
void IOutputPane::visibilityChanged(bool /*visible*/)
|
|
{
|
|
}
|
|
|
|
void IOutputPane::setFont(const QFont &font)
|
|
{
|
|
emit fontChanged(font);
|
|
}
|
|
|
|
void IOutputPane::setWheelZoomEnabled(bool enabled)
|
|
{
|
|
emit wheelZoomEnabledChanged(enabled);
|
|
}
|
|
|
|
void IOutputPane::setupFilterUi(const QString &historyKey)
|
|
{
|
|
m_filterOutputLineEdit = new FancyLineEdit;
|
|
m_filterActionRegexp = new QAction(this);
|
|
m_filterActionRegexp->setCheckable(true);
|
|
m_filterActionRegexp->setText(tr("Use Regular Expressions"));
|
|
connect(m_filterActionRegexp, &QAction::toggled, this, &IOutputPane::setRegularExpressions);
|
|
Core::ActionManager::registerAction(m_filterActionRegexp, filterRegexpActionId());
|
|
|
|
m_filterActionCaseSensitive = new QAction(this);
|
|
m_filterActionCaseSensitive->setCheckable(true);
|
|
m_filterActionCaseSensitive->setText(tr("Case Sensitive"));
|
|
connect(m_filterActionCaseSensitive, &QAction::toggled, this, &IOutputPane::setCaseSensitive);
|
|
Core::ActionManager::registerAction(m_filterActionCaseSensitive,
|
|
filterCaseSensitivityActionId());
|
|
|
|
m_invertFilterAction = new QAction(this);
|
|
m_invertFilterAction->setCheckable(true);
|
|
m_invertFilterAction->setText(tr("Show Non-matching Lines"));
|
|
connect(m_invertFilterAction, &QAction::toggled, this, [this] {
|
|
m_invertFilter = m_invertFilterAction->isChecked();
|
|
updateFilter();
|
|
});
|
|
Core::ActionManager::registerAction(m_invertFilterAction, filterInvertedActionId());
|
|
|
|
m_filterOutputLineEdit->setPlaceholderText(tr("Filter output..."));
|
|
m_filterOutputLineEdit->setButtonVisible(FancyLineEdit::Left, true);
|
|
m_filterOutputLineEdit->setButtonIcon(FancyLineEdit::Left, Icons::MAGNIFIER.icon());
|
|
m_filterOutputLineEdit->setFiltering(true);
|
|
m_filterOutputLineEdit->setEnabled(false);
|
|
m_filterOutputLineEdit->setHistoryCompleter(historyKey);
|
|
connect(m_filterOutputLineEdit, &FancyLineEdit::textChanged,
|
|
this, &IOutputPane::updateFilter);
|
|
connect(m_filterOutputLineEdit, &FancyLineEdit::returnPressed,
|
|
this, &IOutputPane::updateFilter);
|
|
connect(m_filterOutputLineEdit, &FancyLineEdit::leftButtonClicked,
|
|
this, &IOutputPane::filterOutputButtonClicked);
|
|
}
|
|
|
|
QString IOutputPane::filterText() const
|
|
{
|
|
return m_filterOutputLineEdit->text();
|
|
}
|
|
|
|
void IOutputPane::setFilteringEnabled(bool enable)
|
|
{
|
|
m_filterOutputLineEdit->setEnabled(enable);
|
|
}
|
|
|
|
void IOutputPane::setupContext(const char *context, QWidget *widget)
|
|
{
|
|
QTC_ASSERT(!m_context, return);
|
|
m_context = new IContext(this);
|
|
m_context->setContext(Context(context));
|
|
m_context->setWidget(widget);
|
|
ICore::addContextObject(m_context);
|
|
|
|
const auto zoomInAction = new QAction(this);
|
|
Core::ActionManager::registerAction(zoomInAction, Constants::ZOOM_IN, m_context->context());
|
|
connect(zoomInAction, &QAction::triggered, this, [this] { emit zoomInRequested(1); });
|
|
const auto zoomOutAction = new QAction(this);
|
|
Core::ActionManager::registerAction(zoomOutAction, Constants::ZOOM_OUT, m_context->context());
|
|
connect(zoomOutAction, &QAction::triggered, this, [this] { emit zoomOutRequested(1); });
|
|
const auto resetZoomAction = new QAction(this);
|
|
Core::ActionManager::registerAction(resetZoomAction, Constants::ZOOM_RESET,
|
|
m_context->context());
|
|
connect(resetZoomAction, &QAction::triggered, this, &IOutputPane::resetZoomRequested);
|
|
}
|
|
|
|
void IOutputPane::setZoomButtonsEnabled(bool enabled)
|
|
{
|
|
m_zoomInButton->setEnabled(enabled);
|
|
m_zoomOutButton->setEnabled(enabled);
|
|
}
|
|
|
|
void IOutputPane::updateFilter()
|
|
{
|
|
QTC_ASSERT(false, qDebug() << "updateFilter() needs to get re-implemented");
|
|
}
|
|
|
|
void IOutputPane::filterOutputButtonClicked()
|
|
{
|
|
auto popup = new Core::OptionsPopup(m_filterOutputLineEdit,
|
|
{filterRegexpActionId(), filterCaseSensitivityActionId(), filterInvertedActionId()});
|
|
popup->show();
|
|
}
|
|
|
|
void IOutputPane::setRegularExpressions(bool regularExpressions)
|
|
{
|
|
m_filterRegexp = regularExpressions;
|
|
updateFilter();
|
|
}
|
|
|
|
Id IOutputPane::filterRegexpActionId() const
|
|
{
|
|
return Id("OutputFilter.RegularExpressions").withSuffix(metaObject()->className());
|
|
}
|
|
|
|
Id IOutputPane::filterCaseSensitivityActionId() const
|
|
{
|
|
return Id("OutputFilter.CaseSensitive").withSuffix(metaObject()->className());
|
|
}
|
|
|
|
Id IOutputPane::filterInvertedActionId() const
|
|
{
|
|
return Id("OutputFilter.Invert").withSuffix(metaObject()->className());
|
|
}
|
|
|
|
void IOutputPane::setCaseSensitive(bool caseSensitive)
|
|
{
|
|
m_filterCaseSensitivity = caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
|
updateFilter();
|
|
}
|
|
|
|
namespace Internal {
|
|
|
|
const char outputPaneSettingsKeyC[] = "OutputPaneVisibility";
|
|
const char outputPaneIdKeyC[] = "id";
|
|
const char outputPaneVisibleKeyC[] = "visible";
|
|
const int buttonBorderWidth = 3;
|
|
|
|
static int numberAreaWidth()
|
|
{
|
|
return creatorTheme()->flag(Theme::FlatToolBars) ? 15 : 19;
|
|
}
|
|
|
|
////
|
|
// OutputPaneManager
|
|
////
|
|
|
|
static OutputPaneManager *m_instance = nullptr;
|
|
|
|
void OutputPaneManager::create()
|
|
{
|
|
m_instance = new OutputPaneManager;
|
|
}
|
|
|
|
void OutputPaneManager::destroy()
|
|
{
|
|
delete m_instance;
|
|
m_instance = nullptr;
|
|
}
|
|
|
|
OutputPaneManager *OutputPaneManager::instance()
|
|
{
|
|
return m_instance;
|
|
}
|
|
|
|
void OutputPaneManager::updateStatusButtons(bool visible)
|
|
{
|
|
int idx = currentIndex();
|
|
if (idx == -1)
|
|
return;
|
|
QTC_ASSERT(idx < g_outputPanes.size(), return);
|
|
const OutputPaneData &data = g_outputPanes.at(idx);
|
|
QTC_ASSERT(data.button, return);
|
|
data.button->setChecked(visible);
|
|
data.pane->visibilityChanged(visible);
|
|
}
|
|
|
|
void OutputPaneManager::updateMaximizeButton(bool maximized)
|
|
{
|
|
if (maximized) {
|
|
m_instance->m_minMaxAction->setIcon(m_instance->m_minimizeIcon);
|
|
m_instance->m_minMaxAction->setText(tr("Minimize"));
|
|
} else {
|
|
m_instance->m_minMaxAction->setIcon(m_instance->m_maximizeIcon);
|
|
m_instance->m_minMaxAction->setText(tr("Maximize"));
|
|
}
|
|
}
|
|
|
|
// Return shortcut as Alt+<number> or Cmd+<number> if number is a non-zero digit
|
|
static QKeySequence paneShortCut(int number)
|
|
{
|
|
if (number < 1 || number > 9)
|
|
return QKeySequence();
|
|
|
|
const int modifier = HostOsInfo::isMacHost() ? Qt::CTRL : Qt::ALT;
|
|
return QKeySequence(modifier | (Qt::Key_0 + number));
|
|
}
|
|
|
|
OutputPaneManager::OutputPaneManager(QWidget *parent) :
|
|
QWidget(parent),
|
|
m_titleLabel(new QLabel),
|
|
m_manageButton(new OutputPaneManageButton),
|
|
m_closeButton(new QToolButton),
|
|
m_minMaxButton(new QToolButton),
|
|
m_outputWidgetPane(new QStackedWidget),
|
|
m_opToolBarWidgets(new QStackedWidget),
|
|
m_minimizeIcon(Utils::Icons::ARROW_DOWN.icon()),
|
|
m_maximizeIcon(Utils::Icons::ARROW_UP.icon())
|
|
{
|
|
setWindowTitle(tr("Output"));
|
|
|
|
m_titleLabel->setContentsMargins(5, 0, 5, 0);
|
|
|
|
m_clearAction = new QAction(this);
|
|
m_clearAction->setIcon(Utils::Icons::CLEAN.icon());
|
|
m_clearAction->setText(tr("Clear"));
|
|
connect(m_clearAction, &QAction::triggered, this, &OutputPaneManager::clearPage);
|
|
|
|
m_nextAction = new QAction(this);
|
|
m_nextAction->setIcon(Utils::Icons::ARROW_DOWN_TOOLBAR.icon());
|
|
m_nextAction->setText(tr("Next Item"));
|
|
connect(m_nextAction, &QAction::triggered, this, &OutputPaneManager::slotNext);
|
|
|
|
m_prevAction = new QAction(this);
|
|
m_prevAction->setIcon(Utils::Icons::ARROW_UP_TOOLBAR.icon());
|
|
m_prevAction->setText(tr("Previous Item"));
|
|
connect(m_prevAction, &QAction::triggered, this, &OutputPaneManager::slotPrev);
|
|
|
|
m_minMaxAction = new QAction(this);
|
|
m_minMaxAction->setIcon(m_maximizeIcon);
|
|
m_minMaxAction->setText(tr("Maximize"));
|
|
|
|
m_closeButton->setIcon(Icons::CLOSE_SPLIT_BOTTOM.icon());
|
|
connect(m_closeButton, &QAbstractButton::clicked, this, &OutputPaneManager::slotHide);
|
|
|
|
connect(ICore::instance(), &ICore::saveSettingsRequested, this, &OutputPaneManager::saveSettings);
|
|
|
|
auto mainlayout = new QVBoxLayout;
|
|
mainlayout->setSpacing(0);
|
|
mainlayout->setContentsMargins(0, 0, 0, 0);
|
|
m_toolBar = new StyledBar;
|
|
auto toolLayout = new QHBoxLayout(m_toolBar);
|
|
toolLayout->setContentsMargins(0, 0, 0, 0);
|
|
toolLayout->setSpacing(0);
|
|
toolLayout->addWidget(m_titleLabel);
|
|
toolLayout->addWidget(new StyledSeparator);
|
|
m_clearButton = new QToolButton;
|
|
toolLayout->addWidget(m_clearButton);
|
|
m_prevToolButton = new QToolButton;
|
|
toolLayout->addWidget(m_prevToolButton);
|
|
m_nextToolButton = new QToolButton;
|
|
toolLayout->addWidget(m_nextToolButton);
|
|
toolLayout->addWidget(m_opToolBarWidgets);
|
|
toolLayout->addWidget(m_minMaxButton);
|
|
toolLayout->addWidget(m_closeButton);
|
|
mainlayout->addWidget(m_toolBar);
|
|
mainlayout->addWidget(m_outputWidgetPane, 10);
|
|
mainlayout->addWidget(new FindToolBarPlaceHolder(this));
|
|
setLayout(mainlayout);
|
|
|
|
m_buttonsWidget = new QWidget;
|
|
m_buttonsWidget->setObjectName("OutputPaneButtons"); // used for UI introduction
|
|
m_buttonsWidget->setLayout(new QHBoxLayout);
|
|
m_buttonsWidget->layout()->setContentsMargins(5,0,0,0);
|
|
m_buttonsWidget->layout()->setSpacing(
|
|
creatorTheme()->flag(Theme::FlatToolBars) ? 9 : 4);
|
|
|
|
StatusBarManager::addStatusBarWidget(m_buttonsWidget, StatusBarManager::Second);
|
|
|
|
ActionContainer *mview = ActionManager::actionContainer(Constants::M_VIEW);
|
|
|
|
// Window->Output Panes
|
|
ActionContainer *mpanes = ActionManager::createMenu(Constants::M_VIEW_PANES);
|
|
mview->addMenu(mpanes, Constants::G_VIEW_PANES);
|
|
mpanes->menu()->setTitle(tr("Out&put"));
|
|
mpanes->appendGroup("Coreplugin.OutputPane.ActionsGroup");
|
|
mpanes->appendGroup("Coreplugin.OutputPane.PanesGroup");
|
|
|
|
Command *cmd;
|
|
|
|
cmd = ActionManager::registerAction(m_clearAction, Constants::OUTPUTPANE_CLEAR);
|
|
m_clearButton->setDefaultAction(
|
|
ProxyAction::proxyActionWithIcon(m_clearAction, Utils::Icons::CLEAN_TOOLBAR.icon()));
|
|
mpanes->addAction(cmd, "Coreplugin.OutputPane.ActionsGroup");
|
|
|
|
cmd = ActionManager::registerAction(m_prevAction, "Coreplugin.OutputPane.previtem");
|
|
cmd->setDefaultKeySequence(QKeySequence(tr("Shift+F6")));
|
|
m_prevToolButton->setDefaultAction(
|
|
ProxyAction::proxyActionWithIcon(m_prevAction, Utils::Icons::ARROW_UP_TOOLBAR.icon()));
|
|
mpanes->addAction(cmd, "Coreplugin.OutputPane.ActionsGroup");
|
|
|
|
cmd = ActionManager::registerAction(m_nextAction, "Coreplugin.OutputPane.nextitem");
|
|
m_nextToolButton->setDefaultAction(
|
|
ProxyAction::proxyActionWithIcon(m_nextAction, Utils::Icons::ARROW_DOWN_TOOLBAR.icon()));
|
|
cmd->setDefaultKeySequence(QKeySequence(tr("F6")));
|
|
mpanes->addAction(cmd, "Coreplugin.OutputPane.ActionsGroup");
|
|
|
|
cmd = ActionManager::registerAction(m_minMaxAction, "Coreplugin.OutputPane.minmax");
|
|
cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Ctrl+Shift+9") : tr("Alt+Shift+9")));
|
|
cmd->setAttribute(Command::CA_UpdateText);
|
|
cmd->setAttribute(Command::CA_UpdateIcon);
|
|
mpanes->addAction(cmd, "Coreplugin.OutputPane.ActionsGroup");
|
|
connect(m_minMaxAction, &QAction::triggered, this, &OutputPaneManager::toggleMaximized);
|
|
m_minMaxButton->setDefaultAction(cmd->action());
|
|
|
|
mpanes->addSeparator("Coreplugin.OutputPane.ActionsGroup");
|
|
}
|
|
|
|
void OutputPaneManager::initialize()
|
|
{
|
|
ActionContainer *mpanes = ActionManager::actionContainer(Constants::M_VIEW_PANES);
|
|
QFontMetrics titleFm = m_instance->m_titleLabel->fontMetrics();
|
|
int minTitleWidth = 0;
|
|
|
|
Utils::sort(g_outputPanes, [](const OutputPaneData &d1, const OutputPaneData &d2) {
|
|
return d1.pane->priorityInStatusBar() > d2.pane->priorityInStatusBar();
|
|
});
|
|
const int n = g_outputPanes.size();
|
|
|
|
int shortcutNumber = 1;
|
|
const Id baseId = "QtCreator.Pane.";
|
|
for (int i = 0; i != n; ++i) {
|
|
OutputPaneData &data = g_outputPanes[i];
|
|
IOutputPane *outPane = data.pane;
|
|
const int idx = m_instance->m_outputWidgetPane->addWidget(outPane->outputWidget(m_instance));
|
|
QTC_CHECK(idx == i);
|
|
|
|
connect(outPane, &IOutputPane::showPage, m_instance, [idx](int flags) {
|
|
m_instance->showPage(idx, flags);
|
|
});
|
|
connect(outPane, &IOutputPane::hidePage, m_instance, &OutputPaneManager::slotHide);
|
|
|
|
connect(outPane, &IOutputPane::togglePage, m_instance, [idx](int flags) {
|
|
if (OutputPanePlaceHolder::isCurrentVisible() && m_instance->currentIndex() == idx)
|
|
m_instance->slotHide();
|
|
else
|
|
m_instance->showPage(idx, flags);
|
|
});
|
|
|
|
connect(outPane, &IOutputPane::navigateStateUpdate, m_instance, [idx, outPane] {
|
|
if (m_instance->currentIndex() == idx) {
|
|
m_instance->m_prevAction->setEnabled(outPane->canNavigate()
|
|
&& outPane->canPrevious());
|
|
m_instance->m_nextAction->setEnabled(outPane->canNavigate() && outPane->canNext());
|
|
}
|
|
});
|
|
|
|
QWidget *toolButtonsContainer = new QWidget(m_instance->m_opToolBarWidgets);
|
|
auto toolButtonsLayout = new QHBoxLayout;
|
|
toolButtonsLayout->setContentsMargins(0, 0, 0, 0);
|
|
toolButtonsLayout->setSpacing(0);
|
|
const QList<QWidget *> toolBarWidgets = outPane->toolBarWidgets();
|
|
for (QWidget *toolButton : toolBarWidgets)
|
|
toolButtonsLayout->addWidget(toolButton);
|
|
toolButtonsLayout->addStretch(5);
|
|
toolButtonsContainer->setLayout(toolButtonsLayout);
|
|
|
|
m_instance->m_opToolBarWidgets->addWidget(toolButtonsContainer);
|
|
|
|
minTitleWidth = qMax(minTitleWidth, titleFm.horizontalAdvance(outPane->displayName()));
|
|
|
|
QString suffix = outPane->displayName().simplified();
|
|
suffix.remove(QLatin1Char(' '));
|
|
data.id = baseId.withSuffix(suffix);
|
|
data.action = new QAction(outPane->displayName(), m_instance);
|
|
Command *cmd = ActionManager::registerAction(data.action, data.id);
|
|
|
|
mpanes->addAction(cmd, "Coreplugin.OutputPane.PanesGroup");
|
|
|
|
cmd->setDefaultKeySequence(paneShortCut(shortcutNumber));
|
|
auto button = new OutputPaneToggleButton(shortcutNumber,
|
|
outPane->displayName(),
|
|
cmd->action());
|
|
data.button = button;
|
|
|
|
connect(outPane, &IOutputPane::flashButton, button, [button] { button->flash(); });
|
|
connect(outPane,
|
|
&IOutputPane::setBadgeNumber,
|
|
button,
|
|
&OutputPaneToggleButton::setIconBadgeNumber);
|
|
|
|
++shortcutNumber;
|
|
m_instance->m_buttonsWidget->layout()->addWidget(data.button);
|
|
connect(data.button, &QAbstractButton::clicked, m_instance, [i] {
|
|
m_instance->buttonTriggered(i);
|
|
});
|
|
|
|
bool visible = outPane->priorityInStatusBar() != -1;
|
|
data.button->setVisible(visible);
|
|
|
|
connect(data.action, &QAction::triggered, m_instance, [i] {
|
|
m_instance->shortcutTriggered(i);
|
|
});
|
|
}
|
|
|
|
m_instance->m_titleLabel->setMinimumWidth(
|
|
minTitleWidth + m_instance->m_titleLabel->contentsMargins().left()
|
|
+ m_instance->m_titleLabel->contentsMargins().right());
|
|
m_instance->m_buttonsWidget->layout()->addWidget(m_instance->m_manageButton);
|
|
connect(m_instance->m_manageButton,
|
|
&QAbstractButton::clicked,
|
|
m_instance,
|
|
&OutputPaneManager::popupMenu);
|
|
|
|
m_instance->readSettings();
|
|
}
|
|
|
|
OutputPaneManager::~OutputPaneManager() = default;
|
|
|
|
void OutputPaneManager::shortcutTriggered(int idx)
|
|
{
|
|
IOutputPane *outputPane = g_outputPanes.at(idx).pane;
|
|
// Now check the special case, the output window is already visible,
|
|
// we are already on that page but the outputpane doesn't have focus
|
|
// then just give it focus.
|
|
int current = currentIndex();
|
|
if (OutputPanePlaceHolder::isCurrentVisible() && current == idx) {
|
|
if ((!m_outputWidgetPane->isActiveWindow() || !outputPane->hasFocus())
|
|
&& outputPane->canFocus()) {
|
|
outputPane->setFocus();
|
|
ICore::raiseWindow(m_outputWidgetPane);
|
|
} else {
|
|
slotHide();
|
|
}
|
|
} else {
|
|
// Else do the same as clicking on the button does.
|
|
buttonTriggered(idx);
|
|
}
|
|
}
|
|
|
|
int OutputPaneManager::outputPaneHeightSetting()
|
|
{
|
|
return m_instance->m_outputPaneHeightSetting;
|
|
}
|
|
|
|
void OutputPaneManager::setOutputPaneHeightSetting(int value)
|
|
{
|
|
m_instance->m_outputPaneHeightSetting = value;
|
|
}
|
|
|
|
void OutputPaneManager::toggleMaximized()
|
|
{
|
|
OutputPanePlaceHolder *ph = OutputPanePlaceHolder::getCurrent();
|
|
QTC_ASSERT(ph, return);
|
|
|
|
if (!ph->isVisible()) // easier than disabling/enabling the action
|
|
return;
|
|
ph->setMaximized(!ph->isMaximized());
|
|
}
|
|
|
|
void OutputPaneManager::buttonTriggered(int idx)
|
|
{
|
|
QTC_ASSERT(idx >= 0, return);
|
|
if (idx == currentIndex() && OutputPanePlaceHolder::isCurrentVisible()) {
|
|
// we should toggle and the page is already visible and we are actually closeable
|
|
slotHide();
|
|
} else {
|
|
showPage(idx, IOutputPane::ModeSwitch | IOutputPane::WithFocus);
|
|
}
|
|
}
|
|
|
|
void OutputPaneManager::readSettings()
|
|
{
|
|
QSettings *settings = ICore::settings();
|
|
int num = settings->beginReadArray(QLatin1String(outputPaneSettingsKeyC));
|
|
for (int i = 0; i < num; ++i) {
|
|
settings->setArrayIndex(i);
|
|
Id id = Id::fromSetting(settings->value(QLatin1String(outputPaneIdKeyC)));
|
|
const int idx = Utils::indexOf(g_outputPanes, Utils::equal(&OutputPaneData::id, id));
|
|
if (idx < 0) // happens for e.g. disabled plugins (with outputpanes) that were loaded before
|
|
continue;
|
|
const bool visible = settings->value(QLatin1String(outputPaneVisibleKeyC)).toBool();
|
|
g_outputPanes[idx].button->setVisible(visible);
|
|
}
|
|
settings->endArray();
|
|
|
|
m_outputPaneHeightSetting = settings->value(QLatin1String("OutputPanePlaceHolder/Height"), 0).toInt();
|
|
}
|
|
|
|
void OutputPaneManager::slotNext()
|
|
{
|
|
int idx = currentIndex();
|
|
ensurePageVisible(idx);
|
|
IOutputPane *out = g_outputPanes.at(idx).pane;
|
|
if (out->canNext())
|
|
out->goToNext();
|
|
}
|
|
|
|
void OutputPaneManager::slotPrev()
|
|
{
|
|
int idx = currentIndex();
|
|
ensurePageVisible(idx);
|
|
IOutputPane *out = g_outputPanes.at(idx).pane;
|
|
if (out->canPrevious())
|
|
out->goToPrev();
|
|
}
|
|
|
|
void OutputPaneManager::slotHide()
|
|
{
|
|
OutputPanePlaceHolder *ph = OutputPanePlaceHolder::getCurrent();
|
|
if (ph) {
|
|
emit ph->visibilityChangeRequested(false);
|
|
ph->setVisible(false);
|
|
int idx = currentIndex();
|
|
QTC_ASSERT(idx >= 0, return);
|
|
g_outputPanes.at(idx).button->setChecked(false);
|
|
g_outputPanes.at(idx).pane->visibilityChanged(false);
|
|
if (IEditor *editor = EditorManager::currentEditor()) {
|
|
QWidget *w = editor->widget()->focusWidget();
|
|
if (!w)
|
|
w = editor->widget();
|
|
w->setFocus();
|
|
}
|
|
}
|
|
}
|
|
|
|
void OutputPaneManager::ensurePageVisible(int idx)
|
|
{
|
|
//int current = currentIndex();
|
|
//if (current != idx)
|
|
// m_outputWidgetPane->setCurrentIndex(idx);
|
|
setCurrentIndex(idx);
|
|
}
|
|
|
|
void OutputPaneManager::showPage(int idx, int flags)
|
|
{
|
|
QTC_ASSERT(idx >= 0, return);
|
|
OutputPanePlaceHolder *ph = OutputPanePlaceHolder::getCurrent();
|
|
|
|
if (!ph && flags & IOutputPane::ModeSwitch) {
|
|
// In this mode we don't have a placeholder
|
|
// switch to the output mode and switch the page
|
|
ModeManager::activateMode(Id(Constants::MODE_EDIT));
|
|
ph = OutputPanePlaceHolder::getCurrent();
|
|
}
|
|
|
|
bool onlyFlash = !ph
|
|
|| (g_outputPanes.at(currentIndex()).pane->hasFocus()
|
|
&& !(flags & IOutputPane::WithFocus)
|
|
&& idx != currentIndex());
|
|
|
|
if (onlyFlash) {
|
|
g_outputPanes.at(idx).button->flash();
|
|
} else {
|
|
emit ph->visibilityChangeRequested(true);
|
|
// make the page visible
|
|
ph->setVisible(true);
|
|
|
|
ensurePageVisible(idx);
|
|
IOutputPane *out = g_outputPanes.at(idx).pane;
|
|
if (flags & IOutputPane::WithFocus) {
|
|
if (out->canFocus())
|
|
out->setFocus();
|
|
ICore::raiseWindow(m_outputWidgetPane);
|
|
}
|
|
|
|
if (flags & IOutputPane::EnsureSizeHint)
|
|
ph->ensureSizeHintAsMinimum();
|
|
}
|
|
}
|
|
|
|
void OutputPaneManager::focusInEvent(QFocusEvent *e)
|
|
{
|
|
if (QWidget *w = m_outputWidgetPane->currentWidget())
|
|
w->setFocus(e->reason());
|
|
}
|
|
|
|
void OutputPaneManager::setCurrentIndex(int idx)
|
|
{
|
|
static int lastIndex = -1;
|
|
|
|
if (lastIndex != -1) {
|
|
g_outputPanes.at(lastIndex).button->setChecked(false);
|
|
g_outputPanes.at(lastIndex).pane->visibilityChanged(false);
|
|
}
|
|
|
|
if (idx != -1) {
|
|
m_outputWidgetPane->setCurrentIndex(idx);
|
|
m_opToolBarWidgets->setCurrentIndex(idx);
|
|
|
|
OutputPaneData &data = g_outputPanes[idx];
|
|
IOutputPane *pane = data.pane;
|
|
data.button->show();
|
|
pane->visibilityChanged(true);
|
|
|
|
bool canNavigate = pane->canNavigate();
|
|
m_prevAction->setEnabled(canNavigate && pane->canPrevious());
|
|
m_nextAction->setEnabled(canNavigate && pane->canNext());
|
|
g_outputPanes.at(idx).button->setChecked(OutputPanePlaceHolder::isCurrentVisible());
|
|
m_titleLabel->setText(pane->displayName());
|
|
}
|
|
|
|
lastIndex = idx;
|
|
}
|
|
|
|
void OutputPaneManager::popupMenu()
|
|
{
|
|
QMenu menu;
|
|
int idx = 0;
|
|
for (OutputPaneData &data : g_outputPanes) {
|
|
QAction *act = menu.addAction(data.pane->displayName());
|
|
act->setCheckable(true);
|
|
act->setChecked(data.button->isPaneVisible());
|
|
act->setData(idx);
|
|
++idx;
|
|
}
|
|
QAction *result = menu.exec(QCursor::pos());
|
|
if (!result)
|
|
return;
|
|
idx = result->data().toInt();
|
|
QTC_ASSERT(idx >= 0 && idx < g_outputPanes.size(), return);
|
|
OutputPaneData &data = g_outputPanes[idx];
|
|
if (data.button->isPaneVisible()) {
|
|
data.pane->visibilityChanged(false);
|
|
data.button->setChecked(false);
|
|
data.button->hide();
|
|
} else {
|
|
showPage(idx, IOutputPane::ModeSwitch);
|
|
}
|
|
}
|
|
|
|
void OutputPaneManager::saveSettings() const
|
|
{
|
|
QSettings *settings = ICore::settings();
|
|
const int n = g_outputPanes.size();
|
|
settings->beginWriteArray(QLatin1String(outputPaneSettingsKeyC), n);
|
|
for (int i = 0; i < n; ++i) {
|
|
const OutputPaneData &data = g_outputPanes.at(i);
|
|
settings->setArrayIndex(i);
|
|
settings->setValue(QLatin1String(outputPaneIdKeyC), data.id.toSetting());
|
|
settings->setValue(QLatin1String(outputPaneVisibleKeyC), data.button->isPaneVisible());
|
|
}
|
|
settings->endArray();
|
|
int heightSetting = m_outputPaneHeightSetting;
|
|
// update if possible
|
|
if (OutputPanePlaceHolder *curr = OutputPanePlaceHolder::getCurrent())
|
|
heightSetting = curr->nonMaximizedSize();
|
|
settings->setValue(QLatin1String("OutputPanePlaceHolder/Height"), heightSetting);
|
|
}
|
|
|
|
void OutputPaneManager::clearPage()
|
|
{
|
|
int idx = currentIndex();
|
|
if (idx >= 0)
|
|
g_outputPanes.at(idx).pane->clearContents();
|
|
}
|
|
|
|
int OutputPaneManager::currentIndex() const
|
|
{
|
|
return m_outputWidgetPane->currentIndex();
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// OutputPaneToolButton
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
OutputPaneToggleButton::OutputPaneToggleButton(int number, const QString &text,
|
|
QAction *action, QWidget *parent)
|
|
: QToolButton(parent)
|
|
, m_number(QString::number(number))
|
|
, m_text(text)
|
|
, m_action(action)
|
|
, m_flashTimer(new QTimeLine(1000, this))
|
|
{
|
|
setFocusPolicy(Qt::NoFocus);
|
|
setCheckable(true);
|
|
QFont fnt = QApplication::font();
|
|
setFont(fnt);
|
|
if (m_action)
|
|
connect(m_action, &QAction::changed, this, &OutputPaneToggleButton::updateToolTip);
|
|
|
|
m_flashTimer->setDirection(QTimeLine::Forward);
|
|
m_flashTimer->setEasingCurve(QEasingCurve::SineCurve);
|
|
m_flashTimer->setFrameRange(0, 92);
|
|
auto updateSlot = QOverload<>::of(&QWidget::update);
|
|
connect(m_flashTimer, &QTimeLine::valueChanged, this, updateSlot);
|
|
connect(m_flashTimer, &QTimeLine::finished, this, updateSlot);
|
|
updateToolTip();
|
|
}
|
|
|
|
void OutputPaneToggleButton::updateToolTip()
|
|
{
|
|
QTC_ASSERT(m_action, return);
|
|
setToolTip(m_action->toolTip());
|
|
}
|
|
|
|
QSize OutputPaneToggleButton::sizeHint() const
|
|
{
|
|
ensurePolished();
|
|
|
|
QSize s = fontMetrics().size(Qt::TextSingleLine, m_text);
|
|
|
|
// Expand to account for border image
|
|
s.rwidth() += numberAreaWidth() + 1 + buttonBorderWidth + buttonBorderWidth;
|
|
|
|
if (!m_badgeNumberLabel.text().isNull())
|
|
s.rwidth() += m_badgeNumberLabel.sizeHint().width() + 1;
|
|
|
|
return s;
|
|
}
|
|
|
|
void OutputPaneToggleButton::paintEvent(QPaintEvent*)
|
|
{
|
|
const QFontMetrics fm = fontMetrics();
|
|
const int baseLine = (height() - fm.height() + 1) / 2 + fm.ascent();
|
|
const int numberWidth = fm.horizontalAdvance(m_number);
|
|
|
|
QPainter p(this);
|
|
|
|
QStyleOption styleOption;
|
|
styleOption.initFrom(this);
|
|
const bool hovered = !HostOsInfo::isMacHost() && (styleOption.state & QStyle::State_MouseOver);
|
|
|
|
if (creatorTheme()->flag(Theme::FlatToolBars)) {
|
|
Theme::Color c = Theme::BackgroundColorDark;
|
|
|
|
if (hovered)
|
|
c = Theme::BackgroundColorHover;
|
|
else if (isDown() || isChecked())
|
|
c = Theme::BackgroundColorSelected;
|
|
|
|
if (c != Theme::BackgroundColorDark)
|
|
p.fillRect(rect(), creatorTheme()->color(c));
|
|
} else {
|
|
const QImage *image = nullptr;
|
|
if (isDown()) {
|
|
static const QImage pressed(
|
|
StyleHelper::dpiSpecificImageFile(":/utils/images/panel_button_pressed.png"));
|
|
image = &pressed;
|
|
} else if (isChecked()) {
|
|
if (hovered) {
|
|
static const QImage checkedHover(
|
|
StyleHelper::dpiSpecificImageFile(":/utils/images/panel_button_checked_hover.png"));
|
|
image = &checkedHover;
|
|
} else {
|
|
static const QImage checked(
|
|
StyleHelper::dpiSpecificImageFile(":/utils/images/panel_button_checked.png"));
|
|
image = &checked;
|
|
}
|
|
} else {
|
|
if (hovered) {
|
|
static const QImage hover(
|
|
StyleHelper::dpiSpecificImageFile(":/utils/images/panel_button_hover.png"));
|
|
image = &hover;
|
|
} else {
|
|
static const QImage button(
|
|
StyleHelper::dpiSpecificImageFile(":/utils/images/panel_button.png"));
|
|
image = &button;
|
|
}
|
|
}
|
|
if (image)
|
|
StyleHelper::drawCornerImage(*image, &p, rect(), numberAreaWidth(), buttonBorderWidth, buttonBorderWidth, buttonBorderWidth);
|
|
}
|
|
|
|
if (m_flashTimer->state() == QTimeLine::Running)
|
|
{
|
|
QColor c = creatorTheme()->color(Theme::OutputPaneButtonFlashColor);
|
|
c.setAlpha (m_flashTimer->currentFrame());
|
|
QRect r = creatorTheme()->flag(Theme::FlatToolBars)
|
|
? rect() : rect().adjusted(numberAreaWidth(), 1, -1, -1);
|
|
p.fillRect(r, c);
|
|
}
|
|
|
|
p.setFont(font());
|
|
p.setPen(creatorTheme()->color(Theme::OutputPaneToggleButtonTextColorChecked));
|
|
p.drawText((numberAreaWidth() - numberWidth) / 2, baseLine, m_number);
|
|
if (!isChecked())
|
|
p.setPen(creatorTheme()->color(Theme::OutputPaneToggleButtonTextColorUnchecked));
|
|
int leftPart = numberAreaWidth() + buttonBorderWidth;
|
|
int labelWidth = 0;
|
|
if (!m_badgeNumberLabel.text().isEmpty()) {
|
|
const QSize labelSize = m_badgeNumberLabel.sizeHint();
|
|
labelWidth = labelSize.width() + 3;
|
|
m_badgeNumberLabel.paint(&p, width() - labelWidth, (height() - labelSize.height()) / 2, isChecked());
|
|
}
|
|
p.drawText(leftPart, baseLine, fm.elidedText(m_text, Qt::ElideRight, width() - leftPart - 1 - labelWidth));
|
|
}
|
|
|
|
void OutputPaneToggleButton::checkStateSet()
|
|
{
|
|
//Stop flashing when button is checked
|
|
QToolButton::checkStateSet();
|
|
m_flashTimer->stop();
|
|
}
|
|
|
|
void OutputPaneToggleButton::flash(int count)
|
|
{
|
|
setVisible(true);
|
|
//Start flashing if button is not checked
|
|
if (!isChecked()) {
|
|
m_flashTimer->setLoopCount(count);
|
|
if (m_flashTimer->state() != QTimeLine::Running)
|
|
m_flashTimer->start();
|
|
update();
|
|
}
|
|
}
|
|
|
|
void OutputPaneToggleButton::setIconBadgeNumber(int number)
|
|
{
|
|
QString text = (number ? QString::number(number) : QString());
|
|
m_badgeNumberLabel.setText(text);
|
|
updateGeometry();
|
|
}
|
|
|
|
bool OutputPaneToggleButton::isPaneVisible() const
|
|
{
|
|
return isVisibleTo(parentWidget());
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// OutputPaneManageButton
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
OutputPaneManageButton::OutputPaneManageButton()
|
|
{
|
|
setFocusPolicy(Qt::NoFocus);
|
|
setCheckable(true);
|
|
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
|
|
}
|
|
|
|
QSize OutputPaneManageButton::sizeHint() const
|
|
{
|
|
ensurePolished();
|
|
return QSize(numberAreaWidth(), 16);
|
|
}
|
|
|
|
void OutputPaneManageButton::paintEvent(QPaintEvent*)
|
|
{
|
|
QPainter p(this);
|
|
if (!creatorTheme()->flag(Theme::FlatToolBars)) {
|
|
static const QImage button(StyleHelper::dpiSpecificImageFile(QStringLiteral(":/utils/images/panel_manage_button.png")));
|
|
StyleHelper::drawCornerImage(button, &p, rect(), buttonBorderWidth, buttonBorderWidth, buttonBorderWidth, buttonBorderWidth);
|
|
}
|
|
QStyle *s = style();
|
|
QStyleOption arrowOpt;
|
|
arrowOpt.initFrom(this);
|
|
arrowOpt.rect = QRect(6, rect().center().y() - 3, 8, 8);
|
|
arrowOpt.rect.translate(0, -3);
|
|
s->drawPrimitive(QStyle::PE_IndicatorArrowUp, &arrowOpt, &p, this);
|
|
arrowOpt.rect.translate(0, 6);
|
|
s->drawPrimitive(QStyle::PE_IndicatorArrowDown, &arrowOpt, &p, this);
|
|
}
|
|
|
|
BadgeLabel::BadgeLabel()
|
|
{
|
|
m_font = QApplication::font();
|
|
m_font.setBold(true);
|
|
m_font.setPixelSize(11);
|
|
}
|
|
|
|
void BadgeLabel::paint(QPainter *p, int x, int y, bool isChecked)
|
|
{
|
|
const QRectF rect(QRect(QPoint(x, y), m_size));
|
|
p->save();
|
|
|
|
p->setBrush(creatorTheme()->color(isChecked? Theme::BadgeLabelBackgroundColorChecked
|
|
: Theme::BadgeLabelBackgroundColorUnchecked));
|
|
p->setPen(Qt::NoPen);
|
|
p->setRenderHint(QPainter::Antialiasing, true);
|
|
p->drawRoundedRect(rect, m_padding, m_padding, Qt::AbsoluteSize);
|
|
|
|
p->setFont(m_font);
|
|
p->setPen(creatorTheme()->color(isChecked ? Theme::BadgeLabelTextColorChecked
|
|
: Theme::BadgeLabelTextColorUnchecked));
|
|
p->drawText(rect, Qt::AlignCenter, m_text);
|
|
|
|
p->restore();
|
|
}
|
|
|
|
void BadgeLabel::setText(const QString &text)
|
|
{
|
|
m_text = text;
|
|
calculateSize();
|
|
}
|
|
|
|
QString BadgeLabel::text() const
|
|
{
|
|
return m_text;
|
|
}
|
|
|
|
QSize BadgeLabel::sizeHint() const
|
|
{
|
|
return m_size;
|
|
}
|
|
|
|
void BadgeLabel::calculateSize()
|
|
{
|
|
const QFontMetrics fm(m_font);
|
|
m_size = fm.size(Qt::TextSingleLine, m_text);
|
|
m_size.setWidth(m_size.width() + m_padding * 1.5);
|
|
m_size.setHeight(2 * m_padding + 1); // Needs to be uneven for pixel perfect vertical centering in the button
|
|
}
|
|
|
|
} // namespace Internal
|
|
} // namespace Core
|