/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * MichaƂ Sawicz * Michal Hruby * * 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; version 3. * * 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, see . */ // self #include "categories.h" #include "categoryresults.h" // Qt #include // TODO: use something from libunity once it's public enum CategoryColumn { ID, DISPLAY_NAME, ICON_HINT, RENDERER_NAME, HINTS }; Categories::Categories(QObject* parent) : DeeListModel(parent) { m_roles[Categories::RoleCategoryId] = "categoryId"; m_roles[Categories::RoleName] = "name"; m_roles[Categories::RoleIcon] = "icon"; m_roles[Categories::RoleRenderer] = "renderer"; m_roles[Categories::RoleContentType] = "contentType"; m_roles[Categories::RoleProgressSource] = "progressSource"; m_roles[Categories::RoleHints] = "hints"; m_roles[Categories::RoleResults] = "results"; m_roles[Categories::RoleCount] = "count"; m_roles[Categories::RoleCategoryIndex] = "categoryIndex"; // TODO This should not be needed but accumulatting the count changes // makes the visualization more stable and also makes crashes on fast // change of the search term harder to reproduce m_timer.setSingleShot(true); m_timer.setInterval(50); connect(&m_timer, &QTimer::timeout, this, &Categories::onEmitCountChanged); } DeeListModel* Categories::getResults(int index) const { if (!m_results.contains(index)) { CategoryResults* results = new CategoryResults(const_cast(this)); results->setCategoryIndex(index); connect(results, &DeeListModel::countChanged, this, &Categories::onCountChanged); unsigned categoryIndex = static_cast(index); auto unity_results = m_unityScope->GetResultsForCategory(categoryIndex); results->setModel(unity_results->model()); m_results.insert(index, results); } return m_results[index]; } void Categories::onCategoriesModelChanged(unity::glib::Object model) { m_updatedCategories.clear(); // FIXME: this might destroy the renderer view and re-create it, optimize? Q_FOREACH(DeeListModel* model, m_results) { delete model; } m_results.clear(); setModel(model); } void Categories::onCategoryOrderChanged(std::vector cat_order) { for (unsigned int pos = 0; pos(pos) != old_pos) { m_categoryOrder.move(old_pos, pos); int target_pos = pos; //if (pos > old_pos) // target_pos = target_pos - old_pos; bool status = beginMoveRows(QModelIndex(), old_pos, old_pos, QModelIndex(), target_pos); if (status != true) qWarning("!!!!!!!!!!!!!!!!!!!!!!!"); qDebug() << "Moving category" << cat << DeeListModel::data(createIndex(cat, 0),CategoryColumn::DISPLAY_NAME).toString() << ":" << old_pos << "->" << pos << target_pos << "g"; endMoveRows(); } } Q_FOREACH(auto cat, m_categoryOrder) { qDebug() << "Category" << DeeListModel::data(createIndex(cat, 0),CategoryColumn::DISPLAY_NAME).toString(); } } void Categories::setUnityScope(const unity::dash::Scope::Ptr& scope) { m_unityScope = scope; // no need to call this, we'll get notified //setModel(m_unityScope->categories()->model()); m_signals.disconnectAll(); m_signals << m_unityScope->categories()->model.changed.connect(sigc::mem_fun(this, &Categories::onCategoriesModelChanged)); m_signals << m_unityScope->category_order.changed.connect(sigc::mem_fun(this, &Categories::onCategoryOrderChanged)); } void Categories::onCountChanged() { CategoryResults* results = qobject_cast(sender()); if (results) { if (!m_updatedCategories.contains(results->categoryIndex())) { m_updatedCategories << results->categoryIndex(); qDebug() << "Updated count of" << results->categoryIndex(); } m_timer.start(); } } void Categories::onRowCountChanged() { qDebug() << "Overriden model updated"; QAbstractItemModel* model = qobject_cast(sender()); // find the corresponding category index for (auto iter = m_overriddenCategories.begin(); iter != m_overriddenCategories.end(); ++iter) { if (iter.value() == model) { for (int i = 0; i < rowCount(); i++) { auto id = data(index(i), RoleCategoryId).toString(); if (id != iter.key()) continue; const int mappedIndex = m_categoryOrder.indexOf(i); QVector roles; roles.append(RoleCount); QModelIndex changedIndex = index(i); qDebug() << "Overriden model" << id << "count update at index" << i; Q_EMIT dataChanged(changedIndex, changedIndex, roles); break; } } } } void Categories::onEmitCountChanged() { QVector roles; roles.append(Categories::RoleCount); Q_FOREACH(int categoryIndex, m_updatedCategories) { if (!m_results.contains(categoryIndex)) continue; const int realIndex = m_categoryOrder.indexOf(categoryIndex); const QModelIndex changedIndex = index(realIndex); Q_EMIT dataChanged(changedIndex, changedIndex, roles); } m_updatedCategories.clear(); } QHash Categories::roleNames() const { return m_roles; } void Categories::onOverrideModelDestroyed() { QObject* model = sender(); auto iter = m_overriddenCategories.begin(); while (iter != m_overriddenCategories.end()) { if (iter.value() == model) { iter = m_overriddenCategories.erase(iter); continue; } ++iter; } } void Categories::overrideResults(const QString& categoryId, QAbstractItemModel* model) { m_overriddenCategories[categoryId] = model; // watch the model connect(model, &QObject::destroyed, this, &Categories::onOverrideModelDestroyed); connect(model, &QAbstractItemModel::rowsInserted, this, &Categories::onRowCountChanged); connect(model, &QAbstractItemModel::rowsRemoved, this, &Categories::onRowCountChanged); connect(model, &QAbstractItemModel::modelReset, this, &Categories::onRowCountChanged); // emit the dataChanged signal if the category is already in the model for (int i = 0; i < rowCount(); i++) { auto id = data(index(i), RoleCategoryId).toString(); if (id != categoryId) continue; QVector roles; roles.append(RoleCount); roles.append(RoleResults); QModelIndex changedIndex = index(i); Q_EMIT dataChanged(changedIndex, changedIndex, roles); break; } } QVariant Categories::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } if (m_categoryOrder.size() != rowCount()) { // populate category order vector with 0..n m_categoryOrder.clear(); const unsigned int lim = rowCount(); for (unsigned int i = 0; i 0) { auto id = DeeListModel::data(realIndex, CategoryColumn::ID).toString(); if (m_overriddenCategories.find(id) != m_overriddenCategories.end()) return QVariant::fromValue(m_overriddenCategories[id]); } return QVariant::fromValue(getResults(realRow)); case RoleCount: if (m_overriddenCategories.size() > 0) { auto id = DeeListModel::data(realIndex, CategoryColumn::ID).toString(); if (m_overriddenCategories.find(id) != m_overriddenCategories.end()) { qDebug() << "Get count of overriden cat" << realRow << id << "at pos" << index.row() << "Count=" << m_overriddenCategories[id]->rowCount(); return QVariant::fromValue(m_overriddenCategories[id]->rowCount()); } } qDebug() << "Get count of cat" << DeeListModel::data(realIndex, CategoryColumn::ID).toString() << realRow << "at pos" << index.row() << "Count=" << getResults(realIndex.row())->rowCount(); return QVariant::fromValue(getResults(realRow)->rowCount()); case RoleCategoryIndex: return QVariant::fromValue(index.row()); // no remapping to realIndex default: return QVariant(); } }