diff --git a/data/themes/default/detach.svg b/data/themes/default/detach.svg new file mode 100644 index 00000000000..337e3660801 --- /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 - - - - - - - - - - - - - diff --git a/include/ControllerDialog.h b/include/ControllerDialog.h index f9145d3b06f..fc6e78f84da 100644 --- a/include/ControllerDialog.h +++ b/include/ControllerDialog.h @@ -26,9 +26,11 @@ #ifndef LMMS_GUI_CONTROLLER_DIALOG_H #define LMMS_GUI_CONTROLLER_DIALOG_H -#include "DetachableWidget.h" +#include + #include "ModelView.h" + namespace lmms { @@ -37,7 +39,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..f54b3e1a1c5 100644 --- a/include/ControllerRackView.h +++ b/include/ControllerRackView.h @@ -25,13 +25,15 @@ #ifndef LMMS_GUI_CONTROLLER_RACK_VIEW_H #define LMMS_GUI_CONTROLLER_RACK_VIEW_H -#include "DetachableWidget.h" +#include + #include "SerializingObject.h" class QPushButton; class QScrollArea; class QVBoxLayout; + namespace lmms { @@ -42,7 +44,7 @@ namespace gui class ControllerView; -class ControllerRackView : public DetachableWidget, public SerializingObject +class ControllerRackView : public QWidget, public SerializingObject { Q_OBJECT public: 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/include/DetachableWidget.h b/include/DetachableWidget.h deleted file mode 100644 index 532dec3303f..00000000000 --- a/include/DetachableWidget.h +++ /dev/null @@ -1,49 +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" - -namespace lmms::gui { - -class LMMS_EXPORT DetachableWidget : public QWidget -{ - Q_OBJECT -public: - using QWidget::QWidget; - - void closeEvent(QCloseEvent* ce) override; - -signals: - void closed(); -}; - -} // namespace lmms::gui - -#endif // LMMS_GUI_DETACHABLE_WIDGET 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..bce85000f6f 100644 --- a/include/EffectControlDialog.h +++ b/include/EffectControlDialog.h @@ -26,9 +26,11 @@ #ifndef LMMS_GUI_EFFECT_CONTROL_DIALOG_H #define LMMS_GUI_EFFECT_CONTROL_DIALOG_H -#include "DetachableWidget.h" +#include + #include "ModelView.h" + namespace lmms { @@ -37,14 +39,12 @@ class EffectControls; namespace gui { -class LMMS_EXPORT EffectControlDialog : public DetachableWidget, public ModelView +class LMMS_EXPORT EffectControlDialog : public QWidget, public ModelView { public: EffectControlDialog(EffectControls* controls); ~EffectControlDialog() override = default; - virtual bool isResizable() const { return false; } - protected: EffectControls* m_effectControls; }; 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/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/include/MicrotunerConfig.h b/include/MicrotunerConfig.h index dc3632b87dd..13d4bbc9789 100644 --- a/include/MicrotunerConfig.h +++ b/include/MicrotunerConfig.h @@ -25,9 +25,10 @@ #ifndef LMMS_GUI_MICROTUNER_CONFIG_H #define LMMS_GUI_MICROTUNER_CONFIG_H +#include + #include "AutomatableModel.h" #include "ComboBoxModel.h" -#include "DetachableWidget.h" #include "SerializingObject.h" class QLineEdit; @@ -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..8eed38bf2d7 100644 --- a/include/MixerView.h +++ b/include/MixerView.h @@ -25,8 +25,9 @@ #ifndef LMMS_GUI_MIXER_VIEW_H #define LMMS_GUI_MIXER_VIEW_H +#include + #include "MixerChannelView.h" -#include "DetachableWidget.h" #include "ModelView.h" #include "SerializingObject.h" @@ -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..daa7436b9b1 100644 --- a/include/ProjectNotes.h +++ b/include/ProjectNotes.h @@ -25,7 +25,8 @@ #ifndef LMMS_GUI_PROJECT_NOTES_H #define LMMS_GUI_PROJECT_NOTES_H -#include "DetachableWindow.h" +#include + #include "SerializingObject.h" class QAction; @@ -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..47ce765f1b8 100644 --- a/include/SampleTrackWindow.h +++ b/include/SampleTrackWindow.h @@ -25,7 +25,8 @@ #ifndef LMMS_GUI_SAMPLE_TRACK_WINDOW_H #define LMMS_GUI_SAMPLE_TRACK_WINDOW_H -#include "DetachableWidget.h" +#include + #include "ModelView.h" #include "SampleTrack.h" #include "SerializingObject.h" @@ -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/SetupDialog.h b/include/SetupDialog.h index c314ff42d81..f97ba306bed 100644 --- a/include/SetupDialog.h +++ b/include/SetupDialog.h @@ -85,6 +85,7 @@ private slots: void toggleMMPZ(bool enabled); void toggleDisableBackup(bool enabled); void toggleOpenLastProject(bool enabled); + void detachBehaviorChanged(); void loopMarkerModeChanged(); void setLanguage(int lang); @@ -147,6 +148,8 @@ private slots: 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/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/include/SubWindow.h b/include/SubWindow.h index f300335dbe6..656dc0980a3 100644 --- a/include/SubWindow.h +++ b/include/SubWindow.h @@ -63,11 +63,16 @@ 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 ); int titleBarHeight() const; int frameWidth() const; + bool isDetachable() const; + void setDetachable(bool on); + bool isDetached() const; + void setDetached(bool on); // TODO Needed to update the title bar when replacing instruments. // Update works automatically if QMdiSubWindows are used. @@ -80,14 +85,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; - bool isDetached() const; - signals: void focusLost(); @@ -106,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/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/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 ); diff --git a/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp b/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp index c05481c6d81..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); @@ -72,11 +73,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/plugins/Vestige/Vestige.cpp b/plugins/Vestige/Vestige.cpp index 426856f1e30..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; @@ -113,15 +114,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/CMakeLists.txt b/src/gui/CMakeLists.txt index 41fecd2aec1..f1712053f67 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -9,8 +9,6 @@ SET(LMMS_SRCS gui/ControllerRackView.cpp 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/ControllerView.cpp b/src/gui/ControllerView.cpp index 7f7c4729c67..21424385f5f 100644 --- a/src/gui/ControllerView.cpp +++ b/src/gui/ControllerView.cpp @@ -44,15 +44,14 @@ 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 ); + this->setFrameStyle(QFrame::StyledPanel); + this->setFrameShadow(QFrame::Raised); setFocusPolicy(Qt::StrongFocus); auto vBoxLayout = new QVBoxLayout(this); @@ -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_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_controllerDlg = getController()->createDialog(getGUI()->mainWindow()->workspace()); + m_subWindow = getGUI()->mainWindow()->addWindowedWidget(m_controllerDlg); + 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 diff --git a/src/gui/DetachableWidget.cpp b/src/gui/DetachableWidget.cpp deleted file mode 100644 index 240ddec16b1..00000000000 --- a/src/gui/DetachableWidget.cpp +++ /dev/null @@ -1,56 +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" -#include "SubWindow.h" - -namespace lmms::gui { - -void DetachableWidget::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/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/EffectView.cpp b/src/gui/EffectView.cpp index 428035b9564..3874f0de647 100644 --- a/src/gui/EffectView.cpp +++ b/src/gui/EffectView.cpp @@ -87,26 +87,10 @@ 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() ) - { - 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 = getGUI()->mainWindow()->addWindowedWidget(m_controlView); + m_subWindow->setWindowFlag(Qt::WindowMaximizeButtonHint, false); m_subWindow->hide(); } } @@ -134,11 +118,11 @@ void EffectView::editControls() { if( m_subWindow ) { - if( !m_subWindow->isVisible() ) + if( !m_controlView->isVisible() ) { m_subWindow->show(); m_subWindow->raise(); - effect()->controls()->setViewVisible( true ); + effect()->controls()->setViewVisible( true ); // TODO is this even needed? } else { @@ -174,17 +158,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..3f68e16575e 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"), @@ -361,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 ); @@ -505,10 +499,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(); @@ -557,8 +548,9 @@ 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); + 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()) { auto titleBarHeight = win->titleBarHeight(); auto frameWidth = win->frameWidth(); @@ -570,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" )); @@ -660,77 +659,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() ) + // 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()) { - // 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(); - } // 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() { } @@ -1084,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. 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..6dc8cde4d92 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) @@ -172,10 +172,6 @@ MixerView::MixerView(Mixer* mixer) : // timer for updating faders connect(mainWindow, &MainWindow::periodicUpdate, this, &MixerView::updateFaders); - // adjust window size - layout()->invalidate(); - resize(sizeHint()); - setFixedHeight(height()); layout()->setSizeConstraint(QLayout::SetMinimumSize); // add ourself to workspace 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..6651739512c 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} @@ -169,27 +169,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()); + // better than `setFixedSize` because it still responds to layout changes + layout()->setSizeConstraint(QLayout::SetFixedSize); - // 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 - - subWin->setWindowIcon(embed::getIconPixmap("sample_track")); - subWin->setFixedSize(subWin->size()); + setWindowIcon(embed::getIconPixmap("sample_track")); subWin->hide(); } @@ -266,8 +253,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..9a24be84960 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -28,6 +28,8 @@ #include "SubWindow.h" +#include + #include #include #include @@ -41,17 +43,19 @@ #include #include +#include "ConfigManager.h" #include "embed.h" 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 @@ -81,7 +85,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 @@ -158,21 +162,49 @@ void SubWindow::changeEvent( QEvent *event ) { adjustTitleBar(); } - } + + + void SubWindow::setVisible(bool visible) { - if (isDetached() && visible) // avoid showing titlebar here + if (isDetached() || visible) { widget()->setVisible(visible); } + if (!isDetached()) { QMdiSubWindow::setVisible(visible); } +} + + + + +void SubWindow::showEvent(QShowEvent* e) +{ + if (ConfigManager::inst()->value("ui", "detachbehavior", "show") == "detached") { detach(); } + if (isDetached()) { - widget()->show(); - // raise the detached window in case it was minimized widget()->setWindowState((widget()->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - return; } - QMdiSubWindow::setVisible(visible); } + + + +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); @@ -181,6 +213,15 @@ bool SubWindow::isDetached() const +void SubWindow::setDetached(bool on) +{ + if (on) { detach(); } + else { attach(); } +} + + + + /** * @brief SubWindow::elideText * @@ -261,65 +302,70 @@ void SubWindow::setBorderColor( const QColor &c ) m_borderColor = 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; } + +void SubWindow::detach() +{ + if (!isDetachable() || isDetached()) { return; } const auto pos = mapToGlobal(widget()->pos()); + const bool shown = isVisible(); auto flags = windowFlags(); flags |= Qt::Window; - flags &= ~Qt::Widget; - widget()->setWindowFlags(flags); - widget()->show(); + flags &= ~Qt::SubWindow; + flags |= Qt::WindowMinimizeButtonHint; + hide(); + widget()->setWindowFlags(flags); - widget()->windowHandle()->setPosition(pos); + if (shown) { widget()->show(); } + + widget()->move(pos); } 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 + if (!isDetached()) { return; } - using ::operator|; -#endif + const bool shown = widget()->isVisible(); - if (!isDetached()) { return; } + auto frame = widget()->geometry(); + frame.moveTo(mdiArea()->mapFromGlobal(frame.topLeft())); + frame += decorationMargins(); - auto frame = widget()->windowHandle()->frameGeometry(); + // 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(), + 0, + mdiArea()->rect().width() - frame.width()), + std::clamp(frame.top(), + 0, + mdiArea()->rect().height() - frame.height())); auto flags = windowFlags(); flags &= ~Qt::Window; - flags |= Qt::Widget; + flags |= Qt::SubWindow; + flags &= ~Qt::WindowMinimizeButtonHint; widget()->setWindowFlags(flags); - 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 (shown) + { + widget()->show(); + show(); + } + + if (QGuiApplication::platformName() == "wayland") + { + 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 + { + setGeometry(frame); + } } @@ -342,6 +388,19 @@ int SubWindow::frameWidth() const } + + +QMargins SubWindow::decorationMargins() const +{ + return QMargins(frameWidth(), // left + titleBarHeight(), // top + frameWidth(), // right + frameWidth()); // bottom +} + + + + void SubWindow::updateTitleBar() { adjustTitleBar(); @@ -402,6 +461,7 @@ void SubWindow::adjustTitleBar() // button adjustments m_maximizeBtn->hide(); m_restoreBtn->hide(); + m_detachBtn->hide(); m_closeBtn->show(); const int rightSpace = 3; @@ -442,8 +502,12 @@ void SubWindow::adjustTitleBar() buttonPos -= buttonStep; } - m_detachBtn->move(buttonPos); - m_detachBtn->show(); + if (isDetachable()) + { + m_detachBtn->move(buttonPos); + m_detachBtn->show(); + buttonBarWidth = buttonBarWidth + m_buttonSize.width() + buttonGap; + } if( widget() ) { @@ -515,6 +579,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())) @@ -524,11 +600,41 @@ bool SubWindow::eventFilter(QObject* obj, QEvent* event) switch (event->type()) { - case QEvent::WindowStateChange: - event->accept(); - return true; - default: - return QMdiSubWindow::eventFilter(obj, event); + case QEvent::WindowStateChange: + event->accept(); + return true; + + case QEvent::Close: + if (isDetached()) + { + 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") + { + event->accept(); + return QMdiSubWindow::eventFilter(obj, event); + } + } + else + { + hide(); + } + return QMdiSubWindow::eventFilter(obj, event); + + default: + return QMdiSubWindow::eventFilter(obj, event); } } 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), diff --git a/src/gui/instrument/InstrumentTrackWindow.cpp b/src/gui/instrument/InstrumentTrackWindow.cpp index ecb926ee747..47e2b41caff 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(); - subWin->setWindowIcon(embed::getIconPixmap("instrument_track")); subWin->hide(); } @@ -531,22 +530,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); } @@ -618,6 +601,7 @@ void InstrumentTrackWindow::dropEvent( QDropEvent* event ) event->accept(); setFocus(); } + updateSubWindow(); } @@ -677,13 +661,34 @@ 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); // 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 + + // 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 bringToFront->trackContainerView()->scrollToTrackView(bringToFront); @@ -741,25 +746,18 @@ void InstrumentTrackWindow::updateSubWindow() auto subWindow = findSubWindowInParents(); if (subWindow && m_instrumentView) { - Qt::WindowFlags flags = subWindow->windowFlags(); - const auto instrumentViewResizable = m_instrumentView->isResizable(); 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); - - 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 @@ -770,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 @@ -780,14 +778,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 - } } } diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 4ea0d4dddf9..d3b92ede4e2 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -118,6 +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_loopMarkerMode{ConfigManager::inst()->value("app", "loopmarkermode", "dual")}, m_lang(ConfigManager::inst()->value( "app", "language")), @@ -255,6 +256,19 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : addCheckBox(tr("Show warning when deleting a mixer channel that is in use"), guiGroupBox, guiGroupLayout, m_mixerChannelDeletionWarning, SLOT(toggleMixerChannelDeletionWarning(bool)), false); + m_detachBehaviorComboBox = new QComboBox{guiGroupBox}; + + 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("Detached window behavior"), guiGroupBox}); + guiGroupLayout->addWidget(m_detachBehaviorComboBox); + m_loopMarkerComboBox = new QComboBox{guiGroupBox}; m_loopMarkerComboBox->addItem(tr("Dual-button"), "dual"); @@ -981,6 +995,7 @@ void SetupDialog::accept() 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", @@ -1120,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(); 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; } 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