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