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