657 lines
17 KiB
C++
657 lines
17 KiB
C++
/*
|
|
* SPDX-License-Identifier: GPL-3.0-only
|
|
* MuseScore-CLA-applies
|
|
*
|
|
* MuseScore
|
|
* Music Composition & Notation
|
|
*
|
|
* Copyright (C) 2021 MuseScore BVBA and others
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 3 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "dockwindow.h"
|
|
|
|
#include "thirdparty/KDDockWidgets/src/DockWidgetQuick.h"
|
|
#include "thirdparty/KDDockWidgets/src/LayoutSaver.h"
|
|
#include "thirdparty/KDDockWidgets/src/private/quick/MainWindowQuick_p.h"
|
|
#include "thirdparty/KDDockWidgets/src/private/DockRegistry_p.h"
|
|
#include "thirdparty/KDDockWidgets/src/Config.h"
|
|
|
|
#include "dockcentralview.h"
|
|
#include "dockpageview.h"
|
|
#include "dockpanelview.h"
|
|
#include "dockstatusbarview.h"
|
|
#include "docktoolbarview.h"
|
|
#include "dockingholderview.h"
|
|
#include "dockwindow.h"
|
|
|
|
#include "async/async.h"
|
|
#include "log.h"
|
|
|
|
using namespace mu::dock;
|
|
using namespace mu::async;
|
|
|
|
namespace mu::dock {
|
|
static const QList<Location> POSSIBLE_LOCATIONS {
|
|
Location::Left,
|
|
Location::Right,
|
|
Location::Top,
|
|
Location::Bottom
|
|
};
|
|
|
|
static KDDockWidgets::Location locationToKLocation(Location location)
|
|
{
|
|
switch (location) {
|
|
case Location::Left: return KDDockWidgets::Location_OnLeft;
|
|
case Location::Right: return KDDockWidgets::Location_OnRight;
|
|
case Location::Top: return KDDockWidgets::Location_OnTop;
|
|
case Location::Bottom: return KDDockWidgets::Location_OnBottom;
|
|
case Location::Center: break;
|
|
case Location::Undefined: break;
|
|
}
|
|
|
|
return KDDockWidgets::Location_None;
|
|
}
|
|
|
|
static void clearRegistry()
|
|
{
|
|
TRACEFUNC;
|
|
|
|
auto registry = KDDockWidgets::DockRegistry::self();
|
|
|
|
registry->clear();
|
|
|
|
for (KDDockWidgets::DockWidgetBase* dock : registry->dockwidgets()) {
|
|
registry->unregisterDockWidget(dock);
|
|
}
|
|
}
|
|
}
|
|
|
|
DockWindow::DockWindow(QQuickItem* parent)
|
|
: QQuickItem(parent),
|
|
m_toolBars(this),
|
|
m_pages(this)
|
|
{
|
|
}
|
|
|
|
DockWindow::~DockWindow()
|
|
{
|
|
dockWindowProvider()->deinit();
|
|
}
|
|
|
|
void DockWindow::componentComplete()
|
|
{
|
|
TRACEFUNC;
|
|
|
|
QQuickItem::componentComplete();
|
|
|
|
m_mainWindow = new KDDockWidgets::MainWindowQuick("mainWindow",
|
|
KDDockWidgets::MainWindowOption_None,
|
|
this);
|
|
|
|
connect(qApp, &QCoreApplication::aboutToQuit, this, &DockWindow::onQuit);
|
|
}
|
|
|
|
void DockWindow::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry)
|
|
{
|
|
if (!m_currentPage) {
|
|
QQuickItem::geometryChanged(newGeometry, oldGeometry);
|
|
return;
|
|
}
|
|
|
|
//! NOTE: it is important to reset the current minimum width for all top-level toolbars
|
|
//! Otherwise, the window content can be displaced after LayoutWidget::onResize(QSize newSize)
|
|
//! due to lack of free space
|
|
QList<DockToolBarView*> topToolBars = topLevelToolBars(m_currentPage);
|
|
for (DockToolBarView* toolBar : topToolBars) {
|
|
toolBar->setMinimumWidth(toolBar->contentWidth());
|
|
}
|
|
|
|
QQuickItem::geometryChanged(newGeometry, oldGeometry);
|
|
|
|
alignToolBars(m_currentPage);
|
|
}
|
|
|
|
void DockWindow::onQuit()
|
|
{
|
|
TRACEFUNC;
|
|
|
|
IF_ASSERT_FAILED(m_currentPage) {
|
|
return;
|
|
}
|
|
|
|
savePageState(m_currentPage->objectName());
|
|
|
|
clearRegistry();
|
|
|
|
saveGeometry();
|
|
}
|
|
|
|
QString DockWindow::currentPageUri() const
|
|
{
|
|
return m_currentPage ? m_currentPage->uri() : QString();
|
|
}
|
|
|
|
QQmlListProperty<mu::dock::DockToolBarView> DockWindow::toolBarsProperty()
|
|
{
|
|
return m_toolBars.property();
|
|
}
|
|
|
|
QQmlListProperty<mu::dock::DockPageView> DockWindow::pagesProperty()
|
|
{
|
|
return m_pages.property();
|
|
}
|
|
|
|
void DockWindow::init()
|
|
{
|
|
clearRegistry();
|
|
|
|
#ifdef Q_OS_MACOS
|
|
/*! TODO: restoring of the window geometry is temporarily disabled for macOS
|
|
* because it has a problem with saving a normal geometry of main window on KDDockWidgets
|
|
* see https://github.com/KDAB/KDDockWidgets/pull/273
|
|
*/
|
|
#else
|
|
restoreGeometry();
|
|
#endif
|
|
|
|
dockWindowProvider()->init(this);
|
|
|
|
uiConfiguration()->windowGeometryChanged().onNotify(this, [this]() {
|
|
reloadCurrentPage();
|
|
});
|
|
|
|
workspaceManager()->currentWorkspaceAboutToBeChanged().onNotify(this, [this]() {
|
|
if (const DockPageView* page = currentPage()) {
|
|
savePageState(page->objectName());
|
|
}
|
|
});
|
|
|
|
Async::call(this, [this]() {
|
|
startupScenario()->run();
|
|
});
|
|
}
|
|
|
|
void DockWindow::loadPage(const QString& uri, const QVariantMap& params)
|
|
{
|
|
TRACEFUNC;
|
|
|
|
if (currentPageUri() == uri) {
|
|
if (m_currentPage) {
|
|
m_currentPage->setParams(params);
|
|
}
|
|
return;
|
|
}
|
|
|
|
bool isFirstOpening = (m_currentPage == nullptr);
|
|
|
|
if (!isFirstOpening) {
|
|
savePageState(m_currentPage->objectName());
|
|
clearRegistry();
|
|
m_currentPage->setVisible(false);
|
|
}
|
|
|
|
bool ok = doLoadPage(uri, params);
|
|
if (!ok) {
|
|
return;
|
|
}
|
|
|
|
emit currentPageUriChanged(uri);
|
|
|
|
if (isFirstOpening) {
|
|
if (!m_hasGeometryBeenRestored
|
|
|| (m_mainWindow->windowHandle()->windowStates() & QWindow::FullScreen)) {
|
|
//! NOTE: show window as maximized if no geometry has been restored
|
|
//! or if the user had closed app in FullScreen mode
|
|
m_mainWindow->windowHandle()->showMaximized();
|
|
} else {
|
|
m_mainWindow->windowHandle()->setVisible(true);
|
|
}
|
|
}
|
|
|
|
emit pageLoaded();
|
|
|
|
notifyAboutDocksOpenStatus();
|
|
}
|
|
|
|
bool DockWindow::isDockOpen(const QString& dockName) const
|
|
{
|
|
return m_currentPage && m_currentPage->isDockOpen(dockName);
|
|
}
|
|
|
|
void DockWindow::toggleDock(const QString& dockName)
|
|
{
|
|
if (m_currentPage) {
|
|
m_currentPage->toggleDock(dockName);
|
|
m_docksOpenStatusChanged.send({ dockName });
|
|
}
|
|
}
|
|
|
|
void DockWindow::setDockOpen(const QString& dockName, bool open)
|
|
{
|
|
if (m_currentPage) {
|
|
m_currentPage->setDockOpen(dockName, open);
|
|
m_docksOpenStatusChanged.send({ dockName });
|
|
}
|
|
}
|
|
|
|
Channel<QStringList> DockWindow::docksOpenStatusChanged() const
|
|
{
|
|
return m_docksOpenStatusChanged;
|
|
}
|
|
|
|
bool DockWindow::isDockFloating(const QString& dockName) const
|
|
{
|
|
return m_currentPage && m_currentPage->isDockFloating(dockName);
|
|
}
|
|
|
|
void DockWindow::toggleDockFloating(const QString& dockName)
|
|
{
|
|
if (m_currentPage) {
|
|
m_currentPage->toggleDockFloating(dockName);
|
|
}
|
|
}
|
|
|
|
DockPageView* DockWindow::currentPage() const
|
|
{
|
|
return m_currentPage;
|
|
}
|
|
|
|
QQuickItem& DockWindow::asItem() const
|
|
{
|
|
return *m_mainWindow;
|
|
}
|
|
|
|
void DockWindow::restoreDefaultLayout()
|
|
{
|
|
TRACEFUNC;
|
|
|
|
clearRegistry();
|
|
|
|
if (m_currentPage) {
|
|
for (DockBase* dock : m_currentPage->allDocks()) {
|
|
dock->resetToDefault();
|
|
}
|
|
}
|
|
|
|
m_reloadCurrentPageAllowed = false;
|
|
for (const DockPageView* page : m_pages.list()) {
|
|
uiConfiguration()->setPageState(page->objectName(), QByteArray());
|
|
}
|
|
|
|
uiConfiguration()->setWindowGeometry(QByteArray());
|
|
m_reloadCurrentPageAllowed = true;
|
|
|
|
reloadCurrentPage();
|
|
}
|
|
|
|
void DockWindow::loadPageContent(const DockPageView* page)
|
|
{
|
|
TRACEFUNC;
|
|
|
|
addDock(page->centralDock());
|
|
|
|
loadPanels(page);
|
|
loadToolBars(page);
|
|
|
|
if (page->statusBar()) {
|
|
addDock(page->statusBar(), Location::Bottom);
|
|
}
|
|
|
|
loadTopLevelToolBars(page);
|
|
}
|
|
|
|
void DockWindow::loadTopLevelToolBars(const DockPageView* page)
|
|
{
|
|
TRACEFUNC;
|
|
|
|
QList<DockToolBarView*> allToolBars = m_toolBars.list();
|
|
allToolBars << page->mainToolBars();
|
|
|
|
DockToolBarView* prevToolBar = nullptr;
|
|
|
|
for (DockToolBarView* toolBar : allToolBars) {
|
|
auto location = prevToolBar ? Location::Right : Location::Top;
|
|
addDock(toolBar, location, prevToolBar);
|
|
prevToolBar = toolBar;
|
|
}
|
|
}
|
|
|
|
void DockWindow::loadToolBars(const DockPageView* page)
|
|
{
|
|
TRACEFUNC;
|
|
|
|
for (DockToolBarView* toolBar : page->toolBars()) {
|
|
addDock(toolBar, toolBar->location());
|
|
}
|
|
|
|
for (Location location : POSSIBLE_LOCATIONS) {
|
|
if (auto holder = page->holder(DockType::ToolBar, location)) {
|
|
addDock(holder, location);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DockWindow::loadPanels(const DockPageView* page)
|
|
{
|
|
TRACEFUNC;
|
|
|
|
auto addPanel = [this, page](DockPanelView* panel, Location location) {
|
|
for (DockPanelView* destinationPanel : page->panels()) {
|
|
if (panel->isVisible() && destinationPanel->isTabAllowed(panel)) {
|
|
registerDock(panel);
|
|
|
|
destinationPanel->addPanelAsTab(panel);
|
|
destinationPanel->setCurrentTabIndex(0);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
addDock(panel, location);
|
|
};
|
|
|
|
for (DockPanelView* panel : page->panels()) {
|
|
addPanel(panel, panel->location());
|
|
}
|
|
|
|
for (Location location : POSSIBLE_LOCATIONS) {
|
|
if (auto holder = page->holder(DockType::Panel, location)) {
|
|
addDock(holder, location);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DockWindow::alignToolBars(const DockPageView* page)
|
|
{
|
|
QList<DockToolBarView*> topToolBars = topLevelToolBars(page);
|
|
|
|
DockToolBarView* lastLeftToolBar = nullptr;
|
|
DockToolBarView* lastCentralToolBar = nullptr;
|
|
|
|
int leftToolBarsWidth = 0;
|
|
int centralToolBarsWidth = 0;
|
|
int rightToolBarsWidth = 0;
|
|
|
|
int separatorThickness = KDDockWidgets::Config::self().separatorThickness();
|
|
|
|
for (DockToolBarView* toolBar : topToolBars) {
|
|
if (toolBar->floating() || !toolBar->isVisible()) {
|
|
continue;
|
|
}
|
|
|
|
switch (static_cast<DockToolBarAlignment::Type>(toolBar->alignment())) {
|
|
case DockToolBarAlignment::Left:
|
|
lastLeftToolBar = toolBar;
|
|
leftToolBarsWidth += toolBar->contentWidth();
|
|
break;
|
|
case DockToolBarAlignment::Center:
|
|
lastCentralToolBar = toolBar;
|
|
centralToolBarsWidth += (toolBar->contentWidth() + separatorThickness);
|
|
break;
|
|
case DockToolBarAlignment::Right:
|
|
rightToolBarsWidth += (toolBar->contentWidth() + separatorThickness);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!lastLeftToolBar || !lastCentralToolBar) {
|
|
return;
|
|
}
|
|
|
|
int deltaForLastLeftToolbar = (width() - centralToolBarsWidth) / 2 - leftToolBarsWidth;
|
|
int deltaForLastCentralToolBar = (width() - centralToolBarsWidth) / 2 - rightToolBarsWidth;
|
|
|
|
deltaForLastLeftToolbar = std::max(deltaForLastLeftToolbar, 0);
|
|
deltaForLastCentralToolBar = std::max(deltaForLastCentralToolBar, 0);
|
|
|
|
int freeSpace = width() - (leftToolBarsWidth + centralToolBarsWidth + rightToolBarsWidth);
|
|
|
|
if (deltaForLastLeftToolbar + deltaForLastCentralToolBar > freeSpace) {
|
|
deltaForLastLeftToolbar = freeSpace;
|
|
deltaForLastCentralToolBar = 0;
|
|
}
|
|
|
|
lastLeftToolBar->setMinimumWidth(lastLeftToolBar->contentWidth() + deltaForLastLeftToolbar);
|
|
lastCentralToolBar->setMinimumWidth(lastCentralToolBar->contentWidth() + deltaForLastCentralToolBar);
|
|
}
|
|
|
|
void DockWindow::addDock(DockBase* dock, Location location, const DockBase* relativeTo)
|
|
{
|
|
TRACEFUNC;
|
|
|
|
registerDock(dock);
|
|
|
|
KDDockWidgets::DockWidgetBase* relativeDock = relativeTo ? relativeTo->dockWidget() : nullptr;
|
|
|
|
auto visibilityOption = dock->isVisible() ? KDDockWidgets::InitialVisibilityOption::StartVisible
|
|
: KDDockWidgets::InitialVisibilityOption::StartHidden;
|
|
|
|
KDDockWidgets::InitialOption options(visibilityOption, dock->preferredSize());
|
|
|
|
m_mainWindow->addDockWidget(dock->dockWidget(), locationToKLocation(location), relativeDock, options);
|
|
}
|
|
|
|
void DockWindow::registerDock(DockBase* dock)
|
|
{
|
|
TRACEFUNC;
|
|
|
|
IF_ASSERT_FAILED(dock) {
|
|
return;
|
|
}
|
|
|
|
auto registry = KDDockWidgets::DockRegistry::self();
|
|
auto dockWidget = dock->dockWidget();
|
|
|
|
if (!registry->containsDockWidget(dockWidget->uniqueName())) {
|
|
registry->registerDockWidget(dockWidget);
|
|
}
|
|
}
|
|
|
|
DockPageView* DockWindow::pageByUri(const QString& uri) const
|
|
{
|
|
for (DockPageView* page : m_pages.list()) {
|
|
if (page->uri() == uri) {
|
|
return page;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool DockWindow::doLoadPage(const QString& uri, const QVariantMap& params)
|
|
{
|
|
DockPageView* newPage = pageByUri(uri);
|
|
IF_ASSERT_FAILED(newPage) {
|
|
return false;
|
|
}
|
|
|
|
loadPageContent(newPage);
|
|
restorePageState(newPage->objectName());
|
|
initDocks(newPage);
|
|
|
|
newPage->setParams(params);
|
|
|
|
m_currentPage = newPage;
|
|
m_currentPage->setVisible(true);
|
|
|
|
return true;
|
|
}
|
|
|
|
void DockWindow::saveGeometry()
|
|
{
|
|
TRACEFUNC;
|
|
|
|
/// NOTE: The state of all dock widgets is also saved here,
|
|
/// since the library does not provide the ability to save
|
|
/// and restore only the application geometry.
|
|
/// Therefore, for correct operation after saving or restoring geometry,
|
|
/// it is necessary to apply the appropriate method for the state.
|
|
m_reloadCurrentPageAllowed = false;
|
|
uiConfiguration()->setWindowGeometry(windowState());
|
|
m_reloadCurrentPageAllowed = true;
|
|
}
|
|
|
|
void DockWindow::restoreGeometry()
|
|
{
|
|
TRACEFUNC;
|
|
|
|
if (uiConfiguration()->windowGeometry().isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
if (restoreLayout(uiConfiguration()->windowGeometry())) {
|
|
m_hasGeometryBeenRestored = true;
|
|
} else {
|
|
LOGE() << "Could not restore the window geometry!";
|
|
}
|
|
}
|
|
|
|
void DockWindow::savePageState(const QString& pageName)
|
|
{
|
|
TRACEFUNC;
|
|
|
|
m_reloadCurrentPageAllowed = false;
|
|
uiConfiguration()->setPageState(pageName, windowState());
|
|
m_reloadCurrentPageAllowed = true;
|
|
}
|
|
|
|
void DockWindow::restorePageState(const QString& pageName)
|
|
{
|
|
TRACEFUNC;
|
|
|
|
ValNt<QByteArray> pageStateValNt = uiConfiguration()->pageState(pageName);
|
|
|
|
/// NOTE: Do not restore geometry
|
|
bool ok = restoreLayout(pageStateValNt.val, true /*restoreRelativeToMainWindow*/);
|
|
if (!ok) {
|
|
LOGE() << "Could not restore the state of " << pageName << "!";
|
|
}
|
|
|
|
if (!pageStateValNt.notification.isConnected()) {
|
|
pageStateValNt.notification.onNotify(this, [this, pageName]() {
|
|
bool isCurrentPage = m_currentPage && (m_currentPage->objectName() == pageName);
|
|
if (isCurrentPage) {
|
|
reloadCurrentPage();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
bool DockWindow::restoreLayout(const QByteArray& layout, bool restoreRelativeToMainWindow)
|
|
{
|
|
if (layout.isEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
TRACEFUNC;
|
|
|
|
auto option = restoreRelativeToMainWindow ? KDDockWidgets::RestoreOption_RelativeToMainWindow
|
|
: KDDockWidgets::RestoreOption_None;
|
|
|
|
KDDockWidgets::LayoutSaver layoutSaver(option);
|
|
return layoutSaver.restoreLayout(layout);
|
|
}
|
|
|
|
QByteArray DockWindow::windowState() const
|
|
{
|
|
TRACEFUNC;
|
|
|
|
KDDockWidgets::LayoutSaver layoutSaver;
|
|
return layoutSaver.serializeLayout();
|
|
}
|
|
|
|
void DockWindow::reloadCurrentPage()
|
|
{
|
|
if (!m_reloadCurrentPageAllowed) {
|
|
return;
|
|
}
|
|
|
|
TRACEFUNC;
|
|
|
|
for (DockBase* dock : m_currentPage->allDocks()) {
|
|
dock->deinit();
|
|
}
|
|
|
|
QString currentPageUriBackup = currentPageUri();
|
|
|
|
/// NOTE: for reset geometry
|
|
m_currentPage = nullptr;
|
|
|
|
if (doLoadPage(currentPageUriBackup)) {
|
|
notifyAboutDocksOpenStatus();
|
|
}
|
|
}
|
|
|
|
void DockWindow::initDocks(DockPageView* page)
|
|
{
|
|
TRACEFUNC;
|
|
|
|
for (DockToolBarView* toolbar : m_toolBars.list()) {
|
|
toolbar->init();
|
|
}
|
|
|
|
if (page) {
|
|
page->init();
|
|
}
|
|
|
|
alignToolBars(page);
|
|
|
|
for (DockToolBarView* toolbar : page->mainToolBars()) {
|
|
connect(toolbar, &DockToolBarView::floatingChanged, this, [this, page]() {
|
|
alignToolBars(page);
|
|
}, Qt::UniqueConnection);
|
|
}
|
|
}
|
|
|
|
void DockWindow::notifyAboutDocksOpenStatus()
|
|
{
|
|
const DockPageView* page = currentPage();
|
|
|
|
IF_ASSERT_FAILED(page) {
|
|
return;
|
|
}
|
|
|
|
QStringList dockNames;
|
|
|
|
for (DockToolBarView* toolBar : page->mainToolBars()) {
|
|
dockNames << toolBar->objectName();
|
|
}
|
|
|
|
for (DockToolBarView* toolBar : page->toolBars()) {
|
|
dockNames << toolBar->objectName();
|
|
}
|
|
|
|
for (DockPanelView* panel : page->panels()) {
|
|
dockNames << panel->objectName();
|
|
}
|
|
|
|
if (page->statusBar()) {
|
|
dockNames << page->statusBar()->objectName();
|
|
}
|
|
|
|
m_docksOpenStatusChanged.send(dockNames);
|
|
}
|
|
|
|
QList<DockToolBarView*> DockWindow::topLevelToolBars(const DockPageView* page) const
|
|
{
|
|
QList<DockToolBarView*> toolBars = m_toolBars.list();
|
|
|
|
if (page) {
|
|
toolBars << page->mainToolBars();
|
|
}
|
|
|
|
return toolBars;
|
|
}
|