OpenCPN Partial API docs
Loading...
Searching...
No Matches
route_printout.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * Copyright (C) 2012 by David S. Register *
3 * Copyright (C) 2025 by NoCodeHummel *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
17 **************************************************************************/
18
25#ifdef __WXMSW__
26#include <stdlib.h>
27#include <math.h>
28#include <time.h>
29#include <psapi.h>
30#endif
31
32#ifndef __WXMSW__
33#include <signal.h>
34#include <setjmp.h>
35#endif
36
37#include "gl_headers.h" // Must be included before anything using GL stuff
38
39#include <wx/wxprec.h>
40
41#ifndef WX_PRECOMP
42#include <wx/wx.h>
43#endif
44
45#ifdef __WXMSW__
46// #include "c:\\Program Files\\visual leak detector\\include\\vld.h"
47#endif
48
49#include <wx/aui/aui.h>
50#include <wx/brush.h>
51#include <wx/colour.h>
52#include <wx/dialog.h>
53#include <wx/intl.h>
54#include <wx/printdlg.h>
55#include <wx/print.h>
56#include <wx/statline.h>
57#include <wx/stdpaths.h>
58
59#include "libgui/button_switch.h"
60
61#include "model/navutil_base.h"
62#include "model/route.h"
63#include "navutil.h"
64#include "print_dialog.h"
65#include "printtable.h"
66#include "route_printout.h"
67#include "tcmgr.h"
68
69// Make _() return std::string instead of wxString;
70#undef _
71#if wxCHECK_VERSION(3, 2, 0)
72#define _(s) wxGetTranslation(wxASCII_STR(s)).ToStdString()
73#else
74#define _(s) wxGetTranslation((s)).ToStdString()
75#endif
76
77using namespace std;
78
79static const std::unordered_map<RoutePrintOptions, std::string> kLabelByOption =
80 {{RoutePrintOptions::kWaypointName, _("Waypoint")},
81 {RoutePrintOptions::kWaypointPosition, _("Position")},
82 {RoutePrintOptions::kWaypointCourse, _("Course")},
83 {RoutePrintOptions::kWaypointDistance, _("Distance")},
84 {RoutePrintOptions::kWaypointDescription, _("Description")},
85 {RoutePrintOptions::kWaypointSpeed, _("Speed")},
86 {RoutePrintOptions::kWaypointETA, _("ETA")},
87 {RoutePrintOptions::kWaypointETD, _("ETD")},
88 {RoutePrintOptions::kWaypointTideEvent, _("Next tide event")}};
89
90namespace {
91
92class ButtonSizer : public wxStdDialogButtonSizer {
93public:
94 explicit ButtonSizer(wxWindow* parent) : wxStdDialogButtonSizer() {
95 auto ok_btn = new wxButton(parent, wxID_OK, _("Print..."));
96 AddButton(ok_btn);
97 AddButton(new wxButton(parent, wxID_CANCEL));
98 SetAffirmativeButton(ok_btn);
99 Realize();
100 }
101};
102
103} // namespace
104
105RoutePrintDlg::RoutePrintDlg(wxWindow* parent)
106 : wxDialog(parent, wxID_ANY, _("Print route")) {
107 auto flags = wxSizerFlags().Border();
108 auto grid = new wxFlexGridSizer(2);
109 for (auto& [option, label] : kLabelByOption) {
110 grid->Add(new wxStaticText(this, wxID_ANY, label), flags.Expand());
111 int id = wxWindow::NewControlId();
112 grid->Add(new SwitchButton(this, id, true), flags);
113 IdByOption[option] = id;
114 }
115 auto vbox = new wxBoxSizer(wxVERTICAL);
116 vbox->Add(grid, wxSizerFlags().Expand().Border());
117 vbox->Add(new wxStaticLine(this, wxID_ANY), flags.Expand());
118 vbox->Add(new ButtonSizer(this), flags.Expand());
119 SetSizer(vbox);
120 Fit();
121
122 Bind(wxEVT_CLOSE_WINDOW, [&](wxCloseEvent&) { Destroy(); });
123}
124
125bool RoutePrintDlg::IsEnabled(RoutePrintOptions option) const {
126 auto found = IdByOption.find(option);
127 assert(found != IdByOption.end() && "Illegal option");
128 int id = 0;
129 try {
130 id = IdByOption.at(option);
131 } catch (std::out_of_range&) {
132 }
133 SwitchButton* btn = dynamic_cast<SwitchButton*>(wxWindow::FindWindow(id));
134 assert(btn && "Could not look up button");
135 return btn->GetValue();
136}
137
139 const int tz_selection)
140 : m_route(route) {
141 // Offset text from the edge of the cell (Needed on Linux)
142 m_text_offset_x = 5;
143 m_text_offset_y = 8;
144
145 m_table.StartFillHeader();
146
147 m_table << _("Leg");
148
149 if (dlg.IsEnabled(RoutePrintOptions::kWaypointName)) {
150 m_table << _("Waypoint");
151 }
152 if (dlg.IsEnabled(RoutePrintOptions::kWaypointPosition)) {
153 m_table << _("Position");
154 }
155 if (dlg.IsEnabled(RoutePrintOptions::kWaypointCourse)) {
156 m_table << _("Course");
157 }
158 if (dlg.IsEnabled(RoutePrintOptions::kWaypointDistance)) {
159 m_table << _("Distance");
160 }
161 if (dlg.IsEnabled(RoutePrintOptions::kWaypointSpeed)) {
162 m_table << _("Speed");
163 }
164 if (dlg.IsEnabled(RoutePrintOptions::kWaypointETA)) {
165 m_table << _("ETA");
166 }
167 if (dlg.IsEnabled(RoutePrintOptions::kWaypointETD)) {
168 m_table << _("ETD");
169 }
170 if (dlg.IsEnabled(RoutePrintOptions::kWaypointTideEvent)) {
171 m_table << _("Next tide event");
172 }
173 if (dlg.IsEnabled(RoutePrintOptions::kWaypointDescription)) {
174 m_table << _("Description");
175 }
176
177 // setup widths for columns
178 m_table.StartFillWidths();
179 m_table << 20;
180
181 if (dlg.IsEnabled(RoutePrintOptions::kWaypointName)) {
182 m_table << 60;
183 }
184 if (dlg.IsEnabled(RoutePrintOptions::kWaypointPosition)) {
185 m_table << 60;
186 }
187 if (dlg.IsEnabled(RoutePrintOptions::kWaypointCourse)) {
188 m_table << 40;
189 }
190 if (dlg.IsEnabled(RoutePrintOptions::kWaypointDistance)) {
191 m_table << 40;
192 }
193 if (dlg.IsEnabled(RoutePrintOptions::kWaypointSpeed)) {
194 m_table << 40;
195 }
196 if (dlg.IsEnabled(RoutePrintOptions::kWaypointETA)) {
197 m_table << 80;
198 }
199 if (dlg.IsEnabled(RoutePrintOptions::kWaypointETD)) {
200 m_table << 80;
201 }
202 if (dlg.IsEnabled(RoutePrintOptions::kWaypointTideEvent)) {
203 m_table << 120;
204 }
205 if (dlg.IsEnabled(RoutePrintOptions::kWaypointDescription)) {
206 m_table << 120;
207 }
208
209 m_table.StartFillData();
210
211 for (int n = 1; n <= m_route->GetnPoints(); n++) {
212 RoutePoint* point = m_route->GetPoint(n);
213 if (NULL == point) continue;
214
215 if (n > 1) {
216 m_table << n - 1;
217 } else {
218 m_table << "---";
219 }
220
221 if (dlg.IsEnabled(RoutePrintOptions::kWaypointName)) {
222 m_table << point->GetName();
223 }
224 if (dlg.IsEnabled(RoutePrintOptions::kWaypointPosition)) {
225 std::wostringstream point_position;
226 point_position << toSDMM(1, point->m_lat, false) << "\n"
227 << toSDMM(2, point->m_lon, false);
228 m_table << point_position.str();
229 }
230 if (dlg.IsEnabled(RoutePrintOptions::kWaypointCourse)) {
231 if (n > 1) {
232 m_table << formatAngle(point->GetCourse());
233 } else {
234 m_table << "---";
235 }
236 }
237 if (dlg.IsEnabled(RoutePrintOptions::kWaypointDistance)) {
238 if (n > 1) {
239 std::ostringstream point_distance;
240 point_distance << std::fixed << std::setprecision(2) << std::setw(6)
241 << toUsrDistance(point->GetDistance())
242 << getUsrDistanceUnit();
243 m_table << point_distance.str();
244 } else {
245 m_table << "---";
246 }
247 }
248 if (dlg.IsEnabled(RoutePrintOptions::kWaypointSpeed)) {
249 std::wostringstream point_speed;
250 if (n > 1) {
251 point_speed << std::fixed << std::setprecision(1);
252 if (point->GetPlannedSpeed() < .1) {
253 point_speed << toUsrSpeed(m_route->m_PlannedSpeed);
254 } else {
255 point_speed << toUsrSpeed(point->GetPlannedSpeed());
256 }
257 point_speed << getUsrSpeedUnit().ToStdString();
258 m_table << point_speed.str();
259 } else {
260 m_table << "---";
261 }
262 }
263 if (dlg.IsEnabled(RoutePrintOptions::kWaypointETA)) {
264 m_table << toUsrDateTime(point->GetETA(), tz_selection, point->m_lon)
265 .FormatISOCombined(' ');
266 }
267 if (dlg.IsEnabled(RoutePrintOptions::kWaypointETD)) {
268 if (point->GetManualETD().IsValid()) {
269 m_table << toUsrDateTime(point->GetManualETD(), tz_selection,
270 point->m_lon)
271 .FormatISOCombined(' ');
272 } else {
273 m_table << "---";
274 }
275 }
276 if (dlg.IsEnabled(RoutePrintOptions::kWaypointTideEvent)) {
277 std::wostringstream point_tide;
278 if (point->m_TideStation.Len() > 0 && point->GetETA().IsValid()) {
279 int station_id = ptcmgr->GetStationIDXbyName(
280 point->m_TideStation, point->m_lat, point->m_lon);
281 if (station_id > 0) {
282 point_tide << ptcmgr->GetTidalEventStr(station_id, point->GetETA(),
283 point->m_lat, point->m_lon,
284 tz_selection);
285 point_tide << "\n@" << point->m_TideStation;
286 m_table << point_tide.str();
287 } else {
288 m_table << "---";
289 }
290 } else {
291 m_table << "---";
292 }
293 }
294 if (dlg.IsEnabled(RoutePrintOptions::kWaypointDescription)) {
295 m_table << point->GetDescription();
296 }
297 m_table << "\n";
298 }
299}
300
301void RoutePrintout::OnPreparePrinting() {
302 wxDC* dc = GetDC();
303 wxFont routePrintFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
304 wxFONTWEIGHT_NORMAL);
305 dc->SetFont(routePrintFont);
306
307 // Get the size of the DC in pixels
308 int w, h;
309 dc->GetSize(&w, &h);
310
311 // We don't know before hand what size the Print DC will be, in pixels.
312 // Varies by host. So, if the dc size is greater than 1000 pixels, we scale
313 // accordinly.
314 int max_x = wxMin(w, 1000);
315 int max_y = wxMin(h, 1000);
316
317 // Calculate a suitable scaling factor
318 double scale_x = (double)(w / max_x);
319 double scale_y = (double)(h / max_y);
320
321 // Use x or y scaling factor, whichever fits on the DC
322 double actual_scale = wxMin(scale_x, scale_y);
323
324 // Set the scale and origin
325 dc->SetUserScale(actual_scale, actual_scale);
326 dc->SetDeviceOrigin((long)m_margin_x, (long)m_margin_y);
327
328 m_table.AdjustCells(dc, m_margin_x, m_margin_y);
329 m_pages = m_table.GetNumberPages();
330}
331
332void RoutePrintout::DrawPage(wxDC* dc, int page) {
333 wxFont title_font(16, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
334 wxFONTWEIGHT_BOLD);
335 wxFont subtitle_font(12, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
336 wxFONTWEIGHT_NORMAL);
337 wxFont header_font(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
338 wxFONTWEIGHT_BOLD);
339 wxFont normal_font(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
340 wxFONTWEIGHT_NORMAL);
341
342 wxBrush brush(wxColour(255, 255, 255), wxBRUSHSTYLE_TRANSPARENT);
343 dc->SetBrush(brush);
344
345 int current_x = m_margin_x;
346 int current_y = m_margin_y;
347
348 std::wostringstream title;
349 std::wostringstream subtitle;
350 std::wostringstream distance;
351
352 distance << std::fixed << std::setprecision(1)
353 << toUsrDistance(m_route->m_route_length)
354 << getUsrDistanceUnit().ToStdString();
355
356 if (m_route->m_RouteNameString.Trim().Len() > 0) {
357 title << m_route->m_RouteNameString.ToStdString();
358 title << " (" << distance.str() << ")";
359 } else {
360 title << _("Total distance ") << distance.str();
361 }
362
363 if (m_route->m_RouteStartString.Trim().Len() > 0) {
364 subtitle << _("From") << " " << m_route->m_RouteStartString;
365 if (m_route->m_RouteEndString.Trim().Len() > 0) {
366 subtitle << " " << _("To") << " " << m_route->m_RouteEndString;
367 }
368 } else if (m_route->m_RouteEndString.Trim().Len() > 0) {
369 subtitle << _("Destination") << " " << m_route->m_RouteEndString;
370 }
371
372 int title_width, title_height;
373 dc->SetFont(title_font);
374 dc->GetTextExtent(title.str(), &title_width, &title_height);
375 dc->DrawText(title.str(), current_x, current_y);
376 current_y += title_height + m_text_offset_y;
377
378 if (subtitle.str().length() > 0) {
379 int subtitle_width, subtitle_height;
380 dc->SetFont(subtitle_font);
381 dc->GetTextExtent(subtitle.str(), &subtitle_width, &subtitle_height);
382 dc->DrawText(subtitle.str(), current_x, current_y);
383 current_y += subtitle_height + m_text_offset_y;
384 }
385
386 dc->SetFont(normal_font);
387
388 // Route description on page 1.
389 if (page == 1 && m_route->m_RouteDescription.Trim().Len() > 0) {
390 int page_size_x, page_size_y;
391 dc->GetSize(&page_size_x, &page_size_y);
392
393 PrintCell cell_desc;
394 cell_desc.Init(m_route->m_RouteDescription, dc, page_size_x, m_margin_x);
395 dc->DrawText(cell_desc.GetText(), current_x, current_y);
396 current_y += cell_desc.GetHeight() + m_text_offset_y;
397 }
398
399 vector<PrintCell>& header_content = m_table.GetHeader();
400 for (size_t j = 0; j < header_content.size(); j++) {
401 PrintCell& cell = header_content[j];
402 dc->DrawRectangle(current_x, current_y, cell.GetWidth() + m_text_offset_x,
403 cell.GetHeight() + m_text_offset_y);
404 dc->DrawText(cell.GetText(), current_x + m_text_offset_x,
405 current_y + m_text_offset_y);
406 current_x += cell.GetWidth() + m_text_offset_x;
407 }
408
409 wxFont routePrintFont_normal(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
410 wxFONTWEIGHT_NORMAL);
411 dc->SetFont(routePrintFont_normal);
412
413 vector<vector<PrintCell>>& cells = m_table.GetContent();
414 current_y += m_table.GetHeaderHeight() + m_text_offset_y;
415 int current_height = 0;
416 for (size_t i = 0; i < cells.size(); i++) {
417 vector<PrintCell>& content_row = cells[i];
418 current_x = m_margin_x;
419 for (size_t j = 0; j < content_row.size(); j++) {
420 PrintCell& cell = content_row[j];
421 if (cell.GetPage() == page) {
422 wxRect r(current_x, current_y, cell.GetWidth() + m_text_offset_x,
423 cell.GetHeight() + m_text_offset_y);
424 dc->DrawRectangle(r);
425 r.Offset(m_text_offset_x, m_text_offset_y);
426 dc->DrawLabel(cell.GetText(), r);
427 current_x += cell.GetWidth() + m_text_offset_x;
428 current_height = cell.GetHeight() + m_text_offset_y;
429 }
430 }
431 current_y += current_height;
432 }
433}
This class takes multilined string and modifies it to fit into given width for given device.
Definition printtable.h:106
Represents a waypoint or mark within the navigation system.
Definition route_point.h:71
wxDateTime GetManualETD()
Retrieves the manually set Estimated Time of Departure for this waypoint, in UTC.
double GetPlannedSpeed()
Return the planned speed associated with this waypoint.
wxString m_TideStation
Associated tide station identifier.
wxDateTime GetETA()
Retrieves the Estimated Time of Arrival for this waypoint, in UTC.
Input dialog with route print selection.
RoutePrintout(Route *route, const RoutePrintDlg &dlg, const int tz_selection)
Create route prinout.
void DrawPage(wxDC *dc, int page) override
Called by the print framework to draw the page.
Represents a navigational route in the navigation system.
Definition route.h:99
double m_PlannedSpeed
Default planned speed for the route in knots.
Definition route.h:321
wxString m_RouteStartString
Name or description of the route's starting point.
Definition route.h:252
wxString m_RouteDescription
Additional descriptive information about the route.
Definition route.h:262
double m_route_length
Total length of the route in nautical miles, calculated using rhumb line (Mercator) distances.
Definition route.h:237
wxString m_RouteEndString
Name or description of the route's ending point.
Definition route.h:257
wxString m_RouteNameString
User-assigned name for the route.
Definition route.h:247
On/Off switch button.
bool GetValue()
Return on/off state as visible in UI.
Platform independent GL includes.
wxDateTime toUsrDateTime(const wxDateTime ts, const int format, const double lon)
Converts a timestamp from UTC to the user's preferred time format.
Definition navutil.cpp:2922
Utility functions.
double toUsrDistance(double nm_distance, int unit)
Convert a distance from nautical miles (NMi) to user display units.
Navigation Utility Functions without GUI dependencies.
Generic, styled prit dialog.
OpenCPN Route table printout.
Route abstraction.
Route print dialog.
TCMgr * ptcmgr
Global instance.
Definition tcmgr.cpp:42
Tide and Current Manager @TODO Add original author copyright.