1
// This file is part of Agros2D.
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.
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.
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/>.
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/
20
#include "pythoneditor.h"
21
#include "pythonhighlighter.h"
22
#include "pythonconsole.h"
23
#include "pythonbrowser.h"
24
#include "pythonengine.h"
25
#include "pythoncompleter.h"
27
// #include "util/constants.h"
28
#include "gui/filebrowser.h"
29
#include "gui/about.h"
31
const QString TABS = " ";
32
const int TABS_SIZE = 4;
34
int firstNonSpace(const QString& text)
37
while (i < text.size())
39
if (!text.at(i).isSpace())
46
int indentedColumn(int column, bool doIndent)
48
int aligned = (column / TABS_SIZE) * TABS_SIZE;
50
return aligned + TABS_SIZE;
53
return qMax(0, aligned - TABS_SIZE);
56
int columnAt(const QString& text, int position)
59
for (int i = 0; i < position; ++i)
61
if (text.at(i) == QLatin1Char('\t'))
62
column = column - (column % TABS_SIZE) + TABS_SIZE;
69
PythonEditorWidget::PythonEditorWidget(PythonEngine *pythonEngine, QWidget *parent)
70
: QWidget(parent), pythonEngine(pythonEngine)
76
QTimer *timer = new QTimer(this);
77
connect(timer, SIGNAL(timeout()), this, SLOT(pyFlakesAnalyse()));
80
txtEditor->setAcceptDrops(false);
83
PythonEditorWidget::~PythonEditorWidget()
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());
93
void PythonEditorWidget::createControls()
95
txtEditor = new ScriptEditor(pythonEngine, this);
96
searchWidget = new SearchWidget(txtEditor, this);
98
QVBoxLayout *layoutEditor = new QVBoxLayout();
99
layoutEditor->addWidget(txtEditor);
100
layoutEditor->addWidget(searchWidget);
102
QWidget *editor = new QWidget();
103
editor->setLayout(layoutEditor);
105
splitter = new QSplitter(this);
106
splitter->setOrientation(Qt::Vertical);
107
splitter->addWidget(editor);
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)));
118
splitter->addWidget(trvPyLint);
121
QHBoxLayout *layout = new QHBoxLayout();
122
layout->setMargin(1);
123
layout->addWidget(splitter);
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());
133
void PythonEditorWidget::pyLintAnalyse()
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)));
143
QString pylintBinary = datadir() + "/resources/python/pylint_lab";
146
QString pylintBinary = datadir() + "/resources/python/pylint_lab.bat";
149
QString test = txtEditor->toPlainText();
150
writeStringContent(tempProblemFileName() + ".pylint.py", &test);
152
QStringList arguments;
153
arguments << "-i" << "yes" << tempProblemFileName() + ".pylint.py";
155
processPyLint.setWorkingDirectory(datadir() + "/resources/python");
156
processPyLint.start(pylintBinary, arguments);
158
if (!processPyLint.waitForStarted())
160
qDebug() << "Could not start PyLint: " << processPyLint.errorString();
162
processPyLint.kill();
166
while (!processPyLint.waitForFinished(-1)) {}
169
void PythonEditorWidget::pyLintAnalyseStopped(int exitCode)
171
// QString output = readFileContent(tempProblemFileName() + ".pylint.out");
172
// qDebug() << output;
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);
186
QFile fileOutput(tempProblemFileName() + ".pylint.out");
187
if (!fileOutput.open(QIODevice::ReadOnly | QIODevice::Text))
189
qDebug() << tr("Could not read PyLint output.");
192
QTextStream inOutput(&fileOutput);
197
line_str = inOutput.readLine();
199
if (!line_str.isEmpty())
201
if (line_str.startsWith("C") || line_str.startsWith("W") || line_str.startsWith("E"))
209
QStringList list = line_str.split(":");
210
if (list.count() == 3)
213
line_column = list[1];
214
line = line_column.split(",").at(0).toInt();
217
QTreeWidgetItem *item;
218
if (type.startsWith("C"))
220
typeFamily = tr("Convention");
221
item = new QTreeWidgetItem(itemConvention);
223
else if (type.startsWith("W"))
225
typeFamily = tr("Warning");
226
item = new QTreeWidgetItem(itemWarning);
230
typeFamily = tr("Error");
231
item = new QTreeWidgetItem(itemError);
234
item->setText(0, QString("%1: %2").
238
item->setData(0, Qt::UserRole, line);
242
} while (!line_str.isNull());
244
txtEditor->repaint();
246
// QString error = readFileContent(tempProblemFileName() + ".pylint.err");
247
// qDebug() << error;
250
void PythonEditorWidget::pyFlakesAnalyse()
252
if (isVisible() && !pythonEngine->isScriptRunning())
254
QString fn = tempProblemFileName() + ".pyflakes_str.py";
255
QString str = txtEditor->toPlainText();
256
writeStringContent(fn, &str);
258
QStringList messages = pythonEngine->codePyFlakes(fn);
260
txtEditor->errorMessagesPyFlakes.clear();
261
foreach (QString line, messages)
268
QStringList list = line.split(":");
269
if (list.count() == 3)
271
number = list[1].toInt();
274
txtEditor->errorMessagesPyFlakes[number] = message;
281
txtEditor->repaint();
285
void PythonEditorWidget::doHighlightLine(QTreeWidgetItem *item, int role)
289
int line = item->data(0, Qt::UserRole).value<int>();
291
txtEditor->gotoLine(line, true);
295
// ***********************************************************************************************************
297
PythonEditorDialog::PythonEditorDialog(PythonEngine *pythonEngine, QStringList args, QWidget *parent)
298
: QMainWindow(parent), pythonEngine(pythonEngine)
300
setWindowIcon(icon("pythonlab"));
308
fileBrowser->setDir(settings.value("PythonEditorDialog/WorkDir", datadir()).value<QString>());
309
fileBrowser->refresh();
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()));
316
connect(pythonEngine, SIGNAL(startedScript()), this, SLOT(doStartedScript()));
317
connect(pythonEngine, SIGNAL(executedScript()), this, SLOT(doExecutedScript()));
320
setUnifiedTitleAndToolBarOnMac(true);
323
for (int i = 1; i < args.count(); i++)
326
QFile::exists(args[i]) ? args[i] : QApplication::applicationDirPath() + QDir::separator() + args[i];
328
if (QFile::exists(fileName))
330
QFileInfo fileInfo(fileName);
331
doFileOpen(fileInfo.absoluteFilePath());
335
setAcceptDrops(true);
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());
345
PythonEditorDialog::~PythonEditorDialog()
348
settings.setValue("PythonEditorDialog/Geometry", saveGeometry());
349
settings.setValue("PythonEditorDialog/State", saveState());
350
settings.setValue("PythonEditorDialog/RecentFiles", m_recentFiles);
353
void PythonEditorDialog::closeEvent(QCloseEvent *event)
355
// check script editor
358
if (!isScriptModified())
363
// show script editor
364
if (isScriptModified())
369
void PythonEditorDialog::dragEnterEvent(QDragEnterEvent *event)
371
event->acceptProposedAction();
374
void PythonEditorDialog::dragLeaveEvent(QDragLeaveEvent *event)
379
void PythonEditorDialog::dropEvent(QDropEvent *event)
381
if (event->mimeData()->hasUrls())
383
QString fileName = QUrl(event->mimeData()->urls().at(0)).toLocalFile().trimmed();
384
if (QFile::exists(fileName))
386
QFileInfo fileInfo(fileName);
387
doFileOpen(fileInfo.absoluteFilePath());
389
event->acceptProposedAction();
394
void PythonEditorDialog::showDialog()
398
txtEditor->setFocus();
401
void PythonEditorDialog::createActions()
403
actFileNew = new QAction(icon("document-new"), tr("&New"), this);
404
actFileNew->setShortcuts(QKeySequence::AddTab);
405
connect(actFileNew, SIGNAL(triggered()), this, SLOT(doFileNew()));
407
actFileOpen = new QAction(icon("document-open"), tr("&Open..."), this);
408
actFileOpen->setShortcuts(QKeySequence::Open);
409
connect(actFileOpen, SIGNAL(triggered()), this, SLOT(doFileOpen()));
411
actFileSave = new QAction(icon("document-save"), tr("&Save"), this);
412
actFileSave->setShortcuts(QKeySequence::Save);
413
connect(actFileSave, SIGNAL(triggered()), this, SLOT(doFileSave()));
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()));
419
actFileSaveConsoleAs = new QAction(icon(""), tr("Save console output as..."), this);
420
connect(actFileSaveConsoleAs, SIGNAL(triggered()), this, SLOT(doFileSaveConsoleAs()));
422
actFileOpenRecentGroup = new QActionGroup(this);
423
connect(actFileOpenRecentGroup, SIGNAL(triggered(QAction *)), this, SLOT(doFileOpenRecent(QAction *)));
425
actFileClose = new QAction(icon(""), tr("&Close"), this);
426
actFileClose->setShortcuts(QKeySequence::Close);
427
connect(actFileClose, SIGNAL(triggered()), this, SLOT(doFileClose()));
429
actFilePrint = new QAction(icon(""), tr("&Print"), this);
430
actFilePrint->setShortcuts(QKeySequence::Print);
431
connect(actFilePrint, SIGNAL(triggered()), this, SLOT(doFilePrint()));
433
actUndo = new QAction(icon("edit-undo"), tr("&Undo"), this);
434
actUndo->setShortcut(QKeySequence::Undo);
436
actRedo = new QAction(icon("edit-redo"), tr("&Redo"), this);
437
actRedo->setShortcut(QKeySequence::Redo);
439
actCut = new QAction(icon("edit-cut"), tr("Cu&t"), this);
440
actCut->setShortcut(QKeySequence::Cut);
441
actCut->setEnabled(false);
443
actCopy = new QAction(icon("edit-copy"), tr("&Copy"), this);
444
actCopy->setShortcut(QKeySequence::Copy);
445
actCopy->setEnabled(false);
447
actPaste = new QAction(icon("edit-paste"), tr("&Paste"), this);
448
actPaste->setShortcut(QKeySequence::Paste);
450
actFind = new QAction(icon("edit-find"), tr("&Find"), this);
451
actFind->setShortcut(QKeySequence::Find);
452
connect(actFind, SIGNAL(triggered()), this, SLOT(doFind()));
454
actFindNext = new QAction(icon("edit-find"), tr("Find &next"), this);
455
actFindNext->setShortcut(QKeySequence::FindNext);
456
connect(actFindNext, SIGNAL(triggered()), this, SLOT(doFindNext()));
458
actReplace = new QAction(icon("edit-find-replace"), tr("Replace"), this);
459
actReplace->setShortcut(QKeySequence::Replace);
460
connect(actReplace, SIGNAL(triggered()), this, SLOT(doReplace()));
462
actReplace = new QAction(icon("edit-find-replace"), tr("Replace"), this);
463
actReplace->setShortcut(QKeySequence::Replace);
464
connect(actReplace, SIGNAL(triggered()), this, SLOT(doReplace()));
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+<"));
471
actCommentAndUncommentSelection = new QAction(icon(""), tr("Toggle comment selection"), this);
472
actCommentAndUncommentSelection->setShortcut(tr("Ctrl+/"));
474
actGotoLine = new QAction(icon(""), tr("Goto line"), this);
475
actGotoLine->setShortcut(tr("Alt+G"));
477
actRunPython = new QAction(icon("run"), tr("&Run Python script"), this);
478
actRunPython->setShortcut(QKeySequence(tr("Ctrl+R")));
480
actStopPython = new QAction(icon("stop"), tr("Stop Python script"), this);
481
actStopPython->setEnabled(false);
483
actReplaceTabsWithSpaces = new QAction(icon(""), tr("Replace tabs with spaces"), this);
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")));
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()));
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()));
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()));
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()));
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()));
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()));
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()));
524
actAbout = new QAction(icon("about"), tr("About &PythonLab"), this);
525
actAbout->setMenuRole(QAction::AboutRole);
526
connect(actAbout, SIGNAL(triggered()), this, SLOT(doAbout()));
528
actAboutQt = new QAction(icon("help-about"), tr("About &Qt"), this);
529
actAboutQt->setMenuRole(QAction::AboutQtRole);
530
connect(actAboutQt, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
533
void PythonEditorDialog::createControls()
535
mnuRecentFiles = new QMenu(tr("&Recent files"), this);
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);
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);
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);
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);
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
595
tlbFile = addToolBar(tr("File"));
597
tlbFile->setFixedHeight(iconHeight);
598
tlbFile->setStyleSheet("QToolButton { border: 0px; padding: 0px; margin: 0px; }");
600
tlbFile->setObjectName("File");
601
tlbFile->addAction(actFileNew);
602
tlbFile->addAction(actFileOpen);
603
tlbFile->addAction(actFileSave);
605
tlbEdit = addToolBar(tr("Edit"));
607
tlbEdit->setFixedHeight(iconHeight);
608
tlbEdit->setStyleSheet("QToolButton { border: 0px; padding: 0px; margin: 0px; }");
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);
618
tlbRun = addToolBar(tr("Run"));
620
tlbRun->setFixedHeight(iconHeight);
621
tlbRun->setStyleSheet("QToolButton { border: 0px; padding: 0px; margin: 0px; }");
623
tlbRun->setObjectName("Run");
624
tlbRun->addAction(actRunPython);
625
tlbRun->addAction(actStopPython);
627
tlbTools = addToolBar(tr("Tools"));
629
tlbTools->setFixedHeight(iconHeight);
630
tlbTools->setStyleSheet("QToolButton { border: 0px; padding: 0px; margin: 0px; }");
632
tlbTools->setObjectName("Tools");
633
tlbTools->addAction(actCheckPyLint);
636
QLabel *lblPath = new QLabel();
637
lblPath->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
639
QPushButton *btnPath = new QPushButton(icon("three-dots"), "");
640
btnPath->setMaximumSize(btnPath->sizeHint());
642
connect(btnPath, SIGNAL(clicked()), this, SLOT(doPathChangeDir()));
643
connect(fileBrowser, SIGNAL(directoryChanged(QString)), lblPath, SLOT(setText(QString)));
645
QToolBar *tlbPath = addToolBar(tr("Path"));
647
tlbPath->setFixedHeight(iconHeight);
648
tlbPath->setStyleSheet("QToolButton { border: 0px; padding: 0px; margin: 0px; }");
650
tlbPath->setObjectName("Path");
651
tlbPath->addWidget(new QLabel(tr("Working directory: ")));
652
tlbPath->addWidget(lblPath);
653
tlbPath->addWidget(btnPath);
656
tabWidget = new QTabWidget(this);
657
tabWidget->setDocumentMode(true);
658
tabWidget->setMovable(true);
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()));
668
connect(tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(doCloseTab(int)));
669
connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(doCurrentPageChanged(int)));
672
QHBoxLayout *layout = new QHBoxLayout();
673
layout->addWidget(tabWidget);
675
QWidget *widget = new QWidget(this);
676
widget->setLayout(layout);
678
setCentralWidget(widget);
680
connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(doDataChanged()));
688
void PythonEditorDialog::createViews()
691
fileBrowser = new FileBrowser(this);
692
fileBrowser->setNameFilter("*.py");
693
connect(fileBrowser, SIGNAL(fileItemDoubleClick(QString)), this, SLOT(doFileItemDoubleClick(QString)));
695
QVBoxLayout *layout = new QVBoxLayout();
696
layout->addWidget(fileBrowser);
697
layout->setContentsMargins(0, 0, 0, 7);
699
QWidget *widget = new QWidget(this);
700
widget->setLayout(layout);
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);
708
consoleView = new PythonScriptingConsoleView(pythonEngine, this);
709
consoleView->setAllowedAreas(Qt::AllDockWidgetAreas);
710
addDockWidget(Qt::RightDockWidgetArea, consoleView);
712
consoleHistoryView = new PythonScriptingHistoryView(consoleView->console(), this);
713
consoleHistoryView->setAllowedAreas(Qt::AllDockWidgetAreas);
714
addDockWidget(Qt::LeftDockWidgetArea, consoleHistoryView);
716
variablesView = new PythonBrowserView(pythonEngine, consoleView->console(), this);
717
variablesView->setAllowedAreas(Qt::AllDockWidgetAreas);
718
addDockWidget(Qt::LeftDockWidgetArea, variablesView);
721
void PythonEditorDialog::createStatusBar()
723
lblCurrentPosition = new QLabel(statusBar());
725
statusBar()->showMessage(tr("Ready"));
726
statusBar()->addPermanentWidget(lblCurrentPosition);
729
void PythonEditorDialog::doRunPython()
731
if (pythonEngine->isScriptRunning())
734
if (!scriptEditorWidget()->fileName().isEmpty())
735
fileBrowser->setDir(QFileInfo(scriptEditorWidget()->fileName()).absolutePath());
739
// connect stdout and set current path
740
consoleView->console()->connectStdOut(QFile::exists(scriptEditorWidget()->fileName()) ?
741
QFileInfo(scriptEditorWidget()->fileName()).absolutePath() : "");
746
consoleView->console()->consoleMessage(tr("Run script: %1\n").arg(tabWidget->tabText(tabWidget->currentIndex()).replace("* ", "")), Qt::gray);
748
bool successfulRun = false;
749
if (txtEditor->textCursor().hasSelection())
751
QFileInfo fileInfo(scriptEditorWidget()->fileName());
752
successfulRun = pythonEngine->runScript(txtEditor->textCursor().selectedText().replace(0x2029, "\n"),
753
fileInfo.exists() ? fileInfo.absoluteFilePath() : "");
755
else if (scriptEditorWidget()->fileName().isEmpty())
757
successfulRun = pythonEngine->runScript(txtEditor->toPlainText());
761
if (!scriptEditorWidget()->fileName().isEmpty() &&
762
QFile::exists(scriptEditorWidget()->fileName()))
767
bool useProfiler = settings.value("PythonEditorWidget/UseProfiler", false).toBool();
770
pythonEngine->useProfiler(useProfiler);
771
successfulRun = pythonEngine->runScript(txtEditor->toPlainText(),
772
QFileInfo(scriptEditorWidget()->fileName()).absoluteFilePath());
774
pythonEngine->useProfiler(!useProfiler);
777
txtEditor->setProfiled(useProfiler);
779
txtEditor->setProfilerAccumulatedLines(currentPythonEngine()->profilerAccumulatedLines());
780
txtEditor->setProfilerAccumulatedTimes(currentPythonEngine()->profilerAccumulatedTimes());
782
txtEditor->setProfilerMaxAccumulatedLine(currentPythonEngine()->profilerMaxAccumulatedLine());
783
txtEditor->setProfilerMaxAccumulatedTime(currentPythonEngine()->profilerMaxAccumulatedTime());
784
txtEditor->setProfilerMaxAccumulatedCallLine(currentPythonEngine()->profilerMaxAccumulatedCallLine());
785
txtEditor->setProfilerMaxAccumulatedCall(currentPythonEngine()->profilerMaxAccumulatedCall());
788
txtEditor->updateLineNumberAreaWidth();
792
consoleView->console()->consoleMessage(tr("Finish script: %1\n").arg(milisecondsToTime(time.elapsed()).toString("hh:mm:ss.zzz")), Qt::gray);
795
consoleView->console()->disconnectStdOut();
800
ErrorResult result = pythonEngine->parseError();
802
consoleView->console()->stdErr(result.error());
805
if (settings.value("PythonEditorWidget/PrintStacktrace", true).toBool())
807
consoleView->console()->stdErr("\nStacktrace:\n");
808
consoleView->console()->stdErr(result.traceback());
811
if (!txtEditor->textCursor().hasSelection() && result.line() >= 0)
812
txtEditor->gotoLine(result.line(), true);
814
consoleView->console()->appendCommandPrompt();
819
void PythonEditorDialog::doStopScript()
821
actStopPython->setEnabled(false);
824
consoleView->console()->consoleMessage(tr("\nScript is being aborted.\n"), Qt::blue);
826
currentPythonEngine()->abortScript();
827
QApplication::processEvents();
830
void PythonEditorDialog::doStartedScript()
833
setEnabledControls(false);
834
scriptEditorWidget()->setCursor(Qt::BusyCursor);
836
actRunPython->setEnabled(false);
837
actStopPython->setEnabled(true);
839
// QApplication::processEvents();
842
void PythonEditorDialog::doExecutedScript()
845
setEnabledControls(true);
846
scriptEditorWidget()->setCursor(Qt::ArrowCursor);
848
actRunPython->setEnabled(true);
849
actStopPython->setEnabled(false);
851
if (txtEditor->isVisible())
853
txtEditor->setFocus();
857
void PythonEditorDialog::setEnabledControls(bool state)
859
tlbFile->setEnabled(state);
860
tlbEdit->setEnabled(state);
861
tlbTools->setEnabled(state);
863
txtEditor->setEnabled(state);
864
consoleView->setEnabled(state);
865
consoleHistoryView->setEnabled(state);
866
consoleHistoryView->setEnabled(state);
867
variablesView->setEnabled(state);
868
fileBrowserView->setEnabled(state);
870
menuBar()->setEnabled(state);
873
void PythonEditorDialog::doReplaceTabsWithSpaces()
875
txtEditor->replaceTabsWithSpaces();
878
void PythonEditorDialog::doPyLintPython()
880
if (!scriptEditorWidget()->fileName().isEmpty())
881
fileBrowser->setDir(QFileInfo(scriptEditorWidget()->fileName()).absolutePath());
884
scriptEditorWidget()->pyLintAnalyse();
886
txtEditor->setFocus();
889
void PythonEditorDialog::doOptionsPrintStacktrace()
892
settings.setValue("PythonEditorWidget/PrintStacktrace", actOptionsPrintStacktrace->isChecked());
895
void PythonEditorDialog::doOptionsEnablePyFlakes()
898
settings.setValue("PythonEditorWidget/EnablePyFlakes", actOptionsEnablePyFlakes->isChecked());
901
void PythonEditorDialog::doOptionsEnablePyLint()
903
actCheckPyLint->setEnabled(actOptionsEnablePyLint->isChecked());
906
settings.setValue("PythonEditorWidget/EnablePyLint", actOptionsEnablePyLint->isChecked());
909
void PythonEditorDialog::doOptionsEnableUseProfiler()
912
settings.setValue("PythonEditorWidget/UseProfiler", actOptionsEnableUseProfiler->isChecked());
915
txtEditor->setPlainText(txtEditor->toPlainText());
918
void PythonEditorDialog::doFileItemDoubleClick(const QString &path)
920
QFileInfo fileInfo(path);
923
if (QDir(path).exists())
924
settings.setValue("PythonEditorDialog/WorkDir", path);
927
settings.setValue("PythonEditorDialog/WorkDir", fileInfo.absolutePath());
929
if (fileInfo.suffix() == "py")
930
doFileOpen(fileInfo.absoluteFilePath());
934
void PythonEditorDialog::doPathChangeDir()
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);
942
void PythonEditorDialog::doFileNew()
944
tabWidget->addTab(new PythonEditorWidget(pythonEngine, this), tr("Untitled"));
945
tabWidget->setCurrentIndex(tabWidget->count()-1);
946
doCurrentPageChanged(tabWidget->count()-1);
949
void PythonEditorDialog::doFileOpen(const QString &file)
952
QString dir = settings.value("PythonEditorDialog/WorkDir").toString();
955
QString fileName = file;
956
if (fileName.isEmpty())
957
fileName = QFileDialog::getOpenFileName(this, tr("Open File"), dir, tr("Python scripts (*.py)"));
960
if (!fileName.isEmpty())
962
QFileInfo fileInfo(fileName);
963
if (fileInfo.suffix() != "py")
966
PythonEditorWidget *scriptEditor = scriptEditorWidget();
968
for (int i = 0; i < tabWidget->count(); i++)
970
PythonEditorWidget *scriptEditorWidgetTmp = dynamic_cast<PythonEditorWidget *>(tabWidget->widget(i));
971
if (scriptEditorWidgetTmp->fileName() == fileName)
973
tabWidget->setCurrentIndex(i);
974
QMessageBox::information(this, tr("Information"), tr("Script is already opened."));
979
// check empty document
980
if (!scriptEditor->txtEditor->toPlainText().isEmpty())
984
scriptEditor = scriptEditorWidget();
987
scriptEditor->setFileName(fileName);
988
txtEditor->setPlainText(readFileContent(scriptEditor->fileName()));
992
tabWidget->setTabText(tabWidget->currentIndex(), fileInfo.baseName());
994
doCurrentPageChanged(tabWidget->currentIndex());
996
if (fileInfo.absoluteDir() != tempProblemDir() && !fileName.contains("resources/examples"))
997
settings.setValue("PythonEditorDialog/WorkDir", fileInfo.absolutePath());
1001
void PythonEditorDialog::doFileOpenRecent(QAction *action)
1003
QString fileName = action->text();
1004
if (QFile::exists(fileName))
1006
doFileOpen(fileName);
1011
void PythonEditorDialog::doFileSave()
1014
QString dir = settings.value("PythonEditorDialog/WorkDir").toString();
1017
if (scriptEditorWidget()->fileName().isEmpty())
1018
scriptEditorWidget()->setFileName(QFileDialog::getSaveFileName(this, tr("Save file"), dir, tr("Python scripts (*.py)")));
1021
if (!scriptEditorWidget()->fileName().isEmpty())
1023
QFileInfo fileInfo(scriptEditorWidget()->fileName());
1024
if (fileInfo.suffix() != "py")
1025
scriptEditorWidget()->setFileName(scriptEditorWidget()->fileName() + ".py");
1027
QFile fileName(scriptEditorWidget()->fileName());
1028
if (fileName.open(QFile::WriteOnly | QFile::Text))
1030
QTextStream out(&fileName);
1031
out << txtEditor->toPlainText();
1036
tabWidget->setTabText(tabWidget->currentIndex(), fileInfo.baseName());
1037
txtEditor->document()->setModified(false);
1041
// throw AgrosException(tr("File '%1' cannot be saved.").arg(scriptEditorWidget()->fileName));
1042
qDebug() << tr("File '%1' cannot be saved.").arg(scriptEditorWidget()->fileName());
1045
if (fileInfo.absoluteDir() != tempProblemDir())
1046
settings.setValue("PythonEditorDialog/WorkDir", fileInfo.absolutePath());
1050
void PythonEditorDialog::doFileSaveAs()
1053
QString dir = settings.value("PythonEditorDialog/WorkDir").toString();
1055
QString fileName = QFileDialog::getSaveFileName(this, tr("Save file"), dir, tr("Python scripts (*.py)"));
1056
if (!fileName.isEmpty())
1058
if (QFileInfo(fileName).suffix() != "py")
1061
scriptEditorWidget()->setFileName(fileName);
1064
QFileInfo fileInfo(fileName);
1065
if (fileInfo.absoluteDir() != tempProblemDir())
1066
settings.setValue("PythonEditorDialog/WorkDir", fileInfo.absolutePath());
1070
void PythonEditorDialog::doFileSaveConsoleAs()
1073
QString dir = settings.value("PythonEditorDialog/WorkDir").toString();
1075
QString fileName = QFileDialog::getSaveFileName(this, tr("Save file"), dir, tr("Html files (*.html)"));
1076
if (!fileName.isEmpty())
1078
if (QFileInfo(fileName).suffix() == "html" || QFileInfo(fileName).suffix() == "htm")
1081
fileName += ".html";
1083
QString str = consoleView->console()->document()->toHtml();
1084
writeStringContent(fileName, &str);
1086
QFileInfo fileInfo(fileName);
1087
if (fileInfo.absoluteDir() != tempProblemDir())
1088
settings.setValue("PythonEditorDialog/WorkDir", fileInfo.absolutePath());
1092
void PythonEditorDialog::doFileClose()
1094
doCloseTab(tabWidget->currentIndex());
1097
void PythonEditorDialog::doFilePrint()
1099
QPrinter printer(QPrinter::HighResolution);
1101
QPrintDialog printDialog(&printer, this);
1102
printDialog.addEnabledOption(QAbstractPrintDialog::PrintCollateCopies);
1103
printDialog.setWindowTitle(tr("Print Document"));
1104
if (printDialog.exec() == QDialog::Accepted)
1106
txtEditor->print(&printer);
1110
void PythonEditorDialog::doFind()
1112
QTextCursor cursor = txtEditor->textCursor();
1113
scriptEditorWidget()->searchWidget->showFind(cursor.selectedText());
1116
void PythonEditorDialog::doFindNext(bool fromBegining)
1118
scriptEditorWidget()->searchWidget->findNext(false);
1121
void PythonEditorDialog::doReplace()
1123
QTextCursor cursor = txtEditor->textCursor();
1124
scriptEditorWidget()->searchWidget->showReplaceAll(cursor.selectedText());
1127
void PythonEditorDialog::doDataChanged()
1129
actPaste->setEnabled(!QApplication::clipboard()->text().isEmpty());
1132
void PythonEditorDialog::doHelp()
1134
showPage("scripting/commands.html");
1137
void PythonEditorDialog::doHelpKeywordList()
1139
showPage("scripting/keyword_list.html");
1142
void PythonEditorDialog::doAbout()
1144
AboutDialog about(this);
1148
void PythonEditorDialog::onOtherInstanceMessage(const QString &msg)
1150
QStringList args = msg.split("#!#");
1151
for (int i = 1; i < args.count()-1; i++)
1154
QFile::exists(args[i]) ? args[i] : QApplication::applicationDirPath() + QDir::separator() + args[i];
1156
if (QFile::exists(fileName))
1157
doFileOpen(fileName);
1160
// setWindowState(Qt::WindowMinimized);
1161
// setWindowState(windowState() & ~Qt::WindowMinimized | Qt::WindowActive);
1165
void PythonEditorDialog::doCloseTab(int index)
1167
tabWidget->setCurrentIndex(index);
1169
QString fileName = tr("Untitled");
1170
if (!scriptEditorWidget()->fileName().isEmpty())
1172
QFileInfo fileInfo(scriptEditorWidget()->fileName());
1173
fileName = fileInfo.completeBaseName();
1176
while (txtEditor->document()->isModified())
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)
1183
else if (ret == QMessageBox::Discard)
1185
else if (ret == QMessageBox::Cancel)
1189
if (tabWidget->count() == 1)
1194
tabWidget->removeTab(index);
1197
void PythonEditorDialog::doCurrentPageChanged(int index)
1199
txtEditor = scriptEditorWidget()->txtEditor;
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()));
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()));
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)));
1231
connect(txtEditor->document(), SIGNAL(modificationChanged(bool)), this, SLOT(doCurrentDocumentChanged(bool)));
1234
connect(txtEditor, SIGNAL(cursorPositionChanged()), this, SLOT(doCursorPositionChanged()));
1235
doCursorPositionChanged();
1237
actUndo->setEnabled(txtEditor->document()->isUndoAvailable());
1238
actRedo->setEnabled(txtEditor->document()->isRedoAvailable());
1240
// tabWidget->setTabsClosable(tabWidget->count() > 1);
1241
tabWidget->setTabsClosable(true);
1242
tabWidget->cornerWidget(Qt::TopLeftCorner)->setEnabled(true);
1244
QString fileName = tr("Untitled");
1245
if (!scriptEditorWidget()->fileName().isEmpty())
1247
QFileInfo fileInfo(scriptEditorWidget()->fileName());
1248
fileName = fileInfo.completeBaseName();
1250
setWindowTitle(tr("PythonLab - %1").arg(fileName));
1252
txtEditor->setFocus();
1255
void PythonEditorDialog::doCursorPositionChanged()
1257
QTextCursor cur(txtEditor->textCursor());
1258
lblCurrentPosition->setText(tr("Line: %1, Col: %2").arg(cur.blockNumber()+1)
1259
.arg(cur.columnNumber()+1));
1262
void PythonEditorDialog::doCurrentDocumentChanged(bool changed)
1265
QString fileName = tr("Untitled");
1266
if (!scriptEditorWidget()->fileName().isEmpty())
1268
QFileInfo fileInfo(scriptEditorWidget()->fileName());
1269
fileName = fileInfo.completeBaseName();
1273
tabWidget->setTabText(tabWidget->currentIndex(), QString("* %1").arg(fileName));
1275
tabWidget->setTabText(tabWidget->currentIndex(), fileName);
1278
void PythonEditorDialog::setRecentFiles()
1280
if (!tabWidget) return;
1283
if (!scriptEditorWidget()->fileName().isEmpty())
1285
QFileInfo fileInfo(scriptEditorWidget()->fileName());
1286
if (m_recentFiles.indexOf(fileInfo.absoluteFilePath()) == -1)
1287
m_recentFiles.insert(0, fileInfo.absoluteFilePath());
1289
m_recentFiles.move(m_recentFiles.indexOf(fileInfo.absoluteFilePath()), 0);
1291
while (m_recentFiles.count() > 15) m_recentFiles.removeLast();
1294
mnuRecentFiles->clear();
1295
for (int i = 0; i<m_recentFiles.count(); i++)
1297
QAction *actMenuRecentItem = new QAction(m_recentFiles[i], this);
1298
actFileOpenRecentGroup->addAction(actMenuRecentItem);
1299
mnuRecentFiles->addAction(actMenuRecentItem);
1303
void PythonEditorDialog::closeTabs()
1305
for (int i = tabWidget->count()-1; i >= 0 ; i--)
1309
bool PythonEditorDialog::isScriptModified()
1311
return txtEditor->document()->isModified();
1314
// ********************************************************************************
1316
ScriptEditor::ScriptEditor(PythonEngine *pythonEngine, QWidget *parent)
1317
: PlainTextEditParenthesis(parent), pythonEngine(pythonEngine), m_isProfiled(false), m_isLineNumbersVisible(true)
1319
lineNumberArea = new ScriptEditorLineNumberArea(this);
1322
setTabStopWidth(fontMetrics().width(TABS));
1323
setLineWrapMode(QPlainTextEdit::NoWrap);
1324
setTabChangesFocus(false);
1327
new PythonHighlighter(document());
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()));
1333
updateLineNumberAreaWidth();
1334
highlightCurrentLine();
1336
completer = new PythonCompleter();
1337
completer->setWidget(this);
1338
connect(completer, SIGNAL(activated(const QString&)), this, SLOT(insertCompletion(const QString&)));
1341
ScriptEditor::~ScriptEditor()
1346
void ScriptEditor::resizeEvent(QResizeEvent *e)
1348
QPlainTextEdit::resizeEvent(e);
1350
QRect cr = contentsRect();
1351
lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
1354
void ScriptEditor::updateLineNumberAreaWidth()
1356
setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
1359
void ScriptEditor::updateLineNumberArea(const QRect &rect, int dy)
1363
lineNumberArea->scroll(0, dy);
1367
lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
1370
if (rect.contains(viewport()->rect()))
1371
updateLineNumberAreaWidth();
1374
void ScriptEditor::keyPressEvent(QKeyEvent *event)
1376
QTextCursor cursor = textCursor();
1377
int oldPos = cursor.position();
1378
int indent = firstNonSpace(cursor.block().text());
1380
if (completer && completer->popup()->isVisible())
1382
// The following keys are forwarded by the completer to the widget
1383
switch (event->key())
1385
case Qt::Key_Return:
1387
case Qt::Key_Escape:
1389
case Qt::Key_Backtab:
1392
return; // let the completer do default behavior
1398
if (event->key() == Qt::Key_Tab && !(event->modifiers() & Qt::ShiftModifier))
1400
if (!textCursor().hasSelection())
1402
// insert 4 spaces instead of tab
1403
textCursor().insertText(QString(4, ' '));
1407
// indent the selection
1411
else if (event->key() == Qt::Key_Backtab && (event->modifiers() & Qt::ShiftModifier))
1413
if (!textCursor().hasSelection())
1415
// moves position backward 4 spaces
1416
QTextCursor cursor = textCursor();
1417
cursor.setPosition(cursor.position() - 4, QTextCursor::MoveAnchor);
1418
setTextCursor(cursor);
1422
// unindent the selection
1423
unindentSelection();
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) == ' '))
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;
1439
cursor.setPosition(oldPos, QTextCursor::MoveAnchor);
1440
cursor.setPosition(newPos, QTextCursor::KeepAnchor);
1441
cursor.deleteChar();
1442
cursor.endEditBlock();
1443
setTextCursor(cursor);
1445
else if ((event->key() == Qt::Key_Return) && (indent))
1447
cursor.beginEditBlock();
1449
// add 1 extra indent if current line begins a code block
1450
bool inCodeBlock = false;
1451
if (QRegExp("\\:").indexIn(cursor.block().text()) != -1)
1453
indent += TABS_SIZE;
1457
cursor.insertBlock();
1458
QString spaces(indent, true ? QLatin1Char(' ') : QLatin1Char('\t'));
1459
cursor.insertText(spaces);
1461
cursor.endEditBlock();
1462
setTextCursor(cursor);
1466
QPlainTextEdit::keyPressEvent(event);
1469
if ((event->key() == Qt::Key_Space && event->modifiers() & Qt::ControlModifier)
1470
|| completer->popup()->isVisible())
1472
QTextCursor tc = textCursor();
1473
QStringList found = pythonEngine->codeCompletionScript(toPlainText(),
1474
tc.blockNumber() + 1,
1475
tc.columnNumber() + 1);
1477
QString textToComplete = textCursor().block().text().trimmed();
1478
QStringList foundInterpreter = pythonEngine->codeCompletionInterpreter(textToComplete);
1480
found << foundInterpreter;
1481
found.removeDuplicates();
1483
if (!found.isEmpty())
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);
1497
completer->popup()->hide();
1502
void ScriptEditor::indentSelection()
1504
indentAndUnindentSelection(true);
1507
void ScriptEditor::unindentSelection()
1509
indentAndUnindentSelection(false);
1512
void ScriptEditor::indentAndUnindentSelection(bool doIndent)
1514
QTextCursor cursor = textCursor();
1515
cursor.beginEditBlock();
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);
1523
QTextDocument *doc = document();
1524
QTextBlock startBlock = doc->findBlock(start);
1525
QTextBlock endBlock = doc->findBlock(end-1).next();
1527
for (QTextBlock block = startBlock; block != endBlock; block = block.next())
1529
QString text = block.text();
1532
int indentPosition = firstNonSpace(text);
1533
cursor.setPosition(block.position() + indentPosition);
1534
cursor.insertText(QString(TABS_SIZE, ' '));
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();
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);
1551
cursor.endEditBlock();
1552
setTextCursor(cursor);
1555
void ScriptEditor::commentAndUncommentSelection()
1557
QTextCursor cursor = textCursor();
1558
cursor.beginEditBlock();
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();
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();
1575
// process first block
1576
cursor.setPosition(selStart, QTextCursor::MoveAnchor);
1577
QRegExp commentPattern("^#");
1578
if (commentPattern.indexIn(cursor.block().text()) == -1)
1580
// comment it, if the block does not starts with '#'
1581
cursor.insertText("#");
1586
// else uncomment it
1587
cursor.setPosition(selStart + commentPattern.matchedLength(), QTextCursor::KeepAnchor);
1588
cursor.deleteChar();
1592
// loop through all blocks
1593
while (cursor.blockNumber() < blockEnd)
1595
cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor);
1596
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
1597
if (commentPattern.indexIn(cursor.block().text()) == -1)
1599
cursor.insertText("#");
1604
cursor.setPosition(cursor.position() + commentPattern.matchedLength(), QTextCursor::KeepAnchor);
1605
cursor.deleteChar();
1610
// restore selection state
1611
cursor.setPosition(selStart, QTextCursor::MoveAnchor);
1612
cursor.setPosition(selEnd, QTextCursor::KeepAnchor);
1615
cursor.endEditBlock();
1616
setTextCursor(cursor);
1619
void ScriptEditor::gotoLine(int line, bool isError)
1621
// use dialog when (line == -1)
1625
int lineDialog = QInputDialog::getInt(this, tr("Goto line"), tr("Line number:"),
1626
0, 1, document()->blockCount(), 1, &ok);
1631
if (line >= 0 && line <= document()->blockCount())
1633
int pos = document()->findBlockByNumber(line - 1).position();
1634
QTextCursor cur = textCursor();
1635
cur.setPosition(pos, QTextCursor::MoveAnchor);
1637
ensureCursorVisible();
1638
highlightCurrentLine(true);
1643
void ScriptEditor::highlightCurrentLine(bool isError)
1645
QList<QTextEdit::ExtraSelection> selections;
1649
QTextEdit::ExtraSelection selection;
1650
QColor lineColor = QColor(Qt::yellow).lighter(180);
1652
lineColor = QColor(Qt::red).lighter(180);
1654
selection.format.setBackground(lineColor);
1655
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
1656
selection.cursor = textCursor();
1657
selection.cursor.clearSelection();
1658
selections.append(selection);
1661
setExtraSelections(selections);
1663
matchParentheses('(', ')');
1664
// matchParentheses('[', ']');
1665
// matchParentheses('{', '}');
1668
void ScriptEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
1671
const QBrush bookmarkBrushPyFlakes(QColor(Qt::red).lighter());
1677
timesWidth = fontMetrics().width(QLatin1Char('9')) * QString::number(profilerMaxAccumulatedTime()).length() + 1;
1678
callWidth = fontMetrics().width(QLatin1Char('9')) * QString::number(profilerMaxAccumulatedCall()).length() + 1;
1681
QPainter painter(lineNumberArea);
1682
painter.fillRect(event->rect(), Qt::lightGray);
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();
1689
while (block.isValid() && top <= event->rect().bottom())
1691
if (block.isVisible() && bottom >= event->rect().top())
1694
QString lineNumber = QString::number(blockNumber + 1);
1697
if (errorMessagesPyFlakes.contains(blockNumber + 1))
1698
painter.fillRect(0, top, lineNumberArea->width(), fontMetrics().height(),
1699
bookmarkBrushPyFlakes);
1702
painter.setPen(Qt::black);
1703
painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
1704
Qt::AlignRight, lineNumber);
1706
// draw profiler number
1709
if (profilerAccumulatedTimes().value(blockNumber + 1) > 0)
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);
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);
1725
block = block.next();
1727
bottom = top + (int) blockBoundingRect(block).height();
1732
void ScriptEditor::lineNumberAreaMouseMoveEvent(QMouseEvent *event)
1734
QTextBlock block = firstVisibleBlock();
1735
int blockNumber = block.blockNumber();
1737
int line = blockNumber + event->pos().y() / (int) blockBoundingRect(block).height() + 1;
1739
if (line <= document()->blockCount())
1741
if (errorMessagesPyFlakes.contains(line))
1742
QToolTip::showText(event->globalPos(), errorMessagesPyFlakes[line]);
1746
int ScriptEditor::lineNumberAreaWidth()
1748
if (m_isLineNumbersVisible)
1751
int max = qMax(1, blockCount());
1759
digits += QString::number(profilerMaxAccumulatedTime()).length() +
1760
QString::number(profilerMaxAccumulatedCall()).length();
1763
int space = 15 + fontMetrics().width(QLatin1Char('9')) * digits;
1773
void ScriptEditor::insertCompletion(const QString& completion)
1775
QString str = completion.left(completion.indexOf("(") - 1);
1777
QTextCursor tc = textCursor();
1778
tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
1779
// if (tc.selectedText() == ".")
1781
// tc.insertText(QString(".") + str);
1786
tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
1787
tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
1793
void ScriptEditor::replaceTabsWithSpaces()
1795
QString text = document()->toPlainText();
1796
text = text.replace("\t", TABS);
1797
document()->setPlainText(text);
1800
// ********************************************************************************************************
1802
SearchWidget::SearchWidget(ScriptEditor *txtEditor, QWidget *parent)
1803
: QWidget(parent), txtEditor(txtEditor)
1805
lblFind = new QLabel(tr("Search for:"));
1806
lblReplace = new QLabel(tr("Replace with:"));
1808
txtFind = new QLineEdit();
1809
connect(txtFind, SIGNAL(returnPressed()), this, SLOT(find()));
1810
txtReplace = new QLineEdit();
1811
connect(txtReplace, SIGNAL(returnPressed()), this, SLOT(replaceAll()));
1813
btnFind = new QPushButton(tr("Find"), this);
1814
btnFind->setDefault(true);
1815
connect(btnFind, SIGNAL(clicked()), this, SLOT(find()));
1817
btnReplace = new QPushButton(tr("Replace all"), this);
1818
connect(btnReplace, SIGNAL(clicked()), this, SLOT(replaceAll()));
1820
btnHide = new QPushButton(tr("Hide"), this);
1821
connect(btnHide, SIGNAL(clicked()), this, SLOT(hideWidget()));
1823
QGridLayout *findReplaceLayout = new QGridLayout();
1824
findReplaceLayout->setMargin(2);
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);
1834
setLayout(findReplaceLayout);
1836
lblReplace->setVisible(false);
1837
txtReplace->setVisible(false);
1838
btnReplace->setVisible(false);
1843
void SearchWidget::keyPressEvent(QKeyEvent *event)
1845
int key = event->key();
1848
case Qt::Key_Escape:
1852
QWidget::keyPressEvent(event);
1856
void SearchWidget::showFind(const QString &text)
1858
if (!text.isEmpty())
1859
txtFind->setText(text);
1861
txtFind->setFocus();
1862
txtFind->selectAll();
1863
lblReplace->setVisible(false);
1864
txtReplace->setVisible(false);
1865
btnReplace->setVisible(false);
1867
startFromBeginning = true;
1872
void SearchWidget::showReplaceAll(const QString &text)
1874
if (!text.isEmpty())
1875
txtFind->setText(text);
1877
txtFind->setFocus();
1878
txtFind->selectAll();
1879
lblReplace->setVisible(true);
1880
txtReplace->setVisible(true);
1881
btnReplace->setVisible(true);
1886
void SearchWidget::find()
1888
findNext(startFromBeginning);
1889
startFromBeginning = false;
1892
void SearchWidget::findNext(bool fromBegining)
1894
if (!txtFind->text().isEmpty())
1897
QTextCursor cursor = txtEditor->textCursor();
1899
cursor = txtEditor->document()->find(txtFind->text());
1901
cursor = txtEditor->document()->find(txtFind->text(), cursor);
1903
if (cursor.position() >= 0)
1904
txtEditor->setTextCursor(cursor);
1905
txtEditor->setFocus();
1908
txtFind->setFocus();
1912
void SearchWidget::replaceAll()
1914
if (!txtFind->text().isEmpty())
1916
QTextCursor cursor = txtEditor->textCursor();
1918
QString text = txtEditor->document()->toPlainText();
1919
text.replace(txtFind->text(), txtReplace->text());
1920
txtEditor->document()->setPlainText(text);
1922
txtEditor->setTextCursor(cursor);
1928
void SearchWidget::hideWidget()
1931
txtEditor->setFocus();