source: git/src/mainfrm.cc @ d9cd8ae

RELEASE/1.2debug-cidebug-ci-sanitisersstereowalls-data
Last change on this file since d9cd8ae was d9cd8ae, checked in by Olly Betts <olly@…>, 10 years ago

src/img.c,src/mainfrm.cc: Although these are often referred to as
"CMAP .XYZ files", it seems that actually, the extension .XYZ isn't
used, rather .SHT (shot variant, produced by CMAP v16 and later),
.UNA (unadjusted) and .ADJ (adjusted) extensions are. Since we've
long checked for .XYZ, we continue to do so in case anyone is
relying on it, but also check for the other extensions.

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