1 | /* |
2 | This file is part of the KDE libraries |
3 | SPDX-FileCopyrightText: 2010 Teo Mrnjavac <teo@kde.org> |
4 | |
5 | SPDX-License-Identifier: LGPL-2.1-or-later |
6 | */ |
7 | |
8 | #include "kaboutapplicationpersonlistdelegate_p.h" |
9 | |
10 | #include "kaboutapplicationlistview_p.h" |
11 | #include "kaboutapplicationpersonmodel_p.h" |
12 | #include "ktoolbar.h" |
13 | |
14 | #include <KLocalizedString> |
15 | |
16 | #include <QAction> |
17 | #include <QApplication> |
18 | #include <QDesktopServices> |
19 | #include <QLabel> |
20 | #include <QPainter> |
21 | #include <QStandardPaths> |
22 | |
23 | namespace KDEPrivate |
24 | { |
25 | Q_GLOBAL_STATIC(QPixmap, s_avatarFallback) |
26 | static QPixmap avatarFallback() |
27 | { |
28 | if (s_avatarFallback->isNull()) { |
29 | const QIcon icon = QIcon::fromTheme(QStringLiteral("user" )); |
30 | *s_avatarFallback = icon.pixmap(size: icon.actualSize(size: QSize(AVATAR_WIDTH / qGuiApp->devicePixelRatio(), AVATAR_HEIGHT / qGuiApp->devicePixelRatio())), |
31 | mode: QIcon::Normal, |
32 | state: QIcon::On); |
33 | } |
34 | return *s_avatarFallback; |
35 | } |
36 | |
37 | KAboutApplicationPersonListDelegate::KAboutApplicationPersonListDelegate(QAbstractItemView *itemView, QObject *parent) |
38 | : KWidgetItemDelegate(itemView, parent) |
39 | { |
40 | } |
41 | |
42 | QList<QWidget *> KAboutApplicationPersonListDelegate::createItemWidgets(const QModelIndex &index) const |
43 | { |
44 | Q_UNUSED(index); |
45 | QList<QWidget *> list; |
46 | |
47 | QLabel *textLabel = new QLabel(itemView()); |
48 | list.append(t: textLabel); |
49 | |
50 | KToolBar *mainLinks = new KToolBar(itemView(), false, false); |
51 | |
52 | QAction *emailAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-send" )), // |
53 | i18nc("Action to send an email to a contributor" , "Email contributor" ), |
54 | mainLinks); |
55 | emailAction->setVisible(false); |
56 | mainLinks->addAction(action: emailAction); |
57 | QAction *homepageAction = new QAction(QIcon::fromTheme(QStringLiteral("internet-services" )), // |
58 | i18n("Visit contributor's homepage" ), |
59 | mainLinks); |
60 | homepageAction->setVisible(false); |
61 | mainLinks->addAction(action: homepageAction); |
62 | QAction *visitProfileAction = new QAction(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff" )), QString(), mainLinks); |
63 | visitProfileAction->setVisible(false); |
64 | mainLinks->addAction(action: visitProfileAction); |
65 | |
66 | list.append(t: mainLinks); |
67 | |
68 | connect(sender: mainLinks, signal: &QToolBar::actionTriggered, context: this, slot: &KAboutApplicationPersonListDelegate::launchUrl); |
69 | |
70 | return list; |
71 | } |
72 | |
73 | void KAboutApplicationPersonListDelegate::updateItemWidgets(const QList<QWidget *> &widgets, |
74 | const QStyleOptionViewItem &option, |
75 | const QPersistentModelIndex &index) const |
76 | { |
77 | const int margin = option.fontMetrics.height() / 2; |
78 | |
79 | KAboutApplicationPersonProfile profile = index.data().value<KAboutApplicationPersonProfile>(); |
80 | |
81 | QRect wRect = widgetsRect(option, index); |
82 | |
83 | // Let's fill in the text first... |
84 | QLabel *label = qobject_cast<QLabel *>(object: widgets.at(i: TextLabel)); |
85 | label->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Fixed); |
86 | |
87 | QString text = buildTextForProfile(profile); |
88 | |
89 | label->move(ax: wRect.left(), ay: wRect.top()); |
90 | label->resize(w: wRect.width(), h: heightForString(string: text, lineWidth: wRect.width() - margin, option) + margin); |
91 | label->setWordWrap(true); |
92 | label->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0); |
93 | label->setAlignment(Qt::AlignBottom | Qt::AlignLeft); |
94 | label->setForegroundRole(QPalette::WindowText); |
95 | |
96 | label->setText(text); |
97 | |
98 | // And now we fill in the main links (email + homepage) |
99 | KToolBar *mainLinks = qobject_cast<KToolBar *>(object: widgets.at(i: MainLinks)); |
100 | mainLinks->setIconSize(QSize(22, 22)); |
101 | mainLinks->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0); |
102 | mainLinks->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Fixed); |
103 | QAction *action; |
104 | if (!profile.email().isEmpty()) { |
105 | action = mainLinks->actions().at(i: EmailAction); |
106 | action->setToolTip(i18nc("@info:tooltip Action to send an email to a contributor" , "Email contributor\n%1" , profile.email())); |
107 | action->setData(QString(QLatin1String("mailto:" ) + profile.email())); |
108 | action->setVisible(true); |
109 | } |
110 | if (!profile.homepage().isEmpty()) { |
111 | action = mainLinks->actions().at(i: HomepageAction); |
112 | action->setToolTip(i18nc("@info:tooltip" , "Visit contributor's homepage\n%1" , profile.homepage().toString())); |
113 | action->setData(profile.homepage().toString()); |
114 | action->setVisible(true); |
115 | } |
116 | mainLinks->resize(QSize(mainLinks->sizeHint().width(), MAIN_LINKS_HEIGHT)); |
117 | mainLinks->move(ax: wRect.left(), ay: wRect.top() + label->height()); |
118 | itemView()->reset(); |
119 | } |
120 | |
121 | QSize KAboutApplicationPersonListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const |
122 | { |
123 | int margin = option.fontMetrics.height() / 2; |
124 | |
125 | int height = qMax(a: widgetsRect(option, index).height(), b: AVATAR_HEIGHT + 2 * margin); |
126 | |
127 | QSize metrics(option.fontMetrics.height() * 7, height); |
128 | return metrics; |
129 | } |
130 | |
131 | void KAboutApplicationPersonListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const |
132 | { |
133 | int margin = option.fontMetrics.height() / 2; |
134 | |
135 | QStyle *style = QApplication::style(); |
136 | style->drawPrimitive(pe: QStyle::PE_Widget, opt: &option, p: painter, w: nullptr); |
137 | |
138 | const KAboutApplicationPersonModel *model = qobject_cast<const KAboutApplicationPersonModel *>(object: index.model()); |
139 | |
140 | if (model->showRemoteAvatars() && model->hasAvatarPixmaps()) { |
141 | int height = qMax(a: widgetsRect(option, index).height(), b: AVATAR_HEIGHT + 2 * margin); |
142 | QPoint point(option.rect.left() + 2 * margin, // |
143 | option.rect.top() + ((height - AVATAR_HEIGHT) / 2)); |
144 | |
145 | KAboutApplicationPersonProfile profile = index.data().value<KAboutApplicationPersonProfile>(); |
146 | |
147 | QPixmap fallback; |
148 | if (profile.avatar().isNull()) { |
149 | fallback = avatarFallback(); |
150 | fallback.setDevicePixelRatio(itemView()->devicePixelRatio()); |
151 | } |
152 | const QPixmap &pixmap = profile.avatar().isNull() ? fallback : profile.avatar(); |
153 | point.setX((AVATAR_WIDTH - pixmap.width()) / 2 + 5); |
154 | point.setY(option.rect.top() + ((height - pixmap.height()) / 2)); |
155 | painter->drawPixmap(p: point, pm: pixmap); |
156 | |
157 | QPoint framePoint(point.x() - 5, point.y() - 5); |
158 | QPixmap framePixmap(QStringLiteral(":/kxmlgui5/thumb_frame.png" )); |
159 | painter->drawPixmap(p: framePoint, pm: framePixmap.scaled(w: pixmap.width() + 10, h: pixmap.height() + 10)); |
160 | } |
161 | } |
162 | |
163 | void KAboutApplicationPersonListDelegate::launchUrl(QAction *action) const |
164 | { |
165 | QString url = action->data().toString(); |
166 | if (!url.isEmpty()) { |
167 | QDesktopServices::openUrl(url: QUrl(url)); |
168 | } |
169 | } |
170 | |
171 | int KAboutApplicationPersonListDelegate::heightForString(const QString &string, int lineWidth, const QStyleOptionViewItem &option) const |
172 | { |
173 | QFontMetrics fm = option.fontMetrics; |
174 | constexpr auto opts = Qt::AlignLeft | Qt::AlignBottom | Qt::TextWordWrap; |
175 | QRect boundingRect = fm.boundingRect(x: 0, y: 0, w: lineWidth, h: 9999, flags: opts, text: string); |
176 | return boundingRect.height(); |
177 | } |
178 | |
179 | QString KAboutApplicationPersonListDelegate::buildTextForProfile(const KAboutApplicationPersonProfile &profile) const |
180 | { |
181 | QString text = QLatin1String("<b>" ) + i18nc("@item Contributor name in about dialog." , "%1" , profile.name()) + QLatin1String("</b>" ); |
182 | |
183 | if (!profile.task().isEmpty()) { |
184 | text += QLatin1String("\n<br><i>%1</i>" ).arg(args: profile.task()); |
185 | } |
186 | |
187 | if (!profile.location().isEmpty()) { |
188 | text += QLatin1String("\n<br>" ) + profile.location(); |
189 | } |
190 | return text; |
191 | } |
192 | |
193 | QRect KAboutApplicationPersonListDelegate::widgetsRect(const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const |
194 | { |
195 | KAboutApplicationPersonProfile profile = index.data().value<KAboutApplicationPersonProfile>(); |
196 | int margin = option.fontMetrics.height() / 2; |
197 | |
198 | QRect widgetsRect; |
199 | if (qobject_cast<const KAboutApplicationPersonModel *>(object: index.model())->showRemoteAvatars()) { |
200 | widgetsRect = QRect(option.rect.left() + AVATAR_WIDTH + 3 * margin, // |
201 | margin / 2, // |
202 | option.rect.width() - AVATAR_WIDTH - 4 * margin, // |
203 | 0); |
204 | } else { |
205 | widgetsRect = QRect(option.rect.left() + margin, // |
206 | margin / 2, // |
207 | option.rect.width() - 2 * margin, // |
208 | 0); |
209 | } |
210 | |
211 | int textHeight = heightForString(string: buildTextForProfile(profile), lineWidth: widgetsRect.width() - margin, option); |
212 | widgetsRect.setHeight(textHeight + MAIN_LINKS_HEIGHT + 1.5 * margin); |
213 | |
214 | return widgetsRect; |
215 | } |
216 | |
217 | } // namespace KDEPrivate |
218 | |
219 | #include "moc_kaboutapplicationpersonlistdelegate_p.cpp" |
220 | |