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.
1025 lines
27 KiB
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 <tdepopupmenu.h>
|
|
#include <tqtimer.h>
|
|
#include <tqcursor.h>
|
|
#include <tqvaluelist.h>
|
|
|
|
#include <tdelocale.h>
|
|
#include <tdemessagebox.h>
|
|
#include <tdestartupinfo.h>
|
|
#include <twin.h>
|
|
#include <twinmodule.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, TQString 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 length 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" ), TQObject()
|
|
{
|
|
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, TQT_SIGNAL(activeWindowChanged(WId)),
|
|
this, TQT_SLOT(slotWindowActivate(WId)));
|
|
connect(mWin, TQT_SIGNAL(windowRemoved(WId)),
|
|
this, TQT_SLOT(slotWindowRemove(WId)));
|
|
connect(mWin, TQT_SIGNAL(stackingOrderChanged()),
|
|
this, TQT_SLOT(slotStackingChanged()));
|
|
connect(mWin, TQT_SIGNAL(windowChanged(WId, const unsigned long *)),
|
|
this, TQT_SLOT(slotWindowChange(WId, const unsigned long *)));
|
|
connect(mWin, TQT_SIGNAL(currentDesktopChanged(int)),
|
|
this, TQT_SLOT(slotDesktopChange(int)));
|
|
|
|
mAmor = new AmorWidget();
|
|
connect(mAmor, TQT_SIGNAL(mouseClicked(const TQPoint &)),
|
|
TQT_SLOT(slotMouseClicked(const TQPoint &)));
|
|
connect(mAmor, TQT_SIGNAL(dragged(const TQPoint &, bool)),
|
|
TQT_SLOT(slotWidgetDragged(const TQPoint &, bool)));
|
|
mAmor->resize(mTheme.maximumSize());
|
|
|
|
mTimer = new TQTimer(this);
|
|
connect(mTimer, TQT_SIGNAL(timeout()), TQT_SLOT(slotTimeout()));
|
|
|
|
mStackTimer = new TQTimer(this);
|
|
connect(mStackTimer, TQT_SIGNAL(timeout()), TQT_SLOT(restack()));
|
|
|
|
mBubbleTimer = new TQTimer(this);
|
|
connect(mBubbleTimer, TQT_SIGNAL(timeout()), TQT_SLOT(slotBubbleTimeout()));
|
|
|
|
time(&mActiveTime);
|
|
mCursPos = TQCursor::pos();
|
|
mCursorTimer = new TQTimer(this);
|
|
connect(mCursorTimer, TQT_SIGNAL(timeout()), TQT_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);
|
|
|
|
TDEStartupInfo::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( TQString 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( TQString message )
|
|
{
|
|
showMessage(message, -1);
|
|
}
|
|
|
|
void Amor::showMessage( TQString 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)
|
|
{
|
|
TQStringList files;
|
|
|
|
// Store relative paths into files to avoid storing absolute pathnames.
|
|
TDEGlobal::dirs()->findAllResources("appdata", "*rc", false, false, files);
|
|
int randomTheme = kapp->random() % files.count();
|
|
mConfig.mTheme = (TQString)*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
|
|
TQRect 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 = TQRect(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(tqt_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(tqt_xdisplay(), mAmor->winId(), CWSibling | CWStackMode,
|
|
&values);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// The user clicked on our animation.
|
|
//
|
|
void Amor::slotMouseClicked(const TQPoint &pos)
|
|
{
|
|
bool restartTimer = mTimer->isActive();
|
|
|
|
// Stop the animation while the menu is open.
|
|
if (restartTimer)
|
|
{
|
|
mTimer->stop();
|
|
}
|
|
|
|
if (!mMenu)
|
|
{
|
|
KHelpMenu* help = new KHelpMenu(0, TDEGlobal::instance()->aboutData(), false);
|
|
TDEPopupMenu* helpMnu = help->menu();
|
|
mMenu = new TDEPopupMenu();
|
|
mMenu->insertTitle("Amor"); // I really don't want this i18n'ed
|
|
mMenu->insertItem(SmallIcon("configure"), i18n("&Configure..."), this, TQT_SLOT(slotConfigure()));
|
|
mMenu->insertSeparator();
|
|
mMenu->insertItem(SmallIcon("help"), i18n("&Help"), helpMnu);
|
|
mMenu->insertItem(SmallIcon("exit"), i18n("&Quit"), kapp, TQT_SLOT(quit()));
|
|
}
|
|
|
|
mMenu->exec(pos);
|
|
|
|
if (restartTimer)
|
|
{
|
|
mTimer->start(1000, true);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// Check cursor position
|
|
//
|
|
void Amor::slotCursorTimeout()
|
|
{
|
|
TQPoint currPos = TQCursor::pos();
|
|
TQPoint 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, TQT_SIGNAL(changed()), TQT_SLOT(slotConfigChanged()));
|
|
connect(mAmorDialog, TQT_SIGNAL(offsetChanged(int)),
|
|
TQT_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()
|
|
{
|
|
TQString 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 TQPoint &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
|
|
|
|
TQRect 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.
|
|
TQRect 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, TQT_SIGNAL(saveYourself()), TQT_SLOT(wm_saveyourself()));
|
|
}
|
|
|
|
void AmorSessionWidget::wm_saveyourself()
|
|
{
|
|
// no action required currently.
|
|
}
|
|
|