diff options
Diffstat (limited to 'src/tagcloud')
| -rw-r--r-- | src/tagcloud/newtagdialog.cpp | 83 | ||||
| -rw-r--r-- | src/tagcloud/newtagdialog.h | 45 | ||||
| -rw-r--r-- | src/tagcloud/newtagdialog.ui | 113 | ||||
| -rw-r--r-- | src/tagcloud/resourcetaggingwidget.cpp | 138 | ||||
| -rw-r--r-- | src/tagcloud/resourcetaggingwidget.h | 60 | ||||
| -rw-r--r-- | src/tagcloud/tagcloud.cpp | 1002 | ||||
| -rw-r--r-- | src/tagcloud/tagcloud.h | 143 | ||||
| -rw-r--r-- | src/tagcloud/taggingpopup.cpp | 148 | ||||
| -rw-r--r-- | src/tagcloud/taggingpopup.h | 50 |
9 files changed, 1782 insertions, 0 deletions
diff --git a/src/tagcloud/newtagdialog.cpp b/src/tagcloud/newtagdialog.cpp new file mode 100644 index 000000000..0fe574fdc --- /dev/null +++ b/src/tagcloud/newtagdialog.cpp @@ -0,0 +1,83 @@ +/* + Copyright (C) 2008 by Sebastian Trueg <trueg at kde.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, or (at your option) + any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "newtagdialog.h" + +#include <nepomuk/tag.h> + +#include <KDebug> +#include <KLocale> +#include <KTitleWidget> + + +NewTagDialog::NewTagDialog( QWidget* parent ) + : KDialog( parent ) +{ + setCaption( i18n( "Create new Tag" ) ); + setButtons( Ok|Cancel ); + enableButtonOk( false ); + + setupUi( mainWidget() ); + + connect( m_editTagLabel, SIGNAL( textChanged(const QString&) ), + this, SLOT( slotLabelChanged(const QString&) ) ); +} + + +NewTagDialog::~NewTagDialog() +{ +} + + +void NewTagDialog::slotLabelChanged( const QString& text ) +{ + enableButtonOk( !text.isEmpty() ); +} + + +Nepomuk::Tag NewTagDialog::createTag( QWidget* parent ) +{ + NewTagDialog dlg( parent ); + dlg.m_labelTitle->setText( i18n( "Create New Tag" ) ); + dlg.m_labelTitle->setComment( i18n( "with optional icon and description" ) ); + dlg.m_labelTitle->setPixmap( KIcon( "nepomuk" ).pixmap( 32, 32 ) ); + + dlg.m_editTagLabel->setFocus(); + + if ( dlg.exec() ) { + QString name = dlg.m_editTagLabel->text(); + QString comment = dlg.m_editTagComment->text(); + QString icon = dlg.m_buttonTagIcon->icon(); + + Nepomuk::Tag newTag( name ); + newTag.setLabel( name ); + newTag.addIdentifier( name ); + if ( !comment.isEmpty() ) { + newTag.setDescription( comment ); + } + if ( !icon.isEmpty() ) { + newTag.addSymbol( icon ); + } + return newTag; + } + else { + return Nepomuk::Tag(); + } +} + +#include "newtagdialog.moc" diff --git a/src/tagcloud/newtagdialog.h b/src/tagcloud/newtagdialog.h new file mode 100644 index 000000000..c3f6ff017 --- /dev/null +++ b/src/tagcloud/newtagdialog.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2008 by Sebastian Trueg <trueg at kde.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, or (at your option) + any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _NEW_TAG_DIALOG_H_ +#define _NEW_TAG_DIALOG_H_ + +#include <KDialog> +#include "ui_newtagdialog.h" + +namespace Nepomuk { + class Tag; +} + +class NewTagDialog : public KDialog, public Ui_NewTagDialog +{ + Q_OBJECT + +public: + ~NewTagDialog(); + + static Nepomuk::Tag createTag( QWidget* parent = 0 ); + +private Q_SLOTS: + void slotLabelChanged( const QString& text ); + +private: + NewTagDialog( QWidget* parent = 0 ); +}; + +#endif diff --git a/src/tagcloud/newtagdialog.ui b/src/tagcloud/newtagdialog.ui new file mode 100644 index 000000000..ab2ea97a6 --- /dev/null +++ b/src/tagcloud/newtagdialog.ui @@ -0,0 +1,113 @@ +<ui version="4.0" > + <class>NewTagDialog</class> + <widget class="QWidget" name="NewTagDialog" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>390</width> + <height>149</height> + </rect> + </property> + <property name="windowTitle" > + <string>Form</string> + </property> + <layout class="QVBoxLayout" > + <item> + <widget class="KTitleWidget" native="1" name="m_labelTitle" /> + </item> + <item> + <layout class="QHBoxLayout" > + <item> + <layout class="QVBoxLayout" > + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>5</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_2" > + <property name="text" > + <string>Name:</string> + </property> + </widget> + </item> + <item> + <widget class="KLineEdit" name="m_editTagLabel" /> + </item> + </layout> + </item> + <item> + <widget class="KIconButton" name="m_buttonTagIcon" > + <property name="sizePolicy" > + <sizepolicy vsizetype="Preferred" hsizetype="Preferred" > + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="icon" > + <iconset/> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>10</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label" > + <property name="text" > + <string>Detailed Desctiption (optional):</string> + </property> + </widget> + </item> + <item> + <widget class="KLineEdit" name="m_editTagComment" /> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>KIconButton</class> + <extends>QPushButton</extends> + <header>kicondialog.h</header> + </customwidget> + <customwidget> + <class>KLineEdit</class> + <extends>QLineEdit</extends> + <header>klineedit.h</header> + </customwidget> + <customwidget> + <class>KTitleWidget</class> + <extends>QWidget</extends> + <header>ktitlewidget.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/src/tagcloud/resourcetaggingwidget.cpp b/src/tagcloud/resourcetaggingwidget.cpp new file mode 100644 index 000000000..e7d6dfa78 --- /dev/null +++ b/src/tagcloud/resourcetaggingwidget.cpp @@ -0,0 +1,138 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2007 Sebastian Trueg <[email protected]> + + 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 "resourcetaggingwidget.h" +#include "tagcloud.h" +#include "taggingpopup.h" + +#include <QtGui/QVBoxLayout> +#include <QtGui/QContextMenuEvent> +#include <QtGui/QCursor> +#include <QtGui/QAction> + +#include <KLocale> + + +class Nepomuk::ResourceTaggingWidget::Private +{ +public: + Nepomuk::Resource resource; + + TagCloud* resourceTagCloud; + TaggingPopup* popup; + + QList<Tag> resourceTags; + + QAction* changeTagsAction; + + void showTaggingPopup( const QPoint& ); + void _k_slotShowTaggingPopup(); +}; + + +void Nepomuk::ResourceTaggingWidget::Private::showTaggingPopup( const QPoint& pos ) +{ + popup->showAllTags(); + resourceTags = resource.tags(); + Q_FOREACH( Tag tag, resourceTags ) { + popup->setTagSelected( tag, true ); + } + + popup->exec( pos ); + + resource.setTags( resourceTags ); +} + + +void Nepomuk::ResourceTaggingWidget::Private::_k_slotShowTaggingPopup() +{ + showTaggingPopup( QCursor::pos() ); +} + + +Nepomuk::ResourceTaggingWidget::ResourceTaggingWidget( QWidget* parent ) + : QWidget( parent ), + d( new Private() ) +{ + QVBoxLayout* layout = new QVBoxLayout( this ); + layout->setMargin( 0 ); + d->resourceTagCloud = new TagCloud( this ); + layout->addWidget( d->resourceTagCloud ); + + d->changeTagsAction = new QAction( i18n( "Change tags..." ), this ); + d->resourceTagCloud->setCustomNewTagAction( d->changeTagsAction ); + + // the popup tag cloud + d->popup = new TaggingPopup; + d->popup->setSelectionEnabled( true ); + d->popup->setNewTagButtonEnabled( true ); + + connect( d->popup, SIGNAL( tagToggled( const Nepomuk::Tag&, bool ) ), + this, SLOT( slotTagToggled( const Nepomuk::Tag&, bool ) ) ); + connect( d->popup, SIGNAL( tagAdded( const Nepomuk::Tag& ) ), + this, SLOT( slotTagAdded( const Nepomuk::Tag& ) ) ); + + connect( d->changeTagsAction, SIGNAL( activated() ), + this, SLOT( _k_slotShowTaggingPopup() ) ); + + connect( d->resourceTagCloud, SIGNAL( tagClicked( const Nepomuk::Tag& ) ), + this, SIGNAL( tagClicked( const Nepomuk::Tag& ) ) ); +} + + +Nepomuk::ResourceTaggingWidget::~ResourceTaggingWidget() +{ + delete d->popup; + delete d; +} + + +void Nepomuk::ResourceTaggingWidget::setResource( const Nepomuk::Resource& res ) +{ + d->resource = res; + d->resourceTagCloud->showResourceTags( res ); +} + + +void Nepomuk::ResourceTaggingWidget::slotTagToggled( const Nepomuk::Tag& tag, bool enabled ) +{ + if ( enabled ) { + d->resourceTags.append( tag ); + } + else { + d->resourceTags.removeAll( tag ); + } + d->popup->hide(); +} + + +void Nepomuk::ResourceTaggingWidget::slotTagAdded( const Nepomuk::Tag& tag ) +{ + // assign it right away + d->resourceTags.append( tag ); + d->resource.addTag( tag ); +} + + +void Nepomuk::ResourceTaggingWidget::contextMenuEvent( QContextMenuEvent* e ) +{ + d->showTaggingPopup( e->globalPos() ); +} + +#include "resourcetaggingwidget.moc" diff --git a/src/tagcloud/resourcetaggingwidget.h b/src/tagcloud/resourcetaggingwidget.h new file mode 100644 index 000000000..a75746348 --- /dev/null +++ b/src/tagcloud/resourcetaggingwidget.h @@ -0,0 +1,60 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2007 Sebastian Trueg <[email protected]> + + 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. + */ + +#ifndef _NEPOMUK_RESOURCE_TAGGING_WIDGET_H_ +#define _NEPOMUK_RESOURCE_TAGGING_WIDGET_H_ + +#include <QtGui/QWidget> + +#include <nepomuk/tag.h> + +class QEvent; +class QContextMenuEvent; + +namespace Nepomuk { + class ResourceTaggingWidget : public QWidget + { + Q_OBJECT + + public: + ResourceTaggingWidget( QWidget* parent = 0 ); + ~ResourceTaggingWidget(); + + Q_SIGNALS: + void tagClicked( const Nepomuk::Tag& tag ); + + public Q_SLOTS: + void setResource( const Nepomuk::Resource& ); + + private Q_SLOTS: + void slotTagToggled( const Nepomuk::Tag& tag, bool enabled ); + void slotTagAdded( const Nepomuk::Tag& tag ); + + protected: + void contextMenuEvent( QContextMenuEvent* e ); + + private: + class Private; + Private* const d; + + Q_PRIVATE_SLOT( d, void _k_slotShowTaggingPopup() ) + }; +} + +#endif diff --git a/src/tagcloud/tagcloud.cpp b/src/tagcloud/tagcloud.cpp new file mode 100644 index 000000000..8fe5cba89 --- /dev/null +++ b/src/tagcloud/tagcloud.cpp @@ -0,0 +1,1002 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2007 Sebastian Trueg <[email protected]> + + 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 <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 ) { + } + + // 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 ), + showAllTags( false ), + customNewTagAction( 0 ), + hoverTag( 0 ), + cachedHfwWidth( -1 ), + m_parent( parent ) { + newTagNode.text = i18n( "New Tag..." ); + } + + int maxFontSize; + int minFontSize; + int maxNumberDisplayedTags; + bool selectionEnabled; + bool newTagButtonEnabled; + Qt::Alignment alignment; + Sorting sorting; + bool zoomEnabled; + + // The resource whose tags we are showing + // invalid if we show all tags or a selection + QUrl 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( tag.resourceUri().toString() ), + 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() +{ + if ( nodes.isEmpty() && !newTagButtonEnabled ) { + return; + } + + // - 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 ( 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 = row.count() * pos.x() / m_parent->width(); + 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 = rows.count() * pos.y() / m_parent->height(); + + 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 ) +{ +} + + +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.uri(); +} + + +void Nepomuk::TagCloud::showTags( const QList<Tag>& tags ) +{ + d->resource = QUrl(); + d->showAllTags = false; + d->invalidateCachedValues(); + d->nodes.clear(); + Q_FOREACH( 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 +{ + // 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( i18n( "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; + 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(); + } + } + if ( rowH > 0 ) { + h += s_vSpacing + rowH; + } + + d->cachedHfwWidth = contentsWidth; + d->cachedHfwHeight = h; + } + + 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(); + + if ( d->nodes.isEmpty() && !d->newTagButtonEnabled ) { + p.drawText( contentsRect(), d->alignment, i18n( "No Tags" ) ); + } + else { + 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 ); + } + } + } + } +} + + +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" diff --git a/src/tagcloud/tagcloud.h b/src/tagcloud/tagcloud.h new file mode 100644 index 000000000..2c641e7fb --- /dev/null +++ b/src/tagcloud/tagcloud.h @@ -0,0 +1,143 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2007 Sebastian Trueg <[email protected]> + + 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. + */ + +#ifndef _NEPOMUK_TAG_CLOUD_H_ +#define _NEPOMUK_TAG_CLOUD_H_ + +#include <QtGui/QFrame> +#include <QtCore/QList> + +#include <nepomuk/tag.h> +#include <nepomuk/nepomuk_export.h> + +#include <Soprano/Statement> + +class QResizeEvent; +class QPaintEvent; +class QMouseEvent; +class QEvent; + +namespace Nepomuk { + class NEPOMUK_EXPORT TagCloud : public QFrame + { + Q_OBJECT + + public: + TagCloud( QWidget* parent = 0 ); + ~TagCloud(); + + enum Sorting { + SortAlpabetically, + SortByWeight, + SortRandom + }; + + int heightForWidth( int w ) const; + QSize sizeHint() const; + QSize minimumSizeHint() const; + + bool zoomEnabled() const; + + public Q_SLOTS: + /** + * Set the maximum used font size. The default is 0 + * which means to calculate proper values from the KDE + * defaults. + */ + void setMaxFontSize( int size ); + + /** + * Set the minimum used font size. The default is 0 + * which means to calculate proper values from the KDE + * defaults. + */ + void setMinFontSize( int size ); + + /** + * Set the maximum number of displayed tags. The default is 0 + * which means to display all tags. + * + * NOT IMPLEMENTED YET + */ + void setMaxNumberDisplayedTags( int n ); + + /** + * Allow selection of tags, i.e. enabling and disabling of tags. + */ + void setSelectionEnabled( bool enabled ); + + void setNewTagButtonEnabled( bool enabled ); + void setContextMenuEnabled( bool enabled ); + void setAlignment( Qt::Alignment alignment ); + + void setZoomEnabled( bool zoom ); + + /** + * Default: SortAlpabetically + */ + void setSorting( Sorting ); + + /** + * Will reset tags set via showTags() + */ + void showAllTags(); + + /** + * Set the tags to be shown in the tag cloud. + * If the new tag button is enabled (setEnableNewTagButton()) + * new tags will automatically be added to the list of shown tags. + */ + void showTags( const QList<Tag>& tags ); + + void showResourceTags( const Resource& resource ); + + /** + * Select or deselect a tag. This does only make sense + * if selection is enabled and \p tag is actually + * displayed. + * + * \sa setSelectionEnabled + */ + void setTagSelected( const Tag& tag, bool selected ); + + void setCustomNewTagAction( QAction* action ); + + Q_SIGNALS: + void tagClicked( const Nepomuk::Tag& tag ); + void tagAdded( const Nepomuk::Tag& tag ); + void tagToggled( const Nepomuk::Tag& tag, bool enabled ); + + protected: + void resizeEvent( QResizeEvent* e ); + void paintEvent( QPaintEvent* e ); + void mousePressEvent( QMouseEvent* ); + void mouseMoveEvent( QMouseEvent* ); + void leaveEvent( QEvent* ); + + private Q_SLOTS: + void slotStatementAdded( const Soprano::Statement& s ); + void slotStatementRemoved( const Soprano::Statement& s ); + + private: + class Private; + Private* const d; + }; +} + +#endif diff --git a/src/tagcloud/taggingpopup.cpp b/src/tagcloud/taggingpopup.cpp new file mode 100644 index 000000000..a1024254d --- /dev/null +++ b/src/tagcloud/taggingpopup.cpp @@ -0,0 +1,148 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2007 Sebastian Trueg <[email protected]> + + 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 "taggingpopup.h" + +#include <QtCore/QEventLoop> +#include <QtCore/QPointer> +#include <QtGui/QApplication> +#include <QtGui/QDesktopWidget> +#include <QtGui/QMouseEvent> + +#include <KDebug> +#include <KDialog> + + +class Nepomuk::TaggingPopup::Private +{ +public: + Private( TaggingPopup* parent ) + : eventLoop( 0 ), + m_parent( parent ) { + } + + QEventLoop* eventLoop; + QPoint popupPos; + + QRect geometryForPopupPos( const QPoint& p ) { + QSize size = m_parent->sizeHint(); + + // we want a little margin + const int margin = KDialog::marginHint(); + size.setHeight( size.height() + margin*2 ); + size.setWidth( size.width() + margin*2 ); + + QRect screen = QApplication::desktop()->screenGeometry( QApplication::desktop()->screenNumber( p ) ); + + // calculate popup position + QPoint pos( p.x() - size.width()/2, p.y() - size.height()/2 ); + + // ensure we do not leave the desktop + if ( pos.x() + size.width() > screen.right() ) { + pos.setX( screen.right() - size.width() ); + } + else if ( pos.x() < screen.left() ) { + pos.setX( screen.left() ); + } + + if ( pos.y() + size.height() > screen.bottom() ) { + pos.setY( screen.bottom() - size.height() ); + } + else if ( pos.y() < screen.top() ) { + pos.setY( screen.top() ); + } + + return QRect( pos, size ); + } + +private: + TaggingPopup* m_parent; +}; + + +Nepomuk::TaggingPopup::TaggingPopup( QWidget* parent ) + : TagCloud( parent ), + d( new Private( this ) ) +{ + setFrameStyle( QFrame::Box|QFrame::Plain ); + setWindowFlags( Qt::Popup ); +} + + +Nepomuk::TaggingPopup::~TaggingPopup() +{ + delete d; +} + + +void Nepomuk::TaggingPopup::popup( const QPoint& p ) +{ + setGeometry( d->geometryForPopupPos( p ) ); + d->popupPos = p; + + show(); +} + + +void Nepomuk::TaggingPopup::exec( const QPoint& pos ) +{ + QEventLoop eventLoop; + d->eventLoop = &eventLoop; + popup( pos ); + + QPointer<QObject> guard = this; + (void) eventLoop.exec(); + if ( !guard.isNull() ) + d->eventLoop = 0; +} + + +void Nepomuk::TaggingPopup::mousePressEvent( QMouseEvent* e ) +{ + if ( !rect().contains( e->pos() ) ) { + hide(); + } + else { + TagCloud::mousePressEvent( e ); + } +} + + +void Nepomuk::TaggingPopup::hideEvent( QHideEvent* e ) +{ + Q_UNUSED( e ); + if ( d->eventLoop ) { + d->eventLoop->exit(); + } +} + + +bool Nepomuk::TaggingPopup::event( QEvent* e ) +{ + if ( e->type() == QEvent::LayoutRequest ) { + if ( isVisible() ) { + setGeometry( d->geometryForPopupPos( d->popupPos ) ); + return true; + } + } + + return TagCloud::event( e ); +} + +#include "taggingpopup.moc" diff --git a/src/tagcloud/taggingpopup.h b/src/tagcloud/taggingpopup.h new file mode 100644 index 000000000..99cee701c --- /dev/null +++ b/src/tagcloud/taggingpopup.h @@ -0,0 +1,50 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2007 Sebastian Trueg <[email protected]> + + 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. + */ + +#ifndef _NEPOMUK_TAGGING_POPUP_H_ +#define _NEPOMUK_TAGGING_POPUP_H_ + +#include "tagcloud.h" + +class QMouseEvent; +class QHideEvent; + +namespace Nepomuk { + class TaggingPopup : public TagCloud + { + public: + TaggingPopup( QWidget* parent = 0 ); + ~TaggingPopup(); + + void popup( const QPoint& pos ); + void exec( const QPoint& pos ); + + bool event( QEvent* e ); + + protected: + void mousePressEvent( QMouseEvent* e ); + void hideEvent( QHideEvent* e ); + + private: + class Private; + Private* const d; + }; +} + +#endif |
