source: git/src/mainfrm.cc @ d3fa693

faster-cavernloglog-selectstereo-2025walls-datawalls-data-hanging-as-warningwarn-only-for-hanging-survey
Last change on this file since d3fa693 was 4c83f84, checked in by Olly Betts <olly@…>, 15 months ago

Don't check HAVE_CONFIG_H in most cases

This check is only useful for img.c, which is intended to be usable
outside of Survex (and had fallbacks for functions which may not be
available which will get used if built in a non-autotools project).
For all the other source files it's just useless boilerplate.

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