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     bool nSwap = comphelper::LibreOfficeKit::isActive() && (nModifier & KEY_MOD1) && (nModifier & KEY_MOD2);
123 
124     if ( !nSwap && (nModifier & KEY_MOD2) )
125         return false;
126     // in SingleSelection: filter Control-Key,
127     // so that a D&D can be also started with a Ctrl-Click
128     if ( nModifier == KEY_MOD1 && eSelMode == SelectionMode::Single )
129         nModifier = 0;
130 
131     Point aPos = rMEvt.GetPosPixel();
132     aLastMove = rMEvt;
133 
134     if( !rMEvt.IsRight() )
135     {
136         CaptureMouse();
137         nFlags |= SelectionEngineFlags::IN_SEL;
138     }
139     else
140     {
141         nModifier = 0;
142     }
143 
144     if (nSwap)
145     {
146         pFunctionSet->CreateAnchor();
147         pFunctionSet->SetCursorAtPoint( aPos );
148         return true;
149     }
150 
151     switch ( nModifier )
152     {
153         case 0:     // KEY_NO_KEY
154         {
155             bool bSelAtPoint = pFunctionSet->IsSelectionAtPoint( aPos );
156             nFlags &= ~SelectionEngineFlags::IN_ADD;
157             if ( (nFlags & SelectionEngineFlags::DRG_ENAB) && bSelAtPoint )
158             {
159                 nFlags |= SelectionEngineFlags::WAIT_UPEVT;
160                 nFlags &= ~SelectionEngineFlags::IN_SEL;
161                 ReleaseMouse();
162                 return true;  // wait for STARTDRAG-Command-Event
163             }
164             if ( eSelMode != SelectionMode::Single )
165             {
166                 if( !IsAddMode() )
167                     pFunctionSet->DeselectAll();
168                 else
169                     pFunctionSet->DestroyAnchor();
170                 nFlags &= ~SelectionEngineFlags::HAS_ANCH; // bHasAnchor = false;
171             }
172             pFunctionSet->SetCursorAtPoint( aPos );
173             // special case Single-Selection, to enable simple Select+Drag
174             if (eSelMode == SelectionMode::Single && (nFlags & SelectionEngineFlags::DRG_ENAB))
175                 nFlags |= SelectionEngineFlags::WAIT_UPEVT;
176             return true;
177         }
178 
179         case KEY_SHIFT:
180             if ( eSelMode == SelectionMode::Single )
181             {
182                 ReleaseMouse();
183                 nFlags &= ~SelectionEngineFlags::IN_SEL;
184                 return false;
185             }
186             if ( nFlags & SelectionEngineFlags::ADD_ALW )
187                 nFlags |= SelectionEngineFlags::IN_ADD;
188             else
189                 nFlags &= ~SelectionEngineFlags::IN_ADD;
190 
191             if( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
192             {
193                 if ( !(nFlags & SelectionEngineFlags::IN_ADD) )
194                     pFunctionSet->DeselectAll();
195                 pFunctionSet->CreateAnchor();
196                 nFlags |= SelectionEngineFlags::HAS_ANCH;
197             }
198             pFunctionSet->SetCursorAtPoint( aPos );
199             return true;
200 
201         case KEY_MOD1:
202             // allow Control only for Multi-Select
203             if ( eSelMode != SelectionMode::Multiple )
204             {
205                 nFlags &= ~SelectionEngineFlags::IN_SEL;
206                 ReleaseMouse();
207                 return true;  // skip Mouse-Click
208             }
209             if ( nFlags & SelectionEngineFlags::HAS_ANCH )
210             {
211                 // pFunctionSet->CreateCursor();
212                 pFunctionSet->DestroyAnchor();
213                 nFlags &= ~SelectionEngineFlags::HAS_ANCH;
214             }
215             if ( pFunctionSet->IsSelectionAtPoint( aPos ) )
216             {
217                 pFunctionSet->DeselectAtPoint( aPos );
218                 pFunctionSet->SetCursorAtPoint( aPos, true );
219             }
220             else
221             {
222                 pFunctionSet->SetCursorAtPoint( aPos );
223             }
224             return true;
225 
226         case KEY_SHIFT + KEY_MOD1:
227             if ( eSelMode != SelectionMode::Multiple )
228             {
229                 ReleaseMouse();
230                 nFlags &= ~SelectionEngineFlags::IN_SEL;
231                 return false;
232             }
233             nFlags |= SelectionEngineFlags::IN_ADD; //bIsInAddMode = true;
234             if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
235             {
236                 pFunctionSet->CreateAnchor();
237                 nFlags |= SelectionEngineFlags::HAS_ANCH;
238             }
239             pFunctionSet->SetCursorAtPoint( aPos );
240             return true;
241     }
242 
243     return false;
244 }
245 
SelMouseButtonUp(const MouseEvent & rMEvt)246 bool SelectionEngine::SelMouseButtonUp( const MouseEvent& rMEvt )
247 {
248     aWTimer.Stop();
249     if (!pFunctionSet)
250     {
251         const SelectionEngineFlags nMask = SelectionEngineFlags::CMDEVT | SelectionEngineFlags::WAIT_UPEVT | SelectionEngineFlags::IN_SEL;
252         nFlags &= ~nMask;
253         return false;
254     }
255 
256     if (!rMEvt.IsRight())
257         ReleaseMouse();
258 
259 #if defined IOS || defined ANDROID
260     const bool bDoMessWithSelection = !rMEvt.IsRight();
261 #else
262     constexpr bool bDoMessWithSelection = true;
263 #endif
264 
265     if( (nFlags & SelectionEngineFlags::WAIT_UPEVT) && !(nFlags & SelectionEngineFlags::CMDEVT) &&
266         eSelMode != SelectionMode::Single)
267     {
268         // MouseButtonDown in Sel but no CommandEvent yet
269         // ==> deselect
270         sal_uInt16 nModifier = aLastMove.GetModifier() | nLockedMods;
271         if( nModifier == KEY_MOD1 || IsAlwaysAdding() )
272         {
273             if( !(nModifier & KEY_SHIFT) )
274             {
275                 pFunctionSet->DestroyAnchor();
276                 nFlags &= ~SelectionEngineFlags::HAS_ANCH; // uncheck anchor
277             }
278             pFunctionSet->DeselectAtPoint( aLastMove.GetPosPixel() );
279             nFlags &= ~SelectionEngineFlags::HAS_ANCH; // uncheck anchor
280             if (bDoMessWithSelection)
281                 pFunctionSet->SetCursorAtPoint( aLastMove.GetPosPixel(), true );
282         }
283         else
284         {
285             if (bDoMessWithSelection)
286                 pFunctionSet->DeselectAll();
287             nFlags &= ~SelectionEngineFlags::HAS_ANCH; // uncheck anchor
288             if (bDoMessWithSelection)
289                 pFunctionSet->SetCursorAtPoint( aLastMove.GetPosPixel() );
290         }
291     }
292 
293     const SelectionEngineFlags nMask = SelectionEngineFlags::CMDEVT | SelectionEngineFlags::WAIT_UPEVT | SelectionEngineFlags::IN_SEL;
294     nFlags &= ~nMask;
295     return true;
296 }
297 
ReleaseMouse()298 void SelectionEngine::ReleaseMouse()
299 {
300     if (!pWin || !pWin->IsMouseCaptured())
301         return;
302     pWin->ReleaseMouse();
303 }
304 
CaptureMouse()305 void SelectionEngine::CaptureMouse()
306 {
307     if (!pWin || pWin->IsMouseCaptured())
308         return;
309     pWin->CaptureMouse();
310 }
311 
SelMouseMove(const MouseEvent & rMEvt)312 bool SelectionEngine::SelMouseMove( const MouseEvent& rMEvt )
313 {
314 
315     if ( !pFunctionSet || !(nFlags & SelectionEngineFlags::IN_SEL) ||
316          (nFlags & (SelectionEngineFlags::CMDEVT | SelectionEngineFlags::WAIT_UPEVT)) )
317         return false;
318 
319     if( !(nFlags & SelectionEngineFlags::EXPANDONMOVE) )
320         return false; // wait for DragEvent!
321 
322     aLastMove = rMEvt;
323     // if the mouse is outside the area, the frequency of
324     // SetCursorAtPoint() is only set by the Timer
325     if( aWTimer.IsActive() && !aArea.IsInside( rMEvt.GetPosPixel() ))
326         return true;
327 
328     aWTimer.SetTimeout( nUpdateInterval );
329     if (!comphelper::LibreOfficeKit::isActive())
330         // Generating fake mouse moves does not work with LOK.
331         aWTimer.Start();
332     if ( eSelMode != SelectionMode::Single )
333     {
334         if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
335         {
336             pFunctionSet->CreateAnchor();
337             nFlags |= SelectionEngineFlags::HAS_ANCH;
338         }
339     }
340 
341     pFunctionSet->SetCursorAtPoint( rMEvt.GetPosPixel() );
342 
343     return true;
344 }
345 
SetWindow(vcl::Window * pNewWin)346 void SelectionEngine::SetWindow( vcl::Window* pNewWin )
347 {
348     if( pNewWin != pWin )
349     {
350         if (nFlags & SelectionEngineFlags::IN_SEL)
351             ReleaseMouse();
352         pWin = pNewWin;
353         if (nFlags & SelectionEngineFlags::IN_SEL)
354             CaptureMouse();
355     }
356 }
357 
Reset()358 void SelectionEngine::Reset()
359 {
360     aWTimer.Stop();
361     if (nFlags & SelectionEngineFlags::IN_SEL)
362         ReleaseMouse();
363     nFlags &= ~SelectionEngineFlags(SelectionEngineFlags::HAS_ANCH | SelectionEngineFlags::IN_SEL);
364     nLockedMods = 0;
365 }
366 
Command(const CommandEvent & rCEvt)367 bool SelectionEngine::Command( const CommandEvent& rCEvt )
368 {
369     // Timer aWTimer is active during enlarging a selection
370     if ( !pFunctionSet || aWTimer.IsActive() )
371         return false;
372     aWTimer.Stop();
373     if ( rCEvt.GetCommand() != CommandEventId::StartDrag )
374         return false;
375 
376     nFlags |= SelectionEngineFlags::CMDEVT;
377     if ( nFlags & SelectionEngineFlags::DRG_ENAB )
378     {
379         SAL_WARN_IF( !rCEvt.IsMouseEvent(), "vcl", "STARTDRAG: Not a MouseEvent" );
380         if ( pFunctionSet->IsSelectionAtPoint( rCEvt.GetMousePosPixel() ) )
381         {
382             aLastMove = MouseEvent( rCEvt.GetMousePosPixel(),
383                            aLastMove.GetClicks(), aLastMove.GetMode(),
384                            aLastMove.GetButtons(), aLastMove.GetModifier() );
385             pFunctionSet->BeginDrag();
386             const SelectionEngineFlags nMask = SelectionEngineFlags::CMDEVT|SelectionEngineFlags::WAIT_UPEVT|SelectionEngineFlags::IN_SEL;
387             nFlags &= ~nMask;
388         }
389         else
390             nFlags &= ~SelectionEngineFlags::CMDEVT;
391     }
392     else
393         nFlags &= ~SelectionEngineFlags::CMDEVT;
394     return true;
395 }
396 
SetUpdateInterval(sal_uLong nInterval)397 void SelectionEngine::SetUpdateInterval( sal_uLong nInterval )
398 {
399     if (nInterval < SELENG_AUTOREPEAT_INTERVAL_MIN)
400         // Set a lower threshold.  On Windows, setting this value too low
401         // would cause selection to get updated indefinitely.
402         nInterval = SELENG_AUTOREPEAT_INTERVAL_MIN;
403 
404     if (nUpdateInterval == nInterval)
405         // no update needed.
406         return;
407 
408     if (aWTimer.IsActive())
409     {
410         // reset the timer right away on interval change.
411         aWTimer.Stop();
412         aWTimer.SetTimeout(nInterval);
413         aWTimer.Start();
414     }
415     else
416         aWTimer.SetTimeout(nInterval);
417 
418     nUpdateInterval = nInterval;
419 }
420 
421 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
422