1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <vcl/svapp.hxx>
21 #include <vcl/event.hxx>
22 #include <vcl/ctrl.hxx>
23 #include <vcl/decoview.hxx>
24 #include <vcl/settings.hxx>
25 #include <vcl/uitest/logger.hxx>
26 #include <sal/log.hxx>
28 #include <textlayout.hxx>
30 #include <controldata.hxx>
34 void Control::ImplInitControlData()
36 mbHasControlFocus
= false;
37 mbShowAccelerator
= false;
38 mpControlData
.reset(new ImplControlData
);
41 Control::Control( WindowType nType
) :
44 ImplInitControlData();
47 Control::Control( vcl::Window
* pParent
, WinBits nStyle
) :
48 Window( WindowType::CONTROL
)
50 ImplInitControlData();
51 ImplInit( pParent
, nStyle
, nullptr );
59 void Control::dispose()
61 mpControlData
.reset();
65 void Control::EnableRTL( bool bEnable
)
67 // convenience: for controls also switch layout mode
68 SetLayoutMode( bEnable
? ComplexTextLayoutFlags::BiDiRtl
| ComplexTextLayoutFlags::TextOriginLeft
:
69 ComplexTextLayoutFlags::TextOriginLeft
);
70 CompatStateChanged( StateChangedType::Mirroring
);
71 OutputDevice::EnableRTL(bEnable
);
74 void Control::Resize()
76 ImplClearLayoutData();
80 void Control::FillLayoutData() const
84 void Control::CreateLayoutData() const
86 SAL_WARN_IF( mpControlData
->mpLayoutData
, "vcl", "Control::CreateLayoutData: should be called with non-existent layout data only!" );
87 mpControlData
->mpLayoutData
.reset( new vcl::ControlLayoutData
);
90 bool Control::HasLayoutData() const
92 return mpControlData
&& mpControlData
->mpLayoutData
!= nullptr;
95 void Control::SetText( const OUString
& rStr
)
97 ImplClearLayoutData();
98 Window::SetText( rStr
);
101 ControlLayoutData::ControlLayoutData() : m_pParent( nullptr )
105 tools::Rectangle
ControlLayoutData::GetCharacterBounds( long nIndex
) const
107 return (nIndex
>= 0 && nIndex
< static_cast<long>(m_aUnicodeBoundRects
.size())) ? m_aUnicodeBoundRects
[ nIndex
] : tools::Rectangle();
110 tools::Rectangle
Control::GetCharacterBounds( long nIndex
) const
112 if( !HasLayoutData() )
114 return mpControlData
->mpLayoutData
? mpControlData
->mpLayoutData
->GetCharacterBounds( nIndex
) : tools::Rectangle();
117 long ControlLayoutData::GetIndexForPoint( const Point
& rPoint
) const
120 for( long i
= m_aUnicodeBoundRects
.size()-1; i
>= 0; i
-- )
122 Point aTopLeft
= m_aUnicodeBoundRects
[i
].TopLeft();
123 Point aBottomRight
= m_aUnicodeBoundRects
[i
].BottomRight();
124 if (rPoint
.X() >= aTopLeft
.X() && rPoint
.Y() >= aTopLeft
.Y() &&
125 rPoint
.X() <= aBottomRight
.X() && rPoint
.Y() <= aBottomRight
.Y())
134 long Control::GetIndexForPoint( const Point
& rPoint
) const
136 if( ! HasLayoutData() )
138 return mpControlData
->mpLayoutData
? mpControlData
->mpLayoutData
->GetIndexForPoint( rPoint
) : -1;
141 long ControlLayoutData::GetLineCount() const
143 long nLines
= m_aLineIndices
.size();
144 if( nLines
== 0 && !m_aDisplayText
.isEmpty() )
149 Pair
ControlLayoutData::GetLineStartEnd( long nLine
) const
151 Pair
aPair( -1, -1 );
153 int nDisplayLines
= m_aLineIndices
.size();
154 if( nLine
>= 0 && nLine
< nDisplayLines
)
156 aPair
.A() = m_aLineIndices
[nLine
];
157 if( nLine
+1 < nDisplayLines
)
158 aPair
.B() = m_aLineIndices
[nLine
+1]-1;
160 aPair
.B() = m_aDisplayText
.getLength()-1;
162 else if( nLine
== 0 && nDisplayLines
== 0 && !m_aDisplayText
.isEmpty() )
164 // special case for single line controls so the implementations
165 // in that case do not have to fill in the line indices
167 aPair
.B() = m_aDisplayText
.getLength()-1;
172 Pair
Control::GetLineStartEnd( long nLine
) const
174 if( !HasLayoutData() )
176 return mpControlData
->mpLayoutData
? mpControlData
->mpLayoutData
->GetLineStartEnd( nLine
) : Pair( -1, -1 );
179 long ControlLayoutData::ToRelativeLineIndex( long nIndex
) const
181 // is the index sensible at all ?
182 if( nIndex
>= 0 && nIndex
< m_aDisplayText
.getLength() )
184 int nDisplayLines
= m_aLineIndices
.size();
185 // if only 1 line exists, then absolute and relative index are
186 // identical -> do nothing
187 if( nDisplayLines
> 1 )
190 for( nLine
= nDisplayLines
-1; nLine
>= 0; nLine
-- )
192 if( m_aLineIndices
[nLine
] <= nIndex
)
194 nIndex
-= m_aLineIndices
[nLine
];
200 SAL_WARN_IF( nLine
< 0, "vcl", "ToRelativeLineIndex failed" );
211 long Control::ToRelativeLineIndex( long nIndex
) const
213 if( !HasLayoutData() )
215 return mpControlData
->mpLayoutData
? mpControlData
->mpLayoutData
->ToRelativeLineIndex( nIndex
) : -1;
218 OUString
Control::GetDisplayText() const
220 if( !HasLayoutData() )
222 return mpControlData
->mpLayoutData
? mpControlData
->mpLayoutData
->m_aDisplayText
: GetText();
225 bool Control::EventNotify( NotifyEvent
& rNEvt
)
227 // tdf#91081 if control is not valid, skip the emission - chaining to the parent
230 if ( rNEvt
.GetType() == MouseNotifyEvent::GETFOCUS
)
232 if ( !mbHasControlFocus
)
234 mbHasControlFocus
= true;
235 CompatStateChanged( StateChangedType::ControlFocus
);
236 if ( ImplCallEventListenersAndHandler( VclEventId::ControlGetFocus
, [this] () { maGetFocusHdl
.Call(*this); } ) )
237 // been destroyed within the handler
243 if ( rNEvt
.GetType() == MouseNotifyEvent::LOSEFOCUS
)
245 vcl::Window
* pFocusWin
= Application::GetFocusWindow();
246 if ( !pFocusWin
|| !ImplIsWindowOrChild( pFocusWin
) )
248 mbHasControlFocus
= false;
249 CompatStateChanged( StateChangedType::ControlFocus
);
250 if ( ImplCallEventListenersAndHandler( VclEventId::ControlLoseFocus
, [this] () { maLoseFocusHdl
.Call(*this); } ) )
251 // been destroyed within the handler
257 return Window::EventNotify( rNEvt
);
260 void Control::StateChanged( StateChangedType nStateChange
)
262 if( nStateChange
== StateChangedType::InitShow
||
263 nStateChange
== StateChangedType::Visible
||
264 nStateChange
== StateChangedType::Zoom
||
265 nStateChange
== StateChangedType::ControlFont
268 ImplClearLayoutData();
270 Window::StateChanged( nStateChange
);
273 void Control::AppendLayoutData( const Control
& rSubControl
) const
275 if( !rSubControl
.HasLayoutData() )
276 rSubControl
.FillLayoutData();
277 if( !rSubControl
.HasLayoutData() || rSubControl
.mpControlData
->mpLayoutData
->m_aDisplayText
.isEmpty() )
280 long nCurrentIndex
= mpControlData
->mpLayoutData
->m_aDisplayText
.getLength();
281 mpControlData
->mpLayoutData
->m_aDisplayText
+= rSubControl
.mpControlData
->mpLayoutData
->m_aDisplayText
;
282 int nLines
= rSubControl
.mpControlData
->mpLayoutData
->m_aLineIndices
.size();
284 mpControlData
->mpLayoutData
->m_aLineIndices
.push_back( nCurrentIndex
);
285 for( n
= 1; n
< nLines
; n
++ )
286 mpControlData
->mpLayoutData
->m_aLineIndices
.push_back( rSubControl
.mpControlData
->mpLayoutData
->m_aLineIndices
[n
] + nCurrentIndex
);
287 int nRectangles
= rSubControl
.mpControlData
->mpLayoutData
->m_aUnicodeBoundRects
.size();
288 tools::Rectangle aRel
= const_cast<Control
&>(rSubControl
).GetWindowExtentsRelative( const_cast<Control
*>(this) );
289 for( n
= 0; n
< nRectangles
; n
++ )
291 tools::Rectangle aRect
= rSubControl
.mpControlData
->mpLayoutData
->m_aUnicodeBoundRects
[n
];
292 aRect
.Move( aRel
.Left(), aRel
.Top() );
293 mpControlData
->mpLayoutData
->m_aUnicodeBoundRects
.push_back( aRect
);
297 void Control::CallEventListeners( VclEventId nEvent
, void* pData
)
299 VclPtr
<Control
> xThis(this);
300 UITestLogger::getInstance().logAction(xThis
, nEvent
);
302 vcl::Window::CallEventListeners(nEvent
, pData
);
305 bool Control::ImplCallEventListenersAndHandler( VclEventId nEvent
, std::function
<void()> const & callHandler
)
307 VclPtr
<Control
> xThis(this);
309 Control::CallEventListeners( nEvent
);
311 if ( !xThis
->IsDisposed() )
318 if ( !xThis
->IsDisposed() )
324 void Control::SetLayoutDataParent( const Control
* pParent
) const
326 if( HasLayoutData() )
327 mpControlData
->mpLayoutData
->m_pParent
= pParent
;
330 void Control::ImplClearLayoutData() const
333 mpControlData
->mpLayoutData
.reset();
336 void Control::ImplDrawFrame( OutputDevice
* pDev
, tools::Rectangle
& rRect
)
338 // use a deco view to draw the frame
339 // However, since there happens a lot of magic there, we need to fake some (style) settings
341 AllSettings
aOriginalSettings( pDev
->GetSettings() );
343 AllSettings
aNewSettings( aOriginalSettings
);
344 StyleSettings
aStyle( aNewSettings
.GetStyleSettings() );
346 // The *only known* clients of the Draw methods of the various VCL-controls are form controls:
347 // During print preview, and during printing, Draw is called. Thus, drawing always happens with a
348 // mono (colored) border
349 aStyle
.SetOptions( aStyle
.GetOptions() | StyleSettingsOptions::Mono
);
350 aStyle
.SetMonoColor( GetSettings().GetStyleSettings().GetMonoColor() );
352 aNewSettings
.SetStyleSettings( aStyle
);
353 // #i67023# do not call data changed listeners for this fake
354 // since they may understandably invalidate on settings changed
355 pDev
->OutputDevice::SetSettings( aNewSettings
);
357 DecorationView
aDecoView( pDev
);
358 rRect
= aDecoView
.DrawFrame( rRect
, DrawFrameStyle::Out
, DrawFrameFlags::WindowBorder
);
360 pDev
->OutputDevice::SetSettings( aOriginalSettings
);
363 void Control::SetShowAccelerator(bool bVal
)
365 mbShowAccelerator
= bVal
;
368 ControlLayoutData::~ControlLayoutData()
371 m_pParent
->ImplClearLayoutData();
374 Size
Control::GetOptimalSize() const
376 return Size( GetTextWidth( GetText() ) + 2 * 12,
377 GetTextHeight() + 2 * 6 );
380 void Control::SetReferenceDevice( OutputDevice
* _referenceDevice
)
382 if ( mpControlData
->mpReferenceDevice
== _referenceDevice
)
385 mpControlData
->mpReferenceDevice
= _referenceDevice
;
389 OutputDevice
* Control::GetReferenceDevice() const
391 // tdf#118377 It can happen that mpReferenceDevice is already disposed and
392 // stays disposed (see task, even when Dialog is closed). I have no idea if
393 // this may be very bad - someone who knows more about lifetime of OutputDevice's
394 // will have to decide.
395 // To secure this, I changed all accesses to mpControlData->mpReferenceDevice to
396 // use Control::GetReferenceDevice() - only use mpControlData->mpReferenceDevice
397 // inside Control::SetReferenceDevice and Control::GetReferenceDevice().
398 // Control::GetReferenceDevice() will now reset mpReferenceDevice if it is already
399 // disposed. This way all usages will do a kind of 'test-and-get' call.
400 if(nullptr != mpControlData
->mpReferenceDevice
&& mpControlData
->mpReferenceDevice
->isDisposed())
402 const_cast<Control
*>(this)->SetReferenceDevice(nullptr);
405 return mpControlData
->mpReferenceDevice
;
408 const vcl::Font
& Control::GetCanonicalFont( const StyleSettings
& _rStyle
) const
410 return _rStyle
.GetLabelFont();
413 const Color
& Control::GetCanonicalTextColor( const StyleSettings
& _rStyle
) const
415 return _rStyle
.GetLabelTextColor();
418 void Control::ApplySettings(vcl::RenderContext
& rRenderContext
)
420 const StyleSettings
& rStyleSettings
= rRenderContext
.GetSettings().GetStyleSettings();
422 ApplyControlFont(rRenderContext
, GetCanonicalFont(rStyleSettings
));
424 ApplyControlForeground(rRenderContext
, GetCanonicalTextColor(rStyleSettings
));
425 rRenderContext
.SetTextFillColor();
428 void Control::ImplInitSettings()
430 ApplySettings(*this);
433 tools::Rectangle
Control::DrawControlText( OutputDevice
& _rTargetDevice
, const tools::Rectangle
& rRect
, const OUString
& _rStr
,
434 DrawTextFlags _nStyle
, MetricVector
* _pVector
, OUString
* _pDisplayText
, const Size
* i_pDeviceSize
) const
436 OUString rPStr
= _rStr
;
437 DrawTextFlags nPStyle
= _nStyle
;
439 bool autoacc
= ImplGetSVData()->maNWFData
.mbAutoAccel
;
441 if (autoacc
&& !mbShowAccelerator
)
443 rPStr
= GetNonMnemonicString( _rStr
);
444 nPStyle
&= ~DrawTextFlags::HideMnemonic
;
447 if( !GetReferenceDevice() || ( GetReferenceDevice() == &_rTargetDevice
) )
449 const tools::Rectangle aRet
= _rTargetDevice
.GetTextRect(rRect
, rPStr
, nPStyle
);
450 _rTargetDevice
.DrawText(aRet
, rPStr
, nPStyle
, _pVector
, _pDisplayText
);
454 ControlTextRenderer
aRenderer( *this, _rTargetDevice
, *GetReferenceDevice() );
455 return aRenderer
.DrawText(rRect
, rPStr
, nPStyle
, _pVector
, _pDisplayText
, i_pDeviceSize
);
458 tools::Rectangle
Control::GetControlTextRect( OutputDevice
& _rTargetDevice
, const tools::Rectangle
& rRect
,
459 const OUString
& _rStr
, DrawTextFlags _nStyle
, Size
* o_pDeviceSize
) const
461 OUString rPStr
= _rStr
;
462 DrawTextFlags nPStyle
= _nStyle
;
464 bool autoacc
= ImplGetSVData()->maNWFData
.mbAutoAccel
;
466 if (autoacc
&& !mbShowAccelerator
)
468 rPStr
= GetNonMnemonicString( _rStr
);
469 nPStyle
&= ~DrawTextFlags::HideMnemonic
;
472 if ( !GetReferenceDevice() || ( GetReferenceDevice() == &_rTargetDevice
) )
474 tools::Rectangle aRet
= _rTargetDevice
.GetTextRect( rRect
, rPStr
, nPStyle
);
477 *o_pDeviceSize
= aRet
.GetSize();
482 ControlTextRenderer
aRenderer( *this, _rTargetDevice
, *GetReferenceDevice() );
483 return aRenderer
.GetTextRect(rRect
, rPStr
, nPStyle
, o_pDeviceSize
);
487 Control::GetUnzoomedControlPointFont() const
489 Font
aFont(GetCanonicalFont(GetSettings().GetStyleSettings()));
491 aFont
.Merge(GetControlFont());
495 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */