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