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 #include <tbzoomsliderctrl.hxx>
20 #include <vcl/event.hxx>
21 #include <vcl/image.hxx>
22 #include <vcl/toolbox.hxx>
23 #include <vcl/virdev.hxx>
24 #include <vcl/gradient.hxx>
25 #include <vcl/settings.hxx>
26 #include <svx/zoomslideritem.hxx>
27 #include <iterator>
28 #include <set>
29 #include <bitmaps.hlst>
30 
31 #include <com/sun/star/frame/XFrame.hpp>
32 #include <com/sun/star/frame/XDispatchProvider.hpp>
33 
34 // class ScZoomSliderControl ---------------------------------------
35 
36 SFX_IMPL_TOOLBOX_CONTROL( ScZoomSliderControl, SvxZoomSliderItem );
37 
ScZoomSliderControl(sal_uInt16 nSlotId,sal_uInt16 nId,ToolBox & rTbx)38 ScZoomSliderControl::ScZoomSliderControl(
39     sal_uInt16     nSlotId,
40     sal_uInt16     nId,
41     ToolBox&   rTbx )
42     :SfxToolBoxControl( nSlotId, nId, rTbx )
43 {
44     rTbx.Invalidate();
45 }
46 
~ScZoomSliderControl()47 ScZoomSliderControl::~ScZoomSliderControl()
48 {
49 
50 }
51 
StateChanged(sal_uInt16,SfxItemState eState,const SfxPoolItem * pState)52 void ScZoomSliderControl::StateChanged( sal_uInt16 /*nSID*/, SfxItemState eState,
53                                        const SfxPoolItem* pState )
54 {
55     sal_uInt16                  nId  = GetId();
56     ToolBox&                rTbx = GetToolBox();
57     ScZoomSliderWnd*        pBox = static_cast<ScZoomSliderWnd*>(rTbx.GetItemWindow( nId ));
58     OSL_ENSURE( pBox ,"Control not found!" );
59 
60     if ( SfxItemState::DEFAULT != eState || pState->IsVoidItem() )
61     {
62         SvxZoomSliderItem aZoomSliderItem( 100 );
63         pBox->Disable();
64         pBox->UpdateFromItem( &aZoomSliderItem );
65     }
66     else
67     {
68         pBox->Enable();
69         OSL_ENSURE( dynamic_cast<const SvxZoomSliderItem*>( pState) !=  nullptr, "invalid item type" );
70         const SvxZoomSliderItem* pZoomSliderItem = dynamic_cast< const SvxZoomSliderItem* >( pState );
71 
72         OSL_ENSURE( pZoomSliderItem, "Sc::ScZoomSliderControl::StateChanged(), wrong item type!" );
73         if( pZoomSliderItem )
74             pBox->UpdateFromItem( pZoomSliderItem );
75     }
76 }
77 
CreateItemWindow(vcl::Window * pParent)78 VclPtr<vcl::Window> ScZoomSliderControl::CreateItemWindow( vcl::Window *pParent )
79 {
80     // #i98000# Don't try to get a value via SfxViewFrame::Current here.
81     // The view's value is always notified via StateChanged later.
82     VclPtrInstance<ScZoomSliderWnd> pSlider( pParent,
83         css::uno::Reference< css::frame::XDispatchProvider >( m_xFrame->getController(),
84         css::uno::UNO_QUERY ), 100 );
85     return pSlider.get();
86 }
87 
88 struct ScZoomSliderWnd::ScZoomSliderWnd_Impl
89 {
90     sal_uInt16                   mnCurrentZoom;
91     sal_uInt16                   mnMinZoom;
92     sal_uInt16                   mnMaxZoom;
93     std::vector< long >      maSnappingPointOffsets;
94     std::vector< sal_uInt16 >    maSnappingPointZooms;
95     Image                    maSliderButton;
96     Image                    maIncreaseButton;
97     Image                    maDecreaseButton;
98     bool                     mbOmitPaint;
99 
ScZoomSliderWnd_ImplScZoomSliderWnd::ScZoomSliderWnd_Impl100     explicit ScZoomSliderWnd_Impl( sal_uInt16 nCurrentZoom ) :
101         mnCurrentZoom( nCurrentZoom ),
102         mnMinZoom( 10 ),
103         mnMaxZoom( 400 ),
104         maSnappingPointOffsets(),
105         maSnappingPointZooms(),
106         maSliderButton(),
107         maIncreaseButton(),
108         maDecreaseButton(),
109         mbOmitPaint( false )
110         {
111         }
112 };
113 
114 static constexpr sal_uInt16 gnSliderCenter(100);
115 
116 const long nButtonWidth     = 10;
117 const long nButtonHeight    = 10;
118 const long nIncDecWidth     = 11;
119 const long nIncDecHeight    = 11;
120 const long nSliderHeight    = 2;
121 const long nSliderWidth     = 4;
122 const long nSnappingHeight  = 4;
123 const long nSliderXOffset   = 20;
124 const long nSnappingEpsilon = 5; // snapping epsilon in pixels
125 const long nSnappingPointsMinDist = nSnappingEpsilon; // minimum distance of two adjacent snapping points
126 
Offset2Zoom(long nOffset) const127 sal_uInt16 ScZoomSliderWnd::Offset2Zoom( long nOffset ) const
128 {
129     Size aSliderWindowSize = GetOutputSizePixel();
130     const long nControlWidth = aSliderWindowSize.Width();
131     sal_uInt16 nRet = 0;
132 
133     if( nOffset < nSliderXOffset )
134         return mpImpl->mnMinZoom;
135     if( nOffset > nControlWidth - nSliderXOffset )
136         return mpImpl->mnMaxZoom;
137 
138     // check for snapping points:
139     auto aSnappingPointIter = std::find_if(mpImpl->maSnappingPointOffsets.begin(), mpImpl->maSnappingPointOffsets.end(),
140         [nOffset](const long nCurrent) { return std::abs(nCurrent - nOffset) < nSnappingEpsilon; });
141     if (aSnappingPointIter != mpImpl->maSnappingPointOffsets.end())
142     {
143         nOffset = *aSnappingPointIter;
144         auto nCount = static_cast<sal_uInt16>(std::distance(mpImpl->maSnappingPointOffsets.begin(), aSnappingPointIter));
145         nRet = mpImpl->maSnappingPointZooms[ nCount ];
146     }
147 
148     if( 0 == nRet )
149     {
150         if( nOffset < nControlWidth / 2 )
151         {
152             // first half of slider
153             const long nFirstHalfRange      = gnSliderCenter - mpImpl->mnMinZoom;
154             const long nHalfSliderWidth     = nControlWidth/2 - nSliderXOffset;
155             const long nZoomPerSliderPixel  = (1000 * nFirstHalfRange) / nHalfSliderWidth;
156             const long nOffsetToSliderLeft  = nOffset - nSliderXOffset;
157             nRet = mpImpl->mnMinZoom + sal_uInt16( nOffsetToSliderLeft * nZoomPerSliderPixel / 1000 );
158         }
159         else
160         {
161             // second half of slider
162             const long nSecondHalfRange         = mpImpl->mnMaxZoom - gnSliderCenter;
163             const long nHalfSliderWidth         = nControlWidth/2 - nSliderXOffset;
164             const long nZoomPerSliderPixel      = 1000 * nSecondHalfRange / nHalfSliderWidth;
165             const long nOffsetToSliderCenter    = nOffset - nControlWidth/2;
166             nRet = gnSliderCenter + sal_uInt16( nOffsetToSliderCenter * nZoomPerSliderPixel / 1000 );
167         }
168     }
169 
170     if( nRet < mpImpl->mnMinZoom )
171         return mpImpl->mnMinZoom;
172 
173     else if( nRet > mpImpl->mnMaxZoom )
174         return mpImpl->mnMaxZoom;
175 
176     return nRet;
177 }
178 
Zoom2Offset(sal_uInt16 nCurrentZoom) const179 long ScZoomSliderWnd::Zoom2Offset( sal_uInt16 nCurrentZoom ) const
180 {
181     Size aSliderWindowSize = GetOutputSizePixel();
182     const long nControlWidth = aSliderWindowSize.Width();
183     long  nRect = nSliderXOffset;
184 
185     const long nHalfSliderWidth = nControlWidth/2 - nSliderXOffset;
186     if( nCurrentZoom <= gnSliderCenter )
187     {
188         nCurrentZoom = nCurrentZoom - mpImpl->mnMinZoom;
189         const long nFirstHalfRange = gnSliderCenter - mpImpl->mnMinZoom;
190         const long nSliderPixelPerZoomPercent = 1000 * nHalfSliderWidth  / nFirstHalfRange;
191         const long nOffset = (nSliderPixelPerZoomPercent * nCurrentZoom) / 1000;
192         nRect += nOffset;
193     }
194     else
195     {
196         nCurrentZoom = nCurrentZoom - gnSliderCenter;
197         const long nSecondHalfRange = mpImpl->mnMaxZoom - gnSliderCenter;
198         const long nSliderPixelPerZoomPercent = 1000 * nHalfSliderWidth  / nSecondHalfRange;
199         const long nOffset = (nSliderPixelPerZoomPercent * nCurrentZoom) / 1000;
200         nRect += nHalfSliderWidth + nOffset;
201     }
202     return nRect;
203 }
204 
ScZoomSliderWnd(vcl::Window * pParent,const css::uno::Reference<css::frame::XDispatchProvider> & rDispatchProvider,sal_uInt16 nCurrentZoom)205 ScZoomSliderWnd::ScZoomSliderWnd( vcl::Window* pParent,
206                 const css::uno::Reference< css::frame::XDispatchProvider >& rDispatchProvider,
207                 sal_uInt16 nCurrentZoom ):
208                 Window( pParent ),
209                 mpImpl( new ScZoomSliderWnd_Impl( nCurrentZoom ) ),
210                 aLogicalSize( 115, 40 ),
211                 m_xDispatchProvider( rDispatchProvider )
212 {
213     mpImpl->maSliderButton      = Image(StockImage::Yes, RID_SVXBMP_SLIDERBUTTON);
214     mpImpl->maIncreaseButton    = Image(StockImage::Yes, RID_SVXBMP_SLIDERINCREASE);
215     mpImpl->maDecreaseButton    = Image(StockImage::Yes, RID_SVXBMP_SLIDERDECREASE);
216     Size  aSliderSize           = LogicToPixel( aLogicalSize, MapMode( MapUnit::Map10thMM ) );
217     SetSizePixel( Size( aSliderSize.Width() * nSliderWidth-1, aSliderSize.Height() + nSliderHeight ) );
218 }
219 
~ScZoomSliderWnd()220 ScZoomSliderWnd::~ScZoomSliderWnd()
221 {
222     disposeOnce();
223 }
224 
dispose()225 void ScZoomSliderWnd::dispose()
226 {
227     mpImpl.reset();
228     vcl::Window::dispose();
229 }
230 
MouseButtonDown(const MouseEvent & rMEvt)231 void ScZoomSliderWnd::MouseButtonDown( const MouseEvent& rMEvt )
232 {
233     Size aSliderWindowSize = GetOutputSizePixel();
234 
235     const Point aPoint = rMEvt.GetPosPixel();
236 
237     const long nButtonLeftOffset    = ( nSliderXOffset - nIncDecWidth )/2;
238     const long nButtonRightOffset   = ( nSliderXOffset + nIncDecWidth )/2;
239 
240     const long nOldZoom = mpImpl->mnCurrentZoom;
241 
242     // click to - button
243     if ( aPoint.X() >= nButtonLeftOffset && aPoint.X() <= nButtonRightOffset )
244     {
245         mpImpl->mnCurrentZoom = mpImpl->mnCurrentZoom - 5;
246     }
247     // click to + button
248     else if ( aPoint.X() >= aSliderWindowSize.Width() - nSliderXOffset + nButtonLeftOffset &&
249               aPoint.X() <= aSliderWindowSize.Width() - nSliderXOffset + nButtonRightOffset )
250     {
251         mpImpl->mnCurrentZoom = mpImpl->mnCurrentZoom + 5;
252     }
253     else if( aPoint.X() >= nSliderXOffset && aPoint.X() <= aSliderWindowSize.Width() - nSliderXOffset )
254     {
255         mpImpl->mnCurrentZoom = Offset2Zoom( aPoint.X() );
256     }
257 
258     if( mpImpl->mnCurrentZoom < mpImpl->mnMinZoom )
259         mpImpl->mnCurrentZoom = mpImpl->mnMinZoom;
260     else if( mpImpl->mnCurrentZoom > mpImpl->mnMaxZoom )
261         mpImpl->mnCurrentZoom = mpImpl->mnMaxZoom;
262 
263     if( nOldZoom == mpImpl->mnCurrentZoom )
264         return ;
265 
266     tools::Rectangle aRect( Point( 0, 0 ), aSliderWindowSize );
267 
268     Invalidate(aRect);
269     mpImpl->mbOmitPaint = true;
270 
271     SvxZoomSliderItem   aZoomSliderItem( mpImpl->mnCurrentZoom );
272 
273     css::uno::Any  a;
274     aZoomSliderItem.QueryValue( a );
275 
276     css::uno::Sequence< css::beans::PropertyValue > aArgs( 1 );
277     aArgs[0].Name = "ScalingFactor";
278     aArgs[0].Value = a;
279 
280     SfxToolBoxControl::Dispatch( m_xDispatchProvider, ".uno:ScalingFactor", aArgs );
281 
282     mpImpl->mbOmitPaint = false;
283 }
284 
MouseMove(const MouseEvent & rMEvt)285 void ScZoomSliderWnd::MouseMove( const MouseEvent& rMEvt )
286 {
287     Size aSliderWindowSize   = GetOutputSizePixel();
288     const long nControlWidth = aSliderWindowSize.Width();
289     const short nButtons     = rMEvt.GetButtons();
290 
291     // check mouse move with button pressed
292     if ( 1 == nButtons )
293     {
294         const Point aPoint = rMEvt.GetPosPixel();
295 
296         if ( aPoint.X() >= nSliderXOffset && aPoint.X() <= nControlWidth - nSliderXOffset )
297         {
298             mpImpl->mnCurrentZoom = Offset2Zoom( aPoint.X() );
299 
300             tools::Rectangle aRect(Point(0, 0), aSliderWindowSize);
301             Invalidate(aRect);
302 
303             mpImpl->mbOmitPaint = true; // optimization: paint before executing command,
304 
305             // commit state change
306             SvxZoomSliderItem aZoomSliderItem( mpImpl->mnCurrentZoom );
307 
308             css::uno::Any a;
309             aZoomSliderItem.QueryValue( a );
310 
311             css::uno::Sequence< css::beans::PropertyValue > aArgs( 1 );
312             aArgs[0].Name = "ScalingFactor";
313             aArgs[0].Value = a;
314 
315             SfxToolBoxControl::Dispatch( m_xDispatchProvider, ".uno:ScalingFactor", aArgs );
316 
317             mpImpl->mbOmitPaint = false;
318         }
319     }
320 }
321 
UpdateFromItem(const SvxZoomSliderItem * pZoomSliderItem)322 void ScZoomSliderWnd::UpdateFromItem( const SvxZoomSliderItem* pZoomSliderItem )
323 {
324     if( pZoomSliderItem )
325     {
326         mpImpl->mnCurrentZoom = pZoomSliderItem->GetValue();
327         mpImpl->mnMinZoom     = pZoomSliderItem->GetMinZoom();
328         mpImpl->mnMaxZoom     = pZoomSliderItem->GetMaxZoom();
329 
330         OSL_ENSURE( mpImpl->mnMinZoom <= mpImpl->mnCurrentZoom &&
331             mpImpl->mnMinZoom <  gnSliderCenter &&
332             mpImpl->mnMaxZoom >= mpImpl->mnCurrentZoom &&
333             mpImpl->mnMaxZoom > gnSliderCenter,
334             "Looks like the zoom slider item is corrupted" );
335         const css::uno::Sequence < sal_Int32 >& rSnappingPoints = pZoomSliderItem->GetSnappingPoints();
336         mpImpl->maSnappingPointOffsets.clear();
337         mpImpl->maSnappingPointZooms.clear();
338 
339         // get all snapping points:
340         std::set< sal_uInt16 > aTmpSnappingPoints;
341         std::transform(rSnappingPoints.begin(), rSnappingPoints.end(), std::inserter(aTmpSnappingPoints, aTmpSnappingPoints.end()),
342             [](const sal_Int32 nSnappingPoint) -> sal_uInt16 { return static_cast<sal_uInt16>(nSnappingPoint); });
343 
344         // remove snapping points that are too close to each other:
345         long nLastOffset = 0;
346 
347         for ( const sal_uInt16 nCurrent : aTmpSnappingPoints )
348         {
349             const long nCurrentOffset = Zoom2Offset( nCurrent );
350 
351             if ( nCurrentOffset - nLastOffset >= nSnappingPointsMinDist )
352             {
353                 mpImpl->maSnappingPointOffsets.push_back( nCurrentOffset );
354                 mpImpl->maSnappingPointZooms.push_back( nCurrent );
355                 nLastOffset = nCurrentOffset;
356             }
357         }
358     }
359 
360     Size aSliderWindowSize = GetOutputSizePixel();
361     tools::Rectangle aRect(Point(0, 0), aSliderWindowSize);
362 
363     if ( !mpImpl->mbOmitPaint )
364        Invalidate(aRect);
365 }
366 
Paint(vcl::RenderContext & rRenderContext,const tools::Rectangle &)367 void ScZoomSliderWnd::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
368 {
369     DoPaint(rRenderContext);
370 }
371 
DoPaint(vcl::RenderContext & rRenderContext)372 void ScZoomSliderWnd::DoPaint(vcl::RenderContext& rRenderContext)
373 {
374     if (mpImpl->mbOmitPaint)
375         return;
376 
377     Size aSliderWindowSize(GetOutputSizePixel());
378     tools::Rectangle aRect(Point(0, 0), aSliderWindowSize);
379 
380     ScopedVclPtrInstance< VirtualDevice > pVDev(rRenderContext);
381     pVDev->SetOutputSizePixel(aSliderWindowSize);
382 
383     tools::Rectangle aSlider = aRect;
384 
385     aSlider.AdjustTop((aSliderWindowSize.Height() - nSliderHeight) / 2 - 1 );
386     aSlider.SetBottom( aSlider.Top() + nSliderHeight );
387     aSlider.AdjustLeft(nSliderXOffset );
388     aSlider.AdjustRight( -nSliderXOffset );
389 
390     tools::Rectangle aFirstLine(aSlider);
391     aFirstLine.SetBottom( aFirstLine.Top() );
392 
393     tools::Rectangle aSecondLine(aSlider);
394     aSecondLine.SetTop( aSecondLine.Bottom() );
395 
396     tools::Rectangle aLeft(aSlider);
397     aLeft.SetRight( aLeft.Left() );
398 
399     tools::Rectangle aRight(aSlider);
400     aRight.SetLeft( aRight.Right() );
401 
402     // draw VirtualDevice's background color
403     Color aStartColor = rRenderContext.GetSettings().GetStyleSettings().GetFaceColor();
404     Color aEndColor   = rRenderContext.GetSettings().GetStyleSettings().GetFaceColor();
405 
406     if (aEndColor.IsDark())
407         aStartColor = aEndColor;
408 
409     Gradient aGradient;
410     aGradient.SetAngle(0);
411     aGradient.SetStyle(GradientStyle::Linear);
412 
413     aGradient.SetStartColor(aStartColor);
414     aGradient.SetEndColor(aEndColor);
415     pVDev->DrawGradient(aRect, aGradient);
416 
417     // draw slider
418     pVDev->SetLineColor(COL_WHITE);
419     pVDev->DrawRect(aSecondLine);
420     pVDev->DrawRect(aRight);
421 
422     pVDev->SetLineColor(COL_GRAY);
423     pVDev->DrawRect(aFirstLine);
424     pVDev->DrawRect(aLeft);
425 
426     // draw snapping points:
427     for (const auto& rSnappingPointOffset : mpImpl->maSnappingPointOffsets)
428     {
429         pVDev->SetLineColor(COL_GRAY);
430         tools::Rectangle aSnapping(aRect);
431         aSnapping.SetBottom( aSlider.Top() );
432         aSnapping.SetTop( aSnapping.Bottom() - nSnappingHeight );
433         aSnapping.AdjustLeft(rSnappingPointOffset );
434         aSnapping.SetRight( aSnapping.Left() );
435         pVDev->DrawRect(aSnapping);
436 
437         aSnapping.AdjustTop(nSnappingHeight + nSliderHeight );
438         aSnapping.AdjustBottom(nSnappingHeight + nSliderHeight );
439         pVDev->DrawRect(aSnapping);
440     }
441 
442     // draw slider button
443     Point aImagePoint = aRect.TopLeft();
444     aImagePoint.AdjustX(Zoom2Offset(mpImpl->mnCurrentZoom) );
445     aImagePoint.AdjustX( -(nButtonWidth / 2) );
446     aImagePoint.AdjustY( (aSliderWindowSize.Height() - nButtonHeight) / 2 );
447     pVDev->DrawImage(aImagePoint, mpImpl->maSliderButton);
448 
449     // draw decrease button
450     aImagePoint = aRect.TopLeft();
451     aImagePoint.AdjustX((nSliderXOffset - nIncDecWidth) / 2 );
452     aImagePoint.AdjustY((aSliderWindowSize.Height() - nIncDecHeight) / 2 );
453     pVDev->DrawImage(aImagePoint, mpImpl->maDecreaseButton);
454 
455     // draw increase button
456     aImagePoint.setX( aRect.TopLeft().X() + aSliderWindowSize.Width() - nIncDecWidth - (nSliderXOffset - nIncDecWidth) / 2 );
457     pVDev->DrawImage(aImagePoint, mpImpl->maIncreaseButton);
458 
459     rRenderContext.DrawOutDev(Point(0, 0), aSliderWindowSize, Point(0, 0), aSliderWindowSize, *pVDev);
460 }
461 
462 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
463