2319 lines
75 KiB
C++
2319 lines
75 KiB
C++
/***************************************************************************
|
|
kmessview.cpp - description
|
|
-------------------
|
|
begin : Thu Jan 9 2003
|
|
copyright : (C) 2003 by Mike K. Bennett
|
|
(C) 2005 by Diederik van der Boor
|
|
email : mkb137b@hotmail.com
|
|
vdboor --at-- codingdomain.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 "kmessview.h"
|
|
|
|
#include "contact/contact.h"
|
|
#include "contact/group.h"
|
|
#include "contact/msnstatus.h"
|
|
#include "contact/specialgroups.h"
|
|
#include "dialogs/chathistorydialog.h"
|
|
#include "dialogs/contactpropertiesdialog.h"
|
|
#include "model/contactlist.h"
|
|
#include "model/contactlistmodelfilter.h"
|
|
#include "utils/kmessshared.h"
|
|
#include "utils/kmessconfig.h"
|
|
#include "utils/richtextparser.h"
|
|
#include "currentaccount.h"
|
|
#include "emoticonmanager.h"
|
|
#include "kmessdebug.h"
|
|
#include "kmessviewdelegate.h"
|
|
|
|
#include <QClipboard>
|
|
#include <QGuiApplication>
|
|
#include <QKeyEvent>
|
|
#include <QImage>
|
|
#include <QItemSelectionModel>
|
|
#include <QMimeData>
|
|
#include <QPixmap>
|
|
#include <QRegularExpression>
|
|
#include <QSignalMapper>
|
|
#include <QStyleHints>
|
|
#include <QStringList>
|
|
#include <QToolTip>
|
|
|
|
#include <QAction>
|
|
#include <KActionMenu>
|
|
#include <QApplication>
|
|
#include <QIcon>
|
|
#include <KIconLoader>
|
|
#include <KIconEffect>
|
|
#include <KLocalizedString>
|
|
#include <QMenu>
|
|
#include <KMessageBox>
|
|
#include <QUrl>
|
|
|
|
|
|
#ifdef KMESSDEBUG_CONTACTLISTMODELTEST
|
|
#include "../tests/modeltest/modeltest.h"
|
|
#endif
|
|
|
|
|
|
#ifdef KMESSDEBUG_KMESSVIEW
|
|
#define KMESSDEBUG_KMESSVIEW_GENERAL
|
|
|
|
#ifdef KMESSDEBUG_CONTACTLISTMODEL
|
|
#define KMESSDEBUG_KMESSVIEW_ITEM_VISIBILITY
|
|
#endif
|
|
#endif
|
|
|
|
|
|
// The constructor
|
|
KMessView::KMessView( QWidget *parent )
|
|
: QWidget( parent ),
|
|
Ui::KMessView(),
|
|
contactCopyMapper_(0),
|
|
contactMoveMapper_(0),
|
|
currentAccount_(0),
|
|
initialized_(false),
|
|
viewModel_(0)
|
|
{
|
|
setupUi( this );
|
|
searchFrame_->hide();
|
|
|
|
// Capture focus events without subclassing the widget from the .UI file
|
|
currentAccountDisplayPic_->installEventFilter(this);
|
|
contactListView_->installEventFilter(this);
|
|
contactListView_->viewport()->installEventFilter(this);
|
|
|
|
// Windows Live Messenger 8.5 and 2009 (latter only in the chat screen) don't
|
|
// display any part further than 129 characters.
|
|
personalMessageInput_->setMaxLength( 129 );
|
|
|
|
friendlyNameInput_->setFormattingMode( STRING_LIST_SETTING );
|
|
personalMessageInput_->setFormattingMode( STRING_LIST_SETTING );
|
|
|
|
// Set up the group update timer
|
|
updateTimer_.setSingleShot( true );
|
|
connect( &updateTimer_, SIGNAL( timeout() ),
|
|
this, SLOT ( slotUpdateGroups() ) );
|
|
|
|
// Set the owner window to always show tooltips on request,
|
|
// even if it doesn't have focus.
|
|
window()->setAttribute( Qt::WA_AlwaysShowToolTips );
|
|
}
|
|
|
|
|
|
|
|
// The destructor
|
|
KMessView::~KMessView()
|
|
{
|
|
// Delete the contact actions for copy/move to group
|
|
qDeleteAll( groupCopyActionsList_ );
|
|
qDeleteAll( groupMoveActionsList_ );
|
|
qDeleteAll( groupCopyLinkActionsList_ );
|
|
delete contactCopyMapper_;
|
|
delete contactMoveMapper_;
|
|
|
|
delete selectionModel_;
|
|
delete viewModel_;
|
|
selectionModel_ = 0;
|
|
viewModel_ = 0;
|
|
|
|
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
|
|
kmDebug() << "DESTROYED.";
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
// The currently playing song was changed.
|
|
void KMessView::changedSong( const QString &artist, const QString &/*album*/, const QString &track, bool playing )
|
|
{
|
|
songPlaying_ = playing;
|
|
|
|
if( playing )
|
|
{
|
|
// Show status line
|
|
if( ! nowListeningFrame_->isVisible() )
|
|
{
|
|
nowListeningFrame_->setHidden(false);
|
|
nowListeningFrame_->setMaximumHeight(40);
|
|
}
|
|
|
|
// Update label
|
|
bool showDash = ( ! track.isEmpty() && ! artist.isEmpty() );
|
|
QString longTitle( track + ( showDash ? " - " : "" ) + artist );
|
|
nowListeningLabel_->setText( longTitle );
|
|
}
|
|
else
|
|
{
|
|
// Hide status line
|
|
nowListeningFrame_->setHidden(true);
|
|
nowListeningFrame_->setMaximumHeight(0);
|
|
}
|
|
|
|
// update size of the DP.
|
|
slotUpdateDisplayPicture();
|
|
}
|
|
|
|
|
|
|
|
// Get the specified item's or the current item's data
|
|
const ModelDataList KMessView::getItemData( const QModelIndex &index )
|
|
{
|
|
const QModelIndexList selection( selectionModel_->selectedIndexes() );
|
|
QModelIndex itemIndex( index );
|
|
|
|
// If the index is not valid, get the current selection
|
|
// TODO is this check necessary? isn't it better to return ModelDataList() to avoid any problems?
|
|
if( ! itemIndex.isValid() && ! selection.isEmpty() )
|
|
{
|
|
// Only one index can be returned at once by the contact list (has single selection mode)
|
|
itemIndex = QModelIndex( selection.first() );
|
|
// return ModelDataList();
|
|
}
|
|
|
|
// The index or the selection aren't valid, abort.
|
|
if( ! itemIndex.isValid() || itemIndex.model() != viewModel_ )
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW_ITEM_VISIBILITY
|
|
kmDebug() << "Skipping an invalid item at index:" << index;
|
|
#endif
|
|
return ModelDataList();
|
|
}
|
|
|
|
// Get the item data
|
|
const QVariant data( itemIndex.data() );
|
|
|
|
// If the data is not a variant map, abort
|
|
if( ! data.canConvert<QVariantMap>() )
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW_ITEM_VISIBILITY
|
|
kmDebug() << "Skipping an item with invalid data at index:" << index;
|
|
#endif
|
|
return ModelDataList();
|
|
}
|
|
|
|
// Convert the variant data to a normal map
|
|
const ModelDataList itemData( data.toMap() );
|
|
|
|
// Do nothing if there's no data
|
|
if( itemData.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW_ITEM_VISIBILITY
|
|
kmDebug() << "Skipping an empty item at index:" << index;
|
|
#endif
|
|
return ModelDataList();
|
|
}
|
|
|
|
int type = itemData[ "type" ].toInt();
|
|
|
|
// Do nothing if the item is not of a valid type
|
|
if( type != ContactListModelItem::ItemGroup
|
|
&& type != ContactListModelItem::ItemContact )
|
|
{
|
|
return ModelDataList();
|
|
}
|
|
|
|
return itemData;
|
|
}
|
|
|
|
|
|
|
|
// Initialize the class
|
|
bool KMessView::initialize( QAbstractItemModel *viewModel )
|
|
{
|
|
bool success;
|
|
KIconLoader *loader = KIconLoader::global();
|
|
|
|
// Get a ref to the current account
|
|
currentAccount_ = CurrentAccount::instance();
|
|
|
|
if ( initialized_ )
|
|
{
|
|
kmDebug() << "already initialized.";
|
|
return false;
|
|
}
|
|
|
|
success = initContactListView( viewModel );
|
|
if(! success)
|
|
{
|
|
kmDebug() << "Couldn't initialize contact list view.";
|
|
return false;
|
|
}
|
|
|
|
success = initContactPopup();
|
|
if(! success)
|
|
{
|
|
kmDebug() << "Couldn't initialize contact popup.";
|
|
return false;
|
|
}
|
|
|
|
success = initGroupPopup();
|
|
if(! success)
|
|
{
|
|
kmDebug() << "Couldn't initialize group popup.";
|
|
return false;
|
|
}
|
|
|
|
//set the nowListening button
|
|
slotUpdateNowListeningToggle();
|
|
|
|
// Set an icon for the email label
|
|
// Using "knotify" icon because it's available everywhere. "multimedia" is SUSE-specific.
|
|
emailPixmapLabel_ ->setPixmap( loader->loadIcon( "mail-mark-unread", KIconLoader::Small ) );
|
|
nowListeningPixmapLabel_->setPixmap( loader->loadIcon( "speaker", KIconLoader::Small ) );
|
|
searchIcon_ ->setPixmap( loader->loadIcon( "edit-find-user", KIconLoader::Small ) );
|
|
|
|
// Create a menu for the status button
|
|
|
|
// Set the menu into button and connect the signals
|
|
QMenu *menu = MsnStatus::getStatusMenu();
|
|
statusButton_->setMenu( menu );
|
|
connect( statusButton_, SIGNAL( clicked() ),
|
|
statusButton_, SLOT ( showMenu() ) );
|
|
|
|
// Enable the labels
|
|
emailLabel_->setEnabled(true);
|
|
|
|
QFont appFont( QApplication::font() );
|
|
int appFontSize = appFont.pointSize();
|
|
friendlyNameInput_->setStyleSheet( "QLabel { font-size: " + QString::number( appFontSize + 2 ) + "pt; }" );
|
|
personalMessageInput_->setStyleSheet( "QLabel { font-size: " + QString::number( appFontSize - 1 ) + "pt; }" );
|
|
nowListeningLabel_->setStyleSheet( "QLabel { font-style: italic; font-size: " + QString::number( appFontSize - 1 ) +"pt; }" );
|
|
|
|
// Hide the now listening until something is playing
|
|
changedSong( QString(), QString(), QString(), false );
|
|
|
|
// Connect signals for the current account
|
|
connect( currentAccount_, SIGNAL( changedMsnObject() ),
|
|
this, SLOT ( slotUpdateDisplayPicture() ) ); // Changed users display pic
|
|
connect( currentAccount_, SIGNAL( changedNoEmails() ), // Changed email count
|
|
this, SLOT ( slotUpdateNoEmails() ) );
|
|
connect( currentAccount_, SIGNAL( changedStatus() ), // Changed status
|
|
this, SLOT ( slotUpdateUserStatus() ) );
|
|
connect( currentAccount_, SIGNAL( changedFriendlyName() ), // Changed name
|
|
this, SLOT ( slotUpdateUserStatus() ) );
|
|
connect( currentAccount_, SIGNAL( changedPersonalMessage() ), // Changed personal message
|
|
this, SLOT ( slotUpdateUserStatus() ) );
|
|
connect( currentAccount_, SIGNAL( changedEmailDisplaySettings() ), // Changed email display settings
|
|
this, SLOT ( slotUpdateEmailDisplay() ) );
|
|
connect( currentAccount_, SIGNAL( changedDisplaySettings() ),
|
|
this, SLOT ( slotUpdateGroups() ) );
|
|
connect( currentAccount_, SIGNAL( changedViewMode() ),
|
|
this, SLOT ( slotUpdateGroups() ) );
|
|
connect( currentAccount_, SIGNAL( changedContactListOptions() ), // Changed contact list options
|
|
this, SLOT ( slotUpdateView() ) );
|
|
connect( currentAccount_, SIGNAL( changedContactListOptions() ), // Changed contact list options
|
|
this, SLOT ( slotUpdateUserStatus() ) );
|
|
// These update the display pic size depending on the email & now listening options
|
|
connect( currentAccount_, SIGNAL( changedNowListeningSettings() ),
|
|
this, SLOT ( slotUpdateDisplayPicture() ) );
|
|
connect( currentAccount_, SIGNAL( changedNowListeningSettings() ),
|
|
this, SLOT (slotUpdateNowListeningToggle() ) );
|
|
connect( currentAccount_, SIGNAL( changedEmailDisplaySettings() ),
|
|
this, SLOT ( slotUpdateDisplayPicture() ) );
|
|
|
|
// Connect signals for the contactlist
|
|
const ContactList *contactList = currentAccount_->getContactList();
|
|
connect( contactList, SIGNAL( groupAdded(const Group*) ), // A group was added
|
|
this, SLOT( rebuildContactActions() ) );
|
|
connect( contactList, SIGNAL( groupChanged(const Group*) ), // A group was modified
|
|
this, SLOT( rebuildContactActions() ) );
|
|
connect( contactList, SIGNAL( groupRemoved(const Group*) ), // A group was removed
|
|
this, SLOT( rebuildContactActions() ) );
|
|
|
|
// Connect signals for the history combobox and put the first string into it
|
|
connect( contactList, SIGNAL( contactOnline( Contact*, bool ) ),
|
|
this, SLOT( slotContactChangedStatus( Contact *, bool ) ) );
|
|
connect( contactList, SIGNAL( contactOffline( Contact*, bool ) ),
|
|
this, SLOT( slotContactChangedStatus( Contact *, bool ) ) );
|
|
connect( contactList, SIGNAL( contactOnline( Contact*, bool ) ),
|
|
this, SLOT( slotScheduleGroupUpdate() ) );
|
|
connect( contactList, SIGNAL( contactOffline( Contact*, bool ) ),
|
|
this, SLOT( slotScheduleGroupUpdate() ) );
|
|
|
|
historyBox_->insertItem( 0, i18n( "[%1] Logged in with %2", QTime::currentTime ().toString( "HH:mm:ss" )
|
|
, currentAccount_->getHandle() ) );
|
|
// Enable changing inline the friendly name and PM
|
|
connect( friendlyNameInput_, SIGNAL( changed() ),
|
|
this, SLOT ( updateFriendlyName() ) );
|
|
connect( personalMessageInput_, SIGNAL( changed() ),
|
|
this, SLOT ( updatePersonalMessage() ) );
|
|
|
|
// Connect the search edit line with slot for searching
|
|
connect( searchEdit_, SIGNAL( textChanged(QString) ),
|
|
this, SLOT ( slotSearchContact(QString) ) );
|
|
|
|
// Connect toggle now listening button
|
|
connect( nowListeningPushButton_, SIGNAL( clicked() ),
|
|
this, SLOT ( slotToggleNowListening()) );
|
|
|
|
// Enable the search box if the user has left it open at the last disconnection
|
|
toggleShowSearchFrame( currentAccount_->getShowSearchBar() );
|
|
|
|
// Enable the history box
|
|
toggleShowHistoryBox( currentAccount_->getShowHistoryBox() );
|
|
|
|
// Run the update functions with the current user data
|
|
slotUpdateEmailDisplay();
|
|
slotUpdateUserStatus();
|
|
slotUpdateNoEmails();
|
|
slotUpdateDisplayPicture();
|
|
slotUpdateGroups();
|
|
|
|
updateFriendlyName( true );
|
|
updatePersonalMessage( true );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// Called when a contact is now online/offline
|
|
void KMessView::slotContactChangedStatus( Contact *contact, bool showNotify )
|
|
{
|
|
// One contact is now online/offline, put it in history combobox
|
|
if( showNotify )
|
|
{
|
|
const QString& contactName = currentAccount_->getShowContactEmail()
|
|
? contact->getHandle()
|
|
: contact->getFriendlyName();
|
|
QString item;
|
|
|
|
if( contact->isOnline() )
|
|
{
|
|
item = i18n( "[%1] %2 goes online", QTime::currentTime ().toString( "HH:mm:ss" )
|
|
, contactName );
|
|
}
|
|
else
|
|
{
|
|
item = i18n( "[%1] %2 goes offline", QTime::currentTime ().toString( "HH:mm:ss" )
|
|
, contactName );
|
|
}
|
|
|
|
// Remove oldest item if the count is >= 500 elements
|
|
if( historyBox_->count() >= 500 )
|
|
{
|
|
historyBox_->removeItem( historyBox_->count() - 1 );
|
|
}
|
|
|
|
historyBox_->insertItem( 0, item );
|
|
historyBox_->setCurrentIndex ( 0 );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// The connection was closed, clean up before deletion
|
|
void KMessView::slotDisconnected()
|
|
{
|
|
updateTimer_.stop();
|
|
|
|
contactListView_->setModel( 0 );
|
|
}
|
|
|
|
|
|
|
|
// The "show history box" menu item has been toggled.
|
|
void KMessView::toggleShowHistoryBox( bool show )
|
|
{
|
|
historyBox_->setVisible( show );
|
|
|
|
// Save the current state to the Account
|
|
currentAccount_->setShowHistoryBox( show );
|
|
}
|
|
|
|
|
|
|
|
// The "show search in contact list" menu item has been toggled.
|
|
void KMessView::toggleShowSearchFrame( bool show )
|
|
{
|
|
if( show )
|
|
{
|
|
searchFrame_->show();
|
|
searchEdit_->setPlaceholderText( i18n( "Search in contact list..." ) );
|
|
}
|
|
else
|
|
{
|
|
// Remove the query when closing the search bar
|
|
searchEdit_->setText( QString() );
|
|
|
|
searchFrame_->hide();
|
|
contactListView_->setFocus();
|
|
}
|
|
|
|
// Save the current state to the Account
|
|
currentAccount_->setShowSearchBar( show );
|
|
}
|
|
|
|
|
|
|
|
// The personal status message received an event.
|
|
bool KMessView::eventFilter(QObject *obj, QEvent *event)
|
|
{
|
|
if(obj != currentAccountDisplayPic_
|
|
&& obj != contactListView_
|
|
&& obj != contactListView_->viewport() )
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
|
|
kmWarning() << "Received event '" << event->type() << "' from object '" << obj->objectName() << "'.";
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
QEvent::Type eventType = event->type();
|
|
|
|
if( obj == currentAccountDisplayPic_ )
|
|
{
|
|
if( eventType == QEvent::Enter )
|
|
{
|
|
QImage glowingImage( currentAccount_->getPicturePath() );
|
|
KIconEffect::toGamma( glowingImage, 0.8f );
|
|
currentAccountDisplayPic_->setPixmap( QPixmap::fromImage( glowingImage ) );
|
|
}
|
|
else if( eventType == QEvent::Leave )
|
|
{
|
|
currentAccountDisplayPic_->setPixmap( QPixmap( currentAccount_->getPicturePath() ) );
|
|
}
|
|
else if( eventType == QEvent::MouseButtonRelease )
|
|
{
|
|
emit showSettings();
|
|
}
|
|
else if( eventType == 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();
|
|
}
|
|
}
|
|
else if( eventType == 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;
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
currentAccount_->setPicturePath( pictureDir + pictureName );
|
|
currentAccount_->setOriginalPicturePath( mimeUrl.path() );
|
|
|
|
dropEvent->acceptProposedAction();
|
|
}
|
|
}
|
|
}
|
|
else if( obj == contactListView_->viewport() )
|
|
{
|
|
if( eventType == QEvent::ToolTip )
|
|
{
|
|
showToolTip( QCursor::pos() );
|
|
return true;
|
|
}
|
|
}
|
|
else if( obj == contactListView_ )
|
|
{
|
|
if( eventType == QEvent::KeyPress )
|
|
{
|
|
int key = static_cast<QKeyEvent*>( event )->key();
|
|
|
|
// Detect enter key press to open a chat with the selected contact
|
|
if( key == Qt::Key_Return || key == Qt::Key_Enter )
|
|
{
|
|
const QModelIndexList selection( selectionModel_->selectedIndexes() );
|
|
|
|
// Get the currently selected item
|
|
if( ! selection.isEmpty() )
|
|
{
|
|
slotItemClicked( selection.first(), false );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false; // don't stop processing.
|
|
}
|
|
|
|
|
|
|
|
// Initialize the contact list view
|
|
bool KMessView::initContactListView( QAbstractItemModel *viewModel )
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
|
|
kmDebug() << "Initializing the contact list view.";
|
|
#endif
|
|
|
|
// Create a proxy model to filter and sort the raw contact list view
|
|
viewModel_ = new ContactListModelFilter( this );
|
|
viewModel_->setSourceModel( viewModel );
|
|
|
|
// Create a selection model to get data about the list selections
|
|
selectionModel_ = new QItemSelectionModel( viewModel_ );
|
|
|
|
// Activate the custom list item painter
|
|
contactListView_->setItemDelegate( new KMessViewDelegate( contactListView_ ) );
|
|
|
|
// Set the contact list view's properties
|
|
#if QT_VERSION >= 0x040400
|
|
contactListView_->setHeaderHidden( true );
|
|
#else
|
|
contactListView_->header()->hide();
|
|
#endif
|
|
contactListView_->setAnimated( true );
|
|
contactListView_->setDragEnabled( true );
|
|
contactListView_->setDropIndicatorShown( true );
|
|
contactListView_->setDragDropOverwriteMode( false );
|
|
|
|
// TODO: support drag/drop contacts from another application!
|
|
contactListView_->setDragDropMode( QAbstractItemView::InternalMove );
|
|
contactListView_->viewport()->setAcceptDrops( true );
|
|
contactListView_->setAcceptDrops( true );
|
|
|
|
// Set the list stylesheet
|
|
slotUpdateView();
|
|
|
|
// Connect the list to the models
|
|
contactListView_->setModel( viewModel_ );
|
|
contactListView_->setSelectionModel( selectionModel_ );
|
|
|
|
// Connect the list's signals
|
|
connect( contactListView_, SIGNAL( clicked(const QModelIndex&) ),
|
|
this, SLOT( slotItemSingleClicked(const QModelIndex&) ) );
|
|
connect( contactListView_, SIGNAL( doubleClicked(const QModelIndex&) ),
|
|
this, SLOT( slotItemDoubleClicked(const QModelIndex&) ) );
|
|
connect( contactListView_, SIGNAL( collapsed(const QModelIndex&) ),
|
|
this, SLOT( slotGroupChanged(const QModelIndex&) ) );
|
|
connect( contactListView_, SIGNAL( expanded(const QModelIndex&) ),
|
|
this, SLOT( slotGroupChanged(const QModelIndex&) ) );
|
|
|
|
// Enable the right-click context menu
|
|
contactListView_->setContextMenuPolicy( Qt::CustomContextMenu );
|
|
connect( contactListView_, SIGNAL( customContextMenuRequested(const QPoint&) ),
|
|
this, SLOT( showContextMenu(const QPoint&) ) );
|
|
|
|
// Connect the selection model's signals
|
|
connect( selectionModel_, SIGNAL( selectionChanged(const QItemSelection&,const QItemSelection&) ),
|
|
this, SIGNAL( selectionChanged(const QItemSelection&) ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// Initialize the contact popup
|
|
bool KMessView::initContactPopup()
|
|
{
|
|
// Initialize context menu actions
|
|
chatWithContact_ = new QAction( QIcon::fromTheme( "user-group-new" ), i18n("Cha&t"), this );
|
|
emailContact_ = new QAction( QIcon::fromTheme( "mail-message-new" ), i18n("&Send Email"), this );
|
|
msnProfile_ = new QAction( QIcon::fromTheme( "preferences-desktop-user" ), i18n("&View Profile"), this );
|
|
chatHistory_ = new QAction( QIcon::fromTheme( "chronometer" ), i18n("Show Chat &History..."), this );
|
|
contactProperties_ = new QAction( QIcon::fromTheme( "user-properties" ), i18n("&Properties"), this );
|
|
|
|
addContact_ = new QAction( QIcon::fromTheme( "list-add" ), i18n("&Add Contact"), this );
|
|
allowContact_ = new QAction( QIcon::fromTheme( "dialog-ok-apply" ), i18n("A&llow Contact"), this );
|
|
blockContact_ = new QAction( QIcon::fromTheme( "dialog-cancel" ), i18n("&Block Contact"), this );
|
|
unblockContact_ = new QAction( QIcon::fromTheme( "dialog-ok" ), i18n("&Unblock Contact"), this );
|
|
removeContact_ = new QAction( QIcon::fromTheme( "list-remove-user" ), i18n("&Delete Contact"), this );
|
|
removeFromGroup_ = new QAction( QIcon::fromTheme( "list-remove" ), i18n("&Remove From Group"), this );
|
|
|
|
popupCopyFriendlyName_ = new QAction( i18n("&Friendly Name"), this );
|
|
popupCopyPersonalMessage_ = new QAction( i18n("&Personal Message"), this );
|
|
popupCopyHandle_ = new QAction( i18n("&Email Address"), this );
|
|
popupCopyMusic_ = new QAction( i18n("Song &Name"), this );
|
|
|
|
// Connect the actions
|
|
connect( chatWithContact_, SIGNAL(triggered(bool)), this, SLOT(slotForwardStartChat()) );
|
|
connect( emailContact_, SIGNAL(triggered(bool)), this, SLOT(slotEmailContact()) );
|
|
connect( msnProfile_, SIGNAL(triggered(bool)), this, SLOT(slotShowContactProfile()) );
|
|
connect( chatHistory_, SIGNAL(triggered(bool)), this, SLOT(slotShowChatHistory()) );
|
|
connect( contactProperties_, SIGNAL(triggered(bool)), this, SLOT(slotShowContactProperties()) );
|
|
|
|
connect( addContact_, SIGNAL(triggered(bool)), this, SLOT(slotForwardAddContact()) );
|
|
connect( allowContact_, SIGNAL(triggered(bool)), this, SLOT(slotForwardAllowContact()) );
|
|
connect( blockContact_, SIGNAL(triggered(bool)), this, SLOT(slotForwardBlockContact()) );
|
|
connect( unblockContact_, SIGNAL(triggered(bool)), this, SLOT(slotForwardUnblockContact()) );
|
|
connect( removeContact_, SIGNAL(triggered(bool)), this, SLOT(slotForwardRemoveContact()) );
|
|
connect( removeFromGroup_, SIGNAL(triggered(bool)), this, SLOT(slotForwardRemoveFromGroup()));
|
|
|
|
connect( popupCopyFriendlyName_, SIGNAL(triggered(bool)), this, SLOT( copyText() ) );
|
|
connect( popupCopyPersonalMessage_, SIGNAL(triggered(bool)), this, SLOT( copyText() ) );
|
|
connect( popupCopyHandle_, SIGNAL(triggered(bool)), this, SLOT( copyText() ) );
|
|
connect( popupCopyMusic_, SIGNAL(triggered(bool)), this, SLOT( copyText() ) );
|
|
|
|
// Create the signal mappers needed to assign all actions to the move/copy contact slots
|
|
contactCopyMapper_ = new QSignalMapper( this );
|
|
contactMoveMapper_ = new QSignalMapper( this );
|
|
connect( contactCopyMapper_, SIGNAL(mappedString(const QString&)), this, SLOT(slotForwardCopyContact(const QString&)) );
|
|
connect( contactMoveMapper_, SIGNAL(mappedString(const QString&)), this, SLOT(slotForwardMoveContact(const QString&)) );
|
|
|
|
// Initialize the sub menus to copy/move contacts
|
|
copyContactToGroup_ = new KActionMenu( i18n("&Copy to Group"), this );
|
|
moveContactToGroup_ = new KActionMenu( i18n("&Move to Group"), this );
|
|
|
|
// Fill them with the current groups names
|
|
rebuildContactActions();
|
|
|
|
// Initialize Copy sub popups
|
|
popupCopyMenu_ = new KActionMenu( i18n("&Copy"), this );
|
|
popupCopyMenu_ ->addAction( popupCopyFriendlyName_ );
|
|
popupCopyMenu_ ->addAction( popupCopyPersonalMessage_ );
|
|
popupCopyMenu_ ->addAction( popupCopyHandle_ );
|
|
popupCopyMenu_ ->addAction( popupCopyMusic_ );
|
|
popupCopyMenu_->addSeparator();
|
|
|
|
// Initialize the popup menu
|
|
// If you re-organize the menu, also consider updating ContactFrame.
|
|
contactActionPopup_ = new QMenu( "KMess", this );
|
|
|
|
contactActionPopup_->addAction( chatWithContact_ );
|
|
contactActionPopup_->addAction( emailContact_ );
|
|
contactActionPopup_->addAction( msnProfile_ );
|
|
contactActionPopup_->addAction( chatHistory_ );
|
|
contactActionPopup_->addAction( popupCopyMenu_ );
|
|
|
|
contactActionPopup_->addSeparator();
|
|
|
|
contactActionPopup_->addAction( moveContactToGroup_ );
|
|
contactActionPopup_->addAction( copyContactToGroup_ );
|
|
contactActionPopup_->addAction( removeFromGroup_ );
|
|
|
|
contactActionPopup_->addSeparator();
|
|
|
|
contactActionPopup_->addAction( addContact_ );
|
|
contactActionPopup_->addAction( allowContact_ );
|
|
contactActionPopup_->addAction( removeContact_ );
|
|
|
|
contactActionPopup_->addAction( blockContact_ );
|
|
contactActionPopup_->addAction( unblockContact_ );
|
|
|
|
contactActionPopup_->addSeparator();
|
|
|
|
contactActionPopup_->addAction( contactProperties_ );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// Initialize the group popup
|
|
bool KMessView::initGroupPopup()
|
|
{
|
|
// Initialize context menu actions
|
|
moveGroupDown_ = new QAction( QIcon::fromTheme( "arrow-down" ), i18n("Move Group &Down"), this );
|
|
moveGroupUp_ = new QAction( QIcon::fromTheme( "arrow-up" ), i18n("Move Group &Up"), this );
|
|
removeGroup_ = new QAction( QIcon::fromTheme( "edit-delete" ), i18n("Re&move Group"), this );
|
|
renameGroup_ = new QAction( QIcon::fromTheme( "edit-rename" ), i18n("Re&name Group"), this );
|
|
|
|
// Connect the actions
|
|
connect( moveGroupDown_, SIGNAL(triggered(bool)), this, SLOT( slotMoveGroupDown()) );
|
|
connect( moveGroupUp_, SIGNAL(triggered(bool)), this, SLOT( slotMoveGroupUp()) );
|
|
connect( removeGroup_, SIGNAL(triggered(bool)), this, SLOT(slotForwardRemoveGroup()) );
|
|
connect( renameGroup_, SIGNAL(triggered(bool)), this, SLOT(slotForwardRenameGroup()) );
|
|
|
|
// Initialize the popup menu
|
|
groupActionPopup_ = new QMenu( "KMess", this );
|
|
|
|
groupActionPopup_->addAction( moveGroupUp_ );
|
|
groupActionPopup_->addAction( moveGroupDown_ );
|
|
groupActionPopup_->addSeparator();
|
|
|
|
groupActionPopup_->addAction( renameGroup_ );
|
|
groupActionPopup_->addAction( removeGroup_ );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// Generate the lists of actions for the "copy/move contact to group" menus
|
|
void KMessView::rebuildContactActions()
|
|
{
|
|
// Remove all copy and move actions
|
|
foreach( QAction *action, groupCopyActionsList_ )
|
|
{
|
|
copyContactToGroup_->removeAction( action );
|
|
groupCopyActionsList_.removeAll( action );
|
|
delete action;
|
|
}
|
|
foreach( QAction *action, groupMoveActionsList_ )
|
|
{
|
|
moveContactToGroup_->removeAction( action );
|
|
groupMoveActionsList_.removeAll( action );
|
|
delete action;
|
|
}
|
|
|
|
// Create the list of groups between which contacts can be moved & copied
|
|
QList<Group*>groupsList( currentAccount_->getContactList()->getGroupList() );
|
|
foreach( Group *group, groupsList )
|
|
{
|
|
// Disallow copying and moving to special groups
|
|
if( group->isSpecialGroup() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Since we need both a name to display and a group ID to distinguish groups,
|
|
// but KActions only support the name, we'll use the Qt objectName to store
|
|
// their internal id
|
|
const QString& id = group->getId();
|
|
QAction *copyAction = new QAction( group->getName(), copyContactToGroup_ );
|
|
QAction *moveAction = new QAction( group->getName(), moveContactToGroup_ );
|
|
copyAction->setObjectName( id );
|
|
moveAction->setObjectName( id );
|
|
|
|
copyContactToGroup_->addAction( copyAction );
|
|
moveContactToGroup_->addAction( moveAction );
|
|
|
|
connect( copyAction, SIGNAL(triggered(bool)), contactCopyMapper_, SLOT(map()) );
|
|
connect( moveAction, SIGNAL(triggered(bool)), contactMoveMapper_, SLOT(map()) );
|
|
contactCopyMapper_->setMapping( copyAction, id );
|
|
contactMoveMapper_->setMapping( moveAction, id );
|
|
|
|
groupCopyActionsList_.append( copyAction );
|
|
groupMoveActionsList_.append( moveAction );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// The email label was clicked so open the user's preferred email url
|
|
void KMessView::slotEmailLabelClicked()
|
|
{
|
|
currentAccount_->openMailAtInbox();
|
|
}
|
|
|
|
|
|
|
|
// A group was expanded or collapsed
|
|
void KMessView::slotGroupChanged( const QModelIndex &index )
|
|
{
|
|
Q_ASSERT( viewModel_ != 0 );
|
|
|
|
// When searching, the expanded status of the groups is altered, but the settings
|
|
// should not be applied to the actual groups
|
|
if( ! viewModel_->filterRegExp().isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Do nothing if the group is empty
|
|
if( viewModel_->index( 0, 0, index ) == QModelIndex() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CONTACTLISTMODEL
|
|
kmDebug() << "Changed index:" << index;
|
|
kmDebug() << "Current index at that row:" << viewModel_->index( index.row(), 0, QModelIndex() );
|
|
#endif
|
|
|
|
// Skip invalid items
|
|
if( ! index.isValid()
|
|
|| ! viewModel_->hasIndex( index.row(), 0, QModelIndex() )
|
|
|| ! index.data().canConvert<QVariantMap>() )
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW_ITEM_VISIBILITY
|
|
kmDebug() << "Skipping an invalid index:" << index;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// Get the data from the selected index
|
|
const ModelDataList itemData( index.data().toMap() );
|
|
|
|
// Do nothing if the input index was not valid
|
|
if( itemData.isEmpty() || itemData[ "type" ] != ContactListModelItem::ItemGroup )
|
|
{
|
|
return;
|
|
}
|
|
|
|
Group *group = currentAccount_->getContactList()->getGroupById( itemData[ "id" ].toString() );
|
|
|
|
// Stop if the group had not been found
|
|
if( ! group )
|
|
{
|
|
return;
|
|
}
|
|
|
|
group->setExpanded( contactListView_->isExpanded( index ) );
|
|
|
|
// Save the changed setting
|
|
currentAccount_->getContactList()->saveProperties( group );
|
|
}
|
|
|
|
|
|
|
|
// Show the context menu
|
|
void KMessView::showContextMenu( const QPoint &point )
|
|
{
|
|
const ContactList *contactList = currentAccount_->getContactList();
|
|
|
|
// Get the data from the selected index
|
|
const ModelDataList itemData( getItemData( contactListView_->indexAt( point ) ) );
|
|
|
|
// The index was empty
|
|
if( itemData.isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Depending on the item type, show the appropriate menu
|
|
if( itemData[ "type" ] == ContactListModelItem::ItemContact )
|
|
{
|
|
const QString handle( itemData[ "handle" ].toString() );
|
|
const Contact *contact = contactList->getContactByHandle( handle );
|
|
|
|
bool isBlocked = contact->isBlocked();
|
|
bool isFriend = contact->isFriend();
|
|
const QStringList& contactGroups = contact->getGroupIds();
|
|
|
|
// Only if not added yet
|
|
addContact_ ->setVisible( ! isFriend );
|
|
|
|
// Only allow if contact is on the "removed" group.
|
|
allowContact_ ->setVisible( ! isFriend && ! contact->isAllowed() && ! isBlocked );
|
|
|
|
// Only one of these is enabled:
|
|
blockContact_ ->setVisible( ! isBlocked );
|
|
unblockContact_ ->setVisible( isBlocked );
|
|
|
|
removeContact_ ->setVisible( isFriend );
|
|
|
|
// Only show the group management options when it makes sense to; contacts
|
|
// in the Allowed and Removed groups are technically not part of your friends list
|
|
if( itemData[ "group" ] == SpecialGroups::ALLOWED
|
|
|| itemData[ "group" ] == SpecialGroups::REMOVED )
|
|
{
|
|
copyContactToGroup_->setVisible( false );
|
|
moveContactToGroup_->setVisible( false );
|
|
removeFromGroup_ ->setVisible( false );
|
|
}
|
|
else if( currentAccount_->getContactListDisplayMode() == CurrentAccount::VIEW_BYSTATUS
|
|
|| ( currentAccount_->getContactListDisplayMode() == CurrentAccount::VIEW_MIXED
|
|
&& ! contact->isOnline() )
|
|
)
|
|
{
|
|
copyContactToGroup_->setVisible( true );
|
|
moveContactToGroup_->setVisible( false );
|
|
removeFromGroup_ ->setVisible( false);
|
|
}
|
|
else if( contactGroups.isEmpty() )
|
|
{
|
|
moveContactToGroup_->setVisible( true );
|
|
copyContactToGroup_->setVisible( false );
|
|
removeFromGroup_ ->setVisible( false );
|
|
}
|
|
else
|
|
{
|
|
moveContactToGroup_->setVisible( isFriend );
|
|
copyContactToGroup_->setVisible( isFriend );
|
|
removeFromGroup_ ->setVisible( isFriend );
|
|
}
|
|
|
|
// Show all copy and move actions
|
|
foreach( QAction *action, groupCopyActionsList_ )
|
|
action->setVisible( true );
|
|
foreach( QAction *action, groupMoveActionsList_ )
|
|
action->setVisible( true );
|
|
|
|
// Then hide only the invalid choices
|
|
int enabledCopyActions = groupCopyActionsList_.count();
|
|
int enabledMoveActions = groupMoveActionsList_.count();
|
|
foreach( const QString &groupId, contactGroups )
|
|
{
|
|
foreach( QAction *action, groupCopyActionsList_ )
|
|
{
|
|
// See initContactPopup() for the choice of using objectName to store the group ID
|
|
if( action->objectName() == groupId )
|
|
{
|
|
enabledCopyActions--;
|
|
action->setVisible( false );
|
|
break;
|
|
}
|
|
}
|
|
foreach( QAction *action, groupMoveActionsList_ )
|
|
{
|
|
// See initContactPopup() for the choice of using objectName to store the group ID
|
|
if( action->objectName() == groupId )
|
|
{
|
|
enabledMoveActions--;
|
|
action->setVisible( false );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no actions have been enabled in a menu, disable it too
|
|
if( enabledCopyActions <= 0 )
|
|
{
|
|
copyContactToGroup_ ->setVisible( false );
|
|
}
|
|
if( enabledMoveActions <= 0 )
|
|
{
|
|
moveContactToGroup_ ->setVisible( false );
|
|
}
|
|
|
|
// Clear the previous actions to avoid duplication of links
|
|
qDeleteAll( groupCopyLinkActionsList_ );
|
|
groupCopyLinkActionsList_.clear();
|
|
|
|
// If detailed contact information is available, search for links
|
|
if( contact != 0 )
|
|
{
|
|
// Append friendly name and personal message so cycle only one time to search links
|
|
const QString nameAndPm( contact->getFriendlyName() + ' ' + contact->getPersonalMessage() );
|
|
QStringList links;
|
|
|
|
//Initialize index, link RegExp and found boolean for insert separator only at first found link
|
|
int pos = 0;
|
|
QRegularExpression linkRegExp( "(http://|https://|ftp://|sftp://|www\\..)\\S+" );
|
|
|
|
QRegularExpressionMatch match;
|
|
while( ( match = linkRegExp.match( nameAndPm, pos ) ).hasMatch() )
|
|
{
|
|
// Grep the found link and update the position for the next cycle
|
|
QString foundLink( match.captured( 0 ) );
|
|
pos = match.capturedEnd();
|
|
|
|
// Skip duplicated links
|
|
if( links.contains( foundLink ) )
|
|
{
|
|
continue;
|
|
}
|
|
links.append( foundLink );
|
|
|
|
// Put the found link into QAction, replace & with && to avoid shortcut problem and use tooltip
|
|
// for future grep of link ( to avoid shortcut problem too )
|
|
QAction *popupCopyLink = new QAction( foundLink.replace( '&', "&&" ), this );
|
|
popupCopyLink->setToolTip( foundLink );
|
|
connect( popupCopyLink, SIGNAL( triggered( bool ) ), this, SLOT( copyText() ) );
|
|
|
|
// Append current QAction to the copy link list
|
|
groupCopyLinkActionsList_.append( popupCopyLink );
|
|
|
|
// Add action to copy menu
|
|
popupCopyMenu_->addAction( popupCopyLink );
|
|
}
|
|
}
|
|
|
|
// Forbid chatting with yourself
|
|
chatWithContact_->setEnabled( handle != currentAccount_->getHandle() );
|
|
|
|
// Set tooltip ( informations ) of other copy actions
|
|
popupCopyFriendlyName_ ->setToolTip( contact->getFriendlyName() );
|
|
popupCopyPersonalMessage_->setToolTip( contact->getPersonalMessage() );
|
|
popupCopyHandle_ ->setToolTip( contact->getHandle() );
|
|
popupCopyMusic_ ->setToolTip( contact->getCurrentMediaString() );
|
|
// Hide the unavailable options
|
|
popupCopyPersonalMessage_->setVisible( ! contact->getPersonalMessage ().isEmpty() );
|
|
popupCopyMusic_ ->setVisible( ! contact->getCurrentMediaString().isEmpty() );
|
|
|
|
// Show the popup
|
|
contactActionPopup_->popup( QCursor::pos() );
|
|
}
|
|
else if( itemData[ "type" ] == ContactListModelItem::ItemGroup )
|
|
{
|
|
const Group *group = contactList->getGroupById( itemData[ "id" ].toString() );
|
|
|
|
// No context menu for special groups
|
|
if( ! group || group->isSpecialGroup() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int pos = group->getSortPosition();
|
|
|
|
moveGroupUp_ ->setEnabled( contactList->getGroupBySortPosition( pos - 1, false ) != 0 );
|
|
moveGroupDown_ ->setEnabled( contactList->getGroupBySortPosition( pos + 1, true ) != 0 );
|
|
|
|
// Show the popup
|
|
groupActionPopup_->popup( QCursor::pos() );
|
|
}
|
|
else
|
|
{
|
|
kmWarning() << "Invalid type of list item" << itemData[ "type" ];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Copy some details of the contact to the clipboard.
|
|
void KMessView::copyText()
|
|
{
|
|
// Grep the action sender for copy the tooltip that contains the information
|
|
QAction *action = static_cast<QAction*>( const_cast<QObject*>( sender() ) );
|
|
QApplication::clipboard()->setText( action->toolTip() );
|
|
}
|
|
|
|
|
|
|
|
// Show the contact & group tool tip
|
|
void KMessView::showToolTip( const QPoint &point )
|
|
{
|
|
const QModelIndex index( contactListView_->indexAt( contactListView_->mapFromGlobal( point ) ) );
|
|
QString tipText;
|
|
|
|
// Read the actual data off the model index
|
|
const ModelDataList itemData( index.data().toMap() );
|
|
|
|
// Do nothing if the index is not valid
|
|
if( ! index.isValid() || itemData.isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
const QString textAlignment( QApplication::isRightToLeft() ? "right" : "left" );
|
|
const QString textDirection( "dir='" + QString(QApplication::isRightToLeft() ? "rtl" : "ltr") + "'" );
|
|
|
|
// Paint the different types of list items
|
|
// see ContactListModelItem::data() for a list of itemData elements (role == DataRole)
|
|
switch( itemData[ "type" ].toInt() )
|
|
{
|
|
case ContactListModelItem::ItemContact:
|
|
{
|
|
tipText = "<html " + textDirection + "><table border='0' " + textDirection + ">"
|
|
"<tr>"
|
|
"<td valign='top'>"
|
|
"<table style='border-width:1px;border-color:palette(mid);border-style:solid;'>"
|
|
"<tr><td><img src='%1' width='96' height='96'/></td></tr>"
|
|
"</table>"
|
|
"</td>"
|
|
"<td align='" + textAlignment + "' valign='top' style='padding-" + textAlignment + ":10px;'>"
|
|
"<b>%2</b> (%3)<br/>"
|
|
"%4 %5 %6"
|
|
"</td>"
|
|
"</tr>"
|
|
"<tr>"
|
|
"<td colspan='2'><hr width='98%'/></td>"
|
|
"</tr>"
|
|
"</table>"
|
|
"<table border='0' " + textDirection + " align='" + textAlignment + "'>"
|
|
"%7%8%9%10%11"
|
|
"</table>"
|
|
"</html>";
|
|
|
|
// Add the display picture (if there is any), or the default KMess logo
|
|
tipText = tipText.arg( itemData[ "displayPicture" ].toString() );
|
|
|
|
// Add the contact's real name - the alias is not shown:
|
|
// you can read it in the contact list, after all
|
|
QString friendlyName( itemData[ "trueFriendly" ].toString() );
|
|
// Escape HTML, replace standard emoticons, but no links and formatting
|
|
RichTextParser::parseMsnString( friendlyName, true, true, false, false );
|
|
tipText = tipText.arg( friendlyName );
|
|
|
|
// Add status
|
|
tipText = tipText.arg( MsnStatus::getName( (Status) itemData[ "status" ].toInt() ) );
|
|
|
|
// Add the personal message
|
|
QString personalMessage( itemData[ "personalMessage" ].toString() );
|
|
if( ! personalMessage.isEmpty() )
|
|
{
|
|
// Escape HTML, replace standard emoticons, but no links and formatting
|
|
RichTextParser::parseMsnString( personalMessage, true, true, false, false );
|
|
tipText = tipText.arg( "<i>" + personalMessage + "</i><br/>" );
|
|
}
|
|
else
|
|
{
|
|
tipText = tipText.arg( "" );
|
|
}
|
|
|
|
// If the contact is listening to music, show it
|
|
const QString mediaType ( itemData[ "mediaType" ].toString() );
|
|
const QString mediaString( itemData[ "mediaString" ].toString() );
|
|
if( ! mediaString.isEmpty() )
|
|
{
|
|
QString mediaEmoticon;
|
|
|
|
// Determine icon for special media types
|
|
if( mediaType == "Music" )
|
|
{
|
|
mediaEmoticon = EmoticonManager::instance()->getReplacement( "(8)", true );
|
|
}
|
|
else if( mediaType == "Gaming" )
|
|
{
|
|
mediaEmoticon = EmoticonManager::instance()->getReplacement( "(xx)", true );
|
|
}
|
|
|
|
tipText = tipText.arg( "<i><small>" +
|
|
mediaEmoticon + mediaString.toHtmlEscaped() +
|
|
"</small></i><br/>" );
|
|
}
|
|
else
|
|
{
|
|
tipText = tipText.arg( "" );
|
|
}
|
|
|
|
// Add a message about our presence in this contact's list
|
|
// fontknocker: removed until reverse list memberships are resolved.
|
|
// if( itemData[ "isReverse" ].toBool() )
|
|
// {
|
|
tipText = tipText.arg( "" );
|
|
// }
|
|
// else
|
|
// {
|
|
// tipText = tipText.arg( "<i>" +
|
|
// i18nc( "Message in list tooltip",
|
|
// "This contact does not have you in his or her contact list." ) +
|
|
// "</i>" );
|
|
// }
|
|
|
|
// Here starts the list of simple property:value lines
|
|
|
|
// Adding a space to better separate the properties and values
|
|
QString propertyLine;
|
|
if( QApplication::isLeftToRight() )
|
|
{
|
|
propertyLine = "<tr><td align='right'><i>%1:</i> </td><td align='left'>%2</td></tr>";
|
|
}
|
|
else
|
|
{
|
|
propertyLine = "<tr><td align='right'>%2</td><td align='left'> <i>%1:</i></td></tr>";
|
|
}
|
|
|
|
// Add handle
|
|
tipText = tipText.arg( propertyLine.arg( i18nc( "Contact email label in list tooltip", "Email address" ) )
|
|
.arg( itemData[ "handle" ].toString() ) );
|
|
|
|
// Add client identifier
|
|
const QString clientName( itemData[ "clientName" ].toString() );
|
|
if( ! clientName.isEmpty() )
|
|
{
|
|
tipText = tipText.arg( propertyLine.arg( i18nc( "Contact Live Messenger client label in list tooltip", "Client" ) )
|
|
.arg( clientName.toHtmlEscaped() ) );
|
|
}
|
|
else
|
|
{
|
|
tipText = tipText.arg( "" );
|
|
}
|
|
|
|
//add info whether the contact is blocked
|
|
QString blockedString;
|
|
if( itemData[ "isBlocked" ].toBool() )
|
|
{
|
|
blockedString = i18n( "Yes" );
|
|
}
|
|
else
|
|
{
|
|
blockedString = i18n( "No" );
|
|
}
|
|
|
|
tipText = tipText.arg( propertyLine.arg( i18nc( "Contact blocked status label in list tooltip", "Blocked" ) )
|
|
.arg( blockedString ) );
|
|
|
|
// Add last seen date
|
|
const QDateTime lastSeen( itemData[ "lastSeen" ].toDateTime() );
|
|
QString lastSeenString;
|
|
|
|
if( ( (Status) itemData[ "status" ].toInt() ) != STATUS_OFFLINE )
|
|
{
|
|
lastSeenString = i18n( "Connected" );
|
|
}
|
|
else if( lastSeen.toSecsSinceEpoch() == 0
|
|
|| lastSeen.toSecsSinceEpoch() == UINT_MAX
|
|
|| lastSeen.isNull() )
|
|
{
|
|
lastSeenString = i18n( "Not seen yet" );
|
|
}
|
|
else
|
|
{
|
|
lastSeenString = lastSeen.toString();
|
|
}
|
|
|
|
tipText = tipText.arg( propertyLine.arg( i18nc( "Contact last presence label in list tooltip", "Last seen" ) )
|
|
.arg( lastSeenString ) );
|
|
|
|
// Add last message date
|
|
const QDateTime lastMessage( itemData[ "lastMessage" ].toDateTime() );
|
|
QString lastMessageString;
|
|
if( lastMessage.toSecsSinceEpoch() == 0
|
|
|| lastMessage.toSecsSinceEpoch() == UINT_MAX
|
|
|| lastMessage.isNull() )
|
|
{
|
|
lastMessageString = i18n( "No messages yet" );
|
|
}
|
|
else
|
|
{
|
|
lastMessageString = lastMessage.toString();
|
|
}
|
|
|
|
tipText = tipText.arg( propertyLine.arg( i18nc( "Contact last message label in list tooltip", "Last message" ) )
|
|
.arg( lastMessageString ) );
|
|
}
|
|
break;
|
|
|
|
|
|
case ContactListModelItem::ItemGroup:
|
|
tipText = "<qt><div " + textDirection + ">%1<br/><i>(%2)</i></div></qt>";
|
|
|
|
// Add the group name
|
|
tipText = tipText.arg( i18nc( "Group name in group tooltip", "Group <b>%1</b>", itemData[ "name" ].toString() ) );
|
|
|
|
// Non-empty and non-special groups also have an Online contacts count.
|
|
// (Individuals is treated as a non-special group)
|
|
if( ( ! itemData[ "isSpecialGroup" ].toBool() || itemData[ "id" ].toString() == SpecialGroups::INDIVIDUALS )
|
|
&& itemData[ "totalContacts" ].toInt() > 0 )
|
|
{
|
|
tipText = tipText.arg( "%1%2" ).arg( i18ncp( "Contact counters in normal group tooltip, first part",
|
|
"%1 contact, ", "%1 contacts, ",
|
|
itemData[ "totalContacts" ].toInt() ) )
|
|
.arg( i18ncp( "Contact counters in normal group tooltip, second part",
|
|
"%1 online", "%1 online",
|
|
itemData[ "onlineContacts" ].toInt() ) );
|
|
}
|
|
else
|
|
{
|
|
tipText = tipText.arg( i18ncp( "Contacts count in special group tooltip",
|
|
"%1 contact", "%1 contacts", itemData[ "totalContacts" ].toInt() ) );
|
|
}
|
|
break;
|
|
|
|
|
|
default:
|
|
// Nothing to do!
|
|
return;
|
|
}
|
|
|
|
// Show the tooltip
|
|
QToolTip::showText( point, tipText, contactListView_, contactListView_->visualRect( index ) );
|
|
}
|
|
|
|
|
|
|
|
// Email the current contact
|
|
void KMessView::slotEmailContact()
|
|
{
|
|
// Get the data from the currently selected index
|
|
const ModelDataList itemData( getItemData() );
|
|
|
|
// The index was not valid, do nothing
|
|
if( itemData.isEmpty() || itemData[ "type" ] != ContactListModelItem::ItemContact )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Send mail to that account
|
|
currentAccount_->openMailAtCompose( itemData[ "handle" ].toString() );
|
|
}
|
|
|
|
|
|
// An item was double clicked
|
|
void KMessView::slotItemDoubleClicked( const QModelIndex &index )
|
|
{
|
|
// Detect accidental clicks
|
|
if( QGuiApplication::styleHints()->singleClickActivation() )
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
|
|
kmDebug() << "Accidental double click detected, skipping click event.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
slotItemClicked( index );
|
|
}
|
|
|
|
|
|
|
|
void KMessView::slotItemSingleClicked( const QModelIndex &index )
|
|
{
|
|
bool accidental = false;
|
|
if( ! QGuiApplication::styleHints()->singleClickActivation() )
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
|
|
kmDebug() << "Accidental single click detected, skipping click event.";
|
|
#endif
|
|
|
|
accidental = true;
|
|
}
|
|
|
|
slotItemClicked( index, accidental );
|
|
}
|
|
|
|
|
|
|
|
// An item was clicked
|
|
void KMessView::slotItemClicked( const QModelIndex &index, const bool accidental )
|
|
{
|
|
// Get the data from the selected index
|
|
const ModelDataList itemData( getItemData( index ) );
|
|
|
|
// The index was empty
|
|
if( itemData.isEmpty() )
|
|
{
|
|
kmWarning() << "Invalid list index given!";
|
|
return;
|
|
}
|
|
|
|
switch( itemData[ "type" ].toInt() )
|
|
{
|
|
// Expand/collapse the groups when clicked
|
|
case ContactListModelItem::ItemGroup:
|
|
|
|
// Do nothing if the group is empty
|
|
if( viewModel_->index( 0, 0, index ) == QModelIndex() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Keep the group expansion state volatile while filtering (don't save it)
|
|
if( ! viewModel_->filterRegExp().isEmpty() )
|
|
{
|
|
contactListView_->setExpanded( index, ! contactListView_->isExpanded( index ) );
|
|
}
|
|
else
|
|
{
|
|
contactListView_->setExpanded( index, ! itemData[ "isExpanded" ].toBool() );
|
|
}
|
|
break;
|
|
|
|
|
|
// Request a chat with that contact
|
|
case ContactListModelItem::ItemContact:
|
|
if( ! accidental )
|
|
{
|
|
// Tell the Chat Master we'd like to start a chat.
|
|
emit requestChat( itemData[ "handle" ].toString() );
|
|
}
|
|
break;
|
|
|
|
|
|
default:
|
|
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
|
|
kmWarning() << "Nothing to do!";
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Move down the selected group
|
|
void KMessView::slotMoveGroupDown()
|
|
{
|
|
const ContactList *contactList = currentAccount_->getContactList();
|
|
|
|
// Get the group data from the selected index
|
|
const ModelDataList itemData( getItemData() );
|
|
Group *group = contactList->getGroupById( itemData[ "id" ].toString() );
|
|
if( ! group || itemData.isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Obtain the next listed group
|
|
Group *nextGroup = contactList->getGroupBySortPosition( group->getSortPosition() + 1, true );
|
|
if( ! nextGroup )
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
|
|
kmDebug() << "Selected group:" << group->getId() << "to move down in place of group:" << nextGroup->getId();
|
|
#endif
|
|
|
|
// Get the group sorting position for both groups
|
|
int oldGroupPos = group ->getSortPosition();
|
|
int newGroupPos = nextGroup->getSortPosition();
|
|
|
|
// Swap them
|
|
group ->setSortPosition( newGroupPos );
|
|
nextGroup->setSortPosition( oldGroupPos );
|
|
|
|
// Reset old sort value so KMess is not confused (XXX HACK FIXME OMG)
|
|
group ->setOldSortPosition( 0 );
|
|
nextGroup->setOldSortPosition( 0 );
|
|
}
|
|
|
|
|
|
|
|
// Move up the selected group
|
|
void KMessView::slotMoveGroupUp()
|
|
{
|
|
const ContactList *contactList = currentAccount_->getContactList();
|
|
|
|
// Get the group data from the selected index
|
|
const ModelDataList itemData( getItemData() );
|
|
Group *group = contactList->getGroupById( itemData[ "id" ].toString() );
|
|
if( ! group || itemData.isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Obtain the previous listed group
|
|
Group *prevGroup = contactList->getGroupBySortPosition( group->getSortPosition() - 1, false );
|
|
if( ! prevGroup )
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
|
|
kmDebug() << "Selected group:" << group->getId() << "to move up in place of group:" << prevGroup->getId();
|
|
#endif
|
|
|
|
// Get the group sorting position for both groups
|
|
int oldGroupPos = group ->getSortPosition();
|
|
int newGroupPos = prevGroup->getSortPosition();
|
|
|
|
// Swap them
|
|
group ->setSortPosition( newGroupPos );
|
|
prevGroup->setSortPosition( oldGroupPos );
|
|
|
|
// Save the changed setting
|
|
contactList->saveProperties( group );
|
|
contactList->saveProperties( prevGroup );
|
|
|
|
// Reset old sort value so KMess is not confused (XXX HACK FIXME OMG)
|
|
group ->setOldSortPosition( 0 );
|
|
prevGroup->setOldSortPosition( 0 );
|
|
}
|
|
|
|
|
|
|
|
// Forward the "add contact" menu action
|
|
void KMessView::slotForwardAddContact()
|
|
{
|
|
// Get the data from the currently selected index
|
|
const ModelDataList itemData( getItemData() );
|
|
|
|
// The index was not empty
|
|
if( itemData.isEmpty() || itemData[ "type" ] != ContactListModelItem::ItemContact )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Add that contact
|
|
emit addContact( itemData[ "handle" ].toString() );
|
|
}
|
|
|
|
|
|
|
|
// Forward the "allow contact" menu action
|
|
void KMessView::slotForwardAllowContact()
|
|
{
|
|
// Get the data from the currently selected index
|
|
const ModelDataList itemData( getItemData() );
|
|
|
|
// The index was not empty
|
|
if( itemData.isEmpty() || itemData[ "type" ] != ContactListModelItem::ItemContact )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Allow that contact
|
|
emit allowContact( itemData[ "handle" ].toString() );
|
|
}
|
|
|
|
|
|
|
|
// Forward the "block contact" menu action
|
|
void KMessView::slotForwardBlockContact()
|
|
{
|
|
// Get the data from the currently selected index
|
|
const ModelDataList itemData( getItemData() );
|
|
|
|
// The index was not empty
|
|
if( itemData.isEmpty() || itemData[ "type" ] != ContactListModelItem::ItemContact )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Block that contact
|
|
emit blockContact( itemData[ "handle" ].toString() );
|
|
}
|
|
|
|
|
|
|
|
// Called when a contact should be copied
|
|
void KMessView::slotForwardCopyContact( const QString &newGroupId )
|
|
{
|
|
// Get the data from the currently selected index
|
|
const ModelDataList itemData( getItemData() );
|
|
|
|
// The index was not empty
|
|
if( itemData.isEmpty() || itemData[ "type" ] != ContactListModelItem::ItemContact )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Copy that contact
|
|
emit copyContact( itemData[ "handle" ].toString(), newGroupId );
|
|
}
|
|
|
|
|
|
|
|
// Called when a contact should be moved
|
|
void KMessView::slotForwardMoveContact( const QString &newGroupId )
|
|
{
|
|
// Get the data from the currently selected index
|
|
const ModelDataList itemData( getItemData() );
|
|
|
|
// The selection was not valid
|
|
if( itemData.isEmpty() || itemData[ "type" ] != ContactListModelItem::ItemContact )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get the old group ID, and if it's not valid, set it to zero
|
|
QString oldGroupId;
|
|
if( itemData[ "isInSpecialGroup" ].toBool() == false )
|
|
{
|
|
oldGroupId = itemData[ "group" ].toString();
|
|
}
|
|
else
|
|
{
|
|
oldGroupId = "0";
|
|
}
|
|
|
|
// If the old and new groups are the same one, do nothing
|
|
if( oldGroupId == newGroupId )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Move that contact
|
|
emit moveContact( itemData[ "handle" ].toString(), oldGroupId, newGroupId );
|
|
}
|
|
|
|
|
|
|
|
// Forward the "remove contact" menu action
|
|
void KMessView::slotForwardRemoveContact()
|
|
{
|
|
// Get the data from the currently selected index
|
|
const ModelDataList itemData( getItemData() );
|
|
|
|
// The index was empty
|
|
if( itemData.isEmpty() || itemData[ "type" ] != ContactListModelItem::ItemContact )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Remove that contact
|
|
emit removeContact( itemData[ "handle" ].toString() );
|
|
}
|
|
|
|
|
|
void KMessView::slotForwardRemoveFromGroup()
|
|
{
|
|
// Get the data from the currently selected index
|
|
const ModelDataList itemData( getItemData() );
|
|
|
|
// The index was invalid
|
|
if( itemData.isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get the ID of the group which contained this instance of the contact
|
|
const ModelDataList parentItemData( getItemData( selectionModel_->selectedIndexes().first().parent() ) );
|
|
if( parentItemData.isEmpty() || parentItemData[ "type" ] != ContactListModelItem::ItemGroup )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Remove that contact
|
|
emit removeContactFromGroup( itemData[ "handle" ].toString(), parentItemData[ "id" ].toString() );
|
|
}
|
|
|
|
|
|
// Forward the "remove group" menu action
|
|
void KMessView::slotForwardRemoveGroup()
|
|
{
|
|
// Get the data from the currently selected index
|
|
const ModelDataList itemData( getItemData() );
|
|
|
|
// The index was invalid
|
|
if( itemData.isEmpty() || itemData[ "type" ] != ContactListModelItem::ItemGroup )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Remove that group
|
|
emit removeGroup( itemData[ "id" ].toString() );
|
|
}
|
|
|
|
|
|
|
|
|
|
// Forward the "rename group" menu action
|
|
void KMessView::slotForwardRenameGroup()
|
|
{
|
|
// Get the data from the currently selected index
|
|
const ModelDataList itemData( getItemData() );
|
|
|
|
// The index was invalid
|
|
if( itemData.isEmpty() || itemData[ "type" ] != ContactListModelItem::ItemGroup )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Rename that group
|
|
emit renameGroup( itemData[ "id" ].toString() );
|
|
}
|
|
|
|
|
|
|
|
// Forward the "start chat" menu action
|
|
void KMessView::slotForwardStartChat()
|
|
{
|
|
// Only one index can be returned at once by the contact list (has single selection mode)
|
|
slotItemClicked( selectionModel_->selectedIndexes().first() );
|
|
}
|
|
|
|
|
|
|
|
// Forward the "unblock contact" menu action
|
|
void KMessView::slotForwardUnblockContact()
|
|
{
|
|
// Get the data from the currently selected index
|
|
const ModelDataList itemData( getItemData() );
|
|
|
|
// The index was not empty
|
|
if( itemData.isEmpty() || itemData[ "type" ] != ContactListModelItem::ItemContact )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Unblock that contact
|
|
emit unblockContact( itemData[ "handle" ].toString() );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Update the friendly name from the inline editing label.
|
|
*
|
|
* Sends to the server the new friendly name selected by clicking
|
|
* the friendly name inline editing label.
|
|
*
|
|
* @param refreshOnly When true, the FN widget is only updated with the
|
|
* current account's name. When false, it is updated
|
|
* with the default message, and nothing is sent to
|
|
* the server.
|
|
*/
|
|
void KMessView::updateFriendlyName( bool refreshOnly )
|
|
{
|
|
const QString defaultTooltip( i18nc( "Default friendly name tooltip", "Click here to change your friendly name" ) );
|
|
const QString defaultName( currentAccount_->getFriendlyName( STRING_ORIGINAL ).trimmed() );
|
|
|
|
// When refreshing the message or when the focus losing isn't voluntary, don't update it on the server
|
|
if( refreshOnly || ! isActiveWindow() || ! topLevelWidget()->isActiveWindow() )
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
|
|
kmDebug() << "Refreshing name only: initial update or non-intentional focus lose.";
|
|
#endif
|
|
|
|
// Refresh the message
|
|
friendlyNameInput_->setToolTip( defaultTooltip );
|
|
friendlyNameInput_->setText( defaultName );
|
|
return;
|
|
}
|
|
|
|
// Get message
|
|
QString newName( friendlyNameInput_->text().trimmed() );
|
|
|
|
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
|
|
kmDebug() << "Updating friendly name to:" << newName;
|
|
#endif
|
|
|
|
// Set a default name (the email address) if the field is left empty
|
|
if( newName.isEmpty() )
|
|
{
|
|
newName = currentAccount_->getHandle();
|
|
}
|
|
|
|
// Only update the name if it's been changed
|
|
if( defaultName == newName )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Update the name and save the changes
|
|
currentAccount_->setFriendlyName( newName );
|
|
friendlyNameInput_->setToolTip( newName );
|
|
// Commit changes to disk, now
|
|
currentAccount_->saveProperties();
|
|
|
|
// Notify changes to the MSN server
|
|
emit changeFriendlyName( newName );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Update the personal message from the inline editing label.
|
|
*
|
|
* Sends to the server the new personal message selected by clicking
|
|
* the personal message inline editing label.
|
|
*
|
|
* @param refreshOnly When true, the PM widget is only updated with the
|
|
* current account's message. When false, it is updated
|
|
* with the default message, and nothing is sent to
|
|
* the server.
|
|
*/
|
|
void KMessView::updatePersonalMessage( bool refreshOnly )
|
|
{
|
|
const QString defaultMessage( i18nc( "Default personal message shown in the contact list", "[i]Click to set a personal message[/i]" ) );
|
|
const QString defaultTooltip( i18nc( "Default personal message tooltip", "Click here to insert a message to show to your contacts: they will see it along with your friendly name" ) );
|
|
|
|
// When refreshing the message or when the focus losing isn't voluntary, don't update it on the server
|
|
if( refreshOnly || ! isActiveWindow() || ! topLevelWidget()->isActiveWindow() )
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
|
|
kmDebug() << "Refreshing message only: initial update or non-intentional focus lose.";
|
|
#endif
|
|
|
|
// Refresh the message
|
|
const QString message( currentAccount_->getPersonalMessage( STRING_ORIGINAL ).trimmed() );
|
|
if( message.isEmpty() )
|
|
{
|
|
personalMessageInput_->setToolTip( defaultTooltip );
|
|
personalMessageInput_->setText( defaultMessage, true );
|
|
}
|
|
else
|
|
{
|
|
personalMessageInput_->setToolTip( message );
|
|
personalMessageInput_->setText( message );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Get message
|
|
const QString message( personalMessageInput_->text().trimmed() );
|
|
|
|
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
|
|
kmDebug() << "Updating personal message to:" << message;
|
|
#endif
|
|
|
|
// Protect against weird situations where the label is seen as text.
|
|
// The lost focus event is also sent when the while window looses focus
|
|
if( message == defaultMessage )
|
|
{
|
|
kmWarning() << "Personal message label was about to be used as personal message!";
|
|
return;
|
|
}
|
|
|
|
// Only update the message if it's been changed
|
|
if( currentAccount_->getPersonalMessage( STRING_ORIGINAL ) != message )
|
|
{
|
|
// Update message and save the changes
|
|
currentAccount_->setPersonalMessage( message );
|
|
personalMessageInput_->setToolTip( message );
|
|
// Commit changes to disk, now
|
|
currentAccount_->saveProperties();
|
|
|
|
// Notify changes to the MSN server
|
|
emit changePersonalMessage( message );
|
|
}
|
|
|
|
// Restore the placeholder.
|
|
if( message.isEmpty() )
|
|
{
|
|
personalMessageInput_->setToolTip( defaultTooltip );
|
|
personalMessageInput_->setText( defaultMessage, true );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Delay a bit updating the groups status
|
|
void KMessView::slotScheduleGroupUpdate()
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW_ITEM_VISIBILITY
|
|
kmDebug() << "Scheduling another groups update";
|
|
#endif
|
|
updateTimer_.start( 250 );
|
|
}
|
|
|
|
|
|
|
|
// Slot for searching contact
|
|
void KMessView::slotSearchContact( const QString& searchFor )
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW_ITEM_VISIBILITY
|
|
kmDebug() << "Search expression changed to:" << searchFor;
|
|
#endif
|
|
|
|
QString searchExpression;
|
|
const QString regexpMagic( "regexp:" );
|
|
|
|
// If the search string starts with "regexp:", allow searching with a regexp :)
|
|
if( searchFor.startsWith( regexpMagic ) )
|
|
{
|
|
searchExpression = searchFor.mid( regexpMagic.length() );
|
|
|
|
// Colorize the search bar when using regexps, according to if they're valid or invalid
|
|
if( QRegularExpression( searchExpression ).isValid() )
|
|
{
|
|
searchEdit_->setStyleSheet( "background-color: #bbffbb" );
|
|
}
|
|
else
|
|
{
|
|
searchEdit_->setStyleSheet( "background-color: #ffbbbb" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Remove trailing spaces and other unneeded (invisible) characters, then escape the searchstring
|
|
searchExpression = QRegularExpression::escape( searchFor.simplified() );
|
|
searchEdit_->setStyleSheet( "" );
|
|
}
|
|
|
|
// True if the user was filtering for something and wanted to filter for something else;
|
|
// used to maintain the group expansion states while refining the search terms
|
|
bool isFilteringAgain = ( ! searchExpression.isEmpty() && ! viewModel_->filterRegExp().isEmpty() );
|
|
|
|
viewModel_->setFilterRegExp( searchExpression );
|
|
|
|
// Show or hide groups which do or do not contain results
|
|
|
|
for( int row = viewModel_->rowCount() - 1; row >= 0; --row )
|
|
{
|
|
const QModelIndex index( viewModel_->index( row, 0, QModelIndex() ) );
|
|
|
|
// Skip invalid items
|
|
if( ! index.isValid() )
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW_ITEM_VISIBILITY
|
|
kmDebug() << "Skipping invalid index" << index;
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
// Access the index's data
|
|
const ModelDataList itemData( getItemData( index ) );
|
|
|
|
// Skip items which have no children
|
|
if( itemData.isEmpty() || itemData[ "type" ].toInt() != ContactListModelItem::ItemGroup )
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW_ITEM_VISIBILITY
|
|
kmDebug() << "Skipping non-group index" << index;
|
|
kmDebug() << "-- Index data:" << itemData;
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
// Hide groups which don't contain results
|
|
if( ! searchFor.isEmpty() )
|
|
{
|
|
bool isHidden = ( ! viewModel_->hasChildren( index ) );
|
|
|
|
contactListView_->setRowHidden( row, QModelIndex(), isHidden );
|
|
|
|
if( ! isFilteringAgain )
|
|
{
|
|
contactListView_->setExpanded( index, true );
|
|
}
|
|
}
|
|
// When the search terms are removed, enable back all groups
|
|
else
|
|
{
|
|
contactListView_->setRowHidden( row, QModelIndex(), false );
|
|
contactListView_->setExpanded( index, itemData[ "isExpanded" ].toBool() );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Display the chat history of the current contact
|
|
void KMessView::slotShowChatHistory()
|
|
{
|
|
// Get the data from the currently selected index
|
|
const ModelDataList itemData( getItemData() );
|
|
|
|
// The index was not empty
|
|
if( itemData.isEmpty() || itemData[ "type" ] != ContactListModelItem::ItemContact )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ChatHistoryDialog *dialog = new ChatHistoryDialog( this );
|
|
|
|
// Show the selected contact. If no log exists
|
|
// (no chats with that person yet), then don't show the History dialog
|
|
if( dialog->setInitialContact( currentAccount_->getHandle(), itemData[ "handle" ].toString() ) )
|
|
{
|
|
dialog->show();
|
|
}
|
|
else
|
|
{
|
|
if( currentAccount_->getSaveChats() )
|
|
{
|
|
KMessageBox::error( this,
|
|
i18n( "No chat logs could be found for this contact." ),
|
|
i18n( "No chat history found" ) );
|
|
}
|
|
else
|
|
{
|
|
KMessageBox::error( this,
|
|
i18n( "No chat logs could be found for this contact. Note that new chats are not logged; if you want your chats to be logged, you can enable it in your account settings." ),
|
|
i18n( "No chat history found") );
|
|
}
|
|
|
|
delete dialog;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Display the profile of the current contact
|
|
void KMessView::slotShowContactProfile()
|
|
{
|
|
// Get the data from the currently selected index
|
|
const ModelDataList itemData( getItemData() );
|
|
|
|
// The index was not empty
|
|
if( itemData.isEmpty() || itemData[ "type" ] != ContactListModelItem::ItemContact )
|
|
{
|
|
return;
|
|
}
|
|
|
|
const QString handle( itemData[ "handle" ].toString() );
|
|
Q_UNUSED( handle );
|
|
KMessShared::openBrowser( QUrl( "https://nina.chat/contacts/" ) );
|
|
}
|
|
|
|
|
|
|
|
// Display the properties of the current contact
|
|
void KMessView::slotShowContactProperties()
|
|
{
|
|
// Get the data from the currently selected index
|
|
const ModelDataList itemData( getItemData() );
|
|
|
|
// The index was not empty
|
|
if( itemData.isEmpty() || itemData[ "type" ] != ContactListModelItem::ItemContact )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get the contact data to pass to the properties dialog
|
|
Contact *contact = currentAccount_->getContactList()->getContactByHandle( itemData[ "handle" ].toString() );
|
|
|
|
// If no corresponding contact has been found, do nothing
|
|
if( ! contact )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ContactPropertiesDialog *dialog = new ContactPropertiesDialog( this );
|
|
connect( dialog, SIGNAL( removeContactFromGroup(QString,QString) ),
|
|
this, SIGNAL( removeContactFromGroup(QString,QString) ) );
|
|
connect( dialog, SIGNAL( addContactToGroup(QString,QString) ),
|
|
this, SIGNAL( copyContact(QString,QString) ) );
|
|
dialog->launch( contact );
|
|
delete dialog;
|
|
}
|
|
|
|
|
|
|
|
//toggle now listening
|
|
void KMessView::slotToggleNowListening()
|
|
{
|
|
currentAccount_->setShowNowListening( ! currentAccount_->getShowNowListening() );
|
|
}
|
|
|
|
|
|
|
|
// Update the users display pic
|
|
void KMessView::slotUpdateDisplayPicture()
|
|
{
|
|
// Adjust the picture size depending on the rest of the widgets:
|
|
// it should be small only when both now playing and new email info are off.
|
|
int size = 96;
|
|
if( ( ! currentAccount_->getShowNowListening() || ! songPlaying_ )
|
|
&& ( ! currentAccount_->getShowEmail() || ! currentAccount_->getEmailSupported() ) )
|
|
{
|
|
size = 64;
|
|
}
|
|
|
|
currentAccountDisplayPic_->setFixedSize( size, size );
|
|
|
|
// The user does not want to show a display pic, so just show an icon for their sign in status
|
|
if( ! currentAccount_->getShowPicture() )
|
|
{
|
|
displayPicFrame_->hide();
|
|
return;
|
|
}
|
|
|
|
// Show the picture frame first
|
|
displayPicFrame_->show();
|
|
|
|
// The picture is unchanged, do nothing
|
|
const QString pictureFileName( currentAccount_->getPicturePath() );
|
|
if( displayPicFrame_->property( "PictureFileName" ) == pictureFileName )
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW
|
|
kmDebug() << "No need to change the display picture";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_KMESSVIEW
|
|
kmDebug() << "Changing display picture to path:" << pictureFileName;
|
|
#endif
|
|
|
|
// Change the picture
|
|
const QPixmap image( pictureFileName );
|
|
currentAccountDisplayPic_->setPixmap( image );
|
|
displayPicFrame_->setProperty( "PictureFileName", pictureFileName );
|
|
}
|
|
|
|
|
|
|
|
// Change whether or not the email label is displayed based on account settings.
|
|
void KMessView::slotUpdateEmailDisplay()
|
|
{
|
|
if ( currentAccount_->getShowEmail() && currentAccount_->getEmailSupported() )
|
|
{
|
|
emailFrame_->setHidden(false);
|
|
//emailFrame_->setMargin(statusLayout_->margin());
|
|
emailFrame_->setMaximumHeight(40);
|
|
}
|
|
else
|
|
{
|
|
emailFrame_->setHidden(true);
|
|
//emailFrame_->setMargin(0);
|
|
emailFrame_->setMaximumHeight(0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Update the expanded status of the list groups
|
|
void KMessView::slotUpdateGroups()
|
|
{
|
|
Q_ASSERT( viewModel_ != 0 );
|
|
|
|
// When searching, the expanded status of the groups is altered, but the settings
|
|
// should not be applied to the actual groups
|
|
if( ! viewModel_->filterRegExp().isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_KMESSVIEW_ITEM_VISIBILITY
|
|
kmDebug() << "Updating groups expanded status for" << viewModel_->rowCount() << "rows";
|
|
#endif
|
|
|
|
for( int row = viewModel_->rowCount() - 1; row >= 0; row-- )
|
|
{
|
|
const QModelIndex index( viewModel_->index( row, 0, QModelIndex() ) );
|
|
|
|
#ifdef KMESSDEBUG_KMESSVIEW_ITEM_VISIBILITY
|
|
kmDebug() << "Current row:" << row << "of" << viewModel_->rowCount();
|
|
kmDebug() << "Index:" << index;
|
|
kmDebug() << "Model has it?" << viewModel_->hasIndex( row, 0, QModelIndex() );
|
|
#endif
|
|
|
|
// Skip invalid items
|
|
if( ! index.isValid()
|
|
|| ! viewModel_->hasIndex( row, 0, QModelIndex() )
|
|
|| ! index.data().canConvert<QVariantMap>() )
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW_ITEM_VISIBILITY
|
|
kmDebug() << "Skipping an invalid item at row" << row << ":" << index;
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
// Access the index's data
|
|
const ModelDataList itemData( index.data().toMap() );
|
|
|
|
// Skip items which have no children
|
|
if( itemData.isEmpty() || itemData[ "type" ].toInt() != ContactListModelItem::ItemGroup )
|
|
{
|
|
#ifdef KMESSDEBUG_KMESSVIEW_ITEM_VISIBILITY
|
|
kmDebug() << "Skipping a non-group at row" << row << ":" << index;
|
|
kmDebug() << "Skipped item's details:" << itemData;
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
// Set the expanded status
|
|
contactListView_->setExpanded( index, itemData[ "isExpanded" ].toBool() );
|
|
|
|
#ifdef KMESSDEBUG_KMESSVIEW_ITEM_VISIBILITY
|
|
kmDebug() << "Group" << itemData[ "name" ].toString() << "is"
|
|
<< ( itemData[ "isExpanded" ].toBool() ? "expanded" : "contracted" );
|
|
#endif
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_KMESSVIEW_ITEM_VISIBILITY
|
|
kmDebug() << "Update done.";
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
// Update the email count.
|
|
void KMessView::slotUpdateNoEmails()
|
|
{
|
|
const QString message( i18np( "%1 new email message",
|
|
"%1 new email messages",
|
|
currentAccount_->getNoEmails() ) );
|
|
|
|
// Update the label
|
|
emailLabel_->setText( message );
|
|
|
|
// Set the user's preferred email website.
|
|
emailLabel_->setUrl( currentAccount_->getEmailUrl() );
|
|
}
|
|
|
|
|
|
|
|
// set the icon and tooltip for the now listening toggle button.
|
|
void KMessView::slotUpdateNowListeningToggle()
|
|
{
|
|
if( currentAccount_->getShowNowListening() )
|
|
{
|
|
nowListeningPushButton_->setIcon( KIconLoader::global()->loadIcon( "audio-volume-high", KIconLoader::Small, 0, KIconLoader::DisabledState ) );
|
|
nowListeningPushButton_->setToolTip( i18n( "Show contacts what you are listening to (click to disable)" ) );
|
|
}
|
|
else
|
|
{
|
|
nowListeningPushButton_->setIcon( KIconLoader::global()->loadIcon( "audio-volume-muted", KIconLoader::Small, 0, KIconLoader::DisabledState ) );
|
|
nowListeningPushButton_->setToolTip( i18n( "Show contacts what you are listening to (click to enable)" ) );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Update the user's status.
|
|
void KMessView::slotUpdateUserStatus()
|
|
{
|
|
const Status status( currentAccount_->getStatus() );
|
|
|
|
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
|
|
kmDebug() << "Updating user status to:" << MsnStatus::getName( status );
|
|
#endif
|
|
|
|
// Update the widgets
|
|
|
|
updateFriendlyName( true );
|
|
updatePersonalMessage( true );
|
|
|
|
statusButton_->setText( MsnStatus::getName( status ) );
|
|
statusButton_->setIcon( MsnStatus::getIcon( status ) );
|
|
}
|
|
|
|
|
|
|
|
// Update the contact list widgets
|
|
void KMessView::slotUpdateView()
|
|
{
|
|
QString birdStyle;
|
|
|
|
// Hide the tree branches
|
|
QString listStyle( "QTreeView::branch:has-siblings:adjoins-item { "
|
|
" border-image: url(empty.png) 0; "
|
|
"} "
|
|
"QTreeView::branch:!has-children:!has-siblings:adjoins-item { "
|
|
" border-image: url(empty.png) 0; "
|
|
"} " );
|
|
|
|
// Set a background pixmap (but allow not to, by popular request)
|
|
if( currentAccount_->getShowContactListBird() )
|
|
{
|
|
birdStyle = "QTreeView { "
|
|
" background-attachment: fixed; "
|
|
" background-origin: content; "
|
|
" background-position: bottom right; "
|
|
" background-repeat: no-repeat; "
|
|
" background-image: url(" +
|
|
KIconLoader::global()->iconPath( "background", KIconLoader::User ) + ");"
|
|
"}"
|
|
;
|
|
}
|
|
|
|
contactListView_->setStyleSheet( listStyle + birdStyle );
|
|
}
|