1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 #include "precompiled_sfx2.hxx"
25 
26 #include "DeckLayouter.hxx"
27 #include "sfx2/sidebar/Theme.hxx"
28 #include "Panel.hxx"
29 #include "PanelTitleBar.hxx"
30 #include "Deck.hxx"
31 
32 #include <vcl/window.hxx>
33 #include <vcl/scrbar.hxx>
34 
35 using namespace ::com::sun::star;
36 using namespace ::com::sun::star::uno;
37 
38 
39 namespace sfx2 { namespace sidebar {
40 
41 
42 namespace {
43     static const sal_Int32 MinimalPanelHeight (25);
44 }
45 
46 #define IterateLayoutItems(iterator_name,container)                     \
47     for(::std::vector<LayoutItem>::iterator                             \
48                    iterator_name(container.begin()),                    \
49                    iEnd(container.end());                               \
50         iterator_name!=iEnd;                                            \
51         ++iterator_name)
52 
53 
54 
LayoutDeck(const Rectangle aContentArea,SharedPanelContainer & rPanels,Window & rDeckTitleBar,Window & rScrollClipWindow,Window & rScrollContainer,Window & rFiller,ScrollBar & rVerticalScrollBar)55 void DeckLayouter::LayoutDeck (
56     const Rectangle aContentArea,
57     SharedPanelContainer& rPanels,
58     Window& rDeckTitleBar,
59     Window& rScrollClipWindow,
60     Window& rScrollContainer,
61     Window& rFiller,
62     ScrollBar& rVerticalScrollBar)
63 {
64     if (aContentArea.GetWidth()<=0 || aContentArea.GetHeight()<=0)
65         return;
66     Rectangle aBox (PlaceDeckTitle(rDeckTitleBar, aContentArea));
67 
68     if ( ! rPanels.empty())
69     {
70         // Prepare the layout item container.
71         ::std::vector<LayoutItem> aLayoutItems;
72         aLayoutItems.resize(rPanels.size());
73         for (sal_Int32 nIndex(0),nCount(rPanels.size()); nIndex<nCount; ++nIndex)
74         {
75             aLayoutItems[nIndex].mpPanel = rPanels[nIndex];
76             aLayoutItems[nIndex].mnPanelIndex = nIndex;
77         }
78         aBox = LayoutPanels(
79             aBox,
80             aLayoutItems,
81             rScrollClipWindow,
82             rScrollContainer,
83             rVerticalScrollBar,
84             false);
85     }
86     UpdateFiller(rFiller, aBox);
87 }
88 
89 
90 
91 
LayoutPanels(const Rectangle aContentArea,::std::vector<LayoutItem> & rLayoutItems,Window & rScrollClipWindow,Window & rScrollContainer,ScrollBar & rVerticalScrollBar,const bool bShowVerticalScrollBar)92 Rectangle DeckLayouter::LayoutPanels (
93     const Rectangle aContentArea,
94     ::std::vector<LayoutItem>& rLayoutItems,
95     Window& rScrollClipWindow,
96     Window& rScrollContainer,
97     ScrollBar& rVerticalScrollBar,
98     const bool bShowVerticalScrollBar)
99 {
100     Rectangle aBox (PlaceVerticalScrollBar(rVerticalScrollBar, aContentArea, bShowVerticalScrollBar));
101 
102     const sal_Int32 nWidth (aBox.GetWidth());
103     // const sal_Int32 nPanelTitleBarHeight (Theme::GetInteger(Theme::Int_PanelTitleBarHeight));
104 
105     // Prepare the separators, horizontal lines above and below the
106     // panel titles.
107     // const sal_Int32 nDeckSeparatorHeight (Theme::GetInteger(Theme::Int_DeckSeparatorHeight));
108 
109     // Get the requested heights of the panels and the available
110     // height that is left when all panel titles and separators are
111     // taken into account.
112     sal_Int32 nAvailableHeight (aBox.GetHeight());
113     GetRequestedSizes(rLayoutItems, nAvailableHeight, aBox);
114     const sal_Int32 nTotalDecorationHeight (aBox.GetHeight() - nAvailableHeight);
115 
116     // Analyze the requested heights.
117     // Determine the height that is available for panel content
118     // and count the different layouts.
119     sal_Int32 nTotalPreferredHeight (0);
120     sal_Int32 nTotalMinimumHeight (0);
121     IterateLayoutItems(iItem,rLayoutItems)
122     {
123         nTotalMinimumHeight += iItem->maLayoutSize.Minimum;
124         nTotalPreferredHeight += iItem->maLayoutSize.Preferred;
125     }
126 
127     if (nTotalMinimumHeight > nAvailableHeight
128         && ! bShowVerticalScrollBar)
129     {
130         // Not enough space, even when all panels are shrunk to their
131         // minimum height.
132         // Show a vertical scrollbar.
133         return LayoutPanels(
134             aContentArea,
135             rLayoutItems,
136             rScrollClipWindow,
137             rScrollContainer,
138             rVerticalScrollBar,
139             true);
140     }
141 
142     // We are now in one of three modes.
143     // - The preferred height fits into the available size:
144     //   Use the preferred size, distribute the remaining height by
145     //   enlarging panels.
146     // - The total minimum height fits into the available size:
147     //   Use the minimum size, distribute the remaining height by
148     //   enlarging panels.
149     // - The total minimum height does not fit into the available
150     //   size:
151     //   Use the unmodified preferred height for all panels.
152 
153     LayoutMode eMode (MinimumOrLarger);
154     if (bShowVerticalScrollBar)
155         eMode = Preferred;
156     else if (nTotalPreferredHeight <= nAvailableHeight)
157         eMode = PreferredOrLarger;
158     else
159         eMode = MinimumOrLarger;
160 
161     if (eMode != Preferred)
162     {
163         const sal_Int32 nTotalHeight (eMode==MinimumOrLarger ? nTotalMinimumHeight : nTotalPreferredHeight);
164 
165         DistributeHeights(
166             rLayoutItems,
167             nAvailableHeight-nTotalHeight,
168             aBox.GetHeight(),
169             eMode==MinimumOrLarger);
170     }
171 
172     // Set position and size of the mpScrollClipWindow to the available
173     // size. Its child, the mpScrollContainer, may have a bigger
174     // height.
175     rScrollClipWindow.SetPosSizePixel(aBox.Left(), aBox.Top(), aBox.GetWidth(), aBox.GetHeight());
176 
177     const sal_Int32 nContentHeight (
178         eMode==Preferred
179             ? nTotalPreferredHeight + nTotalDecorationHeight
180             : aBox.GetHeight());
181     sal_Int32 nY = rVerticalScrollBar.GetThumbPos();
182     if (nContentHeight-nY < aBox.GetHeight())
183         nY = nContentHeight-aBox.GetHeight();
184     if (nY < 0)
185         nY = 0;
186     rScrollContainer.SetPosSizePixel(
187         0,
188         -nY,
189         nWidth,
190         nContentHeight);
191 
192     if (bShowVerticalScrollBar)
193         SetupVerticalScrollBar(rVerticalScrollBar, nContentHeight, aBox.GetHeight());
194 
195     const sal_Int32 nUsedHeight (PlacePanels(rLayoutItems, nWidth, eMode, rScrollContainer));
196     aBox.Top() += nUsedHeight;
197     return aBox;
198 }
199 
200 
201 
202 
PlacePanels(::std::vector<LayoutItem> & rLayoutItems,const sal_Int32 nWidth,const LayoutMode eMode,Window & rScrollContainer)203 sal_Int32 DeckLayouter::PlacePanels (
204     ::std::vector<LayoutItem>& rLayoutItems,
205     const sal_Int32 nWidth,
206     const LayoutMode eMode,
207     Window& rScrollContainer)
208 {
209     ::std::vector<sal_Int32> aSeparators;
210     const sal_Int32 nDeckSeparatorHeight (Theme::GetInteger(Theme::Int_DeckSeparatorHeight));
211     const sal_Int32 nPanelTitleBarHeight (Theme::GetInteger(Theme::Int_PanelTitleBarHeight));
212     sal_Int32 nY (0);
213 
214     // Assign heights and places.
215     IterateLayoutItems(iItem,rLayoutItems)
216     {
217         if( !bool(iItem->mpPanel))
218             continue;
219 
220         Panel& rPanel (*iItem->mpPanel);
221 
222         // Separator above the panel title bar.
223         aSeparators.push_back(nY);
224         nY += nDeckSeparatorHeight;
225 
226         // Place the title bar.
227         PanelTitleBar* pTitleBar = rPanel.GetTitleBar();
228         if (pTitleBar != NULL)
229         {
230             if (iItem->mbShowTitleBar)
231             {
232                 pTitleBar->SetPosSizePixel(0, nY, nWidth, nPanelTitleBarHeight);
233                 pTitleBar->Show();
234                 nY += nPanelTitleBarHeight;
235             }
236             else
237             {
238                 pTitleBar->Hide();
239             }
240         }
241 
242         if (rPanel.IsExpanded())
243         {
244             rPanel.Show();
245 
246             // Determine the height of the panel depending on layout
247             // mode and distributed heights.
248             sal_Int32 nPanelHeight (0);
249             switch(eMode)
250             {
251                 case MinimumOrLarger:
252                     nPanelHeight = iItem->maLayoutSize.Minimum + iItem->mnDistributedHeight;
253                     break;
254                 case PreferredOrLarger:
255                     nPanelHeight = iItem->maLayoutSize.Preferred + iItem->mnDistributedHeight;
256                     break;
257                 case Preferred:
258                     nPanelHeight = iItem->maLayoutSize.Preferred;
259                     break;
260                 default:
261                     OSL_ASSERT(false);
262                     break;
263             }
264 
265             // Place the panel.
266             rPanel.SetPosSizePixel(0, nY, nWidth, nPanelHeight);
267             rPanel.Invalidate();
268 
269             nY += nPanelHeight;
270         }
271         else
272         {
273             rPanel.Hide();
274 
275             // Add a separator below the collapsed panel, if it is the
276             // last panel in the deck.
277             if (iItem == rLayoutItems.end()-1)
278             {
279                 // Separator below the panel title bar.
280                 aSeparators.push_back(nY);
281                 nY += nDeckSeparatorHeight;
282             }
283         }
284     }
285 
286     Deck::ScrollContainerWindow* pScrollContainerWindow
287         = dynamic_cast<Deck::ScrollContainerWindow*>(&rScrollContainer);
288     if (pScrollContainerWindow != NULL)
289         pScrollContainerWindow->SetSeparators(aSeparators);
290 
291     return nY;
292 }
293 
294 
295 
296 
GetRequestedSizes(::std::vector<LayoutItem> & rLayoutItems,sal_Int32 & rAvailableHeight,const Rectangle & rContentBox)297 void DeckLayouter::GetRequestedSizes (
298     ::std::vector<LayoutItem>& rLayoutItems,
299     sal_Int32& rAvailableHeight,
300     const Rectangle& rContentBox)
301 {
302     rAvailableHeight = rContentBox.GetHeight();
303 
304     const sal_Int32 nPanelTitleBarHeight (Theme::GetInteger(Theme::Int_PanelTitleBarHeight));
305     const sal_Int32 nDeckSeparatorHeight (Theme::GetInteger(Theme::Int_DeckSeparatorHeight));
306 
307     IterateLayoutItems(iItem,rLayoutItems)
308     {
309         ui::LayoutSize aLayoutSize (ui::LayoutSize(0,0,0));
310         if( bool(iItem->mpPanel))
311         {
312             if (rLayoutItems.size() == 1
313                 && iItem->mpPanel->IsTitleBarOptional())
314             {
315                 // There is only one panel and its title bar is
316                 // optional => hide it.
317                 rAvailableHeight -= nDeckSeparatorHeight;
318                 iItem->mbShowTitleBar = false;
319             }
320             else
321             {
322                 // Show the title bar and a separator above and below
323                 // the title bar.
324                 rAvailableHeight -= nPanelTitleBarHeight;
325                 rAvailableHeight -= nDeckSeparatorHeight;
326             }
327 
328             if (iItem->mpPanel->IsExpanded())
329             {
330                 Reference<ui::XSidebarPanel> xPanel (iItem->mpPanel->GetPanelComponent());
331                 if (xPanel.is())
332                     aLayoutSize = xPanel->getHeightForWidth(rContentBox.GetWidth());
333                 else
334                     aLayoutSize = ui::LayoutSize(MinimalPanelHeight, -1, 0);
335             }
336         }
337         iItem->maLayoutSize = aLayoutSize;
338     }
339 }
340 
341 
342 
343 
DistributeHeights(::std::vector<LayoutItem> & rLayoutItems,const sal_Int32 nHeightToDistribute,const sal_Int32 nContainerHeight,const bool bMinimumHeightIsBase)344 void DeckLayouter::DistributeHeights (
345     ::std::vector<LayoutItem>& rLayoutItems,
346     const sal_Int32 nHeightToDistribute,
347     const sal_Int32 nContainerHeight,
348     const bool bMinimumHeightIsBase)
349 {
350     if (nHeightToDistribute <= 0)
351         return;
352 
353     sal_Int32 nRemainingHeightToDistribute (nHeightToDistribute);
354 
355     // Compute the weights as difference between panel base height
356     // (either its minimum or preferred height) and the container height.
357     sal_Int32 nTotalWeight (0);
358     sal_Int32 nNoMaximumCount (0);
359     IterateLayoutItems(iItem,rLayoutItems)
360     {
361         if (iItem->maLayoutSize.Maximum == 0)
362             continue;
363         if (iItem->maLayoutSize.Maximum < 0)
364             ++nNoMaximumCount;
365 
366         const sal_Int32 nBaseHeight (
367             bMinimumHeightIsBase
368                 ? iItem->maLayoutSize.Minimum
369                 : iItem->maLayoutSize.Preferred);
370         if (nBaseHeight < nContainerHeight)
371         {
372             iItem->mnWeight = nContainerHeight - nBaseHeight;
373             nTotalWeight += iItem->mnWeight;
374         }
375     }
376 
377 	if (nTotalWeight == 0)
378 		return;
379 
380     // First pass of height distribution.
381     IterateLayoutItems(iItem,rLayoutItems)
382     {
383         const sal_Int32 nBaseHeight (
384             bMinimumHeightIsBase
385                 ? iItem->maLayoutSize.Minimum
386                 : iItem->maLayoutSize.Preferred);
387         sal_Int32 nDistributedHeight (iItem->mnWeight * nHeightToDistribute / nTotalWeight);
388         if (nBaseHeight+nDistributedHeight > iItem->maLayoutSize.Maximum
389             && iItem->maLayoutSize.Maximum >= 0)
390         {
391             nDistributedHeight = ::std::max<sal_Int32>(0,iItem->maLayoutSize.Maximum - nBaseHeight);
392         }
393         iItem->mnDistributedHeight = nDistributedHeight;
394         nRemainingHeightToDistribute -= nDistributedHeight;
395     }
396 
397     if (nRemainingHeightToDistribute == 0)
398         return;
399     OSL_ASSERT(nRemainingHeightToDistribute > 0);
400 
401     // It is possible that not all of the height could be distributed
402     // because of Maximum heights being smaller than expected.
403     // Distribute the remaining height between the panels that have no
404     // Maximum (ie Maximum==-1).
405     if (nNoMaximumCount == 0)
406     {
407         // There are no panels with unrestricted height.
408         return;
409     }
410     const sal_Int32 nAdditionalHeightPerPanel (nRemainingHeightToDistribute / nNoMaximumCount);
411     // Handle rounding error.
412     sal_Int32 nAdditionalHeightForFirstPanel (nRemainingHeightToDistribute
413         - nNoMaximumCount*nAdditionalHeightPerPanel);
414     IterateLayoutItems(iItem,rLayoutItems)
415     {
416         if (iItem->maLayoutSize.Maximum < 0)
417         {
418             iItem->mnDistributedHeight += nAdditionalHeightPerPanel + nAdditionalHeightForFirstPanel;
419             nRemainingHeightToDistribute -= nAdditionalHeightPerPanel + nAdditionalHeightForFirstPanel;
420         }
421     }
422 
423     OSL_ASSERT(nRemainingHeightToDistribute==0);
424 }
425 
426 
427 
428 
PlaceDeckTitle(Window & rDeckTitleBar,const Rectangle & rAvailableSpace)429 Rectangle DeckLayouter::PlaceDeckTitle (
430     Window& rDeckTitleBar,
431     const Rectangle& rAvailableSpace)
432 {
433     if (static_cast<DockingWindow*>(rDeckTitleBar.GetParent()->GetParent())->IsFloatingMode())
434     {
435         // When the side bar is undocked then the outer system window displays the deck title.
436         rDeckTitleBar.Hide();
437         return rAvailableSpace;
438     }
439     else
440     {
441         const sal_Int32 nDeckTitleBarHeight (Theme::GetInteger(Theme::Int_DeckTitleBarHeight));
442         rDeckTitleBar.SetPosSizePixel(
443             rAvailableSpace.Left(),
444             rAvailableSpace.Top(),
445             rAvailableSpace.GetWidth(),
446             nDeckTitleBarHeight);
447         rDeckTitleBar.Show();
448         return Rectangle(
449             rAvailableSpace.Left(),
450             rAvailableSpace.Top() + nDeckTitleBarHeight,
451             rAvailableSpace.Right(),
452             rAvailableSpace.Bottom());
453     }
454 }
455 
456 
457 
458 
PlaceVerticalScrollBar(ScrollBar & rVerticalScrollBar,const Rectangle & rAvailableSpace,const bool bShowVerticalScrollBar)459 Rectangle DeckLayouter::PlaceVerticalScrollBar (
460     ScrollBar& rVerticalScrollBar,
461     const Rectangle& rAvailableSpace,
462     const bool bShowVerticalScrollBar)
463 {
464     if (bShowVerticalScrollBar)
465     {
466         const sal_Int32 nScrollBarWidth (rVerticalScrollBar.GetSizePixel().Width());
467         rVerticalScrollBar.SetPosSizePixel(
468             rAvailableSpace.Right() - nScrollBarWidth + 1,
469             rAvailableSpace.Top(),
470             nScrollBarWidth,
471             rAvailableSpace.GetHeight());
472         rVerticalScrollBar.Show();
473         return Rectangle(
474             rAvailableSpace.Left(),
475             rAvailableSpace.Top(),
476             rAvailableSpace.Right() - nScrollBarWidth,
477             rAvailableSpace.Bottom());
478     }
479     else
480     {
481         rVerticalScrollBar.Hide();
482         return rAvailableSpace;
483     }
484 }
485 
486 
487 
488 
SetupVerticalScrollBar(ScrollBar & rVerticalScrollBar,const sal_Int32 nContentHeight,const sal_Int32 nVisibleHeight)489 void DeckLayouter::SetupVerticalScrollBar(
490     ScrollBar& rVerticalScrollBar,
491     const sal_Int32 nContentHeight,
492     const sal_Int32 nVisibleHeight)
493 {
494     OSL_ASSERT(nContentHeight > nVisibleHeight);
495 
496     rVerticalScrollBar.SetRangeMin(0);
497     rVerticalScrollBar.SetRangeMax(nContentHeight-1);
498     rVerticalScrollBar.SetVisibleSize(nVisibleHeight);
499 }
500 
501 
502 
503 
UpdateFiller(Window & rFiller,const Rectangle & rBox)504 void DeckLayouter::UpdateFiller (
505     Window& rFiller,
506     const Rectangle& rBox)
507 {
508     if (rBox.GetHeight() > 0)
509     {
510         // Show the filler.
511         rFiller.SetBackground(Theme::GetPaint(Theme::Paint_PanelBackground).GetWallpaper());
512         rFiller.SetPosSizePixel(rBox.TopLeft(), rBox.GetSize());
513         rFiller.Show();
514     }
515     else
516     {
517         // Hide the filler.
518         rFiller.Hide();
519     }
520 }
521 
522 
523 
524 } } // end of namespace sfx2::sidebar
525