//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Copyright (C) 2019 Werner Schweer and others
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2.
//
//  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., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================

#include "palettemodel.h"

#include "libmscore/beam.h"
#include "libmscore/chordrest.h"
#include "libmscore/icon.h"
#include "libmscore/select.h"
#include "palettetree.h"
#include "preferences.h"
#include "scoreaccessibility.h"

namespace Ms {
//---------------------------------------------------------
//   PaletteTreeModel::PaletteTreeModel
//---------------------------------------------------------

PaletteTreeModel::PaletteTreeModel(std::unique_ptr<PaletteTree> tree, QObject* parent)
   : QAbstractItemModel(parent), _paletteTree(std::move(tree))
      {
      connect(this, &QAbstractItemModel::dataChanged, this, &PaletteTreeModel::onDataChanged);
      connect(this, &QAbstractItemModel::layoutChanged, this, &PaletteTreeModel::setTreeChanged);
      connect(this, &QAbstractItemModel::modelReset, this, &PaletteTreeModel::setTreeChanged);
      connect(this, &QAbstractItemModel::rowsInserted, this, &PaletteTreeModel::setTreeChanged);
      connect(this, &QAbstractItemModel::rowsMoved, this, &PaletteTreeModel::setTreeChanged);
      connect(this, &QAbstractItemModel::rowsRemoved, this, &PaletteTreeModel::setTreeChanged);
      }

//---------------------------------------------------------
//   PaletteTreeModel::onDataChanged
//---------------------------------------------------------

void PaletteTreeModel::onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
      {
      Q_UNUSED(topLeft);
      Q_UNUSED(bottomRight);
      static const std::set<int> nonPersistentRoles({ CellActiveRole, PaletteExpandedRole });

      bool treeChanged = false;
      for (int role : roles) {
            if (!nonPersistentRoles.count(role)) {
                  treeChanged = true;
                  break;
                  }
            }

      if (treeChanged)
            setTreeChanged();
      }

//---------------------------------------------------------
//   PaletteTreeModel::iptrToPalettePanel
//---------------------------------------------------------

PalettePanel* PaletteTreeModel::iptrToPalettePanel(void* iptr, int* idx)
      {
      const auto palette = std::find_if(
         palettes().begin(), palettes().end(),
         [iptr](const std::unique_ptr<PalettePanel>& p) {
               return iptr == p.get();
               });

      if (idx)
            (*idx) = palette - palettes().begin();

      if (palette != palettes().end())
            return static_cast<PalettePanel*>(iptr);
      return nullptr;
      }

//---------------------------------------------------------
//   PaletteTreeModel::findPalettePanel
//---------------------------------------------------------

const PalettePanel* PaletteTreeModel::findPalettePanel(const QModelIndex& index) const
      {
      if (index.internalPointer() != _paletteTree.get())
            return nullptr;

      const int row = index.row();
      if (index.column() != 0 || row < 0 || row > int(palettes().size()))
            return nullptr;

      return palettes()[row].get();
      }

//---------------------------------------------------------
//   PaletteTreeModel::findPalettePanel
//---------------------------------------------------------

PalettePanel* PaletteTreeModel::findPalettePanel(const QModelIndex& index)
      {
      return const_cast<PalettePanel*>(const_cast<const PaletteTreeModel*>(this)->findPalettePanel(index));
      }

//---------------------------------------------------------
//   PaletteTreeModel::findCell
//---------------------------------------------------------

PaletteCellConstPtr PaletteTreeModel::findCell(const QModelIndex& index) const
      {
      if (const PalettePanel* pp = iptrToPalettePanel(index.internalPointer())) {
            const int row = index.row();
            if (index.column() != 0 || row < 0 || row >= pp->ncells())
                  return nullptr;
            return pp->cell(row);
            }

      return nullptr;
      }

//---------------------------------------------------------
//   PaletteTreeModel::findCell
//---------------------------------------------------------

PaletteCellPtr PaletteTreeModel::findCell(const QModelIndex& index)
      {
      return std::const_pointer_cast<PaletteCell>(const_cast<const PaletteTreeModel*>(this)->findCell(index));
      }

//---------------------------------------------------------
//   PaletteTreeModel::setPaletteTree
//---------------------------------------------------------

void PaletteTreeModel::setPaletteTree(std::unique_ptr<PaletteTree> newTree)
      {
      beginResetModel();
      _paletteTree = std::move(newTree);
      endResetModel();

      _treeChanged = false;
      }

//---------------------------------------------------------
//   PaletteTreeModel::index
//---------------------------------------------------------

QModelIndex PaletteTreeModel::index(int row, int column, const QModelIndex& parent) const
      {
      if (!hasIndex(row, column, parent))
            return QModelIndex();

      if (!parent.isValid())
            return createIndex(row, column, const_cast<void*>(static_cast<const void*>(_paletteTree.get())));

      void* iptr = parent.internalPointer();

      if (iptr == _paletteTree.get())
            return createIndex(row, column, palettes()[parent.row()].get());

      return QModelIndex();
      }

//---------------------------------------------------------
//   PaletteTreeModel::parent
//---------------------------------------------------------

QModelIndex PaletteTreeModel::parent(const QModelIndex& modelIndex) const
      {
      void* iptr = modelIndex.internalPointer();

      if (iptr == _paletteTree.get())
            return QModelIndex();

      int row;
      if (iptrToPalettePanel(iptr, &row))
            return index(row, /* column */ 0, QModelIndex());

      return QModelIndex();
      }

//---------------------------------------------------------
//   PaletteTreeModel::rowCount
//---------------------------------------------------------

int PaletteTreeModel::rowCount(const QModelIndex& parent) const
      {
      if (!parent.isValid())
            return palettes().size();

      void* iptr = parent.internalPointer();

      if (iptr == _paletteTree.get()) {
            const int row = parent.row();
            if (parent.column() != 0 || row < 0 || row >= int(palettes().size()))
                  return 0;
            return palettes()[row]->ncells();
            }

      return 0;
      }

//---------------------------------------------------------
//   PaletteTreeModel::columnCount
//---------------------------------------------------------

int PaletteTreeModel::columnCount(const QModelIndex& parent) const
      {
      Q_UNUSED(parent);
      return 1;
      }

//---------------------------------------------------------
//   PaletteTreeModel::data
//---------------------------------------------------------

QVariant PaletteTreeModel::data(const QModelIndex& index, int role) const
      {
      if (const PalettePanel* pp = findPalettePanel(index)) {
            switch (role) {
                  case Qt::DisplayRole:
                  case Qt::AccessibleTextRole:
                        return qApp->translate("Palette", pp->name().toUtf8());
                  case VisibleRole:
                        return pp->visible();
                  case CustomRole:
                        return pp->custom();
                  case EditableRole:
                        return pp->editable();
                  case GridSizeRole:
                        return pp->gridSize();
                  case DrawGridRole:
                        return pp->drawGrid();
                  case PaletteExpandedRole:
                        return pp->expanded();
                  // TODO showMore?
                  case PaletteTypeRole:
                        return QVariant::fromValue(pp->type());
                  }
            return QVariant();
            }

      if (PaletteCellConstPtr cell = findCell(index)) {
            switch (role) {
                  case Qt::DisplayRole: // TODO don't display cell names in palettes
                  case Qt::ToolTipRole:
                        return cell->translatedName();
                  case Qt::AccessibleTextRole: {
                        QString name = cell->translatedName();
                        ScoreAccessibility::makeReadable(name);
                        return name;
                        }
                  case Qt::DecorationRole: {
                        qreal extraMag = 1.0;
                        if (const PalettePanel* pp = iptrToPalettePanel(index.internalPointer()))
                              extraMag = pp->mag();
                        return QIcon(new PaletteCellIconEngine(cell, extraMag));
                        }
                  case PaletteCellRole:
                        return QVariant::fromValue(cell.get());
                  case VisibleRole:
                        return cell->visible;
                  case CustomRole:
                        return cell->custom;
                  case EditableRole: {
                        if (const PalettePanel* pp = iptrToPalettePanel(index.internalPointer()))
                              return pp->editable();
                        return false;
                        }
                  case MimeDataRole: {
                        QVariantMap map;
                        if (cell->element)
                              map[mimeSymbolFormat] = cell->element->mimeData(QPointF());
                        map[PaletteCell::mimeDataFormat] = cell->mimeData();
                        return map;
                        }
                  case CellActiveRole:
                        return cell->active;
                  default:
                        break;
                  }
            return QVariant();
            }

      // data for root item
      switch (role) {
            case EditableRole:
                  return true;
            default:
                  break;
            }

      return QVariant();
      }

//---------------------------------------------------------
//   PaletteTreeModel::setData
//---------------------------------------------------------

bool PaletteTreeModel::setData(const QModelIndex& index, const QVariant& value, int role)
      {
      if (PalettePanel* pp = findPalettePanel(index)) {
            switch (role) {
                  case VisibleRole:
                        if (value.canConvert<bool>()) {
                              const bool val = value.toBool();
                              if (val != pp->visible()) {
                                    pp->setVisible(val);
                                    emit dataChanged(index, index, { VisibleRole });
                                    }
                              return true;
                              }
                        return false;
                  case EditableRole:
                        if (value.canConvert<bool>()) {
                              const bool val = value.toBool();
                              if (val != pp->editable()) {
                                    pp->setEditable(val);
                                    emit dataChanged(index, index, { EditableRole });
                                    // notify cells editability changed too
                                    const QModelIndex childFirstIndex = PaletteTreeModel::index(0, 0, index);
                                    const int rows = rowCount(index);
                                    const QModelIndex childLastIndex = PaletteTreeModel::index(rows - 1, 0, index);
                                    emit dataChanged(childFirstIndex, childLastIndex, { EditableRole });
                              }
                              return true;
                        }
                        return false;
                  case PaletteExpandedRole:
                        if (value.canConvert<bool>()) {
                              const bool val = value.toBool();
                              if (val != pp->expanded()) {
                                    const bool singlePalette = preferences.getBool(PREF_APP_USESINGLEPALETTE);

                                    if (singlePalette && val) {
                                          for (auto& palette : palettes())
                                                palette->setExpanded(false);
                                          pp->setExpanded(val);

                                          const QModelIndex parent = index.parent();
                                          const int rows = rowCount(parent);
                                          const QModelIndex first = PaletteTreeModel::index(0, 0, parent);
                                          const QModelIndex last = PaletteTreeModel::index(rows - 1, 0, parent);
                                          emit dataChanged(first, last, { PaletteExpandedRole });
                                          }
                                    else {
                                          pp->setExpanded(val);
                                          emit dataChanged(index, index, { PaletteExpandedRole });
                                          }
                                    }
                              return true;
                              }
                        return false;
//                   case CustomRole:
//                         if (value.canConvert<bool>()) {
//                               const bool val = value.toBool();
//                               if (val != pp->custom()) {
//                                     pp->setCustom(val);
//                                     emit dataChanged(index, index, { CustomRole });
//                                     }
//                               return true;
//                               }
//                         return false;
//                   case Qt::DisplayRole:
//                   case Qt::AccessibleTextRole:
//                         return pp->name();
//                   case gridSizeRole:
//                         return pp->gridSize();
//                   case drawGridRole:
//                         return pp->drawGrid();
                  default:
                        break;
                  }
            return false;
            }

      if (PaletteCellPtr cell = findCell(index)) {
            switch (role) {
//                   case Qt::DisplayRole:
//                   case Qt::ToolTipRole:
                  // or EditRole?
//                         return cell->name;
                  case PaletteCellRole: {
                        PaletteCell* newCell = value.value<PaletteCell*>();
                        if (!newCell)
                              return false;
                        *cell = std::move(*newCell);
                        emit dataChanged(index, index);
                        return true;
                        };
                  case VisibleRole:
                        if (value.canConvert<bool>()) {
                              const bool val = value.toBool();
                              if (val != cell->visible) {
                                    cell->visible = val;
                                    emit dataChanged(index, index, { VisibleRole });
                                    }
                              return true;
                              }
                        return false;
                  case CustomRole:
                        if (value.canConvert<bool>()) {
                              const bool val = value.toBool();
                              if (val != cell->custom) {
                                    cell->custom = val;
                                    emit dataChanged(index, index, { CustomRole });
                                    }
                              return true;
                              }
                        return false;
                  case MimeDataRole: {
                        const QVariantMap map = value.toMap();

                        if (map.contains(PaletteCell::mimeDataFormat)) {
                              const QByteArray cellMimeData = map[PaletteCell::mimeDataFormat].toByteArray();
                              PaletteCellPtr newCell(PaletteCell::readMimeData(cellMimeData));
                              if (!newCell)
                                    return false;
                              *cell = std::move(*newCell);
                              }
                        else if (map.contains(mimeSymbolFormat)) {
                              const QByteArray elementMimeData = map[mimeSymbolFormat].toByteArray();
                              *cell = std::move(*PaletteCell::readElementMimeData(elementMimeData));
                              cell->custom = true; // mark the updated cell custom
                              }
                        else {
                              return false;
                              }

                        emit dataChanged(index, index);
                        return true;
                        }
                  default:
                        break;
                  }
            return false;
            }

      return false;
      }

//---------------------------------------------------------
//   PaletteTreeModel::roleNames
//---------------------------------------------------------

QHash<int, QByteArray> PaletteTreeModel::roleNames() const
      {
      QHash<int, QByteArray> roles(QAbstractItemModel::roleNames());
      roles[MimeDataRole] = "mimeData";
      roles[GridSizeRole] = "gridSize";
      roles[DrawGridRole] = "drawGrid";
      roles[CustomRole] = "custom";
      roles[EditableRole] = "editable";
      roles[PaletteExpandedRole] = "expanded";
      roles[CellActiveRole] = "cellActive";
      roles[Qt::AccessibleTextRole] = "accessibleText";
      return roles;
      }

//---------------------------------------------------------
//   PaletteTreeModel::flags
//---------------------------------------------------------

Qt::ItemFlags PaletteTreeModel::flags(const QModelIndex& index) const
      {
      return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled;
      }

//---------------------------------------------------------
//   PaletteTreeModel::supportedDropActions
//---------------------------------------------------------

Qt::DropActions PaletteTreeModel::supportedDropActions() const
      {
      return Qt::DropActions(Qt::CopyAction | Qt::MoveAction);
      }

//---------------------------------------------------------
//   PaletteTreeModel::mimeData
//---------------------------------------------------------

QMimeData* PaletteTreeModel::mimeData(const QModelIndexList& indexes) const
      {
      QMimeData* mime = QAbstractItemModel::mimeData(indexes); // TODO: needed or use only "our" MIME data?

      if (indexes.empty() || indexes.size() > 1)
            return mime;

      if (const PalettePanel* pp = findPalettePanel(indexes[0])) {
            mime->setData(PalettePanel::mimeDataFormat, pp->mimeData());
            }
      else if (PaletteCellConstPtr cell = findCell(indexes[0])) {
            mime->setData(mimeSymbolFormat, cell->element->mimeData(QPointF()));
            }

      return mime;
      }

//---------------------------------------------------------
//   PaletteTreeModel::mimeTypes
//---------------------------------------------------------

QStringList PaletteTreeModel::mimeTypes() const
      {
      QStringList types = QAbstractItemModel::mimeTypes();
      types << mimeSymbolFormat;
      return types;
      }

//---------------------------------------------------------
//   PaletteTreeModel::canDropMimeData
//---------------------------------------------------------

bool PaletteTreeModel::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
      {
      Q_UNUSED(column);

      if (!parent.isValid())
            return (action == Qt::CopyAction) && data->hasFormat(PalettePanel::mimeDataFormat);

      if (const PalettePanel* pp = findPalettePanel(parent)) {
            if (row < 0 || row > pp->ncells())
                  return false;

            if (data->hasFormat(PaletteCell::mimeDataFormat))
                  return action & (Qt::CopyAction | Qt::MoveAction);
            else if (data->hasFormat(mimeSymbolFormat))
                  return action == Qt::CopyAction;
            }
      return false;
      }

//---------------------------------------------------------
//   PaletteTreeModel::dropMimeData
//---------------------------------------------------------

bool PaletteTreeModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
      {
      Q_UNUSED(column); // when dropping at the end of palette, column == -1. Probably an effect of proxy models

      if (!parent.isValid()) {
            if (action != Qt::CopyAction || !data->hasFormat(PalettePanel::mimeDataFormat))
                  return false;
            if (row < 0 || row > int(palettes().size()))
                  return false;

            auto panel = PalettePanel::readMimeData(data->data(PalettePanel::mimeDataFormat));
            if (!panel)
                  return false;

            beginInsertRows(parent, row, row);
            palettes().insert(palettes().begin() + row, std::move(panel));
            endInsertRows();
            return true;
            }

      if (PalettePanel* pp = findPalettePanel(parent)) {
            if (row < 0 || row > pp->ncells())
                  return false;

            PaletteCellPtr cell;

            if (data->hasFormat(PaletteCell::mimeDataFormat)) {
                  cell = PaletteCell::readMimeData(data->data(PaletteCell::mimeDataFormat));
                  if (action == Qt::CopyAction)
                        cell->custom = true;
                  }
            else if (data->hasFormat(mimeSymbolFormat)) {
                  cell = PaletteCell::readElementMimeData(data->data(mimeSymbolFormat));
                  cell->custom = true; // the cell is created by dropping an element so it is custom
                  }

            if (!cell)
                  return false;

            beginInsertRows(parent, row, row);
            pp->insertCell(row, std::move(cell));
            endInsertRows();
            return true;
            }
      return false;
      }

//---------------------------------------------------------
//   PaletteTreeModel::moveRows
//---------------------------------------------------------

bool PaletteTreeModel::moveRows(const QModelIndex& sourceParent, int sourceRow, int count, const QModelIndex& destinationParent, int destinationChild)
      {
      const bool sameParent = sourceParent == destinationParent;

      if (!sourceParent.isValid()) {
            // moving palette panels
            if (sourceRow + count > int(palettes().size()) || destinationChild >= int(palettes().size()))
                  return false;

            if (!sameParent) {
                  // Cannot move between different parents, at least for now
                  // TODO: reconsider?
                  return false;
                  }

            // The moved rows are considered to be inserted *before* destinationRow,
            // so if we want to move a row down in the list within the same parent
            // then we should increment the destinationChild index.
            const int destinationRow = (destinationChild >= sourceRow) ? destinationChild + 1 : destinationChild;

            if (sameParent && sourceRow == destinationRow)
                return false;

            std::vector<std::unique_ptr<PalettePanel>> movedPanels;

            auto srcBegin = palettes().begin() + sourceRow;
            auto srcEnd = srcBegin + count;

            // As of Qt 5.12, Qt proxy models rebuild the entire index mapping if
            // layoutChanged() gets emitted (i.e. if begin/endMoveRows() gets called).
            // Performance is much better when doing remove + insert rows instead.
            beginRemoveRows(sourceParent, sourceRow, sourceRow + count - 1);
            movedPanels.reserve(count);
            movedPanels.insert(movedPanels.end(), std::make_move_iterator(srcBegin), std::make_move_iterator(srcEnd));
            palettes().erase(srcBegin, srcEnd);
            endRemoveRows();

            const int destIdx = (destinationRow < sourceRow) ? destinationRow : (destinationRow - count);
            auto dest = palettes().begin() + destIdx;

            beginInsertRows(destinationParent, destIdx, destIdx + count - 1);
            palettes().insert(dest, std::make_move_iterator(movedPanels.begin()), std::make_move_iterator(movedPanels.end()));
            endInsertRows();

            return true;
            }

      PalettePanel* sourcePanel = findPalettePanel(sourceParent);
      PalettePanel* destPanel = sameParent ? sourcePanel : findPalettePanel(destinationParent);
      if (sourcePanel && destPanel) {
            // moving palette cells
            if (sourceRow + count > sourcePanel->ncells() || destinationChild > destPanel->ncells())
                  return false;

            // The moved rows are considered to be inserted *before* destinationRow,
            // so if we want to move a row down in the list within the same parent
            // then we should increment the destinationChild index.
            const int destinationRow = (sameParent && destinationChild >= sourceRow) ? destinationChild + 1 : destinationChild;

            if (sameParent && sourceRow == destinationRow)
                return false;

            // As of Qt 5.12, Qt proxy models rebuild the entire index mapping if
            // layoutChanged() gets emitted (i.e. if begin/endMoveRows() gets called).
            // Performance is much better when doing remove + insert rows instead.
            beginRemoveRows(sourceParent, sourceRow, sourceRow + count - 1);
            auto movedCells(sourcePanel->takeCells(sourceRow, count));
            endRemoveRows();

            const int destIdx = (sameParent && destinationRow >= sourceRow) ? (destinationRow - count) : destinationRow;

            beginInsertRows(destinationParent, destIdx, destIdx + count - 1);
            destPanel->insertCells(destIdx, std::move(movedCells));
            endInsertRows();

            return true;
            }

      return false;
      }

//---------------------------------------------------------
//   PaletteTreeModel::removeRows
//---------------------------------------------------------

bool PaletteTreeModel::removeRows(int row, int count, const QModelIndex& parent)
      {
      if (!parent.isValid()) {
            // removing palette panels
            if (row < 0 || row + count > int(palettes().size()))
                  return false;

            beginRemoveRows(parent, row, row + count - 1);
            auto rangeBegin = palettes().begin() + row;
            auto rangeEnd = rangeBegin + count;
            palettes().erase(rangeBegin, rangeEnd);
            endRemoveRows();
            return true;
            }

      if (PalettePanel* panel = findPalettePanel(parent)) {
            // removing palette cells
            if (row < 0 || row + count > panel->ncells())
                  return false;

            beginRemoveRows(parent, row, row + count - 1);
            panel->takeCells(row, count);
            endRemoveRows();
            return true;
            }

      return false;
      }

//---------------------------------------------------------
//   PaletteTreeModel::insertRows
//---------------------------------------------------------

bool PaletteTreeModel::insertRows(int row, int count, const QModelIndex& parent)
      {
      if (!parent.isValid()) {
            // inserting palette panels
            if (row < 0 || row > int(palettes().size()))
                  return false;

            beginInsertRows(parent, row, row + count - 1);
            for (int i = 0; i < count; ++i) {
                  std::unique_ptr<PalettePanel> p(new PalettePanel(PalettePanel::Type::Custom));
                  p->setName(QT_TRANSLATE_NOOP("Palette", "Custom"));
                  p->setExpanded(true);
                  palettes().insert(palettes().begin() + row, std::move(p));
                  }
            endInsertRows();
            return true;
            }

      if (PalettePanel* panel = findPalettePanel(parent)) {
            // inserting palette cells
            if (row < 0 || row > panel->ncells())
                  return false;

            beginInsertRows(parent, row, row + count - 1);
            for (int i = 0; i < count; ++i)
                  panel->insertCell(row, PaletteCellPtr(new PaletteCell));
            endInsertRows();
            return true;
            }

      return false;
      }

//---------------------------------------------------------
//   PaletteTreeModel::updateCellsState
//---------------------------------------------------------

void PaletteTreeModel::updateCellsState(const Selection& sel, bool deactivateAll)
      {
      const ChordRest* cr = sel.cr();
      const IconType beamIconType = cr ? Beam::iconType(cr->beamMode()) : IconType::NONE;

      if (!sel.isSingle() || !cr)
            deactivateAll = true;

      const int npalettes = palettes().size();
      for (int row = 0; row < npalettes; ++row) {
            PalettePanel* palette = palettes()[row].get();
            // TODO: should this be turned on for all palettes?
            if (palette->type() != PalettePanel::Type::Beam)
                  continue;

            for (int ci = 0; ci < palette->ncells(); ++ci) {
                  PaletteCellPtr cell = palette->cell(ci);
                  if (deactivateAll)
                        cell->active = false;
                  else if (cell->element && cell->element->isIcon()) {
                        const Icon* icon = toIcon(cell->element.get());
                        cell->active = (icon->iconType() == beamIconType);
                        }
                  }

            const QModelIndex parent = index(row, 0, QModelIndex());
            const QModelIndex first = index(0, 0, parent);
            const QModelIndex last = index(palette->ncells() - 1, 0, parent);
            emit dataChanged(first, last, { CellActiveRole });
            }
      }

//---------------------------------------------------------
//   PaletteTreeModel::retranslate
//---------------------------------------------------------

void PaletteTreeModel::retranslate()
      {
      _paletteTree->retranslate();
      }

//---------------------------------------------------------
//   PaletteTreeModel::findPaletteCell
//---------------------------------------------------------

QModelIndex PaletteTreeModel::findPaletteCell(const PaletteCell& cell, const QModelIndex& parent) const
      {
      if (const PalettePanel* pp = findPalettePanel(parent)) {
            const int idx = pp->findPaletteCell(cell);
            if (idx == -1)
                  return QModelIndex();
            return index(idx, /* column */ 0, parent);
            }
      return QModelIndex();
      }

//---------------------------------------------------------
//   PaletteTreeModel::match
///   Currently only searching for a given palette cell is
///   implemented, for anything else the corresponding
///   member function of QAbstractItemModel is used.
//---------------------------------------------------------

QModelIndexList PaletteTreeModel::match(const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags) const
      {
      if (role != PaletteCellRole || flags != Qt::MatchExactly || hits != 1
         || !value.canConvert<const PaletteCell*>()
         || !findPalettePanel(start.parent())
         )
            return QAbstractItemModel::match(start, role, value, hits, flags);

      const PaletteCell* cell = value.value<const PaletteCell*>();
      if (!cell)
            return QModelIndexList();

      return QModelIndexList({ findPaletteCell(*cell, start.parent()) });
      }

//---------------------------------------------------------
//   PaletteTreeModel::itemDataChanged
//---------------------------------------------------------

void PaletteTreeModel::itemDataChanged(const QModelIndex& idx)
      {
      emit dataChanged(idx, idx);
      if (findPalettePanel(idx)) {
            // palette cells appearance depends on palette settings
            const QModelIndex childFirstIndex = index(0, 0, idx);
            const int rows = rowCount(idx);
            const QModelIndex childLastIndex = index(rows - 1, 0, idx);
            emit dataChanged(childFirstIndex, childLastIndex, { Qt::DecorationRole });
            }
      }

//---------------------------------------------------------
//   PaletteCellFilter::addChainedFilter
///   Ownership over the added filter is passed to this
///   filter.
//---------------------------------------------------------

void PaletteCellFilter::addChainedFilter(PaletteCellFilter* newFilter)
      {
      PaletteCellFilter* f = this;
      while (f->chainedFilter)
            f = f->chainedFilter;

      newFilter->setParent(f);
      f->chainedFilter = newFilter;
      connect(newFilter, &PaletteCellFilter::filterChanged, f, &PaletteCellFilter::filterChanged);
      }

//---------------------------------------------------------
//   PaletteCellFilter::accept
//---------------------------------------------------------

bool PaletteCellFilter::accept(const PaletteCell& cell) const
      {
      const PaletteCellFilter* f = this;
      while (f) {
            if (!f->acceptCell(cell))
                  return false;
            f = f->chainedFilter;
            }
      return true;
      }

//---------------------------------------------------------
//   PaletteCellFilter::connectToModel
//---------------------------------------------------------

void PaletteCellFilter::connectToModel(const QAbstractItemModel* model)
      {
//       TODO: are all these needed?
//       columnsInserted(const QModelIndex &parent, int first, int last)
//       columnsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int column)
//       columnsRemoved(const QModelIndex &parent, int first, int last)
      connect(model, &QAbstractItemModel::dataChanged, this, &PaletteCellFilter::filterChanged);
//       dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = ...)
      connect(model, &QAbstractItemModel::layoutChanged, this, &PaletteCellFilter::filterChanged);
//       layoutChanged(const QList<QPersistentModelIndex> &parents = ..., QAbstractItemModel::LayoutChangeHint hint = ...)
      connect(model, &QAbstractItemModel::modelReset, this, &PaletteCellFilter::filterChanged);
//       modelReset()
      connect(model, &QAbstractItemModel::rowsInserted, this, &PaletteCellFilter::filterChanged);
//       rowsInserted(const QModelIndex &parent, int first, int last)
      connect(model, &QAbstractItemModel::rowsMoved, this, &PaletteCellFilter::filterChanged);
//       rowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
      connect(model, &QAbstractItemModel::rowsRemoved, this, &PaletteCellFilter::filterChanged);
//       rowsRemoved(const QModelIndex &parent, int first, int last)
      }

//---------------------------------------------------------
//   ExcludePaletteCellFilter
//---------------------------------------------------------

class ExcludePaletteCellFilter : public PaletteCellFilter {
      const PalettePanel* excludePanel;
      const QPersistentModelIndex panelIndex; // filter is valid as long as this index is valid too

