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