cmake: Autogenerate most of the .qrc resource files
[quassel.git] / src / client / selectionmodelsynchronizer.cpp
index d533ea4..fe93c41 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-08 by the Quassel Project                          *
+ *   Copyright (C) 2005-2018 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
  *   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.,                                       *
- *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
 #include "selectionmodelsynchronizer.h"
 
-
 #include <QAbstractItemModel>
-#include "mappedselectionmodel.h"
 #include <QAbstractProxyModel>
 
 #include <QDebug>
 
 SelectionModelSynchronizer::SelectionModelSynchronizer(QAbstractItemModel *parent)
-  : QObject(parent),
+    : QObject(parent),
     _model(parent),
-    _selectionModel(parent)
+    _selectionModel(parent),
+    _changeCurrentEnabled(true),
+    _changeSelectionEnabled(true)
 {
-  connect(&_selectionModel, SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
-         this, SLOT(currentChanged(const QModelIndex &, const QModelIndex &)));
-  connect(&_selectionModel, SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
-         this, SLOT(selectionChanged(const QItemSelection &, const QItemSelection &)));
+    connect(&_selectionModel, SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
+        this, SLOT(currentChanged(const QModelIndex &, const QModelIndex &)));
+    connect(&_selectionModel, SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
+        this, SLOT(selectionChanged(const QItemSelection &, const QItemSelection &)));
 }
 
