Solved Row selection state of QTreeWidget list breaks when keys are pressed too fast
-
Hi.
I have a simple list implemented with a QTreeWidget, where each row contains info on some audio file which is played (via the QAudioOutput class) and analyzed/plotted when the row is selected.For user convenience, the selection can be changed by up/down key presses and when the selection changes, the playback of the previous file is stopped, and the new file is played and analyzed in the itemSelectionChanged() slot.
The GUI controls are updated according to the state of the QAudioOutput player (e.g. play button becomes a stop button when player reports ActiveState, and vice-versa when player reports StoppedState, and the stylesheet of the tree widget is changed so that the row has a different style when playing, etc.).
The above works fine as long as there is sufficient time between key presses (interrupting the playback also works). However, when pressing the keys too fast, the QTreeWidget does not keep up, and the selection highlight even moves in the opposite direction than intended!?? E.g. when quickly pressing the down key several times, the actual selection highlight starts to move in the up direction! When everything gets settled (after stopping the key presses), row 3 would be highlighted, while the actual selected row (based on the presses) would be e.g 13. This becomes obvious at the next down key press, as the highlight jumps from row 3 to row 14.
What is happening? Does this has to do with reentrance (of itemSelectionChanged())?
-
Hi,
Without knowing the implementation details it's impossible to answer, you might be doing enough stuff that the event loop is slowed down to a crawl and hence when it has again the chance to run it processes events as fast at it can.
You should consider adding an "anti-bounce" to your selection handling. There's no use loading a new audio file while your user is still moving around with the keyboard. That mechanism is basically a single shot QTimer that you reset each time the selection changed. If the user hasn't moved for some time, then load the new file.
-
@SGaist said in Row selection state of QTreeWidget list breaks when keys are pressed too fast:
There's no use loading a new audio file while your user is still moving around with the keyboard. That mechanism is basically a single shot QTimer that you reset each time the selection changed. If the user hasn't moved for some time, then load the new file
Thank you , SGaist. I understand the delays can cause problems, but it does not necessarily justify the fact that this can cause the row selection state to run backwards! I could understand that it cannot catch up as fast as the keys are pressed, but I certainly was not prepared to see the selection highlight run in the opposite direction all of a sudden. In the meanwhile I have found that the reversing occurs when the list starts to scroll (when I have reached the bottom or top row in the currently visible range.)
Edit: this does not seem to have anything to do with the scrolling. The reversing also happens before the top or bottom is reached.The fast key presses are a means to quickly visually inspect the files. Since the loading into memory seems fast enough, I will indeed consider your suggestion and delay playback until the key presses settle down.
Thanks for your infinite patience helping others out with your expertise.
-
As you can imagine, there was something quite wrong with my implementation.
First error was to disconnect the slots from the
QAudioOutput
object immediately after issuing thestop()
command. I just stupidly followed the approach in the Qt Spectrum example, but this does not work in my case, because I rely on thestateChanged
signal to update my UI state.The second error was that immediately after issuing the
start()
command, I called the slotonAudioNotify()
which I implemented as follows:void AudioEngine::onAudioNotify() { const qint64 uSecsPlayed = m_audioOutput->processedUSecs(); emit playPosChanged((double(uSecsPlayed) + m_selStartMicroSecs)/1000000); //All the values are truncated such that they are audio frame boundary-aligned const qint64 playPos = numAudioBytes(m_outputSettings.format, uSecsPlayed + m_selStartMicroSecs); calculateLevel(m_data, playPos, m_outInterval_bytes); }
where
calculateLevel()
is implemented as follows:void AudioEngine::calculateLevel(QByteArray* data, qint64 position, qint64 length) { //__UM_ENGINE_TRACE__; qreal peakLevel = 0.0; if (position + length > data->length()) length = data->length() - position; //qreal sum = 0.0; const char *ptr = data->constData() + position; const char *const end = ptr + length; //The level is computed over all channels while (ptr < end) { const qint16 value = *reinterpret_cast<const qint16*>(ptr); const qreal fracValue = pcmToReal(value); peakLevel = qMax(peakLevel, fracValue); ptr += 2; } emit levelChanged(peakLevel); }
By invoking the above
onAudioNotify()
immediately after thestart()
command, I just wanted to initialize the level meter and play position marker, but somehow this also breaks the UI state of the QTreeWidget.Thus I replaced
m_audioOutput->start(&m_buffer); onAudioNotify();
by
m_audioOutput->start(&m_buffer); emit playPosChanged(0);
For reasons I don't get, invoking the level calculation immediately after
start()
broke my UI state somehow. Only emitting the playPosChanged signal just after start fixed things. The QTreeWidget is now always in a consistent selection state.