File indexing completed on 2024-05-12 05:40:37

0001 #include <QtGui>
0002 
0003 #include <QRegularExpression>
0004 #include <QTextCursor>
0005 #include <QTextDocument>
0006 
0007 #include "codeeditor.h"
0008 #include "controller/view_controller/sharednotecontroller.h"
0009 #include "enu.h"
0010 #include "utilities.h"
0011 
0012 namespace sharedNotes
0013 {
0014 CodeEditor::CodeEditor(SharedNoteController* ctrl, QWidget* parent) : QPlainTextEdit(parent), m_sharedCtrl(ctrl)
0015 {
0016     lineNumberArea= new LineNumberArea(this);
0017     QPalette palette(Qt::white);
0018     setPalette(palette);
0019 
0020     connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
0021     connect(this, SIGNAL(updateRequest(QRect, int)), this, SLOT(updateLineNumberArea(QRect, int)));
0022     connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
0023     connect(this, &CodeEditor::textChanged, this, [this]() { m_sharedCtrl->setText(toPlainText()); });
0024     connect(m_sharedCtrl, &SharedNoteController::collabTextChanged, this, &CodeEditor::collabTextChange);
0025     connect(m_sharedCtrl, &SharedNoteController::permissionChanged, this,
0026             [this]() { setReadOnly(m_sharedCtrl->permission() == ParticipantModel::readOnly); });
0027     updateLineNumberAreaWidth(0);
0028     highlightCurrentLine();
0029 
0030     if(m_sharedCtrl)
0031         setReadOnly(m_sharedCtrl->permission() == ParticipantModel::readOnly);
0032 
0033     isFirstTime= true;
0034 }
0035 
0036 int CodeEditor::lineNumberAreaWidth()
0037 {
0038     int digits= 1;
0039     int max= qMax(1, blockCount());
0040     while(max >= 10)
0041     {
0042         max/= 10;
0043         ++digits;
0044     }
0045 
0046     int space= 4 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits;
0047 
0048     return space;
0049 }
0050 
0051 void CodeEditor::collabTextChange(int pos, int charsRemoved, int charsAdded, QString data)
0052 {
0053     m_sharedCtrl->blockSignals(true);
0054     if(charsRemoved > 0 && charsAdded == 0)
0055     {
0056         QTextCursor cursor= textCursor();
0057         cursor.setPosition(pos);
0058         cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, charsRemoved);
0059         cursor.removeSelectedText();
0060     }
0061     else if(charsRemoved > 0 && charsAdded > 0)
0062     {
0063         QTextCursor cursor= textCursor();
0064         cursor.setPosition(pos);
0065         cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, charsRemoved);
0066         cursor.insertText(data);
0067     }
0068     else if(charsRemoved == 0 && charsAdded > 0)
0069     {
0070         QTextCursor cursor= textCursor();
0071         cursor.setPosition(pos);
0072         cursor.insertText(data);
0073     }
0074     m_sharedCtrl->blockSignals(false);
0075 }
0076 
0077 void CodeEditor::unCommentSelection()
0078 {
0079     QTextCursor cursor= textCursor();
0080     QTextDocument* doc= cursor.document();
0081     cursor.beginEditBlock();
0082 
0083     int pos= cursor.position();
0084     int anchor= cursor.anchor();
0085     int start= qMin(anchor, pos);
0086     int end= qMax(anchor, pos);
0087     bool anchorIsStart= (anchor == start);
0088 
0089     QTextBlock startBlock= doc->findBlock(start);
0090     QTextBlock endBlock= doc->findBlock(end);
0091 
0092     if(end > start && endBlock.position() == end)
0093     {
0094         --end;
0095         endBlock= endBlock.previous();
0096     }
0097 
0098     bool doCStyleUncomment= false;
0099     bool doCStyleComment= false;
0100     bool doCppStyleUncomment= false;
0101 
0102     bool hasSelection= cursor.hasSelection();
0103 
0104     if(hasSelection)
0105     {
0106         QString startText= startBlock.text();
0107         int startPos= start - startBlock.position();
0108         bool hasLeadingCharacters= !startText.left(startPos).trimmed().isEmpty();
0109         if((startPos >= 2 && startText.at(startPos - 2) == QLatin1Char('/')
0110             && startText.at(startPos - 1) == QLatin1Char('*')))
0111         {
0112             startPos-= 2;
0113             start-= 2;
0114         }
0115 
0116         bool hasSelStart= (startPos < startText.length() - 2 && startText.at(startPos) == QLatin1Char('/')
0117                            && startText.at(startPos + 1) == QLatin1Char('*'));
0118 
0119         QString endText= endBlock.text();
0120         int endPos= end - endBlock.position();
0121         bool hasTrailingCharacters= !endText.left(endPos).remove(QStringLiteral("//")).trimmed().isEmpty()
0122                                     && !endText.mid(endPos).trimmed().isEmpty();
0123         if((endPos <= endText.length() - 2 && endText.at(endPos) == QLatin1Char('*')
0124             && endText.at(endPos + 1) == QLatin1Char('/')))
0125         {
0126             endPos+= 2;
0127             end+= 2;
0128         }
0129 
0130         bool hasSelEnd
0131             = (endPos >= 2 && endText.at(endPos - 2) == QLatin1Char('*') && endText.at(endPos - 1) == QLatin1Char('/'));
0132 
0133         doCStyleUncomment= hasSelStart && hasSelEnd;
0134         doCStyleComment= !doCStyleUncomment && (hasLeadingCharacters || hasTrailingCharacters);
0135     }
0136 
0137     if(doCStyleUncomment)
0138     {
0139         cursor.setPosition(end);
0140         cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor, 2);
0141         cursor.removeSelectedText();
0142         cursor.setPosition(start);
0143         cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 2);
0144         cursor.removeSelectedText();
0145     }
0146     else if(doCStyleComment)
0147     {
0148         cursor.setPosition(end);
0149         cursor.insertText(QStringLiteral("*/"));
0150         cursor.setPosition(start);
0151         cursor.insertText(QStringLiteral("/*"));
0152     }
0153     else
0154     {
0155         endBlock= endBlock.next();
0156         doCppStyleUncomment= true;
0157         for(QTextBlock block= startBlock; block != endBlock; block= block.next())
0158         {
0159             QString text= block.text();
0160             if(!text.trimmed().startsWith(QStringLiteral("//")))
0161             {
0162                 doCppStyleUncomment= false;
0163                 break;
0164             }
0165         }
0166         for(QTextBlock block= startBlock; block != endBlock; block= block.next())
0167         {
0168             if(doCppStyleUncomment)
0169             {
0170                 QString text= block.text();
0171                 int i= 0;
0172                 while(i < text.size() - 1)
0173                 {
0174                     if(text.at(i) == QLatin1Char('/') && text.at(i + 1) == QLatin1Char('/'))
0175                     {
0176                         cursor.setPosition(block.position() + i);
0177                         cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 2);
0178                         cursor.removeSelectedText();
0179                         break;
0180                     }
0181                     if(!text.at(i).isSpace())
0182                         break;
0183                     ++i;
0184                 }
0185             }
0186             else
0187             {
0188                 cursor.setPosition(block.position());
0189                 cursor.insertText(QLatin1String("//"));
0190             }
0191         }
0192     }
0193 
0194     // adjust selection when commenting out
0195     if(hasSelection && !doCStyleUncomment && !doCppStyleUncomment)
0196     {
0197         cursor= textCursor();
0198         if(!doCStyleComment)
0199             start= startBlock.position(); // move the double slashes into the selection
0200         int lastSelPos= anchorIsStart ? cursor.position() : cursor.anchor();
0201         if(anchorIsStart)
0202         {
0203             cursor.setPosition(start);
0204             cursor.setPosition(lastSelPos, QTextCursor::KeepAnchor);
0205         }
0206         else
0207         {
0208             cursor.setPosition(lastSelPos);
0209             cursor.setPosition(start, QTextCursor::KeepAnchor);
0210         }
0211         setTextCursor(cursor);
0212     }
0213 
0214     cursor.endEditBlock();
0215 }
0216 
0217 void CodeEditor::shiftLeft()
0218 {
0219     QTextCursor cursor= textCursor();
0220     if(cursor.hasSelection())
0221     {
0222         int start= cursor.selectionStart();
0223         int end= cursor.selectionEnd();
0224         cursor.setPosition(start);
0225         int i= cursor.position();
0226         cursor.beginEditBlock();
0227         while(i < end)
0228         {
0229             cursor.movePosition(QTextCursor::StartOfLine);
0230             cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
0231             QString line= cursor.selectedText();
0232             cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
0233             if(line.startsWith("    "))
0234             {
0235                 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 4);
0236                 cursor.removeSelectedText();
0237                 end-= 4;
0238             }
0239             else if(line.startsWith("\t"))
0240             {
0241                 cursor.deleteChar();
0242             }
0243             cursor.movePosition(QTextCursor::EndOfLine);
0244             if(cursor.atEnd())
0245             {
0246                 break;
0247             }
0248             else
0249             {
0250                 cursor.movePosition(QTextCursor::StartOfLine);
0251                 cursor.movePosition(QTextCursor::Down);
0252                 i= cursor.position();
0253             }
0254         }
0255         cursor.endEditBlock();
0256     }
0257     else
0258     {
0259         cursor.movePosition(QTextCursor::StartOfLine);
0260         cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
0261         QString line= cursor.selectedText();
0262         cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
0263         if(line.startsWith("    "))
0264         {
0265             cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 4);
0266             cursor.removeSelectedText();
0267         }
0268         else if(line.startsWith("\t"))
0269         {
0270             cursor.deleteChar();
0271         }
0272     }
0273 }
0274 
0275 void CodeEditor::shiftRight()
0276 {
0277     QTextCursor cursor= textCursor();
0278     int end= cursor.selectionEnd();
0279     int start= cursor.selectionStart();
0280     if(cursor.hasSelection())
0281     {
0282         //        setWordWrapMode(QTextOption::NoWrap);    //I need to figure out how to handle if WordWrap is enabled
0283         cursor.setPosition(start);
0284         cursor.movePosition(QTextCursor::StartOfLine);
0285         int i= cursor.position();
0286         cursor.beginEditBlock();
0287         while(i < end)
0288         {
0289             cursor.insertText("    ");
0290             end+= 4;
0291             cursor.movePosition(QTextCursor::EndOfLine);
0292             if(cursor.atEnd())
0293             {
0294                 break;
0295             }
0296             else
0297             {
0298                 cursor.movePosition(QTextCursor::StartOfLine);
0299                 cursor.movePosition(QTextCursor::Down);
0300                 i= cursor.position();
0301             }
0302             //            if (wordWrapMode()) {
0303             //
0304             //            }
0305             //            Need to figure out what to do here
0306         }
0307         start+= 4;
0308         cursor.endEditBlock();
0309     }
0310     else
0311     {
0312         cursor.movePosition(QTextCursor::StartOfLine);
0313         cursor.insertText("    ");
0314     }
0315     cursor.setPosition(start);
0316     cursor.setPosition(end, QTextCursor::KeepAnchor);
0317     setTextCursor(cursor);
0318 }
0319 
0320 bool CodeEditor::findNext(QString searchString, Qt::CaseSensitivity sensitivity, bool wrapAround, Enu::FindMode mode)
0321 {
0322     QString documentString= document()->toPlainText();
0323     QTextCursor cursor(document());
0324     int position;
0325     bool found= false;
0326 
0327     QRegularExpression rx;
0328     switch(mode)
0329     {
0330     case Enu::Contains:
0331         rx= QRegularExpression(searchString);
0332         break;
0333     case Enu::StartsWith:
0334         rx= QRegularExpression("\\b" + searchString);
0335         break;
0336     case Enu::EntireWord:
0337         rx= QRegularExpression("\\b" + searchString + "\\b");
0338     }
0339     if(sensitivity == Qt::CaseInsensitive)
0340         rx.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
0341 
0342     position= textCursor().position();
0343 
0344     position= documentString.indexOf(rx, position);
0345     int length= searchString.size();
0346 
0347     if(position != -1)
0348     {
0349         cursor.setPosition(position, QTextCursor::MoveAnchor);
0350         for(int i= 0; i < length; i++)
0351         {
0352             cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
0353         }
0354         setTextCursor(cursor);
0355         found= true;
0356     }
0357     else if(wrapAround)
0358     {
0359         position= 0; // move cursor to the beginning and begin searching again
0360         position= documentString.indexOf(rx, position);
0361         length= searchString.size();
0362 
0363         if(position != -1)
0364         {
0365             cursor.setPosition(position, QTextCursor::MoveAnchor);
0366             for(int i= 0; i < length; i++)
0367             {
0368                 cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
0369             }
0370             setTextCursor(cursor);
0371             found= true;
0372         }
0373     }
0374 
0375     return found;
0376 }
0377 
0378 bool CodeEditor::findPrev(QString searchString, Qt::CaseSensitivity sensitivity, bool wrapAround, Enu::FindMode mode)
0379 {
0380     QString documentString= document()->toPlainText();
0381     QTextCursor cursor(document());
0382     int position;
0383     bool found= false;
0384 
0385     QRegularExpression rx;
0386     switch(mode)
0387     {
0388     case Enu::Contains:
0389         rx= QRegularExpression(searchString);
0390         break;
0391     case Enu::StartsWith:
0392         rx= QRegularExpression("\\b" + searchString);
0393         break;
0394     case Enu::EntireWord:
0395         rx= QRegularExpression("\\b" + searchString + "\\b");
0396     }
0397     if(sensitivity == Qt::CaseInsensitive)
0398         rx.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
0399 
0400     if(textCursor().hasSelection())
0401     {
0402         position= textCursor().selectionStart() - 1;
0403     }
0404     else
0405     {
0406         position= textCursor().position();
0407     }
0408 
0409     position= documentString.lastIndexOf(rx, position);
0410     int length= searchString.size();
0411 
0412     if(position != -1)
0413     {
0414         found= true;
0415         cursor.setPosition(position, QTextCursor::MoveAnchor);
0416         for(int i= 0; i < length; i++)
0417         {
0418             cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
0419         }
0420         setTextCursor(cursor);
0421     }
0422     else if(wrapAround)
0423     {
0424         // Move position to the end of the document.
0425         // magic # 2 is magic, anything less and it's beyond the scope of the document?
0426         position= documentString.lastIndexOf(rx, document()->characterCount() - 2);
0427         if(position != -1)
0428         {
0429             found= true;
0430             cursor.setPosition(position, QTextCursor::MoveAnchor);
0431             for(int i= 0; i < length; i++)
0432             {
0433                 cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
0434             }
0435             setTextCursor(cursor);
0436         }
0437     }
0438 
0439     return found;
0440 }
0441 
0442 bool CodeEditor::replaceAll(QString searchString, QString replaceString, Qt::CaseSensitivity sensitivity,
0443                             Enu::FindMode mode)
0444 {
0445     QString documentString= document()->toPlainText();
0446     bool isFound= false;
0447     bool isReplaced= false;
0448 
0449     QTextCursor cursor(document());
0450     int position= 0;
0451     cursor.setPosition(position);
0452 
0453     int length= searchString.size();
0454 
0455     QRegularExpression rx;
0456     switch(mode)
0457     {
0458     case Enu::Contains:
0459         rx= QRegularExpression(searchString);
0460         break;
0461     case Enu::StartsWith:
0462         rx= QRegularExpression("\\b" + searchString);
0463         break;
0464     case Enu::EntireWord:
0465         rx= QRegularExpression("\\b" + searchString + "\\b");
0466     }
0467     if(sensitivity == Qt::CaseInsensitive)
0468         rx.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
0469 
0470     //    int count = documentString.count(rx);
0471 
0472     cursor.beginEditBlock();
0473 
0474     while(position != -1)
0475     {
0476         position= documentString.indexOf(rx, position + 1);
0477 
0478         if(position != -1)
0479         {
0480             cursor.setPosition(position, QTextCursor::MoveAnchor);
0481             for(int i= 0; i < length; i++)
0482             {
0483                 cursor.deleteChar();
0484             }
0485             isFound= true;
0486             cursor.insertText(replaceString);
0487             isReplaced= true;
0488         }
0489     }
0490 
0491     cursor.endEditBlock();
0492 
0493     if(isFound && isReplaced)
0494     {
0495         return true;
0496     }
0497     return false;
0498 }
0499 
0500 bool CodeEditor::replace(QString replaceString)
0501 {
0502     QTextCursor cursor(document());
0503 
0504     if(textCursor().hasSelection())
0505     {
0506         cursor.setPosition(textCursor().position());
0507         textCursor().removeSelectedText();
0508         cursor.insertText(replaceString);
0509         return true;
0510     }
0511     return false;
0512 }
0513 
0514 bool CodeEditor::findReplace(QString searchString, QString replaceString, Qt::CaseSensitivity sensitivity,
0515                              bool wrapAround, Enu::FindMode mode)
0516 {
0517     bool isReplaced= replace(replaceString);
0518     bool isFound= findNext(searchString, sensitivity, wrapAround, mode);
0519     if(isReplaced && isFound)
0520     {
0521         return true;
0522     }
0523     return false;
0524 }
0525 
0526 bool CodeEditor::findAll(QString searchString)
0527 {
0528     QString documentString= document()->toPlainText();
0529     bool isFound= false;
0530     if(!isFirstTime)
0531     {
0532         undo();
0533     }
0534 
0535     QTextCursor cursor(document());
0536 
0537     cursor.select(QTextCursor::Document);
0538     QTextCharFormat format;
0539     format.setBackground(Qt::white);
0540     cursor.mergeCharFormat(format);
0541 
0542     QTextCharFormat plainFormat(cursor.charFormat());
0543     QTextCharFormat colorFormat= plainFormat;
0544     colorFormat.setBackground(Qt::yellow);
0545 
0546     int position= 0;
0547     cursor.setPosition(position);
0548 
0549     int length= searchString.size();
0550 
0551     cursor.beginEditBlock();
0552 
0553     while(position != -1)
0554     {
0555         position= documentString.indexOf(searchString, position + 1, Qt::CaseInsensitive);
0556 
0557         if(position != -1)
0558         {
0559             cursor.setPosition(position, QTextCursor::MoveAnchor);
0560             for(int i= 0; i < length; i++)
0561             {
0562                 cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
0563                 cursor.mergeCharFormat(colorFormat);
0564             }
0565             setTextCursor(cursor);
0566             isFound= true;
0567         }
0568     }
0569 
0570     cursor.endEditBlock();
0571     isFirstTime= false;
0572 
0573     return isFound;
0574 }
0575 
0576 void CodeEditor::updateLineNumberAreaWidth(int /* newBlockCount */)
0577 {
0578     setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
0579 }
0580 
0581 void CodeEditor::updateLineNumberArea(const QRect& rect, int dy)
0582 {
0583     if(dy)
0584         lineNumberArea->scroll(0, dy);
0585     else
0586         lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
0587 
0588     if(rect.contains(viewport()->rect()))
0589         updateLineNumberAreaWidth(0);
0590 }
0591 
0592 void CodeEditor::resizeEvent(QResizeEvent* e)
0593 {
0594     QPlainTextEdit::resizeEvent(e);
0595 
0596     QRect cr= contentsRect();
0597     lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
0598 }
0599 
0600 void CodeEditor::highlightCurrentLine()
0601 {
0602     QList<QTextEdit::ExtraSelection> extraSelections;
0603 
0604     if(!isReadOnly())
0605     {
0606         QTextEdit::ExtraSelection selection;
0607 
0608         QColor lineColor= QColor(233, 243, 255); // light blue
0609 
0610         selection.format.setBackground(lineColor);
0611         selection.format.setProperty(QTextFormat::FullWidthSelection, true);
0612         selection.cursor= textCursor();
0613         selection.cursor.clearSelection();
0614         extraSelections.append(selection);
0615     }
0616 
0617     setExtraSelections(extraSelections);
0618 }
0619 
0620 void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent* event)
0621 {
0622     QPainter painter(lineNumberArea);
0623     painter.fillRect(event->rect(), QColor(232, 232, 232)); // light grey
0624 
0625     QTextBlock block= firstVisibleBlock();
0626     int blockNumber= block.blockNumber();
0627     int top= static_cast<int>(blockBoundingGeometry(block).translated(contentOffset()).top());
0628     int bottom= top + static_cast<int>(blockBoundingRect(block).height());
0629 
0630     while(block.isValid() && top <= event->rect().bottom())
0631     {
0632         if(block.isVisible() && bottom >= event->rect().top())
0633         {
0634             QString number= QString::number(blockNumber + 1);
0635             painter.setPen(QColor(128, 128, 130)); // grey
0636             painter.drawText(-1, top, lineNumberArea->width(), fontMetrics().height(), Qt::AlignRight, number);
0637         }
0638 
0639         block= block.next();
0640         top= bottom;
0641         bottom= top + static_cast<int>(blockBoundingRect(block).height());
0642         ++blockNumber;
0643     }
0644 }
0645 }