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 &current, 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