From 0e7048e461751cf162da750461d79844972405d9 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 16 Oct 2025 21:30:32 +0300 Subject: [PATCH 01/70] Use eventFilter to process closeEvent This frees us from having to inherit Detachable{Widget,Window}, and instead we hook the handling directly in SubWindow. --- include/ControllerDialog.h | 5 +-- include/ControllerRackView.h | 5 +-- include/DetachableWidget.h | 13 ++++---- include/DetachableWindow.h | 49 ----------------------------- include/Editor.h | 5 ++- include/EffectControlDialog.h | 5 +-- include/MicrotunerConfig.h | 5 +-- include/MixerView.h | 5 +-- include/ProjectNotes.h | 5 +-- include/SampleTrackWindow.h | 5 +-- include/SubWindow.h | 3 ++ src/gui/CMakeLists.txt | 1 - src/gui/ControllerDialog.cpp | 2 +- src/gui/ControllerRackView.cpp | 2 +- src/gui/DetachableWidget.cpp | 44 ++++++++++++++++---------- src/gui/DetachableWindow.cpp | 56 --------------------------------- src/gui/EffectControlDialog.cpp | 2 +- src/gui/MicrotunerConfig.cpp | 2 +- src/gui/MixerView.cpp | 2 +- src/gui/ProjectNotes.cpp | 2 +- src/gui/SampleTrackWindow.cpp | 4 +-- src/gui/SubWindow.cpp | 42 +++++++++++++++++++++---- src/gui/editors/Editor.cpp | 2 +- 23 files changed, 104 insertions(+), 162 deletions(-) delete mode 100644 include/DetachableWindow.h delete mode 100644 src/gui/DetachableWindow.cpp diff --git a/include/ControllerDialog.h b/include/ControllerDialog.h index f9145d3b06f..bd4087eb9f5 100644 --- a/include/ControllerDialog.h +++ b/include/ControllerDialog.h @@ -26,9 +26,10 @@ #ifndef LMMS_GUI_CONTROLLER_DIALOG_H #define LMMS_GUI_CONTROLLER_DIALOG_H -#include "DetachableWidget.h" #include "ModelView.h" +#include + namespace lmms { @@ -37,7 +38,7 @@ class Controller; namespace gui { -class ControllerDialog : public DetachableWidget, public ModelView +class ControllerDialog : public QWidget, public ModelView { public: ControllerDialog(Controller* controller, QWidget* parent); diff --git a/include/ControllerRackView.h b/include/ControllerRackView.h index 93f125c1f9d..c56b67883ff 100644 --- a/include/ControllerRackView.h +++ b/include/ControllerRackView.h @@ -25,9 +25,10 @@ #ifndef LMMS_GUI_CONTROLLER_RACK_VIEW_H #define LMMS_GUI_CONTROLLER_RACK_VIEW_H -#include "DetachableWidget.h" #include "SerializingObject.h" +#include + class QPushButton; class QScrollArea; class QVBoxLayout; @@ -42,7 +43,7 @@ namespace gui class ControllerView; -class ControllerRackView : public DetachableWidget, public SerializingObject +class ControllerRackView : public QWidget, public SerializingObject { Q_OBJECT public: diff --git a/include/DetachableWidget.h b/include/DetachableWidget.h index 532dec3303f..293f6a3b2f4 100644 --- a/include/DetachableWidget.h +++ b/include/DetachableWidget.h @@ -26,22 +26,23 @@ #ifndef LMMS_GUI_DETACHABLE_WIDGET #define LMMS_GUI_DETACHABLE_WIDGET -#include +#include #include "lmms_export.h" +class QWidget; +class QEvent; + namespace lmms::gui { -class LMMS_EXPORT DetachableWidget : public QWidget +class LMMS_EXPORT DetachableWidget : public QObject { Q_OBJECT public: - using QWidget::QWidget; - - void closeEvent(QCloseEvent* ce) override; + bool eventFilter(QObject* obj, QEvent* e) override; signals: - void closed(); + void attach(); }; } // namespace lmms::gui diff --git a/include/DetachableWindow.h b/include/DetachableWindow.h deleted file mode 100644 index 1636b4fad25..00000000000 --- a/include/DetachableWindow.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * DetachableWindow.h - Allows a window to be detached from - * LMMS's main window - * - * Copyright (c) 2023 Dalton Messmer - * - * This file is part of LMMS - https://lmms.io - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -#ifndef LMMS_GUI_DETACHABLE_WINDOW -#define LMMS_GUI_DETACHABLE_WINDOW - -#include - -#include "lmms_export.h" - -namespace lmms::gui { - -class LMMS_EXPORT DetachableWindow : public QMainWindow -{ - Q_OBJECT -public: - using QMainWindow::QMainWindow; - - void closeEvent(QCloseEvent* ce) override; - -signals: - void closed(); -}; - -} // namespace lmms::gui - -#endif // LMMS_GUI_DETACHABLE_WINDOW diff --git a/include/Editor.h b/include/Editor.h index 34e3b48092d..2141717af58 100644 --- a/include/Editor.h +++ b/include/Editor.h @@ -25,10 +25,9 @@ #ifndef LMMS_GUI_EDITOR_H #define LMMS_GUI_EDITOR_H +#include #include -#include "DetachableWindow.h" - class QAction; namespace lmms::gui @@ -46,7 +45,7 @@ class DropToolBar; /// /// Those editors include the Song Editor, the Automation Editor, B&B Editor, /// and the Piano Roll. -class Editor : public DetachableWindow +class Editor : public QMainWindow { Q_OBJECT public: diff --git a/include/EffectControlDialog.h b/include/EffectControlDialog.h index 1cc748e9a5f..7adbbf3b607 100644 --- a/include/EffectControlDialog.h +++ b/include/EffectControlDialog.h @@ -26,9 +26,10 @@ #ifndef LMMS_GUI_EFFECT_CONTROL_DIALOG_H #define LMMS_GUI_EFFECT_CONTROL_DIALOG_H -#include "DetachableWidget.h" #include "ModelView.h" +#include + namespace lmms { @@ -37,7 +38,7 @@ class EffectControls; namespace gui { -class LMMS_EXPORT EffectControlDialog : public DetachableWidget, public ModelView +class LMMS_EXPORT EffectControlDialog : public QWidget, public ModelView { public: EffectControlDialog(EffectControls* controls); diff --git a/include/MicrotunerConfig.h b/include/MicrotunerConfig.h index dc3632b87dd..a990ef87199 100644 --- a/include/MicrotunerConfig.h +++ b/include/MicrotunerConfig.h @@ -27,9 +27,10 @@ #include "AutomatableModel.h" #include "ComboBoxModel.h" -#include "DetachableWidget.h" #include "SerializingObject.h" +#include + class QLineEdit; class QPlainTextEdit; @@ -37,7 +38,7 @@ namespace lmms::gui { -class LMMS_EXPORT MicrotunerConfig : public DetachableWidget, public SerializingObject +class LMMS_EXPORT MicrotunerConfig : public QWidget, public SerializingObject { Q_OBJECT public: diff --git a/include/MixerView.h b/include/MixerView.h index a82b84a9835..6dc5698b2fe 100644 --- a/include/MixerView.h +++ b/include/MixerView.h @@ -26,10 +26,11 @@ #define LMMS_GUI_MIXER_VIEW_H #include "MixerChannelView.h" -#include "DetachableWidget.h" #include "ModelView.h" #include "SerializingObject.h" +#include + class QDomDocument; // IWYU pragma: keep class QDomElement; // IWYU pragma: keep class QHBoxLayout; @@ -45,7 +46,7 @@ namespace lmms::gui { class LMMS_EXPORT MixerView - : public DetachableWidget + : public QWidget , public ModelView , public SerializingObjectHook { diff --git a/include/ProjectNotes.h b/include/ProjectNotes.h index 9154f13ea04..f719f6e3e71 100644 --- a/include/ProjectNotes.h +++ b/include/ProjectNotes.h @@ -25,9 +25,10 @@ #ifndef LMMS_GUI_PROJECT_NOTES_H #define LMMS_GUI_PROJECT_NOTES_H -#include "DetachableWindow.h" #include "SerializingObject.h" +#include + class QAction; class QComboBox; class QTextCharFormat; @@ -37,7 +38,7 @@ namespace lmms::gui { -class LMMS_EXPORT ProjectNotes : public DetachableWindow, public SerializingObject +class LMMS_EXPORT ProjectNotes : public QMainWindow, public SerializingObject { Q_OBJECT public: diff --git a/include/SampleTrackWindow.h b/include/SampleTrackWindow.h index 8b1a136c625..b90e61f07c1 100644 --- a/include/SampleTrackWindow.h +++ b/include/SampleTrackWindow.h @@ -25,11 +25,12 @@ #ifndef LMMS_GUI_SAMPLE_TRACK_WINDOW_H #define LMMS_GUI_SAMPLE_TRACK_WINDOW_H -#include "DetachableWidget.h" #include "ModelView.h" #include "SampleTrack.h" #include "SerializingObject.h" +#include + class QLineEdit; namespace lmms::gui @@ -42,7 +43,7 @@ class MixerChannelLcdSpinBox; class SampleTrackView; -class SampleTrackWindow : public DetachableWidget, public ModelView, public SerializingObjectHook +class SampleTrackWindow : public QWidget, public ModelView, public SerializingObjectHook { Q_OBJECT public: diff --git a/include/SubWindow.h b/include/SubWindow.h index f300335dbe6..2a677b86a4c 100644 --- a/include/SubWindow.h +++ b/include/SubWindow.h @@ -30,6 +30,7 @@ #include #include +#include "DetachableWidget.h" #include "lmms_export.h" class QGraphicsDropShadowEffect; @@ -68,6 +69,7 @@ class LMMS_EXPORT SubWindow : public QMdiSubWindow void setBorderColor( const QColor &c ); int titleBarHeight() const; int frameWidth() const; + void setWidget(QWidget* widget); // Hook for QMdiSubWindow::setWidget // TODO Needed to update the title bar when replacing instruments. // Update works automatically if QMdiSubWindows are used. @@ -106,6 +108,7 @@ public slots: QLabel * m_windowTitle; QGraphicsDropShadowEffect * m_shadow; bool m_hasFocus; + DetachableWidget* m_detachHandler; static void elideText( QLabel *label, QString text ); void adjustTitleBar(); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 41fecd2aec1..cb5d552a3de 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -10,7 +10,6 @@ SET(LMMS_SRCS gui/ControllerView.cpp gui/Controls.cpp gui/DetachableWidget.cpp - gui/DetachableWindow.cpp gui/EffectControlDialog.cpp gui/EffectRackView.cpp gui/EffectView.cpp diff --git a/src/gui/ControllerDialog.cpp b/src/gui/ControllerDialog.cpp index d47da1a98de..ef4cec2c32b 100644 --- a/src/gui/ControllerDialog.cpp +++ b/src/gui/ControllerDialog.cpp @@ -31,7 +31,7 @@ namespace lmms::gui { ControllerDialog::ControllerDialog(Controller* controller, QWidget* parent) - : DetachableWidget{parent} + : QWidget{parent} , ModelView{controller, this} { } diff --git a/src/gui/ControllerRackView.cpp b/src/gui/ControllerRackView.cpp index 299e48772ff..001c8c0bb2d 100644 --- a/src/gui/ControllerRackView.cpp +++ b/src/gui/ControllerRackView.cpp @@ -47,7 +47,7 @@ namespace lmms::gui ControllerRackView::ControllerRackView() - : DetachableWidget{} + : QWidget{} , m_nextIndex{0} { setWindowIcon( embed::getIconPixmap( "controller" ) ); diff --git a/src/gui/DetachableWidget.cpp b/src/gui/DetachableWidget.cpp index 240ddec16b1..b984bc64074 100644 --- a/src/gui/DetachableWidget.cpp +++ b/src/gui/DetachableWidget.cpp @@ -25,32 +25,42 @@ #include "DetachableWidget.h" -#include +//#include +#include #include "GuiApplication.h" #include "MainWindow.h" -#include "SubWindow.h" namespace lmms::gui { -void DetachableWidget::closeEvent(QCloseEvent* ce) + +bool DetachableWidget::eventFilter(QObject* obj, QEvent* e) { - if (windowFlags().testFlag(Qt::Window)) - { - dynamic_cast(*parentWidget()).attach(); - ce->ignore(); - } - else if (getGUI()->mainWindow()->workspace()) + if (!obj->isWidgetType()) + return false; + auto w = static_cast(obj); + if (e->type() == QEvent::Close) { - parentWidget()->hide(); - ce->ignore(); + if (w->windowFlags().testFlag(Qt::Window)) + { + emit attach(); // we'll handle this in SubWinoow + e->ignore(); + return true; + } + else if (getGUI()->mainWindow()->workspace()) // mdiArea exists + { + w->parentWidget()->hide(); + e->ignore(); + } + else // is this even reachable? + { + w->hide(); + e->ignore(); + } + return false; } - else - { - hide(); - ce->ignore(); - } - emit closed(); + return false; } + } // namespace lmms::gui diff --git a/src/gui/DetachableWindow.cpp b/src/gui/DetachableWindow.cpp deleted file mode 100644 index d800ebf64f1..00000000000 --- a/src/gui/DetachableWindow.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * DetachableWindow.cpp - Allows a window to be detached from - * LMMS's main window - * - * Copyright (c) 2023 Dalton Messmer - * - * This file is part of LMMS - https://lmms.io - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -#include "DetachableWindow.h" - -#include - -#include "GuiApplication.h" -#include "MainWindow.h" -#include "SubWindow.h" - -namespace lmms::gui { - -void DetachableWindow::closeEvent(QCloseEvent* ce) -{ - if (windowFlags().testFlag(Qt::Window)) - { - dynamic_cast(*parentWidget()).attach(); - ce->ignore(); - } - else if (getGUI()->mainWindow()->workspace()) - { - parentWidget()->hide(); - ce->ignore(); - } - else - { - hide(); - ce->ignore(); - } - emit closed(); -} - -} // namespace lmms::gui diff --git a/src/gui/EffectControlDialog.cpp b/src/gui/EffectControlDialog.cpp index 9c689bf5ba9..6a624e4dc49 100644 --- a/src/gui/EffectControlDialog.cpp +++ b/src/gui/EffectControlDialog.cpp @@ -31,7 +31,7 @@ namespace lmms::gui { EffectControlDialog::EffectControlDialog(EffectControls* controls) - : DetachableWidget{nullptr} + : QWidget{nullptr} , ModelView{controls, this} , m_effectControls{controls} { diff --git a/src/gui/MicrotunerConfig.cpp b/src/gui/MicrotunerConfig.cpp index 74231c5a5cd..c686b2c01a8 100644 --- a/src/gui/MicrotunerConfig.cpp +++ b/src/gui/MicrotunerConfig.cpp @@ -55,7 +55,7 @@ namespace lmms::gui MicrotunerConfig::MicrotunerConfig() : - DetachableWidget(), + QWidget(), m_scaleComboModel(nullptr, tr("Selected scale slot")), m_keymapComboModel(nullptr, tr("Selected keymap slot")), m_firstKeyModel(0, 0, NumKeys - 1, nullptr, tr("First key")), diff --git a/src/gui/MixerView.cpp b/src/gui/MixerView.cpp index 72621a173ef..3e58351a048 100644 --- a/src/gui/MixerView.cpp +++ b/src/gui/MixerView.cpp @@ -56,7 +56,7 @@ namespace lmms::gui MixerView::MixerView(Mixer* mixer) : - DetachableWidget{}, + QWidget{}, ModelView{nullptr, this}, SerializingObjectHook{}, m_mixer(mixer) diff --git a/src/gui/ProjectNotes.cpp b/src/gui/ProjectNotes.cpp index b1d8b58b35d..cff5bc81c76 100644 --- a/src/gui/ProjectNotes.cpp +++ b/src/gui/ProjectNotes.cpp @@ -49,7 +49,7 @@ namespace lmms::gui ProjectNotes::ProjectNotes() - : DetachableWindow{getGUI()->mainWindow()->workspace()} + : QMainWindow{getGUI()->mainWindow()->workspace()} { m_edit = new QTextEdit( this ); m_edit->setAutoFillBackground( true ); diff --git a/src/gui/SampleTrackWindow.cpp b/src/gui/SampleTrackWindow.cpp index e21fbd5eeb5..3f85f4671be 100644 --- a/src/gui/SampleTrackWindow.cpp +++ b/src/gui/SampleTrackWindow.cpp @@ -48,7 +48,7 @@ namespace lmms::gui SampleTrackWindow::SampleTrackWindow(SampleTrackView* stv) - : DetachableWidget{} + : QWidget{} , ModelView{nullptr, this} , m_track{stv->model()} , m_stv{stv} @@ -266,8 +266,6 @@ void SampleTrackWindow::toggleVisibility(bool on) void SampleTrackWindow::closeEvent(QCloseEvent* ce) { - DetachableWidget::closeEvent(ce); - m_stv->setFocus(); m_stv->m_tlb->setChecked(false); } diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index a3b3ae78041..79d74c29107 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -101,6 +101,29 @@ SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags windowFlags) : setWindowFlags((this->windowFlags() & ~Qt::WindowMinimizeButtonHint) | Qt::CustomizeWindowHint); connect( mdiArea(), SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(focusChanged(QMdiSubWindow*))); + + m_detachHandler = new DetachableWidget(); + connect(m_detachHandler, &DetachableWidget::attach, this, &SubWindow::attach); +} + + + + +/** + * @brief SubWindow::setWidget + * + * This is a wrapper to initialize everything + * that depends on a child widget. + */ +void SubWindow::setWidget(QWidget* w) +{ + if (widget()) + widget()->removeEventFilter(m_detachHandler); + + QMdiSubWindow::setWidget(w); + + if (widget()) + widget()->installEventFilter(m_detachHandler); } @@ -158,16 +181,22 @@ void SubWindow::changeEvent( QEvent *event ) { adjustTitleBar(); } - } void SubWindow::setVisible(bool visible) { - if (isDetached() && visible) // avoid showing titlebar here + if (isDetached()) // avoid showing titlebar here { - widget()->show(); - // raise the detached window in case it was minimized - widget()->setWindowState((widget()->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); + if (visible) + { + widget()->show(); + // raise the detached window in case it was minimized + widget()->setWindowState((widget()->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); + } + else + { + widget()->hide(); + } return; } QMdiSubWindow::setVisible(visible); @@ -280,9 +309,10 @@ void SubWindow::detach() auto flags = windowFlags(); flags |= Qt::Window; flags &= ~Qt::Widget; + + hide(); widget()->setWindowFlags(flags); widget()->show(); - hide(); widget()->windowHandle()->setPosition(pos); } diff --git a/src/gui/editors/Editor.cpp b/src/gui/editors/Editor.cpp index 84f57aaf0c2..1b7f102947a 100644 --- a/src/gui/editors/Editor.cpp +++ b/src/gui/editors/Editor.cpp @@ -89,7 +89,7 @@ void Editor::toggleMaximize() } Editor::Editor(bool record, bool stepRecord) : - DetachableWindow(), + QMainWindow(), m_toolBar(new DropToolBar(this)), m_playAction(nullptr), m_recordAction(nullptr), From 76da99739554fe983cc54fc66a9e923521e4c486 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sun, 19 Oct 2025 01:18:45 +0300 Subject: [PATCH 02/70] revert MixerView height cap --- src/gui/MixerView.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/MixerView.cpp b/src/gui/MixerView.cpp index 3e58351a048..9868569edfe 100644 --- a/src/gui/MixerView.cpp +++ b/src/gui/MixerView.cpp @@ -175,7 +175,6 @@ MixerView::MixerView(Mixer* mixer) : // adjust window size layout()->invalidate(); resize(sizeHint()); - setFixedHeight(height()); layout()->setSizeConstraint(QLayout::SetMinimumSize); // add ourself to workspace From 6fb9f9a1607882ddb800cfe2217369e105c0c7f7 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sun, 19 Oct 2025 01:28:48 +0300 Subject: [PATCH 03/70] Remove obsolete part of ITW::closeEvent All removed functionality is being done in the event handler which gets installed immediately after widget is given to SubWindow. --- src/gui/instrument/InstrumentTrackWindow.cpp | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/gui/instrument/InstrumentTrackWindow.cpp b/src/gui/instrument/InstrumentTrackWindow.cpp index ecb926ee747..a5f843fe57a 100644 --- a/src/gui/instrument/InstrumentTrackWindow.cpp +++ b/src/gui/instrument/InstrumentTrackWindow.cpp @@ -531,22 +531,6 @@ void InstrumentTrackWindow::toggleVisibility( bool on ) void InstrumentTrackWindow::closeEvent( QCloseEvent* event ) { - // TODO: When is this event used? - if (windowFlags().testFlag(Qt::Window)) - { - event->accept(); - } - else if (getGUI()->mainWindow()->workspace()) - { - parentWidget()->hide(); - event->ignore(); - } - else - { - hide(); - event->ignore(); - } - m_itv->setFocus(); m_itv->m_tlb->setChecked(false); } From 0fe643f92704bf7d9f2c713bbd6ba154cce29a53 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sun, 19 Oct 2025 02:40:36 +0300 Subject: [PATCH 04/70] refactor SampleTrackWindow, sync detached icon - Use QLayout::setSizeConstraint to set fixed size since it responds to layout changes. - Set widget icon directly since it's useful for detaching, and SubWindow inherits it anyway. --- src/gui/SampleTrackWindow.cpp | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/gui/SampleTrackWindow.cpp b/src/gui/SampleTrackWindow.cpp index 3f85f4671be..767e9ee1bf1 100644 --- a/src/gui/SampleTrackWindow.cpp +++ b/src/gui/SampleTrackWindow.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "AutomatableButton.h" #include "EffectRackView.h" @@ -169,27 +170,14 @@ SampleTrackWindow::SampleTrackWindow(SampleTrackView* stv) QMdiSubWindow * subWin = getGUI()->mainWindow()->addWindowedWidget(this); Qt::WindowFlags flags = subWin->windowFlags(); - flags |= Qt::MSWindowsFixedSizeDialogHint; + flags |= Qt::MSWindowsFixedSizeDialogHint; // resizing is disabled regardless, this makes SubWindow hide related actions flags &= ~Qt::WindowMaximizeButtonHint; subWin->setWindowFlags(flags); - // adjust window size - layout()->invalidate(); - resize(sizeHint()); - if (parentWidget()) - { - parentWidget()->resize(parentWidget()->sizeHint()); - } - setFixedSize(size()); - - // Hide the Size and Maximize options from the system menu - // since the dialog size is fixed. - QMenu * systemMenu = subWin->systemMenu(); - systemMenu->actions().at(2)->setVisible(false); // Size - systemMenu->actions().at(4)->setVisible(false); // Maximize + layout()->setSizeConstraint(QLayout::SetFixedSize); + // better than setFixedSize because it still responds to layout changes - subWin->setWindowIcon(embed::getIconPixmap("sample_track")); - subWin->setFixedSize(subWin->size()); + setWindowIcon(embed::getIconPixmap("sample_track")); subWin->hide(); } From c1e9c1eca0476b31a18a70f308fc5c9ca0a69cb7 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sun, 19 Oct 2025 03:32:21 +0300 Subject: [PATCH 05/70] Set window icon directly on InstrumenTrackView --- src/gui/instrument/InstrumentTrackWindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/instrument/InstrumentTrackWindow.cpp b/src/gui/instrument/InstrumentTrackWindow.cpp index a5f843fe57a..93992926591 100644 --- a/src/gui/instrument/InstrumentTrackWindow.cpp +++ b/src/gui/instrument/InstrumentTrackWindow.cpp @@ -304,7 +304,7 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : // we can reuse this method. updateSubWindow(); - subWin->setWindowIcon(embed::getIconPixmap("instrument_track")); + setWindowIcon(embed::getIconPixmap("instrument_track")); subWin->hide(); } @@ -732,6 +732,7 @@ void InstrumentTrackWindow::updateSubWindow() if (instrumentViewResizable) { // TODO As of writing SlicerT is the only resizable instrument. Is this code specific to SlicerT? + // TODO Expand extraSpace in terms of specific widget sizes or replace with QLayout::setSizeConstraint. const auto extraSpace = QSize(12, 208); setMaximumSize(m_instrumentView->maximumSize() + extraSpace); setMinimumSize(m_instrumentView->minimumSize() + extraSpace); From b1280094a6e0da45f078eb29c214799809651fc1 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sun, 19 Oct 2025 22:36:45 +0300 Subject: [PATCH 06/70] Preserve detached state in ITW prev/next buttons Make InstrumentTrackWindow prev/next windows preserve detached state, matching how positions are set. SubWindow::attach() move & resize delays removed since their execution order becomes ambiguous, causing issues with manual movement outside the class. --- include/SubWindow.h | 3 +- src/gui/SubWindow.cpp | 32 ++++++++++++++------ src/gui/instrument/InstrumentTrackWindow.cpp | 24 +++++++++++++-- 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/include/SubWindow.h b/include/SubWindow.h index 2a677b86a4c..352dade8613 100644 --- a/include/SubWindow.h +++ b/include/SubWindow.h @@ -70,6 +70,8 @@ class LMMS_EXPORT SubWindow : public QMdiSubWindow int titleBarHeight() const; int frameWidth() const; void setWidget(QWidget* widget); // Hook for QMdiSubWindow::setWidget + bool isDetached() const; + void setDetached(bool on); // TODO Needed to update the title bar when replacing instruments. // Update works automatically if QMdiSubWindows are used. @@ -88,7 +90,6 @@ public slots: void changeEvent( QEvent * event ) override; bool eventFilter(QObject* obj, QEvent* event) override; - bool isDetached() const; signals: void focusLost(); diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index 79d74c29107..d58a52bb105 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -202,6 +202,9 @@ void SubWindow::setVisible(bool visible) QMdiSubWindow::setVisible(visible); } + + + bool SubWindow::isDetached() const { return widget()->windowFlags().testFlag(Qt::Window); @@ -210,6 +213,17 @@ bool SubWindow::isDetached() const +void SubWindow::setDetached(bool on) +{ + if (on) + detach(); + else + attach(); +} + + + + /** * @brief SubWindow::elideText * @@ -290,6 +304,9 @@ void SubWindow::setBorderColor( const QColor &c ) m_borderColor = c; } + + + void SubWindow::detach() { #if QT_VERSION < 0x50C00 @@ -340,16 +357,11 @@ void SubWindow::attach() widget()->show(); show(); - // Delay moving & resizing using event queue. Ensures that this widget is - // visible first, so that resizing works. - QObject obj; - connect(&obj, &QObject::destroyed, this, [this, frame]() { - if (QGuiApplication::platformName() != "wayland") - { // Workaround for wayland reporting on-screen pos as 0-0. If ever solved on wayland side, this check is safe to remove. - move(mdiArea()->mapFromGlobal(frame.topLeft())); - } - resize(frame.size()); - }, Qt::QueuedConnection); + if (QGuiApplication::platformName() != "wayland") + { // Workaround for wayland reporting on-screen pos as 0-0. If ever solved on wayland side, this check is safe to remove. + move(mdiArea()->mapFromGlobal(frame.topLeft())); + } + resize(frame.size()); } diff --git a/src/gui/instrument/InstrumentTrackWindow.cpp b/src/gui/instrument/InstrumentTrackWindow.cpp index 93992926591..bdba2eeb624 100644 --- a/src/gui/instrument/InstrumentTrackWindow.cpp +++ b/src/gui/instrument/InstrumentTrackWindow.cpp @@ -661,13 +661,33 @@ void InstrumentTrackWindow::viewInstrumentInDirection(int d) // avoid reloading the window if there is only one instrument, as that will just change the active tab if (idxOfNext != idxOfMe) { + SubWindow* source_subwin = static_cast(parentWidget()); + SubWindow* target_subwin = static_cast(newView->getInstrumentTrackWindow()->parentWidget()); + QWidget* source_widget; + QWidget* target_widget; + + // set widgets we move and get our position from + if (source_subwin->isDetached()) + { + source_widget = this; + target_widget = newView->getInstrumentTrackWindow(); + } + else + { + source_widget = parentWidget(); + target_widget = newView->getInstrumentTrackWindow()->parentWidget(); + } + // save current window pos and then hide the window by unchecking its button in the track list - QPoint curPos = parentWidget()->pos(); + QPoint curPos = source_widget->pos(); m_itv->m_tlb->setChecked(false); + // sync detached state with current widget like we do with position + target_subwin->setDetached(source_subwin->isDetached()); + // enable the new window by checking its track list button & moving it to where our window just was newView->m_tlb->setChecked(true); - newView->getInstrumentTrackWindow()->parentWidget()->move(curPos); // TODO + target_widget->move(curPos); // scroll the SongEditor/PatternEditor to make sure the new trackview label is visible bringToFront->trackContainerView()->scrollToTrackView(bringToFront); From 806d964f78b18411b388fbd9df7e5d25538f76cb Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sun, 19 Oct 2025 22:47:16 +0300 Subject: [PATCH 07/70] remove leftover comment --- src/gui/DetachableWidget.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/DetachableWidget.cpp b/src/gui/DetachableWidget.cpp index b984bc64074..2cc833439c8 100644 --- a/src/gui/DetachableWidget.cpp +++ b/src/gui/DetachableWidget.cpp @@ -25,7 +25,6 @@ #include "DetachableWidget.h" -//#include #include #include "GuiApplication.h" From 7656b1c16d948b56703fae8d5ff14026d670e855 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Mon, 20 Oct 2025 13:17:38 +0300 Subject: [PATCH 08/70] Account for size of decorations Make SubWindow::attach() translate and move the child widget accounting for SubWindow decorations (frame size and title bar height). --- include/SubWindow.h | 1 + src/gui/SubWindow.cpp | 24 ++++++++++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/include/SubWindow.h b/include/SubWindow.h index 352dade8613..d80bf1f2169 100644 --- a/include/SubWindow.h +++ b/include/SubWindow.h @@ -64,6 +64,7 @@ class LMMS_EXPORT SubWindow : public QMdiSubWindow QBrush activeColor() const; QColor textShadowColor() const; QColor borderColor() const; + QMargins decorationMargins() const; void setActiveColor( const QBrush & b ); void setTextShadowColor( const QColor &c ); void setBorderColor( const QColor &c ); diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index d58a52bb105..581db612ac8 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -348,7 +348,9 @@ void SubWindow::attach() if (!isDetached()) { return; } - auto frame = widget()->windowHandle()->frameGeometry(); + auto frame = widget()->windowHandle()->geometry(); + frame.moveTo(mdiArea()->mapFromGlobal(frame.topLeft())); + frame += decorationMargins(); auto flags = windowFlags(); flags &= ~Qt::Window; @@ -357,11 +359,10 @@ void SubWindow::attach() widget()->show(); show(); - if (QGuiApplication::platformName() != "wayland") - { // Workaround for wayland reporting on-screen pos as 0-0. If ever solved on wayland side, this check is safe to remove. - move(mdiArea()->mapFromGlobal(frame.topLeft())); - } - resize(frame.size()); + if (QGuiApplication::platformName() == "wayland") + resize(frame.size()); // Workaround for wayland reporting position as 0-0. + else + setGeometry(frame); } @@ -384,6 +385,17 @@ int SubWindow::frameWidth() const } + + +QMargins SubWindow::decorationMargins() const +{ + // left, top, right, bottom + return QMargins(frameWidth(), titleBarHeight(), frameWidth(), frameWidth()); +} + + + + void SubWindow::updateTitleBar() { adjustTitleBar(); From a7f6c608904e174df76bfb7afe89f289b5a413d1 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Mon, 20 Oct 2025 13:35:37 +0300 Subject: [PATCH 09/70] Fix ITW movement again (lazy version) --- src/gui/instrument/InstrumentTrackWindow.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/gui/instrument/InstrumentTrackWindow.cpp b/src/gui/instrument/InstrumentTrackWindow.cpp index bdba2eeb624..852f2a922e2 100644 --- a/src/gui/instrument/InstrumentTrackWindow.cpp +++ b/src/gui/instrument/InstrumentTrackWindow.cpp @@ -682,11 +682,14 @@ void InstrumentTrackWindow::viewInstrumentInDirection(int d) QPoint curPos = source_widget->pos(); m_itv->m_tlb->setChecked(false); + // enable the new window by checking its track list button & moving it to where our window just was + newView->m_tlb->setChecked(true); + + // Can a detached window exist between these? + // sync detached state with current widget like we do with position target_subwin->setDetached(source_subwin->isDetached()); - // enable the new window by checking its track list button & moving it to where our window just was - newView->m_tlb->setChecked(true); target_widget->move(curPos); // scroll the SongEditor/PatternEditor to make sure the new trackview label is visible From e3c854b9f579102a0c97c3ebd45259466800fa8c Mon Sep 17 00:00:00 2001 From: SpomJ Date: Tue, 21 Oct 2025 02:13:36 +0300 Subject: [PATCH 10/70] Move the close hook into SubWindow eventFilter --- include/DetachableWidget.h | 50 --------------------------- include/SubWindow.h | 2 -- src/gui/CMakeLists.txt | 1 - src/gui/DetachableWidget.cpp | 65 ------------------------------------ src/gui/SubWindow.cpp | 29 +++++++++++----- 5 files changed, 20 insertions(+), 127 deletions(-) delete mode 100644 include/DetachableWidget.h delete mode 100644 src/gui/DetachableWidget.cpp diff --git a/include/DetachableWidget.h b/include/DetachableWidget.h deleted file mode 100644 index 293f6a3b2f4..00000000000 --- a/include/DetachableWidget.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * DetachableWidget.h - Allows a widget to be detached from - * LMMS's main window - * - * Copyright (c) 2023 Dalton Messmer - * - * This file is part of LMMS - https://lmms.io - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -#ifndef LMMS_GUI_DETACHABLE_WIDGET -#define LMMS_GUI_DETACHABLE_WIDGET - -#include - -#include "lmms_export.h" - -class QWidget; -class QEvent; - -namespace lmms::gui { - -class LMMS_EXPORT DetachableWidget : public QObject -{ - Q_OBJECT -public: - bool eventFilter(QObject* obj, QEvent* e) override; - -signals: - void attach(); -}; - -} // namespace lmms::gui - -#endif // LMMS_GUI_DETACHABLE_WIDGET diff --git a/include/SubWindow.h b/include/SubWindow.h index d80bf1f2169..36e48025e99 100644 --- a/include/SubWindow.h +++ b/include/SubWindow.h @@ -30,7 +30,6 @@ #include #include -#include "DetachableWidget.h" #include "lmms_export.h" class QGraphicsDropShadowEffect; @@ -110,7 +109,6 @@ public slots: QLabel * m_windowTitle; QGraphicsDropShadowEffect * m_shadow; bool m_hasFocus; - DetachableWidget* m_detachHandler; static void elideText( QLabel *label, QString text ); void adjustTitleBar(); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index cb5d552a3de..f1712053f67 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -9,7 +9,6 @@ SET(LMMS_SRCS gui/ControllerRackView.cpp gui/ControllerView.cpp gui/Controls.cpp - gui/DetachableWidget.cpp gui/EffectControlDialog.cpp gui/EffectRackView.cpp gui/EffectView.cpp diff --git a/src/gui/DetachableWidget.cpp b/src/gui/DetachableWidget.cpp deleted file mode 100644 index 2cc833439c8..00000000000 --- a/src/gui/DetachableWidget.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * DetachableWidget.cpp - Allows a widget to be detached from - * LMMS's main window - * - * Copyright (c) 2023 Dalton Messmer - * - * This file is part of LMMS - https://lmms.io - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -#include "DetachableWidget.h" - -#include - -#include "GuiApplication.h" -#include "MainWindow.h" - -namespace lmms::gui { - - -bool DetachableWidget::eventFilter(QObject* obj, QEvent* e) -{ - if (!obj->isWidgetType()) - return false; - auto w = static_cast(obj); - if (e->type() == QEvent::Close) - { - if (w->windowFlags().testFlag(Qt::Window)) - { - emit attach(); // we'll handle this in SubWinoow - e->ignore(); - return true; - } - else if (getGUI()->mainWindow()->workspace()) // mdiArea exists - { - w->parentWidget()->hide(); - e->ignore(); - } - else // is this even reachable? - { - w->hide(); - e->ignore(); - } - return false; - } - return false; -} - - -} // namespace lmms::gui diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index 581db612ac8..f8d4ebe1503 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -42,6 +42,8 @@ #include #include "embed.h" +#include "GuiApplication.h" +#include "MainWindow.h" namespace lmms::gui { @@ -101,9 +103,6 @@ SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags windowFlags) : setWindowFlags((this->windowFlags() & ~Qt::WindowMinimizeButtonHint) | Qt::CustomizeWindowHint); connect( mdiArea(), SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(focusChanged(QMdiSubWindow*))); - - m_detachHandler = new DetachableWidget(); - connect(m_detachHandler, &DetachableWidget::attach, this, &SubWindow::attach); } @@ -117,13 +116,7 @@ SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags windowFlags) : */ void SubWindow::setWidget(QWidget* w) { - if (widget()) - widget()->removeEventFilter(m_detachHandler); - QMdiSubWindow::setWidget(w); - - if (widget()) - widget()->installEventFilter(m_detachHandler); } @@ -581,6 +574,24 @@ bool SubWindow::eventFilter(QObject* obj, QEvent* event) case QEvent::WindowStateChange: event->accept(); return true; + case QEvent::Close: + if (widget()->windowFlags().testFlag(Qt::Window)) + { + attach(); + event->ignore(); + return true; + } + else if (getGUI()->mainWindow()->workspace()) // mdiArea exists + { + widget()->parentWidget()->hide(); + event->ignore(); + } + else // is this even reachable? + { + widget()->hide(); + event->ignore(); + } + return QMdiSubWindow::eventFilter(obj, event); default: return QMdiSubWindow::eventFilter(obj, event); } From fac61600ee4a24d7df5733130f6c1bcd994e6eaf Mon Sep 17 00:00:00 2001 From: SpomJ Date: Tue, 21 Oct 2025 03:07:41 +0300 Subject: [PATCH 11/70] Keep track of detached window while it's hidden Introduce m_childGeom to track window movements independantly of Qt. Workaround for Qt not moving top-level windows if they're hidden. Additionally make attach & detach preserve the visibility state instead of force-showing the window. --- include/SubWindow.h | 1 + src/gui/SubWindow.cpp | 74 +++++++++++++------- src/gui/instrument/InstrumentTrackWindow.cpp | 8 +-- 3 files changed, 52 insertions(+), 31 deletions(-) diff --git a/include/SubWindow.h b/include/SubWindow.h index 36e48025e99..0ed40e11b2c 100644 --- a/include/SubWindow.h +++ b/include/SubWindow.h @@ -109,6 +109,7 @@ public slots: QLabel * m_windowTitle; QGraphicsDropShadowEffect * m_shadow; bool m_hasFocus; + QRect m_childGeom; static void elideText( QLabel *label, QString text ); void adjustTitleBar(); diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index f8d4ebe1503..d2ae6c83979 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -117,6 +117,11 @@ SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags windowFlags) : void SubWindow::setWidget(QWidget* w) { QMdiSubWindow::setWidget(w); + + if (widget()) + { + m_childGeom = widget()->geometry(); + } } @@ -183,6 +188,7 @@ void SubWindow::setVisible(bool visible) if (visible) { widget()->show(); + widget()->setGeometry(m_childGeom); // raise the detached window in case it was minimized widget()->setWindowState((widget()->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); } @@ -315,6 +321,7 @@ void SubWindow::detach() if (isDetached()) { return; } const auto pos = mapToGlobal(widget()->pos()); + const bool shown = isVisible(); auto flags = windowFlags(); flags |= Qt::Window; @@ -322,9 +329,11 @@ void SubWindow::detach() hide(); widget()->setWindowFlags(flags); - widget()->show(); - widget()->windowHandle()->setPosition(pos); + if (shown) + widget()->show(); + + widget()->move(pos); } void SubWindow::attach() @@ -341,6 +350,8 @@ void SubWindow::attach() if (!isDetached()) { return; } + const bool shown = widget()->isVisible(); + auto frame = widget()->windowHandle()->geometry(); frame.moveTo(mdiArea()->mapFromGlobal(frame.topLeft())); frame += decorationMargins(); @@ -349,8 +360,9 @@ void SubWindow::attach() flags &= ~Qt::Window; flags |= Qt::Widget; widget()->setWindowFlags(flags); - widget()->show(); - show(); + + if (shown) + show(); if (QGuiApplication::platformName() == "wayland") resize(frame.size()); // Workaround for wayland reporting position as 0-0. @@ -571,29 +583,39 @@ bool SubWindow::eventFilter(QObject* obj, QEvent* event) switch (event->type()) { - case QEvent::WindowStateChange: - event->accept(); - return true; - case QEvent::Close: - if (widget()->windowFlags().testFlag(Qt::Window)) - { - attach(); - event->ignore(); + case QEvent::WindowStateChange: + event->accept(); return true; - } - else if (getGUI()->mainWindow()->workspace()) // mdiArea exists - { - widget()->parentWidget()->hide(); - event->ignore(); - } - else // is this even reachable? - { - widget()->hide(); - event->ignore(); - } - return QMdiSubWindow::eventFilter(obj, event); - default: - return QMdiSubWindow::eventFilter(obj, event); + + case QEvent::Close: + if (widget()->windowFlags().testFlag(Qt::Window)) + { + attach(); + event->ignore(); + return true; + } + else if (getGUI()->mainWindow()->workspace()) // mdiArea exists + { + widget()->parentWidget()->hide(); + event->ignore(); + } + else // is this even reachable? + { + widget()->hide(); + event->ignore(); + } + return QMdiSubWindow::eventFilter(obj, event); + + case QEvent::Move: + m_childGeom.moveTo(static_cast(event)->pos()); + return QMdiSubWindow::eventFilter(obj, event); + + case QEvent::Resize: + m_childGeom.setSize(static_cast(event)->size()); + return QMdiSubWindow::eventFilter(obj, event); + + default: + return QMdiSubWindow::eventFilter(obj, event); } } diff --git a/src/gui/instrument/InstrumentTrackWindow.cpp b/src/gui/instrument/InstrumentTrackWindow.cpp index 852f2a922e2..ab076a7bc8c 100644 --- a/src/gui/instrument/InstrumentTrackWindow.cpp +++ b/src/gui/instrument/InstrumentTrackWindow.cpp @@ -682,14 +682,12 @@ void InstrumentTrackWindow::viewInstrumentInDirection(int d) QPoint curPos = source_widget->pos(); m_itv->m_tlb->setChecked(false); - // enable the new window by checking its track list button & moving it to where our window just was - newView->m_tlb->setChecked(true); - - // Can a detached window exist between these? - // sync detached state with current widget like we do with position target_subwin->setDetached(source_subwin->isDetached()); + // enable the new window by checking its track list button & moving it to where our window just was + newView->m_tlb->setChecked(true); + target_widget->move(curPos); // scroll the SongEditor/PatternEditor to make sure the new trackview label is visible From bad5f04391b2c353afcb2f38ccf7d11058966490 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Tue, 21 Oct 2025 03:36:42 +0300 Subject: [PATCH 12/70] Code cleanup --- include/SubWindow.h | 10 +++++----- src/gui/SubWindow.cpp | 43 ++++++++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/include/SubWindow.h b/include/SubWindow.h index 0ed40e11b2c..2de9cb31daf 100644 --- a/include/SubWindow.h +++ b/include/SubWindow.h @@ -84,13 +84,13 @@ public slots: protected: // hook the QWidget move/resize events to update the tracked geometry - void moveEvent( QMoveEvent * event ) override; - void resizeEvent( QResizeEvent * event ) override; - void paintEvent( QPaintEvent * pe ) override; - void changeEvent( QEvent * event ) override; + void moveEvent(QMoveEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + void paintEvent(QPaintEvent* pe) override; + void changeEvent(QEvent* event) override; + void showEvent(QShowEvent* e) override; bool eventFilter(QObject* obj, QEvent* event) override; - signals: void focusLost(); diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index d2ae6c83979..f5e135a281f 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -181,24 +181,27 @@ void SubWindow::changeEvent( QEvent *event ) } } + + + void SubWindow::setVisible(bool visible) { - if (isDetached()) // avoid showing titlebar here + if (isDetached()) + widget()->setVisible(visible); + else + QMdiSubWindow::setVisible(visible); +} + + + + +void SubWindow::showEvent(QShowEvent* e) +{ + if (isDetached()) { - if (visible) - { - widget()->show(); - widget()->setGeometry(m_childGeom); - // raise the detached window in case it was minimized - widget()->setWindowState((widget()->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - } - else - { - widget()->hide(); - } - return; + widget()->setGeometry(m_childGeom); + widget()->setWindowState((widget()->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); } - QMdiSubWindow::setVisible(visible); } @@ -362,7 +365,10 @@ void SubWindow::attach() widget()->setWindowFlags(flags); if (shown) + { + widget()->show(); show(); + } if (QGuiApplication::platformName() == "wayland") resize(frame.size()); // Workaround for wayland reporting position as 0-0. @@ -588,22 +594,17 @@ bool SubWindow::eventFilter(QObject* obj, QEvent* event) return true; case QEvent::Close: - if (widget()->windowFlags().testFlag(Qt::Window)) + if (isDetached()) { attach(); event->ignore(); return true; } - else if (getGUI()->mainWindow()->workspace()) // mdiArea exists + else { widget()->parentWidget()->hide(); event->ignore(); } - else // is this even reachable? - { - widget()->hide(); - event->ignore(); - } return QMdiSubWindow::eventFilter(obj, event); case QEvent::Move: From 6e45b96e9216cd655558e1ba580b20c1fc40e31a Mon Sep 17 00:00:00 2001 From: SpomJ Date: Tue, 21 Oct 2025 04:10:31 +0300 Subject: [PATCH 13/70] re-fix ITW because apparently it still breaks --- src/gui/instrument/InstrumentTrackWindow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/instrument/InstrumentTrackWindow.cpp b/src/gui/instrument/InstrumentTrackWindow.cpp index ab076a7bc8c..e0a550e5532 100644 --- a/src/gui/instrument/InstrumentTrackWindow.cpp +++ b/src/gui/instrument/InstrumentTrackWindow.cpp @@ -682,12 +682,12 @@ void InstrumentTrackWindow::viewInstrumentInDirection(int d) QPoint curPos = source_widget->pos(); m_itv->m_tlb->setChecked(false); - // sync detached state with current widget like we do with position - target_subwin->setDetached(source_subwin->isDetached()); - // enable the new window by checking its track list button & moving it to where our window just was newView->m_tlb->setChecked(true); + // sync detached state with current widget like we do with position + target_subwin->setDetached(source_subwin->isDetached()); + target_widget->move(curPos); // scroll the SongEditor/PatternEditor to make sure the new trackview label is visible From 34681b2d97eba06b91b65243b23733ca5a31347c Mon Sep 17 00:00:00 2001 From: SpomJ Date: Wed, 22 Oct 2025 18:08:25 +0300 Subject: [PATCH 14/70] fix codestyle --- src/gui/SampleTrackWindow.cpp | 2 +- src/gui/SubWindow.cpp | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/gui/SampleTrackWindow.cpp b/src/gui/SampleTrackWindow.cpp index 767e9ee1bf1..d3a6602e5a7 100644 --- a/src/gui/SampleTrackWindow.cpp +++ b/src/gui/SampleTrackWindow.cpp @@ -174,8 +174,8 @@ SampleTrackWindow::SampleTrackWindow(SampleTrackView* stv) flags &= ~Qt::WindowMaximizeButtonHint; subWin->setWindowFlags(flags); - layout()->setSizeConstraint(QLayout::SetFixedSize); // better than setFixedSize because it still responds to layout changes + layout()->setSizeConstraint(QLayout::SetFixedSize); setWindowIcon(embed::getIconPixmap("sample_track")); subWin->hide(); diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index f5e135a281f..e3ce161c91f 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -186,10 +186,8 @@ void SubWindow::changeEvent( QEvent *event ) void SubWindow::setVisible(bool visible) { - if (isDetached()) - widget()->setVisible(visible); - else - QMdiSubWindow::setVisible(visible); + if (isDetached()) { widget()->setVisible(visible); } + else { QMdiSubWindow::setVisible(visible); } } @@ -217,10 +215,8 @@ bool SubWindow::isDetached() const void SubWindow::setDetached(bool on) { - if (on) - detach(); - else - attach(); + if (on) { detach(); } + else { attach(); } } @@ -334,7 +330,9 @@ void SubWindow::detach() widget()->setWindowFlags(flags); if (shown) + { widget()->show(); + } widget()->move(pos); } @@ -371,9 +369,13 @@ void SubWindow::attach() } if (QGuiApplication::platformName() == "wayland") + { resize(frame.size()); // Workaround for wayland reporting position as 0-0. + } else + { setGeometry(frame); + } } @@ -400,8 +402,10 @@ int SubWindow::frameWidth() const QMargins SubWindow::decorationMargins() const { - // left, top, right, bottom - return QMargins(frameWidth(), titleBarHeight(), frameWidth(), frameWidth()); + return QMargins(frameWidth(), // left + titleBarHeight(), // top + frameWidth(), // right + frameWidth()); // bottom } From 5529ed33610d2b1cb65606a75d119a5098fd4c56 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Wed, 22 Oct 2025 22:34:46 +0300 Subject: [PATCH 15/70] make another `if` inline --- src/gui/SubWindow.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index e3ce161c91f..f4ed6eb7f6d 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -329,10 +329,7 @@ void SubWindow::detach() hide(); widget()->setWindowFlags(flags); - if (shown) - { - widget()->show(); - } + if (shown) { widget()->show(); } widget()->move(pos); } From 50f70ac70fc9439ebef38b899c88e75eb27ccfc5 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Wed, 22 Oct 2025 22:36:26 +0300 Subject: [PATCH 16/70] more codestyle fixes --- src/gui/SampleTrackWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/SampleTrackWindow.cpp b/src/gui/SampleTrackWindow.cpp index d3a6602e5a7..662de2b0e2c 100644 --- a/src/gui/SampleTrackWindow.cpp +++ b/src/gui/SampleTrackWindow.cpp @@ -174,7 +174,7 @@ SampleTrackWindow::SampleTrackWindow(SampleTrackView* stv) flags &= ~Qt::WindowMaximizeButtonHint; subWin->setWindowFlags(flags); - // better than setFixedSize because it still responds to layout changes + // better than `setFixedSize` because it still responds to layout changes layout()->setSizeConstraint(QLayout::SetFixedSize); setWindowIcon(embed::getIconPixmap("sample_track")); From e62b03932e1811aec7778abf8b81eecaae478d4c Mon Sep 17 00:00:00 2001 From: SpomJ Date: Wed, 22 Oct 2025 22:40:31 +0300 Subject: [PATCH 17/70] Expand SubWindow::setWidget() docs --- src/gui/SubWindow.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index f4ed6eb7f6d..0595e685939 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -111,8 +111,9 @@ SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags windowFlags) : /** * @brief SubWindow::setWidget * - * This is a wrapper to initialize everything - * that depends on a child widget. + * This is a wrapper to initialize everything that depends on a child widget. + * Though technically not an override, this is the only way + * to set the child widget, and the best way to track that. */ void SubWindow::setWidget(QWidget* w) { From 608fe14efe6cb2f9b214d15ee32f795f630ae418 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 23 Oct 2025 02:31:31 +0300 Subject: [PATCH 18/70] Constrain attached window positions Limit the positions to which the windows can be attached to those that leave at least a small margin (currently 40px width, 40px height incl. decorations) of the window visible. This is done to avoid windows attaching extremely far if they're on a different workspace / monitor, as well as hopefully help the user avoid losing their windows in general. --- src/gui/SubWindow.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index 0595e685939..15303defb6f 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -28,6 +28,7 @@ #include "SubWindow.h" +#include #include #include #include @@ -355,6 +356,17 @@ void SubWindow::attach() frame.moveTo(mdiArea()->mapFromGlobal(frame.topLeft())); frame += decorationMargins(); + // arbitrary values, require window to touch `mdiArea - margin` + // min(max(a, b), c) ensures that a <= b <= c + // TODO make this live configurble maybe? + const auto margin = QMargins(40, 40, 40, 40); + frame.moveTo(std::min(std::max(margin.left() - frame.width(), + frame.left()), + mdiArea()->rect().width() - margin.right()), + std::min(std::max(margin.top() - frame.height(), + frame.top()), + mdiArea()->rect().height() - margin.bottom())); + auto flags = windowFlags(); flags &= ~Qt::Window; flags |= Qt::Widget; From ddcadc9ef1fe9a785429e0a587d820c0afa394b0 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 23 Oct 2025 03:05:58 +0300 Subject: [PATCH 19/70] Hide windows when attaching on closeEvent --- src/gui/SubWindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index 15303defb6f..b02ea59ef94 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -611,12 +611,13 @@ bool SubWindow::eventFilter(QObject* obj, QEvent* event) if (isDetached()) { attach(); + hide(); event->ignore(); return true; } else { - widget()->parentWidget()->hide(); + hide(); event->ignore(); } return QMdiSubWindow::eventFilter(obj, event); From fedfef86da9a615285aef914cfc1eb16234954df Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 23 Oct 2025 03:13:29 +0300 Subject: [PATCH 20/70] Clarify wayland move workaround --- src/gui/SubWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index b02ea59ef94..d5f7d88244f 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -380,7 +380,7 @@ void SubWindow::attach() if (QGuiApplication::platformName() == "wayland") { - resize(frame.size()); // Workaround for wayland reporting position as 0-0. + resize(frame.size()); // Workaround for wayland reporting position as 0-0, see https://doc.qt.io/qt-6.9/application-windows.html#wayland-peculiarities } else { From c21f641dc905d2aedbb0fd0b70f7d95d93b78375 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 23 Oct 2025 03:19:29 +0300 Subject: [PATCH 21/70] Remove redundant includes --- src/gui/SubWindow.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index d5f7d88244f..a006dd6c2ca 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -43,8 +43,6 @@ #include #include "embed.h" -#include "GuiApplication.h" -#include "MainWindow.h" namespace lmms::gui { From c8cfe6785f13959988c76c0c070069e89d6d0bc6 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 23 Oct 2025 04:00:02 +0300 Subject: [PATCH 22/70] make detached closeEvent transparent --- src/gui/SubWindow.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index a006dd6c2ca..a2183eae7d8 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -611,7 +611,6 @@ bool SubWindow::eventFilter(QObject* obj, QEvent* event) attach(); hide(); event->ignore(); - return true; } else { From 21beffdecf36188d8148a0ef0025ef68c42df73c Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 23 Oct 2025 11:56:06 +0300 Subject: [PATCH 23/70] fix editor windows minimizing their height on close --- include/Editor.h | 1 + src/gui/editors/Editor.cpp | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/include/Editor.h b/include/Editor.h index 2141717af58..9cabc21666d 100644 --- a/include/Editor.h +++ b/include/Editor.h @@ -57,6 +57,7 @@ class Editor : public QMainWindow DropToolBar * addDropToolBar(Qt::ToolBarArea whereToAdd, QString const & windowTitle); DropToolBar * addDropToolBar(QWidget * parent, Qt::ToolBarArea whereToAdd, QString const & windowTitle); + void closeEvent(QCloseEvent* event) override; void keyPressEvent(QKeyEvent* ke) override; public slots: diff --git a/src/gui/editors/Editor.cpp b/src/gui/editors/Editor.cpp index 1b7f102947a..912209c2df5 100644 --- a/src/gui/editors/Editor.cpp +++ b/src/gui/editors/Editor.cpp @@ -139,6 +139,14 @@ QAction *Editor::playAction() const return m_playAction; } +// Workaround: for some reason editor windows minimize their height when close is unhandled +void Editor::closeEvent(QCloseEvent* ce) +{ + if (!parentWidget()) { hide(); } + getGUI()->mainWindow()->refocus(); + ce->ignore(); +} + void Editor::keyPressEvent(QKeyEvent* ke) { if (ke->key() == Qt::Key_Space) From 9b596b0d0256500a3c8b7b352f927b58cc2a2ed4 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 23 Oct 2025 20:00:55 +0300 Subject: [PATCH 24/70] Enable minimize button on detached windows --- src/gui/SubWindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index a2183eae7d8..a8486e64ea5 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -325,6 +325,7 @@ void SubWindow::detach() auto flags = windowFlags(); flags |= Qt::Window; flags &= ~Qt::Widget; + flags |= Qt::WindowMinimizeButtonHint; hide(); widget()->setWindowFlags(flags); @@ -368,6 +369,7 @@ void SubWindow::attach() auto flags = windowFlags(); flags &= ~Qt::Window; flags |= Qt::Widget; + flags &= ~Qt::WindowMinimizeButtonHint; widget()->setWindowFlags(flags); if (shown) From 5b1b493cd84f0ee5b3cb8911dfc59068b42dc6c6 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Fri, 24 Oct 2025 23:57:18 +0300 Subject: [PATCH 25/70] Change detach button SVG --- data/themes/default/detach.svg | 6 ++ data/themes/default/window.svg | 108 --------------------------------- 2 files changed, 6 insertions(+), 108 deletions(-) create mode 100644 data/themes/default/detach.svg delete mode 100644 data/themes/default/window.svg diff --git a/data/themes/default/detach.svg b/data/themes/default/detach.svg new file mode 100644 index 00000000000..53c0e39d7f5 --- /dev/null +++ b/data/themes/default/detach.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/data/themes/default/window.svg b/data/themes/default/window.svg deleted file mode 100644 index fa32ed168c6..00000000000 --- a/data/themes/default/window.svg +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - From deeb80274ed86832bf2956695d5f0af9904dcde4 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sat, 25 Oct 2025 00:09:28 +0300 Subject: [PATCH 26/70] rename detach icon --- src/gui/SubWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index a8486e64ea5..73ead40c1b2 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -82,7 +82,7 @@ SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags windowFlags) : m_restoreBtn = createButton("restore", tr("Restore")); connect(m_restoreBtn, &QPushButton::clicked, this, &QWidget::showNormal); - m_detachBtn = createButton("window", tr("Detach")); + m_detachBtn = createButton("detach", tr("Detach")); connect(m_detachBtn, &QPushButton::clicked, this, &SubWindow::detach); // QLabel for the window title and the shadow effect From 89686529903f5e6d4b8f672f81725ac01a2640c4 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sat, 25 Oct 2025 16:04:25 +0300 Subject: [PATCH 27/70] clean up detach.svg --- data/themes/default/detach.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/themes/default/detach.svg b/data/themes/default/detach.svg index 53c0e39d7f5..337e3660801 100644 --- a/data/themes/default/detach.svg +++ b/data/themes/default/detach.svg @@ -1,6 +1,6 @@ - - - - + + + + From f6e2cddb8ddb3e5e971396487bfff95ebc69cd97 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sat, 25 Oct 2025 16:27:29 +0300 Subject: [PATCH 28/70] Disable broken detach for MacOS builds --- src/gui/SubWindow.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index 73ead40c1b2..c4dfb22cdeb 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -44,6 +44,10 @@ #include "embed.h" +#ifdef LMMS_BUILD_APPLE +#include "TextFloat.h" // Only needed for error display for missing detach feature +#endif + namespace lmms::gui { @@ -305,6 +309,15 @@ void SubWindow::setBorderColor( const QColor &c ) +#ifndef LMMS_BUILD_APPLE +// FIXME: For some reason detaching on MacOS seems to never show the detached window. +void SubWindow::detach() +{ + TextFloat::displayMessage("Missing Feature",\ + tr("Sorry, detach is not yet available on this platform."), embed::getIconPixmap("error"), 2000); +} + +#else void SubWindow::detach() { #if QT_VERSION < 0x50C00 @@ -334,6 +347,7 @@ void SubWindow::detach() widget()->move(pos); } +#endif void SubWindow::attach() { From 36cbf3578e33c7a001dbd2d310171136c613b24e Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sat, 25 Oct 2025 16:38:32 +0300 Subject: [PATCH 29/70] fix typo --- src/gui/SubWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index c4dfb22cdeb..1c340b05212 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -309,7 +309,7 @@ void SubWindow::setBorderColor( const QColor &c ) -#ifndef LMMS_BUILD_APPLE +#ifdef LMMS_BUILD_APPLE // FIXME: For some reason detaching on MacOS seems to never show the detached window. void SubWindow::detach() { From d6129cced928719c4d7dc1ff89314831d528d291 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sat, 25 Oct 2025 17:51:14 +0300 Subject: [PATCH 30/70] revert SimpleTextFloat to master branch --- include/SimpleTextFloat.h | 4 ++-- src/gui/widgets/SimpleTextFloat.cpp | 24 ++++++++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/include/SimpleTextFloat.h b/include/SimpleTextFloat.h index b8858418d34..2821ca41183 100644 --- a/include/SimpleTextFloat.h +++ b/include/SimpleTextFloat.h @@ -30,6 +30,7 @@ #include "lmms_export.h" +class QLabel; class QTimer; namespace lmms::gui @@ -57,10 +58,9 @@ class LMMS_EXPORT SimpleTextFloat : public QWidget } void hide(); - void show(); private: - QString m_text; + QLabel * m_textLabel; QTimer * m_showTimer; QTimer * m_hideTimer; }; diff --git a/src/gui/widgets/SimpleTextFloat.cpp b/src/gui/widgets/SimpleTextFloat.cpp index 282fe6500d3..89a45c7f78b 100644 --- a/src/gui/widgets/SimpleTextFloat.cpp +++ b/src/gui/widgets/SimpleTextFloat.cpp @@ -25,16 +25,25 @@ #include "SimpleTextFloat.h" #include -#include +#include +#include + +#include "GuiApplication.h" +#include "MainWindow.h" namespace lmms::gui { SimpleTextFloat::SimpleTextFloat() : - QWidget() + QWidget(getGUI()->mainWindow(), Qt::ToolTip) { - m_text = QString(); + QHBoxLayout * layout = new QHBoxLayout(this); + layout->setMargin(3); + setLayout(layout); + + m_textLabel = new QLabel(this); + layout->addWidget(m_textLabel); m_showTimer = new QTimer(this); m_showTimer->setSingleShot(true); @@ -47,7 +56,7 @@ SimpleTextFloat::SimpleTextFloat() : void SimpleTextFloat::setText(const QString & text) { - m_text = text; + m_textLabel->setText(text); } void SimpleTextFloat::showWithDelay(int msecBeforeDisplay, int msecDisplayTime) @@ -67,16 +76,11 @@ void SimpleTextFloat::showWithDelay(int msecBeforeDisplay, int msecDisplayTime) } } -void SimpleTextFloat::show() -{ - QToolTip::showText(mapToGlobal(QPoint(0, 0)), m_text); -} - void SimpleTextFloat::hide() { m_showTimer->stop(); m_hideTimer->stop(); - QToolTip::hideText(); + QWidget::hide(); } } // namespace lmms::gui From 08fbfedac87d7b5ea66b5f98fbbe818e56427ef3 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sat, 25 Oct 2025 20:58:48 +0300 Subject: [PATCH 31/70] clean up diff against master --- include/ControllerDialog.h | 3 ++- include/ControllerRackView.h | 5 +++-- include/EffectControlDialog.h | 3 ++- include/MicrotunerConfig.h | 4 ++-- include/MixerView.h | 4 ++-- include/ProjectNotes.h | 4 ++-- include/SampleTrackWindow.h | 4 ++-- plugins/Vestige/Vestige.cpp | 11 ++--------- src/gui/MixerView.cpp | 3 --- src/gui/SampleTrackWindow.cpp | 1 - src/gui/SubWindow.cpp | 4 +++- src/gui/instrument/InstrumentTrackWindow.cpp | 8 -------- 12 files changed, 20 insertions(+), 34 deletions(-) diff --git a/include/ControllerDialog.h b/include/ControllerDialog.h index bd4087eb9f5..fc6e78f84da 100644 --- a/include/ControllerDialog.h +++ b/include/ControllerDialog.h @@ -26,9 +26,10 @@ #ifndef LMMS_GUI_CONTROLLER_DIALOG_H #define LMMS_GUI_CONTROLLER_DIALOG_H +#include + #include "ModelView.h" -#include namespace lmms { diff --git a/include/ControllerRackView.h b/include/ControllerRackView.h index c56b67883ff..f54b3e1a1c5 100644 --- a/include/ControllerRackView.h +++ b/include/ControllerRackView.h @@ -25,14 +25,15 @@ #ifndef LMMS_GUI_CONTROLLER_RACK_VIEW_H #define LMMS_GUI_CONTROLLER_RACK_VIEW_H -#include "SerializingObject.h" - #include +#include "SerializingObject.h" + class QPushButton; class QScrollArea; class QVBoxLayout; + namespace lmms { diff --git a/include/EffectControlDialog.h b/include/EffectControlDialog.h index 7adbbf3b607..675c2cb58d1 100644 --- a/include/EffectControlDialog.h +++ b/include/EffectControlDialog.h @@ -26,9 +26,10 @@ #ifndef LMMS_GUI_EFFECT_CONTROL_DIALOG_H #define LMMS_GUI_EFFECT_CONTROL_DIALOG_H +#include + #include "ModelView.h" -#include namespace lmms { diff --git a/include/MicrotunerConfig.h b/include/MicrotunerConfig.h index a990ef87199..13d4bbc9789 100644 --- a/include/MicrotunerConfig.h +++ b/include/MicrotunerConfig.h @@ -25,12 +25,12 @@ #ifndef LMMS_GUI_MICROTUNER_CONFIG_H #define LMMS_GUI_MICROTUNER_CONFIG_H +#include + #include "AutomatableModel.h" #include "ComboBoxModel.h" #include "SerializingObject.h" -#include - class QLineEdit; class QPlainTextEdit; diff --git a/include/MixerView.h b/include/MixerView.h index 6dc5698b2fe..8eed38bf2d7 100644 --- a/include/MixerView.h +++ b/include/MixerView.h @@ -25,12 +25,12 @@ #ifndef LMMS_GUI_MIXER_VIEW_H #define LMMS_GUI_MIXER_VIEW_H +#include + #include "MixerChannelView.h" #include "ModelView.h" #include "SerializingObject.h" -#include - class QDomDocument; // IWYU pragma: keep class QDomElement; // IWYU pragma: keep class QHBoxLayout; diff --git a/include/ProjectNotes.h b/include/ProjectNotes.h index f719f6e3e71..daa7436b9b1 100644 --- a/include/ProjectNotes.h +++ b/include/ProjectNotes.h @@ -25,10 +25,10 @@ #ifndef LMMS_GUI_PROJECT_NOTES_H #define LMMS_GUI_PROJECT_NOTES_H -#include "SerializingObject.h" - #include +#include "SerializingObject.h" + class QAction; class QComboBox; class QTextCharFormat; diff --git a/include/SampleTrackWindow.h b/include/SampleTrackWindow.h index b90e61f07c1..47ce765f1b8 100644 --- a/include/SampleTrackWindow.h +++ b/include/SampleTrackWindow.h @@ -25,12 +25,12 @@ #ifndef LMMS_GUI_SAMPLE_TRACK_WINDOW_H #define LMMS_GUI_SAMPLE_TRACK_WINDOW_H +#include + #include "ModelView.h" #include "SampleTrack.h" #include "SerializingObject.h" -#include - class QLineEdit; namespace lmms::gui diff --git a/plugins/Vestige/Vestige.cpp b/plugins/Vestige/Vestige.cpp index 426856f1e30..4f3dd53ca94 100644 --- a/plugins/Vestige/Vestige.cpp +++ b/plugins/Vestige/Vestige.cpp @@ -113,15 +113,8 @@ class vstSubWin : public SubWindow { // ignore close-events - for some reason otherwise the VST GUI // remains hidden when re-opening - if (windowFlags().testFlag(Qt::Window)) - { - e->accept(); - } - else - { - hide(); - e->ignore(); - } + hide(); + e->ignore(); } }; diff --git a/src/gui/MixerView.cpp b/src/gui/MixerView.cpp index 9868569edfe..6dc8cde4d92 100644 --- a/src/gui/MixerView.cpp +++ b/src/gui/MixerView.cpp @@ -172,9 +172,6 @@ MixerView::MixerView(Mixer* mixer) : // timer for updating faders connect(mainWindow, &MainWindow::periodicUpdate, this, &MixerView::updateFaders); - // adjust window size - layout()->invalidate(); - resize(sizeHint()); layout()->setSizeConstraint(QLayout::SetMinimumSize); // add ourself to workspace diff --git a/src/gui/SampleTrackWindow.cpp b/src/gui/SampleTrackWindow.cpp index 662de2b0e2c..6651739512c 100644 --- a/src/gui/SampleTrackWindow.cpp +++ b/src/gui/SampleTrackWindow.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include "AutomatableButton.h" #include "EffectRackView.h" diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index 1c340b05212..8d681cb15ec 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -44,8 +44,10 @@ #include "embed.h" +// Only needed for error display for missing detach feature +#include "lmmsconfig.h" #ifdef LMMS_BUILD_APPLE -#include "TextFloat.h" // Only needed for error display for missing detach feature +#include "TextFloat.h" #endif namespace lmms::gui diff --git a/src/gui/instrument/InstrumentTrackWindow.cpp b/src/gui/instrument/InstrumentTrackWindow.cpp index e0a550e5532..553c2953b87 100644 --- a/src/gui/instrument/InstrumentTrackWindow.cpp +++ b/src/gui/instrument/InstrumentTrackWindow.cpp @@ -786,14 +786,6 @@ void InstrumentTrackWindow::updateSubWindow() { subWin->updateTitleBar(); } - else - { - // TODO: Can this be removed? - // Show or hide the Size and Maximize options from the system menu depending on whether the view is resizable or not - QMenu* systemMenu = subWindow->systemMenu(); - systemMenu->actions().at(2)->setVisible(instrumentViewResizable); // Size - systemMenu->actions().at(4)->setVisible(instrumentViewResizable); // Maximize - } } } From 58ab6807bb3b05f48a6b565e9826928a5bb08bd4 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sun, 26 Oct 2025 23:56:45 +0300 Subject: [PATCH 32/70] Fix effect control views bing killed on close DeleteOnClose flag is unset from being default in SubWindow since it doesn't seem to be a desired default and in many places simply requires workarounds that disable it. Useless size restrictions EffectView imposes on SubWindow are removed since those are already implicit and conform to layout size policy of controlView. --- include/EffectView.h | 1 - src/gui/EffectView.cpp | 28 ---------------------------- src/gui/MainWindow.cpp | 1 - src/gui/SubWindow.cpp | 1 + 4 files changed, 1 insertion(+), 30 deletions(-) diff --git a/include/EffectView.h b/include/EffectView.h index cd45b735e77..382f7025a2f 100644 --- a/include/EffectView.h +++ b/include/EffectView.h @@ -73,7 +73,6 @@ public slots: void moveUp(); void moveDown(); void deletePlugin(); - void closeEffects(); signals: diff --git a/src/gui/EffectView.cpp b/src/gui/EffectView.cpp index 428035b9564..e75e93acc15 100644 --- a/src/gui/EffectView.cpp +++ b/src/gui/EffectView.cpp @@ -90,23 +90,6 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) : if( m_controlView ) { m_subWindow = getGUI()->mainWindow()->addWindowedWidget( m_controlView ); - - if ( !m_controlView->isResizable() ) - { - m_subWindow->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); - if (m_subWindow->layout()) - { - m_subWindow->layout()->setSizeConstraint(QLayout::SetFixedSize); - } - } - - Qt::WindowFlags flags = m_subWindow->windowFlags(); - flags &= ~Qt::WindowMaximizeButtonHint; - m_subWindow->setWindowFlags( flags ); - - connect( m_controlView, SIGNAL(closed()), - this, SLOT(closeEffects())); - m_subWindow->hide(); } } @@ -174,17 +157,6 @@ void EffectView::deletePlugin() -void EffectView::closeEffects() -{ - if( m_subWindow ) - { - m_subWindow->hide(); - } - effect()->controls()->setViewVisible( false ); -} - - - void EffectView::contextMenuEvent( QContextMenuEvent * ) { QPointer contextMenu = new CaptionMenu( model()->displayName(), this ); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index ff5b05bed25..2f211ddebcb 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -557,7 +557,6 @@ SubWindow* MainWindow::addWindowedWidget(QWidget *w, Qt::WindowFlags windowFlags { // wrap the widget in our own *custom* window that patches some errors in QMdiSubWindow auto win = new SubWindow(m_workspace->viewport(), windowFlags); - win->setAttribute(Qt::WA_DeleteOnClose); win->setWidget(w); if (w && w->sizeHint().isValid()) { auto titleBarHeight = win->titleBarHeight(); diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index 8d681cb15ec..c7da1007aaa 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -201,6 +201,7 @@ void SubWindow::setVisible(bool visible) void SubWindow::showEvent(QShowEvent* e) { + widget()->show(); if (isDetached()) { widget()->setGeometry(m_childGeom); From 6e3515771eb1822726229281534e5430b0403d89 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Mon, 27 Oct 2025 23:49:31 +0300 Subject: [PATCH 33/70] Use, init and update childGeomm only when detached Tracking is only required for OS-level windows, and so is only initialized when necessary. Avoids a function overload. --- include/SubWindow.h | 1 - src/gui/SubWindow.cpp | 25 +++---------------------- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/include/SubWindow.h b/include/SubWindow.h index 2de9cb31daf..98a74330423 100644 --- a/include/SubWindow.h +++ b/include/SubWindow.h @@ -69,7 +69,6 @@ class LMMS_EXPORT SubWindow : public QMdiSubWindow void setBorderColor( const QColor &c ); int titleBarHeight() const; int frameWidth() const; - void setWidget(QWidget* widget); // Hook for QMdiSubWindow::setWidget bool isDetached() const; void setDetached(bool on); diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index c7da1007aaa..f6129f8adfd 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -113,26 +113,6 @@ SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags windowFlags) : -/** - * @brief SubWindow::setWidget - * - * This is a wrapper to initialize everything that depends on a child widget. - * Though technically not an override, this is the only way - * to set the child widget, and the best way to track that. - */ -void SubWindow::setWidget(QWidget* w) -{ - QMdiSubWindow::setWidget(w); - - if (widget()) - { - m_childGeom = widget()->geometry(); - } -} - - - - /** * @brief SubWindow::paintEvent * @@ -345,6 +325,7 @@ void SubWindow::detach() hide(); widget()->setWindowFlags(flags); + m_childGeom = widget()->geometry(); // reset/init tracked detached geometry since it's only needed there if (shown) { widget()->show(); } @@ -639,11 +620,11 @@ bool SubWindow::eventFilter(QObject* obj, QEvent* event) return QMdiSubWindow::eventFilter(obj, event); case QEvent::Move: - m_childGeom.moveTo(static_cast(event)->pos()); + if (isDetached()) { m_childGeom.moveTo(static_cast(event)->pos()); } return QMdiSubWindow::eventFilter(obj, event); case QEvent::Resize: - m_childGeom.setSize(static_cast(event)->size()); + if (isDetached()) { m_childGeom.setSize(static_cast(event)->size()); } return QMdiSubWindow::eventFilter(obj, event); default: From 50d527c8221be7d57a8aff55d28774ee637559a2 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Tue, 28 Oct 2025 13:07:14 +0300 Subject: [PATCH 34/70] Use std::clamp --- src/gui/SubWindow.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index f6129f8adfd..82f5d702e76 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -354,15 +354,14 @@ void SubWindow::attach() frame += decorationMargins(); // arbitrary values, require window to touch `mdiArea - margin` - // min(max(a, b), c) ensures that a <= b <= c // TODO make this live configurble maybe? const auto margin = QMargins(40, 40, 40, 40); - frame.moveTo(std::min(std::max(margin.left() - frame.width(), - frame.left()), - mdiArea()->rect().width() - margin.right()), - std::min(std::max(margin.top() - frame.height(), - frame.top()), - mdiArea()->rect().height() - margin.bottom())); + frame.moveTo(std::clamp(frame.left(), + margin.left() - frame.width(), + mdiArea()->rect().width() - margin.right()), + std::clamp(frame.top(), + margin.top() - frame.height(), + mdiArea()->rect().height() - margin.bottom())); auto flags = windowFlags(); flags &= ~Qt::Window; From c60029e877cce19c6dbd872a08e0aa94f188d7cd Mon Sep 17 00:00:00 2001 From: SpomJ Date: Wed, 29 Oct 2025 15:43:15 +0300 Subject: [PATCH 35/70] Make hide-on-attach-on-close configurable Provide an option to hide/show SubWindow after attaching with close button. --- include/SetupDialog.h | 2 ++ src/gui/SubWindow.cpp | 10 +++++++++- src/gui/modals/SetupDialog.cpp | 12 ++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/include/SetupDialog.h b/include/SetupDialog.h index c314ff42d81..ad944541aef 100644 --- a/include/SetupDialog.h +++ b/include/SetupDialog.h @@ -82,6 +82,7 @@ private slots: void toggleSoloLegacyBehavior(bool enabled); void toggleTrackDeletionWarning(bool enabled); void toggleMixerChannelDeletionWarning(bool enabled); + void toggleHideOnDetachedClosed(bool enabled); void toggleMMPZ(bool enabled); void toggleDisableBackup(bool enabled); void toggleOpenLastProject(bool enabled); @@ -144,6 +145,7 @@ private slots: bool m_soloLegacyBehavior; bool m_trackDeletionWarning; bool m_mixerChannelDeletionWarning; + bool m_hideOnDetachedClosed; bool m_MMPZ; bool m_disableBackup; bool m_openLastProject; diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index 82f5d702e76..c012081a49d 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -42,6 +42,7 @@ #include #include +#include "ConfigManager.h" #include "embed.h" // Only needed for error display for missing detach feature @@ -608,8 +609,15 @@ bool SubWindow::eventFilter(QObject* obj, QEvent* event) if (isDetached()) { attach(); - hide(); event->ignore(); + if (ConfigManager::inst()->value("ui", "hideondetachedclosed", "0").toInt()) + { + hide(); + } + else + { + return true; + } } else { diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 4ea0d4dddf9..e637df95de1 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -112,6 +112,8 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : "ui", "trackdeletionwarning", "1").toInt()), m_mixerChannelDeletionWarning(ConfigManager::inst()->value( "ui", "mixerchanneldeletionwarning", "1").toInt()), + m_hideOnDetachedClosed(ConfigManager::inst()->value( + "ui", "hideondetachedclosed", "0").toInt()), m_MMPZ(!ConfigManager::inst()->value( "app", "nommpz").toInt()), m_disableBackup(!ConfigManager::inst()->value( @@ -254,6 +256,8 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : m_trackDeletionWarning, SLOT(toggleTrackDeletionWarning(bool)), false); addCheckBox(tr("Show warning when deleting a mixer channel that is in use"), guiGroupBox, guiGroupLayout, m_mixerChannelDeletionWarning, SLOT(toggleMixerChannelDeletionWarning(bool)), false); + addCheckBox(tr("Hide subwindows when attaching them"), guiGroupBox, guiGroupLayout, + m_hideOnDetachedClosed, SLOT(toggleHideOnDetachedClosed(bool)), false); m_loopMarkerComboBox = new QComboBox{guiGroupBox}; @@ -975,6 +979,8 @@ void SetupDialog::accept() QString::number(m_trackDeletionWarning)); ConfigManager::inst()->setValue("ui", "mixerchanneldeletionwarning", QString::number(m_mixerChannelDeletionWarning)); + ConfigManager::inst()->setValue("ui", "hideondetachedclosed", + QString::number(m_hideOnDetachedClosed)); ConfigManager::inst()->setValue("app", "nommpz", QString::number(!m_MMPZ)); ConfigManager::inst()->setValue("app", "disablebackup", @@ -1102,6 +1108,12 @@ void SetupDialog::toggleMixerChannelDeletionWarning(bool enabled) } +void SetupDialog::toggleHideOnDetachedClosed(bool enabled) +{ + m_hideOnDetachedClosed = enabled; +} + + void SetupDialog::toggleMMPZ(bool enabled) { m_MMPZ = enabled; From 1d0634c0041fbb7e98a35fde341968b0c0aaac3e Mon Sep 17 00:00:00 2001 From: SpomJ Date: Wed, 29 Oct 2025 15:49:39 +0300 Subject: [PATCH 36/70] Fix window size resetting for real this time The reason it happened was because SubWindowgot shown before the widget was visible, causing its height to reset to 0. Now widget visible state is always set first, so this doesn't happen. --- include/Editor.h | 1 - src/gui/SubWindow.cpp | 4 ++-- src/gui/editors/Editor.cpp | 8 -------- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/include/Editor.h b/include/Editor.h index 9cabc21666d..2141717af58 100644 --- a/include/Editor.h +++ b/include/Editor.h @@ -57,7 +57,6 @@ class Editor : public QMainWindow DropToolBar * addDropToolBar(Qt::ToolBarArea whereToAdd, QString const & windowTitle); DropToolBar * addDropToolBar(QWidget * parent, Qt::ToolBarArea whereToAdd, QString const & windowTitle); - void closeEvent(QCloseEvent* event) override; void keyPressEvent(QKeyEvent* ke) override; public slots: diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index c012081a49d..e1c949a67c8 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -173,8 +173,8 @@ void SubWindow::changeEvent( QEvent *event ) void SubWindow::setVisible(bool visible) { - if (isDetached()) { widget()->setVisible(visible); } - else { QMdiSubWindow::setVisible(visible); } + widget()->setVisible(visible); + if (!isDetached()) { QMdiSubWindow::setVisible(visible); } } diff --git a/src/gui/editors/Editor.cpp b/src/gui/editors/Editor.cpp index 912209c2df5..1b7f102947a 100644 --- a/src/gui/editors/Editor.cpp +++ b/src/gui/editors/Editor.cpp @@ -139,14 +139,6 @@ QAction *Editor::playAction() const return m_playAction; } -// Workaround: for some reason editor windows minimize their height when close is unhandled -void Editor::closeEvent(QCloseEvent* ce) -{ - if (!parentWidget()) { hide(); } - getGUI()->mainWindow()->refocus(); - ce->ignore(); -} - void Editor::keyPressEvent(QKeyEvent* ke) { if (ke->key() == Qt::Key_Space) From ca140484d2cb0742d4bb6eda833aad69b3a2eba5 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 30 Oct 2025 01:44:35 +0300 Subject: [PATCH 37/70] Restore EffectView functionality actually --- src/gui/EffectView.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/gui/EffectView.cpp b/src/gui/EffectView.cpp index e75e93acc15..3eaa3fadaed 100644 --- a/src/gui/EffectView.cpp +++ b/src/gui/EffectView.cpp @@ -90,6 +90,18 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) : if( m_controlView ) { m_subWindow = getGUI()->mainWindow()->addWindowedWidget( m_controlView ); + if (!m_controlView->isResizable()) + { + if (m_controlView->layout()) + { + m_controlView->layout()->setSizeConstraint(QLayout::SetFixedSize); + } + else + { + m_controlView->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + } + } + m_subWindow->setWindowFlag(Qt::WindowMaximizeButtonHint, false); m_subWindow->hide(); } } From 696e2e341b277b79085925e22bea600003e1f789 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 30 Oct 2025 02:00:56 +0300 Subject: [PATCH 38/70] Fix SubWindow label overlapping detach button --- src/gui/SubWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index e1c949a67c8..29630d82c9b 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -521,6 +521,7 @@ void SubWindow::adjustTitleBar() m_detachBtn->move(buttonPos); m_detachBtn->show(); + buttonBarWidth = buttonBarWidth + m_buttonSize.width() + buttonGap; if( widget() ) { From 12e150925391c60c0654c2bd27ac757fdc038591 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 30 Oct 2025 02:11:59 +0300 Subject: [PATCH 39/70] Add SubWindow::eventFilter docs --- src/gui/SubWindow.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index 29630d82c9b..a8c175690df 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -593,6 +593,18 @@ void SubWindow::resizeEvent( QResizeEvent * event ) } } + + + +/** + * @brief SubWindow::eventFilter + * + * Override of QMdiSubWindow's event filter. + * This is not how regular eventFilters work, it is never installed explicitly. + * Instead, it is installed by Qt and conveniently installs itself + * onto the child widget. Despite relying on internal implementation details, + * as of writing this it seems to be the best way to do so as soon as the widget is set. + */ bool SubWindow::eventFilter(QObject* obj, QEvent* event) { if (obj != static_cast(widget())) From 69e4ed06102d31941674e8e411e09e6298b97af3 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 30 Oct 2025 02:27:10 +0300 Subject: [PATCH 40/70] Get rid of childGeom --- include/SubWindow.h | 1 - src/gui/SubWindow.cpp | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/include/SubWindow.h b/include/SubWindow.h index 98a74330423..ebdda771ff1 100644 --- a/include/SubWindow.h +++ b/include/SubWindow.h @@ -108,7 +108,6 @@ public slots: QLabel * m_windowTitle; QGraphicsDropShadowEffect * m_shadow; bool m_hasFocus; - QRect m_childGeom; static void elideText( QLabel *label, QString text ); void adjustTitleBar(); diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index a8c175690df..d657a182d4d 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -185,7 +185,6 @@ void SubWindow::showEvent(QShowEvent* e) widget()->show(); if (isDetached()) { - widget()->setGeometry(m_childGeom); widget()->setWindowState((widget()->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); } } @@ -326,7 +325,6 @@ void SubWindow::detach() hide(); widget()->setWindowFlags(flags); - m_childGeom = widget()->geometry(); // reset/init tracked detached geometry since it's only needed there if (shown) { widget()->show(); } @@ -635,18 +633,9 @@ bool SubWindow::eventFilter(QObject* obj, QEvent* event) else { hide(); - event->ignore(); } return QMdiSubWindow::eventFilter(obj, event); - case QEvent::Move: - if (isDetached()) { m_childGeom.moveTo(static_cast(event)->pos()); } - return QMdiSubWindow::eventFilter(obj, event); - - case QEvent::Resize: - if (isDetached()) { m_childGeom.setSize(static_cast(event)->size()); } - return QMdiSubWindow::eventFilter(obj, event); - default: return QMdiSubWindow::eventFilter(obj, event); } From a9654fc1c27c9821beb53756b588baa413048db8 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 30 Oct 2025 02:39:18 +0300 Subject: [PATCH 41/70] Keep attached widget position --- src/gui/SubWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index d657a182d4d..d1e8ad29e0f 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -348,7 +348,7 @@ void SubWindow::attach() const bool shown = widget()->isVisible(); - auto frame = widget()->windowHandle()->geometry(); + auto frame = widget()->geometry(); frame.moveTo(mdiArea()->mapFromGlobal(frame.topLeft())); frame += decorationMargins(); From d144abb75f9436e3ad5d5351aee77253c23bea08 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 30 Oct 2025 14:09:59 +0300 Subject: [PATCH 42/70] Initial attempt at always-detach --- include/SetupDialog.h | 5 +++-- src/gui/EffectView.cpp | 8 ++++---- src/gui/SubWindow.cpp | 16 +++++++++++++--- src/gui/modals/SetupDialog.cpp | 33 +++++++++++++++++++++------------ 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/include/SetupDialog.h b/include/SetupDialog.h index ad944541aef..f97ba306bed 100644 --- a/include/SetupDialog.h +++ b/include/SetupDialog.h @@ -82,10 +82,10 @@ private slots: void toggleSoloLegacyBehavior(bool enabled); void toggleTrackDeletionWarning(bool enabled); void toggleMixerChannelDeletionWarning(bool enabled); - void toggleHideOnDetachedClosed(bool enabled); void toggleMMPZ(bool enabled); void toggleDisableBackup(bool enabled); void toggleOpenLastProject(bool enabled); + void detachBehaviorChanged(); void loopMarkerModeChanged(); void setLanguage(int lang); @@ -145,10 +145,11 @@ private slots: bool m_soloLegacyBehavior; bool m_trackDeletionWarning; bool m_mixerChannelDeletionWarning; - bool m_hideOnDetachedClosed; bool m_MMPZ; bool m_disableBackup; bool m_openLastProject; + QString m_detachBehavior; + QComboBox* m_detachBehaviorComboBox; QString m_loopMarkerMode; QComboBox* m_loopMarkerComboBox; QString m_lang; diff --git a/src/gui/EffectView.cpp b/src/gui/EffectView.cpp index 3eaa3fadaed..ece9953aace 100644 --- a/src/gui/EffectView.cpp +++ b/src/gui/EffectView.cpp @@ -129,15 +129,15 @@ void EffectView::editControls() { if( m_subWindow ) { - if( !m_subWindow->isVisible() ) + if( !m_controlView->isVisible() ) { - m_subWindow->show(); - m_subWindow->raise(); + m_controlView->show(); + m_controlView->raise(); effect()->controls()->setViewVisible( true ); } else { - m_subWindow->hide(); + m_controlView->hide(); effect()->controls()->setViewVisible( false ); } } diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index d1e8ad29e0f..9298d91b99b 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -183,6 +183,7 @@ void SubWindow::setVisible(bool visible) void SubWindow::showEvent(QShowEvent* e) { widget()->show(); + if (ConfigManager::inst()->value("ui", "detachbehavior", "show") == "detached") { detach(); } if (isDetached()) { widget()->setWindowState((widget()->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); @@ -619,14 +620,23 @@ bool SubWindow::eventFilter(QObject* obj, QEvent* event) case QEvent::Close: if (isDetached()) { - attach(); + //attach(); event->ignore(); - if (ConfigManager::inst()->value("ui", "hideondetachedclosed", "0").toInt()) + QString detachBehavior = ConfigManager::inst()->value("ui", "detachbehavior", "show"); + if (detachBehavior == "show") { + attach(); + return true; + } + else if (detachBehavior == "hide") + { + attach(); hide(); + return QMdiSubWindow::eventFilter(obj, event); } - else + else if (detachBehavior == "detached") { + widget()->hide(); return true; } } diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index e637df95de1..85dae4757dc 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -112,14 +112,13 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : "ui", "trackdeletionwarning", "1").toInt()), m_mixerChannelDeletionWarning(ConfigManager::inst()->value( "ui", "mixerchanneldeletionwarning", "1").toInt()), - m_hideOnDetachedClosed(ConfigManager::inst()->value( - "ui", "hideondetachedclosed", "0").toInt()), m_MMPZ(!ConfigManager::inst()->value( "app", "nommpz").toInt()), m_disableBackup(!ConfigManager::inst()->value( "app", "disablebackup").toInt()), m_openLastProject(ConfigManager::inst()->value( "app", "openlastproject").toInt()), + m_detachBehavior{ConfigManager::inst()->value("ui", "detachBehavior", "show")}, m_loopMarkerMode{ConfigManager::inst()->value("app", "loopmarkermode", "dual")}, m_lang(ConfigManager::inst()->value( "app", "language")), @@ -256,8 +255,19 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : m_trackDeletionWarning, SLOT(toggleTrackDeletionWarning(bool)), false); addCheckBox(tr("Show warning when deleting a mixer channel that is in use"), guiGroupBox, guiGroupLayout, m_mixerChannelDeletionWarning, SLOT(toggleMixerChannelDeletionWarning(bool)), false); - addCheckBox(tr("Hide subwindows when attaching them"), guiGroupBox, guiGroupLayout, - m_hideOnDetachedClosed, SLOT(toggleHideOnDetachedClosed(bool)), false); + + m_detachBehaviorComboBox = new QComboBox{guiGroupBox}; + + m_detachBehaviorComboBox->addItem(tr("Show when attaching"), "show"); + m_detachBehaviorComboBox->addItem(tr("Hide when attaching"), "hide"); + m_detachBehaviorComboBox->addItem(tr("Always detached"), "detached"); + + m_detachBehaviorComboBox->setCurrentIndex(m_detachBehaviorComboBox->findData(m_detachBehavior)); + connect(m_detachBehaviorComboBox, qOverload(&QComboBox::currentIndexChanged), + this, &SetupDialog::detachBehaviorChanged); + + guiGroupLayout->addWidget(new QLabel{tr("Detaching behavior"), guiGroupBox}); + guiGroupLayout->addWidget(m_detachBehaviorComboBox); m_loopMarkerComboBox = new QComboBox{guiGroupBox}; @@ -979,14 +989,13 @@ void SetupDialog::accept() QString::number(m_trackDeletionWarning)); ConfigManager::inst()->setValue("ui", "mixerchanneldeletionwarning", QString::number(m_mixerChannelDeletionWarning)); - ConfigManager::inst()->setValue("ui", "hideondetachedclosed", - QString::number(m_hideOnDetachedClosed)); ConfigManager::inst()->setValue("app", "nommpz", QString::number(!m_MMPZ)); ConfigManager::inst()->setValue("app", "disablebackup", QString::number(!m_disableBackup)); ConfigManager::inst()->setValue("app", "openlastproject", QString::number(m_openLastProject)); + ConfigManager::inst()->setValue("ui", "detachbehavior", m_detachBehavior); ConfigManager::inst()->setValue("app", "loopmarkermode", m_loopMarkerMode); ConfigManager::inst()->setValue("app", "language", m_lang); ConfigManager::inst()->setValue("ui", "saveinterval", @@ -1108,12 +1117,6 @@ void SetupDialog::toggleMixerChannelDeletionWarning(bool enabled) } -void SetupDialog::toggleHideOnDetachedClosed(bool enabled) -{ - m_hideOnDetachedClosed = enabled; -} - - void SetupDialog::toggleMMPZ(bool enabled) { m_MMPZ = enabled; @@ -1132,6 +1135,12 @@ void SetupDialog::toggleOpenLastProject(bool enabled) } +void SetupDialog::detachBehaviorChanged() +{ + m_detachBehavior = m_detachBehaviorComboBox->currentData().toString(); +} + + void SetupDialog::loopMarkerModeChanged() { m_loopMarkerMode = m_loopMarkerComboBox->currentData().toString(); From 34888a7e1d57bcb1d4f9938ca5908a81512a5e6c Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 30 Oct 2025 22:25:36 +0300 Subject: [PATCH 43/70] close autodetached windows properly --- src/gui/SubWindow.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index 9298d91b99b..e60bda92c22 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -620,24 +620,24 @@ bool SubWindow::eventFilter(QObject* obj, QEvent* event) case QEvent::Close: if (isDetached()) { - //attach(); - event->ignore(); QString detachBehavior = ConfigManager::inst()->value("ui", "detachbehavior", "show"); if (detachBehavior == "show") { attach(); + event->ignore(); return true; } else if (detachBehavior == "hide") { attach(); hide(); + event->ignore(); return QMdiSubWindow::eventFilter(obj, event); } else if (detachBehavior == "detached") { - widget()->hide(); - return true; + event->accept(); + return QMdiSubWindow::eventFilter(obj, event); } } else From 93f4dc64945bf3925e8aeafc43479d931eb1e75a Mon Sep 17 00:00:00 2001 From: SpomJ Date: Fri, 31 Oct 2025 20:22:00 +0300 Subject: [PATCH 44/70] Improve window positioning on attach --- src/gui/SubWindow.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index e60bda92c22..7b52502d390 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -353,15 +353,16 @@ void SubWindow::attach() frame.moveTo(mdiArea()->mapFromGlobal(frame.topLeft())); frame += decorationMargins(); - // arbitrary values, require window to touch `mdiArea - margin` - // TODO make this live configurble maybe? - const auto margin = QMargins(40, 40, 40, 40); + // Make sure the window fully fits on screen + frame.setSize({std::min(frame.width(), mdiArea()->width()), + std::min(frame.height(), mdiArea()->height())}); + frame.moveTo(std::clamp(frame.left(), - margin.left() - frame.width(), - mdiArea()->rect().width() - margin.right()), + 0, + mdiArea()->rect().width() - frame.width()), std::clamp(frame.top(), - margin.top() - frame.height(), - mdiArea()->rect().height() - margin.bottom())); + 0, + mdiArea()->rect().height() - frame.height())); auto flags = windowFlags(); flags &= ~Qt::Window; From c1ed85d987295fa1da0587b4f7635e23e9f5bbc8 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sat, 1 Nov 2025 02:11:39 +0300 Subject: [PATCH 45/70] Fix editor windows having minimal height on first show --- src/gui/SubWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index 7b52502d390..d689e13d8a2 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -173,7 +173,7 @@ void SubWindow::changeEvent( QEvent *event ) void SubWindow::setVisible(bool visible) { - widget()->setVisible(visible); + if (isDetached() || visible) { widget()->setVisible(visible); } if (!isDetached()) { QMdiSubWindow::setVisible(visible); } } From 49b23d553d44acabe00642be7661e11546c0bda0 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sat, 1 Nov 2025 03:14:16 +0300 Subject: [PATCH 46/70] Remove redundant subwindow-related code from MainWindow --- src/gui/MainWindow.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 2f211ddebcb..2e38b0ee946 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -505,10 +505,7 @@ void MainWindow::finalize() getGUI()->songEditor() }) { - QMdiSubWindow* window = addWindowedWidget(widget); - window->setWindowIcon(widget->windowIcon()); - window->setAttribute(Qt::WA_DeleteOnClose, false); - window->resize(widget->sizeHint()); + addWindowedWidget(widget); } getGUI()->automationEditor()->parentWidget()->hide(); From 53c23c21b440f0e5fd4af1bd21652df09e1dada4 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sat, 1 Nov 2025 03:19:54 +0300 Subject: [PATCH 47/70] fix typo in setup menu --- src/gui/modals/SetupDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 85dae4757dc..fd70369a3d8 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -118,7 +118,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : "app", "disablebackup").toInt()), m_openLastProject(ConfigManager::inst()->value( "app", "openlastproject").toInt()), - m_detachBehavior{ConfigManager::inst()->value("ui", "detachBehavior", "show")}, + m_detachBehavior{ConfigManager::inst()->value("ui", "detachbehavior", "show")}, m_loopMarkerMode{ConfigManager::inst()->value("app", "loopmarkermode", "dual")}, m_lang(ConfigManager::inst()->value( "app", "language")), From 0255db65da5ebec867ba61a669e78b3e9f178e23 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sat, 1 Nov 2025 03:28:48 +0300 Subject: [PATCH 48/70] Fix ITW icon always showing up as piano InstrumentView always overrides the icon (which didn't happen here for wharever reason), rendering the icon setting useless even without the bugfix. --- src/gui/instrument/InstrumentTrackWindow.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/instrument/InstrumentTrackWindow.cpp b/src/gui/instrument/InstrumentTrackWindow.cpp index 553c2953b87..dd27ee381a4 100644 --- a/src/gui/instrument/InstrumentTrackWindow.cpp +++ b/src/gui/instrument/InstrumentTrackWindow.cpp @@ -304,7 +304,6 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : // we can reuse this method. updateSubWindow(); - setWindowIcon(embed::getIconPixmap("instrument_track")); subWin->hide(); } From 80a9ffdc9d6451de1607f97184d7a946058f1c58 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sat, 1 Nov 2025 15:18:51 +0300 Subject: [PATCH 49/70] Update ControllerView --- include/ControllerView.h | 2 -- src/gui/ControllerView.cpp | 43 +++++++++++--------------------------- 2 files changed, 12 insertions(+), 33 deletions(-) diff --git a/include/ControllerView.h b/include/ControllerView.h index 9b442672d94..acb52247058 100644 --- a/include/ControllerView.h +++ b/include/ControllerView.h @@ -64,7 +64,6 @@ class ControllerView : public QFrame, public ModelView public slots: void editControls(); void removeController(); - void closeControls(); void renameController(); void moveUp(); void moveDown(); @@ -85,7 +84,6 @@ public slots: QMdiSubWindow * m_subWindow; ControllerDialog * m_controllerDlg; QLabel * m_nameLabel; - bool m_show; } ; diff --git a/src/gui/ControllerView.cpp b/src/gui/ControllerView.cpp index 7f7c4729c67..4b458b152a0 100644 --- a/src/gui/ControllerView.cpp +++ b/src/gui/ControllerView.cpp @@ -44,12 +44,11 @@ namespace lmms::gui { -ControllerView::ControllerView( Controller * _model, QWidget * _parent ) : - QFrame( _parent ), - ModelView( _model, this ), - m_subWindow( nullptr ), - m_controllerDlg( nullptr ), - m_show( true ) +ControllerView::ControllerView (Controller * model, QWidget * parent) + : QFrame{parent} + , ModelView{model, this} + , m_subWindow{nullptr} + , m_controllerDlg{nullptr} { this->setFrameStyle( QFrame::StyledPanel ); this->setFrameShadow( QFrame::Raised ); @@ -60,7 +59,7 @@ ControllerView::ControllerView( Controller * _model, QWidget * _parent ) : auto hBox = new QHBoxLayout(); vBoxLayout->addLayout(hBox); - auto label = new QLabel("" + _model->displayName() + "", this); + auto label = new QLabel("" + model->displayName() + "", this); QSizePolicy sizePolicy = label->sizePolicy(); sizePolicy.setHorizontalStretch(1); label->setSizePolicy(sizePolicy); @@ -72,27 +71,17 @@ ControllerView::ControllerView( Controller * _model, QWidget * _parent ) : hBox->addWidget(controlsButton); - m_nameLabel = new QLabel(_model->name(), this); + m_nameLabel = new QLabel(model->name(), this); vBoxLayout->addWidget(m_nameLabel); - m_controllerDlg = getController()->createDialog( getGUI()->mainWindow()->workspace() ); + m_controllerDlg = getController()->createDialog(getGUI()->mainWindow()->workspace()); m_subWindow = getGUI()->mainWindow()->addWindowedWidget( m_controllerDlg ); - - Qt::WindowFlags flags = m_subWindow->windowFlags(); - flags &= ~Qt::WindowMaximizeButtonHint; - m_subWindow->setWindowFlags( flags ); - m_subWindow->setFixedSize( m_subWindow->size() ); - - m_subWindow->setWindowIcon( m_controllerDlg->windowIcon() ); - - connect( m_controllerDlg, SIGNAL(closed()), - this, SLOT(closeControls())); - + m_subWindow->setWindowFlag(Qt::WindowMaximizeButtonHint, false); m_subWindow->hide(); - setModel( _model ); + setModel(model); } @@ -111,28 +100,20 @@ ControllerView::~ControllerView() void ControllerView::editControls() { - if( m_show ) + if (!m_controllerDlg->isVisible()) { m_subWindow->show(); m_subWindow->raise(); - m_show = false; } else { m_subWindow->hide(); - m_show = true; } } -void ControllerView::closeControls() -{ - m_subWindow->hide(); - m_show = true; -} - void ControllerView::moveUp() { emit movedUp(this); } void ControllerView::moveDown() { emit movedDown(this); } @@ -188,4 +169,4 @@ void ControllerView::contextMenuEvent( QContextMenuEvent * ) } -} // namespace lmms::gui \ No newline at end of file +} // namespace lmms::gui From 485c8c1d25f995515193d75bebfc3e6aa9a89339 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sat, 1 Nov 2025 15:34:17 +0300 Subject: [PATCH 50/70] minor formatting fixes --- src/gui/ControllerView.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/ControllerView.cpp b/src/gui/ControllerView.cpp index 4b458b152a0..802f64449df 100644 --- a/src/gui/ControllerView.cpp +++ b/src/gui/ControllerView.cpp @@ -44,14 +44,14 @@ namespace lmms::gui { -ControllerView::ControllerView (Controller * model, QWidget * parent) +ControllerView::ControllerView(Controller * model, QWidget * parent) : QFrame{parent} , ModelView{model, this} , m_subWindow{nullptr} , m_controllerDlg{nullptr} { - this->setFrameStyle( QFrame::StyledPanel ); - this->setFrameShadow( QFrame::Raised ); + this->setFrameStyle(QFrame::StyledPanel); + this->setFrameShadow(QFrame::Raised); setFocusPolicy(Qt::StrongFocus); auto vBoxLayout = new QVBoxLayout(this); @@ -77,7 +77,7 @@ ControllerView::ControllerView (Controller * model, QWidget * parent) m_controllerDlg = getController()->createDialog(getGUI()->mainWindow()->workspace()); - m_subWindow = getGUI()->mainWindow()->addWindowedWidget( m_controllerDlg ); + m_subWindow = getGUI()->mainWindow()->addWindowedWidget(m_controllerDlg); m_subWindow->setWindowFlag(Qt::WindowMaximizeButtonHint, false); m_subWindow->hide(); From 4a8d6f9a5a48449f24dfb15c100ecbc6f5df938b Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sat, 1 Nov 2025 15:35:14 +0300 Subject: [PATCH 51/70] even more minor formatting fixes --- src/gui/ControllerView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/ControllerView.cpp b/src/gui/ControllerView.cpp index 802f64449df..21424385f5f 100644 --- a/src/gui/ControllerView.cpp +++ b/src/gui/ControllerView.cpp @@ -44,7 +44,7 @@ namespace lmms::gui { -ControllerView::ControllerView(Controller * model, QWidget * parent) +ControllerView::ControllerView(Controller* model, QWidget* parent) : QFrame{parent} , ModelView{model, this} , m_subWindow{nullptr} From ff29cf5089ab9ba23be574da59d9b51b97d0bec0 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sun, 2 Nov 2025 03:52:47 +0300 Subject: [PATCH 52/70] Don't hard-set size of EffectView This is optimistic and assumes all widgets that don't have a layout already fix their size. Even when this isn't true, it's better to set it inside the effects and not the EffectView. --- src/gui/EffectView.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/gui/EffectView.cpp b/src/gui/EffectView.cpp index ece9953aace..06a06771a98 100644 --- a/src/gui/EffectView.cpp +++ b/src/gui/EffectView.cpp @@ -96,10 +96,7 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) : { m_controlView->layout()->setSizeConstraint(QLayout::SetFixedSize); } - else - { - m_controlView->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - } + // else hope it already manages its size } m_subWindow->setWindowFlag(Qt::WindowMaximizeButtonHint, false); m_subWindow->hide(); From 666094a9d5f680e6da836b1b4ea031b5f574ec6d Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sun, 2 Nov 2025 23:16:04 +0300 Subject: [PATCH 53/70] Change label text --- src/gui/modals/SetupDialog.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index fd70369a3d8..d3b92ede4e2 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -258,15 +258,15 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : m_detachBehaviorComboBox = new QComboBox{guiGroupBox}; - m_detachBehaviorComboBox->addItem(tr("Show when attaching"), "show"); - m_detachBehaviorComboBox->addItem(tr("Hide when attaching"), "hide"); + m_detachBehaviorComboBox->addItem(tr("Attach and show when closed"), "show"); + m_detachBehaviorComboBox->addItem(tr("Attach and hide when closed"), "hide"); m_detachBehaviorComboBox->addItem(tr("Always detached"), "detached"); m_detachBehaviorComboBox->setCurrentIndex(m_detachBehaviorComboBox->findData(m_detachBehavior)); connect(m_detachBehaviorComboBox, qOverload(&QComboBox::currentIndexChanged), this, &SetupDialog::detachBehaviorChanged); - guiGroupLayout->addWidget(new QLabel{tr("Detaching behavior"), guiGroupBox}); + guiGroupLayout->addWidget(new QLabel{tr("Detached window behavior"), guiGroupBox}); guiGroupLayout->addWidget(m_detachBehaviorComboBox); m_loopMarkerComboBox = new QComboBox{guiGroupBox}; From f0282aa7500b7e33dc59d59164cce6e04517f0fd Mon Sep 17 00:00:00 2001 From: SpomJ Date: Mon, 3 Nov 2025 01:34:32 +0300 Subject: [PATCH 54/70] Destroy childless subwindows This is done in hopes that nothing ever uses SubWindow::setWidget directly. Perhaps a more stable solution using SubWindow::childEvent is due. --- src/gui/MainWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 2e38b0ee946..b4d6f6656b6 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -555,6 +555,7 @@ SubWindow* MainWindow::addWindowedWidget(QWidget *w, Qt::WindowFlags windowFlags // wrap the widget in our own *custom* window that patches some errors in QMdiSubWindow auto win = new SubWindow(m_workspace->viewport(), windowFlags); win->setWidget(w); + if (w) { connect(w, &QWidget::destroyed, win, &SubWindow::deleteLater); } // TODO somehow make this work on any setWidget if (w && w->sizeHint().isValid()) { auto titleBarHeight = win->titleBarHeight(); auto frameWidth = win->frameWidth(); From 6acbae9cf897b167de158da4d4ecf2a69db1b791 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Tue, 4 Nov 2025 18:44:45 +0300 Subject: [PATCH 55/70] Get rid of isResizable in EffectView There's no consistent way to control whether detached windows are resizable. The fixed size needs to be applied directly to child widget. isResizable() being false by default also encourages new effects to have fixed size, which something LMMS is movng away from. --- include/EffectControlDialog.h | 2 -- plugins/Amplifier/AmplifierControlDialog.cpp | 1 + plugins/BassBooster/BassBoosterControlDialog.cpp | 1 + plugins/Compressor/CompressorControlDialog.h | 1 - plugins/Dispersion/DispersionControlDialog.cpp | 1 + plugins/Flanger/FlangerControlsDialog.cpp | 1 + plugins/LadspaEffect/LadspaMatrixControlDialog.cpp | 5 ----- plugins/LadspaEffect/LadspaMatrixControlDialog.h | 1 - .../PeakControllerEffectControlDialog.cpp | 2 ++ plugins/ReverbSC/ReverbSCControlDialog.cpp | 3 ++- plugins/SpectrumAnalyzer/SaControlsDialog.h | 1 - .../StereoEnhancer/StereoEnhancerControlDialog.cpp | 1 + plugins/Vectorscope/VecControlsDialog.h | 1 - src/gui/EffectView.cpp | 12 ++---------- 14 files changed, 11 insertions(+), 22 deletions(-) diff --git a/include/EffectControlDialog.h b/include/EffectControlDialog.h index 675c2cb58d1..bce85000f6f 100644 --- a/include/EffectControlDialog.h +++ b/include/EffectControlDialog.h @@ -45,8 +45,6 @@ class LMMS_EXPORT EffectControlDialog : public QWidget, public ModelView EffectControlDialog(EffectControls* controls); ~EffectControlDialog() override = default; - virtual bool isResizable() const { return false; } - protected: EffectControls* m_effectControls; }; diff --git a/plugins/Amplifier/AmplifierControlDialog.cpp b/plugins/Amplifier/AmplifierControlDialog.cpp index 9bf0bb649b1..44c0e3be265 100644 --- a/plugins/Amplifier/AmplifierControlDialog.cpp +++ b/plugins/Amplifier/AmplifierControlDialog.cpp @@ -57,6 +57,7 @@ AmplifierControlDialog::AmplifierControlDialog(AmplifierControls* controls) : gridLayout->addWidget(makeKnob(tr("PAN"), tr("Panning:"), "%", &controls->m_panModel, false), 0, 1, Qt::AlignHCenter); gridLayout->addWidget(makeKnob(tr("LEFT"), tr("Left gain:"), "%", &controls->m_leftModel, true), 1, 0, Qt::AlignHCenter); gridLayout->addWidget(makeKnob(tr("RIGHT"), tr("Right gain:"), "%", &controls->m_rightModel, true), 1, 1, Qt::AlignHCenter); + gridLayout->setSizeConstraint(QLayout::SetFixedSize); } } // namespace lmms::gui diff --git a/plugins/BassBooster/BassBoosterControlDialog.cpp b/plugins/BassBooster/BassBoosterControlDialog.cpp index fcdf10cc260..93708dbd76c 100644 --- a/plugins/BassBooster/BassBoosterControlDialog.cpp +++ b/plugins/BassBooster/BassBoosterControlDialog.cpp @@ -67,6 +67,7 @@ BassBoosterControlDialog::BassBoosterControlDialog( BassBoosterControls* control tl->addLayout( l ); setLayout( tl ); + tl->setSizeConstraint(QLayout::SetFixedSize); } diff --git a/plugins/Compressor/CompressorControlDialog.h b/plugins/Compressor/CompressorControlDialog.h index b7e2a87e141..7fe79dcc52b 100755 --- a/plugins/Compressor/CompressorControlDialog.h +++ b/plugins/Compressor/CompressorControlDialog.h @@ -71,7 +71,6 @@ class CompressorControlDialog : public EffectControlDialog public: CompressorControlDialog(CompressorControls* controls); - bool isResizable() const override {return true;} QSize sizeHint() const override {return QSize(COMP_SCREEN_X, COMP_SCREEN_Y);} // For theming purposes diff --git a/plugins/Dispersion/DispersionControlDialog.cpp b/plugins/Dispersion/DispersionControlDialog.cpp index fbf37923cfd..2784da35c37 100644 --- a/plugins/Dispersion/DispersionControlDialog.cpp +++ b/plugins/Dispersion/DispersionControlDialog.cpp @@ -43,6 +43,7 @@ DispersionControlDialog::DispersionControlDialog(DispersionControls* controls) : setAutoFillBackground(true); auto layout = new QHBoxLayout(this); layout->setSpacing(5); + layout->setSizeConstraint(QLayout::SetFixedSize); auto amountBox = new LcdSpinBox(3, this, "Amount"); amountBox->setModel(&controls->m_amountModel); diff --git a/plugins/Flanger/FlangerControlsDialog.cpp b/plugins/Flanger/FlangerControlsDialog.cpp index 7433a6b2c1c..e8d382e52db 100644 --- a/plugins/Flanger/FlangerControlsDialog.cpp +++ b/plugins/Flanger/FlangerControlsDialog.cpp @@ -44,6 +44,7 @@ FlangerControlsDialog::FlangerControlsDialog( FlangerControls *controls ) : setPalette( pal ); auto mainLayout = new QVBoxLayout(this); + mainLayout->setSizeConstraint(QLayout::SetFixedSize); auto knobLayout = new QHBoxLayout(); mainLayout->addLayout(knobLayout); diff --git a/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp b/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp index c05481c6d81..45aae33f2ac 100644 --- a/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp +++ b/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp @@ -72,11 +72,6 @@ LadspaMatrixControlDialog::LadspaMatrixControlDialog(LadspaControls * ladspaCont } } -bool LadspaMatrixControlDialog::isResizable() const -{ - return true; -} - bool LadspaMatrixControlDialog::needsLinkColumn() const { LadspaControls * ladspaControls = getLadspaControls(); diff --git a/plugins/LadspaEffect/LadspaMatrixControlDialog.h b/plugins/LadspaEffect/LadspaMatrixControlDialog.h index fa9a6e1b3db..0189c815947 100644 --- a/plugins/LadspaEffect/LadspaMatrixControlDialog.h +++ b/plugins/LadspaEffect/LadspaMatrixControlDialog.h @@ -50,7 +50,6 @@ class LadspaMatrixControlDialog : public EffectControlDialog Q_OBJECT public: LadspaMatrixControlDialog(LadspaControls* ctl); - bool isResizable() const override; private slots: void updateEffectView(LadspaControls* ctl); diff --git a/plugins/PeakControllerEffect/PeakControllerEffectControlDialog.cpp b/plugins/PeakControllerEffect/PeakControllerEffectControlDialog.cpp index 7ce05bc4548..d89ef02bf1d 100644 --- a/plugins/PeakControllerEffect/PeakControllerEffectControlDialog.cpp +++ b/plugins/PeakControllerEffect/PeakControllerEffectControlDialog.cpp @@ -95,6 +95,8 @@ PeakControllerEffectControlDialog::PeakControllerEffectControlDialog( mainLayout->addLayout( knobLayout ); mainLayout->addLayout( ledLayout ); + mainLayout->setSizeConstraint(QLayout::SetFixedSize); + setLayout( mainLayout ); } diff --git a/plugins/ReverbSC/ReverbSCControlDialog.cpp b/plugins/ReverbSC/ReverbSCControlDialog.cpp index 3be156396b2..d507a879275 100644 --- a/plugins/ReverbSC/ReverbSCControlDialog.cpp +++ b/plugins/ReverbSC/ReverbSCControlDialog.cpp @@ -44,6 +44,7 @@ ReverbSCControlDialog::ReverbSCControlDialog( ReverbSCControls* controls ) : setPalette( pal ); auto knobLayout = new QHBoxLayout(this); + knobLayout->setSizeConstraint(QLayout::SetFixedSize); auto inputGainKnob = new Knob(KnobType::Bright26, tr("Input"), this); inputGainKnob->setModel( &controls->m_inputGainModel ); @@ -68,4 +69,4 @@ ReverbSCControlDialog::ReverbSCControlDialog( ReverbSCControls* controls ) : } -} // namespace lmms::gui \ No newline at end of file +} // namespace lmms::gui diff --git a/plugins/SpectrumAnalyzer/SaControlsDialog.h b/plugins/SpectrumAnalyzer/SaControlsDialog.h index f8d67f1e8f5..8567561f35d 100644 --- a/plugins/SpectrumAnalyzer/SaControlsDialog.h +++ b/plugins/SpectrumAnalyzer/SaControlsDialog.h @@ -48,7 +48,6 @@ class SaControlsDialog : public EffectControlDialog explicit SaControlsDialog(SaControls *controls, SaProcessor *processor); ~SaControlsDialog() override = default; - bool isResizable() const override {return true;} QSize sizeHint() const override; private: diff --git a/plugins/StereoEnhancer/StereoEnhancerControlDialog.cpp b/plugins/StereoEnhancer/StereoEnhancerControlDialog.cpp index 1440a4be51f..e19d7363a51 100644 --- a/plugins/StereoEnhancer/StereoEnhancerControlDialog.cpp +++ b/plugins/StereoEnhancer/StereoEnhancerControlDialog.cpp @@ -39,6 +39,7 @@ StereoEnhancerControlDialog::StereoEnhancerControlDialog( EffectControlDialog( _controls ) { auto l = new QHBoxLayout(this); + l->setSizeConstraint(QLayout::SetFixedSize); auto widthKnob = new Knob(KnobType::Bright26, tr("WIDTH"), this); widthKnob->setModel( &_controls->m_widthModel ); diff --git a/plugins/Vectorscope/VecControlsDialog.h b/plugins/Vectorscope/VecControlsDialog.h index 8fee75b4b30..400e04ff5d5 100644 --- a/plugins/Vectorscope/VecControlsDialog.h +++ b/plugins/Vectorscope/VecControlsDialog.h @@ -45,7 +45,6 @@ class VecControlsDialog : public EffectControlDialog explicit VecControlsDialog(VecControls *controls); ~VecControlsDialog() override = default; - bool isResizable() const override {return true;} QSize sizeHint() const override; private: diff --git a/src/gui/EffectView.cpp b/src/gui/EffectView.cpp index 06a06771a98..651fc7c0297 100644 --- a/src/gui/EffectView.cpp +++ b/src/gui/EffectView.cpp @@ -87,17 +87,9 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) : this, SLOT(editControls())); m_controlView = effect()->controls()->createView(); - if( m_controlView ) + if (m_controlView) { - m_subWindow = getGUI()->mainWindow()->addWindowedWidget( m_controlView ); - if (!m_controlView->isResizable()) - { - if (m_controlView->layout()) - { - m_controlView->layout()->setSizeConstraint(QLayout::SetFixedSize); - } - // else hope it already manages its size - } + m_subWindow = getGUI()->mainWindow()->addWindowedWidget(m_controlView); m_subWindow->setWindowFlag(Qt::WindowMaximizeButtonHint, false); m_subWindow->hide(); } From f0d57d1856167299f55d953785cd84927c67cd70 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Tue, 4 Nov 2025 19:08:07 +0300 Subject: [PATCH 56/70] Fix EffectControlDialog not hiding the title bar --- src/gui/EffectView.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/EffectView.cpp b/src/gui/EffectView.cpp index 651fc7c0297..3874f0de647 100644 --- a/src/gui/EffectView.cpp +++ b/src/gui/EffectView.cpp @@ -120,13 +120,13 @@ void EffectView::editControls() { if( !m_controlView->isVisible() ) { - m_controlView->show(); - m_controlView->raise(); - effect()->controls()->setViewVisible( true ); + m_subWindow->show(); + m_subWindow->raise(); + effect()->controls()->setViewVisible( true ); // TODO is this even needed? } else { - m_controlView->hide(); + m_subWindow->hide(); effect()->controls()->setViewVisible( false ); } } From 50a73e4c499320106da2143f522041d54c60b6c9 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Tue, 4 Nov 2025 19:10:20 +0300 Subject: [PATCH 57/70] Remove redundant widget->show() on showEvent this is already being done in setVisible --- src/gui/SubWindow.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index d689e13d8a2..ad0d4d56d6e 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -182,7 +182,6 @@ void SubWindow::setVisible(bool visible) void SubWindow::showEvent(QShowEvent* e) { - widget()->show(); if (ConfigManager::inst()->value("ui", "detachbehavior", "show") == "detached") { detach(); } if (isDetached()) { From e50247e07a2d2d6fce1f1820e9278bd89ef2536b Mon Sep 17 00:00:00 2001 From: SpomJ Date: Fri, 7 Nov 2025 01:45:25 +0300 Subject: [PATCH 58/70] fix drag&drop not updating ITW SubWindow --- src/gui/instrument/InstrumentTrackWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/instrument/InstrumentTrackWindow.cpp b/src/gui/instrument/InstrumentTrackWindow.cpp index dd27ee381a4..cf61fb2e82a 100644 --- a/src/gui/instrument/InstrumentTrackWindow.cpp +++ b/src/gui/instrument/InstrumentTrackWindow.cpp @@ -601,6 +601,7 @@ void InstrumentTrackWindow::dropEvent( QDropEvent* event ) event->accept(); setFocus(); } + updateSubWindow(); } From 98e60786bee56f70e36171ccf1d8a14fac2e30e3 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Fri, 7 Nov 2025 17:42:43 +0300 Subject: [PATCH 59/70] Disable FixedSizeDialogHint on ITW Returns windowing buttons to all ITWs. Also conforms to Qt's advice, see https://doc.qt.io/qt-6/qt.html#WindowType-enum --- src/gui/instrument/InstrumentTrackWindow.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/gui/instrument/InstrumentTrackWindow.cpp b/src/gui/instrument/InstrumentTrackWindow.cpp index cf61fb2e82a..47e2b41caff 100644 --- a/src/gui/instrument/InstrumentTrackWindow.cpp +++ b/src/gui/instrument/InstrumentTrackWindow.cpp @@ -746,8 +746,6 @@ void InstrumentTrackWindow::updateSubWindow() auto subWindow = findSubWindowInParents(); if (subWindow && m_instrumentView) { - Qt::WindowFlags flags = subWindow->windowFlags(); - const auto instrumentViewResizable = m_instrumentView->isResizable(); if (instrumentViewResizable) @@ -757,15 +755,9 @@ void InstrumentTrackWindow::updateSubWindow() const auto extraSpace = QSize(12, 208); setMaximumSize(m_instrumentView->maximumSize() + extraSpace); setMinimumSize(m_instrumentView->minimumSize() + extraSpace); - - flags &= ~Qt::MSWindowsFixedSizeDialogHint; - flags |= Qt::WindowMaximizeButtonHint; } else { - flags |= Qt::MSWindowsFixedSizeDialogHint; - flags &= ~Qt::WindowMaximizeButtonHint; - setFixedSize(sizeHint()); // The sub window might be reused from an instrument that was maximized. Show the sub window @@ -776,7 +768,7 @@ void InstrumentTrackWindow::updateSubWindow() } } - subWindow->setWindowFlags(flags); + subWindow->setWindowFlag(Qt::WindowMaximizeButtonHint, instrumentViewResizable); // TODO This is only needed if the sub window is implemented with LMMS' own SubWindow class. // If an QMdiSubWindow is used everything works automatically. It seems that SubWindow is From 1a7f6be258046b5500d70d8dd68e8dca1be47ac8 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sat, 8 Nov 2025 23:25:08 +0300 Subject: [PATCH 60/70] Disable SubWindow flag when detaching a window Could fix some bugs. Also get rid of manipulating Qt::Widget flag manipulation since it's just 0 (fallback). --- src/gui/SubWindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index ad0d4d56d6e..21da51f9a5c 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -320,7 +320,7 @@ void SubWindow::detach() auto flags = windowFlags(); flags |= Qt::Window; - flags &= ~Qt::Widget; + flags &= ~Qt::SubWindow; flags |= Qt::WindowMinimizeButtonHint; hide(); @@ -365,7 +365,7 @@ void SubWindow::attach() auto flags = windowFlags(); flags &= ~Qt::Window; - flags |= Qt::Widget; + flags |= Qt::SubWindow; flags &= ~Qt::WindowMinimizeButtonHint; widget()->setWindowFlags(flags); From 49817e4cfcb4256708ea86b4d3419da15dd1d4c2 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sun, 9 Nov 2025 05:06:47 +0300 Subject: [PATCH 61/70] Try disabling MacOS soft-fail --- src/gui/SubWindow.cpp | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index 21da51f9a5c..a1ef881f242 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -45,12 +45,6 @@ #include "ConfigManager.h" #include "embed.h" -// Only needed for error display for missing detach feature -#include "lmmsconfig.h" -#ifdef LMMS_BUILD_APPLE -#include "TextFloat.h" -#endif - namespace lmms::gui { @@ -292,15 +286,6 @@ void SubWindow::setBorderColor( const QColor &c ) -#ifdef LMMS_BUILD_APPLE -// FIXME: For some reason detaching on MacOS seems to never show the detached window. -void SubWindow::detach() -{ - TextFloat::displayMessage("Missing Feature",\ - tr("Sorry, detach is not yet available on this platform."), embed::getIconPixmap("error"), 2000); -} - -#else void SubWindow::detach() { #if QT_VERSION < 0x50C00 @@ -330,7 +315,6 @@ void SubWindow::detach() widget()->move(pos); } -#endif void SubWindow::attach() { From 0ca04740f7c0bca19428d3a38651a4e60db52a36 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Mon, 10 Nov 2025 02:49:48 +0300 Subject: [PATCH 62/70] Enable Undo/Redo globally "Ensure both c-s-z and c-y are redo" is removed for parity reasons because it relied on methods that aren't QAction and thus remain local. --- src/gui/MainWindow.cpp | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index b4d6f6656b6..5d7de41128f 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -329,23 +329,16 @@ void MainWindow::finalize() auto edit_menu = new QMenu(this); menuBar()->addMenu( edit_menu )->setText( tr( "&Edit" ) ); - m_undoAction = edit_menu->addAction( embed::getIconPixmap( "edit_undo" ), - tr( "Undo" ), + m_undoAction = edit_menu->addAction(embed::getIconPixmap("edit_undo"), + tr("Undo"), this, SLOT(undo()), - QKeySequence::Undo ); - m_redoAction = edit_menu->addAction( embed::getIconPixmap( "edit_redo" ), - tr( "Redo" ), + QKeySequence::Undo); + m_redoAction = edit_menu->addAction(embed::getIconPixmap("edit_redo"), + tr("Redo"), this, SLOT(redo()), - QKeySequence::Redo ); - // Ensure that both (Ctrl+Y) and (Ctrl+Shift+Z) activate redo shortcut regardless of OS defaults - if (QKeySequence(QKeySequence::Redo) != QKeySequence(combine(Qt::CTRL, Qt::Key_Y))) - { - new QShortcut(QKeySequence(combine(Qt::CTRL, Qt::Key_Y)), this, SLOT(redo())); - } - if (QKeySequence(QKeySequence::Redo) != QKeySequence(combine(Qt::CTRL, Qt::SHIFT, Qt::Key_Z))) - { - new QShortcut(QKeySequence(combine(Qt::CTRL, Qt::SHIFT, Qt::Key_Z)), this, SLOT(redo())); - } + QKeySequence::Redo); + m_undoAction->setShortcutContext(Qt::ApplicationShortcut); + m_redoAction->setShortcutContext(Qt::ApplicationShortcut); edit_menu->addSeparator(); edit_menu->addAction(embed::getIconPixmap("microtuner"), tr("Scales and keymaps"), From 84e83e001bfcb0dcfafddf4a376146ae5f267aff Mon Sep 17 00:00:00 2001 From: SpomJ Date: Tue, 11 Nov 2025 04:09:15 +0300 Subject: [PATCH 63/70] Save detached window visibility properly; refactor --- src/gui/MainWindow.cpp | 92 +++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 55 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 5d7de41128f..4af2fdf4a0e 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -650,77 +650,59 @@ void MainWindow::clearKeyModifiers() -void MainWindow::saveWidgetState( QWidget * _w, QDomElement & _de ) +void MainWindow::saveWidgetState(QWidget* w, QDomElement& de) { - // If our widget is the main content of a window (e.g. piano roll, Mixer, etc), - // we really care about the position of the *window* - not the position of the widget within its window - if( _w->parentWidget() != nullptr && - _w->parentWidget()->inherits( "QMdiSubWindow" ) ) - { - _w = _w->parentWidget(); - } - - // If the widget is a SubWindow, then we can make use of the getTrueNormalGeometry() method that - // performs the same as normalGeometry, but isn't broken on X11 ( see https://bugreports.qt.io/browse/QTBUG-256 ) - auto asSubWindow = qobject_cast(_w); - QRect normalGeom = asSubWindow != nullptr ? asSubWindow->getTrueNormalGeometry() : _w->normalGeometry(); - - bool visible = _w->isVisible(); - _de.setAttribute( "visible", visible ); - _de.setAttribute( "minimized", _w->isMinimized() ); - _de.setAttribute( "maximized", _w->isMaximized() ); - - _de.setAttribute( "x", normalGeom.x() ); - _de.setAttribute( "y", normalGeom.y() ); - - QSize sizeToStore = normalGeom.size(); - _de.setAttribute( "width", sizeToStore.width() ); - _de.setAttribute( "height", sizeToStore.height() ); + // TODO only use one of these + SubWindow* win = qobject_cast(w); // nullptr if not + if (!win && w->parentWidget()) { win = qobject_cast(w->parentWidget()); } // try parent instead + if (!win) { return; } // finally, soft-fail if neither can find the window + + de.setAttribute("visible", bool{win->widget() && win->widget()->isVisible()}); + de.setAttribute("maximized", win->isMaximized()); + + QRect normalGeometry = win->getTrueNormalGeometry(); + de.setAttribute("x", normalGeometry.x()); + de.setAttribute("y", normalGeometry.y() ); + de.setAttribute("width", normalGeometry.width()); + de.setAttribute("height", normalGeometry.height()); } -void MainWindow::restoreWidgetState( QWidget * _w, const QDomElement & _de ) +void MainWindow::restoreWidgetState(QWidget* w, const QDomElement& de) { - QRect r( qMax( 1, _de.attribute( "x" ).toInt() ), - qMax( 1, _de.attribute( "y" ).toInt() ), - qMax( _w->sizeHint().width(), _de.attribute( "width" ).toInt() ), - qMax( _w->minimumHeight(), _de.attribute( "height" ).toInt() ) ); - if( _de.hasAttribute( "visible" ) && !r.isNull() ) - { - // If our widget is the main content of a window (e.g. piano roll, Mixer, etc), - // we really care about the position of the *window* - not the position of the widget within its window - if ( _w->parentWidget() != nullptr && - _w->parentWidget()->inherits( "QMdiSubWindow" ) ) - { - _w = _w->parentWidget(); - } + // TODO only use one of these + SubWindow* win = qobject_cast(w); // nullptr if not + if (!win && w->parentWidget()) { win = qobject_cast(w->parentWidget()); } // try parent instead + if (!win) { return; } // finally, soft-fail if neither can find the window + + QRect normalGeometry(de.attribute("x").toInt(), + de.attribute("y").toInt(), + de.attribute("width").toInt(), + de.attribute("height").toInt()); + + if (normalGeometry.isValid()) + { // first restore the window, as attempting to resize a maximized window causes graphics glitching - _w->setWindowState( _w->windowState() & ~(Qt::WindowMaximized | Qt::WindowMinimized) ); + win->setWindowState(win->windowState() & ~(Qt::WindowMaximized | Qt::WindowMinimized)); - // Check isEmpty() to work around corrupt project files with empty size - if ( ! r.size().isEmpty() ) { - _w->resize( r.size() ); - } - _w->move( r.topLeft() ); + win->setGeometry(normalGeometry); // set the window to its correct minimized/maximized/restored state - Qt::WindowStates flags = _w->windowState(); - flags = _de.attribute( "minimized" ).toInt() ? - ( flags | Qt::WindowMinimized ) : - ( flags & ~Qt::WindowMinimized ); - flags = _de.attribute( "maximized" ).toInt() ? - ( flags | Qt::WindowMaximized ) : - ( flags & ~Qt::WindowMaximized ); - _w->setWindowState( flags ); - - _w->setVisible( _de.attribute( "visible" ).toInt() ); + Qt::WindowStates winState = win->windowState(); + winState = de.attribute("maximized").toInt() + ? (winState | Qt::WindowMaximized) + : (winState & ~Qt::WindowMaximized); + win->setWindowState(winState); } + + if (de.hasAttribute("visible")) { win->setVisible(de.attribute("visible").toInt()); } } + void MainWindow::emptySlot() { } From 944328f42d0131f57bee4bd91c7406450fb015f6 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Tue, 11 Nov 2025 11:53:43 +0300 Subject: [PATCH 64/70] remove obsolete qt version workarounds --- src/gui/SubWindow.cpp | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index a1ef881f242..934ec631921 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -288,16 +288,6 @@ void SubWindow::setBorderColor( const QColor &c ) void SubWindow::detach() { -#if QT_VERSION < 0x50C00 - // Workaround for a bug in Qt versions below 5.12, - // where argument-dependent-lookup fails for QFlags operators - // declared inside a namepsace. - // This affects the Q_DECLARE_OPERATORS_FOR_FLAGS macro in Instrument.h - // See also: https://codereview.qt-project.org/c/qt/qtbase/+/225348 - - using ::operator|; -#endif - if (isDetached()) { return; } const auto pos = mapToGlobal(widget()->pos()); @@ -318,16 +308,6 @@ void SubWindow::detach() void SubWindow::attach() { -#if QT_VERSION < 0x50C00 - // Workaround for a bug in Qt versions below 5.12, - // where argument-dependent-lookup fails for QFlags operators - // declared inside a namepsace. - // This affects the Q_DECLARE_OPERATORS_FOR_FLAGS macro in Instrument.h - // See also: https://codereview.qt-project.org/c/qt/qtbase/+/225348 - - using ::operator|; -#endif - if (!isDetached()) { return; } const bool shown = widget()->isVisible(); From 029a0e4e43fe1aa42c1cd15de00bc51d8d3cbf6b Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 13 Nov 2025 18:57:29 +0300 Subject: [PATCH 65/70] remove setFixedSize from LadspaControlDialog --- plugins/LadspaEffect/LadspaControlDialog.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/LadspaEffect/LadspaControlDialog.cpp b/plugins/LadspaEffect/LadspaControlDialog.cpp index 62eb30ba312..5189b0cde80 100644 --- a/plugins/LadspaEffect/LadspaControlDialog.cpp +++ b/plugins/LadspaEffect/LadspaControlDialog.cpp @@ -47,7 +47,6 @@ LadspaControlDialog::LadspaControlDialog( LadspaControls * _ctl ) : m_stereoLink( nullptr ) { auto mainLay = new QVBoxLayout(this); - mainLay->setSizeConstraint(QLayout::SetFixedSize); m_effectLayout = new QHBoxLayout(); mainLay->addLayout( m_effectLayout ); From 40d342093e5a73973b9d362d370315fdce267d55 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Thu, 13 Nov 2025 20:06:38 +0300 Subject: [PATCH 66/70] Fix resizing LadspaMatrixControlDialog --- plugins/LadspaEffect/LadspaMatrixControlDialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp b/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp index 45aae33f2ac..f778fb07e54 100644 --- a/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp +++ b/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp @@ -47,6 +47,7 @@ LadspaMatrixControlDialog::LadspaMatrixControlDialog(LadspaControls * ladspaCont m_stereoLink(nullptr) { QVBoxLayout * mainLayout = new QVBoxLayout(this); + mainLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); m_scrollArea = new QScrollArea(this); m_scrollArea->setWidgetResizable(true); From 6aa4b13eff824f8c66703f1601c20becae5315bd Mon Sep 17 00:00:00 2001 From: SpomJ Date: Fri, 14 Nov 2025 01:55:05 +0300 Subject: [PATCH 67/70] Allow detached windows in topLevelITW; refactor --- src/gui/tracks/InstrumentTrackView.cpp | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/gui/tracks/InstrumentTrackView.cpp b/src/gui/tracks/InstrumentTrackView.cpp index c0cba7eefe2..40270db4029 100644 --- a/src/gui/tracks/InstrumentTrackView.cpp +++ b/src/gui/tracks/InstrumentTrackView.cpp @@ -205,20 +205,16 @@ void InstrumentTrackView::toggleMidiCCRack() -InstrumentTrackWindow * InstrumentTrackView::topLevelInstrumentTrackWindow() +InstrumentTrackWindow* InstrumentTrackView::topLevelInstrumentTrackWindow() { - InstrumentTrackWindow * w = nullptr; - for( const QMdiSubWindow * sw : - getGUI()->mainWindow()->workspace()->subWindowList( - QMdiArea::ActivationHistoryOrder ) ) - { - if( sw->isVisible() && sw->widget()->inherits( "lmms::gui::InstrumentTrackWindow" ) ) - { - w = qobject_cast( sw->widget() ); - } - } - - return w; + auto subWindowList = getGUI()->mainWindow()->workspace()->subWindowList(QMdiArea::ActivationHistoryOrder); + auto winIt = std::find_if(subWindowList.rbegin(), subWindowList.rend(), + [](QMdiSubWindow* sw){return sw->widget() + && sw->widget()->isVisible() + && sw->widget()->inherits("lmms::gui::InstrumentTrackWindow");}); + return winIt != subWindowList.rend() && (**winIt).widget() + ? qobject_cast((**winIt).widget()) + : nullptr; } From 17157a00bb0f6e4d3e62b3d17e0bcd6e9e58cf22 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sat, 15 Nov 2025 23:14:21 +0300 Subject: [PATCH 68/70] allow detach to be disabled --- include/SubWindow.h | 3 +++ src/gui/SubWindow.cpp | 41 ++++++++++++++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/include/SubWindow.h b/include/SubWindow.h index ebdda771ff1..656dc0980a3 100644 --- a/include/SubWindow.h +++ b/include/SubWindow.h @@ -69,6 +69,8 @@ class LMMS_EXPORT SubWindow : public QMdiSubWindow void setBorderColor( const QColor &c ); int titleBarHeight() const; int frameWidth() const; + bool isDetachable() const; + void setDetachable(bool on); bool isDetached() const; void setDetached(bool on); @@ -108,6 +110,7 @@ public slots: QLabel * m_windowTitle; QGraphicsDropShadowEffect * m_shadow; bool m_hasFocus; + bool m_isDetachable; static void elideText( QLabel *label, QString text ); void adjustTitleBar(); diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index 934ec631921..9a24be84960 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -29,6 +29,7 @@ #include "SubWindow.h" #include + #include #include #include @@ -49,11 +50,12 @@ namespace lmms::gui { -SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags windowFlags) : - QMdiSubWindow(parent, windowFlags), - m_buttonSize(17, 17), - m_titleBarHeight(titleBarHeight()), - m_hasFocus(false) +SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags windowFlags) + : QMdiSubWindow{parent, windowFlags} + , m_buttonSize{17, 17} + , m_titleBarHeight{titleBarHeight()} + , m_hasFocus{false} + , m_isDetachable{true} { // initialize the tracked geometry to whatever Qt thinks the normal geometry currently is. // this should always work, since QMdiSubWindows will not start as maximized @@ -186,6 +188,23 @@ void SubWindow::showEvent(QShowEvent* e) +bool SubWindow::isDetachable() const +{ + return m_isDetachable; +} + + + + +void SubWindow::setDetachable(bool on) +{ + m_isDetachable = on; + +} + + + + bool SubWindow::isDetached() const { return widget()->windowFlags().testFlag(Qt::Window); @@ -288,7 +307,7 @@ void SubWindow::setBorderColor( const QColor &c ) void SubWindow::detach() { - if (isDetached()) { return; } + if (!isDetachable() || isDetached()) { return; } const auto pos = mapToGlobal(widget()->pos()); const bool shown = isVisible(); @@ -442,6 +461,7 @@ void SubWindow::adjustTitleBar() // button adjustments m_maximizeBtn->hide(); m_restoreBtn->hide(); + m_detachBtn->hide(); m_closeBtn->show(); const int rightSpace = 3; @@ -482,9 +502,12 @@ void SubWindow::adjustTitleBar() buttonPos -= buttonStep; } - m_detachBtn->move(buttonPos); - m_detachBtn->show(); - buttonBarWidth = buttonBarWidth + m_buttonSize.width() + buttonGap; + if (isDetachable()) + { + m_detachBtn->move(buttonPos); + m_detachBtn->show(); + buttonBarWidth = buttonBarWidth + m_buttonSize.width() + buttonGap; + } if( widget() ) { From fcb8fdb1057c2bb82aa67800c6a18de4b76a174f Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sat, 15 Nov 2025 23:39:07 +0300 Subject: [PATCH 69/70] disable detach on embedded VST subwindows --- plugins/Vestige/Vestige.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/Vestige/Vestige.cpp b/plugins/Vestige/Vestige.cpp index 4f3dd53ca94..abad6df233e 100644 --- a/plugins/Vestige/Vestige.cpp +++ b/plugins/Vestige/Vestige.cpp @@ -103,8 +103,9 @@ class vstSubWin : public SubWindow vstSubWin( QWidget * _parent ) : SubWindow( _parent ) { - setAttribute( Qt::WA_DeleteOnClose, false ); - setWindowFlags( Qt::WindowCloseButtonHint ); + setAttribute(Qt::WA_DeleteOnClose, false); + setWindowFlag(Qt::WindowMaximizeButtonHint, false); + setDetachable(false); } ~vstSubWin() override = default; From d74cd8da4b5df28add08aa29010addf140b7a964 Mon Sep 17 00:00:00 2001 From: SpomJ Date: Sun, 16 Nov 2025 02:04:41 +0300 Subject: [PATCH 70/70] add attach/detach everything actions --- include/MainWindow.h | 4 ++++ src/gui/MainWindow.cpp | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/include/MainWindow.h b/include/MainWindow.h index 5f0f23f716e..40d376fe352 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -141,9 +141,13 @@ class MainWindow : public QMainWindow static void saveWidgetState( QWidget * _w, QDomElement & _de ); static void restoreWidgetState( QWidget * _w, const QDomElement & _de ); + void setAllSubWindowsDetached(bool detached); bool eventFilter(QObject* watched, QEvent* event) override; +signals: + void detachAllSubWindows(bool detached); + public slots: void resetWindowTitle(); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 4af2fdf4a0e..3f68e16575e 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -354,6 +354,7 @@ void MainWindow::finalize() this, SLOT(updateViewMenu())); connect( m_viewMenu, SIGNAL(triggered(QAction*)), this, SLOT(updateConfig(QAction*))); + updateViewMenu(); m_toolsMenu = new QMenu( this ); @@ -547,6 +548,7 @@ SubWindow* MainWindow::addWindowedWidget(QWidget *w, Qt::WindowFlags windowFlags { // wrap the widget in our own *custom* window that patches some errors in QMdiSubWindow auto win = new SubWindow(m_workspace->viewport(), windowFlags); + connect(this, &MainWindow::detachAllSubWindows, win, &SubWindow::setDetached); win->setWidget(w); if (w) { connect(w, &QWidget::destroyed, win, &SubWindow::deleteLater); } // TODO somehow make this work on any setWidget if (w && w->sizeHint().isValid()) { @@ -560,6 +562,13 @@ SubWindow* MainWindow::addWindowedWidget(QWidget *w, Qt::WindowFlags windowFlags } + +void MainWindow::setAllSubWindowsDetached(bool detachState) +{ + emit detachAllSubWindows(detachState); +} + + void MainWindow::resetWindowTitle() { QString title(tr( "Untitled" )); @@ -1056,6 +1065,22 @@ void MainWindow::updateViewMenu() m_viewMenu->addSeparator(); + auto detachAllAction = m_viewMenu->addAction(embed::getIconPixmap("detach"), + tr("Detach all subwindows"), + this, [this](){setAllSubWindowsDetached(true);}, + QKeySequence {Qt::CTRL | Qt::SHIFT | Qt::Key_D} + ); + auto attachAllAction = m_viewMenu->addAction(embed::getIconPixmap("detach"), + tr("Attach all subwindows"), + this, [this](){setAllSubWindowsDetached(false);}, + QKeySequence {Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_D} + ); + + detachAllAction->setShortcutContext(Qt::ApplicationShortcut); + attachAllAction->setShortcutContext(Qt::ApplicationShortcut); + + m_viewMenu->addSeparator(); + // Here we should put all look&feel -stuff from configmanager // that is safe to change on the fly. There is probably some // more elegant way to do this.