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-2008 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 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
)
60 , timerRedraw(this, TIMER_OSCOPE
)
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
= bStopped
= false;
86 nYDecimals
= nDecimals
;
87 m_bgColour
= wxColour( 0, 0, 0) ; // see also SetBackgroundColor
88 m_gridColour
= wxColour( 0, 255, 255) ; // see also SetGridColor
89 brushBack
= *wxBLACK_BRUSH
;
91 strXUnits
= wxT("X"); // can also be set with SetXUnits
92 strYUnits
= wxT("Y"); // can also be set with SetYUnits
99 // Ensure that various size-constraints are calculated (via OnSize).
100 SetClientSize(GetClientSize());
104 COScopeCtrl::~COScopeCtrl()
110 void COScopeCtrl::SetRange(float fLower
, float fUpper
, unsigned iTrend
)
112 PlotData_t
* ppds
= pdsTrends
+iTrend
;
113 if ((ppds
->fLowerLimit
== fLower
) && (ppds
->fUpperLimit
== fUpper
))
115 ppds
->fLowerLimit
= fLower
;
116 ppds
->fUpperLimit
= fUpper
;
117 ppds
->fVertScale
= (float)m_rectPlot
.GetHeight() / (fUpper
-fLower
);
118 ppds
->yPrev
= GetPlotY(ppds
->fPrev
, ppds
);
128 void COScopeCtrl::SetRanges(float fLower
, float fUpper
)
130 for (unsigned iTrend
= 0; iTrend
< nTrends
; ++iTrend
) {
131 SetRange(fLower
, fUpper
, iTrend
);
136 void COScopeCtrl::SetYUnits(const wxString
& strUnits
, const wxString
& strMin
, const wxString
& strMax
)
138 strYUnits
= strUnits
;
145 void COScopeCtrl::SetGridColor(const wxColour
& cr
)
148 if (cr
== m_gridColour
) {
157 void COScopeCtrl::SetPlotColor(const wxColour
& cr
, unsigned iTrend
)
159 PlotData_t
* ppds
= pdsTrends
+iTrend
;
160 if (ppds
->crPlot
== cr
)
163 ppds
->penPlot
=*(wxThePenList
->FindOrCreatePen(ppds
->crPlot
, 1, wxSOLID
));
168 void COScopeCtrl::SetBackgroundColor(const wxColour
& cr
)
171 if (m_bgColour
== cr
) {
176 brushBack
= *(wxTheBrushList
->FindOrCreateBrush(cr
, wxSOLID
));
181 void COScopeCtrl::RecreateGrid()
183 // There is a lot of drawing going on here - particularly in terms of
184 // drawing the grid. Don't panic, this is all being drawn (only once)
185 // to a bitmap. The result is then BitBlt'd to the control whenever needed.
186 bRecreateGrid
= false;
187 if (m_rectClient
.GetWidth() == 0 || m_rectClient
.GetHeight() == 0) {
191 wxMemoryDC
dcGrid(m_bmapGrid
);
194 wxPen solidPen
= *(wxThePenList
->FindOrCreatePen(m_gridColour
, 1, wxSOLID
));
197 // fill the grid background
198 dcGrid
.SetBrush(brushBack
);
199 dcGrid
.SetPen(*wxTRANSPARENT_PEN
);
200 dcGrid
.DrawRectangle(m_rectClient
);
201 // draw the plot rectangle: determine how wide the y axis scaling values are,
202 // add the units digit, decimal point, one decimal place, and an extra space
203 nCharacters
= std::abs((int)std::log10(std::fabs(pdsTrends
[0].fUpperLimit
))) ;
204 nCharacters
= std::max(nCharacters
, std::abs((int)std::log10(std::fabs(pdsTrends
[0].fLowerLimit
)))) + 4;
206 // adjust the plot rectangle dimensions
207 // assume 6 pixels per character (this may need to be adjusted)
208 m_rectPlot
.x
= m_rectClient
.GetLeft() + 6*7+4;
209 // draw the plot rectangle
210 dcGrid
.SetPen(solidPen
);
211 dcGrid
.DrawRectangle(m_rectPlot
.x
- 1, m_rectPlot
.y
- 1, m_rectPlot
.GetWidth() + 2, m_rectPlot
.GetHeight() + 2);
212 dcGrid
.SetPen(wxNullPen
);
214 // create some fonts (horizontal and vertical)
215 wxFont
axisFont(10, wxSWISS
, wxNORMAL
, wxNORMAL
, false);
216 dcGrid
.SetFont(axisFont
);
219 dcGrid
.SetTextForeground(m_gridColour
);
220 if( strYMax
.IsEmpty() ) {
221 strTemp
= wxString::Format(wxT("%.*f"), nYDecimals
, pdsTrends
[ 0 ].fUpperLimit
);
226 dcGrid
.GetTextExtent(strTemp
,&sizX
,&sizY
);
227 dcGrid
.DrawText(strTemp
,m_rectPlot
.GetLeft()-4-sizX
,m_rectPlot
.GetTop()-7);
229 if( strYMin
.IsEmpty() ) {
230 strTemp
= wxString::Format(wxT("%.*f"), nYDecimals
, pdsTrends
[ 0 ].fLowerLimit
) ;
234 dcGrid
.GetTextExtent(strTemp
,&sizX
,&sizY
);
235 dcGrid
.DrawText(strTemp
,m_rectPlot
.GetLeft()-4-sizX
, m_rectPlot
.GetBottom());
238 strTemp
= CastSecondsToHM((m_rectPlot
.GetWidth()/nShiftPixels
) * (int)floor(sLastPeriod
+0.5));
239 // floor(x + 0.5) is a way of doing round(x) that works with gcc < 3 ...
241 strXUnits
= CFormat( _("Disabled [%s]") ) % strTemp
;
246 dcGrid
.GetTextExtent(strXUnits
,&sizX
,&sizY
);
247 dcGrid
.DrawText(strXUnits
,(m_rectPlot
.GetLeft() + m_rectPlot
.GetRight())/2-sizX
/2,m_rectPlot
.GetBottom()+4);
250 if (!strYUnits
.IsEmpty()) {
251 dcGrid
.GetTextExtent(strYUnits
,&sizX
,&sizY
);
252 dcGrid
.DrawText(strYUnits
, m_rectPlot
.GetLeft()-4-sizX
, (m_rectPlot
.GetTop()+m_rectPlot
.GetBottom())/2-sizY
/2);
254 // no more drawing to this bitmap is needed until the setting are changed
256 if (bRecreateGraph
) {
257 RecreateGraph(false);
260 // finally, force the plot area to redraw
265 void COScopeCtrl::AppendPoints(double sTimestamp
, const std::vector
<float *> &apf
)
267 sLastTimestamp
= sTimestamp
;
269 if (nDelayedPoints
) {
270 // Ensures that delayed points get added before the new point.
271 // We do this by simply drawing the history up to and including
273 int n
= std::min(m_rectPlot
.GetWidth(), nDelayedPoints
+ 1);
275 PlotHistory(n
, true, false);
285 void COScopeCtrl::OnPaint(wxPaintEvent
& WXUNUSED(evt
))
287 // no real plotting work is performed here unless we are coming out of a hidden state;
288 // normally, just putting the existing bitmaps on the client to avoid flicker,
289 // establish a memory dc and then BitBlt it to the client
290 if (bRecreateGrid
|| bRecreateGraph
) {
294 RecreateGrid(); // this will also recreate the graph if that flag is set
295 } else if (bRecreateGraph
) {
300 if (nDelayedPoints
) { // we've just come out of hiding, so catch up
301 int n
= std::min(m_rectPlot
.GetWidth(), nDelayedPoints
);
302 nDelayedPoints
= 0; // (this is more efficient than plotting in the
303 PlotHistory(n
, true, false); // background because the bitmap is shifted only
304 } // once for all delayed points together)
306 wxBufferedPaintDC
dc(this);
308 // We have assured that we have a valid and resized if needed
309 // wxDc and bitmap. Proceed to blit.
310 dc
.DrawBitmap(m_bmapGrid
, 0, 0, false);
312 // Overwrites the plot section of the image
313 dc
.DrawBitmap(m_bmapPlot
, m_rectPlot
.x
, m_rectPlot
.y
, false);
315 // draw the dotted lines.
316 // This is done last because wxMAC does't support the wxOR logical
317 // operation, preventing us from simply blitting the plot on top of
320 dc
.SetPen(*(wxThePenList
->FindOrCreatePen(m_gridColour
, 1, wxLONG_DASH
)));
321 for (unsigned j
= 1; j
< (nYGrids
+ 1); ++j
) {
322 unsigned GridPos
= (m_rectPlot
.GetHeight())*j
/( nYGrids
+ 1 ) + m_rectPlot
.GetTop();
324 dc
.DrawLine(m_rectPlot
.GetLeft(), GridPos
, m_rectPlot
.GetRight(), GridPos
);
329 void COScopeCtrl::OnSize(wxSizeEvent
& WXUNUSED(evt
))
331 // This gets called repeatedly as the user resizes the app;
332 // we use the timer mechanism through InvalidateCtrl to avoid unnecessary redrawing
333 // NOTE: OnSize automatically gets called during the setup of the control
334 if(GetClientRect() == m_rectClient
) {
338 m_rectClient
= GetClientRect();
339 if (m_rectClient
.GetWidth() < 1 || m_rectClient
.GetHeight() < 1) {
343 // the "left" coordinate and "width" will be modified in
344 // InvalidateCtrl to be based on the y axis scaling
345 m_rectPlot
.SetLeft(20);
346 m_rectPlot
.SetTop(10);
347 m_rectPlot
.SetRight(std::max
<int>(m_rectPlot
.GetLeft() + 1, m_rectClient
.GetRight() - 40));
348 m_rectPlot
.SetBottom(std::max
<int>(m_rectPlot
.GetTop() + 1, m_rectClient
.GetBottom() - 25));
350 PlotData_t
* ppds
= pdsTrends
;
351 for(unsigned iTrend
=0; iTrend
<nTrends
; ++iTrend
, ++ppds
) {
352 ppds
->fVertScale
= (float)m_rectPlot
.GetHeight() / (ppds
->fUpperLimit
-ppds
->fLowerLimit
);
353 ppds
->yPrev
= GetPlotY(ppds
->fPrev
, ppds
);
356 if (!m_bmapGrid
.IsOk() || (m_rectClient
!= wxSize(m_bmapGrid
.GetWidth(), m_bmapGrid
.GetHeight()))) {
357 m_bmapGrid
.Create(m_rectClient
.GetWidth(), m_rectClient
.GetHeight());
359 if (!m_bmapPlot
.IsOk() || (m_rectPlot
!= wxSize(m_bmapPlot
.GetWidth(), m_bmapPlot
.GetHeight()))) {
360 m_bmapPlot
.Create(m_rectPlot
.GetWidth(), m_rectPlot
.GetHeight());
367 void COScopeCtrl::ShiftGraph(unsigned cntPoints
)
369 wxMemoryDC
dcPlot(m_bmapPlot
);
371 unsigned cntPixelOffset
= cntPoints
*nShiftPixels
;
372 if (cntPixelOffset
>= (unsigned)m_rectPlot
.GetWidth()) {
373 cntPixelOffset
= m_rectPlot
.GetWidth();
375 dcPlot
.Blit(0, 0, m_rectPlot
.GetWidth(), m_rectPlot
.GetHeight(), &dcPlot
,
376 cntPixelOffset
, 0); // scroll graph to the left
379 // clear a rectangle over the right side of plot prior to adding the new points
380 dcPlot
.SetPen(*wxTRANSPARENT_PEN
);
381 dcPlot
.SetBrush(brushBack
); // fill with background color
382 dcPlot
.DrawRectangle(m_rectPlot
.GetWidth()-cntPixelOffset
, 0,
383 cntPixelOffset
, m_rectPlot
.GetHeight());
387 unsigned COScopeCtrl::GetPlotY(float fPlot
, PlotData_t
* ppds
)
389 if (fPlot
<= ppds
->fLowerLimit
) {
390 return m_rectPlot
.GetBottom();
391 } else if (fPlot
>= ppds
->fUpperLimit
) {
392 return m_rectPlot
.GetTop() + 1;
394 return m_rectPlot
.GetBottom() - (unsigned)((fPlot
- ppds
->fLowerLimit
) * ppds
->fVertScale
);
399 void COScopeCtrl::DrawPoints(const std::vector
<float *> &apf
, unsigned cntPoints
)
401 // this appends a new set of data points to a graph; all of the plotting is
402 // directed to the memory based bitmap associated with dcPlot
403 // the will subsequently be BitBlt'd to the client in OnPaint
404 // draw the next line segement
406 unsigned cntPixelOffset
= std::min((unsigned)(m_rectPlot
.GetWidth()-1), (cntPoints
-1)*nShiftPixels
);
407 PlotData_t
* ppds
= pdsTrends
;
409 wxMemoryDC
dcPlot(m_bmapPlot
);
411 for (unsigned iTrend
=0; iTrend
<nTrends
; ++iTrend
, ++ppds
) {
412 const float* pf
= apf
[iTrend
] + cntPoints
- 1;
414 dcPlot
.SetPen(ppds
->penPlot
);
416 for (int x
= m_rectPlot
.GetRight() - cntPixelOffset
; x
<= m_rectPlot
.GetRight(); x
+=nShiftPixels
) {
417 y
= GetPlotY(*pf
--, ppds
);
419 // Map onto the smaller bitmap
420 dcPlot
.DrawLine(x
- nShiftPixels
- m_rectPlot
.GetX(),
421 yPrev
- m_rectPlot
.GetY(),
422 x
- m_rectPlot
.GetX(),
423 y
- m_rectPlot
.GetY());
427 ppds
->fPrev
= *(pf
+1);
434 void COScopeCtrl::PlotHistory(unsigned cntPoints
, bool bShiftGraph
, bool bRefresh
)
436 wxASSERT(graph_type
!= GRAPH_INVALID
);
438 if (graph_type
!= GRAPH_INVALID
) {
441 std::vector
<float *> apf(nTrends
);
443 for (i
= 0; i
< nTrends
; ++i
) {
444 apf
[i
] = new float[cntPoints
];
446 double sFinal
= (bStopped
? sLastTimestamp
: -1.0);
447 cntFilled
= theApp
->m_statistics
->GetHistory(cntPoints
, sLastPeriod
, sFinal
, apf
, graph_type
);
448 if (cntFilled
>1 || (bShiftGraph
&& cntFilled
!=0)) {
449 if (bShiftGraph
) { // delayed points - we have an fPrev
450 ShiftGraph(cntFilled
);
451 } else { // fresh graph, we need to preset fPrev, yPrev
452 PlotData_t
* ppds
= pdsTrends
;
453 for(i
=0; i
<nTrends
; ++i
, ++ppds
)
454 ppds
->yPrev
= GetPlotY(ppds
->fPrev
= *(apf
[i
] + cntFilled
- 1), ppds
);
457 DrawPoints(apf
, cntFilled
);
461 for (i
= 0; i
< nTrends
; ++i
) {
464 } catch(std::bad_alloc
) {
465 // Failed memory allocation
466 AddLogLineM(true, wxString(
467 wxT("Error: COScopeCtrl::PlotHistory: Insuficient memory, cntPoints == ")) <<
468 cntPoints
<< wxT("."));
469 for (i
= 0; i
< nTrends
; ++i
) {
474 // No history (yet) for Kad.
478 //#warning CORE/GUI -- EC needed
479 void COScopeCtrl::PlotHistory(unsigned, bool, bool)
485 void COScopeCtrl::RecreateGraph(bool bRefresh
)
487 bRecreateGraph
= false;
490 wxMemoryDC
dcPlot(m_bmapPlot
);
491 dcPlot
.SetBackground(brushBack
);
494 PlotHistory(m_rectPlot
.GetWidth(), false, bRefresh
);
498 void COScopeCtrl::Reset(double sNewPeriod
)
500 bool bStoppedPrev
= bStopped
;
502 if (sLastPeriod
!= sNewPeriod
|| bStoppedPrev
) {
503 sLastPeriod
= sNewPeriod
;
509 void COScopeCtrl::Stop()
512 bRecreateGraph
= false;
517 void COScopeCtrl::InvalidateCtrl(bool bInvalidateGraph
, bool bInvalidateGrid
)
520 timerRedraw
.SetOwner(this, TIMER_OSCOPE
);
522 bRecreateGraph
|= bInvalidateGraph
;
523 bRecreateGrid
|= bInvalidateGrid
;
525 timerRedraw
.Start(100);
529 void COScopeCtrl::OnTimer(wxTimerEvent
& WXUNUSED(evt
))
530 /* The timer is used to consolidate redrawing of the graphs: when the user resizes
531 the application, we get multiple calls to OnSize. If he changes several parameters
532 in the Preferences, we get several individual SetXYZ calls. If we were to try to
533 recreate the graphs for each such event, performance would be sluggish, but with
534 the timer, each event (if they come in quick succession) simply restarts the timer
535 until there is a little pause and OnTimer actually gets called and does its work.
538 if( !theApp
->amuledlg
|| !theApp
->amuledlg
->SafeState()) {
543 RecreateGrid(); // this will also recreate the graph if that flag is set
544 } else if (bRecreateGraph
) {
549 // File_checked_for_headers