You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdetoys/amor/amor.cpp

1025 lines
27 KiB

/* 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.
}