1 /* This file is part of the KDE project
2    Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
3    Copyright (C) 2004-2011 Jarosław Staniek <staniek@kde.org>
4 
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License as published by the Free Software Foundation; either
8    version 2 of the License, or (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public License
16    along with this program; see the file COPYING.  If not, write to
17    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19 */
20 
21 #include "kexidbimagebox.h"
22 #include <KexiIcon.h>
23 #include <widget/utils/kexidropdownbutton.h>
24 #include <kexiutils/utils.h>
25 #include <formeditor/widgetlibrary.h>
26 #include <formeditor/utils.h>
27 #include <kexi_global.h>
28 #include "kexidbutils.h"
29 #include "kexiformpart.h"
30 #include "kexiformmanager.h"
31 
32 #include <KDbField>
33 #include <KDbUtils>
34 #include <KDbQuerySchema>
35 
36 #include <KIconLoader>
37 #include <KLocalizedString>
38 #include <KIconEffect>
39 
40 #include <QApplication>
41 #include <QPixmap>
42 #include <QStyle>
43 #include <QStyleOptionFocusRect>
44 #include <QStyleOptionFrame>
45 #include <QClipboard>
46 #include <QFile>
47 #include <QBuffer>
48 #include <QPainter>
49 #include <QMimeDatabase>
50 #include <QStandardPaths>
51 #include <QMimeType>
52 #include <QImageReader>
53 #include <QDebug>
54 
55 //! @internal
56 struct KexiDBImageBox_Static {
KexiDBImageBox_StaticKexiDBImageBox_Static57     KexiDBImageBox_Static() : pixmap(0), small(0) {}
~KexiDBImageBox_StaticKexiDBImageBox_Static58     ~KexiDBImageBox_Static() { delete pixmap; delete small; }
59     QPixmap *pixmap;
60     QPixmap *small;
61 };
62 
Q_GLOBAL_STATIC(KexiDBImageBox_Static,KexiDBImageBox_static)63 Q_GLOBAL_STATIC(KexiDBImageBox_Static, KexiDBImageBox_static)
64 
65 KexiDBImageBox::KexiDBImageBox(bool designMode, QWidget *parent)
66         : KexiFrame(parent)
67         , KexiFormDataItemInterface()
68         , m_alignment(Qt::AlignLeft | Qt::AlignTop)
69         , m_readOnly(false)
70         , m_scaledContents(false)
71         , m_smoothTransformation(true)
72         , m_keepAspectRatio(true)
73         , m_insideSetData(false)
74         , m_setFocusOnButtonAfterClosingPopup(false)
75         , m_paintEventEnabled(true)
76         , m_dropDownButtonVisible(true)
77         , m_insideSetPalette(false)
78 {
79     setDesignMode(designMode);
80     installEventFilter(this);
81     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
82     QPalette pal(palette());
83     pal.setBrush(backgroundRole(), QBrush(Qt::transparent));
84     KexiFrame::setPalette(pal);
85 
86     m_contextMenu = new KexiImageContextMenu(this);
87     m_contextMenu->installEventFilter(this);
88 
89     if (designMode) {
90         m_chooser = 0;
91     } else {
92         m_chooser = new KexiDropDownButton(this);
93         m_chooser->setFocusPolicy(Qt::StrongFocus);
94         m_chooser->setMenu(m_contextMenu);
95         setFocusProxy(m_chooser);
96         m_chooser->installEventFilter(this);
97     }
98 
99     setFrameShape(QFrame::Box);
100     setFrameShadow(QFrame::Plain);
101     setFrameColor(palette().color(QPalette::Foreground));
102 
103     m_paletteBackgroundColorChanged = false; //set this here, not before
104 
105     connect(m_contextMenu, SIGNAL(updateActionsAvailabilityRequested(bool*,bool*)),
106             this, SLOT(slotUpdateActionsAvailabilityRequested(bool*,bool*)));
107     connect(m_contextMenu, SIGNAL(insertFromFileRequested(QUrl)),
108             this, SLOT(handleInsertFromFileAction(QUrl)));
109     connect(m_contextMenu, SIGNAL(saveAsRequested(QUrl)),
110             this, SLOT(handleSaveAsAction(QUrl)));
111     connect(m_contextMenu, SIGNAL(cutRequested()),
112             this, SLOT(handleCutAction()));
113     connect(m_contextMenu, SIGNAL(copyRequested()),
114             this, SLOT(handleCopyAction()));
115     connect(m_contextMenu, SIGNAL(pasteRequested()),
116             this, SLOT(handlePasteAction()));
117     connect(m_contextMenu, SIGNAL(clearRequested()),
118             this, SLOT(clear()));
119     connect(m_contextMenu, SIGNAL(showPropertiesRequested()),
120             this, SLOT(handleShowPropertiesAction()));
121 
122     KexiFrame::setLineWidth(0);
123     setDataSource(QString());   //to initialize popup menu and actions availability
124 }
125 
~KexiDBImageBox()126 KexiDBImageBox::~KexiDBImageBox()
127 {
128 }
129 
contextMenu() const130 KexiImageContextMenu* KexiDBImageBox::contextMenu() const
131 {
132     return m_contextMenu;
133 }
134 
value()135 QVariant KexiDBImageBox::value()
136 {
137     if (dataSource().isEmpty()) {
138         //not db-aware
139         return QVariant();
140     }
141     //db-aware mode
142     return m_value; //!< @todo
143 }
144 
setValueInternal(const QVariant & add,bool removeOld,bool loadPixmap)145 void KexiDBImageBox::setValueInternal(const QVariant& add, bool removeOld, bool loadPixmap)
146 {
147     if (isReadOnly())
148         return;
149     m_contextMenu->hide();
150     if (removeOld)
151         m_value = add.toByteArray();
152     else //do not add "m_origValue" to "add" as this is QByteArray
153         m_value = KexiDataItemInterface::originalValue().toByteArray();
154     bool ok = !m_value.isEmpty();
155     if (ok) {
156         if (loadPixmap) {
157             ok = KexiUtils::loadPixmapFromData(&m_pixmap, m_value);
158             m_currentScaledPixmap = QPixmap(); // clear cache
159         }
160         if (!ok) {
161             //! @todo inform about error?
162         }
163     }
164     if (!ok) {
165         m_valueMimeType.clear();
166         m_pixmap = QPixmap();
167         m_currentScaledPixmap = QPixmap(); // clear cache
168     }
169     repaint();
170 }
171 
setInvalidState(const QString & displayText)172 void KexiDBImageBox::setInvalidState(const QString& displayText)
173 {
174     Q_UNUSED(displayText);
175     if (!dataSource().isEmpty()) {
176         m_value = QByteArray();
177     }
178 
179 //! @todo m_pixmapLabel->setText( displayText );
180 
181     if (m_chooser)
182         m_chooser->hide();
183     setReadOnly(true);
184 }
185 
valueIsNull()186 bool KexiDBImageBox::valueIsNull()
187 {
188     return m_value.isEmpty();
189 }
190 
valueIsEmpty()191 bool KexiDBImageBox::valueIsEmpty()
192 {
193     return false;
194 }
195 
isReadOnly() const196 bool KexiDBImageBox::isReadOnly() const
197 {
198     return m_readOnly;
199 }
200 
setReadOnly(bool set)201 void KexiDBImageBox::setReadOnly(bool set)
202 {
203     m_readOnly = set;
204 }
205 
pixmap() const206 QPixmap KexiDBImageBox::pixmap() const
207 {
208     if (dataSource().isEmpty()) {
209         //not db-aware
210         return m_data.pixmap();
211     }
212     //db-aware mode
213     return m_pixmap;
214 }
215 
pixmapId() const216 int KexiDBImageBox::pixmapId() const
217 {
218     if (dataSource().isEmpty()) {
219         //not db-aware
220         return m_data.id();
221     }
222     return 0;
223 }
224 
setPixmapId(int id)225 void KexiDBImageBox::setPixmapId(int id)
226 {
227     if (m_insideSetData) //avoid recursion
228         return;
229     setData(KexiBLOBBuffer::self()->objectForId(id, /*unstored*/false));
230     repaint();
231 }
232 
storedPixmapId() const233 int KexiDBImageBox::storedPixmapId() const
234 {
235     if (dataSource().isEmpty() && m_data.stored()) {
236         //not db-aware
237         return m_data.id();
238     }
239     return 0;
240 }
241 
setStoredPixmapId(int id)242 void KexiDBImageBox::setStoredPixmapId(int id)
243 {
244     setData(KexiBLOBBuffer::self()->objectForId(id, /*stored*/true));
245     repaint();
246 }
247 
hasScaledContents() const248 bool KexiDBImageBox::hasScaledContents() const
249 {
250     return m_scaledContents;
251 }
252 
setScaledContents(bool set)253 void KexiDBImageBox::setScaledContents(bool set)
254 {
255 //! @todo m_pixmapLabel->setScaledContents(set);
256     m_scaledContents = set;
257     m_currentScaledPixmap = QPixmap();
258     repaint();
259 }
260 
smoothTransformation() const261 bool KexiDBImageBox::smoothTransformation() const
262 {
263     return m_smoothTransformation;
264 }
265 
alignment() const266 Qt::Alignment KexiDBImageBox::alignment() const
267 {
268     return m_alignment;
269 }
270 
keepAspectRatio() const271 bool KexiDBImageBox::keepAspectRatio() const
272 {
273     return m_keepAspectRatio;
274 }
275 
setSmoothTransformation(bool set)276 void KexiDBImageBox::setSmoothTransformation(bool set)
277 {
278     m_smoothTransformation = set;
279     m_currentScaledPixmap = QPixmap();
280     repaint();
281 }
282 
setKeepAspectRatio(bool set)283 void KexiDBImageBox::setKeepAspectRatio(bool set)
284 {
285     m_keepAspectRatio = set;
286     m_currentScaledPixmap = QPixmap();
287     if (m_scaledContents) {
288         repaint();
289     }
290 }
291 
widget()292 QWidget* KexiDBImageBox::widget()
293 {
294     //! @todo return m_pixmapLabel;
295     return this;
296 }
297 
cursorAtStart()298 bool KexiDBImageBox::cursorAtStart()
299 {
300     return true;
301 }
302 
cursorAtEnd()303 bool KexiDBImageBox::cursorAtEnd()
304 {
305     return true;
306 }
307 
data() const308 QByteArray KexiDBImageBox::data() const
309 {
310     if (dataSource().isEmpty()) {
311         //static mode
312         return m_data.data();
313     } else {
314         //db-aware mode
315         return m_value;
316     }
317 }
318 
insertFromFile()319 void KexiDBImageBox::insertFromFile()
320 {
321     m_contextMenu->insertFromFile();
322 }
323 
handleInsertFromFileAction(const QUrl & url)324 void KexiDBImageBox::handleInsertFromFileAction(const QUrl &url)
325 {
326     if (!dataSource().isEmpty() && isReadOnly())
327         return;
328 
329     if (dataSource().isEmpty()) {
330         //static mode
331         KexiBLOBBuffer::Handle h = KexiBLOBBuffer::self()->insertPixmap(url);
332         if (!h)
333             return;
334         setData(h);
335         repaint();
336     } else {
337         //db-aware
338         QString fileName(url.isLocalFile() ? url.toLocalFile() : url.toDisplayString());
339 
340         //! @todo download the file if remote, then set fileName properly
341         QFile f(fileName);
342         if (!f.open(QIODevice::ReadOnly)) {
343             //! @todo err msg
344             return;
345         }
346         QByteArray ba = f.readAll();
347         if (f.error() != QFile::NoError) {
348             //! @todo err msg
349             f.close();
350             return;
351         }
352         QMimeDatabase db;
353         m_valueMimeType = db.mimeTypeForFile(fileName, QMimeDatabase::MatchExtension).name();
354         setValueInternal(ba, true);
355     }
356 
357 //! @todo emit signal for setting "dirty" flag within the design
358     if (!dataSource().isEmpty()) {
359         signalValueChanged();
360     }
361 }
362 
handleAboutToSaveAsAction(QString * origFilename,QString * mimeType,bool * dataIsEmpty)363 void KexiDBImageBox::handleAboutToSaveAsAction(
364     QString* origFilename, QString* mimeType, bool *dataIsEmpty)
365 {
366     Q_ASSERT(origFilename);
367     Q_ASSERT(mimeType);
368     Q_ASSERT(dataIsEmpty);
369     if (data().isEmpty()) {
370         qWarning() << "no pixmap!";
371         *dataIsEmpty = false;
372         return;
373     }
374     if (dataSource().isEmpty()) { //for static images filename and mimetype can be available
375         *origFilename = m_data.originalFileName();
376         if (!origFilename->isEmpty()) {
377             *origFilename = QLatin1String("/") + *origFilename;
378         }
379         const QMimeDatabase db;
380         const QMimeType mime(db.mimeTypeForName(m_data.mimeType()));
381         if (!m_data.mimeType().isEmpty() && QImageReader::supportedMimeTypes().contains(mime.name().toLatin1())) {
382             *mimeType = mime.name();
383         }
384     }
385 }
386 
handleSaveAsAction(const QUrl & url)387 bool KexiDBImageBox::handleSaveAsAction(const QUrl &url)
388 {
389     //! @todo handle remote URLs
390     QFile f(url.toLocalFile());
391     if (!f.open(QIODevice::WriteOnly)) {
392         //! @todo err msg
393         return false;
394     }
395     f.write(data());
396     if (f.error() != QFile::NoError) {
397         //! @todo err msg
398         f.close();
399         return false;
400     }
401     f.close();
402     return true;
403 }
404 
handleCutAction()405 void KexiDBImageBox::handleCutAction()
406 {
407     if (!dataSource().isEmpty() && isReadOnly())
408         return;
409     handleCopyAction();
410     clear();
411 }
412 
handleCopyAction()413 void KexiDBImageBox::handleCopyAction()
414 {
415     qApp->clipboard()->setPixmap(pixmap(), QClipboard::Clipboard);
416 }
417 
handlePasteAction()418 void KexiDBImageBox::handlePasteAction()
419 {
420     if (isReadOnly() || (!designMode() && !hasFocus()))
421         return;
422     QPixmap pm(qApp->clipboard()->pixmap(QClipboard::Clipboard));
423     if (dataSource().isEmpty()) {
424         //static mode
425         KexiBLOBBuffer::Handle h = KexiBLOBBuffer::self()->insertPixmap(pm);
426         if (!h)
427             return;
428         setData(h);
429     } else {
430         //db-aware mode
431         m_pixmap = pm;
432         QByteArray ba;
433         QBuffer buffer(&ba);
434         buffer.open(QIODevice::WriteOnly);
435         if (m_pixmap.save(&buffer, "PNG")) {  // write pixmap into ba in PNG format
436             setValueInternal(ba, true, false/* !loadPixmap */);
437             m_currentScaledPixmap = QPixmap(); // clear cache
438         } else {
439             setValueInternal(QByteArray(), true);
440         }
441     }
442 
443     repaint();
444     if (!dataSource().isEmpty()) {
445         signalValueChanged();
446     }
447 }
448 
clear()449 void KexiDBImageBox::clear()
450 {
451     if (dataSource().isEmpty()) {
452         //static mode
453         setData(KexiBLOBBuffer::Handle());
454     } else {
455         if (isReadOnly())
456             return;
457         //db-aware mode
458         setValueInternal(QByteArray(), true);
459     }
460 
461     //! @todo emit signal for setting "dirty" flag within the design
462 
463     repaint();
464     if (!dataSource().isEmpty()) {
465         signalValueChanged();
466     }
467 }
468 
handleShowPropertiesAction()469 void KexiDBImageBox::handleShowPropertiesAction()
470 {
471     //! @todo
472 }
473 
slotUpdateActionsAvailabilityRequested(bool * valueIsNull,bool * valueIsReadOnly)474 void KexiDBImageBox::slotUpdateActionsAvailabilityRequested(bool* valueIsNull, bool* valueIsReadOnly)
475 {
476     Q_ASSERT(valueIsNull);
477     Q_ASSERT(valueIsReadOnly);
478     *valueIsNull = !(
479                          (dataSource().isEmpty() && !pixmap().isNull()) /*static pixmap available*/
480                       || (!dataSource().isEmpty() && !this->valueIsNull())  /*db-aware pixmap available*/
481                   );
482     // read-only if static pixmap or db-aware pixmap for read-only widget:
483     *valueIsReadOnly =
484            (!designMode() && dataSource().isEmpty())
485         || (!dataSource().isEmpty() && isReadOnly())
486         || (designMode() && !dataSource().isEmpty());
487 }
488 
contextMenuEvent(QContextMenuEvent * e)489 void KexiDBImageBox::contextMenuEvent(QContextMenuEvent * e)
490 {
491     if (popupMenuAvailable())
492         m_contextMenu->exec(e->globalPos());
493 }
494 
updateActionStrings()495 void KexiDBImageBox::updateActionStrings()
496 {
497     if (!m_contextMenu)
498         return;
499     if (designMode()) {
500     }
501     else {
502         //update title in data view mode, based on the data source
503         if (columnInfo()) {
504             KexiImageContextMenu::updateTitle(m_contextMenu, columnInfo()->captionOrAliasOrName(),
505                                               KexiFormManager::self()->library()->iconName(metaObject()->className()));
506         }
507     }
508 
509     if (m_chooser) {
510         if (popupMenuAvailable() && dataSource().isEmpty()) { //this may work in the future (see @todo below)
511             m_chooser->setToolTip(xi18n("Click to show actions for this image box"));
512         } else {
513             QString beautifiedImageBoxName;
514             if (designMode()) {
515                 beautifiedImageBoxName = dataSource();
516             } else {
517                 beautifiedImageBoxName = columnInfo() ? columnInfo()->captionOrAliasOrName() : QString();
518                 /*! @todo look at makeFirstCharacterUpperCaseInCaptions setting [bool]
519                  (see doc/dev/settings.txt) */
520                 beautifiedImageBoxName = beautifiedImageBoxName[0].toUpper() + beautifiedImageBoxName.mid(1);
521             }
522             m_chooser->setToolTip(
523                 xi18n("Click to show actions for <interface>%1</interface> image box", beautifiedImageBoxName));
524         }
525     }
526 }
527 
popupMenuAvailable()528 bool KexiDBImageBox::popupMenuAvailable()
529 {
530     /*! @todo add kexi-global setting which anyway, allows to show this button
531               (read-only actions like copy/save as/print can be available) */
532     //chooser button can be only visible when data source is specified
533     return !dataSource().isEmpty();
534 }
535 
setDataSource(const QString & ds)536 void KexiDBImageBox::setDataSource(const QString &ds)
537 {
538     KexiFormDataItemInterface::setDataSource(ds);
539     setData(KexiBLOBBuffer::Handle());
540     updateActionStrings();
541     KexiFrame::setFocusPolicy(focusPolicy());   //set modified policy
542 
543     if (m_chooser) {
544         m_chooser->setEnabled(popupMenuAvailable());
545         if (m_dropDownButtonVisible && popupMenuAvailable()) {
546             m_chooser->show();
547         } else {
548             m_chooser->hide();
549         }
550     }
551 
552     // update some properties not changed by user
553 //! @todo get default line width from global style settings
554 //    if (!m_lineWidthChanged) {
555 //        KexiFrame::setLineWidth(ds.isEmpty() ? 0 : 1);
556 //    }
557     if (!m_paletteBackgroundColorChanged && parentWidget()) {
558         QPalette p = palette();
559         p.setColor(backgroundRole(),
560                    dataSource().isEmpty()
561                      ? parentWidget()->palette().color(parentWidget()->backgroundRole())
562                      : palette().color(QPalette::Active, QPalette::Base)
563         );
564         KexiFrame::setPalette(p);
565     }
566 }
567 
sizeHint() const568 QSize KexiDBImageBox::sizeHint() const
569 {
570     if (pixmap().isNull())
571         return QSize(80, 80);
572     return pixmap().size();
573 }
574 
realLineWidth() const575 int KexiDBImageBox::realLineWidth() const
576 {
577     switch (frameShape()) {
578     case QFrame::NoFrame:
579         // shadow, line, midline unused
580         return 0;
581     case QFrame::Box:
582         switch (frameShadow()) {
583         case QFrame::Plain:
584             // midline unused
585             return lineWidth();
586         default: // sunken, raised:
587             return 2 * lineWidth() + midLineWidth();
588         }
589         break;
590     case QFrame::Panel:
591         // shadow, midline unused
592         return lineWidth();
593     case QFrame::WinPanel:
594         // shadow, line, midline unused
595         return 2;
596     case QFrame::StyledPanel: {
597         // shadow, line, midline unused
598         QStyleOptionFrame option;
599         option.initFrom(this);
600         return style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &option, this);
601     }
602     default:
603         return lineWidth();
604     }
605 }
606 
scaledImageBoxIcon(const QMargins & margins,const QSize & size)607 static QPixmap *scaledImageBoxIcon(const QMargins& margins, const QSize& size)
608 {
609     const int realHeight = size.height() - margins.top() - margins.bottom();
610     const int realWidth = size.width() - margins.left() - margins.right();
611     if (   realHeight <= KexiDBImageBox_static->pixmap->height()
612         || realWidth <= KexiDBImageBox_static->pixmap->width())
613     {
614         if (   realHeight <= KexiDBImageBox_static->small->height()
615             || realWidth <= KexiDBImageBox_static->small->width())
616         {
617             return 0;
618         }
619         return KexiDBImageBox_static->small;
620     }
621     return KexiDBImageBox_static->pixmap;
622 }
623 
paintEvent(QPaintEvent * pe)624 void KexiDBImageBox::paintEvent(QPaintEvent *pe)
625 {
626     if (!m_paintEventEnabled)
627         return;
628     QPainter p(this);
629     p.setClipRect(pe->rect());
630     QMargins margins(contentsMargins());
631     margins += realLineWidth();
632     if (designMode() && pixmap().isNull()) {
633         QRect r(
634             QPoint(margins.left(), margins.top()),
635             size() - QSize(margins.left() + margins.right(), margins.top() + margins.bottom()));
636 
637         updatePixmap();
638         QPixmap *imagBoxPm = scaledImageBoxIcon(margins, size());
639         if (imagBoxPm) {
640             p.drawPixmap(2, height() - margins.top() - margins.bottom() - imagBoxPm->height() - 2,
641                          *imagBoxPm);
642         }
643         QFont f(qApp->font());
644         p.setFont(f);
645         const QFontMetrics fm(fontMetrics());
646         QString text;
647         if (dataSource().isEmpty()) {
648             if ((fm.height() * 2) > height()) {
649                 text = xi18nc("Unbound Image Box", "%1 (unbound)", objectName());
650             } else {
651                 text = xi18nc("Unbound Image Box", "%1\n(unbound)", objectName());
652             }
653         }
654         else {
655             text = dataSource();
656             const QPixmap dataSourceTagIcon(KexiFormUtils::dataSourceTagIcon());
657             if (width() >= (dataSourceTagIcon.width() + 2 + fm.boundingRect(r, Qt::AlignCenter, text).width())) {
658                 r.setLeft( r.left() + dataSourceTagIcon.width() + 2 ); // make some room for the [>] icon
659                 QRect bounding = fm.boundingRect(r, Qt::AlignCenter, text);
660                 p.drawPixmap(
661                     bounding.left() - dataSourceTagIcon.width() - 2,
662                     bounding.top() + bounding.height() / 2 - dataSourceTagIcon.height() / 2,
663                     dataSourceTagIcon);
664             }
665         }
666         p.drawText(r, Qt::AlignCenter, text);
667     }
668     else {
669         QSize internalSize(size());
670         if (m_chooser && m_dropDownButtonVisible && !dataSource().isEmpty())
671             internalSize.setWidth(internalSize.width() - m_chooser->width());
672 
673         const QRect internalRect(QPoint(0, 0), internalSize);
674         if (m_currentScaledPixmap.isNull() || internalRect != m_currentRect) {
675             m_currentRect = internalRect;
676             m_currentPixmapPos = QPoint(0, 0);
677             m_currentScaledPixmap = KexiUtils::scaledPixmap(
678                 margins, m_currentRect, pixmap(), &m_currentPixmapPos, m_alignment,
679                 m_scaledContents, m_keepAspectRatio,
680                 m_smoothTransformation ? Qt::SmoothTransformation : Qt::FastTransformation);
681         }
682         p.drawPixmap(m_currentPixmapPos, m_currentScaledPixmap);
683     }
684     KexiFrame::drawFrame(&p);
685 
686     if (designMode()) {
687         const bool hasFrame = frameWidth() >= 1 && frameShape() != QFrame::NoFrame;
688         if (!hasFrame) {
689             KFormDesigner::paintWidgetFrame(p, rect());
690         }
691     }
692     else { // data mode
693         // if the widget is focused, draw focus indicator rect _if_ there is no chooser button
694         if (   !dataSource().isEmpty()
695             && hasFocus()
696             && (!m_chooser || !m_chooser->isVisible()))
697         {
698             QStyleOptionFocusRect option;
699             option.initFrom(this);
700             style()->drawPrimitive(
701                 QStyle::PE_FrameFocusRect, &option, &p, this);
702         }
703     }
704 }
705 
updatePixmap()706 void KexiDBImageBox::updatePixmap()
707 {
708     if (!(designMode() && pixmap().isNull()))
709         return;
710 
711     if (!KexiDBImageBox_static->pixmap) {
712         QPixmap pm( KIconLoader::global()->loadMimeTypeIcon(
713             koIconNameCStr("image-x-generic"), KIconLoader::NoGroup, KIconLoader::SizeLarge, KIconLoader::DisabledState) );
714         if (!pm.isNull()) {
715             KIconEffect::semiTransparent(pm);
716             KIconEffect::semiTransparent(pm);
717         }
718         KexiDBImageBox_static->pixmap = new QPixmap(pm);
719         KexiDBImageBox_static->small = new QPixmap(
720             KexiDBImageBox_static->pixmap->scaled(
721                 KexiDBImageBox_static->pixmap->width() / 2, KexiDBImageBox_static->pixmap->height() / 2,
722                 Qt::KeepAspectRatio, Qt::SmoothTransformation) );
723     }
724 }
725 
setAlignment(Qt::Alignment alignment)726 void KexiDBImageBox::setAlignment(Qt::Alignment alignment)
727 {
728     m_alignment = alignment;
729     m_currentScaledPixmap = QPixmap(); // clear cache
730     repaint();
731 }
732 
setData(const KexiBLOBBuffer::Handle & handle)733 void KexiDBImageBox::setData(const KexiBLOBBuffer::Handle& handle)
734 {
735     if (m_insideSetData) //avoid recursion
736         return;
737     m_insideSetData = true;
738     m_data = handle;
739     m_currentScaledPixmap = QPixmap(); // clear cache
740     emit idChanged(handle.id());
741     m_insideSetData = false;
742     update();
743 }
744 
resizeEvent(QResizeEvent * e)745 void KexiDBImageBox::resizeEvent(QResizeEvent * e)
746 {
747     KexiFrame::resizeEvent(e);
748     if (m_chooser) {
749         QSize s(m_chooser->sizeHint());
750         const int _realLineWidth = realLineWidth();
751         QSize margin(_realLineWidth, _realLineWidth);
752         s.setHeight(height() - 2*margin.height());
753         s = s.boundedTo(size() - 2 * margin);
754         m_chooser->resize(s);
755         m_chooser->move(QRect(QPoint(0, 0), e->size() - m_chooser->size() - margin + QSize(1, 1)).bottomRight());
756     }
757 }
758 
setColumnInfo(KDbConnection * conn,KDbQueryColumnInfo * cinfo)759 void KexiDBImageBox::setColumnInfo(KDbConnection *conn,KDbQueryColumnInfo* cinfo)
760 {
761     KexiFormDataItemInterface::setColumnInfo(conn, cinfo);
762     //updating strings and title is needed
763     updateActionStrings();
764 }
765 
keyPressed(QKeyEvent * ke)766 bool KexiDBImageBox::keyPressed(QKeyEvent *ke)
767 {
768     // Esc key should close the popup
769     if (ke->modifiers() == Qt::NoModifier && ke->key() == Qt::Key_Escape) {
770         if (m_contextMenu->isVisible()) {
771             m_setFocusOnButtonAfterClosingPopup = true;
772             return true;
773         }
774     }
775     return false;
776 }
777 
setPalette(const QPalette & pal)778 void KexiDBImageBox::setPalette(const QPalette &pal)
779 {
780     KexiFrame::setPalette(pal);
781     if (m_insideSetPalette)
782         return;
783     m_insideSetPalette = true;
784     setPaletteBackgroundColor(pal.color(QPalette::Active, QPalette::Base));
785     QPalette p(palette());
786     p.setColor(foregroundRole(), pal.color(foregroundRole()));
787     setPalette(p);
788     m_insideSetPalette = false;
789 }
790 
setPaletteBackgroundColor(const QColor & color)791 void KexiDBImageBox::setPaletteBackgroundColor(const QColor & color)
792 {
793     m_paletteBackgroundColorChanged = true;
794     QPalette pal(palette());
795     pal.setColor(backgroundRole(), color);
796     setPalette(pal);
797     if (m_chooser)
798         m_chooser->setPalette(qApp->palette());
799 }
800 
dropDownButtonVisible() const801 bool KexiDBImageBox::dropDownButtonVisible() const
802 {
803     return m_dropDownButtonVisible;
804 }
805 
lineWidth() const806 int KexiDBImageBox::lineWidth() const
807 {
808     return KexiFrame::lineWidth();
809 }
810 
setDropDownButtonVisible(bool set)811 void KexiDBImageBox::setDropDownButtonVisible(bool set)
812 {
813 //! @todo use global default setting for this property
814     if (m_dropDownButtonVisible == set)
815         return;
816     m_dropDownButtonVisible = set;
817     if (m_chooser) {
818         if (m_dropDownButtonVisible)
819             m_chooser->show();
820         else
821             m_chooser->hide();
822     }
823 }
824 
subwidgetStretchRequired(KexiDBAutoField * autoField) const825 bool KexiDBImageBox::subwidgetStretchRequired(KexiDBAutoField* autoField) const
826 {
827     Q_UNUSED(autoField);
828     return true;
829 }
830 
eventFilter(QObject * watched,QEvent * e)831 bool KexiDBImageBox::eventFilter(QObject * watched, QEvent * e)
832 {
833     if (watched == this || watched == m_chooser) { //we're watching chooser as well because it's a focus proxy even if invisible
834         if (e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut || e->type() == QEvent::MouseButtonPress) {
835             update(); //to repaint focus rect
836         }
837     }
838     // hide popup menu as soon as it loses focus
839     if (watched == m_contextMenu && e->type() == QEvent::FocusOut) {
840         m_contextMenu->hide();
841     }
842     return KexiFrame::eventFilter(watched, e);
843 }
844 
setValueInternal(const QVariant & add,bool removeOld)845 void KexiDBImageBox::setValueInternal(const QVariant& add, bool removeOld)
846 {
847     setValueInternal(add, removeOld, true /*loadPixmap*/);
848 }
849 
focusPolicy() const850 Qt::FocusPolicy KexiDBImageBox::focusPolicy() const
851 {
852     if (dataSource().isEmpty())
853         return Qt::NoFocus;
854     return m_focusPolicyInternal;
855 }
856 
focusPolicyInternal() const857 Qt::FocusPolicy KexiDBImageBox::focusPolicyInternal() const
858 {
859     return m_focusPolicyInternal;
860 }
861 
setFocusPolicy(Qt::FocusPolicy policy)862 void KexiDBImageBox::setFocusPolicy(Qt::FocusPolicy policy)
863 {
864     m_focusPolicyInternal = policy;
865     KexiFrame::setFocusPolicy(focusPolicy());   //set modified policy
866 }
867 
setFrameShape(QFrame::Shape s)868 void KexiDBImageBox::setFrameShape(QFrame::Shape s)
869 {
870     KexiFrame::setFrameShape(s);
871     m_currentScaledPixmap = QPixmap(); // clear cache
872     update();
873 }
874 
setFrameShadow(QFrame::Shadow s)875 void KexiDBImageBox::setFrameShadow(QFrame::Shadow s)
876 {
877     KexiFrame::setFrameShadow(s);
878     m_currentScaledPixmap = QPixmap(); // clear cache
879     update();
880 }
881 
setLineWidth(int w)882 void KexiDBImageBox::setLineWidth(int w)
883 {
884     KexiFrame::setLineWidth(w);
885     m_currentScaledPixmap = QPixmap(); // clear cache
886     update();
887 }
888 
setMidLineWidth(int w)889 void KexiDBImageBox::setMidLineWidth(int w)
890 {
891     KexiFrame::setMidLineWidth(w);
892     m_currentScaledPixmap = QPixmap(); // clear cache
893     update();
894 }
895 
896