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 <vcl/commandevent.hxx>
21 #include <vcl/window.hxx>
22 #include <vcl/seleng.hxx>
23 #include <comphelper/lok.hxx>
24 #include <sal/log.hxx>
25 
~FunctionSet()26 FunctionSet::~FunctionSet()
27 {
28 }
29 
ShouldDeselect(bool bModifierKey1) const30 inline bool SelectionEngine::ShouldDeselect( bool bModifierKey1 ) const
31 {
32     return eSelMode != SelectionMode::Multiple || !bModifierKey1;
33 }
34 
35 // TODO: throw out FunctionSet::SelectAtPoint
36 
SelectionEngine(vcl::Window * pWindow,FunctionSet * pFuncSet)37 SelectionEngine::SelectionEngine( vcl::Window* pWindow, FunctionSet* pFuncSet ) :
38     pWin( pWindow ),
39     nUpdateInterval( SELENG_AUTOREPEAT_INTERVAL )
40 {
41     eSelMode = SelectionMode::Single;
42     pFunctionSet = pFuncSet;
43     nFlags = SelectionEngineFlags::EXPANDONMOVE;
44     nLockedMods = 0;
45 
46     aWTimer.SetInvokeHandler( LINK( this, SelectionEngine, ImpWatchDog ) );
47     aWTimer.SetTimeout( nUpdateInterval );
48     aWTimer.SetDebugName( "vcl::SelectionEngine aWTimer" );
49 }
50 
~SelectionEngine()51 SelectionEngine::~SelectionEngine()
52 {
53     aWTimer.Stop();
54 }
55 
IMPL_LINK_NOARG(SelectionEngine,ImpWatchDog,Timer *,void)56 IMPL_LINK_NOARG(SelectionEngine, ImpWatchDog, Timer *, void)
57 {
58     if ( !aArea.IsInside( aLastMove.GetPosPixel() ) )
59         SelMouseMove( aLastMove );
60 }
61 
SetSelectionMode(SelectionMode eMode)62 void SelectionEngine::SetSelectionMode( SelectionMode eMode )
63 {
64     eSelMode = eMode;
65 }
66 
CursorPosChanging(bool bShift,bool bMod1)67 void SelectionEngine::CursorPosChanging( bool bShift, bool bMod1 )
68 {
69     if ( !pFunctionSet )
70         return;
71 
72     if ( bShift && eSelMode != SelectionMode::Single )
73     {
74         if ( IsAddMode() )
75         {
76             if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
77             {
78                 pFunctionSet->CreateAnchor();
79                 nFlags |= SelectionEngineFlags::HAS_ANCH;
80             }
81         }
82         else
83         {
84             if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
85             {
86                 if( ShouldDeselect( bMod1 ) )
87                     pFunctionSet->DeselectAll();
88                 pFunctionSet->CreateAnchor();
89                 nFlags |= SelectionEngineFlags::HAS_ANCH;
90             }
91         }
92     }
93     else
94     {
95         if ( IsAddMode() )
96         {
97             if ( nFlags & SelectionEngineFlags::HAS_ANCH )
98             {
99                 // pFunctionSet->CreateCursor();
100                 pFunctionSet->DestroyAnchor();
101                 nFlags &= ~SelectionEngineFlags::HAS_ANCH;
102             }
103         }
104         else
105         {
106             if( ShouldDeselect( bMod1 ) )
107                 pFunctionSet->DeselectAll();
108             else
109                 pFunctionSet->DestroyAnchor();
110             nFlags &= ~SelectionEngineFlags::HAS_ANCH;
111         }
112     }
113 }
114 
SelMouseButtonDown(const MouseEvent & rMEvt)115 bool SelectionEngine::SelMouseButtonDown( const MouseEvent& rMEvt )
116 {
117     nFlags &= ~SelectionEngineFlags::CMDEVT;
118     if ( !pFunctionSet || rMEvt.GetClicks() > 1 )
119         return false;
120 
121     sal_uInt16 nModifier = rMEvt.GetModifier() | nLockedMods;
122     if ( nModifier & KEY_MOD2 )
123         return false;
124     // in SingleSelection: filter Control-Key,
125     // so that a D&D can be also started with a Ctrl-Click
126     if ( nModifier == KEY_MOD1 && eSelMode == SelectionMode::Single )
127         nModifier = 0;
128 
129     Point aPos = rMEvt.GetPosPixel();
130     aLastMove = rMEvt;
131 
132     if( !rMEvt.IsRight() )
133     {
134         CaptureMouse();
135         nFlags |= SelectionEngineFlags::IN_SEL;
136     }
137     else
138     {
139         nModifier = 0;
140     }
141 
142     switch ( nModifier )
143     {
144         case 0:     // KEY_NO_KEY
145         {
146             bool bSelAtPoint = pFunctionSet->IsSelectionAtPoint( aPos );
147             nFlags &= ~SelectionEngineFlags::IN_ADD;
148             if ( (nFlags & SelectionEngineFlags::DRG_ENAB) && bSelAtPoint )
149             {
150                 nFlags |= SelectionEngineFlags::WAIT_UPEVT;
151                 nFlags &= ~SelectionEngineFlags::IN_SEL;
152                 ReleaseMouse();
153                 return true;  // wait for STARTDRAG-Command-Event
154             }
155             if ( eSelMode != SelectionMode::Single )
156             {
157                 if( !IsAddMode() )
158                     pFunctionSet->DeselectAll();
159                 else
160                     pFunctionSet->DestroyAnchor();
161                 nFlags &= ~SelectionEngineFlags::HAS_ANCH; // bHasAnchor = false;
162             }
163             pFunctionSet->SetCursorAtPoint( aPos );
164             // special case Single-Selection, to enable simple Select+Drag
165             if (eSelMode == SelectionMode::Single && (nFlags & SelectionEngineFlags::DRG_ENAB))
166                 nFlags |= SelectionEngineFlags::WAIT_UPEVT;
167             return true;
168         }
169 
170         case KEY_SHIFT:
171             if ( eSelMode == SelectionMode::Single )
172             {
173                 ReleaseMouse();
174                 nFlags &= ~SelectionEngineFlags::IN_SEL;
175                 return false;
176             }
177             if ( nFlags & SelectionEngineFlags::ADD_ALW )
178                 nFlags |= SelectionEngineFlags::IN_ADD;
179             else
180                 nFlags &= ~SelectionEngineFlags::IN_ADD;
181 
182             if( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
183             {
184                 if ( !(nFlags & SelectionEngineFlags::IN_ADD) )
185                     pFunctionSet->DeselectAll();
186                 pFunctionSet->CreateAnchor();
187                 nFlags |= SelectionEngineFlags::HAS_ANCH;
188             }
189             pFunctionSet->SetCursorAtPoint( aPos );
190             return true;
191 
192         case KEY_MOD1:
193             // allow Control only for Multi-Select
194             if ( eSelMode != SelectionMode::Multiple )
195             {
196                 nFlags &= ~SelectionEngineFlags::IN_SEL;
197                 ReleaseMouse();
198                 return true;  // skip Mouse-Click
199             }
200             if ( nFlags & SelectionEngineFlags::HAS_ANCH )
201             {
202                 // pFunctionSet->CreateCursor();
203                 pFunctionSet->DestroyAnchor();
204                 nFlags &= ~SelectionEngineFlags::HAS_ANCH;
205             }
206             if ( pFunctionSet->IsSelectionAtPoint( aPos ) )
207             {
208                 pFunctionSet->DeselectAtPoint( aPos );
209                 pFunctionSet->SetCursorAtPoint( aPos, true );
210             }
211             else
212             {
213                 pFunctionSet->SetCursorAtPoint( aPos );
214             }
215             return true;
216 
217         case KEY_SHIFT + KEY_MOD1:
218             if ( eSelMode != SelectionMode::Multiple )
219             {
220                 ReleaseMouse();
221                 nFlags &= ~SelectionEngineFlags::IN_SEL;
222                 return false;
223             }
224             nFlags |= SelectionEngineFlags::IN_ADD; //bIsInAddMode = true;
225             if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
226             {
227                 pFunctionSet->CreateAnchor();
228                 nFlags |= SelectionEngineFlags::HAS_ANCH;
229             }
230             pFunctionSet->SetCursorAtPoint( aPos );
231             return true;
232     }
233 
234     return false;
235 }
236 
SelMouseButtonUp(const MouseEvent & rMEvt)237 bool SelectionEngine::SelMouseButtonUp( const MouseEvent& rMEvt )
238 {
239     aWTimer.Stop();
240     if (!pFunctionSet)
241     {
242         const SelectionEngineFlags nMask = SelectionEngineFlags::CMDEVT | SelectionEngineFlags::WAIT_UPEVT | SelectionEngineFlags::IN_SEL;
243         nFlags &= ~nMask;
244         return false;
245     }
246 
247     if (!rMEvt.IsRight())
248         ReleaseMouse();
249 
250     if( (nFlags & SelectionEngineFlags::WAIT_UPEVT) && !(nFlags & SelectionEngineFlags::CMDEVT) &&
251         eSelMode != SelectionMode::Single)
252     {
253         // MouseButtonDown in Sel but no CommandEvent yet
254         // ==> deselect
255         sal_uInt16 nModifier = aLastMove.GetModifier() | nLockedMods;
256         if( nModifier == KEY_MOD1 || IsAlwaysAdding() )
257         {
258             if( !(nModifier & KEY_SHIFT) )
259             {
260                 pFunctionSet->DestroyAnchor();
261                 nFlags &= ~SelectionEngineFlags::HAS_ANCH; // uncheck anchor
262             }
263             pFunctionSet->DeselectAtPoint( aLastMove.GetPosPixel() );
264             nFlags &= ~SelectionEngineFlags::HAS_ANCH; // uncheck anchor
265             pFunctionSet->SetCursorAtPoint( aLastMove.GetPosPixel(), true );
266         }
267         else
268         {
269             pFunctionSet->DeselectAll();
270             nFlags &= ~SelectionEngineFlags::HAS_ANCH; // uncheck anchor
271             pFunctionSet->SetCursorAtPoint( aLastMove.GetPosPixel() );
272         }
273     }
274 
275     const SelectionEngineFlags nMask = SelectionEngineFlags::CMDEVT | SelectionEngineFlags::WAIT_UPEVT | SelectionEngineFlags::IN_SEL;
276     nFlags &= ~nMask;
277     return true;
278 }
279 
ReleaseMouse()280 void SelectionEngine::ReleaseMouse()
281 {
282     if (!pWin || !pWin->IsMouseCaptured())
283         return;
284     pWin->ReleaseMouse();
285 }
286 
CaptureMouse()287 void SelectionEngine::CaptureMouse()
288 {
289     if (!pWin || pWin->IsMouseCaptured())
290         return;
291     pWin->CaptureMouse();
292 }
293 
SelMouseMove(const MouseEvent & rMEvt)294 bool SelectionEngine::SelMouseMove( const MouseEvent& rMEvt )
295 {
296 
297     if ( !pFunctionSet || !(nFlags & SelectionEngineFlags::IN_SEL) ||
298          (nFlags & (SelectionEngineFlags::CMDEVT | SelectionEngineFlags::WAIT_UPEVT)) )
299         return false;
300 
301     if( !(nFlags & SelectionEngineFlags::EXPANDONMOVE) )
302         return false; // wait for DragEvent!
303 
304     aLastMove = rMEvt;
305     // if the mouse is outside the area, the frequency of
306     // SetCursorAtPoint() is only set by the Timer
307     if( aWTimer.IsActive() && !aArea.IsInside( rMEvt.GetPosPixel() ))
308         return true;
309 
310     aWTimer.SetTimeout( nUpdateInterval );
311     if (!comphelper::LibreOfficeKit::isActive())
312         // Generating fake mouse moves does not work with LOK.
313         aWTimer.Start();
314     if ( eSelMode != SelectionMode::Single )
315     {
316         if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
317         {
318             pFunctionSet->CreateAnchor();
319             nFlags |= SelectionEngineFlags::HAS_ANCH;
320         }
321     }
322 
323     pFunctionSet->SetCursorAtPoint( rMEvt.GetPosPixel() );
324 
325     return true;
326 }
327 
SetWindow(vcl::Window * pNewWin)328 void SelectionEngine::SetWindow( vcl::Window* pNewWin )
329 {
330     if( pNewWin != pWin )
331     {
332         if (nFlags & SelectionEngineFlags::IN_SEL)
333             ReleaseMouse();
334         pWin = pNewWin;
335         if (nFlags & SelectionEngineFlags::IN_SEL)
336             CaptureMouse();
337     }
338 }
339 
Reset()340 void SelectionEngine::Reset()
341 {
342     aWTimer.Stop();
343     if (nFlags & SelectionEngineFlags::IN_SEL)
344         ReleaseMouse();
345     nFlags &= ~SelectionEngineFlags(SelectionEngineFlags::HAS_ANCH | SelectionEngineFlags::IN_SEL);
346     nLockedMods = 0;
347 }
348 
Command(const CommandEvent & rCEvt)349 void SelectionEngine::Command( const CommandEvent& rCEvt )
350 {
351     // Timer aWTimer is active during enlarging a selection
352     if ( !pFunctionSet || aWTimer.IsActive() )
353         return;
354     aWTimer.Stop();
355     if ( rCEvt.GetCommand() == CommandEventId::StartDrag )
356     {
357         nFlags |= SelectionEngineFlags::CMDEVT;
358         if ( nFlags & SelectionEngineFlags::DRG_ENAB )
359         {
360             SAL_WARN_IF( !rCEvt.IsMouseEvent(), "vcl", "STARTDRAG: Not a MouseEvent" );
361             if ( pFunctionSet->IsSelectionAtPoint( rCEvt.GetMousePosPixel() ) )
362             {
363                 aLastMove = MouseEvent( rCEvt.GetMousePosPixel(),
364                                aLastMove.GetClicks(), aLastMove.GetMode(),
365                                aLastMove.GetButtons(), aLastMove.GetModifier() );
366                 pFunctionSet->BeginDrag();
367                 const SelectionEngineFlags nMask = SelectionEngineFlags::CMDEVT|SelectionEngineFlags::WAIT_UPEVT|SelectionEngineFlags::IN_SEL;
368                 nFlags &= ~nMask;
369             }
370             else
371                 nFlags &= ~SelectionEngineFlags::CMDEVT;
372         }
373         else
374             nFlags &= ~SelectionEngineFlags::CMDEVT;
375     }
376 }
377 
SetUpdateInterval(sal_uLong nInterval)378 void SelectionEngine::SetUpdateInterval( sal_uLong nInterval )
379 {
380     if (nInterval < SELENG_AUTOREPEAT_INTERVAL_MIN)
381         // Set a lower threshold.  On Windows, setting this value too low
382         // would cause selection to get updated indefinitely.
383         nInterval = SELENG_AUTOREPEAT_INTERVAL_MIN;
384 
385     if (nUpdateInterval == nInterval)
386         // no update needed.
387         return;
388 
389     if (aWTimer.IsActive())
390     {
391         // reset the timer right away on interval change.
392         aWTimer.Stop();
393         aWTimer.SetTimeout(nInterval);
394         aWTimer.Start();
395     }
396     else
397         aWTimer.SetTimeout(nInterval);
398 
399     nUpdateInterval = nInterval;
400 }
401 
402 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
403