Skip to content

Highlight Crates a Track is in#598

Closed
MK-42 wants to merge 25 commits intomixxxdj:masterfrom
MK-42:HighlightCratesNew
Closed

Highlight Crates a Track is in#598
MK-42 wants to merge 25 commits intomixxxdj:masterfrom
MK-42:HighlightCratesNew

Conversation

@MK-42
Copy link
Copy Markdown
Contributor

@MK-42 MK-42 commented May 28, 2015

I've added some basic handling for lp: 1380467 (https://bugs.launchpad.net/mixxx/+bug/1380467)

This will save the last selected track from trackSelected signal of the library and use a delegate for the LibraryTreeView to highlight the crates that track is in.

Todo:
Find a way to repaint the widget after style-change (any hints on that?) for now scrolling the treeView brings it to live.
Apply the same for playlists

Some questions:

  1. how to trigger the repaint the best/preferred way?
  2. is it ok what I'm doing there? Adding a getter for CrateDao Just trying to get this working, any hints are welcome.

btw: what is dao short for? Maybe st. like dataAccessObject? Just curious, maybe makes it easier to read for me.

Comment thread src/library/cratefeature.cpp Outdated
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

"this->" is redundant

@daschuer
Copy link
Copy Markdown
Member

Hi Markus,

thank you for working on this.

Sorry I have no answer for your question.
Can one else jump in here?
If not I am afraid you need to search the web for a solution.

So far the code seams to be on the right track.
I have added only some code style nit picks.

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented May 29, 2015

Thanks for your comments. Ive changed it and will commit shortly.

The repaint-issue is harder than I thought. I've tried with dataChanged signal of the TreeItemModel but without success. For now the web gives no other solution for me I fear. But will continue searching.

Comment thread src/widget/wlibrarysidebar.cpp Outdated
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this-> is redundant.

@daschuer
Copy link
Copy Markdown
Member

When is initStyleOption() called? The init prefix makes me think that it is only called one time.
You may add a breakpoint or a qDebug() to check it.

It think you have to invalidate the index inside CrateFeature::slotTrackSelected.

Than you probably have to override the paint() function to paint yourself or override displayText and change the style there.

http://doc.qt.io/qt-4.8/qstyleditemdelegate.html

You may also search Github for qstyleditemdelegate to see how others use it.

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented May 29, 2015

I've managed to fix it. kind of hackish, but works. I'm telling the model that I will insert rows what gives a repaint. Nothing else worked for me.

If you're ok with this, I will also add handling for playlists before we merge this.

Comment thread src/library/cratehighlightdelegate.cpp Outdated
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add here a comment, when it is called?

Comment thread src/library/treeitemmodel.cpp Outdated
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

did you try
emit dataChanged()
http://doc.qt.io/qt-5/qabstractitemmodel.html#dataChanged
"When reimplementing the setData() function, this signal must be emitted explicitly."

QModelIndex().parent() looks a bit scary can we use at least the real indexes here?

This function repaints the whole tree, for some extra points, it would be nice to invalidate only the effected items.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, i tried emit dataChanged() with various parameters. I couldn't get it to work with that. From that point on, I was unable to repaint only some items so I stick with repainting the whole tree.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is similar to how we do it in BaseSqlTableModel. Did you try this way?

QModelIndex left = index(0, 0);
QModelIndex right = index(numRows, numColumns);
emit(dataChanged(left, right));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes I did (just now once again to be sure). It has no effect. The Widget will not repaint.

I have a solution now without the sideeffect of moving selection by doing
beginInsertRows(QModelIndex(), rowCount(), rowCount());
endInsertRows()

but that also isn't the nice way...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I figured this one out -- SidebarModel wasn't re-broadcasting the dataChanged signal. (sent a PR)

@daschuer
Copy link
Copy Markdown
Member

If you're ok with this, I will also add handling for playlists before we merge this.

Yes, go ahead.

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented May 29, 2015

well... playlists are buggy. For some reason, I do not understand, the selection in the treeview moves one item down if a playlist is selected. Will investigate...

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented May 29, 2015

Oh. actually that happens for crates also. So we need another way to trigger the repaint than that hack, that seems to have side-effects.

@rryan
Copy link
Copy Markdown
Member

rryan commented May 29, 2015

Thanks for working on this Markus.

I'm worried that doing blocking database queries in response to UI movements will make it jerky for rapid scrolling through the library. Also, this implementation does one database query per crate/playlist when you could do the same with 2 queries (one to get all the crates a track is in and one to get all the playlists a track is in).

