1 /*
2 * advwidget.cpp - AdvancedWidget template class
3 * Copyright (C) 2005-2007 Michail Pishchagin
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 */
20
21 #include <QtGlobal> // required to make mingw32 happy
22 #ifdef Q_OS_WIN
23 #if __GNUC__ >= 3
24 # define WINVER 0x0500
25 #endif
26 #include <windows.h>
27 #include <winuser.h>
28 #endif
29
30 #include "advwidget.h"
31
32 #include <QApplication>
33 #include <QWidget>
34 #include <QTimer>
35 #include <QDesktopWidget>
36 #include <QDebug>
37
38 #include "psioptions.h"
39
40 #ifdef HAVE_X11
41 #include <X11/Xlib.h>
42 #include <X11/Xutil.h>
43 #include <QX11Info>
44 #endif
45
46 // TODO: Make use of KDE taskbar flashing support
47
48 //----------------------------------------------------------------------------
49 // AdvancedWidgetShared
50 //----------------------------------------------------------------------------
51
52 class AdvancedWidgetShared : public QObject
53 {
54 Q_OBJECT
55 public:
56 AdvancedWidgetShared();
57 ~AdvancedWidgetShared();
58 };
59
AdvancedWidgetShared()60 AdvancedWidgetShared::AdvancedWidgetShared()
61 : QObject(qApp)
62 {
63 }
64
~AdvancedWidgetShared()65 AdvancedWidgetShared::~AdvancedWidgetShared()
66 {
67 }
68
69 static AdvancedWidgetShared *advancedWidgetShared = 0;
70
71 //----------------------------------------------------------------------------
72 // GAdvancedWidget::Private
73 //----------------------------------------------------------------------------
74
75 class GAdvancedWidget::Private : public QObject
76 {
77 Q_OBJECT
78 public:
79 Private(QWidget *parent);
80
81 static int stickAt;
82 static bool stickToWindows;
83 static bool stickEnabled;
84
85 QWidget* parentWidget_;
86 bool flashing_;
87 QString geometryOptionPath_;
88
89 bool flashing() const;
90 void doFlash(bool on);
91 void posChanging(int *x, int *y, int *width, int *height);
92 void moveEvent(QMoveEvent *e);
93
94 protected:
95 // reimplemented
96 bool eventFilter(QObject* obj, QEvent* e);
97
98 private:
99 QTimer* saveGeometryTimer_;
100 QRect newGeometry_;
101 QRect normalGeometry_;
102
103 public slots:
104 void saveGeometry();
105 void restoreGeometry();
106
107 private slots:
108 void updateGeometry();
109
110 void restoreGeometry(QRect savedGeometry);
111 };
112
113 int GAdvancedWidget::Private::stickAt = 5;
114 bool GAdvancedWidget::Private::stickToWindows = true;
115 bool GAdvancedWidget::Private::stickEnabled = true;
116
Private(QWidget * parent)117 GAdvancedWidget::Private::Private(QWidget *parent)
118 : QObject(parent)
119 , parentWidget_(parent)
120 , flashing_(false)
121 {
122 if (!advancedWidgetShared)
123 advancedWidgetShared = new AdvancedWidgetShared();
124
125 saveGeometryTimer_ = new QTimer(this);
126 saveGeometryTimer_->setInterval(100);
127 saveGeometryTimer_->setSingleShot(true);
128 connect(saveGeometryTimer_, SIGNAL(timeout()), SLOT(saveGeometry()));
129 }
130
posChanging(int * x,int * y,int * width,int * height)131 void GAdvancedWidget::Private::posChanging(int *x, int *y, int *width, int *height)
132 {
133 if ( stickAt <= 0 ||
134 !stickEnabled ||
135 !parentWidget_->isTopLevel() ||
136 parentWidget_->isMaximized() ||
137 !parentWidget_->updatesEnabled() )
138 {
139 return;
140 }
141
142 QWidget *p = parentWidget_;
143 if ( p->pos() == QPoint(*x, *y) &&
144 p->frameSize() == QSize(*width, *height) )
145 return;
146
147 bool resizing = p->frameSize() != QSize(*width, *height);
148
149 QDesktopWidget *desktop = qApp->desktop();
150 QWidgetList list;
151
152 if ( stickToWindows )
153 list = QApplication::topLevelWidgets();
154
155 foreach(QWidget *w, list) {
156 QRect rect;
157 bool dockWidget = false;
158
159 if ( w->windowType() == Qt::Desktop )
160 rect = ((QDesktopWidget *)w)->availableGeometry((QWidget *)parent());
161 else {
162 if ( w == p ||
163 desktop->screenNumber(p) != desktop->screenNumber(w) )
164 continue;
165
166 if ( !w->isVisible() )
167 continue;
168
169 // we want for widget to stick to outer edges of another widget, so
170 // we'll change the rect to what it'll snap
171 rect = QRect(w->frameGeometry().bottomRight(), w->frameGeometry().topLeft());
172 dockWidget = true;
173 }
174
175 if ( *x != p->x() )
176 if ( *x <= rect.left() + stickAt &&
177 *x > rect.left() - stickAt ) {
178 if ( !dockWidget ||
179 (p->frameGeometry().bottom() >= rect.bottom() &&
180 p->frameGeometry().top() <= rect.top()) ) {
181 *x = rect.left();
182 if ( resizing )
183 *width = p->frameSize().width() + p->x() - *x;
184 }
185 }
186
187 if ( *x + *width >= rect.right() - stickAt &&
188 *x + *width <= rect.right() + stickAt ) {
189 if ( !dockWidget ||
190 (p->frameGeometry().bottom() >= rect.bottom() &&
191 p->frameGeometry().top() <= rect.top()) ) {
192 if ( resizing )
193 *width = rect.right() - *x + 1;
194 else
195 *x = rect.right() - *width + 1;
196 }
197 }
198
199 if ( *y != p->y() )
200 if ( *y <= rect.top() + stickAt &&
201 *y > rect.top() - stickAt ) {
202 if ( !dockWidget ||
203 (p->frameGeometry().right() >= rect.right() &&
204 p->frameGeometry().left() <= rect.left()) ) {
205 *y = rect.top();
206 if ( resizing )
207 *height = p->frameSize().height() + p->y() - *y;
208 }
209 }
210
211 if ( *y + *height >= rect.bottom() - stickAt &&
212 *y + *height <= rect.bottom() + stickAt ) {
213 if ( !dockWidget ||
214 (p->frameGeometry().right() >= rect.right() &&
215 p->frameGeometry().left() <= rect.left()) ) {
216 if ( resizing )
217 *height = rect.bottom() - *y + 1;
218 else
219 *y = rect.bottom() - *height + 1;
220 }
221 }
222 }
223 }
224
flashing() const225 bool GAdvancedWidget::Private::flashing() const
226 {
227 return flashing_;
228 }
229
doFlash(bool yes)230 void GAdvancedWidget::Private::doFlash(bool yes)
231 {
232 flashing_ = yes;
233 if (parentWidget_->window() != parentWidget_)
234 return;
235
236 #ifdef Q_OS_WIN
237 FLASHWINFO fwi;
238 fwi.cbSize = sizeof(fwi);
239 fwi.hwnd = (HWND)parentWidget_->winId();
240 if (yes) {
241 fwi.dwFlags = FLASHW_ALL | FLASHW_TIMER;
242 fwi.dwTimeout = 0;
243 fwi.uCount = 5;
244 }
245 else {
246 fwi.dwFlags = FLASHW_STOP;
247 fwi.uCount = 0;
248 }
249 FlashWindowEx(&fwi);
250
251 #elif defined( HAVE_X11 )
252 if (QX11Info::isPlatformX11()) {
253 static Atom demandsAttention = None;
254 static Atom wmState = None;
255
256
257 /* Xlib-based solution */
258 // adopted from http://www.qtforum.org/article/12334/Taskbar-flashing.html
259 // public domain by Marcin Jakubowski
260 Display *xdisplay = QX11Info::display();
261 Window rootwin = QX11Info::appRootWindow();
262
263 if (demandsAttention == None) {
264 demandsAttention = XInternAtom(xdisplay, "_NET_WM_STATE_DEMANDS_ATTENTION", true);
265 }
266 if (wmState == None) {
267 wmState = XInternAtom(xdisplay, "_NET_WM_STATE", true);
268 }
269
270 XEvent e;
271 e.xclient.type = ClientMessage;
272 e.xclient.message_type = wmState;
273 e.xclient.display = xdisplay;
274 e.xclient.window = parentWidget_->winId();
275 e.xclient.format = 32;
276 e.xclient.data.l[1] = demandsAttention;
277 e.xclient.data.l[2] = 0l;
278 e.xclient.data.l[3] = 0l;
279 e.xclient.data.l[4] = 0l;
280
281 if (yes) {
282 e.xclient.data.l[0] = 1;
283 }
284 else {
285 e.xclient.data.l[0] = 0;
286 }
287 XSendEvent(xdisplay, rootwin, False, (SubstructureRedirectMask | SubstructureNotifyMask), &e);
288 }
289 #else
290 Q_UNUSED(yes)
291 #endif
292 }
293
moveEvent(QMoveEvent *)294 void GAdvancedWidget::Private::moveEvent(QMoveEvent *)
295 {
296 if (!parentWidget_->isTopLevel())
297 return;
298 #ifdef Q_OS_MAC
299 QRect r = qApp->desktop()->availableGeometry(parentWidget_);
300 QRect g = parentWidget_->frameGeometry();
301
302 int margin = 5;
303
304 if (g.top() < r.top())
305 g.moveTo(g.x(), r.top());
306
307 if (g.right() < r.left() + margin)
308 g.moveTo(r.left() + margin - g.width(), g.y());
309
310 if (g.left() > r.right() - margin)
311 g.moveTo(r.right() - margin, g.y());
312
313 if (g.top() > r.bottom() - margin)
314 g.moveTo(g.x(), r.bottom() - margin);
315
316 newGeometry_ = g;
317 QTimer::singleShot(0, this, SLOT(updateGeometry()));
318 #endif
319 }
320
updateGeometry()321 void GAdvancedWidget::Private::updateGeometry()
322 {
323 QWidget *w = (QWidget *)parent();
324 w->move(newGeometry_.topLeft());
325 }
326
saveGeometry()327 void GAdvancedWidget::Private::saveGeometry()
328 {
329 bool isMaximized = parentWidget_->windowState() & Qt::WindowMaximized;
330 //if window is maximized normalGeometry() returns null rect. So in this case we use cached geometry
331 PsiOptions::instance()->setOption(geometryOptionPath_, isMaximized ? normalGeometry_ : parentWidget_->normalGeometry());
332 PsiOptions::instance()->setOption(geometryOptionPath_ + "-frame", parentWidget_->frameGeometry());
333 PsiOptions::instance()->setOption(geometryOptionPath_ + "-screen", QApplication::desktop()->screenNumber(parentWidget_));
334 PsiOptions::instance()->setOption(geometryOptionPath_ + "-maximized", isMaximized);
335 PsiOptions::instance()->setOption(geometryOptionPath_ + "-fullscreen", bool(parentWidget_->windowState() & Qt::WindowFullScreen));
336 }
337
restoreGeometry()338 void GAdvancedWidget::Private::restoreGeometry()
339 {
340 PsiOptions *o = PsiOptions::instance();
341 QVariant v(o->getOption(geometryOptionPath_));
342
343 if (v.type() == QVariant::ByteArray) {
344 // migrate options back from format used for a short time before
345 // 0.12-RC2. This can be removed later.
346 parentWidget_->restoreGeometry(v.toByteArray());
347 } else if (o->getOption(geometryOptionPath_ + "-frame").toRect() != QRect(0,0,0,0)) {
348 // this is at least as safe as saving this kind of binary blob into the options file.
349 // if future Qt versions drop support for restoring from this format old options files
350 // would break anyway. If we want to (e.g. to add other features) we can also reimplement
351 // restoring any other way without breaking the options format at all.
352 // and this way we are sure no Qt version the user happens to have installed writes some
353 // newer version of the format that older Qts can't restore from.
354
355 QByteArray array;
356 QDataStream stream(&array, QIODevice::WriteOnly);
357 stream.setVersion(QDataStream::Qt_4_0);
358 const quint32 magicNumber = 0x1D9D0CB;
359 quint16 majorVersion = 1;
360 quint16 minorVersion = 0;
361 QRect restoredFrameGeometry = o->getOption(geometryOptionPath_ + "-frame").toRect();
362 normalGeometry_ = o->getOption(geometryOptionPath_).toRect();
363
364 stream << magicNumber
365 << majorVersion
366 << minorVersion
367 << restoredFrameGeometry
368 << normalGeometry_
369 << qint32(o->getOption(geometryOptionPath_ + "-screen").toInt())
370 << quint8(o->getOption(geometryOptionPath_ + "-maximized").toBool())
371 << quint8(o->getOption(geometryOptionPath_ + "-fullscreen").toBool());
372
373 parentWidget_->restoreGeometry(array);
374 return;
375 } else {
376 // used for bootstrapping and for pre 0.12-RC1 options files.
377 QRect savedGeometry = o->getOption(geometryOptionPath_).toRect();
378 if (!savedGeometry.isEmpty()) {
379 restoreGeometry(savedGeometry);
380 }
381 }
382 }
383
384 // FIXME: should use frameGeometry
restoreGeometry(QRect savedGeometry)385 void GAdvancedWidget::Private::restoreGeometry(QRect savedGeometry)
386 {
387 QRect geom = savedGeometry;
388 QDesktopWidget *pdesktop = QApplication::desktop();
389 int nscreen = pdesktop->screenNumber(geom.topLeft());
390 QRect r = pdesktop->screenGeometry(nscreen);
391
392 // if the coordinates are out of the desktop bounds, reset to the top left
393 int pad = 10;
394 if (geom.left() < r.left())
395 geom.moveLeft(r.left());
396 if (geom.right() >= r.right())
397 geom.moveRight(r.right() - 1);
398 if (geom.top() < r.top())
399 geom.moveTop(r.top());
400 if (geom.bottom() >= r.bottom())
401 geom.moveBottom(r.bottom() - 1);
402 if ((geom.width() + pad * 2) > r.width())
403 geom.setWidth(r.width() - pad * 2);
404 if ((geom.height() + pad * 2) > r.height())
405 geom.setHeight(r.height() - pad * 2);
406
407 parentWidget_->move(geom.topLeft());
408 parentWidget_->resize(geom.size());
409 }
410
eventFilter(QObject * obj,QEvent * e)411 bool GAdvancedWidget::Private::eventFilter(QObject* obj, QEvent* e)
412 {
413 if (obj == parentWidget_) {
414 if (e->type() == QEvent::Move || e->type() == QEvent::Resize) {
415 Qt::WindowStates ws = parentWidget_->windowState();
416 if( !(ws & Qt::WindowMaximized) && !(ws & Qt::WindowFullScreen) ) {
417 normalGeometry_ = parentWidget_->normalGeometry();
418 }
419 saveGeometryTimer_->start();
420 }
421
422 return false;
423 }
424
425 return QObject::eventFilter(obj, e);
426 }
427
428 //----------------------------------------------------------------------------
429 // GAdvancedWidget
430 //----------------------------------------------------------------------------
431
GAdvancedWidget(QWidget * parent)432 GAdvancedWidget::GAdvancedWidget(QWidget *parent)
433 : QObject(parent)
434 {
435 d = new Private(parent);
436 }
437
438 #ifdef Q_OS_WIN
winEvent(MSG * msg,long * result)439 bool GAdvancedWidget::winEvent(MSG* msg, long* result)
440 {
441 if ( msg->message == WM_WINDOWPOSCHANGING ) {
442 WINDOWPOS *wpos = (WINDOWPOS *)msg->lParam;
443
444 d->posChanging(&wpos->x, &wpos->y, &wpos->cx, &wpos->cy);
445
446 result = 0;
447 return true;
448 }
449
450 return false;
451 }
452 #endif
453
geometryOptionPath() const454 QString GAdvancedWidget::geometryOptionPath() const
455 {
456 return d->geometryOptionPath_;
457 }
458
setGeometryOptionPath(const QString & optionPath)459 void GAdvancedWidget::setGeometryOptionPath(const QString& optionPath)
460 {
461 Q_ASSERT(d->geometryOptionPath_.isEmpty());
462 Q_ASSERT(!optionPath.isEmpty());
463 d->geometryOptionPath_ = optionPath;
464 d->restoreGeometry();
465 d->parentWidget_->installEventFilter(d);
466 }
467
flashing() const468 bool GAdvancedWidget::flashing() const
469 {
470 return d->flashing();
471 }
472
doFlash(bool on)473 void GAdvancedWidget::doFlash(bool on)
474 {
475 d->doFlash( on );
476 }
477
478 #ifdef Q_OS_WIN
479 // http://groups.google.ru/group/borland.public.cppbuilder.winapi/msg/6eb6f1832d68686d?hl=ru&
ForceForegroundWindow(HWND hwnd)480 bool ForceForegroundWindow(HWND hwnd)
481 {
482 // Code from Thomas Stutz @ delphi3000.com
483 // Converted to Borland C++ Builder Code by Wolfgang Frisch
484 bool Result;
485 // #define SPI_GETFOREGROUNDLOCKTIMEOUT (0x2000);
486 // #define SPI_SETFOREGROUNDLOCKTIMEOUT (0x2001);
487 DWORD nullvalue = 0;
488
489 DWORD ForegroundThreadID;
490 DWORD ThisThreadID;
491 DWORD timeout;
492
493 if (IsIconic(hwnd)) {
494 ShowWindow(hwnd, SW_RESTORE);
495 }
496
497 if (GetForegroundWindow() == hwnd) {
498 return true;
499 }
500 else {
501 OSVERSIONINFO osvi;
502 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
503 GetVersionEx(&osvi);
504
505 // Windows 98/2000 doesn't want to foreground a window when some other
506 // window has keyboard focus
507 if (((osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) && (osvi.dwMajorVersion > 4))
508 || ((osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
509 && ((osvi.dwMajorVersion > 4) || ((osvi.dwMajorVersion == 4) &&
510 (osvi.dwMinorVersion > 0))))) {
511 // Code from Karl E. Peterson, www.mvps.org/vb/sample.htm
512 // Converted to Delphi by Ray Lischner
513 // Published in The Delphi Magazine 55, page 16
514
515 Result = false;
516 ForegroundThreadID = GetWindowThreadProcessId(GetForegroundWindow(), NULL);
517 ThisThreadID = GetWindowThreadProcessId(hwnd, NULL);
518 if (AttachThreadInput(ThisThreadID, ForegroundThreadID, true)) {
519 BringWindowToTop(hwnd); // IE 5.5 related hack
520 SetForegroundWindow(hwnd);
521 AttachThreadInput(ThisThreadID, ForegroundThreadID, false);
522 Result = (GetForegroundWindow() == hwnd);
523 }
524
525 if (!Result) {
526 // Code by Daniel P. Stasinski
527 SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, &timeout, 0);
528 SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, &nullvalue, SPIF_SENDCHANGE);
529 BringWindowToTop(hwnd); // IE 5.5 related hack
530 SetForegroundWindow(hwnd);
531 SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, &timeout, SPIF_SENDCHANGE);
532 }
533 }
534 else {
535 BringWindowToTop(hwnd); // IE 5.5 related hack
536 SetForegroundWindow(hwnd);
537 }
538 Result = (GetForegroundWindow() == hwnd);
539 }
540 return Result;
541 }
542 #endif
543
544 /**
545 * http://trolltech.com/developer/task-tracker/index_html?id=202971&method=entry
546 * There's a bug in Qt that prevents us to show a window on Windows
547 * without it being activated in the process. This is not good in some cases.
548 * We try our best to work-around this on Windows.
549 */
showWithoutActivation()550 void GAdvancedWidget::showWithoutActivation()
551 {
552 if (d->parentWidget_->isVisible())
553 return;
554
555 // TODO: look at Qt::WA_ShowWithoutActivating that was introduced
556 // in Qt 4.4.0, maybe it'll provide a simpler alternative to this
557 // windows-specific code
558
559 #ifdef Q_OS_WIN
560 HWND foregroundWindow = GetForegroundWindow();
561 #endif
562
563 bool showWithoutActivating = d->parentWidget_->testAttribute(Qt::WA_ShowWithoutActivating);
564 d->parentWidget_->setAttribute(Qt::WA_ShowWithoutActivating, true);
565 d->parentWidget_->show();
566 d->parentWidget_->setAttribute(Qt::WA_ShowWithoutActivating, showWithoutActivating);
567
568 #ifdef Q_OS_WIN
569 if (foregroundWindow) {
570 // the first step is to make sure we're the topmost window
571 // otherwise step two doesn't seem to have any effect at all
572 ForceForegroundWindow((HWND)d->parentWidget_->winId());
573 ForceForegroundWindow(foregroundWindow);
574 }
575 #endif
576 }
577
changeEvent(QEvent * event)578 void GAdvancedWidget::changeEvent(QEvent *event)
579 {
580 if (event->type() == QEvent::ActivationChange ||
581 event->type() == QEvent::WindowStateChange)
582 {
583 if (d->parentWidget_->isActiveWindow()) {
584 doFlash(false);
585 }
586 }
587 }
588
stickAt()589 int GAdvancedWidget::stickAt()
590 {
591 return Private::stickAt;
592 }
593
setStickAt(int val)594 void GAdvancedWidget::setStickAt(int val)
595 {
596 Private::stickAt = val;
597 }
598
stickToWindows()599 bool GAdvancedWidget::stickToWindows()
600 {
601 return Private::stickToWindows;
602 }
603
setStickToWindows(bool val)604 void GAdvancedWidget::setStickToWindows(bool val)
605 {
606 Private::stickToWindows = val;
607 }
608
stickEnabled()609 bool GAdvancedWidget::stickEnabled()
610 {
611 return Private::stickEnabled;
612 }
613
setStickEnabled(bool val)614 void GAdvancedWidget::setStickEnabled(bool val)
615 {
616 Private::stickEnabled = val;
617 }
618
moveEvent(QMoveEvent * e)619 void GAdvancedWidget::moveEvent(QMoveEvent *e)
620 {
621 d->moveEvent(e);
622 }
623
624 #include "advwidget.moc"
625