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