1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 // MARKER(update_precomp.py): autogen include statement, do not remove 25 #include "precompiled_extensions.hxx" 26 #include <grid.hrc> 27 #include <cstdio> 28 #include <math.h> // for M_LN10 and M_E 29 30 #define _USE_MATH_DEFINES 31 #include <cmath> 32 #undef _USE_MATH_DEFINES 33 34 #include <grid.hxx> 35 36 // for ::std::sort 37 #include <algorithm> 38 39 ResId SaneResId( sal_uInt32 ); 40 41 /*********************************************************************** 42 * 43 * GridWindow 44 * 45 ***********************************************************************/ 46 47 // --------------------------------------------------------------------- 48 49 GridWindow::GridWindow(double* pXValues, double* pYValues, int nValues, Window* pParent, sal_Bool bCutValues ) 50 : ModalDialog( pParent, SaneResId( GRID_DIALOG ) ), 51 m_aGridArea( 50, 15, 100, 100 ), 52 m_pXValues( pXValues ), 53 m_pOrigYValues( pYValues ), 54 m_nValues( nValues ), 55 m_pNewYValues( NULL ), 56 m_bCutValues( bCutValues ), 57 m_aHandles(), 58 m_nDragIndex( 0xffffffff ), 59 m_aMarkerBitmap( Bitmap( SaneResId( GRID_DIALOG_HANDLE_BMP ) ), Color( 255, 255, 255 ) ), 60 m_aOKButton( this, SaneResId( GRID_DIALOG_OK_BTN ) ), 61 m_aCancelButton( this, SaneResId( GRID_DIALOG_CANCEL_BTN ) ), 62 m_aResetTypeBox( this, SaneResId( GRID_DIALOG_TYPE_BOX ) ), 63 m_aResetButton( this, SaneResId( GRID_DIALOG_RESET_BTN ) ) 64 { 65 sal_uInt16 nPos = m_aResetTypeBox.InsertEntry( String( SaneResId( RESET_TYPE_LINEAR_ASCENDING ) ) ); 66 m_aResetTypeBox.SetEntryData( nPos, (void *)RESET_TYPE_LINEAR_ASCENDING ); 67 68 nPos = m_aResetTypeBox.InsertEntry( String( SaneResId( RESET_TYPE_LINEAR_DESCENDING ) ) ); 69 m_aResetTypeBox.SetEntryData( nPos, (void *)RESET_TYPE_LINEAR_DESCENDING ); 70 71 nPos = m_aResetTypeBox.InsertEntry( String( SaneResId( RESET_TYPE_RESET ) ) ); 72 m_aResetTypeBox.SetEntryData( nPos, (void *)RESET_TYPE_RESET ); 73 74 nPos = m_aResetTypeBox.InsertEntry( String( SaneResId( RESET_TYPE_EXPONENTIAL ) ) ); 75 m_aResetTypeBox.SetEntryData( nPos, (void *)RESET_TYPE_EXPONENTIAL ); 76 77 m_aResetTypeBox.SelectEntryPos( 0 ); 78 79 m_aResetButton.SetClickHdl( LINK( this, GridWindow, ClickButtonHdl ) ); 80 81 SetMapMode( MapMode( MAP_PIXEL ) ); 82 Size aSize = GetOutputSizePixel(); 83 Size aBtnSize = m_aOKButton.GetOutputSizePixel(); 84 m_aGridArea.setWidth( aSize.Width() - aBtnSize.Width() - 80 ); 85 m_aGridArea.setHeight( aSize.Height() - 40 ); 86 87 if( m_pOrigYValues && m_nValues ) 88 { 89 m_pNewYValues = new double[ m_nValues ]; 90 memcpy( m_pNewYValues, m_pOrigYValues, sizeof( double ) * m_nValues ); 91 } 92 93 setBoundings( 0, 0, 1023, 1023 ); 94 computeExtremes(); 95 96 // create left and right marker as first and last entry 97 m_BmOffX = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Width() >> 1); 98 m_BmOffY = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Height() >> 1); 99 m_aHandles.push_back(impHandle(transform(findMinX(), findMinY()), m_BmOffX, m_BmOffY)); 100 m_aHandles.push_back(impHandle(transform(findMaxX(), findMaxY()), m_BmOffX, m_BmOffY)); 101 102 FreeResource(); 103 } 104 105 // --------------------------------------------------------------------- 106 107 GridWindow::~GridWindow() 108 { 109 if( m_pNewYValues ) 110 delete [] m_pNewYValues; 111 } 112 113 // --------------------------------------------------------------------- 114 115 double GridWindow::findMinX() 116 { 117 if( ! m_pXValues ) 118 return 0.0; 119 double fMin = m_pXValues[0]; 120 for( int i = 1; i < m_nValues; i++ ) 121 if( m_pXValues[ i ] < fMin ) 122 fMin = m_pXValues[ i ]; 123 return fMin; 124 } 125 126 // --------------------------------------------------------------------- 127 128 double GridWindow::findMinY() 129 { 130 if( ! m_pNewYValues ) 131 return 0.0; 132 double fMin = m_pNewYValues[0]; 133 for( int i = 1; i < m_nValues; i++ ) 134 if( m_pNewYValues[ i ] < fMin ) 135 fMin = m_pNewYValues[ i ]; 136 return fMin; 137 } 138 139 // --------------------------------------------------------------------- 140 141 double GridWindow::findMaxX() 142 { 143 if( ! m_pXValues ) 144 return 0.0; 145 double fMax = m_pXValues[0]; 146 for( int i = 1; i < m_nValues; i++ ) 147 if( m_pXValues[ i ] > fMax ) 148 fMax = m_pXValues[ i ]; 149 return fMax; 150 } 151 152 // --------------------------------------------------------------------- 153 154 double GridWindow::findMaxY() 155 { 156 if( ! m_pNewYValues ) 157 return 0.0; 158 double fMax = m_pNewYValues[0]; 159 for( int i = 1; i < m_nValues; i++ ) 160 if( m_pNewYValues[ i ] > fMax ) 161 fMax = m_pNewYValues[ i ]; 162 return fMax; 163 } 164 165 // --------------------------------------------------------------------- 166 167 void GridWindow::computeExtremes() 168 { 169 if( m_nValues && m_pXValues && m_pOrigYValues ) 170 { 171 m_fMaxX = m_fMinX = m_pXValues[0]; 172 m_fMaxY = m_fMinY = m_pOrigYValues[0]; 173 for( int i = 1; i < m_nValues; i++ ) 174 { 175 if( m_pXValues[ i ] > m_fMaxX ) 176 m_fMaxX = m_pXValues[ i ]; 177 else if( m_pXValues[ i ] < m_fMinX ) 178 m_fMinX = m_pXValues[ i ]; 179 if( m_pOrigYValues[ i ] > m_fMaxY ) 180 m_fMaxY = m_pOrigYValues[ i ]; 181 else if( m_pOrigYValues[ i ] < m_fMinY ) 182 m_fMinY = m_pOrigYValues[ i ]; 183 } 184 setBoundings( m_fMinX, m_fMinY, m_fMaxX, m_fMaxY ); 185 } 186 } 187 188 // --------------------------------------------------------------------- 189 190 Point GridWindow::transform( double x, double y ) 191 { 192 Point aRet; 193 194 aRet.X() = (long)( ( x - m_fMinX ) * 195 (double)m_aGridArea.GetWidth() / ( m_fMaxX - m_fMinX ) 196 + m_aGridArea.Left() ); 197 aRet.Y() = (long)( 198 m_aGridArea.Bottom() - 199 ( y - m_fMinY ) * 200 (double)m_aGridArea.GetHeight() / ( m_fMaxY - m_fMinY ) ); 201 return aRet; 202 } 203 204 // --------------------------------------------------------------------- 205 206 void GridWindow::transform( const Point& rOriginal, double& x, double& y ) 207 { 208 x = ( rOriginal.X() - m_aGridArea.Left() ) * (m_fMaxX - m_fMinX) / (double)m_aGridArea.GetWidth() + m_fMinX; 209 y = ( m_aGridArea.Bottom() - rOriginal.Y() ) * (m_fMaxY - m_fMinY) / (double)m_aGridArea.GetHeight() + m_fMinY; 210 } 211 212 // --------------------------------------------------------------------- 213 214 void GridWindow::drawLine( double x1, double y1, double x2, double y2 ) 215 { 216 DrawLine( transform( x1, y1 ), transform( x2, y2 ) ); 217 } 218 219 // --------------------------------------------------------------------- 220 221 void GridWindow::computeChunk( double fMin, double fMax, double& fChunkOut, double& fMinChunkOut ) 222 { 223 // get a nice chunk size like 10, 100, 25 or such 224 fChunkOut = ( fMax - fMin ) / 6.0; 225 int logchunk = (int)std::log10( fChunkOut ); 226 int nChunk = (int)( fChunkOut / std::exp( (double)(logchunk-1) * M_LN10 ) ); 227 if( nChunk >= 75 ) 228 nChunk = 100; 229 else if( nChunk >= 35 ) 230 nChunk = 50; 231 else if ( nChunk > 20 ) 232 nChunk = 25; 233 else if ( nChunk >= 13 ) 234 nChunk = 20; 235 else if( nChunk > 5 ) 236 nChunk = 10; 237 else 238 nChunk = 5; 239 fChunkOut = (double) nChunk * exp( (double)(logchunk-1) * M_LN10 ); 240 // compute whole chunks fitting into fMin 241 nChunk = (int)( fMin / fChunkOut ); 242 fMinChunkOut = (double)nChunk * fChunkOut; 243 while( fMinChunkOut < fMin ) 244 fMinChunkOut += fChunkOut; 245 } 246 247 // --------------------------------------------------------------------- 248 249 void GridWindow::computeNew() 250 { 251 if(2L == m_aHandles.size()) 252 { 253 // special case: only left and right markers 254 double xleft, yleft; 255 double xright, yright; 256 transform(m_aHandles[0L].maPos, xleft, yleft); 257 transform(m_aHandles[1L].maPos, xright, yright ); 258 double factor = (yright-yleft)/(xright-xleft); 259 for( int i = 0; i < m_nValues; i++ ) 260 { 261 m_pNewYValues[ i ] = yleft + ( m_pXValues[ i ] - xleft )*factor; 262 } 263 } 264 else 265 { 266 // sort markers 267 std::sort(m_aHandles.begin(), m_aHandles.end()); 268 const int nSorted = m_aHandles.size(); 269 int i; 270 271 // get node arrays 272 double* nodex = new double[ nSorted ]; 273 double* nodey = new double[ nSorted ]; 274 275 for( i = 0L; i < nSorted; i++ ) 276 transform( m_aHandles[i].maPos, nodex[ i ], nodey[ i ] ); 277 278 for( i = 0; i < m_nValues; i++ ) 279 { 280 double x = m_pXValues[ i ]; 281 m_pNewYValues[ i ] = interpolate( x, nodex, nodey, nSorted ); 282 if( m_bCutValues ) 283 { 284 if( m_pNewYValues[ i ] > m_fMaxY ) 285 m_pNewYValues[ i ] = m_fMaxY; 286 else if( m_pNewYValues[ i ] < m_fMinY ) 287 m_pNewYValues[ i ] = m_fMinY; 288 } 289 } 290 291 delete [] nodex; 292 delete [] nodey; 293 } 294 } 295 296 // --------------------------------------------------------------------- 297 298 double GridWindow::interpolate( 299 double x, 300 double* pNodeX, 301 double* pNodeY, 302 int nNodes ) 303 { 304 // compute Lagrange interpolation 305 double ret = 0; 306 for( int i = 0; i < nNodes; i++ ) 307 { 308 double sum = pNodeY[ i ]; 309 for( int n = 0; n < nNodes; n++ ) 310 { 311 if( n != i ) 312 { 313 sum *= x - pNodeX[ n ]; 314 sum /= pNodeX[ i ] - pNodeX[ n ]; 315 } 316 } 317 ret += sum; 318 } 319 return ret; 320 } 321 322 // --------------------------------------------------------------------- 323 324 void GridWindow::setBoundings( double fMinX, double fMinY, double fMaxX, double fMaxY ) 325 { 326 m_fMinX = fMinX; 327 m_fMinY = fMinY; 328 m_fMaxX = fMaxX; 329 m_fMaxY = fMaxY; 330 331 computeChunk( m_fMinX, m_fMaxX, m_fChunkX, m_fMinChunkX ); 332 computeChunk( m_fMinY, m_fMaxY, m_fChunkY, m_fMinChunkY ); 333 } 334 335 // --------------------------------------------------------------------- 336 337 void GridWindow::drawGrid() 338 { 339 char pBuf[256]; 340 SetLineColor( Color( COL_BLACK ) ); 341 // draw vertical lines 342 for( double fX = m_fMinChunkX; fX < m_fMaxX; fX += m_fChunkX ) 343 { 344 drawLine( fX, m_fMinY, fX, m_fMaxY ); 345 // draw tickmarks 346 Point aPt = transform( fX, m_fMinY ); 347 std::sprintf( pBuf, "%g", fX ); 348 String aMark( pBuf, gsl_getSystemTextEncoding() ); 349 Size aTextSize( GetTextWidth( aMark ), GetTextHeight() ); 350 aPt.X() -= aTextSize.Width()/2; 351 aPt.Y() += aTextSize.Height()/2; 352 DrawText( aPt, aMark ); 353 } 354 // draw horizontal lines 355 for( double fY = m_fMinChunkY; fY < m_fMaxY; fY += m_fChunkY ) 356 { 357 drawLine( m_fMinX, fY, m_fMaxX, fY ); 358 // draw tickmarks 359 Point aPt = transform( m_fMinX, fY ); 360 std::sprintf( pBuf, "%g", fY ); 361 String aMark( pBuf, gsl_getSystemTextEncoding() ); 362 Size aTextSize( GetTextWidth( aMark ), GetTextHeight() ); 363 aPt.X() -= aTextSize.Width() + 2; 364 aPt.Y() -= aTextSize.Height()/2; 365 DrawText( aPt, aMark ); 366 } 367 368 // draw boundings 369 drawLine( m_fMinX, m_fMinY, m_fMaxX, m_fMinY ); 370 drawLine( m_fMinX, m_fMaxY, m_fMaxX, m_fMaxY ); 371 drawLine( m_fMinX, m_fMinY, m_fMinX, m_fMaxY ); 372 drawLine( m_fMaxX, m_fMinY, m_fMaxX, m_fMaxY ); 373 } 374 375 // --------------------------------------------------------------------- 376 377 void GridWindow::drawOriginal() 378 { 379 if( m_nValues && m_pXValues && m_pOrigYValues ) 380 { 381 SetLineColor( Color( COL_RED ) ); 382 for( int i = 0; i < m_nValues-1; i++ ) 383 { 384 drawLine( m_pXValues[ i ], m_pOrigYValues[ i ], 385 m_pXValues[ i+1 ], m_pOrigYValues[ i+1 ] ); 386 } 387 } 388 } 389 390 // --------------------------------------------------------------------- 391 392 void GridWindow::drawNew() 393 { 394 if( m_nValues && m_pXValues && m_pNewYValues ) 395 { 396 SetClipRegion( m_aGridArea ); 397 SetLineColor( Color( COL_YELLOW ) ); 398 for( int i = 0; i < m_nValues-1; i++ ) 399 { 400 drawLine( m_pXValues[ i ], m_pNewYValues[ i ], 401 m_pXValues[ i+1 ], m_pNewYValues[ i+1 ] ); 402 } 403 SetClipRegion(); 404 } 405 } 406 407 // --------------------------------------------------------------------- 408 409 void GridWindow::drawHandles() 410 { 411 for(sal_uInt32 i(0L); i < m_aHandles.size(); i++) 412 { 413 m_aHandles[i].draw(*this, m_aMarkerBitmap); 414 } 415 } 416 417 // --------------------------------------------------------------------- 418 419 void GridWindow::Paint( const Rectangle& rRect ) 420 { 421 ModalDialog::Paint( rRect ); 422 drawGrid(); 423 drawOriginal(); 424 drawNew(); 425 drawHandles(); 426 } 427 428 // --------------------------------------------------------------------- 429 430 void GridWindow::MouseMove( const MouseEvent& rEvt ) 431 { 432 if( rEvt.GetButtons() == MOUSE_LEFT && m_nDragIndex != 0xffffffff ) 433 { 434 Point aPoint( rEvt.GetPosPixel() ); 435 436 if( m_nDragIndex == 0L || m_nDragIndex == m_aHandles.size() - 1L) 437 { 438 aPoint.X() = m_aHandles[m_nDragIndex].maPos.X(); 439 } 440 else 441 { 442 if(aPoint.X() < m_aGridArea.Left()) 443 aPoint.X() = m_aGridArea.Left(); 444 else if(aPoint.X() > m_aGridArea.Right()) 445 aPoint.X() = m_aGridArea.Right(); 446 } 447 448 if( aPoint.Y() < m_aGridArea.Top() ) 449 aPoint.Y() = m_aGridArea.Top(); 450 else if( aPoint.Y() > m_aGridArea.Bottom() ) 451 aPoint.Y() = m_aGridArea.Bottom(); 452 453 if( aPoint != m_aHandles[m_nDragIndex].maPos ) 454 { 455 m_aHandles[m_nDragIndex].maPos = aPoint; 456 Invalidate( m_aGridArea ); 457 } 458 } 459 460 ModalDialog::MouseMove( rEvt ); 461 } 462 463 // --------------------------------------------------------------------- 464 465 void GridWindow::MouseButtonUp( const MouseEvent& rEvt ) 466 { 467 if( rEvt.GetButtons() == MOUSE_LEFT ) 468 { 469 if( m_nDragIndex != 0xffffffff ) 470 { 471 m_nDragIndex = 0xffffffff; 472 computeNew(); 473 Invalidate( m_aGridArea ); 474 Paint( m_aGridArea ); 475 } 476 } 477 478 ModalDialog::MouseButtonUp( rEvt ); 479 } 480 481 // --------------------------------------------------------------------- 482 483 void GridWindow::MouseButtonDown( const MouseEvent& rEvt ) 484 { 485 Point aPoint( rEvt.GetPosPixel() ); 486 sal_uInt32 nMarkerIndex = 0xffffffff; 487 488 for(sal_uInt32 a(0L); nMarkerIndex == 0xffffffff && a < m_aHandles.size(); a++) 489 { 490 if(m_aHandles[a].isHit(*this, aPoint)) 491 { 492 nMarkerIndex = a; 493 } 494 } 495 496 if( rEvt.GetButtons() == MOUSE_LEFT ) 497 { 498 // user wants to drag a button 499 if( nMarkerIndex != 0xffffffff ) 500 { 501 m_nDragIndex = nMarkerIndex; 502 } 503 } 504 else if( rEvt.GetButtons() == MOUSE_RIGHT ) 505 { 506 // user wants to add/delete a button 507 if( nMarkerIndex != 0xffffffff ) 508 { 509 if( nMarkerIndex != 0L && nMarkerIndex != m_aHandles.size() - 1L) 510 { 511 // delete marker under mouse 512 if( m_nDragIndex == nMarkerIndex ) 513 m_nDragIndex = 0xffffffff; 514 515 m_aHandles.erase(m_aHandles.begin() + nMarkerIndex); 516 } 517 } 518 else 519 { 520 m_BmOffX = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Width() >> 1); 521 m_BmOffY = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Height() >> 1); 522 m_aHandles.push_back(impHandle(aPoint, m_BmOffX, m_BmOffY)); 523 } 524 525 computeNew(); 526 Invalidate( m_aGridArea ); 527 Paint( m_aGridArea ); 528 } 529 530 ModalDialog::MouseButtonDown( rEvt ); 531 } 532 533 // --------------------------------------------------------------------- 534 535 IMPL_LINK( GridWindow, ClickButtonHdl, Button*, pButton ) 536 { 537 if( pButton == &m_aResetButton ) 538 { 539 int nType = (int)(sal_IntPtr)m_aResetTypeBox.GetEntryData( m_aResetTypeBox.GetSelectEntryPos() ); 540 switch( nType ) 541 { 542 case RESET_TYPE_LINEAR_ASCENDING: 543 { 544 for( int i = 0; i < m_nValues; i++ ) 545 { 546 m_pNewYValues[ i ] = m_fMinY + (m_fMaxY-m_fMinY)/(m_fMaxX-m_fMinX)*(m_pXValues[i]-m_fMinX); 547 } 548 } 549 break; 550 case RESET_TYPE_LINEAR_DESCENDING: 551 { 552 for( int i = 0; i < m_nValues; i++ ) 553 { 554 m_pNewYValues[ i ] = m_fMaxY - (m_fMaxY-m_fMinY)/(m_fMaxX-m_fMinX)*(m_pXValues[i]-m_fMinX); 555 } 556 } 557 break; 558 case RESET_TYPE_RESET: 559 { 560 if( m_pOrigYValues && m_pNewYValues && m_nValues ) 561 memcpy( m_pNewYValues, m_pOrigYValues, m_nValues*sizeof(double) ); 562 } 563 break; 564 case RESET_TYPE_EXPONENTIAL: 565 { 566 for( int i = 0; i < m_nValues; i++ ) 567 { 568 m_pNewYValues[ i ] = m_fMinY + (m_fMaxY-m_fMinY)*(std::exp((m_pXValues[i]-m_fMinX)/(m_fMaxX-m_fMinX))-1.0)/(M_E-1.0); 569 } 570 } 571 break; 572 573 default: 574 break; 575 } 576 577 for(sal_uInt32 i(0L); i < m_aHandles.size(); i++) 578 { 579 // find nearest xvalue 580 double x, y; 581 transform( m_aHandles[i].maPos, x, y ); 582 int nIndex = 0; 583 double delta = std::fabs( x-m_pXValues[0] ); 584 for( int n = 1; n < m_nValues; n++ ) 585 { 586 if( delta > std::fabs( x - m_pXValues[ n ] ) ) 587 { 588 delta = std::fabs( x - m_pXValues[ n ] ); 589 nIndex = n; 590 } 591 } 592 if( 0 == i ) 593 m_aHandles[i].maPos = transform( m_fMinX, m_pNewYValues[ nIndex ] ); 594 else if( m_aHandles.size() - 1L == i ) 595 m_aHandles[i].maPos = transform( m_fMaxX, m_pNewYValues[ nIndex ] ); 596 else 597 m_aHandles[i].maPos = transform( m_pXValues[ nIndex ], m_pNewYValues[ nIndex ] ); 598 } 599 600 Invalidate( m_aGridArea ); 601 Paint(Rectangle()); 602 } 603 return 0; 604 } 605