~gabriel1984sibiu/agros2d/agros2d

« back to all changes in this revision

Viewing changes to pythonlab-library/pythonlab/pythoneditor.cpp

  • Committer: Grevutiu Gabriel
  • Date: 2014-11-15 19:05:36 UTC
  • Revision ID: gabriel1984sibiu@gmail.com-20141115190536-1d4q8ez0f8b89ktj
originalĀ upstreamĀ code

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// This file is part of Agros2D.
 
2
//
 
3
// Agros2D is free software: you can redistribute it and/or modify
 
4
// it under the terms of the GNU General Public License as published by
 
5
// the Free Software Foundation, either version 2 of the License, or
 
6
// (at your option) any later version.
 
7
//
 
8
// Agros2D is distributed in the hope that it will be useful,
 
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
// GNU General Public License for more details.
 
12
//
 
13
// You should have received a copy of the GNU General Public License
 
14
// along with Agros2D.  If not, see <http://www.gnu.org/licenses/>.
 
15
//
 
16
// hp-FEM group (http://hpfem.org/)
 
17
// University of Nevada, Reno (UNR) and University of West Bohemia, Pilsen
 
18
// Email: agros2d@googlegroups.com, home page: http://hpfem.org/agros2d/
 
19
 
 
20
#include "pythoneditor.h"
 
21
#include "pythonhighlighter.h"
 
22
#include "pythonconsole.h"
 
23
#include "pythonbrowser.h"
 
24
#include "pythonengine.h"
 
25
#include "pythoncompleter.h"
 
26
 
 
27
// #include "util/constants.h"
 
28
#include "gui/filebrowser.h"
 
29
#include "gui/about.h"
 
30
 
 
31
const QString TABS = "    ";
 
32
const int TABS_SIZE = 4;
 
33
 
 
34
int firstNonSpace(const QString& text)
 
35
{
 
36
    int i = 0;
 
37
    while (i < text.size())
 
38
    {
 
39
        if (!text.at(i).isSpace())
 
40
            return i;
 
41
        ++i;
 
42
    }
 
43
    return i;
 
44
}
 
45
 
 
46
int indentedColumn(int column, bool doIndent)
 
47
{
 
48
    int aligned = (column / TABS_SIZE) * TABS_SIZE;
 
49
    if (doIndent)
 
50
        return aligned + TABS_SIZE;
 
51
    if (aligned < column)
 
52
        return aligned;
 
53
    return qMax(0, aligned - TABS_SIZE);
 
54
}
 
55
 
 
56
int columnAt(const QString& text, int position)
 
57
{
 
58
    int column = 0;
 
59
    for (int i = 0; i < position; ++i)
 
60
    {
 
61
        if (text.at(i) == QLatin1Char('\t'))
 
62
            column = column - (column % TABS_SIZE) + TABS_SIZE;
 
63
        else
 
64
            ++column;
 
65
    }
 
66
    return column;
 
67
}
 
68
 
 
69
PythonEditorWidget::PythonEditorWidget(PythonEngine *pythonEngine, QWidget *parent)
 
70
    : QWidget(parent), pythonEngine(pythonEngine)
 
71
{
 
72
    m_fileName = "";
 
73
 
 
74
    createControls();
 
75
 
 
76
    QTimer *timer = new QTimer(this);
 
77
    connect(timer, SIGNAL(timeout()), this, SLOT(pyFlakesAnalyse()));
 
78
    timer->start(4000);
 
79
 
 
80
    txtEditor->setAcceptDrops(false);
 
81
}
 
82
 
 
83
PythonEditorWidget::~PythonEditorWidget()
 
84
{
 
85
    QSettings settings;
 
86
 
 
87
    settings.setValue("PythonEditorWidget/Geometry", saveGeometry());
 
88
    settings.setValue("PythonEditorWidget/SplitterState", splitter->saveState());
 
89
    settings.setValue("PythonEditorWidget/SplitterGeometry", splitter->saveGeometry());
 
90
    settings.setValue("PythonEditorWidget/EditorHeight", txtEditor->height());
 
91
}
 
92
 
 
93
void PythonEditorWidget::createControls()
 
94
{
 
95
    txtEditor = new ScriptEditor(pythonEngine, this);
 
96
    searchWidget = new SearchWidget(txtEditor, this);
 
97
 
 
98
    QVBoxLayout *layoutEditor = new QVBoxLayout();
 
99
    layoutEditor->addWidget(txtEditor);
 
100
    layoutEditor->addWidget(searchWidget);
 
101
 
 
102
    QWidget *editor = new QWidget();
 
103
    editor->setLayout(layoutEditor);
 
104
 
 
105
    splitter = new QSplitter(this);
 
106
    splitter->setOrientation(Qt::Vertical);
 
107
    splitter->addWidget(editor);
 
108
 
 
109
    QSettings settings;
 
110
    trvPyLint = new QTreeWidget(this);
 
111
    trvPyLint->setHeaderHidden(true);
 
112
    trvPyLint->setMouseTracking(true);
 
113
    trvPyLint->setColumnCount(1);
 
114
    trvPyLint->setIndentation(12);
 
115
    trvPyLint->setMaximumHeight(150);
 
116
    connect(trvPyLint, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(doHighlightLine(QTreeWidgetItem *, int)));
 
117
 
 
118
    splitter->addWidget(trvPyLint);
 
119
 
 
120
    // contents
 
121
    QHBoxLayout *layout = new QHBoxLayout();
 
122
    layout->setMargin(1);
 
123
    layout->addWidget(splitter);
 
124
 
 
125
    setLayout(layout);
 
126
 
 
127
    restoreGeometry(settings.value("PythonEditorWidget/Geometry", saveGeometry()).toByteArray());
 
128
    splitter->restoreState(settings.value("PythonEditorWidget/SplitterState").toByteArray());
 
129
    splitter->restoreGeometry(settings.value("PythonEditorWidget/SplitterGeometry").toByteArray());
 
130
    txtEditor->resize(txtEditor->height(), settings.value("PythonEditorWidget/EditorHeight").toInt());
 
131
}
 
132
 
 
133
void PythonEditorWidget::pyLintAnalyse()
 
134
{
 
135
    trvPyLint->clear();
 
136
 
 
137
    QProcess processPyLint;
 
138
    processPyLint.setStandardOutputFile(tempProblemFileName() + ".pylint.out");
 
139
    processPyLint.setStandardErrorFile(tempProblemFileName() + ".pylint.err");
 
140
    connect(&processPyLint, SIGNAL(finished(int)), this, SLOT(pyLintAnalyseStopped(int)));
 
141
 
 
142
#ifdef Q_WS_X11
 
143
    QString pylintBinary = datadir() + "/resources/python/pylint_lab";
 
144
#endif
 
145
#ifdef Q_WS_WIN
 
146
    QString pylintBinary = datadir() + "/resources/python/pylint_lab.bat";
 
147
#endif
 
148
 
 
149
    QString test = txtEditor->toPlainText();
 
150
    writeStringContent(tempProblemFileName() + ".pylint.py", &test);
 
151
 
 
152
    QStringList arguments;
 
153
    arguments << "-i" << "yes" << tempProblemFileName() + ".pylint.py";
 
154
 
 
155
    processPyLint.setWorkingDirectory(datadir() + "/resources/python");
 
156
    processPyLint.start(pylintBinary, arguments);
 
157
 
 
158
    if (!processPyLint.waitForStarted())
 
159
    {
 
160
        qDebug() << "Could not start PyLint: " << processPyLint.errorString();
 
161
 
 
162
        processPyLint.kill();
 
163
        return;
 
164
    }
 
165
 
 
166
    while (!processPyLint.waitForFinished(-1)) {}
 
167
}
 
168
 
 
169
void PythonEditorWidget::pyLintAnalyseStopped(int exitCode)
 
170
{
 
171
    // QString output = readFileContent(tempProblemFileName() + ".pylint.out");
 
172
    // qDebug() << output;
 
173
 
 
174
    QTreeWidgetItem *itemConvention = new QTreeWidgetItem(trvPyLint);
 
175
    itemConvention->setText(0, tr("Convention"));
 
176
    itemConvention->setIcon(0, icon("check-convention"));
 
177
    QTreeWidgetItem *itemWarning = new QTreeWidgetItem(trvPyLint);
 
178
    itemWarning->setText(0, tr("Warning"));
 
179
    itemWarning->setIcon(0, icon("check-warning"));
 
180
    itemWarning->setExpanded(true);
 
181
    QTreeWidgetItem *itemError = new QTreeWidgetItem(trvPyLint);
 
182
    itemError->setText(0, tr("Error"));
 
183
    itemError->setIcon(0, icon("check-error"));
 
184
    itemError->setExpanded(true);
 
185
 
 
186
    QFile fileOutput(tempProblemFileName() + ".pylint.out");
 
187
    if (!fileOutput.open(QIODevice::ReadOnly | QIODevice::Text))
 
188
    {
 
189
        qDebug() << tr("Could not read PyLint output.");
 
190
        return;
 
191
    }
 
192
    QTextStream inOutput(&fileOutput);
 
193
 
 
194
    QString line_str;
 
195
    do
 
196
    {
 
197
        line_str = inOutput.readLine();
 
198
 
 
199
        if (!line_str.isEmpty())
 
200
        {
 
201
            if (line_str.startsWith("C") || line_str.startsWith("W") || line_str.startsWith("E"))
 
202
            {
 
203
                QString type;
 
204
                QString typeFamily;
 
205
                QString line_column;
 
206
                int line;
 
207
                QString message;
 
208
 
 
209
                QStringList list = line_str.split(":");
 
210
                if (list.count() == 3)
 
211
                {
 
212
                    type = list[0];
 
213
                    line_column = list[1];
 
214
                    line = line_column.split(",").at(0).toInt();
 
215
                    message = list[2];
 
216
 
 
217
                    QTreeWidgetItem *item;
 
218
                    if (type.startsWith("C"))
 
219
                    {
 
220
                        typeFamily = tr("Convention");
 
221
                        item = new QTreeWidgetItem(itemConvention);
 
222
                    }
 
223
                    else if (type.startsWith("W"))
 
224
                    {
 
225
                        typeFamily = tr("Warning");
 
226
                        item = new QTreeWidgetItem(itemWarning);
 
227
                    }
 
228
                    else
 
229
                    {
 
230
                        typeFamily = tr("Error");
 
231
                        item = new QTreeWidgetItem(itemError);
 
232
                    }
 
233
 
 
234
                    item->setText(0, QString("%1: %2").
 
235
                                  arg(line_column).
 
236
                                  arg(message));
 
237
 
 
238
                    item->setData(0, Qt::UserRole, line);
 
239
                }
 
240
            }
 
241
        }
 
242
    } while (!line_str.isNull());
 
243
 
 
244
    txtEditor->repaint();
 
245
 
 
246
    // QString error = readFileContent(tempProblemFileName() + ".pylint.err");
 
247
    // qDebug() << error;
 
248
}
 
249
 
 
250
void PythonEditorWidget::pyFlakesAnalyse()
 