A couple ways this could be resolved:

  • A track could have a list of playlist and crates it is a member of that is maintained by the TrackInfoObject. (Hard because PlaylistDao / CrateDao do not have access to the TIO currently).
  • PlaylistDao/CrateDao could keep an in-memory map of track id -> [playlist/crate id], that represents the state of the database for fast lookups.

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented May 29, 2015

would you prefer to go with the in memory map or doing it with two querys, by holding the crates/playlists a track is in in memory?
The second one would mean 2 Queries each selection move, the first would need one initializing and none after that by handling all the signals of the DAOs i think.

When we decide to do it in memory, which dataStructure would be preferred? using
List<Pair<int, List<int> > > seems ugly :D
Edit: A QMultiMap would be a solution maybe

Still no nice solution for repainting, but one that works without sideeffects so far.

@rryan
Copy link
Copy Markdown
Member

rryan commented May 29, 2015

would you prefer to go with the in memory map or doing it with two querys, by holding the crates/playlists a track is in in memory?
The second one would mean 2 Queries each selection move, the first would need one initializing and none after that by handling all the signals of the DAOs i think.

I think it'd be best to go with the in-memory map idea. That way we only pay a small insert/update cost on every Playlist/Crate modification operation in the DAOs. Compared to the DB queries associated with each of those operations the extra cost is tiny :).

Whereas doing queries in response to GUI operations has caused a lot of jerkiness in our UIs in the past.

When we decide to do it in memory, which dataStructure would be preferred? using
List<Pair<int, List > > seems ugly :D

A QMultiHash<int, int> is probably best here. That provides constant-time lookup.

@rryan
Copy link
Copy Markdown
Member

rryan commented May 29, 2015

Hm, I'm getting segfaults on start:

