┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
path: root/src/panels
diff options
context:
space:
mode:
authorPeter Penz <[email protected]>2009-01-14 19:26:23 +0000
committerPeter Penz <[email protected]>2009-01-14 19:26:23 +0000
commit307285e9635a4bf584d6e5d7478876b90ef870f0 (patch)
treedb36cbbdc7996ab86c38be8f96fe83597d350dfb /src/panels
parent86d9c40ab71df5b8bd5063251337d5ca0c22380a (diff)
Group classes into folders, Dolphin is too big in the meantime for having a flat directory hierarchy. dolphin/src/CMakeLists.txt will be cleaned up later.
svn path=/trunk/KDE/kdebase/apps/; revision=911065
Diffstat (limited to 'src/panels')
-rw-r--r--src/panels/folders/dolphin_folderspanelsettings.kcfg11
-rw-r--r--src/panels/folders/dolphin_folderspanelsettings.kcfgc4
-rw-r--r--src/panels/folders/ktreeview.cpp180
-rw-r--r--src/panels/folders/ktreeview.h48
-rw-r--r--src/panels/folders/ktreeview_p.h49
-rw-r--r--src/panels/folders/sidebartreeview.cpp147
-rw-r--r--src/panels/folders/sidebartreeview.h58
-rw-r--r--src/panels/folders/treeviewcontextmenu.cpp194
-rw-r--r--src/panels/folders/treeviewcontextmenu.h88
-rw-r--r--src/panels/folders/treeviewsidebarpage.cpp279
-rw-r--r--src/panels/folders/treeviewsidebarpage.h135
-rw-r--r--src/panels/information/commenteditwidget.cpp241
-rw-r--r--src/panels/information/commenteditwidget.h62
-rw-r--r--src/panels/information/commentwidget.cpp114
-rw-r--r--src/panels/information/commentwidget.h48
-rw-r--r--src/panels/information/infosidebarpage.cpp587
-rw-r--r--src/panels/information/infosidebarpage.h194
-rw-r--r--src/panels/information/metadatawidget.cpp281
-rw-r--r--src/panels/information/metadatawidget.h72
-rw-r--r--src/panels/information/metatextlabel.cpp135
-rw-r--r--src/panels/information/metatextlabel.h64
-rw-r--r--src/panels/information/nepomukmassupdatejob.cpp163
-rw-r--r--src/panels/information/nepomukmassupdatejob.h85
-rw-r--r--src/panels/information/newtagdialog.cpp95
-rw-r--r--src/panels/information/newtagdialog.h47
-rw-r--r--src/panels/information/newtagdialog.ui110
-rw-r--r--src/panels/information/resourcetaggingwidget.cpp193
-rw-r--r--src/panels/information/resourcetaggingwidget.h63
-rw-r--r--src/panels/information/tagcloud.cpp1005
-rw-r--r--src/panels/information/tagcloud.h142
-rw-r--r--src/panels/information/taggingpopup.cpp147
-rw-r--r--src/panels/information/taggingpopup.h50
-rw-r--r--src/panels/places/dolphinfileplacesview.cpp56
-rw-r--r--src/panels/places/dolphinfileplacesview.h51
-rw-r--r--src/panels/sidebarpage.cpp46
-rw-r--r--src/panels/sidebarpage.h53
-rw-r--r--src/panels/terminal/terminalsidebarpage.cpp97
-rw-r--r--src/panels/terminal/terminalsidebarpage.h62
38 files changed, 5456 insertions, 0 deletions
diff --git a/src/panels/folders/dolphin_folderspanelsettings.kcfg b/src/panels/folders/dolphin_folderspanelsettings.kcfg
new file mode 100644
index 000000000..816d783c8
--- /dev/null
+++ b/src/panels/folders/dolphin_folderspanelsettings.kcfg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE kcfg SYSTEM "http://www.kde.org/standards/kcfg/1.0/kcfg.dtd">
+<kcfg>
+ <kcfgfile name="dolphinrc"/>
+ <group name="FoldersPanel">
+ <entry name="ShowHiddenFiles" type="Bool">
+ <label context="@label">Show hidden files</label>
+ <default>false</default>
+ </entry>
+ </group>
+</kcfg>
diff --git a/src/panels/folders/dolphin_folderspanelsettings.kcfgc b/src/panels/folders/dolphin_folderspanelsettings.kcfgc
new file mode 100644
index 000000000..f73e8807d
--- /dev/null
+++ b/src/panels/folders/dolphin_folderspanelsettings.kcfgc
@@ -0,0 +1,4 @@
+File=dolphin_folderspanelsettings.kcfg
+ClassName=FoldersPanelSettings
+Singleton=true
+Mutators=true
diff --git a/src/panels/folders/ktreeview.cpp b/src/panels/folders/ktreeview.cpp
new file mode 100644
index 000000000..7c30fad33
--- /dev/null
+++ b/src/panels/folders/ktreeview.cpp
@@ -0,0 +1,180 @@
+/***************************************************************************
+ * Copyright (C) 2008 by <haraldhv (at) stud.ntnu.no> *
+ * Copyright (C) 2008 by <[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 "ktreeview.h"
+#include "ktreeview_p.h"
+
+#include <KGlobalSettings>
+
+#include <QItemSelectionModel>
+#include <QScrollBar>
+#include <QTimer>
+#include <QTimeLine>
+
+KTreeView::KTreeViewPrivate::KTreeViewPrivate(KTreeView *parent) :
+ parent(parent),
+ autoHorizontalScroll(false),
+ timeLine(0),
+ startScrollTimer(0)
+{
+ startScrollTimer = new QTimer(this);
+ startScrollTimer->setSingleShot(true);
+ startScrollTimer->setInterval(300);
+ connect(startScrollTimer, SIGNAL(timeout()),
+ this, SLOT(startScrolling()));
+
+ timeLine = new QTimeLine(300, this);
+ connect(timeLine, SIGNAL(frameChanged(int)),
+ this, SLOT(updateVerticalScrollBar(int)));
+
+ connect(parent->verticalScrollBar(), SIGNAL(rangeChanged(int, int)),
+ startScrollTimer, SLOT(start()));
+ connect(parent->verticalScrollBar(), SIGNAL(valueChanged(int)),
+ startScrollTimer, SLOT(start()));
+ connect(parent, SIGNAL(collapsed(const QModelIndex&)),
+ startScrollTimer, SLOT(start()));
+ connect(parent, SIGNAL(expanded(const QModelIndex&)),
+ startScrollTimer, SLOT(start()));
+}
+
+void KTreeView::KTreeViewPrivate::startScrolling()
+{
+ QModelIndex index;
+
+ const int viewportHeight = parent->viewport()->height();
+
+ // check whether there is a selected index which is partly visible
+ const QModelIndexList selectedIndexes = parent->selectionModel()->selectedIndexes();
+ if (selectedIndexes.count() == 1) {
+ QModelIndex selectedIndex = selectedIndexes.first();
+ const QRect rect = parent->visualRect(selectedIndex);
+ if ((rect.bottom() >= 0) && (rect.top() <= viewportHeight)) {
+ // the selected index is (at least partly) visible, use it as
+ // scroll target
+ index = selectedIndex;
+ }
+ }
+
+ if (!index.isValid()) {
+ // no partly selected index is visible, determine the most left visual index
+ QModelIndex visibleIndex = parent->indexAt(QPoint(0, 0));
+ if (!visibleIndex.isValid()) {
+ return;
+ }
+
+ index = visibleIndex;
+ int minimum = parent->width();
+ do {
+ const QRect rect = parent->visualRect(visibleIndex);
+ if (rect.top() > viewportHeight) {
+ // the current index and all successors are not visible anymore
+ break;
+ }
+ if (rect.left() < minimum) {
+ minimum = rect.left();
+ index = visibleIndex;
+ }
+ visibleIndex = parent->indexBelow(visibleIndex);
+ } while (visibleIndex.isValid());
+ }
+
+ // start the horizontal scrolling to assure that the item indicated by 'index' gets fully visible
+ Q_ASSERT(index.isValid());
+ const QRect rect = parent->visualRect(index);
+
+ QScrollBar *scrollBar = parent->horizontalScrollBar();
+ const int oldScrollBarPos = scrollBar->value();
+
+ const int itemRight = oldScrollBarPos + rect.left() + rect.width() - 1;
+ const int availableWidth = parent->viewport()->width();
+ int scrollBarPos = itemRight - availableWidth;
+ const int scrollBarPosMax = oldScrollBarPos + rect.left() - parent->indentation();
+ if (scrollBarPos > scrollBarPosMax) {
+ scrollBarPos = scrollBarPosMax;
+ }
+
+ if (scrollBarPos != oldScrollBarPos) {
+ timeLine->setFrameRange(oldScrollBarPos, scrollBarPos);
+ timeLine->start();
+ }
+}
+
+void KTreeView::KTreeViewPrivate::updateVerticalScrollBar(int value)
+{
+ QScrollBar *scrollBar = parent->horizontalScrollBar();
+ scrollBar->setValue(value);
+ startScrollTimer->stop();
+}
+
+// ************************************************
+
+KTreeView::KTreeView(QWidget *parent) :
+ QTreeView(parent),
+ d(new KTreeViewPrivate(this))
+{
+ if (KGlobalSettings::graphicEffectsLevel() >= KGlobalSettings::SimpleAnimationEffects) {
+ setAutoHorizontalScroll(true);
+ }
+}
+
+KTreeView::~KTreeView()
+{
+}
+
+void KTreeView::setAutoHorizontalScroll(bool value)
+{
+ d->autoHorizontalScroll = value;
+}
+
+bool KTreeView::autoHorizontalScroll() const
+{
+ return d->autoHorizontalScroll;
+}
+
+void KTreeView::setSelectionModel(QItemSelectionModel *selectionModel)
+{
+ QTreeView::setSelectionModel(selectionModel);
+ connect(selectionModel,
+ SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
+ d->startScrollTimer, SLOT(start()));
+}
+
+void KTreeView::scrollTo(const QModelIndex& index, ScrollHint hint)
+{
+ if (d->autoHorizontalScroll) {
+ // assure that the value of the horizontal scrollbar stays on its current value,
+ // KTreeView will adjust the value manually
+ const int value = horizontalScrollBar()->value();
+ QTreeView::scrollTo(index, hint);
+ horizontalScrollBar()->setValue(value);
+ } else {
+ QTreeView::scrollTo(index, hint);
+ }
+}
+
+void KTreeView::hideEvent(QHideEvent *event)
+{
+ d->startScrollTimer->stop();
+ d->timeLine->stop();
+ QTreeView::hideEvent(event);
+}
+
+#include "ktreeview.moc"
+#include "ktreeview_p.moc"
diff --git a/src/panels/folders/ktreeview.h b/src/panels/folders/ktreeview.h
new file mode 100644
index 000000000..6bc6ea56a
--- /dev/null
+++ b/src/panels/folders/ktreeview.h
@@ -0,0 +1,48 @@
+/***************************************************************************
+ * Copyright (C) 2008 by <haraldhv (at) stud.ntnu.no> *
+ * Copyright (C) 2008 by <[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 KTREEVIEW_H
+#define KTREEVIEW_H
+
+#include <QTreeView>
+
+class KTreeView : public QTreeView
+{
+ Q_OBJECT
+
+public:
+ KTreeView(QWidget *parent = 0);
+ virtual ~KTreeView();
+
+ void setAutoHorizontalScroll(bool value);
+ bool autoHorizontalScroll() const;
+
+ virtual void setSelectionModel(QItemSelectionModel *selectionModel);
+ virtual void scrollTo(const QModelIndex& index, ScrollHint hint = EnsureVisible);
+
+protected:
+ virtual void hideEvent(QHideEvent *event);
+
+private:
+ class KTreeViewPrivate;
+ KTreeViewPrivate *d;
+};
+
+#endif /* ifndef KTREEVIEW_H */
diff --git a/src/panels/folders/ktreeview_p.h b/src/panels/folders/ktreeview_p.h
new file mode 100644
index 000000000..1cfa463cd
--- /dev/null
+++ b/src/panels/folders/ktreeview_p.h
@@ -0,0 +1,49 @@
+/***************************************************************************
+ * Copyright (C) 2008 by <haraldhv (at) stud.ntnu.no> *
+ * Copyright (C) 2008 by <[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 KTREEVIEW_P_H
+#define KTREEVIEW_P_H
+
+#include <QObject>
+
+#include "ktreeview.h"
+
+class QTimer;
+class QTimeLine;
+
+class KTreeView::KTreeViewPrivate : public QObject
+{
+ Q_OBJECT
+
+public Q_SLOTS:
+ void startScrolling();
+ void updateVerticalScrollBar(int value);
+
+public:
+ KTreeViewPrivate(KTreeView *parent);
+ KTreeView *parent;
+ void setScrollTowards( int scrollTowards );
+
+ bool autoHorizontalScroll;
+ QTimeLine *timeLine;
+ QTimer *startScrollTimer;
+};
+
+#endif /* ifndef KTREEVIEW_P_H */
diff --git a/src/panels/folders/sidebartreeview.cpp b/src/panels/folders/sidebartreeview.cpp
new file mode 100644
index 000000000..a876ee6c3
--- /dev/null
+++ b/src/panels/folders/sidebartreeview.cpp
@@ -0,0 +1,147 @@
+/***************************************************************************
+ * Copyright (C) 2006 by Peter Penz <[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 "sidebartreeview.h"
+
+#include "dolphincontroller.h"
+#include "dolphinmodel.h"
+#include "draganddrophelper.h"
+
+#include <kfileitemdelegate.h>
+#include <QKeyEvent>
+#include <QPainter>
+#include <QHeaderView>
+#include <QScrollBar>
+
+SidebarTreeView::SidebarTreeView(QWidget* parent) :
+ KTreeView(parent)
+{
+ setAcceptDrops(true);
+ setUniformRowHeights(true);
+ setSelectionMode(QAbstractItemView::SingleSelection);
+ setEditTriggers(QAbstractItemView::NoEditTriggers);
+ setSortingEnabled(true);
+ setFrameStyle(QFrame::NoFrame);
+ setDragDropMode(QAbstractItemView::DragDrop);
+ setDropIndicatorShown(false);
+
+ setVerticalScrollMode(QListView::ScrollPerPixel);
+ setHorizontalScrollMode(QListView::ScrollPerPixel);
+
+ viewport()->setAttribute(Qt::WA_Hover);
+
+ // make the background transparent and apply the window-text color
+ // to the text color, so that enough contrast is given for all color
+ // schemes
+ QPalette p = palette();
+ p.setColor(QPalette::Active, QPalette::Text, p.color(QPalette::Active, QPalette::WindowText));
+ p.setColor(QPalette::Inactive, QPalette::Text, p.color(QPalette::Inactive, QPalette::WindowText));
+ p.setColor(QPalette::Disabled, QPalette::Text, p.color(QPalette::Disabled, QPalette::WindowText));
+ setPalette(p);
+ viewport()->setAutoFillBackground(false);
+
+ KFileItemDelegate* delegate = new KFileItemDelegate(this);
+ setItemDelegate(delegate);
+}
+
+SidebarTreeView::~SidebarTreeView()
+{
+}
+
+bool SidebarTreeView::event(QEvent* event)
+{
+ switch (event->type()) {
+ case QEvent::Polish:
+ // hide all columns except of the 'Name' column
+ hideColumn(DolphinModel::Size);
+ hideColumn(DolphinModel::ModifiedTime);
+ hideColumn(DolphinModel::Permissions);
+ hideColumn(DolphinModel::Owner);
+ hideColumn(DolphinModel::Group);
+ hideColumn(DolphinModel::Type);
+ hideColumn(DolphinModel::Rating);
+ hideColumn(DolphinModel::Tags);
+ header()->hide();
+ break;
+
+ case QEvent::Show:
+ // TODO: The opening/closing animation of subtrees flickers in combination with the
+ // sidebar when using the Oxygen style. As workaround the animation is turned off:
+ setAnimated(false);
+ break;
+
+ case QEvent::UpdateRequest:
+ // a wheel movement will scroll 1 item
+ if (model()->rowCount() > 0) {
+ verticalScrollBar()->setSingleStep(sizeHintForRow(0) / 3);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return KTreeView::event(event);
+}
+
+void SidebarTreeView::startDrag(Qt::DropActions supportedActions)
+{
+ DragAndDropHelper::instance().startDrag(this, supportedActions);
+}
+
+void SidebarTreeView::dragEnterEvent(QDragEnterEvent* event)
+{
+ KTreeView::dragEnterEvent(event);
+ if (event->mimeData()->hasUrls()) {
+ event->acceptProposedAction();
+ }
+}
+
+void SidebarTreeView::dragLeaveEvent(QDragLeaveEvent* event)
+{
+ KTreeView::dragLeaveEvent(event);
+ setDirtyRegion(m_dropRect);
+}
+
+void SidebarTreeView::dragMoveEvent(QDragMoveEvent* event)
+{
+ KTreeView::dragMoveEvent(event);
+
+ // TODO: remove this code when the issue #160611 is solved in Qt 4.4
+ const QModelIndex index = indexAt(event->pos());
+ setDirtyRegion(m_dropRect);
+ m_dropRect = visualRect(index);
+ setDirtyRegion(m_dropRect);
+
+ if (event->mimeData()->hasUrls()) {
+ // accept url drops, independently from the destination item
+ event->acceptProposedAction();
+ }
+}
+
+void SidebarTreeView::dropEvent(QDropEvent* event)
+{
+ const QModelIndex index = indexAt(event->pos());
+ if (index.isValid()) {
+ emit urlsDropped(index, event);
+ }
+ KTreeView::dropEvent(event);
+}
+
+#include "sidebartreeview.moc"
diff --git a/src/panels/folders/sidebartreeview.h b/src/panels/folders/sidebartreeview.h
new file mode 100644
index 000000000..a5e8b63ce
--- /dev/null
+++ b/src/panels/folders/sidebartreeview.h
@@ -0,0 +1,58 @@
+/***************************************************************************
+ * Copyright (C) 2006 by Peter Penz <[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 SIDEBARTREEVIEW_H
+#define SIDEBARTREEVIEW_H
+
+#include <kurl.h>
+#include <panels/folders/ktreeview.h>
+
+/**
+ * @brief Tree view widget which is used for the sidebar panel.
+ *
+ * @see TreeViewSidebarPage
+ */
+class SidebarTreeView : public KTreeView
+{
+ Q_OBJECT
+
+public:
+ explicit SidebarTreeView(QWidget* parent = 0);
+ virtual ~SidebarTreeView();
+
+signals:
+ /**
+ * Is emitted if the URL have been dropped to
+ * the index \a index.
+ */
+ void urlsDropped(const QModelIndex& index, QDropEvent* event);
+
+protected:
+ virtual bool event(QEvent* event);
+ virtual void startDrag(Qt::DropActions supportedActions);
+ virtual void dragEnterEvent(QDragEnterEvent* event);
+ virtual void dragLeaveEvent(QDragLeaveEvent* event);
+ virtual void dragMoveEvent(QDragMoveEvent* event);
+ virtual void dropEvent(QDropEvent* event);
+
+private:
+ QRect m_dropRect;
+};
+
+#endif
diff --git a/src/panels/folders/treeviewcontextmenu.cpp b/src/panels/folders/treeviewcontextmenu.cpp
new file mode 100644
index 000000000..9e8638002
--- /dev/null
+++ b/src/panels/folders/treeviewcontextmenu.cpp
@@ -0,0 +1,194 @@
+/***************************************************************************
+ * Copyright (C) 2006 by Peter Penz ([email protected]) and *
+ * Cvetoslav Ludmiloff *
+ * *
+ * 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 "treeviewcontextmenu.h"
+
+#include "dolphin_folderspanelsettings.h"
+
+#include <kfileitem.h>
+#include <kiconloader.h>
+#include <kio/deletejob.h>
+#include <kmenu.h>
+#include <konqmimedata.h>
+#include <konq_fileitemcapabilities.h>
+#include <konq_operations.h>
+#include <klocale.h>
+#include <kpropertiesdialog.h>
+
+#include "treeviewsidebarpage.h"
+
+#include <QtGui/QApplication>
+#include <QtGui/QClipboard>
+
+TreeViewContextMenu::TreeViewContextMenu(TreeViewSidebarPage* parent,
+ const KFileItem& fileInfo) :
+ QObject(parent),
+ m_parent(parent),
+ m_fileInfo(fileInfo)
+{
+}
+
+TreeViewContextMenu::~TreeViewContextMenu()
+{
+}
+
+void TreeViewContextMenu::open()
+{
+ KMenu* popup = new KMenu(m_parent);
+
+ if (!m_fileInfo.isNull()) {
+ KonqFileItemCapabilities capabilities(KFileItemList() << m_fileInfo);
+
+ // insert 'Cut', 'Copy' and 'Paste'
+ QAction* cutAction = new QAction(KIcon("edit-cut"), i18nc("@action:inmenu", "Cut"), this);
+ cutAction->setEnabled(capabilities.supportsMoving());
+ connect(cutAction, SIGNAL(triggered()), this, SLOT(cut()));
+
+ QAction* copyAction = new QAction(KIcon("edit-copy"), i18nc("@action:inmenu", "Copy"), this);
+ connect(copyAction, SIGNAL(triggered()), this, SLOT(copy()));
+
+ QAction* pasteAction = new QAction(KIcon("edit-paste"), i18nc("@action:inmenu", "Paste"), this);
+ const QMimeData* mimeData = QApplication::clipboard()->mimeData();
+ const KUrl::List pasteData = KUrl::List::fromMimeData(mimeData);
+ connect(pasteAction, SIGNAL(triggered()), this, SLOT(paste()));
+ pasteAction->setEnabled(!pasteData.isEmpty() && capabilities.supportsWriting());
+
+ popup->addAction(cutAction);
+ popup->addAction(copyAction);
+ popup->addAction(pasteAction);
+ popup->addSeparator();
+
+ // insert 'Rename'
+ QAction* renameAction = new QAction(i18nc("@action:inmenu", "Rename..."), this);
+ renameAction->setEnabled(capabilities.supportsMoving());
+ connect(renameAction, SIGNAL(triggered()), this, SLOT(rename()));
+ popup->addAction(renameAction);
+
+ // insert 'Move to Trash' and (optionally) 'Delete'
+ KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig("kdeglobals", KConfig::IncludeGlobals);
+ KConfigGroup configGroup(globalConfig, "KDE");
+ bool showDeleteCommand = configGroup.readEntry("ShowDeleteCommand", false);
+
+ const KUrl& url = m_fileInfo.url();
+ if (url.isLocalFile()) {
+ QAction* moveToTrashAction = new QAction(KIcon("user-trash"),
+ i18nc("@action:inmenu", "Move To Trash"), this);
+ const bool enableMoveToTrash = capabilities.isLocal() && capabilities.supportsMoving();
+ moveToTrashAction->setEnabled(enableMoveToTrash);
+ connect(moveToTrashAction, SIGNAL(triggered()), this, SLOT(moveToTrash()));
+ popup->addAction(moveToTrashAction);
+ } else {
+ showDeleteCommand = true;
+ }
+
+ if (showDeleteCommand) {
+ QAction* deleteAction = new QAction(KIcon("edit-delete"), i18nc("@action:inmenu", "Delete"), this);
+ deleteAction->setEnabled(capabilities.supportsDeleting());
+ connect(deleteAction, SIGNAL(triggered()), this, SLOT(deleteItem()));
+ popup->addAction(deleteAction);
+ }
+
+ popup->addSeparator();
+
+ // insert 'Properties' entry
+ QAction* propertiesAction = new QAction(i18nc("@action:inmenu", "Properties"), this);
+ connect(propertiesAction, SIGNAL(triggered()), this, SLOT(showProperties()));
+ popup->addAction(propertiesAction);
+
+ popup->addSeparator();
+ }
+
+ QAction* showHiddenFilesAction = new QAction(i18nc("@action:inmenu", "Show Hidden Files"), this);
+ showHiddenFilesAction->setCheckable(true);
+ showHiddenFilesAction->setChecked(FoldersPanelSettings::showHiddenFiles());
+ popup->addAction(showHiddenFilesAction);
+
+ connect(showHiddenFilesAction, SIGNAL(toggled(bool)), this, SLOT(setShowHiddenFiles(bool)));
+
+ popup->exec(QCursor::pos());
+ popup->deleteLater();
+}
+
+void TreeViewContextMenu::populateMimeData(QMimeData* mimeData, bool cut)
+{
+ KUrl::List kdeUrls;
+ kdeUrls.append(m_fileInfo.url());
+ KUrl::List mostLocalUrls;
+ bool dummy;
+ mostLocalUrls.append(m_fileInfo.mostLocalUrl(dummy));
+ KonqMimeData::populateMimeData(mimeData, kdeUrls, mostLocalUrls, cut);
+}
+
+void TreeViewContextMenu::cut()
+{
+ QMimeData* mimeData = new QMimeData();
+ populateMimeData(mimeData, true);
+ QApplication::clipboard()->setMimeData(mimeData);
+}
+
+void TreeViewContextMenu::copy()
+{
+ QMimeData* mimeData = new QMimeData();
+ populateMimeData(mimeData, false);
+ QApplication::clipboard()->setMimeData(mimeData);
+}
+
+void TreeViewContextMenu::paste()
+{
+ QClipboard* clipboard = QApplication::clipboard();
+ const QMimeData* mimeData = clipboard->mimeData();
+
+ const KUrl::List source = KUrl::List::fromMimeData(mimeData);
+ const KUrl& dest = m_fileInfo.url();
+ if (KonqMimeData::decodeIsCutSelection(mimeData)) {
+ KonqOperations::copy(m_parent, KonqOperations::MOVE, source, dest);
+ clipboard->clear();
+ } else {
+ KonqOperations::copy(m_parent, KonqOperations::COPY, source, dest);
+ }
+}
+
+void TreeViewContextMenu::rename()
+{
+ m_parent->rename(m_fileInfo);
+}
+
+void TreeViewContextMenu::moveToTrash()
+{
+ KonqOperations::del(m_parent, KonqOperations::TRASH, m_fileInfo.url());
+}
+
+void TreeViewContextMenu::deleteItem()
+{
+ KonqOperations::del(m_parent, KonqOperations::DEL, m_fileInfo.url());
+}
+
+void TreeViewContextMenu::showProperties()
+{
+ KPropertiesDialog dialog(m_fileInfo.url(), m_parent);
+ dialog.exec();
+}
+
+void TreeViewContextMenu::setShowHiddenFiles(bool show)
+{
+ m_parent->setShowHiddenFiles(show);
+}
+
+#include "treeviewcontextmenu.moc"
diff --git a/src/panels/folders/treeviewcontextmenu.h b/src/panels/folders/treeviewcontextmenu.h
new file mode 100644
index 000000000..20a603573
--- /dev/null
+++ b/src/panels/folders/treeviewcontextmenu.h
@@ -0,0 +1,88 @@
+/***************************************************************************
+ * Copyright (C) 2006 by Peter Penz <[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 TREEVIEWCONTEXTMENU_H
+#define TREEVIEWCONTEXTMENU_H
+
+#include <QtCore/QObject>
+#include <KFileItem>
+
+class TreeViewSidebarPage;
+
+/**
+ * @brief Represents the context menu which appears when doing a right
+ * click on an item of the treeview.
+ */
+class TreeViewContextMenu : public QObject
+{
+ Q_OBJECT
+
+public:
+ /**
+ * @parent Pointer to the treeview sidebar page the context menu
+ * belongs to.
+ * @fileInfo Pointer to the file item the context menu
+ * is applied. If 0 is passed, the context menu
+ * is above the viewport.
+ */
+ TreeViewContextMenu(TreeViewSidebarPage* parent,
+ const KFileItem& fileInfo);
+
+ virtual ~TreeViewContextMenu();
+
+ /** Opens the context menu modal. */
+ void open();
+
+private slots:
+ /** Cuts the item m_fileInfo. */
+ void cut();
+
+ /** Copies the item m_fileInfo. */
+ void copy();
+
+ /** Paste the clipboard to m_fileInfo. */
+ void paste();
+
+ /** Renames the item m_fileInfo. */
+ void rename();
+
+ /** Moves the item m_fileInfo to the trash. */
+ void moveToTrash();
+
+ /** Deletes the item m_fileInfo. */
+ void deleteItem();
+
+ /** Shows the properties of the item m_fileInfo. */
+ void showProperties();
+
+ /**
+ * Sets the 'Show Hidden Files' setting for the
+ * folders panel to \a show.
+ */
+ void setShowHiddenFiles(bool show);
+
+private:
+ void populateMimeData(QMimeData* mimeData, bool cut);
+
+private:
+ TreeViewSidebarPage* m_parent;
+ KFileItem m_fileInfo;
+};
+
+#endif
diff --git a/src/panels/folders/treeviewsidebarpage.cpp b/src/panels/folders/treeviewsidebarpage.cpp
new file mode 100644
index 000000000..7801a97cc
--- /dev/null
+++ b/src/panels/folders/treeviewsidebarpage.cpp
@@ -0,0 +1,279 @@
+/***************************************************************************
+ * Copyright (C) 2006 by Peter Penz <[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 "treeviewsidebarpage.h"
+
+#include "dolphinmodel.h"
+#include "dolphinsortfilterproxymodel.h"
+#include "dolphinview.h"
+#include "settings/dolphinsettings.h"
+#include "dolphin_folderspanelsettings.h"
+#include "dolphin_generalsettings.h"
+#include "draganddrophelper.h"
+#include "folderexpander.h"
+#include "renamedialog.h"
+#include "sidebartreeview.h"
+#include "treeviewcontextmenu.h"
+
+#include <kfileplacesmodel.h>
+#include <kdirlister.h>
+#include <kfileitem.h>
+#include <konq_operations.h>
+
+#include <QApplication>
+#include <QItemSelection>
+#include <QTreeView>
+#include <QBoxLayout>
+#include <QModelIndex>
+#include <QScrollBar>
+#include <QTimer>
+
+TreeViewSidebarPage::TreeViewSidebarPage(QWidget* parent) :
+ SidebarPage(parent),
+ m_setLeafVisible(false),
+ m_mouseButtons(Qt::NoButton),
+ m_dirLister(0),
+ m_dolphinModel(0),
+ m_proxyModel(0),
+ m_treeView(0),
+ m_leafDir()
+{
+ setLayoutDirection(Qt::LeftToRight);
+}
+
+TreeViewSidebarPage::~TreeViewSidebarPage()
+{
+ FoldersPanelSettings::self()->writeConfig();
+
+ delete m_proxyModel;
+ m_proxyModel = 0;
+ delete m_dolphinModel;
+ m_dolphinModel = 0;
+ m_dirLister = 0; // deleted by m_dolphinModel
+}
+
+QSize TreeViewSidebarPage::sizeHint() const
+{
+ return QSize(200, 400);
+}
+
+void TreeViewSidebarPage::setShowHiddenFiles(bool show)
+{
+ FoldersPanelSettings::setShowHiddenFiles(show);
+ if (m_dirLister != 0) {
+ m_dirLister->setShowingDotFiles(show);
+ m_dirLister->openUrl(m_dirLister->url(), KDirLister::Reload);
+ }
+}
+
+bool TreeViewSidebarPage::showHiddenFiles() const
+{
+ return FoldersPanelSettings::showHiddenFiles();
+}
+
+void TreeViewSidebarPage::rename(const KFileItem& item)
+{
+ if (DolphinSettings::instance().generalSettings()->renameInline()) {
+ const QModelIndex dirIndex = m_dolphinModel->indexForItem(item);
+ const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex);
+ m_treeView->edit(proxyIndex);
+ } else {
+ KFileItemList items;
+ items.append(item);
+ RenameDialog dialog(this, items);
+ if (dialog.exec() == QDialog::Accepted) {
+ const QString& newName = dialog.newName();
+ if (!newName.isEmpty()) {
+ KUrl newUrl = item.url();
+ newUrl.setFileName(newName);
+ KonqOperations::rename(this, item.url(), newUrl);
+ }
+ }
+ }
+}
+
+void TreeViewSidebarPage::setUrl(const KUrl& url)
+{
+ if (!url.isValid() || (url == SidebarPage::url())) {
+ return;
+ }
+
+ SidebarPage::setUrl(url);
+ if (m_dirLister != 0) {
+ m_setLeafVisible = true;
+ loadTree(url);
+ }
+}
+
+void TreeViewSidebarPage::showEvent(QShowEvent* event)
+{
+ if (event->spontaneous()) {
+ SidebarPage::showEvent(event);
+ return;
+ }
+
+ if (m_dirLister == 0) {
+ // Postpone the creating of the dir lister to the first show event.
+ // This assures that no performance and memory overhead is given when the TreeView is not
+ // used at all (see TreeViewSidebarPage::setUrl()).
+ m_dirLister = new KDirLister();
+ m_dirLister->setDirOnlyMode(true);
+ m_dirLister->setAutoUpdate(true);
+ m_dirLister->setMainWindow(window());
+ m_dirLister->setDelayedMimeTypes(true);
+ m_dirLister->setAutoErrorHandlingEnabled(false, this);
+ m_dirLister->setShowingDotFiles(FoldersPanelSettings::showHiddenFiles());
+
+ Q_ASSERT(m_dolphinModel == 0);
+ m_dolphinModel = new DolphinModel(this);
+ m_dolphinModel->setDirLister(m_dirLister);
+ m_dolphinModel->setDropsAllowed(DolphinModel::DropOnDirectory);
+ connect(m_dolphinModel, SIGNAL(expand(const QModelIndex&)),
+ this, SLOT(expandToDir(const QModelIndex&)));
+
+ Q_ASSERT(m_proxyModel == 0);
+ m_proxyModel = new DolphinSortFilterProxyModel(this);
+ m_proxyModel->setSourceModel(m_dolphinModel);
+
+ Q_ASSERT(m_treeView == 0);
+ m_treeView = new SidebarTreeView(this);
+ m_treeView->setModel(m_proxyModel);
+ m_proxyModel->setSorting(DolphinView::SortByName);
+ m_proxyModel->setSortOrder(Qt::AscendingOrder);
+
+ new FolderExpander(m_treeView, m_proxyModel);
+
+ connect(m_treeView, SIGNAL(clicked(const QModelIndex&)),
+ this, SLOT(updateActiveView(const QModelIndex&)));
+ connect(m_treeView, SIGNAL(urlsDropped(const QModelIndex&, QDropEvent*)),
+ this, SLOT(dropUrls(const QModelIndex&, QDropEvent*)));
+ connect(m_treeView, SIGNAL(pressed(const QModelIndex&)),
+ this, SLOT(updateMouseButtons()));
+
+ QVBoxLayout* layout = new QVBoxLayout(this);
+ layout->setMargin(0);
+ layout->addWidget(m_treeView);
+ }
+
+ loadTree(url());
+ SidebarPage::showEvent(event);
+}
+
+void TreeViewSidebarPage::contextMenuEvent(QContextMenuEvent* event)
+{
+ SidebarPage::contextMenuEvent(event);
+
+ KFileItem item;
+ const QModelIndex index = m_treeView->indexAt(event->pos());
+ if (index.isValid()) {
+ const QModelIndex dolphinModelIndex = m_proxyModel->mapToSource(index);
+ item = m_dolphinModel->itemForIndex(dolphinModelIndex);
+ emit changeSelection(KFileItemList());
+ }
+
+ TreeViewContextMenu contextMenu(this, item);
+ contextMenu.open();
+}
+
+void TreeViewSidebarPage::updateActiveView(const QModelIndex& index)
+{
+ const QModelIndex dirIndex = m_proxyModel->mapToSource(index);
+ const KFileItem item = m_dolphinModel->itemForIndex(dirIndex);
+ if (!item.isNull()) {
+ emit changeUrl(item.url(), m_mouseButtons);
+ }
+}
+
+void TreeViewSidebarPage::dropUrls(const QModelIndex& index, QDropEvent* event)
+{
+ if (index.isValid()) {
+ const QModelIndex dirIndex = m_proxyModel->mapToSource(index);
+ KFileItem item = m_dolphinModel->itemForIndex(dirIndex);
+ Q_ASSERT(!item.isNull());
+ if (item.isDir()) {
+ DragAndDropHelper::instance().dropUrls(item, item.url(), event, this);
+ }
+ }
+}
+
+void TreeViewSidebarPage::expandToDir(const QModelIndex& index)
+{
+ m_treeView->setExpanded(index, true);
+ selectLeafDirectory();
+ m_treeView->resizeColumnToContents(DolphinModel::Name);
+}
+
+void TreeViewSidebarPage::scrollToLeaf()
+{
+ const QModelIndex dirIndex = m_dolphinModel->indexForUrl(m_leafDir);
+ const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex);
+ if (proxyIndex.isValid()) {
+ m_treeView->scrollTo(proxyIndex);
+ }
+}
+
+void TreeViewSidebarPage::updateMouseButtons()
+{
+ m_mouseButtons = QApplication::mouseButtons();
+}
+
+void TreeViewSidebarPage::loadTree(const KUrl& url)
+{
+ Q_ASSERT(m_dirLister != 0);
+ m_leafDir = url;
+
+ KUrl baseUrl;
+ if (url.isLocalFile()) {
+ // use the root directory as base for local URLs (#150941)
+ baseUrl = QDir::rootPath();
+ } else {
+ // clear the path for non-local URLs and use it as base
+ baseUrl = url;
+ baseUrl.setPath(QString('/'));
+ }
+
+ if (m_dirLister->url() != baseUrl) {
+ m_dirLister->stop();
+ m_dirLister->openUrl(baseUrl, KDirLister::Reload);
+ }
+ m_dolphinModel->expandToUrl(m_leafDir);
+}
+
+void TreeViewSidebarPage::selectLeafDirectory()
+{
+ const QModelIndex dirIndex = m_dolphinModel->indexForUrl(m_leafDir);
+ const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex);
+ if (!proxyIndex.isValid()) {
+ return;
+ }
+
+ if (m_setLeafVisible) {
+ // Invoke m_treeView->scrollTo(proxyIndex) asynchronously by
+ // scrollToLeaf(). This assures that the scrolling is done after
+ // the horizontal scrollbar gets visible (otherwise the scrollbar
+ // might hide the leaf).
+ QTimer::singleShot(100, this, SLOT(scrollToLeaf()));
+ m_setLeafVisible = false;
+ }
+
+ QItemSelectionModel* selModel = m_treeView->selectionModel();
+ selModel->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
+}
+
+#include "treeviewsidebarpage.moc"
diff --git a/src/panels/folders/treeviewsidebarpage.h b/src/panels/folders/treeviewsidebarpage.h
new file mode 100644
index 000000000..39f2323ad
--- /dev/null
+++ b/src/panels/folders/treeviewsidebarpage.h
@@ -0,0 +1,135 @@
+/***************************************************************************
+ * Copyright (C) 2006 by Peter Penz <[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 TREEVIEWSIDEBARPAGE_H
+#define TREEVIEWSIDEBARPAGE_H
+
+#include <kurl.h>
+#include <panels/sidebarpage.h>
+
+class KDirLister;
+class DolphinModel;
+
+class DolphinSortFilterProxyModel;
+class SidebarTreeView;
+class QModelIndex;
+
+/**
+ * @brief Shows a tree view of the directories starting from
+ * the currently selected place.
+ *
+ * The tree view is always synchronized with the currently active view
+ * from the main window.
+ */
+class TreeViewSidebarPage : public SidebarPage
+{
+ Q_OBJECT
+
+public:
+ TreeViewSidebarPage(QWidget* parent = 0);
+ virtual ~TreeViewSidebarPage();
+
+ /** @see QWidget::sizeHint() */
+ virtual QSize sizeHint() const;
+
+ void setShowHiddenFiles(bool show);
+ bool showHiddenFiles() const;
+
+ void rename(const KFileItem& item);
+
+signals:
+ /**
+ * Is emitted if the an URL change is requested.
+ */
+ void changeUrl(const KUrl& url, Qt::MouseButtons buttons);
+
+ /**
+ * This signal is emitted when the sidebar requests a change in the
+ * current selection. The file-management view recieving this signal is
+ * not required to select all listed files, limiting the selection to
+ * e.g. the current folder. The new selection will be reported via the
+ * setSelection slot.
+ */
+ void changeSelection(const KFileItemList& selection);
+
+public slots:
+ /**
+ * Changes the current selection inside the tree to \a url.
+ */
+ virtual void setUrl(const KUrl& url);
+
+protected:
+ /** @see QWidget::showEvent() */
+ virtual void showEvent(QShowEvent* event);
+
+ /** @see QWidget::contextMenuEvent() */
+ virtual void contextMenuEvent(QContextMenuEvent* event);
+
+private slots:
+ /**
+ * Updates the active view to the URL
+ * which is given by the item with the index \a index.
+ */
+ void updateActiveView(const QModelIndex& index);
+
+ /**
+ * Is emitted if URLs have been dropped
+ * to the index \a index.
+ */
+ void dropUrls(const QModelIndex& index, QDropEvent* event);
+
+ /**
+ * Expands the treeview to show the directory
+ * specified by \a index.
+ */
+ void expandToDir(const QModelIndex& index);
+
+ /**
+ * Assures that the leaf folder gets visible.
+ */
+ void scrollToLeaf();
+
+ void updateMouseButtons();
+
+private:
+ /**
+ * Initializes the base URL of the tree and expands all
+ * directories until \a url.
+ * @param url URL of the leaf directory that should get expanded.
+ */
+ void loadTree(const KUrl& url);
+
+ /**
+ * Selects the current leaf directory m_leafDir and assures
+ * that the directory is visible if the leaf has been set by
+ * TreeViewSidebarPage::setUrl().
+ */
+ void selectLeafDirectory();
+
+private:
+ bool m_setLeafVisible;
+ Qt::MouseButtons m_mouseButtons;
+ KDirLister* m_dirLister;
+ DolphinModel* m_dolphinModel;
+ DolphinSortFilterProxyModel* m_proxyModel;
+ SidebarTreeView* m_treeView;
+ KUrl m_leafDir;
+};
+
+#endif // TREEVIEWSIDEBARPAGE_H
diff --git a/src/panels/information/commenteditwidget.cpp b/src/panels/information/commenteditwidget.cpp
new file mode 100644
index 000000000..a55adbea4
--- /dev/null
+++ b/src/panels/information/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/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>
+#include <KTextEdit>
+
+
+class CommentEditWidget::Private
+{
+public:
+ Private( CommentEditWidget* parent )
+ : eventLoop( 0 ),
+ q( parent ) {
+ }
+
+ QEventLoop* eventLoop;
+ bool success;
+ KTextEdit* 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 KTextEdit( 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( i18nc( "@action:button", "Save" ) );
+ d->buttonCancel->setText( i18nc( "@action:button", "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->setPlainText( 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/panels/information/commenteditwidget.h b/src/panels/information/commenteditwidget.h
new file mode 100644
index 000000000..18ab8d7b2
--- /dev/null
+++ b/src/panels/information/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/panels/information/commentwidget.cpp b/src/panels/information/commentwidget.cpp
new file mode 100644
index 000000000..586be63aa
--- /dev/null
+++ b/src/panels/information/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\">" + i18nc( "@label", "Add Comment..." ) + "</a>" );
+ }
+ else {
+ label->setText( "<p>" + comment + "<p align=center><a style=\"font-size:small;\" href=\"addComment\">" + i18nc( "@label", "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/panels/information/commentwidget.h b/src/panels/information/commentwidget.h
new file mode 100644
index 000000000..8c588518c
--- /dev/null
+++ b/src/panels/information/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/panels/information/infosidebarpage.cpp b/src/panels/information/infosidebarpage.cpp
new file mode 100644
index 000000000..9eb35bc8d
--- /dev/null
+++ b/src/panels/information/infosidebarpage.cpp
@@ -0,0 +1,587 @@
+/***************************************************************************
+ * Copyright (C) 2006 by Peter Penz <[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 "infosidebarpage.h"
+
+#include <config-nepomuk.h>
+
+#include <kdialog.h>
+#include <kdirnotify.h>
+#include <kfileplacesmodel.h>
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kio/previewjob.h>
+#include <kfileitem.h>
+#include <kglobalsettings.h>
+#include <kfilemetainfo.h>
+#include <kiconeffect.h>
+#include <kseparator.h>
+#include <kiconloader.h>
+
+#include <QEvent>
+#include <QInputDialog>
+#include <QLabel>
+#include <QPainter>
+#include <QPixmap>
+#include <QResizeEvent>
+#include <QTextLayout>
+#include <QTextLine>
+#include <QTimer>
+#include <QVBoxLayout>
+
+#include "settings/dolphinsettings.h"
+#include "metadatawidget.h"
+#include "metatextlabel.h"
+#include "pixmapviewer.h"
+
+InfoSidebarPage::InfoSidebarPage(QWidget* parent) :
+ SidebarPage(parent),
+ m_initialized(false),
+ m_pendingPreview(false),
+ m_infoTimer(0),
+ m_outdatedPreviewTimer(0),
+ m_shownUrl(),
+ m_urlCandidate(),
+ m_fileItem(),
+ m_selection(),
+ m_nameLabel(0),
+ m_preview(0),
+ m_metaDataWidget(0),
+ m_metaTextLabel(0)
+{
+}
+
+InfoSidebarPage::~InfoSidebarPage()
+{
+}
+
+QSize InfoSidebarPage::sizeHint() const
+{
+ QSize size = SidebarPage::sizeHint();
+ size.setWidth(minimumSizeHint().width());
+ return size;
+}
+
+void InfoSidebarPage::setUrl(const KUrl& url)
+{
+ SidebarPage::setUrl(url);
+ if (url.isValid() && !isEqualToShownUrl(url)) {
+ if (isVisible()) {
+ cancelRequest();
+ m_shownUrl = url;
+ showItemInfo();
+ } else {
+ m_shownUrl = url;
+ }
+ }
+}
+
+void InfoSidebarPage::setSelection(const KFileItemList& selection)
+{
+ if (!isVisible()) {
+ return;
+ }
+
+ if ((selection.count() == 0) && (m_selection.count() == 0)) {
+ // The selection has not really changed, only the current index.
+ // QItemSelectionModel emits a signal in this case and it is less
+ // expensive doing the check this way instead of patching
+ // DolphinView::emitSelectionChanged().
+ return;
+ }
+
+ m_selection = selection;
+
+ const int count = selection.count();
+ if (count == 0) {
+ if (!isEqualToShownUrl(url())) {
+ m_shownUrl = url();
+ showItemInfo();
+ }
+ } else {
+ if ((count == 1) && !selection.first().url().isEmpty()) {
+ m_urlCandidate = selection.first().url();
+ }
+ m_infoTimer->start();
+ }
+}
+
+void InfoSidebarPage::requestDelayedItemInfo(const KFileItem& item)
+{
+ if (!isVisible()) {
+ return;
+ }
+
+ cancelRequest();
+
+ m_fileItem = KFileItem();
+ if (item.isNull()) {
+ // The cursor is above the viewport. If files are selected,
+ // show information regarding the selection.
+ if (m_selection.size() > 0) {
+ m_pendingPreview = false;
+ m_infoTimer->start();
+ }
+ } else {
+ const KUrl url = item.url();
+ if (url.isValid() && !isEqualToShownUrl(url)) {
+ m_urlCandidate = item.url();
+ m_fileItem = item;
+ m_infoTimer->start();
+ }
+ }
+}
+
+void InfoSidebarPage::showEvent(QShowEvent* event)
+{
+ SidebarPage::showEvent(event);
+ if (!event->spontaneous()) {
+ if (!m_initialized) {
+ // do a delayed initialization so that no performance
+ // penalty is given when Dolphin is started with a closed
+ // Information Panel
+ init();
+ }
+ showItemInfo();
+ }
+}
+
+void InfoSidebarPage::resizeEvent(QResizeEvent* event)
+{
+ if (isVisible()) {
+ // If the text inside the name label or the info label cannot
+ // get wrapped, then the maximum width of the label is increased
+ // so that the width of the information sidebar gets increased.
+ // To prevent this, the maximum width is adjusted to
+ // the current width of the sidebar.
+ const int maxWidth = event->size().width() - KDialog::spacingHint() * 4;
+ m_nameLabel->setMaximumWidth(maxWidth);
+
+ // try to increase the preview as large as possible
+ m_preview->setSizeHint(QSize(maxWidth, maxWidth));
+ m_urlCandidate = m_shownUrl; // reset the URL candidate if a resizing is done
+ m_infoTimer->start();
+ }
+
+ SidebarPage::resizeEvent(event);
+}
+
+void InfoSidebarPage::showItemInfo()
+{
+ if (!isVisible()) {
+ return;
+ }
+
+ cancelRequest();
+
+ if (showMultipleSelectionInfo()) {
+ KIconLoader iconLoader;
+ QPixmap icon = iconLoader.loadIcon("dialog-information",
+ KIconLoader::NoGroup,
+ KIconLoader::SizeEnormous);
+ m_preview->setPixmap(icon);
+ setNameLabelText(i18ncp("@info", "%1 item selected", "%1 items selected", m_selection.count()));
+ m_shownUrl = KUrl();
+ } else {
+ const KFileItem item = fileItem();
+ const KUrl itemUrl = item.url();
+ if (!applyPlace(itemUrl)) {
+ // try to get a preview pixmap from the item...
+ m_pendingPreview = true;
+
+ // Mark the currently shown preview as outdated. This is done
+ // with a small delay to prevent a flickering when the next preview
+ // can be shown within a short timeframe.
+ m_outdatedPreviewTimer->start();
+
+ KIO::PreviewJob* job = KIO::filePreview(KFileItemList() << item,
+ m_preview->width(),
+ m_preview->height(),
+ 0,
+ 0,
+ false,
+ true);
+
+ connect(job, SIGNAL(gotPreview(const KFileItem&, const QPixmap&)),
+ this, SLOT(showPreview(const KFileItem&, const QPixmap&)));
+ connect(job, SIGNAL(failed(const KFileItem&)),
+ this, SLOT(showIcon(const KFileItem&)));
+
+ setNameLabelText(itemUrl.fileName());
+ }
+ }
+
+ showMetaInfo();
+}
+
+void InfoSidebarPage::slotInfoTimeout()
+{
+ m_shownUrl = m_urlCandidate;
+ showItemInfo();
+}
+
+void InfoSidebarPage::markOutdatedPreview()
+{
+ KIconEffect iconEffect;
+ QPixmap disabledPixmap = iconEffect.apply(m_preview->pixmap(),
+ KIconLoader::Desktop,
+ KIconLoader::DisabledState);
+ m_preview->setPixmap(disabledPixmap);
+}
+
+void InfoSidebarPage::showIcon(const KFileItem& item)
+{
+ m_outdatedPreviewTimer->stop();
+ m_pendingPreview = false;
+ if (!applyPlace(item.url())) {
+ m_preview->setPixmap(item.pixmap(KIconLoader::SizeEnormous));
+ }
+}
+
+void InfoSidebarPage::showPreview(const KFileItem& item,
+ const QPixmap& pixmap)
+{
+ m_outdatedPreviewTimer->stop();
+
+ Q_UNUSED(item);
+ if (m_pendingPreview) {
+ m_preview->setPixmap(pixmap);
+ m_pendingPreview = false;
+ }
+}
+
+void InfoSidebarPage::slotFileRenamed(const QString& source, const QString& dest)
+{
+ if (m_shownUrl == KUrl(source)) {
+ // the currently shown file has been renamed, hence update the item information
+ // for the renamed file
+ KFileItem item(KFileItem::Unknown, KFileItem::Unknown, KUrl(dest));
+ requestDelayedItemInfo(item);
+ }
+}
+
+void InfoSidebarPage::slotFilesAdded(const QString& directory)
+{
+ if (m_shownUrl == KUrl(directory)) {
+ // If the 'trash' icon changes because the trash has been emptied or got filled,
+ // the signal filesAdded("trash:/") will be emitted.
+ KFileItem item(KFileItem::Unknown, KFileItem::Unknown, KUrl(directory));
+ requestDelayedItemInfo(item);
+ }
+}
+
+void InfoSidebarPage::slotFilesChanged(const QStringList& files)
+{
+ foreach (const QString& fileName, files) {
+ if (m_shownUrl == KUrl(fileName)) {
+ showItemInfo();
+ break;
+ }
+ }
+}
+
+void InfoSidebarPage::slotFilesRemoved(const QStringList& files)
+{
+ foreach (const QString& fileName, files) {
+ if (m_shownUrl == KUrl(fileName)) {
+ // the currently shown item has been removed, show
+ // the parent directory as fallback
+ m_shownUrl = url();
+ showItemInfo();
+ break;
+ }
+ }
+}
+
+void InfoSidebarPage::slotEnteredDirectory(const QString& directory)
+{
+ if (m_shownUrl == KUrl(directory)) {
+ KFileItem item(KFileItem::Unknown, KFileItem::Unknown, KUrl(directory));
+ requestDelayedItemInfo(item);
+ }
+}
+
+void InfoSidebarPage::slotLeftDirectory(const QString& directory)
+{
+ if (m_shownUrl == KUrl(directory)) {
+ // The signal 'leftDirectory' is also emitted when a media
+ // has been unmounted. In this case no directory change will be
+ // done in Dolphin, but the Information Panel must be updated to
+ // indicate an invalid directory.
+ m_shownUrl = url();
+ showItemInfo();
+ }
+}
+
+bool InfoSidebarPage::applyPlace(const KUrl& url)
+{
+ KFilePlacesModel* placesModel = DolphinSettings::instance().placesModel();
+ int count = placesModel->rowCount();
+
+ for (int i = 0; i < count; ++i) {
+ QModelIndex index = placesModel->index(i, 0);
+
+ if (url.equals(placesModel->url(index), KUrl::CompareWithoutTrailingSlash)) {
+ setNameLabelText(placesModel->text(index));
+ m_preview->setPixmap(placesModel->icon(index).pixmap(128, 128));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void InfoSidebarPage::cancelRequest()
+{
+ m_infoTimer->stop();
+}
+
+void InfoSidebarPage::showMetaInfo()
+{
+ m_metaTextLabel->clear();
+
+ if (showMultipleSelectionInfo()) {
+ if (m_metaDataWidget != 0) {
+ KUrl::List urls;
+ foreach (const KFileItem& item, m_selection) {
+ urls.append(item.targetUrl());
+ }
+ m_metaDataWidget->setFiles(urls);
+ }
+
+ quint64 totalSize = 0;
+ foreach (const KFileItem& item, m_selection) {
+ // Only count the size of files, not dirs to match what
+ // DolphinViewContainer::selectionStatusBarText() does.
+ if (!item.isDir() && !item.isLink()) {
+ totalSize += item.size();
+ }
+ }
+ m_metaTextLabel->add(i18nc("@label", "Total size:"), KIO::convertSize(totalSize));
+ } else {
+ const KFileItem item = fileItem();
+ if (item.isDir()) {
+ m_metaTextLabel->add(i18nc("@label", "Type:"), i18nc("@label", "Folder"));
+ m_metaTextLabel->add(i18nc("@label", "Modified:"), item.timeString());
+ } else {
+ m_metaTextLabel->add(i18nc("@label", "Type:"), item.mimeComment());
+
+ m_metaTextLabel->add(i18nc("@label", "Size:"), KIO::convertSize(item.size()));
+ m_metaTextLabel->add(i18nc("@label", "Modified:"), item.timeString());
+
+ if (item.isLocalFile()) {
+ // TODO: See convertMetaInfo below, find a way to display only interesting information
+ // in a readable way
+ const KFileMetaInfo::WhatFlags flags = KFileMetaInfo::Fastest |
+ KFileMetaInfo::TechnicalInfo |
+ KFileMetaInfo::ContentInfo;
+ const QString path = item.url().path();
+ const KFileMetaInfo fileMetaInfo(path, QString(), flags);
+ if (fileMetaInfo.isValid()) {
+ const QHash<QString, KFileMetaInfoItem>& items = fileMetaInfo.items();
+ QHash<QString, KFileMetaInfoItem>::const_iterator it = items.constBegin();
+ const QHash<QString, KFileMetaInfoItem>::const_iterator end = items.constEnd();
+ QString labelText;
+ while (it != end) {
+ const KFileMetaInfoItem& metaInfoItem = it.value();
+ const QVariant& value = metaInfoItem.value();
+ if (value.isValid() && convertMetaInfo(metaInfoItem.name(), labelText)) {
+ m_metaTextLabel->add(labelText, value.toString());
+ }
+ ++it;
+ }
+ }
+ }
+ }
+
+ if (m_metaDataWidget != 0) {
+ m_metaDataWidget->setFile(item.targetUrl());
+ }
+ }
+}
+
+bool InfoSidebarPage::convertMetaInfo(const QString& key, QString& text) const
+{
+ struct MetaKey {
+ const char* key;
+ QString text;
+ };
+
+ // sorted list of keys, where its data should be shown
+ static const MetaKey keys[] = {
+ { "http://freedesktop.org/standards/xesam/1.0/core#album", i18nc("@label", "Album:") },
+ { "http://freedesktop.org/standards/xesam/1.0/core#artist", i18nc("@label", "Artist:") },
+ { "http://freedesktop.org/standards/xesam/1.0/core#genre", i18nc("@label", "Genre:") },
+ { "http://freedesktop.org/standards/xesam/1.0/core#height", i18nc("@label", "Height:") },
+ { "http://freedesktop.org/standards/xesam/1.0/core#lineCount", i18nc("@label", "Lines:") },
+ { "http://freedesktop.org/standards/xesam/1.0/core#title", i18nc("@label", "Title:") },
+ { "http://freedesktop.org/standards/xesam/1.0/core#type", i18nc("@label", "Type:") },
+ { "http://freedesktop.org/standards/xesam/1.0/core#trackNumber", i18nc("@label", "Track:") },
+ { "http://freedesktop.org/standards/xesam/1.0/core#width", i18nc("@label", "Width:") }
+ };
+
+ // do a binary search for the key...
+ int top = 0;
+ int bottom = sizeof(keys) / sizeof(MetaKey) - 1;
+ while (top <= bottom) {
+ const int middle = (top + bottom) / 2;
+ const int result = key.compare(keys[middle].key);
+ if (result < 0) {
+ bottom = middle - 1;
+ } else if (result > 0) {
+ top = middle + 1;
+ } else {
+ text = keys[middle].text;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+KFileItem InfoSidebarPage::fileItem() const
+{
+ if (!m_fileItem.isNull()) {
+ return m_fileItem;
+ }
+
+ if (!m_selection.isEmpty()) {
+ Q_ASSERT(m_selection.count() == 1);
+ return m_selection.first();
+ }
+
+ // no item is hovered and no selection has been done: provide
+ // an item for the directory represented by m_shownUrl
+ KFileItem item(KFileItem::Unknown, KFileItem::Unknown, m_shownUrl);
+ item.refresh();
+ return item;
+}
+
+bool InfoSidebarPage::showMultipleSelectionInfo() const
+{
+ return m_fileItem.isNull() && (m_selection.count() > 1);
+}
+
+bool InfoSidebarPage::isEqualToShownUrl(const KUrl& url) const
+{
+ return m_shownUrl.equals(url, KUrl::CompareWithoutTrailingSlash);
+}
+
+void InfoSidebarPage::setNameLabelText(const QString& text)
+{
+ QTextOption textOption;
+ textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
+
+ QTextLayout textLayout(text);
+ textLayout.setFont(m_nameLabel->font());
+ textLayout.setTextOption(textOption);
+
+ QString wrappedText;
+ wrappedText.reserve(text.length());
+
+ // wrap the text to fit into the width of m_nameLabel
+ textLayout.beginLayout();
+ QTextLine line = textLayout.createLine();
+ while (line.isValid()) {
+ line.setLineWidth(m_nameLabel->width());
+ wrappedText += text.mid(line.textStart(), line.textLength());
+
+ line = textLayout.createLine();
+ if (line.isValid()) {
+ wrappedText += QChar::LineSeparator;
+ }
+ }
+ textLayout.endLayout();
+
+ m_nameLabel->setText(wrappedText);
+}
+
+void InfoSidebarPage::init()
+{
+ const int spacing = KDialog::spacingHint();
+
+ m_infoTimer = new QTimer(this);
+ m_infoTimer->setInterval(300);
+ m_infoTimer->setSingleShot(true);
+ connect(m_infoTimer, SIGNAL(timeout()),
+ this, SLOT(slotInfoTimeout()));
+
+ // Initialize timer for disabling an outdated preview with a small
+ // delay. This prevents flickering if the new preview can be generated
+ // within a very small timeframe.
+ m_outdatedPreviewTimer = new QTimer(this);
+ m_outdatedPreviewTimer->setInterval(300);
+ m_outdatedPreviewTimer->setSingleShot(true);
+ connect(m_outdatedPreviewTimer, SIGNAL(timeout()),
+ this, SLOT(markOutdatedPreview()));
+
+ QVBoxLayout* layout = new QVBoxLayout;
+ layout->setSpacing(spacing);
+
+ // name
+ m_nameLabel = new QLabel(this);
+ QFont font = m_nameLabel->font();
+ font.setBold(true);
+ m_nameLabel->setFont(font);
+ m_nameLabel->setAlignment(Qt::AlignHCenter);
+ m_nameLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+
+ // preview
+ m_preview = new PixmapViewer(this);
+ m_preview->setMinimumWidth(KIconLoader::SizeEnormous + KIconLoader::SizeMedium);
+ m_preview->setMinimumHeight(KIconLoader::SizeEnormous);
+
+ if (MetaDataWidget::metaDataAvailable()) {
+ // rating, comment and tags
+ m_metaDataWidget = new MetaDataWidget(this);
+ m_metaDataWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ }
+
+ // general meta text information
+ m_metaTextLabel = new MetaTextLabel(this);
+ m_metaTextLabel->setMinimumWidth(spacing);
+ m_metaTextLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+
+ layout->addWidget(m_nameLabel);
+ layout->addWidget(new KSeparator(this));
+ layout->addWidget(m_preview);
+ layout->addWidget(new KSeparator(this));
+ if (m_metaDataWidget != 0) {
+ layout->addWidget(m_metaDataWidget);
+ layout->addWidget(new KSeparator(this));
+ }
+ layout->addWidget(m_metaTextLabel);
+
+ // ensure that widgets in the information side bar are aligned towards the top
+ layout->addStretch(1);
+ setLayout(layout);
+
+ org::kde::KDirNotify* dirNotify = new org::kde::KDirNotify(QString(), QString(),
+ QDBusConnection::sessionBus(), this);
+ connect(dirNotify, SIGNAL(FileRenamed(QString, QString)), SLOT(slotFileRenamed(QString, QString)));
+ connect(dirNotify, SIGNAL(FilesAdded(QString)), SLOT(slotFilesAdded(QString)));
+ connect(dirNotify, SIGNAL(FilesChanged(QStringList)), SLOT(slotFilesChanged(QStringList)));
+ connect(dirNotify, SIGNAL(FilesRemoved(QStringList)), SLOT(slotFilesRemoved(QStringList)));
+ connect(dirNotify, SIGNAL(enteredDirectory(QString)), SLOT(slotEnteredDirectory(QString)));
+ connect(dirNotify, SIGNAL(leftDirectory(QString)), SLOT(slotLeftDirectory(QString)));
+
+ m_initialized = true;
+}
+
+#include "infosidebarpage.moc"
diff --git a/src/panels/information/infosidebarpage.h b/src/panels/information/infosidebarpage.h
new file mode 100644
index 000000000..879a245ad
--- /dev/null
+++ b/src/panels/information/infosidebarpage.h
@@ -0,0 +1,194 @@
+/***************************************************************************
+ * Copyright (C) 2006 by Peter Penz <[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 INFOSIDEBARPAGE_H
+#define INFOSIDEBARPAGE_H
+
+#include <panels/sidebarpage.h>
+
+#include <QtGui/QPushButton>
+#include <QtGui/QPixmap>
+#include <QtCore/QEvent>
+#include <QtGui/QLabel>
+#include <QtCore/QList>
+
+#include <kurl.h>
+#include <kmimetype.h>
+#include <kdesktopfileactions.h>
+#include <kvbox.h>
+
+class QPixmap;
+class QString;
+class KFileItem;
+class QLabel;
+class PixmapViewer;
+class MetaDataWidget;
+class MetaTextLabel;
+
+/**
+ * @brief Sidebar for showing meta information of one ore more selected items.
+ */
+class InfoSidebarPage : public SidebarPage
+{
+ Q_OBJECT
+
+public:
+ explicit InfoSidebarPage(QWidget* parent = 0);
+ virtual ~InfoSidebarPage();
+
+ /** @see QWidget::sizeHint() */
+ virtual QSize sizeHint() const;
+
+public slots:
+ /** @see SidebarPage::setUrl() */
+ virtual void setUrl(const KUrl& url);
+
+ /**
+ * This is invoked to inform the sidebar that the user has selected a new
+ * set of items.
+ */
+ void setSelection(const KFileItemList& selection);
+
+ /**
+ * Does a delayed request of information for the item \a item.
+ * If within this delay InfoSidebarPage::setUrl() or InfoSidebarPage::setSelection()
+ * are invoked, then the request will be skipped. Requesting a delayed item information
+ * makes sense when hovering items.
+ */
+ void requestDelayedItemInfo(const KFileItem& item);
+
+protected:
+ /** @see QWidget::showEvent() */
+ virtual void showEvent(QShowEvent* event);
+
+ /** @see QWidget::resizeEvent() */
+ virtual void resizeEvent(QResizeEvent* event);
+
+private slots:
+ /**
+ * Shows the information for the item of the URL which has been provided by
+ * InfoSidebarPage::requestItemInfo() and provides default actions.
+ */
+ void showItemInfo();
+
+ /**
+ * Triggered if the request for item information has timed out.
+ * @see InfoSidebarPage::requestDelayedItemInfo()
+ */
+ void slotInfoTimeout();
+
+ /**
+ * Marks the currently shown preview as outdated
+ * by greying the content.
+ */
+ void markOutdatedPreview();
+
+ /**
+ * Is invoked if no preview is available for the item. In this
+ * case the icon will be shown.
+ */
+ void showIcon(const KFileItem& item);
+
+ /**
+ * Is invoked if a preview is available for the item. The preview
+ * \a pixmap is shown inside the info page.
+ */
+ void showPreview(const KFileItem& item, const QPixmap& pixmap);
+
+ void slotFileRenamed(const QString& source, const QString& dest);
+ void slotFilesAdded(const QString& directory);
+ void slotFilesChanged(const QStringList& files);
+ void slotFilesRemoved(const QStringList& files);
+ void slotEnteredDirectory(const QString& directory);
+ void slotLeftDirectory(const QString& directory);
+
+private:
+ /**
+ * Checks whether the an URL is repesented by a place. If yes,
+ * then the place icon and name are shown instead of a preview.
+ * @return True, if the URL represents exactly a place.
+ * @param url The url to check.
+ */
+ bool applyPlace(const KUrl& url);
+
+ /** Assures that any pending item information request is cancelled. */
+ void cancelRequest();
+
+ /**
+ * Shows the meta information for the current shown item inside
+ * a label.
+ */
+ void showMetaInfo();
+
+ /**
+ * Converts the meta key \a key to a readable format into \a text.
+ * Returns true, if the string \a key represents a meta information
+ * that should be shown. If false is returned, \a text is not modified.
+ */
+ bool convertMetaInfo(const QString& key, QString& text) const;
+
+ /**
+ * Returns the item for file where the preview and meta information
+ * should be received, if InfoSidebarPage::showMultipleSelectionInfo()
+ * returns false.
+ */
+ KFileItem fileItem() const;
+
+ /**
+ * Returns true, if the meta information should be shown for
+ * the multiple selected items that are stored in
+ * m_selection. If true is returned, it is assured that
+ * m_selection.count() > 1. If false is returned, the meta
+ * information should be shown for the file
+ * InfosidebarPage::fileUrl();
+ */
+ bool showMultipleSelectionInfo() const;
+
+ /**
+ * Returns true, if \a url is equal to the shown URL m_shownUrl.
+ */
+ bool isEqualToShownUrl(const KUrl& url) const;
+
+ /**
+ * Sets the text for the label \a m_nameLabel and assures that the
+ * text is split in a way that it can be wrapped within the
+ * label width (QLabel::setWordWrap() does not work if the
+ * text represents one extremely long word).
+ */
+ void setNameLabelText(const QString& text);
+
+ void init();
+
+private:
+ bool m_initialized;
+ bool m_pendingPreview;
+ QTimer* m_infoTimer;
+ QTimer* m_outdatedPreviewTimer;
+ KUrl m_shownUrl; // URL that is shown as info
+ KUrl m_urlCandidate; // URL candidate that will replace m_shownURL after a delay
+ KFileItem m_fileItem; // file item for m_shownUrl if available (otherwise null)
+ KFileItemList m_selection;
+
+ QLabel* m_nameLabel;
+ PixmapViewer* m_preview;
+ MetaDataWidget* m_metaDataWidget;
+ MetaTextLabel* m_metaTextLabel;
+};
+
+#endif // INFOSIDEBARPAGE_H
diff --git a/src/panels/information/metadatawidget.cpp b/src/panels/information/metadatawidget.cpp
new file mode 100644
index 000000000..44a4c029f
--- /dev/null
+++ b/src/panels/information/metadatawidget.cpp
@@ -0,0 +1,281 @@
+/***************************************************************************
+ * Copyright (C) 2007 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 "metadatawidget.h"
+
+#include "commentwidget.h"
+
+#include <config-nepomuk.h>
+
+#include <klocale.h>
+#include <KDebug>
+#include <KMessageBox>
+
+#include <QtCore/QEvent>
+#include <QtCore/QMutex>
+#include <QtCore/QMutexLocker>
+#include <QtCore/QThread>
+#include <QtGui/QLabel>
+#include <QtGui/QGridLayout>
+#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 <Soprano/Vocabulary/Xesam>
+#include "resourcetaggingwidget.h"
+#endif
+
+
+bool MetaDataWidget::metaDataAvailable()
+{
+#ifdef HAVE_NEPOMUK
+ return !Nepomuk::ResourceManager::instance()->init();
+#else
+ return false;
+#endif
+}
+
+
+class MetaDataWidget::Private
+{
+public:
+#ifdef HAVE_NEPOMUK
+ void loadComment(const QString& comment);
+
+ CommentWidget* editComment;
+ KRatingWidget* ratingWidget;
+ Nepomuk::ResourceTaggingWidget* tagWidget;
+
+ // shared data between the GUI-thread and
+ // the loader-thread (see LoadFilesThread):
+ QMutex mutex;
+ struct SharedData
+ {
+ int rating;
+ QString comment;
+ QList<Nepomuk::Resource> fileRes;
+ QMap<KUrl, Nepomuk::Resource> files;
+ } sharedData;
+
+ /**
+ * Loads the meta data of files and writes
+ * the result into a shared data pool that
+ * can be used by the widgets in the GUI thread.
+ */
+ class LoadFilesThread : public QThread
+ {
+ public:
+ LoadFilesThread(SharedData* sharedData, QMutex* mutex);
+ void setFiles(const KUrl::List& urls);
+ virtual void run();
+
+ private:
+ SharedData* m_sharedData;
+ QMutex* m_mutex;
+ KUrl::List m_urls;
+ };
+
+ LoadFilesThread* loadFilesThread;
+#endif
+};
+
+#ifdef HAVE_NEPOMUK
+void MetaDataWidget::Private::loadComment(const QString& comment)
+{
+ editComment->setComment( comment );
+}
+
+MetaDataWidget::Private::LoadFilesThread::LoadFilesThread(
+ MetaDataWidget::Private::SharedData* sharedData,
+ QMutex* mutex) :
+ m_sharedData(sharedData),
+ m_mutex(mutex),
+ m_urls()
+{
+}
+
+void MetaDataWidget::Private::LoadFilesThread::setFiles(const KUrl::List& urls)
+{
+ QMutexLocker locker( m_mutex );
+ m_urls = urls;
+}
+
+void MetaDataWidget::Private::LoadFilesThread::run()
+{
+ QMutexLocker locker( m_mutex );
+ const KUrl::List urls = m_urls;
+ locker.unlock();
+
+ bool first = true;
+ QList<Nepomuk::Resource> fileRes;
+ QMap<KUrl, Nepomuk::Resource> files;
+ unsigned int rating = 0;
+ QString comment;
+ Q_FOREACH( const KUrl &url, urls ) {
+ Nepomuk::Resource file( url, Soprano::Vocabulary::Xesam::File() );
+ files.insert( url, file );
+ fileRes.append( file );
+
+ if ( !first && rating != file.rating() ) {
+ rating = 0; // reset rating
+ }
+ else if ( first ) {
+ rating = file.rating();
+ }
+
+ if ( !first && comment != file.description() ) {
+ comment.clear();
+ }
+ else if ( first ) {
+ comment = file.description();
+ }
+ first = false;
+ }
+
+ locker.relock();
+ m_sharedData->rating = rating;
+ m_sharedData->comment = comment;
+ m_sharedData->fileRes = fileRes;
+ m_sharedData->files = files;
+}
+#endif
+
+MetaDataWidget::MetaDataWidget(QWidget* parent) :
+ QWidget(parent)
+{
+#ifdef HAVE_NEPOMUK
+ d = new Private;
+ d->editComment = new CommentWidget(this);
+ d->editComment->setFocusPolicy(Qt::ClickFocus);
+ d->ratingWidget = new KRatingWidget(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(commentChanged(const QString&)), this, SLOT(slotCommentChanged(const QString&)));
+ connect( d->tagWidget, SIGNAL( tagClicked( const Nepomuk::Tag& ) ), this, SLOT( slotTagClicked( const Nepomuk::Tag& ) ) );
+
+ d->sharedData.rating = 0;
+ d->loadFilesThread = new Private::LoadFilesThread(&d->sharedData, &d->mutex);
+ connect(d->loadFilesThread, SIGNAL(finished()), this, SLOT(slotLoadingFinished()));
+
+ QVBoxLayout* lay = new QVBoxLayout(this);
+ lay->setMargin(0);
+ lay->addWidget(d->ratingWidget);
+ lay->addWidget(d->editComment);
+ lay->addWidget( d->tagWidget );
+#else
+ d = 0;
+#endif
+}
+
+
+MetaDataWidget::~MetaDataWidget()
+{
+#ifdef HAVE_NEPOMUK
+ delete d->loadFilesThread;
+#endif
+ delete d;
+}
+
+
+void MetaDataWidget::setFile(const KUrl& url)
+{
+ kDebug() << url;
+ KUrl::List urls;
+ urls.append( url );
+ setFiles( urls );
+}
+
+void MetaDataWidget::setFiles(const KUrl::List& urls)
+{
+#ifdef HAVE_NEPOMUK
+ d->loadFilesThread->setFiles( urls );
+ d->loadFilesThread->start();
+#else
+ Q_UNUSED( urls );
+#endif
+}
+
+
+void MetaDataWidget::slotCommentChanged( const QString& s )
+{
+#ifdef HAVE_NEPOMUK
+ QMutexLocker locker( &d->mutex );
+ Nepomuk::MassUpdateJob* job = Nepomuk::MassUpdateJob::commentResources( d->sharedData.files.values(), s );
+ connect( job, SIGNAL( result( KJob* ) ),
+ this, SLOT( metadataUpdateDone() ) );
+ setEnabled( false ); // no updates during execution
+ job->start();
+#else
+ Q_UNUSED( s );
+#endif
+}
+
+
+void MetaDataWidget::slotRatingChanged(unsigned int rating)
+{
+#ifdef HAVE_NEPOMUK
+ QMutexLocker locker( &d->mutex );
+ Nepomuk::MassUpdateJob* job = Nepomuk::MassUpdateJob::rateResources( d->sharedData.files.values(), rating );
+ connect( job, SIGNAL( result( KJob* ) ),
+ this, SLOT( metadataUpdateDone() ) );
+ setEnabled( false ); // no updates during execution
+ job->start();
+#else
+ Q_UNUSED( rating );
+#endif
+}
+
+
+void MetaDataWidget::metadataUpdateDone()
+{
+ setEnabled( true );
+}
+
+
+bool MetaDataWidget::eventFilter(QObject* obj, QEvent* event)
+{
+ return QWidget::eventFilter(obj, event);
+}
+
+
+void MetaDataWidget::slotTagClicked( const Nepomuk::Tag& tag )
+{
+ Q_UNUSED( tag );
+#ifdef HAVE_NEPOMUK
+ d->tagWidget->showTagPopup( QCursor::pos() );
+#endif
+}
+
+void MetaDataWidget::slotLoadingFinished()
+{
+#ifdef HAVE_NEPOMUK
+ QMutexLocker locker( &d->mutex );
+ d->ratingWidget->setRating( d->sharedData.rating );
+ d->loadComment( d->sharedData.comment );
+ d->tagWidget->setResources( d->sharedData.fileRes );
+#endif
+}
+
+#include "metadatawidget.moc"
diff --git a/src/panels/information/metadatawidget.h b/src/panels/information/metadatawidget.h
new file mode 100644
index 000000000..881c23c42
--- /dev/null
+++ b/src/panels/information/metadatawidget.h
@@ -0,0 +1,72 @@
+/***************************************************************************
+ * Copyright (C) 2007 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 METADATA_WIDGET_H
+#define METADATA_WIDGET_H
+
+#include <QtGui/QWidget>
+
+#include <kurl.h>
+
+namespace Nepomuk {
+ class Tag;
+}
+
+class MetaDataWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ MetaDataWidget(QWidget* parent = 0);
+ virtual ~MetaDataWidget();
+
+ /**
+ * \return true if the KMetaData system could be found and initialized.
+ * false if KMetaData was not available at compile time or if it has not
+ * been initialized properly.
+ */
+ static bool metaDataAvailable();
+
+public Q_SLOTS:
+ void setFile(const KUrl& url);
+ void setFiles(const KUrl::List& urls);
+
+signals:
+ /**
+ * This signal gets emitted if the metadata for the set file was changed on the
+ * outside. NOT IMPLEMENTED YET.
+ */
+ void metaDataChanged();
+
+private Q_SLOTS:
+ void slotCommentChanged(const QString&);
+ void slotRatingChanged(unsigned int rating);
+ void metadataUpdateDone();
+ void slotTagClicked( const Nepomuk::Tag& );
+ void slotLoadingFinished();
+
+protected:
+ bool eventFilter(QObject* obj, QEvent* event);
+
+private:
+ class Private;
+ Private* d;
+};
+
+#endif
diff --git a/src/panels/information/metatextlabel.cpp b/src/panels/information/metatextlabel.cpp
new file mode 100644
index 000000000..66f12db90
--- /dev/null
+++ b/src/panels/information/metatextlabel.cpp
@@ -0,0 +1,135 @@
+/***************************************************************************
+ * Copyright (C) 2008 by Peter Penz <[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 "metatextlabel.h"
+
+#include <kglobalsettings.h>
+#include <klocale.h>
+
+#include <QPainter>
+#include <QTextLayout>
+#include <QTextLine>
+#include <kdebug.h>
+
+MetaTextLabel::MetaTextLabel(QWidget* parent) :
+ QWidget(parent),
+ m_minimumHeight(0),
+ m_metaInfos()
+{
+ setFont(KGlobalSettings::smallestReadableFont());
+}
+
+MetaTextLabel::~MetaTextLabel()
+{
+}
+
+void MetaTextLabel::clear()
+{
+ m_minimumHeight = 0;
+ m_metaInfos.clear();
+ update();
+}
+
+void MetaTextLabel::add(const QString& labelText, const QString& infoText)
+{
+ MetaInfo metaInfo;
+ metaInfo.label = labelText;
+ metaInfo.info = infoText;
+
+ m_metaInfos.append(metaInfo);
+
+ m_minimumHeight += requiredHeight(metaInfo);
+ setMinimumHeight(m_minimumHeight);
+
+ update();
+}
+
+void MetaTextLabel::paintEvent(QPaintEvent* event)
+{
+ QWidget::paintEvent(event);
+
+ QPainter painter(this);
+
+ const QColor infoColor = palette().color(QPalette::Foreground);
+ QColor labelColor = infoColor;
+ labelColor.setAlpha(128);
+
+ int y = 0;
+ const int infoWidth = width() / 2;
+ const int labelWidth = infoWidth - 2 * Spacing;
+ const int infoX = infoWidth;
+ const int maxHeight = fontMetrics().height() * 5;
+
+ QRect boundingRect;
+ foreach (const MetaInfo& metaInfo, m_metaInfos) {
+ // draw label (e. g. "Date:")
+ painter.setPen(labelColor);
+ painter.drawText(0, y, labelWidth, maxHeight,
+ Qt::AlignTop | Qt::AlignRight | Qt::TextWordWrap,
+ metaInfo.label);
+
+ // draw information (e. g. "2008-11-09 20:12")
+ painter.setPen(infoColor);
+ painter.drawText(infoX, y, infoWidth, maxHeight,
+ Qt::AlignTop | Qt::AlignLeft | Qt::TextWordWrap,
+ metaInfo.info,
+ &boundingRect);
+
+ y += boundingRect.height() + Spacing;
+ }
+}
+
+void MetaTextLabel::resizeEvent(QResizeEvent* event)
+{
+ QWidget::resizeEvent(event);
+
+ m_minimumHeight = 0;
+ foreach (const MetaInfo& metaInfo, m_metaInfos) {
+ m_minimumHeight += requiredHeight(metaInfo);
+ }
+ setMinimumHeight(m_minimumHeight);
+}
+
+int MetaTextLabel::requiredHeight(const MetaInfo& metaInfo) const
+{
+ QTextOption textOption;
+ textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
+
+ qreal height = 0;
+ const int leading = fontMetrics().leading();
+ const int availableWidth = width() / 2;
+
+ QTextLayout textLayout(metaInfo.info);
+ textLayout.setFont(font());
+ textLayout.setTextOption(textOption);
+
+ textLayout.beginLayout();
+ QTextLine line = textLayout.createLine();
+ while (line.isValid()) {
+ line.setLineWidth(availableWidth);
+ height += leading;
+ height += line.height();
+ line = textLayout.createLine();
+ }
+ textLayout.endLayout();
+
+ return static_cast<int>(height) + Spacing;
+}
+
+#include "metatextlabel.moc"
diff --git a/src/panels/information/metatextlabel.h b/src/panels/information/metatextlabel.h
new file mode 100644
index 000000000..f70d29d8e
--- /dev/null
+++ b/src/panels/information/metatextlabel.h
@@ -0,0 +1,64 @@
+/***************************************************************************
+ * Copyright (C) 2008 by Peter Penz <[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 METATEXTLABEL_H
+#define METATEXTLABEL_H
+
+#include <QWidget>
+
+/**
+ * @brief Displays general meta in several lines.
+ *
+ * Each line contains a label and the meta information.
+ */
+class MetaTextLabel : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit MetaTextLabel(QWidget* parent = 0);
+ virtual ~MetaTextLabel();
+
+ void clear();
+ void add(const QString& labelText, const QString& infoText);
+
+protected:
+ virtual void paintEvent(QPaintEvent* event);
+ virtual void resizeEvent(QResizeEvent* event);
+
+private:
+ enum { Spacing = 2 };
+
+ struct MetaInfo
+ {
+ QString label;
+ QString info;
+ };
+
+ int m_minimumHeight;
+ QList<MetaInfo> m_metaInfos;
+
+ /**
+ * Returns the required height in pixels for \a metaInfo to
+ * fit into the available width of the widget.
+ */
+ int requiredHeight(const MetaInfo& metaInfo) const;
+};
+
+#endif
diff --git a/src/panels/information/nepomukmassupdatejob.cpp b/src/panels/information/nepomukmassupdatejob.cpp
new file mode 100644
index 000000000..5c883fd4b
--- /dev/null
+++ b/src/panels/information/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( const 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,
+ i18nc("@info:progress", "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/panels/information/nepomukmassupdatejob.h b/src/panels/information/nepomukmassupdatejob.h
new file mode 100644
index 000000000..a19fa5ff9
--- /dev/null
+++ b/src/panels/information/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/panels/information/newtagdialog.cpp b/src/panels/information/newtagdialog.cpp
new file mode 100644
index 000000000..8785d578c
--- /dev/null
+++ b/src/panels/information/newtagdialog.cpp
@@ -0,0 +1,95 @@
+/*
+ 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( i18nc( "@title:window", "Create New Tag" ) );
+ setButtons( Ok|Cancel );
+ enableButtonOk( false );
+
+ setupUi( mainWidget() );
+
+ connect( m_editTagLabel, SIGNAL( textChanged(const QString&) ),
+ this, SLOT( slotLabelChanged(const QString&) ) );
+
+ // TODO: use KGlobal::config() if NewTagDialog will be moved to kdelibs (KDE 4.2?)
+ KConfigGroup group(KSharedConfig::openConfig("dolphinrc"), "NewTagDialog");
+ restoreDialogSize(group);
+}
+
+
+NewTagDialog::~NewTagDialog()
+{
+ // TODO: use KGlobal::config() if NewTagDialog will be moved to kdelibs (KDE 4.2?)
+ KConfigGroup group(KSharedConfig::openConfig("dolphinrc"), "NewTagDialog");
+ saveDialogSize(group, KConfigBase::Persistent);
+}
+
+
+QSize NewTagDialog::sizeHint() const
+{
+ return QSize(400, 256);
+}
+
+void NewTagDialog::slotLabelChanged( const QString& text )
+{
+ enableButtonOk( !text.isEmpty() );
+}
+
+
+Nepomuk::Tag NewTagDialog::createTag( QWidget* parent )
+{
+ NewTagDialog dlg( parent );
+ dlg.m_labelTitle->setText( i18nc( "@title:window", "Create New Tag" ) );
+ dlg.m_labelTitle->setComment( i18nc( "@title:window subtitle to previous message", "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/panels/information/newtagdialog.h b/src/panels/information/newtagdialog.h
new file mode 100644
index 000000000..b11cd1611
--- /dev/null
+++ b/src/panels/information/newtagdialog.h
@@ -0,0 +1,47 @@
+/*
+ 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();
+
+ virtual QSize sizeHint() const;
+
+ 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/panels/information/newtagdialog.ui b/src/panels/information/newtagdialog.ui
new file mode 100644
index 000000000..d9bd666b5
--- /dev/null
+++ b/src/panels/information/newtagdialog.ui
@@ -0,0 +1,110 @@
+<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>
+ <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 comment="@label Tag name">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 comment="@label">Detailed description (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/panels/information/resourcetaggingwidget.cpp b/src/panels/information/resourcetaggingwidget.cpp
new file mode 100644
index 000000000..3a4da3ea9
--- /dev/null
+++ b/src/panels/information/resourcetaggingwidget.cpp
@@ -0,0 +1,193 @@
+/*
+ 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 "nepomukmassupdatejob.h"
+
+#include <QtGui/QVBoxLayout>
+#include <QtGui/QContextMenuEvent>
+#include <QtGui/QCursor>
+#include <QtGui/QLabel>
+#include <QtCore/QSet>
+
+#include <KLocale>
+
+namespace Nepomuk {
+ inline uint qHash( const Tag& res )
+ {
+ return qHash( res.resourceUri().toString() );
+ }
+}
+
+
+class Nepomuk::ResourceTaggingWidget::Private
+{
+public:
+ QList<Nepomuk::Resource> resources;
+
+ TagCloud* resourceTagCloud;
+ TaggingPopup* popup;
+
+ QList<Tag> resourceTags;
+
+ void showTaggingPopup( const QPoint& );
+ void _k_slotShowTaggingPopup();
+ void _k_metadataUpdateDone();
+ static QList<Tag> intersectTags( const QList<Resource>& );
+
+ ResourceTaggingWidget* q;
+};
+
+
+void Nepomuk::ResourceTaggingWidget::Private::showTaggingPopup( const QPoint& pos )
+{
+ popup->showAllTags();
+ resourceTags = intersectTags( resources );
+ Q_FOREACH( const Tag &tag, resourceTags ) {
+ popup->setTagSelected( tag, true );
+ }
+
+ popup->exec( pos );
+
+ MassUpdateJob* job = MassUpdateJob::tagResources( resources, resourceTags );
+ connect( job, SIGNAL( result( KJob* ) ),
+ q, SLOT( _k_metadataUpdateDone() ) );
+ q->setEnabled( false ); // no updates during execution
+ job->start();
+
+ resourceTagCloud->showTags( resourceTags );
+}
+
+
+void Nepomuk::ResourceTaggingWidget::Private::_k_slotShowTaggingPopup()
+{
+ showTaggingPopup( QCursor::pos() );
+}
+
+
+void Nepomuk::ResourceTaggingWidget::Private::_k_metadataUpdateDone()
+{
+ q->setEnabled( true );
+}
+
+
+QList<Nepomuk::Tag> Nepomuk::ResourceTaggingWidget::Private::intersectTags( const QList<Resource>& res )
+{
+ if ( res.count() == 1 ) {
+ return res.first().tags();
+ }
+ else if ( !res.isEmpty() ) {
+ // determine the tags used for all resources
+ QSet<Tag> tags = QSet<Tag>::fromList( res.first().tags() );
+ QList<Resource>::const_iterator it = res.begin();
+ for ( ++it; it != res.end(); ++it ) {
+ tags.intersect( QSet<Tag>::fromList( (*it).tags() ) );
+ }
+ return tags.values();
+ }
+ else {
+ return QList<Tag>();
+ }
+}
+
+
+Nepomuk::ResourceTaggingWidget::ResourceTaggingWidget( QWidget* parent )
+ : QWidget( parent ),
+ d( new Private() )
+{
+ d->q = this;
+
+ QVBoxLayout* layout = new QVBoxLayout( this );
+ layout->setMargin( 0 );
+ d->resourceTagCloud = new TagCloud( this );
+ layout->addWidget( d->resourceTagCloud );
+ QLabel* changeTagsLabel = new QLabel( "<p align=center><a style=\"font-size:small;\" href=\"dummy\">" + i18nc( "@label", "Change Tags..." ) + "</a>", this );
+ connect( changeTagsLabel, SIGNAL( linkActivated( const QString ) ),
+ this, SLOT( _k_slotShowTaggingPopup() ) );
+ layout->addWidget( changeTagsLabel );
+
+ // 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->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 )
+{
+ setResources( QList<Resource>() << res );
+}
+
+
+void Nepomuk::ResourceTaggingWidget::setResources( const QList<Nepomuk::Resource>& resList )
+{
+ d->resources = resList;
+ d->resourceTagCloud->showTags( d->intersectTags( resList ) );
+}
+
+
+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() );
+}
+
+
+void Nepomuk::ResourceTaggingWidget::showTagPopup( const QPoint& pos )
+{
+ d->showTaggingPopup( pos );
+}
+
+#include "resourcetaggingwidget.moc"
diff --git a/src/panels/information/resourcetaggingwidget.h b/src/panels/information/resourcetaggingwidget.h
new file mode 100644
index 000000000..4ddcea881
--- /dev/null
+++ b/src/panels/information/resourcetaggingwidget.h
@@ -0,0 +1,63 @@
+/*
+ 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& );
+ void setResources( const QList<Nepomuk::Resource>& );
+ void showTagPopup( const QPoint& pos );
+
+ 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() )
+ Q_PRIVATE_SLOT( d, void _k_metadataUpdateDone() )
+ };
+}
+
+#endif
diff --git a/src/panels/information/tagcloud.cpp b/src/panels/information/tagcloud.cpp
new file mode 100644
index 000000000..0074d1796
--- /dev/null
+++ b/src/panels/information/tagcloud.cpp
@@ -0,0 +1,1005 @@
+/*
+ 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 = i18nc( "@label", "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
+ KUrl 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( QString::fromAscii( tag.resourceUri().toEncoded() ) ),
+ 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()
+{
+ // - 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 ( !nodes.isEmpty() || newTagButtonEnabled ) {
+ 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 = m_parent->width() ? row.count() * pos.x() / m_parent->width() : 0;
+
+ 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 = m_parent->height() ? rows.count() * pos.y() / m_parent->height() : 0;
+
+ 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 )
+{
+ Q_UNUSED(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( const 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
+{
+ return QFrame::minimumSizeHint();
+ // 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( i18nc( "@label Indicator when no tags defined", "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;
+ int maxW = 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();
+ }
+ maxW = qMax( maxW, rowW );
+ }
+ if ( rowH > 0 ) {
+ h += s_vSpacing + rowH;
+ }
+
+ d->cachedHfwWidth = contentsWidth;
+ d->cachedHfwHeight = h;
+
+ // zooming
+ if ( maxW > contentsWidth ) {
+ d->cachedHfwHeight = d->cachedHfwHeight * contentsWidth / maxW;
+ }
+ }
+
+ 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();
+
+ 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/panels/information/tagcloud.h b/src/panels/information/tagcloud.h
new file mode 100644
index 000000000..9710ca9b7
--- /dev/null
+++ b/src/panels/information/tagcloud.h
@@ -0,0 +1,142 @@
+/*
+ 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 <Soprano/Statement>
+
+class QResizeEvent;
+class QPaintEvent;
+class QMouseEvent;
+class QEvent;
+
+namespace Nepomuk {
+ class 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/panels/information/taggingpopup.cpp b/src/panels/information/taggingpopup.cpp
new file mode 100644
index 000000000..3e59c80d1
--- /dev/null
+++ b/src/panels/information/taggingpopup.cpp
@@ -0,0 +1,147 @@
+/*
+ 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 );
+}
+
diff --git a/src/panels/information/taggingpopup.h b/src/panels/information/taggingpopup.h
new file mode 100644
index 000000000..99cee701c
--- /dev/null
+++ b/src/panels/information/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
diff --git a/src/panels/places/dolphinfileplacesview.cpp b/src/panels/places/dolphinfileplacesview.cpp
new file mode 100644
index 000000000..df3e6fd87
--- /dev/null
+++ b/src/panels/places/dolphinfileplacesview.cpp
@@ -0,0 +1,56 @@
+/***************************************************************************
+ * Copyright (C) 2008 by Peter Penz <[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 "dolphinfileplacesview.h"
+#include "draganddrophelper.h"
+#include <kfileitem.h>
+#include <konq_operations.h>
+
+DolphinFilePlacesView::DolphinFilePlacesView(QWidget* parent) :
+ KFilePlacesView(parent),
+ m_mouseButtons(Qt::NoButton)
+{
+ setDropOnPlaceEnabled(true);
+ connect(this, SIGNAL(urlsDropped(const KUrl&, QDropEvent*, QWidget*)),
+ this, SLOT(slotUrlsDropped(const KUrl&, QDropEvent*, QWidget*)));
+ connect(this, SIGNAL(urlChanged(const KUrl&)),
+ this, SLOT(emitExtendedUrlChangedSignal(const KUrl&)));
+}
+
+DolphinFilePlacesView::~DolphinFilePlacesView()
+{
+}
+
+void DolphinFilePlacesView::mousePressEvent(QMouseEvent* event)
+{
+ m_mouseButtons = event->buttons();
+ KFilePlacesView::mousePressEvent(event);
+}
+
+void DolphinFilePlacesView::slotUrlsDropped(const KUrl& dest, QDropEvent* event, QWidget* parent)
+{
+ DragAndDropHelper::instance().dropUrls(KFileItem(), dest, event, parent);
+}
+
+void DolphinFilePlacesView::emitExtendedUrlChangedSignal(const KUrl& url)
+{
+ emit urlChanged(url, m_mouseButtons);
+}
+
+#include "dolphinfileplacesview.moc"
diff --git a/src/panels/places/dolphinfileplacesview.h b/src/panels/places/dolphinfileplacesview.h
new file mode 100644
index 000000000..46d574606
--- /dev/null
+++ b/src/panels/places/dolphinfileplacesview.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+ * Copyright (C) 2008 by Peter Penz <[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 DOLPHINFILEPLACESVIEW_H
+#define DOLPHINFILEPLACESVIEW_H
+
+#include <kfileplacesview.h>
+
+/**
+ * @brief Combines bookmarks and mounted devices as list which is
+ * embedded as panel.
+ */
+class DolphinFilePlacesView : public KFilePlacesView
+{
+ Q_OBJECT
+
+public:
+ DolphinFilePlacesView(QWidget* parent);
+ virtual ~DolphinFilePlacesView();
+
+signals:
+ void urlChanged(const KUrl& url, Qt::MouseButtons buttons);
+
+protected:
+ virtual void mousePressEvent(QMouseEvent* event);
+
+private slots:
+ void slotUrlsDropped(const KUrl& dest, QDropEvent* event, QWidget* parent);
+ void emitExtendedUrlChangedSignal(const KUrl& url);
+
+private:
+ Qt::MouseButtons m_mouseButtons;
+};
+
+#endif // DOLPHINFILEPLACESVIEW_H
diff --git a/src/panels/sidebarpage.cpp b/src/panels/sidebarpage.cpp
new file mode 100644
index 000000000..594f59f24
--- /dev/null
+++ b/src/panels/sidebarpage.cpp
@@ -0,0 +1,46 @@
+/***************************************************************************
+ * Copyright (C) 2006 by Cvetoslav Ludmiloff <[email protected]> *
+ * Copyright (C) 2006 by Peter Penz <[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 "sidebarpage.h"
+#include <QtGui/QWidget>
+#include <kfileitem.h>
+#include <kurl.h>
+
+SidebarPage::SidebarPage(QWidget* parent) :
+ QWidget(parent),
+ m_url(KUrl())
+{
+}
+
+SidebarPage::~SidebarPage()
+{
+}
+
+const KUrl& SidebarPage::url() const
+{
+ return m_url;
+}
+
+void SidebarPage::setUrl(const KUrl& url)
+{
+ m_url = url;
+}
+
+#include "sidebarpage.moc"
diff --git a/src/panels/sidebarpage.h b/src/panels/sidebarpage.h
new file mode 100644
index 000000000..20a44758e
--- /dev/null
+++ b/src/panels/sidebarpage.h
@@ -0,0 +1,53 @@
+/***************************************************************************
+ * Copyright (C) 2006 by Cvetoslav Ludmiloff <[email protected]> *
+ * Copyright (C) 2006 by Peter Penz <[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 _SIDEBARPAGE_H_
+#define _SIDEBARPAGE_H_
+
+#include <QtGui/QWidget>
+#include <kurl.h>
+#include <kfileitem.h>
+
+/**
+ * @brief Base widget for all pages that can be embedded into the Sidebar.
+ */
+class SidebarPage : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit SidebarPage(QWidget* parent = 0);
+ virtual ~SidebarPage();
+
+ /** Returns the current set URL of the active Dolphin view. */
+ const KUrl& url() const;
+
+public slots:
+ /**
+ * This is invoked every time the folder being displayed in the
+ * active Dolphin view changes.
+ */
+ virtual void setUrl(const KUrl& url);
+
+private:
+ KUrl m_url;
+};
+
+#endif // _SIDEBARPAGE_H_
diff --git a/src/panels/terminal/terminalsidebarpage.cpp b/src/panels/terminal/terminalsidebarpage.cpp
new file mode 100644
index 000000000..4749e7714
--- /dev/null
+++ b/src/panels/terminal/terminalsidebarpage.cpp
@@ -0,0 +1,97 @@
+/***************************************************************************
+ * Copyright (C) 2007 by Peter Penz <[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 "terminalsidebarpage.h"
+
+#include <klibloader.h>
+#include <kde_terminal_interface.h>
+#include <kparts/part.h>
+#include <kshell.h>
+
+#include <QBoxLayout>
+#include <QShowEvent>
+
+TerminalSidebarPage::TerminalSidebarPage(QWidget* parent) :
+ SidebarPage(parent),
+ m_layout(0),
+ m_terminal(0),
+ m_terminalWidget(0)
+{
+ m_layout = new QVBoxLayout(this);
+ m_layout->setMargin(0);
+}
+
+TerminalSidebarPage::~TerminalSidebarPage()
+{
+}
+
+QSize TerminalSidebarPage::sizeHint() const
+{
+ QSize size = SidebarPage::sizeHint();
+ size.setHeight(200);
+ return size;
+}
+
+void TerminalSidebarPage::setUrl(const KUrl& url)
+{
+ if (!url.isValid() || (url == SidebarPage::url())) {
+ return;
+ }
+
+ SidebarPage::setUrl(url);
+ if ((m_terminal != 0) && isVisible() && url.isLocalFile()) {
+ m_terminal->sendInput("cd " + KShell::quoteArg(url.path()) + '\n');
+ }
+}
+
+void TerminalSidebarPage::terminalExited()
+{
+ emit hideTerminalSidebarPage();
+
+ m_terminal = 0;
+}
+
+void TerminalSidebarPage::showEvent(QShowEvent* event)
+{
+ if (event->spontaneous()) {
+ SidebarPage::showEvent(event);
+ return;
+ }
+
+ if (m_terminal == 0) {
+ KPluginFactory* factory = KPluginLoader("libkonsolepart").factory();
+ KParts::ReadOnlyPart* part = factory ? (factory->create<KParts::ReadOnlyPart>(this)) : 0;
+ if (part != 0) {
+ connect(part, SIGNAL(destroyed(QObject*)), this, SLOT(terminalExited()));
+ m_terminalWidget = part->widget();
+ m_layout->addWidget(m_terminalWidget);
+ m_terminal = qobject_cast<TerminalInterface *>(part);
+ m_terminal->showShellInDir(url().path());
+ }
+ }
+ if (m_terminal != 0) {
+ m_terminal->sendInput("cd " + KShell::quoteArg(url().path()) + '\n');
+ m_terminal->sendInput("clear\n");
+ m_terminalWidget->setFocus();
+ }
+
+ SidebarPage::showEvent(event);
+}
+
+#include "terminalsidebarpage.moc"
diff --git a/src/panels/terminal/terminalsidebarpage.h b/src/panels/terminal/terminalsidebarpage.h
new file mode 100644
index 000000000..4e8c40593
--- /dev/null
+++ b/src/panels/terminal/terminalsidebarpage.h
@@ -0,0 +1,62 @@
+/***************************************************************************
+ * Copyright (C) 2007 by Peter Penz <[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 TERMINALSIDEBARPAGE_H
+#define TERMINALSIDEBARPAGE_H
+
+#include <panels/sidebarpage.h>
+
+class TerminalInterface;
+class QVBoxLayout;
+class QWidget;
+
+/**
+ * @brief Shows the terminal which is synchronized with the URL of the
+ * active view.
+ */
+class TerminalSidebarPage : public SidebarPage
+{
+ Q_OBJECT
+
+public:
+ TerminalSidebarPage(QWidget* parent = 0);
+ virtual ~TerminalSidebarPage();
+
+ /** @see QWidget::sizeHint() */
+ virtual QSize sizeHint() const;
+
+public slots:
+ /** @see SidebarPage::setUrl(). */
+ virtual void setUrl(const KUrl& url);
+ void terminalExited();
+
+signals:
+ void hideTerminalSidebarPage();
+
+protected:
+ /** @see QWidget::showEvent() */
+ virtual void showEvent(QShowEvent* event);
+
+private:
+ QVBoxLayout* m_layout;
+ TerminalInterface* m_terminal;
+ QWidget* m_terminalWidget;
+};
+
+#endif // TERMINALSIDEBARPAGE_H