diff --git a/data/themes/default/detach.svg b/data/themes/default/detach.svg new file mode 100644 index 000000000..337e36608 --- /dev/null +++ b/data/themes/default/detach.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/include/ControllerDialog.h b/include/ControllerDialog.h index 0c53a8c84..a8803de60 100644 --- a/include/ControllerDialog.h +++ b/include/ControllerDialog.h @@ -40,22 +40,10 @@ namespace gui class ControllerDialog : public QWidget, public ModelView { - Q_OBJECT public: - ControllerDialog( Controller * _controller, QWidget * _parent ); - + ControllerDialog(Controller* controller, QWidget* parent); ~ControllerDialog() override = default; - - -signals: - void closed(); - - -protected: - void closeEvent( QCloseEvent * _ce ) override; - -} ; - +}; } // namespace gui diff --git a/include/ControllerRackView.h b/include/ControllerRackView.h index 02f90b623..f54b3e1a1 100644 --- a/include/ControllerRackView.h +++ b/include/ControllerRackView.h @@ -29,7 +29,6 @@ #include "SerializingObject.h" - class QPushButton; class QScrollArea; class QVBoxLayout; @@ -45,7 +44,6 @@ namespace gui class ControllerView; - class ControllerRackView : public QWidget, public SerializingObject { Q_OBJECT @@ -69,9 +67,6 @@ public slots: void addController(Controller* controller); void removeController(Controller* controller); -protected: - void closeEvent( QCloseEvent * _ce ) override; - private slots: void addController(); diff --git a/include/ControllerView.h b/include/ControllerView.h index 9b442672d..03fb1ec32 100644 --- a/include/ControllerView.h +++ b/include/ControllerView.h @@ -64,7 +64,6 @@ public: public slots: void editControls(); void removeController(); - void closeControls(); void renameController(); void moveUp(); void moveDown(); @@ -85,9 +84,7 @@ private: QMdiSubWindow * m_subWindow; ControllerDialog * m_controllerDlg; QLabel * m_nameLabel; - bool m_show; - -} ; +}; } // namespace lmms::gui diff --git a/include/Editor.h b/include/Editor.h index 4687d0448..2141717af 100644 --- a/include/Editor.h +++ b/include/Editor.h @@ -57,8 +57,8 @@ protected: 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; + void keyPressEvent(QKeyEvent* ke) override; + public slots: //! Called by pressing the space key. Plays or stops. void togglePlayStop(); diff --git a/include/EffectControlDialog.h b/include/EffectControlDialog.h index 4bef5ee79..5f475594c 100644 --- a/include/EffectControlDialog.h +++ b/include/EffectControlDialog.h @@ -35,30 +35,18 @@ namespace lmms class EffectControls; - namespace gui { class LMMS_EXPORT EffectControlDialog : public QWidget, public ModelView { - Q_OBJECT public: - EffectControlDialog( EffectControls * _controls ); + EffectControlDialog(EffectControls* controls); ~EffectControlDialog() override = default; - virtual bool isResizable() const {return false;} - - -signals: - void closed(); - - protected: - void closeEvent( QCloseEvent * _ce ) override; - - EffectControls * m_effectControls; - -} ; + EffectControls* m_effectControls; +}; } // namespace gui diff --git a/include/EffectView.h b/include/EffectView.h index cd45b735e..bb9c91498 100644 --- a/include/EffectView.h +++ b/include/EffectView.h @@ -73,8 +73,6 @@ public slots: void moveUp(); void moveDown(); void deletePlugin(); - void closeEffects(); - signals: void movedUp(EffectView* view); diff --git a/include/MainWindow.h b/include/MainWindow.h index 5f0f23f71..40d376fe3 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -141,9 +141,13 @@ public: 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 0706a530f..13d4bbc97 100644 --- a/include/MicrotunerConfig.h +++ b/include/MicrotunerConfig.h @@ -59,9 +59,6 @@ public slots: void updateScaleForm(); void updateKeymapForm(); -protected: - void closeEvent(QCloseEvent *ce) override; - private slots: bool loadScaleFromFile(); bool loadKeymapFromFile(); diff --git a/include/MixerView.h b/include/MixerView.h index 6d9fded64..ac95b3c68 100644 --- a/include/MixerView.h +++ b/include/MixerView.h @@ -44,8 +44,11 @@ namespace lmms namespace lmms::gui { -class LMMS_EXPORT MixerView : public QWidget, public ModelView, - public SerializingObjectHook + +class LMMS_EXPORT MixerView + : public QWidget + , public ModelView + , public SerializingObjectHook { Q_OBJECT public: @@ -94,9 +97,6 @@ public: public slots: int addNewChannel(); -protected: - void closeEvent(QCloseEvent* ce) override; - private slots: void updateFaders(); // TODO This should be improved. Currently the solo and mute models are connected via diff --git a/include/ProjectNotes.h b/include/ProjectNotes.h index 861dcb4a8..daa7436b9 100644 --- a/include/ProjectNotes.h +++ b/include/ProjectNotes.h @@ -58,7 +58,6 @@ public: protected: - void closeEvent( QCloseEvent * _ce ) override; void setupActions(); diff --git a/include/SampleTrackWindow.h b/include/SampleTrackWindow.h index 05e38fd2f..47ce765f1 100644 --- a/include/SampleTrackWindow.h +++ b/include/SampleTrackWindow.h @@ -47,7 +47,7 @@ class SampleTrackWindow : public QWidget, public ModelView, public SerializingOb { Q_OBJECT public: - SampleTrackWindow(SampleTrackView * tv); + SampleTrackWindow(SampleTrackView* stv); ~SampleTrackWindow() override = default; SampleTrack * model() @@ -76,7 +76,7 @@ public slots: protected: // capture close-events for toggling sample-track-button - void closeEvent(QCloseEvent * ce) override; + void closeEvent(QCloseEvent* ce) override; void saveSettings(QDomDocument & doc, QDomElement & element) override; void loadSettings(const QDomElement & element) override; diff --git a/include/SetupDialog.h b/include/SetupDialog.h index a382df917..a82d21ace 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: bool m_MMPZ; bool m_disableBackup; bool m_openLastProject; + QString m_detachBehavior; + QComboBox* m_detachBehaviorComboBox; QString m_loopMarkerMode; QComboBox* m_loopMarkerComboBox; QString m_autoScroll; diff --git a/include/SongEditor.h b/include/SongEditor.h index 932d026be..afbcc5141 100644 --- a/include/SongEditor.h +++ b/include/SongEditor.h @@ -94,7 +94,6 @@ public slots: void selectAllClips( bool select ); protected: - void closeEvent( QCloseEvent * ce ) override; void mousePressEvent(QMouseEvent * me) override; void mouseMoveEvent(QMouseEvent * me) override; void mouseReleaseEvent(QMouseEvent * me) override; diff --git a/include/SubWindow.h b/include/SubWindow.h index 246bbdd9f..a29378fcf 100644 --- a/include/SubWindow.h +++ b/include/SubWindow.h @@ -63,24 +63,34 @@ public: 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. void updateTitleBar(); +public slots: + void detach(); + void attach(); + void setVisible(bool visible) override; + 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; - - QPushButton* addTitleButton(const std::string& iconName, const QString& toolTip); + 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(); @@ -91,6 +101,7 @@ private: QPushButton * m_closeBtn; QPushButton * m_maximizeBtn; QPushButton * m_restoreBtn; + QPushButton* m_detachBtn; QBrush m_activeColor; QColor m_textShadowColor; QColor m_borderColor; @@ -99,6 +110,7 @@ private: 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 9bf0bb649..44c0e3be2 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 fcdf10cc2..93708dbd7 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 b7e2a87e1..7fe79dcc5 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 fbf37923c..2784da35c 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 7433a6b2c..e8d382e52 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/LadspaBrowser/LadspaBrowser.cpp b/plugins/LadspaBrowser/LadspaBrowser.cpp index e6a31e15a..6f2111234 100644 --- a/plugins/LadspaBrowser/LadspaBrowser.cpp +++ b/plugins/LadspaBrowser/LadspaBrowser.cpp @@ -140,18 +140,13 @@ LadspaBrowserView::LadspaBrowserView( ToolPlugin * _tool ) : hlayout->addWidget( ws ); hlayout->addSpacing( 10 ); hlayout->addStretch(); + + layout()->setSizeConstraint(QLayout::SetFixedSize); hide(); if( parentWidget() ) { parentWidget()->hide(); - parentWidget()->layout()->setSizeConstraint( - QLayout::SetFixedSize ); - - Qt::WindowFlags flags = parentWidget()->windowFlags(); - flags |= Qt::MSWindowsFixedSizeDialogHint; - flags &= ~Qt::WindowMaximizeButtonHint; - parentWidget()->setWindowFlags( flags ); } } diff --git a/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp b/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp index f8cfbe3b3..134e0de02 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 fa9a6e1b3..0189c8159 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 7ce05bc45..d89ef02bf 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 3be156396..d507a8792 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 f8d67f1e8..8567561f3 100644 --- a/plugins/SpectrumAnalyzer/SaControlsDialog.h +++ b/plugins/SpectrumAnalyzer/SaControlsDialog.h @@ -48,7 +48,6 @@ public: 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 1440a4be5..e19d7363a 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/TapTempo/TapTempoView.cpp b/plugins/TapTempo/TapTempoView.cpp index 182218cdd..468e951f3 100644 --- a/plugins/TapTempo/TapTempoView.cpp +++ b/plugins/TapTempo/TapTempoView.cpp @@ -128,10 +128,10 @@ TapTempoView::TapTempoView(TapTempo* plugin) }); hide(); + layout()->setSizeConstraint(QLayout::SetFixedSize); if (parentWidget()) { parentWidget()->hide(); - parentWidget()->layout()->setSizeConstraint(QLayout::SetFixedSize); Qt::WindowFlags flags = parentWidget()->windowFlags(); flags |= Qt::MSWindowsFixedSizeDialogHint; diff --git a/plugins/Vectorscope/VecControlsDialog.h b/plugins/Vectorscope/VecControlsDialog.h index 8fee75b4b..400e04ff5 100644 --- a/plugins/Vectorscope/VecControlsDialog.h +++ b/plugins/Vectorscope/VecControlsDialog.h @@ -45,7 +45,6 @@ public: 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 34fd431cd..56dda1eea 100644 --- a/plugins/Vestige/Vestige.cpp +++ b/plugins/Vestige/Vestige.cpp @@ -103,8 +103,9 @@ public: 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; @@ -918,12 +919,9 @@ ManageVestigeInstrumentView::ManageVestigeInstrumentView( Instrument * _instrume widget = new QWidget(this); l = new QGridLayout( this ); - m_vi->m_subWindow = getGUI()->mainWindow()->addWindowedWidget(nullptr, Qt::SubWindow | - Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint); - m_vi->m_subWindow->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::MinimumExpanding ); - m_vi->m_subWindow->setFixedWidth( 960 ); - m_vi->m_subWindow->setMinimumHeight( 300 ); - m_vi->m_subWindow->setWidget(m_vi->m_scrollArea); + m_vi->m_subWindow = getGUI()->mainWindow()->addWindowedWidget(m_vi->m_scrollArea); + m_vi->m_scrollArea->setFixedWidth(960); + m_vi->m_scrollArea->setMinimumHeight(300); m_vi->m_subWindow->setWindowTitle( m_vi->instrumentTrack()->name() + tr( " - VST plugin control" ) ); m_vi->m_subWindow->setWindowIcon( PLUGIN_NAME::getIconPixmap( "logo" ) ); diff --git a/plugins/Vestige/Vestige.h b/plugins/Vestige/Vestige.h index 69833065e..3bdd0644e 100644 --- a/plugins/Vestige/Vestige.h +++ b/plugins/Vestige/Vestige.h @@ -31,6 +31,7 @@ #include "Instrument.h" #include "InstrumentView.h" +#include "SubWindow.h" class QGridLayout; @@ -86,7 +87,7 @@ private: QMutex m_pluginMutex; QString m_pluginDLL; - QMdiSubWindow * m_subWindow; + gui::SubWindow* m_subWindow; QScrollArea * m_scrollArea; FloatModel ** knobFModel; QObject * p_subWindow; diff --git a/src/gui/ControllerDialog.cpp b/src/gui/ControllerDialog.cpp index 344898808..ef4cec2c3 100644 --- a/src/gui/ControllerDialog.cpp +++ b/src/gui/ControllerDialog.cpp @@ -23,30 +23,17 @@ * */ -#include - #include "ControllerDialog.h" + #include "Controller.h" namespace lmms::gui { - -ControllerDialog::ControllerDialog( Controller * _controller, - QWidget * _parent ) : - QWidget( _parent ), - ModelView( _controller, this ) +ControllerDialog::ControllerDialog(Controller* controller, QWidget* parent) + : QWidget{parent} + , ModelView{controller, this} { } - - -void ControllerDialog::closeEvent( QCloseEvent * _ce ) -{ - _ce->ignore(); - emit closed(); -} - - - } // namespace lmms::gui diff --git a/src/gui/ControllerRackView.cpp b/src/gui/ControllerRackView.cpp index a3c504323..508b4197a 100644 --- a/src/gui/ControllerRackView.cpp +++ b/src/gui/ControllerRackView.cpp @@ -45,9 +45,9 @@ namespace lmms::gui { -ControllerRackView::ControllerRackView() : - QWidget(), - m_nextIndex(0) +ControllerRackView::ControllerRackView() + : QWidget{} + , m_nextIndex{0} { setWindowIcon( embed::getIconPixmap( "controller" ) ); setWindowTitle( tr( "Controller Rack" ) ); @@ -79,7 +79,10 @@ ControllerRackView::ControllerRackView() : layout->addWidget( m_addButton ); this->setLayout( layout ); - QMdiSubWindow * subWin = getGUI()->mainWindow()->addWindowedWidget( this ); + SubWindow* subWin = getGUI()->mainWindow()->addWindowedWidget(this); + + setFixedWidth(350); + setMinimumHeight(200); // No maximize button Qt::WindowFlags flags = subWin->windowFlags(); @@ -88,9 +91,6 @@ ControllerRackView::ControllerRackView() : subWin->setAttribute( Qt::WA_DeleteOnClose, false ); subWin->move( 680, 310 ); - subWin->resize( 350, 200 ); - subWin->setFixedWidth( 350 ); - subWin->setMinimumHeight( 200 ); } @@ -229,21 +229,4 @@ void ControllerRackView::addController() setFocus(); } - - - -void ControllerRackView::closeEvent( QCloseEvent * _ce ) - { - if( parentWidget() ) - { - parentWidget()->hide(); - } - else - { - hide(); - } - _ce->ignore(); - } - - } // namespace lmms::gui diff --git a/src/gui/ControllerView.cpp b/src/gui/ControllerView.cpp index 7f7c4729c..adf2d2eb7 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 ); + setFrameStyle(QFrame::StyledPanel); + 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/EffectControlDialog.cpp b/src/gui/EffectControlDialog.cpp index 1e0fbb802..6a624e4dc 100644 --- a/src/gui/EffectControlDialog.cpp +++ b/src/gui/EffectControlDialog.cpp @@ -23,32 +23,20 @@ * */ -#include - #include "EffectControlDialog.h" + #include "EffectControls.h" namespace lmms::gui { - -EffectControlDialog::EffectControlDialog( EffectControls * _controls ) : - QWidget( nullptr ), - ModelView( _controls, this ), - m_effectControls( _controls ) +EffectControlDialog::EffectControlDialog(EffectControls* controls) + : QWidget{nullptr} + , ModelView{controls, this} + , m_effectControls{controls} { - setWindowTitle( m_effectControls->effect()->displayName() ); - setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred ); + setWindowTitle(m_effectControls->effect()->displayName()); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); } - - - -void EffectControlDialog::closeEvent( QCloseEvent * _ce ) -{ - _ce->ignore(); - emit closed(); -} - - } // namespace lmms::gui diff --git a/src/gui/EffectView.cpp b/src/gui/EffectView.cpp index 428035b95..5db377de4 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 7e9c16ab9..73c477647 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -329,15 +329,8 @@ void MainWindow::finalize() m_redoAction = addAction(edit_menu, "edit_redo", tr("Redo"), QKeySequence::Redo, &MainWindow::redo); - // Ensure that both (Ctrl+Y) and (Ctrl+Shift+Z) activate redo shortcut regardless of OS defaults - if (QKeySequence(QKeySequence::Redo) != keySequence(Qt::CTRL, Qt::Key_Y)) - { - new QShortcut(keySequence(Qt::CTRL, Qt::Key_Y), this, SLOT(redo())); - } - if (QKeySequence(QKeySequence::Redo) != keySequence(Qt::CTRL, Qt::SHIFT, Qt::Key_Z)) - { - new QShortcut(keySequence(Qt::CTRL, Qt::SHIFT, Qt::Key_Z), this, SLOT(redo())); - } + m_undoAction->setShortcutContext(Qt::ApplicationShortcut); + m_redoAction->setShortcutContext(Qt::ApplicationShortcut); edit_menu->addSeparator(); edit_menu->addAction(embed::getIconPixmap("microtuner"), tr("Scales and keymaps"), @@ -353,6 +346,7 @@ void MainWindow::finalize() this, SLOT(updateViewMenu())); connect( m_viewMenu, SIGNAL(triggered(QAction*)), this, SLOT(updateConfig(QAction*))); + updateViewMenu(); m_toolsMenu = new QMenu( this ); @@ -493,18 +487,10 @@ void MainWindow::finalize() } // Add editor subwindows - for (QWidget* widget : std::list{ - getGUI()->automationEditor(), - getGUI()->patternEditor(), - getGUI()->pianoRoll(), - getGUI()->songEditor() - }) - { - QMdiSubWindow* window = addWindowedWidget(widget); - window->setWindowIcon(widget->windowIcon()); - window->setAttribute(Qt::WA_DeleteOnClose, false); - window->resize(widget->sizeHint()); - } + addWindowedWidget(getGUI()->automationEditor()); + addWindowedWidget(getGUI()->patternEditor()); + addWindowedWidget(getGUI()->pianoRoll()); + addWindowedWidget(getGUI()->songEditor()); getGUI()->automationEditor()->parentWidget()->hide(); getGUI()->patternEditor()->parentWidget()->move(610, 5); @@ -552,19 +538,35 @@ 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 && w->sizeHint().isValid()) { - auto titleBarHeight = win->titleBarHeight(); - auto frameWidth = win->frameWidth(); - QSize delta(2* frameWidth, titleBarHeight + frameWidth); - win->resize(delta + w->sizeHint()); + + if (w) + { + // TODO: somehow make this work on any setWidget + connect(w, &QWidget::destroyed, win, &SubWindow::deleteLater); + + if (w->sizeHint().isValid()) + { + auto titleBarHeight = win->titleBarHeight(); + auto frameWidth = win->frameWidth(); + QSize delta(2* frameWidth, titleBarHeight + frameWidth); + win->resize(delta + w->sizeHint()); + } } + m_workspace->addSubWindow(win); return win; } + +void MainWindow::setAllSubWindowsDetached(bool detached) +{ + emit detachAllSubWindows(detached); +} + + void MainWindow::resetWindowTitle() { QString title(tr( "Untitled" )); @@ -659,77 +661,80 @@ 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" ) ) + // TODO: Only use one of these + auto win = qobject_cast(w); + if (!win) { - _w = _w->parentWidget(); + // Fall back on parent + win = qobject_cast(w->parentWidget()); + if (!win) + { + // Still could not find the window - soft fail + return; + } } - // 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(); + de.setAttribute("visible", bool{win->widget() && win->widget()->isVisible()}); + de.setAttribute("maximized", win->isMaximized()); - 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() ); + 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 + auto win = qobject_cast(w); + if (!win) { - // 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" ) ) + // Fall back on parent + win = qobject_cast(w->parentWidget()); + if (!win) { - _w = _w->parentWidget(); + // Still could not find the window - soft fail + return; } - // first restore the window, as attempting to resize a maximized window causes graphics glitching - _w->setWindowState( _w->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() ); + const auto normalGeometry = QRect { + 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 + win->setWindowState(win->windowState() & ~(Qt::WindowMaximized | Qt::WindowMinimized)); + + 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 ); + Qt::WindowStates winState = win->windowState(); + winState = de.attribute("maximized").toInt() + ? (winState | Qt::WindowMaximized) + : (winState & ~Qt::WindowMaximized); + win->setWindowState(winState); + } - _w->setVisible( _de.attribute( "visible" ).toInt() ); + if (const auto visible = de.attribute("visible"); !visible.isEmpty()) + { + win->setVisible(visible.toInt()); } } + void MainWindow::emptySlot() { } @@ -1086,6 +1091,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 4dbb6064d..59f293dab 100644 --- a/src/gui/MicrotunerConfig.cpp +++ b/src/gui/MicrotunerConfig.cpp @@ -192,13 +192,10 @@ MicrotunerConfig::MicrotunerConfig() : this->setLayout(microtunerLayout); // Add to the main window and setup fixed size etc. - QMdiSubWindow *subWin = getGUI()->mainWindow()->addWindowedWidget(this); - + SubWindow* subWin = getGUI()->mainWindow()->addWindowedWidget(this); subWin->setAttribute(Qt::WA_DeleteOnClose, false); - subWin->setMinimumWidth(300); - subWin->setMinimumHeight(300); - subWin->setMaximumWidth(500); - subWin->setMaximumHeight(700); + setMinimumSize(300, 300); + setMaximumSize(500, 700); subWin->hide(); // No maximize button @@ -654,12 +651,4 @@ void MicrotunerConfig::loadSettings(const QDomElement &element) } -void MicrotunerConfig::closeEvent(QCloseEvent *ce) -{ - if (parentWidget()) {parentWidget()->hide();} - else {hide();} - ce->ignore(); -} - - } // namespace lmms::gui diff --git a/src/gui/MidiCCRackView.cpp b/src/gui/MidiCCRackView.cpp index efeb46d46..86271a252 100644 --- a/src/gui/MidiCCRackView.cpp +++ b/src/gui/MidiCCRackView.cpp @@ -49,7 +49,7 @@ MidiCCRackView::MidiCCRackView(InstrumentTrack * track) : setWindowIcon(embed::getIconPixmap("midi_cc_rack")); setWindowTitle(tr("MIDI CC Rack - %1").arg(m_track->name())); - QMdiSubWindow * subWin = getGUI()->mainWindow()->addWindowedWidget(this); + SubWindow* subWin = getGUI()->mainWindow()->addWindowedWidget(this); // Remove maximize button Qt::WindowFlags flags = subWin->windowFlags(); @@ -58,9 +58,8 @@ MidiCCRackView::MidiCCRackView(InstrumentTrack * track) : // Adjust window attributes, sizing and position subWin->setAttribute(Qt::WA_DeleteOnClose, false); - subWin->resize(350, 300); - subWin->setFixedWidth(350); - subWin->setMinimumHeight(300); + setFixedWidth(350); + setMinimumHeight(300); subWin->hide(); // Main window layout diff --git a/src/gui/MixerView.cpp b/src/gui/MixerView.cpp index ef1f35a20..dc1626d5a 100644 --- a/src/gui/MixerView.cpp +++ b/src/gui/MixerView.cpp @@ -162,10 +162,10 @@ MixerView::MixerView(Mixer* mixer) : // timer for updating faders connect(mainWindow, &MainWindow::periodicUpdate, this, &MixerView::updateFaders); - // add ourself to workspace - QMdiSubWindow* subWin = mainWindow->addWindowedWidget(this); layout()->setSizeConstraint(QLayout::SetMinimumSize); - subWin->layout()->setSizeConstraint(QLayout::SetMinAndMaxSize); + + // add ourself to workspace + mainWindow->addWindowedWidget(this); parentWidget()->setAttribute(Qt::WA_DeleteOnClose, false); parentWidget()->move(5, 310); @@ -532,21 +532,6 @@ void MixerView::keyPressEvent(QKeyEvent * e) -void MixerView::closeEvent(QCloseEvent * ce) - { - if (parentWidget()) - { - parentWidget()->hide(); - } - else - { - hide(); - } - ce->ignore(); - } - - - void MixerView::setCurrentMixerChannel(int channel) { if (channel >= 0 && channel < m_mixerChannelViews.size()) diff --git a/src/gui/ProjectNotes.cpp b/src/gui/ProjectNotes.cpp index 8cbe18d87..56d694969 100644 --- a/src/gui/ProjectNotes.cpp +++ b/src/gui/ProjectNotes.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include @@ -50,8 +49,8 @@ namespace lmms::gui { -ProjectNotes::ProjectNotes() : - QMainWindow( getGUI()->mainWindow()->workspace() ) +ProjectNotes::ProjectNotes() + : QMainWindow{getGUI()->mainWindow()->workspace()} { m_edit = new QTextEdit( this ); m_edit->setAutoFillBackground( true ); @@ -391,20 +390,4 @@ void ProjectNotes::loadSettings( const QDomElement & _this ) m_edit->setHtml( _this.text() ); } - - - -void ProjectNotes::closeEvent( QCloseEvent * _ce ) -{ - if( parentWidget() ) - { - parentWidget()->hide(); - } - else - { - hide(); - } - _ce->ignore(); - } - } // namespace lmms::gui diff --git a/src/gui/SampleTrackWindow.cpp b/src/gui/SampleTrackWindow.cpp index e0fea45ce..872e7ad20 100644 --- a/src/gui/SampleTrackWindow.cpp +++ b/src/gui/SampleTrackWindow.cpp @@ -47,11 +47,11 @@ namespace lmms::gui { -SampleTrackWindow::SampleTrackWindow(SampleTrackView * tv) : - QWidget(), - ModelView(nullptr, this), - m_track(tv->model()), - m_stv(tv) +SampleTrackWindow::SampleTrackWindow(SampleTrackView* stv) + : QWidget{} + , ModelView{nullptr, this} + , m_track{stv->model()} + , m_stv{stv} { // init own layout + widgets setFocusPolicy(Qt::StrongFocus); @@ -148,29 +148,25 @@ SampleTrackWindow::SampleTrackWindow(SampleTrackView * tv) : generalSettingsLayout->addLayout(basicControlsLayout); - m_effectRack = new EffectRackView(tv->model()->audioBusHandle()->effects()); + m_effectRack = new EffectRackView(stv->model()->audioBusHandle()->effects()); m_effectRack->setFixedSize(EffectRackView::DEFAULT_WIDTH, 242); vlayout->addWidget(generalSettingsWidget); vlayout->addWidget(m_effectRack); - setModel(tv->model()); + setModel(stv->model()); 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); - // 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 + // better than `setFixedSize` because it still responds to layout changes + layout()->setSizeConstraint(QLayout::SetFixedSize); - subWin->setWindowIcon(embed::getIconPixmap("sample_track")); - subWin->setFixedSize(subWin->size()); + setWindowIcon(embed::getIconPixmap("sample_track")); subWin->hide(); } @@ -247,27 +243,15 @@ void SampleTrackWindow::toggleVisibility(bool on) void SampleTrackWindow::closeEvent(QCloseEvent* ce) { - ce->ignore(); - - if(getGUI()->mainWindow()->workspace()) - { - parentWidget()->hide(); - } - else - { - hide(); - } - m_stv->setFocus(); m_stv->m_tlb->setChecked(false); } -void SampleTrackWindow::saveSettings(QDomDocument& doc, QDomElement & element) +void SampleTrackWindow::saveSettings(QDomDocument& doc, QDomElement& element) { MainWindow::saveWidgetState(this, element); - Q_UNUSED(element) } diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index a9a652e98..d65964048 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -28,25 +28,34 @@ #include "SubWindow.h" +#include + #include +#include #include +#include #include +#include #include #include #include +#include #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 @@ -57,15 +66,27 @@ SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags windowFlags) : m_textShadowColor = Qt::black; m_borderColor = Qt::black; - // close, maximize and restore (after maximizing) buttons - m_closeBtn = addTitleButton("close", tr("Close")); - connect( m_closeBtn, SIGNAL(clicked(bool)), this, SLOT(close())); + // close, maximize, restore, and detach buttons + auto createButton = [this](std::string_view iconName, const QString& tooltip) -> QPushButton* { + auto button = new QPushButton{embed::getIconPixmap(iconName), QString{}, this}; + button->resize(m_buttonSize); + button->setFocusPolicy(Qt::NoFocus); + button->setCursor(Qt::ArrowCursor); + button->setAttribute(Qt::WA_NoMousePropagation); + button->setToolTip(tooltip); + return button; + }; + m_closeBtn = createButton("close", tr("Close")); + connect(m_closeBtn, &QPushButton::clicked, this, &QWidget::close); - m_maximizeBtn = addTitleButton("maximize", tr("Maximize")); - connect( m_maximizeBtn, SIGNAL(clicked(bool)), this, SLOT(showMaximized())); + m_maximizeBtn = createButton("maximize", tr("Maximize")); + connect(m_maximizeBtn, &QPushButton::clicked, this, &QWidget::showMaximized); - m_restoreBtn = addTitleButton("restore", tr("Restore")); - connect( m_restoreBtn, SIGNAL(clicked(bool)), this, SLOT(showNormal())); + m_restoreBtn = createButton("restore", tr("Restore")); + connect(m_restoreBtn, &QPushButton::clicked, this, &QWidget::showNormal); + + m_detachBtn = createButton("detach", tr("Detach")); + connect(m_detachBtn, &QPushButton::clicked, this, &SubWindow::detach); // QLabel for the window title and the shadow effect m_shadow = new QGraphicsDropShadowEffect(); @@ -78,6 +99,8 @@ SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags windowFlags) : m_windowTitle->setAttribute( Qt::WA_TransparentForMouseEvents, true ); m_windowTitle->setGraphicsEffect( m_shadow ); + layout()->setSizeConstraint(QLayout::SetMinAndMaxSize); + // Disable the minimize button and make sure that the custom window hint is set setWindowFlags((this->windowFlags() & ~Qt::WindowMinimizeButtonHint) | Qt::CustomizeWindowHint); @@ -139,7 +162,60 @@ void SubWindow::changeEvent( QEvent *event ) { adjustTitleBar(); } +} + + + +void SubWindow::setVisible(bool visible) +{ + 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()->setWindowState((widget()->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); + } +} + + + + +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); +} + + + + +void SubWindow::setDetached(bool on) +{ + if (on) { detach(); } + else { attach(); } } @@ -228,6 +304,73 @@ void SubWindow::setBorderColor( const QColor &c ) +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::SubWindow; + flags |= Qt::WindowMinimizeButtonHint; + + hide(); + widget()->setWindowFlags(flags); + + if (shown) { widget()->show(); } + + widget()->move(pos); +} + +void SubWindow::attach() +{ + if (!isDetached()) { return; } + + const bool shown = widget()->isVisible(); + + auto frame = widget()->geometry(); + frame.moveTo(mdiArea()->mapFromGlobal(frame.topLeft())); + frame += decorationMargins(); + + // 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::SubWindow; + flags &= ~Qt::WindowMinimizeButtonHint; + widget()->setWindowFlags(flags); + + if (shown) + { + widget()->show(); + show(); + } + + if (QGuiApplication::platformName() == "wayland") + { + // Workaround for wayland reporting position as 0-0 + // See https://doc.qt.io/qt-6.9/application-windows.html#wayland-peculiarities + resize(frame.size()); + } + else + { + setGeometry(frame); + } +} + + + int SubWindow::titleBarHeight() const { QStyleOptionTitleBar so; @@ -246,6 +389,21 @@ int SubWindow::frameWidth() const } + + +QMargins SubWindow::decorationMargins() const +{ + return { + frameWidth(), // left + titleBarHeight(), // top + frameWidth(), // right + frameWidth() // bottom + }; +} + + + + void SubWindow::updateTitleBar() { adjustTitleBar(); @@ -306,15 +464,15 @@ void SubWindow::adjustTitleBar() // button adjustments m_maximizeBtn->hide(); m_restoreBtn->hide(); + m_detachBtn->hide(); m_closeBtn->show(); const int rightSpace = 3; const int buttonGap = 1; const int menuButtonSpace = 24; - QPoint rightButtonPos( width() - rightSpace - m_buttonSize.width(), 3 ); - QPoint middleButtonPos( width() - rightSpace - ( 2 * m_buttonSize.width() ) - buttonGap, 3 ); - QPoint leftButtonPos( width() - rightSpace - ( 3 * m_buttonSize.width() ) - ( 2 * buttonGap ), 3 ); + auto buttonPos = QPoint{width() - rightSpace - m_buttonSize.width(), 3}; + const auto buttonStep = QPoint{m_buttonSize.width() + buttonGap, 0}; // the buttonBarWidth depends on the number of buttons. // we need it to calculate the width of window title label @@ -322,24 +480,36 @@ void SubWindow::adjustTitleBar() // set the buttons on their positions. // the close button is always needed and on the rightButtonPos - m_closeBtn->move( rightButtonPos ); + m_closeBtn->move(buttonPos); + buttonPos -= buttonStep; // here we ask: is the Subwindow maximizable and // then we set the buttons and show them if needed if( windowFlags() & Qt::WindowMaximizeButtonHint ) { buttonBarWidth = buttonBarWidth + m_buttonSize.width() + buttonGap; - m_maximizeBtn->move( middleButtonPos ); - m_restoreBtn->move( middleButtonPos ); - m_maximizeBtn->setVisible(true); + m_maximizeBtn->move(buttonPos); + m_restoreBtn->move(buttonPos); + if (!isMaximized()) + { + m_maximizeBtn->show(); + buttonPos -= buttonStep; + } } // we're keeping the restore button around if we open projects // from older versions that have saved minimized windows - m_restoreBtn->setVisible(isMinimized()); - if( isMinimized() ) + if (isMinimized()) { - m_restoreBtn->move( m_maximizeBtn->isHidden() ? middleButtonPos : leftButtonPos ); + m_restoreBtn->show(); + buttonPos -= buttonStep; + } + + if (isDetachable()) + { + m_detachBtn->move(buttonPos); + m_detachBtn->show(); + buttonBarWidth = buttonBarWidth + m_buttonSize.width() + buttonGap; } if( widget() ) @@ -412,16 +582,63 @@ void SubWindow::resizeEvent( QResizeEvent * event ) } } -QPushButton* SubWindow::addTitleButton(const std::string& iconName, const QString& toolTip) -{ - auto button = new QPushButton(embed::getIconPixmap(iconName), QString(), this); - button->resize(m_buttonSize); - button->setFocusPolicy(Qt::NoFocus); - button->setCursor(Qt::ArrowCursor); - button->setAttribute(Qt::WA_NoMousePropagation); - button->setToolTip(toolTip); - return button; + + +/** + * @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())) + { + return QMdiSubWindow::eventFilter(obj, event); + } + + switch (event->type()) + { + 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 2b2c1534c..92c778cd2 100644 --- a/src/gui/editors/Editor.cpp +++ b/src/gui/editors/Editor.cpp @@ -24,18 +24,15 @@ #include "Editor.h" +#include +#include + #include "DeprecationHelper.h" #include "GuiApplication.h" #include "MainWindow.h" #include "Song.h" - #include "embed.h" -#include -#include -#include - - namespace lmms::gui { @@ -92,6 +89,7 @@ void Editor::toggleMaximize() } Editor::Editor(bool record, bool stepRecord) : + QMainWindow(), m_toolBar(new DropToolBar(this)), m_playAction(nullptr), m_recordAction(nullptr), @@ -141,22 +139,8 @@ QAction *Editor::playAction() const return m_playAction; } -void Editor::closeEvent(QCloseEvent * event) +void Editor::keyPressEvent(QKeyEvent* ke) { - if( parentWidget() ) - { - parentWidget()->hide(); - } - else - { - hide(); - } - getGUI()->mainWindow()->refocus(); - event->ignore(); - } - - void Editor::keyPressEvent(QKeyEvent *ke) - { if (ke->key() == Qt::Key_Space) { if (ke->modifiers() & Qt::ShiftModifier) @@ -170,7 +154,7 @@ void Editor::closeEvent(QCloseEvent * event) return; } ke->ignore(); - } +} DropToolBar::DropToolBar(QWidget* parent) : QToolBar(parent) { diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index 55400cd3f..2abde6b7d 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -572,21 +572,6 @@ void SongEditor::wheelEvent( QWheelEvent * we ) -void SongEditor::closeEvent( QCloseEvent * ce ) -{ - if( parentWidget() ) - { - parentWidget()->hide(); - } - else - { - hide(); - } - ce->ignore(); -} - - - void SongEditor::mousePressEvent(QMouseEvent *me) { diff --git a/src/gui/instrument/InstrumentTrackWindow.cpp b/src/gui/instrument/InstrumentTrackWindow.cpp index 2b5f71e6e..71d2eadd8 100644 --- a/src/gui/instrument/InstrumentTrackWindow.cpp +++ b/src/gui/instrument/InstrumentTrackWindow.cpp @@ -293,7 +293,6 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : // we can reuse this method. updateSubWindow(); - subWin->setWindowIcon(embed::getIconPixmap("instrument_track")); subWin->hide(); } @@ -530,6 +529,7 @@ void InstrumentTrackWindow::toggleVisibility( bool on ) else { parentWidget()->hide(); + hide(); } } @@ -538,17 +538,6 @@ void InstrumentTrackWindow::toggleVisibility( bool on ) void InstrumentTrackWindow::closeEvent( QCloseEvent* event ) { - event->ignore(); - - if( getGUI()->mainWindow()->workspace() ) - { - parentWidget()->hide(); - } - else - { - hide(); - } - m_itv->setFocus(); m_itv->m_tlb->setChecked(false); } @@ -620,6 +609,7 @@ void InstrumentTrackWindow::dropEvent( QDropEvent* event ) event->accept(); setFocus(); } + updateSubWindow(); } @@ -679,13 +669,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) { + const auto sourceSubwin = static_cast(parentWidget()); + const auto targetSubwin = static_cast(newView->getInstrumentTrackWindow()->parentWidget()); + QWidget* sourceWidget; + QWidget* targetWidget; + + // set widgets we move and get our position from + if (sourceSubwin->isDetached()) + { + sourceWidget = this; + targetWidget = newView->getInstrumentTrackWindow(); + } + else + { + sourceWidget = parentWidget(); + targetWidget = 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 = sourceWidget->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); + + // sync detached state with current widget like we do with position + targetSubwin->setDetached(sourceSubwin->isDetached()); + + targetWidget->move(curPos); // scroll the SongEditor/PatternEditor to make sure the new trackview label is visible bringToFront->trackContainerView()->scrollToTrackView(bringToFront); @@ -695,13 +706,8 @@ void InstrumentTrackWindow::viewInstrumentInDirection(int d) } Q_ASSERT(bringToFront); bringToFront->getInstrumentTrackWindow()->setFocus(); - Qt::WindowFlags flags = windowFlags(); - if (!m_instrumentView->isResizable()) { - flags |= Qt::MSWindowsFixedSizeDialogHint; - } else { - flags &= ~Qt::MSWindowsFixedSizeDialogHint; - } - setWindowFlags( flags ); + + updateSubWindow(); } void InstrumentTrackWindow::viewNextInstrument() @@ -748,24 +754,19 @@ 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); - subWindow->setMaximumSize(m_instrumentView->maximumSize() + extraSpace); - subWindow->setMinimumSize(m_instrumentView->minimumSize() + extraSpace); - - flags &= ~Qt::MSWindowsFixedSizeDialogHint; - flags |= Qt::WindowMaximizeButtonHint; + setMaximumSize(m_instrumentView->maximumSize() + extraSpace); + setMinimumSize(m_instrumentView->minimumSize() + extraSpace); } 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 // as normal, i.e. not maximized, if the instrument view is not resizable. @@ -775,13 +776,8 @@ void InstrumentTrackWindow::updateSubWindow() } } - subWindow->setWindowFlags(flags); + subWindow->setWindowFlag(Qt::WindowMaximizeButtonHint, instrumentViewResizable); - // 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 - // 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 // missing some implementation details that QMdiSubWindow has. diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 258a176ad..a6ab7a76a 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -119,6 +119,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_autoScroll(ConfigManager::inst()->value("ui", "autoscroll", "stepped")), m_lang(ConfigManager::inst()->value( @@ -257,6 +258,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"); @@ -991,6 +1005,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", "autoscroll", m_autoScroll); @@ -1131,6 +1146,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 c0cba7eef..782b4e6a6 100644 --- a/src/gui/tracks/InstrumentTrackView.cpp +++ b/src/gui/tracks/InstrumentTrackView.cpp @@ -24,6 +24,8 @@ #include "InstrumentTrackView.h" +#include + #include #include #include @@ -205,20 +207,21 @@ void InstrumentTrackView::toggleMidiCCRack() -InstrumentTrackWindow * InstrumentTrackView::topLevelInstrumentTrackWindow() +InstrumentTrackWindow* InstrumentTrackView::topLevelInstrumentTrackWindow() { - InstrumentTrackWindow * w = nullptr; - for( const QMdiSubWindow * sw : - getGUI()->mainWindow()->workspace()->subWindowList( - QMdiArea::ActivationHistoryOrder ) ) + const auto subWindows = getGUI()->mainWindow()->workspace()->subWindowList(QMdiArea::ActivationHistoryOrder); + + for (QMdiSubWindow* sw : subWindows | std::views::reverse) { - if( sw->isVisible() && sw->widget()->inherits( "lmms::gui::InstrumentTrackWindow" ) ) + if (!sw->widget() || !sw->widget()->isVisible()) { continue; } + + if (auto itw = qobject_cast(sw->widget())) { - w = qobject_cast( sw->widget() ); + return itw; } } - return w; + return nullptr; } diff --git a/src/gui/widgets/TempoSyncBarModelEditor.cpp b/src/gui/widgets/TempoSyncBarModelEditor.cpp index d524e5844..628e470d8 100644 --- a/src/gui/widgets/TempoSyncBarModelEditor.cpp +++ b/src/gui/widgets/TempoSyncBarModelEditor.cpp @@ -205,11 +205,11 @@ void TempoSyncBarModelEditor::showCustom() if(m_custom == nullptr) { m_custom = new MeterDialog(getGUI()->mainWindow()->workspace()); - QMdiSubWindow * subWindow = getGUI()->mainWindow()->addWindowedWidget(m_custom); + SubWindow* subWindow = getGUI()->mainWindow()->addWindowedWidget(m_custom); Qt::WindowFlags flags = subWindow->windowFlags(); flags &= ~Qt::WindowMaximizeButtonHint; subWindow->setWindowFlags(flags); - subWindow->setFixedSize(subWindow->size()); + setFixedSize(size()); m_custom->setWindowTitle("Meter"); m_custom->setModel(&model()->getCustomMeterModel()); } diff --git a/src/gui/widgets/TempoSyncKnob.cpp b/src/gui/widgets/TempoSyncKnob.cpp index 895e5bd01..3ed2a1100 100644 --- a/src/gui/widgets/TempoSyncKnob.cpp +++ b/src/gui/widgets/TempoSyncKnob.cpp @@ -315,11 +315,11 @@ void TempoSyncKnob::showCustom() if( m_custom == nullptr ) { m_custom = new MeterDialog( getGUI()->mainWindow()->workspace() ); - QMdiSubWindow * subWindow = getGUI()->mainWindow()->addWindowedWidget( m_custom ); + SubWindow* subWindow = getGUI()->mainWindow()->addWindowedWidget(m_custom); Qt::WindowFlags flags = subWindow->windowFlags(); flags &= ~Qt::WindowMaximizeButtonHint; subWindow->setWindowFlags( flags ); - subWindow->setFixedSize( subWindow->size() ); + setFixedSize(size()); m_custom->setWindowTitle( "Meter" ); m_custom->setModel( &model()->m_custom ); }