Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include "topleveltoolbarslayout.h"
24 :
25 : #include "kddockwidgets/src/Config.h"
26 : #include "kddockwidgets/src/core/DockWidget.h"
27 : #include "kddockwidgets/src/core/Layout.h"
28 : #include "kddockwidgets/src/core/MainWindow.h"
29 : #include "kddockwidgets/src/qtquick/views/MainWindow.h"
30 :
31 : #include "dockpageview.h"
32 : #include "docktoolbarview.h"
33 : #include "dockwindow.h"
34 :
35 : using namespace muse::dock;
36 :
37 : namespace muse::dock {
38 : //! NOTE: Toolbar's maximum lenght, see DockToolBar.qml
39 : static constexpr int UNCONSTRAINED_TOOLBAR_WIDTH = 16777215;
40 : }
41 :
42 24 : TopLevelToolBarsLayout::TopLevelToolBarsLayout(KDDockWidgets::QtQuick::MainWindow* mainWindow, DockWindow* window)
43 24 : : QObject(window), m_mainWindow(mainWindow), m_window(window)
44 : {
45 24 : }
46 :
47 6 : void TopLevelToolBarsLayout::setUpPageConnections(const DockPageView* page)
48 : {
49 15 : for (DockToolBarView* toolBar : topLevelToolBars(page)) {
50 9 : connect(toolBar, &DockToolBarView::floatingChanged,
51 9 : this, &TopLevelToolBarsLayout::scheduleRelayout, Qt::UniqueConnection);
52 :
53 9 : connect(toolBar, &DockToolBarView::contentSizeChanged,
54 9 : this, &TopLevelToolBarsLayout::scheduleRelayout, Qt::UniqueConnection);
55 :
56 9 : connect(toolBar, &DockToolBarView::visibleChanged,
57 18 : this, &TopLevelToolBarsLayout::scheduleRelayout, Qt::UniqueConnection);
58 6 : }
59 6 : }
60 :
61 9 : void TopLevelToolBarsLayout::scheduleRelayout()
62 : {
63 9 : m_window->polish();
64 9 : }
65 :
66 14 : void TopLevelToolBarsLayout::relayout()
67 : {
68 14 : DockPageView* page = currentPage();
69 14 : if (!page) {
70 1 : return;
71 : }
72 :
73 13 : const int width = availableWidth();
74 :
75 13 : adjustContentForAvailableSpace(page);
76 13 : alignToolBars(page, width);
77 :
78 : //! NOTE: Force the layouting items to pick up the current dock minimums
79 32 : for (DockBase* dock : page->allDocks()) {
80 19 : if (dock) {
81 9 : dock->syncLayoutItemMinSize();
82 : }
83 13 : }
84 :
85 13 : if (!applyLayoutSizeToFitWindow(width)) {
86 1 : scheduleRelayout();
87 : }
88 : }
89 :
90 25 : void TopLevelToolBarsLayout::adjustContentForAvailableSpace(const DockPageView* page)
91 : {
92 25 : if (!page) {
93 0 : return;
94 : }
95 :
96 25 : const int spaceWidth = availableWidth();
97 25 : const int separator = separatorThickness();
98 :
99 25 : auto adjustDocks = [spaceWidth, separator](QList<DockBase*> docks) {
100 25 : int width = separator * std::max(0, int(docks.size()) - 1);
101 74 : for (DockBase* dock : docks) {
102 49 : width += dock->contentWidth();
103 : }
104 :
105 25 : docks.erase(std::remove_if(docks.begin(), docks.end(), [](const DockBase* dock){
106 49 : return dock->compactPriorityOrder() == -1;
107 : }), docks.end());
108 :
109 25 : if (docks.empty()) {
110 16 : return;
111 : }
112 :
113 9 : std::sort(docks.begin(), docks.end(), [](const DockBase* dock1, DockBase* dock2) {
114 10 : return dock1->compactPriorityOrder() < dock2->compactPriorityOrder();
115 : });
116 :
117 9 : if (width > spaceWidth) {
118 5 : for (DockBase* dock : docks) {
119 4 : if (width <= spaceWidth) {
120 1 : break;
121 : }
122 :
123 3 : if (!dock->isCompact()) {
124 3 : dock->setIsCompact(true);
125 :
126 3 : width -= dock->nonCompactWidth();
127 3 : width += dock->width();
128 : }
129 : }
130 : } else {
131 12 : for (int i = docks.size() - 1; i >= 0; i--) {
132 9 : DockBase* dock = docks.at(i);
133 9 : if (!dock->isCompact()) {
134 5 : continue;
135 : }
136 :
137 4 : int actualWidth = dock->contentWidth();
138 4 : int nonCompactWidth = dock->nonCompactWidth();
139 4 : if (width - actualWidth + nonCompactWidth <= spaceWidth) {
140 3 : dock->setIsCompact(false);
141 : }
142 :
143 4 : break;
144 : }
145 : }
146 25 : };
147 :
148 25 : QList<DockBase*> topLevelToolBarsDocks;
149 :
150 78 : for (DockToolBarView* toolBar : topLevelToolBars(page)) {
151 53 : if (!isToolBarFloating(toolBar) && toolBar->isVisible()) {
152 49 : topLevelToolBarsDocks << toolBar;
153 : }
154 25 : }
155 :
156 25 : adjustDocks(topLevelToolBarsDocks);
157 25 : }
158 :
159 13 : void TopLevelToolBarsLayout::alignToolBars(const DockPageView* page, int availableWidth)
160 : {
161 13 : const QList<DockToolBarView*> topToolBars = topLevelToolBars(page);
162 :
163 : //! NOTE: Reset paddings/pins first, so that widths from a previous
164 : //! configuration do not accumulate
165 42 : for (DockToolBarView* toolBar : topToolBars) {
166 29 : toolBar->setMaximumWidth(UNCONSTRAINED_TOOLBAR_WIDTH);
167 29 : toolBar->setMinimumWidth(toolBar->contentWidth());
168 : }
169 :
170 13 : DockToolBarView* lastLeftToolBar = nullptr;
171 13 : DockToolBarView* lastCentralToolBar = nullptr;
172 :
173 13 : int leftBlockWidth = 0;
174 13 : int centralBlockWidth = 0;
175 13 : int rightBlockWidth = 0;
176 :
177 13 : int leftCount = 0;
178 13 : int centralCount = 0;
179 13 : int rightCount = 0;
180 :
181 13 : const int separator = separatorThickness();
182 :
183 42 : for (DockToolBarView* toolBar : topToolBars) {
184 29 : if (toolBar->floating() || !toolBar->isVisible()) {
185 2 : continue;
186 : }
187 :
188 27 : switch (static_cast<DockToolBarAlignment::Type>(toolBar->alignment())) {
189 10 : case DockToolBarAlignment::Left:
190 10 : lastLeftToolBar = toolBar;
191 10 : leftBlockWidth += toolBar->contentWidth();
192 10 : ++leftCount;
193 10 : break;
194 5 : case DockToolBarAlignment::Center:
195 5 : lastCentralToolBar = toolBar;
196 5 : centralBlockWidth += toolBar->contentWidth();
197 5 : ++centralCount;
198 5 : break;
199 12 : case DockToolBarAlignment::Right:
200 12 : rightBlockWidth += toolBar->contentWidth();
201 12 : ++rightCount;
202 12 : break;
203 : }
204 : }
205 :
206 13 : leftBlockWidth += separator * std::max(0, leftCount - 1);
207 13 : centralBlockWidth += separator * std::max(0, centralCount - 1);
208 13 : rightBlockWidth += separator * std::max(0, rightCount - 1);
209 :
210 13 : const int separatorLeftCentral = (leftCount > 0 && centralCount > 0) ? separator : 0;
211 13 : const int separatorCentralRight = (centralCount > 0 && rightCount > 0) ? separator : 0;
212 13 : const int separatorLeftRight = (leftCount > 0 && centralCount == 0 && rightCount > 0) ? separator : 0;
213 :
214 13 : const int occupied = leftBlockWidth + centralBlockWidth + rightBlockWidth
215 13 : + separatorLeftCentral + separatorCentralRight + separatorLeftRight;
216 :
217 13 : const int freeSpace = availableWidth - occupied;
218 13 : if (freeSpace <= 0) {
219 1 : return;
220 : }
221 :
222 12 : DockToolBarView* grower = lastLeftToolBar ? lastLeftToolBar : lastCentralToolBar;
223 12 : if (!grower) {
224 : //! NOTE: No toolbar to absorb the free space; leave all the toolbars
225 : //! unpinned so the layout can distribute it
226 3 : return;
227 : }
228 :
229 9 : int paddingForLastCentral = 0;
230 9 : if (lastCentralToolBar && lastCentralToolBar != grower) {
231 3 : paddingForLastCentral = (availableWidth - centralBlockWidth) / 2 - rightBlockWidth - separatorCentralRight;
232 3 : paddingForLastCentral = std::min(std::max(paddingForLastCentral, 0), freeSpace);
233 : }
234 :
235 34 : for (DockToolBarView* toolBar : topToolBars) {
236 25 : if (toolBar->floating() || !toolBar->isVisible() || toolBar == grower) {
237 11 : continue;
238 : }
239 :
240 : const int pinnedWidth = (toolBar == lastCentralToolBar)
241 14 : ? toolBar->contentWidth() + paddingForLastCentral
242 11 : : toolBar->contentWidth();
243 :
244 : //! NOTE: max before min
245 14 : toolBar->setMaximumWidth(pinnedWidth);
246 14 : toolBar->setMinimumWidth(pinnedWidth);
247 : }
248 13 : }
249 :
250 3 : bool TopLevelToolBarsLayout::applyLayoutSizeToFitWindow(int targetWidth)
251 : {
252 3 : KDDockWidgets::Core::MainWindow* mw = m_mainWindow ? m_mainWindow->mainWindow() : nullptr;
253 3 : KDDockWidgets::Core::Layout* layout = mw ? mw->layout() : nullptr;
254 3 : if (!layout) {
255 0 : return true;
256 : }
257 :
258 3 : const int applyWidth = std::max(targetWidth, layout->layoutMinimumSize().width());
259 :
260 3 : if (applyWidth != layout->layoutSize().width()) {
261 0 : layout->setLayoutSize(QSize(applyWidth, layout->layoutSize().height()));
262 : }
263 :
264 3 : return layout->layoutSize().width() == applyWidth;
265 : }
266 :
267 3 : DockPageView* TopLevelToolBarsLayout::currentPage() const
268 : {
269 3 : return m_window->currentPage();
270 : }
271 :
272 12 : QList<DockToolBarView*> TopLevelToolBarsLayout::topLevelToolBars(const DockPageView* page) const
273 : {
274 12 : return m_window->topLevelToolBars(page);
275 : }
276 :
277 9 : int TopLevelToolBarsLayout::availableWidth() const
278 : {
279 9 : return int(m_window->width());
280 : }
281 :
282 9 : int TopLevelToolBarsLayout::separatorThickness() const
283 : {
284 9 : return KDDockWidgets::Config::self(m_window->iocContext()->id).separatorThickness();
285 : }
286 :
287 12 : bool TopLevelToolBarsLayout::isToolBarFloating(const DockToolBarView* toolBar) const
288 : {
289 12 : return toolBar->dockWidget()->isFloating();
290 : }
|