blob: 2a31b7da724315c90cc843bd45b859f880433fd5 [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 Traczykb8b13ba2018-08-21 16:30:16 -040031
32#include <ciso646>
33
Andreas Traczyk43c08232018-10-31 13:42:09 -040034ConversationItemDelegate::ConversationItemDelegate(QObject* parent)
35 : QItemDelegate(parent)
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040036{
37}
38
39void
40ConversationItemDelegate::paint(QPainter* painter
41 , const QStyleOptionViewItem& option
42 , const QModelIndex& index
43 ) const
44{
45 QStyleOptionViewItem opt(option);
Andreas Traczyk43c08232018-10-31 13:42:09 -040046 painter->setRenderHint(QPainter::Antialiasing, true);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040047
48 // Not having focus removes dotted lines around the item
49 if (opt.state & QStyle::State_HasFocus)
50 opt.state ^= QStyle::State_HasFocus;
51
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040052 auto isContextMenuOpen = index.data(static_cast<int>(SmartListModel::Role::ContextMenuOpen)).value<bool>();
53 bool selected = false;
54 if (option.state & QStyle::State_Selected) {
55 selected = true;
56 opt.state ^= QStyle::State_Selected;
57 } else if (!isContextMenuOpen) {
Andreas Traczyk912242e2018-10-29 14:44:44 -040058 highlightMap_[index.row()] = option.state & QStyle::State_MouseOver;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040059 }
60
61 // One does not simply keep the highlighted state drawn when the context
62 // menu is openÂ…
63 auto rowHighlight = highlightMap_.find(index.row());
64 if (selected) {
Andreas Traczyk6b5ad3e2019-01-02 17:04:36 -050065 painter->fillRect(option.rect, RingTheme::smartlistSelection_);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040066 } else if (rowHighlight != highlightMap_.end() && (*rowHighlight).second) {
Andreas Traczyk6b5ad3e2019-01-02 17:04:36 -050067 painter->fillRect(option.rect, RingTheme::smartlistHighlight_);
68 }
69 auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
70 auto conversation = Utils::getConversationFromUid(convUid, *LRCInstance::getCurrentConversationModel());
71 if (LRCInstance::getCurrentCallModel()->hasCall(conversation->callId)) {
72 auto color = QColor(RingTheme::blue_.lighter(180)); color.setAlpha(128);
73 painter->fillRect(option.rect, color);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040074 }
75
76 QRect &rect = opt.rect;
77
78 // Avatar drawing
79 opt.decorationSize = QSize(sizeImage_, sizeImage_);
80 opt.decorationPosition = QStyleOptionViewItem::Left;
81 opt.decorationAlignment = Qt::AlignCenter;
82
83 QRect rectAvatar(dx_ + rect.left(), rect.top() + dy_, sizeImage_, sizeImage_);
84 drawDecoration(painter, opt, rectAvatar,
85 QPixmap::fromImage(index.data(Qt::DecorationRole).value<QImage>())
86 .scaled(sizeImage_, sizeImage_, Qt::KeepAspectRatio, Qt::SmoothTransformation));
87
88 QFont font(painter->font());
89
90 // If there's unread messages, a message count is displayed
91 if (auto messageCount = index.data(static_cast<int>(SmartListModel::Role::UnreadMessagesCount)).toInt()) {
92 QString messageCountText = (messageCount > 9) ? "9+" : QString::number(messageCount);
93 qreal fontSize = messageCountText.count() > 1 ? 7 : 8;
94 font.setPointSize(fontSize);
95
96 // ellipse
97 QPainterPath ellipse;
98 qreal ellipseHeight = sizeImage_ / 6;
99 qreal ellipseWidth = ellipseHeight;
100 QPointF ellipseCenter(rectAvatar.right() - ellipseWidth, rectAvatar.top() + ellipseHeight + 1);
101 QRect ellipseRect(ellipseCenter.x() - ellipseWidth, ellipseCenter.y() - ellipseHeight,
102 ellipseWidth * 2, ellipseHeight * 2);
103 ellipse.addRoundedRect(ellipseRect, ellipseWidth, ellipseHeight);
104 painter->fillPath(ellipse, RingTheme::notificationRed_);
105
106 // text
107 painter->setPen(Qt::white);
108 painter->setOpacity(1);
109 painter->setFont(font);
110 ellipseRect.setTop(ellipseRect.top() - 2);
111 painter->drawText(ellipseRect, Qt::AlignCenter, messageCountText);
112 }
113
114 // Presence indicator
115 if (index.data(static_cast<int>(SmartListModel::Role::Presence)).value<bool>()) {
116 qreal radius = sizeImage_ / 6;
117 QPainterPath outerCircle, innerCircle;
118 QPointF center(rectAvatar.right() - radius, (rectAvatar.bottom() - radius) + 1);
119 qreal outerCRadius = radius;
120 qreal innerCRadius = outerCRadius * 0.75;
121 outerCircle.addEllipse(center, outerCRadius, outerCRadius);
122 innerCircle.addEllipse(center, innerCRadius, innerCRadius);
123 painter->fillPath(outerCircle, Qt::white);
124 painter->fillPath(innerCircle, RingTheme::presenceGreen_);
125 }
126
127 using namespace lrc::api;
128 auto type = Utils::toEnum<profile::Type>(
129 index.data(static_cast<int>(SmartListModel::Role::ContactType)).value<int>()
130 );
131 switch (type) {
132 case profile::Type::RING:
133 case profile::Type::TEMPORARY:
134 paintRingConversationItem(painter, option, rect, index);
135 break;
136 case profile::Type::PENDING:
137 paintRingInviteConversationItem(painter, option, rect, index);
138 break;
139 case profile::Type::SIP:
140 break;
141 default:
142 paintRingConversationItem(painter, option, rect, index);
143 break;
144 }
145}
146
147QSize
148ConversationItemDelegate::sizeHint(const QStyleOptionViewItem& option,
Andreas Traczyk43c08232018-10-31 13:42:09 -0400149 const QModelIndex& index) const
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400150{
Andreas Traczyk43c08232018-10-31 13:42:09 -0400151 Q_UNUSED(option);
152 Q_UNUSED(index);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400153 return QSize(0, cellHeight_);
154}
155
156void
157ConversationItemDelegate::paintRingConversationItem(QPainter* painter,
158 const QStyleOptionViewItem& option,
159 const QRect& rect,
160 const QModelIndex& index) const
161{
Andreas Traczyk43c08232018-10-31 13:42:09 -0400162 Q_UNUSED(option);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400163 QFont font(painter->font());
164 font.setPointSize(fontSize_);
165 QPen pen(painter->pen());
166 painter->setPen(pen);
167
168 auto leftMargin = dx_ + sizeImage_ + dx_ / 2;
169 auto rightMargin = dx_;
170 auto topMargin = 0;
171 auto bottomMargin = 12;
172
173 QRect rectName1(rect.left() + leftMargin,
174 rect.top() + topMargin,
175 rect.width() - leftMargin - infoTextWidth_,
176 rect.height() / 2);
177
178 QRect rectName2(rectName1.left(),
179 rectName1.top() + rectName1.height(),
180 rectName1.width(),
181 rectName1.height() - bottomMargin);
182
183 QRect rectInfo1(rectName1.left() + rectName1.width(),
184 rect.top() + topMargin,
185 infoTextWidth_ - rightMargin,
186 rect.height() / 2);
187
188 QRect rectInfo2(rectInfo1.left(),
189 rectInfo1.top() + rectInfo1.height(),
190 rectInfo1.width(),
191 rectInfo1.height() - bottomMargin);
192
193 QFontMetrics fontMetrics(font);
194
195 // The name is displayed at the avatar's right
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500196 QString nameStr = index.data(static_cast<int>(SmartListModel::Role::DisplayName)).value<QString>();
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400197 if (!nameStr.isNull()) {
198 font.setItalic(false);
199 font.setBold(true);
200 pen.setColor(RingTheme::lightBlack_);
201 painter->setPen(pen);
202 painter->setFont(font);
203 QString elidedNameStr = fontMetrics.elidedText(nameStr, Qt::ElideRight, rectName1.width());
204 painter->drawText(rectName1, Qt::AlignVCenter | Qt::AlignLeft, elidedNameStr);
205 }
206
207 // Display the ID under the name
208 QString idStr = index.data(static_cast<int>(SmartListModel::Role::DisplayID)).value<QString>();
209 if (idStr != nameStr && !idStr.isNull()) {
210 font.setItalic(false);
211 font.setBold(false);
212 pen.setColor(RingTheme::grey_);
213 painter->setPen(pen);
214 painter->setFont(font);
215 idStr = fontMetrics.elidedText(idStr, Qt::ElideRight, rectName2.width());
216 painter->drawText(rectName2, Qt::AlignVCenter | Qt::AlignLeft, idStr);
217 }
218
219 // top-right: last interaction date/time
220 QString lastUsedStr = index.data(static_cast<int>(SmartListModel::Role::LastInteractionDate)).value<QString>();
221 if (!lastUsedStr.isNull()) {
222 font.setItalic(false);
223 font.setBold(false);
224 pen.setColor(RingTheme::grey_);
225 painter->setPen(pen);
226 painter->setFont(font);
227 lastUsedStr = fontMetrics.elidedText(lastUsedStr, Qt::ElideRight, rectInfo1.width());
228 painter->drawText(rectInfo1, Qt::AlignVCenter | Qt::AlignRight, lastUsedStr);
229 }
230
231 // bottom-right: last interaction snippet
232 QString interactionStr = index.data(static_cast<int>(SmartListModel::Role::LastInteraction)).value<QString>();
233 if (!interactionStr.isNull()) {
Andreas Traczyk43c08232018-10-31 13:42:09 -0400234 painter->save();
235 interactionStr = interactionStr.simplified();
236 auto type = Utils::toEnum<lrc::api::interaction::Type>(index
237 .data(static_cast<int>(SmartListModel::Role::LastInteractionType))
238 .value<int>());
239 if (type == lrc::api::interaction::Type::CALL ||
240 type == lrc::api::interaction::Type::CONTACT) {
241 font.setItalic(false);
242 font.setBold(false);
243 pen.setColor(RingTheme::grey_.darker(140));
244 painter->setPen(pen);
245 painter->setFont(font);
246 // strip emojis if it's a call/contact type message
247 VectorUInt emojiless;
248 for (auto unicode : interactionStr.toUcs4()) {
249 if (!(unicode >= 0x1F000 && unicode <= 0x1FFFF)) {
250 emojiless.push_back(unicode);
251 }
252 }
253 interactionStr = QString::fromUcs4(&emojiless.at(0), emojiless.size());
254 } else {
255 QFont emojiMsgFont(QStringLiteral("Segoe UI Emoji"));
256 emojiMsgFont.setItalic(false);
257 emojiMsgFont.setBold(false);
258 emojiMsgFont.setPointSize(fontSize_);
259 painter->setOpacity(0.7);
260 painter->setFont(emojiMsgFont);
261 }
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400262 interactionStr = fontMetrics.elidedText(interactionStr, Qt::ElideRight, rectInfo2.width());
263 painter->drawText(rectInfo2, Qt::AlignVCenter | Qt::AlignRight, interactionStr);
Andreas Traczyk43c08232018-10-31 13:42:09 -0400264 painter->restore();
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400265 }
266}
267
268void
269ConversationItemDelegate::paintRingInviteConversationItem(QPainter* painter,
270 const QStyleOptionViewItem& option,
271 const QRect& rect,
272 const QModelIndex& index) const
273{
274 QFont font(painter->font());
275 font.setPointSize(fontSize_);
276 QPen pen(painter->pen());
277 painter->setPen(pen);
278
279 auto leftMargin = dx_ + sizeImage_ + dx_ / 2;
280 auto rightMargin = dx_;
281 if (option.state & QStyle::State_MouseOver) {
282 rightMargin = infoTextWidth_ - dx_ * 2;
283 }
284 auto topMargin = 0;
285 auto bottomMargin = 12;
286
287 QRect rectName1(rect.left() + leftMargin,
288 rect.top() + topMargin,
289 rect.width() - leftMargin - rightMargin,
290 rect.height() / 2);
291
292 QRect rectName2(rectName1.left(),
293 rectName1.top() + rectName1.height(),
294 rectName1.width(),
295 rectName1.height() - bottomMargin);
296
297 QFontMetrics fontMetrics(font);
298
299 // The name is displayed at the avatar's right
Andreas Traczyk43c08232018-10-31 13:42:09 -0400300 QString nameStr = index.data(static_cast<int>(SmartListModel::Role::DisplayName)).value<QString>();
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400301 if (!nameStr.isNull()) {
302 font.setItalic(false);
303 font.setBold(true);
304 pen.setColor(RingTheme::lightBlack_);
305 painter->setPen(pen);
306 painter->setFont(font);
307 QString elidedNameStr = fontMetrics.elidedText(nameStr, Qt::ElideRight, rectName1.width());
308 painter->drawText(rectName1, Qt::AlignVCenter | Qt::AlignLeft, elidedNameStr);
309 }
310
311 // Display the ID under the name
312 QString idStr = index.data(static_cast<int>(SmartListModel::Role::DisplayID)).value<QString>();
313 if (idStr != nameStr && !idStr.isNull()) {
314 font.setItalic(false);
315 font.setBold(false);
316 pen.setColor(RingTheme::grey_);
317 painter->setPen(pen);
318 painter->setFont(font);
319 idStr = fontMetrics.elidedText(idStr, Qt::ElideRight, rectName2.width());
320 painter->drawText(rectName2, Qt::AlignVCenter | Qt::AlignLeft, idStr);
321 }
322}
323
324void
Andreas Traczyk43c08232018-10-31 13:42:09 -0400325ConversationItemDelegate::paintSIPConversationItem(QPainter* painter,
326 const QStyleOptionViewItem& option,
327 const QModelIndex& index) const
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400328{
Andreas Traczyk43c08232018-10-31 13:42:09 -0400329 Q_UNUSED(painter);
330 Q_UNUSED(option);
331 Q_UNUSED(index);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400332}