blob: 04c392ebddfff52f5a90b63119bec0d30dad989c [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,
127 bool display_presence,
128 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
161 /* draw presence */
162 if (display_presence)
163 result.reset(ring_draw_presence(result.get(), is_present), g_object_unref);
164
Nicolas Jagerd64d88e2017-12-11 14:26:00 -0500165 /* draw visual notification for unread messages */
166 if (unreadMessages)
167 result.reset(ring_draw_unread_messages(result.get(), unreadMessages), g_object_unref);
168
aviauc372e812016-12-01 16:13:16 -0500169 return result;
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400170}
171
172QVariant
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400173PixbufManipulator::callPhoto(Call* c, const QSize& size, bool displayPresence)
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400174{
aviauc372e812016-12-01 16:13:16 -0500175 if (c->type() == Call::Type::CONFERENCE) {
176 /* conferences are always "online" */
177 return QVariant::fromValue(scaleAndFrame(conferenceAvatar_.get(), size, displayPresence, TRUE));
178 }
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400179 return callPhoto(c->peerContactMethod(), size, displayPresence);
180}
181
182QVariant
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400183PixbufManipulator::callPhoto(const ContactMethod* n, const QSize& size, bool displayPresence)
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400184{
185 if (n->contact()) {
186 return contactPhoto(n->contact(), size, displayPresence);
187 } else {
Sébastien Blin9684dd32017-07-24 11:15:19 -0400188 return QVariant::fromValue(scaleAndFrame(generateAvatar(n).get(), size, displayPresence, n->isPresent()));
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400189 }
190}
191
192QVariant
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400193PixbufManipulator::contactPhoto(Person* c, const QSize& size, bool displayPresence)
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400194{
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400195 /**
196 * try to get the photo
Sébastien Blin9684dd32017-07-24 11:15:19 -0400197 * otherwise use the generated avatar
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400198 */
199
200 std::shared_ptr<GdkPixbuf> photo;
201
202 if (c->photo().isValid())
203 photo = c->photo().value<std::shared_ptr<GdkPixbuf>>();
Sébastien Blin9684dd32017-07-24 11:15:19 -0400204 else {
205 auto cm = c->phoneNumbers().size() > 0 ? c->phoneNumbers().first() : nullptr;
206 photo = generateAvatar(cm);
207 }
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400208
aviauc372e812016-12-01 16:13:16 -0500209 return QVariant::fromValue(scaleAndFrame(photo.get(), size, displayPresence, c->isPresent()));
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400210}
211
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400212QVariant PixbufManipulator::personPhoto(const QByteArray& data, const QString& type)
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400213{
214 Q_UNUSED(type);
215 /* Try to load the image from the data provided by lrc vcard utils;
216 * lrc is getting the image data assuming that it is inlined in the vcard,
217 * for now URIs are not supported.
218 *
219 * The format of the data should be either base 64 or ascii (hex), try both
220 */
221
222 GError *error = NULL;
223 GdkPixbuf *pixbuf = NULL;
224 GInputStream *stream = NULL;
225
226 /* first try using base64 */
227 QByteArray ba64 = QByteArray::fromBase64(data);
228 stream = g_memory_input_stream_new_from_data(ba64.constData(),
229 ba64.size(),
230 NULL);
231
232 pixbuf = gdk_pixbuf_new_from_stream(stream, NULL, &error);
233 g_input_stream_close(stream, NULL, NULL);
234 g_object_unref(stream);
235
236 if (!pixbuf) {
237 // g_debug("failed decoding person photo using base64: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -0400238 g_clear_error(&error);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400239
240 /* failed with base64, try hex */
241 QByteArray baHex = QByteArray::fromHex(data);
242 stream = g_memory_input_stream_new_from_data(baHex.constData(),
243 baHex.size(),
244 NULL);
245
246 pixbuf = gdk_pixbuf_new_from_stream(stream, NULL, &error);
247 g_input_stream_close(stream, NULL, NULL);
248 g_object_unref(stream);
249
250 if (!pixbuf) {
251 // g_debug("failed decoding person photo using hex (ASCII): %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -0400252 g_clear_error(&error);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400253 }
254 }
255
256 if (pixbuf) {
257 std::shared_ptr<GdkPixbuf> avatar(pixbuf, g_object_unref);
258 return QVariant::fromValue(avatar);
259 }
260
261 /* could not load image, return emtpy QVariant */
262 return QVariant();
263}
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400264
265QVariant
Nicolas Jagerd64d88e2017-12-11 14:26:00 -0500266PixbufManipulator::conversationPhoto(const lrc::api::conversation::Info& conversationInfo,
Sébastien Bline72d43c2017-10-03 11:37:33 -0400267 const lrc::api::account::Info& accountInfo,
268 const QSize& size,
269 bool displayPresence)
270{
Nicolas Jagerd64d88e2017-12-11 14:26:00 -0500271 auto contacts = conversationInfo.participants;
Sébastien Bline72d43c2017-10-03 11:37:33 -0400272 if (!contacts.empty())
273 {
274 // Get first contact photo
275 auto contactUri = contacts.front();
276 auto contactInfo = accountInfo.contactModel->getContact(contactUri);
277 auto contactPhoto = contactInfo.profileInfo.avatar;
278 auto bestName = contactInfo.profileInfo.alias.empty()? contactInfo.registeredName : contactInfo.profileInfo.alias;
Nicolas Jagerd64d88e2017-12-11 14:26:00 -0500279 auto unreadMessages = conversationInfo.unreadMessages;
Sébastien Blinfbcb2292017-10-16 11:54:14 -0400280 if (contactInfo.profileInfo.type == lrc::api::profile::Type::TEMPORARY && contactInfo.profileInfo.uri.empty()) {
Nicolas Jagerd64d88e2017-12-11 14:26:00 -0500281 return QVariant::fromValue(scaleAndFrame(temporaryItemAvatar().get(), size, false, false, unreadMessages));
Sébastien Blinfbcb2292017-10-16 11:54:14 -0400282 } else if (contactInfo.profileInfo.type == lrc::api::profile::Type::SIP) {
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400283 return QVariant::fromValue(scaleAndFrame(generateAvatar(bestName, "").get(), size, displayPresence, contactInfo.isPresent));
Sébastien Bline72d43c2017-10-03 11:37:33 -0400284 } else if (!contactPhoto.empty()) {
285 QByteArray byteArray(contactPhoto.c_str(), contactPhoto.length());
286 QVariant photo = personPhoto(byteArray);
Nicolas Jagerd64d88e2017-12-11 14:26:00 -0500287 return QVariant::fromValue(scaleAndFrame(photo.value<std::shared_ptr<GdkPixbuf>>().get(), size, displayPresence, contactInfo.isPresent, unreadMessages));
Sébastien Bline72d43c2017-10-03 11:37:33 -0400288 } else {
Nicolas Jagerd64d88e2017-12-11 14:26:00 -0500289 return QVariant::fromValue(scaleAndFrame(generateAvatar(bestName, contactInfo.profileInfo.uri).get(), size, displayPresence, contactInfo.isPresent, unreadMessages));
Sébastien Bline72d43c2017-10-03 11:37:33 -0400290 }
291 }
292 // should not
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400293 return QVariant::fromValue(scaleAndFrame(generateAvatar("", "").get(), size, displayPresence, false));
Sébastien Bline72d43c2017-10-03 11:37:33 -0400294
295}
296
297QVariant
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400298PixbufManipulator::numberCategoryIcon(const QVariant& p, const QSize& size, bool displayPresence, bool isPresent)
299{
300 Q_UNUSED(p)
301 Q_UNUSED(size)
302 Q_UNUSED(displayPresence)
303 Q_UNUSED(isPresent)
304 return QVariant();
305}
306
307QVariant
308PixbufManipulator::securityIssueIcon(const QModelIndex& index)
309{
310 Q_UNUSED(index)
311 return QVariant();
312}
313
314QByteArray
315PixbufManipulator::toByteArray(const QVariant& pxm)
316{
Nicolas Jager1cf27112016-05-17 11:51:28 -0400317 std::shared_ptr<GdkPixbuf> pixbuf_photo = pxm.value<std::shared_ptr<GdkPixbuf>>();
318
319 if(pixbuf_photo.get()) {
320 gchar* png_buffer = nullptr;
321 gsize png_buffer_size;
322 GError *error = nullptr;
323
324 gdk_pixbuf_save_to_buffer(pixbuf_photo.get(), &png_buffer, &png_buffer_size, "png", &error, NULL);
325 QByteArray array = QByteArray(png_buffer, png_buffer_size);
326
327 g_free(png_buffer);
328
329 if (error != NULL) {
330 g_warning("in toByteArray, gdk_pixbuf_save_to_buffer failed : %s\n", error->message);
331 g_clear_error(&error);
332 }
333
334 return array;
335 } else {
336 g_debug("in toByteArray, failed to retrieve data from parameter pxm");
337 return QByteArray();
338 }
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400339}
340
341QVariant
342PixbufManipulator::collectionIcon(const CollectionInterface* interface, PixmapManipulatorI::CollectionIconHint hint) const
343{
344 Q_UNUSED(interface)
345 Q_UNUSED(hint)
346 return QVariant();
347}
348QVariant
349PixbufManipulator::securityLevelIcon(const SecurityEvaluationModel::SecurityLevel level) const
350{
351 Q_UNUSED(level)
352 return QVariant();
353}
354QVariant
355PixbufManipulator::historySortingCategoryIcon(const CategorizedHistoryModel::SortedProxy::Categories cat) const
356{
357 Q_UNUSED(cat)
358 return QVariant();
359}
360QVariant
361PixbufManipulator::contactSortingCategoryIcon(const CategorizedContactModel::SortedProxy::Categories cat) const
362{
363 Q_UNUSED(cat)
364 return QVariant();
365}
366QVariant
367PixbufManipulator::userActionIcon(const UserActionElement& state) const
368{
369 Q_UNUSED(state)
370 return QVariant();
371}
372
Stepan Salenikovich8d076952016-01-08 13:48:43 -0500373QVariant PixbufManipulator::decorationRole(const QModelIndex& index)
374{
375 Q_UNUSED(index)
376 return QVariant();
377}
378
379QVariant PixbufManipulator::decorationRole(const Call* c)
380{
381 Q_UNUSED(c)
382 return QVariant();
383}
384
385QVariant PixbufManipulator::decorationRole(const ContactMethod* cm)
386{
387 Q_UNUSED(cm)
388 return QVariant();
389}
390
391QVariant PixbufManipulator::decorationRole(const Person* p)
392{
393 Q_UNUSED(p)
394 return QVariant();
395}
396
Alexandre Lision50fd6af2016-04-20 17:13:58 -0400397QVariant PixbufManipulator::decorationRole(const Account* p)
398{
399 Q_UNUSED(p)
400 return QVariant();
401}
402
Sébastien Bline72d43c2017-10-03 11:37:33 -0400403QVariant PixbufManipulator::decorationRole(const lrc::api::conversation::Info& conversation,
404 const lrc::api::account::Info& accountInfo)
405{
406 Q_UNUSED(conversation)
407 Q_UNUSED(accountInfo)
408 return QVariant();
409}
410
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400411} // namespace Interfaces