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 }