source: git/src/gfxcore.cc @ 722886f

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

src/gfxcore.cc: Initialise m_MouseOutsideCompass and
m_MouseOutsideElev to avoid using them potentially uninitialised.

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