blob: e1c4a85aeae58bb505d78cf0f1d7c7e36136d544 [file] [log] [blame]
Andreas Traczykb8b13ba2018-08-21 16:30:16 -04001/***************************************************************************
2 * Copyright (C) 2015-2017 by Savoir-faire Linux *
3 * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>*
4 * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 3 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
18 **************************************************************************/
19
20#include "conversationitemdelegate.h"
21
22#include <QApplication>
23#include <QPainter>
24#include <QPixmap>
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040025
26// Client
27#include "smartlistmodel.h"
28#include "ringthemeutils.h"
29#include "utils.h"
Andreas Traczyk43c08232018-10-31 13:42:09 -040030#include "lrcinstance.h"
Andreas Traczyk29650142019-01-03 20:33:56 -050031#include "mainwindow.h"
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040032
33#include <ciso646>
34
Andreas Traczyk43c08232018-10-31 13:42:09 -040035ConversationItemDelegate::ConversationItemDelegate(QObject* parent)
36 : QItemDelegate(parent)
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040037{
38}
39
40void
41ConversationItemDelegate::paint(QPainter* painter
42 , const QStyleOptionViewItem& option
43 , const QModelIndex& index
44 ) const
45{
46 QStyleOptionViewItem opt(option);
Andreas Traczyk43c08232018-10-31 13:42:09 -040047 painter->setRenderHint(QPainter::Antialiasing, true);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040048
49 // Not having focus removes dotted lines around the item
50 if (opt.state & QStyle::State_HasFocus)
51 opt.state ^= QStyle::State_HasFocus;
52
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040053 auto isContextMenuOpen = index.data(static_cast<int>(SmartListModel::Role::ContextMenuOpen)).value<bool>();
54 bool selected = false;
55 if (option.state & QStyle::State_Selected) {
56 selected = true;
57 opt.state ^= QStyle::State_Selected;
58 } else if (!isContextMenuOpen) {
Andreas Traczyk912242e2018-10-29 14:44:44 -040059 highlightMap_[index.row()] = option.state & QStyle::State_MouseOver;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040060 }
61
62 // One does not simply keep the highlighted state drawn when the context
63 // menu is openÂ…
Andreas Traczykdc2703e2019-01-17 18:02:31 -050064 QColor presenceBorderColor = Qt::white;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040065 auto rowHighlight = highlightMap_.find(index.row());
66 if (selected) {
Andreas Traczyk6b5ad3e2019-01-02 17:04:36 -050067 painter->fillRect(option.rect, RingTheme::smartlistSelection_);
Andreas Traczykdc2703e2019-01-17 18:02:31 -050068 presenceBorderColor = RingTheme::smartlistSelection_;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040069 } else if (rowHighlight != highlightMap_.end() && (*rowHighlight).second) {
Andreas Traczyk6b5ad3e2019-01-02 17:04:36 -050070 painter->fillRect(option.rect, RingTheme::smartlistHighlight_);
Andreas Traczykdc2703e2019-01-17 18:02:31 -050071 presenceBorderColor = RingTheme::smartlistHighlight_;
Andreas Traczyk6b5ad3e2019-01-02 17:04:36 -050072 }
73 auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
74 auto conversation = Utils::getConversationFromUid(convUid, *LRCInstance::getCurrentConversationModel());
75 if (LRCInstance::getCurrentCallModel()->hasCall(conversation->callId)) {
Andreas Traczykdc2703e2019-01-17 18:02:31 -050076 auto color = QColor(RingTheme::blue_.lighter(180));
77 presenceBorderColor = color;
78 color.setAlpha(128);
Andreas Traczyk6b5ad3e2019-01-02 17:04:36 -050079 painter->fillRect(option.rect, color);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040080 }
81
82 QRect &rect = opt.rect;
83
84 // Avatar drawing
85 opt.decorationSize = QSize(sizeImage_, sizeImage_);
86 opt.decorationPosition = QStyleOptionViewItem::Left;
87 opt.decorationAlignment = Qt::AlignCenter;
88
89 QRect rectAvatar(dx_ + rect.left(), rect.top() + dy_, sizeImage_, sizeImage_);
90 drawDecoration(painter, opt, rectAvatar,
91 QPixmap::fromImage(index.data(Qt::DecorationRole).value<QImage>())
92 .scaled(sizeImage_, sizeImage_, Qt::KeepAspectRatio, Qt::SmoothTransformation));
93
94 QFont font(painter->font());
95
96 // If there's unread messages, a message count is displayed
97 if (auto messageCount = index.data(static_cast<int>(SmartListModel::Role::UnreadMessagesCount)).toInt()) {
98 QString messageCountText = (messageCount > 9) ? "9+" : QString::number(messageCount);
99 qreal fontSize = messageCountText.count() > 1 ? 7 : 8;
100 font.setPointSize(fontSize);
101
102 // ellipse
103 QPainterPath ellipse;
104 qreal ellipseHeight = sizeImage_ / 6;
105 qreal ellipseWidth = ellipseHeight;
Andreas Traczykdc2703e2019-01-17 18:02:31 -0500106 QPointF ellipseCenter(rectAvatar.right() - ellipseWidth + 1, rectAvatar.top() + ellipseHeight + 1);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400107 QRect ellipseRect(ellipseCenter.x() - ellipseWidth, ellipseCenter.y() - ellipseHeight,
108 ellipseWidth * 2, ellipseHeight * 2);
109 ellipse.addRoundedRect(ellipseRect, ellipseWidth, ellipseHeight);
110 painter->fillPath(ellipse, RingTheme::notificationRed_);
111
112 // text
113 painter->setPen(Qt::white);
114 painter->setOpacity(1);
115 painter->setFont(font);
116 ellipseRect.setTop(ellipseRect.top() - 2);
117 painter->drawText(ellipseRect, Qt::AlignCenter, messageCountText);
118 }
119
120 // Presence indicator
121 if (index.data(static_cast<int>(SmartListModel::Role::Presence)).value<bool>()) {
122 qreal radius = sizeImage_ / 6;
123 QPainterPath outerCircle, innerCircle;
Andreas Traczykdc2703e2019-01-17 18:02:31 -0500124 QPointF center(rectAvatar.right() - radius + 2, (rectAvatar.bottom() - radius) + 1 + 2);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400125 qreal outerCRadius = radius;
126 qreal innerCRadius = outerCRadius * 0.75;
127 outerCircle.addEllipse(center, outerCRadius, outerCRadius);
128 innerCircle.addEllipse(center, innerCRadius, innerCRadius);
Andreas Traczykdc2703e2019-01-17 18:02:31 -0500129 painter->fillPath(outerCircle, presenceBorderColor);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400130 painter->fillPath(innerCircle, RingTheme::presenceGreen_);
131 }
132
133 using namespace lrc::api;
134 auto type = Utils::toEnum<profile::Type>(
Andreas Traczyk29650142019-01-03 20:33:56 -0500135 index.data(static_cast<int>(SmartListModel::Role::ContactType)).value<int>()
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400136 );
137 switch (type) {
138 case profile::Type::RING:
139 case profile::Type::TEMPORARY:
140 paintRingConversationItem(painter, option, rect, index);
141 break;
142 case profile::Type::PENDING:
143 paintRingInviteConversationItem(painter, option, rect, index);
144 break;
145 case profile::Type::SIP:
146 break;
147 default:
148 paintRingConversationItem(painter, option, rect, index);
149 break;
150 }
151}
152
153QSize
154ConversationItemDelegate::sizeHint(const QStyleOptionViewItem& option,
Andreas Traczyk43c08232018-10-31 13:42:09 -0400155 const QModelIndex& index) const
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400156{
Andreas Traczyk43c08232018-10-31 13:42:09 -0400157 Q_UNUSED(option);
158 Q_UNUSED(index);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400159 return QSize(0, cellHeight_);
160}
161
162void
163ConversationItemDelegate::paintRingConversationItem(QPainter* painter,
164 const QStyleOptionViewItem& option,
165 const QRect& rect,
166 const QModelIndex& index) const
167{
Andreas Traczyk43c08232018-10-31 13:42:09 -0400168 Q_UNUSED(option);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400169 QFont font(painter->font());
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400170 QPen pen(painter->pen());
171 painter->setPen(pen);
172
Andreas Traczyk29650142019-01-03 20:33:56 -0500173 int infoTextWidthModifier = 0;
Andreas Traczyk46508b52019-01-04 11:49:24 -0500174 int infoText2HeightModifier = 0;
Andreas Traczyk29650142019-01-03 20:33:56 -0500175 auto scalingRatio = MainWindow::instance().getCurrentScalingRatio();
176 if (scalingRatio > 1.0) {
Andreas Traczyk46508b52019-01-04 11:49:24 -0500177 font.setPointSize(fontSize_ - 2);
Andreas Traczyk29650142019-01-03 20:33:56 -0500178 infoTextWidthModifier = 12;
Andreas Traczyk46508b52019-01-04 11:49:24 -0500179 infoText2HeightModifier = 2;
180 } else {
181 font.setPointSize(fontSize_);
Andreas Traczyk59ba48a2019-01-04 16:12:03 -0500182 infoTextWidthModifier = 10;
Andreas Traczyk29650142019-01-03 20:33:56 -0500183 }
184
185 auto leftMargin = dx_ + sizeImage_ + dx_;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400186 auto rightMargin = dx_;
Andreas Traczyk29650142019-01-03 20:33:56 -0500187 auto topMargin = 4;
188 auto bottomMargin = 8;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400189
190 QRect rectName1(rect.left() + leftMargin,
191 rect.top() + topMargin,
Andreas Traczykad97faa2019-01-18 13:45:31 -0500192 rect.width() - leftMargin - infoTextWidth_ - infoTextWidthModifier - 4,
Andreas Traczyk29650142019-01-03 20:33:56 -0500193 rect.height() / 2 - 2);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400194
195 QRect rectName2(rectName1.left(),
Andreas Traczyk2771e2e2019-01-08 17:44:33 -0500196 rectName1.top() + rectName1.height() - infoText2HeightModifier,
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400197 rectName1.width(),
Andreas Traczyk2771e2e2019-01-08 17:44:33 -0500198 rectName1.height() - bottomMargin + infoText2HeightModifier);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400199
200 QRect rectInfo1(rectName1.left() + rectName1.width(),
201 rect.top() + topMargin,
Andreas Traczykad97faa2019-01-18 13:45:31 -0500202 infoTextWidth_ - rightMargin + infoTextWidthModifier + 2,
Andreas Traczyk29650142019-01-03 20:33:56 -0500203 rect.height() / 2 - 2);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400204
205 QRect rectInfo2(rectInfo1.left(),
Andreas Traczyk46508b52019-01-04 11:49:24 -0500206 rectInfo1.top() + rectInfo1.height() - infoText2HeightModifier,
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400207 rectInfo1.width(),
Andreas Traczyk46508b52019-01-04 11:49:24 -0500208 rectInfo1.height() - bottomMargin + infoText2HeightModifier);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400209
210 QFontMetrics fontMetrics(font);
211
212 // The name is displayed at the avatar's right
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500213 QString nameStr = index.data(static_cast<int>(SmartListModel::Role::DisplayName)).value<QString>();
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400214 if (!nameStr.isNull()) {
215 font.setItalic(false);
Andreas Traczyk29650142019-01-03 20:33:56 -0500216 font.setBold(false);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400217 pen.setColor(RingTheme::lightBlack_);
218 painter->setPen(pen);
219 painter->setFont(font);
220 QString elidedNameStr = fontMetrics.elidedText(nameStr, Qt::ElideRight, rectName1.width());
221 painter->drawText(rectName1, Qt::AlignVCenter | Qt::AlignLeft, elidedNameStr);
222 }
223
224 // Display the ID under the name
225 QString idStr = index.data(static_cast<int>(SmartListModel::Role::DisplayID)).value<QString>();
226 if (idStr != nameStr && !idStr.isNull()) {
227 font.setItalic(false);
228 font.setBold(false);
229 pen.setColor(RingTheme::grey_);
230 painter->setPen(pen);
231 painter->setFont(font);
232 idStr = fontMetrics.elidedText(idStr, Qt::ElideRight, rectName2.width());
233 painter->drawText(rectName2, Qt::AlignVCenter | Qt::AlignLeft, idStr);
234 }
235
236 // top-right: last interaction date/time
237 QString lastUsedStr = index.data(static_cast<int>(SmartListModel::Role::LastInteractionDate)).value<QString>();
238 if (!lastUsedStr.isNull()) {
239 font.setItalic(false);
240 font.setBold(false);
241 pen.setColor(RingTheme::grey_);
242 painter->setPen(pen);
243 painter->setFont(font);
244 lastUsedStr = fontMetrics.elidedText(lastUsedStr, Qt::ElideRight, rectInfo1.width());
245 painter->drawText(rectInfo1, Qt::AlignVCenter | Qt::AlignRight, lastUsedStr);
246 }
247
248 // bottom-right: last interaction snippet
249 QString interactionStr = index.data(static_cast<int>(SmartListModel::Role::LastInteraction)).value<QString>();
250 if (!interactionStr.isNull()) {
Andreas Traczyk43c08232018-10-31 13:42:09 -0400251 painter->save();
Andreas Traczyk46508b52019-01-04 11:49:24 -0500252 font.setWeight(QFont::ExtraLight);
Andreas Traczyk43c08232018-10-31 13:42:09 -0400253 interactionStr = interactionStr.simplified();
254 auto type = Utils::toEnum<lrc::api::interaction::Type>(index
255 .data(static_cast<int>(SmartListModel::Role::LastInteractionType))
256 .value<int>());
257 if (type == lrc::api::interaction::Type::CALL ||
258 type == lrc::api::interaction::Type::CONTACT) {
259 font.setItalic(false);
260 font.setBold(false);
261 pen.setColor(RingTheme::grey_.darker(140));
262 painter->setPen(pen);
263 painter->setFont(font);
264 // strip emojis if it's a call/contact type message
265 VectorUInt emojiless;
266 for (auto unicode : interactionStr.toUcs4()) {
267 if (!(unicode >= 0x1F000 && unicode <= 0x1FFFF)) {
268 emojiless.push_back(unicode);
269 }
270 }
271 interactionStr = QString::fromUcs4(&emojiless.at(0), emojiless.size());
Andreas Traczykdc2703e2019-01-17 18:02:31 -0500272 // remove everythin after 'call'
273 auto indexOfCallStr = interactionStr.lastIndexOf(QString("call"), -1, Qt::CaseInsensitive);
274 if (indexOfCallStr != -1) {
275 interactionStr = interactionStr.left(indexOfCallStr + 4);
276 }
Andreas Traczyk43c08232018-10-31 13:42:09 -0400277 } else {
278 QFont emojiMsgFont(QStringLiteral("Segoe UI Emoji"));
279 emojiMsgFont.setItalic(false);
280 emojiMsgFont.setBold(false);
Andreas Traczyk46508b52019-01-04 11:49:24 -0500281 emojiMsgFont.setPointSize(scalingRatio > 1.0 ? fontSize_ - 2 : fontSize_);
Andreas Traczyk2771e2e2019-01-08 17:44:33 -0500282 rectInfo2.setTop(rectInfo2.top() - 6);
Andreas Traczyk43c08232018-10-31 13:42:09 -0400283 painter->setOpacity(0.7);
284 painter->setFont(emojiMsgFont);
285 }
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400286 interactionStr = fontMetrics.elidedText(interactionStr, Qt::ElideRight, rectInfo2.width());
287 painter->drawText(rectInfo2, Qt::AlignVCenter | Qt::AlignRight, interactionStr);
Andreas Traczyk43c08232018-10-31 13:42:09 -0400288 painter->restore();
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400289 }
290}
291
292void
293ConversationItemDelegate::paintRingInviteConversationItem(QPainter* painter,
294 const QStyleOptionViewItem& option,
295 const QRect& rect,
296 const QModelIndex& index) const
297{
298 QFont font(painter->font());
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400299 QPen pen(painter->pen());
300 painter->setPen(pen);
301
Andreas Traczyk29650142019-01-03 20:33:56 -0500302 auto leftMargin = dx_ + sizeImage_ + dx_;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400303 auto rightMargin = dx_;
Andreas Traczyk45d83bd2019-02-05 15:45:34 -0500304
305 auto scalingRatio = MainWindow::instance().getCurrentScalingRatio();
306 font.setPointSize(scalingRatio > 1.0 ? fontSize_ - 2 : fontSize_);
307
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400308 if (option.state & QStyle::State_MouseOver) {
Andreas Traczykad97faa2019-01-18 13:45:31 -0500309 if (scalingRatio > 1.0) {
310 rightMargin = infoTextWidth_ + 12 - dx_ * 2;
311 } else {
312 rightMargin = infoTextWidth_ - dx_ * 2;
313 }
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400314 }
Andreas Traczyk45d83bd2019-02-05 15:45:34 -0500315
Andreas Traczyk29650142019-01-03 20:33:56 -0500316 auto topMargin = 4;
317 auto bottomMargin = 8;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400318
319 QRect rectName1(rect.left() + leftMargin,
320 rect.top() + topMargin,
321 rect.width() - leftMargin - rightMargin,
Andreas Traczyk29650142019-01-03 20:33:56 -0500322 rect.height() / 2 - 2);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400323
324 QRect rectName2(rectName1.left(),
325 rectName1.top() + rectName1.height(),
326 rectName1.width(),
327 rectName1.height() - bottomMargin);
328
329 QFontMetrics fontMetrics(font);
330
331 // The name is displayed at the avatar's right
Andreas Traczyk43c08232018-10-31 13:42:09 -0400332 QString nameStr = index.data(static_cast<int>(SmartListModel::Role::DisplayName)).value<QString>();
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400333 if (!nameStr.isNull()) {
334 font.setItalic(false);
Andreas Traczyk29650142019-01-03 20:33:56 -0500335 font.setBold(false);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400336 pen.setColor(RingTheme::lightBlack_);
337 painter->setPen(pen);
338 painter->setFont(font);
339 QString elidedNameStr = fontMetrics.elidedText(nameStr, Qt::ElideRight, rectName1.width());
340 painter->drawText(rectName1, Qt::AlignVCenter | Qt::AlignLeft, elidedNameStr);
341 }
342
343 // Display the ID under the name
344 QString idStr = index.data(static_cast<int>(SmartListModel::Role::DisplayID)).value<QString>();
345 if (idStr != nameStr && !idStr.isNull()) {
346 font.setItalic(false);
347 font.setBold(false);
348 pen.setColor(RingTheme::grey_);
349 painter->setPen(pen);
350 painter->setFont(font);
351 idStr = fontMetrics.elidedText(idStr, Qt::ElideRight, rectName2.width());
352 painter->drawText(rectName2, Qt::AlignVCenter | Qt::AlignLeft, idStr);
353 }
354}
355
356void
Andreas Traczyk43c08232018-10-31 13:42:09 -0400357ConversationItemDelegate::paintSIPConversationItem(QPainter* painter,
358 const QStyleOptionViewItem& option,
359 const QModelIndex& index) const
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400360{
Andreas Traczyk43c08232018-10-31 13:42:09 -0400361 Q_UNUSED(painter);
362 Q_UNUSED(option);
363 Q_UNUSED(index);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400364}