1 /*
2 SPDX-FileCopyrightText: 2005 Jason Harris <kstars@30doradus.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "thumbnailpicker.h"
8
9 #include "kstarsdata.h"
10 #include "ksutils.h"
11 #include "ksnotification.h"
12 #include "skyobjectuserdata.h"
13 #include "thumbnaileditor.h"
14 #include "dialogs/detaildialog.h"
15 #include "skyobjects/skyobject.h"
16
17 #include <KIO/CopyJob>
18 #include <KMessageBox>
19 #include <KJobUiDelegate>
20
21 #include <QDebug>
22
23 #include <QLineEdit>
24 #include <QPainter>
25 #include <QPointer>
26 #include <QScreen>
27 #include <QUrlQuery>
28
ThumbnailPickerUI(QWidget * parent)29 ThumbnailPickerUI::ThumbnailPickerUI(QWidget *parent) : QFrame(parent)
30 {
31 setupUi(this);
32 }
33
ThumbnailPicker(SkyObject * o,const QPixmap & current,QWidget * parent,double _w,double _h,QString cap)34 ThumbnailPicker::ThumbnailPicker(SkyObject *o, const QPixmap ¤t, QWidget *parent, double _w, double _h,
35 QString cap)
36 : QDialog(parent), SelectedImageIndex(-1), Object(o), bImageFound(false)
37 {
38 #ifdef Q_OS_OSX
39 setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
40 #endif
41 thumbWidth = _w;
42 thumbHeight = _h;
43 Image = new QPixmap(current.scaled(_w, _h, Qt::KeepAspectRatio, Qt::FastTransformation));
44 ImageRect = new QRect(0, 0, 200, 200);
45
46 ui = new ThumbnailPickerUI(this);
47
48 setWindowTitle(cap);
49
50 QVBoxLayout *mainLayout = new QVBoxLayout;
51 mainLayout->addWidget(ui);
52 setLayout(mainLayout);
53
54 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
55 mainLayout->addWidget(buttonBox);
56 connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
57 connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
58
59 ui->CurrentImage->setPixmap(*Image);
60
61 connect(ui->EditButton, SIGNAL(clicked()), this, SLOT(slotEditImage()));
62 connect(ui->UnsetButton, SIGNAL(clicked()), this, SLOT(slotUnsetImage()));
63 connect(ui->ImageList, SIGNAL(currentRowChanged(int)), this, SLOT(slotSetFromList(int)));
64 connect(ui->ImageURLBox, SIGNAL(urlSelected(QUrl)), this, SLOT(slotSetFromURL()));
65 connect(ui->ImageURLBox, SIGNAL(returnPressed()), this, SLOT(slotSetFromURL()));
66
67 //ui->ImageURLBox->lineEdit()->setTrapReturnKey( true );
68 ui->EditButton->setEnabled(false);
69
70 slotFillList();
71 }
72
~ThumbnailPicker()73 ThumbnailPicker::~ThumbnailPicker()
74 {
75 while (!PixList.isEmpty())
76 delete PixList.takeFirst();
77 }
78
79 //Query online sources for images of the object
slotFillList()80 void ThumbnailPicker::slotFillList()
81 {
82 //Query Google Image Search:
83
84 //Search for the primary name, or longname and primary name
85 QString sName = QString("%1 ").arg(Object->name());
86 if (Object->longname() != Object->name())
87 {
88 sName = QString("%1 ").arg(Object->longname()) + sName;
89 }
90 QString query =
91 QString("http://www.google.com/search?q=%1&tbs=itp:photo,isz:ex,iszw:200,iszh:200&tbm=isch&source=lnt")
92 .arg(sName);
93 QUrlQuery gURL(query);
94
95 //gURL.addQueryItem( "q", sName ); //add the Google-image query string
96
97 parseGooglePage(gURL.query());
98 }
99
slotProcessGoogleResult(KJob * result)100 void ThumbnailPicker::slotProcessGoogleResult(KJob *result)
101 {
102 //Preload ImageList with the URLs in the object's ImageList:
103 SkyObjectUserdata::LinkList ImageList{
104 KStarsData::Instance()->getUserData(Object->name()).images()
105 };
106
107 if (result->error())
108 {
109 result->uiDelegate()->showErrorMessage();
110 result->kill();
111 return;
112 }
113
114 QString PageHTML(static_cast<KIO::StoredTransferJob *>(result)->data());
115
116 int index = PageHTML.indexOf("src=\"http:", 0);
117 while (index >= 0)
118 {
119 index += 5; //move to end of "src=\"http:" marker
120
121 //Image URL is everything from index to next occurrence of "\""
122 ImageList.push_back(SkyObjectUserdata::LinkData{
123 "", QUrl{ PageHTML.mid(index, PageHTML.indexOf("\"", index) - index) },
124 SkyObjectUserdata::Type::website });
125
126 index = PageHTML.indexOf("src=\"http:", index);
127 }
128
129 //Total Number of images to be loaded:
130 int nImages = ImageList.size();
131 if (nImages)
132 {
133 ui->SearchProgress->setMinimum(0);
134 ui->SearchProgress->setMaximum(nImages - 1);
135 ui->SearchLabel->setText(i18n("Loading images..."));
136 }
137 else
138 {
139 close();
140 return;
141 }
142
143 //Add images from the ImageList
144 for (const auto &image : ImageList)
145 {
146 const QUrl &u{ image.url };
147
148 if (u.isValid())
149 {
150 KIO::StoredTransferJob *j =
151 KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo);
152 j->setUiDelegate(nullptr);
153 connect(j, SIGNAL(result(KJob *)), SLOT(slotJobResult(KJob *)));
154 }
155 }
156 }
157
slotJobResult(KJob * job)158 void ThumbnailPicker::slotJobResult(KJob *job)
159 {
160 KIO::StoredTransferJob *stjob = (KIO::StoredTransferJob *)job;
161
162 //Update Progressbar
163 if (!ui->SearchProgress->isHidden())
164 {
165 ui->SearchProgress->setValue(ui->SearchProgress->value() + 1);
166 if (ui->SearchProgress->value() == ui->SearchProgress->maximum())
167 {
168 ui->SearchProgress->hide();
169 ui->SearchLabel->setText(i18n("Search results:"));
170 }
171 }
172
173 //If there was a problem, just return silently without adding image to list.
174 if (job->error())
175 {
176 qDebug() << " error=" << job->error();
177 job->kill();
178 return;
179 }
180
181 QPixmap *pm = new QPixmap();
182 pm->loadFromData(stjob->data());
183
184 uint w = pm->width();
185 uint h = pm->height();
186 uint pad =
187 0; /*FIXME LATER 4* QDialogBase::marginHint() + 2*ui->SearchLabel->height() + QDialogBase::actionButton( QDialogBase::Ok )->height() + 25;*/
188 uint hDesk = QGuiApplication::primaryScreen()->geometry().height() - pad;
189
190 if (h > hDesk)
191 *pm = pm->scaled(w * hDesk / h, hDesk, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
192
193 PixList.append(pm);
194
195 //Add 50x50 image and URL to listbox
196 //ui->ImageList->insertItem( shrinkImage( PixList.last(), 50 ),
197 // cjob->srcURLs().first().prettyUrl() );
198 ui->ImageList->addItem(new QListWidgetItem(QIcon(shrinkImage(PixList.last(), 200)), stjob->url().url()));
199 }
200
201 //void ThumbnailPicker::parseGooglePage( QStringList &ImList, const QString &URL )
parseGooglePage(const QString & URL)202 void ThumbnailPicker::parseGooglePage(const QString &URL)
203 {
204 QUrl googleURL(URL);
205 KIO::StoredTransferJob *job = KIO::storedGet(googleURL);
206 connect(job, SIGNAL(result(KJob*)), this, SLOT(slotProcessGoogleResult(KJob*)));
207
208 job->start();
209 }
210
shrinkImage(QPixmap * pm,int size,bool setImage)211 QPixmap ThumbnailPicker::shrinkImage(QPixmap *pm, int size, bool setImage)
212 {
213 int w(pm->width()), h(pm->height());
214 int bigSize(w);
215 int rx(0), ry(0), sx(0), sy(0), bx(0), by(0);
216 if (size == 0)
217 return QPixmap();
218
219 //Prepare variables for rescaling image (if it is larger than 'size')
220 if (w > size && w >= h)
221 {
222 h = size;
223 w = size * pm->width() / pm->height();
224 }
225 else if (h > size && h > w)
226 {
227 w = size;
228 h = size * pm->height() / pm->width();
229 }
230 sx = (w - size) / 2;
231 sy = (h - size) / 2;
232 if (sx < 0)
233 {
234 rx = -sx;
235 sx = 0;
236 }
237 if (sy < 0)
238 {
239 ry = -sy;
240 sy = 0;
241 }
242
243 if (setImage)
244 bigSize = int(200. * float(pm->width()) / float(w));
245
246 QPixmap result(size, size);
247 result.fill(Qt::black); //in case final image is smaller than 'size'
248
249 if (pm->width() > size || pm->height() > size) //image larger than 'size'?
250 {
251 //convert to QImage so we can smoothscale it
252 QImage im(pm->toImage());
253 im = im.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
254
255 //bitBlt sizexsize square section of image
256 QPainter p;
257 p.begin(&result);
258 p.drawImage(rx, ry, im, sx, sy, size, size);
259 p.end();
260
261 if (setImage)
262 {
263 bx = int(sx * float(pm->width()) / float(w));
264 by = int(sy * float(pm->width()) / float(w));
265 ImageRect->setRect(bx, by, bigSize, bigSize);
266 }
267 }
268 else //image is smaller than size x size
269 {
270 QPainter p;
271 p.begin(&result);
272 p.drawImage(rx, ry, pm->toImage());
273 p.end();
274
275 if (setImage)
276 {
277 bx = int(rx * float(pm->width()) / float(w));
278 by = int(ry * float(pm->width()) / float(w));
279 ImageRect->setRect(bx, by, bigSize, bigSize);
280 }
281 }
282
283 return result;
284 }
285
slotEditImage()286 void ThumbnailPicker::slotEditImage()
287 {
288 QPointer<ThumbnailEditor> te = new ThumbnailEditor(this, thumbWidth, thumbHeight);
289 if (te->exec() == QDialog::Accepted)
290 {
291 QPixmap pm = te->thumbnail();
292 *Image = pm;
293 ui->CurrentImage->setPixmap(pm);
294 ui->CurrentImage->update();
295 }
296 delete te;
297 }
298
slotUnsetImage()299 void ThumbnailPicker::slotUnsetImage()
300 {
301 // QFile file;
302 //if ( KSUtils::openDataFile( file, "noimage.png" ) ) {
303 // file.close();
304 // Image->load( file.fileName(), "PNG" );
305 // } else {
306 // *Image = Image->scaled( dd->thumbnail()->width(), dd->thumbnail()->height() );
307 // Image->fill( dd->palette().color( QPalette::Window ) );
308 // }
309
310 QPixmap noImage;
311 noImage.load(":/images/noimage.png");
312 Image = new QPixmap(noImage.scaled(thumbWidth, thumbHeight, Qt::KeepAspectRatio, Qt::FastTransformation));
313
314 ui->EditButton->setEnabled(false);
315 ui->CurrentImage->setPixmap(*Image);
316 ui->CurrentImage->update();
317
318 bImageFound = false;
319 }
320
slotSetFromList(int i)321 void ThumbnailPicker::slotSetFromList(int i)
322 {
323 //Display image in preview pane
324 QPixmap pm;
325 pm = shrinkImage(PixList[i], 200, true); //scale image
326 SelectedImageIndex = i;
327
328 ui->CurrentImage->setPixmap(pm);
329 ui->CurrentImage->update();
330 ui->EditButton->setEnabled(true);
331
332 *Image = PixList[i]->scaled(thumbWidth, thumbHeight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
333 bImageFound = true;
334 }
335
slotSetFromURL()336 void ThumbnailPicker::slotSetFromURL()
337 {
338 //Attempt to load the specified URL
339 QUrl u = ui->ImageURLBox->url();
340
341 if (u.isValid())
342 {
343 if (u.isLocalFile())
344 {
345 QFile localFile(u.toLocalFile());
346
347 //Add image to list
348 //If image is taller than desktop, rescale it.
349 QImage im(localFile.fileName());
350
351 if (im.isNull())
352 {
353 KSNotification::sorry(i18n("Failed to load image at %1", localFile.fileName()), i18n("Failed to load image"));
354 return;
355 }
356
357 uint w = im.width();
358 uint h = im.height();
359 uint pad =
360 0; /* FIXME later 4*marginHint() + 2*ui->SearchLabel->height() + actionButton( Ok )->height() + 25; */
361 uint hDesk = QGuiApplication::primaryScreen()->geometry().height() - pad;
362
363 if (h > hDesk)
364 im = im.scaled(w * hDesk / h, hDesk, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
365
366 //Add Image to top of list and 50x50 thumbnail image and URL to top of listbox
367 PixList.insert(0, new QPixmap(QPixmap::fromImage(im)));
368 ui->ImageList->insertItem(0, new QListWidgetItem(QIcon(shrinkImage(PixList.last(), 50)), u.url()));
369
370 //Select the new image
371 ui->ImageList->setCurrentRow(0);
372 slotSetFromList(0);
373 }
374 else
375 {
376 KIO::StoredTransferJob *j = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo);
377 j->setUiDelegate(nullptr);
378 connect(j, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*)));
379 }
380 }
381 }
382