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
);
199 wxPen solidPen
= *(wxThePenList
->FindOrCreatePen(m_gridColour
, 1, wxSOLID
));
202 // fill the grid background
203 dcGrid
.SetBrush(brushBack
);
204 dcGrid
.SetPen(*wxTRANSPARENT_PEN
);
205 dcGrid
.DrawRectangle(m_rectClient
);
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
);
215 // create some fonts (horizontal and vertical)
216 wxFont
axisFont(10, wxSWISS
, wxNORMAL
, wxNORMAL
, false);
217 dcGrid
.SetFont(axisFont
);
220 dcGrid
.SetTextForeground(m_gridColour
);
221 if( strYMax
.IsEmpty() ) {
222 strTemp
= wxString::Format(wxT("%.*f"), nYDecimals
, pdsTrends
[ 0 ].fUpperLimit
);
227 dcGrid
.GetTextExtent(strTemp
,&sizX
,&sizY
);
228 dcGrid
.DrawText(strTemp
,m_rectPlot
.GetLeft()-4-sizX
,m_rectPlot
.GetTop()-7);
230 if( strYMin
.IsEmpty() ) {
231 strTemp
= wxString::Format(wxT("%.*f"), nYDecimals
, pdsTrends
[ 0 ].fLowerLimit
) ;
235 dcGrid
.GetTextExtent(strTemp
,&sizX
,&sizY
);
236 dcGrid
.DrawText(strTemp
,m_rectPlot
.GetLeft()-4-sizX
, m_rectPlot
.GetBottom());
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 ...
242 strXUnits
= CFormat( _("Disabled [%s]") ) % strTemp
;
247 dcGrid
.GetTextExtent(strXUnits
,&sizX
,&sizY
);
248 dcGrid
.DrawText(strXUnits
,(m_rectPlot
.GetLeft() + m_rectPlot
.GetRight())/2-sizX
/2,m_rectPlot
.GetBottom()+4);
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);
255 // no more drawing to this bitmap is needed until the setting are changed
257 if (bRecreateGraph
) {
258 RecreateGraph(false);
261 // finally, force the plot area to redraw
266 void COScopeCtrl::AppendPoints(double sTimestamp
, const std::vector
<float *> &apf
)
268 sLastTimestamp
= sTimestamp
;
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
274 int n
= std::min(m_rectPlot
.GetWidth(), nDelayedPoints
+ 1);
276 PlotHistory(n
, true, false);
286 void COScopeCtrl::OnPaint(wxPaintEvent
& WXUNUSED(evt
))
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);
300 RecreateGrid(); // this will also recreate the graph if that flag is set
301 } else if (bRecreateGraph
) {
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)
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);
315 // Overwrites the plot section of the image
316 dc
.DrawBitmap(m_bmapPlot
, m_rectPlot
.x
, m_rectPlot
.y
, false);
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
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();
327 dc
.DrawLine(m_rectPlot
.GetLeft(), GridPos
, m_rectPlot
.GetRight(), GridPos
);
332 void COScopeCtrl::OnSize(wxSizeEvent
& WXUNUSED(evt
))
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
) {
341 m_rectClient
= GetClientRect();
342 if (m_rectClient
.GetWidth() < 1 || m_rectClient
.GetHeight() < 1) {
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));
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
);
359 if (!m_bmapGrid
.IsOk() || (m_rectClient
!= wxSize(m_bmapGrid
.GetWidth(), m_bmapGrid
.GetHeight()))) {
360 m_bmapGrid
.Create(m_rectClient
.GetWidth(), m_rectClient
.GetHeight());
362 if (!m_bmapPlot
.IsOk() || (m_rectPlot
!= wxSize(m_bmapPlot
.GetWidth(), m_bmapPlot
.GetHeight()))) {
363 m_bmapPlot
.Create(m_rectPlot
.GetWidth(), m_rectPlot
.GetHeight());
370 void COScopeCtrl::ShiftGraph(unsigned cntPoints
)
372 wxMemoryDC
dcPlot(m_bmapPlot
);
374 unsigned cntPixelOffset
= cntPoints
*nShiftPixels
;
375 if (cntPixelOffset
>= (unsigned)m_rectPlot
.GetWidth()) {
376 cntPixelOffset
= m_rectPlot
.GetWidth();
378 dcPlot
.Blit(0, 0, m_rectPlot
.GetWidth(), m_rectPlot
.GetHeight(), &dcPlot
,
379 cntPixelOffset
, 0); // scroll graph to the left
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());
390 unsigned COScopeCtrl::GetPlotY(float fPlot
, PlotData_t
* ppds
)
392 if (fPlot
<= ppds
->fLowerLimit
) {
393 return m_rectPlot
.GetBottom();
394 } else if (fPlot
>= ppds
->fUpperLimit
) {
395 return m_rectPlot
.GetTop() + 1;
397 return m_rectPlot
.GetBottom() - (unsigned)((fPlot
- ppds
->fLowerLimit
) * ppds
->fVertScale
);
402 void COScopeCtrl::DrawPoints(const std::vector
<float *> &apf
, unsigned cntPoints
)
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
409 unsigned cntPixelOffset
= std::min((unsigned)(m_rectPlot
.GetWidth()-1), (cntPoints
-1)*nShiftPixels
);
410 PlotData_t
* ppds
= pdsTrends
;
412 wxMemoryDC
dcPlot(m_bmapPlot
);
414 for (unsigned iTrend
=0; iTrend
<nTrends
; ++iTrend
, ++ppds
) {
415 const float* pf
= apf
[iTrend
] + cntPoints
- 1;
417 dcPlot
.SetPen(ppds
->penPlot
);
419 for (int x
= m_rectPlot
.GetRight() - cntPixelOffset
; x
<= m_rectPlot
.GetRight(); x
+=nShiftPixels
) {
420 y
= GetPlotY(*pf
--, ppds
);
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());
430 ppds
->fPrev
= *(pf
+1);
437 void COScopeCtrl::PlotHistory(unsigned cntPoints
, bool bShiftGraph
, bool bRefresh
)
439 wxASSERT(graph_type
!= GRAPH_INVALID
);
441 if (graph_type
!= GRAPH_INVALID
) {
444 std::vector
<float *> apf(nTrends
);
446 for (i
= 0; i
< nTrends
; ++i
) {
447 apf
[i
] = new float[cntPoints
];
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
);
460 DrawPoints(apf
, cntFilled
);
464 for (i
= 0; i
< nTrends
; ++i
) {
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
) {
477 // No history (yet) for Kad.
481 //#warning CORE/GUI -- EC needed
482 void COScopeCtrl::PlotHistory(unsigned, bool, bool)
488 void COScopeCtrl::RecreateGraph(bool bRefresh
)
490 bRecreateGraph
= false;
493 wxMemoryDC
dcPlot(m_bmapPlot
);
494 dcPlot
.SetBackground(brushBack
);
497 PlotHistory(m_rectPlot
.GetWidth(), false, bRefresh
);
501 void COScopeCtrl::Reset(double sNewPeriod
)
503 bool bStoppedPrev
= bStopped
;
505 if (sLastPeriod
!= sNewPeriod
|| bStoppedPrev
) {
506 sLastPeriod
= sNewPeriod
;
512 void COScopeCtrl::Stop()
515 bRecreateGraph
= false;
520 void COScopeCtrl::InvalidateCtrl(bool bInvalidateGraph
, bool bInvalidateGrid
)
522 bRecreateGraph
|= bInvalidateGraph
;
523 bRecreateGrid
|= bInvalidateGrid
;
524 // It appears the timerRedraw logic screws up Mac, disable it there
526 // To prevent startup problems don't start timer logic until
527 // a native OnPaint event has been generated.
529 bRecreateAll
|= bInvalidateGraph
&& bInvalidateGrid
;
530 timerRedraw
.Start(100, true); // One-shot timer
536 void COScopeCtrl::OnTimer(wxTimerEvent
& WXUNUSED(evt
))
537 /* The timer is used to consolidate redrawing of the graphs: when the user resizes
538 the application, we get multiple calls to OnSize. If he changes several parameters
539 in the Preferences, we get several individual SetXYZ calls. If we were to try to
540 recreate the graphs for each such event, performance would be sluggish, but with
541 the timer, each event (if they come in quick succession) simply restarts the timer
542 until there is a little pause and OnTimer actually gets called and does its work.
545 if( !theApp
->amuledlg
|| !theApp
->amuledlg
->SafeState()) {
548 bRecreateAll
= false;
552 // File_checked_for_headers