blob: 8763d103aa9422dd0bc8ddc6f126f5ec16eaf2e4 [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Â…
64 auto rowHighlight = highlightMap_.find(index.row());
65 if (selected) {
Andreas Traczyk6b5ad3e2019-01-02 17:04:36 -050066 painter->fillRect(option.rect, RingTheme::smartlistSelection_);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040067 } else if (rowHighlight != highlightMap_.end() && (*rowHighlight).second) {
Andreas Traczyk6b5ad3e2019-01-02 17:04:36 -050068 painter->fillRect(option.rect, RingTheme::smartlistHighlight_);
69 }
70 auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
71 auto conversation = Utils::getConversationFromUid(convUid, *LRCInstance::getCurrentConversationModel());
72 if (LRCInstance::getCurrentCallModel()->hasCall(conversation->callId)) {
73 auto color = QColor(RingTheme::blue_.lighter(180)); color.setAlpha(128);
74 painter->fillRect(option.rect, color);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040075 }
76
77 QRect &rect = opt.rect;
78
79 // Avatar drawing
80 opt.decorationSize = QSize(sizeImage_, sizeImage_);
81 opt.decorationPosition = QStyleOptionViewItem::Left;
82 opt.decorationAlignment = Qt::AlignCenter;
83
84 QRect rectAvatar(dx_ + rect.left(), rect.top() + dy_, sizeImage_, sizeImage_);
85 drawDecoration(painter, opt, rectAvatar,
86 QPixmap::fromImage(index.data(Qt::DecorationRole).value<QImage>())
87 .scaled(sizeImage_, sizeImage_, Qt::KeepAspectRatio, Qt::SmoothTransformation));
88
89 QFont font(painter->font());
90
91 // If there's unread messages, a message count is displayed
92 if (auto messageCount = index.data(static_cast<int>(SmartListModel::Role::UnreadMessagesCount)).toInt()) {
93 QString messageCountText = (messageCount > 9) ? "9+" : QString::number(messageCount);
94 qreal fontSize = messageCountText.count() > 1 ? 7 : 8;
95 font.setPointSize(fontSize);
96
97 // ellipse
98 QPainterPath ellipse;
99 qreal ellipseHeight = sizeImage_ / 6;
100 qreal ellipseWidth = ellipseHeight;
101 QPointF ellipseCenter(rectAvatar.right() - ellipseWidth, rectAvatar.top() + ellipseHeight + 1);
102 QRect ellipseRect(ellipseCenter.x() - ellipseWidth, ellipseCenter.y() - ellipseHeight,
103 ellipseWidth * 2, ellipseHeight * 2);
104 ellipse.addRoundedRect(ellipseRect, ellipseWidth, ellipseHeight);
105 painter->fillPath(ellipse, RingTheme::notificationRed_);
106
107 // text
108 painter->setPen(Qt::white);
109 painter->setOpacity(1);
110 painter->setFont(font);
111 ellipseRect.setTop(ellipseRect.top() - 2);
112 painter->drawText(ellipseRect, Qt::AlignCenter, messageCountText);
113 }
114
115 // Presence indicator
116 if (index.data(static_cast<int>(SmartListModel::Role::Presence)).value<bool>()) {
117 qreal radius = sizeImage_ / 6;
118 QPainterPath outerCircle, innerCircle;
119 QPointF center(rectAvatar.right() - radius, (rectAvatar.bottom() - radius) + 1);
120 qreal outerCRadius = radius;
121 qreal innerCRadius = outerCRadius * 0.75;
122 outerCircle.addEllipse(center, outerCRadius, outerCRadius);
123 innerCircle.addEllipse(center, innerCRadius, innerCRadius);
124 painter->fillPath(outerCircle, Qt::white);
125 painter->fillPath(innerCircle, RingTheme::presenceGreen_);
126 }
127
128 using namespace lrc::api;
129 auto type = Utils::toEnum<profile::Type>(
Andreas Traczyk29650142019-01-03 20:33:56 -0500130 index.data(static_cast<int>(SmartListModel::Role::ContactType)).value<int>()
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400131 );
132 switch (type) {
133 case profile::Type::RING:
134 case profile::Type::TEMPORARY:
135 paintRingConversationItem(painter, option, rect, index);
136 break;
137 case profile::Type::PENDING:
138 paintRingInviteConversationItem(painter, option, rect, index);
139 break;
140 case profile::Type::SIP:
141 break;
142 default:
143 paintRingConversationItem(painter, option, rect, index);
144 break;
145 }
146}
147
148QSize
149ConversationItemDelegate::sizeHint(const QStyleOptionViewItem& option,
Andreas Traczyk43c08232018-10-31 13:42:09 -0400150 const QModelIndex& index) const
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400151{
Andreas Traczyk43c08232018-10-31 13:42:09 -0400152 Q_UNUSED(option);
153 Q_UNUSED(index);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400154 return QSize(0, cellHeight_);
155}
156
157void
158ConversationItemDelegate::paintRingConversationItem(QPainter* painter,
159 const QStyleOptionViewItem& option,
160 const QRect& rect,
161 const QModelIndex& index) const
162{
Andreas Traczyk43c08232018-10-31 13:42:09 -0400163 Q_UNUSED(option);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400164 QFont font(painter->font());
165 font.setPointSize(fontSize_);
166 QPen pen(painter->pen());
167 painter->setPen(pen);
168
Andreas Traczyk29650142019-01-03 20:33:56 -0500169 int infoTextWidthModifier = 0;
170 auto scalingRatio = MainWindow::instance().getCurrentScalingRatio();
171 if (scalingRatio > 1.0) {
172 font.setPointSize(fontSize_ - 1);
173 infoTextWidthModifier = 12;
174 }
175
176 auto leftMargin = dx_ + sizeImage_ + dx_;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400177 auto rightMargin = dx_;
Andreas Traczyk29650142019-01-03 20:33:56 -0500178 auto topMargin = 4;
179 auto bottomMargin = 8;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400180
181 QRect rectName1(rect.left() + leftMargin,
182 rect.top() + topMargin,
Andreas Traczyk29650142019-01-03 20:33:56 -0500183 rect.width() - leftMargin - infoTextWidth_ - infoTextWidthModifier,
184 rect.height() / 2 - 2);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400185
186 QRect rectName2(rectName1.left(),
187 rectName1.top() + rectName1.height(),
188 rectName1.width(),
189 rectName1.height() - bottomMargin);
190
191 QRect rectInfo1(rectName1.left() + rectName1.width(),
192 rect.top() + topMargin,
Andreas Traczyk29650142019-01-03 20:33:56 -0500193 infoTextWidth_ - rightMargin + infoTextWidthModifier,
194 rect.height() / 2 - 2);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400195
196 QRect rectInfo2(rectInfo1.left(),
197 rectInfo1.top() + rectInfo1.height(),
198 rectInfo1.width(),
199 rectInfo1.height() - bottomMargin);
200
201 QFontMetrics fontMetrics(font);
202
203 // The name is displayed at the avatar's right
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500204 QString nameStr = index.data(static_cast<int>(SmartListModel::Role::DisplayName)).value<QString>();
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400205 if (!nameStr.isNull()) {
206 font.setItalic(false);
Andreas Traczyk29650142019-01-03 20:33:56 -0500207 font.setBold(false);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400208 pen.setColor(RingTheme::lightBlack_);
209 painter->setPen(pen);
210 painter->setFont(font);
211 QString elidedNameStr = fontMetrics.elidedText(nameStr, Qt::ElideRight, rectName1.width());
212 painter->drawText(rectName1, Qt::AlignVCenter | Qt::AlignLeft, elidedNameStr);
213 }
214
215 // Display the ID under the name
216 QString idStr = index.data(static_cast<int>(SmartListModel::Role::DisplayID)).value<QString>();
217 if (idStr != nameStr && !idStr.isNull()) {
218 font.setItalic(false);
219 font.setBold(false);
220 pen.setColor(RingTheme::grey_);
221 painter->setPen(pen);
222 painter->setFont(font);
223 idStr = fontMetrics.elidedText(idStr, Qt::ElideRight, rectName2.width());
224 painter->drawText(rectName2, Qt::AlignVCenter | Qt::AlignLeft, idStr);
225 }
226
227 // top-right: last interaction date/time
228 QString lastUsedStr = index.data(static_cast<int>(SmartListModel::Role::LastInteractionDate)).value<QString>();
229 if (!lastUsedStr.isNull()) {
230 font.setItalic(false);
231 font.setBold(false);
232 pen.setColor(RingTheme::grey_);
233 painter->setPen(pen);
234 painter->setFont(font);
235 lastUsedStr = fontMetrics.elidedText(lastUsedStr, Qt::ElideRight, rectInfo1.width());
236 painter->drawText(rectInfo1, Qt::AlignVCenter | Qt::AlignRight, lastUsedStr);
237 }
238
239 // bottom-right: last interaction snippet
240 QString interactionStr = index.data(static_cast<int>(SmartListModel::Role::LastInteraction)).value<QString>();
241 if (!interactionStr.isNull()) {
Andreas Traczyk43c08232018-10-31 13:42:09 -0400242 painter->save();
243 interactionStr = interactionStr.simplified();
244 auto type = Utils::toEnum<lrc::api::interaction::Type>(index
245 .data(static_cast<int>(SmartListModel::Role::LastInteractionType))
246 .value<int>());
247 if (type == lrc::api::interaction::Type::CALL ||
248 type == lrc::api::interaction::Type::CONTACT) {
249 font.setItalic(false);
250 font.setBold(false);
251 pen.setColor(RingTheme::grey_.darker(140));
252 painter->setPen(pen);
253 painter->setFont(font);
254 // strip emojis if it's a call/contact type message
255 VectorUInt emojiless;
256 for (auto unicode : interactionStr.toUcs4()) {
257 if (!(unicode >= 0x1F000 && unicode <= 0x1FFFF)) {
258 emojiless.push_back(unicode);
259 }
260 }
261 interactionStr = QString::fromUcs4(&emojiless.at(0), emojiless.size());
262 } else {
263 QFont emojiMsgFont(QStringLiteral("Segoe UI Emoji"));
264 emojiMsgFont.setItalic(false);
265 emojiMsgFont.setBold(false);
266 emojiMsgFont.setPointSize(fontSize_);
267 painter->setOpacity(0.7);
268 painter->setFont(emojiMsgFont);
269 }
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400270 interactionStr = fontMetrics.elidedText(interactionStr, Qt::ElideRight, rectInfo2.width());
271 painter->drawText(rectInfo2, Qt::AlignVCenter | Qt::AlignRight, interactionStr);
Andreas Traczyk43c08232018-10-31 13:42:09 -0400272 painter->restore();
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400273 }
274}
275
276void
277ConversationItemDelegate::paintRingInviteConversationItem(QPainter* painter,
278 const QStyleOptionViewItem& option,
279 const QRect& rect,
280 const QModelIndex& index) const
281{
282 QFont font(painter->font());
283 font.setPointSize(fontSize_);
284 QPen pen(painter->pen());
285 painter->setPen(pen);
286
Andreas Traczyk29650142019-01-03 20:33:56 -0500287 auto leftMargin = dx_ + sizeImage_ + dx_;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400288 auto rightMargin = dx_;
289 if (option.state & QStyle::State_MouseOver) {
290 rightMargin = infoTextWidth_ - dx_ * 2;
291 }
Andreas Traczyk29650142019-01-03 20:33:56 -0500292 auto topMargin = 4;
293 auto bottomMargin = 8;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400294
295 QRect rectName1(rect.left() + leftMargin,
296 rect.top() + topMargin,
297 rect.width() - leftMargin - rightMargin,
Andreas Traczyk29650142019-01-03 20:33:56 -0500298 rect.height() / 2 - 2);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400299
300 QRect rectName2(rectName1.left(),
301 rectName1.top() + rectName1.height(),
302 rectName1.width(),
303 rectName1.height() - bottomMargin);
304
305 QFontMetrics fontMetrics(font);
306
307 // The name is displayed at the avatar's right
Andreas Traczyk43c08232018-10-31 13:42:09 -0400308 QString nameStr = index.data(static_cast<int>(SmartListModel::Role::DisplayName)).value<QString>();
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400309 if (!nameStr.isNull()) {
310 font.setItalic(false);
Andreas Traczyk29650142019-01-03 20:33:56 -0500311 font.setBold(false);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400312 pen.setColor(RingTheme::lightBlack_);
313 painter->setPen(pen);
314 painter->setFont(font);
315 QString elidedNameStr = fontMetrics.elidedText(nameStr, Qt::ElideRight, rectName1.width());
316 painter->drawText(rectName1, Qt::AlignVCenter | Qt::AlignLeft, elidedNameStr);
317 }
318
319 // Display the ID under the name
320 QString idStr = index.data(static_cast<int>(SmartListModel::Role::DisplayID)).value<QString>();
321 if (idStr != nameStr && !idStr.isNull()) {
322 font.setItalic(false);
323 font.setBold(false);
324 pen.setColor(RingTheme::grey_);
325 painter->setPen(pen);
326 painter->setFont(font);
327 idStr = fontMetrics.elidedText(idStr, Qt::ElideRight, rectName2.width());
328 painter->drawText(rectName2, Qt::AlignVCenter | Qt::AlignLeft, idStr);
329 }
330}
331
332void
Andreas Traczyk43c08232018-10-31 13:42:09 -0400333ConversationItemDelegate::paintSIPConversationItem(QPainter* painter,
334 const QStyleOptionViewItem& option,
335 const QModelIndex& index) const
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400336{
Andreas Traczyk43c08232018-10-31 13:42:09 -0400337 Q_UNUSED(painter);
338 Q_UNUSED(option);
339 Q_UNUSED(index);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400340}