1 /******************************************************************************
2  * $Id: wind.cpp, v1.0 2010/08/05 SethDart Exp $
3  *
4  * Project:  OpenCPN
5  * Purpose:  Dashboard Plugin
6  * Author:   Jean-Eudes Onfray
7  *
8  ***************************************************************************
9  *   Copyright (C) 2010 by David S. Register   *
10  *                                                                         *
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.                                   *
15  *                                                                         *
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.                          *
20  *                                                                         *
21  *   You should have received a copy of the GNU General Public License     *
22  *   along with this program; if not, write to the                         *
23  *   Free Software Foundation, Inc.,                                       *
24  *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,  USA.             *
25  ***************************************************************************
26  */
27 
28 #include "wind.h"
29 
30 // For compilers that support precompilation, includes "wx/wx.h".
31 #include <wx/wxprec.h>
32 
33 #ifdef __BORLANDC__
34     #pragma hdrstop
35 #endif
36 #include <cmath>
37 
38 // for all others, include the necessary headers (this file is usually all you
39 // need because it includes almost all "standard" wxWidgets headers)
40 #ifndef WX_PRECOMP
41     #include <wx/wx.h>
42 #endif
43 #include "wx/tokenzr.h"
44 
45 // Display the arrow for MainValue (wind angle)
46 // We also want the extra value (wind speed) displayed inside the dial
47 
DashboardInstrument_Wind(wxWindow * parent,wxWindowID id,wxString title,int cap_flag)48 DashboardInstrument_Wind::DashboardInstrument_Wind( wxWindow *parent, wxWindowID id, wxString title, int cap_flag) :
49       DashboardInstrument_Dial( parent, id, title, cap_flag, 0, 360, 0, 360)
50 {
51       SetOptionMarker(10, DIAL_MARKER_REDGREENBAR, 3);
52       // Labels are set static because we've no logic to display them this way
53       wxString labels[] = {_T(""), _T("30"), _T("60"), _T("90"), _T("120"), _T("150"), _T(""), _T("150"), _T("120"), _T("90"), _T("60"), _T("30")};
54       SetOptionLabel(30, DIAL_LABEL_HORIZONTAL, wxArrayString(12, labels));
55 }
56 
DrawBackground(wxGCDC * dc)57 void DashboardInstrument_Wind::DrawBackground(wxGCDC* dc)
58 {
59     DrawBoat( dc, m_cx, m_cy, m_radius );
60 }
61 
DashboardInstrument_WindCompass(wxWindow * parent,wxWindowID id,wxString title,int cap_flag)62 DashboardInstrument_WindCompass::DashboardInstrument_WindCompass( wxWindow *parent, wxWindowID id, wxString title, int cap_flag ) :
63       DashboardInstrument_Dial( parent, id, title, cap_flag, 0, 360, 0, 360 )
64 {
65       SetOptionMarker(5, DIAL_MARKER_SIMPLE, 2);
66       wxString labels[] = {_("N"), _("NE"), _("E"), _("SE"), _("S"), _("SW"), _("W"), _("NW")};
67       SetOptionLabel(45, DIAL_LABEL_HORIZONTAL, wxArrayString(8, labels));
68 }
69 
DrawBackground(wxGCDC * dc)70 void DashboardInstrument_WindCompass::DrawBackground(wxGCDC* dc)
71 {
72       DrawCompassRose(dc, m_cx, m_cy, m_radius * 0.85, m_AngleStart, false);
73 }
74 
75 // Display the arrow for MainValue (wind angle)
76 // We also want the extra value (wind speed) displayed inside the dial
77 
DashboardInstrument_TrueWindAngle(wxWindow * parent,wxWindowID id,wxString title,int cap_flag)78 DashboardInstrument_TrueWindAngle::DashboardInstrument_TrueWindAngle( wxWindow *parent, wxWindowID id, wxString title, int cap_flag) :
79       DashboardInstrument_Dial( parent, id, title, cap_flag, 0, 360, 0, 360)
80 {
81       SetOptionMarker(10, DIAL_MARKER_REDGREENBAR, 3);
82       // Labels are set static because we've no logic to display them this way
83       wxString labels[] = {_T(""), _T("30"), _T("60"), _T("90"), _T("120"), _T("150"), _T(""), _T("150"), _T("120"), _T("90"), _T("60"), _T("30")};
84       SetOptionLabel(30, DIAL_LABEL_HORIZONTAL, wxArrayString(12, labels));
85 }
86 
DrawBackground(wxGCDC * dc)87 void DashboardInstrument_TrueWindAngle::DrawBackground(wxGCDC* dc)
88 {
89     DrawBoat( dc, m_cx, m_cy, m_radius );
90 }
91 
92 /*****************************************************************************
93   Apparent & True wind angle combined in one dial instrument
94   Author: Thomas Rauch
95 ******************************************************************************/
DashboardInstrument_AppTrueWindAngle(wxWindow * parent,wxWindowID id,wxString title,int cap_flag)96 DashboardInstrument_AppTrueWindAngle::DashboardInstrument_AppTrueWindAngle(wxWindow *parent, wxWindowID id, wxString title, int cap_flag) :
97 DashboardInstrument_Dial(parent, id, title, cap_flag, 0, 360, 0, 360)
98 {
99 	SetOptionMarker(10, DIAL_MARKER_REDGREENBAR, 3);
100 	// Labels are set static because we've no logic to display them this way
101 	wxString labels[] = { _T(""), _T("30"), _T("60"), _T("90"), _T("120"), _T("150"), _T(""), _T("150"), _T("120"), _T("90"), _T("60"), _T("30") };
102 	SetOptionLabel(30, DIAL_LABEL_HORIZONTAL, wxArrayString(12, labels));
103 }
104 
DrawBackground(wxGCDC * dc)105 void DashboardInstrument_AppTrueWindAngle::DrawBackground(wxGCDC* dc)
106 {
107 	DrawBoat(dc, m_cx, m_cy, m_radius);
108 }
109 
SetData(int st,double data,wxString unit)110 void DashboardInstrument_AppTrueWindAngle::SetData(int st, double data, wxString unit)
111 {
112 	if (st == OCPN_DBP_STC_TWA){
113 		m_MainValueTrue = data;
114 		m_MainValueTrueUnit = unit;
115 		m_MainValueOption2 = DIAL_POSITION_BOTTOMLEFT;
116 	}
117 	else if (st == OCPN_DBP_STC_AWA){
118 		m_MainValueApp = data;
119 		m_MainValueAppUnit = unit;
120 		m_MainValueOption1 = DIAL_POSITION_TOPLEFT;
121 	}
122 	else if (st == OCPN_DBP_STC_AWS ){
123 		m_ExtraValueApp = data;
124 		m_ExtraValueAppUnit = unit;
125 		m_ExtraValueOption1 = DIAL_POSITION_TOPRIGHT;
126 	}
127 	else if (st == OCPN_DBP_STC_TWS ){
128 		m_ExtraValueTrue = data;
129 		m_ExtraValueTrueUnit = unit;
130 		m_ExtraValueOption2 = DIAL_POSITION_BOTTOMRIGHT;
131 	}
132 	Refresh();
133 }
Draw(wxGCDC * bdc)134 void DashboardInstrument_AppTrueWindAngle::Draw(wxGCDC* bdc)
135 {
136 	wxColour c1;
137 	GetGlobalColor(_T("DASHB"), &c1);
138 	wxBrush b1(c1);
139 	bdc->SetBackground(b1);
140 	bdc->Clear();
141 
142 	wxSize size = GetClientSize();
143 	m_cx = size.x / 2;
144 	int availableHeight = size.y - m_TitleHeight - 6;
145 	int width, height;
146 	bdc->GetTextExtent(_T("000"), &width, &height, 0, 0, g_pFontLabel);
147 	m_cy = m_TitleHeight + 2;
148 	m_cy += availableHeight / 2;
149 	m_radius = availableHeight / 2.0 * 0.95;
150 
151 
152 	DrawLabels(bdc);
153 	DrawFrame(bdc);
154 	DrawMarkers(bdc);
155 	DrawBackground(bdc);
156 	DrawData(bdc, m_MainValueApp, m_MainValueAppUnit, m_MainValueFormat, m_MainValueOption1);
157 	DrawData(bdc, m_MainValueTrue, m_MainValueTrueUnit, m_MainValueFormat, m_MainValueOption2);
158 	DrawData(bdc, m_ExtraValueApp, m_ExtraValueAppUnit, m_ExtraValueFormat, m_ExtraValueOption1);
159 	DrawData(bdc, m_ExtraValueTrue, m_ExtraValueTrueUnit, m_ExtraValueFormat, m_ExtraValueOption2);
160 	DrawForeground(bdc);
161 }
DrawForeground(wxGCDC * dc)162 void DashboardInstrument_AppTrueWindAngle::DrawForeground(wxGCDC* dc)
163 {
164 	wxPoint points[4];
165 	double data;
166 	double val;
167 	double value;
168 	// The default foreground is the arrow used in most dials
169 	wxColour cl;
170 	GetGlobalColor(_T("DASH2"), &cl);
171 	wxPen pen1;
172 	pen1.SetStyle(wxPENSTYLE_SOLID);
173 	pen1.SetColour(cl);
174 	pen1.SetWidth(2);
175 	dc->SetPen(pen1);
176 	GetGlobalColor(_T("DASH1"), &cl);
177 	wxBrush brush1;
178 	brush1.SetStyle(wxBRUSHSTYLE_SOLID);
179 	brush1.SetColour(cl);
180 	dc->SetBrush(brush1);
181 	dc->DrawCircle(m_cx, m_cy, m_radius / 8);
182 
183 	/*True Wind*/
184 	dc->SetPen(*wxTRANSPARENT_PEN);
185 
186 	GetGlobalColor(_T("BLUE3"), &cl);
187 	wxBrush brush2;
188 	brush2.SetStyle(wxBRUSHSTYLE_SOLID);
189 	brush2.SetColour(cl);
190 	dc->SetBrush(brush2);
191 
192 	/* this is fix for a +/-180� round instrument, when m_MainValue is supplied as <0..180><L | R>
193 	* for example TWA & AWA */
194 	if (m_MainValueTrueUnit == _T("\u00B0L"))
195 		data = 360 - m_MainValueTrue;
196 	else
197 		data = m_MainValueTrue;
198 
199 	// The arrow should stay inside fixed limits
200 	if (data < m_MainValueMin) val = m_MainValueMin;
201 	else if (data > m_MainValueMax) val = m_MainValueMax;
202 	else val = data;
203 
204 	value = deg2rad((val - m_MainValueMin) * m_AngleRange / (m_MainValueMax - m_MainValueMin)) + deg2rad(m_AngleStart - ANGLE_OFFSET);
205 
206 	points[0].x = m_cx + (m_radius * 0.95 * cos(value - .010));
207 	points[0].y = m_cy + (m_radius * 0.95 * sin(value - .010));
208 	points[1].x = m_cx + (m_radius * 0.95 * cos(value + .015));
209 	points[1].y = m_cy + (m_radius * 0.95 * sin(value + .015));
210 	points[2].x = m_cx + (m_radius * 0.22 * cos(value + 2.8));
211 	points[2].y = m_cy + (m_radius * 0.22 * sin(value + 2.8));
212 	points[3].x = m_cx + (m_radius * 0.22 * cos(value - 2.8));
213 	points[3].y = m_cy + (m_radius * 0.22 * sin(value - 2.8));
214 	dc->DrawPolygon(4, points, 0, 0);
215 
216 	/* Apparent Wind*/
217 	dc->SetPen(*wxTRANSPARENT_PEN);
218 
219 	GetGlobalColor(_T("DASHN"), &cl);
220 	wxBrush brush;
221 	brush.SetStyle(wxBRUSHSTYLE_SOLID);
222 	brush.SetColour(cl);
223 	dc->SetBrush(brush);
224 
225 	/* this is fix for a +/-180� round instrument, when m_MainValue is supplied as <0..180><L | R>
226 	* for example TWA & AWA */
227 	if (m_MainValueAppUnit == _T("\u00B0L"))
228 		data = 360 - m_MainValueApp;
229 	else
230 		data = m_MainValueApp;
231 
232 	// The arrow should stay inside fixed limits
233 	if (data < m_MainValueMin) val = m_MainValueMin;
234 	else if (data > m_MainValueMax) val = m_MainValueMax;
235 	else val = data;
236 
237 	value = deg2rad((val - m_MainValueMin) * m_AngleRange / (m_MainValueMax - m_MainValueMin)) + deg2rad(m_AngleStart - ANGLE_OFFSET);
238 
239 	points[0].x = m_cx + (m_radius * 0.95 * cos(value - .010));
240 	points[0].y = m_cy + (m_radius * 0.95 * sin(value - .010));
241 	points[1].x = m_cx + (m_radius * 0.95 * cos(value + .015));
242 	points[1].y = m_cy + (m_radius * 0.95 * sin(value + .015));
243 	points[2].x = m_cx + (m_radius * 0.22 * cos(value + 2.8));
244 	points[2].y = m_cy + (m_radius * 0.22 * sin(value + 2.8));
245 	points[3].x = m_cx + (m_radius * 0.22 * cos(value - 2.8));
246 	points[3].y = m_cy + (m_radius * 0.22 * sin(value - 2.8));
247 	dc->DrawPolygon(4, points, 0, 0);
248 }
DrawData(wxGCDC * dc,double value,wxString unit,wxString format,DialPositionOption position)249 void DashboardInstrument_AppTrueWindAngle::DrawData(wxGCDC* dc, double value,
250 	wxString unit, wxString format, DialPositionOption position)
251 {
252 	if (position == DIAL_POSITION_NONE)
253 		return;
254 
255 	dc->SetFont(*g_pFontLabel);
256 	wxColour cl;
257 	GetGlobalColor(_T("DASHF"), &cl);
258 	dc->SetTextForeground(cl);
259 
260 	wxSize size = GetClientSize();
261 
262 	wxString text;
263 	if (!std::isnan(value))
264 	{
265 		if (unit == _T("\u00B0"))
266 			text = wxString::Format(format, value) + DEGREE_SIGN;
267 		else if (unit == _T("\u00B0L")) // No special display for now, might be XX�< (as in text-only instrument)
268 			text = wxString::Format(format, value) + DEGREE_SIGN;
269 		else if (unit == _T("\u00B0R")) // No special display for now, might be >XX�
270 			text = wxString::Format(format, value) + DEGREE_SIGN;
271 		else if (unit == _T("\u00B0T"))
272 			text = wxString::Format(format, value) + DEGREE_SIGN + _T("T");
273 		else if (unit == _T("\u00B0M"))
274 			text = wxString::Format(format, value) + DEGREE_SIGN + _T("M");
275 		else if (unit == _T("N")) // Knots
276 			text = wxString::Format(format, value) + _T(" Kts");
277 		else
278 			text = wxString::Format(format, value) + _T(" ") + unit;
279 	}
280 	else
281 		text = _T("---");
282 
283 	int width, height;
284 	dc->GetMultiLineTextExtent(text, &width, &height, NULL, g_pFontLabel);
285 
286 	wxRect TextPoint;
287 	TextPoint.width = width;
288 	TextPoint.height = height;
289 	wxColour c3;
290 
291 	switch (position)
292 	{
293 	case DIAL_POSITION_NONE:
294 		// This case was already handled before, it's here just
295 		// to avoid compiler warning.
296 		return;
297 	case DIAL_POSITION_INSIDE:
298 	{
299 		TextPoint.x = m_cx - (width / 2) - 1;
300 		TextPoint.y = (size.y * .75) - height;
301 		GetGlobalColor(_T("DASHL"), &cl);
302 		int penwidth = size.x / 100;
303 		wxPen* pen = wxThePenList->FindOrCreatePen(cl, penwidth, wxPENSTYLE_SOLID);
304 		dc->SetPen(*pen);
305 		GetGlobalColor(_T("DASHB"), &cl);
306 		dc->SetBrush(cl);
307 		// There might be a background drawn below
308 		// so we must clear it first.
309 		dc->DrawRoundedRectangle(TextPoint.x - 2, TextPoint.y - 2, width + 4, height + 4, 3);
310 		break;
311 	}
312 	case DIAL_POSITION_TOPLEFT:
313 		GetGlobalColor(_T("DASHN"), &c3);
314 		TextPoint.x = 0;
315 		TextPoint.y = m_TitleHeight;
316 		text = _T("A:") + text;
317 		break;
318 	case DIAL_POSITION_TOPRIGHT:
319 		GetGlobalColor(_T("DASHN"), &c3);
320 		TextPoint.x = size.x - width - 1;
321 		TextPoint.y = m_TitleHeight;
322 		break;
323 	case DIAL_POSITION_BOTTOMLEFT:
324 		GetGlobalColor(_T("BLUE3"), &c3);
325 		text = _T("T:") + text;
326 		TextPoint.x = 0;
327 		TextPoint.y = size.y - height;
328 		break;
329 	case DIAL_POSITION_BOTTOMRIGHT:
330 		GetGlobalColor(_T("BLUE3"), &c3);
331 		TextPoint.x = size.x - width - 1;
332 		TextPoint.y = size.y - height;
333 		break;
334 	}
335 	wxColour c2;
336 	GetGlobalColor(_T("DASHB"), &c2);
337 	wxStringTokenizer tkz(text, _T("\n"));
338 	wxString token;
339 
340 	token = tkz.GetNextToken();
341 	while (token.Length()) {
342 		dc->GetTextExtent(token, &width, &height, NULL, NULL, g_pFontLabel);
343 
344 #ifdef __WXMSW__
345 		if (g_pFontLabel->GetPointSize() <= 12) {
346 			wxBitmap tbm(width, height, -1);
347 			wxMemoryDC tdc(tbm);
348 
349 			tdc.SetBackground(c2);
350 			tdc.Clear();
351 			tdc.SetFont(*g_pFontLabel);
352 			tdc.SetTextForeground(c3);
353 
354 			tdc.DrawText(token, 0, 0);
355 			tdc.SelectObject(wxNullBitmap);
356 
357 			dc->DrawBitmap(tbm, TextPoint.x, TextPoint.y, false);
358 		}
359 		else
360 #endif
361 			dc->DrawText(token, TextPoint.x, TextPoint.y);
362 
363 
364 		TextPoint.y += height;
365 		token = tkz.GetNextToken();
366 	}
367 }