Add action to expand/collapse items (effects, folders) and navigate effects with arrows

BUG: 470987
BUG: 497834
FIXED-IN: 25.08.0
This commit is contained in:
Jean-Baptiste Mardelle 2025-06-03 17:21:23 +02:00
parent 4df36597f4
commit 1df1e4c955
16 changed files with 225 additions and 42 deletions

View file

@ -131,7 +131,7 @@ find_package(Qt${QT_MAJOR_VERSION}
NetworkAuth
SvgWidgets
Xml
# Test
# Test
)
if(USE_DBUS)

View file

@ -256,7 +256,7 @@ target_link_libraries(kdenliveLib PUBLIC
Qt${QT_MAJOR_VERSION}::Multimedia
Qt${QT_MAJOR_VERSION}::NetworkAuth
Qt${QT_MAJOR_VERSION}::SvgWidgets
# Qt${QT_MAJOR_VERSION}::Test
# Qt${QT_MAJOR_VERSION}::Test
)
target_link_libraries(kdenliveLib PUBLIC PkgConfig::LIBAV)

View file

@ -178,6 +178,7 @@ AssetPanel::AssetPanel(QWidget *parent)
m_mixWidget->setVisible(false);
m_effectStackWidget->setVisible(false);
m_maskManager->setVisible(false);
connect(this, &AssetPanel::slotSwitchCollapseAll, m_effectStackWidget, &EffectStackView::slotSwitchCollapseAll);
connect(m_effectStackWidget, &EffectStackView::checkScrollBar, this, &AssetPanel::slotCheckWheelEventFilter);
connect(m_effectStackWidget, &EffectStackView::scrollView, this, &AssetPanel::scrollTo);
connect(m_effectStackWidget, &EffectStackView::checkDragScrolling, this, &AssetPanel::checkDragScroll);
@ -437,10 +438,10 @@ void AssetPanel::scrollTo(QRect rect)
{
// Ensure the scrollview widget adapted its height to the effectstackview height change
m_sc->widget()->adjustSize();
if (rect.height() < m_sc->height()) {
m_sc->ensureVisible(0, rect.y() + rect.height(), 0, 0);
if (rect.y() < m_sc->verticalScrollBar()->value()) {
m_sc->ensureVisible(0, rect.y(), 0, 0);
} else {
m_sc->ensureVisible(0, rect.y() + m_sc->height(), 0, 0);
m_sc->ensureVisible(0, rect.y() + qMin(m_sc->height(), rect.height()), 0, 0);
}
}

View file

@ -119,4 +119,5 @@ Q_SIGNALS:
void reloadEffect(const QString &path);
void switchCurrentComposition(int tid, const QString &compoId);
void slotSaveStack();
void slotSwitchCollapseAll();
};

View file

@ -79,7 +79,6 @@ void AssetParameterView::setModel(const std::shared_ptr<AssetParameterModel> &mo
});
Q_EMIT updatePresets();
connect(m_model.get(), &AssetParameterModel::dataChanged, this, &AssetParameterView::refresh);
int minHeight = 0;
int keyframeRow = -1;
// First pass: find and create the keyframe widget for the first animated parameter
for (int i = 0; i < model->rowCount(); ++i) {
@ -101,7 +100,6 @@ void AssetParameterView::setModel(const std::shared_ptr<AssetParameterModel> &mo
connect(this, &AssetParameterView::previousKeyframe, m_mainKeyframeWidget, &KeyframeContainer::goToPrevious);
connect(this, &AssetParameterView::addRemoveKeyframe, m_mainKeyframeWidget, &KeyframeContainer::addRemove);
connect(this, &AssetParameterView::sendStandardCommand, m_mainKeyframeWidget, &KeyframeContainer::sendStandardCommand);
minHeight += m_mainKeyframeWidget->minimumHeight();
m_mainKeyframeWidget->initNeededSceneAndHelper();
}
break;
@ -135,16 +133,12 @@ void AssetParameterView::setModel(const std::shared_ptr<AssetParameterModel> &mo
m_lay->insertRow(keyframeRow, w->createLabel(), w);
keyframeRow++;
}
minHeight += w->minimumHeight();
}
}
m_widgets.push_back(w);
}
}
setMinimumHeight(minHeight);
if (addSpacer) {
// m_lay->addStretch();
}
setFixedHeight(contentHeight());
// Ensure effect parameters are adjusted to current position
Monitor *monitor = pCore->getMonitor(m_model->monitorId);
Q_EMIT monitor->seekPosition(monitor->position());