251
{
 
252
    if (isVisible() && !pythonEngine->isScriptRunning())
 
253
    {
 
254
        QString fn = tempProblemFileName() + ".pyflakes_str.py";
 
255
        QString str = txtEditor->toPlainText();
 
256
        writeStringContent(fn, &str);
 
257
 
 
258
        QStringList messages = pythonEngine->codePyFlakes(fn);
 
259
 
 
260
        txtEditor->errorMessagesPyFlakes.clear();
 
261
        foreach (QString line, messages)
 
262
        {
 
263
            if (!line.isEmpty())
 
264
            {
 
265
                int number;
 
266
                QString message;
 
267
 
 
268
                QStringList list = line.split(":");
 
269
                if (list.count() == 3)
 
270
                {
 
271
                    number = list[1].toInt();
 
272
                    message = list[2];
 
273
 
 
274
                    txtEditor->errorMessagesPyFlakes[number] = message;
 
275
                }
 
276
            }
 
277
        }
 
278
 
 
279
        QFile::remove(fn);
 
280
 
 
281
        txtEditor->repaint();
 
282
    }
 
283
}
 
284
 
 
285
void PythonEditorWidget::doHighlightLine(QTreeWidgetItem *item, int role)
 
286
{
 
287
    if (item)
 
288
    {
 
289
        int line = item->data(0, Qt::UserRole).value<int>();
 
290
 
 
291
        txtEditor->gotoLine(line, true);
 
292
    }
 
293
}
 
294
 
 
295
// ***********************************************************************************************************
 
296
 
 
297
PythonEditorDialog::PythonEditorDialog(PythonEngine *pythonEngine, QStringList args, QWidget *parent)
 
298
    : QMainWindow(parent), pythonEngine(pythonEngine)
 
299
{
 
300
    setWindowIcon(icon("pythonlab"));
 
301
 
 
302
    createStatusBar();
 
303
    createActions();
 
304
    createViews();
 
305
    createControls();
 
306
 
 
307
    QSettings settings;
 
308
    fileBrowser->setDir(settings.value("PythonEditorDialog/WorkDir", datadir()).value<QString>());
 
309
    fileBrowser->refresh();
 
310
 
 
311
    connect(actRunPython, SIGNAL(triggered()), this, SLOT(doRunPython()));
 
312
    connect(actStopPython, SIGNAL(triggered()), this, SLOT(doStopScript()));
 
313
    connect(actReplaceTabsWithSpaces, SIGNAL(triggered()), this, SLOT(doReplaceTabsWithSpaces()));
 
314
    connect(actCheckPyLint, SIGNAL(triggered()), this, SLOT(doPyLintPython()));
 
315
 
 
316
    connect(pythonEngine, SIGNAL(startedScript()), this, SLOT(doStartedScript()));
 
317
    connect(pythonEngine, SIGNAL(executedScript()), this, SLOT(doExecutedScript()));
 
318
 
 
319
    // macx
 
320
    setUnifiedTitleAndToolBarOnMac(true);
 
321
 
 
322
    // parameters
 
323
    for (int i = 1; i < args.count(); i++)
 
324
    {
 
325
        QString fileName =
 
326
                QFile::exists(args[i]) ? args[i] : QApplication::applicationDirPath() + QDir::separator() + args[i];
 
327
 
 
328
        if (QFile::exists(fileName))
 
329
        {
 
330
            QFileInfo fileInfo(fileName);
 
331
            doFileOpen(fileInfo.absoluteFilePath());
 
332
        }
 
333
    }
 
334
 
 
335
    setAcceptDrops(true);
 
336
 
 
337
    restoreGeometry(settings.value("PythonEditorDialog/Geometry", saveGeometry()).toByteArray());
 
338
    m_recentFiles = settings.value("PythonEditorDialog/RecentFiles").value<QStringList>();
 
339
    restoreState(settings.value("PythonEditorDialog/State", saveState()).toByteArray());
 
340
 
 
341
    // set recent files
 
342
    setRecentFiles();
 
343
}
 
344
 
 
345
PythonEditorDialog::~PythonEditorDialog()
 
346
{
 
347
    QSettings settings;
 
348
    settings.setValue("PythonEditorDialog/Geometry", saveGeometry());
 
349
    settings.setValue("PythonEditorDialog/State", saveState());
 
350
    settings.setValue("PythonEditorDialog/RecentFiles", m_recentFiles);
 
351
}
 
352
 
 
353
void PythonEditorDialog::closeEvent(QCloseEvent *event)
 
354
{
 
355
    // check script editor
 
356
    closeTabs();
 
357
 
 
358
    if (!isScriptModified())
 
359
        event->accept();
 
360
    else
 
361
    {
 
362
        event->ignore();
 
363
        // show script editor
 
364
        if (isScriptModified())
 
365
            show();
 
366
    }
 
367
}
 
368
 
 
369
void PythonEditorDialog::dragEnterEvent(QDragEnterEvent *event)
 
370
{
 
371
    event->acceptProposedAction();
 
372
}
 
373
 
 
374
void PythonEditorDialog::dragLeaveEvent(QDragLeaveEvent *event)
 
375
{
 
376
    event->accept();
 
377
}
 
378
 
 
379
void PythonEditorDialog::dropEvent(QDropEvent *event)
 
380
{
 
381
    if (event->mimeData()->hasUrls())
 
382
    {
 
383
        QString fileName = QUrl(event->mimeData()->urls().at(0)).toLocalFile().trimmed();
 
384
        if (QFile::exists(fileName))
 
385
        {
 
386
            QFileInfo fileInfo(fileName);
 
387
            doFileOpen(fileInfo.absoluteFilePath());
 
388
 
 
389
            event->acceptProposedAction();
 
390
        }
 
391
    }
 
392
}
 
393
 
 
394
void PythonEditorDialog::showDialog()
 
395
{
 
396
    show();
 
397
    activateWindow();
 
398
    txtEditor->setFocus();
 
399
}
 
400
 
 
401
void PythonEditorDialog::createActions()
 
