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 }