1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "sidebysidediffeditorwidget.h"
27 #include "selectabletexteditorwidget.h"
28 #include "diffeditorconstants.h"
29 #include "diffeditordocument.h"
30 #include "diffutils.h"
31
32 #include <QMenu>
33 #include <QPainter>
34 #include <QScrollBar>
35 #include <QTextBlock>
36 #include <QVBoxLayout>
37
38 #include <texteditor/textdocument.h>
39 #include <texteditor/textdocumentlayout.h>
40 #include <texteditor/texteditorsettings.h>
41 #include <texteditor/fontsettings.h>
42 #include <texteditor/displaysettings.h>
43
44 #include <coreplugin/icore.h>
45 #include <coreplugin/find/highlightscrollbarcontroller.h>
46 #include <coreplugin/minisplitter.h>
47
48 #include <utils/porting.h>
49 #include <utils/tooltip/tooltip.h>
50
51 using namespace Core;
52 using namespace TextEditor;
53 using namespace Utils;
54
55 namespace DiffEditor {
56 namespace Internal {
57
58 class SideDiffEditorWidget : public SelectableTextEditorWidget
59 {
60 Q_OBJECT
61 public:
62 SideDiffEditorWidget(QWidget *parent = nullptr);
63
64 // block number, file info
fileInfo() const65 QMap<int, DiffFileInfo> fileInfo() const { return m_fileInfo; }
66
67 void setLineNumber(int blockNumber, int lineNumber);
68 void setFileInfo(int blockNumber, const DiffFileInfo &fileInfo);
setSkippedLines(int blockNumber,int skippedLines,const QString & contextInfo=QString ())69 void setSkippedLines(int blockNumber, int skippedLines, const QString &contextInfo = QString()) {
70 m_skippedLines[blockNumber] = qMakePair(skippedLines, contextInfo);
71 setSeparator(blockNumber, true);
72 }
73 void setChunkIndex(int startBlockNumber, int blockCount, int chunkIndex);
setSeparator(int blockNumber,bool separator)74 void setSeparator(int blockNumber, bool separator) {
75 m_separators[blockNumber] = separator;
76 }
isFileLine(int blockNumber) const77 bool isFileLine(int blockNumber) const {
78 return m_fileInfo.contains(blockNumber);
79 }
80 int blockNumberForFileIndex(int fileIndex) const;
81 int fileIndexForBlockNumber(int blockNumber) const;
82 int chunkIndexForBlockNumber(int blockNumber) const;
83 int chunkRowForBlockNumber(int blockNumber) const;
84 int chunkRowsCountForBlockNumber(int blockNumber) const;
isChunkLine(int blockNumber) const85 bool isChunkLine(int blockNumber) const {
86 return m_skippedLines.contains(blockNumber);
87 }
88 void clearAll(const QString &message);
89 void clearAllData();
90 void saveState();
91 using TextEditor::TextEditorWidget::restoreState;
92 void restoreState();
93
94 void setFolded(int blockNumber, bool folded);
95
96 void setDisplaySettings(const DisplaySettings &ds) override;
97
98 signals:
99 void jumpToOriginalFileRequested(int diffFileIndex,
100 int lineNumber,
101 int columnNumber);
102 void contextMenuRequested(QMenu *menu,
103 int diffFileIndex,
104 int chunkIndex,
105 const ChunkSelection &selection);
106 void foldChanged(int blockNumber, bool folded);
107 void gotDisplaySettings();
108 void gotFocus();
109
110 protected:
extraAreaWidth(int * markWidthPtr=nullptr) const111 int extraAreaWidth(int *markWidthPtr = nullptr) const override {
112 return SelectableTextEditorWidget::extraAreaWidth(markWidthPtr);
113 }
114 void applyFontSettings() override;
115
116 QString lineNumber(int blockNumber) const override;
117 int lineNumberDigits() const override;
118 bool selectionVisible(int blockNumber) const override;
119 bool replacementVisible(int blockNumber) const override;
120 QColor replacementPenColor(int blockNumber) const override;
121 QString plainTextFromSelection(const QTextCursor &cursor) const override;
122 void mouseDoubleClickEvent(QMouseEvent *e) override;
123 void keyPressEvent(QKeyEvent *e) override;
124 void contextMenuEvent(QContextMenuEvent *e) override;
125 void paintEvent(QPaintEvent *e) override;
126 void scrollContentsBy(int dx, int dy) override;
127 void customDrawCollapsedBlockPopup(QPainter &painter,
128 const QTextBlock &block,
129 QPointF offset,
130 const QRect &clip);
131 void drawCollapsedBlockPopup(QPainter &painter,
132 const QTextBlock &block,
133 QPointF offset,
134 const QRect &clip) override;
135 void focusInEvent(QFocusEvent *e) override;
136
137 private:
138 void paintSeparator(QPainter &painter, QColor &color, const QString &text,
139 const QTextBlock &block, int top);
140 void jumpToOriginalFile(const QTextCursor &cursor);
141
142 // block number, visual line number.
143 QMap<int, int> m_lineNumbers;
144 // block number, fileInfo. Set for file lines only.
145 QMap<int, DiffFileInfo> m_fileInfo;
146 // block number, skipped lines and context info. Set for chunk lines only.
147 QMap<int, QPair<int, QString> > m_skippedLines;
148 // start block number, block count of a chunk, chunk index inside a file.
149 QMap<int, QPair<int, int> > m_chunkInfo;
150 // block number, separator. Set for file, chunk or span line.
151 QMap<int, bool> m_separators;
152 QColor m_fileLineForeground;
153 QColor m_chunkLineForeground;
154 QColor m_textForeground;
155 QByteArray m_state;
156
157 QTextBlock m_drawCollapsedBlock;
158 QPointF m_drawCollapsedOffset;
159 QRect m_drawCollapsedClip;
160 int m_lineNumberDigits = 1;
161 };
162
SideDiffEditorWidget(QWidget * parent)163 SideDiffEditorWidget::SideDiffEditorWidget(QWidget *parent)
164 : SelectableTextEditorWidget("DiffEditor.SideDiffEditor", parent)
165 {
166 DisplaySettings settings = displaySettings();
167 settings.m_textWrapping = false;
168 settings.m_displayLineNumbers = true;
169 settings.m_markTextChanges = false;
170 settings.m_highlightBlocks = false;
171 SelectableTextEditorWidget::setDisplaySettings(settings);
172
173 connect(this, &TextEditorWidget::tooltipRequested, this, [this](const QPoint &point, int position) {
174 const int block = document()->findBlock(position).blockNumber();
175 const auto it = m_fileInfo.constFind(block);
176 if (it != m_fileInfo.constEnd())
177 ToolTip::show(point, it.value().fileName, this);
178 else
179 ToolTip::hide();
180 });
181
182 auto documentLayout = qobject_cast<TextDocumentLayout*>(document()->documentLayout());
183 if (documentLayout)
184 connect(documentLayout, &TextDocumentLayout::foldChanged,
185 this, &SideDiffEditorWidget::foldChanged);
186 setCodeFoldingSupported(true);
187 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
188 }
189
saveState()190 void SideDiffEditorWidget::saveState()
191 {
192 if (!m_state.isNull())
193 return;
194
195 m_state = SelectableTextEditorWidget::saveState();
196 }
197
restoreState()198 void SideDiffEditorWidget::restoreState()
199 {
200 if (m_state.isNull())
201 return;
202
203 SelectableTextEditorWidget::restoreState(m_state);
204 m_state.clear();
205 }
206
setFolded(int blockNumber,bool folded)207 void SideDiffEditorWidget::setFolded(int blockNumber, bool folded)
208 {
209 QTextBlock block = document()->findBlockByNumber(blockNumber);
210 if (!block.isValid())
211 return;
212
213 if (TextDocumentLayout::isFolded(block) == folded)
214 return;
215
216 TextDocumentLayout::doFoldOrUnfold(block, !folded);
217
218 auto documentLayout = qobject_cast<TextDocumentLayout*>(document()->documentLayout());
219 documentLayout->requestUpdate();
220 documentLayout->emitDocumentSizeChanged();
221 }
222
setDisplaySettings(const DisplaySettings & ds)223 void SideDiffEditorWidget::setDisplaySettings(const DisplaySettings &ds)
224 {
225 DisplaySettings settings = displaySettings();
226 settings.m_visualizeWhitespace = ds.m_visualizeWhitespace;
227 settings.m_displayFoldingMarkers = ds.m_displayFoldingMarkers;
228 settings.m_scrollBarHighlights = ds.m_scrollBarHighlights;
229 settings.m_highlightCurrentLine = ds.m_highlightCurrentLine;
230 SelectableTextEditorWidget::setDisplaySettings(settings);
231 emit gotDisplaySettings();
232 }
233
applyFontSettings()234 void SideDiffEditorWidget::applyFontSettings()
235 {
236 SelectableTextEditorWidget::applyFontSettings();
237 const FontSettings &fs = textDocument()->fontSettings();
238 m_fileLineForeground = fs.formatFor(C_DIFF_FILE_LINE).foreground();
239 m_chunkLineForeground = fs.formatFor(C_DIFF_CONTEXT_LINE).foreground();
240 m_textForeground = fs.toTextCharFormat(C_TEXT).foreground().color();
241 update();
242 }
243
lineNumber(int blockNumber) const244 QString SideDiffEditorWidget::lineNumber(int blockNumber) const
245 {
246 const auto it = m_lineNumbers.constFind(blockNumber);
247 if (it != m_lineNumbers.constEnd())
248 return QString::number(it.value());
249 return QString();
250 }
251
lineNumberDigits() const252 int SideDiffEditorWidget::lineNumberDigits() const
253 {
254 return m_lineNumberDigits;
255 }
256
selectionVisible(int blockNumber) const257 bool SideDiffEditorWidget::selectionVisible(int blockNumber) const
258 {
259 return !m_separators.value(blockNumber, false);
260 }
261
replacementVisible(int blockNumber) const262 bool SideDiffEditorWidget::replacementVisible(int blockNumber) const
263 {
264 return isChunkLine(blockNumber) || (isFileLine(blockNumber)
265 && TextDocumentLayout::isFolded(document()->findBlockByNumber(blockNumber)));
266 }
267
replacementPenColor(int blockNumber) const268 QColor SideDiffEditorWidget::replacementPenColor(int blockNumber) const
269 {
270 Q_UNUSED(blockNumber)
271 return m_chunkLineForeground;
272 }
273
plainTextFromSelection(const QTextCursor & cursor) const274 QString SideDiffEditorWidget::plainTextFromSelection(const QTextCursor &cursor) const
275 {
276 const int startPosition = cursor.selectionStart();
277 const int endPosition = cursor.selectionEnd();
278 if (startPosition == endPosition)
279 return QString(); // no selection
280
281 const QTextBlock startBlock = document()->findBlock(startPosition);
282 const QTextBlock endBlock = document()->findBlock(endPosition);
283 QTextBlock block = startBlock;
284 QString text;
285 bool textInserted = false;
286 while (block.isValid() && block.blockNumber() <= endBlock.blockNumber()) {
287 if (selectionVisible(block.blockNumber())) {
288 if (block == startBlock) {
289 if (block == endBlock)
290 text = cursor.selectedText(); // just one line text
291 else
292 text = block.text().mid(startPosition - block.position());
293 } else {
294 if (textInserted)
295 text += '\n';
296 if (block == endBlock)
297 text += make_stringview(block.text()).left(endPosition - block.position());
298 else
299 text += block.text();
300 }
301 textInserted = true;
302 }
303 block = block.next();
304 }
305
306 return convertToPlainText(text);
307 }
308
setLineNumber(int blockNumber,int lineNumber)309 void SideDiffEditorWidget::setLineNumber(int blockNumber, int lineNumber)
310 {
311 const QString lineNumberString = QString::number(lineNumber);
312 m_lineNumbers.insert(blockNumber, lineNumber);
313 m_lineNumberDigits = qMax(m_lineNumberDigits, lineNumberString.count());
314 }
315
setFileInfo(int blockNumber,const DiffFileInfo & fileInfo)316 void SideDiffEditorWidget::setFileInfo(int blockNumber, const DiffFileInfo &fileInfo)
317 {
318 m_fileInfo[blockNumber] = fileInfo;
319 setSeparator(blockNumber, true);
320 }
321
setChunkIndex(int startBlockNumber,int blockCount,int chunkIndex)322 void SideDiffEditorWidget::setChunkIndex(int startBlockNumber, int blockCount, int chunkIndex)
323 {
324 m_chunkInfo.insert(startBlockNumber, qMakePair(blockCount, chunkIndex));
325 }
326
blockNumberForFileIndex(int fileIndex) const327 int SideDiffEditorWidget::blockNumberForFileIndex(int fileIndex) const
328 {
329 if (fileIndex < 0 || fileIndex >= m_fileInfo.count())
330 return -1;
331
332 return std::next(m_fileInfo.constBegin(), fileIndex).key();
333 }
334
fileIndexForBlockNumber(int blockNumber) const335 int SideDiffEditorWidget::fileIndexForBlockNumber(int blockNumber) const
336 {
337 int i = -1;
338 for (auto it = m_fileInfo.cbegin(), end = m_fileInfo.cend(); it != end; ++it, ++i) {
339 if (it.key() > blockNumber)
340 break;
341 }
342
343 return i;
344 }
345
chunkIndexForBlockNumber(int blockNumber) const346 int SideDiffEditorWidget::chunkIndexForBlockNumber(int blockNumber) const
347 {
348 if (m_chunkInfo.isEmpty())
349 return -1;
350
351 auto it = m_chunkInfo.upperBound(blockNumber);
352 if (it == m_chunkInfo.constBegin())
353 return -1;
354
355 --it;
356
357 if (blockNumber < it.key() + it.value().first)
358 return it.value().second;
359
360 return -1;
361 }
362
chunkRowForBlockNumber(int blockNumber) const363 int SideDiffEditorWidget::chunkRowForBlockNumber(int blockNumber) const
364 {
365 if (m_chunkInfo.isEmpty())
366 return -1;
367
368 auto it = m_chunkInfo.upperBound(blockNumber);
369 if (it == m_chunkInfo.constBegin())
370 return -1;
371
372 --it;
373
374 if (blockNumber < it.key() + it.value().first)
375 return blockNumber - it.key();
376
377 return -1;
378 }
379
chunkRowsCountForBlockNumber(int blockNumber) const380 int SideDiffEditorWidget::chunkRowsCountForBlockNumber(int blockNumber) const
381 {
382 if (m_chunkInfo.isEmpty())
383 return -1;
384
385 auto it = m_chunkInfo.upperBound(blockNumber);
386 if (it == m_chunkInfo.constBegin())
387 return -1;
388
389 --it;
390
391 if (blockNumber < it.key() + it.value().first)
392 return it.value().first;
393
394 return -1;
395 }
396
clearAll(const QString & message)397 void SideDiffEditorWidget::clearAll(const QString &message)
398 {
399 setBlockSelection(false);
400 clear();
401 clearAllData();
402 setExtraSelections(TextEditorWidget::OtherSelection,
403 QList<QTextEdit::ExtraSelection>());
404 setPlainText(message);
405 }
406
clearAllData()407 void SideDiffEditorWidget::clearAllData()
408 {
409 m_lineNumberDigits = 1;
410 m_lineNumbers.clear();
411 m_fileInfo.clear();
412 m_skippedLines.clear();
413 m_chunkInfo.clear();
414 m_separators.clear();
415 setSelections(QMap<int, QList<DiffSelection> >());
416 }
417
scrollContentsBy(int dx,int dy)418 void SideDiffEditorWidget::scrollContentsBy(int dx, int dy)
419 {
420 SelectableTextEditorWidget::scrollContentsBy(dx, dy);
421 // TODO: update only chunk lines
422 viewport()->update();
423 }
424
paintSeparator(QPainter & painter,QColor & color,const QString & text,const QTextBlock & block,int top)425 void SideDiffEditorWidget::paintSeparator(QPainter &painter,
426 QColor &color,
427 const QString &text,
428 const QTextBlock &block,
429 int top)
430 {
431 QPointF offset = contentOffset();
432 painter.save();
433
434 QColor foreground = color;
435 if (!foreground.isValid())
436 foreground = m_textForeground;
437 if (!foreground.isValid())
438 foreground = palette().windowText().color();
439
440 painter.setPen(foreground);
441
442 const QString replacementText = " {" + foldReplacementText(block) + "}; ";
443 const int replacementTextWidth = fontMetrics().horizontalAdvance(replacementText) + 24;
444 int x = replacementTextWidth + int(offset.x());
445 if (x < document()->documentMargin()
446 || !TextDocumentLayout::isFolded(block)) {
447 x = int(document()->documentMargin());
448 }
449 const QString elidedText = fontMetrics().elidedText(text,
450 Qt::ElideRight,
451 viewport()->width() - x);
452 QTextLayout *layout = block.layout();
453 QTextLine textLine = layout->lineAt(0);
454 QRectF lineRect = textLine.naturalTextRect().translated(offset.x(), top);
455 QRect clipRect = contentsRect();
456 clipRect.setLeft(x);
457 painter.setClipRect(clipRect);
458 painter.drawText(QPointF(x, lineRect.top() + textLine.ascent()),
459 elidedText);
460 painter.restore();
461 }
462
mouseDoubleClickEvent(QMouseEvent * e)463 void SideDiffEditorWidget::mouseDoubleClickEvent(QMouseEvent *e)
464 {
465 if (e->button() == Qt::LeftButton && !(e->modifiers() & Qt::ShiftModifier)) {
466 QTextCursor cursor = cursorForPosition(e->pos());
467 jumpToOriginalFile(cursor);
468 e->accept();
469 return;
470 }
471 SelectableTextEditorWidget::mouseDoubleClickEvent(e);
472 }
473
keyPressEvent(QKeyEvent * e)474 void SideDiffEditorWidget::keyPressEvent(QKeyEvent *e)
475 {
476 if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
477 jumpToOriginalFile(textCursor());
478 e->accept();
479 return;
480 }
481 SelectableTextEditorWidget::keyPressEvent(e);
482 }
483
contextMenuEvent(QContextMenuEvent * e)484 void SideDiffEditorWidget::contextMenuEvent(QContextMenuEvent *e)
485 {
486 QPointer<QMenu> menu = createStandardContextMenu();
487
488 const QTextCursor tc = textCursor();
489 QTextCursor start = tc;
490 start.setPosition(tc.selectionStart());
491 QTextCursor end = tc;
492 end.setPosition(tc.selectionEnd());
493 const int startBlockNumber = start.blockNumber();
494 const int endBlockNumber = end.blockNumber();
495
496 QTextCursor cursor = cursorForPosition(e->pos());
497 const int blockNumber = cursor.blockNumber();
498
499 const int fileIndex = fileIndexForBlockNumber(blockNumber);
500 const int chunkIndex = chunkIndexForBlockNumber(blockNumber);
501
502 const int selectionStartFileIndex = fileIndexForBlockNumber(startBlockNumber);
503 const int selectionStartChunkIndex = chunkIndexForBlockNumber(startBlockNumber);
504 const int selectionEndFileIndex = fileIndexForBlockNumber(endBlockNumber);
505 const int selectionEndChunkIndex = chunkIndexForBlockNumber(endBlockNumber);
506
507 const int selectionStart = selectionStartFileIndex == fileIndex
508 && selectionStartChunkIndex == chunkIndex
509 ? chunkRowForBlockNumber(startBlockNumber)
510 : 0;
511
512 const int selectionEnd = selectionEndFileIndex == fileIndex
513 && selectionEndChunkIndex == chunkIndex
514 ? chunkRowForBlockNumber(endBlockNumber)
515 : chunkRowsCountForBlockNumber(blockNumber);
516
517 QList<int> rows;
518 for (int i = selectionStart; i <= selectionEnd; ++i)
519 rows.append(i);
520
521 const ChunkSelection selection(rows, rows);
522
523 emit contextMenuRequested(menu, fileIndexForBlockNumber(blockNumber),
524 chunkIndexForBlockNumber(blockNumber),
525 selection);
526
527 connect(this, &SideDiffEditorWidget::destroyed, menu.data(), &QMenu::deleteLater);
528 menu->exec(e->globalPos());
529 delete menu;
530 }
531
jumpToOriginalFile(const QTextCursor & cursor)532 void SideDiffEditorWidget::jumpToOriginalFile(const QTextCursor &cursor)
533 {
534 if (m_fileInfo.isEmpty())
535 return;
536
537 const int blockNumber = cursor.blockNumber();
538 if (!m_lineNumbers.contains(blockNumber))
539 return;
540
541 const int lineNumber = m_lineNumbers.value(blockNumber);
542 const int columnNumber = cursor.positionInBlock();
543
544 emit jumpToOriginalFileRequested(fileIndexForBlockNumber(blockNumber),
545 lineNumber, columnNumber);
546 }
547
skippedText(int skippedNumber)548 static QString skippedText(int skippedNumber)
549 {
550 if (skippedNumber > 0)
551 return SideBySideDiffEditorWidget::tr("Skipped %n lines...", nullptr, skippedNumber);
552 if (skippedNumber == -2)
553 return SideBySideDiffEditorWidget::tr("Binary files differ");
554 return SideBySideDiffEditorWidget::tr("Skipped unknown number of lines...");
555 }
556
paintEvent(QPaintEvent * e)557 void SideDiffEditorWidget::paintEvent(QPaintEvent *e)
558 {
559 SelectableTextEditorWidget::paintEvent(e);
560
561 QPainter painter(viewport());
562 QPointF offset = contentOffset();
563 QTextBlock currentBlock = firstVisibleBlock();
564
565 while (currentBlock.isValid()) {
566 if (currentBlock.isVisible()) {
567 qreal top = blockBoundingGeometry(currentBlock).translated(offset).top();
568 qreal bottom = top + blockBoundingRect(currentBlock).height();
569
570 if (top > e->rect().bottom())
571 break;
572
573 if (bottom >= e->rect().top()) {
574 const int blockNumber = currentBlock.blockNumber();
575
576 auto it = m_skippedLines.constFind(blockNumber);
577 if (it != m_skippedLines.constEnd()) {
578 QString skippedRowsText = '[' + skippedText(it->first) + ']';
579 if (!it->second.isEmpty())
580 skippedRowsText += ' ' + it->second;
581 paintSeparator(painter, m_chunkLineForeground,
582 skippedRowsText, currentBlock, top);
583 }
584
585 const DiffFileInfo fileInfo = m_fileInfo.value(blockNumber);
586 if (!fileInfo.fileName.isEmpty()) {
587 const QString fileNameText = fileInfo.typeInfo.isEmpty()
588 ? fileInfo.fileName
589 : tr("[%1] %2").arg(fileInfo.typeInfo)
590 .arg(fileInfo.fileName);
591 paintSeparator(painter, m_fileLineForeground,
592 fileNameText, currentBlock, top);
593 }
594 }
595 }
596 currentBlock = currentBlock.next();
597 }
598
599 if (m_drawCollapsedBlock.isValid()) {
600 // draw it now
601 customDrawCollapsedBlockPopup(painter,
602 m_drawCollapsedBlock,
603 m_drawCollapsedOffset,
604 m_drawCollapsedClip);
605 // reset the data for the drawing
606 m_drawCollapsedBlock = QTextBlock();
607 }
608 }
609
customDrawCollapsedBlockPopup(QPainter & painter,const QTextBlock & block,QPointF offset,const QRect & clip)610 void SideDiffEditorWidget::customDrawCollapsedBlockPopup(QPainter &painter,
611 const QTextBlock &block,
612 QPointF offset,
613 const QRect &clip)
614 {
615 int margin = block.document()->documentMargin();
616 qreal maxWidth = 0;
617 qreal blockHeight = 0;
618 QTextBlock b = block;
619
620 while (!b.isVisible()) {
621 const int blockNumber = b.blockNumber();
622 if (!m_skippedLines.contains(blockNumber) && !m_separators.contains(blockNumber)) {
623 b.setVisible(true); // make sure block bounding rect works
624 QRectF r = blockBoundingRect(b).translated(offset);
625
626 QTextLayout *layout = b.layout();
627 for (int i = layout->lineCount()-1; i >= 0; --i)
628 maxWidth = qMax(maxWidth, layout->lineAt(i).naturalTextWidth() + 2*margin);
629
630 blockHeight += r.height();
631
632 b.setVisible(false); // restore previous state
633 b.setLineCount(0); // restore 0 line count for invisible block
634 }
635 b = b.next();
636 }
637
638 painter.save();
639 painter.setRenderHint(QPainter::Antialiasing, true);
640 painter.translate(.5, .5);
641 QBrush brush = palette().base();
642 const QTextCharFormat &ifdefedOutFormat
643 = textDocument()->fontSettings().toTextCharFormat(C_DISABLED_CODE);
644 if (ifdefedOutFormat.hasProperty(QTextFormat::BackgroundBrush))
645 brush = ifdefedOutFormat.background();
646 painter.setBrush(brush);
647 painter.drawRoundedRect(QRectF(offset.x(),
648 offset.y(),
649 maxWidth, blockHeight).adjusted(0, 0, 0, 0), 3, 3);
650 painter.restore();
651
652 QTextBlock end = b;
653 b = block;
654 while (b != end) {
655 const int blockNumber = b.blockNumber();
656 if (!m_skippedLines.contains(blockNumber) && !m_separators.contains(blockNumber)) {
657 b.setVisible(true); // make sure block bounding rect works
658 QRectF r = blockBoundingRect(b).translated(offset);
659 QTextLayout *layout = b.layout();
660 QVector<QTextLayout::FormatRange> selections;
661 layout->draw(&painter, offset, selections, clip);
662
663 b.setVisible(false); // restore previous state
664 b.setLineCount(0); // restore 0 line count for invisible block
665 offset.ry() += r.height();
666 }
667 b = b.next();
668 }
669 }
670
drawCollapsedBlockPopup(QPainter & painter,const QTextBlock & block,QPointF offset,const QRect & clip)671 void SideDiffEditorWidget::drawCollapsedBlockPopup(QPainter &painter,
672 const QTextBlock &block,
673 QPointF offset,
674 const QRect &clip)
675 {
676 Q_UNUSED(painter)
677 // Called from inside SelectableTextEditorWidget::paintEvent().
678 // Postpone the drawing for now, do it after our paintEvent's
679 // custom painting. Store the data for the future redraw.
680 m_drawCollapsedBlock = block;
681 m_drawCollapsedOffset = offset;
682 m_drawCollapsedClip = clip;
683 }
684
focusInEvent(QFocusEvent * e)685 void SideDiffEditorWidget::focusInEvent(QFocusEvent *e)
686 {
687 SelectableTextEditorWidget::focusInEvent(e);
688 emit gotFocus();
689 }
690
691 //////////////////
692
SideBySideDiffEditorWidget(QWidget * parent)693 SideBySideDiffEditorWidget::SideBySideDiffEditorWidget(QWidget *parent)
694 : QWidget(parent)
695 , m_controller(this)
696 {
697 m_leftEditor = new SideDiffEditorWidget(this);
698 m_leftEditor->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
699 m_leftEditor->setReadOnly(true);
700 m_leftEditor->setCodeStyle(TextEditorSettings::codeStyle());
701 connect(m_leftEditor, &SideDiffEditorWidget::jumpToOriginalFileRequested,
702 this, &SideBySideDiffEditorWidget::slotLeftJumpToOriginalFileRequested);
703 connect(m_leftEditor, &SideDiffEditorWidget::contextMenuRequested,
704 this, &SideBySideDiffEditorWidget::slotLeftContextMenuRequested,
705 Qt::DirectConnection);
706
707 m_rightEditor = new SideDiffEditorWidget(this);
708 m_rightEditor->setReadOnly(true);
709 m_rightEditor->setCodeStyle(TextEditorSettings::codeStyle());
710 connect(m_rightEditor, &SideDiffEditorWidget::jumpToOriginalFileRequested,
711 this, &SideBySideDiffEditorWidget::slotRightJumpToOriginalFileRequested);
712 connect(m_rightEditor, &SideDiffEditorWidget::contextMenuRequested,
713 this, &SideBySideDiffEditorWidget::slotRightContextMenuRequested,
714 Qt::DirectConnection);
715
716 auto setupHighlightController = [this]() {
717 HighlightScrollBarController *highlightController = m_leftEditor->highlightScrollBarController();
718 if (highlightController)
719 highlightController->setScrollArea(m_rightEditor);
720 };
721
722 setupHighlightController();
723 connect(m_leftEditor, &SideDiffEditorWidget::gotDisplaySettings, this, setupHighlightController);
724
725 m_rightEditor->verticalScrollBar()->setFocusProxy(m_leftEditor);
726 connect(m_leftEditor, &SideDiffEditorWidget::gotFocus, this, [this]() {
727 if (m_rightEditor->verticalScrollBar()->focusProxy() == m_leftEditor)
728 return; // We already did it before.
729
730 // Hack #1. If the left editor got a focus last time
731 // we don't want to focus right editor when clicking the right
732 // scrollbar.
733 m_rightEditor->verticalScrollBar()->setFocusProxy(m_leftEditor);
734
735 // Hack #2. If the focus is currently not on the scrollbar's proxy
736 // and we click on the scrollbar, the focus will go to the parent
737 // of the scrollbar. In order to give the focus to the proxy
738 // we need to set a click focus policy on the scrollbar.
739 // See QApplicationPrivate::giveFocusAccordingToFocusPolicy().
740 m_rightEditor->verticalScrollBar()->setFocusPolicy(Qt::ClickFocus);
741
742 // Hack #3. Setting the focus policy is not orthogonal to setting
743 // the focus proxy and unfortuantely it changes the policy of the proxy
744 // too. We bring back the original policy to keep tab focus working.
745 m_leftEditor->setFocusPolicy(Qt::StrongFocus);
746 });
747 connect(m_rightEditor, &SideDiffEditorWidget::gotFocus, this, [this]() {
748 // Unhack #1.
749 m_rightEditor->verticalScrollBar()->setFocusProxy(nullptr);
750 // Unhack #2.
751 m_rightEditor->verticalScrollBar()->setFocusPolicy(Qt::NoFocus);
752 });
753
754 connect(TextEditorSettings::instance(),
755 &TextEditorSettings::fontSettingsChanged,
756 this, &SideBySideDiffEditorWidget::setFontSettings);
757 setFontSettings(TextEditorSettings::fontSettings());
758
759 connect(m_leftEditor->verticalScrollBar(), &QAbstractSlider::valueChanged,
760 this, &SideBySideDiffEditorWidget::leftVSliderChanged);
761 connect(m_leftEditor->verticalScrollBar(), &QAbstractSlider::actionTriggered,
762 this, &SideBySideDiffEditorWidget::leftVSliderChanged);
763
764 connect(m_leftEditor->horizontalScrollBar(), &QAbstractSlider::valueChanged,
765 this, &SideBySideDiffEditorWidget::leftHSliderChanged);
766 connect(m_leftEditor->horizontalScrollBar(), &QAbstractSlider::actionTriggered,
767 this, &SideBySideDiffEditorWidget::leftHSliderChanged);
768
769 connect(m_leftEditor, &QPlainTextEdit::cursorPositionChanged,
770 this, &SideBySideDiffEditorWidget::leftCursorPositionChanged);
771
772 connect(m_rightEditor->verticalScrollBar(), &QAbstractSlider::valueChanged,
773 this, &SideBySideDiffEditorWidget::rightVSliderChanged);
774 connect(m_rightEditor->verticalScrollBar(), &QAbstractSlider::actionTriggered,
775 this, &SideBySideDiffEditorWidget::rightVSliderChanged);
776
777 connect(m_rightEditor->horizontalScrollBar(), &QAbstractSlider::valueChanged,
778 this, &SideBySideDiffEditorWidget::rightHSliderChanged);
779 connect(m_rightEditor->horizontalScrollBar(), &QAbstractSlider::actionTriggered,
780 this, &SideBySideDiffEditorWidget::rightHSliderChanged);
781
782 connect(m_rightEditor, &QPlainTextEdit::cursorPositionChanged,
783 this, &SideBySideDiffEditorWidget::rightCursorPositionChanged);
784
785 connect(m_leftEditor, &SideDiffEditorWidget::foldChanged,
786 m_rightEditor, &SideDiffEditorWidget::setFolded);
787 connect(m_rightEditor, &SideDiffEditorWidget::foldChanged,
788 m_leftEditor, &SideDiffEditorWidget::setFolded);
789
790 connect(m_leftEditor->horizontalScrollBar(), &QAbstractSlider::rangeChanged,
791 this, &SideBySideDiffEditorWidget::syncHorizontalScrollBarPolicy);
792
793 connect(m_rightEditor->horizontalScrollBar(), &QAbstractSlider::rangeChanged,
794 this, &SideBySideDiffEditorWidget::syncHorizontalScrollBarPolicy);
795
796 syncHorizontalScrollBarPolicy();
797
798 m_splitter = new MiniSplitter(this);
799 m_splitter->addWidget(m_leftEditor);
800 m_splitter->addWidget(m_rightEditor);
801 QVBoxLayout *l = new QVBoxLayout(this);
802 l->setContentsMargins(0, 0, 0, 0);
803 l->addWidget(m_splitter);
804 setFocusProxy(m_leftEditor);
805
806 auto leftContext = new IContext(this);
807 leftContext->setWidget(m_leftEditor);
808 leftContext->setContext(Core::Context(Utils::Id(Constants::SIDE_BY_SIDE_VIEW_ID).withSuffix(1)));
809 Core::ICore::addContextObject(leftContext);
810 auto rightContext = new IContext(this);
811 rightContext->setWidget(m_rightEditor);
812 rightContext->setContext(Core::Context(Utils::Id(Constants::SIDE_BY_SIDE_VIEW_ID).withSuffix(2)));
813 Core::ICore::addContextObject(rightContext);
814 }
815
leftEditorWidget() const816 TextEditorWidget *SideBySideDiffEditorWidget::leftEditorWidget() const
817 {
818 return m_leftEditor;
819 }
820
rightEditorWidget() const821 TextEditorWidget *SideBySideDiffEditorWidget::rightEditorWidget() const
822 {
823 return m_rightEditor;
824 }
825
setDocument(DiffEditorDocument * document)826 void SideBySideDiffEditorWidget::setDocument(DiffEditorDocument *document)
827 {
828 m_controller.setDocument(document);
829 clear();
830 QList<FileData> diffFileList;
831 QString workingDirectory;
832 if (document) {
833 diffFileList = document->diffFiles();
834 workingDirectory = document->baseDirectory();
835 }
836 setDiff(diffFileList, workingDirectory);
837 }
838
diffDocument() const839 DiffEditorDocument *SideBySideDiffEditorWidget::diffDocument() const
840 {
841 return m_controller.document();
842 }
843
clear(const QString & message)844 void SideBySideDiffEditorWidget::clear(const QString &message)
845 {
846 const bool oldIgnore = m_controller.m_ignoreCurrentIndexChange;
847 m_controller.m_ignoreCurrentIndexChange = true;
848 setDiff(QList<FileData>(), QString());
849 m_leftEditor->clearAll(message);
850 m_rightEditor->clearAll(message);
851 m_controller.m_ignoreCurrentIndexChange = oldIgnore;
852 }
853
setDiff(const QList<FileData> & diffFileList,const QString & workingDirectory)854 void SideBySideDiffEditorWidget::setDiff(const QList<FileData> &diffFileList,
855 const QString &workingDirectory)
856 {
857 Q_UNUSED(workingDirectory)
858
859 const bool oldIgnore = m_controller.m_ignoreCurrentIndexChange;
860 m_controller.m_ignoreCurrentIndexChange = true;
861 m_leftEditor->clear();
862 m_rightEditor->clear();
863
864 m_controller.m_contextFileData = diffFileList;
865 if (m_controller.m_contextFileData.isEmpty()) {
866 const QString msg = tr("No difference.");
867 m_leftEditor->setPlainText(msg);
868 m_rightEditor->setPlainText(msg);
869 } else {
870 showDiff();
871 }
872 m_controller.m_ignoreCurrentIndexChange = oldIgnore;
873 }
874
setCurrentDiffFileIndex(int diffFileIndex)875 void SideBySideDiffEditorWidget::setCurrentDiffFileIndex(int diffFileIndex)
876 {
877 if (m_controller.m_ignoreCurrentIndexChange)
878 return;
879
880 const int blockNumber = m_leftEditor->blockNumberForFileIndex(diffFileIndex);
881
882 const bool oldIgnore = m_controller.m_ignoreCurrentIndexChange;
883 m_controller.m_ignoreCurrentIndexChange = true;
884 QTextBlock leftBlock = m_leftEditor->document()->findBlockByNumber(blockNumber);
885 QTextCursor leftCursor = m_leftEditor->textCursor();
886 leftCursor.setPosition(leftBlock.position());
887 m_leftEditor->setTextCursor(leftCursor);
888 m_leftEditor->verticalScrollBar()->setValue(blockNumber);
889
890 QTextBlock rightBlock = m_rightEditor->document()->findBlockByNumber(blockNumber);
891 QTextCursor rightCursor = m_rightEditor->textCursor();
892 rightCursor.setPosition(rightBlock.position());
893 m_rightEditor->setTextCursor(rightCursor);
894 m_rightEditor->verticalScrollBar()->setValue(blockNumber);
895
896 m_controller.m_ignoreCurrentIndexChange = oldIgnore;
897 }
898
setHorizontalSync(bool sync)899 void SideBySideDiffEditorWidget::setHorizontalSync(bool sync)
900 {
901 m_horizontalSync = sync;
902 rightHSliderChanged();
903 }
904
saveState()905 void SideBySideDiffEditorWidget::saveState()
906 {
907 m_leftEditor->saveState();
908 m_rightEditor->saveState();
909 }
910
restoreState()911 void SideBySideDiffEditorWidget::restoreState()
912 {
913 m_leftEditor->restoreState();
914 m_rightEditor->restoreState();
915 }
916
showDiff()917 void SideBySideDiffEditorWidget::showDiff()
918 {
919 QMap<int, QList<DiffSelection> > leftFormats;
920 QMap<int, QList<DiffSelection> > rightFormats;
921
922 QString leftTexts, rightTexts;
923 int blockNumber = 0;
924 QChar separator = '\n';
925 QHash<int, int> foldingIndent;
926 for (const FileData &contextFileData : qAsConst(m_controller.m_contextFileData)) {
927 QString leftText, rightText;
928
929 foldingIndent.insert(blockNumber, 1);
930 leftFormats[blockNumber].append(DiffSelection(&m_controller.m_fileLineFormat));
931 rightFormats[blockNumber].append(DiffSelection(&m_controller.m_fileLineFormat));
932 m_leftEditor->setFileInfo(blockNumber, contextFileData.leftFileInfo);
933 m_rightEditor->setFileInfo(blockNumber, contextFileData.rightFileInfo);
934 leftText = separator;
935 rightText = separator;
936 blockNumber++;
937
938 int lastLeftLineNumber = -1;
939
940 if (contextFileData.binaryFiles) {
941 foldingIndent.insert(blockNumber, 2);
942 leftFormats[blockNumber].append(DiffSelection(&m_controller.m_chunkLineFormat));
943 rightFormats[blockNumber].append(DiffSelection(&m_controller.m_chunkLineFormat));
944 m_leftEditor->setSkippedLines(blockNumber, -2);
945 m_rightEditor->setSkippedLines(blockNumber, -2);
946 leftText += separator;
947 rightText += separator;
948 blockNumber++;
949 } else {
950 for (int j = 0; j < contextFileData.chunks.count(); j++) {
951 const ChunkData &chunkData = contextFileData.chunks.at(j);
952
953 int leftLineNumber = chunkData.leftStartingLineNumber;
954 int rightLineNumber = chunkData.rightStartingLineNumber;
955
956 if (!chunkData.contextChunk) {
957 const int skippedLines = leftLineNumber - lastLeftLineNumber - 1;
958 if (skippedLines > 0) {
959 foldingIndent.insert(blockNumber, 2);
960 leftFormats[blockNumber].append(DiffSelection(&m_controller.m_chunkLineFormat));
961 rightFormats[blockNumber].append(DiffSelection(&m_controller.m_chunkLineFormat));
962 m_leftEditor->setSkippedLines(blockNumber, skippedLines, chunkData.contextInfo);
963 m_rightEditor->setSkippedLines(blockNumber, skippedLines, chunkData.contextInfo);
964 leftText += separator;
965 rightText += separator;
966 blockNumber++;
967 }
968
969 m_leftEditor->setChunkIndex(blockNumber, chunkData.rows.count(), j);
970 m_rightEditor->setChunkIndex(blockNumber, chunkData.rows.count(), j);
971
972 for (const RowData &rowData : chunkData.rows) {
973 TextLineData leftLineData = rowData.leftLine;
974 TextLineData rightLineData = rowData.rightLine;
975 if (leftLineData.textLineType == TextLineData::TextLine) {
976 leftText += leftLineData.text;
977 lastLeftLineNumber = leftLineNumber;
978 leftLineNumber++;
979 m_leftEditor->setLineNumber(blockNumber, leftLineNumber);
980 } else if (leftLineData.textLineType == TextLineData::Separator) {
981 m_leftEditor->setSeparator(blockNumber, true);
982 }
983
984 if (rightLineData.textLineType == TextLineData::TextLine) {
985 rightText += rightLineData.text;
986 rightLineNumber++;
987 m_rightEditor->setLineNumber(blockNumber, rightLineNumber);
988 } else if (rightLineData.textLineType == TextLineData::Separator) {
989 m_rightEditor->setSeparator(blockNumber, true);
990 }
991
992 if (!rowData.equal) {
993 if (rowData.leftLine.textLineType == TextLineData::TextLine)
994 leftFormats[blockNumber].append(DiffSelection(&m_controller.m_leftLineFormat));
995 else
996 leftFormats[blockNumber].append(DiffSelection(&m_spanLineFormat));
997 if (rowData.rightLine.textLineType == TextLineData::TextLine)
998 rightFormats[blockNumber].append(DiffSelection(&m_controller.m_rightLineFormat));
999 else
1000 rightFormats[blockNumber].append(DiffSelection(&m_spanLineFormat));
1001 }
1002
1003 for (auto it = leftLineData.changedPositions.cbegin(),
1004 end = leftLineData.changedPositions.cend(); it != end; ++it) {
1005 leftFormats[blockNumber].append(
1006 DiffSelection(it.key(), it.value(),
1007 &m_controller.m_leftCharFormat));
1008 }
1009
1010 for (auto it = rightLineData.changedPositions.cbegin(),
1011 end = rightLineData.changedPositions.cend(); it != end; ++it) {
1012 rightFormats[blockNumber].append(
1013 DiffSelection(it.key(), it.value(),
1014 &m_controller.m_rightCharFormat));
1015 }
1016
1017 leftText += separator;
1018 rightText += separator;
1019 blockNumber++;
1020 }
1021 }
1022
1023 if (j == contextFileData.chunks.count() - 1) { // the last chunk
1024 int skippedLines = -2;
1025 if (chunkData.contextChunk) {
1026 // if it's context chunk
1027 skippedLines = chunkData.rows.count();
1028 } else if (!contextFileData.lastChunkAtTheEndOfFile
1029 && !contextFileData.contextChunksIncluded) {
1030 // if not a context chunk and not a chunk at the end of file
1031 // and context lines not included
1032 skippedLines = -1; // unknown count skipped by the end of file
1033 }
1034
1035 if (skippedLines >= -1) {
1036 leftFormats[blockNumber].append(DiffSelection(&m_controller.m_chunkLineFormat));
1037 rightFormats[blockNumber].append(DiffSelection(&m_controller.m_chunkLineFormat));
1038 m_leftEditor->setSkippedLines(blockNumber, skippedLines);
1039 m_rightEditor->setSkippedLines(blockNumber, skippedLines);
1040 leftText += separator;
1041 rightText += separator;
1042 blockNumber++;
1043 } // otherwise nothing skipped
1044 }
1045 }
1046 }
1047 leftText.replace('\r', ' ');
1048 rightText.replace('\r', ' ');
1049 leftTexts += leftText;
1050 rightTexts += rightText;
1051 }
1052
1053 if (leftTexts.isEmpty() && rightTexts.isEmpty())
1054 return;
1055
1056 const bool oldIgnore = m_controller.m_ignoreCurrentIndexChange;
1057 m_controller.m_ignoreCurrentIndexChange = true;
1058 m_leftEditor->clear();
1059 m_leftEditor->setPlainText(leftTexts);
1060 m_rightEditor->clear();
1061 m_rightEditor->setPlainText(rightTexts);
1062 m_controller.m_ignoreCurrentIndexChange = oldIgnore;
1063
1064 QTextBlock block = m_leftEditor->document()->firstBlock();
1065 for (int b = 0; block.isValid(); block = block.next(), ++b)
1066 SelectableTextEditorWidget::setFoldingIndent(block, foldingIndent.value(b, 3));
1067 block = m_rightEditor->document()->firstBlock();
1068 for (int b = 0; block.isValid(); block = block.next(), ++b)
1069 SelectableTextEditorWidget::setFoldingIndent(block, foldingIndent.value(b, 3));
1070
1071 m_leftEditor->setSelections(leftFormats);
1072 m_rightEditor->setSelections(rightFormats);
1073 }
1074
setFontSettings(const FontSettings & fontSettings)1075 void SideBySideDiffEditorWidget::setFontSettings(
1076 const FontSettings &fontSettings)
1077 {
1078 m_spanLineFormat = fontSettings.toTextCharFormat(C_LINE_NUMBER);
1079 m_controller.setFontSettings(fontSettings);
1080 }
1081
slotLeftJumpToOriginalFileRequested(int diffFileIndex,int lineNumber,int columnNumber)1082 void SideBySideDiffEditorWidget::slotLeftJumpToOriginalFileRequested(
1083 int diffFileIndex,
1084 int lineNumber,
1085 int columnNumber)
1086 {
1087 if (diffFileIndex < 0 || diffFileIndex >= m_controller.m_contextFileData.count())
1088 return;
1089
1090 const FileData fileData = m_controller.m_contextFileData.at(diffFileIndex);
1091 const QString leftFileName = fileData.leftFileInfo.fileName;
1092 const QString rightFileName = fileData.rightFileInfo.fileName;
1093 if (leftFileName == rightFileName) {
1094 // The same file (e.g. in git diff), jump to the line number taken from the right editor.
1095 // Warning: git show SHA^ vs SHA or git diff HEAD vs Index
1096 // (when Working tree has changed in meantime) will not work properly.
1097 for (const ChunkData &chunkData : fileData.chunks) {
1098
1099 int leftLineNumber = chunkData.leftStartingLineNumber;
1100 int rightLineNumber = chunkData.rightStartingLineNumber;
1101
1102 for (int j = 0; j < chunkData.rows.count(); j++) {
1103 const RowData rowData = chunkData.rows.at(j);
1104 if (rowData.leftLine.textLineType == TextLineData::TextLine)
1105 leftLineNumber++;
1106 if (rowData.rightLine.textLineType == TextLineData::TextLine)
1107 rightLineNumber++;
1108 if (leftLineNumber == lineNumber) {
1109 int colNr = rowData.equal ? columnNumber : 0;
1110 m_controller.jumpToOriginalFile(leftFileName, rightLineNumber, colNr);
1111 return;
1112 }
1113 }
1114 }
1115 } else {
1116 // different file (e.g. in Tools | Diff...)
1117 m_controller.jumpToOriginalFile(leftFileName, lineNumber, columnNumber);
1118 }
1119 }
1120
slotRightJumpToOriginalFileRequested(int diffFileIndex,int lineNumber,int columnNumber)1121 void SideBySideDiffEditorWidget::slotRightJumpToOriginalFileRequested(
1122 int diffFileIndex,
1123 int lineNumber,
1124 int columnNumber)
1125 {
1126 if (diffFileIndex < 0 || diffFileIndex >= m_controller.m_contextFileData.count())
1127 return;
1128
1129 const FileData fileData = m_controller.m_contextFileData.at(diffFileIndex);
1130 const QString fileName = fileData.rightFileInfo.fileName;
1131 m_controller.jumpToOriginalFile(fileName, lineNumber, columnNumber);
1132 }
1133
slotLeftContextMenuRequested(QMenu * menu,int fileIndex,int chunkIndex,const ChunkSelection & selection)1134 void SideBySideDiffEditorWidget::slotLeftContextMenuRequested(QMenu *menu,
1135 int fileIndex,
1136 int chunkIndex,
1137 const ChunkSelection &selection)
1138 {
1139 menu->addSeparator();
1140
1141 m_controller.addCodePasterAction(menu, fileIndex, chunkIndex);
1142 m_controller.addApplyAction(menu, fileIndex, chunkIndex);
1143 m_controller.addExtraActions(menu, fileIndex, chunkIndex, selection);
1144 }
1145
slotRightContextMenuRequested(QMenu * menu,int fileIndex,int chunkIndex,const ChunkSelection & selection)1146 void SideBySideDiffEditorWidget::slotRightContextMenuRequested(QMenu *menu,
1147 int fileIndex,
1148 int chunkIndex,
1149 const ChunkSelection &selection)
1150 {
1151 menu->addSeparator();
1152
1153 m_controller.addCodePasterAction(menu, fileIndex, chunkIndex);
1154 m_controller.addRevertAction(menu, fileIndex, chunkIndex);
1155 m_controller.addExtraActions(menu, fileIndex, chunkIndex, selection);
1156 }
1157
leftVSliderChanged()1158 void SideBySideDiffEditorWidget::leftVSliderChanged()
1159 {
1160 if (m_controller.m_ignoreCurrentIndexChange)
1161 return;
1162
1163 m_rightEditor->verticalScrollBar()->setValue(m_leftEditor->verticalScrollBar()->value());
1164 }
1165
rightVSliderChanged()1166 void SideBySideDiffEditorWidget::rightVSliderChanged()
1167 {
1168 if (m_controller.m_ignoreCurrentIndexChange)
1169 return;
1170
1171 m_leftEditor->verticalScrollBar()->setValue(m_rightEditor->verticalScrollBar()->value());
1172 }
1173
leftHSliderChanged()1174 void SideBySideDiffEditorWidget::leftHSliderChanged()
1175 {
1176 if (m_controller.m_ignoreCurrentIndexChange)
1177 return;
1178
1179 if (m_horizontalSync)
1180 m_rightEditor->horizontalScrollBar()->setValue(m_leftEditor->horizontalScrollBar()->value());
1181 }
1182
rightHSliderChanged()1183 void SideBySideDiffEditorWidget::rightHSliderChanged()
1184 {
1185 if (m_controller.m_ignoreCurrentIndexChange)
1186 return;
1187
1188 if (m_horizontalSync)
1189 m_leftEditor->horizontalScrollBar()->setValue(m_rightEditor->horizontalScrollBar()->value());
1190 }
1191
leftCursorPositionChanged()1192 void SideBySideDiffEditorWidget::leftCursorPositionChanged()
1193 {
1194 if (m_controller.m_ignoreCurrentIndexChange)
1195 return;
1196
1197 handlePositionChange(m_leftEditor, m_rightEditor);
1198 leftVSliderChanged();
1199 leftHSliderChanged();
1200 }
1201
rightCursorPositionChanged()1202 void SideBySideDiffEditorWidget::rightCursorPositionChanged()
1203 {
1204 if (m_controller.m_ignoreCurrentIndexChange)
1205 return;
1206
1207 handlePositionChange(m_rightEditor, m_leftEditor);
1208 rightVSliderChanged();
1209 rightHSliderChanged();
1210 }
1211
syncHorizontalScrollBarPolicy()1212 void SideBySideDiffEditorWidget::syncHorizontalScrollBarPolicy()
1213 {
1214 const bool alwaysOn = m_leftEditor->horizontalScrollBar()->maximum()
1215 || m_rightEditor->horizontalScrollBar()->maximum();
1216 const Qt::ScrollBarPolicy newPolicy = alwaysOn
1217 ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAsNeeded;
1218 if (m_leftEditor->horizontalScrollBarPolicy() != newPolicy)
1219 m_leftEditor->setHorizontalScrollBarPolicy(newPolicy);
1220 if (m_rightEditor->horizontalScrollBarPolicy() != newPolicy)
1221 m_rightEditor->setHorizontalScrollBarPolicy(newPolicy);
1222 }
1223
handlePositionChange(SideDiffEditorWidget * source,SideDiffEditorWidget * dest)1224 void SideBySideDiffEditorWidget::handlePositionChange(SideDiffEditorWidget *source, SideDiffEditorWidget *dest)
1225 {
1226 if (m_controller.m_ignoreCurrentIndexChange)
1227 return;
1228
1229 const bool oldIgnore = m_controller.m_ignoreCurrentIndexChange;
1230 m_controller.m_ignoreCurrentIndexChange = true;
1231 syncCursor(source, dest);
1232 emit currentDiffFileIndexChanged(
1233 source->fileIndexForBlockNumber(source->textCursor().blockNumber()));
1234 m_controller.m_ignoreCurrentIndexChange = oldIgnore;
1235 }
1236
syncCursor(SideDiffEditorWidget * source,SideDiffEditorWidget * dest)1237 void SideBySideDiffEditorWidget::syncCursor(SideDiffEditorWidget *source, SideDiffEditorWidget *dest)
1238 {
1239 const int oldHSliderPos = dest->horizontalScrollBar()->value();
1240
1241 const QTextCursor sourceCursor = source->textCursor();
1242 const int sourceLine = sourceCursor.blockNumber();
1243 const int sourceColumn = sourceCursor.positionInBlock();
1244 QTextCursor destCursor = dest->textCursor();
1245 const QTextBlock destBlock = dest->document()->findBlockByNumber(sourceLine);
1246 const int destColumn = qMin(sourceColumn, destBlock.length());
1247 const int destPosition = destBlock.position() + destColumn;
1248 destCursor.setPosition(destPosition);
1249 dest->setTextCursor(destCursor);
1250
1251 dest->horizontalScrollBar()->setValue(oldHSliderPos);
1252 }
1253
1254 } // namespace Internal
1255 } // namespace DiffEditor
1256
1257 #include "sidebysidediffeditorwidget.moc"
1258