Show invite menu in wlm chat window immediately
[kdenetwork.git] / kopete / libkopete / kopeteavatarmanager.cpp
blob668ae0ad6cb71e74beb9b4e90f9301543cdb2045
1 /*
2 kopeteavatarmanager.cpp - Global avatar manager
4 Copyright (c) 2007 by Michaƫl Larouche <larouche@kde.org>
6 Kopete (c) 2002-2007 by the Kopete developers <kopete-devel@kde.org>
8 *************************************************************************
9 * *
10 * This library is free software; you can redistribute it and/or *
11 * modify it under the terms of the GNU Lesser General Public *
12 * License as published by the Free Software Foundation; either *
13 * version 2 of the License, or (at your option) any later version. *
14 * *
15 *************************************************************************
17 #include "kopeteavatarmanager.h"
19 // Qt includes
20 #include <QtCore/QLatin1String>
21 #include <QtCore/QBuffer>
22 #include <QtCore/QCoreApplication>
23 #include <QtCore/QFile>
24 #include <QtCore/QPointer>
25 #include <QtCore/QStringList>
26 #include <QtCore/QDir>
27 #include <QtGui/QPainter>
28 #include <QtGui/QImageReader>
30 // KDE includes
31 #include <kdebug.h>
32 #include <kstandarddirs.h>
33 #include <kconfig.h>
34 #include <kcodecs.h>
35 #include <kurl.h>
36 #include <kio/job.h>
37 #include <kio/netaccess.h>
38 #include <klocale.h>
40 // Kopete includes
41 #include <kopetecontact.h>
42 #include <kopeteaccount.h>
44 namespace Kopete
47 //BEGIN AvatarManager
48 AvatarManager *AvatarManager::s_self = 0;
50 AvatarManager *AvatarManager::self()
52 if( !s_self )
54 s_self = new AvatarManager;
56 return s_self;
59 class AvatarManager::Private
61 public:
62 KUrl baseDir;
64 /**
65 * Create directory if needed
66 * @param directory URL of the directory to create
68 void createDirectory(const KUrl &directory);
70 /**
71 * Scale the given image to 96x96.
72 * @param source Original image
74 QImage scaleImage(const QImage &source);
77 static const QString UserDir("User");
78 static const QString ContactDir("Contacts");
79 static const QString AvatarConfig("avatarconfig.rc");
82 AvatarManager::AvatarManager(QObject *parent)
83 : QObject(parent), d(new Private)
85 // Locate avatar data dir on disk
86 d->baseDir = KUrl( KStandardDirs::locateLocal("appdata", QLatin1String("avatars") ) );
88 // Create directory on disk, if necessary
89 d->createDirectory( d->baseDir );
92 AvatarManager::~AvatarManager()
94 delete d;
97 Kopete::AvatarManager::AvatarEntry AvatarManager::add(Kopete::AvatarManager::AvatarEntry newEntry)
99 Q_ASSERT(!newEntry.name.isEmpty());
101 KUrl avatarUrl(d->baseDir);
103 // First find where to save the file
104 switch(newEntry.category)
106 case AvatarManager::User:
107 avatarUrl.addPath( UserDir );
108 d->createDirectory( avatarUrl );
109 break;
110 case AvatarManager::Contact:
111 avatarUrl.addPath( ContactDir );
112 d->createDirectory( avatarUrl );
113 // Use the account id for sub directory
114 if( newEntry.contact && newEntry.contact->account() )
116 QString accountName = newEntry.contact->account()->accountId();
117 avatarUrl.addPath( accountName );
118 d->createDirectory( avatarUrl );
120 break;
121 default:
122 break;
125 KUrl dataUrl(avatarUrl);
127 kDebug(14010) << "Base directory: " << avatarUrl.path();
129 // Second, open the avatar configuration in current directory.
130 KUrl configUrl = avatarUrl;
131 configUrl.addPath( AvatarConfig );
133 QByteArray data = newEntry.data;
134 QImage avatar = newEntry.image;
136 if (!data.isNull())
138 avatar.loadFromData(data);
140 else if(!newEntry.dataPath.isEmpty()){
141 QFile f(newEntry.dataPath);
142 f.open(QIODevice::ReadOnly);
143 data = f.readAll();
144 f.close();
146 avatar.loadFromData(data);
148 else if(!avatar.isNull()){
149 QByteArray tempArray;
150 QBuffer tempBuffer(&tempArray);
151 tempBuffer.open( QIODevice::WriteOnly );
152 avatar.save(&tempBuffer, "PNG");
154 data = tempArray;
156 else if(!newEntry.path.isEmpty()){
157 avatar = QImage(newEntry.path);
159 QFile f(newEntry.path);
160 f.open(QIODevice::ReadOnly);
161 data = f.readAll();
162 f.close();
164 else
166 kDebug() << "Warning: No valid image source!";
169 // Scale avatar
170 avatar = d->scaleImage(avatar);
172 QString avatarFilename;
174 // for the contact avatar, save it with the contactId + .png
175 if (newEntry.category == AvatarManager::Contact && newEntry.contact)
177 avatarFilename = newEntry.contact->contactId() + ".png";
179 else
181 // Find MD5 hash for filename
182 // MD5 always contain ASCII caracteres so it is more save for a filename.
183 // Name can use UTF-8 characters.
184 QByteArray tempArray;
185 QBuffer tempBuffer(&tempArray);
186 tempBuffer.open( QIODevice::WriteOnly );
187 avatar.save(&tempBuffer, "PNG");
188 KMD5 context(tempArray);
189 avatarFilename = context.hexDigest() + QLatin1String(".png");
192 // Save image on disk
193 kDebug(14010) << "Saving " << avatarFilename << " on disk.";
194 avatarUrl.addPath( avatarFilename );
196 if( !avatar.save( avatarUrl.path(), "PNG") )
198 kDebug(14010) << "Saving of " << avatarUrl.path() << " failed !";
199 return AvatarEntry();
202 QString dataFilename;
204 // for the contact avatar, save it with the contactId + .png
205 if (newEntry.category == AvatarManager::Contact && newEntry.contact)
207 dataFilename = newEntry.contact->contactId() + QLatin1String("_");
210 dataFilename += KMD5(data).hexDigest();
212 QBuffer buffer(&data);
213 buffer.open(QIODevice::ReadOnly);
214 QImageReader ir(&buffer);
215 dataFilename += QLatin1String(".") + QLatin1String(ir.format());
217 // Save (original) data on disk
218 dataUrl.addPath(dataFilename);
219 QFile f(dataUrl.path());
220 if (!f.open(QIODevice::WriteOnly))
222 kDebug(14010) << "Saving of " << dataUrl.path() << " failed !";
223 return AvatarEntry();
225 f.write(data);
226 f.flush();
227 f.close();
229 // Save metadata of image
230 KConfigGroup avatarConfig(KSharedConfig::openConfig( configUrl.path(), KConfig::SimpleConfig), newEntry.name );
232 avatarConfig.writeEntry( "Filename", avatarFilename );
233 avatarConfig.writeEntry( "DataFilename", dataFilename );
234 avatarConfig.writeEntry( "Category", int(newEntry.category) );
236 avatarConfig.sync();
238 // Add final path to the new entry for avatarAdded signal
239 newEntry.path = avatarUrl.path();
240 newEntry.dataPath = dataUrl.path();
242 emit avatarAdded(newEntry);
244 return newEntry;
247 bool AvatarManager::remove(Kopete::AvatarManager::AvatarEntry entryToRemove)
249 // We need name and path to remove an avatar from the storage.
250 if( entryToRemove.name.isEmpty() && entryToRemove.path.isEmpty() )
251 return false;
253 // We don't allow removing avatars from Contact category
254 if( entryToRemove.category & Kopete::AvatarManager::Contact )
255 return false;
257 // Delete the image file first, file delete is more likely to fail than config group remove.
258 if( KIO::NetAccess::del(KUrl(entryToRemove.path),0) )
260 kDebug(14010) << "Removing avatar from config.";
262 KUrl configUrl(d->baseDir);
263 configUrl.addPath( UserDir );
264 configUrl.addPath( AvatarConfig );
266 KConfigGroup avatarConfig ( KSharedConfig::openConfig( configUrl.path(), KConfig::SimpleConfig ), entryToRemove.name );
267 avatarConfig.deleteGroup();
268 avatarConfig.sync();
270 emit avatarRemoved(entryToRemove);
272 return true;
275 return false;
278 void AvatarManager::Private::createDirectory(const KUrl &directory)
280 if( !QFile::exists(directory.path()) )
282 kDebug(14010) << "Creating directory: " << directory.path();
283 if( !KIO::NetAccess::mkdir(directory,0) )
285 kDebug(14010) << "Directory " << directory.path() <<" creating failed.";
290 QImage AvatarManager::Private::scaleImage(const QImage &source)
292 if (source.isNull())
294 return QImage();
297 //make an empty image and fill with transparent color
298 QImage result(96, 96, QImage::Format_ARGB32);
299 result.fill(0);
301 QPainter paint(&result);
302 float x = 0, y = 0;
304 // scale and center the image
305 if( source.width() > 96 || source.height() > 96 )
307 QImage scaled = source.scaled( 96, 96, Qt::KeepAspectRatio, Qt::SmoothTransformation );
309 x = (96 - scaled.width()) / 2.0;
310 y = (96 - scaled.height()) / 2.0;
312 paint.translate(x, y);
313 paint.drawImage(QPoint(0, 0), scaled);
314 } else {
315 x = (96 - source.width()) / 2.0;
316 y = (96 - source.height()) / 2.0;
318 paint.translate(x, y);
319 paint.drawImage(QPoint(0, 0), source);
322 return result;
325 //END AvatarManager
327 //BEGIN AvatarQueryJob
328 class AvatarQueryJob::Private
330 public:
331 Private(AvatarQueryJob *parent)
332 : queryJob(parent), category(AvatarManager::All)
335 QPointer<AvatarQueryJob> queryJob;
336 AvatarManager::AvatarCategory category;
337 QList<AvatarManager::AvatarEntry> avatarList;
338 KUrl baseDir;
340 void listAvatarDirectory(const QString &path);
343 AvatarQueryJob::AvatarQueryJob(QObject *parent)
344 : KJob(parent), d(new Private(this))
349 AvatarQueryJob::~AvatarQueryJob()
351 delete d;
354 void AvatarQueryJob::setQueryFilter(Kopete::AvatarManager::AvatarCategory category)
356 d->category = category;
359 void AvatarQueryJob::start()
361 d->baseDir = KUrl( KStandardDirs::locateLocal("appdata", QLatin1String("avatars") ) );
363 if( d->category & Kopete::AvatarManager::User )
365 d->listAvatarDirectory( UserDir );
367 if( d->category & Kopete::AvatarManager::Contact )
369 KUrl contactUrl(d->baseDir);
370 contactUrl.addPath( ContactDir );
372 QDir contactDir(contactUrl.path());
373 QStringList subdirsList = contactDir.entryList( QDir::AllDirs | QDir::NoDotAndDotDot );
374 foreach(const QString &subdir, subdirsList)
376 d->listAvatarDirectory( ContactDir + QDir::separator() + subdir );
380 // Finish the job
381 emitResult();
384 QList<Kopete::AvatarManager::AvatarEntry> AvatarQueryJob::avatarList() const
386 return d->avatarList;
389 void AvatarQueryJob::Private::listAvatarDirectory(const QString &relativeDirectory)
391 KUrl avatarDirectory = baseDir;
392 avatarDirectory.addPath(relativeDirectory);
394 kDebug(14010) << "Listing avatars in " << avatarDirectory.path();
396 // Look for Avatar configuration
397 KUrl avatarConfigUrl = avatarDirectory;
398 avatarConfigUrl.addPath( AvatarConfig );
399 if( QFile::exists(avatarConfigUrl.path()) )
401 KConfig *avatarConfig = new KConfig( avatarConfigUrl.path(), KConfig::SimpleConfig);
402 // Each avatar entry in configuration is a group
403 QStringList groupEntryList = avatarConfig->groupList();
404 foreach(const QString &groupEntry, groupEntryList)
406 KConfigGroup cg(avatarConfig, groupEntry);
408 Kopete::AvatarManager::AvatarEntry listedEntry;
409 listedEntry.name = groupEntry;
410 listedEntry.category = static_cast<Kopete::AvatarManager::AvatarCategory>( cg.readEntry("Category", 0) );
412 QString filename = cg.readEntry( "Filename", QString() );
413 KUrl avatarPath(avatarDirectory);
414 avatarPath.addPath( filename );
415 listedEntry.path = avatarPath.path();
417 QString dataFilename = cg.readEntry( "DataFilename", QString() );
418 KUrl dataPath(avatarDirectory);
419 dataPath.addPath( dataFilename );
420 listedEntry.dataPath = dataPath.path();
422 avatarList << listedEntry;
424 delete avatarConfig;
428 //END AvatarQueryJob
432 #include "kopeteavatarmanager.moc"