Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested crates #1304

Closed
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
f3ad479
Add cratetreemodel.h
gramanas Jun 7, 2017
1fba5bc
Merge branch 'jmigual-library-redesign' of https://github.com/daschue…
gramanas Jun 15, 2017
3c09995
Nested Crate Insertion with bad code
gramanas Jun 20, 2017
1635ac3
Merge branch 'jmigual-library-redesign' of https://github.com/daschue…
gramanas Jun 20, 2017
2720d6c
Add nested crates
gramanas Jun 21, 2017
f3e474f
Disguise sql columns as variables
gramanas Jul 6, 2017
bc97923
Merge branch 'jmigual-library-redesign' of https://github.com/daschue…
gramanas Jul 6, 2017
8242c67
Add 'Add new crate' that adds a crate to the top level
gramanas Jul 7, 2017
596df84
Merge branch 'jmigual-library-redesign' of https://github.com/mixxxdj…
gramanas Jul 15, 2017
8d187b5
Move hierarchy functions into CrateHierarchy
gramanas Jul 16, 2017
7cf56f3
Split CrateStorage file
gramanas Jul 16, 2017
f9946ec
Moved crateHierarchy to crateStorage
gramanas Jul 16, 2017
d137704
Merge branch 'jmigual-library-redesign' of https://github.com/mixxxdj…
gramanas Jul 18, 2017
1d48fa5
Fix small mistakes and typos
gramanas Jul 19, 2017
5587e1c
Refactor CrateStorage
gramanas Jul 23, 2017
06d9cbe
Select crates usinc the crate filter
gramanas Jul 27, 2017
bf5cd72
First attempt at a recursive crate select via filter
gramanas Jul 27, 2017
9948c55
Duplicate crate, small fixes and typos, privacy at crateHierarchy
gramanas Jul 29, 2017
6fa0f83
Rename now avaliable. Fix typos
gramanas Jul 31, 2017
add5459
insertCrate is now the only thing gui knows when inserting a crate
gramanas Aug 1, 2017
b0ca91d
Add recursive deletion, no filter in crate select anymore
gramanas Aug 4, 2017
6a7ac10
Toggle recursion in crate selection + small fixes
gramanas Aug 4, 2017
9d74122
Fix bug with crateSelect, typos & small fixes
gramanas Aug 5, 2017
d380b51
More small changes
gramanas Aug 5, 2017
f2fc75a
Fix renaming issue and small changes
gramanas Aug 7, 2017
e7f6781
Introduce new more stable crate filter
gramanas Aug 7, 2017
5f3daab
RichtClick > add to crate on a track now shows paths, fix filter bug
gramanas Aug 9, 2017
82c3d45
Add crate summary
gramanas Aug 12, 2017
3bdecdb
small code cleanup
gramanas Aug 15, 2017
774ba58
Change naming conventions to work more like a file system
gramanas Aug 20, 2017
79ee638
Merge branch 'NestedCrates' of https://github.com/gramanas/mixxx into…
gramanas Aug 20, 2017
aa4d06f
Add moving capabilities to crates via right click option
gramanas Aug 22, 2017
d0591a9
Enforce naming conventions
gramanas Aug 22, 2017
41d2e81
Correct crate filter tests
gramanas Aug 23, 2017
59af9a9
Don't move a crate to it's parent
gramanas Aug 23, 2017
e8417d5
Fix bug...
gramanas Oct 11, 2017
8996708
Wrap moveCrate in a transaction
gramanas Oct 12, 2017
14c2cfc
Merge branch 'NestedCrates' of https://github.com/gramanas/mixxx into…
gramanas Oct 12, 2017
311e22b
Merge branch 'jmigual-library-redesign' of https://github.com/mixxxdj…
gramanas Oct 12, 2017
23c2c9d
Fix bugs with move crate and with the merge problem
gramanas Oct 14, 2017
de6cd1b
Merge branch 'jmigual-library-redesign' of https://github.com/mixxxdj…
gramanas Jan 5, 2018
a73cbc0
Merge commit '9df14fa6f3706df6b3563ad2cd8f66ae56ca99db' into NestedCr…
gramanas Apr 20, 2018
47157bb
Merge branch 'jmigual-library-redesign' into NestedCrates
gramanas Apr 20, 2018
a26ff55
Merge branch 'jmigual-library-redesign' into NestedCrates
gramanas May 7, 2018
e2cf708
Merge remote-tracking branch 'upstream/jmigual-library-redesign' into…
Be-ing Jun 23, 2018
e4da311
fix database schema upgrade for nested crates
Be-ing Jun 23, 2018
cdb08fc
show context menu to add crate when right clicking empty area
Be-ing Jun 23, 2018
a5c5adc
Merge pull request #3 from Be-ing/nested-crates
gramanas Jun 24, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build/depends.py
Original file line number Diff line number Diff line change
Expand Up @@ -947,8 +947,11 @@ def sources(self, build):

