Coverage Report

Created: 2026-04-09 11:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sc/source/ui/view/select.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 <tools/urlobj.hxx>
21
#include <sfx2/docfile.hxx>
22
#include <osl/diagnose.h>
23
24
#include <select.hxx>
25
#include <tabvwsh.hxx>
26
#include <scmod.hxx>
27
#include <document.hxx>
28
#include <transobj.hxx>
29
#include <docsh.hxx>
30
#include <tabprotection.hxx>
31
#include <markdata.hxx>
32
#include <gridwin.hxx>
33
#include <sfx2/lokhelper.hxx>
34
#include <comphelper/lok.hxx>
35
36
#if defined(_WIN32)
37
#define SC_SELENG_REFMODE_UPDATE_INTERVAL_MIN 65
38
#endif
39
40
using namespace com::sun::star;
41
42
static Point aSwitchPos;                //! Member
43
static bool bDidSwitch = false;
44
45
// View (Gridwin / keyboard)
46
ScViewFunctionSet::ScViewFunctionSet( ScViewData& rViewData ) :
47
0
        m_rViewData( rViewData ),
48
0
        m_pEngine( nullptr ),
49
0
        m_bAnchor( false ),
50
0
        m_bStarted( false )
51
0
{
52
0
}
53
54
ScSplitPos ScViewFunctionSet::GetWhich() const
55
0
{
56
0
    if (m_pEngine)
57
0
        return m_pEngine->GetWhich();
58
0
    else
59
0
        return m_rViewData.GetActivePart();
60
0
}
61
62
sal_uInt64 ScViewFunctionSet::CalcUpdateInterval( const Size& rWinSize, const Point& rEffPos,
63
                                             bool bLeftScroll, bool bTopScroll, bool bRightScroll, bool bBottomScroll )
