source: git/src/mainfrm.cc @ de7f61a6

stereo-2025
Last change on this file since de7f61a6 was 8048171, checked in by Olly Betts <olly@…>, 4 months ago

Improve the station search UI

Use wxSearchCtrl for the control which means it'll now use the
standard search control for platforms which have one. This
allows us to easily set a placeholder text to make it clearer
what the control is for.

  • Property mode set to 100644
File size: 82.4 KB
Line 
1//
2//  mainfrm.cc
3//
4//  Main frame handling for Aven.
5//
6//  Copyright (C) 2000-2002,2005,2006 Mark R. Shinwell
7//  Copyright (C) 2001-2025 Olly Betts
8//  Copyright (C) 2005 Martin Green
9//
10//  This program is free software; you can redistribute it and/or modify
11//  it under the terms of the GNU General Public License as published by
12//  the Free Software Foundation; either version 2 of the License, or
13//  (at your option) any later version.
14//
15//  This program is distributed in the hope that it will be useful,
16//  but WITHOUT ANY WARRANTY; without even the implied warranty of
17//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18//  GNU General Public License for more details.
19//
20//  You should have received a copy of the GNU General Public License
21//  along with this program; if not, write to the Free Software
22//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
23//
24
25#include <config.h>
26
27#include "cavernlog.h"
28#include "mainfrm.h"
29#include "aven.h"
30#include "aboutdlg.h"
31
32#include "message.h"
33#include "img_for_survex.h"
34#include "printing.h"
35#include "filename.h"
36#include "useful.h"
37
38#include <wx/confbase.h>
39//#include <wx/filefn.h>
40#include <wx/filename.h>
41#include <wx/image.h>
42#include <wx/imaglist.h>
43#include <wx/process.h>
44#include <wx/regex.h>
45#include <wx/srchctrl.h>
46#include <wx/spinctrl.h>
47#ifdef USING_GENERIC_TOOLBAR
48# include <wx/sysopt.h>
49#endif
50
51#include <cerrno>
52#include <cstdlib>
53#include <float.h>
54#include <functional>
55#include <vector>
56
57// XPM files declare the array as static, but we also want it to be const too.
58// This avoids a compiler warning, and also means the data can go in a
59// read-only page and be shared between processes.
60#define static static const
61#ifndef __WXMSW__
62#include "../lib/icons/aven.xpm"
63#endif
64#include "../lib/icons/log.xpm"
65#include "../lib/icons/open.xpm"
66#include "../lib/icons/open_pres.xpm"
67#include "../lib/icons/rotation.xpm"
68#include "../lib/icons/plan.xpm"
69#include "../lib/icons/elevation.xpm"
70#include "../lib/icons/defaults.xpm"
71#include "../lib/icons/names.xpm"
72#include "../lib/icons/crosses.xpm"
73#include "../lib/icons/entrances.xpm"
74#include "../lib/icons/fixed_pts.xpm"
75#include "../lib/icons/exported_pts.xpm"
76#include "../lib/icons/ug_legs.xpm"
77#include "../lib/icons/surface_legs.xpm"
78#include "../lib/icons/tubes.xpm"
79#include "../lib/icons/solid_surface.xpm"
80#include "../lib/icons/pres_frew.xpm"
81#include "../lib/icons/pres_rew.xpm"
82#include "../lib/icons/pres_go_back.xpm"
83#include "../lib/icons/pres_pause.xpm"
84#include "../lib/icons/pres_go.xpm"
85#include "../lib/icons/pres_ff.xpm"
86#include "../lib/icons/pres_fff.xpm"
87#include "../lib/icons/pres_stop.xpm"
88#include "../lib/icons/survey_tree.xpm"
89#include "../lib/icons/pres_tree.xpm"
90#undef static
91#ifdef __WXMSW__
92# define TOOL(x) wxBitmap(x##_xpm)
93#else
94# define TOOL(x) wxBITMAP(x)
95#endif
96
97using namespace std;
98
99class AvenSplitterWindow : public wxSplitterWindow {
100    MainFrm *parent;
101
102    public:
103        explicit AvenSplitterWindow(MainFrm *parent_)
104            : wxSplitterWindow(parent_, wxID_ANY,
105                               wxDefaultPosition, wxDefaultSize,
106                               wxSP_3DSASH),
107              parent(parent_)
108        {
109        }
110
111        void OnSplitterDClick(wxSplitterEvent &) {
112            parent->ToggleSidePanel();
113        }
114
115    private:
116        DECLARE_EVENT_TABLE()
117};
118
119BEGIN_EVENT_TABLE(AvenSplitterWindow, wxSplitterWindow)
120    EVT_SPLITTER_DCLICK(wxID_ANY, AvenSplitterWindow::OnSplitterDClick)
121END_EVENT_TABLE()
122
123class EditMarkDlg : public wxDialog {
124    wxTextCtrl * easting, * northing, * altitude;
125    wxTextCtrl * angle, * tilt_angle, * scale, * time;
126public:
127    // TRANSLATORS: Title of dialog to edit a waypoint in a presentation.
128    EditMarkDlg(wxWindow* parent, const PresentationMark & p)
129        : wxDialog(parent, 500, wmsg(/*Edit Waypoint*/404))
130    {
131        easting = new wxTextCtrl(this, 601, wxString::Format(wxT("%.3f"), p.GetX()));
132        northing = new wxTextCtrl(this, 602, wxString::Format(wxT("%.3f"), p.GetY()));
133        altitude = new wxTextCtrl(this, 603, wxString::Format(wxT("%.3f"), p.GetZ()));
134        angle = new wxTextCtrl(this, 604, wxString::Format(wxT("%.3f"), p.angle));
135        tilt_angle = new wxTextCtrl(this, 605, wxString::Format(wxT("%.3f"), p.tilt_angle));
136        scale = new wxTextCtrl(this, 606, wxString::Format(wxT("%.3f"), p.scale));
137        if (p.time > 0.0) {
138            time = new wxTextCtrl(this, 607, wxString::Format(wxT("%.3f"), p.time));
139        } else if (p.time < 0.0) {
140            time = new wxTextCtrl(this, 607, wxString::Format(wxT("*%.3f"), -p.time));
141        } else {
142            time = new wxTextCtrl(this, 607, wxT("0"));
143        }
144
145        wxBoxSizer * coords = new wxBoxSizer(wxHORIZONTAL);
146        coords->Add(new wxStaticText(this, 610, wxT("(")), 0, wxALIGN_CENTRE_VERTICAL);
147        coords->Add(easting, 1);
148        coords->Add(new wxStaticText(this, 611, wxT(",")), 0, wxALIGN_CENTRE_VERTICAL);
149        coords->Add(northing, 1);
150        coords->Add(new wxStaticText(this, 612, wxT(",")), 0, wxALIGN_CENTRE_VERTICAL);
151        coords->Add(altitude, 1);
152        coords->Add(new wxStaticText(this, 613, wxT(")")), 0, wxALIGN_CENTRE_VERTICAL);
153        wxBoxSizer* vert = new wxBoxSizer(wxVERTICAL);
154        vert->Add(coords, 0, wxALL, 8);
155        wxBoxSizer * r2 = new wxBoxSizer(wxHORIZONTAL);
156        r2->Add(new wxStaticText(this, 614, wmsg(/*Bearing*/259) + wxT(": ")), 0, wxALIGN_CENTRE_VERTICAL);
157        r2->Add(angle);
158        vert->Add(r2, 0, wxALL, 8);
159        wxBoxSizer * r3 = new wxBoxSizer(wxHORIZONTAL);
160        r3->Add(new wxStaticText(this, 615, wmsg(/*Elevation*/118) + wxT(": ")), 0, wxALIGN_CENTRE_VERTICAL);
161        r3->Add(tilt_angle);
162        vert->Add(r3, 0, wxALL, 8);
163        wxBoxSizer * r4 = new wxBoxSizer(wxHORIZONTAL);
164        r4->Add(new wxStaticText(this, 616, wmsg(/*Scale*/154) + wxT(": ")), 0, wxALIGN_CENTRE_VERTICAL);
165        r4->Add(scale);
166        /* TRANSLATORS: Note after "Scale" field in dialog to edit a waypoint
167         * in a presentation. */
168        r4->Add(new wxStaticText(this, 617, wmsg(/* (unused in perspective view)*/278)),
169                0, wxALIGN_CENTRE_VERTICAL);
170        vert->Add(r4, 0, wxALL, 8);
171
172        wxBoxSizer * r5 = new wxBoxSizer(wxHORIZONTAL);
173        /* TRANSLATORS: Field label in dialog to edit a waypoint in a
174         * presentation. */
175        r5->Add(new wxStaticText(this, 616, wmsg(/*Time: */279)), 0, wxALIGN_CENTRE_VERTICAL);
176        r5->Add(time);
177        /* TRANSLATORS: units+info after time field in dialog to edit a
178         * waypoint in a presentation. */
179        r5->Add(new wxStaticText(this, 617, wmsg(/* secs (0 = auto; *6 = 6 times auto)*/282)),
180                0, wxALIGN_CENTRE_VERTICAL);
181        vert->Add(r5, 0, wxALL, 8);
182
183        wxBoxSizer * buttons = new wxBoxSizer(wxHORIZONTAL);
184        wxButton* cancel = new wxButton(this, wxID_CANCEL);
185        buttons->Add(cancel, 0, wxALL, 8);
186        wxButton* ok = new wxButton(this, wxID_OK);
187        ok->SetDefault();
188        buttons->Add(ok, 0, wxALL, 8);
189        vert->Add(buttons, 0, wxALL|wxALIGN_RIGHT);
190
191        SetAutoLayout(true);
192        SetSizer(vert);
193
194        vert->SetSizeHints(this);
195    }
196    PresentationMark GetMark() const {
197        double a, t, s, T;
198        Vector3 v(wxAtof(easting->GetValue()),
199                  wxAtof(northing->GetValue()),
200                  wxAtof(altitude->GetValue()));
201        a = wxAtof(angle->GetValue());
202        t = wxAtof(tilt_angle->GetValue());
203        s = wxAtof(scale->GetValue());
204        wxString str = time->GetValue();
205        if (!str.empty() && str[0u] == '*') str[0u] = '-';
206        T = wxAtof(str);
207        return PresentationMark(v, a, t, s, T);
208    }
209
210private:
211    DECLARE_EVENT_TABLE()
212};
213
214// Write a value without trailing zeros after the decimal point.
215static void write_double(double d, FILE * fh) {
216    char buf[64];
217    snprintf(buf, sizeof(buf), "%.21f", d);
218    char * p = strchr(buf, ',');
219    if (p) *p = '.';
220    size_t l = strlen(buf);
221    while (l > 1 && buf[l - 1] == '0') --l;
222    if (l > 1 && buf[l - 1] == '.') --l;
223    FWRITE_(buf, l, 1, fh);
224}
225
226class AvenPresList : public wxListCtrl {
227    MainFrm * mainfrm;
228    GfxCore * gfx;
229    vector<PresentationMark> entries;
230    long current_item = -1;
231    bool modified = false;
232    bool force_save_as = true;
233    wxString filename;
234
235    public:
236        AvenPresList(MainFrm * mainfrm_, wxWindow * parent, GfxCore * gfx_)
237            : wxListCtrl(parent, listctrl_PRES, wxDefaultPosition, wxDefaultSize,
238                         wxLC_REPORT|wxLC_VIRTUAL),
239              mainfrm(mainfrm_), gfx(gfx_)
240        {
241            InsertColumn(0, wmsg(/*Easting*/378));
242            InsertColumn(1, wmsg(/*Northing*/379));
243            InsertColumn(2, wmsg(/*Altitude*/335));
244        }
245
246        void OnBeginLabelEdit(wxListEvent& event) {
247            event.Veto(); // No editting allowed
248        }
249        void OnDeleteItem(wxListEvent& event) {
250            long item = event.GetIndex();
251            if (current_item == item) {
252                current_item = -1;
253            } else if (current_item > item) {
254                --current_item;
255            }
256            entries.erase(entries.begin() + item);
257            SetItemCount(entries.size());
258            modified = true;
259        }
260        void OnDeleteAllItems(wxListEvent&) {
261            entries.clear();
262            SetItemCount(entries.size());
263            filename = wxString();
264            modified = false;
265            force_save_as = true;
266        }
267        void OnListKeyDown(wxListEvent& event) {
268            switch (event.GetKeyCode()) {
269                case WXK_DELETE: {
270                    long item = GetNextItem(-1, wxLIST_NEXT_ALL,
271                                            wxLIST_STATE_SELECTED);
272                    while (item != -1) {
273                        DeleteItem(item);
274                        // - 1 because the indices were shifted by DeleteItem()
275                        item = GetNextItem(item - 1, wxLIST_NEXT_ALL,
276                                           wxLIST_STATE_SELECTED);
277                    }
278                    break;
279                }
280                default:
281                    //printf("event.GetIndex() = %ld %d\n", event.GetIndex(), event.GetKeyCode());
282                    event.Skip();
283            }
284        }
285        void OnActivated(wxListEvent& event) {
286            // Jump to this view.
287            long item = event.GetIndex();
288            gfx->SetView(entries[item]);
289        }
290        void OnFocused(wxListEvent& event) {
291            current_item = event.GetIndex();
292        }
293        void OnRightClick(wxListEvent& event) {
294            long item = event.GetIndex();
295            if (item < 0) {
296                AddMark(item, gfx->GetView());
297                item = 0;
298            }
299            EditMarkDlg edit(mainfrm, entries[item]);
300            if (edit.ShowModal() == wxID_OK) {
301                entries[item] = edit.GetMark();
302            }
303        }
304        void OnChar(wxKeyEvent& event) {
305            switch (event.GetKeyCode()) {
306                case WXK_INSERT:
307                    if (event.GetModifiers() == wxMOD_CONTROL) {
308                        if (current_item != -1 &&
309                            size_t(current_item) < entries.size()) {
310                            AddMark(current_item, entries[current_item]);
311                        }
312                    } else {
313                        AddMark(current_item);
314                    }
315                    break;
316                case WXK_DELETE:
317                    // Already handled in OnListKeyDown.
318                    break;
319                case WXK_UP: case WXK_DOWN:
320                    event.Skip();
321                    break;
322                default:
323                    gfx->OnKeyPress(event);
324            }
325        }
326        void AddMark(long item = -1) {
327            AddMark(item, gfx->GetView());
328        }
329        void AddMark(long item, const PresentationMark & mark) {
330            if (item == -1) item = entries.size();
331            entries.insert(entries.begin() + item, mark);
332            SetItemCount(entries.size());
333            modified = true;
334        }
335        virtual wxString OnGetItemText(long item, long column) const {
336            if (item < 0 || item >= (long)entries.size()) return wxString();
337            const PresentationMark & p = entries[item];
338            double v;
339            switch (column) {
340                case 0: v = p.GetX(); break;
341                case 1: v = p.GetY(); break;
342                case 2: v = p.GetZ(); break;
343#if 0
344                case 3: v = p.angle; break;
345                case 4: v = p.tilt_angle; break;
346                case 5: v = p.scale; break;
347                case 6: v = p.time; break;
348#endif
349                default: return wxString();
350            }
351            return wxString::Format(wxT("%ld"), (long)v);
352        }
353        void Save(bool use_default_name) {
354            wxString fnm = filename;
355            if (!use_default_name || force_save_as) {
356#ifdef __WXMOTIF__
357                wxString ext(wxT("*.fly"));
358#else
359                wxString ext = wmsg(/*Aven presentations*/320);
360                ext += wxT("|*.fly");
361#endif
362                wxFileDialog dlg(this, wmsg(/*Select an output filename*/319),
363                                 wxString(), fnm, ext,
364                                 wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
365                if (dlg.ShowModal() != wxID_OK) return;
366                fnm = dlg.GetPath();
367            }
368
369            FILE * fh_pres = wxFopen(fnm, wxT("w"));
370            if (!fh_pres) {
371                wxGetApp().ReportError(wxString::Format(wmsg(/*Error writing to file “%s”*/7), fnm.c_str()));
372                return;
373            }
374            vector<PresentationMark>::const_iterator i;
375            for (i = entries.begin(); i != entries.end(); ++i) {
376                const PresentationMark &p = *i;
377                write_double(p.GetX(), fh_pres);
378                PUTC(' ', fh_pres);
379                write_double(p.GetY(), fh_pres);
380                PUTC(' ', fh_pres);
381                write_double(p.GetZ(), fh_pres);
382                PUTC(' ', fh_pres);
383                write_double(p.angle, fh_pres);
384                PUTC(' ', fh_pres);
385                write_double(p.tilt_angle, fh_pres);
386                PUTC(' ', fh_pres);
387                write_double(p.scale, fh_pres);
388                if (p.time != 0.0) {
389                    PUTC(' ', fh_pres);
390                    write_double(p.time, fh_pres);
391                }
392                PUTC('\n', fh_pres);
393            }
394            fclose(fh_pres);
395            filename = fnm;
396            modified = false;
397            force_save_as = false;
398        }
399        void New(const wxString &fnm) {
400            DeleteAllItems();
401            wxFileName::SplitPath(fnm, NULL, NULL, &filename, NULL, wxPATH_NATIVE);
402            filename += wxT(".fly");
403            force_save_as = true;
404        }
405        bool Load(const wxString &fnm) {
406            FILE * fh_pres = wxFopen(fnm, wxT("r"));
407            if (!fh_pres) {
408                wxString m;
409                m.Printf(wmsg(/*Couldn’t open file “%s”*/1), fnm.c_str());
410                wxGetApp().ReportError(m);
411                return false;
412            }
413            DeleteAllItems();
414            long item = 0;
415            while (!FEOF(fh_pres)) {
416                char buf[4096];
417                size_t i = 0;
418                while (i < sizeof(buf) - 1) {
419                    int ch = GETC(fh_pres);
420                    if (ch == EOF || ch == '\n' || ch == '\r') break;
421                    buf[i++] = ch;
422                }
423                if (i) {
424                    buf[i] = 0;
425                    double x, y, z, a, t, s, T;
426                    int c = sscanf(buf, "%lf %lf %lf %lf %lf %lf %lf", &x, &y, &z, &a, &t, &s, &T);
427                    if (c < 6) {
428                        char *p = buf;
429                        while ((p = strchr(p, '.'))) *p++ = ',';
430                        c = sscanf(buf, "%lf %lf %lf %lf %lf %lf %lf", &x, &y, &z, &a, &t, &s, &T);
431                        if (c < 6) {
432                            DeleteAllItems();
433                            wxGetApp().ReportError(wxString::Format(wmsg(/*Error in format of presentation file “%s”*/323), fnm.c_str()));
434                            return false;
435                        }
436                    }
437                    if (c == 6) T = 0;
438                    AddMark(item, PresentationMark(Vector3(x, y, z), a, t, s, T));
439                    ++item;
440                }
441            }
442            fclose(fh_pres);
443            filename = fnm;
444            modified = false;
445            force_save_as = false;
446            return true;
447        }
448        bool Modified() const { return modified; }
449        bool Empty() const { return entries.empty(); }
450        PresentationMark GetPresMark(int which) {
451            long item = current_item;
452            if (which == MARK_FIRST) {
453                item = 0;
454            } else if (which == MARK_NEXT) {
455                ++item;
456            } else if (which == MARK_PREV) {
457                --item;
458            }
459            if (item == -1 || item == (long)entries.size())
460                return PresentationMark();
461            if (item != current_item) {
462                // Move the focus
463                if (current_item != -1) {
464                    wxListCtrl::SetItemState(current_item, wxLIST_STATE_FOCUSED,
465                                             0);
466                }
467                wxListCtrl::SetItemState(item, wxLIST_STATE_FOCUSED,
468                                         wxLIST_STATE_FOCUSED);
469            }
470            return entries[item];
471        }
472
473    private:
474
475        DECLARE_NO_COPY_CLASS(AvenPresList)
476        DECLARE_EVENT_TABLE()
477};
478
479BEGIN_EVENT_TABLE(EditMarkDlg, wxDialog)
480END_EVENT_TABLE()
481
482BEGIN_EVENT_TABLE(AvenPresList, wxListCtrl)
483    EVT_LIST_BEGIN_LABEL_EDIT(listctrl_PRES, AvenPresList::OnBeginLabelEdit)
484    EVT_LIST_DELETE_ITEM(listctrl_PRES, AvenPresList::OnDeleteItem)
485    EVT_LIST_DELETE_ALL_ITEMS(listctrl_PRES, AvenPresList::OnDeleteAllItems)
486    EVT_LIST_KEY_DOWN(listctrl_PRES, AvenPresList::OnListKeyDown)
487    EVT_LIST_ITEM_ACTIVATED(listctrl_PRES, AvenPresList::OnActivated)
488    EVT_LIST_ITEM_FOCUSED(listctrl_PRES, AvenPresList::OnFocused)
489    EVT_LIST_ITEM_RIGHT_CLICK(listctrl_PRES, AvenPresList::OnRightClick)
490    EVT_CHAR(AvenPresList::OnChar)
491END_EVENT_TABLE()
492
493BEGIN_EVENT_TABLE(MainFrm, wxFrame)
494    EVT_TEXT(textctrl_FIND, MainFrm::OnFind)
495    EVT_TEXT_ENTER(textctrl_FIND, MainFrm::OnGotoFound)
496    EVT_SPINCTRLDOUBLE(spinctrl_Z_STRETCH, MainFrm::OnZStretch)
497    EVT_IDLE(MainFrm::OnIdle)
498
499    EVT_MENU(wxID_OPEN, MainFrm::OnOpen)
500    EVT_MENU(menu_FILE_OPEN_TERRAIN, MainFrm::OnOpenTerrain)
501    EVT_MENU(menu_FILE_OVERLAY_GEODATA, MainFrm::OnOverlayGeodata)
502    EVT_MENU(menu_FILE_LOG, MainFrm::OnShowLog)
503    EVT_MENU(wxID_PRINT, MainFrm::OnPrint)
504    EVT_MENU(menu_FILE_PAGE_SETUP, MainFrm::OnPageSetup)
505    EVT_MENU(menu_FILE_SCREENSHOT, MainFrm::OnScreenshot)
506//    EVT_MENU(wxID_PREFERENCES, MainFrm::OnFilePreferences)
507    EVT_MENU(menu_FILE_EXPORT, MainFrm::OnExport)
508    EVT_MENU(menu_FILE_EXTEND, MainFrm::OnExtend)
509    EVT_MENU(wxID_EXIT, MainFrm::OnQuit)
510    EVT_MENU_RANGE(wxID_FILE1, wxID_FILE9, MainFrm::OnMRUFile)
511
512    EVT_MENU(menu_PRES_NEW, MainFrm::OnPresNew)
513    EVT_MENU(menu_PRES_OPEN, MainFrm::OnPresOpen)
514    EVT_MENU(menu_PRES_SAVE, MainFrm::OnPresSave)
515    EVT_MENU(menu_PRES_SAVE_AS, MainFrm::OnPresSaveAs)
516    EVT_MENU(menu_PRES_MARK, MainFrm::OnPresMark)
517    EVT_MENU(menu_PRES_FREWIND, MainFrm::OnPresFRewind)
518    EVT_MENU(menu_PRES_REWIND, MainFrm::OnPresRewind)
519    EVT_MENU(menu_PRES_REVERSE, MainFrm::OnPresReverse)
520    EVT_MENU(menu_PRES_PLAY, MainFrm::OnPresPlay)
521    EVT_MENU(menu_PRES_FF, MainFrm::OnPresFF)
522    EVT_MENU(menu_PRES_FFF, MainFrm::OnPresFFF)
523    EVT_MENU(menu_PRES_PAUSE, MainFrm::OnPresPause)
524    EVT_MENU(wxID_STOP, MainFrm::OnPresStop)
525    EVT_MENU(menu_PRES_EXPORT_MOVIE, MainFrm::OnPresExportMovie)
526
527    EVT_UPDATE_UI(menu_PRES_NEW, MainFrm::OnPresNewUpdate)
528    EVT_UPDATE_UI(menu_PRES_OPEN, MainFrm::OnPresOpenUpdate)
529    EVT_UPDATE_UI(menu_PRES_SAVE, MainFrm::OnPresSaveUpdate)
530    EVT_UPDATE_UI(menu_PRES_SAVE_AS, MainFrm::OnPresSaveAsUpdate)
531    EVT_UPDATE_UI(menu_PRES_MARK, MainFrm::OnPresMarkUpdate)
532    EVT_UPDATE_UI(menu_PRES_FREWIND, MainFrm::OnPresFRewindUpdate)
533    EVT_UPDATE_UI(menu_PRES_REWIND, MainFrm::OnPresRewindUpdate)
534    EVT_UPDATE_UI(menu_PRES_REVERSE, MainFrm::OnPresReverseUpdate)
535    EVT_UPDATE_UI(menu_PRES_PLAY, MainFrm::OnPresPlayUpdate)
536    EVT_UPDATE_UI(menu_PRES_FF, MainFrm::OnPresFFUpdate)
537    EVT_UPDATE_UI(menu_PRES_FFF, MainFrm::OnPresFFFUpdate)
538    EVT_UPDATE_UI(menu_PRES_PAUSE, MainFrm::OnPresPauseUpdate)
539    EVT_UPDATE_UI(wxID_STOP, MainFrm::OnPresStopUpdate)
540    EVT_UPDATE_UI(menu_PRES_EXPORT_MOVIE, MainFrm::OnPresExportMovieUpdate)
541
542    EVT_CLOSE(MainFrm::OnClose)
543    EVT_SET_FOCUS(MainFrm::OnSetFocus)
544
545    EVT_MENU(menu_ROTATION_TOGGLE, MainFrm::OnToggleRotation)
546    EVT_MENU(menu_ROTATION_REVERSE, MainFrm::OnReverseDirectionOfRotation)
547    EVT_MENU(menu_ORIENT_MOVE_NORTH, MainFrm::OnMoveNorth)
548    EVT_MENU(menu_ORIENT_MOVE_EAST, MainFrm::OnMoveEast)
549    EVT_MENU(menu_ORIENT_MOVE_SOUTH, MainFrm::OnMoveSouth)
550    EVT_MENU(menu_ORIENT_MOVE_WEST, MainFrm::OnMoveWest)
551    EVT_MENU(menu_ORIENT_PLAN, MainFrm::OnPlan)
552    EVT_MENU(menu_ORIENT_ELEVATION, MainFrm::OnElevation)
553    EVT_MENU(menu_ORIENT_DEFAULTS, MainFrm::OnDefaults)
554    EVT_MENU(menu_VIEW_SHOW_LEGS, MainFrm::OnShowSurveyLegs)
555    EVT_MENU(menu_SPLAYS_HIDE, MainFrm::OnHideSplays)
556    EVT_MENU(menu_SPLAYS_SHOW_DASHED, MainFrm::OnShowSplaysDashed)
557    EVT_MENU(menu_SPLAYS_SHOW_FADED, MainFrm::OnShowSplaysFaded)
558    EVT_MENU(menu_SPLAYS_SHOW_NORMAL, MainFrm::OnShowSplaysNormal)
559    EVT_MENU(menu_DUPES_HIDE, MainFrm::OnHideDupes)
560    EVT_MENU(menu_DUPES_SHOW_DASHED, MainFrm::OnShowDupesDashed)
561    EVT_MENU(menu_DUPES_SHOW_FADED, MainFrm::OnShowDupesFaded)
562    EVT_MENU(menu_DUPES_SHOW_NORMAL, MainFrm::OnShowDupesNormal)
563    EVT_MENU(menu_VIEW_SHOW_CROSSES, MainFrm::OnShowCrosses)
564    EVT_MENU(menu_VIEW_SHOW_ENTRANCES, MainFrm::OnShowEntrances)
565    EVT_MENU(menu_VIEW_SHOW_FIXED_PTS, MainFrm::OnShowFixedPts)
566    EVT_MENU(menu_VIEW_SHOW_EXPORTED_PTS, MainFrm::OnShowExportedPts)
567    EVT_MENU(menu_VIEW_SHOW_NAMES, MainFrm::OnShowStationNames)
568    EVT_MENU(menu_VIEW_SHOW_OVERLAPPING_NAMES, MainFrm::OnDisplayOverlappingNames)
569    EVT_MENU(menu_COLOUR_BY_DEPTH, MainFrm::OnColourByDepth)
570    EVT_MENU(menu_COLOUR_BY_DATE, MainFrm::OnColourByDate)
571    EVT_MENU(menu_COLOUR_BY_ERROR, MainFrm::OnColourByError)
572    EVT_MENU(menu_COLOUR_BY_H_ERROR, MainFrm::OnColourByHError)
573    EVT_MENU(menu_COLOUR_BY_V_ERROR, MainFrm::OnColourByVError)
574    EVT_MENU(menu_COLOUR_BY_GRADIENT, MainFrm::OnColourByGradient)
575    EVT_MENU(menu_COLOUR_BY_LENGTH, MainFrm::OnColourByLength)
576    EVT_MENU(menu_COLOUR_BY_SURVEY, MainFrm::OnColourBySurvey)
577    EVT_MENU(menu_COLOUR_BY_STYLE, MainFrm::OnColourByStyle)
578    EVT_MENU(menu_VIEW_SHOW_SURFACE, MainFrm::OnShowSurface)
579    EVT_MENU(menu_VIEW_GRID, MainFrm::OnViewGrid)
580    EVT_MENU(menu_VIEW_BOUNDING_BOX, MainFrm::OnViewBoundingBox)
581    EVT_MENU(menu_VIEW_PERSPECTIVE, MainFrm::OnViewPerspective)
582    EVT_MENU(menu_VIEW_SMOOTH_SHADING, MainFrm::OnViewSmoothShading)
583    EVT_MENU(menu_VIEW_TEXTURED, MainFrm::OnViewTextured)
584    EVT_MENU(menu_VIEW_FOG, MainFrm::OnViewFog)
585    EVT_MENU(menu_VIEW_SMOOTH_LINES, MainFrm::OnViewSmoothLines)
586    EVT_MENU(menu_VIEW_FULLSCREEN, MainFrm::OnViewFullScreen)
587    EVT_MENU(menu_VIEW_SHOW_TUBES, MainFrm::OnToggleTubes)
588    EVT_MENU(menu_VIEW_TERRAIN, MainFrm::OnViewTerrain)
589    EVT_MENU(menu_IND_COMPASS, MainFrm::OnViewCompass)
590    EVT_MENU(menu_IND_CLINO, MainFrm::OnViewClino)
591    EVT_MENU(menu_IND_COLOUR_KEY, MainFrm::OnToggleColourKey)
592    EVT_MENU(menu_IND_SCALE_BAR, MainFrm::OnToggleScalebar)
593    EVT_MENU(menu_CTL_SIDE_PANEL, MainFrm::OnViewSidePanel)
594    EVT_MENU(menu_CTL_METRIC, MainFrm::OnToggleMetric)
595    EVT_MENU(menu_CTL_DEGREES, MainFrm::OnToggleDegrees)
596    EVT_MENU(menu_CTL_PERCENT, MainFrm::OnTogglePercent)
597    EVT_MENU(menu_CTL_REVERSE, MainFrm::OnReverseControls)
598    EVT_MENU(menu_CTL_CANCEL_DIST_LINE, MainFrm::OnCancelDistLine)
599    EVT_MENU(wxID_ABOUT, MainFrm::OnAbout)
600
601    EVT_UPDATE_UI(menu_FILE_OPEN_TERRAIN, MainFrm::OnOpenTerrainUpdate)
602    EVT_UPDATE_UI(menu_FILE_OVERLAY_GEODATA, MainFrm::OnOverlayGeodataUpdate)
603    EVT_UPDATE_UI(menu_FILE_LOG, MainFrm::OnShowLogUpdate)
604    EVT_UPDATE_UI(wxID_PRINT, MainFrm::OnPrintUpdate)
605    EVT_UPDATE_UI(menu_FILE_SCREENSHOT, MainFrm::OnScreenshotUpdate)
606    EVT_UPDATE_UI(menu_FILE_EXPORT, MainFrm::OnExportUpdate)
607    EVT_UPDATE_UI(menu_FILE_EXTEND, MainFrm::OnExtendUpdate)
608    EVT_UPDATE_UI(menu_ROTATION_TOGGLE, MainFrm::OnToggleRotationUpdate)
609    EVT_UPDATE_UI(menu_ROTATION_REVERSE, MainFrm::OnReverseDirectionOfRotationUpdate)
610    EVT_UPDATE_UI(menu_ORIENT_MOVE_NORTH, MainFrm::OnMoveNorthUpdate)
611    EVT_UPDATE_UI(menu_ORIENT_MOVE_EAST, MainFrm::OnMoveEastUpdate)
612    EVT_UPDATE_UI(menu_ORIENT_MOVE_SOUTH, MainFrm::OnMoveSouthUpdate)
613    EVT_UPDATE_UI(menu_ORIENT_MOVE_WEST, MainFrm::OnMoveWestUpdate)
614    EVT_UPDATE_UI(menu_ORIENT_PLAN, MainFrm::OnPlanUpdate)
615    EVT_UPDATE_UI(menu_ORIENT_ELEVATION, MainFrm::OnElevationUpdate)
616    EVT_UPDATE_UI(menu_ORIENT_DEFAULTS, MainFrm::OnDefaultsUpdate)
617    EVT_UPDATE_UI(menu_VIEW_SHOW_LEGS, MainFrm::OnShowSurveyLegsUpdate)
618    EVT_UPDATE_UI(menu_VIEW_SPLAYS, MainFrm::OnSplaysUpdate)
619    EVT_UPDATE_UI(menu_SPLAYS_HIDE, MainFrm::OnHideSplaysUpdate)
620    EVT_UPDATE_UI(menu_SPLAYS_SHOW_DASHED, MainFrm::OnShowSplaysDashedUpdate)
621    EVT_UPDATE_UI(menu_SPLAYS_SHOW_FADED, MainFrm::OnShowSplaysFadedUpdate)
622    EVT_UPDATE_UI(menu_SPLAYS_SHOW_NORMAL, MainFrm::OnShowSplaysNormalUpdate)
623    EVT_UPDATE_UI(menu_VIEW_DUPES, MainFrm::OnDupesUpdate)
624    EVT_UPDATE_UI(menu_DUPES_HIDE, MainFrm::OnHideDupesUpdate)
625    EVT_UPDATE_UI(menu_DUPES_SHOW_DASHED, MainFrm::OnShowDupesDashedUpdate)
626    EVT_UPDATE_UI(menu_DUPES_SHOW_FADED, MainFrm::OnShowDupesFadedUpdate)
627    EVT_UPDATE_UI(menu_DUPES_SHOW_NORMAL, MainFrm::OnShowDupesNormalUpdate)
628    EVT_UPDATE_UI(menu_VIEW_SHOW_CROSSES, MainFrm::OnShowCrossesUpdate)
629    EVT_UPDATE_UI(menu_VIEW_SHOW_ENTRANCES, MainFrm::OnShowEntrancesUpdate)
630    EVT_UPDATE_UI(menu_VIEW_SHOW_FIXED_PTS, MainFrm::OnShowFixedPtsUpdate)
631    EVT_UPDATE_UI(menu_VIEW_SHOW_EXPORTED_PTS, MainFrm::OnShowExportedPtsUpdate)
632    EVT_UPDATE_UI(menu_VIEW_SHOW_NAMES, MainFrm::OnShowStationNamesUpdate)
633    EVT_UPDATE_UI(menu_VIEW_SHOW_SURFACE, MainFrm::OnShowSurfaceUpdate)
634    EVT_UPDATE_UI(menu_VIEW_SHOW_OVERLAPPING_NAMES, MainFrm::OnDisplayOverlappingNamesUpdate)
635    EVT_UPDATE_UI(menu_VIEW_COLOUR_BY, MainFrm::OnColourByUpdate)
636    EVT_UPDATE_UI(menu_COLOUR_BY_DEPTH, MainFrm::OnColourByDepthUpdate)
637    EVT_UPDATE_UI(menu_COLOUR_BY_DATE, MainFrm::OnColourByDateUpdate)
638    EVT_UPDATE_UI(menu_COLOUR_BY_ERROR, MainFrm::OnColourByErrorUpdate)
639    EVT_UPDATE_UI(menu_COLOUR_BY_H_ERROR, MainFrm::OnColourByHErrorUpdate)
640    EVT_UPDATE_UI(menu_COLOUR_BY_V_ERROR, MainFrm::OnColourByVErrorUpdate)
641    EVT_UPDATE_UI(menu_COLOUR_BY_GRADIENT, MainFrm::OnColourByGradientUpdate)
642    EVT_UPDATE_UI(menu_COLOUR_BY_LENGTH, MainFrm::OnColourByLengthUpdate)
643    EVT_UPDATE_UI(menu_COLOUR_BY_SURVEY, MainFrm::OnColourBySurveyUpdate)
644    EVT_UPDATE_UI(menu_COLOUR_BY_STYLE, MainFrm::OnColourByStyleUpdate)
645    EVT_UPDATE_UI(menu_VIEW_GRID, MainFrm::OnViewGridUpdate)
646    EVT_UPDATE_UI(menu_VIEW_BOUNDING_BOX, MainFrm::OnViewBoundingBoxUpdate)
647    EVT_UPDATE_UI(menu_VIEW_PERSPECTIVE, MainFrm::OnViewPerspectiveUpdate)
648    EVT_UPDATE_UI(menu_VIEW_SMOOTH_SHADING, MainFrm::OnViewSmoothShadingUpdate)
649    EVT_UPDATE_UI(menu_VIEW_TEXTURED, MainFrm::OnViewTexturedUpdate)
650    EVT_UPDATE_UI(menu_VIEW_FOG, MainFrm::OnViewFogUpdate)
651    EVT_UPDATE_UI(menu_VIEW_SMOOTH_LINES, MainFrm::OnViewSmoothLinesUpdate)
652    EVT_UPDATE_UI(menu_VIEW_FULLSCREEN, MainFrm::OnViewFullScreenUpdate)
653    EVT_UPDATE_UI(menu_VIEW_SHOW_TUBES, MainFrm::OnToggleTubesUpdate)
654    EVT_UPDATE_UI(menu_VIEW_TERRAIN, MainFrm::OnViewTerrainUpdate)
655    EVT_UPDATE_UI(menu_IND_COMPASS, MainFrm::OnViewCompassUpdate)
656    EVT_UPDATE_UI(menu_IND_CLINO, MainFrm::OnViewClinoUpdate)
657    EVT_UPDATE_UI(menu_IND_COLOUR_KEY, MainFrm::OnToggleColourKeyUpdate)
658    EVT_UPDATE_UI(menu_IND_SCALE_BAR, MainFrm::OnToggleScalebarUpdate)
659    EVT_UPDATE_UI(menu_CTL_INDICATORS, MainFrm::OnIndicatorsUpdate)
660    EVT_UPDATE_UI(menu_CTL_SIDE_PANEL, MainFrm::OnViewSidePanelUpdate)
661    EVT_UPDATE_UI(menu_CTL_REVERSE, MainFrm::OnReverseControlsUpdate)
662    EVT_UPDATE_UI(menu_CTL_CANCEL_DIST_LINE, MainFrm::OnCancelDistLineUpdate)
663    EVT_UPDATE_UI(menu_CTL_METRIC, MainFrm::OnToggleMetricUpdate)
664    EVT_UPDATE_UI(menu_CTL_DEGREES, MainFrm::OnToggleDegreesUpdate)
665    EVT_UPDATE_UI(menu_CTL_PERCENT, MainFrm::OnTogglePercentUpdate)
666END_EVENT_TABLE()
667
668#if wxUSE_DRAG_AND_DROP
669class DnDFile : public wxFileDropTarget {
670    public:
671        explicit DnDFile(MainFrm *parent) : m_Parent(parent) { }
672        virtual bool OnDropFiles(wxCoord, wxCoord,
673                                 const wxArrayString &filenames);
674
675    private:
676        MainFrm * m_Parent;
677};
678
679bool
680DnDFile::OnDropFiles(wxCoord, wxCoord, const wxArrayString &filenames)
681{
682    // Load a survey file by drag-and-drop.
683    assert(filenames.GetCount() > 0);
684
685    if (filenames.GetCount() != 1) {
686        /* TRANSLATORS: error if you try to drag multiple files to the aven
687         * window */
688        wxGetApp().ReportError(wmsg(/*You may only view one 3d file at a time.*/336));
689        return false;
690    }
691
692    m_Parent->OpenFile(filenames[0]);
693    return true;
694}
695#endif
696
697MainFrm::MainFrm(const wxString& title, const wxPoint& pos, const wxSize& size) :
698    wxFrame(NULL, 101, title, pos, size, wxDEFAULT_FRAME_STYLE)
699{
700#ifdef _WIN32
701    // The peculiar name is so that the icon is the first in the file
702    // (required by Microsoft Windows for this type of icon)
703    SetIcon(wxICON(AAA_aven));
704#else
705    SetIcon(wxICON(aven));
706#endif
707
708#if defined(__WXMAC__) && wxCHECK_VERSION(3,1,0)
709    // Add a full screen button to the right upper corner of title bar under OS
710    // X 10.7 and later.
711    using_macos_full_screen_view = EnableFullScreenView();
712#endif
713    CreateMenuBar();
714    MakeToolBar();
715    CreateStatusBar(2, wxST_SIZEGRIP);
716    CreateSidePanel();
717
718    int widths[2] = { -1 /* variable width */, -1 };
719    GetStatusBar()->SetStatusWidths(2, widths);
720
721#ifdef __X__ // wxMotif or wxX11
722    int x;
723    int y;
724    GetSize(&x, &y);
725    // X seems to require a forced resize.
726    SetSize(-1, -1, x, y);
727#endif
728
729#if wxUSE_DRAG_AND_DROP
730    SetDropTarget(new DnDFile(this));
731#endif
732
733#ifdef __WXMAC__
734    m_Gfx->ForceRefresh();
735    m_Gfx->Show(true);
736#endif
737    m_Gfx->SetFocus();
738}
739
740void MainFrm::CreateMenuBar()
741{
742    // Create the menus and the menu bar.
743
744    wxMenu* filemenu = new wxMenu;
745    // wxID_OPEN stock label lacks the ellipses
746    /* TRANSLATORS: Aven menu items.  An “&” goes before the letter of any
747     * accelerator key.
748     *
749     * The string "\t" separates the menu text and any accelerator key.
750     *
751     * "File" menu.  The accelerators must be different within this group.
752     * c.f. 201, 380, 381. */
753    filemenu->Append(wxID_OPEN, wmsg(/*&Open...\tCtrl+O*/220));
754    /* TRANSLATORS: Open a "Terrain file" - i.e. a digital model of the
755     * terrain. */
756    filemenu->Append(menu_FILE_OPEN_TERRAIN, wmsg(/*Open &Terrain...*/453));
757    filemenu->Append(menu_FILE_OVERLAY_GEODATA, wmsg(/*Overlay &Geodata...*/494));
758    filemenu->AppendCheckItem(menu_FILE_LOG, wmsg(/*Show &Log*/144));
759    filemenu->AppendSeparator();
760    // wxID_PRINT stock label lacks the ellipses
761    filemenu->Append(wxID_PRINT, wmsg(/*&Print...\tCtrl+P*/380));
762    filemenu->Append(menu_FILE_PAGE_SETUP, wmsg(/*P&age Setup...*/381));
763    filemenu->AppendSeparator();
764    /* TRANSLATORS: In the "File" menu */
765    filemenu->Append(menu_FILE_SCREENSHOT, wmsg(/*&Screenshot...*/201));
766    filemenu->Append(menu_FILE_EXPORT, wmsg(/*&Export as...*/382));
767    /* TRANSLATORS: In the "File" menu - c.f. n:191 */
768    filemenu->Append(menu_FILE_EXTEND, wmsg(/*E&xtended Elevation...*/247));
769#ifndef __WXMAC__
770    // On wxMac the "Quit" menu item will be moved elsewhere, so we suppress
771    // this separator.
772    filemenu->AppendSeparator();
773#else
774    // We suppress the "Help" menu under macOS as it would otherwise end up as
775    // an empty menu, but we need to add the "About" menu item somewhere.  It
776    // really doesn't matter where as wxWidgets will move it to the "Apple"
777    // menu.
778    filemenu->Append(wxID_ABOUT);
779#endif
780    filemenu->Append(wxID_EXIT);
781
782    m_history.UseMenu(filemenu);
783    m_history.Load(*wxConfigBase::Get());
784
785    wxMenu* rotmenu = new wxMenu;
786    /* TRANSLATORS: "Rotation" menu.  The accelerators must be different within
787     * this group.  Tickable menu item which toggles auto rotation.
788     * Please don't translate "Space" - that's the shortcut key to use which
789     * wxWidgets needs to parse and it should then handle translating.
790     */
791    rotmenu->AppendCheckItem(menu_ROTATION_TOGGLE, wmsg(/*Au&to-Rotate\tSpace*/231));
792    rotmenu->AppendSeparator();
793    rotmenu->Append(menu_ROTATION_REVERSE, wmsg(/*&Reverse Direction*/234));
794
795    wxMenu* orientmenu = new wxMenu;
796    orientmenu->Append(menu_ORIENT_MOVE_NORTH, wmsg(/*View &North*/240));
797    orientmenu->Append(menu_ORIENT_MOVE_EAST, wmsg(/*View &East*/241));
798    orientmenu->Append(menu_ORIENT_MOVE_SOUTH, wmsg(/*View &South*/242));
799    orientmenu->Append(menu_ORIENT_MOVE_WEST, wmsg(/*View &West*/243));
800    orientmenu->AppendSeparator();
801    orientmenu->Append(menu_ORIENT_PLAN, wmsg(/*&Plan View*/248));
802    orientmenu->Append(menu_ORIENT_ELEVATION, wmsg(/*Ele&vation*/249));
803    orientmenu->AppendSeparator();
804    orientmenu->Append(menu_ORIENT_DEFAULTS, wmsg(/*Restore De&fault View*/254));
805
806    wxMenu* presmenu = new wxMenu;
807    presmenu->Append(menu_PRES_NEW, wmsg(/*&New Presentation*/311));
808    presmenu->Append(menu_PRES_OPEN, wmsg(/*&Open Presentation...*/312));
809    presmenu->Append(menu_PRES_SAVE, wmsg(/*&Save Presentation*/313));
810    presmenu->Append(menu_PRES_SAVE_AS, wmsg(/*Sa&ve Presentation As...*/314));
811    presmenu->AppendSeparator();
812    /* TRANSLATORS: "Mark" as in "Mark this position" */
813    presmenu->Append(menu_PRES_MARK, wmsg(/*&Mark*/315));
814    /* TRANSLATORS: "Play" as in "Play back a recording" */
815    presmenu->AppendCheckItem(menu_PRES_PLAY, wmsg(/*Pla&y*/316));
816    presmenu->Append(menu_PRES_EXPORT_MOVIE, wmsg(/*&Export as Movie...*/317));
817
818    wxMenu* viewmenu = new wxMenu;
819#ifndef PREFDLG
820    /* TRANSLATORS: Items in the "View" menu: */
821    viewmenu->AppendCheckItem(menu_VIEW_SHOW_NAMES, wmsg(/*Station &Names\tCtrl+N*/270));
822    /* TRANSLATORS: Toggles drawing of 3D passages */
823    viewmenu->AppendCheckItem(menu_VIEW_SHOW_TUBES, wmsg(/*Passage &Tubes\tCtrl+T*/346));
824    /* TRANSLATORS: Toggles drawing the surface of the Earth */
825    viewmenu->AppendCheckItem(menu_VIEW_TERRAIN, wmsg(/*Terr&ain*/449));
826    viewmenu->AppendCheckItem(menu_VIEW_SHOW_CROSSES, wmsg(/*&Crosses\tCtrl+X*/271));
827    viewmenu->AppendCheckItem(menu_VIEW_GRID, wmsg(/*&Grid\tCtrl+G*/297));
828    viewmenu->AppendCheckItem(menu_VIEW_BOUNDING_BOX, wmsg(/*&Bounding Box\tCtrl+B*/318));
829    viewmenu->AppendSeparator();
830    /* TRANSLATORS: Here a "survey leg" is a set of measurements between two
831     * "survey stations". */
832    viewmenu->AppendCheckItem(menu_VIEW_SHOW_LEGS, wmsg(/*&Underground Survey Legs\tCtrl+L*/272));
833    /* TRANSLATORS: Here a "survey leg" is a set of measurements between two
834     * "survey stations". */
835    viewmenu->AppendCheckItem(menu_VIEW_SHOW_SURFACE, wmsg(/*&Surface Survey Legs\tCtrl+F*/291));
836
837    wxMenu* splaymenu = new wxMenu;
838    /* TRANSLATORS: Item in the "Splay Legs" and "Duplicate Legs" submenus - if
839     * this is selected, such legs are not shown. */
840    splaymenu->AppendCheckItem(menu_SPLAYS_HIDE, wmsg(/*&Hide*/407));
841    /* TRANSLATORS: Item in the "Splay Legs" and "Duplicate Legs" submenus - if
842     * this is selected, aven will show such legs with dashed lines. */
843    splaymenu->AppendCheckItem(menu_SPLAYS_SHOW_DASHED, wmsg(/*&Dashed*/250));
844    /* TRANSLATORS: Item in the "Splay Legs" and "Duplicate Legs" submenus - if
845     * this is selected, aven will show such legs with less bright colours. */
846    splaymenu->AppendCheckItem(menu_SPLAYS_SHOW_FADED, wmsg(/*&Fade*/408));
847    /* TRANSLATORS: Item in the "Splay Legs" and "Duplicate Legs" submenus - if
848     * this is selected, such legs are shown the same as other legs. */
849    splaymenu->AppendCheckItem(menu_SPLAYS_SHOW_NORMAL, wmsg(/*&Show*/409));
850    viewmenu->Append(menu_VIEW_SPLAYS, wmsg(/*Spla&y Legs*/406), splaymenu);
851
852    wxMenu* dupemenu = new wxMenu;
853    dupemenu->AppendCheckItem(menu_DUPES_HIDE, wmsg(/*&Hide*/407));
854    dupemenu->AppendCheckItem(menu_DUPES_SHOW_DASHED, wmsg(/*&Dashed*/250));
855    dupemenu->AppendCheckItem(menu_DUPES_SHOW_FADED, wmsg(/*&Fade*/408));
856    dupemenu->AppendCheckItem(menu_DUPES_SHOW_NORMAL, wmsg(/*&Show*/409));
857    viewmenu->Append(menu_VIEW_DUPES, wmsg(/*&Duplicate Legs*/251), dupemenu);
858
859    viewmenu->AppendSeparator();
860    viewmenu->AppendCheckItem(menu_VIEW_SHOW_OVERLAPPING_NAMES, wmsg(/*&Overlapping Names*/273));
861
862    wxMenu* colourbymenu = new wxMenu;
863    colourbymenu->AppendCheckItem(menu_COLOUR_BY_DEPTH, wmsg(/*Colour by &Depth*/292));
864    colourbymenu->AppendCheckItem(menu_COLOUR_BY_DATE, wmsg(/*Colour by D&ate*/293));
865    colourbymenu->AppendCheckItem(menu_COLOUR_BY_ERROR, wmsg(/*Colour by &Error*/289));
866    colourbymenu->AppendCheckItem(menu_COLOUR_BY_H_ERROR, wmsg(/*Colour by &Horizontal Error*/480));
867    colourbymenu->AppendCheckItem(menu_COLOUR_BY_V_ERROR, wmsg(/*Colour by &Vertical Error*/481));
868    colourbymenu->AppendCheckItem(menu_COLOUR_BY_GRADIENT, wmsg(/*Colour by &Gradient*/85));
869    colourbymenu->AppendCheckItem(menu_COLOUR_BY_LENGTH, wmsg(/*Colour by &Length*/82));
870    colourbymenu->AppendCheckItem(menu_COLOUR_BY_SURVEY, wmsg(/*Colour by &Survey*/448));
871    colourbymenu->AppendCheckItem(menu_COLOUR_BY_STYLE, wmsg(/*Colour by St&yle*/482));
872
873    viewmenu->Append(menu_VIEW_COLOUR_BY, wmsg(/*Co&lour by*/450), colourbymenu);
874
875    viewmenu->AppendSeparator();
876    viewmenu->AppendCheckItem(menu_VIEW_SHOW_ENTRANCES, wmsg(/*Highlight &Entrances*/294));
877    viewmenu->AppendCheckItem(menu_VIEW_SHOW_FIXED_PTS, wmsg(/*Highlight &Fixed Points*/295));
878    viewmenu->AppendCheckItem(menu_VIEW_SHOW_EXPORTED_PTS, wmsg(/*Highlight E&xported Points*/296));
879    viewmenu->AppendSeparator();
880#else
881    /* TRANSLATORS: Please don't translate "Escape" - that's the shortcut key
882     * to use which wxWidgets needs to parse and it should then handle
883     * translating.
884     */
885    viewmenu-> Append(menu_VIEW_CANCEL_DIST_LINE, wmsg(/*&Cancel Measuring Line\tEscape*/281));
886#endif
887    viewmenu->AppendCheckItem(menu_VIEW_PERSPECTIVE, wmsg(/*&Perspective*/237));
888// FIXME: enable this    viewmenu->AppendCheckItem(menu_VIEW_SMOOTH_SHADING, wmsg(/*&Smooth Shading*/?!?);
889    viewmenu->AppendCheckItem(menu_VIEW_TEXTURED, wmsg(/*Textured &Walls*/238));
890    /* TRANSLATORS: Toggles OpenGL "Depth Fogging" - feel free to translate
891     * using that term instead if it gives a better translation which most
892     * users will understand. */
893    viewmenu->AppendCheckItem(menu_VIEW_FOG, wmsg(/*Fade Distant Ob&jects*/239));
894    /* TRANSLATORS: Here a "survey leg" is a set of measurements between two
895     * "survey stations". */
896    viewmenu->AppendCheckItem(menu_VIEW_SMOOTH_LINES, wmsg(/*Smoot&hed Survey Legs*/298));
897    viewmenu->AppendSeparator();
898#ifdef __WXMAC__
899    // F11 on macOS is used by the desktop (for speaker volume and/or window
900    // navigation).  The standard macOS shortcut for full screen mode is
901    // Ctrl-Command-F which in wxWidgets terms is RawCtrl+Ctrl+F.
902    wxString wxmac_fullscreen = wmsg(/*Full Screen &Mode\tF11*/356);
903    wxmac_fullscreen.Replace(wxT("\tF11"), wxT("\tRawCtrl+Ctrl+F"), false);
904    viewmenu->AppendCheckItem(menu_VIEW_FULLSCREEN, wxmac_fullscreen);
905    // FIXME: On macOS, the standard wording here is "Enter Full Screen" and
906    // "Exit Full Screen", depending whether we are in full screen mode or not,
907    // and this isn't a checked menu item.
908#else
909    viewmenu->AppendCheckItem(menu_VIEW_FULLSCREEN, wmsg(/*Full Screen &Mode\tF11*/356));
910#endif
911#ifdef PREFDLG
912    viewmenu->AppendSeparator();
913    viewmenu-> Append(wxID_PREFERENCES, wmsg(/*&Preferences...*/347));
914#endif
915
916#ifndef PREFDLG
917    wxMenu* ctlmenu = new wxMenu;
918    ctlmenu->AppendCheckItem(menu_CTL_REVERSE, wmsg(/*&Reverse Sense\tCtrl+R*/280));
919    ctlmenu->AppendSeparator();
920#ifdef __WXGTK__
921    // wxGTK (at least with GTK+ v2.24), if we specify a short-cut here then
922    // the key handler isn't called, so we can't exit full screen mode on
923    // Escape.  wxGTK doesn't actually show the "Escape" shortcut text in the
924    // menu item, so removing it doesn't make any visual difference, and doing
925    // so allows Escape to still cancel the measuring line, but also serve to
926    // exit full screen mode if no measuring line is shown.
927    wxString wxgtk_cancelline = wmsg(/*&Cancel Measuring Line\tEscape*/281);
928    wxgtk_cancelline.Replace(wxT("\tEscape"), wxT(""), false);
929    ctlmenu->Append(menu_CTL_CANCEL_DIST_LINE, wxgtk_cancelline);
930#else
931    // With wxMac and wxMSW, we can have the short-cut on the menu and still
932    // have Escape handled by the key handler to exit full screen mode.
933    ctlmenu->Append(menu_CTL_CANCEL_DIST_LINE, wmsg(/*&Cancel Measuring Line\tEscape*/281));
934#endif
935    ctlmenu->AppendSeparator();
936    wxMenu* indmenu = new wxMenu;
937    indmenu->AppendCheckItem(menu_IND_COMPASS, wmsg(/*&Compass*/274));
938    indmenu->AppendCheckItem(menu_IND_CLINO, wmsg(/*C&linometer*/275));
939    /* TRANSLATORS: The "Colour Key" is the thing in aven showing which colour
940     * corresponds to which depth, date, survey closure error, etc. */
941    indmenu->AppendCheckItem(menu_IND_COLOUR_KEY, wmsg(/*Colour &Key*/276));
942    indmenu->AppendCheckItem(menu_IND_SCALE_BAR, wmsg(/*&Scale Bar*/277));
943    ctlmenu->Append(menu_CTL_INDICATORS, wmsg(/*&Indicators*/299), indmenu);
944    ctlmenu->AppendCheckItem(menu_CTL_SIDE_PANEL, wmsg(/*&Side Panel*/337));
945    ctlmenu->AppendSeparator();
946    ctlmenu->AppendCheckItem(menu_CTL_METRIC, wmsg(/*&Metric*/342));
947    ctlmenu->AppendCheckItem(menu_CTL_DEGREES, wmsg(/*&Degrees*/343));
948    ctlmenu->AppendCheckItem(menu_CTL_PERCENT, wmsg(/*&Percent*/430));
949#endif
950
951    wxMenuBar* menubar = new wxMenuBar();
952    /* TRANSLATORS: Aven menu titles.  An “&” goes before the letter of any
953     * accelerator key.  The accelerators must be different within this group
954     */
955    menubar->Append(filemenu, wmsg(/*&File*/210));
956    menubar->Append(rotmenu, wmsg(/*&Rotation*/211));
957    menubar->Append(orientmenu, wmsg(/*&Orientation*/212));
958    menubar->Append(viewmenu, wmsg(/*&View*/213));
959#ifndef PREFDLG
960    menubar->Append(ctlmenu, wmsg(/*&Controls*/214));
961#endif
962    // TRANSLATORS: "Presentation" in the sense of a talk with a slideshow -
963    // the items in this menu allow the user to animate between preset
964    // views.
965    menubar->Append(presmenu, wmsg(/*&Presentation*/216));
966#ifndef __WXMAC__
967    // On wxMac the "About" menu item will be moved elsewhere, so we suppress
968    // this menu since it will then be empty.
969    wxMenu* helpmenu = new wxMenu;
970    helpmenu->Append(wxID_ABOUT);
971
972    menubar->Append(helpmenu, wmsg(/*&Help*/215));
973#endif
974    SetMenuBar(menubar);
975}
976
977void MainFrm::MakeToolBar()
978{
979    // Make the toolbar.
980
981#ifdef USING_GENERIC_TOOLBAR
982    // This OS-X-specific code is only needed to stop the toolbar icons getting
983    // scaled up, which just makes them look nasty and fuzzy.  Once we have
984    // larger versions of the icons, we can drop this code.
985    wxSystemOptions::SetOption(wxT("mac.toolbar.no-native"), 1);
986    wxToolBar* toolbar = new wxToolBar(this, wxID_ANY, wxDefaultPosition,
987                                       wxDefaultSize, wxNO_BORDER|wxTB_FLAT|wxTB_NODIVIDER|wxTB_NOALIGN);
988    wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
989    sizer->Add(toolbar, 0, wxEXPAND);
990    SetSizer(sizer);
991#else
992    wxToolBar* toolbar = wxFrame::CreateToolBar();
993#endif
994
995#ifndef __WXGTK20__
996    toolbar->SetMargins(5, 5);
997#endif
998
999    // FIXME: TRANSLATE tooltips
1000    toolbar->AddTool(wxID_OPEN, wxT("Open"), TOOL(open), wxT("Open a survey file for viewing"));
1001    toolbar->AddTool(menu_PRES_OPEN, wxT("Open presentation"), TOOL(open_pres), wxT("Open a presentation"));
1002    toolbar->AddCheckTool(menu_FILE_LOG, wxT("View log"), TOOL(log), wxNullBitmap, wxT("View log from processing survey data"));
1003    toolbar->AddSeparator();
1004    toolbar->AddCheckTool(menu_ROTATION_TOGGLE, wxT("Toggle rotation"), TOOL(rotation), wxNullBitmap, wxT("Toggle rotation"));
1005    toolbar->AddTool(menu_ORIENT_PLAN, wxT("Plan"), TOOL(plan), wxT("Switch to plan view"));
1006    toolbar->AddTool(menu_ORIENT_ELEVATION, wxT("Elevation"), TOOL(elevation), wxT("Switch to elevation view"));
1007    toolbar->AddTool(menu_ORIENT_DEFAULTS, wxT("Default view"), TOOL(defaults), wxT("Restore default view"));
1008    toolbar->AddSeparator();
1009    toolbar->AddCheckTool(menu_VIEW_SHOW_NAMES, wxT("Names"), TOOL(names), wxNullBitmap, wxT("Show station names"));
1010    toolbar->AddCheckTool(menu_VIEW_SHOW_CROSSES, wxT("Crosses"), TOOL(crosses), wxNullBitmap, wxT("Show crosses on stations"));
1011    toolbar->AddCheckTool(menu_VIEW_SHOW_ENTRANCES, wxT("Entrances"), TOOL(entrances), wxNullBitmap, wxT("Highlight entrances"));
1012    toolbar->AddCheckTool(menu_VIEW_SHOW_FIXED_PTS, wxT("Fixed points"), TOOL(fixed_pts), wxNullBitmap, wxT("Highlight fixed points"));
1013    toolbar->AddCheckTool(menu_VIEW_SHOW_EXPORTED_PTS, wxT("Exported points"), TOOL(exported_pts), wxNullBitmap, wxT("Highlight exported stations"));
1014    toolbar->AddSeparator();
1015    toolbar->AddCheckTool(menu_VIEW_SHOW_LEGS, wxT("Underground legs"), TOOL(ug_legs), wxNullBitmap, wxT("Show underground surveys"));
1016    toolbar->AddCheckTool(menu_VIEW_SHOW_SURFACE, wxT("Surface legs"), TOOL(surface_legs), wxNullBitmap, wxT("Show surface surveys"));
1017    toolbar->AddCheckTool(menu_VIEW_SHOW_TUBES, wxT("Tubes"), TOOL(tubes), wxNullBitmap, wxT("Show passage tubes"));
1018    toolbar->AddCheckTool(menu_VIEW_TERRAIN, wxT("Terrain"), TOOL(solid_surface), wxNullBitmap, wxT("Show terrain"));
1019    toolbar->AddSeparator();
1020    toolbar->AddCheckTool(menu_PRES_FREWIND, wxT("Fast Rewind"), TOOL(pres_frew), wxNullBitmap, wxT("Very Fast Rewind"));
1021    toolbar->AddCheckTool(menu_PRES_REWIND, wxT("Rewind"), TOOL(pres_rew), wxNullBitmap, wxT("Fast Rewind"));
1022    toolbar->AddCheckTool(menu_PRES_REVERSE, wxT("Backwards"), TOOL(pres_go_back), wxNullBitmap, wxT("Play Backwards"));
1023    toolbar->AddCheckTool(menu_PRES_PAUSE, wxT("Pause"), TOOL(pres_pause), wxNullBitmap, wxT("Pause"));
1024    toolbar->AddCheckTool(menu_PRES_PLAY, wxT("Go"), TOOL(pres_go), wxNullBitmap, wxT("Play"));
1025    toolbar->AddCheckTool(menu_PRES_FF, wxT("FF"), TOOL(pres_ff), wxNullBitmap, wxT("Fast Forward"));
1026    toolbar->AddCheckTool(menu_PRES_FFF, wxT("Very FF"), TOOL(pres_fff), wxNullBitmap, wxT("Very Fast Forward"));
1027    toolbar->AddTool(wxID_STOP, wxT("Stop"), TOOL(pres_stop), wxT("Stop"));
1028
1029    toolbar->AddSeparator();
1030    m_FindBox = new wxSearchCtrl(toolbar, textctrl_FIND);
1031    m_FindBox->SetSize(160, -1);
1032    // TRANSLATORS: Placeholder text in aven's station search control.
1033    m_FindBox->SetDescriptiveText(wmsg(/*Find stations*/333));
1034    toolbar->AddControl(m_FindBox);
1035
1036    auto z_stretch = new wxSpinCtrlDouble(toolbar, spinctrl_Z_STRETCH,
1037                                          wxEmptyString,
1038                                          wxDefaultPosition, wxDefaultSize,
1039                                          wxSP_ARROW_KEYS|wxALIGN_RIGHT);
1040    z_stretch->SetValue(1.0);
1041    z_stretch->SetRange(0.1, 10.0);
1042    z_stretch->SetIncrement(0.1);
1043    toolbar->AddSeparator();
1044    toolbar->AddControl(z_stretch);
1045
1046    toolbar->Realize();
1047}
1048
1049void MainFrm::CreateSidePanel()
1050{
1051    m_Splitter = new AvenSplitterWindow(this);
1052#ifdef USING_GENERIC_TOOLBAR
1053    // This OS-X-specific code is only needed to stop the toolbar icons getting
1054    // scaled up, which just makes them look nasty and fuzzy.  Once we have
1055    // larger versions of the icons, we can drop this code.
1056    GetSizer()->Add(m_Splitter, 1, wxEXPAND);
1057    Layout();
1058#endif
1059
1060    m_Notebook = new wxNotebook(m_Splitter, 400, wxDefaultPosition,
1061                                wxDefaultSize,
1062                                wxBK_BOTTOM);
1063    m_Notebook->Show(false);
1064
1065    wxPanel * panel = new wxPanel(m_Notebook);
1066    m_Tree = new AvenTreeCtrl(this, panel);
1067
1068//    m_RegexpCheckBox = new wxCheckBox(find_panel, -1,
1069//                                    msg(/*Regular expression*/));
1070
1071    wxBoxSizer *panel_sizer = new wxBoxSizer(wxVERTICAL);
1072    panel_sizer->Add(m_Tree, 1, wxALL | wxEXPAND, 2);
1073    panel->SetAutoLayout(true);
1074    panel->SetSizer(panel_sizer);
1075//    panel_sizer->SetSizeHints(panel);
1076
1077    m_Control = new GUIControl();
1078    m_Gfx = new GfxCore(this, m_Splitter, m_Control);
1079    m_Control->SetView(m_Gfx);
1080
1081    // Presentation panel:
1082    wxPanel * prespanel = new wxPanel(m_Notebook);
1083
1084    m_PresList = new AvenPresList(this, prespanel, m_Gfx);
1085
1086    wxBoxSizer *pres_panel_sizer = new wxBoxSizer(wxVERTICAL);
1087    pres_panel_sizer->Add(m_PresList, 1, wxALL | wxEXPAND, 2);
1088    prespanel->SetAutoLayout(true);
1089    prespanel->SetSizer(pres_panel_sizer);
1090
1091    // Overall tabbed structure:
1092    // FIXME: this assumes images are 15x15
1093    wxImageList* image_list = new wxImageList(15, 15);
1094    image_list->Add(TOOL(survey_tree));
1095    image_list->Add(TOOL(pres_tree));
1096    m_Notebook->SetImageList(image_list);
1097    /* TRANSLATORS: labels for tabbed side panel this is for the tab with the
1098     * tree hierarchy of survey station names */
1099    m_Notebook->AddPage(panel, wmsg(/*Surveys*/376), true, 0);
1100    m_Notebook->AddPage(prespanel, wmsg(/*Presentation*/377), false, 1);
1101
1102    m_Splitter->Initialize(m_Gfx);
1103}
1104
1105bool MainFrm::LoadData(const wxString& file, const wxString& prefix)
1106{
1107    // Load survey data from file, centre the dataset around the origin,
1108    // and prepare the data for drawing.
1109
1110#if 0
1111    wxStopWatch timer;
1112    timer.Start();
1113#endif
1114
1115    int err_msg_code = Model::Load(file, prefix);
1116    if (err_msg_code) {
1117        wxString m = wxString::Format(wmsg(err_msg_code), file.c_str());
1118        wxGetApp().ReportError(m);
1119        return false;
1120    }
1121
1122    // Update window title.
1123    SetTitle(GetSurveyTitle() + " - " APP_NAME);
1124
1125    // Sort the labels ready for filling the tree.
1126    SortLabelsByName();
1127
1128    // Fill the tree of stations and prefixes.
1129    wxString root_name = wxFileNameFromPath(file);
1130    if (!prefix.empty()) {
1131        root_name += " (";
1132        root_name += prefix;
1133        root_name += ")";
1134    }
1135    m_Tree->FillTree(root_name);
1136
1137    // Sort labels so that entrances are displayed in preference,
1138    // then fixed points, then exported points, then other points.
1139    //
1140    // Also sort by leaf name so that we'll tend to choose labels
1141    // from different surveys, rather than labels from surveys which
1142    // are earlier in the list.
1143    SortLabelsByPlotOrder();
1144
1145    if (!m_FindBox->GetValue().empty()) {
1146        // Highlight any stations matching the current search.
1147        DoFind();
1148    }
1149
1150    m_FileProcessed = file;
1151
1152    return true;
1153}
1154
1155#if 0
1156// Run along a newly read in traverse and make up plausible LRUD where
1157// it is missing.
1158void
1159MainFrm::FixLRUD(traverse & centreline)
1160{
1161    assert(centreline.size() > 1);
1162
1163    double last_size = 0;
1164    vector<PointInfo>::iterator i = centreline.begin();
1165    while (i != centreline.end()) {
1166        // Get the coordinates of this vertex.
1167        Point & pt_v = *i++;
1168        double size;
1169
1170        if (i != centreline.end()) {
1171            double h = sqrd(i->GetX() - pt_v.GetX()) +
1172                       sqrd(i->GetY() - pt_v.GetY());
1173            double v = sqrd(i->GetZ() - pt_v.GetZ());
1174            if (h + v > 30.0 * 30.0) {
1175                double scale = 30.0 / sqrt(h + v);
1176                h *= scale;
1177                v *= scale;
1178            }
1179            size = sqrt(h + v / 9);
1180            size /= 4;
1181            if (i == centreline.begin() + 1) {
1182                // First segment.
1183                last_size = size;
1184            } else {
1185                // Intermediate segment.
1186                swap(size, last_size);
1187                size += last_size;
1188                size /= 2;
1189            }
1190        } else {
1191            // Last segment.
1192            size = last_size;
1193        }
1194
1195        double & l = pt_v.l;
1196        double & r = pt_v.r;
1197        double & u = pt_v.u;
1198        double & d = pt_v.d;
1199
1200        if (l == 0 && r == 0 && u == 0 && d == 0) {
1201            l = r = u = d = -size;
1202        } else {
1203            if (l < 0 && r < 0) {
1204                l = r = -size;
1205            } else if (l < 0) {
1206                l = -(2 * size - r);
1207                if (l >= 0) l = -0.01;
1208            } else if (r < 0) {
1209                r = -(2 * size - l);
1210                if (r >= 0) r = -0.01;
1211            }
1212            if (u < 0 && d < 0) {
1213                u = d = -size;
1214            } else if (u < 0) {
1215                u = -(2 * size - d);
1216                if (u >= 0) u = -0.01;
1217            } else if (d < 0) {
1218                d = -(2 * size - u);
1219                if (d >= 0) d = -0.01;
1220            }
1221        }
1222    }
1223}
1224#endif
1225
1226void MainFrm::OnMRUFile(wxCommandEvent& event)
1227{
1228    wxString f(m_history.GetHistoryFile(event.GetId() - wxID_FILE1));
1229    if (!f.empty()) OpenFile(f);
1230}
1231
1232void MainFrm::AddToFileHistory(const wxString & file)
1233{
1234    if (wxIsAbsolutePath(file)) {
1235        m_history.AddFileToHistory(file);
1236    } else {
1237        wxString abs = wxGetCwd();
1238        abs += wxCONFIG_PATH_SEPARATOR;
1239        abs += file;
1240        m_history.AddFileToHistory(abs);
1241    }
1242    wxConfigBase *b = wxConfigBase::Get();
1243    m_history.Save(*b);
1244    b->Flush();
1245}
1246
1247void MainFrm::OpenFile(const wxString& file, const wxString& survey)
1248{
1249    wxBusyCursor hourglass;
1250
1251    // Check if this is an unprocessed survey data file.
1252    if (file.length() > 4 && file[file.length() - 4] == '.') {
1253        wxString ext(file, file.length() - 3, 3);
1254        ext.MakeLower();
1255        if (ext == wxT("svx") || ext == wxT("dat") || ext == wxT("mak") ||
1256            ext == wxT("clp") || ext == wxT("srv") || ext == wxT("wpj")) {
1257            CavernLogWindow * log = new CavernLogWindow(this, survey, m_Splitter);
1258            wxWindow * win = m_Splitter->GetWindow1();
1259            m_Splitter->ReplaceWindow(win, log);
1260            win->Show(false);
1261            if (m_Splitter->GetWindow2() == NULL) {
1262                if (win != m_Gfx) win->Destroy();
1263            } else {
1264                if (m_Splitter->IsSplit()) m_Splitter->Unsplit();
1265            }
1266
1267            if (wxFileExists(file)) AddToFileHistory(file);
1268            log->process(file);
1269            // Log window will tell us to load file if it successfully completes.
1270            return;
1271        }
1272    }
1273
1274    if (!LoadData(file, survey))
1275        return;
1276    AddToFileHistory(file);
1277    InitialiseAfterLoad(file, survey);
1278
1279    // If aven is showing the log for a .svx file and you load a .3d file, then
1280    // at this point m_Log will be the log window for the .svx file, so destroy
1281    // it - it should never legitimately be set if we get here.
1282    if (m_Log) {
1283        m_Log->Destroy();
1284        m_Log = NULL;
1285    }
1286}
1287
1288void MainFrm::InitialiseAfterLoad(const wxString & file, const wxString & prefix)
1289{
1290    if (m_SashPosition < 0) {
1291        // Calculate sane default width for side panel.
1292        int x;
1293        int y;
1294        GetClientSize(&x, &y);
1295        if (x < 600)
1296            x /= 3;
1297        else if (x < 1000)
1298            x = 200;
1299        else
1300            x /= 5;
1301        m_SashPosition = x;
1302    }
1303
1304    // Do this before we potentially delete the log window which may own the
1305    // wxString which parameter file refers to!
1306    bool same_file = (file == m_File);
1307    if (!same_file)
1308        m_File = file;
1309    m_Survey = prefix;
1310
1311    wxWindow * win = NULL;
1312    if (m_Splitter->GetWindow2() == NULL) {
1313        win = m_Splitter->GetWindow1();
1314        if (win == m_Gfx) win = NULL;
1315    }
1316
1317    if (!IsFullScreen()) {
1318        m_Splitter->SplitVertically(m_Notebook, m_Gfx, m_SashPosition);
1319    } else {
1320        was_showing_sidepanel_before_fullscreen = true;
1321    }
1322
1323    m_Gfx->Initialise(same_file);
1324
1325    if (win) {
1326        // FIXME: check it actually is the log window!
1327        if (m_Log && m_Log != win)
1328            m_Log->Destroy();
1329        m_Log = win;
1330        m_Log->Show(false);
1331    }
1332
1333    if (!IsFullScreen()) {
1334        m_Notebook->Show(true);
1335    }
1336
1337    m_Gfx->Show(true);
1338    m_Gfx->SetFocus();
1339}
1340
1341void MainFrm::HideLog(wxWindow * log_window)
1342{
1343    if (!IsFullScreen()) {
1344        m_Splitter->SplitVertically(m_Notebook, m_Gfx, m_SashPosition);
1345    }
1346
1347    m_Log = log_window;
1348    m_Log->Show(false);
1349
1350    if (!IsFullScreen()) {
1351        m_Notebook->Show(true);
1352    }
1353
1354    m_Gfx->Show(true);
1355    m_Gfx->SetFocus();
1356}
1357
1358//
1359//  UI event handlers
1360//
1361
1362// For Unix we want "*.svx;*.SVX" while for Windows we only want "*.svx".
1363#ifdef _WIN32
1364# define CASE(X)
1365#else
1366# define CASE(X) ";" X
1367#endif
1368
1369void MainFrm::OnOpen(wxCommandEvent&)
1370{
1371#ifdef __WXMOTIF__
1372    wxString filetypes = wxT("*.3d");
1373#else
1374    wxString filetypes;
1375    filetypes.Printf(wxT("%s|*.3d;*.svx;*.plt;*.plf;*.dat;*.mak;*.clp;*.adj;*.sht;*.una;*.xyz"
1376                     CASE("*.3D;*.SVX;*.PLT;*.PLF;*.DAT;*.MAK;*.CLP;*.ADJ;*.SHT;*.UNA;*.XYZ")
1377                     "|%s|*.3d" CASE("*.3D")
1378                     "|%s|*.svx" CASE("*.SVX")
1379                     "|%s|*.plt;*.plf" CASE("*.PLT;*.PLF")
1380                     "|%s|*.mak" CASE("*.MAK")
1381                     "|%s|*.dat" CASE("*.DAT")
1382                     "|%s|*.clp" CASE("*.CLP")
1383                     "|%s|*.wpj" CASE("*.WPJ")
1384                     "|%s|*.srv" CASE("*.SRV")
1385                     "|%s|*.adj;*.sht;*.una;*.xyz" CASE("*.ADJ;*.SHT;*.UNA;*.XYZ")
1386                     "|%s|%s"),
1387                     /* TRANSLATORS: Here "survey" is a "cave map" rather than
1388                      * list of questions - it should be translated to the
1389                      * terminology that cavers using the language would use.
1390                      */
1391                     wmsg(/*All survey files*/229).c_str(),
1392                     /* TRANSLATORS: Survex is the name of the software, and "3d" refers to a
1393                      * file extension, so neither should be translated. */
1394                     wmsg(/*Survex 3d files*/207).c_str(),
1395                     /* TRANSLATORS: Survex is the name of the software, and "svx" refers to a
1396                      * file extension, so neither should be translated. */
1397                     wmsg(/*Survex svx files*/329).c_str(),
1398                     /* TRANSLATORS: "Compass" as in Larry Fish’s cave
1399                      * surveying package, so probably shouldn’t be translated
1400                      */
1401                     wmsg(/*Compass PLT files*/324).c_str(),
1402                     /* TRANSLATORS: "Compass" as in Larry Fish’s cave
1403                      * surveying package, so should not be translated
1404                      */
1405                     wmsg(/*Compass MAK files*/330).c_str(),
1406                     /* TRANSLATORS: "Compass" as in Larry Fish’s cave
1407                      * surveying package, so should not be translated
1408                      */
1409                     wmsg(/*Compass DAT files*/490).c_str(),
1410                     /* TRANSLATORS: "Compass" as in Larry Fish’s cave
1411                      * surveying package, so should not be translated
1412                      */
1413                     wmsg(/*Compass CLP files*/491).c_str(),
1414                     /* TRANSLATORS: "Walls" is David McKenzie's cave
1415                      * surveying package, so should not be translated
1416                      */
1417                     wmsg(/*Walls project files*/504).c_str(),
1418                     /* TRANSLATORS: "Walls" is David McKenzie's cave
1419                      * surveying package, so should not be translated
1420                      */
1421                     wmsg(/*Walls survey data files*/505).c_str(),
1422                     /* TRANSLATORS: "CMAP" is Bob Thrun’s cave surveying
1423                      * package, so don’t translate it. */
1424                     wmsg(/*CMAP XYZ files*/325).c_str(),
1425                     wmsg(/*All files*/208).c_str(),
1426                     wxFileSelectorDefaultWildcardStr);
1427#endif
1428    /* TRANSLATORS: Here "survey" is a "cave map" rather than list of questions
1429     * - it should be translated to the terminology that cavers using the
1430     * language would use.
1431     *
1432     * File->Open dialog: */
1433    wxFileDialog dlg(this, wmsg(/*Select a survey file to view*/206),
1434                     wxString(), wxString(),
1435                     filetypes, wxFD_OPEN|wxFD_FILE_MUST_EXIST);
1436    if (dlg.ShowModal() == wxID_OK) {
1437        OpenFile(dlg.GetPath());
1438    }
1439}
1440
1441void MainFrm::OnOpenTerrain(wxCommandEvent&)
1442{
1443    if (!m_Gfx) return;
1444
1445    if (GetCSProj().empty()) {
1446        wxMessageBox(wxT("No coordinate system specified in survey data"));
1447        return;
1448    }
1449
1450#ifdef __WXMOTIF__
1451    wxString filetypes = wxT("*.*");
1452#else
1453    wxString filetypes;
1454    filetypes.Printf(wxT("%s|*.bil;*.hgt;*.zip" CASE("*.BIL;*.HGT;*.ZIP")
1455                     "|%s|%s"),
1456                     wmsg(/*Terrain files*/452).c_str(),
1457                     wmsg(/*All files*/208).c_str(),
1458                     wxFileSelectorDefaultWildcardStr);
1459#endif
1460    /* TRANSLATORS: "Terrain file" being a digital model of the terrain (e.g. a
1461     * grid of height values). */
1462    wxFileDialog dlg(this, wmsg(/*Select a terrain file to view*/451),
1463                     wxString(), wxString(),
1464                     filetypes, wxFD_OPEN|wxFD_FILE_MUST_EXIST);
1465    if (dlg.ShowModal() == wxID_OK && m_Gfx->LoadDEM(dlg.GetPath())) {
1466        if (!m_Gfx->DisplayingTerrain()) m_Gfx->ToggleTerrain();
1467    }
1468}
1469
1470void MainFrm::OnOverlayGeodata(wxCommandEvent&)
1471{
1472#ifdef HAVE_GDAL
1473    if (!m_Gfx) return;
1474
1475    if (GetCSProj().empty()) {
1476        wxMessageBox(wxT("No coordinate system specified in survey data"));
1477        return;
1478    }
1479
1480#ifdef __WXMOTIF__
1481    wxString filetypes = wxT("*.*");
1482#else
1483    wxString filetypes;
1484    // FIXME: Add more extensions here?
1485    filetypes.Printf(wxT("%s|*.gpx;*.kml;*.geojson;*.json;*.shp"
1486                       CASE("*.GPX;*.KML;*.GEOJSON;*.JSON;*.SHP")
1487                     "|%s|%s"),
1488                     wmsg(/*Geodata files*/495).c_str(),
1489                     wmsg(/*All files*/208).c_str(),
1490                     wxFileSelectorDefaultWildcardStr);
1491#endif
1492    wxFileDialog dlg(this, wmsg(/*Select a geodata file to overlay*/496),
1493                     wxString(), wxString(),
1494                     filetypes, wxFD_OPEN|wxFD_FILE_MUST_EXIST);
1495    if (dlg.ShowModal() == wxID_OK) {
1496        m_Tree->AddOverlay(dlg.GetPath());
1497        m_Gfx->InvalidateOverlays();
1498    }
1499#else
1500    wxMessageBox(wxT("GDAL support not enabled in this build"),
1501                 wxT("Aven GDAL support"),
1502                 wxOK | wxICON_INFORMATION);
1503#endif
1504}
1505
1506void MainFrm::OnShowLog(wxCommandEvent&)
1507{
1508    if (!m_Log) {
1509        HideLog(m_Splitter->GetWindow1());
1510        return;
1511    }
1512    wxWindow * win = m_Splitter->GetWindow1();
1513    m_Splitter->ReplaceWindow(win, m_Log);
1514    win->Show(false);
1515    if (m_Splitter->IsSplit()) {
1516        m_SashPosition = m_Splitter->GetSashPosition(); // save width of panel
1517        m_Splitter->Unsplit();
1518    }
1519    m_Log->Show(true);
1520    m_Log->SetFocus();
1521    m_Log = NULL;
1522}
1523
1524void MainFrm::OnScreenshot(wxCommandEvent&)
1525{
1526    wxString baseleaf;
1527    wxFileName::SplitPath(m_File, NULL, NULL, &baseleaf, NULL, wxPATH_NATIVE);
1528    /* TRANSLATORS: title of the save screenshot dialog */
1529    wxFileDialog dlg(this, wmsg(/*Save Screenshot*/321), wxString(),
1530                     baseleaf + wxT(".png"),
1531                     wxT("*.png"), wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
1532    if (dlg.ShowModal() == wxID_OK) {
1533        static bool png_handled = false;
1534        if (!png_handled) {
1535#if 0 // FIXME : enable this to allow other export formats...
1536            ::wxInitAllImageHandlers();
1537#else
1538            wxImage::AddHandler(new wxPNGHandler);
1539#endif
1540            png_handled = true;
1541        }
1542        if (!m_Gfx->SaveScreenshot(dlg.GetPath(), wxBITMAP_TYPE_PNG)) {
1543            wxGetApp().ReportError(wxString::Format(wmsg(/*Error writing to file “%s”*/7), dlg.GetPath().c_str()));
1544        }
1545    }
1546}
1547
1548void MainFrm::OnScreenshotUpdate(wxUpdateUIEvent& event)
1549{
1550    event.Enable(!m_File.empty());
1551}
1552
1553void MainFrm::OnFilePreferences(wxCommandEvent&)
1554{
1555#ifdef PREFDLG
1556    m_PrefsDlg = new PrefsDlg(m_Gfx, this);
1557    m_PrefsDlg->Show(true);
1558#endif
1559}
1560
1561void MainFrm::OnPrint(wxCommandEvent&)
1562{
1563    m_Gfx->OnPrint(m_File, GetSurveyTitle(), GetDateString());
1564}
1565
1566void MainFrm::PrintAndExit()
1567{
1568    m_Gfx->OnPrint(m_File, GetSurveyTitle(), GetDateString(), true);
1569}
1570
1571void MainFrm::OnPageSetup(wxCommandEvent&)
1572{
1573    wxPageSetupDialog dlg(this, wxGetApp().GetPageSetupDialogData());
1574    if (dlg.ShowModal() == wxID_OK) {
1575        wxGetApp().SetPageSetupDialogData(dlg.GetPageSetupData());
1576    }
1577}
1578
1579void MainFrm::OnExport(wxCommandEvent&)
1580{
1581    m_Gfx->OnExport(m_File, GetSurveyTitle(), GetDateString());
1582}
1583
1584void MainFrm::OnExtend(wxCommandEvent&)
1585{
1586    wxString output = m_Survey;
1587    if (output.empty()) {
1588        wxFileName::SplitPath(m_File, NULL, NULL, &output, NULL, wxPATH_NATIVE);
1589    }
1590    output += wxT("_extend.3d");
1591    {
1592#ifdef __WXMOTIF__
1593        wxString ext(wxT("*.3d"));
1594#else
1595        /* TRANSLATORS: Survex is the name of the software, and "3d" refers to a
1596         * file extension, so neither should be translated. */
1597        wxString ext = wmsg(/*Survex 3d files*/207);
1598        ext += wxT("|*.3d");
1599#endif
1600        wxFileDialog dlg(this, wmsg(/*Select an output filename*/319),
1601                         wxString(), output, ext,
1602                         wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
1603        if (dlg.ShowModal() != wxID_OK) return;
1604        output = dlg.GetPath();
1605    }
1606    wxString cmd = get_command_path(L"extend");
1607    cmd = escape_for_shell(cmd, false);
1608    if (!m_Survey.empty()) {
1609        cmd += wxT(" --survey=");
1610        cmd += escape_for_shell(m_Survey, false);
1611    }
1612    cmd += wxT(" --show-breaks ");
1613    cmd += escape_for_shell(m_FileProcessed, true);
1614    cmd += wxT(" ");
1615    cmd += escape_for_shell(output, true);
1616    if (wxExecute(cmd, wxEXEC_SYNC) < 0) {
1617        wxString m;
1618        m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
1619        m += wxT(" (");
1620        m += wxString(strerror(errno), wxConvUTF8);
1621        m += wxT(')');
1622        wxGetApp().ReportError(m);
1623        return;
1624    }
1625    if (LoadData(output, wxString()))
1626        InitialiseAfterLoad(output, wxString());
1627}
1628
1629void MainFrm::OnQuit(wxCommandEvent&)
1630{
1631    if (m_PresList->Modified()) {
1632        // FIXME: better to ask "Do you want to save your changes?" and offer [Save] [Discard] [Cancel]
1633        /* TRANSLATORS: and the question in that box */
1634        if (wxMessageBox(wmsg(/*The current presentation has been modified.  Abandon unsaved changes?*/327),
1635                         /* TRANSLATORS: title of message box */
1636                         wmsg(/*Modified Presentation*/326),
1637                         wxOK|wxCANCEL|wxICON_QUESTION) == wxCANCEL) {
1638            return;
1639        }
1640    }
1641    wxConfigBase *b = wxConfigBase::Get();
1642    if (IsFullScreen()) {
1643        b->Write(wxT("width"), -2);
1644        b->DeleteEntry(wxT("height"));
1645    } else if (IsMaximized()) {
1646        b->Write(wxT("width"), -1);
1647        b->DeleteEntry(wxT("height"));
1648    } else {
1649        int width, height;
1650        GetSize(&width, &height);
1651        b->Write(wxT("width"), width);
1652        b->Write(wxT("height"), height);
1653    }
1654    b->Flush();
1655    exit(0);
1656}
1657
1658void MainFrm::OnClose(wxCloseEvent&)
1659{
1660    wxCommandEvent dummy;
1661    OnQuit(dummy);
1662}
1663
1664void MainFrm::OnAbout(wxCommandEvent&)
1665{
1666#ifdef __WXMAC__
1667    // GetIcon() returns an invalid wxIcon under macOS.
1668    AboutDlg dlg(this, wxICON(aven));
1669#else
1670    AboutDlg dlg(this, GetIcon());
1671#endif
1672    dlg.Centre();
1673    dlg.ShowModal();
1674}
1675
1676void MainFrm::UpdateStatusBar()
1677{
1678    if (!here_text.empty()) {
1679        GetStatusBar()->SetStatusText(here_text);
1680        GetStatusBar()->SetStatusText(dist_text, 1);
1681    } else if (!coords_text.empty()) {
1682        GetStatusBar()->SetStatusText(coords_text);
1683        GetStatusBar()->SetStatusText(distfree_text, 1);
1684    } else {
1685        GetStatusBar()->SetStatusText(wxString());
1686        GetStatusBar()->SetStatusText(wxString(), 1);
1687    }
1688}
1689
1690void MainFrm::ClearTreeSelection()
1691{
1692    m_Tree->UnselectAll();
1693    m_Gfx->SetThere();
1694    ShowInfo();
1695}
1696
1697void MainFrm::ClearCoords()
1698{
1699    if (!coords_text.empty()) {
1700        coords_text = wxString();
1701        UpdateStatusBar();
1702    }
1703}
1704
1705void MainFrm::SetCoords(const Vector3 &v)
1706{
1707    double x = v.GetX();
1708    double y = v.GetY();
1709    double z = v.GetZ();
1710    int units;
1711    if (m_Gfx->GetMetric()) {
1712        units = /*m*/424;
1713    } else {
1714        x /= METRES_PER_FOOT;
1715        y /= METRES_PER_FOOT;
1716        z /= METRES_PER_FOOT;
1717        units = /*′*/428;
1718    }
1719    /* TRANSLATORS: show coordinates (N = North or Northing, E = East or
1720     * Easting) */
1721    coords_text.Printf(wmsg(/*%.2f E, %.2f N*/338), x, y);
1722    coords_text += wxString::Format(wxT(", %s %.2f%s"),
1723                                    wmsg(/*Altitude*/335).c_str(),
1724                                    z, wmsg(units).c_str());
1725    distfree_text = wxString();
1726    UpdateStatusBar();
1727}
1728
1729const LabelInfo * MainFrm::GetTreeSelection() const {
1730    wxTreeItemData* sel_wx;
1731    if (!m_Tree->GetSelectionData(&sel_wx)) return NULL;
1732
1733    const TreeData* data = static_cast<const TreeData*>(sel_wx);
1734    if (!data->IsStation()) return NULL;
1735
1736    return data->GetLabel();
1737}
1738
1739void MainFrm::SetCoords(double x, double y, const LabelInfo * there)
1740{
1741    wxString & s = coords_text;
1742    if (m_Gfx->GetMetric()) {
1743        s.Printf(wmsg(/*%.2f E, %.2f N*/338), x, y);
1744    } else {
1745        s.Printf(wmsg(/*%.2f E, %.2f N*/338),
1746                 x / METRES_PER_FOOT, y / METRES_PER_FOOT);
1747    }
1748
1749    wxString & t = distfree_text;
1750    t = wxString();
1751    if (m_Gfx->ShowingMeasuringLine() && there) {
1752        auto offset = GetOffset();
1753        Vector3 delta(x - offset.GetX() - there->GetX(),
1754                      y - offset.GetY() - there->GetY(), 0);
1755        double dh = sqrt(delta.GetX()*delta.GetX() + delta.GetY()*delta.GetY());
1756        double brg = deg(atan2(delta.GetX(), delta.GetY()));
1757        if (brg < 0) brg += 360;
1758
1759        wxString from_str;
1760        /* TRANSLATORS: Used in Aven:
1761         * From <stationname>: H 12.24m, Brg 234.5°
1762         */
1763        from_str.Printf(wmsg(/*From %s*/339), there->name_or_anon().c_str());
1764        int brg_unit;
1765        if (m_Gfx->GetDegrees()) {
1766            brg_unit = /*°*/344;
1767        } else {
1768            brg *= 400.0 / 360.0;
1769            brg_unit = /*ᵍ*/345;
1770        }
1771
1772        int units;
1773        if (m_Gfx->GetMetric()) {
1774            units = /*m*/424;
1775        } else {
1776            dh /= METRES_PER_FOOT;
1777            units = /*′*/428;
1778        }
1779        /* TRANSLATORS: "H" is short for "Horizontal", "Brg" for "Bearing" (as
1780         * in Compass bearing) */
1781        t.Printf(wmsg(/*%s: H %.2f%s, Brg %03.1f%s*/374),
1782                 from_str.c_str(), dh, wmsg(units).c_str(),
1783                 brg, wmsg(brg_unit).c_str());
1784    }
1785
1786    UpdateStatusBar();
1787}
1788
1789void MainFrm::SetAltitude(double z, const LabelInfo * there)
1790{
1791    double alt = z;
1792    int units;
1793    if (m_Gfx->GetMetric()) {
1794        units = /*m*/424;
1795    } else {
1796        alt /= METRES_PER_FOOT;
1797        units = /*′*/428;
1798    }
1799    coords_text.Printf(wxT("%s %.2f%s"), wmsg(/*Altitude*/335).c_str(),
1800                       alt, wmsg(units).c_str());
1801
1802    wxString & t = distfree_text;
1803    t = wxString();
1804    if (m_Gfx->ShowingMeasuringLine() && there) {
1805        double dz = z - GetOffset().GetZ() - there->GetZ();
1806
1807        wxString from_str;
1808        from_str.Printf(wmsg(/*From %s*/339), there->name_or_anon().c_str());
1809
1810        if (!m_Gfx->GetMetric()) {
1811            dz /= METRES_PER_FOOT;
1812        }
1813        // TRANSLATORS: "V" is short for "Vertical"
1814        t.Printf(wmsg(/*%s: V %.2f%s*/375), from_str.c_str(),
1815                 dz, wmsg(units).c_str());
1816    }
1817
1818    UpdateStatusBar();
1819}
1820
1821void MainFrm::ShowInfo(const LabelInfo *here, const LabelInfo *there)
1822{
1823    assert(m_Gfx);
1824
1825    if (!here) {
1826        m_Gfx->SetHere();
1827        m_Tree->SetHere(wxTreeItemId());
1828        // Don't clear "There" mark here.
1829        if (here_text.empty() && dist_text.empty()) return;
1830        here_text = wxString();
1831        dist_text = wxString();
1832        UpdateStatusBar();
1833        return;
1834    }
1835
1836    Vector3 v = *here + GetOffset();
1837    wxString & s = here_text;
1838    double x = v.GetX();
1839    double y = v.GetY();
1840    double z = v.GetZ();
1841    int units;
1842    if (m_Gfx->GetMetric()) {
1843        units = /*m*/424;
1844    } else {
1845        x /= METRES_PER_FOOT;
1846        y /= METRES_PER_FOOT;
1847        z /= METRES_PER_FOOT;
1848        units = /*′*/428;
1849    }
1850    s.Printf(wmsg(/*%.2f E, %.2f N*/338), x, y);
1851    s += wxString::Format(wxT(", %s %.2f%s"), wmsg(/*Altitude*/335).c_str(),
1852                          z, wmsg(units).c_str());
1853    s += wxT(": ");
1854    s += here->name_or_anon();
1855    m_Gfx->SetHere(here);
1856    m_Tree->SetHere(here->tree_id);
1857
1858    if (m_Gfx->ShowingMeasuringLine() && there) {
1859        Vector3 delta = *here - *there;
1860
1861        double d_horiz = sqrt(delta.GetX()*delta.GetX() +
1862                              delta.GetY()*delta.GetY());
1863        double dr = delta.magnitude();
1864        double dz = delta.GetZ();
1865
1866        double brg = deg(atan2(delta.GetX(), delta.GetY()));
1867        if (brg < 0) brg += 360;
1868
1869        double grd = deg(atan2(delta.GetZ(), d_horiz));
1870
1871        wxString from_str;
1872        from_str.Printf(wmsg(/*From %s*/339), there->name_or_anon().c_str());
1873
1874        wxString hv_str;
1875        if (m_Gfx->GetMetric()) {
1876            units = /*m*/424;
1877        } else {
1878            d_horiz /= METRES_PER_FOOT;
1879            dr /= METRES_PER_FOOT;
1880            dz /= METRES_PER_FOOT;
1881            units = /*′*/428;
1882        }
1883        wxString len_unit = wmsg(units);
1884        /* TRANSLATORS: "H" is short for "Horizontal", "V" for "Vertical" */
1885        hv_str.Printf(wmsg(/*H %.2f%s, V %.2f%s*/340),
1886                      d_horiz, len_unit.c_str(), dz, len_unit.c_str());
1887        int brg_unit;
1888        if (m_Gfx->GetDegrees()) {
1889            brg_unit = /*°*/344;
1890        } else {
1891            brg *= 400.0 / 360.0;
1892            brg_unit = /*ᵍ*/345;
1893        }
1894        int grd_unit;
1895        wxString grd_str;
1896        if (m_Gfx->GetPercent()) {
1897            if (grd > 89.99) {
1898                grd = 1000000;
1899            } else if (grd < -89.99) {
1900                grd = -1000000;
1901            } else {
1902                grd = int(100 * tan(rad(grd)));
1903            }
1904            if (grd > 99999 || grd < -99999) {
1905                grd_str = grd > 0 ? wxT("+") : wxT("-");
1906                /* TRANSLATORS: infinity symbol - used for the percentage gradient on
1907                 * vertical angles. */
1908                grd_str += wmsg(/*∞*/431);
1909            }
1910            grd_unit = /*%*/96;
1911        } else if (m_Gfx->GetDegrees()) {
1912            grd_unit = /*°*/344;
1913        } else {
1914            grd *= 400.0 / 360.0;
1915            grd_unit = /*ᵍ*/345;
1916        }
1917        if (grd_str.empty()) {
1918            grd_str.Printf(wxT("%+02.1f%s"), grd, wmsg(grd_unit).c_str());
1919        }
1920
1921        wxString & d = dist_text;
1922        /* TRANSLATORS: "Dist" is short for "Distance", "Brg" for "Bearing" (as
1923         * in Compass bearing) and "Grd" for "Gradient" (the slope angle
1924         * measured by the clino) */
1925        d.Printf(wmsg(/*%s: %s, Dist %.2f%s, Brg %03.1f%s, Grd %s*/341),
1926                 from_str.c_str(), hv_str.c_str(),
1927                 dr, len_unit.c_str(),
1928                 brg, wmsg(brg_unit).c_str(),
1929                 grd_str.c_str());
1930    } else {
1931        dist_text = wxString();
1932        m_Gfx->SetThere();
1933    }
1934    UpdateStatusBar();
1935}
1936
1937void MainFrm::DisplayTreeInfo(const wxTreeItemData* item)
1938{
1939    const TreeData* data = static_cast<const TreeData*>(item);
1940    if (data) {
1941        if (data->IsStation()) {
1942            m_Gfx->SetHereFromTree(data->GetLabel());
1943            return;
1944        }
1945        if (data->IsSurvey()) {
1946            m_Gfx->SetHereSurvey(data->GetSurvey());
1947            ShowInfo();
1948            return;
1949        }
1950    }
1951    m_Gfx->SetHereSurvey(wxString());
1952    ShowInfo();
1953}
1954
1955void MainFrm::TreeItemSelected(const wxTreeItemData* item)
1956{
1957    const TreeData* data = static_cast<const TreeData*>(item);
1958    if (data && data->IsStation()) {
1959        const LabelInfo* label = data->GetLabel();
1960        if (m_Gfx->GetThere() == label) {
1961            m_Gfx->CentreOn(*label);
1962        } else {
1963            m_Gfx->SetThere(label);
1964        }
1965        dist_text = wxString();
1966        // FIXME: Need to update dist_text (From ... etc)
1967        // But we don't currently know where "here" is at this point in the
1968        // code!
1969    } else {
1970        dist_text = wxString();
1971        m_Gfx->SetThere();
1972        if (!data) {
1973            // Must be the root.
1974            wxCommandEvent dummy;
1975            OnDefaults(dummy);
1976        } else if (data->IsSurvey()) {
1977            m_Gfx->ZoomToSurvey(data->GetSurvey());
1978        } else {
1979            // FIXME: Click on overlay
1980        }
1981    }
1982    UpdateStatusBar();
1983}
1984
1985void MainFrm::TreeItemSearch(const wxTreeItemData* item)
1986{
1987    const TreeData* data = static_cast<const TreeData*>(item);
1988    if (!data) return;
1989
1990    pending_find = PENDING_FIND_AND_GO;
1991    if (data->IsStation()) {
1992        m_FindBox->ChangeValue(data->GetLabel()->GetText());
1993    } else {
1994        m_FindBox->ChangeValue(data->GetSurvey() + ".*");
1995    }
1996}
1997
1998void MainFrm::OnPresNew(wxCommandEvent&)
1999{
2000    if (m_PresList->Modified()) {
2001        // FIXME: better to ask "Do you want to save your changes?" and offer [Save] [Discard] [Cancel]
2002        if (wxMessageBox(wmsg(/*The current presentation has been modified.  Abandon unsaved changes?*/327),
2003                         wmsg(/*Modified Presentation*/326),
2004                         wxOK|wxCANCEL|wxICON_QUESTION) == wxCANCEL) {
2005            return;
2006        }
2007    }
2008    m_PresList->New(m_File);
2009    if (!ShowingSidePanel()) ToggleSidePanel();
2010    // Select the presentation page in the notebook.
2011    m_Notebook->SetSelection(1);
2012}
2013
2014void MainFrm::OnPresOpen(wxCommandEvent&)
2015{
2016    if (m_PresList->Modified()) {
2017        // FIXME: better to ask "Do you want to save your changes?" and offer [Save] [Discard] [Cancel]
2018        if (wxMessageBox(wmsg(/*The current presentation has been modified.  Abandon unsaved changes?*/327),
2019                         wmsg(/*Modified Presentation*/326),
2020                         wxOK|wxCANCEL|wxICON_QUESTION) == wxCANCEL) {
2021            return;
2022        }
2023    }
2024#ifdef __WXMOTIF__
2025    wxFileDialog dlg(this, wmsg(/*Select a presentation to open*/322), wxString(), wxString(),
2026                     wxT("*.fly"), wxFD_OPEN);
2027#else
2028    wxFileDialog dlg(this, wmsg(/*Select a presentation to open*/322), wxString(), wxString(),
2029                     wxString::Format(wxT("%s|*.fly|%s|%s"),
2030                               wmsg(/*Aven presentations*/320).c_str(),
2031                               wmsg(/*All files*/208).c_str(),
2032                               wxFileSelectorDefaultWildcardStr),
2033                     wxFD_OPEN|wxFD_FILE_MUST_EXIST);
2034#endif
2035    if (dlg.ShowModal() == wxID_OK) {
2036        if (!m_PresList->Load(dlg.GetPath())) {
2037            return;
2038        }
2039        // FIXME : keep a history of loaded/saved presentations, like we do for
2040        // loaded surveys...
2041        // Select the presentation page in the notebook.
2042        m_Notebook->SetSelection(1);
2043    }
2044}
2045
2046void MainFrm::OnPresSave(wxCommandEvent&)
2047{
2048    m_PresList->Save(true);
2049}
2050
2051void MainFrm::OnPresSaveAs(wxCommandEvent&)
2052{
2053    m_PresList->Save(false);
2054}
2055
2056void MainFrm::OnPresMark(wxCommandEvent&)
2057{
2058    m_PresList->AddMark();
2059}
2060
2061void MainFrm::OnPresFRewind(wxCommandEvent&)
2062{
2063    m_Gfx->PlayPres(-100);
2064}
2065
2066void MainFrm::OnPresRewind(wxCommandEvent&)
2067{
2068    m_Gfx->PlayPres(-10);
2069}
2070
2071void MainFrm::OnPresReverse(wxCommandEvent&)
2072{
2073    m_Gfx->PlayPres(-1);
2074}
2075
2076void MainFrm::OnPresPlay(wxCommandEvent&)
2077{
2078    m_Gfx->PlayPres(1);
2079}
2080
2081void MainFrm::OnPresFF(wxCommandEvent&)
2082{
2083    m_Gfx->PlayPres(10);
2084}
2085
2086void MainFrm::OnPresFFF(wxCommandEvent&)
2087{
2088    m_Gfx->PlayPres(100);
2089}
2090
2091void MainFrm::OnPresPause(wxCommandEvent&)
2092{
2093    m_Gfx->PlayPres(0);
2094}
2095
2096void MainFrm::OnPresStop(wxCommandEvent&)
2097{
2098    m_Gfx->PlayPres(0, false);
2099}
2100
2101void MainFrm::OnPresExportMovie(wxCommandEvent&)
2102{
2103#ifdef WITH_FFMPEG
2104    // FIXME : Taking the leaf of the currently loaded presentation as the
2105    // default might make more sense?
2106    wxString baseleaf;
2107    wxFileName::SplitPath(m_File, NULL, NULL, &baseleaf, NULL, wxPATH_NATIVE);
2108    wxFileDialog dlg(this, wmsg(/*Export Movie*/331), wxString(),
2109                     baseleaf + wxT(".mp4"),
2110                     wxT("MPEG|*.mp4|OGG|*.ogv|AVI|*.avi|QuickTime|*.mov|WMV|*.wmv;*.asf"),
2111                     wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
2112    if (dlg.ShowModal() == wxID_OK) {
2113        // Error is reported by GfxCore.
2114        (void)m_Gfx->ExportMovie(dlg.GetPath());
2115    }
2116#else
2117    wxGetApp().ReportError(wxT("Movie generation support code not present"));
2118#endif
2119}
2120
2121PresentationMark MainFrm::GetPresMark(int which)
2122{
2123    return m_PresList->GetPresMark(which);
2124}
2125
2126void MainFrm::RestrictTo(const wxString & survey)
2127{
2128    // The station names will change, so clear the current search.
2129    wxCommandEvent dummy;
2130    OnHide(dummy);
2131
2132    wxString new_prefix;
2133    if (!survey.empty()) {
2134        if (!m_Survey.empty()) {
2135            new_prefix = m_Survey;
2136            new_prefix += GetSeparator();
2137        }
2138        new_prefix += survey;
2139    }
2140    // Reload the processed data rather rather than potentially reprocessing.
2141    if (!LoadData(m_FileProcessed, new_prefix))
2142        return;
2143    InitialiseAfterLoad(m_File, new_prefix);
2144}
2145
2146void MainFrm::OnOpenTerrainUpdate(wxUpdateUIEvent& event)
2147{
2148    event.Enable(!m_File.empty());
2149}
2150
2151void MainFrm::OnOverlayGeodataUpdate(wxUpdateUIEvent& event)
2152{
2153    event.Enable(!m_File.empty());
2154}
2155
2156void MainFrm::OnPresNewUpdate(wxUpdateUIEvent& event)
2157{
2158    event.Enable(!m_File.empty());
2159}
2160
2161void MainFrm::OnPresOpenUpdate(wxUpdateUIEvent& event)
2162{
2163    event.Enable(!m_File.empty());
2164}
2165
2166void MainFrm::OnPresSaveUpdate(wxUpdateUIEvent& event)
2167{
2168    event.Enable(!m_PresList->Empty());
2169}
2170
2171void MainFrm::OnPresSaveAsUpdate(wxUpdateUIEvent& event)
2172{
2173    event.Enable(!m_PresList->Empty());
2174}
2175
2176void MainFrm::OnPresMarkUpdate(wxUpdateUIEvent& event)
2177{
2178    event.Enable(!m_File.empty());
2179}
2180
2181void MainFrm::OnPresFRewindUpdate(wxUpdateUIEvent& event)
2182{
2183    event.Enable(m_Gfx && m_Gfx->GetPresentationMode());
2184    event.Check(m_Gfx && m_Gfx->GetPresentationSpeed() < -10);
2185}
2186
2187void MainFrm::OnPresRewindUpdate(wxUpdateUIEvent& event)
2188{
2189    event.Enable(m_Gfx && m_Gfx->GetPresentationMode());
2190    event.Check(m_Gfx && m_Gfx->GetPresentationSpeed() == -10);
2191}
2192
2193void MainFrm::OnPresReverseUpdate(wxUpdateUIEvent& event)
2194{
2195    event.Enable(m_Gfx && m_Gfx->GetPresentationMode());
2196    event.Check(m_Gfx && m_Gfx->GetPresentationSpeed() == -1);
2197}
2198
2199void MainFrm::OnPresPlayUpdate(wxUpdateUIEvent& event)
2200{
2201    event.Enable(!m_PresList->Empty());
2202    event.Check(m_Gfx && m_Gfx->GetPresentationMode() &&
2203                m_Gfx->GetPresentationSpeed() == 1);
2204}
2205
2206void MainFrm::OnPresFFUpdate(wxUpdateUIEvent& event)
2207{
2208    event.Enable(m_Gfx && m_Gfx->GetPresentationMode());
2209    event.Check(m_Gfx && m_Gfx->GetPresentationSpeed() == 10);
2210}
2211
2212void MainFrm::OnPresFFFUpdate(wxUpdateUIEvent& event)
2213{
2214    event.Enable(m_Gfx && m_Gfx->GetPresentationMode());
2215    event.Check(m_Gfx && m_Gfx->GetPresentationSpeed() > 10);
2216}
2217
2218void MainFrm::OnPresPauseUpdate(wxUpdateUIEvent& event)
2219{
2220    event.Enable(m_Gfx && m_Gfx->GetPresentationMode());
2221    event.Check(m_Gfx && m_Gfx->GetPresentationSpeed() == 0);
2222}
2223
2224void MainFrm::OnPresStopUpdate(wxUpdateUIEvent& event)
2225{
2226    event.Enable(m_Gfx && m_Gfx->GetPresentationMode());
2227}
2228
2229void MainFrm::OnPresExportMovieUpdate(wxUpdateUIEvent& event)
2230{
2231    event.Enable(!m_PresList->Empty());
2232}
2233
2234void MainFrm::OnFind(wxCommandEvent&)
2235{
2236    if (pending_find == PENDING_FIND_NONE) pending_find = PENDING_FIND;
2237}
2238
2239void MainFrm::OnIdle(wxIdleEvent&)
2240{
2241    if (pending_find) {
2242        DoFind();
2243    }
2244}
2245
2246void MainFrm::DoFind()
2247{
2248    wxBusyCursor hourglass;
2249    // Find stations specified by a string or regular expression pattern.
2250
2251    bool substring = true;
2252    wxString pattern = m_FindBox->GetValue();
2253    if (pattern.empty()) {
2254        // Hide any search result highlights.
2255        list<LabelInfo*>::iterator pos = GetLabelsNC();
2256        while (pos != GetLabelsNCEnd()) {
2257            LabelInfo* label = *pos++;
2258            label->clear_flags(LFLAG_HIGHLIGHTED);
2259        }
2260        m_NumHighlighted = 0;
2261    } else {
2262        int re_flags = wxRE_NOSUB;
2263
2264        if (true /* case insensitive */) {
2265            re_flags |= wxRE_ICASE;
2266        }
2267
2268        if (false /*m_RegexpCheckBox->GetValue()*/) {
2269            re_flags |= wxRE_EXTENDED;
2270        } else if (true /* simple glob-style */) {
2271            wxString pat;
2272            for (size_t i = 0; i < pattern.size(); i++) {
2273               wxChar ch = pattern[i];
2274               // ^ only special at start; $ at end.  But this is simpler...
2275               switch (ch) {
2276                case '^': case '$': case '.': case '[': case '\\':
2277                  pat += wxT('\\');
2278                  pat += ch;
2279                  break;
2280                case '*':
2281                  pat += wxT(".*");
2282                  substring = false;
2283                  break;
2284                case '?':
2285                  pat += wxT('.');
2286                  substring = false;
2287                  break;
2288                default:
2289                  pat += ch;
2290               }
2291            }
2292            pattern = pat;
2293            re_flags |= wxRE_BASIC;
2294        } else {
2295            wxString pat;
2296            for (size_t i = 0; i < pattern.size(); i++) {
2297               wxChar ch = pattern[i];
2298               // ^ only special at start; $ at end.  But this is simpler...
2299               switch (ch) {
2300                case '^': case '$': case '*': case '.': case '[': case '\\':
2301                  pat += wxT('\\');
2302               }
2303               pat += ch;
2304            }
2305            pattern = pat;
2306            re_flags |= wxRE_BASIC;
2307        }
2308
2309        if (!substring) {
2310            // FIXME "0u" required to avoid compilation error with g++-3.0
2311            if (pattern.empty() || pattern[0u] != '^') pattern = wxT('^') + pattern;
2312            // FIXME: this fails to cope with "\$" at the end of pattern...
2313            if (pattern[pattern.size() - 1] != '$') pattern += wxT('$');
2314        }
2315
2316        wxRegEx regex;
2317        if (!regex.Compile(pattern, re_flags)) {
2318            pending_find = PENDING_FIND_NONE;
2319            wxBell();
2320            return;
2321        }
2322
2323        int found = 0;
2324
2325        list<LabelInfo*>::iterator pos = GetLabelsNC();
2326        while (pos != GetLabelsNCEnd()) {
2327            LabelInfo* label = *pos++;
2328
2329            if (regex.Matches(label->GetText())) {
2330                label->set_flags(LFLAG_HIGHLIGHTED);
2331                ++found;
2332            } else {
2333                label->clear_flags(LFLAG_HIGHLIGHTED);
2334            }
2335        }
2336
2337        m_NumHighlighted = found;
2338
2339        // Re-sort so highlighted points get names in preference
2340        if (found) SortLabelsByPlotOrder();
2341    }
2342
2343    m_Gfx->UpdateBlobs();
2344    m_Gfx->ForceRefresh();
2345
2346    if (!m_NumHighlighted) {
2347        GetToolBar()->SetToolShortHelp(textctrl_FIND, wmsg(/*No matches were found.*/328));
2348    } else {
2349        pattern = m_FindBox->GetValue();
2350        /* TRANSLATORS: Find station tooltip when stations are found.  %d is
2351         * replaced by the number of matching stations and %s%s%s by the
2352         * pattern searched for.
2353         */
2354        GetToolBar()->SetToolShortHelp(textctrl_FIND, wxString::Format(wmsg(/*%d stations match %s%s%s*/334).c_str(),
2355                                                                       m_NumHighlighted,
2356                                                                       substring ? "*" : "",
2357                                                                       m_FindBox->GetValue(),
2358                                                                       substring ? "*" : ""));
2359    }
2360    if (pending_find == PENDING_FIND_AND_GO) {
2361        wxCommandEvent dummy;
2362        OnGotoFound(dummy);
2363    }
2364    pending_find = PENDING_FIND_NONE;
2365}
2366
2367void MainFrm::OnGotoFound(wxCommandEvent&)
2368{
2369    if (!m_NumHighlighted) {
2370        wxGetApp().ReportError(wmsg(/*No matches were found.*/328));
2371        return;
2372    }
2373
2374    double xmin = DBL_MAX;
2375    double xmax = -DBL_MAX;
2376    double ymin = DBL_MAX;
2377    double ymax = -DBL_MAX;
2378    double zmin = DBL_MAX;
2379    double zmax = -DBL_MAX;
2380
2381    list<LabelInfo*>::iterator pos = GetLabelsNC();
2382    while (pos != GetLabelsNCEnd()) {
2383        LabelInfo* label = *pos++;
2384
2385        if (label->IsHighLighted()) {
2386            if (label->GetX() < xmin) xmin = label->GetX();
2387            if (label->GetX() > xmax) xmax = label->GetX();
2388            if (label->GetY() < ymin) ymin = label->GetY();
2389            if (label->GetY() > ymax) ymax = label->GetY();
2390            if (label->GetZ() < zmin) zmin = label->GetZ();
2391            if (label->GetZ() > zmax) zmax = label->GetZ();
2392        }
2393    }
2394
2395    m_Gfx->SetViewTo(xmin, xmax, ymin, ymax, zmin, zmax);
2396    m_Gfx->SetFocus();
2397}
2398
2399void MainFrm::OnHide(wxCommandEvent&)
2400{
2401    m_FindBox->SetValue(wxString());
2402    // TRANSLATORS: Status bar help message for aven's station search control.
2403    GetToolBar()->SetToolShortHelp(textctrl_FIND,
2404                                   wmsg(/*Station name search (substring or wildcard)*/533));
2405}
2406
2407void MainFrm::OnHideUpdate(wxUpdateUIEvent& ui)
2408{
2409    ui.Enable(m_NumHighlighted != 0);
2410}
2411
2412void MainFrm::OnZStretch(wxSpinDoubleEvent& event)
2413{
2414    m_Gfx->SetZStretch(event.GetValue());
2415    if (static_cast<wxWindow*>(event.GetEventObject())->HasFocus()) {
2416        m_Gfx->SetFocus();
2417    }
2418}
2419
2420void MainFrm::OnViewSidePanel(wxCommandEvent&)
2421{
2422    ToggleSidePanel();
2423}
2424
2425void MainFrm::ToggleSidePanel()
2426{
2427    // Toggle display of the side panel.
2428
2429    assert(m_Gfx);
2430
2431    if (m_Splitter->IsSplit()) {
2432        m_SashPosition = m_Splitter->GetSashPosition(); // save width of panel
2433        m_Splitter->Unsplit(m_Notebook);
2434    } else {
2435        m_Notebook->Show(true);
2436        m_Gfx->Show(true);
2437        m_Splitter->SplitVertically(m_Notebook, m_Gfx, m_SashPosition);
2438    }
2439}
2440
2441void MainFrm::OnViewSidePanelUpdate(wxUpdateUIEvent& ui)
2442{
2443    ui.Enable(!m_File.empty());
2444    ui.Check(ShowingSidePanel());
2445}
2446
2447bool MainFrm::ShowingSidePanel()
2448{
2449    return m_Splitter->IsSplit();
2450}
2451
2452void MainFrm::ViewFullScreen() {
2453#ifdef __WXMAC__
2454    // On macOS:
2455    //
2456    // If !using_macos_full_screen_view, wxWidgets doesn't currently hide the
2457    // toolbar or statusbar in full screen mode (last checked with 3.0.2).
2458    //
2459    // If using_macos_full_screen_view, apparently wxWidgets hides the toolbar
2460    // but not the statusbar (or maybe the status bar gets hidden by macOS
2461    // unconditionally?)
2462    if (!IsFullScreen()) {
2463        // On macOS when not using the full screen view API, wxWidgets doesn't
2464        // hide the toolbar in full screen mode (last checked with 3.0.2).
2465        if (!using_macos_full_screen_view) GetToolBar()->Hide();
2466        // The statusbar isn't automatically hidden without the full screen view
2467        // API (last checked with 3.0.2); with the full screen view API the
2468        // wxFULLSCREEN_NOSTATUSBAR flag is ignored, but possibly macOS
2469        // unconditionally hides the status bar?  FIXME Need to get someone to
2470        // test this.
2471        GetStatusBar()->Hide();
2472    }
2473#endif
2474
2475    ShowFullScreen(!IsFullScreen());
2476    fullscreen_showing_menus = false;
2477    if (IsFullScreen())
2478        was_showing_sidepanel_before_fullscreen = ShowingSidePanel();
2479    if (was_showing_sidepanel_before_fullscreen)
2480        ToggleSidePanel();
2481
2482#ifdef __WXMAC__
2483    if (!IsFullScreen()) {
2484        GetStatusBar()->Show();
2485        if (!using_macos_full_screen_view) GetToolBar()->Show();
2486#ifdef USING_GENERIC_TOOLBAR
2487        Layout();
2488#endif
2489    }
2490#endif
2491}
2492
2493bool MainFrm::FullScreenModeShowingMenus() const
2494{
2495    return fullscreen_showing_menus;
2496}
2497
2498void MainFrm::FullScreenModeShowMenus(bool show)
2499{
2500    if (!IsFullScreen() || show == fullscreen_showing_menus)
2501        return;
2502#ifdef __WXMAC__
2503    // If we're using the macOS full screen view API then auto-showing the menu
2504    // bar happens automatically when the mouse is moved near it.  Otherwise
2505    // enabling the menu bar while in full screen mode doesn't have any effect
2506    // (probably last tested with 3.0.x), so instead make moving the mouse to
2507    // the top of the screen drop us out of full screen mode.
2508    if (!using_macos_full_screen_view) ViewFullScreen();
2509#else
2510    GetMenuBar()->Show(show);
2511    fullscreen_showing_menus = show;
2512#endif
2513}
Note: See TracBrowser for help on using the repository browser.