]>
cloud.milkyroute.net Git - dolphin.git/blob - src/tagcloud/tagcloud.cpp
0074d17968239a33e4bee172739b796ee65b7ee0
2 This file is part of the Nepomuk KDE project.
3 Copyright (C) 2007 Sebastian Trueg <trueg@kde.org>
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License version 2 as published by the Free Software Foundation.
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
21 #include "newtagdialog.h"
23 #include <QtGui/QFont>
24 #include <QtGui/QFontMetrics>
25 #include <QtCore/QList>
26 #include <QtGui/QPushButton>
28 #include <QtCore/QTime>
29 #include <QtGui/QPainter>
30 #include <QtGui/QMouseEvent>
31 #include <QtGui/QPalette>
32 #include <QtGui/QInputDialog>
33 #include <QtGui/QAction>
35 #include <KRandomSequence>
37 #include <KColorScheme>
40 #include <Soprano/Client/DBusModel>
41 #include <Soprano/QueryResultIterator>
42 #include <Soprano/Vocabulary/RDF>
43 #include <Soprano/Vocabulary/NAO>
45 #include <nepomuk/resourcemanager.h>
51 const int s_hSpacing
= 10;
52 const int s_vSpacing
= 5;
68 // info generated by rebuildCloud
75 bool tagNodeNameLessThan( const TagNode
& n1
, const TagNode
& n2
) {
76 return n1
.text
< n2
.text
;
79 bool tagNodeWeightLessThan( const TagNode
& n1
, const TagNode
& n2
) {
80 return n1
.weight
< n2
.weight
;
83 int rowLength( const QList
<TagNode
*>& row
) {
85 for ( int j
= 0; j
< row
.count(); ++j
) {
86 rowLen
+= row
[j
]->rect
.width();
87 if ( j
< row
.count()-1 ) {
94 int rowHeight( const QList
<TagNode
*>& row
) {
96 for ( int j
= 0; j
< row
.count(); ++j
) {
97 h
= qMax( row
[j
]->rect
.height(), h
);
102 QSize
cloudSize( const QList
<QList
<TagNode
*> >& rows
) {
105 for ( int i
= 0; i
< rows
.count(); ++i
) {
106 w
= qMax( w
, rowLength( rows
[i
] ) );
107 h
+= rowHeight( rows
[i
] );
108 if ( i
< rows
.count()-1 ) {
112 return QSize( w
, h
);
117 class Nepomuk::TagCloud::Private
120 Private( TagCloud
* parent
)
123 maxNumberDisplayedTags( 0 ),
124 selectionEnabled( false ),
125 newTagButtonEnabled( false ),
126 alignment( Qt::AlignCenter
),
127 sorting( SortAlpabetically
),
129 showAllTags( false ),
130 customNewTagAction( 0 ),
132 cachedHfwWidth( -1 ),
134 newTagNode
.text
= i18nc( "@label", "New Tag..." );
139 int maxNumberDisplayedTags
;
140 bool selectionEnabled
;
141 bool newTagButtonEnabled
;
142 Qt::Alignment alignment
;
146 // The resource whose tags we are showing
147 // invalid if we show all tags or a selection
152 QList
<TagNode
> nodes
;
154 // just a helper structure for speeding up things
155 QList
<QList
<TagNode
*> > rows
;
158 QAction
* customNewTagAction
;
164 QSize cachedSizeHint
;
168 void invalidateCachedValues() {
169 cachedSizeHint
= QSize();
173 int getMinFontSize() const;
174 int getMaxFontSize() const;
175 void updateNodeWeights();
176 void updateNodeFonts();
179 TagNode
* tagAt( const QPoint
& pos
);
180 TagNode
* findTagInRow( const QList
<TagNode
*>& row
, const QPoint
& pos
);
181 TagNode
* nodeForTag( const Tag
& tag
);
182 int calculateWeight( const Nepomuk::Tag
& tag
);
189 int Nepomuk::TagCloud::Private::getMinFontSize() const
191 return minFontSize
> 0 ? minFontSize
: ( 8 * m_parent
->font().pointSize() / 10 );
195 int Nepomuk::TagCloud::Private::getMaxFontSize() const
197 return maxFontSize
> 0 ? maxFontSize
: ( 22 * m_parent
->font().pointSize() / 10 );
201 int Nepomuk::TagCloud::Private::calculateWeight( const Nepomuk::Tag
& tag
)
203 // stupid SPARQL has no functions such as count!
204 Soprano::QueryResultIterator it
205 = ResourceManager::instance()->mainModel()->executeQuery( QString( "select ?r where { ?r <%1> <%2> . }" )
206 .arg( Soprano::Vocabulary::NAO::hasTag().toString() )
207 .arg( QString::fromAscii( tag
.resourceUri().toEncoded() ) ),
208 Soprano::Query::QueryLanguageSparql
);
210 while ( it
.next() ) {
217 void Nepomuk::TagCloud::Private::updateNodeWeights()
219 bool changedWeights
= false;
220 for ( QList
<TagNode
>::iterator it
= nodes
.begin();
221 it
!= nodes
.end(); ++it
) {
223 int w
= calculateWeight( node
.tag
);
224 if ( w
!= node
.weight
) {
226 changedWeights
= true;
229 if ( changedWeights
) {
235 void Nepomuk::TagCloud::Private::updateNodeFonts()
239 for ( QList
<TagNode
>::iterator it
= nodes
.begin();
240 it
!= nodes
.end(); ++it
) {
242 minWeight
= qMin( minWeight
, node
.weight
);
243 maxWeight
= qMax( maxWeight
, node
.weight
);
246 // calculate font sizes
247 // ----------------------------------------------
248 int usedMinFontSize
= getMinFontSize();
249 int usedMaxFontSize
= getMaxFontSize();
250 for ( QList
<TagNode
>::iterator it
= nodes
.begin();
251 it
!= nodes
.end(); ++it
) {
253 double normalizedWeight
= (double)(node
.weight
- minWeight
) / (double)qMax(maxWeight
- minWeight
, 1);
254 node
.font
= m_parent
->font();
255 node
.font
.setPointSize( usedMinFontSize
+ (int)((double)(usedMaxFontSize
-usedMinFontSize
) * normalizedWeight
) );
256 if( normalizedWeight
> 0.8 )
257 node
.font
.setBold( true );
260 if ( newTagButtonEnabled
) {
261 newTagNode
.font
= m_parent
->font();
262 newTagNode
.font
.setPointSize( usedMinFontSize
);
263 newTagNode
.font
.setUnderline( true );
268 void Nepomuk::TagCloud::Private::sortNodes()
270 if ( sorting
== SortAlpabetically
) {
271 qSort( nodes
.begin(), nodes
.end(), tagNodeNameLessThan
);
273 else if ( sorting
== SortByWeight
) {
274 qSort( nodes
.begin(), nodes
.end(), tagNodeWeightLessThan
);
276 else if ( sorting
== SortRandom
) {
277 KRandomSequence().randomize( nodes
);
282 void Nepomuk::TagCloud::Private::rebuildCloud()
284 // - Always try to be quadratic
285 // - Always prefer to expand horizontally
286 // - If we cannot fit everything into m_parent->contentsRect(), zoom
287 // - If alignment & Qt::AlignJustify insert spaces between tags
291 QRect contentsRect
= m_parent
->contentsRect();
293 // initialize the nodes' sizes
294 // ----------------------------------------------
295 for ( QList
<TagNode
>::iterator it
= nodes
.begin();
296 it
!= nodes
.end(); ++it
) {
298 node
.rect
= QFontMetrics( node
.font
).boundingRect( node
.text
);
300 if ( newTagButtonEnabled
) {
301 newTagNode
.rect
= QFontMetrics( newTagNode
.font
).boundingRect( customNewTagAction
? customNewTagAction
->text() : newTagNode
.text
);
305 // and position the nodes
306 // ----------------------------------------------
308 if ( !nodes
.isEmpty() || newTagButtonEnabled
) {
309 if ( 0 ) { // FIXME: make it configurable
313 for ( QList
<TagNode
>::iterator it
= nodes
.begin();
314 it
!= nodes
.end(); /* We do increment it below */ ) {
317 int usedSpacing
= row
.isEmpty() ? 0 : s_hSpacing
;
318 if ( lineRect
.width() + usedSpacing
+ node
.rect
.width() <= contentsRect
.width() ) {
319 node
.rect
.moveBottomLeft( QPoint( lineRect
.right() + usedSpacing
, lineRect
.bottom() ) );
320 QRect newLineRect
= lineRect
.united( node
.rect
);
321 newLineRect
.moveTopLeft( lineRect
.topLeft() );
322 lineRect
= newLineRect
;
325 // update all other nodes in this line
326 Q_FOREACH( TagNode
* n
, row
) {
327 n
->rect
.moveBottom( lineRect
.bottom() - ( lineRect
.height() - n
->rect
.height() )/2 );
335 int newLineTop
= lineRect
.bottom() + s_vSpacing
;
337 lineRect
.moveTop( newLineTop
);
343 // initialize first row
344 rows
.append( QList
<TagNode
*>() );
345 for ( QList
<TagNode
>::iterator it
= nodes
.begin();
346 it
!= nodes
.end(); ++it
) {
348 rows
.first().append( &node
);
350 if ( newTagButtonEnabled
) {
351 rows
.first().append( &newTagNode
);
354 // calculate the rows
355 QList
<QList
<TagNode
*> > bestRows( rows
);
356 QSize
size( rowLength( rows
.first() ), rowHeight( rows
.first() ) );
357 QSize
bestSize( size
);
358 while ( ( size
.height() < size
.width() ||
359 size
.width() > contentsRect
.width() ) &&
360 size
.height() <= contentsRect
.height() ) {
361 // find the longest row
364 for ( int i
= 0; i
< rows
.count(); ++i
) {
365 int rowLen
= rowLength( rows
[i
] );
366 if ( rowLen
> maxLen
) {
372 // move the last item from the longest row to the next row
373 TagNode
* node
= rows
[maxRow
].takeLast();
374 if ( rows
.count() <= maxRow
+1 ) {
375 rows
.append( QList
<TagNode
*>() );
377 rows
[maxRow
+1].prepend( node
);
380 size
= cloudSize( rows
);
382 if ( size
.width() < bestSize
.width() &&
383 ( size
.width() > size
.height() ||
384 bestSize
.width() > contentsRect
.width() ) &&
385 size
.height() <= contentsRect
.height() ) {
394 for ( QList
<QList
<TagNode
*> >::iterator rowIt
= rows
.begin(); rowIt
!= rows
.end(); ++rowIt
) {
395 QList
<TagNode
*>& row
= *rowIt
;
396 int h
= rowHeight( row
);
398 Q_FOREACH( TagNode
* node
, row
) {
399 node
->rect
.moveTop( y
+ ( h
- node
->rect
.height() )/2 );
400 node
->rect
.moveLeft( x
);
401 x
+= s_hSpacing
+ node
->rect
.width();
408 // let's see if we have to zoom
409 // ----------------------------------------------
410 zoomMatrix
= QMatrix();
411 int w
= contentsRect
.width();
413 for ( QList
<QList
<TagNode
*> >::iterator rowIt
= rows
.begin(); rowIt
!= rows
.end(); ++rowIt
) {
414 QList
<TagNode
*>& row
= *rowIt
;
415 w
= qMax( w
, row
.last()->rect
.right() );
417 if ( w
> contentsRect
.width() ) {
418 double zoomFactor
= ( double )contentsRect
.width() / ( double )w
;
419 zoomMatrix
.scale( zoomFactor
, zoomFactor
);
423 // force horizontal alignment
424 // ----------------------------------------------
425 for ( QList
<QList
<TagNode
*> >::iterator rowIt
= rows
.begin(); rowIt
!= rows
.end(); ++rowIt
) {
426 QList
<TagNode
*>& row
= *rowIt
;
427 int space
= /*contentsRect.right()*/w
- row
.last()->rect
.right();
428 if ( alignment
& ( Qt::AlignRight
|Qt::AlignHCenter
) ) {
429 Q_FOREACH( TagNode
* node
, row
) {
430 node
->rect
.moveLeft( node
->rect
.left() + ( alignment
& Qt::AlignRight
? space
: space
/2 ) );
433 else if ( alignment
& Qt::AlignJustify
&& row
.count() > 1 ) {
434 space
/= ( row
.count()-1 );
436 Q_FOREACH( TagNode
* node
, row
) {
437 node
->rect
.moveLeft( node
->rect
.left() + ( space
* i
++ ) );
442 // force vertical alignment
443 // ----------------------------------------------
444 int verticalSpace
= contentsRect
.bottom() - rows
.last().first()->rect
.bottom();
445 if ( alignment
& ( Qt::AlignBottom
|Qt::AlignVCenter
) ) {
446 for ( QList
<QList
<TagNode
*> >::iterator rowIt
= rows
.begin(); rowIt
!= rows
.end(); ++rowIt
) {
447 Q_FOREACH( TagNode
* node
, *rowIt
) {
448 node
->rect
.moveTop( node
->rect
.top() + ( alignment
& Qt::AlignBottom
? verticalSpace
: verticalSpace
/2 ) );
453 for( QList
<TagNode
>::iterator it
= nodes
.begin(); it
!= nodes
.end(); ++it
) {
454 it
->zoomedRect
= zoomMatrix
.mapRect( it
->rect
);
456 newTagNode
.zoomedRect
= zoomMatrix
.mapRect( newTagNode
.rect
);
459 m_parent
->updateGeometry();
464 // binary search in row
465 TagNode
* Nepomuk::TagCloud::Private::findTagInRow( const QList
<TagNode
*>& row
, const QPoint
& pos
)
467 int x
= m_parent
->width() ? row
.count() * pos
.x() / m_parent
->width() : 0;
471 if ( x
-i
>= 0 && x
-i
< row
.count() && row
[x
-i
]->zoomedRect
.contains( pos
) ) {
474 else if ( x
+i
>= 0 && x
+i
< row
.count() && row
[x
+i
]->zoomedRect
.contains( pos
) ) {
477 if ( x
-i
< 0 && x
+i
>= row
.count() ) {
486 // binary search in cloud
487 TagNode
* Nepomuk::TagCloud::Private::tagAt( const QPoint
& pos
)
489 int y
= m_parent
->height() ? rows
.count() * pos
.y() / m_parent
->height() : 0;
493 if ( y
-i
>= 0 && y
-i
< rows
.count() ) {
494 if ( TagNode
* node
= findTagInRow( rows
[y
-i
], pos
) ) {
498 if ( y
+i
>= 0 && y
+i
< rows
.count() ) {
499 if ( TagNode
* node
= findTagInRow( rows
[y
+i
], pos
) ) {
503 if ( y
-i
< 0 && y
+i
>= rows
.count() ) {
512 TagNode
* Nepomuk::TagCloud::Private::nodeForTag( const Tag
& tag
)
514 for ( QList
<TagNode
>::iterator it
= nodes
.begin();
515 it
!= nodes
.end(); ++it
) {
517 if ( tag
== node
.tag
) {
526 Nepomuk::TagCloud::TagCloud( QWidget
* parent
)
528 d( new Private(this) )
530 QSizePolicy
policy( QSizePolicy::Preferred
,
531 QSizePolicy::Preferred
);
532 policy
.setHeightForWidth( true );
533 setSizePolicy( policy
);
534 setMouseTracking( true );
536 // Since signals are delivered in no particular order
537 // our slot might be called before the resources are updated
538 // Then, we would use invalid cached data.
539 // By using queued connections this problem should be solved.
540 connect( ResourceManager::instance()->mainModel(),
541 SIGNAL( statementAdded( const Soprano::Statement
& ) ),
543 SLOT( slotStatementAdded( const Soprano::Statement
& ) ),
544 Qt::QueuedConnection
);
545 connect( ResourceManager::instance()->mainModel(),
546 SIGNAL( statementRemoved( const Soprano::Statement
& ) ),
548 SLOT( slotStatementRemoved( const Soprano::Statement
& ) ),
549 Qt::QueuedConnection
);
553 Nepomuk::TagCloud::~TagCloud()
559 void Nepomuk::TagCloud::setMaxFontSize( int size
)
561 d
->invalidateCachedValues();
562 d
->maxFontSize
= size
;
563 d
->updateNodeFonts();
568 void Nepomuk::TagCloud::setMinFontSize( int size
)
570 d
->invalidateCachedValues();
571 d
->minFontSize
= size
;
572 d
->updateNodeFonts();
577 void Nepomuk::TagCloud::setMaxNumberDisplayedTags( int n
)
579 d
->maxNumberDisplayedTags
= n
;
584 void Nepomuk::TagCloud::setSelectionEnabled( bool enabled
)
586 d
->selectionEnabled
= enabled
;
591 void Nepomuk::TagCloud::setNewTagButtonEnabled( bool enabled
)
593 d
->newTagButtonEnabled
= enabled
;
598 bool Nepomuk::TagCloud::zoomEnabled() const
600 return d
->zoomEnabled
;
604 void Nepomuk::TagCloud::setZoomEnabled( bool zoom
)
606 if ( d
->zoomEnabled
!= zoom
) {
607 d
->zoomEnabled
= zoom
;
613 void Nepomuk::TagCloud::setContextMenuEnabled( bool enabled
)
619 void Nepomuk::TagCloud::setAlignment( Qt::Alignment alignment
)
621 d
->alignment
= alignment
;
626 void Nepomuk::TagCloud::setSorting( Sorting s
)
628 d
->invalidateCachedValues();
634 void Nepomuk::TagCloud::showAllTags()
636 showTags( Nepomuk::Tag::allTags() );
637 d
->showAllTags
= true;
641 void Nepomuk::TagCloud::showResourceTags( const Resource
& resource
)
643 showTags( resource
.tags() );
644 d
->resource
= resource
.uri();
648 void Nepomuk::TagCloud::showTags( const QList
<Tag
>& tags
)
650 d
->resource
= QUrl();
651 d
->showAllTags
= false;
652 d
->invalidateCachedValues();
654 Q_FOREACH( const Tag
&tag
, tags
) {
657 node
.weight
= d
->calculateWeight( tag
);
658 node
.text
= node
.tag
.genericLabel();
660 d
->nodes
.append( node
);
662 d
->updateNodeFonts();
667 void Nepomuk::TagCloud::setTagSelected( const Tag
& tag
, bool selected
)
669 if ( TagNode
* node
= d
->nodeForTag( tag
) ) {
670 node
->selected
= selected
;
671 if ( d
->selectionEnabled
) {
672 update( node
->zoomedRect
);
678 QSize
Nepomuk::TagCloud::sizeHint() const
680 // If we have tags d->rebuildCloud() has been called at least once,
681 // thus, we have proper rects (i.e. needed sizes)
683 if ( !d
->cachedSizeHint
.isValid() ) {
684 QList
<QList
<TagNode
*> > rows
;
685 rows
.append( QList
<TagNode
*>() );
686 for ( QList
<TagNode
>::iterator it
= d
->nodes
.begin();
687 it
!= d
->nodes
.end(); ++it
) {
689 rows
.first().append( &node
);
691 if ( d
->newTagButtonEnabled
) {
692 rows
.first().append( &d
->newTagNode
);
695 QSize
size( rowLength( rows
.first() ), rowHeight( rows
.first() ) );
696 QSize
bestSize( size
);
697 while ( size
.height() < size
.width() ) {
698 // find the longest row
701 for ( int i
= 0; i
< rows
.count(); ++i
) {
702 int rowLen
= rowLength( rows
[i
] );
703 if ( rowLen
> maxLen
) {
709 // move the last item from the longest row to the next row
710 TagNode
* node
= rows
[maxRow
].takeLast();
711 if ( rows
.count() <= maxRow
+1 ) {
712 rows
.append( QList
<TagNode
*>() );
714 rows
[maxRow
+1].prepend( node
);
717 size
= cloudSize( rows
);
719 if ( size
.width() < bestSize
.width() &&
720 size
.width() > size
.height() ) {
725 d
->cachedSizeHint
= QSize( bestSize
.width() + frameWidth()*2,
726 bestSize
.height() + frameWidth()*2 );
729 return d
->cachedSizeHint
;
733 QSize
Nepomuk::TagCloud::minimumSizeHint() const
735 return QFrame::minimumSizeHint();
736 // If we have tags d->rebuildCloud() has been called at least once,
737 // thus, we have proper rects (i.e. needed sizes)
738 if ( d
->nodes
.isEmpty() && !d
->newTagButtonEnabled
) {
739 return QSize( fontMetrics().width( i18nc( "@label Indicator when no tags defined", "No Tags" ) ), fontMetrics().height() );
743 for ( QList
<TagNode
>::iterator it
= d
->nodes
.begin();
744 it
!= d
->nodes
.end(); ++it
) {
745 size
.setWidth( qMax( size
.width(), ( *it
).rect
.width() ) );
746 size
.setHeight( qMax( size
.height(), ( *it
).rect
.height() ) );
748 if ( d
->newTagButtonEnabled
) {
749 size
.setWidth( qMax( size
.width(), d
->newTagNode
.rect
.width() ) );
750 size
.setHeight( qMax( size
.height(), d
->newTagNode
.rect
.height() ) );
752 size
.setWidth( size
.width() + frameWidth()*2 );
753 size
.setHeight( size
.height() + frameWidth()*2 );
759 int Nepomuk::TagCloud::heightForWidth( int contentsWidth
) const
761 // If we have tags d->rebuildCloud() has been called at least once,
762 // thus, we have proper rects (i.e. needed sizes)
764 if ( d
->cachedHfwWidth
!= contentsWidth
) {
765 // have to keep in mind the frame
766 contentsWidth
-= frameWidth()*2;
768 QList
<TagNode
*> allNodes
;
769 for ( QList
<TagNode
>::iterator it
= d
->nodes
.begin();
770 it
!= d
->nodes
.end(); ++it
) {
772 allNodes
.append( &node
);
774 if ( d
->newTagButtonEnabled
) {
775 allNodes
.append( &d
->newTagNode
);
783 for ( int i
= 0; i
< allNodes
.count(); ++i
) {
789 w
+= allNodes
[i
]->rect
.width();
790 if ( w
<= contentsWidth
) {
791 rowH
= qMax( rowH
, allNodes
[i
]->rect
.height() );
799 rowH
= allNodes
[i
]->rect
.height();
800 rowW
= allNodes
[i
]->rect
.width();
802 maxW
= qMax( maxW
, rowW
);
805 h
+= s_vSpacing
+ rowH
;
808 d
->cachedHfwWidth
= contentsWidth
;
809 d
->cachedHfwHeight
= h
;
812 if ( maxW
> contentsWidth
) {
813 d
->cachedHfwHeight
= d
->cachedHfwHeight
* contentsWidth
/ maxW
;
817 return d
->cachedHfwHeight
+ frameWidth()*2;
821 void Nepomuk::TagCloud::resizeEvent( QResizeEvent
* e
)
823 QFrame::resizeEvent( e
);
829 void Nepomuk::TagCloud::paintEvent( QPaintEvent
* e
)
831 QFrame::paintEvent( e
);
833 KStatefulBrush
normalTextBrush( KColorScheme::View
, KColorScheme::NormalText
);
834 KStatefulBrush
activeTextBrush( KColorScheme::View
, KColorScheme::VisitedText
);
835 KStatefulBrush
hoverTextBrush( KColorScheme::View
, KColorScheme::ActiveText
);
838 QRegion paintRegion
= e
->region();
841 p
.setMatrix( d
->zoomMatrix
);
843 for ( QList
<TagNode
>::iterator it
= d
->nodes
.begin();
844 it
!= d
->nodes
.end(); ++it
) {
847 if ( paintRegion
.contains( node
.zoomedRect
) ) {
848 p
.setFont( node
.font
);
850 if ( &node
== d
->hoverTag
) {
851 p
.setPen( hoverTextBrush
.brush( this ).color() );
853 else if ( d
->selectionEnabled
&& node
.selected
) {
854 p
.setPen( activeTextBrush
.brush( this ).color() );
857 p
.setPen( normalTextBrush
.brush( this ).color() );
859 p
.drawText( node
.rect
, Qt::AlignCenter
, node
.text
);
863 if ( d
->newTagButtonEnabled
) {
864 p
.setFont( d
->newTagNode
.font
);
865 if ( &d
->newTagNode
== d
->hoverTag
) {
866 p
.setPen( hoverTextBrush
.brush( this ).color() );
869 p
.setPen( normalTextBrush
.brush( this ).color() );
871 p
.drawText( d
->newTagNode
.rect
, Qt::AlignCenter
, d
->customNewTagAction
? d
->customNewTagAction
->text() : d
->newTagNode
.text
);
878 void Nepomuk::TagCloud::mousePressEvent( QMouseEvent
* e
)
880 if ( e
->button() == Qt::LeftButton
) {
881 if ( TagNode
* node
= d
->tagAt( e
->pos() ) ) {
882 kDebug() << "clicked" << node
->text
;
883 if ( node
== &d
->newTagNode
) {
884 if ( d
->customNewTagAction
) {
885 d
->customNewTagAction
->trigger();
889 Tag newTag
= NewTagDialog::createTag( this );
890 if ( newTag
.isValid() ) {
891 emit
tagAdded( newTag
);
896 emit
tagClicked( node
->tag
);
897 if ( d
->selectionEnabled
) {
898 kDebug() << "Toggleing tag" << node
->text
;
899 node
->selected
= !node
->selected
;
900 emit
tagToggled( node
->tag
, node
->selected
);
901 update( node
->zoomedRect
);
909 void Nepomuk::TagCloud::mouseMoveEvent( QMouseEvent
* e
)
911 if ( e
->buttons() == Qt::NoButton
) {
913 TagNode
* oldHoverTag
= d
->hoverTag
;
915 if ( ( d
->hoverTag
= d
->tagAt( e
->pos() ) ) &&
916 !d
->selectionEnabled
) {
917 setCursor( Qt::PointingHandCursor
);
919 else if ( d
->newTagButtonEnabled
&&
920 d
->newTagNode
.zoomedRect
.contains( e
->pos() ) ) {
921 d
->hoverTag
= &d
->newTagNode
;
922 setCursor( Qt::PointingHandCursor
);
928 if ( oldHoverTag
|| d
->hoverTag
) {
931 updateRect
= updateRect
.united( d
->hoverTag
->zoomedRect
);
933 updateRect
= updateRect
.united( oldHoverTag
->zoomedRect
);
935 update( updateRect
);
941 void Nepomuk::TagCloud::leaveEvent( QEvent
* )
945 QRect updateRect
= d
->hoverTag
->zoomedRect
;
947 update( updateRect
);
952 void Nepomuk::TagCloud::slotStatementAdded( const Soprano::Statement
& s
)
954 if ( s
.predicate().uri() == Soprano::Vocabulary::RDF::type() &&
955 s
.object().uri() == Nepomuk::Tag::resourceTypeUri() ) {
957 if ( d
->showAllTags
) {
961 else if ( s
.predicate().uri() == Nepomuk::Resource::tagUri() ) {
962 if ( s
.subject().uri() == d
->resource
) {
963 showResourceTags( d
->resource
);
966 // weights might have changed
967 d
->updateNodeWeights();
974 void Nepomuk::TagCloud::slotStatementRemoved( const Soprano::Statement
& s
)
976 // FIXME: In theory might contain empty nodes as wildcards
978 if ( s
.predicate().uri() == Nepomuk::Resource::tagUri() ) {
979 if ( d
->resource
.isValid() &&
980 d
->resource
== s
.subject().uri() ) {
981 showResourceTags( d
->resource
);
984 // weights might have changed
985 d
->updateNodeWeights();
989 else if ( s
.predicate().uri() == Soprano::Vocabulary::RDF::type() &&
990 s
.object().uri() == Nepomuk::Tag::resourceTypeUri() ) {
992 if ( d
->showAllTags
) {
999 void Nepomuk::TagCloud::setCustomNewTagAction( QAction
* action
)
1001 d
->customNewTagAction
= action
;
1002 setNewTagButtonEnabled( action
!= 0 );
1005 #include "tagcloud.moc"