64
0
{
65
0
    sal_uInt64 nUpdateInterval = SELENG_AUTOREPEAT_INTERVAL_MAX;
66
0
    vcl::Window* pWin = m_pEngine->GetWindow();
67
0
    AbsoluteScreenPixelRectangle aScrRect = pWin->GetDesktopRectPixel();
68
0
    AbsoluteScreenPixelPoint aRootPos = pWin->OutputToAbsoluteScreenPixel(Point(0,0));
69
0
    if (bRightScroll)
70
0
    {
71
0
        double nWinRight = rWinSize.getWidth() + aRootPos.getX();
72
0
        double nMarginRight = aScrRect.GetWidth() - nWinRight;
73
0
        double nHOffset = rEffPos.X() - rWinSize.Width();
74
0
        double nHAccelRate = nHOffset / nMarginRight;
75
76
0
        if (nHAccelRate > 1.0)
77
0
            nHAccelRate = 1.0;
78
79
0
        nUpdateInterval = SELENG_AUTOREPEAT_INTERVAL_MAX*(1.0 - nHAccelRate);
80
0
    }
81
82
0
    if (bLeftScroll)
83
0
    {
84
0
        double nMarginLeft = aRootPos.getX();
85
0
        double nHOffset = -rEffPos.X();
86
0
        double nHAccelRate = nHOffset / nMarginLeft;
87
88
0
        if (nHAccelRate > 1.0)
89
0
            nHAccelRate = 1.0;
90
91
0
        sal_uLong nTmp = static_cast<sal_uLong>(SELENG_AUTOREPEAT_INTERVAL_MAX*(1.0 - nHAccelRate));
92
0
        if (nUpdateInterval > nTmp)
93
0
            nUpdateInterval = nTmp;
94
0
    }
95
96
0
    if (bBottomScroll)
97
0
    {
98
0
        double nWinBottom = rWinSize.getHeight() + aRootPos.getY();
99
0
        double nMarginBottom = aScrRect.GetHeight() - nWinBottom;
100
0
        double nVOffset = rEffPos.Y() - rWinSize.Height();
101
0
        double nVAccelRate = nVOffset / nMarginBottom;
102
103
0
        if (nVAccelRate > 1.0)
104
0
            nVAccelRate = 1.0;
105
106
0
        sal_uInt64 nTmp = SELENG_AUTOREPEAT_INTERVAL_MAX*(1.0 - nVAccelRate);
107
0
        if (nUpdateInterval > nTmp)
108
0
            nUpdateInterval = nTmp;
109
0
    }
110
111
0
    if (bTopScroll)
112
0
    {
113
0
        double nMarginTop = aRootPos.getY();
114
0
        double nVOffset = -rEffPos.Y();
115
0
        double nVAccelRate = nVOffset / nMarginTop;
116
117
0
        if (nVAccelRate > 1.0)
118
0
            nVAccelRate = 1.0;
119
120
0
        sal_uInt64 nTmp = SELENG_AUTOREPEAT_INTERVAL_MAX*(1.0 - nVAccelRate);
121
0
        if (nUpdateInterval > nTmp)
122
0
            nUpdateInterval = nTmp;
123
0
    }
124
125
#ifdef _WIN32
126
    ScTabViewShell* pViewShell = m_rViewData.GetViewShell();
127
    bool bRefMode = pViewShell && pViewShell->IsRefInputMode();
128
    if (bRefMode && nUpdateInterval < SC_SELENG_REFMODE_UPDATE_INTERVAL_MIN)
129
        // Lower the update interval during ref mode, because re-draw can be
130
        // expensive on Windows.  Making this interval too small would queue up
131
        // the scroll/paint requests which would cause semi-infinite
132
        // scrolls even after the mouse cursor is released.  We don't have
133
        // this problem on Linux.
134
        nUpdateInterval = SC_SELENG_REFMODE_UPDATE_INTERVAL_MIN;
135
#endif
136
0
    return nUpdateInterval;
137
0
}
138
139
void ScViewFunctionSet::SetSelectionEngine( ScViewSelectionEngine* pSelEngine )
140
0
{
141
0
    m_pEngine = pSelEngine;
142
0
}
143
144
// Drag & Drop
145
void ScViewFunctionSet::BeginDrag()
146
0
{
147
0
    if (m_rViewData.GetViewShell()->IsLokReadOnlyView())
148
0
        return;
149
150
0
    SCTAB nTab = m_rViewData.CurrentTabForData();
151
152
0
    SCCOL nPosX;
153
0
    SCROW nPosY;
154
0
    if (m_pEngine)
155
0
    {
156
0
        Point aMPos = m_pEngine->GetMousePosPixel();
157
0
        m_rViewData.GetPosFromPixel( aMPos.X(), aMPos.Y(), GetWhich(), nPosX, nPosY );
158
0
    }
159
0
    else
160
0
    {
161
0
        nPosX = m_rViewData.GetCurX();
162
0
        nPosY = m_rViewData.GetCurY();
163
0
    }
164
165
0
    ScModule* pScMod = ScModule::get();
166
0
    bool bRefMode = pScMod->IsFormulaMode();
167
0
    if (bRefMode)
168
0
        return;
169
170
0
    m_rViewData.GetView()->FakeButtonUp( GetWhich() );   // ButtonUp is swallowed
171
172
0
    ScMarkData& rMark = m_rViewData.GetMarkData();
173
0
    rMark.MarkToSimple();
174
0
    if ( !rMark.IsMarked() || rMark.IsMultiMarked() )
175
0
        return;
176
177
0
    ScDocumentUniquePtr pClipDoc(new ScDocument( SCDOCMODE_CLIP ));
178
    // bApi = TRUE -> no error messages
179
0
    bool bCopied = m_rViewData.GetView()->CopyToClip( pClipDoc.get(), false, true );
180
0
    if ( !bCopied )
181
0
        return;
182
183
0
    sal_Int8 nDragActions = m_rViewData.GetView()->SelectionEditable() ?
184
0
                            ( DND_ACTION_COPYMOVE | DND_ACTION_LINK ) :
185
0
                            ( DND_ACTION_COPY | DND_ACTION_LINK );
186
187
0
    ScDocShell* pDocSh = m_rViewData.GetDocShell();
188
0
    TransferableObjectDescriptor aObjDesc;
189
0
    pDocSh->FillTransferableObjectDescriptor( aObjDesc );
190
0
    aObjDesc.maDisplayName = pDocSh->GetMedium()->GetURLObject().GetURLNoPass();
191
    // maSize is set in ScTransferObj ctor
192
193
0
    rtl::Reference<ScTransferObj> pTransferObj = new ScTransferObj( std::move(pClipDoc), std::move(aObjDesc) );
194
195
    // set position of dragged cell within range
196
0
    ScRange aMarkRange = pTransferObj->GetRange();
197
0
    SCCOL nStartX = aMarkRange.aStart.Col();
198
0
    SCROW nStartY = aMarkRange.aStart.Row();
199
0
    SCCOL nHandleX = (nPosX >= nStartX) ? nPosX - nStartX : 0;
200
0
    SCROW nHandleY = (nPosY >= nStartY) ? nPosY - nStartY : 0;
201
0
    pTransferObj->SetDragHandlePos( nHandleX, nHandleY );
202
0
    pTransferObj->SetSourceCursorPos( m_rViewData.GetCurX(), m_rViewData.GetCurY() );
203
0
    pTransferObj->SetVisibleTab( nTab );
204
205
0
    pTransferObj->SetDragSource( pDocSh, rMark );
206
207
0
    vcl::Window* pWindow = m_rViewData.GetActiveWin();
208
0
    if ( pWindow->IsTracking() )
209
0
        pWindow->EndTracking( TrackingEventFlags::Cancel );    // abort selecting
210
211
0
    if (comphelper::LibreOfficeKit::isActive())
212
0
        pWindow->LocalStartDrag();
213
214
0
    pScMod->SetDragObject( pTransferObj.get(), nullptr );      // for internal D&D
215
0
    pTransferObj->StartDrag( pWindow, nDragActions );
216
217
0
    return;         // dragging started
218
219
0
}
220
221
// Selection
222
void ScViewFunctionSet::CreateAnchor()
223
0
{
224
0
    if (m_bAnchor) return;
225
226
0
    bool bRefMode = ScModule::get()->IsFormulaMode();
227
0
    if (bRefMode)
228
0
        SetAnchor( m_rViewData.GetRefStartX(), m_rViewData.GetRefStartY() );
229
0
    else
230
0
        SetAnchor( m_rViewData.GetCurX(), m_rViewData.GetCurY() );
231
0
}
232
233
void ScViewFunctionSet::SetAnchor( SCCOL nPosX, SCROW nPosY )
234
0
{
235
0
    bool bRefMode = ScModule::get()->IsFormulaMode();
236
0
    ScTabView* pView = m_rViewData.GetView();
237
0
    SCTAB nTab = m_rViewData.CurrentTabForData();
238
239
0
    if (bRefMode)
240
0
    {
241
0
        pView->DoneRefMode();
242
0
        m_aAnchorPos.Set( nPosX, nPosY, nTab );
243
0
        pView->InitRefMode( m_aAnchorPos.Col(), m_aAnchorPos.Row(), m_aAnchorPos.Tab(),
244
0
                            SC_REFTYPE_REF );
245
0
        m_bStarted = true;
246
0
    }
247
0
    else if (m_rViewData.IsAnyFillMode())
248
0
    {
249
0
        m_aAnchorPos.Set( nPosX, nPosY, nTab );
250
0
        m_bStarted = true;
251
0
    }
252
0
    else
253
0
    {
254
        // don't go there and back again
255
0
        if ( m_bStarted && pView->IsMarking( nPosX, nPosY, nTab ) )
256
0
        {
257
            // don't do anything
258
0
        }
259
0
        else
260
0
        {
261
0
            pView->DoneBlockMode( true );
262
0
            m_aAnchorPos.Set( nPosX, nPosY, nTab );
263
0
            ScMarkData& rMark = m_rViewData.GetMarkData();
264
0
            if ( rMark.IsMarked() || rMark.IsMultiMarked() )
265
0
            {
266
0
                pView->InitBlockMode( m_aAnchorPos.Col(), m_aAnchorPos.Row(),
267
0
                                      m_aAnchorPos.Tab(), true );
268
0
                m_bStarted = true;
269
0
            }
270
0
            else
271
0
                m_bStarted = false;
272
0
        }
273
0
    }
274
0
    m_bAnchor = true;
275
0
}
276
277
void ScViewFunctionSet::DestroyAnchor()
278
0
{
279
0
    if (m_rViewData.IsAnyFillMode())
280
0
        return;
281
282
0
    bool bRefMode = ScModule::get()->IsFormulaMode();
283
0
    if (bRefMode)
284
0
        m_rViewData.GetView()->DoneRefMode( true );
285
0
    else
286
0
        m_rViewData.GetView()->DoneBlockMode( true );
287
288
0
    m_bAnchor = false;
289
0
}
290
291
void ScViewFunctionSet::SetAnchorFlag( bool bSet )
292
0
{
293
0
    m_bAnchor = bSet;
294
0
}
295
296
void ScViewFunctionSet::SetCursorAtPoint( const Point& rPointPixel, bool /* bDontSelectAtCursor */ )
297
0
{
298
0
    if ( bDidSwitch )
299
0
    {
300
0
        if ( rPointPixel == aSwitchPos )
301
0
            return;                   // don't scroll in wrong window
302
0
        else
303
0
            bDidSwitch = false;
304
0
    }
305
0
    aSwitchPos = rPointPixel;       // only important, if bDidSwitch
306
307
    //  treat position 0 as -1, so scrolling is always possible
308
    //  (with full screen and hidden headers, the top left border may be at 0)
309
    //  (moved from ScViewData::GetPosFromPixel)
310
311
0
    Point aEffPos = rPointPixel;
312
0
    if ( aEffPos.X() == 0 )
313
0
        aEffPos.setX( -1 );
314
0
    if ( aEffPos.Y() == 0 )
315
0
        aEffPos.setY( -1 );
316
317
    //  Scrolling
318
0
    Size aWinSize = m_pEngine->GetWindow()->GetOutputSizePixel();
319
0
    bool bLeftScroll  = ( aEffPos.X() < 0 );
320
0
    bool bTopScroll = ( aEffPos.Y() < 0 );
321
322
0
    SCCOL  nPosX;
323
0
    SCROW  nPosY;
324
0
    m_rViewData.GetPosFromPixel( aEffPos.X(), aEffPos.Y(), GetWhich(),
325
0
                                  nPosX, nPosY, true, true );     // with Repair
326
327
0
    tools::Rectangle aEditArea = m_rViewData.GetEditArea(GetWhich(), nPosX, nPosY,
328
0
                                                          m_pEngine->GetWindow(),
329
0
                                                          nullptr, false);
330
331
0
    bool bFillingSelection = m_rViewData.IsFillMode() || m_rViewData.GetFillMode() == ScFillMode::MATRIX;
332
0
    bool bBottomScroll;
333
0
    bool bRightScroll;
334
    // for Autofill don't yet assume we want to auto-scroll to the cell under the mouse
335
    // because the autofill handle extends into a cells neighbours so initial click is usually
336
    // above a neighbour cell
337
0
    if (bFillingSelection)
338
0
    {
339
0
        bBottomScroll = aEffPos.Y() >= aWinSize.Height();
340
0
        bRightScroll  = aEffPos.X() >= aWinSize.Width();
341
0
    }
342
0
    else
343
0
    {
344
        //in the normal case make the full selected cell visible
345
0
        bBottomScroll = aEditArea.Bottom() >= aWinSize.Height();
346
0
        bRightScroll  = aEditArea.Right() >= aWinSize.Width();
347
0
    }
348
349
0
    bool bScroll = bRightScroll || bBottomScroll || bLeftScroll || bTopScroll;
350
351
    // for Autofill switch in the center of cell thereby don't prevent scrolling to bottom/right
352
0
    if (bFillingSelection)
353
0
    {
354
0
        bool bLeft, bTop;
355
0
        m_rViewData.GetMouseQuadrant( aEffPos, GetWhich(), nPosX, nPosY, bLeft, bTop );
356
0
        ScDocument& rDoc = m_rViewData.GetDocument();
357
0
        SCTAB nTab = m_rViewData.CurrentTabForData();
358
0
        if ( bLeft && !bRightScroll )
359
0
            do --nPosX; while ( nPosX>=0 && rDoc.ColHidden( nPosX, nTab ) );
360
0
        if ( bTop && !bBottomScroll )
361
0
        {
362
0
            if (--nPosY >= 0)
363
0
            {
364
0
                nPosY = rDoc.LastVisibleRow(0, nPosY, nTab);
365
0
                if (!rDoc.ValidRow(nPosY))
366
0
                    nPosY = -1;
367
0
            }
368
0
        }
369
        // negative value is allowed
370
0
    }
371
372
    // moved out of fix limit?
373
0
    ScSplitPos eWhich = GetWhich();
374
0
    if ( eWhich == m_rViewData.GetActivePart() )
375
0
    {
376
0
        if ( m_rViewData.GetHSplitMode() == SC_SPLIT_FIX )
377
0
            if ( aEffPos.X() >= aWinSize.Width() )
378
0
            {
379
0
                if ( eWhich == SC_SPLIT_TOPLEFT )
380
0
                {
381
0
                    m_rViewData.GetView()->ActivatePart( SC_SPLIT_TOPRIGHT );
382
0
                    bScroll = false;
383
0
                    bDidSwitch = true;
384
0
                }
385
0
                else if ( eWhich == SC_SPLIT_BOTTOMLEFT )
386
0
                {
387
0
                    m_rViewData.GetView()->ActivatePart( SC_SPLIT_BOTTOMRIGHT );
388
0
                    bScroll = false;
389
0
                    bDidSwitch = true;
390
0
                }
391
0
            }
392
393
0
        if ( m_rViewData.GetVSplitMode() == SC_SPLIT_FIX )
394
0
            if ( aEffPos.Y() >= aWinSize.Height() )
395
0
            {
396
0
                if ( eWhich == SC_SPLIT_TOPLEFT )
397
0
                {
398
0
                    m_rViewData.GetView()->ActivatePart( SC_SPLIT_BOTTOMLEFT );
399
0
                    bScroll = false;
400
0
                    bDidSwitch = true;
401
0
                }
402
0
                else if ( eWhich == SC_SPLIT_TOPRIGHT )
403
0
                {
404
0
                    m_rViewData.GetView()->ActivatePart( SC_SPLIT_BOTTOMRIGHT );
405
0
                    bScroll = false;
406
0
                    bDidSwitch = true;
407
0
                }
408
0
            }
409
0
    }
410
411
0
    if (bScroll)
412
0
    {
413
        // Adjust update interval based on how far the mouse pointer is from the edge.
414
0
        sal_uInt64 nUpdateInterval = CalcUpdateInterval(
415
0
            aWinSize, aEffPos, bLeftScroll, bTopScroll, bRightScroll, bBottomScroll);
416
0
        m_pEngine->SetUpdateInterval(nUpdateInterval);
417
0
    }
418
0
    else
419
0
    {
420
        // Don't forget to reset the interval when not scrolling!
421
0
        m_pEngine->SetUpdateInterval(SELENG_AUTOREPEAT_INTERVAL);
422
0
    }
423
424
0
    m_rViewData.ResetOldCursor();
425
0
    SetCursorAtCell( nPosX, nPosY, bScroll );
426
0
}
427
428
bool ScViewFunctionSet::CheckRefBounds(SCCOL nPosX, SCROW nPosY)
429
0
{
430
0
    SCCOL startX = m_rViewData.GetRefStartX();
431
0
    SCROW startY = m_rViewData.GetRefStartY();
432
433
0
    SCCOL endX = m_rViewData.GetRefEndX();
434
0
    SCROW endY = m_rViewData.GetRefEndY();
435
436
0
    return nPosX >= startX && nPosX <= endX && nPosY >= startY && nPosY <= endY;
437
0
}
438
439
bool ScViewFunctionSet::SetCursorAtCell( SCCOL nPosX, SCROW nPosY, bool bScroll )
440
0
{
441
0
    ScTabView* pView = m_rViewData.GetView();
442
0
    SCTAB nTab = m_rViewData.CurrentTabForData();
443
0
    ScDocument& rDoc = m_rViewData.GetDocument();
444
445
0
    if ( rDoc.IsTabProtected(nTab) )
446
0
    {
447
0
        if (nPosX < 0 || nPosY < 0)
448
0
            return false;
449
450
0
        const ScTableProtection* pProtect = rDoc.GetTabProtection(nTab);
451
0
        if (!pProtect)
452
0
            return false;
453
454
0
        bool bSkipProtected   = !pProtect->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS);
455
0
        bool bSkipUnprotected = !pProtect->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS);
