cosmetix
[dyskinesia.git] / src / floatwin.cpp
blobcc6e3dfcb8aa446ce3d2b0bd0e6ee154cdc14987
1 /* coded by Ketmar // Vampire Avalon (psyc://ketmar.no-ip.org/~Ketmar)
2 * Understanding is not required. Only obedience.
4 * This program is free software. It comes without any warranty, to
5 * the extent permitted by applicable law. You can redistribute it
6 * and/or modify it under the terms of the Do What The Fuck You Want
7 * To Public License, Version 2, as published by Sam Hocevar. See
8 * http://sam.zoy.org/wtfpl/COPYING for more details.
10 * we are the Borg.
12 #include "floatwin.h"
14 #include <QApplication>
15 #include <QDataStream>
16 #include <QDateTime>
17 #include <QDesktopWidget>
18 #include <QFont>
19 #include <QList>
20 #include <QMouseEvent>
21 #include <QPixmap>
24 ///////////////////////////////////////////////////////////////////////////////
25 static bool DoSticking (int &x0, int &y0, int w, int h, const QRect &itsRect, int power) {
26 bool res = false;
27 int d;
28 int x1, y1;
29 int ix0, iy0, ix1, iy1;
31 x1 = x0+w-1; y1 = y0+h-1;
32 QRect my(x0-power, y0-power, x1+power, y1+power);
33 if (!my.intersects(itsRect)) return false; // no magnet
34 itsRect.getCoords(&ix0, &iy0, &ix1, &iy1);
35 // left side of it
36 d = ix0-x1;
37 if (d > 0 && d <= power) {
38 x0 += d; x1 += d;
39 // top
40 d = iy0-y0; if (d >= -power && d <= power) { y0 += d; y1 += d; }
41 res = true;
43 // right side of it
44 d = x0-ix1;
45 if (d > 0 && d <= power) {
46 x0 -= d;
47 // top
48 d = iy0-y0; if (d >= -power && d <= power) { y0 += d; y1 += d; }
49 res = true;
51 // top side of it
52 d = iy0-y1;
53 if (d > 0 && d <= power) {
54 y0 += d; y1 += d;
55 // left
56 d = ix0-x0; if (d >= -power && d <= power) { x0 += d; x1 += d; }
57 // right
58 d = ix1-x1; if (d >= -power && d <= power) { x0 += d; x1 += d; }
59 res = true;
61 // bottom side of it
62 d = y0-iy1;
63 if (d > 0 && d <= power) {
64 y0 -= d;
65 // left
66 d = ix0-x0; if (d >= -power && d <= power) { x0 += d; x1 += d; }
67 // right
68 d = ix1-x1; if (d >= -power && d <= power) { x0 += d; x1 += d; }
69 res = true;
71 return res;
75 static bool DoDeskSticking (int &x0, int &y0, int w, int h, int power) {
76 QDesktopWidget *desktop = QApplication::desktop();
77 QRect geometry = desktop->availableGeometry();
78 bool res = false;
79 int d;
80 int x1, y1;
81 int ix0, iy0, ix1, iy1;
83 x1 = x0+w-1; y1 = y0+h-1;
84 geometry.getCoords(&ix0, &iy0, &ix1, &iy1); //ix1 += ix0-1; iy1 += iy0-1;
85 //fprintf(stderr, "desk: (%i, %i)-(%i, %i)\n", ix0, iy0, ix1, iy1);
86 // left side of desktop
87 d = x0-ix0;
88 if (d > 0 && d <= power) {
89 x0 -= d; x1 -= d;
90 res = true;
92 // right side of desctop
93 d = ix1-x1;
94 if (d > 0 && d <= power) {
95 x0 += d;
96 res = true;
98 // top side of desctop
99 d = y0-iy0;
100 if (d > 0 && d <= power) {
101 y0 -= d; y1 -= d;
102 res = true;
104 // bottom side of desctop
105 d = iy1-y1;
106 if (d > 0 && d <= power) {
107 y0 += d;
108 res = true;
110 return res;
114 ///////////////////////////////////////////////////////////////////////////////
115 FloatingWindow::FloatingWindow (FloatWinManager *wman, const QString &uni) :
116 QWidget(0), mMan(wman),
117 mUni(uni), mTitle(""), mStatus(PsycProto::Offline),
118 mMaybeMoving(false), mBMsgPhase(false), mBTitlePhase(false), mIgnoreMouseClickRelease(false),
119 mIcon(0), mMsgIcon(0), mMoveTimer(0),
120 mMousePos(QPoint()), mInitMousePos(QPoint()),
121 mBackBuf(0)
123 setMouseTracking(true);
124 setAttribute(Qt::WA_QuitOnClose, false);
125 setAttribute(Qt::WA_DeleteOnClose, true);
126 setAttribute(Qt::WA_KeyboardFocusChange, false);
127 setAttribute(Qt::WA_NoSystemBackground, true);
128 setAttribute(Qt::WA_AlwaysShowToolTips, true);
129 setWindowFlags(windowFlags() | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::ToolTip);
131 QFont f(font());
132 f.setPixelSize(12);
133 setFont(f);
135 calcSize();
139 FloatingWindow::~FloatingWindow () {
140 if (mMan) mMan->remove(this);
141 discardBuf();
145 const QIcon *FloatingWindow::statusIcon () {
146 if (!mIcon) mIcon = mMan->getStatusIcon(this, mStatus);
147 return mIcon;
151 const QIcon *FloatingWindow::msgIcon () {
152 if (!mMsgIcon) mMsgIcon = mMan->getMsgIcon(this);
153 return mMsgIcon;
157 void FloatingWindow::startMoveTimer () {
158 if (!mMoveTimer) {
159 mMoveTimer = new QTimer(this);
160 connect(mMoveTimer, SIGNAL(timeout()), this, SLOT(onStartMove()));
161 mMoveTimer->start(QApplication::startDragTime());
166 void FloatingWindow::stopMoveTimer () {
167 if (mMoveTimer) {
168 delete mMoveTimer;
169 mMoveTimer = 0;
174 void FloatingWindow::discardBuf () {
175 if (mBackBuf) {
176 delete mBackBuf;
177 mBackBuf = 0;
182 void FloatingWindow::setTitle (const QString &title) {
183 if (title != mTitle) {
184 mTitle = title;
185 calcSize();
186 update();
191 void FloatingWindow::setTip (const QString &tip) {
192 setToolTip(tip);
196 void FloatingWindow::setStatus (PsycProto::Status status) {
197 if (mStatus != status) {
198 mStatus = status;
199 mIcon = 0;
200 discardBuf();
201 update();
206 void FloatingWindow::calcSize () {
207 discardBuf();
208 QPixmap pict(2, 2);
209 QPainter p(&pict);
210 QFont f(font());
211 f.setUnderline(true);
212 f.setItalic(true);
213 f.setStrikeOut(true);
214 f.setOverline(true);
215 f.setBold(true);
216 p.setFont(f);
217 QRect br;
218 p.drawText(0, 0, 1, 1, Qt::AlignLeft | Qt::AlignVCenter, mTitle, &br);
219 int hgt = br.bottom(); if (hgt < 20) hgt = 20;
220 resize(2+16+4+br.right()+4+2, hgt);
224 void FloatingWindow::paintEvent (QPaintEvent *e) {
225 Q_UNUSED(e)
226 const QIcon *icon;
227 int w = width(), h = height();
229 if (!mBackBuf) {
230 mBackBuf = new QPixmap(w, h);
231 QPainter p(mBackBuf);
232 //p.begin(pict);
233 int fClr = 96, bClr = 32;
234 p.fillRect(QRect(0, 0, w, h), QColor(fClr, fClr, fClr));
235 p.fillRect(QRect(1, 1, w-2, h-2), QColor(bClr, bClr, bClr));
237 icon = mBMsgPhase ? msgIcon() : statusIcon();
238 if (icon && !icon->isNull()) icon->paint(&p, 2, (h-16)/2, 16, 16, Qt::AlignLeft);
240 //int ow = w, oh = h;
241 p.setPen(mBTitlePhase ? QColor(255, 255, 0) :
242 mStatus==PsycProto::Offline ? QColor(255, 0, 0) : QColor(255, 127, 0)
245 QFont f(font());
247 f.setUnderline(fBuddy->notAutho);
248 f.setItalic(fBuddy->IsInVisList());
249 f.setStrikeOut(fBuddy->IsInInvisList());
250 f.setOverline(fBuddy->IsInIgnoreList());
251 f.setBold(false);//!fBuddy->isOffline);
253 f.setUnderline(false);
254 f.setItalic(false);
255 f.setStrikeOut(false);
256 f.setOverline(false);
257 f.setBold(false);
258 p.setFont(f);
259 int x = 4+16+2; w -= x+2;
260 p.drawText(x, 2, w, h-4, Qt::AlignLeft | Qt::AlignVCenter, mTitle);
262 p.end();
265 // cache image, so we can redraw it only if something was changed?
266 QPainter p(this);
267 p.drawPixmap(0, 0, *mBackBuf);
268 p.end();
272 void FloatingWindow::mouseDoubleClickEvent (QMouseEvent *e) {
273 QPoint cp(e->globalPos());
274 emit mouseDblClicked(cp.x(), cp.y());
278 void FloatingWindow::mousePressEvent (QMouseEvent *e) {
279 if (e->button() == Qt::LeftButton) {
280 mInitMousePos = e->pos();
281 mMaybeMoving = true;
282 startMoveTimer();
284 if (e->button() == Qt::RightButton) {
285 QPoint cp(e->globalPos());
286 emit mouseRButtonClicked(cp.x(), cp.y());
291 void FloatingWindow::mouseReleaseEvent (QMouseEvent *e) {
292 if (e->button() == Qt::LeftButton) {
293 mInitMousePos = QPoint(0, 0);
294 mMaybeMoving = false;
295 stopMoveTimer();
296 if (mIgnoreMouseClickRelease) {
297 releaseMouse();
298 mIgnoreMouseClickRelease = false;
299 emit positionChanged();
300 // neighbours
301 QList<FloatingWindow *> neighbors;
302 addNeighbors(neighbors);
303 // list can grow, so can't do foreach here
304 for (int f = 0; f < neighbors.size(); f++) neighbors.at(f)->addNeighbors(neighbors);
305 foreach (FloatingWindow *w, neighbors) w->emitChangePosition();
306 // done
307 mMousePos = QPoint();
308 return;
314 void FloatingWindow::mouseMoveEvent (QMouseEvent *e) {
315 if ((e->buttons() & Qt::LeftButton) && !mInitMousePos.isNull() &&
316 (QPoint(e->pos()-mInitMousePos).manhattanLength() > QApplication::startDragDistance())) onStartMove();
317 if (mMousePos.isNull()) return;
319 QPoint newPos = e->globalPos()-mMousePos;
320 if (!(e->modifiers() & Qt::ShiftModifier)) {
321 // find neighbors
322 QList<FloatingWindow *> neighbors;
323 FloatingWindow *rightmost = 0;
324 QRect rgeo;
325 addNeighbors(neighbors);
326 // list can grow, so can't do foreach here
327 for (int f = 0; f < neighbors.size(); f++) {
328 if (!rightmost) {
329 rightmost = neighbors.at(f);
330 rgeo = rightmost->geometry();
331 } else {
332 QRect geo = neighbors.at(f)->geometry();
333 if (rgeo.right() < geo.right()) {
334 rightmost = neighbors.at(f);
335 rgeo = geo;
338 neighbors.at(f)->addNeighbors(neighbors);
340 // do sticking
341 int power = e->modifiers()&Qt::ControlModifier?8:4;
342 bool moved = false;
343 int x0 = newPos.x(), y0 = newPos.y(), wdt = width(), h = height();
344 foreach (QWidget *w, QApplication::topLevelWidgets()) {
345 if (w != this && !w->isHidden() && w->inherits("FloatingWindow")) {
346 FloatingWindow *its = static_cast<FloatingWindow *>(w);
347 if (neighbors.contains(its)) continue;
348 if (DoSticking(x0, y0, wdt, h, its->geometry(), power)) { moved = true; break; }
349 if (rightmost) {
350 QPoint p = this->pos();
351 int rx0 = newPos.x()-p.x()+rgeo.left(), ry0 = newPos.y()-p.y()+rgeo.top();
352 int ox = rx0, oy = ry0;
353 int rwdt = rightmost->width(), rhgt = rightmost->height();
354 if (DoSticking(rx0, ry0, rwdt, rhgt, its->geometry(), power)) {
355 moved = true;
356 x0 += rx0-ox; y0 += ry0-oy;
357 break;
362 if (!moved) { moved = DoDeskSticking(x0, y0, wdt, h, power); }
363 if (!moved && rightmost) {
364 QPoint p = this->pos();
365 int rx0 = newPos.x()-p.x()+rgeo.left(), ry0 = newPos.y()-p.y()+rgeo.top();
366 int ox = rx0, oy = ry0;
367 int rwdt = rightmost->width(), rhgt = rightmost->height();
368 moved = DoDeskSticking(rx0, ry0, rwdt, rhgt, power);
369 if (moved) { x0 += rx0-ox; y0 += ry0-oy; }
371 if (moved) { newPos.setX(x0); newPos.setY(y0); }
372 // move neighbors
373 QPoint myPos = pos();
374 int dx = newPos.x()-myPos.x();
375 int dy = newPos.y()-myPos.y();
376 foreach (FloatingWindow *w, neighbors) {
377 QPoint p = w->pos();
378 w->move(p.x()+dx, p.y()+dy);
382 move(newPos.x(), newPos.y());
383 //repaint();
384 update();
388 void FloatingWindow::emitChangePosition () {
389 emit positionChanged();
393 void FloatingWindow::addNeighbors (QList<FloatingWindow *> &list) {
394 QRect myGeo = this->geometry();
395 foreach (QWidget *w, QApplication::topLevelWidgets()) {
396 if (w == this || w->isHidden() || !w->inherits("FloatingWindow")) continue;
397 QRect geo = w->geometry();
398 if (myGeo.right() == geo.left() && myGeo.top() == geo.top()) list.append(static_cast<FloatingWindow *>(w));
403 void FloatingWindow::onStartMove () {
404 if (mInitMousePos.isNull()) return;
405 mMaybeMoving = false;
406 mMoveTimer->stop();
407 mMousePos = mInitMousePos;
408 mInitMousePos = QPoint(0, 0);
409 mIgnoreMouseClickRelease = true;
410 grabMouse();
414 void FloatingWindow::saveFState (QDataStream &st) const {
415 QPoint g = this->pos();
416 st << (quint8)0; // version
417 st << (int)(sizeof(int)*2); // data size
418 int x = g.x(), y = g.y();
419 st << x;
420 st << y;
424 void FloatingWindow::restoreFState (QDataStream &st) {
425 quint8 ver; int sz, x, y;
426 st >> ver;
427 st >> sz;
428 if (ver == 0 && sz >= (int)(sizeof(int)*2)) {
429 st >> x;
430 st >> y;
431 sz -= (sizeof(int)*2);
432 move(QPoint(x, y));
434 // skip the rest
435 if (sz > 0) st.skipRawData(sz);
439 ///////////////////////////////////////////////////////////////////////////////
440 FloatWinManager::FloatWinManager (int titleBlinkTO, int msgBlinkTO, QObject *parent) :
441 QObject(parent),
442 mBTitleTO(titleBlinkTO), mBMsgTO(msgBlinkTO),
443 mBTitlePhase(false), mBTitleTimer(0),
444 mBMsgPhase(false), mBMsgTimer(0)
446 mBTitleTimer = new QTimer(this);
447 connect(mBTitleTimer, SIGNAL(timeout()), this, SLOT(btitleTick()));
449 mBMsgTimer = new QTimer(this);
450 connect(mBMsgTimer, SIGNAL(timeout()), this, SLOT(bmsgTick()));
455 FloatWinManager::~FloatWinManager () {
456 clear();
460 void FloatWinManager::clear () {
461 mMine.clear(); mBMsgWin.clear(); mBTitleStop.clear();
462 foreach (FloatingWindow *w, mList) {
463 w->mMan = 0;
464 delete w;
469 void FloatWinManager::startBTitleTimer () {
470 if (!mBTitleTimer->isActive()) {
471 mBTitleTimer->start(mBTitleTO);
472 mBTitlePhase = true;
477 void FloatWinManager::stopBTitleTimer () {
478 if (mBTitleTimer->isActive()) {
479 mBTitlePhase = false;
480 mBTitleTimer->stop();
485 void FloatWinManager::startBMsgTimer () {
486 if (!mBMsgTimer->isActive()) {
487 mBMsgTimer->start(mBMsgTO);
488 mBMsgPhase = true;
493 void FloatWinManager::stopBMsgTimer () {
494 if (mBMsgTimer->isActive()) {
495 mBMsgPhase = false;
496 mBMsgTimer->stop();
501 void FloatWinManager::setMsgBlink (FloatingWindow *w, bool doBlink) {
502 if (mBMsgTO <= 0 || !w || !mMine.contains(w)) return;
503 if (doBlink) {
504 if (mBMsgWin.contains(w)) return;
505 mBMsgWin << w;
506 startBMsgTimer();
507 w->mBMsgPhase = mBMsgPhase;
508 } else {
509 if (!mBMsgWin.contains(w)) return;
510 mBMsgWin.remove(w);
511 if (mBMsgWin.isEmpty()) stopBMsgTimer();
512 w->mBMsgPhase = false;
514 w->discardBuf();
515 w->update(); // redraw us
519 void FloatWinManager::setTitleBlink (FloatingWindow *w, bool doBlink) {
520 if (mBTitleTO <= 0 || !w || !mMine.contains(w)) return;
521 if (doBlink) {
522 if (mBTitleStop.contains(w)) mBTitleStop.remove(w);
523 uint to = QDateTime::currentDateTime().toTime_t()+6;
524 mBTitleStop.insert(w, to);
525 startBTitleTimer();
526 w->mBTitlePhase = mBTitlePhase;
527 } else {
528 if (!mBTitleStop.contains(w)) return;
529 mBTitleStop.remove(w);
530 if (mBTitleStop.isEmpty()) stopBTitleTimer();
531 w->mBTitlePhase = false;
533 w->discardBuf();
534 w->update(); // redraw us
538 void FloatWinManager::bmsgTick () {
539 if (!mBMsgWin.size()) {
540 // nobody wants to blink anymore
541 stopBMsgTimer();
542 return;
544 mBMsgPhase = !mBMsgPhase;
545 // iterate over all windows
546 foreach (FloatingWindow *w, mBMsgWin) {
547 w->mBMsgPhase = mBMsgPhase;
548 w->discardBuf();
549 w->update();
554 void FloatWinManager::btitleTick () {
555 mBTitlePhase = !mBTitlePhase;
556 uint now = QDateTime::currentDateTime().toTime_t();
557 // iterators, 'cause we can erase items
558 QHash<FloatingWindow *, uint>::iterator i = mBTitleStop.begin();
559 while (i != mBTitleStop.end()) {
560 uint to = i.value();
561 FloatingWindow *w = i.key();
562 if (now >= to) {
563 w->mBTitlePhase = false;
564 i = mBTitleStop.erase(i);
565 } else {
566 w->mBTitlePhase = mBTitlePhase;
567 ++i;
569 w->discardBuf();
570 w->update();
572 if (mBTitleStop.isEmpty()) {
573 // nobody wants to blink anymore
574 stopBTitleTimer();
575 return;
580 FloatingWindow *FloatWinManager::change (const QString &uni, const QString &title, const QString &tip, PsycProto::Status status) {
581 FloatingWindow *res = find(uni);
582 if (res) {
583 res->setTitle(title);
584 res->setTip(tip);
585 res->setStatus(status);
587 return res;
591 FloatingWindow *FloatWinManager::produce (const QString &uni, const QString &title, const QString &tip,
592 PsycProto::Status status)
594 //if (uni.isEmpty()) return 0;
595 FloatingWindow *res = find(uni);
596 if (!res) {
597 res = new FloatingWindow(this, uni.toLower());
598 connect(res, SIGNAL(positionChanged()), this, SLOT(doSave()));
599 connect(res, SIGNAL(mouseRButtonClicked(int, int)), this, SLOT(onMouseRButtonClicked(int, int)));
600 connect(res, SIGNAL(mouseDblClicked(int, int)), this, SLOT(onMouseDblClicked(int, int)));
601 mMine.insert(res);
602 mList.insert(uni, res);
604 change(uni, title, tip, status);
605 res->show();
606 return res;
610 void FloatWinManager::onMouseRButtonClicked (int x, int y) {
611 emit mouseRButtonClicked(x, y, static_cast<FloatingWindow *>(sender())->mUni);
615 void FloatWinManager::onMouseDblClicked (int x, int y) {
616 emit mouseDblClicked(x, y, static_cast<FloatingWindow *>(sender())->mUni);
620 void FloatWinManager::doSave () {
621 saveFState();
625 bool FloatWinManager::contains (const QString &uni) {
626 return find(uni) ? true : false;
630 FloatingWindow *FloatWinManager::find (const QString &uni) {
631 if (!mList.contains(uni.toLower())) return 0;
632 return mList.value(uni.toLower());
636 void FloatWinManager::remove (const QString &uni) {
637 FloatingWindow *w = find(uni);
638 if (w && w->mMan == this) {
639 remove(w);
640 w->close(); // it will remove itself
645 void FloatWinManager::remove (FloatingWindow *w) {
646 if (!w || !mMine.contains(w)) return;
647 mMine.remove(w);
648 mList.remove(w->mUni);
649 mBMsgWin.remove(w);
650 mBTitleStop.remove(w);
654 void FloatWinManager::newMessage (const QString &uni) {
655 //qDebug() << "new msg:" << uni;
656 setMsgBlink(find(uni), true);
660 void FloatWinManager::noMessages (const QString &uni) {
661 //qDebug() << "NO msg:" << uni;
662 setMsgBlink(find(uni), false);
666 void FloatWinManager::newStatus (const QString &uni, PsycProto::Status status) {
667 FloatingWindow *w = find(uni);
668 if (!w) return;
669 if (w->mStatus != status) {
670 w->setStatus(status);
671 setTitleBlink(w, true);
676 void FloatWinManager::newTip (const QString &uni, const QString &tip) {
677 FloatingWindow *w = find(uni);
678 if (!w) return;
679 w->setTip(tip);
683 void FloatWinManager::newTitle (const QString &uni, const QString &title) {
684 FloatingWindow *w = find(uni);
685 if (!w) return;
686 w->setTitle(title);
690 void FloatWinManager::offlineAll () {
691 foreach (FloatingWindow *w, mList) w->setStatus(PsycProto::Offline);