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 | |
38 | static const int actionIconSize = 22; |
39 | static const int toolBarRBMargin = 2; |
40 | static const double toolBarOpacity = 0.8; |
41 | static const int visiblePixelWhenAutoHidden = 6; |
42 | static const int autoHideTimeout = 500; |
43 | static const int initialAutoHideTimeout = 2000; |
44 | |
45 | /** |
46 | * Denotes the verious states of the animation. |
47 | */ |
48 | enum AnimState { |
49 | Hiding, |
50 | Showing, |
51 | Still |
52 | }; |
53 | |
54 | class FloatingToolBarPrivate |
55 | { |
56 | public: |
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 | |
97 | FloatingToolBar::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 | |
117 | FloatingToolBar::~FloatingToolBar() |
118 | { |
119 | delete d; |
120 | } |
121 | |
122 | void 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 | |
132 | void FloatingToolBar::setSide(Side side) |
133 | { |
134 | d->anchorSide = side; |
135 | |
136 | if (isVisible()) |
137 | d->reposition(); |
138 | } |
139 | |
140 | void FloatingToolBar::setSticky(bool sticky) |
141 | { |
142 | d->sticky = sticky; |
143 | |
144 | if (sticky) |
145 | d->autoHideTimer->stop(); |
146 | } |
147 | |
148 | void 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 | |
168 | void 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 | |
182 | void 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 | |
211 | bool 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 | |
231 | void 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 | |
241 | void FloatingToolBar::mousePressEvent(QMouseEvent *e) |
242 | { |
243 | if (e->button() == Qt::LeftButton) |
244 | setCursor(Qt::SizeAllCursor); |
245 | |
246 | QToolBar::mousePressEvent(e); |
247 | } |
248 | |
249 | void 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 | |
281 | void 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 | |
291 | void FloatingToolBar::leaveEvent(QEvent *e) |
292 | { |
293 | if (!d->sticky) |
294 | d->autoHideTimer->start(autoHideTimeout); |
295 | QToolBar::leaveEvent(e); |
296 | } |
297 | |
298 | void FloatingToolBar::mouseReleaseEvent(QMouseEvent *e) |
299 | { |
300 | if (e->button() == Qt::LeftButton) |
301 | setCursor(Qt::ArrowCursor); |
302 | |
303 | QToolBar::mouseReleaseEvent(e); |
304 | } |
305 | |
306 | void 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 | |
320 | void 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 | |
411 | void 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 | |
426 | QPoint 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 | |
438 | QPoint 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 | |
450 | void 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 | |