456
457
0
        if ( bSkipProtected && bSkipUnprotected )
458
0
            return false;
459
460
0
        bool bCellProtected = rDoc.HasAttrib(nPosX, nPosY, nTab, nPosX, nPosY, nTab, HasAttrFlags::Protected);
461
0
        if ( (bCellProtected && bSkipProtected) || (!bCellProtected && bSkipUnprotected) )
462
            // Don't select this cell!
463
0
            return false;
464
0
    }
465
466
0
    ScTabViewShell* pViewShell = m_rViewData.GetViewShell();
467
0
    bool bRefMode = pViewShell && pViewShell->IsRefInputMode();
468
469
0
    bool bHide = !bRefMode && !m_rViewData.IsAnyFillMode() &&
470
0
            ( nPosX != m_rViewData.GetCurX() || nPosY != m_rViewData.GetCurY() );
471
472
0
    if (bHide)
473
0
        pView->HideAllCursors();
474
475
0
    if (bScroll)
476
0
    {
477
0
        if (bRefMode)
478
0
        {
479
0
            ScSplitPos eWhich = GetWhich();
480
0
            pView->AlignToCursor( nPosX, nPosY, SC_FOLLOW_LINE, &eWhich );
481
0
        }
482
0
        else
483
0
            pView->AlignToCursor( nPosX, nPosY, SC_FOLLOW_LINE );
484
0
    }
