2 // This file is part of the aMule Project.
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 )
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
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
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
)
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
)
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
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;
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
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()
116 void COScopeCtrl::SetRange(float fLower
, float fUpper
, unsigned iTrend
)
118 PlotData_t
* ppds
= pdsTrends
+iTrend
;
119 if ((ppds
->fLowerLimit
== fLower
) && (ppds
->fUpperLimit
== fUpper
))
121 ppds
->fLowerLimit
= fLower
;
122 ppds
->fUpperLimit
= fUpper
;
123 ppds
->fVertScale
= (float)m_rectPlot
.GetHeight() / (fUpper
-fLower
);
124 ppds
->yPrev
= GetPlotY(ppds
->fPrev
, ppds
);
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
;
151 void COScopeCtrl::SetGridColor(const wxColour
& cr
)
154 if (cr
== m_gridColour
) {
163 void COScopeCtrl::SetPlotColor(const wxColour
& cr
, unsigned iTrend
)
165 PlotData_t
* ppds
= pdsTrends
+iTrend
;
166 if (ppds
->crPlot
== cr
)
169 ppds
->penPlot
=*(wxThePenList
->FindOrCreatePen(ppds
->crPlot
, 1, wxSOLID
));
174 void COScopeCtrl::SetBackgroundColor(const wxColour
& cr
)
177 if (m_bgColour
== cr
) {
182 brushBack
= *(wxTheBrushList
->FindOrCreateBrush(cr
, wxSOLID
));
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) {
197 wxMemoryDC
dcGrid(m_bmapGrid
);
200 wxPen solidPen
= *(wxThePenList
->FindOrCreatePen(m_gridColour
, 1, wxSOLID
));
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
);
225 dcGrid
.SetTextForeground(m_gridColour
);
226 if( strYMax
.IsEmpty() ) {
227 strTemp
= wxString::Format(wxT("%.*f"), nYDecimals
, pdsTrends
[ 0 ].fUpperLimit
);
232 dcGrid
.GetTextExtent(strTemp
,&sizX
,&sizY
);
233 dcGrid
.DrawText(strTemp
,m_rectPlot
.GetLeft()-4-sizX
,m_rectPlot
.GetTop()-7);
235 if( strYMin
.IsEmpty() ) {
236 strTemp
= wxString::Format(wxT("%.*f"), nYDecimals
, pdsTrends
[ 0 ].fLowerLimit
) ;
240 dcGrid
.GetTextExtent(strTemp
,&sizX
,&sizY
);
241 dcGrid
.DrawText(strTemp
,m_rectPlot
.GetLeft()-4-sizX
, m_rectPlot
.GetBottom());
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 ...
247 strXUnits
= CFormat( _("Disabled [%s]") ) % strTemp
;
252 dcGrid
.GetTextExtent(strXUnits
,&sizX
,&sizY
);
253 dcGrid
.DrawText(strXUnits
,(m_rectPlot
.GetLeft() + m_rectPlot
.GetRight())/2-sizX
/2,m_rectPlot
.GetBottom()+4);
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
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
279 int n
= std::min(m_rectPlot
.GetWidth(), nDelayedPoints
+ 1);
281 PlotHistory(n
, true, false);
291 void COScopeCtrl::OnPaint(wxPaintEvent
& WXUNUSED(evt
))
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);
305 RecreateGrid(); // this will also recreate the graph if that flag is set
306 } else if (bRecreateGraph
) {
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
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
) {
346 m_rectClient
= GetClientRect();
347 if (m_rectClient
.GetWidth() < 1 || m_rectClient
.GetHeight() < 1) {
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());
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();
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;
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
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;
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());
435 ppds
->fPrev
= *(pf
+1);
442 void COScopeCtrl::PlotHistory(unsigned cntPoints
, bool bShiftGraph
, bool bRefresh
)
444 wxASSERT(graph_type
!= GRAPH_INVALID
);
446 if (graph_type
!= GRAPH_INVALID
) {
449 std::vector
<float *> apf(nTrends
);
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
);
465 DrawPoints(apf
, cntFilled
);
469 for (i
= 0; i
< nTrends
; ++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
) {
482 // No history (yet) for Kad.
486 //#warning CORE/GUI -- EC needed
487 void COScopeCtrl::PlotHistory(unsigned, bool, bool)
493 void COScopeCtrl::RecreateGraph(bool bRefresh
)
495 bRecreateGraph
= false;
498 wxMemoryDC
dcPlot(m_bmapPlot
);
499 dcPlot
.SetBackground(brushBack
);
502 PlotHistory(m_rectPlot
.GetWidth(), false, bRefresh
);
506 void COScopeCtrl::Reset(double sNewPeriod
)
508 bool bStoppedPrev
= bStopped
;
510 if (sLastPeriod
!= sNewPeriod
|| bStoppedPrev
) {
511 sLastPeriod
= sNewPeriod
;
517 void COScopeCtrl::Stop()
520 bRecreateGraph
= false;
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
531 // To prevent startup problems don't start timer logic until
532 // a native OnPaint event has been generated.
534 bRecreateAll
|= bInvalidateGraph
&& bInvalidateGrid
;
535 timerRedraw
.Start(100, true); // One-shot timer
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()) {
553 bRecreateAll
= false;
558 // File_checked_for_headers