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