402
{
 
403
    actFileNew = new QAction(icon("document-new"), tr("&New"), this);
 
404
    actFileNew->setShortcuts(QKeySequence::AddTab);
 
405
    connect(actFileNew, SIGNAL(triggered()), this, SLOT(doFileNew()));
 
406
 
 
407
    actFileOpen = new QAction(icon("document-open"), tr("&Open..."), this);
 
408
    actFileOpen->setShortcuts(QKeySequence::Open);
 
409
    connect(actFileOpen, SIGNAL(triggered()), this, SLOT(doFileOpen()));
 
410
 
 
411
    actFileSave = new QAction(icon("document-save"), tr("&Save"), this);
 
412
    actFileSave->setShortcuts(QKeySequence::Save);
 
413
    connect(actFileSave, SIGNAL(triggered()), this, SLOT(doFileSave()));
 
414
 
 
415
    actFileSaveAs = new QAction(icon("document-save-as"), tr("Save &as..."), this);
 
416
    actFileSaveAs->setShortcuts(QKeySequence::SaveAs);
 
417
    connect(actFileSaveAs, SIGNAL(triggered()), this, SLOT(doFileSaveAs()));
 
418
 
 
419
    actFileSaveConsoleAs = new QAction(icon(""), tr("Save console output as..."), this);
 
420
    connect(actFileSaveConsoleAs, SIGNAL(triggered()), this, SLOT(doFileSaveConsoleAs()));
 
421
 
 
422
    actFileOpenRecentGroup = new QActionGroup(this);
 
423
    connect(actFileOpenRecentGroup, SIGNAL(triggered(QAction *)), this, SLOT(doFileOpenRecent(QAction *)));
 
424
 
 
425
    actFileClose = new QAction(icon(""), tr("&Close"), this);
 
426
    actFileClose->setShortcuts(QKeySequence::Close);
 
427
    connect(actFileClose, SIGNAL(triggered()), this, SLOT(doFileClose()));
 
428
 
 
429
    actFilePrint = new QAction(icon(""), tr("&Print"), this);
 
430
    actFilePrint->setShortcuts(QKeySequence::Print);
 
431
    connect(actFilePrint, SIGNAL(triggered()), this, SLOT(doFilePrint()));
 
432
 
 
433
    actUndo = new QAction(icon("edit-undo"), tr("&Undo"), this);
 
434
    actUndo->setShortcut(QKeySequence::Undo);
 
435
 
 
436
    actRedo = new QAction(icon("edit-redo"), tr("&Redo"), this);
 
437
    actRedo->setShortcut(QKeySequence::Redo);
 
438
 
 
439
    actCut = new QAction(icon("edit-cut"), tr("Cu&t"), this);
 
440
    actCut->setShortcut(QKeySequence::Cut);
 
441
    actCut->setEnabled(false);
 
442
 
 
443
    actCopy = new QAction(icon("edit-copy"), tr("&Copy"), this);
 
444
    actCopy->setShortcut(QKeySequence::Copy);
 
445
    actCopy->setEnabled(false);
 
446
 
 
447
    actPaste = new QAction(icon("edit-paste"), tr("&Paste"), this);
 
448
    actPaste->setShortcut(QKeySequence::Paste);
 
449
 
 
450
    actFind = new QAction(icon("edit-find"), tr("&Find"), this);
 
451
    actFind->setShortcut(QKeySequence::Find);
 
452
    connect(actFind, SIGNAL(triggered()), this, SLOT(doFind()));
 
453
 
 
454
    actFindNext = new QAction(icon("edit-find"), tr("Find &next"), this);
 
455
    actFindNext->setShortcut(QKeySequence::FindNext);
 
456
    connect(actFindNext, SIGNAL(triggered()), this, SLOT(doFindNext()));
 
457
 
 
458
    actReplace = new QAction(icon("edit-find-replace"), tr("Replace"), this);
 
459
    actReplace->setShortcut(QKeySequence::Replace);
 
460
    connect(actReplace, SIGNAL(triggered()), this, SLOT(doReplace()));
 
461
 
 
462
    actReplace = new QAction(icon("edit-find-replace"), tr("Replace"), this);
 
463
    actReplace->setShortcut(QKeySequence::Replace);
 
464
    connect(actReplace, SIGNAL(triggered()), this, SLOT(doReplace()));
 
465
 
 
466
    actIndentSelection = new QAction(icon(""), tr("Indent"), this);
 
467
    actIndentSelection->setShortcut(tr("Ctrl+>"));
 
468
    actUnindentSelection = new QAction(icon(""), tr("Unindent"), this);
 
469
    actUnindentSelection->setShortcut(tr("Ctrl+<"));
 
470
 
 
471
    actCommentAndUncommentSelection = new QAction(icon(""), tr("Toggle comment selection"), this);
 
472
    actCommentAndUncommentSelection->setShortcut(tr("Ctrl+/"));
 
473
 
 
474
    actGotoLine = new QAction(icon(""), tr("Goto line"), this);
 
475
    actGotoLine->setShortcut(tr("Alt+G"));
 
476
 
 
477
    actRunPython = new QAction(icon("run"), tr("&Run Python script"), this);
 
478
    actRunPython->setShortcut(QKeySequence(tr("Ctrl+R")));
 
479
 
 
480
    actStopPython = new QAction(icon("stop"), tr("Stop Python script"), this);
 
481
    actStopPython->setEnabled(false);
 
482
 
 
483
    actReplaceTabsWithSpaces = new QAction(icon(""), tr("Replace tabs with spaces"), this);
 
484
 
 
485
    QSettings settings;
 
486
    actCheckPyLint = new QAction(icon("checkbox"), tr("&Check Python script (PyLint)"), this);
 
487
    actCheckPyLint->setEnabled(settings.value("PythonEditorWidget/EnablePyLint", true).toBool());
 
488
    actCheckPyLint->setShortcut(QKeySequence(tr("Alt+C")));
 
489
 
 
490
    actOptionsEnablePyFlakes = new QAction(icon(""), tr("PyFlakes enabled"), this);
 
491
    actOptionsEnablePyFlakes->setCheckable(true);
 
492
    actOptionsEnablePyFlakes->setChecked(settings.value("PythonEditorWidget/EnablePyFlakes", true).toBool());
 
493
    connect(actOptionsEnablePyFlakes, SIGNAL(triggered()), this, SLOT(doOptionsEnablePyFlakes()));
 
494
 
 
495
    actOptionsEnablePyLint = new QAction(icon(""), tr("PyLint enabled"), this);
 
496
    actOptionsEnablePyLint->setCheckable(true);
 
497
    actOptionsEnablePyLint->setChecked(settings.value("PythonEditorWidget/EnablePyLint", true).toBool());
 
498
    connect(actOptionsEnablePyLint, SIGNAL(triggered()), this, SLOT(doOptionsEnablePyLint()));
 
499
 
 
500
    actOptionsPrintStacktrace = new QAction(icon(""), tr("Print stacktrace"), this);
 
501
    actOptionsPrintStacktrace->setCheckable(true);
 
502
    actOptionsPrintStacktrace->setChecked(settings.value("PythonEditorWidget/PrintStacktrace", true).toBool());
 
503
    connect(actOptionsPrintStacktrace, SIGNAL(triggered()), this, SLOT(doOptionsPrintStacktrace()));
 
504
 
 
505
    actOptionsEnableUseProfiler = new QAction(icon(""), tr("Profiler enabled"), this);
 
506
    actOptionsEnableUseProfiler->setCheckable(true);
 
507
    actOptionsEnableUseProfiler->setChecked(settings.value("PythonEditorWidget/UseProfiler", false).toBool());
 
508
    connect(actOptionsEnableUseProfiler, SIGNAL(triggered()), this, SLOT(doOptionsEnableUseProfiler()));
 
509
 
 
510
    actExit = new QAction(icon("application-exit"), tr("E&xit"), this);
 
511
    actExit->setShortcut(tr("Ctrl+Q"));
 
512
    connect(actExit, SIGNAL(triggered()), this, SLOT(close()));
 
513
 
 
514
    actHelp = new QAction(icon("help-contents"), tr("&Help"), this);
 
515
    actHelp->setShortcut(QKeySequence::HelpContents);
 
516
    actHelp->setEnabled(false);
 
517
    connect(actHelp, SIGNAL(triggered()), this, SLOT(doHelp()));
 
518
 
 
519
    actHelpKeywordList = new QAction(icon("help-contents"), tr("&Keyword List"), this);
 
520
    actHelpKeywordList->setShortcut(QKeySequence::HelpContents);
 
521
    actHelpKeywordList->setEnabled(false);
 
522
    connect(actHelpKeywordList, SIGNAL(triggered()), this, SLOT(doHelpKeywordList()));
 
523
 
 
524
    actAbout = new QAction(icon("about"), tr("About &PythonLab"), this);
 
525
    actAbout->setMenuRole(QAction::AboutRole);
 
526
    connect(actAbout, SIGNAL(triggered()), this, SLOT(doAbout()));
 
527
 
 
528
    actAboutQt = new QAction(icon("help-about"), tr("About &Qt"), this);
 
529
    actAboutQt->setMenuRole(QAction::AboutQtRole);
 
530
    connect(actAboutQt, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
 
531
}
 
532
 
 
533
void PythonEditorDialog::createControls()
 
534
{
 
535
    mnuRecentFiles = new QMenu(tr("&Recent files"), this);
 
536
 
 
537
    mnuFile = menuBar()->addMenu(tr("&File"));
 
538
    mnuFile->addAction(actFileNew);
 
539
    mnuFile->addAction(actFileOpen);
 
540
    mnuFile->addAction(actFileSave);
 
541
    mnuFile->addAction(actFileSaveAs);
 
542
    mnuFile->addAction(actFileSaveConsoleAs);
 
543
    mnuFile->addSeparator();
 
544
    mnuFile->addMenu(mnuRecentFiles);
 
545
    mnuFile->addAction(actFileClose);
 
546
    mnuFile->addSeparator();
 
547
    mnuFile->addAction(actFilePrint);
 
548
    mnuFile->addSeparator();
 
549
    mnuFile->addAction(actExit);
 
550
 
 
551
    mnuEdit = menuBar()->addMenu(tr("&Edit"));
 
552
    mnuEdit->addAction(actUndo);
 
553
    mnuEdit->addAction(actRedo);
 
554
    mnuEdit->addSeparator();
 
555
    mnuEdit->addAction(actCut);
 
556
    mnuEdit->addAction(actCopy);
 
557
    mnuEdit->addAction(actPaste);
 
558
    mnuEdit->addSeparator();
 
559
    mnuEdit->addAction(actFind);
 
560
    mnuEdit->addAction(actFindNext);
 
561
    mnuEdit->addAction(actReplace);
 
562
    mnuEdit->addSeparator();
 
563
    mnuEdit->addAction(actIndentSelection);
 
564
    mnuEdit->addAction(actUnindentSelection);
 
565
    mnuEdit->addSeparator();
 
566
    mnuEdit->addAction(actCommentAndUncommentSelection);
 
567
    mnuEdit->addSeparator();
 
568
    mnuEdit->addAction(actGotoLine);
 
569
 
 
570
    mnuTools = menuBar()->addMenu(tr("&Tools"));
 
571
    mnuTools->addAction(actRunPython);
 
572
    mnuTools->addAction(actStopPython);
 
573
    mnuTools->addAction(actCheckPyLint);
 
574
    mnuTools->addSeparator();
 
575
    mnuTools->addAction(actReplaceTabsWithSpaces);
 
576
 
 
577
    mnuOptions = menuBar()->addMenu(tr("&Options"));
 
578
    mnuOptions->addAction(actOptionsEnablePyFlakes);
 
579
    mnuOptions->addAction(actOptionsEnablePyLint);
 
580
    mnuOptions->addSeparator();
 
581
    mnuOptions->addAction(actOptionsPrintStacktrace);
 
582
    mnuOptions->addSeparator();
 
583
    mnuOptions->addAction(actOptionsEnableUseProfiler);
 
584
 
 
585
    mnuHelp = menuBar()->addMenu(tr("&Help"));
 
586
    // mnuHelp->addAction(actHelp);
 
587
    // mnuHelp->addAction(actHelpKeywordList);
 
588
    mnuHelp->addAction(actAbout);   // will be added to "PythonLab" MacOSX menu
 
589
    mnuHelp->addAction(actAboutQt); // will be added to "PythonLab" MacOSX menu
 
590
 
 
591
#ifdef Q_WS_MAC
 
592
    int iconHeight = 24;
 
593
#endif
 
594
 
 
595
    tlbFile = addToolBar(tr("File"));
 
596
#ifdef Q_WS_MAC
 
597
    tlbFile->setFixedHeight(iconHeight);
 
598
    tlbFile->setStyleSheet("QToolButton { border: 0px; padding: 0px; margin: 0px; }");
 
599
#endif
 
600
    tlbFile->setObjectName("File");
 
601
    tlbFile->addAction(actFileNew);
 
602
    tlbFile->addAction(actFileOpen);
 
603
    tlbFile->addAction(actFileSave);
 
604
 
 
605
    tlbEdit = addToolBar(tr("Edit"));
 
606
#ifdef Q_WS_MAC
 
607
    tlbEdit->setFixedHeight(iconHeight);
 
608
    tlbEdit->setStyleSheet("QToolButton { border: 0px; padding: 0px; margin: 0px; }");
 
609
#endif
 
610
    tlbEdit->setObjectName("Edit");
 
611
    tlbEdit->addAction(actUndo);
 
612
    tlbEdit->addAction(actRedo);
 
613
    tlbEdit->addSeparator();
 
614
    tlbEdit->addAction(actCut);
 
615
    tlbEdit->addAction(actCopy);
 
616
    tlbEdit->addAction(actPaste);
 
617
 
 
618
    tlbRun = addToolBar(tr("Run"));
 
619
#ifdef Q_WS_MAC
 
620
    tlbRun->setFixedHeight(iconHeight);
 
621
    tlbRun->setStyleSheet("QToolButton { border: 0px; padding: 0px; margin: 0px; }");
 
622
#endif
 
623
    tlbRun->setObjectName("Run");
 
624
    tlbRun->addAction(actRunPython);
 
625
    tlbRun->addAction(actStopPython);
 
626
 
 
627
    tlbTools = addToolBar(tr("Tools"));
 
628
#ifdef Q_WS_MAC
 
629
    tlbTools->setFixedHeight(iconHeight);
 
630
    tlbTools->setStyleSheet("QToolButton { border: 0px; padding: 0px; margin: 0px; }");
 
631
#endif
 
632
    tlbTools->setObjectName("Tools");
 
633
    tlbTools->addAction(actCheckPyLint);
 
634
 
 
635
    // path
 
636
    QLabel *lblPath = new QLabel();
 
637
    lblPath->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
 
638
 
 
639
    QPushButton *btnPath = new QPushButton(icon("three-dots"), "");
 
640
    btnPath->setMaximumSize(btnPath->sizeHint());
 
641
 
 
642
    connect(btnPath, SIGNAL(clicked()), this, SLOT(doPathChangeDir()));
 
643
    connect(fileBrowser, SIGNAL(directoryChanged(QString)), lblPath, SLOT(setText(QString)));
 
644
 
 
645
    QToolBar *tlbPath = addToolBar(tr("Path"));
 
646
#ifdef Q_WS_MAC
 
647
    tlbPath->setFixedHeight(iconHeight);
 
648
    tlbPath->setStyleSheet("QToolButton { border: 0px; padding: 0px; margin: 0px; }");
 
649
#endif
 
650
    tlbPath->setObjectName("Path");
 
651
    tlbPath->addWidget(new QLabel(tr("Working directory: ")));
 
652
    tlbPath->addWidget(lblPath);
 
653
    tlbPath->addWidget(btnPath);
 
654
 
 
655
    // contents
 
656
    tabWidget = new QTabWidget(this);
 
657
    tabWidget->setDocumentMode(true);
 
658
    tabWidget->setMovable(true);
 
659
 
 
660
    QToolButton *btnNewTab = new QToolButton(this);
 
661
    btnNewTab->setAutoRaise(true);
 
662
    btnNewTab->setToolTip(tr("Add new document"));
 
663
    btnNewTab->setIcon(icon("tabadd"));
 
664
    btnNewTab->setToolButtonStyle(Qt::ToolButtonIconOnly);
 
665
    tabWidget->setCornerWidget(btnNewTab, Qt::TopLeftCorner);
 
666
    connect(btnNewTab, SIGNAL(clicked()), this, SLOT(doFileNew()));
 
667
 
 
668
    connect(tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(doCloseTab(int)));
 
669
    connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(doCurrentPageChanged(int)));
 
670
 
 
671
    // main widget
 
672
    QHBoxLayout *layout = new QHBoxLayout();
 
673
    layout->addWidget(tabWidget);
 
674
 
 
675
    QWidget *widget = new QWidget(this);
 
676
    widget->setLayout(layout);
 
677
 
 
678
    setCentralWidget(widget);
 
679
 
 
680
    connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(doDataChanged()));
 
