/*************************************************************************** notificationmanager.cpp - manage the notifications stack and updates them as needed ------------------- begin : Thursday April 19 2007 copyright : (C) 2007 by Valerio Pilo email : valerio@kmess.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. * * * ***************************************************************************/ #include "notificationmanager.h" #include "../contact/contactbase.h" #include "../utils/xautolock.h" #include "../currentaccount.h" #include "../kmessdebug.h" #include <QSystemTrayIcon> #include <QWidget> #include <KLocale> #ifndef Q_WS_MAC #include <KNotification> #else #include "macnotification.h" #endif #include <KWindowSystem> #ifdef Q_WS_WIN #include <windows.h> #endif // Initialize the instance to zero NotificationManager* NotificationManager::instance_(0); // Class constructor NotificationManager::NotificationManager() { // Initialize the interfaces to screen saver and fullscreen apps checks autoLock_ = new XAutoLock(); } // Class destructor NotificationManager::~NotificationManager() { #ifndef Q_WS_MAC foreach( KNotification *notification, events_.keys() ) { notification->close(); } events_.clear(); eventSettings_.clear(); #endif delete autoLock_; } // Associate a KNotification action with an item of the Buttons bitfield NotificationManager::Buttons NotificationManager::getButtonFromAction( int buttons, int action ) { int number = 1; // It's important to keep the synchronization and the same order for the buttons, // both here and in getButtonsLabels(): KNotification just uses the index for each action if( ( buttons & BUTTON_OPEN_CHAT ) && number++ == action ) { return BUTTON_OPEN_CHAT; } if( ( buttons & BUTTON_OPEN_OFFLINE_CHAT ) && number++ == action ) { return BUTTON_OPEN_OFFLINE_CHAT; } if( ( buttons & BUTTON_VIEW_MESSAGE ) && number++ == action ) { return BUTTON_VIEW_MESSAGE; } if( ( buttons & BUTTON_MORE_DETAILS ) && number++ == action ) { return BUTTON_MORE_DETAILS; } if( ( buttons & BUTTON_OPEN_MAILBOX ) && number++ == action ) { return BUTTON_OPEN_MAILBOX; } if( ( buttons & BUTTON_HIDE ) && number++ == action ) { return BUTTON_HIDE; } return BUTTON_INVALID; } // Associate a Buttons bitfield with button actions QStringList NotificationManager::getButtonsLabels( int buttons ) { QStringList labels; // It's important to keep the synchronization and the same order for the buttons, // both here and in getButtonFromAction(): KNotification just uses the index for each action if( buttons & BUTTON_OPEN_CHAT ) { labels << i18nc( "Button text for KDE notification boxes", "Start Chatting" ); } if( buttons & BUTTON_OPEN_OFFLINE_CHAT ) { labels << i18nc( "Button text for KDE notification boxes", "Leave a Message" ); } if( buttons & BUTTON_VIEW_MESSAGE ) { labels << i18nc( "Button text for KDE notification boxes", "View Message" ); } if( buttons & BUTTON_MORE_DETAILS ) { labels << i18nc( "Button text for KDE notification boxes", "Details" ); } if( buttons & BUTTON_OPEN_MAILBOX ) { labels << i18nc( "Button text for KDE notification boxes", "Read Email" ); } if( buttons & BUTTON_HIDE ) { labels << i18nc( "Button text for KDE notification boxes", "Hide" ); } return labels; } // Return the current tray object QSystemTrayIcon *NotificationManager::getTrayObject() { return trayObject_; } // Insert a new notification in the stack if needed, or update an existing one void NotificationManager::notify( const QString &event, const QString &text, EventSettings settings ) { // Check if the screen saver is running if( autoLock_->isScreenSaverActive() ) { #ifdef KMESSDEBUG_NOTIFICATIONMANAGER kDebug() << "Screen saver/locker is active, not showing notifications."; #endif return; } CurrentAccount *currentAccount = CurrentAccount::instance(); // Do nothing if, when busy, the user doesn't want to be disturbed if( currentAccount->getStatus() == STATUS_BUSY && currentAccount->getHideNotificationsWhenBusy() ) { #ifdef KMESSDEBUG_NOTIFICATIONMANAGER kDebug() << "User is busy, not showing notifications."; #endif return; } // On Mac, notification emitting goes via the MacNotifications class #ifdef Q_WS_MAC MacNotification::notify( event, text, settings ); #else bool isFullscreen = false; #ifdef Q_WS_X11 // Check if a full screen application is running by querying the window manager and asking // the state of the currently active window KWindowInfo info = KWindowSystem::windowInfo( KWindowSystem::activeWindow(), NET::WMState | NET::FullScreen ); isFullscreen = ( info.valid() && info.hasState( NET::FullScreen ) ); #else #ifdef Q_WS_WIN // Windows version HWND hwnd = GetForegroundWindow(); RECT rcWindow; GetWindowRect( hwnd, &rcWindow ); int screenWidth = GetSystemMetrics( SM_CXSCREEN ); int screenHeight = GetSystemMetrics( SM_CYSCREEN ); int windowWidth = ( rcWindow.right - rcWindow.left ); int windowHeight = ( rcWindow.bottom - rcWindow.top ); isFullscreen = ( screenWidth == windowWidth && screenHeight == windowHeight ); #else #warning Full screen application detection is not implemented for this platform yet. #endif #endif if( isFullscreen ) { kDebug() << "Active window is full screen, not showing notifications."; return; } #ifdef KMESSDEBUG_NOTIFICATIONMANAGER #ifdef Q_WS_X11 kDebug() << "Active window was valid?" << info.valid() <<". If so, it is not fullscreen, showing notifications."; #endif #endif KNotification *notification; // An event already exists for this contact, update it notification = events_.key( settings.contact, (KNotification*)0 ); if( notification ) { #ifdef KMESSDEBUG_NOTIFICATIONMANAGER kDebug() << "Updating existing notification" << notification; #endif notification->setText ( text ); notification->setActions( getButtonsLabels( settings.buttons ) ); // We can't update the event ID, crap. if( settings.contact != 0 ) { notification->setPixmap( QPixmap( settings.contact->getContactPicturePath() ).scaled( 96, 96 ) ); } eventSettings_[ notification ] = settings; notification->update(); return; } // We have to send a new event // Create the notification notification = new KNotification( event ); #ifdef KMESSDEBUG_NOTIFICATIONMANAGER kDebug() << "Showing notification" << notification << "for event" << event; #endif // Add it to our lists to be able to find it later events_ .insert( notification, settings.contact ); eventSettings_.insert( notification, settings ); // Set it up notification->setWidget( settings.widget ); notification->setText ( text ); notification->setActions( getButtonsLabels( settings.buttons ) ); notification->setFlags ( KNotification::CloseOnTimeout | KNotification::CloseWhenWidgetActivated ); if( settings.contact != 0 ) { notification->setPixmap( QPixmap( settings.contact->getContactPicturePath() ).scaled( 96, 96 ) ); } // The slots will call the appropriate *Notification class connect( notification, SIGNAL( activated(unsigned int) ), this, SLOT ( relayActivation(unsigned int) ) ); connect( notification, SIGNAL( destroyed(QObject*) ), this, SLOT ( remove(QObject*) ) ); notification->sendEvent(); #endif /* !Q_WS_MAC */ } // Return a singleton instance of the current account NotificationManager* NotificationManager::instance() { // If the instance is null, create a new manager and return that. if ( instance_ == 0 ) { instance_ = new NotificationManager(); } return instance_; } // Forward an activation action to the class which will manage it void NotificationManager::relayActivation( unsigned int action ) { #ifdef Q_WS_MAC #warning TODO! #else KNotification *notification = static_cast<KNotification*>( sender() ); if( ! notification || ! eventSettings_.contains( notification ) ) { kWarning() << "Cannot relay notification activation, pointer" << notification << "not found!"; return; } #ifdef KMESSDEBUG_NOTIFICATIONMANAGER kDebug() << "Relaying activation for notification" << notification; #endif EventSettings settings = eventSettings_[ notification ]; Buttons clickedButton = getButtonFromAction( settings.buttons, action ); emit eventActivated( settings, clickedButton ); #endif /* !Q_WS_MAC */ } // Delete an expired notification void NotificationManager::remove( QObject *object ) { #ifdef Q_WS_MAC // This doesn't need to happen on Mac #else KNotification *notification = static_cast<KNotification*>( object ); if( notification == 0 ) { #ifdef KMESSDEBUG_NOTIFICATIONMANAGER kDebug() << "Cannot remove notification, pointer" << notification << "not found!"; #endif return; } // Don't delete the notification, it will do it automatically events_.remove( notification ); eventSettings_.remove( notification ); #ifdef KMESSDEBUG_NOTIFICATIONMANAGER kDebug() << "Deleted notification" << notification << "from the stack"; #endif #endif /* !Q_WS_MAC */ } // Set the tray object that will be used void NotificationManager::setTrayObject( QSystemTrayIcon *trayObject ) { trayObject_ = trayObject; } #include "notificationmanager.moc"