1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Copyright (C) 2020 Francesc M. |
5 | ** Contact: https://www.qt.io/licensing/ |
6 | ** |
7 | ** This file is part of the examples of the Qt Toolkit. |
8 | ** |
9 | ** $QT_BEGIN_LICENSE:BSD$ |
10 | ** Commercial License Usage |
11 | ** Licensees holding valid commercial Qt licenses may use this file in |
12 | ** accordance with the commercial license agreement provided with the |
13 | ** Software or, alternatively, in accordance with the terms contained in |
14 | ** a written agreement between you and The Qt Company. For licensing terms |
15 | ** and conditions see https://www.qt.io/terms-conditions. For further |
16 | ** information use the contact form at https://www.qt.io/contact-us. |
17 | ** |
18 | ** BSD License Usage |
19 | ** Alternatively, you may use this file under the terms of the BSD license |
20 | ** as follows: |
21 | ** |
22 | ** "Redistribution and use in source and binary forms, with or without |
23 | ** modification, are permitted provided that the following conditions are |
24 | ** met: |
25 | ** * Redistributions of source code must retain the above copyright |
26 | ** notice, this list of conditions and the following disclaimer. |
27 | ** * Redistributions in binary form must reproduce the above copyright |
28 | ** notice, this list of conditions and the following disclaimer in |
29 | ** the documentation and/or other materials provided with the |
30 | ** distribution. |
31 | ** * Neither the name of The Qt Company Ltd nor the names of its |
32 | ** contributors may be used to endorse or promote products derived |
33 | ** from this software without specific prior written permission. |
34 | ** |
35 | ** |
36 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
37 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
38 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
39 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
40 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
41 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
42 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
43 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
44 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
45 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
46 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
47 | ** |
48 | ** $QT_END_LICENSE$ |
49 | ** |
50 | ****************************************************************************/ |
51 | |
52 | #include "FileDiffView.h" |
53 | |
54 | #include <FileDiffHighlighter.h> |
55 | #include <LineNumberArea.h> |
56 | |
57 | #include <QLogger.h> |
58 | |
59 | #include <QScrollBar> |
60 | #include <QMenu> |
61 | |
62 | using namespace QLogger; |
63 | |
64 | FileDiffView::FileDiffView(QWidget *parent) |
65 | : QPlainTextEdit(parent) |
66 | , mDiffHighlighter(new FileDiffHighlighter(document())) |
67 | { |
68 | setAttribute(Qt::WA_DeleteOnClose); |
69 | setReadOnly(true); |
70 | setContextMenuPolicy(Qt::CustomContextMenu); |
71 | |
72 | connect(this, &FileDiffView::customContextMenuRequested, this, &FileDiffView::showStagingMenu); |
73 | connect(this, &FileDiffView::blockCountChanged, this, &FileDiffView::updateLineNumberAreaWidth); |
74 | connect(this, &FileDiffView::updateRequest, this, &FileDiffView::updateLineNumberArea); |
75 | connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &FileDiffView::signalScrollChanged); |
76 | } |
77 | |
78 | FileDiffView::~FileDiffView() |
79 | { |
80 | delete mDiffHighlighter; |
81 | } |
82 | |
83 | void FileDiffView::addNumberArea(LineNumberArea *numberArea) |
84 | { |
85 | mLineNumberArea = numberArea; |
86 | |
87 | if (mLineNumberArea->commentsAllowed()) |
88 | { |
89 | installEventFilter(this); |
90 | setMouseTracking(true); |
91 | } |
92 | } |
93 | |
94 | void FileDiffView::loadDiff(const QString &text, const QVector<ChunkDiffInfo::ChunkInfo> &fileDiffInfo) |
95 | { |
96 | QLog_Trace("UI" , |
97 | QString("FileDiffView::loadDiff - {%1} move scroll to pos {%2}" ) |
98 | .arg(objectName(), QString::number(verticalScrollBar()->value()))); |
99 | |
100 | mFileDiffInfo = fileDiffInfo; |
101 | |
102 | mDiffHighlighter->setDiffInfo(mFileDiffInfo); |
103 | |
104 | const auto pos = verticalScrollBar()->value(); |
105 | auto cursor = textCursor(); |
106 | const auto tmpCursor = textCursor().position(); |
107 | setPlainText(text); |
108 | |
109 | cursor.setPosition(tmpCursor); |
110 | setTextCursor(cursor); |
111 | |
112 | blockSignals(true); |
113 | verticalScrollBar()->setValue(pos); |
114 | blockSignals(false); |
115 | |
116 | emit updateRequest(viewport()->rect(), 0); |
117 | |
118 | QLog_Trace("UI" , |
119 | QString("FileDiffView::loadDiff - {%1} move scroll to pos {%2}" ).arg(objectName(), QString::number(pos))); |
120 | } |
121 | |
122 | void FileDiffView::moveScrollBarToPos(int value) |
123 | { |
124 | blockSignals(true); |
125 | verticalScrollBar()->setValue(value); |
126 | blockSignals(false); |
127 | |
128 | emit updateRequest(viewport()->rect(), 0); |
129 | |
130 | QLog_Trace("UI" , |
131 | QString("FileDiffView::moveScrollBarToPos - {%1} move scroll to pos {%2}" ) |
132 | .arg(objectName(), QString::number(value))); |
133 | } |
134 | |
135 | int FileDiffView::getHeight() const |
136 | { |
137 | auto block = firstVisibleBlock(); |
138 | auto height = 0; |
139 | |
140 | while (block.isValid()) |
141 | { |
142 | height += blockBoundingRect(block).height(); |
143 | block = block.next(); |
144 | } |
145 | |
146 | return height; |
147 | } |
148 | |
149 | int FileDiffView::getLineHeigth() const |
150 | { |
151 | return blockBoundingRect(firstVisibleBlock()).height(); |
152 | } |
153 | |
154 | int FileDiffView::lineNumberAreaWidth() |
155 | { |
156 | const auto width = fontMetrics().horizontalAdvance(QLatin1Char('9')); |
157 | auto digits = mLineNumberArea ? mLineNumberArea->widthInDigitsSize() : 0; |
158 | auto max = blockCount() + mStartingLine; |
159 | |
160 | while (max >= 10) |
161 | { |
162 | max /= 10; |
163 | ++digits; |
164 | } |
165 | |
166 | auto offset = 0; |
167 | |
168 | if (mLineNumberArea && mLineNumberArea->commentsAllowed()) |
169 | { |
170 | const auto padding = 3; |
171 | offset = (fontMetrics().height() * 2 + padding * 4); |
172 | } |
173 | |
174 | return offset + (width * digits); |
175 | } |
176 | |
177 | void FileDiffView::updateLineNumberAreaWidth(int /* newBlockCount */) |
178 | { |
179 | setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); |
180 | } |
181 | |
182 | void FileDiffView::updateLineNumberArea(const QRect &rect, int dy) |
183 | { |
184 | if (mLineNumberArea) |
185 | { |
186 | if (dy != 0) |
187 | mLineNumberArea->scroll(0, dy); |
188 | else |
189 | mLineNumberArea->update(0, rect.y(), mLineNumberArea->width(), rect.height()); |
190 | |
191 | if (rect.contains(viewport()->rect())) |
192 | updateLineNumberAreaWidth(0); |
193 | } |
194 | } |
195 | |
196 | void FileDiffView::resizeEvent(QResizeEvent *e) |
197 | { |
198 | QPlainTextEdit::resizeEvent(e); |
199 | |
200 | if (mLineNumberArea) |
201 | { |
202 | const auto cr = contentsRect(); |
203 | |
204 | mLineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); |
205 | } |
206 | } |
207 | |
208 | bool FileDiffView::eventFilter(QObject *obj, QEvent *event) |
209 | { |
210 | if (mLineNumberArea && event->type() == QEvent::Enter) |
211 | { |
212 | const auto height = mLineNumberArea->width(); |
213 | const auto helpPos = mapFromGlobal(QCursor::pos()); |
214 | const auto x = helpPos.x(); |
215 | if (x >= 0 && x <= height) |
216 | { |
217 | QTextCursor cursor = cursorForPosition(helpPos); |
218 | const auto textRow = cursor.block().blockNumber(); |
219 | auto found = false; |
220 | |
221 | for (const auto &diff : qAsConst(mFileDiffInfo)) |
222 | { |
223 | if (textRow + 1 >= diff.startLine && textRow + 1 <= diff.endLine) |
224 | { |
225 | mRow = textRow + mStartingLine + 1; |
226 | found = true; |
227 | break; |
228 | } |
229 | } |
230 | |
231 | if (!found) |
232 | mRow = -1; |
233 | |
234 | repaint(); |
235 | } |
236 | } |
237 | else if (event->type() == QEvent::Leave) |
238 | { |
239 | mRow = -1; |
240 | repaint(); |
241 | } |
242 | |
243 | return QPlainTextEdit::eventFilter(obj, event); |
244 | } |
245 | |
246 | void FileDiffView::(const QPoint &cursorPos) |
247 | { |
248 | const auto helpPos = mapFromGlobal(QCursor::pos()); |
249 | const auto x = helpPos.x(); |
250 | auto row = -1; |
251 | |
252 | if (x >= 0 && x > mLineNumberArea->width()) |
253 | { |
254 | const auto cursor = cursorForPosition(helpPos); |
255 | row = cursor.block().blockNumber() + mStartingLine + 1; |
256 | |
257 | if (row != -1) |
258 | { |
259 | const auto chunk |
260 | = std::find_if(mFileDiffInfo.cbegin(), mFileDiffInfo.cend(), [row](const ChunkDiffInfo::ChunkInfo &chunk) { |
261 | return chunk.startLine <= row && row <= chunk.endLine; |
262 | }); |
263 | |
264 | if (chunk != mFileDiffInfo.cend()) |
265 | { |
266 | const auto = new QMenu(this); |
267 | /* |
268 | const auto stageLine = menu->addAction(tr("Stage line")); |
269 | connect(stageLine, &QAction::triggered, this, &FileDiffView::stageLine); |
270 | */ |
271 | |
272 | const auto stageChunk = menu->addAction(tr("Stage chunk" )); |
273 | connect(stageChunk, &QAction::triggered, this, |
274 | [this, chunkId = chunk->id]() { emit signalStageChunk(chunkId); }); |
275 | |
276 | menu->move(viewport()->mapToGlobal(cursorPos)); |
277 | menu->exec(); |
278 | } |
279 | } |
280 | } |
281 | } |
282 | |