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>
30 #include <common/Format.h>
32 #include "amule.h" // Needed for theApp
33 #include "amuleDlg.h" // Needed for CamuleDlg
34 #include "Logger.h" // Needed for AddLogLineM
35 #include "OScopeCtrl.h" // Interface declarations
36 #include "OtherFunctions.h" // Needed for CastSecondsToHM
38 BEGIN_EVENT_TABLE(COScopeCtrl
,wxControl
)
39 EVT_PAINT(COScopeCtrl::OnPaint
)
40 EVT_SIZE(COScopeCtrl::OnSize
)
41 EVT_TIMER(TIMER_OSCOPE
,COScopeCtrl::OnTimer
)
45 COScopeCtrl::COScopeCtrl(int cntTrends
, int nDecimals
, StatsGraphType type
, wxWindow
*parent
)
46 : wxControl(parent
,-1, wxDefaultPosition
, wxDefaultSize
), timerRedraw(this, TIMER_OSCOPE
)
48 COLORREF crPreset
[ 16 ] = {
49 RGB( 0xFF, 0x00, 0x00 ), RGB( 0xFF, 0xC0, 0xC0 ),
50 RGB( 0xFF, 0xFF, 0x00 ), RGB( 0xFF, 0xA0, 0x00 ),
51 RGB( 0xA0, 0x60, 0x00 ), RGB( 0x00, 0xFF, 0x00 ),
52 RGB( 0x00, 0xA0, 0x00 ), RGB( 0x00, 0x00, 0xFF ),
53 RGB( 0x00, 0xA0, 0xFF ), RGB( 0x00, 0xFF, 0xFF ),
54 RGB( 0x00, 0xA0, 0xA0 ), RGB( 0xC0, 0xC0, 0xFF ),
55 RGB( 0xFF, 0x00, 0xFF ), RGB( 0xA0, 0x00, 0xA0 ),
56 RGB( 0xFF, 0xFF, 0xFF ), RGB( 0x80, 0x80, 0x80 )
58 // since plotting is based on a LineTo for each new point
59 // we need a starting point (i.e. a "previous" point)
60 // use 0.0 as the default first point.
61 // these are public member variables, and can be changed outside
62 // (after construction).
63 // G.Hayduk: NTrends is the number of trends that will be drawn on
64 // the plot. First 15 plots have predefined colors, but others will
65 // be drawn with white, unless you call SetPlotColor
67 pdsTrends
= new PlotData_t
[nTrends
];
76 PlotData_t
* ppds
= pdsTrends
;
77 for(unsigned i
=0; i
<nTrends
; ++i
, ++ppds
){
78 ppds
->crPlot
= (i
<15 ? crPreset
[i
] : RGB(255, 255, 255));
79 ppds
->penPlot
=*(wxThePenList
->FindOrCreatePen(wxColour(GetRValue(ppds
->crPlot
),GetGValue(ppds
->crPlot
),GetBValue(ppds
->crPlot
)),1,wxSOLID
));
80 ppds
->fPrev
= ppds
->fLowerLimit
= ppds
->fUpperLimit
= 0.0;
84 bRecreateGraph
= bRecreateGrid
= bStopped
= false;
89 nYDecimals
= nDecimals
;
90 crBackground
= RGB( 0, 0, 0) ; // see also SetBackgroundColor
91 crGrid
= RGB( 0, 255, 255) ; // see also SetGridColor
92 brushBack
=*(wxTheBrushList
->FindOrCreateBrush(wxColour(GetRValue(crBackground
),GetGValue(crBackground
),GetBValue(crBackground
)),wxSOLID
));
94 strXUnits
= wxT("X"); // can also be set with SetXUnits
95 strYUnits
= wxT("Y"); // can also be set with SetYUnits
105 timerRedraw
.SetOwner(this);
107 // Ensure that various size-constraints are calculated (via OnSize).
108 SetClientSize(GetClientSize());
113 COScopeCtrl::~COScopeCtrl()
115 // just to be picky restore the bitmaps for the two memory dc's
116 // (these dc's are being destroyed so there shouldn't be any leaks)
117 if (bmapOldGrid
!= NULL
)
118 dcGrid
->SelectObject(wxNullBitmap
);
119 if (bmapOldPlot
!= NULL
)
120 dcPlot
->SelectObject(wxNullBitmap
);
124 memDC
->SelectObject(wxNullBitmap
);
130 if(bmapPlot
) delete bmapPlot
;
131 if(bmapGrid
) delete bmapGrid
;
132 if (memBitmap
) delete memBitmap
;
136 void COScopeCtrl::SetRange(float fLower
, float fUpper
, unsigned iTrend
)
138 PlotData_t
* ppds
= pdsTrends
+iTrend
;
139 if ((ppds
->fLowerLimit
== fLower
) && (ppds
->fUpperLimit
== fUpper
))
141 ppds
->fLowerLimit
= fLower
;
142 ppds
->fUpperLimit
= fUpper
;
143 ppds
->fVertScale
= (float)nPlotHeight
/ (fUpper
-fLower
);
144 ppds
->yPrev
= GetPlotY(ppds
->fPrev
, ppds
);
152 void COScopeCtrl::SetRanges(float fLower
, float fUpper
)
154 for(unsigned iTrend
=0; iTrend
<nTrends
; ++iTrend
)
155 SetRange(fLower
, fUpper
, iTrend
);
159 // G.Hayduk: Apart from setting title of axis, now you can optionally set
160 // the limits strings (string which will be placed on the left and right of axis)
161 void COScopeCtrl::SetXUnits(const wxString
& strUnits
, const wxString
& strMin
, const wxString
& strMax
)
163 strXUnits
= strUnits
;
170 void COScopeCtrl::SetYUnits(const wxString
& strUnits
, const wxString
& strMin
, const wxString
& strMax
)
172 strYUnits
= strUnits
;
179 void COScopeCtrl::SetGridColor(COLORREF cr
)
188 void COScopeCtrl::SetPlotColor(COLORREF cr
, unsigned iTrend
)
190 PlotData_t
* ppds
= pdsTrends
+iTrend
;
191 if (ppds
->crPlot
== cr
)
194 ppds
->penPlot
=*(wxThePenList
->FindOrCreatePen(wxColour(GetRValue(ppds
->crPlot
),GetGValue(ppds
->crPlot
),GetBValue(ppds
->crPlot
)),1,wxSOLID
));
199 void COScopeCtrl::SetBackgroundColor(COLORREF cr
)
201 if (crBackground
== cr
)
204 brushBack
=*(wxTheBrushList
->FindOrCreateBrush(wxColour(GetRValue(crBackground
),GetGValue(crBackground
),GetBValue(crBackground
)),wxSOLID
));
206 } // SetBackgroundColor
209 void COScopeCtrl::RecreateGrid()
216 // There is a lot of drawing going on here - particularly in terms of
217 // drawing the grid. Don't panic, this is all being drawn (only once)
218 // to a bitmap. The result is then BitBlt'd to the control whenever needed.
219 bRecreateGrid
= false;
220 if(nClientWidth
==0 || nClientHeight
==0)
224 wxPen solidPen
=*(wxThePenList
->FindOrCreatePen(wxColour(GetRValue(crGrid
),GetGValue(crGrid
),GetBValue(crGrid
)),1,wxSOLID
));
227 // fill the grid background
228 dcGrid
->SetBrush(brushBack
);
229 dcGrid
->SetPen(*wxTRANSPARENT_PEN
);
230 dcGrid
->DrawRectangle(rectClient
.left
,rectClient
.top
,rectClient
.right
-rectClient
.left
,
231 rectClient
.bottom
-rectClient
.top
);
232 // draw the plot rectangle: determine how wide the y axis scaling values are,
233 // add the units digit, decimal point, one decimal place, and an extra space
234 nCharacters
= std::abs((int)std::log10(std::fabs(pdsTrends
[0].fUpperLimit
))) ;
235 nCharacters
= std::max(nCharacters
, std::abs((int)std::log10(std::fabs(pdsTrends
[0].fLowerLimit
)))) + 4;
237 // adjust the plot rectangle dimensions
238 // assume 6 pixels per character (this may need to be adjusted)
239 rectPlot
.left
= rectClient
.left
+ 6*7+4;
240 nPlotWidth
= rectPlot
.right
-rectPlot
.left
;
241 // draw the plot rectangle
242 dcGrid
->SetPen(solidPen
);
243 dcGrid
->DrawLine(rectPlot
.left
-1,rectPlot
.top
,rectPlot
.right
+1,rectPlot
.top
);
244 dcGrid
->DrawLine(rectPlot
.right
+1,rectPlot
.top
,rectPlot
.right
+1,rectPlot
.bottom
+1);
245 dcGrid
->DrawLine(rectPlot
.right
+1,rectPlot
.bottom
+1,rectPlot
.left
-1,rectPlot
.bottom
+1);
246 dcGrid
->DrawLine(rectPlot
.left
-1,rectPlot
.bottom
+1,rectPlot
.left
-1,rectPlot
.top
);
247 dcGrid
->SetPen(wxNullPen
);
249 // draw the dotted lines,
250 // G.Hayduk: added configurable number of grids
251 wxColour
col(GetRValue(crGrid
),GetGValue(crGrid
),GetBValue(crGrid
));
252 wxPen
grPen(col
,1,wxSOLID
);
253 dcGrid
->SetPen(grPen
);
255 for(j
=1; j
<(nYGrids
+1); ++j
){
256 GridPos
= (rectPlot
.bottom
-rectPlot
.top
)*j
/( nYGrids
+ 1 ) + rectPlot
.top
;
257 for (unsigned int i
= rectPlot
.left
; i
< rectPlot
.right
; i
+= 4)
258 dcGrid
->DrawPoint(i
,GridPos
);
261 // create some fonts (horizontal and vertical)
262 wxFont
* axisFont
= new wxFont(10,wxSWISS
,wxNORMAL
,wxNORMAL
,FALSE
);
263 dcGrid
->SetFont(*axisFont
);//,this);
266 dcGrid
->SetTextForeground(wxColour(GetRValue(crGrid
),GetGValue(crGrid
),GetBValue(crGrid
)));
267 if( strYMax
.IsEmpty() ) {
268 strTemp
= wxString::Format(wxT("%.*f"), nYDecimals
, pdsTrends
[ 0 ].fUpperLimit
);
273 dcGrid
->GetTextExtent(strTemp
,&sizX
,&sizY
);
274 dcGrid
->DrawText(strTemp
,rectPlot
.left
-4-sizX
,rectPlot
.top
-7);
276 if( strYMin
.IsEmpty() ) {
277 strTemp
= wxString::Format(wxT("%.*f"), nYDecimals
, pdsTrends
[ 0 ].fLowerLimit
) ;
281 dcGrid
->GetTextExtent(strTemp
,&sizX
,&sizY
);
282 dcGrid
->DrawText(strTemp
,rectPlot
.left
-4-sizX
, rectPlot
.bottom
);
285 strTemp
= CastSecondsToHM((nPlotWidth
/nShiftPixels
) * (int)floor(sLastPeriod
+0.5));
286 // floor(x + 0.5) is a way of doing round(x) that works with gcc < 3 ...
288 strXUnits
= CFormat( _("Disabled [%s]") ) % strTemp
;
293 dcGrid
->GetTextExtent(strXUnits
,&sizX
,&sizY
);
294 dcGrid
->DrawText(strXUnits
,(rectPlot
.left
+rectPlot
.right
)/2-sizX
/2,rectPlot
.bottom
+4);
297 if (!strYUnits
.IsEmpty()) {
298 dcGrid
->GetTextExtent(strYUnits
,&sizX
,&sizY
);
299 dcGrid
->DrawText(strYUnits
, rectPlot
.left
-4-sizX
, (rectPlot
.top
+rectPlot
.bottom
)/2-sizY
/2);
301 // if(!strYUnits.IsEmpty())
302 // dcGrid->DrawRotatedText(strYUnits,(rectClient.left+rectPlot.left+4)/2/*+(sizY*2)*/,
303 // ((rectPlot.bottom+rectPlot.top)/2)+sizX/2,90.0);
304 // no more drawing to this bitmap is needed until the setting are changed
309 RecreateGraph(false);
310 // finally, force the plot area to redraw
312 rect
.x
=rectClient
.left
;
313 rect
.y
=rectClient
.top
;
314 rect
.width
=rectClient
.right
-rectClient
.left
;
315 rect
.height
=rectClient
.bottom
-rectClient
.top
;
316 Refresh(FALSE
,&rect
);
320 void COScopeCtrl::AppendPoints(double sTimestamp
, const std::vector
<float *> &apf
)
322 sLastTimestamp
= sTimestamp
;
330 void COScopeCtrl::OnPaint(wxPaintEvent
& WXUNUSED(evt
))
331 { // no real plotting work is performed here unless we are coming out of a hidden state;
332 // normally, just putting the existing bitmaps on the client to avoid flicker,
333 // establish a memory dc and then BitBlt it to the client
334 if (bRecreateGrid
|| bRecreateGraph
) {
337 RecreateGrid(); // this will also recreate the graph if that flag is set
338 else if (bRecreateGraph
)
342 if (nDelayedPoints
>0) { // we've just come out of hiding, so catch up
343 int n
= std::min(nPlotWidth
, nDelayedPoints
);
344 nDelayedPoints
= 0; // (this is more efficient than plotting in the
345 PlotHistory(n
, true, false); // background because the bitmap is shifted only
346 } // once for all delayed points together)
350 dc
.Blit(0,0,nClientWidth
,nClientHeight
, memDC
,0,0);
354 void COScopeCtrl::DoBlit()
357 wxASSERT(!memBitmap
);
358 memBitmap
= new wxBitmap(nClientWidth
,nClientHeight
);
359 memDC
= new wxMemoryDC
;
360 memDC
->SelectObject(*memBitmap
);
363 if ((memBitmap
->GetHeight() != nClientHeight
)
364 || (memBitmap
->GetWidth() != nClientWidth
)) {
366 memDC
->SelectObject(wxNullBitmap
);
368 memBitmap
= new wxBitmap(nClientWidth
,nClientHeight
);
369 memDC
->SelectObject(*memBitmap
);
375 // We have assured that we have a valid and resized if needed
376 // wxDc and bitmap. Proceed to blit.
378 memDC
->Blit(0,0,nClientWidth
,nClientHeight
,dcGrid
,0,0);
379 // now add the plot on top as a "pattern" via SRCPAINT.
380 // works well with dark background and a light plot
381 memDC
->Blit(0,0,nClientWidth
,nClientHeight
,dcPlot
,0,0
392 void COScopeCtrl::OnSize(wxSizeEvent
& evt
)
394 // This gets called repeatedly as the user resizes the app;
395 // we use the timer mechanism through InvalidateCtrl to avoid unnecessary redrawing
396 // NOTE: OnSize automatically gets called during the setup of the control
397 if(oldwidth
==evt
.GetSize().GetWidth() && oldheight
==evt
.GetSize().GetHeight())
399 oldwidth
=evt
.GetSize().GetWidth();
400 oldheight
=evt
.GetSize().GetHeight();
401 wxRect myrect
=GetClientRect();
402 rectClient
.left
=myrect
.x
;
403 rectClient
.top
=myrect
.y
;
404 rectClient
.right
=myrect
.x
+myrect
.width
;
405 rectClient
.bottom
=myrect
.y
+myrect
.height
;
406 if(myrect
.width
<1 || myrect
.height
<1)
409 // set some member variables to avoid multiple function calls
410 nClientHeight
= myrect
.height
;
411 nClientWidth
= myrect
.width
;
412 // the "left" coordinate and "width" will be modified in
413 // InvalidateCtrl to be based on the y axis scaling
416 rectPlot
.right
= std::max(rectPlot
.left
+1, rectClient
.right
-10);
417 rectPlot
.bottom
= std::max(rectPlot
.top
+1, rectClient
.bottom
-25);
418 nPlotHeight
= rectPlot
.bottom
-rectPlot
.top
;
419 nPlotWidth
= rectPlot
.right
-rectPlot
.left
;
420 PlotData_t
* ppds
= pdsTrends
;
421 for(unsigned iTrend
=0; iTrend
<nTrends
; ++iTrend
, ++ppds
) {
422 ppds
->fVertScale
= (float)nPlotHeight
/ (ppds
->fUpperLimit
-ppds
->fLowerLimit
);
423 ppds
->yPrev
= GetPlotY(ppds
->fPrev
, ppds
);
426 // destroy grid dc and bitmap (InvalidateCtrl recreates them in new size)
428 dcGrid
->SelectObject(wxNullBitmap
);
431 dcGrid
=new wxMemoryDC
;
434 bmapGrid
= new wxBitmap(nClientWidth
,nClientHeight
);
435 dcGrid
->SelectObject(*bmapGrid
);
438 dcPlot
->SelectObject(wxNullBitmap
);
441 dcPlot
=new wxMemoryDC
;
444 bmapPlot
= new wxBitmap(nClientWidth
, nClientHeight
);
445 dcPlot
->SelectObject(*bmapPlot
);
451 void COScopeCtrl::ShiftGraph(unsigned cntPoints
)
455 unsigned cntPixelOffset
= cntPoints
*nShiftPixels
;
456 if (cntPixelOffset
>= (unsigned)nPlotWidth
)
457 cntPixelOffset
= nPlotWidth
;
459 dcPlot
->Blit(rectPlot
.left
,rectPlot
.top
+1, nPlotWidth
, nPlotHeight
, dcPlot
,
460 rectPlot
.left
+cntPixelOffset
, rectPlot
.top
+1); // scroll graph to the left
461 // clear a rectangle over the right side of plot prior to adding the new points
462 dcPlot
->SetPen(*wxTRANSPARENT_PEN
);
463 dcPlot
->SetBrush(brushBack
); // fill with background color
464 dcPlot
->DrawRectangle(rectPlot
.right
-cntPixelOffset
+1, rectPlot
.top
,
465 cntPixelOffset
, rectPlot
.bottom
-rectPlot
.top
+1);
470 unsigned COScopeCtrl::GetPlotY(float fPlot
, PlotData_t
* ppds
)
472 if (fPlot
<= ppds
->fLowerLimit
)
473 return rectPlot
.bottom
;
474 else if (fPlot
>= ppds
->fUpperLimit
)
475 return rectPlot
.top
+1;
477 return rectPlot
.bottom
- (unsigned)((fPlot
- ppds
->fLowerLimit
) * ppds
->fVertScale
);
482 void COScopeCtrl::DrawPoints(const std::vector
<float *> &apf
, unsigned cntPoints
)
483 { // this appends a new set of data points to a graph; all of the plotting is
484 // directed to the memory based bitmap associated with dcPlot
485 // the will subsequently be BitBlt'd to the client in OnPaint
486 if (dcPlot
== NULL
) {
487 printf("COScopeCtrl::DrawPoints - dcPlot==NULL unexpected\n");
490 // draw the next line segement
491 unsigned x
, y
, yPrev
;
492 unsigned cntPixelOffset
= std::min((unsigned)(nPlotWidth
-1), (cntPoints
-1)*nShiftPixels
);
493 PlotData_t
* ppds
= pdsTrends
;
495 for (unsigned iTrend
=0; iTrend
<nTrends
; ++iTrend
, ++ppds
) {
496 const float* pf
= apf
[iTrend
] + cntPoints
- 1;
498 dcPlot
->SetPen(ppds
->penPlot
);
499 for (x
=rectPlot
.right
-cntPixelOffset
; x
<=rectPlot
.right
; x
+=nShiftPixels
) {
500 y
= GetPlotY(*pf
--, ppds
);
501 dcPlot
->DrawLine(x
-nShiftPixels
, yPrev
, x
, y
);
504 ppds
->fPrev
= *(pf
+1);
511 void COScopeCtrl::PlotHistory(unsigned cntPoints
, bool bShiftGraph
, bool bRefresh
)
513 wxASSERT(graph_type
!= GRAPH_INVALID
);
515 if (graph_type
!= GRAPH_INVALID
) {
518 std::vector
<float *> apf(nTrends
);
520 for (i
= 0; i
< nTrends
; ++i
) {
521 apf
[i
] = new float[cntPoints
];
523 double sFinal
= (bStopped
? sLastTimestamp
: -1.0);
524 cntFilled
= theApp
->m_statistics
->GetHistory(cntPoints
, sLastPeriod
, sFinal
, apf
, graph_type
);
525 if (cntFilled
>1 || (bShiftGraph
&& cntFilled
!=0)) {
526 if (bShiftGraph
) { // delayed points - we have an fPrev
527 ShiftGraph(cntFilled
);
528 } else { // fresh graph, we need to preset fPrev, yPrev
529 PlotData_t
* ppds
= pdsTrends
;
530 for(i
=0; i
<nTrends
; ++i
, ++ppds
)
531 ppds
->yPrev
= GetPlotY(ppds
->fPrev
= *(apf
[i
] + cntFilled
- 1), ppds
);
534 DrawPoints(apf
, cntFilled
);
538 for (i
= 0; i
< nTrends
; ++i
) {
541 } catch(std::bad_alloc
) {
542 // Failed memory allocation
543 AddLogLineM(true, wxString(
544 wxT("Error: COScopeCtrl::PlotHistory: Insuficient memory, cntPoints == ")) <<
545 cntPoints
<< wxT("."));
546 for (i
= 0; i
< nTrends
; ++i
) {
551 // No history (yet) for Kad.
555 //#warning CORE/GUI -- EC needed
556 void COScopeCtrl::PlotHistory(unsigned, bool, bool)
562 void COScopeCtrl::RecreateGraph(bool bRefresh
)
564 bRecreateGraph
= false;
566 dcPlot
->SetBrush(brushBack
);
567 dcPlot
->SetPen(*wxTRANSPARENT_PEN
);
568 dcPlot
->DrawRectangle(rectClient
.left
, rectClient
.top
, rectClient
.right
-rectClient
.left
,
569 rectClient
.bottom
-rectClient
.top
);
570 PlotHistory(nPlotWidth
, false, bRefresh
);
574 void COScopeCtrl::Reset(double sNewPeriod
)
576 bool bStoppedPrev
= bStopped
;
578 if (sLastPeriod
!= sNewPeriod
|| bStoppedPrev
) {
579 sLastPeriod
= sNewPeriod
;
585 void COScopeCtrl::Stop()
588 bRecreateGraph
= false;
593 void COScopeCtrl::InvalidateCtrl(bool bInvalidateGraph
, bool bInvalidateGrid
)
596 timerRedraw
.SetOwner(this,TIMER_OSCOPE
);
597 if (bInvalidateGraph
)
598 bRecreateGraph
= true;
600 bRecreateGrid
= true;
601 timerRedraw
.Start(100);
605 void COScopeCtrl::OnTimer(wxTimerEvent
& WXUNUSED(evt
))
606 /* The timer is used to consolidate redrawing of the graphs: when the user resizes
607 the application, we get multiple calls to OnSize. If he changes several parameters
608 in the Preferences, we get several individual SetXYZ calls. If we were to try to
609 recreate the graphs for each such event, performance would be sluggish, but with
610 the timer, each event (if they come in quick succession) simply restarts the timer
611 until there is a little pause and OnTimer actually gets called and does its work.
614 if( !theApp
->amuledlg
|| !theApp
->amuledlg
->SafeState()) {
619 RecreateGrid(); // this will also recreate the graph if that flag is set
620 } else if (bRecreateGraph
) {
625 // File_checked_for_headers