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 "compileoutputwindow.h"
27 
28 #include "buildmanager.h"
29 #include "ioutputparser.h"
30 #include "projectexplorer.h"
31 #include "projectexplorericons.h"
32 #include "projectexplorersettings.h"
33 #include "showoutputtaskhandler.h"
34 #include "task.h"
35 #include "taskhub.h"
36 
37 #include <coreplugin/outputwindow.h>
38 #include <coreplugin/find/basetextfind.h>
39 #include <coreplugin/icore.h>
40 #include <coreplugin/coreconstants.h>
41 #include <extensionsystem/pluginmanager.h>
42 #include <texteditor/texteditorsettings.h>
43 #include <texteditor/fontsettings.h>
44 #include <texteditor/behaviorsettings.h>
45 #include <utils/algorithm.h>
46 #include <utils/outputformatter.h>
47 #include <utils/proxyaction.h>
48 #include <utils/theme/theme.h>
49 #include <utils/utilsicons.h>
50 
51 #include <QCheckBox>
52 #include <QHBoxLayout>
53 #include <QIcon>
54 #include <QLabel>
55 #include <QPlainTextEdit>
56 #include <QSpinBox>
57 #include <QTextBlock>
58 #include <QTextCharFormat>
59 #include <QTextCursor>
60 #include <QToolButton>
61 #include <QVBoxLayout>
62 
63 namespace ProjectExplorer {
64 namespace Internal {
65 
66 const char SETTINGS_KEY[] = "ProjectExplorer/CompileOutput/Zoom";
67 const char C_COMPILE_OUTPUT[] = "ProjectExplorer.CompileOutput";
68 const char POP_UP_KEY[] = "ProjectExplorer/Settings/ShowCompilerOutput";
69 const char WRAP_OUTPUT_KEY[] = "ProjectExplorer/Settings/WrapBuildOutput";
70 const char MAX_LINES_KEY[] = "ProjectExplorer/Settings/MaxBuildOutputLines";
71 const char OPTIONS_PAGE_ID[] = "C.ProjectExplorer.CompileOutputOptions";
72 
CompileOutputWindow(QAction * cancelBuildAction)73 CompileOutputWindow::CompileOutputWindow(QAction *cancelBuildAction) :
74     m_cancelBuildButton(new QToolButton),
75     m_settingsButton(new QToolButton)
76 {
77     Core::Context context(C_COMPILE_OUTPUT);
78     m_outputWindow = new Core::OutputWindow(context, SETTINGS_KEY);
79     m_outputWindow->setWindowTitle(displayName());
80     m_outputWindow->setWindowIcon(Icons::WINDOW.icon());
81     m_outputWindow->setReadOnly(true);
82     m_outputWindow->setUndoRedoEnabled(false);
83     m_outputWindow->setMaxCharCount(Core::Constants::DEFAULT_MAX_CHAR_COUNT);
84 
85     outputFormatter()->overridePostPrintAction([this](Utils::OutputLineParser *parser) {
86         if (const auto taskParser = qobject_cast<OutputTaskParser *>(parser)) {
87             int offset = 0;
88             Utils::reverseForeach(taskParser->taskInfo(), [this, &offset](const OutputTaskParser::TaskInfo &ti) {
89                 registerPositionOf(ti.task, ti.linkedLines, ti.skippedLines, offset);
90                 offset += ti.linkedLines;
91             });
92         }
93         parser->runPostPrintActions();
94     });
95 
96     // Let selected text be colored as if the text edit was editable,
97     // otherwise the highlight for searching is too light
98     QPalette p = m_outputWindow->palette();
99     QColor activeHighlight = p.color(QPalette::Active, QPalette::Highlight);
100     p.setColor(QPalette::Highlight, activeHighlight);
101     QColor activeHighlightedText = p.color(QPalette::Active, QPalette::HighlightedText);
102     p.setColor(QPalette::HighlightedText, activeHighlightedText);
103     m_outputWindow->setPalette(p);
104 
105     Utils::ProxyAction *cancelBuildProxyButton =
106             Utils::ProxyAction::proxyActionWithIcon(cancelBuildAction,
107                                                     Utils::Icons::STOP_SMALL_TOOLBAR.icon());
108     m_cancelBuildButton->setDefaultAction(cancelBuildProxyButton);
109     m_settingsButton->setToolTip(tr("Open Settings Page"));
110     m_settingsButton->setIcon(Utils::Icons::SETTINGS_TOOLBAR.icon());
111 
112     auto updateFontSettings = [this] {
113         m_outputWindow->setBaseFont(TextEditor::TextEditorSettings::fontSettings().font());
114     };
115 
116     auto updateZoomEnabled = [this] {
117         m_outputWindow->setWheelZoomEnabled(
118                     TextEditor::TextEditorSettings::behaviorSettings().m_scrollWheelZooming);
119     };
120 
121     updateFontSettings();
122     updateZoomEnabled();
123     setupFilterUi("CompileOutputPane.Filter");
124     setFilteringEnabled(true);
125 
126     connect(this, &IOutputPane::zoomInRequested, m_outputWindow, &Core::OutputWindow::zoomIn);
127     connect(this, &IOutputPane::zoomOutRequested, m_outputWindow, &Core::OutputWindow::zoomOut);
128     connect(this, &IOutputPane::resetZoomRequested, m_outputWindow, &Core::OutputWindow::resetZoom);
129     connect(TextEditor::TextEditorSettings::instance(), &TextEditor::TextEditorSettings::fontSettingsChanged,
130             this, updateFontSettings);
131     connect(TextEditor::TextEditorSettings::instance(), &TextEditor::TextEditorSettings::behaviorSettingsChanged,
132             this, updateZoomEnabled);
133 
134     connect(m_settingsButton, &QToolButton::clicked, this, [] {
135         Core::ICore::showOptionsDialog(OPTIONS_PAGE_ID);
136     });
137 
138     auto agg = new Aggregation::Aggregate;
139     agg->add(m_outputWindow);
140     agg->add(new Core::BaseTextFind(m_outputWindow));
141 
142     qRegisterMetaType<QTextCharFormat>("QTextCharFormat");
143 
144     m_handler = new ShowOutputTaskHandler(this);
145     ExtensionSystem::PluginManager::addObject(m_handler);
146     setupContext(C_COMPILE_OUTPUT, m_outputWindow);
147     loadSettings();
148     updateFromSettings();
149 }
150 
~CompileOutputWindow()151 CompileOutputWindow::~CompileOutputWindow()
152 {
153     ExtensionSystem::PluginManager::removeObject(m_handler);
154     delete m_handler;
155     delete m_cancelBuildButton;
156     delete m_settingsButton;
157 }
158 
updateFromSettings()159 void CompileOutputWindow::updateFromSettings()
160 {
161     m_outputWindow->setWordWrapEnabled(m_settings.wrapOutput);
162     m_outputWindow->setMaxCharCount(m_settings.maxCharCount);
163 }
164 
hasFocus() const165 bool CompileOutputWindow::hasFocus() const
166 {
167     return m_outputWindow->window()->focusWidget() == m_outputWindow;
168 }
169 
canFocus() const170 bool CompileOutputWindow::canFocus() const
171 {
172     return true;
173 }
174 
setFocus()175 void CompileOutputWindow::setFocus()
176 {
177     m_outputWindow->setFocus();
178 }
179 
outputWidget(QWidget *)180 QWidget *CompileOutputWindow::outputWidget(QWidget *)
181 {
182     return m_outputWindow;
183 }
184 
toolBarWidgets() const185 QList<QWidget *> CompileOutputWindow::toolBarWidgets() const
186 {
187     return QList<QWidget *>{m_cancelBuildButton, m_settingsButton} + IOutputPane::toolBarWidgets();
188 }
189 
appendText(const QString & text,BuildStep::OutputFormat format)190 void CompileOutputWindow::appendText(const QString &text, BuildStep::OutputFormat format)
191 {
192     Utils::OutputFormat fmt = Utils::NormalMessageFormat;
193     switch (format) {
194     case BuildStep::OutputFormat::Stdout:
195         fmt = Utils::StdOutFormat;
196         break;
197     case BuildStep::OutputFormat::Stderr:
198         fmt = Utils::StdErrFormat;
199         break;
200     case BuildStep::OutputFormat::NormalMessage:
201         fmt = Utils::NormalMessageFormat;
202         break;
203     case BuildStep::OutputFormat::ErrorMessage:
204         fmt = Utils::ErrorMessageFormat;
205         break;
206 
207     }
208 
209     m_outputWindow->appendMessage(text, fmt);
210 }
211 
clearContents()212 void CompileOutputWindow::clearContents()
213 {
214     m_outputWindow->clear();
215     m_taskPositions.clear();
216 }
217 
priorityInStatusBar() const218 int CompileOutputWindow::priorityInStatusBar() const
219 {
220     return 50;
221 }
222 
canNext() const223 bool CompileOutputWindow::canNext() const
224 {
225     return false;
226 }
227 
canPrevious() const228 bool CompileOutputWindow::canPrevious() const
229 {
230     return false;
231 }
232 
goToNext()233 void CompileOutputWindow::goToNext()
234 { }
235 
goToPrev()236 void CompileOutputWindow::goToPrev()
237 { }
238 
canNavigate() const239 bool CompileOutputWindow::canNavigate() const
240 {
241     return false;
242 }
243 
registerPositionOf(const Task & task,int linkedOutputLines,int skipLines,int offset)244 void CompileOutputWindow::registerPositionOf(const Task &task, int linkedOutputLines, int skipLines,
245                                              int offset)
246 {
247     if (linkedOutputLines <= 0)
248         return;
249 
250     const int blocknumber = m_outputWindow->document()->blockCount() - offset;
251     const int firstLine = blocknumber - linkedOutputLines - skipLines;
252     const int lastLine = firstLine + linkedOutputLines - 1;
253 
254     m_taskPositions.insert(task.taskId, qMakePair(firstLine, lastLine));
255 }
256 
knowsPositionOf(const Task & task)257 bool CompileOutputWindow::knowsPositionOf(const Task &task)
258 {
259     return (m_taskPositions.contains(task.taskId));
260 }
261 
showPositionOf(const Task & task)262 void CompileOutputWindow::showPositionOf(const Task &task)
263 {
264     QPair<int, int> position = m_taskPositions.value(task.taskId);
265     QTextCursor newCursor(m_outputWindow->document()->findBlockByNumber(position.second));
266 
267     // Move cursor to end of last line of interest:
268     newCursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor);
269     m_outputWindow->setTextCursor(newCursor);
270 
271     // Move cursor and select lines:
272     newCursor.setPosition(m_outputWindow->document()->findBlockByNumber(position.first).position(),
273                           QTextCursor::KeepAnchor);
274     m_outputWindow->setTextCursor(newCursor);
275 
276     // Center cursor now:
277     m_outputWindow->centerCursor();
278 }
279 
flush()280 void CompileOutputWindow::flush()
281 {
282     m_outputWindow->flush();
283 }
284 
reset()285 void CompileOutputWindow::reset()
286 {
287     m_outputWindow->reset();
288 }
289 
setSettings(const CompileOutputSettings & settings)290 void CompileOutputWindow::setSettings(const CompileOutputSettings &settings)
291 {
292     m_settings = settings;
293     storeSettings();
294     updateFromSettings();
295 }
296 
outputFormatter() const297 Utils::OutputFormatter *CompileOutputWindow::outputFormatter() const
298 {
299     return m_outputWindow->outputFormatter();
300 }
301 
updateFilter()302 void CompileOutputWindow::updateFilter()
303 {
304     m_outputWindow->updateFilterProperties(filterText(), filterCaseSensitivity(),
305                                            filterUsesRegexp(), filterIsInverted());
306 }
307 
308 const bool kPopUpDefault = false;
309 const bool kWrapOutputDefault = true;
310 
loadSettings()311 void CompileOutputWindow::loadSettings()
312 {
313     QSettings * const s = Core::ICore::settings();
314     m_settings.popUp = s->value(POP_UP_KEY, kPopUpDefault).toBool();
315     m_settings.wrapOutput = s->value(WRAP_OUTPUT_KEY, kWrapOutputDefault).toBool();
316     m_settings.maxCharCount = s->value(MAX_LINES_KEY,
317                                        Core::Constants::DEFAULT_MAX_CHAR_COUNT).toInt() * 100;
318 }
319 
storeSettings() const320 void CompileOutputWindow::storeSettings() const
321 {
322     Utils::QtcSettings *const s = Core::ICore::settings();
323     s->setValueWithDefault(POP_UP_KEY, m_settings.popUp, kPopUpDefault);
324     s->setValueWithDefault(WRAP_OUTPUT_KEY, m_settings.wrapOutput, kWrapOutputDefault);
325     s->setValueWithDefault(MAX_LINES_KEY,
326                            m_settings.maxCharCount / 100,
327                            Core::Constants::DEFAULT_MAX_CHAR_COUNT);
328 }
329 
330 class CompileOutputSettingsWidget : public Core::IOptionsPageWidget
331 {
332     Q_DECLARE_TR_FUNCTIONS(ProjectExplorer::Internal::CompileOutputSettingsPage)
333 public:
CompileOutputSettingsWidget()334     CompileOutputSettingsWidget()
335     {
336         const CompileOutputSettings &settings = BuildManager::compileOutputSettings();
337         m_wrapOutputCheckBox.setText(tr("Word-wrap output"));
338         m_wrapOutputCheckBox.setChecked(settings.wrapOutput);
339         m_popUpCheckBox.setText(tr("Open pane when building"));
340         m_popUpCheckBox.setChecked(settings.popUp);
341         m_maxCharsBox.setMaximum(100000000);
342         m_maxCharsBox.setValue(settings.maxCharCount);
343         const auto layout = new QVBoxLayout(this);
344         layout->addWidget(&m_wrapOutputCheckBox);
345         layout->addWidget(&m_popUpCheckBox);
346         const auto maxCharsLayout = new QHBoxLayout;
347         const QString msg = tr("Limit output to %1 characters");
348         const QStringList parts = msg.split("%1") << QString() << QString();
349         maxCharsLayout->addWidget(new QLabel(parts.at(0).trimmed()));
350         maxCharsLayout->addWidget(&m_maxCharsBox);
351         maxCharsLayout->addWidget(new QLabel(parts.at(1).trimmed()));
352         maxCharsLayout->addStretch(1);
353         layout->addLayout(maxCharsLayout);
354         layout->addStretch(1);
355     }
356 
apply()357     void apply() final
358     {
359         CompileOutputSettings s;
360         s.wrapOutput = m_wrapOutputCheckBox.isChecked();
361         s.popUp = m_popUpCheckBox.isChecked();
362         s.maxCharCount = m_maxCharsBox.value();
363         BuildManager::setCompileOutputSettings(s);
364     }
365 
366 private:
367     QCheckBox m_wrapOutputCheckBox;
368     QCheckBox m_popUpCheckBox;
369     QSpinBox m_maxCharsBox;
370 };
371 
CompileOutputSettingsPage()372 CompileOutputSettingsPage::CompileOutputSettingsPage()
373 {
374     setId(OPTIONS_PAGE_ID);
375     setDisplayName(CompileOutputSettingsWidget::tr("Compile Output"));
376     setCategory(Constants::BUILD_AND_RUN_SETTINGS_CATEGORY);
377     setWidgetCreator([] { return new CompileOutputSettingsWidget; });
378 }
379 
380 } // Internal
381 } // ProjectExplorer
382