1 /** Copyright (C) 2006, Ian Paul Larsen.
2 **
3 ** This program 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 ** This program 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 along
14 ** with this program; if not, write to the Free Software Foundation, Inc.,
15 ** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16 **/
17
18
19
20 #include <iostream>
21 #include <QScrollBar>
22 #include <QTextCursor>
23 #include <QTextBlock>
24 #include <QFlags>
25 #include <QPainter>
26 #include <QResizeEvent>
27 #include <QPaintEvent>
28 #include <QtWidgets/QMessageBox>
29 #include <QtWidgets/QStatusBar>
30 #include <QtPrintSupport/QPrinter>
31 #include <QtPrintSupport/QPrintDialog>
32 #include <QtWidgets/QFontDialog>
33
34 #include "MainWindow.h"
35 #include "BasicEdit.h"
36 #include "LineNumberArea.h"
37 #include "Settings.h"
38 #include "Constants.h"
39
40 extern int guiState;
41
BasicEdit(const QString & defaulttitle)42 BasicEdit::BasicEdit(const QString & defaulttitle) {
43 currentLine = 1;
44 runState = RUNSTATESTOP;
45 rightClickBlockNumber = -1;
46 breakPoints = new QList<int>;
47 title = defaulttitle;
48 windowtitle = defaulttitle;
49 filename.clear();
50 path.clear();
51 undoButton = false;
52 redoButton = false;
53 copyButton = false;
54 action = new QAction(windowtitle, this);
55 action->setCheckable(true);
56 fileChangedOnDiskFlag = false;
57
58 setReadOnly(guiState!=GUISTATENORMAL);
59 if(guiState==GUISTATEAPP){
60 startPos=0;
61 lineNumberArea = NULL;
62 setDisabled(true);
63 }else{
64 lineNumberArea = new LineNumberArea(this);
65 this->setInputMethodHints(Qt::ImhNoPredictiveText);
66 startPos = this->textCursor().position();
67 updateLineNumberAreaWidth(0);
68 connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
69 connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int)));
70 connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(cursorMove()));
71 connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
72 connect(this, SIGNAL(modificationChanged(bool)), this, SLOT(updateTitle()));
73 connect(this, SIGNAL(undoAvailable(bool)), this, SLOT(slotUndoAvailable(bool)));
74 connect(this, SIGNAL(redoAvailable(bool)), this, SLOT(slotRedoAvailable(bool)));
75 connect(this, SIGNAL(copyAvailable(bool)), this, SLOT(slotCopyAvailable(bool)));
76 connect(action, SIGNAL(triggered()), this, SLOT(actionWasTriggered()));
77 highlightCurrentLine();
78 }
79 }
80
81
~BasicEdit()82 BasicEdit::~BasicEdit() {
83 if (breakPoints) {
84 delete breakPoints;
85 breakPoints = NULL;
86 }
87 if (lineNumberArea) {
88 delete lineNumberArea;
89 lineNumberArea = NULL;
90 }
91 }
92
setFont(QFont f)93 void BasicEdit::setFont(QFont f) {
94 // set the font and the tab stop at EDITOR_TAB_WIDTH spaces
95 QPlainTextEdit::setFont(f);
96 QFontMetrics metrics(f);
97 setTabStopWidth(metrics.width(" ")*EDITOR_TAB_WIDTH);
98 updateLineNumberAreaWidth(blockCount());
99 }
100
101
102 void
cursorMove()103 BasicEdit::cursorMove() {
104 QTextCursor t(textCursor());
105 emit(changeStatusBar(tr("Line: ") + QString::number(t.blockNumber()+1)
106 + tr(" Character: ") + QString::number(t.positionInBlock())));
107 }
108
109 void
seekLine(int newLine)110 BasicEdit::seekLine(int newLine) {
111 // go to a line number and set
112 // the text cursor
113 //
114 // code should be proximal in that it should be closest to look at the curent
115 // position than to go and search the entire program from the top
116 QTextCursor t = textCursor();
117 int line = t.blockNumber()+1; // current line number for the block
118 // go back or forward to the line from the current position
119 if (line<newLine) {
120 // advance forward
121 while (line < newLine && t.movePosition(QTextCursor::NextBlock)) {
122 line++;
123 }
124 } else if (line>newLine){
125 // go back to find the line
126 while (line > newLine && t.movePosition(QTextCursor::PreviousBlock)) {
127 line--;
128 }
129 }
130 setTextCursor(t);
131 }
132
slotWhitespace(bool checked)133 void BasicEdit::slotWhitespace(bool checked) {
134 // toggle the display of whitespace characters
135 // http://www.qtcentre.org/threads/27245-Printing-white-spaces-in-QPlainTextEdit-the-QtCreator-way
136 QTextOption option = document()->defaultTextOption();
137 if (checked) {
138 option.setFlags(option.flags() | QTextOption::ShowTabsAndSpaces);
139 } else {
140 option.setFlags(option.flags() & ~QTextOption::ShowTabsAndSpaces);
141 }
142 option.setFlags(option.flags() | QTextOption::AddSpaceForLineAndParagraphSeparators);
143 document()->setDefaultTextOption(option);
144 }
145
146 void
goToLine(int newLine)147 BasicEdit::goToLine(int newLine) {
148 seekLine(newLine);
149 setFocus();
150 }
151
152
153 void
keyPressEvent(QKeyEvent * e)154 BasicEdit::keyPressEvent(QKeyEvent *e) {
155 e->accept();
156 //Autoindent new line as previous one
157 if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter){
158 QPlainTextEdit::keyPressEvent(e);
159 QTextCursor cur = textCursor();
160 cur.movePosition(QTextCursor::PreviousBlock);
161 cur.movePosition(QTextCursor::StartOfBlock);
162 cur.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
163 QString str = cur.selectedText();
164 QRegExp rx("^([\\t ]+)");
165 if(str.indexOf(rx) >= 0)
166 textCursor().insertText(rx.cap(1));
167 }else if(e->key() == Qt::Key_Tab && e->modifiers() == Qt::NoModifier){
168 if(!indentSelection())
169 QPlainTextEdit::keyPressEvent(e);
170 }else if((e->key() == Qt::Key_Tab && e->modifiers() & Qt::ShiftModifier) || e->key() == Qt::Key_Backtab){
171 unindentSelection();
172 }else{
173 QPlainTextEdit::keyPressEvent(e);
174 }
175 }
176
177
saveFile(bool overwrite)178 void BasicEdit::saveFile(bool overwrite) {
179 // BE SURE TO SET filename PROPERTY FIRST
180 // or set it to '' to prompt for a new file name
181 if (filename == "") {
182 emit(setCurrentEditorTab(this)); //activate editor window
183 filename = QFileDialog::getSaveFileName(this, tr("Save file as"), title+".kbs", tr("BASIC-256 File ") + "(*.kbs);;" + tr("Any File ") + "(*.*)");
184 }
185
186 if (filename != "") {
187 QRegExp rx("\\.[^\\/]*$");
188 if (rx.indexIn(filename) == -1) {
189 filename += ".kbs";
190 }
191 QFile f(filename);
192 bool dooverwrite = true;
193 if (!overwrite && f.exists()) {
194 dooverwrite = ( QMessageBox::Yes == QMessageBox::warning(this, tr("Save File"),
195 tr("The file ") + filename + tr(" already exists.")+ "\n" +tr("Do you want to overwrite?"),
196 QMessageBox::Yes | QMessageBox::No,
197 QMessageBox::No));
198 }
199 if (dooverwrite) {
200 f.open(QIODevice::WriteOnly | QIODevice::Truncate);
201 f.write(this->document()->toPlainText().toUtf8());
202 f.close();
203 QFileInfo fi(f);
204 document()->setModified(false);
205 setTitle(fi.fileName());
206 QDir::setCurrent(fi.absolutePath());
207 emit(addFileToRecentList(filename));
208 }
209 }
210 }
211
saveAllStep(int s)212 void BasicEdit::saveAllStep(int s) {
213 if(document()->isModified()){
214 if(s==1){
215 // Step 1 - save changes
216 if(!filename.isEmpty()) saveFile(true);
217 }else{
218 // Step 2 - save unsaved files (need user interaction)
219 if(filename.isEmpty()) saveFile(true);
220 }
221 }
222 }
223
saveProgram()224 void BasicEdit::saveProgram() {
225 saveFile(false);
226 }
227
228 void
saveAsProgram()229 BasicEdit::saveAsProgram() {
230 QString tempfilename = QFileDialog::getSaveFileName(this, tr("Save file as"), ".", tr("BASIC-256 File ")+ "(*.kbs);;" + tr("Any File ")+ "(*.*)");
231 if (tempfilename != "") {
232 filename = tempfilename;
233 saveFile(false);
234 }
235 }
236
237
238
239
slotPrint()240 void BasicEdit::slotPrint() {
241 #ifdef ANDROID
242 QMessageBox::warning(this, tr("Print"),
243 tr("Printing is not supported in this platform at this time."));
244 #else
245 QTextDocument *document = this->document();
246 QPrinter printer;
247
248 QPrintDialog *dialog = new QPrintDialog(&printer, this);
249 dialog->setWindowTitle(tr("Print Code"));
250
251 if (dialog->exec() == QDialog::Accepted) {
252 if ((printer.printerState() != QPrinter::Error) && (printer.printerState() != QPrinter::Aborted)) {
253
254 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
255 document->print(&printer);
256 QApplication::restoreOverrideCursor();
257 } else {
258 QMessageBox::warning(this, tr("Print"),
259 tr("Unable to carry out printing.\nPlease check your printer settings."));
260 }
261
262 }
263
264 delete dialog;
265 #endif
266 }
267
268
269
beautifyProgram()270 void BasicEdit::beautifyProgram() {
271 QString program;
272 QStringList lines;
273 int indent = 0;
274 bool indentThisLine = true;
275 bool increaseIndent = false;
276 bool decreaseIndent = false;
277 bool increaseIndentDouble = false;
278 bool decreaseIndentDouble = false;
279 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
280 program = this->document()->toPlainText();
281 lines = program.split(QRegExp("\\n"));
282 for (int i = 0; i < lines.size(); i++) {
283 QString line = lines.at(i);
284 line = line.trimmed();
285 if(line.isEmpty()){
286 // label - empty line no indent
287 indentThisLine = false;
288 } else if (line.contains(QRegExp("^\\S+[:]"))) {
289 // label - one line no indent
290 indentThisLine = false;
291 } else if (line.contains(QRegExp("^(for)|(foreach)\\s", Qt::CaseInsensitive))) {
292 // for - indent next (block of code)
293 increaseIndent = true;
294 } else if (line.contains(QRegExp("^next(\\s)", Qt::CaseInsensitive))) {
295 // next var - come out of block - reduce indent
296 decreaseIndent = true;
297 } else if (line.contains(QRegExp("^next$", Qt::CaseInsensitive))) {
298 // next - come out of block - reduce indent
299 decreaseIndent = true;
300 } else if (line.contains(QRegExp("^if\\s.+\\sthen\\s*((#|(rem\\s)).*)?$", Qt::CaseInsensitive))) {
301 // if/then (NOTHING FOLLOWING) - indent next (block of code)
302 increaseIndent = true;
303 } else if (line.contains(QRegExp("^else\\s*((#|(rem\\s)).*)?$", Qt::CaseInsensitive))) {
304 // else - come out of block and start new block
305 decreaseIndent = true;
306 increaseIndent = true;
307 } else if (line.contains(QRegExp("^end\\s*if\\s*((#|(rem\\s)).*)?$", Qt::CaseInsensitive))) {
308 // end if - come out of block - reduce indent
309 decreaseIndent = true;
310 } else if (line.contains(QRegExp("^while\\s", Qt::CaseInsensitive))) {
311 // while - indent next (block of code)
312 increaseIndent = true;
313 } else if (line.contains(QRegExp("^end\\s*while\\s*((#|(rem\\s)).*)?$", Qt::CaseInsensitive))) {
314 // endwhile - come out of block
315 decreaseIndent = true;
316 } else if (line.contains(QRegExp("^function\\s", Qt::CaseInsensitive))) {
317 // function - indent next (block of code)
318 increaseIndent = true;
319 } else if (line.contains(QRegExp("^end\\s*function\\s*((#|(rem\\s)).*)?$", Qt::CaseInsensitive))) {
320 // endfunction - come out of block
321 decreaseIndent = true;
322 } else if (line.contains(QRegExp("^subroutine\\s", Qt::CaseInsensitive))) {
323 // function - indent next (block of code)
324 increaseIndent = true;
325 } else if (line.contains(QRegExp("^end\\s*subroutine\\s*((#|(rem\\s)).*)?$", Qt::CaseInsensitive))) {
326 // endfunction - come out of block
327 decreaseIndent = true;
328 } else if (line.contains(QRegExp("^do\\s*((#|(rem\\s)).*)?$", Qt::CaseInsensitive))) {
329 // do - indent next (block of code)
330 increaseIndent = true;
331 } else if (line.contains(QRegExp("^until\\s", Qt::CaseInsensitive))) {
332 // until - come out of block
333 decreaseIndent = true;
334 } else if (line.contains(QRegExp("^try\\s*((#|(rem\\s)).*)?$", Qt::CaseInsensitive))) {
335 // try indent next (block of code)
336 increaseIndent = true;
337 } else if (line.contains(QRegExp("^catch\\s*((#|(rem\\s)).*)?$", Qt::CaseInsensitive))) {
338 // catch - come out of block and start new block
339 decreaseIndent = true;
340 increaseIndent = true;
341 } else if (line.contains(QRegExp("^end\\s*try\\s*((#|(rem\\s)).*)?$", Qt::CaseInsensitive))) {
342 // end try - come out of block - reduce indent
343 decreaseIndent = true;
344 } else if (line.contains(QRegExp("^begin\\s*case\\s*((#|(rem\\s)).*)?$", Qt::CaseInsensitive))) {
345 // begin case double indent next (block of code)
346 increaseIndentDouble = true;
347 } else if (line.contains(QRegExp("^end\\s*case\\s*((#|(rem\\s)).*)?$", Qt::CaseInsensitive))) {
348 // end case double reduce
349 decreaseIndentDouble = true;
350 } else if (line.contains(QRegExp("^case\\s.+\\s*((#|(rem\\s)).*)?$", Qt::CaseInsensitive))) {
351 // case expression - indent one line
352 decreaseIndent = true;
353 increaseIndent = true;
354 }
355 //
356 if (decreaseIndent) {
357 indent--;
358 if (indent<0) indent=0;
359 decreaseIndent = false;
360 }
361 if (decreaseIndentDouble) {
362 indent-=2;
363 if (indent<0) indent=0;
364 decreaseIndentDouble = false;
365 }
366 if (indentThisLine) {
367 line = QString(indent, QChar('\t')) + line;
368 } else {
369 indentThisLine = true;
370 }
371 if (increaseIndent) {
372 indent++;
373 increaseIndent = false;
374 }
375 if (increaseIndentDouble) {
376 indent+=2;
377 increaseIndentDouble = false;
378 }
379 //
380 lines.replace(i, line);
381 }
382 //this->setPlainText(lines.join("\n"));
383 QTextCursor cursor = this->textCursor();
384 cursor.select(QTextCursor::Document);
385 cursor.insertText(lines.join("\n"));
386 QApplication::restoreOverrideCursor();
387 }
388
findString(QString s,bool reverse,bool casesens,bool words)389 void BasicEdit::findString(QString s, bool reverse, bool casesens, bool words)
390 {
391 if(s.length()==0) return;
392 QTextDocument::FindFlags flag;
393 if (reverse) flag |= QTextDocument::FindBackward;
394 if (casesens) flag |= QTextDocument::FindCaseSensitively;
395 if (words) flag |= QTextDocument::FindWholeWords;
396
397 QTextCursor cursor = this->textCursor();
398 // here we save the cursor position and the verticalScrollBar value
399 QTextCursor cursorSaved = cursor;
400 int scroll = verticalScrollBar()->value();
401
402 if (!find(s, flag))
403 {
404 //nothing is found | jump to start/end
405 setUpdatesEnabled(false);
406 cursor.movePosition(reverse?QTextCursor::End:QTextCursor::Start);
407 setTextCursor(cursor);
408
409 if (!find(s, flag))
410 {
411 // word not found : we set the cursor back to its initial position and restore verticalScrollBar value
412 setTextCursor(cursorSaved);
413 verticalScrollBar()->setValue(scroll);
414 setUpdatesEnabled(true);
415 QMessageBox::information(this, tr("Find"),
416 tr("String not found."),
417 QMessageBox::Ok, QMessageBox::Ok);
418 }else{
419 //start from current screen position -> no scroll or scroll only as needed
420 //if next search is in the same view
421 verticalScrollBar()->setValue(scroll);
422 // we can use ensureCursorVisible() but we want to make sure that entire selection is visible
423 cursor = this->textCursor();
424 cursorSaved = cursor;
425 cursor.setPosition(cursor.selectionStart());
426 setTextCursor(cursor);
427 setTextCursor(cursorSaved);
428 setUpdatesEnabled(true);
429 }
430 }
431 }
432
replaceString(QString from,QString to,bool reverse,bool casesens,bool words,bool doall)433 void BasicEdit::replaceString(QString from, QString to, bool reverse, bool casesens, bool words, bool doall) {
434 if(from.length()==0) return;
435
436 // Replace one time.
437 if(!doall){
438 //Replace if text is selected - use the cursor from the last find not the copy
439 QTextCursor cursor = this->textCursor();
440 if (from.compare(cursor.selectedText(),(casesens ? Qt::CaseSensitive : Qt::CaseInsensitive))==0){
441 cursor.insertText(to);
442 }
443
444 //Make a search
445 findString(from, reverse, casesens, words);
446
447 //Replace all
448 }else{
449 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
450 setUpdatesEnabled(false);
451 QTextCursor cursor = this->textCursor();
452 QTextCursor cursorSaved = cursor;
453 int scroll = verticalScrollBar()->value();
454 cursor.beginEditBlock();
455 int n = 0;
456 cursor.movePosition(QTextCursor::Start);
457 setTextCursor(cursor);
458 QTextDocument::FindFlags flag;
459 if (casesens) flag |= QTextDocument::FindCaseSensitively;
460 if (words) flag |= QTextDocument::FindWholeWords;
461 while (find(from, flag)){
462 if (textCursor().hasSelection()){
463 textCursor().insertText(to);
464 n++;
465 }
466 }
467 // set the cursor back to its initial position and restore verticalScrollBar value
468 setTextCursor(cursorSaved);
469 verticalScrollBar()->setValue(scroll);
470 cursor.endEditBlock();
471 setUpdatesEnabled(true);
472 QApplication::restoreOverrideCursor();
473 if(n==0)
474 QMessageBox::information(this, tr("Replace"),
475 tr("String not found."),
476 QMessageBox::Ok, QMessageBox::Ok);
477 else
478 QMessageBox::information(this, tr("Replace"),
479 tr("Replace completed.") + "\n" + QString::number(n) + " " + tr("occurrence(s) were replaced."),
480 QMessageBox::Ok, QMessageBox::Ok);
481 }
482 }
483
getCurrentWord()484 QString BasicEdit::getCurrentWord() {
485 // get the cursor and find the current word on this line
486 // anything but letters delimits
487 QString w;
488 QTextCursor t(textCursor());
489 QTextBlock b(t.block());
490 w = b.text();
491 w = w.left(w.indexOf(QRegExp("[^a-zA-Z0-9]"),t.positionInBlock()));
492 w = w.mid(w.lastIndexOf(QRegExp("[^a-zA-Z0-9]"))+1);
493 return w;
494 }
495
lineNumberAreaWidth()496 int BasicEdit::lineNumberAreaWidth() {
497 int digits = 1;
498 int max = qMax(1, blockCount());
499 while (max >= 10) {
500 max /= 10;
501 ++digits;
502 }
503 int space = 10 + fontMetrics().width(QLatin1Char('9')) * digits;
504 return space;
505 }
506
updateLineNumberAreaWidth(int)507 void BasicEdit::updateLineNumberAreaWidth(int /* newBlockCount */) {
508 //update setViewportMargins only when lineNumberAreaWidth is changed - save CPU
509 //(this signal is emitted even when user scroll up or scroll down code)
510 int w = lineNumberAreaWidth();
511 if(w!=lastLineNumberAreaWidth){
512 lastLineNumberAreaWidth=w;
513 setViewportMargins(w, 0, 0, 0);
514 }
515 }
516
updateLineNumberArea(const QRect & rect,int dy)517 void BasicEdit::updateLineNumberArea(const QRect &rect, int dy) {
518 if (dy) {
519 lineNumberArea->scroll(0, dy);
520 } else {
521 lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
522 }
523 }
524
525
526
highlightCurrentLine()527 void BasicEdit::highlightCurrentLine() {
528 if(guiState==GUISTATEAPP) return;
529 QList<QTextEdit::ExtraSelection> extraSelections;
530 QTextEdit::ExtraSelection blockSelection;
531 QTextEdit::ExtraSelection selection;
532 QColor lineColor;
533 QColor blockColor;
534
535 if(guiState==GUISTATERUN || runState==RUNSTATERUN){
536 // editor is in readOnly mode so line will be red (forbidden)
537 lineColor = QColor(Qt::red).lighter(175);
538 blockColor = QColor(Qt::red).lighter(190);
539 }else if (runState==RUNSTATERUNDEBUG || runState==RUNSTATEDEBUG) {
540 // if we are executing in debug mode
541 lineColor = QColor(Qt::green).lighter(175);
542 blockColor = lineColor;
543 }else{
544 // in edit mode
545 lineColor = QColor(Qt::yellow).lighter(165);
546 blockColor = QColor(Qt::yellow).lighter(190);
547 }
548
549 blockSelection.format.setBackground(blockColor);
550 blockSelection.format.setProperty(QTextFormat::FullWidthSelection, true);
551 blockSelection.cursor = textCursor();
552 blockSelection.cursor.movePosition(QTextCursor::StartOfBlock,QTextCursor::MoveAnchor);
553 blockSelection.cursor.movePosition(QTextCursor::EndOfBlock,QTextCursor::KeepAnchor);
554 extraSelections.append(blockSelection);
555
556 selection.format.setBackground(lineColor);
557 selection.format.setProperty(QTextFormat::FullWidthSelection, true);
558 selection.cursor = textCursor();
559 selection.cursor.clearSelection();
560 extraSelections.append(selection);
561
562
563 //mark brackets if we are editing
564 if (runState==RUNSTATESTOP && !isReadOnly()) {
565 QTextCursor cur = textCursor();
566 int pos = cur.position();
567 cur.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor);
568 cur.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
569 int posDoc = cur.position();
570 pos = pos - posDoc -1;
571 QString s = cur.selectedText(); //current line as QString
572 int length = s.length();
573 QChar cPrev, cNext;
574
575
576 if(length>1){
577 QChar open;
578 QChar close;
579 QChar c;
580 bool lookBefore = false;
581 bool lookAfter = false;
582 int quote=0;
583 int doubleQuote=0;
584 int i = 0;
585 const QColor bracketColor = QColor(Qt::green).lighter(165);
586 QTextEdit::ExtraSelection selectionBracket;
587
588 if(pos>0){
589 cPrev = s.at(pos);
590 if(cPrev==')'){
591 open = '(';
592 lookBefore = true;
593 }else if(cPrev==']'){
594 open = '[';
595 lookBefore = true;
596 }else if(cPrev=='}'){
597 open = '{';
598 lookBefore = true;
599 }
600 }
601
602 if(pos<length-1){
603 cNext = s.at(pos+1);
604 if(cNext=='('){
605 close = ')';
606 lookAfter = true;
607 }else if(cNext=='['){
608 close = ']';
609 lookAfter = true;
610 }else if(cNext=='{'){
611 close = '}';
612 lookAfter = true;
613 }
614 }
615
616 if(lookBefore){
617 //check for open bracket
618 QList<int> list;
619 for(i=0;i<pos;i++){
620 c=s.at(i);
621 if(c=='\"' && quote==0){
622 doubleQuote^=1;
623 }else if(c=='\'' && doubleQuote==0){
624 quote^=1;
625 }else if(quote==0 && doubleQuote==0){
626 if(c==open){
627 list.append(i);
628 }else if(c==cPrev){
629 if(!list.isEmpty())
630 list.removeLast();
631 }
632 }
633 }
634 if(quote==0 && doubleQuote==0 && !list.isEmpty()){
635 //mark current bracket and the corresponding bracket
636 selectionBracket.format.setBackground(bracketColor);
637 selectionBracket.cursor = textCursor();
638 selectionBracket.cursor.setPosition(pos+posDoc+1);
639 selectionBracket.cursor.movePosition(QTextCursor::PreviousCharacter,QTextCursor::KeepAnchor);
640 extraSelections.append(selectionBracket);
641 selectionBracket.cursor.setPosition(list.last()+posDoc+1);
642 selectionBracket.cursor.movePosition(QTextCursor::PreviousCharacter,QTextCursor::KeepAnchor);
643 extraSelections.append(selectionBracket);
644 }
645 }else if(lookAfter){
646 //else check only for quotes if lookAfter
647 for(i=0;i<pos;i++){
648 c=s.at(i);
649 if(c=='\"' && quote==0){
650 doubleQuote^=1;
651 }else if(c=='\'' && doubleQuote==0){
652 quote^=1;
653 }
654 }
655 }
656
657 if(lookAfter && quote==0 && doubleQuote==0){
658 //check for bracket if current bracket is not quoted
659 int count = 1;
660 for(i=pos+2;i<length;i++){
661 c=s.at(i);
662 if(c=='\"' && quote==0){
663 doubleQuote^=1;
664 }else if(c=='\'' && doubleQuote==0){
665 quote^=1;
666 }else if(quote==0 && doubleQuote==0){
667 if(c==cNext){
668 count++;
669 }else if(c==close){
670 count--;
671 if(count==0){
672 //mark current bracket and the corresponding bracket
673 selectionBracket.format.setBackground(bracketColor);
674 selectionBracket.cursor = textCursor();
675 selectionBracket.cursor.setPosition(pos+posDoc+1);
676 selectionBracket.cursor.movePosition(QTextCursor::NextCharacter,QTextCursor::KeepAnchor);
677 extraSelections.append(selectionBracket);
678 selectionBracket.cursor.setPosition(i+posDoc+1);
679 selectionBracket.cursor.movePosition(QTextCursor::PreviousCharacter,QTextCursor::KeepAnchor);
680 extraSelections.append(selectionBracket);
681 break;
682 }
683 }
684 }
685 }
686 }
687 }
688 }
689 setExtraSelections(extraSelections);
690 }
691
692
resizeEvent(QResizeEvent * e)693 void BasicEdit::resizeEvent(QResizeEvent *e) {
694 QPlainTextEdit::resizeEvent(e);
695 if(lineNumberArea){
696 QRect cr = contentsRect();
697 lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
698 }
699 }
700
701
lineNumberAreaPaintEvent(QPaintEvent * event)702 void BasicEdit::lineNumberAreaPaintEvent(QPaintEvent *event) {
703 //We need lastBlockNumber to adjust Qt bug (see below)
704 QTextCursor t(textCursor());
705 int currentBlockNumber = t.blockNumber();
706 t.movePosition(QTextCursor::End);
707 int lastBlockNumber = t.block().blockNumber();
708
709 QPainter painter(lineNumberArea);
710 painter.fillRect(event->rect(), Qt::lightGray);
711
712 QTextBlock block = firstVisibleBlock();
713 int blockNumber = block.blockNumber();
714 int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
715 int bottom = top + (int) blockBoundingRect(block).height();
716 int y = -1;
717
718 if(lineNumberArea->underMouse())
719 y = lineNumberArea->mapFromGlobal(QCursor::pos()).y();
720
721 while (block.isValid() && top <= event->rect().bottom()) {
722 if (block.isVisible() && bottom > event->rect().top()) {
723 QString number = QString::number(blockNumber + 1);
724
725 //Draw lighter background for current paragraph
726 //For this to work (and to correctly change the color of current paragraph number) highlightCurrentLine()
727 //must set a specialSelection for entire current block. If not, QPaintEvent will update only
728 //a small portion of lineNumberAreaPaint (just current line, not entire paragraph), acording with Automatic Clipping.
729 //Also there is a bug with last block. See http://stackoverflow.com/questions/37890906/qt-linenumberarea-example-blockboundingrectlastblock
730 //or http://www.forum.crossplatform.ru/index.php?showtopic=3963
731 if (blockNumber==currentBlockNumber)
732 painter.fillRect(0,top,lineNumberArea->width(),bottom-top-(blockNumber==lastBlockNumber?3:0), QColor(Qt::lightGray).lighter(110));
733 else if(top <= y && bottom > y && y>=0 && runState != RUNSTATERUN)
734 painter.fillRect(0,top,lineNumberArea->width(),bottom-top-(blockNumber==lastBlockNumber?3:0), QColor(Qt::lightGray).lighter(104));
735
736 // Draw breakpoints
737 if (block.userState()==STATEBREAKPOINT) {
738 painter.setBrush(Qt::red);
739 painter.setPen(Qt::red);
740 int w = lineNumberArea->width();
741 int bh = blockBoundingRect(block).height();
742 int fh = fontMetrics().height();
743 painter.drawEllipse((w-(fh-6))/2, top+(bh-(fh-6))/2, fh-6, fh-6);
744 }
745 // draw text
746 if (blockNumber==currentBlockNumber) {
747 painter.setPen(Qt::blue);
748 } else {
749 painter.setPen(Qt::black);
750 }
751 painter.drawText(0, top, lineNumberArea->width()-5, fontMetrics().height(), Qt::AlignRight, number);
752 }
753
754 block = block.next();
755 top = bottom;
756 bottom = top + (int) blockBoundingRect(block).height();
757 ++blockNumber;
758 }
759 }
760
761
lineNumberAreaMouseWheelEvent(QWheelEvent * event)762 void BasicEdit::lineNumberAreaMouseWheelEvent(QWheelEvent *event) {
763 QPlainTextEdit::wheelEvent(event);
764 }
765
766
767 //Breakpoints are stick to each paragraph by using setUserState to avoid messing up while delete/edit/drop/paste large sections of text.
768 //breakPoints list is also needed by interpretor. Before running in debug mode breakPoints list is update by calling updateBreakPointsList().
769 //Because setting/removing breakpoints is enabled in debug mode, we set/unset breakpoints in breakPoints list too.
lineNumberAreaMouseClickEvent(QMouseEvent * event)770 void BasicEdit::lineNumberAreaMouseClickEvent(QMouseEvent *event) {
771 if (runState == RUNSTATERUN)
772 return;
773 // based on mouse click - set the breakpoint in the map/block and highlight the line
774 int line;
775 QTextBlock block = firstVisibleBlock();
776 int bottom = (int) blockBoundingGeometry(block).translated(contentOffset()).top(); //bottom from previous block
777 line = block.blockNumber();
778 // line 0 ... (n-1) of what was clicked
779 while(block.isValid()) {
780 bottom += blockBoundingRect(block).height();
781 if (event->y() < bottom) {
782 if(event->button() == Qt::LeftButton){
783 //keep breakPoints list update for debug running mode
784 if(block.userState()==STATEBREAKPOINT){
785 block.setUserState(STATECLEAR);
786 if (breakPoints->contains(line))
787 breakPoints->removeAt(breakPoints->indexOf(line,0));
788 }else{
789 block.setUserState(STATEBREAKPOINT);
790 breakPoints->append(line);
791 }
792 lineNumberArea->repaint();
793 }else if(event->button() == Qt::RightButton){
794 rightClickBlockNumber = line;
795 QMenu contextMenu(this);
796 if(block.userState()==STATEBREAKPOINT)
797 contextMenu.addAction ( tr("Remove breakpoint from line") + " " + QString::number(line+1), this , SLOT (toggleBreakPoint()) );
798 else
799 contextMenu.addAction ( tr("Set breakpoint at line") + " " + QString::number(line+1), this , SLOT (toggleBreakPoint()) );
800 QAction *act = contextMenu.addAction ( tr("Clear all breakpoints") , this , SLOT (clearBreakPoints()) );
801 act->setEnabled(isBreakPoint());
802 contextMenu.exec (event->globalPos());
803 }
804 return;
805 }
806 block = block.next();
807 line++;
808 }
809 QMenu contextMenu(this);
810 contextMenu.addAction ( tr("Clear all breakpoints") , this , SLOT (clearBreakPoints()) );
811 contextMenu.exec (event->globalPos());
812 }
813
toggleBreakPoint()814 void BasicEdit::toggleBreakPoint(){
815 if(rightClickBlockNumber<0)
816 return;
817 QTextBlock block = firstVisibleBlock();
818 int n = block.blockNumber();
819 if(n<rightClickBlockNumber){
820 for(;n<rightClickBlockNumber;n++){
821 block = block.next();
822 }
823 }else if(n>rightClickBlockNumber){
824 for(;n>rightClickBlockNumber;n--){
825 block = block.previous();
826 }
827 }
828 if(block.isValid()){
829 if(block.blockNumber()==rightClickBlockNumber){
830 //keep breakPoints list update for debug running mode
831 if(block.userState()==STATEBREAKPOINT){
832 block.setUserState(STATECLEAR);
833 if (breakPoints->contains(rightClickBlockNumber))
834 breakPoints->removeAt(breakPoints->indexOf(rightClickBlockNumber,0));
835 }else{
836 block.setUserState(STATEBREAKPOINT);
837 breakPoints->append(rightClickBlockNumber);
838 }
839 lineNumberArea->repaint();
840 }
841 }
842 rightClickBlockNumber=-1;
843 }
844
clearBreakPoints()845 void BasicEdit::clearBreakPoints() {
846 // remove all breakpoints
847 QTextBlock b(document()->firstBlock());
848 while (b.isValid()){
849 b.setUserState(STATECLEAR);
850 b = b.next();
851 }
852 breakPoints->clear();
853 lineNumberArea->repaint();
854 }
855
isBreakPoint()856 bool BasicEdit::isBreakPoint() {
857 // check if there are breakpoints to be cleared (usefull to update menu)
858 QTextBlock b(document()->firstBlock());
859 while (b.isValid()){
860 if(b.userState()==STATEBREAKPOINT)
861 return true;
862 b = b.next();
863 }
864 return false;
865 }
866
updateBreakPointsList()867 void BasicEdit::updateBreakPointsList() {
868 breakPoints->clear();
869 QTextBlock b(document()->firstBlock());
870 while (b.isValid()){
871 if(b.userState()==STATEBREAKPOINT)
872 breakPoints->append(b.blockNumber());
873 b = b.next();
874 }
875 }
876
877
indentSelection()878 int BasicEdit::indentSelection() {
879 QTextCursor cur = textCursor();
880 if(!cur.hasSelection())
881 return false;
882 int a = cur.anchor();
883 int p = cur.position();
884 int start = (a<=p?a:p);
885 int end = (a>p?a:p);
886
887 cur.beginEditBlock();
888 cur.setPosition(end);
889 int eblock = cur.block().blockNumber();
890 cur.setPosition(start);
891 int sblock = cur.block().blockNumber();
892
893 for(int i = sblock; i <= eblock; i++)
894 {
895 cur.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
896 cur.insertText("\t");
897 cur.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor);
898 }
899 cur.endEditBlock();
900 return true;
901 }
902
903
unindentSelection()904 void BasicEdit::unindentSelection() {
905 QTextCursor cur = textCursor();
906 int a = cur.anchor();
907 int p = cur.position();
908 int start = (a<=p?a:p);
909 int end = (a>p?a:p);
910
911 cur.beginEditBlock();
912 cur.setPosition(end);
913 int eblock = cur.block().blockNumber();
914 cur.setPosition(start);
915 int sblock = cur.block().blockNumber();
916 QString s;
917
918 for(int i = sblock; i <= eblock; i++)
919 {
920 cur.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor);
921 cur.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
922 s = cur.selectedText();
923 if(!s.isEmpty()){
924 if(s.startsWith(" ") || s.startsWith(" \t")){
925 cur.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
926 cur.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 4);
927 cur.removeSelectedText();
928 }else if(s.startsWith(" ") || s.startsWith(" \t")){
929 cur.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
930 cur.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 3);
931 cur.removeSelectedText();
932 }else if(s.startsWith(" ") || s.startsWith(" \t")){
933 cur.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
934 cur.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 2);
935 cur.removeSelectedText();
936 }else if(s.startsWith(" ") || s.startsWith("\t")){
937 cur.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
938 cur.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 1);
939 cur.removeSelectedText();
940 }
941 }
942 cur.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor);
943 }
944 cur.endEditBlock();
945 }
946
setTitle(QString newTitle)947 void BasicEdit::setTitle(QString newTitle){
948 title = newTitle;
949 updateTitle();
950 }
951
updateTitle()952 void BasicEdit::updateTitle(){
953 windowtitle = (document()->isModified()?"*":"") + title;
954 action->setText(windowtitle);
955 emit(updateWindowTitle(this));
956 }
957
dragEnterEvent(QDragEnterEvent * event)958 void BasicEdit::dragEnterEvent(QDragEnterEvent *event){
959 if (event->mimeData()->hasFormat("text/plain")){
960 event->acceptProposedAction();
961 QPlainTextEdit::dragEnterEvent(event);
962 }else{
963 event->ignore();
964 }
965 }
966
dropEvent(QDropEvent * event)967 void BasicEdit::dropEvent(QDropEvent *event){
968 if (event->mimeData()->hasFormat("text/plain")){
969 event->acceptProposedAction();
970 QPlainTextEdit::dropEvent(event);
971 }else{
972 event->ignore();
973 }
974 }
975
setEditorRunState(int state)976 void BasicEdit::setEditorRunState(int state){
977 if(runState == state) return;
978 runState = state;
979 if(guiState!=GUISTATEAPP){
980 if(state==RUNSTATESTOP&&guiState==GUISTATENORMAL){
981 setReadOnly(false);
982 setTextInteractionFlags(Qt::TextEditorInteraction);
983 if(fileChangedOnDiskFlag) handleFileChangedOnDisk();
984 } else if(state==RUNSTATERUN||guiState!=GUISTATENORMAL){
985 setReadOnly(true);
986 }else{
987 //Just like readonly but user can not change current line and highlight another one (need in debug mode)
988 setTextInteractionFlags(Qt::NoTextInteraction);
989 }
990 //setDisabled(!(state==RUNSTATESTOP&&guiState==GUISTATENORMAL));
991 highlightCurrentLine();
992 }
993 }
994
slotUndoAvailable(bool val)995 void BasicEdit::slotUndoAvailable(bool val){
996 undoButton = val;
997 emit(updateEditorButtons());
998 }
999
slotRedoAvailable(bool val)1000 void BasicEdit::slotRedoAvailable(bool val){
1001 redoButton = val;
1002 emit(updateEditorButtons());
1003 }
slotCopyAvailable(bool val)1004 void BasicEdit::slotCopyAvailable(bool val){
1005 copyButton = val;
1006 emit(updateEditorButtons());
1007 }
1008
actionWasTriggered()1009 void BasicEdit::actionWasTriggered(){
1010 emit(setCurrentEditorTab(this));
1011 }
1012
fileChangedOnDiskSlot(QString fn)1013 void BasicEdit::fileChangedOnDiskSlot(QString fn){
1014 if(fn==filename){
1015 if(runState==RUNSTATESTOP){
1016 if(!fileChangedOnDiskFlag){
1017 //avoid fileChangedOnDiskSlot to be fired multiple times while program waits user confirmation
1018 fileChangedOnDiskFlag=true;
1019 handleFileChangedOnDisk();
1020 }
1021 }else{
1022 fileChangedOnDiskFlag=true;
1023 }
1024 }
1025 }
1026
handleFileChangedOnDisk()1027 void BasicEdit::handleFileChangedOnDisk(){
1028 document()->setModified(true);
1029 updateTitle();
1030 emit(setCurrentEditorTab(this));
1031 QFileInfo check_file(filename);
1032 if(!check_file.exists()){
1033 QMessageBox::information(this, tr("File has been removed."),
1034 tr("It seems that file %1 was removed from disk. Don't forget to save your work.").arg(filename));
1035 }else{
1036 if( QMessageBox::Yes == QMessageBox::warning(this, tr("File changed"),
1037 tr("The file %1 has changed outside BASIC256 editor. Do you want to reload it?").arg(filename),
1038 QMessageBox::Yes | QMessageBox::No, QMessageBox::No)){
1039 //reload changed file from disk but keep undo history to allow reversing this action
1040 QFile f(filename);
1041 if(f.open(QIODevice::ReadOnly)){
1042 QByteArray ba = f.readAll();
1043 f.close();
1044 QTextCursor cursor = this->textCursor();
1045 cursor.beginEditBlock();
1046 cursor.select(QTextCursor::Document);
1047 cursor.insertText(QString::fromUtf8(ba.data()));
1048 cursor.movePosition(QTextCursor::Start);
1049 setTextCursor(cursor);
1050 cursor.endEditBlock();
1051 document()->setModified(false);
1052 updateTitle();
1053 }else{
1054 QMessageBox::critical(this, tr("Load File"), tr("Unable to open program file") + " \"" + filename + "\".\n" + tr("File permissions problem or file open by another process."));
1055 }
1056 }
1057 }
1058 fileChangedOnDiskFlag=false;
1059 }
1060