681
 
 
682
    // new file
 
683
    doFileNew();
 
684
    // recent files
 
685
    setRecentFiles();
 
686
}
 
687
 
 
688
void PythonEditorDialog::createViews()
 
689
{
 
690
    // file browser
 
691
    fileBrowser = new FileBrowser(this);
 
692
    fileBrowser->setNameFilter("*.py");
 
693
    connect(fileBrowser, SIGNAL(fileItemDoubleClick(QString)), this, SLOT(doFileItemDoubleClick(QString)));
 
694
 
 
695
    QVBoxLayout *layout = new QVBoxLayout();
 
696
    layout->addWidget(fileBrowser);
 
697
    layout->setContentsMargins(0, 0, 0, 7);
 
698
 
 
699
    QWidget *widget = new QWidget(this);
 
700
    widget->setLayout(layout);
 
701
 
 
702
    fileBrowserView = new QDockWidget(tr("File browser"), this);
 
703
    fileBrowserView->setObjectName("ScriptEditorFileBrowserView");
 
704
    fileBrowserView->setWidget(widget);
 
705
    fileBrowserView->setAllowedAreas(Qt::AllDockWidgetAreas);
 
706
    addDockWidget(Qt::LeftDockWidgetArea, fileBrowserView);
 
707
 
 
708
    consoleView = new PythonScriptingConsoleView(pythonEngine, this);
 
709
    consoleView->setAllowedAreas(Qt::AllDockWidgetAreas);
 
710
    addDockWidget(Qt::RightDockWidgetArea, consoleView);
 
711
 
 
712
    consoleHistoryView = new PythonScriptingHistoryView(consoleView->console(), this);
 
713
    consoleHistoryView->setAllowedAreas(Qt::AllDockWidgetAreas);
 
714
    addDockWidget(Qt::LeftDockWidgetArea, consoleHistoryView);
 
715
 
 
716
    variablesView = new PythonBrowserView(pythonEngine, consoleView->console(), this);
 
717
    variablesView->setAllowedAreas(Qt::AllDockWidgetAreas);
 
718
    addDockWidget(Qt::LeftDockWidgetArea, variablesView);
 
719
}
 
720
 
 
721
void PythonEditorDialog::createStatusBar()
 
722
{
 
723
    lblCurrentPosition = new QLabel(statusBar());
 
724
 
 
725
    statusBar()->showMessage(tr("Ready"));
 
726
    statusBar()->addPermanentWidget(lblCurrentPosition);
 
727
}
 
728
 
 
729
void PythonEditorDialog::doRunPython()
 
730
{
 
731
    if (pythonEngine->isScriptRunning())
 
732
        return;
 
733
 
 
734
    if (!scriptEditorWidget()->fileName().isEmpty())
 
735
        fileBrowser->setDir(QFileInfo(scriptEditorWidget()->fileName()).absolutePath());
 
736
 
 
737
    scriptPrepare();
 
738
 
 
739
    // connect stdout and set current path
 
740
    consoleView->console()->connectStdOut(QFile::exists(scriptEditorWidget()->fileName()) ?
 
741
                                              QFileInfo(scriptEditorWidget()->fileName()).absolutePath() : "");
 
742
 
 
743
    // run script
 
744
    QTime time;
 
745
    time.start();
 
746
    consoleView->console()->consoleMessage(tr("Run script: %1\n").arg(tabWidget->tabText(tabWidget->currentIndex()).replace("* ", "")), Qt::gray);
 
747
 
 
748
    bool successfulRun = false;
 
749
    if (txtEditor->textCursor().hasSelection())
 
750
    {
 
751
        QFileInfo fileInfo(scriptEditorWidget()->fileName());
 
752
        successfulRun = pythonEngine->runScript(txtEditor->textCursor().selectedText().replace(0x2029, "\n"),
 
753
                                                fileInfo.exists() ? fileInfo.absoluteFilePath() : "");
 
754
    }
 
755
    else if (scriptEditorWidget()->fileName().isEmpty())
 
756
    {
 
757
        successfulRun = pythonEngine->runScript(txtEditor->toPlainText());
 
758
    }
 
759
    else
 
760
    {
 
761
        if (!scriptEditorWidget()->fileName().isEmpty() &&
 
762
                QFile::exists(scriptEditorWidget()->fileName()))
 
763
            doFileSave();
 
764
 
 
765
        // set profiler
 
766
        QSettings settings;
 
767
        bool useProfiler = settings.value("PythonEditorWidget/UseProfiler", false).toBool();
 
768
 
 
769
        // set profiler
 
770
        pythonEngine->useProfiler(useProfiler);
 
771
        successfulRun = pythonEngine->runScript(txtEditor->toPlainText(),
 
772
                                                QFileInfo(scriptEditorWidget()->fileName()).absoluteFilePath());
 
773
        // reset profiler
 
774
        pythonEngine->useProfiler(!useProfiler);
 
775
 
 
776
        // set profiled
 
777
        txtEditor->setProfiled(useProfiler);
 
778
 
 
779
        txtEditor->setProfilerAccumulatedLines(currentPythonEngine()->profilerAccumulatedLines());
 
780
        txtEditor->setProfilerAccumulatedTimes(currentPythonEngine()->profilerAccumulatedTimes());
 
781
 
 
782
        txtEditor->setProfilerMaxAccumulatedLine(currentPythonEngine()->profilerMaxAccumulatedLine());
 
783
        txtEditor->setProfilerMaxAccumulatedTime(currentPythonEngine()->profilerMaxAccumulatedTime());
 
784
        txtEditor->setProfilerMaxAccumulatedCallLine(currentPythonEngine()->profilerMaxAccumulatedCallLine());
 
785
        txtEditor->setProfilerMaxAccumulatedCall(currentPythonEngine()->profilerMaxAccumulatedCall());
 
786
 
 
787
        // refresh
 
788
        txtEditor->updateLineNumberAreaWidth();
 
789
    }
 
790
 
 
791
    // run script
 
792
    consoleView->console()->consoleMessage(tr("Finish script: %1\n").arg(milisecondsToTime(time.elapsed()).toString("hh:mm:ss.zzz")), Qt::gray);
 
793
 
 
794
    // disconnect stdout
 
795
    consoleView->console()->disconnectStdOut();
 
796
 
 
797
    if (!successfulRun)
 
798
    {
 
799
        // parse error
 
800
        ErrorResult result = pythonEngine->parseError();
 
801
 
 
802
        consoleView->console()->stdErr(result.error());
 
803
 
 
804
        QSettings settings;
 
805
        if (settings.value("PythonEditorWidget/PrintStacktrace", true).toBool())
 
806
        {
 
807
            consoleView->console()->stdErr("\nStacktrace:\n");
 
808
            consoleView->console()->stdErr(result.traceback());
 
809
        }
 
810
 
 
811
        if (!txtEditor->textCursor().hasSelection() && result.line() >= 0)
 
812
            txtEditor->gotoLine(result.line(), true);
 
813
    }
 
814
    consoleView->console()->appendCommandPrompt();
 
815
 
 
816
    scriptFinish();
 
817
}
 
818
 
 
819
void PythonEditorDialog::doStopScript()
 
820
{
 
821
    actStopPython->setEnabled(false);
 
822
 
 
823
    // run script
 
824
    consoleView->console()->consoleMessage(tr("\nScript is being aborted.\n"), Qt::blue);
 
825
 
 
826
    currentPythonEngine()->abortScript();
 
827
    QApplication::processEvents();
 
828
}
 
829
 
 
830
void PythonEditorDialog::doStartedScript()
 
831
{
 
832
    // disable controls
 
833
    setEnabledControls(false);
 
834
    scriptEditorWidget()->setCursor(Qt::BusyCursor);
 
835
 
 
836
    actRunPython->setEnabled(false);
 
837
    actStopPython->setEnabled(true);
 
838
 
 
839
    // QApplication::processEvents();
 
840
}
 
841
 
 
842
void PythonEditorDialog::doExecutedScript()
 
843
{
 
844
    // enable controls
 
845
    setEnabledControls(true);
 
846
    scriptEditorWidget()->setCursor(Qt::ArrowCursor);
 
847
 
 
848
    actRunPython->setEnabled(true);
 
849
    actStopPython->setEnabled(false);
 
850
 
 
851
    if (txtEditor->isVisible())
 
852
    {
 
853
        txtEditor->setFocus();
 
854
    }
 
855
}
 
856
 
 
857
void PythonEditorDialog::setEnabledControls(bool state)
 
858
{
 
859
    tlbFile->setEnabled(state);
 
860
    tlbEdit->setEnabled(state);
 
861
    tlbTools->setEnabled(state);
 
862
 
 
863
    txtEditor->setEnabled(state);
 
864
    consoleView->setEnabled(state);
 
865
    consoleHistoryView->setEnabled(state);
 
866
    consoleHistoryView->setEnabled(state);
 
867
    variablesView->setEnabled(state);
 
868
    fileBrowserView->setEnabled(state);
 
869
 
 
870
    menuBar()->setEnabled(state);
 
871
}
 
872
 
 
873
void PythonEditorDialog::doReplaceTabsWithSpaces()
 
874
{
 
875
    txtEditor->replaceTabsWithSpaces();
 
876
}
 
877
 
 
878
void PythonEditorDialog::doPyLintPython()
 
879
{
 
880
    if (!scriptEditorWidget()->fileName().isEmpty())
 
881
        fileBrowser->setDir(QFileInfo(scriptEditorWidget()->fileName()).absolutePath());
 
882
 
 
883
    // analyse by pylint
 
884
    scriptEditorWidget()->pyLintAnalyse();
 
885
 
 
886
    txtEditor->setFocus();
 
887
}
 
888
 
 
889
void PythonEditorDialog::doOptionsPrintStacktrace()
 
890
{
 
891
    QSettings settings;
 
892
    settings.setValue("PythonEditorWidget/PrintStacktrace", actOptionsPrintStacktrace->isChecked());
 
893
}
 
894
 
 
895
void PythonEditorDialog::doOptionsEnablePyFlakes()
 
896
{
 
897
    QSettings settings;
 
898
    settings.setValue("PythonEditorWidget/EnablePyFlakes", actOptionsEnablePyFlakes->isChecked());
 
899
}
 
900
 
 
901
void PythonEditorDialog::doOptionsEnablePyLint()
 
902
{
 
903
    actCheckPyLint->setEnabled(actOptionsEnablePyLint->isChecked());
 
904
 
 
905
    QSettings settings;
 
906
    settings.setValue("PythonEditorWidget/EnablePyLint", actOptionsEnablePyLint->isChecked());
 
907
}
 
908
 
 
909
void PythonEditorDialog::doOptionsEnableUseProfiler()
 
910
{
 
911
    QSettings settings;
 
912
    settings.setValue("PythonEditorWidget/UseProfiler", actOptionsEnableUseProfiler->isChecked());
 
913
 
 
914
    // refresh
 
915
    txtEditor->setPlainText(txtEditor->toPlainText());
 
916
}
 
