Coverage Report

Created: 2026-05-16 09:25

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