1 /** ===========================================================
2  * @file
3  *
4  * This file is a part of digiKam project
5  * <a href="https://www.digikam.org">https://www.digikam.org</a>
6  *
7  * @date   2014-09-12
8  * @brief  Simple helper widgets collection
9  *
10  * @author Copyright (C) 2014-2015 by Gilles Caulier
11  *         <a href="mailto:caulier dot gilles at gmail dot com">caulier dot gilles at gmail dot com</a>
12  *
13  * This program is free software; you can redistribute it
14  * and/or modify it under the terms of the GNU General
15  * Public License as published by the Free Software Foundation;
16  * either version 2, or (at your option)
17  * any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22  * GNU General Public License for more details.
23  *
24  * ============================================================ */
25 
26 #include "rwidgetutils.h"
27 
28 // Qt includes
29 
30 #include <QWidget>
31 #include <QByteArray>
32 #include <QBuffer>
33 #include <QImage>
34 #include <QHBoxLayout>
35 #include <QVBoxLayout>
36 #include <QApplication>
37 #include <QScreen>
38 #include <QDesktopWidget>
39 #include <QPushButton>
40 #include <QFileInfo>
41 #include <QPainter>
42 #include <QStandardPaths>
43 #include <QVector>
44 #include <QColorDialog>
45 #include <QStyleOptionButton>
46 #include <qdrawutil.h>
47 
48 // KDE includes
49 
50 #include <klocalizedstring.h>
51 
52 // Local includes
53 
54 #include "libkdcraw_debug.h"
55 
56 namespace KDcrawIface
57 {
58 
RActiveLabel(const QUrl & url,const QString & imgPath,QWidget * const parent)59 RActiveLabel::RActiveLabel(const QUrl& url, const QString& imgPath, QWidget* const parent)
60     : QLabel(parent)
61 {
62     setMargin(0);
63     setScaledContents(false);
64     setOpenExternalLinks(true);
65     setTextFormat(Qt::RichText);
66     setFocusPolicy(Qt::NoFocus);
67     setTextInteractionFlags(Qt::LinksAccessibleByMouse);
68     setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum));
69     QImage img = QImage(imgPath);
70 
71     updateData(url, img);
72 }
73 
~RActiveLabel()74 RActiveLabel::~RActiveLabel()
75 {
76 }
77 
updateData(const QUrl & url,const QImage & img)78 void RActiveLabel::updateData(const QUrl& url, const QImage& img)
79 {
80     QByteArray byteArray;
81     QBuffer    buffer(&byteArray);
82     img.save(&buffer, "PNG");
83     setText(QString::fromLatin1("<a href=\"%1\">%2</a>")
84             .arg(url.url())
85             .arg(QString::fromLatin1("<img src=\"data:image/png;base64,%1\">")
86             .arg(QString::fromLatin1(byteArray.toBase64().data()))));
87 }
88 
89 // ------------------------------------------------------------------------------------
90 
RLineWidget(Qt::Orientation orientation,QWidget * const parent)91 RLineWidget::RLineWidget(Qt::Orientation orientation, QWidget* const parent)
92     : QFrame(parent)
93 {
94     setLineWidth(1);
95     setMidLineWidth(0);
96 
97     if (orientation == Qt::Vertical)
98     {
99         setFrameShape(QFrame::VLine);
100         setFrameShadow(QFrame::Sunken);
101         setMinimumSize(2, 0);
102     }
103     else
104     {
105         setFrameShape(QFrame::HLine);
106         setFrameShadow(QFrame::Sunken);
107         setMinimumSize(0, 2);
108     }
109 
110     updateGeometry();
111 }
112 
~RLineWidget()113 RLineWidget::~RLineWidget()
114 {
115 }
116 
117 // ------------------------------------------------------------------------------------
118 
RHBox(QWidget * const parent)119 RHBox::RHBox(QWidget* const parent)
120     : QFrame(parent)
121 {
122     QHBoxLayout* const layout = new QHBoxLayout(this);
123     layout->setSpacing(0);
124     layout->setMargin(0);
125     setLayout(layout);
126 }
127 
RHBox(bool,QWidget * const parent)128 RHBox::RHBox(bool /*vertical*/, QWidget* const parent)
129     : QFrame(parent)
130 {
131     QVBoxLayout* const layout = new QVBoxLayout(this);
132     layout->setSpacing(0);
133     layout->setMargin(0);
134     setLayout(layout);
135 }
136 
~RHBox()137 RHBox::~RHBox()
138 {
139 }
140 
childEvent(QChildEvent * e)141 void RHBox::childEvent(QChildEvent* e)
142 {
143     switch (e->type())
144     {
145         case QEvent::ChildAdded:
146         {
147             QChildEvent* const ce = static_cast<QChildEvent*>(e);
148 
149             if (ce->child()->isWidgetType())
150             {
151                 QWidget* const w = static_cast<QWidget*>(ce->child());
152                 static_cast<QBoxLayout*>(layout())->addWidget(w);
153             }
154 
155             break;
156         }
157 
158         case QEvent::ChildRemoved:
159         {
160             QChildEvent* const ce = static_cast<QChildEvent*>(e);
161 
162             if (ce->child()->isWidgetType())
163             {
164                 QWidget* const w = static_cast<QWidget*>(ce->child());
165                 static_cast<QBoxLayout*>(layout())->removeWidget(w);
166             }
167 
168             break;
169         }
170 
171         default:
172             break;
173     }
174 
175     QFrame::childEvent(e);
176 }
177 
sizeHint() const178 QSize RHBox::sizeHint() const
179 {
180     RHBox* const b = const_cast<RHBox*>(this);
181     QApplication::sendPostedEvents(b, QEvent::ChildAdded);
182 
183     return QFrame::sizeHint();
184 }
185 
minimumSizeHint() const186 QSize RHBox::minimumSizeHint() const
187 {
188     RHBox* const b = const_cast<RHBox*>(this);
189     QApplication::sendPostedEvents(b, QEvent::ChildAdded );
190 
191     return QFrame::minimumSizeHint();
192 }
193 
setSpacing(int spacing)194 void RHBox::setSpacing(int spacing)
195 {
196     layout()->setSpacing(spacing);
197 }
198 
setMargin(int margin)199 void RHBox::setMargin(int margin)
200 {
201     layout()->setMargin(margin);
202 }
203 
setStretchFactor(QWidget * const widget,int stretch)204 void RHBox::setStretchFactor(QWidget* const widget, int stretch)
205 {
206     static_cast<QBoxLayout*>(layout())->setStretchFactor(widget, stretch);
207 }
208 
209 // ------------------------------------------------------------------------------------
210 
RVBox(QWidget * const parent)211 RVBox::RVBox(QWidget* const parent)
212   : RHBox(true, parent)
213 {
214 }
215 
~RVBox()216 RVBox::~RVBox()
217 {
218 }
219 
220 // ------------------------------------------------------------------------------------
221 
222 class Q_DECL_HIDDEN RAdjustableLabel::Private
223 {
224 public:
225 
Private()226     Private()
227     {
228         emode = Qt::ElideMiddle;
229     }
230 
231     QString           ajdText;
232     Qt::TextElideMode emode;
233 };
234 
RAdjustableLabel(QWidget * const parent)235 RAdjustableLabel::RAdjustableLabel(QWidget* const parent)
236     : QLabel(parent),
237       d(new Private)
238 {
239     setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
240 }
241 
~RAdjustableLabel()242 RAdjustableLabel::~RAdjustableLabel()
243 {
244     delete d;
245 }
246 
resizeEvent(QResizeEvent *)247 void RAdjustableLabel::resizeEvent(QResizeEvent*)
248 {
249     adjustTextToLabel();
250 }
251 
minimumSizeHint() const252 QSize RAdjustableLabel::minimumSizeHint() const
253 {
254     QSize sh = QLabel::minimumSizeHint();
255     sh.setWidth(-1);
256     return sh;
257 }
258 
sizeHint() const259 QSize RAdjustableLabel::sizeHint() const
260 {
261     QFontMetrics fm(fontMetrics());
262 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
263     QRect geom = geometry();
264     QPoint p(geom.width() / 2 + geom.left(), geom.height() / 2 + geom.top());
265     QScreen *s = qApp->screenAt(p);
266     int maxW;
267     if (s) {
268          maxW = s->availableGeometry().width() * 3 / 4;
269     }
270     else {
271         maxW = 1024;
272     }
273 #else
274     int maxW = QApplication::desktop()->screenGeometry(this).width() * 3 / 4;
275 #endif
276 
277 #if QT_VERSION >= QT_VERSION_CHECK(5,11,0)
278     int currentW = fm.horizontalAdvance(d->ajdText);
279 #else
280     int currentW = fm.width(d->ajdText);
281 #endif
282 
283     return (QSize(currentW > maxW ? maxW : currentW, QLabel::sizeHint().height()));
284 }
285 
setAdjustedText(const QString & text)286 void RAdjustableLabel::setAdjustedText(const QString& text)
287 {
288     d->ajdText = text;
289 
290     if (d->ajdText.isNull())
291         QLabel::clear();
292 
293     adjustTextToLabel();
294 }
295 
adjustedText() const296 QString RAdjustableLabel::adjustedText() const
297 {
298     return d->ajdText;
299 }
300 
setAlignment(Qt::Alignment alignment)301 void RAdjustableLabel::setAlignment(Qt::Alignment alignment)
302 {
303     QString tmp(d->ajdText);
304     QLabel::setAlignment(alignment);
305     d->ajdText = tmp;
306 }
307 
setElideMode(Qt::TextElideMode mode)308 void RAdjustableLabel::setElideMode(Qt::TextElideMode mode)
309 {
310     d->emode = mode;
311     adjustTextToLabel();
312 }
313 
adjustTextToLabel()314 void RAdjustableLabel::adjustTextToLabel()
315 {
316     QFontMetrics fm(fontMetrics());
317     QStringList adjustedLines;
318     int lblW      = size().width();
319     bool adjusted = false;
320 
321     Q_FOREACH(const QString& line, d->ajdText.split(QLatin1Char('\n')))
322     {
323 #if QT_VERSION >= QT_VERSION_CHECK(5,11,0)
324         int lineW = fm.horizontalAdvance(line);
325 #else
326         int lineW = fm.width(line);
327 #endif
328         if (lineW > lblW)
329         {
330             adjusted = true;
331             adjustedLines << fm.elidedText(line, d->emode, lblW);
332         }
333         else
334         {
335             adjustedLines << line;
336         }
337     }
338 
339     if (adjusted)
340     {
341         QLabel::setText(adjustedLines.join(QStringLiteral("\n")));
342         setToolTip(d->ajdText);
343     }
344     else
345     {
346         QLabel::setText(d->ajdText);
347         setToolTip(QString());
348     }
349 }
350 
351 // ------------------------------------------------------------------------------------
352 
353 class Q_DECL_HIDDEN RFileSelector::Private
354 {
355 public:
356 
Private()357     Private()
358     {
359         edit      = 0;
360         btn       = 0;
361         fdMode    = QFileDialog::ExistingFile;
362         fdOptions = QFileDialog::DontUseNativeDialog;
363     }
364 
365     QLineEdit*            edit;
366     QPushButton*          btn;
367 
368     QFileDialog::FileMode fdMode;
369     QString               fdFilter;
370     QString               fdTitle;
371     QFileDialog::Options  fdOptions;
372 };
373 
RFileSelector(QWidget * const parent)374 RFileSelector::RFileSelector(QWidget* const parent)
375     : RHBox(parent),
376       d(new Private)
377 {
378     d->edit    = new QLineEdit(this);
379     d->btn     = new QPushButton(i18n("Browse..."), this);
380     setStretchFactor(d->edit, 10);
381 
382     connect(d->btn, SIGNAL(clicked()),
383             this, SLOT(slotBtnClicked()));
384 }
385 
~RFileSelector()386 RFileSelector::~RFileSelector()
387 {
388     delete d;
389 }
390 
lineEdit() const391 QLineEdit* RFileSelector::lineEdit() const
392 {
393     return d->edit;
394 }
395 
setFileDlgMode(QFileDialog::FileMode mode)396 void RFileSelector::setFileDlgMode(QFileDialog::FileMode mode)
397 {
398     d->fdMode = mode;
399 }
400 
setFileDlgFilter(const QString & filter)401 void RFileSelector::setFileDlgFilter(const QString& filter)
402 {
403     d->fdFilter = filter;
404 }
405 
setFileDlgTitle(const QString & title)406 void RFileSelector::setFileDlgTitle(const QString& title)
407 {
408     d->fdTitle = title;
409 }
410 
setFileDlgOptions(QFileDialog::Options opts)411 void RFileSelector::setFileDlgOptions(QFileDialog::Options opts)
412 {
413     d->fdOptions = opts;
414 }
415 
slotBtnClicked()416 void RFileSelector::slotBtnClicked()
417 {
418     if (d->fdMode == QFileDialog::ExistingFiles)
419     {
420         qCDebug(LIBKDCRAW_LOG) << "Multiple selection is not supported";
421         return;
422     }
423 
424     QFileDialog* const fileDlg = new QFileDialog(this);
425     fileDlg->setOptions(d->fdOptions);
426     fileDlg->setDirectory(QFileInfo(d->edit->text()).dir());
427     fileDlg->setFileMode(d->fdMode);
428 
429     if (!d->fdFilter.isNull())
430         fileDlg->setNameFilter(d->fdFilter);
431 
432     if (!d->fdTitle.isNull())
433         fileDlg->setWindowTitle(d->fdTitle);
434 
435     connect(fileDlg, SIGNAL(urlSelected(QUrl)),
436             this, SIGNAL(signalUrlSelected(QUrl)));
437 
438     emit signalOpenFileDialog();
439 
440     if (fileDlg->exec() == QDialog::Accepted)
441     {
442         QStringList sel = fileDlg->selectedFiles();
443 
444         if (!sel.isEmpty())
445         {
446             d->edit->setText(sel.first());
447         }
448     }
449 
450     delete fileDlg;
451 }
452 
453 // ---------------------------------------------------------------------------------------
454 
WorkingPixmap()455 WorkingPixmap::WorkingPixmap()
456 {
457     QPixmap pix(QStandardPaths::locate(QStandardPaths::AppDataLocation, QLatin1String("libkdcraw/pics/process-working.png")));
458     QSize   size(22, 22);
459 
460     if (pix.isNull())
461     {
462         qCWarning(LIBKDCRAW_LOG) << "Invalid pixmap specified.";
463         return;
464     }
465 
466     if (!size.isValid())
467     {
468         size = QSize(pix.width(), pix.width());
469     }
470 
471     if (pix.width() % size.width() || pix.height() % size.height())
472     {
473         qCWarning(LIBKDCRAW_LOG) << "Invalid framesize.";
474         return;
475     }
476 
477     const int rowCount = pix.height() / size.height();
478     const int colCount = pix.width()  / size.width();
479     m_frames.resize(rowCount * colCount);
480 
481     int pos = 0;
482 
483     for (int row = 0; row < rowCount; ++row)
484     {
485         for (int col = 0; col < colCount; ++col)
486         {
487             QPixmap frm     = pix.copy(col * size.width(), row * size.height(), size.width(), size.height());
488             m_frames[pos++] = frm;
489         }
490     }
491 }
492 
~WorkingPixmap()493 WorkingPixmap::~WorkingPixmap()
494 {
495 }
496 
isEmpty() const497 bool WorkingPixmap::isEmpty() const
498 {
499     return m_frames.isEmpty();
500 }
501 
frameSize() const502 QSize WorkingPixmap::frameSize() const
503 {
504     if (isEmpty())
505     {
506         qCWarning(LIBKDCRAW_LOG) << "No frame loaded.";
507         return QSize();
508     }
509 
510     return m_frames[0].size();
511 }
512 
frameCount() const513 int WorkingPixmap::frameCount() const
514 {
515     return m_frames.size();
516 }
517 
frameAt(int index) const518 QPixmap WorkingPixmap::frameAt(int index) const
519 {
520     if (isEmpty())
521     {
522         qCWarning(LIBKDCRAW_LOG) << "No frame loaded.";
523         return QPixmap();
524     }
525 
526     return m_frames.at(index);
527 }
528 
529 // ------------------------------------------------------------------------------------
530 
531 class Q_DECL_HIDDEN RColorSelector::Private
532 {
533 public:
534 
Private()535     Private()
536     {
537     }
538 
539     QColor color;
540 };
541 
RColorSelector(QWidget * const parent)542 RColorSelector::RColorSelector(QWidget* const parent)
543     : QPushButton(parent),
544       d(new Private)
545 {
546     connect(this, SIGNAL(clicked()),
547             this, SLOT(slotBtnClicked()));
548 }
549 
~RColorSelector()550 RColorSelector::~RColorSelector()
551 {
552     delete d;
553 }
554 
setColor(const QColor & color)555 void RColorSelector::setColor(const QColor& color)
556 {
557     if (color.isValid())
558     {
559         d->color = color;
560         update();
561     }
562 }
563 
color() const564 QColor RColorSelector::color() const
565 {
566     return d->color;
567 }
568 
slotBtnClicked()569 void RColorSelector::slotBtnClicked()
570 {
571     QColor color = QColorDialog::getColor(d->color);
572 
573     if (color.isValid())
574     {
575         setColor(color);
576         emit signalColorSelected(color);
577     }
578 }
579 
paintEvent(QPaintEvent *)580 void RColorSelector::paintEvent(QPaintEvent*)
581 {
582     QPainter painter(this);
583     QStyle* const style = QWidget::style();
584 
585     QStyleOptionButton opt;
586 
587     opt.initFrom(this);
588     opt.state    |= isDown() ? QStyle::State_Sunken : QStyle::State_Raised;
589     opt.features  = QStyleOptionButton::None;
590     opt.icon      = QIcon();
591     opt.text.clear();
592 
593     style->drawControl(QStyle::CE_PushButtonBevel, &opt, &painter, this);
594 
595     QRect labelRect = style->subElementRect(QStyle::SE_PushButtonContents, &opt, this);
596     int shift       = style->pixelMetric(QStyle::PM_ButtonMargin, &opt, this) / 2;
597     labelRect.adjust(shift, shift, -shift, -shift);
598     int x, y, w, h;
599     labelRect.getRect(&x, &y, &w, &h);
600 
601     if (isChecked() || isDown())
602     {
603         x += style->pixelMetric(QStyle::PM_ButtonShiftHorizontal, &opt, this);
604         y += style->pixelMetric(QStyle::PM_ButtonShiftVertical,   &opt, this);
605     }
606 
607     QColor fillCol = isEnabled() ? d->color : palette().color(backgroundRole());
608     qDrawShadePanel(&painter, x, y, w, h, palette(), true, 1, 0);
609 
610     if (fillCol.isValid())
611     {
612         const QRect rect(x + 1, y + 1, w - 2, h - 2);
613 
614         if (fillCol.alpha() < 255)
615         {
616             QPixmap chessboardPattern(16, 16);
617             QPainter patternPainter(&chessboardPattern);
618             patternPainter.fillRect(0, 0, 8, 8, Qt::black);
619             patternPainter.fillRect(8, 8, 8, 8, Qt::black);
620             patternPainter.fillRect(0, 8, 8, 8, Qt::white);
621             patternPainter.fillRect(8, 0, 8, 8, Qt::white);
622             patternPainter.end();
623             painter.fillRect(rect, QBrush(chessboardPattern));
624         }
625 
626         painter.fillRect(rect, fillCol);
627     }
628 
629     if (hasFocus())
630     {
631         QRect focusRect = style->subElementRect(QStyle::SE_PushButtonFocusRect, &opt, this);
632         QStyleOptionFocusRect focusOpt;
633         focusOpt.init(this);
634         focusOpt.rect            = focusRect;
635         focusOpt.backgroundColor = palette().window().color();
636         style->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpt, &painter, this);
637     }
638 }
639 
640 } // namespace KDcrawIface
641