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 , mImageLoader(nullptr)
107 , mType(type)
108 , mHasImage(false)
109 , mReadOnly(false)
110{
111 setAcceptDrops(true);
112
113 setIconSize(QSize(100, 100));
114 setFixedSize(QSize(120, 120));
115
116 connect(this, &ImageWidget::clicked, this, &ImageWidget::changeImage);
117
118 if (mType == Photo) {
119 setToolTip(i18n("The photo of the contact (click to change)"));
120 } else {
121 setToolTip(i18n("The logo of the company (click to change)"));
122 }
123
124 updateView();
125}
126
127ImageWidget::~ImageWidget()
128{
129 delete mImageLoader;
130}
131
132void ImageWidget::loadContact(const KContacts::Addressee &contact)
133{
134 mPicture = (mType == Photo) ? contact.photo() : contact.logo();
135 if (mPicture.isIntern() && !mPicture.data().isNull()) {
136 mHasImage = true;
137 } else if (!mPicture.isIntern() && !mPicture.url().isEmpty()) {
138 mHasImage = true;
139 }
140
141 updateView();
142}
143
144void ImageWidget::storeContact(KContacts::Addressee &contact) const
145{
146 if (mType == Photo) {
147 contact.setPhoto(mPicture);
148 } else {
149 contact.setLogo(mPicture);
150 }
151}
152
153void ImageWidget::setReadOnly(bool readOnly)
154{
155 mReadOnly = readOnly;
156 setAcceptDrops(!mReadOnly);
157}
158
159void ImageWidget::updateView()
160{
161 if (mHasImage) {
162 if (mPicture.isIntern()) {
163 setIcon(QPixmap::fromImage(mPicture.data()));
164 } else {
165 bool ok = false;
166 const QPixmap pix = QPixmap::fromImage(imageLoader()->loadImage(QUrl(mPicture.url()), &ok, false));
167 if (ok) {
168 setIcon(pix);
169 }
170 }
171 } else {
172 if (mType == Photo) {
173 setIcon(QIcon::fromTheme(QStringLiteral("user-identity")));
174 } else {
175 setIcon(QIcon::fromTheme(QStringLiteral("image-x-generic")));
176 }
177 }
178}
179
180void ImageWidget::dragEnterEvent(QDragEnterEvent *event)
181{
182 const QMimeData *mimeData = event->mimeData();
183 event->setAccepted(mimeData->hasImage() || mimeData->hasUrls());
184}
185
186void ImageWidget::dropEvent(QDropEvent *event)
187{
188 if (mReadOnly) {
189 return;
190 }
191
192 const QMimeData *mimeData = event->mimeData();
193 if (mimeData->hasImage()) {
194 mPicture.setData(qvariant_cast<QImage>(mimeData->imageData()));
195 mHasImage = true;
196 updateView();
197 }
198
199 const QList<QUrl> urls = KUrlMimeData::urlsFromMimeData(mimeData);
200 if (urls.isEmpty()) { // oops, no data
201 event->setAccepted(false);
202 } else {
203 bool ok = false;
204 const QImage image = imageLoader()->loadImage(urls.first(), &ok);
205 if (ok) {
206 mPicture.setData(image);
207 mHasImage = true;
208 updateView();
209 }
210 }
211}
212
213void ImageWidget::mousePressEvent(QMouseEvent *event)
214{
215 mDragStartPos = event->pos();
217}
218
219void ImageWidget::mouseMoveEvent(QMouseEvent *event)
220{
221 if ((event->buttons() & Qt::LeftButton) && (event->pos() - mDragStartPos).manhattanLength() > QApplication::startDragDistance()) {
222 if (mHasImage) {
223 auto drag = new QDrag(this);
224 drag->setMimeData(new QMimeData());
225 drag->mimeData()->setImageData(mPicture.data());
226 drag->exec(Qt::CopyAction);
227 }
228 }
229}
230
231void ImageWidget::contextMenuEvent(QContextMenuEvent *event)
232{
233 QMenu menu;
234
235 if (mType == Photo) {
236 if (!mReadOnly) {
237 menu.addAction(i18n("Change photo..."), this, &ImageWidget::changeImage);
238 menu.addAction(i18n("Change URL..."), this, &ImageWidget::changeUrl);
239 }
240
241 if (mHasImage) {
242 menu.addAction(i18n("Save photo..."), this, &ImageWidget::saveImage);
243
244 if (!mReadOnly) {
245 menu.addAction(i18n("Remove photo"), this, &ImageWidget::deleteImage);
246 }
247 }
248 } else {
249 if (!mReadOnly) {
250 menu.addAction(i18n("Change logo..."), this, &ImageWidget::changeImage);
251 menu.addAction(i18n("Change URL..."), this, &ImageWidget::changeUrl);
252 }
253
254 if (mHasImage) {
255 menu.addAction(i18n("Save logo..."), this, &ImageWidget::saveImage);
256
257 if (!mReadOnly) {
258 menu.addAction(i18n("Remove logo"), this, &ImageWidget::deleteImage);
259 }
260 }
261 }
262
263 menu.exec(event->globalPos());
264}
265
266void ImageWidget::changeUrl()
267{
268 if (mReadOnly) {
269 return;
270 }
271 bool okPath = false;
272 const QString path = QInputDialog::getText(this, i18n("Change image URL"), i18n("Image URL:"), QLineEdit::Normal, mPicture.url(), &okPath);
273 if (okPath) {
274 if (!path.isEmpty()) {
275 bool ok;
276 const QImage image = imageLoader()->loadImage(QUrl(path), &ok, false);
277 if (ok && !image.isNull()) {
278 mPicture.setUrl(path);
279 mHasImage = true;
280 updateView();
281 }
282 }
283 }
284}
285
286void ImageWidget::changeImage()
287{
288 if (mReadOnly) {
289 return;
290 }
291
294 for (const QByteArray &ba : supportedImage) {
295 if (!filter.isEmpty()) {
296 filter += QLatin1Char(' ');
297 }
298 filter += QLatin1StringView("*.") + QString::fromLatin1(ba);
299 }
300
301 const QUrl url = QFileDialog::getOpenFileUrl(this, QString(), QUrl(), i18n("Images (%1)", filter));
302 if (url.isValid()) {
303 bool ok = false;
304 const QImage image = imageLoader()->loadImage(url, &ok);
305 if (ok) {
306 mPicture.setData(image);
307 mHasImage = true;
308 updateView();
309 }
310 }
311}
312
313void ImageWidget::saveImage()
314{
317 for (const QByteArray &ba : supportedImage) {
318 if (!filter.isEmpty()) {
319 filter += QLatin1Char(' ');
320 }
321 filter += QLatin1StringView("*.") + QString::fromLatin1(ba);
322 }
323
324 const QString fileName = QFileDialog::getSaveFileName(this, QString(), QString(), i18n("Images (%1)", filter));
325 if (!fileName.isEmpty()) {
326 mPicture.data().save(fileName);
327 }
328}
329
330void ImageWidget::deleteImage()
331{
332 mHasImage = false;
333 mPicture.setData(QImage());
334 mPicture.setUrl(QString());
335 updateView();
336}
337
338ImageLoader *ImageWidget::imageLoader()
339{
340 if (!mImageLoader) {
341 mImageLoader = new ImageLoader;
342 }
343
344 return mImageLoader;
345}
346
347#include "moc_imagewidget.cpp"
void setLogo(const Picture &logo)
Picture photo() const
Picture logo() const
void setPhoto(const Picture &photo)
void setUrl(const QString &url)
QImage data() const
bool isIntern() const
void setData(const QImage &data)
QString url() const
void data(KIO::Job *job, const QByteArray &data)
bool exec()
static QImage getSelectedImage(const QPixmap &pixmap, int aspectRatioWidth, int aspectRatioHeight, QWidget *parent=nullptr)
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 setIcon(const QIcon &icon)
virtual void mousePressEvent(QMouseEvent *e) override
QByteArray & append(char ch)
QUrl getOpenFileUrl(QWidget *parent, const QString &caption, const QUrl &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options, const QStringList &supportedSchemes)
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
QIcon fromTheme(const QString &name)
int height() const const
bool isNull() const const
bool load(QIODevice *device, const char *format)
bool loadFromData(const uchar *data, int len, const char *format)
bool save(const QString &fileName, const char *format, int quality) const const
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
QAction * addAction(const QString &text)
QAction * exec()
bool hasImage() const const
bool hasUrls() const const
QVariant imageData() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags)
virtual bool event(QEvent *e) override
QMenu * menu() const const
QString fromLatin1(const char *str, int size)
bool isEmpty() const const
CopyAction
LeftButton
QFuture< void > filter(Sequence &sequence, KeepFunctor filterFunction)
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-2024 The KDE developers.
Generated on Sun Feb 25 2024 18:38:22 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.