1 /*************************************************************************
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3 *
4 * Copyright 2009 by Sun Microsystems, Inc.
5 *
6 * OpenOffice.org - a multi-platform office productivity suite
7 *
8 * This file is part of OpenOffice.org.
9 *
10 * OpenOffice.org is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU Lesser General Public License version 3
12 * only, as published by the Free Software Foundation.
13 *
14 * OpenOffice.org is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU Lesser General Public License version 3 for more details
18 * (a copy is included in the LICENSE file that accompanied this code).
19 *
20 * You should have received a copy of the GNU Lesser General Public License
21 * version 3 along with OpenOffice.org.  If not, see
22 * <http://www.openoffice.org/license.html>
23 * for a copy of the LGPLv3 License.
24 ************************************************************************/
25 
26 // MARKER(update_precomp.py): autogen include statement, do not remove
27 #include "precompiled_vcl.hxx"
28 
29 #include "vcl/quickselectionengine.hxx"
30 #include "vcl/event.hxx"
31 #include "vcl/timer.hxx"
32 #include "vcl/i18nhelp.hxx"
33 #include "vcl/svapp.hxx"
34 
35 #include <boost/optional.hpp>
36 
37 //........................................................................
38 namespace vcl
39 {
40 //........................................................................
41 
42 	//====================================================================
43 	//= QuickSelectionEngine_Data
44 	//====================================================================
45     struct QuickSelectionEngine_Data
46     {
47         ISearchableStringList&              rEntryList;
48         String                              sCurrentSearchString;
49         ::boost::optional< sal_Unicode >    aSingleSearchChar;
50         Timer                               aSearchTimeout;
51 
52         QuickSelectionEngine_Data( ISearchableStringList& _entryList )
53             :rEntryList( _entryList )
54             ,sCurrentSearchString()
55             ,aSingleSearchChar()
56             ,aSearchTimeout()
57         {
58 	        aSearchTimeout.SetTimeout( 2500 );
59 	        aSearchTimeout.SetTimeoutHdl( LINK( this, QuickSelectionEngine_Data, SearchStringTimeout ) );
60         }
61 
62         ~QuickSelectionEngine_Data()
63         {
64 	        aSearchTimeout.Stop();
65         }
66 
67 	    DECL_LINK( SearchStringTimeout, Timer* );
68     };
69 
70     //--------------------------------------------------------------------
71     namespace
72     {
73         static void lcl_reset( QuickSelectionEngine_Data& _data )
74         {
75             _data.sCurrentSearchString.Erase();
76             _data.aSingleSearchChar.reset();
77             _data.aSearchTimeout.Stop();
78         }
79     }
80 
81 	//--------------------------------------------------------------------
82     IMPL_LINK( QuickSelectionEngine_Data, SearchStringTimeout, Timer*, /*EMPTYARG*/ )
83     {
84         lcl_reset( *this );
85 	    return 1;
86     }
87 
88 	//--------------------------------------------------------------------
89     static StringEntryIdentifier findMatchingEntry( const String& _searchString, QuickSelectionEngine_Data& _engineData )
90     {
91         const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetLocaleI18nHelper();
92             // TODO: do we really need the Window's settings here? The original code used it ...
93 
94         String sEntryText;
95         // get the "current + 1" entry
96         StringEntryIdentifier pSearchEntry = _engineData.rEntryList.CurrentEntry( sEntryText );
97         if ( pSearchEntry )
98             pSearchEntry = _engineData.rEntryList.NextEntry( pSearchEntry, sEntryText );
99         // loop 'til we find another matching entry
100         StringEntryIdentifier pStartedWith = pSearchEntry;
101         while ( pSearchEntry )
102         {
103             if ( rI18nHelper.MatchString( _searchString, sEntryText ) != 0 )
104                 break;
105 
106             pSearchEntry = _engineData.rEntryList.NextEntry( pSearchEntry, sEntryText );
107             if ( pSearchEntry == pStartedWith )
108                 pSearchEntry = NULL;
109         }
110 
111         return pSearchEntry;
112     }
113 
114     //====================================================================
115 	//= QuickSelectionEngine
116 	//====================================================================
117 	//--------------------------------------------------------------------
118     QuickSelectionEngine::QuickSelectionEngine( ISearchableStringList& _entryList )
119         :m_pData( new QuickSelectionEngine_Data( _entryList ) )
120     {
121     }
122 
123 	//--------------------------------------------------------------------
124     QuickSelectionEngine::~QuickSelectionEngine()
125     {
126     }
127 
128     //--------------------------------------------------------------------
129     bool QuickSelectionEngine::HandleKeyEvent( const KeyEvent& _keyEvent )
130     {
131         xub_Unicode c = _keyEvent.GetCharCode();
132 
133         if ( ( c >= 32 ) && ( c != 127 ) && !_keyEvent.GetKeyCode().IsMod2() )
134         {
135             m_pData->sCurrentSearchString += c;
136             OSL_TRACE( "QuickSelectionEngine::HandleKeyEvent: searching for %s", ByteString( m_pData->sCurrentSearchString, RTL_TEXTENCODING_UTF8 ).GetBuffer() );
137 
138             if ( m_pData->sCurrentSearchString.Len() == 1 )
139             {   // first character in the search -> remmeber
140                 m_pData->aSingleSearchChar.reset( c );
141             }
142             else if ( m_pData->sCurrentSearchString.Len() > 1 )
143             {
144                 if ( !!m_pData->aSingleSearchChar && ( *m_pData->aSingleSearchChar != c ) )
145                     // we already have a "single char", but the current one is different -> reset
146                     m_pData->aSingleSearchChar.reset();
147             }
148 
149             XubString aSearchTemp( m_pData->sCurrentSearchString );
150 
151             StringEntryIdentifier pMatchingEntry = findMatchingEntry( aSearchTemp, *m_pData );
152             OSL_TRACE( "QuickSelectionEngine::HandleKeyEvent: found %p", pMatchingEntry );
153             if ( !pMatchingEntry && ( aSearchTemp.Len() > 1 ) && !!m_pData->aSingleSearchChar )
154             {
155                 // if there's only one letter in the search string, use a different search mode
156                 aSearchTemp = *m_pData->aSingleSearchChar;
157                 pMatchingEntry = findMatchingEntry( aSearchTemp, *m_pData );
158             }
159 
160             if ( pMatchingEntry )
161             {
162                 m_pData->rEntryList.SelectEntry( pMatchingEntry );
163                 m_pData->aSearchTimeout.Start();
164             }
165             else
166             {
167                 lcl_reset( *m_pData );
168             }
169 
170             return true;
171         }
172         return false;
173     }
174 
175     //--------------------------------------------------------------------
176     void QuickSelectionEngine::Reset()
177     {
178         lcl_reset( *m_pData );
179     }
180 
181 //........................................................................
182 } // namespace vcl
183 //........................................................................
184