~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to plasma/desktop/shell/interactiveconsole.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *   Copyright 2009 Aaron Seigo <aseigo@kde.org>
 
3
 *
 
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.
 
8
 *
 
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
 
13
 *
 
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.
 
18
 */
 
19
 
 
20
#include "interactiveconsole.h"
 
21
 
 
22
#include <QDateTime>
 
23
#include <QFile>
 
24
#include <QHBoxLayout>
 
25
#include <QLabel>
 
26
#include <QSplitter>
 
27
#include <QToolButton>
 
28
#include <QVBoxLayout>
 
29
 
 
30
#include <KFileDialog>
 
31
#include <KLocale>
 
32
#include <KAction>
 
33
#include <KShell>
 
34
#include <KMessageBox>
 
35
#include <KMenu>
 
36
#include <KServiceTypeTrader>
 
37
#include <KStandardAction>
 
38
#include <KStandardDirs>
 
39
#include <KTextBrowser>
 
40
#include <KTextEdit>
 
41
#include <KTextEditor/ConfigInterface>
 
42
#include <KTextEditor/Document>
 
43
#include <KTextEditor/View>
 
44
#include <KToolBar>
 
45
 
 
46
#include <Plasma/Corona>
 
47
#include <Plasma/Package>
 
48
 
 
49
#include "plasmaapp.h"
 
50
#include "scripting/desktopscriptengine.h"
 
51
#include "scripting/layouttemplatepackagestructure.h"
 
52
 
 
53
//TODO:
 
54
// use text editor KPart for syntax highlighting?
 
55
// interative help?
 
56
static const QString s_autosaveFileName("interactiveconsoleautosave.js");
 
57
 
 
58
InteractiveConsole::InteractiveConsole(Plasma::Corona *corona, QWidget *parent)
 
59
    : KDialog(parent),
 
60
      m_corona(corona),
 
61
      m_splitter(new QSplitter(Qt::Vertical, this)),
 
62
      m_editorPart(0),
 
63
      m_editor(0),
 
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)),
 
69
      m_fileDialog(0)
 
70
{
 
71
    addAction(KStandardAction::close(this, SLOT(close()), this));
 
72
    addAction(m_saveAction);
 
73
    addAction(m_clearAction);
 
74
 
 
75
    setWindowTitle(KDialog::makeStandardCaption(i18n("Desktop Shell Scripting Console")));
 
76
    setAttribute(Qt::WA_DeleteOnClose);
 
77
    setButtons(KDialog::None);
 
78
 
 
79
    QWidget *widget = new QWidget(m_splitter);
 
80
    QVBoxLayout *editorLayout = new QVBoxLayout(widget);
 
81
 
 
82
    QLabel *label = new QLabel(i18n("Editor"), widget);
 
83
    QFont f = label->font();
 
84
    f.setBold(true);
 
85
    label->setFont(f);
 
86
    editorLayout->addWidget(label);
 
87
 
 
88
    connect(m_snippetsMenu, SIGNAL(aboutToShow()), this, SLOT(populateTemplatesMenu()));
 
89
 
 
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*)));
 
95
 
 
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*)));
 
101
 
 
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);
 
110
 
 
111
    editorLayout->addWidget(toolBar);
 
112
 
 
113
    KService::List offers = KServiceTypeTrader::self()->query("KTextEditor/Document");
 
114
    foreach (const KService::Ptr service, offers) {
 
115
        m_editorPart = service->createInstance<KTextEditor::Document>(widget);
 
116
        if (m_editorPart) {
 
117
            m_editorPart->setHighlightingMode("JavaScript/PlasmaDesktop");
 
118
 
 
119
            KTextEditor::View * view = m_editorPart->createView(widget);
 
120
            view->setContextMenu(view->defaultContextMenu());
 
121
 
 
122
            KTextEditor::ConfigInterface *config = qobject_cast<KTextEditor::ConfigInterface*>(view);
 
123
            if (config) {
 
124
                config->setConfigValue("line-numbers", true);
 
125
                config->setConfigValue("dynamic-word-wrap", true);
 
126
            }
 
127
 
 
128
            editorLayout->addWidget(view);
 
129
            connect(m_editorPart, SIGNAL(textChanged(KTextEditor::Document*)),
 
130
                    this, SLOT(scriptTextChanged()));
 
131
            break;
 
132
        }
 
133
    }
 
