File indexing completed on 2024-05-12 16:39:55
0001 /* This file is part of the KDE project 0002 Copyright (C) 2011-2013 Jarosław Staniek <staniek@kde.org> 0003 0004 This program is free software; you can redistribute it and/or 0005 modify it under the terms of the GNU Library General Public 0006 License as published by the Free Software Foundation; either 0007 version 2 of the License, or (at your option) any later version. 0008 0009 This program is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0012 Library General Public License for more details. 0013 0014 You should have received a copy of the GNU Library General Public License 0015 along with this program; see the file COPYING. If not, write to 0016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0017 * Boston, MA 02110-1301, USA. 0018 */ 0019 0020 #include "KexiContextMessage.h" 0021 0022 #include <QFormLayout> 0023 #include <QTimer> 0024 #include <QAction> 0025 #include <QPointer> 0026 #include <QMouseEvent> 0027 #include <QApplication> 0028 #include <QAbstractScrollArea> 0029 #include <QDebug> 0030 0031 #include <kexiutils/utils.h> 0032 #include "KexiAssistantPage.h" 0033 #include "KexiLinkWidget.h" 0034 0035 #include <KDbUtils> 0036 0037 class Q_DECL_HIDDEN KexiContextMessage::Private 0038 { 0039 public: 0040 Private() : defaultAction(0), contentsWidget(0) {} 0041 ~Private() { 0042 } 0043 QString text; 0044 QList<QAction*> actions; 0045 QSet<QAction*> leftButtonAlignment; 0046 QAction* defaultAction; 0047 QWidget *contentsWidget; 0048 }; 0049 0050 // ---- 0051 0052 KexiContextMessage::KexiContextMessage(const QString& text) 0053 : d(new Private) 0054 { 0055 setText(text); 0056 } 0057 0058 KexiContextMessage::KexiContextMessage(const KexiContextMessage& other) 0059 : d(new Private(*other.d)) 0060 { 0061 } 0062 0063 KexiContextMessage::KexiContextMessage(QWidget *contentsWidget) 0064 : d(new Private) 0065 { 0066 d->contentsWidget = contentsWidget; 0067 } 0068 0069 KexiContextMessage::~KexiContextMessage() 0070 { 0071 delete d; 0072 } 0073 0074 QString KexiContextMessage::text() const 0075 { 0076 return d->text; 0077 } 0078 0079 void KexiContextMessage::setText(const QString text) 0080 { 0081 d->text = text; 0082 } 0083 0084 void KexiContextMessage::addAction(QAction* action, ButtonAlignment alignment) 0085 { 0086 d->actions.append(action); 0087 if (alignment == AlignLeft) { 0088 d->leftButtonAlignment.insert(action); 0089 } 0090 } 0091 0092 QList<QAction*> KexiContextMessage::actions() const 0093 { 0094 return d->actions; 0095 } 0096 0097 KexiContextMessage::ButtonAlignment KexiContextMessage::buttonAlignment(QAction* action) const 0098 { 0099 return d->leftButtonAlignment.contains(action) ? AlignLeft : AlignRight; 0100 } 0101 0102 void KexiContextMessage::setDefaultAction(QAction* action) 0103 { 0104 d->defaultAction = action; 0105 } 0106 0107 QAction* KexiContextMessage::defaultAction() const 0108 { 0109 return d->defaultAction; 0110 } 0111 0112 QWidget* KexiContextMessage::contentsWidget() const 0113 { 0114 return d->contentsWidget; 0115 } 0116 0117 // ---- 0118 0119 struct Palette { 0120 Palette() {} 0121 QPalette palette; 0122 QSet<KexiContextMessageWidget*> set; 0123 }; 0124 0125 class PaletteForPages : public QHash<QWidget*, Palette*> 0126 { 0127 public: 0128 PaletteForPages() {} 0129 ~PaletteForPages() { 0130 qDeleteAll(*this); 0131 } 0132 }; 0133 0134 Q_GLOBAL_STATIC(PaletteForPages, origPagesPalettes) 0135 0136 class Q_DECL_HIDDEN KexiContextMessageWidget::Private 0137 { 0138 public: 0139 explicit Private(KexiContextMessageWidget *_q) 0140 : q(_q) 0141 , resizeTrackingPolicy(0) 0142 , hasActions(false) 0143 , eventBlocking(true) 0144 { 0145 } 0146 ~Private() { 0147 } 0148 0149 void setDisabledColorsForPage() 0150 { 0151 Palette *p = origPagesPalettes->value(page); 0152 //! @todo KEXI3 remove p in page dtor 0153 if (!p) { 0154 p = new Palette; 0155 p->palette = page->palette(); 0156 origPagesPalettes->insert(page, p); 0157 QPalette pal(page->palette()); 0158 for (int i = 0; i < QPalette::NColorRoles; i++) { 0159 pal.setBrush(QPalette::Active, static_cast<QPalette::ColorRole>(i), 0160 pal.brush(QPalette::Disabled, static_cast<QPalette::ColorRole>(i))); 0161 pal.setBrush(QPalette::Inactive, static_cast<QPalette::ColorRole>(i), 0162 pal.brush(QPalette::Disabled, static_cast<QPalette::ColorRole>(i))); 0163 } 0164 page->setPalette(pal); 0165 } 0166 p->set.insert(q); 0167 } 0168 0169 void setEnabledColorsForPage() 0170 { 0171 Palette *p = origPagesPalettes->value(page); 0172 if (page && (hasActions || contentsWidget) && p) { 0173 p->set.remove(q); 0174 if (p->set.isEmpty()) { 0175 page->setPalette(p->palette); 0176 origPagesPalettes->remove(page); 0177 delete p; 0178 } 0179 } 0180 } 0181 0182 //! Enables or disables all viewports of QAbstractScrollArea-derived widgets inside of the page. 0183 //! This is needed because disabled QAbstractScrollArea widget does not disable its viewport. 0184 //! Skips contentsWidget itself and its children. 0185 void setViewportsEnabled(bool set) 0186 { 0187 if (page) { 0188 QSet<QAbstractScrollArea*> contentsWidgetChildren; 0189 if (contentsWidget) { 0190 contentsWidgetChildren = contentsWidget->findChildren<QAbstractScrollArea*>().toSet(); 0191 if (qobject_cast<QAbstractScrollArea*>(contentsWidget)) { 0192 contentsWidgetChildren.insert(qobject_cast<QAbstractScrollArea*>(contentsWidget)); 0193 } 0194 } 0195 for(QAbstractScrollArea *area : page->findChildren<QAbstractScrollArea*>()) { 0196 if (contentsWidgetChildren.contains(area)) { 0197 continue; 0198 } 0199 area->setEnabled(set); 0200 area->repaint(); 0201 } 0202 page->repaint(); 0203 } 0204 } 0205 0206 KexiContextMessageWidget *q; 0207 QPointer<QWidget> page; 0208 QList< QPointer<QWidget> > enabledLinks; 0209 QPointer<QWidget> context; 0210 QPointer<QWidget> nextFocusWidget; 0211 QPointer<QWidget> contentsWidget; 0212 0213 //!< For updating the callout position 0214 QPointer<QWidget> trackedWidget; 0215 QPoint origCalloutPointerPosition; 0216 QSize origSize; 0217 QSize origPageSize; 0218 QPoint trackedWidgetOriginalPos; 0219 Qt::Orientations resizeTrackingPolicy; 0220 0221 bool hasActions; 0222 bool eventBlocking; 0223 }; 0224 0225 KexiContextMessageWidget::KexiContextMessageWidget( 0226 QWidget *page, QFormLayout* layout, 0227 QWidget *context, const KexiContextMessage& message) 0228 : KMessageWidget(message.contentsWidget(), 0) 0229 , d(new Private(this)) 0230 { 0231 init(page, layout, context, message); 0232 } 0233 0234 KexiContextMessageWidget::KexiContextMessageWidget( 0235 QFormLayout* layout, 0236 QWidget *context, const KexiContextMessage& message) 0237 : KMessageWidget() 0238 , d(new Private(this)) 0239 { 0240 init(0, layout, context, message); 0241 } 0242 0243 KexiContextMessageWidget::KexiContextMessageWidget( 0244 QFormLayout* layout, QWidget *context, const QString& message) 0245 : KMessageWidget() 0246 , d(new Private(this)) 0247 { 0248 KexiContextMessage contextMessage(message); 0249 init(0, layout, context, contextMessage); 0250 } 0251 0252 void KexiContextMessageWidget::init( 0253 QWidget *page, QFormLayout* layout, 0254 QWidget *context, const KexiContextMessage& message) 0255 { 0256 d->context = context; 0257 d->page = page; 0258 d->contentsWidget = message.contentsWidget(); 0259 hide(); 0260 setText(message.text()); 0261 setMessageType(KMessageWidget::Warning); 0262 setWordWrap(true); 0263 setCloseButtonVisible(false); 0264 setAutoDelete(true); 0265 setContentsMargins(3, 0, 3, 0); // to better fit to line edits 0266 d->hasActions = !message.actions().isEmpty(); 0267 if ((d->page && d->hasActions) || d->contentsWidget) { 0268 d->setDisabledColorsForPage(); 0269 foreach (KexiLinkWidget* w, d->page->findChildren<KexiLinkWidget*>()) { 0270 //qDebug() << w << w->isEnabled(); 0271 if (w->isEnabled()) { 0272 d->enabledLinks.append(w); 0273 w->setEnabled(false); 0274 } 0275 } 0276 KexiUtils::installRecursiveEventFilter(d->page, this); // before inserting, 0277 // so 'this' is not disabled 0278 } 0279 0280 if (layout) { 0281 int row; 0282 layout->getWidgetPosition(context, &row, 0); 0283 layout->insertRow(row, QString(), this); 0284 setCalloutPointerDirection(KMessageWidget::Down); 0285 } 0286 else { 0287 if (d->page) { 0288 setParent(page); 0289 } 0290 } 0291 0292 if (d->hasActions) { 0293 foreach(QAction* action, message.actions()) { 0294 KMessageWidget::addAction(action); 0295 if (KexiContextMessage::AlignLeft == message.buttonAlignment(action)) { 0296 KMessageWidget::setButtonLeftAlignedForAction(action); 0297 } 0298 connect(action, SIGNAL(triggered()), this, SLOT(actionTriggered())); 0299 } 0300 0301 if (message.defaultAction()) { 0302 setDefaultAction(message.defaultAction()); 0303 } 0304 } 0305 else { 0306 if (d->context) 0307 d->context->setFocus(); 0308 } 0309 d->setViewportsEnabled(false); 0310 connect(this, &KMessageWidget::animatedShowFinished, this, &KexiContextMessageWidget::slotAnimatedShowFinished); 0311 connect(this, &KMessageWidget::animatedHideFinished, this, &KexiContextMessageWidget::slotAnimatedHideFinished); 0312 QTimer::singleShot(10, this, SLOT(animatedShow())); 0313 } 0314 0315 KexiContextMessageWidget::~KexiContextMessageWidget() 0316 { 0317 d->eventBlocking = false; 0318 d->setEnabledColorsForPage(); 0319 foreach (QPointer<QWidget> w, d->enabledLinks) { 0320 if (w) { 0321 w->setEnabled(true); 0322 w->unsetCursor(); 0323 } 0324 } 0325 d->setViewportsEnabled(true); 0326 repaint(); 0327 if (d->nextFocusWidget) { 0328 // qDebug() << d->nextFocusWidget << d->nextFocusWidget->focusProxy(); 0329 setFocus(); // a hack to force focus update 0330 d->nextFocusWidget->setFocus(); 0331 } 0332 else if (d->context) 0333 d->context->setFocus(); 0334 delete d; 0335 } 0336 0337 void KexiContextMessageWidget::actionTriggered() 0338 { 0339 d->eventBlocking = false; 0340 d->setEnabledColorsForPage(); 0341 foreach (QPointer<QWidget> w, d->enabledLinks) { 0342 if (w) { 0343 w->setEnabled(true); 0344 w->unsetCursor(); 0345 } 0346 } 0347 repaint(); 0348 0349 if (d->page) { 0350 d->page->setEnabled(true); 0351 d->page->repaint(); 0352 } 0353 0354 animatedHide(); 0355 } 0356 0357 bool KexiContextMessageWidget::eventFilter(QObject* watched, QEvent* event) 0358 { 0359 if (d->contentsWidget && event->type() == QEvent::MouseButtonRelease) { 0360 // hide the message when clicking outside when contents widget is present 0361 QMouseEvent *me = static_cast<QMouseEvent*>(event); 0362 QWidget *w = QApplication::widgetAt(me->globalPos()); 0363 //qDebug() << watched << w << w->parentWidget(); 0364 if (!KDbUtils::hasParent(this, w)) { 0365 actionTriggered(); 0366 return true; 0367 } 0368 } 0369 0370 if (watched == d->page && event->type() == QEvent::Hide) { 0371 d->setViewportsEnabled(true); 0372 } 0373 0374 if (watched == d->page && event->type() == QEvent::Resize) { 0375 //qDebug() << "RESIZE:" << watched; 0376 if (d->trackedWidget) { 0377 if (d->resizeTrackingPolicy != 0) { 0378 // update size 0379 //qDebug() << d->origSize << d->page->size() << d->origPageSize; 0380 if (!d->origSize.isValid()) { 0381 d->origSize = size(); 0382 } 0383 QSize newSize(d->origSize + d->page->size() - d->origPageSize); 0384 QSize sizeToSet = size(); 0385 if (d->resizeTrackingPolicy & Qt::Horizontal) { 0386 sizeToSet.setWidth(newSize.width()); 0387 } 0388 if (d->resizeTrackingPolicy & Qt::Vertical) { 0389 sizeToSet.setHeight(newSize.height()); 0390 setFixedHeight(newSize.height()); 0391 } 0392 resize(sizeToSet); 0393 setPaletteInherited(); 0394 } 0395 // update position 0396 QPoint delta(d->trackedWidget->mapToGlobal(QPoint(0, 0)) - d->trackedWidgetOriginalPos); 0397 KMessageWidget::setCalloutPointerPosition(d->origCalloutPointerPosition + delta); 0398 } 0399 } 0400 0401 switch (event->type()) { 0402 case QEvent::ActivationChange: 0403 case QEvent::CloseSoftwareInputPanel: 0404 case QEvent::ContextMenu: 0405 case QEvent::CursorChange: 0406 case QEvent::DragEnter: 0407 case QEvent::DragLeave: 0408 case QEvent::DragMove: 0409 case QEvent::Drop: 0410 case QEvent::EnabledChange: 0411 case QEvent::Enter: 0412 #ifdef QT_KEYPAD_NAVIGATION 0413 case QEvent::EnterEditFocus: 0414 case QEvent::LeaveEditFocus: 0415 #endif 0416 case QEvent::FocusIn: 0417 case QEvent::FocusOut: 0418 case QEvent::HoverEnter: 0419 case QEvent::HoverLeave: 0420 case QEvent::HoverMove: 0421 case QEvent::IconDrag: 0422 case QEvent::InputMethod: 0423 case QEvent::KeyPress: 0424 case QEvent::KeyRelease: 0425 case QEvent::Leave: 0426 case QEvent::LeaveWhatsThisMode: 0427 case QEvent::MouseButtonDblClick: 0428 case QEvent::MouseButtonPress: 0429 case QEvent::MouseButtonRelease: 0430 case QEvent::MouseMove: 0431 case QEvent::NonClientAreaMouseButtonDblClick: 0432 case QEvent::NonClientAreaMouseButtonPress: 0433 case QEvent::NonClientAreaMouseButtonRelease: 0434 case QEvent::NonClientAreaMouseMove: 0435 case QEvent::QueryWhatsThis: 0436 case QEvent::RequestSoftwareInputPanel: 0437 case QEvent::Shortcut: 0438 case QEvent::ShortcutOverride: 0439 case QEvent::TabletMove: 0440 case QEvent::TabletPress: 0441 case QEvent::TabletRelease: 0442 case QEvent::ToolTip: 0443 case QEvent::WhatsThis: 0444 case QEvent::WhatsThisClicked: 0445 case QEvent::Wheel: 0446 case QEvent::WindowActivate: 0447 case QEvent::WindowDeactivate: 0448 case QEvent::TouchBegin: 0449 #ifndef QT_NO_GESTURES 0450 case QEvent::Gesture: 0451 #endif 0452 if (d->eventBlocking) 0453 return true; 0454 default:; 0455 } 0456 return KMessageWidget::eventFilter(watched, event); 0457 } 0458 0459 void KexiContextMessageWidget::setNextFocusWidget(QWidget *widget) 0460 { 0461 d->nextFocusWidget = widget; 0462 } 0463 0464 void KexiContextMessageWidget::setCalloutPointerPosition(const QPoint& globalPos, 0465 QWidget *trackedWidget) 0466 { 0467 KMessageWidget::setCalloutPointerPosition(globalPos); 0468 // save current position so delta can be easier computed in the future 0469 // (see KexiContextMessageWidget::eventFilter()) 0470 d->trackedWidget = trackedWidget; 0471 if (d->trackedWidget) { 0472 d->origCalloutPointerPosition = globalPos; 0473 d->origSize = QSize(-1, -1); 0474 d->origPageSize = d->page->size(); 0475 d->trackedWidgetOriginalPos = d->trackedWidget->mapToGlobal(QPoint(0, 0)); 0476 } 0477 } 0478 0479 void KexiContextMessageWidget::setResizeTrackingPolicy(Qt::Orientations orientations) 0480 { 0481 d->resizeTrackingPolicy = orientations; 0482 } 0483 0484 Qt::Orientations KexiContextMessageWidget::resizeTrackingPolicy() const 0485 { 0486 return d->resizeTrackingPolicy; 0487 } 0488 0489 void KexiContextMessageWidget::setPaletteInherited() 0490 { 0491 if (d->contentsWidget) { 0492 // fix palette that gets messed after resize 0493 const QBrush bbrush(backgroundBrush()); 0494 foreach (QWidget* w, findChildren<QWidget*>()) { 0495 QPalette pal(w->palette()); 0496 pal.setBrush(QPalette::Base, bbrush); 0497 pal.setBrush(QPalette::Window, bbrush); 0498 pal.setBrush(QPalette::Button, bbrush); 0499 w->setPalette(pal); 0500 } 0501 } 0502 } 0503 0504 void KexiContextMessageWidget::slotAnimatedShowFinished() 0505 { 0506 d->setViewportsEnabled(false); 0507 } 0508 0509 void KexiContextMessageWidget::slotAnimatedHideFinished() 0510 { 0511 d->setViewportsEnabled(true); 0512 }