485
486
0
    if (bRefMode)
487
0
    {
488
        // if no input is possible from this doc, don't move the reference cursor around
489
0
        if ( !ScModule::get()->IsModalMode(m_rViewData.GetSfxDocShell()) && (!CheckRefBounds(nPosX, nPosY) || SfxLokHelper::getDeviceFormFactor() != LOKDeviceFormFactor::MOBILE))
490
0
        {
491
0
            if (!m_bAnchor)
492
0
            {
493
0
                pView->DoneRefMode( true );
494
0
                pView->InitRefMode( nPosX, nPosY, m_rViewData.CurrentTabForData(), SC_REFTYPE_REF );
495
0
            }
496
497
0
            if(SfxLokHelper::getDeviceFormFactor() != LOKDeviceFormFactor::MOBILE)
498
0
                pView->UpdateRef( nPosX, nPosY, m_rViewData.CurrentTabForData() );
499
500
0
            pView->SelectionChanged();
501
0
        }
502
0
    }
503
0
    else if (m_rViewData.IsFillMode() ||
504
0
            (m_rViewData.GetFillMode() == ScFillMode::MATRIX && (nScFillModeMouseModifier & KEY_MOD1) ))
505
0
    {
506
        // If a matrix got touched, switch back to Autofill is possible with Ctrl
507
508
0
        SCCOL nStartX, nEndX;
509
0
        SCROW nStartY, nEndY; // Block
510
0
        SCTAB nDummy;
511
0
        m_rViewData.GetSimpleArea( nStartX, nStartY, nDummy, nEndX, nEndY, nDummy );
512
513
0
        if (m_rViewData.GetRefType() != SC_REFTYPE_FILL)
514
0
        {
515
0
            pView->InitRefMode( nStartX, nStartY, nTab, SC_REFTYPE_FILL );
516
0
            CreateAnchor();
517
0
        }
518
519
0
        ScRange aDelRange;
520
0
        bool bOldDelMark = m_rViewData.GetDelMark( aDelRange );
521
522
0
        if ( nPosX+1 >= nStartX && nPosX <= nEndX &&
523
0
             nPosY+1 >= nStartY && nPosY <= nEndY &&
524
0
             ( nPosX != nEndX || nPosY != nEndY ) )                     // minimize?
525
0
        {
526
            // direction (left or top)
527
528
0
            tools::Long nSizeX = 0;
529
0
            for (SCCOL i=nPosX+1; i<=nEndX; i++)
530
0
                nSizeX += rDoc.GetColWidth( i, nTab );
531
0
            tools::Long nSizeY = rDoc.GetRowHeight( nPosY+1, nEndY, nTab );
532
533
0
            SCCOL nDelStartX = nStartX;
534
0
            SCROW nDelStartY = nStartY;
535
0
            if ( nSizeX > nSizeY )
536
0
                nDelStartX = nPosX + 1;
537
0
            else
538
0
                nDelStartY = nPosY + 1;
539
            // there is no need to check for zero, because nPosX/Y is also negative
540
541
0
            if ( nDelStartX < nStartX )
542
0
                nDelStartX = nStartX;
543
0
            if ( nDelStartY < nStartY )
544
0
                nDelStartY = nStartY;
545
546
            // set range
547
548
0
            m_rViewData.SetDelMark( ScRange( nDelStartX,nDelStartY,nTab,
549
0
                                              nEndX,nEndY,nTab ) );
550
0
            m_rViewData.GetView()->UpdateShrinkOverlay();
551
552
0
            m_rViewData.GetView()->
553
0
                PaintArea( nStartX,nDelStartY, nEndX,nEndY, ScUpdateMode::Marks );
554
555
0
            nPosX = nEndX;      // keep red border around range
556
0
            nPosY = nEndY;
557
558
            // reference the right way up, if it's upside down below
559
0
            if ( nStartX != m_rViewData.GetRefStartX() || nStartY != m_rViewData.GetRefStartY() )
560
0
            {
561
0
                m_rViewData.GetView()->DoneRefMode();
562
0
                m_rViewData.GetView()->InitRefMode( nStartX, nStartY, nTab, SC_REFTYPE_FILL );
563
0
            }
564
0
        }
565
0
        else
566
0
        {
567
0
            if ( bOldDelMark )
568
0
            {
569
0
                m_rViewData.ResetDelMark();
570
0
                m_rViewData.GetView()->UpdateShrinkOverlay();
571
0
            }
572
573
0
            bool bNegX = ( nPosX < nStartX );
574
0
            bool bNegY = ( nPosY < nStartY );
575
576
0
            tools::Long nSizeX = 0;
577
0
            if ( bNegX )
578
0
            {
579
                //  in SetCursorAtPoint hidden columns are skipped.
580
                //  They must be skipped here too, or the result will always be the first hidden column.
581
0
                do ++nPosX; while ( nPosX<nStartX && rDoc.ColHidden(nPosX, nTab) );
582
0
                for (SCCOL i=nPosX; i<nStartX; i++)
583
0
                    nSizeX += rDoc.GetColWidth( i, nTab );
584
0
            }
585
0
            else
586
0
                for (SCCOL i=nEndX+1; i<=nPosX; i++)
587
0
                    nSizeX += rDoc.GetColWidth( i, nTab );
588
589
0
            tools::Long nSizeY = 0;
590
0
            if ( bNegY )
591
0
            {
592
                //  in SetCursorAtPoint hidden rows are skipped.
593
                //  They must be skipped here too, or the result will always be the first hidden row.
594
0
                if (++nPosY < nStartY)
595
0
                {
596
0
                    nPosY = rDoc.FirstVisibleRow(nPosY, nStartY-1, nTab);
597
0
                    if (!rDoc.ValidRow(nPosY))
598
0
                        nPosY = nStartY;
599
0
                }
600
0
                nSizeY += rDoc.GetRowHeight( nPosY, nStartY-1, nTab );
601
0
            }
602
0
            else
603
0
                nSizeY += rDoc.GetRowHeight( nEndY+1, nPosY, nTab );
604
605
0
            if ( nSizeX > nSizeY )          // Fill only ever in one direction
606
0
            {
607
0
                nPosY = nEndY;
608
0
                bNegY = false;
609
0
            }
610
0
            else
611
0
            {
612
0
                nPosX = nEndX;
613
0
                bNegX = false;
614
0
            }
615
616
0
            SCCOL nRefStX = bNegX ? nEndX : nStartX;
617
0
            SCROW nRefStY = bNegY ? nEndY : nStartY;
618
0
            if ( nRefStX != m_rViewData.GetRefStartX() || nRefStY != m_rViewData.GetRefStartY() )
619
0
            {
620
0
                m_rViewData.GetView()->DoneRefMode();
621
0
                m_rViewData.GetView()->InitRefMode( nRefStX, nRefStY, nTab, SC_REFTYPE_FILL );
622
0
            }
623
0
        }
624
625
0
        pView->UpdateRef( nPosX, nPosY, nTab );
626
0
    }
