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