Coverage Report

Created: 2026-03-31 11:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/control/spinbtn.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/event.hxx>
21
#include <vcl/rendercontext/SystemTextColorFlags.hxx>
22
#include <vcl/toolkit/spin.hxx>
23
#include <vcl/salnativewidgets.hxx>
24
#include <vcl/settings.hxx>
25
#include <vcl/vclevent.hxx>
26
27
#include <spin.hxx>
28
29
SpinButton::SpinButton( vcl::Window* pParent, WinBits nStyle )
30
0
    : Control(WindowType::SPINBUTTON)
31
0
    , maRepeatTimer("SpinButton maRepeatTimer")
32
0
    , mbUpperIsFocused(false)
33
0
{
34
0
    mbUpperIn     = false;
35
0
    mbLowerIn     = false;
36
0
    mbInitialUp   = false;
37
0
    mbInitialDown = false;
38
39
0
    mnMinRange  = 0;
40
0
    mnMaxRange  = 100;
41
0
    mnValue     = 0;
42
0
    mnValueStep = 1;
43
44
0
    maRepeatTimer.SetTimeout(MouseSettings::GetButtonStartRepeat());
45
0
    maRepeatTimer.SetInvokeHandler(LINK(this, SpinButton, ImplTimeout));
46
47
0
    mbRepeat = 0 != (nStyle & WB_REPEAT);
48
49
0
    if (nStyle & WB_HSCROLL)
50
0
        mbHorz = true;
51
0
    else
52
0
        mbHorz = false;
53
54
0
    Control::ImplInit( pParent, nStyle, nullptr );
55
0
}
Unexecuted instantiation: SpinButton::SpinButton(vcl::Window*, long)
Unexecuted instantiation: SpinButton::SpinButton(vcl::Window*, long)
56
57
IMPL_LINK(SpinButton, ImplTimeout, Timer*, pTimer, void)
58
0
{
59
0
    if (pTimer->GetTimeout() == static_cast<sal_uInt64>(MouseSettings::GetButtonStartRepeat()))
60
0
    {
61
0
        pTimer->SetTimeout( GetSettings().GetMouseSettings().GetButtonRepeat() );
62
0
        pTimer->Start();
63
0
    }
64
0
    else
65
0
    {
66
0
        if (mbInitialUp)
67
0
            Up();
68
0
        else
69
0
            Down();
70
0
    }
71
0
}
72
73
void SpinButton::Up()
74
0
{
75
0
    if (ImplIsUpperEnabled())
76
0
    {
77
0
        mnValue += mnValueStep;
78
0
        CompatStateChanged(StateChangedType::Data);
79
80
0
        ImplMoveFocus(true);
81
0
    }
82
83
0
    ImplCallEventListenersAndHandler(VclEventId::SpinbuttonUp, nullptr );
84
0
}
85
86
void SpinButton::Down()
87
0
{
88
0
    if (ImplIsLowerEnabled())
89
0
    {
90
0
        mnValue -= mnValueStep;
91
0
        CompatStateChanged(StateChangedType::Data);
92
93
0
        ImplMoveFocus(false);
94
0
    }
95
96
0
    ImplCallEventListenersAndHandler(VclEventId::SpinbuttonDown, nullptr );
97
0
}
98
99
void SpinButton::Resize()
100
0
{
101
0
    Control::Resize();
102
103
0
    Size aSize(GetOutputSizePixel());
104
0
    tools::Rectangle aRect(Point(), aSize);
105
0
    if (mbHorz)
106
0
    {
107
0
        maLowerRect = tools::Rectangle(0, 0, aSize.Width() / 2, aSize.Height() - 1);
108
0
        maUpperRect = tools::Rectangle(maLowerRect.TopRight(), aRect.BottomRight());
109
0
    }
110
0
    else
111
0
    {
112
0
        maUpperRect = tools::Rectangle(0, 0, aSize.Width() - 1, aSize.Height() / 2);
113
0
        maLowerRect = tools::Rectangle(maUpperRect.BottomLeft(), aRect.BottomRight());
114
0
    }
115
116
0
    ImplCalcFocusRect(ImplIsUpperEnabled() || !ImplIsLowerEnabled());
117
118
0
    Invalidate();
119
0
}
120
121
void SpinButton::Draw(OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags)
122
0
{
123
0
    Point aPos  = pDev->LogicToPixel(rPos);
124
0
    Size aSize = GetSizePixel();
125
126
0
    auto popIt = pDev->ScopedPush();
127
0
    pDev->SetMapMode();
128
0
    if ( !(nFlags & SystemTextColorFlags::Mono) )
129
0
    {
130
        // DecoView uses the FaceColor...
131
0
        AllSettings aSettings = pDev->GetSettings();
132
0
        StyleSettings aStyleSettings = aSettings.GetStyleSettings();
133
0
        if ( IsControlBackground() )
134
0
            aStyleSettings.SetFaceColor( GetControlBackground() );
135
0
        else
136
0
            aStyleSettings.SetFaceColor( GetSettings().GetStyleSettings().GetFaceColor() );
137
138
0
        aSettings.SetStyleSettings( aStyleSettings );
139
0
        pDev->SetSettings( aSettings );
140
0
    }
141
142
0
    tools::Rectangle   aRect( Point( 0, 0 ), aSize );
143
0
    tools::Rectangle aLowerRect, aUpperRect;
144
0
    if ( mbHorz )
145
0
    {
146
0
        aLowerRect = tools::Rectangle( 0, 0, aSize.Width()/2, aSize.Height()-1 );
147
0
        aUpperRect = tools::Rectangle( aLowerRect.TopRight(), aRect.BottomRight() );
148
0
    }
149
0
    else
150
0
    {
151
0
        aUpperRect = tools::Rectangle( 0, 0, aSize.Width()-1, aSize.Height()/2 );
152
0
        aLowerRect = tools::Rectangle( aUpperRect.BottomLeft(), aRect.BottomRight() );
153
0
    }
154
155
0
    aUpperRect += aPos;
156
0
    aLowerRect += aPos;
157
158
0
    ImplDrawSpinButton(*pDev, this, aUpperRect, aLowerRect, false, false,
159
0
                       IsEnabled() && ImplIsUpperEnabled(),
160
0
                       IsEnabled() && ImplIsLowerEnabled(), mbHorz, true);
161
0
}
162
163
void SpinButton::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
164
0
{
165
0
    HideFocus();
166
167
0
    bool bEnable = IsEnabled();
168
0
    ImplDrawSpinButton(rRenderContext, this, maUpperRect, maLowerRect, mbUpperIn, mbLowerIn,
169
0
                       bEnable && ImplIsUpperEnabled(),
170
0
                       bEnable && ImplIsLowerEnabled(), mbHorz, true);
171
172
0
    if (HasFocus())
173
0
        ShowFocus(maFocusRect);
174
0
}
175
176
void SpinButton::MouseButtonDown( const MouseEvent& rMEvt )
177
0
{
178
0
    if ( maUpperRect.Contains( rMEvt.GetPosPixel() ) && ( ImplIsUpperEnabled() ) )
179
0
    {
180
0
        mbUpperIn   = true;
181
0
        mbInitialUp = true;
182
0
        Invalidate( maUpperRect );
183
0
    }
184
0
    else if ( maLowerRect.Contains( rMEvt.GetPosPixel() ) && ( ImplIsLowerEnabled() ) )
185
0
    {
186
0
        mbLowerIn     = true;
187
0
        mbInitialDown = true;
188
0
        Invalidate( maLowerRect );
189
0
    }
190
191
0
    if ( mbUpperIn || mbLowerIn )
192
0
    {
193
0
        CaptureMouse();
194
0
        if ( mbRepeat )
195
0
            maRepeatTimer.Start();
196
0
    }
197
0
}
198
199
void SpinButton::MouseButtonUp( const MouseEvent& )
200
0
{
201
0
    ReleaseMouse();
202
0
    if ( mbRepeat )
203
0
    {
204
0
        maRepeatTimer.Stop();
205
0
        maRepeatTimer.SetTimeout(MouseSettings::GetButtonStartRepeat() );
206
0
    }
207
208
0
    if ( mbUpperIn )
209
0
    {
210
0
        mbUpperIn   = false;
211
0
        Invalidate( maUpperRect );
212
0
        Up();
213
0
    }
214
0
    else if ( mbLowerIn )
215
0
    {
216
0
        mbLowerIn = false;
217
0
        Invalidate( maLowerRect );
218
0
        Down();
219
0
    }
220
221
0
    mbInitialUp = mbInitialDown = false;
222
0
}
223
224
void SpinButton::MouseMove( const MouseEvent& rMEvt )
225
0
{
226
0
    if ( !rMEvt.IsLeft() || (!mbInitialUp && !mbInitialDown) )
227
0
        return;
228
229
0
    if ( !maUpperRect.Contains( rMEvt.GetPosPixel() ) &&
230
0
         mbUpperIn && mbInitialUp )
231
0
    {
232
0
        mbUpperIn = false;
233
0
        maRepeatTimer.Stop();
234
0
        Invalidate( maUpperRect );
235
0
    }
236
0
    else if ( !maLowerRect.Contains( rMEvt.GetPosPixel() ) &&
237
0
              mbLowerIn && mbInitialDown )
238
0
    {
239
0
        mbLowerIn = false;
240
0
        maRepeatTimer.Stop();
241
0
        Invalidate( maLowerRect );
242
0
    }
243
0
    else if ( maUpperRect.Contains( rMEvt.GetPosPixel() ) &&
244
0
              !mbUpperIn && mbInitialUp )
245
0
    {
246
0
        mbUpperIn = true;
247
0
        if ( mbRepeat )
248
0
            maRepeatTimer.Start();
249
0
        Invalidate( maUpperRect );
250
0
    }
251
0
    else if ( maLowerRect.Contains( rMEvt.GetPosPixel() ) &&
252
0
              !mbLowerIn && mbInitialDown )
253
0
    {
254
0
        mbLowerIn = true;
255
0
        if ( mbRepeat )
256
0
            maRepeatTimer.Start();
257
0
        Invalidate( maLowerRect );
258
0
    }
259
0
}
260
261
void SpinButton::KeyInput( const KeyEvent& rKEvt )
262
0
{
263
0
    if ( !rKEvt.GetKeyCode().GetModifier() )
264
0
    {
265
0
        switch ( rKEvt.GetKeyCode().GetCode() )
266
0
        {
267
0
        case KEY_LEFT:
268
0
        case KEY_RIGHT:
269
0
        {
270
0
            bool bUp = KEY_RIGHT == rKEvt.GetKeyCode().GetCode();
271
0
            if ( mbHorz && !ImplMoveFocus( bUp ) )
272
0
                bUp ? Up() : Down();
273
0
        }
274
0
        break;
275
276
0
        case KEY_UP:
277
0
        case KEY_DOWN:
278
0
        {
279
0
            bool bUp = KEY_UP == rKEvt.GetKeyCode().GetCode();
280
0
            if ( !mbHorz && !ImplMoveFocus( KEY_UP == rKEvt.GetKeyCode().GetCode() ) )
281
0
                bUp ? Up() : Down();
282
0
        }
283
0
        break;
284
285
0
        case KEY_SPACE:
286
0
            mbUpperIsFocused ? Up() : Down();
287
0
            break;
288
289
0
        default:
290
0
            Control::KeyInput( rKEvt );
291
0
            break;
292
0
        }
293
0
    }
294
0
    else
295
0
        Control::KeyInput( rKEvt );
296
0
}
297
298
void SpinButton::StateChanged( StateChangedType nType )
299
0
{
300
0
    switch ( nType )
301
0
    {
302
0
    case StateChangedType::Data:
303
0
    case StateChangedType::Enable:
304
0
        Invalidate();
305
0
        break;
306
307
0
    case StateChangedType::Style:
308
0
    {
309
0
        bool bNewRepeat = 0 != ( GetStyle() & WB_REPEAT );
310
0
        if ( bNewRepeat != mbRepeat )
311
0
        {
312
0
            if ( maRepeatTimer.IsActive() )
313
0
            {
314
0
                maRepeatTimer.Stop();
315
0
                maRepeatTimer.SetTimeout( MouseSettings::GetButtonStartRepeat() );
316
0
            }
317
0
            mbRepeat = bNewRepeat;
318
0
        }
319
320
0
        bool bNewHorz = 0 != ( GetStyle() & WB_HSCROLL );
321
0
        if ( bNewHorz != mbHorz )
322
0
        {
323
0
            mbHorz = bNewHorz;
324
0
            Resize();
325
0
        }
326
0
    }
327
0
    break;
328
0
    default:;
329
0
    }
330
331
0
    Control::StateChanged( nType );
332
0
}
333
334
void SpinButton::SetRangeMin( tools::Long nNewRange )
335
0
{
336
0
    SetRange( Range( nNewRange, GetRangeMax() ) );
337
0
}
338
339
void SpinButton::SetRangeMax( tools::Long nNewRange )
340
0
{
341
0
    SetRange( Range( GetRangeMin(), nNewRange ) );
342
0
}
343
344
void SpinButton::SetRange( const Range& rRange )
345
0
{
346
    // adjust rage
347
0
    Range aRange = rRange;
348
0
    aRange.Normalize();
349
0
    tools::Long nNewMinRange = aRange.Min();
350
0
    tools::Long nNewMaxRange = aRange.Max();
351
352
    // do something only if old and new range differ
353
0
    if ( (mnMinRange == nNewMinRange) && (mnMaxRange == nNewMaxRange))
354
0
        return;
355
356
0
    mnMinRange = nNewMinRange;
357
0
    mnMaxRange = nNewMaxRange;
358
359
    // adjust value to new range, if necessary
360
0
    if ( mnValue > mnMaxRange )
361
0
        mnValue = mnMaxRange;
362
0
    if ( mnValue < mnMinRange )
363
0
        mnValue = mnMinRange;
364
365
0
    CompatStateChanged( StateChangedType::Data );
366
0
}
367
368
void SpinButton::SetValue( tools::Long nValue )
369
0
{
370
    // adjust, if necessary
371
0
    if ( nValue > mnMaxRange )
372
0
        nValue = mnMaxRange;
373
0
    if ( nValue < mnMinRange )
374
0
        nValue = mnMinRange;
375
376
0
    if ( mnValue != nValue )
377
0
    {
378
0
        mnValue = nValue;
379
0
        CompatStateChanged( StateChangedType::Data );
380
0
    }
381
0
}
382
383
void SpinButton::GetFocus()
384
0
{
385
0
    ShowFocus( maFocusRect );
386
0
    Control::GetFocus();
387
0
}
388
389
void SpinButton::LoseFocus()
390
0
{
391
0
    HideFocus();
392
0
    Control::LoseFocus();
393
0
}
394
395
bool SpinButton::ImplMoveFocus( bool _bUpper )
396
0
{
397
0
    if ( _bUpper == mbUpperIsFocused )
398
0
        return false;
399
400
0
    HideFocus();
401
0
    ImplCalcFocusRect( _bUpper );
402
0
    if ( HasFocus() )
403
0
        ShowFocus( maFocusRect );
404
0
    return true;
405
0
}
406
407
void SpinButton::ImplCalcFocusRect( bool _bUpper )
408
0
{
409
0
    maFocusRect = _bUpper ? maUpperRect : maLowerRect;
410
    // inflate by some pixels
411
0
    maFocusRect.AdjustLeft(2 );
412
0
    maFocusRect.AdjustTop(2 );
413
0
    maFocusRect.AdjustRight( -2 );
414
0
    maFocusRect.AdjustBottom( -2 );
415
0
    mbUpperIsFocused = _bUpper;
416
0
}
417
418
tools::Rectangle* SpinButton::ImplFindPartRect(const Point& rPt)
419
0
{
420
0
    if (maUpperRect.Contains(rPt))
421
0
        return &maUpperRect;
422
423
0
    if (maLowerRect.Contains(rPt))
424
0
        return &maLowerRect;
425
426
0
    return nullptr;
427
0
}
428
429
bool SpinButton::PreNotify( NotifyEvent& rNEvt )
430
0
{
431
0
    if (rNEvt.GetType() == NotifyEventType::MOUSEMOVE)
432
0
    {
433
0
        const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
434
0
        if (pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged())
435
0
        {
436
            // trigger redraw if mouse over state has changed
437
0
            if (IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire) ||
438
0
                IsNativeControlSupported(ControlType::Spinbox, ControlPart::AllButtons) )
439
0
            {
440
0
                tools::Rectangle* pRect = ImplFindPartRect( GetPointerPosPixel() );
441
0
                tools::Rectangle* pLastRect = ImplFindPartRect( GetLastPointerPosPixel() );
442
0
                if (pRect != pLastRect || (pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow()))
443
0
                {
444
0
                    vcl::Region aRgn(GetOutDev()->GetActiveClipRegion());
445
0
                    if (pLastRect)
446
0
                    {
447
0
                        GetOutDev()->SetClipRegion(vcl::Region(*pLastRect));
448
0
                        Invalidate(*pLastRect);
449
0
                        GetOutDev()->SetClipRegion( aRgn );
450
0
                    }
451
0
                    if (pRect)
452
0
                    {
453
0
                        GetOutDev()->SetClipRegion(vcl::Region(*pRect));
454
0
                        Invalidate(*pRect);
455
0
                        GetOutDev()->SetClipRegion(aRgn);
456
0
                    }
457
0
                }
458
0
            }
459
0
        }
460
0
    }
461
462
0
    return Control::PreNotify(rNEvt);
463
0
}
464
465
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */