source: git/src/mainfrm.cc @ 991a128

debug-cidebug-ci-sanitisersfaster-cavernloglog-selectstereo-2025walls-datawalls-data-hanging-as-warningwarn-only-for-hanging-survey
Last change on this file since 991a128 was bf3acff, checked in by Olly Betts <olly@…>, 17 months ago

cavern: Support reading Compass CLP files

These are very like DAT files, except they contain loop-closed
data. The only parsing differences are we need to not apply the
instrument corrections as they've already been applied, and to
work around an apparently Compass bug in handling corrected
backsights.

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