   public:
      ExcludePaletteCellFilter(const PalettePanel* p, QPersistentModelIndex index, QObject* parent = nullptr)
         : PaletteCellFilter(parent), excludePanel(p), panelIndex(std::move(index)) {}

      bool acceptCell(const PaletteCell& cell) const override
            {
            return panelIndex.isValid() && -1 == excludePanel->findPaletteCell(cell, /* matchName */ false);
            }
      };

//---------------------------------------------------------
//   PaletteTreeModel::getFilter
///   The ownership of the returned filter is passed to a
///   caller.
//---------------------------------------------------------

PaletteCellFilter* PaletteTreeModel::getFilter(const QModelIndex& index) const
      {
      if (const PalettePanel* pp = findPalettePanel(index)) {
            ExcludePaletteCellFilter* filter = new ExcludePaletteCellFilter(pp, index);
            filter->connectToModel(this);
            return filter;
            }

      // TODO: make a filter for a single cell?

      return nullptr;
      }

//---------------------------------------------------------
//   FilterPaletteTreeModel::FilterPaletteTreeModel
//---------------------------------------------------------

FilterPaletteTreeModel::FilterPaletteTreeModel(PaletteCellFilter* filter, PaletteTreeModel* model, QObject* parent)
   : QSortFilterProxyModel(parent), cellFilter(filter)
      {
      cellFilter->setParent(this);
//       connect(cellFilter, &PaletteCellFilter::filterChanged, this, &QSortFilterProxyModel::invalidate);
      connect(cellFilter, &PaletteCellFilter::filterChanged, this, &FilterPaletteTreeModel::invalidateFilter);

      setSourceModel(model);
      }

//---------------------------------------------------------
//   FilterPaletteTreeModel::filterAcceptsRow
//---------------------------------------------------------

bool FilterPaletteTreeModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
      {
      const QModelIndex& cellIndex = sourceModel()->index(sourceRow, /* column */ 0, sourceParent);
      const QVariant cellData = sourceModel()->data(cellIndex, PaletteTreeModel::PaletteCellRole);
      const PaletteCell* cell = cellData.value<const PaletteCell*>();
      if (!cell) // a palette panel or just an unrelated model
            return true;
      return cellFilter->accept(*cell);
      }

//---------------------------------------------------------
//   ChildFilterProxyModel::filterAcceptsRow
//---------------------------------------------------------

bool ChildFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
      {
      const QAbstractItemModel* model = sourceModel();
      const QModelIndex rowIndex = model->index(sourceRow, 0, sourceParent);
      const int rowCount = model->rowCount(rowIndex);

      if (rowCount == 0)
            return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);

      for (int i = 0; i < rowCount; ++i) {
            if (filterAcceptsRow(i, rowIndex))
                  return true;
            }

      return false;
      }
} // namespace Ms
