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
)
46 const wxColour crPreset
[ 16 ] = {
47 wxColour( 0xFF, 0x00, 0x00 ), wxColour( 0xFF, 0xC0, 0xC0 ),
48 wxColour( 0xFF, 0xFF, 0x00 ), wxColour( 0xFF, 0xA0, 0x00 ),
49 wxColour( 0xA0, 0x60, 0x00 ), wxColour( 0x00, 0xFF, 0x00 ),
50 wxColour( 0x00, 0xA0, 0x00 ), wxColour( 0x00, 0x00, 0xFF ),
51 wxColour( 0x00, 0xA0, 0xFF ), wxColour( 0x00, 0xFF, 0xFF ),
52 wxColour( 0x00, 0xA0, 0xA0 ), wxColour( 0xC0, 0xC0, 0xFF ),
53 wxColour( 0xFF, 0x00, 0xFF ), wxColour( 0xA0, 0x00, 0xA0 ),
54 wxColour( 0xFF, 0xFF, 0xFF ), wxColour( 0x80, 0x80, 0x80 )
57 COScopeCtrl::COScopeCtrl(int cntTrends
, int nDecimals
, StatsGraphType type
, wxWindow
* parent
)
58 : wxControl(parent
, -1, wxDefaultPosition
, wxDefaultSize
)
61 // since plotting is based on a LineTo for each new point
62 // we need a starting point (i.e. a "previous" point)
63 // use 0.0 as the default first point.
64 // these are public member variables, and can be changed outside
65 // (after construction).
67 // G.Hayduk: NTrends is the number of trends that will be drawn on
68 // the plot. First 15 plots have predefined colors, but others will
69 // be drawn with white, unless you call SetPlotColor
71 pdsTrends
= new PlotData_t
[nTrends
];
73 PlotData_t
* ppds
= pdsTrends
;
74 for(unsigned i
=0; i
<nTrends
; ++i
, ++ppds
){
75 ppds
->crPlot
= (i
<15 ? crPreset
[i
] : *wxWHITE
);
76 ppds
->penPlot
=*(wxThePenList
->FindOrCreatePen(ppds
->crPlot
, 1, wxSOLID
));
77 ppds
->fPrev
= ppds
->fLowerLimit
= ppds
->fUpperLimit
= 0.0;
80 bRecreateGraph
= bRecreateGrid
= bRecreateAll
= 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 // Connect the timer (dynamically, so the Controls don't have to share a common timer id)
100 Connect(timerRedraw
.GetId(), wxEVT_TIMER
, (wxObjectEventFunction
) &COScopeCtrl::OnTimer
);
101 // Don't draw background (avoid ugly flickering on wxMSW on resize)
102 SetBackgroundStyle(wxBG_STYLE_CUSTOM
);
104 // Ensure that various size-constraints are calculated (via OnSize).
105 SetClientSize(GetClientSize());
109 COScopeCtrl::~COScopeCtrl()
115 void COScopeCtrl::SetRange(float fLower
, float fUpper
, unsigned iTrend
)
117 PlotData_t
* ppds
= pdsTrends
+iTrend
;
118 if ((ppds
->fLowerLimit
== fLower
) && (ppds
->fUpperLimit
== fUpper
))
120 ppds
->fLowerLimit
= fLower
;
121 ppds
->fUpperLimit
= fUpper
;
122 ppds
->fVertScale
= (float)m_rectPlot
.GetHeight() / (fUpper
-fLower
);
123 ppds
->yPrev
= GetPlotY(ppds
->fPrev
, ppds
);
133 void COScopeCtrl::SetRanges(float fLower
, float fUpper
)
135 for (unsigned iTrend
= 0; iTrend
< nTrends
; ++iTrend
) {
136 SetRange(fLower
, fUpper
, iTrend
);
141 void COScopeCtrl::SetYUnits(const wxString
& strUnits
, const wxString
& strMin
, const wxString
& strMax
)
143 strYUnits
= strUnits
;
150 void COScopeCtrl::SetGridColor(const wxColour
& cr
)
153 if (cr
== m_gridColour
) {
162 void COScopeCtrl::SetPlotColor(const wxColour
& cr
, unsigned iTrend
)
164 PlotData_t
* ppds
= pdsTrends
+iTrend
;
165 if (ppds
->crPlot
== cr
)
168 ppds
->penPlot
=*(wxThePenList
->FindOrCreatePen(ppds
->crPlot
, 1, wxSOLID
));
173 void COScopeCtrl::SetBackgroundColor(const wxColour
& cr
)
176 if (m_bgColour
== cr
) {
181 brushBack
= *(wxTheBrushList
->FindOrCreateBrush(cr
, wxSOLID
));
186 void COScopeCtrl::RecreateGrid()
188 // There is a lot of drawing going on here - particularly in terms of
189 // drawing the grid. Don't panic, this is all being drawn (only once)
190 // to a bitmap. The result is then BitBlt'd to the control whenever needed.
191 bRecreateGrid
= false;
192 if (m_rectClient
.GetWidth() == 0 || m_rectClient
.GetHeight() == 0) {
196 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
);
206 // draw the plot rectangle: determine how wide the y axis scaling values are,
207 // add the units digit, decimal point, one decimal place, and an extra space
208 nCharacters
= std::abs((int)std::log10(std::fabs(pdsTrends
[0].fUpperLimit
))) ;
209 nCharacters
= std::max(nCharacters
, std::abs((int)std::log10(std::fabs(pdsTrends
[0].fLowerLimit
)))) + 4;
211 // adjust the plot rectangle dimensions
212 // assume 6 pixels per character (this may need to be adjusted)
213 m_rectPlot
.x
= m_rectClient
.GetLeft() + 6*7+4;
214 // draw the plot rectangle
215 dcGrid
.SetPen(solidPen
);
216 dcGrid
.DrawRectangle(m_rectPlot
.x
- 1, m_rectPlot
.y
- 1, m_rectPlot
.GetWidth() + 2, m_rectPlot
.GetHeight() + 2);
217 dcGrid
.SetPen(wxNullPen
);
219 // create some fonts (horizontal and vertical)
220 wxFont
axisFont(10, wxSWISS
, wxNORMAL
, wxNORMAL
, false);
221 dcGrid
.SetFont(axisFont
);
224 dcGrid
.SetTextForeground(m_gridColour
);
225 if( strYMax
.IsEmpty() ) {
226 strTemp
= wxString::Format(wxT("%.*f"), nYDecimals
, pdsTrends
[ 0 ].fUpperLimit
);
231 dcGrid
.GetTextExtent(strTemp
,&sizX
,&sizY
);
232 dcGrid
.DrawText(strTemp
,m_rectPlot
.GetLeft()-4-sizX
,m_rectPlot
.GetTop()-7);
234 if( strYMin
.IsEmpty() ) {
235 strTemp
= wxString::Format(wxT("%.*f"), nYDecimals
, pdsTrends
[ 0 ].fLowerLimit
) ;
239 dcGrid
.GetTextExtent(strTemp
,&sizX
,&sizY
);
240 dcGrid
.DrawText(strTemp
,m_rectPlot
.GetLeft()-4-sizX
, m_rectPlot
.GetBottom());
243 strTemp
= CastSecondsToHM((m_rectPlot
.GetWidth()/nShiftPixels
) * (int)floor(sLastPeriod
+0.5));
244 // floor(x + 0.5) is a way of doing round(x) that works with gcc < 3 ...
246 strXUnits
= CFormat( _("Disabled [%s]") ) % strTemp
;
251 dcGrid
.GetTextExtent(strXUnits
,&sizX
,&sizY
);
252 dcGrid
.DrawText(strXUnits
,(m_rectPlot
.GetLeft() + m_rectPlot
.GetRight())/2-sizX
/2,m_rectPlot
.GetBottom()+4);
255 if (!strYUnits
.IsEmpty()) {
256 dcGrid
.GetTextExtent(strYUnits
,&sizX
,&sizY
);
257 dcGrid
.DrawText(strYUnits
, m_rectPlot
.GetLeft()-4-sizX
, (m_rectPlot
.GetTop()+m_rectPlot
.GetBottom())/2-sizY
/2);
259 // no more drawing to this bitmap is needed until the setting are changed
261 if (bRecreateGraph
) {
262 RecreateGraph(false);
265 // finally, force the plot area to redraw
270 void COScopeCtrl::AppendPoints(double sTimestamp
, const std::vector
<float *> &apf
)
272 sLastTimestamp
= sTimestamp
;
274 if (nDelayedPoints
) {
275 // Ensures that delayed points get added before the new point.
276 // We do this by simply drawing the history up to and including
278 int n
= std::min(m_rectPlot
.GetWidth(), nDelayedPoints
+ 1);
280 PlotHistory(n
, true, false);
290 void COScopeCtrl::OnPaint(wxPaintEvent
& WXUNUSED(evt
))
294 // no real plotting work is performed here unless we are coming out of a hidden state;
295 // normally, just putting the existing bitmaps on the client to avoid flicker,
296 // establish a memory dc and then BitBlt it to the client
297 wxBufferedPaintDC
dc(this);
304 RecreateGrid(); // this will also recreate the graph if that flag is set
305 } else if (bRecreateGraph
) {
309 if (nDelayedPoints
) { // we've just come out of hiding, so catch up
310 int n
= std::min(m_rectPlot
.GetWidth(), nDelayedPoints
);
311 nDelayedPoints
= 0; // (this is more efficient than plotting in the
312 PlotHistory(n
, true, false); // background because the bitmap is shifted only
313 } // once for all delayed points together)
315 // We have assured that we have a valid and resized if needed
316 // wxDc and bitmap. Proceed to blit.
317 dc
.DrawBitmap(m_bmapGrid
, 0, 0, false);
319 // Overwrites the plot section of the image
320 dc
.DrawBitmap(m_bmapPlot
, m_rectPlot
.x
, m_rectPlot
.y
, false);
322 // draw the dotted lines.
323 // This is done last because wxMAC does't support the wxOR logical
324 // operation, preventing us from simply blitting the plot on top of
327 dc
.SetPen(*(wxThePenList
->FindOrCreatePen(m_gridColour
, 1, wxLONG_DASH
)));
328 for (unsigned j
= 1; j
< (nYGrids
+ 1); ++j
) {
329 unsigned GridPos
= (m_rectPlot
.GetHeight())*j
/( nYGrids
+ 1 ) + m_rectPlot
.GetTop();
331 dc
.DrawLine(m_rectPlot
.GetLeft(), GridPos
, m_rectPlot
.GetRight(), GridPos
);
336 void COScopeCtrl::OnSize(wxSizeEvent
& WXUNUSED(evt
))
338 // This gets called repeatedly as the user resizes the app;
339 // we use the timer mechanism through InvalidateCtrl to avoid unnecessary redrawing
340 // NOTE: OnSize automatically gets called during the setup of the control
341 if(GetClientRect() == m_rectClient
) {
345 m_rectClient
= GetClientRect();
346 if (m_rectClient
.GetWidth() < 1 || m_rectClient
.GetHeight() < 1) {
350 // the "left" coordinate and "width" will be modified in
351 // InvalidateCtrl to be based on the y axis scaling
352 m_rectPlot
.SetLeft(20);
353 m_rectPlot
.SetTop(10);
354 m_rectPlot
.SetRight(std::max
<int>(m_rectPlot
.GetLeft() + 1, m_rectClient
.GetRight() - 40));
355 m_rectPlot
.SetBottom(std::max
<int>(m_rectPlot
.GetTop() + 1, m_rectClient
.GetBottom() - 25));
357 PlotData_t
* ppds
= pdsTrends
;
358 for(unsigned iTrend
=0; iTrend
<nTrends
; ++iTrend
, ++ppds
) {
359 ppds
->fVertScale
= (float)m_rectPlot
.GetHeight() / (ppds
->fUpperLimit
-ppds
->fLowerLimit
);
360 ppds
->yPrev
= GetPlotY(ppds
->fPrev
, ppds
);
363 if (!m_bmapGrid
.IsOk() || (m_rectClient
!= wxSize(m_bmapGrid
.GetWidth(), m_bmapGrid
.GetHeight()))) {
364 m_bmapGrid
.Create(m_rectClient
.GetWidth(), m_rectClient
.GetHeight());
366 if (!m_bmapPlot
.IsOk() || (m_rectPlot
!= wxSize(m_bmapPlot
.GetWidth(), m_bmapPlot
.GetHeight()))) {
367 m_bmapPlot
.Create(m_rectPlot
.GetWidth(), m_rectPlot
.GetHeight());
374 void COScopeCtrl::ShiftGraph(unsigned cntPoints
)
376 wxMemoryDC
dcPlot(m_bmapPlot
);
378 unsigned cntPixelOffset
= cntPoints
*nShiftPixels
;
379 if (cntPixelOffset
>= (unsigned)m_rectPlot
.GetWidth()) {
380 cntPixelOffset
= m_rectPlot
.GetWidth();
382 dcPlot
.Blit(0, 0, m_rectPlot
.GetWidth(), m_rectPlot
.GetHeight(), &dcPlot
,
383 cntPixelOffset
, 0); // scroll graph to the left
386 // clear a rectangle over the right side of plot prior to adding the new points
387 dcPlot
.SetPen(*wxTRANSPARENT_PEN
);
388 dcPlot
.SetBrush(brushBack
); // fill with background color
389 dcPlot
.DrawRectangle(m_rectPlot
.GetWidth()-cntPixelOffset
, 0,
390 cntPixelOffset
, m_rectPlot
.GetHeight());
394 unsigned COScopeCtrl::GetPlotY(float fPlot
, PlotData_t
* ppds
)
396 if (fPlot
<= ppds
->fLowerLimit
) {
397 return m_rectPlot
.GetBottom();
398 } else if (fPlot
>= ppds
->fUpperLimit
) {
399 return m_rectPlot
.GetTop() + 1;
401 return m_rectPlot
.GetBottom() - (unsigned)((fPlot
- ppds
->fLowerLimit
) * ppds
->fVertScale
);
406 void COScopeCtrl::DrawPoints(const std::vector
<float *> &apf
, unsigned cntPoints
)
408 // this appends a new set of data points to a graph; all of the plotting is
409 // directed to the memory based bitmap associated with dcPlot
410 // the will subsequently be BitBlt'd to the client in OnPaint
411 // draw the next line segement
413 unsigned cntPixelOffset
= std::min((unsigned)(m_rectPlot
.GetWidth()-1), (cntPoints
-1)*nShiftPixels
);
414 PlotData_t
* ppds
= pdsTrends
;
416 wxMemoryDC
dcPlot(m_bmapPlot
);
418 for (unsigned iTrend
=0; iTrend
<nTrends
; ++iTrend
, ++ppds
) {
419 const float* pf
= apf
[iTrend
] + cntPoints
- 1;
421 dcPlot
.SetPen(ppds
->penPlot
);
423 for (int x
= m_rectPlot
.GetRight() - cntPixelOffset
; x
<= m_rectPlot
.GetRight(); x
+=nShiftPixels
) {
424 y
= GetPlotY(*pf
--, ppds
);
426 // Map onto the smaller bitmap
427 dcPlot
.DrawLine(x
- nShiftPixels
- m_rectPlot
.GetX(),
428 yPrev
- m_rectPlot
.GetY(),
429 x
- m_rectPlot
.GetX(),
430 y
- m_rectPlot
.GetY());
434 ppds
->fPrev
= *(pf
+1);
441 void COScopeCtrl::PlotHistory(unsigned cntPoints
, bool bShiftGraph
, bool bRefresh
)
443 wxASSERT(graph_type
!= GRAPH_INVALID
);
445 if (graph_type
!= GRAPH_INVALID
) {
448 std::vector
<float *> apf(nTrends
);
450 for (i
= 0; i
< nTrends
; ++i
) {
451 apf
[i
] = new float[cntPoints
];
453 double sFinal
= (bStopped
? sLastTimestamp
: -1.0);
454 cntFilled
= theApp
->m_statistics
->GetHistory(cntPoints
, sLastPeriod
, sFinal
, apf
, graph_type
);
455 if (cntFilled
>1 || (bShiftGraph
&& cntFilled
!=0)) {
456 if (bShiftGraph
) { // delayed points - we have an fPrev
457 ShiftGraph(cntFilled
);
458 } else { // fresh graph, we need to preset fPrev, yPrev
459 PlotData_t
* ppds
= pdsTrends
;
460 for(i
=0; i
<nTrends
; ++i
, ++ppds
)
461 ppds
->yPrev
= GetPlotY(ppds
->fPrev
= *(apf
[i
] + cntFilled
- 1), ppds
);
464 DrawPoints(apf
, cntFilled
);
468 for (i
= 0; i
< nTrends
; ++i
) {
471 } catch(std::bad_alloc
) {
472 // Failed memory allocation
473 AddLogLineM(true, wxString(
474 wxT("Error: COScopeCtrl::PlotHistory: Insuficient memory, cntPoints == ")) <<
475 cntPoints
<< wxT("."));
476 for (i
= 0; i
< nTrends
; ++i
) {
481 // No history (yet) for Kad.
485 //#warning CORE/GUI -- EC needed
486 void COScopeCtrl::PlotHistory(unsigned, bool, bool)
492 void COScopeCtrl::RecreateGraph(bool bRefresh
)
494 bRecreateGraph
= false;
497 wxMemoryDC
dcPlot(m_bmapPlot
);
498 dcPlot
.SetBackground(brushBack
);
501 PlotHistory(m_rectPlot
.GetWidth(), false, bRefresh
);
505 void COScopeCtrl::Reset(double sNewPeriod
)
507 bool bStoppedPrev
= bStopped
;
509 if (sLastPeriod
!= sNewPeriod
|| bStoppedPrev
) {
510 sLastPeriod
= sNewPeriod
;
516 void COScopeCtrl::Stop()
519 bRecreateGraph
= false;
524 void COScopeCtrl::InvalidateCtrl(bool bInvalidateGraph
, bool bInvalidateGrid
)
526 bRecreateGraph
|= bInvalidateGraph
;
527 bRecreateGrid
|= bInvalidateGrid
;
528 // It appears the timerRedraw logic screws up Mac, disable it there
530 // To prevent startup problems don't start timer logic until
531 // a native OnPaint event has been generated.
533 bRecreateAll
|= bInvalidateGraph
&& bInvalidateGrid
;
534 timerRedraw
.Start(100, true); // One-shot timer
540 void COScopeCtrl::OnTimer(wxTimerEvent
& WXUNUSED(evt
))
541 /* The timer is used to consolidate redrawing of the graphs: when the user resizes
542 the application, we get multiple calls to OnSize. If he changes several parameters
543 in the Preferences, we get several individual SetXYZ calls. If we were to try to
544 recreate the graphs for each such event, performance would be sluggish, but with
545 the timer, each event (if they come in quick succession) simply restarts the timer
546 until there is a little pause and OnTimer actually gets called and does its work.
549 if( !theApp
->amuledlg
|| !theApp
->amuledlg
->SafeState()) {
552 bRecreateAll
= false;
557 // File_checked_for_headers