Warning, file /sdk/kdiff3/src/difftextwindow.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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 }