2 // This file is part of the aMule Project.
4 // Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002 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
40 BEGIN_EVENT_TABLE(COScopeCtrl
,wxControl
)
41 EVT_PAINT(COScopeCtrl::OnPaint
)
42 EVT_SIZE(COScopeCtrl::OnSize
)
43 EVT_TIMER(TIMER_OSCOPE
,COScopeCtrl::OnTimer
)
47 const COLORREF crPreset
[ 16 ] = {
48 RGB( 0xFF, 0x00, 0x00 ), RGB( 0xFF, 0xC0, 0xC0 ),
49 RGB( 0xFF, 0xFF, 0x00 ), RGB( 0xFF, 0xA0, 0x00 ),
50 RGB( 0xA0, 0x60, 0x00 ), RGB( 0x00, 0xFF, 0x00 ),
51 RGB( 0x00, 0xA0, 0x00 ), RGB( 0x00, 0x00, 0xFF ),
52 RGB( 0x00, 0xA0, 0xFF ), RGB( 0x00, 0xFF, 0xFF ),
53 RGB( 0x00, 0xA0, 0xA0 ), RGB( 0xC0, 0xC0, 0xFF ),
54 RGB( 0xFF, 0x00, 0xFF ), RGB( 0xA0, 0x00, 0xA0 ),
55 RGB( 0xFF, 0xFF, 0xFF ), RGB( 0x80, 0x80, 0x80 )
59 COScopeCtrl::COScopeCtrl(int cntTrends
, int nDecimals
, StatsGraphType type
, wxWindow
* parent
)
60 : wxControl(parent
, -1, wxDefaultPosition
, wxDefaultSize
)
61 , timerRedraw(this, TIMER_OSCOPE
)
63 // since plotting is based on a LineTo for each new point
64 // we need a starting point (i.e. a "previous" point)
65 // use 0.0 as the default first point.
66 // these are public member variables, and can be changed outside
67 // (after construction).
69 // G.Hayduk: NTrends is the number of trends that will be drawn on
70 // the plot. First 15 plots have predefined colors, but others will
71 // be drawn with white, unless you call SetPlotColor
73 pdsTrends
= new PlotData_t
[nTrends
];
75 PlotData_t
* ppds
= pdsTrends
;
76 for(unsigned i
=0; i
<nTrends
; ++i
, ++ppds
){
77 ppds
->crPlot
= (i
<15 ? crPreset
[i
] : RGB(255, 255, 255));
78 ppds
->penPlot
=*(wxThePenList
->FindOrCreatePen(WxColourFromCr(ppds
->crPlot
), 1, wxSOLID
));
79 ppds
->fPrev
= ppds
->fLowerLimit
= ppds
->fUpperLimit
= 0.0;
82 bRecreateGraph
= bRecreateGrid
= 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
=*(wxTheBrushList
->FindOrCreateBrush(m_bgColour
, wxSOLID
));
92 strXUnits
= wxT("X"); // can also be set with SetXUnits
93 strYUnits
= wxT("Y"); // can also be set with SetYUnits
100 // Ensure that various size-constraints are calculated (via OnSize).
101 SetClientSize(GetClientSize());
105 COScopeCtrl::~COScopeCtrl()
111 void COScopeCtrl::SetRange(float fLower
, float fUpper
, unsigned iTrend
)
113 PlotData_t
* ppds
= pdsTrends
+iTrend
;
114 if ((ppds
->fLowerLimit
== fLower
) && (ppds
->fUpperLimit
== fUpper
))
116 ppds
->fLowerLimit
= fLower
;
117 ppds
->fUpperLimit
= fUpper
;
118 ppds
->fVertScale
= (float)m_rectPlot
.GetHeight() / (fUpper
-fLower
);
119 ppds
->yPrev
= GetPlotY(ppds
->fPrev
, ppds
);
129 void COScopeCtrl::SetRanges(float fLower
, float fUpper
)
131 for (unsigned iTrend
= 0; iTrend
< nTrends
; ++iTrend
) {
132 SetRange(fLower
, fUpper
, iTrend
);
137 void COScopeCtrl::SetYUnits(const wxString
& strUnits
, const wxString
& strMin
, const wxString
& strMax
)
139 strYUnits
= strUnits
;
146 void COScopeCtrl::SetGridColor(COLORREF cr
)
148 wxColour newCol
= WxColourFromCr(cr
);
149 if (newCol
== m_gridColour
) {
153 m_gridColour
= newCol
;
158 void COScopeCtrl::SetPlotColor(COLORREF cr
, unsigned iTrend
)
160 PlotData_t
* ppds
= pdsTrends
+iTrend
;
161 if (ppds
->crPlot
== cr
)
164 ppds
->penPlot
=*(wxThePenList
->FindOrCreatePen(WxColourFromCr(ppds
->crPlot
), 1, wxSOLID
));
169 void COScopeCtrl::SetBackgroundColor(COLORREF cr
)
171 wxColour
newCol(WxColourFromCr(cr
));
172 if (m_bgColour
== newCol
) {
177 brushBack
= *(wxTheBrushList
->FindOrCreateBrush(newCol
, wxSOLID
));
182 void COScopeCtrl::RecreateGrid()
184 // There is a lot of drawing going on here - particularly in terms of
185 // drawing the grid. Don't panic, this is all being drawn (only once)
186 // to a bitmap. The result is then BitBlt'd to the control whenever needed.
187 bRecreateGrid
= false;
188 if (m_rectClient
.GetWidth() == 0 || m_rectClient
.GetHeight() == 0) {
192 wxMemoryDC
dcGrid(m_bmapGrid
);
195 wxPen solidPen
=*(wxThePenList
->FindOrCreatePen(m_gridColour
, 1, wxSOLID
));
198 // fill the grid background
199 dcGrid
.SetBrush(brushBack
);
200 dcGrid
.SetPen(*wxTRANSPARENT_PEN
);
201 dcGrid
.DrawRectangle(m_rectClient
);
202 // draw the plot rectangle: determine how wide the y axis scaling values are,
203 // add the units digit, decimal point, one decimal place, and an extra space
204 nCharacters
= std::abs((int)std::log10(std::fabs(pdsTrends
[0].fUpperLimit
))) ;
205 nCharacters
= std::max(nCharacters
, std::abs((int)std::log10(std::fabs(pdsTrends
[0].fLowerLimit
)))) + 4;
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
))
288 // no real plotting work is performed here unless we are coming out of a hidden state;
289 // normally, just putting the existing bitmaps on the client to avoid flicker,
290 // establish a memory dc and then BitBlt it to the client
291 if (bRecreateGrid
|| bRecreateGraph
) {
295 RecreateGrid(); // this will also recreate the graph if that flag is set
296 } else if (bRecreateGraph
) {
301 if (nDelayedPoints
) { // we've just come out of hiding, so catch up
302 int n
= std::min(m_rectPlot
.GetWidth(), nDelayedPoints
);
303 nDelayedPoints
= 0; // (this is more efficient than plotting in the
304 PlotHistory(n
, true, false); // background because the bitmap is shifted only
305 } // once for all delayed points together)
307 wxBufferedPaintDC
dc(this);
309 // We have assured that we have a valid and resized if needed
310 // wxDc and bitmap. Proceed to blit.
311 dc
.DrawBitmap(m_bmapGrid
, 0, 0, false);
313 // Overwrites the plot section of the image
314 dc
.DrawBitmap(m_bmapPlot
, m_rectPlot
.x
, m_rectPlot
.y
, false);
316 // draw the dotted lines.
317 // This is done last because wxMAC does't support the wxOR logical
318 // operation, preventing us from simply blitting the plot on top of
320 wxColour
col(m_gridColour
);
321 wxPen
grPen(col
, 1, wxLONG_DASH
);
323 for (unsigned j
= 1; j
< (nYGrids
+ 1); ++j
) {
324 unsigned GridPos
= (m_rectPlot
.GetHeight())*j
/( nYGrids
+ 1 ) + m_rectPlot
.GetTop();
326 dc
.DrawLine(m_rectPlot
.GetLeft(), GridPos
, m_rectPlot
.GetRight(), GridPos
);
331 void COScopeCtrl::OnSize(wxSizeEvent
& WXUNUSED(evt
))
333 // This gets called repeatedly as the user resizes the app;
334 // we use the timer mechanism through InvalidateCtrl to avoid unnecessary redrawing
335 // NOTE: OnSize automatically gets called during the setup of the control
336 if(GetClientRect() == m_rectClient
) {
340 m_rectClient
= GetClientRect();
341 if (m_rectClient
.GetWidth() < 1 || m_rectClient
.GetHeight() < 1) {
345 // the "left" coordinate and "width" will be modified in
346 // InvalidateCtrl to be based on the y axis scaling
347 m_rectPlot
.SetLeft(20);
348 m_rectPlot
.SetTop(10);
349 m_rectPlot
.SetRight(std::max
<int>(m_rectPlot
.GetLeft() + 1, m_rectClient
.GetRight() - 40));
350 m_rectPlot
.SetBottom(std::max
<int>(m_rectPlot
.GetTop() + 1, m_rectClient
.GetBottom() - 25));
352 PlotData_t
* ppds
= pdsTrends
;
353 for(unsigned iTrend
=0; iTrend
<nTrends
; ++iTrend
, ++ppds
) {
354 ppds
->fVertScale
= (float)m_rectPlot
.GetHeight() / (ppds
->fUpperLimit
-ppds
->fLowerLimit
);
355 ppds
->yPrev
= GetPlotY(ppds
->fPrev
, ppds
);
358 if (!m_bmapGrid
.IsOk() || (m_rectClient
!= wxSize(m_bmapGrid
.GetWidth(), m_bmapGrid
.GetHeight()))) {
359 m_bmapGrid
.Create(m_rectClient
.GetWidth(), m_rectClient
.GetHeight());
361 if (!m_bmapPlot
.IsOk() || (m_rectPlot
!= wxSize(m_bmapPlot
.GetWidth(), m_bmapPlot
.GetHeight()))) {
362 m_bmapPlot
.Create(m_rectPlot
.GetWidth(), m_rectPlot
.GetHeight());
369 void COScopeCtrl::ShiftGraph(unsigned cntPoints
)
371 wxMemoryDC
dcPlot(m_bmapPlot
);
373 unsigned cntPixelOffset
= cntPoints
*nShiftPixels
;
374 if (cntPixelOffset
>= (unsigned)m_rectPlot
.GetWidth()) {
375 cntPixelOffset
= m_rectPlot
.GetWidth();
377 dcPlot
.Blit(0, 0, m_rectPlot
.GetWidth(), m_rectPlot
.GetHeight(), &dcPlot
,
378 cntPixelOffset
, 0); // scroll graph to the left
381 // clear a rectangle over the right side of plot prior to adding the new points
382 dcPlot
.SetPen(*wxTRANSPARENT_PEN
);
383 dcPlot
.SetBrush(brushBack
); // fill with background color
384 dcPlot
.DrawRectangle(m_rectPlot
.GetWidth()-cntPixelOffset
, 0,
385 cntPixelOffset
, m_rectPlot
.GetHeight());
389 unsigned COScopeCtrl::GetPlotY(float fPlot
, PlotData_t
* ppds
)
391 if (fPlot
<= ppds
->fLowerLimit
) {
392 return m_rectPlot
.GetBottom();
393 } else if (fPlot
>= ppds
->fUpperLimit
) {
394 return m_rectPlot
.GetTop() + 1;
396 return m_rectPlot
.GetBottom() - (unsigned)((fPlot
- ppds
->fLowerLimit
) * ppds
->fVertScale
);
401 void COScopeCtrl::DrawPoints(const std::vector
<float *> &apf
, unsigned cntPoints
)
403 // this appends a new set of data points to a graph; all of the plotting is
404 // directed to the memory based bitmap associated with dcPlot
405 // the will subsequently be BitBlt'd to the client in OnPaint
406 // draw the next line segement
408 unsigned cntPixelOffset
= std::min((unsigned)(m_rectPlot
.GetWidth()-1), (cntPoints
-1)*nShiftPixels
);
409 PlotData_t
* ppds
= pdsTrends
;
411 wxMemoryDC
dcPlot(m_bmapPlot
);
413 for (unsigned iTrend
=0; iTrend
<nTrends
; ++iTrend
, ++ppds
) {
414 const float* pf
= apf
[iTrend
] + cntPoints
- 1;
416 dcPlot
.SetPen(ppds
->penPlot
);
418 for (int x
= m_rectPlot
.GetRight() - cntPixelOffset
; x
<= m_rectPlot
.GetRight(); x
+=nShiftPixels
) {
419 y
= GetPlotY(*pf
--, ppds
);
421 // Map onto the smaller bitmap
422 dcPlot
.DrawLine(x
- nShiftPixels
- m_rectPlot
.GetX(),
423 yPrev
- m_rectPlot
.GetY(),
424 x
- m_rectPlot
.GetX(),
425 y
- m_rectPlot
.GetY());
429 ppds
->fPrev
= *(pf
+1);
436 void COScopeCtrl::PlotHistory(unsigned cntPoints
, bool bShiftGraph
, bool bRefresh
)
438 wxASSERT(graph_type
!= GRAPH_INVALID
);
440 if (graph_type
!= GRAPH_INVALID
) {
443 std::vector
<float *> apf(nTrends
);
445 for (i
= 0; i
< nTrends
; ++i
) {
446 apf
[i
] = new float[cntPoints
];
448 double sFinal
= (bStopped
? sLastTimestamp
: -1.0);
449 cntFilled
= theApp
->m_statistics
->GetHistory(cntPoints
, sLastPeriod
, sFinal
, apf
, graph_type
);
450 if (cntFilled
>1 || (bShiftGraph
&& cntFilled
!=0)) {
451 if (bShiftGraph
) { // delayed points - we have an fPrev
452 ShiftGraph(cntFilled
);
453 } else { // fresh graph, we need to preset fPrev, yPrev
454 PlotData_t
* ppds
= pdsTrends
;
455 for(i
=0; i
<nTrends
; ++i
, ++ppds
)
456 ppds
->yPrev
= GetPlotY(ppds
->fPrev
= *(apf
[i
] + cntFilled
- 1), ppds
);
459 DrawPoints(apf
, cntFilled
);
463 for (i
= 0; i
< nTrends
; ++i
) {
466 } catch(std::bad_alloc
) {
467 // Failed memory allocation
468 AddLogLineM(true, wxString(
469 wxT("Error: COScopeCtrl::PlotHistory: Insuficient memory, cntPoints == ")) <<
470 cntPoints
<< wxT("."));
471 for (i
= 0; i
< nTrends
; ++i
) {
476 // No history (yet) for Kad.
480 //#warning CORE/GUI -- EC needed
481 void COScopeCtrl::PlotHistory(unsigned, bool, bool)
487 void COScopeCtrl::RecreateGraph(bool bRefresh
)
489 bRecreateGraph
= false;
492 wxMemoryDC
dcPlot(m_bmapPlot
);
493 dcPlot
.SetBackground(brushBack
);
496 PlotHistory(m_rectPlot
.GetWidth(), false, bRefresh
);
500 void COScopeCtrl::Reset(double sNewPeriod
)
502 bool bStoppedPrev
= bStopped
;
504 if (sLastPeriod
!= sNewPeriod
|| bStoppedPrev
) {
505 sLastPeriod
= sNewPeriod
;
511 void COScopeCtrl::Stop()
514 bRecreateGraph
= false;
519 void COScopeCtrl::InvalidateCtrl(bool bInvalidateGraph
, bool bInvalidateGrid
)
522 timerRedraw
.SetOwner(this, TIMER_OSCOPE
);
524 bRecreateGraph
|= bInvalidateGraph
;
525 bRecreateGrid
|= bInvalidateGrid
;
527 timerRedraw
.Start(100);
531 void COScopeCtrl::OnTimer(wxTimerEvent
& WXUNUSED(evt
))
532 /* The timer is used to consolidate redrawing of the graphs: when the user resizes
533 the application, we get multiple calls to OnSize. If he changes several parameters
534 in the Preferences, we get several individual SetXYZ calls. If we were to try to
535 recreate the graphs for each such event, performance would be sluggish, but with
536 the timer, each event (if they come in quick succession) simply restarts the timer
537 until there is a little pause and OnTimer actually gets called and does its work.
540 if( !theApp
->amuledlg
|| !theApp
->amuledlg
->SafeState()) {
545 RecreateGrid(); // this will also recreate the graph if that flag is set
546 } else if (bRecreateGraph
) {
551 // File_checked_for_headers