134
 
 
135
    if (!m_editorPart) {
 
136
        m_editor = new KTextEdit(widget);
 
137
        editorLayout->addWidget(m_editor);
 
138
        connect(m_editor, SIGNAL(textChanged()), this, SLOT(scriptTextChanged()));
 
139
    }
 
140
 
 
141
    m_splitter->addWidget(widget);
 
142
 
 
143
    widget = new QWidget(m_splitter);
 
144
    QVBoxLayout *outputLayout = new QVBoxLayout(widget);
 
145
 
 
146
    label = new QLabel(i18n("Output"), widget);
 
147
    f = label->font();
 
148
    f.setBold(true);
 
149
    label->setFont(f);
 
150
    outputLayout->addWidget(label);
 
151
 
 
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);
 
157
 
 
158
    m_output = new KTextBrowser(widget);
 
159
    outputLayout->addWidget(m_output);
 
160
    m_splitter->addWidget(widget);
 
161
 
 
162
    setMainWidget(m_splitter);
 
163
 
 
164
    setInitialSize(QSize(700, 500));
 
165
    KConfigGroup cg(KGlobal::config(), "InteractiveConsole");
 
166
    restoreDialogSize(cg);
 
167
 
 
168
    m_splitter->setStretchFactor(0, 10);
 
169
    m_splitter->restoreState(cg.readEntry("SplitterState", QByteArray()));
 
170
 
 
171
    scriptTextChanged();
 
172
 
 
173
    connect(m_executeAction, SIGNAL(triggered()), this, SLOT(evaluateScript()));
 
174
    m_executeAction->setShortcut(Qt::CTRL + Qt::Key_E);
 
175
 
 
176
    const QString autosave = KStandardDirs::locateLocal("appdata", s_autosaveFileName);
 
177
    if (QFile::exists(autosave)) {
 
178
        loadScript(autosave);
 
179
    }
 
180
}
 
181
 
 
182
InteractiveConsole::~InteractiveConsole()
 
183
{
 
184
    KConfigGroup cg(KGlobal::config(), "InteractiveConsole");
 
185
    saveDialogSize(cg);
 
186
    cg.writeEntry("SplitterState", m_splitter->saveState());
 
187
    kDebug();
 
188
}
 
189
 
 
190
void InteractiveConsole::loadScript(const QString &script)
 
191
{
 
192
    if (m_editorPart) {
 
193
        m_editorPart->closeUrl(false);
 
194
        if (m_editorPart->openUrl(script)) {
 
195
            m_editorPart->setHighlightingMode("JavaScript/PlasmaDesktop");
 
196
            return;
 
197
        }
 
198
    } else {
 
199
        QFile file(KShell::tildeExpand(script));
 
200
        if (file.open(QIODevice::ReadOnly | QIODevice::Text) ) {
 
201
            m_editor->setText(file.readAll());
 
202
            return;
 
203
        }
 
204
    }
 
205
 
 
206
 
 
207
    m_output->append(i18n("Unable to load script file <b>%1</b>", script));
 
208
}
 
209
 
 
210
void InteractiveConsole::showEvent(QShowEvent *)
 
211
{
 
212
    if (m_editorPart) {
 
213
        m_editorPart->activeView()->setFocus();
 
214
    } else {
 
215
        m_editor->setFocus();
 
216
    }
 
217
}
 
218
 
 
219
void InteractiveConsole::closeEvent(QCloseEvent *event)
 
220
{
 
221
    onClose();
 
222
    KDialog::closeEvent(event);
 
223
}
 
224
 
 
225
void InteractiveConsole::reject()
 
226
{
 
227
    onClose();
 
228
    KDialog::reject();
 
229
}
 
230
 
 
231
void InteractiveConsole::onClose()
 
232
{
 
233
    // need to save first!
 
234
    const QString path = KStandardDirs::locateLocal("appdata", s_autosaveFileName);
 
235
    m_closeWhenCompleted = true;
 
236
    saveScript(path);
 
237
}
 
