source: git/src/gfxcore.cc @ 48a1982

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

(MS Windows version): aven: Fix cursor keys to pan survey.

git-svn-id: file:///home/survex-svn/survex/trunk@3138 4b37db11-9a0c-4f06-9ece-9ab7cdaee568

  • Property mode set to 100644
File size: 68.9 KB
Line 
1//
2//  gfxcore.cc
3//
4//  Core drawing code for Aven.
5//
6//  Copyright (C) 2000-2001, Mark R. Shinwell.
7//  Copyright (C) 2001-2003,2004,2005 Olly Betts
8//
9//  This program is free software; you can redistribute it and/or modify
10//  it under the terms of the GNU General Public License as published by
11//  the Free Software Foundation; either version 2 of the License, or
12//  (at your option) any later version.
13//
14//  This program is distributed in the hope that it will be useful,
15//  but WITHOUT ANY WARRANTY; without even the implied warranty of
16//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17//  GNU General Public License for more details.
18//
19//  You should have received a copy of the GNU General Public License
20//  along with this program; if not, write to the Free Software
21//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22//
23
24#ifdef HAVE_CONFIG_H
25#include <config.h>
26#endif
27
28#include <assert.h>
29#include <float.h>
30
31#include "aven.h"
32#include "gfxcore.h"
33#include "mainfrm.h"
34#include "message.h"
35#include "useful.h"
36#include "printwx.h"
37#include "export.h"
38
39#include <wx/confbase.h>
40#include <wx/image.h>
41
42#define HEAVEN 5000.0 // altitude of heaven
43#define INTERPOLATE(a, b, t) ((a) + (((b) - (a)) * Double(t) / 100.0))
44#define MAX(a, b) ((a) > (b) ? (a) : (b))
45#define MAX3(a, b, c) ((a) > (b) ? MAX(a, c) : MAX(b, c))
46#define TEXT_COLOUR  wxColour(0, 255, 40)
47#define LABEL_COLOUR wxColour(160, 255, 0)
48
49// Values for m_SwitchingTo
50#define PLAN 1
51#define ELEVATION 2
52
53// How many bins per letter height to use when working out non-overlapping
54// labels.
55const unsigned int QUANTISE_FACTOR = 2;
56
57#ifdef _WIN32
58static const int FONT_SIZE = 8;
59#else
60static const int FONT_SIZE = 9;
61#endif
62static const int CROSS_SIZE = 3;
63static const int COMPASS_OFFSET_X = 60;
64static const int COMPASS_OFFSET_Y = 80;
65static const int INDICATOR_BOX_SIZE = 60;
66static const int INDICATOR_GAP = 2;
67static const int INDICATOR_MARGIN = 5;
68static const int INDICATOR_OFFSET_X = 15;
69static const int INDICATOR_OFFSET_Y = 15;
70static const int CLINO_OFFSET_X = 6 + INDICATOR_OFFSET_X +
71                                  INDICATOR_BOX_SIZE + INDICATOR_GAP;
72static const int DEPTH_BAR_OFFSET_X = 16;
73static const int DEPTH_BAR_EXTRA_LEFT_MARGIN = 2;
74static const int DEPTH_BAR_BLOCK_WIDTH = 20;
75static const int DEPTH_BAR_BLOCK_HEIGHT = 15;
76static const int DEPTH_BAR_MARGIN = 6;
77static const int DEPTH_BAR_OFFSET_Y = 16 + DEPTH_BAR_MARGIN;
78static const int TICK_LENGTH = 4;
79static const int DISPLAY_SHIFT = 50;
80static const int SCALE_BAR_OFFSET_X = 15;
81static const int SCALE_BAR_OFFSET_Y = 12;
82static const int SCALE_BAR_HEIGHT = 12;
83static const int HIGHLIGHTED_PT_SIZE = 2;
84
85const ColourTriple COLOURS[] = {
86    { 0, 0, 0 },       // black
87    { 100, 100, 100 }, // grey
88    { 180, 180, 180 }, // light grey
89    { 140, 140, 140 }, // light grey 2
90    { 90, 90, 90 },    // dark grey
91    { 255, 255, 255 }, // white
92    { 0, 100, 255},    // turquoise
93    { 0, 255, 40 },    // green
94    { 150, 205, 224 }, // indicator 1
95    { 114, 149, 160 }, // indicator 2
96    { 255, 255, 0 },   // yellow
97    { 255, 0, 0 },     // red
98};
99
100#define DELETE_ARRAY(A) do { assert((A)); delete[] (A); } while (0)
101
102#define HITTEST_SIZE 20
103
104BEGIN_EVENT_TABLE(GfxCore, wxWindow)
105    EVT_PAINT(GfxCore::OnPaint)
106    EVT_LEFT_DOWN(GfxCore::OnLButtonDown)
107    EVT_LEFT_UP(GfxCore::OnLButtonUp)
108    EVT_MIDDLE_DOWN(GfxCore::OnMButtonDown)
109    EVT_MIDDLE_UP(GfxCore::OnMButtonUp)
110    EVT_RIGHT_DOWN(GfxCore::OnRButtonDown)
111    EVT_RIGHT_UP(GfxCore::OnRButtonUp)
112    EVT_MOTION(GfxCore::OnMouseMove)
113    EVT_SIZE(GfxCore::OnSize)
114    EVT_IDLE(GfxCore::OnIdle)
115    EVT_CHAR(GfxCore::OnKeyPress)
116END_EVENT_TABLE()
117
118GfxCore::GfxCore(MainFrm* parent, wxWindow* parent_win) :
119    wxWindow(parent_win, 100, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS),
120    m_Font(FONT_SIZE, wxSWISS, wxNORMAL, wxNORMAL, FALSE, "Helvetica",
121           wxFONTENCODING_ISO8859_1),
122    m_InitialisePending(false)
123{
124    m_OffscreenBitmap = NULL;
125    m_LastDrag = drag_NONE;
126    m_ScaleBar.offset_x = SCALE_BAR_OFFSET_X;
127    m_ScaleBar.offset_y = SCALE_BAR_OFFSET_Y;
128    m_ScaleBar.width = 0;
129    m_DraggingLeft = false;
130    m_DraggingMiddle = false;
131    m_DraggingRight = false;
132    m_Parent = parent;
133    m_RotationOK = true;
134    m_DoneFirstShow = false;
135    m_PlotData = NULL;
136    m_RedrawOffscreen = false;
137    m_Crosses = false;
138    m_Legs = true;
139    m_Names = false;
140    m_OverlappingNames = false;
141    m_Compass = true;
142    m_Clino = true;
143    m_Depthbar = true;
144    m_Scalebar = true;
145    m_ReverseControls = false;
146    m_LabelGrid = NULL;
147    m_Rotating = false;
148    m_SwitchingTo = 0;
149    m_Entrances = false;
150    m_FixedPts = false;
151    m_ExportedPts = false;
152    m_Grid = false;
153    wxConfigBase::Get()->Read("metric", &m_Metric, true);
154    wxConfigBase::Get()->Read("degrees", &m_Degrees, true);
155    m_here.x = DBL_MAX;
156    m_there.x = DBL_MAX;
157    clipping = false;
158
159    // Create pens and brushes for drawing.
160    int num_colours = (int) col_LAST;
161    m_Pens = new wxPen[num_colours];
162    m_Brushes = new wxBrush[num_colours];
163    for (int col = 0; col < num_colours; col++) {
164        m_Pens[col].SetColour(COLOURS[col].r, COLOURS[col].g, COLOURS[col].b);
165        assert(m_Pens[col].Ok());
166        m_Brushes[col].SetColour(COLOURS[col].r, COLOURS[col].g, COLOURS[col].b);
167        assert(m_Brushes[col].Ok());
168    }
169
170    SetBackgroundColour(wxColour(0, 0, 0));
171
172    // Initialise grid for hit testing.
173    m_PointGrid = new list<LabelInfo*>[HITTEST_SIZE * HITTEST_SIZE];
174}
175
176GfxCore::~GfxCore()
177{
178    TryToFreeArrays();
179
180    if (m_OffscreenBitmap) {
181        delete m_OffscreenBitmap;
182    }
183
184    DELETE_ARRAY(m_Pens);
185    DELETE_ARRAY(m_Brushes);
186
187    delete[] m_PointGrid;
188}
189
190void GfxCore::TryToFreeArrays()
191{
192    // Free up any memory allocated for arrays.
193
194    if (m_PlotData) {
195        for (int band = 0; band < m_Bands; band++) {
196            DELETE_ARRAY(m_PlotData[band].vertices);
197            DELETE_ARRAY(m_PlotData[band].num_segs);
198            DELETE_ARRAY(m_PlotData[band].surface_vertices);
199            DELETE_ARRAY(m_PlotData[band].surface_num_segs);
200        }
201
202        DELETE_ARRAY(m_PlotData);
203        DELETE_ARRAY(m_Polylines);
204        DELETE_ARRAY(m_SurfacePolylines);
205
206        if (m_LabelGrid) {
207            DELETE_ARRAY(m_LabelGrid);
208            m_LabelGrid = NULL;
209        }
210
211        m_PlotData = NULL;
212    }
213}
214
215//
216//  Initialisation methods
217//
218
219void GfxCore::Initialise()
220{
221    // Initialise the view from the parent holding the survey data.
222
223    TryToFreeArrays();
224
225    if (!m_InitialisePending) {
226        GetSize(&m_XSize, &m_YSize);
227    }
228
229    m_Bands = m_Parent->GetNumDepthBands(); // last band is surface data
230    m_PlotData = new PlotData[m_Bands];
231    m_Polylines = new int[m_Bands];
232    m_SurfacePolylines = new int[m_Bands];
233
234    for (int band = 0; band < m_Bands; band++) {
235        m_PlotData[band].vertices = new Point[m_Parent->GetNumPoints()];
236        m_PlotData[band].num_segs = new int[m_Parent->GetNumLegs()];
237        m_PlotData[band].surface_vertices = new Point[m_Parent->GetNumPoints()];
238        m_PlotData[band].surface_num_segs = new int[m_Parent->GetNumLegs()];
239    }
240
241    m_UndergroundLegs = false;
242    m_SurfaceLegs = false;
243
244    m_HitTestGridValid = false;
245    m_here.x = DBL_MAX;
246    m_there.x = DBL_MAX;
247
248    // Check for flat/linear/point surveys.
249    m_RotationOK = true;
250
251    m_Lock = lock_NONE;
252    if (m_Parent->GetXExtent() == 0.0) m_Lock = LockFlags(m_Lock | lock_X);
253    if (m_Parent->GetYExtent() == 0.0) m_Lock = LockFlags(m_Lock | lock_Y);
254    if (m_Parent->GetZExtent() == 0.0) m_Lock = LockFlags(m_Lock | lock_Z);
255
256    // Scale the survey to a reasonable initial size.
257    switch (m_Lock) {
258        case lock_POINT:
259            m_RotationOK = false;
260            m_InitialScale = 1.0;
261            break;
262        case lock_YZ:
263            m_InitialScale = Double(m_XSize) / m_Parent->GetXExtent();
264            break;
265        case lock_XZ:
266            m_InitialScale = Double(m_YSize) / m_Parent->GetYExtent();
267            break;
268        case lock_XY:
269            m_RotationOK = false;
270            m_InitialScale = Double(m_YSize) / m_Parent->GetZExtent();
271            break;
272        case lock_X:
273            m_RotationOK = false;
274            m_InitialScale = min(Double(m_YSize) / m_Parent->GetZExtent(),
275                                 Double(m_XSize) / m_Parent->GetYExtent());
276            break;
277        case lock_Y:
278            m_RotationOK = false;
279            m_InitialScale = min(Double(m_YSize) / m_Parent->GetZExtent(),
280                                 Double(m_XSize) / m_Parent->GetXExtent());
281            break;
282        default:
283            m_InitialScale = min(Double(m_XSize) / m_Parent->GetXExtent(),
284                                 Double(m_YSize) / m_Parent->GetYExtent());
285    }
286
287    m_InitialScale *= .85;
288
289    // Calculate screen coordinates and redraw.
290    SetScaleInitial(m_InitialScale);
291
292    // Apply default parameters.
293    OnDefaults();
294}
295
296void GfxCore::FirstShow()
297{
298    // Update our record of the client area size and centre.
299    GetClientSize(&m_XSize, &m_YSize);
300    m_XCentre = m_XSize / 2;
301    m_YCentre = m_YSize / 2;
302
303    // Create the offscreen bitmap.
304    m_OffscreenBitmap = new wxBitmap;
305    m_OffscreenBitmap->Create(m_XSize, m_YSize);
306
307    m_DrawDC.SelectObject(*m_OffscreenBitmap);
308
309    m_DoneFirstShow = true;
310
311    RedrawOffscreen();
312}
313
314//
315//  Recalculating methods
316//
317
318void GfxCore::SetScaleInitial(Double scale)
319{
320    SetScale(scale);
321
322    // Invalidate hit-test grid.
323    m_HitTestGridValid = false;
324
325    for (int band = 0; band < m_Bands; band++) {
326        Point* pt = m_PlotData[band].vertices;
327        assert(pt);
328        int* count = m_PlotData[band].num_segs;
329        assert(count);
330        Point* spt = m_PlotData[band].surface_vertices;
331        assert(spt);
332        int* scount = m_PlotData[band].surface_num_segs;
333        assert(scount);
334        count--;
335        scount--;
336
337        m_Polylines[band] = 0;
338        m_SurfacePolylines[band] = 0;
339        Double x = 0, y = 0, z = 0;
340
341        list<PointInfo*>::iterator pos = m_Parent->GetPointsNC(band);
342        list<PointInfo*>::iterator end = m_Parent->GetPointsEndNC(band);
343        bool first_point = true;
344        bool last_was_move = true;
345        bool current_polyline_is_surface = false;
346        while (pos != end) {
347            PointInfo* pti = *pos++;
348
349            if (pti->IsLine()) {
350                // We have a leg.
351
352                assert(!first_point); // The first point must always be a move.
353                bool changing_ug_state = (current_polyline_is_surface != pti->IsSurface());
354                // Record new underground/surface state.
355                current_polyline_is_surface = pti->IsSurface();
356
357                if (changing_ug_state || last_was_move) {
358                    // Start a new polyline if we're switching
359                    // underground/surface state or if the previous point
360                    // was a move.
361                    Point** dest;
362
363                    if (current_polyline_is_surface) {
364                        m_SurfacePolylines[band]++;
365                        // initialise number of vertices for next polyline
366                        *(++scount) = 1;
367                        dest = &spt;
368                    }
369                    else {
370                        m_Polylines[band]++;
371                        // initialise number of vertices for next polyline
372                        *(++count) = 1;
373                        dest = &pt;
374                    }
375
376                    (*dest)->x = x;
377                    (*dest)->y = y;
378                    (*dest)->z = z;
379
380                    // Advance the relevant coordinate pointer to the next
381                    // position.
382                    (*dest)++;
383                }
384
385                // Add the leg onto the current polyline.
386                Point** dest = &(current_polyline_is_surface ? spt : pt);
387
388                (*dest)->x = x = pti->GetX();
389                (*dest)->y = y = pti->GetY();
390                (*dest)->z = z = pti->GetZ();
391
392                // Advance the relevant coordinate pointer to the next
393                // position.
394                (*dest)++;
395
396                // Increment the relevant vertex count.
397                if (current_polyline_is_surface) {
398                    (*scount)++;
399                }
400                else {
401                    (*count)++;
402                }
403                last_was_move = false;
404            }
405            else {
406                first_point = false;
407                last_was_move = true;
408
409                // Save the current coordinates for the next time around
410                // the loop.
411                x = pti->GetX();
412                y = pti->GetY();
413                z = pti->GetZ();
414            }
415        }
416        if (!m_UndergroundLegs) {
417            m_UndergroundLegs = (m_Polylines[band] > 0);
418        }
419        if (!m_SurfaceLegs) {
420            m_SurfaceLegs = (m_SurfacePolylines[band] > 0);
421        }
422    }
423}
424
425void GfxCore::SetScale(Double scale)
426{
427    // Fill the plot data arrays with screen coordinates, scaling the survey
428    // to a particular absolute scale.
429
430    clipping = false;
431    if (scale < m_InitialScale / 20) {
432        scale = m_InitialScale / 20;
433    } else {
434        if (scale > 1000.0) scale = 1000.0;
435        Double max_scale = 32767.0 / MAX(m_Parent->GetXExtent(), m_Parent->GetYExtent());
436        if (scale > max_scale) clipping = true;
437    }
438
439    m_Params.scale = scale;
440}
441
442//
443//  Repainting methods
444//
445
446void
447GfxCore::DrawBand(int num_polylines, const int *num_segs, const Point *vertices,
448                  Double m_00, Double m_01, Double m_02,
449                  Double m_20, Double m_21, Double m_22)
450{
451    if (clipping) {
452        while (num_polylines--) {
453            double X = vertices->x + m_Params.translation.x;
454            double Y = vertices->y + m_Params.translation.y;
455            double Z = vertices->z + m_Params.translation.z;
456            ++vertices;
457
458            int x = int(X * m_00 + Y * m_01 + Z * m_02) + m_XCentre;
459            int y = -int(X * m_20 + Y * m_21 + Z * m_22) + m_YCentre;
460#define U 1
461#define D 2
462#define L 4
463#define R 8
464            int mask = 0;
465            if (x < 0)
466                mask = L;
467            else if (x > m_XSize)
468                mask = R;
469
470            if (y < 0)
471                mask |= D;
472            else if (y > m_YSize)
473                mask |= U;
474
475            for (size_t n = *num_segs; n > 1; --n) {
476                X = vertices->x + m_Params.translation.x;
477                Y = vertices->y + m_Params.translation.y;
478                Z = vertices->z + m_Params.translation.z;
479                ++vertices;
480
481                int x2 = x;
482                int y2 = y;
483                int mask2 = mask;
484
485                x = int(X * m_00 + Y * m_01 + Z * m_02) + m_XCentre;
486                y = -int(X * m_20 + Y * m_21 + Z * m_22) + m_YCentre;
487
488                mask = 0;
489                if (x < 0)
490                    mask = L;
491                else if (x > m_XSize)
492                    mask = R;
493
494                if (y < 0)
495                    mask |= D;
496                else if (y > m_YSize)
497                    mask |= U;
498
499                // See if whole line is above, left, right, or below
500                // screen.
501                if (mask & mask2) continue;
502                //printf("(%d,%d) - (%d,%d)\n", x, y, x2, y2);
503
504                int a = x, b = y;
505                int c = x2, d = y2;
506
507                // Actually need to clip line to screen...
508                if (mask & U) {
509                    b = m_YSize;
510                    a = ((x - x2) * b + x2 * y - x * y2) / (y - y2);
511                } else if (mask & D) {
512                    b = 0;
513                    a = (x2 * y - x * y2) / (y - y2);
514                }
515
516                //printf("(%d,%d) -\n", a, b);
517                if (a < 0 || a > m_XSize) {
518                    if (!(mask & (L|R))) continue;
519                    if (mask & L) {
520                        a = 0;
521                        b = (x * y2 - x2 * y) / (x - x2);
522                    } else if (mask & R) {
523                        a = m_XSize;
524                        b = ((y - y2) * a + x * y2 - x2 * y) / (x - x2);
525                    }
526                    //printf("(%d,%d) -\n", a, b);
527                    if (b < 0 || b > m_YSize) continue;
528                }
529
530                if (mask2 & U) {
531                    d = m_YSize;
532                    c = ((x - x2) * d + x2 * y - x * y2) / (y - y2);
533                } else if (mask2 & D) {
534                    d = 0;
535                    c = (x2 * y - x * y2) / (y - y2);
536                }
537
538                //printf(" - (%d,%d)\n", c, d);
539                if (c < 0 || c > m_XSize) {
540                    if (!(mask2 & (L|R))) continue;
541                    if (mask2 & L) {
542                        c = 0;
543                        d = (x * y2 - x2 * y) / (x - x2);
544                    } else if (mask2 & R) {
545                        c = m_XSize;
546                        d = ((y - y2) * c + x * y2 - x2 * y) / (x - x2);
547                    }
548                    //printf(" - (%d,%d)\n", c, d);
549                    if (d < 0 || d > m_YSize) continue;
550                }
551#undef U
552#undef D
553#undef L
554#undef R
555                m_DrawDC.DrawLine(a, b, c, d);
556            }
557            ++num_segs;
558        }
559    } else {
560#define MAX_SEGS 64 // Longest run in all.3d is 44 segments - set dynamically?
561        wxPoint tmp[MAX_SEGS];
562        while (num_polylines--) {
563            double X = vertices->x + m_Params.translation.x;
564            double Y = vertices->y + m_Params.translation.y;
565            double Z = vertices->z + m_Params.translation.z;
566            ++vertices;
567
568            int x = int(X * m_00 + Y * m_01 + Z * m_02);
569            int y = -int(X * m_20 + Y * m_21 + Z * m_22);
570            tmp[0].x = x;
571            tmp[0].y = y;
572            size_t count = 1;
573            size_t n = *num_segs;
574            while (1) {
575                X = vertices->x + m_Params.translation.x;
576                Y = vertices->y + m_Params.translation.y;
577                Z = vertices->z + m_Params.translation.z;
578                ++vertices;
579
580                x = int(X * m_00 + Y * m_01 + Z * m_02);
581                y = -int(X * m_20 + Y * m_21 + Z * m_22);
582                tmp[count].x = x;
583                tmp[count].y = y;
584                ++count;
585                --n;
586                if (count == MAX_SEGS || n == 1) {
587                    m_DrawDC.DrawLines(count, tmp, m_XCentre, m_YCentre);
588                    if (n == 1) break;
589                    // printf("had to split traverse with %d segs\n", *num_segs);
590                    tmp[0].x = x;
591                    tmp[0].y = y;
592                    count = 1;
593                }
594            }
595            ++num_segs;
596        }
597    }
598}
599
600static wxPoint blob[10] = {
601    wxPoint(-1,  2),
602    wxPoint( 1,  2),
603    wxPoint( 2,  1),
604    wxPoint(-2,  1),
605    wxPoint(-2,  0),
606    wxPoint( 2,  0),
607    wxPoint( 2, -1),
608    wxPoint(-2, -1),
609    wxPoint(-1, -2),
610    wxPoint( 2, -2) // (1, -2) for any platform which draws the last dot
611};
612
613static wxPoint ring[9] = {
614    wxPoint(-1,  4),
615    wxPoint( 1,  4),
616    wxPoint( 4,  1),
617    wxPoint( 4, -1),
618    wxPoint( 1, -4),
619    wxPoint(-1, -4),
620    wxPoint(-4, -1),
621    wxPoint(-4,  1),
622    wxPoint(-1,  4)
623};
624
625void GfxCore::RedrawOffscreen()
626{
627    static wxPoint cross1[2] = {
628        wxPoint(-CROSS_SIZE, -CROSS_SIZE),
629        wxPoint(CROSS_SIZE + 1, CROSS_SIZE + 1) // remove +1 if last dot drawn
630    };
631    static wxPoint cross2[2] = {
632        wxPoint(-CROSS_SIZE, CROSS_SIZE),
633        wxPoint(CROSS_SIZE + 1, -CROSS_SIZE - 1) // remove +/-1 if last dot
634    };
635#if 0
636    static int ignore = 10; // Ignore the first few redraws before averaging
637    static double total = 0.0;
638    static int count = 0;
639    double t = timer.Time() * 1.0e-3;
640    if (ignore) {
641        --ignore;
642    } else {
643        total += t;
644        count++;
645        cout << count / total << " average fps; " << 1.0 / t << " fps (" << t << " sec)\n";
646    }
647#endif
648    timer.Start(); // reset timer
649
650    // Redraw the offscreen bitmap
651
652    // Invalidate hit-test grid.
653    m_HitTestGridValid = false;
654
655    m_DrawDC.BeginDrawing();
656
657    // Set the font.
658    m_DrawDC.SetFont(m_Font);
659
660    // Clear the background to black.
661    SetColour(col_BLACK);
662    SetColour(col_BLACK, true);
663    m_DrawDC.DrawRectangle(0, 0, m_XSize, m_YSize);
664
665    if (m_PlotData) {
666        Double m_00 = m_RotationMatrix.get(0, 0) * m_Params.scale;
667        Double m_01 = m_RotationMatrix.get(0, 1) * m_Params.scale;
668        Double m_02 = m_RotationMatrix.get(0, 2) * m_Params.scale;
669        Double m_20 = m_RotationMatrix.get(2, 0) * m_Params.scale;
670        Double m_21 = m_RotationMatrix.get(2, 1) * m_Params.scale;
671        Double m_22 = m_RotationMatrix.get(2, 2) * m_Params.scale;
672
673        bool grid_first = (m_TiltAngle >= 0.0);
674
675        if (m_Grid && grid_first) {
676            DrawGrid();
677        }
678
679        // Draw underground legs.
680        if (m_Legs) {
681            int start;
682            int end;
683            int inc;
684
685            if (m_TiltAngle >= 0.0) {
686                start = 0;
687                end = m_Bands;
688                inc = 1;
689            } else {
690                start = m_Bands - 1;
691                end = -1;
692                inc = -1;
693            }
694
695            for (int band = start; band != end; band += inc) {
696                m_DrawDC.SetPen(m_Parent->GetPen(band));
697                DrawBand(m_Polylines[band], m_PlotData[band].num_segs,
698                         m_PlotData[band].vertices,
699                         m_00, m_01, m_02, m_20, m_21, m_22);
700            }
701        }
702
703        if (m_Surface) {
704            // Draw the surface legs.
705            int start;
706            int end;
707            int inc;
708
709            if (m_TiltAngle >= 0.0) {
710                start = 0;
711                end = m_Bands;
712                inc = 1;
713            } else {
714                start = m_Bands - 1;
715                end = -1;
716                inc = -1;
717            }
718
719            for (int band = start; band != end; band += inc) {
720                wxPen pen = m_SurfaceDepth ? m_Parent->GetPen(band) : m_Parent->GetSurfacePen();
721                if (m_SurfaceDashed) {
722#ifdef _WIN32
723                    pen.SetStyle(wxDOT);
724#else
725                    pen.SetStyle(wxSHORT_DASH);
726#endif
727                }
728                m_DrawDC.SetPen(pen);
729                DrawBand(m_SurfacePolylines[band],
730                         m_PlotData[band].surface_num_segs,
731                         m_PlotData[band].surface_vertices,
732                         m_00, m_01, m_02, m_20, m_21, m_22);
733                if (m_SurfaceDashed) {
734                    pen.SetStyle(wxSOLID);
735                }
736            }
737        }
738
739        // Plot crosses and/or blobs.
740        if (true || // FIXME : replace true with test for there being highlighted points...
741                m_Crosses || m_Entrances || m_FixedPts || m_ExportedPts) {
742            list<LabelInfo*>::const_reverse_iterator pos =
743                m_Parent->GetRevLabels();
744            while (pos != m_Parent->GetRevLabelsEnd()) {
745                LabelInfo* label = *pos++;
746
747                // When more than one flag is set on a point:
748                // search results take priority over entrance highlighting
749                // which takes priority over fixed point
750                // highlighting, which in turn takes priority over exported
751                // point highlighting.
752
753                enum AvenColour col;
754                enum {BLOB, CROSS} shape = BLOB;
755                if (!((m_Surface && label->IsSurface()) ||
756                      (m_Legs && label->IsUnderground()) ||
757                      (!label->IsSurface() && !label->IsUnderground()))) {
758                    // if this station isn't to be displayed, skip to the next
759                    // (last case is for stns with no legs attached)
760                    continue;
761                }
762
763                if (label->IsHighLighted()) {
764                    col = col_YELLOW;
765                } else if (m_Entrances && label->IsEntrance()) {
766                    col = col_GREEN;
767                } else if (m_FixedPts && label->IsFixedPt()) {
768                    col = col_RED;
769                } else if (m_ExportedPts && label->IsExportedPt()) {
770                    col = col_TURQUOISE;
771                } else if (m_Crosses) {
772                    col = col_LIGHT_GREY;
773                    shape = CROSS;
774                } else {
775                    continue;
776                }
777
778                Double x3 = label->GetX() + m_Params.translation.x;
779                Double y3 = label->GetY() + m_Params.translation.y;
780                Double z3 = label->GetZ() + m_Params.translation.z;
781
782                // Calculate screen coordinates, and check if the point is
783                // visible - this is faster, and avoids coordinate
784                // wrap-around problems
785                int x = (int) (x3 * m_00 + y3 * m_01 + z3 * m_02) + m_XCentre;
786                if (x < -CROSS_SIZE || x >= m_XSize + CROSS_SIZE) continue;
787                int y = -(int) (x3 * m_20 + y3 * m_21 + z3 * m_22) + m_YCentre;
788                if (y < -CROSS_SIZE || y >= m_YSize + CROSS_SIZE) continue;
789
790                SetColour(col);
791                if (shape == CROSS) {
792                    m_DrawDC.DrawLines(2, cross1, x, y);
793                    m_DrawDC.DrawLines(2, cross2, x, y);
794                } else {
795                    SetColour(col, true);
796                    m_DrawDC.DrawLines(10, blob, x, y);
797                }
798            }
799        }
800
801        if (m_Grid && !grid_first) DrawGrid();
802
803        // Draw station names.
804        if (m_Names) DrawNames();
805
806        // Draw scalebar.
807        if (m_Scalebar) DrawScalebar();
808
809        // Draw depthbar.
810        if (m_Depthbar) DrawDepthbar();
811
812        // Draw compass or elevation/heading indicators.
813        if ((m_Compass && m_RotationOK) || (m_Clino && m_Lock == lock_NONE)) {
814            Draw2dIndicators();
815        }
816    }
817
818    m_DrawDC.EndDrawing();
819
820    drawtime = timer.Time();
821}
822
823void GfxCore::OnPaint(wxPaintEvent&)
824{
825    // Redraw the window.
826
827    // Get a graphics context.
828    wxPaintDC dc(this);
829
830    // Make sure we're initialised.
831    if (!m_DoneFirstShow) {
832        FirstShow();
833    }
834
835    // Redraw the offscreen bitmap if it's out of date.
836    if (m_RedrawOffscreen) {
837        m_RedrawOffscreen = false;
838        RedrawOffscreen();
839    }
840
841    const wxRegion& region = GetUpdateRegion();
842
843    dc.BeginDrawing();
844
845    // Get the areas to redraw and update them.
846    wxRegionIterator iter(region);
847    while (iter) {
848        // Blit the bitmap onto the window.
849
850        int x = iter.GetX();
851        int y = iter.GetY();
852        int width = iter.GetW();
853        int height = iter.GetH();
854
855        dc.Blit(x, y, width, height, &m_DrawDC, x, y);
856
857        iter++;
858    }
859
860    if (!m_Rotating && !m_SwitchingTo) {
861        int here_x = INT_MAX, here_y = INT_MAX;
862        // Draw "here" and "there".
863        if (m_here.x != DBL_MAX) {
864            dc.SetPen(*wxWHITE_PEN);
865            dc.SetBrush(*wxTRANSPARENT_BRUSH);
866            here_x = (int)GridXToScreen(m_here);
867            here_y = (int)GridYToScreen(m_here);
868            dc.DrawLines(10, blob, here_x, here_y);
869        }
870        if (m_there.x != DBL_MAX) {
871            if (here_x == INT_MAX) dc.SetPen(*wxWHITE_PEN);
872            dc.SetBrush(*wxWHITE_BRUSH);
873            int there_x = (int)GridXToScreen(m_there);
874            int there_y = (int)GridYToScreen(m_there);
875            if (here_x != INT_MAX) {
876                dc.DrawLine(here_x, here_y, there_x, there_y);
877            }
878            dc.DrawLines(9, ring, there_x, there_y);
879        }
880    }
881
882    dc.EndDrawing();
883}
884
885Double GfxCore::GridXToScreen(Double x, Double y, Double z)
886{
887    x += m_Params.translation.x;
888    y += m_Params.translation.y;
889    z += m_Params.translation.z;
890
891    return (XToScreen(x, y, z) * m_Params.scale) + m_XCentre;
892}
893
894Double GfxCore::GridYToScreen(Double x, Double y, Double z)
895{
896    x += m_Params.translation.x;
897    y += m_Params.translation.y;
898    z += m_Params.translation.z;
899
900    return m_YCentre - ((ZToScreen(x, y, z) * m_Params.scale));
901}
902
903void GfxCore::DrawGrid()
904{
905    // Draw the grid.
906    SetColour(col_RED);
907
908    // Calculate the extent of the survey, in metres across the screen plane.
909    Double m_across_screen = Double(m_XSize / m_Params.scale);
910    // Calculate the length of the scale bar in metres.
911    //--move this elsewhere
912    Double size_snap = pow(10.0, floor(log10(0.75 * m_across_screen)));
913    Double t = m_across_screen * 0.75 / size_snap;
914    if (t >= 5.0) {
915        size_snap *= 5.0;
916    }
917    else if (t >= 2.0) {
918        size_snap *= 2.0;
919    }
920
921    Double grid_size = size_snap / 10.0;
922    Double edge = grid_size * 2.0;
923    Double grid_z = -m_Parent->GetZExtent()/2.0 - grid_size;
924    Double left = -m_Parent->GetXExtent()/2.0 - edge;
925    Double right = m_Parent->GetXExtent()/2.0 + edge;
926    Double bottom = -m_Parent->GetYExtent()/2.0 - edge;
927    Double top = m_Parent->GetYExtent()/2.0 + edge;
928    int count_x = (int) ceil((right - left) / grid_size);
929    int count_y = (int) ceil((top - bottom) / grid_size);
930    Double actual_right = left + count_x*grid_size;
931    Double actual_top = bottom + count_y*grid_size;
932
933    for (int xc = 0; xc <= count_x; xc++) {
934        Double x = left + xc*grid_size;
935        m_DrawDC.DrawLine((int) GridXToScreen(x, bottom, grid_z), (int) GridYToScreen(x, bottom, grid_z),
936                          (int) GridXToScreen(x, actual_top, grid_z), (int) GridYToScreen(x, actual_top, grid_z));
937    }
938
939    for (int yc = 0; yc <= count_y; yc++) {
940        Double y = bottom + yc*grid_size;
941        m_DrawDC.DrawLine((int) GridXToScreen(left, y, grid_z), (int) GridYToScreen(left, y, grid_z),
942                          (int) GridXToScreen(actual_right, y, grid_z),
943                          (int) GridYToScreen(actual_right, y, grid_z));
944    }
945}
946
947wxCoord GfxCore::GetClinoOffset()
948{
949    return m_Compass ? CLINO_OFFSET_X : INDICATOR_OFFSET_X;
950}
951
952wxPoint GfxCore::CompassPtToScreen(Double x, Double y, Double z)
953{
954    return wxPoint(long(-XToScreen(x, y, z)) + m_XSize - COMPASS_OFFSET_X,
955                   long(ZToScreen(x, y, z)) + m_YSize - COMPASS_OFFSET_Y);
956}
957
958wxPoint GfxCore::IndicatorCompassToScreenPan(int angle)
959{
960    Double theta = (angle * M_PI / 180.0) + m_PanAngle;
961    wxCoord length = (INDICATOR_BOX_SIZE - INDICATOR_MARGIN*2) / 2;
962    wxCoord x = wxCoord(length * sin(theta));
963    wxCoord y = wxCoord(length * cos(theta));
964
965    return wxPoint(m_XSize - INDICATOR_OFFSET_X - INDICATOR_BOX_SIZE/2 - x,
966                   m_YSize - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE/2 - y);
967}
968
969wxPoint GfxCore::IndicatorCompassToScreenElev(int angle)
970{
971    Double theta = (angle * M_PI / 180.0) + m_TiltAngle + M_PI_2;
972    wxCoord length = (INDICATOR_BOX_SIZE - INDICATOR_MARGIN*2) / 2;
973    wxCoord x = wxCoord(length * sin(-theta));
974    wxCoord y = wxCoord(length * cos(-theta));
975
976    return wxPoint(m_XSize - GetClinoOffset() - INDICATOR_BOX_SIZE/2 - x,
977                   m_YSize - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE/2 - y);
978}
979
980void GfxCore::DrawTick(wxCoord cx, wxCoord cy, int angle_cw)
981{
982    Double theta = angle_cw * M_PI / 180.0;
983    wxCoord length1 = (INDICATOR_BOX_SIZE - INDICATOR_MARGIN*2) / 2;
984    wxCoord length0 = length1 + TICK_LENGTH;
985    wxCoord x0 = wxCoord(length0 * sin(theta));
986    wxCoord y0 = wxCoord(length0 * -cos(theta));
987    wxCoord x1 = wxCoord(length1 * sin(theta));
988    wxCoord y1 = wxCoord(length1 * -cos(theta));
989
990    m_DrawDC.DrawLine(cx + x0, cy + y0, cx + x1, cy + y1);
991}
992
993void GfxCore::Draw2dIndicators()
994{
995    // Draw the "traditional" elevation and compass indicators.
996
997    //-- code is a bit messy...
998
999    // Indicator backgrounds
1000    SetColour(col_GREY, true);
1001    SetColour(col_LIGHT_GREY_2);
1002
1003    if (m_Compass && m_RotationOK) {
1004        m_DrawDC.DrawEllipse(m_XSize - INDICATOR_OFFSET_X - INDICATOR_BOX_SIZE + INDICATOR_MARGIN,
1005                             m_YSize - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE + INDICATOR_MARGIN,
1006                             INDICATOR_BOX_SIZE - INDICATOR_MARGIN*2,
1007                             INDICATOR_BOX_SIZE - INDICATOR_MARGIN*2);
1008    }
1009    if (m_Clino && m_Lock == lock_NONE) {
1010        int tilt = (int) (m_TiltAngle * 180.0 / M_PI);
1011        m_DrawDC.DrawEllipticArc(m_XSize - GetClinoOffset() - INDICATOR_BOX_SIZE +
1012                                 INDICATOR_MARGIN,
1013                                 m_YSize - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE +
1014                                 INDICATOR_MARGIN,
1015                                 INDICATOR_BOX_SIZE - INDICATOR_MARGIN*2,
1016                                 INDICATOR_BOX_SIZE - INDICATOR_MARGIN*2,
1017                                 -180 - tilt, -tilt); // do not change the order of these two
1018                                                      // or the code will fail on Windows
1019
1020        m_DrawDC.DrawLine(m_XSize - GetClinoOffset() - INDICATOR_BOX_SIZE/2,
1021                          m_YSize - INDICATOR_OFFSET_Y - INDICATOR_MARGIN,
1022                          m_XSize - GetClinoOffset() - INDICATOR_BOX_SIZE/2,
1023                          m_YSize - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE + INDICATOR_MARGIN);
1024
1025        m_DrawDC.DrawLine(m_XSize - GetClinoOffset() - INDICATOR_BOX_SIZE/2,
1026                          m_YSize - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE/2,
1027                          m_XSize - GetClinoOffset() - INDICATOR_MARGIN,
1028                          m_YSize - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE/2);
1029    }
1030
1031    // Ticks
1032    bool white = m_DraggingLeft && m_LastDrag == drag_COMPASS && m_MouseOutsideCompass;
1033    wxCoord pan_centre_x = m_XSize - INDICATOR_OFFSET_X - INDICATOR_BOX_SIZE/2;
1034    wxCoord centre_y = m_YSize - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE/2;
1035    wxCoord elev_centre_x = m_XSize - GetClinoOffset() - INDICATOR_BOX_SIZE/2;
1036    if (m_Compass && m_RotationOK) {
1037        int deg_pan = (int) (m_PanAngle * 180.0 / M_PI);
1038        //--FIXME: bodge by Olly to stop wrong tick highlighting
1039        if (deg_pan) deg_pan = 360 - deg_pan;
1040        for (int angle = deg_pan; angle <= 315 + deg_pan; angle += 45) {
1041            if (deg_pan == angle) {
1042                SetColour(col_GREEN);
1043            }
1044            else {
1045                SetColour(white ? col_WHITE : col_LIGHT_GREY_2);
1046            }
1047            DrawTick(pan_centre_x, centre_y, angle);
1048        }
1049    }
1050    if (m_Clino && m_Lock == lock_NONE) {
1051        white = m_DraggingLeft && m_LastDrag == drag_ELEV && m_MouseOutsideElev;
1052        int deg_elev = (int) (m_TiltAngle * 180.0 / M_PI);
1053        for (int angle = 0; angle <= 180; angle += 90) {
1054            if (deg_elev == angle - 90) {
1055                SetColour(col_GREEN);
1056            }
1057            else {
1058                SetColour(white ? col_WHITE : col_LIGHT_GREY_2);
1059            }
1060            DrawTick(elev_centre_x, centre_y, angle);
1061        }
1062    }
1063
1064    // Pan arrow
1065    if (m_Compass && m_RotationOK) {
1066        wxPoint p1 = IndicatorCompassToScreenPan(0);
1067        wxPoint p2 = IndicatorCompassToScreenPan(150);
1068        wxPoint p3 = IndicatorCompassToScreenPan(210);
1069        wxPoint pc(pan_centre_x, centre_y);
1070        wxPoint pts1[3] = { p2, p1, pc };
1071        wxPoint pts2[3] = { p3, p1, pc };
1072        SetColour(col_LIGHT_GREY);
1073        SetColour(col_INDICATOR_1, true);
1074        m_DrawDC.DrawPolygon(3, pts1);
1075        SetColour(col_INDICATOR_2, true);
1076        m_DrawDC.DrawPolygon(3, pts2);
1077    }
1078
1079    // Elevation arrow
1080    if (m_Clino && m_Lock == lock_NONE) {
1081        wxPoint p1e = IndicatorCompassToScreenElev(0);
1082        wxPoint p2e = IndicatorCompassToScreenElev(150);
1083        wxPoint p3e = IndicatorCompassToScreenElev(210);
1084        wxPoint pce(elev_centre_x, centre_y);
1085        wxPoint pts1e[3] = { p2e, p1e, pce };
1086        wxPoint pts2e[3] = { p3e, p1e, pce };
1087        SetColour(col_LIGHT_GREY);
1088        SetColour(col_INDICATOR_2, true);
1089        m_DrawDC.DrawPolygon(3, pts1e);
1090        SetColour(col_INDICATOR_1, true);
1091        m_DrawDC.DrawPolygon(3, pts2e);
1092    }
1093
1094    // Text
1095    m_DrawDC.SetTextBackground(wxColour(0, 0, 0));
1096    m_DrawDC.SetTextForeground(TEXT_COLOUR);
1097
1098    wxCoord w, h;
1099    wxCoord width, height;
1100    wxString str;
1101
1102    m_DrawDC.GetTextExtent(wxString("000"), &width, &h);
1103    height = m_YSize - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE - INDICATOR_GAP - h;
1104
1105    if (m_Compass && m_RotationOK) {
1106        if (m_Degrees) {
1107            str = wxString::Format("%03d", int(m_PanAngle * 180.0 / M_PI));
1108        } else {
1109            str = wxString::Format("%03d", int(m_PanAngle * 200.0 / M_PI));
1110        }
1111        m_DrawDC.GetTextExtent(str, &w, &h);
1112        m_DrawDC.DrawText(str, pan_centre_x + width / 2 - w, height);
1113        str = wxString(msg(/*Facing*/203));
1114        m_DrawDC.GetTextExtent(str, &w, &h);
1115        m_DrawDC.DrawText(str, pan_centre_x - w / 2, height - h);
1116    }
1117
1118    if (m_Clino && m_Lock == lock_NONE) {
1119        int angle;
1120        if (m_Degrees) {
1121            angle = int(-m_TiltAngle * 180.0 / M_PI);
1122        } else {
1123            angle = int(-m_TiltAngle * 200.0 / M_PI);
1124        }
1125        str = angle ? wxString::Format("%+03d", angle) : wxString("00");
1126        m_DrawDC.GetTextExtent(str, &w, &h);
1127        m_DrawDC.DrawText(str, elev_centre_x + width / 2 - w, height);
1128        str = wxString(msg(/*Elevation*/118));
1129        m_DrawDC.GetTextExtent(str, &w, &h);
1130        m_DrawDC.DrawText(str, elev_centre_x - w / 2, height - h);
1131    }
1132}
1133
1134void GfxCore::DrawNames()
1135{
1136    // Draw station names.
1137    m_DrawDC.SetTextBackground(wxColour(0, 0, 0));
1138    m_DrawDC.SetTextForeground(LABEL_COLOUR);
1139
1140    if (m_OverlappingNames) {
1141        SimpleDrawNames();
1142    } else {
1143        NattyDrawNames();
1144    }
1145}
1146
1147void GfxCore::NattyDrawNames()
1148{
1149    // Draw station names, without overlapping.
1150    // FIXME: copied to OnSize()
1151    const int quantise(FONT_SIZE / QUANTISE_FACTOR);
1152    const int quantised_x = m_XSize / quantise;
1153    const int quantised_y = m_YSize / quantise;
1154    const size_t buffer_size = quantised_x * quantised_y;
1155    if (!m_LabelGrid) m_LabelGrid = new char[buffer_size];
1156    memset((void*)m_LabelGrid, 0, buffer_size);
1157
1158    list<LabelInfo*>::const_iterator label = m_Parent->GetLabels();
1159    for ( ; label != m_Parent->GetLabelsEnd(); ++label) {
1160        if (!((m_Surface && (*label)->IsSurface()) ||
1161              (m_Legs && (*label)->IsUnderground()) ||
1162              (!(*label)->IsSurface() && !(*label)->IsUnderground()))) {
1163            // if this station isn't to be displayed, skip to the next
1164            // (last case is for stns with no legs attached)
1165            continue;
1166        }
1167
1168        Double x = GridXToScreen((*label)->x, (*label)->y, (*label)->z);
1169        Double y = GridYToScreen((*label)->x, (*label)->y, (*label)->z)
1170            + CROSS_SIZE - FONT_SIZE;
1171
1172        wxString str = (*label)->GetText();
1173
1174        Double t = GridXToScreen(0, 0, 0);
1175        t -= floor(t / quantise) * quantise;
1176        int ix = int(x - t) / quantise;
1177        t = GridYToScreen(0, 0, 0);
1178        t -= floor(t / quantise) * quantise;
1179        int iy = int(y - t) / quantise;
1180
1181        bool reject = true;
1182
1183        if (ix >= 0 && ix < quantised_x && iy >= 0 && iy < quantised_y) {
1184            char * test = &m_LabelGrid[ix + iy * quantised_x];
1185            int len = str.Length() * QUANTISE_FACTOR + 1;
1186            reject = (ix + len >= quantised_x);
1187            int i = 0;
1188            while (!reject && i++ < len) {
1189                reject = *test++;
1190            }
1191
1192            if (!reject) {
1193                m_DrawDC.DrawText(str, (wxCoord)x, (wxCoord)y);
1194
1195                int ymin = (iy >= 2) ? iy - 2 : iy;
1196                int ymax = (iy < quantised_y - 2) ? iy + 2 : iy;
1197                for (int y0 = ymin; y0 <= ymax; y0++) {
1198                    assert((ix + y0 * quantised_x) < (quantised_x * quantised_y));
1199                    memset((void*) &m_LabelGrid[ix + y0 * quantised_x], 1, len);
1200                }
1201            }
1202        }
1203    }
1204}
1205
1206void GfxCore::SimpleDrawNames()
1207{
1208    // Draw all station names, without worrying about overlaps
1209    list<LabelInfo*>::const_iterator label = m_Parent->GetLabels();
1210    while (label != m_Parent->GetLabelsEnd()) {
1211        wxCoord x = (wxCoord)GridXToScreen((*label)->x, (*label)->y, (*label)->z);
1212        wxCoord y = (wxCoord)GridYToScreen((*label)->x, (*label)->y, (*label)->z);
1213        m_DrawDC.DrawText((*label)->GetText(), x, y + CROSS_SIZE - FONT_SIZE);
1214        ++label;
1215    }
1216}
1217
1218void GfxCore::DrawDepthbar()
1219{
1220    if (m_Parent->GetZExtent() == 0.0) return;
1221
1222    m_DrawDC.SetTextBackground(wxColour(0, 0, 0));
1223    m_DrawDC.SetTextForeground(TEXT_COLOUR);
1224
1225    int y = DEPTH_BAR_BLOCK_HEIGHT * (m_Bands - 1) + DEPTH_BAR_OFFSET_Y;
1226    int size = 0;
1227
1228    wxString* strs = new wxString[m_Bands];
1229    int band;
1230    for (band = 0; band < m_Bands; band++) {
1231        Double z = m_Parent->GetZMin() + m_Parent->GetZExtent() * band
1232                / (m_Bands - 1);
1233
1234        strs[band] = FormatLength(z, false);
1235
1236        int x, dummy;
1237        m_DrawDC.GetTextExtent(strs[band], &x, &dummy);
1238        if (x > size) size = x;
1239    }
1240
1241    int x_min = m_XSize - DEPTH_BAR_OFFSET_X - DEPTH_BAR_BLOCK_WIDTH
1242            - DEPTH_BAR_MARGIN - size;
1243
1244    SetColour(col_BLACK);
1245    SetColour(col_DARK_GREY, true);
1246    m_DrawDC.DrawRectangle(x_min - DEPTH_BAR_MARGIN
1247                             - DEPTH_BAR_EXTRA_LEFT_MARGIN,
1248                           DEPTH_BAR_OFFSET_Y - DEPTH_BAR_MARGIN*2,
1249                           DEPTH_BAR_BLOCK_WIDTH + size + DEPTH_BAR_MARGIN*3 +
1250                             DEPTH_BAR_EXTRA_LEFT_MARGIN,
1251                           DEPTH_BAR_BLOCK_HEIGHT*(m_Bands - 1) + DEPTH_BAR_MARGIN*4);
1252
1253    for (band = 0; band < m_Bands; band++) {
1254        if (band < m_Bands - 1) {
1255            m_DrawDC.SetPen(m_Parent->GetPen(band));
1256            m_DrawDC.SetBrush(m_Parent->GetBrush(band));
1257            m_DrawDC.DrawRectangle(x_min,
1258                                   y - DEPTH_BAR_BLOCK_HEIGHT,
1259                                   DEPTH_BAR_BLOCK_WIDTH,
1260                                   DEPTH_BAR_BLOCK_HEIGHT);
1261        }
1262
1263        m_DrawDC.DrawText(strs[band], x_min + DEPTH_BAR_BLOCK_WIDTH + 5,
1264                          y - (FONT_SIZE / 2) - 1);
1265
1266        y -= DEPTH_BAR_BLOCK_HEIGHT;
1267    }
1268
1269    delete[] strs;
1270}
1271
1272wxString GfxCore::FormatLength(Double size_snap, bool scalebar)
1273{
1274    wxString str;
1275    bool negative = (size_snap < 0.0);
1276
1277    if (negative) {
1278        size_snap = -size_snap;
1279    }
1280
1281    if (size_snap == 0.0) {
1282        str = "0";
1283    } else if (m_Metric) {
1284#ifdef SILLY_UNITS
1285        if (size_snap < 1e-12) {
1286            str = wxString::Format("%.3gpm", size_snap * 1e12);
1287        } else if (size_snap < 1e-9) {
1288            str = wxString::Format("%.fpm", size_snap * 1e12);
1289        } else if (size_snap < 1e-6) {
1290            str = wxString::Format("%.fnm", size_snap * 1e9);
1291        } else if (size_snap < 1e-3) {
1292            str = wxString::Format("%.fum", size_snap * 1e6);
1293#else
1294        if (size_snap < 1e-3) {
1295            str = wxString::Format("%.3gmm", size_snap * 1e3);
1296#endif
1297        } else if (size_snap < 1e-2) {
1298            str = wxString::Format("%.fmm", size_snap * 1e3);
1299        } else if (size_snap < 1.0) {
1300            str = wxString::Format("%.fcm", size_snap * 100.0);
1301        } else if (size_snap < 1e3) {
1302            str = wxString::Format("%.fm", size_snap);
1303#ifdef SILLY_UNITS
1304        } else if (size_snap < 1e6) {
1305            str = wxString::Format("%.fkm", size_snap * 1e-3);
1306        } else if (size_snap < 1e9) {
1307            str = wxString::Format("%.fMm", size_snap * 1e-6);
1308        } else {
1309            str = wxString::Format("%.fGm", size_snap * 1e-9);
1310#else
1311        } else {
1312            str = wxString::Format(scalebar ? "%.fkm" : "%.2fkm", size_snap * 1e-3);
1313#endif
1314        }
1315    } else {
1316        size_snap /= METRES_PER_FOOT;
1317        if (scalebar) {
1318            Double inches = size_snap * 12;
1319            if (inches < 1.0) {
1320                str = wxString::Format("%.3gin", inches);
1321            } else if (size_snap < 1.0) {
1322                str = wxString::Format("%.fin", inches);
1323            } else if (size_snap < 5279.5) {
1324                str = wxString::Format("%.fft", size_snap);
1325            } else {
1326                str = wxString::Format("%.f miles", size_snap / 5280.0);
1327            }
1328        } else {
1329            str = wxString::Format("%.fft", size_snap);
1330        }
1331    }
1332
1333    return negative ? wxString("-") + str : str;
1334}
1335
1336void GfxCore::DrawScalebar()
1337{
1338    // Draw the scalebar.
1339
1340    if (m_Lock == lock_POINT) return;
1341
1342    // Calculate how many metres of survey are currently displayed across the
1343    // screen.
1344    int x_size = m_XSize;
1345    Double across_screen = Double(x_size / m_Params.scale);
1346
1347    // Convert to imperial measurements if required.
1348    Double multiplier = 1.0;
1349    if (!m_Metric) {
1350        across_screen /= METRES_PER_FOOT;
1351        multiplier = METRES_PER_FOOT;
1352        if (across_screen >= 5280.0 / 0.75) {
1353            across_screen /= 5280.0;
1354            multiplier *= 5280.0;
1355        }
1356    }
1357
1358    // Calculate the length of the scale bar.
1359    Double size_snap = pow(10.0, floor(log10(0.75 * across_screen)));
1360    Double t = across_screen * 0.75 / size_snap;
1361    if (t >= 5.0) {
1362        size_snap *= 5.0;
1363    } else if (t >= 2.0) {
1364        size_snap *= 2.0;
1365    }
1366
1367    if (!m_Metric) size_snap *= multiplier;
1368
1369    // Actual size of the thing in pixels:
1370    int size = int(size_snap * m_Params.scale);
1371    m_ScaleBar.width = size;
1372
1373    // Draw it...
1374    //--FIXME: improve this
1375    int end_x = m_ScaleBar.offset_x;
1376    int height = SCALE_BAR_HEIGHT;
1377    int end_y = m_YSize - m_ScaleBar.offset_y - height;
1378    int interval = size / 10;
1379
1380    bool solid = true;
1381    for (int ix = 0; ix < 10; ix++) {
1382        int x = end_x + int(ix * ((Double) size / 10.0));
1383
1384        SetColour(solid ? col_GREY : col_WHITE);
1385        SetColour(solid ? col_GREY : col_WHITE, true);
1386
1387        m_DrawDC.DrawRectangle(x, end_y, interval + 2, height);
1388
1389        solid = !solid;
1390    }
1391
1392    // Add labels.
1393    wxString str = FormatLength(size_snap);
1394
1395    m_DrawDC.SetTextBackground(wxColour(0, 0, 0));
1396    m_DrawDC.SetTextForeground(TEXT_COLOUR);
1397    m_DrawDC.DrawText("0", end_x, end_y - FONT_SIZE - 4);
1398
1399    int text_width, text_height;
1400    m_DrawDC.GetTextExtent(str, &text_width, &text_height);
1401    m_DrawDC.DrawText(str, end_x + size - text_width, end_y - FONT_SIZE - 4);
1402}
1403
1404//
1405//  Mouse event handling methods
1406//
1407
1408void GfxCore::OnLButtonDown(wxMouseEvent& event)
1409{
1410    SetFocus();
1411    if (m_PlotData && m_Lock != lock_POINT) {
1412        m_DraggingLeft = true;
1413        m_ScaleBar.drag_start_offset_x = m_ScaleBar.offset_x;
1414        m_ScaleBar.drag_start_offset_y = m_ScaleBar.offset_y;
1415        m_DragStart = m_DragRealStart = wxPoint(event.GetX(), event.GetY());
1416
1417        CaptureMouse();
1418    }
1419}
1420
1421void GfxCore::OnLButtonUp(wxMouseEvent& event)
1422{
1423    if (m_PlotData && m_Lock != lock_POINT) {
1424        if (event.GetPosition() == m_DragRealStart) {
1425            // just a "click"...
1426            CheckHitTestGrid(m_DragStart, true);
1427        }
1428
1429        m_LastDrag = drag_NONE;
1430        m_DraggingLeft = false;
1431        const wxRect r(m_XSize - INDICATOR_OFFSET_X - INDICATOR_BOX_SIZE*2 - INDICATOR_GAP,
1432                       m_YSize - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE,
1433                       INDICATOR_BOX_SIZE*2 + INDICATOR_GAP,
1434                       INDICATOR_BOX_SIZE);
1435        m_RedrawOffscreen = true;
1436        Refresh(false, &r);
1437        ReleaseMouse();
1438    }
1439}
1440
1441void GfxCore::OnMButtonDown(wxMouseEvent& event)
1442{
1443    SetFocus();
1444    if (m_PlotData && m_Lock == lock_NONE) {
1445        m_DraggingMiddle = true;
1446        m_DragStart = wxPoint(event.GetX(), event.GetY());
1447
1448        CaptureMouse();
1449    }
1450}
1451
1452void GfxCore::OnMButtonUp(wxMouseEvent&)
1453{
1454    if (m_PlotData && m_Lock == lock_NONE) {
1455        m_DraggingMiddle = false;
1456        ReleaseMouse();
1457    }
1458}
1459
1460void GfxCore::OnRButtonDown(wxMouseEvent& event)
1461{
1462    SetFocus();
1463    if (m_PlotData) {
1464        m_DragStart = wxPoint(event.GetX(), event.GetY());
1465        m_ScaleBar.drag_start_offset_x = m_ScaleBar.offset_x;
1466        m_ScaleBar.drag_start_offset_y = m_ScaleBar.offset_y;
1467        m_DraggingRight = true;
1468
1469        CaptureMouse();
1470    }
1471}
1472
1473void GfxCore::OnRButtonUp(wxMouseEvent&)
1474{
1475    m_DraggingRight = false;
1476    m_LastDrag = drag_NONE;
1477    ReleaseMouse();
1478}
1479
1480void GfxCore::HandleScaleRotate(bool control, wxPoint point)
1481{
1482    // Handle a mouse movement during scale/rotate mode.
1483    int dx = point.x - m_DragStart.x;
1484    int dy = point.y - m_DragStart.y;
1485
1486    if (m_ReverseControls) {
1487        dx = -dx;
1488        dy = -dy;
1489    }
1490
1491    Double pan_angle = m_RotationOK ? (Double(dx) * (-M_PI / 500.0)) : 0.0;
1492
1493    // left/right => rotate, up/down => scale
1494    TurnCave(pan_angle);
1495
1496    if (control) {
1497        // For now...
1498        if (m_RotationOK) TiltCave(Double(-dy) * M_PI / 500.0);
1499    } else {
1500        SetScale(m_Params.scale *= pow(1.06, 0.08 * dy));
1501    }
1502
1503    ForceRefresh();
1504
1505    m_DragStart = point;
1506}
1507
1508void GfxCore::TurnCave(Double angle)
1509{
1510    // Turn the cave around its z-axis by a given angle.
1511    m_PanAngle += angle;
1512    if (m_PanAngle >= M_PI * 2.0) {
1513        m_PanAngle -= M_PI * 2.0;
1514    } else if (m_PanAngle < 0.0) {
1515        m_PanAngle += M_PI * 2.0;
1516    }
1517    m_Params.rotation.setFromEulerAngles(0.0, 0.0, m_PanAngle);
1518    Quaternion q;
1519    q.setFromEulerAngles(m_TiltAngle, 0.0, 0.0);
1520    m_Params.rotation = q * m_Params.rotation;
1521    m_RotationMatrix = m_Params.rotation.asMatrix();
1522}
1523
1524void GfxCore::TurnCaveTo(Double angle)
1525{
1526    // Turn the cave to a particular pan angle.
1527    TurnCave(angle - m_PanAngle);
1528}
1529
1530void GfxCore::TiltCave(Double tilt_angle)
1531{
1532    // Tilt the cave by a given angle.
1533    if (m_TiltAngle + tilt_angle > M_PI_2) {
1534        tilt_angle = M_PI_2 - m_TiltAngle;
1535    } else if (m_TiltAngle + tilt_angle < -M_PI_2) {
1536        tilt_angle = -M_PI_2 - m_TiltAngle;
1537    }
1538
1539    m_TiltAngle += tilt_angle;
1540
1541    m_Params.rotation.setFromEulerAngles(0.0, 0.0, m_PanAngle);
1542    Quaternion q;
1543    q.setFromEulerAngles(m_TiltAngle, 0.0, 0.0);
1544    m_Params.rotation = q * m_Params.rotation;
1545    m_RotationMatrix = m_Params.rotation.asMatrix();
1546}
1547
1548void GfxCore::HandleTilt(wxPoint point)
1549{
1550    // Handle a mouse movement during tilt mode.
1551    int dy = point.y - m_DragStart.y;
1552
1553    if (m_ReverseControls) dy = -dy;
1554
1555    TiltCave(Double(-dy) * M_PI / 500.0);
1556
1557    m_DragStart = point;
1558
1559    ForceRefresh();
1560}
1561
1562void GfxCore::HandleTranslate(wxPoint point)
1563{
1564    // Handle a mouse movement during translation mode.
1565    int dx = point.x - m_DragStart.x;
1566    int dy = point.y - m_DragStart.y;
1567
1568    if (m_ReverseControls) {
1569        dx = -dx;
1570        dy = -dy;
1571    }
1572
1573    TranslateCave(dx, dy);
1574    m_DragStart = point;
1575}
1576
1577void GfxCore::TranslateCave(int dx, int dy)
1578{
1579    // Find out how far the screen movement takes us in cave coords.
1580    Double x = Double(dx / m_Params.scale);
1581    Double z = Double(-dy / m_Params.scale);
1582
1583    Matrix4 inverse_rotation = m_Params.rotation.asInverseMatrix();
1584
1585    Double cx = Double(inverse_rotation.get(0, 0)*x + inverse_rotation.get(0, 2)*z);
1586    Double cy = Double(inverse_rotation.get(1, 0)*x + inverse_rotation.get(1, 2)*z);
1587    Double cz = Double(inverse_rotation.get(2, 0)*x + inverse_rotation.get(2, 2)*z);
1588
1589    // Update parameters and redraw.
1590    m_Params.translation.x += cx;
1591    m_Params.translation.y += cy;
1592    m_Params.translation.z += cz;
1593
1594    ForceRefresh();
1595}
1596
1597void GfxCore::CheckHitTestGrid(wxPoint& point, bool centre)
1598{
1599    if (point.x < 0 || point.x >= m_XSize || point.y < 0 || point.y >= m_YSize) {
1600        return;
1601    }
1602
1603    if (!m_HitTestGridValid) CreateHitTestGrid();
1604
1605    int grid_x = (point.x * (HITTEST_SIZE - 1)) / m_XSize;
1606    int grid_y = (point.y * (HITTEST_SIZE - 1)) / m_YSize;
1607
1608    LabelInfo *best = NULL;
1609    int dist_sqrd = 25;
1610    int square = grid_x + grid_y * HITTEST_SIZE;
1611    list<LabelInfo*>::iterator iter = m_PointGrid[square].begin();
1612    while (iter != m_PointGrid[square].end()) {
1613        LabelInfo *pt = *iter++;
1614
1615        int dx = point.x -
1616                (int)GridXToScreen(pt->GetX(), pt->GetY(), pt->GetZ());
1617        int ds = dx * dx;
1618        if (ds >= dist_sqrd) continue;
1619        int dy = point.y -
1620                (int)GridYToScreen(pt->GetX(), pt->GetY(), pt->GetZ());
1621
1622        ds += dy * dy;
1623        if (ds >= dist_sqrd) continue;
1624
1625        dist_sqrd = ds;
1626        best = pt;
1627
1628        if (ds == 0) break;
1629    }
1630
1631    if (best) {
1632        m_Parent->SetMouseOverStation(best);
1633        if (centre) {
1634            CentreOn(best->GetX(), best->GetY(), best->GetZ());
1635            SetThere(best->GetX(), best->GetY(), best->GetZ());
1636            m_Parent->SelectTreeItem(best);
1637        }
1638    } else {
1639        m_Parent->SetMouseOverStation(NULL);
1640    }
1641}
1642
1643void GfxCore::OnMouseMove(wxMouseEvent& event)
1644{
1645    // Mouse motion event handler.
1646    if (!m_PlotData) return;
1647
1648    wxPoint point = wxPoint(event.GetX(), event.GetY());
1649
1650    // Check hit-test grid (only if no buttons are pressed).
1651    if (!event.LeftIsDown() && !event.MiddleIsDown() && !event.RightIsDown()) {
1652        CheckHitTestGrid(point, false);
1653    }
1654
1655    // Update coordinate display if in plan view, or altitude if in elevation
1656    // view.
1657    if (m_TiltAngle == M_PI_2) {
1658        int x = event.GetX() - m_XCentre;
1659        int y = -(event.GetY() - m_YCentre);
1660        Matrix4 inverse_rotation = m_Params.rotation.asInverseMatrix();
1661
1662        Double cx = Double(inverse_rotation.get(0, 0)*x + inverse_rotation.get(0, 2)*y);
1663        Double cy = Double(inverse_rotation.get(1, 0)*x + inverse_rotation.get(1, 2)*y);
1664
1665        m_Parent->SetCoords(cx / m_Params.scale - m_Params.translation.x + m_Parent->GetXOffset(),
1666                            cy / m_Params.scale - m_Params.translation.y + m_Parent->GetYOffset());
1667    } else if (m_TiltAngle == 0.0) {
1668        int z = -(event.GetY() - m_YCentre);
1669        m_Parent->SetAltitude(z / m_Params.scale - m_Params.translation.z + m_Parent->GetZOffset());
1670    } else {
1671        m_Parent->ClearCoords();
1672    }
1673
1674    if (!m_SwitchingTo) {
1675        if (m_DraggingLeft) {
1676            wxCoord x0 = m_XSize - INDICATOR_OFFSET_X - INDICATOR_BOX_SIZE/2;
1677            wxCoord x1 = wxCoord(m_XSize - GetClinoOffset() - INDICATOR_BOX_SIZE/2);
1678            wxCoord y = m_YSize - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE/2;
1679
1680            wxCoord dx0 = point.x - x0;
1681            wxCoord dx1 = point.x - x1;
1682            wxCoord dy = point.y - y;
1683
1684            wxCoord radius = (INDICATOR_BOX_SIZE - INDICATOR_MARGIN*2) / 2;
1685
1686            if (m_LastDrag == drag_NONE) {
1687                if (m_Compass && dx0 * dx0 + dy * dy <= radius * radius)
1688                    m_LastDrag = drag_COMPASS;
1689                else if (m_Clino && dx1 * dx1 + dy * dy <= radius * radius)
1690                    m_LastDrag = drag_ELEV;
1691                else if (point.x >= m_ScaleBar.offset_x &&
1692                         point.x <= m_ScaleBar.offset_x + m_ScaleBar.width &&
1693                         point.y <= m_YSize - m_ScaleBar.offset_y &&
1694                         point.y >= m_YSize - m_ScaleBar.offset_y - SCALE_BAR_HEIGHT)
1695                    m_LastDrag = drag_SCALE;
1696            }
1697            if (m_LastDrag == drag_COMPASS) {
1698                // drag in heading indicator
1699                double angle = atan2(double(dx0), double(dy)) - M_PI;
1700                if (dx0 * dx0 + dy * dy <= radius * radius) {
1701                    TurnCaveTo(angle);
1702                    m_MouseOutsideCompass = false;
1703                }
1704                else {
1705                    TurnCaveTo(int(int(angle * 180.0 / M_PI) / 45) *
1706                               M_PI_4);
1707                    m_MouseOutsideCompass = true;
1708                }
1709                ForceRefresh();
1710            }
1711            else if (m_LastDrag == drag_ELEV) {
1712                // drag in elevation indicator
1713                if (dx1 >= 0 && dx1 * dx1 + dy * dy <= radius * radius) {
1714                    TiltCave(atan2(double(dy), double(dx1)) - m_TiltAngle);
1715                    m_MouseOutsideElev = false;
1716                }
1717                else if (dy >= INDICATOR_MARGIN) {
1718                    TiltCave(M_PI_2 - m_TiltAngle);
1719                    m_MouseOutsideElev = true;
1720                }
1721                else if (dy <= -INDICATOR_MARGIN) {
1722                    TiltCave(-M_PI_2 - m_TiltAngle);
1723                    m_MouseOutsideElev = true;
1724                }
1725                else {
1726                    TiltCave(-m_TiltAngle);
1727                    m_MouseOutsideElev = true;
1728                }
1729                ForceRefresh();
1730            }
1731            else if (m_LastDrag == drag_SCALE) {
1732                if (point.x >= 0 && point.x <= m_XSize) {
1733                    Double size_snap = Double(m_ScaleBar.width) / m_Params.scale;
1734                    int dx = point.x - m_DragLast.x;
1735
1736                    SetScale((m_ScaleBar.width + dx) / size_snap);
1737                    ForceRefresh();
1738                }
1739            }
1740            else if (m_LastDrag == drag_NONE || m_LastDrag == drag_MAIN) {
1741                m_LastDrag = drag_MAIN;
1742                HandleScaleRotate(event.ControlDown(), point);
1743            }
1744        }
1745        else if (m_DraggingMiddle) {
1746            HandleTilt(point);
1747        }
1748        else if (m_DraggingRight) {
1749            if ((m_LastDrag == drag_NONE &&
1750                 point.x >= m_ScaleBar.offset_x &&
1751                 point.x <= m_ScaleBar.offset_x + m_ScaleBar.width &&
1752                 point.y <= m_YSize - m_ScaleBar.offset_y &&
1753                 point.y >= m_YSize - m_ScaleBar.offset_y - SCALE_BAR_HEIGHT) ||
1754                 m_LastDrag == drag_SCALE) {
1755                  if (point.x < 0) point.x = 0;
1756                  if (point.y < 0) point.y = 0;
1757                  if (point.x > m_XSize) point.x = m_XSize;
1758                  if (point.y > m_YSize) point.y = m_YSize;
1759                  m_LastDrag = drag_SCALE;
1760                  int x_inside_bar = m_DragStart.x - m_ScaleBar.drag_start_offset_x;
1761                  int y_inside_bar = m_YSize - m_ScaleBar.drag_start_offset_y - m_DragStart.y;
1762                  m_ScaleBar.offset_x = point.x - x_inside_bar;
1763                  m_ScaleBar.offset_y = (m_YSize - point.y) - y_inside_bar;
1764                  ForceRefresh();
1765            }
1766            else {
1767                m_LastDrag = drag_MAIN;
1768                HandleTranslate(point);
1769            }
1770        }
1771    }
1772
1773    m_DragLast = point;
1774}
1775
1776void GfxCore::OnSize(wxSizeEvent& event)
1777{
1778    // Handle a change in window size.
1779
1780    wxSize size = event.GetSize();
1781
1782    if (size.GetWidth() <= 0 || size.GetHeight() <= 0) {
1783        // Before things are fully initialised, we sometimes get a bogus
1784        // resize message...
1785        return;
1786    }
1787
1788    m_XSize = size.GetWidth();
1789    m_YSize = size.GetHeight();
1790    m_XCentre = m_XSize / 2;
1791    m_YCentre = m_YSize / 2;
1792
1793    if (m_InitialisePending) {
1794        Initialise();
1795        m_InitialisePending = false;
1796        m_DoneFirstShow = true;
1797    }
1798
1799    if (m_DoneFirstShow) {
1800        // FIXME: copied from NattyDrawNames()
1801        const int quantise(FONT_SIZE / QUANTISE_FACTOR);
1802        const int quantised_x = m_XSize / quantise;
1803        const int quantised_y = m_YSize / quantise;
1804        const size_t buffer_size = quantised_x * quantised_y;
1805        if (m_LabelGrid) delete[] m_LabelGrid;
1806
1807        m_LabelGrid = new char[buffer_size];
1808        CreateHitTestGrid();
1809
1810#ifndef __WXMOTIF__
1811        m_DrawDC.SelectObject(wxNullBitmap);
1812#endif
1813        if (m_OffscreenBitmap) {
1814            delete m_OffscreenBitmap;
1815        }
1816        m_OffscreenBitmap = new wxBitmap;
1817        m_OffscreenBitmap->Create(m_XSize, m_YSize);
1818        m_DrawDC.SelectObject(*m_OffscreenBitmap);
1819        RedrawOffscreen();
1820        Refresh(false);
1821    }
1822}
1823
1824void GfxCore::OnDisplayOverlappingNames()
1825{
1826    m_OverlappingNames = !m_OverlappingNames;
1827    ForceRefresh();
1828}
1829
1830void GfxCore::OnDisplayOverlappingNamesUpdate(wxUpdateUIEvent& cmd)
1831{
1832    cmd.Enable(m_PlotData != NULL && m_Names);
1833    cmd.Check(m_OverlappingNames);
1834}
1835
1836void GfxCore::OnShowCrosses()
1837{
1838    m_Crosses = !m_Crosses;
1839    ForceRefresh();
1840}
1841
1842void GfxCore::OnShowCrossesUpdate(wxUpdateUIEvent& cmd)
1843{
1844    cmd.Enable(m_PlotData != NULL);
1845    cmd.Check(m_Crosses);
1846}
1847
1848void GfxCore::OnShowStationNames()
1849{
1850    m_Names = !m_Names;
1851    ForceRefresh();
1852}
1853
1854void GfxCore::OnShowStationNamesUpdate(wxUpdateUIEvent& cmd)
1855{
1856    cmd.Enable(m_PlotData != NULL);
1857    cmd.Check(m_Names);
1858}
1859
1860void GfxCore::OnShowSurveyLegs()
1861{
1862    m_Legs = !m_Legs;
1863    ForceRefresh();
1864}
1865
1866void GfxCore::OnShowSurveyLegsUpdate(wxUpdateUIEvent& cmd)
1867{
1868    cmd.Enable(m_PlotData != NULL && m_Lock != lock_POINT && m_UndergroundLegs);
1869    cmd.Check(m_Legs);
1870}
1871
1872void GfxCore::OnMoveEast()
1873{
1874    TurnCaveTo(M_PI_2);
1875    ForceRefresh();
1876}
1877
1878void GfxCore::OnMoveEastUpdate(wxUpdateUIEvent& cmd)
1879{
1880    cmd.Enable(m_PlotData != NULL && !(m_Lock & lock_Y));
1881}
1882
1883void GfxCore::OnMoveNorth()
1884{
1885    TurnCaveTo(0.0);
1886    ForceRefresh();
1887}
1888
1889void GfxCore::OnMoveNorthUpdate(wxUpdateUIEvent& cmd)
1890{
1891    cmd.Enable(m_PlotData != NULL && !(m_Lock & lock_X));
1892}
1893
1894void GfxCore::OnMoveSouth()
1895{
1896    TurnCaveTo(M_PI);
1897    ForceRefresh();
1898}
1899
1900void GfxCore::OnMoveSouthUpdate(wxUpdateUIEvent& cmd)
1901{
1902    cmd.Enable(m_PlotData != NULL && !(m_Lock & lock_X));
1903}
1904
1905void GfxCore::OnMoveWest()
1906{
1907    TurnCaveTo(M_PI * 1.5);
1908    ForceRefresh();
1909}
1910
1911void GfxCore::OnMoveWestUpdate(wxUpdateUIEvent& cmd)
1912{
1913    cmd.Enable(m_PlotData != NULL && !(m_Lock & lock_Y));
1914}
1915
1916void GfxCore::OnStartRotation()
1917{
1918    m_Rotating = true;
1919    timer.Start(drawtime);
1920}
1921
1922void GfxCore::OnStartRotationUpdate(wxUpdateUIEvent& cmd)
1923{
1924    cmd.Enable(m_PlotData != NULL && !m_Rotating && m_RotationOK);
1925}
1926
1927void GfxCore::OnToggleRotation()
1928{
1929    m_Rotating = !m_Rotating;
1930    if (m_Rotating) timer.Start(drawtime);
1931}
1932
1933void GfxCore::OnToggleRotationUpdate(wxUpdateUIEvent& cmd)
1934{
1935    cmd.Enable(m_PlotData != NULL && m_RotationOK);
1936    cmd.Check(m_PlotData != NULL && m_Rotating);
1937}
1938
1939void GfxCore::OnStopRotation()
1940{
1941    m_Rotating = false;
1942}
1943
1944void GfxCore::OnStopRotationUpdate(wxUpdateUIEvent& cmd)
1945{
1946    cmd.Enable(m_PlotData != NULL && m_Rotating);
1947}
1948
1949void GfxCore::OnReverseControls()
1950{
1951    m_ReverseControls = !m_ReverseControls;
1952}
1953
1954void GfxCore::OnReverseControlsUpdate(wxUpdateUIEvent& cmd)
1955{
1956    cmd.Enable(m_PlotData != NULL);
1957    cmd.Check(m_ReverseControls);
1958}
1959
1960void GfxCore::OnReverseDirectionOfRotation()
1961{
1962    m_RotationStep = -m_RotationStep;
1963}
1964
1965void GfxCore::OnReverseDirectionOfRotationUpdate(wxUpdateUIEvent& cmd)
1966{
1967    cmd.Enable(m_PlotData != NULL && m_RotationOK);
1968}
1969
1970void GfxCore::OnSlowDown(bool accel)
1971{
1972    m_RotationStep /= accel ? 1.44 : 1.2;
1973    if (m_RotationStep < M_PI / 180.0) {
1974        m_RotationStep = (Double) M_PI / 180.0;
1975    }
1976}
1977
1978void GfxCore::OnSlowDownUpdate(wxUpdateUIEvent& cmd)
1979{
1980    cmd.Enable(m_PlotData != NULL && m_RotationOK);
1981}
1982
1983void GfxCore::OnSpeedUp(bool accel)
1984{
1985    m_RotationStep *= accel ? 1.44 : 1.2;
1986    if (m_RotationStep > 2.5 * M_PI) {
1987        m_RotationStep = (Double) 2.5 * M_PI;
1988    }
1989}
1990
1991void GfxCore::OnSpeedUpUpdate(wxUpdateUIEvent& cmd)
1992{
1993    cmd.Enable(m_PlotData != NULL && m_RotationOK);
1994}
1995
1996void GfxCore::OnStepOnceAnticlockwise(bool accel)
1997{
1998    TurnCave(accel ? 5.0 * M_PI / 18.0 : M_PI / 18.0);
1999    ForceRefresh();
2000}
2001
2002void GfxCore::OnStepOnceAnticlockwiseUpdate(wxUpdateUIEvent& cmd)
2003{
2004    cmd.Enable(m_PlotData != NULL && m_RotationOK && !m_Rotating);
2005}
2006
2007void GfxCore::OnStepOnceClockwise(bool accel)
2008{
2009    TurnCave(accel ? 5.0 * -M_PI / 18.0 : -M_PI / 18.0);
2010    ForceRefresh();
2011}
2012
2013void GfxCore::OnStepOnceClockwiseUpdate(wxUpdateUIEvent& cmd)
2014{
2015    cmd.Enable(m_PlotData != NULL && m_RotationOK && !m_Rotating);
2016}
2017
2018void GfxCore::OnDefaults()
2019{
2020    // If there are no legs (e.g. after loading a .pos file), turn crosses on.
2021    if (m_Parent->GetNumLegs() == 0) {
2022        m_Crosses = true;
2023    }
2024
2025    m_PanAngle = 0.0;
2026    m_TiltAngle = M_PI_2;
2027    switch (m_Lock) {
2028        case lock_X:
2029        {
2030            // elevation looking along X axis (East)
2031            m_PanAngle = M_PI * 1.5;
2032            m_TiltAngle = 0.0;
2033            break;
2034        }
2035
2036        case lock_Y:
2037        case lock_XY: // survey is linearface and parallel to the Z axis => display in elevation.
2038            // elevation looking along Y axis (North)
2039            m_TiltAngle = 0.0;
2040            break;
2041
2042        case lock_Z:
2043        case lock_XZ: // linearface survey parallel to Y axis
2044        case lock_YZ: // linearface survey parallel to X axis
2045        {
2046            // flat survey (zero height range) => go into plan view (default orientation).
2047            break;
2048        }
2049
2050        case lock_POINT:
2051            m_Crosses = true;
2052            break;
2053
2054        case lock_NONE:
2055            break;
2056    }
2057
2058    m_Params.rotation.setFromEulerAngles(m_TiltAngle, 0.0, m_PanAngle);
2059    m_RotationMatrix = m_Params.rotation.asMatrix();
2060
2061    m_Params.translation.x = 0.0;
2062    m_Params.translation.y = 0.0;
2063    m_Params.translation.z = 0.0;
2064
2065    m_Surface = false;
2066    m_SurfaceDepth = false;
2067    m_SurfaceDashed = true;
2068    m_RotationStep = M_PI / 6.0;
2069    m_Rotating = false;
2070    m_SwitchingTo = 0;
2071    m_Entrances = false;
2072    m_FixedPts = false;
2073    m_ExportedPts = false;
2074    m_Grid = false;
2075    SetScale(m_InitialScale);
2076    ForceRefresh();
2077}
2078
2079void GfxCore::OnDefaultsUpdate(wxUpdateUIEvent& cmd)
2080{
2081    cmd.Enable(m_PlotData != NULL);
2082}
2083
2084void GfxCore::OnElevation()
2085{
2086    // Switch to elevation view.
2087    switch (m_SwitchingTo) {
2088        case 0:
2089            timer.Start(drawtime);
2090            m_SwitchingTo = ELEVATION;
2091            break;
2092        case PLAN:
2093            m_SwitchingTo = ELEVATION;
2094            break;
2095        case ELEVATION:
2096            // A second order to switch takes us there right away
2097            TiltCave(-m_TiltAngle);
2098            m_SwitchingTo = 0;
2099            ForceRefresh();
2100    }
2101}
2102
2103void GfxCore::OnElevationUpdate(wxUpdateUIEvent& cmd)
2104{
2105    cmd.Enable(m_PlotData != NULL && m_Lock == lock_NONE && m_TiltAngle != 0.0);
2106}
2107
2108void GfxCore::OnHigherViewpoint(bool accel)
2109{
2110    // Raise the viewpoint.
2111    TiltCave(accel ? 5.0 * M_PI / 18.0 : M_PI / 18.0);
2112    ForceRefresh();
2113}
2114
2115void GfxCore::OnHigherViewpointUpdate(wxUpdateUIEvent& cmd)
2116{
2117    cmd.Enable(m_PlotData != NULL && m_TiltAngle < M_PI_2 &&
2118               m_Lock == lock_NONE);
2119}
2120
2121void GfxCore::OnLowerViewpoint(bool accel)
2122{
2123    // Lower the viewpoint.
2124    TiltCave(accel ? 5.0 * -M_PI / 18.0 : -M_PI / 18.0);
2125    ForceRefresh();
2126}
2127
2128void GfxCore::OnLowerViewpointUpdate(wxUpdateUIEvent& cmd)
2129{
2130    cmd.Enable(m_PlotData != NULL && m_TiltAngle > -M_PI_2 &&
2131               m_Lock == lock_NONE);
2132}
2133
2134void GfxCore::OnPlan()
2135{
2136    // Switch to plan view.
2137    switch (m_SwitchingTo) {
2138        case 0:
2139            timer.Start(drawtime);
2140            m_SwitchingTo = PLAN;
2141            break;
2142        case ELEVATION:
2143            m_SwitchingTo = PLAN;
2144            break;
2145        case PLAN:
2146            // A second order to switch takes us there right away
2147            TiltCave(M_PI_2 - m_TiltAngle);
2148            m_SwitchingTo = 0;
2149            ForceRefresh();
2150    }
2151}
2152
2153void GfxCore::OnPlanUpdate(wxUpdateUIEvent& cmd)
2154{
2155    cmd.Enable(m_PlotData != NULL && m_Lock == lock_NONE &&
2156               m_TiltAngle != M_PI_2);
2157}
2158
2159void GfxCore::OnShiftDisplayDown(bool accel)
2160{
2161    TranslateCave(0, accel ? 5 * DISPLAY_SHIFT : DISPLAY_SHIFT);
2162}
2163
2164void GfxCore::OnShiftDisplayDownUpdate(wxUpdateUIEvent& cmd)
2165{
2166    cmd.Enable(m_PlotData != NULL);
2167}
2168
2169void GfxCore::OnShiftDisplayLeft(bool accel)
2170{
2171    TranslateCave(accel ? -5 * DISPLAY_SHIFT : -DISPLAY_SHIFT, 0);
2172}
2173
2174void GfxCore::OnShiftDisplayLeftUpdate(wxUpdateUIEvent& cmd)
2175{
2176    cmd.Enable(m_PlotData != NULL);
2177}
2178
2179void GfxCore::OnShiftDisplayRight(bool accel)
2180{
2181    TranslateCave(accel ? 5 * DISPLAY_SHIFT : DISPLAY_SHIFT, 0);
2182}
2183
2184void GfxCore::OnShiftDisplayRightUpdate(wxUpdateUIEvent& cmd)
2185{
2186    cmd.Enable(m_PlotData != NULL);
2187}
2188
2189void GfxCore::OnShiftDisplayUp(bool accel)
2190{
2191    TranslateCave(0, accel ? -5 * DISPLAY_SHIFT : -DISPLAY_SHIFT);
2192}
2193
2194void GfxCore::OnShiftDisplayUpUpdate(wxUpdateUIEvent& cmd)
2195{
2196    cmd.Enable(m_PlotData != NULL);
2197}
2198
2199void GfxCore::OnZoomIn(bool accel)
2200{
2201    // Increase the scale.
2202
2203    SetScale(m_Params.scale * (accel ? 1.1236 : 1.06));
2204    ForceRefresh();
2205}
2206
2207void GfxCore::OnZoomInUpdate(wxUpdateUIEvent& cmd)
2208{
2209    cmd.Enable(m_PlotData != NULL && m_Lock != lock_POINT);
2210}
2211
2212void GfxCore::OnZoomOut(bool accel)
2213{
2214    // Decrease the scale.
2215
2216    SetScale(m_Params.scale / (accel ? 1.1236 : 1.06));
2217    ForceRefresh();
2218}
2219
2220void GfxCore::OnZoomOutUpdate(wxUpdateUIEvent& cmd)
2221{
2222    cmd.Enable(m_PlotData != NULL && m_Lock != lock_POINT);
2223}
2224
2225void GfxCore::OnIdle(wxIdleEvent& event)
2226{
2227    // Handle an idle event.
2228    if (Animate()) ForceRefresh();
2229}
2230
2231// return: true if animation occured (and ForceRefresh() needs to be called)
2232bool GfxCore::Animate()
2233{
2234    if (!m_Rotating && !m_SwitchingTo) {
2235        return false;
2236    }
2237
2238    static double last_t = 0;
2239    double t = timer.Time() * 1.0e-3;
2240//    cout << 1.0 / t << " fps (i.e. " << t << " sec)\n";
2241    if (t == 0) t = 0.001;
2242    else if (t > 1.0) t = 1.0;
2243    if (last_t > 0) t = (t + last_t) / 2;
2244    last_t = t;
2245
2246    // When rotating...
2247    if (m_Rotating) {
2248        TurnCave(m_RotationStep * t);
2249    }
2250
2251    if (m_SwitchingTo == PLAN) {
2252        // When switching to plan view...
2253        TiltCave(M_PI_2 * t);
2254        if (m_TiltAngle == M_PI_2) m_SwitchingTo = 0;
2255    } else if (m_SwitchingTo == ELEVATION) {
2256        // When switching to elevation view...
2257        if (fabs(m_TiltAngle) < M_PI_2 * t) {
2258            TiltCave(-m_TiltAngle);
2259            m_SwitchingTo = 0;
2260        } else if (m_TiltAngle < 0.0) {
2261            TiltCave(M_PI_2 * t);
2262        } else {
2263            TiltCave(-M_PI_2 * t);
2264        }
2265    }
2266
2267    return true;
2268}
2269
2270void GfxCore::OnToggleScalebar()
2271{
2272    m_Scalebar = !m_Scalebar;
2273    ForceRefresh();
2274}
2275
2276void GfxCore::OnToggleScalebarUpdate(wxUpdateUIEvent& cmd)
2277{
2278    cmd.Enable(m_PlotData != NULL && m_Lock != lock_POINT);
2279    cmd.Check(m_Scalebar);
2280}
2281
2282void GfxCore::OnToggleDepthbar()
2283{
2284    m_Depthbar = !m_Depthbar;
2285    ForceRefresh();
2286}
2287
2288void GfxCore::OnToggleDepthbarUpdate(wxUpdateUIEvent& cmd)
2289{
2290    cmd.Enable(m_PlotData != NULL && !(m_Lock && lock_Z));
2291    cmd.Check(m_Depthbar);
2292}
2293
2294void GfxCore::OnViewCompass()
2295{
2296    m_Compass = !m_Compass;
2297    ForceRefresh();
2298}
2299
2300void GfxCore::OnViewCompassUpdate(wxUpdateUIEvent& cmd)
2301{
2302    cmd.Enable(m_PlotData != NULL && m_RotationOK);
2303    cmd.Check(m_Compass);
2304}
2305
2306void GfxCore::OnViewClino()
2307{
2308    m_Clino = !m_Clino;
2309    ForceRefresh();
2310}
2311
2312void GfxCore::OnViewClinoUpdate(wxUpdateUIEvent& cmd)
2313{
2314    cmd.Enable(m_PlotData != NULL && m_Lock == lock_NONE);
2315    cmd.Check(m_Clino);
2316}
2317
2318void GfxCore::OnShowSurface()
2319{
2320    m_Surface = !m_Surface;
2321    ForceRefresh();
2322}
2323
2324void GfxCore::OnShowSurfaceDepth()
2325{
2326    m_SurfaceDepth = !m_SurfaceDepth;
2327    ForceRefresh();
2328}
2329
2330void GfxCore::OnShowSurfaceDashed()
2331{
2332    m_SurfaceDashed = !m_SurfaceDashed;
2333    ForceRefresh();
2334}
2335
2336void GfxCore::OnShowSurfaceUpdate(wxUpdateUIEvent& cmd)
2337{
2338    cmd.Enable(m_PlotData && m_SurfaceLegs);
2339    cmd.Check(m_Surface);
2340}
2341
2342void GfxCore::OnShowSurfaceDepthUpdate(wxUpdateUIEvent& cmd)
2343{
2344    cmd.Enable(m_PlotData && m_Surface);
2345    cmd.Check(m_SurfaceDepth);
2346}
2347
2348void GfxCore::OnShowSurfaceDashedUpdate(wxUpdateUIEvent& cmd)
2349{
2350    cmd.Enable(m_PlotData && m_SurfaceLegs && m_Surface);
2351    cmd.Check(m_SurfaceDashed);
2352}
2353
2354void GfxCore::OnShowEntrances()
2355{
2356    m_Entrances = !m_Entrances;
2357    ForceRefresh();
2358}
2359
2360void GfxCore::OnShowEntrancesUpdate(wxUpdateUIEvent& cmd)
2361{
2362    cmd.Enable(m_PlotData && (m_Parent->GetNumEntrances() > 0));
2363    cmd.Check(m_Entrances);
2364}
2365
2366void GfxCore::OnShowFixedPts()
2367{
2368    m_FixedPts = !m_FixedPts;
2369    ForceRefresh();
2370}
2371
2372void GfxCore::OnShowFixedPtsUpdate(wxUpdateUIEvent& cmd)
2373{
2374    cmd.Enable(m_PlotData && (m_Parent->GetNumFixedPts() > 0));
2375    cmd.Check(m_FixedPts);
2376}
2377
2378void GfxCore::OnShowExportedPts()
2379{
2380    m_ExportedPts = !m_ExportedPts;
2381    ForceRefresh();
2382}
2383
2384void GfxCore::OnShowExportedPtsUpdate(wxUpdateUIEvent& cmd)
2385{
2386    cmd.Enable(m_PlotData && (m_Parent->GetNumExportedPts() > 0));
2387    cmd.Check(m_ExportedPts);
2388}
2389
2390void GfxCore::OnViewGrid()
2391{
2392    m_Grid = !m_Grid;
2393    ForceRefresh();
2394}
2395
2396void GfxCore::OnViewGridUpdate(wxUpdateUIEvent& cmd)
2397{
2398    cmd.Enable(m_PlotData);
2399    cmd.Check(m_Grid);
2400}
2401
2402void GfxCore::OnIndicatorsUpdate(wxUpdateUIEvent& cmd)
2403{
2404    cmd.Enable(m_PlotData);
2405}
2406
2407void GfxCore::OnToggleMetric()
2408{
2409    m_Metric = !m_Metric;
2410    wxConfigBase::Get()->Write("metric", m_Metric);
2411    wxConfigBase::Get()->Flush();
2412    ForceRefresh();
2413}
2414
2415void GfxCore::OnToggleMetricUpdate(wxUpdateUIEvent& cmd)
2416{
2417    cmd.Enable(m_PlotData);
2418    cmd.Check(m_Metric);
2419}
2420
2421void GfxCore::OnToggleDegrees()
2422{
2423    m_Degrees = !m_Degrees;
2424    wxConfigBase::Get()->Write("degrees", m_Degrees);
2425    wxConfigBase::Get()->Flush();
2426    ForceRefresh();
2427}
2428
2429void GfxCore::OnToggleDegreesUpdate(wxUpdateUIEvent& cmd)
2430{
2431    cmd.Enable(m_PlotData);
2432    cmd.Check(m_Degrees);
2433}
2434
2435void GfxCore::CentreOn(Double x, Double y, Double z)
2436{
2437    m_Params.translation.x = -x;
2438    m_Params.translation.y = -y;
2439    m_Params.translation.z = -z;
2440    ForceRefresh();
2441}
2442
2443void GfxCore::ForceRefresh()
2444{
2445    // Redraw the offscreen bitmap
2446    m_RedrawOffscreen = true;
2447    Refresh(false);
2448}
2449
2450// How much to allow around the box - this is because of the ring shape
2451// at one end of the line.
2452#define MARGIN (HIGHLIGHTED_PT_SIZE * 2 + 1)
2453void GfxCore::RefreshLine(const Point &a, const Point &b, const Point &c)
2454{
2455    // Calculate the minimum rectangle which includes the old and new
2456    // measuring lines to minimise the redraw time
2457    int l = INT_MAX, r = INT_MIN, u = INT_MIN, d = INT_MAX;
2458    if (a.x != DBL_MAX) {
2459        int x = (int)GridXToScreen(a);
2460        int y = (int)GridYToScreen(a);
2461        l = x - MARGIN;
2462        r = x + MARGIN;
2463        u = y + MARGIN;
2464        d = y - MARGIN;
2465    }
2466    if (b.x != DBL_MAX) {
2467        int x = (int)GridXToScreen(b);
2468        int y = (int)GridYToScreen(b);
2469        l = min(l, x - MARGIN);
2470        r = max(r, x + MARGIN);
2471        u = max(u, y + MARGIN);
2472        d = min(d, y - MARGIN);
2473    }
2474    if (c.x != DBL_MAX) {
2475        int x = (int)GridXToScreen(c);
2476        int y = (int)GridYToScreen(c);
2477        l = min(l, x - MARGIN);
2478        r = max(r, x + MARGIN);
2479        u = max(u, y + MARGIN);
2480        d = min(d, y - MARGIN);
2481    }
2482    const wxRect R(l, d, r - l, u - d);
2483    Refresh(false, &R);
2484}
2485
2486void GfxCore::SetHere()
2487{
2488    if (m_here.x == DBL_MAX) return;
2489    Point old = m_here;
2490    m_here.x = DBL_MAX;
2491    RefreshLine(old, m_there, m_here);
2492}
2493
2494void GfxCore::SetHere(Double x, Double y, Double z)
2495{
2496    Point old = m_here;
2497    m_here.x = x;
2498    m_here.y = y;
2499    m_here.z = z;
2500    RefreshLine(old, m_there, m_here);
2501}
2502
2503void GfxCore::SetThere()
2504{
2505    if (m_there.x == DBL_MAX) return;
2506    Point old = m_there;
2507    m_there.x = DBL_MAX;
2508    RefreshLine(m_here, old, m_there);
2509}
2510
2511void GfxCore::SetThere(Double x, Double y, Double z)
2512{
2513    Point old = m_there;
2514    m_there.x = x;
2515    m_there.y = y;
2516    m_there.z = z;
2517    RefreshLine(m_here, old, m_there);
2518}
2519
2520void GfxCore::OnCancelDistLine()
2521{
2522    m_Parent->ClearTreeSelection();
2523}
2524
2525void GfxCore::OnCancelDistLineUpdate(wxUpdateUIEvent& cmd)
2526{
2527    cmd.Enable(m_there.x != DBL_MAX);
2528}
2529
2530void GfxCore::CreateHitTestGrid()
2531{
2532    // Clear hit-test grid.
2533    for (int i = 0; i < HITTEST_SIZE * HITTEST_SIZE; i++) {
2534        m_PointGrid[i].clear();
2535    }
2536
2537    // Fill the grid.
2538    list<LabelInfo*>::const_iterator pos = m_Parent->GetLabels();
2539    list<LabelInfo*>::const_iterator end = m_Parent->GetLabelsEnd();
2540    while (pos != end) {
2541        LabelInfo *label = *pos++;
2542
2543        if (!((m_Surface && label->IsSurface()) ||
2544              (m_Legs && label->IsUnderground()) ||
2545              (!label->IsSurface() && !label->IsUnderground()))) {
2546            // if this station isn't to be displayed, skip to the next
2547            // (last case is for stns with no legs attached)
2548            continue;
2549        }
2550
2551        // Calculate screen coordinates.
2552        int cx = (int)GridXToScreen(label->GetX(), label->GetY(), label->GetZ());
2553        if (cx < 0 || cx >= m_XSize) continue;
2554        int cy = (int)GridYToScreen(label->GetX(), label->GetY(), label->GetZ());
2555        if (cy < 0 || cy >= m_YSize) continue;
2556
2557        // On-screen, so add to hit-test grid...
2558        int grid_x = (cx * (HITTEST_SIZE - 1)) / m_XSize;
2559        int grid_y = (cy * (HITTEST_SIZE - 1)) / m_YSize;
2560
2561        m_PointGrid[grid_x + grid_y * HITTEST_SIZE].push_back(label);
2562    }
2563
2564    m_HitTestGridValid = true;
2565}
2566
2567void GfxCore::OnKeyPress(wxKeyEvent &e)
2568{
2569    if (!m_PlotData) {
2570        e.Skip();
2571        return;
2572    }
2573
2574    m_RedrawOffscreen = Animate();
2575
2576    switch (e.m_keyCode) {
2577        case '/': case '?':
2578            if (m_TiltAngle > -M_PI_2 && m_Lock == lock_NONE)
2579                OnLowerViewpoint(e.m_shiftDown);
2580            break;
2581        case '\'': case '@': case '"': // both shifted forms - US and UK kbd
2582            if (m_TiltAngle < M_PI_2 && m_Lock == lock_NONE)
2583                OnHigherViewpoint(e.m_shiftDown);
2584            break;
2585        case 'C': case 'c':
2586            if (m_RotationOK && !m_Rotating)
2587                OnStepOnceAnticlockwise(e.m_shiftDown);
2588            break;
2589        case 'V': case 'v':
2590            if (m_RotationOK && !m_Rotating)
2591                OnStepOnceClockwise(e.m_shiftDown);
2592            break;
2593        case ']': case '}':
2594            if (m_Lock != lock_POINT)
2595                OnZoomIn(e.m_shiftDown);
2596            break;
2597        case '[': case '{':
2598            if (m_Lock != lock_POINT)
2599                OnZoomOut(e.m_shiftDown);
2600            break;
2601        case 'N': case 'n':
2602            if (!(m_Lock & lock_X))
2603                OnMoveNorth();
2604            break;
2605        case 'S': case 's':
2606            if (!(m_Lock & lock_X))
2607                OnMoveSouth();
2608            break;
2609        case 'E': case 'e':
2610            if (!(m_Lock & lock_Y))
2611                OnMoveEast();
2612            break;
2613        case 'W': case 'w':
2614            if (!(m_Lock & lock_Y))
2615                OnMoveWest();
2616            break;
2617        case 'Z': case 'z':
2618            if (m_RotationOK)
2619                OnSpeedUp(e.m_shiftDown);
2620            break;
2621        case 'X': case 'x':
2622            if (m_RotationOK)
2623                OnSlowDown(e.m_shiftDown);
2624            break;
2625        case 'R': case 'r':
2626            if (m_RotationOK)
2627                OnReverseDirectionOfRotation();
2628            break;
2629        case 'P': case 'p':
2630            if (m_Lock == lock_NONE && m_TiltAngle != M_PI_2)
2631                OnPlan();
2632            break;
2633        case 'L': case 'l':
2634            if (m_Lock == lock_NONE && m_TiltAngle != 0.0)
2635                OnElevation();
2636            break;
2637        case 'O': case 'o':
2638            OnDisplayOverlappingNames();
2639            break;
2640        case WXK_DELETE:
2641            OnDefaults();
2642            break;
2643        case WXK_RETURN:
2644            if (m_RotationOK && !m_Rotating)
2645                OnStartRotation();
2646            break;
2647        case WXK_SPACE:
2648            if (m_Rotating)
2649                OnStopRotation();
2650            break;
2651        case WXK_LEFT:
2652            if (e.m_controlDown) {
2653                if (m_RotationOK && !m_Rotating)
2654                    OnStepOnceAnticlockwise(e.m_shiftDown);
2655            } else {
2656                OnShiftDisplayLeft(e.m_shiftDown);
2657            }
2658            break;
2659        case WXK_RIGHT:
2660            if (e.m_controlDown) {
2661                if (m_RotationOK && !m_Rotating)
2662                    OnStepOnceClockwise(e.m_shiftDown);
2663            } else {
2664                OnShiftDisplayRight(e.m_shiftDown);
2665            }
2666            break;
2667        case WXK_UP:
2668            if (e.m_controlDown) {
2669                if (m_TiltAngle < M_PI_2 && m_Lock == lock_NONE)
2670                    OnHigherViewpoint(e.m_shiftDown);
2671            } else {
2672                OnShiftDisplayUp(e.m_shiftDown);
2673            }
2674            break;
2675        case WXK_DOWN:
2676            if (e.m_controlDown) {
2677                if (m_TiltAngle > -M_PI_2 && m_Lock == lock_NONE)
2678                    OnLowerViewpoint(e.m_shiftDown);
2679            } else {
2680                OnShiftDisplayDown(e.m_shiftDown);
2681            }
2682            break;
2683        case WXK_ESCAPE:
2684            if (m_there.x != DBL_MAX)
2685                OnCancelDistLine();
2686            break;
2687        default:
2688            e.Skip();
2689    }
2690
2691    // OnPaint clears m_RedrawOffscreen so it'll only still be set if we need
2692    // to redraw
2693    if (m_RedrawOffscreen) ForceRefresh();
2694}
2695
2696void
2697GfxCore::OnPrint(const wxString &filename, const wxString &title,
2698                 const wxString &datestamp)
2699{
2700    svxPrintDlg * p;
2701    p = new svxPrintDlg(m_Parent, filename, title, datestamp,
2702                        deg(m_PanAngle), deg(m_TiltAngle),
2703                        m_Names, m_Crosses, m_Legs, m_Surface);
2704    p->Show(TRUE);
2705}
2706
2707bool
2708GfxCore::OnExport(const wxString &filename, const wxString &title)
2709{
2710    return Export(filename, title, m_Parent,
2711           m_PanAngle, m_TiltAngle, m_Names, m_Crosses, m_Legs, m_Surface);
2712}
Note: See TracBrowser for help on using the repository browser.