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