1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <sal/config.h>
21 
22 #include <o3tl/safeint.hxx>
23 #include <svx/frmsel.hxx>
24 #include <vcl/event.hxx>
25 #include <sal/log.hxx>
26 #include <tools/debug.hxx>
27 #include <svtools/colorcfg.hxx>
28 
29 #include <algorithm>
30 #include <math.h>
31 #include <string_view>
32 
33 #include <frmselimpl.hxx>
34 #include <AccessibleFrameSelector.hxx>
35 #include <com/sun/star/accessibility/AccessibleEventId.hpp>
36 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
37 #include <vcl/settings.hxx>
38 #include <vcl/svapp.hxx>
39 #include <drawinglayer/processor2d/processor2dtools.hxx>
40 #include <drawinglayer/processor2d/baseprocessor2d.hxx>
41 
42 #include <bitmaps.hlst>
43 
44 using namespace ::com::sun::star;
45 using namespace ::editeng;
46 
47 namespace svx {
48 
49 using ::com::sun::star::uno::Reference;
50 using ::com::sun::star::uno::Any;
51 using ::com::sun::star::accessibility::XAccessible;
52 using namespace ::com::sun::star::accessibility;
53 
54 // global functions from framebordertype.hxx
55 
GetFrameBorderTypeFromIndex(size_t nIndex)56 FrameBorderType GetFrameBorderTypeFromIndex( size_t nIndex )
57 {
58     DBG_ASSERT( nIndex < o3tl::make_unsigned(FRAMEBORDERTYPE_COUNT),
59         "svx::GetFrameBorderTypeFromIndex - invalid index" );
60     return static_cast< FrameBorderType >( nIndex + 1 );
61 }
62 
GetIndexFromFrameBorderType(FrameBorderType eBorder)63 size_t GetIndexFromFrameBorderType( FrameBorderType eBorder )
64 {
65     DBG_ASSERT( eBorder != FrameBorderType::NONE,
66         "svx::GetIndexFromFrameBorderType - invalid frame border type" );
67     return static_cast< size_t >( eBorder ) - 1;
68 }
69 
70 namespace
71 {
72 
73 /** Space between outer control border and any graphical element of the control. */
74 const tools::Long FRAMESEL_GEOM_OUTER    = 2;
75 
76 /** Space between arrows and usable inner area. */
77 const tools::Long FRAMESEL_GEOM_INNER    = 3;
78 
79 /** Maximum width to draw a frame border style. */
80 const tools::Long FRAMESEL_GEOM_WIDTH    = 9;
81 
82 /** Additional margin for click area of outer lines. */
83 const tools::Long FRAMESEL_GEOM_ADD_CLICK_OUTER = 5;
84 
85 /** Additional margin for click area of inner lines. */
86 const tools::Long FRAMESEL_GEOM_ADD_CLICK_INNER = 2;
87 
88 
89 /** Returns the corresponding flag for a frame border. */
lclGetFlagFromType(FrameBorderType eBorder)90 FrameSelFlags lclGetFlagFromType( FrameBorderType eBorder )
91 {
92     switch( eBorder )
93     {
94         case FrameBorderType::Left:      return FrameSelFlags::Left;
95         case FrameBorderType::Right:     return FrameSelFlags::Right;
96         case FrameBorderType::Top:       return FrameSelFlags::Top;
97         case FrameBorderType::Bottom:    return FrameSelFlags::Bottom;
98         case FrameBorderType::Horizontal:       return FrameSelFlags::InnerHorizontal;
99         case FrameBorderType::Vertical:       return FrameSelFlags::InnerVertical;
100         case FrameBorderType::TLBR:      return FrameSelFlags::DiagonalTLBR;
101         case FrameBorderType::BLTR:      return FrameSelFlags::DiagonalBLTR;
102         case FrameBorderType::NONE : break;
103     }
104     return FrameSelFlags::NONE;
105 }
106 
107 /** Merges the rSource polypolygon into the rDest polypolygon. */
lclPolyPolyUnion(tools::PolyPolygon & rDest,const tools::PolyPolygon & rSource)108 void lclPolyPolyUnion( tools::PolyPolygon& rDest, const tools::PolyPolygon& rSource )
109 {
110     const tools::PolyPolygon aTmp( rDest );
111     aTmp.GetUnion( rSource, rDest );
112 }
113 
114 } // namespace
115 
FrameBorder(FrameBorderType eType)116 FrameBorder::FrameBorder( FrameBorderType eType ) :
117     meType( eType ),
118     meState( FrameBorderState::Hide ),
119     meKeyLeft( FrameBorderType::NONE ),
120     meKeyRight( FrameBorderType::NONE ),
121     meKeyTop( FrameBorderType::NONE ),
122     meKeyBottom( FrameBorderType::NONE ),
123     mbEnabled( false ),
124     mbSelected( false )
125 {
126 }
127 
Enable(FrameSelFlags nFlags)128 void FrameBorder::Enable( FrameSelFlags nFlags )
129 {
130     mbEnabled = bool(nFlags & lclGetFlagFromType( meType ));
131     if( !mbEnabled )
132         SetState( FrameBorderState::Hide );
133 }
134 
SetCoreStyle(const SvxBorderLine * pStyle)135 void FrameBorder::SetCoreStyle( const SvxBorderLine* pStyle )
136 {
137     if( pStyle )
138         maCoreStyle = *pStyle;
139     else
140         maCoreStyle = SvxBorderLine();
141 
142     // from twips to points
143     maUIStyle.Set( &maCoreStyle, FrameBorder::GetDefaultPatternScale(), FRAMESEL_GEOM_WIDTH );
144     meState = maUIStyle.IsUsed() ? FrameBorderState::Show : FrameBorderState::Hide;
145 }
146 
SetState(FrameBorderState eState)147 void FrameBorder::SetState( FrameBorderState eState )
148 {
149     meState = eState;
150     switch( meState )
151     {
152         case FrameBorderState::Show:
153             SAL_WARN( "svx.dialog", "svx::FrameBorder::SetState - use SetCoreStyle to make border visible" );
154         break;
155         case FrameBorderState::Hide:
156             maCoreStyle = SvxBorderLine();
157             maUIStyle.Clear();
158         break;
159         case FrameBorderState::DontCare:
160             maCoreStyle = SvxBorderLine();
161             maUIStyle = frame::Style(3, 0, 0, SvxBorderLineStyle::SOLID, FrameBorder::GetDefaultPatternScale()); //OBJ_FRAMESTYLE_DONTCARE
162         break;
163     }
164 }
165 
AddFocusPolygon(const tools::Polygon & rFocus)166 void FrameBorder::AddFocusPolygon( const tools::Polygon& rFocus )
167 {
168     lclPolyPolyUnion( maFocusArea, rFocus );
169 }
170 
MergeFocusToPolyPolygon(tools::PolyPolygon & rPPoly) const171 void FrameBorder::MergeFocusToPolyPolygon( tools::PolyPolygon& rPPoly ) const
172 {
173     lclPolyPolyUnion( rPPoly, maFocusArea );
174 }
175 
AddClickRect(const tools::Rectangle & rRect)176 void FrameBorder::AddClickRect( const tools::Rectangle& rRect )
177 {
178     lclPolyPolyUnion( maClickArea, tools::Polygon( rRect ) );
179 }
180 
ContainsClickPoint(const Point & rPos) const181 bool FrameBorder::ContainsClickPoint( const Point& rPos ) const
182 {
183     return vcl::Region( maClickArea ).IsInside( rPos );
184 }
185 
GetClickBoundRect() const186 tools::Rectangle FrameBorder::GetClickBoundRect() const
187 {
188     return maClickArea.GetBoundRect();
189 }
190 
SetKeyboardNeighbors(FrameBorderType eLeft,FrameBorderType eRight,FrameBorderType eTop,FrameBorderType eBottom)191 void FrameBorder::SetKeyboardNeighbors(
192         FrameBorderType eLeft, FrameBorderType eRight, FrameBorderType eTop, FrameBorderType eBottom )
193 {
194     meKeyLeft = eLeft;
195     meKeyRight = eRight;
196     meKeyTop = eTop;
197     meKeyBottom = eBottom;
198 }
199 
GetKeyboardNeighbor(sal_uInt16 nKeyCode) const200 FrameBorderType FrameBorder::GetKeyboardNeighbor( sal_uInt16 nKeyCode ) const
201 {
202     FrameBorderType eBorder = FrameBorderType::NONE;
203     switch( nKeyCode )
204     {
205         case KEY_LEFT:  eBorder = meKeyLeft;      break;
206         case KEY_RIGHT: eBorder = meKeyRight;     break;
207         case KEY_UP:    eBorder = meKeyTop;       break;
208         case KEY_DOWN:  eBorder = meKeyBottom;    break;
209         default:        SAL_WARN( "svx.dialog", "svx::FrameBorder::GetKeyboardNeighbor - unknown key code" );
210     }
211     return eBorder;
212 }
213 
FrameSelectorImpl(FrameSelector & rFrameSel)214 FrameSelectorImpl::FrameSelectorImpl( FrameSelector& rFrameSel ) :
215     mrFrameSel( rFrameSel ),
216     mpVirDev( VclPtr<VirtualDevice>::Create() ),
217     maLeft( FrameBorderType::Left ),
218     maRight( FrameBorderType::Right ),
219     maTop( FrameBorderType::Top ),
220     maBottom( FrameBorderType::Bottom ),
221     maHor( FrameBorderType::Horizontal ),
222     maVer( FrameBorderType::Vertical ),
223     maTLBR( FrameBorderType::TLBR ),
224     maBLTR( FrameBorderType::BLTR ),
225     mnFlags( FrameSelFlags::Outer ),
226     mnCtrlSize( 0 ),
227     mnArrowSize( 0 ),
228     mnLine1( 0 ),
229     mnLine2( 0 ),
230     mnLine3( 0 ),
231     mnFocusOffs( 0 ),
232     mbHor( false ),
233     mbVer( false ),
234     mbTLBR( false ),
235     mbBLTR( false ),
236     mbFullRepaint( true ),
237     mbAutoSelect( true ),
238     mbHCMode( false ),
239     maChildVec( 8 )
240 {
241     maAllBorders.resize( FRAMEBORDERTYPE_COUNT, nullptr );
242     maAllBorders[ GetIndexFromFrameBorderType( FrameBorderType::Left   ) ] = &maLeft;
243     maAllBorders[ GetIndexFromFrameBorderType( FrameBorderType::Right  ) ] = &maRight;
244     maAllBorders[ GetIndexFromFrameBorderType( FrameBorderType::Top    ) ] = &maTop;
245     maAllBorders[ GetIndexFromFrameBorderType( FrameBorderType::Bottom ) ] = &maBottom;
246     maAllBorders[ GetIndexFromFrameBorderType( FrameBorderType::Horizontal    ) ] = &maHor;
247     maAllBorders[ GetIndexFromFrameBorderType( FrameBorderType::Vertical    ) ] = &maVer;
248     maAllBorders[ GetIndexFromFrameBorderType( FrameBorderType::TLBR   ) ] = &maTLBR;
249     maAllBorders[ GetIndexFromFrameBorderType( FrameBorderType::BLTR   ) ] = &maBLTR;
250 #if OSL_DEBUG_LEVEL >= 2
251     {
252         bool bOk = true;
253         for( FrameBorderCIter aIt( maAllBorders ); bOk && aIt.Is(); bOk = (*aIt != 0), ++aIt );
254         DBG_ASSERT( bOk, "svx::FrameSelectorImpl::FrameSelectorImpl - missing entry in maAllBorders" );
255     }
256 #endif
257     //                             left neighbor     right neighbor     upper neighbor    lower neighbor
258     maLeft.SetKeyboardNeighbors(   FrameBorderType::NONE, FrameBorderType::TLBR,  FrameBorderType::Top,  FrameBorderType::Bottom );
259     maRight.SetKeyboardNeighbors(  FrameBorderType::BLTR, FrameBorderType::NONE,  FrameBorderType::Top,  FrameBorderType::Bottom );
260     maTop.SetKeyboardNeighbors(    FrameBorderType::Left, FrameBorderType::Right, FrameBorderType::NONE, FrameBorderType::TLBR );
261     maBottom.SetKeyboardNeighbors( FrameBorderType::Left, FrameBorderType::Right, FrameBorderType::BLTR, FrameBorderType::NONE );
262     maHor.SetKeyboardNeighbors(    FrameBorderType::Left, FrameBorderType::Right, FrameBorderType::TLBR, FrameBorderType::BLTR );
263     maVer.SetKeyboardNeighbors(    FrameBorderType::TLBR, FrameBorderType::BLTR,  FrameBorderType::Top,  FrameBorderType::Bottom );
264     maTLBR.SetKeyboardNeighbors(   FrameBorderType::Left, FrameBorderType::Vertical,   FrameBorderType::Top,  FrameBorderType::Horizontal );
265     maBLTR.SetKeyboardNeighbors(   FrameBorderType::Vertical,  FrameBorderType::Right, FrameBorderType::Horizontal,  FrameBorderType::Bottom );
266 
267     Initialize(mnFlags);
268 }
269 
~FrameSelectorImpl()270 FrameSelectorImpl::~FrameSelectorImpl()
271 
272 {
273     for( auto& rpChild : maChildVec )
274         if( rpChild.is() )
275             rpChild->Invalidate();
276 }
277 
278 // initialization
Initialize(FrameSelFlags nFlags)279 void FrameSelectorImpl::Initialize( FrameSelFlags nFlags )
280 {
281     mnFlags = nFlags;
282 
283     maEnabBorders.clear();
284     for( FrameBorderIter aIt( maAllBorders ); aIt.Is(); ++aIt )
285     {
286         (*aIt)->Enable( mnFlags );
287         if( (*aIt)->IsEnabled() )
288             maEnabBorders.push_back( *aIt );
289     }
290     mbHor = maHor.IsEnabled();
291     mbVer = maVer.IsEnabled();
292     mbTLBR = maTLBR.IsEnabled();
293     mbBLTR = maBLTR.IsEnabled();
294 
295     InitVirtualDevice();
296 }
297 
InitColors()298 void FrameSelectorImpl::InitColors()
299 {
300     const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
301     svtools::ColorConfig aColorConfig;
302     maBackCol = aColorConfig.GetColorValue(svtools::DOCCOLOR).nColor;
303     mbHCMode = rSettings.GetHighContrastMode();
304     maArrowCol = aColorConfig.GetColorValue(svtools::DOCBOUNDARIES).nColor;
305     maMarkCol = aColorConfig.GetColorValue(svtools::TABLEBOUNDARIES).nColor;
306     maHCLineCol = COL_BLACK;
307 }
308 
309 const std::u16string_view aImageIds[] =
310 {
311     u"" RID_SVXBMP_FRMSEL_ARROW1,
312     u"" RID_SVXBMP_FRMSEL_ARROW2,
313     u"" RID_SVXBMP_FRMSEL_ARROW3,
314     u"" RID_SVXBMP_FRMSEL_ARROW4,
315     u"" RID_SVXBMP_FRMSEL_ARROW5,
316     u"" RID_SVXBMP_FRMSEL_ARROW6,
317     u"" RID_SVXBMP_FRMSEL_ARROW7,
318     u"" RID_SVXBMP_FRMSEL_ARROW8,
319     u"" RID_SVXBMP_FRMSEL_ARROW9,
320     u"" RID_SVXBMP_FRMSEL_ARROW10,
321     u"" RID_SVXBMP_FRMSEL_ARROW11,
322     u"" RID_SVXBMP_FRMSEL_ARROW12,
323     u"" RID_SVXBMP_FRMSEL_ARROW13,
324     u"" RID_SVXBMP_FRMSEL_ARROW14,
325     u"" RID_SVXBMP_FRMSEL_ARROW15,
326     u"" RID_SVXBMP_FRMSEL_ARROW16
327 };
328 
InitArrowImageList()329 void FrameSelectorImpl::InitArrowImageList()
330 {
331     maArrows.clear();
332 
333     /* Build the arrow images bitmap with current colors. */
334     Color pColorAry1[3];
335     Color pColorAry2[3];
336     pColorAry1[0] = Color( 0, 0, 0 );
337     pColorAry2[0] = maArrowCol;       // black -> arrow color
338     pColorAry1[1] = Color( 0, 255, 0 );
339     pColorAry2[1] = maMarkCol;        // green -> marker color
340     pColorAry1[2] = Color( 255, 0, 255 );
341     pColorAry2[2] = maBackCol;       // magenta -> background
342 
343     assert(SAL_N_ELEMENTS(aImageIds) == 16);
344     for (size_t i = 0; i < SAL_N_ELEMENTS(aImageIds); ++i)
345     {
346         BitmapEx aBmpEx { OUString(aImageIds[i]) };
347         aBmpEx.Replace(pColorAry1, pColorAry2, 3);
348         maArrows.emplace_back(aBmpEx);
349     }
350     assert(maArrows.size() == 16);
351 
352     mnArrowSize = maArrows[0].GetSizePixel().Height();
353 }
354 
InitGlobalGeometry()355 void FrameSelectorImpl::InitGlobalGeometry()
356 {
357     Size aCtrlSize(mrFrameSel.GetOutputSizePixel());
358     /*  nMinSize is the lower of width and height (control will always be squarish).
359         FRAMESEL_GEOM_OUTER is the minimal distance between inner control border
360         and any element. */
361     tools::Long nMinSize = std::min( aCtrlSize.Width(), aCtrlSize.Height() ) - 2 * FRAMESEL_GEOM_OUTER;
362     /*  nFixedSize is the size all existing elements need in one direction:
363         the diag. arrow, space betw. arrow and frame border, outer frame border,
364         inner frame border, other outer frame border, space betw. frame border
365         and arrow, the other arrow. */
366     tools::Long nFixedSize = 2 * mnArrowSize + 2 * FRAMESEL_GEOM_INNER + 3 * FRAMESEL_GEOM_WIDTH;
367     /*  nBetwBordersSize contains the size between an outer and inner frame border (made odd). */
368     tools::Long nBetwBordersSize = (((nMinSize - nFixedSize) / 2) - 1) | 1;
369 
370     /*  The final size of the usable area. At least do not get negative */
371     mnCtrlSize = 2 * nBetwBordersSize + nFixedSize;
372     mnCtrlSize = std::max(mnCtrlSize, static_cast<tools::Long>(0));
373     mpVirDev->SetOutputSizePixel( Size( mnCtrlSize, mnCtrlSize ) );
374 
375     /*  Center the virtual device in the control. */
376     maVirDevPos = Point( (aCtrlSize.Width() - mnCtrlSize) / 2, (aCtrlSize.Height() - mnCtrlSize) / 2 );
377 }
378 
InitBorderGeometry()379 void FrameSelectorImpl::InitBorderGeometry()
380 {
381     size_t nCol, nCols, nRow, nRows;
382 
383     // Global border geometry values
384     /*  mnLine* is the middle point inside a frame border (i.e. mnLine1 is mid X inside left border). */
385     mnLine1 = mnArrowSize + FRAMESEL_GEOM_INNER + FRAMESEL_GEOM_WIDTH / 2;
386     mnLine2 = mnCtrlSize / 2;
387     mnLine3 = 2 * mnLine2 - mnLine1;
388 
389     // Frame helper array
390     maArray.Initialize( mbVer ? 2 : 1, mbHor ? 2 : 1 );
391 
392     maArray.SetXOffset( mnLine1 );
393     maArray.SetAllColWidths( (mbVer ? mnLine2 : mnLine3) - mnLine1 );
394 
395     maArray.SetYOffset( mnLine1 );
396     maArray.SetAllRowHeights( (mbHor ? mnLine2 : mnLine3) - mnLine1 );
397 
398     // Focus polygons
399     /*  Width for focus rectangles from center of frame borders. */
400     mnFocusOffs = FRAMESEL_GEOM_WIDTH / 2 + 1;
401 
402     maLeft.ClearFocusArea();
403     maVer.ClearFocusArea();
404     maRight.ClearFocusArea();
405     maTop.ClearFocusArea();
406     maHor.ClearFocusArea();
407     maBottom.ClearFocusArea();
408 
409     maLeft.AddFocusPolygon(   tools::Rectangle( mnLine1 - mnFocusOffs, mnLine1 - mnFocusOffs, mnLine1 + mnFocusOffs, mnLine3 + mnFocusOffs ) );
410     maVer.AddFocusPolygon(    tools::Rectangle( mnLine2 - mnFocusOffs, mnLine1 - mnFocusOffs, mnLine2 + mnFocusOffs, mnLine3 + mnFocusOffs ) );
411     maRight.AddFocusPolygon(  tools::Rectangle( mnLine3 - mnFocusOffs, mnLine1 - mnFocusOffs, mnLine3 + mnFocusOffs, mnLine3 + mnFocusOffs ) );
412     maTop.AddFocusPolygon(    tools::Rectangle( mnLine1 - mnFocusOffs, mnLine1 - mnFocusOffs, mnLine3 + mnFocusOffs, mnLine1 + mnFocusOffs ) );
413     maHor.AddFocusPolygon(    tools::Rectangle( mnLine1 - mnFocusOffs, mnLine2 - mnFocusOffs, mnLine3 + mnFocusOffs, mnLine2 + mnFocusOffs ) );
414     maBottom.AddFocusPolygon( tools::Rectangle( mnLine1 - mnFocusOffs, mnLine3 - mnFocusOffs, mnLine3 + mnFocusOffs, mnLine3 + mnFocusOffs ) );
415 
416     maTLBR.ClearFocusArea();
417     maBLTR.ClearFocusArea();
418 
419     for( nCol = 0, nCols = maArray.GetColCount(); nCol < nCols; ++nCol )
420     {
421         for( nRow = 0, nRows = maArray.GetRowCount(); nRow < nRows; ++nRow )
422         {
423             const basegfx::B2DRange aCellRange(maArray.GetCellRange( nCol, nRow, true ));
424             const tools::Rectangle aRect(
425                 basegfx::fround(aCellRange.getMinX()), basegfx::fround(aCellRange.getMinY()),
426                 basegfx::fround(aCellRange.getMaxX()), basegfx::fround(aCellRange.getMaxY()));
427             const double fHorDiagAngle(atan2(fabs(aCellRange.getHeight()), fabs(aCellRange.getWidth())));
428             const double fVerDiagAngle(fHorDiagAngle > 0.0 ? F_PI2 - fHorDiagAngle : 0.0);
429             const tools::Long nDiagFocusOffsX(basegfx::fround(-mnFocusOffs / tan(fHorDiagAngle) + mnFocusOffs / sin(fHorDiagAngle)));
430             const tools::Long nDiagFocusOffsY(basegfx::fround(-mnFocusOffs / tan(fVerDiagAngle) + mnFocusOffs / sin(fVerDiagAngle)));
431 
432             std::vector< Point > aFocusVec;
433             aFocusVec.emplace_back( aRect.Left()  - mnFocusOffs,     aRect.Top()    + nDiagFocusOffsY );
434             aFocusVec.emplace_back( aRect.Left()  - mnFocusOffs,     aRect.Top()    - mnFocusOffs     );
435             aFocusVec.emplace_back( aRect.Left()  + nDiagFocusOffsX, aRect.Top()    - mnFocusOffs     );
436             aFocusVec.emplace_back( aRect.Right() + mnFocusOffs,     aRect.Bottom() - nDiagFocusOffsY );
437             aFocusVec.emplace_back( aRect.Right() + mnFocusOffs,     aRect.Bottom() + mnFocusOffs     );
438             aFocusVec.emplace_back( aRect.Right() - nDiagFocusOffsX, aRect.Bottom() + mnFocusOffs     );
439             maTLBR.AddFocusPolygon( tools::Polygon( static_cast< sal_uInt16 >( aFocusVec.size() ), aFocusVec.data() ) );
440 
441             aFocusVec.clear();
442             aFocusVec.emplace_back( aRect.Right() + mnFocusOffs,     aRect.Top()    + nDiagFocusOffsY );
443             aFocusVec.emplace_back( aRect.Right() + mnFocusOffs,     aRect.Top()    - mnFocusOffs     );
444             aFocusVec.emplace_back( aRect.Right() - nDiagFocusOffsX, aRect.Top()    - mnFocusOffs     );
445             aFocusVec.emplace_back( aRect.Left()  - mnFocusOffs,     aRect.Bottom() - nDiagFocusOffsY );
446             aFocusVec.emplace_back( aRect.Left()  - mnFocusOffs,     aRect.Bottom() + mnFocusOffs     );
447             aFocusVec.emplace_back( aRect.Left()  + nDiagFocusOffsX, aRect.Bottom() + mnFocusOffs     );
448             maBLTR.AddFocusPolygon( tools::Polygon( static_cast< sal_uInt16 >( aFocusVec.size() ), aFocusVec.data() ) );
449         }
450     }
451 
452     // Click areas
453     for( FrameBorderIter aIt( maAllBorders ); aIt.Is(); ++aIt )
454         (*aIt)->ClearClickArea();
455 
456     /*  Additional space for click area: is added to the space available to draw
457         the frame borders. For instance left frame border:
458         - To left, top, and bottom always big additional space (outer area).
459         - To right: Dependent on existence of inner vertical frame border
460             (if enabled, use less space).
461      */
462     tools::Long nClO = FRAMESEL_GEOM_WIDTH / 2 + FRAMESEL_GEOM_ADD_CLICK_OUTER;
463     tools::Long nClI = (mbTLBR && mbBLTR) ? (FRAMESEL_GEOM_WIDTH / 2 + FRAMESEL_GEOM_ADD_CLICK_INNER) : nClO;
464     tools::Long nClH = mbHor ? nClI : nClO;            // additional space dependent of horizontal inner border
465     tools::Long nClV = mbVer ? nClI : nClO;            // additional space dependent of vertical inner border
466 
467     maLeft.AddClickRect(   tools::Rectangle( mnLine1 - nClO, mnLine1 - nClO, mnLine1 + nClV, mnLine3 + nClO ) );
468     maVer.AddClickRect(    tools::Rectangle( mnLine2 - nClI, mnLine1 - nClO, mnLine2 + nClI, mnLine3 + nClO ) );
469     maRight.AddClickRect(  tools::Rectangle( mnLine3 - nClV, mnLine1 - nClO, mnLine3 + nClO, mnLine3 + nClO ) );
470     maTop.AddClickRect(    tools::Rectangle( mnLine1 - nClO, mnLine1 - nClO, mnLine3 + nClO, mnLine1 + nClH ) );
471     maHor.AddClickRect(    tools::Rectangle( mnLine1 - nClO, mnLine2 - nClI, mnLine3 + nClO, mnLine2 + nClI ) );
472     maBottom.AddClickRect( tools::Rectangle( mnLine1 - nClO, mnLine3 - nClH, mnLine3 + nClO, mnLine3 + nClO ) );
473 
474     /*  Diagonal frame borders use the remaining space between outer and inner frame borders. */
475     if( !(mbTLBR || mbBLTR) )
476         return;
477 
478     for( nCol = 0, nCols = maArray.GetColCount(); nCol < nCols; ++nCol )
479     {
480         for( nRow = 0, nRows = maArray.GetRowCount(); nRow < nRows; ++nRow )
481         {
482             // the usable area between horizontal/vertical frame borders of current quadrant
483             const basegfx::B2DRange aCellRange(maArray.GetCellRange( nCol, nRow, true ));
484             const tools::Rectangle aRect(
485                 basegfx::fround(aCellRange.getMinX()) + nClV + 1, basegfx::fround(aCellRange.getMinY()) + nClH + 1,
486                 basegfx::fround(aCellRange.getMaxX()) - nClV + 1, basegfx::fround(aCellRange.getMaxY()) - nClH + 1);
487 
488             /*  Both diagonal frame borders enabled. */
489             if( mbTLBR && mbBLTR )
490             {
491                 // single areas
492                 Point aMid( aRect.Center() );
493                 maTLBR.AddClickRect( tools::Rectangle( aRect.TopLeft(), aMid ) );
494                 maTLBR.AddClickRect( tools::Rectangle( aMid + Point( 1, 1 ), aRect.BottomRight() ) );
495                 maBLTR.AddClickRect( tools::Rectangle( aRect.Left(), aMid.Y() + 1, aMid.X(), aRect.Bottom() ) );
496                 maBLTR.AddClickRect( tools::Rectangle( aMid.X() + 1, aRect.Top(), aRect.Right(), aMid.Y() ) );
497                 // centered rectangle for both frame borders
498                 tools::Rectangle aMidRect( aRect.TopLeft(), Size( aRect.GetWidth() / 3, aRect.GetHeight() / 3 ) );
499                 aMidRect.Move( (aRect.GetWidth() - aMidRect.GetWidth()) / 2, (aRect.GetHeight() - aMidRect.GetHeight()) / 2 );
500                 maTLBR.AddClickRect( aMidRect );
501                 maBLTR.AddClickRect( aMidRect );
502             }
503             /*  One of the diagonal frame borders enabled - use entire rectangle. */
504             else if( mbTLBR && !mbBLTR )    // top-left to bottom-right only
505                 maTLBR.AddClickRect( aRect );
506             else if( !mbTLBR && mbBLTR )    // bottom-left to top-right only
507                 maBLTR.AddClickRect( aRect );
508         }
509     }
510 }
511 
InitVirtualDevice()512 void FrameSelectorImpl::InitVirtualDevice()
513 {
514     // initialize resources
515     InitColors();
516     InitArrowImageList();
517 
518     sizeChanged();
519 }
520 
sizeChanged()521 void FrameSelectorImpl::sizeChanged()
522 {
523     // initialize geometry
524     InitGlobalGeometry();
525     InitBorderGeometry();
526 
527     DoInvalidate( true );
528 }
529 
530 // frame border access
GetBorder(FrameBorderType eBorder) const531 const FrameBorder& FrameSelectorImpl::GetBorder( FrameBorderType eBorder ) const
532 {
533     size_t nIndex = GetIndexFromFrameBorderType( eBorder );
534     if( nIndex < maAllBorders.size() )
535         return *maAllBorders[ nIndex ];
536     SAL_WARN( "svx.dialog", "svx::FrameSelectorImpl::GetBorder - unknown border type" );
537     return maTop;
538 }
539 
GetBorderAccess(FrameBorderType eBorder)540 FrameBorder& FrameSelectorImpl::GetBorderAccess( FrameBorderType eBorder )
541 {
542     return const_cast< FrameBorder& >( GetBorder( eBorder ) );
543 }
544 
545 // drawing
DrawBackground()546 void FrameSelectorImpl::DrawBackground()
547 {
548     // clear the area
549     mpVirDev->SetLineColor();
550     mpVirDev->SetFillColor( maBackCol );
551     mpVirDev->DrawRect( tools::Rectangle( Point( 0, 0 ), mpVirDev->GetOutputSizePixel() ) );
552 
553     // draw the inner gray (or whatever color) rectangle
554     mpVirDev->SetLineColor();
555     mpVirDev->SetFillColor( maMarkCol );
556     mpVirDev->DrawRect( tools::Rectangle(
557         mnLine1 - mnFocusOffs, mnLine1 - mnFocusOffs, mnLine3 + mnFocusOffs, mnLine3 + mnFocusOffs ) );
558 
559     // draw the white space for enabled frame borders
560     tools::PolyPolygon aPPoly;
561     for( FrameBorderCIter aIt( maEnabBorders ); aIt.Is(); ++aIt )
562         (*aIt)->MergeFocusToPolyPolygon( aPPoly );
563     aPPoly.Optimize( PolyOptimizeFlags::CLOSE );
564     mpVirDev->SetLineColor( maBackCol );
565     mpVirDev->SetFillColor( maBackCol );
566     mpVirDev->DrawPolyPolygon( aPPoly );
567 }
568 
DrawArrows(const FrameBorder & rBorder)569 void FrameSelectorImpl::DrawArrows( const FrameBorder& rBorder )
570 {
571     DBG_ASSERT( rBorder.IsEnabled(), "svx::FrameSelectorImpl::DrawArrows - access to disabled border" );
572 
573     tools::Long nLinePos = 0;
574     switch( rBorder.GetType() )
575     {
576         case FrameBorderType::Left:
577         case FrameBorderType::Top:       nLinePos = mnLine1; break;
578         case FrameBorderType::Vertical:
579         case FrameBorderType::Horizontal:       nLinePos = mnLine2; break;
580         case FrameBorderType::Right:
581         case FrameBorderType::Bottom:    nLinePos = mnLine3; break;
582         default: ; //prevent warning
583     }
584     nLinePos -= mnArrowSize / 2;
585 
586     tools::Long nTLPos = 0;
587     tools::Long nBRPos = mnCtrlSize - mnArrowSize;
588     Point aPos1, aPos2;
589     int nImgIndex1 = -1, nImgIndex2 = -1;
590     switch( rBorder.GetType() )
591     {
592         case FrameBorderType::Left:
593         case FrameBorderType::Right:
594         case FrameBorderType::Vertical:
595             aPos1 = Point( nLinePos, nTLPos ); nImgIndex1 = 0;
596             aPos2 = Point( nLinePos, nBRPos ); nImgIndex2 = 1;
597         break;
598 
599         case FrameBorderType::Top:
600         case FrameBorderType::Bottom:
601         case FrameBorderType::Horizontal:
602             aPos1 = Point( nTLPos, nLinePos ); nImgIndex1 = 2;
603             aPos2 = Point( nBRPos, nLinePos ); nImgIndex2 = 3;
604         break;
605 
606         case FrameBorderType::TLBR:
607             aPos1 = Point( nTLPos, nTLPos ); nImgIndex1 = 4;
608             aPos2 = Point( nBRPos, nBRPos ); nImgIndex2 = 5;
609         break;
610         case FrameBorderType::BLTR:
611             aPos1 = Point( nTLPos, nBRPos ); nImgIndex1 = 6;
612             aPos2 = Point( nBRPos, nTLPos ); nImgIndex2 = 7;
613         break;
614         default: ; //prevent warning
615     }
616 
617     // Arrow or marker? Do not draw arrows into disabled control.
618     sal_uInt16 nSelectAdd = (mrFrameSel.IsEnabled() && rBorder.IsSelected()) ? 0 : 8;
619     if (nImgIndex1 >= 0)
620         mpVirDev->DrawImage(aPos1, maArrows[nImgIndex1 + nSelectAdd]);
621     if (nImgIndex2 >= 0)
622         mpVirDev->DrawImage(aPos2, maArrows[nImgIndex2 + nSelectAdd]);
623 }
624 
GetDrawLineColor(const Color & rColor) const625 Color FrameSelectorImpl::GetDrawLineColor( const Color& rColor ) const
626 {
627     Color aColor( mbHCMode ? maHCLineCol : rColor );
628     if( aColor == maBackCol )
629         aColor.Invert();
630     return aColor;
631 }
632 
DrawAllFrameBorders()633 void FrameSelectorImpl::DrawAllFrameBorders()
634 {
635     // Translate core colors to current UI colors (regards current background and HC mode).
636     for( FrameBorderIter aIt( maEnabBorders ); aIt.Is(); ++aIt )
637     {
638         Color aCoreColorPrim = ((*aIt)->GetState() == FrameBorderState::DontCare) ? maMarkCol : (*aIt)->GetCoreStyle().GetColorOut();
639         Color aCoreColorSecn = ((*aIt)->GetState() == FrameBorderState::DontCare) ? maMarkCol : (*aIt)->GetCoreStyle().GetColorIn();
640         (*aIt)->SetUIColorPrim( GetDrawLineColor( aCoreColorPrim ) );
641         (*aIt)->SetUIColorSecn( GetDrawLineColor( aCoreColorSecn ) );
642     }
643 
644     // Copy all frame border styles to the helper array
645     maArray.SetColumnStyleLeft( 0, maLeft.GetUIStyle() );
646     if( mbVer ) maArray.SetColumnStyleLeft( 1, maVer.GetUIStyle() );
647 
648     // Invert the style for the right line
649     const frame::Style rRightStyle = maRight.GetUIStyle( );
650     frame::Style rInvertedRight( rRightStyle.GetColorPrim(),
651             rRightStyle.GetColorSecn(), rRightStyle.GetColorGap(),
652             rRightStyle.UseGapColor(),
653             rRightStyle.Secn(), rRightStyle.Dist(), rRightStyle.Prim( ),
654             rRightStyle.Type( ), rRightStyle.PatternScale() );
655     maArray.SetColumnStyleRight( mbVer ? 1 : 0, rInvertedRight );
656 
657     maArray.SetRowStyleTop( 0, maTop.GetUIStyle() );
658     if( mbHor )
659     {
660         // Invert the style for the hor line to match the real borders
661         const frame::Style rHorStyle = maHor.GetUIStyle();
662         frame::Style rInvertedHor( rHorStyle.GetColorPrim(),
663             rHorStyle.GetColorSecn(), rHorStyle.GetColorGap(),
664             rHorStyle.UseGapColor(),
665             rHorStyle.Secn(), rHorStyle.Dist(), rHorStyle.Prim( ),
666             rHorStyle.Type(), rHorStyle.PatternScale() );
667         maArray.SetRowStyleTop( 1, rInvertedHor );
668     }
669 
670     // Invert the style for the bottom line
671     const frame::Style rBottomStyle = maBottom.GetUIStyle( );
672     frame::Style rInvertedBottom( rBottomStyle.GetColorPrim(),
673             rBottomStyle.GetColorSecn(), rBottomStyle.GetColorGap(),
674             rBottomStyle.UseGapColor(),
675             rBottomStyle.Secn(), rBottomStyle.Dist(), rBottomStyle.Prim( ),
676             rBottomStyle.Type(), rBottomStyle.PatternScale() );
677     maArray.SetRowStyleBottom( mbHor ? 1 : 0, rInvertedBottom );
678 
679     for( size_t nCol = 0; nCol < maArray.GetColCount(); ++nCol )
680         for( size_t nRow = 0; nRow < maArray.GetRowCount(); ++nRow )
681             maArray.SetCellStyleDiag( nCol, nRow, maTLBR.GetUIStyle(), maBLTR.GetUIStyle() );
682 
683     // This is used in the dialog/control for 'Border' attributes. When using
684     // the original paint below instead of primitives, the advantage currently
685     // is the correct visualization of diagonal line(s) including overlaying,
686     // but the rest is bad. Since the edit views use primitives and the preview
687     // should be 'real' I opt for also changing this to primitives. I will
688     // keep the old solution and add a switch (above) based on a static bool so
689     // that interested people may test this out in the debugger.
690     // This is one more hint to enhance the primitive visualization further to
691     // support diagonals better - that's the way to go.
692     const drawinglayer::geometry::ViewInformation2D aNewViewInformation2D;
693     std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor2D(
694         drawinglayer::processor2d::createPixelProcessor2DFromOutputDevice(
695             *mpVirDev,
696             aNewViewInformation2D));
697 
698     pProcessor2D->process(maArray.CreateB2DPrimitiveArray());
699     pProcessor2D.reset();
700 }
701 
DrawVirtualDevice()702 void FrameSelectorImpl::DrawVirtualDevice()
703 {
704     DrawBackground();
705     for(FrameBorderCIter aIt(maEnabBorders); aIt.Is(); ++aIt)
706         DrawArrows(**aIt);
707     DrawAllFrameBorders();
708     mbFullRepaint = false;
709 }
710 
CopyVirDevToControl(vcl::RenderContext & rRenderContext)711 void FrameSelectorImpl::CopyVirDevToControl(vcl::RenderContext& rRenderContext)
712 {
713     if (mbFullRepaint)
714         DrawVirtualDevice();
715     rRenderContext.DrawBitmapEx(maVirDevPos, mpVirDev->GetBitmapEx(Point(0, 0), mpVirDev->GetOutputSizePixel()));
716 }
717 
DrawAllTrackingRects(vcl::RenderContext & rRenderContext)718 void FrameSelectorImpl::DrawAllTrackingRects(vcl::RenderContext& rRenderContext)
719 {
720     tools::PolyPolygon aPPoly;
721     if (mrFrameSel.IsAnyBorderSelected())
722     {
723         for(SelFrameBorderCIter aIt( maEnabBorders ); aIt.Is(); ++aIt)
724             (*aIt)->MergeFocusToPolyPolygon(aPPoly);
725         aPPoly.Move(maVirDevPos.X(), maVirDevPos.Y());
726     }
727     else
728         // no frame border selected -> draw tracking rectangle around entire control
729         aPPoly.Insert( tools::Polygon(tools::Rectangle(maVirDevPos, mpVirDev->GetOutputSizePixel())));
730 
731     aPPoly.Optimize(PolyOptimizeFlags::CLOSE);
732 
733     for(sal_uInt16 nIdx = 0, nCount = aPPoly.Count(); nIdx < nCount; ++nIdx)
734         rRenderContext.Invert(aPPoly.GetObject(nIdx), InvertFlags::TrackFrame);
735 }
736 
GetDevPosFromMousePos(const Point & rMousePos) const737 Point FrameSelectorImpl::GetDevPosFromMousePos( const Point& rMousePos ) const
738 {
739     return rMousePos - maVirDevPos;
740 }
741 
DoInvalidate(bool bFullRepaint)742 void FrameSelectorImpl::DoInvalidate( bool bFullRepaint )
743 {
744     mbFullRepaint |= bFullRepaint;
745     mrFrameSel.Invalidate();
746 }
747 
748 // frame border state and style
SetBorderState(FrameBorder & rBorder,FrameBorderState eState)749 void FrameSelectorImpl::SetBorderState( FrameBorder& rBorder, FrameBorderState eState )
750 {
751     DBG_ASSERT( rBorder.IsEnabled(), "svx::FrameSelectorImpl::SetBorderState - access to disabled border" );
752     Any aOld;
753     Any aNew;
754     Any& rMod = eState == FrameBorderState::Show ? aNew : aOld;
755     rMod <<= AccessibleStateType::CHECKED;
756     rtl::Reference< a11y::AccFrameSelectorChild > xRet;
757     size_t nVecIdx = static_cast< size_t >( rBorder.GetType() );
758     if( GetBorder(rBorder.GetType()).IsEnabled() && (1 <= nVecIdx) && (nVecIdx <= maChildVec.size()) )
759         xRet = maChildVec[ --nVecIdx ].get();
760 
761     if( eState == FrameBorderState::Show )
762         SetBorderCoreStyle( rBorder, &maCurrStyle );
763     else
764         rBorder.SetState( eState );
765     if (xRet.is())
766         xRet->NotifyAccessibleEvent( AccessibleEventId::STATE_CHANGED, aOld, aNew );
767     DoInvalidate( true );
768 }
769 
SetBorderCoreStyle(FrameBorder & rBorder,const SvxBorderLine * pStyle)770 void FrameSelectorImpl::SetBorderCoreStyle( FrameBorder& rBorder, const SvxBorderLine* pStyle )
771 {
772     DBG_ASSERT( rBorder.IsEnabled(), "svx::FrameSelectorImpl::SetBorderCoreStyle - access to disabled border" );
773     rBorder.SetCoreStyle( pStyle );
774     DoInvalidate( true );
775 }
776 
ToggleBorderState(FrameBorder & rBorder)777 void FrameSelectorImpl::ToggleBorderState( FrameBorder& rBorder )
778 {
779     bool bDontCare = mrFrameSel.SupportsDontCareState();
780     switch( rBorder.GetState() )
781     {
782         // same order as tristate check box: visible -> don't care -> hidden
783         case FrameBorderState::Show:
784             SetBorderState( rBorder, bDontCare ? FrameBorderState::DontCare : FrameBorderState::Hide );
785         break;
786         case FrameBorderState::Hide:
787             SetBorderState( rBorder, FrameBorderState::Show );
788         break;
789         case FrameBorderState::DontCare:
790             SetBorderState( rBorder, FrameBorderState::Hide );
791         break;
792     }
793 }
794 
795 // frame border selection
SelectBorder(FrameBorder & rBorder,bool bSelect)796 void FrameSelectorImpl::SelectBorder( FrameBorder& rBorder, bool bSelect )
797 {
798     DBG_ASSERT( rBorder.IsEnabled(), "svx::FrameSelectorImpl::SelectBorder - access to disabled border" );
799     rBorder.Select( bSelect );
800     DrawArrows( rBorder );
801     DoInvalidate( false );
802 }
803 
SilentGrabFocus()804 void FrameSelectorImpl::SilentGrabFocus()
805 {
806     bool bOldAuto = mbAutoSelect;
807     mbAutoSelect = false;
808     mrFrameSel.GrabFocus();
809     mbAutoSelect = bOldAuto;
810 }
811 
SelectedBordersEqual() const812 bool FrameSelectorImpl::SelectedBordersEqual() const
813 {
814     bool bEqual = true;
815     SelFrameBorderCIter aIt( maEnabBorders );
816     if( aIt.Is() )
817     {
818         const SvxBorderLine& rFirstStyle = (*aIt)->GetCoreStyle();
819         for( ++aIt; bEqual && aIt.Is(); ++aIt )
820             bEqual = ((*aIt)->GetCoreStyle() == rFirstStyle);
821     }
822     return bEqual;
823 }
824 
FrameSelector()825 FrameSelector::FrameSelector()
826 {
827 }
828 
SetDrawingArea(weld::DrawingArea * pDrawingArea)829 void FrameSelector::SetDrawingArea(weld::DrawingArea* pDrawingArea)
830 {
831     CustomWidgetController::SetDrawingArea(pDrawingArea);
832     mxImpl.reset( new FrameSelectorImpl( *this ) );
833     Size aPrefSize = pDrawingArea->get_ref_device().LogicToPixel(Size(61, 65), MapMode(MapUnit::MapAppFont));
834     pDrawingArea->set_size_request(aPrefSize.Width(), aPrefSize.Height());
835     EnableRTL( false ); // #107808# don't mirror the mouse handling
836 }
837 
~FrameSelector()838 FrameSelector::~FrameSelector()
839 {
840     if( mxAccess.is() )
841         mxAccess->Invalidate();
842 }
843 
Initialize(FrameSelFlags nFlags)844 void FrameSelector::Initialize( FrameSelFlags nFlags )
845 {
846     mxImpl->Initialize( nFlags );
847     Show();
848 }
849 
850 // enabled frame borders
IsBorderEnabled(FrameBorderType eBorder) const851 bool FrameSelector::IsBorderEnabled( FrameBorderType eBorder ) const
852 {
853     return mxImpl->GetBorder( eBorder ).IsEnabled();
854 }
855 
GetEnabledBorderCount() const856 sal_Int32 FrameSelector::GetEnabledBorderCount() const
857 {
858     return static_cast< sal_Int32 >( mxImpl->maEnabBorders.size() );
859 }
860 
GetEnabledBorderType(sal_Int32 nIndex) const861 FrameBorderType FrameSelector::GetEnabledBorderType( sal_Int32 nIndex ) const
862 {
863     FrameBorderType eBorder = FrameBorderType::NONE;
864     if( nIndex >= 0 )
865     {
866         size_t nVecIdx = static_cast< size_t >( nIndex );
867         if( nVecIdx < mxImpl->maEnabBorders.size() )
868             eBorder = mxImpl->maEnabBorders[ nVecIdx ]->GetType();
869     }
870     return eBorder;
871 }
872 
873 // frame border state and style
SupportsDontCareState() const874 bool FrameSelector::SupportsDontCareState() const
875 {
876     return bool(mxImpl->mnFlags & FrameSelFlags::DontCare);
877 }
878 
GetFrameBorderState(FrameBorderType eBorder) const879 FrameBorderState FrameSelector::GetFrameBorderState( FrameBorderType eBorder ) const
880 {
881     return mxImpl->GetBorder( eBorder ).GetState();
882 }
883 
GetFrameBorderStyle(FrameBorderType eBorder) const884 const SvxBorderLine* FrameSelector::GetFrameBorderStyle( FrameBorderType eBorder ) const
885 {
886     const SvxBorderLine& rStyle = mxImpl->GetBorder( eBorder ).GetCoreStyle();
887     // rest of the world uses null pointer for invisible frame border
888     return rStyle.GetOutWidth() ? &rStyle : nullptr;
889 }
890 
ShowBorder(FrameBorderType eBorder,const SvxBorderLine * pStyle)891 void FrameSelector::ShowBorder( FrameBorderType eBorder, const SvxBorderLine* pStyle )
892 {
893     mxImpl->SetBorderCoreStyle( mxImpl->GetBorderAccess( eBorder ), pStyle );
894 }
895 
SetBorderDontCare(FrameBorderType eBorder)896 void FrameSelector::SetBorderDontCare( FrameBorderType eBorder )
897 {
898     mxImpl->SetBorderState( mxImpl->GetBorderAccess( eBorder ), FrameBorderState::DontCare );
899 }
900 
IsAnyBorderVisible() const901 bool FrameSelector::IsAnyBorderVisible() const
902 {
903     bool bIsSet = false;
904     for( FrameBorderCIter aIt( mxImpl->maEnabBorders ); !bIsSet && aIt.Is(); ++aIt )
905         bIsSet = ((*aIt)->GetState() == FrameBorderState::Show);
906     return bIsSet;
907 }
908 
HideAllBorders()909 void FrameSelector::HideAllBorders()
910 {
911     for( FrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
912         mxImpl->SetBorderState( **aIt, FrameBorderState::Hide );
913 }
914 
GetVisibleWidth(tools::Long & rnWidth,SvxBorderLineStyle & rnStyle) const915 bool FrameSelector::GetVisibleWidth( tools::Long& rnWidth, SvxBorderLineStyle& rnStyle ) const
916 {
917     VisFrameBorderCIter aIt( mxImpl->maEnabBorders );
918     if( !aIt.Is() )
919         return false;
920 
921     const SvxBorderLine& rStyle = (*aIt)->GetCoreStyle();
922     bool bFound = true;
923     for( ++aIt; bFound && aIt.Is(); ++aIt )
924     {
925         bFound =
926             (rStyle.GetWidth() == (*aIt)->GetCoreStyle().GetWidth()) &&
927             (rStyle.GetBorderLineStyle() ==
928                 (*aIt)->GetCoreStyle().GetBorderLineStyle());
929     }
930 
931     if( bFound )
932     {
933         rnWidth = rStyle.GetWidth();
934         rnStyle = rStyle.GetBorderLineStyle();
935     }
936     return bFound;
937 }
938 
GetVisibleColor(Color & rColor) const939 bool FrameSelector::GetVisibleColor( Color& rColor ) const
940 {
941     VisFrameBorderCIter aIt( mxImpl->maEnabBorders );
942     if( !aIt.Is() )
943         return false;
944 
945     const SvxBorderLine& rStyle = (*aIt)->GetCoreStyle();
946     bool bFound = true;
947     for( ++aIt; bFound && aIt.Is(); ++aIt )
948         bFound = (rStyle.GetColor() == (*aIt)->GetCoreStyle().GetColor());
949 
950     if( bFound )
951         rColor = rStyle.GetColor();
952     return bFound;
953 }
954 
955 // frame border selection
GetSelectHdl() const956 const Link<LinkParamNone*,void>& FrameSelector::GetSelectHdl() const
957 {
958     return mxImpl->maSelectHdl;
959 }
960 
SetSelectHdl(const Link<LinkParamNone *,void> & rHdl)961 void FrameSelector::SetSelectHdl( const Link<LinkParamNone*,void>& rHdl )
962 {
963     mxImpl->maSelectHdl = rHdl;
964 }
965 
IsBorderSelected(FrameBorderType eBorder) const966 bool FrameSelector::IsBorderSelected( FrameBorderType eBorder ) const
967 {
968     return mxImpl->GetBorder( eBorder ).IsSelected();
969 }
970 
SelectBorder(FrameBorderType eBorder)971 void FrameSelector::SelectBorder( FrameBorderType eBorder )
972 {
973     mxImpl->SelectBorder( mxImpl->GetBorderAccess( eBorder ), true/*bSelect*/ );
974     // MT: bFireFox as API parameter is ugly...
975     // if (bFocus)
976     {
977         rtl::Reference< a11y::AccFrameSelectorChild > xRet = GetChildAccessible(eBorder);
978         if (xRet.is())
979         {
980             Any aOldValue, aNewValue;
981             aNewValue <<= AccessibleStateType::FOCUSED;
982             xRet->NotifyAccessibleEvent( AccessibleEventId::STATE_CHANGED, aOldValue, aNewValue );
983         }
984     }
985 }
986 
IsAnyBorderSelected() const987 bool FrameSelector::IsAnyBorderSelected() const
988 {
989     // Construct an iterator for selected borders. If it is valid, there is a selected border.
990     return SelFrameBorderCIter( mxImpl->maEnabBorders ).Is();
991 }
992 
SelectAllBorders(bool bSelect)993 void FrameSelector::SelectAllBorders( bool bSelect )
994 {
995     for( FrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
996         mxImpl->SelectBorder( **aIt, bSelect );
997 }
998 
SelectAllVisibleBorders()999 void FrameSelector::SelectAllVisibleBorders()
1000 {
1001     for( VisFrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
1002         mxImpl->SelectBorder( **aIt, true/*bSelect*/ );
1003 }
1004 
SetStyleToSelection(tools::Long nWidth,SvxBorderLineStyle nStyle)1005 void FrameSelector::SetStyleToSelection( tools::Long nWidth, SvxBorderLineStyle nStyle )
1006 {
1007     mxImpl->maCurrStyle.SetBorderLineStyle( nStyle );
1008     mxImpl->maCurrStyle.SetWidth( nWidth );
1009     for( SelFrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
1010         mxImpl->SetBorderState( **aIt, FrameBorderState::Show );
1011 }
1012 
SetColorToSelection(const Color & rColor)1013 void FrameSelector::SetColorToSelection( const Color& rColor )
1014 {
1015     mxImpl->maCurrStyle.SetColor( rColor );
1016     for( SelFrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
1017         mxImpl->SetBorderState( **aIt, FrameBorderState::Show );
1018 }
1019 
getCurrentStyleLineStyle() const1020 SvxBorderLineStyle FrameSelector::getCurrentStyleLineStyle() const
1021 {
1022     return mxImpl->maCurrStyle.GetBorderLineStyle();
1023 }
1024 
1025 // accessibility
CreateAccessible()1026 Reference< XAccessible > FrameSelector::CreateAccessible()
1027 {
1028     if( !mxAccess.is() )
1029         mxAccess = new a11y::AccFrameSelector(*this);
1030     return mxAccess;
1031 }
1032 
GetChildAccessible(FrameBorderType eBorder)1033 rtl::Reference< a11y::AccFrameSelectorChild > FrameSelector::GetChildAccessible( FrameBorderType eBorder )
1034 {
1035     rtl::Reference< a11y::AccFrameSelectorChild > xRet;
1036     size_t nVecIdx = static_cast< size_t >( eBorder );
1037     if( IsBorderEnabled( eBorder ) && (1 <= nVecIdx) && (nVecIdx <= mxImpl->maChildVec.size()) )
1038     {
1039         --nVecIdx;
1040         if( !mxImpl->maChildVec[ nVecIdx ].is() )
1041             mxImpl->maChildVec[ nVecIdx ] = new a11y::AccFrameSelectorChild( *this, eBorder );
1042         xRet = mxImpl->maChildVec[ nVecIdx ].get();
1043     }
1044     return xRet;
1045 }
1046 
GetChildAccessible(sal_Int32 nIndex)1047 Reference< XAccessible > FrameSelector::GetChildAccessible( sal_Int32 nIndex )
1048 {
1049     return GetChildAccessible( GetEnabledBorderType( nIndex ) );
1050 }
1051 
GetChildAccessible(const Point & rPos)1052 Reference< XAccessible > FrameSelector::GetChildAccessible( const Point& rPos )
1053 {
1054     Reference< XAccessible > xRet;
1055     for( FrameBorderCIter aIt( mxImpl->maEnabBorders ); !xRet.is() && aIt.Is(); ++aIt )
1056         if( (*aIt)->ContainsClickPoint( rPos ) )
1057             xRet = GetChildAccessible( (*aIt)->GetType() ).get();
1058     return xRet;
1059 }
1060 
GetClickBoundRect(FrameBorderType eBorder) const1061 tools::Rectangle FrameSelector::GetClickBoundRect( FrameBorderType eBorder ) const
1062 {
1063     tools::Rectangle aRect;
1064     const FrameBorder& rBorder = mxImpl->GetBorder( eBorder );
1065     if( rBorder.IsEnabled() )
1066         aRect = rBorder.GetClickBoundRect();
1067     return aRect;
1068 }
1069 
1070 // virtual functions from base class
Paint(vcl::RenderContext & rRenderContext,const tools::Rectangle &)1071 void FrameSelector::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
1072 {
1073     mxImpl->CopyVirDevToControl(rRenderContext);
1074     if (HasFocus())
1075         mxImpl->DrawAllTrackingRects(rRenderContext);
1076 }
1077 
MouseButtonDown(const MouseEvent & rMEvt)1078 bool FrameSelector::MouseButtonDown( const MouseEvent& rMEvt )
1079 {
1080     /*  Mouse handling:
1081         * Click on an unselected frame border:
1082             Set current style/color, make frame border visible, deselect all
1083             other frame borders.
1084         * Click on a selected frame border:
1085             Toggle state of the frame border (visible -> don't care -> hidden),
1086             deselect all other frame borders.
1087         * SHIFT+Click or CTRL+Click on an unselected frame border:
1088             Extend selection, set current style/color to all selected frame
1089             borders independent of the state/style/color of the borders.
1090         * SHIFT+Click or CTRL+Click on a selected frame border:
1091             If all frame borders have same style/color, toggle state of all
1092             borders (see above), otherwise set current style/color to all
1093             borders.
1094         * Click on unused area: Do not modify selection and selected frame
1095             borders.
1096      */
1097 
1098     // #107394# do not auto-select a frame border
1099     mxImpl->SilentGrabFocus();
1100 
1101     if( rMEvt.IsLeft() )
1102     {
1103         Point aPos( mxImpl->GetDevPosFromMousePos( rMEvt.GetPosPixel() ) );
1104         FrameBorderPtrVec aDeselectBorders;
1105 
1106         bool bAnyClicked = false;   // Any frame border clicked?
1107         bool bNewSelected = false;  // Any unselected frame border selected?
1108 
1109         /*  If frame borders are set to "don't care" and the control does not
1110             support this state, hide them on first mouse click.
1111             DR 2004-01-30: Why are the borders set to "don't care" then?!? */
1112         bool bHideDontCare = !SupportsDontCareState();
1113 
1114         for( FrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
1115         {
1116             if( (*aIt)->ContainsClickPoint( aPos ) )
1117             {
1118                 // frame border is clicked
1119                 bAnyClicked = true;
1120                 if( !(*aIt)->IsSelected() )
1121                 {
1122                     bNewSelected = true;
1123                     //mxImpl->SelectBorder( **aIt, true );
1124                     SelectBorder((**aIt).GetType());
1125                 }
1126             }
1127             else
1128             {
1129                 // hide a "don't care" frame border only if it is not clicked
1130                 if( bHideDontCare && ((*aIt)->GetState() == FrameBorderState::DontCare) )
1131                     mxImpl->SetBorderState( **aIt, FrameBorderState::Hide );
1132 
1133                 // deselect frame borders not clicked (if SHIFT or CTRL are not pressed)
1134                 if( !rMEvt.IsShift() && !rMEvt.IsMod1() )
1135                     aDeselectBorders.push_back( *aIt );
1136             }
1137         }
1138 
1139         if( bAnyClicked )
1140         {
1141             // any valid frame border clicked? -> deselect other frame borders
1142             for( FrameBorderIter aIt( aDeselectBorders ); aIt.Is(); ++aIt )
1143                 mxImpl->SelectBorder( **aIt, false );
1144 
1145             if( bNewSelected || !mxImpl->SelectedBordersEqual() )
1146             {
1147                 // new frame border selected, selection extended, or selected borders different? -> show
1148                 for( SelFrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
1149                     // SetBorderState() sets current style and color to the frame border
1150                     mxImpl->SetBorderState( **aIt, FrameBorderState::Show );
1151             }
1152             else
1153             {
1154                 // all selected frame borders are equal -> toggle state
1155                 for( SelFrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
1156                     mxImpl->ToggleBorderState( **aIt );
1157             }
1158 
1159             GetSelectHdl().Call( nullptr );
1160         }
1161     }
1162 
1163     return true;
1164 }
1165 
KeyInput(const KeyEvent & rKEvt)1166 bool FrameSelector::KeyInput( const KeyEvent& rKEvt )
1167 {
1168     bool bHandled = false;
1169     vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
1170     if( !aKeyCode.GetModifier() )
1171     {
1172         sal_uInt16 nCode = aKeyCode.GetCode();
1173         switch( nCode )
1174         {
1175             case KEY_SPACE:
1176             {
1177                 for( SelFrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
1178                     mxImpl->ToggleBorderState( **aIt );
1179                 bHandled = true;
1180             }
1181             break;
1182 
1183             case KEY_UP:
1184             case KEY_DOWN:
1185             case KEY_LEFT:
1186             case KEY_RIGHT:
1187             {
1188                 if( !mxImpl->maEnabBorders.empty() )
1189                 {
1190                     // start from first selected frame border
1191                     SelFrameBorderCIter aIt( mxImpl->maEnabBorders );
1192                     FrameBorderType eBorder = aIt.Is() ? (*aIt)->GetType() : mxImpl->maEnabBorders.front()->GetType();
1193 
1194                     // search for next enabled frame border
1195                     do
1196                     {
1197                         eBorder = mxImpl->GetBorder( eBorder ).GetKeyboardNeighbor( nCode );
1198                     }
1199                     while( (eBorder != FrameBorderType::NONE) && !IsBorderEnabled( eBorder ) );
1200 
1201                     // select the frame border
1202                     if( eBorder != FrameBorderType::NONE )
1203                     {
1204                         DeselectAllBorders();
1205                         SelectBorder( eBorder );
1206                     }
1207                     bHandled = true;
1208                 }
1209             }
1210             break;
1211         }
1212     }
1213     if (bHandled)
1214         return true;
1215     return CustomWidgetController::KeyInput(rKEvt);
1216 }
1217 
GetFocus()1218 void FrameSelector::GetFocus()
1219 {
1220     // auto-selection of a frame border, if focus reaches control, and nothing is selected
1221     if( mxImpl->mbAutoSelect && !IsAnyBorderSelected() && !mxImpl->maEnabBorders.empty() )
1222         mxImpl->SelectBorder( *mxImpl->maEnabBorders.front(), true );
1223 
1224     mxImpl->DoInvalidate( false );
1225     if (IsAnyBorderSelected())
1226     {
1227         FrameBorderType borderType = FrameBorderType::NONE;
1228         if (mxImpl->maLeft.IsSelected())
1229             borderType = FrameBorderType::Left;
1230         else if (mxImpl->maRight.IsSelected())
1231             borderType = FrameBorderType::Right;
1232         else if (mxImpl->maTop.IsSelected())
1233             borderType = FrameBorderType::Top;
1234         else if (mxImpl->maBottom.IsSelected())
1235             borderType = FrameBorderType::Bottom;
1236         else if (mxImpl->maHor.IsSelected())
1237             borderType = FrameBorderType::Horizontal;
1238         else if (mxImpl->maVer.IsSelected())
1239             borderType = FrameBorderType::Vertical;
1240         else if (mxImpl->maTLBR.IsSelected())
1241             borderType = FrameBorderType::TLBR;
1242         else if (mxImpl->maBLTR.IsSelected())
1243             borderType = FrameBorderType::BLTR;
1244         SelectBorder(borderType);
1245     }
1246     for( SelFrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
1247             mxImpl->SetBorderState( **aIt, FrameBorderState::Show );
1248     CustomWidgetController::GetFocus();
1249 }
1250 
LoseFocus()1251 void FrameSelector::LoseFocus()
1252 {
1253     mxImpl->DoInvalidate( false );
1254     CustomWidgetController::LoseFocus();
1255 }
1256 
StyleUpdated()1257 void FrameSelector::StyleUpdated()
1258 {
1259     mxImpl->InitVirtualDevice();
1260     CustomWidgetController::StyleUpdated();
1261 }
1262 
Resize()1263 void FrameSelector::Resize()
1264 {
1265     CustomWidgetController::Resize();
1266     mxImpl->sizeChanged();
1267 }
1268 
1269 template< typename Cont, typename Iter, typename Pred >
FrameBorderIterBase(container_type & rCont)1270 FrameBorderIterBase< Cont, Iter, Pred >::FrameBorderIterBase( container_type& rCont ) :
1271     maIt( rCont.begin() ),
1272     maEnd( rCont.end() )
1273 {
1274     while( Is() && !maPred( *maIt ) ) ++maIt;
1275 }
1276 
1277 template< typename Cont, typename Iter, typename Pred >
operator ++()1278 FrameBorderIterBase< Cont, Iter, Pred >& FrameBorderIterBase< Cont, Iter, Pred >::operator++()
1279 {
1280     do { ++maIt; } while( Is() && !maPred( *maIt ) );
1281     return *this;
1282 }
1283 
1284 }
1285 
1286 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1287