238
 
 
239
void InteractiveConsole::print(const QString &string)
 
240
{
 
241
    m_output->append(string);
 
242
}
 
243
 
 
244
void InteractiveConsole::scriptTextChanged()
 
245
{
 
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);
 
250
}
 
251
 
 
252
void InteractiveConsole::openScriptFile()
 
253
{
 
254
    delete m_fileDialog;
 
255
 
 
256
    m_fileDialog = new KFileDialog(KUrl(), QString(), 0);
 
257
    m_fileDialog->setOperationMode(KFileDialog::Opening);
 
258
    m_fileDialog->setCaption(i18n("Open Script File"));
 
259
 
 
260
    QStringList mimetypes;
 
261
    mimetypes << "application/javascript";
 
262
    m_fileDialog->setMimeFilter(mimetypes);
 
263
 
 
264
    connect(m_fileDialog, SIGNAL(finished()), this, SLOT(openScriptUrlSelected()));
 
265
    m_fileDialog->show();
 
266
}
 
267
 
 
268
void InteractiveConsole::openScriptUrlSelected()
 
269
{
 
270
    if (!m_fileDialog) {
 
271
        return;
 
272
    }
 
273
 
 
274
    KUrl url = m_fileDialog->selectedUrl();
 
275
    m_fileDialog->deleteLater();
 
276
    m_fileDialog = 0;
 
277
 
 
278
    if (url.isEmpty()) {
 
279
        return;
 
280
    }
 
281
 
 
282
    loadScriptFromUrl(url);
 
283
}
 
284
 
 
285
void InteractiveConsole::loadScriptFromUrl(const KUrl &url)
 
286
{
 
287
    if (m_editorPart) {
 
288
        m_editorPart->closeUrl(false);
 
289
        m_editorPart->openUrl(url);
 
290
        m_editorPart->setHighlightingMode("JavaScript/PlasmaDesktop");
 
291
    } else {
 
292
        m_editor->clear();
 
293
        m_editor->setEnabled(false);
 
294
 
 
295
        if (m_job) {
 
296
            m_job.data()->kill();
 
297
        }
 
298
 
 
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*)));
 
302
    }
 
303
}
 
304
 
 
305
void InteractiveConsole::populateTemplatesMenu()
 
306
{
 
307
    m_snippetsMenu->clear();
 
308
 
 
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);
 
315
    }
 
316
 
 
317
    QMapIterator<QString, KService::Ptr> it(sorted);
 
318
    Plasma::PackageStructure::Ptr templateStructure(new WorkspaceScripting::LayoutTemplatePackageStructure);
 
319
    while (it.hasNext()) {
 
320
        it.next();
 
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());
 
329
            }
 
330
        }
 
331
    }
 
332
}
 
333
 
 
334
void InteractiveConsole::loadTemplate(QAction *action)
 
335
{
 
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));
 
344
        }
 
345
    }
 
346
}
 
347
 
 
348
void InteractiveConsole::useTemplate(QAction *action)
 
349
{
 
350
    QString code("var template = loadTemplate('" + action->data().toString() + "')");
 
351
    if (m_editorPart) {
 
352
        const QList<KTextEditor::View *> views = m_editorPart->views();
 
353
        if (views.isEmpty()) {
 
354
            m_editorPart->insertLines(m_editorPart->lines(), QStringList() << code);
 
355
        } else {
 
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);
 
360
        }
 
361
    } else {
 
362
        m_editor->insertPlainText(code);
 
363
    }
 
364
}
 
365
 
 
366
void InteractiveConsole::scriptFileDataRecvd(KIO::Job *job, const QByteArray &data)
 
367
{
 
368
    Q_ASSERT(m_editor);
 
369
 
 
370
    if (job == m_job.data()) {
 
371
        m_editor->insertPlainText(data);
 
372
    }
 
373
}
 
374
 
 
375
void InteractiveConsole::saveScript()
 
