/*************************************************************************** initialview.cpp - description ------------------- begin : Sun Jan 12 2003 copyright : (C) 2003 by Mike K. Bennett email : mkb137b@hotmail.com ***************************************************************************/ /*************************************************************************** * * * 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 "initialview.h" #include "network/msnconnection.h" // Required for the offline socket #define #include "utils/kmessconfig.h" #include "utils/kmessshared.h" #include "accountsmanager.h" #include "account.h" #include "kmessdebug.h" #include <QTimer> #include <QWheelEvent> #include <kdeversion.h> #include <KIconEffect> #include <KIconLoader> #include <KLocale> #include <KStandardDirs> /** * Reconnection timeout * * This number is the time, expressed in seconds, after which a connection is attempted again */ #ifndef RECONNECTION_TIME #define RECONNECTION_TIME 30 #endif // The constructor InitialView::InitialView( QWidget *parent ) : QWidget( parent ) , Ui::InitialView() , isConnectingUI_( false ) , networkStatus_( Solid::Networking::Connected ) , reconnectionRemainingSeconds_( 30 ) , triedConnecting_( false ) { // Set up the UI first setupUi( this ); // Set the username box with the last used email address config_ = KMessConfig::instance()->getGlobalConfig( "InitialView" ); lastUsedHandle_ = config_.readEntry( "defaultHandle", "me@hotmail.com" ); // Enable auto completion handleCombobox_->setCompletionMode( KGlobalSettings::CompletionPopup ); // Select an image to load into the main screen.. mmmm, eggs! QString imageName; QDate currentDate = QDate::currentDate(); if( currentDate.month() == 12 && currentDate.day() > 15 ) { imageName = "kmessstrip2"; } else { imageName = "kmessstrip1"; } // Load the chosen icon loader_ = KIconLoader::global(); QPixmap icon = loader_->loadIcon( imageName, KIconLoader::User ); kmessLogo_->setPixmap( icon ); kmessLogo_->setMinimumSize( icon.width(), icon.height() ); // Add the items to the initial status combo box QStringList states; states << i18n("Online") << i18n("Away") << i18n("Be Right Back") << i18n("Busy") << i18n("Out to Lunch") << i18n("On the Phone") << i18n("Invisible"); initialStatus_->insertItems( 0, states ); // Load the accounts list into our combo box const QList<Account*> accountsList = AccountsManager::instance()->getAccounts(); foreach( Account *account, accountsList ) { addAccount( account ); } // Connect the remaining signals connect( handleCombobox_, SIGNAL( editTextChanged(const QString&) ), this, SLOT ( updateView() ) ); connect( connectButton_, SIGNAL( clicked() ), this, SLOT ( slotConnectClicked() ) ); connect( rememberAccountCheckBox_, SIGNAL( stateChanged(int) ), this, SLOT ( rememberAccountStateChanged(int) ) ); connect( forgottenPasswordLabel_, SIGNAL( leftClickedUrl(const QString&) ), this, SLOT ( slotClickedUrl(const QString&) ) ); connect( newAccountLabel_, SIGNAL( leftClickedUrl(const QString&) ), this, SLOT ( slotClickedUrl(const QString&) ) ); connect( statusLabel_, SIGNAL( linkActivated(const QString&) ), this, SLOT ( slotClickedUrl(const QString&) ) ); connect( rememberPasswordCheckBox_, SIGNAL( stateChanged(int) ), this, SLOT (rememberPasswordStateChanged(int) ) ); connect( rememberAutoLoginCheckBox_, SIGNAL( stateChanged(int) ), this, SLOT ( autoLoginStateChanged(int) ) ); // Enable tooltips for the two URL labels (they're disabled by default) newAccountLabel_->setUseTips( true ); forgottenPasswordLabel_->setUseTips( true ); // When an account is edited, reflect the changes in the accounts list connect( AccountsManager::instance(), SIGNAL( accountChanged(Account*,QString,QString) ), this, SLOT ( updateView() ) ); // When (if!) the passwords have been read from the user's password wallet, show them connect( AccountsManager::instance(), SIGNAL( passwordsReady() ), this, SLOT ( updateView() ) ); // Use Solid to find out whether we're connected or not, and apply the status immediately connect( Solid::Networking::notifier(), SIGNAL( statusChanged(Solid::Networking::Status) ), this, SLOT ( slotConnectionStatusChanged(Solid::Networking::Status) ) ); // Set the initial status of the widgets reset(); // Add an event filter to create a quick account chooser pictureLabel_->installEventFilter( this ); installEventFilter( this ); // Set up the timers reconnectionTimer_.setInterval( 1000 ); // Fire every second connect( &reconnectionTimer_, SIGNAL( timeout() ), this, SLOT ( slotReconnectTimerEvent() ) ); statusMessageTimer_.setSingleShot( true ); connect( &statusMessageTimer_, SIGNAL( timeout() ), this, SLOT ( statusMessage() ) ); } // The destructor InitialView::~InitialView() { #ifdef KMESSDEBUG_INITIALVIEW kDebug() << "DESTROYED."; #endif } // Add an account to the list of displayed accounts from which the user can choose void InitialView::addAccount(Account *account) { const QString &handle = account->getHandle(); // Add account to internal list accounts_.insert( handle, account ); // Add an entry for the account to the dropdown list handleCombobox_->addItem( handle ); handleCombobox_->completionObject()->addItem( handle ); // Select the account in the combobox if it's the default account. // Don't update the view if we're currently connecting; when connecting // to an account for the first time and it is not set as a guest account, // the account is added bringing code flow here, and updateView() will // clear the password field because no password is saved yet. We don't // want that. if( !isConnectingUI_ && handle == lastUsedHandle_ ) { handleCombobox_->setCurrentItem( lastUsedHandle_ ); updateView(); } } //The "auto connect" checkbox changed its state void InitialView::autoLoginStateChanged( int state ) { if( state == Qt::Checked ) { rememberPasswordCheckBox_->setCheckState( Qt::Checked ); } } // Modify an account in the list of displayed accounts from which the user can choose void InitialView::changedAccount( QString oldName, QString newName ) { if( accounts_[ oldName ] == 0 ) { return; } Account *account = accounts_[ oldName ]; accounts_.remove( oldName ); accounts_.insert( newName, account ); // Change completion box entry handleCombobox_->completionObject()->removeItem( oldName ); handleCombobox_->completionObject()->addItem( newName ); // Remove account from qcombobox bool foundItem = false; for( int i = 0; i < handleCombobox_->count(); i++ ) { if( handleCombobox_->itemText(i).toLower() == oldName.toLower() ) { handleCombobox_->removeItem(i); foundItem = true; break; } } handleCombobox_->addItem( newName ); if( ! foundItem ) { kWarning() << "Account not found in pulldown list!"; } } /** * @brief Automatically reconnect with a specified account [slot]. * * This method starts reconnecting to an account * * @param handle The email address of the account to reconnect * @param connectImmediately Whether to wait for a little time or not before (re)connecting. * Useful when reconnecting. */ 00247 void InitialView::reconnect( QString handle, bool connectImmediately ) { #ifdef KMESSDEBUG_INITIALVIEW kDebug() << "Reconnecting account" << handle; #endif Account *account = accounts_[ handle ]; if( account == 0 ) { #ifdef KMESSDEBUG_INITIALVIEW kDebug() << "Account not found, aborting."; #endif statusMessage( i18nc( "Status message on login screen", "Cannot reconnect: account not found" ), 10000 ); return; } // If the account has not a saved password, we can't reconnect if( account->getPassword().isEmpty() || ! account->getSavePassword() ) { #ifdef KMESSDEBUG_INITIALVIEW kDebug() << "Account has no password, aborting."; #endif statusMessage( i18nc( "Status message on login screen", "Cannot reconnect: this account has no saved password" ), 10000 ); return; } // Save the account which we will connect to, later on reconnectionHandle_ = handle; triedConnecting_ = true; switch( networkStatus_ ) { // We're connected to the network, try connecting now case Solid::Networking::Connected: #ifdef KMESSDEBUG_INITIALVIEW kDebug() << "Network status: Connected. Scheduling reconnection."; #endif if( connectImmediately ) { reconnectionRemainingSeconds_ = 0; } else { reconnectionRemainingSeconds_ = 5; } break; // The network connection is down, wait for it to come up again case Solid::Networking::Unconnected: case Solid::Networking::Connecting: #ifdef KMESSDEBUG_INITIALVIEW kDebug() << "Network status: Unconnected/connecting. Waiting for a connection."; #endif setEnabled( false ); statusMessage( i18nc( "Status message on login screen", "Waiting for an Internet connection to reconnect...<br /><a href='%1'>Reconnect now!</a>", "kmess://reconnect/" ) ); return; // The connection is being brought down, wait for it case Solid::Networking::Disconnecting: #ifdef KMESSDEBUG_INITIALVIEW kDebug() << "Network status: disconnecting. Waiting a moment for reconnection."; #endif reconnectionRemainingSeconds_ = 30; return; // The network connection status is not known, wait a while before reconnecting case Solid::Networking::Unknown: #ifdef KMESSDEBUG_INITIALVIEW kDebug() << "Network status: Unknown. Scheduling reconnection."; #endif // When the user starts kmess and the connection status is unknown, connect immediately. if( connectImmediately ) { reconnectionRemainingSeconds_ = 0; } else { reconnectionRemainingSeconds_ = 30; } break; } // Start the reconnection delay timer setEnabled( false ); reconnectionTimer_.start(); slotReconnectTimerEvent(); } /** * @brief Update the view when the network connection status changes * * This method receives KDE's Solid network connection status changes, and updates the view's widgets * to reflect it. * * @param newStatus The new status of the network connection */ 00358 void InitialView::slotConnectionStatusChanged( Solid::Networking::Status newStatus ) { networkStatus_ = newStatus; #ifdef KMESSDEBUG_INITIALVIEW QString status; switch( networkStatus_ ) { case Solid::Networking::Unknown: status = "Unknown"; break; case Solid::Networking::Unconnected: status = "Unconnected"; break; case Solid::Networking::Disconnecting: status = "Disconnecting"; break; case Solid::Networking::Connecting: status = "Connecting"; break; case Solid::Networking::Connected: status = "Connected"; break; default: status = "Other (not known)"; break; } kDebug() << "The network connection status has changed to:" << status << "."; #endif // If Solid is unable to retrieve the status, assume we're connected if( networkStatus_ == Solid::Networking::Connected || networkStatus_ == Solid::Networking::Unknown ) { if( ! triedConnecting_ ) { #ifdef KMESSDEBUG_INITIALVIEW kDebug() << "no connection attempt active, do nothing."; #endif statusMessage(); setEnabled( true ); } else { #ifdef KMESSDEBUG_INITIALVIEW kDebug() << "A connection attempt was in progress, continuing..."; #endif if( ! reconnectionHandle_.isEmpty() ) { #ifdef KMESSDEBUG_INITIALVIEW kDebug() << "Reconnection asked, scheduling."; #endif reconnect( reconnectionHandle_ ); } else { statusMessage(); setEnabled( true ); } } } else { // Solid connection is not up. // Reset the dialog, keep the "isConnecting" status setEnabled( ! triedConnecting_ ); if( triedConnecting_ ) { // Still show the same message. statusMessage( i18nc( "Status message on login screen", "Waiting for an Internet connection to reconnect...<br /><a href='%1'>Reconnect now!</a>", "kmess://reconnect/" ) ); } else { statusMessage( i18nc( "Status message on login screen", "Internet connection not available" ) ); } } } // Update the reconnection timer data void InitialView::slotReconnectTimerEvent() { if( reconnectionRemainingSeconds_ == 0 ) { // Halt the timer reconnectionTimer_.stop(); // Try reconnecting now startConnecting( reconnectionHandle_ ); return; } statusMessage( i18ncp( "Status message on login screen", "Waiting %1 second before reconnection...<br /><a href='%2'>Reconnect now!</a>", "Waiting %1 seconds before reconnection...<br /><a href='%2'>Reconnect now!</a>", reconnectionRemainingSeconds_, "kmess://reconnect/" ), 5000 ); // Reduce time for reconnect --reconnectionRemainingSeconds_; } // The account was deleted void InitialView::deleteAccount(Account *account) { // Remove account from hashmap. QString handle( account->getHandle() ); accounts_.remove(handle); // Remove account from completionbox (extended qlistbox) handleCombobox_->completionObject()->removeItem( handle ); // Remove account from qcombobox bool foundItem = false; for( int i = 0; i < handleCombobox_->count(); i++ ) { if( handleCombobox_->itemText(i).toLower() == handle.toLower() ) { handleCombobox_->removeItem(i); foundItem = true; break; } } if( ! foundItem ) { kWarning() << "Account not found in pulldown list!"; } } // Get the currently selected handle QString InitialView::getSelectedHandle() const { // Get handle, remove appended friendlyname if present QString handle( handleCombobox_->currentText() ); if( handle.contains(' ') ) { handle = handle.section(' ', 0, 0); } return handle.toLower(); } // A profile was selected from the drop-down list, or written manually. void InitialView::updateView() { Account *account = accounts_[ getSelectedHandle() ]; if( account != 0 ) { rememberAutoLoginCheckBox_->setChecked( false ); // User typed the full account name without using autocompletion. if( ! account->getSavePassword() ) { rememberPasswordCheckBox_->setChecked( false ); passwordEdit_->setText( QString() ); } else { rememberPasswordCheckBox_->setChecked( true ); passwordEdit_->setText( account->getPassword() ); // Check the auto login checkbox if( account->getUseAutologin() ) { rememberAutoLoginCheckBox_->setChecked( true ); } } // Set the current account picture QString picturePath; // Check if the user wants one picture if( account->getShowPicture() ) { picturePath = account->getPicturePath(); pictureLabel_->setEnabled( true ); } else { picturePath = KGlobal::dirs()->findResource( "data", "kmess/pics/kmesspic.png" ); pictureLabel_->setEnabled( false ); } // Check if the picture is valid const QPixmap picture( picturePath ); if( ! picture.isNull() ) { pictureLabel_->setPixmap( picture ); } else { pictureLabel_->setPixmap( QPixmap() ); } rememberAccountCheckBox_->setChecked( ! account->isGuestAccount() ); rememberAccountCheckBox_->setDisabled( ! account->isGuestAccount() ); // Make sure the drop down list matches the user's initial status int index; switch( account->getInitialStatus() ) { case STATUS_AWAY: index = 1; break; case STATUS_BE_RIGHT_BACK: index = 2; break; case STATUS_BUSY: index = 3; break; case STATUS_INVISIBLE: index = 6; break; case STATUS_ON_THE_PHONE: index = 5; break; case STATUS_OUT_TO_LUNCH: index = 4; break; case STATUS_ONLINE: default: index = 0; break; } initialStatus_->setCurrentIndex( index ); } else { // User typed a new account name // Set the default picture KStandardDirs *dirs = KGlobal::dirs(); QPixmap picture( dirs->findResource( "data", "kmess/pics/kmesspic.png" ) ); if( ! picture.isNull() ) { pictureLabel_->setPixmap( picture ); } // Set the default options rememberAccountCheckBox_ ->setDisabled( false ); rememberPasswordCheckBox_ ->setDisabled( false ); rememberAutoLoginCheckBox_->setDisabled( false ); rememberAccountCheckBox_ ->setChecked ( true ); rememberPasswordCheckBox_ ->setChecked ( false ); rememberAutoLoginCheckBox_->setChecked ( false ); // Clear password again passwordEdit_->setText( QString::null ); initialStatus_->setCurrentIndex( 0 ); } // Put the attention back to the email field handleCombobox_->setFocus(); } // The "remember account" checkbox changed its state void InitialView::rememberAccountStateChanged( int state ) { if( state == Qt::Unchecked ) { rememberPasswordCheckBox_ ->setCheckState( Qt::Unchecked ); rememberAutoLoginCheckBox_->setCheckState( Qt::Unchecked ); rememberPasswordCheckBox_ ->setEnabled( false ); rememberAutoLoginCheckBox_->setEnabled( false ); } else { rememberPasswordCheckBox_ ->setEnabled( true ); rememberAutoLoginCheckBox_->setEnabled( true ); } } // The "remember password" checkbox changed its state void InitialView::rememberPasswordStateChanged( int state ) { if( state == Qt::Unchecked ) { rememberAutoLoginCheckBox_->setCheckState( Qt::Unchecked ); } } // Reset the view to its initial state void InitialView::reset() { // Reset internal data setEnabled( true ); reconnectionHandle_ = QString(); reconnectionTimer_.stop(); triedConnecting_ = false; // Update the view again with regard to the current network status updateView(); statusMessage(); slotConnectionStatusChanged( Solid::Networking::status() ); } // Enable or disable the widgets when we're waiting or connecting void InitialView::setEnabled( bool isEnabled ) { isConnectingUI_ = ( ! isEnabled ); initialStatusLabel_ ->setEnabled( isEnabled ); initialStatus_ ->setEnabled( isEnabled ); handleCombobox_ ->setEnabled( isEnabled ); passwordEdit_ ->setEnabled( isEnabled ); rememberAccountCheckBox_ ->setEnabled( isEnabled ); rememberPasswordCheckBox_ ->setEnabled( isEnabled ); rememberAutoLoginCheckBox_->setEnabled( isEnabled ); // Switch the button's look if( isEnabled ) { connectButton_->setText( i18nc( "Button label", "&Connect" ) ); connectButton_->setIcon( loader_->loadIcon( "network-connect", KIconLoader::Small, KIconLoader::SizeMedium ) ); } else { connectButton_->setText( i18nc( "Button label", "&Cancel" ) ); connectButton_->setIcon( loader_->loadIcon( "network-disconnect", KIconLoader::Small, KIconLoader::SizeMedium ) ); } } // The connect/disconnect button has been clicked void InitialView::slotConnectClicked() { #ifdef KMESSDEBUG_INITIALVIEW kDebug() << "Connect button clicked," << ( isConnectingUI_ ? "disconnecting" : "connecting" ); #endif // The button was acting as Disconnect button when it was clicked if( isConnectingUI_ ) { emit disconnectClicked(); // Enable back the widgets to allow connecting again reset(); return; } // The button was acting as Connect button when it was clicked startConnecting( getSelectedHandle() ); } /** Start connecting with a specified account. * * It can also simply update the UI, without actually requesting a connection. * KMess' autologin functions connect by themselves, so this is required. * * @param handle Handle of the account to connect with * @param emitConnectionSignal Whether to emit the connection request signal * to the KMess class or not */ 00708 bool InitialView::startConnecting( const QString handle, bool emitConnectionSignal ) { // If the reconnection timer is active, stop it. if( reconnectionTimer_.isActive() ) { reconnectionTimer_.stop(); } // Fill the UI fields with the selected handle's details, if any // Selecting the account this way also makes a saved account's // password to appear handleCombobox_->setEditText( handle ); const QString password( passwordEdit_->text() ); // Avoid connecting with empty fields if( handle.isEmpty() || password.isEmpty() ) { statusMessage( i18nc( "Status message on login screen", "Please enter both your email address and password" ), 10000 ); // Select the box with the error if( handle.isEmpty() ) { handleCombobox_->setFocus(); } else { passwordEdit_->setFocus(); } return false; } // Also don't connect if the handle is invalid if( ! Account::isValidEmail( handle ) ) { statusMessage( i18nc( "Status message on login screen", "Please enter a valid email address" ), 10000 ); handleCombobox_->setFocus(); return false; } #ifdef KMESSDEBUG_INITIALVIEW kDebug() << "Connecting with handle:" << handle; #endif // Disable the widgets to start connecting setEnabled( false ); // Save the currently selected account for the next logins lastUsedHandle_ = handle; config_.writeEntry( "defaultHandle", handle ); // Find out what status has been chosen Status initialStatus; switch( initialStatus_->currentIndex() ) { case 1: initialStatus = STATUS_AWAY; break; case 2: initialStatus = STATUS_BE_RIGHT_BACK; break; case 3: initialStatus = STATUS_BUSY; break; case 4: initialStatus = STATUS_OUT_TO_LUNCH; break; case 5: initialStatus = STATUS_ON_THE_PHONE; break; case 6: initialStatus = STATUS_INVISIBLE; break; case 0: default: initialStatus = STATUS_ONLINE; break; } // Set the state. // This toggles reconnection, and "continue connecting" stuff. triedConnecting_ = true; // When we connect to an account from outside InitialView, the actual connection // process may be handled externally, so emitting the signal below would cause // problems, and even infinite loops. if( ! emitConnectionSignal ) { return true; } // Inform parent with the account information emit connectWithAccount( handle, password, rememberAccountCheckBox_ ->isChecked(), rememberPasswordCheckBox_ ->isChecked(), rememberAutoLoginCheckBox_->isChecked(), initialStatus ); return true; } // Change the connection status text void InitialView::statusMessage( const QString text, int timeout ) { statusLabel_->setText( text ); if( timeout > 0 ) { statusMessageTimer_.start( timeout ); } else { statusMessageTimer_.stop(); } } // Execute the browser for a clicked UI link void InitialView::slotClickedUrl( const QString &url ) { // Handle special links if( url.startsWith( "kmess://" ) ) { if( url == "kmess://reconnect/" ) { startConnecting( reconnectionHandle_ ); } return; } KMessShared::openBrowser( KUrl( url ) ); } bool InitialView::eventFilter(QObject *obj, QEvent *event) { // Handle keyboard events for all widgets const QEvent::Type& eventType( event->type() ); if( eventType == QEvent::KeyPress ) { const QKeyEvent *keyEvent = static_cast<QKeyEvent*>( event ); int key = keyEvent->key(); // Enable pressing Enter from any widget to start connecting if( ( key == Qt::Key_Return || key == Qt::Key_Enter ) || ( key == Qt::Key_Escape && isConnectingUI_ ) ) // Enable pressing Esc to cancel a connection attempt { slotConnectClicked(); return true; } } // The following code is only for the picture label. If any code has to be added for // another widget, this code may need to be changed.. if( obj != pictureLabel_ ) { return false; // don't stop processing. } // Don't handle events when connecting if( isConnectingUI_ ) { return false; } Account *account = accounts_[ getSelectedHandle() ]; if( eventType == QEvent::Enter ) // Glow effect when mouse enters display picture { QImage glowingImage; if( account != 0 && account->getShowPicture() ) { glowingImage = QImage( account->getPicturePath() ); } else { glowingImage = QImage( KGlobal::dirs()->findResource( "data", "kmess/pics/kmesspic.png" ) ); } KIconEffect::toGamma( glowingImage, 0.8f ); pictureLabel_->setPixmap( QPixmap::fromImage( glowingImage ) ); } else if( eventType == QEvent::Leave ) // Restore default when mouse leaves display picture { QPixmap picture; if( account != 0 && account->getShowPicture() ) { picture = QPixmap( account->getPicturePath() ); } else { picture = QPixmap( KGlobal::dirs()->findResource( "data", "kmess/pics/kmesspic.png" ) ); } pictureLabel_->setPixmap( picture ); } else if( eventType == QEvent::Wheel && handleCombobox_->isEnabled() == true ) // Switch accounts using mousewheel { const QWheelEvent *wheelEvent = static_cast<QWheelEvent*>( event ); int newIndex = handleCombobox_->currentIndex(); if( wheelEvent->delta() > 0 ) { newIndex--; if( newIndex < 0 ) { newIndex = handleCombobox_->count() - 1; } } else { newIndex++; if( newIndex >= handleCombobox_->count() ) { newIndex = 0; } } handleCombobox_->setCurrentIndex( newIndex ); } else if( eventType == QEvent::MouseButtonRelease ) // Show settings when display picture is clicked { emit showSettings( account ); } return false; // don't stop processing. } #include "initialview.moc"