"library/features/crates/cratefeature.cpp",
"library/features/crates/cratetablemodel.cpp",
"library/features/crates/cratetreemodel.cpp",
"library/features/crates/cratestorage.cpp",
"library/features/crates/cratestoragehelpers.cpp",
"library/features/crates/cratefeaturehelper.cpp",
"library/features/crates/cratehierarchy.cpp",

"library/features/history/historyfeature.cpp",
"library/features/history/historytreemodel.cpp",
Expand Down
18 changes: 18 additions & 0 deletions res/schema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -446,4 +446,22 @@ METADATA
);
</sql>
</revision>
<revision version="29" min_compatible="3">
<description>
Add tables to support nested crates.
</description>
<sql>
CREATE TABLE IF NOT EXISTS crateClosure (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicitly specify a primary key id column for each table. Otherwise it will be created implicitly as ROWID. But omit the keyword AUTOINCREMENT:
https://sqlite.org/autoinc.html

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@uklotzde: Why is that an issue?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whith AUTOINCREMENT SQLite needs to keep track of the greatest ID value that has ever been used in a separate table. Without the keyword it can simply leverage the primary key index of the table itself and reuse any currently unused ID value:
https://www.sqlite.org/autoinc.html

Two use cases for AUTOCOMMIT:

  • if you need to store the ID somewhere else outside of the database and you must guarantee that an ID still identifies the same record when coming back later at any time
  • if you are using the ID to enforce a strict ordering of records, i.e. for a persistent FIFO task scheduler

parentId INTEGER,
childId INTEGER,
depth INTEGER
);

CREATE TABLE IF NOT EXISTS cratePath (
crateId INTEGER,
idPath TEXT,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a comment like
"This contains a / separated string of crate Ids"

namePath TEXT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"This contains a / separated string of crate names"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way: how do you treat names which includes a "/"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it smart to store the namePath in the Database? This is a redundant information which can be wrong.
How is guaranteed that this is always correct?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing redundant information for optimizing queries is not an issue as long as you are able to guarantee consistency, either immediately or at least eventually. The method of choice for immediate consistency is encapsulation with hooks that are triggered when needed and everything enclosed in a transaction scope.

I remember that the my last review revealed that consistency cannot be guaranteed by the current implementation, because the transaction scoping was missing. Considering the fact that the whole hierarchy is abandoned and flattened when any inconsistencies are detected, this is a no-go for production and needs to be fixed.

);
</sql>
</revision>
</schema>
2 changes: 1 addition & 1 deletion src/database/mixxxdb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
const QString MixxxDb::kDefaultSchemaFile(":/schema.xml");

//static
const int MixxxDb::kRequiredSchemaVersion = 28;
const int MixxxDb::kRequiredSchemaVersion = 29;

