Upstream tarball 9408
[amule.git] / src / OScopeCtrl.cpp
blob7383020756f8223188d54fbf74de40555eda5c5d
1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002-2008 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.
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.
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
26 #include <cmath>
27 #include <wx/dcmemory.h>
28 #include <wx/dcclient.h>
29 #include <wx/dcbuffer.h>
31 #include <common/Format.h>
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
40 BEGIN_EVENT_TABLE(COScopeCtrl,wxControl)
41 EVT_PAINT(COScopeCtrl::OnPaint)
42 EVT_SIZE(COScopeCtrl::OnSize)
43 EVT_TIMER(TIMER_OSCOPE,COScopeCtrl::OnTimer)
44 END_EVENT_TABLE()
47 const 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 )
58 COScopeCtrl::COScopeCtrl(int cntTrends, int nDecimals, StatsGraphType type, wxWindow* parent)
59 : wxControl(parent, -1, wxDefaultPosition, wxDefaultSize)
60 , timerRedraw(this, TIMER_OSCOPE)
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).
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];
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;
81 bRecreateGraph = bRecreateGrid = bStopped = false;
82 nDelayedPoints = 0;
83 sLastTimestamp = 0.0;
84 sLastPeriod = 1.0;
85 nShiftPixels = 1;
86 nYDecimals = nDecimals;
87 m_bgColour = wxColour( 0, 0, 0) ; // see also SetBackgroundColor
88 m_gridColour = wxColour( 0, 255, 255) ; // see also SetGridColor
89 brushBack = *wxBLACK_BRUSH;
91 strXUnits = wxT("X"); // can also be set with SetXUnits
92 strYUnits = wxT("Y"); // can also be set with SetYUnits
94 nXGrids = 6;
95 nYGrids = 5;
97 graph_type = type;
99 // Ensure that various size-constraints are calculated (via OnSize).
100 SetClientSize(GetClientSize());
104 COScopeCtrl::~COScopeCtrl()
106 delete [] pdsTrends;
110 void COScopeCtrl::SetRange(float fLower, float fUpper, unsigned iTrend)
112 PlotData_t* ppds = pdsTrends+iTrend;
113 if ((ppds->fLowerLimit == fLower) && (ppds->fUpperLimit == fUpper))
114 return;
115 ppds->fLowerLimit = fLower;
116 ppds->fUpperLimit = fUpper;
117 ppds->fVertScale = (float)m_rectPlot.GetHeight() / (fUpper-fLower);
118 ppds->yPrev = GetPlotY(ppds->fPrev, ppds);
120 if (iTrend == 0) {
121 InvalidateCtrl();
122 } else {
123 InvalidateGraph();
128 void COScopeCtrl::SetRanges(float fLower, float fUpper)
130 for (unsigned iTrend = 0; iTrend < nTrends; ++iTrend) {
131 SetRange(fLower, fUpper, iTrend);
136 void COScopeCtrl::SetYUnits(const wxString& strUnits, const wxString& strMin, const wxString& strMax)
138 strYUnits = strUnits;
139 strYMin = strMin;
140 strYMax = strMax;
141 InvalidateGrid();
145 void COScopeCtrl::SetGridColor(const wxColour& cr)
148 if (cr == m_gridColour) {
149 return;
152 m_gridColour = cr;
153 InvalidateGrid() ;
157 void COScopeCtrl::SetPlotColor(const wxColour& cr, unsigned iTrend)
159 PlotData_t* ppds = pdsTrends+iTrend;
160 if (ppds->crPlot == cr)
161 return;
162 ppds->crPlot = cr;
163 ppds->penPlot=*(wxThePenList->FindOrCreatePen(ppds->crPlot, 1, wxSOLID));
164 InvalidateGraph();
168 void COScopeCtrl::SetBackgroundColor(const wxColour& cr)
171 if (m_bgColour == cr) {
172 return;
175 m_bgColour = cr;
176 brushBack= *(wxTheBrushList->FindOrCreateBrush(cr, wxSOLID));
177 InvalidateCtrl() ;
181 void COScopeCtrl::RecreateGrid()
183 // There is a lot of drawing going on here - particularly in terms of
184 // drawing the grid. Don't panic, this is all being drawn (only once)
185 // to a bitmap. The result is then BitBlt'd to the control whenever needed.
186 bRecreateGrid = false;
187 if (m_rectClient.GetWidth() == 0 || m_rectClient.GetHeight() == 0) {
188 return;
191 wxMemoryDC dcGrid(m_bmapGrid);
193 int nCharacters ;
194 wxPen solidPen = *(wxThePenList->FindOrCreatePen(m_gridColour, 1, wxSOLID));
195 wxString strTemp;
197 // fill the grid background
198 dcGrid.SetBrush(brushBack);
199 dcGrid.SetPen(*wxTRANSPARENT_PEN);
200 dcGrid.DrawRectangle(m_rectClient);
201 // draw the plot rectangle: determine how wide the y axis scaling values are,
202 // add the units digit, decimal point, one decimal place, and an extra space
203 nCharacters = std::abs((int)std::log10(std::fabs(pdsTrends[0].fUpperLimit))) ;
204 nCharacters = std::max(nCharacters, std::abs((int)std::log10(std::fabs(pdsTrends[0].fLowerLimit)))) + 4;
206 // adjust the plot rectangle dimensions
207 // assume 6 pixels per character (this may need to be adjusted)
208 m_rectPlot.x = m_rectClient.GetLeft() + 6*7+4;
209 // draw the plot rectangle
210 dcGrid.SetPen(solidPen);
211 dcGrid.DrawRectangle(m_rectPlot.x - 1, m_rectPlot.y - 1, m_rectPlot.GetWidth() + 2, m_rectPlot.GetHeight() + 2);
212 dcGrid.SetPen(wxNullPen);
214 // create some fonts (horizontal and vertical)
215 wxFont axisFont(10, wxSWISS, wxNORMAL, wxNORMAL, false);
216 dcGrid.SetFont(axisFont);
218 // y max
219 dcGrid.SetTextForeground(m_gridColour);
220 if( strYMax.IsEmpty() ) {
221 strTemp = wxString::Format(wxT("%.*f"), nYDecimals, pdsTrends[ 0 ].fUpperLimit);
222 } else {
223 strTemp = strYMax;
225 wxCoord sizX,sizY;
226 dcGrid.GetTextExtent(strTemp,&sizX,&sizY);
227 dcGrid.DrawText(strTemp,m_rectPlot.GetLeft()-4-sizX,m_rectPlot.GetTop()-7);
228 // y min
229 if( strYMin.IsEmpty() ) {
230 strTemp = wxString::Format(wxT("%.*f"), nYDecimals, pdsTrends[ 0 ].fLowerLimit) ;
231 } else {
232 strTemp = strYMin;
234 dcGrid.GetTextExtent(strTemp,&sizX,&sizY);
235 dcGrid.DrawText(strTemp,m_rectPlot.GetLeft()-4-sizX, m_rectPlot.GetBottom());
237 // x units
238 strTemp = CastSecondsToHM((m_rectPlot.GetWidth()/nShiftPixels) * (int)floor(sLastPeriod+0.5));
239 // floor(x + 0.5) is a way of doing round(x) that works with gcc < 3 ...
240 if (bStopped) {
241 strXUnits = CFormat( _("Disabled [%s]") ) % strTemp;
242 } else {
243 strXUnits = strTemp;
246 dcGrid.GetTextExtent(strXUnits,&sizX,&sizY);
247 dcGrid.DrawText(strXUnits,(m_rectPlot.GetLeft() + m_rectPlot.GetRight())/2-sizX/2,m_rectPlot.GetBottom()+4);
249 // y units
250 if (!strYUnits.IsEmpty()) {
251 dcGrid.GetTextExtent(strYUnits,&sizX,&sizY);
252 dcGrid.DrawText(strYUnits, m_rectPlot.GetLeft()-4-sizX, (m_rectPlot.GetTop()+m_rectPlot.GetBottom())/2-sizY/2);
254 // no more drawing to this bitmap is needed until the setting are changed
256 if (bRecreateGraph) {
257 RecreateGraph(false);
260 // finally, force the plot area to redraw
261 Refresh(false);
265 void COScopeCtrl::AppendPoints(double sTimestamp, const std::vector<float *> &apf)
267 sLastTimestamp = sTimestamp;
269 if (nDelayedPoints) {
270 // Ensures that delayed points get added before the new point.
271 // We do this by simply drawing the history up to and including
272 // the new point.
273 int n = std::min(m_rectPlot.GetWidth(), nDelayedPoints + 1);
274 nDelayedPoints = 0;
275 PlotHistory(n, true, false);
276 } else {
277 ShiftGraph(1);
278 DrawPoints(apf, 1);
281 Refresh(false);
285 void COScopeCtrl::OnPaint(wxPaintEvent& WXUNUSED(evt))
287 // no real plotting work is performed here unless we are coming out of a hidden state;
288 // normally, just putting the existing bitmaps on the client to avoid flicker,
289 // establish a memory dc and then BitBlt it to the client
290 if (bRecreateGrid || bRecreateGraph) {
291 timerRedraw.Stop();
293 if (bRecreateGrid) {
294 RecreateGrid(); // this will also recreate the graph if that flag is set
295 } else if (bRecreateGraph) {
296 RecreateGraph(true);
300 if (nDelayedPoints) { // we've just come out of hiding, so catch up
301 int n = std::min(m_rectPlot.GetWidth(), nDelayedPoints);
302 nDelayedPoints = 0; // (this is more efficient than plotting in the
303 PlotHistory(n, true, false); // background because the bitmap is shifted only
304 } // once for all delayed points together)
306 wxBufferedPaintDC dc(this);
308 // We have assured that we have a valid and resized if needed
309 // wxDc and bitmap. Proceed to blit.
310 dc.DrawBitmap(m_bmapGrid, 0, 0, false);
312 // Overwrites the plot section of the image
313 dc.DrawBitmap(m_bmapPlot, m_rectPlot.x, m_rectPlot.y, false);
315 // draw the dotted lines.
316 // This is done last because wxMAC does't support the wxOR logical
317 // operation, preventing us from simply blitting the plot on top of
318 // the grid bitmap.
320 dc.SetPen(*(wxThePenList->FindOrCreatePen(m_gridColour, 1, wxLONG_DASH)));
321 for (unsigned j = 1; j < (nYGrids + 1); ++j) {
322 unsigned GridPos = (m_rectPlot.GetHeight())*j/( nYGrids + 1 ) + m_rectPlot.GetTop();
324 dc.DrawLine(m_rectPlot.GetLeft(), GridPos, m_rectPlot.GetRight(), GridPos);
329 void COScopeCtrl::OnSize(wxSizeEvent& WXUNUSED(evt))
331 // This gets called repeatedly as the user resizes the app;
332 // we use the timer mechanism through InvalidateCtrl to avoid unnecessary redrawing
333 // NOTE: OnSize automatically gets called during the setup of the control
334 if(GetClientRect() == m_rectClient) {
335 return;
338 m_rectClient = GetClientRect();
339 if (m_rectClient.GetWidth() < 1 || m_rectClient.GetHeight() < 1) {
340 return;
343 // the "left" coordinate and "width" will be modified in
344 // InvalidateCtrl to be based on the y axis scaling
345 m_rectPlot.SetLeft(20);
346 m_rectPlot.SetTop(10);
347 m_rectPlot.SetRight(std::max<int>(m_rectPlot.GetLeft() + 1, m_rectClient.GetRight() - 40));
348 m_rectPlot.SetBottom(std::max<int>(m_rectPlot.GetTop() + 1, m_rectClient.GetBottom() - 25));
350 PlotData_t* ppds = pdsTrends;
351 for(unsigned iTrend=0; iTrend<nTrends; ++iTrend, ++ppds) {
352 ppds->fVertScale = (float)m_rectPlot.GetHeight() / (ppds->fUpperLimit-ppds->fLowerLimit);
353 ppds->yPrev = GetPlotY(ppds->fPrev, ppds);
356 if (!m_bmapGrid.IsOk() || (m_rectClient != wxSize(m_bmapGrid.GetWidth(), m_bmapGrid.GetHeight()))) {
357 m_bmapGrid.Create(m_rectClient.GetWidth(), m_rectClient.GetHeight());
359 if (!m_bmapPlot.IsOk() || (m_rectPlot != wxSize(m_bmapPlot.GetWidth(), m_bmapPlot.GetHeight()))) {
360 m_bmapPlot.Create(m_rectPlot.GetWidth(), m_rectPlot.GetHeight());
363 InvalidateCtrl();
367 void COScopeCtrl::ShiftGraph(unsigned cntPoints)
369 wxMemoryDC dcPlot(m_bmapPlot);
371 unsigned cntPixelOffset = cntPoints*nShiftPixels;
372 if (cntPixelOffset >= (unsigned)m_rectPlot.GetWidth()) {
373 cntPixelOffset = m_rectPlot.GetWidth();
374 } else {
375 dcPlot.Blit(0, 0, m_rectPlot.GetWidth(), m_rectPlot.GetHeight(), &dcPlot,
376 cntPixelOffset, 0); // scroll graph to the left
379 // clear a rectangle over the right side of plot prior to adding the new points
380 dcPlot.SetPen(*wxTRANSPARENT_PEN);
381 dcPlot.SetBrush(brushBack); // fill with background color
382 dcPlot.DrawRectangle(m_rectPlot.GetWidth()-cntPixelOffset, 0,
383 cntPixelOffset, m_rectPlot.GetHeight());
387 unsigned COScopeCtrl::GetPlotY(float fPlot, PlotData_t* ppds)
389 if (fPlot <= ppds->fLowerLimit) {
390 return m_rectPlot.GetBottom();
391 } else if (fPlot >= ppds->fUpperLimit) {
392 return m_rectPlot.GetTop() + 1;
393 } else {
394 return m_rectPlot.GetBottom() - (unsigned)((fPlot - ppds->fLowerLimit) * ppds->fVertScale);
399 void COScopeCtrl::DrawPoints(const std::vector<float *> &apf, unsigned cntPoints)
401 // this appends a new set of data points to a graph; all of the plotting is
402 // directed to the memory based bitmap associated with dcPlot
403 // the will subsequently be BitBlt'd to the client in OnPaint
404 // draw the next line segement
405 unsigned y, yPrev;
406 unsigned cntPixelOffset = std::min((unsigned)(m_rectPlot.GetWidth()-1), (cntPoints-1)*nShiftPixels);
407 PlotData_t* ppds = pdsTrends;
409 wxMemoryDC dcPlot(m_bmapPlot);
411 for (unsigned iTrend=0; iTrend<nTrends; ++iTrend, ++ppds) {
412 const float* pf = apf[iTrend] + cntPoints - 1;
413 yPrev = ppds->yPrev;
414 dcPlot.SetPen(ppds->penPlot);
416 for (int x = m_rectPlot.GetRight() - cntPixelOffset; x <= m_rectPlot.GetRight(); x+=nShiftPixels) {
417 y = GetPlotY(*pf--, ppds);
419 // Map onto the smaller bitmap
420 dcPlot.DrawLine(x - nShiftPixels - m_rectPlot.GetX(),
421 yPrev - m_rectPlot.GetY(),
422 x - m_rectPlot.GetX(),
423 y - m_rectPlot.GetY());
425 yPrev = y;
427 ppds->fPrev = *(pf+1);
428 ppds->yPrev = yPrev;
433 #ifndef CLIENT_GUI
434 void COScopeCtrl::PlotHistory(unsigned cntPoints, bool bShiftGraph, bool bRefresh)
436 wxASSERT(graph_type != GRAPH_INVALID);
438 if (graph_type != GRAPH_INVALID) {
439 unsigned i;
440 unsigned cntFilled;
441 std::vector<float *> apf(nTrends);
442 try {
443 for (i = 0; i < nTrends; ++i) {
444 apf[i] = new float[cntPoints];
446 double sFinal = (bStopped ? sLastTimestamp : -1.0);
447 cntFilled = theApp->m_statistics->GetHistory(cntPoints, sLastPeriod, sFinal, apf, graph_type);
448 if (cntFilled >1 || (bShiftGraph && cntFilled!=0)) {
449 if (bShiftGraph) { // delayed points - we have an fPrev
450 ShiftGraph(cntFilled);
451 } else { // fresh graph, we need to preset fPrev, yPrev
452 PlotData_t* ppds = pdsTrends;
453 for(i=0; i<nTrends; ++i, ++ppds)
454 ppds->yPrev = GetPlotY(ppds->fPrev = *(apf[i] + cntFilled - 1), ppds);
455 cntFilled--;
457 DrawPoints(apf, cntFilled);
458 if (bRefresh)
459 Refresh(false);
461 for (i = 0; i < nTrends; ++i) {
462 delete [] apf[i];
464 } catch(std::bad_alloc) {
465 // Failed memory allocation
466 AddLogLineM(true, wxString(
467 wxT("Error: COScopeCtrl::PlotHistory: Insuficient memory, cntPoints == ")) <<
468 cntPoints << wxT("."));
469 for (i = 0; i < nTrends; ++i) {
470 delete [] apf[i];
473 } else {
474 // No history (yet) for Kad.
477 #else
478 //#warning CORE/GUI -- EC needed
479 void COScopeCtrl::PlotHistory(unsigned, bool, bool)
482 #endif
485 void COScopeCtrl::RecreateGraph(bool bRefresh)
487 bRecreateGraph = false;
488 nDelayedPoints = 0;
490 wxMemoryDC dcPlot(m_bmapPlot);
491 dcPlot.SetBackground(brushBack);
492 dcPlot.Clear();
494 PlotHistory(m_rectPlot.GetWidth(), false, bRefresh);
498 void COScopeCtrl::Reset(double sNewPeriod)
500 bool bStoppedPrev = bStopped;
501 bStopped = false;
502 if (sLastPeriod != sNewPeriod || bStoppedPrev) {
503 sLastPeriod = sNewPeriod;
504 InvalidateCtrl();
509 void COScopeCtrl::Stop()
511 bStopped = true;
512 bRecreateGraph = false;
513 RecreateGrid();
517 void COScopeCtrl::InvalidateCtrl(bool bInvalidateGraph, bool bInvalidateGrid)
519 timerRedraw.Stop();
520 timerRedraw.SetOwner(this, TIMER_OSCOPE);
522 bRecreateGraph |= bInvalidateGraph;
523 bRecreateGrid |= bInvalidateGrid;
525 timerRedraw.Start(100);
529 void COScopeCtrl::OnTimer(wxTimerEvent& WXUNUSED(evt))
530 /* The timer is used to consolidate redrawing of the graphs: when the user resizes
531 the application, we get multiple calls to OnSize. If he changes several parameters
532 in the Preferences, we get several individual SetXYZ calls. If we were to try to
533 recreate the graphs for each such event, performance would be sluggish, but with
534 the timer, each event (if they come in quick succession) simply restarts the timer
535 until there is a little pause and OnTimer actually gets called and does its work.
538 if( !theApp->amuledlg || !theApp->amuledlg->SafeState()) {
539 return;
541 timerRedraw.Stop();
542 if (bRecreateGrid) {
543 RecreateGrid(); // this will also recreate the graph if that flag is set
544 } else if (bRecreateGraph) {
545 RecreateGraph(true);
549 // File_checked_for_headers