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