/* * Copyright (C) 2013 Canonical, Ltd. * * 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 . * * Author: Nick Dedekind */ // self #include "fake_categories.h" // local #include "categoryresults.h" #define CATEGORY_COLUMN 2 // 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) { // FIXME: need to clean up unused filters on countChanged 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::RoleRendererHint] = "rendererHint"; m_roles[Categories::RoleHints] = "hints"; m_roles[Categories::RoleResults] = "results"; m_roles[Categories::RoleCount] = "count"; // 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, SIGNAL(timeout()), this, SLOT(onEmitCountChanged())); } DeeListModel* Categories::getFilter(int index) const { if (!m_filters.contains(index)) { auto results = new CategoryResults (); results->setCategoryIndex(index); connect(results, SIGNAL(countChanged()), this, SLOT(onCountChanged())); unsigned cat_index = static_cast(index); auto model = getResultsForCategory(cat_index); results->setModel(model); m_filters.insert(index, results); } return m_filters[index]; } void Categories::onCountChanged() { DeeListModel* filter = qobject_cast(sender()); if (filter) { m_timerFilters << filter; m_timer.start(); } } void Categories::onRowCountChanged() { 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; QVector roles; roles.append(RoleCount); QModelIndex changedIndex = index(i); Q_EMIT dataChanged(changedIndex, changedIndex, roles); break; } } } } void Categories::onEmitCountChanged() { QVector roles; roles.append(Categories::RoleCount); Q_FOREACH(DeeListModel* results, m_timerFilters) { auto cat_results = qobject_cast(results); QModelIndex changedIndex = index(cat_results->categoryIndex()); Q_EMIT dataChanged(changedIndex, changedIndex, roles); } m_timerFilters.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; } } void Categories::setResultModel(DeeModel* model) { // FIXME: should ref it m_dee_results = model; } static void category_filter_map_func (DeeModel* orig_model, DeeFilterModel* filter_model, gpointer user_data) { DeeModelIter* iter; DeeModelIter* end; unsigned index = GPOINTER_TO_UINT(user_data); iter = dee_model_get_first_iter(orig_model); end = dee_model_get_last_iter(orig_model); while (iter != end) { unsigned category_index = dee_model_get_uint32(orig_model, iter, CATEGORY_COLUMN); if (index == category_index) { dee_filter_model_append_iter(filter_model, iter); } iter = dee_model_next(orig_model, iter); } } static gboolean category_filter_notify_func (DeeModel* orig_model, DeeModelIter* orig_iter, DeeFilterModel* filter_model, gpointer user_data) { unsigned index = GPOINTER_TO_UINT(user_data); unsigned category_index = dee_model_get_uint32(orig_model, orig_iter, CATEGORY_COLUMN); if (index != category_index) return FALSE; dee_filter_model_insert_iter_with_original_order(filter_model, orig_iter); return TRUE; } DeeModel* Categories::getResultsForCategory(unsigned cat_index) const { DeeFilter filter; filter.map_func = category_filter_map_func; filter.map_notify = category_filter_notify_func; filter.destroy = nullptr; filter.userdata = GUINT_TO_POINTER(cat_index); DeeModel* filtered_model = dee_filter_model_new(m_dee_results, &filter); return filtered_model; } QVariant Categories::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } switch (role) { case RoleCategoryId: return DeeListModel::data(index, CategoryColumn::ID); case RoleName: return DeeListModel::data(index, CategoryColumn::DISPLAY_NAME); case RoleIcon: return DeeListModel::data(index, CategoryColumn::ICON_HINT); case RoleRenderer: return DeeListModel::data(index, CategoryColumn::RENDERER_NAME); case RoleContentType: { auto hints = DeeListModel::data(index, CategoryColumn::HINTS).toHash(); return hints.contains("content-type") ? hints["content-type"] : QVariant(QString("default")); } case RoleRendererHint: { auto hints = DeeListModel::data(index, CategoryColumn::HINTS).toHash(); return hints.contains("renderer-hint") ? hints["renderer-hint"] : QVariant(QString()); } case RoleHints: return DeeListModel::data(index, CategoryColumn::HINTS); case RoleResults: if (m_overriddenCategories.size() > 0) { auto id = DeeListModel::data(index, CategoryColumn::ID).toString(); if (m_overriddenCategories.find(id) != m_overriddenCategories.end()) return QVariant::fromValue(m_overriddenCategories[id]); } return QVariant::fromValue(getFilter(index.row())); case RoleCount: if (m_overriddenCategories.size() > 0) { auto id = DeeListModel::data(index, CategoryColumn::ID).toString(); if (m_overriddenCategories.find(id) != m_overriddenCategories.end()) return QVariant::fromValue(m_overriddenCategories[id]->rowCount()); } return QVariant::fromValue(getFilter(index.row())->rowCount()); default: return QVariant(); } }