917
 
 
918
void PythonEditorDialog::doFileItemDoubleClick(const QString &path)
 
919
{
 
920
    QFileInfo fileInfo(path);
 
921
 
 
922
    QSettings settings;
 
923
    if (QDir(path).exists())
 
924
        settings.setValue("PythonEditorDialog/WorkDir", path);
 
925
    else
 
926
    {
 
927
        settings.setValue("PythonEditorDialog/WorkDir", fileInfo.absolutePath());
 
928
 
 
929
        if (fileInfo.suffix() == "py")
 
930
            doFileOpen(fileInfo.absoluteFilePath());
 
931
    }
 
932
}
 
933
 
 
934
void PythonEditorDialog::doPathChangeDir()
 
935
{
 
936
    QFileDialog::Options options = QFileDialog::DontResolveSymlinks | QFileDialog::ShowDirsOnly;
 
937
    QString directory = QFileDialog::getExistingDirectory(this, tr("Select directory"), fileBrowser->basePath(), options);
 
938
    if (!directory.isEmpty())
 
939
        fileBrowser->setDir(directory);
 
940
}
 
941
 
 
942
void PythonEditorDialog::doFileNew()
 
943
{
 
944
    tabWidget->addTab(new PythonEditorWidget(pythonEngine, this), tr("Untitled"));
 
945
    tabWidget->setCurrentIndex(tabWidget->count()-1);
 
946
    doCurrentPageChanged(tabWidget->count()-1);
 
947
}
 
948
 
 
949
void PythonEditorDialog::doFileOpen(const QString &file)
 
950
{
 
951
    QSettings settings;
 
952
    QString dir = settings.value("PythonEditorDialog/WorkDir").toString();
 
953
 
 
954
    // open dialog
 
955
    QString fileName = file;
 
956
    if (fileName.isEmpty())
 
957
        fileName = QFileDialog::getOpenFileName(this, tr("Open File"), dir, tr("Python scripts (*.py)"));
 
958
 
 
959
    // read text
 
960
    if (!fileName.isEmpty())
 
961
    {
 
962
        QFileInfo fileInfo(fileName);
 
963
        if (fileInfo.suffix() != "py")
 
964
            return;
 
965
 
 
966
        PythonEditorWidget *scriptEditor = scriptEditorWidget();
 
967
 
 
968
        for (int i = 0; i < tabWidget->count(); i++)
 
969
        {
 
970
            PythonEditorWidget *scriptEditorWidgetTmp = dynamic_cast<PythonEditorWidget *>(tabWidget->widget(i));
 
971
            if (scriptEditorWidgetTmp->fileName() == fileName)
 
972
            {
 
973
                tabWidget->setCurrentIndex(i);
 
974
                QMessageBox::information(this, tr("Information"), tr("Script is already opened."));
 
975
                return;
 
976
            }
 
977
        }
 
978
 
 
979
        // check empty document
 
980
        if (!scriptEditor->txtEditor->toPlainText().isEmpty())
 
981
        {
 
982
            doFileNew();
 
983
            // new widget
 
984
            scriptEditor = scriptEditorWidget();
 
985
        }
 
986
 
 
987
        scriptEditor->setFileName(fileName);
 
988
        txtEditor->setPlainText(readFileContent(scriptEditor->fileName()));
 
989
 
 
990
        setRecentFiles();
 
991
 
 
992
        tabWidget->setTabText(tabWidget->currentIndex(), fileInfo.baseName());
 
993
 
 
994
        doCurrentPageChanged(tabWidget->currentIndex());
 
995
 
 
996
        if (fileInfo.absoluteDir() != tempProblemDir() && !fileName.contains("resources/examples"))
 
997
            settings.setValue("PythonEditorDialog/WorkDir", fileInfo.absolutePath());
 
998
    }
 
999
}
 
1000
 
 
1001
void PythonEditorDialog::doFileOpenRecent(QAction *action)
 
1002
{
 
1003
    QString fileName = action->text();
 
1004
    if (QFile::exists(fileName))
 
1005
    {
 
1006
        doFileOpen(fileName);
 
1007
        setRecentFiles();
 
1008
    }
 
1009
}
 
1010
 
 
1011
void PythonEditorDialog::doFileSave()
 
1012
{
 
1013
    QSettings settings;
 
1014
    QString dir = settings.value("PythonEditorDialog/WorkDir").toString();
 
1015
 
 
1016
    // save dialog
 
1017
    if (scriptEditorWidget()->fileName().isEmpty())
 
1018
        scriptEditorWidget()->setFileName(QFileDialog::getSaveFileName(this, tr("Save file"), dir, tr("Python scripts (*.py)")));
 
1019
 
 
1020
    // write text
 
1021
    if (!scriptEditorWidget()->fileName().isEmpty())
 
1022
    {
 
1023
        QFileInfo fileInfo(scriptEditorWidget()->fileName());
 
1024
        if (fileInfo.suffix() != "py")
 
1025
            scriptEditorWidget()->setFileName(scriptEditorWidget()->fileName() + ".py");
 
1026
 
 
1027
        QFile fileName(scriptEditorWidget()->fileName());
 
1028
        if (fileName.open(QFile::WriteOnly | QFile::Text))
 
1029
        {
 
1030
            QTextStream out(&fileName);
 
1031
            out << txtEditor->toPlainText();
 
1032
            fileName.close();
 
1033
 
 
1034
            setRecentFiles();
 
1035
 
 
1036
            tabWidget->setTabText(tabWidget->currentIndex(), fileInfo.baseName());
 
1037
            txtEditor->document()->setModified(false);
 
1038
        }
 
1039
        else
 
1040
        {
 
1041
            // throw AgrosException(tr("File '%1' cannot be saved.").arg(scriptEditorWidget()->fileName));
 
1042
            qDebug() << tr("File '%1' cannot be saved.").arg(scriptEditorWidget()->fileName());
 
1043
        }
 
1044
 
 
1045
        if (fileInfo.absoluteDir() != tempProblemDir())
 
1046
            settings.setValue("PythonEditorDialog/WorkDir", fileInfo.absolutePath());
 
1047
    }
 
1048
}
 
1049
 
 
1050
void PythonEditorDialog::doFileSaveAs()
 
1051
{
 
1052
    QSettings settings;
 
1053
    QString dir = settings.value("PythonEditorDialog/WorkDir").toString();
 
1054
 
 
1055
    QString fileName = QFileDialog::getSaveFileName(this, tr("Save file"), dir, tr("Python scripts (*.py)"));
 
1056
    if (!fileName.isEmpty())
 
1057
    {
 
1058
        if (QFileInfo(fileName).suffix() != "py")
 
1059
            fileName += ".py";
 
1060
 
 
1061
        scriptEditorWidget()->setFileName(fileName);
 
1062
        doFileSave();
 
1063
 
 
1064
        QFileInfo fileInfo(fileName);
 
1065
        if (fileInfo.absoluteDir() != tempProblemDir())
 
1066
            settings.setValue("PythonEditorDialog/WorkDir", fileInfo.absolutePath());
 
1067
    }
 
1068
}
 
1069
 
 
1070
void PythonEditorDialog::doFileSaveConsoleAs()
 
1071
{
 
1072
    QSettings settings;
 
1073
    QString dir = settings.value("PythonEditorDialog/WorkDir").toString();
 
1074
 
 
1075
    QString fileName = QFileDialog::getSaveFileName(this, tr("Save file"), dir, tr("Html files (*.html)"));
 
1076
    if (!fileName.isEmpty())
 
1077
    {
 
1078
        if (QFileInfo(fileName).suffix() == "html" || QFileInfo(fileName).suffix() == "htm")
 
1079
            ;
 
1080
        else
 
1081
            fileName += ".html";
 
1082
 
 
1083
        QString str = consoleView->console()->document()->toHtml();
 
1084
        writeStringContent(fileName, &str);
 
1085
 
 
1086
        QFileInfo fileInfo(fileName);
 
1087
        if (fileInfo.absoluteDir() != tempProblemDir())
 
1088
            settings.setValue("PythonEditorDialog/WorkDir", fileInfo.absolutePath());
 
1089
    }
 
1090
}
 
1091
 
 
1092
void PythonEditorDialog::doFileClose()
 
1093
{
 
1094
    doCloseTab(tabWidget->currentIndex());
 
1095
}
 
1096
 
 
1097
void PythonEditorDialog::doFilePrint()
 
1098
{
 
1099
    QPrinter printer(QPrinter::HighResolution);
 
1100
 
 
1101
    QPrintDialog printDialog(&printer, this);
 
1102
    printDialog.addEnabledOption(QAbstractPrintDialog::PrintCollateCopies);
 
1103
    printDialog.setWindowTitle(tr("Print Document"));
 
1104
    if (printDialog.exec() == QDialog::Accepted)
 
1105
    {
 
1106
        txtEditor->print(&printer);
 
1107
    }
 
1108
}
 
1109
 
 
1110
void PythonEditorDialog::doFind()
 
1111
{
 
1112
    QTextCursor cursor = txtEditor->textCursor();
 
1113
    scriptEditorWidget()->searchWidget->showFind(cursor.selectedText());
 
1114
}
 
1115
 
 
1116
void PythonEditorDialog::doFindNext(bool fromBegining)
 
1117
{
 
1118
    scriptEditorWidget()->searchWidget->findNext(false);
 
1119
}
 
1120
 
 
1121
void PythonEditorDialog::doReplace()
 
1122
{
 
1123
    QTextCursor cursor = txtEditor->textCursor();
 
1124
    scriptEditorWidget()->searchWidget->showReplaceAll(cursor.selectedText());
 
1125
}
 
1126
 
 
1127
void PythonEditorDialog::doDataChanged()
 
1128
{
 
1129
    actPaste->setEnabled(!QApplication::clipboard()->text().isEmpty());
 
1130
}
 
1131
 
 
1132
void PythonEditorDialog::doHelp()
 
1133
{
 
1134
    showPage("scripting/commands.html");
 
1135
}
 
1136
 
 
1137
void PythonEditorDialog::doHelpKeywordList()
 
1138
{
 
1139
    showPage("scripting/keyword_list.html");
 
1140
}
 
1141
 
 
1142
void PythonEditorDialog::doAbout()
 
1143
{
 
1144
    AboutDialog about(this);
 
1145
    about.exec();
 
1146
}
 
1147
 
 
1148
void PythonEditorDialog::onOtherInstanceMessage(const QString &msg)
 
1149
{
 
1150
    QStringList args = msg.split("#!#");
 
1151
    for (int i = 1; i < args.count()-1; i++)
 
1152
    {
 
1153
        QString fileName =
 
1154
                QFile::exists(args[i]) ? args[i] : QApplication::applicationDirPath() + QDir::separator() + args[i];
 
1155
 
 
1156
        if (QFile::exists(fileName))
 
1157
            doFileOpen(fileName);
 
1158
    }
 
1159
 
 
1160
    // setWindowState(Qt::WindowMinimized);
 
1161
    // setWindowState(windowState() & ~Qt::WindowMinimized | Qt::WindowActive);
 
1162
    show();
 
1163
}
 
1164
 
 
1165
void PythonEditorDialog::doCloseTab(int index)
 