namespace {

Expand Down
9 changes: 9 additions & 0 deletions src/library/crate/crateschema.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#define CRATE_TABLE "crates"
#define CRATE_TRACKS_TABLE "crate_tracks"
#define CRATE_CLOSURE_TABLE "crateClosure"
#define CRATE_PATH_TABLE "cratePath"

const QString CRATETABLE_ID = "id";
const QString CRATETABLE_NAME = "name";
Expand All @@ -24,5 +26,12 @@ const QString CRATETABLE_AUTODJ_SOURCE = "autodj_source";
const QString CRATETRACKSTABLE_CRATEID = "crate_id";
const QString CRATETRACKSTABLE_TRACKID = "track_id";

const QString CLOSURE_PARENTID = "parentId";
const QString CLOSURE_CHILDID = "childId";
const QString CLOSURE_DEPTH = "depth";

const QString PATHTABLE_CRATEID = "crateId";
const QString PATHTABLE_ID_PATH = "idPath";
const QString PATHTABLE_NAME_PATH = "namePath";

#endif // MIXXX_CRATESCHEMA_H
116 changes: 87 additions & 29 deletions src/library/features/crates/cratefeature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,35 @@ CrateFeature::CrateFeature(UserSettingsPointer pConfig,
m_cratesIcon(":/images/library/ic_library_crates.png"),
m_lockedCrateIcon(":/images/library/ic_library_locked.png"),
m_pTrackCollection(pTrackCollection),
m_pCrateTableModel(nullptr) {
m_pCrateTableModel(nullptr),
m_crateHierarchy(pTrackCollection) {


initActions();

// construct child model
m_childModel.setRootItem(std::make_unique<TreeItem>(this));
rebuildChildModel();
// m_childModel.setRootItem(std::make_unique<TreeItem>(this));
// rebuildChildModel();

// if closure does not have the same number of crates as the crates table
// this means that the user just started mixxx with nested crates for the
// first time, so we have to fill the closure table with (self,self,0)
if (!m_crateHierarchy.closureIsValid()) {
QMessageBox::warning(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need for this dialog box to be shown. Please remove it.

nullptr,
tr("Nested Crates"),
tr("Crates now support hierarchical structure."
"All your crates have been converted to Level 1 crates"));

m_crateHierarchy.resetClosure();
m_crateHierarchy.initClosure();
m_crateHierarchy.resetPath();
m_crateHierarchy.generateAllPaths();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you anticipate needing these 4 functions in any other context? If not, it may make sense to make them private members of CrateHierarchy and create a public wrapper function (maybe called CrateHierarchy::initializeHierarchy or something like that) around them to call here. I think this exposes too much of the implementation of the database storage to the GUI class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, you are right, I'll do that

}

m_pChildModel = std::make_unique<CrateTreeModel>(this,
m_pTrackCollection, &m_crateHierarchy);
m_pChildModel->reloadTree();

connectLibrary(pLibrary);
connectTrackCollection();
Expand All @@ -65,6 +87,10 @@ void CrateFeature::initActions() {
connect(m_pCreateCrateAction.get(), SIGNAL(triggered()),
this, SLOT(slotCreateCrate()));

m_pCreateChildCrateAction = std::make_unique<QAction>(tr("Create Subcrate"),this);
connect(m_pCreateChildCrateAction.get(), SIGNAL(triggered()),
this, SLOT(slotCreateChildCrate()));

m_pDeleteCrateAction = std::make_unique<QAction>(tr("Remove"),this);
connect(m_pDeleteCrateAction.get(), SIGNAL(triggered()),
this, SLOT(slotDeleteCrate()));
Expand Down Expand Up @@ -204,7 +230,7 @@ void updateTreeItemForTrackSelection(
pTreeItem->setBold(crateContainsSelectedTrack);
}

}
} // anonymus namespace

bool CrateFeature::dragMoveAccept(QUrl url) {
return SoundSourceProxy::isUrlSupported(url) ||
Expand Down Expand Up @@ -273,7 +299,7 @@ parented_ptr<QWidget> CrateFeature::createPaneWidget(KeyboardEventFilter* pKeybo
}

QPointer<TreeItemModel> CrateFeature::getChildModel() {
return &m_childModel;
return m_pChildModel.get();
}

void CrateFeature::activate() {
Expand Down Expand Up @@ -380,6 +406,7 @@ void CrateFeature::onRightClickChild(const QPoint& globalPos, const QModelIndex&

QMenu menu(NULL);
menu.addAction(m_pCreateCrateAction.get());
menu.addAction(m_pCreateChildCrateAction.get());
menu.addSeparator();
menu.addAction(m_pRenameCrateAction.get());
menu.addAction(m_pDuplicateCrateAction.get());
Expand All @@ -401,8 +428,29 @@ void CrateFeature::onRightClickChild(const QPoint& globalPos, const QModelIndex&
void CrateFeature::slotCreateCrate() {
CrateId crateId = CrateFeatureHelper(
m_pTrackCollection, m_pConfig).createEmptyCrate();
Crate newCrate;
m_pTrackCollection->crates().readCrateById(crateId, &newCrate);
m_crateHierarchy.initClosureForCrate(crateId);
m_crateHierarchy.generateCratePaths(newCrate);
if (crateId.isValid()) {
activateCrate(crateId);
m_pChildModel->reloadTree();
}
}

void CrateFeature::slotCreateChildCrate() {
Crate parent;
if (readLastRightClickedCrate(&parent)) {
CrateId newCrate = CrateFeatureHelper(
m_pTrackCollection, m_pConfig).createEmptyCrate();
if (newCrate.isValid()) {
m_crateHierarchy.initClosureForCrate(newCrate);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CrateHierarchy should be responsible for this workflow (atomically):

CrateId newCrateId = CrateFeatureHelper(...).createEmptyCrate();
if (newCrateId.isValid() && m_crateHierachy.adoptOrphanChild(parent.getId(), newCrateId)) {
   m_pChildModel->reloadTree();
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A single function call to CrateStorage should take care of all this DB logic.

if (m_crateHierarchy.insertIntoClosure(parent.getId(), newCrate)) {
Crate child;
m_pTrackCollection->crates().readCrateById(newCrate, &child);
m_crateHierarchy.generateCratePaths(child);
m_pChildModel->reloadTree();
}
}
}
}

Expand All @@ -413,8 +461,16 @@ void CrateFeature::slotDeleteCrate() {
qWarning() << "Refusing to delete locked crate" << crate;
return;
}
// better deletion requeried
if (m_crateHierarchy.hasChildern(crate.getId())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

qWarning() << "Can't delete " << crate;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please only log a single warning message

qWarning() << "Currently only delete leaf crates";
return;
}
if (m_pTrackCollection->deleteCrate(crate.getId())) {
m_crateHierarchy.deleteCrate(crate.getId());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deletion of crates must be performed atomically in a single transaction

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe m_crateHierarchy as a special kind of DAO should better be added to TrackCollection (our library controller) instead of some GUI component.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GUI code shouldn't have to be concerned with this. Calling TrackCollection::deleteCrate should take care of it automatically.

qDebug() << "Deleted crate" << crate;
m_pChildModel->reloadTree();
return;
}
}
Expand Down Expand Up @@ -502,11 +558,11 @@ void CrateFeature::slotAutoDjTrackSourceChanged() {
QModelIndex CrateFeature::rebuildChildModel(CrateId selectedCrateId) {
qDebug() << "CrateFeature::rebuildChildModel()" << selectedCrateId;

TreeItem* pRootItem = m_childModel.getRootItem();
TreeItem* pRootItem = m_pChildModel->getRootItem();
VERIFY_OR_DEBUG_ASSERT(pRootItem != nullptr) {
return QModelIndex();
}
m_childModel.removeRows(0, pRootItem->childRows());
m_pChildModel->removeRows(0, pRootItem->childRows());

QList<TreeItem*> modelRows;
modelRows.reserve(m_pTrackCollection->crates().countCrates());
Expand All @@ -526,13 +582,13 @@ QModelIndex CrateFeature::rebuildChildModel(CrateId selectedCrateId) {
}

// Append all the newly created TreeItems in a dynamic way to the childmodel
m_childModel.insertTreeItemRows(modelRows, 0);
m_pChildModel->insertTreeItemRows(modelRows, 0);

// Update rendering of crates depending on the currently selected track
slotTrackSelected(m_pSelectedTrack);

if (selectedRow >= 0) {
return m_childModel.index(selectedRow, 0);
return m_pChildModel->index(selectedRow, 0);
} else {
return QModelIndex();
}
Expand All @@ -549,8 +605,8 @@ void CrateFeature::updateChildModel(const QSet<CrateId>& updatedCrateIds) {
VERIFY_OR_DEBUG_ASSERT(crateStorage.readCrateSummaryById(crateId, &crateSummary)) {
continue;
}
updateTreeItemForCrateSummary(m_childModel.getItem(index), crateSummary);
m_childModel.triggerRepaint(index);
updateTreeItemForCrateSummary(m_pChildModel->getItem(index), crateSummary);
m_pChildModel->triggerRepaint(index);
}
if (m_pSelectedTrack) {
// Crates containing the currently selected track might
Expand All @@ -566,7 +622,7 @@ CrateId CrateFeature::crateIdFromIndex(const QModelIndex& index) const {
bool ok = false;
int id = index.data(AbstractRole::RoleData).toInt(&ok);
if (ok) {
return CrateId(id);
return CrateId(id);
}
return CrateId();
}
Expand All @@ -575,9 +631,9 @@ QModelIndex CrateFeature::indexFromCrateId(CrateId crateId) const {
VERIFY_OR_DEBUG_ASSERT(crateId.isValid()) {
return QModelIndex();
}
for (int row = 0; row < m_childModel.rowCount(); ++row) {
QModelIndex index = m_childModel.index(row, 0);
TreeItem* pTreeItem = m_childModel.getItem(index);
for (int row = 0; row < m_pChildModel->rowCount(); ++row) {
QModelIndex index = m_pChildModel->index(row, 0);
TreeItem* pTreeItem = m_pChildModel->getItem(index);
DEBUG_ASSERT(pTreeItem != nullptr);
if (!pTreeItem->hasChildren() && // leaf node
(CrateId(pTreeItem->getData()) == crateId)) {
Expand Down Expand Up @@ -793,17 +849,19 @@ void CrateFeature::slotExportTrackFiles() {
}

void CrateFeature::slotCrateTableChanged(CrateId crateId) {
if (m_lastRightClickedIndex.isValid() &&
(crateIdFromIndex(m_lastRightClickedIndex) == crateId)) {
// Preserve crate selection
m_lastRightClickedIndex = rebuildChildModel(crateId);
if (m_lastRightClickedIndex.isValid()) {
activateCrate(crateId);
}
} else {
// Discard crate selection
rebuildChildModel();
}
Q_UNUSED(crateId)
// if (m_lastRightClickedIndex.isValid() &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't comment out code. Delete it or keep it. Otherwise no one will know how to deal with this code snippets.

// (crateIdFromIndex(m_lastRightClickedIndex) == crateId)) {
// // Preserve crate selection
// m_lastRightClickedIndex = rebuildChildModel(crateId);
// if (m_lastRightClickedIndex.isValid()) {
// activateCrate(crateId);
// }
// } else {
// // Discard crate selection
// rebuildChildModel();
// }
m_pChildModel->reloadTree();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebuilding the tree should preserve the current selection. Otherwise I always need to manually navigate through the hierarchy back to the crate that I had selected.

}

void CrateFeature::slotCrateContentChanged(CrateId crateId) {
Expand All @@ -827,7 +885,7 @@ void CrateFeature::htmlLinkClicked(const QUrl& link) {
void CrateFeature::slotTrackSelected(TrackPointer pTrack) {
m_pSelectedTrack = pTrack;

TreeItem* pRootItem = m_childModel.getRootItem();
TreeItem* pRootItem = m_pChildModel->getRootItem();
VERIFY_OR_DEBUG_ASSERT(pRootItem != nullptr) {
return;
}
Expand All @@ -849,7 +907,7 @@ void CrateFeature::slotTrackSelected(TrackPointer pTrack) {
updateTreeItemForTrackSelection(pTreeItem, selectedTrackId, sortedTrackCrates);
}

m_childModel.triggerRepaint();
m_pChildModel->triggerRepaint();
}

void CrateFeature::slotResetSelectedTrack() {
Expand Down
9 changes: 8 additions & 1 deletion src/library/features/crates/cratefeature.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
#include <QStackedWidget>

#include "library/features/crates/cratetablemodel.h"
#include "library/features/crates/cratetreemodel.h"
#include "library/features/crates/cratehierarchy.h"


#include "library/libraryfeature.h"
#include "library/treeitemmodel.h"
Expand Down Expand Up @@ -59,6 +62,7 @@ class CrateFeature : public LibraryFeature {
void onRightClick(const QPoint& globalPos) override;
void onRightClickChild(const QPoint& globalPos, const QModelIndex& index) override;
void slotCreateCrate();
void slotCreateChildCrate();

private slots:
void slotDeleteCrate();
Expand Down Expand Up @@ -113,7 +117,7 @@ class CrateFeature : public LibraryFeature {
TrackCollection* m_pTrackCollection;

CrateTableModel* m_pCrateTableModel;
TreeItemModel m_childModel;
CrateHierarchy m_crateHierarchy;

QModelIndex m_lastRightClickedIndex;
TrackPointer m_pSelectedTrack;
Expand All @@ -123,6 +127,7 @@ class CrateFeature : public LibraryFeature {
// variant of parented_ptr as soon as it becomes available.
// See also: https://github.com/mixxxdj/mixxx/pull/1161
std::unique_ptr<QAction> m_pCreateCrateAction;
std::unique_ptr<QAction> m_pCreateChildCrateAction;
std::unique_ptr<QAction> m_pDeleteCrateAction;
std::unique_ptr<QAction> m_pRenameCrateAction;
std::unique_ptr<QAction> m_pLockCrateAction;
Expand All @@ -134,6 +139,8 @@ class CrateFeature : public LibraryFeature {
std::unique_ptr<QAction> m_pExportTrackFilesAction;
std::unique_ptr<QAction> m_pAnalyzeCrateAction;

std::unique_ptr<CrateTreeModel> m_pChildModel;

QHash<int, QPointer<QStackedWidget> > m_panes;
QHash<int, QPointer<CrateTableModel> > m_crateTableModel;
QHash<int,int> m_idBrowse;
Expand Down
4 changes: 4 additions & 0 deletions src/library/features/crates/cratefeaturehelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ CrateId CrateFeatureHelper::createEmptyCrate() {
continue;
}

// select name from crateClosure join crates on id = childId group by childId having count(*) = 1;
// check if it has parent, if not compare with names from above
// in closure.
// Else split path into tokens and compare to those
if (m_pTrackCollection->crates().readCrateByName(newCrate.getName())) {
QMessageBox::warning(
nullptr,
Expand Down
Loading