-bool SelectionModelSynchronizer::checkBaseModel(QItemSelectionModel *selectionModel) {
-  if(!selectionModel)
-    return false;
-
-  const QAbstractItemModel *baseModel = selectionModel->model();
-  const QAbstractProxyModel *proxyModel = 0;
-  while((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != 0) {
-    baseModel = proxyModel->sourceModel();
-    if(baseModel == model())
-      break;
-  }
-  return baseModel == model();
+
+bool SelectionModelSynchronizer::checkBaseModel(QItemSelectionModel *selectionModel)
+{
+    if (!selectionModel)
+        return false;
+
+    const QAbstractItemModel *baseModel = selectionModel->model();
+    const QAbstractProxyModel *proxyModel = 0;
+    while ((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != 0) {
+        baseModel = proxyModel->sourceModel();
+        if (baseModel == model())
+            break;
+    }
+    return baseModel == model();
 }
 
-void SelectionModelSynchronizer::addSelectionModel(QItemSelectionModel *selectionModel) {
-  if(!checkBaseModel(selectionModel)) {
-    qWarning() << "cannot Syncronize SelectionModel" << selectionModel << "which has a different baseModel()";
-    return;
-  }
-
-  connect(selectionModel, SIGNAL(currentChanged(QModelIndex, QModelIndex)),
-         this, SLOT(mappedCurrentChanged(QModelIndex, QModelIndex)));
-  connect(selectionModel, SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
-         this, SLOT(mappedSelectionChanged(QItemSelection, QItemSelection)));
-
-  if(qobject_cast<MappedSelectionModel *>(selectionModel)) {
-    connect(this, SIGNAL(setCurrentIndex(QModelIndex, QItemSelectionModel::SelectionFlags)),
-           selectionModel, SLOT(mappedSetCurrentIndex(QModelIndex, QItemSelectionModel::SelectionFlags)));
-    connect(this, SIGNAL(select(QItemSelection, QItemSelectionModel::SelectionFlags)),
-           selectionModel, SLOT(mappedSelect(QItemSelection, QItemSelectionModel::SelectionFlags)));
-  } else {
-    connect(this, SIGNAL(setCurrentIndex(QModelIndex, QItemSelectionModel::SelectionFlags)),
-           selectionModel, SLOT(setCurrentIndex(QModelIndex, QItemSelectionModel::SelectionFlags)));
-    connect(this, SIGNAL(select(QItemSelection, QItemSelectionModel::SelectionFlags)),
-           selectionModel, SLOT(select(QItemSelection, QItemSelectionModel::SelectionFlags)));
-  }
+
+void SelectionModelSynchronizer::synchronizeSelectionModel(QItemSelectionModel *selectionModel)
+{
+    if (!checkBaseModel(selectionModel)) {
+        qWarning() << "cannot Synchronize SelectionModel" << selectionModel << "which has a different baseModel()";
+        return;
+    }
+
+    if (_selectionModels.contains(selectionModel)) {
+        selectionModel->setCurrentIndex(mapFromSource(currentIndex(), selectionModel), QItemSelectionModel::Current);
+        selectionModel->select(mapSelectionFromSource(currentSelection(), selectionModel), QItemSelectionModel::ClearAndSelect);
+        return;
+    }
+
+    connect(selectionModel, SIGNAL(currentChanged(QModelIndex, QModelIndex)),
+        this, SLOT(syncedCurrentChanged(QModelIndex, QModelIndex)));
+    connect(selectionModel, SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
+        this, SLOT(syncedSelectionChanged(QItemSelection, QItemSelection)));
+
+    connect(selectionModel, SIGNAL(destroyed(QObject *)), this, SLOT(selectionModelDestroyed(QObject *)));
+
+    _selectionModels << selectionModel;
 }
 
-void SelectionModelSynchronizer::removeSelectionModel(QItemSelectionModel *model) {
-  disconnect(model, 0, this, 0);
-  disconnect(this, 0, model, 0);
+
+void SelectionModelSynchronizer::removeSelectionModel(QItemSelectionModel *model)
+{
+    disconnect(model, 0, this, 0);
+    disconnect(this, 0, model, 0);
+    selectionModelDestroyed(model);
 }
 
-void SelectionModelSynchronizer::mappedCurrentChanged(const QModelIndex &current, const QModelIndex &previous) {
-  Q_UNUSED(previous);
-  QItemSelectionModel *selectionModel = qobject_cast<QItemSelectionModel *>(sender());
-  Q_ASSERT(selectionModel);
-  QModelIndex newSourceCurrent = mapToSource(current, selectionModel);
-  if(newSourceCurrent.isValid() && newSourceCurrent != currentIndex())
-    setCurrentIndex(newSourceCurrent);
+
+void SelectionModelSynchronizer::selectionModelDestroyed(QObject *object)
+{
+    QItemSelectionModel *model = static_cast<QItemSelectionModel *>(object);
+    QSet<QItemSelectionModel *>::iterator iter = _selectionModels.begin();
+    while (iter != _selectionModels.end()) {
+        if (*iter == model) {
+            iter = _selectionModels.erase(iter);
+        }
+        else {
+            ++iter;
+        }
+    }
 }
 
-void SelectionModelSynchronizer::mappedSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) {
-  Q_UNUSED(selected);
-  Q_UNUSED(deselected);
-  QItemSelectionModel *selectionModel = qobject_cast<QItemSelectionModel *>(sender());
-  Q_ASSERT(selectionModel);
-  QItemSelection newSourceSelection = mapSelectionToSource(selectionModel->selection(), selectionModel);
-  QItemSelection currentContainsSelection = newSourceSelection;
-  currentContainsSelection.merge(currentSelection(), QItemSelectionModel::Deselect);
-  if(!currentContainsSelection.isEmpty())
-    setCurrentSelection(newSourceSelection);
+
+void SelectionModelSynchronizer::syncedCurrentChanged(const QModelIndex &current, const QModelIndex &previous)
+{
+    Q_UNUSED(previous);
+
+    if (!_changeCurrentEnabled)
+        return;
+
+    QItemSelectionModel *selectionModel = qobject_cast<QItemSelectionModel *>(sender());
+    Q_ASSERT(selectionModel);
+    QModelIndex newSourceCurrent = mapToSource(current, selectionModel);
+    if (newSourceCurrent.isValid() && newSourceCurrent != currentIndex())
+        setCurrentIndex(newSourceCurrent);
 }
 
-QModelIndex SelectionModelSynchronizer::mapToSource(const QModelIndex &index, QItemSelectionModel *selectionModel) {
-  Q_ASSERT(selectionModel);
-
-  QModelIndex sourceIndex = index;
-  const QAbstractItemModel *baseModel = selectionModel->model();
-  const QAbstractProxyModel *proxyModel = 0;
-  while((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != 0) {
-    sourceIndex = proxyModel->mapToSource(sourceIndex);
-    baseModel = proxyModel->sourceModel();
-    if(baseModel == model())
-      break;
-  }
-  return sourceIndex;
+
+void SelectionModelSynchronizer::syncedSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
+{
+    Q_UNUSED(selected);
+    Q_UNUSED(deselected);
+
+    if (!_changeSelectionEnabled)
+        return;
+
+    QItemSelectionModel *selectionModel = qobject_cast<QItemSelectionModel *>(sender());
+    Q_ASSERT(selectionModel);
+
+    QItemSelection mappedSelection = selectionModel->selection();
+    QItemSelection currentSelectionMapped = mapSelectionFromSource(currentSelection(), selectionModel);
+
+    QItemSelection checkSelection = currentSelectionMapped;
+    checkSelection.merge(mappedSelection, QItemSelectionModel::Deselect);
+    if (checkSelection.isEmpty()) {
+        // that means the new selection contains the current selection (currentSel - newSel = {})
+        checkSelection = mappedSelection;
+        checkSelection.merge(currentSelectionMapped, QItemSelectionModel::Deselect);
+        if (checkSelection.isEmpty()) {
+            // that means the current selection contains the new selection (newSel - currentSel = {})
+            // -> currentSel == newSel
+            return;
+        }
+    }
+    setCurrentSelection(mapSelectionToSource(mappedSelection, selectionModel));
 }
 
-QItemSelection SelectionModelSynchronizer::mapSelectionToSource(const QItemSelection &selection, QItemSelectionModel *selectionModel) {
-  Q_ASSERT(selectionModel);
-
-  QItemSelection sourceSelection = selection;
-  const QAbstractItemModel *baseModel = selectionModel->model();
-  const QAbstractProxyModel *proxyModel = 0;
-  while((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != 0) {
-    sourceSelection = proxyModel->mapSelectionToSource(sourceSelection);
-    baseModel = proxyModel->sourceModel();
-    if(baseModel == model())
-      break;
-  }
-  return sourceSelection;
+
+QModelIndex SelectionModelSynchronizer::mapFromSource(const QModelIndex &sourceIndex, const QItemSelectionModel *selectionModel)
+{
+    Q_ASSERT(selectionModel);
+
+    QModelIndex mappedIndex = sourceIndex;
+
+    // make a list of all involved proxies, wie have to traverse backwards
+    QList<const QAbstractProxyModel *> proxyModels;
+    const QAbstractItemModel *baseModel = selectionModel->model();
+    const QAbstractProxyModel *proxyModel = 0;
+    while ((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != 0) {
+        if (baseModel == model())
+            break;
+        proxyModels << proxyModel;
+        baseModel = proxyModel->sourceModel();
+    }
+
+    // now traverse it;
+    for (int i = proxyModels.count() - 1; i >= 0; i--) {
+        mappedIndex = proxyModels[i]->mapFromSource(mappedIndex);
+    }
+
+    return mappedIndex;
 }
 
-void SelectionModelSynchronizer::setCurrentIndex(const QModelIndex &index) {
-  _selectionModel.setCurrentIndex(index, QItemSelectionModel::Current);
+
+QItemSelection SelectionModelSynchronizer::mapSelectionFromSource(const QItemSelection &sourceSelection, const QItemSelectionModel *selectionModel)
+{
+    Q_ASSERT(selectionModel);
+
+    QItemSelection mappedSelection = sourceSelection;
+
+    // make a list of all involved proxies, wie have to traverse backwards
+    QList<const QAbstractProxyModel *> proxyModels;
+    const QAbstractItemModel *baseModel = selectionModel->model();
+    const QAbstractProxyModel *proxyModel = 0;
+    while ((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != 0) {
+        if (baseModel == model())
+            break;
+        proxyModels << proxyModel;
+        baseModel = proxyModel->sourceModel();
+    }
+
+    // now traverse it;
+    for (int i = proxyModels.count() - 1; i >= 0; i--) {
+        mappedSelection = proxyModels[i]->mapSelectionFromSource(mappedSelection);
+    }
+    return mappedSelection;
 }
-void SelectionModelSynchronizer::setCurrentSelection(const QItemSelection &selection) {
-  _selectionModel.select(selection, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect);
+
+
+QModelIndex SelectionModelSynchronizer::mapToSource(const QModelIndex &index, QItemSelectionModel *selectionModel)
+{
+    Q_ASSERT(selectionModel);
+
+    QModelIndex sourceIndex = index;
+    const QAbstractItemModel *baseModel = selectionModel->model();
+    const QAbstractProxyModel *proxyModel = 0;
+    while ((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != 0) {
+        sourceIndex = proxyModel->mapToSource(sourceIndex);
+        baseModel = proxyModel->sourceModel();
+        if (baseModel == model())
+            break;
+    }
+    return sourceIndex;
 }
 
-void SelectionModelSynchronizer::currentChanged(const QModelIndex &current, const QModelIndex &previous) {
-  Q_UNUSED(previous);
-  emit setCurrentIndex(current, QItemSelectionModel::Current);
+
+QItemSelection SelectionModelSynchronizer::mapSelectionToSource(const QItemSelection &selection, QItemSelectionModel *selectionModel)
+{
+    Q_ASSERT(selectionModel);
+
+    QItemSelection sourceSelection = selection;
+    const QAbstractItemModel *baseModel = selectionModel->model();
+    const QAbstractProxyModel *proxyModel = 0;
+    while ((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != 0) {
+        sourceSelection = proxyModel->mapSelectionToSource(sourceSelection);
+        baseModel = proxyModel->sourceModel();
+        if (baseModel == model())
+            break;
+    }
+    return sourceSelection;
 }
 
-void SelectionModelSynchronizer::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) {
-  Q_UNUSED(selected);
-  Q_UNUSED(deselected);
-  emit select(_selectionModel.selection(), QItemSelectionModel::ClearAndSelect);
+
+void SelectionModelSynchronizer::setCurrentIndex(const QModelIndex &index)
+{
+    _selectionModel.setCurrentIndex(index, QItemSelectionModel::Current);
+}
+
+
+void SelectionModelSynchronizer::setCurrentSelection(const QItemSelection &selection)
+{
+    _selectionModel.select(selection, QItemSelectionModel::ClearAndSelect);
+}
+
+
+void SelectionModelSynchronizer::currentChanged(const QModelIndex &current, const QModelIndex &previous)
+{
+    Q_UNUSED(previous);
+
+    _changeCurrentEnabled = false;
+    QSet<QItemSelectionModel *>::iterator iter = _selectionModels.begin();
+    while (iter != _selectionModels.end()) {
+        (*iter)->setCurrentIndex(mapFromSource(current, (*iter)), QItemSelectionModel::Current);
+        ++iter;
+    }
+    _changeCurrentEnabled = true;
+
+    // Trigger a dataChanged() signal from the base model to update all proxy models (e.g. filters).
+    // Since signals are protected, we have to use invokeMethod for faking signal emission.
+    if (previous.isValid()) {
+        QMetaObject::invokeMethod(model(), "dataChanged", Qt::DirectConnection,
+                                  Q_ARG(QModelIndex, previous), Q_ARG(QModelIndex, previous));
+    }
+}
+
+
+void SelectionModelSynchronizer::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
+{
+    Q_UNUSED(selected);
+    Q_UNUSED(deselected);
+
+    _changeSelectionEnabled = false;
+    QSet<QItemSelectionModel *>::iterator iter = _selectionModels.begin();
+    while (iter != _selectionModels.end()) {
+        (*iter)->select(mapSelectionFromSource(currentSelection(), (*iter)), QItemSelectionModel::ClearAndSelect);
+        ++iter;
+    }
+    _changeSelectionEnabled = true;
 }