1166
{
 
1167
    tabWidget->setCurrentIndex(index);
 
1168
 
 
1169
    QString fileName = tr("Untitled");
 
1170
    if (!scriptEditorWidget()->fileName().isEmpty())
 
1171
    {
 
1172
        QFileInfo fileInfo(scriptEditorWidget()->fileName());
 
1173
        fileName = fileInfo.completeBaseName();
 
1174
    }
 
1175
 
 
1176
    while (txtEditor->document()->isModified())
 
1177
    {
 
1178
        QMessageBox::StandardButton ret;
 
1179
        ret = QMessageBox::warning(this, tr("Application"), tr("File '%1' has been modified.\nDo you want to save your changes?").arg(fileName),
 
1180
                                   QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
 
1181
        if (ret == QMessageBox::Save)
 
1182
            doFileSave();
 
1183
        else if (ret == QMessageBox::Discard)
 
1184
            break;
 
1185
        else if (ret == QMessageBox::Cancel)
 
1186
            return;
 
1187
    }
 
1188
 
 
1189
    if (tabWidget->count() == 1)
 
1190
    {
 
1191
        doFileNew();
 
1192
    }
 
1193
 
 
1194
    tabWidget->removeTab(index);
 
1195
}
 
1196
 
 
1197
void PythonEditorDialog::doCurrentPageChanged(int index)
 
1198
{
 
1199
    txtEditor = scriptEditorWidget()->txtEditor;
 
1200
 
 
1201
    actCut->disconnect();
 
1202
    connect(actCut, SIGNAL(triggered()), txtEditor, SLOT(cut()));
 
1203
    actCopy->disconnect();
 
1204
    connect(actCopy, SIGNAL(triggered()), txtEditor, SLOT(copy()));
 
1205
    actPaste->disconnect();
 
1206
    connect(actPaste, SIGNAL(triggered()), txtEditor, SLOT(paste()));
 
1207
    actUndo->disconnect();
 
1208
    connect(actUndo, SIGNAL(triggered()), txtEditor, SLOT(undo()));
 
1209
    actRedo->disconnect();
 
1210
    connect(actRedo, SIGNAL(triggered()), txtEditor, SLOT(redo()));
 
1211
 
 
1212
    actIndentSelection->disconnect();
 
1213
    connect(actIndentSelection, SIGNAL(triggered()), txtEditor, SLOT(indentSelection()));
 
1214
    actUnindentSelection->disconnect();
 
1215
    connect(actUnindentSelection, SIGNAL(triggered()), txtEditor, SLOT(unindentSelection()));
 
1216
    actCommentAndUncommentSelection->disconnect();
 
1217
    connect(actCommentAndUncommentSelection, SIGNAL(triggered()), txtEditor, SLOT(commentAndUncommentSelection()));
 
1218
    actGotoLine->disconnect();
 
1219
    connect(actGotoLine, SIGNAL(triggered()), txtEditor, SLOT(gotoLine()));
 
1220
 
 
1221
    txtEditor->document()->disconnect(actUndo);
 
1222
    txtEditor->document()->disconnect(actRedo);
 
1223
    connect(txtEditor->document(), SIGNAL(undoAvailable(bool)), actUndo, SLOT(setEnabled(bool)));
 
1224
    connect(txtEditor->document(), SIGNAL(redoAvailable(bool)), actRedo, SLOT(setEnabled(bool)));
 
1225
    txtEditor->disconnect(actCut);
 
1226
    txtEditor->disconnect(actCopy);
 
1227
    connect(txtEditor, SIGNAL(copyAvailable(bool)), actCut, SLOT(setEnabled(bool)));
 
1228
    connect(txtEditor, SIGNAL(copyAvailable(bool)), actCopy, SLOT(setEnabled(bool)));
 
1229
 
 
1230
    // modifications
 
1231
    connect(txtEditor->document(), SIGNAL(modificationChanged(bool)), this, SLOT(doCurrentDocumentChanged(bool)));
 
1232
 
 
1233
    // line number
 
1234
    connect(txtEditor, SIGNAL(cursorPositionChanged()), this, SLOT(doCursorPositionChanged()));
 
1235
    doCursorPositionChanged();
 
1236
 
 
1237
    actUndo->setEnabled(txtEditor->document()->isUndoAvailable());
 
1238
    actRedo->setEnabled(txtEditor->document()->isRedoAvailable());
 
1239
 
 
1240
    // tabWidget->setTabsClosable(tabWidget->count() > 1);
 
1241
    tabWidget->setTabsClosable(true);
 
1242
    tabWidget->cornerWidget(Qt::TopLeftCorner)->setEnabled(true);
 
1243
 
 
1244
    QString fileName = tr("Untitled");
 
1245
    if (!scriptEditorWidget()->fileName().isEmpty())
 
1246
    {
 
1247
        QFileInfo fileInfo(scriptEditorWidget()->fileName());
 
1248
        fileName = fileInfo.completeBaseName();
 
1249
    }
 
1250
    setWindowTitle(tr("PythonLab - %1").arg(fileName));
 
1251
 
 
1252
    txtEditor->setFocus();
 
1253
}
 
1254
 
 
1255
void PythonEditorDialog::doCursorPositionChanged()
 
1256
{
 
1257
    QTextCursor cur(txtEditor->textCursor());
 
1258
    lblCurrentPosition->setText(tr("Line: %1, Col: %2").arg(cur.blockNumber()+1)
 
1259
                                .arg(cur.columnNumber()+1));
 
1260
}
 
1261
 
 
1262
void PythonEditorDialog::doCurrentDocumentChanged(bool changed)
 
1263
{
 
1264
    // modified
 
1265
    QString fileName = tr("Untitled");
 
1266
    if (!scriptEditorWidget()->fileName().isEmpty())
 
1267
    {
 
1268
        QFileInfo fileInfo(scriptEditorWidget()->fileName());
 
1269
        fileName = fileInfo.completeBaseName();
 
1270
    }
 
1271
 
 
1272
    if (changed)
 
1273
        tabWidget->setTabText(tabWidget->currentIndex(), QString("* %1").arg(fileName));
 
1274
    else
 
1275
        tabWidget->setTabText(tabWidget->currentIndex(), fileName);
 
1276
}
 
1277
 
 
1278
void PythonEditorDialog::setRecentFiles()
 
1279
{
 
1280
    if (!tabWidget) return;
 
1281
 
 
1282
    // recent files
 
1283
    if (!scriptEditorWidget()->fileName().isEmpty())
 
1284
    {
 
1285
        QFileInfo fileInfo(scriptEditorWidget()->fileName());
 
1286
        if (m_recentFiles.indexOf(fileInfo.absoluteFilePath()) == -1)
 
1287
            m_recentFiles.insert(0, fileInfo.absoluteFilePath());
 
1288
        else
 
1289
            m_recentFiles.move(m_recentFiles.indexOf(fileInfo.absoluteFilePath()), 0);
 
1290
 
 
1291
        while (m_recentFiles.count() > 15) m_recentFiles.removeLast();
 
1292
    }
 
1293
 
 
1294
    mnuRecentFiles->clear();
 
1295
    for (int i = 0; i<m_recentFiles.count(); i++)
 
1296
    {
 
1297
        QAction *actMenuRecentItem = new QAction(m_recentFiles[i], this);
 
1298
        actFileOpenRecentGroup->addAction(actMenuRecentItem);
 
1299
        mnuRecentFiles->addAction(actMenuRecentItem);
 
1300
    }
 
1301
}
 
1302
 
 
1303
void PythonEditorDialog::closeTabs()
 
1304
{
 
1305
    for (int i = tabWidget->count()-1; i >= 0 ; i--)
 
1306
        doCloseTab(i);
 
1307
}
 
1308
 
 
1309
bool PythonEditorDialog::isScriptModified()
 
1310
{
 
1311
    return txtEditor->document()->isModified();
 
1312
}
 
1313
 
 
1314
// ********************************************************************************
 
1315
 
 
1316
ScriptEditor::ScriptEditor(PythonEngine *pythonEngine, QWidget *parent)
 
1317
    : PlainTextEditParenthesis(parent), pythonEngine(pythonEngine), m_isProfiled(false), m_isLineNumbersVisible(true)
 
1318
{
 
1319
    lineNumberArea = new ScriptEditorLineNumberArea(this);
 
1320
 
 
1321
    setFont(FONT);
 
1322
    setTabStopWidth(fontMetrics().width(TABS));
 
1323
    setLineWrapMode(QPlainTextEdit::NoWrap);
 
1324
    setTabChangesFocus(false);
 
1325
 
 
1326
    // highlighter
 
1327
    new PythonHighlighter(document());
 
1328
 
 
1329
    connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth()));
 
1330
    connect(this, SIGNAL(updateRequest(const QRect &, int)), this, SLOT(updateLineNumberArea(const QRect &, int)));
 
1331
    connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
 
1332
 
 
1333
    updateLineNumberAreaWidth();
 
1334
    highlightCurrentLine();
 
1335
 
 
1336
    completer = new PythonCompleter();
 
1337
    completer->setWidget(this);
 
1338
    connect(completer, SIGNAL(activated(const QString&)), this, SLOT(insertCompletion(const QString&)));
 
1339
}
 
1340
 
 
1341
ScriptEditor::~ScriptEditor()
 
1342
{
 
1343
    delete completer;
 
1344
}
 
1345
 
 
1346
void ScriptEditor::resizeEvent(QResizeEvent *e)
 
1347
{
 
1348
    QPlainTextEdit::resizeEvent(e);
 
1349
 
 
1350
    QRect cr = contentsRect();
 
1351
    lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
 
1352
}
 
1353
 
 
1354
void ScriptEditor::updateLineNumberAreaWidth()
 
1355
{
 
1356
    setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
 
1357
}
 
1358
 
 
1359
void ScriptEditor::updateLineNumberArea(const QRect &rect, int dy)
 
1360
{
 
1361
    if (dy)
 
1362
    {
 
1363
        lineNumberArea->scroll(0, dy);
 
1364
    }
 
1365
    else
 
1366
    {
 
1367
        lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
 
1368
    }
 
1369
 
 
1370
    if (rect.contains(viewport()->rect()))
 
1371
        updateLineNumberAreaWidth();
 
1372
}
 
1373
 
 
1374
void ScriptEditor::keyPressEvent(QKeyEvent *event)
 
