diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 17 | ||||
| -rw-r--r-- | src/commenteditwidget.cpp | 241 | ||||
| -rw-r--r-- | src/commenteditwidget.h | 62 | ||||
| -rw-r--r-- | src/commentwidget.cpp | 114 | ||||
| -rw-r--r-- | src/commentwidget.h | 48 | ||||
| -rw-r--r-- | src/dolphincategorydrawer.cpp | 16 | ||||
| -rw-r--r-- | src/metadatawidget.cpp | 135 | ||||
| -rw-r--r-- | src/metadatawidget.h | 7 | ||||
| -rw-r--r-- | src/nepomukmassupdatejob.cpp | 163 | ||||
| -rw-r--r-- | src/nepomukmassupdatejob.h | 85 | ||||
| -rw-r--r-- | src/ratingpainter.cpp | 310 | ||||
| -rw-r--r-- | src/ratingpainter.h | 122 | ||||
| -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 |
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 |
