File indexing completed on 2024-04-14 14:20:22

0001 /* This file is part of the KDE libraries
0002     Copyright (C) 1997, 1998, 1999, 2000  Sven Radej (radej@kde.org)
0003     Copyright (C) 1997, 1998, 1999, 2000 Matthias Ettrich (ettrich@kde.org)
0004     Copyright (C) 1999, 2000 Daniel "Mosfet" Duley (mosfet@kde.org)
0005 
0006     This library is free software; you can redistribute it and/or
0007     modify it under the terms of the GNU Library General Public
0008     License as published by the Free Software Foundation; either
0009     version 2 of the License, or (at your option) any later version.
0010 
0011     This library is distributed in the hope that it will be useful,
0012     but WITHOUT ANY WARRANTY; without even the implied warranty of
0013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014     Library General Public License for more details.
0015 
0016     You should have received a copy of the GNU Library General Public License
0017     along with this library; see the file COPYING.LIB.  If not, write to
0018     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0019     Boston, MA 02110-1301, USA.
0020     */
0021 
0022 #include "kmenubar.h"
0023 
0024 #include <config-kdelibs4support.h>
0025 
0026 #include <stdio.h>
0027 
0028 #include <QObject>
0029 #include <QTimer>
0030 #include <QActionEvent>
0031 #include <QDesktopWidget>
0032 #include <QPainter>
0033 #include <QStyle>
0034 #include <QStyleOptionMenuItem>
0035 
0036 #include <kconfig.h>
0037 #include <kglobalsettings.h>
0038 #include <qapplication.h>
0039 #include <kdebug.h>
0040 #include <kconfiggroup.h>
0041 #include <kwindowsystem.h>
0042 
0043 #if HAVE_X11
0044 #include <kmanagerselection.h>
0045 #include <qx11info_x11.h>
0046 
0047 #include <X11/Xatom.h>
0048 #include <X11/Xlib.h>
0049 #include <X11/Xutil.h>
0050 #include <fixx11h.h>
0051 #endif
0052 
0053 /*
0054 
0055  Toplevel menubar (not for the fallback size handling done by itself):
0056  - should not alter position or set strut
0057  - every toplevel must have at most one matching topmenu
0058  - embedder won't allow shrinking below a certain size
0059  - must have WM_TRANSIENT_FOR pointing the its mainwindow
0060      - the exception is desktop's menubar, which can be transient for root window
0061        because of using root window as the desktop window
0062  - Fitts' Law
0063 
0064 */
0065 
0066 static int block_resize = 0;
0067 
0068 class Q_DECL_HIDDEN KMenuBar::KMenuBarPrivate
0069 {
0070 public:
0071     KMenuBarPrivate()
0072         :   forcedTopLevel(false),
0073             topLevel(false),
0074             wasTopLevel(false),
0075 #if HAVE_X11
0076             selection(nullptr),
0077             isX11(QX11Info::isPlatformX11()),
0078 #endif
0079             min_size(0, 0)
0080     {
0081     }
0082     ~KMenuBarPrivate()
0083     {
0084 #if HAVE_X11
0085         delete selection;
0086 #endif
0087     }
0088     int frameStyle; // only valid in toplevel mode
0089     int lineWidth;  // dtto
0090     int margin;     // dtto
0091     bool fallback_mode : 1; // dtto
0092 
0093     bool forcedTopLevel : 1;
0094     bool topLevel : 1;
0095     bool wasTopLevel : 1; // when TLW is fullscreen, remember state
0096 
0097 #if HAVE_X11
0098     KSelectionWatcher *selection;
0099     bool isX11;
0100 #endif
0101     QTimer selection_timer;
0102     QSize min_size;
0103 #if HAVE_X11
0104     static Atom makeSelectionAtom();
0105 #endif
0106 };
0107 
0108 #if HAVE_X11
0109 static Atom selection_atom = None;
0110 static Atom msg_type_atom = None;
0111 
0112 static
0113 void initAtoms()
0114 {
0115     char nm[ 100 ];
0116     sprintf(nm, "_KDE_TOPMENU_OWNER_S%d", DefaultScreen(QX11Info::display()));
0117     char nm2[] = "_KDE_TOPMENU_MINSIZE";
0118     char *names[ 2 ] = { nm, nm2 };
0119     Atom atoms[ 2 ];
0120     XInternAtoms(QX11Info::display(), names, 2, False, atoms);
0121     selection_atom = atoms[ 0 ];
0122     msg_type_atom = atoms[ 1 ];
0123 }
0124 
0125 Atom KMenuBar::KMenuBarPrivate::makeSelectionAtom()
0126 {
0127     if (!QX11Info::isPlatformX11()) {
0128         return 0;
0129     }
0130     if (selection_atom == None) {
0131         initAtoms();
0132     }
0133     return selection_atom;
0134 }
0135 #endif
0136 
0137 KMenuBar::KMenuBar(QWidget *parent)
0138     : QMenuBar(parent), d(new KMenuBarPrivate)
0139 {
0140     connect(&d->selection_timer, SIGNAL(timeout()),
0141             this, SLOT(selectionTimeout()));
0142 
0143     connect(qApp->desktop(), SIGNAL(resized(int)), SLOT(updateFallbackSize()));
0144 
0145     // toolbarAppearanceChanged(int) is sent when changing macstyle
0146     connect(KGlobalSettings::self(), SIGNAL(toolbarAppearanceChanged(int)),
0147             this, SLOT(slotReadConfig()));
0148 
0149     slotReadConfig();
0150 }
0151 
0152 KMenuBar::~KMenuBar()
0153 {
0154     delete d;
0155 }
0156 
0157 void KMenuBar::setTopLevelMenu(bool top_level)
0158 {
0159     d->forcedTopLevel = top_level;
0160     setTopLevelMenuInternal(top_level);
0161 }
0162 
0163 void KMenuBar::setTopLevelMenuInternal(bool top_level)
0164 {
0165     if (d->forcedTopLevel) {
0166         top_level = true;
0167     }
0168 
0169     d->wasTopLevel = top_level;
0170     if (parentWidget()
0171             && parentWidget()->topLevelWidget()->isFullScreen()) {
0172         top_level = false;
0173     }
0174 
0175     if (isTopLevelMenu() == top_level) {
0176         return;
0177     }
0178     d->topLevel = top_level;
0179     if (isTopLevelMenu()) {
0180 #if HAVE_X11
0181         if (d->isX11) {
0182             d->selection = new KSelectionWatcher(KMenuBarPrivate::makeSelectionAtom(),
0183                                                 DefaultScreen(QX11Info::display()));
0184             connect(d->selection, SIGNAL(newOwner(Window)),
0185                     this, SLOT(updateFallbackSize()));
0186             connect(d->selection, SIGNAL(lostOwner()),
0187                     this, SLOT(updateFallbackSize()));
0188         }
0189 #endif
0190         d->frameStyle = 0; //frameStyle();
0191         d->lineWidth = 0; //lineWidth();
0192         d->margin = 0; //margin();
0193         d->fallback_mode = false;
0194         bool wasShown = !isHidden();
0195         setParent(parentWidget(), Qt::Window | Qt::Tool | Qt::FramelessWindowHint);
0196         setGeometry(0, 0, width(), height());
0197 #if HAVE_X11
0198         KWindowSystem::setType(winId(), NET::TopMenu);
0199 #endif
0200 
0201         if (parentWidget()) {
0202             setAttribute(Qt::WA_NativeWindow, true);
0203             KWindowSystem::setMainWindow(windowHandle(), parentWidget()->topLevelWidget()->winId());
0204         }
0205         //QMenuBar::setFrameStyle( NoFrame );
0206         //QMenuBar::setLineWidth( 0 );
0207         //QMenuBar::setMargin( 0 );
0208         updateFallbackSize();
0209         d->min_size = QSize(0, 0);
0210         if (parentWidget() && !parentWidget()->isTopLevel()) {
0211             setVisible(parentWidget()->isVisible());
0212         } else if (wasShown) {
0213             show();
0214         }
0215     } else {
0216 #if HAVE_X11
0217         delete d->selection;
0218         d->selection = nullptr;
0219 #endif
0220         setAttribute(Qt::WA_NoSystemBackground, false);
0221         setBackgroundRole(QPalette::Button);
0222         setFrameStyle(d->frameStyle);
0223         setLineWidth(d->lineWidth);
0224         setMargin(d->margin);
0225         setMinimumSize(0, 0);
0226         setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
0227         updateMenuBarSize();
0228         if (parentWidget()) {
0229             setParent(parentWidget());
0230         }
0231     }
0232 }
0233 
0234 bool KMenuBar::isTopLevelMenu() const
0235 {
0236     return d->topLevel;
0237 }
0238 
0239 void KMenuBar::slotReadConfig()
0240 {
0241     KConfigGroup cg(KSharedConfig::openConfig(), "KDE");
0242     setTopLevelMenuInternal(cg.readEntry("macStyle", false));
0243 }
0244 
0245 bool KMenuBar::eventFilter(QObject *obj, QEvent *ev)
0246 {
0247     if (d->topLevel) {
0248         if (parentWidget() && obj == parentWidget()->topLevelWidget()) {
0249             if (ev->type() == QEvent::Resize) {
0250                 return false;    // ignore resizing of parent, QMenuBar would try to adjust size
0251             }
0252 #ifdef QT3_SUPPORT
0253             if (ev->type() == QEvent::Accel || ev->type() == QEvent::AccelAvailable) {
0254                 if (QApplication::sendEvent(topLevelWidget(), ev)) {
0255                     return true;
0256                 }
0257             }
0258 #endif
0259             /* FIXME QEvent::ShowFullScreen is no more
0260             if(ev->type() == QEvent::ShowFullScreen )
0261                 // will update the state properly
0262                 setTopLevelMenuInternal( d->topLevel );
0263             */
0264         }
0265         if (parentWidget() && obj == parentWidget() && ev->type() == QEvent::ParentChange) {
0266             setAttribute(Qt::WA_NativeWindow, true);
0267             KWindowSystem::setMainWindow(windowHandle(), parentWidget()->topLevelWidget()->winId());
0268             setVisible(parentWidget()->isTopLevel() || parentWidget()->isVisible());
0269         }
0270         if (parentWidget() && !parentWidget()->isTopLevel() && obj == parentWidget()) {
0271             // if the parent is not toplevel, KMenuBar needs to match its visibility status
0272             if (ev->type() == QEvent::Show) {
0273                 setAttribute(Qt::WA_NativeWindow, true);
0274                 KWindowSystem::setMainWindow(windowHandle(), parentWidget()->topLevelWidget()->winId());
0275                 show();
0276             }
0277             if (ev->type() == QEvent::Hide) {
0278                 hide();
0279             }
0280         }
0281     } else {
0282         if (parentWidget() && obj == parentWidget()->topLevelWidget()) {
0283             if (ev->type() == QEvent::WindowStateChange
0284                     && !parentWidget()->topLevelWidget()->isFullScreen()) {
0285                 setTopLevelMenuInternal(d->wasTopLevel);
0286             }
0287         }
0288     }
0289     return QMenuBar::eventFilter(obj, ev);
0290 }
0291 
0292 void KMenuBar::updateFallbackSize()
0293 {
0294     if (!d->topLevel) {
0295         return;
0296     }
0297 #if HAVE_X11
0298     if (d->selection && d->selection->owner() != None)
0299 #endif
0300     {
0301         // somebody is managing us, don't mess anything, undo changes
0302         // done in fallback mode if needed
0303         d->selection_timer.stop();
0304         if (d->fallback_mode) {
0305             d->fallback_mode = false;
0306             KWindowSystem::setStrut(winId(), 0, 0, 0, 0);
0307             setMinimumSize(0, 0);
0308             setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
0309             updateMenuBarSize();
0310         }
0311         return;
0312     }
0313     if (d->selection_timer.isActive()) {
0314         return;
0315     }
0316     d->selection_timer.setInterval(100);
0317     d->selection_timer.setSingleShot(true);
0318     d->selection_timer.start();
0319 }
0320 
0321 void KMenuBar::selectionTimeout()
0322 {
0323     // nobody is managing us, handle resizing
0324     if (d->topLevel) {
0325         d->fallback_mode = true; // KMenuBar is handling its position itself
0326         KConfigGroup xineramaConfig(KSharedConfig::openConfig(), "Xinerama");
0327         int screen = xineramaConfig.readEntry("MenubarScreen",
0328                                               QApplication::desktop()->screenNumber(QPoint(0, 0)));
0329         QRect area = QApplication::desktop()->screenGeometry(screen);
0330         int margin = 0;
0331         move(area.left() - margin, area.top() - margin);
0332         setFixedSize(area.width() + 2 * margin, heightForWidth(area.width() + 2 * margin));
0333 #if HAVE_X11
0334         int strut_height = height() - margin;
0335         if (strut_height < 0) {
0336             strut_height = 0;
0337         }
0338         KWindowSystem::setStrut(winId(), 0, 0, strut_height, 0);
0339 #endif
0340     }
0341 }
0342 
0343 void KMenuBar::resizeEvent(QResizeEvent *e)
0344 {
0345     if (e->spontaneous() && d->topLevel && !d->fallback_mode) {
0346         ++block_resize; // do not respond with configure request to ConfigureNotify event
0347         QMenuBar::resizeEvent(e); // to avoid possible infinite loop
0348         --block_resize;
0349     } else {
0350         QMenuBar::resizeEvent(e);
0351     }
0352 }
0353 
0354 void KMenuBar::setGeometry(const QRect &r)
0355 {
0356     setGeometry(r.x(), r.y(), r.width(), r.height());
0357 }
0358 
0359 void KMenuBar::setGeometry(int x, int y, int w, int h)
0360 {
0361     if (block_resize > 0) {
0362         move(x, y);
0363         return;
0364     }
0365     checkSize(w, h);
0366     if (geometry() != QRect(x, y, w, h)) {
0367         QMenuBar::setGeometry(x, y, w, h);
0368     }
0369 }
0370 
0371 void KMenuBar::resize(int w, int h)
0372 {
0373     if (block_resize > 0) {
0374         return;
0375     }
0376     checkSize(w, h);
0377     if (size() != QSize(w, h)) {
0378         QMenuBar::resize(w, h);
0379     }
0380 //    kDebug() << "RS:" << w << ":" << h << ":" << width() << ":" << height() << ":" << minimumWidth() << ":" << minimumHeight();
0381 }
0382 
0383 void KMenuBar::resize(const QSize &s)
0384 {
0385     QMenuBar::resize(s);
0386 }
0387 
0388 void KMenuBar::checkSize(int &w, int &h)
0389 {
0390     if (!d->topLevel || d->fallback_mode) {
0391         return;
0392     }
0393     QSize s = sizeHint();
0394     w = s.width();
0395     h = s.height();
0396     // This is not done as setMinimumSize(), because that would set the minimum
0397     // size in WM_NORMAL_HINTS, and KWin would not allow changing to smaller size
0398     // anymore
0399     w = qMax(w, d->min_size.width());
0400     h = qMax(h, d->min_size.height());
0401 }
0402 
0403 // QMenuBar's sizeHint() gives wrong size (insufficient width), which causes wrapping in the kicker applet
0404 QSize KMenuBar::sizeHint() const
0405 {
0406     if (!d->topLevel || block_resize > 0) {
0407         return QMenuBar::sizeHint();
0408     }
0409     // Since QMenuBar::sizeHint() may indirectly call resize(),
0410     // avoid infinite recursion.
0411     ++block_resize;
0412     // find the minimum useful height, and enlarge the width until the menu fits in that height (one row)
0413     int h = heightForWidth(1000000);
0414     int w = QMenuBar::sizeHint().width();
0415     // optimization - don't call heightForWidth() too many times
0416     while (heightForWidth(w + 12) > h) {
0417         w += 12;
0418     }
0419     while (heightForWidth(w + 4) > h) {
0420         w += 4;
0421     }
0422     while (heightForWidth(w) > h) {
0423         ++w;
0424     }
0425     --block_resize;
0426     return QSize(w, h);
0427 }
0428 
0429 #pragma message("Port to Qt5 native filter")
0430 #if 0
0431 bool KMenuBar::x11Event(XEvent *ev)
0432 {
0433     if (ev->type == ClientMessage && ev->xclient.message_type == msg_type_atom
0434             && ev->xclient.window == winId()) {
0435         // QMenuBar is trying really hard to keep the size it deems right.
0436         // Forcing minimum size and blocking resizing to match parent size
0437         // in checkResizingToParent() seem to be the only way to make
0438         // KMenuBar keep the size it wants
0439         d->min_size = QSize(ev->xclient.data.l[ 1 ], ev->xclient.data.l[ 2 ]);
0440 //        kDebug() << "MINSIZE:" << d->min_size;
0441         updateMenuBarSize();
0442         return true;
0443     }
0444     return QMenuBar::x11Event(ev);
0445 }
0446 #endif
0447 
0448 void KMenuBar::updateMenuBarSize()
0449 {
0450     //menuContentsChanged(); // trigger invalidating calculated size
0451     resize(sizeHint());    // and resize to preferred size
0452 }
0453 
0454 void KMenuBar::setFrameStyle(int style)
0455 {
0456     if (d->topLevel) {
0457         d->frameStyle = style;
0458     }
0459 //     else
0460 //  QMenuBar::setFrameStyle( style );
0461 }
0462 
0463 void KMenuBar::setLineWidth(int width)
0464 {
0465     if (d->topLevel) {
0466         d->lineWidth = width;
0467     }
0468 //     else
0469 //  QMenuBar::setLineWidth( width );
0470 }
0471 
0472 void KMenuBar::setMargin(int margin)
0473 {
0474     if (d->topLevel) {
0475         d->margin = margin;
0476     }
0477 //     else
0478 //  QMenuBar::setMargin( margin );
0479 }
0480 
0481 void KMenuBar::closeEvent(QCloseEvent *e)
0482 {
0483     if (d->topLevel) {
0484         e->ignore();    // mainly for the fallback mode
0485     } else {
0486         QMenuBar::closeEvent(e);
0487     }
0488 }
0489 
0490 void KMenuBar::paintEvent(QPaintEvent *pe)
0491 {
0492     // Closes the BR77113
0493     // We need to overload this method to paint only the menu items
0494     // This way when the KMenuBar is embedded in the menu applet it
0495     // integrates correctly.
0496     //
0497     // Background mode and origin are set so late because of styles
0498     // using the polish() method to modify these settings.
0499     //
0500     // Of course this hack can safely be removed when real transparency
0501     // will be available
0502 
0503 //    if( !d->topLevel )
0504     {
0505         QMenuBar::paintEvent(pe);
0506     }
0507 #if 0
0508     else {
0509         QPainter p(this);
0510         bool up_enabled = isUpdatesEnabled();
0511         Qt::BackgroundMode bg_mode = backgroundMode();
0512         BackgroundOrigin bg_origin = backgroundOrigin();
0513 
0514         setUpdatesEnabled(false);
0515         setBackgroundMode(Qt::X11ParentRelative);
0516         setBackgroundOrigin(WindowOrigin);
0517 
0518         p.eraseRect(rect());
0519         erase();
0520 
0521         QColorGroup g = colorGroup();
0522         bool e;
0523 
0524         for (int i = 0; i < (int)count(); i++) {
0525             QMenuItem *mi = findItem(idAt(i));
0526 
0527             if (!mi->text().isEmpty() || !mi->icon().isNull()) {
0528                 QRect r = itemRect(i);
0529                 if (r.isEmpty() || !mi->isVisible()) {
0530                     continue;
0531                 }
0532 
0533                 e = mi->isEnabled() && mi->isVisible();
0534                 if (e)
0535                     g = isEnabled() ? (isActiveWindow() ? palette().active() :
0536                                        palette().inactive()) : palette().disabled();
0537                 else {
0538                     g = palette().disabled();
0539                 }
0540 
0541                 bool item_active = (activeAction() ==  mi);
0542 
0543                 p.setClipRect(r);
0544 
0545                 if (item_active) {
0546                     QStyleOptionMenuItem miOpt;
0547                     miOpt.init(this);
0548                     miOpt.rect = r;
0549                     miOpt.text = mi->text();
0550                     miOpt.icon = mi->icon();
0551                     miOpt.palette = g;
0552 
0553                     QStyle::State flags = QStyle::State_None;
0554                     if (isEnabled() && e) {
0555                         flags |= QStyle::State_Enabled;
0556                     }
0557                     if (item_active) {
0558                         flags |= QStyle::State_Active;
0559                     }
0560                     if (item_active && actItemDown) {
0561                         flags |= QStyle::State_Down;
0562                     }
0563                     flags |= QStyle::State_HasFocus;
0564 
0565                     mi->state = flags;
0566 
0567                     style()->drawControl(QStyle::CE_MenuBarItem, &miOpt, &p, this);
0568                 } else {
0569                     style()->drawItem(p, r, Qt::AlignCenter | Qt::AlignVCenter | Qt::TextShowMnemonic,
0570                                       g, e, mi->pixmap(), mi->text());
0571                 }
0572             }
0573         }
0574 
0575         setBackgroundOrigin(bg_origin);
0576         setBackgroundMode(bg_mode);
0577         setUpdatesEnabled(up_enabled);
0578     }
0579 #endif
0580 }
0581 
0582 #include "moc_kmenubar.cpp"