File indexing completed on 2024-05-12 05:08:01

0001 /*
0002     SPDX-FileCopyrightText: 2015-2018 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "widgethintframe.h"
0007 
0008 // ----------------------------------------------------------------------------
0009 // QT Includes
0010 
0011 #include <QEvent>
0012 #include <QMetaMethod>
0013 #include <QMoveEvent>
0014 
0015 // ----------------------------------------------------------------------------
0016 // KDE Includes
0017 
0018 // ----------------------------------------------------------------------------
0019 // Project Includes
0020 
0021 class WidgetHintFrameCollection::Private
0022 {
0023 public:
0024     bool haveChainedCollection = false;
0025     bool chainedCollectionState = true;
0026     QList<WidgetHintFrame*> frameList;
0027 };
0028 
0029 WidgetHintFrameCollection::WidgetHintFrameCollection(QObject* parent)
0030     : QObject(parent)
0031     , d(new Private)
0032 {
0033 }
0034 
0035 WidgetHintFrameCollection::~WidgetHintFrameCollection()
0036 {
0037     delete d;
0038 }
0039 
0040 bool WidgetHintFrameCollection::chainFrameCollection(WidgetHintFrameCollection* chainedCollection)
0041 {
0042     if ((chainedCollection == nullptr) || (d->haveChainedCollection)) {
0043         return false;
0044     }
0045     d->haveChainedCollection = true;
0046     connect(chainedCollection, &WidgetHintFrameCollection::inputIsValid, this, &WidgetHintFrameCollection::changeChainedCollectionState);
0047     connect(chainedCollection, &WidgetHintFrameCollection::destroyed, this, &WidgetHintFrameCollection::unchainFrameCollection);
0048     return true;
0049 }
0050 
0051 void WidgetHintFrameCollection::unchainFrameCollection()
0052 {
0053     if (d->haveChainedCollection) {
0054         d->haveChainedCollection = false;
0055         d->chainedCollectionState = true;
0056     }
0057 }
0058 
0059 void WidgetHintFrameCollection::connectNotify(const QMetaMethod& signal)
0060 {
0061     // Whenever a new object connects to our inputIsValid signal
0062     // we emit the current status right away.
0063     if (signal == QMetaMethod::fromSignal(&WidgetHintFrameCollection::inputIsValid)) {
0064         updateWidgets();
0065     }
0066 }
0067 
0068 void WidgetHintFrameCollection::addFrame(WidgetHintFrame* frame)
0069 {
0070     if (!d->frameList.contains(frame)) {
0071         connect(frame, &QObject::destroyed, this, &WidgetHintFrameCollection::frameDestroyed);
0072         connect(frame, &WidgetHintFrame::changed, this, [=] {
0073             QMetaObject::invokeMethod(this, "updateWidgets", Qt::QueuedConnection);
0074         });
0075         d->frameList.append(frame);
0076     }
0077 }
0078 
0079 void WidgetHintFrameCollection::addWidget(QWidget* w)
0080 {
0081     connect(this, &WidgetHintFrameCollection::inputIsValid, w, &QWidget::setEnabled, Qt::UniqueConnection);
0082 }
0083 
0084 void WidgetHintFrameCollection::changeChainedCollectionState(bool valid)
0085 {
0086     d->chainedCollectionState = valid;
0087     updateWidgets();
0088 }
0089 
0090 void WidgetHintFrameCollection::removeWidget(QWidget* w)
0091 {
0092     disconnect(this, &WidgetHintFrameCollection::inputIsValid, w, &QWidget::setEnabled);
0093     w->setEnabled(true);
0094 }
0095 
0096 void WidgetHintFrameCollection::frameDestroyed(QObject* o)
0097 {
0098     WidgetHintFrame* frame = qobject_cast<WidgetHintFrame*>(o);
0099     if (frame) {
0100         d->frameList.removeAll(frame);
0101     }
0102 }
0103 
0104 void WidgetHintFrameCollection::updateWidgets()
0105 {
0106     bool enabled = d->chainedCollectionState;
0107     for (const auto& frame : qAsConst(d->frameList)) {
0108         enabled &= !frame->isErroneous();
0109         if (!enabled) {
0110             break;
0111         }
0112     }
0113 
0114     Q_EMIT inputIsValid(enabled);
0115 }
0116 
0117 class WidgetHintFrame::Private
0118 {
0119 public:
0120     Private(WidgetHintFrame* qq)
0121         : q(qq)
0122         , editWidget(nullptr)
0123         , status(false)
0124         , style(Error)
0125         , offset(2)
0126     {
0127     }
0128 
0129     void updateStyle()
0130     {
0131         QString color("red");
0132         QString width("2");
0133         QString lineStyle("solid");
0134 
0135         switch (style) {
0136         case Error:
0137             break;
0138         case Warning:
0139         case Info:
0140             lineStyle = QLatin1String("dashed");
0141             break;
0142         case Focus:
0143             color = QStringLiteral("#%1").arg(q->palette().color(QPalette::Active, QPalette::Highlight).rgb() & 0xFFFFFF, 6, 16, QLatin1Char('0'));
0144             width = QLatin1String("1");
0145             break;
0146         }
0147         q->setStyleSheet(
0148             QStringLiteral("QFrame { background-color: none; padding: 1px; border: %1px %2 %3; border-radius: 4px; }").arg(width, lineStyle, color));
0149     }
0150 
0151     WidgetHintFrame* q;
0152     QWidget* editWidget;
0153     bool status;
0154     FrameStyle style;
0155     int offset;
0156 };
0157 
0158 WidgetHintFrame::WidgetHintFrame(QWidget* editWidget, FrameStyle style, Qt::WindowFlags f)
0159     : QFrame(editWidget->parentWidget(), f)
0160     , d(new Private(this))
0161 {
0162     setStyle(style);
0163     attachToWidget(editWidget);
0164 }
0165 
0166 WidgetHintFrame::~WidgetHintFrame()
0167 {
0168     delete d;
0169 }
0170 
0171 bool WidgetHintFrame::isErroneous() const
0172 {
0173     return (d->style == Error) && (d->status == true);
0174 }
0175 
0176 static WidgetHintFrame* frame(QWidget* editWidget)
0177 {
0178     if (editWidget && editWidget->parentWidget()) {
0179         const QList<WidgetHintFrame*> allErrorFrames = editWidget->parentWidget()->findChildren<WidgetHintFrame*>();
0180         QList<WidgetHintFrame*>::const_iterator it;
0181         for (const auto& f : qAsConst(allErrorFrames)) {
0182             if (f->editWidget() == editWidget) {
0183                 return f;
0184             }
0185         }
0186     }
0187     return nullptr;
0188 }
0189 
0190 void WidgetHintFrame::show(QWidget* editWidget, const QString& tooltip)
0191 {
0192     WidgetHintFrame* f = frame(editWidget);
0193     if (f) {
0194         f->QWidget::show();
0195         f->d->status = true;
0196         Q_EMIT f->changed();
0197     }
0198     if (!tooltip.isNull())
0199         editWidget->setToolTip(tooltip);
0200 }
0201 
0202 void WidgetHintFrame::hide(QWidget* editWidget, const QString& tooltip)
0203 {
0204     WidgetHintFrame* f = frame(editWidget);
0205     if (f) {
0206         f->QWidget::hide();
0207         f->d->status = false;
0208         Q_EMIT f->changed();
0209     }
0210     if (!tooltip.isNull())
0211         editWidget->setToolTip(tooltip);
0212 }
0213 
0214 QWidget* WidgetHintFrame::editWidget() const
0215 {
0216     return d->editWidget;
0217 }
0218 
0219 void WidgetHintFrame::detachFromWidget()
0220 {
0221     if (d->editWidget) {
0222         d->editWidget->removeEventFilter(this);
0223         d->editWidget = nullptr;
0224     }
0225 }
0226 
0227 void WidgetHintFrame::attachToWidget(QWidget* w)
0228 {
0229     // detach first
0230     detachFromWidget();
0231     if (w) {
0232         d->editWidget = w;
0233         // make sure we receive changes in position and size
0234         w->installEventFilter(this);
0235         // place frame around widget
0236         move(w->pos() - QPoint(d->offset, d->offset));
0237         const auto increment = d->offset * 2;
0238         resize(w->width() + increment, w->height() + increment);
0239         // make sure widget is on top of frame
0240         w->raise();
0241         // and hide frame for now
0242         QWidget::hide();
0243     }
0244 }
0245 
0246 bool WidgetHintFrame::eventFilter(QObject* o, QEvent* e)
0247 {
0248     if (o == d->editWidget) {
0249         QMoveEvent* mev = 0;
0250         QResizeEvent* sev = 0;
0251         const auto increment = d->offset * 2;
0252         switch (e->type()) {
0253         case QEvent::EnabledChange:
0254         case QEvent::Hide:
0255         case QEvent::Show:
0256             /**
0257              * @todo think about what to do when widget is enabled/disabled
0258              * hidden or shown
0259              */
0260             break;
0261 
0262         case QEvent::Move:
0263             mev = static_cast<QMoveEvent*>(e);
0264             move(mev->pos() - QPoint(d->offset, d->offset));
0265             break;
0266 
0267         case QEvent::Resize:
0268             sev = static_cast<QResizeEvent*>(e);
0269             resize(sev->size().width() + increment, sev->size().height() + increment);
0270             break;
0271         default:
0272             break;
0273         }
0274     }
0275     return QObject::eventFilter(o, e);
0276 }
0277 
0278 void WidgetHintFrame::setOffset(int offset)
0279 {
0280     d->offset = offset;
0281     // update frame position around widget
0282     if (d->editWidget) {
0283         move(d->editWidget->pos() - QPoint(d->offset, d->offset));
0284         const auto increment = d->offset * 2;
0285         resize(d->editWidget->width() + increment, d->editWidget->height() + increment);
0286     }
0287 }
0288 
0289 void WidgetHintFrame::setStyle(WidgetHintFrame::FrameStyle style)
0290 {
0291     d->style = style;
0292     switch (style) {
0293     default:
0294         setOffset(2);
0295         break;
0296     case Focus:
0297         setOffset(0);
0298         break;
0299     }
0300     d->updateStyle();
0301 }