diff options
| author | Peter Penz <[email protected]> | 2009-01-14 19:26:23 +0000 |
|---|---|---|
| committer | Peter Penz <[email protected]> | 2009-01-14 19:26:23 +0000 |
| commit | 307285e9635a4bf584d6e5d7478876b90ef870f0 (patch) | |
| tree | db36cbbdc7996ab86c38be8f96fe83597d350dfb /src/panels | |
| parent | 86d9c40ab71df5b8bd5063251337d5ca0c22380a (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')
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 |
