Commit 2818067f authored by Dmitry Kazakov's avatar Dmitry Kazakov

Fixed a weird Favorite Composite Ops bug

The model of the categorized list is completely refactored. Now there are
at least three classes involved into the creation of the list:

1) KisCategoriesMapper --- stores a list of the items of the list and
   controls their internal state.

2) KisCategorizedListModel --- the model that works on the basis of the
   categories mapper.

3) KisSortedCategorizedListModel --- a wrapper for a categorized model that
   handles the sorting of the list into categories and items.

BUG:318928
parent 96a27c47
......@@ -417,17 +417,16 @@ void KisLayerBox::setCurrentNode(KisNodeSP node)
void KisLayerBox::slotSetCompositeOp(const KoCompositeOp* compositeOp)
{
KoID cmpOp = KoCompositeOpRegistry::instance().getKoID(compositeOp->id());
int index = m_wdgLayerBox->cmbComposite->indexOf(cmpOp);
KoID opId = KoCompositeOpRegistry::instance().getKoID(compositeOp->id());
m_wdgLayerBox->cmbComposite->blockSignals(true);
m_wdgLayerBox->cmbComposite->setCurrentIndex(index);
m_wdgLayerBox->cmbComposite->selectCompositeOp(opId);
m_wdgLayerBox->cmbComposite->blockSignals(false);
}
void KisLayerBox::slotFillCompositeOps(const KoColorSpace* colorSpace)
{
m_wdgLayerBox->cmbComposite->getModel()->validateCompositeOps(colorSpace);
m_wdgLayerBox->cmbComposite->validate(colorSpace);
}
// range: 0-100
......@@ -580,11 +579,11 @@ void KisLayerBox::slotPropertiesClicked()
void KisLayerBox::slotCompositeOpChanged(int index)
{
Q_UNUSED(index);
if(!m_canvas) return;
KoID compositeOp;
if(m_wdgLayerBox->cmbComposite->entryAt(compositeOp, index))
m_nodeManager->nodeCompositeOpChanged(m_nodeManager->activeColorSpace()->compositeOp(compositeOp.id()));
QString compositeOp = m_wdgLayerBox->cmbComposite->selectedCompositeOp().id();
m_nodeManager->nodeCompositeOpChanged(m_nodeManager->activeColorSpace()->compositeOp(compositeOp));
}
void KisLayerBox::slotOpacityChanged()
......
......@@ -90,10 +90,10 @@ void KisCompositeOpOption::changeCompositeOp(const KoID& compositeOp)
void KisCompositeOpOption::slotCompositeOpChanged(const QModelIndex& index)
{
KoID compositeOp;
if(m_list->entryAt(compositeOp, index.row()))
changeCompositeOp(compositeOp);
Q_UNUSED(index);
KoID compositeOp = m_list->selectedCompositeOp();
changeCompositeOp(compositeOp);
}
void KisCompositeOpOption::slotEraserToggled(bool toggled)
......
......@@ -21,60 +21,32 @@
#include "kis_paintop_options_model.h"
#include "kis_paintop_option.h"
void KisPaintOpOptionListModel::addPaintOpOption(KisPaintOpOption* option, int widgetIndex)
{
BaseClass::addEntry(option->category(), KisOptionInfo(option, widgetIndex));
}
QString KisPaintOpOptionListModel::categoryToString(const QString& val) const
KisPaintOpOptionListModel::KisPaintOpOptionListModel(QObject *parent)
: BaseOptionCategorizedListModel(parent)
{
return val;
}
QString KisPaintOpOptionListModel::entryToString(const KisOptionInfo& val) const
{
return val.option->label();
}
QVariant KisPaintOpOptionListModel::data(const QModelIndex& idx, int role) const
void KisPaintOpOptionListModel::addPaintOpOption(KisPaintOpOption* option, int widgetIndex)
{
if (idx.isValid() && role == Qt::CheckStateRole) {
KisOptionInfo info;
DataItem *item = categoriesMapper()->addEntry(option->category(), KisOptionInfo(option, widgetIndex));
if (BaseClass::entryAt(info, idx.row()) && info.option->isCheckable())
return info.option->isChecked() ? Qt::Checked : Qt::Unchecked;
return QVariant();
if (option->isCheckable()) {
item->setCheckable(true);
}
return BaseClass::data(idx, role);
categoriesMapper()->expandAllCategories();
}
bool KisPaintOpOptionListModel::setData(const QModelIndex& idx, const QVariant& value, int role)
{
if (idx.isValid() && role == Qt::CheckStateRole) {
KisOptionInfo info;
if (BaseClass::entryAt(info, idx.row()) && info.option->isCheckable()) {
info.option->setChecked(value.toInt() == Qt::Checked);
return true;
}
return false;
}
if (!idx.isValid()) return false;
return BaseClass::setData(idx, value, role);
}
Qt::ItemFlags KisPaintOpOptionListModel::flags(const QModelIndex& idx) const
{
Qt::ItemFlags flags = 0;
KisOptionInfo info;
DataItem *item = categoriesMapper()->itemFromRow(idx.row());
Q_ASSERT(item);
if (idx.isValid() && BaseClass::entryAt(info, idx.row())) {
if (info.option->isCheckable())
flags |= Qt::ItemIsUserCheckable;
if (role == Qt::CheckStateRole && item->checkable()) {
item->data()->option->setChecked(value.toInt() == Qt::Checked);
}
return BaseClass::flags(idx) | flags;
return BaseOptionCategorizedListModel::setData(idx, value, role);
}
......@@ -22,8 +22,7 @@
#define _KIS_PAINTOP_OPTION_LIST_MODEL_H_
#include <kis_categorized_list_model.h>
class KisPaintOpOption;
#include <kis_paintop_option.h>
struct KisOptionInfo
{
......@@ -34,23 +33,23 @@ struct KisOptionInfo
int index;
};
struct OptionInfoToQStringConverter {
QString operator() (const KisOptionInfo &info) {
return info.option->label();
}
};
typedef KisCategorizedListModel<KisOptionInfo, OptionInfoToQStringConverter> BaseOptionCategorizedListModel;
/**
* This model can be use to show a list of visible composite op in a list view.
*/
class KisPaintOpOptionListModel: public KisCategorizedListModel<QString,KisOptionInfo>
class KisPaintOpOptionListModel : public BaseOptionCategorizedListModel
{
typedef KisCategorizedListModel<QString,KisOptionInfo> BaseClass;
public:
KisPaintOpOptionListModel(QObject *parent);
void addPaintOpOption(KisPaintOpOption* option, int widgetIndex);
virtual QVariant data(const QModelIndex& idx, int role=Qt::DisplayRole) const;
virtual bool setData(const QModelIndex& idx, const QVariant& value, int role=Qt::EditRole);
virtual Qt::ItemFlags flags(const QModelIndex& idx) const;
using QAbstractListModel::reset;
protected:
virtual QString categoryToString(const QString& val) const;
virtual QString entryToString(const KisOptionInfo& val) const;
};
#endif // _KIS_PAINTOP_OPTION_LIST_MODEL_H_
......@@ -36,7 +36,6 @@ struct KisPaintOpOptionsWidget::Private
QList<KisPaintOpOption*> paintOpOptions;
KisCategorizedListView* optionsList;
KisPaintOpOptionListModel* model;
KisCategorizedItemDelegate* delegate;
QStackedWidget* optionsStack;
};
......@@ -46,11 +45,10 @@ KisPaintOpOptionsWidget::KisPaintOpOptionsWidget(QWidget * parent)
{
setObjectName("KisPaintOpPresetsWidget");
m_d->model = new KisPaintOpOptionListModel();
m_d->delegate = new KisCategorizedItemDelegate(false);
m_d->optionsList = new KisCategorizedListView();
m_d->model = new KisPaintOpOptionListModel(this);
m_d->optionsList = new KisCategorizedListView(false, this);
m_d->optionsList->setModel(m_d->model);
m_d->optionsList->setItemDelegate(m_d->delegate);
m_d->optionsList->setItemDelegate(new KisCategorizedItemDelegate(false, this));
m_d->optionsList->setFixedWidth(128);
QSizePolicy policy = QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
......@@ -74,8 +72,6 @@ KisPaintOpOptionsWidget::KisPaintOpOptionsWidget(QWidget * parent)
KisPaintOpOptionsWidget::~KisPaintOpOptionsWidget()
{
qDeleteAll(m_d->paintOpOptions);
delete m_d->model;
delete m_d->delegate;
delete m_d;
}
......@@ -94,9 +90,7 @@ void KisPaintOpOptionsWidget::addPaintOpOption(KisPaintOpOption * option)
void KisPaintOpOptionsWidget::setConfiguration(const KisPropertiesConfiguration * config)
{
Q_ASSERT(!config->getString("paintop").isEmpty());
m_d->model->reset();
foreach(KisPaintOpOption* option, m_d->paintOpOptions) {
option->readOptionSetting(config);
}
......@@ -120,7 +114,7 @@ void KisPaintOpOptionsWidget::changePage(const QModelIndex& index)
{
KisOptionInfo info;
if(m_d->model->entryAt(info, index.row())) {
if(m_d->model->entryAt(info, index)) {
m_d->optionsStack->setCurrentIndex(info.index);
emit sigConfigurationItemChanged();
}
......
......@@ -62,6 +62,8 @@ set(kritaui_LIB_SRCS
kis_bookmarked_configurations_model.cc
kis_bookmarked_filter_configurations_model.cc
kis_canvas_resource_provider.cpp
kis_categories_mapper.cpp
kis_categorized_list_model.cpp
kis_categorized_item_delegate.cpp
kis_clipboard.cc
kis_config.cc
......
......@@ -107,8 +107,8 @@ KisDlgLayerProperties::KisDlgLayerProperties(KisLayerSP layer, KisView2 *view, K
m_page->cmbComposite->setEnabled(d->compositeOp);
if(d->compositeOp) {
m_page->cmbComposite->getModel()->validateCompositeOps(d->colorSpace);
m_page->cmbComposite->setCurrentIndex(m_page->cmbComposite->indexOf(KoID(d->compositeOp->id())));
m_page->cmbComposite->validate(d->colorSpace);
m_page->cmbComposite->selectCompositeOp(KoID(d->compositeOp->id()));
}
slotNameChanged(m_page->editName->text());
......@@ -223,12 +223,7 @@ int KisDlgLayerProperties::getOpacity() const
QString KisDlgLayerProperties::getCompositeOp() const
{
KoID compositeOp;
if(m_page->cmbComposite->entryAt(compositeOp, m_page->cmbComposite->currentIndex()))
return compositeOp.id();
return KoCompositeOpRegistry::instance().getDefaultCompositeOp().id();
return m_page->cmbComposite->selectedCompositeOp().id();
}
QBitArray KisDlgLayerProperties::getChannelFlags() const
......
/*
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_categories_mapper.h"
/*
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_CATEGORIES_MAPPER_H
#define __KIS_CATEGORIES_MAPPER_H
#include <QObject>
#include <boost/scoped_ptr.hpp>
#include <krita_export.h>
/**
* Templated classes cannot inherit QObject, so the signal handling is
* moved to a separate class
*/
class KRITAUI_EXPORT __CategoriesSignalsBase : public QObject
{
Q_OBJECT
signals:
void rowChanged(int row);
void beginInsertRow(int row);
void endInsertRow();
void beginRemoveRow(int row);
void endRemoveRow();
};
template<class TEntry, class TEntryToQStringConverter>
class KRITAUI_EXPORT KisCategoriesMapper : public __CategoriesSignalsBase
{
public:
class DataItem;
class DataItem
{
public:
DataItem(const QString &categoryName, KisCategoriesMapper *parent)
: m_name(categoryName),
m_category(0),
m_expanded(false),
m_enabled(true),
m_checkable(false),
m_checked(false),
m_parent(parent)
{
}
DataItem(const TEntry &entry, DataItem *category, KisCategoriesMapper *parent)
: m_data(new TEntry(entry)),
m_category(category),
m_expanded(false),
m_enabled(true),
m_checkable(false),
m_checked(false),
m_parent(parent)
{
Q_ASSERT(category);
TEntryToQStringConverter converter;
m_name = converter(entry);
}
TEntry* data() const {
return m_data.get();
}
QString name() const {
return m_name;
}
bool isCategory() const {
Q_ASSERT(static_cast<bool>(m_category) == static_cast<bool>(m_data));
return !m_category;
}
DataItem* parentCategory() const {
return m_category;
}
bool expanded() const {
return m_expanded;
}
void setExpanded(bool value) {
Q_ASSERT(isCategory());
m_expanded = value;
m_parent->notifyCategoryExpanded(this);
}
bool enabled() const {
return m_enabled;
}
void setEnabled(bool value) {
m_enabled = value;
notifyItemChanged();
}
bool checkable() const {
return m_checkable;
}
void setCheckable(bool value) {
m_checkable = value;
notifyItemChanged();
}
bool checked() const {
return m_checked;
}
void setChecked(bool value) {
m_checked = value;
notifyItemChanged();
}
private:
void notifyItemChanged() {
m_parent->notifyItemChanged(this);
}
private:
QString m_name;
boost::scoped_ptr<TEntry> m_data;
DataItem *m_category;
bool m_expanded;
bool m_enabled;
bool m_checkable;
bool m_checked;
KisCategoriesMapper *m_parent;
};
public:
KisCategoriesMapper() {}
virtual ~KisCategoriesMapper() {
qDeleteAll(m_items);
}
DataItem* addCategory(const QString &category) {
if (fetchCategory(category)) return 0;
DataItem *item = new DataItem(category, this);
emit beginInsertRow(m_items.size());
m_items.append(item);
emit endInsertRow();
return item;
}
void removeCategory(const QString &category) {
QMutableListIterator<DataItem*> it(m_items);
DataItem *categoryItem = 0;
int row = 0;
while(it.hasNext()) {
DataItem *item = it.next();
if (!item->isCategory() &&
item->parentCategory()->name() == category) {
emit beginRemoveRow(row);
it.remove();
delete item;
emit endRemoveRow();
} else {
if (item->isCategory() && item->name() == category) {
Q_ASSERT(!categoryItem);
categoryItem = item;
}
row++;
}
}
if (categoryItem) {
int row = m_items.indexOf(categoryItem);
emit beginRemoveRow(row);
delete m_items.takeAt(row);
emit endRemoveRow();
}
}
DataItem* addEntry(const QString &category, const TEntry &entry) {
DataItem *categoryItem = fetchCategory(category);
if (!categoryItem) {
categoryItem = addCategory(category);
}
DataItem *item = new DataItem(entry, categoryItem, this);
emit beginInsertRow(m_items.size());
m_items.append(item);
emit endInsertRow();
return item;
}
void removeEntry(const QString &category, const TEntry &entry) {
DataItem *item = fetchEntry(category, entry);
if (!item) return;
int row = m_items.indexOf(item);
emit beginRemoveRow(row);
delete m_items.takeAt(row);
emit endRemoveRow();
}
DataItem* fetchCategory(const QString &category) const {
foreach(DataItem *item, m_items) {
if (item->isCategory() && item->name() == category) return item;
}
return 0;
}
DataItem* fetchEntry(const QString &category, const TEntry &entry) const {
foreach(DataItem *item, m_items) {
if (!item->isCategory() &&
*item->data() == entry &&
item->parentCategory()->name() == category) return item;
}
return 0;
}
DataItem* fetchOneEntry(const TEntry &entry) const {
foreach(DataItem *item, m_items) {
if (!item->isCategory() &&
*item->data() == entry) return item;
}
return 0;
}
QVector<DataItem*> itemsForCategory(const QString &category) const {
QVector<DataItem*> filteredItems;
foreach(DataItem *item, m_items) {
if (!item->isCategory() &&
item->parentCategory()->name() == category) {
filteredItems.append(item);
}
}
return filteredItems;
}
void expandAllCategories() {
foreach(DataItem *item, m_items) {
if (item->isCategory()) {
item->setExpanded(true);
}
}
}
DataItem* itemFromRow(int row) const {
return m_items[row];
}
int rowFromItem(DataItem *item) const {
return m_items.indexOf(item);
}
int rowCount() const {
return m_items.size();
}
private:
void notifyItemChanged(DataItem *item) {
emit rowChanged(m_items.indexOf(item));
}
void notifyCategoryExpanded(DataItem *categoryItem) {
Q_ASSERT(categoryItem->isCategory());
notifyItemChanged(categoryItem);
foreach(DataItem *item, m_items) {
if (!item->isCategory() &&
item->parentCategory() == categoryItem) {
notifyItemChanged(item);
}
}
}
protected:
QList<DataItem*>& testingGetItems() {
return m_items;
}
private:
QList<DataItem*> m_items;
};
#endif /* __KIS_CATEGORIES_MAPPER_H */
......@@ -20,17 +20,16 @@
#include "kis_categorized_item_delegate.h"
#include "kis_categorized_list_model.h"
// #include <kstandardguiitem.h>
#include <QPainter>
#include <QStyle>
#include <QStyleOptionMenuItem>
#include <QStyleOptionViewItemV4>
#include <QApplication>
KisCategorizedItemDelegate::KisCategorizedItemDelegate(bool indicateError):
m_indicateError(indicateError),
m_minimumItemHeight(0)
KisCategorizedItemDelegate::KisCategorizedItemDelegate(bool indicateError, QObject *parent)
: QStyledItemDelegate(parent),
m_indicateError(indicateError),
m_minimumItemHeight(0)
{
}
......@@ -38,7 +37,7 @@ void KisCategorizedItemDelegate::paint(QPainter* painter, const QStyleOptionView
{
painter->resetTransform();
if(!index.data(IsHeaderRole).toBool()) {
if(!index.data(__CategorizedListModelBase::IsHeaderRole).toBool()) {
QStyleOptionViewItem sovi(option);
if(m_indicateError)
......@@ -61,7 +60,7 @@ void KisCategorizedItemDelegate::paint(QPainter* painter, const QStyleOptionView
option.rect.x(),
option.rect.y(),
option.rect.height(),
!index.data(ExpandCategoryRole).toBool()
!index.data(__CategorizedListModelBase::ExpandCategoryRole).toBool()
);
}
......
......@@ -30,7 +30,7 @@
class KRITAUI_EXPORT KisCategorizedItemDelegate: public QStyledItemDelegate
{
public:
KisCategorizedItemDelegate(bool indicateError);
KisCategorizedItemDelegate(bool indicateError, QObject *parent);
virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
......
/*
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_categorized_list_model.h"
__CategorizedListModelBase::__CategorizedListModelBase(QObject *parent)
: QAbstractListModel(parent)
{
}
__CategorizedListModelBase::~__CategorizedListModelBase()
{
}
This diff is collapsed.
......@@ -17,59 +17,77 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <KoCompositeOp.h>
#include "kis_composite_ops_model.h"
#include <KoCompositeOp.h>
#include <KoIcon.h>
#include "kis_composite_ops_model.h"
#include "kis_debug.h"
#include "kis_config.h"
struct CompositeOpModelInitializer
{
CompositeOpModelInitializer() {
model.addEntries(KoCompositeOpRegistry::instance().getCompositeOps(), false, true);
model.expandAllCategories(false);
model.addCategory(KoID("favorites", i18n("Favorites")));
model.readFavoriteCompositeOpsFromConfig();
model.expandCategory(KoID("favorites"), true);
}
KisCompositeOpListModel model;
};
KoID KisCompositeOpListModel::favoriteCategory() {
static KoID category("favorites", i18n("Favorites"));
return category;
}
KisCompositeOpListModel* KisCompositeOpListModel::sharedInstance()
{
static CompositeOpModelInitializer initializer;
return &initializer.model;
static KisCompositeOpListModel *model = 0;
if (!model) {
model = new KisCompositeOpListModel();
QMap<KoID, KoID> ops = KoCompositeOpRegistry::instance().getCompositeOps();