blob: 8de9adb74fdc4ab2cce5ed9f93d496b6c24e1549 [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());
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400165 QPen pen(painter->pen());
166 painter->setPen(pen);
167
Andreas Traczyk29650142019-01-03 20:33:56 -0500168 int infoTextWidthModifier = 0;
Andreas Traczyk46508b52019-01-04 11:49:24 -0500169 int infoText2HeightModifier = 0;
Andreas Traczyk29650142019-01-03 20:33:56 -0500170 auto scalingRatio = MainWindow::instance().getCurrentScalingRatio();
171 if (scalingRatio > 1.0) {
Andreas Traczyk46508b52019-01-04 11:49:24 -0500172 font.setPointSize(fontSize_ - 2);
Andreas Traczyk29650142019-01-03 20:33:56 -0500173 infoTextWidthModifier = 12;
Andreas Traczyk46508b52019-01-04 11:49:24 -0500174 infoText2HeightModifier = 2;
175 } else {
176 font.setPointSize(fontSize_);
Andreas Traczyk59ba48a2019-01-04 16:12:03 -0500177 infoTextWidthModifier = 10;
Andreas Traczyk29650142019-01-03 20:33:56 -0500178 }
179
180 auto leftMargin = dx_ + sizeImage_ + dx_;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400181 auto rightMargin = dx_;
Andreas Traczyk29650142019-01-03 20:33:56 -0500182 auto topMargin = 4;
183 auto bottomMargin = 8;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400184
185 QRect rectName1(rect.left() + leftMargin,
186 rect.top() + topMargin,
Andreas Traczyk2771e2e2019-01-08 17:44:33 -0500187 rect.width() - leftMargin - infoTextWidth_ - infoTextWidthModifier - 2,
Andreas Traczyk29650142019-01-03 20:33:56 -0500188 rect.height() / 2 - 2);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400189
190 QRect rectName2(rectName1.left(),
Andreas Traczyk2771e2e2019-01-08 17:44:33 -0500191 rectName1.top() + rectName1.height() - infoText2HeightModifier,
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400192 rectName1.width(),
Andreas Traczyk2771e2e2019-01-08 17:44:33 -0500193 rectName1.height() - bottomMargin + infoText2HeightModifier);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400194
195 QRect rectInfo1(rectName1.left() + rectName1.width(),
196 rect.top() + topMargin,
Andreas Traczyk29650142019-01-03 20:33:56 -0500197 infoTextWidth_ - rightMargin + infoTextWidthModifier,
198 rect.height() / 2 - 2);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400199
200 QRect rectInfo2(rectInfo1.left(),
Andreas Traczyk46508b52019-01-04 11:49:24 -0500201 rectInfo1.top() + rectInfo1.height() - infoText2HeightModifier,
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400202 rectInfo1.width(),
Andreas Traczyk46508b52019-01-04 11:49:24 -0500203 rectInfo1.height() - bottomMargin + infoText2HeightModifier);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400204
205 QFontMetrics fontMetrics(font);
206
207 // The name is displayed at the avatar's right
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500208 QString nameStr = index.data(static_cast<int>(SmartListModel::Role::DisplayName)).value<QString>();
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400209 if (!nameStr.isNull()) {
210 font.setItalic(false);
Andreas Traczyk29650142019-01-03 20:33:56 -0500211 font.setBold(false);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400212 pen.setColor(RingTheme::lightBlack_);
213 painter->setPen(pen);
214 painter->setFont(font);
215 QString elidedNameStr = fontMetrics.elidedText(nameStr, Qt::ElideRight, rectName1.width());
216 painter->drawText(rectName1, Qt::AlignVCenter | Qt::AlignLeft, elidedNameStr);
217 }
218
219 // Display the ID under the name
220 QString idStr = index.data(static_cast<int>(SmartListModel::Role::DisplayID)).value<QString>();
221 if (idStr != nameStr && !idStr.isNull()) {
222 font.setItalic(false);
223 font.setBold(false);
224 pen.setColor(RingTheme::grey_);
225 painter->setPen(pen);
226 painter->setFont(font);
227 idStr = fontMetrics.elidedText(idStr, Qt::ElideRight, rectName2.width());
228 painter->drawText(rectName2, Qt::AlignVCenter | Qt::AlignLeft, idStr);
229 }
230
231 // top-right: last interaction date/time
232 QString lastUsedStr = index.data(static_cast<int>(SmartListModel::Role::LastInteractionDate)).value<QString>();
233 if (!lastUsedStr.isNull()) {
234 font.setItalic(false);
235 font.setBold(false);
236 pen.setColor(RingTheme::grey_);
237 painter->setPen(pen);
238 painter->setFont(font);
239 lastUsedStr = fontMetrics.elidedText(lastUsedStr, Qt::ElideRight, rectInfo1.width());
240 painter->drawText(rectInfo1, Qt::AlignVCenter | Qt::AlignRight, lastUsedStr);
241 }
242
243 // bottom-right: last interaction snippet
244 QString interactionStr = index.data(static_cast<int>(SmartListModel::Role::LastInteraction)).value<QString>();
245 if (!interactionStr.isNull()) {
Andreas Traczyk43c08232018-10-31 13:42:09 -0400246 painter->save();
Andreas Traczyk46508b52019-01-04 11:49:24 -0500247 font.setWeight(QFont::ExtraLight);
Andreas Traczyk43c08232018-10-31 13:42:09 -0400248 interactionStr = interactionStr.simplified();
249 auto type = Utils::toEnum<lrc::api::interaction::Type>(index
250 .data(static_cast<int>(SmartListModel::Role::LastInteractionType))
251 .value<int>());
252 if (type == lrc::api::interaction::Type::CALL ||
253 type == lrc::api::interaction::Type::CONTACT) {
254 font.setItalic(false);
255 font.setBold(false);
256 pen.setColor(RingTheme::grey_.darker(140));
257 painter->setPen(pen);
258 painter->setFont(font);
259 // strip emojis if it's a call/contact type message
260 VectorUInt emojiless;
261 for (auto unicode : interactionStr.toUcs4()) {
262 if (!(unicode >= 0x1F000 && unicode <= 0x1FFFF)) {
263 emojiless.push_back(unicode);
264 }
265 }
266 interactionStr = QString::fromUcs4(&emojiless.at(0), emojiless.size());
267 } else {
268 QFont emojiMsgFont(QStringLiteral("Segoe UI Emoji"));
269 emojiMsgFont.setItalic(false);
270 emojiMsgFont.setBold(false);
Andreas Traczyk46508b52019-01-04 11:49:24 -0500271 emojiMsgFont.setPointSize(scalingRatio > 1.0 ? fontSize_ - 2 : fontSize_);
Andreas Traczyk2771e2e2019-01-08 17:44:33 -0500272 rectInfo2.setTop(rectInfo2.top() - 6);
Andreas Traczyk43c08232018-10-31 13:42:09 -0400273 painter->setOpacity(0.7);
274 painter->setFont(emojiMsgFont);
275 }
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400276 interactionStr = fontMetrics.elidedText(interactionStr, Qt::ElideRight, rectInfo2.width());
277 painter->drawText(rectInfo2, Qt::AlignVCenter | Qt::AlignRight, interactionStr);
Andreas Traczyk43c08232018-10-31 13:42:09 -0400278 painter->restore();
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400279 }
280}
281
282void
283ConversationItemDelegate::paintRingInviteConversationItem(QPainter* painter,
284 const QStyleOptionViewItem& option,
285 const QRect& rect,
286 const QModelIndex& index) const
287{
288 QFont font(painter->font());
289 font.setPointSize(fontSize_);
290 QPen pen(painter->pen());
291 painter->setPen(pen);
292
Andreas Traczyk29650142019-01-03 20:33:56 -0500293 auto leftMargin = dx_ + sizeImage_ + dx_;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400294 auto rightMargin = dx_;
295 if (option.state & QStyle::State_MouseOver) {
296 rightMargin = infoTextWidth_ - dx_ * 2;
297 }
Andreas Traczyk29650142019-01-03 20:33:56 -0500298 auto topMargin = 4;
299 auto bottomMargin = 8;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400300
301 QRect rectName1(rect.left() + leftMargin,
302 rect.top() + topMargin,
303 rect.width() - leftMargin - rightMargin,
Andreas Traczyk29650142019-01-03 20:33:56 -0500304 rect.height() / 2 - 2);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400305
306 QRect rectName2(rectName1.left(),
307 rectName1.top() + rectName1.height(),
308 rectName1.width(),
309 rectName1.height() - bottomMargin);
310
311 QFontMetrics fontMetrics(font);
312
313 // The name is displayed at the avatar's right
Andreas Traczyk43c08232018-10-31 13:42:09 -0400314 QString nameStr = index.data(static_cast<int>(SmartListModel::Role::DisplayName)).value<QString>();
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400315 if (!nameStr.isNull()) {
316 font.setItalic(false);
Andreas Traczyk29650142019-01-03 20:33:56 -0500317 font.setBold(false);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400318 pen.setColor(RingTheme::lightBlack_);
319 painter->setPen(pen);
320 painter->setFont(font);
321 QString elidedNameStr = fontMetrics.elidedText(nameStr, Qt::ElideRight, rectName1.width());
322 painter->drawText(rectName1, Qt::AlignVCenter | Qt::AlignLeft, elidedNameStr);
323 }
324
325 // Display the ID under the name
326 QString idStr = index.data(static_cast<int>(SmartListModel::Role::DisplayID)).value<QString>();
327 if (idStr != nameStr && !idStr.isNull()) {
328 font.setItalic(false);
329 font.setBold(false);
330 pen.setColor(RingTheme::grey_);
331 painter->setPen(pen);
332 painter->setFont(font);
333 idStr = fontMetrics.elidedText(idStr, Qt::ElideRight, rectName2.width());
334 painter->drawText(rectName2, Qt::AlignVCenter | Qt::AlignLeft, idStr);
335 }
336}
337
338void
Andreas Traczyk43c08232018-10-31 13:42:09 -0400339ConversationItemDelegate::paintSIPConversationItem(QPainter* painter,
340 const QStyleOptionViewItem& option,
341 const QModelIndex& index) const
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400342{
Andreas Traczyk43c08232018-10-31 13:42:09 -0400343 Q_UNUSED(painter);
344 Q_UNUSED(option);
345 Q_UNUSED(index);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400346}