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