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