1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/config.h>
21 #include <o3tl/sprintf.hxx>
22 #include <osl/thread.h>
23 #include <rtl/math.hxx>
25 #include <bitmaps.hlst>
29 #include <vcl/bitmapex.hxx>
30 #include <vcl/customweld.hxx>
31 #include <vcl/event.hxx>
32 #include <vcl/settings.hxx>
33 #include <vcl/svapp.hxx>
39 class GridWindow
: public weld::CustomWidgetController
41 // helper class for handles
48 impHandle(const Point
& rPos
, sal_uInt16 nX
, sal_uInt16 nY
)
49 : maPos(rPos
), mnOffX(nX
), mnOffY(nY
)
53 bool operator<(const impHandle
& rComp
) const
55 return (maPos
.X() < rComp
.maPos
.X());
58 void draw(vcl::RenderContext
& rRenderContext
, const BitmapEx
& rBitmapEx
)
60 const Point
aOffset(rRenderContext
.PixelToLogic(Point(mnOffX
, mnOffY
)));
61 rRenderContext
.DrawBitmapEx(maPos
- aOffset
, rBitmapEx
);
64 bool isHit(OutputDevice
const & rWin
, const Point
& rPos
)
66 const Point
aOffset(rWin
.PixelToLogic(Point(mnOffX
, mnOffY
)));
67 const tools::Rectangle
aTarget(maPos
- aOffset
, maPos
+ aOffset
);
68 return aTarget
.Contains(rPos
);
72 tools::Rectangle m_aGridArea
;
85 double* m_pOrigYValues
;
87 std::unique_ptr
<double[]> m_pNewYValues
;
95 using Handles
= std::vector
<impHandle
>;
96 static constexpr auto npos
= std::numeric_limits
<Handles::size_type
>::max();
98 Handles::size_type m_nDragIndex
;
100 BitmapEx m_aMarkerBitmap
;
102 Point
transform( double x
, double y
);
103 void transform( const Point
& rOriginal
, double& x
, double& y
);
110 void drawGrid(vcl::RenderContext
& rRenderContext
);
111 void drawOriginal(vcl::RenderContext
& rRenderContext
);
112 void drawNew(vcl::RenderContext
& rRenderContext
);
113 void drawHandles(vcl::RenderContext
& rRenderContext
);
115 void computeExtremes();
116 static void computeChunk( double fMin
, double fMax
, double& fChunkOut
, double& fMinChunkOut
);
118 static double interpolate( double x
, double const * pNodeX
, double const * pNodeY
, int nNodes
);
120 virtual bool MouseMove( const MouseEvent
& ) override
;
121 virtual bool MouseButtonDown( const MouseEvent
& ) override
;
122 virtual bool MouseButtonUp( const MouseEvent
& ) override
;
124 virtual void Resize() override
;
125 virtual void SetDrawingArea(weld::DrawingArea
* pDrawingArea
) override
;
126 void drawLine(vcl::RenderContext
& rRenderContext
, double x1
, double y1
, double x2
, double y2
);
129 void Init(double* pXValues
, double* pYValues
, int nValues
, bool bCutValues
, const BitmapEx
&rMarkerBitmap
);
130 virtual ~GridWindow() override
;
132 void setBoundings( double fMinX
, double fMinY
, double fMaxX
, double fMaxY
);
134 double* getNewYValues() { return m_pNewYValues
.get(); }
136 void ChangeMode(ResetType nType
);
138 virtual void Paint( vcl::RenderContext
& /*rRenderContext*/, const tools::Rectangle
& rRect
) override
;
141 GridWindow::GridWindow()
142 : m_aGridArea(50, 15, 100, 100)
151 , m_pXValues(nullptr)
152 , m_pOrigYValues(nullptr)
156 , m_bCutValues(false)
161 void GridWindow::Init(double* pXValues
, double* pYValues
, int nValues
, bool bCutValues
, const BitmapEx
&rMarkerBitmap
)
163 m_aMarkerBitmap
= rMarkerBitmap
;
164 m_pXValues
= pXValues
;
165 m_pOrigYValues
= pYValues
;
167 m_bCutValues
= bCutValues
;
171 if (m_pOrigYValues
&& m_nValues
)
173 m_pNewYValues
.reset(new double[ m_nValues
]);
174 memcpy( m_pNewYValues
.get(), m_pOrigYValues
, sizeof( double ) * m_nValues
);
177 setBoundings( 0, 0, 1023, 1023 );
180 // create left and right marker as first and last entry
181 m_BmOffX
= sal_uInt16(m_aMarkerBitmap
.GetSizePixel().Width() >> 1);
182 m_BmOffY
= sal_uInt16(m_aMarkerBitmap
.GetSizePixel().Height() >> 1);
183 m_aHandles
.push_back(impHandle(transform(findMinX(), findMinY()), m_BmOffX
, m_BmOffY
));
184 m_aHandles
.push_back(impHandle(transform(findMaxX(), findMaxY()), m_BmOffX
, m_BmOffY
));
187 void GridWindow::Resize()
192 void GridWindow::onResize()
194 Size aSize
= GetOutputSizePixel();
195 m_aGridArea
.setWidth( aSize
.Width() - 80 );
196 m_aGridArea
.setHeight( aSize
.Height() - 40 );
199 void GridWindow::SetDrawingArea(weld::DrawingArea
* pDrawingArea
)
201 Size
aSize(pDrawingArea
->get_ref_device().LogicToPixel(Size(240, 200), MapMode(MapUnit::MapAppFont
)));
202 pDrawingArea
->set_size_request(aSize
.Width(), aSize
.Height());
203 CustomWidgetController::SetDrawingArea(pDrawingArea
);
204 SetOutputSizePixel(aSize
);
207 GridDialog::GridDialog(weld::Window
* pParent
, double* pXValues
, double* pYValues
, int nValues
)
208 : GenericDialogController(pParent
, u
"modules/scanner/ui/griddialog.ui"_ustr
, u
"GridDialog"_ustr
)
209 , m_xResetTypeBox(m_xBuilder
->weld_combo_box(u
"resetTypeCombobox"_ustr
))
210 , m_xResetButton(m_xBuilder
->weld_button(u
"resetButton"_ustr
))
211 , m_xGridWindow(new GridWindow
)
212 , m_xGridWindowWND(new weld::CustomWeld(*m_xBuilder
, u
"gridwindow"_ustr
, *m_xGridWindow
))
214 m_xGridWindow
->Init(pXValues
, pYValues
, nValues
, true/*bCutValues*/, BitmapEx(RID_SCANNER_HANDLE
));
215 m_xResetTypeBox
->set_active(0);
216 m_xResetButton
->connect_clicked( LINK( this, GridDialog
, ClickButtonHdl
) );
219 GridDialog::~GridDialog()
223 GridWindow::~GridWindow()
225 m_pNewYValues
.reset();
228 double GridWindow::findMinX()
232 double fMin
= m_pXValues
[0];
233 for( int i
= 1; i
< m_nValues
; i
++ )
234 if( m_pXValues
[ i
] < fMin
)
235 fMin
= m_pXValues
[ i
];
239 double GridWindow::findMinY()
241 if( ! m_pNewYValues
)
243 double fMin
= m_pNewYValues
[0];
244 for( int i
= 1; i
< m_nValues
; i
++ )
245 if( m_pNewYValues
[ i
] < fMin
)
246 fMin
= m_pNewYValues
[ i
];
251 double GridWindow::findMaxX()
255 double fMax
= m_pXValues
[0];
256 for( int i
= 1; i
< m_nValues
; i
++ )
257 if( m_pXValues
[ i
] > fMax
)
258 fMax
= m_pXValues
[ i
];
263 double GridWindow::findMaxY()
265 if( ! m_pNewYValues
)
267 double fMax
= m_pNewYValues
[0];
268 for( int i
= 1; i
< m_nValues
; i
++ )
269 if( m_pNewYValues
[ i
] > fMax
)
270 fMax
= m_pNewYValues
[ i
];
275 void GridWindow::computeExtremes()
277 if( !(m_nValues
&& m_pXValues
&& m_pOrigYValues
) )
280 m_fMaxX
= m_fMinX
= m_pXValues
[0];
281 m_fMaxY
= m_fMinY
= m_pOrigYValues
[0];
282 for( int i
= 1; i
< m_nValues
; i
++ )
284 if( m_pXValues
[ i
] > m_fMaxX
)
285 m_fMaxX
= m_pXValues
[ i
];
286 else if( m_pXValues
[ i
] < m_fMinX
)
287 m_fMinX
= m_pXValues
[ i
];
288 if( m_pOrigYValues
[ i
] > m_fMaxY
)
289 m_fMaxY
= m_pOrigYValues
[ i
];
290 else if( m_pOrigYValues
[ i
] < m_fMinY
)
291 m_fMinY
= m_pOrigYValues
[ i
];
293 setBoundings( m_fMinX
, m_fMinY
, m_fMaxX
, m_fMaxY
);
297 Point
GridWindow::transform( double x
, double y
)
301 aRet
.setX( static_cast<tools::Long
>( ( x
- m_fMinX
) *
302 static_cast<double>(m_aGridArea
.GetWidth()) / ( m_fMaxX
- m_fMinX
)
303 + m_aGridArea
.Left() ) );
304 aRet
.setY( static_cast<tools::Long
>(
305 m_aGridArea
.Bottom() -
307 static_cast<double>(m_aGridArea
.GetHeight()) / ( m_fMaxY
- m_fMinY
) ) );
311 void GridWindow::transform( const Point
& rOriginal
, double& x
, double& y
)
313 const tools::Long nWidth
= m_aGridArea
.GetWidth();
314 const tools::Long nHeight
= m_aGridArea
.GetHeight();
315 if (!nWidth
|| !nHeight
)
317 x
= ( rOriginal
.X() - m_aGridArea
.Left() ) * (m_fMaxX
- m_fMinX
) / static_cast<double>(nWidth
) + m_fMinX
;
318 y
= ( m_aGridArea
.Bottom() - rOriginal
.Y() ) * (m_fMaxY
- m_fMinY
) / static_cast<double>(nHeight
) + m_fMinY
;
321 void GridWindow::drawLine(vcl::RenderContext
& rRenderContext
, double x1
, double y1
, double x2
, double y2
)
323 rRenderContext
.DrawLine(transform(x1
, y1
), transform(x2
, y2
));
326 void GridWindow::computeChunk( double fMin
, double fMax
, double& fChunkOut
, double& fMinChunkOut
)
328 // get a nice chunk size like 10, 100, 25 or such
329 fChunkOut
= ( fMax
- fMin
) / 6.0;
330 int logchunk
= static_cast<int>(std::log10( fChunkOut
));
331 int nChunk
= static_cast<int>( fChunkOut
/ std::exp( static_cast<double>(logchunk
-1) * M_LN10
) );
334 else if( nChunk
>= 35 )
336 else if ( nChunk
> 20 )
338 else if ( nChunk
>= 13 )
340 else if( nChunk
> 5 )
344 fChunkOut
= static_cast<double>(nChunk
) * exp( static_cast<double>(logchunk
-1) * M_LN10
);
345 // compute whole chunks fitting into fMin
346 nChunk
= static_cast<int>( fMin
/ fChunkOut
);
347 fMinChunkOut
= static_cast<double>(nChunk
) * fChunkOut
;
348 while( fMinChunkOut
< fMin
)
349 fMinChunkOut
+= fChunkOut
;
353 void GridWindow::computeNew()
355 if(2 == m_aHandles
.size())
357 // special case: only left and right markers
359 double xright
, yright
;
360 transform(m_aHandles
[0].maPos
, xleft
, yleft
);
361 transform(m_aHandles
[1].maPos
, xright
, yright
);
362 double factor
= (yright
-yleft
)/(xright
-xleft
);
363 for( int i
= 0; i
< m_nValues
; i
++ )
365 m_pNewYValues
[ i
] = yleft
+ ( m_pXValues
[ i
] - xleft
)*factor
;
371 std::sort(m_aHandles
.begin(), m_aHandles
.end());
372 const int nSorted
= m_aHandles
.size();
376 std::unique_ptr
<double[]> nodex(new double[ nSorted
]);
377 std::unique_ptr
<double[]> nodey(new double[ nSorted
]);
379 for( i
= 0; i
< nSorted
; i
++ )
380 transform( m_aHandles
[i
].maPos
, nodex
[ i
], nodey
[ i
] );
382 for( i
= 0; i
< m_nValues
; i
++ )
384 double x
= m_pXValues
[ i
];
385 m_pNewYValues
[ i
] = interpolate( x
, nodex
.get(), nodey
.get(), nSorted
);
388 if( m_pNewYValues
[ i
] > m_fMaxY
)
389 m_pNewYValues
[ i
] = m_fMaxY
;
390 else if( m_pNewYValues
[ i
] < m_fMinY
)
391 m_pNewYValues
[ i
] = m_fMinY
;
398 double GridWindow::interpolate(
400 double const * pNodeX
,
401 double const * pNodeY
,
404 // compute Lagrange interpolation
406 for( int i
= 0; i
< nNodes
; i
++ )
408 double sum
= pNodeY
[ i
];
409 for( int n
= 0; n
< nNodes
; n
++ )
413 sum
*= x
- pNodeX
[ n
];
414 sum
/= pNodeX
[ i
] - pNodeX
[ n
];
422 void GridDialog::setBoundings(double fMinX
, double fMinY
, double fMaxX
, double fMaxY
)
424 m_xGridWindow
->setBoundings(fMinX
, fMinY
, fMaxX
, fMaxY
);
427 void GridWindow::setBoundings(double fMinX
, double fMinY
, double fMaxX
, double fMaxY
)
434 computeChunk( m_fMinX
, m_fMaxX
, m_fChunkX
, m_fMinChunkX
);
435 computeChunk( m_fMinY
, m_fMaxY
, m_fChunkY
, m_fMinChunkY
);
438 void GridWindow::drawGrid(vcl::RenderContext
& rRenderContext
)
441 rRenderContext
.SetLineColor(COL_BLACK
);
442 // draw vertical lines
443 for (double fX
= m_fMinChunkX
; fX
< m_fMaxX
; fX
+= m_fChunkX
)
445 drawLine(rRenderContext
, fX
, m_fMinY
, fX
, m_fMaxY
);
447 Point aPt
= transform(fX
, m_fMinY
);
448 o3tl::sprintf(pBuf
, "%g", fX
);
449 OUString
aMark(pBuf
, strlen(pBuf
), osl_getThreadTextEncoding());
450 Size
aTextSize(rRenderContext
.GetTextWidth(aMark
), rRenderContext
.GetTextHeight());
451 aPt
.AdjustX( -(aTextSize
.Width() / 2) );
452 aPt
.AdjustY(aTextSize
.Height() / 2 );
453 rRenderContext
.DrawText(aPt
, aMark
);
455 // draw horizontal lines
456 for (double fY
= m_fMinChunkY
; fY
< m_fMaxY
; fY
+= m_fChunkY
)
458 drawLine(rRenderContext
, m_fMinX
, fY
, m_fMaxX
, fY
);
460 Point aPt
= transform(m_fMinX
, fY
);
461 o3tl::sprintf(pBuf
, "%g", fY
);
462 OUString
aMark(pBuf
, strlen(pBuf
), osl_getThreadTextEncoding());
463 Size
aTextSize(rRenderContext
.GetTextWidth(aMark
), rRenderContext
.GetTextHeight());
464 aPt
.AdjustX( -(aTextSize
.Width() + 2) );
465 aPt
.AdjustY( -(aTextSize
.Height() / 2) );
466 rRenderContext
.DrawText(aPt
, aMark
);
470 drawLine(rRenderContext
, m_fMinX
, m_fMinY
, m_fMaxX
, m_fMinY
);
471 drawLine(rRenderContext
, m_fMinX
, m_fMaxY
, m_fMaxX
, m_fMaxY
);
472 drawLine(rRenderContext
, m_fMinX
, m_fMinY
, m_fMinX
, m_fMaxY
);
473 drawLine(rRenderContext
, m_fMaxX
, m_fMinY
, m_fMaxX
, m_fMaxY
);
476 void GridWindow::drawOriginal(vcl::RenderContext
& rRenderContext
)
478 if (m_nValues
&& m_pXValues
&& m_pOrigYValues
)
480 rRenderContext
.SetLineColor(COL_RED
);
481 for (int i
= 0; i
< m_nValues
- 1; i
++)
483 drawLine(rRenderContext
,
484 m_pXValues
[i
], m_pOrigYValues
[i
],
485 m_pXValues
[i
+ 1], m_pOrigYValues
[i
+ 1]);
490 void GridWindow::drawNew(vcl::RenderContext
& rRenderContext
)
492 if (m_nValues
&& m_pXValues
&& m_pNewYValues
)
494 rRenderContext
.SetClipRegion(vcl::Region(m_aGridArea
));
495 rRenderContext
.SetLineColor(COL_YELLOW
);
496 for (int i
= 0; i
< m_nValues
- 1; i
++)
498 drawLine(rRenderContext
,
499 m_pXValues
[i
], m_pNewYValues
[i
],
500 m_pXValues
[i
+ 1], m_pNewYValues
[i
+ 1]);
502 rRenderContext
.SetClipRegion();
506 void GridWindow::drawHandles(vcl::RenderContext
& rRenderContext
)
508 for(impHandle
& rHandle
: m_aHandles
)
510 rHandle
.draw(rRenderContext
, m_aMarkerBitmap
);
514 void GridWindow::Paint(vcl::RenderContext
& rRenderContext
, const tools::Rectangle
&)
516 rRenderContext
.SetBackground(Wallpaper(Application::GetSettings().GetStyleSettings().GetDialogColor()));
517 drawGrid(rRenderContext
);
518 drawOriginal(rRenderContext
);
519 drawNew(rRenderContext
);
520 drawHandles(rRenderContext
);
523 bool GridWindow::MouseMove( const MouseEvent
& rEvt
)
525 if( rEvt
.GetButtons() != MOUSE_LEFT
|| m_nDragIndex
== npos
)
528 Point
aPoint( rEvt
.GetPosPixel() );
530 if( m_nDragIndex
== 0 || m_nDragIndex
== m_aHandles
.size() - 1)
532 aPoint
.setX( m_aHandles
[m_nDragIndex
].maPos
.X() );
536 if(aPoint
.X() < m_aGridArea
.Left())
537 aPoint
.setX( m_aGridArea
.Left() );
538 else if(aPoint
.X() > m_aGridArea
.Right())
539 aPoint
.setX( m_aGridArea
.Right() );
542 if( aPoint
.Y() < m_aGridArea
.Top() )
543 aPoint
.setY( m_aGridArea
.Top() );
544 else if( aPoint
.Y() > m_aGridArea
.Bottom() )
545 aPoint
.setY( m_aGridArea
.Bottom() );
547 if( aPoint
!= m_aHandles
[m_nDragIndex
].maPos
)
549 m_aHandles
[m_nDragIndex
].maPos
= aPoint
;
550 Invalidate( m_aGridArea
);
556 bool GridWindow::MouseButtonUp( const MouseEvent
& rEvt
)
558 if( rEvt
.GetButtons() == MOUSE_LEFT
)
560 if( m_nDragIndex
!= npos
)
564 Invalidate(m_aGridArea
);
571 bool GridWindow::MouseButtonDown( const MouseEvent
& rEvt
)
573 Point
aPoint( rEvt
.GetPosPixel() );
574 Handles::size_type nMarkerIndex
= npos
;
576 for(Handles::size_type
a(0); nMarkerIndex
== npos
&& a
< m_aHandles
.size(); a
++)
578 if(m_aHandles
[a
].isHit(GetDrawingArea()->get_ref_device(), aPoint
))
584 if( rEvt
.GetButtons() == MOUSE_LEFT
)
586 // user wants to drag a button
587 if( nMarkerIndex
!= npos
)
589 m_nDragIndex
= nMarkerIndex
;
592 else if( rEvt
.GetButtons() == MOUSE_RIGHT
)
594 // user wants to add/delete a button
595 if( nMarkerIndex
!= npos
)
597 if( nMarkerIndex
!= 0 && nMarkerIndex
!= m_aHandles
.size() - 1)
599 // delete marker under mouse
600 if( m_nDragIndex
== nMarkerIndex
)
603 m_aHandles
.erase(m_aHandles
.begin() + nMarkerIndex
);
608 m_BmOffX
= sal_uInt16(m_aMarkerBitmap
.GetSizePixel().Width() >> 1);
609 m_BmOffY
= sal_uInt16(m_aMarkerBitmap
.GetSizePixel().Height() >> 1);
610 m_aHandles
.push_back(impHandle(aPoint
, m_BmOffX
, m_BmOffY
));
614 Invalidate(m_aGridArea
);
620 void GridWindow::ChangeMode(ResetType nType
)
624 case ResetType::LINEAR_ASCENDING
:
626 for( int i
= 0; i
< m_nValues
; i
++ )
628 m_pNewYValues
[ i
] = m_fMinY
+ (m_fMaxY
-m_fMinY
)/(m_fMaxX
-m_fMinX
)*(m_pXValues
[i
]-m_fMinX
);
632 case ResetType::LINEAR_DESCENDING
:
634 for( int i
= 0; i
< m_nValues
; i
++ )
636 m_pNewYValues
[ i
] = m_fMaxY
- (m_fMaxY
-m_fMinY
)/(m_fMaxX
-m_fMinX
)*(m_pXValues
[i
]-m_fMinX
);
640 case ResetType::RESET
:
642 if( m_pOrigYValues
&& m_pNewYValues
&& m_nValues
)
643 memcpy( m_pNewYValues
.get(), m_pOrigYValues
, m_nValues
*sizeof(double) );
646 case ResetType::EXPONENTIAL
:
648 for( int i
= 0; i
< m_nValues
; i
++ )
650 m_pNewYValues
[ i
] = m_fMinY
+ (m_fMaxY
-m_fMinY
)*(std::expm1((m_pXValues
[i
]-m_fMinX
)/(m_fMaxX
-m_fMinX
)))/(M_E
-1.0);
661 for(size_t i(0); i
< m_aHandles
.size(); i
++)
663 // find nearest xvalue
665 transform( m_aHandles
[i
].maPos
, x
, y
);
667 double delta
= std::fabs( x
-m_pXValues
[0] );
668 for( int n
= 1; n
< m_nValues
; n
++ )
670 if( delta
> std::fabs( x
- m_pXValues
[ n
] ) )
672 delta
= std::fabs( x
- m_pXValues
[ n
] );
677 m_aHandles
[i
].maPos
= transform( m_fMinX
, m_pNewYValues
[ nIndex
] );
678 else if( m_aHandles
.size() - 1 == i
)
679 m_aHandles
[i
].maPos
= transform( m_fMaxX
, m_pNewYValues
[ nIndex
] );
681 m_aHandles
[i
].maPos
= transform( m_pXValues
[ nIndex
], m_pNewYValues
[ nIndex
] );
688 IMPL_LINK_NOARG(GridDialog
, ClickButtonHdl
, weld::Button
&, void)
690 int nType
= m_xResetTypeBox
->get_active();
691 m_xGridWindow
->ChangeMode(static_cast<ResetType
>(nType
));
694 double* GridDialog::getNewYValues()
696 return m_xGridWindow
->getNewYValues();
699 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */