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 <comphelper/lok.hxx>
21 #include <o3tl/safeint.hxx>
22 #include <vcl/svapp.hxx>
23 #include <vcl/event.hxx>
24 #include <vcl/ctrl.hxx>
25 #include <vcl/decoview.hxx>
26 #include <vcl/mnemonic.hxx>
27 #include <vcl/settings.hxx>
28 #include <vcl/uitest/logger.hxx>
29 #include <vcl/DocWindow.hxx>
30 #include <sal/log.hxx>
32 #include <textlayout.hxx>
37 void Control::ImplInitControlData()
39 mbHasControlFocus
= false;
40 mbShowAccelerator
= false;
43 Control::Control( WindowType nType
) :
46 ImplInitControlData();
49 Control::Control( vcl::Window
* pParent
, WinBits nStyle
) :
50 Window( WindowType::CONTROL
)
52 ImplInitControlData();
53 ImplInit( pParent
, nStyle
, nullptr );
61 void Control::dispose()
64 mpReferenceDevice
.clear();
68 void Control::EnableRTL( bool bEnable
)
70 // convenience: for controls also switch layout mode
71 GetOutDev()->SetLayoutMode( bEnable
? vcl::text::ComplexTextLayoutFlags::BiDiRtl
| vcl::text::ComplexTextLayoutFlags::TextOriginLeft
:
72 vcl::text::ComplexTextLayoutFlags::TextOriginLeft
);
73 CompatStateChanged( StateChangedType::Mirroring
);
74 Window::EnableRTL(bEnable
);
77 void Control::Resize()
79 ImplClearLayoutData();
83 void Control::FillLayoutData() const
87 void Control::CreateLayoutData() const
89 SAL_WARN_IF( mxLayoutData
, "vcl", "Control::CreateLayoutData: should be called with non-existent layout data only!" );
90 mxLayoutData
.emplace();
93 bool Control::HasLayoutData() const
95 return bool(mxLayoutData
);
98 void Control::SetText( const OUString
& rStr
)
100 ImplClearLayoutData();
101 Window::SetText( rStr
);
104 ControlLayoutData::ControlLayoutData() : m_pParent( nullptr )
108 tools::Rectangle
ControlLayoutData::GetCharacterBounds( tools::Long nIndex
) const
110 return (nIndex
>= 0 && o3tl::make_unsigned(nIndex
) < m_aUnicodeBoundRects
.size()) ? m_aUnicodeBoundRects
[ nIndex
] : tools::Rectangle();
113 tools::Rectangle
Control::GetCharacterBounds( tools::Long nIndex
) const
115 if( !HasLayoutData() )
117 return mxLayoutData
? mxLayoutData
->GetCharacterBounds( nIndex
) : tools::Rectangle();
120 tools::Long
ControlLayoutData::GetIndexForPoint( const Point
& rPoint
) const
122 tools::Long nIndex
= -1;
123 for( tools::Long i
= m_aUnicodeBoundRects
.size()-1; i
>= 0; i
-- )
125 Point aTopLeft
= m_aUnicodeBoundRects
[i
].TopLeft();
126 Point aBottomRight
= m_aUnicodeBoundRects
[i
].BottomRight();
127 if (rPoint
.X() >= aTopLeft
.X() && rPoint
.Y() >= aTopLeft
.Y() &&
128 rPoint
.X() <= aBottomRight
.X() && rPoint
.Y() <= aBottomRight
.Y())
137 tools::Long
Control::GetIndexForPoint( const Point
& rPoint
) const
139 if( ! HasLayoutData() )
141 return mxLayoutData
? mxLayoutData
->GetIndexForPoint( rPoint
) : -1;
144 tools::Long
ControlLayoutData::GetLineCount() const
146 tools::Long nLines
= m_aLineIndices
.size();
147 if( nLines
== 0 && !m_aDisplayText
.isEmpty() )
152 Pair
ControlLayoutData::GetLineStartEnd( tools::Long nLine
) const
154 Pair
aPair( -1, -1 );
156 int nDisplayLines
= m_aLineIndices
.size();
157 if( nLine
>= 0 && nLine
< nDisplayLines
)
159 aPair
.A() = m_aLineIndices
[nLine
];
160 if( nLine
+1 < nDisplayLines
)
161 aPair
.B() = m_aLineIndices
[nLine
+1]-1;
163 aPair
.B() = m_aDisplayText
.getLength()-1;
165 else if( nLine
== 0 && nDisplayLines
== 0 && !m_aDisplayText
.isEmpty() )
167 // special case for single line controls so the implementations
168 // in that case do not have to fill in the line indices
170 aPair
.B() = m_aDisplayText
.getLength()-1;
175 Pair
Control::GetLineStartEnd( tools::Long nLine
) const
177 if( !HasLayoutData() )
179 return mxLayoutData
? mxLayoutData
->GetLineStartEnd( nLine
) : Pair( -1, -1 );
182 tools::Long
ControlLayoutData::ToRelativeLineIndex( tools::Long nIndex
) const
184 // is the index sensible at all ?
185 if( nIndex
>= 0 && nIndex
< m_aDisplayText
.getLength() )
187 int nDisplayLines
= m_aLineIndices
.size();
188 // if only 1 line exists, then absolute and relative index are
189 // identical -> do nothing
190 if( nDisplayLines
> 1 )
193 for( nLine
= nDisplayLines
-1; nLine
>= 0; nLine
-- )
195 if( m_aLineIndices
[nLine
] <= nIndex
)
197 nIndex
-= m_aLineIndices
[nLine
];
203 SAL_WARN_IF( nLine
< 0, "vcl", "ToRelativeLineIndex failed" );
214 tools::Long
Control::ToRelativeLineIndex( tools::Long nIndex
) const
216 if( !HasLayoutData() )
218 return mxLayoutData
? mxLayoutData
->ToRelativeLineIndex( nIndex
) : -1;
221 OUString
Control::GetDisplayText() const
223 if( !HasLayoutData() )
225 return mxLayoutData
? mxLayoutData
->m_aDisplayText
: GetText();
228 bool Control::FocusWindowBelongsToControl(const vcl::Window
* pFocusWin
) const
230 return ImplIsWindowOrChild(pFocusWin
);
233 bool Control::EventNotify( NotifyEvent
& rNEvt
)
235 if ( rNEvt
.GetType() == NotifyEventType::GETFOCUS
)
237 if ( !mbHasControlFocus
)
239 mbHasControlFocus
= true;
240 CompatStateChanged( StateChangedType::ControlFocus
);
241 if ( ImplCallEventListenersAndHandler( VclEventId::ControlGetFocus
, {} ) )
242 // been destroyed within the handler
248 if ( rNEvt
.GetType() == NotifyEventType::LOSEFOCUS
)
250 vcl::Window
* pFocusWin
= Application::GetFocusWindow();
251 if ( !pFocusWin
|| !FocusWindowBelongsToControl(pFocusWin
) )
253 mbHasControlFocus
= false;
254 CompatStateChanged( StateChangedType::ControlFocus
);
255 if ( ImplCallEventListenersAndHandler( VclEventId::ControlLoseFocus
, [this] () { maLoseFocusHdl
.Call(*this); } ) )
256 // been destroyed within the handler
261 return Window::EventNotify( rNEvt
);
264 void Control::StateChanged( StateChangedType nStateChange
)
266 if( nStateChange
== StateChangedType::InitShow
||
267 nStateChange
== StateChangedType::Visible
||
268 nStateChange
== StateChangedType::Zoom
||
269 nStateChange
== StateChangedType::ControlFont
272 ImplClearLayoutData();
274 Window::StateChanged( nStateChange
);
277 void Control::AppendLayoutData( const Control
& rSubControl
) const
279 if( !rSubControl
.HasLayoutData() )
280 rSubControl
.FillLayoutData();
281 if( !rSubControl
.HasLayoutData() || rSubControl
.mxLayoutData
->m_aDisplayText
.isEmpty() )
284 tools::Long nCurrentIndex
= mxLayoutData
->m_aDisplayText
.getLength();
285 mxLayoutData
->m_aDisplayText
+= rSubControl
.mxLayoutData
->m_aDisplayText
;
286 int nLines
= rSubControl
.mxLayoutData
->m_aLineIndices
.size();
288 mxLayoutData
->m_aLineIndices
.push_back( nCurrentIndex
);
289 for( n
= 1; n
< nLines
; n
++ )
290 mxLayoutData
->m_aLineIndices
.push_back( rSubControl
.mxLayoutData
->m_aLineIndices
[n
] + nCurrentIndex
);
291 int nRectangles
= rSubControl
.mxLayoutData
->m_aUnicodeBoundRects
.size();
292 tools::Rectangle aRel
= rSubControl
.GetWindowExtentsRelative(this);
293 for( n
= 0; n
< nRectangles
; n
++ )
295 tools::Rectangle aRect
= rSubControl
.mxLayoutData
->m_aUnicodeBoundRects
[n
];
296 aRect
.Move( aRel
.Left(), aRel
.Top() );
297 mxLayoutData
->m_aUnicodeBoundRects
.push_back( aRect
);
301 void Control::CallEventListeners( VclEventId nEvent
, void* pData
)
303 VclPtr
<Control
> xThis(this);
304 UITestLogger::getInstance().logAction(xThis
, nEvent
);
306 vcl::Window::CallEventListeners(nEvent
, pData
);
309 bool Control::ImplCallEventListenersAndHandler( VclEventId nEvent
, std::function
<void()> const & callHandler
)
311 VclPtr
<Control
> xThis(this);
313 Control::CallEventListeners( nEvent
);
315 if ( !xThis
->isDisposed() )
322 if ( !xThis
->isDisposed() )
328 void Control::SetLayoutDataParent( const Control
* pParent
) const
330 if( HasLayoutData() )
331 mxLayoutData
->m_pParent
= pParent
;
334 void Control::ImplClearLayoutData() const
336 mxLayoutData
.reset();
339 void Control::ImplDrawFrame( OutputDevice
* pDev
, tools::Rectangle
& rRect
)
341 // use a deco view to draw the frame
342 // However, since there happens a lot of magic there, we need to fake some (style) settings
344 AllSettings
aOriginalSettings( pDev
->GetSettings() );
346 AllSettings
aNewSettings( aOriginalSettings
);
347 StyleSettings
aStyle( aNewSettings
.GetStyleSettings() );
349 // The *only known* clients of the Draw methods of the various VCL-controls are form controls:
350 // During print preview, and during printing, Draw is called. Thus, drawing always happens with a
351 // mono (colored) border
352 aStyle
.SetOptions( aStyle
.GetOptions() | StyleSettingsOptions::Mono
);
353 aStyle
.SetMonoColor( GetSettings().GetStyleSettings().GetMonoColor() );
355 aNewSettings
.SetStyleSettings( aStyle
);
356 // #i67023# do not call data changed listeners for this fake
357 // since they may understandably invalidate on settings changed
358 pDev
->OutputDevice::SetSettings( aNewSettings
);
360 DecorationView
aDecoView( pDev
);
361 rRect
= aDecoView
.DrawFrame( rRect
, DrawFrameStyle::Out
, DrawFrameFlags::WindowBorder
);
363 pDev
->OutputDevice::SetSettings( aOriginalSettings
);
366 void Control::SetShowAccelerator(bool bVal
)
368 mbShowAccelerator
= bVal
;
371 ControlLayoutData::~ControlLayoutData()
374 m_pParent
->ImplClearLayoutData();
377 Size
Control::GetOptimalSize() const
379 return Size( GetTextWidth( GetText() ) + 2 * 12,
380 GetTextHeight() + 2 * 6 );
383 void Control::SetReferenceDevice( OutputDevice
* _referenceDevice
)
385 if ( mpReferenceDevice
== _referenceDevice
)
388 mpReferenceDevice
= _referenceDevice
;
392 OutputDevice
* Control::GetReferenceDevice() const
394 // tdf#118377 It can happen that mpReferenceDevice is already disposed and
395 // stays disposed (see task, even when Dialog is closed). I have no idea if
396 // this may be very bad - someone who knows more about lifetime of OutputDevice's
397 // will have to decide.
398 // To secure this, I changed all accesses to mpControlData->mpReferenceDevice to
399 // use Control::GetReferenceDevice() - only use mpControlData->mpReferenceDevice
400 // inside Control::SetReferenceDevice and Control::GetReferenceDevice().
401 // Control::GetReferenceDevice() will now reset mpReferenceDevice if it is already
402 // disposed. This way all usages will do a kind of 'test-and-get' call.
403 if(nullptr != mpReferenceDevice
&& mpReferenceDevice
->isDisposed())
405 const_cast<Control
*>(this)->SetReferenceDevice(nullptr);
408 return mpReferenceDevice
;
411 const vcl::Font
& Control::GetCanonicalFont( const StyleSettings
& _rStyle
) const
413 return _rStyle
.GetLabelFont();
416 const Color
& Control::GetCanonicalTextColor( const StyleSettings
& _rStyle
) const
418 return _rStyle
.GetLabelTextColor();
421 void Control::ApplySettings(vcl::RenderContext
& rRenderContext
)
423 const StyleSettings
& rStyleSettings
= rRenderContext
.GetSettings().GetStyleSettings();
425 ApplyControlFont(rRenderContext
, GetCanonicalFont(rStyleSettings
));
427 ApplyControlForeground(rRenderContext
, GetCanonicalTextColor(rStyleSettings
));
428 rRenderContext
.SetTextFillColor();
431 void Control::ImplInitSettings()
433 ApplySettings(*GetOutDev());
436 tools::Rectangle
Control::DrawControlText( OutputDevice
& _rTargetDevice
, const tools::Rectangle
& rRect
, const OUString
& _rStr
,
437 DrawTextFlags _nStyle
, std::vector
< tools::Rectangle
>* _pVector
, OUString
* _pDisplayText
, const Size
* i_pDeviceSize
) const
439 OUString rPStr
= _rStr
;
440 DrawTextFlags nPStyle
= _nStyle
;
442 bool autoacc
= ImplGetSVData()->maNWFData
.mbAutoAccel
;
444 if (autoacc
&& !mbShowAccelerator
)
446 rPStr
= removeMnemonicFromString( _rStr
);
447 nPStyle
&= ~DrawTextFlags::HideMnemonic
;
450 if( !GetReferenceDevice() || ( GetReferenceDevice() == &_rTargetDevice
) )
452 const tools::Rectangle aRet
= _rTargetDevice
.GetTextRect(rRect
, rPStr
, nPStyle
);
453 _rTargetDevice
.DrawText(aRet
, rPStr
, nPStyle
, _pVector
, _pDisplayText
);
457 ControlTextRenderer
aRenderer( *this, _rTargetDevice
, *GetReferenceDevice() );
458 return aRenderer
.DrawText(rRect
, rPStr
, nPStyle
, _pVector
, _pDisplayText
, i_pDeviceSize
);
461 tools::Rectangle
Control::GetControlTextRect( OutputDevice
& _rTargetDevice
, const tools::Rectangle
& rRect
,
462 const OUString
& _rStr
, DrawTextFlags _nStyle
, Size
* o_pDeviceSize
) const
464 OUString rPStr
= _rStr
;
465 DrawTextFlags nPStyle
= _nStyle
;
467 bool autoacc
= ImplGetSVData()->maNWFData
.mbAutoAccel
;
469 if (autoacc
&& !mbShowAccelerator
)
471 rPStr
= removeMnemonicFromString( _rStr
);
472 nPStyle
&= ~DrawTextFlags::HideMnemonic
;
475 if ( !GetReferenceDevice() || ( GetReferenceDevice() == &_rTargetDevice
) )
477 tools::Rectangle aRet
= _rTargetDevice
.GetTextRect( rRect
, rPStr
, nPStyle
);
480 *o_pDeviceSize
= aRet
.GetSize();
485 ControlTextRenderer
aRenderer( *this, _rTargetDevice
, *GetReferenceDevice() );
486 return aRenderer
.GetTextRect(rRect
, rPStr
, nPStyle
, o_pDeviceSize
);
490 Control::GetUnzoomedControlPointFont() const
492 Font
aFont(GetCanonicalFont(GetSettings().GetStyleSettings()));
494 aFont
.Merge(GetControlFont());
498 void Control::LogicInvalidate(const tools::Rectangle
* pRectangle
)
500 VclPtr
<vcl::Window
> pParent
= GetParentWithLOKNotifier();
501 if (!pParent
|| !dynamic_cast<vcl::DocWindow
*>(GetParent()))
503 // if control doesn't belong to a DocWindow, the overridden base class
504 // method has to be invoked
505 Window::LogicInvalidate(pRectangle
);
509 // avoid endless paint/invalidate loop in Impress
510 if (comphelper::LibreOfficeKit::isTiledPainting())
513 tools::Rectangle aResultRectangle
;
516 // we have to invalidate the whole control area not the whole document
517 aResultRectangle
= tools::Rectangle(GetPosPixel(), GetSizePixel());
521 aResultRectangle
= *pRectangle
;
524 aResultRectangle
= PixelToLogic(aResultRectangle
, MapMode(MapUnit::MapTwip
));
525 pParent
->GetLOKNotifier()->notifyInvalidation(&aResultRectangle
);
528 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */