Improve speed of category tab title updates
[amule.git] / src / OScopeCtrl.cpp
blobd68afe72fe126251143087748f983b05239a6808
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
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 int nCharacters ;
200 wxPen solidPen = *(wxThePenList->FindOrCreatePen(m_gridColour, 1, wxSOLID));
201 wxString strTemp;
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;
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);
220 // create some fonts (horizontal and vertical)
221 wxFont axisFont(10, wxSWISS, wxNORMAL, wxNORMAL, false);
222 dcGrid.SetFont(axisFont);
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;
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;
240 dcGrid.GetTextExtent(strTemp,&sizX,&sizY);
241 dcGrid.DrawText(strTemp,m_rectPlot.GetLeft()-4-sizX, m_rectPlot.GetBottom());
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;
252 dcGrid.GetTextExtent(strXUnits,&sizX,&sizY);
253 dcGrid.DrawText(strXUnits,(m_rectPlot.GetLeft() + m_rectPlot.GetRight())/2-sizX/2,m_rectPlot.GetBottom()+4);
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);
260 // no more drawing to this bitmap is needed until the setting are changed
262 if (bRecreateGraph) {
263 RecreateGraph(false);
266 // finally, force the plot area to redraw
267 Refresh(false);
271 void COScopeCtrl::AppendPoints(double sTimestamp, const std::vector<float *> &apf)
273 sLastTimestamp = sTimestamp;
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);
287 Refresh(false);
291 void COScopeCtrl::OnPaint(wxPaintEvent& WXUNUSED(evt))
293 m_onPaint = true;
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);
300 if (bRecreateAll) {
301 return;
304 if (bRecreateGrid) {
305 RecreateGrid(); // this will also recreate the graph if that flag is set
306 } else if (bRecreateGraph) {
307 RecreateGraph(true);
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)
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);
320 // Overwrites the plot section of the image
321 dc.DrawBitmap(m_bmapPlot, m_rectPlot.x, m_rectPlot.y, false);
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.
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();
332 dc.DrawLine(m_rectPlot.GetLeft(), GridPos, m_rectPlot.GetRight(), GridPos);
337 void COScopeCtrl::OnSize(wxSizeEvent& WXUNUSED(evt))
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;
346 m_rectClient = GetClientRect();
347 if (m_rectClient.GetWidth() < 1 || m_rectClient.GetHeight() < 1) {
348 return;
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));
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);
364 if (!m_bmapGrid.IsOk() || (m_rectClient != wxSize(m_bmapGrid.GetWidth(), m_bmapGrid.GetHeight()))) {
365 m_bmapGrid.Create(m_rectClient.GetWidth(), m_rectClient.GetHeight());
367 if (!m_bmapPlot.IsOk() || (m_rectPlot != wxSize(m_bmapPlot.GetWidth(), m_bmapPlot.GetHeight()))) {
368 m_bmapPlot.Create(m_rectPlot.GetWidth(), m_rectPlot.GetHeight());
371 InvalidateCtrl();
375 void COScopeCtrl::ShiftGraph(unsigned cntPoints)
377 wxMemoryDC dcPlot(m_bmapPlot);
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
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());
395 unsigned COScopeCtrl::GetPlotY(float fPlot, PlotData_t* ppds)
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);
407 void COScopeCtrl::DrawPoints(const std::vector<float *> &apf, unsigned cntPoints)
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;
417 wxMemoryDC dcPlot(m_bmapPlot);
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);
424 for (int x = m_rectPlot.GetRight() - cntPixelOffset; x <= m_rectPlot.GetRight(); x+=nShiftPixels) {
425 y = GetPlotY(*pf--, ppds);
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());
433 yPrev = y;
435 ppds->fPrev = *(pf+1);
436 ppds->yPrev = yPrev;
441 #ifndef CLIENT_GUI
442 void COScopeCtrl::PlotHistory(unsigned cntPoints, bool bShiftGraph, bool bRefresh)
444 wxASSERT(graph_type != GRAPH_INVALID);
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];
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--;
465 DrawPoints(apf, cntFilled);
466 if (bRefresh)
467 Refresh(false);
469 for (i = 0; i < nTrends; ++i) {
470 delete [] apf[i];
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];
481 } else {
482 // No history (yet) for Kad.
485 #else
486 //#warning CORE/GUI -- EC needed
487 void COScopeCtrl::PlotHistory(unsigned, bool, bool)
490 #endif
493 void COScopeCtrl::RecreateGraph(bool bRefresh)
495 bRecreateGraph = false;
496 nDelayedPoints = 0;
498 wxMemoryDC dcPlot(m_bmapPlot);
499 dcPlot.SetBackground(brushBack);
500 dcPlot.Clear();
502 PlotHistory(m_rectPlot.GetWidth(), false, bRefresh);
506 void COScopeCtrl::Reset(double sNewPeriod)
508 bool bStoppedPrev = bStopped;
509 bStopped = false;
510 if (sLastPeriod != sNewPeriod || bStoppedPrev) {
511 sLastPeriod = sNewPeriod;
512 InvalidateCtrl();
517 void COScopeCtrl::Stop()
519 bStopped = true;
520 bRecreateGraph = false;
521 RecreateGrid();
525 void COScopeCtrl::InvalidateCtrl(bool bInvalidateGraph, bool bInvalidateGrid)
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
537 #endif
541 void 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.
550 if( !theApp->amuledlg || !theApp->amuledlg->SafeState()) {
551 return;
553 bRecreateAll = false;
554 wxPaintEvent paint;
555 ProcessEvent(paint);
558 // File_checked_for_headers