File indexing completed on 2024-04-28 05:34:59

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