• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdevelop API Reference
  • KDE Home
  • Contact Us
 

kdevelop/kdevplatform/vcs

  • extragear
  • kdevelop
  • kdevelop
  • kdevplatform
  • vcs
  • widgets
vcsannotationitemdelegate.cpp
Go to the documentation of this file.
1 /* This file is part of KDevelop
2  *
3  * Copyright 2017-2018 Friedrich W. H. Kossebau <[email protected]>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  * 02110-1301, USA.
19  */
20 
21 #include "vcsannotationitemdelegate.h"
22 
23 #include <models/vcsannotationmodel.h>
24 #include <vcsannotation.h>
25 #include <debug.h>
26 
27 #include <KTextEditor/AnnotationInterface>
28 #include <KTextEditor/View>
29 #include <KTextEditor/ConfigInterface>
30 #include <KTextEditor/Attribute>
31 #include <KLocalizedString>
32 
33 #include <QHelpEvent>
34 #include <QPainter>
35 #include <QAction>
36 #include <QMenu>
37 #include <QToolTip>
38 #include <QFontMetricsF>
39 #include <QDate>
40 #include <QStyle>
41 #include <QApplication>
42 
43 #include <cmath>
44 
45 using namespace KDevelop;
46 
47 VcsAnnotationItemDelegate::VcsAnnotationItemDelegate(KTextEditor::View* view, KTextEditor::AnnotationModel* model,
48  QObject* parent)
49  : KTextEditor::AbstractAnnotationItemDelegate(parent)
50  , m_model(model)
51 {
52  // dump background brushes on schema change
53  Q_ASSERT(qobject_cast<KTextEditor::ConfigInterface*>(view));
54  connect(view, SIGNAL(configChanged()), this, SLOT(resetBackgrounds()));
55 
56  view->installEventFilter(this);
57 }
58 
59 VcsAnnotationItemDelegate::~VcsAnnotationItemDelegate() = default;
60 
61 static QString ageOfDate(const QDate& date)
62 {
63  const auto now = QDate::currentDate();
64  int ageInYears = now.year() - date.year();
65  if (now < date.addYears(ageInYears)) {
66  --ageInYears;
67  }
68  if (ageInYears > 0) {
69  return i18ncp("age", "%1 year", "%1 years", ageInYears);
70  }
71  int ageInMonths = now.month() - date.month();
72  if (now.day() < date.day()) {
73  --ageInMonths;
74  }
75  if (ageInMonths < 0) {
76  ageInMonths += 12;
77  }
78  if (ageInMonths > 0) {
79  return i18ncp("age", "%1 month", "%1 months", ageInMonths);
80  }
81  const int ageInDays = date.daysTo(now);
82  if (ageInDays > 0) {
83  return i18ncp("age", "%1 day", "%1 days", ageInDays);
84  }
85  return i18n("Today");
86 }
87 
88 void VcsAnnotationItemDelegate::doMessageLineLayout(const KTextEditor::StyleOptionAnnotationItem& option,
89  QRect* messageRect, QRect* ageRect) const
90 {
91  Q_ASSERT(messageRect && messageRect->isValid());
92  Q_ASSERT(ageRect);
93 
94  const QWidget* const widget = option.view;
95  QStyle* const style = widget ? widget->style() : QApplication::style();
96  const bool hasAge = ageRect->isValid();
97  // "+ 1" as used in QItemDelegate
98  const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, widget) + 1;
99  const int ageMargin = hasAge ? textMargin : 0;
100 
101  const int x = option.rect.left();
102  const int y = option.rect.top();
103  const int w = option.rect.width();
104  const int h = option.rect.height();
105 
106  // add margins for fixed elements
107  QSize ageSize(0, 0); // ageRect could be invalid, so use separate object for calculation
108  if (hasAge) {
109  ageSize = ageRect->size();
110  ageSize.rwidth() += 2 * ageMargin;
111  }
112 
113  // distribute space among layout items
114  QRect message;
115  QRect age;
116  if (option.direction == Qt::LeftToRight) {
117  message.setRect(x, y, w - ageSize.width(), h);
118  age.setRect(message.right() + 1, y, ageSize.width(), h);
119  } else {
120  age.setRect(x, y, ageSize.width(), h);
121  message.setRect(age.right() + 1, y, w - ageSize.width(), h);
122  }
123  // remove margins here, so renderMessageAndAge does not have to
124  message.adjust(textMargin, 0, -textMargin, 0);
125  age.adjust(ageMargin, 0, -ageMargin, 0);
126 
127  // return result
128  *ageRect = age;
129  *messageRect = QStyle::alignedRect(option.direction, Qt::AlignLeading,
130  messageRect->size().boundedTo(message.size()), message);
131 }
132 
133 void VcsAnnotationItemDelegate::doAuthorLineLayout(const KTextEditor::StyleOptionAnnotationItem& option,
134  QRect* authorRect) const
135 {
136  Q_ASSERT(authorRect);
137 
138  // if invalid, nothing to be done, keep as is
139  if (!authorRect->isValid()) {
140  return;
141  }
142 
143  const QWidget* const widget = option.view;
144  QStyle* const style = widget ? widget->style() : QApplication::style();
145  // "+ 1" as used in QItemDelegate
146  const int authorMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, widget) + 1;
147 
148  QRect author = option.rect;
149  // remove margins here, so renderAuthor does not have to
150  author.adjust(authorMargin, 0, -authorMargin, 0);
151 
152  // return result
153  *authorRect = QStyle::alignedRect(option.direction, Qt::AlignLeading,
154  authorRect->size().boundedTo(author.size()), author);
155 }
156 
157 void VcsAnnotationItemDelegate::renderBackground(QPainter* painter,
158  const KTextEditor::StyleOptionAnnotationItem& option,
159  const VcsAnnotationLine& annotationLine) const
160 {
161  Q_UNUSED(option);
162 
163  const auto revision = annotationLine.revision();
164  auto brushIt = m_backgrounds.find(revision);
165  if (brushIt == m_backgrounds.end()) {
166  KTextEditor::Attribute::Ptr normalStyle = option.view->defaultStyleAttribute(KTextEditor::dsNormal);
167  const auto background = (normalStyle->hasProperty(QTextFormat::BackgroundBrush)) ? normalStyle->background().color() : QColor(Qt::white);
168  const int background_y = background.red()*0.299 + 0.587*background.green()
169  + 0.114*background.blue();
170  // get random, but reproducable 8-bit values from last two bytes of the revision hash
171  const uint revisionHash = qHash(revision);
172  const int u = static_cast<int>((0xFF & revisionHash));
173  const int v = static_cast<int>((0xFF00 & revisionHash) >> 8);
174  const int r = qRound(qMin(255.0, qMax(0.0, background_y + 1.402*(v-128))));
175  const int g = qRound(qMin(255.0, qMax(0.0, background_y - 0.344*(u-128) - 0.714*(v-128))));
176  const int b = qRound(qMin(255.0, qMax(0.0, background_y + 1.772*(u-128))));
177  brushIt = m_backgrounds.insert(revision, QBrush(QColor(r, g, b)));
178  }
179 
180  painter->fillRect(option.rect, brushIt.value());
181 }
182 
183 void VcsAnnotationItemDelegate::renderMessageAndAge(QPainter* painter,
184  const KTextEditor::StyleOptionAnnotationItem& option,
185  const QRect& messageRect, const QString& messageText,
186  const QRect& ageRect, const QString& ageText) const
187 {
188  Q_UNUSED(option);
189 
190  painter->save();
191 
192  KTextEditor::Attribute::Ptr normalStyle = option.view->defaultStyleAttribute(KTextEditor::dsNormal);
193  painter->setPen(normalStyle->foreground().color());
194  painter->drawText(messageRect, Qt::AlignLeading | Qt::AlignVCenter,
195  painter->fontMetrics().elidedText(messageText, Qt::ElideRight, messageRect.width()));
196 
197  // TODO: defaultStyleAttribute only returns reliably for dsNormal, so what to do for a comment-like color?
198  KTextEditor::Attribute::Ptr commentStyle = option.view->defaultStyleAttribute(KTextEditor::dsNormal);
199  painter->setPen(commentStyle->foreground().color());
200  painter->drawText(ageRect, Qt::AlignTrailing | Qt::AlignVCenter, ageText);
201 
202  painter->restore();
203 }
204 
205 void VcsAnnotationItemDelegate::renderAuthor(QPainter* painter,
206  const KTextEditor::StyleOptionAnnotationItem& option,
207  const QRect& authorRect, const QString& authorText) const
208 {
209  Q_UNUSED(option);
210 
211  painter->save();
212 
213  // TODO: defaultStyleAttribute only returns reliably for dsNormal, so what to do for a comment-like color?
214  KTextEditor::Attribute::Ptr commentStyle = option.view->defaultStyleAttribute(KTextEditor::dsNormal);
215  painter->setPen(commentStyle->foreground().color());
216  painter->drawText(authorRect, Qt::AlignLeading | Qt::AlignVCenter,
217  painter->fontMetrics().elidedText(authorText, Qt::ElideRight, authorRect.width()));
218 
219  painter->restore();
220 }
221 
222 void VcsAnnotationItemDelegate::renderHighlight(QPainter* painter,
223  const KTextEditor::StyleOptionAnnotationItem& option) const
224 {
225  // Draw a border around all adjacent entries that have the same text as the currently hovered one
226  if ((option.state & QStyle::State_MouseOver) &&
227  (option.annotationItemGroupingPosition & KTextEditor::StyleOptionAnnotationItem::InGroup)) {
228  KTextEditor::Attribute::Ptr style = option.view->defaultStyleAttribute(KTextEditor::dsNormal);
229  painter->setPen(style->foreground().color());
230  // Use floating point coordinates to support scaled rendering
231  QRectF rect(option.rect);
232  rect.adjust(0.5, 0.5, -0.5, -0.5);
233  // draw left and right highlight borders
234  painter->drawLine(rect.topLeft(), rect.bottomLeft());
235  painter->drawLine(rect.topRight(), rect.bottomRight());
236 
237  if ((option.annotationItemGroupingPosition & KTextEditor::StyleOptionAnnotationItem::GroupBegin) &&
238  (option.wrappedLine == 0)) {
239  painter->drawLine(rect.topLeft(), rect.topRight());
240  }
241 
242  if ((option.annotationItemGroupingPosition & KTextEditor::StyleOptionAnnotationItem::GroupEnd) &&
243  (option.wrappedLine == (option.wrappedLineCount-1))) {
244  painter->drawLine(rect.bottomLeft(), rect.bottomRight());
245  }
246  }
247 }
248 
249 void VcsAnnotationItemDelegate::paint(QPainter* painter, const KTextEditor::StyleOptionAnnotationItem& option,
250  KTextEditor::AnnotationModel* model, int line) const
251 {
252  Q_ASSERT(painter);
253  // we cannot use custom roles and data() API (cmp. VcsAnnotationModel dox), so accessing custom API instead
254  VcsAnnotationModel* vcsModel = qobject_cast<VcsAnnotationModel*>(model);
255  Q_ASSERT(vcsModel);
256  if (!painter || !vcsModel) {
257  return;
258  }
259  // test of line just for sake of completeness skipped here
260 
261  // Fetch data from the model
262  const VcsAnnotationLine annotationLine = vcsModel->annotationLine(line);
263 
264  if (annotationLine.revision().revisionType() == VcsRevision::Invalid) {
265  return;
266  }
267 
268  // prepare
269  painter->save();
270 
271  renderBackground(painter, option, annotationLine);
272 
273  // We use the normal UI font here, which usually is a proportimal one,
274  // so more text fits into the available space.
275  // Though we do this at the cost of not adapting to any scaled content font size,
276  // as there is no zooming state info available, so we cannot adapt.
277  // Tooltip font also is not scaled, and annotations could be considered to fall into
278  // that category, so might be fine.
279  painter->setFont(option.view->font());
280 
281  if (option.visibleWrappedLineInGroup == 0) {
282  QRect ageRect;
283  QString ageText;
284  const auto date = annotationLine.date();
285  if (date.isValid()) {
286  ageText = ageOfDate(date.date());
287  ageRect = QRect(QPoint(0, 0),
288 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
289  QSize(option.fontMetrics.horizontalAdvance(ageText), option.rect.height()));
290 #else
291  QSize(option.fontMetrics.width(ageText), option.rect.height()));
292 #endif
293  }
294  const auto messageText = annotationLine.commitMessage();
295  auto messageRect = QRect(QPoint(0, 0),
296 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
297  QSize(option.fontMetrics.horizontalAdvance(messageText), option.rect.height()));
298 #else
299  QSize(option.fontMetrics.width(messageText), option.rect.height()));
300 #endif
301 
302  doMessageLineLayout(option, &messageRect, &ageRect);
303 
304  renderMessageAndAge(painter, option, messageRect, messageText, ageRect, ageText);
305  } else if (option.visibleWrappedLineInGroup == 1) {
306  const auto author = annotationLine.author();
307  if (!author.isEmpty()) {
308  const auto authorText = i18nc("By: commit author", "By: %1", author);
309  auto authorRect = QRect(QPoint(0, 0),
310 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
311  QSize(option.fontMetrics.horizontalAdvance(authorText), option.rect.height()));
312 #else
313  QSize(option.fontMetrics.width(authorText), option.rect.height()));
314 #endif
315 
316  doAuthorLineLayout(option, &authorRect);
317 
318  renderAuthor(painter, option, authorRect, authorText);
319  }
320  }
321 
322  renderHighlight(painter, option);
323 
324  // done
325  painter->restore();
326 }
327 
328 bool VcsAnnotationItemDelegate::helpEvent(QHelpEvent* event, KTextEditor::View* view,
329  const KTextEditor::StyleOptionAnnotationItem& option,
330  KTextEditor::AnnotationModel* model, int line)
331 {
332  Q_UNUSED(option);
333  if (!model || event->type() != QEvent::ToolTip) {
334  return false;
335  }
336 
337  const QString annotationGroupId = model->data(line, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole).toString();
338 
339  const QVariant data = model->data(line, Qt::ToolTipRole);
340  if (!data.isValid()) {
341  return false;
342  }
343 
344  const QString toolTipText = data.toString();
345  if (toolTipText.isEmpty()) {
346  return false;
347  }
348 
349  QToolTip::showText(event->globalPos(), toolTipText, view, option.rect);
350 
351  return true;
352 }
353 
354 void VcsAnnotationItemDelegate::hideTooltip(KTextEditor::View *view)
355 {
356  Q_UNUSED(view);
357  QToolTip::hideText();
358 }
359 
360 QSize VcsAnnotationItemDelegate::sizeHint(const KTextEditor::StyleOptionAnnotationItem& option,
361  KTextEditor::AnnotationModel* model, int line) const
362 {
363  Q_UNUSED(line);
364  Q_ASSERT(model);
365  if (!model) {
366  return QSize(0, 0);
367  }
368 
369  // Ideally the user could configure the width of the annotations, best interactively.
370  // Until this is possible, the sizehint is: roughly 40 chars, but maximal 25 % of the view
371  // See eventFilter for making sure we adapt the max 25 % to a changed width.
372 
373  const QFontMetricsF& fm(option.fontMetrics);
374  // if only averageCharWidth would yield sane values,
375  // multiply by 40 in average seemed okayish at least with english, showing enough of message
376  m_lastCharBasedWidthHint = ceil(40 * fm.averageCharWidth());
377  m_lastViewBasedWidthHint = widthHintFromViewWidth(option.view->width());
378  return QSize(qMin(m_lastCharBasedWidthHint, m_lastViewBasedWidthHint), fm.height());
379 }
380 
381 bool VcsAnnotationItemDelegate::eventFilter(QObject* object, QEvent* event)
382 {
383  if (event->type() == QEvent::Resize) {
384  auto resizeEvent = static_cast<QResizeEvent*>(event);
385  const int viewBasedWidthHint = widthHintFromViewWidth(resizeEvent->size().width());
386  if ((viewBasedWidthHint < m_lastCharBasedWidthHint) &&
387  (viewBasedWidthHint != m_lastViewBasedWidthHint)) {
388  // emit for first line only, assuming uniformAnnotationItemSizes is set to true
389  emit sizeHintChanged(m_model, 0);
390  }
391  }
392 
393  return KTextEditor::AbstractAnnotationItemDelegate::eventFilter(object, event);
394 }
395 
396 void VcsAnnotationItemDelegate::resetBackgrounds()
397 {
398  m_backgrounds.clear();
399 }
400 
401 int VcsAnnotationItemDelegate::widthHintFromViewWidth(int viewWidth) const
402 {
403  return viewWidth * m_maxWidthViewPercent / 100;
404 }
KDevelop::VcsAnnotationModel::annotationLine
VcsAnnotationLine annotationLine(int line) const
Definition: vcsannotationmodel.cpp:191
QDate::daysTo
int daysTo(const QDate &d) const
KDevelop::VcsAnnotationItemDelegate::VcsAnnotationItemDelegate
VcsAnnotationItemDelegate(KTextEditor::View *view, KTextEditor::AnnotationModel *model, QObject *parent)
Definition: vcsannotationitemdelegate.cpp:47
QSize::boundedTo
QSize boundedTo(const QSize &otherSize) const
QEvent
vcsannotation.h
QResizeEvent
QWidget
QRect::size
QSize size() const
QEvent::type
Type type() const
QDate::addYears
QDate addYears(int nyears) const
KDevelop::VcsAnnotationLine
Annotation information for a line of a version controlled file.
Definition: vcsannotation.h:40
KDevelop::VcsAnnotationItemDelegate::eventFilter
bool eventFilter(QObject *object, QEvent *event) override
Definition: vcsannotationitemdelegate.cpp:381
QPainter::fillRect
void fillRect(const QRectF &rectangle, const QBrush &brush)
KDevelop::VcsAnnotationItemDelegate::hideTooltip
void hideTooltip(KTextEditor::View *view) override
Definition: vcsannotationitemdelegate.cpp:354
QRect::right
int right() const
KDevelop::VcsRevision::Invalid
The type is not set, this is an invalid revision.
Definition: vcsrevision.h:82
QWidget::style
QStyle * style() const
QStyle::pixelMetric
virtual int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const =0
KDevelop::VcsAnnotationItemDelegate::~VcsAnnotationItemDelegate
~VcsAnnotationItemDelegate() override
QSize::rwidth
int & rwidth()
QPainter::save
void save()
KDevelop::VcsRevision::revisionType
RevisionType revisionType() const
returns the type of the revision
Definition: vcsrevision.cpp:72
QBrush
vcsannotationitemdelegate.h
QPoint
KDevelop::VcsAnnotationLine::author
QString author() const
Definition: vcsannotation.cpp:75
QPainter::drawLine
void drawLine(const QLineF &line)
QDate::month
int month() const
QToolTip::showText
void showText(const QPoint &pos, const QString &text, QWidget *w)
KDevelop::VcsAnnotationLine::revision
VcsRevision revision() const
Definition: vcsannotation.cpp:80
QStyle::alignedRect
QRect alignedRect(Qt::LayoutDirection direction, QFlags< Qt::AlignmentFlag > alignment, const QSize &size, const QRect &rectangle)
QRect
QPainter::setFont
void setFont(const QFont &font)
QObject
QStyle
QPainter::setPen
void setPen(const QColor &color)
QHelpEvent::globalPos
const QPoint & globalPos() const
QPainter
KDevelop::VcsAnnotationLine::date
QDateTime date() const
Definition: vcsannotation.cpp:85
QString::isEmpty
bool isEmpty() const
ageOfDate
static QString ageOfDate(const QDate &date)
Definition: vcsannotationitemdelegate.cpp:61
QDate::day
int day() const
QFontMetrics::elidedText
QString elidedText(const QString &text, Qt::TextElideMode mode, int width, int flags) const
KDevelop::VcsAnnotationModel
Definition: vcsannotationmodel.h:41
QPainter::drawText
void drawText(const QPointF &position, const QString &text)
QDate
QDate::year
int year() const
QString
QColor
vcsannotationmodel.h
QSize
QRect::isEmpty
bool isEmpty() const
QToolTip::hideText
void hideText()
QRect::isValid
bool isValid() const
KDevelop::VcsAnnotationItemDelegate::sizeHint
QSize sizeHint(const KTextEditor::StyleOptionAnnotationItem &option, KTextEditor::AnnotationModel *model, int line) const override
Definition: vcsannotationitemdelegate.cpp:360
KDevelop::VcsAnnotationLine::commitMessage
QString commitMessage() const
Definition: vcsannotation.cpp:121
QPainter::restore
void restore()
QRect::width
int width() const
QApplication::style
QStyle * style()
QRect::setRect
void setRect(int x, int y, int width, int height)
QRectF
QPainter::fontMetrics
QFontMetrics fontMetrics() const
QFontMetricsF::averageCharWidth
qreal averageCharWidth() const
QDate::currentDate
QDate currentDate()
QRect::adjust
void adjust(int dx1, int dy1, int dx2, int dy2)
KDevelop::VcsAnnotationItemDelegate::helpEvent
bool helpEvent(QHelpEvent *event, KTextEditor::View *view, const KTextEditor::StyleOptionAnnotationItem &option, KTextEditor::AnnotationModel *model, int line) override
Definition: vcsannotationitemdelegate.cpp:328
QVariant::isValid
bool isValid() const
KDevelop::VcsAnnotationItemDelegate::paint
void paint(QPainter *painter, const KTextEditor::StyleOptionAnnotationItem &option, KTextEditor::AnnotationModel *model, int line) const override
Definition: vcsannotationitemdelegate.cpp:249
QHelpEvent
QString::data
QChar * data()
QVariant::toString
QString toString() const
QFontMetricsF::height
qreal height() const
KDevelop::qHash
uint qHash(const KDevelop::VcsLocation &loc)
Definition: vcslocation.h:141
QVariant
QFontMetricsF
QRect::rect
void rect(int *x, int *y, int *width, int *height) const
This file is part of the KDE documentation.
Documentation copyright © 1996-2019 The KDE developers.
Generated on Fri Dec 6 2019 04:52:21 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

kdevelop/kdevplatform/vcs

Skip menu "kdevelop/kdevplatform/vcs"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdevelop API Reference

Skip menu "kdevelop API Reference"
  •   kdevplatform
  •     debugger
  •     documentation
  •     interfaces
  •     language
  •       assistant
  •       backgroundparser
  •       checks
  •       classmodel
  •       codecompletion
  •       codegen
  •       duchain
  •       editor
  •       highlighting
  •       interfaces
  •       util
  •     outputview
  •     project
  •     serialization
  •     shell
  •     sublime
  •     tests
  •     util
  •     vcs

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal