Version 7.1.7.1, tag libreoffice-7.1.7.1
[LibreOffice.git] / sfx2 / source / sidebar / DeckLayouter.cxx
blobc9d59136c3792879781edab46a41d4bc7ce4dbd1
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sidebar/DeckLayouter.hxx>
21 #include <sfx2/sidebar/Theme.hxx>
22 #include <sfx2/sidebar/Panel.hxx>
23 #include <sidebar/PanelTitleBar.hxx>
24 #include <sfx2/sidebar/Deck.hxx>
25 #include <sfx2/sidebar/SidebarController.hxx>
26 #include <comphelper/lok.hxx>
28 #include <comphelper/processfactory.hxx>
29 #include <vcl/window.hxx>
30 #include <vcl/scrbar.hxx>
32 #include <com/sun/star/uno/Reference.hxx>
33 #include <com/sun/star/frame/Desktop.hpp>
34 #include <com/sun/star/frame/XDesktop2.hpp>
35 #include <com/sun/star/frame/XFrame.hpp>
36 #include <com/sun/star/ui/XSidebarPanel.hpp>
38 using namespace css;
39 using namespace css::uno;
41 namespace sfx2::sidebar {
43 namespace {
44 const sal_Int32 MinimalPanelHeight (25);
46 enum LayoutMode
48 MinimumOrLarger,
49 PreferredOrLarger,
50 Preferred
52 class LayoutItem
54 public:
55 VclPtr<Panel> mpPanel;
56 css::ui::LayoutSize maLayoutSize;
57 sal_Int32 mnDistributedHeight;
58 sal_Int32 mnWeight;
59 bool mbShowTitleBar;
61 LayoutItem(const VclPtr<Panel>& rPanel)
62 : mpPanel(rPanel)
63 , maLayoutSize(0, 0, 0)
64 , mnDistributedHeight(0)
65 , mnWeight(0)
66 , mbShowTitleBar(true)
70 tools::Rectangle LayoutPanels (
71 const tools::Rectangle& rContentArea,
72 sal_Int32& rMinimalWidth,
73 sal_Int32& rMinimalHeight,
74 ::std::vector<LayoutItem>& rLayoutItems,
75 vcl::Window& rScrollClipWindow,
76 vcl::Window& rScrollContainer,
77 ScrollBar& pVerticalScrollBar,
78 const bool bShowVerticalScrollBar);
79 void GetRequestedSizes (
80 ::std::vector<LayoutItem>& rLayoutItem,
81 sal_Int32& rAvailableHeight,
82 sal_Int32& rMinimalWidth,
83 const tools::Rectangle& rContentBox);
84 void DistributeHeights (
85 ::std::vector<LayoutItem>& rLayoutItems,
86 const sal_Int32 nHeightToDistribute,
87 const sal_Int32 nContainerHeight,
88 const bool bMinimumHeightIsBase);
89 bool MoveResizePixel(const VclPtr<vcl::Window> &pWindow,
90 const Point &rNewPos, const Size &rNewSize);
91 sal_Int32 PlacePanels (
92 ::std::vector<LayoutItem>& rLayoutItems,
93 const sal_Int32 nWidth,
94 const LayoutMode eMode,
95 vcl::Window& rScrollContainer);
96 tools::Rectangle PlaceDeckTitle (
97 vcl::Window& rTitleBar,
98 const tools::Rectangle& rAvailableSpace);
99 tools::Rectangle PlaceVerticalScrollBar (
100 ScrollBar& rVerticalScrollBar,
101 const tools::Rectangle& rAvailableSpace,
102 const bool bShowVerticalScrollBar);
103 void SetupVerticalScrollBar(
104 ScrollBar& rVerticalScrollBar,
105 const sal_Int32 nContentHeight,
106 const sal_Int32 nVisibleHeight);
107 void UpdateFiller (
108 vcl::Window& rFiller,
109 const tools::Rectangle& rBox);
112 void DeckLayouter::LayoutDeck (
113 const tools::Rectangle& rContentArea,
114 sal_Int32& rMinimalWidth,
115 sal_Int32& rMinimalHeight,
116 SharedPanelContainer& rPanels,
117 vcl::Window& rDeckTitleBar,
118 vcl::Window& rScrollClipWindow,
119 vcl::Window& rScrollContainer,
120 vcl::Window& rFiller,
121 ScrollBar& rVerticalScrollBar)
123 if (rContentArea.GetWidth()<=0 || rContentArea.GetHeight()<=0)
124 return;
125 tools::Rectangle aBox (PlaceDeckTitle(rDeckTitleBar, rContentArea));
127 if ( ! rPanels.empty())
129 // Prepare the layout item container.
130 ::std::vector<LayoutItem> aLayoutItems;
131 aLayoutItems.reserve(rPanels.size());
132 for (const auto& rPanel : rPanels)
133 aLayoutItems.emplace_back(rPanel);
135 aBox = LayoutPanels(
136 aBox,
137 rMinimalWidth,
138 rMinimalHeight,
139 aLayoutItems,
140 rScrollClipWindow,
141 rScrollContainer,
142 rVerticalScrollBar,
143 false);
145 UpdateFiller(rFiller, aBox);
148 namespace {
150 tools::Rectangle LayoutPanels (
151 const tools::Rectangle& rContentArea,
152 sal_Int32& rMinimalWidth,
153 sal_Int32& rMinimalHeight,
154 ::std::vector<LayoutItem>& rLayoutItems,
155 vcl::Window& rScrollClipWindow,
156 vcl::Window& rScrollContainer,
157 ScrollBar& rVerticalScrollBar,
158 const bool bShowVerticalScrollBar)
160 tools::Rectangle aBox (PlaceVerticalScrollBar(rVerticalScrollBar, rContentArea, bShowVerticalScrollBar));
162 const sal_Int32 nWidth (aBox.GetWidth());
164 // Get the requested heights of the panels and the available
165 // height that is left when all panel titles and separators are
166 // taken into account.
167 sal_Int32 nAvailableHeight (aBox.GetHeight());
168 GetRequestedSizes(rLayoutItems, nAvailableHeight, rMinimalWidth, aBox);
169 const sal_Int32 nTotalDecorationHeight (aBox.GetHeight() - nAvailableHeight);
171 // Analyze the requested heights.
172 // Determine the height that is available for panel content
173 // and count the different layouts.
174 sal_Int32 nTotalPreferredHeight (0);
175 sal_Int32 nTotalMinimumHeight (0);
177 for (const auto& rItem : rLayoutItems)
179 nTotalMinimumHeight += rItem.maLayoutSize.Minimum;
180 nTotalPreferredHeight += rItem.maLayoutSize.Preferred;
183 if (nTotalMinimumHeight > nAvailableHeight && !bShowVerticalScrollBar
184 && !comphelper::LibreOfficeKit::isActive())
186 // Not enough space, even when all panels are shrunk to their
187 // minimum height.
188 // Show a vertical scrollbar.
189 return LayoutPanels(
190 rContentArea,
191 rMinimalWidth,
192 rMinimalHeight,
193 rLayoutItems,
194 rScrollClipWindow,
195 rScrollContainer,
196 rVerticalScrollBar,
197 true);
200 // We are now in one of three modes.
201 // - The preferred height fits into the available size:
202 // Use the preferred size, distribute the remaining height by
203 // enlarging panels.
204 // - The total minimum height fits into the available size:
205 // Use the minimum size, distribute the remaining height by
206 // enlarging panels.
207 // - The total minimum height does not fit into the available
208 // size:
209 // Use the unmodified preferred height for all panels.
211 LayoutMode eMode (MinimumOrLarger);
212 if (bShowVerticalScrollBar)
213 eMode = Preferred;
214 else if (nTotalPreferredHeight <= nAvailableHeight)
215 eMode = PreferredOrLarger;
216 else
217 eMode = MinimumOrLarger;
219 if (eMode != Preferred)
221 const sal_Int32 nTotalHeight (eMode==MinimumOrLarger ? nTotalMinimumHeight : nTotalPreferredHeight);
223 DistributeHeights(
224 rLayoutItems,
225 nAvailableHeight-nTotalHeight,
226 aBox.GetHeight(),
227 eMode==MinimumOrLarger);
230 // Set position and size of the mpScrollClipWindow to the available
231 // size. Its child, the mpScrollContainer, may have a bigger
232 // height.
233 rScrollClipWindow.setPosSizePixel(aBox.Left(), aBox.Top(), aBox.GetWidth(), aBox.GetHeight());
235 const sal_Int32 nContentHeight (
236 eMode==Preferred
237 ? nTotalPreferredHeight + nTotalDecorationHeight
238 : aBox.GetHeight());
239 sal_Int32 nY = rVerticalScrollBar.GetThumbPos();
240 if (nContentHeight-nY < aBox.GetHeight())
241 nY = nContentHeight-aBox.GetHeight();
242 if (nY < 0)
243 nY = 0;
244 rScrollContainer.setPosSizePixel(
246 -nY,
247 nWidth,
248 nContentHeight);
250 if (bShowVerticalScrollBar)
251 SetupVerticalScrollBar(rVerticalScrollBar, nContentHeight, aBox.GetHeight());
253 const sal_Int32 nUsedHeight (PlacePanels(rLayoutItems, nWidth, eMode, rScrollContainer));
254 aBox.AdjustTop(nUsedHeight );
255 rMinimalHeight = nUsedHeight;
256 return aBox;
259 bool MoveResizePixel(const VclPtr<vcl::Window> &pWindow,
260 const Point &rNewPos, const Size &rNewSize)
262 Point aCurPos = pWindow->GetPosPixel();
263 Size aCurSize = pWindow->GetSizePixel();
264 if (rNewPos == aCurPos && aCurSize == rNewSize)
265 return false;
266 pWindow->setPosSizePixel(rNewPos.X(), rNewPos.Y(), rNewSize.Width(), rNewSize.Height());
267 return true;
270 sal_Int32 PlacePanels (
271 ::std::vector<LayoutItem>& rLayoutItems,
272 const sal_Int32 nWidth,
273 const LayoutMode eMode,
274 vcl::Window& rScrollContainer)
276 ::std::vector<sal_Int32> aSeparators;
277 const sal_Int32 nDeckSeparatorHeight (Theme::GetInteger(Theme::Int_DeckSeparatorHeight));
278 sal_Int32 nY (0);
280 vcl::Region aInvalidRegions;
282 // Assign heights and places.
283 for(::std::vector<LayoutItem>::const_iterator iItem(rLayoutItems.begin()),
284 iEnd(rLayoutItems.end());
285 iItem!=iEnd;
286 ++iItem)
288 if (!iItem->mpPanel)
289 continue;
291 Panel& rPanel (*iItem->mpPanel);
293 // Separator above the panel title bar.
294 if (!rPanel.IsLurking())
296 aSeparators.push_back(nY);
297 nY += nDeckSeparatorHeight;
300 // Place the title bar.
301 VclPtr<PanelTitleBar> pTitleBar = rPanel.GetTitleBar();
302 if (pTitleBar)
304 const sal_Int32 nPanelTitleBarHeight (Theme::GetInteger(Theme::Int_PanelTitleBarHeight) * rPanel.GetDPIScaleFactor());
306 if (iItem->mbShowTitleBar)
308 pTitleBar->setPosSizePixel(0, nY, nWidth, nPanelTitleBarHeight);
309 pTitleBar->Show();
310 nY += nPanelTitleBarHeight;
312 else
314 pTitleBar->Hide();
318 if (rPanel.IsExpanded() && !rPanel.IsLurking())
320 rPanel.Show();
322 // Determine the height of the panel depending on layout
323 // mode and distributed heights.
324 sal_Int32 nPanelHeight (0);
325 switch(eMode)
327 case MinimumOrLarger:
328 nPanelHeight = iItem->maLayoutSize.Minimum + iItem->mnDistributedHeight;
329 break;
330 case PreferredOrLarger:
331 nPanelHeight = iItem->maLayoutSize.Preferred + iItem->mnDistributedHeight;
332 break;
333 case Preferred:
334 nPanelHeight = iItem->maLayoutSize.Preferred;
335 break;
336 default:
337 OSL_ASSERT(false);
338 break;
341 // Place the panel.
342 Point aNewPos(0, nY);
343 Size aNewSize(nWidth, nPanelHeight);
345 // Only invalidate if we moved
346 if (MoveResizePixel(&rPanel, aNewPos, aNewSize))
348 tools::Rectangle aRect(aNewPos, aNewSize);
349 aInvalidRegions.Union(rPanel.PixelToLogic(aRect));
352 nY += nPanelHeight;
354 else
356 rPanel.Hide();
358 // Add a separator below the collapsed panel, if it is the
359 // last panel in the deck.
360 if (iItem == rLayoutItems.end()-1)
362 // Separator below the panel title bar.
363 aSeparators.push_back(nY);
364 nY += nDeckSeparatorHeight;
369 Deck::ScrollContainerWindow* pScrollContainerWindow
370 = dynamic_cast<Deck::ScrollContainerWindow*>(&rScrollContainer);
371 if (pScrollContainerWindow != nullptr)
372 pScrollContainerWindow->SetSeparators(aSeparators);
374 rScrollContainer.Invalidate(aInvalidRegions);
376 return nY;
379 void GetRequestedSizes (
380 ::std::vector<LayoutItem>& rLayoutItems,
381 sal_Int32& rAvailableHeight,
382 sal_Int32& rMinimalWidth,
383 const tools::Rectangle& rContentBox)
385 rAvailableHeight = rContentBox.GetHeight();
387 const sal_Int32 nDeckSeparatorHeight (Theme::GetInteger(Theme::Int_DeckSeparatorHeight));
389 for (auto& rItem : rLayoutItems)
391 rItem.maLayoutSize = ui::LayoutSize(0,0,0);
393 if (rItem.mpPanel == nullptr)
394 continue;
396 if (rItem.mpPanel->IsLurking())
398 rItem.mbShowTitleBar = false;
399 continue;
402 if (rLayoutItems.size() == 1
403 && rItem.mpPanel->IsTitleBarOptional())
405 // There is only one panel and its title bar is
406 // optional => hide it.
407 rAvailableHeight -= nDeckSeparatorHeight;
408 rItem.mbShowTitleBar = false;
410 else
412 // Show the title bar and a separator above and below
413 // the title bar.
414 const sal_Int32 nPanelTitleBarHeight (Theme::GetInteger(Theme::Int_PanelTitleBarHeight) * rItem.mpPanel->GetDPIScaleFactor());
416 rAvailableHeight -= nPanelTitleBarHeight;
417 rAvailableHeight -= nDeckSeparatorHeight;
420 if (rItem.mpPanel->IsExpanded() && rItem.mpPanel->GetPanelComponent().is())
422 Reference<ui::XSidebarPanel> xPanel (rItem.mpPanel->GetPanelComponent());
424 rItem.maLayoutSize = xPanel->getHeightForWidth(rContentBox.GetWidth());
425 if (!(0 <= rItem.maLayoutSize.Minimum && rItem.maLayoutSize.Minimum <= rItem.maLayoutSize.Preferred
426 && rItem.maLayoutSize.Preferred <= rItem.maLayoutSize.Maximum))
428 SAL_INFO("sfx.sidebar", "Please follow LayoutSize constraints: 0 ≤ "
429 "Minimum ≤ Preferred ≤ Maximum."
430 " Currently: Minimum: "
431 << rItem.maLayoutSize.Minimum
432 << " Preferred: " << rItem.maLayoutSize.Preferred
433 << " Maximum: " << rItem.maLayoutSize.Maximum);
436 sal_Int32 nWidth = rMinimalWidth;
439 // The demo sidebar extension "Analog Clock" fails with
440 // java.lang.AbstractMethodError here
441 nWidth = xPanel->getMinimalWidth();
443 catch (...)
447 uno::Reference<frame::XDesktop2> xDesktop
448 = frame::Desktop::create(comphelper::getProcessComponentContext());
449 uno::Reference<frame::XFrame> xFrame = xDesktop->getActiveFrame();
450 if (xFrame.is())
452 SidebarController* pController
453 = SidebarController::GetSidebarControllerForFrame(xFrame);
454 if (pController && pController->getMaximumWidth() < nWidth)
456 // Add 100 extra pixels to still have the sidebar resizable
457 // (See also documentation of XSidebarPanel::getMinimalWidth)
458 pController->setMaximumWidth(nWidth + 100);
462 if (nWidth > rMinimalWidth)
463 rMinimalWidth = nWidth;
465 else
466 rItem.maLayoutSize = ui::LayoutSize(MinimalPanelHeight, -1, 0);
470 void DistributeHeights (
471 ::std::vector<LayoutItem>& rLayoutItems,
472 const sal_Int32 nHeightToDistribute,
473 const sal_Int32 nContainerHeight,
474 const bool bMinimumHeightIsBase)
476 if (nHeightToDistribute <= 0)
477 return;
479 sal_Int32 nRemainingHeightToDistribute (nHeightToDistribute);
481 // Compute the weights as difference between panel base height
482 // (either its minimum or preferred height) and the container height.
483 sal_Int32 nTotalWeight (0);
484 sal_Int32 nNoMaximumCount (0);
486 for (auto& rItem : rLayoutItems)
488 if (rItem.maLayoutSize.Maximum == 0)
489 continue;
490 if (rItem.maLayoutSize.Maximum < 0)
491 ++nNoMaximumCount;
493 const sal_Int32 nBaseHeight (
494 bMinimumHeightIsBase
495 ? rItem.maLayoutSize.Minimum
496 : rItem.maLayoutSize.Preferred);
497 if (nBaseHeight < nContainerHeight)
499 rItem.mnWeight = nContainerHeight - nBaseHeight;
500 nTotalWeight += rItem.mnWeight;
504 if (nTotalWeight == 0)
505 return;
507 // First pass of height distribution.
508 for (auto& rItem : rLayoutItems)
510 const sal_Int32 nBaseHeight (
511 bMinimumHeightIsBase
512 ? rItem.maLayoutSize.Minimum
513 : rItem.maLayoutSize.Preferred);
514 sal_Int32 nDistributedHeight (rItem.mnWeight * nHeightToDistribute / nTotalWeight);
515 if (nBaseHeight+nDistributedHeight > rItem.maLayoutSize.Maximum
516 && rItem.maLayoutSize.Maximum >= 0)
518 nDistributedHeight = ::std::max<sal_Int32>(0, rItem.maLayoutSize.Maximum - nBaseHeight);
520 rItem.mnDistributedHeight = nDistributedHeight;
521 nRemainingHeightToDistribute -= nDistributedHeight;
524 if (nRemainingHeightToDistribute == 0)
525 return;
526 OSL_ASSERT(nRemainingHeightToDistribute > 0);
528 // It is possible that not all of the height could be distributed
529 // because of Maximum heights being smaller than expected.
530 // Distribute the remaining height between the panels that have no
531 // Maximum (ie Maximum==-1).
532 if (nNoMaximumCount == 0)
534 // There are no panels with unrestricted height.
535 return;
538 const sal_Int32 nAdditionalHeightPerPanel(nRemainingHeightToDistribute / nNoMaximumCount);
539 // Handle rounding error.
540 sal_Int32 nAdditionalHeightForFirstPanel (nRemainingHeightToDistribute
541 - nNoMaximumCount*nAdditionalHeightPerPanel);
543 for (auto& rItem : rLayoutItems)
545 if (rItem.maLayoutSize.Maximum < 0)
547 rItem.mnDistributedHeight += nAdditionalHeightPerPanel + nAdditionalHeightForFirstPanel;
548 nRemainingHeightToDistribute -= nAdditionalHeightPerPanel + nAdditionalHeightForFirstPanel;
552 OSL_ASSERT(nRemainingHeightToDistribute==0);
555 tools::Rectangle PlaceDeckTitle (
556 vcl::Window& rDeckTitleBar,
557 const tools::Rectangle& rAvailableSpace)
559 if (static_cast<DockingWindow*>(rDeckTitleBar.GetParent()->GetParent())->IsFloatingMode())
561 // When the side bar is undocked then the outer system window displays the deck title.
562 rDeckTitleBar.Hide();
563 return rAvailableSpace;
565 else
567 const sal_Int32 nDeckTitleBarHeight (Theme::GetInteger(Theme::Int_DeckTitleBarHeight) * rDeckTitleBar.GetDPIScaleFactor());
568 rDeckTitleBar.setPosSizePixel(
569 rAvailableSpace.Left(),
570 rAvailableSpace.Top(),
571 rAvailableSpace.GetWidth(),
572 nDeckTitleBarHeight);
573 rDeckTitleBar.Show();
574 return tools::Rectangle(
575 rAvailableSpace.Left(),
576 rAvailableSpace.Top() + nDeckTitleBarHeight,
577 rAvailableSpace.Right(),
578 rAvailableSpace.Bottom());
582 tools::Rectangle PlaceVerticalScrollBar (
583 ScrollBar& rVerticalScrollBar,
584 const tools::Rectangle& rAvailableSpace,
585 const bool bShowVerticalScrollBar)
587 if (bShowVerticalScrollBar)
589 const sal_Int32 nScrollBarWidth (rVerticalScrollBar.GetSizePixel().Width());
590 rVerticalScrollBar.setPosSizePixel(
591 rAvailableSpace.Right() - nScrollBarWidth + 1,
592 rAvailableSpace.Top(),
593 nScrollBarWidth,
594 rAvailableSpace.GetHeight());
595 rVerticalScrollBar.Show();
596 return tools::Rectangle(
597 rAvailableSpace.Left(),
598 rAvailableSpace.Top(),
599 rAvailableSpace.Right() - nScrollBarWidth,
600 rAvailableSpace.Bottom());
602 else
604 rVerticalScrollBar.Hide();
605 return rAvailableSpace;
609 void SetupVerticalScrollBar(
610 ScrollBar& rVerticalScrollBar,
611 const sal_Int32 nContentHeight,
612 const sal_Int32 nVisibleHeight)
614 OSL_ASSERT(nContentHeight > nVisibleHeight);
616 rVerticalScrollBar.SetRangeMin(0);
617 rVerticalScrollBar.SetRangeMax(nContentHeight-1);
618 rVerticalScrollBar.SetVisibleSize(nVisibleHeight);
621 void UpdateFiller (
622 vcl::Window& rFiller,
623 const tools::Rectangle& rBox)
625 if (rBox.GetHeight() > 0)
627 // Show the filler.
628 rFiller.SetBackground(Theme::GetColor(Theme::Color_PanelBackground));
629 rFiller.SetPosSizePixel(rBox.TopLeft(), rBox.GetSize());
630 rFiller.Show();
632 else
634 // Hide the filler.
635 rFiller.Hide();
641 } // end of namespace sfx2::sidebar
643 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */