1// 2// This file is part of the aMule Project. 3// 4// Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org ) 5// Copyright (c) 2002-2011 Merkur ( devs@emule-project.net / http://www.emule-project.net ) 6// 7// Any parts of this program derived from the xMule, lMule or eMule project, 8// or contributed by third-party developers are copyrighted by their 9// respective authors. 10// 11// This program is free software; you can redistribute it and/or modify 12// it under the terms of the GNU General Public License as published by 13// the Free Software Foundation; either version 2 of the License, or 14// (at your option) any later version. 15// 16// This program is distributed in the hope that it will be useful, 17// but WITHOUT ANY WARRANTY; without even the implied warranty of 18// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19// GNU General Public License for more details. 20// 21// You should have received a copy of the GNU General Public License 22// along with this program; if not, write to the Free Software 23// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 24// 25 26#include <cmath> 27#include <wx/dcmemory.h> 28#include <wx/dcclient.h> 29#include <wx/dcbuffer.h> 30 31#include <common/Format.h> 32 33#include "amule.h" // Needed for theApp 34#include "amuleDlg.h" // Needed for CamuleDlg 35#include "Logger.h" // Needed for AddLogLineM 36#include "OScopeCtrl.h" // Interface declarations 37#include "OtherFunctions.h" // Needed for CastSecondsToHM 38#include "Statistics.h" 39 40 41BEGIN_EVENT_TABLE(COScopeCtrl,wxControl) 42 EVT_PAINT(COScopeCtrl::OnPaint) 43 EVT_SIZE(COScopeCtrl::OnSize) 44END_EVENT_TABLE() 45 46 47const wxColour crPreset [ 16 ] = { 48 wxColour( 0xFF, 0x00, 0x00 ), wxColour( 0xFF, 0xC0, 0xC0 ), 49 wxColour( 0xFF, 0xFF, 0x00 ), wxColour( 0xFF, 0xA0, 0x00 ), 50 wxColour( 0xA0, 0x60, 0x00 ), wxColour( 0x00, 0xFF, 0x00 ), 51 wxColour( 0x00, 0xA0, 0x00 ), wxColour( 0x00, 0x00, 0xFF ), 52 wxColour( 0x00, 0xA0, 0xFF ), wxColour( 0x00, 0xFF, 0xFF ), 53 wxColour( 0x00, 0xA0, 0xA0 ), wxColour( 0xC0, 0xC0, 0xFF ), 54 wxColour( 0xFF, 0x00, 0xFF ), wxColour( 0xA0, 0x00, 0xA0 ), 55 wxColour( 0xFF, 0xFF, 0xFF ), wxColour( 0x80, 0x80, 0x80 ) 56}; 57 58COScopeCtrl::COScopeCtrl(int cntTrends, int nDecimals, StatsGraphType type, wxWindow* parent) 59 : wxControl(parent, -1, wxDefaultPosition, wxDefaultSize) 60 , timerRedraw(this) 61{ 62 // since plotting is based on a LineTo for each new point 63 // we need a starting point (i.e. a "previous" point) 64 // use 0.0 as the default first point. 65 // these are public member variables, and can be changed outside 66 // (after construction). 67 68 // G.Hayduk: NTrends is the number of trends that will be drawn on 69 // the plot. First 15 plots have predefined colors, but others will 70 // be drawn with white, unless you call SetPlotColor 71 nTrends = cntTrends; 72 pdsTrends = new PlotData_t[nTrends]; 73 74 PlotData_t* ppds = pdsTrends; 75 for(unsigned i=0; i<nTrends; ++i, ++ppds){ 76 ppds->crPlot = (i<15 ? crPreset[i] : *wxWHITE); 77 ppds->penPlot=*(wxThePenList->FindOrCreatePen(ppds->crPlot, 1, wxSOLID)); 78 ppds->fPrev = ppds->fLowerLimit = ppds->fUpperLimit = 0.0; 79 } 80 81 bRecreateGraph = bRecreateGrid = bRecreateAll = bStopped = false; 82 m_onPaint = false; 83 nDelayedPoints = 0; 84 sLastTimestamp = 0.0; 85 sLastPeriod = 1.0; 86 nShiftPixels = 1; 87 nYDecimals = nDecimals; 88 m_bgColour = wxColour( 0, 0, 0) ; // see also SetBackgroundColor 89 m_gridColour = wxColour( 0, 255, 255) ; // see also SetGridColor 90 brushBack = *wxBLACK_BRUSH; 91 92 strXUnits = wxT("X"); // can also be set with SetXUnits 93 strYUnits = wxT("Y"); // can also be set with SetYUnits 94 95 nXGrids = 6; 96 nYGrids = 5; 97 98 graph_type = type; 99 100 // Connect the timer (dynamically, so the Controls don't have to share a common timer id) 101 Connect(timerRedraw.GetId(), wxEVT_TIMER, (wxObjectEventFunction) &COScopeCtrl::OnTimer); 102 // Don't draw background (avoid ugly flickering on wxMSW on resize) 103 SetBackgroundStyle(wxBG_STYLE_CUSTOM); 104 105 // Ensure that various size-constraints are calculated (via OnSize). 106 SetClientSize(GetClientSize()); 107} 108 109 110COScopeCtrl::~COScopeCtrl() 111{ 112 delete [] pdsTrends; 113} 114 115 116void COScopeCtrl::SetRange(float fLower, float fUpper, unsigned iTrend) 117{ 118 PlotData_t* ppds = pdsTrends+iTrend; 119 if ((ppds->fLowerLimit == fLower) && (ppds->fUpperLimit == fUpper)) 120 return; 121 ppds->fLowerLimit = fLower; 122 ppds->fUpperLimit = fUpper; 123 ppds->fVertScale = (float)m_rectPlot.GetHeight() / (fUpper-fLower); 124 ppds->yPrev = GetPlotY(ppds->fPrev, ppds); 125 126 if (iTrend == 0) { 127 InvalidateCtrl(); 128 } else { 129 InvalidateGraph(); 130 } 131} 132 133 134void COScopeCtrl::SetRanges(float fLower, float fUpper) 135{ 136 for (unsigned iTrend = 0; iTrend < nTrends; ++iTrend) { 137 SetRange(fLower, fUpper, iTrend); 138 } 139} 140 141 142void COScopeCtrl::SetYUnits(const wxString& strUnits, const wxString& strMin, const wxString& strMax) 143{ 144 strYUnits = strUnits; 145 strYMin = strMin; 146 strYMax = strMax; 147 InvalidateGrid(); 148} 149 150 151void COScopeCtrl::SetGridColor(const wxColour& cr) 152{ 153 154 if (cr == m_gridColour) { 155 return; 156 } 157 158 m_gridColour = cr; 159 InvalidateGrid() ; 160} 161 162 163void COScopeCtrl::SetPlotColor(const wxColour& cr, unsigned iTrend) 164{ 165 PlotData_t* ppds = pdsTrends+iTrend; 166 if (ppds->crPlot == cr) 167 return; 168 ppds->crPlot = cr; 169 ppds->penPlot=*(wxThePenList->FindOrCreatePen(ppds->crPlot, 1, wxSOLID)); 170 InvalidateGraph(); 171} 172 173 174void COScopeCtrl::SetBackgroundColor(const wxColour& cr) 175{ 176 177 if (m_bgColour == cr) { 178 return; 179 } 180 181 m_bgColour = cr; 182 brushBack= *(wxTheBrushList->FindOrCreateBrush(cr, wxSOLID)); 183 InvalidateCtrl() ; 184} 185 186 187void COScopeCtrl::RecreateGrid() 188{ 189 // There is a lot of drawing going on here - particularly in terms of 190 // drawing the grid. Don't panic, this is all being drawn (only once) 191 // to a bitmap. The result is then BitBlt'd to the control whenever needed. 192 bRecreateGrid = false; 193 if (m_rectClient.GetWidth() == 0 || m_rectClient.GetHeight() == 0) { 194 return; 195 } 196 197 wxMemoryDC dcGrid(m_bmapGrid); 198 199 int nCharacters ; 200 wxPen solidPen = *(wxThePenList->FindOrCreatePen(m_gridColour, 1, wxSOLID)); 201 wxString strTemp; 202 203 // fill the grid background 204 dcGrid.SetBrush(brushBack); 205 dcGrid.SetPen(*wxTRANSPARENT_PEN); 206 dcGrid.DrawRectangle(m_rectClient); 207 // draw the plot rectangle: determine how wide the y axis scaling values are, 208 // add the units digit, decimal point, one decimal place, and an extra space 209 nCharacters = std::abs((int)std::log10(std::fabs(pdsTrends[0].fUpperLimit))) ; 210 nCharacters = std::max(nCharacters, std::abs((int)std::log10(std::fabs(pdsTrends[0].fLowerLimit)))) + 4; 211 212 // adjust the plot rectangle dimensions 213 // assume 6 pixels per character (this may need to be adjusted) 214 m_rectPlot.x = m_rectClient.GetLeft() + 6*7+4; 215 // draw the plot rectangle 216 dcGrid.SetPen(solidPen); 217 dcGrid.DrawRectangle(m_rectPlot.x - 1, m_rectPlot.y - 1, m_rectPlot.GetWidth() + 2, m_rectPlot.GetHeight() + 2); 218 dcGrid.SetPen(wxNullPen); 219 220 // create some fonts (horizontal and vertical) 221 wxFont axisFont(10, wxSWISS, wxNORMAL, wxNORMAL, false); 222 dcGrid.SetFont(axisFont); 223 224 // y max 225 dcGrid.SetTextForeground(m_gridColour); 226 if( strYMax.IsEmpty() ) { 227 strTemp = wxString::Format(wxT("%.*f"), nYDecimals, pdsTrends[ 0 ].fUpperLimit); 228 } else { 229 strTemp = strYMax; 230 } 231 wxCoord sizX,sizY; 232 dcGrid.GetTextExtent(strTemp,&sizX,&sizY); 233 dcGrid.DrawText(strTemp,m_rectPlot.GetLeft()-4-sizX,m_rectPlot.GetTop()-7); 234 // y min 235 if( strYMin.IsEmpty() ) { 236 strTemp = wxString::Format(wxT("%.*f"), nYDecimals, pdsTrends[ 0 ].fLowerLimit) ; 237 } else { 238 strTemp = strYMin; 239 } 240 dcGrid.GetTextExtent(strTemp,&sizX,&sizY); 241 dcGrid.DrawText(strTemp,m_rectPlot.GetLeft()-4-sizX, m_rectPlot.GetBottom()); 242 243 // x units 244 strTemp = CastSecondsToHM((m_rectPlot.GetWidth()/nShiftPixels) * (int)floor(sLastPeriod+0.5)); 245 // floor(x + 0.5) is a way of doing round(x) that works with gcc < 3 ... 246 if (bStopped) { 247 strXUnits = CFormat( _("Disabled [%s]") ) % strTemp; 248 } else { 249 strXUnits = strTemp; 250 } 251 252 dcGrid.GetTextExtent(strXUnits,&sizX,&sizY); 253 dcGrid.DrawText(strXUnits,(m_rectPlot.GetLeft() + m_rectPlot.GetRight())/2-sizX/2,m_rectPlot.GetBottom()+4); 254 255 // y units 256 if (!strYUnits.IsEmpty()) { 257 dcGrid.GetTextExtent(strYUnits,&sizX,&sizY); 258 dcGrid.DrawText(strYUnits, m_rectPlot.GetLeft()-4-sizX, (m_rectPlot.GetTop()+m_rectPlot.GetBottom())/2-sizY/2); 259 } 260 // no more drawing to this bitmap is needed until the setting are changed 261 262 if (bRecreateGraph) { 263 RecreateGraph(false); 264 } 265 266 // finally, force the plot area to redraw 267 Refresh(false); 268} 269 270 271void COScopeCtrl::AppendPoints(double sTimestamp, const std::vector<float *> &apf) 272{ 273 sLastTimestamp = sTimestamp; 274 275 if (nDelayedPoints) { 276 // Ensures that delayed points get added before the new point. 277 // We do this by simply drawing the history up to and including 278 // the new point. 279 int n = std::min(m_rectPlot.GetWidth(), nDelayedPoints + 1); 280 nDelayedPoints = 0; 281 PlotHistory(n, true, false); 282 } else { 283 ShiftGraph(1); 284 DrawPoints(apf, 1); 285 } 286 287 Refresh(false); 288} 289 290 291void COScopeCtrl::OnPaint(wxPaintEvent& WXUNUSED(evt)) 292{ 293 m_onPaint = true; 294 295 // no real plotting work is performed here unless we are coming out of a hidden state; 296 // normally, just putting the existing bitmaps on the client to avoid flicker, 297 // establish a memory dc and then BitBlt it to the client 298 wxBufferedPaintDC dc(this); 299 300 if (bRecreateAll) { 301 return; 302 } 303 304 if (bRecreateGrid) { 305 RecreateGrid(); // this will also recreate the graph if that flag is set 306 } else if (bRecreateGraph) { 307 RecreateGraph(true); 308 } 309 310 if (nDelayedPoints) { // we've just come out of hiding, so catch up 311 int n = std::min(m_rectPlot.GetWidth(), nDelayedPoints); 312 nDelayedPoints = 0; // (this is more efficient than plotting in the 313 PlotHistory(n, true, false); // background because the bitmap is shifted only 314 } // once for all delayed points together) 315 316 // We have assured that we have a valid and resized if needed 317 // wxDc and bitmap. Proceed to blit. 318 dc.DrawBitmap(m_bmapGrid, 0, 0, false); 319 320 // Overwrites the plot section of the image 321 dc.DrawBitmap(m_bmapPlot, m_rectPlot.x, m_rectPlot.y, false); 322 323 // draw the dotted lines. 324 // This is done last because wxMAC does't support the wxOR logical 325 // operation, preventing us from simply blitting the plot on top of 326 // the grid bitmap. 327 328 dc.SetPen(*(wxThePenList->FindOrCreatePen(m_gridColour, 1, wxLONG_DASH))); 329 for (unsigned j = 1; j < (nYGrids + 1); ++j) { 330 unsigned GridPos = (m_rectPlot.GetHeight())*j/( nYGrids + 1 ) + m_rectPlot.GetTop(); 331 332 dc.DrawLine(m_rectPlot.GetLeft(), GridPos, m_rectPlot.GetRight(), GridPos); 333 } 334} 335 336 337void COScopeCtrl::OnSize(wxSizeEvent& WXUNUSED(evt)) 338{ 339 // This gets called repeatedly as the user resizes the app; 340 // we use the timer mechanism through InvalidateCtrl to avoid unnecessary redrawing 341 // NOTE: OnSize automatically gets called during the setup of the control 342 if(GetClientRect() == m_rectClient) { 343 return; 344 } 345 346 m_rectClient = GetClientRect(); 347 if (m_rectClient.GetWidth() < 1 || m_rectClient.GetHeight() < 1) { 348 return; 349 } 350 351 // the "left" coordinate and "width" will be modified in 352 // InvalidateCtrl to be based on the y axis scaling 353 m_rectPlot.SetLeft(20); 354 m_rectPlot.SetTop(10); 355 m_rectPlot.SetRight(std::max<int>(m_rectPlot.GetLeft() + 1, m_rectClient.GetRight() - 40)); 356 m_rectPlot.SetBottom(std::max<int>(m_rectPlot.GetTop() + 1, m_rectClient.GetBottom() - 25)); 357 358 PlotData_t* ppds = pdsTrends; 359 for(unsigned iTrend=0; iTrend<nTrends; ++iTrend, ++ppds) { 360 ppds->fVertScale = (float)m_rectPlot.GetHeight() / (ppds->fUpperLimit-ppds->fLowerLimit); 361 ppds->yPrev = GetPlotY(ppds->fPrev, ppds); 362 } 363 364 if (!m_bmapGrid.IsOk() || (m_rectClient != wxSize(m_bmapGrid.GetWidth(), m_bmapGrid.GetHeight()))) { 365 m_bmapGrid.Create(m_rectClient.GetWidth(), m_rectClient.GetHeight()); 366 } 367 if (!m_bmapPlot.IsOk() || (m_rectPlot != wxSize(m_bmapPlot.GetWidth(), m_bmapPlot.GetHeight()))) { 368 m_bmapPlot.Create(m_rectPlot.GetWidth(), m_rectPlot.GetHeight()); 369 } 370 371 InvalidateCtrl(); 372} 373 374 375void COScopeCtrl::ShiftGraph(unsigned cntPoints) 376{ 377 wxMemoryDC dcPlot(m_bmapPlot); 378 379 unsigned cntPixelOffset = cntPoints*nShiftPixels; 380 if (cntPixelOffset >= (unsigned)m_rectPlot.GetWidth()) { 381 cntPixelOffset = m_rectPlot.GetWidth(); 382 } else { 383 dcPlot.Blit(0, 0, m_rectPlot.GetWidth(), m_rectPlot.GetHeight(), &dcPlot, 384 cntPixelOffset, 0); // scroll graph to the left 385 } 386 387 // clear a rectangle over the right side of plot prior to adding the new points 388 dcPlot.SetPen(*wxTRANSPARENT_PEN); 389 dcPlot.SetBrush(brushBack); // fill with background color 390 dcPlot.DrawRectangle(m_rectPlot.GetWidth()-cntPixelOffset, 0, 391 cntPixelOffset, m_rectPlot.GetHeight()); 392} 393 394 395unsigned COScopeCtrl::GetPlotY(float fPlot, PlotData_t* ppds) 396{ 397 if (fPlot <= ppds->fLowerLimit) { 398 return m_rectPlot.GetBottom(); 399 } else if (fPlot >= ppds->fUpperLimit) { 400 return m_rectPlot.GetTop() + 1; 401 } else { 402 return m_rectPlot.GetBottom() - (unsigned)((fPlot - ppds->fLowerLimit) * ppds->fVertScale); 403 } 404} 405 406 407void COScopeCtrl::DrawPoints(const std::vector<float *> &apf, unsigned cntPoints) 408{ 409 // this appends a new set of data points to a graph; all of the plotting is 410 // directed to the memory based bitmap associated with dcPlot 411 // the will subsequently be BitBlt'd to the client in OnPaint 412 // draw the next line segement 413 unsigned y, yPrev; 414 unsigned cntPixelOffset = std::min((unsigned)(m_rectPlot.GetWidth()-1), (cntPoints-1)*nShiftPixels); 415 PlotData_t* ppds = pdsTrends; 416 417 wxMemoryDC dcPlot(m_bmapPlot); 418 419 for (unsigned iTrend=0; iTrend<nTrends; ++iTrend, ++ppds) { 420 const float* pf = apf[iTrend] + cntPoints - 1; 421 yPrev = ppds->yPrev; 422 dcPlot.SetPen(ppds->penPlot); 423 424 for (int x = m_rectPlot.GetRight() - cntPixelOffset; x <= m_rectPlot.GetRight(); x+=nShiftPixels) { 425 y = GetPlotY(*pf--, ppds); 426 427 // Map onto the smaller bitmap 428 dcPlot.DrawLine(x - nShiftPixels - m_rectPlot.GetX(), 429 yPrev - m_rectPlot.GetY(), 430 x - m_rectPlot.GetX(), 431 y - m_rectPlot.GetY()); 432 433 yPrev = y; 434 } 435 ppds->fPrev = *(pf+1); 436 ppds->yPrev = yPrev; 437 } 438} 439 440 441#ifndef CLIENT_GUI 442void COScopeCtrl::PlotHistory(unsigned cntPoints, bool bShiftGraph, bool bRefresh) 443{ 444 wxASSERT(graph_type != GRAPH_INVALID); 445 446 if (graph_type != GRAPH_INVALID) { 447 unsigned i; 448 unsigned cntFilled; 449 std::vector<float *> apf(nTrends); 450 try { 451 for (i = 0; i < nTrends; ++i) { 452 apf[i] = new float[cntPoints]; 453 } 454 double sFinal = (bStopped ? sLastTimestamp : -1.0); 455 cntFilled = theApp->m_statistics->GetHistory(cntPoints, sLastPeriod, sFinal, apf, graph_type); 456 if (cntFilled >1 || (bShiftGraph && cntFilled!=0)) { 457 if (bShiftGraph) { // delayed points - we have an fPrev 458 ShiftGraph(cntFilled); 459 } else { // fresh graph, we need to preset fPrev, yPrev 460 PlotData_t* ppds = pdsTrends; 461 for(i=0; i<nTrends; ++i, ++ppds) 462 ppds->yPrev = GetPlotY(ppds->fPrev = *(apf[i] + cntFilled - 1), ppds); 463 cntFilled--; 464 } 465 DrawPoints(apf, cntFilled); 466 if (bRefresh) 467 Refresh(false); 468 } 469 for (i = 0; i < nTrends; ++i) { 470 delete [] apf[i]; 471 } 472 } catch(std::bad_alloc) { 473 // Failed memory allocation 474 AddLogLineC(wxString( 475 wxT("Error: COScopeCtrl::PlotHistory: Insuficient memory, cntPoints == ")) << 476 cntPoints << wxT(".")); 477 for (i = 0; i < nTrends; ++i) { 478 delete [] apf[i]; 479 } 480 } 481 } else { 482 // No history (yet) for Kad. 483 } 484} 485#else 486//#warning CORE/GUI -- EC needed 487void COScopeCtrl::PlotHistory(unsigned, bool, bool) 488{ 489} 490#endif 491 492 493void COScopeCtrl::RecreateGraph(bool bRefresh) 494{ 495 bRecreateGraph = false; 496 nDelayedPoints = 0; 497 498 wxMemoryDC dcPlot(m_bmapPlot); 499 dcPlot.SetBackground(brushBack); 500 dcPlot.Clear(); 501 502 PlotHistory(m_rectPlot.GetWidth(), false, bRefresh); 503} 504 505 506void COScopeCtrl::Reset(double sNewPeriod) 507{ 508 bool bStoppedPrev = bStopped; 509 bStopped = false; 510 if (sLastPeriod != sNewPeriod || bStoppedPrev) { 511 sLastPeriod = sNewPeriod; 512 InvalidateCtrl(); 513 } 514} 515 516 517void COScopeCtrl::Stop() 518{ 519 bStopped = true; 520 bRecreateGraph = false; 521 RecreateGrid(); 522} 523 524 525void COScopeCtrl::InvalidateCtrl(bool bInvalidateGraph, bool bInvalidateGrid) 526{ 527 bRecreateGraph |= bInvalidateGraph; 528 bRecreateGrid |= bInvalidateGrid; 529 // It appears the timerRedraw logic screws up Mac, disable it there 530#ifndef __WXMAC__ 531 // To prevent startup problems don't start timer logic until 532 // a native OnPaint event has been generated. 533 if (m_onPaint) { 534 bRecreateAll |= bInvalidateGraph && bInvalidateGrid; 535 timerRedraw.Start(100, true); // One-shot timer 536 } 537#endif 538} 539 540 541void COScopeCtrl::OnTimer(wxTimerEvent& WXUNUSED(evt)) 542/* The timer is used to consolidate redrawing of the graphs: when the user resizes 543 the application, we get multiple calls to OnSize. If he changes several parameters 544 in the Preferences, we get several individual SetXYZ calls. If we were to try to 545 recreate the graphs for each such event, performance would be sluggish, but with 546 the timer, each event (if they come in quick succession) simply restarts the timer 547 until there is a little pause and OnTimer actually gets called and does its work. 548*/ 549{ 550 if( !theApp->amuledlg || !theApp->amuledlg->SafeState()) { 551 return; 552 } 553 bRecreateAll = false; 554 wxPaintEvent paint; 555 ProcessEvent(paint); 556} 557 558// File_checked_for_headers 559