diff --git a/common/lc_model.cpp b/common/lc_model.cpp index 52a5ada6..fb5a6f3a 100644 --- a/common/lc_model.cpp +++ b/common/lc_model.cpp @@ -2371,50 +2371,57 @@ void lcModel::RemoveFocusPieceFromGroup() void lcModel::ShowEditGroupsDialog() { - QMap PieceParents; - QMap GroupParents; - - for (const std::unique_ptr& Piece : mPieces) - PieceParents[Piece.get()] = Piece->GetGroup(); - - for (const std::unique_ptr& Group : mGroups) - GroupParents[Group.get()] = Group->mGroup; - - lcQEditGroupsDialog Dialog(gMainWindow, PieceParents, GroupParents, this); + lcQEditGroupsDialog Dialog(gMainWindow, this); if (Dialog.exec() != QDialog::Accepted) return; - - bool Modified = Dialog.mNewGroups.isEmpty(); - - for (const std::unique_ptr& Piece : mPieces) + + BeginActionSequence(); + BeginObjectEditAction(); + + std::function UpdateGroups=[this, &UpdateGroups](const lcQEditGroupsDialog::GroupInfo& GroupInfo, lcGroup* ParentGroup) { - lcGroup* ParentGroup = Dialog.mPieceParents.value(Piece.get()); - - if (ParentGroup != Piece->GetGroup()) + lcGroup* Group = GroupInfo.Group; + + if (!Group) { - Piece->SetGroup(ParentGroup); - Modified = true; + Group = new lcGroup(); + mGroups.emplace_back(Group); } - } + + Group->mName = GroupInfo.Name; + Group->mGroup = ParentGroup; + + for (const lcQEditGroupsDialog::GroupInfo& ChildGroupInfo : GroupInfo.ChildGroups) + UpdateGroups(ChildGroupInfo, Group); + + for (lcPiece* Piece : GroupInfo.ChildPieces) + Piece->SetGroup(Group); + }; + + lcQEditGroupsDialog::GroupInfo GroupInfo = Dialog.GetGroups(); + + for (const lcQEditGroupsDialog::GroupInfo& ChildGroupInfo : GroupInfo.ChildGroups) + UpdateGroups(ChildGroupInfo, nullptr); + + for (lcPiece* Piece : GroupInfo.ChildPieces) + Piece->SetGroup(nullptr); + + RemoveEmptyGroups(); - for (const std::unique_ptr& Group : mGroups) + EndObjectEditAction(); + + if (mActionSequence.empty()) { - lcGroup* ParentGroup = Dialog.mGroupParents.value(Group.get()); - - if (ParentGroup != Group->mGroup) - { - Group->mGroup = ParentGroup; - Modified = true; - } + DiscardActionSequence(); + return; } + + RecordClearSelectionAction(); + + EndActionSequence(tr("Edit Groups")); - if (Modified) - { - DeselectAllObjects(); - gMainWindow->UpdateSelectedObjects(true); - SaveCheckpoint(tr("Editing Groups")); - } + gMainWindow->UpdateSelectedObjects(true); } QString lcModel::GetGroupName(const QString& Prefix) diff --git a/qt/lc_qeditgroupsdialog.cpp b/qt/lc_qeditgroupsdialog.cpp index 1fb28e30..6ddd289f 100644 --- a/qt/lc_qeditgroupsdialog.cpp +++ b/qt/lc_qeditgroupsdialog.cpp @@ -1,28 +1,52 @@ #include "lc_global.h" #include "lc_qeditgroupsdialog.h" #include "ui_lc_qeditgroupsdialog.h" -#include "lc_application.h" #include "lc_model.h" #include "piece.h" #include "group.h" -lcQEditGroupsDialog::lcQEditGroupsDialog(QWidget* Parent, const QMap& PieceParents, const QMap& GroupParents, lcModel* Model) - : QDialog(Parent), mPieceParents(PieceParents), mGroupParents(GroupParents) +constexpr uintptr_t LC_GROUPDIALOG_NEW_GROUP = ~0; + +class lcEditGroupsDialogDelegate : public QItemDelegate +{ +public: + lcEditGroupsDialogDelegate(QObject* Parent) + : QItemDelegate(Parent) + { + } + + void setModelData(QWidget* Editor, QAbstractItemModel* Model, const QModelIndex& Index) const + { + QLineEdit* LineEdit = qobject_cast(Editor); + + if (!LineEdit->isModified()) + return; + + QString Text = LineEdit->text().trimmed(); + + lcQEditGroupsDialog* Dialog = qobject_cast(parent()); + + if (Dialog && !Dialog->CanRenameGroup(Text)) + return; + + QItemDelegate::setModelData(Editor, Model, Index); + } +}; + +lcQEditGroupsDialog::lcQEditGroupsDialog(QWidget* Parent, const lcModel* Model) + : QDialog(Parent), mModel(Model) { - mLastItemClicked = nullptr; - mModel = Model; ui = new Ui::lcQEditGroupsDialog; ui->setupUi(this); + + QPushButton* NewGroup = ui->buttonBox->addButton(tr("&New Group"), QDialogButtonBox::ActionRole); - connect(ui->treeWidget, &QTreeWidget::itemClicked, this, &lcQEditGroupsDialog::onItemClicked); - connect(ui->treeWidget, &QTreeWidget::itemDoubleClicked, this, &lcQEditGroupsDialog::onItemDoubleClicked); + connect(NewGroup, &QPushButton::clicked, this, &lcQEditGroupsDialog::NewGroupClicked); - QPushButton *newGroup = ui->buttonBox->addButton(tr("New Group"), QDialogButtonBox::ActionRole); - - connect(newGroup, &QPushButton::clicked, this, &lcQEditGroupsDialog::on_newGroup_clicked); - - AddChildren(ui->treeWidget->invisibleRootItem(), nullptr); + PopulateTree(); + + ui->treeWidget->setItemDelegate(new lcEditGroupsDialogDelegate(this)); ui->treeWidget->expandAll(); } @@ -31,139 +55,164 @@ lcQEditGroupsDialog::~lcQEditGroupsDialog() delete ui; } -void lcQEditGroupsDialog::accept() +bool lcQEditGroupsDialog::CanRenameGroup(const QString& Text) const { - UpdateParents(ui->treeWidget->invisibleRootItem(), nullptr); - - QDialog::accept(); + if (Text.isEmpty()) + return false; + + std::function ScanGroups = [&ScanGroups, &Text](QTreeWidgetItem* ParentItem) + { + for (int ChildIndex = 0; ChildIndex < ParentItem->childCount(); ChildIndex++) + { + QTreeWidgetItem* ChildItem = ParentItem->child(ChildIndex); + + if (ChildItem->data(0, GroupRole).value()) + { + if (ChildItem->text(0) == Text || !ScanGroups(ChildItem)) + return false; + } + } + + return true; + }; + + return ScanGroups(ui->treeWidget->invisibleRootItem()); } -void lcQEditGroupsDialog::reject() -{ - for (int GroupIdx = 0; GroupIdx < mNewGroups.size(); GroupIdx++) - mModel->RemoveGroup(mNewGroups[GroupIdx]); - - QDialog::reject(); -} - -void lcQEditGroupsDialog::on_newGroup_clicked() +void lcQEditGroupsDialog::NewGroupClicked() { QTreeWidgetItem* CurrentItem = ui->treeWidget->currentItem(); - + if (CurrentItem && CurrentItem->data(0, PieceRole).value()) CurrentItem = CurrentItem->parent(); - + if (!CurrentItem) CurrentItem = ui->treeWidget->invisibleRootItem(); - - lcGroup* ParentGroup = (lcGroup*)CurrentItem->data(0, GroupRole).value(); - - lcGroup* NewGroup = mModel->AddGroup(tr("Group #"), ParentGroup); - mGroupParents[NewGroup] = ParentGroup; - mNewGroups.append(NewGroup); - - QTreeWidgetItem* GroupItem = new QTreeWidgetItem(CurrentItem, QStringList(NewGroup->mName)); + + QString Prefix = tr("Group #"); + int MaxIndex = 0; + + std::function ScanGroups = [&ScanGroups, &Prefix, &MaxIndex](QTreeWidgetItem* ParentItem) + { + for (int ChildIndex = 0; ChildIndex < ParentItem->childCount(); ChildIndex++) + { + QTreeWidgetItem* ChildItem = ParentItem->child(ChildIndex); + + if (ChildItem->data(0, GroupRole).value()) + { + QString Name = ChildItem->text(0); + + if (Name.startsWith(Prefix)) + { + bool Ok = false; + int GroupNumber = Name.mid(Prefix.length()).toInt(&Ok); + + if (Ok && GroupNumber > MaxIndex) + MaxIndex = GroupNumber; + } + + ScanGroups(ChildItem); + } + } + }; + + ScanGroups(ui->treeWidget->invisibleRootItem()); + + QString Name = Prefix + QString::number(MaxIndex + 1); + + QTreeWidgetItem* GroupItem = new QTreeWidgetItem(CurrentItem, QStringList(Name)); GroupItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable); - GroupItem->setData(0, GroupRole, QVariant::fromValue((uintptr_t)NewGroup)); + GroupItem->setData(0, GroupRole, QVariant::fromValue(LC_GROUPDIALOG_NEW_GROUP)); + GroupItem->setExpanded(true); } -void lcQEditGroupsDialog::onItemClicked(QTreeWidgetItem *item, int column) +lcQEditGroupsDialog::GroupInfo lcQEditGroupsDialog::GetGroupInfo(QTreeWidgetItem* ParentItem) const { - Q_UNUSED(column); - - if (item->flags() & Qt::ItemIsEditable) + lcQEditGroupsDialog::GroupInfo GroupInfo; + + GroupInfo.Name = ParentItem->text(0); + + uintptr_t GroupPointer = ParentItem->data(0, GroupRole).value(); + GroupInfo.Group = (GroupPointer == LC_GROUPDIALOG_NEW_GROUP) ? nullptr : reinterpret_cast(GroupPointer); + + for (int ChildIndex = 0; ChildIndex < ParentItem->childCount(); ChildIndex++) { - mClickTimer.stop(); - - if (mLastItemClicked != item) - { - mLastItemClicked = item; - mEditableDoubleClicked = false; - } + QTreeWidgetItem* ChildItem = ParentItem->child(ChildIndex); + + if (ChildItem->data(0, GroupRole).value()) + GroupInfo.ChildGroups.emplace_back(GetGroupInfo(ChildItem)); else { - mClickTimer.start(QApplication::doubleClickInterval() + 50, this); + lcPiece* Piece = reinterpret_cast(ChildItem->data(0, PieceRole).value()); + + if (Piece) + GroupInfo.ChildPieces.push_back(Piece); } - } + } + + return GroupInfo; } -void lcQEditGroupsDialog::onItemDoubleClicked(QTreeWidgetItem *item, int column) +lcQEditGroupsDialog::GroupInfo lcQEditGroupsDialog::GetGroups() const { - Q_UNUSED(column); - - if (item->flags() & Qt::ItemIsEditable) - { - mEditableDoubleClicked = true; - } + return GetGroupInfo(ui->treeWidget->invisibleRootItem()); } -void lcQEditGroupsDialog::timerEvent(QTimerEvent *event) +void lcQEditGroupsDialog::PopulateTree() { - Q_UNUSED(event); - - mClickTimer.stop(); - if (!mEditableDoubleClicked) + const std::vector>& Groups = mModel->GetGroups(); + std::unordered_map AddedGroups; + std::vector GroupsToAdd; + + AddedGroups[nullptr] = ui->treeWidget->invisibleRootItem(); + + auto CreateGroupItem = [&AddedGroups](lcGroup* Group) { - ui->treeWidget->editItem(mLastItemClicked); - } - - mEditableDoubleClicked = false; -} - -void lcQEditGroupsDialog::UpdateParents(QTreeWidgetItem* ParentItem, lcGroup* ParentGroup) -{ - for (int ChildIdx = 0; ChildIdx < ParentItem->childCount(); ChildIdx++) - { - QTreeWidgetItem* ChildItem = ParentItem->child(ChildIdx); - - lcPiece* Piece = (lcPiece*)ChildItem->data(0, PieceRole).value(); - - if (Piece) - { - mPieceParents[Piece] = ParentGroup; - } - else - { - lcGroup* Group = (lcGroup*)ChildItem->data(0, GroupRole).value(); - - // todo: validate unique group name - if (Group) - Group->mName = ChildItem->text(0); - - mGroupParents[Group] = ParentGroup; - - UpdateParents(ChildItem, Group); - } - } -} - -void lcQEditGroupsDialog::AddChildren(QTreeWidgetItem* ParentItem, lcGroup* ParentGroup) -{ - for (QMap::const_iterator it = mGroupParents.constBegin(); it != mGroupParents.constEnd(); it++) - { - lcGroup* Group = it.key(); - lcGroup* Parent = it.value(); - - if (Parent != ParentGroup) - continue; - + QTreeWidgetItem* ParentItem = AddedGroups.find(Group->mGroup)->second; + + if (!ParentItem) + return false; + QTreeWidgetItem* GroupItem = new QTreeWidgetItem(ParentItem, QStringList(Group->mName)); GroupItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable); GroupItem->setData(0, GroupRole, QVariant::fromValue((uintptr_t)Group)); - - AddChildren(GroupItem, Group); - } - - for (QMap::const_iterator it = mPieceParents.constBegin(); it != mPieceParents.constEnd(); it++) + + AddedGroups[Group] = GroupItem; + + return true; + }; + + for (const std::unique_ptr& Group : Groups) + if (!CreateGroupItem(Group.get())) + GroupsToAdd.push_back(Group.get()); + + while (!GroupsToAdd.empty()) { - lcPiece* Piece = it.key(); - lcGroup* Parent = it.value(); - - if (Parent != ParentGroup) - continue; - - QTreeWidgetItem* PieceItem = new QTreeWidgetItem(ParentItem, QStringList(Piece->GetName())); - PieceItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); - PieceItem->setData(0, PieceRole, QVariant::fromValue((uintptr_t)Piece)); + size_t GroupCount = GroupsToAdd.size(); + + for (auto GroupIt = GroupsToAdd.begin(); GroupIt != GroupsToAdd.end();) + { + if (CreateGroupItem(*GroupIt)) + GroupIt = GroupsToAdd.erase(GroupIt); + else + ++GroupIt; + } + + if (GroupCount == GroupsToAdd.size()) + break; + } + + const std::vector>& Pieces = mModel->GetPieces(); + + for (const std::unique_ptr& Piece : Pieces) + { + QTreeWidgetItem* ParentItem = AddedGroups.find(Piece->GetGroup())->second; + + if (ParentItem) + { + QTreeWidgetItem* PieceItem = new QTreeWidgetItem(ParentItem, QStringList(Piece->GetName())); + PieceItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + PieceItem->setData(0, PieceRole, QVariant::fromValue((uintptr_t)Piece.get())); + } } } diff --git a/qt/lc_qeditgroupsdialog.h b/qt/lc_qeditgroupsdialog.h index b05dca2c..b4f7286a 100644 --- a/qt/lc_qeditgroupsdialog.h +++ b/qt/lc_qeditgroupsdialog.h @@ -1,7 +1,5 @@ #pragma once -#include - namespace Ui { class lcQEditGroupsDialog; } @@ -11,37 +9,33 @@ class lcQEditGroupsDialog : public QDialog Q_OBJECT public: - lcQEditGroupsDialog(QWidget* Parent, const QMap& PieceParents, const QMap& GroupParents, lcModel* Model); - ~lcQEditGroupsDialog(); + lcQEditGroupsDialog(QWidget* Parent, const lcModel* Model); + virtual ~lcQEditGroupsDialog(); - QMap mPieceParents; - QMap mGroupParents; - QList mNewGroups; - //QList mDeletedGroups; // todo: support deleting groups in the edit groups dialog + struct GroupInfo + { + QString Name; + lcGroup* Group; + std::vector ChildGroups; + std::vector ChildPieces; + }; + + GroupInfo GetGroups() const; + bool CanRenameGroup(const QString& Text) const; + +protected slots: + void NewGroupClicked(); +protected: enum { PieceRole = Qt::UserRole, GroupRole }; - -public slots: - void accept() override; - void reject() override; - void on_newGroup_clicked(); - void onItemClicked(QTreeWidgetItem* Item, int Column); - void onItemDoubleClicked(QTreeWidgetItem* Item, int Column); - -private: - Ui::lcQEditGroupsDialog *ui; - - void UpdateParents(QTreeWidgetItem* ParentItem, lcGroup* ParentGroup); - void AddChildren(QTreeWidgetItem* ParentItem, lcGroup* ParentGroup); - - void timerEvent(QTimerEvent* Event) override; - - lcModel* mModel; - QTreeWidgetItem* mLastItemClicked; - bool mEditableDoubleClicked; - QBasicTimer mClickTimer; + + GroupInfo GetGroupInfo(QTreeWidgetItem* ParentItem) const; + void PopulateTree(); + + Ui::lcQEditGroupsDialog* ui = nullptr; + const lcModel* mModel = nullptr; }; diff --git a/qt/lc_qeditgroupsdialog.ui b/qt/lc_qeditgroupsdialog.ui index 522be73a..d2b58f15 100644 --- a/qt/lc_qeditgroupsdialog.ui +++ b/qt/lc_qeditgroupsdialog.ui @@ -17,13 +17,13 @@ - QAbstractItemView::NoEditTriggers + QAbstractItemView::EditTrigger::DoubleClicked|QAbstractItemView::EditTrigger::EditKeyPressed|QAbstractItemView::EditTrigger::SelectedClicked true - QAbstractItemView::InternalMove + QAbstractItemView::DragDropMode::InternalMove true @@ -41,10 +41,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok