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/event.hxx>
22 #include <vcl/decoview.hxx>
23 #include <vcl/toolkit/spinfld.hxx>
24 #include <vcl/settings.hxx>
25 #include <vcl/uitest/uiobject.hxx>
26 #include <sal/log.hxx>
27 
28 #include <controldata.hxx>
29 #include <spin.hxx>
30 #include <svdata.hxx>
31 
32 namespace {
33 
ImplGetSpinbuttonValue(vcl::Window * pWin,const tools::Rectangle & rUpperRect,const tools::Rectangle & rLowerRect,bool bUpperIn,bool bLowerIn,bool bUpperEnabled,bool bLowerEnabled,bool bHorz,SpinbuttonValue & rValue)34 void ImplGetSpinbuttonValue(vcl::Window* pWin,
35                             const tools::Rectangle& rUpperRect, const tools::Rectangle& rLowerRect,
36                             bool bUpperIn, bool bLowerIn, bool bUpperEnabled, bool bLowerEnabled,
37                             bool bHorz, SpinbuttonValue& rValue )
38 {
39     // convert spinbutton data to a SpinbuttonValue structure for native painting
40 
41     rValue.maUpperRect = rUpperRect;
42     rValue.maLowerRect = rLowerRect;
43 
44     Point aPointerPos = pWin->GetPointerPosPixel();
45 
46     ControlState nState = ControlState::ENABLED;
47     if (bUpperIn)
48         nState |= ControlState::PRESSED;
49     if (!pWin->IsEnabled() || !bUpperEnabled)
50         nState &= ~ControlState::ENABLED;
51     if (pWin->HasFocus())
52         nState |= ControlState::FOCUSED;
53     if (pWin->IsMouseOver() && rUpperRect.IsInside(aPointerPos))
54         nState |= ControlState::ROLLOVER;
55     rValue.mnUpperState = nState;
56 
57     nState = ControlState::ENABLED;
58     if (bLowerIn)
59         nState |= ControlState::PRESSED;
60     if (!pWin->IsEnabled() || !bLowerEnabled)
61         nState &= ~ControlState::ENABLED;
62     if (pWin->HasFocus())
63         nState |= ControlState::FOCUSED;
64     // for overlapping spins: highlight only one
65     if (pWin->IsMouseOver() && rLowerRect.IsInside(aPointerPos) && !rUpperRect.IsInside(aPointerPos))
66         nState |= ControlState::ROLLOVER;
67     rValue.mnLowerState = nState;
68 
69     rValue.mnUpperPart = bHorz ? ControlPart::ButtonLeft : ControlPart::ButtonUp;
70     rValue.mnLowerPart = bHorz ? ControlPart::ButtonRight : ControlPart::ButtonDown;
71 }
72 
ImplDrawNativeSpinfield(vcl::RenderContext & rRenderContext,vcl::Window const * pWin,const SpinbuttonValue & rSpinbuttonValue)73 bool ImplDrawNativeSpinfield(vcl::RenderContext& rRenderContext, vcl::Window const * pWin, const SpinbuttonValue& rSpinbuttonValue)
74 {
75     bool bNativeOK = false;
76 
77     if (rRenderContext.IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire) &&
78         // there is just no useful native support for spinfields with dropdown
79         !(pWin->GetStyle() & WB_DROPDOWN))
80     {
81         if (rRenderContext.IsNativeControlSupported(ControlType::Spinbox, rSpinbuttonValue.mnUpperPart) &&
82             rRenderContext.IsNativeControlSupported(ControlType::Spinbox, rSpinbuttonValue.mnLowerPart))
83         {
84             // only paint the embedded spin buttons, all buttons are painted at once
85             tools::Rectangle aUpperAndLowerButtons( rSpinbuttonValue.maUpperRect.GetUnion( rSpinbuttonValue.maLowerRect ) );
86             bNativeOK = rRenderContext.DrawNativeControl(ControlType::Spinbox, ControlPart::AllButtons, aUpperAndLowerButtons,
87                                                          ControlState::ENABLED, rSpinbuttonValue, OUString());
88         }
89         else
90         {
91             // paint the spinbox as a whole, use borderwindow to have proper clipping
92             vcl::Window* pBorder = pWin->GetWindow(GetWindowType::Border);
93 
94             // to not overwrite everything, set the button region as clipregion to the border window
95             tools::Rectangle aClipRect(rSpinbuttonValue.maLowerRect);
96             aClipRect.Union(rSpinbuttonValue.maUpperRect);
97 
98             vcl::RenderContext* pContext = &rRenderContext;
99             vcl::Region oldRgn;
100             Point aPt;
101             Size aSize(pBorder->GetOutputSizePixel());    // the size of the border window, i.e., the whole control
102             tools::Rectangle aNatRgn(aPt, aSize);
103 
104             if (!pWin->SupportsDoubleBuffering())
105             {
106                 // convert from screen space to borderwin space
107                 aClipRect.SetPos(pBorder->ScreenToOutputPixel(pWin->OutputToScreenPixel(aClipRect.TopLeft())));
108 
109                 oldRgn = pBorder->GetOutDev()->GetClipRegion();
110                 pBorder->GetOutDev()->SetClipRegion(vcl::Region(aClipRect));
111 
112                 pContext = pBorder->GetOutDev();
113             }
114 
115             tools::Rectangle aBound, aContent;
116             if (!ImplGetSVData()->maNWFData.mbCanDrawWidgetAnySize &&
117                 pContext->GetNativeControlRegion(ControlType::Spinbox, ControlPart::Entire,
118                                                 aNatRgn, ControlState::NONE, rSpinbuttonValue,
119                                                 aBound, aContent))
120             {
121                 aSize = aContent.GetSize();
122             }
123 
124             tools::Rectangle aRgn(aPt, aSize);
125             if (pWin->SupportsDoubleBuffering())
126             {
127                 // convert from borderwin space, to the pWin's space
128                 aRgn.SetPos(pWin->ScreenToOutputPixel(pBorder->OutputToScreenPixel(aRgn.TopLeft())));
129             }
130 
131             bNativeOK = pContext->DrawNativeControl(ControlType::Spinbox, ControlPart::Entire, aRgn,
132                                                    ControlState::ENABLED, rSpinbuttonValue, OUString());
133 
134             if (!pWin->SupportsDoubleBuffering())
135                 pBorder->GetOutDev()->SetClipRegion(oldRgn);
136         }
137     }
138     return bNativeOK;
139 }
140 
ImplDrawNativeSpinbuttons(vcl::RenderContext & rRenderContext,const SpinbuttonValue & rSpinbuttonValue)141 bool ImplDrawNativeSpinbuttons(vcl::RenderContext& rRenderContext, const SpinbuttonValue& rSpinbuttonValue)
142 {
143     bool bNativeOK = false;
144 
145     if (rRenderContext.IsNativeControlSupported(ControlType::SpinButtons, ControlPart::Entire))
146     {
147         tools::Rectangle aArea = rSpinbuttonValue.maUpperRect.GetUnion(rSpinbuttonValue.maLowerRect);
148         // only paint the standalone spin buttons, all buttons are painted at once
149         bNativeOK = rRenderContext.DrawNativeControl(ControlType::SpinButtons, ControlPart::AllButtons, aArea,
150                                                      ControlState::ENABLED, rSpinbuttonValue, OUString());
151     }
152     return bNativeOK;
153 }
154 
155 }
156 
ImplDrawSpinButton(vcl::RenderContext & rRenderContext,vcl::Window * pWindow,const tools::Rectangle & rUpperRect,const tools::Rectangle & rLowerRect,bool bUpperIn,bool bLowerIn,bool bUpperEnabled,bool bLowerEnabled,bool bHorz,bool bMirrorHorz)157 void ImplDrawSpinButton(vcl::RenderContext& rRenderContext, vcl::Window* pWindow,
158                         const tools::Rectangle& rUpperRect, const tools::Rectangle& rLowerRect,
159                         bool bUpperIn, bool bLowerIn, bool bUpperEnabled, bool bLowerEnabled,
160                         bool bHorz, bool bMirrorHorz)
161 {
162     bool bNativeOK = false;
163 
164     if (pWindow)
165     {
166         // are we drawing standalone spin buttons or members of a spinfield ?
167         ControlType aControl = ControlType::SpinButtons;
168         switch (pWindow->GetType())
169         {
170             case WindowType::EDIT:
171             case WindowType::MULTILINEEDIT:
172             case WindowType::PATTERNFIELD:
173             case WindowType::METRICFIELD:
174             case WindowType::CURRENCYFIELD:
175             case WindowType::DATEFIELD:
176             case WindowType::TIMEFIELD:
177             case WindowType::SPINFIELD:
178             case WindowType::FORMATTEDFIELD:
179                 aControl = ControlType::Spinbox;
180                 break;
181             default:
182                 aControl = ControlType::SpinButtons;
183                 break;
184         }
185 
186         SpinbuttonValue aValue;
187         ImplGetSpinbuttonValue(pWindow, rUpperRect, rLowerRect,
188                                bUpperIn, bLowerIn, bUpperEnabled, bLowerEnabled,
189                                bHorz, aValue);
190 
191         if( aControl == ControlType::Spinbox )
192             bNativeOK = ImplDrawNativeSpinfield(rRenderContext, pWindow, aValue);
193         else if( aControl == ControlType::SpinButtons )
194             bNativeOK = ImplDrawNativeSpinbuttons(rRenderContext, aValue);
195     }
196 
197     if (bNativeOK)
198         return;
199 
200     ImplDrawUpDownButtons(rRenderContext,
201                           rUpperRect, rLowerRect,
202                           bUpperIn, bLowerIn, bUpperEnabled, bLowerEnabled,
203                           bHorz, bMirrorHorz);
204 }
205 
ImplDrawUpDownButtons(vcl::RenderContext & rRenderContext,const tools::Rectangle & rUpperRect,const tools::Rectangle & rLowerRect,bool bUpperIn,bool bLowerIn,bool bUpperEnabled,bool bLowerEnabled,bool bHorz,bool bMirrorHorz)206 void ImplDrawUpDownButtons(vcl::RenderContext& rRenderContext,
207                            const tools::Rectangle& rUpperRect, const tools::Rectangle& rLowerRect,
208                            bool bUpperIn, bool bLowerIn, bool bUpperEnabled, bool bLowerEnabled,
209                            bool bHorz, bool bMirrorHorz)
210 {
211     DecorationView aDecoView(&rRenderContext);
212 
213     SymbolType eType1, eType2;
214 
215     if ( bHorz )
216     {
217         eType1 = bMirrorHorz ? SymbolType::SPIN_RIGHT : SymbolType::SPIN_LEFT;
218         eType2 = bMirrorHorz ? SymbolType::SPIN_LEFT : SymbolType::SPIN_RIGHT;
219     }
220     else
221     {
222         eType1 = SymbolType::SPIN_UP;
223         eType2 = SymbolType::SPIN_DOWN;
224     }
225 
226     DrawButtonFlags nStyle = DrawButtonFlags::NoLeftLightBorder;
227     // draw upper/left Button
228     if (bUpperIn)
229         nStyle |= DrawButtonFlags::Pressed;
230 
231     tools::Rectangle aUpRect = aDecoView.DrawButton(rUpperRect, nStyle);
232 
233     nStyle = DrawButtonFlags::NoLeftLightBorder;
234     // draw lower/right Button
235     if (bLowerIn)
236         nStyle |= DrawButtonFlags::Pressed;
237 
238     tools::Rectangle aLowRect = aDecoView.DrawButton(rLowerRect, nStyle);
239 
240      // make use of additional default edge
241     aUpRect.AdjustLeft( -1 );
242     aUpRect.AdjustTop( -1 );
243     aUpRect.AdjustRight( 1 );
244     aUpRect.AdjustBottom( 1 );
245     aLowRect.AdjustLeft( -1 );
246     aLowRect.AdjustTop( -1 );
247     aLowRect.AdjustRight( 1 );
248     aLowRect.AdjustBottom( 1 );
249 
250     // draw into the edge, so that something is visible if the rectangle is too small
251     if (aUpRect.GetHeight() < 4)
252     {
253         aUpRect.AdjustRight( 1 );
254         aUpRect.AdjustBottom( 1 );
255         aLowRect.AdjustRight( 1 );
256         aLowRect.AdjustBottom( 1 );
257     }
258 
259     // calculate Symbol size
260     tools::Long nTempSize1 = aUpRect.GetWidth();
261     tools::Long nTempSize2 = aLowRect.GetWidth();
262     if (std::abs( nTempSize1-nTempSize2 ) == 1)
263     {
264         if (nTempSize1 > nTempSize2)
265             aUpRect.AdjustLeft( 1 );
266         else
267             aLowRect.AdjustLeft( 1 );
268     }
269     nTempSize1 = aUpRect.GetHeight();
270     nTempSize2 = aLowRect.GetHeight();
271     if (std::abs(nTempSize1 - nTempSize2) == 1)
272     {
273         if (nTempSize1 > nTempSize2)
274             aUpRect.AdjustTop( 1 );
275         else
276             aLowRect.AdjustTop( 1 );
277     }
278 
279     const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
280 
281     DrawSymbolFlags nSymStyle = DrawSymbolFlags::NONE;
282     if (!bUpperEnabled)
283         nSymStyle |= DrawSymbolFlags::Disable;
284     aDecoView.DrawSymbol(aUpRect, eType1, rStyleSettings.GetButtonTextColor(), nSymStyle);
285 
286     nSymStyle = DrawSymbolFlags::NONE;
287     if (!bLowerEnabled)
288         nSymStyle |= DrawSymbolFlags::Disable;
289     aDecoView.DrawSymbol(aLowRect, eType2, rStyleSettings.GetButtonTextColor(), nSymStyle);
290 }
291 
ImplInitSpinFieldData()292 void SpinField::ImplInitSpinFieldData()
293 {
294     mpEdit.disposeAndClear();
295     mbSpin          = false;
296     mbRepeat        = false;
297     mbUpperIn       = false;
298     mbLowerIn       = false;
299     mbInitialUp     = false;
300     mbInitialDown   = false;
301     mbInDropDown    = false;
302 }
303 
ImplInit(vcl::Window * pParent,WinBits nWinStyle)304 void SpinField::ImplInit(vcl::Window* pParent, WinBits nWinStyle)
305 {
306     Edit::ImplInit( pParent, nWinStyle );
307 
308     if (!(nWinStyle & (WB_SPIN | WB_DROPDOWN)))
309         return;
310 
311     mbSpin = true;
312 
313     // Some themes want external spin buttons, therefore the main
314     // spinfield should not overdraw the border between its encapsulated
315     // edit field and the spin buttons
316     if ((nWinStyle & WB_SPIN) && ImplUseNativeBorder(*GetOutDev(), nWinStyle))
317     {
318         SetBackground();
319         mpEdit.set(VclPtr<Edit>::Create(this, WB_NOBORDER));
320         mpEdit->SetBackground();
321     }
322     else
323         mpEdit.set(VclPtr<Edit>::Create(this, WB_NOBORDER));
324 
325     mpEdit->EnableRTL(false);
326     mpEdit->SetPosPixel(Point());
327     mpEdit->Show();
328 
329     SetSubEdit(mpEdit);
330 
331     maRepeatTimer.SetInvokeHandler(LINK( this, SpinField, ImplTimeout));
332     maRepeatTimer.SetTimeout(MouseSettings::GetButtonStartRepeat());
333     if (nWinStyle & WB_REPEAT)
334         mbRepeat = true;
335 
336     SetCompoundControl(true);
337 }
338 
SpinField(vcl::Window * pParent,WinBits nWinStyle,WindowType nType)339 SpinField::SpinField(vcl::Window* pParent, WinBits nWinStyle, WindowType nType) :
340     Edit(nType)
341 {
342     ImplInitSpinFieldData();
343     ImplInit(pParent, nWinStyle);
344 }
345 
~SpinField()346 SpinField::~SpinField()
347 {
348     disposeOnce();
349 }
350 
dispose()351 void SpinField::dispose()
352 {
353     mpEdit.disposeAndClear();
354 
355     Edit::dispose();
356 }
357 
Up()358 void SpinField::Up()
359 {
360     ImplCallEventListenersAndHandler( VclEventId::SpinfieldUp, [this] () { maUpHdlLink.Call(*this); } );
361 }
362 
Down()363 void SpinField::Down()
364 {
365     ImplCallEventListenersAndHandler( VclEventId::SpinfieldDown, [this] () { maDownHdlLink.Call(*this); } );
366 }
367 
First()368 void SpinField::First()
369 {
370     ImplCallEventListenersAndHandler(VclEventId::SpinfieldFirst, nullptr);
371 }
372 
Last()373 void SpinField::Last()
374 {
375     ImplCallEventListenersAndHandler(VclEventId::SpinfieldLast, nullptr);
376 }
377 
MouseButtonDown(const MouseEvent & rMEvt)378 void SpinField::MouseButtonDown( const MouseEvent& rMEvt )
379 {
380     if (!HasFocus() && (!mpEdit || !mpEdit->HasFocus()))
381     {
382         GrabFocus();
383     }
384 
385     if (!IsReadOnly())
386     {
387         if (maUpperRect.IsInside(rMEvt.GetPosPixel()))
388         {
389             mbUpperIn   = true;
390             mbInitialUp = true;
391             Invalidate(maUpperRect);
392         }
393         else if (maLowerRect.IsInside(rMEvt.GetPosPixel()))
394         {
395             mbLowerIn    = true;
396             mbInitialDown = true;
397             Invalidate(maLowerRect);
398         }
399         else if (maDropDownRect.IsInside(rMEvt.GetPosPixel()))
400         {
401             // put DropDownButton to the right
402             mbInDropDown = ShowDropDown( !mbInDropDown );
403             Invalidate(tools::Rectangle(Point(), GetOutputSizePixel()));
404         }
405 
406         if (mbUpperIn || mbLowerIn)
407         {
408             CaptureMouse();
409             if (mbRepeat)
410                 maRepeatTimer.Start();
411             return;
412         }
413     }
414 
415     Edit::MouseButtonDown(rMEvt);
416 }
417 
MouseButtonUp(const MouseEvent & rMEvt)418 void SpinField::MouseButtonUp(const MouseEvent& rMEvt)
419 {
420     ReleaseMouse();
421     mbInitialUp = mbInitialDown = false;
422     maRepeatTimer.Stop();
423     maRepeatTimer.SetTimeout(MouseSettings::GetButtonStartRepeat());
424 
425     if (mbUpperIn)
426     {
427         mbUpperIn = false;
428         Invalidate(maUpperRect);
429         Up();
430     }
431     else if (mbLowerIn)
432     {
433         mbLowerIn = false;
434         Invalidate(maLowerRect);
435         Down();
436     }
437 
438     Edit::MouseButtonUp(rMEvt);
439 }
440 
MouseMove(const MouseEvent & rMEvt)441 void SpinField::MouseMove(const MouseEvent& rMEvt)
442 {
443     if (rMEvt.IsLeft())
444     {
445         if (mbInitialUp)
446         {
447             bool bNewUpperIn = maUpperRect.IsInside(rMEvt.GetPosPixel());
448             if (bNewUpperIn != mbUpperIn)
449             {
450                 if (bNewUpperIn)
451                 {
452                     if (mbRepeat)
453                         maRepeatTimer.Start();
454                 }
455                 else
456                     maRepeatTimer.Stop();
457 
458                 mbUpperIn = bNewUpperIn;
459                 Invalidate(maUpperRect);
460             }
461         }
462         else if (mbInitialDown)
463         {
464             bool bNewLowerIn = maLowerRect.IsInside(rMEvt.GetPosPixel());
465             if (bNewLowerIn != mbLowerIn)
466             {
467                 if (bNewLowerIn)
468                 {
469                     if (mbRepeat)
470                         maRepeatTimer.Start();
471                 }
472                 else
473                     maRepeatTimer.Stop();
474 
475                 mbLowerIn = bNewLowerIn;
476                 Invalidate(maLowerRect);
477             }
478         }
479     }
480 
481     Edit::MouseMove(rMEvt);
482 }
483 
EventNotify(NotifyEvent & rNEvt)484 bool SpinField::EventNotify(NotifyEvent& rNEvt)
485 {
486     bool bDone = false;
487     if (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT)
488     {
489         const KeyEvent& rKEvt = *rNEvt.GetKeyEvent();
490         if (!IsReadOnly())
491         {
492             sal_uInt16 nMod = rKEvt.GetKeyCode().GetModifier();
493             switch (rKEvt.GetKeyCode().GetCode())
494             {
495                 case KEY_UP:
496                 {
497                     if (!nMod)
498                     {
499                         Up();
500                         bDone = true;
501                     }
502                 }
503                 break;
504                 case KEY_DOWN:
505                 {
506                     if (!nMod)
507                     {
508                         Down();
509                         bDone = true;
510                     }
511                     else if ((nMod == KEY_MOD2) && !mbInDropDown && (GetStyle() & WB_DROPDOWN))
512                     {
513                         mbInDropDown = ShowDropDown(true);
514                         Invalidate(tools::Rectangle(Point(), GetOutputSizePixel()));
515                         bDone = true;
516                     }
517                 }
518                 break;
519                 case KEY_PAGEUP:
520                 {
521                     if (!nMod)
522                     {
523                         Last();
524                         bDone = true;
525                     }
526                 }
527                 break;
528                 case KEY_PAGEDOWN:
529                 {
530                     if (!nMod)
531                     {
532                         First();
533                         bDone = true;
534                     }
535                 }
536                 break;
537             }
538         }
539     }
540 
541     if (rNEvt.GetType() == MouseNotifyEvent::COMMAND)
542     {
543         if ((rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) && !IsReadOnly())
544         {
545             MouseWheelBehaviour nWheelBehavior(GetSettings().GetMouseSettings().GetWheelBehavior());
546             if (nWheelBehavior == MouseWheelBehaviour::ALWAYS
547                || (nWheelBehavior == MouseWheelBehaviour::FocusOnly && HasChildPathFocus()))
548             {
549                 const CommandWheelData* pData = rNEvt.GetCommandEvent()->GetWheelData();
550                 if (pData->GetMode() == CommandWheelMode::SCROLL)
551                 {
552                     if (pData->GetDelta() < 0)
553                         Down();
554                     else
555                         Up();
556                     bDone = true;
557 
558                     if (!HasChildPathFocus())
559                         GrabFocus();
560                 }
561             }
562             else
563                 bDone = false;  // don't eat this event, let the default handling happen (i.e. scroll the context)
564         }
565     }
566 
567     return bDone || Edit::EventNotify(rNEvt);
568 }
569 
FillLayoutData() const570 void SpinField::FillLayoutData() const
571 {
572     if (mbSpin)
573     {
574         mpControlData->mpLayoutData.reset( new vcl::ControlLayoutData );
575         AppendLayoutData(*GetSubEdit());
576         GetSubEdit()->SetLayoutDataParent(this);
577     }
578     else
579         Edit::FillLayoutData();
580 }
581 
Paint(vcl::RenderContext & rRenderContext,const tools::Rectangle & rRect)582 void SpinField::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
583 {
584     if (mbSpin)
585     {
586         bool bEnable = IsEnabled();
587         ImplDrawSpinButton(rRenderContext, this, maUpperRect, maLowerRect,
588                            mbUpperIn, mbLowerIn, bEnable, bEnable);
589     }
590 
591     if (GetStyle() & WB_DROPDOWN)
592     {
593         DecorationView aView(&rRenderContext);
594 
595         DrawButtonFlags nStyle = DrawButtonFlags::NoLightBorder;
596         if (mbInDropDown)
597             nStyle |= DrawButtonFlags::Pressed;
598         tools::Rectangle aInnerRect = aView.DrawButton(maDropDownRect, nStyle);
599 
600         DrawSymbolFlags nSymbolStyle = IsEnabled() ? DrawSymbolFlags::NONE : DrawSymbolFlags::Disable;
601         aView.DrawSymbol(aInnerRect, SymbolType::SPIN_DOWN, rRenderContext.GetSettings().GetStyleSettings().GetButtonTextColor(), nSymbolStyle);
602     }
603 
604     Edit::Paint(rRenderContext, rRect);
605 }
606 
ImplCalcButtonAreas(OutputDevice * pDev,const Size & rOutSz,tools::Rectangle & rDDArea,tools::Rectangle & rSpinUpArea,tools::Rectangle & rSpinDownArea)607 void SpinField::ImplCalcButtonAreas(OutputDevice* pDev, const Size& rOutSz, tools::Rectangle& rDDArea,
608                                     tools::Rectangle& rSpinUpArea, tools::Rectangle& rSpinDownArea)
609 {
610     const StyleSettings& rStyleSettings = pDev->GetSettings().GetStyleSettings();
611 
612     Size aSize = rOutSz;
613     Size aDropDownSize;
614 
615     if (GetStyle() & WB_DROPDOWN)
616     {
617         tools::Long nW = rStyleSettings.GetScrollBarSize();
618         nW = GetDrawPixel( pDev, nW );
619         aDropDownSize = Size( CalcZoom( nW ), aSize.Height() );
620         aSize.AdjustWidth( -(aDropDownSize.Width()) );
621         rDDArea = tools::Rectangle( Point( aSize.Width(), 0 ), aDropDownSize );
622         rDDArea.AdjustTop( -1 );
623     }
624     else
625         rDDArea.SetEmpty();
626 
627     // calculate sizes according to the height
628     if (GetStyle() & WB_SPIN)
629     {
630         tools::Long nBottom1 = aSize.Height()/2;
631         tools::Long nBottom2 = aSize.Height()-1;
632         tools::Long nTop2 = nBottom1;
633         if ( !(aSize.Height() & 0x01) )
634             nBottom1--;
635 
636         bool bNativeRegionOK = false;
637         tools::Rectangle aContentUp, aContentDown;
638 
639         if ((pDev->GetOutDevType() == OUTDEV_WINDOW) &&
640             // there is just no useful native support for spinfields with dropdown
641             ! (GetStyle() & WB_DROPDOWN) &&
642             IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire))
643         {
644             vcl::Window *pWin = pDev->GetOwnerWindow();
645             vcl::Window *pBorder = pWin->GetWindow( GetWindowType::Border );
646 
647             // get the system's spin button size
648             ImplControlValue aControlValue;
649             tools::Rectangle aBound;
650             Point aPoint;
651 
652             // use the full extent of the control
653             tools::Rectangle aArea( aPoint, pBorder->GetOutputSizePixel() );
654 
655             bNativeRegionOK =
656                 pWin->GetNativeControlRegion(ControlType::Spinbox, ControlPart::ButtonUp,
657                     aArea, ControlState::NONE, aControlValue, aBound, aContentUp) &&
658                 pWin->GetNativeControlRegion(ControlType::Spinbox, ControlPart::ButtonDown,
659                     aArea, ControlState::NONE, aControlValue, aBound, aContentDown);
660 
661             if (bNativeRegionOK)
662             {
663                 // convert back from border space to local coordinates
664                 aPoint = pBorder->ScreenToOutputPixel( pWin->OutputToScreenPixel( aPoint ) );
665                 aContentUp.Move(-aPoint.X(), -aPoint.Y());
666                 aContentDown.Move(-aPoint.X(), -aPoint.Y());
667             }
668         }
669 
670         if (bNativeRegionOK)
671         {
672             rSpinUpArea = aContentUp;
673             rSpinDownArea = aContentDown;
674         }
675         else
676         {
677             aSize.AdjustWidth( -(CalcZoom( GetDrawPixel( pDev, rStyleSettings.GetSpinSize() ) )) );
678 
679             rSpinUpArea = tools::Rectangle( aSize.Width(), 0, rOutSz.Width()-aDropDownSize.Width()-1, nBottom1 );
680             rSpinDownArea = tools::Rectangle( rSpinUpArea.Left(), nTop2, rSpinUpArea.Right(), nBottom2 );
681         }
682     }
683     else
684     {
685         rSpinUpArea.SetEmpty();
686         rSpinDownArea.SetEmpty();
687     }
688 }
689 
Resize()690 void SpinField::Resize()
691 {
692     if (!mbSpin)
693         return;
694 
695     Control::Resize();
696     Size aSize = GetOutputSizePixel();
697     bool bSubEditPositioned = false;
698 
699     if (GetStyle() & (WB_SPIN | WB_DROPDOWN))
700     {
701         ImplCalcButtonAreas( GetOutDev(), aSize, maDropDownRect, maUpperRect, maLowerRect );
702 
703         ImplControlValue aControlValue;
704         Point aPoint;
705         tools::Rectangle aContent, aBound;
706 
707         // use the full extent of the control
708         vcl::Window *pBorder = GetWindow( GetWindowType::Border );
709         tools::Rectangle aArea( aPoint, pBorder->GetOutputSizePixel() );
710 
711         // adjust position and size of the edit field
712         if (GetNativeControlRegion(ControlType::Spinbox, ControlPart::SubEdit, aArea, ControlState::NONE,
713                                    aControlValue, aBound, aContent) &&
714             // there is just no useful native support for spinfields with dropdown
715             !(GetStyle() & WB_DROPDOWN))
716         {
717             // convert back from border space to local coordinates
718             aPoint = pBorder->ScreenToOutputPixel(OutputToScreenPixel(aPoint));
719             aContent.Move(-aPoint.X(), -aPoint.Y());
720 
721             // use the themes drop down size
722             mpEdit->SetPosPixel( aContent.TopLeft() );
723             bSubEditPositioned = true;
724             aSize = aContent.GetSize();
725         }
726         else
727         {
728             if (maUpperRect.IsEmpty())
729             {
730                 SAL_WARN_IF( maDropDownRect.IsEmpty(), "vcl", "SpinField::Resize: SPIN && DROPDOWN, but all empty rects?" );
731                 aSize.setWidth( maDropDownRect.Left() );
732             }
733             else
734                 aSize.setWidth( maUpperRect.Left() );
735         }
736     }
737 
738     if (!bSubEditPositioned)
739     {
740         // this moves our sub edit if RTL gets switched
741         mpEdit->SetPosPixel(Point());
742     }
743     mpEdit->SetSizePixel(aSize);
744 
745     if (GetStyle() & WB_SPIN)
746         Invalidate(tools::Rectangle(maUpperRect.TopLeft(), maLowerRect.BottomRight()));
747     if (GetStyle() & WB_DROPDOWN)
748         Invalidate(maDropDownRect);
749 }
750 
StateChanged(StateChangedType nType)751 void SpinField::StateChanged(StateChangedType nType)
752 {
753     Edit::StateChanged(nType);
754 
755     if (nType == StateChangedType::Enable)
756     {
757         if (mbSpin || (GetStyle() & WB_DROPDOWN))
758         {
759             mpEdit->Enable(IsEnabled());
760 
761             if (mbSpin)
762             {
763                 Invalidate(maLowerRect);
764                 Invalidate(maUpperRect);
765             }
766             if (GetStyle() & WB_DROPDOWN)
767                 Invalidate(maDropDownRect);
768         }
769     }
770     else if (nType == StateChangedType::Style)
771     {
772         if (GetStyle() & WB_REPEAT)
773             mbRepeat = true;
774         else
775             mbRepeat = false;
776     }
777     else if (nType == StateChangedType::Zoom)
778     {
779         Resize();
780         if (mpEdit)
781             mpEdit->SetZoom(GetZoom());
782         Invalidate();
783     }
784     else if (nType == StateChangedType::ControlFont)
785     {
786         if (mpEdit)
787             mpEdit->SetControlFont(GetControlFont());
788         Invalidate();
789     }
790     else if (nType == StateChangedType::ControlForeground)
791     {
792         if (mpEdit)
793             mpEdit->SetControlForeground(GetControlForeground());
794         Invalidate();
795     }
796     else if (nType == StateChangedType::ControlBackground)
797     {
798         if (mpEdit)
799             mpEdit->SetControlBackground(GetControlBackground());
800         Invalidate();
801     }
802     else if( nType == StateChangedType::Mirroring )
803     {
804         if (mpEdit)
805             mpEdit->CompatStateChanged(StateChangedType::Mirroring);
806         Resize();
807     }
808 }
809 
DataChanged(const DataChangedEvent & rDCEvt)810 void SpinField::DataChanged( const DataChangedEvent& rDCEvt )
811 {
812     Edit::DataChanged(rDCEvt);
813 
814     if ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
815         (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))
816     {
817         Resize();
818         Invalidate();
819     }
820 }
821 
ImplFindPartRect(const Point & rPt)822 tools::Rectangle* SpinField::ImplFindPartRect(const Point& rPt)
823 {
824     if (maUpperRect.IsInside(rPt))
825         return &maUpperRect;
826     else if (maLowerRect.IsInside(rPt))
827         return &maLowerRect;
828     else
829         return nullptr;
830 }
831 
PreNotify(NotifyEvent & rNEvt)832 bool SpinField::PreNotify(NotifyEvent& rNEvt)
833 {
834     if (rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE)
835     {
836         const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
837         if (pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged())
838         {
839             // trigger redraw if mouse over state has changed
840             if( IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire) ||
841                 IsNativeControlSupported(ControlType::Spinbox, ControlPart::AllButtons) )
842             {
843                 tools::Rectangle* pRect = ImplFindPartRect( GetPointerPosPixel() );
844                 tools::Rectangle* pLastRect = ImplFindPartRect( GetLastPointerPosPixel() );
845                 if( pRect != pLastRect || (pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow()) )
846                 {
847                     // FIXME: this is currently only on macOS
848                     // check for other platforms that need similar handling
849                     if (ImplGetSVData()->maNWFData.mbNoFocusRects && IsNativeWidgetEnabled() &&
850                         IsNativeControlSupported(ControlType::Editbox, ControlPart::Entire))
851                     {
852                         ImplInvalidateOutermostBorder(this);
853                     }
854                     else
855                     {
856                         // paint directly
857                         vcl::Region aRgn( GetOutDev()->GetActiveClipRegion() );
858                         if (pLastRect)
859                         {
860                             GetOutDev()->SetClipRegion(vcl::Region(*pLastRect));
861                             Invalidate(*pLastRect);
862                             GetOutDev()->SetClipRegion( aRgn );
863                         }
864                         if (pRect)
865                         {
866                             GetOutDev()->SetClipRegion(vcl::Region(*pRect));
867                             Invalidate(*pRect);
868                             GetOutDev()->SetClipRegion( aRgn );
869                         }
870                     }
871                 }
872             }
873         }
874     }
875 
876     return Edit::PreNotify(rNEvt);
877 }
878 
EndDropDown()879 void SpinField::EndDropDown()
880 {
881     mbInDropDown = false;
882     Invalidate(tools::Rectangle(Point(), GetOutputSizePixel()));
883 }
884 
ShowDropDown(bool)885 bool SpinField::ShowDropDown( bool )
886 {
887     return false;
888 }
889 
CalcMinimumSizeForText(const OUString & rString) const890 Size SpinField::CalcMinimumSizeForText(const OUString &rString) const
891 {
892     Size aSz = Edit::CalcMinimumSizeForText(rString);
893 
894     if ( GetStyle() & WB_DROPDOWN )
895         aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
896     if ( GetStyle() & WB_SPIN )
897     {
898         ImplControlValue aControlValue;
899         tools::Rectangle aArea( Point(), Size(100, aSz.Height()));
900         tools::Rectangle aEntireBound, aEntireContent, aEditBound, aEditContent;
901         if (
902                GetNativeControlRegion(ControlType::Spinbox, ControlPart::Entire,
903                    aArea, ControlState::NONE, aControlValue, aEntireBound, aEntireContent) &&
904                GetNativeControlRegion(ControlType::Spinbox, ControlPart::SubEdit,
905                    aArea, ControlState::NONE, aControlValue, aEditBound, aEditContent)
906            )
907         {
908             aSz.AdjustWidth(aEntireContent.GetWidth() - aEditContent.GetWidth());
909         }
910         else
911         {
912             aSz.AdjustWidth(maUpperRect.GetWidth() );
913         }
914     }
915 
916     return aSz;
917 }
918 
CalcMinimumSize() const919 Size SpinField::CalcMinimumSize() const
920 {
921     return CalcMinimumSizeForText(GetText());
922 }
923 
GetOptimalSize() const924 Size SpinField::GetOptimalSize() const
925 {
926     return CalcMinimumSize();
927 }
928 
CalcSize(sal_Int32 nChars) const929 Size SpinField::CalcSize(sal_Int32 nChars) const
930 {
931     Size aSz = Edit::CalcSize( nChars );
932 
933     if ( GetStyle() & WB_DROPDOWN )
934         aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
935     if ( GetStyle() & WB_SPIN )
936         aSz.AdjustWidth(GetSettings().GetStyleSettings().GetSpinSize() );
937 
938     return aSz;
939 }
940 
IMPL_LINK(SpinField,ImplTimeout,Timer *,pTimer,void)941 IMPL_LINK( SpinField, ImplTimeout, Timer*, pTimer, void )
942 {
943     if ( pTimer->GetTimeout() == static_cast<sal_uInt64>(MouseSettings::GetButtonStartRepeat()) )
944     {
945         pTimer->SetTimeout( GetSettings().GetMouseSettings().GetButtonRepeat() );
946         pTimer->Start();
947     }
948     else
949     {
950         if ( mbInitialUp )
951             Up();
952         else
953             Down();
954     }
955 }
956 
Draw(OutputDevice * pDev,const Point & rPos,DrawFlags nFlags)957 void SpinField::Draw(OutputDevice* pDev, const Point& rPos, DrawFlags nFlags)
958 {
959     Edit::Draw(pDev, rPos, nFlags);
960 
961     WinBits nFieldStyle = GetStyle();
962     if ( (nFlags & DrawFlags::NoControls ) || !( nFieldStyle & (WB_SPIN|WB_DROPDOWN) ) )
963         return;
964 
965     Point aPos = pDev->LogicToPixel( rPos );
966     Size aSize = GetSizePixel();
967     AllSettings aOldSettings = pDev->GetSettings();
968 
969     pDev->Push();
970     pDev->SetMapMode();
971 
972     tools::Rectangle aDD, aUp, aDown;
973     ImplCalcButtonAreas(pDev, aSize, aDD, aUp, aDown);
974     aDD.Move(aPos.X(), aPos.Y());
975     aUp.Move(aPos.X(), aPos.Y());
976     aUp.AdjustTop( 1 );
977     aDown.Move(aPos.X(), aPos.Y());
978 
979     Color aButtonTextColor;
980     if (nFlags & DrawFlags::Mono)
981         aButtonTextColor = COL_BLACK;
982     else
983         aButtonTextColor = GetSettings().GetStyleSettings().GetButtonTextColor();
984 
985     if (GetStyle() & WB_DROPDOWN)
986     {
987         DecorationView aView( pDev );
988         tools::Rectangle aInnerRect = aView.DrawButton( aDD, DrawButtonFlags::NoLightBorder );
989         DrawSymbolFlags nSymbolStyle = IsEnabled() ? DrawSymbolFlags::NONE : DrawSymbolFlags::Disable;
990         aView.DrawSymbol(aInnerRect, SymbolType::SPIN_DOWN, aButtonTextColor, nSymbolStyle);
991     }
992 
993     if (GetStyle() & WB_SPIN)
994     {
995         ImplDrawSpinButton(*pDev, this, aUp, aDown, false, false);
996     }
997 
998     pDev->Pop();
999     pDev->SetSettings(aOldSettings);
1000 
1001 }
1002 
GetUITestFactory() const1003 FactoryFunction SpinField::GetUITestFactory() const
1004 {
1005     return SpinFieldUIObject::create;
1006 }
1007 
1008 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1009