source: git/src/gfxcore.cc @ 1b71c05

RELEASE/1.0
Last change on this file since 1b71c05 was 1b71c05, checked in by Olly Betts <olly@…>, 14 years ago

src/: Update FSF address in (C) notices in source files.

git-svn-id: file:///home/survex-svn/survex/branches/1.0@3463 4b37db11-9a0c-4f06-9ece-9ab7cdaee568

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