376
{
 
377
    if (m_editorPart) {
 
378
        m_editorPart->documentSaveAs();
 
379
        return;
 
380
    }
 
381
 
 
382
    delete m_fileDialog;
 
383
 
 
384
    m_fileDialog = new KFileDialog(KUrl(), QString(), 0);
 
385
    m_fileDialog->setOperationMode(KFileDialog::Saving);
 
386
    m_fileDialog->setCaption(i18n("Save Script File"));
 
387
 
 
388
    QStringList mimetypes;
 
389
    mimetypes << "application/javascript";
 
390
    m_fileDialog->setMimeFilter(mimetypes);
 
391
 
 
392
    connect(m_fileDialog, SIGNAL(finished()), this, SLOT(saveScriptUrlSelected()));
 
393
    m_fileDialog->show();
 
394
}
 
395
 
 
396
void InteractiveConsole::saveScriptUrlSelected()
 
397
{
 
398
    if (!m_fileDialog) {
 
399
        return;
 
400
    }
 
401
 
 
402
    KUrl url = m_fileDialog->selectedUrl();
 
403
    if (url.isEmpty()) {
 
404
        return;
 
405
    }
 
406
 
 
407
    saveScript(url);
 
408
}
 
409
 
 
410
void InteractiveConsole::saveScript(const KUrl &url)
 
411
{
 
412
    if (m_editorPart) {
 
413
        m_editorPart->saveAs(url);
 
414
    } else {
 
415
        m_editor->setEnabled(false);
 
416
 
 
417
        if (m_job) {
 
418
            m_job.data()->kill();
 
419
        }
 
420
 
 
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*)));
 
424
    }
 
425
}
 
426
 
 
427
void InteractiveConsole::scriptFileDataReq(KIO::Job *job, QByteArray &data)
 
428
{
 
429
    Q_ASSERT(m_editor);
 
430
 
 
431
    if (!m_job || m_job.data() != job) {
 
432
        return;
 
433
    }
 
434
 
 
435
    data.append(m_editor->toPlainText().toLocal8Bit());
 
436
    m_job.clear();
 
437
}
 
438
 
 
439
void InteractiveConsole::reenableEditor(KJob* job)
 
440
{
 
441
    Q_ASSERT(m_editor);
 
442
    if (m_closeWhenCompleted && job->error() != 0) {
 
443
        close();
 
444
    }
 
445
 
 
446
    m_closeWhenCompleted = false;
 
447
    m_editor->setEnabled(true);
 
448
}
 
449
 
 
450
void InteractiveConsole::evaluateScript()
 
451
{
 
452
    //kDebug() << "evaluating" << m_editor->toPlainText();
 
453
    const QString path = KStandardDirs::locateLocal("appdata", s_autosaveFileName);
 
454
    saveScript(path);
 
455
 
 
456
    m_output->moveCursor(QTextCursor::End);
 
457
    QTextCursor cursor = m_output->textCursor();
 
458
    m_output->setTextCursor(cursor);
 
459
 
 
460
    QTextCharFormat format;
 
461
    format.setFontWeight(QFont::Bold);
 
462
    format.setFontUnderline(true);
 
463
 
 
464
    if (cursor.position() > 0) {
 
465
        cursor.insertText("\n\n");
 
466
    }
 
467
 
 
468
    QDateTime dt = QDateTime::currentDateTime();
 
469
    cursor.insertText(i18n("Executing script at %1", KGlobal::locale()->formatDateTime(dt)), format);
 
470
 
 
471
    format.setFontWeight(QFont::Normal);
 
472
    format.setFontUnderline(false);
 
473
    QTextBlockFormat block = cursor.blockFormat();
 
474
    block.setLeftMargin(10);
 
475
    cursor.insertBlock(block, format);
 
476
    QTime t;
 
477
    t.start();
 
478
 
 
479
    {
 
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());
 
485
    }
 
486
 
 
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();
 
494
}
 
495
 
 
496
void InteractiveConsole::clearEditor()
 
497
{
 
498
    if (m_editorPart) {
 
499
        m_editorPart->clear();
 
500
    } else {
 
501
        m_editor->clear();
 
502
    }
 
503
}
 
504
 
 
505
void InteractiveConsole::clearOutput()
 
506
{
 
507
    m_output->clear();
 
508
}
 
509
 
 
510
#include "interactiveconsole.moc"
 
511