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_svtools.hxx"
25 
26 #include "tabbargeometry.hxx"
27 
28 #include <basegfx/range/b2drange.hxx>
29 #include <basegfx/matrix/b2dhommatrix.hxx>
30 #include <basegfx/numeric/ftools.hxx>
31 
32 #include <vcl/window.hxx>
33 
34 #include <algorithm>
35 
36 // the width (or height, depending on alignment) of the scroll buttons
37 #define BUTTON_FLOW_WIDTH       20
38 // the space between the scroll buttons and the items
39 #define BUTTON_FLOW_SPACE       2
40 // outer space to apply between the tab bar borders and any content. Note that those refer to a "normalized" geometry,
41 // i.e. if the tab bar were aligned at the top
42 #define OUTER_SPACE_LEFT        2
43 #define OUTER_SPACE_TOP         4
44 #define OUTER_SPACE_RIGHT       4
45 #define OUTER_SPACE_BOTTOM      2
46 
47 // outer space to apply between the area for the items, and the actual items. They refer to a normalized geometry.
48 #define ITEMS_INSET_LEFT        4
49 #define ITEMS_INSET_TOP         3
50 #define ITEMS_INSET_RIGHT       4
51 #define ITEMS_INSET_BOTTOM      0
52 
53 //......................................................................................................................
54 namespace svt
55 {
56 //......................................................................................................................
57 
58 	//==================================================================================================================
59 	//= helper
60 	//==================================================================================================================
61     namespace
62     {
63 	    //--------------------------------------------------------------------------------------------------------------
lcl_transform(Rectangle & io_rRect,const::basegfx::B2DHomMatrix & i_rTransformation)64         static void lcl_transform( Rectangle& io_rRect, const ::basegfx::B2DHomMatrix& i_rTransformation )
65         {
66             ::basegfx::B2DRange aRect( io_rRect.Left(), io_rRect.Top(), io_rRect.Right(), io_rRect.Bottom() );
67             aRect.transform( i_rTransformation );
68             io_rRect.Left() = long( aRect.getMinX() );
69             io_rRect.Top() = long( aRect.getMinY() );
70             io_rRect.Right() = long( aRect.getMaxX() );
71             io_rRect.Bottom() = long( aRect.getMaxY() );
72         }
73 
74 	    //--------------------------------------------------------------------------------------------------------------
75         /** transforms the given, possible rotated playground,
76         */
lcl_rotate(const Rectangle & i_rReference,Rectangle & io_rArea,const bool i_bRight)77         void lcl_rotate( const Rectangle& i_rReference, Rectangle& io_rArea, const bool i_bRight )
78         {
79             // step 1: move the to-be-upper-left corner (left/bottom) of the rectangle to (0,0)
80             ::basegfx::B2DHomMatrix aTransformation;
81             aTransformation.translate(
82                 i_bRight ? -i_rReference.Left() : -i_rReference.Right(),
83                 i_bRight ? -i_rReference.Bottom() : -i_rReference.Top()
84             );
85 
86             // step 2: rotate by -90 degrees
87             aTransformation.rotate( i_bRight ? +F_PI2 : -F_PI2 );
88                 // note:
89                 // on the screen, the ordinate goes top-down, while basegfx calculates in a system where the
90                 // ordinate goes bottom-up; thus the "wrong" sign before F_PI2 here
91 
92             // step 3: move back to original coordinates
93             aTransformation.translate( i_rReference.Left(), i_rReference.Top() );
94 
95             // apply transformation
96             lcl_transform( io_rArea, aTransformation );
97         }
98     }
99 
100 	//------------------------------------------------------------------------------------------------------------------
lcl_mirrorHorizontally(const Rectangle & i_rReferenceArea,Rectangle & io_rArea)101     void lcl_mirrorHorizontally( const Rectangle& i_rReferenceArea, Rectangle& io_rArea )
102     {
103         io_rArea.Left() = i_rReferenceArea.Left() + i_rReferenceArea.Right() - io_rArea.Left();
104         io_rArea.Right() = i_rReferenceArea.Left() + i_rReferenceArea.Right() - io_rArea.Right();
105         ::std::swap( io_rArea.Left(), io_rArea.Right() );
106     }
107 
108 	//------------------------------------------------------------------------------------------------------------------
lcl_mirrorVertically(const Rectangle & i_rReferenceArea,Rectangle & io_rArea)109     void lcl_mirrorVertically( const Rectangle& i_rReferenceArea, Rectangle& io_rArea )
110     {
111         io_rArea.Top() = i_rReferenceArea.Top() + i_rReferenceArea.Bottom() - io_rArea.Top();
112         io_rArea.Bottom() = i_rReferenceArea.Top() + i_rReferenceArea.Bottom() - io_rArea.Bottom();
113         ::std::swap( io_rArea.Top(), io_rArea.Bottom() );
114     }
115 
116 	//==================================================================================================================
117 	//= NormalizedArea
118 	//==================================================================================================================
119 	//------------------------------------------------------------------------------------------------------------------
NormalizedArea()120     NormalizedArea::NormalizedArea()
121         :m_aReference()
122     {
123     }
124 
125 	//------------------------------------------------------------------------------------------------------------------
NormalizedArea(const Rectangle & i_rReference,const bool i_bIsVertical)126     NormalizedArea::NormalizedArea( const Rectangle& i_rReference, const bool i_bIsVertical )
127         :m_aReference( i_bIsVertical ? Rectangle( i_rReference.TopLeft(), Size( i_rReference.GetHeight(), i_rReference.GetWidth() ) ) : i_rReference )
128     {
129     }
130 
131 	//------------------------------------------------------------------------------------------------------------------
getTransformed(const Rectangle & i_rArea,const TabAlignment i_eTargetAlignment) const132     Rectangle NormalizedArea::getTransformed( const Rectangle& i_rArea, const TabAlignment i_eTargetAlignment ) const
133     {
134         Rectangle aResult( i_rArea );
135 
136         if  (   ( i_eTargetAlignment == TABS_RIGHT )
137             ||  ( i_eTargetAlignment == TABS_LEFT )
138             )
139         {
140             lcl_rotate( m_aReference, aResult, true );
141 
142             if ( i_eTargetAlignment == TABS_LEFT )
143             {
144                 Rectangle aReference( m_aReference );
145                 aReference.Transpose();
146                 lcl_mirrorHorizontally( aReference, aResult );
147             }
148         }
149         else
150         if  ( i_eTargetAlignment == TABS_BOTTOM )
151         {
152             lcl_mirrorVertically( m_aReference, aResult );
153         }
154 
155         return aResult;
156     }
157 
158 	//------------------------------------------------------------------------------------------------------------------
getNormalized(const Rectangle & i_rArea,const TabAlignment i_eTargetAlignment) const159     Rectangle NormalizedArea::getNormalized( const Rectangle& i_rArea, const TabAlignment i_eTargetAlignment ) const
160     {
161         Rectangle aResult( i_rArea );
162 
163         if  (   ( i_eTargetAlignment == TABS_RIGHT )
164             ||  ( i_eTargetAlignment == TABS_LEFT )
165             )
166         {
167             Rectangle aReference( m_aReference );
168             lcl_rotate( m_aReference, aReference, true );
169 
170             if ( i_eTargetAlignment == TABS_LEFT )
171             {
172                 lcl_mirrorHorizontally( aReference, aResult );
173             }
174 
175             lcl_rotate( aReference, aResult, false );
176         }
177         else
178         if  ( i_eTargetAlignment == TABS_BOTTOM )
179         {
180             lcl_mirrorVertically( m_aReference, aResult );
181         }
182         return aResult;
183     }
184 
185 	//==================================================================================================================
186 	//= TabBarGeometry
187 	//==================================================================================================================
188 	//------------------------------------------------------------------------------------------------------------------
TabBarGeometry(const TabItemContent i_eItemContent)189     TabBarGeometry::TabBarGeometry( const TabItemContent i_eItemContent )
190         :m_eTabItemContent( i_eItemContent )
191         ,m_aItemsInset()
192         ,m_aButtonBackRect()
193         ,m_aItemsRect()
194         ,m_aButtonForwardRect()
195     {
196         m_aItemsInset.Left()   = ITEMS_INSET_LEFT;
197         m_aItemsInset.Top()    = ITEMS_INSET_TOP;
198         m_aItemsInset.Right()  = ITEMS_INSET_RIGHT;
199         m_aItemsInset.Bottom() = ITEMS_INSET_BOTTOM;
200     }
201 
202 	//------------------------------------------------------------------------------------------------------------------
~TabBarGeometry()203     TabBarGeometry::~TabBarGeometry()
204     {
205     }
206 
207     //------------------------------------------------------------------------------------------------------------------
impl_fitItems(ItemDescriptors & io_rItems) const208     bool TabBarGeometry::impl_fitItems( ItemDescriptors& io_rItems ) const
209     {
210         if ( io_rItems.empty() )
211             // nothing to do, "no items" perfectly fit into any space we have ...
212             return true;
213 
214         // the available size
215         Size aOutputSize( getItemsRect().GetSize() );
216         // shrunk by the outer space
217         aOutputSize.Width() -= m_aItemsInset.Right();
218         aOutputSize.Height() -= m_aItemsInset.Bottom();
219         const Rectangle aFitInto( Point( 0, 0 ), aOutputSize );
220 
221         TabItemContent eItemContent( getItemContent() );
222         if ( eItemContent == TABITEM_AUTO )
223         {
224             // the "content modes" to try
225             TabItemContent eTryThis[] =
226             {
227                 TABITEM_IMAGE_ONLY,     // assumed to have the smallest rects
228                 TABITEM_TEXT_ONLY,
229                 TABITEM_IMAGE_AND_TEXT  // assumed to have the largest rects
230             };
231 
232 
233             // determine which of the different version fits
234             eItemContent = eTryThis[0];
235             size_t nTryIndex = 2;
236             while ( nTryIndex > 0 )
237             {
238                 const Point aBottomRight( io_rItems.rbegin()->GetRect( eTryThis[ nTryIndex ] ).BottomRight() );
239                 if ( aFitInto.IsInside( aBottomRight ) )
240                 {
241                     eItemContent = eTryThis[ nTryIndex ];
242                     break;
243                 }
244                 --nTryIndex;
245             }
246         }
247 
248         // propagate to the items
249         for (   ItemDescriptors::iterator item = io_rItems.begin();
250                 item != io_rItems.end();
251                 ++item
252             )
253         {
254             item->eContent = eItemContent;
255         }
256 
257         const ItemDescriptor& rLastItem( *io_rItems.rbegin() );
258         const Point aLastItemBottomRight( rLastItem.GetCurrentRect().BottomRight() );
259         return  aFitInto.Left() <= aLastItemBottomRight.X()
260             &&  aFitInto.Right() >= aLastItemBottomRight.X();
261     }
262 
263 	//------------------------------------------------------------------------------------------------------------------
getOptimalSize(ItemDescriptors & io_rItems,const bool i_bMinimalSize) const264     Size TabBarGeometry::getOptimalSize( ItemDescriptors& io_rItems, const bool i_bMinimalSize ) const
265     {
266         if ( io_rItems.empty() )
267             return Size(
268                 m_aItemsInset.Left() + m_aItemsInset.Right(),
269                 m_aItemsInset.Top() + m_aItemsInset.Bottom()
270             );
271 
272         // the rect of the last item
273         const Rectangle& rLastItemRect( i_bMinimalSize ? io_rItems.rbegin()->aIconOnlyArea : io_rItems.rbegin()->aCompleteArea );
274         return Size(
275                     rLastItemRect.Left() + 1 + m_aItemsInset.Right(),
276                     rLastItemRect.Top() + 1 + rLastItemRect.Bottom() + m_aItemsInset.Bottom()
277                 );
278     }
279 
280 	//------------------------------------------------------------------------------------------------------------------
relayout(const Size & i_rActualOutputSize,ItemDescriptors & io_rItems)281     void TabBarGeometry::relayout( const Size& i_rActualOutputSize, ItemDescriptors& io_rItems )
282     {
283         // assume all items fit
284         Point aButtonBackPos( OUTER_SPACE_LEFT, OUTER_SPACE_TOP );
285         m_aButtonBackRect = Rectangle( aButtonBackPos, Size( 1, 1 ) );
286         m_aButtonBackRect.SetEmpty();
287 
288         Point aButtonForwardPos( i_rActualOutputSize.Width(), OUTER_SPACE_TOP );
289         m_aButtonForwardRect = Rectangle( aButtonForwardPos, Size( 1, 1 ) );
290         m_aButtonForwardRect.SetEmpty();
291 
292         Point aItemsPos( OUTER_SPACE_LEFT, 0 );
293         Size aItemsSize( i_rActualOutputSize.Width() - OUTER_SPACE_LEFT - OUTER_SPACE_RIGHT, i_rActualOutputSize.Height() );
294         m_aItemsRect = Rectangle( aItemsPos, aItemsSize );
295 
296         if ( !impl_fitItems( io_rItems ) )
297         {
298             // assumption was wrong, the items do not fit => calculate rects for the scroll buttons
299             const Size aButtonSize( BUTTON_FLOW_WIDTH, i_rActualOutputSize.Height() - OUTER_SPACE_TOP - OUTER_SPACE_BOTTOM );
300 
301             aButtonBackPos = Point( OUTER_SPACE_LEFT, OUTER_SPACE_TOP );
302             m_aButtonBackRect = Rectangle( aButtonBackPos, aButtonSize );
303 
304             aButtonForwardPos = Point( i_rActualOutputSize.Width() - BUTTON_FLOW_WIDTH - OUTER_SPACE_RIGHT, OUTER_SPACE_TOP );
305             m_aButtonForwardRect = Rectangle( aButtonForwardPos, aButtonSize );
306 
307             aItemsPos.X() = aButtonBackPos.X() + aButtonSize.Width() + BUTTON_FLOW_SPACE;
308             aItemsSize.Width() = aButtonForwardPos.X() - BUTTON_FLOW_SPACE - aItemsPos.X();
309             m_aItemsRect = Rectangle( aItemsPos, aItemsSize );
310 
311             // fit items, again. In the TABITEM_AUTO case, the smaller playground for the items might lead to another
312             // item content.
313             impl_fitItems( io_rItems );
314         }
315     }
316 
317 	//------------------------------------------------------------------------------------------------------------------
getFirstItemPosition() const318     Point TabBarGeometry::getFirstItemPosition() const
319     {
320         return Point( m_aItemsInset.Left(), m_aItemsInset.Top() );
321     }
322 
323 //......................................................................................................................
324 } // namespace svt
325 //......................................................................................................................
326