Upstream tarball 10153
[amule.git] / src / OScopeCtrl.cpp
blob1496588b281344453a54e9f10c458af8e930d7f3
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 END_EVENT_TABLE()
46 const wxColour crPreset [ 16 ] = {
47 wxColour( 0xFF, 0x00, 0x00 ), wxColour( 0xFF, 0xC0, 0xC0 ),
48 wxColour( 0xFF, 0xFF, 0x00 ), wxColour( 0xFF, 0xA0, 0x00 ),
49 wxColour( 0xA0, 0x60, 0x00 ), wxColour( 0x00, 0xFF, 0x00 ),
50 wxColour( 0x00, 0xA0, 0x00 ), wxColour( 0x00, 0x00, 0xFF ),
51 wxColour( 0x00, 0xA0, 0xFF ), wxColour( 0x00, 0xFF, 0xFF ),
52 wxColour( 0x00, 0xA0, 0xA0 ), wxColour( 0xC0, 0xC0, 0xFF ),
53 wxColour( 0xFF, 0x00, 0xFF ), wxColour( 0xA0, 0x00, 0xA0 ),
54 wxColour( 0xFF, 0xFF, 0xFF ), wxColour( 0x80, 0x80, 0x80 )
57 COScopeCtrl::COScopeCtrl(int cntTrends, int nDecimals, StatsGraphType type, wxWindow* parent)
58 : wxControl(parent, -1, wxDefaultPosition, wxDefaultSize)
59 , timerRedraw(this)
61 // since plotting is based on a LineTo for each new point
62 // we need a starting point (i.e. a "previous" point)
63 // use 0.0 as the default first point.
64 // these are public member variables, and can be changed outside
65 // (after construction).
67 // G.Hayduk: NTrends is the number of trends that will be drawn on
68 // the plot. First 15 plots have predefined colors, but others will
69 // be drawn with white, unless you call SetPlotColor
70 nTrends = cntTrends;
71 pdsTrends = new PlotData_t[nTrends];
73 PlotData_t* ppds = pdsTrends;
74 for(unsigned i=0; i<nTrends; ++i, ++ppds){
75 ppds->crPlot = (i<15 ? crPreset[i] : *wxWHITE);
76 ppds->penPlot=*(wxThePenList->FindOrCreatePen(ppds->crPlot, 1, wxSOLID));
77 ppds->fPrev = ppds->fLowerLimit = ppds->fUpperLimit = 0.0;
80 bRecreateGraph = bRecreateGrid = bRecreateAll = bStopped = false;
81 m_onPaint = 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 // Connect the timer (dynamically, so the Controls don't have to share a common timer id)
100 Connect(timerRedraw.GetId(), wxEVT_TIMER, (wxObjectEventFunction) &COScopeCtrl::OnTimer);
101 // Don't draw background (avoid ugly flickering on wxMSW on resize)
102 SetBackgroundStyle(wxBG_STYLE_CUSTOM);
104 // Ensure that various size-constraints are calculated (via OnSize).
105 SetClientSize(GetClientSize());
109 COScopeCtrl::~COScopeCtrl()
111 delete [] pdsTrends;
115 void COScopeCtrl::SetRange(float fLower, float fUpper, unsigned iTrend)
117 PlotData_t* ppds = pdsTrends+iTrend;
118 if ((ppds->fLowerLimit == fLower) && (ppds->fUpperLimit == fUpper))
119 return;
120 ppds->fLowerLimit = fLower;
121 ppds->fUpperLimit = fUpper;
122 ppds->fVertScale = (float)m_rectPlot.GetHeight() / (fUpper-fLower);
123 ppds->yPrev = GetPlotY(ppds->fPrev, ppds);
125 if (iTrend == 0) {
126 InvalidateCtrl();
127 } else {
128 InvalidateGraph();
133 void COScopeCtrl::SetRanges(float fLower, float fUpper)
135 for (unsigned iTrend = 0; iTrend < nTrends; ++iTrend) {
136 SetRange(fLower, fUpper, iTrend);
141 void COScopeCtrl::SetYUnits(const wxString& strUnits, const wxString& strMin, const wxString& strMax)
143 strYUnits = strUnits;
144 strYMin = strMin;
145 strYMax = strMax;
146 InvalidateGrid();
150 void COScopeCtrl::SetGridColor(const wxColour& cr)
153 if (cr == m_gridColour) {
154 return;
157 m_gridColour = cr;
158 InvalidateGrid() ;
162 void COScopeCtrl::SetPlotColor(const wxColour& cr, unsigned iTrend)
164 PlotData_t* ppds = pdsTrends+iTrend;
165 if (ppds->crPlot == cr)
166 return;
167 ppds->crPlot = cr;
168 ppds->penPlot=*(wxThePenList->FindOrCreatePen(ppds->crPlot, 1, wxSOLID));
169 InvalidateGraph();
173 void COScopeCtrl::SetBackgroundColor(const wxColour& cr)
176 if (m_bgColour == cr) {
177 return;
180 m_bgColour = cr;
181 brushBack= *(wxTheBrushList->FindOrCreateBrush(cr, wxSOLID));
182 InvalidateCtrl() ;
186 void COScopeCtrl::RecreateGrid()
188 // There is a lot of drawing going on here - particularly in terms of
189 // drawing the grid. Don't panic, this is all being drawn (only once)
190 // to a bitmap. The result is then BitBlt'd to the control whenever needed.
191 bRecreateGrid = false;
192 if (m_rectClient.GetWidth() == 0 || m_rectClient.GetHeight() == 0) {
193 return;
196 wxMemoryDC dcGrid(m_bmapGrid);
198 int nCharacters ;
199 wxPen solidPen = *(wxThePenList->FindOrCreatePen(m_gridColour, 1, wxSOLID));
200 wxString strTemp;
202 // fill the grid background
203 dcGrid.SetBrush(brushBack);
204 dcGrid.SetPen(*wxTRANSPARENT_PEN);
205 dcGrid.DrawRectangle(m_rectClient);
206 // draw the plot rectangle: determine how wide the y axis scaling values are,
207 // add the units digit, decimal point, one decimal place, and an extra space
208 nCharacters = std::abs((int)std::log10(std::fabs(pdsTrends[0].fUpperLimit))) ;
209 nCharacters = std::max(nCharacters, std::abs((int)std::log10(std::fabs(pdsTrends[0].fLowerLimit)))) + 4;
211 // adjust the plot rectangle dimensions
212 // assume 6 pixels per character (this may need to be adjusted)
213 m_rectPlot.x = m_rectClient.GetLeft() + 6*7+4;
214 // draw the plot rectangle
215 dcGrid.SetPen(solidPen);
216 dcGrid.DrawRectangle(m_rectPlot.x - 1, m_rectPlot.y - 1, m_rectPlot.GetWidth() + 2, m_rectPlot.GetHeight() + 2);
217 dcGrid.SetPen(wxNullPen);
219 // create some fonts (horizontal and vertical)
220 wxFont axisFont(10, wxSWISS, wxNORMAL, wxNORMAL, false);
221 dcGrid.SetFont(axisFont);
223 // y max
224 dcGrid.SetTextForeground(m_gridColour);
225 if( strYMax.IsEmpty() ) {
226 strTemp = wxString::Format(wxT("%.*f"), nYDecimals, pdsTrends[ 0 ].fUpperLimit);
227 } else {
228 strTemp = strYMax;
230 wxCoord sizX,sizY;
231 dcGrid.GetTextExtent(strTemp,&sizX,&sizY);
232 dcGrid.DrawText(strTemp,m_rectPlot.GetLeft()-4-sizX,m_rectPlot.GetTop()-7);
233 // y min
234 if( strYMin.IsEmpty() ) {
235 strTemp = wxString::Format(wxT("%.*f"), nYDecimals, pdsTrends[ 0 ].fLowerLimit) ;
236 } else {
237 strTemp = strYMin;
239 dcGrid.GetTextExtent(strTemp,&sizX,&sizY);
240 dcGrid.DrawText(strTemp,m_rectPlot.GetLeft()-4-sizX, m_rectPlot.GetBottom());
242 // x units
243 strTemp = CastSecondsToHM((m_rectPlot.GetWidth()/nShiftPixels) * (int)floor(sLastPeriod+0.5));
244 // floor(x + 0.5) is a way of doing round(x) that works with gcc < 3 ...
245 if (bStopped) {
246 strXUnits = CFormat( _("Disabled [%s]") ) % strTemp;
247 } else {
248 strXUnits = strTemp;
251 dcGrid.GetTextExtent(strXUnits,&sizX,&sizY);
252 dcGrid.DrawText(strXUnits,(m_rectPlot.GetLeft() + m_rectPlot.GetRight())/2-sizX/2,m_rectPlot.GetBottom()+4);
254 // y units
255 if (!strYUnits.IsEmpty()) {
256 dcGrid.GetTextExtent(strYUnits,&sizX,&sizY);
257 dcGrid.DrawText(strYUnits, m_rectPlot.GetLeft()-4-sizX, (m_rectPlot.GetTop()+m_rectPlot.GetBottom())/2-sizY/2);
259 // no more drawing to this bitmap is needed until the setting are changed
261 if (bRecreateGraph) {
262 RecreateGraph(false);
265 // finally, force the plot area to redraw
266 Refresh(false);
270 void COScopeCtrl::AppendPoints(double sTimestamp, const std::vector<float *> &apf)
272 sLastTimestamp = sTimestamp;
274 if (nDelayedPoints) {
275 // Ensures that delayed points get added before the new point.
276 // We do this by simply drawing the history up to and including
277 // the new point.
278 int n = std::min(m_rectPlot.GetWidth(), nDelayedPoints + 1);
279 nDelayedPoints = 0;
280 PlotHistory(n, true, false);
281 } else {
282 ShiftGraph(1);
283 DrawPoints(apf, 1);
286 Refresh(false);
290 void COScopeCtrl::OnPaint(wxPaintEvent& WXUNUSED(evt))
292 m_onPaint = true;
294 // no real plotting work is performed here unless we are coming out of a hidden state;
295 // normally, just putting the existing bitmaps on the client to avoid flicker,
296 // establish a memory dc and then BitBlt it to the client
297 wxBufferedPaintDC dc(this);
299 if (bRecreateAll) {
300 return;
303 if (bRecreateGrid) {
304 RecreateGrid(); // this will also recreate the graph if that flag is set
305 } else if (bRecreateGraph) {
306 RecreateGraph(true);
309 if (nDelayedPoints) { // we've just come out of hiding, so catch up
310 int n = std::min(m_rectPlot.GetWidth(), nDelayedPoints);
311 nDelayedPoints = 0; // (this is more efficient than plotting in the
312 PlotHistory(n, true, false); // background because the bitmap is shifted only
313 } // once for all delayed points together)
315 // We have assured that we have a valid and resized if needed
316 // wxDc and bitmap. Proceed to blit.
317 dc.DrawBitmap(m_bmapGrid, 0, 0, false);
319 // Overwrites the plot section of the image
320 dc.DrawBitmap(m_bmapPlot, m_rectPlot.x, m_rectPlot.y, false);
322 // draw the dotted lines.
323 // This is done last because wxMAC does't support the wxOR logical
324 // operation, preventing us from simply blitting the plot on top of
325 // the grid bitmap.
327 dc.SetPen(*(wxThePenList->FindOrCreatePen(m_gridColour, 1, wxLONG_DASH)));
328 for (unsigned j = 1; j < (nYGrids + 1); ++j) {
329 unsigned GridPos = (m_rectPlot.GetHeight())*j/( nYGrids + 1 ) + m_rectPlot.GetTop();
331 dc.DrawLine(m_rectPlot.GetLeft(), GridPos, m_rectPlot.GetRight(), GridPos);
336 void COScopeCtrl::OnSize(wxSizeEvent& WXUNUSED(evt))
338 // This gets called repeatedly as the user resizes the app;
339 // we use the timer mechanism through InvalidateCtrl to avoid unnecessary redrawing
340 // NOTE: OnSize automatically gets called during the setup of the control
341 if(GetClientRect() == m_rectClient) {
342 return;
345 m_rectClient = GetClientRect();
346 if (m_rectClient.GetWidth() < 1 || m_rectClient.GetHeight() < 1) {
347 return;
350 // the "left" coordinate and "width" will be modified in
351 // InvalidateCtrl to be based on the y axis scaling
352 m_rectPlot.SetLeft(20);
353 m_rectPlot.SetTop(10);
354 m_rectPlot.SetRight(std::max<int>(m_rectPlot.GetLeft() + 1, m_rectClient.GetRight() - 40));
355 m_rectPlot.SetBottom(std::max<int>(m_rectPlot.GetTop() + 1, m_rectClient.GetBottom() - 25));
357 PlotData_t* ppds = pdsTrends;
358 for(unsigned iTrend=0; iTrend<nTrends; ++iTrend, ++ppds) {
359 ppds->fVertScale = (float)m_rectPlot.GetHeight() / (ppds->fUpperLimit-ppds->fLowerLimit);
360 ppds->yPrev = GetPlotY(ppds->fPrev, ppds);
363 if (!m_bmapGrid.IsOk() || (m_rectClient != wxSize(m_bmapGrid.GetWidth(), m_bmapGrid.GetHeight()))) {
364 m_bmapGrid.Create(m_rectClient.GetWidth(), m_rectClient.GetHeight());
366 if (!m_bmapPlot.IsOk() || (m_rectPlot != wxSize(m_bmapPlot.GetWidth(), m_bmapPlot.GetHeight()))) {
367 m_bmapPlot.Create(m_rectPlot.GetWidth(), m_rectPlot.GetHeight());
370 InvalidateCtrl();
374 void COScopeCtrl::ShiftGraph(unsigned cntPoints)
376 wxMemoryDC dcPlot(m_bmapPlot);
378 unsigned cntPixelOffset = cntPoints*nShiftPixels;
379 if (cntPixelOffset >= (unsigned)m_rectPlot.GetWidth()) {
380 cntPixelOffset = m_rectPlot.GetWidth();
381 } else {
382 dcPlot.Blit(0, 0, m_rectPlot.GetWidth(), m_rectPlot.GetHeight(), &dcPlot,
383 cntPixelOffset, 0); // scroll graph to the left
386 // clear a rectangle over the right side of plot prior to adding the new points
387 dcPlot.SetPen(*wxTRANSPARENT_PEN);
388 dcPlot.SetBrush(brushBack); // fill with background color
389 dcPlot.DrawRectangle(m_rectPlot.GetWidth()-cntPixelOffset, 0,
390 cntPixelOffset, m_rectPlot.GetHeight());
394 unsigned COScopeCtrl::GetPlotY(float fPlot, PlotData_t* ppds)
396 if (fPlot <= ppds->fLowerLimit) {
397 return m_rectPlot.GetBottom();
398 } else if (fPlot >= ppds->fUpperLimit) {
399 return m_rectPlot.GetTop() + 1;
400 } else {
401 return m_rectPlot.GetBottom() - (unsigned)((fPlot - ppds->fLowerLimit) * ppds->fVertScale);
406 void COScopeCtrl::DrawPoints(const std::vector<float *> &apf, unsigned cntPoints)
408 // this appends a new set of data points to a graph; all of the plotting is
409 // directed to the memory based bitmap associated with dcPlot
410 // the will subsequently be BitBlt'd to the client in OnPaint
411 // draw the next line segement
412 unsigned y, yPrev;
413 unsigned cntPixelOffset = std::min((unsigned)(m_rectPlot.GetWidth()-1), (cntPoints-1)*nShiftPixels);
414 PlotData_t* ppds = pdsTrends;
416 wxMemoryDC dcPlot(m_bmapPlot);
418 for (unsigned iTrend=0; iTrend<nTrends; ++iTrend, ++ppds) {
419 const float* pf = apf[iTrend] + cntPoints - 1;
420 yPrev = ppds->yPrev;
421 dcPlot.SetPen(ppds->penPlot);
423 for (int x = m_rectPlot.GetRight() - cntPixelOffset; x <= m_rectPlot.GetRight(); x+=nShiftPixels) {
424 y = GetPlotY(*pf--, ppds);
426 // Map onto the smaller bitmap
427 dcPlot.DrawLine(x - nShiftPixels - m_rectPlot.GetX(),
428 yPrev - m_rectPlot.GetY(),
429 x - m_rectPlot.GetX(),
430 y - m_rectPlot.GetY());
432 yPrev = y;
434 ppds->fPrev = *(pf+1);
435 ppds->yPrev = yPrev;
440 #ifndef CLIENT_GUI
441 void COScopeCtrl::PlotHistory(unsigned cntPoints, bool bShiftGraph, bool bRefresh)
443 wxASSERT(graph_type != GRAPH_INVALID);
445 if (graph_type != GRAPH_INVALID) {
446 unsigned i;
447 unsigned cntFilled;
448 std::vector<float *> apf(nTrends);
449 try {
450 for (i = 0; i < nTrends; ++i) {
451 apf[i] = new float[cntPoints];
453 double sFinal = (bStopped ? sLastTimestamp : -1.0);
454 cntFilled = theApp->m_statistics->GetHistory(cntPoints, sLastPeriod, sFinal, apf, graph_type);
455 if (cntFilled >1 || (bShiftGraph && cntFilled!=0)) {
456 if (bShiftGraph) { // delayed points - we have an fPrev
457 ShiftGraph(cntFilled);
458 } else { // fresh graph, we need to preset fPrev, yPrev
459 PlotData_t* ppds = pdsTrends;
460 for(i=0; i<nTrends; ++i, ++ppds)
461 ppds->yPrev = GetPlotY(ppds->fPrev = *(apf[i] + cntFilled - 1), ppds);
462 cntFilled--;
464 DrawPoints(apf, cntFilled);
465 if (bRefresh)
466 Refresh(false);
468 for (i = 0; i < nTrends; ++i) {
469 delete [] apf[i];
471 } catch(std::bad_alloc) {
472 // Failed memory allocation
473 AddLogLineM(true, wxString(
474 wxT("Error: COScopeCtrl::PlotHistory: Insuficient memory, cntPoints == ")) <<
475 cntPoints << wxT("."));
476 for (i = 0; i < nTrends; ++i) {
477 delete [] apf[i];
480 } else {
481 // No history (yet) for Kad.
484 #else
485 //#warning CORE/GUI -- EC needed
486 void COScopeCtrl::PlotHistory(unsigned, bool, bool)
489 #endif
492 void COScopeCtrl::RecreateGraph(bool bRefresh)
494 bRecreateGraph = false;
495 nDelayedPoints = 0;
497 wxMemoryDC dcPlot(m_bmapPlot);
498 dcPlot.SetBackground(brushBack);
499 dcPlot.Clear();
501 PlotHistory(m_rectPlot.GetWidth(), false, bRefresh);
505 void COScopeCtrl::Reset(double sNewPeriod)
507 bool bStoppedPrev = bStopped;
508 bStopped = false;
509 if (sLastPeriod != sNewPeriod || bStoppedPrev) {
510 sLastPeriod = sNewPeriod;
511 InvalidateCtrl();
516 void COScopeCtrl::Stop()
518 bStopped = true;
519 bRecreateGraph = false;
520 RecreateGrid();
524 void COScopeCtrl::InvalidateCtrl(bool bInvalidateGraph, bool bInvalidateGrid)
526 bRecreateGraph |= bInvalidateGraph;
527 bRecreateGrid |= bInvalidateGrid;
528 // It appears the timerRedraw logic screws up Mac, disable it there
529 #ifndef __WXMAC__
530 // To prevent startup problems don't start timer logic until
531 // a native OnPaint event has been generated.
532 if (m_onPaint) {
533 bRecreateAll |= bInvalidateGraph && bInvalidateGrid;
534 timerRedraw.Start(100, true); // One-shot timer
536 #endif
540 void COScopeCtrl::OnTimer(wxTimerEvent& WXUNUSED(evt))
541 /* The timer is used to consolidate redrawing of the graphs: when the user resizes
542 the application, we get multiple calls to OnSize. If he changes several parameters
543 in the Preferences, we get several individual SetXYZ calls. If we were to try to
544 recreate the graphs for each such event, performance would be sluggish, but with
545 the timer, each event (if they come in quick succession) simply restarts the timer
546 until there is a little pause and OnTimer actually gets called and does its work.
549 if( !theApp->amuledlg || !theApp->amuledlg->SafeState()) {
550 return;
552 bRecreateAll = false;
553 wxPaintEvent paint;
554 ProcessEvent(paint);
557 // File_checked_for_headers