]>
cloud.milkyroute.net Git - dolphin.git/blob - src/tagcloud/tagcloud.cpp
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
= i18n( "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 if ( nodes
.isEmpty() && !newTagButtonEnabled
) {
288 // - Always try to be quadratic
289 // - Always prefer to expand horizontally
290 // - If we cannot fit everything into m_parent->contentsRect(), zoom
291 // - If alignment & Qt::AlignJustify insert spaces between tags
295 QRect contentsRect
= m_parent
->contentsRect();
297 // initialize the nodes' sizes
298 // ----------------------------------------------
299 for ( QList
<TagNode
>::iterator it
= nodes
.begin();
300 it
!= nodes
.end(); ++it
) {
302 node
.rect
= QFontMetrics( node
.font
).boundingRect( node
.text
);
304 if ( newTagButtonEnabled
) {
305 newTagNode
.rect
= QFontMetrics( newTagNode
.font
).boundingRect( customNewTagAction
? customNewTagAction
->text() : newTagNode
.text
);
309 // and position the nodes
310 // ----------------------------------------------
312 if ( 0 ) { // FIXME: make it configurable
316 for ( QList
<TagNode
>::iterator it
= nodes
.begin();
317 it
!= nodes
.end(); /* We do increment it below */ ) {
320 int usedSpacing
= row
.isEmpty() ? 0 : s_hSpacing
;
321 if ( lineRect
.width() + usedSpacing
+ node
.rect
.width() <= contentsRect
.width() ) {
322 node
.rect
.moveBottomLeft( QPoint( lineRect
.right() + usedSpacing
, lineRect
.bottom() ) );
323 QRect newLineRect
= lineRect
.united( node
.rect
);
324 newLineRect
.moveTopLeft( lineRect
.topLeft() );
325 lineRect
= newLineRect
;
328 // update all other nodes in this line
329 Q_FOREACH( TagNode
* n
, row
) {
330 n
->rect
.moveBottom( lineRect
.bottom() - ( lineRect
.height() - n
->rect
.height() )/2 );
338 int newLineTop
= lineRect
.bottom() + s_vSpacing
;
340 lineRect
.moveTop( newLineTop
);
346 // initialize first row
347 rows
.append( QList
<TagNode
*>() );
348 for ( QList
<TagNode
>::iterator it
= nodes
.begin();
349 it
!= nodes
.end(); ++it
) {
351 rows
.first().append( &node
);
353 if ( newTagButtonEnabled
) {
354 rows
.first().append( &newTagNode
);
357 // calculate the rows
358 QList
<QList
<TagNode
*> > bestRows( rows
);
359 QSize
size( rowLength( rows
.first() ), rowHeight( rows
.first() ) );
360 QSize
bestSize( size
);
361 while ( ( size
.height() < size
.width() ||
362 size
.width() > contentsRect
.width() ) &&
363 size
.height() <= contentsRect
.height() ) {
364 // find the longest row
367 for ( int i
= 0; i
< rows
.count(); ++i
) {
368 int rowLen
= rowLength( rows
[i
] );
369 if ( rowLen
> maxLen
) {
375 // move the last item from the longest row to the next row
376 TagNode
* node
= rows
[maxRow
].takeLast();
377 if ( rows
.count() <= maxRow
+1 ) {
378 rows
.append( QList
<TagNode
*>() );
380 rows
[maxRow
+1].prepend( node
);
383 size
= cloudSize( rows
);
385 if ( size
.width() < bestSize
.width() &&
386 ( size
.width() > size
.height() ||
387 bestSize
.width() > contentsRect
.width() ) &&
388 size
.height() <= contentsRect
.height() ) {
397 for ( QList
<QList
<TagNode
*> >::iterator rowIt
= rows
.begin(); rowIt
!= rows
.end(); ++rowIt
) {
398 QList
<TagNode
*>& row
= *rowIt
;
399 int h
= rowHeight( row
);
401 Q_FOREACH( TagNode
* node
, row
) {
402 node
->rect
.moveTop( y
+ ( h
- node
->rect
.height() )/2 );
403 node
->rect
.moveLeft( x
);
404 x
+= s_hSpacing
+ node
->rect
.width();
411 // let's see if we have to zoom
412 // ----------------------------------------------
413 zoomMatrix
= QMatrix();
414 int w
= contentsRect
.width();
416 for ( QList
<QList
<TagNode
*> >::iterator rowIt
= rows
.begin(); rowIt
!= rows
.end(); ++rowIt
) {
417 QList
<TagNode
*>& row
= *rowIt
;
418 w
= qMax( w
, row
.last()->rect
.right() );
420 if ( w
> contentsRect
.width() ) {
421 double zoomFactor
= ( double )contentsRect
.width() / ( double )w
;
422 zoomMatrix
.scale( zoomFactor
, zoomFactor
);
426 // force horizontal alignment
427 // ----------------------------------------------
428 for ( QList
<QList
<TagNode
*> >::iterator rowIt
= rows
.begin(); rowIt
!= rows
.end(); ++rowIt
) {
429 QList
<TagNode
*>& row
= *rowIt
;
430 int space
= /*contentsRect.right()*/w
- row
.last()->rect
.right();
431 if ( alignment
& ( Qt::AlignRight
|Qt::AlignHCenter
) ) {
432 Q_FOREACH( TagNode
* node
, row
) {
433 node
->rect
.moveLeft( node
->rect
.left() + ( alignment
& Qt::AlignRight
? space
: space
/2 ) );
436 else if ( alignment
& Qt::AlignJustify
&& row
.count() > 1 ) {
437 space
/= ( row
.count()-1 );
439 Q_FOREACH( TagNode
* node
, row
) {
440 node
->rect
.moveLeft( node
->rect
.left() + ( space
* i
++ ) );
445 // force vertical alignment
446 // ----------------------------------------------
447 int verticalSpace
= contentsRect
.bottom() - rows
.last().first()->rect
.bottom();
448 if ( alignment
& ( Qt::AlignBottom
|Qt::AlignVCenter
) ) {
449 for ( QList
<QList
<TagNode
*> >::iterator rowIt
= rows
.begin(); rowIt
!= rows
.end(); ++rowIt
) {
450 Q_FOREACH( TagNode
* node
, *rowIt
) {
451 node
->rect
.moveTop( node
->rect
.top() + ( alignment
& Qt::AlignBottom
? verticalSpace
: verticalSpace
/2 ) );
456 for( QList
<TagNode
>::iterator it
= nodes
.begin(); it
!= nodes
.end(); ++it
) {
457 it
->zoomedRect
= zoomMatrix
.mapRect( it
->rect
);
459 newTagNode
.zoomedRect
= zoomMatrix
.mapRect( newTagNode
.rect
);
461 m_parent
->updateGeometry();
466 // binary search in row
467 TagNode
* Nepomuk::TagCloud::Private::findTagInRow( const QList
<TagNode
*>& row
, const QPoint
& pos
)
469 int x
= row
.count() * pos
.x() / m_parent
->width();
472 if ( x
-i
>= 0 && x
-i
< row
.count() && row
[x
-i
]->zoomedRect
.contains( pos
) ) {
475 else if ( x
+i
>= 0 && x
+i
< row
.count() && row
[x
+i
]->zoomedRect
.contains( pos
) ) {
478 if ( x
-i
< 0 && x
+i
>= row
.count() ) {
487 // binary search in cloud
488 TagNode
* Nepomuk::TagCloud::Private::tagAt( const QPoint
& pos
)
490 int y
= rows
.count() * pos
.y() / m_parent
->height();
494 if ( y
-i
>= 0 && y
-i
< rows
.count() ) {
495 if ( TagNode
* node
= findTagInRow( rows
[y
-i
], pos
) ) {
499 if ( y
+i
>= 0 && y
+i
< rows
.count() ) {
500 if ( TagNode
* node
= findTagInRow( rows
[y
+i
], pos
) ) {
504 if ( y
-i
< 0 && y
+i
>= rows
.count() ) {
513 TagNode
* Nepomuk::TagCloud::Private::nodeForTag( const Tag
& tag
)
515 for ( QList
<TagNode
>::iterator it
= nodes
.begin();
516 it
!= nodes
.end(); ++it
) {
518 if ( tag
== node
.tag
) {
527 Nepomuk::TagCloud::TagCloud( QWidget
* parent
)
529 d( new Private(this) )
531 QSizePolicy
policy( QSizePolicy::Preferred
,
532 QSizePolicy::Preferred
);
533 policy
.setHeightForWidth( true );
534 setSizePolicy( policy
);
535 setMouseTracking( true );
537 // Since signals are delivered in no particular order
538 // our slot might be called before the resources are updated
539 // Then, we would use invalid cached data.
540 // By using queued connections this problem should be solved.
541 connect( ResourceManager::instance()->mainModel(),
542 SIGNAL( statementAdded( const Soprano::Statement
& ) ),
544 SLOT( slotStatementAdded( const Soprano::Statement
& ) ),
545 Qt::QueuedConnection
);
546 connect( ResourceManager::instance()->mainModel(),
547 SIGNAL( statementRemoved( const Soprano::Statement
& ) ),
549 SLOT( slotStatementRemoved( const Soprano::Statement
& ) ),
550 Qt::QueuedConnection
);
554 Nepomuk::TagCloud::~TagCloud()
560 void Nepomuk::TagCloud::setMaxFontSize( int size
)
562 d
->invalidateCachedValues();
563 d
->maxFontSize
= size
;
564 d
->updateNodeFonts();
569 void Nepomuk::TagCloud::setMinFontSize( int size
)
571 d
->invalidateCachedValues();
572 d
->minFontSize
= size
;
573 d
->updateNodeFonts();
578 void Nepomuk::TagCloud::setMaxNumberDisplayedTags( int n
)
580 d
->maxNumberDisplayedTags
= n
;
585 void Nepomuk::TagCloud::setSelectionEnabled( bool enabled
)
587 d
->selectionEnabled
= enabled
;
592 void Nepomuk::TagCloud::setNewTagButtonEnabled( bool enabled
)
594 d
->newTagButtonEnabled
= enabled
;
599 bool Nepomuk::TagCloud::zoomEnabled() const
601 return d
->zoomEnabled
;
605 void Nepomuk::TagCloud::setZoomEnabled( bool zoom
)
607 if ( d
->zoomEnabled
!= zoom
) {
608 d
->zoomEnabled
= zoom
;
614 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( 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( i18n( "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 // FIXME: add zoom here
766 if ( d
->cachedHfwWidth
!= contentsWidth
) {
767 // have to keep in mind the frame
768 contentsWidth
-= frameWidth()*2;
770 QList
<TagNode
*> allNodes
;
771 for ( QList
<TagNode
>::iterator it
= d
->nodes
.begin();
772 it
!= d
->nodes
.end(); ++it
) {
774 allNodes
.append( &node
);
776 if ( d
->newTagButtonEnabled
) {
777 allNodes
.append( &d
->newTagNode
);
784 for ( int i
= 0; i
< allNodes
.count(); ++i
) {
790 w
+= allNodes
[i
]->rect
.width();
791 if ( w
<= contentsWidth
) {
792 rowH
= qMax( rowH
, allNodes
[i
]->rect
.height() );
800 rowH
= allNodes
[i
]->rect
.height();
801 rowW
= allNodes
[i
]->rect
.width();
805 h
+= s_vSpacing
+ rowH
;
808 d
->cachedHfwWidth
= contentsWidth
;
809 d
->cachedHfwHeight
= h
;
812 return d
->cachedHfwHeight
+ frameWidth()*2;
816 void Nepomuk::TagCloud::resizeEvent( QResizeEvent
* e
)
818 QFrame::resizeEvent( e
);
824 void Nepomuk::TagCloud::paintEvent( QPaintEvent
* e
)
826 QFrame::paintEvent( e
);
828 KStatefulBrush
normalTextBrush( KColorScheme::View
, KColorScheme::NormalText
);
829 KStatefulBrush
activeTextBrush( KColorScheme::View
, KColorScheme::VisitedText
);
830 KStatefulBrush
hoverTextBrush( KColorScheme::View
, KColorScheme::ActiveText
);
833 QRegion paintRegion
= e
->region();
836 p
.setMatrix( d
->zoomMatrix
);
838 for ( QList
<TagNode
>::iterator it
= d
->nodes
.begin();
839 it
!= d
->nodes
.end(); ++it
) {
842 if ( paintRegion
.contains( node
.zoomedRect
) ) {
843 p
.setFont( node
.font
);
845 if ( &node
== d
->hoverTag
) {
846 p
.setPen( hoverTextBrush
.brush( this ).color() );
848 else if ( d
->selectionEnabled
&& node
.selected
) {
849 p
.setPen( activeTextBrush
.brush( this ).color() );
852 p
.setPen( normalTextBrush
.brush( this ).color() );
854 p
.drawText( node
.rect
, Qt::AlignCenter
, node
.text
);
858 if ( d
->newTagButtonEnabled
) {
859 p
.setFont( d
->newTagNode
.font
);
860 if ( &d
->newTagNode
== d
->hoverTag
) {
861 p
.setPen( hoverTextBrush
.brush( this ).color() );
864 p
.setPen( normalTextBrush
.brush( this ).color() );
866 p
.drawText( d
->newTagNode
.rect
, Qt::AlignCenter
, d
->customNewTagAction
? d
->customNewTagAction
->text() : d
->newTagNode
.text
);
873 void Nepomuk::TagCloud::mousePressEvent( QMouseEvent
* e
)
875 if ( e
->button() == Qt::LeftButton
) {
876 if ( TagNode
* node
= d
->tagAt( e
->pos() ) ) {
877 kDebug() << "clicked" << node
->text
;
878 if ( node
== &d
->newTagNode
) {
879 if ( d
->customNewTagAction
) {
880 d
->customNewTagAction
->trigger();
884 Tag newTag
= NewTagDialog::createTag( this );
885 if ( newTag
.isValid() ) {
886 emit
tagAdded( newTag
);
891 emit
tagClicked( node
->tag
);
892 if ( d
->selectionEnabled
) {
893 kDebug() << "Toggleing tag" << node
->text
;
894 node
->selected
= !node
->selected
;
895 emit
tagToggled( node
->tag
, node
->selected
);
896 update( node
->zoomedRect
);
904 void Nepomuk::TagCloud::mouseMoveEvent( QMouseEvent
* e
)
906 if ( e
->buttons() == Qt::NoButton
) {
908 TagNode
* oldHoverTag
= d
->hoverTag
;
910 if ( ( d
->hoverTag
= d
->tagAt( e
->pos() ) ) &&
911 !d
->selectionEnabled
) {
912 setCursor( Qt::PointingHandCursor
);
914 else if ( d
->newTagButtonEnabled
&&
915 d
->newTagNode
.zoomedRect
.contains( e
->pos() ) ) {
916 d
->hoverTag
= &d
->newTagNode
;
917 setCursor( Qt::PointingHandCursor
);
923 if ( oldHoverTag
|| d
->hoverTag
) {
926 updateRect
= updateRect
.united( d
->hoverTag
->zoomedRect
);
928 updateRect
= updateRect
.united( oldHoverTag
->zoomedRect
);
930 update( updateRect
);
936 void Nepomuk::TagCloud::leaveEvent( QEvent
* )
940 QRect updateRect
= d
->hoverTag
->zoomedRect
;
942 update( updateRect
);
947 void Nepomuk::TagCloud::slotStatementAdded( const Soprano::Statement
& s
)
949 if ( s
.predicate().uri() == Soprano::Vocabulary::RDF::type() &&
950 s
.object().uri() == Nepomuk::Tag::resourceTypeUri() ) {
952 if ( d
->showAllTags
) {
956 else if ( s
.predicate().uri() == Nepomuk::Resource::tagUri() ) {
957 if ( s
.subject().uri() == d
->resource
) {
958 showResourceTags( d
->resource
);
961 // weights might have changed
962 d
->updateNodeWeights();
969 void Nepomuk::TagCloud::slotStatementRemoved( const Soprano::Statement
& s
)
971 // FIXME: In theory might contain empty nodes as wildcards
973 if ( s
.predicate().uri() == Nepomuk::Resource::tagUri() ) {
974 if ( d
->resource
.isValid() &&
975 d
->resource
== s
.subject().uri() ) {
976 showResourceTags( d
->resource
);
979 // weights might have changed
980 d
->updateNodeWeights();
984 else if ( s
.predicate().uri() == Soprano::Vocabulary::RDF::type() &&
985 s
.object().uri() == Nepomuk::Tag::resourceTypeUri() ) {
987 if ( d
->showAllTags
) {
994 void Nepomuk::TagCloud::setCustomNewTagAction( QAction
* action
)
996 d
->customNewTagAction
= action
;
997 setNewTagButtonEnabled( action
!= 0 );
1000 #include "tagcloud.moc"