File indexing completed on 2024-05-12 04:38:54

0001 /*
0002     SPDX-FileCopyrightText: 2007 Andreas Pakulat <apaku@gmx.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "vcsannotationmodel.h"
0008 
0009 #include "../vcsannotation.h"
0010 #include "../vcsrevision.h"
0011 #include "../vcsjob.h"
0012 
0013 #include <QDateTime>
0014 #include <QBrush>
0015 #include <QPen>
0016 #include <QHash>
0017 #include <QLocale>
0018 #include <QUrl>
0019 #include <QApplication>
0020 
0021 #include <KLocalizedString>
0022 
0023 #include <interfaces/icore.h>
0024 #include <interfaces/iruncontroller.h>
0025 
0026 namespace KDevelop
0027 {
0028 
0029 class VcsAnnotationModelPrivate
0030 {
0031 public:
0032     explicit VcsAnnotationModelPrivate( VcsAnnotationModel* q_ ) : q(q_) {}
0033     KDevelop::VcsAnnotation m_annotation;
0034     mutable QHash<KDevelop::VcsRevision, QBrush> m_brushes;
0035     VcsAnnotationModel* q;
0036     VcsJob* job;
0037     QColor foreground;
0038     QColor background;
0039 
0040     const QBrush& brush(const VcsRevision& revision) const
0041     {
0042         auto brushIt = m_brushes.find(revision);
0043         if (brushIt == m_brushes.end()) {
0044             const int background_y = background.red()*0.299 + 0.587*background.green()
0045                                                             + 0.114*background.blue();
0046             // get random, but reproducible 8-bit values from last two bytes of the revision hash
0047             const uint revisionHash = qHash(revision);
0048             const int u = static_cast<int>((0xFF & revisionHash));
0049             const int v = static_cast<int>((0xFF00 & revisionHash) >> 8);
0050             const int r = qRound(qMin(255.0, qMax(0.0, background_y + 1.402*(v-128))));
0051             const int g = qRound(qMin(255.0, qMax(0.0, background_y - 0.344*(u-128) - 0.714*(v-128))));
0052             const int b = qRound(qMin(255.0, qMax(0.0, background_y + 1.772*(u-128))));
0053             brushIt = m_brushes.insert(revision, QBrush(QColor(r, g, b)));
0054         }
0055         return brushIt.value();
0056     }
0057 
0058     void addLines( KDevelop::VcsJob* job )
0059     {
0060         if( job == this->job )
0061         {
0062             const auto results = job->fetchResults().toList();
0063             for (const QVariant& v : results) {
0064                 if( v.canConvert<KDevelop::VcsAnnotationLine>() )
0065                 {
0066                     VcsAnnotationLine l = v.value<KDevelop::VcsAnnotationLine>();
0067                     m_annotation.insertLine( l.lineNumber(), l );
0068                     emit q->lineChanged( l.lineNumber() );
0069                 }
0070             }
0071         }
0072     }
0073 };
0074 
0075 VcsAnnotationModel::VcsAnnotationModel(VcsJob *job, const QUrl& url, QObject* parent,
0076                                        const QColor &foreground, const QColor &background)
0077     : d_ptr(new VcsAnnotationModelPrivate(this))
0078 {
0079     Q_D(VcsAnnotationModel);
0080 
0081     setParent( parent );
0082     d->m_annotation.setLocation( url );
0083     d->job = job;
0084     d->foreground = foreground;
0085     d->background = background;
0086     connect( d->job, &VcsJob::resultsReady,this, [this] (VcsJob* job) { Q_D(VcsAnnotationModel); d->addLines(job); } );
0087     ICore::self()->runController()->registerJob( d->job );
0088 }
0089 
0090 VcsAnnotationModel::~VcsAnnotationModel() = default;
0091 
0092 static QString abbreviateLastName(const QString& author) {
0093     auto parts = author.split(QLatin1Char(' '));
0094     bool onlyOneFragment = parts.size() == 1 || ( parts.size() == 2 && parts.at(1).isEmpty() );
0095     return onlyOneFragment ? parts.first() : parts.first() + QStringLiteral(" %1.").arg(parts.last()[0]);
0096 }
0097 
0098 static QString annotationToolTip(const VcsAnnotationLine& aline)
0099 {
0100     const bool textIsLeftToRight = (QApplication::layoutDirection() == Qt::LeftToRight);
0101 
0102     const QString boldStyle = QStringLiteral(";font-weight:bold");
0103     const QString one = QStringLiteral("1");
0104     const QString two = QStringLiteral("2");
0105     const QString line = QStringLiteral(
0106         "<tr>"
0107           "<td align=\"right\" style=\"white-space:nowrap%1\">%%2</td>"
0108           "<td align=\"left\" style=\"white-space:nowrap%3\">%%4</td>"
0109         "</tr>").arg(
0110             (textIsLeftToRight ? boldStyle : QString()),
0111             (textIsLeftToRight ? one : two),
0112             (textIsLeftToRight ? QString() : boldStyle),
0113             (textIsLeftToRight ? two : one)
0114         );
0115 
0116     const QString authorLabel = i18n("Author:").toHtmlEscaped();
0117     const QString dateLabel = i18n("Date:").toHtmlEscaped();
0118     const QString messageLabel = i18n("Commit message:").toHtmlEscaped();
0119 
0120     const QString author = aline.author().toHtmlEscaped();
0121     const QString date = QLocale().toString(aline.date()).toHtmlEscaped();
0122     const QString message = aline.commitMessage().toHtmlEscaped().replace(QLatin1Char('\n'), QLatin1String("<br/>"));
0123 
0124     return
0125         QLatin1String("<table>") +
0126         line.arg(authorLabel, author) +
0127         line.arg(dateLabel, date) +
0128         line.arg(messageLabel, message) +
0129         QLatin1String("</table>");
0130 }
0131 
0132 QVariant VcsAnnotationModel::data( int line, Qt::ItemDataRole role ) const
0133 {
0134     Q_D(const VcsAnnotationModel);
0135 
0136     if( line < 0 || !d->m_annotation.containsLine( line ) )
0137     {
0138         return QVariant();
0139     }
0140 
0141     KDevelop::VcsAnnotationLine aline = d->m_annotation.line( line );
0142     if( role == Qt::ForegroundRole )
0143     {
0144         return QVariant(QPen(d->foreground));
0145     }
0146     if( role == Qt::BackgroundRole )
0147     {
0148         return QVariant(d->brush(aline.revision()));
0149     } else if( role == Qt::DisplayRole )
0150     {
0151         return QVariant( QStringLiteral("%1 ").arg(aline.date().date().year()) + abbreviateLastName(aline.author()) );
0152     } else if( role == (int) KTextEditor::AnnotationModel::GroupIdentifierRole )
0153     {
0154         return aline.revision().revisionValue();
0155     } else if( role == Qt::ToolTipRole )
0156     {
0157         return QVariant(annotationToolTip(aline));
0158     }
0159     return QVariant();
0160 }
0161 
0162 VcsRevision VcsAnnotationModel::revisionForLine( int line ) const
0163 {
0164     Q_D(const VcsAnnotationModel);
0165 
0166     ///FIXME: update the annotation bar on edit/reload somehow
0167     ///BUG: https://bugs.kde.org/show_bug.cgi?id=269757
0168     if (!d->m_annotation.containsLine(line)) {
0169         return VcsRevision();
0170     }
0171 
0172     Q_ASSERT(line >= 0 && d->m_annotation.containsLine(line));
0173     return d->m_annotation.line( line ).revision();
0174 }
0175 
0176 VcsAnnotationLine VcsAnnotationModel::annotationLine(int line) const
0177 {
0178     Q_D(const VcsAnnotationModel);
0179 
0180     if (line < 0 || !d->m_annotation.containsLine(line)) {
0181         return VcsAnnotationLine();
0182     }
0183 
0184     return d->m_annotation.line(line);
0185 }
0186 
0187 }
0188 
0189 #include "moc_vcsannotationmodel.cpp"