source: git/src/mainfrm.cc @ 598b577

debug-cidebug-ci-sanitisersfaster-cavernloglog-selectstereo-2025walls-datawalls-data-hanging-as-warningwarn-only-for-hanging-survey
Last change on this file since 598b577 was 8a7f1c5, checked in by Olly Betts <olly@…>, 3 years ago

Fix aven startup with no file on macos

Previously aven hung in this case.

It seems we need to draw the "empty" aven window using OpenGL
rather than using the wxPaintDC directly.

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