627
0
    else if (m_rViewData.IsDBExpandMode())
628
0
    {
629
0
        SCCOL nStartX, nEndX;
630
0
        SCROW nStartY, nEndY;
631
0
        m_rViewData.GetFillData(nStartX, nStartY, nEndX, nEndY);
632
633
0
        if (m_rViewData.GetRefType() != SC_REFTYPE_FILL)
634
0
        {
635
0
            pView->InitRefMode( nStartX, nStartY, nTab, SC_REFTYPE_FILL );
636
0
            CreateAnchor();
637
0
        }
638
639
0
        if ( nPosX >= nStartX && nPosX <= nEndX &&
640
0
             nPosY >= nStartY && nPosY <= nEndY &&
641
0
             ( nPosX != nEndX || nPosY != nEndY ) )
642
0
        {
643
            // inside
644
0
            tools::Long nSizeX = 0;
645
0
            for (SCCOL i = nPosX + 1; i <= nEndX; i++)
646
0
                nSizeX += rDoc.GetColWidth(i, nTab);
647
0
            tools::Long nSizeY = rDoc.GetRowHeight(nPosY + 1, nEndY, nTab);
648
649
0
            if (nSizeX > nSizeY)
650
0
            {
651
0
                nPosY = nEndY;
652
0
            }
653
0
            else
654
0
            {
655
0
                nPosX = nEndX;
656
0
            }
657
658
            // Header row or first row
659
0
            if (nPosY == nStartY)
660
0
                nPosY++;
661
662
0
            if ( nStartX != m_rViewData.GetRefStartX() || nStartY != m_rViewData.GetRefStartY() )
663
0
            {
664
0
                m_rViewData.GetView()->DoneRefMode();
665
0
                m_rViewData.GetView()->InitRefMode( nStartX, nStartY, nTab, SC_REFTYPE_FILL );
666
0
            }
667
0
        }
668
0
        else
669
0
        {
670
            // outside
671
0
            bool bNegX = ( nPosX < nStartX );
672
0
            bool bNegY = ( nPosY < nStartY );
673
674
0
            tools::Long nSizeX = 0;
675
0
            if ( bNegX )
676
0
            {
677
                //  in SetCursorAtPoint hidden columns are skipped.
678
                //  They must be skipped here too, or the result will always be the first hidden column.
679
0
                do ++nPosX; while ( nPosX<nStartX && rDoc.ColHidden(nPosX, nTab) );
680
0
                for (SCCOL i=nPosX; i<nStartX; i++)
681
0
                    nSizeX += rDoc.GetColWidth( i, nTab );
682
0
            }
683
0
            else
684
0
                for (SCCOL i=nEndX+1; i<=nPosX; i++)
685
0
                    nSizeX += rDoc.GetColWidth( i, nTab );
686
687
0
            tools::Long nSizeY = 0;
688
0
            if ( bNegY )
689
0
            {
690
                //  in SetCursorAtPoint hidden rows are skipped.
691
                //  They must be skipped here too, or the result will always be the first hidden row.
692
0
                if (++nPosY < nStartY)
693
0
                {
694
0
                    nPosY = rDoc.FirstVisibleRow(nPosY, nStartY-1, nTab);
695
0
                    if (!rDoc.ValidRow(nPosY))
696
0
                        nPosY = nStartY;
697
0
                }
698
0
                nSizeY += rDoc.GetRowHeight( nPosY, nStartY-1, nTab );
699
0
            }
700
0
            else
701
0
                nSizeY += rDoc.GetRowHeight( nEndY+1, nPosY, nTab );
702
703
0
            if ( nSizeX > nSizeY ) // Fill only ever in one direction
704
0
            {
705
0
                nPosY = nEndY;
706
0
                bNegY = false;
707
0
            }
708
0
            else
709
0
            {
710
0
                nPosX = nEndX;
711
0
                bNegX = false;
712
0
            }
713
714
0
            nPosX = bNegX ? nStartX : nPosX;
715
0
            nPosY = bNegY ? nStartY + 1 : nPosY; // Header row or first row
716
0
            if ( nStartX != m_rViewData.GetRefStartX() || nStartY != m_rViewData.GetRefStartY() )
717
0
            {
718
0
                m_rViewData.GetView()->DoneRefMode();
719
0
                m_rViewData.GetView()->InitRefMode( nStartX, nStartY, nTab, SC_REFTYPE_FILL );
720
0
            }
721
0
        }
722
723
0
        pView->UpdateRef( nPosX, nPosY, nTab );
724
0
    }
