//=============================================================================
//  MuseScore
//  Linux Music Score Editor
//  $Id: instrdialog.cpp 5580 2012-04-27 15:36:57Z wschweer $
//
//  Copyright (C) 2002-2009 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 "config.h"
#include "icons.h"
#include "instrwidget.h"
 
#include "libmscore/clef.h"
#include "libmscore/instrtemplate.h"
#include "libmscore/line.h"
#include "libmscore/measure.h"
#include "libmscore/part.h"
#include "libmscore/score.h"
#include "libmscore/segment.h"
#include "libmscore/staff.h"
#include "libmscore/stafftype.h"
#include "libmscore/style.h"
#include "libmscore/system.h"
#include "libmscore/stringdata.h"
#include "libmscore/undo.h"
#include "libmscore/keysig.h"
 
namespace Ms {
 
void filterInstruments(QTreeWidget *instrumentList, const QString &searchPhrase = QString());
 
//---------------------------------------------------------
//   filterInstruments
//---------------------------------------------------------
 
void filterInstruments(QTreeWidget* instrumentList, const QString &searchPhrase)
      {
      QTreeWidgetItem* item = 0;
 
      for (int idx = 0; (item = instrumentList->topLevelItem(idx)); ++idx) {
            int numMatchedChildren = 0;
            QTreeWidgetItem* ci = 0;
 
            for (int cidx = 0; (ci = item->child(cidx)); ++cidx) {
                  // replace the unicode b (accidential) so a search phrase of "bb" would give Bb Trumpet...
                  QString text = ci->text(0).replace(QChar(0x266d), QChar('b'));
                  bool isMatch = text.contains(searchPhrase, Qt::CaseInsensitive);
                  ci->setHidden(!isMatch);
 
                  if (isMatch)
                        numMatchedChildren++;
                  }
 
            item->setHidden(numMatchedChildren == 0);
            item->setExpanded(numMatchedChildren > 0 && !searchPhrase.isEmpty());
            }
      }
 
//---------------------------------------------------------
//   StaffListItem
//---------------------------------------------------------
 
StaffListItem::StaffListItem(PartListItem* li)
   : QTreeWidgetItem(li, STAFF_LIST_ITEM)
      {
      setPartIdx(0);
      setLinked(false);
      setClefType(ClefTypeList(ClefType::G));
      initStaffTypeCombo();
      }
 
StaffListItem::StaffListItem()
   : QTreeWidgetItem(STAFF_LIST_ITEM)
      {
      setPartIdx(0);
      setLinked(false);
      setClefType(ClefTypeList(ClefType::G));
      }
 
//---------------------------------------------------------
//   initStaffTypeCombo
//---------------------------------------------------------
 
void StaffListItem::initStaffTypeCombo(bool forceRecreate)
      {
      if (_staffTypeCombo && !forceRecreate)    // do not init more than once
            return;
 
      // NOTE: DO NOT DELETE the old _staffTypeCombo if already created:
      // a bug in Qt looses track of (and presumably deletes) the combo set into the item
      // if the item is repositioned in the tree; in this case, the pointer to the combo
      // is no longer valid and cannot be used to delete it
      // Call initStaffTypeCombo(true) ONLY if the item has been repositioned
      // or a memory leak may result
 
      bool canUseTabs = false; // assume only normal staves are applicable
      bool canUsePerc = false;
      PartListItem* part = static_cast<PartListItem*>(QTreeWidgetItem::parent());
 
      // PartListItem has different members filled out if used in New Score Wizard
      // or in Instruments Wizard
      if (part) {
            const StringData* stringData = part->it ? &(part->it->stringData) :
                        ( (part->part && part->part->instrument()) ? part->part->instrument()->stringData() : 0);
            canUseTabs = stringData && stringData->strings() > 0;
            canUsePerc = part->it ? part->it->useDrumset :
                        ( (part->part && part->part->instrument()) ? part->part->instrument()->useDrumset() : false);
            }
      _staffTypeCombo = new QComboBox();
      _staffTypeCombo->setAutoFillBackground(true);
      int idx = 0;
      for (const StaffType& st : StaffType::presets()) {
            if ( (st.group() == StaffGroup::STANDARD && (!canUsePerc))    // percussion excludes standard
                        || (st.group() == StaffGroup::PERCUSSION && canUsePerc)
                        || (st.group() == StaffGroup::TAB && canUseTabs)) {
                  _staffTypeCombo->addItem(st.name(), idx);
                  }
            ++idx;
            }
      treeWidget()->setItemWidget(this, 4, _staffTypeCombo);
      connect(_staffTypeCombo, SIGNAL(currentIndexChanged(int)), SLOT(staffTypeChanged(int)) );
      }
 
//---------------------------------------------------------
//   setPartIdx
//---------------------------------------------------------
 
void StaffListItem::setPartIdx(int val)
      {
      _partIdx = val;
      setText(0, InstrumentsWidget::tr("Staff %1").arg(_partIdx + 1));
      }
 
//---------------------------------------------------------
//   setClefType
//---------------------------------------------------------
 
void StaffListItem::setClefType(const ClefTypeList& val)
      {
      _clefType = val;
      setText(2, qApp->translate("clefTable", ClefInfo::name(_clefType._transposingClef)));
      }
 
//---------------------------------------------------------
//   setLinked
//---------------------------------------------------------
 
void StaffListItem::setLinked(bool val)
      {
      _linked = val;
      setIcon(3, _linked ? *icons[int(Icons::checkmark_ICON)] : QIcon() );
      }
 
//---------------------------------------------------------
//   setStaffType
//---------------------------------------------------------
 
void StaffListItem::setStaffType(const StaffType* st)
      {
      if (!st)                                        // if no staff type given, dault to stadard
            _staffTypeCombo->setCurrentIndex(0);      // staff type (at combo box index 0)
      else {
            // if staff type given, look into combo box item data for a preset equal to staff type
            for (int i = 0; i < _staffTypeCombo->count(); ++i) {
                  const StaffType* _st = StaffType::preset(StaffTypes(_staffTypeCombo->itemData(i).toInt()));
                  if (*_st == *st) {
                        _staffTypeCombo->setCurrentIndex(i);
                        return;
                        }
                  }
            // try harder
            for (int i = 0; i < _staffTypeCombo->count(); ++i) {
                  const StaffType* _st = StaffType::preset(StaffTypes(_staffTypeCombo->itemData(i).toInt()));
                  if (_st->isSameStructure(*st)) {
                        _staffTypeCombo->setCurrentIndex(i);
                        return;
                        }
                  }
            qDebug("StaffListItem::setStaffType: not found\n");
            _staffTypeCombo->setCurrentIndex(0);      // if none found, default to standard staff type
            }
      }
 
//---------------------------------------------------------
//   setStaffType
//---------------------------------------------------------
 
void StaffListItem::setStaffType(int idx)
      {
      int i = _staffTypeCombo->findData(idx);
      if (i != -1)
            _staffTypeCombo->setCurrentIndex(i);
      }
 
//---------------------------------------------------------
//   staffType
//---------------------------------------------------------
 
const StaffType* StaffListItem::staffType() const
      {
      return StaffType::preset(StaffTypes((staffTypeIdx())));
      }
 
//---------------------------------------------------------
//   staffTypeIdx
//---------------------------------------------------------
 
int StaffListItem::staffTypeIdx() const
      {
      return _staffTypeCombo->itemData(_staffTypeCombo->currentIndex()).toInt();
      }
 
//---------------------------------------------------------
//   staffTypeChanged
//---------------------------------------------------------
 
void StaffListItem::staffTypeChanged(int idx)
      {
      // check current clef matches new staff type
      int staffTypeIdx = _staffTypeCombo->itemData(idx).toInt();
      const StaffType* stfType = StaffType::preset(StaffTypes(staffTypeIdx));
 
      PartListItem* pli = static_cast<PartListItem*>(QTreeWidgetItem::parent());
      pli->updateClefs();
 
      if (_staff && _staff->staffType(0)->name() != stfType->name()) {
            if (_op != ListItemOp::I_DELETE && _op != ListItemOp::ADD)
                  _op = ListItemOp::UPDATE;
            }
      }
 
//---------------------------------------------------------
//   setVisible
//---------------------------------------------------------
 
void PartListItem::setVisible(bool val)
      {
      setCheckState(1, val ? Qt::Checked : Qt::Unchecked);
      }
 
//---------------------------------------------------------
//   visible
//---------------------------------------------------------
 
bool PartListItem::visible() const
      {
      return checkState(1) == Qt::Checked;
      }
 
//---------------------------------------------------------
//   updateClefs
//---------------------------------------------------------
 
void PartListItem::updateClefs()
      {
      for (int i = 0; i < childCount(); ++i) {
            StaffListItem* sli = static_cast<StaffListItem*>(child(i));
            const StaffType* stfType = StaffType::preset(StaffTypes(sli->staffTypeIdx()));
 
            ClefTypeList clefType;
            switch (stfType->group()) {
                  case StaffGroup::STANDARD:
                        clefType = sli->defaultClefType();
                        break;
                  case StaffGroup::TAB:
                        clefType = ClefTypeList(ClefType::TAB);
                        break;
                  case StaffGroup::PERCUSSION:
                        clefType = ClefTypeList(ClefType::PERC);
                        break;
                  }
            sli->setClefType(clefType);
            }
      }
 
//---------------------------------------------------------
//   PartListItem
//---------------------------------------------------------
 
PartListItem::PartListItem(Part* p, QTreeWidget* lv)
   : QTreeWidgetItem(lv, PART_LIST_ITEM)
      {
      part = p;
      it   = 0;
      op   = ListItemOp::KEEP;
      setText(0, p->partName());
      setFlags(flags() | Qt::ItemIsUserCheckable);
      }
 
PartListItem::PartListItem(const InstrumentTemplate* i, QTreeWidget* lv)
   : QTreeWidgetItem(lv, PART_LIST_ITEM)
      {
      part = 0;
      it   = i;
      op   = ListItemOp::ADD;
      setText(0, it->trackName);
      }
 
//---------------------------------------------------------
//   InstrumentTemplateListItem
//---------------------------------------------------------
 
InstrumentTemplateListItem::InstrumentTemplateListItem(QString group, QTreeWidget* parent)
   : QTreeWidgetItem(parent) {
      _instrumentTemplate = 0;
      _group = group;
      setText(0, group);
      }
 
InstrumentTemplateListItem::InstrumentTemplateListItem(InstrumentTemplate* i, InstrumentTemplateListItem* item)
   : QTreeWidgetItem(item) {
      _instrumentTemplate = i;
      setText(0, i->trackName);
      }
 
InstrumentTemplateListItem::InstrumentTemplateListItem(InstrumentTemplate* i, QTreeWidget* parent)
   : QTreeWidgetItem(parent) {
      _instrumentTemplate = i;
      setText(0, i->trackName);
      }
 
//---------------------------------------------------------
//   text
//---------------------------------------------------------
 
QString InstrumentTemplateListItem::text(int col) const
      {
      switch (col) {
            case 0:
                  return _instrumentTemplate ?
                     _instrumentTemplate->trackName : _group;
            default:
                  return QString("");
            }
      }
 
//---------------------------------------------------------
//   InstrumentsWidget
//---------------------------------------------------------
 
InstrumentsWidget::InstrumentsWidget(QWidget* parent)
   : QWidget(parent)
      {
      setupUi(this);
      splitter->setStretchFactor(0, 10);
      splitter->setStretchFactor(1, 0);
      splitter->setStretchFactor(2, 15);
      setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
 
      instrumentList->setSelectionMode(QAbstractItemView::ExtendedSelection);
      partiturList->setSelectionMode(QAbstractItemView::SingleSelection);
      QStringList header = (QStringList() << tr("Staves") << tr("Visible") << tr("Clef") << tr("Linked") << tr("Staff type"));
      partiturList->setHeaderLabels(header);
      partiturList->resizeColumnToContents(1);  // shrink "visible "and "linked" columns as much as possible
      partiturList->resizeColumnToContents(3);
 
      buildTemplateList();
 
      addButton->setEnabled(false);
      removeButton->setEnabled(false);
      upButton->setEnabled(false);
      downButton->setEnabled(false);
      belowButton->setEnabled(false);
      linkedButton->setEnabled(false);
 
      connect(instrumentList, SIGNAL(clicked(const QModelIndex &)), SLOT(expandOrCollapse(const QModelIndex &)));
      }
 
//---------------------------------------------------------
//   populateGenreCombo
//---------------------------------------------------------
 
void populateGenreCombo(QComboBox* combo)
      {
      combo->clear();
      combo->addItem(qApp->translate("InstrumentsDialog", "All instruments"), "all");
      int i = 1;
      int defaultIndex = 0;
      foreach (InstrumentGenre *ig, instrumentGenres) {
            combo->addItem(ig->name, ig->id);
            if (ig->id == "common")
                  defaultIndex = i;
            ++i;
            }
      combo->setCurrentIndex(defaultIndex);
      }
 
//---------------------------------------------------------
 //   populateInstrumentList
//---------------------------------------------------------
 
void populateInstrumentList(QTreeWidget* instrumentList)
      {
      instrumentList->clear();
      // TODO: memory leak
      foreach(InstrumentGroup* g, instrumentGroups) {
            InstrumentTemplateListItem* group = new InstrumentTemplateListItem(g->name, instrumentList);
            group->setFlags(Qt::ItemIsEnabled);
            foreach(InstrumentTemplate* t, g->instrumentTemplates) {
                  new InstrumentTemplateListItem(t, group);
                  }
            }
      }
 
//---------------------------------------------------------
//   buildTemplateList
//---------------------------------------------------------
 
void InstrumentsWidget::buildTemplateList()
      {
      // clear search if instrument list is updated
      search->clear();
      filterInstruments(instrumentList, search->text());
 
      populateInstrumentList(instrumentList);
      populateGenreCombo(instrumentGenreFilter);
      }
 
//---------------------------------------------------------
//   expandOrCollapse
//---------------------------------------------------------
 
void InstrumentsWidget::expandOrCollapse(const QModelIndex &model)
      {
      if(instrumentList->isExpanded(model))
            instrumentList->collapse(model);
      else
            instrumentList->expand(model);
      }
 
//---------------------------------------------------------
//   genPartList
//---------------------------------------------------------
 
void InstrumentsWidget::genPartList(Score* cs)
      {
      partiturList->clear();
 
      foreach (Part* p, cs->parts()) {
            PartListItem* pli = new PartListItem(p, partiturList);
            pli->setVisible(p->show());
            foreach (Staff* s, *p->staves()) {
                  StaffListItem* sli = new StaffListItem(pli);
                  sli->setStaff(s);
                  sli->setClefType(s->clefType(0));
                  sli->setDefaultClefType(s->defaultClefType());
                  sli->setPartIdx(s->rstaff());
                  const LinkedStaves* ls = s->linkedStaves();
                  bool bLinked = false;
                  if (ls && !ls->empty()) {
                        foreach(Staff* ps, ls->staves()) {
                              if (ps != s && ps->score() == s->score()) {
                                    bLinked = true;
                                    break;
                                    }
                              }
                        }
                  sli->setLinked(bLinked);
                  sli->setStaffType(s->staffType(0));    // TODO
                  }
            pli->updateClefs();
            partiturList->setItemExpanded(pli, true);
            }
      partiturList->resizeColumnToContents(2);  // adjust width of "Clef " and "Staff type" columns
      partiturList->resizeColumnToContents(4);
      }
 
//---------------------------------------------------------
//   on_instrumentList_itemSelectionChanged
//---------------------------------------------------------
 
void InstrumentsWidget::on_instrumentList_itemSelectionChanged()
      {
      QList<QTreeWidgetItem*> wi = instrumentList->selectedItems();
      bool flag = !wi.isEmpty();
      addButton->setEnabled(flag);
      }
 
//---------------------------------------------------------
//   on_partiturList_itemSelectionChanged
//---------------------------------------------------------
 
void InstrumentsWidget::on_partiturList_itemSelectionChanged()
      {
      QList<QTreeWidgetItem*> wi = partiturList->selectedItems();
      if (wi.isEmpty()) {
            removeButton->setEnabled(false);
            upButton->setEnabled(false);
            downButton->setEnabled(false);
            linkedButton->setEnabled(false);
            belowButton->setEnabled(false);
            return;
            }
      QTreeWidgetItem* item = wi.front();
      bool flag = item != 0;
 
      int count = 0; // item can be hidden
      QTreeWidgetItem* it = 0;
      QList<QTreeWidgetItem*> witems;
      if(item->type() == PART_LIST_ITEM) {
            for (int idx = 0; (it = partiturList->topLevelItem(idx)); ++idx) {
                  if (!it->isHidden()) {
                        count++;
                        witems.append(it);
                        }
                  }
            }
      else {
            for (int idx = 0; (it = item->parent()->child(idx)); ++idx) {
                  if (!it->isHidden()){
                        count++;
                        witems.append(it);
                        }
                  }
            }
 
      bool onlyOne = (count == 1);
      bool first = (witems.first() == item);
      bool last = (witems.last() == item);
 
      removeButton->setEnabled(flag && !onlyOne);
      upButton->setEnabled(flag && !onlyOne && !first);
      downButton->setEnabled(flag && !onlyOne && !last);
      linkedButton->setEnabled(item && item->type() == STAFF_LIST_ITEM);
      belowButton->setEnabled(item && item->type() == STAFF_LIST_ITEM);
      }
 
//---------------------------------------------------------
//   on_instrumentList
//---------------------------------------------------------
 
void InstrumentsWidget::on_instrumentList_itemDoubleClicked(QTreeWidgetItem*, int)
      {
      on_addButton_clicked();
      }
 
//---------------------------------------------------------
//   on_addButton_clicked
//    add instrument to partitur
//---------------------------------------------------------
 
void InstrumentsWidget::on_addButton_clicked()
      {
      foreach(QTreeWidgetItem* i, instrumentList->selectedItems()) {
            InstrumentTemplateListItem* item = static_cast<InstrumentTemplateListItem*>(i);
            const InstrumentTemplate* it     = item->instrumentTemplate();
            if (it == 0)
                  continue;
            PartListItem* pli = new PartListItem(it, partiturList);
            pli->setFirstColumnSpanned(true);
            pli->op = ListItemOp::ADD;
 
            int n = it->nstaves();
            for (int i = 0; i < n; ++i) {
                  StaffListItem* sli = new StaffListItem(pli);
                  sli->setOp(ListItemOp::ADD);
                  sli->setStaff(0);
                  sli->setPartIdx(i);
                  sli->setDefaultClefType(it->clefType(i));
                  sli->setStaffType(it->staffTypePreset);
                  }
            pli->updateClefs();
            partiturList->setItemExpanded(pli, true);
            partiturList->clearSelection();     // should not be necessary
            partiturList->setItemSelected(pli, true);
            }
      emit completeChanged(true);
      }
 
//---------------------------------------------------------
//   on_removeButton_clicked
//    remove instrument from partitur
//---------------------------------------------------------
 
void InstrumentsWidget::on_removeButton_clicked()
      {
      QList<QTreeWidgetItem*> wi = partiturList->selectedItems();
      if (wi.isEmpty())
            return;
      QTreeWidgetItem* item   = wi.front();
      QTreeWidgetItem* parent = item->parent();
 
      if (parent) {
            if (((StaffListItem*)item)->op() == ListItemOp::ADD) {
                  if (parent->childCount() == 1) {
                        partiturList->takeTopLevelItem(partiturList->indexOfTopLevelItem(parent));
                        delete parent;
                        }
                  else {
                        parent->takeChild(parent->indexOfChild(item));
                        delete item;
                        }
                  }
            else {
                  ((StaffListItem*)item)->setOp(ListItemOp::I_DELETE);
                  item->setHidden(true);
 
                  // check if a staff is linked to this staff
 
                  int idx = parent->indexOfChild(item);
                  StaffListItem* sli = static_cast<StaffListItem*>(parent->child(idx+1));
                  if (sli) {
                        StaffListItem* sli2 = static_cast<StaffListItem*>(parent->child(idx+2));
                        if (sli->linked() && !(sli2 && sli2->linked())) {
                              sli->setLinked(false);
                              partiturList->update();
                              }
                        }
                  }
            static_cast<PartListItem*>(parent)->updateClefs();
            partiturList->setItemSelected(parent, true);
            }
      else {
            int idx = partiturList->indexOfTopLevelItem(item);
            if (((PartListItem*)item)->op == ListItemOp::ADD)
                  delete item;
            else {
                  ((PartListItem*)item)->op = ListItemOp::I_DELETE;
                  item->setHidden(true);
                  }
            // select an item, do not consider hidden ones
            int plusIdx = 0;
            QTreeWidgetItem* nextParent = partiturList->topLevelItem(idx + plusIdx);
            while (nextParent && nextParent->isHidden()) {
                  plusIdx++;
                  nextParent = partiturList->topLevelItem(idx + plusIdx);
                  }
            if(!nextParent) { // could find after, check before
                  plusIdx = 1;
                  nextParent = partiturList->topLevelItem(idx - plusIdx);
                  while (nextParent && nextParent->isHidden()) {
                       plusIdx++;
                       nextParent = partiturList->topLevelItem(idx - plusIdx);
                  }
                  }
            partiturList->setItemSelected(nextParent, true);
            }
      }
 
//---------------------------------------------------------
//   on_upButton_clicked
//    move instrument up in partitur
//---------------------------------------------------------
 
void InstrumentsWidget::on_upButton_clicked()
      {
      QList<QTreeWidgetItem*> wi = partiturList->selectedItems();
      if (wi.isEmpty())
            return;
      QTreeWidgetItem* item = wi.front();
 
      if (item->type() == PART_LIST_ITEM) {
            bool isExpanded = partiturList->isItemExpanded(item);
            int idx = partiturList->indexOfTopLevelItem(item);
            // if part item not first, move one slot up
            if (idx) {
                  partiturList->selectionModel()->clear();
                  QTreeWidgetItem* item = partiturList->takeTopLevelItem(idx);
                  // Qt looses the QComboBox set into StaffListItem's when they are re-inserted into the tree:
                  // get the currently selected staff type of each combo and re-insert
                  int numOfStaffListItems = item->childCount();
                  int staffIdx[numOfStaffListItems];
                  for (int itemIdx=0; itemIdx < numOfStaffListItems; ++itemIdx)
                        staffIdx[itemIdx] = (static_cast<StaffListItem*>(item->child(itemIdx)))->staffTypeIdx();
                  // do not consider hidden ones
                  int minusIdx = 1;
                  QTreeWidgetItem* prevParent = partiturList->topLevelItem(idx - minusIdx);
                  while (prevParent && prevParent->isHidden()) {
                       minusIdx++;
                       prevParent = partiturList->topLevelItem(idx - minusIdx);
                  }
                  partiturList->insertTopLevelItem(idx - minusIdx, item);
                  // after-re-insertion, recreate each combo and set its index
                  for (int itemIdx=0; itemIdx < numOfStaffListItems; ++itemIdx) {
                        StaffListItem* staffItem = static_cast<StaffListItem*>(item->child(itemIdx));
                        staffItem->initStaffTypeCombo(true);
                        staffItem->setStaffType(staffIdx[itemIdx]);
                        }
                  partiturList->setItemExpanded(item, isExpanded);
                  partiturList->setItemSelected(item, true);
                  }
            }
      else {
            QTreeWidgetItem* parent = item->parent();
            int idx = parent->indexOfChild(item);
            // if staff item not first of its part, move one slot up
            if (idx) {
                  partiturList->selectionModel()->clear();
                  StaffListItem* item = static_cast<StaffListItem*>(parent->takeChild(idx));
                  // Qt looses the QComboBox set into StaffListItem when it is re-inserted into the tree:
                  // get currently selected staff type and re-insert
                  int staffTypeIdx = item->staffTypeIdx();
                  parent->insertChild(idx - 1, item);
                  // after item has been inserted into the tree, create a new QComboBox and set its index
                  item->initStaffTypeCombo(true);
                  item->setStaffType(staffTypeIdx);
                  partiturList->setItemSelected(item, true);
                  }
            else {
                  // if staff item first of its part...
                  int parentIdx = partiturList->indexOfTopLevelItem(parent);
                  // ...and there is a previous part, move staff item to previous part
                  if (parentIdx) {
                        partiturList->selectionModel()->clear();
                        QTreeWidgetItem* prevParent = partiturList->topLevelItem(parentIdx - 1);
                        StaffListItem* sli = static_cast<StaffListItem*>(parent->takeChild(idx));
                        int staffTypeIdx = sli->staffTypeIdx();
                        prevParent->addChild(sli);
                        sli->initStaffTypeCombo(true);
                        sli->setStaffType(staffTypeIdx);
                        partiturList->setItemSelected(sli, true);
//                        PartListItem* pli = static_cast<PartListItem*>(prevParent);
//                        int idx = pli->part->nstaves();
//??                        cs->undo(new MoveStaff(sli->staff(), pli->part, idx));
                        //
                        // TODO : if staff was linked to a staff of the old parent part, unlink it!
                        //
                        }
                  }
            }
      }
 
//---------------------------------------------------------
//   on_downButton_clicked
//    move instrument down in partitur
//---------------------------------------------------------
 
void InstrumentsWidget::on_downButton_clicked()
      {
      QList<QTreeWidgetItem*> wi = partiturList->selectedItems();
      if (wi.isEmpty())
            return;
      QTreeWidgetItem* item = wi.front();
      if (item->type() == PART_LIST_ITEM) {
            bool isExpanded = partiturList->isItemExpanded(item);
            int idx = partiturList->indexOfTopLevelItem(item);
            int n = partiturList->topLevelItemCount();
            // if part not last, move one slot down
            if (idx < (n-1)) {
                  partiturList->selectionModel()->clear();
                  QTreeWidgetItem* item = partiturList->takeTopLevelItem(idx);
                  // Qt looses the QComboBox set into StaffListItem's when they are re-inserted into the tree:
                  // get the currently selected staff type of each combo and re-insert
                  int numOfStaffListItems = item->childCount();
                  int staffIdx[numOfStaffListItems];
                  int itemIdx;
                  for (itemIdx=0; itemIdx < numOfStaffListItems; ++itemIdx)
                        staffIdx[itemIdx] = (static_cast<StaffListItem*>(item->child(itemIdx)))->staffTypeIdx();
                  // do not consider hidden ones
                  int plusIdx = 1;
                  QTreeWidgetItem* nextParent = partiturList->topLevelItem(idx + plusIdx);
                  while (nextParent && nextParent->isHidden()) {
                       plusIdx++;
                       nextParent = partiturList->topLevelItem(idx + plusIdx);
                  }
                  partiturList->insertTopLevelItem(idx + plusIdx, item);
                  // after-re-insertion, recreate each combo and set its index
                  for (itemIdx=0; itemIdx < numOfStaffListItems; ++itemIdx) {
                        StaffListItem* staffItem = static_cast<StaffListItem*>(item->child(itemIdx));
                        staffItem->initStaffTypeCombo(true);
                        staffItem->setStaffType(staffIdx[itemIdx]);
                        }
                  partiturList->setItemExpanded(item, isExpanded);
                  partiturList->setItemSelected(item, true);
                  }
            }
      else {
            QTreeWidgetItem* parent = item->parent();
            int idx = parent->indexOfChild(item);
            int n = parent->childCount();
            // if staff item is not last of its part, move one slot down in part
            if (idx < (n-1)) {
                  partiturList->selectionModel()->clear();
                  StaffListItem* item = static_cast<StaffListItem*>(parent->takeChild(idx));
                  // Qt looses the QComboBox set into StaffListItem when it is re-inserted into the tree:
                  // get currently selected staff type and re-insert
                  int staffTypeIdx = item->staffTypeIdx();
                  parent->insertChild(idx+1, item);
                  // after item has been inserted into the tree, create a new QComboBox and set its index
                  item->initStaffTypeCombo(true);
                  item->setStaffType(staffTypeIdx);
                  partiturList->setItemSelected(item, true);
                  }
            else {
                  // if staff item is last of its part...
                  int parentIdx = partiturList->indexOfTopLevelItem(parent);
                  int n = partiturList->topLevelItemCount();
                  //..and there is a next part, move to next part
                  if (parentIdx < (n-1)) {
                        partiturList->selectionModel()->clear();
                        StaffListItem* sli = static_cast<StaffListItem*>(parent->takeChild(idx));
                        QTreeWidgetItem* nextParent = partiturList->topLevelItem(parentIdx - 1);
                        int staffTypeIdx = sli->staffTypeIdx();
                        nextParent->addChild(sli);
                        sli->initStaffTypeCombo(true);
                        sli->setStaffType(staffTypeIdx);
                        partiturList->setItemSelected(sli, true);
//                        PartListItem* pli = static_cast<PartListItem*>(nextParent);
//                        cs->undo(new MoveStaff(sli->staff(), pli->part, 0));
                        //
                        // TODO : if staff was linked to a staff of the old parent part, unlink it!
                        //
                        }
                  }
            }
      }
 
//---------------------------------------------------------
//   on_belowButton_clicked
//    (actually "Add Staff" button)
//---------------------------------------------------------
 
StaffListItem* InstrumentsWidget::on_belowButton_clicked()
      {
      QList<QTreeWidgetItem*> wi = partiturList->selectedItems();
      if (wi.isEmpty())
            return 0;
      QTreeWidgetItem* item = wi.front();
      if (item->type() != STAFF_LIST_ITEM)
            return 0;
 
      StaffListItem* sli  = static_cast<StaffListItem*>(item);
//      Staff* staff        = sli->staff();
      PartListItem* pli   = static_cast<PartListItem*>(sli->QTreeWidgetItem::parent());
      StaffListItem* nsli = new StaffListItem();
//      nsli->setStaff(staff);
      nsli->setStaff(0);
//      if (staff)
            nsli->setOp(ListItemOp::ADD);
      int ridx = pli->indexOfChild(sli) + 1;
      pli->insertChild(ridx, nsli);
      nsli->initStaffTypeCombo();               // StaffListItem needs to be inserted in the tree hierarchy
      nsli->setStaffType(sli->staffType());     // before a widget can be set into it
 
      ClefTypeList clefType;
      if (pli->it)
            clefType = pli->it->clefType(ridx);
      else
            clefType = pli->part->instrument()->clefType(ridx);
      nsli->setDefaultClefType(clefType);
      pli->updateClefs();
 
      partiturList->clearSelection();           // should not be necessary
      partiturList->setItemSelected(nsli, true);
      pli->updateClefs();
      return nsli;
      }
 
//---------------------------------------------------------
//   on_linkedButton_clicked
//---------------------------------------------------------
 
void InstrumentsWidget::on_linkedButton_clicked()
      {
      StaffListItem* nsli = on_belowButton_clicked();
      if (nsli)
            nsli->setLinked(true);
      }
 
//---------------------------------------------------------
//   on_search_textChanged
//---------------------------------------------------------
 
void InstrumentsWidget::on_search_textChanged(const QString &searchPhrase)
      {
      if (searchPhrase.isEmpty())
            return;
 
      filterInstruments(instrumentList, searchPhrase);
      instrumentGenreFilter->blockSignals(true);
      instrumentGenreFilter->setCurrentIndex(0);
      instrumentGenreFilter->blockSignals(false);
      }
 
//---------------------------------------------------------
//   on_clearSearch_clicked
//---------------------------------------------------------
 
void InstrumentsWidget::on_clearSearch_clicked()
      {
      search->clear();
      QString genre = instrumentGenreFilter->currentData().toString();
      filterInstrumentsByGenre(instrumentList, genre);
      }
 
//---------------------------------------------------------
//   on_instrumentGenreFilter_currentTextChanged
//---------------------------------------------------------
 
void InstrumentsWidget::on_instrumentGenreFilter_currentIndexChanged(int index)
      {
      QString id = instrumentGenreFilter->itemData(index).toString();
      // Redisplay tree, only showing items from the selected genre
      filterInstrumentsByGenre(instrumentList, id);
      }
 
//---------------------------------------------------------
//   filterInstrumentsByGenre
//---------------------------------------------------------
 
void InstrumentsWidget::filterInstrumentsByGenre(QTreeWidget *instrumentList, QString genre)
      {
      QTreeWidgetItemIterator iList(instrumentList);
      while (*iList) {
            (*iList)->setHidden(true);
            InstrumentTemplateListItem* itli = static_cast<InstrumentTemplateListItem*>(*iList);
            InstrumentTemplate *it=itli->instrumentTemplate();
 
            if(it) {
                  if (genre == "all" || it->genreMember(genre)) {
                        (*iList)->setHidden(false);
 
                        QTreeWidgetItem *iParent = (*iList)->parent();
                        while(iParent) {
                              if(!iParent->isHidden())
                                    break;
 
                              iParent->setHidden(false);
                              iParent = iParent->parent();
                              }
                        }
                  }
            ++iList;
            }
      }
 
//---------------------------------------------------------
//   createInstruments
//---------------------------------------------------------
 
void InstrumentsWidget::createInstruments(Score* cs)
      {
      //
      // process modified partitur list
      //
      QTreeWidget* pl = partiturList;
      Part* part   = 0;
      int staffIdx = 0;
 
      QTreeWidgetItem* item = 0;
      for (int idx = 0; (item = pl->topLevelItem(idx)); ++idx) {
            PartListItem* pli = (PartListItem*)item;
            if (pli->op != ListItemOp::ADD) {
                  qDebug("bad op");
                  continue;
                  }
            const InstrumentTemplate* t = ((PartListItem*)item)->it;
            part = new Part(cs);
            part->initFromInstrTemplate(t);
 
            pli->part = part;
            QTreeWidgetItem* ci = 0;
            int rstaff = 0;
            for (int cidx = 0; (ci = pli->child(cidx)); ++cidx) {
                  if (ci->isHidden())
                        continue;
                  StaffListItem* sli = (StaffListItem*)ci;
                  Staff* staff       = new Staff(cs);
                  staff->setPart(part);
                  sli->setStaff(staff);
                  ++rstaff;
 
                  staff->init(t, sli->staffType(), cidx);
                  staff->setDefaultClefType(sli->defaultClefType());
 
                  if (sli->linked() && !part->staves()->isEmpty()) {
                        Staff* linkedStaff = part->staves()->back();
                        staff->linkTo(linkedStaff);
                        }
                  part->staves()->push_back(staff);
                  cs->staves().insert(staffIdx + rstaff, staff);
                  }
            // if a staff was removed from instrument:
            if (part->staff(0)->barLineSpan() > rstaff) {
//TODO                  part->staff(0)->setBarLineSpan(rstaff);
                  part->staff(0)->setBracketType(0, BracketType::NO_BRACKET);
                  }
 
            // insert part
            cs->insertPart(part, staffIdx);
            int sidx = cs->staffIdx(part);
            int eidx = sidx + part->nstaves();
            for (Measure* m = cs->firstMeasure(); m; m = m->nextMeasure())
                  m->cmdAddStaves(sidx, eidx, true);
            staffIdx += rstaff;
            }
#if 0 // TODO
      //
      // check for bar lines
      //
      for (int staffIdx = 0; staffIdx < cs->nstaves();) {
            Staff* staff = cs->staff(staffIdx);
            int barLineSpan = staff->barLineSpan();
            if (barLineSpan == 0)
                  staff->setBarLineSpan(1);
            int nstaffIdx = staffIdx + barLineSpan;
 
            for (int idx = staffIdx+1; idx < nstaffIdx; ++idx) {
                  Staff* tStaff = cs->staff(idx);
                  if (tStaff)
                        tStaff->setBarLineSpan(0);
                  }
 
            staffIdx = nstaffIdx;
            }
#endif
      cs->setLayoutAll();
      }
 
//---------------------------------------------------------
//   init
//---------------------------------------------------------
 
void InstrumentsWidget::init()
      {
      partiturList->clear();
      instrumentList->clearSelection();
      addButton->setEnabled(false);
      removeButton->setEnabled(false);
      upButton->setEnabled(false);
      downButton->setEnabled(false);
      linkedButton->setEnabled(false);
      belowButton->setEnabled(false);
      emit completeChanged(false);
      }
 
//---------------------------------------------------------
//   getPartiturList
//---------------------------------------------------------
 
QTreeWidget* InstrumentsWidget::getPartiturList()
      {
      return partiturList;
      }
}

V595 The 'item' pointer was utilized before it was verified against nullptr. Check lines: 521, 536.