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.
14 #include <QApplication>
15 #include <QDataStream>
17 #include <QDesktopWidget>
20 #include <QMouseEvent>
24 ///////////////////////////////////////////////////////////////////////////////
25 static bool DoSticking (int &x0
, int &y0
, int w
, int h
, const QRect
&itsRect
, int power
) {
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
);
37 if (d
> 0 && d
<= power
) {
40 d
= iy0
-y0
; if (d
>= -power
&& d
<= power
) { y0
+= d
; y1
+= d
; }
45 if (d
> 0 && d
<= power
) {
48 d
= iy0
-y0
; if (d
>= -power
&& d
<= power
) { y0
+= d
; y1
+= d
; }
53 if (d
> 0 && d
<= power
) {
56 d
= ix0
-x0
; if (d
>= -power
&& d
<= power
) { x0
+= d
; x1
+= d
; }
58 d
= ix1
-x1
; if (d
>= -power
&& d
<= power
) { x0
+= d
; x1
+= d
; }
63 if (d
> 0 && d
<= power
) {
66 d
= ix0
-x0
; if (d
>= -power
&& d
<= power
) { x0
+= d
; x1
+= d
; }
68 d
= ix1
-x1
; if (d
>= -power
&& d
<= power
) { x0
+= d
; x1
+= d
; }
75 static bool DoDeskSticking (int &x0
, int &y0
, int w
, int h
, int power
) {
76 QDesktopWidget
*desktop
= QApplication::desktop();
77 QRect geometry
= desktop
->availableGeometry();
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
88 if (d
> 0 && d
<= power
) {
92 // right side of desctop
94 if (d
> 0 && d
<= power
) {
98 // top side of desctop
100 if (d
> 0 && d
<= power
) {
104 // bottom side of desctop
106 if (d
> 0 && d
<= power
) {
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()),
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
);
139 FloatingWindow::~FloatingWindow () {
140 if (mMan
) mMan
->remove(this);
145 const QIcon
*FloatingWindow::statusIcon () {
146 if (!mIcon
) mIcon
= mMan
->getStatusIcon(this, mStatus
);
151 const QIcon
*FloatingWindow::msgIcon () {
152 if (!mMsgIcon
) mMsgIcon
= mMan
->getMsgIcon(this);
157 void FloatingWindow::startMoveTimer () {
159 mMoveTimer
= new QTimer(this);
160 connect(mMoveTimer
, SIGNAL(timeout()), this, SLOT(onStartMove()));
161 mMoveTimer
->start(QApplication::startDragTime());
166 void FloatingWindow::stopMoveTimer () {
174 void FloatingWindow::discardBuf () {
182 void FloatingWindow::setTitle (const QString
&title
) {
183 if (title
!= mTitle
) {
191 void FloatingWindow::setTip (const QString
&tip
) {
196 void FloatingWindow::setStatus (PsycProto::Status status
) {
197 if (mStatus
!= status
) {
206 void FloatingWindow::calcSize () {
211 f
.setUnderline(true);
213 f
.setStrikeOut(true);
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
) {
227 int w
= width(), h
= height();
230 mBackBuf
= new QPixmap(w
, h
);
231 QPainter
p(mBackBuf
);
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)
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);
255 f
.setStrikeOut(false);
256 f
.setOverline(false);
259 int x
= 4+16+2; w
-= x
+2;
260 p
.drawText(x
, 2, w
, h
-4, Qt::AlignLeft
| Qt::AlignVCenter
, mTitle
);
265 // cache image, so we can redraw it only if something was changed?
267 p
.drawPixmap(0, 0, *mBackBuf
);
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();
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;
296 if (mIgnoreMouseClickRelease
) {
298 mIgnoreMouseClickRelease
= false;
299 emit
positionChanged();
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();
307 mMousePos
= QPoint();
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
)) {
322 QList
<FloatingWindow
*> neighbors
;
323 FloatingWindow
*rightmost
= 0;
325 addNeighbors(neighbors
);
326 // list can grow, so can't do foreach here
327 for (int f
= 0; f
< neighbors
.size(); f
++) {
329 rightmost
= neighbors
.at(f
);
330 rgeo
= rightmost
->geometry();
332 QRect geo
= neighbors
.at(f
)->geometry();
333 if (rgeo
.right() < geo
.right()) {
334 rightmost
= neighbors
.at(f
);
338 neighbors
.at(f
)->addNeighbors(neighbors
);
341 int power
= e
->modifiers()&Qt::ControlModifier
?8:4;
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; }
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
)) {
356 x0
+= rx0
-ox
; y0
+= ry0
-oy
;
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
); }
373 QPoint myPos
= pos();
374 int dx
= newPos
.x()-myPos
.x();
375 int dy
= newPos
.y()-myPos
.y();
376 foreach (FloatingWindow
*w
, neighbors
) {
378 w
->move(p
.x()+dx
, p
.y()+dy
);
382 move(newPos
.x(), newPos
.y());
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;
407 mMousePos
= mInitMousePos
;
408 mInitMousePos
= QPoint(0, 0);
409 mIgnoreMouseClickRelease
= true;
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();
424 void FloatingWindow::restoreFState (QDataStream
&st
) {
425 quint8 ver
; int sz
, x
, y
;
428 if (ver
== 0 && sz
>= (int)(sizeof(int)*2)) {
431 sz
-= (sizeof(int)*2);
435 if (sz
> 0) st
.skipRawData(sz
);
439 ///////////////////////////////////////////////////////////////////////////////
440 FloatWinManager::FloatWinManager (int titleBlinkTO
, int msgBlinkTO
, 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 () {
460 void FloatWinManager::clear () {
461 mMine
.clear(); mBMsgWin
.clear(); mBTitleStop
.clear();
462 foreach (FloatingWindow
*w
, mList
) {
469 void FloatWinManager::startBTitleTimer () {
470 if (!mBTitleTimer
->isActive()) {
471 mBTitleTimer
->start(mBTitleTO
);
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
);
493 void FloatWinManager::stopBMsgTimer () {
494 if (mBMsgTimer
->isActive()) {
501 void FloatWinManager::setMsgBlink (FloatingWindow
*w
, bool doBlink
) {
502 if (mBMsgTO
<= 0 || !w
|| !mMine
.contains(w
)) return;
504 if (mBMsgWin
.contains(w
)) return;
507 w
->mBMsgPhase
= mBMsgPhase
;
509 if (!mBMsgWin
.contains(w
)) return;
511 if (mBMsgWin
.isEmpty()) stopBMsgTimer();
512 w
->mBMsgPhase
= false;
515 w
->update(); // redraw us
519 void FloatWinManager::setTitleBlink (FloatingWindow
*w
, bool doBlink
) {
520 if (mBTitleTO
<= 0 || !w
|| !mMine
.contains(w
)) return;
522 if (mBTitleStop
.contains(w
)) mBTitleStop
.remove(w
);
523 uint to
= QDateTime::currentDateTime().toTime_t()+6;
524 mBTitleStop
.insert(w
, to
);
526 w
->mBTitlePhase
= mBTitlePhase
;
528 if (!mBTitleStop
.contains(w
)) return;
529 mBTitleStop
.remove(w
);
530 if (mBTitleStop
.isEmpty()) stopBTitleTimer();
531 w
->mBTitlePhase
= false;
534 w
->update(); // redraw us
538 void FloatWinManager::bmsgTick () {
539 if (!mBMsgWin
.size()) {
540 // nobody wants to blink anymore
544 mBMsgPhase
= !mBMsgPhase
;
545 // iterate over all windows
546 foreach (FloatingWindow
*w
, mBMsgWin
) {
547 w
->mBMsgPhase
= mBMsgPhase
;
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()) {
561 FloatingWindow
*w
= i
.key();
563 w
->mBTitlePhase
= false;
564 i
= mBTitleStop
.erase(i
);
566 w
->mBTitlePhase
= mBTitlePhase
;
572 if (mBTitleStop
.isEmpty()) {
573 // nobody wants to blink anymore
580 FloatingWindow
*FloatWinManager::change (const QString
&uni
, const QString
&title
, const QString
&tip
, PsycProto::Status status
) {
581 FloatingWindow
*res
= find(uni
);
583 res
->setTitle(title
);
585 res
->setStatus(status
);
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
);
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)));
602 mList
.insert(uni
, res
);
604 change(uni
, title
, tip
, status
);
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 () {
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) {
640 w
->close(); // it will remove itself
645 void FloatWinManager::remove (FloatingWindow
*w
) {
646 if (!w
|| !mMine
.contains(w
)) return;
648 mList
.remove(w
->mUni
);
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
);
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
);
683 void FloatWinManager::newTitle (const QString
&uni
, const QString
&title
) {
684 FloatingWindow
*w
= find(uni
);
690 void FloatWinManager::offlineAll () {
691 foreach (FloatingWindow
*w
, mList
) w
->setStatus(PsycProto::Offline
);