/* amor.cpp ** ** Copyright (c) 1999 Martin R. Jones ** */ /* ** 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "amor.h" #include "amor.moc" #include "amorpm.h" #include "amorbubble.h" #include "amorwidget.h" #include "amordialog.h" #include "version.h" #include #include // #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("system-log-out"), 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 \n\n") + i18n("Original Author: Martin R. Jones \n") + i18n("Current Maintainer: Gerardo Puga \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. }