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