Upstream tarball 20080414
[amule.git] / src / OScopeCtrl.cpp
blob079a466c89761e500e0d99c318ff90551c38570e
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 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 COLORREF crPreset [ 16 ] = {
48 RGB( 0xFF, 0x00, 0x00 ), RGB( 0xFF, 0xC0, 0xC0 ),
49 RGB( 0xFF, 0xFF, 0x00 ), RGB( 0xFF, 0xA0, 0x00 ),
50 RGB( 0xA0, 0x60, 0x00 ), RGB( 0x00, 0xFF, 0x00 ),
51 RGB( 0x00, 0xA0, 0x00 ), RGB( 0x00, 0x00, 0xFF ),
52 RGB( 0x00, 0xA0, 0xFF ), RGB( 0x00, 0xFF, 0xFF ),
53 RGB( 0x00, 0xA0, 0xA0 ), RGB( 0xC0, 0xC0, 0xFF ),
54 RGB( 0xFF, 0x00, 0xFF ), RGB( 0xA0, 0x00, 0xA0 ),
55 RGB( 0xFF, 0xFF, 0xFF ), RGB( 0x80, 0x80, 0x80 )
59 COScopeCtrl::COScopeCtrl(int cntTrends, int nDecimals, StatsGraphType type, wxWindow* parent)
60 : wxControl(parent, -1, wxDefaultPosition, wxDefaultSize)
61 , timerRedraw(this, TIMER_OSCOPE)
63 // since plotting is based on a LineTo for each new point
64 // we need a starting point (i.e. a "previous" point)
65 // use 0.0 as the default first point.
66 // these are public member variables, and can be changed outside
67 // (after construction).
69 // G.Hayduk: NTrends is the number of trends that will be drawn on
70 // the plot. First 15 plots have predefined colors, but others will
71 // be drawn with white, unless you call SetPlotColor
72 nTrends = cntTrends;
73 pdsTrends = new PlotData_t[nTrends];
75 PlotData_t* ppds = pdsTrends;
76 for(unsigned i=0; i<nTrends; ++i, ++ppds){
77 ppds->crPlot = (i<15 ? crPreset[i] : RGB(255, 255, 255));
78 ppds->penPlot=*(wxThePenList->FindOrCreatePen(WxColourFromCr(ppds->crPlot), 1, wxSOLID));
79 ppds->fPrev = ppds->fLowerLimit = ppds->fUpperLimit = 0.0;
82 bRecreateGraph = bRecreateGrid = bStopped = 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=*(wxTheBrushList->FindOrCreateBrush(m_bgColour, wxSOLID));
92 strXUnits = wxT("X"); // can also be set with SetXUnits
93 strYUnits = wxT("Y"); // can also be set with SetYUnits
95 nXGrids = 6;
96 nYGrids = 5;
98 graph_type = type;
100 // Ensure that various size-constraints are calculated (via OnSize).
101 SetClientSize(GetClientSize());
105 COScopeCtrl::~COScopeCtrl()
107 delete [] pdsTrends;
111 void COScopeCtrl::SetRange(float fLower, float fUpper, unsigned iTrend)
113 PlotData_t* ppds = pdsTrends+iTrend;
114 if ((ppds->fLowerLimit == fLower) && (ppds->fUpperLimit == fUpper))
115 return;
116 ppds->fLowerLimit = fLower;
117 ppds->fUpperLimit = fUpper;
118 ppds->fVertScale = (float)m_rectPlot.GetHeight() / (fUpper-fLower);
119 ppds->yPrev = GetPlotY(ppds->fPrev, ppds);
121 if (iTrend == 0) {
122 InvalidateCtrl();
123 } else {
124 InvalidateGraph();
129 void COScopeCtrl::SetRanges(float fLower, float fUpper)
131 for (unsigned iTrend = 0; iTrend < nTrends; ++iTrend) {
132 SetRange(fLower, fUpper, iTrend);
137 void COScopeCtrl::SetYUnits(const wxString& strUnits, const wxString& strMin, const wxString& strMax)
139 strYUnits = strUnits;
140 strYMin = strMin;
141 strYMax = strMax;
142 InvalidateGrid();
146 void COScopeCtrl::SetGridColor(COLORREF cr)
148 wxColour newCol = WxColourFromCr(cr);
149 if (newCol == m_gridColour) {
150 return;
153 m_gridColour = newCol;
154 InvalidateGrid() ;
158 void COScopeCtrl::SetPlotColor(COLORREF cr, unsigned iTrend)
160 PlotData_t* ppds = pdsTrends+iTrend;
161 if (ppds->crPlot == cr)
162 return;
163 ppds->crPlot = cr;
164 ppds->penPlot=*(wxThePenList->FindOrCreatePen(WxColourFromCr(ppds->crPlot), 1, wxSOLID));
165 InvalidateGraph();
169 void COScopeCtrl::SetBackgroundColor(COLORREF cr)
171 wxColour newCol(WxColourFromCr(cr));
172 if (m_bgColour == newCol) {
173 return;
176 m_bgColour = newCol;
177 brushBack= *(wxTheBrushList->FindOrCreateBrush(newCol, wxSOLID));
178 InvalidateCtrl() ;
182 void COScopeCtrl::RecreateGrid()
184 // There is a lot of drawing going on here - particularly in terms of
185 // drawing the grid. Don't panic, this is all being drawn (only once)
186 // to a bitmap. The result is then BitBlt'd to the control whenever needed.
187 bRecreateGrid = false;
188 if (m_rectClient.GetWidth() == 0 || m_rectClient.GetHeight() == 0) {
189 return;
192 wxMemoryDC dcGrid(m_bmapGrid);
194 int nCharacters ;
195 wxPen solidPen=*(wxThePenList->FindOrCreatePen(m_gridColour, 1, wxSOLID));
196 wxString strTemp;
198 // fill the grid background
199 dcGrid.SetBrush(brushBack);
200 dcGrid.SetPen(*wxTRANSPARENT_PEN);
201 dcGrid.DrawRectangle(m_rectClient);
202 // draw the plot rectangle: determine how wide the y axis scaling values are,
203 // add the units digit, decimal point, one decimal place, and an extra space
204 nCharacters = std::abs((int)std::log10(std::fabs(pdsTrends[0].fUpperLimit))) ;
205 nCharacters = std::max(nCharacters, std::abs((int)std::log10(std::fabs(pdsTrends[0].fLowerLimit)))) + 4;
207 // adjust the plot rectangle dimensions
208 // assume 6 pixels per character (this may need to be adjusted)
209 m_rectPlot.x = m_rectClient.GetLeft() + 6*7+4;
210 // draw the plot rectangle
211 dcGrid.SetPen(solidPen);
212 dcGrid.DrawRectangle(m_rectPlot.x - 1, m_rectPlot.y - 1, m_rectPlot.GetWidth() + 2, m_rectPlot.GetHeight() + 2);
213 dcGrid.SetPen(wxNullPen);
215 // create some fonts (horizontal and vertical)
216 wxFont axisFont(10, wxSWISS, wxNORMAL, wxNORMAL, false);
217 dcGrid.SetFont(axisFont);
219 // y max
220 dcGrid.SetTextForeground(m_gridColour);
221 if( strYMax.IsEmpty() ) {
222 strTemp = wxString::Format(wxT("%.*f"), nYDecimals, pdsTrends[ 0 ].fUpperLimit);
223 } else {
224 strTemp = strYMax;
226 wxCoord sizX,sizY;
227 dcGrid.GetTextExtent(strTemp,&sizX,&sizY);
228 dcGrid.DrawText(strTemp,m_rectPlot.GetLeft()-4-sizX,m_rectPlot.GetTop()-7);
229 // y min
230 if( strYMin.IsEmpty() ) {
231 strTemp = wxString::Format(wxT("%.*f"), nYDecimals, pdsTrends[ 0 ].fLowerLimit) ;
232 } else {
233 strTemp = strYMin;
235 dcGrid.GetTextExtent(strTemp,&sizX,&sizY);
236 dcGrid.DrawText(strTemp,m_rectPlot.GetLeft()-4-sizX, m_rectPlot.GetBottom());
238 // x units
239 strTemp = CastSecondsToHM((m_rectPlot.GetWidth()/nShiftPixels) * (int)floor(sLastPeriod+0.5));
240 // floor(x + 0.5) is a way of doing round(x) that works with gcc < 3 ...
241 if (bStopped) {
242 strXUnits = CFormat( _("Disabled [%s]") ) % strTemp;
243 } else {
244 strXUnits = strTemp;
247 dcGrid.GetTextExtent(strXUnits,&sizX,&sizY);
248 dcGrid.DrawText(strXUnits,(m_rectPlot.GetLeft() + m_rectPlot.GetRight())/2-sizX/2,m_rectPlot.GetBottom()+4);
250 // y units
251 if (!strYUnits.IsEmpty()) {
252 dcGrid.GetTextExtent(strYUnits,&sizX,&sizY);
253 dcGrid.DrawText(strYUnits, m_rectPlot.GetLeft()-4-sizX, (m_rectPlot.GetTop()+m_rectPlot.GetBottom())/2-sizY/2);
255 // no more drawing to this bitmap is needed until the setting are changed
257 if (bRecreateGraph) {
258 RecreateGraph(false);
261 // finally, force the plot area to redraw
262 Refresh(false);
266 void COScopeCtrl::AppendPoints(double sTimestamp, const std::vector<float *> &apf)
268 sLastTimestamp = sTimestamp;
270 if (nDelayedPoints) {
271 // Ensures that delayed points get added before the new point.
272 // We do this by simply drawing the history up to and including
273 // the new point.
274 int n = std::min(m_rectPlot.GetWidth(), nDelayedPoints + 1);
275 nDelayedPoints = 0;
276 PlotHistory(n, true, false);
277 } else {
278 ShiftGraph(1);
279 DrawPoints(apf, 1);
282 Refresh(false);
286 void COScopeCtrl::OnPaint(wxPaintEvent& WXUNUSED(evt))
288 // no real plotting work is performed here unless we are coming out of a hidden state;
289 // normally, just putting the existing bitmaps on the client to avoid flicker,
290 // establish a memory dc and then BitBlt it to the client
291 if (bRecreateGrid || bRecreateGraph) {
292 timerRedraw.Stop();
294 if (bRecreateGrid) {
295 RecreateGrid(); // this will also recreate the graph if that flag is set
296 } else if (bRecreateGraph) {
297 RecreateGraph(true);
301 if (nDelayedPoints) { // we've just come out of hiding, so catch up
302 int n = std::min(m_rectPlot.GetWidth(), nDelayedPoints);
303 nDelayedPoints = 0; // (this is more efficient than plotting in the
304 PlotHistory(n, true, false); // background because the bitmap is shifted only
305 } // once for all delayed points together)
307 wxBufferedPaintDC dc(this);
309 // We have assured that we have a valid and resized if needed
310 // wxDc and bitmap. Proceed to blit.
311 dc.DrawBitmap(m_bmapGrid, 0, 0, false);
313 // Overwrites the plot section of the image
314 dc.DrawBitmap(m_bmapPlot, m_rectPlot.x, m_rectPlot.y, false);
316 // draw the dotted lines.
317 // This is done last because wxMAC does't support the wxOR logical
318 // operation, preventing us from simply blitting the plot on top of
319 // the grid bitmap.
320 wxColour col(m_gridColour);
321 wxPen grPen(col, 1, wxLONG_DASH);
322 dc.SetPen(grPen);
323 for (unsigned j = 1; j < (nYGrids + 1); ++j) {
324 unsigned GridPos = (m_rectPlot.GetHeight())*j/( nYGrids + 1 ) + m_rectPlot.GetTop();
326 dc.DrawLine(m_rectPlot.GetLeft(), GridPos, m_rectPlot.GetRight(), GridPos);
331 void COScopeCtrl::OnSize(wxSizeEvent& WXUNUSED(evt))
333 // This gets called repeatedly as the user resizes the app;
334 // we use the timer mechanism through InvalidateCtrl to avoid unnecessary redrawing
335 // NOTE: OnSize automatically gets called during the setup of the control
336 if(GetClientRect() == m_rectClient) {
337 return;
340 m_rectClient = GetClientRect();
341 if (m_rectClient.GetWidth() < 1 || m_rectClient.GetHeight() < 1) {
342 return;
345 // the "left" coordinate and "width" will be modified in
346 // InvalidateCtrl to be based on the y axis scaling
347 m_rectPlot.SetLeft(20);
348 m_rectPlot.SetTop(10);
349 m_rectPlot.SetRight(std::max<int>(m_rectPlot.GetLeft() + 1, m_rectClient.GetRight() - 40));
350 m_rectPlot.SetBottom(std::max<int>(m_rectPlot.GetTop() + 1, m_rectClient.GetBottom() - 25));
352 PlotData_t* ppds = pdsTrends;
353 for(unsigned iTrend=0; iTrend<nTrends; ++iTrend, ++ppds) {
354 ppds->fVertScale = (float)m_rectPlot.GetHeight() / (ppds->fUpperLimit-ppds->fLowerLimit);
355 ppds->yPrev = GetPlotY(ppds->fPrev, ppds);
358 if (!m_bmapGrid.IsOk() || (m_rectClient != wxSize(m_bmapGrid.GetWidth(), m_bmapGrid.GetHeight()))) {
359 m_bmapGrid.Create(m_rectClient.GetWidth(), m_rectClient.GetHeight());
361 if (!m_bmapPlot.IsOk() || (m_rectPlot != wxSize(m_bmapPlot.GetWidth(), m_bmapPlot.GetHeight()))) {
362 m_bmapPlot.Create(m_rectPlot.GetWidth(), m_rectPlot.GetHeight());
365 InvalidateCtrl();
369 void COScopeCtrl::ShiftGraph(unsigned cntPoints)
371 wxMemoryDC dcPlot(m_bmapPlot);
373 unsigned cntPixelOffset = cntPoints*nShiftPixels;
374 if (cntPixelOffset >= (unsigned)m_rectPlot.GetWidth()) {
375 cntPixelOffset = m_rectPlot.GetWidth();
376 } else {
377 dcPlot.Blit(0, 0, m_rectPlot.GetWidth(), m_rectPlot.GetHeight(), &dcPlot,
378 cntPixelOffset, 0); // scroll graph to the left
381 // clear a rectangle over the right side of plot prior to adding the new points
382 dcPlot.SetPen(*wxTRANSPARENT_PEN);
383 dcPlot.SetBrush(brushBack); // fill with background color
384 dcPlot.DrawRectangle(m_rectPlot.GetWidth()-cntPixelOffset, 0,
385 cntPixelOffset, m_rectPlot.GetHeight());
389 unsigned COScopeCtrl::GetPlotY(float fPlot, PlotData_t* ppds)
391 if (fPlot <= ppds->fLowerLimit) {
392 return m_rectPlot.GetBottom();
393 } else if (fPlot >= ppds->fUpperLimit) {
394 return m_rectPlot.GetTop() + 1;
395 } else {
396 return m_rectPlot.GetBottom() - (unsigned)((fPlot - ppds->fLowerLimit) * ppds->fVertScale);
401 void COScopeCtrl::DrawPoints(const std::vector<float *> &apf, unsigned cntPoints)
403 // this appends a new set of data points to a graph; all of the plotting is
404 // directed to the memory based bitmap associated with dcPlot
405 // the will subsequently be BitBlt'd to the client in OnPaint
406 // draw the next line segement
407 unsigned y, yPrev;
408 unsigned cntPixelOffset = std::min((unsigned)(m_rectPlot.GetWidth()-1), (cntPoints-1)*nShiftPixels);
409 PlotData_t* ppds = pdsTrends;
411 wxMemoryDC dcPlot(m_bmapPlot);
413 for (unsigned iTrend=0; iTrend<nTrends; ++iTrend, ++ppds) {
414 const float* pf = apf[iTrend] + cntPoints - 1;
415 yPrev = ppds->yPrev;
416 dcPlot.SetPen(ppds->penPlot);
418 for (int x = m_rectPlot.GetRight() - cntPixelOffset; x <= m_rectPlot.GetRight(); x+=nShiftPixels) {
419 y = GetPlotY(*pf--, ppds);
421 // Map onto the smaller bitmap
422 dcPlot.DrawLine(x - nShiftPixels - m_rectPlot.GetX(),
423 yPrev - m_rectPlot.GetY(),
424 x - m_rectPlot.GetX(),
425 y - m_rectPlot.GetY());
427 yPrev = y;
429 ppds->fPrev = *(pf+1);
430 ppds->yPrev = yPrev;
435 #ifndef CLIENT_GUI
436 void COScopeCtrl::PlotHistory(unsigned cntPoints, bool bShiftGraph, bool bRefresh)
438 wxASSERT(graph_type != GRAPH_INVALID);
440 if (graph_type != GRAPH_INVALID) {
441 unsigned i;
442 unsigned cntFilled;
443 std::vector<float *> apf(nTrends);
444 try {
445 for (i = 0; i < nTrends; ++i) {
446 apf[i] = new float[cntPoints];
448 double sFinal = (bStopped ? sLastTimestamp : -1.0);
449 cntFilled = theApp->m_statistics->GetHistory(cntPoints, sLastPeriod, sFinal, apf, graph_type);
450 if (cntFilled >1 || (bShiftGraph && cntFilled!=0)) {
451 if (bShiftGraph) { // delayed points - we have an fPrev
452 ShiftGraph(cntFilled);
453 } else { // fresh graph, we need to preset fPrev, yPrev
454 PlotData_t* ppds = pdsTrends;
455 for(i=0; i<nTrends; ++i, ++ppds)
456 ppds->yPrev = GetPlotY(ppds->fPrev = *(apf[i] + cntFilled - 1), ppds);
457 cntFilled--;
459 DrawPoints(apf, cntFilled);
460 if (bRefresh)
461 Refresh(false);
463 for (i = 0; i < nTrends; ++i) {
464 delete [] apf[i];
466 } catch(std::bad_alloc) {
467 // Failed memory allocation
468 AddLogLineM(true, wxString(
469 wxT("Error: COScopeCtrl::PlotHistory: Insuficient memory, cntPoints == ")) <<
470 cntPoints << wxT("."));
471 for (i = 0; i < nTrends; ++i) {
472 delete [] apf[i];
475 } else {
476 // No history (yet) for Kad.
479 #else
480 //#warning CORE/GUI -- EC needed
481 void COScopeCtrl::PlotHistory(unsigned, bool, bool)
484 #endif
487 void COScopeCtrl::RecreateGraph(bool bRefresh)
489 bRecreateGraph = false;
490 nDelayedPoints = 0;
492 wxMemoryDC dcPlot(m_bmapPlot);
493 dcPlot.SetBackground(brushBack);
494 dcPlot.Clear();
496 PlotHistory(m_rectPlot.GetWidth(), false, bRefresh);
500 void COScopeCtrl::Reset(double sNewPeriod)
502 bool bStoppedPrev = bStopped;
503 bStopped = false;
504 if (sLastPeriod != sNewPeriod || bStoppedPrev) {
505 sLastPeriod = sNewPeriod;
506 InvalidateCtrl();
511 void COScopeCtrl::Stop()
513 bStopped = true;
514 bRecreateGraph = false;
515 RecreateGrid();
519 void COScopeCtrl::InvalidateCtrl(bool bInvalidateGraph, bool bInvalidateGrid)
521 timerRedraw.Stop();
522 timerRedraw.SetOwner(this, TIMER_OSCOPE);
524 bRecreateGraph |= bInvalidateGraph;
525 bRecreateGrid |= bInvalidateGrid;
527 timerRedraw.Start(100);
531 void COScopeCtrl::OnTimer(wxTimerEvent& WXUNUSED(evt))
532 /* The timer is used to consolidate redrawing of the graphs: when the user resizes
533 the application, we get multiple calls to OnSize. If he changes several parameters
534 in the Preferences, we get several individual SetXYZ calls. If we were to try to
535 recreate the graphs for each such event, performance would be sluggish, but with
536 the timer, each event (if they come in quick succession) simply restarts the timer
537 until there is a little pause and OnTimer actually gets called and does its work.
540 if( !theApp->amuledlg || !theApp->amuledlg->SafeState()) {
541 return;
543 timerRedraw.Stop();
544 if (bRecreateGrid) {
545 RecreateGrid(); // this will also recreate the graph if that flag is set
546 } else if (bRecreateGraph) {
547 RecreateGraph(true);
551 // File_checked_for_headers