OScopeCtrl.cpp: use Refresh() instead of ProcessEvent() to refresh the UI, fix #146.
[amule.git] / src / OScopeCtrl.cpp
blob9a8b51a82af4bcceceb81859667bffd6d55d3e4a
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.
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.
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
38 #include "Statistics.h"
41 BEGIN_EVENT_TABLE(COScopeCtrl,wxControl)
42 EVT_PAINT(COScopeCtrl::OnPaint)
43 EVT_SIZE(COScopeCtrl::OnSize)
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)
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 = 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;
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 // 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);
105 // Ensure that various size-constraints are calculated (via OnSize).
106 SetClientSize(GetClientSize());
110 COScopeCtrl::~COScopeCtrl()
112 delete [] pdsTrends;
116 void COScopeCtrl::SetRange(float fLower, float fUpper, unsigned iTrend)
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);
126 if (iTrend == 0) {
127 InvalidateCtrl();
128 } else {
129 InvalidateGraph();
134 void COScopeCtrl::SetRanges(float fLower, float fUpper)
136 for (unsigned iTrend = 0; iTrend < nTrends; ++iTrend) {
137 SetRange(fLower, fUpper, iTrend);
142 void COScopeCtrl::SetYUnits(const wxString& strUnits, const wxString& strMin, const wxString& strMax)
144 strYUnits = strUnits;
145 strYMin = strMin;
146 strYMax = strMax;
147 InvalidateGrid();
151 void COScopeCtrl::SetGridColor(const wxColour& cr)
154 if (cr == m_gridColour) {
155 return;
158 m_gridColour = cr;
159 InvalidateGrid() ;
163 void COScopeCtrl::SetPlotColor(const wxColour& cr, unsigned iTrend)
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();
174 void COScopeCtrl::SetBackgroundColor(const wxColour& cr)
177 if (m_bgColour == cr) {
178 return;
181 m_bgColour = cr;
182 brushBack= *(wxTheBrushList->FindOrCreateBrush(cr, wxSOLID));
183 InvalidateCtrl() ;
187 void COScopeCtrl::RecreateGrid()
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;
197 wxMemoryDC dcGrid(m_bmapGrid);
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);
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 m_onPaint = true;
290 // no real plotting work is performed here unless we are coming out of a hidden state;
291 // normally, just putting the existing bitmaps on the client to avoid flicker,
292 // establish a memory dc and then BitBlt it to the client
293 wxBufferedPaintDC dc(this);
295 if (bRecreateAll) {
296 return;
299 if (bRecreateGrid) {
300 RecreateGrid(); // this will also recreate the graph if that flag is set
301 } else if (bRecreateGraph) {
302 RecreateGraph(true);
305 if (nDelayedPoints) { // we've just come out of hiding, so catch up
306 int n = std::min(m_rectPlot.GetWidth(), nDelayedPoints);
307 nDelayedPoints = 0; // (this is more efficient than plotting in the
308 PlotHistory(n, true, false); // background because the bitmap is shifted only
309 } // once for all delayed points together)
311 // We have assured that we have a valid and resized if needed
312 // wxDc and bitmap. Proceed to blit.
313 dc.DrawBitmap(m_bmapGrid, 0, 0, false);
315 // Overwrites the plot section of the image
316 dc.DrawBitmap(m_bmapPlot, m_rectPlot.x, m_rectPlot.y, false);
318 // draw the dotted lines.
319 // This is done last because wxMAC does't support the wxOR logical
320 // operation, preventing us from simply blitting the plot on top of
321 // the grid bitmap.
323 dc.SetPen(*(wxThePenList->FindOrCreatePen(m_gridColour, 1, wxLONG_DASH)));
324 for (unsigned j = 1; j < (nYGrids + 1); ++j) {
325 unsigned GridPos = (m_rectPlot.GetHeight())*j/( nYGrids + 1 ) + m_rectPlot.GetTop();
327 dc.DrawLine(m_rectPlot.GetLeft(), GridPos, m_rectPlot.GetRight(), GridPos);
332 void COScopeCtrl::OnSize(wxSizeEvent& WXUNUSED(evt))
334 // This gets called repeatedly as the user resizes the app;
335 // we use the timer mechanism through InvalidateCtrl to avoid unnecessary redrawing
336 // NOTE: OnSize automatically gets called during the setup of the control
337 if(GetClientRect() == m_rectClient) {
338 return;
341 m_rectClient = GetClientRect();
342 if (m_rectClient.GetWidth() < 1 || m_rectClient.GetHeight() < 1) {
343 return;
346 // the "left" coordinate and "width" will be modified in
347 // InvalidateCtrl to be based on the y axis scaling
348 m_rectPlot.SetLeft(20);
349 m_rectPlot.SetTop(10);
350 m_rectPlot.SetRight(std::max<int>(m_rectPlot.GetLeft() + 1, m_rectClient.GetRight() - 40));
351 m_rectPlot.SetBottom(std::max<int>(m_rectPlot.GetTop() + 1, m_rectClient.GetBottom() - 25));
353 PlotData_t* ppds = pdsTrends;
354 for(unsigned iTrend=0; iTrend<nTrends; ++iTrend, ++ppds) {
355 ppds->fVertScale = (float)m_rectPlot.GetHeight() / (ppds->fUpperLimit-ppds->fLowerLimit);
356 ppds->yPrev = GetPlotY(ppds->fPrev, ppds);
359 if (!m_bmapGrid.IsOk() || (m_rectClient != wxSize(m_bmapGrid.GetWidth(), m_bmapGrid.GetHeight()))) {
360 m_bmapGrid.Create(m_rectClient.GetWidth(), m_rectClient.GetHeight());
362 if (!m_bmapPlot.IsOk() || (m_rectPlot != wxSize(m_bmapPlot.GetWidth(), m_bmapPlot.GetHeight()))) {
363 m_bmapPlot.Create(m_rectPlot.GetWidth(), m_rectPlot.GetHeight());
366 InvalidateCtrl();
370 void COScopeCtrl::ShiftGraph(unsigned cntPoints)
372 wxMemoryDC dcPlot(m_bmapPlot);
374 unsigned cntPixelOffset = cntPoints*nShiftPixels;
375 if (cntPixelOffset >= (unsigned)m_rectPlot.GetWidth()) {
376 cntPixelOffset = m_rectPlot.GetWidth();
377 } else {
378 dcPlot.Blit(0, 0, m_rectPlot.GetWidth(), m_rectPlot.GetHeight(), &dcPlot,
379 cntPixelOffset, 0); // scroll graph to the left
382 // clear a rectangle over the right side of plot prior to adding the new points
383 dcPlot.SetPen(*wxTRANSPARENT_PEN);
384 dcPlot.SetBrush(brushBack); // fill with background color
385 dcPlot.DrawRectangle(m_rectPlot.GetWidth()-cntPixelOffset, 0,
386 cntPixelOffset, m_rectPlot.GetHeight());
390 unsigned COScopeCtrl::GetPlotY(float fPlot, PlotData_t* ppds)
392 if (fPlot <= ppds->fLowerLimit) {
393 return m_rectPlot.GetBottom();
394 } else if (fPlot >= ppds->fUpperLimit) {
395 return m_rectPlot.GetTop() + 1;
396 } else {
397 return m_rectPlot.GetBottom() - (unsigned)((fPlot - ppds->fLowerLimit) * ppds->fVertScale);
402 void COScopeCtrl::DrawPoints(const std::vector<float *> &apf, unsigned cntPoints)
404 // this appends a new set of data points to a graph; all of the plotting is
405 // directed to the memory based bitmap associated with dcPlot
406 // the will subsequently be BitBlt'd to the client in OnPaint
407 // draw the next line segement
408 unsigned y, yPrev;
409 unsigned cntPixelOffset = std::min((unsigned)(m_rectPlot.GetWidth()-1), (cntPoints-1)*nShiftPixels);
410 PlotData_t* ppds = pdsTrends;
412 wxMemoryDC dcPlot(m_bmapPlot);
414 for (unsigned iTrend=0; iTrend<nTrends; ++iTrend, ++ppds) {
415 const float* pf = apf[iTrend] + cntPoints - 1;
416 yPrev = ppds->yPrev;
417 dcPlot.SetPen(ppds->penPlot);
419 for (int x = m_rectPlot.GetRight() - cntPixelOffset; x <= m_rectPlot.GetRight(); x+=nShiftPixels) {
420 y = GetPlotY(*pf--, ppds);
422 // Map onto the smaller bitmap
423 dcPlot.DrawLine(x - nShiftPixels - m_rectPlot.GetX(),
424 yPrev - m_rectPlot.GetY(),
425 x - m_rectPlot.GetX(),
426 y - m_rectPlot.GetY());
428 yPrev = y;
430 ppds->fPrev = *(pf+1);
431 ppds->yPrev = yPrev;
436 #ifndef CLIENT_GUI
437 void COScopeCtrl::PlotHistory(unsigned cntPoints, bool bShiftGraph, bool bRefresh)
439 wxASSERT(graph_type != GRAPH_INVALID);
441 if (graph_type != GRAPH_INVALID) {
442 unsigned i;
443 unsigned cntFilled;
444 std::vector<float *> apf(nTrends);
445 try {
446 for (i = 0; i < nTrends; ++i) {
447 apf[i] = new float[cntPoints];
449 double sFinal = (bStopped ? sLastTimestamp : -1.0);
450 cntFilled = theApp->m_statistics->GetHistory(cntPoints, sLastPeriod, sFinal, apf, graph_type);
451 if (cntFilled >1 || (bShiftGraph && cntFilled!=0)) {
452 if (bShiftGraph) { // delayed points - we have an fPrev
453 ShiftGraph(cntFilled);
454 } else { // fresh graph, we need to preset fPrev, yPrev
455 PlotData_t* ppds = pdsTrends;
456 for(i=0; i<nTrends; ++i, ++ppds)
457 ppds->yPrev = GetPlotY(ppds->fPrev = *(apf[i] + cntFilled - 1), ppds);
458 cntFilled--;
460 DrawPoints(apf, cntFilled);
461 if (bRefresh)
462 Refresh(false);
464 for (i = 0; i < nTrends; ++i) {
465 delete [] apf[i];
467 } catch(std::bad_alloc) {
468 // Failed memory allocation
469 AddLogLineC(wxString(
470 wxT("Error: COScopeCtrl::PlotHistory: Insuficient memory, cntPoints == ")) <<
471 cntPoints << wxT("."));
472 for (i = 0; i < nTrends; ++i) {
473 delete [] apf[i];
476 } else {
477 // No history (yet) for Kad.
480 #else
481 //#warning CORE/GUI -- EC needed
482 void COScopeCtrl::PlotHistory(unsigned, bool, bool)
485 #endif
488 void COScopeCtrl::RecreateGraph(bool bRefresh)
490 bRecreateGraph = false;
491 nDelayedPoints = 0;
493 wxMemoryDC dcPlot(m_bmapPlot);
494 dcPlot.SetBackground(brushBack);
495 dcPlot.Clear();
497 PlotHistory(m_rectPlot.GetWidth(), false, bRefresh);
501 void COScopeCtrl::Reset(double sNewPeriod)
503 bool bStoppedPrev = bStopped;
504 bStopped = false;
505 if (sLastPeriod != sNewPeriod || bStoppedPrev) {
506 sLastPeriod = sNewPeriod;
507 InvalidateCtrl();
512 void COScopeCtrl::Stop()
514 bStopped = true;
515 bRecreateGraph = false;
516 RecreateGrid();
520 void COScopeCtrl::InvalidateCtrl(bool bInvalidateGraph, bool bInvalidateGrid)
522 bRecreateGraph |= bInvalidateGraph;
523 bRecreateGrid |= bInvalidateGrid;
524 // It appears the timerRedraw logic screws up Mac, disable it there
525 #ifndef __WXMAC__
526 // To prevent startup problems don't start timer logic until
527 // a native OnPaint event has been generated.
528 if (m_onPaint) {
529 bRecreateAll |= bInvalidateGraph && bInvalidateGrid;
530 timerRedraw.Start(100, true); // One-shot timer
532 #endif
536 void COScopeCtrl::OnTimer(wxTimerEvent& WXUNUSED(evt))
537 /* The timer is used to consolidate redrawing of the graphs: when the user resizes
538 the application, we get multiple calls to OnSize. If he changes several parameters
539 in the Preferences, we get several individual SetXYZ calls. If we were to try to
540 recreate the graphs for each such event, performance would be sluggish, but with
541 the timer, each event (if they come in quick succession) simply restarts the timer
542 until there is a little pause and OnTimer actually gets called and does its work.
545 if( !theApp->amuledlg || !theApp->amuledlg->SafeState()) {
546 return;
548 bRecreateAll = false;
549 Refresh();
552 // File_checked_for_headers