725
0
    else if (m_rViewData.IsAnyFillMode())
726
0
    {
727
0
        ScFillMode nMode = m_rViewData.GetFillMode();
728
0
        if ( nMode == ScFillMode::EMBED_LT || nMode == ScFillMode::EMBED_RB )
729
0
        {
730
0
            OSL_ENSURE( rDoc.IsEmbedded(), "!rDoc.IsEmbedded()" );
731
0
            ScRange aRange;
732
0
            rDoc.GetEmbedded( aRange);
733
0
            ScRefType eRefMode = (nMode == ScFillMode::EMBED_LT) ? SC_REFTYPE_EMBED_LT : SC_REFTYPE_EMBED_RB;
734
0
            if (m_rViewData.GetRefType() != eRefMode)
735
0
            {
736
0
                if ( nMode == ScFillMode::EMBED_LT )
737
0
                    pView->InitRefMode( aRange.aEnd.Col(), aRange.aEnd.Row(), nTab, eRefMode );
738
0
                else
739
0
                    pView->InitRefMode( aRange.aStart.Col(), aRange.aStart.Row(), nTab, eRefMode );
740
0
                CreateAnchor();
741
0
            }
742
743
0
            pView->UpdateRef( nPosX, nPosY, nTab );
744
0
        }
745
0
        else if ( nMode == ScFillMode::MATRIX )
746
0
        {
747
0
            SCCOL nStartX, nEndX;
748
0
            SCROW nStartY, nEndY; // Block
749
0
            SCTAB nDummy;
750
0
            m_rViewData.GetSimpleArea( nStartX, nStartY, nDummy, nEndX, nEndY, nDummy );
751
752
0
            if (m_rViewData.GetRefType() != SC_REFTYPE_FILL)
753
0
            {
754
0
                pView->InitRefMode( nStartX, nStartY, nTab, SC_REFTYPE_FILL );
755
0
                CreateAnchor();
756
0
            }
757
758
0
            if ( nPosX < nStartX ) nPosX = nStartX;
759
0
            if ( nPosY < nStartY ) nPosY = nStartY;
760
761
0
            pView->UpdateRef( nPosX, nPosY, nTab );
762
0
        }
763
        // else new modes
764
0
    }
765
0
    else                    // regular selection
