Files
kmess/src/chat/contactswidget.cpp
T
2026-07-06 09:37:59 +02:00

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 );
}