1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "outputpanemanager.h"
27 #include "outputpane.h"
28 #include "findplaceholder.h"
29 
30 #include "icore.h"
31 #include "ioutputpane.h"
32 #include "modemanager.h"
33 #include "statusbarmanager.h"
34 
35 #include <coreplugin/actionmanager/actionmanager.h>
36 #include <coreplugin/actionmanager/actioncontainer.h>
37 #include <coreplugin/actionmanager/command.h>
38 #include <coreplugin/actionmanager/commandbutton.h>
39 #include <coreplugin/editormanager/editormanager.h>
40 #include <coreplugin/editormanager/ieditor.h>
41 #include <coreplugin/find/optionspopup.h>
42 
43 #include <utils/algorithm.h>
44 #include <utils/hostosinfo.h>
45 #include <utils/styledbar.h>
46 #include <utils/stylehelper.h>
47 #include <utils/proxyaction.h>
48 #include <utils/qtcassert.h>
49 #include <utils/theme/theme.h>
50 #include <utils/utilsicons.h>
51 
52 #include <QDebug>
53 
54 #include <QAction>
55 #include <QApplication>
56 #include <QComboBox>
57 #include <QFocusEvent>
58 #include <QHBoxLayout>
59 #include <QLabel>
60 #include <QMenu>
61 #include <QPainter>
62 #include <QStyle>
63 #include <QStackedWidget>
64 #include <QToolButton>
65 #include <QTimeLine>
66 
67 using namespace Utils;
68 using namespace Core::Internal;
69 
70 namespace Core {
71 namespace Internal {
72 
73 class OutputPaneData
74 {
75 public:
OutputPaneData(IOutputPane * pane=nullptr)76     OutputPaneData(IOutputPane *pane = nullptr) : pane(pane) {}
77 
78     IOutputPane *pane = nullptr;
79     Id id;
80     OutputPaneToggleButton *button = nullptr;
81     QAction *action = nullptr;
82     bool buttonVisible = false;
83 };
84 
85 static QVector<OutputPaneData> g_outputPanes;
86 static bool g_managerConstructed = false; // For debugging reasons.
87 
88 } // Internal
89 
90 // OutputPane
91 
IOutputPane(QObject * parent)92 IOutputPane::IOutputPane(QObject *parent)
93     : QObject(parent),
94       m_zoomInButton(new Core::CommandButton),
95       m_zoomOutButton(new Core::CommandButton)
96 {
97     // We need all pages first. Ignore latecomers and shout.
98     QTC_ASSERT(!g_managerConstructed, return);
99     g_outputPanes.append(OutputPaneData(this));
100 
101     m_zoomInButton->setIcon(Utils::Icons::PLUS_TOOLBAR.icon());
102     m_zoomInButton->setCommandId(Constants::ZOOM_IN);
103     connect(m_zoomInButton, &QToolButton::clicked, this, [this] { emit zoomInRequested(1); });
104 
105     m_zoomOutButton->setIcon(Utils::Icons::MINUS.icon());
106     m_zoomOutButton->setCommandId(Constants::ZOOM_OUT);
107     connect(m_zoomOutButton, &QToolButton::clicked, this, [this] { emit zoomOutRequested(1); });
108 }
109 
~IOutputPane()110 IOutputPane::~IOutputPane()
111 {
112     const int i = Utils::indexOf(g_outputPanes, Utils::equal(&OutputPaneData::pane, this));
113     QTC_ASSERT(i >= 0, return);
114     delete g_outputPanes.at(i).button;
115     g_outputPanes.removeAt(i);
116 
117     delete m_zoomInButton;
118     delete m_zoomOutButton;
119 }
120 
toolBarWidgets() const121 QList<QWidget *> IOutputPane::toolBarWidgets() const
122 {
123     QList<QWidget *> widgets;
124     if (m_filterOutputLineEdit)
125         widgets << m_filterOutputLineEdit;
126     return widgets << m_zoomInButton << m_zoomOutButton;
127 }
128 
visibilityChanged(bool)129 void IOutputPane::visibilityChanged(bool /*visible*/)
130 {
131 }
132 
setFont(const QFont & font)133 void IOutputPane::setFont(const QFont &font)
134 {
135     emit fontChanged(font);
136 }
137 
setWheelZoomEnabled(bool enabled)138 void IOutputPane::setWheelZoomEnabled(bool enabled)
139 {
140     emit wheelZoomEnabledChanged(enabled);
141 }
142 
setupFilterUi(const QString & historyKey)143 void IOutputPane::setupFilterUi(const QString &historyKey)
144 {
145     m_filterOutputLineEdit = new FancyLineEdit;
146     m_filterActionRegexp = new QAction(this);
147     m_filterActionRegexp->setCheckable(true);
148     m_filterActionRegexp->setText(tr("Use Regular Expressions"));
149     connect(m_filterActionRegexp, &QAction::toggled, this, &IOutputPane::setRegularExpressions);
150     Core::ActionManager::registerAction(m_filterActionRegexp, filterRegexpActionId());
151 
152     m_filterActionCaseSensitive = new QAction(this);
153     m_filterActionCaseSensitive->setCheckable(true);
154     m_filterActionCaseSensitive->setText(tr("Case Sensitive"));
155     connect(m_filterActionCaseSensitive, &QAction::toggled, this, &IOutputPane::setCaseSensitive);
156     Core::ActionManager::registerAction(m_filterActionCaseSensitive,
157                                         filterCaseSensitivityActionId());
158 
159     m_invertFilterAction = new QAction(this);
160     m_invertFilterAction->setCheckable(true);
161     m_invertFilterAction->setText(tr("Show Non-matching Lines"));
162     connect(m_invertFilterAction, &QAction::toggled, this, [this] {
163         m_invertFilter = m_invertFilterAction->isChecked();
164         updateFilter();
165     });
166     Core::ActionManager::registerAction(m_invertFilterAction, filterInvertedActionId());
167 
168     m_filterOutputLineEdit->setPlaceholderText(tr("Filter output..."));
169     m_filterOutputLineEdit->setButtonVisible(FancyLineEdit::Left, true);
170     m_filterOutputLineEdit->setButtonIcon(FancyLineEdit::Left, Icons::MAGNIFIER.icon());
171     m_filterOutputLineEdit->setFiltering(true);
172     m_filterOutputLineEdit->setEnabled(false);
173     m_filterOutputLineEdit->setHistoryCompleter(historyKey);
174     connect(m_filterOutputLineEdit, &FancyLineEdit::textChanged,
175             this, &IOutputPane::updateFilter);
176     connect(m_filterOutputLineEdit, &FancyLineEdit::returnPressed,
177             this, &IOutputPane::updateFilter);
178     connect(m_filterOutputLineEdit, &FancyLineEdit::leftButtonClicked,
179             this, &IOutputPane::filterOutputButtonClicked);
180 }
181 
filterText() const182 QString IOutputPane::filterText() const
183 {
184     return m_filterOutputLineEdit->text();
185 }
186 
setFilteringEnabled(bool enable)187 void IOutputPane::setFilteringEnabled(bool enable)
188 {
189     m_filterOutputLineEdit->setEnabled(enable);
190 }
191 
setupContext(const char * context,QWidget * widget)192 void IOutputPane::setupContext(const char *context, QWidget *widget)
193 {
194     QTC_ASSERT(!m_context, return);
195     m_context = new IContext(this);
196     m_context->setContext(Context(context));
197     m_context->setWidget(widget);
198     ICore::addContextObject(m_context);
199 
200     const auto zoomInAction = new QAction(this);
201     Core::ActionManager::registerAction(zoomInAction, Constants::ZOOM_IN, m_context->context());
202     connect(zoomInAction, &QAction::triggered, this, [this] { emit zoomInRequested(1); });
203     const auto zoomOutAction = new QAction(this);
204     Core::ActionManager::registerAction(zoomOutAction, Constants::ZOOM_OUT, m_context->context());
205     connect(zoomOutAction, &QAction::triggered, this, [this] { emit zoomOutRequested(1); });
206     const auto resetZoomAction = new QAction(this);
207     Core::ActionManager::registerAction(resetZoomAction, Constants::ZOOM_RESET,
208                                         m_context->context());
209     connect(resetZoomAction, &QAction::triggered, this, &IOutputPane::resetZoomRequested);
210 }
211 
setZoomButtonsEnabled(bool enabled)212 void IOutputPane::setZoomButtonsEnabled(bool enabled)
213 {
214     m_zoomInButton->setEnabled(enabled);
215     m_zoomOutButton->setEnabled(enabled);
216 }
217 
updateFilter()218 void IOutputPane::updateFilter()
219 {
220     QTC_ASSERT(false, qDebug() << "updateFilter() needs to get re-implemented");
221 }
222 
filterOutputButtonClicked()223 void IOutputPane::filterOutputButtonClicked()
224 {
225     auto popup = new Core::OptionsPopup(m_filterOutputLineEdit,
226     {filterRegexpActionId(), filterCaseSensitivityActionId(), filterInvertedActionId()});
227     popup->show();
228 }
229 
setRegularExpressions(bool regularExpressions)230 void IOutputPane::setRegularExpressions(bool regularExpressions)
231 {
232     m_filterRegexp = regularExpressions;
233     updateFilter();
234 }
235 
filterRegexpActionId() const236 Id IOutputPane::filterRegexpActionId() const
237 {
238     return Id("OutputFilter.RegularExpressions").withSuffix(metaObject()->className());
239 }
240 
filterCaseSensitivityActionId() const241 Id IOutputPane::filterCaseSensitivityActionId() const
242 {
243     return Id("OutputFilter.CaseSensitive").withSuffix(metaObject()->className());
244 }
245 
filterInvertedActionId() const246 Id IOutputPane::filterInvertedActionId() const
247 {
248     return Id("OutputFilter.Invert").withSuffix(metaObject()->className());
249 }
250 
setCaseSensitive(bool caseSensitive)251 void IOutputPane::setCaseSensitive(bool caseSensitive)
252 {
253     m_filterCaseSensitivity = caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
254     updateFilter();
255 }
256 
257 namespace Internal {
258 
259 const char outputPaneSettingsKeyC[] = "OutputPaneVisibility";
260 const char outputPaneIdKeyC[] = "id";
261 const char outputPaneVisibleKeyC[] = "visible";
262 const int buttonBorderWidth = 3;
263 
numberAreaWidth()264 static int numberAreaWidth()
265 {
266     return creatorTheme()->flag(Theme::FlatToolBars) ? 15 : 19;
267 }
268 
269 ////
270 // OutputPaneManager
271 ////
272 
273 static OutputPaneManager *m_instance = nullptr;
274 
create()275 void OutputPaneManager::create()
276 {
277    m_instance = new OutputPaneManager;
278 }
279 
destroy()280 void OutputPaneManager::destroy()
281 {
282     delete m_instance;
283     m_instance = nullptr;
284 }
285 
instance()286 OutputPaneManager *OutputPaneManager::instance()
287 {
288     return m_instance;
289 }
290 
updateStatusButtons(bool visible)291 void OutputPaneManager::updateStatusButtons(bool visible)
292 {
293     int idx = currentIndex();
294     if (idx == -1)
295         return;
296     QTC_ASSERT(idx < g_outputPanes.size(), return);
297     const OutputPaneData &data = g_outputPanes.at(idx);
298     QTC_ASSERT(data.button, return);
299     data.button->setChecked(visible);
300     data.pane->visibilityChanged(visible);
301 }
302 
updateMaximizeButton(bool maximized)303 void OutputPaneManager::updateMaximizeButton(bool maximized)
304 {
305     if (maximized) {
306         m_instance->m_minMaxAction->setIcon(m_instance->m_minimizeIcon);
307         m_instance->m_minMaxAction->setText(tr("Minimize Output Pane"));
308     } else {
309         m_instance->m_minMaxAction->setIcon(m_instance->m_maximizeIcon);
310         m_instance->m_minMaxAction->setText(tr("Maximize Output Pane"));
311     }
312 }
313 
314 // Return shortcut as Alt+<number> or Cmd+<number> if number is a non-zero digit
paneShortCut(int number)315 static QKeySequence paneShortCut(int number)
316 {
317     if (number < 1 || number > 9)
318         return QKeySequence();
319 
320     const int modifier = HostOsInfo::isMacHost() ? Qt::CTRL : Qt::ALT;
321     return QKeySequence(modifier | (Qt::Key_0 + number));
322 }
323 
OutputPaneManager(QWidget * parent)324 OutputPaneManager::OutputPaneManager(QWidget *parent) :
325     QWidget(parent),
326     m_titleLabel(new QLabel),
327     m_manageButton(new OutputPaneManageButton),
328     m_closeButton(new QToolButton),
329     m_minMaxButton(new QToolButton),
330     m_outputWidgetPane(new QStackedWidget),
331     m_opToolBarWidgets(new QStackedWidget),
332     m_minimizeIcon(Utils::Icons::ARROW_DOWN.icon()),
333     m_maximizeIcon(Utils::Icons::ARROW_UP.icon())
334 {
335     setWindowTitle(tr("Output"));
336 
337     m_titleLabel->setContentsMargins(5, 0, 5, 0);
338 
339     m_clearAction = new QAction(this);
340     m_clearAction->setIcon(Utils::Icons::CLEAN.icon());
341     m_clearAction->setText(tr("Clear"));
342     connect(m_clearAction, &QAction::triggered, this, &OutputPaneManager::clearPage);
343 
344     m_nextAction = new QAction(this);
345     m_nextAction->setIcon(Utils::Icons::NEXT.icon());
346     m_nextAction->setText(tr("Next Item"));
347     connect(m_nextAction, &QAction::triggered, this, &OutputPaneManager::slotNext);
348 
349     m_prevAction = new QAction(this);
350     m_prevAction->setIcon(Utils::Icons::PREV.icon());
351     m_prevAction->setText(tr("Previous Item"));
352     connect(m_prevAction, &QAction::triggered, this, &OutputPaneManager::slotPrev);
353 
354     m_minMaxAction = new QAction(this);
355     m_minMaxAction->setIcon(m_maximizeIcon);
356     m_minMaxAction->setText(tr("Maximize Output Pane"));
357 
358     m_closeButton->setIcon(Icons::CLOSE_SPLIT_BOTTOM.icon());
359     connect(m_closeButton, &QAbstractButton::clicked, this, &OutputPaneManager::slotHide);
360 
361     connect(ICore::instance(), &ICore::saveSettingsRequested, this, &OutputPaneManager::saveSettings);
362 
363     auto mainlayout = new QVBoxLayout;
364     mainlayout->setSpacing(0);
365     mainlayout->setContentsMargins(0, 0, 0, 0);
366     m_toolBar = new StyledBar;
367     auto toolLayout = new QHBoxLayout(m_toolBar);
368     toolLayout->setContentsMargins(0, 0, 0, 0);
369     toolLayout->setSpacing(0);
370     toolLayout->addWidget(m_titleLabel);
371     toolLayout->addWidget(new StyledSeparator);
372     m_clearButton = new QToolButton;
373     toolLayout->addWidget(m_clearButton);
374     m_prevToolButton = new QToolButton;
375     toolLayout->addWidget(m_prevToolButton);
376     m_nextToolButton = new QToolButton;
377     toolLayout->addWidget(m_nextToolButton);
378     toolLayout->addWidget(m_opToolBarWidgets);
379     toolLayout->addWidget(m_minMaxButton);
380     toolLayout->addWidget(m_closeButton);
381     mainlayout->addWidget(m_toolBar);
382     mainlayout->addWidget(m_outputWidgetPane, 10);
383     mainlayout->addWidget(new FindToolBarPlaceHolder(this));
384     setLayout(mainlayout);
385 
386     m_buttonsWidget = new QWidget;
387     m_buttonsWidget->setObjectName("OutputPaneButtons"); // used for UI introduction
388     m_buttonsWidget->setLayout(new QHBoxLayout);
389     m_buttonsWidget->layout()->setContentsMargins(5,0,0,0);
390     m_buttonsWidget->layout()->setSpacing(
391             creatorTheme()->flag(Theme::FlatToolBars) ? 9 : 4);
392 
393     StatusBarManager::addStatusBarWidget(m_buttonsWidget, StatusBarManager::Second);
394 
395     ActionContainer *mview = ActionManager::actionContainer(Constants::M_VIEW);
396 
397     // Window->Output Panes
398     ActionContainer *mpanes = ActionManager::createMenu(Constants::M_VIEW_PANES);
399     mview->addMenu(mpanes, Constants::G_VIEW_PANES);
400     mpanes->menu()->setTitle(tr("Output &Panes"));
401     mpanes->appendGroup("Coreplugin.OutputPane.ActionsGroup");
402     mpanes->appendGroup("Coreplugin.OutputPane.PanesGroup");
403 
404     Command *cmd;
405 
406     cmd = ActionManager::registerAction(m_clearAction, Constants::OUTPUTPANE_CLEAR);
407     m_clearButton->setDefaultAction(
408         ProxyAction::proxyActionWithIcon(m_clearAction, Utils::Icons::CLEAN_TOOLBAR.icon()));
409     mpanes->addAction(cmd, "Coreplugin.OutputPane.ActionsGroup");
410 
411     cmd = ActionManager::registerAction(m_prevAction, "Coreplugin.OutputPane.previtem");
412     cmd->setDefaultKeySequence(QKeySequence(tr("Shift+F6")));
413     m_prevToolButton->setDefaultAction(
414         ProxyAction::proxyActionWithIcon(m_prevAction, Utils::Icons::PREV_TOOLBAR.icon()));
415     mpanes->addAction(cmd, "Coreplugin.OutputPane.ActionsGroup");
416 
417     cmd = ActionManager::registerAction(m_nextAction, "Coreplugin.OutputPane.nextitem");
418     m_nextToolButton->setDefaultAction(
419         ProxyAction::proxyActionWithIcon(m_nextAction, Utils::Icons::NEXT_TOOLBAR.icon()));
420     cmd->setDefaultKeySequence(QKeySequence(tr("F6")));
421     mpanes->addAction(cmd, "Coreplugin.OutputPane.ActionsGroup");
422 
423     cmd = ActionManager::registerAction(m_minMaxAction, "Coreplugin.OutputPane.minmax");
424     cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Ctrl+Shift+9") : tr("Alt+Shift+9")));
425     cmd->setAttribute(Command::CA_UpdateText);
426     cmd->setAttribute(Command::CA_UpdateIcon);
427     mpanes->addAction(cmd, "Coreplugin.OutputPane.ActionsGroup");
428     connect(m_minMaxAction, &QAction::triggered, this, &OutputPaneManager::toggleMaximized);
429     m_minMaxButton->setDefaultAction(cmd->action());
430 
431     mpanes->addSeparator("Coreplugin.OutputPane.ActionsGroup");
432 }
433 
initialize()434 void OutputPaneManager::initialize()
435 {
436     ActionContainer *mpanes = ActionManager::actionContainer(Constants::M_VIEW_PANES);
437     QFontMetrics titleFm = m_instance->m_titleLabel->fontMetrics();
438     int minTitleWidth = 0;
439 
440     Utils::sort(g_outputPanes, [](const OutputPaneData &d1, const OutputPaneData &d2) {
441         return d1.pane->priorityInStatusBar() > d2.pane->priorityInStatusBar();
442     });
443     const int n = g_outputPanes.size();
444 
445     int shortcutNumber = 1;
446     const Id baseId = "QtCreator.Pane.";
447     for (int i = 0; i != n; ++i) {
448         OutputPaneData &data = g_outputPanes[i];
449         IOutputPane *outPane = data.pane;
450         const int idx = m_instance->m_outputWidgetPane->addWidget(outPane->outputWidget(m_instance));
451         QTC_CHECK(idx == i);
452 
453         connect(outPane, &IOutputPane::showPage, m_instance, [idx](int flags) {
454             m_instance->showPage(idx, flags);
455         });
456         connect(outPane, &IOutputPane::hidePage, m_instance, &OutputPaneManager::slotHide);
457 
458         connect(outPane, &IOutputPane::togglePage, m_instance, [idx](int flags) {
459             if (OutputPanePlaceHolder::isCurrentVisible() && m_instance->currentIndex() == idx)
460                 m_instance->slotHide();
461             else
462                 m_instance->showPage(idx, flags);
463         });
464 
465         connect(outPane, &IOutputPane::navigateStateUpdate, m_instance, [idx, outPane] {
466             if (m_instance->currentIndex() == idx) {
467                 m_instance->m_prevAction->setEnabled(outPane->canNavigate()
468                                                      && outPane->canPrevious());
469                 m_instance->m_nextAction->setEnabled(outPane->canNavigate() && outPane->canNext());
470             }
471         });
472 
473         QWidget *toolButtonsContainer = new QWidget(m_instance->m_opToolBarWidgets);
474         auto toolButtonsLayout = new QHBoxLayout;
475         toolButtonsLayout->setContentsMargins(0, 0, 0, 0);
476         toolButtonsLayout->setSpacing(0);
477         foreach (QWidget *toolButton, outPane->toolBarWidgets())
478             toolButtonsLayout->addWidget(toolButton);
479         toolButtonsLayout->addStretch(5);
480         toolButtonsContainer->setLayout(toolButtonsLayout);
481 
482         m_instance->m_opToolBarWidgets->addWidget(toolButtonsContainer);
483 
484         minTitleWidth = qMax(minTitleWidth, titleFm.horizontalAdvance(outPane->displayName()));
485 
486         QString suffix = outPane->displayName().simplified();
487         suffix.remove(QLatin1Char(' '));
488         data.id = baseId.withSuffix(suffix);
489         data.action = new QAction(outPane->displayName(), m_instance);
490         Command *cmd = ActionManager::registerAction(data.action, data.id);
491 
492         mpanes->addAction(cmd, "Coreplugin.OutputPane.PanesGroup");
493 
494         cmd->setDefaultKeySequence(paneShortCut(shortcutNumber));
495         auto button = new OutputPaneToggleButton(shortcutNumber,
496                                                  outPane->displayName(),
497                                                  cmd->action());
498         data.button = button;
499 
500         connect(outPane, &IOutputPane::flashButton, button, [button] { button->flash(); });
501         connect(outPane,
502                 &IOutputPane::setBadgeNumber,
503                 button,
504                 &OutputPaneToggleButton::setIconBadgeNumber);
505 
506         ++shortcutNumber;
507         m_instance->m_buttonsWidget->layout()->addWidget(data.button);
508         connect(data.button, &QAbstractButton::clicked, m_instance, [i] {
509             m_instance->buttonTriggered(i);
510         });
511 
512         bool visible = outPane->priorityInStatusBar() != -1;
513         data.button->setVisible(visible);
514         data.buttonVisible = visible;
515 
516         connect(data.action, &QAction::triggered, m_instance, [i] {
517             m_instance->shortcutTriggered(i);
518         });
519     }
520 
521     m_instance->m_titleLabel->setMinimumWidth(
522         minTitleWidth + m_instance->m_titleLabel->contentsMargins().left()
523         + m_instance->m_titleLabel->contentsMargins().right());
524     m_instance->m_buttonsWidget->layout()->addWidget(m_instance->m_manageButton);
525     connect(m_instance->m_manageButton,
526             &QAbstractButton::clicked,
527             m_instance,
528             &OutputPaneManager::popupMenu);
529 
530     m_instance->readSettings();
531 }
532 
533 OutputPaneManager::~OutputPaneManager() = default;
534 
shortcutTriggered(int idx)535 void OutputPaneManager::shortcutTriggered(int idx)
536 {
537     IOutputPane *outputPane = g_outputPanes.at(idx).pane;
538     // Now check the special case, the output window is already visible,
539     // we are already on that page but the outputpane doesn't have focus
540     // then just give it focus.
541     int current = currentIndex();
542     if (OutputPanePlaceHolder::isCurrentVisible() && current == idx) {
543         if ((!m_outputWidgetPane->isActiveWindow() || !outputPane->hasFocus())
544             && outputPane->canFocus()) {
545             outputPane->setFocus();
546             ICore::raiseWindow(m_outputWidgetPane);
547         } else {
548             slotHide();
549         }
550     } else {
551         // Else do the same as clicking on the button does.
552         buttonTriggered(idx);
553     }
554 }
555 
outputPaneHeightSetting()556 int OutputPaneManager::outputPaneHeightSetting()
557 {
558     return m_instance->m_outputPaneHeightSetting;
559 }
560 
setOutputPaneHeightSetting(int value)561 void OutputPaneManager::setOutputPaneHeightSetting(int value)
562 {
563     m_instance->m_outputPaneHeightSetting = value;
564 }
565 
toggleMaximized()566 void OutputPaneManager::toggleMaximized()
567 {
568     OutputPanePlaceHolder *ph = OutputPanePlaceHolder::getCurrent();
569     QTC_ASSERT(ph, return);
570 
571     if (!ph->isVisible()) // easier than disabling/enabling the action
572         return;
573     ph->setMaximized(!ph->isMaximized());
574 }
575 
buttonTriggered(int idx)576 void OutputPaneManager::buttonTriggered(int idx)
577 {
578     QTC_ASSERT(idx >= 0, return);
579     if (idx == currentIndex() && OutputPanePlaceHolder::isCurrentVisible()) {
580         // we should toggle and the page is already visible and we are actually closeable
581         slotHide();
582     } else {
583         showPage(idx, IOutputPane::ModeSwitch | IOutputPane::WithFocus);
584     }
585 }
586 
readSettings()587 void OutputPaneManager::readSettings()
588 {
589     QSettings *settings = ICore::settings();
590     int num = settings->beginReadArray(QLatin1String(outputPaneSettingsKeyC));
591     for (int i = 0; i < num; ++i) {
592         settings->setArrayIndex(i);
593         Id id = Id::fromSetting(settings->value(QLatin1String(outputPaneIdKeyC)));
594         const int idx = Utils::indexOf(g_outputPanes, Utils::equal(&OutputPaneData::id, id));
595         if (idx < 0) // happens for e.g. disabled plugins (with outputpanes) that were loaded before
596             continue;
597         const bool visible = settings->value(QLatin1String(outputPaneVisibleKeyC)).toBool();
598         g_outputPanes[idx].buttonVisible = visible;
599         g_outputPanes[idx].button->setVisible(visible);
600     }
601     settings->endArray();
602 
603     m_outputPaneHeightSetting = settings->value(QLatin1String("OutputPanePlaceHolder/Height"), 0).toInt();
604 }
605 
slotNext()606 void OutputPaneManager::slotNext()
607 {
608     int idx = currentIndex();
609     ensurePageVisible(idx);
610     IOutputPane *out = g_outputPanes.at(idx).pane;
611     if (out->canNext())
612         out->goToNext();
613 }
614 
slotPrev()615 void OutputPaneManager::slotPrev()
616 {
617     int idx = currentIndex();
618     ensurePageVisible(idx);
619     IOutputPane *out = g_outputPanes.at(idx).pane;
620     if (out->canPrevious())
621         out->goToPrev();
622 }
623 
slotHide()624 void OutputPaneManager::slotHide()
625 {
626     OutputPanePlaceHolder *ph = OutputPanePlaceHolder::getCurrent();
627     if (ph) {
628         emit ph->visibilityChangeRequested(false);
629         ph->setVisible(false);
630         int idx = currentIndex();
631         QTC_ASSERT(idx >= 0, return);
632         g_outputPanes.at(idx).button->setChecked(false);
633         g_outputPanes.at(idx).pane->visibilityChanged(false);
634         if (IEditor *editor = EditorManager::currentEditor()) {
635             QWidget *w = editor->widget()->focusWidget();
636             if (!w)
637                 w = editor->widget();
638             w->setFocus();
639         }
640     }
641 }
642 
ensurePageVisible(int idx)643 void OutputPaneManager::ensurePageVisible(int idx)
644 {
645     //int current = currentIndex();
646     //if (current != idx)
647     //    m_outputWidgetPane->setCurrentIndex(idx);
648     setCurrentIndex(idx);
649 }
650 
showPage(int idx,int flags)651 void OutputPaneManager::showPage(int idx, int flags)
652 {
653     QTC_ASSERT(idx >= 0, return);
654     OutputPanePlaceHolder *ph = OutputPanePlaceHolder::getCurrent();
655 
656     if (!ph && flags & IOutputPane::ModeSwitch) {
657         // In this mode we don't have a placeholder
658         // switch to the output mode and switch the page
659         ModeManager::activateMode(Id(Constants::MODE_EDIT));
660         ph = OutputPanePlaceHolder::getCurrent();
661     }
662 
663     bool onlyFlash = !ph
664             || (g_outputPanes.at(currentIndex()).pane->hasFocus()
665                 && !(flags & IOutputPane::WithFocus)
666                 && idx != currentIndex());
667 
668     if (onlyFlash) {
669         g_outputPanes.at(idx).button->flash();
670     } else {
671         emit ph->visibilityChangeRequested(true);
672         // make the page visible
673         ph->setVisible(true);
674 
675         ensurePageVisible(idx);
676         IOutputPane *out = g_outputPanes.at(idx).pane;
677         if (flags & IOutputPane::WithFocus) {
678             if (out->canFocus())
679                 out->setFocus();
680             ICore::raiseWindow(m_outputWidgetPane);
681         }
682 
683         if (flags & IOutputPane::EnsureSizeHint)
684             ph->ensureSizeHintAsMinimum();
685     }
686 }
687 
focusInEvent(QFocusEvent * e)688 void OutputPaneManager::focusInEvent(QFocusEvent *e)
689 {
690     if (QWidget *w = m_outputWidgetPane->currentWidget())
691         w->setFocus(e->reason());
692 }
693 
setCurrentIndex(int idx)694 void OutputPaneManager::setCurrentIndex(int idx)
695 {
696     static int lastIndex = -1;
697 
698     if (lastIndex != -1) {
699         g_outputPanes.at(lastIndex).button->setChecked(false);
700         g_outputPanes.at(lastIndex).pane->visibilityChanged(false);
701     }
702 
703     if (idx != -1) {
704         m_outputWidgetPane->setCurrentIndex(idx);
705         m_opToolBarWidgets->setCurrentIndex(idx);
706 
707         OutputPaneData &data = g_outputPanes[idx];
708         IOutputPane *pane = data.pane;
709         data.button->show();
710         data.buttonVisible = true;
711         pane->visibilityChanged(true);
712 
713         bool canNavigate = pane->canNavigate();
714         m_prevAction->setEnabled(canNavigate && pane->canPrevious());
715         m_nextAction->setEnabled(canNavigate && pane->canNext());
716         g_outputPanes.at(idx).button->setChecked(OutputPanePlaceHolder::isCurrentVisible());
717         m_titleLabel->setText(pane->displayName());
718     }
719 
720     lastIndex = idx;
721 }
722 
popupMenu()723 void OutputPaneManager::popupMenu()
724 {
725     QMenu menu;
726     int idx = 0;
727     for (OutputPaneData &data : g_outputPanes) {
728         QAction *act = menu.addAction(data.pane->displayName());
729         act->setCheckable(true);
730         act->setChecked(data.buttonVisible);
731         act->setData(idx);
732         ++idx;
733     }
734     QAction *result = menu.exec(QCursor::pos());
735     if (!result)
736         return;
737     idx = result->data().toInt();
738     QTC_ASSERT(idx >= 0 && idx < g_outputPanes.size(), return);
739     OutputPaneData &data = g_outputPanes[idx];
740     if (data.buttonVisible) {
741         data.pane->visibilityChanged(false);
742         data.button->setChecked(false);
743         data.button->hide();
744         data.buttonVisible = false;
745     } else {
746         showPage(idx, IOutputPane::ModeSwitch);
747     }
748 }
749 
saveSettings() const750 void OutputPaneManager::saveSettings() const
751 {
752     QSettings *settings = ICore::settings();
753     const int n = g_outputPanes.size();
754     settings->beginWriteArray(QLatin1String(outputPaneSettingsKeyC), n);
755     for (int i = 0; i < n; ++i) {
756         const OutputPaneData &data = g_outputPanes.at(i);
757         settings->setArrayIndex(i);
758         settings->setValue(QLatin1String(outputPaneIdKeyC), data.id.toSetting());
759         settings->setValue(QLatin1String(outputPaneVisibleKeyC), data.buttonVisible);
760     }
761     settings->endArray();
762     int heightSetting = m_outputPaneHeightSetting;
763     // update if possible
764     if (OutputPanePlaceHolder *curr = OutputPanePlaceHolder::getCurrent())
765         heightSetting = curr->nonMaximizedSize();
766     settings->setValue(QLatin1String("OutputPanePlaceHolder/Height"), heightSetting);
767 }
768 
clearPage()769 void OutputPaneManager::clearPage()
770 {
771     int idx = currentIndex();
772     if (idx >= 0)
773         g_outputPanes.at(idx).pane->clearContents();
774 }
775 
currentIndex() const776 int OutputPaneManager::currentIndex() const
777 {
778     return m_outputWidgetPane->currentIndex();
779 }
780 
781 
782 ///////////////////////////////////////////////////////////////////////
783 //
784 // OutputPaneToolButton
785 //
786 ///////////////////////////////////////////////////////////////////////
787 
OutputPaneToggleButton(int number,const QString & text,QAction * action,QWidget * parent)788 OutputPaneToggleButton::OutputPaneToggleButton(int number, const QString &text,
789                                                QAction *action, QWidget *parent)
790     : QToolButton(parent)
791     , m_number(QString::number(number))
792     , m_text(text)
793     , m_action(action)
794     , m_flashTimer(new QTimeLine(1000, this))
795 {
796     setFocusPolicy(Qt::NoFocus);
797     setCheckable(true);
798     QFont fnt = QApplication::font();
799     setFont(fnt);
800     if (m_action)
801         connect(m_action, &QAction::changed, this, &OutputPaneToggleButton::updateToolTip);
802 
803     m_flashTimer->setDirection(QTimeLine::Forward);
804     m_flashTimer->setEasingCurve(QEasingCurve::SineCurve);
805     m_flashTimer->setFrameRange(0, 92);
806     auto updateSlot = QOverload<>::of(&QWidget::update);
807     connect(m_flashTimer, &QTimeLine::valueChanged, this, updateSlot);
808     connect(m_flashTimer, &QTimeLine::finished, this, updateSlot);
809     updateToolTip();
810 }
811 
updateToolTip()812 void OutputPaneToggleButton::updateToolTip()
813 {
814     QTC_ASSERT(m_action, return);
815     setToolTip(m_action->toolTip());
816 }
817 
sizeHint() const818 QSize OutputPaneToggleButton::sizeHint() const
819 {
820     ensurePolished();
821 
822     QSize s = fontMetrics().size(Qt::TextSingleLine, m_text);
823 
824     // Expand to account for border image
825     s.rwidth() += numberAreaWidth() + 1 + buttonBorderWidth + buttonBorderWidth;
826 
827     if (!m_badgeNumberLabel.text().isNull())
828         s.rwidth() += m_badgeNumberLabel.sizeHint().width() + 1;
829 
830     return s;
831 }
832 
paintEvent(QPaintEvent *)833 void OutputPaneToggleButton::paintEvent(QPaintEvent*)
834 {
835     const QFontMetrics fm = fontMetrics();
836     const int baseLine = (height() - fm.height() + 1) / 2 + fm.ascent();
837     const int numberWidth = fm.horizontalAdvance(m_number);
838 
839     QPainter p(this);
840 
841     QStyleOption styleOption;
842     styleOption.initFrom(this);
843     const bool hovered = !HostOsInfo::isMacHost() && (styleOption.state & QStyle::State_MouseOver);
844 
845     if (creatorTheme()->flag(Theme::FlatToolBars)) {
846         Theme::Color c = Theme::BackgroundColorDark;
847 
848         if (hovered)
849             c = Theme::BackgroundColorHover;
850         else if (isDown() || isChecked())
851             c = Theme::BackgroundColorSelected;
852 
853         if (c != Theme::BackgroundColorDark)
854             p.fillRect(rect(), creatorTheme()->color(c));
855     } else {
856         const QImage *image = nullptr;
857         if (isDown()) {
858             static const QImage pressed(
859                         StyleHelper::dpiSpecificImageFile(":/utils/images/panel_button_pressed.png"));
860             image = &pressed;
861         } else if (isChecked()) {
862             if (hovered) {
863                 static const QImage checkedHover(
864                             StyleHelper::dpiSpecificImageFile(":/utils/images/panel_button_checked_hover.png"));
865                 image = &checkedHover;
866             } else {
867                 static const QImage checked(
868                             StyleHelper::dpiSpecificImageFile(":/utils/images/panel_button_checked.png"));
869                 image = &checked;
870             }
871         } else {
872             if (hovered) {
873                 static const QImage hover(
874                             StyleHelper::dpiSpecificImageFile(":/utils/images/panel_button_hover.png"));
875                 image = &hover;
876             } else {
877                 static const QImage button(
878                             StyleHelper::dpiSpecificImageFile(":/utils/images/panel_button.png"));
879                 image = &button;
880             }
881         }
882         if (image)
883             StyleHelper::drawCornerImage(*image, &p, rect(), numberAreaWidth(), buttonBorderWidth, buttonBorderWidth, buttonBorderWidth);
884     }
885 
886     if (m_flashTimer->state() == QTimeLine::Running)
887     {
888         QColor c = creatorTheme()->color(Theme::OutputPaneButtonFlashColor);
889         c.setAlpha (m_flashTimer->currentFrame());
890         QRect r = creatorTheme()->flag(Theme::FlatToolBars)
891                   ? rect() : rect().adjusted(numberAreaWidth(), 1, -1, -1);
892         p.fillRect(r, c);
893     }
894 
895     p.setFont(font());
896     p.setPen(creatorTheme()->color(Theme::OutputPaneToggleButtonTextColorChecked));
897     p.drawText((numberAreaWidth() - numberWidth) / 2, baseLine, m_number);
898     if (!isChecked())
899         p.setPen(creatorTheme()->color(Theme::OutputPaneToggleButtonTextColorUnchecked));
900     int leftPart = numberAreaWidth() + buttonBorderWidth;
901     int labelWidth = 0;
902     if (!m_badgeNumberLabel.text().isEmpty()) {
903         const QSize labelSize = m_badgeNumberLabel.sizeHint();
904         labelWidth = labelSize.width() + 3;
905         m_badgeNumberLabel.paint(&p, width() - labelWidth, (height() - labelSize.height()) / 2, isChecked());
906     }
907     p.drawText(leftPart, baseLine, fm.elidedText(m_text, Qt::ElideRight, width() - leftPart - 1 - labelWidth));
908 }
909 
checkStateSet()910 void OutputPaneToggleButton::checkStateSet()
911 {
912     //Stop flashing when button is checked
913     QToolButton::checkStateSet();
914     m_flashTimer->stop();
915 }
916 
flash(int count)917 void OutputPaneToggleButton::flash(int count)
918 {
919     setVisible(true);
920     //Start flashing if button is not checked
921     if (!isChecked()) {
922         m_flashTimer->setLoopCount(count);
923         if (m_flashTimer->state() != QTimeLine::Running)
924             m_flashTimer->start();
925         update();
926     }
927 }
928 
setIconBadgeNumber(int number)929 void OutputPaneToggleButton::setIconBadgeNumber(int number)
930 {
931     QString text = (number ? QString::number(number) : QString());
932     m_badgeNumberLabel.setText(text);
933     updateGeometry();
934 }
935 
936 
937 ///////////////////////////////////////////////////////////////////////
938 //
939 // OutputPaneManageButton
940 //
941 ///////////////////////////////////////////////////////////////////////
942 
OutputPaneManageButton()943 OutputPaneManageButton::OutputPaneManageButton()
944 {
945     setFocusPolicy(Qt::NoFocus);
946     setCheckable(true);
947     setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
948 }
949 
sizeHint() const950 QSize OutputPaneManageButton::sizeHint() const
951 {
952     ensurePolished();
953     return QSize(numberAreaWidth(), 16);
954 }
955 
paintEvent(QPaintEvent *)956 void OutputPaneManageButton::paintEvent(QPaintEvent*)
957 {
958     QPainter p(this);
959     if (!creatorTheme()->flag(Theme::FlatToolBars)) {
960         static const QImage button(StyleHelper::dpiSpecificImageFile(QStringLiteral(":/utils/images/panel_manage_button.png")));
961         StyleHelper::drawCornerImage(button, &p, rect(), buttonBorderWidth, buttonBorderWidth, buttonBorderWidth, buttonBorderWidth);
962     }
963     QStyle *s = style();
964     QStyleOption arrowOpt;
965     arrowOpt.initFrom(this);
966     arrowOpt.rect = QRect(6, rect().center().y() - 3, 8, 8);
967     arrowOpt.rect.translate(0, -3);
968     s->drawPrimitive(QStyle::PE_IndicatorArrowUp, &arrowOpt, &p, this);
969     arrowOpt.rect.translate(0, 6);
970     s->drawPrimitive(QStyle::PE_IndicatorArrowDown, &arrowOpt, &p, this);
971 }
972 
BadgeLabel()973 BadgeLabel::BadgeLabel()
974 {
975     m_font = QApplication::font();
976     m_font.setBold(true);
977     m_font.setPixelSize(11);
978 }
979 
paint(QPainter * p,int x,int y,bool isChecked)980 void BadgeLabel::paint(QPainter *p, int x, int y, bool isChecked)
981 {
982     const QRectF rect(QRect(QPoint(x, y), m_size));
983     p->save();
984 
985     p->setBrush(creatorTheme()->color(isChecked? Theme::BadgeLabelBackgroundColorChecked
986                                                : Theme::BadgeLabelBackgroundColorUnchecked));
987     p->setPen(Qt::NoPen);
988     p->setRenderHint(QPainter::Antialiasing, true);
989     p->drawRoundedRect(rect, m_padding, m_padding, Qt::AbsoluteSize);
990 
991     p->setFont(m_font);
992     p->setPen(creatorTheme()->color(isChecked ? Theme::BadgeLabelTextColorChecked
993                                               : Theme::BadgeLabelTextColorUnchecked));
994     p->drawText(rect, Qt::AlignCenter, m_text);
995 
996     p->restore();
997 }
998 
setText(const QString & text)999 void BadgeLabel::setText(const QString &text)
1000 {
1001     m_text = text;
1002     calculateSize();
1003 }
1004 
text() const1005 QString BadgeLabel::text() const
1006 {
1007     return m_text;
1008 }
1009 
sizeHint() const1010 QSize BadgeLabel::sizeHint() const
1011 {
1012     return m_size;
1013 }
1014 
calculateSize()1015 void BadgeLabel::calculateSize()
1016 {
1017     const QFontMetrics fm(m_font);
1018     m_size = fm.size(Qt::TextSingleLine, m_text);
1019     m_size.setWidth(m_size.width() + m_padding * 1.5);
1020     m_size.setHeight(2 * m_padding + 1); // Needs to be uneven for pixel perfect vertical centering in the button
1021 }
1022 
1023 } // namespace Internal
1024 } // namespace Core
1025