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 }