]> cloud.milkyroute.net Git - dolphin.git/commitdiff
Improved Subversion test plugin to allow committing, updating, diffing, adding and...
authorPeter Penz <peter.penz19@gmail.com>
Mon, 27 Jul 2009 05:31:48 +0000 (05:31 +0000)
committerPeter Penz <peter.penz19@gmail.com>
Mon, 27 Jul 2009 05:31:48 +0000 (05:31 +0000)
svn path=/trunk/KDE/kdebase/apps/; revision=1002839

src/dolphinfileitemdelegate.cpp
src/dolphinfileitemdelegate.h
src/dolphinmodel.cpp
src/dolphinmodel.h
src/dolphinview.cpp
src/revisioncontrolobserver.cpp
src/revisioncontrolobserver.h
src/revisioncontrolplugin.cpp
src/revisioncontrolplugin.h

index 367435cedb603376c8111159090a6f72ed2a0c12..ddd4435a7fa3e178644c57789911669683f20883 100644 (file)
@@ -33,7 +33,9 @@
 
 DolphinFileItemDelegate::DolphinFileItemDelegate(QObject* parent) :
     KFileItemDelegate(parent),
-    m_hasMinimizedNameColumn(false)
+    m_hasMinimizedNameColumn(false),
+    m_cachedSize(),
+    m_cachedEmblems()
 {
 }
 
@@ -65,12 +67,10 @@ void DolphinFileItemDelegate::paint(QPainter* painter,
         const QVariant data = dolphinModel->data(revisionIndex, Qt::DecorationRole);
         const RevisionControlPlugin::RevisionState state = static_cast<RevisionControlPlugin::RevisionState>(data.toInt());
 
-        if (state != RevisionControlPlugin::LocalRevision) {
-            // TODO: extend KFileItemDelegate to be able to get the icon boundaries
-            const QRect iconRect(option.rect.x(), option.rect.y(),
-                                 KIconLoader::SizeSmall, KIconLoader::SizeSmall);
-            const QPixmap emblem = emblemForState(state, iconRect.size());
-            painter->drawPixmap(iconRect.x(), iconRect.y(), emblem);
+        if (state != RevisionControlPlugin::UnversionedRevision) {
+            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,23 +105,42 @@ void DolphinFileItemDelegate::adjustOptionWidth(QStyleOptionViewItemV4& option,
     }
 }
 
-QPixmap DolphinFileItemDelegate::emblemForState(RevisionControlPlugin::RevisionState state, const QSize& size)
+QPixmap DolphinFileItemDelegate::emblemForState(RevisionControlPlugin::RevisionState state, const QSize& size) const
 {
-    // TODO #1: all icons that are use here will be replaced by revision control emblems provided by the
+    // TODO: all icons that are use here will be replaced by revision control emblems provided by the
     // Oxygen team before KDE 4.4
-    // TODO #2: cache the icons
-    switch (state) {
-    case RevisionControlPlugin::LatestRevision:
-        return KIcon("dialog-ok-apply").pixmap(size);
-    case RevisionControlPlugin::ConflictingRevision:
-        return KIcon("application-exit").pixmap(size);
-    case RevisionControlPlugin::UpdateRequiredRevision:
-        return KIcon("rating").pixmap(size);
-    case RevisionControlPlugin::EditingRevision:
-        return KIcon("emblem-important").pixmap(size);
-    default:
-        break;
+    Q_ASSERT(state <= RevisionControlPlugin::ConflictingRevision);
+    if ((m_cachedSize != size) || !m_cachedEmblems[state].isNull()) {
+        m_cachedSize = size;
+
+        const int iconHeight = size.height();
+        int emblemHeight = KIconLoader::SizeSmall;
+        if (iconHeight >= KIconLoader::SizeEnormous) {
+            emblemHeight = KIconLoader::SizeMedium;
+        } else if (iconHeight >= KIconLoader::SizeLarge) {
+            emblemHeight = KIconLoader::SizeSmallMedium;
+        } else if (iconHeight >= KIconLoader::SizeMedium) {
+            emblemHeight = KIconLoader::SizeSmall;
+        } else {
+            // TODO: it depends on the final icons whether a smaller size works
+            emblemHeight = KIconLoader::SizeSmall /* / 2 */;
+        }
+
+        const QSize emblemSize(emblemHeight, emblemHeight);
+        for (int i = 0; i <= RevisionControlPlugin::ConflictingRevision; ++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;
+            default: Q_ASSERT(false); break;
+            }
+
+            m_cachedEmblems[i] = KIcon(iconName).pixmap(emblemSize);
+        }
     }
-    return QPixmap();
+    return m_cachedEmblems[state];
 }
 
index 2f8d3c35f9a645be11be8b96a1332c1642dd4136..bd6bfdf073f0cd0a6f5f23e3f30e7ca85f792d12 100644 (file)
@@ -66,10 +66,12 @@ private:
                                   const DolphinModel* dolphinModel,
                                   const QModelIndex& index);
 
-    static QPixmap emblemForState(RevisionControlPlugin::RevisionState state, const QSize& size);
+    QPixmap emblemForState(RevisionControlPlugin::RevisionState state, const QSize& size) const;
 
 private:
     bool m_hasMinimizedNameColumn;
+    mutable QSize m_cachedSize;
+    mutable QPixmap m_cachedEmblems[RevisionControlPlugin::ConflictingRevision + 1];
 };
 
 inline void DolphinFileItemDelegate::setMinimizedNameColumn(bool minimized)
