source: git/src/printing.cc

Last change on this file was 696db01, checked in by Olly Betts <olly@…>, 4 months ago

Stop passing string datestamp to export machinery

We now only actually use the time_t datestamp value.

  • Property mode set to 100644
File size: 68.0 KB
Line 
1/* printing.cc */
2/* Aven printing code */
3/* Copyright (C) 1993-2003,2004,2005,2006,2010,2011,2012,2013,2014,2015,2016,2017,2018 Olly Betts
4 * Copyright (C) 2001,2004 Philip Underwood
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19 */
20
21#include <config.h>
22
23#include <wx/confbase.h>
24#include <wx/filename.h>
25#include <wx/print.h>
26#include <wx/printdlg.h>
27#include <wx/spinctrl.h>
28#include <wx/radiobox.h>
29#include <wx/statbox.h>
30#include <wx/valgen.h>
31
32#include <algorithm>
33#include <vector>
34
35#include <stdio.h>
36#include <stdlib.h>
37#include <math.h>
38#include <string.h>
39#include <ctype.h>
40#include <float.h>
41#include <limits.h>
42
43#include "export.h"
44#include "filelist.h"
45#include "filename.h"
46#include "message.h"
47#include "useful.h"
48
49#include "aven.h"
50#include "avenprcore.h"
51#include "mainfrm.h"
52#include "printing.h"
53
54using namespace std;
55
56// How many decimal points to show on angles:
57#define ANGLE_DP 1
58
59#if ANGLE_DP == 0
60# define ANGLE_FMT wxT("%03.f")
61# define ANGLE2_FMT wxT("%.f")
62#elif ANGLE_DP == 1
63# define ANGLE_FMT wxT("%05.1f")
64# define ANGLE2_FMT wxT("%.1f")
65#elif ANGLE_DP == 2
66# define ANGLE_FMT wxT("%06.2f")
67# define ANGLE2_FMT wxT("%.2f")
68#else
69# error Need to add ANGLE_FMT and ANGLE2_FMT for the currently set ANGLE_DP
70#endif
71
72static wxString
73format_angle(const wxChar * fmt, double angle)
74{
75    wxString s;
76    s.Printf(fmt, angle);
77    size_t dot = s.find('.');
78    size_t i = s.size();
79    while (i > dot) {
80        --i;
81        if (s[i] != '0') {
82            if (i != dot) ++i;
83            s.resize(i);
84            break;
85        }
86    }
87    s += wmsg(/*°*/344);
88    return s;
89}
90
91enum {
92        svx_EXPORT = 1200,
93        svx_FORMAT,
94        svx_SCALE,
95        svx_BEARING,
96        svx_TILT,
97        svx_LEGS,
98        svx_STATIONS,
99        svx_NAMES,
100        svx_XSECT,
101        svx_WALLS,
102        svx_PASSAGES,
103        svx_BORDERS,
104        svx_BLANKS,
105        svx_LEGEND,
106        svx_SURFACE,
107        svx_SPLAYS,
108        svx_PLAN,
109        svx_ELEV,
110        svx_ENTS,
111        svx_FIXES,
112        svx_EXPORTS,
113        svx_GRID,
114        svx_TEXT_HEIGHT,
115        svx_MARKER_SIZE,
116        svx_CENTRED,
117        svx_FULLCOORDS,
118        svx_CLAMP_TO_GROUND
119};
120
121class BitValidator : public wxValidator {
122    // Disallow assignment.
123    BitValidator & operator=(const BitValidator&);
124
125  protected:
126    int * val;
127
128    int mask;
129
130  public:
131    BitValidator(int * val_, int mask_)
132        : val(val_), mask(mask_) { }
133
134    BitValidator(const BitValidator &o) : wxValidator() {
135        Copy(o);
136    }
137
138    ~BitValidator() { }
139
140    wxObject *Clone() const { return new BitValidator(val, mask); }
141
142    bool Copy(const BitValidator& o) {
143        wxValidator::Copy(o);
144        val = o.val;
145        mask = o.mask;
146        return true;
147    }
148
149    bool Validate(wxWindow *) { return true; }
150
151    bool TransferToWindow() {
152        if (!m_validatorWindow->IsKindOf(CLASSINFO(wxCheckBox)))
153            return false;
154        ((wxCheckBox*)m_validatorWindow)->SetValue(*val & mask);
155        return true;
156    }
157
158    bool TransferFromWindow() {
159        if (!m_validatorWindow->IsKindOf(CLASSINFO(wxCheckBox)))
160            return false;
161        if (((wxCheckBox*)m_validatorWindow)->IsChecked())
162            *val |= mask;
163        else
164            *val &= ~mask;
165        return true;
166    }
167};
168
169class svxPrintout : public wxPrintout {
170    MainFrm *mainfrm;
171    layout *m_layout;
172    wxPageSetupDialogData* m_data;
173    wxDC* pdc;
174    wxFont *font_labels = nullptr, *font_default = nullptr;
175    // Currently unused, but "skip blank pages" would use it.
176    bool scan_for_blank_pages = false;
177
178    wxPen *pen_frame, *pen_cross, *pen_leg, *pen_surface_leg, *pen_splay;
179    wxColour colour_text, colour_labels;
180
181    long x_t, y_t;
182    double font_scaling_x, font_scaling_y;
183
184    struct {
185        long x_min, y_min, x_max, y_max;
186    } clip;
187
188    bool fBlankPage;
189
190    int check_intersection(long x_p, long y_p);
191    void draw_info_box();
192    void draw_scale_bar(double x, double y, double MaxLength);
193    int next_page(int *pstate, char **q, int pageLim);
194    void drawticks(int tsize, int x, int y);
195
196    void MOVEMM(double X, double Y) {
197        MoveTo((long)(X * m_layout->scX), (long)(Y * m_layout->scY));
198    }
199    void DRAWMM(double X, double Y) {
200        DrawTo((long)(X * m_layout->scX), (long)(Y * m_layout->scY));
201    }
202    void MoveTo(long x, long y);
203    void DrawTo(long x, long y);
204    void DrawCross(long x, long y);
205    void SetFont(wxFont * font) {
206        pdc->SetFont(*font);
207    }
208    void WriteString(const wxString & s);
209    void DrawEllipse(long x, long y, long r, long R);
210    void SolidRectangle(long x, long y, long w, long h);
211    void NewPage(int pg, int pagesX, int pagesY);
212    void PlotLR(const vector<XSect> & centreline);
213    void PlotUD(const vector<XSect> & centreline);
214  public:
215    svxPrintout(MainFrm *mainfrm, layout *l, wxPageSetupDialogData *data, const wxString & title);
216    bool OnPrintPage(int pageNum);
217    void GetPageInfo(int *minPage, int *maxPage,
218                     int *pageFrom, int *pageTo);
219    bool HasPage(int pageNum);
220    void OnBeginPrinting();
221    void OnEndPrinting();
222};
223
224BEGIN_EVENT_TABLE(svxPrintDlg, wxDialog)
225    EVT_CHOICE(svx_FORMAT, svxPrintDlg::OnChange)
226    EVT_TEXT(svx_SCALE, svxPrintDlg::OnChangeScale)
227    EVT_COMBOBOX(svx_SCALE, svxPrintDlg::OnChangeScale)
228    EVT_SPINCTRLDOUBLE(svx_BEARING, svxPrintDlg::OnChangeSpin)
229    EVT_SPINCTRLDOUBLE(svx_TILT, svxPrintDlg::OnChangeSpin)
230    EVT_BUTTON(wxID_PRINT, svxPrintDlg::OnPrint)
231    EVT_BUTTON(svx_EXPORT, svxPrintDlg::OnExport)
232    EVT_BUTTON(wxID_CANCEL, svxPrintDlg::OnCancel)
233#ifdef AVEN_PRINT_PREVIEW
234    EVT_BUTTON(wxID_PREVIEW, svxPrintDlg::OnPreview)
235#endif
236    EVT_BUTTON(svx_PLAN, svxPrintDlg::OnPlan)
237    EVT_BUTTON(svx_ELEV, svxPrintDlg::OnElevation)
238    EVT_UPDATE_UI(svx_PLAN, svxPrintDlg::OnPlanUpdate)
239    EVT_UPDATE_UI(svx_ELEV, svxPrintDlg::OnElevationUpdate)
240    EVT_CHECKBOX(svx_LEGS, svxPrintDlg::OnChange)
241    EVT_CHECKBOX(svx_STATIONS, svxPrintDlg::OnChange)
242    EVT_CHECKBOX(svx_NAMES, svxPrintDlg::OnChange)
243    EVT_CHECKBOX(svx_SURFACE, svxPrintDlg::OnChange)
244    EVT_CHECKBOX(svx_SPLAYS, svxPrintDlg::OnChange)
245    EVT_CHECKBOX(svx_ENTS, svxPrintDlg::OnChange)
246    EVT_CHECKBOX(svx_FIXES, svxPrintDlg::OnChange)
247    EVT_CHECKBOX(svx_EXPORTS, svxPrintDlg::OnChange)
248END_EVENT_TABLE()
249
250static wxString scales[] = {
251    wxT(""),
252    wxT("25"),
253    wxT("50"),
254    wxT("100"),
255    wxT("250"),
256    wxT("500"),
257    wxT("1000"),
258    wxT("2500"),
259    wxT("5000"),
260    wxT("10000"),
261    wxT("25000"),
262    wxT("50000"),
263    wxT("100000"),
264    wxT("240 (1\":20')"),
265    wxT("300 (1\":25')"),
266    // This entry will be "304.8 (1mm:1ft)" but we need to use the
267    // locale-specific decimal point so this gets filled in on first
268    // use, after the locale is initialised.
269#define SCALES_INDEX_MM_TO_FEET 15
270    wxT(""),
271    wxT("480 (1\":40')"),
272    wxT("600 (1\":50')"),
273    wxT("...")
274};
275
276// The order of these arrays must match export_format in export.h.
277
278static wxString formats[] = {
279    wxT("Survex 3d"),
280    wxT("CSV"),
281    wxT("DXF"),
282    wxT("EPS"),
283    wxT("GPX"),
284    wxT("HPGL"),
285    wxT("JSON"),
286    wxT("KML"),
287    wxT("Plot"),
288    wxT("Survex pos"),
289    wxT("SVG"),
290    // These next two get filled in lazily since they are translated which
291    // means we need to wait until after the messages are loaded.
292    wxT(""), // "Shapefiles (lines)"
293    wxT("") // "Shapefiles (points)"
294};
295
296static_assert(sizeof(formats) == FMT_MAX_PLUS_ONE_ * sizeof(formats[0]),
297              "formats[] matches enum export_format");
298
299// We discriminate as "One page" isn't valid for exporting.
300static wxString default_scale_print;
301static wxString default_scale_export;
302
303svxPrintDlg::svxPrintDlg(MainFrm* mainfrm_, const wxString & filename,
304                         const wxString & title,
305                         const wxString & datestamp,
306                         double angle, double tilt_angle,
307                         bool labels, bool crosses, bool legs, bool surf,
308                         bool splays, bool tubes, bool ents, bool fixes,
309                         bool exports, bool printing, bool close_after_)
310        : wxDialog(mainfrm_, wxID_ANY,
311                   wxString(printing ? /* TRANSLATORS: Title of the print
312                                        * dialog */
313                                       wmsg(/*Print*/399) :
314                                       /* TRANSLATORS: Title of the export
315                                        * dialog */
316                                       wmsg(/*Export*/383))),
317          m_layout(printing ? wxGetApp().GetPageSetupDialogData() : NULL),
318          m_File(filename), mainfrm(mainfrm_), close_after(close_after_)
319{
320    int show_mask = 0;
321    if (labels)
322        show_mask |= LABELS;
323    if (crosses)
324        show_mask |= STNS;
325    if (legs)
326        show_mask |= LEGS;
327    if (surf)
328        show_mask |= SURF;
329    if (splays)
330        show_mask |= SPLAYS;
331    if (tubes)
332        show_mask |= XSECT|WALLS|PASG;
333    if (ents)
334        show_mask |= ENTS;
335    if (fixes)
336        show_mask |= FIXES;
337    if (exports)
338        show_mask |= EXPORTS;
339    m_layout.show_mask = show_mask;
340    m_layout.datestamp = datestamp;
341    m_layout.rot = angle;
342    m_layout.title = title;
343    if (mainfrm->IsExtendedElevation()) {
344        m_layout.view = layout::EXTELEV;
345        if (m_layout.rot != 0.0 && m_layout.rot != 180.0) m_layout.rot = 0;
346        m_layout.tilt = 0;
347    } else {
348        m_layout.tilt = tilt_angle;
349        if (m_layout.tilt == -90.0) {
350            m_layout.view = layout::PLAN;
351        } else if (m_layout.tilt == 0.0) {
352            m_layout.view = layout::ELEV;
353        } else {
354            m_layout.view = layout::TILT;
355        }
356    }
357
358    /* setup our print dialog*/
359    wxBoxSizer* v1 = new wxBoxSizer(wxVERTICAL);
360    wxBoxSizer* h1 = new wxBoxSizer(wxHORIZONTAL); // holds controls
361    /* TRANSLATORS: Used as a label for the surrounding box for the "Bearing"
362     * and "Tilt angle" fields, and the "Plan view" and "Elevation" buttons in
363     * the "what to print/export" dialog. */
364    m_viewbox = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, wmsg(/*View*/283)), wxVERTICAL);
365    /* TRANSLATORS: Used as a label for the surrounding box for the "survey
366     * legs" "stations" "names" etc checkboxes in the "what to print" dialog.
367     * "Elements" isn’t a good name for this but nothing better has yet come to
368     * mind! */
369    wxBoxSizer* v2 = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, wmsg(/*Elements*/256)), wxVERTICAL);
370    wxBoxSizer* h2 = new wxBoxSizer(wxHORIZONTAL); // holds buttons
371
372    if (!printing) {
373        wxStaticText* label;
374        label = new wxStaticText(this, wxID_ANY, wxString(wmsg(/*Export format*/410)));
375        if (formats[FMT_SHP_LINES].empty()) {
376            formats[FMT_SHP_LINES] = wmsg(/*Shapefiles (lines)*/523);
377            formats[FMT_SHP_POINTS] = wmsg(/*Shapefiles (points)*/524);
378        }
379        const size_t n_formats = sizeof(formats) / sizeof(formats[0]);
380        m_format = new wxChoice(this, svx_FORMAT,
381                                wxDefaultPosition, wxDefaultSize,
382                                n_formats, formats);
383        unsigned current_format = 0;
384        wxConfigBase * cfg = wxConfigBase::Get();
385        wxString s;
386        if (cfg->Read(wxT("export_format"), &s, wxString())) {
387            for (unsigned i = 0; i != n_formats; ++i) {
388                if (s == formats[i]) {
389                    current_format = i;
390                    break;
391                }
392            }
393        }
394        m_format->SetSelection(current_format);
395        wxBoxSizer* formatbox = new wxBoxSizer(wxHORIZONTAL);
396        formatbox->Add(label, 0, wxALIGN_CENTRE_VERTICAL|wxALL, 5);
397        formatbox->Add(m_format, 0, wxALIGN_CENTRE_VERTICAL|wxALL, 5);
398
399        v1->Add(formatbox, 0, wxALIGN_LEFT|wxALL, 0);
400    }
401
402    if (scales[SCALES_INDEX_MM_TO_FEET][0] == '\0') {
403        scales[SCALES_INDEX_MM_TO_FEET] = wxString::FromDouble(304.8) + wxT(" (1mm:1ft)");
404    }
405    wxStaticText* label;
406    label = new wxStaticText(this, wxID_ANY, wxString(wmsg(/*Scale*/154)) + wxT(" 1:"));
407    if (printing && scales[0].empty()) {
408        /* TRANSLATORS: used in the scale drop down selector in the print
409         * dialog the implicit meaning is "choose a suitable scale to fit
410         * the plot on a single page", but we need something shorter */
411        scales[0].assign(wmsg(/*One page*/258));
412    }
413    wxString default_scale;
414    if (printing) {
415        default_scale = default_scale_print;
416        if (default_scale.empty()) default_scale = scales[0];
417    } else {
418        default_scale = default_scale_export;
419        if (default_scale.empty()) default_scale = wxT("1000");
420    }
421    const wxString* scale_list = scales;
422    size_t n_scales = sizeof(scales) / sizeof(scales[0]);
423    if (!printing) {
424        ++scale_list;
425        --n_scales;
426    }
427    m_scale = new wxComboBox(this, svx_SCALE, default_scale, wxDefaultPosition,
428                             wxDefaultSize, n_scales, scale_list);
429    m_scalebox = new wxBoxSizer(wxHORIZONTAL);
430    m_scalebox->Add(label, 0, wxALIGN_CENTRE_VERTICAL|wxALL, 5);
431    m_scalebox->Add(m_scale, 0, wxALIGN_CENTRE_VERTICAL|wxALL, 5);
432
433    m_viewbox->Add(m_scalebox, 0, wxALIGN_LEFT|wxALL, 0);
434
435    if (printing) {
436        // Make the dummy string wider than any sane value and use that to
437        // fix the width of the control so the sizers allow space for bigger
438        // page layouts.
439        m_printSize = new wxStaticText(this, wxID_ANY, wxString::Format(wmsg(/*%d pages (%dx%d)*/257), 9604, 98, 98));
440        m_viewbox->Add(m_printSize, 0, wxALIGN_LEFT|wxALL, 5);
441    }
442
443    if (m_layout.view != layout::EXTELEV) {
444        wxFlexGridSizer* anglebox = new wxFlexGridSizer(2);
445        wxStaticText * brg_label, * tilt_label;
446        brg_label = new wxStaticText(this, wxID_ANY, wmsg(/*Bearing*/259));
447        anglebox->Add(brg_label, 0, wxALIGN_CENTRE_VERTICAL|wxALIGN_LEFT|wxALL, 5);
448        // wSP_WRAP means that you can scroll past 360 to 0, and vice versa.
449        m_bearing = new wxSpinCtrlDouble(this, svx_BEARING, wxEmptyString,
450                wxDefaultPosition, wxDefaultSize,
451                wxSP_ARROW_KEYS|wxALIGN_RIGHT|wxSP_WRAP);
452        m_bearing->SetRange(0.0, 360.0);
453        m_bearing->SetDigits(ANGLE_DP);
454        anglebox->Add(m_bearing, 0, wxALIGN_CENTRE|wxALL, 5);
455        /* TRANSLATORS: Used in the print dialog: */
456        tilt_label = new wxStaticText(this, wxID_ANY, wmsg(/*Tilt angle*/263));
457        anglebox->Add(tilt_label, 0, wxALIGN_CENTRE_VERTICAL|wxALIGN_LEFT|wxALL, 5);
458        m_tilt = new wxSpinCtrlDouble(this, svx_TILT, wxEmptyString,
459                wxDefaultPosition, wxDefaultSize,
460                wxSP_ARROW_KEYS|wxALIGN_RIGHT);
461        m_tilt->SetRange(-90.0, 90.0);
462        m_tilt->SetDigits(ANGLE_DP);
463        anglebox->Add(m_tilt, 0, wxALIGN_CENTRE|wxALL, 5);
464
465        m_viewbox->Add(anglebox, 0, wxALIGN_LEFT|wxALL, 0);
466
467        wxBoxSizer * planelevsizer = new wxBoxSizer(wxHORIZONTAL);
468        planelevsizer->Add(new wxButton(this, svx_PLAN, wmsg(/*P&lan view*/117)),
469                           0, wxALIGN_CENTRE_VERTICAL|wxALL, 5);
470        planelevsizer->Add(new wxButton(this, svx_ELEV, wmsg(/*&Elevation*/285)),
471                           0, wxALIGN_CENTRE_VERTICAL|wxALL, 5);
472
473        m_viewbox->Add(planelevsizer, 0, wxALIGN_LEFT|wxALL, 5);
474    }
475
476    /* TRANSLATORS: Here a "survey leg" is a set of measurements between two
477     * "survey stations". */
478    v2->Add(new wxCheckBox(this, svx_LEGS, wmsg(/*Underground Survey Legs*/262),
479                           wxDefaultPosition, wxDefaultSize, 0,
480                           BitValidator(&m_layout.show_mask, LEGS)),
481            0, wxALIGN_LEFT|wxALL, 2);
482    /* TRANSLATORS: Here a "survey leg" is a set of measurements between two
483     * "survey stations". */
484    v2->Add(new wxCheckBox(this, svx_SURFACE, wmsg(/*Sur&face Survey Legs*/403),
485                           wxDefaultPosition, wxDefaultSize, 0,
486                           BitValidator(&m_layout.show_mask, SURF)),
487            0, wxALIGN_LEFT|wxALL, 2);
488    v2->Add(new wxCheckBox(this, svx_SPLAYS, wmsg(/*Spla&y Legs*/406),
489                           wxDefaultPosition, wxDefaultSize, 0,
490                           BitValidator(&m_layout.show_mask, SPLAYS)),
491            0, wxALIGN_LEFT|wxALL, 2);
492    v2->Add(new wxCheckBox(this, svx_STATIONS, wmsg(/*Crosses*/261),
493                           wxDefaultPosition, wxDefaultSize, 0,
494                           BitValidator(&m_layout.show_mask, STNS)),
495            0, wxALIGN_LEFT|wxALL, 2);
496    v2->Add(new wxCheckBox(this, svx_NAMES, wmsg(/*Station Names*/260),
497                           wxDefaultPosition, wxDefaultSize, 0,
498                           BitValidator(&m_layout.show_mask, LABELS)),
499            0, wxALIGN_LEFT|wxALL, 2);
500    v2->Add(new wxCheckBox(this, svx_ENTS, wmsg(/*Entrances*/418),
501                           wxDefaultPosition, wxDefaultSize, 0,
502                           BitValidator(&m_layout.show_mask, ENTS)),
503            0, wxALIGN_LEFT|wxALL, 2);
504    v2->Add(new wxCheckBox(this, svx_FIXES, wmsg(/*Fixed Points*/419),
505                           wxDefaultPosition, wxDefaultSize, 0,
506                           BitValidator(&m_layout.show_mask, FIXES)),
507            0, wxALIGN_LEFT|wxALL, 2);
508    v2->Add(new wxCheckBox(this, svx_EXPORTS, wmsg(/*Exported Stations*/420),
509                           wxDefaultPosition, wxDefaultSize, 0,
510                           BitValidator(&m_layout.show_mask, EXPORTS)),
511            0, wxALIGN_LEFT|wxALL, 2);
512    v2->Add(new wxCheckBox(this, svx_XSECT, wmsg(/*Cross-sections*/393),
513                           wxDefaultPosition, wxDefaultSize, 0,
514                           BitValidator(&m_layout.show_mask, XSECT)),
515            0, wxALIGN_LEFT|wxALL, 2);
516    if (!printing) {
517        v2->Add(new wxCheckBox(this, svx_WALLS, wmsg(/*Walls*/394),
518                               wxDefaultPosition, wxDefaultSize, 0,
519                               BitValidator(&m_layout.show_mask, WALLS)),
520                0, wxALIGN_LEFT|wxALL, 2);
521        // TRANSLATORS: Label for checkbox which controls whether there's a
522        // layer in the exported file (for formats such as DXF and SVG)
523        // containing polygons for the inside of cave passages).
524        v2->Add(new wxCheckBox(this, svx_PASSAGES, wmsg(/*Passages*/395),
525                               wxDefaultPosition, wxDefaultSize, 0,
526                               BitValidator(&m_layout.show_mask, PASG)),
527                0, wxALIGN_LEFT|wxALL, 2);
528        v2->Add(new wxCheckBox(this, svx_CENTRED, wmsg(/*Origin in centre*/421),
529                               wxDefaultPosition, wxDefaultSize, 0,
530                               BitValidator(&m_layout.show_mask, CENTRED)),
531                0, wxALIGN_LEFT|wxALL, 2);
532        v2->Add(new wxCheckBox(this, svx_FULLCOORDS, wmsg(/*Full coordinates*/422),
533                               wxDefaultPosition, wxDefaultSize, 0,
534                               BitValidator(&m_layout.show_mask, FULL_COORDS)),
535                0, wxALIGN_LEFT|wxALL, 2);
536        v2->Add(new wxCheckBox(this, svx_CLAMP_TO_GROUND, wmsg(/*Clamp to ground*/477),
537                               wxDefaultPosition, wxDefaultSize, 0,
538                               BitValidator(&m_layout.show_mask, CLAMP_TO_GROUND)),
539                0, wxALIGN_LEFT|wxALL, 2);
540    }
541    if (printing) {
542        /* TRANSLATORS: used in the print dialog - controls drawing lines
543         * around each page */
544        v2->Add(new wxCheckBox(this, svx_BORDERS, wmsg(/*Page Borders*/264),
545                               wxDefaultPosition, wxDefaultSize, 0,
546                               wxGenericValidator(&m_layout.Border)),
547                0, wxALIGN_LEFT|wxALL, 2);
548        /* TRANSLATORS: will be used in the print dialog - check this to print
549         * blank pages (otherwise they’ll be skipped to save paper) */
550//      m_blanks = new wxCheckBox(this, svx_BLANKS, wmsg(/*Blank Pages*/266));
551//      v2->Add(m_blanks, 0, wxALIGN_LEFT|wxALL, 2);
552        /* TRANSLATORS: As in the legend on a map.  Used in the print dialog -
553         * controls drawing the box at the lower left with survey name, view
554         * angles, etc */
555        v2->Add(new wxCheckBox(this, svx_LEGEND, wmsg(/*Legend*/265),
556                               wxDefaultPosition, wxDefaultSize, 0,
557                               wxGenericValidator(&m_layout.Legend)),
558                0, wxALIGN_LEFT|wxALL, 2);
559    }
560
561    h1->Add(v2, 0, wxALIGN_LEFT|wxALL, 5);
562    h1->Add(m_viewbox, 0, wxALIGN_LEFT|wxLEFT, 5);
563
564    v1->Add(h1, 0, wxALIGN_LEFT|wxALL, 5);
565
566    // When we enable/disable checkboxes in the export dialog, ideally we'd
567    // like the dialog to resize, but not sure how to achieve that, so we
568    // add a stretchable spacer here so at least the buttons stay in the
569    // lower right corner.
570    v1->AddStretchSpacer();
571
572    wxButton * but;
573    but = new wxButton(this, wxID_CANCEL);
574    h2->Add(but, 0, wxALL, 5);
575    if (printing) {
576#ifdef AVEN_PRINT_PREVIEW
577        but = new wxButton(this, wxID_PREVIEW);
578        h2->Add(but, 0, wxALL, 5);
579        but = new wxButton(this, wxID_PRINT);
580#else
581        but = new wxButton(this, wxID_PRINT, wmsg(/*&Print...*/400));
582#endif
583    } else {
584        /* TRANSLATORS: The text on the action button in the "Export" settings
585         * dialog */
586        but = new wxButton(this, svx_EXPORT, wmsg(/*&Export...*/230));
587    }
588    but->SetDefault();
589    h2->Add(but, 0, wxALL, 5);
590    v1->Add(h2, 0, wxALIGN_RIGHT|wxALL, 5);
591
592    SetAutoLayout(true);
593    SetSizer(v1);
594    v1->SetSizeHints(this);
595
596    LayoutToUI();
597    SomethingChanged(0);
598}
599
600void
601svxPrintDlg::OnPrint(wxCommandEvent&) {
602    SomethingChanged(0);
603    TransferDataFromWindow();
604    wxPageSetupDialogData * psdd = wxGetApp().GetPageSetupDialogData();
605    wxPrintDialogData pd(psdd->GetPrintData());
606    wxPrinter pr(&pd);
607    svxPrintout po(mainfrm, &m_layout, psdd, m_File);
608    if (m_layout.SkipBlank) {
609        // FIXME: wx's printing requires a contiguous range of valid page
610        // numbers.  To achieve that, we need to run a scan for blank pages
611        // here, so that GetPageInfo() knows what range to return, and so
612        // that OnPrintPage() can map a page number back to where in the
613        // MxN multi-page layout.
614#if 0
615        po.scan_for_blank_pages = true;
616        for (int page = 1; page <= m_layout->pages; ++page) {
617            po.fBlankPage = true;
618            po.OnPrintPage(page);
619            // FIXME: Do something with po.fBlankPage
620        }
621        po.scan_for_blank_pages = false;
622#endif
623    }
624    if (pr.Print(this, &po, true)) {
625        // Close the print dialog if printing succeeded.
626        Destroy();
627    }
628}
629
630void
631svxPrintDlg::OnExport(wxCommandEvent&) {
632    UIToLayout();
633    TransferDataFromWindow();
634    wxString leaf;
635    wxFileName::SplitPath(m_File, NULL, NULL, &leaf, NULL, wxPATH_NATIVE);
636    unsigned format_idx = ((wxChoice*)FindWindow(svx_FORMAT))->GetSelection();
637    const auto& info = export_format_info[format_idx];
638    leaf += wxString::FromUTF8(info.extension);
639
640    wxString filespec = wmsg(info.msg_filetype);
641    filespec += wxT("|*");
642    filespec += wxString::FromUTF8(info.extension);
643    filespec += wxT("|");
644    filespec += wmsg(/*All files*/208);
645    filespec += wxT("|");
646    filespec += wxFileSelectorDefaultWildcardStr;
647
648    /* TRANSLATORS: Title of file dialog to choose name and type of exported
649     * file. */
650    wxFileDialog dlg(this, wmsg(/*Export as:*/401), wxString(), leaf,
651                     filespec, wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
652    if (dlg.ShowModal() == wxID_OK) {
653        /* FIXME: Set up a way for the user to specify these: */
654        double grid = DEFAULT_GRID_SPACING; // metres
655        double text_height = DEFAULT_TEXT_HEIGHT;
656        double marker_size = DEFAULT_MARKER_SIZE;
657
658        try {
659            const wxString& export_fnm = dlg.GetPath();
660            unsigned mask = info.mask;
661            double rot, tilt;
662            if (mask & ORIENTABLE) {
663                rot = m_layout.rot;
664                tilt = m_layout.tilt;
665            } else {
666                rot = 0.0;
667                tilt = -90.0;
668            }
669            if (!Export(export_fnm, m_layout.title,
670                        *mainfrm, mainfrm->GetTreeFilter(),
671                        rot, tilt, m_layout.get_effective_show_mask(),
672                        export_format(format_idx),
673                        grid, text_height, marker_size, m_layout.Scale)) {
674                wxString m = wxString::Format(wmsg(/*Couldn’t write file “%s”*/402).c_str(),
675                                              export_fnm.c_str());
676                wxGetApp().ReportError(m);
677            }
678        } catch (const wxString & m) {
679            wxGetApp().ReportError(m);
680        }
681    }
682    Destroy();
683}
684
685#ifdef AVEN_PRINT_PREVIEW
686void
687svxPrintDlg::OnPreview(wxCommandEvent&) {
688    SomethingChanged(0);
689    TransferDataFromWindow();
690    wxPageSetupDialogData * psdd = wxGetApp().GetPageSetupDialogData();
691    wxPrintDialogData pd(psdd->GetPrintData());
692    wxPrintPreview* pv;
693    pv = new wxPrintPreview(new svxPrintout(mainfrm, &m_layout, psdd, m_File),
694                            new svxPrintout(mainfrm, &m_layout, psdd, m_File),
695                            &pd);
696    // TRANSLATORS: Title of the print preview dialog
697    wxPreviewFrame *frame = new wxPreviewFrame(pv, mainfrm, wmsg(/*Print Preview*/398));
698    frame->Initialize();
699
700    // Size preview frame so that all of the controlbar and canvas can be seen
701    // if possible.
702    int w, h;
703    // GetBestSize gives us the width needed to show the whole controlbar.
704    frame->GetBestSize(&w, &h);
705    if (h < w) {
706        // On wxGTK at least, GetBestSize() returns much too small a height.
707        h = w * 6 / 5;
708    }
709    // Ensure that we don't make the window bigger than the screen.
710    // Use wxGetClientDisplayRect() so we don't cover the MS Windows
711    // task bar either.
712    wxRect disp = wxGetClientDisplayRect();
713    if (w > disp.GetWidth()) w = disp.GetWidth();
714    if (h > disp.GetHeight()) h = disp.GetHeight();
715    // Centre the window within the "ClientDisplayRect".
716    int x = disp.GetLeft() + (disp.GetWidth() - w) / 2;
717    int y = disp.GetTop() + (disp.GetHeight() - h) / 2;
718    frame->SetSize(x, y, w, h);
719
720    frame->Show();
721}
722#endif
723
724void
725svxPrintDlg::OnPlan(wxCommandEvent&) {
726    m_tilt->SetValue(-90.0);
727    SomethingChanged(svx_TILT);
728}
729
730void
731svxPrintDlg::OnElevation(wxCommandEvent&) {
732    m_tilt->SetValue(0.0);
733    SomethingChanged(svx_TILT);
734}
735
736void
737svxPrintDlg::OnPlanUpdate(wxUpdateUIEvent& e) {
738    e.Enable(m_tilt->GetValue() != -90.0);
739}
740
741void
742svxPrintDlg::OnElevationUpdate(wxUpdateUIEvent& e) {
743    e.Enable(m_tilt->GetValue() != 0.0);
744}
745
746void
747svxPrintDlg::OnChangeSpin(wxSpinDoubleEvent& e) {
748    SomethingChanged(e.GetId());
749}
750
751void
752svxPrintDlg::OnChange(wxCommandEvent& e) {
753    SomethingChanged(e.GetId());
754}
755
756void
757svxPrintDlg::OnChangeScale(wxCommandEvent& e) {
758    // Seems to be needed on macOS.
759    if (!m_scale) return;
760    wxString value = m_scale->GetValue();
761    if (value == "...") {
762        m_scale->SetValue("");
763        m_scale->SetFocus();
764    } else {
765        default_scale_print = value;
766        if (default_scale_print != scales[0]) {
767            // Don't store "One page" for use when exporting.
768            default_scale_export = default_scale_print;
769        }
770    }
771    SomethingChanged(e.GetId());
772}
773
774void
775svxPrintDlg::OnCancel(wxCommandEvent&) {
776    if (close_after)
777        mainfrm->Close();
778    Destroy();
779}
780
781void
782svxPrintDlg::SomethingChanged(int control_id) {
783    if ((control_id == 0 || control_id == svx_FORMAT) && m_format) {
784        // Update the shown/hidden fields for the newly selected export filter.
785        int new_filter_idx = m_format->GetSelection();
786        if (new_filter_idx != wxNOT_FOUND) {
787            unsigned mask = export_format_info[new_filter_idx].mask;
788            static const struct { int id; unsigned mask; } controls[] = {
789                { svx_LEGS, LEGS },
790                { svx_SURFACE, SURF },
791                { svx_SPLAYS, SPLAYS },
792                { svx_STATIONS, STNS },
793                { svx_NAMES, LABELS },
794                { svx_XSECT, XSECT },
795                { svx_WALLS, WALLS },
796                { svx_PASSAGES, PASG },
797                { svx_ENTS, ENTS },
798                { svx_FIXES, FIXES },
799                { svx_EXPORTS, EXPORTS },
800                { svx_CENTRED, CENTRED },
801                { svx_FULLCOORDS, FULL_COORDS },
802                { svx_CLAMP_TO_GROUND, CLAMP_TO_GROUND },
803            };
804            static unsigned n_controls = sizeof(controls) / sizeof(controls[0]);
805            for (unsigned i = 0; i != n_controls; ++i) {
806                wxWindow * control = FindWindow(controls[i].id);
807                if (control) control->Show(mask & controls[i].mask);
808            }
809            m_scalebox->Show(bool(mask & SCALE));
810            m_viewbox->Show(bool(mask & ORIENTABLE));
811            GetSizer()->Layout();
812            // Force the window to resize to match the updated layout.
813            if (control_id) SetSizerAndFit(GetSizer());
814            if (control_id == svx_FORMAT) {
815                wxConfigBase * cfg = wxConfigBase::Get();
816                cfg->Write(wxT("export_format"), formats[new_filter_idx]);
817            }
818        }
819    }
820
821    UIToLayout();
822
823    if (m_printSize || m_scale) {
824        // Update the bounding box.
825        RecalcBounds();
826
827        if (m_scale) {
828            // Remove the comment part (e.g. `(1":20')`).
829            wxString value = m_scale->GetValue();
830            auto comment = value.find('(');
831            if (comment != value.npos) value.resize(comment);
832            // Strip spaces as trailing spaces cause wxWidgets to fail to
833            // parse.
834            value.Replace(" ", "");
835            // Convert `,` to `.` and parse with ToCDouble() so either decimal
836            // separator works regardless of locale settings.
837            value.Replace(",", ".");
838            if (!value.ToCDouble(&(m_layout.Scale)) ||
839                m_layout.Scale == 0.0) {
840                m_layout.pick_scale(1, 1);
841            }
842        }
843    }
844
845    if (m_printSize && m_layout.xMax >= m_layout.xMin) {
846        m_layout.pages_required();
847        m_printSize->SetLabel(wxString::Format(wmsg(/*%d pages (%dx%d)*/257), m_layout.pages, m_layout.pagesX, m_layout.pagesY));
848    }
849}
850
851void
852svxPrintDlg::LayoutToUI()
853{
854//    m_blanks->SetValue(m_layout.SkipBlank);
855    if (m_layout.view != layout::EXTELEV) {
856        m_tilt->SetValue(m_layout.tilt);
857        m_bearing->SetValue(m_layout.rot);
858    }
859
860    if (m_scale && m_layout.Scale != 0) {
861        // Do this last as it causes an OnChange message which calls UIToLayout
862        wxString temp;
863        temp << m_layout.Scale;
864        m_scale->SetValue(temp);
865    }
866}
867
868void
869svxPrintDlg::UIToLayout()
870{
871//    m_layout.SkipBlank = m_blanks->IsChecked();
872
873    if (m_layout.view != layout::EXTELEV && m_tilt) {
874        m_layout.tilt = m_tilt->GetValue();
875        if (m_layout.tilt == -90.0) {
876            m_layout.view = layout::PLAN;
877        } else if (m_layout.tilt == 0.0) {
878            m_layout.view = layout::ELEV;
879        } else {
880            m_layout.view = layout::TILT;
881        }
882
883        bool enable_passage_opts = (m_layout.view != layout::TILT);
884        wxWindow * win;
885        win = FindWindow(svx_XSECT);
886        if (win) win->Enable(enable_passage_opts);
887        win = FindWindow(svx_WALLS);
888        if (win) win->Enable(enable_passage_opts);
889        win = FindWindow(svx_PASSAGES);
890        if (win) win->Enable(enable_passage_opts);
891
892        m_layout.rot = m_bearing->GetValue();
893    }
894}
895
896void
897svxPrintDlg::RecalcBounds()
898{
899    m_layout.yMax = m_layout.xMax = -DBL_MAX;
900    m_layout.yMin = m_layout.xMin = DBL_MAX;
901
902    double SIN = sin(rad(m_layout.rot));
903    double COS = cos(rad(m_layout.rot));
904    double SINT = sin(rad(m_layout.tilt));
905    double COST = cos(rad(m_layout.tilt));
906
907    const SurveyFilter* filter = mainfrm->GetTreeFilter();
908    int show_mask = m_layout.get_effective_show_mask();
909    if (show_mask & LEGS) {
910        for (int f = 0; f != 8; ++f) {
911            if ((show_mask & (f & img_FLAG_SURFACE) ? SURF : LEGS) == 0) {
912                // Not showing traverse because of surface/underground status.
913                continue;
914            }
915            if ((f & img_FLAG_SPLAY) && (show_mask & SPLAYS) == 0) {
916                // Not showing because it's a splay.
917                continue;
918            }
919            list<traverse>::const_iterator trav = mainfrm->traverses_begin(f, filter);
920            list<traverse>::const_iterator tend = mainfrm->traverses_end(f);
921            for ( ; trav != tend; trav = mainfrm->traverses_next(f, filter, trav)) {
922                vector<PointInfo>::const_iterator pos = trav->begin();
923                vector<PointInfo>::const_iterator end = trav->end();
924                for ( ; pos != end; ++pos) {
925                    double x = pos->GetX();
926                    double y = pos->GetY();
927                    double z = pos->GetZ();
928                    double X = x * COS - y * SIN;
929                    if (X > m_layout.xMax) m_layout.xMax = X;
930                    if (X < m_layout.xMin) m_layout.xMin = X;
931                    double Y = z * COST - (x * SIN + y * COS) * SINT;
932                    if (Y > m_layout.yMax) m_layout.yMax = Y;
933                    if (Y < m_layout.yMin) m_layout.yMin = Y;
934                }
935            }
936        }
937    }
938
939    if ((show_mask & XSECT) &&
940        (m_layout.tilt == 0.0 || m_layout.tilt == 90.0 || m_layout.tilt == -90.0)) {
941        list<vector<XSect>>::const_iterator trav = mainfrm->tubes_begin();
942        list<vector<XSect>>::const_iterator tend = mainfrm->tubes_end();
943        for ( ; trav != tend; ++trav) {
944            const XSect* prev_pt_v = NULL;
945            Vector3 last_right(1.0, 0.0, 0.0);
946
947            vector<XSect>::const_iterator i = trav->begin();
948            vector<XSect>::size_type segment = 0;
949            while (i != trav->end()) {
950                // get the coordinates of this vertex
951                const XSect & pt_v = *i++;
952                if (m_layout.tilt == 0.0) {
953                    double u = pt_v.GetU();
954                    double d = pt_v.GetD();
955
956                    if (u >= 0 || d >= 0) {
957                        if (filter && !filter->CheckVisible(pt_v.GetLabel()))
958                            continue;
959
960                        double x = pt_v.GetX();
961                        double y = pt_v.GetY();
962                        double z = pt_v.GetZ();
963                        double X = x * COS - y * SIN;
964                        double Y = z * COST - (x * SIN + y * COS) * SINT;
965
966                        if (X > m_layout.xMax) m_layout.xMax = X;
967                        if (X < m_layout.xMin) m_layout.xMin = X;
968                        double U = Y + max(0.0, pt_v.GetU());
969                        if (U > m_layout.yMax) m_layout.yMax = U;
970                        double D = Y - max(0.0, pt_v.GetD());
971                        if (D < m_layout.yMin) m_layout.yMin = D;
972                    }
973                } else {
974                    // More complex, and this duplicates the algorithm from
975                    // PlotLR() - we should try to share that, maybe via a
976                    // template.
977                    Vector3 right;
978
979                    const Vector3 up_v(0.0, 0.0, 1.0);
980
981                    if (segment == 0) {
982                        assert(i != trav->end());
983                        // first segment
984
985                        // get the coordinates of the next vertex
986                        const XSect & next_pt_v = *i;
987
988                        // calculate vector from this pt to the next one
989                        Vector3 leg_v = next_pt_v - pt_v;
990
991                        // obtain a vector in the LRUD plane
992                        right = leg_v * up_v;
993                        if (right.magnitude() == 0) {
994                            right = last_right;
995                        } else {
996                            last_right = right;
997                        }
998                    } else if (segment + 1 == trav->size()) {
999                        // last segment
1000
1001                        // Calculate vector from the previous pt to this one.
1002                        Vector3 leg_v = pt_v - *prev_pt_v;
1003
1004                        // Obtain a horizontal vector in the LRUD plane.
1005                        right = leg_v * up_v;
1006                        if (right.magnitude() == 0) {
1007                            right = Vector3(last_right.GetX(), last_right.GetY(), 0.0);
1008                        } else {
1009                            last_right = right;
1010                        }
1011                    } else {
1012                        assert(i != trav->end());
1013                        // Intermediate segment.
1014
1015                        // Get the coordinates of the next vertex.
1016                        const XSect & next_pt_v = *i;
1017
1018                        // Calculate vectors from this vertex to the
1019                        // next vertex, and from the previous vertex to
1020                        // this one.
1021                        Vector3 leg1_v = pt_v - *prev_pt_v;
1022                        Vector3 leg2_v = next_pt_v - pt_v;
1023
1024                        // Obtain horizontal vectors perpendicular to
1025                        // both legs, then normalise and average to get
1026                        // a horizontal bisector.
1027                        Vector3 r1 = leg1_v * up_v;
1028                        Vector3 r2 = leg2_v * up_v;
1029                        r1.normalise();
1030                        r2.normalise();
1031                        right = r1 + r2;
1032                        if (right.magnitude() == 0) {
1033                            // This is the "mid-pitch" case...
1034                            right = last_right;
1035                        }
1036                        last_right = right;
1037                    }
1038
1039                    // Scale to unit vectors in the LRUD plane.
1040                    right.normalise();
1041
1042                    double l = pt_v.GetL();
1043                    double r = pt_v.GetR();
1044
1045                    if (l >= 0 || r >= 0) {
1046                        if (!filter || filter->CheckVisible(pt_v.GetLabel())) {
1047                            // Get the x and y coordinates of the survey station
1048                            double pt_X = pt_v.GetX() * COS - pt_v.GetY() * SIN;
1049                            double pt_Y = pt_v.GetX() * SIN + pt_v.GetY() * COS;
1050
1051                            double X, Y;
1052                            if (l >= 0) {
1053                                // Get the x and y coordinates of the end of the left arrow
1054                                Vector3 p = pt_v.GetPoint() - right * l;
1055                                X = p.GetX() * COS - p.GetY() * SIN;
1056                                Y = (p.GetX() * SIN + p.GetY() * COS);
1057                            } else {
1058                                X = pt_X;
1059                                Y = pt_Y;
1060                            }
1061                            if (X > m_layout.xMax) m_layout.xMax = X;
1062                            if (X < m_layout.xMin) m_layout.xMin = X;
1063                            if (Y > m_layout.yMax) m_layout.yMax = Y;
1064                            if (Y < m_layout.yMin) m_layout.yMin = Y;
1065
1066                            if (r >= 0) {
1067                                // Get the x and y coordinates of the end of the right arrow
1068                                Vector3 p = pt_v.GetPoint() + right * r;
1069                                X = p.GetX() * COS - p.GetY() * SIN;
1070                                Y = (p.GetX() * SIN + p.GetY() * COS);
1071                            } else {
1072                                X = pt_X;
1073                                Y = pt_Y;
1074                            }
1075                            if (X > m_layout.xMax) m_layout.xMax = X;
1076                            if (X < m_layout.xMin) m_layout.xMin = X;
1077                            if (Y > m_layout.yMax) m_layout.yMax = Y;
1078                            if (Y < m_layout.yMin) m_layout.yMin = Y;
1079                        }
1080                    }
1081
1082                    prev_pt_v = &pt_v;
1083
1084                    ++segment;
1085                }
1086            }
1087        }
1088    }
1089
1090    if (show_mask & (LABELS|STNS)) {
1091        for (auto label = mainfrm->GetLabels();
1092             label != mainfrm->GetLabelsEnd();
1093             ++label) {
1094            if (filter && !filter->CheckVisible((*label)->GetText()))
1095                continue;
1096            double x = (*label)->GetX();
1097            double y = (*label)->GetY();
1098            double z = (*label)->GetZ();
1099            if ((show_mask & SURF) || (*label)->IsUnderground()) {
1100                double X = x * COS - y * SIN;
1101                if (X > m_layout.xMax) m_layout.xMax = X;
1102                if (X < m_layout.xMin) m_layout.xMin = X;
1103                double Y = z * COST - (x * SIN + y * COS) * SINT;
1104                if (Y > m_layout.yMax) m_layout.yMax = Y;
1105                if (Y < m_layout.yMin) m_layout.yMin = Y;
1106            }
1107        }
1108    }
1109}
1110
1111static int xpPageWidth, ypPageDepth;
1112static long x_offset, y_offset;
1113static int fontsize, fontsize_labels;
1114
1115/* FIXME: allow the font to be set */
1116
1117static const char *fontname = "Arial", *fontname_labels = "Arial";
1118
1119svxPrintout::svxPrintout(MainFrm *mainfrm_, layout *l,
1120                         wxPageSetupDialogData *data, const wxString & title)
1121    : wxPrintout(title),
1122      mainfrm(mainfrm_), m_layout(l), m_data(data)
1123{
1124}
1125
1126void
1127svxPrintout::draw_info_box()
1128{
1129   layout *l = m_layout;
1130   int boxwidth = 70;
1131   int boxheight = 30;
1132
1133   pdc->SetPen(*pen_frame);
1134
1135   int div = boxwidth;
1136   if (l->view != layout::EXTELEV) {
1137      boxwidth += boxheight;
1138      MOVEMM(div, boxheight);
1139      DRAWMM(div, 0);
1140      MOVEMM(0, 30); DRAWMM(div, 30);
1141   }
1142
1143   MOVEMM(0, boxheight);
1144   DRAWMM(boxwidth, boxheight);
1145   DRAWMM(boxwidth, 0);
1146   if (!l->Border) {
1147      DRAWMM(0, 0);
1148      DRAWMM(0, boxheight);
1149   }
1150
1151   MOVEMM(0, 20); DRAWMM(div, 20);
1152   MOVEMM(0, 10); DRAWMM(div, 10);
1153
1154   switch (l->view) {
1155    case layout::PLAN: {
1156      long ax, ay, bx, by, cx, cy, dx, dy;
1157
1158      long xc = boxwidth - boxheight / 2;
1159      long yc = boxheight / 2;
1160      const double RADIUS = boxheight / 3;
1161      DrawEllipse(long(xc * l->scX), long(yc * l->scY),
1162                  long(RADIUS * l->scX), long(RADIUS * l->scY));
1163
1164      ax = (long)((xc - (RADIUS - 1) * sin(rad(000.0 + l->rot))) * l->scX);
1165      ay = (long)((yc + (RADIUS - 1) * cos(rad(000.0 + l->rot))) * l->scY);
1166      bx = (long)((xc - RADIUS * 0.5 * sin(rad(180.0 + l->rot))) * l->scX);
1167      by = (long)((yc + RADIUS * 0.5 * cos(rad(180.0 + l->rot))) * l->scY);
1168      cx = (long)((xc - (RADIUS - 1) * sin(rad(160.0 + l->rot))) * l->scX);
1169      cy = (long)((yc + (RADIUS - 1) * cos(rad(160.0 + l->rot))) * l->scY);
1170      dx = (long)((xc - (RADIUS - 1) * sin(rad(200.0 + l->rot))) * l->scX);
1171      dy = (long)((yc + (RADIUS - 1) * cos(rad(200.0 + l->rot))) * l->scY);
1172
1173      MoveTo(ax, ay);
1174      DrawTo(bx, by);
1175      DrawTo(cx, cy);
1176      DrawTo(ax, ay);
1177      DrawTo(dx, dy);
1178      DrawTo(bx, by);
1179
1180      pdc->SetTextForeground(colour_text);
1181      MOVEMM(div + 0.5, boxheight - 5.5);
1182      WriteString(wmsg(/*North*/115));
1183
1184      wxString angle = format_angle(ANGLE_FMT, l->rot);
1185      wxString s;
1186      /* TRANSLATORS: This is used on printouts of plans, with %s replaced by
1187       * something like "123°".  The bearing is up the page. */
1188      s.Printf(wmsg(/*Plan view, %s up page*/168), angle.c_str());
1189      MOVEMM(2, 12); WriteString(s);
1190      break;
1191    }
1192    case layout::ELEV: case layout::TILT: {
1193      const int L = div + 2;
1194      const int R = boxwidth - 2;
1195      const int H = boxheight / 2;
1196      MOVEMM(L, H); DRAWMM(L + 5, H - 3); DRAWMM(L + 3, H); DRAWMM(L + 5, H + 3);
1197
1198      DRAWMM(L, H); DRAWMM(R, H);
1199
1200      DRAWMM(R - 5, H + 3); DRAWMM(R - 3, H); DRAWMM(R - 5, H - 3); DRAWMM(R, H);
1201
1202      MOVEMM((L + R) / 2, H - 2); DRAWMM((L + R) / 2, H + 2);
1203
1204      pdc->SetTextForeground(colour_text);
1205      MOVEMM(div + 2, boxheight - 8);
1206      /* TRANSLATORS: "Elevation on" 020 <-> 200 degrees */
1207      WriteString(wmsg(/*Elevation on*/116));
1208
1209      MOVEMM(L, 2);
1210      WriteString(format_angle(ANGLE_FMT, fmod(l->rot + 270.0, 360.0)));
1211      MOVEMM(R - 10, 2);
1212      WriteString(format_angle(ANGLE_FMT, fmod(l->rot + 90.0, 360.0)));
1213
1214      wxString angle = format_angle(ANGLE_FMT, l->rot);
1215      wxString s;
1216      if (l->view == layout::ELEV) {
1217          /* TRANSLATORS: This is used on printouts of elevations, with %s
1218           * replaced by something like "123°".  The bearing is the direction
1219           * we’re looking. */
1220          s.Printf(wmsg(/*Elevation facing %s*/169), angle.c_str());
1221      } else {
1222          wxString a2 = format_angle(ANGLE2_FMT, l->tilt);
1223          /* TRANSLATORS: This is used on printouts of tilted elevations, with
1224           * the first %s replaced by something like "123°", and the second by
1225           * something like "-45°".  The bearing is the direction we’re
1226           * looking. */
1227          s.Printf(wmsg(/*Elevation facing %s, tilted %s*/284), angle.c_str(), a2.c_str());
1228      }
1229      MOVEMM(2, 12); WriteString(s);
1230      break;
1231    }
1232    case layout::EXTELEV:
1233      pdc->SetTextForeground(colour_text);
1234      MOVEMM(2, 12);
1235      /* TRANSLATORS: This is used on printouts of extended elevations. */
1236      WriteString(wmsg(/*Extended elevation*/191));
1237      break;
1238   }
1239
1240   MOVEMM(2, boxheight - 8); WriteString(l->title);
1241
1242   MOVEMM(2, 2);
1243   // FIXME: "Original Scale" better?
1244   WriteString(wxString::Format(wmsg(/*Scale*/154) + wxT(" 1:%.0f"),
1245                                l->Scale));
1246
1247   /* This used to be a copyright line, but it was occasionally
1248    * mis-interpreted as us claiming copyright on the survey, so let's
1249    * give the website URL instead */
1250   MOVEMM(boxwidth + 2, 2);
1251   WriteString(wxT("Survex " VERSION " - https://survex.com/"));
1252
1253   draw_scale_bar(boxwidth + 10.0, 17.0, l->PaperWidth - boxwidth - 18.0);
1254}
1255
1256/* Draw fancy scale bar with bottom left at (x,y) (both in mm) and at most */
1257/* MaxLength mm long. The scaling in use is 1:scale */
1258void
1259svxPrintout::draw_scale_bar(double x, double y, double MaxLength)
1260{
1261   double StepEst, d;
1262   int E, Step, n, c;
1263   wxString buf;
1264   /* Limit scalebar to 20cm to stop people with A0 plotters complaining */
1265   if (MaxLength > 200.0) MaxLength = 200.0;
1266
1267#define dmin 10.0      /* each division >= dmin mm long */
1268#define StepMax 5      /* number in steps of at most StepMax (x 10^N) */
1269#define epsilon (1e-4) /* fudge factor to prevent rounding problems */
1270
1271   E = (int)ceil(log10((dmin * 0.001 * m_layout->Scale) / StepMax));
1272   StepEst = pow(10.0, -(double)E) * (dmin * 0.001) * m_layout->Scale - epsilon;
1273
1274   /* Force labelling to be in multiples of 1, 2, or 5 */
1275   Step = (StepEst <= 1.0 ? 1 : (StepEst <= 2.0 ? 2 : 5));
1276
1277   /* Work out actual length of each scale bar division */
1278   d = Step * pow(10.0, (double)E) / m_layout->Scale * 1000.0;
1279
1280   /* FIXME: Non-metric units here... */
1281   /* Choose appropriate units, s.t. if possible E is >=0 and minimized */
1282   int units;
1283   if (E >= 3) {
1284      E -= 3;
1285      units = /*km*/423;
1286   } else if (E >= 0) {
1287      units = /*m*/424;
1288   } else {
1289      E += 2;
1290      units = /*cm*/425;
1291   }
1292
1293   buf = wmsg(/*Scale*/154);
1294
1295   /* Add units used - eg. "Scale (10m)" */
1296   double pow10_E = pow(10.0, (double)E);
1297   if (E >= 0) {
1298      buf += wxString::Format(wxT(" (%.f%s)"), pow10_E, wmsg(units).c_str());
1299   } else {
1300      int sf = -(int)floor(E);
1301      buf += wxString::Format(wxT(" (%.*f%s)"), sf, pow10_E, wmsg(units).c_str());
1302   }
1303   pdc->SetTextForeground(colour_text);
1304   MOVEMM(x, y + 4); WriteString(buf);
1305
1306   /* Work out how many divisions there will be */
1307   n = (int)(MaxLength / d);
1308
1309   pdc->SetPen(*pen_frame);
1310
1311   long Y = long(y * m_layout->scY);
1312   long Y2 = long((y + 3) * m_layout->scY);
1313   long X = long(x * m_layout->scX);
1314   long X2 = long((x + n * d) * m_layout->scX);
1315
1316   /* Draw top of scale bar */
1317   MoveTo(X2, Y2);
1318   DrawTo(X, Y2);
1319#if 0
1320   DrawTo(X2, Y);
1321   DrawTo(X, Y);
1322   MOVEMM(x + n * d, y); DRAWMM(x, y);
1323#endif
1324   /* Draw divisions and label them */
1325   for (c = 0; c <= n; c++) {
1326      pdc->SetPen(*pen_frame);
1327      X = long((x + c * d) * m_layout->scX);
1328      MoveTo(X, Y);
1329      DrawTo(X, Y2);
1330#if 0 // Don't waste toner!
1331      /* Draw a "zebra crossing" scale bar. */
1332      if (c < n && (c & 1) == 0) {
1333          X2 = long((x + (c + 1) * d) * m_layout->scX);
1334          SolidRectangle(X, Y, X2 - X, Y2 - Y);
1335      }
1336#endif
1337      buf.Printf(wxT("%d"), c * Step);
1338      pdc->SetTextForeground(colour_text);
1339      MOVEMM(x + c * d - buf.length(), y - 5);
1340      WriteString(buf);
1341   }
1342}
1343
1344#if 0
1345void
1346make_calibration(layout *l) {
1347      img_point pt = { 0.0, 0.0, 0.0 };
1348      l->xMax = l->yMax = 0.1;
1349      l->xMin = l->yMin = 0;
1350
1351      stack(l, img_MOVE, NULL, &pt);
1352      pt.x = 0.1;
1353      stack(l, img_LINE, NULL, &pt);
1354      pt.y = 0.1;
1355      stack(l, img_LINE, NULL, &pt);
1356      pt.x = 0.0;
1357      stack(l, img_LINE, NULL, &pt);
1358      pt.y = 0.0;
1359      stack(l, img_LINE, NULL, &pt);
1360      pt.x = 0.05;
1361      pt.y = 0.001;
1362      stack(l, img_LABEL, "10cm", &pt);
1363      pt.x = 0.001;
1364      pt.y = 0.05;
1365      stack(l, img_LABEL, "10cm", &pt);
1366      l->Scale = 1.0;
1367}
1368#endif
1369
1370int
1371svxPrintout::next_page(int *pstate, char **q, int pageLim)
1372{
1373   char *p;
1374   int page;
1375   int c;
1376   p = *q;
1377   if (*pstate > 0) {
1378      /* doing a range */
1379      (*pstate)++;
1380      wxASSERT(*p == '-');
1381      p++;
1382      while (isspace((unsigned char)*p)) p++;
1383      if (sscanf(p, "%u%n", &page, &c) > 0) {
1384         p += c;
1385      } else {
1386         page = pageLim;
1387      }
1388      if (*pstate > page) goto err;
1389      if (*pstate < page) return *pstate;
1390      *q = p;
1391      *pstate = 0;
1392      return page;
1393   }
1394
1395   while (isspace((unsigned char)*p) || *p == ',') p++;
1396
1397   if (!*p) return 0; /* done */
1398
1399   if (*p == '-') {
1400      *q = p;
1401      *pstate = 1;
1402      return 1; /* range with initial parameter omitted */
1403   }
1404   if (sscanf(p, "%u%n", &page, &c) > 0) {
1405      p += c;
1406      while (isspace((unsigned char)*p)) p++;
1407      *q = p;
1408      if (0 < page && page <= pageLim) {
1409         if (*p == '-') *pstate = page; /* range with start */
1410         return page;
1411      }
1412   }
1413   err:
1414   *pstate = -1;
1415   return 0;
1416}
1417
1418/* Draws in alignment marks on each page or borders on edge pages */
1419void
1420svxPrintout::drawticks(int tsize, int x, int y)
1421{
1422   long i;
1423   int s = tsize * 4;
1424   int o = s / 8;
1425   bool fAtCorner = false;
1426   pdc->SetPen(*pen_frame);
1427   if (x == 0 && m_layout->Border) {
1428      /* solid left border */
1429      MoveTo(clip.x_min, clip.y_min);
1430      DrawTo(clip.x_min, clip.y_max);
1431      fAtCorner = true;
1432   } else {
1433      if (x > 0 || y > 0) {
1434         MoveTo(clip.x_min, clip.y_min);
1435         DrawTo(clip.x_min, clip.y_min + tsize);
1436      }
1437      if (s && x > 0 && m_layout->Cutlines) {
1438         /* dashed left border */
1439         i = (clip.y_max - clip.y_min) -
1440             (tsize + ((clip.y_max - clip.y_min - tsize * 2L) % s) / 2);
1441         for ( ; i > tsize; i -= s) {
1442            MoveTo(clip.x_min, clip.y_max - (i + o));
1443            DrawTo(clip.x_min, clip.y_max - (i - o));
1444         }
1445      }
1446      if (x > 0 || y < m_layout->pagesY - 1) {
1447         MoveTo(clip.x_min, clip.y_max - tsize);
1448         DrawTo(clip.x_min, clip.y_max);
1449         fAtCorner = true;
1450      }
1451   }
1452
1453   if (y == m_layout->pagesY - 1 && m_layout->Border) {
1454      /* solid top border */
1455      if (!fAtCorner) MoveTo(clip.x_min, clip.y_max);
1456      DrawTo(clip.x_max, clip.y_max);
1457      fAtCorner = true;
1458   } else {
1459      if (y < m_layout->pagesY - 1 || x > 0) {
1460         if (!fAtCorner) MoveTo(clip.x_min, clip.y_max);
1461         DrawTo(clip.x_min + tsize, clip.y_max);
1462      }
1463      if (s && y < m_layout->pagesY - 1 && m_layout->Cutlines) {
1464         /* dashed top border */
1465         i = (clip.x_max - clip.x_min) -
1466             (tsize + ((clip.x_max - clip.x_min - tsize * 2L) % s) / 2);
1467         for ( ; i > tsize; i -= s) {
1468            MoveTo(clip.x_max - (i + o), clip.y_max);
1469            DrawTo(clip.x_max - (i - o), clip.y_max);
1470         }
1471      }
1472      if (y < m_layout->pagesY - 1 || x < m_layout->pagesX - 1) {
1473         MoveTo(clip.x_max - tsize, clip.y_max);
1474         DrawTo(clip.x_max, clip.y_max);
1475         fAtCorner = true;
1476      } else {
1477         fAtCorner = false;
1478      }
1479   }
1480
1481   if (x == m_layout->pagesX - 1 && m_layout->Border) {
1482      /* solid right border */
1483      if (!fAtCorner) MoveTo(clip.x_max, clip.y_max);
1484      DrawTo(clip.x_max, clip.y_min);
1485      fAtCorner = true;
1486   } else {
1487      if (x < m_layout->pagesX - 1 || y < m_layout->pagesY - 1) {
1488         if (!fAtCorner) MoveTo(clip.x_max, clip.y_max);
1489         DrawTo(clip.x_max, clip.y_max - tsize);
1490      }
1491      if (s && x < m_layout->pagesX - 1 && m_layout->Cutlines) {
1492         /* dashed right border */
1493         i = (clip.y_max - clip.y_min) -
1494             (tsize + ((clip.y_max - clip.y_min - tsize * 2L) % s) / 2);
1495         for ( ; i > tsize; i -= s) {
1496            MoveTo(clip.x_max, clip.y_min + (i + o));
1497            DrawTo(clip.x_max, clip.y_min + (i - o));
1498         }
1499      }
1500      if (x < m_layout->pagesX - 1 || y > 0) {
1501         MoveTo(clip.x_max, clip.y_min + tsize);
1502         DrawTo(clip.x_max, clip.y_min);
1503         fAtCorner = true;
1504      } else {
1505         fAtCorner = false;
1506      }
1507   }
1508
1509   if (y == 0 && m_layout->Border) {
1510      /* solid bottom border */
1511      if (!fAtCorner) MoveTo(clip.x_max, clip.y_min);
1512      DrawTo(clip.x_min, clip.y_min);
1513   } else {
1514      if (y > 0 || x < m_layout->pagesX - 1) {
1515         if (!fAtCorner) MoveTo(clip.x_max, clip.y_min);
1516         DrawTo(clip.x_max - tsize, clip.y_min);
1517      }
1518      if (s && y > 0 && m_layout->Cutlines) {
1519         /* dashed bottom border */
1520         i = (clip.x_max - clip.x_min) -
1521             (tsize + ((clip.x_max - clip.x_min - tsize * 2L) % s) / 2);
1522         for ( ; i > tsize; i -= s) {
1523            MoveTo(clip.x_min + (i + o), clip.y_min);
1524            DrawTo(clip.x_min + (i - o), clip.y_min);
1525         }
1526      }
1527      if (y > 0 || x > 0) {
1528         MoveTo(clip.x_min + tsize, clip.y_min);
1529         DrawTo(clip.x_min, clip.y_min);
1530      }
1531   }
1532}
1533
1534bool
1535svxPrintout::OnPrintPage(int pageNum) {
1536    GetPageSizePixels(&xpPageWidth, &ypPageDepth);
1537    pdc = GetDC();
1538    pdc->SetBackgroundMode(wxTRANSPARENT);
1539#ifdef AVEN_PRINT_PREVIEW
1540    if (IsPreview()) {
1541        int dcx, dcy;
1542        pdc->GetSize(&dcx, &dcy);
1543        pdc->SetUserScale((double)dcx / xpPageWidth, (double)dcy / ypPageDepth);
1544    }
1545#endif
1546
1547    layout * l = m_layout;
1548    {
1549        int pwidth, pdepth;
1550        GetPageSizeMM(&pwidth, &pdepth);
1551        l->scX = (double)xpPageWidth / pwidth;
1552        l->scY = (double)ypPageDepth / pdepth;
1553        font_scaling_x = l->scX * (25.4 / 72.0);
1554        font_scaling_y = l->scY * (25.4 / 72.0);
1555        long MarginLeft = m_data->GetMarginTopLeft().x;
1556        long MarginTop = m_data->GetMarginTopLeft().y;
1557        long MarginBottom = m_data->GetMarginBottomRight().y;
1558        long MarginRight = m_data->GetMarginBottomRight().x;
1559        xpPageWidth -= (int)(l->scX * (MarginLeft + MarginRight));
1560        ypPageDepth -= (int)(l->scY * (FOOTER_HEIGHT_MM + MarginBottom + MarginTop));
1561        // xpPageWidth -= 1;
1562        pdepth -= FOOTER_HEIGHT_MM;
1563        x_offset = (long)(l->scX * MarginLeft);
1564        y_offset = (long)(l->scY * MarginTop);
1565        l->PaperWidth = pwidth -= MarginLeft + MarginRight;
1566        l->PaperDepth = pdepth -= MarginTop + MarginBottom;
1567    }
1568
1569    double SIN = sin(rad(l->rot));
1570    double COS = cos(rad(l->rot));
1571    double SINT = sin(rad(l->tilt));
1572    double COST = cos(rad(l->tilt));
1573
1574    NewPage(pageNum, l->pagesX, l->pagesY);
1575
1576    if (l->Legend && pageNum == (l->pagesY - 1) * l->pagesX + 1) {
1577        SetFont(font_default);
1578        draw_info_box();
1579    }
1580
1581    pdc->SetClippingRegion(x_offset, y_offset, xpPageWidth + 1, ypPageDepth + 1);
1582
1583    const double Sc = 1000 / l->Scale;
1584
1585    const SurveyFilter* filter = mainfrm->GetTreeFilter();
1586    int show_mask = l->get_effective_show_mask();
1587    if (show_mask & (LEGS|SURF)) {
1588        for (int f = 0; f != 8; ++f) {
1589            if ((show_mask & (f & img_FLAG_SURFACE) ? SURF : LEGS) == 0) {
1590                // Not showing traverse because of surface/underground status.
1591                continue;
1592            }
1593            if ((f & img_FLAG_SPLAY) && (show_mask & SPLAYS) == 0) {
1594                // Not showing because it's a splay.
1595                continue;
1596            }
1597            if (f & img_FLAG_SPLAY) {
1598                pdc->SetPen(*pen_splay);
1599            } else if (f & img_FLAG_SURFACE) {
1600                pdc->SetPen(*pen_surface_leg);
1601            } else {
1602                pdc->SetPen(*pen_leg);
1603            }
1604            list<traverse>::const_iterator trav = mainfrm->traverses_begin(f, filter);
1605            list<traverse>::const_iterator tend = mainfrm->traverses_end(f);
1606            for ( ; trav != tend; trav = mainfrm->traverses_next(f, filter, trav)) {
1607                vector<PointInfo>::const_iterator pos = trav->begin();
1608                vector<PointInfo>::const_iterator end = trav->end();
1609                for ( ; pos != end; ++pos) {
1610                    double x = pos->GetX();
1611                    double y = pos->GetY();
1612                    double z = pos->GetZ();
1613                    double X = x * COS - y * SIN;
1614                    double Y = z * COST - (x * SIN + y * COS) * SINT;
1615                    long px = (long)((X * Sc + l->xOrg) * l->scX);
1616                    long py = (long)((Y * Sc + l->yOrg) * l->scY);
1617                    if (pos == trav->begin()) {
1618                        MoveTo(px, py);
1619                    } else {
1620                        DrawTo(px, py);
1621                    }
1622                }
1623            }
1624        }
1625    }
1626
1627    if ((show_mask & XSECT) &&
1628        (l->tilt == 0.0 || l->tilt == 90.0 || l->tilt == -90.0)) {
1629        pdc->SetPen(*pen_splay);
1630        list<vector<XSect>>::const_iterator trav = mainfrm->tubes_begin();
1631        list<vector<XSect>>::const_iterator tend = mainfrm->tubes_end();
1632        for ( ; trav != tend; ++trav) {
1633            if (l->tilt == 0.0) {
1634                PlotUD(*trav);
1635            } else {
1636                // m_layout.tilt is 90.0 or -90.0 due to check above.
1637                PlotLR(*trav);
1638            }
1639        }
1640    }
1641
1642    if (show_mask & (LABELS|STNS)) {
1643        if (show_mask & LABELS) SetFont(font_labels);
1644        for (auto label = mainfrm->GetLabels();
1645             label != mainfrm->GetLabelsEnd();
1646             ++label) {
1647            if (filter && !filter->CheckVisible((*label)->GetText()))
1648                continue;
1649            double px = (*label)->GetX();
1650            double py = (*label)->GetY();
1651            double pz = (*label)->GetZ();
1652            if ((show_mask & SURF) || (*label)->IsUnderground()) {
1653                double X = px * COS - py * SIN;
1654                double Y = pz * COST - (px * SIN + py * COS) * SINT;
1655                long xnew, ynew;
1656                xnew = (long)((X * Sc + l->xOrg) * l->scX);
1657                ynew = (long)((Y * Sc + l->yOrg) * l->scY);
1658                if (show_mask & STNS) {
1659                    pdc->SetPen(*pen_cross);
1660                    DrawCross(xnew, ynew);
1661                }
1662                if (show_mask & LABELS) {
1663                    pdc->SetTextForeground(colour_labels);
1664                    MoveTo(xnew, ynew);
1665                    WriteString((*label)->GetText());
1666                }
1667            }
1668        }
1669    }
1670
1671    return true;
1672}
1673
1674void
1675svxPrintout::GetPageInfo(int *minPage, int *maxPage,
1676                         int *pageFrom, int *pageTo)
1677{
1678    *minPage = *pageFrom = 1;
1679    *maxPage = *pageTo = m_layout->pages;
1680}
1681
1682bool
1683svxPrintout::HasPage(int pageNum) {
1684    return (pageNum <= m_layout->pages);
1685}
1686
1687void
1688svxPrintout::OnBeginPrinting() {
1689    /* Initialise printer routines */
1690    fontsize_labels = 10;
1691    fontsize = 10;
1692
1693    colour_text = colour_labels = *wxBLACK;
1694
1695    wxColour colour_frame, colour_cross, colour_leg, colour_surface_leg;
1696    colour_frame = colour_cross = colour_leg = colour_surface_leg = *wxBLACK;
1697
1698    pen_frame = new wxPen(colour_frame);
1699    pen_cross = new wxPen(colour_cross);
1700    pen_leg = new wxPen(colour_leg);
1701    pen_surface_leg = new wxPen(colour_surface_leg);
1702    pen_splay = new wxPen(wxColour(128, 128, 128));
1703
1704    m_layout->scX = 1;
1705    m_layout->scY = 1;
1706
1707    font_labels = new wxFont(fontsize_labels, wxFONTFAMILY_DEFAULT,
1708                             wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
1709                             false, wxString(fontname_labels, wxConvUTF8),
1710                             wxFONTENCODING_ISO8859_1);
1711    font_default = new wxFont(fontsize, wxFONTFAMILY_DEFAULT,
1712                              wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
1713                              false, wxString(fontname, wxConvUTF8),
1714                              wxFONTENCODING_ISO8859_1);
1715}
1716
1717void
1718svxPrintout::OnEndPrinting() {
1719    delete font_labels;
1720    delete font_default;
1721    delete pen_frame;
1722    delete pen_cross;
1723    delete pen_leg;
1724    delete pen_surface_leg;
1725    delete pen_splay;
1726}
1727
1728int
1729svxPrintout::check_intersection(long x_p, long y_p)
1730{
1731#define U 1
1732#define D 2
1733#define L 4
1734#define R 8
1735   int mask_p = 0, mask_t = 0;
1736   if (x_p < 0)
1737      mask_p = L;
1738   else if (x_p > xpPageWidth)
1739      mask_p = R;
1740
1741   if (y_p < 0)
1742      mask_p |= D;
1743   else if (y_p > ypPageDepth)
1744      mask_p |= U;
1745
1746   if (x_t < 0)
1747      mask_t = L;
1748   else if (x_t > xpPageWidth)
1749      mask_t = R;
1750
1751   if (y_t < 0)
1752      mask_t |= D;
1753   else if (y_t > ypPageDepth)
1754      mask_t |= U;
1755
1756#if 0
1757   /* approximation to correct answer */
1758   return !(mask_t & mask_p);
1759#else
1760   /* One end of the line is on the page */
1761   if (!mask_t || !mask_p) return 1;
1762
1763   /* whole line is above, left, right, or below page */
1764   if (mask_t & mask_p) return 0;
1765
1766   if (mask_t == 0) mask_t = mask_p;
1767   if (mask_t & U) {
1768      double v = (double)(y_p - ypPageDepth) / (y_p - y_t);
1769      return v >= 0 && v <= 1;
1770   }
1771   if (mask_t & D) {
1772      double v = (double)y_p / (y_p - y_t);
1773      return v >= 0 && v <= 1;
1774   }
1775   if (mask_t & R) {
1776      double v = (double)(x_p - xpPageWidth) / (x_p - x_t);
1777      return v >= 0 && v <= 1;
1778   }
1779   wxASSERT(mask_t & L);
1780   {
1781      double v = (double)x_p / (x_p - x_t);
1782      return v >= 0 && v <= 1;
1783   }
1784#endif
1785#undef U
1786#undef D
1787#undef L
1788#undef R
1789}
1790
1791void
1792svxPrintout::MoveTo(long x, long y)
1793{
1794    x_t = x_offset + x - clip.x_min;
1795    y_t = y_offset + clip.y_max - y;
1796}
1797
1798void
1799svxPrintout::DrawTo(long x, long y)
1800{
1801    long x_p = x_t, y_p = y_t;
1802    x_t = x_offset + x - clip.x_min;
1803    y_t = y_offset + clip.y_max - y;
1804    if (!scan_for_blank_pages) {
1805        pdc->DrawLine(x_p, y_p, x_t, y_t);
1806    } else {
1807        if (check_intersection(x_p, y_p)) fBlankPage = false;
1808    }
1809}
1810
1811#define PWX_CROSS_SIZE (int)(2 * m_layout->scX / POINTS_PER_MM)
1812
1813void
1814svxPrintout::DrawCross(long x, long y)
1815{
1816   if (!scan_for_blank_pages) {
1817      MoveTo(x - PWX_CROSS_SIZE, y - PWX_CROSS_SIZE);
1818      DrawTo(x + PWX_CROSS_SIZE, y + PWX_CROSS_SIZE);
1819      MoveTo(x + PWX_CROSS_SIZE, y - PWX_CROSS_SIZE);
1820      DrawTo(x - PWX_CROSS_SIZE, y + PWX_CROSS_SIZE);
1821      MoveTo(x, y);
1822   } else {
1823      if ((x + PWX_CROSS_SIZE > clip.x_min &&
1824           x - PWX_CROSS_SIZE < clip.x_max) ||
1825          (y + PWX_CROSS_SIZE > clip.y_min &&
1826           y - PWX_CROSS_SIZE < clip.y_max)) {
1827         fBlankPage = false;
1828      }
1829   }
1830}
1831
1832void
1833svxPrintout::WriteString(const wxString & s)
1834{
1835    double xsc, ysc;
1836    pdc->GetUserScale(&xsc, &ysc);
1837    pdc->SetUserScale(xsc * font_scaling_x, ysc * font_scaling_y);
1838    if (!scan_for_blank_pages) {
1839        pdc->DrawText(s,
1840                      long(x_t / font_scaling_x),
1841                      long(y_t / font_scaling_y) - pdc->GetCharHeight());
1842    } else {
1843        int w, h;
1844        pdc->GetTextExtent(s, &w, &h);
1845        if ((y_t + h > 0 && y_t - h < clip.y_max - clip.y_min) ||
1846            (x_t < clip.x_max - clip.x_min && x_t + w > 0)) {
1847            fBlankPage = false;
1848        }
1849    }
1850    pdc->SetUserScale(xsc, ysc);
1851}
1852
1853void
1854svxPrintout::DrawEllipse(long x, long y, long r, long R)
1855{
1856    if (!scan_for_blank_pages) {
1857        x_t = x_offset + x - clip.x_min;
1858        y_t = y_offset + clip.y_max - y;
1859        const wxBrush & save_brush = pdc->GetBrush();
1860        pdc->SetBrush(*wxTRANSPARENT_BRUSH);
1861        pdc->DrawEllipse(x_t - r, y_t - R, 2 * r, 2 * R);
1862        pdc->SetBrush(save_brush);
1863    } else {
1864        /* No need to check - this is only used in the legend. */
1865    }
1866}
1867
1868void
1869svxPrintout::SolidRectangle(long x, long y, long w, long h)
1870{
1871    long X = x_offset + x - clip.x_min;
1872    long Y = y_offset + clip.y_max - y;
1873    pdc->SetBrush(*wxBLACK_BRUSH);
1874    pdc->DrawRectangle(X, Y - h, w, h);
1875}
1876
1877void
1878svxPrintout::NewPage(int pg, int pagesX, int pagesY)
1879{
1880    pdc->DestroyClippingRegion();
1881
1882    int x, y;
1883    x = (pg - 1) % pagesX;
1884    y = pagesY - 1 - ((pg - 1) / pagesX);
1885
1886    clip.x_min = (long)x * xpPageWidth;
1887    clip.y_min = (long)y * ypPageDepth;
1888    clip.x_max = clip.x_min + xpPageWidth; /* dm/pcl/ps had -1; */
1889    clip.y_max = clip.y_min + ypPageDepth; /* dm/pcl/ps had -1; */
1890
1891    const int FOOTERS = 4;
1892    wxString footer[FOOTERS];
1893    footer[0] = m_layout->title;
1894
1895    double rot = m_layout->rot;
1896    double tilt = m_layout->tilt;
1897    double scale = m_layout->Scale;
1898    switch (m_layout->view) {
1899        case layout::PLAN:
1900            // TRANSLATORS: Used in the footer of printouts to compactly
1901            // indicate this is a plan view and what the viewing angle is.
1902            // Aven will replace %s with the bearing, and %.0f with the scale.
1903            //
1904            // This message probably doesn't need translating for most languages.
1905            footer[1].Printf(wmsg(/*↑%s 1:%.0f*/233),
1906                    format_angle(ANGLE_FMT, rot).c_str(),
1907                    scale);
1908            break;
1909        case layout::ELEV:
1910            // TRANSLATORS: Used in the footer of printouts to compactly
1911            // indicate this is an elevation view and what the viewing angle
1912            // is.  Aven will replace the %s codes with the bearings to the
1913            // left and right of the viewer, and %.0f with the scale.
1914            //
1915            // This message probably doesn't need translating for most languages.
1916            footer[1].Printf(wmsg(/*%s↔%s 1:%.0f*/235),
1917                    format_angle(ANGLE_FMT, fmod(rot + 270.0, 360.0)).c_str(),
1918                    format_angle(ANGLE_FMT, fmod(rot + 90.0, 360.0)).c_str(),
1919                    scale);
1920            break;
1921        case layout::TILT:
1922            // TRANSLATORS: Used in the footer of printouts to compactly
1923            // indicate this is a tilted elevation view and what the viewing
1924            // angles are.  Aven will replace the %s codes with the bearings to
1925            // the left and right of the viewer and the angle the view is
1926            // tilted at, and %.0f with the scale.
1927            //
1928            // This message probably doesn't need translating for most languages.
1929            footer[1].Printf(wmsg(/*%s↔%s ∡%s 1:%.0f*/236),
1930                    format_angle(ANGLE_FMT, fmod(rot + 270.0, 360.0)).c_str(),
1931                    format_angle(ANGLE_FMT, fmod(rot + 90.0, 360.0)).c_str(),
1932                    format_angle(ANGLE2_FMT, tilt).c_str(),
1933                    scale);
1934            break;
1935        case layout::EXTELEV:
1936            // TRANSLATORS: Used in the footer of printouts to compactly
1937            // indicate this is an extended elevation view.  Aven will replace
1938            // %.0f with the scale.
1939            //
1940            // Try to keep the translation short (for example, in English we
1941            // use "Extended" not "Extended elevation") - there is limited room
1942            // in the footer, and the details there are mostly to make it easy
1943            // to check that you have corresponding pages from a multiple page
1944            // printout.
1945            footer[1].Printf(wmsg(/*Extended 1:%.0f*/244), scale);
1946            break;
1947    }
1948
1949    // TRANSLATORS: N/M meaning page N of M in the page footer of a printout.
1950    footer[2].Printf(wmsg(/*%d/%d*/232), pg, m_layout->pagesX * m_layout->pagesY);
1951
1952    wxString datestamp = m_layout->datestamp;
1953    if (!datestamp.empty()) {
1954        // Remove any timezone suffix (e.g. " UTC" or " +1200").
1955        wxChar ch = datestamp[datestamp.size() - 1];
1956        if (ch >= 'A' && ch <= 'Z') {
1957            for (size_t i = datestamp.size() - 1; i; --i) {
1958                ch = datestamp[i];
1959                if (ch < 'A' || ch > 'Z') {
1960                    if (ch == ' ') datestamp.resize(i);
1961                    break;
1962                }
1963            }
1964        } else if (ch >= '0' && ch <= '9') {
1965            for (size_t i = datestamp.size() - 1; i; --i) {
1966                ch = datestamp[i];
1967                if (ch < '0' || ch > '9') {
1968                    if ((ch == '-' || ch == '+') && datestamp[--i] == ' ')
1969                        datestamp.resize(i);
1970                    break;
1971                }
1972            }
1973        }
1974
1975        // Remove any day prefix (e.g. "Mon,").
1976        for (size_t i = 0; i != datestamp.size(); ++i) {
1977            if (datestamp[i] == ',' && i + 1 != datestamp.size()) {
1978                // Also skip a space after the comma.
1979                if (datestamp[i + 1] == ' ') ++i;
1980                datestamp.erase(0, i + 1);
1981                break;
1982            }
1983        }
1984    }
1985
1986    // TRANSLATORS: Used in the footer of printouts to compactly indicate that
1987    // the date which follows is the date that the survey data was processed.
1988    //
1989    // Aven will replace %s with a string giving the date and time (e.g.
1990    // "2015-06-09 12:40:44").
1991    footer[3].Printf(wmsg(/*Processed: %s*/167), datestamp.c_str());
1992
1993    const wxChar * footer_sep = wxT("    ");
1994    int fontsize_footer = fontsize_labels;
1995    wxFont * font_footer;
1996    font_footer = new wxFont(fontsize_footer, wxFONTFAMILY_DEFAULT,
1997                             wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
1998                             false, wxString(fontname_labels, wxConvUTF8),
1999                             wxFONTENCODING_UTF8);
2000    font_footer->Scale(font_scaling_x);
2001    SetFont(font_footer);
2002    int w[FOOTERS], ws, h;
2003    pdc->GetTextExtent(footer_sep, &ws, &h);
2004    int wtotal = ws * (FOOTERS - 1);
2005    for (int i = 0; i < FOOTERS; ++i) {
2006        pdc->GetTextExtent(footer[i], &w[i], &h);
2007        wtotal += w[i];
2008    }
2009
2010    long X = x_offset;
2011    long Y = y_offset + ypPageDepth + (long)(7 * m_layout->scY) - pdc->GetCharHeight();
2012
2013    if (wtotal > xpPageWidth) {
2014        // Rescale the footer so it fits.
2015        double rescale = double(wtotal) / xpPageWidth;
2016        double xsc, ysc;
2017        pdc->GetUserScale(&xsc, &ysc);
2018        pdc->SetUserScale(xsc / rescale, ysc / rescale);
2019        SetFont(font_footer);
2020        wxString fullfooter = footer[0];
2021        for (int i = 1; i < FOOTERS - 1; ++i) {
2022            fullfooter += footer_sep;
2023            fullfooter += footer[i];
2024        }
2025        pdc->DrawText(fullfooter, X * rescale, Y * rescale);
2026        // Draw final item right aligned to avoid misaligning.
2027        wxRect rect(x_offset * rescale, Y * rescale,
2028                    xpPageWidth * rescale, pdc->GetCharHeight() * rescale);
2029        pdc->DrawLabel(footer[FOOTERS - 1], rect, wxALIGN_RIGHT|wxALIGN_TOP);
2030        pdc->SetUserScale(xsc, ysc);
2031    } else {
2032        // Space out the elements of the footer to fill the line.
2033        double extra = double(xpPageWidth - wtotal) / (FOOTERS - 1);
2034        for (int i = 0; i < FOOTERS - 1; ++i) {
2035            pdc->DrawText(footer[i], X + extra * i, Y);
2036            X += ws + w[i];
2037        }
2038        // Draw final item right aligned to avoid misaligning.
2039        wxRect rect(x_offset, Y, xpPageWidth, pdc->GetCharHeight());
2040        pdc->DrawLabel(footer[FOOTERS - 1], rect, wxALIGN_RIGHT|wxALIGN_TOP);
2041    }
2042    drawticks((int)(9 * m_layout->scX / POINTS_PER_MM), x, y);
2043}
2044
2045void
2046svxPrintout::PlotLR(const vector<XSect> & centreline)
2047{
2048    const SurveyFilter* filter = mainfrm->GetTreeFilter();
2049    assert(centreline.size() > 1);
2050    const XSect* prev_pt_v = NULL;
2051    Vector3 last_right(1.0, 0.0, 0.0);
2052
2053    const double Sc = 1000 / m_layout->Scale;
2054    const double SIN = sin(rad(m_layout->rot));
2055    const double COS = cos(rad(m_layout->rot));
2056
2057    vector<XSect>::const_iterator i = centreline.begin();
2058    vector<XSect>::size_type segment = 0;
2059    while (i != centreline.end()) {
2060        // get the coordinates of this vertex
2061        const XSect & pt_v = *i++;
2062
2063        Vector3 right;
2064
2065        const Vector3 up_v(0.0, 0.0, 1.0);
2066
2067        if (segment == 0) {
2068            assert(i != centreline.end());
2069            // first segment
2070
2071            // get the coordinates of the next vertex
2072            const XSect & next_pt_v = *i;
2073
2074            // calculate vector from this pt to the next one
2075            Vector3 leg_v = next_pt_v - pt_v;
2076
2077            // obtain a vector in the LRUD plane
2078            right = leg_v * up_v;
2079            if (right.magnitude() == 0) {
2080                right = last_right;
2081            } else {
2082                last_right = right;
2083            }
2084        } else if (segment + 1 == centreline.size()) {
2085            // last segment
2086
2087            // Calculate vector from the previous pt to this one.
2088            Vector3 leg_v = pt_v - *prev_pt_v;
2089
2090            // Obtain a horizontal vector in the LRUD plane.
2091            right = leg_v * up_v;
2092            if (right.magnitude() == 0) {
2093                right = Vector3(last_right.GetX(), last_right.GetY(), 0.0);
2094            } else {
2095                last_right = right;
2096            }
2097        } else {
2098            assert(i != centreline.end());
2099            // Intermediate segment.
2100
2101            // Get the coordinates of the next vertex.
2102            const XSect & next_pt_v = *i;
2103
2104            // Calculate vectors from this vertex to the
2105            // next vertex, and from the previous vertex to
2106            // this one.
2107            Vector3 leg1_v = pt_v - *prev_pt_v;
2108            Vector3 leg2_v = next_pt_v - pt_v;
2109
2110            // Obtain horizontal vectors perpendicular to
2111            // both legs, then normalise and average to get
2112            // a horizontal bisector.
2113            Vector3 r1 = leg1_v * up_v;
2114            Vector3 r2 = leg2_v * up_v;
2115            r1.normalise();
2116            r2.normalise();
2117            right = r1 + r2;
2118            if (right.magnitude() == 0) {
2119                // This is the "mid-pitch" case...
2120                right = last_right;
2121            }
2122            last_right = right;
2123        }
2124
2125        // Scale to unit vectors in the LRUD plane.
2126        right.normalise();
2127
2128        double l = pt_v.GetL();
2129        double r = pt_v.GetR();
2130
2131        if (l >= 0 || r >= 0) {
2132            if (!filter || filter->CheckVisible(pt_v.GetLabel())) {
2133                // Get the x and y coordinates of the survey station
2134                double pt_X = pt_v.GetX() * COS - pt_v.GetY() * SIN;
2135                double pt_Y = pt_v.GetX() * SIN + pt_v.GetY() * COS;
2136                long pt_x = (long)((pt_X * Sc + m_layout->xOrg) * m_layout->scX);
2137                long pt_y = (long)((pt_Y * Sc + m_layout->yOrg) * m_layout->scY);
2138
2139                // Calculate dimensions for the right arrow
2140                double COSR = right.GetX();
2141                double SINR = right.GetY();
2142                long CROSS_MAJOR = (COSR + SINR) * PWX_CROSS_SIZE;
2143                long CROSS_MINOR = (COSR - SINR) * PWX_CROSS_SIZE;
2144
2145                if (l >= 0) {
2146                    // Get the x and y coordinates of the end of the left arrow
2147                    Vector3 p = pt_v.GetPoint() - right * l;
2148                    double X = p.GetX() * COS - p.GetY() * SIN;
2149                    double Y = (p.GetX() * SIN + p.GetY() * COS);
2150                    long x = (long)((X * Sc + m_layout->xOrg) * m_layout->scX);
2151                    long y = (long)((Y * Sc + m_layout->yOrg) * m_layout->scY);
2152
2153                    // Draw the arrow stem
2154                    MoveTo(pt_x, pt_y);
2155                    DrawTo(x, y);
2156
2157                    // Rotate the arrow by the page rotation
2158                    long dx1 = (+CROSS_MINOR) * COS - (+CROSS_MAJOR) * SIN;
2159                    long dy1 = (+CROSS_MINOR) * SIN + (+CROSS_MAJOR) * COS;
2160                    long dx2 = (+CROSS_MAJOR) * COS - (-CROSS_MINOR) * SIN;
2161                    long dy2 = (+CROSS_MAJOR) * SIN + (-CROSS_MINOR) * COS;
2162
2163                    // Draw the arrow
2164                    MoveTo(x + dx1, y + dy1);
2165                    DrawTo(x, y);
2166                    DrawTo(x + dx2, y + dy2);
2167                }
2168
2169                if (r >= 0) {
2170                    // Get the x and y coordinates of the end of the right arrow
2171                    Vector3 p = pt_v.GetPoint() + right * r;
2172                    double X = p.GetX() * COS - p.GetY() * SIN;
2173                    double Y = (p.GetX() * SIN + p.GetY() * COS);
2174                    long x = (long)((X * Sc + m_layout->xOrg) * m_layout->scX);
2175                    long y = (long)((Y * Sc + m_layout->yOrg) * m_layout->scY);
2176
2177                    // Draw the arrow stem
2178                    MoveTo(pt_x, pt_y);
2179                    DrawTo(x, y);
2180
2181                    // Rotate the arrow by the page rotation
2182                    long dx1 = (-CROSS_MINOR) * COS - (-CROSS_MAJOR) * SIN;
2183                    long dy1 = (-CROSS_MINOR) * SIN + (-CROSS_MAJOR) * COS;
2184                    long dx2 = (-CROSS_MAJOR) * COS - (+CROSS_MINOR) * SIN;
2185                    long dy2 = (-CROSS_MAJOR) * SIN + (+CROSS_MINOR) * COS;
2186
2187                    // Draw the arrow
2188                    MoveTo(x + dx1, y + dy1);
2189                    DrawTo(x, y);
2190                    DrawTo(x + dx2, y + dy2);
2191                }
2192            }
2193        }
2194
2195        prev_pt_v = &pt_v;
2196
2197        ++segment;
2198    }
2199}
2200
2201void
2202svxPrintout::PlotUD(const vector<XSect> & centreline)
2203{
2204    const SurveyFilter* filter = mainfrm->GetTreeFilter();
2205    assert(centreline.size() > 1);
2206    const double Sc = 1000 / m_layout->Scale;
2207
2208    vector<XSect>::const_iterator i = centreline.begin();
2209    while (i != centreline.end()) {
2210        // get the coordinates of this vertex
2211        const XSect & pt_v = *i++;
2212
2213        double u = pt_v.GetU();
2214        double d = pt_v.GetD();
2215
2216        if (u >= 0 || d >= 0) {
2217            if (filter && !filter->CheckVisible(pt_v.GetLabel()))
2218                continue;
2219
2220            // Get the coordinates of the survey point
2221            Vector3 p = pt_v.GetPoint();
2222            double SIN = sin(rad(m_layout->rot));
2223            double COS = cos(rad(m_layout->rot));
2224            double X = p.GetX() * COS - p.GetY() * SIN;
2225            double Y = p.GetZ();
2226            long x = (long)((X * Sc + m_layout->xOrg) * m_layout->scX);
2227            long pt_y = (long)((Y * Sc + m_layout->yOrg) * m_layout->scX);
2228
2229            if (u >= 0) {
2230                // Get the y coordinate of the up arrow
2231                long y = (long)(((Y + u) * Sc + m_layout->yOrg) * m_layout->scY);
2232
2233                // Draw the arrow stem
2234                MoveTo(x, pt_y);
2235                DrawTo(x, y);
2236
2237                // Draw the up arrow
2238                MoveTo(x - PWX_CROSS_SIZE, y - PWX_CROSS_SIZE);
2239                DrawTo(x, y);
2240                DrawTo(x + PWX_CROSS_SIZE, y - PWX_CROSS_SIZE);
2241            }
2242
2243            if (d >= 0) {
2244                // Get the y coordinate of the down arrow
2245                long y = (long)(((Y - d) * Sc + m_layout->yOrg) * m_layout->scY);
2246
2247                // Draw the arrow stem
2248                MoveTo(x, pt_y);
2249                DrawTo(x, y);
2250
2251                // Draw the down arrow
2252                MoveTo(x - PWX_CROSS_SIZE, y + PWX_CROSS_SIZE);
2253                DrawTo(x, y);
2254                DrawTo(x + PWX_CROSS_SIZE, y + PWX_CROSS_SIZE);
2255            }
2256        }
2257    }
2258}
Note: See TracBrowser for help on using the repository browser.