-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Use checkbox menu in crate selection. #1341
Conversation
Thank you for this PR. Nice Idea. It work well for a single track, unfortunately not well for multiple tracks. I think we need a three state check box or something, for the case where some of the selected tracks are in a crate and some not. Nit: Please move the pointer asterisk next to QWidget to meet our coding style. Be fore merge, we need your permission. @gramanas: how does this merge with your crate hierarchy branch? |
I can't check it atm, but this is based on master and not the new library redesign. Since it only changes widget files, I don't think it'll be hard to resolve the merge conflicts, but I can definitely see some. |
59683aa
to
4795bd9
Compare
I implemented the tristate when multiple tracks are selected that do not share a crate fully. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the changes.
It works nice now.
I have added some comments most of them are to improve the readability of the code.
src/library/crate/cratestorage.cpp
Outdated
// here. But since the coresponding FK column is indexed the impact on the | ||
// performance should be negligible. By reusing an existing query we reduce | ||
// the amount of code and the number of prepared SQL queries. | ||
CrateTrackSelectResult crateTracks(selectTrackCratesSorted(trackId)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The plural is kind of misleading here.
Isn't it trackCrates? I am unsure any other name?
src/library/crate/cratestorage.cpp
Outdated
while (crateTracks.next()) { | ||
DEBUG_ASSERT(crateTracks.trackId() == trackId); | ||
CrateId cid = crateTracks.crateId(); | ||
if(!trackCrates.contains(cid)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not understand this. Isn't it always false?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this if condition is redundant, because operator[] has already such a code.
src/widget/wtracktableview.cpp
Outdated
pAction->setDefaultWidget(pCheckBox.get()); | ||
|
||
// FIXME: we could use the tristate feature here and show grey in case of multi track selection | ||
// in which not all track are in the crate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this comment outdated?
src/widget/wtracktableview.cpp
Outdated
|
||
const QList<TrackId> trackIds = getSelectedTrackIds(); | ||
|
||
QHash<CrateId, QSet<TrackId>> currentCrates(m_pTrackCollection->crates().collectCrateTrackListOfTracks(trackIds)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please wrap this line around column 80. Vertical scrolling in Github is odd ;-)
src/widget/wtracktableview.cpp
Outdated
auto pCheckBox = std::make_unique<QCheckBox>(m_pCrateMenu); | ||
|
||
pCheckBox->setText(crate.getName()); | ||
pCheckBox->setProperty("crateId", crate.getId().toInt()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use toVariant()
src/widget/wtracktableview.cpp
Outdated
@@ -1466,22 +1494,51 @@ void WTrackTableView::addSelectionToPlaylist(int iPlaylistId) { | |||
playlistDao.appendTracksToPlaylist(trackIds, iPlaylistId); | |||
} | |||
|
|||
void WTrackTableView::addSelectionToCrate(int iCrateId) { | |||
void WTrackTableView::addRemoveSelectionInCrate(QWidget* qc) { | |||
CrateId crateId = static_cast<CrateId>(qc->property("crateId")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CrateId has a QVariant constructor, so you can omit the static cast.
src/widget/wtracktableview.cpp
Outdated
crateId = CrateFeatureHelper( | ||
m_pTrackCollection, m_pConfig).createEmptyCrate(); | ||
// we need to disable tristate again as the mixed state will now be gone and can't be brought back | ||
if (qc->property("tristate").toBool() == true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using string properties, we should use a qobject_cast at the very beginning of the function and check for null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried different techniques for hours, but this was the only only I got working properly. I'm quite open if someone gets this running using more slick code, but as I think it's none critical code from performance aspects, I think it's OK.
I will write some more optimal SQL queries in a separate pull request which should be a much better optimization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See poelzi#1
src/widget/wtracktableview.cpp
Outdated
// FIXME: we could use the tristate feature here and show grey in case of multi track selection | ||
// in which not all track are in the crate | ||
if(currentCrates.contains(crate.getId())) { | ||
if(currentCrates[crate.getId()].size() != trackIds.size()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, this works, but it took me quite a while to find out how.
I think this requires some comments.
currentCrates should also be renamed ... mmm
or can we move the size compare if condition to collectCrateTrackListOfTracks ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My thought was, that the API maybe more useful in the future. Giving a list of tracks and getting a hash with the crates and the containing tracks. Maybe I was overthinking it. I changed the api to just count the tracks in the crate.
src/widget/wtracktableview.cpp
Outdated
|
||
// FIXME: we could use the tristate feature here and show grey in case of multi track selection | ||
// in which not all track are in the crate | ||
if(currentCrates.contains(crate.getId())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
contains() and a subsequent [] is a double hash lookup. Use find();
4795bd9
to
cc8af31
Compare
Thanks for the review. I haven't user QT C++ before and haven't touched C++ for a very long time. |
src/library/crate/cratestorage.cpp
Outdated
// the amount of code and the number of prepared SQL queries. | ||
CrateTrackSelectResult trackCrates(selectTrackCratesSorted(trackId)); | ||
while (trackCrates.next()) { | ||
rv[trackCrates.crateId()] += 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure that the integer value in each entry is explicitly initialized with 0??? The fundamental type int does not have a default constructor and QHash will leave it uninitialized when inserting a new key/value pair.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I understood the QList documentation. It states that such types are initialized with 0.
Very nice, I like this feature 👍 Now I can delete my custom branch that allowed me to delete selected tracks from a crate. But I omitted to add visual feedback, if those tracks are actually contained in crates. That's why I never created a PR. |
Proposal: The name of the menu entry should be changed from "Add to Crate" to just "Crates"! |
src/widget/wtracktableview.cpp
Outdated
|
||
const QList<TrackId> trackIds = getSelectedTrackIds(); | ||
|
||
QHash<CrateId, int> currentCrates(m_pTrackCollection->crates().\ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately many queries are executed even if I don't open the Crates menu entry. Each right click on selected tracks now has a noticeable delay when you have many crates. The list of crates with their state should only be populated when the corresponding sub-menu entry becomes visible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I will look into optimizing it and change the name to crates :)
src/widget/wtracktableview.cpp
Outdated
Crate crate; | ||
while (allCrates.populateNext(&crate)) { | ||
auto pAction = std::make_unique<QAction>(crate.getName(), m_pCrateMenu); | ||
auto pAction = std::make_unique<QWidgetAction>(m_pCrateMenu); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we have recently introduced "make_parented" to document the qt object tree ownership and to avoid the unused unique_ptr nature.
@poelzi Any plans to address the open comments? I must admit that this is a really helpful feature that I merge into my private builds. I would like to see it in the next release(s). I usually have around 50+ crates and the performance penalties seem to be acceptable. We could even discuss to deliver the performance improvements in a follow-up release if (hopefully) we are able to return to a more frequent release cycle ;) |
I will hopefully get back addressing all the comments I have on my open pull requests and this one is annoying me as well ;) I had no working development machine for a while or was quite busy. |
f257b90
to
97a0684
Compare
I was able to optimize everything into one query with a subselect and reuse the CratesSummery code. |
Switch from normal QAction menu to a composition of QCheckBox and QActionWidget. The checkbox shows in which crates the selection is in. Changing the crates selection does not collapse the menu, which allows much easier categorization of tracks without going through the menu from scratch. Use tristate when multiple tracks are selected. When mulitple tracks are selected which do not share a crate, use the tristate partially selected to indicate this. Fix styling of pointers. Use QVariant to transport CrateId in checkbox Use optimized SQL query to select crates and count tracks. Unfortunatelly QSqlQuery has no way of binding QLists in WHERE x IN statements. Use make_partented as suggested by daschuer rename "Add to Crates" -> "Crates" to reflect function better
bf5080a
to
0a75b38
Compare
Optimize formatting of string lists for SQL queries
@poelzi for the future, it's easier to review changes since the last review if new commits are added instead of rebasing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just some small style nitpicks
src/library/crate/cratestorage.cpp
Outdated
@@ -468,6 +468,42 @@ CrateTrackSelectResult CrateStorage::selectTrackCratesSorted(TrackId trackId) co | |||
} | |||
} | |||
|
|||
CrateSummarySelectResult CrateStorage::selectCratesWithTrackCount(const QList<TrackId>& trackIds) const { | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: extra blank line here
src/library/crate/cratestorage.cpp
Outdated
// Using bindValue did not work, only constructing the SQL string befor. | ||
// As TrackId is a int, we are safe here | ||
QStringList idstrings; | ||
foreach(TrackId id, trackIds) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use standard C++11 for
loop instead of old Qt foreach
macro: for (TrackId id : trackIds) {
src/library/crate/cratestorage.cpp
Outdated
} else { | ||
return CrateSummarySelectResult(); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extra blank line
@Be-ing: I think your nit picks where fixed by the optimization Uwe Klotz did. |
Oh, you're correct. Sorry for the noise. |
The idea of lazily populating the context sub-menus is also mentioned in this bug report: Again, I like the functionality and would accept the lag in the first place. But we should think about how this can be fixed ASAP. |
@uklotzde i totally agree, but the current iteration does not make any more SQL calls then before.
It's 2 loops instead of one but fully covered by indexes. |
The lazy initialization of 2nd level actions affects multiple use cases and is a cross-cutting concern and could be solved in a separate task. I vote for integrating this extremly useful feature before improving the performance of all context menus that require database access. |
If the song changes while the current history view is open, the selection of songs is lost. Therefor all crate changes are lost. The history view saves and restores the selection through a new usefull api for selecting tracks in the wtracklistview.
I tested this and it works nicely. It would be cool to have this for Playlists too, but before that is implemented I think this needs to be lazy initialized or right clicking tracks will become even more terribly slow. |
Is there anything left to do here? Ready for merge? |
LGTM. Some nitpicking about typos and coding style. But I don't think we need another round here. I would like to get the approval of 1 or 2 other reviewers before merging anything myself. @daschuer How do we verify that the author has signed the contributor agreement? |
@poelzi is already a contributor. |
@daschuer it's poelzi on launchpad :) |
src/widget/wtracktableview.cpp
Outdated
@@ -1466,22 +1519,55 @@ void WTrackTableView::addSelectionToPlaylist(int iPlaylistId) { | |||
playlistDao.appendTracksToPlaylist(trackIds, iPlaylistId); | |||
} | |||
|
|||
void WTrackTableView::addSelectionToCrate(int iCrateId) { | |||
void WTrackTableView::addRemoveSelectionInCrate(QWidget* pWidget) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function name is ambiguous. Does it add or does it remove?
src/widget/wtracktableview.cpp
Outdated
void WTrackTableView::addRemoveSelectionInCrate(QWidget* pWidget) { | ||
auto pCheckBox = qobject_cast<QCheckBox*>(pWidget); | ||
VERIFY_OR_DEBUG_ASSERT(pCheckBox) { | ||
qWarning() << "crateId is not ef CrateId type"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"not ef"? Did you mean "not of"?
src/library/crate/cratestorage.cpp
Outdated
// assume that all track ids fit into 6 decimal digits and | ||
// add 1 character for the list separator. | ||
joinedTrackIds.reserve((6 + 1) * trackIds.size()); | ||
for (const auto trackId: trackIds) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const auto& trackId
to prevent unnecessary copy
src/widget/wtracktableview.cpp
Outdated
connect(pAction.get(), SIGNAL(triggered()), &m_crateMapper, SLOT(map())); | ||
pAction->setDefaultWidget(pCheckBox.get()); | ||
|
||
if(crate.getTrackCount() == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (
src/widget/wtracktableview.cpp
Outdated
|
||
if(crate.getTrackCount() == 0) { | ||
pCheckBox->setChecked(false); | ||
} else if(crate.getTrackCount() == (uint)trackIds.length()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} else if (
src/widget/wtracktableview.cpp
Outdated
return; | ||
} | ||
|
||
foreach(TrackId trackId, trackIds) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use modern standard C++ instead of old Qt foreach
macro: for (const auto& trackId : trackIds) {
…e/crate_selectbox
@poelzi Do you have time to address Be's comments and resolve the pending merge conflict, Daniel? I would like to have this in 2.1, because it has proven to be a valuable feature 👍 |
LGTM, thank you! @poelzi would you be interested in implementing the lazy initialization to alleviate https://bugs.launchpad.net/mixxx/+bug/1733200 for 2.1? |
@Be-ing kinda added the required infrastructure in another branch already. I added a widget (qlistview) that displays the crates of the selected track. I used a background thread to fetch the information in background and update the gui when done. If we decide to use one thread for sql queryies and gui support or one thread per component. https://ibb.co/dJsuf6 |
@uklotzde ready for merge? |
@@ -1373,6 +1401,31 @@ QList<TrackId> WTrackTableView::getSelectedTrackIds() const { | |||
return trackIds; | |||
} | |||
|
|||
void WTrackTableView::setSelectedTracks(QList<TrackId> trackIds) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This parameter should be passed as const-ref to avoid a deep copy of the list. Unfortunately the range-based for loops do not work well with Qt containers. Declaring the loop variable as const-ref is not enough. The container itself must be const! There is already a link in our coding guidelines.
We have this issue at many places and I have done this wrong until recently. Checking the code with clazy could reveal those hidden performance killers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will fix this after merge
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I already did in #1427
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost ready. Just one minor issue that we should fix, now that we know better ;) |
Thank you for this big usability improvement for crates! Let's continue this momentum to make Mixxx 2.2 the best music library management software. :) |
Switch from normal QAction menu to a composition of QCheckBox and QActionWidget.
The checkbox shows in which crates the selection is in.
Changing the crates selection does not collapse the menu, which allows
much easier categorization of tracks without going through the menu from scratch.