OpenCPN Partial API docs
Loading...
Searching...
No Matches
TCWin.cpp
1
2// For compilers that support precompilation, includes "wx.h".
3#include <wx/wxprec.h>
4
5#include <wx/listctrl.h>
6#include <wx/choice.h>
7#include <wx/sizer.h>
8#include <wx/dcbuffer.h>
9
10#include "TCWin.h"
11#include "timers.h"
12#include "chcanv.h"
13#include "tide_time.h"
14#include "tcmgr.h"
15#include "dychart.h"
16#include "model/cutil.h"
17#include "FontMgr.h"
18#include "model/wx28compat.h"
19#include "OCPNPlatform.h"
20#include "RolloverWin.h"
21#include "navutil.h"
22#include "gui_lib.h"
23#include "ocpn_frame.h"
24
25// Custom chart panel class definition
26class TCWin::TideChartPanel : public wxPanel {
27public:
28 TideChartPanel(TCWin *parent) : wxPanel(parent, wxID_ANY), m_tcWin(parent) {
29 SetMinSize(wxSize(400, 200));
30 Bind(wxEVT_PAINT, &TideChartPanel::OnPaint, this);
31 Bind(wxEVT_MOTION, &TideChartPanel::OnMouseMove, this);
32 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // Prevent flicker
33 }
34
35private:
36 void OnPaint(wxPaintEvent &event) {
37 wxPaintDC dc(this);
38
39 // Clear the background
40 dc.SetBackground(wxBrush(GetBackgroundColour()));
41 dc.Clear();
42
43 // Calculate chart rectangle within this panel
44 wxSize panelSize = GetClientSize();
45 if (panelSize.GetWidth() <= 0 || panelSize.GetHeight() <= 0) {
46 return;
47 }
48
49 // Use larger left margin for Y-axis labels and units
50 int left_margin = 50; // Space for Y-axis numbers and units
51 int other_margins = 5; // Smaller margins for top, right, bottom
52 int chart_width = panelSize.GetWidth() - left_margin - other_margins;
53 int chart_height = panelSize.GetHeight() - (2 * other_margins);
54
55 // Reserve space at bottom for date/time text
56 int bottom_text_space = 50;
57 chart_height -= bottom_text_space;
58 chart_width = wxMax(chart_width, 300);
59 chart_height = wxMax(chart_height, 150);
60 wxRect chartRect(left_margin, other_margins, chart_width, chart_height);
61
62 // Delegate chart painting to parent TCWin
63 m_tcWin->PaintChart(dc, chartRect);
64 }
65
66 void OnMouseMove(wxMouseEvent &event) {
67 wxPoint panelPos = event.GetPosition();
68 wxPoint mainWindowPos = panelPos + GetPosition();
69 m_tcWin->HandleChartMouseMove(mainWindowPos.x, mainWindowPos.y, panelPos);
70 event.Skip();
71 }
72
73 TCWin *m_tcWin;
74};
75
76extern ColorScheme global_color_scheme;
77extern int gpIDXn;
78extern TCMgr *ptcmgr;
79extern wxString g_locale;
80extern OCPNPlatform *g_Platform;
81extern MyConfig *pConfig;
82extern OCPNPlatform *g_Platform;
83
84int g_tcwin_scale;
85
86enum { ID_TCWIN_NX, ID_TCWIN_PR };
87
88enum { TIDE_PLOT, CURRENT_PLOT };
89
90BEGIN_EVENT_TABLE(TCWin, wxWindow)
91EVT_PAINT(TCWin::OnPaint)
92EVT_SIZE(TCWin::OnSize)
93EVT_MOTION(TCWin::MouseEvent)
94EVT_BUTTON(wxID_OK, TCWin::OKEvent)
95EVT_BUTTON(ID_TCWIN_NX, TCWin::NXEvent)
96EVT_BUTTON(ID_TCWIN_PR, TCWin::PREvent)
97EVT_CLOSE(TCWin::OnCloseWindow)
98EVT_TIMER(TCWININF_TIMER, TCWin::OnTCWinPopupTimerEvent)
99EVT_TIMER(TCWIN_TIME_INDICATOR_TIMER, TCWin::OnTimeIndicatorTimer)
100END_EVENT_TABLE()
101
102// Define a constructor
103extern wxDateTime gTimeSource;
104TCWin::TCWin(ChartCanvas *parent, int x, int y, void *pvIDX) {
105 m_created = false;
106 xSpot = 0;
107 ySpot = 0;
108
109 m_pTCRolloverWin = NULL;
110
111 long wstyle = wxCLIP_CHILDREN | wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER |
112 wxFRAME_FLOAT_ON_PARENT;
113
114 pParent = parent;
115 m_x = x;
116 m_y = y;
117
118 RecalculateSize();
119
120 // Read the config file to get the user specified time zone.
121 if (pConfig) {
122 pConfig->SetPath(_T ( "/Settings/Others" ));
123 pConfig->Read(_T ( "TCWindowTimeZone" ), &m_tzoneDisplay, 0);
124 }
125
126 wxFrame::Create(parent, wxID_ANY, wxString(_T ( "" )), m_position, m_tc_size,
127 wstyle);
128
129 m_created = true;
130 wxFont *qFont = GetOCPNScaledFont(_("Dialog"));
131 SetFont(*qFont);
132
133 pIDX = (IDX_entry *)pvIDX;
134
135 // Set up plot type
136 if (strchr("Tt", pIDX->IDX_type)) {
137 m_plot_type = TIDE_PLOT;
138 SetTitle(wxString(_("Tide")));
139
140 } else {
141 m_plot_type = CURRENT_PLOT;
142 SetTitle(wxString(_("Current")));
143 }
144
145 int sx, sy;
146 GetClientSize(&sx, &sy);
147
148 SetTimeFactors();
149
150 btc_valid = false;
151
152 CreateLayout();
153 Layout();
154 m_graph_rect = wxRect(0, 0, 400, 200);
155
156 // Measure the size of a generic button, with label
157 wxButton *test_button =
158 new wxButton(this, wxID_OK, _("OK"), wxPoint(-1, -1), wxDefaultSize);
159 test_button->GetSize(&m_tsx, &m_tsy);
160 delete test_button;
161
162 m_TCWinPopupTimer.SetOwner(this, TCWININF_TIMER);
163
164 // Timer for refreshing time indicators (red line moves with current time)
165 m_TimeIndicatorTimer.SetOwner(this, TCWIN_TIME_INDICATOR_TIMER);
166 m_TimeIndicatorTimer.Start(60000, false); // Refresh every 60 seconds
167
168 wxScreenDC dc;
169 int text_height;
170 dc.SetFont(*qFont);
171 dc.GetTextExtent("W", NULL, &text_height);
172 m_refTextHeight = text_height;
173 m_button_height = m_tsy;
174
175 // Build graphics tools
176
177 wxFont *dlg_font = FontMgr::Get().GetFont(_("Dialog"));
178 int dlg_font_size = dlg_font->GetPointSize();
179#if defined(__WXOSX__) || defined(__WXGTK3__)
180 // Support scaled HDPI displays.
181 dlg_font_size /= GetContentScaleFactor();
182#endif
183
184 pSFont = FontMgr::Get().FindOrCreateFont(
185 dlg_font_size - 2, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
186 wxFONTWEIGHT_NORMAL, FALSE, wxString(_T ( "Arial" )));
187 pSMFont = FontMgr::Get().FindOrCreateFont(
188 dlg_font_size - 1, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
189 wxFONTWEIGHT_NORMAL, FALSE, wxString(_T ( "Arial" )));
190 pMFont = FontMgr::Get().FindOrCreateFont(
191 dlg_font_size, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD,
192 FALSE, wxString(_T ( "Arial" )));
193 pLFont = FontMgr::Get().FindOrCreateFont(
194 dlg_font_size + 1, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
195 wxFONTWEIGHT_BOLD, FALSE, wxString(_T ( "Arial" )));
196
197 // Secondary grid
198 pblack_1 = wxThePenList->FindOrCreatePen(
199 this->GetForegroundColour(), wxMax(1, (int)(m_tcwin_scaler + 0.5)),
200 wxPENSTYLE_SOLID);
201 // Primary grid
202 pblack_2 = wxThePenList->FindOrCreatePen(
203 this->GetForegroundColour(), wxMax(2, (int)(2 * m_tcwin_scaler + 0.5)),
204 wxPENSTYLE_SOLID);
205 // Tide hours outline
206 pblack_3 = wxThePenList->FindOrCreatePen(
207 wxSystemSettings::GetColour(wxSYS_COLOUR_BTNSHADOW),
208 wxMax(1, (int)(m_tcwin_scaler + 0.5)), wxPENSTYLE_SOLID);
209 // System time vertical line
210 pred_2 = wxThePenList->FindOrCreatePen(
211 wxColor(230, 54, 54), wxMax(4, (int)(4 * m_tcwin_scaler + 0.5)),
212 wxPENSTYLE_SOLID);
213 // Selected time vertical line (from GRIB plugin or timeline widget)
214 pred_time = wxThePenList->FindOrCreatePen(
215 wxColour(0, 100, 255), wxMax(4, (int)(4 * m_tcwin_scaler + 0.5)),
216 wxPENSTYLE_DOT);
217 // Graph background
218 pltgray = wxTheBrushList->FindOrCreateBrush(this->GetBackgroundColour(),
219 wxBRUSHSTYLE_SOLID);
220 // Tide hours background
221 pltgray2 = wxTheBrushList->FindOrCreateBrush(this->GetBackgroundColour(),
222 wxBRUSHSTYLE_SOLID);
223 pgraph = wxThePenList->FindOrCreatePen(
224 wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT),
225 wxMax(1, (int)(m_tcwin_scaler + 0.5)), wxPENSTYLE_SOLID);
226
227 DimeControl(this);
228
229 // Initialize the station text now that fonts are available
230 InitializeStationText();
231}
232
233TCWin::~TCWin() {
234 m_TimeIndicatorTimer.Stop();
235 pParent->Refresh(false);
236}
237
238void TCWin::CreateLayout() {
239 // Create main sizer
240 wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
241
242 // ROW 1: Top panel for station info and tide list (two cells)
243 m_topPanel = new wxPanel(this, wxID_ANY);
244 wxBoxSizer *topSizer = new wxBoxSizer(wxHORIZONTAL);
245
246 // Left cell: Station info text control with minimum size
247 m_ptextctrl =
248 new wxTextCtrl(m_topPanel, -1, "", wxDefaultPosition, wxDefaultSize,
249 wxTE_MULTILINE | wxTE_READONLY | wxTE_DONTWRAP);
250 m_ptextctrl->SetMinSize(wxSize(200, 120)); // Minimum readable size
251
252 // Right cell: Tide list (LW/HW) with minimum size
253 m_tList = new wxListCtrl(m_topPanel, -1, wxDefaultPosition, wxDefaultSize,
254 wxLC_REPORT | wxLC_NO_HEADER);
255 m_tList->SetMinSize(wxSize(150, 120)); // Minimum to show a few entries
256
257 // Add first column to tide list
258 wxListItem col0;
259 col0.SetId(0);
260 col0.SetText("");
261 col0.SetAlign(wxLIST_FORMAT_LEFT);
262 col0.SetWidth(140);
263 m_tList->InsertColumn(0, col0);
264
265 // Add controls to top sizer (first row: two cells)
266 topSizer->Add(m_ptextctrl, 2, wxEXPAND | wxALL,
267 5); // Left cell: 2/3 of width
268 topSizer->Add(m_tList, 1, wxEXPAND | wxALL, 5); // Right cell: 1/3 of width
269
270 m_topPanel->SetSizer(topSizer);
271
272 // ROW 2: Chart panel (expandable - gets remaining space)
273 m_chartPanel = new TideChartPanel(this);
274
275 // ROW 3: Button panel (fixed height at bottom)
276 m_buttonPanel = new wxPanel(this, wxID_ANY);
277 wxBoxSizer *buttonSizer = new wxBoxSizer(wxHORIZONTAL);
278
279 // Create buttons
280 PR_button = new wxButton(m_buttonPanel, ID_TCWIN_PR, _("Prev"));
281 NX_button = new wxButton(m_buttonPanel, ID_TCWIN_NX, _("Next"));
282 OK_button = new wxButton(m_buttonPanel, wxID_OK, _("OK"));
283
284 // Create timezone choice
285 wxString choiceOptions[] = {_("LMT@Station"), _("UTC")};
286 int numChoices = sizeof(choiceOptions) / sizeof(wxString);
287 m_choiceTimezone = new wxChoice(m_buttonPanel, wxID_ANY, wxDefaultPosition,
288 wxDefaultSize, numChoices, choiceOptions);
289 m_choiceTimezone->SetSelection(m_tzoneDisplay);
290 m_choiceTimezone->SetToolTip(
291 _("Select whether tide times are shown in UTC or Local Mean Time (LMT) "
292 "at the station"));
293
294 // Layout buttons: Prev/Next on left, timezone/OK on right
295 buttonSizer->Add(PR_button, 0, wxALL, 5);
296 buttonSizer->Add(NX_button, 0, wxALL, 5);
297 buttonSizer->AddStretchSpacer(1); // Push timezone and OK to the right
298 buttonSizer->Add(m_choiceTimezone, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
299 buttonSizer->AddSpacer(10); // Small space between timezone and OK
300 buttonSizer->Add(OK_button, 0, wxALL, 5);
301
302 m_buttonPanel->SetSizer(buttonSizer);
303
304 // Add all rows to main sizer with proper proportions
305 mainSizer->Add(m_topPanel, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP,
306 5); // Row 1: Fixed height, no overlap
307 mainSizer->Add(m_chartPanel, 1, wxEXPAND | wxLEFT | wxRIGHT,
308 5); // Row 2: Expandable, gets remaining space
309 mainSizer->Add(m_buttonPanel, 0, wxEXPAND | wxALL,
310 5); // Row 3: Fixed height at bottom
311
312 // Set the main sizer
313 SetSizer(mainSizer);
314
315 // Connect timezone choice event
316 m_choiceTimezone->Connect(wxEVT_COMMAND_CHOICE_SELECTED,
317 wxCommandEventHandler(TCWin::TimezoneOnChoice),
318 NULL, this);
319}
320
321void TCWin::InitializeStationText() {
322 // Fill station information in text control
323 m_ptextctrl->Clear();
324
325 wxString locn(pIDX->IDX_station_name, wxConvUTF8);
326 wxString locna, locnb;
327 if (locn.Contains(wxString(_T ( "," )))) {
328 locna = locn.BeforeFirst(',');
329 locnb = locn.AfterFirst(',');
330 } else {
331 locna = locn;
332 locnb.Empty();
333 }
334
335 // write the first line
336 wxTextAttr style;
337 style.SetFont(*pLFont);
338 m_ptextctrl->SetDefaultStyle(style);
339
340 m_ptextctrl->AppendText(locna);
341 m_ptextctrl->AppendText("\n");
342
343 style.SetFont(*pSMFont);
344 m_ptextctrl->SetDefaultStyle(style);
345
346 if (!locnb.IsEmpty()) m_ptextctrl->AppendText(locnb);
347 m_ptextctrl->AppendText("\n");
348
349 // Reference to the master station
350 if (('t' == pIDX->IDX_type) || ('c' == pIDX->IDX_type)) {
351 wxString mref(pIDX->IDX_reference_name, wxConvUTF8);
352 mref.Prepend(" ");
353
354 m_ptextctrl->AppendText(_("Reference Station :"));
355 m_ptextctrl->AppendText("\n");
356
357 m_ptextctrl->AppendText(mref);
358 m_ptextctrl->AppendText("\n");
359
360 } else {
361 m_ptextctrl->AppendText("\n");
362 }
363
364 // Show the data source
365 wxString dsource(pIDX->source_ident, wxConvUTF8);
366 dsource.Prepend(" ");
367
368 m_ptextctrl->AppendText(_("Data Source :"));
369 m_ptextctrl->AppendText("\n");
370
371 m_ptextctrl->AppendText(dsource);
372
373 m_ptextctrl->ShowPosition(0);
374}
375
376void TCWin::PaintChart(wxDC &dc, const wxRect &chartRect) {
377 if (!IsShown()) {
378 return;
379 }
380
381 // Store the original graph rectangle and use the provided chartRect
382 wxRect originalGraphRect = m_graph_rect;
383 m_graph_rect = chartRect;
384
385 int i;
386 char sbuf[100];
387 int w;
388 float tcmax, tcmin;
389
390 if (m_graph_rect.x == 0) {
391 m_graph_rect = originalGraphRect;
392 return;
393 }
394
395 // Get client size for positioning date/timezone text below chart
396 int x, y;
397 GetClientSize(&x, &y);
398
399 // Adjust colors with current color scheme
400 pblack_1->SetColour(this->GetForegroundColour());
401 pblack_2->SetColour(this->GetForegroundColour());
402 pltgray->SetColour(this->GetBackgroundColour());
403 pltgray2->SetColour(this->GetBackgroundColour());
404 pred_2->SetColour(GetDimedColor(wxColor(230, 54, 54)));
405 pred_time->SetColour(GetDimedColor(wxColour(0, 100, 255)));
406
407 // Box the graph
408 dc.SetPen(*pblack_1);
409 dc.SetBrush(*pltgray);
410 dc.DrawRectangle(m_graph_rect.x, m_graph_rect.y, m_graph_rect.width,
411 m_graph_rect.height);
412
413 // On some platforms, we cannot draw rotated text.
414 // So, reduce the complexity of horizontal axis time labels
415#ifndef __WXMSW__
416 const int hour_delta = 4;
417#else
418 const int hour_delta = 1;
419#endif
420
421 int hour_start = 0;
422
423 // Horizontal axis
424 dc.SetFont(*pSFont);
425 for (i = 0; i < 25; i++) {
426 int xd = m_graph_rect.x + ((i)*m_graph_rect.width / 25);
427 if (hour_delta != 1) {
428 if (i % hour_delta == 0) {
429 dc.SetPen(*pblack_2);
430 dc.DrawLine(xd, m_graph_rect.y, xd,
431 m_graph_rect.y + m_graph_rect.height + 5);
432 char sbuf[16];
433 int hour_show = hour_start + i;
434 if (hour_show >= 24) hour_show -= 24;
435 sprintf(sbuf, "%02d", hour_show);
436 int x_shim = -20;
437 dc.DrawText(wxString(sbuf, wxConvUTF8),
438 xd + x_shim + (m_graph_rect.width / 25) / 2,
439 m_graph_rect.y + m_graph_rect.height + 8);
440 } else {
441 dc.SetPen(*pblack_1);
442 dc.DrawLine(xd, m_graph_rect.y, xd,
443 m_graph_rect.y + m_graph_rect.height + 5);
444 }
445 } else {
446 dc.SetPen(*pblack_1);
447 dc.DrawLine(xd, m_graph_rect.y, xd,
448 m_graph_rect.y + m_graph_rect.height + 5);
449 wxString sst;
450 sst.Printf("%02d", i);
451 dc.DrawRotatedText(sst, xd + (m_graph_rect.width / 25) / 2,
452 m_graph_rect.y + m_graph_rect.height + 8, 270.);
453 }
454 }
455
456 // Time indicators - system time and "selected" time (e.g. GRIB time)
457 wxDateTime system_now = wxDateTime::Now();
458 wxDateTime this_now = gTimeSource;
459 bool cur_time = !gTimeSource.IsValid();
460 if (cur_time) this_now = wxDateTime::Now();
461
462 // Always draw system time indicator (solid red line)
463 time_t t_system_now = system_now.GetTicks();
464 t_system_now -= m_diff_mins * 60;
465 if (m_tzoneDisplay == 0) // LMT @ Station
466 t_system_now += m_stationOffset_mins * 60;
467
468 float t_system_ratio =
469 m_graph_rect.width * (t_system_now - m_t_graphday_GMT) / (25 * 3600.0f);
470 int x_system = (t_system_ratio < 0 || t_system_ratio > m_graph_rect.width)
471 ? -1
472 : m_graph_rect.x + (int)t_system_ratio;
473
474 if (x_system >= 0) {
475 dc.SetPen(*pred_2); // solid red line for system time
476 dc.DrawLine(x_system, m_graph_rect.y, x_system,
477 m_graph_rect.y + m_graph_rect.height);
478 }
479
480 // Draw "selected time" indicator (from timeline widget) if different from
481 // system time.
482 if (gTimeSource.IsValid()) {
483 time_t t_selected_time = gTimeSource.GetTicks();
484 if (abs(t_selected_time - t_system_now) > 300) {
485 t_selected_time -= m_diff_mins * 60;
486 if (m_tzoneDisplay == 0) // LMT @ Station
487 t_selected_time += m_stationOffset_mins * 60;
488
489 float t_selected_time_ratio = m_graph_rect.width *
490 (t_selected_time - m_t_graphday_GMT) /
491 (25 * 3600.0f);
492 int x_selected_time = (t_selected_time_ratio < 0 ||
493 t_selected_time_ratio > m_graph_rect.width)
494 ? -1
495 : m_graph_rect.x + (int)t_selected_time_ratio;
496
497 if (x_selected_time >= 0) {
498 dc.SetPen(*pred_time);
499 dc.DrawLine(x_selected_time, m_graph_rect.y, x_selected_time,
500 m_graph_rect.y + m_graph_rect.height);
501 }
502 }
503 }
504 dc.SetPen(*pblack_1);
505
506 // Build the array of values, capturing max and min and HW/LW list
507 if (!btc_valid) {
508 float dir;
509 tcmax = -10;
510 tcmin = 10;
511 float val = -100;
512 m_tList->DeleteAllItems();
513 int list_index = 0;
514 bool wt = false;
515
516 wxBeginBusyCursor();
517
518 // The tide/current modules calculate values based on PC local time
519 // We want UTC, so adjust accordingly
520 int tt_localtz = m_t_graphday_GMT + (m_diff_mins * 60);
521 // then eventually we could need LMT at station
522 if (m_tzoneDisplay == 0)
523 tt_localtz -= m_stationOffset_mins * 60; // LMT at station
524
525 // get tide flow sens ( flood or ebb ? )
526 ptcmgr->GetTideFlowSens(tt_localtz, BACKWARD_TEN_MINUTES_STEP,
527 pIDX->IDX_rec_num, tcv[0], val, wt);
528
529 for (i = 0; i < 26; i++) {
530 int tt = tt_localtz + (i * FORWARD_ONE_HOUR_STEP);
531 ptcmgr->GetTideOrCurrent(tt, pIDX->IDX_rec_num, tcv[i], dir);
532 tt_tcv[i] = tt; // store the corresponding time_t value
533 if (tcv[i] > tcmax) tcmax = tcv[i];
534 if (tcv[i] < tcmin) tcmin = tcv[i];
535
536 if (TIDE_PLOT == m_plot_type) {
537 if (!((tcv[i] > val) == wt) && (i > 0)) { // if tide flow sense change
538 float tcvalue; // look backward for HW or LW
539 time_t tctime;
540 ptcmgr->GetHightOrLowTide(tt, BACKWARD_TEN_MINUTES_STEP,
541 BACKWARD_ONE_MINUTES_STEP, tcv[i], wt,
542 pIDX->IDX_rec_num, tcvalue, tctime);
543 if (tctime > tt_localtz) { // Only show events visible in graphic
544 // presently shown
545 wxDateTime tcd; // write date
546 wxString s, s1;
547 tcd.Set(tctime - (m_diff_mins * 60));
548 if (m_tzoneDisplay == 0) // LMT @ Station
549 tcd.Set(tctime + (m_stationOffset_mins - m_diff_mins) * 60);
550
551 s.Printf(tcd.Format("%H:%M "));
552 s1.Printf("%05.2f ", tcvalue); // write value
553 s.Append(s1);
554 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
555 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
556 s.Append(" ");
557 (wt) ? s.Append(_("HW")) : s.Append(_("LW")); // write HW or LT
558
559 wxListItem li;
560 li.SetId(list_index);
561 li.SetAlign(wxLIST_FORMAT_LEFT);
562 li.SetText(s);
563 li.SetColumn(0);
564 m_tList->InsertItem(li);
565 list_index++;
566 }
567 wt = !wt; // change tide flow sens
568 }
569 val = tcv[i];
570 }
571 if (CURRENT_PLOT == m_plot_type) {
572 wxDateTime thx; // write date
573 wxString s, s1;
574 thx.Set((time_t)tt - (m_diff_mins * 60));
575 if (m_tzoneDisplay == 0) // LMT @ Station
576 thx.Set((time_t)tt + (m_stationOffset_mins - m_diff_mins) * 60);
577
578 s.Printf(thx.Format("%H:%M "));
579 s1.Printf("%05.2f ", fabs(tcv[i])); // write value
580 s.Append(s1);
581 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
582 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
583 s1.Printf(" %03.0f", dir); // write direction
584 s.Append(s1);
585
586 wxListItem li;
587 li.SetId(list_index);
588 li.SetAlign(wxLIST_FORMAT_LEFT);
589 li.SetText(s);
590 li.SetColumn(0);
591 m_tList->InsertItem(li);
592 list_index++;
593 }
594 }
595
596 wxEndBusyCursor();
597
598 // Set up the vertical parameters based on Tide or Current plot
599 if (CURRENT_PLOT == m_plot_type) {
600 it = std::max(abs((int)tcmin - 1), abs((int)tcmax + 1));
601 ib = -it;
602 im = 2 * it;
603 m_plot_y_offset = m_graph_rect.height / 2;
604 val_off = 0;
605 } else {
606 ib = (int)tcmin;
607 if (tcmin < 0) ib -= 1;
608 it = (int)tcmax + 1;
609 im = it - ib;
610 m_plot_y_offset = (m_graph_rect.height * (it - ib)) / im;
611 val_off = ib;
612 }
613
614 // Arrange to skip some lines and legends if there are too many for the
615 // vertical space we have
616 int height_stext;
617 dc.GetTextExtent("1", NULL, &height_stext);
618 float available_lines = (float)m_graph_rect.height / height_stext;
619 i_skip = (int)ceil(im / available_lines);
620
621 if (CURRENT_PLOT == m_plot_type && i_skip != 1) {
622 // Adjust steps so slack current "0" line is always drawn on graph
623 ib -= it % i_skip;
624 it = -ib;
625 im = 2 * it;
626 }
627
628 // Build spline list of points
629 for (auto it = m_sList.begin(); it != m_sList.end(); it++) delete (*it);
630 m_sList.clear();
631
632 for (i = 0; i < 26; i++) {
633 wxPoint *pp = new wxPoint;
634 pp->x = m_graph_rect.x + ((i)*m_graph_rect.width / 25);
635 pp->y = m_graph_rect.y + (m_plot_y_offset) -
636 (int)((tcv[i] - val_off) * m_graph_rect.height / im);
637 m_sList.push_back(pp);
638 }
639
640 btc_valid = true;
641 }
642
643 // Graph legend
644 dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
645
646 // Vertical Axis
647 i = ib;
648 while (i < it + 1) {
649 int yd = m_graph_rect.y + (m_plot_y_offset) -
650 ((i - val_off) * m_graph_rect.height / im);
651 if ((m_plot_y_offset + m_graph_rect.y) == yd)
652 dc.SetPen(*pblack_2);
653 else
654 dc.SetPen(*pblack_1);
655
656 dc.DrawLine(m_graph_rect.x, yd, m_graph_rect.x + m_graph_rect.width, yd);
657 snprintf(sbuf, 99, "%d", i);
658 dc.DrawText(wxString(sbuf, wxConvUTF8), m_graph_rect.x - 20, yd - 5);
659 i += i_skip;
660 }
661
662 // Draw the Value curve
663 wxPointList list;
664 for (auto &p : m_sList) list.Append(p);
665
666 dc.SetPen(*pgraph);
667#if wxUSE_SPLINES
668 dc.DrawSpline(&list);
669#else
670 dc.DrawLines(&list);
671#endif
672
673 // More Info - positioned below chart panel
674 if (m_tzoneDisplay == 0) {
675 int station_offset = ptcmgr->GetStationTimeOffset(pIDX);
676 int h = station_offset / 60;
677 int m = station_offset - (h * 60);
678 if (m_graphday.IsDST()) h += 1;
679 m_stz.Printf("UTC %+03d:%02d", h, m);
680
681 // Make the "nice" (for the US) station time-zone string, brutally by
682 // hand
683 double lat = ptcmgr->GetStationLat(pIDX);
684 if (lat > 20.0) {
685 wxString mtz;
686 switch (ptcmgr->GetStationTimeOffset(pIDX)) {
687 case -240:
688 mtz = "AST";
689 break;
690 case -300:
691 mtz = "EST";
692 break;
693 case -360:
694 mtz = "CST";
695 break;
696 }
697 if (mtz.Len()) {
698 if (m_graphday.IsDST()) mtz[1] = 'D';
699 m_stz = mtz;
700 }
701 }
702 } else {
703 m_stz = "UTC";
704 }
705
706 int h;
707 dc.SetFont(*pSFont);
708 dc.GetTextExtent(m_stz, &w, &h);
709 // Position timezone text below the chart, centered horizontally
710 dc.DrawText(m_stz, m_graph_rect.x + (m_graph_rect.width / 2) - (w / 2),
711 m_graph_rect.y + m_graph_rect.height + 35);
712
713 wxString sdate;
714 if (g_locale == "en_US")
715 sdate = m_graphday.Format("%A %b %d, %Y");
716 else
717 sdate = m_graphday.Format("%A %d %b %Y");
718
719 dc.SetFont(*pMFont);
720 dc.GetTextExtent(sdate, &w, &h);
721 // Position date text below the chart, centered horizontally
722 dc.DrawText(sdate, m_graph_rect.x + (m_graph_rect.width / 2) - (w / 2),
723 m_graph_rect.y + m_graph_rect.height + 15);
724
725 Station_Data *pmsd = pIDX->pref_sta_data;
726 if (pmsd) {
727 dc.GetTextExtent(wxString(pmsd->units_conv, wxConvUTF8), &w, &h);
728 dc.DrawRotatedText(wxString(pmsd->units_conv, wxConvUTF8), 5,
729 m_graph_rect.y + m_graph_rect.height / 2 + w / 2, 90.);
730 }
731
732 // Show flood and ebb directions
733 if ((strchr("c", pIDX->IDX_type)) || (strchr("C", pIDX->IDX_type))) {
734 dc.SetFont(*pSFont);
735 wxString fdir;
736 fdir.Printf("%03d", pIDX->IDX_flood_dir);
737 dc.DrawText(fdir, m_graph_rect.x + m_graph_rect.width + 4,
738 m_graph_rect.y + m_graph_rect.height * 1 / 4);
739
740 wxString edir;
741 edir.Printf("%03d", pIDX->IDX_ebb_dir);
742 dc.DrawText(edir, m_graph_rect.x + m_graph_rect.width + 4,
743 m_graph_rect.y + m_graph_rect.height * 3 / 4);
744 }
745
746 // Today or tomorrow
747 if ((m_button_height * 15) < x && cur_time) { // large enough horizontally?
748 wxString sday;
749 int day = m_graphday.GetDayOfYear();
750 if (m_graphday.GetYear() == this_now.GetYear()) {
751 if (day == this_now.GetDayOfYear())
752 sday.Append(_("Today"));
753 else if (day == this_now.GetDayOfYear() + 1)
754 sday.Append(_("Tomorrow"));
755 else
756 sday.Append(m_graphday.GetWeekDayName(m_graphday.GetWeekDay()));
757 } else if (m_graphday.GetYear() == this_now.GetYear() + 1 &&
758 day == this_now.Add(wxTimeSpan::Day()).GetDayOfYear())
759 sday.Append(_("Tomorrow"));
760
761 dc.SetFont(*pSFont);
762 dc.GetTextExtent(sday, &w, &h);
763 // Position day text at the left side of the chart, below it
764 dc.DrawText(sday, m_graph_rect.x,
765 m_graph_rect.y + m_graph_rect.height + 15);
766 }
767
768 // Render "Spot of interest"
769 double spotDim = 4 * g_Platform->GetDisplayDPmm();
770 dc.SetBrush(*wxTheBrushList->FindOrCreateBrush(GetGlobalColor("YELO1"),
771 wxBRUSHSTYLE_SOLID));
772 dc.SetPen(wxPen(GetGlobalColor("URED"),
773 wxMax(2, 0.5 * g_Platform->GetDisplayDPmm())));
774 dc.DrawRoundedRectangle(xSpot - spotDim / 2, ySpot - spotDim / 2, spotDim,
775 spotDim, spotDim / 2);
776
777 dc.SetBrush(*wxTheBrushList->FindOrCreateBrush(GetGlobalColor("UBLCK"),
778 wxBRUSHSTYLE_SOLID));
779 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
780 double ispotDim = spotDim / 5.;
781 dc.DrawRoundedRectangle(xSpot - ispotDim / 2, ySpot - ispotDim / 2, ispotDim,
782 ispotDim, ispotDim / 2);
783
784 // Restore original graph rectangle
785 m_graph_rect = originalGraphRect;
786}
787
788void TCWin::SetTimeFactors() {
789 // Figure out this computer timezone minute offset
790 wxDateTime this_now = gTimeSource;
791 bool cur_time = !gTimeSource.IsValid();
792
793 if (cur_time) {
794 this_now = wxDateTime::Now();
795 }
796 wxDateTime this_gmt = this_now.ToGMT();
797
798#if wxCHECK_VERSION(2, 6, 2)
799 wxTimeSpan diff = this_now.Subtract(this_gmt);
800#else
801 wxTimeSpan diff = this_gmt.Subtract(this_now);
802#endif
803
804 m_diff_mins = diff.GetMinutes();
805
806 // Correct a bug in wx3.0.2
807 // If the system TZ happens to be GMT, with DST active (e.g.summer in
808 // London), then wxDateTime returns incorrect results for toGMT() method
809#if wxCHECK_VERSION(3, 0, 2)
810 if (m_diff_mins == 0 && this_now.IsDST()) m_diff_mins += 60;
811#endif
812
813 int station_offset = ptcmgr->GetStationTimeOffset(pIDX);
814
815 m_stationOffset_mins = station_offset;
816 if (this_now.IsDST()) {
817 m_stationOffset_mins += 60;
818 }
819
820 // Correct a bug in wx3.0.2
821 // If the system TZ happens to be GMT, with DST active (e.g.summer in
822 // London), then wxDateTime returns incorrect results for toGMT() method
823#if wxCHECK_VERSION(3, 0, 2)
824// if( this_now.IsDST() )
825// m_corr_mins +=60;
826#endif
827
828 // Establish the inital drawing day as today, in the timezone of the
829 // station
830 m_graphday = this_gmt;
831
832 int day_gmt = this_gmt.GetDayOfYear();
833
834 time_t ttNow = this_now.GetTicks();
835 time_t tt_at_station =
836 ttNow - (m_diff_mins * 60) + (m_stationOffset_mins * 60);
837 wxDateTime atStation(tt_at_station);
838 int day_at_station = atStation.GetDayOfYear();
839
840 if (day_gmt > day_at_station) {
841 wxTimeSpan dt(24, 0, 0, 0);
842 m_graphday.Subtract(dt);
843 } else if (day_gmt < day_at_station) {
844 wxTimeSpan dt(24, 0, 0, 0);
845 m_graphday.Add(dt);
846 }
847
848 wxDateTime graphday_00 = m_graphday; // this_gmt;
849 graphday_00.ResetTime();
850 time_t t_graphday_00 = graphday_00.GetTicks();
851
852 // Correct a Bug in wxWidgets time support
853 // if( !graphday_00.IsDST() && m_graphday.IsDST() ) t_graphday_00 -= 3600;
854 // if( graphday_00.IsDST() && !m_graphday.IsDST() ) t_graphday_00 += 3600;
855
856 m_t_graphday_GMT = t_graphday_00;
857
858 btc_valid = false; // Force re-calculation
859}
860
861void TCWin::TimezoneOnChoice(wxCommandEvent &event) {
862 m_tzoneDisplay = m_choiceTimezone->GetSelection();
863 SetTimeFactors();
864
865 Refresh();
866}
867
868void TCWin::RecalculateSize() {
869 wxSize parent_size(2000, 2000);
870 if (pParent) parent_size = pParent->GetClientSize();
871
872 int unscaledheight = 600;
873 int unscaledwidth = 650;
874
875 // value of m_tcwin_scaler should be about unity on a 100 dpi display,
876 // when scale parameter g_tcwin_scale is 100
877 // parameter g_tcwin_scale is set in config file as value of
878 // TideCurrentWindowScale
879 g_tcwin_scale = wxMax(g_tcwin_scale, 10); // sanity check on g_tcwin_scale
880 m_tcwin_scaler = g_Platform->GetDisplayDPmm() * 0.254 * g_tcwin_scale / 100.0;
881
882 m_tc_size.x = (int)(unscaledwidth * m_tcwin_scaler + 0.5);
883 m_tc_size.y = (int)(unscaledheight * m_tcwin_scaler + 0.5);
884
885 m_tc_size.x = wxMin(m_tc_size.x, parent_size.x);
886 m_tc_size.y = wxMin(m_tc_size.y, parent_size.y);
887
888 int xc = m_x + 8;
889 int yc = m_y;
890
891 // Arrange for tcWindow to be always totally visible
892 // by shifting left and/or up
893 if ((m_x + 8 + m_tc_size.x) > parent_size.x) xc = xc - m_tc_size.x - 16;
894 if ((m_y + m_tc_size.y) > parent_size.y) yc = yc - m_tc_size.y;
895
896 // Don't let the window origin move out of client area
897 if (yc < 0) yc = 0;
898 if (xc < 0) xc = 0;
899
900 if (pParent) pParent->ClientToScreen(&xc, &yc);
901 m_position = wxPoint(xc, yc);
902
903 if (m_created) {
904 SetSize(m_tc_size);
905 Move(m_position);
906 }
907}
908
909void TCWin::OKEvent(wxCommandEvent &event) {
910 Hide();
911
912 // Ensure parent pointer is cleared before any potential deletion
913 if (pParent && pParent->pCwin == this) {
914 pParent->pCwin = NULL;
915 }
916
917 // Clean up global tide window counter and associated resources
918 --gpIDXn;
919 delete m_pTCRolloverWin;
920 m_pTCRolloverWin = NULL;
921 delete m_tList;
922 m_tList = NULL;
923
924 if (pParent) {
925 pParent->Refresh(false);
926 }
927
928 // Update the config file to set the user specified time zone.
929 if (pConfig) {
930 pConfig->SetPath(_T ( "/Settings/Others" ));
931 pConfig->Write(_T ( "TCWindowTimeZone" ), m_tzoneDisplay);
932 }
933
934 Destroy(); // that hurts
935}
936
937void TCWin::OnCloseWindow(wxCloseEvent &event) {
938 Hide();
939
940 // Ensure parent pointer is cleared before any potential deletion
941 if (pParent && pParent->pCwin == this) {
942 pParent->pCwin = NULL;
943 }
944
945 // Clean up global tide window counter and associated resources
946 --gpIDXn;
947 delete m_pTCRolloverWin;
948 m_pTCRolloverWin = NULL;
949 delete m_tList;
950 m_tList = NULL;
951
952 // Update the config file to set the user specified time zone.
953 if (pConfig) {
954 pConfig->SetPath(_T ( "/Settings/Others" ));
955 pConfig->Write(_T ( "TCWindowTimeZone" ), m_tzoneDisplay);
956 }
957
958 Destroy(); // that hurts
959}
960
961void TCWin::NXEvent(wxCommandEvent &event) {
962 wxTimeSpan dt(24, 0, 0, 0);
963 m_graphday.Add(dt);
964 wxDateTime dm = m_graphday;
965
966 wxDateTime graphday_00 = dm.ResetTime();
967 time_t t_graphday_00 = graphday_00.GetTicks();
968
969 if (!graphday_00.IsDST() && m_graphday.IsDST()) t_graphday_00 -= 3600;
970 if (graphday_00.IsDST() && !m_graphday.IsDST()) t_graphday_00 += 3600;
971
972 m_t_graphday_GMT = t_graphday_00;
973
974 btc_valid = false;
975 Refresh();
976}
977
978void TCWin::PREvent(wxCommandEvent &event) {
979 wxTimeSpan dt(-24, 0, 0, 0);
980 m_graphday.Add(dt);
981 wxDateTime dm = m_graphday;
982
983 wxDateTime graphday_00 = dm.ResetTime();
984 time_t t_graphday_00 = graphday_00.GetTicks();
985
986 if (!graphday_00.IsDST() && m_graphday.IsDST()) t_graphday_00 -= 3600;
987 if (graphday_00.IsDST() && !m_graphday.IsDST()) t_graphday_00 += 3600;
988
989 m_t_graphday_GMT = t_graphday_00;
990
991 btc_valid = false;
992 Refresh();
993}
994
995void TCWin::RePosition(void) {
996 // Position the window
997 double lon = pIDX->IDX_lon;
998 double lat = pIDX->IDX_lat;
999
1000 wxPoint r;
1001 pParent->GetCanvasPointPix(lat, lon, &r);
1002 pParent->ClientToScreen(&r.x, &r.y);
1003 Move(r);
1004}
1005
1006void TCWin::OnPaint(wxPaintEvent &event) {
1007 if (!IsShown()) {
1008 return;
1009 }
1010
1011 // With the new sizer-based layout, the main OnPaint method is simplified.
1012 // Chart rendering is now handled by the TideChartPanel's OnPaint method,
1013 // which delegates to our PaintChart() method.
1014
1015 wxPaintDC dc(this);
1016
1017 // Clear the background
1018 dc.SetBrush(wxBrush(GetBackgroundColour()));
1019 dc.SetPen(wxPen(GetBackgroundColour()));
1020 wxSize size = GetClientSize();
1021 dc.DrawRectangle(0, 0, size.GetWidth(), size.GetHeight());
1022
1023 // Note: Chart painting is now handled by TideChartPanel::OnPaint()
1024 // which calls our PaintChart() method. This eliminates the need for
1025}
1026
1027void TCWin::OnSize(wxSizeEvent &event) {
1028 if (!m_created) return;
1029
1030 // With sizer-based layout, we don't need manual positioning.
1031 // The sizers automatically handle layout when the window is resized.
1032
1033 // Force chart panel to refresh with new size
1034 if (m_chartPanel) {
1035 m_chartPanel->Refresh();
1036 }
1037
1038 // Invalidate cached chart data to force recalculation
1039 btc_valid = false;
1040
1041 // Allow sizers to handle the layout
1042 event.Skip();
1043}
1044
1045void TCWin::MouseEvent(wxMouseEvent &event) {
1046 // This is now mainly for compatibility.
1047 // Chart mouse events are handled by HandleChartMouseMove
1048 event.GetPosition(&curs_x, &curs_y);
1049
1050 if (!m_TCWinPopupTimer.IsRunning())
1051 m_TCWinPopupTimer.Start(20, wxTIMER_ONE_SHOT);
1052}
1053
1054void TCWin::HandleChartMouseMove(int mainWindowX, int mainWindowY,
1055 const wxPoint &chartPanelPos) {
1056 // Store the main window coordinates for compatibility with existing rollover
1057 // code
1058 curs_x = mainWindowX;
1059 curs_y = mainWindowY;
1060
1061 // Also store the chart panel relative coordinates for calculations
1062 if (m_chartPanel) {
1063 // Calculate the chart rectangle within the chart panel
1064 wxSize panelSize = m_chartPanel->GetClientSize();
1065 int left_margin = 50; // Space for Y-axis numbers and units
1066 int other_margins = 5; // Smaller margins for top, right, bottom
1067 int chart_width = panelSize.GetWidth() - left_margin - other_margins;
1068 int chart_height = panelSize.GetHeight() - (2 * other_margins);
1069 int bottom_text_space = 50; // Increased space for date display
1070 chart_height -= bottom_text_space;
1071 chart_width = wxMax(chart_width, 300);
1072 chart_height = wxMax(chart_height, 150);
1073
1074 // Update the graph rectangle to match the current chart panel layout
1075 wxPoint chartPanelPos = m_chartPanel->GetPosition();
1076 m_graph_rect =
1077 wxRect(chartPanelPos.x + left_margin, chartPanelPos.y + other_margins,
1078 chart_width, chart_height);
1079 }
1080
1081 if (!m_TCWinPopupTimer.IsRunning())
1082 m_TCWinPopupTimer.Start(20, wxTIMER_ONE_SHOT);
1083}
1084
1085void TCWin::OnTCWinPopupTimerEvent(wxTimerEvent &event) {
1086 int x, y;
1087 bool ShowRollover;
1088
1089 GetClientSize(&x, &y);
1090 wxRegion cursorarea(m_graph_rect);
1091 if (cursorarea.Contains(curs_x, curs_y)) {
1092 ShowRollover = true;
1093 SetCursor(*pParent->pCursorCross);
1094 if (NULL == m_pTCRolloverWin) {
1095 m_pTCRolloverWin = new RolloverWin(this, -1, false);
1096 // doesn't really work, mouse positions are relative to rollover window
1097 // not this window.
1098 // effect: hide rollover window if mouse on rollover
1099 m_pTCRolloverWin->SetMousePropogation(1);
1100 m_pTCRolloverWin->Hide();
1101 }
1102 float t, d;
1103 wxString p, s;
1104
1105 // Calculate time based on actual chart rectangle position
1106 // t represents hours into the 25-hour display (0-25)
1107 float relativeX =
1108 (float)(curs_x - m_graph_rect.x) / (float)m_graph_rect.width;
1109 t = relativeX * 25.0f; // 25 hours displayed across the width
1110
1111 // Clamp to valid range
1112 t = wxMax(0.0f, wxMin(25.0f, t));
1113
1114 int tt = m_t_graphday_GMT + (int)(t * 3600);
1115 time_t ths = tt;
1116
1117 wxDateTime thd;
1118 thd.Set(ths);
1119 p.Printf(thd.Format("%Hh %Mmn"));
1120 p.Append("\n");
1121
1122 // The tide/current modules calculate values based on PC local time
1123 // We want UTC, so adjust accordingly
1124 int tt_localtz = m_t_graphday_GMT + (m_diff_mins * 60);
1125
1126 int ttv = tt_localtz + (int)(t * 3600);
1127 if (m_tzoneDisplay == 0) {
1128 ttv -= m_stationOffset_mins * 60; // LMT at station
1129 }
1130
1131 time_t tts = ttv;
1132
1133 // set tide level or current speed at that time
1134 ptcmgr->GetTideOrCurrent(tts, pIDX->IDX_rec_num, t, d);
1135 s.Printf("%3.2f ", (t < 0 && CURRENT_PLOT == m_plot_type)
1136 ? -t
1137 : t); // always positive if current
1138 p.Append(s);
1139
1140 // set unit
1141 Station_Data *pmsd = pIDX->pref_sta_data;
1142 if (pmsd) p.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
1143
1144 // set current direction
1145 if (CURRENT_PLOT == m_plot_type) {
1146 s.Printf("%3.0f%c", d, 0x00B0);
1147 p.Append("\n");
1148 p.Append(s);
1149 }
1150
1151 // set rollover area size
1152 wxSize win_size;
1153 win_size.Set(x * 90 / 100, y * 80 / 100);
1154
1155 m_pTCRolloverWin->SetString(p);
1156 m_pTCRolloverWin->SetBestPosition(curs_x, curs_y, 1, 1, TC_ROLLOVER,
1157 win_size);
1158 m_pTCRolloverWin->SetBitmap(TC_ROLLOVER);
1159 m_pTCRolloverWin->Refresh();
1160 m_pTCRolloverWin->Show();
1161
1162 // Mark the actual spot on the curve
1163 // x value is clear...
1164 // Find the point in the window that is used for the curve rendering,
1165 // rounding as necessary
1166
1167 int idx = 1; // in case m_graph_rect.width is weird ie ppx never > curs_x
1168 for (int i = 0; i < 26; i++) {
1169 float ppx = m_graph_rect.x + ((i)*m_graph_rect.width / 25.f);
1170 if (ppx > curs_x) {
1171 idx = i;
1172 break;
1173 }
1174 }
1175
1176 if (m_sList.size() > 0 && idx > 0 && idx < (int)m_sList.size()) {
1177 // Use iterator to access elements in std::list
1178 auto it_a = m_sList.begin();
1179 std::advance(it_a, idx - 1);
1180 auto it_b = m_sList.begin();
1181 std::advance(it_b, idx);
1182
1183 wxPoint *a = *it_a;
1184 wxPoint *b = *it_b;
1185
1186 float pct = (curs_x - a->x) / (float)((b->x - a->x));
1187 float dy = pct * (b->y - a->y);
1188
1189 ySpot = a->y + dy;
1190 xSpot = curs_x;
1191 } else {
1192 // Fallback if we can't find the curve point
1193 xSpot = curs_x;
1194 ySpot = m_graph_rect.y + m_graph_rect.height / 2;
1195 }
1196
1197 Refresh(true);
1198
1199 } else {
1200 SetCursor(*pParent->pCursorArrow);
1201 ShowRollover = false;
1202 }
1203
1204 if (m_pTCRolloverWin && m_pTCRolloverWin->IsShown() && !ShowRollover) {
1205 m_pTCRolloverWin->Hide();
1206 }
1207}
1208
1209void TCWin::OnTimeIndicatorTimer(wxTimerEvent &event) {
1210 // Refresh to update the red line (system time indicator)
1211 Refresh(false);
1212}
Generic Chart canvas base.
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:151
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4387
wxFont * FindOrCreateFont(int point_size, wxFontFamily family, wxFontStyle style, wxFontWeight weight, bool underline=false, const wxString &facename=wxEmptyString, wxFontEncoding encoding=wxFONTENCODING_DEFAULT)
Creates or finds a matching font in the font cache.
Definition FontMgr.cpp:449
wxFont * GetFont(const wxString &TextElement, int requested_font_size=0)
Gets a font object for a UI element.
Definition FontMgr.cpp:200
Represents an index entry for tidal and current data.
Definition IDX_entry.h:49
char IDX_type
Entry type identifier "TCtcIUu".
Definition IDX_entry.h:61
char IDX_reference_name[MAXNAMELEN]
Name of the reference station.
Definition IDX_entry.h:82
int IDX_flood_dir
Flood current direction (in degrees)
Definition IDX_entry.h:73
char IDX_station_name[MAXNAMELEN]
Name of the tidal or current station.
Definition IDX_entry.h:63
char source_ident[MAXNAMELEN]
Identifier of the source (typically file name)
Definition IDX_entry.h:57
int IDX_ebb_dir
Ebb current direction (in degrees)
Definition IDX_entry.h:74
double IDX_lat
Latitude of the station (in degrees, +North)
Definition IDX_entry.h:65
double IDX_lon
Longitude of the station (in degrees, +East)
Definition IDX_entry.h:64
Station_Data * pref_sta_data
Pointer to the reference station data.
Definition IDX_entry.h:97
int IDX_rec_num
Record number for multiple entries with same name.
Definition IDX_entry.h:60
Provides platform-specific support utilities for OpenCPN.
Definition tcmgr.h:91
Definition TCWin.h:45
wxFont * GetOCPNScaledFont(wxString item, int default_size)
Retrieves a font from FontMgr, optionally scaled for physical readability.
Definition gui_lib.cpp:56
General purpose GUI support.