source: git/src/mainfrm.cc @ 8a7f1c5

debug-cidebug-ci-sanitisersfaster-cavernloglog-selectstereo-2025walls-datawalls-data-hanging-as-warningwarn-only-for-hanging-survey
Last change on this file since 8a7f1c5 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
RevLine 
[5809313]1//
[156dc16]2//  mainfrm.cc
[5809313]3//
4//  Main frame handling for Aven.
5//
[b72f4b5]6//  Copyright (C) 2000-2002,2005,2006 Mark R. Shinwell
[8be4d09]7//  Copyright (C) 2001-2003,2004,2005,2006,2010,2011,2012,2013,2014,2015,2016,2018 Olly Betts
[887c26e]8//  Copyright (C) 2005 Martin Green
[5809313]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
[5940815]22//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
[5809313]23//
24
[cbfa50d]25#ifdef HAVE_CONFIG_H
26#include <config.h>
27#endif
28
[6bec10c]29#include "cavernlog.h"
[5809313]30#include "mainfrm.h"
31#include "aven.h"
[6e65d9e]32#include "aboutdlg.h"
[706b033]33
[93c3f97]34#include "message.h"
[a405bc1]35#include "img_hosted.h"
[41adebb]36#include "namecompare.h"
[4283d6f]37#include "printing.h"
[045e2af]38#include "filename.h"
[e577f89]39#include "useful.h"
[706b033]40
[2d9ed8ad]41#include <wx/confbase.h>
[8991d7f]42//#include <wx/filefn.h>
[f302cbaa]43#include <wx/filename.h>
[ee41e88]44#include <wx/image.h>
[0580c6a]45#include <wx/imaglist.h>
[b88b171]46#include <wx/process.h>
[0580c6a]47#include <wx/regex.h>
[562ca6a]48#ifdef USING_GENERIC_TOOLBAR
[2737db5]49# include <wx/sysopt.h>
50#endif
[203d2a7]51
[549eb37]52#include <cerrno>
[42bd3576]53#include <cstdlib>
[5809313]54#include <float.h>
[63dc4eb]55#include <functional>
[7a2e1ed]56#include <vector>
[0c76ad9]57
[63c2fe8]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
[63a2dba]62#ifndef __WXMSW__
[ea1ea51]63#include "../lib/icons/aven.xpm"
[63a2dba]64#endif
[d7b53e3]65#include "../lib/icons/log.xpm"
[63c2fe8]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"
[4938bcd]80#include "../lib/icons/solid_surface.xpm"
[63c2fe8]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
[63a2dba]94#ifdef __WXMSW__
95# define TOOL(x) wxBitmap(x##_xpm)
96#else
97# define TOOL(x) wxBITMAP(x)
[63c2fe8]98#endif
99
[0580c6a]100using namespace std;
[5809313]101
[b1de3e0]102class AvenSplitterWindow : public wxSplitterWindow {
103    MainFrm *parent;
104
105    public:
[fd4f6ff]106        explicit AvenSplitterWindow(MainFrm *parent_)
[b4fe9fb]107            : wxSplitterWindow(parent_, -1, wxDefaultPosition, wxDefaultSize,
[5ed907b0]108                               wxSP_3DSASH),
[b4fe9fb]109              parent(parent_)
110        {
[b1de3e0]111        }
112
[41f7a27]113        void OnSplitterDClick(wxSplitterEvent &) {
[b1de3e0]114            parent->ToggleSidePanel();
115        }
[f433fda]116
[b1de3e0]117    private:
118        DECLARE_EVENT_TABLE()
119};
120
121BEGIN_EVENT_TABLE(AvenSplitterWindow, wxSplitterWindow)
122    EVT_SPLITTER_DCLICK(-1, AvenSplitterWindow::OnSplitterDClick)
123END_EVENT_TABLE()
124
[9521a42]125class EditMarkDlg : public wxDialog {
126    wxTextCtrl * easting, * northing, * altitude;
[49ce5b0]127    wxTextCtrl * angle, * tilt_angle, * scale, * time;
[9521a42]128public:
[736f7df]129    // TRANSLATORS: Title of dialog to edit a waypoint in a presentation.
[9521a42]130    EditMarkDlg(wxWindow* parent, const PresentationMark & p)
[fbc1d32]131        : wxDialog(parent, 500, wmsg(/*Edit Waypoint*/404))
[9521a42]132    {
[5627cbb]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));
[49ce5b0]139        if (p.time > 0.0) {
[5627cbb]140            time = new wxTextCtrl(this, 607, wxString::Format(wxT("%.3f"), p.time));
[49ce5b0]141        } else if (p.time < 0.0) {
[5627cbb]142            time = new wxTextCtrl(this, 607, wxString::Format(wxT("*%.3f"), -p.time));
[49ce5b0]143        } else {
[5627cbb]144            time = new wxTextCtrl(this, 607, wxT("0"));
[49ce5b0]145        }
146
[9521a42]147        wxBoxSizer * coords = new wxBoxSizer(wxHORIZONTAL);
[5627cbb]148        coords->Add(new wxStaticText(this, 610, wxT("(")), 0, wxALIGN_CENTRE_VERTICAL);
[9521a42]149        coords->Add(easting, 1);
[5627cbb]150        coords->Add(new wxStaticText(this, 611, wxT(",")), 0, wxALIGN_CENTRE_VERTICAL);
[9521a42]151        coords->Add(northing, 1);
[5627cbb]152        coords->Add(new wxStaticText(this, 612, wxT(",")), 0, wxALIGN_CENTRE_VERTICAL);
[9521a42]153        coords->Add(altitude, 1);
[5627cbb]154        coords->Add(new wxStaticText(this, 613, wxT(")")), 0, wxALIGN_CENTRE_VERTICAL);
[9521a42]155        wxBoxSizer* vert = new wxBoxSizer(wxVERTICAL);
156        vert->Add(coords, 0, wxALL, 8);
157        wxBoxSizer * r2 = new wxBoxSizer(wxHORIZONTAL);
[fbc1d32]158        r2->Add(new wxStaticText(this, 614, wmsg(/*Bearing*/259) + wxT(": ")), 0, wxALIGN_CENTRE_VERTICAL);
[9521a42]159        r2->Add(angle);
160        vert->Add(r2, 0, wxALL, 8);
161        wxBoxSizer * r3 = new wxBoxSizer(wxHORIZONTAL);
[fbc1d32]162        r3->Add(new wxStaticText(this, 615, wmsg(/*Elevation*/118) + wxT(": ")), 0, wxALIGN_CENTRE_VERTICAL);
[9521a42]163        r3->Add(tilt_angle);
164        vert->Add(r3, 0, wxALL, 8);
165        wxBoxSizer * r4 = new wxBoxSizer(wxHORIZONTAL);
[fbc1d32]166        r4->Add(new wxStaticText(this, 616, wmsg(/*Scale*/154) + wxT(": ")), 0, wxALIGN_CENTRE_VERTICAL);
[9521a42]167        r4->Add(scale);
[736f7df]168        /* TRANSLATORS: Note after "Scale" field in dialog to edit a waypoint
169         * in a presentation. */
[857408e]170        r4->Add(new wxStaticText(this, 617, wmsg(/* (unused in perspective view)*/278)),
[9521a42]171                0, wxALIGN_CENTRE_VERTICAL);
172        vert->Add(r4, 0, wxALL, 8);
173
174        wxBoxSizer * r5 = new wxBoxSizer(wxHORIZONTAL);
[736f7df]175        /* TRANSLATORS: Field label in dialog to edit a waypoint in a
176         * presentation. */
[857408e]177        r5->Add(new wxStaticText(this, 616, wmsg(/*Time: */279)), 0, wxALIGN_CENTRE_VERTICAL);
[49ce5b0]178        r5->Add(time);
[736f7df]179        /* TRANSLATORS: units+info after time field in dialog to edit a
180         * waypoint in a presentation. */
[857408e]181        r5->Add(new wxStaticText(this, 617, wmsg(/* secs (0 = auto; *6 = 6 times auto)*/282)),
[49ce5b0]182                0, wxALIGN_CENTRE_VERTICAL);
183        vert->Add(r5, 0, wxALL, 8);
184
185        wxBoxSizer * buttons = new wxBoxSizer(wxHORIZONTAL);
[73b3388]186        wxButton* cancel = new wxButton(this, wxID_CANCEL);
[49ce5b0]187        buttons->Add(cancel, 0, wxALL, 8);
[73b3388]188        wxButton* ok = new wxButton(this, wxID_OK);
[9521a42]189        ok->SetDefault();
[49ce5b0]190        buttons->Add(ok, 0, wxALL, 8);
191        vert->Add(buttons, 0, wxALL|wxALIGN_RIGHT);
[9521a42]192
193        SetAutoLayout(true);
194        SetSizer(vert);
195
196        vert->SetSizeHints(this);
[128fac4]197    }
[9521a42]198    PresentationMark GetMark() const {
[d67450e]199        double a, t, s, T;
[430f5e0]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());
[49ce5b0]206        wxString str = time->GetValue();
[e60254e]207        if (!str.empty() && str[0u] == '*') str[0u] = '-';
[430f5e0]208        T = wxAtof(str);
[d67450e]209        return PresentationMark(v, a, t, s, T);
[9521a42]210    }
211
212private:
213    DECLARE_EVENT_TABLE()
214};
215
[3fdeb44d]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);
[c988ae4]220    char * p = strchr(buf, ',');
221    if (p) *p = '.';
[3fdeb44d]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
[1c6b20d7]228class AvenPresList : public wxListCtrl {
229    MainFrm * mainfrm;
[1690fa9]230    GfxCore * gfx;
[9521a42]231    vector<PresentationMark> entries;
[1690fa9]232    long current_item;
233    bool modified;
[bd7607c]234    bool force_save_as;
[1690fa9]235    wxString filename;
[f433fda]236
[1690fa9]237    public:
[1c6b20d7]238        AvenPresList(MainFrm * mainfrm_, wxWindow * parent, GfxCore * gfx_)
[1690fa9]239            : wxListCtrl(parent, listctrl_PRES, wxDefaultPosition, wxDefaultSize,
[9521a42]240                         wxLC_REPORT|wxLC_VIRTUAL),
[bd7607c]241              mainfrm(mainfrm_), gfx(gfx_), current_item(-1), modified(false),
242              force_save_as(true)
[3a3c7f8]243        {
244            InsertColumn(0, wmsg(/*Easting*/378));
245            InsertColumn(1, wmsg(/*Northing*/379));
246            InsertColumn(2, wmsg(/*Altitude*/335));
247        }
[1690fa9]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            }
[9521a42]259            entries.erase(entries.begin() + item);
260            SetItemCount(entries.size());
[1690fa9]261            modified = true;
262        }
[41f7a27]263        void OnDeleteAllItems(wxListEvent&) {
[1690fa9]264            entries.clear();
[9521a42]265            SetItemCount(entries.size());
[5627cbb]266            filename = wxString();
[1690fa9]267            modified = false;
[bd7607c]268            force_save_as = true;
[1690fa9]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:
[c8f929b]284                    //printf("event.GetIndex() = %ld %d\n", event.GetIndex(), event.GetKeyCode());
[1690fa9]285                    event.Skip();
286            }
287        }
288        void OnActivated(wxListEvent& event) {
289            // Jump to this view.
290            long item = event.GetIndex();
[9521a42]291            gfx->SetView(entries[item]);
[1690fa9]292        }
293        void OnFocused(wxListEvent& event) {
294            current_item = event.GetIndex();
295        }
[9521a42]296        void OnRightClick(wxListEvent& event) {
297            long item = event.GetIndex();
[f7d52a1]298            if (item < 0) {
299                AddMark(item, gfx->GetView());
300                item = 0;
301            }
[9521a42]302            EditMarkDlg edit(mainfrm, entries[item]);
303            if (edit.ShowModal() == wxID_OK) {
304                entries[item] = edit.GetMark();
305            }
306        }
[1690fa9]307        void OnChar(wxKeyEvent& event) {
[c8f929b]308            switch (event.GetKeyCode()) {
309                case WXK_INSERT:
[a6e8d45]310                    if (event.GetModifiers() == wxMOD_CONTROL) {
[c8f929b]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);
[5e31bd2]327            }
[1690fa9]328        }
329        void AddMark(long item = -1) {
330            AddMark(item, gfx->GetView());
331        }
332        void AddMark(long item, const PresentationMark & mark) {
[9521a42]333            if (item == -1) item = entries.size();
334            entries.insert(entries.begin() + item, mark);
335            SetItemCount(entries.size());
[1690fa9]336            modified = true;
337        }
[9521a42]338        virtual wxString OnGetItemText(long item, long column) const {
[5627cbb]339            if (item < 0 || item >= (long)entries.size()) return wxString();
[9521a42]340            const PresentationMark & p = entries[item];
341            double v;
342            switch (column) {
[d67450e]343                case 0: v = p.GetX(); break;
344                case 1: v = p.GetY(); break;
345                case 2: v = p.GetZ(); break;
[9521a42]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;
[49ce5b0]350                case 6: v = p.time; break;
[9521a42]351#endif
[5627cbb]352                default: return wxString();
[9521a42]353            }
[5627cbb]354            return wxString::Format(wxT("%ld"), (long)v);
[9521a42]355        }
[1690fa9]356        void Save(bool use_default_name) {
357            wxString fnm = filename;
[bd7607c]358            if (!use_default_name || force_save_as) {
[1690fa9]359#ifdef __WXMOTIF__
[5627cbb]360                wxString ext(wxT("*.fly"));
[1690fa9]361#else
[5627cbb]362                wxString ext = wmsg(/*Aven presentations*/320);
[52f67931]363                ext += wxT("|*.fly");
[1690fa9]364#endif
[5627cbb]365                wxFileDialog dlg(this, wmsg(/*Select an output filename*/319),
366                                 wxString(), fnm, ext,
367                                 wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
[1690fa9]368                if (dlg.ShowModal() != wxID_OK) return;
369                fnm = dlg.GetPath();
370            }
371
[3206c12]372            FILE * fh_pres = wxFopen(fnm, wxT("w"));
[1690fa9]373            if (!fh_pres) {
[0804fbe]374                wxGetApp().ReportError(wxString::Format(wmsg(/*Error writing to file “%s”*/110), fnm.c_str()));
[1690fa9]375                return;
376            }
[9521a42]377            vector<PresentationMark>::const_iterator i;
[1690fa9]378            for (i = entries.begin(); i != entries.end(); ++i) {
379                const PresentationMark &p = *i;
[d67450e]380                write_double(p.GetX(), fh_pres);
[e02a6a6]381                PUTC(' ', fh_pres);
[d67450e]382                write_double(p.GetY(), fh_pres);
[e02a6a6]383                PUTC(' ', fh_pres);
[d67450e]384                write_double(p.GetZ(), fh_pres);
[e02a6a6]385                PUTC(' ', fh_pres);
[3fdeb44d]386                write_double(p.angle, fh_pres);
[e02a6a6]387                PUTC(' ', fh_pres);
[3fdeb44d]388                write_double(p.tilt_angle, fh_pres);
[e02a6a6]389                PUTC(' ', fh_pres);
[3fdeb44d]390                write_double(p.scale, fh_pres);
[49ce5b0]391                if (p.time != 0.0) {
[e02a6a6]392                    PUTC(' ', fh_pres);
[49ce5b0]393                    write_double(p.time, fh_pres);
394                }
[e02a6a6]395                PUTC('\n', fh_pres);
[1690fa9]396            }
397            fclose(fh_pres);
398            filename = fnm;
399            modified = false;
[bd7607c]400            force_save_as = false;
401        }
402        void New(const wxString &fnm) {
403            DeleteAllItems();
[d3172cc]404            wxFileName::SplitPath(fnm, NULL, NULL, &filename, NULL, wxPATH_NATIVE);
[5627cbb]405            filename += wxT(".fly");
[bd7607c]406            force_save_as = true;
[1690fa9]407        }
408        bool Load(const wxString &fnm) {
[3206c12]409            FILE * fh_pres = wxFopen(fnm, wxT("r"));
[9b540e6]410            if (!fh_pres) {
411                wxString m;
[ffee37e]412                m.Printf(wmsg(/*Couldn’t open file “%s”*/24), fnm.c_str());
[9b540e6]413                wxGetApp().ReportError(m);
414                return false;
415            }
[1690fa9]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) {
[e02a6a6]422                    int ch = GETC(fh_pres);
[1690fa9]423                    if (ch == EOF || ch == '\n' || ch == '\r') break;
424                    buf[i++] = ch;
425                }
426                if (i) {
427                    buf[i] = 0;
[49ce5b0]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) {
[c988ae4]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();
[0804fbe]436                            wxGetApp().ReportError(wxString::Format(wmsg(/*Error in format of presentation file “%s”*/323), fnm.c_str()));
[c988ae4]437                            return false;
438                        }
[1690fa9]439                    }
[49ce5b0]440                    if (c == 6) T = 0;
[d67450e]441                    AddMark(item, PresentationMark(Vector3(x, y, z), a, t, s, T));
[9b540e6]442                    ++item;
[1690fa9]443                }
444            }
445            fclose(fh_pres);
446            filename = fnm;
447            modified = false;
[bd7607c]448            force_save_as = false;
[1690fa9]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) {
[9521a42]458                ++item;
[128fac4]459            } else if (which == MARK_PREV) {
460                --item;
[1690fa9]461            }
[128fac4]462            if (item == -1 || item == (long)entries.size())
463                return PresentationMark();
[1690fa9]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            }
[9521a42]473            return entries[item];
[1690fa9]474        }
475
476    private:
477
[1c6b20d7]478        DECLARE_NO_COPY_CLASS(AvenPresList)
[1690fa9]479        DECLARE_EVENT_TABLE()
480};
[f433fda]481
[9521a42]482BEGIN_EVENT_TABLE(EditMarkDlg, wxDialog)
483END_EVENT_TABLE()
484
[1c6b20d7]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)
[9521a42]492    EVT_LIST_ITEM_RIGHT_CLICK(listctrl_PRES, AvenPresList::OnRightClick)
[1c6b20d7]493    EVT_CHAR(AvenPresList::OnChar)
[1690fa9]494END_EVENT_TABLE()
495
[137bf99]496BEGIN_EVENT_TABLE(MainFrm, wxFrame)
[d1628e8e]497    EVT_TEXT(textctrl_FIND, MainFrm::OnFind)
498    EVT_TEXT_ENTER(textctrl_FIND, MainFrm::OnGotoFound)
[42c7efe]499    EVT_MENU(wxID_FIND, MainFrm::OnGotoFound)
[8e1a10e]500    EVT_MENU(button_HIDE, MainFrm::OnHide)
[429465a]501    EVT_UPDATE_UI(button_HIDE, MainFrm::OnHideUpdate)
[bc60689]502    EVT_IDLE(MainFrm::OnIdle)
[2a02de2]503
[42c7efe]504    EVT_MENU(wxID_OPEN, MainFrm::OnOpen)
[622460e]505    EVT_MENU(menu_FILE_OPEN_TERRAIN, MainFrm::OnOpenTerrain)
[d7b53e3]506    EVT_MENU(menu_FILE_LOG, MainFrm::OnShowLog)
[42c7efe]507    EVT_MENU(wxID_PRINT, MainFrm::OnPrint)
[ce403f1]508    EVT_MENU(menu_FILE_PAGE_SETUP, MainFrm::OnPageSetup)
[045e2af]509    EVT_MENU(menu_FILE_SCREENSHOT, MainFrm::OnScreenshot)
[42c7efe]510//    EVT_MENU(wxID_PREFERENCES, MainFrm::OnFilePreferences)
[223f1ad]511    EVT_MENU(menu_FILE_EXPORT, MainFrm::OnExport)
[549eb37]512    EVT_MENU(menu_FILE_EXTEND, MainFrm::OnExtend)
[42c7efe]513    EVT_MENU(wxID_EXIT, MainFrm::OnQuit)
[732b9b0]514    EVT_MENU_RANGE(wxID_FILE1, wxID_FILE9, MainFrm::OnMRUFile)
[137bf99]515
[1690fa9]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)
[128fac4]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)
[42c7efe]528    EVT_MENU(wxID_STOP, MainFrm::OnPresStop)
[6a4cdcb6]529    EVT_MENU(menu_PRES_EXPORT_MOVIE, MainFrm::OnPresExportMovie)
[1690fa9]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)
[128fac4]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)
[42c7efe]543    EVT_UPDATE_UI(wxID_STOP, MainFrm::OnPresStopUpdate)
[6a4cdcb6]544    EVT_UPDATE_UI(menu_PRES_EXPORT_MOVIE, MainFrm::OnPresExportMovieUpdate)
[5ffa439]545
[137bf99]546    EVT_CLOSE(MainFrm::OnClose)
[4b1fc48]547    EVT_SET_FOCUS(MainFrm::OnSetFocus)
[137bf99]548
[7ebc3d1]549    EVT_MENU(menu_ROTATION_TOGGLE, MainFrm::OnToggleRotation)
[137bf99]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)
[8666fc7]559    EVT_MENU(menu_SPLAYS_HIDE, MainFrm::OnHideSplays)
[b96edeb]560    EVT_MENU(menu_SPLAYS_SHOW_DASHED, MainFrm::OnShowSplaysDashed)
[8666fc7]561    EVT_MENU(menu_SPLAYS_SHOW_FADED, MainFrm::OnShowSplaysFaded)
[b96edeb]562    EVT_MENU(menu_SPLAYS_SHOW_NORMAL, MainFrm::OnShowSplaysNormal)
[e5c5f3c]563    EVT_MENU(menu_DUPES_HIDE, MainFrm::OnHideDupes)
564    EVT_MENU(menu_DUPES_SHOW_DASHED, MainFrm::OnShowDupesDashed)
[b96edeb]565    EVT_MENU(menu_DUPES_SHOW_FADED, MainFrm::OnShowDupesFaded)
566    EVT_MENU(menu_DUPES_SHOW_NORMAL, MainFrm::OnShowDupesNormal)
[137bf99]567    EVT_MENU(menu_VIEW_SHOW_CROSSES, MainFrm::OnShowCrosses)
[fe444b8]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)
[137bf99]571    EVT_MENU(menu_VIEW_SHOW_NAMES, MainFrm::OnShowStationNames)
572    EVT_MENU(menu_VIEW_SHOW_OVERLAPPING_NAMES, MainFrm::OnDisplayOverlappingNames)
[46beda0]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)
[fc43dda]576    EVT_MENU(menu_COLOUR_BY_H_ERROR, MainFrm::OnColourByHError)
577    EVT_MENU(menu_COLOUR_BY_V_ERROR, MainFrm::OnColourByVError)
[46beda0]578    EVT_MENU(menu_COLOUR_BY_GRADIENT, MainFrm::OnColourByGradient)
579    EVT_MENU(menu_COLOUR_BY_LENGTH, MainFrm::OnColourByLength)
[68fb07a]580    EVT_MENU(menu_COLOUR_BY_SURVEY, MainFrm::OnColourBySurvey)
[36f0d86]581    EVT_MENU(menu_COLOUR_BY_STYLE, MainFrm::OnColourByStyle)
[137bf99]582    EVT_MENU(menu_VIEW_SHOW_SURFACE, MainFrm::OnShowSurface)
[c1cf79d]583    EVT_MENU(menu_VIEW_GRID, MainFrm::OnViewGrid)
[f4c5932]584    EVT_MENU(menu_VIEW_BOUNDING_BOX, MainFrm::OnViewBoundingBox)
[6abab84]585    EVT_MENU(menu_VIEW_PERSPECTIVE, MainFrm::OnViewPerspective)
[d67450e]586    EVT_MENU(menu_VIEW_SMOOTH_SHADING, MainFrm::OnViewSmoothShading)
[a517825]587    EVT_MENU(menu_VIEW_TEXTURED, MainFrm::OnViewTextured)
[c60062d]588    EVT_MENU(menu_VIEW_FOG, MainFrm::OnViewFog)
[db452ae]589    EVT_MENU(menu_VIEW_SMOOTH_LINES, MainFrm::OnViewSmoothLines)
[b13aee4]590    EVT_MENU(menu_VIEW_FULLSCREEN, MainFrm::OnViewFullScreen)
[33b2094]591    EVT_MENU(menu_VIEW_SHOW_TUBES, MainFrm::OnToggleTubes)
[4938bcd]592    EVT_MENU(menu_VIEW_TERRAIN, MainFrm::OnViewTerrain)
[7a36b67]593    EVT_MENU(menu_IND_COMPASS, MainFrm::OnViewCompass)
594    EVT_MENU(menu_IND_CLINO, MainFrm::OnViewClino)
[97ea48d]595    EVT_MENU(menu_IND_COLOUR_KEY, MainFrm::OnToggleColourKey)
[7a36b67]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)
[d171c0c]600    EVT_MENU(menu_CTL_PERCENT, MainFrm::OnTogglePercent)
[137bf99]601    EVT_MENU(menu_CTL_REVERSE, MainFrm::OnReverseControls)
[1fd2edb]602    EVT_MENU(menu_CTL_CANCEL_DIST_LINE, MainFrm::OnCancelDistLine)
[42c7efe]603    EVT_MENU(wxID_ABOUT, MainFrm::OnAbout)
[137bf99]604
[622460e]605    EVT_UPDATE_UI(menu_FILE_OPEN_TERRAIN, MainFrm::OnOpenTerrainUpdate)
[d7b53e3]606    EVT_UPDATE_UI(menu_FILE_LOG, MainFrm::OnShowLogUpdate)
[42c7efe]607    EVT_UPDATE_UI(wxID_PRINT, MainFrm::OnPrintUpdate)
[045e2af]608    EVT_UPDATE_UI(menu_FILE_SCREENSHOT, MainFrm::OnScreenshotUpdate)
[223f1ad]609    EVT_UPDATE_UI(menu_FILE_EXPORT, MainFrm::OnExportUpdate)
[549eb37]610    EVT_UPDATE_UI(menu_FILE_EXTEND, MainFrm::OnExtendUpdate)
[546a6f3]611    EVT_UPDATE_UI(menu_ROTATION_TOGGLE, MainFrm::OnToggleRotationUpdate)
[137bf99]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)
[9cb97b7]621    EVT_UPDATE_UI(menu_VIEW_SPLAYS, MainFrm::OnSplaysUpdate)
[8666fc7]622    EVT_UPDATE_UI(menu_SPLAYS_HIDE, MainFrm::OnHideSplaysUpdate)
[b96edeb]623    EVT_UPDATE_UI(menu_SPLAYS_SHOW_DASHED, MainFrm::OnShowSplaysDashedUpdate)
[8666fc7]624    EVT_UPDATE_UI(menu_SPLAYS_SHOW_FADED, MainFrm::OnShowSplaysFadedUpdate)
[b96edeb]625    EVT_UPDATE_UI(menu_SPLAYS_SHOW_NORMAL, MainFrm::OnShowSplaysNormalUpdate)
[e5c5f3c]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)
[b96edeb]629    EVT_UPDATE_UI(menu_DUPES_SHOW_FADED, MainFrm::OnShowDupesFadedUpdate)
630    EVT_UPDATE_UI(menu_DUPES_SHOW_NORMAL, MainFrm::OnShowDupesNormalUpdate)
[137bf99]631    EVT_UPDATE_UI(menu_VIEW_SHOW_CROSSES, MainFrm::OnShowCrossesUpdate)
[fe444b8]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)
[137bf99]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)
[46beda0]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)
[fc43dda]642    EVT_UPDATE_UI(menu_COLOUR_BY_H_ERROR, MainFrm::OnColourByHErrorUpdate)
643    EVT_UPDATE_UI(menu_COLOUR_BY_V_ERROR, MainFrm::OnColourByVErrorUpdate)
[46beda0]644    EVT_UPDATE_UI(menu_COLOUR_BY_GRADIENT, MainFrm::OnColourByGradientUpdate)
645    EVT_UPDATE_UI(menu_COLOUR_BY_LENGTH, MainFrm::OnColourByLengthUpdate)
[68fb07a]646    EVT_UPDATE_UI(menu_COLOUR_BY_SURVEY, MainFrm::OnColourBySurveyUpdate)
[36f0d86]647    EVT_UPDATE_UI(menu_COLOUR_BY_STYLE, MainFrm::OnColourByStyleUpdate)
[c1cf79d]648    EVT_UPDATE_UI(menu_VIEW_GRID, MainFrm::OnViewGridUpdate)
[f4c5932]649    EVT_UPDATE_UI(menu_VIEW_BOUNDING_BOX, MainFrm::OnViewBoundingBoxUpdate)
[6abab84]650    EVT_UPDATE_UI(menu_VIEW_PERSPECTIVE, MainFrm::OnViewPerspectiveUpdate)
[d67450e]651    EVT_UPDATE_UI(menu_VIEW_SMOOTH_SHADING, MainFrm::OnViewSmoothShadingUpdate)
[a517825]652    EVT_UPDATE_UI(menu_VIEW_TEXTURED, MainFrm::OnViewTexturedUpdate)
[c60062d]653    EVT_UPDATE_UI(menu_VIEW_FOG, MainFrm::OnViewFogUpdate)
[db452ae]654    EVT_UPDATE_UI(menu_VIEW_SMOOTH_LINES, MainFrm::OnViewSmoothLinesUpdate)
[b13aee4]655    EVT_UPDATE_UI(menu_VIEW_FULLSCREEN, MainFrm::OnViewFullScreenUpdate)
[33b2094]656    EVT_UPDATE_UI(menu_VIEW_SHOW_TUBES, MainFrm::OnToggleTubesUpdate)
[4938bcd]657    EVT_UPDATE_UI(menu_VIEW_TERRAIN, MainFrm::OnViewTerrainUpdate)
[7a36b67]658    EVT_UPDATE_UI(menu_IND_COMPASS, MainFrm::OnViewCompassUpdate)
659    EVT_UPDATE_UI(menu_IND_CLINO, MainFrm::OnViewClinoUpdate)
[97ea48d]660    EVT_UPDATE_UI(menu_IND_COLOUR_KEY, MainFrm::OnToggleColourKeyUpdate)
[7a36b67]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)
[d171c0c]668    EVT_UPDATE_UI(menu_CTL_PERCENT, MainFrm::OnTogglePercentUpdate)
[5809313]669END_EVENT_TABLE()
670
[63dc4eb]671class LabelCmp : public greater<const LabelInfo*> {
[ff9f695]672    wxChar separator;
[156dc16]673public:
[fd4f6ff]674    explicit LabelCmp(wxChar separator_) : separator(separator_) {}
[156dc16]675    bool operator()(const LabelInfo* pt1, const LabelInfo* pt2) {
[2f36993]676        return name_cmp(pt1->GetText(), pt2->GetText(), separator) < 0;
[156dc16]677    }
678};
679
[63dc4eb]680class LabelPlotCmp : public greater<const LabelInfo*> {
[ff9f695]681    wxChar separator;
[291ca5d]682public:
[fd4f6ff]683    explicit LabelPlotCmp(wxChar separator_) : separator(separator_) {}
[291ca5d]684    bool operator()(const LabelInfo* pt1, const LabelInfo* pt2) {
[5a24583]685        int n = pt1->get_flags() - pt2->get_flags();
[291ca5d]686        if (n) return n > 0;
[5a24583]687        wxString l1 = pt1->GetText().AfterLast(separator);
688        wxString l2 = pt2->GetText().AfterLast(separator);
[f11f2af]689        n = name_cmp(l1, l2, separator);
690        if (n) return n < 0;
[2a94924]691        // Prefer non-2-nodes...
692        // FIXME; implement
[f11f2af]693        // if leaf names are the same, prefer shorter labels as we can
694        // display more of them
[5a24583]695        n = pt1->GetText().length() - pt2->GetText().length();
[f11f2af]696        if (n) return n < 0;
[2a94924]697        // make sure that we don't ever compare different labels as equal
[5a24583]698        return name_cmp(pt1->GetText(), pt2->GetText(), separator) < 0;
[291ca5d]699    }
700};
701
[f6fe808]702#if wxUSE_DRAG_AND_DROP
[5901b62]703class DnDFile : public wxFileDropTarget {
704    public:
[fd4f6ff]705        explicit DnDFile(MainFrm *parent) : m_Parent(parent) { }
[5901b62]706        virtual bool OnDropFiles(wxCoord, wxCoord,
[3a3c7f8]707                                 const wxArrayString &filenames);
[5901b62]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) {
[736f7df]720        /* TRANSLATORS: error if you try to drag multiple files to the aven
721         * window */
[5627cbb]722        wxGetApp().ReportError(wmsg(/*You may only view one 3d file at a time.*/336));
[6d1bc83]723        return false;
[5901b62]724    }
[421b7d2]725
[5901b62]726    m_Parent->OpenFile(filenames[0]);
[6d1bc83]727    return true;
[5901b62]728}
[f6fe808]729#endif
[5901b62]730
[137bf99]731MainFrm::MainFrm(const wxString& title, const wxPoint& pos, const wxSize& size) :
[ad3d0a2]732    wxFrame(NULL, 101, title, pos, size, wxDEFAULT_FRAME_STYLE),
[486c619]733    m_SashPosition(-1),
[d7b53e3]734    m_Gfx(NULL), m_Log(NULL),
[b75a37d]735    pending_find(false), fullscreen_showing_menus(false)
[9c3d91e]736#ifdef PREFDLG
737    , m_PrefsDlg(NULL)
738#endif
[5809313]739{
[cfd6c44]740#ifdef _WIN32
[003d953]741    // The peculiar name is so that the icon is the first in the file
[b1de3e0]742    // (required by Microsoft Windows for this type of icon)
[ea1ea51]743    SetIcon(wxICON(AAA_aven));
[95fa391]744#else
[ea1ea51]745    SetIcon(wxICON(aven));
[cfd6c44]746#endif
747
[83478f5]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
[9d3d8cc]753    CreateMenuBar();
[242cb07]754    MakeToolBar();
[7c29c976]755    CreateStatusBar(2, wxST_SIZEGRIP);
[9d3d8cc]756    CreateSidePanel();
[f433fda]757
[7c29c976]758    int widths[2] = { -1 /* variable width */, -1 };
759    GetStatusBar()->SetStatusWidths(2, widths);
[9d3d8cc]760
[0580c6a]761#ifdef __X__ // wxMotif or wxX11
[9d3d8cc]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
[0edb0d0]769#if wxUSE_DRAG_AND_DROP
[5901b62]770    SetDropTarget(new DnDFile(this));
[f6fe808]771#endif
[8a7f1c5]772
773#ifdef __WXMAC__
774    m_Gfx->ForceRefresh();
775    m_Gfx->Show(true);
776#endif
[9d3d8cc]777}
778
779void MainFrm::CreateMenuBar()
780{
781    // Create the menus and the menu bar.
[137bf99]782
783    wxMenu* filemenu = new wxMenu;
[42c7efe]784    // wxID_OPEN stock label lacks the ellipses
[736f7df]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. */
[7f928d3]792    filemenu->Append(wxID_OPEN, wmsg(/*&Open...\tCtrl+O*/220));
[51755e1]793    /* TRANSLATORS: Open a "Terrain file" - i.e. a digital model of the
794     * terrain. */
[7f928d3]795    filemenu->Append(menu_FILE_OPEN_TERRAIN, wmsg(/*Open &Terrain...*/453));
[4b2d207]796    filemenu->AppendCheckItem(menu_FILE_LOG, wmsg(/*Show &Log*/144));
[137bf99]797    filemenu->AppendSeparator();
[42c7efe]798    // wxID_PRINT stock label lacks the ellipses
[7f928d3]799    filemenu->Append(wxID_PRINT, wmsg(/*&Print...\tCtrl+P*/380));
800    filemenu->Append(menu_FILE_PAGE_SETUP, wmsg(/*P&age Setup...*/381));
[ce403f1]801    filemenu->AppendSeparator();
[736f7df]802    /* TRANSLATORS: In the "File" menu */
[7f928d3]803    filemenu->Append(menu_FILE_SCREENSHOT, wmsg(/*&Screenshot...*/201));
804    filemenu->Append(menu_FILE_EXPORT, wmsg(/*&Export as...*/382));
[549eb37]805    /* TRANSLATORS: In the "File" menu - c.f. n:191 */
806    filemenu->Append(menu_FILE_EXTEND, wmsg(/*E&xtended Elevation...*/247));
[b72f4b5]807#ifndef __WXMAC__
808    // On wxMac the "Quit" menu item will be moved elsewhere, so we suppress
809    // this separator.
[223f1ad]810    filemenu->AppendSeparator();
[f930738]811#else
[559cd60]812    // We suppress the "Help" menu under macOS as it would otherwise end up as
[f930738]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);
[b72f4b5]817#endif
[42c7efe]818    filemenu->Append(wxID_EXIT);
[137bf99]819
[cf8c84b]820    m_history.UseMenu(filemenu);
821    m_history.Load(*wxConfigBase::Get());
822
[137bf99]823    wxMenu* rotmenu = new wxMenu;
[736f7df]824    /* TRANSLATORS: "Rotation" menu.  The accelerators must be different within
[de6d5e3]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     */
[ee7511a]829    rotmenu->AppendCheckItem(menu_ROTATION_TOGGLE, wmsg(/*Au&to-Rotate\tSpace*/231));
[137bf99]830    rotmenu->AppendSeparator();
[d86459c]831    rotmenu->Append(menu_ROTATION_REVERSE, wmsg(/*&Reverse Direction*/234));
[137bf99]832
833    wxMenu* orientmenu = new wxMenu;
[d86459c]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));
[137bf99]838    orientmenu->AppendSeparator();
[d86459c]839    orientmenu->Append(menu_ORIENT_PLAN, wmsg(/*&Plan View*/248));
840    orientmenu->Append(menu_ORIENT_ELEVATION, wmsg(/*Ele&vation*/249));
[137bf99]841    orientmenu->AppendSeparator();
[d86459c]842    orientmenu->Append(menu_ORIENT_DEFAULTS, wmsg(/*Restore De&fault View*/254));
[1690fa9]843
[203d2a7]844    wxMenu* presmenu = new wxMenu;
[d86459c]845    presmenu->Append(menu_PRES_NEW, wmsg(/*&New Presentation*/311));
[7f928d3]846    presmenu->Append(menu_PRES_OPEN, wmsg(/*&Open Presentation...*/312));
[d86459c]847    presmenu->Append(menu_PRES_SAVE, wmsg(/*&Save Presentation*/313));
[7f928d3]848    presmenu->Append(menu_PRES_SAVE_AS, wmsg(/*Sa&ve Presentation As...*/314));
[203d2a7]849    presmenu->AppendSeparator();
[736f7df]850    /* TRANSLATORS: "Mark" as in "Mark this position" */
[d86459c]851    presmenu->Append(menu_PRES_MARK, wmsg(/*&Mark*/315));
[736f7df]852    /* TRANSLATORS: "Play" as in "Play back a recording" */
[a2a8d23]853    presmenu->AppendCheckItem(menu_PRES_PLAY, wmsg(/*Pla&y*/316));
[7f928d3]854    presmenu->Append(menu_PRES_EXPORT_MOVIE, wmsg(/*&Export as Movie...*/317));
[1690fa9]855
[203d2a7]856    wxMenu* viewmenu = new wxMenu;
[edb6576]857#ifndef PREFDLG
[736f7df]858    /* TRANSLATORS: Items in the "View" menu: */
[d86459c]859    viewmenu->AppendCheckItem(menu_VIEW_SHOW_NAMES, wmsg(/*Station &Names\tCtrl+N*/270));
[736f7df]860    /* TRANSLATORS: Toggles drawing of 3D passages */
[ee7511a]861    viewmenu->AppendCheckItem(menu_VIEW_SHOW_TUBES, wmsg(/*Passage &Tubes\tCtrl+T*/346));
[4938bcd]862    /* TRANSLATORS: Toggles drawing the surface of the Earth */
[90c628d]863    viewmenu->AppendCheckItem(menu_VIEW_TERRAIN, wmsg(/*Terr&ain*/449));
[d86459c]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));
[edb6576]867    viewmenu->AppendSeparator();
[736f7df]868    /* TRANSLATORS: Here a "survey leg" is a set of measurements between two
869     * "survey stations". */
[d86459c]870    viewmenu->AppendCheckItem(menu_VIEW_SHOW_LEGS, wmsg(/*&Underground Survey Legs\tCtrl+L*/272));
[736f7df]871    /* TRANSLATORS: Here a "survey leg" is a set of measurements between two
872     * "survey stations". */
[d86459c]873    viewmenu->AppendCheckItem(menu_VIEW_SHOW_SURFACE, wmsg(/*&Surface Survey Legs\tCtrl+F*/291));
[8666fc7]874
875    wxMenu* splaymenu = new wxMenu;
[b96edeb]876    /* TRANSLATORS: Item in the "Splay Legs" and "Duplicate Legs" submenus - if
877     * this is selected, such legs are not shown. */
[82ae44a]878    splaymenu->AppendCheckItem(menu_SPLAYS_HIDE, wmsg(/*&Hide*/407));
[b96edeb]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. */
[82ae44a]884    splaymenu->AppendCheckItem(menu_SPLAYS_SHOW_FADED, wmsg(/*&Fade*/408));
[b96edeb]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. */
[82ae44a]887    splaymenu->AppendCheckItem(menu_SPLAYS_SHOW_NORMAL, wmsg(/*&Show*/409));
[9cb97b7]888    viewmenu->Append(menu_VIEW_SPLAYS, wmsg(/*Spla&y Legs*/406), splaymenu);
[8666fc7]889
[e5c5f3c]890    wxMenu* dupemenu = new wxMenu;
[2299161]891    dupemenu->AppendCheckItem(menu_DUPES_HIDE, wmsg(/*&Hide*/407));
892    dupemenu->AppendCheckItem(menu_DUPES_SHOW_DASHED, wmsg(/*&Dashed*/250));
[b96edeb]893    dupemenu->AppendCheckItem(menu_DUPES_SHOW_FADED, wmsg(/*&Fade*/408));
[2299161]894    dupemenu->AppendCheckItem(menu_DUPES_SHOW_NORMAL, wmsg(/*&Show*/409));
895    viewmenu->Append(menu_VIEW_DUPES, wmsg(/*&Duplicate Legs*/251), dupemenu);
[e5c5f3c]896
[edb6576]897    viewmenu->AppendSeparator();
[d86459c]898    viewmenu->AppendCheckItem(menu_VIEW_SHOW_OVERLAPPING_NAMES, wmsg(/*&Overlapping Names*/273));
[46beda0]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));
[fc43dda]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));
[46beda0]906    colourbymenu->AppendCheckItem(menu_COLOUR_BY_GRADIENT, wmsg(/*Colour by &Gradient*/85));
907    colourbymenu->AppendCheckItem(menu_COLOUR_BY_LENGTH, wmsg(/*Colour by &Length*/82));
[68fb07a]908    colourbymenu->AppendCheckItem(menu_COLOUR_BY_SURVEY, wmsg(/*Colour by &Survey*/448));
[36f0d86]909    colourbymenu->AppendCheckItem(menu_COLOUR_BY_STYLE, wmsg(/*Colour by St&yle*/482));
[46beda0]910
[90c628d]911    viewmenu->Append(menu_VIEW_COLOUR_BY, wmsg(/*Co&lour by*/450), colourbymenu);
[46beda0]912
[edb6576]913    viewmenu->AppendSeparator();
[d86459c]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));
[edb6576]917    viewmenu->AppendSeparator();
918#else
[de6d5e3]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     */
[d86459c]923    viewmenu-> Append(menu_VIEW_CANCEL_DIST_LINE, wmsg(/*&Cancel Measuring Line\tEscape*/281));
[edb6576]924#endif
[d86459c]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));
[736f7df]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. */
[ee7511a]931    viewmenu->AppendCheckItem(menu_VIEW_FOG, wmsg(/*Fade Distant Ob&jects*/239));
[736f7df]932    /* TRANSLATORS: Here a "survey leg" is a set of measurements between two
933     * "survey stations". */
[5c2bff7]934    viewmenu->AppendCheckItem(menu_VIEW_SMOOTH_LINES, wmsg(/*Smoot&hed Survey Legs*/298));
[6abab84]935    viewmenu->AppendSeparator();
[85c83ac5]936#ifdef __WXMAC__
[559cd60]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
[8962fc9]939    // Ctrl-Command-F which in wxWidgets terms is RawCtrl+Ctrl+F.
[85c83ac5]940    wxString wxmac_fullscreen = wmsg(/*Full Screen &Mode\tF11*/356);
[8962fc9]941    wxmac_fullscreen.Replace(wxT("\tF11"), wxT("\tRawCtrl+Ctrl+F"), false);
[85c83ac5]942    viewmenu->AppendCheckItem(menu_VIEW_FULLSCREEN, wxmac_fullscreen);
[559cd60]943    // FIXME: On macOS, the standard wording here is "Enter Full Screen" and
[98bd0a0]944    // "Exit Full Screen", depending whether we are in full screen mode or not,
945    // and this isn't a checked menu item.
[85c83ac5]946#else
[ee7511a]947    viewmenu->AppendCheckItem(menu_VIEW_FULLSCREEN, wmsg(/*Full Screen &Mode\tF11*/356));
[85c83ac5]948#endif
[edb6576]949#ifdef PREFDLG
[203d2a7]950    viewmenu->AppendSeparator();
[7f928d3]951    viewmenu-> Append(wxID_PREFERENCES, wmsg(/*&Preferences...*/347));
[edb6576]952#endif
953
954#ifndef PREFDLG
955    wxMenu* ctlmenu = new wxMenu;
[d86459c]956    ctlmenu->AppendCheckItem(menu_CTL_REVERSE, wmsg(/*&Reverse Sense\tCtrl+R*/280));
[edb6576]957    ctlmenu->AppendSeparator();
[bf2d0ba]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.
[d86459c]971    ctlmenu->Append(menu_CTL_CANCEL_DIST_LINE, wmsg(/*&Cancel Measuring Line\tEscape*/281));
[bf2d0ba]972#endif
[edb6576]973    ctlmenu->AppendSeparator();
974    wxMenu* indmenu = new wxMenu;
[d86459c]975    indmenu->AppendCheckItem(menu_IND_COMPASS, wmsg(/*&Compass*/274));
976    indmenu->AppendCheckItem(menu_IND_CLINO, wmsg(/*C&linometer*/275));
[736f7df]977    /* TRANSLATORS: The "Colour Key" is the thing in aven showing which colour
978     * corresponds to which depth, date, survey closure error, etc. */
[97ea48d]979    indmenu->AppendCheckItem(menu_IND_COLOUR_KEY, wmsg(/*Colour &Key*/276));
[d86459c]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));
[edb6576]983    ctlmenu->AppendSeparator();
[d86459c]984    ctlmenu->AppendCheckItem(menu_CTL_METRIC, wmsg(/*&Metric*/342));
985    ctlmenu->AppendCheckItem(menu_CTL_DEGREES, wmsg(/*&Degrees*/343));
[d171c0c]986    ctlmenu->AppendCheckItem(menu_CTL_PERCENT, wmsg(/*&Percent*/430));
[edb6576]987#endif
[137bf99]988
[c988ae4]989    wxMenuBar* menubar = new wxMenuBar();
[736f7df]990    /* TRANSLATORS: Aven menu titles.  An “&” goes before the letter of any
991     * accelerator key.  The accelerators must be different within this group
992     */
[d86459c]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));
[5de0cac]997#ifndef PREFDLG
[d86459c]998    menubar->Append(ctlmenu, wmsg(/*&Controls*/214));
[5de0cac]999#endif
[adc7f92]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.
[d86459c]1003    menubar->Append(presmenu, wmsg(/*&Presentation*/216));
[b72f4b5]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.
[c5dd692]1007    wxMenu* helpmenu = new wxMenu;
1008    helpmenu->Append(wxID_ABOUT);
1009
[d86459c]1010    menubar->Append(helpmenu, wmsg(/*&Help*/215));
[b72f4b5]1011#endif
[137bf99]1012    SetMenuBar(menubar);
[9d3d8cc]1013}
[137bf99]1014
[242cb07]1015void MainFrm::MakeToolBar()
[9d3d8cc]1016{
[242cb07]1017    // Make the toolbar.
[137bf99]1018
[562ca6a]1019#ifdef USING_GENERIC_TOOLBAR
[2737db5]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);
[562ca6a]1026    wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
[2737db5]1027    sizer->Add(toolbar, 0, wxEXPAND);
[562ca6a]1028    SetSizer(sizer);
[2737db5]1029#else
[9d3d8cc]1030    wxToolBar* toolbar = wxFrame::CreateToolBar();
[2737db5]1031#endif
[137bf99]1032
[b65c13f]1033#ifndef __WXGTK20__
[d94f5db]1034    toolbar->SetMargins(5, 5);
1035#endif
1036
[9b540e6]1037    // FIXME: TRANSLATE tooltips
[b4f28e0]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"));
[4b2d207]1040    toolbar->AddCheckTool(menu_FILE_LOG, wxT("View log"), TOOL(log), wxNullBitmap, wxT("View log from processing survey data"));
[2d9ed8ad]1041    toolbar->AddSeparator();
[b4f28e0]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"));
[2d9ed8ad]1046    toolbar->AddSeparator();
[b4f28e0]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"));
[2d9ed8ad]1052    toolbar->AddSeparator();
[b4f28e0]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"));
[4938bcd]1056    toolbar->AddCheckTool(menu_VIEW_TERRAIN, wxT("Terrain"), TOOL(solid_surface), wxNullBitmap, wxT("Show terrain"));
[128fac4]1057    toolbar->AddSeparator();
[b4f28e0]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"));
[fd6e0d5]1066
[203d2a7]1067    toolbar->AddSeparator();
[5627cbb]1068    m_FindBox = new wxTextCtrl(toolbar, textctrl_FIND, wxString(), wxDefaultPosition,
[429465a]1069                               wxDefaultSize, wxTE_PROCESS_ENTER);
[fd6e0d5]1070    toolbar->AddControl(m_FindBox);
[736f7df]1071    /* TRANSLATORS: "Find stations" button tooltip */
[b4f28e0]1072    toolbar->AddTool(wxID_FIND, wmsg(/*Find*/332), TOOL(find)/*, "Search for station name"*/);
[736f7df]1073    /* TRANSLATORS: "Hide stations" button default tooltip */
[b4f28e0]1074    toolbar->AddTool(button_HIDE, wmsg(/*Hide*/333), TOOL(hideresults)/*, "Hide search results"*/);
[fd6e0d5]1075
[2d9ed8ad]1076    toolbar->Realize();
[9d3d8cc]1077}
[2d9ed8ad]1078
[9d3d8cc]1079void MainFrm::CreateSidePanel()
1080{
[b1de3e0]1081    m_Splitter = new AvenSplitterWindow(this);
[562ca6a]1082#ifdef USING_GENERIC_TOOLBAR
[2737db5]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
[421b7d2]1089
[203d2a7]1090    m_Notebook = new wxNotebook(m_Splitter, 400, wxDefaultPosition,
[b63cf6cc]1091                                wxDefaultSize,
[e83fa9fc]1092                                wxBK_BOTTOM);
[203d2a7]1093    m_Notebook->Show(false);
[f433fda]1094
[1be3a88]1095    wxPanel * panel = new wxPanel(m_Notebook);
1096    m_Tree = new AvenTreeCtrl(this, panel);
[156dc16]1097
[429465a]1098//    m_RegexpCheckBox = new wxCheckBox(find_panel, -1,
[ee7511a]1099//                                    msg(/*Regular expression*/));
[2c30188]1100
1101    wxBoxSizer *panel_sizer = new wxBoxSizer(wxVERTICAL);
1102    panel_sizer->Add(m_Tree, 1, wxALL | wxEXPAND, 2);
[1be3a88]1103    panel->SetAutoLayout(true);
1104    panel->SetSizer(panel_sizer);
1105//    panel_sizer->SetSizeHints(panel);
[156dc16]1106
[5876fcb]1107    m_Control = new GUIControl();
1108    m_Gfx = new GfxCore(this, m_Splitter, m_Control);
1109    m_Control->SetView(m_Gfx);
[156dc16]1110
[203d2a7]1111    // Presentation panel:
[1be3a88]1112    wxPanel * prespanel = new wxPanel(m_Notebook);
[203d2a7]1113
[1be3a88]1114    m_PresList = new AvenPresList(this, prespanel, m_Gfx);
[f433fda]1115
[203d2a7]1116    wxBoxSizer *pres_panel_sizer = new wxBoxSizer(wxVERTICAL);
1117    pres_panel_sizer->Add(m_PresList, 1, wxALL | wxEXPAND, 2);
[1be3a88]1118    prespanel->SetAutoLayout(true);
1119    prespanel->SetSizer(pres_panel_sizer);
[1690fa9]1120
[203d2a7]1121    // Overall tabbed structure:
[c5032bb0]1122    // FIXME: this assumes images are 15x15
1123    wxImageList* image_list = new wxImageList(15, 15);
[b4f28e0]1124    image_list->Add(TOOL(survey_tree));
1125    image_list->Add(TOOL(pres_tree));
[203d2a7]1126    m_Notebook->SetImageList(image_list);
[736f7df]1127    /* TRANSLATORS: labels for tabbed side panel this is for the tab with the
1128     * tree hierarchy of survey station names */
[5627cbb]1129    m_Notebook->AddPage(panel, wmsg(/*Surveys*/376), true, 0);
1130    m_Notebook->AddPage(prespanel, wmsg(/*Presentation*/377), false, 1);
[203d2a7]1131
[e9b97eb]1132    m_Splitter->Initialize(m_Gfx);
[068b4f2]1133}
[137bf99]1134
[373b738]1135bool MainFrm::LoadData(const wxString& file, const wxString& prefix)
[b88b171]1136{
[137bf99]1137    // Load survey data from file, centre the dataset around the origin,
[f4fae12]1138    // and prepare the data for drawing.
[137bf99]1139
[c9427d2]1140#if 0
1141    wxStopWatch timer;
1142    timer.Start();
1143#endif
[b88b171]1144
[373b738]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    }
[17a38ded]1165    m_Tree->FillTree(root_name);
[373b738]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
[d4650b3]1185#if 0
[57a3cd4]1186// Run along a newly read in traverse and make up plausible LRUD where
1187// it is missing.
1188void
[c61aa79]1189MainFrm::FixLRUD(traverse & centreline)
[57a3cd4]1190{
1191    assert(centreline.size() > 1);
1192
1193    Double last_size = 0;
[d4650b3]1194    vector<PointInfo>::iterator i = centreline.begin();
[57a3cd4]1195    while (i != centreline.end()) {
[4c9e87b]1196        // Get the coordinates of this vertex.
[db36155]1197        Point & pt_v = *i++;
[57a3cd4]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}
[d4650b3]1254#endif
[57a3cd4]1255
[732b9b0]1256void MainFrm::OnMRUFile(wxCommandEvent& event)
1257{
1258    wxString f(m_history.GetHistoryFile(event.GetId() - wxID_FILE1));
1259    if (!f.empty()) OpenFile(f);
1260}
1261
[bc73093]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
[5f06d2c]1277void MainFrm::OpenFile(const wxString& file, const wxString& survey)
[137bf99]1278{
[4b1fc48]1279    wxBusyCursor hourglass;
[bc73093]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")) {
[d7b53e3]1286            CavernLogWindow * log = new CavernLogWindow(this, survey, m_Splitter);
[fb5887c]1287            wxWindow * win = m_Splitter->GetWindow1();
1288            m_Splitter->ReplaceWindow(win, log);
[fd96020]1289            win->Show(false);
[fb5887c]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
[8991d7f]1296            if (wxFileExists(file)) AddToFileHistory(file);
1297            log->process(file);
1298            // Log window will tell us to load file if it successfully completes.
[bc73093]1299            return;
[eb5b48df]1300        }
[137bf99]1301    }
[bc73093]1302
1303    if (!LoadData(file, survey))
1304        return;
1305    AddToFileHistory(file);
[5e0b9f9d]1306    InitialiseAfterLoad(file, survey);
[d7b53e3]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    }
[bc73093]1315}
1316
[5e0b9f9d]1317void MainFrm::InitialiseAfterLoad(const wxString & file, const wxString & prefix)
[bc73093]1318{
[486c619]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    }
[bc73093]1332
[fb5887c]1333    // Do this before we potentially delete the log window which may own the
1334    // wxString which parameter file refers to!
[bc73093]1335    bool same_file = (file == m_File);
1336    if (!same_file)
1337        m_File = file;
[5e0b9f9d]1338    m_Survey = prefix;
[bc73093]1339
[fb5887c]1340    wxWindow * win = NULL;
1341    if (m_Splitter->GetWindow2() == NULL) {
1342        win = m_Splitter->GetWindow1();
1343        if (win == m_Gfx) win = NULL;
1344    }
1345
[486c619]1346    if (!IsFullScreen()) {
1347        m_Splitter->SplitVertically(m_Notebook, m_Gfx, m_SashPosition);
1348    } else {
1349        was_showing_sidepanel_before_fullscreen = true;
1350    }
[fb5887c]1351
[bc73093]1352    m_Gfx->Initialise(same_file);
[d7b53e3]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
[486c619]1362    if (!IsFullScreen()) {
1363        m_Notebook->Show(true);
1364    }
[bc73093]1365
1366    m_Gfx->Show(true);
1367    m_Gfx->SetFocus();
[d7b53e3]1368}
[fb5887c]1369
[d7b53e3]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();
[137bf99]1385}
1386
1387//
1388//  UI event handlers
1389//
1390
[c988ae4]1391// For Unix we want "*.svx;*.SVX" while for Windows we only want "*.svx".
1392#ifdef _WIN32
1393# define CASE(X)
1394#else
[1c55fb5]1395# define CASE(X) ";" X
[496a208]1396#endif
1397
[137bf99]1398void MainFrm::OnOpen(wxCommandEvent&)
1399{
1400#ifdef __WXMOTIF__
[5627cbb]1401    wxString filetypes = wxT("*.3d");
[137bf99]1402#else
[c988ae4]1403    wxString filetypes;
[d9cd8ae]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")
[1c55fb5]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")
[5627cbb]1411                     "|%s|%s"),
[736f7df]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                      */
[6e63fd3]1416                     wmsg(/*All survey files*/229).c_str(),
[0b8c321]1417                     /* TRANSLATORS: Survex is the name of the software, and "3d" refers to a
1418                      * file extension, so neither should be translated. */
[5627cbb]1419                     wmsg(/*Survex 3d files*/207).c_str(),
[0b8c321]1420                     /* TRANSLATORS: Survex is the name of the software, and "svx" refers to a
1421                      * file extension, so neither should be translated. */
[6e63fd3]1422                     wmsg(/*Survex svx files*/329).c_str(),
[736f7df]1423                     /* TRANSLATORS: "Compass" as in Larry Fish’s cave
1424                      * surveying package, so probably shouldn’t be translated
1425                      */
[5627cbb]1426                     wmsg(/*Compass PLT files*/324).c_str(),
[736f7df]1427                     /* TRANSLATORS: "Compass" as in Larry Fish’s cave
[0b8c321]1428                      * surveying package, so should not be translated
[736f7df]1429                      */
[6e63fd3]1430                     wmsg(/*Compass DAT and MAK files*/330).c_str(),
[736f7df]1431                     /* TRANSLATORS: "CMAP" is Bob Thrun’s cave surveying
1432                      * package, so don’t translate it. */
[5627cbb]1433                     wmsg(/*CMAP XYZ files*/325).c_str(),
1434                     wmsg(/*All files*/208).c_str(),
[c988ae4]1435                     wxFileSelectorDefaultWildcardStr);
[5627cbb]1436#endif
[736f7df]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.
[b49ac56]1440     *
[736f7df]1441     * File->Open dialog: */
[6e63fd3]1442    wxFileDialog dlg(this, wmsg(/*Select a survey file to view*/206),
[5627cbb]1443                     wxString(), wxString(),
[c988ae4]1444                     filetypes, wxFD_OPEN|wxFD_FILE_MUST_EXIST);
[137bf99]1445    if (dlg.ShowModal() == wxID_OK) {
[421b7d2]1446        OpenFile(dlg.GetPath());
[137bf99]1447    }
1448}
1449
[622460e]1450void MainFrm::OnOpenTerrain(wxCommandEvent&)
1451{
1452    if (!m_Gfx) return;
1453
[373b738]1454    if (GetCSProj().empty()) {
[ceb2edb]1455        wxMessageBox(wxT("No coordinate system specified in survey data"));
1456        return;
1457    }
1458
[622460e]1459#ifdef __WXMOTIF__
[112f80c]1460    wxString filetypes = wxT("*.*");
[622460e]1461#else
1462    wxString filetypes;
[112f80c]1463    filetypes.Printf(wxT("%s|*.bil;*.hgt;*.zip" CASE("*.BIL;*.HGT;*.ZIP")
[622460e]1464                     "|%s|%s"),
1465                     wmsg(/*Terrain files*/452).c_str(),
1466                     wmsg(/*All files*/208).c_str(),
1467                     wxFileSelectorDefaultWildcardStr);
1468#endif
[51755e1]1469    /* TRANSLATORS: "Terrain file" being a digital model of the terrain (e.g. a
1470     * grid of height values). */
[622460e]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
[d7b53e3]1479void MainFrm::OnShowLog(wxCommandEvent&)
1480{
[4b2d207]1481    if (!m_Log) {
1482        HideLog(m_Splitter->GetWindow1());
1483        return;
1484    }
[d7b53e3]1485    wxWindow * win = m_Splitter->GetWindow1();
1486    m_Splitter->ReplaceWindow(win, m_Log);
[fd96020]1487    win->Show(false);
[d7b53e3]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
[045e2af]1497void MainFrm::OnScreenshot(wxCommandEvent&)
1498{
[f302cbaa]1499    wxString baseleaf;
1500    wxFileName::SplitPath(m_File, NULL, NULL, &baseleaf, NULL, wxPATH_NATIVE);
[736f7df]1501    /* TRANSLATORS: title of the save screenshot dialog */
[5627cbb]1502    wxFileDialog dlg(this, wmsg(/*Save Screenshot*/321), wxString(),
[f302cbaa]1503                     baseleaf + wxT(".png"),
[5627cbb]1504                     wxT("*.png"), wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
[045e2af]1505    if (dlg.ShowModal() == wxID_OK) {
1506        static bool png_handled = false;
1507        if (!png_handled) {
[4a0e6b35]1508#if 0 // FIXME : enable this to allow other export formats...
[ee41e88]1509            ::wxInitAllImageHandlers();
1510#else
[045e2af]1511            wxImage::AddHandler(new wxPNGHandler);
[ee41e88]1512#endif
[045e2af]1513            png_handled = true;
1514        }
1515        if (!m_Gfx->SaveScreenshot(dlg.GetPath(), wxBITMAP_TYPE_PNG)) {
[0804fbe]1516            wxGetApp().ReportError(wxString::Format(wmsg(/*Error writing to file “%s”*/110), dlg.GetPath().c_str()));
[045e2af]1517        }
1518    }
1519}
1520
1521void MainFrm::OnScreenshotUpdate(wxUpdateUIEvent& event)
1522{
1523    event.Enable(!m_File.empty());
1524}
1525
[02c7c1a]1526void MainFrm::OnFilePreferences(wxCommandEvent&)
1527{
[edb6576]1528#ifdef PREFDLG
[203d2a7]1529    m_PrefsDlg = new PrefsDlg(m_Gfx, this);
[02c7c1a]1530    m_PrefsDlg->Show(true);
[edb6576]1531#endif
[02c7c1a]1532}
[8000d8f]1533
[ce403f1]1534void MainFrm::OnPrint(wxCommandEvent&)
1535{
[60d7755]1536    m_Gfx->OnPrint(m_File, GetSurveyTitle(), GetDateString());
[ce403f1]1537}
1538
[4ed8154]1539void MainFrm::PrintAndExit()
1540{
[60d7755]1541    m_Gfx->OnPrint(m_File, GetSurveyTitle(), GetDateString(), true);
[4ed8154]1542}
1543
[ce403f1]1544void MainFrm::OnPageSetup(wxCommandEvent&)
1545{
[e0ffc2c]1546    wxPageSetupDialog dlg(this, wxGetApp().GetPageSetupDialogData());
1547    if (dlg.ShowModal() == wxID_OK) {
1548        wxGetApp().SetPageSetupDialogData(dlg.GetPageSetupData());
1549    }
[ce403f1]1550}
1551
[223f1ad]1552void MainFrm::OnExport(wxCommandEvent&)
1553{
[60d7755]1554    m_Gfx->OnExport(m_File, GetSurveyTitle(), GetDateString());
[223f1ad]1555}
1556
[549eb37]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
[137bf99]1602void MainFrm::OnQuit(wxCommandEvent&)
1603{
[1690fa9]1604    if (m_PresList->Modified()) {
1605        // FIXME: better to ask "Do you want to save your changes?" and offer [Save] [Discard] [Cancel]
[736f7df]1606        /* TRANSLATORS: and the question in that box */
[5627cbb]1607        if (wxMessageBox(wmsg(/*The current presentation has been modified.  Abandon unsaved changes?*/327),
[736f7df]1608                         /* TRANSLATORS: title of message box */
[5627cbb]1609                         wmsg(/*Modified Presentation*/326),
[1690fa9]1610                         wxOK|wxCANCEL|wxICON_QUESTION) == wxCANCEL) {
1611            return;
1612        }
1613    }
[3db0798]1614    wxConfigBase *b = wxConfigBase::Get();
1615    if (IsFullScreen()) {
[5627cbb]1616        b->Write(wxT("width"), -2);
1617        b->DeleteEntry(wxT("height"));
[3db0798]1618    } else if (IsMaximized()) {
[5627cbb]1619        b->Write(wxT("width"), -1);
1620        b->DeleteEntry(wxT("height"));
[3db0798]1621    } else {
1622        int width, height;
1623        GetSize(&width, &height);
[5627cbb]1624        b->Write(wxT("width"), width);
1625        b->Write(wxT("height"), height);
[3db0798]1626    }
1627    b->Flush();
[137bf99]1628    exit(0);
1629}
1630
1631void MainFrm::OnClose(wxCloseEvent&)
1632{
[3db0798]1633    wxCommandEvent dummy;
1634    OnQuit(dummy);
[137bf99]1635}
1636
1637void MainFrm::OnAbout(wxCommandEvent&)
1638{
[261ab22]1639#ifdef __WXMAC__
[559cd60]1640    // GetIcon() returns an invalid wxIcon under macOS.
[261ab22]1641    AboutDlg dlg(this, wxICON(aven));
1642#else
[ea1ea51]1643    AboutDlg dlg(this, GetIcon());
[261ab22]1644#endif
[796aee3]1645    dlg.Centre();
1646    dlg.ShowModal();
[137bf99]1647}
[c6d95d8]1648
[7c29c976]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);
[0633bcc]1656        GetStatusBar()->SetStatusText(distfree_text, 1);
[7c29c976]1657    } else {
[5627cbb]1658        GetStatusBar()->SetStatusText(wxString());
1659        GetStatusBar()->SetStatusText(wxString(), 1);
[7c29c976]1660    }
1661}
1662
[1fd2edb]1663void MainFrm::ClearTreeSelection()
1664{
1665    m_Tree->UnselectAll();
[19026f3]1666    m_Gfx->SetThere();
[381ae6e]1667    ShowInfo();
[1fd2edb]1668}
1669
[156dc16]1670void MainFrm::ClearCoords()
1671{
[4b031c0]1672    if (!coords_text.empty()) {
[5627cbb]1673        coords_text = wxString();
[4b031c0]1674        UpdateStatusBar();
1675    }
[156dc16]1676}
1677
[d67450e]1678void MainFrm::SetCoords(const Vector3 &v)
[d877aa2]1679{
[ccb83b7]1680    Double x = v.GetX();
1681    Double y = v.GetY();
1682    Double z = v.GetZ();
1683    int units;
[d877aa2]1684    if (m_Gfx->GetMetric()) {
[ccb83b7]1685        units = /*m*/424;
[d877aa2]1686    } else {
[ccb83b7]1687        x /= METRES_PER_FOOT;
1688        y /= METRES_PER_FOOT;
1689        z /= METRES_PER_FOOT;
1690        units = /*ft*/428;
[d877aa2]1691    }
[736f7df]1692    /* TRANSLATORS: show coordinates (N = North or Northing, E = East or
1693     * Easting) */
[ccb83b7]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());
[5627cbb]1698    distfree_text = wxString();
[7c29c976]1699    UpdateStatusBar();
[d877aa2]1700}
1701
[0633bcc]1702const LabelInfo * MainFrm::GetTreeSelection() const {
1703    wxTreeItemData* sel_wx;
1704    if (!m_Tree->GetSelectionData(&sel_wx)) return NULL;
1705
[44ed489]1706    const TreeData* data = static_cast<const TreeData*>(sel_wx);
[0633bcc]1707    if (!data->IsStation()) return NULL;
1708
1709    return data->GetLabel();
1710}
1711
[381ae6e]1712void MainFrm::SetCoords(Double x, Double y, const LabelInfo * there)
[156dc16]1713{
[7c29c976]1714    wxString & s = coords_text;
[33b2094]1715    if (m_Gfx->GetMetric()) {
[5627cbb]1716        s.Printf(wmsg(/*%.2f E, %.2f N*/338), x, y);
[7a89dc2]1717    } else {
[5627cbb]1718        s.Printf(wmsg(/*%.2f E, %.2f N*/338),
[3d4bee1]1719                 x / METRES_PER_FOOT, y / METRES_PER_FOOT);
[7a89dc2]1720    }
[0633bcc]1721
[c00c6713]1722    wxString & t = distfree_text;
[5627cbb]1723    t = wxString();
[381ae6e]1724    if (m_Gfx->ShowingMeasuringLine() && there) {
[373b738]1725        auto offset = GetOffset();
1726        Vector3 delta(x - offset.GetX() - there->GetX(),
1727                      y - offset.GetY() - there->GetY(), 0);
[0633bcc]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;
[736f7df]1733        /* TRANSLATORS: Used in Aven:
[113731f]1734         * From <stationname>: H 12.24m, Brg 234.5°
1735         */
[7b9b700]1736        from_str.Printf(wmsg(/*From %s*/339), there->name_or_anon().c_str());
[ccb83b7]1737        int brg_unit;
[0633bcc]1738        if (m_Gfx->GetDegrees()) {
[ccb83b7]1739            brg_unit = /*°*/344;
[0633bcc]1740        } else {
1741            brg *= 400.0 / 360.0;
[85dcdcd]1742            brg_unit = /*ᵍ*/345;
[0633bcc]1743        }
1744
[ccb83b7]1745        int units;
[0633bcc]1746        if (m_Gfx->GetMetric()) {
[ccb83b7]1747            units = /*m*/424;
[0633bcc]1748        } else {
[ccb83b7]1749            dh /= METRES_PER_FOOT;
1750            units = /*ft*/428;
[0633bcc]1751        }
[736f7df]1752        /* TRANSLATORS: "H" is short for "Horizontal", "Brg" for "Bearing" (as
1753         * in Compass bearing) */
[113731f]1754        t.Printf(wmsg(/*%s: H %.2f%s, Brg %03.1f%s*/374),
[ccb83b7]1755                 from_str.c_str(), dh, wmsg(units).c_str(),
[113731f]1756                 brg, wmsg(brg_unit).c_str());
[0633bcc]1757    }
1758
[7c29c976]1759    UpdateStatusBar();
[156dc16]1760}
1761
[381ae6e]1762void MainFrm::SetAltitude(Double z, const LabelInfo * there)
[156dc16]1763{
[ccb83b7]1764    double alt = z;
1765    int units;
[33b2094]1766    if (m_Gfx->GetMetric()) {
[ccb83b7]1767        units = /*m*/424;
[7a89dc2]1768    } else {
[ccb83b7]1769        alt /= METRES_PER_FOOT;
1770        units = /*ft*/428;
[7a89dc2]1771    }
[ccb83b7]1772    coords_text.Printf(wxT("%s %.2f%s"), wmsg(/*Altitude*/335).c_str(),
1773                       alt, wmsg(units).c_str());
[0633bcc]1774
[c00c6713]1775    wxString & t = distfree_text;
[5627cbb]1776    t = wxString();
[381ae6e]1777    if (m_Gfx->ShowingMeasuringLine() && there) {
[373b738]1778        Double dz = z - GetOffset().GetZ() - there->GetZ();
[0633bcc]1779
1780        wxString from_str;
[7b9b700]1781        from_str.Printf(wmsg(/*From %s*/339), there->name_or_anon().c_str());
[0633bcc]1782
[ccb83b7]1783        if (!m_Gfx->GetMetric()) {
1784            dz /= METRES_PER_FOOT;
[0633bcc]1785        }
[736f7df]1786        // TRANSLATORS: "V" is short for "Vertical"
[ccb83b7]1787        t.Printf(wmsg(/*%s: V %.2f%s*/375), from_str.c_str(),
1788                 dz, wmsg(units).c_str());
[0633bcc]1789    }
1790
[7c29c976]1791    UpdateStatusBar();
[2a3d328]1792}
1793
[381ae6e]1794void MainFrm::ShowInfo(const LabelInfo *here, const LabelInfo *there)
[7a89dc2]1795{
1796    assert(m_Gfx);
[f433fda]1797
[0633bcc]1798    if (!here) {
[7c29c976]1799        m_Gfx->SetHere();
[570d62c3]1800        m_Tree->SetHere(wxTreeItemId());
[1235f9b]1801        // Don't clear "There" mark here.
[4b031c0]1802        if (here_text.empty() && dist_text.empty()) return;
[5627cbb]1803        here_text = wxString();
1804        dist_text = wxString();
[7c29c976]1805        UpdateStatusBar();
1806        return;
1807    }
1808
[373b738]1809    Vector3 v = *here + GetOffset();
[7c29c976]1810    wxString & s = here_text;
[ccb83b7]1811    Double x = v.GetX();
1812    Double y = v.GetY();
1813    Double z = v.GetZ();
1814    int units;
[33b2094]1815    if (m_Gfx->GetMetric()) {
[ccb83b7]1816        units = /*m*/424;
[7a89dc2]1817    } else {
[ccb83b7]1818        x /= METRES_PER_FOOT;
1819        y /= METRES_PER_FOOT;
1820        z /= METRES_PER_FOOT;
1821        units = /*ft*/428;
[7a89dc2]1822    }
[ccb83b7]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());
[5627cbb]1826    s += wxT(": ");
[f153906]1827    s += here->name_or_anon();
[381ae6e]1828    m_Gfx->SetHere(here);
[570d62c3]1829    m_Tree->SetHere(here->tree_id);
[7a89dc2]1830
[381ae6e]1831    if (m_Gfx->ShowingMeasuringLine() && there) {
1832        Vector3 delta = *here - *there;
[d67450e]1833
[87d2ab6]1834        Double d_horiz = sqrt(delta.GetX()*delta.GetX() +
1835                              delta.GetY()*delta.GetY());
[0633bcc]1836        Double dr = delta.magnitude();
[87d2ab6]1837        Double dz = delta.GetZ();
[d67450e]1838
[0633bcc]1839        Double brg = deg(atan2(delta.GetX(), delta.GetY()));
1840        if (brg < 0) brg += 360;
[421b7d2]1841
[533d8e0]1842        Double grd = deg(atan2(delta.GetZ(), d_horiz));
1843
[0633bcc]1844        wxString from_str;
[7b9b700]1845        from_str.Printf(wmsg(/*From %s*/339), there->name_or_anon().c_str());
[203d2a7]1846
[0633bcc]1847        wxString hv_str;
1848        if (m_Gfx->GetMetric()) {
[ccb83b7]1849            units = /*m*/424;
[0633bcc]1850        } else {
[ccb83b7]1851            d_horiz /= METRES_PER_FOOT;
1852            dr /= METRES_PER_FOOT;
[87d2ab6]1853            dz /= METRES_PER_FOOT;
[ccb83b7]1854            units = /*ft*/428;
[0633bcc]1855        }
[ccb83b7]1856        wxString len_unit = wmsg(units);
[736f7df]1857        /* TRANSLATORS: "H" is short for "Horizontal", "V" for "Vertical" */
[ccb83b7]1858        hv_str.Printf(wmsg(/*H %.2f%s, V %.2f%s*/340),
[87d2ab6]1859                      d_horiz, len_unit.c_str(), dz, len_unit.c_str());
[ccb83b7]1860        int brg_unit;
[0633bcc]1861        if (m_Gfx->GetDegrees()) {
[ccb83b7]1862            brg_unit = /*°*/344;
[7a89dc2]1863        } else {
[0633bcc]1864            brg *= 400.0 / 360.0;
[85dcdcd]1865            brg_unit = /*ᵍ*/345;
[421b7d2]1866        }
[533d8e0]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
[0633bcc]1894        wxString & d = dist_text;
[736f7df]1895        /* TRANSLATORS: "Dist" is short for "Distance", "Brg" for "Bearing" (as
[533d8e0]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),
[ccb83b7]1899                 from_str.c_str(), hv_str.c_str(),
[533d8e0]1900                 dr, len_unit.c_str(),
1901                 brg, wmsg(brg_unit).c_str(),
1902                 grd_str.c_str());
[0633bcc]1903    } else {
[5627cbb]1904        dist_text = wxString();
[0633bcc]1905        m_Gfx->SetThere();
[156dc16]1906    }
[7c29c976]1907    UpdateStatusBar();
[7a89dc2]1908}
1909
[8734409]1910void MainFrm::DisplayTreeInfo(const wxTreeItemData* item)
[7a89dc2]1911{
[8734409]1912    const TreeData* data = static_cast<const TreeData*>(item);
[0642381]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;
[156dc16]1921    }
[0642381]1922    m_Gfx->SetHereSurvey(wxString());
1923    ShowInfo();
[156dc16]1924}
1925
[672459c]1926void MainFrm::TreeItemSelected(const wxTreeItemData* item)
[156dc16]1927{
[44ed489]1928    const TreeData* data = static_cast<const TreeData*>(item);
[156dc16]1929    if (data && data->IsStation()) {
[8734409]1930        const LabelInfo* label = data->GetLabel();
[672459c]1931        if (m_Gfx->GetThere() == label) {
1932            m_Gfx->CentreOn(*label);
1933        } else {
1934            m_Gfx->SetThere(label);
1935        }
[5627cbb]1936        dist_text = wxString();
[7c29c976]1937        // FIXME: Need to update dist_text (From ... etc)
[887c26e]1938        // But we don't currently know where "here" is at this point in the
1939        // code!
[0580c6a]1940    } else {
[5627cbb]1941        dist_text = wxString();
[0580c6a]1942        m_Gfx->SetThere();
[672459c]1943        if (!data) {
1944            // Must be the root.
[0642381]1945            wxCommandEvent dummy;
1946            OnDefaults(dummy);
[672459c]1947        } else {
[0642381]1948            m_Gfx->ZoomToSurvey(data->GetSurvey());
[44ed489]1949        }
1950    }
[7c29c976]1951    UpdateStatusBar();
[156dc16]1952}
1953
[97a414f]1954void MainFrm::OnPresNew(wxCommandEvent&)
[156dc16]1955{
[1690fa9]1956    if (m_PresList->Modified()) {
1957        // FIXME: better to ask "Do you want to save your changes?" and offer [Save] [Discard] [Cancel]
[5627cbb]1958        if (wxMessageBox(wmsg(/*The current presentation has been modified.  Abandon unsaved changes?*/327),
1959                         wmsg(/*Modified Presentation*/326),
[1690fa9]1960                         wxOK|wxCANCEL|wxICON_QUESTION) == wxCANCEL) {
1961            return;
1962        }
[156dc16]1963    }
[bd7607c]1964    m_PresList->New(m_File);
[24961e2]1965    if (!ShowingSidePanel()) ToggleSidePanel();
[c8f929b]1966    // Select the presentation page in the notebook.
1967    m_Notebook->SetSelection(1);
[156dc16]1968}
1969
[97a414f]1970void MainFrm::OnPresOpen(wxCommandEvent&)
[156dc16]1971{
[1690fa9]1972    if (m_PresList->Modified()) {
1973        // FIXME: better to ask "Do you want to save your changes?" and offer [Save] [Discard] [Cancel]
[5627cbb]1974        if (wxMessageBox(wmsg(/*The current presentation has been modified.  Abandon unsaved changes?*/327),
1975                         wmsg(/*Modified Presentation*/326),
[1690fa9]1976                         wxOK|wxCANCEL|wxICON_QUESTION) == wxCANCEL) {
1977            return;
1978        }
1979    }
[156dc16]1980#ifdef __WXMOTIF__
[5627cbb]1981    wxFileDialog dlg(this, wmsg(/*Select a presentation to open*/322), wxString(), wxString(),
1982                     wxT("*.fly"), wxFD_OPEN);
[156dc16]1983#else
[5627cbb]1984    wxFileDialog dlg(this, wmsg(/*Select a presentation to open*/322), wxString(), wxString(),
1985                     wxString::Format(wxT("%s|*.fly|%s|%s"),
[b49ac56]1986                               wmsg(/*Aven presentations*/320).c_str(),
1987                               wmsg(/*All files*/208).c_str(),
1988                               wxFileSelectorDefaultWildcardStr),
[5627cbb]1989                     wxFD_OPEN|wxFD_FILE_MUST_EXIST);
[156dc16]1990#endif
1991    if (dlg.ShowModal() == wxID_OK) {
[1690fa9]1992        if (!m_PresList->Load(dlg.GetPath())) {
1993            return;
1994        }
[97a414f]1995        // FIXME : keep a history of loaded/saved presentations, like we do for
1996        // loaded surveys...
[c8f929b]1997        // Select the presentation page in the notebook.
1998        m_Notebook->SetSelection(1);
[156dc16]1999    }
2000}
[5ffa439]2001
[97a414f]2002void MainFrm::OnPresSave(wxCommandEvent&)
[8000d8f]2003{
[1690fa9]2004    m_PresList->Save(true);
[8000d8f]2005}
2006
[97a414f]2007void MainFrm::OnPresSaveAs(wxCommandEvent&)
[5ffa439]2008{
[1690fa9]2009    m_PresList->Save(false);
[5ffa439]2010}
2011
[97a414f]2012void MainFrm::OnPresMark(wxCommandEvent&)
[5ffa439]2013{
[1690fa9]2014    m_PresList->AddMark();
[5ffa439]2015}
2016
[128fac4]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&)
[5ffa439]2033{
[128fac4]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);
[5ffa439]2055}
2056
[97a414f]2057void MainFrm::OnPresExportMovie(wxCommandEvent&)
[6a4cdcb6]2058{
[f4e4b56]2059#ifdef WITH_LIBAV
[97a414f]2060    // FIXME : Taking the leaf of the currently loaded presentation as the
2061    // default might make more sense?
[f302cbaa]2062    wxString baseleaf;
2063    wxFileName::SplitPath(m_File, NULL, NULL, &baseleaf, NULL, wxPATH_NATIVE);
[6e63fd3]2064    wxFileDialog dlg(this, wmsg(/*Export Movie*/331), wxString(),
[709dd14]2065                     baseleaf + wxT(".mp4"),
[65f6d8e]2066                     wxT("MPEG|*.mp4|OGG|*.ogv|AVI|*.avi|QuickTime|*.mov|WMV|*.wmv;*.asf"),
[4e98397]2067                     wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
[97a414f]2068    if (dlg.ShowModal() == wxID_OK) {
[091069f]2069        // Error is reported by GfxCore.
2070        (void)m_Gfx->ExportMovie(dlg.GetPath());
[6a4cdcb6]2071    }
[f4e4b56]2072#else
2073    wxGetApp().ReportError(wxT("Movie generation support code not present"));
2074#endif
[6a4cdcb6]2075}
2076
[1690fa9]2077PresentationMark MainFrm::GetPresMark(int which)
[5ffa439]2078{
[1690fa9]2079    return m_PresList->GetPresMark(which);
[5ffa439]2080}
2081
[86895ec]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;
[373b738]2092            new_prefix += GetSeparator();
[86895ec]2093        }
2094        new_prefix += survey;
2095    }
[c0aabea]2096    // Reload the processed data rather rather than potentially reprocessing.
2097    if (!LoadData(m_FileProcessed, new_prefix))
[86895ec]2098        return;
2099    InitialiseAfterLoad(m_File, new_prefix);
2100}
2101
[622460e]2102void MainFrm::OnOpenTerrainUpdate(wxUpdateUIEvent& event)
2103{
2104    event.Enable(!m_File.empty());
2105}
[1690fa9]2106
2107void MainFrm::OnPresNewUpdate(wxUpdateUIEvent& event)
[5ffa439]2108{
[1366db2]2109    event.Enable(!m_File.empty());
[5ffa439]2110}
2111
[1690fa9]2112void MainFrm::OnPresOpenUpdate(wxUpdateUIEvent& event)
[5ffa439]2113{
[1690fa9]2114    event.Enable(!m_File.empty());
[5ffa439]2115}
2116
[1690fa9]2117void MainFrm::OnPresSaveUpdate(wxUpdateUIEvent& event)
[5ffa439]2118{
[1690fa9]2119    event.Enable(!m_PresList->Empty());
[5ffa439]2120}
2121
[1690fa9]2122void MainFrm::OnPresSaveAsUpdate(wxUpdateUIEvent& event)
[5ffa439]2123{
[1690fa9]2124    event.Enable(!m_PresList->Empty());
[5ffa439]2125}
2126
[1690fa9]2127void MainFrm::OnPresMarkUpdate(wxUpdateUIEvent& event)
[5ffa439]2128{
[1690fa9]2129    event.Enable(!m_File.empty());
2130}
2131
[128fac4]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)
[1690fa9]2151{
2152    event.Enable(!m_PresList->Empty());
[128fac4]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());
[5ffa439]2178}
[2a02de2]2179
[6a4cdcb6]2180void MainFrm::OnPresExportMovieUpdate(wxUpdateUIEvent& event)
2181{
2182    event.Enable(!m_PresList->Empty());
2183}
2184
[b4fe9fb]2185void MainFrm::OnFind(wxCommandEvent&)
[2a02de2]2186{
[bc60689]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;
[e98af57]2200    wxBusyCursor hourglass;
[44ed489]2201    // Find stations specified by a string or regular expression pattern.
[9d3d8cc]2202
[0580c6a]2203    wxString pattern = m_FindBox->GetValue();
[d1628e8e]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++;
[5a24583]2209            label->clear_flags(LFLAG_HIGHLIGHTED);
[c8aff97]2210        }
[d1628e8e]2211        m_NumHighlighted = 0;
[0c76ad9]2212    } else {
[d1628e8e]2213        int re_flags = wxRE_NOSUB;
2214
2215        if (true /* case insensitive */) {
2216            re_flags |= wxRE_ICASE;
[0c76ad9]2217        }
2218
[d1628e8e]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++) {
[5627cbb]2225               wxChar ch = pattern[i];
[d1628e8e]2226               // ^ only special at start; $ at end.  But this is simpler...
2227               switch (ch) {
2228                case '^': case '$': case '.': case '[': case '\\':
[5627cbb]2229                  pat += wxT('\\');
[d1628e8e]2230                  pat += ch;
2231                  break;
2232                case '*':
[5627cbb]2233                  pat += wxT(".*");
[d1628e8e]2234                  substring = false;
2235                  break;
2236                case '?':
[5627cbb]2237                  pat += wxT('.');
[d1628e8e]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++) {
[5627cbb]2249               wxChar ch = pattern[i];
[d1628e8e]2250               // ^ only special at start; $ at end.  But this is simpler...
2251               switch (ch) {
2252                case '^': case '$': case '*': case '.': case '[': case '\\':
[5627cbb]2253                  pat += wxT('\\');
[d1628e8e]2254               }
2255               pat += ch;
2256            }
2257            pattern = pat;
2258            re_flags |= wxRE_BASIC;
2259        }
[421b7d2]2260
[d1628e8e]2261        if (!substring) {
2262            // FIXME "0u" required to avoid compilation error with g++-3.0
[5627cbb]2263            if (pattern.empty() || pattern[0u] != '^') pattern = wxT('^') + pattern;
[d1628e8e]2264            // FIXME: this fails to cope with "\$" at the end of pattern...
[5627cbb]2265            if (pattern[pattern.size() - 1] != '$') pattern += wxT('$');
[d1628e8e]2266        }
[2a02de2]2267
[d1628e8e]2268        wxRegEx regex;
2269        if (!regex.Compile(pattern, re_flags)) {
2270            wxBell();
2271            return;
2272        }
[2a02de2]2273
[d1628e8e]2274        int found = 0;
[1be3a88]2275
[d1628e8e]2276        list<LabelInfo*>::iterator pos = m_Labels.begin();
2277        while (pos != m_Labels.end()) {
2278            LabelInfo* label = *pos++;
2279
[5a24583]2280            if (regex.Matches(label->GetText())) {
2281                label->set_flags(LFLAG_HIGHLIGHTED);
[d1628e8e]2282                ++found;
2283            } else {
[5a24583]2284                label->clear_flags(LFLAG_HIGHLIGHTED);
[d1628e8e]2285            }
[1be3a88]2286        }
2287
[d1628e8e]2288        m_NumHighlighted = found;
2289
2290        // Re-sort so highlighted points get names in preference
[373b738]2291        if (found) m_Labels.sort(LabelPlotCmp(GetSeparator()));
[d1628e8e]2292    }
[421b7d2]2293
[429465a]2294    m_Gfx->UpdateBlobs();
[fa42426]2295    m_Gfx->ForceRefresh();
[9d3d8cc]2296
[d1628e8e]2297    if (!m_NumHighlighted) {
[857408e]2298        GetToolBar()->SetToolShortHelp(button_HIDE, wmsg(/*No matches were found.*/328));
[a2f4720]2299    } else {
[736f7df]2300        /* TRANSLATORS: "Hide stations" button tooltip when stations are found
2301         */
[857408e]2302        GetToolBar()->SetToolShortHelp(button_HIDE, wxString::Format(wmsg(/*Hide %d found stations*/334).c_str(), m_NumHighlighted));
[9d3d8cc]2303    }
[2a02de2]2304}
2305
[d1628e8e]2306void MainFrm::OnGotoFound(wxCommandEvent&)
[2a02de2]2307{
[d1628e8e]2308    if (!m_NumHighlighted) {
[5627cbb]2309        wxGetApp().ReportError(wmsg(/*No matches were found.*/328));
[d1628e8e]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
[fa42426]2320    list<LabelInfo*>::iterator pos = m_Labels.begin();
2321    while (pos != m_Labels.end()) {
2322        LabelInfo* label = *pos++;
[d1628e8e]2323
[5a24583]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();
[d1628e8e]2331        }
[fa42426]2332    }
[d1628e8e]2333
2334    m_Gfx->SetViewTo(xmin, xmax, ymin, ymax, zmin, zmax);
2335    m_Gfx->SetFocus();
2336}
2337
2338void MainFrm::OnHide(wxCommandEvent&)
2339{
[5627cbb]2340    m_FindBox->SetValue(wxString());
[857408e]2341    GetToolBar()->SetToolShortHelp(button_HIDE, wmsg(/*Hide*/333));
[2a02de2]2342}
[39e460c9]2343
[429465a]2344void MainFrm::OnHideUpdate(wxUpdateUIEvent& ui)
2345{
2346    ui.Enable(m_NumHighlighted != 0);
2347}
2348
[9059368]2349void MainFrm::OnViewSidePanel(wxCommandEvent&)
[b1de3e0]2350{
2351    ToggleSidePanel();
2352}
2353
2354void MainFrm::ToggleSidePanel()
[9059368]2355{
2356    // Toggle display of the side panel.
2357
2358    assert(m_Gfx);
2359
2360    if (m_Splitter->IsSplit()) {
[421b7d2]2361        m_SashPosition = m_Splitter->GetSashPosition(); // save width of panel
[1690fa9]2362        m_Splitter->Unsplit(m_Notebook);
[b63cf6cc]2363    } else {
[203d2a7]2364        m_Notebook->Show(true);
[b1de3e0]2365        m_Gfx->Show(true);
[203d2a7]2366        m_Splitter->SplitVertically(m_Notebook, m_Gfx, m_SashPosition);
[9059368]2367    }
2368}
2369
2370void MainFrm::OnViewSidePanelUpdate(wxUpdateUIEvent& ui)
2371{
[f48d6949]2372    ui.Enable(!m_File.empty());
[203d2a7]2373    ui.Check(ShowingSidePanel());
[9059368]2374}
[203d2a7]2375
2376bool MainFrm::ShowingSidePanel()
2377{
2378    return m_Splitter->IsSplit();
2379}
[fdfa926]2380
2381void MainFrm::ViewFullScreen() {
[e4b87e5]2382#ifdef __WXMAC__
[559cd60]2383    // On macOS, wxWidgets doesn't currently hide the toolbar or statusbar in
[e4b87e5]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
[fdfa926]2392    ShowFullScreen(!IsFullScreen());
[b75a37d]2393    fullscreen_showing_menus = false;
[486c619]2394    if (IsFullScreen())
2395        was_showing_sidepanel_before_fullscreen = ShowingSidePanel();
2396    if (was_showing_sidepanel_before_fullscreen)
2397        ToggleSidePanel();
[e4b87e5]2398
2399#ifdef __WXMAC__
2400    if (!IsFullScreen()) {
2401        GetStatusBar()->Show();
2402        GetToolBar()->Show();
[3465803]2403#ifdef USING_GENERIC_TOOLBAR
2404        Layout();
2405#endif
[e4b87e5]2406    }
2407#endif
[fdfa926]2408}
[b75a37d]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;
[7413829]2419#ifdef __WXMAC__
[559cd60]2420    // On macOS, enabling the menu bar while in full
[7413829]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
[3c65a9f]2426    GetMenuBar()->Show(show);
[b75a37d]2427    fullscreen_showing_menus = show;
[7413829]2428#endif
[b75a37d]2429}
Note: See TracBrowser for help on using the repository browser.