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.
10 //
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.
15 //
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
24 //
25 
26 #include <cmath>
27 #include <wx/dcmemory.h>
28 #include <wx/dcclient.h>
29 #include <wx/dcbuffer.h>
30 
31 #include <common/Format.h>
32 
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"
39 
40 
41 BEGIN_EVENT_TABLE(COScopeCtrl,wxControl)
42 	EVT_PAINT(COScopeCtrl::OnPaint)
43 	EVT_SIZE(COScopeCtrl::OnSize)
44 END_EVENT_TABLE()
45 
46 
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 )
56 };
57 
COScopeCtrl(int cntTrends,int nDecimals,StatsGraphType type,wxWindow * parent)58 COScopeCtrl::COScopeCtrl(int cntTrends, int nDecimals, StatsGraphType type, wxWindow* parent)
59 	: wxControl(parent, -1, wxDefaultPosition, wxDefaultSize)
60 	, timerRedraw(this)
61 {
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).
67 
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];
73 
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;
79 	}
80 
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;
91 
92 	strXUnits = wxT("X");  // can also be set with SetXUnits
93 	strYUnits = wxT("Y");  // can also be set with SetYUnits
94 
95 	nXGrids = 6;
96 	nYGrids = 5;
97 
98 	graph_type = type;
99 
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);
104 
105 	// Ensure that various size-constraints are calculated (via OnSize).
106 	SetClientSize(GetClientSize());
107 }
108 
109 
~COScopeCtrl()110 COScopeCtrl::~COScopeCtrl()
111 {
112 	delete [] pdsTrends;
113 }
114 
115 
SetRange(float fLower,float fUpper,unsigned iTrend)116 void COScopeCtrl::SetRange(float fLower, float fUpper, unsigned iTrend)
117 {
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);
125 
126 	if (iTrend == 0) {
127 		InvalidateCtrl();
128 	} else {
129 		InvalidateGraph();
130 	}
131 }
132 
133 
SetRanges(float fLower,float fUpper)134 void COScopeCtrl::SetRanges(float fLower, float fUpper)
135 {
136 	for (unsigned iTrend = 0; iTrend < nTrends; ++iTrend) {
137 		SetRange(fLower, fUpper, iTrend);
138 	}
139 }
140 
141 
SetYUnits(const wxString & strUnits,const wxString & strMin,const wxString & strMax)142 void COScopeCtrl::SetYUnits(const wxString& strUnits, const wxString& strMin, const wxString& strMax)
143 {
144 	strYUnits = strUnits;
145 	strYMin = strMin;
146 	strYMax = strMax;
147 	InvalidateGrid();
148 }
149 
150 
SetGridColor(const wxColour & cr)151 void COScopeCtrl::SetGridColor(const wxColour& cr)
152 {
153 
154 	if (cr == m_gridColour) {
155 		return;
156 	}
157 
158 	m_gridColour = cr;
159 	InvalidateGrid() ;
160 }
161 
162 
SetPlotColor(const wxColour & cr,unsigned iTrend)163 void COScopeCtrl::SetPlotColor(const wxColour& cr, unsigned iTrend)
164 {
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();
171 }
172 
173 
SetBackgroundColor(const wxColour & cr)174 void COScopeCtrl::SetBackgroundColor(const wxColour& cr)
175 {
176 
177 	if (m_bgColour == cr) {
178 		return;
179 	}
180 
181 	m_bgColour = cr;
182 	brushBack= *(wxTheBrushList->FindOrCreateBrush(cr, wxSOLID));
183 	InvalidateCtrl() ;
184 }
185 
186 
RecreateGrid()187 void COScopeCtrl::RecreateGrid()
188 {
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;
195 	}
196 
197 	wxMemoryDC dcGrid(m_bmapGrid);
198 
199 	wxPen solidPen = *(wxThePenList->FindOrCreatePen(m_gridColour, 1, wxSOLID));
200 	wxString strTemp;
201 
202 	// fill the grid background
203 	dcGrid.SetBrush(brushBack);
204 	dcGrid.SetPen(*wxTRANSPARENT_PEN);
205 	dcGrid.DrawRectangle(m_rectClient);
206 
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);
214 
215 	// create some fonts (horizontal and vertical)
216 	wxFont axisFont(10, wxSWISS, wxNORMAL, wxNORMAL, false);
217 	dcGrid.SetFont(axisFont);
218 
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;
225 	}
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;
234 	}
235 	dcGrid.GetTextExtent(strTemp,&sizX,&sizY);
236 	dcGrid.DrawText(strTemp,m_rectPlot.GetLeft()-4-sizX, m_rectPlot.GetBottom());
237 
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;
245 	}
246 
247 	dcGrid.GetTextExtent(strXUnits,&sizX,&sizY);
248 	dcGrid.DrawText(strXUnits,(m_rectPlot.GetLeft() + m_rectPlot.GetRight())/2-sizX/2,m_rectPlot.GetBottom()+4);
249 
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);
254 	}
255 	// no more drawing to this bitmap is needed until the setting are changed
256 
257 	if (bRecreateGraph) {
258 		RecreateGraph(false);
259 	}
260 
261 	// finally, force the plot area to redraw
262 	Refresh(false);
263 }
264 
265 
AppendPoints(double sTimestamp,const std::vector<float * > & apf)266 void COScopeCtrl::AppendPoints(double sTimestamp, const std::vector<float *> &apf)
267 {
268 	sLastTimestamp = sTimestamp;
269 
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);
280 	}
281 
282 	Refresh(false);
283 }
284 
285 
OnPaint(wxPaintEvent & WXUNUSED (evt))286 void COScopeCtrl::OnPaint(wxPaintEvent& WXUNUSED(evt))
287 {
288 	m_onPaint = true;
289 
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);
294 
295 	if (bRecreateAll) {
296 		return;
297 	}
298 
299 	if (bRecreateGrid) {
300 		RecreateGrid();  // this will also recreate the graph if that flag is set
301 	} else if (bRecreateGraph) {
302 		RecreateGraph(true);
303 	}
304 
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)
310 
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);
314 
315 	// Overwrites the plot section of the image
316 	dc.DrawBitmap(m_bmapPlot, m_rectPlot.x, m_rectPlot.y, false);
317 
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.
322 
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();
326 
327 		dc.DrawLine(m_rectPlot.GetLeft(), GridPos, m_rectPlot.GetRight(), GridPos);
328 	}
329 }
330 
331 
OnSize(wxSizeEvent & WXUNUSED (evt))332 void COScopeCtrl::OnSize(wxSizeEvent& WXUNUSED(evt))
333 {
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;
339 	}
340 
341 	m_rectClient = GetClientRect();
342 	if (m_rectClient.GetWidth() < 1 || m_rectClient.GetHeight() < 1) {
343 		return;
344 	}
345 
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));
352 
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);
357 	}
358 
359 	if (!m_bmapGrid.IsOk() || (m_rectClient != wxSize(m_bmapGrid.GetWidth(), m_bmapGrid.GetHeight()))) {
360 		m_bmapGrid.Create(m_rectClient.GetWidth(), m_rectClient.GetHeight());
361 	}
362 	if (!m_bmapPlot.IsOk() || (m_rectPlot != wxSize(m_bmapPlot.GetWidth(), m_bmapPlot.GetHeight()))) {
363 		m_bmapPlot.Create(m_rectPlot.GetWidth(), m_rectPlot.GetHeight());
364 	}
365 
366 	InvalidateCtrl();
367 }
368 
369 
ShiftGraph(unsigned cntPoints)370 void COScopeCtrl::ShiftGraph(unsigned cntPoints)
371 {
372 	wxMemoryDC dcPlot(m_bmapPlot);
373 
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
380 	}
381 
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());
387 }
388 
389 
GetPlotY(float fPlot,PlotData_t * ppds)390 unsigned COScopeCtrl::GetPlotY(float fPlot, PlotData_t* ppds)
391 {
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);
398 	}
399 }
400 
401 
DrawPoints(const std::vector<float * > & apf,unsigned cntPoints)402 void COScopeCtrl::DrawPoints(const std::vector<float *> &apf, unsigned cntPoints)
403 {
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;
411 
412 	wxMemoryDC dcPlot(m_bmapPlot);
413 
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);
418 
419 		for (int x = m_rectPlot.GetRight() - cntPixelOffset; x <= m_rectPlot.GetRight(); x+=nShiftPixels) {
420 			y = GetPlotY(*pf--, ppds);
421 
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());
427 
428 			yPrev = y;
429 		}
430 		ppds->fPrev = *(pf+1);
431 		ppds->yPrev = yPrev;
432 	}
433 }
434 
435 
436 #ifndef CLIENT_GUI
PlotHistory(unsigned cntPoints,bool bShiftGraph,bool bRefresh)437 void COScopeCtrl::PlotHistory(unsigned cntPoints, bool bShiftGraph, bool bRefresh)
438 {
439 	wxASSERT(graph_type != GRAPH_INVALID);
440 
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];
448 			}
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--;
459 				}
460 				DrawPoints(apf, cntFilled);
461 				if (bRefresh)
462 					Refresh(false);
463 			}
464 			for (i = 0; i < nTrends; ++i) {
465 				delete [] apf[i];
466 			}
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];
474 			}
475 		}
476 	} else {
477 		// No history (yet) for Kad.
478 	}
479 }
480 #else
481 //#warning CORE/GUI -- EC needed
PlotHistory(unsigned,bool,bool)482 void COScopeCtrl::PlotHistory(unsigned, bool, bool)
483 {
484 }
485 #endif
486 
487 
RecreateGraph(bool bRefresh)488 void COScopeCtrl::RecreateGraph(bool bRefresh)
489 {
490 	bRecreateGraph = false;
491 	nDelayedPoints = 0;
492 
493 	wxMemoryDC dcPlot(m_bmapPlot);
494 	dcPlot.SetBackground(brushBack);
495 	dcPlot.Clear();
496 
497 	PlotHistory(m_rectPlot.GetWidth(), false, bRefresh);
498 }
499 
500 
Reset(double sNewPeriod)501 void COScopeCtrl::Reset(double sNewPeriod)
502 {
503 	bool bStoppedPrev = bStopped;
504 	bStopped = false;
505 	if (sLastPeriod != sNewPeriod  ||  bStoppedPrev) {
506 		sLastPeriod = sNewPeriod;
507 		InvalidateCtrl();
508 	}
509 }
510 
511 
Stop()512 void COScopeCtrl::Stop()
513 {
514 	bStopped = true;
515 	bRecreateGraph = false;
516 	RecreateGrid();
517 }
518 
519 
InvalidateCtrl(bool bInvalidateGraph,bool bInvalidateGrid)520 void COScopeCtrl::InvalidateCtrl(bool bInvalidateGraph, bool bInvalidateGrid)
521 {
522 	bRecreateGraph |= bInvalidateGraph;
523 	bRecreateGrid |= bInvalidateGrid;
524 	// To prevent startup problems don't start timer logic until
525 	// a native OnPaint event has been generated.
526 	if (m_onPaint) {
527 		bRecreateAll  |= bInvalidateGraph && bInvalidateGrid;
528 		timerRedraw.Start(100, true);	// One-shot timer
529 	}
530 }
531 
532 
OnTimer(wxTimerEvent & WXUNUSED (evt))533 void COScopeCtrl::OnTimer(wxTimerEvent& WXUNUSED(evt))
534 /*	The timer is used to consolidate redrawing of the graphs:  when the user resizes
535 	the application, we get multiple calls to OnSize.  If he changes several parameters
536 	in the Preferences, we get several individual SetXYZ calls.  If we were to try to
537 	recreate the graphs for each such event, performance would be sluggish, but with
538 	the timer, each event (if they come in quick succession) simply restarts the timer
539 	until there is a little pause and OnTimer actually gets called and does its work.
540 */
541 {
542 	if( !theApp->amuledlg || !theApp->amuledlg->SafeState()) {
543 		return;
544 	}
545 	bRecreateAll = false;
546 	Refresh();
547 }
548 
549 // File_checked_for_headers
550