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