File indexing completed on 2024-05-19 12:54:53

0001 /* This file is part of the KDE project
0002    Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
0003    Copyright (C) 2011-2014 Jarosław Staniek <staniek@kde.org>
0004 
0005    Based on qmenu.cpp from Qt 4.7
0006 
0007    Based on oxygenhelper.cpp
0008 
0009    Copyright 2009-2010 Hugo Pereira Da Costa <hugo@oxygen-icons.org>
0010    Copyright 2008 Long Huynh Huu <long.upcase@googlemail.com>
0011    Copyright 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
0012    Copyright 2007 C. Boemann <cbo@boemann.dk>
0013    Copyright 2007 Fredrik Höglund <fredrik@kde.org>
0014 
0015    This library is free software; you can redistribute it and/or
0016    modify it under the terms of the GNU Library General Public
0017    License as published by the Free Software Foundation; either
0018    version 2 of the License, or (at your option) any later version.
0019 
0020    This library is distributed in the hope that it will be useful,
0021    but WITHOUT ANY WARRANTY; without even the implied warranty of
0022    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0023    Library General Public License for more details.
0024 
0025    You should have received a copy of the GNU Library General Public License
0026    along with this library; see the file COPYING.LIB.  If not, write to
0027    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0028  * Boston, MA 02110-1301, USA.
0029 */
0030 
0031 #include "KexiMenuWidget.h"
0032 #include "KexiMenuWidget_p.h"
0033 #include <KColorScheme>
0034 #include <KColorUtils>
0035 #include <KIconLoader>
0036 #include <kexiutils/utils.h>
0037 #include <KexiVersion.h>
0038 #include <KexiIcon.h>
0039 #include <KLocalizedString>
0040 
0041 #include <QApplication>
0042 #include <QBoxLayout>
0043 #include <QCache>
0044 #include <QDebug>
0045 #include <QDesktopServices>
0046 #include <QDesktopWidget>
0047 #include <QEvent>
0048 #include <QFontDatabase>
0049 #include <QLabel>
0050 #include <QMenu>
0051 #include <QPainter>
0052 #include <QPushButton>
0053 #include <QScopedPointer>
0054 #include <QStyle>
0055 #include <QTimer>
0056 #include <QToolButton>
0057 #include <QUrl>
0058 #include <QWidgetAction>
0059 #ifndef QT_NO_ACCESSIBILITY
0060 # include <qaccessible.h>
0061 #endif
0062 #ifndef QT_NO_WHATSTHIS
0063 # include <QWhatsThis>
0064 #endif
0065 
0066 const int calligraLogoPixmapInternalWidth = 100;
0067 const int calligraLogoPixmapInternalHeight = 71;
0068 const char calligraUrl[] = "https://www.calligra.org";
0069 const char facebookUrl[] = "https://www.facebook.com/kexi.project";
0070 const char twitterUrl[] = "https://twitter.com/kexi_project";
0071 
0072 //! @todo KEXI3 port OxygenHelper
0073 #if 0
0074 // from oxygenhelper.cpp:
0075 OxygenHelper::OxygenHelper()
0076  : _componentData("oxygen", 0, KComponentData::SkipMainComponentRegistration)
0077 {
0078     _config = _componentData.config();
0079     _contrast = KColorScheme::contrastF(_config);
0080     // background contrast is calculated so that it is 0.9
0081     // when KGlobalSettings contrast value of 0.7
0082     _bgcontrast = qMin(1.0, 0.9*_contrast/0.7);
0083 }
0084 
0085 OxygenHelper::~OxygenHelper()
0086 {
0087 }
0088 
0089 bool OxygenHelper::lowThreshold(const QColor &color)
0090 {
0091     const quint32 key( color.rgba() );
0092     ColorMap::iterator iter( m_lowThreshold.find( key ) );
0093     if( iter != m_lowThreshold.end() ) return iter.value();
0094     else {
0095 
0096         const QColor darker( KColorScheme::shade(color, KColorScheme::MidShade, 0.5 ) );
0097         const bool result( KColorUtils::luma(darker) > KColorUtils::luma(color) );
0098         m_lowThreshold.insert( key, result );
0099         return result;
0100 
0101     }
0102 }
0103 
0104 bool OxygenHelper::highThreshold(const QColor &color)
0105 {
0106     const quint32 key( color.rgba() );
0107     ColorMap::iterator iter( m_highThreshold.find( key ) );
0108     if( iter != m_highThreshold.end() ) return iter.value();
0109     else {
0110 
0111         const QColor lighter( KColorScheme::shade(color, KColorScheme::LightShade, 0.5 ) );
0112         const bool result( KColorUtils::luma(lighter) < KColorUtils::luma(color) );
0113         m_highThreshold.insert( key, result );
0114         return result;
0115 
0116     }
0117 }
0118 
0119 const QColor& OxygenHelper::backgroundTopColor(const QColor &color)
0120 {
0121     const quint64 key( color.rgba() );
0122     QColor* out( m_backgroundTopColorCache.object( key ) );
0123     if( !out )
0124     {
0125         if( lowThreshold(color) ) out = new QColor( KColorScheme::shade(color, KColorScheme::MidlightShade, 0.0) );
0126         else {
0127             const qreal my( KColorUtils::luma( KColorScheme::shade(color, KColorScheme::LightShade, 0.0) ) );
0128             const qreal by( KColorUtils::luma(color) );
0129             out = new QColor( KColorUtils::shade(color, (my - by) * _bgcontrast) );
0130         }
0131 
0132         m_backgroundTopColorCache.insert( key, out );
0133     }
0134 
0135     return *out;
0136 
0137 }
0138 
0139 const QColor& OxygenHelper::backgroundBottomColor(const QColor &color)
0140 {
0141     const quint64 key( color.rgba() );
0142     QColor* out( m_backgroundBottomColorCache.object( key ) );
0143     if( !out )
0144     {
0145         const QColor midColor( KColorScheme::shade(color, KColorScheme::MidShade, 0.0) );
0146         if( lowThreshold(color) ) out = new QColor( midColor );
0147         else {
0148 
0149             const qreal by( KColorUtils::luma(color) );
0150             const qreal my( KColorUtils::luma(midColor) );
0151             out = new QColor( KColorUtils::shade(color, (my - by) * _bgcontrast) );
0152 
0153         }
0154 
0155         m_backgroundBottomColorCache.insert( key, out );
0156     }
0157 
0158     return *out;
0159 
0160 }
0161 
0162 const QColor& OxygenHelper::backgroundRadialColor(const QColor &color)
0163 {
0164     const quint64 key( color.rgba() );
0165     QColor* out( m_backgroundRadialColorCache.object( key ) );
0166     if( !out )
0167     {
0168         if( lowThreshold(color) ) out = new QColor( KColorScheme::shade(color, KColorScheme::LightShade, 0.0) );
0169         else if( highThreshold( color ) ) out = new QColor( color );
0170         else out = new QColor( KColorScheme::shade(color, KColorScheme::LightShade, _bgcontrast) );
0171         m_backgroundRadialColorCache.insert( key, out );
0172     }
0173 
0174     return *out;
0175 }
0176 
0177 QPixmap OxygenHelper::verticalGradient(const QColor &color, int height, int offset)
0178 {
0179     const quint64 key( (quint64(color.rgba()) << 32) | height | 0x8000 );
0180     QPixmap *pixmap( m_backgroundCache.object( key ) );
0181 
0182     if (!pixmap)
0183     {
0184         pixmap = new QPixmap(1, height);
0185         pixmap->fill( Qt::transparent );
0186 
0187         QLinearGradient gradient(0, offset, 0, height+offset);
0188         gradient.setColorAt(0.0, backgroundTopColor(color));
0189         gradient.setColorAt(0.5, color);
0190         gradient.setColorAt(1.0, backgroundBottomColor(color));
0191 
0192         QPainter p(pixmap);
0193         p.setCompositionMode(QPainter::CompositionMode_Source);
0194         p.fillRect(pixmap->rect(), gradient);
0195 
0196         p.end();
0197 
0198         m_backgroundCache.insert(key, pixmap);
0199     }
0200 
0201     return *pixmap;
0202 }
0203 
0204 QPixmap OxygenHelper::radialGradient(const QColor &color, int width, int height)
0205 {
0206     const quint64 key( ( quint64(color.rgba()) << 32) | width | 0xb000 );
0207     QPixmap *pixmap( m_backgroundCache.object( key ) );
0208 
0209     if (!pixmap)
0210     {
0211         pixmap = new QPixmap(width, height);
0212         pixmap->fill(Qt::transparent);
0213 
0214         QColor radialColor = backgroundRadialColor(color);
0215         radialColor.setAlpha(255);
0216         QRadialGradient gradient(64, height-64, 64);
0217         gradient.setColorAt(0, radialColor);
0218         radialColor.setAlpha(101);
0219         gradient.setColorAt(0.5, radialColor);
0220         radialColor.setAlpha(37);
0221         gradient.setColorAt(0.75, radialColor);
0222         radialColor.setAlpha(0);
0223         gradient.setColorAt(1, radialColor);
0224 
0225         QPainter p(pixmap);
0226         p.scale(width/128.0,1);
0227         p.fillRect(QRect(0,0,128,height), gradient);
0228 
0229         p.end();
0230 
0231         m_backgroundCache.insert(key, pixmap);
0232     }
0233 
0234     return *pixmap;
0235 }
0236 
0237 void OxygenHelper::renderOxygenWindowBackground(QPainter *p, const QRect &clipRect,
0238                                          const QWidget *widget, const QWidget* window,
0239                                          const QColor& color, int y_shift,
0240                                          int gradientHeight)
0241 {
0242     // get coordinates relative to the client area
0243     // this is stupid. One could use mapTo if this was taking const QWidget* and not
0244     // QWidget* as argument.
0245     const QWidget* w( widget );
0246     int x(0);
0247     int y(-y_shift);
0248 
0249     while ( w != window && !w->isWindow() && w != w->parentWidget() )
0250     {
0251         x += w->geometry().x();
0252         y += w->geometry().y();
0253         w = w->parentWidget();
0254     }
0255 
0256     if (clipRect.isValid())
0257     {
0258         p->save();
0259         p->setClipRegion(clipRect,Qt::IntersectClip);
0260     }
0261 
0262     // calculate upper part height
0263     // special tricks are needed
0264     // to handle both window contents and window decoration
0265     const QRect r = window->rect();
0266     int height( window->frameGeometry().height() );
0267     int width( window->frameGeometry().width() );
0268     if( y_shift > 0 )
0269     {
0270         height -= 2*y_shift;
0271         width -= 2*y_shift;
0272     }
0273 
0274     const int splitY( qMin(300, (3*height)/4) );
0275 
0276     // draw upper linear gradient
0277     const QRect upperRect(-x, -y, r.width(), splitY);
0278     QPixmap tile( verticalGradient(color, splitY, gradientHeight-64) );
0279     p->drawTiledPixmap(upperRect, tile);
0280 
0281     // draw lower flat part
0282     const QRect lowerRect(-x, splitY-y, r.width(), r.height() - splitY-y_shift);
0283     p->fillRect(lowerRect, backgroundBottomColor(color));
0284 
0285     // draw upper radial gradient
0286     const int radialW( qMin(600, width) );
0287     const QRect radialRect( (r.width() - radialW) / 2-x, -y, radialW, gradientHeight);
0288     if (clipRect.intersects(radialRect))
0289     {
0290         tile = radialGradient(color, radialW, gradientHeight);
0291         p->drawPixmap(radialRect, tile);
0292     }
0293 
0294     if (clipRect.isValid())
0295     { p->restore(); }
0296 }
0297 
0298 //!@name window background gradients
0299 //@{
0300 /*!
0301 \par y_shift: shift the background gradient upwards, to fit with the windec
0302 \par gradientHeight: the height of the generated gradient.
0303 for different heights, the gradient is translated so that it is always at the same position from the bottom
0304 */
0305 void OxygenHelper::renderWindowBackground(QPainter *p, const QRect &clipRect,
0306                                    const QWidget *widget, const QPalette & pal,
0307                                    int y_shift, int gradientHeight)
0308 {
0309     renderOxygenWindowBackground(
0310                            p, clipRect, widget, widget->window(),
0311                            pal.color( widget->window()->backgroundRole() ),
0312                            y_shift, gradientHeight );
0313 }
0314 
0315 void OxygenHelper::renderMenuBackground( QPainter* p, const QRect& clipRect,
0316                                          const QWidget* widget, const QPalette& pal)
0317 {
0318     renderMenuBackground( p, clipRect, widget, pal.color( widget->window()->backgroundRole() ) );
0319 }
0320 
0321 void OxygenHelper::renderMenuBackground( QPainter* p, const QRect& clipRect,
0322                                          const QWidget* widget, const QColor& color )
0323 {
0324 
0325     // get coordinates relative to the client area
0326     // this is stupid. One could use mapTo if this was taking const QWidget* and not
0327     // QWidget* as argument.
0328     const QWidget* w( widget );
0329     int x(0);
0330     int y(0);
0331 
0332     while( !w->isWindow() && w != w->parentWidget() )
0333     {
0334         x += w->geometry().x();
0335         y += w->geometry().y();
0336         w = w->parentWidget();
0337     }
0338 
0339     if (clipRect.isValid()) {
0340         p->save();
0341         p->setClipRegion(clipRect,Qt::IntersectClip);
0342     }
0343 
0344     // calculate upper part height
0345     // special tricks are needed
0346     // to handle both window contents and window decoration
0347     QRect r = w->rect();
0348     const int height( w->frameGeometry().height() );
0349     const int splitY( qMin(200, (3*height)/4) );
0350 
0351     const QRect upperRect( QRect(0, 0, r.width(), splitY) );
0352     const QPixmap tile( verticalGradient(color, splitY) );
0353     p->drawTiledPixmap(upperRect, tile);
0354 
0355     const QRect lowerRect( 0,splitY, r.width(), r.height() - splitY );
0356     p->fillRect(lowerRect, backgroundBottomColor(color));
0357 
0358     if (clipRect.isValid())
0359     { p->restore(); }
0360 
0361 }
0362 
0363 // </oxygen>
0364 #endif // 0
0365 
0366 class KexiMenuWidgetActionPrivate
0367 {
0368 public:
0369     KexiMenuWidgetActionPrivate()
0370      : persistentlySelected(false)
0371     {
0372     }
0373     bool persistentlySelected;
0374 };
0375 
0376 KexiMenuWidgetAction::KexiMenuWidgetAction(QObject *parent)
0377  : QAction(parent)
0378  , d(new KexiMenuWidgetActionPrivate)
0379 {
0380 }
0381 
0382 KexiMenuWidgetAction::KexiMenuWidgetAction(const QString &text, QObject *parent)
0383  : QAction(text, parent)
0384  , d(new KexiMenuWidgetActionPrivate)
0385 {
0386 }
0387 
0388 KexiMenuWidgetAction::KexiMenuWidgetAction(const QIcon &icon, const QString &text,
0389                                            QObject *parent)
0390  : QAction(icon, text, parent)
0391  , d(new KexiMenuWidgetActionPrivate)
0392 {
0393 }
0394 
0395 KexiMenuWidgetAction::KexiMenuWidgetAction(KStandardAction::StandardAction id, QObject *parent)
0396  : QAction(parent)
0397  , d(new KexiMenuWidgetActionPrivate)
0398 {
0399     QScopedPointer<QAction> tmp(KStandardAction::create(id, 0, 0, 0));
0400     setIcon(tmp->icon());
0401     setText(tmp->text());
0402     setShortcut(tmp->shortcut());
0403     setToolTip(tmp->toolTip());
0404 }
0405 
0406 KexiMenuWidgetAction::~KexiMenuWidgetAction()
0407 {
0408 }
0409 
0410 void KexiMenuWidgetAction::setPersistentlySelected(bool set)
0411 {
0412     if (set == d->persistentlySelected)
0413         return;
0414     //qDebug() << "^^^^" << objectName() << set;
0415     d->persistentlySelected = set;
0416 }
0417 
0418 bool KexiMenuWidgetAction::persistentlySelected() const
0419 {
0420     return d->persistentlySelected;
0421 }
0422 
0423 KexiMenuWidget *KexiMenuWidgetPrivate::mouseDown = 0;
0424 int KexiMenuWidgetPrivate::sloppyDelayTimer = 0;
0425 
0426 void KexiMenuWidgetPrivate::init()
0427 {
0428 //! @todo KEXI3 port OxygenHelper
0429 #if 0
0430     oxygenHelper = q->style()->objectName() == "oxygen" ? new OxygenHelper : 0;
0431     bespin = oxygenHelper ? false : q->style()->objectName() == "bespin";
0432     qtcurve = oxygenHelper ? false : q->style()->objectName() == "qtcurve";
0433 #else
0434     bespin = q->style()->objectName() == "bespin";
0435     qtcurve = q->style()->objectName() == "qtcurve";
0436 #endif
0437 
0438 #ifndef QT_NO_WHATSTHIS
0439     //q->setAttribute(Qt::WA_CustomWhatsThis);
0440 #endif
0441     //q->setAttribute(Qt::WA_X11NetWmWindowTypePopupMenu);
0442     defaultMenuAction = menuAction = new QAction(q);
0443     //menuAction->d_func()->menu = q;
0444     q->setMouseTracking(q->style()->styleHint(QStyle::SH_Menu_MouseTracking, 0, q));
0445     if (q->style()->styleHint(QStyle::SH_Menu_Scrollable, 0, q)) {
0446         scroll = new KexiMenuWidgetPrivate::QMenuScroller;
0447         scroll->scrollFlags = KexiMenuWidgetPrivate::QMenuScroller::ScrollNone;
0448     }
0449 
0450 #ifdef QT_SOFTKEYS_ENABLED
0451     selectAction = QSoftKeyManager::createKeyedAction(QSoftKeyManager::SelectSoftKey, Qt::Key_Select, q);
0452     cancelAction = QSoftKeyManager::createKeyedAction(QSoftKeyManager::CancelSoftKey, Qt::Key_Back, q);
0453     selectAction->setPriority(QAction::HighPriority);
0454     cancelAction->setPriority(QAction::HighPriority);
0455     q->addAction(selectAction);
0456     q->addAction(cancelAction);
0457 #endif
0458     q->setFocusPolicy(Qt::StrongFocus);
0459 
0460     smallTextFont = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
0461 
0462     QVBoxLayout *vlyr = new QVBoxLayout(q);
0463     vlyr->setSpacing(0);
0464     vlyr->setMargin(0);
0465     vlyr->addStretch(1);
0466 
0467     // social media section
0468     socialWidget = new QWidget;
0469     QHBoxLayout *socialLayout = new QHBoxLayout(socialWidget);
0470     socialLayout->setMargin(3);
0471     socialLayout->setSpacing(6);
0472     socialLayout->addStretch(1);
0473     QLabel *followUs = new QLabel(xi18n("Join us on:"));
0474     followUs->setFont(smallTextFont);
0475     followUs->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
0476     socialLayout->addWidget(followUs, 1);
0477 
0478     QToolButton *fbButton  = new QToolButton;
0479     fbButton->setIcon(KexiIcon("im-facebook"));
0480     fbButton->setAutoRaise(true);
0481     fbButton->setCursor(Qt::PointingHandCursor);
0482     fbButton->setFocusPolicy(Qt::NoFocus);
0483     fbButton->setToolTip(xi18n("Visit KEXI Facebook page at %1", QLatin1String(facebookUrl)));
0484     QObject::connect(fbButton, &QPushButton::clicked,
0485                      []() { QDesktopServices::openUrl(QUrl(facebookUrl)); });
0486 
0487     QToolButton *twButton  = new QToolButton;
0488     twButton->setIcon(KexiIcon("im-twitter"));
0489     twButton->setAutoRaise(true);
0490     twButton->setCursor(Qt::PointingHandCursor);
0491     twButton->setFocusPolicy(Qt::NoFocus);
0492     twButton->setToolTip(xi18n("Visit KEXI Twitter page at %1", QLatin1String(twitterUrl)));
0493     QObject::connect(twButton, &QPushButton::clicked,
0494                      []() { QDesktopServices::openUrl(QUrl(twitterUrl)); });
0495 
0496     socialLayout->addWidget(fbButton);
0497     socialLayout->addWidget(twButton);
0498     socialLayout->addStretch(1);
0499     vlyr->addWidget(socialWidget, 0 , Qt::AlignCenter);
0500 }
0501 
0502 int KexiMenuWidgetPrivate::scrollerHeight() const
0503 {
0504     return qMax(QApplication::globalStrut().height(), q->style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, q));
0505 }
0506 
0507 //Windows and KDE allows menus to cover the taskbar, while GNOME and Mac don't
0508 QRect KexiMenuWidgetPrivate::popupGeometry(const QWidget *widget) const
0509 {
0510     return QApplication::desktop()->screenGeometry(widget);
0511 }
0512 
0513 //Windows and KDE allows menus to cover the taskbar, while GNOME and Mac don't
0514 QRect KexiMenuWidgetPrivate::popupGeometry(int screen) const
0515 {
0516     return QApplication::desktop()->screenGeometry(screen);
0517 }
0518 
0519 QList<QPointer<QWidget> > KexiMenuWidgetPrivate::calcCausedStack() const
0520 {
0521     QList<QPointer<QWidget> > ret;
0522     for(QWidget *widget = causedPopup.widget; widget; ) {
0523         ret.append(widget);
0524         if (KexiMenuWidget *qmenu = qobject_cast<KexiMenuWidget*>(widget))
0525             widget = qmenu->causedPopup().widget;
0526         else
0527             break;
0528     }
0529     return ret;
0530 }
0531 
0532 void KexiMenuWidgetPrivate::updateActionRects() const
0533 {
0534     if (!itemsDirty)
0535         return;
0536 
0537     q->ensurePolished();
0538 
0539     //let's reinitialize the buffer
0540     QList<QAction*> actionsList = q->actions();
0541     actionRects.resize(actionsList.count());
0542     actionRects.fill(QRect());
0543 
0544     //let's try to get the last visible action
0545     int lastVisibleAction = actionsList.count() - 1;
0546     for(;lastVisibleAction >= 0; --lastVisibleAction) {
0547         const QAction *action = actionsList.at(lastVisibleAction);
0548         if (action->isVisible()) {
0549             //removing trailing separators
0550             if (action->isSeparator() && collapsibleSeparators)
0551                 continue;
0552             break;
0553         }
0554     }
0555 
0556     int max_column_width = 0,
0557         dh = popupGeometry(q).height(),
0558         y = 0;
0559     QStyle *style = q->style();
0560     QStyleOption opt;
0561     opt.initFrom(q);
0562     const int hmargin = style->pixelMetric(QStyle::PM_MenuHMargin, &opt, q),
0563               vmargin = style->pixelMetric(QStyle::PM_MenuVMargin, &opt, q),
0564               icone = KIconLoader::SizeMedium;
0565 //! @todo KEXI3 adjust this size for smaller displays
0566               //style->pixelMetric(QStyle::PM_SmallIconSize, &opt, q);
0567     const int fw = style->pixelMetric(QStyle::PM_MenuPanelWidth, &opt, q);
0568     const int deskFw = frameWidth(&opt);
0569     //const int tearoffHeight = tearoff ? style->pixelMetric(QStyle::PM_MenuTearoffHeight, &opt, q) : 0;
0570 
0571     //for compatibility now - will have to refactor this away
0572     tabWidth = 0;
0573     maxIconWidth = 0;
0574     hasCheckableItems = false;
0575     ncols = 1;
0576     sloppyAction = 0;
0577 
0578     for (int i = 0; i < actionsList.count(); ++i) {
0579         QAction *action = actionsList.at(i);
0580         if (action->isSeparator() || !action->isVisible() || widgetItems.contains(action))
0581             continue;
0582         //..and some members
0583         hasCheckableItems |= action->isCheckable();
0584         QIcon is = action->icon();
0585         if (!is.isNull()) {
0586             maxIconWidth = qMax<int>(maxIconWidth, icone + 4);
0587         }
0588     }
0589 
0590     //calculate size
0591     QFontMetrics qfm = q->fontMetrics();
0592     bool previousWasSeparator = true; // this is true to allow removing the leading separators
0593     for(int i = 0; i <= lastVisibleAction; i++) {
0594         QAction *action = actionsList.at(i);
0595 
0596         if (!action->isVisible() ||
0597             (collapsibleSeparators && previousWasSeparator && action->isSeparator()))
0598             continue; // we continue, this action will get an empty QRect
0599 
0600         previousWasSeparator = action->isSeparator();
0601 
0602         //let the style modify the above size..
0603         QStyleOptionMenuItem opt;
0604         q->initStyleOption(&opt, action);
0605         const QFontMetrics &fm = opt.fontMetrics;
0606 
0607         QSize sz;
0608         if (QWidget *w = widgetItems.value(action)) {
0609           sz = w->sizeHint().expandedTo(w->minimumSize()).expandedTo(w->minimumSizeHint()).boundedTo(w->maximumSize());
0610         } else {
0611             //calc what I think the size is..
0612             if (action->isSeparator()) {
0613                 sz = QSize(2, 2);
0614             } else {
0615                 QString s = action->text();
0616                 int t = s.indexOf(QLatin1Char('\t'));
0617                 if (t != -1) {
0618                     tabWidth = qMax(int(tabWidth), qfm.width(s.mid(t+1)));
0619                     s = s.left(t);
0620     #ifndef QT_NO_SHORTCUT
0621                 } else {
0622                     QKeySequence seq = action->shortcut();
0623                     if (!seq.isEmpty())
0624                         tabWidth = qMax(int(tabWidth), qfm.width(seq.toString()));
0625     #endif
0626                 }
0627                 sz.setWidth(fm.boundingRect(QRect(), Qt::TextSingleLine | Qt::TextShowMnemonic, s).width());
0628                 sz.setHeight(qMax(fm.height(), qfm.height()));
0629 
0630                 //(not need because iconless items have to be of the same size as icon ones): if (!is.isNull()) {
0631                     QSize is_sz = QSize(icone, icone);
0632                     if (is_sz.height() > sz.height())
0633                         sz.setHeight(is_sz.height());
0634                 //}
0635             }
0636             sz = style->sizeFromContents(QStyle::CT_MenuItem, &opt, sz, q);
0637         }
0638 
0639 
0640         if (!sz.isEmpty()) {
0641             max_column_width = qMax(max_column_width, sz.width());
0642             //wrapping
0643             if (!scroll &&
0644                y+sz.height()+vmargin > dh - (deskFw * 2)) {
0645                 ncols++;
0646                 y = vmargin;
0647             }
0648             y += sz.height();
0649             //update the item
0650             actionRects[i] = QRect(0, 0, sz.width(), sz.height());
0651         }
0652     }
0653 
0654     max_column_width += tabWidth; //finally add in the tab width
0655     const int sfcMargin = style->sizeFromContents(QStyle::CT_Menu, &opt, QApplication::globalStrut(), q).width() - QApplication::globalStrut().width();
0656     int leftmargin, topmargin, rightmargin, bottommargin;
0657     q->getContentsMargins(&leftmargin, &topmargin, &rightmargin, &bottommargin);
0658     const int min_column_width = q->minimumWidth() - (sfcMargin + leftmargin + rightmargin + 2 * (fw + hmargin));
0659     max_column_width = qMax(min_column_width, max_column_width);
0660 
0661 
0662     //calculate position
0663     const int base_y = vmargin + fw + topmargin +
0664         (scroll ? scroll->scrollOffset : 0)
0665         /*+ tearoffHeight*/;
0666     int x = hmargin + fw + leftmargin;
0667     y = base_y;
0668 
0669     for(int i = 0; i < actionsList.count(); i++) {
0670         QRect &rect = actionRects[i];
0671         if (rect.isNull())
0672             continue;
0673         if (!scroll &&
0674            y+rect.height() > dh - deskFw * 2) {
0675             x += max_column_width + hmargin;
0676             y = base_y;
0677         }
0678         rect.translate(x, y);                        //move
0679         rect.setWidth(max_column_width); //uniform width
0680 
0681         //we need to update the widgets geometry
0682         if (QWidget *widget = widgetItems.value(actionsList.at(i))) {
0683             widget->setGeometry(rect);
0684             widget->setVisible(actionsList.at(i)->isVisible());
0685         }
0686 
0687         y += rect.height();
0688     }
0689     itemsDirty = 0;
0690 }
0691 
0692 QRect KexiMenuWidgetPrivate::actionRect(QAction *act) const
0693 {
0694     QList<QAction*> actionsList = q->actions();
0695     int index = actionsList.indexOf(act);
0696     if (index == -1)
0697         return QRect();
0698 
0699     updateActionRects();
0700 
0701     //we found the action
0702     return actionRects.at(index);
0703 }
0704 
0705 void KexiMenuWidgetPrivate::hideUpToMenuBar()
0706 {
0707     bool fadeMenus = q->style()->styleHint(QStyle::SH_Menu_FadeOutOnHide);
0708     QWidget *caused = causedPopup.widget;
0709     hideMenu(q); //hide after getting causedPopup
0710     while(caused) {
0711         if (KexiMenuWidget *m = qobject_cast<KexiMenuWidget*>(caused)) {
0712             caused = m->causedPopup().widget;
0713             hideMenu(m, fadeMenus);
0714             if (!fadeMenus) // Mac doesn't clear the action until after hidden.
0715                 m->setCurrentAction(0);
0716         } else {
0717             caused = 0;
0718         }
0719     }
0720     setCurrentAction(0);
0721 }
0722 
0723 void KexiMenuWidgetPrivate::hideMenu(KexiMenuWidget *menu, bool justRegister)
0724 {
0725     Q_UNUSED(menu);
0726     Q_UNUSED(justRegister);
0727 }
0728 
0729 #if 0
0730 void KexiMenuWidgetPrivate::popupAction(QAction *action, int delay, bool activateFirst)
0731 {
0732     Q_UNUSED(activateFirst);
0733     if (action && action->isEnabled()) {
0734         if (!delay)
0735             q->internalDelayedPopup();
0736         else if (!menuDelayTimer.isActive() && (!action->menu() || !action->menu()->isVisible()))
0737             menuDelayTimer.start(delay, q);
0738 /*        if (activateFirst && action->menu())
0739             action->menu()->d_func()->setFirstActionActive();*/
0740     } else if (KexiMenuWidget *menu = activeMenu) {  //hide the current item
0741         activeMenu = 0;
0742         hideMenu(menu);
0743     }
0744 }
0745 #endif
0746 
0747 void KexiMenuWidgetPrivate::setSyncAction()
0748 {
0749     QAction *current = currentAction;
0750     if(current && (!current->isEnabled() || current->menu() || current->isSeparator()))
0751         current = 0;
0752     for(QWidget *caused = q; caused;) {
0753         if (KexiMenuWidget *m = qobject_cast<KexiMenuWidget*>(caused)) {
0754             caused = m->causedPopup().widget;
0755             if (m->eventLoop())
0756                 m->setSyncAction(current); // synchronous operation
0757         } else {
0758             break;
0759         }
0760     }
0761 }
0762 
0763 
0764 void KexiMenuWidgetPrivate::setFirstActionActive()
0765 {
0766     updateActionRects();
0767     QList<QAction*> actionsList = q->actions();
0768     for(int i = 0, saccum = 0; i < actionsList.count(); i++) {
0769         const QRect &rect = actionRects.at(i);
0770         if (rect.isNull())
0771             continue;
0772         if (scroll && scroll->scrollFlags & QMenuScroller::ScrollUp) {
0773             saccum -= rect.height();
0774             if (saccum > scroll->scrollOffset - scrollerHeight())
0775                 continue;
0776         }
0777         QAction *act = actionsList.at(i);
0778         if (!act->isSeparator() &&
0779            (q->style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, q)
0780             || act->isEnabled())) {
0781             setCurrentAction(act);
0782             break;
0783         }
0784     }
0785 }
0786 
0787 // popup == -1 means do not popup, 0 means immediately, others mean use a timer
0788 void KexiMenuWidgetPrivate::setCurrentAction(QAction *action, int popup, KexiMenuWidget::SelectionReason reason, bool activateFirst)
0789 {
0790     Q_UNUSED(activateFirst);
0791     //tearoffHighlighted = 0;
0792     if (currentAction)
0793         q->update(actionRect(currentAction));
0794 
0795     sloppyAction = 0;
0796     if (!sloppyRegion.isEmpty())
0797         sloppyRegion = QRegion();
0798     KexiMenuWidget *hideActiveMenu = activeMenu;
0799 #ifndef QT_NO_STATUSTIP
0800     QAction *previousAction = currentAction;
0801 #endif
0802 
0803     currentAction = action;
0804     if (action) {
0805         if (!action->isSeparator()) {
0806             activateAction(action, QAction::Hover);
0807             /*if (popup != -1) {
0808                 hideActiveMenu = 0; //will be done "later"
0809                 // if the menu is visible then activate the required action,
0810                 // otherwise we just mark the action as currentAction
0811                 // and activate it when the menu will be popuped.
0812                 if (q->isVisible())
0813                     popupAction(currentAction, popup, activateFirst);
0814             }*/
0815             q->update(actionRect(action));
0816 
0817             if (reason == KexiMenuWidget::SelectedFromKeyboard) {
0818                 QWidget *widget = widgetItems.value(action);
0819                 if (widget) {
0820                     if (widget->focusPolicy() != Qt::NoFocus)
0821                         widget->setFocus(Qt::TabFocusReason);
0822                 } else {
0823                     //when the action has no QWidget, the KexiMenuWidget itself should
0824                     // get the focus
0825                     // Since the menu is a pop-up, it uses the popup reason.
0826                     if (!q->hasFocus()) {
0827                         q->setFocus(Qt::PopupFocusReason);
0828                     }
0829                 }
0830             }
0831         } else { //action is a separator
0832             if (popup != -1)
0833                 hideActiveMenu = 0; //will be done "later"
0834         }
0835 #ifndef QT_NO_STATUSTIP
0836     }  else if (previousAction) {
0837         //previousAction->d_func()->showStatusText(topCausedWidget(), QString());
0838 #endif
0839     }
0840     if (hideActiveMenu) {
0841         activeMenu = 0;
0842         hideMenu(hideActiveMenu);
0843     }
0844 }
0845 
0846 //return the top causedPopup.widget that is not a KexiMenuWidget
0847 QWidget *KexiMenuWidgetPrivate::topCausedWidget() const
0848 {
0849     QWidget* top = causedPopup.widget;
0850     while (KexiMenuWidget* m = qobject_cast<KexiMenuWidget *>(top))
0851         top = m->causedPopup().widget;
0852     return top;
0853 }
0854 
0855 QAction *KexiMenuWidgetPrivate::actionAt(QPoint p) const
0856 {
0857     if (!q->rect().contains(p))     //sanity check
0858        return 0;
0859 
0860     QList<QAction*> actionsList = q->actions();
0861     for(int i = 0; i < actionRects.count(); i++) {
0862         if (actionRects.at(i).contains(p))
0863             return actionsList.at(i);
0864     }
0865     return 0;
0866 }
0867 
0868 void KexiMenuWidgetPrivate::setOverrideMenuAction(QAction *a)
0869 {
0870     QObject::disconnect(menuAction, SIGNAL(destroyed()), q, SLOT(overrideMenuActionDestroyed()));
0871     if (a) {
0872         menuAction = a;
0873         QObject::connect(a, SIGNAL(destroyed()), q, SLOT(overrideMenuActionDestroyed()));
0874     } else { //we revert back to the default action created by the KexiMenuWidget itself
0875         menuAction = defaultMenuAction;
0876     }
0877 }
0878 
0879 void KexiMenuWidget::overrideMenuActionDestroyed()
0880 {
0881     d->menuAction = d->defaultMenuAction;
0882 }
0883 
0884 
0885 void KexiMenuWidgetPrivate::updateLayoutDirection()
0886 {
0887     //we need to mimic the cause of the popup's layout direction
0888     //to allow setting it on a mainwindow for example
0889     //we call setLayoutDirection_helper to not overwrite a user-defined value
0890     if (!q->testAttribute(Qt::WA_SetLayoutDirection)) {
0891         if (QWidget *w = causedPopup.widget)
0892             setLayoutDirection_helper(w, w->layoutDirection());
0893         else if (QWidget *w = q->parentWidget())
0894             setLayoutDirection_helper(w, w->layoutDirection());
0895         else
0896             setLayoutDirection_helper(w, QApplication::layoutDirection());
0897     }
0898 }
0899 
0900 bool KexiMenuWidgetPrivate::actionPersistentlySelected(const QAction* action) const
0901 {
0902     const KexiMenuWidgetAction* kaction = qobject_cast<const KexiMenuWidgetAction*>(action);
0903     return kaction ? kaction->persistentlySelected() : false;
0904 }
0905 
0906 void KexiMenuWidgetPrivate::setActionPersistentlySelected(QAction* action, bool set)
0907 {
0908     KexiMenuWidgetAction* kaction = qobject_cast<KexiMenuWidgetAction*>(action);
0909     if (previousPersistentlySelectedAction) {
0910         previousPersistentlySelectedAction->setPersistentlySelected(false);
0911         q->update(actionRect(previousPersistentlySelectedAction));
0912     }
0913     if (kaction)
0914         kaction->setPersistentlySelected(set);
0915     previousPersistentlySelectedAction = kaction;
0916 }
0917 
0918 void KexiMenuWidgetPrivate::toggleActionPersistentlySelected(QAction* action)
0919 {
0920     KexiMenuWidgetAction* kaction = qobject_cast<KexiMenuWidgetAction*>(action);
0921     if (!kaction)
0922         return;
0923     setActionPersistentlySelected(kaction, !kaction->persistentlySelected());
0924 }
0925 
0926 /*!
0927     Returns the action associated with this menu.
0928 */
0929 QAction *KexiMenuWidget::menuAction() const
0930 {
0931     return d->menuAction;
0932 }
0933 
0934 /*!
0935   \property KexiMenuWidget::title
0936   \brief The title of the menu
0937 
0938   This is equivalent to the QAction::text property of the menuAction().
0939 
0940   By default, this property contains an empty string.
0941 */
0942 QString KexiMenuWidget::title() const
0943 {
0944     return d->menuAction->text();
0945 }
0946 
0947 void KexiMenuWidget::setTitle(const QString &text)
0948 {
0949     d->menuAction->setText(text);
0950 }
0951 
0952 /*!
0953   \property KexiMenuWidget::icon
0954 
0955   \brief The icon of the menu
0956 
0957   This is equivalent to the QAction::icon property of the menuAction().
0958 
0959   By default, if no icon is explicitly set, this property contains a null icon.
0960 */
0961 QIcon KexiMenuWidget::icon() const
0962 {
0963     return d->menuAction->icon();
0964 }
0965 
0966 void KexiMenuWidget::setIcon(const QIcon &icon)
0967 {
0968     d->menuAction->setIcon(icon);
0969 }
0970 
0971 
0972 //actually performs the scrolling
0973 void KexiMenuWidgetPrivate::scrollMenu(QAction *action, QMenuScroller::ScrollLocation location, bool active)
0974 {
0975     Q_UNUSED(action)
0976     Q_UNUSED(location)
0977     Q_UNUSED(active)
0978 #if 0
0979     if (!scroll || !scroll->scrollFlags)
0980         return;
0981     updateActionRects();
0982     int newOffset = 0;
0983     const int topScroll = (scroll->scrollFlags & QMenuScroller::ScrollUp)   ? scrollerHeight() : 0;
0984     const int botScroll = (scroll->scrollFlags & QMenuScroller::ScrollDown) ? scrollerHeight() : 0;
0985     const int vmargin = q->style()->pixelMetric(QStyle::PM_MenuVMargin, 0, q);
0986     const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, q);
0987 
0988     QList<QAction*> actionsList = q->actions();
0989     if (location == QMenuScroller::ScrollTop) {
0990         for(int i = 0, saccum = 0; i < actionsList.count(); i++) {
0991             if (actionsList.at(i) == action) {
0992                 newOffset = topScroll - saccum;
0993                 break;
0994             }
0995             saccum += actionRects.at(i).height();
0996         }
0997     } else {
0998         for(int i = 0, saccum = 0; i < actionsList.count(); i++) {
0999             saccum += actionRects.at(i).height();
1000             if (actionsList.at(i) == action) {
1001                 if (location == QMenuScroller::ScrollCenter)
1002                     newOffset = ((q->height() / 2) - botScroll) - (saccum - topScroll);
1003                 else
1004                     newOffset = (q->height() - botScroll) - saccum;
1005                 break;
1006             }
1007         }
1008         if(newOffset)
1009             newOffset -= fw * 2;
1010     }
1011 
1012     //figure out which scroll flags
1013     int newScrollFlags = QMenuScroller::ScrollNone;
1014     if (newOffset < 0) //easy and cheap one
1015         newScrollFlags |= QMenuScroller::ScrollUp;
1016     int saccum = newOffset;
1017     for(int i = 0; i < actionRects.count(); i++) {
1018         saccum += actionRects.at(i).height();
1019         if (saccum > q->height()) {
1020             newScrollFlags |= QMenuScroller::ScrollDown;
1021             break;
1022         }
1023     }
1024 
1025     if (!(newScrollFlags & QMenuScroller::ScrollDown) && (scroll->scrollFlags & QMenuScroller::ScrollDown)) {
1026         newOffset = q->height() - (saccum - newOffset) - fw*2 - vmargin;    //last item at bottom
1027     }
1028 
1029     if (!(newScrollFlags & QMenuScroller::ScrollUp) && (scroll->scrollFlags & QMenuScroller::ScrollUp)) {
1030         newOffset = 0;  //first item at top
1031     }
1032 
1033     if (newScrollFlags & QMenuScroller::ScrollUp)
1034         newOffset -= vmargin;
1035 
1036     QRect screen = popupGeometry(q);
1037     const int desktopFrame = frameWidth();
1038     if (q->height() < screen.height()-(desktopFrame*2)-1) {
1039         QRect geom = q->geometry();
1040         if (newOffset > scroll->scrollOffset && (scroll->scrollFlags & newScrollFlags & QMenuScroller::ScrollUp)) { //scroll up
1041             const int newHeight = geom.height()-(newOffset-scroll->scrollOffset);
1042             if(newHeight > geom.height())
1043                 geom.setHeight(newHeight);
1044         } else if(scroll->scrollFlags & newScrollFlags & QMenuScroller::ScrollDown) {
1045             int newTop = geom.top() + (newOffset-scroll->scrollOffset);
1046             if (newTop < desktopFrame+screen.top())
1047                 newTop = desktopFrame+screen.top();
1048             if (newTop < geom.top()) {
1049                 geom.setTop(newTop);
1050                 newOffset = 0;
1051                 newScrollFlags &= ~QMenuScroller::ScrollUp;
1052             }
1053         }
1054         if (geom.bottom() > screen.bottom() - desktopFrame)
1055             geom.setBottom(screen.bottom() - desktopFrame);
1056         if (geom.top() < desktopFrame+screen.top())
1057             geom.setTop(desktopFrame+screen.top());
1058         if (geom != q->geometry()) {
1059 #if 0
1060             if (newScrollFlags & QMenuScroller::ScrollDown &&
1061                q->geometry().top() - geom.top() >= -newOffset)
1062                 newScrollFlags &= ~QMenuScroller::ScrollDown;
1063 #endif
1064             q->setGeometry(geom);
1065         }
1066     }
1067 
1068     //actually update flags
1069     const int delta = qMin(0, newOffset) - scroll->scrollOffset; //make sure the new offset is always negative
1070     if (!itemsDirty && delta) {
1071         //we've scrolled so we need to update the action rects
1072         for (int i = 0; i < actionRects.count(); ++i) {
1073             QRect &current = actionRects[i];
1074             current.moveTop(current.top() + delta);
1075 
1076             //we need to update the widgets geometry
1077             if (QWidget *w = widgetItems.value(actionsList.at(i)))
1078                 w->setGeometry(current);
1079         }
1080     }
1081     scroll->scrollOffset += delta;
1082     scroll->scrollFlags = newScrollFlags;
1083     if (active)
1084         setCurrentAction(action);
1085 
1086     q->update();     //issue an update so we see all the new state..
1087 #endif
1088 }
1089 
1090 void KexiMenuWidgetPrivate::scrollMenu(QMenuScroller::ScrollLocation location, bool active)
1091 {
1092     Q_UNUSED(location)
1093     Q_UNUSED(active)
1094 #if 0
1095     updateActionRects();
1096     QList<QAction*> actionsList = q->actions();
1097     if(location == QMenuScroller::ScrollBottom) {
1098         for(int i = actionsList.size()-1; i >= 0; --i) {
1099             QAction *act = actionsList.at(i);
1100             if (actionRects.at(i).isNull())
1101                 continue;
1102             if (!act->isSeparator() &&
1103                 (q->style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, q)
1104                  || act->isEnabled())) {
1105                 if(scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollDown)
1106                     scrollMenu(act, KexiMenuWidgetPrivate::QMenuScroller::ScrollBottom, active);
1107                 else if(active)
1108                     setCurrentAction(act, /*popup*/-1, KexiMenuWidget::SelectedFromKeyboard);
1109                 break;
1110             }
1111         }
1112     } else if(location == QMenuScroller::ScrollTop) {
1113         for(int i = 0; i < actionsList.size(); ++i) {
1114             QAction *act = actionsList.at(i);
1115             if (actionRects.at(i).isNull())
1116                 continue;
1117             if (!act->isSeparator() &&
1118                 (q->style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, q)
1119                  || act->isEnabled())) {
1120                 if(scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollUp)
1121                     scrollMenu(act, KexiMenuWidgetPrivate::QMenuScroller::ScrollTop, active);
1122                 else if(active)
1123                     setCurrentAction(act, /*popup*/-1, KexiMenuWidget::SelectedFromKeyboard);
1124                 break;
1125             }
1126         }
1127     }
1128 #endif
1129 }
1130 
1131 //only directional
1132 void KexiMenuWidgetPrivate::scrollMenu(QMenuScroller::ScrollDirection direction, bool page, bool active)
1133 {
1134     Q_UNUSED(direction)
1135     Q_UNUSED(page)
1136     Q_UNUSED(active)
1137 #if 0
1138     if (!scroll || !(scroll->scrollFlags & direction)) //not really possible...
1139         return;
1140     updateActionRects();
1141     const int topScroll = (scroll->scrollFlags & QMenuScroller::ScrollUp)   ? scrollerHeight() : 0;
1142     const int botScroll = (scroll->scrollFlags & QMenuScroller::ScrollDown) ? scrollerHeight() : 0;
1143     const int vmargin = q->style()->pixelMetric(QStyle::PM_MenuVMargin, 0, q);
1144     const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, q);
1145     const int offset = topScroll ? topScroll-vmargin : 0;
1146     QList<QAction*> actionsList = q->actions();
1147     if (direction == QMenuScroller::ScrollUp) {
1148         for(int i = 0, saccum = 0; i < actionsList.count(); i++) {
1149             saccum -= actionRects.at(i).height();
1150             if (saccum <= scroll->scrollOffset-offset) {
1151                 scrollMenu(actionsList.at(i), page ? QMenuScroller::ScrollBottom : QMenuScroller::ScrollTop, active);
1152                 break;
1153             }
1154         }
1155     } else if (direction == QMenuScroller::ScrollDown) {
1156         bool scrolled = false;
1157         for(int i = 0, saccum = 0; i < actionsList.count(); i++) {
1158             const int iHeight = actionRects.at(i).height();
1159             saccum -= iHeight;
1160             if (saccum <= scroll->scrollOffset-offset) {
1161                 const int scrollerArea = q->height() - botScroll - fw*2;
1162                 int visible = (scroll->scrollOffset-offset) - saccum;
1163                 for(i++ ; i < actionsList.count(); i++) {
1164                     visible += actionRects.at(i).height();
1165                     if (visible > scrollerArea - topScroll) {
1166                         scrolled = true;
1167                         scrollMenu(actionsList.at(i), page ? QMenuScroller::ScrollTop : QMenuScroller::ScrollBottom, active);
1168                         break;
1169                     }
1170                 }
1171                 break;
1172             }
1173         }
1174         if(!scrolled) {
1175             scroll->scrollFlags &= ~QMenuScroller::ScrollDown;
1176             q->update();
1177         }
1178     }
1179 #endif
1180 }
1181 
1182 /* This is poor-mans eventfilters. This avoids the use of
1183    eventFilter (which can be nasty for users of QMenuBar's). */
1184 bool KexiMenuWidgetPrivate::mouseEventTaken(QMouseEvent *e)
1185 {
1186     QPoint pos = q->mapFromGlobal(e->globalPos());
1187     if (scroll && !activeMenu) { //let the scroller "steal" the event
1188         bool isScroll = false;
1189         if (pos.x() >= 0 && pos.x() < q->width()) {
1190             for(int dir = QMenuScroller::ScrollUp; dir <= QMenuScroller::ScrollDown; dir = dir << 1) {
1191                 if (scroll->scrollFlags & dir) {
1192                     if (dir == QMenuScroller::ScrollUp)
1193                         isScroll = (pos.y() <= scrollerHeight());
1194                     else if (dir == QMenuScroller::ScrollDown)
1195                         isScroll = (pos.y() >= q->height() - scrollerHeight());
1196                     if (isScroll) {
1197                         scroll->scrollDirection = dir;
1198                         break;
1199                     }
1200                 }
1201             }
1202         }
1203         if (isScroll) {
1204             scroll->scrollTimer.start(50, q);
1205             return true;
1206         } else {
1207             scroll->scrollTimer.stop();
1208         }
1209     }
1210 
1211 //     if (tearoff) { //let the tear off thingie "steal" the event..
1212 //         QRect tearRect(0, 0, q->width(), q->style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, q));
1213 //         if (scroll && scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollUp)
1214 //             tearRect.translate(0, scrollerHeight());
1215 //         q->update(tearRect);
1216 //         if (tearRect.contains(pos) && hasMouseMoved(e->globalPos())) {
1217 //             setCurrentAction(0);
1218 //             tearoffHighlighted = 1;
1219 //             if (e->type() == QEvent::MouseButtonRelease) {
1220 //                 hideUpToMenuBar();
1221 //             }
1222 //             return true;
1223 //         }
1224 //         tearoffHighlighted = 0;
1225 //     }
1226 
1227     if (q->frameGeometry().contains(e->globalPos())) //otherwise if the event is in our rect we want it..
1228         return false;
1229 
1230     for(QWidget *caused = causedPopup.widget; caused;) {
1231         bool passOnEvent = false;
1232         QWidget *next_widget = 0;
1233         QPoint cpos = caused->mapFromGlobal(e->globalPos());
1234         if (KexiMenuWidget *m = qobject_cast<KexiMenuWidget*>(caused)) {
1235             passOnEvent = m->rect().contains(cpos);
1236             next_widget = m->causedPopup().widget;
1237         }
1238         if (passOnEvent) {
1239             if(e->type() != QEvent::MouseButtonRelease || mouseDown == caused) {
1240             QMouseEvent new_e(e->type(), cpos, e->button(), e->buttons(), e->modifiers());
1241             QApplication::sendEvent(caused, &new_e);
1242             return true;
1243         }
1244         }
1245         if (!next_widget)
1246             break;
1247         caused = next_widget;
1248     }
1249     return false;
1250 }
1251 
1252 void KexiMenuWidgetPrivate::activateCausedStack(const QList<QPointer<QWidget> > &causedStack, QAction *action, QAction::ActionEvent action_e, bool self)
1253 {
1254     KexiUtils::BoolBlocker guard(&activationRecursionGuard, true);
1255     if(self)
1256         action->activate(action_e);
1257 
1258     for(int i = 0; i < causedStack.size(); ++i) {
1259         QPointer<QWidget> widget = causedStack.at(i);
1260         if (!widget)
1261             continue;
1262         //fire
1263         if (KexiMenuWidget *qmenu = qobject_cast<KexiMenuWidget*>(widget)) {
1264             widget = qmenu->causedPopup().widget;
1265             if (action_e == QAction::Trigger) {
1266                 emit qmenu->triggered(action);
1267            } else if (action_e == QAction::Hover) {
1268                 emit qmenu->hovered(action);
1269             }
1270         }
1271     }
1272 }
1273 
1274 void KexiMenuWidgetPrivate::activateAction(QAction *action, QAction::ActionEvent action_e, bool self)
1275 {
1276 #ifndef QT_NO_WHATSTHIS
1277     bool inWhatsThisMode = QWhatsThis::inWhatsThisMode();
1278 #endif
1279     if (!action || !q->isEnabled()
1280         || (action_e == QAction::Trigger
1281 #ifndef QT_NO_WHATSTHIS
1282             && !inWhatsThisMode
1283 #endif
1284             && (action->isSeparator() ||!action->isEnabled())))
1285         return;
1286 
1287     /* I have to save the caused stack here because it will be undone after popup execution (ie in the hide).
1288        Then I iterate over the list to actually send the events. --Sam
1289     */
1290     const QList<QPointer<QWidget> > causedStack = calcCausedStack();
1291     if (action_e == QAction::Trigger) {
1292 #ifndef QT_NO_WHATSTHIS
1293         if (!inWhatsThisMode)
1294             actionAboutToTrigger = action;
1295 #endif
1296 
1297         if (q->testAttribute(Qt::WA_DontShowOnScreen)) {
1298             hideUpToMenuBar();
1299         } else {
1300             for(QWidget *widget = QApplication::activePopupWidget(); widget; ) {
1301                 if (KexiMenuWidget *qmenu = qobject_cast<KexiMenuWidget*>(widget)) {
1302                     if(qmenu == q)
1303                         hideUpToMenuBar();
1304                     widget = qmenu->causedPopup().widget;
1305                 } else {
1306                     break;
1307                 }
1308             }
1309         }
1310 
1311 #ifndef QT_NO_WHATSTHIS
1312         if (inWhatsThisMode) {
1313             QString s = action->whatsThis();
1314             if (s.isEmpty())
1315                 s = q->whatsThis();
1316             QWhatsThis::showText(q->mapToGlobal(actionRect(action).center()), s, q);
1317             return;
1318         }
1319 #endif
1320     }
1321 
1322 
1323     activateCausedStack(causedStack, action, action_e, self);
1324 
1325 
1326     if (action_e == QAction::Hover) {
1327 #ifndef QT_NO_ACCESSIBILITY
1328         if (QAccessible::isActive()) {
1329             int actionIndex = indexOf(action) + 1;
1330             QAccessibleEvent focusEvent(q, QAccessible::Focus);
1331             focusEvent.setChild(actionIndex);
1332             QAccessible::updateAccessibility(&focusEvent);
1333             QAccessibleEvent selectionEvent(q, QAccessible::Selection);
1334             selectionEvent.setChild(actionIndex);
1335             QAccessible::updateAccessibility(&selectionEvent);
1336         }
1337 #endif
1338         action->showStatusText(topCausedWidget());
1339     } else {
1340         actionAboutToTrigger = 0;
1341     }
1342 }
1343 
1344 void KexiMenuWidget::actionTriggered()
1345 {
1346     if (QAction *action = qobject_cast<QAction *>(sender())) {
1347         QPointer<QAction> actionGuard = action;
1348         emit triggered(action);
1349 
1350         if (!d->activationRecursionGuard && actionGuard) {
1351             //in case the action has not been activated by the mouse
1352             //we check the parent hierarchy
1353             QList< QPointer<QWidget> > list;
1354             for(QWidget *widget = parentWidget(); widget; ) {
1355                 if (qobject_cast<KexiMenuWidget*>(widget))
1356                 {
1357                     list.append(widget);
1358                     widget = widget->parentWidget();
1359                 } else {
1360                     break;
1361                 }
1362             }
1363             d->activateCausedStack(list, action, QAction::Trigger, false);
1364         }
1365     }
1366 }
1367 
1368 void KexiMenuWidget::actionHovered()
1369 {
1370     if (QAction * action = qobject_cast<QAction *>(sender())) {
1371         emit hovered(action);
1372     }
1373 }
1374 
1375 bool KexiMenuWidgetPrivate::hasMouseMoved(const QPoint &globalPos)
1376 {
1377     //determines if the mouse has moved (ie its initial position has
1378     //changed by more than QApplication::startDragDistance()
1379     //or if there were at least 6 mouse motions)
1380     return motions > 6 ||
1381         QApplication::startDragDistance() < (mousePopupPos - globalPos).manhattanLength();
1382 }
1383 
1384 // from QWidgetPrivate
1385 void KexiMenuWidgetPrivate::setLayoutDirection_helper(QWidget* w, Qt::LayoutDirection direction)
1386 {
1387     if (!w || (direction == Qt::RightToLeft) == w->testAttribute(Qt::WA_RightToLeft))
1388         return;
1389     w->setAttribute(Qt::WA_RightToLeft, (direction == Qt::RightToLeft));
1390     foreach (QObject *obj, w->children()) {
1391         QWidget *widget = qobject_cast<QWidget*>(obj);
1392         if (widget && !widget->isWindow() && !widget->testAttribute(Qt::WA_SetLayoutDirection))
1393             setLayoutDirection_helper(widget, direction);
1394     }
1395     QEvent e(QEvent::LayoutDirectionChange);
1396     QApplication::sendEvent(w, &e);
1397 }
1398 
1399 int KexiMenuWidgetPrivate::frameWidth(const QStyleOption* opt) const
1400 {
1401     if (!hasFrame)
1402         return 0;
1403     return q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, opt, q);
1404 }
1405 
1406 bool KexiMenuWidget::hasFrame() const
1407 {
1408     return d->hasFrame;
1409 }
1410 
1411 void KexiMenuWidget::setFrame(bool set)
1412 {
1413     d->hasFrame = set;
1414 }
1415 
1416 // void QWidgetPrivate::resolveLayoutDirection()
1417 // {
1418 //     Q_Q(const QWidget);
1419 //     if (!q->testAttribute(Qt::WA_SetLayoutDirection))
1420 //         setLayoutDirection_helper(q->isWindow() ? QApplication::layoutDirection() : q->parentWidget()->layoutDirection());
1421 // }
1422 
1423 /*!
1424     Initialize \a option with the values from this menu and information from \a action. This method
1425     is useful for subclasses when they need a QStyleOptionMenuItem, but don't want
1426     to fill in all the information themselves.
1427 
1428     \sa QStyleOption::initFrom() QMenuBar::initStyleOption()
1429 */
1430 void KexiMenuWidget::initStyleOption(QStyleOptionMenuItem *option, const QAction *action) const
1431 {
1432     if (!option || !action)
1433         return;
1434 
1435     option->initFrom(this);
1436     option->palette = palette();
1437     option->state = QStyle::State_None;
1438 
1439     if (window()->isActiveWindow())
1440         option->state |= QStyle::State_Active;
1441     if (isEnabled() && action->isEnabled()
1442             && (!action->menu() || action->menu()->isEnabled()))
1443         option->state |= QStyle::State_Enabled;
1444     else
1445         option->palette.setCurrentColorGroup(QPalette::Disabled);
1446 
1447     option->font = action->font().resolve(font());
1448     option->fontMetrics = QFontMetrics(option->font);
1449 
1450     if (d->currentAction && d->currentAction == action && !d->currentAction->isSeparator()) {
1451         option->state |= QStyle::State_Selected
1452                      | (d->mouseDown ? QStyle::State_Sunken : QStyle::State_None);
1453     }
1454 
1455     option->menuHasCheckableItems = d->hasCheckableItems;
1456     if (!action->isCheckable()) {
1457         option->checkType = QStyleOptionMenuItem::NotCheckable;
1458     } else {
1459         option->checkType = (action->actionGroup() && action->actionGroup()->isExclusive())
1460                             ? QStyleOptionMenuItem::Exclusive : QStyleOptionMenuItem::NonExclusive;
1461         option->checked = action->isChecked();
1462     }
1463     if (action->menu())
1464         option->menuItemType = QStyleOptionMenuItem::SubMenu;
1465     else if (action->isSeparator())
1466         option->menuItemType = QStyleOptionMenuItem::Separator;
1467     else if (d->defaultAction == action)
1468         option->menuItemType = QStyleOptionMenuItem::DefaultItem;
1469     else
1470         option->menuItemType = QStyleOptionMenuItem::Normal;
1471     if (action->isIconVisibleInMenu())
1472         option->icon = action->icon();
1473     QString textAndAccel = action->text();
1474 #ifndef QT_NO_SHORTCUT
1475     if (textAndAccel.indexOf(QLatin1Char('\t')) == -1) {
1476         QKeySequence seq = action->shortcut();
1477         if (!seq.isEmpty())
1478             textAndAccel += QLatin1Char('\t') + seq.toString();
1479     }
1480 #endif
1481     option->text = textAndAccel;
1482     option->tabWidth = d->tabWidth;
1483     option->maxIconWidth = d->maxIconWidth;
1484     option->menuRect = rect();
1485 }
1486 
1487 /*!
1488     Constructs a menu with parent \a parent.
1489 
1490     Although a popup menu is always a top-level widget, if a parent is
1491     passed the popup menu will be deleted when that parent is
1492     destroyed (as with any other QObject).
1493 */
1494 KexiMenuWidget::KexiMenuWidget(QWidget *parent)
1495     : QWidget(parent), d(new KexiMenuWidgetPrivate(this))
1496 {
1497     d->init();
1498 }
1499 
1500 /*!
1501     Constructs a menu with a \a title and a \a parent.
1502 
1503     Although a popup menu is always a top-level widget, if a parent is
1504     passed the popup menu will be deleted when that parent is
1505     destroyed (as with any other QObject).
1506 
1507     \sa title
1508 */
1509 KexiMenuWidget::KexiMenuWidget(const QString &title, QWidget *parent)
1510     : QWidget(parent), d(new KexiMenuWidgetPrivate(this))
1511 {
1512     d->init();
1513     d->menuAction->setText(title);
1514 }
1515 
1516 /*! \internal
1517  */
1518 // KexiMenuWidget::KexiMenuWidget(KexiMenuWidgetPrivate &dd, QWidget *parent)
1519 //     : QWidget(dd, parent, Qt::Popup)
1520 // {
1521 //     d->init();
1522 // }
1523 
1524 /*!
1525     Destroys the menu.
1526 */
1527 KexiMenuWidget::~KexiMenuWidget()
1528 {
1529     if (!d->widgetItems.isEmpty()) {  // avoid detach on shared null hash
1530         QHash<QAction *, QWidget *>::iterator it = d->widgetItems.begin();
1531         for (; it != d->widgetItems.end(); ++it) {
1532             if (QWidget *widget = it.value()) {
1533                 QWidgetAction *action = static_cast<QWidgetAction *>(it.key());
1534                 action->releaseWidget(widget);
1535                 *it = 0;
1536             }
1537         }
1538     }
1539 
1540     if (d->eventLoop)
1541         d->eventLoop->exit();
1542     //hideTearOffMenu();
1543     delete d;
1544 }
1545 
1546 KexiMenuWidgetAction* KexiMenuWidget::persistentlySelectedAction() const
1547 {
1548     if (d->previousPersistentlySelectedAction
1549         && d->previousPersistentlySelectedAction->persistentlySelected())
1550     {
1551         return d->previousPersistentlySelectedAction;
1552     }
1553     return 0;
1554 }
1555 
1556 void KexiMenuWidget::setPersistentlySelectedAction(KexiMenuWidgetAction* action, bool set)
1557 {
1558     d->setActionPersistentlySelected(action, set);
1559 }
1560 
1561 const KexiMenuWidgetCaused& KexiMenuWidget::causedPopup() const
1562 {
1563     return d->causedPopup;
1564 }
1565 
1566 void KexiMenuWidget::setCurrentAction(QAction *a, int popup, KexiMenuWidget::SelectionReason reason, bool activateFirst)
1567 {
1568     d->setCurrentAction(a, popup, reason, activateFirst);
1569 }
1570 
1571 QEventLoop *KexiMenuWidget::eventLoop() const
1572 {
1573     return d->eventLoop;
1574 }
1575 
1576 void KexiMenuWidget::setSyncAction(QAction *a)
1577 {
1578     d->syncAction = a;
1579 }
1580 
1581 /*!
1582     \overload
1583 
1584     This convenience function creates a new action with \a text.
1585     The function adds the newly created action to the menu's
1586     list of actions, and returns it.
1587 
1588     \sa QWidget::addAction()
1589 */
1590 QAction *KexiMenuWidget::addAction(const QString &text)
1591 {
1592     QAction *ret = new QAction(text, this);
1593     addAction(ret);
1594     return ret;
1595 }
1596 
1597 /*!
1598     \overload
1599 
1600     This convenience function creates a new action with an \a icon
1601     and some \a text. The function adds the newly created action to
1602     the menu's list of actions, and returns it.
1603 
1604     \sa QWidget::addAction()
1605 */
1606 QAction *KexiMenuWidget::addAction(const QIcon &icon, const QString &text)
1607 {
1608     QAction *ret = new QAction(icon, text, this);
1609     addAction(ret);
1610     return ret;
1611 }
1612 
1613 /*!
1614     \overload
1615 
1616     This convenience function creates a new action with the text \a
1617     text and an optional shortcut \a shortcut. The action's
1618     QAction::triggered() signal is connected to the
1619     \a receiver's \a member slot. The function adds the newly created
1620     action to the menu's list of actions and returns it.
1621 
1622     \sa QWidget::addAction()
1623 */
1624 QAction *KexiMenuWidget::addAction(const QString &text, const QObject *receiver, const char* member, const QKeySequence &shortcut)
1625 {
1626     QAction *action = new QAction(text, this);
1627 #ifdef QT_NO_SHORTCUT
1628     Q_UNUSED(shortcut);
1629 #else
1630     action->setShortcut(shortcut);
1631 #endif
1632     QObject::connect(action, SIGNAL(triggered(bool)), receiver, member);
1633     addAction(action);
1634     return action;
1635 }
1636 
1637 /*!
1638     \overload
1639 
1640     This convenience function creates a new action with an \a icon and
1641     some \a text and an optional shortcut \a shortcut. The action's
1642     QAction::triggered() signal is connected to the
1643     \a member slot of the \a receiver object. The function adds the
1644     newly created action to the menu's list of actions, and returns it.
1645 
1646     \sa QWidget::addAction()
1647 */
1648 QAction *KexiMenuWidget::addAction(const QIcon &icon, const QString &text, const QObject *receiver,
1649                           const char* member, const QKeySequence &shortcut)
1650 {
1651     QAction *action = new QAction(icon, text, this);
1652 #ifdef QT_NO_SHORTCUT
1653     Q_UNUSED(shortcut);
1654 #else
1655     action->setShortcut(shortcut);
1656 #endif
1657     QObject::connect(action, SIGNAL(triggered(bool)), receiver, member);
1658     addAction(action);
1659     return action;
1660 }
1661 
1662 /*!
1663     This convenience function creates a new separator action, i.e. an
1664     action with QAction::isSeparator() returning true, and adds the new
1665     action to this menu's list of actions. It returns the newly
1666     created action.
1667 
1668     \sa QWidget::addAction()
1669 */
1670 QAction *KexiMenuWidget::addSeparator()
1671 {
1672     QAction *action = new QAction(this);
1673     action->setSeparator(true);
1674     addAction(action);
1675     return action;
1676 }
1677 
1678 /*!
1679     This convenience function creates a new separator action, i.e. an
1680     action with QAction::isSeparator() returning true. The function inserts
1681     the newly created action into this menu's list of actions before
1682     action \a before and returns it.
1683 
1684     \sa QWidget::insertAction(), addSeparator()
1685 */
1686 QAction *KexiMenuWidget::insertSeparator(QAction *before)
1687 {
1688     QAction *action = new QAction(this);
1689     action->setSeparator(true);
1690     insertAction(before, action);
1691     return action;
1692 }
1693 
1694 /*!
1695   This sets the default action to \a act. The default action may have
1696   a visual cue, depending on the current QStyle. A default action
1697   usually indicates what will happen by default when a drop occurs.
1698 
1699   \sa defaultAction()
1700 */
1701 void KexiMenuWidget::setDefaultAction(QAction *act)
1702 {
1703     d->defaultAction = act;
1704 }
1705 
1706 /*!
1707   Returns the current default action.
1708 
1709   \sa setDefaultAction()
1710 */
1711 QAction *KexiMenuWidget::defaultAction() const
1712 {
1713     return d->defaultAction;
1714 }
1715 
1716 /*!
1717   Sets the currently highlighted action to \a act.
1718 */
1719 void KexiMenuWidget::setActiveAction(QAction *act)
1720 {
1721     d->setCurrentAction(act, 0);
1722     if (d->scroll)
1723         d->scrollMenu(act, KexiMenuWidgetPrivate::QMenuScroller::ScrollCenter);
1724 }
1725 
1726 
1727 /*!
1728     Returns the currently highlighted action, or 0 if no
1729     action is currently highlighted.
1730 */
1731 QAction *KexiMenuWidget::activeAction() const
1732 {
1733     return d->currentAction;
1734 }
1735 
1736 /*!
1737     Returns true if there are no visible actions inserted into the menu, false
1738     otherwise.
1739 
1740     \sa QWidget::actions()
1741 */
1742 bool KexiMenuWidget::isEmpty() const
1743 {
1744     bool ret = true;
1745     QList<QAction*> actionsList = this->actions();
1746     for(int i = 0; ret && i < actionsList.count(); ++i) {
1747         const QAction *action = actionsList.at(i);
1748         if (!action->isSeparator() && action->isVisible()) {
1749             ret = false;
1750         }
1751     }
1752     return ret;
1753 }
1754 
1755 /*!
1756     Removes all the menu's actions. Actions owned by the menu and not
1757     shown in any other widget are deleted.
1758 
1759     \sa removeAction()
1760 */
1761 void KexiMenuWidget::clear()
1762 {
1763     QList<QAction*> acts = actions();
1764 
1765     for(int i = 0; i < acts.size(); i++) {
1766 #ifdef QT_SOFTKEYS_ENABLED
1767         // Lets not touch to our internal softkey actions
1768         if(acts[i] == d->selectAction || acts[i] == d->cancelAction)
1769             continue;
1770 #endif
1771         removeAction(acts[i]);
1772         if (acts[i]->parent() == this && acts[i]->associatedWidgets().isEmpty())
1773             delete acts[i];
1774     }
1775 }
1776 
1777 /*!
1778   If a menu does not fit on the screen it lays itself out so that it
1779   does fit. It is style dependent what layout means (for example, on
1780   Windows it will use multiple columns).
1781 
1782   This functions returns the number of columns necessary.
1783 */
1784 int KexiMenuWidget::columnCount() const
1785 {
1786     return d->ncols;
1787 }
1788 
1789 /*!
1790   Returns the item at \a pt; returns 0 if there is no item there.
1791 */
1792 QAction *KexiMenuWidget::actionAt(const QPoint &pt) const
1793 {
1794     if (QAction *ret = d->actionAt(pt))
1795         return ret;
1796     return 0;
1797 }
1798 
1799 /*!
1800   Returns the geometry of action \a act.
1801 */
1802 QRect KexiMenuWidget::actionGeometry(QAction *act) const
1803 {
1804     return d->actionRect(act);
1805 }
1806 
1807 /*!
1808     \reimp
1809 */
1810 QSize KexiMenuWidget::sizeHint() const
1811 {
1812     d->updateActionRects();
1813 
1814     QSize s;
1815     for (int i = 0; i < d->actionRects.count(); ++i) {
1816         const QRect &rect = d->actionRects.at(i);
1817         if (rect.isNull())
1818             continue;
1819         if (rect.bottom() >= s.height())
1820             s.setHeight(rect.y() + rect.height());
1821         if (rect.right() >= s.width())
1822             s.setWidth(rect.x() + rect.width());
1823     }
1824     // Note that the action rects calculated above already include
1825     // the top and left margins, so we only need to add margins for
1826     // the bottom and right.
1827     QStyleOption opt(0);
1828     opt.initFrom(this);
1829     const int fw = d->frameWidth(&opt);
1830     int leftmargin, topmargin, rightmargin, bottommargin;
1831     getContentsMargins(&leftmargin, &topmargin, &rightmargin, &bottommargin);
1832     s.rwidth() += style()->pixelMetric(QStyle::PM_MenuHMargin, &opt, this)
1833         + fw + rightmargin + 2 /*frame*/;
1834     s.rheight() += style()->pixelMetric(QStyle::PM_MenuVMargin, &opt, this) + fw + bottommargin;
1835 
1836     return style()->sizeFromContents(QStyle::CT_Menu, &opt,
1837                                     s.expandedTo(QApplication::globalStrut()), this);
1838 }
1839 
1840 /*!
1841     Displays the menu so that the action \a atAction will be at the
1842     specified \e global position \a p. To translate a widget's local
1843     coordinates into global coordinates, use QWidget::mapToGlobal().
1844 
1845     When positioning a menu with exec() or popup(), bear in mind that
1846     you cannot rely on the menu's current size(). For performance
1847     reasons, the menu adapts its size only when necessary, so in many
1848     cases, the size before and after the show is different. Instead,
1849     use sizeHint() which calculates the proper size depending on the
1850     menu's current contents.
1851 
1852     \sa QWidget::mapToGlobal(), exec()
1853 */
1854 void KexiMenuWidget::popup(const QPoint &p, QAction *atAction)
1855 {
1856 #ifndef Q_OS_SYMBIAN
1857     if (d->scroll) { // reset scroll state from last popup
1858         d->scroll->scrollOffset = 0;
1859         d->scroll->scrollFlags = KexiMenuWidgetPrivate::QMenuScroller::ScrollNone;
1860     }
1861 #endif
1862     //d->tearoffHighlighted = 0;
1863     d->motions = 0;
1864     //d->doChildEffects = true;
1865     d->updateLayoutDirection();
1866 
1867     ensurePolished(); // Get the right font
1868     emit aboutToShow();
1869     //const bool actionListChanged = d->itemsDirty;
1870     d->updateActionRects();
1871     QPoint pos;
1872     /*QPushButton *causedButton = qobject_cast<QPushButton*>(d->causedPopup().widget);
1873     if (actionListChanged && causedButton)
1874         pos = QPushButtonPrivate::get(causedButton)->adjustedMenuPosition();
1875     else*/
1876         pos = p;
1877 
1878     QSize size = sizeHint();
1879     QRect screen;
1880 #ifndef QT_NO_GRAPHICSVIEW
1881     /*bool isEmbedded = !bypassGraphicsProxyWidget(this) && d->nearestGraphicsProxyWidget(this);
1882     if (isEmbedded)
1883         screen = d->popupGeometry(this);
1884     else*/
1885 #endif
1886     screen = d->popupGeometry(QApplication::desktop()->screenNumber(p));
1887 
1888     const int desktopFrame = d->frameWidth();
1889     bool adjustToDesktop = !window()->testAttribute(Qt::WA_DontShowOnScreen);
1890     QList<QAction*> actionsList = this->actions();
1891 #ifdef QT_KEYPAD_NAVIGATION
1892     if (!atAction && QApplication::keypadNavigationEnabled()) {
1893         // Try to have one item activated
1894         if (d->defaultAction && d->defaultAction->isEnabled()) {
1895             atAction = d->defaultAction;
1896             // TODO: This works for first level menus, not yet sub menus
1897         } else {
1898             foreach (QAction *action, actionsList)
1899                 if (action->isEnabled()) {
1900                     atAction = action;
1901                     break;
1902                 }
1903         }
1904         d->currentAction = atAction;
1905     }
1906 #endif
1907     if (d->ncols > 1) {
1908         pos.setY(screen.top() + desktopFrame);
1909     } else if (atAction) {
1910         for (int i = 0, above_height = 0; i < actionsList.count(); i++) {
1911             QAction *action = actionsList.at(i);
1912             if (action == atAction) {
1913                 int newY = pos.y() - above_height;
1914                 if (d->scroll && newY < desktopFrame) {
1915                     d->scroll->scrollFlags = d->scroll->scrollFlags
1916                                              | KexiMenuWidgetPrivate::QMenuScroller::ScrollUp;
1917                     d->scroll->scrollOffset = newY;
1918                     newY = desktopFrame;
1919                 }
1920                 pos.setY(newY);
1921 
1922                 if (d->scroll && d->scroll->scrollFlags != KexiMenuWidgetPrivate::QMenuScroller::ScrollNone
1923                     && !style()->styleHint(QStyle::SH_Menu_FillScreenWithScroll, 0, this)) {
1924                     int below_height = above_height + d->scroll->scrollOffset;
1925                     for (int i2 = i; i2 < d->actionRects.count(); i2++)
1926                         below_height += d->actionRects.at(i2).height();
1927                     size.setHeight(below_height);
1928                 }
1929                 break;
1930             } else {
1931                 above_height += d->actionRects.at(i).height();
1932             }
1933         }
1934     }
1935 
1936     QPoint mouse = QCursor::pos();
1937     d->mousePopupPos = mouse;
1938     const bool snapToMouse = (QRect(p.x() - 3, p.y() - 3, 6, 6).contains(mouse));
1939 
1940     if (adjustToDesktop) {
1941         // handle popup falling "off screen"
1942         if (isRightToLeft()) {
1943             if (snapToMouse) // position flowing left from the mouse
1944                 pos.setX(mouse.x() - size.width());
1945 
1946             if (pos.x() < screen.left() + desktopFrame)
1947                 pos.setX(qMax(p.x(), screen.left() + desktopFrame));
1948             if (pos.x() + size.width() - 1 > screen.right() - desktopFrame)
1949                 pos.setX(qMax(p.x() - size.width(), screen.right() - desktopFrame - size.width() + 1));
1950         } else {
1951             if (pos.x() + size.width() - 1 > screen.right() - desktopFrame)
1952                 pos.setX(screen.right() - desktopFrame - size.width() + 1);
1953             if (pos.x() < screen.left() + desktopFrame)
1954                 pos.setX(screen.left() + desktopFrame);
1955         }
1956         if (pos.y() + size.height() - 1 > screen.bottom() - desktopFrame) {
1957             if(snapToMouse)
1958                 pos.setY(qMin(mouse.y() - (size.height() + desktopFrame), screen.bottom()-desktopFrame-size.height()+1));
1959             else
1960                 pos.setY(qMax(p.y() - (size.height() + desktopFrame), screen.bottom()-desktopFrame-size.height()+1));
1961         } else if (pos.y() < screen.top() + desktopFrame) {
1962             pos.setY(screen.top() + desktopFrame);
1963         }
1964 
1965         if (pos.y() < screen.top() + desktopFrame)
1966             pos.setY(screen.top() + desktopFrame);
1967         if (pos.y() + size.height() - 1 > screen.bottom() - desktopFrame) {
1968             if (d->scroll) {
1969                 d->scroll->scrollFlags |= int(KexiMenuWidgetPrivate::QMenuScroller::ScrollDown);
1970                 int y = qMax(screen.y(),pos.y());
1971                 size.setHeight(screen.bottom() - (desktopFrame * 2) - y);
1972             } else {
1973                 // Too big for screen, bias to see bottom of menu (for some reason)
1974                 pos.setY(screen.bottom() - size.height() + 1);
1975             }
1976         }
1977     }
1978     //setGeometry(QRect(pos, size));
1979     //resize(size);
1980     show();
1981 
1982 #ifndef QT_NO_ACCESSIBILITY
1983     //QAccessible::updateAccessibility(this, 0, QAccessible::PopupMenuStart);
1984 #endif
1985 }
1986 
1987 #if 0
1988 /*!
1989     Executes this menu synchronously.
1990 
1991     This is equivalent to \c{exec(pos())}.
1992 
1993     This returns the triggered QAction in either the popup menu or one
1994     of its submenus, or 0 if no item was triggered (normally because
1995     the user pressed Esc).
1996 
1997     In most situations you'll want to specify the position yourself,
1998     for example, the current mouse position:
1999     \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 0
2000     or aligned to a widget:
2001     \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 1
2002     or in reaction to a QMouseEvent *e:
2003     \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 2
2004 */
2005 QAction *KexiMenuWidget::exec()
2006 {
2007     return exec(pos());
2008 }
2009 
2010 
2011 /*!
2012     \overload
2013 
2014     Executes this menu synchronously.
2015 
2016     Pops up the menu so that the action \a action will be at the
2017     specified \e global position \a p. To translate a widget's local
2018     coordinates into global coordinates, use QWidget::mapToGlobal().
2019 
2020     This returns the triggered QAction in either the popup menu or one
2021     of its submenus, or 0 if no item was triggered (normally because
2022     the user pressed Esc).
2023 
2024     Note that all signals are emitted as usual. If you connect a
2025     QAction to a slot and call the menu's exec(), you get the result
2026     both via the signal-slot connection and in the return value of
2027     exec().
2028 
2029     Common usage is to position the menu at the current mouse
2030     position:
2031     \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 3
2032     or aligned to a widget:
2033     \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 4
2034     or in reaction to a QMouseEvent *e:
2035     \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 5
2036 
2037     When positioning a menu with exec() or popup(), bear in mind that
2038     you cannot rely on the menu's current size(). For performance
2039     reasons, the menu adapts its size only when necessary. So in many
2040     cases, the size before and after the show is different. Instead,
2041     use sizeHint() which calculates the proper size depending on the
2042     menu's current contents.
2043 
2044     \sa popup(), QWidget::mapToGlobal()
2045 */
2046 QAction *KexiMenuWidget::exec(const QPoint &p, QAction *action)
2047 {
2048     createWinId();
2049     QEventLoop eventLoop;
2050     d->eventLoop = &eventLoop;
2051     popup(p, action);
2052 
2053     QPointer<QObject> guard = this;
2054     (void) eventLoop.exec();
2055     if (guard.isNull())
2056         return 0;
2057 
2058     action = d->syncAction;
2059     d->syncAction = 0;
2060     d->eventLoop = 0;
2061     return action;
2062 }
2063 
2064 /*!
2065     \overload
2066 
2067     Executes a menu synchronously.
2068 
2069     The menu's actions are specified by the list of \a actions. The menu will
2070     pop up so that the specified action, \a at, appears at global position \a
2071     pos. If \a at is not specified then the menu appears at position \a
2072     pos. \a parent is the menu's parent widget; specifying the parent will
2073     provide context when \a pos alone is not enough to decide where the menu
2074     should go (e.g., with multiple desktops or when the parent is embedded in
2075     QGraphicsView).
2076 
2077     The function returns the triggered QAction in either the popup
2078     menu or one of its submenus, or 0 if no item was triggered
2079     (normally because the user pressed Esc).
2080 
2081     This is equivalent to:
2082     \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 6
2083 
2084     \sa popup(), QWidget::mapToGlobal()
2085 */
2086 QAction *KexiMenuWidget::exec(QList<QAction*> actions, const QPoint &pos, QAction *at, QWidget *parent)
2087 {
2088     KexiMenuWidget menu(parent);
2089     menu.addActions(actions);
2090     return menu.exec(pos, at);
2091 }
2092 
2093 /*!
2094     \overload
2095 
2096     Executes a menu synchronously.
2097 
2098     The menu's actions are specified by the list of \a actions. The menu
2099     will pop up so that the specified action, \a at, appears at global
2100     position \a pos. If \a at is not specified then the menu appears
2101     at position \a pos.
2102 
2103     The function returns the triggered QAction in either the popup
2104     menu or one of its submenus, or 0 if no item was triggered
2105     (normally because the user pressed Esc).
2106 
2107     This is equivalent to:
2108     \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 6
2109 
2110     \sa popup(), QWidget::mapToGlobal()
2111 */
2112 QAction *KexiMenuWidget::exec(QList<QAction*> actions, const QPoint &pos, QAction *at)
2113 {
2114     // ### Qt 5: merge
2115     return exec(actions, pos, at, 0);
2116 }
2117 #endif
2118 
2119 ClickableLogoArea::ClickableLogoArea(QWidget *parent)
2120  : QAbstractButton(parent)
2121 {
2122     connect(this, SIGNAL(clicked()), this, SLOT(slotClicked()));
2123 }
2124 
2125 void ClickableLogoArea::slotClicked()
2126 {
2127     QDesktopServices::openUrl(QUrl(calligraUrl));
2128 }
2129 
2130 void ClickableLogoArea::paintEvent(QPaintEvent*)
2131 {
2132 }
2133 
2134 void KexiMenuWidgetPrivate::updateLogoPixmap()
2135 {
2136     bool isLight;
2137     if (bespin) {
2138         isLight = q->palette().color(QPalette::Shadow).lightness() >= 128;
2139     }
2140     else {
2141         isLight = KexiUtils::isLightColorScheme();
2142     }
2143     calligraLogoPixmap = QPixmap(isLight ? ":/calligra-logo-white-glow" : ":/calligra-logo-black-glow");
2144 }
2145 
2146 int KexiMenuWidgetPrivate::bottomOfLastItem() const
2147 {
2148     if (actionRects.isEmpty()) {
2149         return 0;
2150     }
2151     return actionRects.last().bottom();
2152 }
2153 
2154 /* Logo's pixmap is consisted of logo and some glow (cut-off vertically).
2155    (without the glow the math would be simpler)
2156  +--------+   +
2157  |  glow  |   |--- glowHeight - cutOffGlow
2158  | +----+ |   +
2159  | |logo| |   |--- calligraLogoPixmapInternalHeight
2160  | +----+ |   +
2161  |  glow  |   |--- glowHeight - cutOffGlow
2162  +--------+   +
2163 */
2164 const int glowHeight = 64;
2165 const int cutOffGlow = 12;
2166 const int spacingAfterLastItem = 10;
2167 
2168 //! @return distance between bottom border of the logo and bottom border of the entire menu widget
2169 int KexiMenuWidgetPrivate::logoBottomMargin() const
2170 {
2171 /*
2172     +----------------+
2173     | Menu item 1    |                             |
2174     | ...            |                             |
2175     | Last menu item |                             | q->height()
2176     +----------------+ <--- bottomOfLastItem()     |
2177     |                | |--- spacingAfterLastItem   |
2178     +----------------+
2179     |      glow      |
2180     |     +----+     |
2181     |     |logo|     |
2182     |     +----+     | +
2183     |      glow      | |--- bottomMargin
2184     +----------------+ +
2185 */
2186     int bottomMargin = glowHeight - cutOffGlow;
2187     bool showSocial = true;
2188     if ((q->height() - bottomMargin - calligraLogoPixmapInternalHeight - cutOffGlow)
2189             <= (bottomOfLastItem() + spacingAfterLastItem + socialWidget->height()))
2190     {
2191         /* Special case when the last menu item would cover the logo: keep the logo below
2192             +----------------+
2193             |     +----+     |
2194             |     |logo|     |
2195             +----------------+ |--- bottomMargin (can be 0 or negative)
2196                                |    */
2197         bottomMargin = q->height() - bottomOfLastItem() - spacingAfterLastItem
2198                        - cutOffGlow - calligraLogoPixmapInternalHeight - socialWidget->height();
2199         showSocial = bottomMargin > QFontMetrics(smallTextFont).height();
2200     }
2201     socialWidget->setVisible(showSocial);
2202     return bottomMargin;
2203 }
2204 
2205 void KexiMenuWidgetPrivate::updateLogo()
2206 {
2207     const QRect logoRect((q->width() - 2 - calligraLogoPixmapInternalWidth) / 2,
2208                          q->height() - logoBottomMargin()
2209                          - calligraLogoPixmapInternalHeight - cutOffGlow,
2210                          calligraLogoPixmapInternalWidth,
2211                          calligraLogoPixmapInternalHeight);
2212     if (!clickableLogoArea) {
2213         updateLogoPixmap();
2214         clickableLogoArea = new ClickableLogoArea(q);
2215         clickableLogoArea->setCursor(Qt::PointingHandCursor);
2216         clickableLogoArea->setToolTip(xi18n("Visit Calligra home page at %1", QLatin1String(calligraUrl)));
2217     }
2218     clickableLogoArea->setGeometry(logoRect.translated(0, -socialWidget->height()));
2219 }
2220 
2221 /*!
2222   \reimp
2223 */
2224 void KexiMenuWidget::showEvent(QShowEvent* event)
2225 {
2226     QWidget::showEvent(event);
2227     d->updateLogo();
2228     d->clickableLogoArea->show();
2229 }
2230 
2231 /*!
2232   \reimp
2233 */
2234 void KexiMenuWidget::hideEvent(QHideEvent *)
2235 {
2236     emit aboutToHide();
2237     if (d->eventLoop)
2238         d->eventLoop->exit();
2239     d->setCurrentAction(0);
2240 #ifndef QT_NO_ACCESSIBILITY
2241     QAccessibleEvent event(this, QAccessible::PopupMenuEnd);
2242     event.setChild(0);
2243     QAccessible::updateAccessibility(&event);
2244 #endif
2245     d->mouseDown = 0;
2246     d->hasHadMouse = false;
2247     d->causedPopup.widget = 0;
2248     d->causedPopup.action = 0;
2249     if (d->scroll)
2250         d->scroll->scrollTimer.stop(); //make sure the timer stops
2251     // this unmarks the previous persistent action
2252     d->setActionPersistentlySelected(0, false);
2253 }
2254 
2255 /*!
2256   \reimp
2257 */
2258 void KexiMenuWidget::paintEvent(QPaintEvent *e)
2259 {
2260     d->updateActionRects();
2261     QPainter p(this);
2262     QRegion emptyArea = QRegion(rect());
2263 
2264 //! @todo KEXI3 port OxygenHelper
2265 #if 0
2266     if (d->oxygenHelper) {
2267         //d->oxygenHelper->renderWindowBackground(&p, rect(), this, palette());
2268         d->oxygenHelper->renderMenuBackground(&p, rect(), this, palette());
2269 //        d->oxygenHelper->drawFloatFrame(&p, rect(), palette().window().color(), true);
2270     }
2271 #endif
2272 
2273     QStyleOptionMenuItem menuOpt;
2274     menuOpt.initFrom(this);
2275     menuOpt.state = QStyle::State_None;
2276     menuOpt.checkType = QStyleOptionMenuItem::NotCheckable;
2277     menuOpt.maxIconWidth = 0;
2278     menuOpt.tabWidth = 0;
2279     if (d->bespin) {
2280         p.fillRect(rect(), palette().shadow());
2281     }
2282     else {
2283         style()->drawPrimitive(QStyle::PE_PanelMenu, &menuOpt, &p, this);
2284     }
2285 
2286     {
2287         QStyleOptionFrame opt;
2288         opt.initFrom(this);
2289         opt.frameShape = QFrame::VLine;
2290         opt.state |= QStyle::State_Raised;
2291         opt.lineWidth = 1;
2292         opt.midLineWidth = 0;
2293         //opt.rect = QRect(opt.rect.topRight() - QPoint(0, 0), opt.rect.bottomRight() + QPoint(10, 0));
2294         //const QRect cr = style()->subElementRect(QStyle::SE_ShapedFrameContents, &opt);
2295         //opt.lineWidth = opt.rect.right() - cr.right();
2296         opt.rect.setX(opt.rect.width() - opt.lineWidth);
2297         opt.rect.setWidth(opt.lineWidth);
2298         style()->drawControl(QStyle::CE_ShapedFrame, &opt, &p);
2299 
2300         /*
2301         QStyleOption opt;
2302         opt.initFrom(this);
2303         opt.state |= QStyle::State_Horizontal;
2304         opt.rect = QRect(opt.rect.topRight() - QPoint(10, 0), opt.rect.bottomRight() + QPoint(10, 0));
2305         style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, &p, this);
2306         */
2307     }
2308 
2309     //draw the items that need updating..
2310     QList<QAction*> actionsList = this->actions();
2311     for (int i = 0; i < actionsList.count(); ++i) {
2312         QAction *action = actionsList.at(i);
2313         QRect adjustedActionRect = d->actionRects.at(i);
2314         if (!e->rect().intersects(adjustedActionRect)
2315             || d->widgetItems.value(action))
2316            continue;
2317         //set the clip region to be extra safe (and adjust for the scrollers)
2318         QRegion adjustedActionReg(adjustedActionRect);
2319         emptyArea -= adjustedActionReg;
2320         p.setClipRegion(adjustedActionReg);
2321 
2322         QStyleOptionMenuItem opt;
2323         initStyleOption(&opt, action);
2324         opt.rect = adjustedActionRect;
2325         if (d->actionPersistentlySelected(action)) {
2326             opt.state |= QStyle::State_Selected;
2327             if (!d->bespin && !d->qtcurve) {
2328                 opt.palette.setBrush(QPalette::Window, opt.palette.brush(QPalette::Highlight));
2329                 opt.palette.setBrush(QPalette::WindowText, opt.palette.brush(QPalette::HighlightedText));
2330             }
2331         }
2332         else if (!action->isSeparator()) {
2333             if (!d->bespin && opt.state & QStyle::State_Selected) {
2334                 // lighten the highlight to make it different from
2335                 // the persistently selected item
2336                 opt.palette.setColor(QPalette::Highlight,
2337                                      KColorUtils::mix(
2338                                         opt.palette.color(QPalette::Highlight),
2339                                         opt.palette.color(QPalette::Window)));
2340                 opt.palette.setColor(QPalette::HighlightedText, opt.palette.color(QPalette::Text));
2341             }
2342         }
2343 
2344         // Depending on style Button or Background brush may be used
2345         // to fill background of deselected items. Make it transparent.
2346         bool transparentBackground = !(opt.state & QStyle::State_Selected);
2347 //! @todo KEXI3 port OxygenHelper
2348 #if 0
2349         if (d->oxygenHelper && action->isSeparator()) {
2350             transparentBackground = false;
2351         }
2352 #endif
2353         if (transparentBackground) {
2354             opt.palette.setBrush(QPalette::Button, QBrush(Qt::transparent));
2355             if (!d->bespin) {
2356                 opt.palette.setBrush(QPalette::Background, QBrush(Qt::transparent));
2357             }
2358         }
2359 
2360         style()->drawControl(QStyle::CE_MenuItem, &opt, &p, this);
2361     }
2362 
2363     const int fw = d->frameWidth();
2364     //draw the scroller regions..
2365     if (d->scroll) {
2366         menuOpt.menuItemType = QStyleOptionMenuItem::Scroller;
2367         menuOpt.state |= QStyle::State_Enabled;
2368         if (d->scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollUp) {
2369             menuOpt.rect.setRect(fw, fw, width() - (fw * 2), d->scrollerHeight());
2370             emptyArea -= QRegion(menuOpt.rect);
2371             p.setClipRect(menuOpt.rect);
2372             style()->drawControl(QStyle::CE_MenuScroller, &menuOpt, &p, this);
2373         }
2374         if (d->scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollDown) {
2375             menuOpt.rect.setRect(fw, height() - d->scrollerHeight() - fw, width() - (fw * 2),
2376                                      d->scrollerHeight());
2377             emptyArea -= QRegion(menuOpt.rect);
2378             menuOpt.state |= QStyle::State_DownArrow;
2379             p.setClipRect(menuOpt.rect);
2380             style()->drawControl(QStyle::CE_MenuScroller, &menuOpt, &p, this);
2381         }
2382     }
2383     //paint the tear off..
2384 /*    if (d->tearoff) {
2385         menuOpt.menuItemType = QStyleOptionMenuItem::TearOff;
2386         menuOpt.rect.setRect(fw, fw, width() - (fw * 2),
2387                              style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, this));
2388         if (d->scroll && d->scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollUp)
2389             menuOpt.rect.translate(0, d->scrollerHeight());
2390         emptyArea -= QRegion(menuOpt.rect);
2391         p.setClipRect(menuOpt.rect);
2392         menuOpt.state = QStyle::State_None;
2393         if (d->tearoffHighlighted)
2394             menuOpt.state |= QStyle::State_Selected;
2395         style()->drawControl(QStyle::CE_MenuTearoff, &menuOpt, &p, this);
2396     }*/
2397     //draw border
2398     if (fw > 0) {
2399         QRegion borderReg;
2400         borderReg += QRect(0, 0, fw, height()); //left
2401         borderReg += QRect(width()-fw, 0, fw, height()); //right
2402         borderReg += QRect(0, 0, width(), fw); //top
2403         borderReg += QRect(0, height()-fw, width(), fw); //bottom
2404         p.setClipRegion(borderReg);
2405         emptyArea -= borderReg;
2406         QStyleOptionFrame frame;
2407         frame.rect = rect();
2408         frame.palette = palette();
2409         frame.state = QStyle::State_None;
2410         frame.lineWidth = style()->pixelMetric(QStyle::PM_MenuPanelWidth);
2411         frame.midLineWidth = 0;
2412         style()->drawPrimitive(QStyle::PE_FrameMenu, &frame, &p, this);
2413         // full frame:
2414         style()->drawPrimitive(QStyle::PE_FrameWindow, &frame, &p, this);
2415     }
2416 
2417     //finally the rest of the space
2418     p.setClipRegion(emptyArea);
2419     menuOpt.state = QStyle::State_None;
2420     menuOpt.menuItemType = QStyleOptionMenuItem::EmptyArea;
2421     menuOpt.checkType = QStyleOptionMenuItem::NotCheckable;
2422     menuOpt.rect = rect();
2423     menuOpt.menuRect = rect();
2424     style()->drawControl(QStyle::CE_MenuEmptyArea, &menuOpt, &p, this);
2425 
2426     p.translate(0, -d->socialWidget->height());
2427 
2428     // version
2429     p.setFont(d->smallTextFont);
2430     QColor textColor;
2431     textColor = palette().color(QPalette::Base);
2432     p.setPen(QPen(textColor));
2433     const int logoBottomMargin = d->logoBottomMargin();
2434     QRect textRect(0, height() - logoBottomMargin + 1, width(),
2435                    logoBottomMargin - 1 + d->socialWidget->height());
2436     p.drawText(textRect, Qt::AlignHCenter | Qt::AlignTop, QLatin1String(Kexi::versionString()));
2437     textColor = palette().color(QPalette::WindowText);
2438     textColor.setAlpha(180);
2439     p.setPen(QPen(textColor));
2440     textRect.translate(0, -1);
2441     p.drawText(textRect, Qt::AlignHCenter | Qt::AlignTop, QLatin1String(Kexi::versionString()));
2442 
2443     // logo
2444     p.drawPixmap((width() - d->calligraLogoPixmap.width()) / 2,
2445                     textRect.top() - d->calligraLogoPixmap.height()
2446                     + calligraLogoPixmapInternalHeight - 20,
2447                     d->calligraLogoPixmap);
2448 }
2449 
2450 #ifndef QT_NO_WHEELEVENT
2451 /*!
2452   \reimp
2453 */
2454 void KexiMenuWidget::wheelEvent(QWheelEvent *e)
2455 {
2456     if (d->scroll && rect().contains(e->pos()))
2457         d->scrollMenu(e->delta() > 0 ?
2458                       KexiMenuWidgetPrivate::QMenuScroller::ScrollUp : KexiMenuWidgetPrivate::QMenuScroller::ScrollDown);
2459 }
2460 #endif
2461 
2462 /*!
2463   \reimp
2464 */
2465 void KexiMenuWidget::mousePressEvent(QMouseEvent *e)
2466 {
2467     if (d->aboutToHide || d->mouseEventTaken(e))
2468         return;
2469     if (!rect().contains(e->pos())) {
2470          if (d->noReplayFor
2471              && QRect(d->noReplayFor->mapToGlobal(QPoint()), d->noReplayFor->size()).contains(e->globalPos()))
2472              setAttribute(Qt::WA_NoMouseReplay);
2473          if (d->eventLoop) // synchronous operation
2474              d->syncAction = 0;
2475         d->hideUpToMenuBar();
2476         return;
2477     }
2478     d->mouseDown = this;
2479 
2480     QAction *action = d->actionAt(e->pos());
2481     d->setCurrentAction(action, 20);
2482     update();
2483 }
2484 
2485 /*!
2486   \reimp
2487 */
2488 void KexiMenuWidget::mouseReleaseEvent(QMouseEvent *e)
2489 {
2490     if (d->aboutToHide || d->mouseEventTaken(e))
2491         return;
2492     if(d->mouseDown != this) {
2493         d->mouseDown = 0;
2494         return;
2495     }
2496 
2497     d->mouseDown = 0;
2498     d->setSyncAction();
2499     QAction *action = d->actionAt(e->pos());
2500     //qDebug() << "action:" << action << "d->currentAction:" << d->currentAction;
2501     if (action && action == d->currentAction) {
2502         if (!action->menu()){
2503 #if defined(Q_OS_WIN)
2504             //On Windows only context menus can be activated with the right button
2505             if (e->button() == Qt::LeftButton || d->topCausedWidget() == 0)
2506 #endif
2507             {
2508                 if (!d->actionPersistentlySelected(action)) {
2509                     d->toggleActionPersistentlySelected(action);
2510                     update();
2511                     d->activateAction(action, QAction::Trigger);
2512                 }
2513             }
2514         }
2515     } else if (d->hasMouseMoved(e->globalPos())) {
2516         d->hideUpToMenuBar();
2517     }
2518 }
2519 
2520 /*!
2521   \reimp
2522 */
2523 void KexiMenuWidget::changeEvent(QEvent *e)
2524 {
2525     if (e->type() == QEvent::StyleChange || e->type() == QEvent::FontChange ||
2526         e->type() == QEvent::LayoutDirectionChange) {
2527         d->itemsDirty = 1;
2528         setMouseTracking(style()->styleHint(QStyle::SH_Menu_MouseTracking, 0, this));
2529         if (isVisible())
2530             resize(sizeHint());
2531         if (!style()->styleHint(QStyle::SH_Menu_Scrollable, 0, this)) {
2532             delete d->scroll;
2533             d->scroll = 0;
2534         } else if (!d->scroll) {
2535             d->scroll = new KexiMenuWidgetPrivate::QMenuScroller;
2536             d->scroll->scrollFlags = KexiMenuWidgetPrivate::QMenuScroller::ScrollNone;
2537         }
2538     } else if (e->type() == QEvent::EnabledChange) {
2539         d->menuAction->setEnabled(isEnabled());
2540     }
2541     else if (e->type() == QEvent::PaletteChange) {
2542         d->updateLogoPixmap();
2543         d->updateLogo();
2544     }
2545     QWidget::changeEvent(e);
2546 }
2547 
2548 
2549 /*!
2550   \reimp
2551 */
2552 bool
2553 KexiMenuWidget::event(QEvent *e)
2554 {
2555     switch (e->type()) {
2556     case QEvent::Polish:
2557         d->updateLayoutDirection();
2558         break;
2559     case QEvent::ShortcutOverride: {
2560             QKeyEvent *kev = static_cast<QKeyEvent*>(e);
2561             if (kev->key() == Qt::Key_Up || kev->key() == Qt::Key_Down
2562                 || kev->key() == Qt::Key_Left || kev->key() == Qt::Key_Right
2563                 || kev->key() == Qt::Key_Enter || kev->key() == Qt::Key_Return
2564                 || kev->key() == Qt::Key_Escape) {
2565                 e->accept();
2566                 return true;
2567             }
2568         }
2569         break;
2570     case QEvent::KeyPress: {
2571         QKeyEvent *ke = (QKeyEvent*)e;
2572         if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
2573             keyPressEvent(ke);
2574             return true;
2575         }
2576     } break;
2577     /*case QEvent::ContextMenu:
2578         if(d->menuDelayTimer.isActive()) {
2579             d->menuDelayTimer.stop();
2580             internalDelayedPopup();
2581         }
2582         break;*/
2583     case QEvent::Resize: {
2584         QStyleHintReturnMask menuMask;
2585         QStyleOption option;
2586         option.initFrom(this);
2587         if (style()->styleHint(QStyle::SH_Menu_Mask, &option, this, &menuMask)) {
2588             setMask(menuMask.region);
2589         }
2590         d->itemsDirty = 1;
2591         d->updateActionRects();
2592         d->updateLogo();
2593         break;
2594     }
2595     case QEvent::Show:
2596         d->mouseDown = 0;
2597         d->updateActionRects();
2598         show();
2599 //         if (d->currentAction)
2600 //             d->popupAction(d->currentAction, 0, false);
2601         break;
2602 #ifndef QT_NO_WHATSTHIS
2603     case QEvent::QueryWhatsThis:
2604         e->setAccepted(whatsThis().size());
2605         if (QAction *action = d->actionAt(static_cast<QHelpEvent*>(e)->pos())) {
2606             if (action->whatsThis().size() || action->menu())
2607                 e->accept();
2608         }
2609         return true;
2610 #endif
2611 #ifdef QT_SOFTKEYS_ENABLED
2612     case QEvent::LanguageChange: {
2613         d->selectAction->setText(QSoftKeyManager::standardSoftKeyText(QSoftKeyManager::SelectSoftKey));
2614         d->cancelAction->setText(QSoftKeyManager::standardSoftKeyText(QSoftKeyManager::CancelSoftKey));
2615         }
2616         break;
2617 #endif
2618     default:
2619         break;
2620     }
2621     return QWidget::event(e);
2622 }
2623 
2624 /*!
2625     \reimp
2626 */
2627 bool KexiMenuWidget::focusNextPrevChild(bool next)
2628 {
2629     setFocus();
2630     QKeyEvent ev(QEvent::KeyPress, next ? Qt::Key_Tab : Qt::Key_Backtab, Qt::NoModifier);
2631     keyPressEvent(&ev);
2632     return true;
2633 }
2634 
2635 /*!
2636   \reimp
2637 */
2638 void KexiMenuWidget::keyPressEvent(QKeyEvent *e)
2639 {
2640     d->updateActionRects();
2641     int key = e->key();
2642     if (isRightToLeft()) {  // in reverse mode open/close key for submenues are reversed
2643         if (key == Qt::Key_Left)
2644             key = Qt::Key_Right;
2645         else if (key == Qt::Key_Right)
2646             key = Qt::Key_Left;
2647     }
2648 #ifndef Q_OS_MACOS
2649     if (key == Qt::Key_Tab) //means down
2650         key = Qt::Key_Down;
2651     if (key == Qt::Key_Backtab) //means up
2652         key = Qt::Key_Up;
2653 #endif
2654 
2655     bool key_consumed = false;
2656     QList<QAction*> actionsList = this->actions();
2657     switch(key) {
2658     case Qt::Key_Home:
2659         key_consumed = true;
2660         if (d->scroll)
2661             d->scrollMenu(KexiMenuWidgetPrivate::QMenuScroller::ScrollTop, true);
2662         break;
2663     case Qt::Key_End:
2664         key_consumed = true;
2665         if (d->scroll)
2666             d->scrollMenu(KexiMenuWidgetPrivate::QMenuScroller::ScrollBottom, true);
2667         break;
2668     case Qt::Key_PageUp:
2669         key_consumed = true;
2670         if (d->currentAction && d->scroll) {
2671             if(d->scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollUp)
2672                 d->scrollMenu(KexiMenuWidgetPrivate::QMenuScroller::ScrollUp, true, true);
2673             else
2674                 d->scrollMenu(KexiMenuWidgetPrivate::QMenuScroller::ScrollTop, true);
2675         }
2676         break;
2677     case Qt::Key_PageDown:
2678         key_consumed = true;
2679         if (d->currentAction && d->scroll) {
2680             if(d->scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollDown)
2681                 d->scrollMenu(KexiMenuWidgetPrivate::QMenuScroller::ScrollDown, true, true);
2682             else
2683                 d->scrollMenu(KexiMenuWidgetPrivate::QMenuScroller::ScrollBottom, true);
2684         }
2685         break;
2686     case Qt::Key_Up:
2687     case Qt::Key_Down: {
2688         key_consumed = true;
2689         QAction *nextAction = 0;
2690         KexiMenuWidgetPrivate::QMenuScroller::ScrollLocation scroll_loc = KexiMenuWidgetPrivate::QMenuScroller::ScrollStay;
2691         if (!d->currentAction) {
2692             if(key == Qt::Key_Down) {
2693                 for(int i = 0; i < actionsList.count(); ++i) {
2694                     QAction *act = actionsList.at(i);
2695                     if (d->actionRects.at(i).isNull())
2696                         continue;
2697                     if (!act->isSeparator() &&
2698                         (style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this)
2699                          || act->isEnabled())) {
2700                         nextAction = act;
2701                         break;
2702                     }
2703                 }
2704             } else {
2705                 for(int i = actionsList.count()-1; i >= 0; --i) {
2706                     QAction *act = actionsList.at(i);
2707                     if (d->actionRects.at(i).isNull())
2708                         continue;
2709                     if (!act->isSeparator() &&
2710                         (style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this)
2711                          || act->isEnabled())) {
2712                         nextAction = act;
2713                         break;
2714                     }
2715                 }
2716             }
2717         } else {
2718             for(int i = 0, y = 0; !nextAction && i < actionsList.count(); i++) {
2719                 QAction *act = actionsList.at(i);
2720                 if (act == d->currentAction) {
2721                     if (key == Qt::Key_Up) {
2722                         for(int next_i = i-1; true; next_i--) {
2723                             if (next_i == -1) {
2724                                 if(!style()->styleHint(QStyle::SH_Menu_SelectionWrap, 0, this))
2725                                     break;
2726                                 if (d->scroll)
2727                                     scroll_loc = KexiMenuWidgetPrivate::QMenuScroller::ScrollBottom;
2728                                 next_i = d->actionRects.count()-1;
2729                             }
2730                             QAction *next = actionsList.at(next_i);
2731                             if (next == d->currentAction)
2732                                 break;
2733                             if (d->actionRects.at(next_i).isNull())
2734                                 continue;
2735                             if (next->isSeparator() ||
2736                                (!next->isEnabled() &&
2737                                 !style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this)))
2738                                 continue;
2739                             nextAction = next;
2740                             if (d->scroll && (d->scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollUp)) {
2741                                 int topVisible = d->scrollerHeight();
2742 /*                                if (d->tearoff)
2743                                     topVisible += style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, this);*/
2744                                 if (((y + d->scroll->scrollOffset) - topVisible) <= d->actionRects.at(next_i).height())
2745                                     scroll_loc = KexiMenuWidgetPrivate::QMenuScroller::ScrollTop;
2746                             }
2747                             break;
2748                         }
2749 /*                        if (!nextAction && d->tearoff)
2750                             d->tearoffHighlighted = 1;*/
2751                     } else {
2752                         y += d->actionRects.at(i).height();
2753                         for(int next_i = i+1; true; next_i++) {
2754                             if (next_i == d->actionRects.count()) {
2755                                 if(!style()->styleHint(QStyle::SH_Menu_SelectionWrap, 0, this))
2756                                     break;
2757                                 if (d->scroll)
2758                                     scroll_loc = KexiMenuWidgetPrivate::QMenuScroller::ScrollTop;
2759                                 next_i = 0;
2760                             }
2761                             QAction *next = actionsList.at(next_i);
2762                             if (next == d->currentAction)
2763                                 break;
2764                             if (d->actionRects.at(next_i).isNull())
2765                                 continue;
2766                             if (next->isSeparator() ||
2767                                (!next->isEnabled() &&
2768                                 !style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this)))
2769                                 continue;
2770                             nextAction = next;
2771                             if (d->scroll && (d->scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollDown)) {
2772                                 int bottomVisible = height() - d->scrollerHeight();
2773                                 if (d->scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollUp)
2774                                     bottomVisible -= d->scrollerHeight();
2775 /*                                if (d->tearoff)
2776                                     bottomVisible -= style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, this);*/
2777                                 if ((y + d->scroll->scrollOffset + d->actionRects.at(next_i).height()) > bottomVisible)
2778                                     scroll_loc = KexiMenuWidgetPrivate::QMenuScroller::ScrollBottom;
2779                             }
2780                             break;
2781                         }
2782                     }
2783                     break;
2784                 }
2785                 y += d->actionRects.at(i).height();
2786             }
2787         }
2788         if (nextAction) {
2789             if (d->scroll && scroll_loc != KexiMenuWidgetPrivate::QMenuScroller::ScrollStay) {
2790                 d->scroll->scrollTimer.stop();
2791                 d->scrollMenu(nextAction, scroll_loc);
2792             }
2793             d->setCurrentAction(nextAction, /*popup*/-1, KexiMenuWidget::SelectedFromKeyboard);
2794         }
2795         break; }
2796 
2797     /*case Qt::Key_Right:
2798         if (d->currentAction && d->currentAction->isEnabled() && d->currentAction->menu()) {
2799             d->popupAction(d->currentAction, 0, true);
2800             key_consumed = true;
2801             break;
2802         }
2803         //FALL THROUGH*/
2804     case Qt::Key_Left: {
2805         if (d->currentAction && !d->scroll) {
2806             QAction *nextAction = 0;
2807             QRect actionR = d->actionRect(d->currentAction);
2808             for(int x = actionR.left()-1; !nextAction && x >= 0; x--)
2809                 nextAction = d->actionAt(QPoint(x, actionR.center().y()));
2810             if (nextAction) {
2811                 d->setCurrentAction(nextAction, /*popup*/-1, KexiMenuWidget::SelectedFromKeyboard);
2812                 key_consumed = true;
2813             }
2814         }
2815         if (!key_consumed && key == Qt::Key_Left && qobject_cast<KexiMenuWidget*>(d->causedPopup.widget)) {
2816             QPointer<QWidget> caused = d->causedPopup.widget;
2817             d->hideMenu(this);
2818             if (caused)
2819                 caused->setFocus();
2820             key_consumed = true;
2821         }
2822         break; }
2823 
2824     case Qt::Key_Alt:
2825 
2826         key_consumed = true;
2827         if (style()->styleHint(QStyle::SH_MenuBar_AltKeyNavigation, 0, this))
2828         {
2829             d->hideMenu(this);
2830         }
2831         break;
2832 
2833     case Qt::Key_Escape:
2834 #ifdef QT_KEYPAD_NAVIGATION
2835     case Qt::Key_Back:
2836 #endif
2837         key_consumed = true;
2838         {
2839             QPointer<QWidget> caused = d->causedPopup.widget;
2840             d->hideMenu(this); // hide after getting causedPopup
2841         }
2842         break;
2843 
2844     case Qt::Key_Space:
2845         if (style()->styleHint(QStyle::SH_Menu_SpaceActivatesItem, 0, this)) {
2846             key_consumed = true; // for motif
2847         }
2848         break;
2849 #ifdef QT_KEYPAD_NAVIGATION
2850     case Qt::Key_Select:
2851 #endif
2852     case Qt::Key_Return:
2853     case Qt::Key_Enter: {
2854         if (!d->currentAction) {
2855             d->setFirstActionActive();
2856             key_consumed = true;
2857             break;
2858         }
2859 
2860         d->setSyncAction();
2861 
2862         if (d->currentAction->menu()) {
2863             /*d->popupAction(d->currentAction, 0, true);*/
2864         }
2865         else {
2866             d->activateAction(d->currentAction, QAction::Trigger);
2867         }
2868         key_consumed = true;
2869         break;
2870     }
2871 #ifndef QT_NO_WHATSTHIS
2872     case Qt::Key_F1:
2873         if (!d->currentAction || d->currentAction->whatsThis().isNull())
2874             break;
2875         QWhatsThis::enterWhatsThisMode();
2876         d->activateAction(d->currentAction, QAction::Trigger);
2877         return;
2878 #endif
2879     default:
2880         break;
2881     }
2882 
2883     if (!key_consumed) {                                // send to menu bar
2884         if ((!e->modifiers() || e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ShiftModifier) &&
2885            e->text().length()==1) {
2886             bool activateAction = false;
2887             QAction *nextAction = 0;
2888             if (style()->styleHint(QStyle::SH_Menu_KeyboardSearch, 0, this) && !e->modifiers()) {
2889                 int best_match_count = 0;
2890                 d->searchBufferTimer.start(2000, this);
2891                 d->searchBuffer += e->text();
2892                 for(int i = 0; i < actionsList.size(); ++i) {
2893                     int match_count = 0;
2894                     if (d->actionRects.at(i).isNull())
2895                         continue;
2896                     QAction *act = actionsList.at(i);
2897                     const QString act_text = act->text();
2898                     for(int c = 0; c < d->searchBuffer.size(); ++c) {
2899                         if(act_text.indexOf(d->searchBuffer.at(c), 0, Qt::CaseInsensitive) != -1)
2900                             ++match_count;
2901                     }
2902                     if(match_count > best_match_count) {
2903                         best_match_count = match_count;
2904                         nextAction = act;
2905                     }
2906                 }
2907             }
2908 #ifndef QT_NO_SHORTCUT
2909             else {
2910                 int clashCount = 0;
2911                 QAction *first = 0, *currentSelected = 0, *firstAfterCurrent = 0;
2912                 QChar c = e->text().at(0).toUpper();
2913                 for(int i = 0; i < actionsList.size(); ++i) {
2914                     if (d->actionRects.at(i).isNull())
2915                         continue;
2916                     QAction *act = actionsList.at(i);
2917                     QKeySequence sequence = QKeySequence::mnemonic(act->text());
2918                     int key = sequence[0] & 0xffff;
2919                     if (key == c.unicode()) {
2920                         clashCount++;
2921                         if (!first)
2922                             first = act;
2923                         if (act == d->currentAction)
2924                             currentSelected = act;
2925                         else if (!firstAfterCurrent && currentSelected)
2926                             firstAfterCurrent = act;
2927                     }
2928                 }
2929                 if (clashCount == 1)
2930                     activateAction = true;
2931                 if (clashCount >= 1) {
2932                     if (clashCount == 1 || !currentSelected || !firstAfterCurrent)
2933                         nextAction = first;
2934                     else
2935                         nextAction = firstAfterCurrent;
2936                 }
2937             }
2938 #endif
2939             if (nextAction) {
2940                 key_consumed = true;
2941                 if(d->scroll)
2942                     d->scrollMenu(nextAction, KexiMenuWidgetPrivate::QMenuScroller::ScrollCenter, false);
2943                 d->setCurrentAction(nextAction, 20, KexiMenuWidget::SelectedFromElsewhere, true);
2944                 if (!nextAction->menu() && activateAction) {
2945                     d->setSyncAction();
2946                     d->activateAction(nextAction, QAction::Trigger);
2947                 }
2948             }
2949         }
2950         if (!key_consumed) {
2951         }
2952 
2953 #ifdef Q_OS_WIN32
2954         if (key_consumed && (e->key() == Qt::Key_Control || e->key() == Qt::Key_Shift || e->key() == Qt::Key_Meta))
2955             QApplication::beep();
2956 #endif // Q_OS_WIN32
2957     }
2958     if (key_consumed)
2959         e->accept();
2960     else
2961         e->ignore();
2962 }
2963 
2964 /*!
2965   \reimp
2966 */
2967 void KexiMenuWidget::mouseMoveEvent(QMouseEvent *e)
2968 {
2969     if (!isVisible() || d->aboutToHide || d->mouseEventTaken(e))
2970         return;
2971     d->motions++;
2972     if (d->motions == 0) // ignore first mouse move event (see enterEvent())
2973         return;
2974     d->hasHadMouse = d->hasHadMouse || rect().contains(e->pos());
2975 
2976     QAction *action = d->actionAt(e->pos());
2977     if (!action) {
2978         if (d->hasHadMouse
2979             && (!d->currentAction
2980                 || !(d->currentAction->menu() && d->currentAction->menu()->isVisible())))
2981             d->setCurrentAction(0);
2982         return;
2983     } else if(e->buttons()) {
2984         d->mouseDown = this;
2985     }
2986     if (d->sloppyRegion.contains(e->pos())) {
2987         d->sloppyAction = action;
2988         KexiMenuWidgetPrivate::sloppyDelayTimer = startTimer(style()->styleHint(QStyle::SH_Menu_SubMenuPopupDelay, 0, this)*6);
2989     } else {
2990         d->setCurrentAction(action, style()->styleHint(QStyle::SH_Menu_SubMenuPopupDelay, 0, this));
2991     }
2992 }
2993 
2994 /*!
2995   \reimp
2996 */
2997 void KexiMenuWidget::enterEvent(QEvent *)
2998 {
2999     d->motions = -1; // force us to ignore the generate mouse move in mouseMoveEvent()
3000 }
3001 
3002 /*!
3003   \reimp
3004 */
3005 void KexiMenuWidget::leaveEvent(QEvent *)
3006 {
3007     d->sloppyAction = 0;
3008     if (!d->sloppyRegion.isEmpty())
3009         d->sloppyRegion = QRegion();
3010     if (!d->activeMenu && d->currentAction)
3011         setActiveAction(0);
3012 }
3013 
3014 /*!
3015   \reimp
3016 */
3017 void
3018 KexiMenuWidget::timerEvent(QTimerEvent *e)
3019 {
3020     if (d->scroll && d->scroll->scrollTimer.timerId() == e->timerId()) {
3021         d->scrollMenu((KexiMenuWidgetPrivate::QMenuScroller::ScrollDirection)d->scroll->scrollDirection);
3022         if (d->scroll->scrollFlags == KexiMenuWidgetPrivate::QMenuScroller::ScrollNone)
3023             d->scroll->scrollTimer.stop();
3024     }/* else if(d->menuDelayTimer.timerId() == e->timerId()) {
3025         d->menuDelayTimer.stop();
3026         internalDelayedPopup();
3027     }*/ else if(KexiMenuWidgetPrivate::sloppyDelayTimer == e->timerId()) {
3028         killTimer(KexiMenuWidgetPrivate::sloppyDelayTimer);
3029         KexiMenuWidgetPrivate::sloppyDelayTimer = 0;
3030         internalSetSloppyAction();
3031     } else if(d->searchBufferTimer.timerId() == e->timerId()) {
3032         d->searchBuffer.clear();
3033     }
3034 }
3035 
3036 /*!
3037   \reimp
3038 */
3039 void KexiMenuWidget::actionEvent(QActionEvent *e)
3040 {
3041     d->itemsDirty = 1;
3042     //setAttribute(Qt::WA_Resized, false);
3043     if (e->type() == QEvent::ActionAdded) {
3044         connect(e->action(), SIGNAL(triggered()), this, SLOT(actionTriggered()));
3045         connect(e->action(), SIGNAL(hovered()), this, SLOT(actionHovered()));
3046         if (QWidgetAction *wa = qobject_cast<QWidgetAction *>(e->action())) {
3047             QWidget *widget = wa->requestWidget(this);
3048             if (widget)
3049                 d->widgetItems.insert(wa, widget);
3050         }
3051     } else if (e->type() == QEvent::ActionRemoved) {
3052         e->action()->disconnect(this);
3053         if (e->action() == d->currentAction)
3054             d->currentAction = 0;
3055         if (QWidgetAction *wa = qobject_cast<QWidgetAction *>(e->action())) {
3056             if (QWidget *widget = d->widgetItems.value(wa))
3057                 wa->releaseWidget(widget);
3058         }
3059         d->widgetItems.remove(e->action());
3060     }
3061 
3062     if (isVisible()) {
3063         d->updateActionRects();
3064         resize(sizeHint());
3065         update();
3066     }
3067 }
3068 
3069 /*!
3070   \internal
3071 */
3072 void KexiMenuWidget::internalSetSloppyAction()
3073 {
3074     if (d->sloppyAction)
3075         d->setCurrentAction(d->sloppyAction, 0);
3076 }
3077 
3078 #if 0
3079 /*!
3080   \internal
3081 */
3082 void KexiMenuWidget::internalDelayedPopup()
3083 {
3084     //hide the current item
3085     if (KexiMenuWidget *menu = d->activeMenu) {
3086         d->activeMenu = 0;
3087         d->hideMenu(menu);
3088     }
3089 
3090     if (!d->currentAction || !d->currentAction->isEnabled() || !d->currentAction->menu() ||
3091         !d->currentAction->menu()->isEnabled() || d->currentAction->menu()->isVisible())
3092         return;
3093 
3094     //setup
3095     d->activeMenu = d->currentAction->menu();
3096     d->activeMenu->causedPopup().widget = this;
3097     d->activeMenu->causedPopup().action = d->currentAction;
3098 
3099     int subMenuOffset = style()->pixelMetric(QStyle::PM_SubMenuOverlap, 0, this);
3100     const QRect actionRect(d->actionRect(d->currentAction));
3101     const QSize menuSize(d->activeMenu->sizeHint());
3102     const QPoint rightPos(mapToGlobal(QPoint(actionRect.right() + subMenuOffset + 1, actionRect.top())));
3103     const QPoint leftPos(mapToGlobal(QPoint(actionRect.left() - subMenuOffset - menuSize.width(), actionRect.top())));
3104 
3105     QPoint pos(rightPos);
3106     KexiMenuWidget *caused = qobject_cast<KexiMenuWidget*>(d->activeMenu->causedPopup().widget);
3107 
3108     const QRect availGeometry(d->popupGeometry(caused));
3109     if (isRightToLeft()) {
3110         pos = leftPos;
3111         if ((caused && caused->x() < x()) || pos.x() < availGeometry.left()) {
3112             if(rightPos.x() + menuSize.width() < availGeometry.right())
3113                 pos = rightPos;
3114             else
3115                 pos.rx() = availGeometry.left();
3116         }
3117     } else {
3118         if ((caused && caused->x() > x()) || pos.x() + menuSize.width() > availGeometry.right()) {
3119             if(leftPos.x() < availGeometry.left())
3120                 pos.rx() = availGeometry.right() - menuSize.width();
3121             else
3122                 pos = leftPos;
3123         }
3124     }
3125 
3126     //calc sloppy focus buffer
3127     if (style()->styleHint(QStyle::SH_Menu_SloppySubMenus, 0, this)) {
3128         QPoint cur = QCursor::pos();
3129         if (actionRect.contains(mapFromGlobal(cur))) {
3130             QPoint pts[4];
3131             pts[0] = QPoint(cur.x(), cur.y() - 2);
3132             pts[3] = QPoint(cur.x(), cur.y() + 2);
3133             if (pos.x() >= cur.x())        {
3134                 pts[1] = QPoint(geometry().right(), pos.y());
3135                 pts[2] = QPoint(geometry().right(), pos.y() + menuSize.height());
3136             } else {
3137                 pts[1] = QPoint(pos.x() + menuSize.width(), pos.y());
3138                 pts[2] = QPoint(pos.x() + menuSize.width(), pos.y() + menuSize.height());
3139             }
3140             QPolygon points(4);
3141             for(int i = 0; i < 4; i++)
3142                 points.setPoint(i, mapFromGlobal(pts[i]));
3143             d->sloppyRegion = QRegion(points);
3144         }
3145     }
3146 
3147     //do the popup
3148     d->activeMenu->popup(pos);
3149 }
3150 #endif
3151 
3152 /*!\internal
3153 */
3154 void KexiMenuWidget::setNoReplayFor(QWidget *noReplayFor)
3155 {
3156 #ifdef Q_OS_WIN
3157     d->noReplayFor = noReplayFor;
3158 #else
3159     Q_UNUSED(noReplayFor);
3160 #endif
3161 }
3162 
3163 /*!
3164   \brief whether consecutive separators should be collapsed
3165 
3166   This property specifies whether consecutive separators in the menu
3167   should be visually collapsed to a single one. Separators at the
3168   beginning or the end of the menu are also hidden.
3169 
3170   By default, this property is true.
3171 */
3172 bool KexiMenuWidget::separatorsCollapsible() const
3173 {
3174     return d->collapsibleSeparators;
3175 }
3176 
3177 void KexiMenuWidget::setSeparatorsCollapsible(bool collapse)
3178 {
3179     if (d->collapsibleSeparators == collapse)
3180         return;
3181 
3182     d->collapsibleSeparators = collapse;
3183     d->itemsDirty = 1;
3184     if (isVisible()) {
3185         d->updateActionRects();
3186         update();
3187     }
3188 }