Akonadi Contacts

imagewidget.cpp
1 /*
2  This file is part of Contact Editor.
3 
4  SPDX-FileCopyrightText: 2009 Tobias Koenig <[email protected]>
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>
29 /**
30  * @short Small helper class to load image from network
31  */
32 class ImageLoader
33 {
34 public:
35  ImageLoader(QWidget *parent = nullptr);
36 
37  Q_REQUIRED_RESULT QImage loadImage(const QUrl &url, bool *ok, bool selectPictureSize = true);
38 
39 private:
40  QWidget *const mParent;
41 };
42 
43 ImageLoader::ImageLoader(QWidget *parent)
44  : mParent(parent)
45 {
46 }
47 
48 QImage ImageLoader::loadImage(const QUrl &url, bool *ok, bool selectPictureSize)
49 {
50  QImage image;
51 
52  if (url.isEmpty()) {
53  return image;
54  }
55 
56  (*ok) = false;
57 
58  if (url.isLocalFile()) {
59  if (image.load(url.toLocalFile())) {
60  (*ok) = true;
61  }
62  } else {
63  QByteArray imageData;
64  KIO::TransferJob *job = KIO::get(url, KIO::NoReload);
65  QObject::connect(job, &KIO::TransferJob::data, [&imageData](KIO::Job *, const QByteArray &data) {
66  imageData.append(data);
67  });
68  if (job->exec()) {
69  if (image.loadFromData(imageData)) {
70  (*ok) = true;
71  }
72  }
73  }
74 
75  if (!(*ok)) {
76  // image does not exist (any more)
77  KMessageBox::error(mParent, i18n("This contact's image cannot be found."));
78  return image;
79  }
80 
81  if (selectPictureSize) {
82  QPixmap pixmap = QPixmap::fromImage(image);
83  image = KPixmapRegionSelectorDialog::getSelectedImage(pixmap, 1, 1, mParent);
84  if (image.isNull()) {
85  (*ok) = false;
86  return image;
87  }
88  }
89 
90  if (image.height() > 720 || image.width() > 720) {
91  if (image.height() > image.width()) {
92  image = image.scaledToHeight(720);
93  } else {
94  image = image.scaledToWidth(720);
95  }
96  }
97 
98  (*ok) = true;
99 
100  return image;
101 }
102 
103 ImageWidget::ImageWidget(Type type, QWidget *parent)
104  : QPushButton(parent)
105  , mImageLoader(nullptr)
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(i18n("The photo of the contact (click to change)"));
119  } else {
120  setToolTip(i18n("The logo of the company (click to change)"));
121  }
122 
123  updateView();
124 }
125 
126 ImageWidget::~ImageWidget()
127 {
128  delete mImageLoader;
129 }
130 
131 void 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 
143 void ImageWidget::storeContact(KContacts::Addressee &contact) const
144 {
145  if (mType == Photo) {
146  contact.setPhoto(mPicture);
147  } else {
148  contact.setLogo(mPicture);
149  }
150 }
151 
152 void ImageWidget::setReadOnly(bool readOnly)
153 {
154  mReadOnly = readOnly;
155  setAcceptDrops(!mReadOnly);
156 }
157 
158 void 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 
179 void ImageWidget::dragEnterEvent(QDragEnterEvent *event)
180 {
181  const QMimeData *mimeData = event->mimeData();
182  event->setAccepted(mimeData->hasImage() || mimeData->hasUrls());
183 }
184 
185 void 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 
212 void ImageWidget::mousePressEvent(QMouseEvent *event)
213 {
214  mDragStartPos = event->pos();
216 }
217 
218 void 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 
230 void 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 
265 void 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 
285 void ImageWidget::changeImage()
286 {
287  if (mReadOnly) {
288  return;
289  }
290 
292  QString filter;
293  for (const QByteArray &ba : supportedImage) {
294  if (!filter.isEmpty()) {
295  filter += QLatin1Char(' ');
296  }
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 
312 void ImageWidget::saveImage()
313 {
315  QString filter;
316  for (const QByteArray &ba : supportedImage) {
317  if (!filter.isEmpty()) {
318  filter += QLatin1Char(' ');
319  }
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 
329 void ImageWidget::deleteImage()
330 {
331  mHasImage = false;
332  mPicture.setData(QImage());
333  mPicture.setUrl(QString());
334  updateView();
335 }
336 
337 ImageLoader *ImageWidget::imageLoader()
338 {
339  if (!mImageLoader) {
340  mImageLoader = new ImageLoader;
341  }
342 
343  return mImageLoader;
344 }
QImage scaledToWidth(int width, Qt::TransformationMode mode) const const
T & first()
QUrl getOpenFileUrl(QWidget *parent, const QString &caption, const QUrl &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options, const QStringList &supportedSchemes)
void setPhoto(const Picture &photo)
QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags)
int height() const const
void setData(const QString &mimeType, const QByteArray &data)
Type type(const QSqlDatabase &db)
QByteArray & append(char ch)
void setLogo(const Picture &logo)
QIcon fromTheme(const QString &name)
QVariant imageData() const const
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
LeftButton
QList< QByteArray > supportedImageFormats()
bool loadFromData(const uchar *data, int len, const char *format)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
bool load(QIODevice *device, const char *format)
Picture logo() const
QAction * addAction(const QString &text)
bool isValid() const const
QString i18n(const char *text, const TYPE &arg...)
bool isEmpty() const const
bool isEmpty() const const
QImage scaledToHeight(int height, Qt::TransformationMode mode) const const
bool isNull() const const
static QImage getSelectedImage(const QPixmap &pixmap, int aspectRatioWidth, int aspectRatioHeight, QWidget *parent=nullptr)
QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode, const QString &text, bool *ok, Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints)
QFuture< void > filter(Sequence &sequence, KeepFunctor filterFunction)
bool isEmpty() const const
QString toLocalFile() const const
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
CopyAction
bool hasUrls() const const
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
QString path(const QString &relativePath)
QString fromLatin1(const char *str, int size)
bool isLocalFile() const const
virtual void mousePressEvent(QMouseEvent *e) override
QList< QByteArray > supportedImageFormats()
KCOREADDONS_EXPORT QList< QUrl > urlsFromMimeData(const QMimeData *mimeData, DecodeOptions decodeOptions=PreferKdeUrls, MetaDataMap *metaData=nullptr)
bool hasImage() const const
Picture photo() const
void data(KIO::Job *job, const QByteArray &data)
bool exec()
QAction * exec()
int width() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sat Apr 1 2023 04:09:05 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.