LibreOffice Module sfx2 (master) 1
DeckLayouter.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
27#include <sfx2/viewsh.hxx>
28#include <comphelper/lok.hxx>
29#include <osl/diagnose.h>
30
32
33#include <com/sun/star/uno/Reference.hxx>
34#include <com/sun/star/frame/Desktop.hpp>
35#include <com/sun/star/frame/XDesktop2.hpp>
36#include <com/sun/star/frame/XFrame.hpp>
37#include <com/sun/star/ui/XSidebarPanel.hpp>
38
39#include <utility>
40
41using namespace css;
42using namespace css::uno;
43
44namespace sfx2::sidebar {
45
46namespace {
47 const sal_Int32 MinimalPanelHeight (25);
48
49 enum LayoutMode
50 {
51 MinimumOrLarger,
52 PreferredOrLarger,
53 Preferred
54 };
55 class LayoutItem
56 {
57 public:
58 std::shared_ptr<Panel> mpPanel;
59 css::ui::LayoutSize maLayoutSize;
61 sal_Int32 mnWeight;
63
64 LayoutItem(std::shared_ptr<Panel> pPanel)
65 : mpPanel(std::move(pPanel))
66 , maLayoutSize(0, 0, 0)
68 , mnWeight(0)
69 , mbShowTitleBar(true)
70 {
71 }
72 };
73 void LayoutPanels (
74 const tools::Rectangle& rContentArea,
75 sal_Int32& rMinimalWidth,
76 sal_Int32& rMinimalHeight,
77 ::std::vector<LayoutItem>& rLayoutItems,
78 weld::ScrolledWindow& pVerticalScrollBar,
79 const bool bShowVerticalScrollBar);
80 void GetRequestedSizes (
81 ::std::vector<LayoutItem>& rLayoutItem,
82 sal_Int32& rAvailableHeight,
83 sal_Int32& rMinimalWidth,
84 const tools::Rectangle& rContentBox);
85 void DistributeHeights (
86 ::std::vector<LayoutItem>& rLayoutItems,
87 const sal_Int32 nHeightToDistribute,
88 const sal_Int32 nContainerHeight,
89 const bool bMinimumHeightIsBase);
90 sal_Int32 PlacePanels (
91 ::std::vector<LayoutItem>& rLayoutItems,
92 const LayoutMode eMode_);
93 tools::Rectangle PlaceDeckTitle (
94 const SidebarDockingWindow* pDockingWindow,
95 DeckTitleBar& rTitleBar,
96 const tools::Rectangle& rAvailableSpace);
97 tools::Rectangle PlaceVerticalScrollBar (
98 weld::ScrolledWindow& rVerticalScrollBar,
99 const tools::Rectangle& rAvailableSpace,
100 const bool bShowVerticalScrollBar);
101 void SetupVerticalScrollBar(
102 weld::ScrolledWindow& rVerticalScrollBar,
103 const sal_Int32 nContentHeight,
104 const sal_Int32 nVisibleHeight);
105}
106
108 const SidebarDockingWindow* pDockingWindow,
109 const tools::Rectangle& rContentArea,
110 sal_Int32& rMinimalWidth,
111 sal_Int32& rMinimalHeight,
112 SharedPanelContainer& rPanels,
113 DeckTitleBar& rDeckTitleBar,
114 weld::ScrolledWindow& rVerticalScrollBar)
115{
116 if (rContentArea.GetWidth()<=0 || rContentArea.GetHeight()<=0)
117 return;
118 tools::Rectangle aBox(PlaceDeckTitle(pDockingWindow, rDeckTitleBar, rContentArea));
119
120 if ( rPanels.empty())
121 return;
122
123 // Prepare the layout item container.
124 ::std::vector<LayoutItem> aLayoutItems;
125 aLayoutItems.reserve(rPanels.size());
126 for (auto& rPanel : rPanels)
127 aLayoutItems.emplace_back(rPanel);
128
129 LayoutPanels(
130 aBox,
131 rMinimalWidth,
132 rMinimalHeight,
133 aLayoutItems,
134 rVerticalScrollBar,
135 false);
136}
137
138namespace {
139
140void LayoutPanels (
141 const tools::Rectangle& rContentArea,
142 sal_Int32& rMinimalWidth,
143 sal_Int32& rMinimalHeight,
144 ::std::vector<LayoutItem>& rLayoutItems,
145 weld::ScrolledWindow& rVerticalScrollBar,
146 const bool bShowVerticalScrollBar)
147{
148 tools::Rectangle aBox (PlaceVerticalScrollBar(rVerticalScrollBar, rContentArea, bShowVerticalScrollBar));
149
150 // Get the requested heights of the panels and the available
151 // height that is left when all panel titles and separators are
152 // taken into account.
153 sal_Int32 nAvailableHeight (aBox.GetHeight());
154 GetRequestedSizes(rLayoutItems, nAvailableHeight, rMinimalWidth, aBox);
155 const sal_Int32 nTotalDecorationHeight (aBox.GetHeight() - nAvailableHeight);
156
157 // Analyze the requested heights.
158 // Determine the height that is available for panel content
159 // and count the different layouts.
160 sal_Int32 nTotalPreferredHeight (0);
161 sal_Int32 nTotalMinimumHeight (0);
162
163 for (const auto& rItem : rLayoutItems)
164 {
165 nTotalMinimumHeight += rItem.maLayoutSize.Minimum;
166 nTotalPreferredHeight += rItem.maLayoutSize.Preferred;
167 }
168
169 if (nTotalMinimumHeight > nAvailableHeight && !bShowVerticalScrollBar
171 {
172 // Not enough space, even when all panels are shrunk to their
173 // minimum height.
174 // Show a vertical scrollbar.
175 LayoutPanels(
176 rContentArea,
177 rMinimalWidth,
178 rMinimalHeight,
179 rLayoutItems,
180 rVerticalScrollBar,
181 true);
182 return;
183 }
184
185 // We are now in one of three modes.
186 // - The preferred height fits into the available size:
187 // Use the preferred size, distribute the remaining height by
188 // enlarging panels.
189 // - The total minimum height fits into the available size:
190 // Use the minimum size, distribute the remaining height by
191 // enlarging panels.
192 // - The total minimum height does not fit into the available
193 // size:
194 // Use the unmodified preferred height for all panels.
195
196 LayoutMode eMode(MinimumOrLarger);
197 if (bShowVerticalScrollBar)
198 {
199 eMode = Preferred;
200
201 const sal_Int32 nContentHeight(nTotalPreferredHeight + nTotalDecorationHeight);
202 SetupVerticalScrollBar(rVerticalScrollBar, nContentHeight, aBox.GetHeight());
203 }
204 else
205 {
206 if (nTotalPreferredHeight <= nAvailableHeight)
207 eMode = PreferredOrLarger;
208 else
209 eMode = MinimumOrLarger;
210
211 const sal_Int32 nTotalHeight (eMode==MinimumOrLarger ? nTotalMinimumHeight : nTotalPreferredHeight);
212
213 DistributeHeights(
214 rLayoutItems,
215 nAvailableHeight-nTotalHeight,
216 aBox.GetHeight(),
217 eMode==MinimumOrLarger);
218 }
219
220 const sal_Int32 nUsedHeight(PlacePanels(rLayoutItems, eMode));
221 rMinimalHeight = nUsedHeight;
222}
223
224sal_Int32 PlacePanels (
225 ::std::vector<LayoutItem>& rLayoutItems,
226 const LayoutMode eMode)
227{
228 const sal_Int32 nDeckSeparatorHeight (Theme::GetInteger(Theme::Int_DeckSeparatorHeight));
229 sal_Int32 nY (0);
230
231 // Assign heights and places.
232 for(::std::vector<LayoutItem>::const_iterator iItem(rLayoutItems.begin()),
233 iEnd(rLayoutItems.end());
234 iItem!=iEnd;
235 ++iItem)
236 {
237 if (!iItem->mpPanel)
238 continue;
239
240 Panel& rPanel (*iItem->mpPanel);
241
242 rPanel.set_margin_top(nDeckSeparatorHeight);
243 rPanel.set_margin_bottom(0);
244
245 // Separator above the panel title bar.
246 if (!rPanel.IsLurking())
247 {
248 nY += nDeckSeparatorHeight;
249 }
250
251 bool bShowTitlebar = iItem->mbShowTitleBar;
252 PanelTitleBar* pTitleBar = rPanel.GetTitleBar();
253 pTitleBar->Show(bShowTitlebar);
254 rPanel.set_vexpand(!bShowTitlebar);
255 weld::Container* pContents = rPanel.GetContents();
256 pContents->set_vexpand(true);
257
258 bool bExpanded = rPanel.IsExpanded() && !rPanel.IsLurking();
259 if (bShowTitlebar || bExpanded)
260 {
261 rPanel.Show(true);
262
263 sal_Int32 nPanelHeight(0);
264 if (bExpanded)
265 {
266 // Determine the height of the panel depending on layout
267 // mode and distributed heights.
268 switch(eMode)
269 {
270 case MinimumOrLarger:
271 nPanelHeight = iItem->maLayoutSize.Minimum + iItem->mnDistributedHeight;
272 break;
273 case PreferredOrLarger:
274 nPanelHeight = iItem->maLayoutSize.Preferred + iItem->mnDistributedHeight;
275 break;
276 case Preferred:
277 nPanelHeight = iItem->maLayoutSize.Preferred;
278 break;
279 default:
280 OSL_ASSERT(false);
281 break;
282 }
283 }
284 if (bShowTitlebar)
285 nPanelHeight += pTitleBar->get_preferred_size().Height();
286
287 rPanel.SetHeightPixel(nPanelHeight);
288
289 nY += nPanelHeight;
290 }
291 else
292 {
293 rPanel.Show(false);
294 }
295
296 if (!bExpanded)
297 {
298 // Add a separator below the collapsed panel, if it is the
299 // last panel in the deck.
300 if (iItem == rLayoutItems.end()-1)
301 {
302 // Separator below the panel title bar.
303 rPanel.set_margin_bottom(nDeckSeparatorHeight);
304 nY += nDeckSeparatorHeight;
305 }
306 }
307 }
308
309 return nY;
310}
311
312void GetRequestedSizes (
313 ::std::vector<LayoutItem>& rLayoutItems,
314 sal_Int32& rAvailableHeight,
315 sal_Int32& rMinimalWidth,
316 const tools::Rectangle& rContentBox)
317{
318 rAvailableHeight = rContentBox.GetHeight();
319
320 const sal_Int32 nDeckSeparatorHeight (Theme::GetInteger(Theme::Int_DeckSeparatorHeight));
321
322 for (auto& rItem : rLayoutItems)
323 {
324 rItem.maLayoutSize = ui::LayoutSize(0,0,0);
325
326 if (rItem.mpPanel == nullptr)
327 continue;
328
329 if (rItem.mpPanel->IsLurking())
330 {
331 rItem.mbShowTitleBar = false;
332 continue;
333 }
334
335 if (rLayoutItems.size() == 1
336 && rItem.mpPanel->IsTitleBarOptional())
337 {
338 // There is only one panel and its title bar is
339 // optional => hide it.
340 rAvailableHeight -= nDeckSeparatorHeight;
341 rItem.mbShowTitleBar = false;
342 }
343 else
344 {
345 // Show the title bar and a separator above and below
346 // the title bar.
347 PanelTitleBar* pTitleBar = rItem.mpPanel->GetTitleBar();
348 const sal_Int32 nPanelTitleBarHeight = pTitleBar->get_preferred_size().Height();
349
350 rAvailableHeight -= nPanelTitleBarHeight;
351 rAvailableHeight -= nDeckSeparatorHeight;
352 }
353
354 if (rItem.mpPanel->IsExpanded() && rItem.mpPanel->GetPanelComponent().is())
355 {
356 Reference<ui::XSidebarPanel> xPanel (rItem.mpPanel->GetPanelComponent());
357
358 rItem.maLayoutSize = xPanel->getHeightForWidth(rContentBox.GetWidth());
359 if (!(0 <= rItem.maLayoutSize.Minimum && rItem.maLayoutSize.Minimum <= rItem.maLayoutSize.Preferred
360 && rItem.maLayoutSize.Preferred <= rItem.maLayoutSize.Maximum))
361 {
362 SAL_INFO("sfx.sidebar", "Please follow LayoutSize constraints: 0 ≤ "
363 "Minimum ≤ Preferred ≤ Maximum."
364 " Currently: Minimum: "
365 << rItem.maLayoutSize.Minimum
366 << " Preferred: " << rItem.maLayoutSize.Preferred
367 << " Maximum: " << rItem.maLayoutSize.Maximum);
368 }
369
370 sal_Int32 nWidth = rMinimalWidth;
371 try
372 {
373 // The demo sidebar extension "Analog Clock" fails with
374 // java.lang.AbstractMethodError here
375 nWidth = xPanel->getMinimalWidth();
376 }
377 catch (...)
378 {
379 }
380
381 uno::Reference<frame::XDesktop2> xDesktop
382 = frame::Desktop::create(comphelper::getProcessComponentContext());
383 uno::Reference<frame::XFrame> xFrame = xDesktop->getActiveFrame();
384 if (xFrame.is())
385 {
386 SidebarController* pController
388 if (pController && pController->getMaximumWidth() < nWidth)
389 {
390 // Add 100 extra pixels to still have the sidebar resizable
391 // (See also documentation of XSidebarPanel::getMinimalWidth)
392 pController->setMaximumWidth(nWidth + 100);
393 }
394 }
395
396 if (nWidth > rMinimalWidth)
397 rMinimalWidth = nWidth;
398 }
399 else
400 rItem.maLayoutSize = ui::LayoutSize(MinimalPanelHeight, -1, 0);
401 }
402}
403
404void DistributeHeights (
405 ::std::vector<LayoutItem>& rLayoutItems,
406 const sal_Int32 nHeightToDistribute,
407 const sal_Int32 nContainerHeight,
408 const bool bMinimumHeightIsBase)
409{
410 if (nHeightToDistribute <= 0)
411 return;
412
413 sal_Int32 nRemainingHeightToDistribute (nHeightToDistribute);
414
415 // Compute the weights as difference between panel base height
416 // (either its minimum or preferred height) and the container height.
417 sal_Int32 nTotalWeight (0);
418 sal_Int32 nNoMaximumCount (0);
419
420 for (auto& rItem : rLayoutItems)
421 {
422 if (rItem.maLayoutSize.Maximum == 0)
423 continue;
424 if (rItem.maLayoutSize.Maximum < 0)
425 ++nNoMaximumCount;
426
427 const sal_Int32 nBaseHeight (
428 bMinimumHeightIsBase
429 ? rItem.maLayoutSize.Minimum
430 : rItem.maLayoutSize.Preferred);
431 if (nBaseHeight < nContainerHeight)
432 {
433 rItem.mnWeight = nContainerHeight - nBaseHeight;
434 nTotalWeight += rItem.mnWeight;
435 }
436 }
437
438 if (nTotalWeight == 0)
439 return;
440
441 // First pass of height distribution.
442 for (auto& rItem : rLayoutItems)
443 {
444 const sal_Int32 nBaseHeight (
445 bMinimumHeightIsBase
446 ? rItem.maLayoutSize.Minimum
447 : rItem.maLayoutSize.Preferred);
448 sal_Int32 nDistributedHeight (rItem.mnWeight * nHeightToDistribute / nTotalWeight);
449 if (nBaseHeight+nDistributedHeight > rItem.maLayoutSize.Maximum
450 && rItem.maLayoutSize.Maximum >= 0)
451 {
452 nDistributedHeight = ::std::max<sal_Int32>(0, rItem.maLayoutSize.Maximum - nBaseHeight);
453 }
454 rItem.mnDistributedHeight = nDistributedHeight;
455 nRemainingHeightToDistribute -= nDistributedHeight;
456 }
457
458 if (nRemainingHeightToDistribute == 0)
459 return;
460 OSL_ASSERT(nRemainingHeightToDistribute > 0);
461
462 // It is possible that not all of the height could be distributed
463 // because of Maximum heights being smaller than expected.
464 // Distribute the remaining height between the panels that have no
465 // Maximum (ie Maximum==-1).
466 if (nNoMaximumCount == 0)
467 {
468 // There are no panels with unrestricted height.
469 return;
470 }
471
472 const sal_Int32 nAdditionalHeightPerPanel(nRemainingHeightToDistribute / nNoMaximumCount);
473 // Handle rounding error.
474 sal_Int32 nAdditionalHeightForFirstPanel (nRemainingHeightToDistribute
475 - nNoMaximumCount*nAdditionalHeightPerPanel);
476
477 for (auto& rItem : rLayoutItems)
478 {
479 if (rItem.maLayoutSize.Maximum < 0)
480 {
481 rItem.mnDistributedHeight += nAdditionalHeightPerPanel + nAdditionalHeightForFirstPanel;
482 nRemainingHeightToDistribute -= nAdditionalHeightPerPanel + nAdditionalHeightForFirstPanel;
483 }
484 }
485
486 OSL_ASSERT(nRemainingHeightToDistribute==0);
487}
488
489tools::Rectangle PlaceDeckTitle(
490 const SidebarDockingWindow* pDockingWindow,
491 DeckTitleBar& rDeckTitleBar,
492 const tools::Rectangle& rAvailableSpace)
493{
494 if (pDockingWindow->IsFloatingMode())
495 {
496 // When the side bar is undocked then the outer system window displays the deck title.
497 rDeckTitleBar.Show(false);
498 return rAvailableSpace;
499 }
500 else
501 {
502 rDeckTitleBar.Show(true);
503 const sal_Int32 nDeckTitleBarHeight(rDeckTitleBar.get_preferred_size().Height());
504 return tools::Rectangle(
505 rAvailableSpace.Left(),
506 rAvailableSpace.Top() + nDeckTitleBarHeight,
507 rAvailableSpace.Right(),
508 rAvailableSpace.Bottom());
509 }
510}
511
512tools::Rectangle PlaceVerticalScrollBar (
513 weld::ScrolledWindow& rVerticalScrollBar,
514 const tools::Rectangle& rAvailableSpace,
515 const bool bShowVerticalScrollBar)
516{
517 if (bShowVerticalScrollBar)
518 {
519 const sal_Int32 nScrollBarWidth(rVerticalScrollBar.get_scroll_thickness());
520 rVerticalScrollBar.set_vpolicy(VclPolicyType::ALWAYS);
521 return tools::Rectangle(
522 rAvailableSpace.Left(),
523 rAvailableSpace.Top(),
524 rAvailableSpace.Right() - nScrollBarWidth,
525 rAvailableSpace.Bottom());
526 }
527 else
528 {
529 rVerticalScrollBar.set_vpolicy(VclPolicyType::NEVER);
530 return rAvailableSpace;
531 }
532}
533
534void SetupVerticalScrollBar(
535 weld::ScrolledWindow& rVerticalScrollBar,
536 const sal_Int32 nContentHeight,
537 const sal_Int32 nVisibleHeight)
538{
539 OSL_ASSERT(nContentHeight > nVisibleHeight);
540
541 rVerticalScrollBar.vadjustment_set_upper(nContentHeight-1);
542 rVerticalScrollBar.vadjustment_set_page_size(nVisibleHeight);
543}
544
545}
546
547} // end of namespace sfx2::sidebar
548
549/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
css::ui::LayoutSize maLayoutSize
sal_Int32 mnDistributedHeight
bool mbShowTitleBar
sal_Int32 mnWeight
std::shared_ptr< Panel > mpPanel
static SidebarController * GetSidebarControllerForFrame(const css::uno::Reference< css::frame::XFrame > &rxFrame)
Return the SidebarController object that is associated with the given XFrame.
static sal_Int32 GetInteger(const ThemeItem eItem)
Definition: Theme.cxx:61
constexpr tools::Long GetWidth() const
constexpr tools::Long Top() const
constexpr tools::Long Right() const
constexpr tools::Long GetHeight() const
constexpr tools::Long Left() const
constexpr tools::Long Bottom() const
virtual void vadjustment_set_page_size(int size)=0
virtual int get_scroll_thickness() const=0
virtual void set_vpolicy(VclPolicyType eVPolicy)=0
virtual void vadjustment_set_upper(int upper)=0
virtual void set_vexpand(bool bExpand)=0
Mode eMode
#define SAL_INFO(area, stream)
Reference< XComponentContext > getProcessComponentContext()
void LayoutDeck(const SidebarDockingWindow *pDockingWindow, const tools::Rectangle &rContentArea, sal_Int32 &rMinimalWidth, sal_Int32 &rMinimalHeight, SharedPanelContainer &rPanels, DeckTitleBar &pDeckTitleBar, weld::ScrolledWindow &pVerticalScrollBar)
std::vector< std::shared_ptr< Panel > > SharedPanelContainer
Definition: Panel.hxx:118
Reference< XFrame > xFrame