File indexing completed on 2025-02-09 06:57:15
0001 // clang-format off 0002 /* 0003 * KDiff3 - Text Diff And Merge Tool 0004 * 0005 * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de 0006 * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com 0007 * SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 // clang-format on 0010 0011 #include "difftextwindow.h" 0012 0013 #include "defmac.h" 0014 #include "FileNameLineEdit.h" 0015 #include "kdiff3.h" 0016 #include "LineRef.h" 0017 #include "Logging.h" 0018 #include "merger.h" 0019 #include "options.h" 0020 #include "progress.h" 0021 #include "RLPainter.h" 0022 #include "selection.h" 0023 #include "SourceData.h" 0024 #include "TypeUtils.h" 0025 #include "Utils.h" 0026 0027 #include <algorithm> 0028 #include <cmath> 0029 #include <cstdlib> 0030 #include <memory> 0031 #include <utility> 0032 #include <vector> 0033 0034 #include <KLocalizedString> 0035 #include <KMessageBox> 0036 0037 #include <QClipboard> 0038 #include <QDir> 0039 #include <QDragEnterEvent> 0040 #include <QFileDialog> 0041 #include <QLabel> 0042 #include <QLayout> 0043 #include <QLineEdit> 0044 #include <QMenu> 0045 #include <QMimeData> 0046 #include <QPainter> 0047 #include <QPushButton> 0048 #include <QRunnable> 0049 #include <QScrollBar> 0050 #include <QStatusBar> 0051 #include <QTextCodec> 0052 #include <QTextLayout> 0053 #include <QThread> 0054 #include <QtMath> 0055 #include <QToolTip> 0056 #include <QUrl> 0057 0058 QPointer<QScrollBar> DiffTextWindow::mVScrollBar = nullptr; 0059 std::vector<RecalcWordWrapThread*> DiffTextWindow::s_runnables; //Used in startRunnables and recalWordWrap 0060 0061 /* 0062 QRunnable is not enough here be may appear to work depending on configuration. 0063 That is an artifact of the threads being short-lived. Never the less such code is 0064 not safe because of a potenial race condition on exit. 0065 0066 The use of Qt's parent system establishes order of destruction for QObjects. 0067 Allowing us to guarantee clearance of these helper threads and their accompanying 0068 objects before the associated DiffTextWindow begins teardown. 0069 */ 0070 class RecalcWordWrapThread: public QThread 0071 { 0072 private: 0073 static QAtomicInteger<size_t> s_runnableCount; 0074 0075 qint32 m_visibleTextWidth; 0076 size_t m_cacheIdx; 0077 0078 public: 0079 static QAtomicInteger<size_t> s_maxNofRunnables; 0080 RecalcWordWrapThread(DiffTextWindow* parent, qint32 visibleTextWidth, SafeInt<size_t> cacheIdx): 0081 QThread(parent), m_visibleTextWidth(visibleTextWidth), m_cacheIdx(cacheIdx) 0082 { 0083 setTerminationEnabled(true); 0084 s_runnableCount.fetchAndAddOrdered(1); 0085 } 0086 void run() override 0087 { 0088 //safe thanks to Qt memory mangement 0089 DiffTextWindow* pDTW = qobject_cast<DiffTextWindow*>(parent()); 0090 0091 pDTW->recalcWordWrapHelper(0, m_visibleTextWidth, m_cacheIdx); 0092 size_t newValue = s_runnableCount.fetchAndSubOrdered(1) - 1; 0093 ProgressProxy::setCurrent(s_maxNofRunnables - s_runnableCount.loadRelaxed()); 0094 if(newValue == 0) 0095 { 0096 Q_EMIT pDTW->finishRecalcWordWrap(m_visibleTextWidth); 0097 0098 s_maxNofRunnables.storeRelease(0); 0099 } 0100 //Cleanup our object to avoid a memory leak 0101 deleteLater(); 0102 } 0103 0104 ~RecalcWordWrapThread() override 0105 { 0106 //Wait for thread to finish. 0107 if(isRunning()) 0108 wait(); 0109 } 0110 }; 0111 0112 QAtomicInteger<size_t> RecalcWordWrapThread::s_runnableCount = 0; 0113 QAtomicInteger<size_t> RecalcWordWrapThread::s_maxNofRunnables = 0; 0114 0115 class WrapLineCacheData 0116 { 0117 public: 0118 WrapLineCacheData() = default; 0119 WrapLineCacheData(qint32 d3LineIdx, qint32 textStart, qint32 textLength): 0120 m_d3LineIdx(d3LineIdx), m_textStart(textStart), m_textLength(textLength) {} 0121 [[nodiscard]] qint32 d3LineIdx() const { return m_d3LineIdx; } 0122 [[nodiscard]] qint32 textStart() const { return m_textStart; } 0123 [[nodiscard]] qint32 textLength() const { return m_textLength; } 0124 0125 private: 0126 qint32 m_d3LineIdx = 0; 0127 qint32 m_textStart = 0; 0128 qint32 m_textLength = 0; 0129 }; 0130 0131 class DiffTextWindowData 0132 { 0133 public: 0134 explicit DiffTextWindowData(QPointer<DiffTextWindow> p) 0135 { 0136 m_pDiffTextWindow = p; 0137 #if defined(Q_OS_WIN) 0138 m_eLineEndStyle = eLineEndStyleDos; 0139 #else 0140 m_eLineEndStyle = eLineEndStyleUnix; 0141 #endif 0142 } 0143 0144 void init( 0145 const QString& filename, 0146 const char* encoding, 0147 e_LineEndStyle eLineEndStyle, 0148 const std::shared_ptr<LineDataVector>& pLineData, 0149 LineType size, 0150 const Diff3LineVector* pDiff3LineVector, 0151 const ManualDiffHelpList* pManualDiffHelpList) 0152 { 0153 m_filename = filename; 0154 m_pLineData = pLineData; 0155 m_size = size; 0156 mDiff3LineVector = pDiff3LineVector; 0157 m_diff3WrapLineVector.clear(); 0158 m_pManualDiffHelpList = pManualDiffHelpList; 0159 0160 mTextEncoding = QString::fromLatin1(encoding); 0161 m_eLineEndStyle = eLineEndStyle; 0162 } 0163 0164 void reset() 0165 { 0166 while(DiffTextWindow::maxThreads() > 0) {} //Clear word wrap threads. 0167 0168 m_firstLine = 0; 0169 m_oldFirstLine = LineRef::invalid; 0170 m_horizScrollOffset = 0; 0171 m_scrollDeltaX = 0; 0172 m_scrollDeltaY = 0; 0173 m_bMyUpdate = false; 0174 m_fastSelectorLine1 = 0; 0175 m_fastSelectorNofLines = 0; 0176 m_lineNumberWidth = 0; 0177 m_maxTextWidth = -1; 0178 0179 m_pLineData = nullptr; 0180 m_size = 0; 0181 mDiff3LineVector = nullptr; 0182 m_filename = ""; 0183 m_diff3WrapLineVector.clear(); 0184 } 0185 0186 [[nodiscard]] QString getString(const LineType d3lIdx) const; 0187 [[nodiscard]] QString getLineString(const qint32 line) const; 0188 0189 void writeLine( 0190 RLPainter& p, const LineData* pld, 0191 const std::shared_ptr<const DiffList>& pLineDiff1, const std::shared_ptr<const DiffList>& pLineDiff2, const LineRef& line, 0192 const ChangeFlags whatChanged, const ChangeFlags whatChanged2, const LineRef& srcLineIdx, 0193 qint32 wrapLineOffset, qint32 wrapLineLength, bool bWrapLine, const QRect& invalidRect); 0194 0195 void draw(RLPainter& p, const QRect& invalidRect, const qint32 beginLine, const LineRef& endLine); 0196 0197 void myUpdate(qint32 afterMilliSecs); 0198 0199 [[nodiscard]] qint32 leftInfoWidth() const { return 4 + m_lineNumberWidth; } // Number of information columns on left side 0200 [[nodiscard]] LineRef convertLineOnScreenToLineInSource(const qint32 lineOnScreen, const e_CoordType coordType, const bool bFirstLine) const; 0201 0202 void prepareTextLayout(QTextLayout& textLayout, qint32 visibleTextWidth = -1); 0203 0204 [[nodiscard]] bool isThreeWay() const { return KDiff3App::isTripleDiff(); }; 0205 [[nodiscard]] const QString& getFileName() const { return m_filename; } 0206 0207 [[nodiscard]] const Diff3LineVector* getDiff3LineVector() const { return mDiff3LineVector; } 0208 private: 0209 friend DiffTextWindow; 0210 e_SrcSelector getWindowIndex() const { return m_pDiffTextWindow->getWindowIndex(); } 0211 0212 QPointer<DiffTextWindow> m_pDiffTextWindow; 0213 QString mTextEncoding; 0214 e_LineEndStyle m_eLineEndStyle; 0215 0216 std::shared_ptr<LineDataVector> m_pLineData; 0217 LineType m_size = 0; 0218 QString m_filename; 0219 bool m_bWordWrap = false; 0220 qint32 m_delayedDrawTimer = 0; 0221 0222 const Diff3LineVector* mDiff3LineVector = nullptr; 0223 Diff3WrapLineVector m_diff3WrapLineVector; 0224 const ManualDiffHelpList* m_pManualDiffHelpList = nullptr; 0225 std::vector<std::vector<WrapLineCacheData>> m_wrapLineCacheList; 0226 0227 QColor m_cThis; 0228 QColor m_cDiff1; 0229 QColor m_cDiff2; 0230 QColor m_cDiffBoth; 0231 0232 LineRef m_fastSelectorLine1 = 0; 0233 LineType m_fastSelectorNofLines = 0; 0234 0235 LineRef m_firstLine = 0; 0236 LineRef m_oldFirstLine; 0237 qint32 m_horizScrollOffset = 0; 0238 qint32 m_lineNumberWidth = 0; 0239 QAtomicInt m_maxTextWidth = -1; 0240 0241 Selection m_selection; 0242 0243 qint32 m_scrollDeltaX = 0; 0244 qint32 m_scrollDeltaY = 0; 0245 0246 bool m_bMyUpdate = false; 0247 0248 bool m_bSelectionInProgress = false; 0249 QPoint m_lastKnownMousePos; 0250 0251 QSharedPointer<SourceData> sourceData; 0252 }; 0253 0254 QAtomicInteger<size_t> DiffTextWindow::maxThreads() 0255 { 0256 return RecalcWordWrapThread::s_maxNofRunnables.loadAcquire(); 0257 } 0258 0259 void DiffTextWindow::setSourceData(const QSharedPointer<SourceData>& inData) 0260 { 0261 d->sourceData = inData; 0262 } 0263 0264 bool DiffTextWindow::isThreeWay() const 0265 { 0266 return d->isThreeWay(); 0267 }; 0268 0269 const QString& DiffTextWindow::getFileName() const 0270 { 0271 return d->getFileName(); 0272 } 0273 0274 e_SrcSelector DiffTextWindow::getWindowIndex() const 0275 { 0276 return mWinIdx; 0277 }; 0278 0279 const QString DiffTextWindow::getEncodingDisplayString() const 0280 { 0281 return d->mTextEncoding; 0282 } 0283 0284 e_LineEndStyle DiffTextWindow::getLineEndStyle() const 0285 { 0286 return d->m_eLineEndStyle; 0287 } 0288 0289 const Diff3LineVector* DiffTextWindow::getDiff3LineVector() const 0290 { 0291 return d->getDiff3LineVector(); 0292 } 0293 0294 qint32 DiffTextWindow::getLineNumberWidth() const 0295 { 0296 return (qint32)floor(log10((double)std::max(d->m_size, 1))) + 1; 0297 } 0298 0299 DiffTextWindow::DiffTextWindow(DiffTextWindowFrame* pParent, 0300 e_SrcSelector winIdx, 0301 KDiff3App& app): 0302 QWidget(pParent), 0303 m_app(app) 0304 { 0305 setObjectName(QString("DiffTextWindow%1").arg((qint32)winIdx)); 0306 setAttribute(Qt::WA_OpaquePaintEvent); 0307 setUpdatesEnabled(false); 0308 0309 d = std::make_unique<DiffTextWindowData>(this); 0310 setFocusPolicy(Qt::ClickFocus); 0311 setAcceptDrops(true); 0312 mWinIdx = winIdx; 0313 0314 init(QString(""), nullptr, d->m_eLineEndStyle, nullptr, 0, nullptr, nullptr); 0315 0316 setMinimumSize(QSize(20, 20)); 0317 0318 setUpdatesEnabled(true); 0319 0320 setFont(gOptions->defaultFont()); 0321 } 0322 0323 DiffTextWindow::~DiffTextWindow() = default; 0324 0325 void DiffTextWindow::init( 0326 const QString& filename, 0327 const char* encoding, 0328 e_LineEndStyle eLineEndStyle, 0329 const std::shared_ptr<LineDataVector>& pLineData, 0330 LineType size, 0331 const Diff3LineVector* pDiff3LineVector, 0332 const ManualDiffHelpList* pManualDiffHelpList) 0333 { 0334 d->init(filename, encoding, eLineEndStyle, pLineData, size, pDiff3LineVector, pManualDiffHelpList); 0335 0336 update(); 0337 } 0338 0339 void DiffTextWindow::reset() 0340 { 0341 d->reset(); 0342 } 0343 0344 void DiffTextWindow::setupConnections(const KDiff3App* app) 0345 { 0346 assert(qobject_cast<DiffTextWindowFrame*>(parent()) != nullptr); 0347 0348 chk_connect_a(this, &DiffTextWindow::firstLineChanged, dynamic_cast<DiffTextWindowFrame*>(parent()), &DiffTextWindowFrame::setFirstLine); 0349 chk_connect_a(this, &DiffTextWindow::newSelection, app, &KDiff3App::slotSelectionStart); 0350 chk_connect_a(this, &DiffTextWindow::selectionEnd, app, &KDiff3App::slotSelectionEnd); 0351 chk_connect_a(this, &DiffTextWindow::scrollDiffTextWindow, app, &KDiff3App::scrollDiffTextWindow); 0352 chk_connect_q(this, &DiffTextWindow::finishRecalcWordWrap, app, &KDiff3App::slotFinishRecalcWordWrap); 0353 0354 chk_connect_a(this, &DiffTextWindow::finishDrop, app, &KDiff3App::slotFinishDrop); 0355 0356 chk_connect_a(this, &DiffTextWindow::statusBarMessage, app, &KDiff3App::slotStatusMsg); 0357 0358 chk_connect_a(app, &KDiff3App::showWhiteSpaceToggled, this, static_cast<void (DiffTextWindow::*)(void)>(&DiffTextWindow::update)); 0359 chk_connect_a(app, &KDiff3App::showLineNumbersToggled, this, static_cast<void (DiffTextWindow::*)(void)>(&DiffTextWindow::update)); 0360 chk_connect_a(app, &KDiff3App::doRefresh, this, &DiffTextWindow::slotRefresh); 0361 chk_connect_a(app, &KDiff3App::selectAll, this, &DiffTextWindow::slotSelectAll); 0362 chk_connect_a(app, &KDiff3App::copy, this, &DiffTextWindow::slotCopy); 0363 0364 connections.push_back(KDiff3App::allowCopy.connect(boost::bind(&DiffTextWindow::canCopy, this))); 0365 connections.push_back(KDiff3App::getSelection.connect(boost::bind(&DiffTextWindow::getSelection, this))); 0366 } 0367 0368 void DiffTextWindow::slotRefresh() 0369 { 0370 setFont(gOptions->defaultFont()); 0371 update(); 0372 } 0373 0374 void DiffTextWindow::slotSelectAll() 0375 { 0376 LineRef l; 0377 QtSizeType p = 0; // needed as dummy return values 0378 0379 if(hasFocus()) 0380 { 0381 setSelection(0, 0, getNofLines(), 0, l, p); 0382 } 0383 } 0384 0385 void DiffTextWindow::slotCopy() 0386 { 0387 if(!hasFocus()) 0388 return; 0389 0390 const QString curSelection = getSelection(); 0391 0392 if(!curSelection.isEmpty()) 0393 { 0394 QApplication::clipboard()->setText(curSelection, QClipboard::Clipboard); 0395 } 0396 } 0397 0398 void DiffTextWindow::setPaintingAllowed(bool bAllowPainting) 0399 { 0400 if(updatesEnabled() != bAllowPainting) 0401 { 0402 setUpdatesEnabled(bAllowPainting); 0403 if(bAllowPainting) 0404 update(); 0405 } 0406 } 0407 0408 void DiffTextWindow::dragEnterEvent(QDragEnterEvent* dragEnterEvent) 0409 { 0410 dragEnterEvent->setAccepted(dragEnterEvent->mimeData()->hasUrls() || dragEnterEvent->mimeData()->hasText()); 0411 } 0412 0413 void DiffTextWindow::dropEvent(QDropEvent* dropEvent) 0414 { 0415 dropEvent->accept(); 0416 0417 if(dropEvent->mimeData()->hasUrls()) 0418 { 0419 QList<QUrl> urlList = dropEvent->mimeData()->urls(); 0420 0421 if(m_app.canContinue() && !urlList.isEmpty()) 0422 { 0423 FileAccess fa(urlList.first()); 0424 if(fa.isDir()) 0425 return; 0426 0427 d->sourceData->setFileAccess(fa); 0428 0429 Q_EMIT finishDrop(); 0430 } 0431 } 0432 else if(dropEvent->mimeData()->hasText()) 0433 { 0434 QString text = dropEvent->mimeData()->text(); 0435 0436 if(m_app.canContinue()) 0437 { 0438 QString error; 0439 0440 d->sourceData->setData(text); 0441 const QStringList& errors = d->sourceData->getErrors(); 0442 if(!errors.isEmpty()) 0443 error = d->sourceData->getErrors()[0]; 0444 0445 if(!error.isEmpty()) 0446 { 0447 KMessageBox::error(this, error); 0448 } 0449 0450 Q_EMIT finishDrop(); 0451 } 0452 } 0453 } 0454 0455 void DiffTextWindow::printWindow(RLPainter& painter, const QRect& view, const QString& headerText, qint32 line, const LineType linesPerPage, const QColor& fgColor) 0456 { 0457 QRect clipRect = view; 0458 clipRect.setTop(0); 0459 painter.setClipRect(clipRect); 0460 painter.translate(view.left(), 0); 0461 QFontMetrics fm = painter.fontMetrics(); 0462 { 0463 qint32 lineHeight = fm.height() + fm.ascent(); 0464 const QRectF headerRect(0, 5, view.width(), 3 * (lineHeight)); 0465 QTextOption options; 0466 options.setWrapMode(QTextOption::WordWrap); 0467 // TODO: transition to Qt::LayoutDirectionAuto 0468 options.setTextDirection(Qt::LeftToRight); 0469 static_cast<QPainter&>(painter).drawText(headerRect, headerText, options); 0470 0471 painter.setPen(fgColor); 0472 painter.drawLine(0, view.top() - 2, view.width(), view.top() - 2); 0473 } 0474 0475 painter.translate(0, view.top()); 0476 print(painter, view, line, linesPerPage); 0477 painter.resetTransform(); 0478 } 0479 0480 void DiffTextWindow::setFirstLine(LineRef firstLine) 0481 { 0482 qint32 fontHeight = fontMetrics().lineSpacing(); 0483 0484 LineRef newFirstLine = std::max<LineRef>(0, firstLine); 0485 0486 qint32 deltaY = fontHeight * (d->m_firstLine - newFirstLine); 0487 0488 d->m_firstLine = newFirstLine; 0489 0490 if(d->m_bSelectionInProgress && d->m_selection.isValidFirstLine()) 0491 { 0492 LineRef line; 0493 qint32 pos; 0494 convertToLinePos(d->m_lastKnownMousePos.x(), d->m_lastKnownMousePos.y(), line, pos); 0495 d->m_selection.end(line, pos); 0496 update(); 0497 } 0498 else 0499 { 0500 scroll(0, deltaY); 0501 } 0502 0503 Q_EMIT firstLineChanged(d->m_firstLine); 0504 } 0505 0506 LineRef DiffTextWindow::getFirstLine() const 0507 { 0508 return d->m_firstLine; 0509 } 0510 0511 void DiffTextWindow::setHorizScrollOffset(qint32 horizScrollOffset) 0512 { 0513 d->m_horizScrollOffset = std::max(0, horizScrollOffset); 0514 0515 if(d->m_bSelectionInProgress && d->m_selection.isValidFirstLine()) 0516 { 0517 LineRef line; 0518 qint32 pos; 0519 convertToLinePos(d->m_lastKnownMousePos.x(), d->m_lastKnownMousePos.y(), line, pos); 0520 d->m_selection.end(line, pos); 0521 } 0522 0523 update(); 0524 } 0525 0526 qint32 DiffTextWindow::getMaxTextWidth() const 0527 { 0528 if(d->m_bWordWrap) 0529 { 0530 return getVisibleTextAreaWidth(); 0531 } 0532 else if(d->m_maxTextWidth.loadRelaxed() < 0) 0533 { 0534 d->m_maxTextWidth = 0; 0535 QTextLayout textLayout(QString(), font(), this); 0536 for(qint32 i = 0; i < d->m_size; ++i) 0537 { 0538 textLayout.clearLayout(); 0539 textLayout.setText(d->getString(i)); 0540 d->prepareTextLayout(textLayout); 0541 if(textLayout.maximumWidth() > d->m_maxTextWidth.loadRelaxed()) 0542 d->m_maxTextWidth = qCeil(textLayout.maximumWidth()); 0543 } 0544 } 0545 return d->m_maxTextWidth.loadRelaxed(); 0546 } 0547 0548 LineType DiffTextWindow::getNofLines() const 0549 { 0550 return static_cast<LineType>(d->m_bWordWrap ? d->m_diff3WrapLineVector.size() : d->getDiff3LineVector()->size()); 0551 } 0552 0553 LineType DiffTextWindow::convertLineToDiff3LineIdx(const LineRef line) const 0554 { 0555 if(line.isValid() && d->m_bWordWrap && d->m_diff3WrapLineVector.size() > 0) 0556 return d->m_diff3WrapLineVector[std::min<QtSizeType>(line, d->m_diff3WrapLineVector.size() - 1)].diff3LineIndex; 0557 else 0558 return line; 0559 } 0560 0561 LineRef DiffTextWindow::convertDiff3LineIdxToLine(const LineType d3lIdx) const 0562 { 0563 assert(d3lIdx >= 0); 0564 0565 if(d->m_bWordWrap && d->getDiff3LineVector() != nullptr && d->getDiff3LineVector()->size() > 0) 0566 return (*d->getDiff3LineVector())[std::min((QtSizeType)d3lIdx, d->getDiff3LineVector()->size() - 1)]->sumLinesNeededForDisplay(); 0567 else 0568 return d3lIdx; 0569 } 0570 0571 /** Returns a line number where the linerange [line, line+nofLines] can 0572 be displayed best. If it fits into the currently visible range then 0573 the returned value is the current firstLine. 0574 */ 0575 LineRef getBestFirstLine(LineRef line, LineType nofLines, LineRef firstLine, LineType visibleLines) 0576 { 0577 LineRef newFirstLine = firstLine; 0578 if(line < firstLine || line + nofLines + 2 > firstLine + visibleLines) 0579 { 0580 if(nofLines > visibleLines || nofLines <= (2 * visibleLines / 3 - 1)) 0581 newFirstLine = line - visibleLines / 3; 0582 else 0583 newFirstLine = line - (visibleLines - nofLines); 0584 } 0585 0586 return newFirstLine; 0587 } 0588 0589 void DiffTextWindow::setFastSelectorRange(qint32 line1, qint32 nofLines) 0590 { 0591 d->m_fastSelectorLine1 = line1; 0592 d->m_fastSelectorNofLines = nofLines; 0593 if(isVisible()) 0594 { 0595 LineRef newFirstLine = getBestFirstLine( 0596 convertDiff3LineIdxToLine(d->m_fastSelectorLine1), 0597 convertDiff3LineIdxToLine(d->m_fastSelectorLine1 + d->m_fastSelectorNofLines) - convertDiff3LineIdxToLine(d->m_fastSelectorLine1), 0598 d->m_firstLine, 0599 getNofVisibleLines()); 0600 if(newFirstLine != d->m_firstLine) 0601 { 0602 scrollVertically(newFirstLine - d->m_firstLine); 0603 } 0604 0605 update(); 0606 } 0607 } 0608 /* 0609 Takes the line number estimated from mouse position and converts it to the actual line in 0610 the file. Then sets the status message accordingly. 0611 0612 emits lineClicked signal. 0613 */ 0614 void DiffTextWindow::showStatusLine(const LineRef lineFromPos) 0615 { 0616 LineType d3lIdx = convertLineToDiff3LineIdx(lineFromPos); 0617 0618 if(d->getDiff3LineVector() != nullptr && d3lIdx >= 0 && d3lIdx < (qint32)d->getDiff3LineVector()->size()) 0619 { 0620 const Diff3Line* pD3l = (*d->getDiff3LineVector())[d3lIdx]; 0621 if(pD3l != nullptr) 0622 { 0623 LineRef actualLine = pD3l->getLineInFile(getWindowIndex()); 0624 0625 QString message; 0626 if(actualLine.isValid()) 0627 message = i18n("File %1: Line %2", getFileName(), actualLine + 1); 0628 else 0629 message = i18n("File %1: Line not available", getFileName()); 0630 Q_EMIT statusBarMessage(message); 0631 0632 Q_EMIT lineClicked(getWindowIndex(), actualLine); 0633 } 0634 } 0635 } 0636 0637 void DiffTextWindow::scrollVertically(qint32 deltaY) 0638 { 0639 mVScrollBar->setValue(mVScrollBar->value() + deltaY); 0640 } 0641 0642 void DiffTextWindow::focusInEvent(QFocusEvent* e) 0643 { 0644 Q_EMIT gotFocus(); 0645 QWidget::focusInEvent(e); 0646 } 0647 0648 void DiffTextWindow::mousePressEvent(QMouseEvent* e) 0649 { 0650 qCInfo(kdiffDiffTextWindow) << "mousePressEvent triggered"; 0651 if(e->button() == Qt::LeftButton) 0652 { 0653 LineRef line; 0654 qint32 pos; 0655 convertToLinePos(e->pos().x(), e->pos().y(), line, pos); 0656 qCInfo(kdiffDiffTextWindow) << "Left Button detected,"; 0657 qCDebug(kdiffDiffTextWindow) << "line = " << line << ", pos = " << pos; 0658 0659 //TODO: Fix after line number area is converted to a QWidget. 0660 qint32 fontWidth = fontMetrics().horizontalAdvance('0'); 0661 qint32 xOffset = d->leftInfoWidth() * fontWidth; 0662 0663 if((!gOptions->m_bRightToLeftLanguage && e->pos().x() < xOffset) || (gOptions->m_bRightToLeftLanguage && e->pos().x() > width() - xOffset)) 0664 { 0665 Q_EMIT setFastSelectorLine(convertLineToDiff3LineIdx(line)); 0666 d->m_selection.reset(); // Disable current d->m_selection 0667 } 0668 else 0669 { // Selection 0670 resetSelection(); 0671 d->m_selection.start(line, pos); 0672 d->m_selection.end(line, pos); 0673 d->m_bSelectionInProgress = true; 0674 d->m_lastKnownMousePos = e->pos(); 0675 0676 showStatusLine(line); 0677 } 0678 } 0679 } 0680 0681 void DiffTextWindow::mouseDoubleClickEvent(QMouseEvent* e) 0682 { 0683 qCInfo(kdiffDiffTextWindow) << "Mouse Double Clicked"; 0684 qCDebug(kdiffDiffTextWindow) << "d->m_lastKnownMousePos = " << d->m_lastKnownMousePos << ", e->pos() = " << e->pos(); 0685 qCDebug(kdiffDiffTextWindow) << "d->m_bSelectionInProgress = " << d->m_bSelectionInProgress; 0686 0687 d->m_bSelectionInProgress = false; 0688 d->m_lastKnownMousePos = e->pos(); 0689 if(e->button() == Qt::LeftButton) 0690 { 0691 LineRef line; 0692 qint32 pos; 0693 convertToLinePos(e->pos().x(), e->pos().y(), line, pos); 0694 qCInfo(kdiffDiffTextWindow) << "Left Button detected,"; 0695 qCDebug(kdiffDiffTextWindow) << "line = " << line << ", pos = " << pos; 0696 0697 // Get the string data of the current line 0698 QString s; 0699 if(d->m_bWordWrap) 0700 { 0701 if(!line.isValid() || line >= d->m_diff3WrapLineVector.size()) 0702 return; 0703 const Diff3WrapLine& d3wl = d->m_diff3WrapLineVector[line]; 0704 s = d->getString(d3wl.diff3LineIndex).mid(d3wl.wrapLineOffset, d3wl.wrapLineLength); 0705 } 0706 else 0707 { 0708 if(!line.isValid() || line >= d->getDiff3LineVector()->size()) 0709 return; 0710 s = d->getString(line); 0711 } 0712 0713 if(!s.isEmpty()) 0714 { 0715 const bool selectionWasEmpty = d->m_selection.isEmpty(); 0716 QtSizeType pos1, pos2; 0717 Utils::calcTokenPos(s, pos, pos1, pos2); 0718 0719 resetSelection(); 0720 d->m_selection.start(line, pos1); 0721 d->m_selection.end(line, pos2); 0722 if(!d->m_selection.isEmpty() && selectionWasEmpty) 0723 Q_EMIT newSelection(); 0724 0725 update(); 0726 // Q_EMIT d->m_selectionEnd() happens in the mouseReleaseEvent. 0727 showStatusLine(line); 0728 } 0729 } 0730 } 0731 0732 void DiffTextWindow::mouseReleaseEvent(QMouseEvent* e) 0733 { 0734 qCInfo(kdiffDiffTextWindow) << "Mouse Released"; 0735 qCDebug(kdiffDiffTextWindow) << "d->m_lastKnownMousePos = " << d->m_lastKnownMousePos << ", e->pos() = " << e->pos(); 0736 qCDebug(kdiffDiffTextWindow) << "d->m_bSelectionInProgress = " << d->m_bSelectionInProgress; 0737 0738 d->m_bSelectionInProgress = false; 0739 d->m_lastKnownMousePos = e->pos(); 0740 0741 if(d->m_delayedDrawTimer) 0742 killTimer(d->m_delayedDrawTimer); 0743 d->m_delayedDrawTimer = 0; 0744 if(d->m_selection.isValidFirstLine()) 0745 { 0746 qCInfo(kdiffDiffTextWindow) << "Ending selection."; 0747 Q_EMIT selectionEnd(); 0748 } 0749 0750 d->m_scrollDeltaX = 0; 0751 d->m_scrollDeltaY = 0; 0752 } 0753 0754 void DiffTextWindow::mouseMoveEvent(QMouseEvent* e) 0755 { //Handles selection highlighting. 0756 LineRef line; 0757 qint32 pos; 0758 0759 qCInfo(kdiffDiffTextWindow) << "Mouse Moved"; 0760 qCDebug(kdiffDiffTextWindow) << "d->m_lastKnownMousePos = " << d->m_lastKnownMousePos << ", e->pos() = " << e->pos(); 0761 0762 convertToLinePos(e->pos().x(), e->pos().y(), line, pos); 0763 d->m_lastKnownMousePos = e->pos(); 0764 0765 qCDebug(kdiffDiffTextWindow) << "line = " << line << ", pos = " << pos; 0766 0767 if(d->m_selection.isValidFirstLine()) 0768 { 0769 qCDebug(kdiffDiffTextWindow) << "d->m_selection.isValidFirstLine() = " << d->m_selection.isValidFirstLine(); 0770 const bool selectionWasEmpty = d->m_selection.isEmpty(); 0771 d->m_selection.end(line, pos); 0772 if(!d->m_selection.isEmpty() && selectionWasEmpty) 0773 Q_EMIT newSelection(); 0774 0775 showStatusLine(line); 0776 0777 // Scroll because mouse moved out of the window 0778 const QFontMetrics& fm = fontMetrics(); 0779 qint32 fontWidth = fm.horizontalAdvance('0'); 0780 qint32 deltaX = 0; 0781 qint32 deltaY = 0; 0782 //TODO: Fix after line number area is converted to a QWidget. 0783 //FIXME: Why are we manually doing Layout adjustments? 0784 if(!gOptions->m_bRightToLeftLanguage) 0785 { 0786 if(e->pos().x() < d->leftInfoWidth() * fontWidth) deltaX = -1 - abs(e->pos().x() - d->leftInfoWidth() * fontWidth) / fontWidth; 0787 if(e->pos().x() > width()) deltaX = +1 + abs(e->pos().x() - width()) / fontWidth; 0788 } 0789 else 0790 { 0791 if(e->pos().x() > width() - 1 - d->leftInfoWidth() * fontWidth) deltaX = +1 + abs(e->pos().x() - (width() - 1 - d->leftInfoWidth() * fontWidth)) / fontWidth; 0792 if(e->pos().x() < fontWidth) deltaX = -1 - abs(e->pos().x() - fontWidth) / fontWidth; 0793 } 0794 if(e->pos().y() < 0) deltaY = -1 - (qint32)std::pow<qint32, qint32>(e->pos().y(), 2) / (qint32)std::pow(fm.lineSpacing(), 2); 0795 if(e->pos().y() > height()) deltaY = 1 + (qint32)std::pow(e->pos().y() - height(), 2) / (qint32)std::pow(fm.lineSpacing(), 2); 0796 if((deltaX != 0 && d->m_scrollDeltaX != deltaX) || (deltaY != 0 && d->m_scrollDeltaY != deltaY)) 0797 { 0798 d->m_scrollDeltaX = deltaX; 0799 d->m_scrollDeltaY = deltaY; 0800 Q_EMIT scrollDiffTextWindow(deltaX, deltaY); 0801 if(d->m_delayedDrawTimer) 0802 killTimer(d->m_delayedDrawTimer); 0803 d->m_delayedDrawTimer = startTimer(50); 0804 } 0805 else 0806 { 0807 d->m_scrollDeltaX = deltaX; 0808 d->m_scrollDeltaY = deltaY; 0809 d->myUpdate(0); 0810 } 0811 } 0812 } 0813 0814 void DiffTextWindow::wheelEvent(QWheelEvent* pWheelEvent) 0815 { 0816 QPoint delta = pWheelEvent->angleDelta(); 0817 0818 //Block diagonal scrolling easily generated unintentionally with track pads. 0819 if(delta.y() != 0 && abs(delta.y()) > abs(delta.x()) && mVScrollBar != nullptr) 0820 { 0821 pWheelEvent->accept(); 0822 QCoreApplication::sendEvent(mVScrollBar, pWheelEvent); 0823 } 0824 } 0825 0826 void DiffTextWindowData::myUpdate(qint32 afterMilliSecs) 0827 { 0828 if(m_delayedDrawTimer) 0829 m_pDiffTextWindow->killTimer(m_delayedDrawTimer); 0830 m_bMyUpdate = true; 0831 m_delayedDrawTimer = m_pDiffTextWindow->startTimer(afterMilliSecs); 0832 } 0833 0834 void DiffTextWindow::timerEvent(QTimerEvent*) 0835 { 0836 killTimer(d->m_delayedDrawTimer); 0837 d->m_delayedDrawTimer = 0; 0838 0839 if(d->m_bMyUpdate) 0840 { 0841 qint32 fontHeight = fontMetrics().lineSpacing(); 0842 0843 if(d->m_selection.getOldLastLine().isValid()) 0844 { 0845 LineRef lastLine; 0846 LineRef firstLine; 0847 if(d->m_selection.getOldFirstLine().isValid()) 0848 { 0849 firstLine = std::min({d->m_selection.getOldFirstLine(), d->m_selection.getLastLine(), d->m_selection.getOldLastLine()}); 0850 lastLine = std::max({d->m_selection.getOldFirstLine(), d->m_selection.getLastLine(), d->m_selection.getOldLastLine()}); 0851 } 0852 else 0853 { 0854 firstLine = std::min(d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); 0855 lastLine = std::max(d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); 0856 } 0857 qint32 y1 = (firstLine - d->m_firstLine) * fontHeight; 0858 qint32 y2 = std::min(height(), (lastLine - d->m_firstLine + 1) * fontHeight); 0859 0860 if(y1 < height() && y2 > 0) 0861 { 0862 QRect invalidRect = QRect(0, y1 - 1, width(), y2 - y1 + fontHeight); // Some characters in exotic exceed the regular bottom. 0863 update(invalidRect); 0864 } 0865 } 0866 0867 d->m_bMyUpdate = false; 0868 } 0869 0870 if(d->m_scrollDeltaX != 0 || d->m_scrollDeltaY != 0) 0871 { 0872 QtSizeType newPos = d->m_selection.getLastPos() + d->m_scrollDeltaX; 0873 try 0874 { 0875 LineRef newLine = d->m_selection.getLastLine() + d->m_scrollDeltaY; 0876 0877 d->m_selection.end(newLine, newPos > 0 ? newPos : 0); 0878 } 0879 catch(const std::system_error&) 0880 { 0881 d->m_selection.end(LineRef::invalid, newPos > 0 ? newPos : 0); 0882 } 0883 Q_EMIT scrollDiffTextWindow(d->m_scrollDeltaX, d->m_scrollDeltaY); 0884 killTimer(d->m_delayedDrawTimer); 0885 d->m_delayedDrawTimer = startTimer(50); 0886 } 0887 } 0888 0889 void DiffTextWindow::resetSelection() 0890 { 0891 qCInfo(kdiffDiffTextWindow) << "Resetting Selection"; 0892 d->m_selection.reset(); 0893 update(); 0894 } 0895 0896 void DiffTextWindow::convertToLinePos(qint32 x, qint32 y, LineRef& line, qint32& pos) 0897 { 0898 const QFontMetrics& fm = fontMetrics(); 0899 qint32 fontHeight = fm.lineSpacing(); 0900 0901 qint32 yOffset = d->m_firstLine * fontHeight; 0902 0903 if((y + yOffset) >= 0) 0904 line = (y + yOffset) / fontHeight; 0905 else 0906 line = LineRef::invalid; 0907 0908 if(line.isValid() && (!gOptions->wordWrapOn() || line < d->m_diff3WrapLineVector.count())) 0909 { 0910 QString s = d->getLineString(line); 0911 QTextLayout textLayout(s, font(), this); 0912 d->prepareTextLayout(textLayout); 0913 pos = textLayout.lineAt(0).xToCursor(x - textLayout.position().x()); 0914 } 0915 else 0916 pos = -1; 0917 } 0918 0919 class FormatRangeHelper 0920 { 0921 private: 0922 QFont m_font; 0923 QPen m_pen; 0924 QColor m_background; 0925 qint32 m_currentPos; 0926 0927 QVector<QTextLayout::FormatRange> m_formatRanges; 0928 0929 public: 0930 inline operator QVector<QTextLayout::FormatRange>() { return m_formatRanges; } 0931 FormatRangeHelper() 0932 { 0933 m_pen = QColor(Qt::black); 0934 m_background = QColor(Qt::white); 0935 m_currentPos = 0; 0936 } 0937 0938 void setFont(const QFont& f) 0939 { 0940 m_font = f; 0941 } 0942 0943 void setPen(const QPen& pen) 0944 { 0945 m_pen = pen; 0946 } 0947 0948 void setBackground(const QColor& background) 0949 { 0950 m_background = background; 0951 } 0952 0953 void next() 0954 { 0955 if(m_formatRanges.isEmpty() || m_formatRanges.back().format.foreground().color() != m_pen.color() || m_formatRanges.back().format.background().color() != m_background) 0956 { 0957 QTextLayout::FormatRange fr; 0958 fr.length = 1; 0959 fr.start = m_currentPos; 0960 fr.format.setForeground(m_pen.color()); 0961 fr.format.setBackground(m_background); 0962 m_formatRanges.append(fr); 0963 } 0964 else 0965 { 0966 ++m_formatRanges.back().length; 0967 } 0968 ++m_currentPos; 0969 } 0970 }; 0971 0972 void DiffTextWindowData::prepareTextLayout(QTextLayout& textLayout, qint32 visibleTextWidth) 0973 { 0974 QTextOption textOption; 0975 0976 textOption.setTabStopDistance(QFontMetricsF(m_pDiffTextWindow->font()).horizontalAdvance(' ') * gOptions->tabSize()); 0977 0978 if(gOptions->m_bShowWhiteSpaceCharacters) 0979 textOption.setFlags(QTextOption::ShowTabsAndSpaces); 0980 if(gOptions->m_bRightToLeftLanguage) 0981 0982 { 0983 textOption.setAlignment(Qt::AlignRight); // only relevant for multi line text layout 0984 } 0985 if(visibleTextWidth >= 0) 0986 textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); 0987 0988 textLayout.setTextOption(textOption); 0989 0990 if(gOptions->m_bShowWhiteSpaceCharacters) 0991 { 0992 // This additional format is only necessary for the tab arrow 0993 QVector<QTextLayout::FormatRange> formats; 0994 QTextLayout::FormatRange formatRange; 0995 formatRange.start = 0; 0996 formatRange.length = SafeInt<qint32>(textLayout.text().length()); 0997 formatRange.format.setFont(m_pDiffTextWindow->font()); 0998 formats.append(formatRange); 0999 textLayout.setFormats(formats); 1000 } 1001 textLayout.beginLayout(); 1002 1003 qint32 leading = m_pDiffTextWindow->fontMetrics().leading(); 1004 qint32 height = 0; 1005 //TODO: Fix after line number area is converted to a QWidget. 1006 qint32 fontWidth = m_pDiffTextWindow->fontMetrics().horizontalAdvance('0'); 1007 qint32 xOffset = leftInfoWidth() * fontWidth - m_horizScrollOffset; 1008 qint32 textWidth = visibleTextWidth; 1009 if(textWidth < 0) 1010 textWidth = m_pDiffTextWindow->width() - xOffset; 1011 1012 qint32 indentation = 0; 1013 while(true) 1014 { 1015 QTextLine line = textLayout.createLine(); 1016 if(!line.isValid()) 1017 break; 1018 1019 height += leading; 1020 if(visibleTextWidth >= 0) 1021 { 1022 line.setLineWidth(visibleTextWidth - indentation); 1023 line.setPosition(QPointF(indentation, height)); 1024 height += qCeil(line.height()); 1025 } 1026 else // only one line 1027 { 1028 line.setPosition(QPointF(indentation, height)); 1029 break; 1030 } 1031 } 1032 1033 textLayout.endLayout(); 1034 if(gOptions->m_bRightToLeftLanguage) 1035 textLayout.setPosition(QPointF(textWidth - textLayout.maximumWidth(), 0)); 1036 else 1037 textLayout.setPosition(QPointF(xOffset, 0)); 1038 } 1039 1040 /* 1041 Don't try to use invalid rect to block drawing of lines based on there apparent horizontal dementions. 1042 This does not always work for very long lines being scrolled horizontally. (Causes blanking of diff text area) 1043 */ 1044 void DiffTextWindowData::writeLine( 1045 RLPainter& p, 1046 const LineData* pld, 1047 const std::shared_ptr<const DiffList>& pLineDiff1, 1048 const std::shared_ptr<const DiffList>& pLineDiff2, 1049 const LineRef& line, 1050 const ChangeFlags whatChanged, 1051 const ChangeFlags whatChanged2, 1052 const LineRef& srcLineIdx, 1053 qint32 wrapLineOffset, 1054 qint32 wrapLineLength, 1055 bool bWrapLine, 1056 const QRect& invalidRect) 1057 { 1058 QFont normalFont = p.font(); 1059 1060 const QFontMetrics& fm = p.fontMetrics(); 1061 qint32 fontHeight = fm.lineSpacing(); 1062 qint32 fontAscent = fm.ascent(); 1063 qint32 fontWidth = fm.horizontalAdvance('0'); 1064 1065 qint32 xOffset = 0; 1066 qint32 yOffset = (line - m_firstLine) * fontHeight; 1067 1068 qint32 fastSelectorLine1 = m_pDiffTextWindow->convertDiff3LineIdxToLine(m_fastSelectorLine1); 1069 qint32 fastSelectorLine2 = m_pDiffTextWindow->convertDiff3LineIdxToLine(m_fastSelectorLine1 + m_fastSelectorNofLines) - 1; 1070 1071 bool bFastSelectionRange = (line >= fastSelectorLine1 && line <= fastSelectorLine2); 1072 QColor bgColor = gOptions->backgroundColor(); 1073 QColor diffBgColor = gOptions->diffBackgroundColor(); 1074 1075 if(bFastSelectionRange) 1076 { 1077 bgColor = gOptions->getCurrentRangeBgColor(); 1078 diffBgColor = gOptions->getCurrentRangeDiffBgColor(); 1079 } 1080 1081 if(yOffset + fontHeight < invalidRect.top() || invalidRect.bottom() < yOffset - fontHeight) 1082 return; 1083 1084 ChangeFlags changed = whatChanged; 1085 if(pLineDiff1 != nullptr) changed |= AChanged; 1086 if(pLineDiff2 != nullptr) changed |= BChanged; 1087 1088 QColor penColor = gOptions->foregroundColor(); 1089 p.setPen(penColor); 1090 if(changed == BChanged) 1091 { 1092 penColor = m_cDiff2; 1093 } 1094 else if(changed == AChanged) 1095 { 1096 penColor = m_cDiff1; 1097 } 1098 else if(changed == Both) 1099 { 1100 penColor = m_cDiffBoth; 1101 } 1102 1103 if(pld != nullptr) 1104 { 1105 // First calculate the "changed" information for each character. 1106 QtSizeType i = 0; 1107 QString lineString = pld->getLine(); 1108 if(!lineString.isEmpty()) 1109 { 1110 switch(lineString[lineString.length() - 1].unicode()) 1111 { 1112 case '\n': 1113 lineString[lineString.length() - 1] = QChar(0x00B6); 1114 break; // "Pilcrow", "paragraph mark" 1115 case '\r': 1116 lineString[lineString.length() - 1] = QChar(0x00A4); 1117 break; // Currency sign ;0x2761 "curved stem paragraph sign ornament" 1118 //case '\0b' : lineString[lineString.length()-1] = 0x2756; break; // some other nice looking character 1119 } 1120 } 1121 QVector<ChangeFlags> charChanged(pld->size()); 1122 Merger merger(pLineDiff1, pLineDiff2); 1123 while(!merger.isEndReached() && i < pld->size()) 1124 { 1125 charChanged[i] = merger.whatChanged(); 1126 ++i; 1127 1128 merger.next(); 1129 } 1130 1131 qint32 outPos = 0; 1132 1133 QtSizeType lineLength = m_bWordWrap ? wrapLineOffset + wrapLineLength : lineString.length(); 1134 1135 FormatRangeHelper frh; 1136 1137 for(i = wrapLineOffset; i < lineLength; ++i) 1138 { 1139 penColor = gOptions->foregroundColor(); 1140 ChangeFlags cchanged = charChanged[i] | whatChanged; 1141 1142 if(cchanged == BChanged) 1143 { 1144 penColor = m_cDiff2; 1145 } 1146 else if(cchanged == AChanged) 1147 { 1148 penColor = m_cDiff1; 1149 } 1150 else if(cchanged == Both) 1151 { 1152 penColor = m_cDiffBoth; 1153 } 1154 1155 if(penColor != gOptions->foregroundColor() && whatChanged2 == NoChange && !gOptions->m_bShowWhiteSpace) 1156 { 1157 // The user doesn't want to see highlighted white space. 1158 penColor = gOptions->foregroundColor(); 1159 } 1160 1161 frh.setBackground(bgColor); 1162 if(!m_selection.within(line, outPos)) 1163 { 1164 if(penColor != gOptions->foregroundColor()) 1165 { 1166 frh.setBackground(diffBgColor); 1167 // Setting italic font here doesn't work: Changing the font only when drawing is too late 1168 } 1169 1170 frh.setPen(penColor); 1171 frh.next(); 1172 frh.setFont(normalFont); 1173 } 1174 else 1175 { 1176 frh.setBackground(m_pDiffTextWindow->palette().highlight().color()); 1177 frh.setPen(m_pDiffTextWindow->palette().highlightedText().color()); 1178 frh.next(); 1179 } 1180 1181 ++outPos; 1182 } // end for 1183 1184 QTextLayout textLayout(lineString.mid(wrapLineOffset, lineLength - wrapLineOffset), m_pDiffTextWindow->font(), m_pDiffTextWindow); 1185 prepareTextLayout(textLayout); 1186 textLayout.draw(&p, QPoint(0, yOffset), frh /*, const QRectF & clip = QRectF() */); 1187 } 1188 1189 p.fillRect(0, yOffset, leftInfoWidth() * fontWidth, fontHeight, gOptions->backgroundColor()); 1190 1191 //TODO: Fix after line number area is converted to a QWidget. 1192 xOffset = (m_lineNumberWidth + 2) * fontWidth; 1193 qint32 xLeft = m_lineNumberWidth * fontWidth; 1194 p.setPen(gOptions->foregroundColor()); 1195 if(pld != nullptr) 1196 { 1197 if(gOptions->m_bShowLineNumbers && !bWrapLine) 1198 { 1199 QString num = QString::number(srcLineIdx + 1); 1200 assert(!num.isEmpty()); 1201 p.drawText(0, yOffset + fontAscent, num); 1202 } 1203 if(!bWrapLine || wrapLineLength > 0) 1204 { 1205 Qt::PenStyle wrapLinePenStyle = Qt::DotLine; 1206 1207 p.setPen(QPen(gOptions->foregroundColor(), 0, bWrapLine ? wrapLinePenStyle : Qt::SolidLine)); 1208 p.drawLine(xOffset + 1, yOffset, xOffset + 1, yOffset + fontHeight - 1); 1209 p.setPen(QPen(gOptions->foregroundColor(), 0, Qt::SolidLine)); 1210 } 1211 } 1212 if(penColor != gOptions->foregroundColor() && whatChanged2 == NoChange) 1213 { 1214 if(gOptions->m_bShowWhiteSpace) 1215 { 1216 p.setBrushOrigin(0, 0); 1217 p.fillRect(xLeft, yOffset, fontWidth * 2 - 1, fontHeight, QBrush(penColor, Qt::Dense5Pattern)); 1218 } 1219 } 1220 else 1221 { 1222 p.fillRect(xLeft, yOffset, fontWidth * 2 - 1, fontHeight, penColor == gOptions->foregroundColor() ? bgColor : penColor); 1223 } 1224 1225 if(bFastSelectionRange) 1226 { 1227 p.fillRect(xOffset + fontWidth - 1, yOffset, 3, fontHeight, gOptions->foregroundColor()); 1228 } 1229 1230 // Check if line needs a manual diff help mark 1231 ManualDiffHelpList::const_iterator ci; 1232 for(ci = m_pManualDiffHelpList->begin(); ci != m_pManualDiffHelpList->end(); ++ci) 1233 { 1234 const ManualDiffHelpEntry& mdhe = *ci; 1235 LineRef rangeLine1; 1236 LineRef rangeLine2; 1237 1238 mdhe.getRangeForUI(getWindowIndex(), &rangeLine1, &rangeLine2); 1239 if(rangeLine1.isValid() && rangeLine2.isValid() && srcLineIdx >= rangeLine1 && srcLineIdx <= rangeLine2) 1240 { 1241 p.fillRect(xOffset - fontWidth, yOffset, fontWidth - 1, fontHeight, gOptions->manualHelpRangeColor()); 1242 break; 1243 } 1244 } 1245 } 1246 1247 void DiffTextWindow::paintEvent(QPaintEvent* e) 1248 { 1249 QRect invalidRect = e->rect(); 1250 if(invalidRect.isEmpty()) 1251 return; 1252 1253 if(d->getDiff3LineVector() == nullptr || (d->m_diff3WrapLineVector.empty() && d->m_bWordWrap)) 1254 { 1255 QPainter p(this); 1256 p.fillRect(invalidRect, gOptions->backgroundColor()); 1257 return; 1258 } 1259 1260 LineRef endLine = std::min(d->m_firstLine + getNofVisibleLines() + 2, getNofLines()); 1261 RLPainter p(this, gOptions->m_bRightToLeftLanguage, width(), fontMetrics().horizontalAdvance('0')); 1262 1263 p.setFont(font()); 1264 p.QPainter::fillRect(invalidRect, gOptions->backgroundColor()); 1265 1266 d->draw(p, invalidRect, d->m_firstLine, endLine); 1267 p.end(); 1268 1269 d->m_oldFirstLine = d->m_firstLine; 1270 d->m_selection.clearOldSelection(); 1271 } 1272 1273 void DiffTextWindow::print(RLPainter& p, const QRect&, qint32 firstLine, const LineType nofLinesPerPage) 1274 { 1275 if(d->getDiff3LineVector() == nullptr || !updatesEnabled() || 1276 (d->m_diff3WrapLineVector.empty() && d->m_bWordWrap)) 1277 return; 1278 resetSelection(); 1279 LineRef oldFirstLine = d->m_firstLine; 1280 d->m_firstLine = firstLine; 1281 QRect invalidRect = QRect(0, 0, 1000000000, 1000000000); 1282 gOptions->beginPrint(); 1283 d->draw(p, invalidRect, firstLine, std::min(firstLine + nofLinesPerPage, getNofLines())); 1284 gOptions->endPrint(); 1285 d->m_firstLine = oldFirstLine; 1286 } 1287 1288 void DiffTextWindowData::draw(RLPainter& p, const QRect& invalidRect, const qint32 beginLine, const LineRef& endLine) 1289 { 1290 if(m_pLineData == nullptr || m_pLineData->empty()) return; 1291 //TODO: Fix after line number area is converted to a QWidget. 1292 m_lineNumberWidth = gOptions->m_bShowLineNumbers ? m_pDiffTextWindow->getLineNumberWidth() : 0; 1293 1294 if(getWindowIndex() == e_SrcSelector::A) 1295 { 1296 m_cThis = gOptions->aColor(); 1297 m_cDiff1 = gOptions->bColor(); 1298 m_cDiff2 = gOptions->cColor(); 1299 } 1300 else if(getWindowIndex() == e_SrcSelector::B) 1301 { 1302 m_cThis = gOptions->bColor(); 1303 m_cDiff1 = gOptions->cColor(); 1304 m_cDiff2 = gOptions->aColor(); 1305 } 1306 else if(getWindowIndex() == e_SrcSelector::C) 1307 { 1308 m_cThis = gOptions->cColor(); 1309 m_cDiff1 = gOptions->aColor(); 1310 m_cDiff2 = gOptions->bColor(); 1311 } 1312 m_cDiffBoth = gOptions->conflictColor(); // Conflict color 1313 1314 p.setPen(m_cThis); 1315 1316 for(qint32 line = beginLine; line < endLine; ++line) 1317 { 1318 qint32 wrapLineOffset = 0; 1319 qint32 wrapLineLength = 0; 1320 const Diff3Line* d3l = nullptr; 1321 bool bWrapLine = false; 1322 if(m_bWordWrap) 1323 { 1324 Diff3WrapLine& d3wl = m_diff3WrapLineVector[line]; 1325 wrapLineOffset = d3wl.wrapLineOffset; 1326 wrapLineLength = d3wl.wrapLineLength; 1327 d3l = d3wl.pD3L; 1328 bWrapLine = line > 0 && m_diff3WrapLineVector[line - 1].pD3L == d3l; 1329 } 1330 else 1331 { 1332 d3l = (*mDiff3LineVector)[line]; 1333 } 1334 std::shared_ptr<const DiffList> pFineDiff1; 1335 std::shared_ptr<const DiffList> pFineDiff2; 1336 ChangeFlags changed = NoChange; 1337 ChangeFlags changed2 = NoChange; 1338 1339 LineRef srcLineIdx; 1340 d3l->getLineInfo(getWindowIndex(), KDiff3App::isTripleDiff(), srcLineIdx, pFineDiff1, pFineDiff2, changed, changed2); 1341 1342 writeLine( 1343 p, // QPainter 1344 !srcLineIdx.isValid() ? nullptr : &(*m_pLineData)[srcLineIdx], // Text in this line 1345 pFineDiff1, 1346 pFineDiff2, 1347 line, // Line on the screen 1348 changed, 1349 changed2, 1350 srcLineIdx, 1351 wrapLineOffset, 1352 wrapLineLength, 1353 bWrapLine, 1354 invalidRect); 1355 } 1356 } 1357 1358 QString DiffTextWindowData::getString(const LineType d3lIdx) const 1359 { 1360 assert(!(m_pLineData != nullptr && m_pLineData->empty() && m_size != 0)); 1361 1362 if(m_pLineData == nullptr || m_pLineData->empty() || d3lIdx < 0 || d3lIdx >= mDiff3LineVector->size()) 1363 return QString(); 1364 1365 const Diff3Line* d3l = (*mDiff3LineVector)[d3lIdx]; 1366 const LineType lineIdx = d3l->getLineIndex(getWindowIndex()); 1367 1368 if(lineIdx == LineRef::invalid) 1369 return QString(); 1370 1371 return (*m_pLineData)[lineIdx].getLine(); 1372 } 1373 1374 QString DiffTextWindowData::getLineString(const LineType line) const 1375 { 1376 if(m_bWordWrap) 1377 { 1378 if(line < m_diff3WrapLineVector.count()) 1379 { 1380 LineType d3LIdx = m_pDiffTextWindow->convertLineToDiff3LineIdx(line); 1381 return getString(d3LIdx).mid(m_diff3WrapLineVector[line].wrapLineOffset, m_diff3WrapLineVector[line].wrapLineLength); 1382 } 1383 else 1384 return QString(); 1385 } 1386 else 1387 { 1388 return getString(line); 1389 } 1390 } 1391 1392 void DiffTextWindow::resizeEvent(QResizeEvent* e) 1393 { 1394 QSize newSize = e->size(); 1395 QFontMetrics fm = fontMetrics(); 1396 qint32 visibleLines = newSize.height() / fm.lineSpacing() - 2; 1397 //TODO: Fix after line number area is converted to a QWidget. 1398 qint32 visibleColumns = newSize.width() / fm.horizontalAdvance('0') - d->leftInfoWidth(); 1399 1400 if(e->size().height() != e->oldSize().height()) 1401 Q_EMIT resizeHeightChangedSignal(visibleLines); 1402 if(e->size().width() != e->oldSize().width()) 1403 Q_EMIT resizeWidthChangedSignal(visibleColumns); 1404 QWidget::resizeEvent(e); 1405 } 1406 1407 LineType DiffTextWindow::getNofVisibleLines() const 1408 { 1409 QFontMetrics fm = fontMetrics(); 1410 1411 return height() / fm.lineSpacing() - 1; 1412 } 1413 1414 qint32 DiffTextWindow::getVisibleTextAreaWidth() const 1415 { 1416 //TODO: Check after line number area is converted to a QWidget. 1417 QFontMetrics fm = fontMetrics(); 1418 1419 return width() - d->leftInfoWidth() * fm.horizontalAdvance('0'); 1420 } 1421 1422 QString DiffTextWindow::getSelection() const 1423 { 1424 if(d->m_pLineData == nullptr) 1425 return QString(); 1426 1427 QString selectionString; 1428 1429 LineRef line = 0; 1430 LineType lineIdx = 0; 1431 1432 QtSizeType it; 1433 QtSizeType vectorSize = d->m_bWordWrap ? d->m_diff3WrapLineVector.size() : d->getDiff3LineVector()->size(); 1434 for(it = 0; it < vectorSize; ++it) 1435 { 1436 const Diff3Line* d3l = d->m_bWordWrap ? d->m_diff3WrapLineVector[it].pD3L : (*d->getDiff3LineVector())[it]; 1437 1438 assert(getWindowIndex() >= e_SrcSelector::A && getWindowIndex() <= e_SrcSelector::C); 1439 1440 if(getWindowIndex() == e_SrcSelector::A) 1441 { 1442 lineIdx = d3l->getLineA(); 1443 } 1444 else if(getWindowIndex() == e_SrcSelector::B) 1445 { 1446 lineIdx = d3l->getLineB(); 1447 } 1448 else if(getWindowIndex() == e_SrcSelector::C) 1449 { 1450 lineIdx = d3l->getLineC(); 1451 } 1452 1453 if(lineIdx != LineRef::invalid) 1454 { 1455 QtSizeType size = (*d->m_pLineData)[lineIdx].size(); 1456 QString lineString = (*d->m_pLineData)[lineIdx].getLine(); 1457 1458 if(d->m_bWordWrap) 1459 { 1460 size = d->m_diff3WrapLineVector[it].wrapLineLength; 1461 lineString = lineString.mid(d->m_diff3WrapLineVector[it].wrapLineOffset, size); 1462 } 1463 1464 for(QtSizeType i = 0; i < size; ++i) 1465 { 1466 if(d->m_selection.within(line, i)) 1467 { 1468 selectionString += lineString[i]; 1469 } 1470 } 1471 1472 if(d->m_selection.within(line, size) && 1473 (!d->m_bWordWrap || it + 1 >= vectorSize || d3l != d->m_diff3WrapLineVector[it + 1].pD3L)) 1474 { 1475 #if defined(Q_OS_WIN) 1476 selectionString += '\r'; 1477 #endif 1478 selectionString += '\n'; 1479 } 1480 } 1481 1482 ++line; 1483 } 1484 1485 return selectionString; 1486 } 1487 1488 bool DiffTextWindow::findString(const QString& s, LineRef& d3vLine, QtSizeType& posInLine, bool bDirDown, bool bCaseSensitive) 1489 { 1490 LineRef it = d3vLine; 1491 QtSizeType endIt = bDirDown ? d->getDiff3LineVector()->size() : -1; 1492 qint32 step = bDirDown ? 1 : -1; 1493 QtSizeType startPos = posInLine; 1494 1495 for(; it != endIt; it += step) 1496 { 1497 QString line = d->getString(it); 1498 if(!line.isEmpty()) 1499 { 1500 QtSizeType pos = line.indexOf(s, startPos, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); 1501 //TODO: Provide error message when failsafe is triggered. 1502 if(Q_UNLIKELY(pos > limits<qint32>::max())) 1503 { 1504 qCWarning(kdiffMain) << "Skip possiable match line offset to large."; 1505 continue; 1506 } 1507 1508 if(pos != -1) 1509 { 1510 d3vLine = it; 1511 posInLine = pos; 1512 return true; 1513 } 1514 1515 startPos = 0; 1516 } 1517 } 1518 return false; 1519 } 1520 1521 void DiffTextWindow::convertD3LCoordsToLineCoords(LineType d3LIdx, QtSizeType d3LPos, LineRef& line, QtSizeType& pos) const 1522 { 1523 if(d->m_bWordWrap) 1524 { 1525 QtSizeType wrapPos = d3LPos; 1526 LineRef wrapLine = convertDiff3LineIdxToLine(d3LIdx); 1527 while(wrapPos > d->m_diff3WrapLineVector[wrapLine].wrapLineLength) 1528 { 1529 wrapPos -= d->m_diff3WrapLineVector[wrapLine].wrapLineLength; 1530 ++wrapLine; 1531 } 1532 pos = wrapPos; 1533 line = wrapLine; 1534 } 1535 else 1536 { 1537 pos = d3LPos; 1538 line = d3LIdx; 1539 } 1540 } 1541 1542 void DiffTextWindow::convertLineCoordsToD3LCoords(LineRef line, QtSizeType pos, LineType& d3LIdx, QtSizeType& d3LPos) const 1543 { 1544 if(d->m_bWordWrap && !d->m_diff3WrapLineVector.empty()) 1545 { 1546 d3LPos = pos; 1547 d3LIdx = convertLineToDiff3LineIdx(line); 1548 LineRef wrapLine = convertDiff3LineIdxToLine(d3LIdx); // First wrap line belonging to this d3LIdx 1549 while(wrapLine < line) 1550 { 1551 d3LPos += d->m_diff3WrapLineVector[wrapLine].wrapLineLength; 1552 ++wrapLine; 1553 } 1554 } 1555 else 1556 { 1557 d3LPos = pos; 1558 d3LIdx = line; 1559 } 1560 } 1561 1562 void DiffTextWindow::setSelection(LineRef firstLine, QtSizeType startPos, LineRef lastLine, QtSizeType endPos, LineRef& l, QtSizeType& p) 1563 { 1564 d->m_selection.reset(); 1565 if(lastLine >= getNofLines()) 1566 { 1567 lastLine = getNofLines() - 1; 1568 1569 const Diff3Line* d3l = (*d->getDiff3LineVector())[convertLineToDiff3LineIdx(lastLine)]; 1570 LineRef line; 1571 if(getWindowIndex() == e_SrcSelector::A) line = d3l->getLineA(); 1572 if(getWindowIndex() == e_SrcSelector::B) line = d3l->getLineB(); 1573 if(getWindowIndex() == e_SrcSelector::C) line = d3l->getLineC(); 1574 if(line.isValid()) 1575 endPos = (*d->m_pLineData)[line].width(gOptions->tabSize()); 1576 } 1577 1578 if(d->m_bWordWrap && d->getDiff3LineVector() != nullptr) 1579 { 1580 QString s1 = d->getString(firstLine); 1581 LineRef firstWrapLine = convertDiff3LineIdxToLine(firstLine); 1582 QtSizeType wrapStartPos = startPos; 1583 while(wrapStartPos > d->m_diff3WrapLineVector[firstWrapLine].wrapLineLength) 1584 { 1585 wrapStartPos -= d->m_diff3WrapLineVector[firstWrapLine].wrapLineLength; 1586 s1 = s1.mid(d->m_diff3WrapLineVector[firstWrapLine].wrapLineLength); 1587 ++firstWrapLine; 1588 } 1589 1590 QString s2 = d->getString(lastLine); 1591 LineRef lastWrapLine = convertDiff3LineIdxToLine(lastLine); 1592 QtSizeType wrapEndPos = endPos; 1593 while(wrapEndPos > d->m_diff3WrapLineVector[lastWrapLine].wrapLineLength) 1594 { 1595 wrapEndPos -= d->m_diff3WrapLineVector[lastWrapLine].wrapLineLength; 1596 s2 = s2.mid(d->m_diff3WrapLineVector[lastWrapLine].wrapLineLength); 1597 ++lastWrapLine; 1598 } 1599 1600 d->m_selection.start(firstWrapLine, wrapStartPos); 1601 d->m_selection.end(lastWrapLine, wrapEndPos); 1602 l = firstWrapLine; 1603 p = wrapStartPos; 1604 } 1605 else 1606 { 1607 if(d->getDiff3LineVector() != nullptr) 1608 { 1609 d->m_selection.start(firstLine, startPos); 1610 d->m_selection.end(lastLine, endPos); 1611 l = firstLine; 1612 p = startPos; 1613 } 1614 } 1615 update(); 1616 } 1617 1618 LineRef DiffTextWindowData::convertLineOnScreenToLineInSource(const qint32 lineOnScreen, const e_CoordType coordType, const bool bFirstLine) const 1619 { 1620 LineRef line; 1621 if(lineOnScreen >= 0) 1622 { 1623 if(coordType == eWrapCoords) return lineOnScreen; 1624 LineType d3lIdx = m_pDiffTextWindow->convertLineToDiff3LineIdx(lineOnScreen); 1625 if(!bFirstLine && d3lIdx >= mDiff3LineVector->size()) 1626 d3lIdx = SafeInt<LineType>(mDiff3LineVector->size() - 1); 1627 if(coordType == eD3LLineCoords) return d3lIdx; 1628 while(!line.isValid() && d3lIdx < mDiff3LineVector->size() && d3lIdx >= 0) 1629 { 1630 const Diff3Line* d3l = (*mDiff3LineVector)[d3lIdx]; 1631 if(getWindowIndex() == e_SrcSelector::A) line = d3l->getLineA(); 1632 if(getWindowIndex() == e_SrcSelector::B) line = d3l->getLineB(); 1633 if(getWindowIndex() == e_SrcSelector::C) line = d3l->getLineC(); 1634 if(bFirstLine) 1635 ++d3lIdx; 1636 else 1637 --d3lIdx; 1638 } 1639 assert(coordType == eFileCoords); 1640 } 1641 return line; 1642 } 1643 1644 void DiffTextWindow::getSelectionRange(LineRef* pFirstLine, LineRef* pLastLine, e_CoordType coordType) const 1645 { 1646 if(pFirstLine) 1647 *pFirstLine = d->convertLineOnScreenToLineInSource(d->m_selection.beginLine(), coordType, true); 1648 if(pLastLine) 1649 *pLastLine = d->convertLineOnScreenToLineInSource(d->m_selection.endLine(), coordType, false); 1650 } 1651 1652 void DiffTextWindow::convertSelectionToD3LCoords() const 1653 { 1654 if(d->getDiff3LineVector() == nullptr || !updatesEnabled() || !isVisible() || d->m_selection.isEmpty()) 1655 { 1656 return; 1657 } 1658 1659 // convert the d->m_selection to unwrapped coordinates: Later restore to new coords 1660 LineType firstD3LIdx; 1661 QtSizeType firstD3LPos; 1662 QtSizeType firstPosInText = d->m_selection.beginPos(); 1663 convertLineCoordsToD3LCoords(d->m_selection.beginLine(), firstPosInText, firstD3LIdx, firstD3LPos); 1664 1665 LineType lastD3LIdx; 1666 QtSizeType lastD3LPos; 1667 QtSizeType lastPosInText = d->m_selection.endPos(); 1668 convertLineCoordsToD3LCoords(d->m_selection.endLine(), lastPosInText, lastD3LIdx, lastD3LPos); 1669 1670 d->m_selection.start(firstD3LIdx, firstD3LPos); 1671 d->m_selection.end(lastD3LIdx, lastD3LPos); 1672 } 1673 1674 bool DiffTextWindow::startRunnables() 1675 { 1676 assert(RecalcWordWrapThread::s_maxNofRunnables == 0); 1677 1678 if(s_runnables.size() == 0) 1679 { 1680 return false; 1681 } 1682 else 1683 { 1684 g_pProgressDialog->setStayHidden(true); 1685 ProgressProxy::startBackgroundTask(); 1686 g_pProgressDialog->setMaxNofSteps(s_runnables.size()); 1687 RecalcWordWrapThread::s_maxNofRunnables = s_runnables.size(); 1688 g_pProgressDialog->setCurrent(0); 1689 1690 for(size_t i = 0; i < s_runnables.size(); ++i) 1691 { 1692 s_runnables[i]->start(); 1693 } 1694 1695 s_runnables.clear(); 1696 return true; 1697 } 1698 } 1699 1700 void DiffTextWindow::recalcWordWrap(bool bWordWrap, QtSizeType wrapLineVectorSize, qint32 visibleTextWidth) 1701 { 1702 if(d->getDiff3LineVector() == nullptr || !isVisible()) 1703 { 1704 d->m_bWordWrap = bWordWrap; 1705 if(!bWordWrap) d->m_diff3WrapLineVector.resize(0); 1706 return; 1707 } 1708 1709 d->m_bWordWrap = bWordWrap; 1710 1711 if(bWordWrap) 1712 { 1713 //TODO: Fix after line number area is converted to a QWidget. 1714 d->m_lineNumberWidth = gOptions->m_bShowLineNumbers ? getLineNumberWidth() : 0; 1715 1716 d->m_diff3WrapLineVector.resize(wrapLineVectorSize); 1717 1718 if(wrapLineVectorSize == 0) 1719 { 1720 d->m_wrapLineCacheList.clear(); 1721 setUpdatesEnabled(false); 1722 for(QtSizeType i = 0, j = 0; i < d->getDiff3LineVector()->size(); i += s_linesPerRunnable, ++j) 1723 { 1724 d->m_wrapLineCacheList.push_back(std::vector<WrapLineCacheData>()); 1725 s_runnables.push_back(new RecalcWordWrapThread(this, visibleTextWidth, j)); 1726 } 1727 } 1728 else 1729 { 1730 recalcWordWrapHelper(wrapLineVectorSize, visibleTextWidth, 0); 1731 setUpdatesEnabled(true); 1732 } 1733 } 1734 else 1735 { 1736 if(wrapLineVectorSize == 0 && d->m_maxTextWidth.loadRelaxed() < 0) 1737 { 1738 d->m_diff3WrapLineVector.resize(0); 1739 d->m_wrapLineCacheList.clear(); 1740 setUpdatesEnabled(false); 1741 for(qint32 i = 0, j = 0; i < d->getDiff3LineVector()->size(); i += s_linesPerRunnable, ++j) 1742 { 1743 s_runnables.push_back(new RecalcWordWrapThread(this, visibleTextWidth, j)); 1744 } 1745 } 1746 else 1747 { 1748 setUpdatesEnabled(true); 1749 } 1750 } 1751 } 1752 1753 void DiffTextWindow::recalcWordWrapHelper(QtSizeType wrapLineVectorSize, qint32 visibleTextWidth, size_t cacheListIdx) 1754 { 1755 if(d->m_bWordWrap) 1756 { 1757 if(ProgressProxy::wasCancelled()) 1758 return; 1759 if(visibleTextWidth < 0) 1760 visibleTextWidth = getVisibleTextAreaWidth(); 1761 else //TODO: Drop after line number area is converted to a QWidget. 1762 visibleTextWidth -= d->leftInfoWidth() * fontMetrics().horizontalAdvance('0'); 1763 LineType i; 1764 QtSizeType wrapLineIdx = 0; 1765 QtSizeType size = d->getDiff3LineVector()->size(); 1766 LineType firstD3LineIdx = SafeInt<LineType>(wrapLineVectorSize > 0 ? 0 : cacheListIdx * s_linesPerRunnable); 1767 LineType endIdx = SafeInt<LineType>(wrapLineVectorSize > 0 ? size : std::min<QtSizeType>(firstD3LineIdx + s_linesPerRunnable, size)); 1768 std::vector<WrapLineCacheData>& wrapLineCache = d->m_wrapLineCacheList[cacheListIdx]; 1769 size_t cacheListIdx2 = 0; 1770 QTextLayout textLayout(QString(), font(), this); 1771 1772 for(i = firstD3LineIdx; i < endIdx; ++i) 1773 { 1774 if(ProgressProxy::wasCancelled()) 1775 return; 1776 1777 LineType linesNeeded = 0; 1778 if(wrapLineVectorSize == 0) 1779 { 1780 QString s = d->getString(i); 1781 textLayout.clearLayout(); 1782 textLayout.setText(s); 1783 d->prepareTextLayout(textLayout, visibleTextWidth); 1784 linesNeeded = textLayout.lineCount(); 1785 for(qint32 l = 0; l < linesNeeded; ++l) 1786 { 1787 QTextLine line = textLayout.lineAt(l); 1788 wrapLineCache.push_back(WrapLineCacheData(i, line.textStart(), line.textLength())); 1789 } 1790 } 1791 else if(wrapLineVectorSize > 0 && cacheListIdx2 < d->m_wrapLineCacheList.size()) 1792 { 1793 WrapLineCacheData* pWrapLineCache = d->m_wrapLineCacheList[cacheListIdx2].data(); 1794 size_t cacheIdx = 0; 1795 size_t clc = d->m_wrapLineCacheList.size() - 1; 1796 size_t cllc = d->m_wrapLineCacheList.back().size(); 1797 size_t curCount = d->m_wrapLineCacheList[cacheListIdx2].size() - 1; 1798 LineType l = 0; 1799 1800 while(wrapLineIdx + l < d->m_diff3WrapLineVector.size() && (cacheListIdx2 < clc || (cacheListIdx2 == clc && cacheIdx < cllc)) && pWrapLineCache->d3LineIdx() <= i) 1801 { 1802 if(pWrapLineCache->d3LineIdx() == i) 1803 { 1804 Diff3WrapLine* pDiff3WrapLine = &d->m_diff3WrapLineVector[wrapLineIdx + l]; 1805 pDiff3WrapLine->wrapLineOffset = pWrapLineCache->textStart(); 1806 pDiff3WrapLine->wrapLineLength = pWrapLineCache->textLength(); 1807 ++l; 1808 } 1809 if(cacheIdx < curCount) 1810 { 1811 ++cacheIdx; 1812 ++pWrapLineCache; 1813 } 1814 else 1815 { 1816 ++cacheListIdx2; 1817 if(cacheListIdx2 >= d->m_wrapLineCacheList.size()) 1818 break; 1819 pWrapLineCache = d->m_wrapLineCacheList[cacheListIdx2].data(); 1820 curCount = d->m_wrapLineCacheList[cacheListIdx2].size(); 1821 cacheIdx = 0; 1822 } 1823 } 1824 linesNeeded = l; 1825 } 1826 1827 Diff3Line& d3l = *(*d->getDiff3LineVector())[i]; 1828 if(d3l.linesNeededForDisplay() < linesNeeded) 1829 { 1830 assert(wrapLineVectorSize == 0); 1831 d3l.setLinesNeeded(linesNeeded); 1832 } 1833 1834 if(wrapLineVectorSize > 0) 1835 { 1836 qint32 j; 1837 for(j = 0; wrapLineIdx < d->m_diff3WrapLineVector.size() && j < d3l.linesNeededForDisplay(); ++j, ++wrapLineIdx) 1838 { 1839 Diff3WrapLine& d3wl = d->m_diff3WrapLineVector[wrapLineIdx]; 1840 d3wl.diff3LineIndex = i; 1841 d3wl.pD3L = (*d->getDiff3LineVector())[i]; 1842 if(j >= linesNeeded) 1843 { 1844 d3wl.wrapLineOffset = 0; 1845 d3wl.wrapLineLength = 0; 1846 } 1847 } 1848 1849 if(wrapLineIdx >= d->m_diff3WrapLineVector.size()) 1850 break; 1851 } 1852 } 1853 1854 if(wrapLineVectorSize > 0) 1855 { 1856 assert(wrapLineVectorSize <= limits<LineType>::max()); //Posiable but unlikely starting in Qt6 1857 d->m_firstLine = (LineType)std::min<SafeInt<LineType>>(d->m_firstLine, wrapLineVectorSize - 1); 1858 d->m_horizScrollOffset = 0; 1859 1860 Q_EMIT firstLineChanged(d->m_firstLine); 1861 } 1862 } 1863 else // no word wrap, just calc the maximum text width 1864 { 1865 if(ProgressProxy::wasCancelled()) 1866 return; 1867 1868 QtSizeType size = d->getDiff3LineVector()->size(); 1869 LineType firstD3LineIdx = SafeInt<LineType>(cacheListIdx * s_linesPerRunnable); 1870 LineType endIdx = std::min(firstD3LineIdx + s_linesPerRunnable, (LineType)size); 1871 1872 qint32 maxTextWidth = d->m_maxTextWidth.loadRelaxed(); // current value 1873 QTextLayout textLayout(QString(), font(), this); 1874 for(LineType i = firstD3LineIdx; i < endIdx; ++i) 1875 { 1876 if(ProgressProxy::wasCancelled()) 1877 return; 1878 textLayout.clearLayout(); 1879 textLayout.setText(d->getString(i)); 1880 d->prepareTextLayout(textLayout); 1881 if(textLayout.maximumWidth() > maxTextWidth) 1882 maxTextWidth = qCeil(textLayout.maximumWidth()); 1883 } 1884 1885 for(qint32 prevMaxTextWidth = d->m_maxTextWidth.fetchAndStoreOrdered(maxTextWidth); 1886 prevMaxTextWidth > maxTextWidth; 1887 prevMaxTextWidth = d->m_maxTextWidth.fetchAndStoreOrdered(maxTextWidth)) 1888 { 1889 maxTextWidth = prevMaxTextWidth; 1890 } 1891 } 1892 1893 if(!d->m_selection.isEmpty() && (!d->m_bWordWrap || wrapLineVectorSize > 0)) 1894 { 1895 // Assume unwrapped coordinates 1896 //( Why? ->Conversion to unwrapped coords happened a few lines above in this method. 1897 // Also see KDiff3App::recalcWordWrap() on the role of wrapLineVectorSize) 1898 1899 // Wrap them now. 1900 1901 // convert the d->m_selection to unwrapped coordinates. 1902 LineRef firstLine; 1903 QtSizeType firstPos; 1904 convertD3LCoordsToLineCoords(d->m_selection.beginLine(), d->m_selection.beginPos(), firstLine, firstPos); 1905 1906 LineRef lastLine; 1907 QtSizeType lastPos; 1908 convertD3LCoordsToLineCoords(d->m_selection.endLine(), d->m_selection.endPos(), lastLine, lastPos); 1909 1910 d->m_selection.start(firstLine, firstPos); 1911 d->m_selection.end(lastLine, lastPos); 1912 } 1913 } 1914 1915 DiffTextWindowFrame::DiffTextWindowFrame(QWidget* pParent, e_SrcSelector winIdx, const QSharedPointer<SourceData>& psd, KDiff3App& app): 1916 QWidget(pParent) 1917 { 1918 m_winIdx = winIdx; 1919 1920 m_pTopLineWidget = new QWidget(this); 1921 m_pFileSelection = new FileNameLineEdit(m_pTopLineWidget); 1922 m_pBrowseButton = new QPushButton("...", m_pTopLineWidget); 1923 m_pBrowseButton->setFixedWidth(30); 1924 1925 m_pFileSelection->setAcceptDrops(true); 1926 1927 m_pLabel = new QLabel("A:", m_pTopLineWidget); 1928 m_pTopLine = new QLabel(m_pTopLineWidget); 1929 1930 mSourceData = psd; 1931 setAutoFillBackground(true); 1932 chk_connect_a(m_pBrowseButton, &QPushButton::clicked, this, &DiffTextWindowFrame::slotBrowseButtonClicked); 1933 chk_connect_a(m_pFileSelection, &QLineEdit::returnPressed, this, &DiffTextWindowFrame::slotReturnPressed); 1934 1935 m_pDiffTextWindow = new DiffTextWindow(this, winIdx, app); 1936 m_pDiffTextWindow->setSourceData(psd); 1937 QVBoxLayout* pVTopLayout = new QVBoxLayout(m_pTopLineWidget); 1938 pVTopLayout->setContentsMargins(2, 2, 2, 2); 1939 pVTopLayout->setSpacing(0); 1940 QHBoxLayout* pHL = new QHBoxLayout(); 1941 QHBoxLayout* pHL2 = new QHBoxLayout(); 1942 pVTopLayout->addLayout(pHL); 1943 pVTopLayout->addLayout(pHL2); 1944 1945 // Upper line: 1946 pHL->setContentsMargins(0, 0, 0, 0); 1947 pHL->setSpacing(2); 1948 1949 pHL->addWidget(m_pLabel, 0); 1950 pHL->addWidget(m_pFileSelection, 1); 1951 pHL->addWidget(m_pBrowseButton, 0); 1952 pHL->addWidget(m_pTopLine, 0); 1953 1954 // Lower line 1955 pHL2->setContentsMargins(0, 0, 0, 0); 1956 pHL2->setSpacing(2); 1957 pHL2->addWidget(m_pTopLine, 0); 1958 m_pEncoding = new EncodingLabel(i18n("Encoding:"), psd); 1959 //EncodeLabel::EncodingChanged should be handled asyncroniously. 1960 chk_connect_q((EncodingLabel*)m_pEncoding, &EncodingLabel::encodingChanged, this, &DiffTextWindowFrame::slotEncodingChanged); 1961 1962 m_pLineEndStyle = new QLabel(i18n("Line end style:")); 1963 pHL2->addWidget(m_pEncoding); 1964 pHL2->addWidget(m_pLineEndStyle); 1965 1966 QVBoxLayout* pVL = new QVBoxLayout(this); 1967 pVL->setContentsMargins(0, 0, 0, 0); 1968 pVL->setSpacing(0); 1969 pVL->addWidget(m_pTopLineWidget, 0); 1970 pVL->addWidget(m_pDiffTextWindow, 1); 1971 1972 m_pDiffTextWindow->installEventFilter(this); 1973 m_pFileSelection->installEventFilter(this); 1974 m_pBrowseButton->installEventFilter(this); 1975 init(); 1976 } 1977 1978 DiffTextWindowFrame::~DiffTextWindowFrame() = default; 1979 1980 void DiffTextWindowFrame::init() 1981 { 1982 QPointer<DiffTextWindow> pDTW = m_pDiffTextWindow; 1983 if(pDTW) 1984 { 1985 QString s = QDir::toNativeSeparators(pDTW->getFileName()); 1986 m_pFileSelection->setText(s); 1987 QString winId = pDTW->getWindowIndex() == e_SrcSelector::A ? (pDTW->isThreeWay() ? i18n("A (Base)") : QStringLiteral("A")) : (pDTW->getWindowIndex() == e_SrcSelector::B ? QStringLiteral("B") : QStringLiteral("C")); 1988 m_pLabel->setText(winId + ':'); 1989 m_pEncoding->setText(i18n("Encoding: %1", pDTW->getEncodingDisplayString())); 1990 m_pLineEndStyle->setText(i18n("Line end style: %1", pDTW->getLineEndStyle() == eLineEndStyleDos ? i18n("DOS") : pDTW->getLineEndStyle() == eLineEndStyleUnix ? i18n("Unix") : 1991 i18n("Unknown"))); 1992 } 1993 } 1994 1995 void DiffTextWindowFrame::setupConnections(const KDiff3App* app) 1996 { 1997 chk_connect_a(this, &DiffTextWindowFrame::fileNameChanged, app, &KDiff3App::slotFileNameChanged); 1998 chk_connect_a(this, &DiffTextWindowFrame::encodingChanged, app, &KDiff3App::slotEncodingChanged); 1999 } 2000 2001 // Search for the first visible line (search loop needed when no line exists for this file.) 2002 LineRef DiffTextWindow::calcTopLineInFile(const LineRef firstLine) const 2003 { 2004 LineRef currentLine; 2005 for(QtSizeType i = convertLineToDiff3LineIdx(firstLine); i < d->getDiff3LineVector()->size(); ++i) 2006 { 2007 const Diff3Line* d3l = (*d->getDiff3LineVector())[i]; 2008 currentLine = d3l->getLineInFile(getWindowIndex()); 2009 if(currentLine.isValid()) break; 2010 } 2011 return currentLine; 2012 } 2013 2014 void DiffTextWindowFrame::setFirstLine(const LineRef firstLine) 2015 { 2016 QPointer<DiffTextWindow> pDTW = m_pDiffTextWindow; 2017 if(pDTW && pDTW->getDiff3LineVector()) 2018 { 2019 QString s = i18n("Top line"); 2020 qint32 lineNumberWidth = pDTW->getLineNumberWidth(); 2021 2022 LineRef topVisiableLine = pDTW->calcTopLineInFile(firstLine); 2023 2024 qint32 w = m_pTopLine->fontMetrics().horizontalAdvance(s + ' ' + QString().fill('0', lineNumberWidth)); 2025 m_pTopLine->setMinimumWidth(w); 2026 2027 if(!topVisiableLine.isValid()) 2028 s = i18n("End"); 2029 else 2030 s += ' ' + QString::number(topVisiableLine + 1); 2031 2032 m_pTopLine->setText(s); 2033 m_pTopLine->repaint(); 2034 } 2035 } 2036 2037 QPointer<DiffTextWindow> DiffTextWindowFrame::getDiffTextWindow() 2038 { 2039 return m_pDiffTextWindow; 2040 } 2041 2042 bool DiffTextWindowFrame::eventFilter([[maybe_unused]] QObject* o, QEvent* e) 2043 { 2044 if(e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) 2045 { 2046 QColor c1 = gOptions->backgroundColor(); 2047 QColor c2; 2048 if(m_winIdx == e_SrcSelector::A) 2049 c2 = gOptions->aColor(); 2050 else if(m_winIdx == e_SrcSelector::B) 2051 c2 = gOptions->bColor(); 2052 else if(m_winIdx == e_SrcSelector::C) 2053 c2 = gOptions->cColor(); 2054 2055 QPalette p = m_pTopLineWidget->palette(); 2056 if(e->type() == QEvent::FocusOut) 2057 std::swap(c1, c2); 2058 2059 p.setColor(QPalette::Window, c2); 2060 setPalette(p); 2061 2062 p.setColor(QPalette::WindowText, c1); 2063 m_pLabel->setPalette(p); 2064 m_pTopLine->setPalette(p); 2065 m_pEncoding->setPalette(p); 2066 m_pLineEndStyle->setPalette(p); 2067 } 2068 2069 return false; 2070 } 2071 2072 void DiffTextWindowFrame::slotReturnPressed() 2073 { 2074 if(m_pDiffTextWindow->getFileName() != m_pFileSelection->text()) 2075 { 2076 Q_EMIT fileNameChanged(m_pFileSelection->text(), m_pDiffTextWindow->getWindowIndex()); 2077 } 2078 } 2079 2080 void DiffTextWindowFrame::slotBrowseButtonClicked() 2081 { 2082 QString current = m_pFileSelection->text(); 2083 2084 QUrl newURL = QFileDialog::getOpenFileUrl(this, i18n("Open File"), QUrl::fromUserInput(current, QString(), QUrl::AssumeLocalFile)); 2085 if(!newURL.isEmpty()) 2086 { 2087 Q_EMIT fileNameChanged(newURL.url(), m_pDiffTextWindow->getWindowIndex()); 2088 } 2089 } 2090 2091 void DiffTextWindowFrame::slotEncodingChanged(const QByteArray& name) 2092 { 2093 Q_EMIT encodingChanged(name); //relay signal from encoding label 2094 mSourceData->setEncoding(name); 2095 } 2096 2097 EncodingLabel::EncodingLabel(const QString& text, const QSharedPointer<SourceData>& pSD): 2098 QLabel(text) 2099 { 2100 m_pSourceData = pSD; 2101 m_pContextEncodingMenu = nullptr; 2102 setMouseTracking(true); 2103 } 2104 2105 void EncodingLabel::mouseMoveEvent(QMouseEvent*) 2106 { 2107 // When there is no data to display or it came from clipboard, 2108 // we will be use UTF-8 only, 2109 // in that case there is no possibility to change the encoding in the SourceData 2110 // so, we should hide the HandCursor and display usual ArrowCursor 2111 if(m_pSourceData->isFromBuffer() || m_pSourceData->isEmpty()) 2112 setCursor(QCursor(Qt::ArrowCursor)); 2113 else 2114 setCursor(QCursor(Qt::PointingHandCursor)); 2115 } 2116 2117 void EncodingLabel::mousePressEvent(QMouseEvent*) 2118 { 2119 if(!(m_pSourceData->isFromBuffer() || m_pSourceData->isEmpty())) 2120 { 2121 delete m_pContextEncodingMenu; 2122 m_pContextEncodingMenu = new QMenu(this); 2123 QMenu* pContextEncodingSubMenu = new QMenu(m_pContextEncodingMenu); 2124 2125 const QByteArray& currentTextCodec = m_pSourceData->getEncoding(); // the codec that will be checked in the context menu 2126 2127 const QList<int> mibs = QTextCodec::availableMibs(); 2128 QList<QByteArray> names; 2129 QList<QByteArray> codecNameList; 2130 for(const qint32 mib: mibs) 2131 { 2132 names.append(QTextCodec::codecForMib(mib)->name()); 2133 } 2134 2135 // Adding "main" encodings 2136 insertCodec(i18n("Unicode, 8 bit"), "UTF-8", codecNameList, m_pContextEncodingMenu, currentTextCodec); 2137 insertCodec(i18n("Unicode, 8 bit (BOM)"), "UTF-8-BOM", codecNameList, m_pContextEncodingMenu, currentTextCodec); 2138 if(QTextCodec::codecForName("System")) 2139 { 2140 insertCodec(QString(), "System", codecNameList, m_pContextEncodingMenu, currentTextCodec); 2141 } 2142 2143 // Adding recent encodings 2144 if(gOptions != nullptr) 2145 { 2146 const RecentItems<maxNofRecentCodecs>& recentEncodings = gOptions->m_recentEncodings; 2147 for(const QString& s: recentEncodings) 2148 { 2149 insertCodec("", s.toLatin1(), codecNameList, m_pContextEncodingMenu, currentTextCodec); 2150 } 2151 } 2152 // Submenu to add the rest of available encodings 2153 pContextEncodingSubMenu->setTitle(i18n("Other")); 2154 for(const QByteArray& name: names) 2155 { 2156 insertCodec("", name, codecNameList, pContextEncodingSubMenu, currentTextCodec); 2157 } 2158 2159 m_pContextEncodingMenu->addMenu(pContextEncodingSubMenu); 2160 2161 m_pContextEncodingMenu->exec(QCursor::pos()); 2162 } 2163 } 2164 2165 void EncodingLabel::insertCodec(const QString& visibleCodecName, const QByteArray& nameArray, QList<QByteArray>& codecList, QMenu* pMenu, const QByteArray& currentTextCodecName) const 2166 { 2167 if(!codecList.contains(nameArray)) 2168 { 2169 QAction* pAction = new QAction(pMenu); // menu takes ownership, so deleting the menu deletes the action too. 2170 const QLatin1String codecName = QLatin1String(nameArray); 2171 2172 pAction->setText(visibleCodecName.isEmpty() ? codecName : visibleCodecName + u8" (" + codecName + u8")"); 2173 pAction->setData(nameArray); 2174 pAction->setCheckable(true); 2175 if(currentTextCodecName == nameArray) 2176 pAction->setChecked(true); 2177 pMenu->addAction(pAction); 2178 chk_connect_a(pAction, &QAction::triggered, this, &EncodingLabel::slotSelectEncoding); 2179 codecList.append(nameArray); 2180 } 2181 } 2182 2183 void EncodingLabel::slotSelectEncoding() 2184 { 2185 QAction* pAction = qobject_cast<QAction*>(sender()); 2186 if(pAction) 2187 { 2188 QByteArray codecName = pAction->data().toByteArray(); 2189 QString s{QLatin1String(codecName)}; 2190 RecentItems<maxNofRecentCodecs>& recentEncodings = gOptions->m_recentEncodings; 2191 if(s != "UTF-8" && s != "System") 2192 { 2193 recentEncodings.push_front(s); 2194 } 2195 2196 Q_EMIT encodingChanged(codecName); 2197 } 2198 }