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 
21 #include <fmcontrolbordermanager.hxx>
22 
23 #include <fmprop.hxx>
24 
25 #include <com/sun/star/form/validation/XValidatableFormComponent.hpp>
26 #include <com/sun/star/awt/XTextComponent.hpp>
27 #include <com/sun/star/awt/XListBox.hpp>
28 #include <tools/debug.hxx>
29 #include <tools/diagnose_ex.h>
30 #include <osl/diagnose.h>
31 
32 
33 namespace svxform
34 {
35 
36 
37     using namespace ::com::sun::star::uno;
38     using namespace ::com::sun::star::awt;
39     using namespace ::com::sun::star::form::validation;
40 
41 
42     //= helper
43 
44 
setUnderline(const Reference<XVclWindowPeer> & _rxPeer,const UnderlineDescriptor & _rUnderline)45     static void setUnderline( const Reference< XVclWindowPeer >& _rxPeer, const UnderlineDescriptor& _rUnderline )
46     {
47         OSL_ENSURE( _rxPeer.is(), "setUnderline: invalid peer!" );
48 
49         // the underline type is an aspect of the font
50         FontDescriptor aFont;
51         OSL_VERIFY( _rxPeer->getProperty( FM_PROP_FONT ) >>= aFont );
52         aFont.Underline = _rUnderline.nUnderlineType;
53         _rxPeer->setProperty( FM_PROP_FONT, makeAny( aFont ) );
54         // the underline color is a separate property
55         _rxPeer->setProperty( FM_PROP_TEXTLINECOLOR, makeAny( _rUnderline.nUnderlineColor ) );
56     }
57 
58 
getUnderline(const Reference<XVclWindowPeer> & _rxPeer,UnderlineDescriptor & _rUnderline)59     static void getUnderline( const Reference< XVclWindowPeer >& _rxPeer, UnderlineDescriptor& _rUnderline )
60     {
61         OSL_ENSURE( _rxPeer.is(), "getUnderline: invalid peer!" );
62 
63         FontDescriptor aFont;
64         OSL_VERIFY( _rxPeer->getProperty( FM_PROP_FONT ) >>= aFont );
65         _rUnderline.nUnderlineType = aFont.Underline;
66 
67         OSL_VERIFY( _rxPeer->getProperty( FM_PROP_TEXTLINECOLOR ) >>= _rUnderline.nUnderlineColor );
68     }
69 
70 
getBorder(const Reference<XVclWindowPeer> & _rxPeer,BorderDescriptor & _rBorder)71     static void getBorder( const Reference< XVclWindowPeer >& _rxPeer, BorderDescriptor& _rBorder )
72     {
73         OSL_ENSURE( _rxPeer.is(), "getBorder: invalid peer!" );
74 
75         OSL_VERIFY( _rxPeer->getProperty( FM_PROP_BORDER ) >>= _rBorder.nBorderType );
76         OSL_VERIFY( _rxPeer->getProperty( FM_PROP_BORDERCOLOR ) >>= _rBorder.nBorderColor );
77     }
78 
79 
setBorder(const Reference<XVclWindowPeer> & _rxPeer,const BorderDescriptor & _rBorder)80     static void setBorder( const Reference< XVclWindowPeer >& _rxPeer, const BorderDescriptor& _rBorder )
81     {
82         OSL_ENSURE( _rxPeer.is(), "setBorder: invalid peer!" );
83 
84         _rxPeer->setProperty( FM_PROP_BORDER, makeAny( _rBorder.nBorderType ) );
85         _rxPeer->setProperty( FM_PROP_BORDERCOLOR, makeAny( _rBorder.nBorderColor ) );
86     }
87 
ControlBorderManager()88     ControlBorderManager::ControlBorderManager()
89         :m_nFocusColor    ( 0x000000FF )
90         ,m_nMouseHoveColor( 0x007098BE )
91         ,m_nInvalidColor  ( 0x00FF0000 )
92         ,m_bDynamicBorderColors( false )
93     {
94     }
95 
96 
~ControlBorderManager()97     ControlBorderManager::~ControlBorderManager()
98     {
99     }
100 
101 
canColorBorder(const Reference<XVclWindowPeer> & _rxPeer)102     bool ControlBorderManager::canColorBorder( const Reference< XVclWindowPeer >& _rxPeer )
103     {
104         OSL_PRECOND( _rxPeer.is(), "ControlBorderManager::canColorBorder: invalid peer!" );
105 
106         PeerBag::const_iterator aPos = m_aColorableControls.find( _rxPeer );
107         if ( aPos != m_aColorableControls.end() )
108             return true;
109 
110         aPos = m_aNonColorableControls.find( _rxPeer );
111         if ( aPos != m_aNonColorableControls.end() )
112             return false;
113 
114         // this peer is not yet known
115 
116         // no border coloring for controls which are not for text input
117         // #i37434# / 2004-11-19 / frank.schoenheit@sun.com
118         Reference< XTextComponent > xText( _rxPeer, UNO_QUERY );
119         Reference< XListBox > xListBox( _rxPeer, UNO_QUERY );
120         if ( xText.is() || xListBox.is() )
121         {
122             sal_Int16 nBorderStyle = VisualEffect::NONE;
123             OSL_VERIFY( _rxPeer->getProperty( FM_PROP_BORDER ) >>= nBorderStyle );
124             if ( nBorderStyle == VisualEffect::FLAT )
125                 // if you change this to also accept LOOK3D, then this would also work, but look ugly
126             {
127                 m_aColorableControls.insert( _rxPeer );
128                 return true;
129             }
130         }
131 
132         m_aNonColorableControls.insert( _rxPeer );
133         return false;
134     }
135 
136 
getControlStatus(const Reference<XControl> & _rxControl)137     ControlStatus ControlBorderManager::getControlStatus( const Reference< XControl >& _rxControl )
138     {
139         ControlStatus nStatus = ControlStatus::NONE;
140 
141         if ( _rxControl.get() == m_aFocusControl.xControl.get() )
142             nStatus |= ControlStatus::Focused;
143 
144         if ( _rxControl.get() == m_aMouseHoverControl.xControl.get() )
145             nStatus |= ControlStatus::MouseHover;
146 
147         if ( m_aInvalidControls.find( ControlData( _rxControl ) ) != m_aInvalidControls.end() )
148             nStatus |= ControlStatus::Invalid;
149 
150         return nStatus;
151     }
152 
153 
getControlColorByStatus(ControlStatus _nStatus)154     Color ControlBorderManager::getControlColorByStatus( ControlStatus _nStatus )
155     {
156         // "invalid" is ranked highest
157         if ( _nStatus & ControlStatus::Invalid )
158             return m_nInvalidColor;
159 
160         // then, "focused" is more important than ...
161         if ( _nStatus & ControlStatus::Focused )
162             return m_nFocusColor;
163 
164         // ... "mouse over"
165         if ( _nStatus & ControlStatus::MouseHover )
166             return m_nMouseHoveColor;
167 
168         OSL_FAIL( "ControlBorderManager::getControlColorByStatus: invalid status!" );
169         return Color(0);
170     }
171 
172 
updateBorderStyle(const Reference<XControl> & _rxControl,const Reference<XVclWindowPeer> & _rxPeer,const BorderDescriptor & _rFallback)173     void ControlBorderManager::updateBorderStyle( const Reference< XControl >& _rxControl, const Reference< XVclWindowPeer >& _rxPeer, const BorderDescriptor& _rFallback )
174     {
175         OSL_PRECOND( _rxControl.is() && _rxPeer.is(), "ControlBorderManager::updateBorderStyle: invalid parameters!" );
176 
177         ControlStatus nStatus = getControlStatus( _rxControl );
178         BorderDescriptor aBorder;
179         aBorder.nBorderType =   ( nStatus == ControlStatus::NONE )
180                             ?   _rFallback.nBorderType
181                             :   VisualEffect::FLAT;
182         aBorder.nBorderColor =   ( nStatus == ControlStatus::NONE )
183                              ?   _rFallback.nBorderColor
184                              :   getControlColorByStatus( nStatus );
185         setBorder( _rxPeer, aBorder );
186     }
187 
188 
determineOriginalBorderStyle(const Reference<XControl> & _rxControl,BorderDescriptor & _rData) const189     void ControlBorderManager::determineOriginalBorderStyle( const Reference< XControl >& _rxControl, BorderDescriptor& _rData ) const
190     {
191         _rData = ControlData();
192         if ( m_aFocusControl.xControl.get() == _rxControl.get() )
193         {
194             _rData = m_aFocusControl;
195         }
196         else if ( m_aMouseHoverControl.xControl.get() == _rxControl.get() )
197         {
198             _rData = m_aMouseHoverControl;
199         }
200         else
201         {
202             ControlBag::const_iterator aPos = m_aInvalidControls.find( _rxControl );
203             if ( aPos != m_aInvalidControls.end() )
204             {
205                 _rData = *aPos;
206             }
207             else
208             {
209                 Reference< XVclWindowPeer > xPeer( _rxControl->getPeer(), UNO_QUERY );
210                 getBorder( xPeer, _rData );
211             }
212         }
213     }
214 
215 
controlStatusGained(const Reference<XInterface> & _rxControl,ControlData & _rControlData)216     void ControlBorderManager::controlStatusGained( const Reference< XInterface >& _rxControl, ControlData& _rControlData )
217     {
218         if ( _rxControl == _rControlData.xControl )
219             // nothing to do - though suspicious
220             return;
221 
222         Reference< XControl > xAsControl( _rxControl, UNO_QUERY );
223         DBG_ASSERT( xAsControl.is(), "ControlBorderManager::controlStatusGained: invalid control!" );
224         if ( !xAsControl.is() )
225             return;
226 
227         try
228         {
229             Reference< XVclWindowPeer > xPeer( xAsControl->getPeer(), UNO_QUERY );
230             if ( xPeer.is() && canColorBorder( xPeer ) )
231             {
232                 // remember the control and its current border color
233                 _rControlData.xControl.clear(); // so determineOriginalBorderStyle doesn't get confused
234 
235                 determineOriginalBorderStyle( xAsControl, _rControlData );
236 
237                 _rControlData.xControl = xAsControl;
238 
239                 updateBorderStyle( xAsControl, xPeer, _rControlData );
240             }
241         }
242         catch( const Exception& )
243         {
244             TOOLS_WARN_EXCEPTION( "svx", "ControlBorderManager::controlStatusGained" );
245         }
246     }
247 
248 
controlStatusLost(const Reference<XInterface> & _rxControl,ControlData & _rControlData)249     void ControlBorderManager::controlStatusLost( const Reference< XInterface >& _rxControl, ControlData& _rControlData )
250     {
251         if ( _rxControl != _rControlData.xControl )
252             // nothing to do
253             return;
254 
255         OSL_PRECOND( _rControlData.xControl.is(), "ControlBorderManager::controlStatusLost: invalid control data - this will crash!" );
256         try
257         {
258             Reference< XVclWindowPeer > xPeer( _rControlData.xControl->getPeer(), UNO_QUERY );
259             if ( xPeer.is() && canColorBorder( xPeer ) )
260             {
261                 ControlData aPreviousStatus( _rControlData );
262                 _rControlData = ControlData();
263                 updateBorderStyle( aPreviousStatus.xControl, xPeer, aPreviousStatus );
264             }
265         }
266         catch( const Exception& )
267         {
268             TOOLS_WARN_EXCEPTION( "svx", "ControlBorderManager::controlStatusLost" );
269         }
270     }
271 
272 
enableDynamicBorderColor()273     void ControlBorderManager::enableDynamicBorderColor( )
274     {
275         m_bDynamicBorderColors = true;
276     }
277 
278 
disableDynamicBorderColor()279     void ControlBorderManager::disableDynamicBorderColor( )
280     {
281         m_bDynamicBorderColors = false;
282         restoreAll();
283     }
284 
285 
setStatusColor(ControlStatus _nStatus,Color _nColor)286     void ControlBorderManager::setStatusColor( ControlStatus _nStatus, Color _nColor )
287     {
288         switch ( _nStatus )
289         {
290         case ControlStatus::Focused:
291             m_nFocusColor = _nColor;
292             break;
293         case ControlStatus::MouseHover:
294             m_nMouseHoveColor = _nColor;
295             break;
296         case ControlStatus::Invalid:
297             m_nInvalidColor = _nColor;
298             break;
299         default:
300             OSL_FAIL( "ControlBorderManager::setStatusColor: invalid status!" );
301         }
302     }
303 
304 
restoreAll()305     void ControlBorderManager::restoreAll()
306     {
307         if ( m_aFocusControl.xControl.is() )
308             controlStatusLost( m_aFocusControl.xControl, m_aFocusControl );
309         if ( m_aMouseHoverControl.xControl.is() )
310             controlStatusLost( m_aMouseHoverControl.xControl, m_aMouseHoverControl );
311 
312         ControlBag aInvalidControls;
313         m_aInvalidControls.swap( aInvalidControls );
314 
315         for (const auto& rControl : aInvalidControls)
316         {
317             Reference< XVclWindowPeer > xPeer( rControl.xControl->getPeer(), UNO_QUERY );
318             if ( xPeer.is() )
319             {
320                 updateBorderStyle( rControl.xControl, xPeer, rControl );
321                 xPeer->setProperty( FM_PROP_HELPTEXT, makeAny( rControl.sOriginalHelpText ) );
322                 setUnderline( xPeer, rControl );
323             }
324         }
325     }
326 
327 
focusGained(const Reference<XInterface> & _rxControl)328     void ControlBorderManager::focusGained( const Reference< XInterface >& _rxControl )
329     {
330         if ( m_bDynamicBorderColors )
331             controlStatusGained( _rxControl, m_aFocusControl );
332     }
333 
334 
focusLost(const Reference<XInterface> & _rxControl)335     void ControlBorderManager::focusLost( const Reference< XInterface >& _rxControl )
336     {
337         if ( m_bDynamicBorderColors )
338             controlStatusLost( _rxControl, m_aFocusControl );
339     }
340 
341 
mouseEntered(const Reference<XInterface> & _rxControl)342     void ControlBorderManager::mouseEntered( const Reference< XInterface >& _rxControl )
343     {
344         if ( m_bDynamicBorderColors )
345             controlStatusGained( _rxControl, m_aMouseHoverControl );
346     }
347 
348 
mouseExited(const Reference<XInterface> & _rxControl)349     void ControlBorderManager::mouseExited( const Reference< XInterface >& _rxControl )
350     {
351         if ( m_bDynamicBorderColors )
352             controlStatusLost( _rxControl, m_aMouseHoverControl );
353     }
354 
355 
validityChanged(const Reference<XControl> & _rxControl,const Reference<XValidatableFormComponent> & _rxValidatable)356     void ControlBorderManager::validityChanged( const Reference< XControl >& _rxControl, const Reference< XValidatableFormComponent >& _rxValidatable )
357     {
358         try
359         {
360             OSL_ENSURE( _rxControl.is(), "ControlBorderManager::validityChanged: invalid control!" );
361             OSL_ENSURE( _rxValidatable.is(), "ControlBorderManager::validityChanged: invalid validatable!" );
362 
363             Reference< XVclWindowPeer > xPeer( _rxControl.is() ? _rxControl->getPeer() : Reference< XWindowPeer >(), UNO_QUERY );
364             if ( !xPeer.is() || !_rxValidatable.is() )
365                 return;
366 
367             ControlData aData( _rxControl );
368 
369             if ( _rxValidatable->isValid() )
370             {
371                 ControlBag::iterator aPos = m_aInvalidControls.find( aData );
372                 if ( aPos != m_aInvalidControls.end() )
373                 {   // invalid before, valid now
374                     ControlData aOriginalLayout( *aPos );
375                     m_aInvalidControls.erase( aPos );
376 
377                     // restore all the things we used to indicate invalidity
378                     if ( m_bDynamicBorderColors )
379                         updateBorderStyle( _rxControl, xPeer, aOriginalLayout );
380                     xPeer->setProperty( FM_PROP_HELPTEXT, makeAny( aOriginalLayout.sOriginalHelpText ) );
381                     setUnderline( xPeer, aOriginalLayout );
382                 }
383                 return;
384             }
385 
386             // we're here in the INVALID case
387             if ( m_aInvalidControls.find( _rxControl ) == m_aInvalidControls.end() )
388             {   // valid before, invalid now
389 
390                 // remember the current border
391                 determineOriginalBorderStyle( _rxControl, aData );
392                 // and tool tip
393                 xPeer->getProperty( FM_PROP_HELPTEXT ) >>= aData.sOriginalHelpText;
394                 // and font
395                 getUnderline( xPeer, aData );
396 
397                 m_aInvalidControls.insert( aData );
398 
399                 // update the border to the new invalidity
400                 if ( m_bDynamicBorderColors && canColorBorder( xPeer ) )
401                     updateBorderStyle( _rxControl, xPeer, aData );
402                 else
403                 {
404                     // and also the new font
405                     setUnderline( xPeer, UnderlineDescriptor( css::awt::FontUnderline::WAVE, m_nInvalidColor ) );
406                 }
407             }
408 
409             // update the explanation for invalidity (this is always done, even if the validity did not change)
410             Reference< XValidator > xValidator = _rxValidatable->getValidator();
411             OSL_ENSURE( xValidator.is(), "ControlBorderManager::validityChanged: invalid, but no validator?" );
412             OUString sExplainInvalidity = xValidator.is() ? xValidator->explainInvalid( _rxValidatable->getCurrentValue() ) : OUString();
413             xPeer->setProperty( FM_PROP_HELPTEXT, makeAny( sExplainInvalidity ) );
414         }
415         catch( const Exception& )
416         {
417             TOOLS_WARN_EXCEPTION( "svx", "ControlBorderManager::validityChanged" );
418         }
419     }
420 
421 
422 }
423 
424 
425 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
426