From d84909dc36acbd91c37de76e793f81dca9b2ace9 Mon Sep 17 00:00:00 2001 From: Peter Penz Date: Thu, 13 Aug 2009 20:48:58 +0000 Subject: * Use the term "version control" instead of "revision control" to be consistent with the naming in kdevplatform. * Renamed VersionControlPlugin to KVersionControlPlugin so that the interface can be moved out of Dolphin. svn path=/trunk/KDE/kdebase/apps/; revision=1011058 --- src/CMakeLists.txt | 4 +- src/dolphincolumnwidget.cpp | 4 +- src/dolphincontextmenu.cpp | 8 +- src/dolphindetailsview.cpp | 8 +- src/dolphinfileitemdelegate.cpp | 24 +-- src/dolphinfileitemdelegate.h | 4 +- src/dolphinmodel.cpp | 50 ++--- src/dolphinmodel.h | 12 +- src/dolphinview.cpp | 22 +-- src/dolphinview.h | 8 +- src/kversioncontrolplugin.cpp | 365 +++++++++++++++++++++++++++++++++++ src/kversioncontrolplugin.h | 233 ++++++++++++++++++++++ src/panels/folders/paneltreeview.cpp | 2 +- src/revisioncontrolobserver.cpp | 326 ------------------------------- src/revisioncontrolobserver.h | 134 ------------- src/revisioncontrolplugin.cpp | 365 ----------------------------------- src/revisioncontrolplugin.h | 233 ---------------------- src/versioncontrolobserver.cpp | 326 +++++++++++++++++++++++++++++++ src/versioncontrolobserver.h | 134 +++++++++++++ 19 files changed, 1131 insertions(+), 1131 deletions(-) create mode 100644 src/kversioncontrolplugin.cpp create mode 100644 src/kversioncontrolplugin.h delete mode 100644 src/revisioncontrolobserver.cpp delete mode 100644 src/revisioncontrolobserver.h delete mode 100644 src/revisioncontrolplugin.cpp delete mode 100644 src/revisioncontrolplugin.h create mode 100644 src/versioncontrolobserver.cpp create mode 100644 src/versioncontrolobserver.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4da9a6628..f22059723 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,9 +33,8 @@ set(dolphinprivate_LIB_SRCS dolphinremoteencoding.cpp draganddrophelper.cpp folderexpander.cpp + kversioncontrolplugin.cpp renamedialog.cpp - revisioncontrolobserver.cpp - revisioncontrolplugin.cpp selectiontoggle.cpp selectionmanager.cpp settings/additionalinfodialog.cpp @@ -49,6 +48,7 @@ set(dolphinprivate_LIB_SRCS tooltips/ktooltipitem.cpp tooltips/kformattedballoontipdelegate.cpp tooltips/tooltipmanager.cpp + versioncontrolobserver.cpp viewproperties.cpp zoomlevelinfo.cpp ) diff --git a/src/dolphincolumnwidget.cpp b/src/dolphincolumnwidget.cpp index afa79e78f..0ad02c71e 100644 --- a/src/dolphincolumnwidget.cpp +++ b/src/dolphincolumnwidget.cpp @@ -30,9 +30,9 @@ #include "dolphin_generalsettings.h" #include "draganddrophelper.h" #include "folderexpander.h" -#include "revisioncontrolobserver.h" #include "selectionmanager.h" #include "tooltips/tooltipmanager.h" +#include "versioncontrolobserver.h" #include #include @@ -156,7 +156,7 @@ DolphinColumnWidget::DolphinColumnWidget(QWidget* parent, connect (folderExpander, SIGNAL(enterDir(const QModelIndex&)), m_view->m_controller, SLOT(triggerItem(const QModelIndex&))); - new RevisionControlObserver(this); + new VersionControlObserver(this); } DolphinColumnWidget::~DolphinColumnWidget() diff --git a/src/dolphincontextmenu.cpp b/src/dolphincontextmenu.cpp index 86b0ff159..ed99a34e7 100644 --- a/src/dolphincontextmenu.cpp +++ b/src/dolphincontextmenu.cpp @@ -223,7 +223,7 @@ void DolphinContextMenu::openItemContextMenu() popup->addSeparator(); } - // insert revision control actions + // insert version control actions addRevisionControlActions(popup); // insert 'Copy To' and 'Move To' sub menus @@ -393,9 +393,9 @@ KFileItemListProperties& DolphinContextMenu::capabilities() void DolphinContextMenu::addRevisionControlActions(KMenu* menu) { const DolphinView* view = m_mainWindow->activeViewContainer()->view(); - const QList revControlActions = view->revisionControlActions(m_selectedItems); - if (!revControlActions.isEmpty()) { - foreach (QAction* action, revControlActions) { + const QList versionControlActions = view->versionControlActions(m_selectedItems); + if (!versionControlActions.isEmpty()) { + foreach (QAction* action, versionControlActions) { menu->addAction(action); } menu->addSeparator(); diff --git a/src/dolphindetailsview.cpp b/src/dolphindetailsview.cpp index 4a9b9038d..8c1e6bf3b 100644 --- a/src/dolphindetailsview.cpp +++ b/src/dolphindetailsview.cpp @@ -563,7 +563,7 @@ void DolphinDetailsView::configureSettings(const QPoint& pos) // add checkbox items for each column QHeaderView* headerView = header(); - for (int i = DolphinModel::Size; i <= DolphinModel::Revision; ++i) { + for (int i = DolphinModel::Size; i <= DolphinModel::Version; ++i) { const int logicalIndex = headerView->logicalIndex(i); const QString text = model()->headerData(i, Qt::Horizontal).toString(); QAction* action = popup.addAction(text); @@ -598,7 +598,7 @@ void DolphinDetailsView::configureSettings(const QPoint& pos) void DolphinDetailsView::updateColumnVisibility() { const KFileItemDelegate::InformationList list = m_controller->dolphinView()->additionalInfo(); - for (int i = DolphinModel::Size; i <= DolphinModel::Revision; ++i) { + for (int i = DolphinModel::Size; i <= DolphinModel::Version; ++i) { const KFileItemDelegate::Information info = infoForColumn(i); const bool hide = !list.contains(info); if (isColumnHidden(i) != hide) { @@ -899,14 +899,14 @@ void DolphinDetailsView::resizeColumns() QHeaderView* headerView = header(); QFontMetrics fontMetrics(viewport()->font()); - int columnWidth[DolphinModel::Revision + 1]; + int columnWidth[DolphinModel::Version + 1]; columnWidth[DolphinModel::Size] = fontMetrics.width("00000 Items"); columnWidth[DolphinModel::ModifiedTime] = fontMetrics.width("0000-00-00 00:00"); columnWidth[DolphinModel::Permissions] = fontMetrics.width("xxxxxxxxxx"); columnWidth[DolphinModel::Owner] = fontMetrics.width("xxxxxxxxxx"); columnWidth[DolphinModel::Group] = fontMetrics.width("xxxxxxxxxx"); columnWidth[DolphinModel::Type] = fontMetrics.width("XXXX Xxxxxxx"); - columnWidth[DolphinModel::Revision] = fontMetrics.width("xxxxxxxx"); + columnWidth[DolphinModel::Version] = fontMetrics.width("xxxxxxxx"); int requiredWidth = 0; for (int i = KDirModel::Size; i <= KDirModel::Type; ++i) { diff --git a/src/dolphinfileitemdelegate.cpp b/src/dolphinfileitemdelegate.cpp index d0c84a6bb..5060dd0d7 100644 --- a/src/dolphinfileitemdelegate.cpp +++ b/src/dolphinfileitemdelegate.cpp @@ -59,15 +59,15 @@ void DolphinFileItemDelegate::paint(QPainter* painter, KFileItemDelegate::paint(painter, option, index); } - if (dolphinModel->hasRevisionData() && isNameColumn) { + if (dolphinModel->hasVersionData() && isNameColumn) { // The currently shown items are under revision control. Show the current revision // state by adding an emblem. const QModelIndex dirIndex = proxyModel->mapToSource(index); - const QModelIndex revisionIndex = dolphinModel->index(dirIndex.row(), DolphinModel::Revision, dirIndex.parent()); + const QModelIndex revisionIndex = dolphinModel->index(dirIndex.row(), DolphinModel::Version, dirIndex.parent()); const QVariant data = dolphinModel->data(revisionIndex, Qt::DecorationRole); - const RevisionControlPlugin::RevisionState state = static_cast(data.toInt()); + const KVersionControlPlugin::VersionState state = static_cast(data.toInt()); - if (state != RevisionControlPlugin::UnversionedRevision) { + if (state != KVersionControlPlugin::UnversionedVersion) { const QRect rect = iconRect(option, index); const QPixmap emblem = emblemForState(state, rect.size()); painter->drawPixmap(rect.x(), rect.y() + rect.height() - emblem.height(), emblem); @@ -105,11 +105,11 @@ void DolphinFileItemDelegate::adjustOptionWidth(QStyleOptionViewItemV4& option, } } -QPixmap DolphinFileItemDelegate::emblemForState(RevisionControlPlugin::RevisionState state, const QSize& size) const +QPixmap DolphinFileItemDelegate::emblemForState(KVersionControlPlugin::VersionState state, const QSize& size) const { // TODO: all icons that are use here will be replaced by revision control emblems provided by the // Oxygen team before KDE 4.4 - Q_ASSERT(state <= RevisionControlPlugin::ConflictingRevision); + Q_ASSERT(state <= KVersionControlPlugin::ConflictingVersion); if ((m_cachedSize != size) || !m_cachedEmblems[state].isNull()) { m_cachedSize = size; @@ -127,14 +127,14 @@ QPixmap DolphinFileItemDelegate::emblemForState(RevisionControlPlugin::RevisionS } const QSize emblemSize(emblemHeight, emblemHeight); - for (int i = 0; i <= RevisionControlPlugin::ConflictingRevision; ++i) { + for (int i = 0; i <= KVersionControlPlugin::ConflictingVersion; ++i) { QString iconName; switch (state) { - case RevisionControlPlugin::NormalRevision: iconName = "dialog-ok-apply"; break; - case RevisionControlPlugin::UpdateRequiredRevision: iconName = "rating"; break; - case RevisionControlPlugin::LocallyModifiedRevision: iconName = "emblem-important"; break; - case RevisionControlPlugin::AddedRevision: iconName = "list-add"; break; - case RevisionControlPlugin::ConflictingRevision: iconName = "application-exit"; break; + case KVersionControlPlugin::NormalVersion: iconName = "dialog-ok-apply"; break; + case KVersionControlPlugin::UpdateRequiredVersion: iconName = "rating"; break; + case KVersionControlPlugin::LocallyModifiedVersion: iconName = "emblem-important"; break; + case KVersionControlPlugin::AddedVersion: iconName = "list-add"; break; + case KVersionControlPlugin::ConflictingVersion: iconName = "application-exit"; break; default: Q_ASSERT(false); break; } diff --git a/src/dolphinfileitemdelegate.h b/src/dolphinfileitemdelegate.h index bd6bfdf07..287136862 100644 --- a/src/dolphinfileitemdelegate.h +++ b/src/dolphinfileitemdelegate.h @@ -66,12 +66,12 @@ private: const DolphinModel* dolphinModel, const QModelIndex& index); - QPixmap emblemForState(RevisionControlPlugin::RevisionState state, const QSize& size) const; + QPixmap emblemForState(KVersionControlPlugin::VersionState state, const QSize& size) const; private: bool m_hasMinimizedNameColumn; mutable QSize m_cachedSize; - mutable QPixmap m_cachedEmblems[RevisionControlPlugin::ConflictingRevision + 1]; + mutable QPixmap m_cachedEmblems[KVersionControlPlugin::ConflictingVersion + 1]; }; inline void DolphinFileItemDelegate::setMinimizedNameColumn(bool minimized) diff --git a/src/dolphinmodel.cpp b/src/dolphinmodel.cpp index 04bf4486e..3b507a895 100644 --- a/src/dolphinmodel.cpp +++ b/src/dolphinmodel.cpp @@ -53,7 +53,7 @@ const char* DolphinModel::m_others = I18N_NOOP2("@title:group Name", "Others"); DolphinModel::DolphinModel(QObject* parent) : KDirModel(parent), - m_hasRevisionData(false), + m_hasVersionData(false), m_revisionHash() { } @@ -64,16 +64,16 @@ DolphinModel::~DolphinModel() bool DolphinModel::setData(const QModelIndex& index, const QVariant& value, int role) { - if ((index.column() == DolphinModel::Revision) && (role == Qt::DecorationRole)) { + if ((index.column() == DolphinModel::Version) && (role == Qt::DecorationRole)) { // TODO: remove data again when items are deleted... const QPersistentModelIndex key = index; - const RevisionControlPlugin::RevisionState state = static_cast(value.toInt()); - if (m_revisionHash.value(key, RevisionControlPlugin::UnversionedRevision) != state) { - if (!m_hasRevisionData) { + const KVersionControlPlugin::VersionState state = static_cast(value.toInt()); + if (m_revisionHash.value(key, KVersionControlPlugin::UnversionedVersion) != state) { + if (!m_hasVersionData) { connect(this, SIGNAL(rowsRemoved (const QModelIndex&, int, int)), this, SLOT(slotRowsRemoved(const QModelIndex&, int, int))); - m_hasRevisionData = true; + m_hasVersionData = true; } m_revisionHash.insert(key, state); @@ -95,27 +95,27 @@ QVariant DolphinModel::data(const QModelIndex& index, int role) const return sortRoleData(index); case Qt::DecorationRole: - if (index.column() == DolphinModel::Revision) { - return m_revisionHash.value(index, RevisionControlPlugin::UnversionedRevision); + if (index.column() == DolphinModel::Version) { + return m_revisionHash.value(index, KVersionControlPlugin::UnversionedVersion); } break; case Qt::DisplayRole: - if (index.column() == DolphinModel::Revision) { - switch (m_revisionHash.value(index, RevisionControlPlugin::UnversionedRevision)) { - case RevisionControlPlugin::NormalRevision: + if (index.column() == DolphinModel::Version) { + switch (m_revisionHash.value(index, KVersionControlPlugin::UnversionedVersion)) { + case KVersionControlPlugin::NormalVersion: return i18nc("@item::intable", "Normal"); - case RevisionControlPlugin::UpdateRequiredRevision: + case KVersionControlPlugin::UpdateRequiredVersion: return i18nc("@item::intable", "Update required"); - case RevisionControlPlugin::LocallyModifiedRevision: + case KVersionControlPlugin::LocallyModifiedVersion: return i18nc("@item::intable", "Locally modified"); - case RevisionControlPlugin::AddedRevision: + case KVersionControlPlugin::AddedVersion: return i18nc("@item::intable", "Added"); - case RevisionControlPlugin::RemovedRevision: + case KVersionControlPlugin::RemovedVersion: return i18nc("@item::intable", "Removed"); - case RevisionControlPlugin::ConflictingRevision: + case KVersionControlPlugin::ConflictingVersion: return i18nc("@item::intable", "Conflicting"); - case RevisionControlPlugin::UnversionedRevision: + case KVersionControlPlugin::UnversionedVersion: default: return i18nc("@item::intable", "Unversioned"); } @@ -136,8 +136,8 @@ QVariant DolphinModel::headerData(int section, Qt::Orientation orientation, int return KDirModel::headerData(section, orientation, role); } - Q_ASSERT(section == DolphinModel::Revision); - return i18nc("@title::column", "Revision"); + Q_ASSERT(section == DolphinModel::Version); + return i18nc("@title::column", "Version"); } return QVariant(); } @@ -147,20 +147,20 @@ int DolphinModel::columnCount(const QModelIndex& parent) const return KDirModel::columnCount(parent) + (ExtraColumnCount - ColumnCount); } -void DolphinModel::clearRevisionData() +void DolphinModel::clearVersionData() { m_revisionHash.clear(); - m_hasRevisionData = false; + m_hasVersionData = false; } -bool DolphinModel::hasRevisionData() const +bool DolphinModel::hasVersionData() const { - return m_hasRevisionData; + return m_hasVersionData; } void DolphinModel::slotRowsRemoved(const QModelIndex& parent, int start, int end) { - if (m_hasRevisionData) { + if (m_hasVersionData) { const int column = parent.column(); for (int row = start; row <= end; ++row) { m_revisionHash.remove(parent.child(row, column)); @@ -369,7 +369,7 @@ QVariant DolphinModel::displayRoleData(const QModelIndex& index) const retString = item.mimeComment(); break; - case DolphinModel::Revision: + case DolphinModel::Version: retString = "test"; break; } diff --git a/src/dolphinmodel.h b/src/dolphinmodel.h index 1fd96c5f6..89a0a761c 100644 --- a/src/dolphinmodel.h +++ b/src/dolphinmodel.h @@ -22,7 +22,7 @@ #define DOLPHINMODEL_H #include -#include +#include #include #include @@ -34,7 +34,7 @@ class LIBDOLPHINPRIVATE_EXPORT DolphinModel : public KDirModel public: enum AdditionalColumns { - Revision = KDirModel::ColumnCount, + Version = KDirModel::ColumnCount, ExtraColumnCount }; @@ -46,8 +46,8 @@ public: virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; virtual int columnCount(const QModelIndex& parent = QModelIndex()) const; - void clearRevisionData(); - bool hasRevisionData() const; + void clearVersionData(); + bool hasVersionData() const; private slots: void slotRowsRemoved(const QModelIndex& parent, int start, int end); @@ -57,8 +57,8 @@ private: QVariant sortRoleData(const QModelIndex& index) const; private: - bool m_hasRevisionData; - QHash m_revisionHash; + bool m_hasVersionData; + QHash m_revisionHash; static const char* m_others; }; diff --git a/src/dolphinview.cpp b/src/dolphinview.cpp index 589940479..aa0bb9619 100644 --- a/src/dolphinview.cpp +++ b/src/dolphinview.cpp @@ -62,9 +62,9 @@ #include "draganddrophelper.h" #include "folderexpander.h" #include "renamedialog.h" -#include "revisioncontrolobserver.h" #include "tooltips/tooltipmanager.h" #include "settings/dolphinsettings.h" +#include "versioncontrolobserver.h" #include "viewproperties.h" #include "zoomlevelinfo.h" @@ -105,7 +105,7 @@ DolphinView::DolphinView(QWidget* parent, m_proxyModel(proxyModel), m_previewGenerator(0), m_toolTipManager(0), - m_revisionControlObserver(0), + m_versionControlObserver(0), m_rootUrl(), m_activeItemUrl(), m_createdItemUrl(), @@ -522,9 +522,9 @@ void DolphinView::updateView(const KUrl& url, const KUrl& rootUrl) loadDirectory(url); } - // When changing the URL there is no need to keep the revision + // When changing the URL there is no need to keep the version // data of the previous URL. - m_dolphinModel->clearRevisionData(); + m_dolphinModel->clearVersionData(); emit startedPathLoading(url); } @@ -613,11 +613,11 @@ QString DolphinView::statusBarText() const return text; } -QList DolphinView::revisionControlActions(const KFileItemList& items) const +QList DolphinView::versionControlActions(const KFileItemList& items) const { return items.isEmpty() - ? m_revisionControlObserver->contextMenuActions(url().path(KUrl::AddTrailingSlash)) - : m_revisionControlObserver->contextMenuActions(items); + ? m_versionControlObserver->contextMenuActions(url().path(KUrl::AddTrailingSlash)) + : m_versionControlObserver->contextMenuActions(items); } void DolphinView::setUrl(const KUrl& url) @@ -1473,12 +1473,12 @@ void DolphinView::createView() m_previewGenerator = new KFilePreviewGenerator(view); m_previewGenerator->setPreviewShown(m_showPreview); - m_revisionControlObserver = new RevisionControlObserver(view); - connect(m_revisionControlObserver, SIGNAL(infoMessage(const QString&)), + m_versionControlObserver = new VersionControlObserver(view); + connect(m_versionControlObserver, SIGNAL(infoMessage(const QString&)), this, SIGNAL(infoMessage(const QString&))); - connect(m_revisionControlObserver, SIGNAL(errorMessage(const QString&)), + connect(m_versionControlObserver, SIGNAL(errorMessage(const QString&)), this, SIGNAL(errorMessage(const QString&))); - connect(m_revisionControlObserver, SIGNAL(operationCompletedMessage(const QString&)), + connect(m_versionControlObserver, SIGNAL(operationCompletedMessage(const QString&)), this, SIGNAL(operationCompletedMessage(const QString&))); if (DolphinSettings::instance().generalSettings()->showToolTips()) { diff --git a/src/dolphinview.h b/src/dolphinview.h index 0aa3c4b74..58924697e 100644 --- a/src/dolphinview.h +++ b/src/dolphinview.h @@ -54,8 +54,8 @@ class KAction; class KActionCollection; class KDirLister; class KUrl; -class RevisionControlObserver; class ToolTipManager; +class VersionControlObserver; class ViewProperties; /** @@ -324,10 +324,10 @@ public: QString statusBarText() const; /** - * Returns the revision control actions that are provided for the items \p items. + * Returns the version control actions that are provided for the items \p items. * Usually the actions are presented in the context menu. */ - QList revisionControlActions(const KFileItemList& items) const; + QList versionControlActions(const KFileItemList& items) const; /** * Updates the state of the 'Additional Information' actions in \a collection. @@ -814,7 +814,7 @@ private: KFilePreviewGenerator* m_previewGenerator; ToolTipManager* m_toolTipManager; - RevisionControlObserver* m_revisionControlObserver; + VersionControlObserver* m_versionControlObserver; KUrl m_rootUrl; KUrl m_activeItemUrl; diff --git a/src/kversioncontrolplugin.cpp b/src/kversioncontrolplugin.cpp new file mode 100644 index 000000000..eb683c17c --- /dev/null +++ b/src/kversioncontrolplugin.cpp @@ -0,0 +1,365 @@ +/*************************************************************************** + * Copyright (C) 2009 by Peter Penz * + * * + * 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 "kversioncontrolplugin.h" + +KVersionControlPlugin::KVersionControlPlugin() +{ +} + +KVersionControlPlugin::~KVersionControlPlugin() +{ +} + +#include "kversioncontrolplugin.moc" + +// ---------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +SubversionPlugin::SubversionPlugin() : + m_versionInfoHash(), + m_versionInfoKeys(), + m_updateAction(0), + m_showLocalChangesAction(0), + m_commitAction(0), + m_addAction(0), + m_removeAction(0), + m_command(), + m_errorMsg(), + m_operationCompletedMsg(), + m_contextDir(), + m_contextItems(), + m_tempFile() +{ + m_updateAction = new KAction(this); + m_updateAction->setIcon(KIcon("view-refresh")); + m_updateAction->setText(i18nc("@item:inmenu", "SVN Update")); + connect(m_updateAction, SIGNAL(triggered()), + this, SLOT(updateFiles())); + + m_showLocalChangesAction = new KAction(this); + m_showLocalChangesAction->setIcon(KIcon("view-split-left-right")); + m_showLocalChangesAction->setText(i18nc("@item:inmenu", "Show Local SVN Changes")); + connect(m_showLocalChangesAction, SIGNAL(triggered()), + this, SLOT(showLocalChanges())); + + m_commitAction = new KAction(this); + m_commitAction->setText(i18nc("@item:inmenu", "SVN Commit...")); + connect(m_commitAction, SIGNAL(triggered()), + this, SLOT(commitFiles())); + + m_addAction = new KAction(this); + m_addAction->setIcon(KIcon("list-add")); + m_addAction->setText(i18nc("@item:inmenu", "SVN Add")); + connect(m_addAction, SIGNAL(triggered()), + this, SLOT(addFiles())); + + m_removeAction = new KAction(this); + m_removeAction->setIcon(KIcon("list-remove")); + m_removeAction->setText(i18nc("@item:inmenu", "SVN Delete")); + connect(m_removeAction, SIGNAL(triggered()), + this, SLOT(removeFiles())); +} + +SubversionPlugin::~SubversionPlugin() +{ +} + +QString SubversionPlugin::fileName() const +{ + return ".svn"; +} + +bool SubversionPlugin::beginRetrieval(const QString& directory) +{ + Q_ASSERT(directory.endsWith('/')); + + QStringList arguments; + arguments << "status" << "--show-updates" << directory; + + QProcess process; + process.start("svn", arguments); + while (process.waitForReadyRead()) { + char buffer[1024]; + while (process.readLine(buffer, sizeof(buffer)) > 0) { + VersionState state = NormalVersion; + QString filePath(buffer); + + switch (buffer[0]) { + case '?': state = UnversionedVersion; break; + case 'M': state = LocallyModifiedVersion; break; + case 'A': state = AddedVersion; break; + case 'D': state = RemovedVersion; break; + case 'C': state = ConflictingVersion; break; + default: + if (filePath.contains('*')) { + state = UpdateRequiredVersion; + } + break; + } + + int pos = filePath.indexOf('/'); + const int length = filePath.length() - pos - 1; + filePath = filePath.mid(pos, length); + if (!filePath.isEmpty()) { + m_versionInfoHash.insert(filePath, state); + } + } + } + + m_versionInfoKeys = m_versionInfoHash.keys(); + return true; +} + +void SubversionPlugin::endRetrieval() +{ +} + +KVersionControlPlugin::VersionState SubversionPlugin::versionState(const KFileItem& item) +{ + const QString itemUrl = item.localPath(); + if (m_versionInfoHash.contains(itemUrl)) { + return m_versionInfoHash.value(itemUrl); + } + + if (!item.isDir()) { + // files that have not been listed by 'svn status' (= m_versionInfoHash) + // are under version control per definition + return NormalVersion; + } + + // The item is a directory. Check whether an item listed by 'svn status' (= m_versionInfoHash) + // is part of this directory. In this case a local modification should be indicated in the + // directory already. + foreach (const QString& key, m_versionInfoKeys) { + if (key.startsWith(itemUrl)) { + const VersionState state = m_versionInfoHash.value(key); + if (state == LocallyModifiedVersion) { + return LocallyModifiedVersion; + } + } + } + + return NormalVersion; +} + +QList SubversionPlugin::contextMenuActions(const KFileItemList& items) +{ + Q_ASSERT(!items.isEmpty()); + foreach (const KFileItem& item, items) { + m_contextItems.append(item); + } + m_contextDir.clear(); + + // iterate all items and check the version state to know which + // actions can be enabled + const int itemsCount = items.count(); + int versionedCount = 0; + int editingCount = 0; + foreach (const KFileItem& item, items) { + const VersionState state = versionState(item); + if (state != UnversionedVersion) { + ++versionedCount; + } + + switch (state) { + case LocallyModifiedVersion: + case ConflictingVersion: + ++editingCount; + break; + default: + break; + } + } + m_commitAction->setEnabled(editingCount > 0); + m_addAction->setEnabled(versionedCount == 0); + m_removeAction->setEnabled(versionedCount == itemsCount); + + QList actions; + actions.append(m_updateAction); + actions.append(m_commitAction); + actions.append(m_addAction); + actions.append(m_removeAction); + return actions; +} + +QList SubversionPlugin::contextMenuActions(const QString& directory) +{ + const bool enabled = m_contextItems.isEmpty(); + if (enabled) { + m_contextDir = directory; + } + + // Only enable the SVN actions if no SVN commands are + // executed currently (see slotOperationCompleted() and + // startSvnCommandProcess()). + m_updateAction->setEnabled(enabled); + m_showLocalChangesAction->setEnabled(enabled); + m_commitAction->setEnabled(enabled); + + QList actions; + actions.append(m_updateAction); + actions.append(m_showLocalChangesAction); + actions.append(m_commitAction); + return actions; +} + +void SubversionPlugin::updateFiles() +{ + execSvnCommand("update", + i18nc("@info:status", "Updating SVN repository..."), + i18nc("@info:status", "Update of SVN repository failed."), + i18nc("@info:status", "Updated SVN repository.")); +} + +void SubversionPlugin::showLocalChanges() +{ + Q_ASSERT(!m_contextDir.isEmpty()); + Q_ASSERT(m_contextItems.isEmpty()); + + const QString command = "mkfifo /tmp/fifo; svn diff " + + KShell::quoteArg(m_contextDir) + + " > /tmp/fifo & kompare /tmp/fifo; rm /tmp/fifo"; + KRun::runCommand(command, 0); +} + +void SubversionPlugin::commitFiles() +{ + KDialog dialog(0, Qt::Dialog); + + KVBox* box = new KVBox(&dialog); + new QLabel(i18nc("@label", "Description:"), box); + QTextEdit* editor = new QTextEdit(box); + + dialog.setMainWidget(box); + dialog.setCaption(i18nc("@title:window", "SVN Commit")); + dialog.setButtons(KDialog::Ok | KDialog::Cancel); + dialog.setDefaultButton(KDialog::Ok); + dialog.setButtonText(KDialog::Ok, i18nc("@action:button", "Commit")); + + KConfigGroup dialogConfig(KSharedConfig::openConfig("dolphinrc"), + "SvnCommitDialog"); + dialog.restoreDialogSize(dialogConfig); + + if (dialog.exec() == QDialog::Accepted) { + // Write the commit description into a temporary file, so + // that it can be read by the command "svn commit -F". The temporary + // file must stay alive until slotOperationCompleted() is invoked and will + // be destroyed when the version plugin is destructed. + if (!m_tempFile.open()) { + emit errorMessage(i18nc("@info:status", "Commit of SVN changes failed.")); + return; + } + + QTextStream out(&m_tempFile); + const QString fileName = m_tempFile.fileName(); + out << editor->toPlainText(); + m_tempFile.close(); + + execSvnCommand("commit -F " + KShell::quoteArg(fileName), + i18nc("@info:status", "Committing SVN changes..."), + i18nc("@info:status", "Commit of SVN changes failed."), + i18nc("@info:status", "Committed SVN changes.")); + } + + dialog.saveDialogSize(dialogConfig, KConfigBase::Persistent); +} + +void SubversionPlugin::addFiles() +{ + execSvnCommand("add", + i18nc("@info:status", "Adding files to SVN repository..."), + i18nc("@info:status", "Adding of files to SVN repository failed."), + i18nc("@info:status", "Added files to SVN repository.")); +} + +void SubversionPlugin::removeFiles() +{ + execSvnCommand("remove", + i18nc("@info:status", "Removing files from SVN repository..."), + i18nc("@info:status", "Removing of files from SVN repository failed."), + i18nc("@info:status", "Removed files from SVN repository.")); +} + +void SubversionPlugin::slotOperationCompleted() +{ + if (m_contextItems.isEmpty()) { + emit operationCompletedMessage(m_operationCompletedMsg); + emit versionStatesChanged(); + } else { + startSvnCommandProcess(); + } +} + +void SubversionPlugin::slotOperationError() +{ + emit errorMessage(m_errorMsg); + + // don't do any operation on other items anymore + m_contextItems.clear(); +} + +void SubversionPlugin::execSvnCommand(const QString& svnCommand, + const QString& infoMsg, + const QString& errorMsg, + const QString& operationCompletedMsg) +{ + emit infoMessage(infoMsg); + + m_command = svnCommand; + m_errorMsg = errorMsg; + m_operationCompletedMsg = operationCompletedMsg; + + startSvnCommandProcess(); +} + +void SubversionPlugin::startSvnCommandProcess() +{ + QProcess* process = new QProcess(this); + connect(process, SIGNAL(finished(int)), + this, SLOT(slotOperationCompleted())); + connect(process, SIGNAL(error(QProcess::ProcessError)), + this, SLOT(slotOperationError())); + + const QString program = "svn " + m_command + ' '; + if (!m_contextDir.isEmpty()) { + process->start(program + KShell::quoteArg(m_contextDir)); + m_contextDir.clear(); + } else { + const KFileItem item = m_contextItems.takeLast(); + process->start(program + KShell::quoteArg(item.localPath())); + // the remaining items of m_contextItems will be executed + // after the process has finished (see slotOperationFinished()) + } +} diff --git a/src/kversioncontrolplugin.h b/src/kversioncontrolplugin.h new file mode 100644 index 000000000..e4f55a1c2 --- /dev/null +++ b/src/kversioncontrolplugin.h @@ -0,0 +1,233 @@ +/*************************************************************************** + * Copyright (C) 2009 by Peter Penz * + * * + * 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 REVISIONCONTROLPLUGIN_H +#define REVISIONCONTROLPLUGIN_H + +#include + +#include +#include +#include + +class KFileItem; +class KFileItemList; +class QAction; + +/** + * @brief Base class for version control plugins. + * + * Enables the file manager to show the version state + * of a versioned file. + */ +class LIBDOLPHINPRIVATE_EXPORT KVersionControlPlugin : public QObject +{ + Q_OBJECT + +public: + enum VersionState + { + /** The file is not under version control. */ + UnversionedVersion, + /** + * The file is under version control and represents + * the latest version. + */ + NormalVersion, + /** + * The file is under version control and a newer + * version exists on the main branch. + */ + UpdateRequiredVersion, + /** + * The file is under version control and has been + * modified locally. + */ + LocallyModifiedVersion, + /** + * The file has not been under version control but + * has been marked to get added with the next commit. + */ + AddedVersion, + /** + * The file is under version control but has been marked + * for getting removed with the next commit. + */ + RemovedVersion, + /** + * The file is under version control and has been locally + * modified. A modification has also been done on the main + * branch. + */ + ConflictingVersion + }; + + KVersionControlPlugin(); + virtual ~KVersionControlPlugin(); + + /** + * Returns the name of the file which stores + * the version control informations. + * (e. g. .svn, .cvs, .git). + */ + virtual QString fileName() const = 0; + + /** + * Is invoked whenever the version control + * information will get retrieved for the directory + * \p directory. It is assured that the directory + * contains a trailing slash. + */ + virtual bool beginRetrieval(const QString& directory) = 0; + + /** + * Is invoked after the version control information has been + * received. It is assured that + * KVersionControlPlugin::beginInfoRetrieval() has been + * invoked before. + */ + virtual void endRetrieval() = 0; + + /** + * Returns the version state for the file \p item. + * It is assured that KVersionControlPlugin::beginInfoRetrieval() has been + * invoked before and that the file is part of the directory specified + * in beginInfoRetrieval(). + */ + virtual VersionState versionState(const KFileItem& item) = 0; + + /** + * Returns the list of actions that should be shown in the context menu + * for the files \p items. It is assured that the passed list is not empty. + * If an action triggers a change of the versions, the signal + * KVersionControlPlugin::versionStatesChanged() must be emitted. + */ + virtual QList contextMenuActions(const KFileItemList& items) = 0; + + /** + * Returns the list of actions that should be shown in the context menu + * for the directory \p directory. If an action triggers a change of the versions, + * the signal KVersionControlPlugin::versionStatesChanged() must be emitted. + */ + virtual QList contextMenuActions(const QString& directory) = 0; + +signals: + /** + * Should be emitted when the version state of files might have been changed + * after the last retrieval (e. g. by executing a context menu action + * of the version control plugin). The file manager will be triggered to + * update the version states of the directory \p directory by invoking + * KVersionControlPlugin::beginRetrieval(), + * KVersionControlPlugin::versionState() and + * KVersionControlPlugin::endRetrieval(). + */ + void versionStatesChanged(); + + /** + * Is emitted if an information message with the content \a msg + * should be shown. + */ + void infoMessage(const QString& msg); + + /** + * Is emitted if an error message with the content \a msg + * should be shown. + */ + void errorMessage(const QString& msg); + + /** + * Is emitted if an "operation completed" message with the content \a msg + * should be shown. + */ + void operationCompletedMessage(const QString& msg); +}; + + + + +// TODO: This is just a temporary test class. It will be made available as +// plugin outside Dolphin later. + +#include +#include +#include + +class LIBDOLPHINPRIVATE_EXPORT SubversionPlugin : public KVersionControlPlugin +{ + Q_OBJECT + +public: + SubversionPlugin(); + virtual ~SubversionPlugin(); + virtual QString fileName() const; + virtual bool beginRetrieval(const QString& directory); + virtual void endRetrieval(); + virtual KVersionControlPlugin::VersionState versionState(const KFileItem& item); + virtual QList contextMenuActions(const KFileItemList& items); + virtual QList contextMenuActions(const QString& directory); + +private slots: + void updateFiles(); + void showLocalChanges(); + void commitFiles(); + void addFiles(); + void removeFiles(); + + void slotOperationCompleted(); + void slotOperationError(); + +private: + /** + * Executes the command "svn {svnCommand}" for the files that have been + * set by getting the context menu actions (see contextMenuActions()). + * @param infoMsg Message that should be shown before the command is executed. + * @param errorMsg Message that should be shown if the execution of the command + * has been failed. + * @param operationCompletedMsg + * Message that should be shown if the execution of the command + * has been completed successfully. + */ + void execSvnCommand(const QString& svnCommand, + const QString& infoMsg, + const QString& errorMsg, + const QString& operationCompletedMsg); + + void startSvnCommandProcess(); + +private: + QHash m_versionInfoHash; + QList m_versionInfoKeys; // cache for accessing the keys of the hash + + QAction* m_updateAction; + QAction* m_showLocalChangesAction; + QAction* m_commitAction; + QAction* m_addAction; + QAction* m_removeAction; + + QString m_command; + QString m_errorMsg; + QString m_operationCompletedMsg; + + QString m_contextDir; + KFileItemList m_contextItems; + + QTemporaryFile m_tempFile; +}; +#endif // REVISIONCONTROLPLUGIN_H + diff --git a/src/panels/folders/paneltreeview.cpp b/src/panels/folders/paneltreeview.cpp index 5d3f8a9b8..a162d54e3 100644 --- a/src/panels/folders/paneltreeview.cpp +++ b/src/panels/folders/paneltreeview.cpp @@ -75,7 +75,7 @@ bool PanelTreeView::event(QEvent* event) hideColumn(DolphinModel::Owner); hideColumn(DolphinModel::Group); hideColumn(DolphinModel::Type); - hideColumn(DolphinModel::Revision); + hideColumn(DolphinModel::Version); header()->hide(); break; diff --git a/src/revisioncontrolobserver.cpp b/src/revisioncontrolobserver.cpp deleted file mode 100644 index 87f0a3aa7..000000000 --- a/src/revisioncontrolobserver.cpp +++ /dev/null @@ -1,326 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009 by Peter Penz * - * * - * 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 "revisioncontrolobserver.h" - -#include "dolphinmodel.h" -#include "revisioncontrolplugin.h" - -#include -#include - -#include -#include -#include -#include - -/** - * The performance of updating the revision state of items depends - * on the used plugin. To prevent that Dolphin gets blocked by a - * slow plugin, the updating is delegated to a thread. - */ -class UpdateItemStatesThread : public QThread -{ -public: - UpdateItemStatesThread(QObject* parent, QMutex* pluginMutex); - void setData(RevisionControlPlugin* plugin, - const QList& itemStates); - QList itemStates() const; - bool retrievedItems() const; - -protected: - virtual void run(); - -private: - bool m_retrievedItems; - RevisionControlPlugin* m_plugin; - QMutex* m_pluginMutex; - QList m_itemStates; -}; - -UpdateItemStatesThread::UpdateItemStatesThread(QObject* parent, QMutex* pluginMutex) : - QThread(parent), - m_retrievedItems(false), - m_pluginMutex(pluginMutex), - m_itemStates() -{ -} - -void UpdateItemStatesThread::setData(RevisionControlPlugin* plugin, - const QList& itemStates) -{ - m_plugin = plugin; - m_itemStates = itemStates; -} - -void UpdateItemStatesThread::run() -{ - Q_ASSERT(!m_itemStates.isEmpty()); - Q_ASSERT(m_plugin != 0); - - // The items from m_itemStates may be located in different directory levels. The revision - // plugin requires the root directory for RevisionControlPlugin::beginRetrieval(). Instead - // of doing an expensive search, we utilize the knowledge of the implementation of - // RevisionControlObserver::addDirectory() to be sure that the last item contains the root. - const QString directory = m_itemStates.last().item.url().directory(KUrl::AppendTrailingSlash); - - QMutexLocker locker(m_pluginMutex); - m_retrievedItems = false; - if (m_plugin->beginRetrieval(directory)) { - const int count = m_itemStates.count(); - for (int i = 0; i < count; ++i) { - m_itemStates[i].revision = m_plugin->revisionState(m_itemStates[i].item); - } - m_plugin->endRetrieval(); - m_retrievedItems = true; - } -} - -QList UpdateItemStatesThread::itemStates() const -{ - return m_itemStates; -} - -bool UpdateItemStatesThread::retrievedItems() const -{ - return m_retrievedItems; -} - -// ------------------------------------------------------------------------------------------------ - -RevisionControlObserver::RevisionControlObserver(QAbstractItemView* view) : - QObject(view), - m_pendingItemStatesUpdate(false), - m_revisionedDirectory(false), - m_silentUpdate(false), - m_view(view), - m_dirLister(0), - m_dolphinModel(0), - m_dirVerificationTimer(0), - m_pluginMutex(QMutex::Recursive), - m_plugin(0), - m_updateItemStatesThread(0) -{ - Q_ASSERT(view != 0); - - QAbstractProxyModel* proxyModel = qobject_cast(view->model()); - m_dolphinModel = (proxyModel == 0) ? - qobject_cast(view->model()) : - qobject_cast(proxyModel->sourceModel()); - if (m_dolphinModel != 0) { - m_dirLister = m_dolphinModel->dirLister(); - connect(m_dirLister, SIGNAL(completed()), - this, SLOT(delayedDirectoryVerification())); - - // The verification timer specifies the timeout until the shown directory - // is checked whether it is versioned. Per default it is assumed that users - // don't iterate through versioned directories and a high timeout is used - // The timeout will be decreased as soon as a versioned directory has been - // found (see verifyDirectory()). - m_dirVerificationTimer = new QTimer(this); - m_dirVerificationTimer->setSingleShot(true); - m_dirVerificationTimer->setInterval(500); - connect(m_dirVerificationTimer, SIGNAL(timeout()), - this, SLOT(verifyDirectory())); - } -} - -RevisionControlObserver::~RevisionControlObserver() -{ - if (m_updateItemStatesThread != 0) { - m_updateItemStatesThread->terminate(); - m_updateItemStatesThread->wait(); - } - delete m_plugin; - m_plugin = 0; -} - -QList RevisionControlObserver::contextMenuActions(const KFileItemList& items) const -{ - if (m_dolphinModel->hasRevisionData() && (m_plugin != 0)) { - QMutexLocker locker(&m_pluginMutex); - return m_plugin->contextMenuActions(items); - } - return QList(); -} - -QList RevisionControlObserver::contextMenuActions(const QString& directory) const -{ - if (m_dolphinModel->hasRevisionData() && (m_plugin != 0)) { - QMutexLocker locker(&m_pluginMutex); - return m_plugin->contextMenuActions(directory); - } - - return QList(); -} - -void RevisionControlObserver::delayedDirectoryVerification() -{ - m_silentUpdate = false; - m_dirVerificationTimer->start(); -} - -void RevisionControlObserver::silentDirectoryVerification() -{ - m_silentUpdate = true; - m_dirVerificationTimer->start(); -} - -void RevisionControlObserver::verifyDirectory() -{ - KUrl revisionControlUrl = m_dirLister->url(); - if (!revisionControlUrl.isLocalFile()) { - return; - } - - if (m_plugin == 0) { - // TODO: just for testing purposes. A plugin approach will be used later. - m_plugin = new SubversionPlugin(); - connect(m_plugin, SIGNAL(infoMessage(const QString&)), - this, SIGNAL(infoMessage(const QString&))); - connect(m_plugin, SIGNAL(errorMessage(const QString&)), - this, SIGNAL(errorMessage(const QString&))); - connect(m_plugin, SIGNAL(operationCompletedMessage(const QString&)), - this, SIGNAL(operationCompletedMessage(const QString&))); - } - - revisionControlUrl.addPath(m_plugin->fileName()); - const KFileItem item = m_dirLister->findByUrl(revisionControlUrl); - - bool foundRevisionInfo = !item.isNull(); - if (!foundRevisionInfo && m_revisionedDirectory) { - // Revision control systems like Git provide the revision information - // file only in the root directory. Check whether the revision information file can - // be found in one of the parent directories. - - // TODO... - } - - if (foundRevisionInfo) { - if (!m_revisionedDirectory) { - m_revisionedDirectory = true; - - // The directory is versioned. Assume that the user will further browse through - // versioned directories and decrease the verification timer. - m_dirVerificationTimer->setInterval(100); - connect(m_dirLister, SIGNAL(refreshItems(const QList>&)), - this, SLOT(delayedDirectoryVerification())); - connect(m_dirLister, SIGNAL(newItems(const KFileItemList&)), - this, SLOT(delayedDirectoryVerification())); - connect(m_plugin, SIGNAL(revisionStatesChanged()), - this, SLOT(silentDirectoryVerification())); - } - updateItemStates(); - } else if (m_revisionedDirectory) { - m_revisionedDirectory = false; - - // The directory is not versioned. Reset the verification timer to a higher - // value, so that browsing through non-versioned directories is not slown down - // by an immediate verification. - m_dirVerificationTimer->setInterval(500); - disconnect(m_dirLister, SIGNAL(refreshItems(const QList>&)), - this, SLOT(delayedDirectoryVerification())); - disconnect(m_dirLister, SIGNAL(newItems(const KFileItemList&)), - this, SLOT(delayedDirectoryVerification())); - disconnect(m_plugin, SIGNAL(revisionStatesChanged()), - this, SLOT(silentDirectoryVerification())); - } -} - -void RevisionControlObserver::applyUpdatedItemStates() -{ - if (!m_updateItemStatesThread->retrievedItems()) { - // ignore m_silentUpdate for an error message - emit errorMessage(i18nc("@info:status", "Update of revision information failed.")); - return; - } - - // QAbstractItemModel::setData() triggers a bottleneck in combination with QListView - // (a detailed description of the root cause is given in the class KFilePreviewGenerator - // from kdelibs). To bypass this bottleneck, the signals of the model are temporary blocked. - // This works as the update of the data does not require a relayout of the views used in Dolphin. - const bool signalsBlocked = m_dolphinModel->signalsBlocked(); - m_dolphinModel->blockSignals(true); - - const QList itemStates = m_updateItemStatesThread->itemStates(); - foreach (const ItemState& itemState, itemStates) { - m_dolphinModel->setData(itemState.index, - QVariant(static_cast(itemState.revision)), - Qt::DecorationRole); - } - - m_dolphinModel->blockSignals(signalsBlocked); - m_view->viewport()->repaint(); - - if (!m_silentUpdate) { - // Using an empty message results in clearing the previously shown information message and showing - // the default status bar information. This is useful as the user already gets feedback that the - // operation has been completed because of the icon emblems. - emit operationCompletedMessage(QString()); - } - - if (m_pendingItemStatesUpdate) { - m_pendingItemStatesUpdate = false; - updateItemStates(); - } -} - -void RevisionControlObserver::updateItemStates() -{ - Q_ASSERT(m_plugin != 0); - if (m_updateItemStatesThread == 0) { - m_updateItemStatesThread = new UpdateItemStatesThread(this, &m_pluginMutex); - connect(m_updateItemStatesThread, SIGNAL(finished()), - this, SLOT(applyUpdatedItemStates())); - } - if (m_updateItemStatesThread->isRunning()) { - // An update is currently ongoing. Wait until the thread has finished - // the update (see applyUpdatedItemStates()). - m_pendingItemStatesUpdate = true; - return; - } - - QList itemStates; - addDirectory(QModelIndex(), itemStates); - if (!itemStates.isEmpty()) { - if (!m_silentUpdate) { - emit infoMessage(i18nc("@info:status", "Updating revision information...")); - } - m_updateItemStatesThread->setData(m_plugin, itemStates); - m_updateItemStatesThread->start(); // applyUpdatedItemStates() is called when finished - } -} - -void RevisionControlObserver::addDirectory(const QModelIndex& parentIndex, QList& itemStates) -{ - const int rowCount = m_dolphinModel->rowCount(parentIndex); - for (int row = 0; row < rowCount; ++row) { - const QModelIndex index = m_dolphinModel->index(row, DolphinModel::Revision, parentIndex); - addDirectory(index, itemStates); - - ItemState itemState; - itemState.index = index; - itemState.item = m_dolphinModel->itemForIndex(index); - itemState.revision = RevisionControlPlugin::UnversionedRevision; - - itemStates.append(itemState); - } -} - -#include "revisioncontrolobserver.moc" diff --git a/src/revisioncontrolobserver.h b/src/revisioncontrolobserver.h deleted file mode 100644 index 2fbd20fb5..000000000 --- a/src/revisioncontrolobserver.h +++ /dev/null @@ -1,134 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009 by Peter Penz * - * * - * 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 REVISIONCONTROLOBSERVER_H -#define REVISIONCONTROLOBSERVER_H - -#include - -#include -#include -#include -#include -#include -#include -#include - -class DolphinModel; -class KDirLister; -class KFileItemList; -class QAbstractItemView; -class QAction; -class QTimer; -class UpdateItemStatesThread; - -/** - * @brief Observes all revision control plugins. - * - * The item view gets updated automatically if the currently shown - * directory is under revision control. - * - * @see RevisionControlPlugin - */ -class LIBDOLPHINPRIVATE_EXPORT RevisionControlObserver : public QObject -{ - Q_OBJECT - -public: - RevisionControlObserver(QAbstractItemView* view); - virtual ~RevisionControlObserver(); - - QList contextMenuActions(const KFileItemList& items) const; - QList contextMenuActions(const QString& directory) const; - -signals: - /** - * Is emitted if an information message with the content \a msg - * should be shown. - */ - void infoMessage(const QString& msg); - - /** - * Is emitted if an error message with the content \a msg - * should be shown. - */ - void errorMessage(const QString& msg); - - /** - * Is emitted if an "operation completed" message with the content \a msg - * should be shown. - */ - void operationCompletedMessage(const QString& msg); - -private slots: - /** - * Invokes verifyDirectory() with a small delay. If delayedDirectoryVerification() - * is invoked before the delay has been exceeded, the delay will be reset. This - * assures that a lot of short requests for directory verification only result - * in one (expensive) call. - */ - void delayedDirectoryVerification(); - - /** - * Invokes verifyDirectory() with a small delay. In opposite to - * delayedDirectoryVerification() it and assures that the verification of - * the directory is done silently without information messages. - */ - void silentDirectoryVerification(); - - void verifyDirectory(); - void applyUpdatedItemStates(); - -private: - struct ItemState - { - QPersistentModelIndex index; - KFileItem item; - RevisionControlPlugin::RevisionState revision; - }; - - void updateItemStates(); - - /** - * Adds recursively all items from the directory \p parentIndex into - * the list \p itemStates. - */ - void addDirectory(const QModelIndex& parentIndex, QList& itemStates); - -private: - bool m_pendingItemStatesUpdate; - bool m_revisionedDirectory; - bool m_silentUpdate; // if true, no messages will be send during the update - // of revision states - - QAbstractItemView* m_view; - KDirLister* m_dirLister; - DolphinModel* m_dolphinModel; - - QTimer* m_dirVerificationTimer; - - mutable QMutex m_pluginMutex; - RevisionControlPlugin* m_plugin; - UpdateItemStatesThread* m_updateItemStatesThread; - - friend class UpdateItemStatesThread; -}; - -#endif // REVISIONCONTROLOBSERVER_H - diff --git a/src/revisioncontrolplugin.cpp b/src/revisioncontrolplugin.cpp deleted file mode 100644 index 683398879..000000000 --- a/src/revisioncontrolplugin.cpp +++ /dev/null @@ -1,365 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009 by Peter Penz * - * * - * 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 "revisioncontrolplugin.h" - -RevisionControlPlugin::RevisionControlPlugin() -{ -} - -RevisionControlPlugin::~RevisionControlPlugin() -{ -} - -#include "revisioncontrolplugin.moc" - -// ---------------------------------------------------------------------------- - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -SubversionPlugin::SubversionPlugin() : - m_revisionInfoHash(), - m_revisionInfoKeys(), - m_updateAction(0), - m_showLocalChangesAction(0), - m_commitAction(0), - m_addAction(0), - m_removeAction(0), - m_command(), - m_errorMsg(), - m_operationCompletedMsg(), - m_contextDir(), - m_contextItems(), - m_tempFile() -{ - m_updateAction = new KAction(this); - m_updateAction->setIcon(KIcon("view-refresh")); - m_updateAction->setText(i18nc("@item:inmenu", "SVN Update")); - connect(m_updateAction, SIGNAL(triggered()), - this, SLOT(updateFiles())); - - m_showLocalChangesAction = new KAction(this); - m_showLocalChangesAction->setIcon(KIcon("view-split-left-right")); - m_showLocalChangesAction->setText(i18nc("@item:inmenu", "Show Local SVN Changes")); - connect(m_showLocalChangesAction, SIGNAL(triggered()), - this, SLOT(showLocalChanges())); - - m_commitAction = new KAction(this); - m_commitAction->setText(i18nc("@item:inmenu", "SVN Commit...")); - connect(m_commitAction, SIGNAL(triggered()), - this, SLOT(commitFiles())); - - m_addAction = new KAction(this); - m_addAction->setIcon(KIcon("list-add")); - m_addAction->setText(i18nc("@item:inmenu", "SVN Add")); - connect(m_addAction, SIGNAL(triggered()), - this, SLOT(addFiles())); - - m_removeAction = new KAction(this); - m_removeAction->setIcon(KIcon("list-remove")); - m_removeAction->setText(i18nc("@item:inmenu", "SVN Delete")); - connect(m_removeAction, SIGNAL(triggered()), - this, SLOT(removeFiles())); -} - -SubversionPlugin::~SubversionPlugin() -{ -} - -QString SubversionPlugin::fileName() const -{ - return ".svn"; -} - -bool SubversionPlugin::beginRetrieval(const QString& directory) -{ - Q_ASSERT(directory.endsWith('/')); - - QStringList arguments; - arguments << "status" << "--show-updates" << directory; - - QProcess process; - process.start("svn", arguments); - while (process.waitForReadyRead()) { - char buffer[1024]; - while (process.readLine(buffer, sizeof(buffer)) > 0) { - RevisionState state = NormalRevision; - QString filePath(buffer); - - switch (buffer[0]) { - case '?': state = UnversionedRevision; break; - case 'M': state = LocallyModifiedRevision; break; - case 'A': state = AddedRevision; break; - case 'D': state = RemovedRevision; break; - case 'C': state = ConflictingRevision; break; - default: - if (filePath.contains('*')) { - state = UpdateRequiredRevision; - } - break; - } - - int pos = filePath.indexOf('/'); - const int length = filePath.length() - pos - 1; - filePath = filePath.mid(pos, length); - if (!filePath.isEmpty()) { - m_revisionInfoHash.insert(filePath, state); - } - } - } - - m_revisionInfoKeys = m_revisionInfoHash.keys(); - return true; -} - -void SubversionPlugin::endRetrieval() -{ -} - -RevisionControlPlugin::RevisionState SubversionPlugin::revisionState(const KFileItem& item) -{ - const QString itemUrl = item.localPath(); - if (m_revisionInfoHash.contains(itemUrl)) { - return m_revisionInfoHash.value(itemUrl); - } - - if (!item.isDir()) { - // files that have not been listed by 'svn status' (= m_revisionInfoHash) - // are under revision control per definition - return NormalRevision; - } - - // The item is a directory. Check whether an item listed by 'svn status' (= m_revisionInfoHash) - // is part of this directory. In this case a local modification should be indicated in the - // directory already. - foreach (const QString& key, m_revisionInfoKeys) { - if (key.startsWith(itemUrl)) { - const RevisionState state = m_revisionInfoHash.value(key); - if (state == LocallyModifiedRevision) { - return LocallyModifiedRevision; - } - } - } - - return NormalRevision; -} - -QList SubversionPlugin::contextMenuActions(const KFileItemList& items) -{ - Q_ASSERT(!items.isEmpty()); - foreach (const KFileItem& item, items) { - m_contextItems.append(item); - } - m_contextDir.clear(); - - // iterate all items and check the revision state to know which - // actions can be enabled - const int itemsCount = items.count(); - int revisionedCount = 0; - int editingCount = 0; - foreach (const KFileItem& item, items) { - const RevisionState state = revisionState(item); - if (state != UnversionedRevision) { - ++revisionedCount; - } - - switch (state) { - case LocallyModifiedRevision: - case ConflictingRevision: - ++editingCount; - break; - default: - break; - } - } - m_commitAction->setEnabled(editingCount > 0); - m_addAction->setEnabled(revisionedCount == 0); - m_removeAction->setEnabled(revisionedCount == itemsCount); - - QList actions; - actions.append(m_updateAction); - actions.append(m_commitAction); - actions.append(m_addAction); - actions.append(m_removeAction); - return actions; -} - -QList SubversionPlugin::contextMenuActions(const QString& directory) -{ - const bool enabled = m_contextItems.isEmpty(); - if (enabled) { - m_contextDir = directory; - } - - // Only enable the SVN actions if no SVN commands are - // executed currently (see slotOperationCompleted() and - // startSvnCommandProcess()). - m_updateAction->setEnabled(enabled); - m_showLocalChangesAction->setEnabled(enabled); - m_commitAction->setEnabled(enabled); - - QList actions; - actions.append(m_updateAction); - actions.append(m_showLocalChangesAction); - actions.append(m_commitAction); - return actions; -} - -void SubversionPlugin::updateFiles() -{ - execSvnCommand("update", - i18nc("@info:status", "Updating SVN repository..."), - i18nc("@info:status", "Update of SVN repository failed."), - i18nc("@info:status", "Updated SVN repository.")); -} - -void SubversionPlugin::showLocalChanges() -{ - Q_ASSERT(!m_contextDir.isEmpty()); - Q_ASSERT(m_contextItems.isEmpty()); - - const QString command = "mkfifo /tmp/fifo; svn diff " + - KShell::quoteArg(m_contextDir) + - " > /tmp/fifo & kompare /tmp/fifo; rm /tmp/fifo"; - KRun::runCommand(command, 0); -} - -void SubversionPlugin::commitFiles() -{ - KDialog dialog(0, Qt::Dialog); - - KVBox* box = new KVBox(&dialog); - new QLabel(i18nc("@label", "Description:"), box); - QTextEdit* editor = new QTextEdit(box); - - dialog.setMainWidget(box); - dialog.setCaption(i18nc("@title:window", "SVN Commit")); - dialog.setButtons(KDialog::Ok | KDialog::Cancel); - dialog.setDefaultButton(KDialog::Ok); - dialog.setButtonText(KDialog::Ok, i18nc("@action:button", "Commit")); - - KConfigGroup dialogConfig(KSharedConfig::openConfig("dolphinrc"), - "SvnCommitDialog"); - dialog.restoreDialogSize(dialogConfig); - - if (dialog.exec() == QDialog::Accepted) { - // Write the commit description into a temporary file, so - // that it can be read by the command "svn commit -F". The temporary - // file must stay alive until slotOperationCompleted() is invoked and will - // be destroyed when the revision plugin is destructed. - if (!m_tempFile.open()) { - emit errorMessage(i18nc("@info:status", "Commit of SVN changes failed.")); - return; - } - - QTextStream out(&m_tempFile); - const QString fileName = m_tempFile.fileName(); - out << editor->toPlainText(); - m_tempFile.close(); - - execSvnCommand("commit -F " + KShell::quoteArg(fileName), - i18nc("@info:status", "Committing SVN changes..."), - i18nc("@info:status", "Commit of SVN changes failed."), - i18nc("@info:status", "Committed SVN changes.")); - } - - dialog.saveDialogSize(dialogConfig, KConfigBase::Persistent); -} - -void SubversionPlugin::addFiles() -{ - execSvnCommand("add", - i18nc("@info:status", "Adding files to SVN repository..."), - i18nc("@info:status", "Adding of files to SVN repository failed."), - i18nc("@info:status", "Added files to SVN repository.")); -} - -void SubversionPlugin::removeFiles() -{ - execSvnCommand("remove", - i18nc("@info:status", "Removing files from SVN repository..."), - i18nc("@info:status", "Removing of files from SVN repository failed."), - i18nc("@info:status", "Removed files from SVN repository.")); -} - -void SubversionPlugin::slotOperationCompleted() -{ - if (m_contextItems.isEmpty()) { - emit operationCompletedMessage(m_operationCompletedMsg); - emit revisionStatesChanged(); - } else { - startSvnCommandProcess(); - } -} - -void SubversionPlugin::slotOperationError() -{ - emit errorMessage(m_errorMsg); - - // don't do any operation on other items anymore - m_contextItems.clear(); -} - -void SubversionPlugin::execSvnCommand(const QString& svnCommand, - const QString& infoMsg, - const QString& errorMsg, - const QString& operationCompletedMsg) -{ - emit infoMessage(infoMsg); - - m_command = svnCommand; - m_errorMsg = errorMsg; - m_operationCompletedMsg = operationCompletedMsg; - - startSvnCommandProcess(); -} - -void SubversionPlugin::startSvnCommandProcess() -{ - QProcess* process = new QProcess(this); - connect(process, SIGNAL(finished(int)), - this, SLOT(slotOperationCompleted())); - connect(process, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(slotOperationError())); - - const QString program = "svn " + m_command + ' '; - if (!m_contextDir.isEmpty()) { - process->start(program + KShell::quoteArg(m_contextDir)); - m_contextDir.clear(); - } else { - const KFileItem item = m_contextItems.takeLast(); - process->start(program + KShell::quoteArg(item.localPath())); - // the remaining items of m_contextItems will be executed - // after the process has finished (see slotOperationFinished()) - } -} diff --git a/src/revisioncontrolplugin.h b/src/revisioncontrolplugin.h deleted file mode 100644 index d9767cf74..000000000 --- a/src/revisioncontrolplugin.h +++ /dev/null @@ -1,233 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009 by Peter Penz * - * * - * 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 REVISIONCONTROLPLUGIN_H -#define REVISIONCONTROLPLUGIN_H - -#include - -#include -#include -#include - -class KFileItem; -class KFileItemList; -class QAction; - -/** - * @brief Base class for revision control plugins. - * - * Enables the file manager to show the revision state - * of a revisioned file. - */ -class LIBDOLPHINPRIVATE_EXPORT RevisionControlPlugin : public QObject -{ - Q_OBJECT - -public: - enum RevisionState - { - /** The file is not under revision control. */ - UnversionedRevision, - /** - * The file is under revision control and represents - * the latest version. - */ - NormalRevision, - /** - * The file is under revision control and a newer - * version exists on the main branch. - */ - UpdateRequiredRevision, - /** - * The file is under revision control and has been - * modified locally. - */ - LocallyModifiedRevision, - /** - * The file has not been under revision control but - * has been marked to get added with the next commit. - */ - AddedRevision, - /** - * The file is under revision control but has been marked - * for getting removed with the next commit. - */ - RemovedRevision, - /** - * The file is under revision control and has been locally - * modified. A modification has also been done on the main - * branch. - */ - ConflictingRevision - }; - - RevisionControlPlugin(); - virtual ~RevisionControlPlugin(); - - /** - * Returns the name of the file which stores - * the revision control informations. - * (e. g. .svn, .cvs, .git). - */ - virtual QString fileName() const = 0; - - /** - * Is invoked whenever the revision control - * information will get retrieved for the directory - * \p directory. It is assured that the directory - * contains a trailing slash. - */ - virtual bool beginRetrieval(const QString& directory) = 0; - - /** - * Is invoked after the revision control information has been - * received. It is assured that - * RevisionControlPlugin::beginInfoRetrieval() has been - * invoked before. - */ - virtual void endRetrieval() = 0; - - /** - * Returns the revision state for the file \p item. - * It is assured that RevisionControlPlugin::beginInfoRetrieval() has been - * invoked before and that the file is part of the directory specified - * in beginInfoRetrieval(). - */ - virtual RevisionState revisionState(const KFileItem& item) = 0; - - /** - * Returns the list of actions that should be shown in the context menu - * for the files \p items. It is assured that the passed list is not empty. - * If an action triggers a change of the revisions, the signal - * RevisionControlPlugin::revisionStatesChanged() must be emitted. - */ - virtual QList contextMenuActions(const KFileItemList& items) = 0; - - /** - * Returns the list of actions that should be shown in the context menu - * for the directory \p directory. If an action triggers a change of the revisions, - * the signal RevisionControlPlugin::revisionStatesChanged() must be emitted. - */ - virtual QList contextMenuActions(const QString& directory) = 0; - -signals: - /** - * Should be emitted when the revision state of files might have been changed - * after the last retrieval (e. g. by executing a context menu action - * of the revision control plugin). The file manager will be triggered to - * update the revision states of the directory \p directory by invoking - * RevisionControlPlugin::beginRetrieval(), - * RevisionControlPlugin::revisionState() and - * RevisionControlPlugin::endRetrieval(). - */ - void revisionStatesChanged(); - - /** - * Is emitted if an information message with the content \a msg - * should be shown. - */ - void infoMessage(const QString& msg); - - /** - * Is emitted if an error message with the content \a msg - * should be shown. - */ - void errorMessage(const QString& msg); - - /** - * Is emitted if an "operation completed" message with the content \a msg - * should be shown. - */ - void operationCompletedMessage(const QString& msg); -}; - - - - -// TODO: This is just a temporary test class. It will be made available as -// plugin outside Dolphin later. - -#include -#include -#include - -class LIBDOLPHINPRIVATE_EXPORT SubversionPlugin : public RevisionControlPlugin -{ - Q_OBJECT - -public: - SubversionPlugin(); - virtual ~SubversionPlugin(); - virtual QString fileName() const; - virtual bool beginRetrieval(const QString& directory); - virtual void endRetrieval(); - virtual RevisionControlPlugin::RevisionState revisionState(const KFileItem& item); - virtual QList contextMenuActions(const KFileItemList& items); - virtual QList contextMenuActions(const QString& directory); - -private slots: - void updateFiles(); - void showLocalChanges(); - void commitFiles(); - void addFiles(); - void removeFiles(); - - void slotOperationCompleted(); - void slotOperationError(); - -private: - /** - * Executes the command "svn {svnCommand}" for the files that have been - * set by getting the context menu actions (see contextMenuActions()). - * @param infoMsg Message that should be shown before the command is executed. - * @param errorMsg Message that should be shown if the execution of the command - * has been failed. - * @param operationCompletedMsg - * Message that should be shown if the execution of the command - * has been completed successfully. - */ - void execSvnCommand(const QString& svnCommand, - const QString& infoMsg, - const QString& errorMsg, - const QString& operationCompletedMsg); - - void startSvnCommandProcess(); - -private: - QHash m_revisionInfoHash; - QList m_revisionInfoKeys; // cache for accessing the keys of the hash - - QAction* m_updateAction; - QAction* m_showLocalChangesAction; - QAction* m_commitAction; - QAction* m_addAction; - QAction* m_removeAction; - - QString m_command; - QString m_errorMsg; - QString m_operationCompletedMsg; - - QString m_contextDir; - KFileItemList m_contextItems; - - QTemporaryFile m_tempFile; -}; -#endif // REVISIONCONTROLPLUGIN_H - diff --git a/src/versioncontrolobserver.cpp b/src/versioncontrolobserver.cpp new file mode 100644 index 000000000..03bba9f02 --- /dev/null +++ b/src/versioncontrolobserver.cpp @@ -0,0 +1,326 @@ +/*************************************************************************** + * Copyright (C) 2009 by Peter Penz * + * * + * 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 "versioncontrolobserver.h" + +#include "dolphinmodel.h" +#include "kversioncontrolplugin.h" + +#include +#include + +#include +#include +#include +#include + +/** + * The performance of updating the version state of items depends + * on the used plugin. To prevent that Dolphin gets blocked by a + * slow plugin, the updating is delegated to a thread. + */ +class UpdateItemStatesThread : public QThread +{ +public: + UpdateItemStatesThread(QObject* parent, QMutex* pluginMutex); + void setData(KVersionControlPlugin* plugin, + const QList& itemStates); + QList itemStates() const; + bool retrievedItems() const; + +protected: + virtual void run(); + +private: + bool m_retrievedItems; + KVersionControlPlugin* m_plugin; + QMutex* m_pluginMutex; + QList m_itemStates; +}; + +UpdateItemStatesThread::UpdateItemStatesThread(QObject* parent, QMutex* pluginMutex) : + QThread(parent), + m_retrievedItems(false), + m_pluginMutex(pluginMutex), + m_itemStates() +{ +} + +void UpdateItemStatesThread::setData(KVersionControlPlugin* plugin, + const QList& itemStates) +{ + m_plugin = plugin; + m_itemStates = itemStates; +} + +void UpdateItemStatesThread::run() +{ + Q_ASSERT(!m_itemStates.isEmpty()); + Q_ASSERT(m_plugin != 0); + + // The items from m_itemStates may be located in different directory levels. The version + // plugin requires the root directory for KVersionControlPlugin::beginRetrieval(). Instead + // of doing an expensive search, we utilize the knowledge of the implementation of + // VersionControlObserver::addDirectory() to be sure that the last item contains the root. + const QString directory = m_itemStates.last().item.url().directory(KUrl::AppendTrailingSlash); + + QMutexLocker locker(m_pluginMutex); + m_retrievedItems = false; + if (m_plugin->beginRetrieval(directory)) { + const int count = m_itemStates.count(); + for (int i = 0; i < count; ++i) { + m_itemStates[i].version = m_plugin->versionState(m_itemStates[i].item); + } + m_plugin->endRetrieval(); + m_retrievedItems = true; + } +} + +QList UpdateItemStatesThread::itemStates() const +{ + return m_itemStates; +} + +bool UpdateItemStatesThread::retrievedItems() const +{ + return m_retrievedItems; +} + +// ------------------------------------------------------------------------------------------------ + +VersionControlObserver::VersionControlObserver(QAbstractItemView* view) : + QObject(view), + m_pendingItemStatesUpdate(false), + m_versionedDirectory(false), + m_silentUpdate(false), + m_view(view), + m_dirLister(0), + m_dolphinModel(0), + m_dirVerificationTimer(0), + m_pluginMutex(QMutex::Recursive), + m_plugin(0), + m_updateItemStatesThread(0) +{ + Q_ASSERT(view != 0); + + QAbstractProxyModel* proxyModel = qobject_cast(view->model()); + m_dolphinModel = (proxyModel == 0) ? + qobject_cast(view->model()) : + qobject_cast(proxyModel->sourceModel()); + if (m_dolphinModel != 0) { + m_dirLister = m_dolphinModel->dirLister(); + connect(m_dirLister, SIGNAL(completed()), + this, SLOT(delayedDirectoryVerification())); + + // The verification timer specifies the timeout until the shown directory + // is checked whether it is versioned. Per default it is assumed that users + // don't iterate through versioned directories and a high timeout is used + // The timeout will be decreased as soon as a versioned directory has been + // found (see verifyDirectory()). + m_dirVerificationTimer = new QTimer(this); + m_dirVerificationTimer->setSingleShot(true); + m_dirVerificationTimer->setInterval(500); + connect(m_dirVerificationTimer, SIGNAL(timeout()), + this, SLOT(verifyDirectory())); + } +} + +VersionControlObserver::~VersionControlObserver() +{ + if (m_updateItemStatesThread != 0) { + m_updateItemStatesThread->terminate(); + m_updateItemStatesThread->wait(); + } + delete m_plugin; + m_plugin = 0; +} + +QList VersionControlObserver::contextMenuActions(const KFileItemList& items) const +{ + if (m_dolphinModel->hasVersionData() && (m_plugin != 0)) { + QMutexLocker locker(&m_pluginMutex); + return m_plugin->contextMenuActions(items); + } + return QList(); +} + +QList VersionControlObserver::contextMenuActions(const QString& directory) const +{ + if (m_dolphinModel->hasVersionData() && (m_plugin != 0)) { + QMutexLocker locker(&m_pluginMutex); + return m_plugin->contextMenuActions(directory); + } + + return QList(); +} + +void VersionControlObserver::delayedDirectoryVerification() +{ + m_silentUpdate = false; + m_dirVerificationTimer->start(); +} + +void VersionControlObserver::silentDirectoryVerification() +{ + m_silentUpdate = true; + m_dirVerificationTimer->start(); +} + +void VersionControlObserver::verifyDirectory() +{ + KUrl versionControlUrl = m_dirLister->url(); + if (!versionControlUrl.isLocalFile()) { + return; + } + + if (m_plugin == 0) { + // TODO: just for testing purposes. A plugin approach will be used later. + m_plugin = new SubversionPlugin(); + connect(m_plugin, SIGNAL(infoMessage(const QString&)), + this, SIGNAL(infoMessage(const QString&))); + connect(m_plugin, SIGNAL(errorMessage(const QString&)), + this, SIGNAL(errorMessage(const QString&))); + connect(m_plugin, SIGNAL(operationCompletedMessage(const QString&)), + this, SIGNAL(operationCompletedMessage(const QString&))); + } + + versionControlUrl.addPath(m_plugin->fileName()); + const KFileItem item = m_dirLister->findByUrl(versionControlUrl); + + bool foundVersionInfo = !item.isNull(); + if (!foundVersionInfo && m_versionedDirectory) { + // Version control systems like Git provide the version information + // file only in the root directory. Check whether the version information file can + // be found in one of the parent directories. + + // TODO... + } + + if (foundVersionInfo) { + if (!m_versionedDirectory) { + m_versionedDirectory = true; + + // The directory is versioned. Assume that the user will further browse through + // versioned directories and decrease the verification timer. + m_dirVerificationTimer->setInterval(100); + connect(m_dirLister, SIGNAL(refreshItems(const QList>&)), + this, SLOT(delayedDirectoryVerification())); + connect(m_dirLister, SIGNAL(newItems(const KFileItemList&)), + this, SLOT(delayedDirectoryVerification())); + connect(m_plugin, SIGNAL(versionStatesChanged()), + this, SLOT(silentDirectoryVerification())); + } + updateItemStates(); + } else if (m_versionedDirectory) { + m_versionedDirectory = false; + + // The directory is not versioned. Reset the verification timer to a higher + // value, so that browsing through non-versioned directories is not slown down + // by an immediate verification. + m_dirVerificationTimer->setInterval(500); + disconnect(m_dirLister, SIGNAL(refreshItems(const QList>&)), + this, SLOT(delayedDirectoryVerification())); + disconnect(m_dirLister, SIGNAL(newItems(const KFileItemList&)), + this, SLOT(delayedDirectoryVerification())); + disconnect(m_plugin, SIGNAL(versionStatesChanged()), + this, SLOT(silentDirectoryVerification())); + } +} + +void VersionControlObserver::applyUpdatedItemStates() +{ + if (!m_updateItemStatesThread->retrievedItems()) { + // ignore m_silentUpdate for an error message + emit errorMessage(i18nc("@info:status", "Update of version information failed.")); + return; + } + + // QAbstractItemModel::setData() triggers a bottleneck in combination with QListView + // (a detailed description of the root cause is given in the class KFilePreviewGenerator + // from kdelibs). To bypass this bottleneck, the signals of the model are temporary blocked. + // This works as the update of the data does not require a relayout of the views used in Dolphin. + const bool signalsBlocked = m_dolphinModel->signalsBlocked(); + m_dolphinModel->blockSignals(true); + + const QList itemStates = m_updateItemStatesThread->itemStates(); + foreach (const ItemState& itemState, itemStates) { + m_dolphinModel->setData(itemState.index, + QVariant(static_cast(itemState.version)), + Qt::DecorationRole); + } + + m_dolphinModel->blockSignals(signalsBlocked); + m_view->viewport()->repaint(); + + if (!m_silentUpdate) { + // Using an empty message results in clearing the previously shown information message and showing + // the default status bar information. This is useful as the user already gets feedback that the + // operation has been completed because of the icon emblems. + emit operationCompletedMessage(QString()); + } + + if (m_pendingItemStatesUpdate) { + m_pendingItemStatesUpdate = false; + updateItemStates(); + } +} + +void VersionControlObserver::updateItemStates() +{ + Q_ASSERT(m_plugin != 0); + if (m_updateItemStatesThread == 0) { + m_updateItemStatesThread = new UpdateItemStatesThread(this, &m_pluginMutex); + connect(m_updateItemStatesThread, SIGNAL(finished()), + this, SLOT(applyUpdatedItemStates())); + } + if (m_updateItemStatesThread->isRunning()) { + // An update is currently ongoing. Wait until the thread has finished + // the update (see applyUpdatedItemStates()). + m_pendingItemStatesUpdate = true; + return; + } + + QList itemStates; + addDirectory(QModelIndex(), itemStates); + if (!itemStates.isEmpty()) { + if (!m_silentUpdate) { + emit infoMessage(i18nc("@info:status", "Updating version information...")); + } + m_updateItemStatesThread->setData(m_plugin, itemStates); + m_updateItemStatesThread->start(); // applyUpdatedItemStates() is called when finished + } +} + +void VersionControlObserver::addDirectory(const QModelIndex& parentIndex, QList& itemStates) +{ + const int rowCount = m_dolphinModel->rowCount(parentIndex); + for (int row = 0; row < rowCount; ++row) { + const QModelIndex index = m_dolphinModel->index(row, DolphinModel::Version, parentIndex); + addDirectory(index, itemStates); + + ItemState itemState; + itemState.index = index; + itemState.item = m_dolphinModel->itemForIndex(index); + itemState.version = KVersionControlPlugin::UnversionedVersion; + + itemStates.append(itemState); + } +} + +#include "versioncontrolobserver.moc" diff --git a/src/versioncontrolobserver.h b/src/versioncontrolobserver.h new file mode 100644 index 000000000..44a0adcad --- /dev/null +++ b/src/versioncontrolobserver.h @@ -0,0 +1,134 @@ +/*************************************************************************** + * Copyright (C) 2009 by Peter Penz * + * * + * 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 REVISIONCONTROLOBSERVER_H +#define REVISIONCONTROLOBSERVER_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +class DolphinModel; +class KDirLister; +class KFileItemList; +class QAbstractItemView; +class QAction; +class QTimer; +class UpdateItemStatesThread; + +/** + * @brief Observes all version control plugins. + * + * The item view gets updated automatically if the currently shown + * directory is under version control. + * + * @see VersionControlPlugin + */ +class LIBDOLPHINPRIVATE_EXPORT VersionControlObserver : public QObject +{ + Q_OBJECT + +public: + VersionControlObserver(QAbstractItemView* view); + virtual ~VersionControlObserver(); + + QList contextMenuActions(const KFileItemList& items) const; + QList contextMenuActions(const QString& directory) const; + +signals: + /** + * Is emitted if an information message with the content \a msg + * should be shown. + */ + void infoMessage(const QString& msg); + + /** + * Is emitted if an error message with the content \a msg + * should be shown. + */ + void errorMessage(const QString& msg); + + /** + * Is emitted if an "operation completed" message with the content \a msg + * should be shown. + */ + void operationCompletedMessage(const QString& msg); + +private slots: + /** + * Invokes verifyDirectory() with a small delay. If delayedDirectoryVerification() + * is invoked before the delay has been exceeded, the delay will be reset. This + * assures that a lot of short requests for directory verification only result + * in one (expensive) call. + */ + void delayedDirectoryVerification(); + + /** + * Invokes verifyDirectory() with a small delay. In opposite to + * delayedDirectoryVerification() it and assures that the verification of + * the directory is done silently without information messages. + */ + void silentDirectoryVerification(); + + void verifyDirectory(); + void applyUpdatedItemStates(); + +private: + struct ItemState + { + QPersistentModelIndex index; + KFileItem item; + KVersionControlPlugin::VersionState version; + }; + + void updateItemStates(); + + /** + * Adds recursively all items from the directory \p parentIndex into + * the list \p itemStates. + */ + void addDirectory(const QModelIndex& parentIndex, QList& itemStates); + +private: + bool m_pendingItemStatesUpdate; + bool m_versionedDirectory; + bool m_silentUpdate; // if true, no messages will be send during the update + // of version states + + QAbstractItemView* m_view; + KDirLister* m_dirLister; + DolphinModel* m_dolphinModel; + + QTimer* m_dirVerificationTimer; + + mutable QMutex m_pluginMutex; + KVersionControlPlugin* m_plugin; + UpdateItemStatesThread* m_updateItemStatesThread; + + friend class UpdateItemStatesThread; +}; + +#endif // REVISIONCONTROLOBSERVER_H + -- cgit v1.3