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