diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | ae2a03c2941bf92573f89b88ef73f8aa842bea0a (patch) | |
tree | 3566563f3fb6ac3cb3496669d8f233062d3091bc /amor/amor.cpp | |
download | tdetoys-ae2a03c2941bf92573f89b88ef73f8aa842bea0a.tar.gz tdetoys-ae2a03c2941bf92573f89b88ef73f8aa842bea0a.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdetoys@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'amor/amor.cpp')
-rw-r--r-- | amor/amor.cpp | 1024 |
1 files changed, 1024 insertions, 0 deletions
diff --git a/amor/amor.cpp b/amor/amor.cpp new file mode 100644 index 0000000..9f98bb3 --- /dev/null +++ b/amor/amor.cpp @@ -0,0 +1,1024 @@ +/* amor.cpp +** +** Copyright (c) 1999 Martin R. Jones <mjones@kde.org> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-devel@kde.org +*/ +#include <stdlib.h> +#include <unistd.h> +#include <time.h> + +#include <kdebug.h> + +#include <kpopupmenu.h> +#include <qtimer.h> +#include <qcursor.h> +#include <qvaluelist.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include <kstartupinfo.h> +#include <kwin.h> +#include <kwinmodule.h> +#include <kstandarddirs.h> +#include <khelpmenu.h> +#include <kiconloader.h> + +#include "amor.h" +#include "amor.moc" +#include "amorpm.h" +#include "amorbubble.h" +#include "amorwidget.h" +#include "amordialog.h" +#include "version.h" +#include <X11/Xlib.h> +#include <kdebug.h> + +// #define DEBUG_AMOR + +#define SLEEP_TIMEOUT 180 // Animation sleeps after SLEEP_TIMEOUT seconds + // of mouse inactivity. +#define TIPS_FILE "tips" // Display tips in TIP_FILE-LANG, e.g "tips-en" +#define TIP_FREQUENCY 20 // Frequency tips are displayed small == more + // often. + +#define BUBBLE_TIME_STEP 250 + +// Standard animation groups +#define ANIM_BASE "Base" +#define ANIM_NORMAL "Sequences" +#define ANIM_FOCUS "Focus" +#define ANIM_BLUR "Blur" +#define ANIM_DESTROY "Destroy" +#define ANIM_SLEEP "Sleep" +#define ANIM_WAKE "Wake" + +//--------------------------------------------------------------------------- +// QueueItem +// Constructor +// + +QueueItem::QueueItem(itemType ty, QString te, int ti) +{ + // if the time field was not given, calculate one based on the type + // and length of the item + int effectiveLength = 0, nesting = 0; + + // discard html code from the lenght count + for (unsigned int i = 0; i < te.length(); i++) + { + if (te[i] == '<') nesting++; + else if (te[i] == '>') nesting--; + else if (!nesting) effectiveLength++; + } + if (nesting) // malformed html + { +#ifdef DEBUG_AMOR + kdDebug(10000) << "QueueItem::QueueItem(): Malformed HTML!" << endl; +#endif + effectiveLength = te.length(); + } + + if (ti == -1) + { + switch (ty) { + case Talk : // shorter times + ti = 1500 + 45 * effectiveLength; + break; + case Tip : // longer times + ti = 4000 + 30 * effectiveLength; + break; + } + } + + iType = ty; + iText = te; + iTime = ti; +} + +//--------------------------------------------------------------------------- +// AMOR +// Constructor +// +Amor::Amor() : DCOPObject( "AmorIface" ), QObject() +{ + mAmor = 0; + mBubble = 0; + mForceHideAmorWidget = false; + if (readConfig()) + { + mTargetWin = 0; + mNextTarget = 0; + mAmorDialog = 0; + mMenu = 0; + mCurrAnim = mBaseAnim; + mPosition = mCurrAnim->hotspot().x(); + mState = Normal; + + mWin = new KWinModule; + connect(mWin, SIGNAL(activeWindowChanged(WId)), + this, SLOT(slotWindowActivate(WId))); + connect(mWin, SIGNAL(windowRemoved(WId)), + this, SLOT(slotWindowRemove(WId))); + connect(mWin, SIGNAL(stackingOrderChanged()), + this, SLOT(slotStackingChanged())); + connect(mWin, SIGNAL(windowChanged(WId, const unsigned long *)), + this, SLOT(slotWindowChange(WId, const unsigned long *))); + connect(mWin, SIGNAL(currentDesktopChanged(int)), + this, SLOT(slotDesktopChange(int))); + + mAmor = new AmorWidget(); + connect(mAmor, SIGNAL(mouseClicked(const QPoint &)), + SLOT(slotMouseClicked(const QPoint &))); + connect(mAmor, SIGNAL(dragged(const QPoint &, bool)), + SLOT(slotWidgetDragged(const QPoint &, bool))); + mAmor->resize(mTheme.maximumSize()); + + mTimer = new QTimer(this); + connect(mTimer, SIGNAL(timeout()), SLOT(slotTimeout())); + + mStackTimer = new QTimer(this); + connect(mStackTimer, SIGNAL(timeout()), SLOT(restack())); + + mBubbleTimer = new QTimer(this); + connect(mBubbleTimer, SIGNAL(timeout()), SLOT(slotBubbleTimeout())); + + time(&mActiveTime); + mCursPos = QCursor::pos(); + mCursorTimer = new QTimer(this); + connect(mCursorTimer, SIGNAL(timeout()), SLOT(slotCursorTimeout())); + mCursorTimer->start( 500 ); + + if (mWin->activeWindow()) + { + mNextTarget = mWin->activeWindow(); + selectAnimation(Focus); + mTimer->start(0, true); + } + if (!connectDCOPSignal(0,0, "KDE_stop_screensaver()", "screenSaverStopped()",false)) + kdDebug(10000) << "Could not attach signal...KDE_stop_screensaver()" << endl; + else + kdDebug(10000) << "attached dcop signals..." << endl; + + if (!connectDCOPSignal(0,0, "KDE_start_screensaver()", "screenSaverStarted()",false)) + kdDebug(10000) << "Could not attach signal...KDE_start_screensaver()" << endl; + else + kdDebug(10000) << "attached dcop signals..." << endl; + + mTipsQueue.setAutoDelete(true); + + KStartupInfo::appStarted(); + } + else + { + kapp->quit(); + } +} + +//--------------------------------------------------------------------------- +// +// Destructor +// +Amor::~Amor() +{ + delete mWin; + delete mAmor; + delete mBubble; +} + +void Amor::screenSaverStopped() +{ +#ifdef DEBUG_AMOR + kdDebug(10000)<<"void Amor::screenSaverStopped() \n"; +#endif + + mAmor->show(); + mForceHideAmorWidget = false; + + mTimer->start(0, true); +} + +void Amor::screenSaverStarted() +{ +#ifdef DEBUG_AMOR + kdDebug(10000)<<"void Amor::screenSaverStarted() \n"; +#endif + + mAmor->hide(); + mTimer->stop(); + mForceHideAmorWidget = true; + + // GP: hide the bubble (if there's any) leaving any current message in the queue + hideBubble(); +} + +//--------------------------------------------------------------------------- +// +void Amor::showTip( QString tip ) +{ + if (mTipsQueue.count() < 5 && !mForceHideAmorWidget) // start dropping tips if the queue is too long + mTipsQueue.enqueue(new QueueItem(QueueItem::Tip, tip)); + + if (mState == Sleeping) + { + selectAnimation(Waking); // Set waking immediatedly + mTimer->start(0, true); + } +} + + +void Amor::showMessage( QString message ) +{ + showMessage(message, -1); +} + +void Amor::showMessage( QString message , int msec ) +{ + // FIXME: What should be done about messages and tips while the screensaver is on? + if (mForceHideAmorWidget) return; // do not show messages sent while in the screensaver + + mTipsQueue.enqueue(new QueueItem(QueueItem::Talk, message, msec)); + + if (mState == Sleeping) + { + selectAnimation(Waking); // Set waking immediatedly + mTimer->start(0, true); + } +} + + +//--------------------------------------------------------------------------- +// +// Clear existing theme and reload configuration +// +void Amor::reset() +{ + hideBubble(); + + mAmor->setPixmap(0L); // get rid of your old copy of the pixmap + + AmorPixmapManager::manager()->reset(); + mTips.reset(); + +// mTipsQueue.clear(); Why had I chosen to clean the tips queue? insane! + + readConfig(); + + mCurrAnim = mBaseAnim; + mPosition = mCurrAnim->hotspot().x(); + mState = Normal; + + mAmor->resize(mTheme.maximumSize()); + mCurrAnim->reset(); + + mTimer->start(0, true); +} + +//--------------------------------------------------------------------------- +// +// Read the selected theme. +// +bool Amor::readConfig() +{ + // Read user preferences + mConfig.read(); + + if (mConfig.mTips) + { + mTips.setFile(TIPS_FILE); + } + + // Select a random theme if user requested it + if (mConfig.mRandomTheme) + { + QStringList files; + + // Store relative paths into files to avoid storing absolute pathnames. + KGlobal::dirs()->findAllResources("appdata", "*rc", false, false, files); + int randomTheme = kapp->random() % files.count(); + mConfig.mTheme = (QString)*files.at(randomTheme); + } + + // read selected theme + if (!mTheme.setTheme(mConfig.mTheme)) + { + KMessageBox::error(0, i18n("Error reading theme: ") + mConfig.mTheme); + return false; + } + + if ( !mTheme.isStatic() ) + { + const char *groups[] = { ANIM_BASE, ANIM_NORMAL, ANIM_FOCUS, ANIM_BLUR, + ANIM_DESTROY, ANIM_SLEEP, ANIM_WAKE, 0 }; + + // Read all the standard animation groups + for (int i = 0; groups[i]; i++) + { + if (mTheme.readGroup(groups[i]) == false) + { + KMessageBox::error(0, i18n("Error reading group: ") + groups[i]); + return false; + } + } + } + else + { + if ( mTheme.readGroup( ANIM_BASE ) == false ) + { + KMessageBox::error(0, i18n("Error reading group: ") + ANIM_BASE); + return false; + } + } + + // Get the base animation + mBaseAnim = mTheme.random(ANIM_BASE); + + return true; +} + +//--------------------------------------------------------------------------- +// +// Show the bubble text +// +void Amor::showBubble() +{ + if (!mTipsQueue.isEmpty()) + { +#ifdef DEBUG_AMOR + kdDebug(10000) << "Amor::showBubble(): Displaying tips bubble." << endl; +#endif + + if (!mBubble) + { + mBubble = new AmorBubble; + } + + mBubble->setOrigin(mAmor->x()+mAmor->width()/2, + mAmor->y()+mAmor->height()/2); + mBubble->setMessage(mTipsQueue.head()->text()); + +// mBubbleTimer->start(mTipsQueue.head()->time(), true); + mBubbleTimer->start(BUBBLE_TIME_STEP, true); + } +} + +//--------------------------------------------------------------------------- +// +// Hide the bubble text if visible +// +void Amor::hideBubble(bool forceDequeue) +{ + if (mBubble) + { +#ifdef DEBUG_AMOR + kdDebug(10000) << "Amor::hideBubble(): Hiding tips bubble" << endl; +#endif + + // GP: stop mBubbleTimer to avoid deleting the first element, just in case we are changing windows + // or something before the tip was shown long enough + mBubbleTimer->stop(); + + // GP: the first message on the queue should be taken off for a + // number of reasons: a) forceDequeue == true, only when called + // from slotBubbleTimeout; b) the bubble is not visible ; c) + // the bubble is visible, but there's Tip being displayed. The + // latter is to keep backwards compatibility and because + // carrying around a tip bubble when switching windows quickly is really + // annoyying + if (forceDequeue || !mBubble->isVisible() || + (mTipsQueue.head()->type() == QueueItem::Tip)) /* there's always an item in the queue here */ + mTipsQueue.dequeue(); + + delete mBubble; + mBubble = 0; + } +} + +//--------------------------------------------------------------------------- +// +// Select a new animation appropriate for the current state. +// +void Amor::selectAnimation(State state) +{ + switch (state) + { + case Blur: + hideBubble(); + mCurrAnim = mTheme.random(ANIM_BLUR); + mState = Focus; + break; + + case Focus: + hideBubble(); + mCurrAnim = mTheme.random(ANIM_FOCUS); + mCurrAnim->reset(); + mTargetWin = mNextTarget; + if (mTargetWin != None) + { + mTargetRect = KWin::windowInfo(mTargetWin).frameGeometry(); + + // if the animation falls outside of the working area, + // then relocate it so that is inside the desktop again + QRect desktopArea = mWin->workArea(); + mInDesktopBottom = false; + + if (mTargetRect.y() - mCurrAnim->hotspot().y() + mConfig.mOffset < + desktopArea.y()) + { + // relocate the animation at the bottom of the screen + mTargetRect = QRect(desktopArea.x(), + desktopArea.y() + desktopArea.height(), + desktopArea.width(), 0); + + // we'll relocate the animation in the desktop + // frame, so do not add the offset to its vertical position + mInDesktopBottom = true; + } + + if ( mTheme.isStatic() ) + { + if ( mConfig.mStaticPos < 0 ) + mPosition = mTargetRect.width() + mConfig.mStaticPos; + else + mPosition = mConfig.mStaticPos; + if ( mPosition >= mTargetRect.width() ) + mPosition = mTargetRect.width()-1; + else if ( mPosition < 0 ) + mPosition = 0; + } + else + { + if (mCurrAnim->frame()) + { + if (mTargetRect.width() == mCurrAnim->frame()->width()) + mPosition = mCurrAnim->hotspot().x(); + else + mPosition = ( kapp->random() % + (mTargetRect.width() - mCurrAnim->frame()->width()) ) + + mCurrAnim->hotspot().x(); + } + else + { + mPosition = mTargetRect.width()/2; + } + } + } + else + { + // We don't want to do anything until a window comes into + // focus. + mTimer->stop(); + } + mAmor->hide(); + + restack(); + mState = Normal; + break; + + case Destroy: + hideBubble(); + mCurrAnim = mTheme.random(ANIM_DESTROY); + mState = Focus; + break; + + case Sleeping: + mCurrAnim = mTheme.random(ANIM_SLEEP); + break; + + case Waking: + mCurrAnim = mTheme.random(ANIM_WAKE); + mState = Normal; + break; + + default: + // Select a random normal animation if the current animation + // is not the base, otherwise select the base. This makes us + // alternate between the base animation and a random + // animination. + if (mCurrAnim == mBaseAnim && !mBubble) + { + mCurrAnim = mTheme.random(ANIM_NORMAL); + } + else + { + mCurrAnim = mBaseAnim; + } + break; + } + + if (mCurrAnim->totalMovement() + mPosition > mTargetRect.width() || + mCurrAnim->totalMovement() + mPosition < 0) + { + // The selected animation would end outside of this window's width + // We could randomly select a different one, but I prefer to just + // use the default animation. + mCurrAnim = mBaseAnim; + } + + mCurrAnim->reset(); +} + +//--------------------------------------------------------------------------- +// +// Set the animation's stacking order to be just above the target window's +// window decoration, or on top. +// +void Amor::restack() +{ + if (mTargetWin == None) + { + return; + } + + if (mConfig.mOnTop) + { + // simply raise the widget to the top + mAmor->raise(); + return; + } + +#ifdef DEBUG_AMOR + kdDebug(10000) << "restacking" << endl; +#endif + + Window sibling = mTargetWin; + Window dw, parent = None, *wins; + + do { + unsigned int nwins = 0; + + // We must use the target window's parent as our sibling. + // Is there a faster way to get parent window than XQueryTree? + if (XQueryTree(qt_xdisplay(), sibling, &dw, &parent, &wins, &nwins)) + { + if (nwins) + { + XFree(wins); + } + } + + if (parent != None && parent != dw ) + sibling = parent; + } while ( parent != None && parent != dw ); + + // Set animation's stacking order to be above the window manager's + // decoration of target window. + XWindowChanges values; + values.sibling = sibling; + values.stack_mode = Above; + XConfigureWindow(qt_xdisplay(), mAmor->winId(), CWSibling | CWStackMode, + &values); +} + +//--------------------------------------------------------------------------- +// +// The user clicked on our animation. +// +void Amor::slotMouseClicked(const QPoint &pos) +{ + bool restartTimer = mTimer->isActive(); + + // Stop the animation while the menu is open. + if (restartTimer) + { + mTimer->stop(); + } + + if (!mMenu) + { + KHelpMenu* help = new KHelpMenu(0, KGlobal::instance()->aboutData(), false); + KPopupMenu* helpMnu = help->menu(); + mMenu = new KPopupMenu(); + mMenu->insertTitle("Amor"); // I really don't want this i18n'ed + mMenu->insertItem(SmallIcon("configure"), i18n("&Configure..."), this, SLOT(slotConfigure())); + mMenu->insertSeparator(); + mMenu->insertItem(SmallIcon("help"), i18n("&Help"), helpMnu); + mMenu->insertItem(SmallIcon("exit"), i18n("&Quit"), kapp, SLOT(quit())); + } + + mMenu->exec(pos); + + if (restartTimer) + { + mTimer->start(1000, true); + } +} + +//--------------------------------------------------------------------------- +// +// Check cursor position +// +void Amor::slotCursorTimeout() +{ + QPoint currPos = QCursor::pos(); + QPoint diff = currPos - mCursPos; + time_t now = time(0); + + if (mForceHideAmorWidget) return; // we're hidden, do nothing + + if (abs(diff.x()) > 1 || abs(diff.y()) > 1) + { + if (mState == Sleeping) + { + // Set waking immediatedly + selectAnimation(Waking); + } + mActiveTime = now; + mCursPos = currPos; + } + else if (mState != Sleeping && now - mActiveTime > SLEEP_TIMEOUT) + { + // GP: can't go to sleep if there are tips in the queue + if (mTipsQueue.isEmpty()) + mState = Sleeping; // The next animation will become sleeping + } +} + +//--------------------------------------------------------------------------- +// +// Display the next frame or a new animation +// +void Amor::slotTimeout() +{ + if ( mForceHideAmorWidget ) + return; + + if (!mTheme.isStatic()) + mPosition += mCurrAnim->movement(); + mAmor->setPixmap(mCurrAnim->frame()); + mAmor->move(mTargetRect.x() + mPosition - mCurrAnim->hotspot().x(), + mTargetRect.y() - mCurrAnim->hotspot().y() + (!mInDesktopBottom?mConfig.mOffset:0)); + if (!mAmor->isVisible()) + { + mAmor->show(); + restack(); + } + + if (mCurrAnim == mBaseAnim && mCurrAnim->validFrame()) + { + // GP: Application tips/messages can be shown in any frame number; amor tips are + // only displayed on the first frame of mBaseAnim (the old way of doing this). + if ( !mTipsQueue.isEmpty() && !mBubble && mConfig.mAppTips) + showBubble(); + else if (kapp->random()%TIP_FREQUENCY == 1 && mConfig.mTips && !mBubble && !mCurrAnim->frameNum()) + { + mTipsQueue.enqueue(new QueueItem(QueueItem::Tip, mTips.tip())); + showBubble(); + } + } + + if (mTheme.isStatic()) + mTimer->start((mState == Normal) || (mState == Sleeping) ? 1000 : 100, true); + else + mTimer->start(mCurrAnim->delay(), true); + + if (!mCurrAnim->next()) + { + if ( mBubble ) + mCurrAnim->reset(); + else + selectAnimation(mState); + } +} + +//--------------------------------------------------------------------------- +// +// Display configuration dialog +// +void Amor::slotConfigure() +{ + if (!mAmorDialog) + { + mAmorDialog = new AmorDialog(); + connect(mAmorDialog, SIGNAL(changed()), SLOT(slotConfigChanged())); + connect(mAmorDialog, SIGNAL(offsetChanged(int)), + SLOT(slotOffsetChanged(int))); + } + + mAmorDialog->show(); +} + +//-------------------------------------------------------------------------- +// +// Configuration changed. +// +void Amor::slotConfigChanged() +{ + reset(); +} + +//--------------------------------------------------------------------------- +// +// Offset changed +// +void Amor::slotOffsetChanged(int off) +{ + mConfig.mOffset = off; + + if (mCurrAnim->frame()) + { + mAmor->move(mPosition + mTargetRect.x() - mCurrAnim->hotspot().x(), + mTargetRect.y() - mCurrAnim->hotspot().y() + (!mInDesktopBottom?mConfig.mOffset:0)); + } +} + +//--------------------------------------------------------------------------- +// +// Display About box +// +void Amor::slotAbout() +{ + QString about = i18n("Amor Version %1\n\n").arg(AMOR_VERSION) + + i18n("Amusing Misuse Of Resources\n\n") + + i18n("Copyright (c) 1999 Martin R. Jones <mjones@kde.org>\n\n") + + i18n("Original Author: Martin R. Jones <mjones@kde.org>\n") + + i18n("Current Maintainer: Gerardo Puga <gpuga@gioia.ing.unlp.edu.ar>\n" ) + + "\nhttp://www.powerup.com.au/~mjones/amor/"; + KMessageBox::about(0, about, i18n("About Amor")); +} + +//--------------------------------------------------------------------------- +// +// Widget dragged +// +void Amor::slotWidgetDragged( const QPoint &delta, bool release ) +{ + if (mCurrAnim->frame()) + { + int newPosition = mPosition + delta.x(); + if (mCurrAnim->totalMovement() + newPosition > mTargetRect.width()) + newPosition = mTargetRect.width() - mCurrAnim->totalMovement(); + else if (mCurrAnim->totalMovement() + newPosition < 0) + newPosition = -mCurrAnim->totalMovement(); + mPosition = newPosition; + mAmor->move(mTargetRect.x() + mPosition - mCurrAnim->hotspot().x(), + mAmor->y()); + + if ( mTheme.isStatic() && release ) { + // static animations save the new position as preferred. + int savePos = mPosition; + if ( savePos > mTargetRect.width()/2 ) + savePos -= (mTargetRect.width()+1); + + mConfig.mStaticPos = savePos; + mConfig.write(); + } + } +} + +//--------------------------------------------------------------------------- +// +// Focus changed to a different window +// +void Amor::slotWindowActivate(WId win) +{ +#ifdef DEBUG_AMOR + kdDebug(10000) << "Window activated:" << win << endl; +#endif + + mTimer->stop(); + mNextTarget = win; + + // This is an active event that affects the target window + time(&mActiveTime); + + // A window gaining focus implies that the current window has lost + // focus. Initiate a blur event if there is a current active window. + if (mTargetWin) + { + // We are losing focus from the current window + selectAnimation(Blur); + mTimer->start(0, true); + } + else if (mNextTarget) + { + // We are setting focus to a new window + if (mState != Focus ) + selectAnimation(Focus); + mTimer->start(0, true); + } + else + { + // No action - We can get this when we switch between two empty + // desktops + mAmor->hide(); + } +} + +//--------------------------------------------------------------------------- +// +// Window removed +// +void Amor::slotWindowRemove(WId win) +{ +#ifdef DEBUG_AMOR + kdDebug(10000) << "Window removed" << endl; +#endif + + if (win == mTargetWin) + { + // This is an active event that affects the target window + time(&mActiveTime); + + selectAnimation(Destroy); + mTimer->stop(); + mTimer->start(0, true); + } +} + +//--------------------------------------------------------------------------- +// +// Window stacking changed +// +void Amor::slotStackingChanged() +{ +#ifdef DEBUG_AMOR + kdDebug(10000) << "Stacking changed" << endl; +#endif + + // This is an active event that affects the target window + time(&mActiveTime); + + // We seem to get this signal before the window has been restacked, + // so we just schedule a restack. + mStackTimer->start( 20, TRUE ); +} + +//--------------------------------------------------------------------------- +// +// Properties of a window changed +// +void Amor::slotWindowChange(WId win, const unsigned long * properties) +{ + + if (win != mTargetWin) + { + return; + } + + // This is an active event that affects the target window + time(&mActiveTime); + + KWin::Info info = KWin::info( mTargetWin ); + + if (info.isIconified() || + info.mappingState == NET::Withdrawn) + { +#ifdef DEBUG_AMOR + kdDebug(10000) << "Target window iconified" << endl; +#endif + + // The target window has been iconified + selectAnimation(Destroy); + mTargetWin = None; + mTimer->stop(); + mTimer->start(0, true); + + return; + } + + if (properties[0] & NET::WMGeometry) + { +#ifdef DEBUG_AMOR + kdDebug(10000) << "Target window moved or resized" << endl; +#endif + + QRect newTargetRect = KWin::windowInfo(mTargetWin).frameGeometry(); + + // if the change in the window caused the animation to fall + // out of the working area of the desktop, or if the animation + // didn't fall in the working area before but it does now, then + // refocus on the current window so that the animation is + // relocated. + QRect desktopArea = mWin->workArea(); + + bool fitsInWorkArea = !(newTargetRect.y() - mCurrAnim->hotspot().y() + mConfig.mOffset < desktopArea.y()); + if ((!fitsInWorkArea && !mInDesktopBottom) || (fitsInWorkArea && mInDesktopBottom)) + { + mNextTarget = mTargetWin; + selectAnimation(Blur); + mTimer->start(0, true); + + return; + } + + if (!mInDesktopBottom) + mTargetRect = newTargetRect; + + // make sure the animation is still on the window. + if (mCurrAnim->frame()) + { + hideBubble(); + if (mTheme.isStatic()) + { + if ( mConfig.mStaticPos < 0 ) + mPosition = mTargetRect.width() + mConfig.mStaticPos; + else + mPosition = mConfig.mStaticPos; + if ( mPosition >= mTargetRect.width() ) + mPosition = mTargetRect.width()-1; + else if ( mPosition < 0 ) + mPosition = 0; + } + else if (mPosition > mTargetRect.width() - + (mCurrAnim->frame()->width() - mCurrAnim->hotspot().x())) + { + mPosition = mTargetRect.width() - (mCurrAnim->frame()->width() - mCurrAnim->hotspot().x()); + } + mAmor->move(mTargetRect.x() + mPosition - mCurrAnim->hotspot().x(), + mTargetRect.y() - mCurrAnim->hotspot().y() + (!mInDesktopBottom?mConfig.mOffset:0)); + } + + return; + } +} + +//--------------------------------------------------------------------------- +// +// Changed to a different desktop +// +void Amor::slotDesktopChange(int desktop) +{ + // GP: signal currentDesktopChanged seems to be emitted even if you + // change to the very same desktop you are in. + if (mWin->currentDesktop() == desktop) + return; + +#ifdef DEBUG_AMOR + kdDebug(10000) << "Desktop change" << endl; +#endif + + mNextTarget = None; + mTargetWin = None; + selectAnimation( Normal ); + mTimer->stop(); + mAmor->hide(); +} + +// GP =========================================================================== + +void Amor::slotBubbleTimeout() +{ + // has the queue item been displayed for long enough? + QueueItem *first = mTipsQueue.head(); +#ifdef DEBUG_AMOR + if (!first) kdDebug(10000) << "Amor::slotBubbleTimeout(): empty queue!" << endl; +#endif + if ((first->time() > BUBBLE_TIME_STEP) && (mBubble->isVisible())) + { + first->setTime(first->time() - BUBBLE_TIME_STEP); + mBubbleTimer->start(BUBBLE_TIME_STEP, true); + return; + } + + // do not do anything if the mouse pointer is in the bubble + if (mBubble->mouseWithin()) + { + first->setTime(500); // show this item for another 500ms + mBubbleTimer->start(BUBBLE_TIME_STEP, true); + return; + } + + // are there any other tips pending? + if (mTipsQueue.count() > 1) + { + mTipsQueue.dequeue(); + showBubble(); // shows the next item in the queue + } else + hideBubble(true); // hideBubble calls dequeue() for itself. +} + +//=========================================================================== + +AmorSessionWidget::AmorSessionWidget() +{ + // the only function of this widget is to catch & forward the + // saveYourself() signal from the session manager + connect(kapp, SIGNAL(saveYourself()), SLOT(wm_saveyourself())); +} + +void AmorSessionWidget::wm_saveyourself() +{ + // no action required currently. +} + |