┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt17
-rw-r--r--src/commenteditwidget.cpp241
-rw-r--r--src/commenteditwidget.h62
-rw-r--r--src/commentwidget.cpp114
-rw-r--r--src/commentwidget.h48
-rw-r--r--src/dolphincategorydrawer.cpp16
-rw-r--r--src/metadatawidget.cpp135
-rw-r--r--src/metadatawidget.h7
-rw-r--r--src/nepomukmassupdatejob.cpp163
-rw-r--r--src/nepomukmassupdatejob.h85
-rw-r--r--src/ratingpainter.cpp310
-rw-r--r--src/ratingpainter.h122
-rw-r--r--src/tagcloud/newtagdialog.cpp83
-rw-r--r--src/tagcloud/newtagdialog.h45
-rw-r--r--src/tagcloud/newtagdialog.ui113
-rw-r--r--src/tagcloud/resourcetaggingwidget.cpp138
-rw-r--r--src/tagcloud/resourcetaggingwidget.h60
-rw-r--r--src/tagcloud/tagcloud.cpp1002
-rw-r--r--src/tagcloud/tagcloud.h143
-rw-r--r--src/tagcloud/taggingpopup.cpp148
-rw-r--r--src/tagcloud/taggingpopup.h50
21 files changed, 2583 insertions, 519 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6af8fc9f3..0354f925d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -5,6 +5,7 @@ add_subdirectory( tests )
macro_optional_find_package(Soprano)
include_directories( ${KDE4_INCLUDE_DIR} ${QT_INCLUDES} ${BLITZ_INCLUDES} )
+
if (Soprano_FOUND)
include_directories( ${SOPRANO_INCLUDE_DIR} )
endif (Soprano_FOUND)
@@ -27,7 +28,6 @@ set(dolphinprivate_LIB_SRCS
dolphinview.cpp
dolphinviewactionhandler.cpp
iconmanager.cpp
- ratingpainter.cpp
renamedialog.cpp
selectiontoggle.cpp
selectionmanager.cpp
@@ -103,6 +103,8 @@ set(dolphin_SRCS
infosidebarpage.cpp
main.cpp
metadatawidget.cpp
+ commentwidget.cpp
+ commenteditwidget.cpp
metatextlabel.cpp
pixmapviewer.cpp
settingspagebase.cpp
@@ -117,6 +119,19 @@ set(dolphin_SRCS
viewsettingspage.cpp
viewpropsprogressinfo.cpp )
+if(Nepomuk_FOUND)
+set(dolphin_SRCS
+ ${dolphin_SRCS}
+ nepomukmassupdatejob.cpp
+ tagcloud/tagcloud.cpp
+ tagcloud/resourcetaggingwidget.cpp
+ tagcloud/taggingpopup.cpp
+ tagcloud/newtagdialog.cpp
+)
+kde4_add_ui_files(dolphin_SRCS tagcloud/newtagdialog.ui)
+
+endif(Nepomuk_FOUND)
+
if(NOT WIN32)
set(dolphin_SRCS ${dolphin_SRCS} terminalsidebarpage.cpp)
endif(NOT WIN32)
diff --git a/src/commenteditwidget.cpp b/src/commenteditwidget.cpp
new file mode 100644
index 000000000..3b98d44d7
--- /dev/null
+++ b/src/commenteditwidget.cpp
@@ -0,0 +1,241 @@
+/***************************************************************************
+ * Copyright (C) 2008 by Sebastian Trueg <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * 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 "commenteditwidget.h"
+
+#include <QtGui/QTextEdit>
+#include <QtGui/QToolButton>
+#include <QtGui/QVBoxLayout>
+#include <QtCore/QEventLoop>
+#include <QtCore/QPointer>
+#include <QtGui/QApplication>
+#include <QtGui/QDesktopWidget>
+#include <QtGui/QMouseEvent>
+#include <QtGui/QFont>
+
+#include <KIcon>
+#include <KDialog>
+#include <KLocale>
+#include <KDebug>
+
+
+class CommentEditWidget::Private
+{
+public:
+ Private( CommentEditWidget* parent )
+ : eventLoop( 0 ),
+ q( parent ) {
+ }
+
+ QEventLoop* eventLoop;
+ bool success;
+ QTextEdit* textEdit;
+ QToolButton* buttonSave;
+ QToolButton* buttonCancel;
+
+ QString comment;
+
+ QRect geometryForPopupPos( const QPoint& p ) {
+ QSize size = q->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 );
+ }
+
+ void _k_saveClicked();
+ void _k_cancelClicked();
+
+private:
+ CommentEditWidget* q;
+};
+
+
+void CommentEditWidget::Private::_k_saveClicked()
+{
+ comment = textEdit->toPlainText();
+ success = true;
+ q->hide();
+}
+
+
+void CommentEditWidget::Private::_k_cancelClicked()
+{
+ success = false;
+ q->hide();
+}
+
+
+CommentEditWidget::CommentEditWidget( QWidget* parent )
+ : QFrame( parent ),
+ d( new Private( this ) )
+{
+ setFrameStyle( QFrame::Box|QFrame::Plain );
+ setWindowFlags( Qt::Popup );
+
+ d->textEdit = new QTextEdit( this );
+ d->textEdit->installEventFilter( this );
+ QVBoxLayout* layout = new QVBoxLayout( this );
+ layout->setMargin( 0 );
+ layout->addWidget( d->textEdit );
+
+ d->buttonSave = new QToolButton( d->textEdit );
+ d->buttonCancel = new QToolButton( d->textEdit );
+ d->buttonSave->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
+ d->buttonCancel->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
+ d->buttonSave->setAutoRaise( true );
+ d->buttonCancel->setAutoRaise( true );
+ d->buttonSave->setIcon( KIcon( "document-save" ) );
+ d->buttonCancel->setIcon( KIcon( "edit-delete" ) );
+ d->buttonSave->setText( i18n( "Save" ) );
+ d->buttonCancel->setText( i18n( "Cancel" ) );
+
+ QFont fnt( font() );
+ fnt.setPointSize( fnt.pointSize()-2 );
+ d->buttonSave->setFont( fnt );
+ d->buttonCancel->setFont( fnt );
+
+ connect( d->buttonSave, SIGNAL(clicked()),
+ this, SLOT( _k_saveClicked() ) );
+ connect( d->buttonCancel, SIGNAL(clicked()),
+ this, SLOT( _k_cancelClicked() ) );
+}
+
+
+CommentEditWidget::~CommentEditWidget()
+{
+ delete d;
+}
+
+
+void CommentEditWidget::setComment( const QString& s )
+{
+ d->comment = s;
+}
+
+
+QString CommentEditWidget::comment()
+{
+ return d->comment;
+}
+
+
+bool CommentEditWidget::exec( const QPoint& pos )
+{
+ d->success = false;
+ d->textEdit->setText( d->comment );
+ d->textEdit->setFocus();
+ d->textEdit->moveCursor( QTextCursor::End );
+ QEventLoop eventLoop;
+ d->eventLoop = &eventLoop;
+ setGeometry( d->geometryForPopupPos( pos ) );
+ show();
+
+ QPointer<QObject> guard = this;
+ (void) eventLoop.exec();
+ if ( !guard.isNull() )
+ d->eventLoop = 0;
+ return d->success;
+}
+
+
+void CommentEditWidget::mousePressEvent( QMouseEvent* e )
+{
+ // clicking outside of the widget means cancel
+ if ( !rect().contains( e->pos() ) ) {
+ d->success = false;
+ hide();
+ }
+ else {
+ QWidget::mousePressEvent( e );
+ }
+}
+
+
+void CommentEditWidget::hideEvent( QHideEvent* e )
+{
+ Q_UNUSED( e );
+ if ( d->eventLoop ) {
+ d->eventLoop->exit();
+ }
+}
+
+
+void CommentEditWidget::updateButtons()
+{
+ QSize sbs = d->buttonSave->sizeHint();
+ QSize cbs = d->buttonCancel->sizeHint();
+
+ // FIXME: button order
+ d->buttonCancel->setGeometry( QRect( QPoint( d->textEdit->width() - cbs.width() - frameWidth(),
+ d->textEdit->height() - cbs.height() - frameWidth() ),
+ cbs ) );
+ d->buttonSave->setGeometry( QRect( QPoint( d->textEdit->width() - cbs.width() - sbs.width() - frameWidth(),
+ d->textEdit->height() - sbs.height() - frameWidth() ),
+ sbs ) );
+}
+
+
+void CommentEditWidget::resizeEvent( QResizeEvent* e )
+{
+ QWidget::resizeEvent( e );
+ updateButtons();
+}
+
+
+bool CommentEditWidget::eventFilter( QObject* watched, QEvent* event )
+{
+ if ( watched == d->textEdit && event->type() == QEvent::KeyPress ) {
+ QKeyEvent* ke = static_cast<QKeyEvent*>( event );
+ kDebug() << "keypress:" << ke->key() << ke->modifiers();
+ if ( ( ke->key() == Qt::Key_Enter ||
+ ke->key() == Qt::Key_Return ) &&
+ ke->modifiers() & Qt::ControlModifier ) {
+ d->_k_saveClicked();
+ return true;
+ }
+ }
+
+ return QFrame::eventFilter( watched, event );
+}
+
+#include "commenteditwidget.moc"
diff --git a/src/commenteditwidget.h b/src/commenteditwidget.h
new file mode 100644
index 000000000..18ab8d7b2
--- /dev/null
+++ b/src/commenteditwidget.h
@@ -0,0 +1,62 @@
+/***************************************************************************
+ * Copyright (C) 2008 by Sebastian Trueg <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * 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 _COMMENT_EDIT_WIDGET_H_
+#define _COMMENT_EDIT_WIDGET_H_
+
+#include <QtGui/QFrame>
+
+class QResizeEvent;
+class QMouseEvent;
+class QHideEvent;
+
+class CommentEditWidget : public QFrame
+{
+ Q_OBJECT
+
+public:
+ CommentEditWidget( QWidget* parent = 0 );
+ ~CommentEditWidget();
+
+ void setComment( const QString& s );
+ QString comment();
+
+ /**
+ * Show the comment widget at position pos.
+ * \return true if the user chose to save the comment,
+ * false otherwise.
+ */
+ bool exec( const QPoint& pos );
+
+ bool eventFilter( QObject* watched, QEvent* event );
+
+private:
+ void updateButtons();
+ void resizeEvent( QResizeEvent* );
+ void mousePressEvent( QMouseEvent* e );
+ void hideEvent( QHideEvent* e );
+
+ class Private;
+ Private* const d;
+
+ Q_PRIVATE_SLOT( d, void _k_saveClicked() )
+ Q_PRIVATE_SLOT( d, void _k_cancelClicked() )
+};
+
+#endif
diff --git a/src/commentwidget.cpp b/src/commentwidget.cpp
new file mode 100644
index 000000000..eebf12ad6
--- /dev/null
+++ b/src/commentwidget.cpp
@@ -0,0 +1,114 @@
+/***************************************************************************
+ * Copyright (C) 2008 by Sebastian Trueg <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * 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 "commentwidget.h"
+#include "commenteditwidget.h"
+
+#include <QtGui/QLabel>
+#include <QtGui/QTextEdit>
+#include <QtGui/QLayout>
+#include <QtGui/QCursor>
+#include <QtCore/QEvent>
+
+#include <KLocale>
+
+
+class CommentWidget::Private
+{
+public:
+ Private( CommentWidget* parent )
+ : q( parent ) {
+ }
+
+ void update();
+ void _k_slotEnableEditing();
+
+ QLabel* label;
+ CommentEditWidget* edit;
+
+ QString comment;
+
+private:
+ CommentWidget* q;
+};
+
+
+void CommentWidget::Private::update()
+{
+ if ( comment.isEmpty() ) {
+ label->setText( "<p align=center><a style=\"font-size:small;\" href=\"addComment\">" + i18n( "Click to add comment..." ) + "</a>" );
+ }
+ else {
+ label->setText( "<p>" + comment + "<p align=right><a style=\"font-size:small;\" href=\"addComment\">" + i18n( "Change comment..." ) + "</a>" );
+ }
+}
+
+
+void CommentWidget::Private::_k_slotEnableEditing()
+{
+ CommentEditWidget w;
+ w.setComment( comment );
+ if ( w.exec( QCursor::pos() ) ) {
+ comment = w.comment();
+ update();
+ emit q->commentChanged( comment );
+ }
+}
+
+
+
+CommentWidget::CommentWidget( QWidget* parent )
+ : QWidget( parent ),
+ d( new Private( this ) )
+{
+ d->label = new QLabel( this );
+ d->label->setWordWrap( true );
+ QVBoxLayout* layout = new QVBoxLayout( this );
+ layout->setMargin( 0 );
+ layout->addWidget( d->label );
+ d->update();
+ connect( d->label, SIGNAL( linkActivated( const QString& ) ), this, SLOT( _k_slotEnableEditing() ) );
+}
+
+
+CommentWidget::~CommentWidget()
+{
+ delete d;
+}
+
+
+void CommentWidget::setComment( const QString& comment )
+{
+ d->comment = comment;
+ d->update();
+}
+
+
+QString CommentWidget::comment() const
+{
+ return d->comment;
+}
+
+
+bool CommentWidget::eventFilter( QObject* watched, QEvent* event )
+{
+ return QWidget::eventFilter( watched, event );
+}
+
+#include "commentwidget.moc"
diff --git a/src/commentwidget.h b/src/commentwidget.h
new file mode 100644
index 000000000..8c588518c
--- /dev/null
+++ b/src/commentwidget.h
@@ -0,0 +1,48 @@
+/***************************************************************************
+ * Copyright (C) 2008 by Sebastian Trueg <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * 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 _NEPOMUK_COMMENT_WIDGET_H_
+#define _NEPOMUK_COMMENT_WIDGET_H_
+
+#include <QtGui/QWidget>
+
+class CommentWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ CommentWidget( QWidget* parent = 0 );
+ ~CommentWidget();
+
+ void setComment( const QString& comment );
+ QString comment() const;
+
+Q_SIGNALS:
+ void commentChanged( const QString& );
+
+private:
+ bool eventFilter( QObject* watched, QEvent* event );
+
+ class Private;
+ Private* const d;
+
+ Q_PRIVATE_SLOT( d, void _k_slotEnableEditing() )
+};
+
+#endif
diff --git a/src/dolphincategorydrawer.cpp b/src/dolphincategorydrawer.cpp
index 6cf84560f..b96387646 100644
--- a/src/dolphincategorydrawer.cpp
+++ b/src/dolphincategorydrawer.cpp
@@ -18,24 +18,22 @@
* Boston, MA 02110-1301, USA.
*/
+#include "config-nepomuk.h"
+
#include "dolphincategorydrawer.h"
-#include "ratingpainter.h"
#include <QPainter>
#include <QFile>
#include <QDir>
+#ifdef HAVE_NEPOMUK
+#include <nepomuk/kratingpainter.h>
+#endif
+
#include <kiconloader.h>
#include <kcategorizedsortfilterproxymodel.h>
#include <qimageblitz.h>
#include <kuser.h>
-#include <config-nepomuk.h>
-#ifdef HAVE_NEPOMUK
-#include <nepomuk/global.h>
-#include <nepomuk/resource.h>
-#include <nepomuk/tag.h>
-#endif
-
#include "dolphinview.h"
#include "dolphinmodel.h"
@@ -217,7 +215,7 @@ void DolphinCategoryDrawer::drawCategory(const QModelIndex &index, int sortRole,
QRect ratingRect( option.rect );
ratingRect.setTop(option.rect.top() + (option.rect.height() / 2) - (iconSize / 2));
ratingRect.setHeight( iconSize );
- Nepomuk::RatingPainter::drawRating( painter, ratingRect, Qt::AlignLeft, category.toInt() );
+ KRatingPainter::paintRating( painter, ratingRect, Qt::AlignLeft, category.toInt() );
break;
}
diff --git a/src/metadatawidget.cpp b/src/metadatawidget.cpp
index a8086d8a5..922b1b495 100644
--- a/src/metadatawidget.cpp
+++ b/src/metadatawidget.cpp
@@ -19,9 +19,13 @@
#include "metadatawidget.h"
+#include "commentwidget.h"
+
#include <config-nepomuk.h>
#include <klocale.h>
+#include <KDebug>
+#include <KMessageBox>
#include <QtCore/QEvent>
#include <QtGui/QLabel>
@@ -29,13 +33,14 @@
#include <QtGui/QTextEdit>
#ifdef HAVE_NEPOMUK
+#include "nepomukmassupdatejob.h"
#include <nepomuk/kmetadatatagwidget.h>
#include <nepomuk/resourcemanager.h>
#include <nepomuk/resource.h>
#include <nepomuk/variant.h>
#include <nepomuk/kratingwidget.h>
-#include <nepomuk/global.h>
#include <Soprano/Vocabulary/Xesam>
+#include "tagcloud/resourcetaggingwidget.h"
#endif
@@ -57,24 +62,16 @@ public:
QMap<KUrl, Nepomuk::Resource> files;
- QTextEdit* editComment;
+ CommentWidget* editComment;
KRatingWidget* ratingWidget;
- Nepomuk::TagWidget* tagWidget;
+ Nepomuk::ResourceTaggingWidget* tagWidget;
#endif
};
#ifdef HAVE_NEPOMUK
void MetaDataWidget::Private::loadComment(const QString& comment)
{
- editComment->blockSignals(true);
- if (comment.isEmpty()) {
- editComment->setFontItalic(true);
- editComment->setText(i18nc("@info:tooltip", "Click to add comment..."));
- } else {
- editComment->setFontItalic(false);
- editComment->setText(comment);
- }
- editComment->blockSignals(false);
+ editComment->setComment( comment );
}
#endif
@@ -84,28 +81,21 @@ MetaDataWidget::MetaDataWidget(QWidget* parent) :
{
#ifdef HAVE_NEPOMUK
d = new Private;
- d->editComment = new QTextEdit(this);
+ d->editComment = new CommentWidget(this);
d->editComment->setFocusPolicy(Qt::ClickFocus);
d->ratingWidget = new KRatingWidget(this);
- d->tagWidget = new Nepomuk::TagWidget(this);
+ d->ratingWidget->setAlignment( Qt::AlignCenter );
+ d->tagWidget = new Nepomuk::ResourceTaggingWidget(this);
connect(d->ratingWidget, SIGNAL(ratingChanged(unsigned int)), this, SLOT(slotRatingChanged(unsigned int)));
- connect(d->editComment, SIGNAL(textChanged()), this, SLOT(slotCommentChanged()));
+ connect(d->editComment, SIGNAL(commentChanged(const QString&)), this, SLOT(slotCommentChanged(const QString&)));
+ connect( d->tagWidget, SIGNAL( tagClicked( const Nepomuk::Tag& ) ), this, SLOT( slotTagClicked( const Nepomuk::Tag& ) ) );
QVBoxLayout* lay = new QVBoxLayout(this);
lay->setMargin(0);
- QHBoxLayout* hbox = new QHBoxLayout;
- hbox->addWidget(new QLabel(i18nc("@label:slider", "Rating:"), this));
- hbox->addStretch(1);
- hbox->addWidget(d->ratingWidget);
- lay->addLayout(hbox);
+ lay->addWidget(d->ratingWidget);
lay->addWidget(d->editComment);
- hbox = new QHBoxLayout;
- hbox->addWidget(new QLabel(i18nc("@label:textbox", "Tags:"), this));
- hbox->addWidget(d->tagWidget, 1);
- lay->addLayout(hbox);
-
- d->editComment->installEventFilter(this);
- d->editComment->viewport()->installEventFilter(this);
+ QHBoxLayout* hbox = new QHBoxLayout;
+ lay->addWidget( d->tagWidget );
#else
d = 0;
#endif
@@ -120,6 +110,7 @@ MetaDataWidget::~MetaDataWidget()
void MetaDataWidget::setFile(const KUrl& url)
{
+ kDebug() << url;
KUrl::List urls;
urls.append( url );
setFiles( urls );
@@ -129,52 +120,44 @@ void MetaDataWidget::setFile(const KUrl& url)
void MetaDataWidget::setFiles(const KUrl::List& urls)
{
#ifdef HAVE_NEPOMUK
- // FIXME #1: For 100 files MetaDataWidget::setFiles() blocks
- // for around 15 seconds (maybe we should delegate this to a KJob).
- // In the meantime we temporary just reset the widgets:
- d->ratingWidget->setRating( 0 );
- d->loadComment( QString() );
- return;
-
- // FIXME #2: replace with KMetaData::File once we have it again
d->files.clear();
bool first = true;
QList<Nepomuk::Resource> fileRes;
Q_FOREACH( KUrl url, urls ) {
- Nepomuk::Resource file( url.url(), Soprano::Vocabulary::Xesam::File() );
-// file.setLocation(url.url());
+ Nepomuk::Resource file( url, Soprano::Vocabulary::Xesam::File() );
d->files.insert( url, file );
fileRes.append( file );
- if ( !first &&
- d->ratingWidget->rating() != file.rating() ) {
- d->ratingWidget->setRating( 0 ); // reset rating
- }
- else if ( first ) {
- d->ratingWidget->setRating( (qint32)(file.rating()) );
- }
+ if ( !first &&
+ d->ratingWidget->rating() != file.rating() ) {
+ d->ratingWidget->setRating( 0 ); // reset rating
+ }
+ else if ( first ) {
+ d->ratingWidget->setRating( (qint32)(file.rating()) );
+ }
- if ( !first &&
- d->editComment->toPlainText() != file.description() ) {
- d->loadComment( QString() );
- }
- else if ( first ) {
- d->loadComment( file.description() );
- }
- first = false;
+ if ( !first &&
+ d->editComment->comment() != file.description() ) {
+ d->loadComment( QString() );
+ }
+ else if ( first ) {
+ d->loadComment( file.description() );
+ }
+ first = false;
}
- d->tagWidget->setTaggedResources(fileRes);
+ d->tagWidget->setResource( fileRes.first() );
#endif
}
-void MetaDataWidget::slotCommentChanged()
+void MetaDataWidget::slotCommentChanged( const QString& s )
{
#ifdef HAVE_NEPOMUK
- for ( QMap<KUrl, Nepomuk::Resource>::iterator it = d->files.begin();
- it != d->files.end(); ++it ) {
- it.value().setDescription(d->editComment->toPlainText());
- }
+ Nepomuk::MassUpdateJob* job = Nepomuk::MassUpdateJob::commentResources( d->files.values(), s );
+ connect( job, SIGNAL( result( KJob* ) ),
+ this, SLOT( metadataUpdateDone() ) );
+ setEnabled( false ); // no updates during execution
+ job->start();
#endif
}
@@ -182,31 +165,31 @@ void MetaDataWidget::slotCommentChanged()
void MetaDataWidget::slotRatingChanged(unsigned int rating)
{
#ifdef HAVE_NEPOMUK
- for ( QMap<KUrl, Nepomuk::Resource>::iterator it = d->files.begin();
- it != d->files.end(); ++it ) {
- it.value().setRating(rating);
- }
+ Nepomuk::MassUpdateJob* job = Nepomuk::MassUpdateJob::rateResources( d->files.values(), rating );
+ connect( job, SIGNAL( result( KJob* ) ),
+ this, SLOT( metadataUpdateDone() ) );
+ setEnabled( false ); // no updates during execution
+ job->start();
#endif
}
-bool MetaDataWidget::eventFilter(QObject* obj, QEvent* event)
+void MetaDataWidget::metadataUpdateDone()
{
-#ifdef HAVE_NEPOMUK
- if (obj == d->editComment->viewport() || obj == d->editComment) {
- if (event->type() == QEvent::FocusOut) {
- // make sure the info text is displayed again
- d->loadComment(d->editComment->toPlainText());
- } else if (event->type() == QEvent::FocusIn) {
- d->editComment->setFontItalic(false);
- if (!d->files.isEmpty() && d->files.begin().value().description().isEmpty()) {
- d->editComment->setText(QString());
- }
- }
- }
-#endif
+ setEnabled( true );
+}
+
+bool MetaDataWidget::eventFilter(QObject* obj, QEvent* event)
+{
return QWidget::eventFilter(obj, event);
}
+
+void MetaDataWidget::slotTagClicked( const Nepomuk::Tag& tag )
+{
+ // FIXME
+ KMessageBox::information( this, "FIXME: connect me to the dolphinmodel: tags:/" + tag.genericLabel() );
+}
+
#include "metadatawidget.moc"
diff --git a/src/metadatawidget.h b/src/metadatawidget.h
index b9b8787d0..06411cd7a 100644
--- a/src/metadatawidget.h
+++ b/src/metadatawidget.h
@@ -24,6 +24,9 @@
#include <kurl.h>
+namespace Nepomuk {
+ class Tag;
+}
class MetaDataWidget : public QWidget
{
@@ -52,8 +55,10 @@ signals:
void metaDataChanged();
private Q_SLOTS:
- void slotCommentChanged();
+ void slotCommentChanged(const QString&);
void slotRatingChanged(unsigned int rating);
+ void metadataUpdateDone();
+ void slotTagClicked( const Nepomuk::Tag& );
protected:
bool eventFilter(QObject* obj, QEvent* event);
diff --git a/src/nepomukmassupdatejob.cpp b/src/nepomukmassupdatejob.cpp
new file mode 100644
index 000000000..484846916
--- /dev/null
+++ b/src/nepomukmassupdatejob.cpp
@@ -0,0 +1,163 @@
+/***************************************************************************
+ * Copyright (C) 2008 by Sebastian Trueg <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * 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 "nepomukmassupdatejob.h"
+
+#include <klocale.h>
+#include <kdebug.h>
+
+#include <nepomuk/tag.h>
+#include <nepomuk/tools.h>
+
+
+Nepomuk::MassUpdateJob::MassUpdateJob( QObject* parent )
+ : KJob( parent ),
+ m_index( -1 )
+{
+ kDebug();
+ setCapabilities( Killable|Suspendable );
+ connect( &m_processTimer, SIGNAL( timeout() ),
+ this, SLOT( slotNext() ) );
+}
+
+
+Nepomuk::MassUpdateJob::~MassUpdateJob()
+{
+ kDebug();
+}
+
+
+void Nepomuk::MassUpdateJob::setFiles( const KUrl::List& urls )
+{
+ m_resources.clear();
+ foreach( KUrl url, urls ) {
+ m_resources.append( Resource( url ) );
+ }
+ setTotalAmount( KJob::Files, m_resources.count() );
+}
+
+
+void Nepomuk::MassUpdateJob::setResources( const QList<Nepomuk::Resource>& rl )
+{
+ m_resources = rl;
+ setTotalAmount( KJob::Files, m_resources.count() );
+}
+
+
+void Nepomuk::MassUpdateJob::setProperties( const QList<QPair<QUrl,Nepomuk::Variant> >& props )
+{
+ m_properties = props;
+}
+
+
+void Nepomuk::MassUpdateJob::start()
+{
+ if ( m_index < 0 ) {
+ kDebug();
+ emit description( this,
+ i18n("Changing annotations") );
+ m_index = 0;
+ m_processTimer.start();
+ }
+ else {
+ kDebug() << "Job has already been started";
+ }
+}
+
+
+bool Nepomuk::MassUpdateJob::doKill()
+{
+ if ( m_index > 0 ) {
+ m_processTimer.stop();
+ m_index = -1;
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+
+bool Nepomuk::MassUpdateJob::doSuspend()
+{
+ m_processTimer.stop();
+ return true;
+}
+
+
+bool Nepomuk::MassUpdateJob::doResume()
+{
+ if ( m_index > 0 ) {
+ m_processTimer.start();
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+
+void Nepomuk::MassUpdateJob::slotNext()
+{
+ if ( !isSuspended() ) {
+ if ( m_index < m_resources.count() ) {
+ Nepomuk::Resource& res = m_resources[m_index];
+ for ( int i = 0; i < m_properties.count(); ++i ) {
+ res.setProperty( m_properties[i].first, m_properties[i].second );
+ }
+ ++m_index;
+ setProcessedAmount( KJob::Files, m_index );
+ }
+ else if ( m_index >= m_resources.count() ) {
+ kDebug() << "done";
+ m_index = -1;
+ m_processTimer.stop();
+ emitResult();
+ }
+ }
+}
+
+
+Nepomuk::MassUpdateJob* Nepomuk::MassUpdateJob::tagResources( const QList<Nepomuk::Resource>& rl, const QList<Nepomuk::Tag>& tags )
+{
+ Nepomuk::MassUpdateJob* job = new Nepomuk::MassUpdateJob();
+ job->setResources( rl );
+ job->setProperties( QList<QPair<QUrl,Nepomuk::Variant> >() << qMakePair( QUrl( Nepomuk::Resource::tagUri() ), Nepomuk::Variant( convertResourceList<Tag>( tags ) ) ) );
+ return job;
+}
+
+
+Nepomuk::MassUpdateJob* Nepomuk::MassUpdateJob::rateResources( const QList<Nepomuk::Resource>& rl, int rating )
+{
+ Nepomuk::MassUpdateJob* job = new Nepomuk::MassUpdateJob();
+ job->setResources( rl );
+ job->setProperties( QList<QPair<QUrl,Nepomuk::Variant> >() << qMakePair( QUrl( Nepomuk::Resource::ratingUri() ), Nepomuk::Variant( rating ) ) );
+ return job;
+}
+
+
+Nepomuk::MassUpdateJob* Nepomuk::MassUpdateJob::commentResources( const QList<Nepomuk::Resource>& rl, const QString& comment )
+{
+ Nepomuk::MassUpdateJob* job = new Nepomuk::MassUpdateJob();
+ job->setResources( rl );
+ job->setProperties( QList<QPair<QUrl,Nepomuk::Variant> >() << qMakePair( QUrl( Nepomuk::Resource::descriptionUri() ), Nepomuk::Variant( comment ) ) );
+ return job;
+}
+
+#include "nepomukmassupdatejob.moc"
diff --git a/src/nepomukmassupdatejob.h b/src/nepomukmassupdatejob.h
new file mode 100644
index 000000000..a19fa5ff9
--- /dev/null
+++ b/src/nepomukmassupdatejob.h
@@ -0,0 +1,85 @@
+/***************************************************************************
+ * Copyright (C) 2008 by Sebastian Trueg <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * 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 _NEPOMUK_MASS_UPDATE_JOB_H_
+#define _NEPOMUK_MASS_UPDATE_JOB_H_
+
+#include <kjob.h>
+#include <kurl.h>
+
+#include <QtCore/QList>
+#include <QtCore/QPair>
+#include <QtCore/QTimer>
+
+#include <nepomuk/resource.h>
+#include <nepomuk/variant.h>
+
+
+namespace Nepomuk {
+ class MassUpdateJob : public KJob
+ {
+ Q_OBJECT
+
+ public:
+ MassUpdateJob( QObject* parent = 0 );
+ ~MassUpdateJob();
+
+ /**
+ * Set a list of files to change
+ * This has the same effect as using setResources
+ * with a list of manually created resources.
+ */
+ void setFiles( const KUrl::List& urls );
+
+ /**
+ * Set a list of resources to change.
+ */
+ void setResources( const QList<Nepomuk::Resource>& );
+
+ /**
+ * Set the properties to change in the mass update.
+ */
+ void setProperties( const QList<QPair<QUrl,Nepomuk::Variant> >& props );
+
+ /**
+ * Actually start the job.
+ */
+ void start();
+
+ static MassUpdateJob* tagResources( const QList<Nepomuk::Resource>&, const QList<Nepomuk::Tag>& tags );
+ static MassUpdateJob* commentResources( const QList<Nepomuk::Resource>&, const QString& comment );
+ static MassUpdateJob* rateResources( const QList<Nepomuk::Resource>&, int rating );
+
+ protected:
+ bool doKill();
+ bool doSuspend();
+ bool doResume();
+
+ private Q_SLOTS:
+ void slotNext();
+
+ private:
+ QList<Nepomuk::Resource> m_resources;
+ QList<QPair<QUrl,Nepomuk::Variant> > m_properties;
+ int m_index;
+ QTimer m_processTimer;
+ };
+}
+
+#endif
diff --git a/src/ratingpainter.cpp b/src/ratingpainter.cpp
deleted file mode 100644
index 5dacf1670..000000000
--- a/src/ratingpainter.cpp
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- 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 "ratingpainter.h"
-
-#include <QtGui/QPainter>
-#include <QtGui/QPixmap>
-
-#include <kicon.h>
-#include <kiconeffect.h>
-#include <kdebug.h>
-
-
-class Nepomuk::RatingPainter::Private
-{
-public:
- Private()
- : maxRating(10),
- icon( "rating" ),
- bHalfSteps(true),
- alignment(Qt::AlignCenter),
- direction(Qt::LeftToRight) {
- }
-
- int maxRating;
- KIcon icon;
- bool bHalfSteps;
- Qt::Alignment alignment;
- Qt::LayoutDirection direction;
- QPixmap customPixmap;
-};
-
-
-Nepomuk::RatingPainter::RatingPainter()
- : d(new Private())
-{
-}
-
-
-Nepomuk::RatingPainter::~RatingPainter()
-{
- delete d;
-}
-
-
-int Nepomuk::RatingPainter::maxRating() const
-{
- return d->maxRating;
-}
-
-
-bool Nepomuk::RatingPainter::halfStepsEnabled() const
-{
- return d->bHalfSteps;
-}
-
-
-Qt::Alignment Nepomuk::RatingPainter::alignment() const
-{
- return d->alignment;
-}
-
-
-Qt::LayoutDirection Nepomuk::RatingPainter::direction() const
-{
- return d->direction;
-}
-
-
-KIcon Nepomuk::RatingPainter::icon() const
-{
- return d->icon;
-}
-
-
-QPixmap Nepomuk::RatingPainter::customPixmap() const
-{
- return d->customPixmap;
-}
-
-
-void Nepomuk::RatingPainter::setMaxRating( int max )
-{
- d->maxRating = max;
-}
-
-
-void Nepomuk::RatingPainter::setHalfStepsEnabled( bool enabled )
-{
- d->bHalfSteps = enabled;
-}
-
-
-void Nepomuk::RatingPainter::setAlignment( Qt::Alignment align )
-{
- d->alignment = align;
-}
-
-
-void Nepomuk::RatingPainter::setLayoutDirection( Qt::LayoutDirection direction )
-{
- d->direction = direction;
-}
-
-
-void Nepomuk::RatingPainter::setIcon( const KIcon& icon )
-{
- d->icon = icon;
-}
-
-
-void Nepomuk::RatingPainter::setCustomPixmap( const QPixmap& pixmap )
-{
- d->customPixmap = pixmap;
-}
-
-
-void Nepomuk::RatingPainter::draw( QPainter* painter, const QRect& rect, int rating, int hoverRating )
-{
- rating = qMin( rating, d->maxRating );
- hoverRating = qMin( hoverRating, d->maxRating );
-
- int numUsedStars = d->bHalfSteps ? d->maxRating/2 : d->maxRating;
-
- if ( hoverRating > 0 && hoverRating < rating ) {
- int tmp = hoverRating;
- hoverRating = rating;
- rating = tmp;
- }
-
- // get the rating pixmaps
- QPixmap ratingPix;
- if ( !d->customPixmap.isNull() ) {
- ratingPix = d->customPixmap;
- }
- else {
- KIcon ratingIcon( "rating" );
- int iconSize = qMin( rect.height(), rect.width() / numUsedStars );
- ratingPix = ratingIcon.pixmap( iconSize );
- }
-
- QPixmap disabledRatingPix = KIconEffect().apply( ratingPix, KIconEffect::ToGray, 1.0, QColor(), false );
- QPixmap hoverPix;
-
- bool half = d->bHalfSteps && rating%2;
- int numRatingStars = d->bHalfSteps ? rating/2 : rating;
-
- int numHoverStars = 0;
- bool halfHover = false;
- if ( hoverRating > 0 && rating != hoverRating ) {
- numHoverStars = d->bHalfSteps ? hoverRating/2 : hoverRating;
- halfHover = d->bHalfSteps && hoverRating%2;
- hoverPix = KIconEffect().apply( ratingPix, KIconEffect::ToGray, 0.5, QColor(), false );
- }
-
- int usedSpacing = 0;
- if ( d->alignment & Qt::AlignJustify ) {
- int w = rect.width();
- w -= numUsedStars * ratingPix.width();
- usedSpacing = w / ( numUsedStars-1 );
- }
-
- int i = 0;
- int x = rect.x();
- if ( d->alignment & Qt::AlignRight ) {
- x += ( rect.width() - ratingPix.width()*numUsedStars );
- }
- else if ( d->alignment & Qt::AlignCenter ) {
- x += ( rect.width() - ratingPix.width()*numUsedStars )/2;
- }
-
- int xInc = ratingPix.width() + usedSpacing;
- if ( d->direction == Qt::RightToLeft ) {
- x = rect.width() - ratingPix.width() - x;
- xInc = -xInc;
- }
-
- int y = rect.y();
- if( d->alignment & Qt::AlignVCenter ) {
- y += ( rect.height() / 2 - ratingPix.height() / 2 );
- }
- else if ( d->alignment & Qt::AlignBottom ) {
- y += ( rect.height() - ratingPix.height() );
- }
- for(; i < numRatingStars; ++i ) {
- painter->drawPixmap( x, y, ratingPix );
- x += xInc;
- }
- if( half ) {
- painter->drawPixmap( x, y, ratingPix.width()/2, ratingPix.height(),
- d->direction == Qt::LeftToRight ? ratingPix : ( numHoverStars > 0 ? hoverPix : disabledRatingPix ),
- 0, 0, ratingPix.width()/2, ratingPix.height() );
- painter->drawPixmap( x + ratingPix.width()/2, y, ratingPix.width()/2, ratingPix.height(),
- d->direction == Qt::LeftToRight ? ( numHoverStars > 0 ? hoverPix : disabledRatingPix ) : ratingPix,
- ratingPix.width()/2, 0, ratingPix.width()/2, ratingPix.height() );
- x += xInc;
- ++i;
- }
- for(; i < numHoverStars; ++i ) {
- painter->drawPixmap( x, y, hoverPix );
- x += xInc;
- }
- if( halfHover ) {
- painter->drawPixmap( x, y, ratingPix.width()/2, ratingPix.height(),
- d->direction == Qt::LeftToRight ? hoverPix : disabledRatingPix,
- 0, 0, ratingPix.width()/2, ratingPix.height() );
- painter->drawPixmap( x + ratingPix.width()/2, y, ratingPix.width()/2, ratingPix.height(),
- d->direction == Qt::LeftToRight ? disabledRatingPix : hoverPix,
- ratingPix.width()/2, 0, ratingPix.width()/2, ratingPix.height() );
- x += xInc;
- ++i;
- }
- for(; i < numUsedStars; ++i ) {
- painter->drawPixmap( x, y, disabledRatingPix );
- x += xInc;
- }
-}
-
-
-int Nepomuk::RatingPainter::fromPosition( const QRect& rect, const QPoint& pos )
-{
- int numUsedStars = d->bHalfSteps ? d->maxRating/2 : d->maxRating;
- QPixmap ratingPix;
- if ( !d->customPixmap.isNull() ) {
- ratingPix = d->customPixmap;
- }
- else {
- KIcon ratingIcon( "rating" );
- int iconSize = qMin( rect.height(), rect.width() / numUsedStars );
- ratingPix = ratingIcon.pixmap( iconSize );
- }
-
- QRect usedRect( rect );
- if ( d->alignment & Qt::AlignRight ) {
- usedRect.setLeft( rect.right() - numUsedStars * ratingPix.width() );
- }
- else if ( d->alignment & Qt::AlignHCenter ) {
- int x = ( rect.width() - numUsedStars * ratingPix.width() )/2;
- usedRect.setLeft( rect.left() + x );
- usedRect.setRight( rect.right() - x );
- }
- else { // d->alignment & Qt::AlignLeft
- usedRect.setRight( rect.left() + numUsedStars * ratingPix.width() - 1 );
- }
-
- if ( d->alignment & Qt::AlignBottom ) {
- usedRect.setTop( rect.bottom() - ratingPix.height() + 1 );
- }
- else if ( d->alignment & Qt::AlignVCenter ) {
- int x = ( rect.height() - ratingPix.height() )/2;
- usedRect.setTop( rect.top() + x );
- usedRect.setBottom( rect.bottom() - x );
- }
- else { // d->alignment & Qt::AlignTop
- usedRect.setBottom( rect.top() + ratingPix.height() - 1 );
- }
-
- if ( usedRect.contains( pos ) ) {
- int x = 0;
- if ( d->direction == Qt::RightToLeft ) {
- x = usedRect.right() - pos.x();
- }
- else {
- x = pos.x() - usedRect.left();
- }
-
- double one = ( double )usedRect.width() / ( double )d->maxRating;
-
- kDebug() << "rating:" << ( int )( ( double )x/one + 0.5 );
-
- return ( int )( ( double )x/one + 0.5 );
- }
- else {
- return -1;
- }
-}
-
-
-void Nepomuk::RatingPainter::drawRating( QPainter* painter, const QRect& rect, Qt::Alignment align, int rating, int hoverRating )
-{
- RatingPainter rp;
- rp.setAlignment( align );
- rp.setLayoutDirection( painter->layoutDirection() );
- rp.draw( painter, rect, rating, hoverRating );
-}
-
-
-int Nepomuk::RatingPainter::getRatingFromPosition( const QRect& rect, Qt::Alignment align, Qt::LayoutDirection direction, const QPoint& pos )
-{
- RatingPainter rp;
- rp.setAlignment( align );
- rp.setLayoutDirection( direction );
- return rp.fromPosition( rect, pos );
-}
diff --git a/src/ratingpainter.h b/src/ratingpainter.h
deleted file mode 100644
index 31c406de2..000000000
--- a/src/ratingpainter.h
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- 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_RATING_PAINTER_H_
-#define _NEPOMUK_RATING_PAINTER_H_
-
-class QPainter;
-
-#include <QtCore/QRect>
-#include <QtCore/QPoint>
-
-#include <kicon.h>
-
-
-namespace Nepomuk {
- /**
- * Utility class that draws a row of stars for a rating value.
- */
- class RatingPainter
- {
- public:
- /**
- * Create a new RatingPainter.
- * Normally the static methods should be sufficient.
- */
- RatingPainter();
- ~RatingPainter();
-
- int maxRating() const;
- bool halfStepsEnabled() const;
- Qt::Alignment alignment() const;
- Qt::LayoutDirection direction() const;
- KIcon icon() const;
- QPixmap customPixmap() const;
-
- /**
- * The maximum rating. Defaults to 10.
- */
- void setMaxRating( int max );
-
- /**
- * If half steps are enabled (the default) then
- * one rating step corresponds to half a star.
- */
- void setHalfStepsEnabled( bool enabled );
-
- /**
- * The alignment of the stars in the drawing rect.
- * All alignment flags are supported.
- */
- void setAlignment( Qt::Alignment align );
-
- /**
- * LTR or RTL
- */
- void setLayoutDirection( Qt::LayoutDirection direction );
-
- /**
- * Set a custom icon. Defaults to "rating".
- */
- void setIcon( const KIcon& icon );
-
- /**
- * Set a custom pixmap.
- */
- void setCustomPixmap( const QPixmap& pixmap );
-
- /**
- * Draw the rating into the configured rect.
- */
- void draw( QPainter* painter, const QRect& rect, int rating, int hoverRating = -1 );
-
- /**
- * Calculate the rating value from mouse position pos.
- *
- * \return The rating corresponding to pos or -1 if pos is
- * outside of the configured rect.
- */
- int fromPosition( const QRect& rect, const QPoint& pos );
-
- /**
- * Convenience method that draws a rating into the given rect.
- *
- * LayoutDirection is read from QPainter.
- *
- * \param align can be aligned vertically and horizontally. Using Qt::AlignJustify will insert spacing
- * between the stars.
- */
- static void drawRating( QPainter* p, const QRect& rect, Qt::Alignment align, int rating, int hoverRating = -1 );
-
- /**
- * Get the rating that would be selected if the user clicked position pos
- * within rect if the rating has been drawn with drawRating() using the same
- * rect and align values.
- *
- * \return The new rating or -1 if pos is outside of the rating area.
- */
- static int getRatingFromPosition( const QRect& rect, Qt::Alignment align, Qt::LayoutDirection direction, const QPoint& pos );
-
- private:
- class Private;
- Private* const d;
- };
-}
-
-#endif
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