/*************************************************************************** kmessviewdelegate.cpp - description ------------------- begin : Sat Feb 16 2008 copyright : (C) 2008 by Valerio Pilo email : valerio@kmess.org ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kmessviewdelegate.h" #include "currentaccount.h" #include "emoticonmanager.h" #include "contact/msnstatus.h" #include "contact/specialgroups.h" #include "model/contactlistmodelitem.h" #include "utils/richtextparser.h" #include "kmessdebug.h" #include "model/contactlist.h" #include "contact/contact.h" #include <QApplication> #include <QLabel> #include <QModelIndex> #include <QPainter> #include <QTextDocument> #include <KIconLoader> #include <KLocale> #include <KGlobalSettings> /// Default spacing between items #define ITEM_SPACE 4 /** * Constructor * * Creates two labels which will be used to paint the contact list elements at the location of every item * * @param parent The parent object, usually a QTreeView */ 00052 KMessViewDelegate::KMessViewDelegate( QWidget *parent ) : QStyledItemDelegate( parent ) , iconLoader_( KIconLoader::global() ) { // Set up the group label style fontBold_.setBold( true ); // Create the label used to render the HTML text labels textLabel_ = new QLabel(); textLabel_->setTextFormat( Qt::RichText ); textLabel_->setAlignment( Qt::AlignTop ); // Save the current account's reference currentAccount_ = CurrentAccount::instance(); // Cache the icons for the various media types mediaEmoticonMusic_ = EmoticonManager::instance()->getReplacement( "(8)", true ); mediaEmoticonGaming_ = EmoticonManager::instance()->getReplacement( "(xx)", true ); } /** * Destructor */ 00077 KMessViewDelegate::~KMessViewDelegate() { delete textLabel_; } /** * Paint an item of the contact list * * This method is called whenever a contact list item needs do be drawn. * The 'index' parameter is used to extract the data from the list item, and depending on the list item type * (contact or group mainly) it decides what needs to be drawn and how. * * @param painter Painter item, we'll use it to draw our item's contents * @param option Contains some details about the item we have to draw * @param index Points to the actual data to be represented */ 00095 void KMessViewDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const { QStyledItemDelegate::paint( painter, option, index ); // Only render column zero if( index.column() != 0 ) { return; } QString text; // Save the painter state to avoid messing up further painting operations with it painter->save(); // Reset coordinate translation in the painter (defaults are relative to the parent's window) - and make rendering prettier possibly painter->translate( 0, 0 ); painter->setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing, true ); // Read the actual data off the model index const ModelDataList& itemData( index.data().toMap() ); // We're only able to display groups and contacts if( itemData[ "type" ].toInt() != ContactListModelItem::ItemGroup && itemData[ "type" ].toInt() != ContactListModelItem::ItemContact ) { // Restore the painter to the previous state painter->restore(); kWarning() << "Unknown type of item:" << itemData[ "type" ].toInt(); kWarning() << "Index:" << index; kWarning() << "Data:" << itemData; return; } int displayPictureSize = currentAccount_->getListPictureSize(); // Load the required pixmaps: their sizes will be used to compute the final // rendering positions QPixmap iconPixmap; QPixmap picturePixmap; // Group pixmaps if( itemData[ "type" ].toInt() == ContactListModelItem::ItemGroup ) { QString groupIconName; bool showOfflineContacts = currentAccount_->getShowOfflineContacts(); // Only show the expansion arrow if there are contacts to show if( ( showOfflineContacts && itemData[ "totalContacts" ].toInt() > 0 ) || ( ! showOfflineContacts && itemData[ "onlineContacts" ].toInt() > 0 ) ) { if( itemData[ "isExpanded" ].toBool() ) { groupIconName = "arrow-down"; } else if( QApplication::isRightToLeft() ) { groupIconName = "arrow-left"; } else { groupIconName = "arrow-right"; } } else { // Show only arrow down if there aren't contacts to show in group groupIconName = "arrow-down"; } iconPixmap = iconLoader_->loadIcon( groupIconName , KIconLoader::NoGroup , KIconLoader::SizeSmall ); } else // Contact pixmaps { // Only load the picture if it must be shown if( displayPictureSize != 0 ) { picturePixmap = CurrentAccount::instance()->getContactList()->getContactByHandle( itemData[ "handle" ].toString() )->getScaledDisplayPicture(); } // Determine which status pixmap to load Flags statusFlags = ( itemData[ "isBlocked" ].toBool() ? FlagBlocked : FlagNone ); iconPixmap = MsnStatus::getIcon( (Status) itemData[ "status" ].toInt(), statusFlags ); } // Compute the positions where the objects will be painted QPoint originPoint; if( QApplication::isLeftToRight() ) { originPoint = option.rect.topLeft(); originPoint.rx()++; } else { originPoint = option.rect.topRight(); originPoint.rx()--; } originPoint.ry()++; QPoint iconPoint( originPoint ); QPoint picturePoint( originPoint ); // Labels get adjusted to 2 pixels below the top border, because otherwise, they get painted // too close to it QRect labelRect( option.rect.adjusted( ITEM_SPACE, 2, -ITEM_SPACE, 0 ) ); // Group item positions if( itemData[ "type" ].toInt() == ContactListModelItem::ItemGroup ) { if( QApplication::isLeftToRight() ) { // iconPoint is painted at originPoint labelRect.setLeft( iconPixmap.width() + ITEM_SPACE ); } else { iconPoint.rx() -= iconPixmap.width(); labelRect.setRight( iconPoint.x() - ITEM_SPACE ); } } else // Contact item positions { if( QApplication::isLeftToRight() ) { // picturePoint is painted at originPoint // Show the icon, then the label if( displayPictureSize == 0 ) { labelRect.setLeft( iconPoint.x() + iconPixmap.width() + ITEM_SPACE ); } else if( displayPictureSize < 48 ) // Show the picture, the icon, then the label { iconPoint.rx() = iconPoint.x() + picturePixmap.width() + ITEM_SPACE; labelRect.setLeft( iconPoint.x() + iconPixmap.width() + ITEM_SPACE ); } else // Show the picture and the label; then show the icon over the picture, at its bottom left corner { iconPoint.ry() += displayPictureSize - KIconLoader::SizeSmall; labelRect.setLeft( iconPoint.x() + picturePixmap.width() + ITEM_SPACE ); } } else { // Show the icon, then the label if( displayPictureSize == 0 ) { iconPoint.rx() -= iconPixmap.width(); labelRect.setRight( iconPoint.x() - ITEM_SPACE ); } else if( displayPictureSize < 48 ) // Show the picture, the icon, then the label { picturePoint.rx() -= picturePixmap.width(); iconPoint.rx() = picturePoint.x() - iconPixmap.width() - ITEM_SPACE; labelRect.setRight( iconPoint.x() - ITEM_SPACE ); } else // Show the picture and the label; then show the icon over the picture, at its bottom right corner { picturePoint.rx() -= picturePixmap.width(); iconPoint.rx() -= iconPixmap.width(); iconPoint.ry() += displayPictureSize - KIconLoader::SizeSmall; labelRect.setRight( picturePoint.x() - ITEM_SPACE ); } } } // If the contact hasn't us in its CL, colorize the line if( itemData[ "type" ].toInt() == ContactListModelItem::ItemContact && ! itemData[ "isReverse" ].toBool() ) // The contact does not have us in its list { painter->fillRect( option.rect, QColor( 255,0,0,20 ) ); } // Paint the images if( ! picturePixmap.isNull() ) { painter->drawPixmap( picturePoint, picturePixmap ); } painter->drawPixmap( iconPoint, iconPixmap ); // Paint the label: first the group case if( itemData[ "type" ].toInt() == ContactListModelItem::ItemGroup ) { QString text; if( ! itemData[ "isSpecialGroup" ].toBool() || itemData[ "id" ] == SpecialGroups::INDIVIDUALS ) { text = i18nc( "Group name in the contact list with online/total contacts of that group" , "%1 (%2/%3)" , Qt::escape( itemData[ "name" ].toString() ) , itemData[ "onlineContacts" ].toString() , itemData[ "totalContacts" ].toString() ); } else { text = i18nc( "Group name in the contact list with total contacts of that group" , "%1 (%2)" , Qt::escape( itemData[ "name" ].toString() ) , itemData[ "totalContacts" ].toString() ); } // The group names are always shown in bold font, to be more visible painter->setFont( fontBold_ ); QFontMetrics fm( painter->font() ); QTextOption textOpt; textOpt.setWrapMode( QTextOption::NoWrap ); if( ( fm.width( text ) + labelRect.height() ) > labelRect.width() ) { QImage label( labelRect.size(), QImage::Format_ARGB32 ); label.fill( Qt::transparent ); // create a linear gradient, white to transparent QLinearGradient gradient( QPoint( 0, 0 ), labelRect.bottomRight() ); if( QApplication::isLeftToRight() ) { gradient.setColorAt( 0.90, Qt::white ); gradient.setColorAt( 1.00, Qt::transparent ); } else { gradient.setColorAt( 0.10, Qt::white ); gradient.setColorAt( 0.00, Qt::transparent ); } QPainter pLabel( &label ); pLabel.translate( -labelRect.topLeft() ); pLabel.setFont( fontBold_ ); pLabel.setRenderHints( QPainter::TextAntialiasing, true ); pLabel.drawText( labelRect, text, textOpt ); pLabel.setCompositionMode( QPainter::CompositionMode_DestinationIn ); pLabel.fillRect( labelRect, QBrush( gradient ) ); painter->drawImage( labelRect, label ); } else { painter->drawText( labelRect, text, textOpt ); } } else // then the contact case { // Get the contact information. QString friendlyName, personalMessage; // Parse the name and message of the contact, adding emoticons and formatting if( currentAccount_->getUseListFormatting() ) { if( ! currentAccount_->getShowContactEmail() ) { friendlyName = itemData[ "friendlyFormatted" ].toString(); } else { friendlyName = itemData[ "handle" ].toString(); } personalMessage = itemData[ "personalMessageFormatted" ].toString(); } else { if( ! currentAccount_->getShowContactEmail() ) { friendlyName = itemData[ "friendly" ].toString(); RichTextParser::parseMsnString( friendlyName, true, true, false, false ); } else { friendlyName = itemData[ "handle" ].toString(); } // Display the emoticons alone when MSN Plus formatting is disabled: the formatted // version has emoticons already. personalMessage = itemData[ "personalMessage" ].toString(); RichTextParser::parseMsnString( personalMessage, true, true, false, false ); } // If it's empty, then the user wants to see the email in the contact list if( friendlyName.isEmpty() ) { friendlyName = itemData[ "handle" ].toString(); } QString messageString; const QString& mediaString( itemData[ "mediaString" ].toString() ); if( ! mediaString.isEmpty() ) { // Determine icon for the various media types const QString& mediaType ( itemData[ "mediaType" ].toString() ); if( mediaType == "Music" ) { messageString = mediaEmoticonMusic_ + "<i>" + Qt::escape( mediaString ) + "</i>"; } else if( mediaType == "Gaming" || mediaType == "Game" ) { messageString = mediaEmoticonGaming_ + "<i>" + Qt::escape( mediaString ) + "</i>"; } else { // unknown media type, fallback to personal message. messageString = "<i>" + personalMessage + "</i>"; } } else if( ! personalMessage.isEmpty() ) // When there's no media, show the PM { messageString = "<i>" + personalMessage + "</i>"; } // Draw the text widget if( messageString.isEmpty() ) { // Only person name text = friendlyName; } else if( ! displayPictureSize ) { // Person with message after it text = friendlyName + "<small><font style='color:palette(window-text)'> - " + messageString + "</font></small>"; } else { // Message below, text = friendlyName + "<br /><small><font style='color:palette(window-text)'>" + messageString + "</font></small>"; } painter->translate( labelRect.topLeft() ); labelRect.translate( - labelRect.topLeft() ); textLabel_->setGeometry( labelRect ); textLabel_->setText( text ); QFontMetrics fm( painter->font() ); const int labelWidth = fm.width( text ); // Paint directly when it fits in the available space... if( ( labelWidth + labelRect.height() ) <= labelRect.width() ) { // The + rect.height() is to have a nicer effect when resizing the window :) textLabel_->render( painter, QPoint(), QRegion(), 0 ); } // ...otherwise (when the text goes over the available space) fade it away else { // Create a transparent image as big as the text label QPixmap labelPixmap( labelRect.width(), labelRect.height() ); labelPixmap.fill( Qt::transparent ); // create a linear gradient, white to transparent QLinearGradient gradient( QPoint( 0, 0 ), labelRect.bottomRight() ); if( QApplication::isLeftToRight() ) { gradient.setColorAt( 0.90, Qt::white ); gradient.setColorAt( 1.00, Qt::transparent ); } else { gradient.setColorAt( 0.10, Qt::white ); gradient.setColorAt( 0.00, Qt::transparent ); } // The painter will be used to blend the label with the gradient QPainter pixmapPainter( &labelPixmap ); pixmapPainter.setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing, true ); // Adjust the label to the contained text textLabel_->adjustSize(); // When using a right-to-left aligned language (like Arabic) the label needs to be // moved on the right side of the available space to honor the alignment. QPoint offset( 0, 0 ); if( QApplication::isRightToLeft() ) { offset.rx() = labelRect.width() - textLabel_->width(); } // Paint the label over the image textLabel_->render( &pixmapPainter, offset, QRegion(), 0 ); // Blend together the image and the gradient pixmapPainter.setCompositionMode( QPainter::CompositionMode_DestinationIn ); pixmapPainter.fillRect( labelRect, QBrush( gradient ) ); // Paint the resulting image over the widget painter->drawPixmap( labelRect, labelPixmap ); } } // Restore the painter to the previous state painter->restore(); } /** * Return an hint about the size of an item * * @param option Contains some details about the item we have to determine size of * @param index Points to the actual list item * @return The ize for the given index */ 00507 QSize KMessViewDelegate::sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const { Q_UNUSED( option ); // Read the actual data off the model index const ModelDataList& itemData( index.data().toMap() ); // the height of the item should be // large enough to accommodate the font. QFont generalFont = KGlobalSettings::generalFont(); QFontMetricsF metrics( generalFont ); QSize size( KIconLoader::SizeSmall + 2, KIconLoader::SizeSmall + 4 ); if ( size.height() < metrics.height() ) { size.setHeight( metrics.height() ); } if( itemData.isEmpty() ) { return size; } int picturesDimension = currentAccount_->getListPictureSize(); switch( itemData[ "type" ].toInt() ) { case ContactListModelItem::ItemContact: if( picturesDimension > 0 ) { QSize pictureSize( size ); if( picturesDimension <= 32 ) { // Fix the margin pictureSize.rheight() = 36; } else { pictureSize.rheight() = picturesDimension + 2; } qreal fontHeight = metrics.height() * 2; // x2 to account for PSM. if ( fontHeight > pictureSize.height() ) { pictureSize.setHeight( fontHeight ); } return pictureSize; } else { return size; } case ContactListModelItem::ItemGroup: default: return size; } }