blob: a3ac06c83e5240b2c85fdc437210fd6b8c51d617 [file] [log] [blame]
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001/*
Guillaume Roguez2a6150d2017-07-19 18:24:47 -04002 * Copyright (C) 2015-2017 Savoir-faire Linux Inc.
Stepan Salenikovich6f687072015-03-26 10:43:37 -04003 * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
Sébastien Bline72d43c2017-10-03 11:37:33 -04004 * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
Stepan Salenikovich6f687072015-03-26 10:43:37 -04005 *
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, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Stepan Salenikovich6f687072015-03-26 10:43:37 -040019 */
20
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -040021#include "pixbufmanipulator.h"
Stepan Salenikovich6f687072015-03-26 10:43:37 -040022
23#include "../utils/drawing.h"
24#include <QtCore/QSize>
25#include <QtCore/QMetaType>
26#include <person.h>
27#include <memory>
28#include <call.h>
29#include <contactmethod.h>
30
Sébastien Bline72d43c2017-10-03 11:37:33 -040031#include <string>
32#include <algorithm>
33
34// lrc
35#include <api/contactmodel.h>
36#include <api/conversation.h>
37#include <api/account.h>
38#include <api/contact.h>
39
40
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -040041namespace Interfaces {
42
43PixbufManipulator::PixbufManipulator()
Sébastien Blin9684dd32017-07-24 11:15:19 -040044 : conferenceAvatar_{ring_draw_conference_avatar(FALLBACK_AVATAR_SIZE), g_object_unref}
Stepan Salenikovich6f687072015-03-26 10:43:37 -040045{
46}
47
48std::shared_ptr<GdkPixbuf>
Sébastien Blinfbcb2292017-10-16 11:54:14 -040049PixbufManipulator::temporaryItemAvatar() const
50{
51 GError *error = nullptr;
52 std::shared_ptr<GdkPixbuf> result(
53 gdk_pixbuf_new_from_resource("/cx/ring/RingGnome/temporary-item", &error),
54 g_object_unref
55 );
56
57 if (result == nullptr) {
58 g_debug("Could not load icon: %s", error->message);
59 g_clear_error(&error);
60 return generateAvatar("", "");
61 }
62 return result;
63}
64
65std::shared_ptr<GdkPixbuf>
Sébastien Blin9684dd32017-07-24 11:15:19 -040066PixbufManipulator::generateAvatar(const ContactMethod* cm) const
67{
68 auto cm_number = QString("0");
Sébastien Blinfbcb2292017-10-16 11:54:14 -040069 auto letter = QChar('?'); // R for ring
Sébastien Blin9684dd32017-07-24 11:15:19 -040070 if (cm) {
71 auto hashName = cm->uri().userinfo();
72 if (hashName.size() > 0) {
73 cm_number = hashName.at(0);
74 }
Sébastien Blin2309eda2017-07-27 09:20:12 -040075 // Get the letter to draw
76 if (!cm->bestName().isEmpty()) {
77 // Prioritize the name
78 letter = cm->bestName().toUpper().at(0);
79 } else if (!cm->bestId().isEmpty()) {
80 // If the contact has no name, use the id
81 letter = cm->bestId().toUpper().at(0);
82 } else {
83 // R for ring is used
84 }
Sébastien Blin9684dd32017-07-24 11:15:19 -040085 }
86
87 bool ok;
88 auto color = cm_number.toUInt(&ok, 16);
89 if (!ok) color = 0;
90
91 return std::shared_ptr<GdkPixbuf> {
92 ring_draw_fallback_avatar(
93 FALLBACK_AVATAR_SIZE,
94 letter.toLatin1(),
95 color
96 ),
97 g_object_unref
98 };
99}
100
101std::shared_ptr<GdkPixbuf>
Sébastien Bline72d43c2017-10-03 11:37:33 -0400102PixbufManipulator::generateAvatar(const std::string& alias, const std::string& uri) const
103{
104 auto name = alias;
105 std::transform(name.begin(), name.end(), name.begin(), ::toupper);
Sébastien Blinfbcb2292017-10-16 11:54:14 -0400106 auto letter = name.length() > 0 ? name[0] : '?';
107 auto color = 0;
108 try {
109 color = uri.length() > 0 ? std::stoi(std::string(1, uri[0]), 0, 16) : 0;
110 } catch (...) {
111 // uri[0] not in "0123456789abcdef"
112 }
Sébastien Bline72d43c2017-10-03 11:37:33 -0400113
114 return std::shared_ptr<GdkPixbuf> {
115 ring_draw_fallback_avatar(
116 FALLBACK_AVATAR_SIZE,
117 letter,
118 color
119 ),
120 g_object_unref
121 };
122}
123
124std::shared_ptr<GdkPixbuf>
Nicolas Jagerd64d88e2017-12-11 14:26:00 -0500125PixbufManipulator::scaleAndFrame(const GdkPixbuf *photo,
126 const QSize& size,
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500127 bool displayInformation,
Nicolas Jagerd64d88e2017-12-11 14:26:00 -0500128 bool is_present,
129 uint unreadMessages)
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400130{
131 /**
132 * for now, respect the height requested
133 * the framing process will add another 10px, so account for that
134 * when scaling the photos
135 */
136
137 int height = size.height();
138 if (size.height() != size.width())
139 g_warning("requested contact photo width != height; only respecting the height as the largest dimension");
140 int photo_h = height - 10;
141 int photo_w = photo_h;
142
143 /* scale photo, make sure to respect the request height as the largest dimension*/
144 int w = gdk_pixbuf_get_width(photo);
145 int h = gdk_pixbuf_get_height(photo);
146 if (h > w)
147 photo_w = w * ((double)photo_h / h);
148 if (w > h)
149 photo_h = h * ((double)photo_w / w);
150
151 std::unique_ptr<GdkPixbuf, decltype(g_object_unref)&> scaled_photo{
152 gdk_pixbuf_scale_simple(photo, photo_w, photo_h, GDK_INTERP_BILINEAR),
153 g_object_unref};
154
155 /* frame photo */
aviauc372e812016-12-01 16:13:16 -0500156 std::shared_ptr<GdkPixbuf> result {
157 ring_frame_avatar(scaled_photo.get()),
158 g_object_unref
159 };
160
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500161 /* draw information */
162 if (displayInformation) {
163 /* draw presence */
aviauc372e812016-12-01 16:13:16 -0500164 result.reset(ring_draw_presence(result.get(), is_present), g_object_unref);
165
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500166 /* draw visual notification for unread messages */
167 if (unreadMessages)
168 result.reset(ring_draw_unread_messages(result.get(), unreadMessages), g_object_unref);
169 }
Nicolas Jagerd64d88e2017-12-11 14:26:00 -0500170
aviauc372e812016-12-01 16:13:16 -0500171 return result;
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400172}
173
174QVariant
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500175PixbufManipulator::callPhoto(Call* c, const QSize& size, bool displayInformation)
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400176{
aviauc372e812016-12-01 16:13:16 -0500177 if (c->type() == Call::Type::CONFERENCE) {
178 /* conferences are always "online" */
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500179 return QVariant::fromValue(scaleAndFrame(conferenceAvatar_.get(), size, displayInformation, TRUE));
aviauc372e812016-12-01 16:13:16 -0500180 }
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500181 return callPhoto(c->peerContactMethod(), size, displayInformation);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400182}
183
184QVariant
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500185PixbufManipulator::callPhoto(const ContactMethod* n, const QSize& size, bool displayInformation)
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400186{
187 if (n->contact()) {
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500188 return contactPhoto(n->contact(), size, displayInformation);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400189 } else {
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500190 return QVariant::fromValue(scaleAndFrame(generateAvatar(n).get(), size, displayInformation, n->isPresent()));
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400191 }
192}
193
194QVariant
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500195PixbufManipulator::contactPhoto(Person* c, const QSize& size, bool displayInformation)
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400196{
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400197 /**
198 * try to get the photo
Sébastien Blin9684dd32017-07-24 11:15:19 -0400199 * otherwise use the generated avatar
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400200 */
201
202 std::shared_ptr<GdkPixbuf> photo;
203
204 if (c->photo().isValid())
205 photo = c->photo().value<std::shared_ptr<GdkPixbuf>>();
Sébastien Blin9684dd32017-07-24 11:15:19 -0400206 else {
207 auto cm = c->phoneNumbers().size() > 0 ? c->phoneNumbers().first() : nullptr;
208 photo = generateAvatar(cm);
209 }
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400210
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500211 return QVariant::fromValue(scaleAndFrame(photo.get(), size, displayInformation, c->isPresent()));
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400212}
213
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400214QVariant PixbufManipulator::personPhoto(const QByteArray& data, const QString& type)
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400215{
216 Q_UNUSED(type);
217 /* Try to load the image from the data provided by lrc vcard utils;
218 * lrc is getting the image data assuming that it is inlined in the vcard,
219 * for now URIs are not supported.
220 *
221 * The format of the data should be either base 64 or ascii (hex), try both
222 */
223
224 GError *error = NULL;
225 GdkPixbuf *pixbuf = NULL;
226 GInputStream *stream = NULL;
227
228 /* first try using base64 */
229 QByteArray ba64 = QByteArray::fromBase64(data);
230 stream = g_memory_input_stream_new_from_data(ba64.constData(),
231 ba64.size(),
232 NULL);
233
234 pixbuf = gdk_pixbuf_new_from_stream(stream, NULL, &error);
235 g_input_stream_close(stream, NULL, NULL);
236 g_object_unref(stream);
237
238 if (!pixbuf) {
239 // g_debug("failed decoding person photo using base64: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -0400240 g_clear_error(&error);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400241
242 /* failed with base64, try hex */
243 QByteArray baHex = QByteArray::fromHex(data);
244 stream = g_memory_input_stream_new_from_data(baHex.constData(),
245 baHex.size(),
246 NULL);
247
248 pixbuf = gdk_pixbuf_new_from_stream(stream, NULL, &error);
249 g_input_stream_close(stream, NULL, NULL);
250 g_object_unref(stream);
251
252 if (!pixbuf) {
253 // g_debug("failed decoding person photo using hex (ASCII): %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -0400254 g_clear_error(&error);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400255 }
256 }
257
258 if (pixbuf) {
259 std::shared_ptr<GdkPixbuf> avatar(pixbuf, g_object_unref);
260 return QVariant::fromValue(avatar);
261 }
262
263 /* could not load image, return emtpy QVariant */
264 return QVariant();
265}
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400266
267QVariant
Nicolas Jagerd64d88e2017-12-11 14:26:00 -0500268PixbufManipulator::conversationPhoto(const lrc::api::conversation::Info& conversationInfo,
Sébastien Bline72d43c2017-10-03 11:37:33 -0400269 const lrc::api::account::Info& accountInfo,
270 const QSize& size,
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500271 bool displayInformation)
Sébastien Bline72d43c2017-10-03 11:37:33 -0400272{
Nicolas Jagerd64d88e2017-12-11 14:26:00 -0500273 auto contacts = conversationInfo.participants;
Guillaume Roguezc2095922017-12-14 14:07:10 -0500274 if (!contacts.empty()) {
275 try {
276 // Get first contact photo
277 auto contactUri = contacts.front();
278 auto contactInfo = accountInfo.contactModel->getContact(contactUri);
279 auto contactPhoto = contactInfo.profileInfo.avatar;
280 auto bestName = contactInfo.profileInfo.alias.empty()? contactInfo.registeredName : contactInfo.profileInfo.alias;
281 auto unreadMessages = conversationInfo.unreadMessages;
282 if (contactInfo.profileInfo.type == lrc::api::profile::Type::TEMPORARY && contactInfo.profileInfo.uri.empty()) {
283 return QVariant::fromValue(scaleAndFrame(temporaryItemAvatar().get(), size, false, false, unreadMessages));
284 } else if (contactInfo.profileInfo.type == lrc::api::profile::Type::SIP) {
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500285 return QVariant::fromValue(scaleAndFrame(generateAvatar(bestName, "").get(), size, displayInformation, contactInfo.isPresent));
Guillaume Roguezc2095922017-12-14 14:07:10 -0500286 } else if (!contactPhoto.empty()) {
287 QByteArray byteArray(contactPhoto.c_str(), contactPhoto.length());
288 QVariant photo = personPhoto(byteArray);
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500289 return QVariant::fromValue(scaleAndFrame(photo.value<std::shared_ptr<GdkPixbuf>>().get(), size, displayInformation, contactInfo.isPresent, unreadMessages));
Guillaume Roguezc2095922017-12-14 14:07:10 -0500290 } else {
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500291 return QVariant::fromValue(scaleAndFrame(generateAvatar(bestName, contactInfo.profileInfo.uri).get(), size, displayInformation, contactInfo.isPresent, unreadMessages));
Guillaume Roguezc2095922017-12-14 14:07:10 -0500292 }
293 } catch (...) {}
Sébastien Bline72d43c2017-10-03 11:37:33 -0400294 }
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500295 return QVariant::fromValue(scaleAndFrame(generateAvatar("", "").get(), size, displayInformation, false));
Sébastien Bline72d43c2017-10-03 11:37:33 -0400296
297}
298
299QVariant
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500300PixbufManipulator::numberCategoryIcon(const QVariant& p, const QSize& size, bool displayInformation, bool isPresent)
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400301{
302 Q_UNUSED(p)
303 Q_UNUSED(size)
Nicolas Jagerdcddfb52017-12-12 15:06:46 -0500304 Q_UNUSED(displayInformation)
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400305 Q_UNUSED(isPresent)
306 return QVariant();
307}
308
309QVariant
310PixbufManipulator::securityIssueIcon(const QModelIndex& index)
311{
312 Q_UNUSED(index)
313 return QVariant();
314}
315
316QByteArray
317PixbufManipulator::toByteArray(const QVariant& pxm)
318{
Nicolas Jager1cf27112016-05-17 11:51:28 -0400319 std::shared_ptr<GdkPixbuf> pixbuf_photo = pxm.value<std::shared_ptr<GdkPixbuf>>();
320
321 if(pixbuf_photo.get()) {
322 gchar* png_buffer = nullptr;
323 gsize png_buffer_size;
324 GError *error = nullptr;
325
326 gdk_pixbuf_save_to_buffer(pixbuf_photo.get(), &png_buffer, &png_buffer_size, "png", &error, NULL);
327 QByteArray array = QByteArray(png_buffer, png_buffer_size);
328
329 g_free(png_buffer);
330
331 if (error != NULL) {
332 g_warning("in toByteArray, gdk_pixbuf_save_to_buffer failed : %s\n", error->message);
333 g_clear_error(&error);
334 }
335
336 return array;
337 } else {
338 g_debug("in toByteArray, failed to retrieve data from parameter pxm");
339 return QByteArray();
340 }
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400341}
342
343QVariant
344PixbufManipulator::collectionIcon(const CollectionInterface* interface, PixmapManipulatorI::CollectionIconHint hint) const
345{
346 Q_UNUSED(interface)
347 Q_UNUSED(hint)
348 return QVariant();
349}
350QVariant
351PixbufManipulator::securityLevelIcon(const SecurityEvaluationModel::SecurityLevel level) const
352{
353 Q_UNUSED(level)
354 return QVariant();
355}
356QVariant
357PixbufManipulator::historySortingCategoryIcon(const CategorizedHistoryModel::SortedProxy::Categories cat) const
358{
359 Q_UNUSED(cat)
360 return QVariant();
361}
362QVariant
363PixbufManipulator::contactSortingCategoryIcon(const CategorizedContactModel::SortedProxy::Categories cat) const
364{
365 Q_UNUSED(cat)
366 return QVariant();
367}
368QVariant
369PixbufManipulator::userActionIcon(const UserActionElement& state) const
370{
371 Q_UNUSED(state)
372 return QVariant();
373}
374
Stepan Salenikovich8d076952016-01-08 13:48:43 -0500375QVariant PixbufManipulator::decorationRole(const QModelIndex& index)
376{
377 Q_UNUSED(index)
378 return QVariant();
379}
380
381QVariant PixbufManipulator::decorationRole(const Call* c)
382{
383 Q_UNUSED(c)
384 return QVariant();
385}
386
387QVariant PixbufManipulator::decorationRole(const ContactMethod* cm)
388{
389 Q_UNUSED(cm)
390 return QVariant();
391}
392
393QVariant PixbufManipulator::decorationRole(const Person* p)
394{
395 Q_UNUSED(p)
396 return QVariant();
397}
398
Alexandre Lision50fd6af2016-04-20 17:13:58 -0400399QVariant PixbufManipulator::decorationRole(const Account* p)
400{
401 Q_UNUSED(p)
402 return QVariant();
403}
404
Sébastien Bline72d43c2017-10-03 11:37:33 -0400405QVariant PixbufManipulator::decorationRole(const lrc::api::conversation::Info& conversation,
406 const lrc::api::account::Info& accountInfo)
407{
408 Q_UNUSED(conversation)
409 Q_UNUSED(accountInfo)
410 return QVariant();
411}
412
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400413} // namespace Interfaces