kmenubar.cpp
00001 /* This file is part of the KDE libraries 00002 Copyright (C) 1997, 1998, 1999, 2000 Sven Radej (radej@kde.org) 00003 Copyright (C) 1997, 1998, 1999, 2000 Matthias Ettrich (ettrich@kde.org) 00004 Copyright (C) 1999, 2000 Daniel "Mosfet" Duley (mosfet@kde.org) 00005 00006 This library is free software; you can redistribute it and/or 00007 modify it under the terms of the GNU Library General Public 00008 License as published by the Free Software Foundation; either 00009 version 2 of the License, or (at your option) any later version. 00010 00011 This library is distributed in the hope that it will be useful, 00012 but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 Library General Public License for more details. 00015 00016 You should have received a copy of the GNU Library General Public License 00017 along with this library; see the file COPYING.LIB. If not, write to 00018 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00019 Boston, MA 02110-1301, USA. 00020 */ 00021 00022 00023 #ifndef INCLUDE_MENUITEM_DEF 00024 #define INCLUDE_MENUITEM_DEF 00025 #endif 00026 00027 #include "config.h" 00028 #include <qevent.h> 00029 #include <qobjectlist.h> 00030 #include <qaccel.h> 00031 #include <qpainter.h> 00032 #include <qstyle.h> 00033 #include <qtimer.h> 00034 00035 #include <kconfig.h> 00036 #include <kglobalsettings.h> 00037 #include <kmenubar.h> 00038 #include <kapplication.h> 00039 #include <kglobal.h> 00040 #include <kdebug.h> 00041 #include <kmanagerselection.h> 00042 00043 #ifdef Q_WS_X11 00044 #include <kwin.h> 00045 #include <kwinmodule.h> 00046 #include <qxembed.h> 00047 00048 #include <X11/Xlib.h> 00049 #include <X11/Xutil.h> 00050 #include <X11/Xatom.h> 00051 #endif 00052 00053 /* 00054 00055 Toplevel menubar (not for the fallback size handling done by itself): 00056 - should not alter position or set strut 00057 - every toplevel must have at most one matching topmenu 00058 - embedder won't allow shrinking below a certain size 00059 - must have WM_TRANSIENT_FOR pointing the its mainwindow 00060 - the exception is desktop's menubar, which can be transient for root window 00061 because of using root window as the desktop window 00062 - Fitts' Law 00063 00064 */ 00065 00066 class KMenuBar::KMenuBarPrivate 00067 { 00068 public: 00069 KMenuBarPrivate() 00070 : forcedTopLevel( false ), 00071 topLevel( false ), 00072 wasTopLevel( false ), 00073 #ifdef Q_WS_X11 00074 selection( NULL ), 00075 #endif 00076 min_size( 0, 0 ) 00077 { 00078 } 00079 ~KMenuBarPrivate() 00080 { 00081 #ifdef Q_WS_X11 00082 delete selection; 00083 #endif 00084 } 00085 bool forcedTopLevel; 00086 bool topLevel; 00087 bool wasTopLevel; // when TLW is fullscreen, remember state 00088 int frameStyle; // only valid in toplevel mode 00089 int lineWidth; // dtto 00090 int margin; // dtto 00091 bool fallback_mode; // dtto 00092 #ifdef Q_WS_X11 00093 KSelectionWatcher* selection; 00094 #endif 00095 QTimer selection_timer; 00096 QSize min_size; 00097 static Atom makeSelectionAtom(); 00098 }; 00099 00100 #ifdef Q_WS_X11 00101 static Atom selection_atom = None; 00102 static Atom msg_type_atom = None; 00103 00104 static 00105 void initAtoms() 00106 { 00107 char nm[ 100 ]; 00108 sprintf( nm, "_KDE_TOPMENU_OWNER_S%d", DefaultScreen( qt_xdisplay())); 00109 char nm2[] = "_KDE_TOPMENU_MINSIZE"; 00110 char* names[ 2 ] = { nm, nm2 }; 00111 Atom atoms[ 2 ]; 00112 XInternAtoms( qt_xdisplay(), names, 2, False, atoms ); 00113 selection_atom = atoms[ 0 ]; 00114 msg_type_atom = atoms[ 1 ]; 00115 } 00116 #endif 00117 00118 Atom KMenuBar::KMenuBarPrivate::makeSelectionAtom() 00119 { 00120 #ifdef Q_WS_X11 00121 if( selection_atom == None ) 00122 initAtoms(); 00123 return selection_atom; 00124 #else 00125 return 0; 00126 #endif 00127 } 00128 00129 KMenuBar::KMenuBar(QWidget *parent, const char *name) 00130 : QMenuBar(parent, name) 00131 { 00132 #ifdef Q_WS_X11 00133 QXEmbed::initialize(); 00134 #endif 00135 d = new KMenuBarPrivate; 00136 connect( &d->selection_timer, SIGNAL( timeout()), 00137 this, SLOT( selectionTimeout())); 00138 00139 connect( qApp->desktop(), SIGNAL( resized( int )), SLOT( updateFallbackSize())); 00140 00141 if ( kapp ) 00142 // toolbarAppearanceChanged(int) is sent when changing macstyle 00143 connect( kapp, SIGNAL(toolbarAppearanceChanged(int)), 00144 this, SLOT(slotReadConfig())); 00145 00146 slotReadConfig(); 00147 } 00148 00149 KMenuBar::~KMenuBar() 00150 { 00151 delete d; 00152 } 00153 00154 void KMenuBar::setTopLevelMenu(bool top_level) 00155 { 00156 d->forcedTopLevel = top_level; 00157 setTopLevelMenuInternal( top_level ); 00158 } 00159 00160 void KMenuBar::setTopLevelMenuInternal(bool top_level) 00161 { 00162 if (d->forcedTopLevel) 00163 top_level = true; 00164 00165 d->wasTopLevel = top_level; 00166 if( parentWidget() 00167 && parentWidget()->topLevelWidget()->isFullScreen()) 00168 top_level = false; 00169 00170 if ( isTopLevelMenu() == top_level ) 00171 return; 00172 d->topLevel = top_level; 00173 if ( isTopLevelMenu() ) 00174 { 00175 #ifdef Q_WS_X11 00176 d->selection = new KSelectionWatcher( KMenuBarPrivate::makeSelectionAtom(), 00177 DefaultScreen( qt_xdisplay())); 00178 connect( d->selection, SIGNAL( newOwner( Window )), 00179 this, SLOT( updateFallbackSize())); 00180 connect( d->selection, SIGNAL( lostOwner()), 00181 this, SLOT( updateFallbackSize())); 00182 #endif 00183 d->frameStyle = frameStyle(); 00184 d->lineWidth = lineWidth(); 00185 d->margin = margin(); 00186 d->fallback_mode = false; 00187 bool wasShown = !isHidden(); 00188 reparent( parentWidget(), WType_TopLevel | WStyle_Tool | WStyle_Customize | WStyle_NoBorder, QPoint(0,0), false ); 00189 #ifdef Q_WS_X11 00190 KWin::setType( winId(), NET::TopMenu ); 00191 if( parentWidget()) 00192 XSetTransientForHint( qt_xdisplay(), winId(), parentWidget()->topLevelWidget()->winId()); 00193 #endif 00194 QMenuBar::setFrameStyle( NoFrame ); 00195 QMenuBar::setLineWidth( 0 ); 00196 QMenuBar::setMargin( 0 ); 00197 updateFallbackSize(); 00198 d->min_size = QSize( 0, 0 ); 00199 if( parentWidget() && !parentWidget()->isTopLevel()) 00200 setShown( parentWidget()->isVisible()); 00201 else if ( wasShown ) 00202 show(); 00203 } else 00204 { 00205 #ifdef Q_WS_X11 00206 delete d->selection; 00207 d->selection = NULL; 00208 #endif 00209 setBackgroundMode( PaletteButton ); 00210 setFrameStyle( d->frameStyle ); 00211 setLineWidth( d->lineWidth ); 00212 setMargin( d->margin ); 00213 setMinimumSize( 0, 0 ); 00214 setMaximumSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX ); 00215 updateMenuBarSize(); 00216 if ( parentWidget() ) 00217 reparent( parentWidget(), QPoint(0,0), !isHidden()); 00218 } 00219 } 00220 00221 bool KMenuBar::isTopLevelMenu() const 00222 { 00223 return d->topLevel; 00224 } 00225 00226 // KDE4 remove 00227 void KMenuBar::show() 00228 { 00229 QMenuBar::show(); 00230 } 00231 00232 void KMenuBar::slotReadConfig() 00233 { 00234 KConfig *config = KGlobal::config(); 00235 KConfigGroupSaver saver( config, "KDE" ); 00236 setTopLevelMenuInternal( config->readBoolEntry( "macStyle", false ) ); 00237 } 00238 00239 bool KMenuBar::eventFilter(QObject *obj, QEvent *ev) 00240 { 00241 if ( d->topLevel ) 00242 { 00243 if ( parentWidget() && obj == parentWidget()->topLevelWidget() ) 00244 { 00245 if( ev->type() == QEvent::Resize ) 00246 return false; // ignore resizing of parent, QMenuBar would try to adjust size 00247 if ( ev->type() == QEvent::Accel || ev->type() == QEvent::AccelAvailable ) 00248 { 00249 if ( QApplication::sendEvent( topLevelWidget(), ev ) ) 00250 return true; 00251 } 00252 if(ev->type() == QEvent::ShowFullScreen ) 00253 // will update the state properly 00254 setTopLevelMenuInternal( d->topLevel ); 00255 } 00256 if( parentWidget() && obj == parentWidget() && ev->type() == QEvent::Reparent ) 00257 { 00258 #ifdef Q_WS_X11 00259 XSetTransientForHint( qt_xdisplay(), winId(), parentWidget()->topLevelWidget()->winId()); 00260 #else 00261 //TODO: WIN32? 00262 #endif 00263 setShown( parentWidget()->isTopLevel() || parentWidget()->isVisible()); 00264 } 00265 if( parentWidget() && !parentWidget()->isTopLevel() && obj == parentWidget()) 00266 { // if the parent is not toplevel, KMenuBar needs to match its visibility status 00267 if( ev->type() == QEvent::Show ) 00268 { 00269 #ifdef Q_WS_X11 00270 XSetTransientForHint( qt_xdisplay(), winId(), parentWidget()->topLevelWidget()->winId()); 00271 #else 00272 //TODO: WIN32? 00273 #endif 00274 show(); 00275 } 00276 if( ev->type() == QEvent::Hide ) 00277 hide(); 00278 } 00279 } 00280 else 00281 { 00282 if( parentWidget() && obj == parentWidget()->topLevelWidget()) 00283 { 00284 if( ev->type() == QEvent::WindowStateChange 00285 && !parentWidget()->topLevelWidget()->isFullScreen() ) 00286 setTopLevelMenuInternal( d->wasTopLevel ); 00287 } 00288 } 00289 return QMenuBar::eventFilter( obj, ev ); 00290 } 00291 00292 // KDE4 remove 00293 void KMenuBar::showEvent( QShowEvent *e ) 00294 { 00295 QMenuBar::showEvent(e); 00296 } 00297 00298 void KMenuBar::updateFallbackSize() 00299 { 00300 if( !d->topLevel ) 00301 return; 00302 #ifdef Q_WS_X11 00303 if( d->selection->owner() != None ) 00304 #endif 00305 { // somebody is managing us, don't mess anything, undo changes 00306 // done in fallback mode if needed 00307 d->selection_timer.stop(); 00308 if( d->fallback_mode ) 00309 { 00310 d->fallback_mode = false; 00311 // KWin::setStrut( winId(), 0, 0, 0, 0 ); KWin will set strut as it will see fit 00312 setMinimumSize( 0, 0 ); 00313 setMaximumSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX ); 00314 updateMenuBarSize(); 00315 } 00316 return; 00317 } 00318 if( d->selection_timer.isActive()) 00319 return; 00320 d->selection_timer.start( 100, true ); 00321 } 00322 00323 void KMenuBar::selectionTimeout() 00324 { // nobody is managing us, handle resizing 00325 if ( d->topLevel ) 00326 { 00327 d->fallback_mode = true; // KMenuBar is handling its position itself 00328 KConfigGroup xineramaConfig(KGlobal::config(),"Xinerama"); 00329 int screen = xineramaConfig.readNumEntry("MenubarScreen", 00330 QApplication::desktop()->screenNumber(QPoint(0,0)) ); 00331 QRect area = QApplication::desktop()->screenGeometry(screen); 00332 int margin = 0; 00333 move(area.left() - margin, area.top() - margin); 00334 setFixedSize(area.width() + 2* margin , heightForWidth( area.width() + 2 * margin ) ); 00335 #ifdef Q_WS_X11 00336 int strut_height = height() - margin; 00337 if( strut_height < 0 ) 00338 strut_height = 0; 00339 KWin::setStrut( winId(), 0, 0, strut_height, 0 ); 00340 #endif 00341 } 00342 } 00343 00344 int KMenuBar::block_resize = 0; 00345 00346 void KMenuBar::resizeEvent( QResizeEvent *e ) 00347 { 00348 if( e->spontaneous() && d->topLevel && !d->fallback_mode ) 00349 { 00350 ++block_resize; // do not respond with configure request to ConfigureNotify event 00351 QMenuBar::resizeEvent(e); // to avoid possible infinite loop 00352 --block_resize; 00353 } 00354 else 00355 QMenuBar::resizeEvent(e); 00356 } 00357 00358 void KMenuBar::setGeometry( const QRect& r ) 00359 { 00360 setGeometry( r.x(), r.y(), r.width(), r.height() ); 00361 } 00362 00363 void KMenuBar::setGeometry( int x, int y, int w, int h ) 00364 { 00365 if( block_resize > 0 ) 00366 { 00367 move( x, y ); 00368 return; 00369 } 00370 checkSize( w, h ); 00371 if( geometry() != QRect( x, y, w, h )) 00372 QMenuBar::setGeometry( x, y, w, h ); 00373 } 00374 00375 void KMenuBar::resize( int w, int h ) 00376 { 00377 if( block_resize > 0 ) 00378 return; 00379 checkSize( w, h ); 00380 if( size() != QSize( w, h )) 00381 QMenuBar::resize( w, h ); 00382 // kdDebug() << "RS:" << w << ":" << h << ":" << width() << ":" << height() << ":" << minimumWidth() << ":" << minimumHeight() << endl; 00383 } 00384 00385 void KMenuBar::checkSize( int& w, int& h ) 00386 { 00387 if( !d->topLevel || d->fallback_mode ) 00388 return; 00389 QSize s = sizeHint(); 00390 w = s.width(); 00391 h = s.height(); 00392 // This is not done as setMinimumSize(), because that would set the minimum 00393 // size in WM_NORMAL_HINTS, and KWin would not allow changing to smaller size 00394 // anymore 00395 w = KMAX( w, d->min_size.width()); 00396 h = KMAX( h, d->min_size.height()); 00397 } 00398 00399 // QMenuBar's sizeHint() gives wrong size (insufficient width), which causes wrapping in the kicker applet 00400 QSize KMenuBar::sizeHint() const 00401 { 00402 if( !d->topLevel || block_resize > 0 ) 00403 return QMenuBar::sizeHint(); 00404 // Since QMenuBar::sizeHint() may indirectly call resize(), 00405 // avoid infinite recursion. 00406 ++block_resize; 00407 // find the minimum useful height, and enlarge the width until the menu fits in that height (one row) 00408 int h = heightForWidth( 1000000 ); 00409 int w = QMenuBar::sizeHint().width(); 00410 // optimization - don't call heightForWidth() too many times 00411 while( heightForWidth( w + 12 ) > h ) 00412 w += 12; 00413 while( heightForWidth( w + 4 ) > h ) 00414 w += 4; 00415 while( heightForWidth( w ) > h ) 00416 ++w; 00417 --block_resize; 00418 return QSize( w, h ); 00419 } 00420 00421 #ifdef Q_WS_X11 00422 bool KMenuBar::x11Event( XEvent* ev ) 00423 { 00424 if( ev->type == ClientMessage && ev->xclient.message_type == msg_type_atom 00425 && ev->xclient.window == winId()) 00426 { 00427 // QMenuBar is trying really hard to keep the size it deems right. 00428 // Forcing minimum size and blocking resizing to match parent size 00429 // in checkResizingToParent() seem to be the only way to make 00430 // KMenuBar keep the size it wants 00431 d->min_size = QSize( ev->xclient.data.l[ 1 ], ev->xclient.data.l[ 2 ] ); 00432 // kdDebug() << "MINSIZE:" << d->min_size << endl; 00433 updateMenuBarSize(); 00434 return true; 00435 } 00436 return QMenuBar::x11Event( ev ); 00437 } 00438 #endif 00439 00440 void KMenuBar::updateMenuBarSize() 00441 { 00442 menuContentsChanged(); // trigger invalidating calculated size 00443 resize( sizeHint()); // and resize to preferred size 00444 } 00445 00446 void KMenuBar::setFrameStyle( int style ) 00447 { 00448 if( d->topLevel ) 00449 d->frameStyle = style; 00450 else 00451 QMenuBar::setFrameStyle( style ); 00452 } 00453 00454 void KMenuBar::setLineWidth( int width ) 00455 { 00456 if( d->topLevel ) 00457 d->lineWidth = width; 00458 else 00459 QMenuBar::setLineWidth( width ); 00460 } 00461 00462 void KMenuBar::setMargin( int margin ) 00463 { 00464 if( d->topLevel ) 00465 d->margin = margin; 00466 else 00467 QMenuBar::setMargin( margin ); 00468 } 00469 00470 void KMenuBar::closeEvent( QCloseEvent* e ) 00471 { 00472 if( d->topLevel ) 00473 e->ignore(); // mainly for the fallback mode 00474 else 00475 QMenuBar::closeEvent( e ); 00476 } 00477 00478 void KMenuBar::drawContents( QPainter* p ) 00479 { 00480 // Closes the BR77113 00481 // We need to overload this method to paint only the menu items 00482 // This way when the KMenuBar is embedded in the menu applet it 00483 // integrates correctly. 00484 // 00485 // Background mode and origin are set so late because of styles 00486 // using the polish() method to modify these settings. 00487 // 00488 // Of course this hack can safely be removed when real transparency 00489 // will be available 00490 00491 if( !d->topLevel ) 00492 { 00493 QMenuBar::drawContents(p); 00494 } 00495 else 00496 { 00497 bool up_enabled = isUpdatesEnabled(); 00498 BackgroundMode bg_mode = backgroundMode(); 00499 BackgroundOrigin bg_origin = backgroundOrigin(); 00500 00501 setUpdatesEnabled(false); 00502 setBackgroundMode(X11ParentRelative); 00503 setBackgroundOrigin(WindowOrigin); 00504 00505 p->eraseRect( rect() ); 00506 erase(); 00507 00508 QColorGroup g = colorGroup(); 00509 bool e; 00510 00511 for ( int i=0; i<(int)count(); i++ ) 00512 { 00513 QMenuItem *mi = findItem( idAt( i ) ); 00514 00515 if ( !mi->text().isNull() || mi->pixmap() ) 00516 { 00517 QRect r = itemRect(i); 00518 if(r.isEmpty() || !mi->isVisible()) 00519 continue; 00520 00521 e = mi->isEnabledAndVisible(); 00522 if ( e ) 00523 g = isEnabled() ? ( isActiveWindow() ? palette().active() : 00524 palette().inactive() ) : palette().disabled(); 00525 else 00526 g = palette().disabled(); 00527 00528 bool item_active = ( actItem == i ); 00529 00530 p->setClipRect(r); 00531 00532 if( item_active ) 00533 { 00534 QStyle::SFlags flags = QStyle::Style_Default; 00535 if (isEnabled() && e) 00536 flags |= QStyle::Style_Enabled; 00537 if ( item_active ) 00538 flags |= QStyle::Style_Active; 00539 if ( item_active && actItemDown ) 00540 flags |= QStyle::Style_Down; 00541 flags |= QStyle::Style_HasFocus; 00542 00543 style().drawControl(QStyle::CE_MenuBarItem, p, this, 00544 r, g, flags, QStyleOption(mi)); 00545 } 00546 else 00547 { 00548 style().drawItem(p, r, AlignCenter | AlignVCenter | ShowPrefix, 00549 g, e, mi->pixmap(), mi->text()); 00550 } 00551 } 00552 } 00553 00554 setBackgroundOrigin(bg_origin); 00555 setBackgroundMode(bg_mode); 00556 setUpdatesEnabled(up_enabled); 00557 } 00558 } 00559 00560 void KMenuBar::virtual_hook( int, void* ) 00561 { /*BASE::virtual_hook( id, data );*/ } 00562 00563 #include "kmenubar.moc"