1375
{
 
1376
    QTextCursor cursor = textCursor();
 
1377
    int oldPos = cursor.position();
 
1378
    int indent = firstNonSpace(cursor.block().text());
 
1379
 
 
1380
    if (completer && completer->popup()->isVisible())
 
1381
    {
 
1382
        // The following keys are forwarded by the completer to the widget
 
1383
        switch (event->key())
 
1384
        {
 
1385
        case Qt::Key_Return:
 
1386
        case Qt::Key_Enter:
 
1387
        case Qt::Key_Escape:
 
1388
        case Qt::Key_Tab:
 
1389
        case Qt::Key_Backtab:
 
1390
 
 
1391
            event->ignore();
 
1392
            return; // let the completer do default behavior
 
1393
        default:
 
1394
            break;
 
1395
        }
 
1396
    }
 
1397
 
 
1398
    if (event->key() == Qt::Key_Tab && !(event->modifiers() & Qt::ShiftModifier))
 
1399
    {
 
1400
        if (!textCursor().hasSelection())
 
1401
        {
 
1402
            // insert 4 spaces instead of tab
 
1403
            textCursor().insertText(QString(4, ' '));
 
1404
        }
 
1405
        else
 
1406
        {
 
1407
            // indent the selection
 
1408
            indentSelection();
 
1409
        }
 
1410
    }
 
1411
    else if (event->key() == Qt::Key_Backtab && (event->modifiers() & Qt::ShiftModifier))
 
1412
    {
 
1413
        if (!textCursor().hasSelection())
 
1414
        {
 
1415
            // moves position backward 4 spaces
 
1416
            QTextCursor cursor = textCursor();
 
1417
            cursor.setPosition(cursor.position() - 4, QTextCursor::MoveAnchor);
 
1418
            setTextCursor(cursor);
 
1419
        }
 
1420
        else
 
1421
        {
 
1422
            // unindent the selection
 
1423
            unindentSelection();
 
1424
        }
 
1425
    }
 
1426
    else if ((event->key() == Qt::Key_Backspace) && (document()->characterAt(oldPos - 1) == ' ')
 
1427
             && (document()->characterAt(oldPos - 2) == ' ')
 
1428
             && (document()->characterAt(oldPos - 3) == ' ')
 
1429
             && (document()->characterAt(oldPos - 4) == ' '))
 
1430
    {
 
1431
        cursor.beginEditBlock();
 
1432
        // determine selection to delete
 
1433
        int newPos = oldPos - 4;
 
1434
        cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
 
1435
        int startPosOfLine = cursor.position();
 
1436
        if (newPos < startPosOfLine)
 
1437
            newPos = startPosOfLine;
 
1438
        // make selection
 
1439
        cursor.setPosition(oldPos, QTextCursor::MoveAnchor);
 
1440
        cursor.setPosition(newPos, QTextCursor::KeepAnchor);
 
1441
        cursor.deleteChar();
 
1442
        cursor.endEditBlock();
 
1443
        setTextCursor(cursor);
 
1444
    }
 
1445
    else if ((event->key() == Qt::Key_Return) && (indent))
 
1446
    {
 
1447
        cursor.beginEditBlock();
 
1448
 
 
1449
        // add 1 extra indent if current line begins a code block
 
1450
        bool inCodeBlock = false;
 
1451
        if (QRegExp("\\:").indexIn(cursor.block().text()) != -1)
 
1452
        {
 
1453
            indent += TABS_SIZE;
 
1454
            inCodeBlock = true;
 
1455
        }
 
1456
 
 
1457
        cursor.insertBlock();
 
1458
        QString spaces(indent, true ? QLatin1Char(' ') : QLatin1Char('\t'));
 
1459
        cursor.insertText(spaces);
 
1460
 
 
1461
        cursor.endEditBlock();
 
1462
        setTextCursor(cursor);
 
1463
    }
 
1464
    else
 
1465
    {
 
1466
        QPlainTextEdit::keyPressEvent(event);
 
1467
    }
 
1468
 
 
1469
    if ((event->key() == Qt::Key_Space && event->modifiers() & Qt::ControlModifier)
 
1470
            || completer->popup()->isVisible())
 
1471
    {
 
1472
        QTextCursor tc = textCursor();
 
1473
        QStringList found = pythonEngine->codeCompletionScript(toPlainText(),
 
1474
                                                               tc.blockNumber() + 1,
 
1475
                                                               tc.columnNumber() + 1);
 
1476
 
 
1477
        QString textToComplete = textCursor().block().text().trimmed();
 
1478
        QStringList foundInterpreter = pythonEngine->codeCompletionInterpreter(textToComplete);
 
1479
 
 
1480
        found << foundInterpreter;
 
1481
        found.removeDuplicates();
 
1482
 
 
1483
        if (!found.isEmpty())
 
1484
        {
 
1485
            // completer->setCompletionPrefix(textToComplete);
 
1486
            completer->setModel(new QStringListModel(found, completer));
 
1487
            QTextCursor c = textCursor();
 
1488
            c.movePosition(QTextCursor::StartOfWord);
 
1489
            QRect cr = cursorRect(c);
 
1490
            cr.setWidth(completer->popup()->sizeHintForColumn(0)
 
1491
                        + completer->popup()->verticalScrollBar()->sizeHint().width() + 30);
 
1492
            cr.translate(lineNumberAreaWidth(), 4);
 
1493
            completer->complete(cr);
 
1494
        }
 
1495
        else
 
1496
        {
 
1497
            completer->popup()->hide();
 
1498
        }
 
1499
    }
 
1500
}
 
1501
 
 
1502
void ScriptEditor::indentSelection()
 
1503
{
 
1504
    indentAndUnindentSelection(true);
 
1505
}
 
1506
 
 
1507
void ScriptEditor::unindentSelection()
 
1508
{
 
1509
    indentAndUnindentSelection(false);
 
1510
}
 
1511
 
 
1512
void ScriptEditor::indentAndUnindentSelection(bool doIndent)
 
1513
{
 
1514
    QTextCursor cursor = textCursor();
 
1515
    cursor.beginEditBlock();
 
1516
 
 
1517
    // indent or unindent the selected lines
 
1518
    int pos = cursor.position();
 
1519
    int anchor = cursor.anchor();
 
1520
    int start = qMin(anchor, pos);
 
1521
    int end = qMax(anchor, pos);
 
1522
 
 
1523
    QTextDocument *doc = document();
 
1524
    QTextBlock startBlock = doc->findBlock(start);
 
1525
    QTextBlock endBlock = doc->findBlock(end-1).next();
 
1526
 
 
1527
    for (QTextBlock block = startBlock; block != endBlock; block = block.next())
 
1528
    {
 
1529
        QString text = block.text();
 
1530
        if (doIndent)
 
1531
        {
 
1532
            int indentPosition = firstNonSpace(text);
 
1533
            cursor.setPosition(block.position() + indentPosition);
 
1534
            cursor.insertText(QString(TABS_SIZE, ' '));
 
1535
        }
 
1536
        else
 
1537
        {
 
1538
            int indentPosition = firstNonSpace(text);
 
1539
            int targetColumn = indentedColumn(columnAt(text, indentPosition), false);
 
1540
            cursor.setPosition(block.position() + indentPosition);
 
1541
            cursor.setPosition(block.position() + targetColumn, QTextCursor::KeepAnchor);
 
1542
            cursor.removeSelectedText();
 
1543
        }
 
1544
    }
 
1545
 
 
1546
    // reselect the selected lines
 
1547
    cursor.setPosition(startBlock.position());
 
1548
    cursor.setPosition(endBlock.previous().position(), QTextCursor::KeepAnchor);
 
1549
    cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
 
1550
 
 
1551
    cursor.endEditBlock();
 
1552
    setTextCursor(cursor);
 
1553
}
 
1554
 
 
1555
void ScriptEditor::commentAndUncommentSelection()
 
1556
{
 
1557
    QTextCursor cursor = textCursor();
 
1558
    cursor.beginEditBlock();
 
1559
 
 
1560
    // previous selection state
 
1561
    int selStart = cursor.selectionStart();
 
1562
    int selEnd = cursor.selectionEnd();
 
1563
    cursor.setPosition(selEnd, QTextCursor::MoveAnchor);
 
1564
    int blockEnd = cursor.blockNumber();
 
1565
 
 
1566
    // extend selStart to first blocks's start-of-block
 
1567
    // extend selEnd to last block's end-of-block
 
1568
    cursor.setPosition(selStart, QTextCursor::MoveAnchor);
 
1569
    cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
 
1570
    selStart = cursor.position();
 
1571
    cursor.setPosition(selEnd, QTextCursor::MoveAnchor);
 
1572
    cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor);
 
1573
    selEnd = cursor.position();
 
1574
 
 
1575
    // process first block
 
1576
    cursor.setPosition(selStart, QTextCursor::MoveAnchor);
 
1577
    QRegExp commentPattern("^#");
 
1578
    if (commentPattern.indexIn(cursor.block().text()) == -1)
 
1579
    {
 
1580
        // comment it, if the block does not starts with '#'
 
1581
        cursor.insertText("#");
 
1582
        selEnd += 1;
 
1583
    }
 
1584
    else
 
1585
    {
 
1586
        // else uncomment it
 
1587
        cursor.setPosition(selStart + commentPattern.matchedLength(), QTextCursor::KeepAnchor);
 
1588
        cursor.deleteChar();
 
1589
        selEnd -= 1;
 
1590
    }
 
1591
 
 
1592
    // loop through all blocks
 
1593
    while (cursor.blockNumber() < blockEnd)
 
1594
    {
 
1595
        cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor);
 
1596
        cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
 
1597
        if (commentPattern.indexIn(cursor.block().text()) == -1)
 
1598
        {
 
1599
            cursor.insertText("#");
 
1600
            selEnd += 1;
 
1601
        }
 
1602
        else
 
1603
        {
 
1604
            cursor.setPosition(cursor.position() + commentPattern.matchedLength(), QTextCursor::KeepAnchor);
 
1605
            cursor.deleteChar();
 
1606
            selEnd -= 1;
 
1607
        }
 
1608
    }
 
1609
 
 
1610
    // restore selection state
 
1611
    cursor.setPosition(selStart, QTextCursor::MoveAnchor);
 
1612
    cursor.setPosition(selEnd, QTextCursor::KeepAnchor);
 
1613
 
 
1614
    // update
 
1615
    cursor.endEditBlock();
 
1616
    setTextCursor(cursor);
 
1617
}
 
1618
 
 
1619
void ScriptEditor::gotoLine(int line, bool isError)
 
1620
{
 
1621
    // use dialog when (line == -1)
 
1622
    if (line == -1)
 
1623
    {
 
1624
        bool ok;
 
1625
        int lineDialog = QInputDialog::getInt(this, tr("Goto line"), tr("Line number:"),
 
1626
                                              0, 1, document()->blockCount(), 1, &ok);
 
1627
        if (ok)
 
1628
            line = lineDialog;
 
1629
    }
 
1630
 
 
1631
    if (line >= 0 && line <= document()->blockCount())
 
1632
    {
 
1633
        int pos = document()->findBlockByNumber(line - 1).position();
 
1634
        QTextCursor cur = textCursor();
 
1635
        cur.setPosition(pos, QTextCursor::MoveAnchor);
 
1636
        setTextCursor(cur);
 
1637
        ensureCursorVisible();
 
1638
        highlightCurrentLine(true);
 
1639
        setFocus();
 
1640
    }
 
1641
}
 
1642
 
 
1643
void ScriptEditor::highlightCurrentLine(bool isError)
 
1644
{
 
1645
    QList<QTextEdit::ExtraSelection> selections;
 
1646
 
 
1647
    if (!isReadOnly())
 
1648
    {
 
1649
        QTextEdit::ExtraSelection selection;
 
1650
        QColor lineColor = QColor(Qt::yellow).lighter(180);
 
1651
        if (isError)
 
1652
            lineColor = QColor(Qt::red).lighter(180);
 
1653
 
 
1654
        selection.format.setBackground(lineColor);
 
1655
        selection.format.setProperty(QTextFormat::FullWidthSelection, true);
 
1656
        selection.cursor = textCursor();
 
1657
        selection.cursor.clearSelection();
 
1658
        selections.append(selection);
 
1659
    }
 
1660
 
 
1661
    setExtraSelections(selections);
 
1662
 
 
1663
    matchParentheses('(', ')');
 
1664
    // matchParentheses('[', ']');
 
1665
    // matchParentheses('{', '}');
 
1666
}
 
