2
* Copyright 2009 Aaron Seigo <aseigo@kde.org>
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU Library General Public License as
6
* published by the Free Software Foundation; either version 2, or
7
* (at your option) any later version.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details
14
* You should have received a copy of the GNU Library General Public
15
* License along with this program; if not, write to the
16
* Free Software Foundation, Inc.,
17
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
#include "interactiveconsole.h"
24
#include <QHBoxLayout>
27
#include <QToolButton>
28
#include <QVBoxLayout>
30
#include <KFileDialog>
34
#include <KMessageBox>
36
#include <KServiceTypeTrader>
37
#include <KStandardAction>
38
#include <KStandardDirs>
39
#include <KTextBrowser>
41
#include <KTextEditor/ConfigInterface>
42
#include <KTextEditor/Document>
43
#include <KTextEditor/View>
46
#include <Plasma/Corona>
47
#include <Plasma/Package>
49
#include "plasmaapp.h"
50
#include "scripting/desktopscriptengine.h"
51
#include "scripting/layouttemplatepackagestructure.h"
54
// use text editor KPart for syntax highlighting?
56
static const QString s_autosaveFileName("interactiveconsoleautosave.js");
58
InteractiveConsole::InteractiveConsole(Plasma::Corona *corona, QWidget *parent)
61
m_splitter(new QSplitter(Qt::Vertical, this)),
64
m_loadAction(KStandardAction::open(this, SLOT(openScriptFile()), this)),
65
m_saveAction(KStandardAction::saveAs(this, SLOT(saveScript()), this)),
66
m_clearAction(KStandardAction::clear(this, SLOT(clearEditor()), this)),
67
m_executeAction(new KAction(KIcon("system-run"), i18n("&Execute"), this)),
68
m_snippetsMenu(new KMenu(i18n("Templates"), this)),
71
addAction(KStandardAction::close(this, SLOT(close()), this));
72
addAction(m_saveAction);
73
addAction(m_clearAction);
75
setWindowTitle(KDialog::makeStandardCaption(i18n("Desktop Shell Scripting Console")));
76
setAttribute(Qt::WA_DeleteOnClose);
77
setButtons(KDialog::None);
79
QWidget *widget = new QWidget(m_splitter);
80
QVBoxLayout *editorLayout = new QVBoxLayout(widget);
82
QLabel *label = new QLabel(i18n("Editor"), widget);
83
QFont f = label->font();
86
editorLayout->addWidget(label);
88
connect(m_snippetsMenu, SIGNAL(aboutToShow()), this, SLOT(populateTemplatesMenu()));
90
QToolButton *loadTemplateButton = new QToolButton(this);
91
loadTemplateButton->setPopupMode(QToolButton::InstantPopup);
92
loadTemplateButton->setMenu(m_snippetsMenu);
93
loadTemplateButton->setText(i18n("Load"));
94
connect(loadTemplateButton, SIGNAL(triggered(QAction*)), this, SLOT(loadTemplate(QAction*)));
96
QToolButton *useTemplateButton = new QToolButton(this);
97
useTemplateButton->setPopupMode(QToolButton::InstantPopup);
98
useTemplateButton->setMenu(m_snippetsMenu);
99
useTemplateButton->setText(i18n("Use"));
100
connect(useTemplateButton, SIGNAL(triggered(QAction*)), this, SLOT(useTemplate(QAction*)));
102
KToolBar *toolBar = new KToolBar(this, true, false);
103
toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
104
toolBar->addAction(m_loadAction);
105
toolBar->addAction(m_saveAction);
106
toolBar->addAction(m_clearAction);
107
toolBar->addAction(m_executeAction);
108
toolBar->addWidget(loadTemplateButton);
109
toolBar->addWidget(useTemplateButton);
111
editorLayout->addWidget(toolBar);
113
KService::List offers = KServiceTypeTrader::self()->query("KTextEditor/Document");
114
foreach (const KService::Ptr service, offers) {
115
m_editorPart = service->createInstance<KTextEditor::Document>(widget);
117
m_editorPart->setHighlightingMode("JavaScript/PlasmaDesktop");
119
KTextEditor::View * view = m_editorPart->createView(widget);
120
view->setContextMenu(view->defaultContextMenu());
122
KTextEditor::ConfigInterface *config = qobject_cast<KTextEditor::ConfigInterface*>(view);
124
config->setConfigValue("line-numbers", true);
125
config->setConfigValue("dynamic-word-wrap", true);
128
editorLayout->addWidget(view);
129
connect(m_editorPart, SIGNAL(textChanged(KTextEditor::Document*)),
130
this, SLOT(scriptTextChanged()));
136
m_editor = new KTextEdit(widget);
137
editorLayout->addWidget(m_editor);
138
connect(m_editor, SIGNAL(textChanged()), this, SLOT(scriptTextChanged()));
141
m_splitter->addWidget(widget);
143
widget = new QWidget(m_splitter);
144
QVBoxLayout *outputLayout = new QVBoxLayout(widget);
146
label = new QLabel(i18n("Output"), widget);
150
outputLayout->addWidget(label);
152
KToolBar *outputToolBar = new KToolBar(widget, true, false);
153
outputToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
154
QAction *clearOutputAction = KStandardAction::clear(this, SLOT(clearOutput()), this);
155
outputToolBar->addAction(clearOutputAction);
156
outputLayout->addWidget(outputToolBar);
158
m_output = new KTextBrowser(widget);
159
outputLayout->addWidget(m_output);
160
m_splitter->addWidget(widget);
162
setMainWidget(m_splitter);
164
setInitialSize(QSize(700, 500));
165
KConfigGroup cg(KGlobal::config(), "InteractiveConsole");
166
restoreDialogSize(cg);
168
m_splitter->setStretchFactor(0, 10);
169
m_splitter->restoreState(cg.readEntry("SplitterState", QByteArray()));
173
connect(m_executeAction, SIGNAL(triggered()), this, SLOT(evaluateScript()));
174
m_executeAction->setShortcut(Qt::CTRL + Qt::Key_E);
176
const QString autosave = KStandardDirs::locateLocal("appdata", s_autosaveFileName);
177
if (QFile::exists(autosave)) {
178
loadScript(autosave);
182
InteractiveConsole::~InteractiveConsole()
184
KConfigGroup cg(KGlobal::config(), "InteractiveConsole");
186
cg.writeEntry("SplitterState", m_splitter->saveState());
190
void InteractiveConsole::loadScript(const QString &script)
193
m_editorPart->closeUrl(false);
194
if (m_editorPart->openUrl(script)) {
195
m_editorPart->setHighlightingMode("JavaScript/PlasmaDesktop");
199
QFile file(KShell::tildeExpand(script));
200
if (file.open(QIODevice::ReadOnly | QIODevice::Text) ) {
201
m_editor->setText(file.readAll());
207
m_output->append(i18n("Unable to load script file <b>%1</b>", script));
210
void InteractiveConsole::showEvent(QShowEvent *)
213
m_editorPart->activeView()->setFocus();
215
m_editor->setFocus();
219
void InteractiveConsole::closeEvent(QCloseEvent *event)
222
KDialog::closeEvent(event);
225
void InteractiveConsole::reject()
231
void InteractiveConsole::onClose()
233
// need to save first!
234
const QString path = KStandardDirs::locateLocal("appdata", s_autosaveFileName);
235
m_closeWhenCompleted = true;
239
void InteractiveConsole::print(const QString &string)
241
m_output->append(string);
244
void InteractiveConsole::scriptTextChanged()
246
const bool enable = m_editorPart ? !m_editorPart->isEmpty() : !m_editor->document()->isEmpty();
247
m_saveAction->setEnabled(enable);
248
m_clearAction->setEnabled(enable);
249
m_executeAction->setEnabled(enable);
252
void InteractiveConsole::openScriptFile()
256
m_fileDialog = new KFileDialog(KUrl(), QString(), 0);
257
m_fileDialog->setOperationMode(KFileDialog::Opening);
258
m_fileDialog->setCaption(i18n("Open Script File"));
260
QStringList mimetypes;
261
mimetypes << "application/javascript";
262
m_fileDialog->setMimeFilter(mimetypes);
264
connect(m_fileDialog, SIGNAL(finished()), this, SLOT(openScriptUrlSelected()));
265
m_fileDialog->show();
268
void InteractiveConsole::openScriptUrlSelected()
274
KUrl url = m_fileDialog->selectedUrl();
275
m_fileDialog->deleteLater();
282
loadScriptFromUrl(url);
285
void InteractiveConsole::loadScriptFromUrl(const KUrl &url)
288
m_editorPart->closeUrl(false);
289
m_editorPart->openUrl(url);
290
m_editorPart->setHighlightingMode("JavaScript/PlasmaDesktop");
293
m_editor->setEnabled(false);
296
m_job.data()->kill();
299
m_job = KIO::get(url, KIO::Reload, KIO::HideProgressInfo);
300
connect(m_job.data(), SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(scriptFileDataRecvd(KIO::Job*,QByteArray)));
301
connect(m_job.data(), SIGNAL(result(KJob*)), this, SLOT(reenableEditor(KJob*)));
305
void InteractiveConsole::populateTemplatesMenu()
307
m_snippetsMenu->clear();
309
QMap<QString, KService::Ptr> sorted;
310
const QString constraint = QString("[X-Plasma-Shell] == '%1'")
311
.arg(KGlobal::mainComponent().componentName());
312
KService::List templates = KServiceTypeTrader::self()->query("Plasma/LayoutTemplate", constraint);
313
foreach (const KService::Ptr &service, templates) {
314
sorted.insert(service->name(), service);
317
QMapIterator<QString, KService::Ptr> it(sorted);
318
Plasma::PackageStructure::Ptr templateStructure(new WorkspaceScripting::LayoutTemplatePackageStructure);
319
while (it.hasNext()) {
321
KPluginInfo info(it.value());
322
const QString path = KStandardDirs::locate("data", templateStructure->defaultPackageRoot() + '/' + info.pluginName() + '/');
323
if (!path.isEmpty()) {
324
Plasma::Package package(path, templateStructure);
325
const QString scriptFile = package.filePath("mainscript");
326
if (!scriptFile.isEmpty()) {
327
QAction *action = m_snippetsMenu->addAction(info.name());
328
action->setData(info.pluginName());
334
void InteractiveConsole::loadTemplate(QAction *action)
336
Plasma::PackageStructure::Ptr templateStructure(new WorkspaceScripting::LayoutTemplatePackageStructure);
337
const QString pluginName = action->data().toString();
338
const QString path = KStandardDirs::locate("data", templateStructure->defaultPackageRoot() + '/' + pluginName + '/');
339
if (!path.isEmpty()) {
340
Plasma::Package package(path, templateStructure);
341
const QString scriptFile = package.filePath("mainscript");
342
if (!scriptFile.isEmpty()) {
343
loadScriptFromUrl(KUrl(scriptFile));
348
void InteractiveConsole::useTemplate(QAction *action)
350
QString code("var template = loadTemplate('" + action->data().toString() + "')");
352
const QList<KTextEditor::View *> views = m_editorPart->views();
353
if (views.isEmpty()) {
354
m_editorPart->insertLines(m_editorPart->lines(), QStringList() << code);
356
KTextEditor::Cursor cursor = views.at(0)->cursorPosition();
357
m_editorPart->insertLines(cursor.line(), QStringList() << code);
358
cursor.setLine(cursor.line() + 1);
359
views.at(0)->setCursorPosition(cursor);
362
m_editor->insertPlainText(code);
366
void InteractiveConsole::scriptFileDataRecvd(KIO::Job *job, const QByteArray &data)
370
if (job == m_job.data()) {
371
m_editor->insertPlainText(data);
375
void InteractiveConsole::saveScript()
378
m_editorPart->documentSaveAs();
384
m_fileDialog = new KFileDialog(KUrl(), QString(), 0);
385
m_fileDialog->setOperationMode(KFileDialog::Saving);
386
m_fileDialog->setCaption(i18n("Save Script File"));
388
QStringList mimetypes;
389
mimetypes << "application/javascript";
390
m_fileDialog->setMimeFilter(mimetypes);
392
connect(m_fileDialog, SIGNAL(finished()), this, SLOT(saveScriptUrlSelected()));
393
m_fileDialog->show();
396
void InteractiveConsole::saveScriptUrlSelected()
402
KUrl url = m_fileDialog->selectedUrl();
410
void InteractiveConsole::saveScript(const KUrl &url)
413
m_editorPart->saveAs(url);
415
m_editor->setEnabled(false);
418
m_job.data()->kill();
421
m_job = KIO::put(url, -1, KIO::HideProgressInfo);
422
connect(m_job.data(), SIGNAL(dataReq(KIO::Job*,QByteArray&)), this, SLOT(scriptFileDataReq(KIO::Job*,QByteArray&)));
423
connect(m_job.data(), SIGNAL(result(KJob*)), this, SLOT(reenableEditor(KJob*)));
427
void InteractiveConsole::scriptFileDataReq(KIO::Job *job, QByteArray &data)
431
if (!m_job || m_job.data() != job) {
435
data.append(m_editor->toPlainText().toLocal8Bit());
439
void InteractiveConsole::reenableEditor(KJob* job)
442
if (m_closeWhenCompleted && job->error() != 0) {
446
m_closeWhenCompleted = false;
447
m_editor->setEnabled(true);
450
void InteractiveConsole::evaluateScript()
452
//kDebug() << "evaluating" << m_editor->toPlainText();
453
const QString path = KStandardDirs::locateLocal("appdata", s_autosaveFileName);
456
m_output->moveCursor(QTextCursor::End);
457
QTextCursor cursor = m_output->textCursor();
458
m_output->setTextCursor(cursor);
460
QTextCharFormat format;
461
format.setFontWeight(QFont::Bold);
462
format.setFontUnderline(true);
464
if (cursor.position() > 0) {
465
cursor.insertText("\n\n");
468
QDateTime dt = QDateTime::currentDateTime();
469
cursor.insertText(i18n("Executing script at %1", KGlobal::locale()->formatDateTime(dt)), format);
471
format.setFontWeight(QFont::Normal);
472
format.setFontUnderline(false);
473
QTextBlockFormat block = cursor.blockFormat();
474
block.setLeftMargin(10);
475
cursor.insertBlock(block, format);
480
WorkspaceScripting::DesktopScriptEngine scriptEngine(m_corona, this);
481
connect(&scriptEngine, SIGNAL(print(QString)), this, SLOT(print(QString)));
482
connect(&scriptEngine, SIGNAL(printError(QString)), this, SLOT(print(QString)));
483
connect(&scriptEngine, SIGNAL(createPendingPanelViews()), PlasmaApp::self(), SLOT(createWaitingPanels()));
484
scriptEngine.evaluateScript(m_editorPart ? m_editorPart->text() : m_editor->toPlainText());
487
cursor.insertText("\n\n");
488
format.setFontWeight(QFont::Bold);
489
// xgettext:no-c-format
490
cursor.insertText(i18n("Runtime: %1ms", QString::number(t.elapsed())), format);
491
block.setLeftMargin(0);
492
cursor.insertBlock(block);
493
m_output->ensureCursorVisible();
496
void InteractiveConsole::clearEditor()
499
m_editorPart->clear();
505
void InteractiveConsole::clearOutput()
510
#include "interactiveconsole.moc"