1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "outputwindow.h"
35 #include "projectexplorerconstants.h"
36 #include "projectexplorer.h"
37 #include "projectexplorersettings.h"
38 #include "runconfiguration.h"
40 #include "outputformatter.h"
42 #include <coreplugin/actionmanager/actionmanager.h>
43 #include <coreplugin/actionmanager/actioncontainer.h>
44 #include <coreplugin/actionmanager/command.h>
45 #include <coreplugin/coreconstants.h>
46 #include <coreplugin/icore.h>
47 #include <coreplugin/uniqueidmanager.h>
48 #include <coreplugin/icontext.h>
49 #include <find/basetextfind.h>
50 #include <aggregation/aggregate.h>
51 #include <texteditor/basetexteditor.h>
52 #include <projectexplorer/project.h>
53 #include <qt4projectmanager/qt4projectmanagerconstants.h>
54 #include <utils/qtcassert.h>
56 #include <QtGui/QIcon>
57 #include <QtGui/QScrollBar>
58 #include <QtGui/QTextLayout>
59 #include <QtGui/QTextBlock>
60 #include <QtGui/QPainter>
61 #include <QtGui/QApplication>
62 #include <QtGui/QClipboard>
63 #include <QtGui/QMenu>
64 #include <QtGui/QMessageBox>
65 #include <QtGui/QVBoxLayout>
66 #include <QtGui/QTabWidget>
67 #include <QtGui/QToolButton>
68 #include <QtGui/QShowEvent>
70 #include <QtCore/QDebug>
72 static const int MaxBlockCount = 100000;
76 namespace ProjectExplorer {
79 OutputPane::RunControlTab::RunControlTab(RunControl *rc, OutputWindow *w) :
80 runControl(rc), window(w), asyncClosing(false)
84 OutputPane::OutputPane() :
85 m_mainWidget(new QWidget),
86 m_tabWidget(new QTabWidget),
87 m_stopAction(new QAction(QIcon(QLatin1String(Constants::ICON_STOP)), tr("Stop"), this)),
88 m_reRunButton(new QToolButton),
89 m_stopButton(new QToolButton)
91 m_runIcon.addFile(Constants::ICON_RUN);
92 m_runIcon.addFile(Constants::ICON_RUN_SMALL);
95 m_reRunButton->setIcon(m_runIcon);
96 m_reRunButton->setToolTip(tr("Re-run this run-configuration"));
97 m_reRunButton->setAutoRaise(true);
98 m_reRunButton->setEnabled(false);
99 connect(m_reRunButton, SIGNAL(clicked()),
100 this, SLOT(reRunRunControl()));
103 Core::ActionManager *am = Core::ICore::instance()->actionManager();
104 Core::Context globalcontext(Core::Constants::C_GLOBAL);
106 m_stopAction->setToolTip(tr("Stop"));
107 m_stopAction->setEnabled(false);
109 Core::Command *cmd = am->registerAction(m_stopAction, Constants::STOP, globalcontext);
111 m_stopButton->setDefaultAction(cmd->action());
112 m_stopButton->setAutoRaise(true);
114 connect(m_stopAction, SIGNAL(triggered()),
115 this, SLOT(stopRunControl()));
119 QVBoxLayout *layout = new QVBoxLayout;
120 layout->setMargin(0);
121 m_tabWidget->setDocumentMode(true);
122 m_tabWidget->setTabsClosable(true);
123 m_tabWidget->setMovable(true);
124 connect(m_tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int)));
125 layout->addWidget(m_tabWidget);
127 connect(m_tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
129 m_mainWidget->setLayout(layout);
131 connect(ProjectExplorer::ProjectExplorerPlugin::instance()->session(), SIGNAL(aboutToUnloadSession()),
132 this, SLOT(aboutToUnloadSession()));
135 OutputPane::~OutputPane()
138 qDebug() << "OutputPane::~OutputPane: Entries left" << m_runControlTabs.size();
140 foreach(const RunControlTab &rt, m_runControlTabs)
141 delete rt.runControl;
145 int OutputPane::currentIndex() const
147 if (const QWidget *w = m_tabWidget->currentWidget())
152 RunControl *OutputPane::currentRunControl() const
154 const int index = currentIndex();
156 return m_runControlTabs.at(index).runControl;
160 int OutputPane::indexOf(const RunControl *rc) const
162 for (int i = m_runControlTabs.size() - 1; i >= 0; i--)
163 if (m_runControlTabs.at(i).runControl == rc)
168 int OutputPane::indexOf(const QWidget *outputWindow) const
170 for (int i = m_runControlTabs.size() - 1; i >= 0; i--)
171 if (m_runControlTabs.at(i).window == outputWindow)
176 int OutputPane::tabWidgetIndexOf(int runControlIndex) const
178 if (runControlIndex >= 0 && runControlIndex < m_runControlTabs.size())
179 return m_tabWidget->indexOf(m_runControlTabs.at(runControlIndex).window);
183 bool OutputPane::aboutToClose() const
185 foreach(const RunControlTab &rt, m_runControlTabs)
186 if (rt.runControl->isRunning() && !rt.runControl->aboutToStop())
191 void OutputPane::aboutToUnloadSession()
196 QWidget *OutputPane::outputWidget(QWidget *)
201 QList<QWidget*> OutputPane::toolBarWidgets() const
203 return QList<QWidget*>() << m_reRunButton << m_stopButton;
206 QString OutputPane::displayName() const
208 return tr("Application Output");
211 int OutputPane::priorityInStatusBar() const
216 void OutputPane::clearContents()
218 OutputWindow *currentWindow = qobject_cast<OutputWindow *>(m_tabWidget->currentWidget());
220 currentWindow->clear();
223 void OutputPane::visibilityChanged(bool /* b */)
227 bool OutputPane::hasFocus()
229 return m_tabWidget->currentWidget() && m_tabWidget->currentWidget()->hasFocus();
232 bool OutputPane::canFocus()
234 return m_tabWidget->currentWidget();
237 void OutputPane::setFocus()
239 if (m_tabWidget->currentWidget())
240 m_tabWidget->currentWidget()->setFocus();
243 void OutputPane::createNewOutputWindow(RunControl *rc)
245 connect(rc, SIGNAL(started()),
246 this, SLOT(runControlStarted()));
247 connect(rc, SIGNAL(finished()),
248 this, SLOT(runControlFinished()));
249 connect(rc, SIGNAL(appendMessage(ProjectExplorer::RunControl*,QString,ProjectExplorer::OutputFormat)),
250 this, SLOT(appendMessage(ProjectExplorer::RunControl*,QString,ProjectExplorer::OutputFormat)));
252 // First look if we can reuse a tab
253 const int size = m_runControlTabs.size();
254 for (int i = 0; i < size; i++) {
255 RunControlTab &tab =m_runControlTabs[i];
256 if (tab.runControl->sameRunConfiguration(rc) && !tab.runControl->isRunning()) {
258 delete tab.runControl;
260 tab.window->handleOldOutput();
261 tab.window->scrollToBottom();
262 tab.window->setFormatter(rc->outputFormatter());
264 qDebug() << "OutputPane::createNewOutputWindow: Reusing tab" << i << " for " << rc;
269 OutputWindow *ow = new OutputWindow(m_tabWidget);
270 ow->setWindowTitle(tr("Application Output Window"));
271 ow->setWindowIcon(QIcon(QLatin1String(Qt4ProjectManager::Constants::ICON_WINDOW)));
272 ow->setFormatter(rc->outputFormatter());
273 Aggregation::Aggregate *agg = new Aggregation::Aggregate;
275 agg->add(new Find::BaseTextFind(ow));
276 m_runControlTabs.push_back(RunControlTab(rc, ow));
277 m_tabWidget->addTab(ow, rc->displayName());
279 qDebug() << "OutputPane::createNewOutputWindow: Adding tab for " << rc;
282 void OutputPane::appendMessage(RunControl *rc, const QString &out, OutputFormat format)
284 const int index = indexOf(rc);
286 m_runControlTabs.at(index).window->appendMessage(out, format);
289 void OutputPane::showTabFor(RunControl *rc)
291 m_tabWidget->setCurrentIndex(tabWidgetIndexOf(indexOf(rc)));
294 void OutputPane::reRunRunControl()
296 const int index = currentIndex();
297 QTC_ASSERT(index != -1 && !m_runControlTabs.at(index).runControl->isRunning(), return;)
299 RunControlTab &tab = m_runControlTabs[index];
301 tab.window->handleOldOutput();
302 tab.window->scrollToBottom();
303 tab.runControl->start();
306 void OutputPane::stopRunControl()
308 const int index = currentIndex();
309 QTC_ASSERT(index != -1 && m_runControlTabs.at(index).runControl->isRunning(), return;)
311 RunControl *rc = m_runControlTabs.at(index).runControl;
312 if (rc->isRunning() && rc->aboutToStop())
316 qDebug() << "OutputPane::stopRunControl " << rc;
319 bool OutputPane::closeTabs(bool prompt)
321 bool allClosed = true;
322 for (int t = m_tabWidget->count() - 1; t >= 0; t--)
323 if (!closeTab(t, prompt))
326 qDebug() << "OutputPane::closeTabs() returns " << allClosed;
330 bool OutputPane::closeTab(int index)
332 return closeTab(index, true);
335 bool OutputPane::closeTab(int tabIndex, bool prompt)
337 const int index = indexOf(m_tabWidget->widget(tabIndex));
338 QTC_ASSERT(index != -1, return true;)
340 RunControlTab &tab = m_runControlTabs[index];
343 qDebug() << "OutputPane::closeTab tab " << tabIndex << tab.runControl
344 << tab.window << tab.asyncClosing;
345 // Prompt user to stop
346 if (tab.runControl->isRunning()) {
347 if (prompt && !tab.runControl->aboutToStop())
349 if (tab.runControl->stop() == RunControl::AsynchronousStop) {
350 tab.asyncClosing = true;
355 m_tabWidget->removeTab(tabIndex);
356 if (tab.asyncClosing) { // We were invoked from its finished() signal.
357 tab.runControl->deleteLater();
359 delete tab.runControl;
362 m_runControlTabs.removeAt(index);
366 void OutputPane::projectRemoved()
368 tabChanged(m_tabWidget->currentIndex());
371 void OutputPane::tabChanged(int i)
374 m_stopAction->setEnabled(false);
375 m_reRunButton->setEnabled(false);
377 const int index = indexOf(m_tabWidget->widget(i));
378 QTC_ASSERT(index != -1, return; )
380 RunControl *rc = m_runControlTabs.at(index).runControl;
381 m_stopAction->setEnabled(rc->isRunning());
382 m_reRunButton->setEnabled(!rc->isRunning());
383 m_reRunButton->setIcon(m_runIcon);
387 void OutputPane::runControlStarted()
389 RunControl *current = currentRunControl();
390 if (current && current == sender()) {
391 m_reRunButton->setEnabled(false);
392 m_stopAction->setEnabled(true);
393 m_reRunButton->setIcon(m_runIcon);
397 void OutputPane::runControlFinished()
399 RunControl *senderRunControl = qobject_cast<RunControl *>(sender());
400 const int senderIndex = indexOf(senderRunControl);
402 QTC_ASSERT(senderIndex != -1, return; )
404 // Enable buttons for current
405 RunControl *current = currentRunControl();
408 qDebug() << "OutputPane::runControlFinished" << senderRunControl << senderIndex
409 << " current " << current << m_runControlTabs.size();
411 if (current && current == sender()) {
412 m_reRunButton->setEnabled(true);
413 m_stopAction->setEnabled(false);
414 m_reRunButton->setIcon(m_runIcon);
416 // Check for asynchronous close. Close the tab.
417 if (m_runControlTabs.at(senderIndex).asyncClosing)
418 closeTab(tabWidgetIndexOf(senderIndex), false);
421 emit allRunControlsFinished();
424 bool OutputPane::isRunning() const
426 foreach(const RunControlTab &rt, m_runControlTabs)
427 if (rt.runControl->isRunning())
432 bool OutputPane::canNext()
437 bool OutputPane::canPrevious()
442 void OutputPane::goToNext()
447 void OutputPane::goToPrev()
452 bool OutputPane::canNavigate()
457 /*******************/
459 OutputWindow::OutputWindow(QWidget *parent)
460 : QPlainTextEdit(parent)
462 , m_enforceNewline(false)
463 , m_scrollToBottom(false)
464 , m_linksActive(true)
465 , m_mousePressed(false)
467 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
468 //setCenterOnScroll(false);
469 setFrameShape(QFrame::NoFrame);
470 setMouseTracking(true);
471 if (!ProjectExplorerPlugin::instance()->projectExplorerSettings().wrapAppOutput)
472 setWordWrapMode(QTextOption::NoWrap);
474 static uint usedIds = 0;
475 Core::ICore *core = Core::ICore::instance();
476 Core::Context context(Constants::C_APP_OUTPUT, usedIds++);
477 m_outputWindowContext = new Core::BaseContext(this, context);
478 core->addContextObject(m_outputWindowContext);
480 QAction *undoAction = new QAction(this);
481 QAction *redoAction = new QAction(this);
482 QAction *cutAction = new QAction(this);
483 QAction *copyAction = new QAction(this);
484 QAction *pasteAction = new QAction(this);
485 QAction *selectAllAction = new QAction(this);
487 Core::ActionManager *am = core->actionManager();
488 am->registerAction(undoAction, Core::Constants::UNDO, context);
489 am->registerAction(redoAction, Core::Constants::REDO, context);
490 am->registerAction(cutAction, Core::Constants::CUT, context);
491 am->registerAction(copyAction, Core::Constants::COPY, context);
492 am->registerAction(pasteAction, Core::Constants::PASTE, context);
493 am->registerAction(selectAllAction, Core::Constants::SELECTALL, context);
495 connect(undoAction, SIGNAL(triggered()), this, SLOT(undo()));
496 connect(redoAction, SIGNAL(triggered()), this, SLOT(redo()));
497 connect(cutAction, SIGNAL(triggered()), this, SLOT(cut()));
498 connect(copyAction, SIGNAL(triggered()), this, SLOT(copy()));
499 connect(pasteAction, SIGNAL(triggered()), this, SLOT(paste()));
500 connect(selectAllAction, SIGNAL(triggered()), this, SLOT(selectAll()));
502 connect(this, SIGNAL(undoAvailable(bool)), undoAction, SLOT(setEnabled(bool)));
503 connect(this, SIGNAL(redoAvailable(bool)), redoAction, SLOT(setEnabled(bool)));
504 connect(this, SIGNAL(copyAvailable(bool)), cutAction, SLOT(setEnabled(bool))); // OutputWindow never read-only
505 connect(this, SIGNAL(copyAvailable(bool)), copyAction, SLOT(setEnabled(bool)));
507 undoAction->setEnabled(false);
508 redoAction->setEnabled(false);
509 cutAction->setEnabled(false);
510 copyAction->setEnabled(false);
512 connect(ProjectExplorerPlugin::instance(), SIGNAL(settingsChanged()),
513 this, SLOT(updateWordWrapMode()));
516 OutputWindow::~OutputWindow()
518 Core::ICore::instance()->removeContextObject(m_outputWindowContext);
519 delete m_outputWindowContext;
522 void OutputWindow::mousePressEvent(QMouseEvent * e)
524 m_mousePressed = true;
525 QPlainTextEdit::mousePressEvent(e);
528 void OutputWindow::mouseReleaseEvent(QMouseEvent *e)
530 m_mousePressed = false;
533 const QString href = anchorAt(e->pos());
535 m_formatter->handleLink(href);
538 // Mouse was released, activate links again
539 m_linksActive = true;
541 QPlainTextEdit::mouseReleaseEvent(e);
544 void OutputWindow::mouseMoveEvent(QMouseEvent *e)
546 // Cursor was dragged to make a selection, deactivate links
547 if (m_mousePressed && textCursor().hasSelection())
548 m_linksActive = false;
550 if (!m_linksActive || anchorAt(e->pos()).isEmpty())
551 viewport()->setCursor(Qt::IBeamCursor);
553 viewport()->setCursor(Qt::PointingHandCursor);
554 QPlainTextEdit::mouseMoveEvent(e);
557 void OutputWindow::resizeEvent(QResizeEvent *e)
559 //Keep scrollbar at bottom of window while resizing, to ensure we keep scrolling
560 //This can happen if window is resized while building, or if the horizontal scrollbar appears
561 bool atBottom = isScrollbarAtBottom();
562 QPlainTextEdit::resizeEvent(e);
567 void OutputWindow::keyPressEvent(QKeyEvent *ev)
569 QPlainTextEdit::keyPressEvent(ev);
571 //Ensure we scroll also on Ctrl+Home or Ctrl+End
572 if (ev->matches(QKeySequence::MoveToStartOfDocument))
573 verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMinimum);
574 else if (ev->matches(QKeySequence::MoveToEndOfDocument))
575 verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMaximum);
578 OutputFormatter *OutputWindow::formatter() const
583 void OutputWindow::setFormatter(OutputFormatter *formatter)
585 m_formatter = formatter;
586 m_formatter->setPlainTextEdit(this);
589 void OutputWindow::showEvent(QShowEvent *e)
591 QPlainTextEdit::showEvent(e);
592 if (m_scrollToBottom) {
593 verticalScrollBar()->setValue(verticalScrollBar()->maximum());
595 m_scrollToBottom = false;
598 QString OutputWindow::doNewlineEnfocement(const QString &out)
600 m_scrollToBottom = true;
602 if (m_enforceNewline)
603 s.prepend(QLatin1Char('\n'));
605 m_enforceNewline = true; // make appendOutputInline put in a newline next time
607 if (s.endsWith(QLatin1Char('\n')))
613 void OutputWindow::appendMessage(const QString &output, OutputFormat format)
615 QString out = output;
616 out.remove(QLatin1Char('\r'));
617 setMaximumBlockCount(MaxBlockCount);
618 const bool atBottom = isScrollbarAtBottom();
620 if (format == ErrorMessageFormat || format == NormalMessageFormat) {
622 m_formatter->appendMessage(doNewlineEnfocement(out), format);
626 bool sameLine = format == StdOutFormatSameLine
627 || format == StdErrFormatSameLine;
630 m_scrollToBottom = true;
633 bool enforceNewline = m_enforceNewline;
634 m_enforceNewline = false;
636 if (!enforceNewline) {
637 newline = out.indexOf(QLatin1Char('\n'));
638 moveCursor(QTextCursor::End);
640 m_formatter->appendMessage(out.left(newline), format);// doesn't enforce new paragraph like appendPlainText
643 QString s = out.mid(newline+1);
645 m_enforceNewline = true;
647 if (s.endsWith(QLatin1Char('\n'))) {
648 m_enforceNewline = true;
651 m_formatter->appendMessage(QLatin1Char('\n') + s, format);
654 m_formatter->appendMessage(doNewlineEnfocement(out), format);
664 void OutputWindow::appendText(const QString &textIn, const QTextCharFormat &format, int maxLineCount)
666 QString text = textIn;
667 text.remove(QLatin1Char('\r'));
668 if (document()->blockCount() > maxLineCount)
670 const bool atBottom = isScrollbarAtBottom();
671 QTextCursor cursor = QTextCursor(document());
672 cursor.movePosition(QTextCursor::End);
673 cursor.beginEditBlock();
674 cursor.insertText(doNewlineEnfocement(text), format);
676 if (document()->blockCount() > maxLineCount) {
678 tmp.setFontWeight(QFont::Bold);
679 cursor.insertText(tr("Additional output omitted\n"), tmp);
682 cursor.endEditBlock();
687 bool OutputWindow::isScrollbarAtBottom() const
689 return verticalScrollBar()->value() == verticalScrollBar()->maximum();
692 void OutputWindow::clear()
694 m_enforceNewline = false;
695 QPlainTextEdit::clear();
698 void OutputWindow::handleOldOutput()
700 if (ProjectExplorerPlugin::instance()->projectExplorerSettings().cleanOldAppOutput)
706 void OutputWindow::scrollToBottom()
708 verticalScrollBar()->setValue(verticalScrollBar()->maximum());
711 void OutputWindow::grayOutOldContent()
713 QTextCursor cursor = textCursor();
714 cursor.movePosition(QTextCursor::End);
715 QTextCharFormat endFormat = cursor.charFormat();
717 cursor.select(QTextCursor::Document);
719 QTextCharFormat format;
720 const QColor bkgColor = palette().base().color();
721 const QColor fgdColor = palette().text().color();
722 double bkgFactor = 0.50;
723 double fgdFactor = 1.-bkgFactor;
724 format.setForeground(QColor((bkgFactor * bkgColor.red() + fgdFactor * fgdColor.red()),
725 (bkgFactor * bkgColor.green() + fgdFactor * fgdColor.green()),
726 (bkgFactor * bkgColor.blue() + fgdFactor * fgdColor.blue()) ));
727 cursor.mergeCharFormat(format);
729 cursor.movePosition(QTextCursor::End);
730 cursor.setCharFormat(endFormat);
731 cursor.insertBlock(QTextBlockFormat());
734 void OutputWindow::enableUndoRedo()
736 setMaximumBlockCount(0);
737 setUndoRedoEnabled(true);
740 void OutputWindow::updateWordWrapMode()
742 if (ProjectExplorerPlugin::instance()->projectExplorerSettings().wrapAppOutput)
743 setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
745 setWordWrapMode(QTextOption::NoWrap);
748 } // namespace Internal
749 } // namespace ProjectExplorer