1667
 
 
1668
void ScriptEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
 
1669
{
 
1670
    // line numbers
 
1671
    const QBrush bookmarkBrushPyFlakes(QColor(Qt::red).lighter());
 
1672
 
 
1673
    int timesWidth = 0;
 
1674
    int callWidth = 0;
 
1675
    if (isProfiled())
 
1676
    {
 
1677
        timesWidth = fontMetrics().width(QLatin1Char('9')) * QString::number(profilerMaxAccumulatedTime()).length() + 1;
 
1678
        callWidth = fontMetrics().width(QLatin1Char('9')) * QString::number(profilerMaxAccumulatedCall()).length() + 1;
 
1679
    }
 
1680
 
 
1681
    QPainter painter(lineNumberArea);
 
1682
    painter.fillRect(event->rect(), Qt::lightGray);
 
1683
 
 
1684
    QTextBlock block = firstVisibleBlock();
 
1685
    int blockNumber = block.blockNumber();
 
1686
    int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
 
1687
    int bottom = top + (int) blockBoundingRect(block).height();
 
1688
 
 
1689
    while (block.isValid() && top <= event->rect().bottom())
 
1690
    {
 
1691
        if (block.isVisible() && bottom >= event->rect().top())
 
1692
        {
 
1693
            // line number
 
1694
            QString lineNumber = QString::number(blockNumber + 1);
 
1695
 
 
1696
            // draw rect
 
1697
            if (errorMessagesPyFlakes.contains(blockNumber + 1))
 
1698
                painter.fillRect(0, top, lineNumberArea->width(), fontMetrics().height(),
 
1699
                                 bookmarkBrushPyFlakes);
 
1700
 
 
1701
            // draw line number
 
1702
            painter.setPen(Qt::black);
 
1703
            painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
 
1704
                             Qt::AlignRight, lineNumber);
 
1705
 
 
1706
            // draw profiler number
 
1707
            if (isProfiled())
 
1708
            {
 
1709
                if (profilerAccumulatedTimes().value(blockNumber + 1) > 0)
 
1710
                {
 
1711
                    QString number = QString::number(profilerAccumulatedTimes().value(blockNumber + 1));
 
1712
                    painter.setPen(Qt::darkBlue);
 
1713
                    painter.drawText(0, top, timesWidth,
 
1714
                                     fontMetrics().height(),
 
1715
                                     Qt::AlignRight, number);
 
1716
 
 
1717
                    number = QString::number(profilerAccumulatedLines().value(blockNumber + 1));
 
1718
                    painter.setPen(Qt::darkGreen);
 
1719
                    painter.drawText(0, top, timesWidth + callWidth + 3, fontMetrics().height(),
 
1720
                                     Qt::AlignRight, number);
 
1721
                }
 
1722
            }
 
1723
        }
 
1724
 
 
1725
        block = block.next();
 
1726
        top = bottom;
 
1727
        bottom = top + (int) blockBoundingRect(block).height();
 
1728
        ++blockNumber;
 
1729
    }
 
1730
}
 
1731
 
 
1732
void ScriptEditor::lineNumberAreaMouseMoveEvent(QMouseEvent *event)
 
1733
{
 
1734
    QTextBlock block = firstVisibleBlock();
 
1735
    int blockNumber = block.blockNumber();
 
1736
 
 
1737
    int line = blockNumber + event->pos().y() / (int) blockBoundingRect(block).height() + 1;
 
1738
 
 
1739
    if (line <= document()->blockCount())
 
1740
    {
 
1741
        if (errorMessagesPyFlakes.contains(line))
 
1742
            QToolTip::showText(event->globalPos(), errorMessagesPyFlakes[line]);
 
1743
    }
 
1744
}
 
1745
 
 
1746
int ScriptEditor::lineNumberAreaWidth()
 
1747
{
 
1748
    if (m_isLineNumbersVisible)
 
1749
    {
 
1750
        int digits = 1;
 
1751
        int max = qMax(1, blockCount());
 
1752
        while (max >= 10) {
 
1753
            max /= 10;
 
1754
            ++digits;
 
1755
        }
 
1756
 
 
1757
        if (isProfiled())
 
1758
        {
 
1759
            digits += QString::number(profilerMaxAccumulatedTime()).length() +
 
1760
                    QString::number(profilerMaxAccumulatedCall()).length();
 
1761
        }
 
1762
 
 
1763
        int space = 15 + fontMetrics().width(QLatin1Char('9')) * digits;
 
1764
 
 
1765
        return space;
 
1766
    }
 
1767
    else
 
1768
    {
 
1769
        return 0;
 
1770
    }
 
1771
}
 
1772
 
 
1773
void ScriptEditor::insertCompletion(const QString& completion)
 
1774
{
 
1775
    QString str = completion.left(completion.indexOf("(") - 1);
 
1776
 
 
1777
    QTextCursor tc = textCursor();
 
1778
    tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
 
1779
    // if (tc.selectedText() == ".")
 
1780
    // {
 
1781
    //     tc.insertText(QString(".") + str);
 
1782
    // }
 
1783
    // else
 
1784
    {
 
1785
        tc = textCursor();
 
1786
        tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
 
1787
        tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
 
1788
        tc.insertText(str);
 
1789
        setTextCursor(tc);
 
1790
    }
 
1791
}
 
1792
 
 
1793
void ScriptEditor::replaceTabsWithSpaces()
 
1794
{
 
1795
    QString text = document()->toPlainText();
 
1796
    text = text.replace("\t", TABS);
 
1797
    document()->setPlainText(text);
 
1798
}
 
1799
 
 
1800
// ********************************************************************************************************
 
1801
 
 
1802
SearchWidget::SearchWidget(ScriptEditor *txtEditor, QWidget *parent)
 
1803
    : QWidget(parent), txtEditor(txtEditor)
 
1804
{
 
1805
    lblFind = new QLabel(tr("Search for:"));
 
1806
    lblReplace = new QLabel(tr("Replace with:"));
 
1807
 
 
1808
    txtFind = new QLineEdit();
 
1809
    connect(txtFind, SIGNAL(returnPressed()), this, SLOT(find()));
 
1810
    txtReplace = new QLineEdit();
 
1811
    connect(txtReplace, SIGNAL(returnPressed()), this, SLOT(replaceAll()));
 
1812
 
 
1813
    btnFind = new QPushButton(tr("Find"), this);
 
1814
    btnFind->setDefault(true);
 
1815
    connect(btnFind, SIGNAL(clicked()), this, SLOT(find()));
 
1816
 
 
1817
    btnReplace = new QPushButton(tr("Replace all"), this);
 
1818
    connect(btnReplace, SIGNAL(clicked()), this, SLOT(replaceAll()));
 
1819
 
 
1820
    btnHide = new QPushButton(tr("Hide"), this);
 
1821
    connect(btnHide, SIGNAL(clicked()), this, SLOT(hideWidget()));
 
1822
 
 
1823
    QGridLayout *findReplaceLayout = new QGridLayout();
 
1824
    findReplaceLayout->setMargin(2);
 
1825
 
 
1826
    findReplaceLayout->addWidget(lblFind, 0, 0);
 
1827
    findReplaceLayout->addWidget(txtFind, 0, 1);
 
1828
    findReplaceLayout->addWidget(btnFind, 0, 2);
 
1829
    findReplaceLayout->addWidget(btnHide, 0, 3);
 
1830
    findReplaceLayout->addWidget(lblReplace, 1, 0);
 
1831
    findReplaceLayout->addWidget(txtReplace, 1, 1);
 
1832
    findReplaceLayout->addWidget(btnReplace, 1, 2);
 
1833
 
 
1834
    setLayout(findReplaceLayout);
 
1835
 
 
1836
    lblReplace->setVisible(false);
 
1837
    txtReplace->setVisible(false);
 
1838
    btnReplace->setVisible(false);
 
1839
 
 
1840
    setVisible(false);
 
1841
}
 
1842
 
 
1843
void SearchWidget::keyPressEvent(QKeyEvent *event)
 
1844
{
 
1845
    int key = event->key();
 
1846
    switch (key)
 
1847
    {
 
1848
    case Qt::Key_Escape:
 
1849
        hideWidget();
 
1850
        break;
 
1851
    default:
 
1852
        QWidget::keyPressEvent(event);
 
1853
    }
 
1854
}
 
1855
 
 
1856
void SearchWidget::showFind(const QString &text)
 
1857
{
 
1858
    if (!text.isEmpty())
 
1859
        txtFind->setText(text);
 
1860
 
 
1861
    txtFind->setFocus();
 
1862
    txtFind->selectAll();
 
1863
    lblReplace->setVisible(false);
 
1864
    txtReplace->setVisible(false);
 
1865
    btnReplace->setVisible(false);
 
1866
 
 
1867
    startFromBeginning = true;
 
1868
 
 
1869
    show();
 
1870
}
 
1871
 
 
1872
void SearchWidget::showReplaceAll(const QString &text)
 
1873
{
 
1874
    if (!text.isEmpty())
 
1875
        txtFind->setText(text);
 
1876
 
 
1877
    txtFind->setFocus();
 
1878
    txtFind->selectAll();
 
1879
    lblReplace->setVisible(true);
 
1880
    txtReplace->setVisible(true);
 
1881
    btnReplace->setVisible(true);
 
1882
 
 
1883
    show();
 
1884
}
 
1885
 
 
1886
void SearchWidget::find()
 
1887
{
 
1888
    findNext(startFromBeginning);
 
1889
    startFromBeginning = false;
 
1890
}
 
1891
 
 
1892
void SearchWidget::findNext(bool fromBegining)
 
1893
{
 
1894
    if (!txtFind->text().isEmpty())
 
1895
    {
 
1896
        // Search
 
1897
        QTextCursor cursor = txtEditor->textCursor();
 
1898
        if (fromBegining)
 
1899
            cursor = txtEditor->document()->find(txtFind->text());
 
1900
        else
 
1901
            cursor = txtEditor->document()->find(txtFind->text(), cursor);
 
1902
 
 
1903
        if (cursor.position() >= 0)
 
1904
            txtEditor->setTextCursor(cursor);
 
1905
        txtEditor->setFocus();
 
1906
 
 
1907
        if (isVisible())
 
1908
            txtFind->setFocus();
 
1909
    }
 
1910
}
 
1911
 
 
1912
void SearchWidget::replaceAll()
 
1913
{
 
1914
    if (!txtFind->text().isEmpty())
 
1915
    {
 
1916
        QTextCursor cursor = txtEditor->textCursor();
 
1917
 
 
1918
        QString text = txtEditor->document()->toPlainText();
 
1919
        text.replace(txtFind->text(), txtReplace->text());
 
1920
        txtEditor->document()->setPlainText(text);
 
1921
 
 
1922
        txtEditor->setTextCursor(cursor);
 
1923
 
 
1924
        hideWidget();
 
1925
    }
 
1926
}
 
1927
 
 
1928
void SearchWidget::hideWidget()
 
1929
{
 
1930
    hide();
 
1931
    txtEditor->setFocus();
 
1932
}