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 "PanelTitleBar.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 
LayoutDeck(const Rectangle aContentArea,SharedPanelContainer & rPanels,Window & rDeckTitleBar,Window & rScrollClipWindow,Window & rScrollContainer,Window & rFiller,ScrollBar & rVerticalScrollBar)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 
LayoutPanels(const Rectangle aContentArea,::std::vector<LayoutItem> & rLayoutItems,Window & rScrollClipWindow,Window & rScrollContainer,ScrollBar & rVerticalScrollBar,const bool bShowVerticalScrollBar)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     sal_Int32 nY = rVerticalScrollBar.GetThumbPos();
180     if (nContentHeight-nY < aBox.GetHeight())
181         nY = nContentHeight-aBox.GetHeight();
182     if (nY < 0)
183         nY = 0;
184     rScrollContainer.SetPosSizePixel(
185         0,
186         -nY,
187         nWidth,
188         nContentHeight);
189 
190     if (bShowVerticalScrollBar)
191         SetupVerticalScrollBar(rVerticalScrollBar, nContentHeight, aBox.GetHeight());
192 
193     const sal_Int32 nUsedHeight (PlacePanels(rLayoutItems, nWidth, eMode, rScrollContainer));
194     aBox.Top() += nUsedHeight;
195     return aBox;
196 }
197 
198 
199 
200 
PlacePanels(::std::vector<LayoutItem> & rLayoutItems,const sal_Int32 nWidth,const LayoutMode eMode,Window & rScrollContainer)201 sal_Int32 DeckLayouter::PlacePanels (
202     ::std::vector<LayoutItem>& rLayoutItems,
203     const sal_Int32 nWidth,
204     const LayoutMode eMode,
205     Window& rScrollContainer)
206 {
207     ::std::vector<sal_Int32> aSeparators;
208     const sal_Int32 nDeckSeparatorHeight (Theme::GetInteger(Theme::Int_DeckSeparatorHeight));
209     const sal_Int32 nPanelTitleBarHeight (Theme::GetInteger(Theme::Int_PanelTitleBarHeight));
210     sal_Int32 nY (0);
211 
212     // Assign heights and places.
213     IterateLayoutItems(iItem,rLayoutItems)
214     {
215         if( !bool(iItem->mpPanel))
216             continue;
217 
218         Panel& rPanel (*iItem->mpPanel);
219 
220         // Separator above the panel title bar.
221         aSeparators.push_back(nY);
222         nY += nDeckSeparatorHeight;
223 
224         // Place the title bar.
225         PanelTitleBar* pTitleBar = rPanel.GetTitleBar();
226         if (pTitleBar != NULL)
227         {
228             if (iItem->mbShowTitleBar)
229             {
230                 pTitleBar->SetPosSizePixel(0, nY, nWidth, nPanelTitleBarHeight);
231                 pTitleBar->Show();
232                 nY += nPanelTitleBarHeight;
233             }
234             else
235             {
236                 pTitleBar->Hide();
237             }
238         }
239 
240         if (rPanel.IsExpanded())
241         {
242             rPanel.Show();
243 
244             // Determine the height of the panel depending on layout
245             // mode and distributed heights.
246             sal_Int32 nPanelHeight (0);
247             switch(eMode)
248             {
249                 case MinimumOrLarger:
250                     nPanelHeight = iItem->maLayoutSize.Minimum + iItem->mnDistributedHeight;
251                     break;
252                 case PreferredOrLarger:
253                     nPanelHeight = iItem->maLayoutSize.Preferred + iItem->mnDistributedHeight;
254                     break;
255                 case Preferred:
256                     nPanelHeight = iItem->maLayoutSize.Preferred;
257                     break;
258                 default:
259                     OSL_ASSERT(false);
260                     break;
261             }
262 
263             // Place the panel.
264             rPanel.SetPosSizePixel(0, nY, nWidth, nPanelHeight);
265             rPanel.Invalidate();
266 
267             nY += nPanelHeight;
268         }
269         else
270         {
271             rPanel.Hide();
272 
273             // Add a separator below the collapsed panel, if it is the
274             // last panel in the deck.
275             if (iItem == rLayoutItems.end()-1)
276             {
277                 // Separator below the panel title bar.
278                 aSeparators.push_back(nY);
279                 nY += nDeckSeparatorHeight;
280             }
281         }
282     }
283 
284     Deck::ScrollContainerWindow* pScrollContainerWindow
285         = dynamic_cast<Deck::ScrollContainerWindow*>(&rScrollContainer);
286     if (pScrollContainerWindow != NULL)
287         pScrollContainerWindow->SetSeparators(aSeparators);
288 
289     return nY;
290 }
291 
292 
293 
294 
GetRequestedSizes(::std::vector<LayoutItem> & rLayoutItems,sal_Int32 & rAvailableHeight,const Rectangle & rContentBox)295 void DeckLayouter::GetRequestedSizes (
296     ::std::vector<LayoutItem>& rLayoutItems,
297     sal_Int32& rAvailableHeight,
298     const Rectangle& rContentBox)
299 {
300     rAvailableHeight = rContentBox.GetHeight();
301 
302     const sal_Int32 nPanelTitleBarHeight (Theme::GetInteger(Theme::Int_PanelTitleBarHeight));
303     const sal_Int32 nDeckSeparatorHeight (Theme::GetInteger(Theme::Int_DeckSeparatorHeight));
304 
305     IterateLayoutItems(iItem,rLayoutItems)
306     {
307         ui::LayoutSize aLayoutSize (ui::LayoutSize(0,0,0));
308         if( bool(iItem->mpPanel))
309         {
310             if (rLayoutItems.size() == 1
311                 && iItem->mpPanel->IsTitleBarOptional())
312             {
313                 // There is only one panel and its title bar is
314                 // optional => hide it.
315                 rAvailableHeight -= nDeckSeparatorHeight;
316                 iItem->mbShowTitleBar = false;
317             }
318             else
319             {
320                 // Show the title bar and a separator above and below
321                 // the title bar.
322                 rAvailableHeight -= nPanelTitleBarHeight;
323                 rAvailableHeight -= nDeckSeparatorHeight;
324             }
325 
326             if (iItem->mpPanel->IsExpanded())
327             {
328                 Reference<ui::XSidebarPanel> xPanel (iItem->mpPanel->GetPanelComponent());
329                 if (xPanel.is())
330                     aLayoutSize = xPanel->getHeightForWidth(rContentBox.GetWidth());
331                 else
332                     aLayoutSize = ui::LayoutSize(MinimalPanelHeight, -1, 0);
333             }
334         }
335         iItem->maLayoutSize = aLayoutSize;
336     }
337 }
338 
339 
340 
341 
DistributeHeights(::std::vector<LayoutItem> & rLayoutItems,const sal_Int32 nHeightToDistribute,const sal_Int32 nContainerHeight,const bool bMinimumHeightIsBase)342 void DeckLayouter::DistributeHeights (
343     ::std::vector<LayoutItem>& rLayoutItems,
344     const sal_Int32 nHeightToDistribute,
345     const sal_Int32 nContainerHeight,
346     const bool bMinimumHeightIsBase)
347 {
348     if (nHeightToDistribute <= 0)
349         return;
350 
351     sal_Int32 nRemainingHeightToDistribute (nHeightToDistribute);
352 
353     // Compute the weights as difference between panel base height
354     // (either its minimum or preferred height) and the container height.
355     sal_Int32 nTotalWeight (0);
356     sal_Int32 nNoMaximumCount (0);
357     IterateLayoutItems(iItem,rLayoutItems)
358     {
359         if (iItem->maLayoutSize.Maximum == 0)
360             continue;
361         if (iItem->maLayoutSize.Maximum < 0)
362             ++nNoMaximumCount;
363 
364         const sal_Int32 nBaseHeight (
365             bMinimumHeightIsBase
366                 ? iItem->maLayoutSize.Minimum
367                 : iItem->maLayoutSize.Preferred);
368         if (nBaseHeight < nContainerHeight)
369         {
370             iItem->mnWeight = nContainerHeight - nBaseHeight;
371             nTotalWeight += iItem->mnWeight;
372         }
373     }
374 
375 	if (nTotalWeight == 0)
376 		return;
377 
378     // First pass of height distribution.
379     IterateLayoutItems(iItem,rLayoutItems)
380     {
381         const sal_Int32 nBaseHeight (
382             bMinimumHeightIsBase
383                 ? iItem->maLayoutSize.Minimum
384                 : iItem->maLayoutSize.Preferred);
385         sal_Int32 nDistributedHeight (iItem->mnWeight * nHeightToDistribute / nTotalWeight);
386         if (nBaseHeight+nDistributedHeight > iItem->maLayoutSize.Maximum
387             && iItem->maLayoutSize.Maximum >= 0)
388         {
389             nDistributedHeight = ::std::max<sal_Int32>(0,iItem->maLayoutSize.Maximum - nBaseHeight);
390         }
391         iItem->mnDistributedHeight = nDistributedHeight;
392         nRemainingHeightToDistribute -= nDistributedHeight;
393     }
394 
395     if (nRemainingHeightToDistribute == 0)
396         return;
397     OSL_ASSERT(nRemainingHeightToDistribute > 0);
398 
399     // It is possible that not all of the height could be distributed
400     // because of Maximum heights being smaller than expected.
401     // Distribute the remaining height between the panels that have no
402     // Maximum (ie Maximum==-1).
403     if (nNoMaximumCount == 0)
404     {
405         // There are no panels with unrestricted height.
406         return;
407     }
408     const sal_Int32 nAdditionalHeightPerPanel (nRemainingHeightToDistribute / nNoMaximumCount);
409     // Handle rounding error.
410     sal_Int32 nAdditionalHeightForFirstPanel (nRemainingHeightToDistribute
411         - nNoMaximumCount*nAdditionalHeightPerPanel);
412     IterateLayoutItems(iItem,rLayoutItems)
413     {
414         if (iItem->maLayoutSize.Maximum < 0)
415         {
416             iItem->mnDistributedHeight += nAdditionalHeightPerPanel + nAdditionalHeightForFirstPanel;
417             nRemainingHeightToDistribute -= nAdditionalHeightPerPanel + nAdditionalHeightForFirstPanel;
418         }
419     }
420 
421     OSL_ASSERT(nRemainingHeightToDistribute==0);
422 }
423 
424 
425 
426 
PlaceDeckTitle(Window & rDeckTitleBar,const Rectangle & rAvailableSpace)427 Rectangle DeckLayouter::PlaceDeckTitle (
428     Window& rDeckTitleBar,
429     const Rectangle& rAvailableSpace)
430 {
431     if (static_cast<DockingWindow*>(rDeckTitleBar.GetParent()->GetParent())->IsFloatingMode())
432     {
433         // When the side bar is undocked then the outer system window displays the deck title.
434         rDeckTitleBar.Hide();
435         return rAvailableSpace;
436     }
437     else
438     {
439         const sal_Int32 nDeckTitleBarHeight (Theme::GetInteger(Theme::Int_DeckTitleBarHeight));
440         rDeckTitleBar.SetPosSizePixel(
441             rAvailableSpace.Left(),
442             rAvailableSpace.Top(),
443             rAvailableSpace.GetWidth(),
444             nDeckTitleBarHeight);
445         rDeckTitleBar.Show();
446         return Rectangle(
447             rAvailableSpace.Left(),
448             rAvailableSpace.Top() + nDeckTitleBarHeight,
449             rAvailableSpace.Right(),
450             rAvailableSpace.Bottom());
451     }
452 }
453 
454 
455 
456 
PlaceVerticalScrollBar(ScrollBar & rVerticalScrollBar,const Rectangle & rAvailableSpace,const bool bShowVerticalScrollBar)457 Rectangle DeckLayouter::PlaceVerticalScrollBar (
458     ScrollBar& rVerticalScrollBar,
459     const Rectangle& rAvailableSpace,
460     const bool bShowVerticalScrollBar)
461 {
462     if (bShowVerticalScrollBar)
463     {
464         const sal_Int32 nScrollBarWidth (rVerticalScrollBar.GetSizePixel().Width());
465         rVerticalScrollBar.SetPosSizePixel(
466             rAvailableSpace.Right() - nScrollBarWidth + 1,
467             rAvailableSpace.Top(),
468             nScrollBarWidth,
469             rAvailableSpace.GetHeight());
470         rVerticalScrollBar.Show();
471         return Rectangle(
472             rAvailableSpace.Left(),
473             rAvailableSpace.Top(),
474             rAvailableSpace.Right() - nScrollBarWidth,
475             rAvailableSpace.Bottom());
476     }
477     else
478     {
479         rVerticalScrollBar.Hide();
480         return rAvailableSpace;
481     }
482 }
483 
484 
485 
486 
SetupVerticalScrollBar(ScrollBar & rVerticalScrollBar,const sal_Int32 nContentHeight,const sal_Int32 nVisibleHeight)487 void DeckLayouter::SetupVerticalScrollBar(
488     ScrollBar& rVerticalScrollBar,
489     const sal_Int32 nContentHeight,
490     const sal_Int32 nVisibleHeight)
491 {
492     OSL_ASSERT(nContentHeight > nVisibleHeight);
493 
494     rVerticalScrollBar.SetRangeMin(0);
495     rVerticalScrollBar.SetRangeMax(nContentHeight-1);
496     rVerticalScrollBar.SetVisibleSize(nVisibleHeight);
497 }
498 
499 
500 
501 
UpdateFiller(Window & rFiller,const Rectangle & rBox)502 void DeckLayouter::UpdateFiller (
503     Window& rFiller,
504     const Rectangle& rBox)
505 {
506     if (rBox.GetHeight() > 0)
507     {
508         // Show the filler.
509         rFiller.SetBackground(Theme::GetPaint(Theme::Paint_PanelBackground).GetWallpaper());
510         rFiller.SetPosSizePixel(rBox.TopLeft(), rBox.GetSize());
511         rFiller.Show();
512     }
513     else
514     {
515         // Hide the filler.
516         rFiller.Hide();
517     }
518 }
519 
520 
521 
522 } } // end of namespace sfx2::sidebar
523