1/****************************************************************************
2**
3** Copyright (C) 2007-2008 Urs Wolfer <uwolfer @ kde.org>
4** Parts of this file have been take from okular:
5** Copyright (C) 2004-2005 Enrico Ros <eros.kde@email.it>
6**
7** This file is part of KDE.
8**
9** This program is free software; you can redistribute it and/or modify
10** it under the terms of the GNU General Public License as published by
11** the Free Software Foundation; either version 2 of the License, or
12** (at your option) any later version.
13**
14** This program is distributed in the hope that it will be useful,
15** but WITHOUT ANY WARRANTY; without even the implied warranty of
16** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17** GNU General Public License for more details.
18**
19** You should have received a copy of the GNU General Public License
20** along with this program; see the file COPYING. If not, write to
21** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22** Boston, MA 02110-1301, USA.
23**
24****************************************************************************/
25
26#include "floatingtoolbar.h"
27
28#include <KDebug>
29#include <KGlobalSettings>
30
31#include <QApplication>
32#include <QBitmap>
33#include <QDesktopWidget>
34#include <QMouseEvent>
35#include <QPainter>
36#include <QTimer>
37
38static const int actionIconSize = 22;
39static const int toolBarRBMargin = 2;
40static const double toolBarOpacity = 0.8;
41static const int visiblePixelWhenAutoHidden = 6;
42static const int autoHideTimeout = 500;
43static const int initialAutoHideTimeout = 2000;
44
45/**
46 * Denotes the verious states of the animation.
47 */
48enum AnimState {
49 Hiding,
50 Showing,
51 Still
52};
53
54class FloatingToolBarPrivate
55{
56public:
57 FloatingToolBarPrivate(FloatingToolBar *qq)
58 : q(qq)
59 , anchorSide(FloatingToolBar::Left)
60 , offsetPlaceHolder(new QWidget(qq))
61 , animState(Still)
62 , toDelete(false)
63 , visible(false)
64 , sticky(false)
65 , opacity(toolBarOpacity)
66 // set queuedShow to true so we show the toolbar if we get a resize event on the anchorWidget
67 , queuedShow(true) {
68 }
69
70 // rebuild contents and reposition then widget
71 void buildToolBar();
72 void reposition();
73 // compute the visible and hidden positions along current side
74 QPoint getInnerPoint() const;
75 QPoint getOuterPoint() const;
76
77 FloatingToolBar *q;
78
79 QWidget *anchorWidget;
80 FloatingToolBar::Side anchorSide;
81 QWidget *offsetPlaceHolder;
82
83 QTimer *animTimer;
84 QTimer *autoHideTimer;
85 QPoint currentPosition;
86 QPoint endPosition;
87 AnimState animState;
88 bool toDelete;
89 bool visible;
90 bool sticky;
91 qreal opacity;
92 bool queuedShow;
93
94 QPixmap backgroundPixmap;
95};
96
97FloatingToolBar::FloatingToolBar(QWidget *parent, QWidget *anchorWidget)
98 : QToolBar(parent), d(new FloatingToolBarPrivate(this))
99{
100 ;
101 addWidget(d->offsetPlaceHolder);
102
103 setMouseTracking(true);
104 setIconSize(QSize(actionIconSize, actionIconSize));
105 d->anchorWidget = anchorWidget;
106
107 d->animTimer = new QTimer(this);
108 connect(d->animTimer, SIGNAL(timeout()), this, SLOT(animate()));
109
110 d->autoHideTimer = new QTimer(this);
111 connect(d->autoHideTimer, SIGNAL(timeout()), this, SLOT(hide()));
112
113 // apply a filter to get notified when anchor changes geometry
114 d->anchorWidget->installEventFilter(this);
115}
116
117FloatingToolBar::~FloatingToolBar()
118{
119 delete d;
120}
121
122void FloatingToolBar::addAction(QAction *action)
123{
124 QToolBar::addAction(action);
125
126 // rebuild toolbar shape and contents only if the toolbar is already visible,
127 // otherwise it will be done in showAndAnimate()
128 if (isVisible())
129 d->reposition();
130}
131
132void FloatingToolBar::setSide(Side side)
133{
134 d->anchorSide = side;
135
136 if (isVisible())
137 d->reposition();
138}
139
140void FloatingToolBar::setSticky(bool sticky)
141{
142 d->sticky = sticky;
143
144 if (sticky)
145 d->autoHideTimer->stop();
146}
147
148void FloatingToolBar::showAndAnimate()
149{
150 if (d->animState == Showing)
151 return;
152
153 d->animState = Showing;
154
155 show();
156
157 // force update for case when toolbar has not been built yet
158 d->reposition();
159
160 // start scrolling in
161 d->animTimer->start(20);
162
163 // This permits to show the toolbar for a while when going full screen.
164 if (!d->sticky)
165 d->autoHideTimer->start(initialAutoHideTimeout);
166}
167
168void FloatingToolBar::hideAndDestroy()
169{
170 if (d->animState == Hiding)
171 return;
172
173 // set parameters for sliding out
174 d->animState = Hiding;
175 d->toDelete = true;
176 d->endPosition = d->getOuterPoint();
177
178 // start scrolling out
179 d->animTimer->start(20);
180}
181
182void FloatingToolBar::hide()
183{
184 if (underMouse())
185 return;
186
187 if (d->visible) {
188 QPoint diff;
189 switch (d->anchorSide) {
190 case Left:
191 diff = QPoint(visiblePixelWhenAutoHidden, 0);
192 break;
193 case Right:
194 diff = QPoint(-visiblePixelWhenAutoHidden, 0);
195 break;
196 case Top:
197 diff = QPoint(0, visiblePixelWhenAutoHidden);
198 break;
199 case Bottom:
200 diff = QPoint(0, -visiblePixelWhenAutoHidden);
201 break;
202 }
203 d->animState = Hiding;
204 d->endPosition = d->getOuterPoint() + diff;
205
206 // start scrolling out
207 d->animTimer->start(20);
208 }
209}
210
211bool FloatingToolBar::eventFilter(QObject *obj, QEvent *e)
212{
213 if (obj == d->anchorWidget && e->type() == QEvent::Resize) {
214 if (d->queuedShow) { // if the toolbar is not visible yet, try to show it if the anchor widget is in fullscreen already
215 d->queuedShow = false;
216 showAndAnimate();
217 return true;
218 }
219
220 // if anchorWidget changed geometry reposition toolbar
221 d->animTimer->stop();
222 if ((d->animState == Hiding || !d->visible) && d->toDelete)
223 deleteLater();
224 else
225 d->reposition();
226 }
227
228 return QToolBar::eventFilter(obj, e);
229}
230
231void FloatingToolBar::paintEvent(QPaintEvent *e)
232{
233 QToolBar::paintEvent(e);
234
235 // paint the internal pixmap over the widget
236 QPainter p(this);
237 p.setOpacity(d->opacity);
238 p.drawImage(e->rect().topLeft(), d->backgroundPixmap.toImage(), e->rect());
239}
240
241void FloatingToolBar::mousePressEvent(QMouseEvent *e)
242{
243 if (e->button() == Qt::LeftButton)
244 setCursor(Qt::SizeAllCursor);
245
246 QToolBar::mousePressEvent(e);
247}
248
249void FloatingToolBar::mouseMoveEvent(QMouseEvent *e)
250{
251 // show the toolbar again when it is auto-hidden
252 if (!d->visible) {
253 showAndAnimate();
254 return;
255 }
256
257 if ((QApplication::mouseButtons() & Qt::LeftButton) != Qt::LeftButton)
258 return;
259
260 // compute the nearest side to attach the widget to
261 const QPoint parentPos = mapToParent(e->pos());
262 const float nX = (float)parentPos.x() / (float)d->anchorWidget->width();
263 const float nY = (float)parentPos.y() / (float)d->anchorWidget->height();
264 if (nX > 0.3 && nX < 0.7 && nY > 0.3 && nY < 0.7)
265 return;
266 bool LT = nX < (1.0 - nY);
267 bool LB = nX < (nY);
268 Side side = LT ? (LB ? Left : Top) : (LB ? Bottom : Right);
269
270 // check if side changed
271 if (side == d->anchorSide)
272 return;
273
274 d->anchorSide = side;
275 d->reposition();
276 emit orientationChanged((int)side);
277
278 QToolBar::mouseMoveEvent(e);
279}
280
281void FloatingToolBar::enterEvent(QEvent *e)
282{
283 // Stop the autohide timer while the mouse is inside
284 d->autoHideTimer->stop();
285
286 if (!d->visible)
287 showAndAnimate();
288 QToolBar::enterEvent(e);
289}
290
291void FloatingToolBar::leaveEvent(QEvent *e)
292{
293 if (!d->sticky)
294 d->autoHideTimer->start(autoHideTimeout);
295 QToolBar::leaveEvent(e);
296}
297
298void FloatingToolBar::mouseReleaseEvent(QMouseEvent *e)
299{
300 if (e->button() == Qt::LeftButton)
301 setCursor(Qt::ArrowCursor);
302
303 QToolBar::mouseReleaseEvent(e);
304}
305
306void FloatingToolBar::wheelEvent(QWheelEvent *e)
307{
308 e->accept();
309
310 const qreal diff = e->delta() / 100.0 / 15.0;
311// kDebug(5010) << diff;
312 if (((d->opacity <= 1) && (diff > 0)) || ((d->opacity >= 0) && (diff < 0)))
313 d->opacity += diff;
314
315 update();
316
317 QToolBar::wheelEvent(e);
318}
319
320void FloatingToolBarPrivate::buildToolBar()
321{
322 const bool prevUpdates = q->updatesEnabled();
323 q->setUpdatesEnabled(false);
324
325 // 1. init numbers we are going to use
326 const bool topLeft = anchorSide == FloatingToolBar::Left || anchorSide == FloatingToolBar::Top;
327 const bool vertical = anchorSide == FloatingToolBar::Left || anchorSide == FloatingToolBar::Right;
328
329 if (vertical) {
330 offsetPlaceHolder->setFixedSize(1, 7);
331 q->setOrientation(Qt::Vertical);
332 } else {
333 offsetPlaceHolder->setFixedSize(7, 1);
334 q->setOrientation(Qt::Horizontal);
335 }
336
337 // 2. compute widget size
338 const int myWidth = q->sizeHint().width() - 1;
339 const int myHeight = q->sizeHint().height() - 1;
340
341 // 3. resize pixmap, mask and widget
342 QBitmap mask(myWidth + 1, myHeight + 1);
343 backgroundPixmap = QPixmap(myWidth + 1, myHeight + 1);
344 backgroundPixmap.fill(Qt::transparent);
345
346 q->resize(myWidth + 1, myHeight + 1);
347
348 // 4. create and set transparency mask
349 QPainter maskPainter(&mask);
350 mask.fill(Qt::white);
351 maskPainter.setBrush(Qt::black);
352 if (vertical)
353 maskPainter.drawRoundRect(topLeft ? -10 : 0, 0, myWidth + 10, myHeight, 2000 / (myWidth + 10), 2000 / myHeight);
354 else
355 maskPainter.drawRoundRect(0, topLeft ? -10 : 0, myWidth, myHeight + 10, 2000 / myWidth, 2000 / (myHeight + 10));
356 maskPainter.end();
357 q->setMask(mask);
358
359 // 5. draw background
360 QPainter bufferPainter(&backgroundPixmap);
361 bufferPainter.translate(0.5, 0.5);
362 QPalette pal = q->palette();
363 // 5.1. draw horizontal/vertical gradient
364 QLinearGradient grad;
365 switch (anchorSide) {
366 case FloatingToolBar::Left:
367 grad = QLinearGradient(0, 1, myWidth + 1, 1);
368 break;
369 case FloatingToolBar::Right:
370 grad = QLinearGradient(myWidth + 1, 1, 0, 1);
371 break;
372 case FloatingToolBar::Top:
373 grad = QLinearGradient(1, 0, 1, myHeight + 1);
374 break;
375 case FloatingToolBar::Bottom:
376 grad = QLinearGradient(1, myHeight + 1, 0, 1);
377 break;
378 }
379 grad.setColorAt(0, pal.color(QPalette::Active, QPalette::Button));
380 grad.setColorAt(1, pal.color(QPalette::Active, QPalette::Light));
381 bufferPainter.setBrush(QBrush(grad));
382 // 5.2. draw rounded border
383 bufferPainter.setPen( pal.color(QPalette::Active, QPalette::Dark).lighter(40));
384 bufferPainter.setRenderHints(QPainter::Antialiasing);
385 if (vertical)
386 bufferPainter.drawRoundRect(topLeft ? -10 : 0, 0, myWidth + 10, myHeight, 2000 / (myWidth + 10), 2000 / myHeight);
387 else
388 bufferPainter.drawRoundRect(0, topLeft ? -10 : 0, myWidth, myHeight + 10, 2000 / myWidth, 2000 / (myHeight + 10));
389 // 5.3. draw handle
390 bufferPainter.translate(-0.5, -0.5);
391 bufferPainter.setPen(pal.color(QPalette::Active, QPalette::Mid));
392 if (vertical) {
393 int dx = anchorSide == FloatingToolBar::Left ? 2 : 4;
394 bufferPainter.drawLine(dx, 6, dx + myWidth - 8, 6);
395 bufferPainter.drawLine(dx, 9, dx + myWidth - 8, 9);
396 bufferPainter.setPen(pal.color(QPalette::Active, QPalette::Light));
397 bufferPainter.drawLine(dx + 1, 7, dx + myWidth - 7, 7);
398 bufferPainter.drawLine(dx + 1, 10, dx + myWidth - 7, 10);
399 } else {
400 int dy = anchorSide == FloatingToolBar::Top ? 2 : 4;
401 bufferPainter.drawLine(6, dy, 6, dy + myHeight - 8);
402 bufferPainter.drawLine(9, dy, 9, dy + myHeight - 8);
403 bufferPainter.setPen(pal.color(QPalette::Active, QPalette::Light));
404 bufferPainter.drawLine(7, dy + 1, 7, dy + myHeight - 7);
405 bufferPainter.drawLine(10, dy + 1, 10, dy + myHeight - 7);
406 }
407
408 q->setUpdatesEnabled(prevUpdates);
409}
410
411void FloatingToolBarPrivate::reposition()
412{
413 // note: hiding widget here will gives better gfx, but ends drag operation
414 // rebuild widget and move it to its final place
415 buildToolBar();
416 if (!visible) {
417 currentPosition = getOuterPoint();
418 endPosition = getInnerPoint();
419 } else {
420 currentPosition = getInnerPoint();
421 endPosition = getOuterPoint();
422 }
423 q->move(currentPosition);
424}
425
426QPoint FloatingToolBarPrivate::getInnerPoint() const
427{
428 // returns the final position of the widget
429 if (anchorSide == FloatingToolBar::Left)
430 return QPoint(0, (anchorWidget->height() - q->height()) / 2);
431 if (anchorSide == FloatingToolBar::Top)
432 return QPoint((anchorWidget->width() - q->width()) / 2, 0);
433 if (anchorSide == FloatingToolBar::Right)
434 return QPoint(anchorWidget->width() - q->width() + toolBarRBMargin, (anchorWidget->height() - q->height()) / 2);
435 return QPoint((anchorWidget->width() - q->width()) / 2, anchorWidget->height() - q->height() + toolBarRBMargin);
436}
437
438QPoint FloatingToolBarPrivate::getOuterPoint() const
439{
440 // returns the point from which the transition starts
441 if (anchorSide == FloatingToolBar::Left)
442 return QPoint(-q->width(), (anchorWidget->height() - q->height()) / 2);
443 if (anchorSide == FloatingToolBar::Top)
444 return QPoint((anchorWidget->width() - q->width()) / 2, -q->height());
445 if (anchorSide == FloatingToolBar::Right)
446 return QPoint(anchorWidget->width() + toolBarRBMargin, (anchorWidget->height() - q->height()) / 2);
447 return QPoint((anchorWidget->width() - q->width()) / 2, anchorWidget->height() + toolBarRBMargin);
448}
449
450void FloatingToolBar::animate()
451{
452 if (KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects) {
453 // move currentPosition towards endPosition
454 int dX = d->endPosition.x() - d->currentPosition.x();
455 int dY = d->endPosition.y() - d->currentPosition.y();
456 dX = dX / 6 + qMax(-1, qMin(1, dX));
457 dY = dY / 6 + qMax(-1, qMin(1, dY));
458 d->currentPosition.setX(d->currentPosition.x() + dX);
459 d->currentPosition.setY(d->currentPosition.y() + dY);
460 } else {
461 d->currentPosition = d->endPosition;
462 }
463
464 move(d->currentPosition);
465
466 // handle arrival to the end
467 if (d->currentPosition == d->endPosition) {
468 d->animTimer->stop();
469 switch (d->animState) {
470 case Hiding:
471 d->visible = false;
472 d->animState = Still;
473 if (d->toDelete)
474 deleteLater();
475 break;
476 case Showing:
477 d->visible = true;
478 d->animState = Still;
479 break;
480 default:
481 kDebug(5010) << "Illegal state";
482 }
483 }
484}
485
486#include "floatingtoolbar.moc"
487