Akonadi Contacts

imagewidget.cpp
1/*
2 This file is part of Contact Editor.
3
4 SPDX-FileCopyrightText: 2009 Tobias Koenig <tokoe@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "imagewidget.h"
10
11#include <KContacts/Addressee>
12
13#include <KIO/TransferJob>
14#include <KLocalizedString>
15#include <KMessageBox>
16#include <KPixmapRegionSelectorDialog>
17#include <KUrlMimeData>
18#include <QApplication>
19#include <QDrag>
20#include <QDragEnterEvent>
21#include <QDropEvent>
22#include <QFileDialog>
23#include <QImageReader>
24#include <QImageWriter>
25#include <QInputDialog>
26#include <QMenu>
27#include <QMimeData>
28#include <QUrl>
29using namespace Akonadi;
30/**
31 * @short Small helper class to load image from network
32 */
33class Akonadi::ImageLoader
34{
35public:
36 ImageLoader(QWidget *parent = nullptr);
37
38 [[nodiscard]] QImage loadImage(const QUrl &url, bool *ok, bool selectPictureSize = true);
39
40private:
41 QWidget *const mParent;
42};
43
44ImageLoader::ImageLoader(QWidget *parent)
45 : mParent(parent)
46{
47}
48
49QImage ImageLoader::loadImage(const QUrl &url, bool *ok, bool selectPictureSize)
50{
51 QImage image;
52
53 if (url.isEmpty()) {
54 return image;
55 }
56
57 (*ok) = false;
58
59 if (url.isLocalFile()) {
60 if (image.load(url.toLocalFile())) {
61 (*ok) = true;
62 }
63 } else {
64 QByteArray imageData;
65 KIO::TransferJob *job = KIO::get(url, KIO::NoReload);
66 QObject::connect(job, &KIO::TransferJob::data, job, [&imageData](KIO::Job *, const QByteArray &data) {
67 imageData.append(data);
68 });
69 if (job->exec()) {
70 if (image.loadFromData(imageData)) {
71 (*ok) = true;
72 }
73 }
74 }
75
76 if (!(*ok)) {
77 // image does not exist (any more)
78 KMessageBox::error(mParent, i18n("This contact's image cannot be found."));
79 return image;
80 }
81
82 if (selectPictureSize) {
83 QPixmap pixmap = QPixmap::fromImage(image);
84 image = KPixmapRegionSelectorDialog::getSelectedImage(pixmap, 1, 1, mParent);
85 if (image.isNull()) {
86 (*ok) = false;
87 return image;
88 }
89 }
90
91 if (image.height() > 720 || image.width() > 720) {
92 if (image.height() > image.width()) {
93 image = image.scaledToHeight(720);
94 } else {
95 image = image.scaledToWidth(720);
96 }
97 }
98
99 (*ok) = true;
100
101 return image;
102}
103
104ImageWidget::ImageWidget(Type type, QWidget *parent)
105 : QPushButton(parent)
106 , mType(type)
107 , mHasImage(false)
108 , mReadOnly(false)
109{
110 setAcceptDrops(true);
111
112 setIconSize(QSize(100, 100));
113 setFixedSize(QSize(120, 120));
114
115 connect(this, &ImageWidget::clicked, this, &ImageWidget::changeImage);
116
117 if (mType == Photo) {
118 setToolTip(i18nc("@info:tooltip", "The photo of the contact (click to change)"));
119 } else {
120 setToolTip(i18nc("@info:tooltip", "The logo of the company (click to change)"));
121 }
122
123 updateView();
124}
125
126ImageWidget::~ImageWidget()
127{
128 delete mImageLoader;
129}
130
131void ImageWidget::loadContact(const KContacts::Addressee &contact)
132{
133 mPicture = (mType == Photo) ? contact.photo() : contact.logo();
134 if (mPicture.isIntern() && !mPicture.data().isNull()) {
135 mHasImage = true;
136 } else if (!mPicture.isIntern() && !mPicture.url().isEmpty()) {
137 mHasImage = true;
138 }
139
140 updateView();
141}
142
143void ImageWidget::storeContact(KContacts::Addressee &contact) const
144{
145 if (mType == Photo) {
146 contact.setPhoto(mPicture);
147 } else {
148 contact.setLogo(mPicture);
149 }
150}
151
152void ImageWidget::setReadOnly(bool readOnly)
153{
154 mReadOnly = readOnly;
155 setAcceptDrops(!mReadOnly);
156}
157
158void ImageWidget::updateView()
159{
160 if (mHasImage) {
161 if (mPicture.isIntern()) {
162 setIcon(QPixmap::fromImage(mPicture.data()));
163 } else {
164 bool ok = false;
165 const QPixmap pix = QPixmap::fromImage(imageLoader()->loadImage(QUrl(mPicture.url()), &ok, false));
166 if (ok) {
167 setIcon(pix);
168 }
169 }
170 } else {
171 if (mType == Photo) {
172 setIcon(QIcon::fromTheme(QStringLiteral("user-identity")));
173 } else {
174 setIcon(QIcon::fromTheme(QStringLiteral("image-x-generic")));
175 }
176 }
177}
178
179void ImageWidget::dragEnterEvent(QDragEnterEvent *event)
180{
181 const QMimeData *mimeData = event->mimeData();
182 event->setAccepted(mimeData->hasImage() || mimeData->hasUrls());
183}
184
185void ImageWidget::dropEvent(QDropEvent *event)
186{
187 if (mReadOnly) {
188 return;
189 }
190
191 const QMimeData *mimeData = event->mimeData();
192 if (mimeData->hasImage()) {
193 mPicture.setData(qvariant_cast<QImage>(mimeData->imageData()));
194 mHasImage = true;
195 updateView();
196 }
197
198 const QList<QUrl> urls = KUrlMimeData::urlsFromMimeData(mimeData);
199 if (urls.isEmpty()) { // oops, no data
200 event->setAccepted(false);
201 } else {
202 bool ok = false;
203 const QImage image = imageLoader()->loadImage(urls.first(), &ok);
204 if (ok) {
205 mPicture.setData(image);
206 mHasImage = true;
207 updateView();
208 }
209 }
210}
211
212void ImageWidget::mousePressEvent(QMouseEvent *event)
213{
214 mDragStartPos = event->pos();
216}
217
218void ImageWidget::mouseMoveEvent(QMouseEvent *event)
219{
220 if ((event->buttons() & Qt::LeftButton) && (event->pos() - mDragStartPos).manhattanLength() > QApplication::startDragDistance()) {
221 if (mHasImage) {
222 auto drag = new QDrag(this);
223 drag->setMimeData(new QMimeData());
224 drag->mimeData()->setImageData(mPicture.data());
225 drag->exec(Qt::CopyAction);
226 }
227 }
228}
229
230void ImageWidget::contextMenuEvent(QContextMenuEvent *event)
231{
232 QMenu menu;
233
234 if (mType == Photo) {
235 if (!mReadOnly) {
236 menu.addAction(i18n("Change photo..."), this, &ImageWidget::changeImage);
237 menu.addAction(i18n("Change URL..."), this, &ImageWidget::changeUrl);
238 }
239
240 if (mHasImage) {
241 menu.addAction(i18n("Save photo..."), this, &ImageWidget::saveImage);
242
243 if (!mReadOnly) {
244 menu.addAction(i18n("Remove photo"), this, &ImageWidget::deleteImage);
245 }
246 }
247 } else {
248 if (!mReadOnly) {
249 menu.addAction(i18n("Change logo..."), this, &ImageWidget::changeImage);
250 menu.addAction(i18n("Change URL..."), this, &ImageWidget::changeUrl);
251 }
252
253 if (mHasImage) {
254 menu.addAction(i18n("Save logo..."), this, &ImageWidget::saveImage);
255
256 if (!mReadOnly) {
257 menu.addAction(i18n("Remove logo"), this, &ImageWidget::deleteImage);
258 }
259 }
260 }
261
262 menu.exec(event->globalPos());
263}
264
265void ImageWidget::changeUrl()
266{
267 if (mReadOnly) {
268 return;
269 }
270 bool okPath = false;
271 const QString path = QInputDialog::getText(this, i18n("Change image URL"), i18n("Image URL:"), QLineEdit::Normal, mPicture.url(), &okPath);
272 if (okPath) {
273 if (!path.isEmpty()) {
274 bool ok;
275 const QImage image = imageLoader()->loadImage(QUrl(path), &ok, false);
276 if (ok && !image.isNull()) {
277 mPicture.setUrl(path);
278 mHasImage = true;
279 updateView();
280 }
281 }
282 }
283}
284
285void ImageWidget::changeImage()
286{
287 if (mReadOnly) {
288 return;
289 }
290
291 const QList<QByteArray> supportedImage = QImageReader::supportedImageFormats();
292 QString filter;
293 for (const QByteArray &ba : supportedImage) {
294 if (!filter.isEmpty()) {
295 filter += QLatin1Char(' ');
296 }
297 filter += QLatin1StringView("*.") + QString::fromLatin1(ba);
298 }
299
300 const QUrl url = QFileDialog::getOpenFileUrl(this, QString(), QUrl(), i18n("Images (%1)", filter));
301 if (url.isValid()) {
302 bool ok = false;
303 const QImage image = imageLoader()->loadImage(url, &ok);
304 if (ok) {
305 mPicture.setData(image);
306 mHasImage = true;
307 updateView();
308 }
309 }
310}
311
312void ImageWidget::saveImage()
313{
314 const QList<QByteArray> supportedImage = QImageWriter::supportedImageFormats();
315 QString filter;
316 for (const QByteArray &ba : supportedImage) {
317 if (!filter.isEmpty()) {
318 filter += QLatin1Char(' ');
319 }
320 filter += QLatin1StringView("*.") + QString::fromLatin1(ba);
321 }
322
323 const QString fileName = QFileDialog::getSaveFileName(this, QString(), QString(), i18n("Images (%1)", filter));
324 if (!fileName.isEmpty()) {
325 mPicture.data().save(fileName);
326 }
327}
328
329void ImageWidget::deleteImage()
330{
331 mHasImage = false;
332 mPicture.setData(QImage());
333 mPicture.setUrl(QString());
334 updateView();
335}
336
337ImageLoader *ImageWidget::imageLoader()
338{
339 if (!mImageLoader) {
340 mImageLoader = new ImageLoader;
341 }
342
343 return mImageLoader;
344}
345
346#include "moc_imagewidget.cpp"
void setLogo(const Picture &logo)
Picture photo() const
Picture logo() const
void setPhoto(const Picture &photo)
void data(KIO::Job *job, const QByteArray &data)
bool exec()
static QImage getSelectedImage(const QPixmap &pixmap, int aspectRatioWidth, int aspectRatioHeight, QWidget *parent=nullptr)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
A widget for editing the display name of a contact.
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
QString path(const QString &relativePath)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KCOREADDONS_EXPORT QList< QUrl > urlsFromMimeData(const QMimeData *mimeData, DecodeOptions decodeOptions=PreferKdeUrls, MetaDataMap *metaData=nullptr)
void clicked(bool checked)
virtual bool event(QEvent *e) override
void setIcon(const QIcon &icon)
virtual void mousePressEvent(QMouseEvent *e) override
QByteArray & append(QByteArrayView data)
QUrl getOpenFileUrl(QWidget *parent, const QString &caption, const QUrl &dir, const QString &filter, QString *selectedFilter, Options options, const QStringList &supportedSchemes)
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options)
QIcon fromTheme(const QString &name)
int height() const const
bool isNull() const const
bool load(QIODevice *device, const char *format)
bool loadFromData(QByteArrayView data, const char *format)
QImage scaledToHeight(int height, Qt::TransformationMode mode) const const
QImage scaledToWidth(int width, Qt::TransformationMode mode) const const
int width() const const
QList< QByteArray > supportedImageFormats()
QList< QByteArray > supportedImageFormats()
QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode, const QString &text, bool *ok, Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints)
T & first()
bool isEmpty() const const
bool hasImage() const const
bool hasUrls() const const
QVariant imageData() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QPixmap fromImage(QImage &&image, Qt::ImageConversionFlags flags)
QMenu * menu() const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
CopyAction
LeftButton
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool isEmpty() const const
bool isLocalFile() const const
bool isValid() const const
QString toLocalFile() const const
void setAcceptDrops(bool on)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri May 2 2025 11:52:44 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.