You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
602 lines
19 KiB
602 lines
19 KiB
/* |
|
* Copyright 2009 Aaron Seigo <aseigo@kde.org> |
|
* Copyright 2014 Marco Martin <mart@kde.org> |
|
* |
|
* This program is free software; you can redistribute it and/or modify |
|
* it under the terms of the GNU Library General Public License as |
|
* published by the Free Software Foundation; either version 2, or |
|
* (at your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details |
|
* |
|
* You should have received a copy of the GNU Library General Public |
|
* License along with this program; if not, write to the |
|
* Free Software Foundation, Inc., |
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
*/ |
|
|
|
#include "interactiveconsole.h" |
|
|
|
#include <QAction> |
|
#include <QDateTime> |
|
#include <QDBusConnection> |
|
#include <QDBusMessage> |
|
#include <QFile> |
|
#include <QFileDialog> |
|
#include <QHBoxLayout> |
|
#include <QLabel> |
|
#include <QMenu> |
|
#include <QSplitter> |
|
#include <QToolButton> |
|
#include <QVBoxLayout> |
|
#include <QStandardPaths> |
|
#include <QTextBrowser> |
|
#include <QIcon> |
|
|
|
#include <klocalizedstring.h> |
|
#include <KSharedConfig> |
|
#include <KMessageBox> |
|
#include <KServiceTypeTrader> |
|
#include <KShell> |
|
#include <KStandardAction> |
|
#include <KTextEdit> |
|
#include <KTextEditor/ConfigInterface> |
|
#include <KTextEditor/Document> |
|
#include <KTextEditor/View> |
|
#include <KToolBar> |
|
#include <KWindowSystem> |
|
|
|
#include <Plasma/Package> |
|
#include <Plasma/PluginLoader> |
|
|
|
//TODO: |
|
// interative help? |
|
static const QString s_autosaveFileName("interactiveconsoleautosave.js"); |
|
static const QString s_kwinService = QStringLiteral("org.kde.KWin"); |
|
|
|
InteractiveConsole::InteractiveConsole(QWidget *parent) |
|
: QDialog(parent), |
|
m_splitter(new QSplitter(Qt::Vertical, this)), |
|
m_editorPart(0), |
|
m_editor(0), |
|
m_output(0), |
|
m_loadAction(KStandardAction::open(this, SLOT(openScriptFile()), this)), |
|
m_saveAction(KStandardAction::saveAs(this, SLOT(saveScript()), this)), |
|
m_clearAction(KStandardAction::clear(this, SLOT(clearEditor()), this)), |
|
m_executeAction(new QAction(QIcon::fromTheme("system-run"), i18n("&Execute"), this)), |
|
m_plasmaAction(new QAction(QIcon::fromTheme("plasma"), i18nc("Toolbar Button to switch to Plasma Scripting Mode", "Plasma"), this)), |
|
m_kwinAction(new QAction(QIcon::fromTheme("kwin"), i18nc("Toolbar Button to switch to KWin Scripting Mode", "KWin"), this)), |
|
m_snippetsMenu(new QMenu(i18n("Templates"), this)), |
|
m_fileDialog(0), |
|
m_closeWhenCompleted(false), |
|
m_mode(PlasmaConsole) |
|
{ |
|
addAction(KStandardAction::close(this, SLOT(close()), this)); |
|
addAction(m_saveAction); |
|
addAction(m_clearAction); |
|
|
|
setWindowTitle(i18n("Desktop Shell Scripting Console")); |
|
setAttribute(Qt::WA_DeleteOnClose); |
|
//setButtons(QDialog::None); |
|
|
|
QWidget *widget = new QWidget(m_splitter); |
|
QVBoxLayout *editorLayout = new QVBoxLayout(widget); |
|
|
|
QLabel *label = new QLabel(i18n("Editor"), widget); |
|
QFont f = label->font(); |
|
f.setBold(true); |
|
label->setFont(f); |
|
editorLayout->addWidget(label); |
|
|
|
connect(m_snippetsMenu, SIGNAL(aboutToShow()), this, SLOT(populateTemplatesMenu())); |
|
|
|
QToolButton *loadTemplateButton = new QToolButton(this); |
|
loadTemplateButton->setPopupMode(QToolButton::InstantPopup); |
|
loadTemplateButton->setMenu(m_snippetsMenu); |
|
loadTemplateButton->setText(i18n("Load")); |
|
connect(loadTemplateButton, SIGNAL(triggered(QAction*)), this, SLOT(loadTemplate(QAction*))); |
|
|
|
QToolButton *useTemplateButton = new QToolButton(this); |
|
useTemplateButton->setPopupMode(QToolButton::InstantPopup); |
|
useTemplateButton->setMenu(m_snippetsMenu); |
|
useTemplateButton->setText(i18n("Use")); |
|
connect(useTemplateButton, SIGNAL(triggered(QAction*)), this, SLOT(useTemplate(QAction*))); |
|
|
|
QActionGroup *modeGroup = new QActionGroup(this); |
|
modeGroup->addAction(m_plasmaAction); |
|
modeGroup->addAction(m_kwinAction); |
|
m_plasmaAction->setCheckable(true); |
|
m_kwinAction->setCheckable(true); |
|
m_plasmaAction->setChecked(true); |
|
connect(modeGroup, SIGNAL(triggered(QAction*)), this, SLOT(modeSelectionChanged())); |
|
|
|
KToolBar *toolBar = new KToolBar(this, true, false); |
|
toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); |
|
toolBar->addAction(m_loadAction); |
|
toolBar->addAction(m_saveAction); |
|
toolBar->addAction(m_clearAction); |
|
toolBar->addAction(m_executeAction); |
|
toolBar->addAction(m_plasmaAction); |
|
toolBar->addAction(m_kwinAction); |
|
toolBar->addWidget(loadTemplateButton); |
|
toolBar->addWidget(useTemplateButton); |
|
|
|
editorLayout->addWidget(toolBar); |
|
|
|
KService::List offers = KServiceTypeTrader::self()->query("KTextEditor/Document"); |
|
foreach (const KService::Ptr service, offers) { |
|
m_editorPart = service->createInstance<KTextEditor::Document>(widget); |
|
if (m_editorPart) { |
|
m_editorPart->setHighlightingMode("JavaScript/PlasmaDesktop"); |
|
|
|
KTextEditor::View * view = m_editorPart->createView(widget); |
|
view->setContextMenu(view->defaultContextMenu()); |
|
|
|
KTextEditor::ConfigInterface *config = qobject_cast<KTextEditor::ConfigInterface*>(view); |
|
if (config) { |
|
config->setConfigValue("line-numbers", true); |
|
config->setConfigValue("dynamic-word-wrap", true); |
|
} |
|
|
|
editorLayout->addWidget(view); |
|
connect(m_editorPart, SIGNAL(textChanged(KTextEditor::Document*)), |
|
this, SLOT(scriptTextChanged())); |
|
break; |
|
} |
|
} |
|
|
|
if (!m_editorPart) { |
|
m_editor = new KTextEdit(widget); |
|
editorLayout->addWidget(m_editor); |
|
connect(m_editor, SIGNAL(textChanged()), this, SLOT(scriptTextChanged())); |
|
} |
|
|
|
m_splitter->addWidget(widget); |
|
|
|
widget = new QWidget(m_splitter); |
|
QVBoxLayout *outputLayout = new QVBoxLayout(widget); |
|
|
|
label = new QLabel(i18n("Output"), widget); |
|
f = label->font(); |
|
f.setBold(true); |
|
label->setFont(f); |
|
outputLayout->addWidget(label); |
|
|
|
KToolBar *outputToolBar = new KToolBar(widget, true, false); |
|
outputToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); |
|
QAction *clearOutputAction = KStandardAction::clear(this, SLOT(clearOutput()), this); |
|
outputToolBar->addAction(clearOutputAction); |
|
outputLayout->addWidget(outputToolBar); |
|
|
|
m_output = new QTextBrowser(widget); |
|
outputLayout->addWidget(m_output); |
|
m_splitter->addWidget(widget); |
|
|
|
QVBoxLayout *l = new QVBoxLayout(this); |
|
l->addWidget(m_splitter); |
|
|
|
KConfigGroup cg(KSharedConfig::openConfig(), "InteractiveConsole"); |
|
restoreGeometry(cg.readEntry<QByteArray>("Geometry", QByteArray())); |
|
|
|
m_splitter->setStretchFactor(0, 10); |
|
m_splitter->restoreState(cg.readEntry("SplitterState", QByteArray())); |
|
|
|
scriptTextChanged(); |
|
|
|
connect(m_executeAction, SIGNAL(triggered()), this, SLOT(evaluateScript())); |
|
m_executeAction->setShortcut(Qt::CTRL + Qt::Key_E); |
|
|
|
const QString autosave = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + s_autosaveFileName; |
|
if (QFile::exists(autosave)) { |
|
loadScript(autosave); |
|
} |
|
} |
|
|
|
InteractiveConsole::~InteractiveConsole() |
|
{ |
|
KConfigGroup cg(KSharedConfig::openConfig(), "InteractiveConsole"); |
|
cg.writeEntry("Geometry", saveGeometry()); |
|
cg.writeEntry("SplitterState", m_splitter->saveState()); |
|
} |
|
|
|
void InteractiveConsole::setMode(const QString &mode) |
|
{ |
|
if (mode.toLower() == "desktop") { |
|
m_plasmaAction->setChecked(true); |
|
} else if (mode.toLower() == "windowmanager") { |
|
m_kwinAction->setChecked(true); |
|
} |
|
} |
|
|
|
void InteractiveConsole::modeSelectionChanged() |
|
{ |
|
if (m_plasmaAction->isChecked()) { |
|
m_mode = PlasmaConsole; |
|
} else if (m_kwinAction->isChecked()) { |
|
m_mode = KWinConsole; |
|
} |
|
|
|
emit modeChanged(); |
|
} |
|
|
|
QString InteractiveConsole::mode() const |
|
{ |
|
if (m_mode == KWinConsole) { |
|
return "windowmanager"; |
|
} |
|
|
|
return "desktop"; |
|
} |
|
|
|
void InteractiveConsole::setScriptInterface(QObject *obj) |
|
{ |
|
if (m_scriptEngine != obj) { |
|
if (m_scriptEngine) { |
|
disconnect(m_scriptEngine, 0, this, 0); |
|
} |
|
|
|
m_scriptEngine = obj; |
|
connect(m_scriptEngine, SIGNAL(print(QString)), this, SLOT(print(QString))); |
|
connect(m_scriptEngine, SIGNAL(printError(QString)), this, SLOT(print(QString))); |
|
emit scriptEngineChanged(); |
|
} |
|
} |
|
|
|
QObject *InteractiveConsole::scriptEngine() const |
|
{ |
|
return m_scriptEngine; |
|
} |
|
|
|
void InteractiveConsole::loadScript(const QString &script) |
|
{ |
|
if (m_editorPart) { |
|
m_editorPart->closeUrl(false); |
|
if (m_editorPart->openUrl(QUrl::fromLocalFile(script))) { |
|
m_editorPart->setHighlightingMode("JavaScript/PlasmaDesktop"); |
|
return; |
|
} |
|
} else { |
|
QFile file(KShell::tildeExpand(script)); |
|
if (file.open(QIODevice::ReadOnly | QIODevice::Text) ) { |
|
m_editor->setText(file.readAll()); |
|
return; |
|
} |
|
} |
|
|
|
|
|
m_output->append(i18n("Unable to load script file <b>%1</b>", script)); |
|
} |
|
|
|
void InteractiveConsole::showEvent(QShowEvent *) |
|
{ |
|
if (m_editorPart) { |
|
m_editorPart->views().first()->setFocus(); |
|
} else { |
|
m_editor->setFocus(); |
|
} |
|
|
|
emit visibilityChanged(); |
|
KWindowSystem::setOnDesktop(winId(), KWindowSystem::currentDesktop()); |
|
} |
|
|
|
void InteractiveConsole::closeEvent(QCloseEvent *event) |
|
{ |
|
// need to save first! |
|
const QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + s_autosaveFileName; |
|
m_closeWhenCompleted = true; |
|
saveScript(QUrl::fromLocalFile(path)); |
|
QDialog::closeEvent(event); |
|
} |
|
|
|
void InteractiveConsole::reject() |
|
{ |
|
QDialog::reject(); |
|
close(); |
|
} |
|
|
|
void InteractiveConsole::print(const QString &string) |
|
{ |
|
m_output->append(string); |
|
} |
|
|
|
void InteractiveConsole::scriptTextChanged() |
|
{ |
|
const bool enable = m_editorPart ? !m_editorPart->isEmpty() : !m_editor->document()->isEmpty(); |
|
m_saveAction->setEnabled(enable); |
|
m_clearAction->setEnabled(enable); |
|
m_executeAction->setEnabled(enable); |
|
} |
|
|
|
void InteractiveConsole::openScriptFile() |
|
{ |
|
delete m_fileDialog; |
|
|
|
m_fileDialog = new QFileDialog(); |
|
m_fileDialog->setAcceptMode(QFileDialog::AcceptOpen); |
|
m_fileDialog->setWindowTitle(i18n("Open Script File")); |
|
|
|
QStringList mimetypes; |
|
mimetypes << "application/javascript"; |
|
m_fileDialog->setMimeTypeFilters(mimetypes); |
|
|
|
connect(m_fileDialog, SIGNAL(finished(int)), this, SLOT(openScriptUrlSelected(int))); |
|
m_fileDialog->show(); |
|
} |
|
|
|
void InteractiveConsole::openScriptUrlSelected(int result) |
|
{ |
|
if (!m_fileDialog) { |
|
return; |
|
} |
|
|
|
if (result == QDialog::Accepted) { |
|
const QUrl url = m_fileDialog->selectedUrls().first(); |
|
if (!url.isEmpty()) { |
|
loadScriptFromUrl(url); |
|
} |
|
} |
|
|
|
m_fileDialog->deleteLater(); |
|
m_fileDialog = 0; |
|
} |
|
|
|
void InteractiveConsole::loadScriptFromUrl(const QUrl &url) |
|
{ |
|
if (m_editorPart) { |
|
m_editorPart->closeUrl(false); |
|
m_editorPart->openUrl(url); |
|
m_editorPart->setHighlightingMode("JavaScript/PlasmaDesktop"); |
|
} else { |
|
m_editor->clear(); |
|
m_editor->setEnabled(false); |
|
|
|
if (m_job) { |
|
m_job.data()->kill(); |
|
} |
|
|
|
m_job = KIO::get(url, KIO::Reload, KIO::HideProgressInfo); |
|
connect(m_job.data(), SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(scriptFileDataRecvd(KIO::Job*,QByteArray))); |
|
connect(m_job.data(), SIGNAL(result(KJob*)), this, SLOT(reenableEditor(KJob*))); |
|
} |
|
} |
|
|
|
void InteractiveConsole::populateTemplatesMenu() |
|
{ |
|
m_snippetsMenu->clear(); |
|
|
|
QMap<QString, KService::Ptr> sorted; |
|
const QString constraint = QString("[X-Plasma-Shell] == '%1'") |
|
.arg(qApp->applicationName()); |
|
KService::List templates = KServiceTypeTrader::self()->query("Plasma/LayoutTemplate", constraint); |
|
foreach (const KService::Ptr &service, templates) { |
|
sorted.insert(service->name(), service); |
|
} |
|
|
|
QMapIterator<QString, KService::Ptr> it(sorted); |
|
|
|
Plasma::Package package = Plasma::PluginLoader::self()->loadPackage("Plasma/LayoutTemplate"); |
|
|
|
while (it.hasNext()) { |
|
it.next(); |
|
KPluginInfo info(it.value()); |
|
const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation |
|
, package.defaultPackageRoot() + '/' + info.pluginName() + '/'); |
|
if (!path.isEmpty()) { |
|
package.setPath(info.pluginName()); |
|
const QString scriptFile = package.filePath("mainscript"); |
|
if (!scriptFile.isEmpty()) { |
|
QAction *action = m_snippetsMenu->addAction(info.name()); |
|
action->setData(info.pluginName()); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void InteractiveConsole::loadTemplate(QAction *action) |
|
{ |
|
Plasma::Package package = Plasma::PluginLoader::self()->loadPackage("Plasma/LayoutTemplate"); |
|
|
|
const QString pluginName = action->data().toString(); |
|
const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation |
|
, package.defaultPackageRoot() + '/' + pluginName + '/'); |
|
if (!path.isEmpty()) { |
|
package.setPath(pluginName); |
|
const QString scriptFile = package.filePath("mainscript"); |
|
if (!scriptFile.isEmpty()) { |
|
loadScriptFromUrl(QUrl::fromLocalFile(scriptFile)); |
|
} |
|
} |
|
} |
|
|
|
void InteractiveConsole::useTemplate(QAction *action) |
|
{ |
|
QString code("var template = loadTemplate('" + action->data().toString() + "')"); |
|
if (m_editorPart) { |
|
const QList<KTextEditor::View *> views = m_editorPart->views(); |
|
if (views.isEmpty()) { |
|
m_editorPart->insertLines(m_editorPart->lines(), QStringList() << code); |
|
} else { |
|
KTextEditor::Cursor cursor = views.at(0)->cursorPosition(); |
|
m_editorPart->insertLines(cursor.line(), QStringList() << code); |
|
cursor.setLine(cursor.line() + 1); |
|
views.at(0)->setCursorPosition(cursor); |
|
} |
|
} else { |
|
m_editor->insertPlainText(code); |
|
} |
|
} |
|
|
|
void InteractiveConsole::scriptFileDataRecvd(KIO::Job *job, const QByteArray &data) |
|
{ |
|
Q_ASSERT(m_editor); |
|
|
|
if (job == m_job.data()) { |
|
m_editor->insertPlainText(data); |
|
} |
|
} |
|
|
|
void InteractiveConsole::saveScript() |
|
{ |
|
if (m_editorPart) { |
|
m_editorPart->documentSaveAs(); |
|
return; |
|
} |
|
|
|
delete m_fileDialog; |
|
|
|
m_fileDialog = new QFileDialog(); |
|
m_fileDialog->setAcceptMode(QFileDialog::AcceptSave); |
|
m_fileDialog->setWindowTitle(i18n("Save Script File")); |
|
|
|
QStringList mimetypes; |
|
mimetypes << "application/javascript"; |
|
m_fileDialog->setMimeTypeFilters(mimetypes); |
|
|
|
connect(m_fileDialog, SIGNAL(finished(int)), this, SLOT(saveScriptUrlSelected(int))); |
|
m_fileDialog->show(); |
|
} |
|
|
|
void InteractiveConsole::saveScriptUrlSelected(int result) |
|
{ |
|
if (!m_fileDialog) { |
|
return; |
|
} |
|
|
|
if (result == QDialog::Accepted) { |
|
const QUrl url = m_fileDialog->selectedUrls().first(); |
|
if (!url.isEmpty()) { |
|
saveScript(url); |
|
} |
|
} |
|
|
|
m_fileDialog->deleteLater(); |
|
m_fileDialog = 0; |
|
} |
|
|
|
void InteractiveConsole::saveScript(const QUrl &url) |
|
{ |
|
//create the folder to save if doesn't exists |
|
QFileInfo info(url.path()); |
|
QDir dir; |
|
dir.mkpath(info.absoluteDir().absolutePath()); |
|
|
|
if (m_editorPart) { |
|
m_editorPart->saveAs(url); |
|
} else { |
|
m_editor->setEnabled(false); |
|
|
|
if (m_job) { |
|
m_job.data()->kill(); |
|
} |
|
|
|
m_job = KIO::put(url, -1, KIO::HideProgressInfo); |
|
connect(m_job.data(), SIGNAL(dataReq(KIO::Job*,QByteArray&)), this, SLOT(scriptFileDataReq(KIO::Job*,QByteArray&))); |
|
connect(m_job.data(), SIGNAL(result(KJob*)), this, SLOT(reenableEditor(KJob*))); |
|
} |
|
} |
|
|
|
void InteractiveConsole::scriptFileDataReq(KIO::Job *job, QByteArray &data) |
|
{ |
|
Q_ASSERT(m_editor); |
|
|
|
if (!m_job || m_job.data() != job) { |
|
return; |
|
} |
|
|
|
data.append(m_editor->toPlainText().toLocal8Bit()); |
|
m_job.clear(); |
|
} |
|
|
|
void InteractiveConsole::reenableEditor(KJob* job) |
|
{ |
|
Q_ASSERT(m_editor); |
|
if (m_closeWhenCompleted && job->error() != 0) { |
|
close(); |
|
} |
|
|
|
m_closeWhenCompleted = false; |
|
m_editor->setEnabled(true); |
|
} |
|
|
|
void InteractiveConsole::evaluateScript() |
|
{ |
|
//qDebug() << "evaluating" << m_editor->toPlainText(); |
|
const QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + s_autosaveFileName; |
|
saveScript(QUrl::fromLocalFile(path)); |
|
|
|
m_output->moveCursor(QTextCursor::End); |
|
QTextCursor cursor = m_output->textCursor(); |
|
m_output->setTextCursor(cursor); |
|
|
|
QTextCharFormat format; |
|
format.setFontWeight(QFont::Bold); |
|
format.setFontUnderline(true); |
|
|
|
if (cursor.position() > 0) { |
|
cursor.insertText("\n\n"); |
|
} |
|
|
|
QDateTime dt = QDateTime::currentDateTime(); |
|
cursor.insertText(i18n("Executing script at %1", QLocale().toString(dt))); |
|
|
|
format.setFontWeight(QFont::Normal); |
|
format.setFontUnderline(false); |
|
QTextBlockFormat block = cursor.blockFormat(); |
|
block.setLeftMargin(10); |
|
cursor.insertBlock(block, format); |
|
QTime t; |
|
t.start(); |
|
|
|
if (m_mode == PlasmaConsole) { |
|
if (m_scriptEngine) { |
|
const QString script = m_editorPart ? m_editorPart->text() : m_editor->toPlainText(); |
|
QMetaObject::invokeMethod(m_scriptEngine, "evaluateScript", Q_ARG(QString, script)); |
|
} |
|
} else if (m_mode == KWinConsole) { |
|
QDBusMessage message = QDBusMessage::createMethodCall(s_kwinService, "/Scripting", QString(), "loadScript"); |
|
QList<QVariant> arguments; |
|
arguments << QVariant(path); |
|
message.setArguments(arguments); |
|
QDBusMessage reply = QDBusConnection::sessionBus().call(message); |
|
if (reply.type() == QDBusMessage::ErrorMessage) { |
|
print(reply.errorMessage()); |
|
} else { |
|
const int id = reply.arguments().first().toInt(); |
|
QDBusConnection::sessionBus().connect(s_kwinService, "/" + QString::number(id), QString(), "print", this, SLOT(print(QString))); |
|
QDBusConnection::sessionBus().connect(s_kwinService, "/" + QString::number(id), QString(), "printError", this, SLOT(print(QString))); |
|
message = QDBusMessage::createMethodCall(s_kwinService, "/" + QString::number(id), QString(), "run"); |
|
reply = QDBusConnection::sessionBus().call(message); |
|
if (reply.type() == QDBusMessage::ErrorMessage) { |
|
print(reply.errorMessage()); |
|
} |
|
} |
|
} |
|
|
|
cursor.insertText("\n\n"); |
|
format.setFontWeight(QFont::Bold); |
|
// xgettext:no-c-format |
|
cursor.insertText(i18n("Runtime: %1ms", QString::number(t.elapsed())), format); |
|
block.setLeftMargin(0); |
|
cursor.insertBlock(block); |
|
m_output->ensureCursorVisible(); |
|
} |
|
|
|
void InteractiveConsole::clearEditor() |
|
{ |
|
if (m_editorPart) { |
|
m_editorPart->clear(); |
|
} else { |
|
m_editor->clear(); |
|
} |
|
} |
|
|
|
void InteractiveConsole::clearOutput() |
|
{ |
|
m_output->clear(); |
|
} |
|
|
|
#include "interactiveconsole.moc" |
|
|
|
|