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 }