index 542d550ae3ff08163db5aa4a11efa83a7ac230ed..207b5a40fd5ab101d065e99cfdba7b5988e8a87b 100644 (file)
@@ -69,7 +69,7 @@ bool DolphinModel::setData(const QModelIndex& index, const QVariant& value, int
 
         const QPersistentModelIndex key = index;
         const RevisionControlPlugin::RevisionState state = static_cast<RevisionControlPlugin::RevisionState>(value.toInt());
-        if (m_revisionHash.value(key, RevisionControlPlugin::LocalRevision) != state) {
+        if (m_revisionHash.value(key, RevisionControlPlugin::UnversionedRevision) != state) {
             if (!m_hasRevisionData) {
                 connect(this, SIGNAL(rowsRemoved (const QModelIndex&, int, int)),
                         this, SLOT(slotRowsRemoved(const QModelIndex&, int, int)));
@@ -96,22 +96,22 @@ QVariant DolphinModel::data(const QModelIndex& index, int role) const
 
     case Qt::DecorationRole:
         if (index.column() == DolphinModel::Revision) {
-            return m_revisionHash.value(index, RevisionControlPlugin::LocalRevision);
+            return m_revisionHash.value(index, RevisionControlPlugin::UnversionedRevision);
         }
         break;
 
     case Qt::DisplayRole:
         if (index.column() == DolphinModel::Revision) {
-            switch (m_revisionHash.value(index, RevisionControlPlugin::LocalRevision)) {
-            case RevisionControlPlugin::LatestRevision:
-                return i18nc("@item::intable", "Latest");
-            case RevisionControlPlugin::EditingRevision:
-                return i18nc("@item::intable", "Editing");
+            switch (m_revisionHash.value(index, RevisionControlPlugin::UnversionedRevision)) {
+            case RevisionControlPlugin::NormalRevision:
+                return i18nc("@item::intable", "Normal");
+            case RevisionControlPlugin::LocallyModifiedRevision:
+                return i18nc("@item::intable", "Locally modified");
             case RevisionControlPlugin::UpdateRequiredRevision:
                 return i18nc("@item::intable", "Update required");
-            case RevisionControlPlugin::LocalRevision:
+            case RevisionControlPlugin::UnversionedRevision:
             default:
-                return i18nc("@item::intable", "Local");
+                return i18nc("@item::intable", "Unversioned");
             }
         }
         break;
@@ -141,6 +141,12 @@ int DolphinModel::columnCount(const QModelIndex& parent) const
     return KDirModel::columnCount(parent) + (ExtraColumnCount - ColumnCount);
 }
 
+void DolphinModel::clearRevisionData()
+{
+    m_revisionHash.clear();
+    m_hasRevisionData = false;
+}
+
 bool DolphinModel::hasRevisionData() const
 {
     return m_hasRevisionData;
@@ -148,11 +154,11 @@ bool DolphinModel::hasRevisionData() const
 
 void DolphinModel::slotRowsRemoved(const QModelIndex& parent, int start, int end)
 {
-    Q_ASSERT(hasRevisionData());
-
-    const int column = parent.column();
-    for (int row = start; row <= end; ++row) {
-        m_revisionHash.remove(parent.child(row, column));
+    if (m_hasRevisionData) {
+        const int column = parent.column();
+        for (int row = start; row <= end; ++row) {
+            m_revisionHash.remove(parent.child(row, column));
+        }
     }
 }
 
index 9ae3d5f7c99f6bb23189fac4425e4f052650ae90..1fd96c5f6eb6f01aa2a802141d6ead236d3825e1 100644 (file)
@@ -46,6 +46,7 @@ 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;
 
 private slots:
index a12e7a991a036fb8e198ac8f3d3309611cc0f01a..2fc664f2140b815ffb8638f5e70e059cad87cb3f 100644 (file)
@@ -522,6 +522,10 @@ void DolphinView::updateView(const KUrl& url, const KUrl& rootUrl)
         loadDirectory(url);
     }
 
+    // When changing the URL there is no need to keep the revision
+    // data of the previous URL.
+    m_dolphinModel->clearRevisionData();
+
     emit startedPathLoading(url);
 }
 
index f3bf08ce42a88a65153fa9a3bd4253ac41407283..1e7347a9bf69e50523df898c4905cfb1dca4f7b7 100644 (file)
@@ -26,6 +26,7 @@
 
 #include <QAbstractProxyModel>
 #include <QAbstractItemView>
+#include <QMutexLocker>
 #include <QTimer>
 
 /**
@@ -36,7 +37,7 @@
 class UpdateItemStatesThread : public QThread
 {
 public:
-    UpdateItemStatesThread(QObject* parent);
+    UpdateItemStatesThread(QObject* parent, QMutex* pluginMutex);
     void setData(RevisionControlPlugin* plugin,
                  const QList<RevisionControlObserver::ItemState>& itemStates);
     QList<RevisionControlObserver::ItemState> itemStates() const;
@@ -46,11 +47,13 @@ protected:
     
 private:
     RevisionControlPlugin* m_plugin;
+    QMutex* m_pluginMutex;
     QList<RevisionControlObserver::ItemState> m_itemStates;
 };
 
-UpdateItemStatesThread::UpdateItemStatesThread(QObject* parent) :
-    QThread(parent)
+UpdateItemStatesThread::UpdateItemStatesThread(QObject* parent, QMutex* pluginMutex) :
+    QThread(parent),
+    m_pluginMutex(pluginMutex)
 {
 }
 
@@ -68,7 +71,8 @@ void UpdateItemStatesThread::run()
     
     // it is assumed that all items have the same parent directory
     const QString directory = m_itemStates.first().item.url().directory(KUrl::AppendTrailingSlash);
-    
+
+    QMutexLocker locker(m_pluginMutex);
     if (m_plugin->beginRetrieval(directory)) {
         const int count = m_itemStates.count();
         for (int i = 0; i < count; ++i) {
@@ -83,7 +87,7 @@ QList<RevisionControlObserver::ItemState> UpdateItemStatesThread::itemStates() c
     return m_itemStates;
 }
 
-// ---
+// ------------------------------------------------------------------------------------------------
 
 RevisionControlObserver::RevisionControlObserver(QAbstractItemView* view) :
     QObject(view),
@@ -93,6 +97,7 @@ RevisionControlObserver::RevisionControlObserver(QAbstractItemView* view) :
     m_dirLister(0),
     m_dolphinModel(0),
     m_dirVerificationTimer(0),
+    m_pluginMutex(QMutex::Recursive),
     m_plugin(0),
     m_updateItemStatesThread(0)
 {
@@ -100,8 +105,8 @@ RevisionControlObserver::RevisionControlObserver(QAbstractItemView* view) :
 
     QAbstractProxyModel* proxyModel = qobject_cast<QAbstractProxyModel*>(view->model());
     m_dolphinModel = (proxyModel == 0) ?
-                 qobject_cast<DolphinModel*>(view->model()) :
-                 qobject_cast<DolphinModel*>(proxyModel->sourceModel());
+                     qobject_cast<DolphinModel*>(view->model()) :
+                     qobject_cast<DolphinModel*>(proxyModel->sourceModel());
     if (m_dolphinModel != 0) {
         m_dirLister = m_dolphinModel->dirLister();
         connect(m_dirLister, SIGNAL(completed()),
@@ -129,6 +134,7 @@ RevisionControlObserver::~RevisionControlObserver()
 QList<QAction*> RevisionControlObserver::contextMenuActions(const KFileItemList& items) const
 {
     if (m_dolphinModel->hasRevisionData() && (m_plugin != 0)) {
+        QMutexLocker locker(&m_pluginMutex);
         return m_plugin->contextMenuActions(items);
     }
     return QList<QAction*>();
@@ -137,6 +143,7 @@ QList<QAction*> RevisionControlObserver::contextMenuActions(const KFileItemList&
 QList<QAction*> RevisionControlObserver::contextMenuActions(const QString& directory) const
 {
     if (m_dolphinModel->hasRevisionData() && (m_plugin != 0)) {
+        QMutexLocker locker(&m_pluginMutex);
         return m_plugin->contextMenuActions(directory);
     }
 
@@ -162,28 +169,44 @@ void RevisionControlObserver::verifyDirectory()
 
     revisionControlUrl.addPath(m_plugin->fileName());
     const KFileItem item = m_dirLister->findByUrl(revisionControlUrl);
-    if (item.isNull() && m_revisionedDirectory) {
-        // 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);
-        m_revisionedDirectory = false;
-        disconnect(m_dirLister, SIGNAL(refreshItems(const QList<QPair<KFileItem,KFileItem>>&)),
-                   this, SLOT(delayedDirectoryVerification()));
-        disconnect(m_dirLister, SIGNAL(newItems(const KFileItemList&)),
-                   this, SLOT(delayedDirectoryVerification()));
-    } else if (!item.isNull()) {
+
+    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);
-            m_revisionedDirectory = true;
             connect(m_dirLister, SIGNAL(refreshItems(const QList<QPair<KFileItem,KFileItem>>&)),
                     this, SLOT(delayedDirectoryVerification()));
             connect(m_dirLister, SIGNAL(newItems(const KFileItemList&)),
                     this, SLOT(delayedDirectoryVerification()));
+            connect(m_plugin, SIGNAL(revisionStatesChanged(const QString&)),
+                    this, SLOT(delayedDirectoryVerification()));
         }
         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<QPair<KFileItem,KFileItem>>&)),
+                   this, SLOT(delayedDirectoryVerification()));
+        disconnect(m_dirLister, SIGNAL(newItems(const KFileItemList&)),
+                   this, SLOT(delayedDirectoryVerification()));
+        disconnect(m_plugin, SIGNAL(revisionStatesChanged(const QString&)),
+                   this, SLOT(delayedDirectoryVerification()));
     }
 }
 
@@ -216,7 +239,7 @@ void RevisionControlObserver::updateItemStates()
 {
     Q_ASSERT(m_plugin != 0);
     if (m_updateItemStatesThread == 0) {
-        m_updateItemStatesThread = new UpdateItemStatesThread(this);
+        m_updateItemStatesThread = new UpdateItemStatesThread(this, &m_pluginMutex);
         connect(m_updateItemStatesThread, SIGNAL(finished()),
                 this, SLOT(applyUpdatedItemStates()));
     }
@@ -238,8 +261,8 @@ void RevisionControlObserver::updateItemStates()
             ItemState itemState;
             itemState.index = index;
             itemState.item = m_dolphinModel->itemForIndex(index);
-            itemState.revision = RevisionControlPlugin::LocalRevision;
-            
+            itemState.revision = RevisionControlPlugin::UnversionedRevision;
+
             itemStates.append(itemState);
         }
         
index 27c7a27a0e02841e33d1af82d8bfe09b88d443f4..ae19e219f67a513bb5e6b073cf6e32dfc5af85e7 100644 (file)
@@ -25,6 +25,7 @@
 #include <kfileitem.h>
 #include <revisioncontrolplugin.h>
 #include <QList>
+#include <QMutex>
 #include <QObject>
 #include <QPersistentModelIndex>
 #include <QString>
@@ -82,6 +83,7 @@ private:
     
     QTimer* m_dirVerificationTimer;
     
+    mutable QMutex m_pluginMutex;
     RevisionControlPlugin* m_plugin;
     UpdateItemStatesThread* m_updateItemStatesThread;
 
index b3b407c6199e299a305750e76eaf2a992c4e6916..1e19ddcf0e7e2b08c090fc3f79dc0bdc56cc0730 100644 (file)
 
 #include "revisioncontrolplugin.h"
 
-#include <kaction.h>
-#include <kicon.h>
-#include <klocale.h>
-#include <kfileitem.h>
-#include <QDir>
-#include <QString>
-#include <QTextStream>
-
 RevisionControlPlugin::RevisionControlPlugin()
 {
 }
@@ -39,28 +31,59 @@ RevisionControlPlugin::~RevisionControlPlugin()
 
 // ----------------------------------------------------------------------------
 
+#include <kaction.h>
+#include <kdialog.h>
+#include <kicon.h>
+#include <klocale.h>
+#include <krun.h>
+#include <kshell.h>
+#include <kfileitem.h>
+#include <kvbox.h>
+#include <QDir>
+#include <QLabel>
+#include <QString>
+#include <QTextEdit>
+#include <QTextStream>
+
 SubversionPlugin::SubversionPlugin() :
-    m_directory(),
+    m_retrievalDir(),
     m_revisionInfoHash(),
     m_updateAction(0),
+    m_showLocalChangesAction(0),
     m_commitAction(0),
     m_addAction(0),
-    m_removeAction(0)
+    m_removeAction(0),
+    m_contextDir(),
+    m_contextItems()
 {
     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()
@@ -75,7 +98,7 @@ QString SubversionPlugin::fileName() const
 bool SubversionPlugin::beginRetrieval(const QString& directory)
 {
     Q_ASSERT(directory.endsWith('/'));
-    m_directory = directory;
+    m_retrievalDir = directory;
     const QString path = directory + ".svn/text-base/";
 
     QDir dir(path);
@@ -83,15 +106,16 @@ bool SubversionPlugin::beginRetrieval(const QString& directory)
     const int size = fileInfoList.size();
     QString fileName;
     for (int i = 0; i < size; ++i) {
-        fileName = fileInfoList.at(i).fileName();
+        const QFileInfo fileInfo = fileInfoList.at(i);
+        fileName = fileInfo.fileName();
         // Remove the ".svn-base" postfix to be able to compare the filenames
         // in a fast way in SubversionPlugin::revisionState().
         fileName.chop(sizeof(".svn-base") / sizeof(char) - 1);
         if (!fileName.isEmpty()) {
             RevisionInfo info;
-            info.size = fileInfoList.at(i).size();
-            info.timeStamp = fileInfoList.at(i).lastModified();            
-            m_revisionInfoHash.insert(fileName, info);
+            info.size = fileInfo.size();
+            info.timeStamp = fileInfo.lastModified();
+            m_revisionInfoHash.insert(directory + fileName, info);
         }
     }
     return size > 0;
@@ -103,37 +127,63 @@ void SubversionPlugin::endRetrieval()
 
 RevisionControlPlugin::RevisionState SubversionPlugin::revisionState(const KFileItem& item)
 {
-    const QString name = item.name();
+    const QString itemUrl = item.localPath();
     if (item.isDir()) {
-        QFile file(m_directory + name + "/.svn");
+        QFile file(itemUrl + "/.svn");
         if (file.open(QIODevice::ReadOnly)) {
             file.close();
-            // TODO...
-            return RevisionControlPlugin::LatestRevision;
+            return RevisionControlPlugin::NormalRevision;
         }
-    } else if (m_revisionInfoHash.contains(name)) {
-        const RevisionInfo info = m_revisionInfoHash.value(item.name());
+    } else if (m_revisionInfoHash.contains(itemUrl)) {
+        const RevisionInfo info = m_revisionInfoHash.value(itemUrl);
         const QDateTime localTimeStamp = item.time(KFileItem::ModificationTime).dateTime();
         const QDateTime versionedTimeStamp = info.timeStamp;
 
         if (localTimeStamp > versionedTimeStamp) {
             if ((info.size != item.size()) || !equalRevisionContent(item.name())) {
-                return RevisionControlPlugin::EditingRevision;
+                return RevisionControlPlugin::LocallyModifiedRevision;
             }
         } else if (localTimeStamp < versionedTimeStamp) {
             if ((info.size != item.size()) || !equalRevisionContent(item.name())) {
                 return RevisionControlPlugin::UpdateRequiredRevision;
             }
         }
-        return  RevisionControlPlugin::LatestRevision;
+        return  RevisionControlPlugin::NormalRevision;
     }
 
-    return RevisionControlPlugin::LocalRevision;
+    return RevisionControlPlugin::UnversionedRevision;
 }
 
-QList<QAction*> SubversionPlugin::contextMenuActions(const KFileItemList& items) const
+QList<QAction*> SubversionPlugin::contextMenuActions(const KFileItemList& items)
 {
-    Q_UNUSED(items);
+    Q_ASSERT(!items.isEmpty());
+
+    m_contextItems = items;
+    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<QAction*> actions;
     actions.append(m_updateAction);
@@ -143,24 +193,90 @@ QList<QAction*> SubversionPlugin::contextMenuActions(const KFileItemList& items)
     return actions;
 }
 
-QList<QAction*> SubversionPlugin::contextMenuActions(const QString& directory) const
+QList<QAction*> SubversionPlugin::contextMenuActions(const QString& directory)
 {
-    Q_UNUSED(directory);
+    m_contextDir = directory;
+    m_contextItems.clear();
 
     QList<QAction*> actions;
     actions.append(m_updateAction);
+    actions.append(m_showLocalChangesAction);
     actions.append(m_commitAction);
     return actions;
 }
 
+void SubversionPlugin::updateFiles()
+{
+    execSvnCommand("update");
+}
+
+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) {
+        const QString description = editor->toPlainText();
+        execSvnCommand("commit -m " + KShell::quoteArg(description));
+    }
+
+    dialog.saveDialogSize(dialogConfig, KConfigBase::Persistent);
+}
+
+void SubversionPlugin::addFiles()
+{
+    execSvnCommand("add");
+}
+
+void SubversionPlugin::removeFiles()
+{
+    execSvnCommand("remove");
+}
+
+void SubversionPlugin::execSvnCommand(const QString& svnCommand)
+{
+    const QString command = "svn " + svnCommand + ' ';
+    if (!m_contextDir.isEmpty()) {
+        KRun::runCommand(command + KShell::quoteArg(m_contextDir), 0);
+    } else {
+        foreach (const KFileItem& item, m_contextItems) {
+            KRun::runCommand(command + KShell::quoteArg(item.localPath()), 0);
+        }
+    }
+}
+
 bool SubversionPlugin::equalRevisionContent(const QString& name) const
 {
-    QFile localFile(m_directory + '/' + name);
+    QFile localFile(m_retrievalDir + '/' + name);
     if (!localFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
         return false;
     }
 
-    QFile revisionedFile(m_directory + "/.svn/text-base/" + name + ".svn-base");
+    QFile revisionedFile(m_retrievalDir + "/.svn/text-base/" + name + ".svn-base");
     if (!revisionedFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
         return false;
     }
@@ -175,4 +291,3 @@ bool SubversionPlugin::equalRevisionContent(const QString& name) const
 
      return localText.atEnd() && revisionedText.atEnd();
 }
-
index bbe66b3c3bc92a38743ef013c780c6c9620ad9ea..7863cfacb391b36e425131f92556224c436747fe 100644 (file)
@@ -34,13 +34,7 @@ class QAction;
  * @brief Base class for revision control plugins.
  *
  * Enables the file manager to show the revision state
- * of a revisioned file. The methods
- * RevisionControlPlugin::beginRetrieval(),
- * RevisionControlPlugin::endRetrieval() and
- * RevisionControlPlugin::revisionState() are invoked
- * from a separate thread to assure that the GUI thread
- * won't be blocked. All other methods are invoked in the
- * scope of the GUI thread.
+ * of a revisioned file.
  */
 class LIBDOLPHINPRIVATE_EXPORT RevisionControlPlugin : public QObject
 {
@@ -49,12 +43,34 @@ class LIBDOLPHINPRIVATE_EXPORT RevisionControlPlugin : public QObject
 public:
     enum RevisionState
     {
-        LocalRevision,
-        LatestRevision,
+        /** 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,
-        EditingRevision,
+        /**
+         * 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 and has been locally
+         * modified. A modification has also been done on the main
+         * branch.
+         */
         ConflictingRevision
-        // TODO...
     };
 
     RevisionControlPlugin();
@@ -97,14 +113,14 @@ public:
      * If an action triggers a change of the revisions, the signal
      * RevisionControlPlugin::revisionStatesChanged() must be emitted.
      */
-    virtual QList<QAction*> contextMenuActions(const KFileItemList& items) const = 0;
+    virtual QList<QAction*> 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<QAction*> contextMenuActions(const QString& directory) const = 0;
+    virtual QList<QAction*> contextMenuActions(const QString& directory) = 0;
 
 signals:
     /**
@@ -124,11 +140,13 @@ signals:
 // TODO: This is just a temporary test class. It will be made available as
 // plugin outside Dolphin later.
 
-#include <QFileInfoList>
+#include <kfileitem.h>
 #include <QHash>
 
 class LIBDOLPHINPRIVATE_EXPORT SubversionPlugin : public RevisionControlPlugin
 {
+    Q_OBJECT
+
 public:
     SubversionPlugin();
     virtual ~SubversionPlugin();
@@ -136,10 +154,23 @@ public:
     virtual bool beginRetrieval(const QString& directory);
     virtual void endRetrieval();
     virtual RevisionControlPlugin::RevisionState revisionState(const KFileItem& item);
-    virtual QList<QAction*> contextMenuActions(const KFileItemList& items) const;
-    virtual QList<QAction*> contextMenuActions(const QString& directory) const;
+    virtual QList<QAction*> contextMenuActions(const KFileItemList& items);
+    virtual QList<QAction*> contextMenuActions(const QString& directory);
+
+private slots:
+    void updateFiles();
+    void showLocalChanges();
+    void commitFiles();
+    void addFiles();
+    void removeFiles();
 
 private:
+    /**
+     * Executes the command "svn {svnCommand}" for the files that have been
+     * set by getting the context menu actions (see contextMenuActions()).
+     */
+    void execSvnCommand(const QString& svnCommand);
+
     /**
      * Returns true, if the content of the local file \p name is equal to the
      * content of the revisioned file.
@@ -153,13 +184,16 @@ private:
         QDateTime timeStamp;
     };
 
-    QString m_directory;
+    QString m_retrievalDir;
     QHash<QString, RevisionInfo> m_revisionInfoHash;
 
     QAction* m_updateAction;
+    QAction* m_showLocalChangesAction;
     QAction* m_commitAction;
     QAction* m_addAction;
     QAction* m_removeAction;
+    mutable QString m_contextDir;
+    mutable KFileItemList m_contextItems;
 };
 #endif // REVISIONCONTROLPLUGIN_H