View file

@ -371,7 +371,7 @@ KeyframeContainer::KeyframeContainer(std::shared_ptr<AssetParameterModel> model,
QMargins mrg = m_layout->contentsMargins();
m_editorviewcontainer->setFixedHeight(m_editorviewcontainer->currentWidget()->height());
m_baseHeight = m_editorviewcontainer->height() + m_toolbar->sizeHint().height();
m_addedHeight = mrg.top() + mrg.bottom();
m_addedHeight = mrg.top() + mrg.bottom() + m_layout->horizontalSpacing();
if (isColorWheel) {
addParameter(index);
}
@ -764,7 +764,7 @@ void KeyframeContainer::addParameter(const QPersistentModelIndex &index)
} else {
m_layout->addRow(paramWidget);
}
m_addedHeight += paramWidget->minimumHeight();
m_addedHeight += paramWidget->minimumHeight() + m_layout->horizontalSpacing();
m_fixedHeight = m_baseHeight + m_addedHeight;
} else {
m_parameters[index] = nullptr;

View file

@ -6386,3 +6386,29 @@ QLineEdit *Bin::searchLine()
{
return m_searchLine;
}
void Bin::expandCurrent()
{
QModelIndex currentSelection = m_proxyModel->selectionModel()->currentIndex();
if (currentSelection.isValid()) {
if ((m_itemView != nullptr) && m_listType == BinTreeView) {
auto *view = static_cast<QTreeView *>(m_itemView);
view->setExpanded(currentSelection, !view->isExpanded(currentSelection));
}
}
}
void Bin::expandAll()
{
QModelIndex currentSelection = m_proxyModel->selectionModel()->currentIndex();
if (currentSelection.isValid()) {
if ((m_itemView != nullptr) && m_listType == BinTreeView) {
auto *view = static_cast<QTreeView *>(m_itemView);
if (!view->isExpanded(currentSelection)) {
view->expandAll();
} else {
view->collapseAll();
}
}
}
}

View file

@ -386,6 +386,10 @@ public:
const QList<std::shared_ptr<MarkerListModel>> getAllClipsMarkers();
/** @brief Get the first selected clip*/
std::shared_ptr<ProjectClip> getFirstSelectedClip();
/** @brief Expand / collapse current item */
void expandCurrent();
/** @brief Expand / collapse all items */
void expandAll();
private Q_SLOTS:
void slotAddClip();

View file

@ -51,6 +51,7 @@ CollapsibleEffectView::CollapsibleEffectView(const QString &effectName, const st
, m_model(effectModel)
, m_blockWheel(false)
{
setFocusPolicy(Qt::ClickFocus);
const QString effectId = effectModel->getAssetId();
buttonUp->setIcon(QIcon::fromTheme(QStringLiteral("selection-raise")));
buttonUp->setToolTip(i18n("Move effect up"));
@ -413,22 +414,10 @@ void CollapsibleEffectView::slotUnGroup()
bool CollapsibleEffectView::eventFilter(QObject *o, QEvent *e)
{
if (o == collapseButton) {
if (e->type() == QEvent::MouseButtonPress) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(e);
if (mouseEvent->modifiers() == Qt::ShiftModifier) {
// do what you need
bool doCollapse = m_collapse->isActive();
Q_EMIT collapseAllEffects(doCollapse);
return true;
}
}
switch (e->type()) {
case QEvent::Enter:
return QWidget::eventFilter(o, e);
}
if (e->type() == QEvent::Enter) {
return QWidget::eventFilter(o, e);
}
if (e->type() == QEvent::Wheel) {
case QEvent::Wheel: {
auto *we = static_cast<QWheelEvent *>(e);
if (!m_blockWheel || we->modifiers() != Qt::NoModifier) {
return false;
@ -468,6 +457,32 @@ bool CollapsibleEffectView::eventFilter(QObject *o, QEvent *e)
}
return false;
}
break;
}
case QEvent::FocusIn:
if (!isActive()) {
Q_EMIT activateEffect(m_model->row());
auto qw = qobject_cast<QWidget *>(o);
if (qw) {
qw->setFocus();
e->accept();
return true;
}
}
return QWidget::eventFilter(o, e);
case QEvent::MouseButtonPress:
if (o == collapseButton) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(e);
if (mouseEvent->modifiers() == Qt::ShiftModifier) {
// do what you need
bool doCollapse = m_collapse->isActive();
Q_EMIT collapseAllEffects(doCollapse);
return true;
}
}
return QWidget::eventFilter(o, e);
default:
break;
}
return QWidget::eventFilter(o, e);
}
@ -759,12 +774,11 @@ void CollapsibleEffectView::slotResetEffect()
void CollapsibleEffectView::updateHeight()
{
if (m_view->height() == widgetFrame->height()) {
if (m_view->minimumHeight() == widgetFrame->height()) {
return;
}
widgetFrame->setFixedHeight(m_collapse->isActive() ? 0 : m_view->height());
setFixedHeight(widgetFrame->height() + border_frame->minimumHeight() + frame->minimumHeight() + zoneFrame->minimumHeight() +
2 * (contentsMargins().top() + decoframe->lineWidth()));
widgetFrame->setFixedHeight(m_collapse->isActive() ? 0 : m_view->minimumHeight());
setFixedHeight(widgetFrame->height() + border_frame->minimumHeight() + frame->minimumHeight() + zoneFrame->minimumHeight());
Q_EMIT switchHeight(m_model, height());
}
@ -777,10 +791,9 @@ void CollapsibleEffectView::switchCollapsed(int row)
void CollapsibleEffectView::slotSwitch(bool collapse)
{
widgetFrame->setFixedHeight(collapse ? 0 : m_view->sizeHint().height());
widgetFrame->setFixedHeight(collapse ? 0 : m_view->minimumHeight());
zoneFrame->setFixedHeight(collapse || !m_inOutButton->isChecked() ? 0 : frame->height());
setFixedHeight(widgetFrame->height() + border_frame->minimumHeight() + frame->minimumHeight() + zoneFrame->height() +
2 * (contentsMargins().top() + decoframe->lineWidth()));
setFixedHeight(widgetFrame->height() + border_frame->minimumHeight() + frame->minimumHeight() + zoneFrame->height());
m_model->setCollapsed(collapse);
keyframesButton->setVisible(!collapse);
inOutButton->setVisible(!collapse);
@ -1164,3 +1177,8 @@ void CollapsibleEffectView::collapseEffect(bool collapse)
{
m_collapse->setActive(!collapse);
}
bool CollapsibleEffectView::isCollapsed() const
{
return !m_collapse->isActive();
}

View file

@ -78,6 +78,7 @@ public:
const QString getAssetId() const;
/** @brief Collapse / expand the effect */
void collapseEffect(bool collapse);
bool isCollapsed() const;
public Q_SLOTS:
void slotSyncEffectsPos(int pos);

View file

@ -89,6 +89,7 @@ EffectStackView::EffectStackView(AssetPanel *parent)
setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
setAcceptDrops(true);
setFocusPolicy(Qt::StrongFocus);
m_builtStack = new QWidget(this);
m_lay->addWidget(m_builtStack);
@ -516,6 +517,7 @@ void EffectStackView::updateTreeHeight()
if (!m_model) {
return;
}
qDebug() << ":::: UPDATING TREE HEIGHT....";
int totalHeight = 0;
for (int j = 0; j < m_model->rowCount(); j++) {
std::shared_ptr<AbstractEffectItem> item2 = m_model->getEffectStackRow(j);
@ -523,9 +525,10 @@ void EffectStackView::updateTreeHeight()
QModelIndex idx = m_filter->mapFromSource(m_model->getIndexFromItem(eff));
auto w = m_effectsTree->indexWidget(idx);
if (w) {
totalHeight += w->height();
totalHeight += w->minimumHeight();
}
}
qDebug() << ":::: UPDATING TREE HEIGHT, TOTAL: " << totalHeight << ", CURRENTR: " << m_effectsTree->height();
if (totalHeight != m_effectsTree->height()) {
m_effectsTree->setFixedHeight(totalHeight);
m_scrollTimer.start();
@ -559,6 +562,16 @@ void EffectStackView::startDrag(const QPixmap pix, const QString assetId, Object
drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction);
}
void EffectStackView::slotSwitchCollapseAll()
{
QModelIndex ix = m_filter->mapFromSource(m_model->index(0, 0, QModelIndex()));
CollapsibleEffectView *w = static_cast<CollapsibleEffectView *>(m_effectsTree->indexWidget(ix));
if (w) {
bool collapsed = w->isCollapsed();
slotCollapseAllEffects(!collapsed);
}
}
void EffectStackView::slotCollapseAllEffects(bool collapse)
{
for (int i = 0; i < m_model->rowCount(); ++i) {
@ -856,7 +869,8 @@ void EffectStackView::sendStandardCommand(int command)
bool EffectStackView::eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::MouseButtonPress) {
switch (e->type()) {
case QEvent::MouseButtonPress: {
auto me = static_cast<QMouseEvent *>(e);
m_dragStart = me->globalPosition().toPoint();
m_dragging = false;
@ -869,7 +883,7 @@ bool EffectStackView::eventFilter(QObject *o, QEvent *e)
e->accept();
return true;
}
if (e->type() == QEvent::MouseMove) {
case QEvent::MouseMove: {
auto me = static_cast<QMouseEvent *>(e);
if (!m_dragging && (me->globalPosition().toPoint() - m_dragStart).manhattanLength() > QApplication::startDragDistance()) {
m_dragging = true;
@ -884,7 +898,80 @@ bool EffectStackView::eventFilter(QObject *o, QEvent *e)
e->accept();
return true;
}
return QWidget::eventFilter(o, e);
default:
if ((qobject_cast<CollapsibleEffectView *>(o)) && e->type() == QEvent::KeyPress) {
switch (static_cast<QKeyEvent *>(e)->key()) {
case Qt::Key_Down: {
int row = m_model->getActiveEffect() + 1;
if (row >= m_model->rowCount()) {
row = 0;
}
activateAndScroll(row);
e->accept();
break;
}
case Qt::Key_Up: {
int row = m_model->getActiveEffect() - 1;
if (row < 0) {
row = m_model->rowCount() - 1;
}
activateAndScroll(row);
e->accept();
break;
}
default:
e->ignore();
}
return true;
}
return QWidget::eventFilter(o, e);
}
}
void EffectStackView::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Down: {
int row = m_model->getActiveEffect() + 1;
if (row >= m_model->rowCount()) {
row = 0;
}
activateAndScroll(row);
break;
}
case Qt::Key_Up: {
int row = m_model->getActiveEffect() - 1;
if (row < 0) {
row = m_model->rowCount() - 1;
}
activateAndScroll(row);
break;
}
default:
break;
}
QWidget::keyPressEvent(event);
}
void EffectStackView::activateAndScroll(int row)
{
m_model->setActiveEffect(row);
int scrollPos = 0;
for (int ix = 0; ix < row; ix++) {
QModelIndex index = m_filter->mapFromSource(m_model->index(ix, 0, QModelIndex()));
auto *del = static_cast<WidgetDelegate *>(m_effectsTree->itemDelegateForIndex(index));
if (del) {
scrollPos += del->height(index);
}
}
/*int height = 50;
QModelIndex index = m_filter->mapFromSource(m_model->index(row, 0, QModelIndex()));
m_effectsTree->setCurrentIndex(index);
auto *del = static_cast<WidgetDelegate *>(m_effectsTree->itemDelegateForIndex(index));
if (del) {
height = del->height(index);
}*/
slotFocusEffect();
}
void EffectStackView::updateSamProgress(int progress, bool exportStep)

View file

@ -82,6 +82,9 @@ public Q_SLOTS:
*/
void slotSaveStack();
void updateSamProgress(int progress, bool exportStep = false);
/** @brief Collapse / expand all effects in the stack
*/
void slotSwitchCollapseAll();
protected:
void dragEnterEvent(QDragEnterEvent *event) override;
@ -92,6 +95,7 @@ protected:
void paintEvent(QPaintEvent *event) override;
/** @brief Install event filter so that scrolling with mouse wheel does not change parameter value. */
bool eventFilter(QObject *o, QEvent *e) override;
void keyPressEvent(QKeyEvent *event) override;
private:
QMutex m_mutex;
@ -125,6 +129,9 @@ private:
void destroyBuildinWidget();
void constructBuildinWidget();
/** @brief Activate an effect and ensure it is visible
*/
void activateAndScroll(int row);
private Q_SLOTS:
void refresh(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles);

View file

@ -1298,6 +1298,10 @@ void MainWindow::setupActions()
addAction(QStringLiteral("collapse_expand"), collapseItem, Qt::Key_Less);
connect(collapseItem, &QAction::triggered, this, &MainWindow::slotCollapse);
QAction *collapseAllItems = new QAction(QIcon::fromTheme(QStringLiteral("collapse-all")), i18n("Collapse/Expand All Items"), this);
addAction(QStringLiteral("collapse_expand_all"), collapseAllItems, QKeySequence(Qt::SHIFT | Qt::Key_Less));
connect(collapseAllItems, &QAction::triggered, this, &MainWindow::slotCollapseAll);
QAction *sameTrack = new QAction(QIcon::fromTheme(QStringLiteral("composite-track-preview")), i18n("Mix Clips"), this);
sameTrack->setWhatsThis(
xi18nc("@info:whatsthis", "Creates a same-track transition between the selected clip and the adjacent one closest to the playhead."));
@ -4580,7 +4584,8 @@ void MainWindow::slotCollapse()
{
if ((QApplication::focusWidget() != nullptr) && (QApplication::focusWidget()->parentWidget() != nullptr) &&
QApplication::focusWidget()->parentWidget() == pCore->bin()) {
// Bin expand/collapse?
// Bin expand/collapse
pCore->bin()->expandCurrent();
} else {
QWidget *widget = QApplication::focusWidget();
@ -4591,12 +4596,32 @@ void MainWindow::slotCollapse()
}
widget = widget->parentWidget();
}
// Collapse / expand track
getCurrentTimeline()->controller()->collapseActiveTrack();
}
}
void MainWindow::slotCollapseAll()
{
if ((QApplication::focusWidget() != nullptr) && (QApplication::focusWidget()->parentWidget() != nullptr) &&
QApplication::focusWidget()->parentWidget() == pCore->bin()) {
// Bin expand/collapse
pCore->bin()->expandAll();
} else {
QWidget *widget = QApplication::focusWidget();
while ((widget != nullptr) && widget != this) {
if (widget == m_effectStackDock) {
Q_EMIT m_assetPanel->slotSwitchCollapseAll();
return;
}
widget = widget->parentWidget();
}
// Collapse / expand track
getCurrentTimeline()->controller()->collapseAllTracks();
}
}
void MainWindow::slotExpandClip()
{
getCurrentTimeline()->controller()->expandActiveClip();

View file

@ -602,6 +602,7 @@ private Q_SLOTS:
void slotGrabItem();
/** @brief Collapse or expand current item (depending on focused widget: effet, track)*/
void slotCollapse();
void slotCollapseAll();
/** @brief Cycle zoom audio waveforms*/
void slotZoomWaveForm();
/** @brief Save currently selected timeline clip as bin subclip*/

View file

@ -1600,6 +1600,23 @@ void TimelineController::adjustAllTrackHeight(int trackId, int height)
Q_EMIT m_model->dataChanged(modelStart, modelEnd, {TimelineModel::HeightRole});
}
void TimelineController::collapseAllTracks()
{
int tid = m_activeTrack;
if (!m_model->isTrack(tid)) {
auto it = m_model->m_allTracks.cbegin();
if (it != m_model->m_allTracks.cend()) {
tid = (*it)->getId();
}
if (!m_model->isTrack(tid)) {
return;
}
}
bool collapsed = m_model->getTrackById_const(tid)->getProperty("kdenlive:collapsed").toInt() > 0;
int collapsedHeight = m_root->property("collapsedHeight").toInt();
collapseAllTrackHeight(tid, !collapsed, collapsedHeight);
}
void TimelineController::collapseAllTrackHeight(int trackId, bool collapse, int collapsedHeight)
{
bool isAudio = m_model->getTrackById_const(trackId)->isAudioTrack();

View file

@ -644,6 +644,7 @@ public:
bool refreshIfVisible(int cid);
/** @brief Collapse / expand active track */
void collapseActiveTrack();
void collapseAllTracks();
/** @brief Expand MLT playlist to its contained clips/compositions */
void expandActiveClip();
/** @brief Retrieve a list of possible audio stream targets */