-/*
- This file is part of the Nepomuk KDE project.
- Copyright (C) 2007 Sebastian Trueg <trueg@kde.org>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License version 2 as published by the Free Software Foundation.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public License
- along with this library; see the file COPYING.LIB. If not, write to
- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- Boston, MA 02110-1301, USA.
- */
-
-#include "tagcloud.h"
-#include "newtagdialog.h"
-
-#include <QtGui/QFont>
-#include <QtGui/QFontMetrics>
-#include <QtCore/QList>
-#include <QtGui/QPushButton>
-#include <QtCore/Qt>
-#include <QtCore/QTime>
-#include <QtGui/QPainter>
-#include <QtGui/QMouseEvent>
-#include <QtGui/QPalette>
-#include <QtGui/QInputDialog>
-#include <QtGui/QAction>
-
-#include <KRandomSequence>
-#include <KLocale>
-#include <KColorScheme>
-#include <KDebug>
-#include <KMenu>
-#include <KIcon>
-#include <KMessageBox>
-
-#include <Soprano/Client/DBusModel>
-#include <Soprano/QueryResultIterator>
-#include <Soprano/Vocabulary/RDF>
-#include <Soprano/Vocabulary/NAO>
-
-#include <nepomuk/resourcemanager.h>
-
-#include <math.h>
-
-
-namespace {
- const int s_hSpacing = 10;
- const int s_vSpacing = 5;
-
- class TagNode {
- public:
- TagNode()
- : weight( 0 ),
- selected( false ) {
- }
-
- bool operator==( const TagNode& other ) const {
- return tag == other.tag;
- }
-
- // fixed info
- Nepomuk::Tag tag;
- int weight;
-
- // misc
- bool selected;
-
- // info generated by rebuildCloud
- QFont font;
- QRect rect;
- QRect zoomedRect;
- QString text;
- };
-
- bool tagNodeNameLessThan( const TagNode& n1, const TagNode& n2 ) {
- return n1.text < n2.text;
- }
-
- bool tagNodeWeightLessThan( const TagNode& n1, const TagNode& n2 ) {
- return n1.weight < n2.weight;
- }
-
- int rowLength( const QList<TagNode*>& row ) {
- int rowLen = 0;
- for ( int j = 0; j < row.count(); ++j ) {
- rowLen += row[j]->rect.width();
- if ( j < row.count()-1 ) {
- rowLen += s_hSpacing;
- }
- }
- return rowLen;
- }
-
- int rowHeight( const QList<TagNode*>& row ) {
- int h = 0;
- for ( int j = 0; j < row.count(); ++j ) {
- h = qMax( row[j]->rect.height(), h );
- }
- return h;
- }
-
- QSize cloudSize( const QList<QList<TagNode*> >& rows ) {
- int w = 0;
- int h = 0;
- for ( int i = 0; i < rows.count(); ++i ) {
- w = qMax( w, rowLength( rows[i] ) );
- h += rowHeight( rows[i] );
- if ( i < rows.count()-1 ) {
- h += s_vSpacing;
- }
- }
- return QSize( w, h );
- }
-}
-
-
-class Nepomuk::TagCloud::Private
-{
-public:
- Private( TagCloud* parent )
- : maxFontSize( 0 ),
- minFontSize( 0 ),
- maxNumberDisplayedTags( 0 ),
- selectionEnabled( false ),
- newTagButtonEnabled( false ),
- alignment( Qt::AlignCenter ),
- sorting( SortAlpabetically ),
- zoomEnabled( true ),
- contextMenuEnabled( true ),
- showAllTags( false ),
- customNewTagAction( 0 ),
- hoverTag( 0 ),
- cachedHfwWidth( -1 ),
- m_parent( parent ) {
- newTagNode.text = i18nc( "@label", "New Tag..." );
- }
-
- int maxFontSize;
- int minFontSize;
- int maxNumberDisplayedTags;
- bool selectionEnabled;
- bool newTagButtonEnabled;
- Qt::Alignment alignment;
- Sorting sorting;
- bool zoomEnabled;
- bool contextMenuEnabled;
-
- // The resource whose tags we are showing
- // invalid if we show all tags or a selection
- KUrl resource;
- bool showAllTags;
-
- // the actual nodes
- QList<TagNode> nodes;
-
- // just a helper structure for speeding up things
- QList<QList<TagNode*> > rows;
-
- TagNode newTagNode;
- QAction* customNewTagAction;
-
- TagNode* hoverTag;
-
- QMatrix zoomMatrix;
-
- QSize cachedSizeHint;
- int cachedHfwWidth;
- int cachedHfwHeight;
-
- void invalidateCachedValues() {
- cachedSizeHint = QSize();
- cachedHfwWidth = -1;
- }
-
- int getMinFontSize() const;
- int getMaxFontSize() const;
- void updateNodeWeights();
- void updateNodeFonts();
- void sortNodes();
- void rebuildCloud();
- TagNode* tagAt( const QPoint& pos );
- TagNode* findTagInRow( const QList<TagNode*>& row, const QPoint& pos );
- TagNode* nodeForTag( const Tag& tag );
- int calculateWeight( const Nepomuk::Tag& tag );
-
-private:
- TagCloud* m_parent;
-};
-
-
-int Nepomuk::TagCloud::Private::getMinFontSize() const
-{
- return minFontSize > 0 ? minFontSize : ( 8 * m_parent->font().pointSize() / 10 );
-}
-
-
-int Nepomuk::TagCloud::Private::getMaxFontSize() const
-{
- return maxFontSize > 0 ? maxFontSize : ( 22 * m_parent->font().pointSize() / 10 );
-}
-
-
-int Nepomuk::TagCloud::Private::calculateWeight( const Nepomuk::Tag& tag )
-{
- // stupid SPARQL has no functions such as count!
- Soprano::QueryResultIterator it
- = ResourceManager::instance()->mainModel()->executeQuery( QString( "select ?r where { ?r <%1> <%2> . }" )
- .arg( Soprano::Vocabulary::NAO::hasTag().toString() )
- .arg( QString::fromAscii( tag.resourceUri().toEncoded() ) ),
- Soprano::Query::QueryLanguageSparql );
- int w = 0;
- while ( it.next() ) {
- ++w;
- }
- return w;
-}
-
-
-void Nepomuk::TagCloud::Private::updateNodeWeights()
-{
- bool changedWeights = false;
- for ( QList<TagNode>::iterator it = nodes.begin();
- it != nodes.end(); ++it ) {
- TagNode& node = *it;
- int w = calculateWeight( node.tag );
- if ( w != node.weight ) {
- node.weight = w;
- changedWeights = true;
- }
- }
- if ( changedWeights ) {
- updateNodeFonts();
- }
-}
-
-
-void Nepomuk::TagCloud::Private::updateNodeFonts()
-{
- int maxWeight = 0;
- int minWeight = 0;
- for ( QList<TagNode>::iterator it = nodes.begin();
- it != nodes.end(); ++it ) {
- TagNode& node = *it;
- minWeight = qMin( minWeight, node.weight );
- maxWeight = qMax( maxWeight, node.weight );
- }
-
- // calculate font sizes
- // ----------------------------------------------
- int usedMinFontSize = getMinFontSize();
- int usedMaxFontSize = getMaxFontSize();
- for ( QList<TagNode>::iterator it = nodes.begin();
- it != nodes.end(); ++it ) {
- TagNode& node = *it;
- double normalizedWeight = (double)(node.weight - minWeight) / (double)qMax(maxWeight - minWeight, 1);
- node.font = m_parent->font();
- node.font.setPointSize( usedMinFontSize + (int)((double)(usedMaxFontSize-usedMinFontSize) * normalizedWeight) );
- if( normalizedWeight > 0.8 )
- node.font.setBold( true );
- }
-
- if ( newTagButtonEnabled ) {
- newTagNode.font = m_parent->font();
- newTagNode.font.setPointSize( usedMinFontSize );
- newTagNode.font.setUnderline( true );
- }
-}
-
-
-void Nepomuk::TagCloud::Private::sortNodes()
-{
- if ( sorting == SortAlpabetically ) {
- qSort( nodes.begin(), nodes.end(), tagNodeNameLessThan );
- }
- else if ( sorting == SortByWeight ) {
- qSort( nodes.begin(), nodes.end(), tagNodeWeightLessThan );
- }
- else if ( sorting == SortRandom ) {
- KRandomSequence().randomize( nodes );
- }
-}
-
-
-void Nepomuk::TagCloud::Private::rebuildCloud()
-{
- // - Always try to be quadratic
- // - Always prefer to expand horizontally
- // - If we cannot fit everything into m_parent->contentsRect(), zoom
- // - If alignment & Qt::AlignJustify insert spaces between tags
-
- sortNodes();
-
- QRect contentsRect = m_parent->contentsRect();
-
- // initialize the nodes' sizes
- // ----------------------------------------------
- for ( QList<TagNode>::iterator it = nodes.begin();
- it != nodes.end(); ++it ) {
- TagNode& node = *it;
- node.rect = QFontMetrics( node.font ).boundingRect( node.text );
- }
- if ( newTagButtonEnabled ) {
- newTagNode.rect = QFontMetrics( newTagNode.font ).boundingRect( customNewTagAction ? customNewTagAction->text() : newTagNode.text );
- }
-
-
- // and position the nodes
- // ----------------------------------------------
- rows.clear();
- if ( !nodes.isEmpty() || newTagButtonEnabled ) {
- if ( 0 ) { // FIXME: make it configurable
- QRect lineRect;
- QRect totalRect;
- QList<TagNode*> row;
- for ( QList<TagNode>::iterator it = nodes.begin();
- it != nodes.end(); /* We do increment it below */ ) {
- TagNode& node = *it;
-
- int usedSpacing = row.isEmpty() ? 0 : s_hSpacing;
- if ( lineRect.width() + usedSpacing + node.rect.width() <= contentsRect.width() ) {
- node.rect.moveBottomLeft( QPoint( lineRect.right() + usedSpacing, lineRect.bottom() ) );
- QRect newLineRect = lineRect.united( node.rect );
- newLineRect.moveTopLeft( lineRect.topLeft() );
- lineRect = newLineRect;
- row.append( &node );
-
- // update all other nodes in this line
- Q_FOREACH( TagNode* n, row ) {
- n->rect.moveBottom( lineRect.bottom() - ( lineRect.height() - n->rect.height() )/2 );
- }
-
- ++it;
- }
- else {
- rows.append( row );
- row.clear();
- int newLineTop = lineRect.bottom() + s_vSpacing;
- lineRect = QRect();
- lineRect.moveTop( newLineTop );
- }
- }
- rows.append( row );
- }
- else {
- // initialize first row
- rows.append( QList<TagNode*>() );
- for ( QList<TagNode>::iterator it = nodes.begin();
- it != nodes.end(); ++it ) {
- TagNode& node = *it;
- rows.first().append( &node );
- }
- if ( newTagButtonEnabled ) {
- rows.first().append( &newTagNode );
- }
-
- // calculate the rows
- QList<QList<TagNode*> > bestRows( rows );
- QSize size( rowLength( rows.first() ), rowHeight( rows.first() ) );
- QSize bestSize( size );
- while ( ( size.height() < size.width() ||
- size.width() > contentsRect.width() ) &&
- size.height() <= contentsRect.height() ) {
- // find the longest row
- int maxRow = 0;
- int maxLen = 0;
- for ( int i = 0; i < rows.count(); ++i ) {
- int rowLen = rowLength( rows[i] );
- if ( rowLen > maxLen ) {
- maxLen = rowLen;
- maxRow = i;
- }
- }
-
- // move the last item from the longest row to the next row
- TagNode* node = rows[maxRow].takeLast();
- if ( rows.count() <= maxRow+1 ) {
- rows.append( QList<TagNode*>() );
- }
- rows[maxRow+1].prepend( node );
-
- // update the size
- size = cloudSize( rows );
-
- if ( size.width() < bestSize.width() &&
- ( size.width() > size.height() ||
- bestSize.width() > contentsRect.width() ) &&
- size.height() <= contentsRect.height() ) {
- bestSize = size;
- bestRows = rows;
- }
- }
- rows = bestRows;
-
- // position the tags
- int y = 0;
- for ( QList<QList<TagNode*> >::iterator rowIt = rows.begin(); rowIt != rows.end(); ++rowIt ) {
- QList<TagNode*>& row = *rowIt;
- int h = rowHeight( row );
- int x = 0;
- Q_FOREACH( TagNode* node, row ) {
- node->rect.moveTop( y + ( h - node->rect.height() )/2 );
- node->rect.moveLeft( x );
- x += s_hSpacing + node->rect.width();
- }
- y += h + s_vSpacing;
- }
- }
-
-
- // let's see if we have to zoom
- // ----------------------------------------------
- zoomMatrix = QMatrix();
- int w = contentsRect.width();
- if ( zoomEnabled ) {
- for ( QList<QList<TagNode*> >::iterator rowIt = rows.begin(); rowIt != rows.end(); ++rowIt ) {
- QList<TagNode*>& row = *rowIt;
- w = qMax( w, row.last()->rect.right() );
- }
- if ( w > contentsRect.width() ) {
- double zoomFactor = ( double )contentsRect.width() / ( double )w;
- zoomMatrix.scale( zoomFactor, zoomFactor );
- }
- }
-
- // force horizontal alignment
- // ----------------------------------------------
- for ( QList<QList<TagNode*> >::iterator rowIt = rows.begin(); rowIt != rows.end(); ++rowIt ) {
- QList<TagNode*>& row = *rowIt;
- int space = /*contentsRect.right()*/w - row.last()->rect.right();
- if ( alignment & ( Qt::AlignRight|Qt::AlignHCenter ) ) {
- Q_FOREACH( TagNode* node, row ) {
- node->rect.moveLeft( node->rect.left() + ( alignment & Qt::AlignRight ? space : space/2 ) );
- }
- }
- else if ( alignment & Qt::AlignJustify && row.count() > 1 ) {
- space /= ( row.count()-1 );
- int i = 0;
- Q_FOREACH( TagNode* node, row ) {
- node->rect.moveLeft( node->rect.left() + ( space * i++ ) );
- }
- }
- }
-
- // force vertical alignment
- // ----------------------------------------------
- int verticalSpace = contentsRect.bottom() - rows.last().first()->rect.bottom();
- if ( alignment & ( Qt::AlignBottom|Qt::AlignVCenter ) ) {
- for ( QList<QList<TagNode*> >::iterator rowIt = rows.begin(); rowIt != rows.end(); ++rowIt ) {
- Q_FOREACH( TagNode* node, *rowIt ) {
- node->rect.moveTop( node->rect.top() + ( alignment & Qt::AlignBottom ? verticalSpace : verticalSpace/2 ) );
- }
- }
- }
-
- for( QList<TagNode>::iterator it = nodes.begin(); it != nodes.end(); ++it ) {
- it->zoomedRect = zoomMatrix.mapRect( it->rect );
- }
- newTagNode.zoomedRect = zoomMatrix.mapRect( newTagNode.rect );
- }
-
- m_parent->updateGeometry();
- m_parent->update();
-}
-
-
-// binary search in row
-TagNode* Nepomuk::TagCloud::Private::findTagInRow( const QList<TagNode*>& row, const QPoint& pos )
-{
- int x = m_parent->width() ? row.count() * pos.x() / m_parent->width() : 0;
-
- int i = 0;
- while ( 1 ) {
- if ( x-i >= 0 && x-i < row.count() && row[x-i]->zoomedRect.contains( pos ) ) {
- return row[x-i];
- }
- else if ( x+i >= 0 && x+i < row.count() && row[x+i]->zoomedRect.contains( pos ) ) {
- return row[x+i];
- }
- if ( x-i < 0 && x+i >= row.count() ) {
- return 0;
- }
- ++i;
- }
- return 0;
-}
-
-
-// binary search in cloud
-TagNode* Nepomuk::TagCloud::Private::tagAt( const QPoint& pos )
-{
- int y = m_parent->height() ? rows.count() * pos.y() / m_parent->height() : 0;
-
- int i = 0;
- while ( 1 ) {
- if ( y-i >= 0 && y-i < rows.count() ) {
- if ( TagNode* node = findTagInRow( rows[y-i], pos ) ) {
- return node;
- }
- }
- if ( y+i >= 0 && y+i < rows.count() ) {
- if ( TagNode* node = findTagInRow( rows[y+i], pos ) ) {
- return node;
- }
- }
- if ( y-i < 0 && y+i >= rows.count() ) {
- return 0;
- }
- ++i;
- }
- return 0;
-}
-
-
-TagNode* Nepomuk::TagCloud::Private::nodeForTag( const Tag& tag )
-{
- for ( QList<TagNode>::iterator it = nodes.begin();
- it != nodes.end(); ++it ) {
- TagNode& node = *it;
- if ( tag == node.tag ) {
- return &node;
- }
- }
- return 0;
-}
-
-
-
-Nepomuk::TagCloud::TagCloud( QWidget* parent )
- : QFrame( parent ),
- d( new Private(this) )
-{
- QSizePolicy policy( QSizePolicy::Preferred,
- QSizePolicy::Preferred );
- policy.setHeightForWidth( true );
- setSizePolicy( policy );
- setMouseTracking( true );
-
- // Since signals are delivered in no particular order
- // our slot might be called before the resources are updated
- // Then, we would use invalid cached data.
- // By using queued connections this problem should be solved.
- connect( ResourceManager::instance()->mainModel(),
- SIGNAL( statementAdded( const Soprano::Statement& ) ),
- this,
- SLOT( slotStatementAdded( const Soprano::Statement& ) ),
- Qt::QueuedConnection );
- connect( ResourceManager::instance()->mainModel(),
- SIGNAL( statementRemoved( const Soprano::Statement& ) ),
- this,
- SLOT( slotStatementRemoved( const Soprano::Statement& ) ),
- Qt::QueuedConnection );
-}
-
-
-Nepomuk::TagCloud::~TagCloud()
-{
- delete d;
-}
-
-
-void Nepomuk::TagCloud::setMaxFontSize( int size )
-{
- d->invalidateCachedValues();
- d->maxFontSize = size;
- d->updateNodeFonts();
- d->rebuildCloud();
-}
-
-
-void Nepomuk::TagCloud::setMinFontSize( int size )
-{
- d->invalidateCachedValues();
- d->minFontSize = size;
- d->updateNodeFonts();
- d->rebuildCloud();
-}
-
-
-void Nepomuk::TagCloud::setMaxNumberDisplayedTags( int n )
-{
- d->maxNumberDisplayedTags = n;
- d->rebuildCloud();
-}
-
-
-void Nepomuk::TagCloud::setSelectionEnabled( bool enabled )
-{
- d->selectionEnabled = enabled;
- update();
-}
-
-
-void Nepomuk::TagCloud::setNewTagButtonEnabled( bool enabled )
-{
- d->newTagButtonEnabled = enabled;
- d->rebuildCloud();
-}
-
-
-bool Nepomuk::TagCloud::zoomEnabled() const
-{
- return d->zoomEnabled;
-}
-
-
-void Nepomuk::TagCloud::setZoomEnabled( bool zoom )
-{
- if ( d->zoomEnabled != zoom ) {
- d->zoomEnabled = zoom;
- d->rebuildCloud();
- }
-}
-
-
-void Nepomuk::TagCloud::setContextMenuEnabled( bool enabled )
-{
- d->contextMenuEnabled = enabled;
-}
-
-
-void Nepomuk::TagCloud::setAlignment( Qt::Alignment alignment )
-{
- d->alignment = alignment;
- d->rebuildCloud();
-}
-
-
-void Nepomuk::TagCloud::setSorting( Sorting s )
-{
- d->invalidateCachedValues();
- d->sorting = s;
- d->rebuildCloud();
-}
-
-
-void Nepomuk::TagCloud::showAllTags()
-{
- showTags( Nepomuk::Tag::allTags() );
- d->showAllTags = true;
-}
-
-
-void Nepomuk::TagCloud::showResourceTags( const Resource& resource )
-{
- showTags( resource.tags() );
- d->resource = resource.resourceUri();
-}
-
-
-void Nepomuk::TagCloud::showTags( const QList<Tag>& tags )
-{
- d->resource = KUrl();
- d->showAllTags = false;
- d->invalidateCachedValues();
- d->nodes.clear();
- Q_FOREACH( const Tag &tag, tags ) {
- TagNode node;
- node.tag = tag;
- node.weight = d->calculateWeight( tag );
- node.text = node.tag.genericLabel();
-
- d->nodes.append( node );
- }
- d->updateNodeFonts();
- d->rebuildCloud();
-}
-
-
-void Nepomuk::TagCloud::setTagSelected( const Tag& tag, bool selected )
-{
- if ( TagNode* node = d->nodeForTag( tag ) ) {
- node->selected = selected;
- if ( d->selectionEnabled ) {
- update( node->zoomedRect );
- }
- }
-}
-
-
-QSize Nepomuk::TagCloud::sizeHint() const
-{
- // If we have tags d->rebuildCloud() has been called at least once,
- // thus, we have proper rects (i.e. needed sizes)
-
- if ( !d->cachedSizeHint.isValid() ) {
- QList<QList<TagNode*> > rows;
- rows.append( QList<TagNode*>() );
- for ( QList<TagNode>::iterator it = d->nodes.begin();
- it != d->nodes.end(); ++it ) {
- TagNode& node = *it;
- rows.first().append( &node );
- }
- if ( d->newTagButtonEnabled ) {
- rows.first().append( &d->newTagNode );
- }
-
- QSize size( rowLength( rows.first() ), rowHeight( rows.first() ) );
- QSize bestSize( size );
- while ( size.height() < size.width() ) {
- // find the longest row
- int maxRow = 0;
- int maxLen = 0;
- for ( int i = 0; i < rows.count(); ++i ) {
- int rowLen = rowLength( rows[i] );
- if ( rowLen > maxLen ) {
- maxLen = rowLen;
- maxRow = i;
- }
- }
-
- // move the last item from the longest row to the next row
- TagNode* node = rows[maxRow].takeLast();
- if ( rows.count() <= maxRow+1 ) {
- rows.append( QList<TagNode*>() );
- }
- rows[maxRow+1].prepend( node );
-
- // update the size
- size = cloudSize( rows );
-
- if ( size.width() < bestSize.width() &&
- size.width() > size.height() ) {
- bestSize = size;
- }
- }
-
- d->cachedSizeHint = QSize( bestSize.width() + frameWidth()*2,
- bestSize.height() + frameWidth()*2 );
- }
-
- return d->cachedSizeHint;
-}
-
-
-QSize Nepomuk::TagCloud::minimumSizeHint() const
-{
- return QFrame::minimumSizeHint();
- // If we have tags d->rebuildCloud() has been called at least once,
- // thus, we have proper rects (i.e. needed sizes)
- if ( d->nodes.isEmpty() && !d->newTagButtonEnabled ) {
- return QSize( fontMetrics().width( i18nc( "@label Indicator when no tags defined", "No Tags" ) ), fontMetrics().height() );
- }
- else {
- QSize size;
- for ( QList<TagNode>::iterator it = d->nodes.begin();
- it != d->nodes.end(); ++it ) {
- size.setWidth( qMax( size.width(), ( *it ).rect.width() ) );
- size.setHeight( qMax( size.height(), ( *it ).rect.height() ) );
- }
- if ( d->newTagButtonEnabled ) {
- size.setWidth( qMax( size.width(), d->newTagNode.rect.width() ) );
- size.setHeight( qMax( size.height(), d->newTagNode.rect.height() ) );
- }
- size.setWidth( size.width() + frameWidth()*2 );
- size.setHeight( size.height() + frameWidth()*2 );
- return size;
- }
-}
-
-
-int Nepomuk::TagCloud::heightForWidth( int contentsWidth ) const
-{
- // If we have tags d->rebuildCloud() has been called at least once,
- // thus, we have proper rects (i.e. needed sizes)
-
- if ( d->cachedHfwWidth != contentsWidth ) {
- // have to keep in mind the frame
- contentsWidth -= frameWidth()*2;
-
- QList<TagNode*> allNodes;
- for ( QList<TagNode>::iterator it = d->nodes.begin();
- it != d->nodes.end(); ++it ) {
- TagNode& node = *it;
- allNodes.append( &node );
- }
- if ( d->newTagButtonEnabled ) {
- allNodes.append( &d->newTagNode );
- }
-
- int h = 0;
- bool newRow = true;
- int rowW = 0;
- int rowH = 0;
- int maxW = 0;
- for ( int i = 0; i < allNodes.count(); ++i ) {
- int w = rowW;
- if ( !newRow ) {
- w += s_hSpacing;
- }
- newRow = false;
- w += allNodes[i]->rect.width();
- if ( w <= contentsWidth ) {
- rowH = qMax( rowH, allNodes[i]->rect.height() );
- rowW = w;
- }
- else {
- if ( h > 0 ) {
- h += s_vSpacing;
- }
- h += rowH;
- rowH = allNodes[i]->rect.height();
- rowW = allNodes[i]->rect.width();
- }
- maxW = qMax( maxW, rowW );
- }
- if ( rowH > 0 ) {
- h += s_vSpacing + rowH;
- }
-
- d->cachedHfwWidth = contentsWidth;
- d->cachedHfwHeight = h;
-
- // zooming
- if ( maxW > contentsWidth ) {
- d->cachedHfwHeight = d->cachedHfwHeight * contentsWidth / maxW;
- }
- }
-
- return d->cachedHfwHeight + frameWidth()*2;
-}
-
-
-void Nepomuk::TagCloud::resizeEvent( QResizeEvent* e )
-{
- QFrame::resizeEvent( e );
- d->rebuildCloud();
- update();
-}
-
-
-void Nepomuk::TagCloud::paintEvent( QPaintEvent* e )
-{
- QFrame::paintEvent( e );
-
- KStatefulBrush normalTextBrush( KColorScheme::View, KColorScheme::NormalText );
- KStatefulBrush activeTextBrush( KColorScheme::View, KColorScheme::VisitedText );
- KStatefulBrush hoverTextBrush( KColorScheme::View, KColorScheme::ActiveText );
-
- QPainter p( this );
- QRegion paintRegion = e->region();
-
- p.save();
- p.setMatrix( d->zoomMatrix );
-
- for ( QList<TagNode>::iterator it = d->nodes.begin();
- it != d->nodes.end(); ++it ) {
- TagNode& node = *it;
-
- if ( paintRegion.contains( node.zoomedRect ) ) {
- p.setFont( node.font );
-
- if ( &node == d->hoverTag ) {
- p.setPen( hoverTextBrush.brush( this ).color() );
- }
- else if ( d->selectionEnabled && node.selected ) {
- p.setPen( activeTextBrush.brush( this ).color() );
- }
- else {
- p.setPen( normalTextBrush.brush( this ).color() );
- }
- p.drawText( node.rect, Qt::AlignCenter, node.text );
- }
- }
-
- if ( d->newTagButtonEnabled ) {
- p.setFont( d->newTagNode.font );
- if ( &d->newTagNode == d->hoverTag ) {
- p.setPen( hoverTextBrush.brush( this ).color() );
- }
- else {
- p.setPen( normalTextBrush.brush( this ).color() );
- }
- p.drawText( d->newTagNode.rect, Qt::AlignCenter, d->customNewTagAction ? d->customNewTagAction->text() : d->newTagNode.text );
- }
-
- p.restore();
-}
-
-
-void Nepomuk::TagCloud::mousePressEvent( QMouseEvent* e )
-{
- if ( e->button() == Qt::LeftButton ) {
- if ( TagNode* node = d->tagAt( e->pos() ) ) {
- kDebug() << "clicked" << node->text;
- if ( node == &d->newTagNode ) {
- if ( d->customNewTagAction ) {
- d->customNewTagAction->trigger();
- }
- else {
- // FIXME: nicer gui
- Tag newTag = NewTagDialog::createTag( this );
- if ( newTag.isValid() ) {
- emit tagAdded( newTag );
- }
- }
- }
- else {
- emit tagClicked( node->tag );
- if ( d->selectionEnabled ) {
- kDebug() << "Toggleing tag" << node->text;
- node->selected = !node->selected;
- emit tagToggled( node->tag, node->selected );
- update( node->zoomedRect );
- }
- }
- }
- }
- else if ( d->contextMenuEnabled &&
- e->button() == Qt::RightButton ) {
- if ( TagNode* node = d->tagAt( e->pos() ) ) {
- KMenu menu;
- QAction* a = menu.addAction( KIcon( "edit-delete" ), i18nc( "@action:menu", "Delete tag '%1'", node->text ) );
- if ( menu.exec( e->globalPos() ) == a &&
- KMessageBox::questionYesNo( this, i18n( "Do you really want to delete tag '%1'?", node->text ) ) == KMessageBox::Yes ) {
- if ( d->selectionEnabled &&
- node->selected ) {
- node->selected = false;
- emit tagToggled( node->tag, false );
- }
- node->tag.remove();
- d->nodes.removeAll( *node );
- d->rebuildCloud();
- }
- }
- }
-}
-
-
-void Nepomuk::TagCloud::mouseMoveEvent( QMouseEvent* e )
-{
- if ( e->buttons() == Qt::NoButton ) {
-
- TagNode* oldHoverTag = d->hoverTag;
-
- if ( ( d->hoverTag = d->tagAt( e->pos() ) ) &&
- !d->selectionEnabled ) {
- setCursor( Qt::PointingHandCursor );
- }
- else if ( d->newTagButtonEnabled &&
- d->newTagNode.zoomedRect.contains( e->pos() ) ) {
- d->hoverTag = &d->newTagNode;
- setCursor( Qt::PointingHandCursor );
- }
- else {
- unsetCursor();
- }
-
- if ( oldHoverTag || d->hoverTag ) {
- QRect updateRect;
- if ( d->hoverTag )
- updateRect = updateRect.united( d->hoverTag->zoomedRect );
- if ( oldHoverTag )
- updateRect = updateRect.united( oldHoverTag->zoomedRect );
-
- update( updateRect );
- }
- }
-}
-
-
-void Nepomuk::TagCloud::leaveEvent( QEvent* )
-{
- unsetCursor();
- if ( d->hoverTag ) {
- QRect updateRect = d->hoverTag->zoomedRect;
- d->hoverTag = 0;
- update( updateRect );
- }
-}
-
-
-void Nepomuk::TagCloud::slotStatementAdded( const Soprano::Statement& s )
-{
- if ( s.predicate().uri() == Soprano::Vocabulary::RDF::type() &&
- s.object().uri() == Nepomuk::Tag::resourceTypeUri() ) {
- // new tag created
- if ( d->showAllTags ) {
- showAllTags();
- }
- }
- else if ( s.predicate().uri() == Nepomuk::Resource::tagUri() ) {
- if ( s.subject().uri() == d->resource ) {
- showResourceTags( d->resource );
- }
- else {
- // weights might have changed
- d->updateNodeWeights();
- d->rebuildCloud();
- }
- }
-}
-
-
-void Nepomuk::TagCloud::slotStatementRemoved( const Soprano::Statement& s )
-{
- // FIXME: In theory might contain empty nodes as wildcards
-
- if ( s.predicate().uri() == Nepomuk::Resource::tagUri() ) {
- if ( d->resource.isValid() &&
- d->resource == s.subject().uri() ) {
- showResourceTags( d->resource );
- }
- else {
- // weights might have changed
- d->updateNodeWeights();
- d->rebuildCloud();
- }
- }
- else if ( s.predicate().uri() == Soprano::Vocabulary::RDF::type() &&
- s.object().uri() == Nepomuk::Tag::resourceTypeUri() ) {
- // tag deleted
- if ( d->showAllTags ) {
- showAllTags();
- }
- }
-}
-
-
-void Nepomuk::TagCloud::setCustomNewTagAction( QAction* action )
-{
- d->customNewTagAction = action;
- setNewTagButtonEnabled( action != 0 );
-}
-
-#include "tagcloud.moc"