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"