1 /* This file is part of the KDE libraries
2 Copyright (C) 1997, 1998, 1999, 2000 Sven Radej (radej@kde.org)
3 Copyright (C) 1997, 1998, 1999, 2000 Matthias Ettrich (ettrich@kde.org)
4 Copyright (C) 1999, 2000 Daniel "Mosfet" Duley (mosfet@kde.org)
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20 */
21
22 #include "kmenubar.h"
23
24 #include <config-kdelibs4support.h>
25
26 #include <stdio.h>
27
28 #include <QObject>
29 #include <QTimer>
30 #include <QActionEvent>
31 #include <QDesktopWidget>
32 #include <QPainter>
33 #include <QStyle>
34 #include <QStyleOptionMenuItem>
35
36 #include <kconfig.h>
37 #include <kglobalsettings.h>
38 #include <qapplication.h>
39 #include <kdebug.h>
40 #include <kconfiggroup.h>
41 #include <kwindowsystem.h>
42
43 #if HAVE_X11
44 #include <kmanagerselection.h>
45 #include <qx11info_x11.h>
46
47 #include <X11/Xlib.h>
48 #include <X11/Xutil.h>
49 #include <X11/Xatom.h>
50 #endif
51
52 /*
53
54 Toplevel menubar (not for the fallback size handling done by itself):
55 - should not alter position or set strut
56 - every toplevel must have at most one matching topmenu
57 - embedder won't allow shrinking below a certain size
58 - must have WM_TRANSIENT_FOR pointing the its mainwindow
59 - the exception is desktop's menubar, which can be transient for root window
60 because of using root window as the desktop window
61 - Fitts' Law
62
63 */
64
65 static int block_resize = 0;
66
67 class Q_DECL_HIDDEN KMenuBar::KMenuBarPrivate
68 {
69 public:
KMenuBarPrivate()70 KMenuBarPrivate()
71 : forcedTopLevel(false),
72 topLevel(false),
73 wasTopLevel(false),
74 #if HAVE_X11
75 selection(nullptr),
76 isX11(QX11Info::isPlatformX11()),
77 #endif
78 min_size(0, 0)
79 {
80 }
~KMenuBarPrivate()81 ~KMenuBarPrivate()
82 {
83 #if HAVE_X11
84 delete selection;
85 #endif
86 }
87 int frameStyle; // only valid in toplevel mode
88 int lineWidth; // dtto
89 int margin; // dtto
90 bool fallback_mode : 1; // dtto
91
92 bool forcedTopLevel : 1;
93 bool topLevel : 1;
94 bool wasTopLevel : 1; // when TLW is fullscreen, remember state
95
96 #if HAVE_X11
97 KSelectionWatcher *selection;
98 bool isX11;
99 #endif
100 QTimer selection_timer;
101 QSize min_size;
102 #if HAVE_X11
103 static Atom makeSelectionAtom();
104 #endif
105 };
106
107 #if HAVE_X11
108 static Atom selection_atom = None;
109 static Atom msg_type_atom = None;
110
111 static
initAtoms()112 void initAtoms()
113 {
114 char nm[ 100 ];
115 sprintf(nm, "_KDE_TOPMENU_OWNER_S%d", DefaultScreen(QX11Info::display()));
116 char nm2[] = "_KDE_TOPMENU_MINSIZE";
117 char *names[ 2 ] = { nm, nm2 };
118 Atom atoms[ 2 ];
119 XInternAtoms(QX11Info::display(), names, 2, False, atoms);
120 selection_atom = atoms[ 0 ];
121 msg_type_atom = atoms[ 1 ];
122 }
123
makeSelectionAtom()124 Atom KMenuBar::KMenuBarPrivate::makeSelectionAtom()
125 {
126 if (!QX11Info::isPlatformX11()) {
127 return 0;
128 }
129 if (selection_atom == None) {
130 initAtoms();
131 }
132 return selection_atom;
133 }
134 #endif
135
KMenuBar(QWidget * parent)136 KMenuBar::KMenuBar(QWidget *parent)
137 : QMenuBar(parent), d(new KMenuBarPrivate)
138 {
139 connect(&d->selection_timer, SIGNAL(timeout()),
140 this, SLOT(selectionTimeout()));
141
142 connect(qApp->desktop(), SIGNAL(resized(int)), SLOT(updateFallbackSize()));
143
144 // toolbarAppearanceChanged(int) is sent when changing macstyle
145 connect(KGlobalSettings::self(), SIGNAL(toolbarAppearanceChanged(int)),
146 this, SLOT(slotReadConfig()));
147
148 slotReadConfig();
149 }
150
~KMenuBar()151 KMenuBar::~KMenuBar()
152 {
153 delete d;
154 }
155
setTopLevelMenu(bool top_level)156 void KMenuBar::setTopLevelMenu(bool top_level)
157 {
158 d->forcedTopLevel = top_level;
159 setTopLevelMenuInternal(top_level);
160 }
161
setTopLevelMenuInternal(bool top_level)162 void KMenuBar::setTopLevelMenuInternal(bool top_level)
163 {
164 if (d->forcedTopLevel) {
165 top_level = true;
166 }
167
168 d->wasTopLevel = top_level;
169 if (parentWidget()
170 && parentWidget()->topLevelWidget()->isFullScreen()) {
171 top_level = false;
172 }
173
174 if (isTopLevelMenu() == top_level) {
175 return;
176 }
177 d->topLevel = top_level;
178 if (isTopLevelMenu()) {
179 #if HAVE_X11
180 if (d->isX11) {
181 d->selection = new KSelectionWatcher(KMenuBarPrivate::makeSelectionAtom(),
182 DefaultScreen(QX11Info::display()));
183 connect(d->selection, SIGNAL(newOwner(Window)),
184 this, SLOT(updateFallbackSize()));
185 connect(d->selection, SIGNAL(lostOwner()),
186 this, SLOT(updateFallbackSize()));
187 }
188 #endif
189 d->frameStyle = 0; //frameStyle();
190 d->lineWidth = 0; //lineWidth();
191 d->margin = 0; //margin();
192 d->fallback_mode = false;
193 bool wasShown = !isHidden();
194 setParent(parentWidget(), Qt::Window | Qt::Tool | Qt::FramelessWindowHint);
195 setGeometry(0, 0, width(), height());
196 #if HAVE_X11
197 KWindowSystem::setType(winId(), NET::TopMenu);
198 #endif
199
200 if (parentWidget()) {
201 setAttribute(Qt::WA_NativeWindow, true);
202 KWindowSystem::setMainWindow(windowHandle(), parentWidget()->topLevelWidget()->winId());
203 }
204 //QMenuBar::setFrameStyle( NoFrame );
205 //QMenuBar::setLineWidth( 0 );
206 //QMenuBar::setMargin( 0 );
207 updateFallbackSize();
208 d->min_size = QSize(0, 0);
209 if (parentWidget() && !parentWidget()->isTopLevel()) {
210 setVisible(parentWidget()->isVisible());
211 } else if (wasShown) {
212 show();
213 }
214 } else {
215 #if HAVE_X11
216 delete d->selection;
217 d->selection = nullptr;
218 #endif
219 setAttribute(Qt::WA_NoSystemBackground, false);
220 setBackgroundRole(QPalette::Button);
221 setFrameStyle(d->frameStyle);
222 setLineWidth(d->lineWidth);
223 setMargin(d->margin);
224 setMinimumSize(0, 0);
225 setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
226 updateMenuBarSize();
227 if (parentWidget()) {
228 setParent(parentWidget());
229 }
230 }
231 }
232
isTopLevelMenu() const233 bool KMenuBar::isTopLevelMenu() const
234 {
235 return d->topLevel;
236 }
237
slotReadConfig()238 void KMenuBar::slotReadConfig()
239 {
240 KConfigGroup cg(KSharedConfig::openConfig(), "KDE");
241 setTopLevelMenuInternal(cg.readEntry("macStyle", false));
242 }
243
eventFilter(QObject * obj,QEvent * ev)244 bool KMenuBar::eventFilter(QObject *obj, QEvent *ev)
245 {
246 if (d->topLevel) {
247 if (parentWidget() && obj == parentWidget()->topLevelWidget()) {
248 if (ev->type() == QEvent::Resize) {
249 return false; // ignore resizing of parent, QMenuBar would try to adjust size
250 }
251 #ifdef QT3_SUPPORT
252 if (ev->type() == QEvent::Accel || ev->type() == QEvent::AccelAvailable) {
253 if (QApplication::sendEvent(topLevelWidget(), ev)) {
254 return true;
255 }
256 }
257 #endif
258 /* FIXME QEvent::ShowFullScreen is no more
259 if(ev->type() == QEvent::ShowFullScreen )
260 // will update the state properly
261 setTopLevelMenuInternal( d->topLevel );
262 */
263 }
264 if (parentWidget() && obj == parentWidget() && ev->type() == QEvent::ParentChange) {
265 setAttribute(Qt::WA_NativeWindow, true);
266 KWindowSystem::setMainWindow(windowHandle(), parentWidget()->topLevelWidget()->winId());
267 setVisible(parentWidget()->isTopLevel() || parentWidget()->isVisible());
268 }
269 if (parentWidget() && !parentWidget()->isTopLevel() && obj == parentWidget()) {
270 // if the parent is not toplevel, KMenuBar needs to match its visibility status
271 if (ev->type() == QEvent::Show) {
272 setAttribute(Qt::WA_NativeWindow, true);
273 KWindowSystem::setMainWindow(windowHandle(), parentWidget()->topLevelWidget()->winId());
274 show();
275 }
276 if (ev->type() == QEvent::Hide) {
277 hide();
278 }
279 }
280 } else {
281 if (parentWidget() && obj == parentWidget()->topLevelWidget()) {
282 if (ev->type() == QEvent::WindowStateChange
283 && !parentWidget()->topLevelWidget()->isFullScreen()) {
284 setTopLevelMenuInternal(d->wasTopLevel);
285 }
286 }
287 }
288 return QMenuBar::eventFilter(obj, ev);
289 }
290
updateFallbackSize()291 void KMenuBar::updateFallbackSize()
292 {
293 if (!d->topLevel) {
294 return;
295 }
296 #if HAVE_X11
297 if (d->selection && d->selection->owner() != None)
298 #endif
299 {
300 // somebody is managing us, don't mess anything, undo changes
301 // done in fallback mode if needed
302 d->selection_timer.stop();
303 if (d->fallback_mode) {
304 d->fallback_mode = false;
305 KWindowSystem::setStrut(winId(), 0, 0, 0, 0);
306 setMinimumSize(0, 0);
307 setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
308 updateMenuBarSize();
309 }
310 return;
311 }
312 if (d->selection_timer.isActive()) {
313 return;
314 }
315 d->selection_timer.setInterval(100);
316 d->selection_timer.setSingleShot(true);
317 d->selection_timer.start();
318 }
319
selectionTimeout()320 void KMenuBar::selectionTimeout()
321 {
322 // nobody is managing us, handle resizing
323 if (d->topLevel) {
324 d->fallback_mode = true; // KMenuBar is handling its position itself
325 KConfigGroup xineramaConfig(KSharedConfig::openConfig(), "Xinerama");
326 int screen = xineramaConfig.readEntry("MenubarScreen",
327 QApplication::desktop()->screenNumber(QPoint(0, 0)));
328 QRect area = QApplication::desktop()->screenGeometry(screen);
329 int margin = 0;
330 move(area.left() - margin, area.top() - margin);
331 setFixedSize(area.width() + 2 * margin, heightForWidth(area.width() + 2 * margin));
332 #if HAVE_X11
333 int strut_height = height() - margin;
334 if (strut_height < 0) {
335 strut_height = 0;
336 }
337 KWindowSystem::setStrut(winId(), 0, 0, strut_height, 0);
338 #endif
339 }
340 }
341
resizeEvent(QResizeEvent * e)342 void KMenuBar::resizeEvent(QResizeEvent *e)
343 {
344 if (e->spontaneous() && d->topLevel && !d->fallback_mode) {
345 ++block_resize; // do not respond with configure request to ConfigureNotify event
346 QMenuBar::resizeEvent(e); // to avoid possible infinite loop
347 --block_resize;
348 } else {
349 QMenuBar::resizeEvent(e);
350 }
351 }
352
setGeometry(const QRect & r)353 void KMenuBar::setGeometry(const QRect &r)
354 {
355 setGeometry(r.x(), r.y(), r.width(), r.height());
356 }
357
setGeometry(int x,int y,int w,int h)358 void KMenuBar::setGeometry(int x, int y, int w, int h)
359 {
360 if (block_resize > 0) {
361 move(x, y);
362 return;
363 }
364 checkSize(w, h);
365 if (geometry() != QRect(x, y, w, h)) {
366 QMenuBar::setGeometry(x, y, w, h);
367 }
368 }
369
resize(int w,int h)370 void KMenuBar::resize(int w, int h)
371 {
372 if (block_resize > 0) {
373 return;
374 }
375 checkSize(w, h);
376 if (size() != QSize(w, h)) {
377 QMenuBar::resize(w, h);
378 }
379 // kDebug() << "RS:" << w << ":" << h << ":" << width() << ":" << height() << ":" << minimumWidth() << ":" << minimumHeight();
380 }
381
resize(const QSize & s)382 void KMenuBar::resize(const QSize &s)
383 {
384 QMenuBar::resize(s);
385 }
386
checkSize(int & w,int & h)387 void KMenuBar::checkSize(int &w, int &h)
388 {
389 if (!d->topLevel || d->fallback_mode) {
390 return;
391 }
392 QSize s = sizeHint();
393 w = s.width();
394 h = s.height();
395 // This is not done as setMinimumSize(), because that would set the minimum
396 // size in WM_NORMAL_HINTS, and KWin would not allow changing to smaller size
397 // anymore
398 w = qMax(w, d->min_size.width());
399 h = qMax(h, d->min_size.height());
400 }
401
402 // QMenuBar's sizeHint() gives wrong size (insufficient width), which causes wrapping in the kicker applet
sizeHint() const403 QSize KMenuBar::sizeHint() const
404 {
405 if (!d->topLevel || block_resize > 0) {
406 return QMenuBar::sizeHint();
407 }
408 // Since QMenuBar::sizeHint() may indirectly call resize(),
409 // avoid infinite recursion.
410 ++block_resize;
411 // find the minimum useful height, and enlarge the width until the menu fits in that height (one row)
412 int h = heightForWidth(1000000);
413 int w = QMenuBar::sizeHint().width();
414 // optimization - don't call heightForWidth() too many times
415 while (heightForWidth(w + 12) > h) {
416 w += 12;
417 }
418 while (heightForWidth(w + 4) > h) {
419 w += 4;
420 }
421 while (heightForWidth(w) > h) {
422 ++w;
423 }
424 --block_resize;
425 return QSize(w, h);
426 }
427
428 #pragma message("Port to Qt5 native filter")
429 #if 0
430 bool KMenuBar::x11Event(XEvent *ev)
431 {
432 if (ev->type == ClientMessage && ev->xclient.message_type == msg_type_atom
433 && ev->xclient.window == winId()) {
434 // QMenuBar is trying really hard to keep the size it deems right.
435 // Forcing minimum size and blocking resizing to match parent size
436 // in checkResizingToParent() seem to be the only way to make
437 // KMenuBar keep the size it wants
438 d->min_size = QSize(ev->xclient.data.l[ 1 ], ev->xclient.data.l[ 2 ]);
439 // kDebug() << "MINSIZE:" << d->min_size;
440 updateMenuBarSize();
441 return true;
442 }
443 return QMenuBar::x11Event(ev);
444 }
445 #endif
446
updateMenuBarSize()447 void KMenuBar::updateMenuBarSize()
448 {
449 //menuContentsChanged(); // trigger invalidating calculated size
450 resize(sizeHint()); // and resize to preferred size
451 }
452
setFrameStyle(int style)453 void KMenuBar::setFrameStyle(int style)
454 {
455 if (d->topLevel) {
456 d->frameStyle = style;
457 }
458 // else
459 // QMenuBar::setFrameStyle( style );
460 }
461
setLineWidth(int width)462 void KMenuBar::setLineWidth(int width)
463 {
464 if (d->topLevel) {
465 d->lineWidth = width;
466 }
467 // else
468 // QMenuBar::setLineWidth( width );
469 }
470
setMargin(int margin)471 void KMenuBar::setMargin(int margin)
472 {
473 if (d->topLevel) {
474 d->margin = margin;
475 }
476 // else
477 // QMenuBar::setMargin( margin );
478 }
479
closeEvent(QCloseEvent * e)480 void KMenuBar::closeEvent(QCloseEvent *e)
481 {
482 if (d->topLevel) {
483 e->ignore(); // mainly for the fallback mode
484 } else {
485 QMenuBar::closeEvent(e);
486 }
487 }
488
paintEvent(QPaintEvent * pe)489 void KMenuBar::paintEvent(QPaintEvent *pe)
490 {
491 // Closes the BR77113
492 // We need to overload this method to paint only the menu items
493 // This way when the KMenuBar is embedded in the menu applet it
494 // integrates correctly.
495 //
496 // Background mode and origin are set so late because of styles
497 // using the polish() method to modify these settings.
498 //
499 // Of course this hack can safely be removed when real transparency
500 // will be available
501
502 // if( !d->topLevel )
503 {
504 QMenuBar::paintEvent(pe);
505 }
506 #if 0
507 else {
508 QPainter p(this);
509 bool up_enabled = isUpdatesEnabled();
510 Qt::BackgroundMode bg_mode = backgroundMode();
511 BackgroundOrigin bg_origin = backgroundOrigin();
512
513 setUpdatesEnabled(false);
514 setBackgroundMode(Qt::X11ParentRelative);
515 setBackgroundOrigin(WindowOrigin);
516
517 p.eraseRect(rect());
518 erase();
519
520 QColorGroup g = colorGroup();
521 bool e;
522
523 for (int i = 0; i < (int)count(); i++) {
524 QMenuItem *mi = findItem(idAt(i));
525
526 if (!mi->text().isEmpty() || !mi->icon().isNull()) {
527 QRect r = itemRect(i);
528 if (r.isEmpty() || !mi->isVisible()) {
529 continue;
530 }
531
532 e = mi->isEnabled() && mi->isVisible();
533 if (e)
534 g = isEnabled() ? (isActiveWindow() ? palette().active() :
535 palette().inactive()) : palette().disabled();
536 else {
537 g = palette().disabled();
538 }
539
540 bool item_active = (activeAction() == mi);
541
542 p.setClipRect(r);
543
544 if (item_active) {
545 QStyleOptionMenuItem miOpt;
546 miOpt.init(this);
547 miOpt.rect = r;
548 miOpt.text = mi->text();
549 miOpt.icon = mi->icon();
550 miOpt.palette = g;
551
552 QStyle::State flags = QStyle::State_None;
553 if (isEnabled() && e) {
554 flags |= QStyle::State_Enabled;
555 }
556 if (item_active) {
557 flags |= QStyle::State_Active;
558 }
559 if (item_active && actItemDown) {
560 flags |= QStyle::State_Down;
561 }
562 flags |= QStyle::State_HasFocus;
563
564 mi->state = flags;
565
566 style()->drawControl(QStyle::CE_MenuBarItem, &miOpt, &p, this);
567 } else {
568 style()->drawItem(p, r, Qt::AlignCenter | Qt::AlignVCenter | Qt::TextShowMnemonic,
569 g, e, mi->pixmap(), mi->text());
570 }
571 }
572 }
573
574 setBackgroundOrigin(bg_origin);
575 setBackgroundMode(bg_mode);
576 setUpdatesEnabled(up_enabled);
577 }
578 #endif
579 }
580
581