Detachable windows (#3532)

Allows detaching a window from LMMS's main window, making things like working on multiple screens easier.

The behavior of detached windows can be customized in the Settings.

Closes #1259

---------

Signed-off-by: Dalton Messmer <messmer.dalton@gmail.com>
Co-authored-by: Hyunjin Song <tteu.ingog@gmail.com>
Co-authored-by: Dalton Messmer <messmer.dalton@gmail.com>
Co-authored-by: SpomJ <mihail_a_m@mail.ru>
This commit is contained in:
Lukas W
2026-02-27 06:20:36 +01:00
committed by GitHub
parent 6854a655a4
commit 5d5f319942
50 changed files with 546 additions and 487 deletions

View File

@@ -0,0 +1,6 @@
<svg width="17" height="17" version="1.1" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg">
<g fill="#fff">
<rect x="4" y="11" width="9" height="2"/>
<path d="m4 9 4.5-5 4.5 5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 203 B

View File

@@ -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

View File

@@ -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();

View File

@@ -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

View File

@@ -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();

View File

@@ -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

View File

@@ -73,8 +73,6 @@ public slots:
void moveUp();
void moveDown();
void deletePlugin();
void closeEffects();
signals:
void movedUp(EffectView* view);

View File

@@ -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();

View File

@@ -59,9 +59,6 @@ public slots:
void updateScaleForm();
void updateKeymapForm();
protected:
void closeEvent(QCloseEvent *ce) override;
private slots:
bool loadScaleFromFile();
bool loadKeymapFromFile();

View File

@@ -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

View File

@@ -58,7 +58,6 @@ public:
protected:
void closeEvent( QCloseEvent * _ce ) override;
void setupActions();

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();

View File

@@ -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

View File

@@ -67,6 +67,7 @@ BassBoosterControlDialog::BassBoosterControlDialog( BassBoosterControls* control
tl->addLayout( l );
setLayout( tl );
tl->setSizeConstraint(QLayout::SetFixedSize);
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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 );
}
}

View File

@@ -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();

View File

@@ -50,7 +50,6 @@ class LadspaMatrixControlDialog : public EffectControlDialog
Q_OBJECT
public:
LadspaMatrixControlDialog(LadspaControls* ctl);
bool isResizable() const override;
private slots:
void updateEffectView(LadspaControls* ctl);

View File

@@ -95,6 +95,8 @@ PeakControllerEffectControlDialog::PeakControllerEffectControlDialog(
mainLayout->addLayout( knobLayout );
mainLayout->addLayout( ledLayout );
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
setLayout( mainLayout );
}

View File

@@ -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
} // namespace lmms::gui

View File

@@ -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:

View File

@@ -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 );

View File

@@ -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;

View File

@@ -45,7 +45,6 @@ public:
explicit VecControlsDialog(VecControls *controls);
~VecControlsDialog() override = default;
bool isResizable() const override {return true;}
QSize sizeHint() const override;
private:

View File

@@ -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" ) );

View File

@@ -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;

View File

@@ -23,30 +23,17 @@
*
*/
#include <QCloseEvent>
#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

View File

@@ -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

View File

@@ -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("<b>" + _model->displayName() + "</b>", this);
auto label = new QLabel("<b>" + model->displayName() + "</b>", 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
} // namespace lmms::gui

View File

@@ -23,32 +23,20 @@
*
*/
#include <QCloseEvent>
#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

View File

@@ -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<CaptionMenu> contextMenu = new CaptionMenu( model()->displayName(), this );

View File

@@ -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<QWidget*>{
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<SubWindow*>(w);
if (!win)
{
_w = _w->parentWidget();
// Fall back on parent
win = qobject_cast<SubWindow*>(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<SubWindow*>(_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<SubWindow*>(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<SubWindow*>(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.

View File

@@ -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

View File

@@ -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

View File

@@ -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())

View File

@@ -28,7 +28,6 @@
#include <QAction>
#include <QActionGroup>
#include <QApplication>
#include <QCloseEvent>
#include <QColorDialog>
#include <QComboBox>
#include <QFontDatabase>
@@ -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

View File

@@ -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)
}

View File

@@ -28,25 +28,34 @@
#include "SubWindow.h"
#include <algorithm>
#include <QGraphicsDropShadowEffect>
#include <QGuiApplication>
#include <QLabel>
#include <QLayout>
#include <QMdiArea>
#include <QMetaMethod>
#include <QMoveEvent>
#include <QPainter>
#include <QPushButton>
#include <QStyleOption>
#include <QStyleOptionTitleBar>
#include <QWindow>
#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<QObject*>(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);
}
}

View File

@@ -24,18 +24,15 @@
#include "Editor.h"
#include <QAction>
#include <QShortcut>
#include "DeprecationHelper.h"
#include "GuiApplication.h"
#include "MainWindow.h"
#include "Song.h"
#include "embed.h"
#include <QAction>
#include <QShortcut>
#include <QCloseEvent>
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)
{

View File

@@ -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)
{

View File

@@ -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<SubWindow*>(parentWidget());
const auto targetSubwin = static_cast<SubWindow*>(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.

View File

@@ -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<int>(&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();

View File

@@ -24,6 +24,8 @@
#include "InstrumentTrackView.h"
#include <ranges>
#include <QAction>
#include <QApplication>
#include <QDragEnterEvent>
@@ -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<InstrumentTrackWindow*>(sw->widget()))
{
w = qobject_cast<InstrumentTrackWindow *>( sw->widget() );
return itw;
}
}
return w;
return nullptr;
}

View File

@@ -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());
}

View File

@@ -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 );
}