Warning [Main]: ControlDoublePrivate::getControl returning NULL for ( "[EffectRack1]" , "show" )
Warning [Main]: Requested control does not exist: "[EffectRack1],show" Creating it.
Warning [Main]: Object::connect: No such signal MixxxLibraryFeature::trackSelected(TrackPointer) in src/library/mixxxlibraryfeature.cpp:131
Warning [Main]: Object::connect:  (sender name:   'DlgHidden')
Warning [Main]: Object::connect: No such signal MixxxLibraryFeature::trackSelected(TrackPointer) in src/library/mixxxlibraryfeature.cpp:137
Warning [Main]: Object::connect:  (sender name:   'DlgMissing')
Warning [Main]: Object::connect: No such signal AutoDJFeature::trackSelected(TrackPointer) in src/library/autodj/autodjfeature.cpp:123
Warning [Main]: Object::connect:  (sender name:   'DlgAutoDJ')
Debug [Main]: Recordings folder set to "/Users/rjryan/Music/Mixxx/Recordings"
Debug [BrowseThread]: Append last  0
Debug [Main]: BaseTrackCache(0x11d621a50) updateIndexWithQuery took 4 ms
Debug [Main]: AnalysisLibraryTableModel(0x15a3b4db0) select() took 12 ms 0
Debug [Main]: AnalysisLibraryTableModel(0x15a3b4db0) select() took 2 ms 0
Warning [Main]: Object::connect: No such signal AnalysisFeature::trackSelected(TrackPointer) in src/library/analysisfeature.cpp:76
Warning [Main]: Object::connect:  (sender name:   'DlgAnalysis')
Debug [Main]: DlgAnalysis(0x15a33e320, name = "DlgAnalysis") analysisActive false
Warning [Main]: ControlDoublePrivate::getControl returning NULL for ( "[VinylControl]" , "show_vinylcontrol" )
Debug [Main]: Setting visibility for "[PreviewDeck]" "show_previewdeck" true
Warning [Main]: ControlDoublePrivate::getControl returning NULL for ( "[VinylControl]" , "show_vinylcontrol" )
Process 16497 stopped
* thread #1: tid = 0x4847f2, 0x00000001003dcd41 mixxx`CrateHighlightDelegate::initStyleOption(this=<unavailable>, option=<unavailable>, index=<unavailable>) const + 113 at cratehighlightdelegate.cpp:25, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
    frame #0: 0x00000001003dcd41 mixxx`CrateHighlightDelegate::initStyleOption(this=<unavailable>, option=<unavailable>, index=<unavailable>) const + 113 at cratehighlightdelegate.cpp:25
   22       TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
   23       LibraryFeature* pFeature = item->getFeature();
   24       if (pFeature) {
-> 25           TrackPointer pTrack = pFeature->getSelectedTrack();
   26           if (pTrack) {
   27               if (pFeature->isTrackInChildModel(pTrack->getId(), item->dataPath())){
   28                   optionV4->font.setBold(true);
(lldb) bt all
* thread #1: tid = 0x4847f2, 0x00000001003dcd41 mixxx`CrateHighlightDelegate::initStyleOption(this=<unavailable>, option=<unavailable>, index=<unavailable>) const + 113 at cratehighlightdelegate.cpp:25, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
  * frame #0: 0x00000001003dcd41 mixxx`CrateHighlightDelegate::initStyleOption(this=<unavailable>, option=<unavailable>, index=<unavailable>) const + 113 at cratehighlightdelegate.cpp:25
    frame #1: 0x0000000101826d7e QtGui`QStyledItemDelegate::sizeHint(QStyleOptionViewItem const&, QModelIndex const&) const + 158
    frame #2: 0x00000001017b2c39 QtGui`QTreeView::indexRowSizeHint(QModelIndex const&) const + 1193
    frame #3: 0x00000001017b2d63 QtGui`QTreeViewPrivate::itemHeight(int) const + 115
    frame #4: 0x00000001017b2f0f QtGui`QTreeViewPrivate::updateScrollBars() + 207
    frame #5: 0x00000001017c013d QtGui`QTreeView::updateGeometries() + 477
    frame #6: 0x000000010176ccde QtGui`QAbstractItemView::doItemsLayout() + 46
    frame #7: 0x00000001017ba72e QtGui`QTreeView::doItemsLayout() + 766
    frame #8: 0x00000001017b4f91 QtGui`QTreeView::scrollTo(QModelIndex const&, QAbstractItemView::ScrollHint) + 113
    frame #9: 0x0000000100643870 mixxx`WLibrarySidebar::selectIndex(this=0x000000015a2d7840, index=0x00007fff5fbfeb70) + 320 at wlibrarysidebar.cpp:209
    frame #10: 0x0000000100fdb4b2 QtCore`QMetaObject::activate(QObject*, QMetaObject const*, int, void**) + 2498
    frame #11: 0x0000000100486623 mixxx`SidebarModel::selectIndex(this=<unavailable>, _t1=<unavailable>) + 51 at moc_sidebarmodel.cc:142
    frame #12: 0x00000001004c4dab mixxx`SidebarModel::activateDefaultSelection(this=0x000000011beeab70) + 107 at sidebarmodel.cpp:60
    frame #13: 0x0000000100fdb4b2 QtCore`QMetaObject::activate(QObject*, QMetaObject const*, int, void**) + 2498
    frame #14: 0x00000001004f1d7a mixxx`MixxxMainWindow::MixxxMainWindow(this=0x00000001032828c0, pApp=<unavailable>, args=0x0000000100a1e060) + 7914 at mixxx.cpp:402
    frame #15: 0x00000001004de817 mixxx`main(argc=<unavailable>, argv=<unavailable>) + 1351 at main.cpp:304
    frame #16: 0x00007fff896705c9 libdyld.dylib`start + 1
    frame #17: 0x00007fff896705c9 libdyld.dylib`start + 1

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented May 29, 2015

thats's weird... I'm on linux, everything is working here...

Comment thread src/library/cratehighlightdelegate.cpp Outdated
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The segfault is because the internal pointers in the sidebar can be SidebarModel* for items that are children of the root and TreeItem* for all others.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm not that experienced with that casting.
So I should do a dynamic_cast here and check if the result is ok?
dynamic_cast cant cast from void* to another, only downcast. Which way to go then?

Still can't reproduce the segfault. strange.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sent you a PR :).

@daschuer
Copy link
Copy Markdown
Member

cannot see difference with my 20 entries for both commits.

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented Jun 1, 2015

@rryan would it be OK to go back to the Delegate solution then, because it was faster for me?

@rryan
Copy link
Copy Markdown
Member

rryan commented Jun 1, 2015

I'd like to avoid delegates as they prevent us from taking faster paths in the Qt library code (generally we've had tons of trouble with the 3 delegates in our library table causing performance issues).

@rryan
Copy link
Copy Markdown
Member

rryan commented Jun 1, 2015

@rryan would it be OK to go back to the Delegate solution then, because it was faster for me?

I think reversing the playlist hash key/value pairing to be (track, playlist) instead of (playlist, track) will help. As it works now we do a contains check for every playlist when you select a new track. Because QHash<int, int>::contains(int, int) does a hash lookup and then linear search through all the values under the key, it first does a constant time hash fetch on the playlist ID and then iterates through every element that matched (i.e. all the tracks in the playlist). So every contains check actually iterates through the whole list of tracks in the playlist.

If the key/values were switched then it would be more efficient since we would typically be iterating through only the playlists IDs the track is in.

Another downside is that we do N hash lookups to iterate through the playlists the track is in. Since we have a multi-hash we could actually just search through the iterator. Instead of offering a contains() check from PlaylistDAO we should probably instead offer a lookup method that returns an iterator or maybe just populates a list or a set.

@rryan
Copy link
Copy Markdown
Member

rryan commented Jun 1, 2015

I'm happy to make the above changes and then maybe you could test if they fix your slowness @MK-42?

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented Jun 1, 2015

then maybe you could test if they fix your slowness @MK-42?

I can test that, no problem 👍

@rryan
Copy link
Copy Markdown
Member

rryan commented Jun 4, 2015

then maybe you could test if they fix your slowness @MK-42?
I can test that, no problem

Sent you a PR

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented Jun 4, 2015

Sorry, I'm busy. Will test this tonight

Am 04.06.2015 um 17:04 schrieb RJ Ryan:

then maybe you could test if they fix your slowness @MK-42
<https://github.com/MK-42>?
I can test that, no problem

Sent you a PR


Reply to this email directly or view it on GitHub
#598 (comment).

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented Jun 10, 2015

As commented in that pr, thats not faster and i have other playlists/tracks highlighted then those that contain the tracks. So highlighting is wrong.
I will now test again performance with my delegate solution and if that works will revert to that position. I think we should not merge the state as is, because its slow for me.

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented Jun 10, 2015

Works for me now as expected - hovers the right things and is fast. ok to merge for me

I think this was why we had to add signals to each individual feature.
@rryan
Copy link
Copy Markdown
Member

rryan commented Jun 10, 2015

I'd prefer to iterate on the previous commit since it avoids adding extra code and avoids a delegate. (the 3 delegates in our library table are the single biggest source of scrolling smoothness issues in the library).

The wrong data was a dumb mistake -- just pushed a commit to my branch:
rryan@41c5fa5

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented Jun 10, 2015

I've just seen that i missed an important change in your changed and cherry-picked that. But will test your branch now once again.

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented Jun 10, 2015

Well. Now it does highlighting right. But as soon as I expand the 'History' LibraryFeature, it takes about 2 seconds to move the selection if I press a arrow-key on my keyboard. The same with selecting one entry with the mouse. It takes about that time to get the selection change. Like this this is nearly unusable. One could deactivate bolding in the history-tree, but I would actually like the feature to see "did i played that the last time?".

Could it be, that the delegate only fetches the 'isBold'-data for the visible items and the current solution for all of them?

Edit: Also switching of libraryFeatures got that slow

@rryan
Copy link
Copy Markdown
Member

rryan commented Jun 10, 2015

Well. Now it does highlighting right. But as soon as I expand the 'History' LibraryFeature, it takes about 2 seconds to move the selection if I press a arrow-key on my keyboard. The same with selecting one entry with the mouse. It takes about that time to get the selection change. Like this this is nearly unusable. One could deactivate bolding in the history-tree, but I would actually like the feature to see "did i played that the last time?".

Could it be, that the delegate only fetches the 'isBold'-data for the visible items and the current solution for all of them?

Yea, this change populates the bold data once when the track is selected for all playlists.

To verify that this method is taking a long time, could you stick a PerformanceTimer in it? For example, stick this at the top of BasePlaylistFeature::slotTrackSelected:

ScopedTimer t("BasePlaylistFeature::slotTrackSelected");

Then run in --developer mode, select some tracks with the History section open, and then quit. It will print out a stats page to the console including statistics about the run time of that method.

How many history playlists do you have?

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented Jun 10, 2015

How many history playlists do you have?

about 300 i think. It's not that comfortable to delete them (there is no mass-delete or is there?) so they just build up...

Debug [Main]: Stat("BasePlaylistFeature::slotTrackSelected","count=18,sum=1.1583e+10ns,average=6.43498e+08ns,min=40365ns,max=1.49523e+09ns,variance=5.48256e+17ns^2,stddev=7.40443e+08ns")

Without history expanded:

Debug [Main]: Stat("BasePlaylistFeature::slotTrackSelected","count=20,sum=3.62196e+06ns,average=181098ns,min=32041ns,max=390973ns,variance=2.1982e+10ns^2,stddev=148263ns")

6.4e+08ns is 0.6 seconds. It feels longer than that for the selection to change but I think that there is the problem then?

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented Jun 10, 2015

The DAO seems fast:

Debug [Main]: Stat("PlaylistDAO::getPlaylistsTrackIsIn","count=16,sum=39816ns,average=2488.5ns,min=171ns,max=5270ns,variance=2.41036e+06ns^2,stddev=1552.53ns")

so I think it is the for-loop after that setting the bold-info or am I missing something?
Ive created a new block around the loop with {} and put a timer in there:

Debug [Main]: Stat("BasePlaylistFeature::slotTrackSelected","count=18,sum=1.13625e+10ns,average=6.31248e+08ns,min=42621ns,max=1.44179e+09ns,variance=5.27324e+17ns^2,stddev=7.26171e+08ns")
Debug [Main]: Stat("BasePlaylistFeature::slotTrackSelected_InLoop","count=18,sum=1.13623e+10ns,average=6.31242e+08ns,min=35990ns,max=1.44178e+09ns,variance=5.27325e+17ns^2,stddev=7.26171e+08ns")

There we are - its nearly the whole time in that loop.

Interesting: The first selection is always fast. And Its not the contains-call on the QSet using that time. Will investigate further

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented Jun 10, 2015

I think the problem is the setData in TreeItemModel which is called hundreds of times and everytime triggers a repaint via emit dataChanged.
I looked for something like beginUpdate and endUpdate but with no success

Edit: now I am sure that thats the problem. I've written an setBold in treeItemModel that does not call the dataChanged signal and now it is fast again.

We need to somehow switch that signal of, update all bold states and then retrigger a whole update for the whole treeview. Or what do you think?

@rryan
Copy link
Copy Markdown
Member

rryan commented Jun 10, 2015

@MK-42 ahh thanks for digging into that! Makes sense. To sidestep the updates we can reach into the TreeItem model itself:

diff --git a/src/library/baseplaylistfeature.cpp b/src/library/baseplaylistfeature.cpp
index 553e381..550f1d3 100644
--- a/src/library/baseplaylistfeature.cpp
+++ b/src/library/baseplaylistfeature.cpp
@@ -600,16 +600,24 @@ void BasePlaylistFeature::slotTrackSelected(TrackPointer pTrack) {
     int trackId = pTrack.isNull() ? -1 : pTrack->getId();
     m_playlistDao.getPlaylistsTrackIsIn(trackId, &m_playlistsSelectedTrackIsIn);

+    TreeItem* rootItem = m_childModel.getItem(QModelIndex());
+    if (rootItem == NULL) {
+        return;
+    }
+
     // Set all playlists the track is in bold (or if there is no track selected,
     // clear all the bolding).
     int row = 0;
     for (QList<QPair<int, QString> >::const_iterator it = m_playlistList.begin();
          it != m_playlistList.end(); ++it, ++row) {
         int playlistId = it->first;
-        QModelIndex index = m_childModel.index(row, 0);

+        TreeItem* playlist = rootItem->child(row);
+        if (playlist == NULL) {
+            continue;
+        }
         bool shouldBold = m_playlistsSelectedTrackIsIn.contains(playlistId);
-        m_childModel.setData(index, shouldBold, TreeItemModel::kBoldRole);
+        playlist->setBold(shouldBold);
     }
 }

@rryan
Copy link
Copy Markdown
Member

rryan commented Jun 10, 2015

But then it needs a full dataChanged refresh using a method like the one you wrote.

@rryan
Copy link
Copy Markdown
Member

rryan commented Jun 10, 2015

But then it needs a full dataChanged refresh using a method like the one you wrote.

TreeItemModel::triggerRepaint

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented Jun 10, 2015

Thats it. Like that it works. Thanks :)

But now ive screwed up that thing so much. Would it be ok to do a PR against your branch and you merge yours into master?

@rryan
Copy link
Copy Markdown
Member

rryan commented Jun 10, 2015

nice! sure I'll commit that patch and make a PR

@MK-42
Copy link
Copy Markdown
Contributor Author

MK-42 commented Jun 10, 2015

closing this as @rryan opend another one for this feature.

@MK-42 MK-42 closed this Jun 10, 2015
@MK-42 MK-42 deleted the HighlightCratesNew branch June 10, 2015 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants