#include "dolphin_versioncontrolsettings.h"
-#include <KDirLister>
#include <KLocale>
#include <KService>
#include <KServiceTypeTrader>
-#include <kversioncontrolplugin.h>
+#include <kitemviews/kfileitemmodel.h>
+#include <kversioncontrolplugin2.h>
-#include "pendingthreadsmaintainer.h"
#include "updateitemstatesthread.h"
-#include <QAbstractProxyModel>
-#include <QAbstractItemView>
+#include <QFile>
#include <QMutexLocker>
#include <QTimer>
-#include <views/dolphinmodel.h>
-
-VersionControlObserver::VersionControlObserver(QAbstractItemView* view) :
- QObject(view),
+VersionControlObserver::VersionControlObserver(QObject* parent) :
+ QObject(parent),
m_pendingItemStatesUpdate(false),
m_versionedDirectory(false),
m_silentUpdate(false),
- m_view(view),
- m_dirLister(0),
- m_dolphinModel(0),
+ m_model(0),
m_dirVerificationTimer(0),
m_plugin(0),
m_updateItemStatesThread(0)
{
- Q_ASSERT(view != 0);
-
- QAbstractProxyModel* proxyModel = qobject_cast<QAbstractProxyModel*>(view->model());
- m_dolphinModel = (proxyModel == 0) ?
- qobject_cast<DolphinModel*>(view->model()) :
- qobject_cast<DolphinModel*>(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()));
+}
- // 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_plugin) {
+ m_plugin->disconnect(this);
+ m_plugin = 0;
}
}
-VersionControlObserver::~VersionControlObserver()
+void VersionControlObserver::setModel(KFileItemModel* model)
{
- if (m_updateItemStatesThread != 0) {
- if (m_updateItemStatesThread->isFinished()) {
- delete m_updateItemStatesThread;
- m_updateItemStatesThread = 0;
- } else {
- // The version controller gets deleted, while a thread still
- // is working to get the version information. To avoid a blocking
- // user interface, the thread will be forwarded to the
- // PendingThreadsMaintainer, which will delete the thread later.
- disconnect(m_updateItemStatesThread, SIGNAL(finished()),
- this, SLOT(slotThreadFinished()));
- PendingThreadsMaintainer::instance().append(m_updateItemStatesThread);
- m_updateItemStatesThread = 0;
- }
+ if (m_model) {
+ disconnect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
+ this, SLOT(delayedDirectoryVerification()));
+ disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(delayedDirectoryVerification()));
}
- if (m_plugin != 0) {
- m_plugin->disconnect();
- m_plugin = 0;
+ m_model = model;
+
+ if (model) {
+ connect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
+ this, SLOT(delayedDirectoryVerification()));
+ connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(delayedDirectoryVerification()));
}
}
-QList<QAction*> VersionControlObserver::contextMenuActions(const KFileItemList& items) const
+KFileItemModel* VersionControlObserver::model() const
{
- QList<QAction*> actions;
- if (isVersioned() && m_updateItemStatesThread->lockPlugin()) {
- actions = m_plugin->contextMenuActions(items);
- m_updateItemStatesThread->unlockPlugin();
- }
- return actions;
+ return m_model;
}
-QList<QAction*> VersionControlObserver::contextMenuActions(const QString& directory) const
+QList<QAction*> VersionControlObserver::actions(const KFileItemList& items) const
{
QList<QAction*> actions;
- if (isVersioned() && m_updateItemStatesThread->lockPlugin()) {
- actions = m_plugin->contextMenuActions(directory);
- m_updateItemStatesThread->unlockPlugin();
+
+ bool hasNullItems = false;
+ foreach (const KFileItem& item, items) {
+ if (item.isNull()) {
+ kWarning() << "Requesting version-control-actions for empty items";
+ hasNullItems = true;
+ break;
+ }
+ }
+
+ if (!m_model || hasNullItems) {
+ return actions;
+ }
+
+ KVersionControlPlugin2* pluginV2 = qobject_cast<KVersionControlPlugin2*>(m_plugin);
+ if (pluginV2) {
+ // Use version 2 of the KVersionControlPlugin which allows providing actions
+ // also for non-versioned directories.
+ actions = pluginV2->actions(items);
+ } else if (isVersioned()) {
+ // Support deprecated interfaces from KVersionControlPlugin version 1.
+ // Context menu actions where only available for versioned directories.
+ QString directory;
+ if (items.count() == 1) {
+ const KFileItem rootItem = m_model->rootItem();
+ if (!rootItem.isNull() && items.first().url() == rootItem.url()) {
+ directory = rootItem.url().path(KUrl::AddTrailingSlash);
+ }
+ }
+
+ actions = directory.isEmpty() ? m_plugin->contextMenuActions(items)
+ : m_plugin->contextMenuActions(directory);
}
return actions;
void VersionControlObserver::verifyDirectory()
{
- const KUrl versionControlUrl = m_dirLister->url();
- if (!versionControlUrl.isLocalFile()) {
+ if (!m_model) {
+ return;
+ }
+
+ const KFileItem rootItem = m_model->rootItem();
+ if (rootItem.isNull() || !rootItem.url().isLocalFile()) {
return;
}
- if (m_plugin != 0) {
- m_plugin->disconnect();
+ if (m_plugin) {
+ m_plugin->disconnect(this);
}
- m_plugin = searchPlugin(versionControlUrl);
- if (m_plugin != 0) {
- connect(m_plugin, SIGNAL(versionStatesChanged()),
- this, SLOT(silentDirectoryVerification()));
+ m_plugin = searchPlugin(rootItem.url());
+ if (m_plugin) {
+ KVersionControlPlugin2* pluginV2 = qobject_cast<KVersionControlPlugin2*>(m_plugin);
+ if (pluginV2) {
+ connect(pluginV2, SIGNAL(itemVersionsChanged()),
+ this, SLOT(silentDirectoryVerification()));
+ } else {
+ connect(m_plugin, SIGNAL(versionStatesChanged()),
+ this, SLOT(silentDirectoryVerification()));
+ }
connect(m_plugin, SIGNAL(infoMessage(QString)),
this, SIGNAL(infoMessage(QString)));
connect(m_plugin, SIGNAL(errorMessage(QString)),
// 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<QPair<KFileItem,KFileItem>>&)),
- this, SLOT(delayedDirectoryVerification()));
- connect(m_dirLister, SIGNAL(newItems(const KFileItemList&)),
- this, SLOT(delayedDirectoryVerification()));
}
updateItemStates();
} else if (m_versionedDirectory) {
// 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()));
}
}
void VersionControlObserver::slotThreadFinished()
{
- if (m_plugin == 0) {
+ UpdateItemStatesThread* thread = m_updateItemStatesThread;
+ m_updateItemStatesThread = 0; // The thread deletes itself automatically (see updateItemStates())
+
+ if (!m_plugin || !thread) {
return;
}
- if (!m_updateItemStatesThread->retrievedItems()) {
- // ignore m_silentUpdate for an error message
+ if (!thread->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<ItemState> itemStates = m_updateItemStatesThread->itemStates();
+ const QList<ItemState> itemStates = thread->itemStates();
foreach (const ItemState& itemState, itemStates) {
- m_dolphinModel->setData(itemState.index,
- QVariant(static_cast<int>(itemState.version)),
- Qt::DecorationRole);
+ QHash<QByteArray, QVariant> values;
+ values.insert("version", QVariant(itemState.version));
+ m_model->setData(itemState.index, values);
}
- 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
void VersionControlObserver::updateItemStates()
{
- Q_ASSERT(m_plugin != 0);
- if (m_updateItemStatesThread == 0) {
- m_updateItemStatesThread = new UpdateItemStatesThread();
- connect(m_updateItemStatesThread, SIGNAL(finished()),
- this, SLOT(slotThreadFinished()));
- }
- if (m_updateItemStatesThread->isRunning()) {
+ Q_ASSERT(m_plugin);
+ if (m_updateItemStatesThread) {
// An update is currently ongoing. Wait until the thread has finished
// the update (see slotThreadFinished()).
m_pendingItemStatesUpdate = true;
return;
}
-
QList<ItemState> itemStates;
- addDirectory(QModelIndex(), itemStates);
+ const int itemCount = m_model->count();
+ itemStates.reserve(itemCount);
+
+ for (int i = 0; i < itemCount; ++i) {
+ ItemState itemState;
+ itemState.index = i;
+ itemState.item = m_model->fileItem(i);
+ itemState.version = KVersionControlPlugin2::UnversionedVersion;
+
+ itemStates.append(itemState);
+ }
+
if (!itemStates.isEmpty()) {
if (!m_silentUpdate) {
emit infoMessage(i18nc("@info:status", "Updating version information..."));
}
- m_updateItemStatesThread->setData(m_plugin, itemStates);
- m_updateItemStatesThread->start(); // slotThreadFinished() is called when finished
- }
-}
-
-void VersionControlObserver::addDirectory(const QModelIndex& parentIndex, QList<ItemState>& 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;
+ m_updateItemStatesThread = new UpdateItemStatesThread(m_plugin, itemStates);
+ connect(m_updateItemStatesThread, SIGNAL(finished()),
+ this, SLOT(slotThreadFinished()));
+ connect(m_updateItemStatesThread, SIGNAL(finished()),
+ m_updateItemStatesThread, SLOT(deleteLater()));
- itemStates.append(itemState);
+ m_updateItemStatesThread->start(); // slotThreadFinished() is called when finished
}
}
for (KService::List::ConstIterator it = pluginServices.constBegin(); it != pluginServices.constEnd(); ++it) {
if (enabledPlugins.contains((*it)->name())) {
KVersionControlPlugin* plugin = (*it)->createInstance<KVersionControlPlugin>();
- if (plugin != 0) {
+ if (plugin) {
plugins.append(plugin);
}
}
// Verify whether the current directory contains revision information
// like .svn, .git, ...
foreach (KVersionControlPlugin* plugin, plugins) {
- // Use the KDirLister cache to check for .svn, .git, ... files
- KUrl dirUrl(directory);
- KUrl fileUrl = dirUrl;
- fileUrl.addPath(plugin->fileName());
- const KFileItem item = m_dirLister->findByUrl(fileUrl);
- if (!item.isNull()) {
+ const QString fileName = directory.path(KUrl::AddTrailingSlash) + plugin->fileName();
+ if (QFile::exists(fileName)) {
return plugin;
}
// m_versionedDirectory. Drawback: Until e. g. Git is recognized, the root directory
// must be shown at least once.
if (m_versionedDirectory) {
+ KUrl dirUrl(directory);
KUrl upUrl = dirUrl.upUrl();
while (upUrl != dirUrl) {
- const QString filePath = dirUrl.pathOrUrl(KUrl::AddTrailingSlash) + plugin->fileName();
- QFileInfo file(filePath);
- if (file.exists()) {
+ const QString fileName = dirUrl.path(KUrl::AddTrailingSlash) + plugin->fileName();
+ if (QFile::exists(fileName)) {
return plugin;
}
dirUrl = upUrl;
bool VersionControlObserver::isVersioned() const
{
- return m_dolphinModel->hasVersionData() && (m_plugin != 0);
+ return m_versionedDirectory && m_plugin;
}
#include "versioncontrolobserver.moc"