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 <svx/zoomsliderctrl.hxx>
21 #include <vcl/status.hxx>
22 #include <vcl/image.hxx>
23 #include <vcl/svapp.hxx>
24 #include <vcl/settings.hxx>
25 #include <vcl/event.hxx>
26 #include <svx/zoomslideritem.hxx>
27 #include <svx/dialmgr.hxx>
28 #include <svx/strings.hrc>
29 #include <basegfx/utils/zoomtools.hxx>
30 #include <bitmaps.hlst>
31 #include <com/sun/star/beans/PropertyValue.hpp>
32
33 #include <set>
34
35 SFX_IMPL_STATUSBAR_CONTROL( SvxZoomSliderControl, SvxZoomSliderItem );
36
37 struct SvxZoomSliderControl::SvxZoomSliderControl_Impl
38 {
39 sal_uInt16 mnCurrentZoom;
40 sal_uInt16 mnMinZoom;
41 sal_uInt16 mnMaxZoom;
42 sal_uInt16 mnSliderCenter;
43 std::vector< tools::Long > maSnappingPointOffsets;
44 std::vector< sal_uInt16 > maSnappingPointZooms;
45 Image maSliderButton;
46 Image maIncreaseButton;
47 Image maDecreaseButton;
48 bool mbValuesSet;
49 bool mbDraggingStarted;
50
SvxZoomSliderControl_ImplSvxZoomSliderControl::SvxZoomSliderControl_Impl51 SvxZoomSliderControl_Impl() :
52 mnCurrentZoom( 0 ),
53 mnMinZoom( 0 ),
54 mnMaxZoom( 0 ),
55 mnSliderCenter( 0 ),
56 maSnappingPointOffsets(),
57 maSnappingPointZooms(),
58 maSliderButton(),
59 maIncreaseButton(),
60 maDecreaseButton(),
61 mbValuesSet( false ),
62 mbDraggingStarted( false ) {}
63 };
64
65 const tools::Long nSliderXOffset = 20;
66 const tools::Long nSnappingEpsilon = 5; // snapping epsilon in pixels
67 const tools::Long nSnappingPointsMinDist = nSnappingEpsilon; // minimum distance of two adjacent snapping points
68
69 // nOffset refers to the origin of the control:
70 // + ----------- -
Offset2Zoom(tools::Long nOffset) const71 sal_uInt16 SvxZoomSliderControl::Offset2Zoom( tools::Long nOffset ) const
72 {
73 const tools::Long nControlWidth = getControlRect().GetWidth();
74 sal_uInt16 nRet = 0;
75
76 if ( nOffset < nSliderXOffset )
77 return mxImpl->mnMinZoom;
78
79 if ( nOffset > nControlWidth - nSliderXOffset )
80 return mxImpl->mnMaxZoom;
81
82 // check for snapping points:
83 sal_uInt16 nCount = 0;
84 for ( const tools::Long nCurrent : mxImpl->maSnappingPointOffsets )
85 {
86 if ( std::abs(nCurrent - nOffset) < nSnappingEpsilon )
87 {
88 nOffset = nCurrent;
89 nRet = mxImpl->maSnappingPointZooms[ nCount ];
90 break;
91 }
92 ++nCount;
93 }
94
95 if ( 0 == nRet )
96 {
97 if ( nOffset < nControlWidth / 2 )
98 {
99 // first half of slider
100 const tools::Long nFirstHalfRange = mxImpl->mnSliderCenter - mxImpl->mnMinZoom;
101 const tools::Long nHalfSliderWidth = nControlWidth/2 - nSliderXOffset;
102 const tools::Long nZoomPerSliderPixel = (1000 * nFirstHalfRange) / nHalfSliderWidth;
103 const tools::Long nOffsetToSliderLeft = nOffset - nSliderXOffset;
104 nRet = mxImpl->mnMinZoom + sal_uInt16( nOffsetToSliderLeft * nZoomPerSliderPixel / 1000 );
105 }
106 else
107 {
108 // second half of slider
109 const tools::Long nSecondHalfRange = mxImpl->mnMaxZoom - mxImpl->mnSliderCenter;
110 const tools::Long nHalfSliderWidth = nControlWidth/2 - nSliderXOffset;
111 const tools::Long nZoomPerSliderPixel = 1000 * nSecondHalfRange / nHalfSliderWidth;
112 const tools::Long nOffsetToSliderCenter = nOffset - nControlWidth/2;
113 nRet = mxImpl->mnSliderCenter + sal_uInt16( nOffsetToSliderCenter * nZoomPerSliderPixel / 1000 );
114 }
115 }
116
117 if ( nRet < mxImpl->mnMinZoom )
118 nRet = mxImpl->mnMinZoom;
119 else if ( nRet > mxImpl->mnMaxZoom )
120 nRet = mxImpl->mnMaxZoom;
121
122 return nRet;
123 }
124
125 // returns the offset to the left control border
Zoom2Offset(sal_uInt16 nCurrentZoom) const126 tools::Long SvxZoomSliderControl::Zoom2Offset( sal_uInt16 nCurrentZoom ) const
127 {
128 const tools::Long nControlWidth = getControlRect().GetWidth();
129 tools::Long nRet = nSliderXOffset;
130
131 const tools::Long nHalfSliderWidth = nControlWidth/2 - nSliderXOffset;
132
133 if ( nCurrentZoom <= mxImpl->mnSliderCenter )
134 {
135 nCurrentZoom = nCurrentZoom - mxImpl->mnMinZoom;
136 const tools::Long nFirstHalfRange = mxImpl->mnSliderCenter - mxImpl->mnMinZoom;
137 const tools::Long nSliderPixelPerZoomPercent = 1000 * nHalfSliderWidth / nFirstHalfRange;
138 const tools::Long nOffset = (nSliderPixelPerZoomPercent * nCurrentZoom) / 1000;
139 nRet += nOffset;
140 }
141 else
142 {
143 nCurrentZoom = nCurrentZoom - mxImpl->mnSliderCenter;
144 const tools::Long nSecondHalfRange = mxImpl->mnMaxZoom - mxImpl->mnSliderCenter;
145 const tools::Long nSliderPixelPerZoomPercent = 1000 * nHalfSliderWidth / nSecondHalfRange;
146 const tools::Long nOffset = (nSliderPixelPerZoomPercent * nCurrentZoom) / 1000;
147 nRet += nHalfSliderWidth + nOffset;
148 }
149
150 return nRet;
151 }
152
SvxZoomSliderControl(sal_uInt16 _nSlotId,sal_uInt16 _nId,StatusBar & rStatusBar)153 SvxZoomSliderControl::SvxZoomSliderControl( sal_uInt16 _nSlotId, sal_uInt16 _nId, StatusBar& rStatusBar ) :
154 SfxStatusBarControl( _nSlotId, _nId, rStatusBar ),
155 mxImpl( new SvxZoomSliderControl_Impl )
156 {
157 mxImpl->maSliderButton = Image(StockImage::Yes, RID_SVXBMP_SLIDERBUTTON);
158 mxImpl->maIncreaseButton = Image(StockImage::Yes, RID_SVXBMP_SLIDERINCREASE);
159 mxImpl->maDecreaseButton = Image(StockImage::Yes, RID_SVXBMP_SLIDERDECREASE);
160 }
161
~SvxZoomSliderControl()162 SvxZoomSliderControl::~SvxZoomSliderControl()
163 {
164 }
165
StateChanged(sal_uInt16,SfxItemState eState,const SfxPoolItem * pState)166 void SvxZoomSliderControl::StateChanged( sal_uInt16 /*nSID*/, SfxItemState eState, const SfxPoolItem* pState )
167 {
168 if ( (SfxItemState::DEFAULT != eState) || pState->IsVoidItem() )
169 {
170 GetStatusBar().SetItemText( GetId(), "" );
171 mxImpl->mbValuesSet = false;
172 }
173 else
174 {
175 OSL_ENSURE( dynamic_cast<const SvxZoomSliderItem*>( pState) != nullptr, "invalid item type: should be a SvxZoomSliderItem" );
176 mxImpl->mnCurrentZoom = static_cast<const SvxZoomSliderItem*>( pState )->GetValue();
177 mxImpl->mnMinZoom = static_cast<const SvxZoomSliderItem*>( pState )->GetMinZoom();
178 mxImpl->mnMaxZoom = static_cast<const SvxZoomSliderItem*>( pState )->GetMaxZoom();
179 mxImpl->mnSliderCenter= 100;
180 mxImpl->mbValuesSet = true;
181
182 if ( mxImpl->mnSliderCenter == mxImpl->mnMaxZoom )
183 mxImpl->mnSliderCenter = mxImpl->mnMinZoom + static_cast<sal_uInt16>((mxImpl->mnMaxZoom - mxImpl->mnMinZoom) * 0.5);
184
185
186 DBG_ASSERT( mxImpl->mnMinZoom <= mxImpl->mnCurrentZoom &&
187 mxImpl->mnMinZoom < mxImpl->mnSliderCenter &&
188 mxImpl->mnMaxZoom >= mxImpl->mnCurrentZoom &&
189 mxImpl->mnMaxZoom > mxImpl->mnSliderCenter,
190 "Looks like the zoom slider item is corrupted" );
191
192 const css::uno::Sequence < sal_Int32 > rSnappingPoints = static_cast<const SvxZoomSliderItem*>( pState )->GetSnappingPoints();
193 mxImpl->maSnappingPointOffsets.clear();
194 mxImpl->maSnappingPointZooms.clear();
195
196 // get all snapping points:
197 std::set< sal_uInt16 > aTmpSnappingPoints;
198 for ( const sal_Int32 nSnappingPoint : rSnappingPoints )
199 {
200 aTmpSnappingPoints.insert( static_cast<sal_uInt16>(nSnappingPoint) );
201 }
202
203 // remove snapping points that are too close to each other:
204 tools::Long nLastOffset = 0;
205
206 for ( const sal_uInt16 nCurrent : aTmpSnappingPoints )
207 {
208 const tools::Long nCurrentOffset = Zoom2Offset( nCurrent );
209
210 if ( nCurrentOffset - nLastOffset >= nSnappingPointsMinDist )
211 {
212 mxImpl->maSnappingPointOffsets.push_back( nCurrentOffset );
213 mxImpl->maSnappingPointZooms.push_back( nCurrent );
214 nLastOffset = nCurrentOffset;
215 }
216 }
217 }
218
219 forceRepaint();
220 }
221
Paint(const UserDrawEvent & rUsrEvt)222 void SvxZoomSliderControl::Paint( const UserDrawEvent& rUsrEvt )
223 {
224 if ( !mxImpl->mbValuesSet )
225 return;
226
227 const tools::Rectangle aControlRect = getControlRect();
228 vcl::RenderContext* pDev = rUsrEvt.GetRenderContext();
229 tools::Rectangle aRect = rUsrEvt.GetRect();
230 tools::Rectangle aSlider = aRect;
231
232 tools::Long nSliderHeight = 1 * pDev->GetDPIScaleFactor();
233 tools::Long nSnappingHeight = 2 * pDev->GetDPIScaleFactor();
234
235 aSlider.AdjustTop((aControlRect.GetHeight() - nSliderHeight)/2 );
236 aSlider.SetBottom( aSlider.Top() + nSliderHeight - 1 );
237 aSlider.AdjustLeft(nSliderXOffset );
238 aSlider.AdjustRight( -nSliderXOffset );
239
240 Color aOldLineColor = pDev->GetLineColor();
241 Color aOldFillColor = pDev->GetFillColor();
242
243 const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
244 pDev->SetLineColor( rStyleSettings.GetDarkShadowColor() );
245 pDev->SetFillColor( rStyleSettings.GetDarkShadowColor() );
246
247 // draw slider
248 pDev->DrawRect( aSlider );
249 // shadow
250 pDev->SetLineColor( rStyleSettings.GetShadowColor() );
251 pDev->DrawLine(Point(aSlider.Left()+1,aSlider.Bottom()+1), Point(aSlider.Right()+1,aSlider.Bottom()+1));
252 pDev->SetLineColor( rStyleSettings.GetDarkShadowColor() );
253
254 // draw snapping points:
255 for ( const auto& rSnappingPoint : mxImpl->maSnappingPointOffsets )
256 {
257 tools::Long nSnapPosX = aRect.Left() + rSnappingPoint;
258
259 pDev->DrawRect( tools::Rectangle( nSnapPosX - 1, aSlider.Top() - nSnappingHeight,
260 nSnapPosX, aSlider.Bottom() + nSnappingHeight ) );
261 }
262
263 // draw slider button
264 Point aImagePoint = aRect.TopLeft();
265 aImagePoint.AdjustX(Zoom2Offset( mxImpl->mnCurrentZoom ) );
266 aImagePoint.AdjustX( -(mxImpl->maSliderButton.GetSizePixel().Width()/2) );
267 aImagePoint.AdjustY((aControlRect.GetHeight() - mxImpl->maSliderButton.GetSizePixel().Height())/2 );
268 pDev->DrawImage( aImagePoint, mxImpl->maSliderButton );
269
270 // draw decrease button
271 aImagePoint = aRect.TopLeft();
272 aImagePoint.AdjustX((nSliderXOffset - mxImpl->maDecreaseButton.GetSizePixel().Width())/2 );
273 aImagePoint.AdjustY((aControlRect.GetHeight() - mxImpl->maDecreaseButton.GetSizePixel().Height())/2 );
274 pDev->DrawImage( aImagePoint, mxImpl->maDecreaseButton );
275
276 // draw increase button
277 aImagePoint.setX( aRect.Left() + aControlRect.GetWidth() - mxImpl->maIncreaseButton.GetSizePixel().Width() - (nSliderXOffset - mxImpl->maIncreaseButton.GetSizePixel().Height())/2 );
278 pDev->DrawImage( aImagePoint, mxImpl->maIncreaseButton );
279
280 pDev->SetLineColor( aOldLineColor );
281 pDev->SetFillColor( aOldFillColor );
282 }
283
MouseButtonDown(const MouseEvent & rEvt)284 bool SvxZoomSliderControl::MouseButtonDown( const MouseEvent & rEvt )
285 {
286 if ( !mxImpl->mbValuesSet )
287 return true;
288
289 const tools::Rectangle aControlRect = getControlRect();
290 const Point aPoint = rEvt.GetPosPixel();
291 const sal_Int32 nXDiff = aPoint.X() - aControlRect.Left();
292
293 tools::Long nIncDecWidth = mxImpl->maIncreaseButton.GetSizePixel().Width();
294
295 const tools::Long nButtonLeftOffset = (nSliderXOffset - nIncDecWidth)/2;
296 const tools::Long nButtonRightOffset = (nSliderXOffset + nIncDecWidth)/2;
297
298 const tools::Long nOldZoom = mxImpl->mnCurrentZoom;
299
300 // click to - button
301 if ( nXDiff >= nButtonLeftOffset && nXDiff <= nButtonRightOffset )
302 mxImpl->mnCurrentZoom = basegfx::zoomtools::zoomOut( static_cast<int>(mxImpl->mnCurrentZoom) );
303 // click to + button
304 else if ( nXDiff >= aControlRect.GetWidth() - nSliderXOffset + nButtonLeftOffset &&
305 nXDiff <= aControlRect.GetWidth() - nSliderXOffset + nButtonRightOffset )
306 mxImpl->mnCurrentZoom = basegfx::zoomtools::zoomIn( static_cast<int>(mxImpl->mnCurrentZoom) );
307 // click to slider
308 else if( nXDiff >= nSliderXOffset && nXDiff <= aControlRect.GetWidth() - nSliderXOffset )
309 {
310 mxImpl->mnCurrentZoom = Offset2Zoom( nXDiff );
311 mxImpl->mbDraggingStarted = true;
312 }
313
314 if ( mxImpl->mnCurrentZoom < mxImpl->mnMinZoom )
315 mxImpl->mnCurrentZoom = mxImpl->mnMinZoom;
316 else if ( mxImpl->mnCurrentZoom > mxImpl->mnMaxZoom )
317 mxImpl->mnCurrentZoom = mxImpl->mnMaxZoom;
318
319 if ( nOldZoom == mxImpl->mnCurrentZoom )
320 return true;
321
322 repaintAndExecute();
323
324 return true;
325 }
326
MouseButtonUp(const MouseEvent &)327 bool SvxZoomSliderControl::MouseButtonUp( const MouseEvent & )
328 {
329 mxImpl->mbDraggingStarted = false;
330 return true;
331 }
332
MouseMove(const MouseEvent & rEvt)333 bool SvxZoomSliderControl::MouseMove( const MouseEvent & rEvt )
334 {
335 if ( !mxImpl->mbValuesSet )
336 return true;
337
338 const short nButtons = rEvt.GetButtons();
339 const tools::Rectangle aControlRect = getControlRect();
340 const Point aPoint = rEvt.GetPosPixel();
341 const sal_Int32 nXDiff = aPoint.X() - aControlRect.Left();
342
343 // check mouse move with button pressed
344 if ( 1 == nButtons && mxImpl->mbDraggingStarted )
345 {
346 if ( nXDiff >= nSliderXOffset && nXDiff <= aControlRect.GetWidth() - nSliderXOffset )
347 {
348 mxImpl->mnCurrentZoom = Offset2Zoom( nXDiff );
349
350 repaintAndExecute();
351 }
352 }
353
354 // Tooltips
355
356 tools::Long nIncDecWidth = mxImpl->maIncreaseButton.GetSizePixel().Width();
357
358 const tools::Long nButtonLeftOffset = (nSliderXOffset - nIncDecWidth)/2;
359 const tools::Long nButtonRightOffset = (nSliderXOffset + nIncDecWidth)/2;
360
361 // click to - button
362 if ( nXDiff >= nButtonLeftOffset && nXDiff <= nButtonRightOffset )
363 GetStatusBar().SetQuickHelpText(GetId(), SvxResId(RID_SVXSTR_ZOOM_OUT));
364 // click to + button
365 else if ( nXDiff >= aControlRect.GetWidth() - nSliderXOffset + nButtonLeftOffset &&
366 nXDiff <= aControlRect.GetWidth() - nSliderXOffset + nButtonRightOffset )
367 GetStatusBar().SetQuickHelpText(GetId(), SvxResId(RID_SVXSTR_ZOOM_IN));
368 else
369 // don't hide the slider and its handle with a tooltip during zooming
370 GetStatusBar().SetQuickHelpText(GetId(), "");
371
372 return true;
373 }
374
forceRepaint() const375 void SvxZoomSliderControl::forceRepaint() const
376 {
377 GetStatusBar().SetItemData(GetId(), nullptr);
378 }
379
repaintAndExecute()380 void SvxZoomSliderControl::repaintAndExecute()
381 {
382 forceRepaint();
383
384 // commit state change
385 SvxZoomSliderItem aZoomSliderItem(mxImpl->mnCurrentZoom);
386
387 css::uno::Any any;
388 aZoomSliderItem.QueryValue(any);
389
390 css::uno::Sequence<css::beans::PropertyValue> aArgs(1);
391 aArgs[0].Name = "ZoomSlider";
392 aArgs[0].Value = any;
393
394 execute(aArgs);
395 }
396
397 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
398