File indexing completed on 2024-05-12 17:09:15
0001 /* 0002 SPDX-FileCopyrightText: 2009 Aaron Seigo <aseigo@kde.org> 0003 SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "interactiveconsole.h" 0009 0010 #include <QAction> 0011 #include <QActionGroup> 0012 #include <QApplication> 0013 #include <QDBusConnection> 0014 #include <QDBusMessage> 0015 #include <QDateTime> 0016 #include <QElapsedTimer> 0017 #include <QFile> 0018 #include <QFileDialog> 0019 #include <QHBoxLayout> 0020 #include <QIcon> 0021 #include <QLabel> 0022 #include <QMenu> 0023 #include <QSplitter> 0024 #include <QStandardPaths> 0025 #include <QTextBrowser> 0026 #include <QToolButton> 0027 #include <QVBoxLayout> 0028 0029 #include <KConfigGroup> 0030 #include <KMessageBox> 0031 #include <KPluginFactory> 0032 #include <KSharedConfig> 0033 #include <KShell> 0034 #include <KStandardAction> 0035 #include <KTextEdit> 0036 #include <KTextEditor/ConfigInterface> 0037 #include <KTextEditor/Document> 0038 #include <KTextEditor/View> 0039 #include <KToolBar> 0040 #include <KX11Extras> 0041 #include <klocalizedstring.h> 0042 0043 #include <KPackage/Package> 0044 #include <KPackage/PackageLoader> 0045 0046 // TODO: 0047 // interactive help? 0048 static const QString s_autosaveFileName(QStringLiteral("interactiveconsoleautosave.js")); 0049 static const QString s_kwinService = QStringLiteral("org.kde.KWin"); 0050 static const QString s_plasmaShellService = QStringLiteral("org.kde.plasmashell"); 0051 0052 InteractiveConsole::InteractiveConsole(ConsoleMode mode, QWidget *parent) 0053 : QDialog(parent) 0054 , m_splitter(new QSplitter(Qt::Vertical, this)) 0055 , m_editorPart(nullptr) 0056 , m_editor(nullptr) 0057 , m_output(nullptr) 0058 , m_loadAction(KStandardAction::open(this, SLOT(openScriptFile()), this)) 0059 , m_saveAction(KStandardAction::saveAs(this, SLOT(saveScript()), this)) 0060 , m_clearAction(KStandardAction::clear(this, SLOT(clearEditor()), this)) 0061 , m_executeAction(new QAction(QIcon::fromTheme(QStringLiteral("system-run")), i18n("&Execute"), this)) 0062 , m_plasmaAction(new QAction(QIcon::fromTheme(QStringLiteral("plasma")), i18nc("Toolbar Button to switch to Plasma Scripting Mode", "Plasma"), this)) 0063 , m_kwinAction(new QAction(QIcon::fromTheme(QStringLiteral("kwin")), i18nc("Toolbar Button to switch to KWin Scripting Mode", "KWin"), this)) 0064 , m_snippetsMenu(new QMenu(i18n("Templates"), this)) 0065 , m_fileDialog(nullptr) 0066 , m_closeWhenCompleted(false) 0067 , m_mode(mode) 0068 { 0069 addAction(KStandardAction::close(this, SLOT(close()), this)); 0070 addAction(m_saveAction); 0071 addAction(m_clearAction); 0072 0073 setWindowTitle(i18n("Desktop Shell Scripting Console")); 0074 setAttribute(Qt::WA_DeleteOnClose); 0075 // setButtons(QDialog::None); 0076 0077 QWidget *widget = new QWidget(m_splitter); 0078 QVBoxLayout *editorLayout = new QVBoxLayout(widget); 0079 0080 QLabel *label = new QLabel(i18n("Editor"), widget); 0081 QFont f = label->font(); 0082 f.setBold(true); 0083 label->setFont(f); 0084 editorLayout->addWidget(label); 0085 0086 connect(m_snippetsMenu, &QMenu::aboutToShow, this, &InteractiveConsole::populateTemplatesMenu); 0087 0088 QToolButton *loadTemplateButton = new QToolButton(this); 0089 loadTemplateButton->setPopupMode(QToolButton::InstantPopup); 0090 loadTemplateButton->setMenu(m_snippetsMenu); 0091 loadTemplateButton->setText(i18n("Load")); 0092 connect(loadTemplateButton, &QToolButton::triggered, this, &InteractiveConsole::loadTemplate); 0093 0094 QToolButton *useTemplateButton = new QToolButton(this); 0095 useTemplateButton->setPopupMode(QToolButton::InstantPopup); 0096 useTemplateButton->setMenu(m_snippetsMenu); 0097 useTemplateButton->setText(i18n("Use")); 0098 connect(useTemplateButton, &QToolButton::triggered, this, &InteractiveConsole::useTemplate); 0099 0100 QActionGroup *modeGroup = new QActionGroup(this); 0101 modeGroup->addAction(m_plasmaAction); 0102 modeGroup->addAction(m_kwinAction); 0103 m_plasmaAction->setCheckable(true); 0104 m_kwinAction->setCheckable(true); 0105 m_kwinAction->setChecked(mode == KWinConsole); 0106 m_plasmaAction->setChecked(mode == PlasmaConsole); 0107 connect(modeGroup, &QActionGroup::triggered, this, &InteractiveConsole::modeSelectionChanged); 0108 0109 KToolBar *toolBar = new KToolBar(this, true, false); 0110 toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 0111 toolBar->addAction(m_loadAction); 0112 toolBar->addAction(m_saveAction); 0113 toolBar->addAction(m_clearAction); 0114 toolBar->addAction(m_executeAction); 0115 toolBar->addAction(m_plasmaAction); 0116 toolBar->addAction(m_kwinAction); 0117 toolBar->addWidget(loadTemplateButton); 0118 toolBar->addWidget(useTemplateButton); 0119 0120 editorLayout->addWidget(toolBar); 0121 0122 auto tryLoadingKatePart = [=]() -> KTextEditor::Document * { 0123 const auto loadResult = 0124 KPluginFactory::instantiatePlugin<KTextEditor::Document>(KPluginMetaData(QStringLiteral("kf" QT_STRINGIFY(QT_VERSION_MAJOR) "/parts/katepart")), 0125 this); 0126 if (!loadResult) { 0127 qWarning() << "Error loading katepart plugin:" << loadResult.errorString; 0128 return nullptr; 0129 } 0130 0131 KTextEditor::Document *result = loadResult.plugin; 0132 0133 result->setHighlightingMode(QStringLiteral("JavaScript/PlasmaDesktop")); 0134 0135 KTextEditor::View *view = result->createView(widget); 0136 view->setContextMenu(view->defaultContextMenu()); 0137 0138 KTextEditor::ConfigInterface *config = qobject_cast<KTextEditor::ConfigInterface *>(view); 0139 if (config) { 0140 config->setConfigValue(QStringLiteral("line-numbers"), true); 0141 config->setConfigValue(QStringLiteral("dynamic-word-wrap"), true); 0142 } 0143 0144 editorLayout->addWidget(view); 0145 connect(result, &KTextEditor::Document::textChanged, this, &InteractiveConsole::scriptTextChanged); 0146 0147 return result; 0148 }; 0149 0150 m_editorPart = tryLoadingKatePart(); 0151 0152 if (!m_editorPart) { 0153 m_editor = new KTextEdit(widget); 0154 editorLayout->addWidget(m_editor); 0155 connect(m_editor, &QTextEdit::textChanged, this, &InteractiveConsole::scriptTextChanged); 0156 } 0157 0158 m_splitter->addWidget(widget); 0159 0160 widget = new QWidget(m_splitter); 0161 QVBoxLayout *outputLayout = new QVBoxLayout(widget); 0162 0163 label = new QLabel(i18n("Output"), widget); 0164 f = label->font(); 0165 f.setBold(true); 0166 label->setFont(f); 0167 outputLayout->addWidget(label); 0168 0169 KToolBar *outputToolBar = new KToolBar(widget, true, false); 0170 outputToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 0171 QAction *clearOutputAction = KStandardAction::clear(this, SLOT(clearOutput()), this); 0172 outputToolBar->addAction(clearOutputAction); 0173 outputLayout->addWidget(outputToolBar); 0174 0175 m_output = new QTextBrowser(widget); 0176 outputLayout->addWidget(m_output); 0177 m_splitter->addWidget(widget); 0178 0179 QVBoxLayout *l = new QVBoxLayout(this); 0180 l->addWidget(m_splitter); 0181 0182 KConfigGroup cg(KSharedConfig::openConfig(), "InteractiveConsole"); 0183 restoreGeometry(cg.readEntry<QByteArray>("Geometry", QByteArray())); 0184 0185 m_splitter->setStretchFactor(0, 10); 0186 m_splitter->restoreState(cg.readEntry("SplitterState", QByteArray())); 0187 0188 scriptTextChanged(); 0189 0190 connect(m_executeAction, &QAction::triggered, this, &InteractiveConsole::evaluateScript); 0191 m_executeAction->setShortcut(Qt::CTRL | Qt::Key_E); 0192 0193 const QString autosave = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/" + s_autosaveFileName; 0194 if (QFile::exists(autosave)) { 0195 loadScript(autosave); 0196 } 0197 } 0198 0199 InteractiveConsole::~InteractiveConsole() 0200 { 0201 KConfigGroup cg(KSharedConfig::openConfig(), "InteractiveConsole"); 0202 cg.writeEntry("Geometry", saveGeometry()); 0203 cg.writeEntry("SplitterState", m_splitter->saveState()); 0204 } 0205 0206 void InteractiveConsole::setMode(const QString &mode) 0207 { 0208 if (mode.toLower() == QLatin1String("desktop")) { 0209 m_plasmaAction->trigger(); 0210 } else if (mode.toLower() == QLatin1String("windowmanager")) { 0211 m_kwinAction->trigger(); 0212 } 0213 } 0214 0215 void InteractiveConsole::modeSelectionChanged() 0216 { 0217 if (m_plasmaAction->isChecked()) { 0218 m_mode = PlasmaConsole; 0219 } else if (m_kwinAction->isChecked()) { 0220 m_mode = KWinConsole; 0221 } 0222 0223 Q_EMIT modeChanged(); 0224 } 0225 0226 QString InteractiveConsole::mode() const 0227 { 0228 if (m_mode == KWinConsole) { 0229 return QStringLiteral("windowmanager"); 0230 } 0231 0232 return QStringLiteral("desktop"); 0233 } 0234 0235 void InteractiveConsole::setScriptInterface(QObject *obj) 0236 { 0237 if (m_scriptEngine != obj) { 0238 if (m_scriptEngine) { 0239 disconnect(m_scriptEngine, nullptr, this, nullptr); 0240 } 0241 0242 m_scriptEngine = obj; 0243 connect(m_scriptEngine, SIGNAL(print(QString)), this, SLOT(print(QString))); 0244 connect(m_scriptEngine, SIGNAL(printError(QString)), this, SLOT(print(QString))); 0245 Q_EMIT scriptEngineChanged(); 0246 } 0247 } 0248 0249 QObject *InteractiveConsole::scriptEngine() const 0250 { 0251 return m_scriptEngine; 0252 } 0253 0254 void InteractiveConsole::loadScript(const QString &script) 0255 { 0256 if (m_editorPart) { 0257 m_editorPart->closeUrl(false); 0258 if (m_editorPart->openUrl(QUrl::fromLocalFile(script))) { 0259 m_editorPart->setHighlightingMode(QStringLiteral("JavaScript/PlasmaDesktop")); 0260 return; 0261 } 0262 } else { 0263 QFile file(KShell::tildeExpand(script)); 0264 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { 0265 m_editor->setText(file.readAll()); 0266 return; 0267 } 0268 } 0269 0270 m_output->append(i18n("Unable to load script file <b>%1</b>", script)); 0271 } 0272 0273 void InteractiveConsole::showEvent(QShowEvent *) 0274 { 0275 if (m_editorPart) { 0276 m_editorPart->views().constFirst()->setFocus(); 0277 } else { 0278 m_editor->setFocus(); 0279 } 0280 0281 KX11Extras::setOnDesktop(winId(), KX11Extras::currentDesktop()); 0282 Q_EMIT visibleChanged(true); 0283 } 0284 0285 void InteractiveConsole::closeEvent(QCloseEvent *event) 0286 { 0287 // need to save first! 0288 const QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/" + s_autosaveFileName; 0289 m_closeWhenCompleted = true; 0290 saveScript(QUrl::fromLocalFile(path)); 0291 QDialog::closeEvent(event); 0292 Q_EMIT visibleChanged(false); 0293 } 0294 0295 void InteractiveConsole::reject() 0296 { 0297 QDialog::reject(); 0298 close(); 0299 } 0300 0301 void InteractiveConsole::print(const QString &string) 0302 { 0303 m_output->append(string); 0304 } 0305 0306 void InteractiveConsole::scriptTextChanged() 0307 { 0308 const bool enable = m_editorPart ? !m_editorPart->isEmpty() : !m_editor->document()->isEmpty(); 0309 m_saveAction->setEnabled(enable); 0310 m_clearAction->setEnabled(enable); 0311 m_executeAction->setEnabled(enable); 0312 } 0313 0314 void InteractiveConsole::openScriptFile() 0315 { 0316 delete m_fileDialog; 0317 0318 m_fileDialog = new QFileDialog(); 0319 m_fileDialog->setAcceptMode(QFileDialog::AcceptOpen); 0320 m_fileDialog->setWindowTitle(i18n("Open Script File")); 0321 0322 QStringList mimetypes; 0323 mimetypes << QStringLiteral("application/javascript"); 0324 m_fileDialog->setMimeTypeFilters(mimetypes); 0325 0326 connect(m_fileDialog, &QDialog::finished, this, &InteractiveConsole::openScriptUrlSelected); 0327 m_fileDialog->show(); 0328 } 0329 0330 void InteractiveConsole::openScriptUrlSelected(int result) 0331 { 0332 if (!m_fileDialog) { 0333 return; 0334 } 0335 0336 if (result == QDialog::Accepted) { 0337 const QUrl url = m_fileDialog->selectedUrls().constFirst(); 0338 if (!url.isEmpty()) { 0339 loadScriptFromUrl(url); 0340 } 0341 } 0342 0343 m_fileDialog->deleteLater(); 0344 m_fileDialog = nullptr; 0345 } 0346 0347 void InteractiveConsole::loadScriptFromUrl(const QUrl &url) 0348 { 0349 if (m_editorPart) { 0350 m_editorPart->closeUrl(false); 0351 m_editorPart->openUrl(url); 0352 m_editorPart->setHighlightingMode(QStringLiteral("JavaScript/PlasmaDesktop")); 0353 } else { 0354 m_editor->clear(); 0355 m_editor->setEnabled(false); 0356 0357 if (m_job) { 0358 m_job.data()->kill(); 0359 } 0360 0361 auto job = KIO::get(url, KIO::Reload, KIO::HideProgressInfo); 0362 connect(job, &KIO::TransferJob::data, this, &InteractiveConsole::scriptFileDataRecvd); 0363 connect(job, &KJob::result, this, &InteractiveConsole::reenableEditor); 0364 m_job = job; 0365 } 0366 } 0367 0368 void InteractiveConsole::populateTemplatesMenu() 0369 { 0370 m_snippetsMenu->clear(); 0371 auto templates = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/LayoutTemplate"), QString(), [](const KPluginMetaData &metaData) { 0372 return metaData.value(QStringLiteral("X-Plasma-Shell")) == qApp->applicationName(); 0373 }); 0374 std::sort(templates.begin(), templates.end(), [](const KPluginMetaData &left, const KPluginMetaData &right) { 0375 return left.name() < right.name(); 0376 }); 0377 KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LayoutTemplate")); 0378 for (const auto &templateMetaData : qAsConst(templates)) { 0379 package.setPath(templateMetaData.pluginId()); 0380 const QString scriptFile = package.filePath("mainscript"); 0381 if (!scriptFile.isEmpty()) { 0382 QAction *action = m_snippetsMenu->addAction(templateMetaData.name()); 0383 action->setData(templateMetaData.pluginId()); 0384 } 0385 } 0386 } 0387 0388 void InteractiveConsole::loadTemplate(QAction *action) 0389 { 0390 KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LayoutTemplate"), action->data().toString()); 0391 const QString scriptFile = package.filePath("mainscript"); 0392 if (!scriptFile.isEmpty()) { 0393 loadScriptFromUrl(QUrl::fromLocalFile(scriptFile)); 0394 } 0395 } 0396 0397 void InteractiveConsole::useTemplate(QAction *action) 0398 { 0399 QString code("var template = loadTemplate('" + action->data().toString() + "')"); 0400 if (m_editorPart) { 0401 const QList<KTextEditor::View *> views = m_editorPart->views(); 0402 if (views.isEmpty()) { 0403 m_editorPart->insertLines(m_editorPart->lines(), QStringList() << code); 0404 } else { 0405 KTextEditor::Cursor cursor = views.at(0)->cursorPosition(); 0406 m_editorPart->insertLines(cursor.line(), QStringList() << code); 0407 cursor.setLine(cursor.line() + 1); 0408 views.at(0)->setCursorPosition(cursor); 0409 } 0410 } else { 0411 m_editor->insertPlainText(code); 0412 } 0413 } 0414 0415 void InteractiveConsole::scriptFileDataRecvd(KIO::Job *job, const QByteArray &data) 0416 { 0417 Q_ASSERT(m_editor); 0418 0419 if (job == m_job.data()) { 0420 m_editor->insertPlainText(data); 0421 } 0422 } 0423 0424 void InteractiveConsole::saveScript() 0425 { 0426 if (m_editorPart) { 0427 m_editorPart->documentSaveAs(); 0428 return; 0429 } 0430 0431 delete m_fileDialog; 0432 0433 m_fileDialog = new QFileDialog(); 0434 m_fileDialog->setAcceptMode(QFileDialog::AcceptSave); 0435 m_fileDialog->setWindowTitle(i18n("Save Script File")); 0436 0437 QStringList mimetypes; 0438 mimetypes << QStringLiteral("application/javascript") << QStringLiteral("text/javascript"); 0439 m_fileDialog->setMimeTypeFilters(mimetypes); 0440 0441 connect(m_fileDialog, &QDialog::finished, this, &InteractiveConsole::saveScriptUrlSelected); 0442 m_fileDialog->show(); 0443 } 0444 0445 void InteractiveConsole::saveScriptUrlSelected(int result) 0446 { 0447 if (!m_fileDialog) { 0448 return; 0449 } 0450 0451 if (result == QDialog::Accepted) { 0452 const QUrl url = m_fileDialog->selectedUrls().constFirst(); 0453 if (!url.isEmpty()) { 0454 saveScript(url); 0455 } 0456 } 0457 0458 m_fileDialog->deleteLater(); 0459 m_fileDialog = nullptr; 0460 } 0461 0462 void InteractiveConsole::saveScript(const QUrl &url) 0463 { 0464 // create the folder to save if doesn't exists 0465 QFileInfo info(url.path()); 0466 QDir dir; 0467 dir.mkpath(info.absoluteDir().absolutePath()); 0468 0469 if (m_editorPart) { 0470 m_editorPart->saveAs(url); 0471 } else { 0472 m_editor->setEnabled(false); 0473 0474 if (m_job) { 0475 m_job.data()->kill(); 0476 } 0477 0478 auto job = KIO::put(url, -1, KIO::HideProgressInfo); 0479 connect(job, &KIO::TransferJob::dataReq, this, &InteractiveConsole::scriptFileDataReq); 0480 connect(job, &KJob::result, this, &InteractiveConsole::reenableEditor); 0481 m_job = job; 0482 } 0483 } 0484 0485 void InteractiveConsole::scriptFileDataReq(KIO::Job *job, QByteArray &data) 0486 { 0487 Q_ASSERT(m_editor); 0488 0489 if (!m_job || m_job.data() != job) { 0490 return; 0491 } 0492 0493 data.append(m_editor->toPlainText().toLocal8Bit()); 0494 m_job.clear(); 0495 } 0496 0497 void InteractiveConsole::reenableEditor(KJob *job) 0498 { 0499 Q_ASSERT(m_editor); 0500 if (m_closeWhenCompleted && job->error() != 0) { 0501 close(); 0502 } 0503 0504 m_closeWhenCompleted = false; 0505 m_editor->setEnabled(true); 0506 } 0507 0508 void InteractiveConsole::evaluateScript() 0509 { 0510 // qDebug() << "evaluating" << m_editor->toPlainText(); 0511 m_output->moveCursor(QTextCursor::End); 0512 QTextCursor cursor = m_output->textCursor(); 0513 m_output->setTextCursor(cursor); 0514 0515 QTextCharFormat format; 0516 format.setFontWeight(QFont::Bold); 0517 format.setFontUnderline(true); 0518 0519 if (cursor.position() > 0) { 0520 cursor.insertText(QStringLiteral("\n\n")); 0521 } 0522 0523 QDateTime dt = QDateTime::currentDateTime(); 0524 cursor.insertText(i18n("Executing script at %1", QLocale().toString(dt))); 0525 0526 format.setFontWeight(QFont::Normal); 0527 format.setFontUnderline(false); 0528 QTextBlockFormat block = cursor.blockFormat(); 0529 block.setLeftMargin(10); 0530 cursor.insertBlock(block, format); 0531 QElapsedTimer t; 0532 t.start(); 0533 0534 if (m_mode == PlasmaConsole) { 0535 QDBusMessage message = QDBusMessage::createMethodCall(s_plasmaShellService, 0536 QStringLiteral("/PlasmaShell"), 0537 QStringLiteral("org.kde.PlasmaShell"), 0538 QStringLiteral("evaluateScript")); 0539 QList<QVariant> arguments; 0540 arguments << QVariant(m_editorPart->text()); 0541 message.setArguments(arguments); 0542 QDBusMessage reply = QDBusConnection::sessionBus().call(message); 0543 if (reply.type() == QDBusMessage::ErrorMessage) { 0544 print(reply.errorMessage()); 0545 } else { 0546 print(reply.arguments().first().toString()); 0547 } 0548 } else if (m_mode == KWinConsole) { 0549 const QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/" + s_autosaveFileName; 0550 saveScript(QUrl::fromLocalFile(path)); 0551 0552 QDBusMessage message = QDBusMessage::createMethodCall(s_kwinService, QStringLiteral("/Scripting"), QString(), QStringLiteral("loadScript")); 0553 QList<QVariant> arguments; 0554 arguments << QVariant(path); 0555 message.setArguments(arguments); 0556 QDBusMessage reply = QDBusConnection::sessionBus().call(message); 0557 if (reply.type() == QDBusMessage::ErrorMessage) { 0558 print(reply.errorMessage()); 0559 } else { 0560 const int id = reply.arguments().constFirst().toInt(); 0561 QDBusConnection::sessionBus().connect(s_kwinService, "/" + QString::number(id), QString(), QStringLiteral("print"), this, SLOT(print(QString))); 0562 QDBusConnection::sessionBus() 0563 .connect(s_kwinService, "/" + QString::number(id), QString(), QStringLiteral("printError"), this, SLOT(print(QString))); 0564 message = QDBusMessage::createMethodCall(s_kwinService, "/" + QString::number(id), QString(), QStringLiteral("run")); 0565 reply = QDBusConnection::sessionBus().call(message); 0566 if (reply.type() == QDBusMessage::ErrorMessage) { 0567 print(reply.errorMessage()); 0568 } 0569 } 0570 } 0571 0572 cursor.insertText(QStringLiteral("\n\n")); 0573 format.setFontWeight(QFont::Bold); 0574 // xgettext:no-c-format 0575 cursor.insertText(i18n("Runtime: %1ms", QString::number(t.elapsed())), format); 0576 block.setLeftMargin(0); 0577 cursor.insertBlock(block); 0578 m_output->ensureCursorVisible(); 0579 } 0580 0581 void InteractiveConsole::clearEditor() 0582 { 0583 if (m_editorPart) { 0584 m_editorPart->clear(); 0585 } else { 0586 m_editor->clear(); 0587 } 0588 } 0589 0590 void InteractiveConsole::clearOutput() 0591 { 0592 m_output->clear(); 0593 }