605 lines
16 KiB
C++
605 lines
16 KiB
C++
/***************************************************************************
|
|
contactswidget.cpp - description
|
|
-------------------
|
|
begin : Thu Jan 16 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 "contactswidget.h"
|
|
|
|
#include "../contact/contact.h"
|
|
#include "../accountsmanager.h"
|
|
#include "../currentaccount.h"
|
|
#include "../kmessdebug.h"
|
|
#include "../network/msnswitchboardconnection.h"
|
|
#include "../utils/kmessshared.h"
|
|
#include "../utils/kmessconfig.h"
|
|
|
|
#include <QDockWidget>
|
|
#include <QDir>
|
|
#include <QMimeData>
|
|
#include <QMouseEvent>
|
|
#include <QRegularExpression>
|
|
|
|
#include <KIconEffect>
|
|
#include <KLocalizedString>
|
|
#include <KMessageBox>
|
|
#include <QUrl>
|
|
|
|
// The constructor
|
|
ContactsWidget::ContactsWidget( QWidget *parent )
|
|
: QWidget( parent )
|
|
, Ui::ContactsWidget()
|
|
, wasVisible_( false )
|
|
{
|
|
setupUi( this );
|
|
|
|
container_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
|
|
|
|
// Create the layout which will organize the contact frames
|
|
layout_ = new QBoxLayout( QBoxLayout::TopToBottom, container_ );
|
|
layout_->setSpacing( 2 );
|
|
layout_->setContentsMargins( 0, 0, 0, 0 );
|
|
//layout_->setSizeConstraint( QLayout::SetMinAndMaxSize );
|
|
layout_->setAlignment( Qt::AlignTop | Qt::AlignLeft );
|
|
|
|
// The scroll area background should mimetize with the rest of the app
|
|
area_->setBackgroundRole( QPalette::Window );
|
|
|
|
// Initialize the user picture frame
|
|
userPixmapLabel_->installEventFilter( this );
|
|
CurrentAccount *account = CurrentAccount::instance();
|
|
connect( account, SIGNAL( changedMsnObject() ),
|
|
this, SLOT ( slotUpdateDisplayPicture() ) );
|
|
slotUpdateDisplayPicture();
|
|
}
|
|
|
|
|
|
|
|
// The destructor
|
|
ContactsWidget::~ContactsWidget()
|
|
{
|
|
// Remove all contact frames
|
|
qDeleteAll( contactFrames_ );
|
|
contactFrames_.clear();
|
|
|
|
#ifdef KMESSDEBUG_CONTACTSWIDGET
|
|
kmDebug() << "DESTROYED.";
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
// A contact was added to the contact list
|
|
void ContactsWidget::contactAdded( Contact *contact )
|
|
{
|
|
// Update any invited contact frame
|
|
ContactFrame *contactFrame = getContactFrameByHandle( contact->getHandle() );
|
|
if ( contactFrame != 0 )
|
|
{
|
|
contactFrame->activate( contact );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// A contact joined the chat
|
|
void ContactsWidget::contactJoined( ContactBase* contact )
|
|
{
|
|
// See if the contact already has a frame
|
|
const QString& handle = contact->getHandle();
|
|
ContactFrame *contactFrame = getContactFrameByHandle( handle );
|
|
|
|
if( contactFrame != 0 )
|
|
{
|
|
// Reactivate the contact's frame
|
|
contactFrame->setEnabled( true );
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CONTACTSWIDGET
|
|
kmDebug() << handle;
|
|
#endif
|
|
|
|
ContactFrame::DisplayMode frameMode;
|
|
|
|
// Get the first available contact frame
|
|
contactFrame = createContactFrame();
|
|
if( KMESS_NULL(contactFrame) ) return;
|
|
|
|
// Activate the frame
|
|
contactFrame->activate( contact );
|
|
|
|
// Find the most suitable frame display mode
|
|
frameMode = getBestFrameMode();
|
|
|
|
// Set it to all the frames, including the new one
|
|
foreach( ContactFrame *frame, contactFrames_ )
|
|
{
|
|
frame->setDisplayMode( frameMode );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// A contact left the chat
|
|
void ContactsWidget::contactLeft( ContactBase *contact, bool /*isChatIdle*/ )
|
|
{
|
|
const QString& handle = contact->getHandle();
|
|
ContactFrame *contactFrame = getContactFrameByHandle( handle );
|
|
|
|
if ( contactFrame == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CONTACTSWIDGET
|
|
kmDebug() << handle;
|
|
#endif
|
|
|
|
|
|
ContactFrame::DisplayMode frameMode;
|
|
|
|
// Delete the frames whenever there is more than one; when only one is left, grey it out instead.
|
|
if( contactFrames_.count() > 1 )
|
|
{
|
|
contactFrames_.removeAll( contactFrame );
|
|
delete contactFrame;
|
|
|
|
// Find the most suitable frame mode
|
|
frameMode = getBestFrameMode();
|
|
|
|
// Set it to all the frames
|
|
foreach( ContactFrame *frame, contactFrames_ )
|
|
{
|
|
frame->setDisplayMode( frameMode );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Deactivate the contact frame
|
|
contactFrame->setEnabled( false );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// A contact was removed from the contact list
|
|
void ContactsWidget::contactRemoved( Contact *contact )
|
|
{
|
|
// Update any invited contact frame
|
|
ContactFrame *contactFrame = getContactFrameByHandle( contact->getHandle() );
|
|
if ( contactFrame != 0 )
|
|
{
|
|
contactFrame->activate( 0 );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// A contact is typing
|
|
void ContactsWidget::contactTyping( ContactBase *contact )
|
|
{
|
|
ContactFrame *contactFrame = getContactFrameByHandle( contact->getHandle() );
|
|
if ( contactFrame != 0 )
|
|
{
|
|
contactFrame->startTyping();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// The user picture frame received an event
|
|
bool ContactsWidget::eventFilter( QObject *obj, QEvent *event )
|
|
{
|
|
QEvent::Type eventType = event->type();
|
|
|
|
if( obj != userPixmapLabel_ )
|
|
{
|
|
#ifdef KMESSDEBUG_CONTACTSWIDGET
|
|
kmWarning() << "Received event '" << eventType << "' from object '" << obj->objectName() << "'.";
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
CurrentAccount *currentAccount = CurrentAccount::instance();
|
|
|
|
// Avoid wasting time :)
|
|
if( ! userPictureFrame_->isVisible() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
switch( eventType )
|
|
{
|
|
// Make the picture to glow when hovered
|
|
case QEvent::Enter:
|
|
{
|
|
QImage glowingImage( currentAccount->getPicturePath() );
|
|
KIconEffect::toGamma( glowingImage, 0.8f );
|
|
userPixmapLabel_->setPixmap( QPixmap::fromImage( glowingImage ) );
|
|
}
|
|
break;
|
|
|
|
// Restore the picture when the mouse is not hovering over it anymore
|
|
case QEvent::Leave:
|
|
{
|
|
userPixmapLabel_->setPixmap( QPixmap( currentAccount->getPicturePath() ) );
|
|
}
|
|
break;
|
|
|
|
// When the picture is clicked, show the account settings
|
|
case QEvent::MouseButtonRelease:
|
|
{
|
|
AccountsManager::instance()->showAccountSettings( currentAccount );
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case QEvent::DragEnter:
|
|
{
|
|
QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent*>( event );
|
|
const QMimeData *mimeData = dragEvent->mimeData();
|
|
|
|
// The mimeData object can contain one of the following objects: an image, HTML text, plain text, or a list of URLs.
|
|
// We need only the first URL object
|
|
if( mimeData->hasUrls() )
|
|
{
|
|
dragEvent->acceptProposedAction();
|
|
}
|
|
else
|
|
{
|
|
dragEvent->ignore();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case QEvent::Drop:
|
|
{
|
|
QDropEvent* dropEvent = static_cast<QDropEvent*>( event );
|
|
const QMimeData *mimeData = dropEvent->mimeData();
|
|
|
|
// The mimeData object can contain one of the following objects: an image, HTML text, plain text, or a list of URLs.
|
|
// We need only the first URL object
|
|
if( ! mimeData->hasUrls() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
QUrl mimeUrl = mimeData->urls().first();
|
|
QString localFilePath;
|
|
QString temporaryFilePath;
|
|
QImage pictureData;
|
|
|
|
if( mimeUrl.isEmpty() )
|
|
{
|
|
kmWarning() << "Dropped empty MIME URL";
|
|
return true;
|
|
}
|
|
|
|
if( ! KMessShared::downloadToLocalFile( mimeUrl, localFilePath, &temporaryFilePath ) )
|
|
{
|
|
KMessageBox::error( this, i18n( "Downloading of display picture failed" ), i18n("KMess") );
|
|
return true;
|
|
}
|
|
|
|
if( ! pictureData.load( localFilePath ) )
|
|
{
|
|
KMessShared::removeDownloadedTempFile( temporaryFilePath );
|
|
kmWarning() << "The file dropped is not an image or doesn't exist";
|
|
return true;
|
|
}
|
|
|
|
KMessShared::removeDownloadedTempFile( temporaryFilePath );
|
|
|
|
pictureData = pictureData.scaled( 96, 96, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation ); // Makes smallest edge 96 pixels, preserving aspect ratio
|
|
|
|
if( pictureData.width() != 96 || pictureData.height() != 96 )
|
|
{
|
|
// Copy the middle part of the picture.
|
|
pictureData = pictureData.copy( (pictureData.width() - 96) / 2, // X
|
|
(pictureData.height() - 96) / 2, // Y
|
|
96, 96 );
|
|
}
|
|
|
|
QString pictureDir = KMessConfig::instance()->getAccountDirectory( currentAccount->getHandle() ) + "/displaypics/";
|
|
QString tempPictureFile = pictureDir + "temporary-picture.png";
|
|
|
|
if( ! pictureData.save( tempPictureFile, "PNG" ) )
|
|
{
|
|
KMessageBox::error( this,
|
|
i18n( "An error occurred while trying to change the display picture.\n"
|
|
"Make sure that you have selected an existing image file." ),
|
|
i18n( "KMess" ) );
|
|
}
|
|
|
|
QString msnObjectHash( KMessShared::generateFileHash( tempPictureFile ).toBase64() );
|
|
const QString safeMsnObjectHash( msnObjectHash.replace( QRegularExpression("[^a-zA-Z0-9+=]"), "_" ) );
|
|
|
|
// Save the new display picture with its hash as name, to ensure correct caching
|
|
QString pictureName = msnObjectHash + ".png";
|
|
QDir pictureFolder = QDir( pictureDir );
|
|
|
|
// Do not overwrite identical pictures
|
|
if( ! pictureFolder.exists( pictureName ) )
|
|
{
|
|
if( ! pictureFolder.rename( tempPictureFile, pictureName ) )
|
|
{
|
|
kmWarning() << "The display picture file could not be renamed from" << tempPictureFile << "to" << pictureName << ".";
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if ( ! currentAccount->getShowPicture() )
|
|
{
|
|
currentAccount->setShowPicture( true );
|
|
currentAccount->saveProperties();
|
|
}
|
|
|
|
currentAccount->setPicturePath( pictureDir + pictureName );
|
|
currentAccount->setOriginalPicturePath( mimeUrl.path() );
|
|
|
|
dropEvent->acceptProposedAction();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
// Get the most suitable frame size, depending on how many contacts are currently in chat with us
|
|
inline ContactFrame::DisplayMode ContactsWidget::getBestFrameMode()
|
|
{
|
|
// Find out how many are chatting at the moment
|
|
int count = contactFrames_.count();
|
|
|
|
if( count > 5 )
|
|
{
|
|
// More than 5 contacts in chat with us: it's a big group chat and we need to be able to display as many frames as possible
|
|
return ContactFrame::ModeTiny;
|
|
}
|
|
else if( count > 2 )
|
|
{
|
|
// A normal group chat: reduce the size of the frames to be able to see all of our contacts at once
|
|
return ContactFrame::ModeSmall;
|
|
}
|
|
else if( count == 2 )
|
|
{
|
|
// A small group chat: Show all details, there's still a lot of space
|
|
return ContactFrame::ModeNormal;
|
|
}
|
|
|
|
// A simple 1-on-1 chat: we can show as much detail as possible
|
|
return ContactFrame::ModeSingle;
|
|
}
|
|
|
|
|
|
|
|
// Find the contact frame with the given handle
|
|
ContactFrame* ContactsWidget::getContactFrameByHandle( const QString& handle )
|
|
{
|
|
foreach( ContactFrame *contactFrame, contactFrames_ )
|
|
{
|
|
if( contactFrame->getHandle() == handle )
|
|
{
|
|
return contactFrame;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
// Return one new contact frame
|
|
ContactFrame* ContactsWidget::createContactFrame()
|
|
{
|
|
ContactFrame *contactFrame = new ContactFrame( container_ );
|
|
|
|
connect( contactFrame, SIGNAL( startPrivateChat( const QString& ) ),
|
|
this, SIGNAL( startPrivateChat( const QString& ) ) );
|
|
connect( contactFrame, SIGNAL( contactAllowed( QString ) ),
|
|
this, SIGNAL( contactAllowed( QString ) ) );
|
|
connect( contactFrame, SIGNAL( contactAdded( QString, bool ) ),
|
|
this, SIGNAL( contactAdded( QString, bool ) ) );
|
|
connect( contactFrame, SIGNAL( contactBlocked( QString, bool ) ),
|
|
this, SIGNAL( contactBlocked( QString, bool ) ) );
|
|
|
|
// Add it to the viewBox so it appears in the widget
|
|
layout_->insertWidget( children().count() - 2, contactFrame );
|
|
|
|
// put it in the list of frames so we can find it again.
|
|
contactFrames_.append( contactFrame );
|
|
|
|
return contactFrame;
|
|
}
|
|
|
|
|
|
|
|
// A message was received from one of the contacts... notify its frame
|
|
void ContactsWidget::messageReceived( const QString& handle )
|
|
{
|
|
ContactFrame *frame = getContactFrameByHandle( handle );
|
|
if ( frame != 0 )
|
|
{
|
|
frame->messageReceived();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Enable/disable the frames
|
|
void ContactsWidget::setEnabled( bool isEnabled )
|
|
{
|
|
ContactBase *contact;
|
|
CurrentAccount *account = CurrentAccount::instance();
|
|
|
|
if( isEnabled )
|
|
{
|
|
// Loop through all frames
|
|
foreach( ContactFrame *frame, contactFrames_ )
|
|
{
|
|
contact = account->getContactByHandle( frame->getHandle() );
|
|
|
|
// Only reactivate frames for contacts which exist in the account
|
|
if( contact )
|
|
{
|
|
// Reactivate the frame
|
|
frame->activate( contact );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Loop through all frames
|
|
foreach( ContactFrame *frame, contactFrames_ )
|
|
{
|
|
// Remove the activation status
|
|
frame->activate( 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Connect the widget to a dock
|
|
void ContactsWidget::setDockWidget( QDockWidget *dockWidget, Qt::DockWidgetArea initialArea )
|
|
{
|
|
#ifdef KMESSTEST
|
|
KMESS_ASSERT( dockWidget );
|
|
#endif
|
|
|
|
// We need to change layout when the dock moves
|
|
connect( dockWidget, SIGNAL( dockLocationChanged(Qt::DockWidgetArea) ),
|
|
this, SLOT ( slotLocationChanged(Qt::DockWidgetArea) ) );
|
|
connect( dockWidget, SIGNAL( topLevelChanged(bool) ),
|
|
this, SLOT ( slotTopLevelChanged(bool) ) );
|
|
|
|
slotLocationChanged( initialArea );
|
|
}
|
|
|
|
|
|
|
|
// The location of the parent dock widget has changed
|
|
void ContactsWidget::slotLocationChanged( Qt::DockWidgetArea area )
|
|
{
|
|
#ifdef KMESSDEBUG_CONTACTSWIDGET
|
|
kmDebug() << "Changing location to" << area;
|
|
#endif
|
|
|
|
switch( area )
|
|
{
|
|
case Qt::TopDockWidgetArea:
|
|
case Qt::BottomDockWidgetArea:
|
|
|
|
// Loop through all frames to switch the layout mode for frames
|
|
foreach( ContactFrame *frame, contactFrames_ )
|
|
{
|
|
frame->setDisplayMode( ContactFrame::ModeSmall );
|
|
}
|
|
|
|
layout_->setDirection( QBoxLayout::LeftToRight );
|
|
area_->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded );
|
|
area_->setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOff );
|
|
userPictureFrame_->setVisible( false );
|
|
break;
|
|
|
|
case Qt::LeftDockWidgetArea:
|
|
case Qt::RightDockWidgetArea:
|
|
default:
|
|
|
|
// Loop through all frames to switch the layout mode for frames
|
|
ContactFrame::DisplayMode mode = getBestFrameMode();
|
|
foreach( ContactFrame *frame, contactFrames_ )
|
|
{
|
|
frame->setDisplayMode( mode );
|
|
}
|
|
|
|
layout_->setDirection( QBoxLayout::TopToBottom );
|
|
area_->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
|
|
area_->setVerticalScrollBarPolicy ( Qt::ScrollBarAsNeeded );
|
|
|
|
// Determine if the user picture needs to be shown
|
|
slotUpdateDisplayPicture();
|
|
break;
|
|
}
|
|
|
|
// Ensure that the view shows the top left corner
|
|
area_->ensureVisible( 0, 0 );
|
|
}
|
|
|
|
|
|
|
|
// The parent dock widget floating status has changed
|
|
void ContactsWidget::slotTopLevelChanged( bool isTopLevel )
|
|
{
|
|
// Moving has started
|
|
if( isTopLevel )
|
|
{
|
|
wasVisible_ = isVisible();
|
|
setVisible( false );
|
|
}
|
|
// The widget has been dropped somewhere
|
|
else
|
|
{
|
|
setVisible( wasVisible_ );
|
|
}
|
|
|
|
adjustSize();
|
|
}
|
|
|
|
|
|
|
|
// Update the user's display picture
|
|
void ContactsWidget::slotUpdateDisplayPicture()
|
|
{
|
|
CurrentAccount *currentAccount = CurrentAccount::instance();
|
|
|
|
bool isVisible = currentAccount->getShowChatUserPicture();
|
|
|
|
userPictureFrame_->setVisible( isVisible );
|
|
|
|
// Don't update it if it's not even visible
|
|
if( ! isVisible )
|
|
{
|
|
#ifdef KMESSDEBUG_CONTACTSWIDGET
|
|
kmDebug() << "Not updating hidden user picture widget";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// The picture is unchanged, do nothing
|
|
const QString pictureFileName = currentAccount->getPicturePath();
|
|
if( userPixmapLabel_->property( "PictureFileName" ) == pictureFileName )
|
|
{
|
|
#ifdef KMESSDEBUG_CONTACTSWIDGET
|
|
kmDebug() << "User picture unchanged, not updating widget";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CONTACTSWIDGET
|
|
kmDebug() << "Updating user picture widget";
|
|
#endif
|
|
|
|
// Change the picture
|
|
const QPixmap image( pictureFileName );
|
|
userPixmapLabel_->setPixmap( image );
|
|
userPixmapLabel_->setProperty( "PictureFileName", pictureFileName );
|
|
}
|