source: git/src/mainfrm.cc

Last change on this file was fd0e32a, checked in by Olly Betts <olly@…>, 3 months ago

Raise wxWidgets requirement to >= 3.2.0

Previously it was >= 3.0.0 but it is starting to become hard to
keep everything working with 3.0.0 and the time and effort seems
better directed to other things.

The last 3.0.x release was 3.0.5.1 released 2020-05-02.

wx3.2.0 was released 2022-07-06 and it seems everywhere with wxWidgets
packages upgraded to 3.2.x some time ago.

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