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
41BEGIN_EVENT_TABLE(COScopeCtrl,wxControl)
42	EVT_PAINT(COScopeCtrl::OnPaint)
43	EVT_SIZE(COScopeCtrl::OnSize)
44END_EVENT_TABLE()
45
46
47const 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
58COScopeCtrl::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
110COScopeCtrl::~COScopeCtrl()
111{
112	delete [] pdsTrends;
113}
114
115
116void 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
134void COScopeCtrl::SetRanges(float fLower, float fUpper)
135{
136	for (unsigned iTrend = 0; iTrend < nTrends; ++iTrend) {
137		SetRange(fLower, fUpper, iTrend);
138	}
139}
140
141
142void 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
151void 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
163void 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
174void 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
187void 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	int nCharacters ;
200	wxPen solidPen = *(wxThePenList->FindOrCreatePen(m_gridColour, 1, wxSOLID));
201	wxString strTemp;
202
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;
211
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);
219
220	// create some fonts (horizontal and vertical)
221	wxFont axisFont(10, wxSWISS, wxNORMAL, wxNORMAL, false);
222	dcGrid.SetFont(axisFont);
223
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;
230	}
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;
239	}
240	dcGrid.GetTextExtent(strTemp,&sizX,&sizY);
241	dcGrid.DrawText(strTemp,m_rectPlot.GetLeft()-4-sizX, m_rectPlot.GetBottom());
242
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;
250	}
251
252	dcGrid.GetTextExtent(strXUnits,&sizX,&sizY);
253	dcGrid.DrawText(strXUnits,(m_rectPlot.GetLeft() + m_rectPlot.GetRight())/2-sizX/2,m_rectPlot.GetBottom()+4);
254
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);
259	}
260	// no more drawing to this bitmap is needed until the setting are changed
261
262	if (bRecreateGraph) {
263		RecreateGraph(false);
264	}
265
266	// finally, force the plot area to redraw
267	Refresh(false);
268}
269
270
271void COScopeCtrl::AppendPoints(double sTimestamp, const std::vector<float *> &apf)
272{
273	sLastTimestamp = sTimestamp;
274
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);
285	}
286
287	Refresh(false);
288}
289
290
291void COScopeCtrl::OnPaint(wxPaintEvent& WXUNUSED(evt))
292{
293	m_onPaint = true;
294
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);
299
300	if (bRecreateAll) {
301		return;
302	}
303
304	if (bRecreateGrid) {
305		RecreateGrid();  // this will also recreate the graph if that flag is set
306	} else if (bRecreateGraph) {
307		RecreateGraph(true);
308	}
309
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)
315
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);
319
320	// Overwrites the plot section of the image
321	dc.DrawBitmap(m_bmapPlot, m_rectPlot.x, m_rectPlot.y, false);
322
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.
327
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();
331
332		dc.DrawLine(m_rectPlot.GetLeft(), GridPos, m_rectPlot.GetRight(), GridPos);
333	}
334}
335
336
337void COScopeCtrl::OnSize(wxSizeEvent& WXUNUSED(evt))
338{
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;
344	}
345
346	m_rectClient = GetClientRect();
347	if (m_rectClient.GetWidth() < 1 || m_rectClient.GetHeight() < 1) {
348		return;
349	}
350
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));
357
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);
362	}
363
364	if (!m_bmapGrid.IsOk() || (m_rectClient != wxSize(m_bmapGrid.GetWidth(), m_bmapGrid.GetHeight()))) {
365		m_bmapGrid.Create(m_rectClient.GetWidth(), m_rectClient.GetHeight());
366	}
367	if (!m_bmapPlot.IsOk() || (m_rectPlot != wxSize(m_bmapPlot.GetWidth(), m_bmapPlot.GetHeight()))) {
368		m_bmapPlot.Create(m_rectPlot.GetWidth(), m_rectPlot.GetHeight());
369	}
370
371	InvalidateCtrl();
372}
373
374
375void COScopeCtrl::ShiftGraph(unsigned cntPoints)
376{
377	wxMemoryDC dcPlot(m_bmapPlot);
378
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
385	}
386
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());
392}
393
394
395unsigned COScopeCtrl::GetPlotY(float fPlot, PlotData_t* ppds)
396{
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);
403	}
404}
405
406
407void COScopeCtrl::DrawPoints(const std::vector<float *> &apf, unsigned cntPoints)
408{
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;
416
417	wxMemoryDC dcPlot(m_bmapPlot);
418
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);
423
424		for (int x = m_rectPlot.GetRight() - cntPixelOffset; x <= m_rectPlot.GetRight(); x+=nShiftPixels) {
425			y = GetPlotY(*pf--, ppds);
426
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());
432
433			yPrev = y;
434		}
435		ppds->fPrev = *(pf+1);
436		ppds->yPrev = yPrev;
437	}
438}
439
440
441#ifndef CLIENT_GUI
442void COScopeCtrl::PlotHistory(unsigned cntPoints, bool bShiftGraph, bool bRefresh)
443{
444	wxASSERT(graph_type != GRAPH_INVALID);
445
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];
453			}
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--;
464				}
465				DrawPoints(apf, cntFilled);
466				if (bRefresh)
467					Refresh(false);
468			}
469			for (i = 0; i < nTrends; ++i) {
470				delete [] apf[i];
471			}
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];
479			}
480		}
481	} else {
482		// No history (yet) for Kad.
483	}
484}
485#else
486//#warning CORE/GUI -- EC needed
487void COScopeCtrl::PlotHistory(unsigned, bool, bool)
488{
489}
490#endif
491
492
493void COScopeCtrl::RecreateGraph(bool bRefresh)
494{
495	bRecreateGraph = false;
496	nDelayedPoints = 0;
497
498	wxMemoryDC dcPlot(m_bmapPlot);
499	dcPlot.SetBackground(brushBack);
500	dcPlot.Clear();
501
502	PlotHistory(m_rectPlot.GetWidth(), false, bRefresh);
503}
504
505
506void COScopeCtrl::Reset(double sNewPeriod)
507{
508	bool bStoppedPrev = bStopped;
509	bStopped = false;
510	if (sLastPeriod != sNewPeriod  ||  bStoppedPrev) {
511		sLastPeriod = sNewPeriod;
512		InvalidateCtrl();
513	}
514}
515
516
517void COScopeCtrl::Stop()
518{
519	bStopped = true;
520	bRecreateGraph = false;
521	RecreateGrid();
522}
523
524
525void COScopeCtrl::InvalidateCtrl(bool bInvalidateGraph, bool bInvalidateGrid)
526{
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
536	}
537#endif
538}
539
540
541void 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.
548*/
549{
550	if( !theApp->amuledlg || !theApp->amuledlg->SafeState()) {
551		return;
552	}
553	bRecreateAll = false;
554	wxPaintEvent paint;
555	ProcessEvent(paint);
556}
557
558// File_checked_for_headers
559