766
0
    {
767
0
        bool bHideCur = m_bAnchor && ( nPosX != m_rViewData.GetCurX() ||
768
0
                                       nPosY != m_rViewData.GetCurY() );
769
0
        if (bHideCur)
770
0
            pView->HideAllCursors();            // otherwise twice: Block and SetCursor
771
772
0
        if (m_bAnchor)
773
0
        {
774
0
            if (!m_bStarted)
775
0
            {
776
0
                bool bMove = ( nPosX != m_aAnchorPos.Col() ||
777
0
                                nPosY != m_aAnchorPos.Row() );
778
0
                if ( bMove || ( m_pEngine && m_pEngine->GetMouseEvent().IsShift() ) )
779
0
                {
780
0
                    pView->InitBlockMode( m_aAnchorPos.Col(), m_aAnchorPos.Row(),
781
0
                                          m_aAnchorPos.Tab(), true );
782
0
                    m_bStarted = true;
783
0
                }
784
0
            }
785
0
            if (m_bStarted)
786
                // If the selection is already started, don't set the cursor.
787
0
                pView->MarkCursor( nPosX, nPosY, nTab, false, false, true );
788
0
            else
789
0
                pView->SetCursor( nPosX, nPosY );
790
0
        }
791
0
        else
792
0
        {
793
0
            ScMarkData& rMark = m_rViewData.GetMarkData();
794
0
            if (rMark.IsMarked() || rMark.IsMultiMarked())
795
0
            {
796
0
                pView->DoneBlockMode(true);
797
0
                pView->InitBlockMode( nPosX, nPosY, nTab, true );
798
0
                pView->MarkCursor( nPosX, nPosY, nTab );
799
800
0
                m_aAnchorPos.Set( nPosX, nPosY, nTab );
801
0
                m_bStarted = true;
802
0
            }
803
            // #i3875# *Hack* When a new cell is Ctrl-clicked with no pre-selected cells,
804
            // it highlights that new cell as well as the old cell where the cursor is
805
            // positioned prior to the click.  A selection mode via Shift-F8 should also
806
            // follow the same behavior.
807
0
            else if ( m_rViewData.IsSelCtrlMouseClick() )
808
0
            {
809
0
                SCCOL nOldX = m_rViewData.GetCurX();
810
0
                SCROW nOldY = m_rViewData.GetCurY();
811
812
0
                pView->InitBlockMode( nOldX, nOldY, nTab, true );
813
0
                pView->MarkCursor( nOldX, nOldY, nTab );
814
815
0
                if ( nOldX != nPosX || nOldY != nPosY )
816
0
                {
817
0
                    pView->DoneBlockMode( true );
818
0
                    pView->InitBlockMode( nPosX, nPosY, nTab, true );
819
0
                    pView->MarkCursor( nPosX, nPosY, nTab );
820
0
                    m_aAnchorPos.Set( nPosX, nPosY, nTab );
821
0
                }
822
823
0
                m_bStarted = true;
824
0
            }
825
0
            pView->SetCursor( nPosX, nPosY );
826
0
        }
827
828
0
        m_rViewData.SetRefStart( nPosX, nPosY, nTab );
829
0
        if (bHideCur)
830
0
            pView->ShowAllCursors();
831
0
    }
832
833
0
    if (bHide)
834
0
        pView->ShowAllCursors();
835
836
0
    return true;
837
0
}
838
839
bool ScViewFunctionSet::IsSelectionAtPoint( const Point& rPointPixel )
840
0
{
841
0
    bool bRefMode = ScModule::get()->IsFormulaMode();
842
0
    if (bRefMode)
843
0
        return false;
844
845
0
    if (m_rViewData.IsAnyFillMode())
846
0
        return false;
847
848
0
    ScMarkData& rMark = m_rViewData.GetMarkData();
849
0
    if (m_bAnchor || !rMark.IsMultiMarked())
850
0
    {
851
0
        SCCOL  nPosX;
852
0
        SCROW  nPosY;
853
0
        m_rViewData.GetPosFromPixel( rPointPixel.X(), rPointPixel.Y(), GetWhich(), nPosX, nPosY );
854
0
        return m_rViewData.GetMarkData().IsCellMarked( nPosX, nPosY );
855
0
    }
856
857
0
    return false;
858
0
}
859
860
void ScViewFunctionSet::DeselectAtPoint( const Point& /* rPointPixel */ )
861
0
{
862
    // doesn't exist
863
0
}
864
865
void ScViewFunctionSet::DeselectAll()
866
0
{
867
0
    if (m_rViewData.IsAnyFillMode())
868
0
        return;
869
870
0
    bool bRefMode = ScModule::get()->IsFormulaMode();
871
0
    if (bRefMode)
872
0
    {
873
0
        m_rViewData.GetView()->DoneRefMode();
874
0
    }
875
0
    else
876
0
    {
877
0
        m_rViewData.GetView()->DoneBlockMode();
878
0
        m_rViewData.GetViewShell()->UpdateInputHandler();
879
0
    }
880
881
0
    m_bAnchor = false;
882
0
}
883
884
ScViewSelectionEngine::ScViewSelectionEngine( vcl::Window* pWindow, ScTabView* pView,
885
                                                ScSplitPos eSplitPos ) :
886
0
        SelectionEngine( pWindow, &pView->GetFunctionSet() ),
887
0
        eWhich( eSplitPos )
888
0
{
889
0
    SetSelectionMode( SelectionMode::Multiple );
890
0
    EnableDrag( true );
891
0
}
892
893
// column and row headers
894
ScHeaderFunctionSet::ScHeaderFunctionSet( ScViewData& rData ) :
895
0
        rViewData( rData ),
896
0
        bColumn( false ),
897
0
        eWhich( SC_SPLIT_TOPLEFT ),
898
0
        bAnchor( false ),
899
0
        nCursorPos( 0 )
900
0
{
901
0
}
902
903
void ScHeaderFunctionSet::SetColumn( bool bSet )
904
0
{
905
0
    bColumn = bSet;
906
0
}
907
908
void ScHeaderFunctionSet::SetWhich( ScSplitPos eNew )
909
0
{
910
0
    eWhich = eNew;
911
0
}
912
913
void ScHeaderFunctionSet::BeginDrag()
914
0
{
915
    // doesn't exist
916
0
}
917
918
void ScHeaderFunctionSet::CreateAnchor()
919
0
{
920
0
    if (bAnchor)
921
0
        return;
922
923
0
    ScTabView* pView = rViewData.GetView();
924
0
    pView->DoneBlockMode( true );
925
0
    if (bColumn)
926
0
    {
927
0
        pView->InitBlockMode( static_cast<SCCOL>(nCursorPos), 0, rViewData.CurrentTabForData(), true, true );
928
0
        pView->MarkCursor( static_cast<SCCOL>(nCursorPos), rViewData.MaxRow(), rViewData.CurrentTabForData() );
929
0
    }
930
0
    else
931
0
    {
932
0
        pView->InitBlockMode( 0, nCursorPos, rViewData.CurrentTabForData(), true, false, true );
933
0
        pView->MarkCursor( rViewData.MaxCol(), nCursorPos, rViewData.CurrentTabForData() );
934
0
    }
935
0
    bAnchor = true;
936
0
}
937
938
void ScHeaderFunctionSet::DestroyAnchor()
939
0
{
940
0
    rViewData.GetView()->DoneBlockMode( true );
941
0
    bAnchor = false;
942
0
}
943
944
void ScHeaderFunctionSet::SetCursorAtPoint( const Point& rPointPixel, bool /* bDontSelectAtCursor */ )
945
0
{
946
0
    if ( bDidSwitch )
947
0
    {
948
        // next valid position has to be originated from another window
949
0
        if ( rPointPixel == aSwitchPos )
950
0
            return;                   // don't scroll in the wrong window
951
0
        else
952
0
            bDidSwitch = false;
953
0
    }
954
955
    //  Scrolling
956
0
    Size aWinSize = rViewData.GetActiveWin()->GetOutputSizePixel();
957
0
    bool bScroll;
958
0
    if (bColumn)
959
0
        bScroll = ( rPointPixel.X() < 0 || rPointPixel.X() >= aWinSize.Width() );
960
0
    else
961
0
        bScroll = ( rPointPixel.Y() < 0 || rPointPixel.Y() >= aWinSize.Height() );
962
963
    // moved out of fix limit?
964
0
    bool bSwitched = false;
965
0
    if ( bColumn )
966
0
    {
967
0
        if ( rViewData.GetHSplitMode() == SC_SPLIT_FIX )
968
0
        {
969
0
            if ( rPointPixel.X() > aWinSize.Width() )
970
0
            {
971
0
                if ( eWhich == SC_SPLIT_TOPLEFT )
972
0
                {
973
0
                    rViewData.GetView()->ActivatePart( SC_SPLIT_TOPRIGHT );
974
0
                    bSwitched = true;
975
0
                }
976
0
                else if ( eWhich == SC_SPLIT_BOTTOMLEFT )
977
0
                {
978
0
                    rViewData.GetView()->ActivatePart( SC_SPLIT_BOTTOMRIGHT );
979
0
                    bSwitched = true;
980
0
                }
981
0
            }
982
0
        }
983
0
    }
984
0
    else                // column headers
985
0
    {
986
0
        if ( rViewData.GetVSplitMode() == SC_SPLIT_FIX )
987
0
        {
988
0
            if ( rPointPixel.Y() > aWinSize.Height() )
989
0
            {
990
0
                if ( eWhich == SC_SPLIT_TOPLEFT )
991
0
                {
992
0
                    rViewData.GetView()->ActivatePart( SC_SPLIT_BOTTOMLEFT );
993
0
                    bSwitched = true;
994
0
                }
995
0
                else if ( eWhich == SC_SPLIT_TOPRIGHT )
996
0
                {
997
0
                    rViewData.GetView()->ActivatePart( SC_SPLIT_BOTTOMRIGHT );
998
0
                    bSwitched = true;
999
0
                }
1000
0
            }
1001
0
        }
1002
0
    }
1003
0
    if (bSwitched)
1004
0
    {
1005
0
        aSwitchPos = rPointPixel;
1006
0
        bDidSwitch = true;
1007
0
        return;               // do not crunch with wrong positions
1008
0
    }
1009
1010
0
    SCCOL  nPosX;
1011
0
    SCROW  nPosY;
1012
0
    rViewData.GetPosFromPixel( rPointPixel.X(), rPointPixel.Y(), rViewData.GetActivePart(),
1013
0
                                nPosX, nPosY, false );
1014
0
    if (bColumn)
1015
0
    {
1016
0
        nCursorPos = static_cast<SCCOLROW>(nPosX);
1017
0
        nPosY = rViewData.GetPosY(WhichV(rViewData.GetActivePart()));
1018
0
    }
1019
0
    else
1020
0
    {
1021
0
        nCursorPos = static_cast<SCCOLROW>(nPosY);
1022
0
        nPosX = rViewData.GetPosX(WhichH(rViewData.GetActivePart()));
1023
0
    }
1024
1025
0
    ScTabView* pView = rViewData.GetView();
1026
0
    bool bHide = rViewData.GetCurX() != nPosX ||
1027
0
                 rViewData.GetCurY() != nPosY;
1028
0
    if (bHide)
1029
0
        pView->HideAllCursors();
1030
1031
0
    if (bScroll)
1032
0
        pView->AlignToCursor( nPosX, nPosY, SC_FOLLOW_LINE );
1033
0
    pView->SetCursor( nPosX, nPosY );
1034
1035
0
    if ( !bAnchor || !pView->IsBlockMode() )
1036
0
    {
1037
0
        pView->DoneBlockMode( true );
1038
0
        rViewData.GetMarkData().MarkToMulti();         //! who changes this?
1039
0
        pView->InitBlockMode( nPosX, nPosY, rViewData.CurrentTabForData(), true, bColumn, !bColumn );
1040
1041
0
        bAnchor = true;
1042
0
    }
1043
1044
0
    pView->MarkCursor( nPosX, nPosY, rViewData.CurrentTabForData(), bColumn, !bColumn );
1045
1046
    // SelectionChanged inside of HideCursor because of UpdateAutoFillMark
1047
0
    pView->SelectionChanged();
1048
1049
0
    if (bHide)
1050
0
        pView->ShowAllCursors();
1051
0
}
1052
1053
bool ScHeaderFunctionSet::IsSelectionAtPoint( const Point& rPointPixel )
1054
0
{
1055
0
    SCCOL  nPosX;
1056
0
    SCROW  nPosY;
1057
0
    rViewData.GetPosFromPixel( rPointPixel.X(), rPointPixel.Y(), rViewData.GetActivePart(),
1058
0
                                nPosX, nPosY, false );
1059
1060
0
    ScMarkData& rMark = rViewData.GetMarkData();
1061
0
    if (bColumn)
1062
0
        return rMark.IsColumnMarked( nPosX );
1063
0
    else
1064
0
        return rMark.IsRowMarked( nPosY );
1065
0
}
1066
1067
void ScHeaderFunctionSet::DeselectAtPoint( const Point& /* rPointPixel */ )
1068
0
{
1069
0
}
1070
1071
void ScHeaderFunctionSet::DeselectAll()
1072
0
{
1073
0
    rViewData.GetView()->DoneBlockMode();
1074
0
    bAnchor = false;
1075
0
}
1076
1077
ScHeaderSelectionEngine::ScHeaderSelectionEngine( vcl::Window* pWindow, ScHeaderFunctionSet* pFuncSet ) :
1078
0
        SelectionEngine( pWindow, pFuncSet )
1079
0
{
1080
0
    SetSelectionMode( SelectionMode::Multiple );
1081
0
    EnableDrag( false );
1082
0
}
1083
1084
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */