source: git/src/gfxcore.cc @ 6a6cb97

RELEASE/1.0
Last change on this file since 6a6cb97 was 722886f, checked in by Olly Betts <olly@…>, 15 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
RevLine 
[5809313]1//
[156dc16]2//  gfxcore.cc
[5809313]3//
[d1a521d]4//  Core drawing code for Aven.
[5809313]5//
6//  Copyright (C) 2000-2001, Mark R. Shinwell.
[301bbc1]7//  Copyright (C) 2001-2003,2004,2005 Olly Betts
[5809313]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
[17aa816]24#ifdef HAVE_CONFIG_H
25#include <config.h>
26#endif
27
[86caef9]28#include <assert.h>
[7aa15c0]29#include <float.h>
30
[7a89dc2]31#include "aven.h"
[5809313]32#include "gfxcore.h"
[137bf99]33#include "mainfrm.h"
[93c3f97]34#include "message.h"
[7a89dc2]35#include "useful.h"
[09d50cc]36#include "printwx.h"
[d8a725c]37#include "export.h"
[8000d8f]38
39#include <wx/confbase.h>
40#include <wx/image.h>
[5809313]41
[2effbf1]42#define HEAVEN 5000.0 // altitude of heaven
[156dc16]43#define INTERPOLATE(a, b, t) ((a) + (((b) - (a)) * Double(t) / 100.0))
[db4b33c]44#define MAX(a, b) ((a) > (b) ? (a) : (b))
[003d953]45#define MAX3(a, b, c) ((a) > (b) ? MAX(a, c) : MAX(b, c))
[84cab34]46#define TEXT_COLOUR  wxColour(0, 255, 40)
[52045fc]47#define LABEL_COLOUR wxColour(160, 255, 0)
[5809313]48
[3d00693]49// Values for m_SwitchingTo
50#define PLAN 1
51#define ELEVATION 2
52
[246426e]53// How many bins per letter height to use when working out non-overlapping
54// labels.
55const unsigned int QUANTISE_FACTOR = 2;
56
[3a0eaad]57#ifdef _WIN32
58static const int FONT_SIZE = 8;
59#else
[7d9e02b]60static const int FONT_SIZE = 9;
[3a0eaad]61#endif
[cd6ea75]62static const int CROSS_SIZE = 3;
[6606406]63static const int COMPASS_OFFSET_X = 60;
64static const int COMPASS_OFFSET_Y = 80;
65static const int INDICATOR_BOX_SIZE = 60;
[c300a04]66static const int INDICATOR_GAP = 2;
[6606406]67static const int INDICATOR_MARGIN = 5;
68static const int INDICATOR_OFFSET_X = 15;
[c300a04]69static const int INDICATOR_OFFSET_Y = 15;
[84e8eea]70static const int CLINO_OFFSET_X = 6 + INDICATOR_OFFSET_X +
[246426e]71                                  INDICATOR_BOX_SIZE + INDICATOR_GAP;
[42adb19]72static const int DEPTH_BAR_OFFSET_X = 16;
73static const int DEPTH_BAR_EXTRA_LEFT_MARGIN = 2;
[c300a04]74static const int DEPTH_BAR_BLOCK_WIDTH = 20;
75static const int DEPTH_BAR_BLOCK_HEIGHT = 15;
[42adb19]76static const int DEPTH_BAR_MARGIN = 6;
77static const int DEPTH_BAR_OFFSET_Y = 16 + DEPTH_BAR_MARGIN;
[b56df45]78static const int TICK_LENGTH = 4;
[5809313]79static const int DISPLAY_SHIFT = 50;
[42adb19]80static const int SCALE_BAR_OFFSET_X = 15;
81static const int SCALE_BAR_OFFSET_Y = 12;
82static const int SCALE_BAR_HEIGHT = 12;
[fe444b8]83static const int HIGHLIGHTED_PT_SIZE = 2;
[5809313]84
[156dc16]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
[ce1e5ac]100#define DELETE_ARRAY(A) do { assert((A)); delete[] (A); } while (0)
[e521e318]101
[39e460c9]102#define HITTEST_SIZE 20
103
[5809313]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)
[a8e9fde]114    EVT_IDLE(GfxCore::OnIdle)
[4b1fc48]115    EVT_CHAR(GfxCore::OnKeyPress)
[5809313]116END_EVENT_TABLE()
117
[156dc16]118GfxCore::GfxCore(MainFrm* parent, wxWindow* parent_win) :
[48a1982]119    wxWindow(parent_win, 100, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS),
[137bf99]120    m_Font(FONT_SIZE, wxSWISS, wxNORMAL, wxNORMAL, FALSE, "Helvetica",
[421b7d2]121           wxFONTENCODING_ISO8859_1),
[137bf99]122    m_InitialisePending(false)
[5809313]123{
[d8622e1]124    m_OffscreenBitmap = NULL;
[29aa51a]125    m_LastDrag = drag_NONE;
[42adb19]126    m_ScaleBar.offset_x = SCALE_BAR_OFFSET_X;
127    m_ScaleBar.offset_y = SCALE_BAR_OFFSET_Y;
128    m_ScaleBar.width = 0;
[5809313]129    m_DraggingLeft = false;
130    m_DraggingMiddle = false;
131    m_DraggingRight = false;
132    m_Parent = parent;
[303a525]133    m_RotationOK = true;
[5809313]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;
[c300a04]142    m_Clino = true;
[5809313]143    m_Depthbar = true;
144    m_Scalebar = true;
145    m_ReverseControls = false;
146    m_LabelGrid = NULL;
147    m_Rotating = false;
[3d00693]148    m_SwitchingTo = 0;
[fe444b8]149    m_Entrances = false;
150    m_FixedPts = false;
151    m_ExportedPts = false;
[c1cf79d]152    m_Grid = false;
[6804731]153    wxConfigBase::Get()->Read("metric", &m_Metric, true);
154    wxConfigBase::Get()->Read("degrees", &m_Degrees, true);
[1fd2edb]155    m_here.x = DBL_MAX;
156    m_there.x = DBL_MAX;
[b6de07d]157    clipping = false;
[5809313]158
[722886f]159    m_MouseOutsideCompass = m_MouseOutsideElev = false;
160
[5809313]161    // Create pens and brushes for drawing.
[156dc16]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++) {
[421b7d2]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());
[156dc16]170    }
[1e3ae11]171
172    SetBackgroundColour(wxColour(0, 0, 0));
[156dc16]173
[39e460c9]174    // Initialise grid for hit testing.
[fa42426]175    m_PointGrid = new list<LabelInfo*>[HITTEST_SIZE * HITTEST_SIZE];
[5809313]176}
177
178GfxCore::~GfxCore()
179{
180    TryToFreeArrays();
[156dc16]181
[3dee384]182    if (m_OffscreenBitmap) {
[1d93721]183        delete m_OffscreenBitmap;
[3dee384]184    }
185
[156dc16]186    DELETE_ARRAY(m_Pens);
187    DELETE_ARRAY(m_Brushes);
[39e460c9]188
189    delete[] m_PointGrid;
[5809313]190}
191
192void GfxCore::TryToFreeArrays()
193{
194    // Free up any memory allocated for arrays.
195
196    if (m_PlotData) {
[421b7d2]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);
[9d3d8cc]207
[421b7d2]208        if (m_LabelGrid) {
209            DELETE_ARRAY(m_LabelGrid);
210            m_LabelGrid = NULL;
211        }
212
213        m_PlotData = NULL;
[5809313]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
[9d3d8cc]227    if (!m_InitialisePending) {
[421b7d2]228        GetSize(&m_XSize, &m_YSize);
[9d3d8cc]229    }
[af35bdb]230
[137bf99]231    m_Bands = m_Parent->GetNumDepthBands(); // last band is surface data
[5809313]232    m_PlotData = new PlotData[m_Bands];
233    m_Polylines = new int[m_Bands];
[11a266e]234    m_SurfacePolylines = new int[m_Bands];
[421b7d2]235
[5809313]236    for (int band = 0; band < m_Bands; band++) {
[cd6ea75]237        m_PlotData[band].vertices = new Point[m_Parent->GetNumPoints()];
[421b7d2]238        m_PlotData[band].num_segs = new int[m_Parent->GetNumLegs()];
[cd6ea75]239        m_PlotData[band].surface_vertices = new Point[m_Parent->GetNumPoints()];
[421b7d2]240        m_PlotData[band].surface_num_segs = new int[m_Parent->GetNumLegs()];
[5809313]241    }
242
[f35efc6]243    m_UndergroundLegs = false;
244    m_SurfaceLegs = false;
245
[dfe4454c]246    m_HitTestGridValid = false;
[1fd2edb]247    m_here.x = DBL_MAX;
248    m_there.x = DBL_MAX;
[dfe4454c]249
[38d9dad]250    // Check for flat/linear/point surveys.
[303a525]251    m_RotationOK = true;
252
[de7a879]253    m_Lock = lock_NONE;
[303a525]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);
[421b7d2]257
[61c7068]258    // Scale the survey to a reasonable initial size.
[5fda6c2]259    switch (m_Lock) {
[84e8eea]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());
[5fda6c2]287    }
[84e8eea]288
[5fda6c2]289    m_InitialScale *= .85;
[61c7068]290
[5d77e43]291    // Calculate screen coordinates and redraw.
292    SetScaleInitial(m_InitialScale);
293
[5dedcca]294    // Apply default parameters.
295    OnDefaults();
[5809313]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.
[d8622e1]306    m_OffscreenBitmap = new wxBitmap;
307    m_OffscreenBitmap->Create(m_XSize, m_YSize);
[5809313]308
[d8622e1]309    m_DrawDC.SelectObject(*m_OffscreenBitmap);
[5809313]310
311    m_DoneFirstShow = true;
312
313    RedrawOffscreen();
314}
315
316//
317//  Recalculating methods
318//
319
[cd6ea75]320void GfxCore::SetScaleInitial(Double scale)
[5809313]321{
[b6de07d]322    SetScale(scale);
[79cb87c]323
[5dedcca]324    // Invalidate hit-test grid.
325    m_HitTestGridValid = false;
[421b7d2]326
[5dedcca]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;
[c072746]341        Double x = 0, y = 0, z = 0;
[5dedcca]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;
[11a266e]364
[5dedcca]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                    }
[9d3d8cc]377
[5dedcca]378                    (*dest)->x = x;
379                    (*dest)->y = y;
380                    (*dest)->z = z;
[e61185d]381
[8f110af]382                    // Advance the relevant coordinate pointer to the next
383                    // position.
[421b7d2]384                    (*dest)++;
[5dedcca]385                }
[9d3d8cc]386
[5dedcca]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)++;
[421b7d2]401                }
402                else {
[5dedcca]403                    (*count)++;
[421b7d2]404                }
[5dedcca]405                last_was_move = false;
[421b7d2]406            }
[5dedcca]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();
[421b7d2]416            }
417        }
[5dedcca]418        if (!m_UndergroundLegs) {
419            m_UndergroundLegs = (m_Polylines[band] > 0);
420        }
421        if (!m_SurfaceLegs) {
422            m_SurfaceLegs = (m_SurfacePolylines[band] > 0);
423        }
[5809313]424    }
[5047c53]425}
426
[cd6ea75]427void GfxCore::SetScale(Double scale)
[5047c53]428{
429    // Fill the plot data arrays with screen coordinates, scaling the survey
430    // to a particular absolute scale.
[421b7d2]431
[b6de07d]432    clipping = false;
[8a03058]433    if (scale < m_InitialScale / 20) {
[421b7d2]434        scale = m_InitialScale / 20;
[8a03058]435    } else {
[b6de07d]436        if (scale > 1000.0) scale = 1000.0;
[8a03058]437        Double max_scale = 32767.0 / MAX(m_Parent->GetXExtent(), m_Parent->GetYExtent());
[b6de07d]438        if (scale > max_scale) clipping = true;
[5047c53]439    }
440
441    m_Params.scale = scale;
[cd6ea75]442}
[5047c53]443
[cd6ea75]444//
445//  Repainting methods
446//
[5047c53]447
[b6de07d]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
[6009a17]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
[cd6ea75]627void GfxCore::RedrawOffscreen()
628{
629    static wxPoint cross1[2] = {
630        wxPoint(-CROSS_SIZE, -CROSS_SIZE),
[b6de07d]631        wxPoint(CROSS_SIZE + 1, CROSS_SIZE + 1) // remove +1 if last dot drawn
[cd6ea75]632    };
633    static wxPoint cross2[2] = {
634        wxPoint(-CROSS_SIZE, CROSS_SIZE),
[b6de07d]635        wxPoint(CROSS_SIZE + 1, -CROSS_SIZE - 1) // remove +/-1 if last dot
[cd6ea75]636    };
[2c7e06b]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";
[5047c53]648    }
[2c7e06b]649#endif
[cd6ea75]650    timer.Start(); // reset timer
[2c7e06b]651
652    // Redraw the offscreen bitmap
[cd6ea75]653
654    // Invalidate hit-test grid.
655    m_HitTestGridValid = false;
[5047c53]656
[5809313]657    m_DrawDC.BeginDrawing();
658
[43714a7]659    // Set the font.
660    m_DrawDC.SetFont(m_Font);
661
[5809313]662    // Clear the background to black.
[156dc16]663    SetColour(col_BLACK);
664    SetColour(col_BLACK, true);
[5809313]665    m_DrawDC.DrawRectangle(0, 0, m_XSize, m_YSize);
666
667    if (m_PlotData) {
[65472a2]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
[421b7d2]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;
[fa42426]691            } else {
[421b7d2]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));
[b6de07d]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);
[421b7d2]702            }
703        }
[246426e]704
[421b7d2]705        if (m_Surface) {
[246426e]706            // Draw the surface legs.
[421b7d2]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;
[fa42426]715            } else {
[421b7d2]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) {
[3a0eaad]724#ifdef _WIN32
[421b7d2]725                    pen.SetStyle(wxDOT);
[3a0eaad]726#else
[421b7d2]727                    pen.SetStyle(wxSHORT_DASH);
728#endif
729                }
730                m_DrawDC.SetPen(pen);
[b6de07d]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);
[421b7d2]735                if (m_SurfaceDashed) {
736                    pen.SetStyle(wxSOLID);
737                }
738            }
739        }
740
[fa42426]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++;
[421b7d2]748
749                // When more than one flag is set on a point:
[fa42426]750                // search results take priority over entrance highlighting
751                // which takes priority over fixed point
[8f110af]752                // highlighting, which in turn takes priority over exported
753                // point highlighting.
[421b7d2]754
[303a525]755                enum AvenColour col;
[fa42426]756                enum {BLOB, CROSS} shape = BLOB;
[7878072]757                if (!((m_Surface && label->IsSurface()) ||
758                      (m_Legs && label->IsUnderground()) ||
[fa42426]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()) {
[303a525]768                    col = col_GREEN;
[fa42426]769                } else if (m_FixedPts && label->IsFixedPt()) {
[303a525]770                    col = col_RED;
[fa42426]771                } else if (m_ExportedPts && label->IsExportedPt()) {
772                    col = col_TURQUOISE;
773                } else if (m_Crosses) {
774                    col = col_LIGHT_GREY;
775                    shape = CROSS;
[303a525]776                } else {
777                    continue;
[421b7d2]778                }
779
[fa42426]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;
[c6028c3]783
[fa42426]784                // Calculate screen coordinates, and check if the point is
785                // visible - this is faster, and avoids coordinate
786                // wrap-around problems
[c6028c3]787                int x = (int) (x3 * m_00 + y3 * m_01 + z3 * m_02) + m_XCentre;
[fa42426]788                if (x < -CROSS_SIZE || x >= m_XSize + CROSS_SIZE) continue;
[c6028c3]789                int y = -(int) (x3 * m_20 + y3 * m_21 + z3 * m_22) + m_YCentre;
[fa42426]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                }
[421b7d2]800            }
801        }
802
[fa42426]803        if (m_Grid && !grid_first) DrawGrid();
[421b7d2]804
805        // Draw station names.
[fa42426]806        if (m_Names) DrawNames();
[9d3d8cc]807
[421b7d2]808        // Draw scalebar.
[fa42426]809        if (m_Scalebar) DrawScalebar();
[9d3d8cc]810
[421b7d2]811        // Draw depthbar.
[fa42426]812        if (m_Depthbar) DrawDepthbar();
[9d3d8cc]813
[421b7d2]814        // Draw compass or elevation/heading indicators.
[303a525]815        if ((m_Compass && m_RotationOK) || (m_Clino && m_Lock == lock_NONE)) {
[cc9150c]816            Draw2dIndicators();
[421b7d2]817        }
[5809313]818    }
819
820    m_DrawDC.EndDrawing();
[1d93721]821
[a8e9fde]822    drawtime = timer.Time();
[5809313]823}
824
[2dfd768]825void GfxCore::OnPaint(wxPaintEvent&)
[5809313]826{
827    // Redraw the window.
828
[a06eb11]829    // Get a graphics context.
830    wxPaintDC dc(this);
831
[5809313]832    // Make sure we're initialised.
833    if (!m_DoneFirstShow) {
[421b7d2]834        FirstShow();
[5809313]835    }
836
837    // Redraw the offscreen bitmap if it's out of date.
838    if (m_RedrawOffscreen) {
[421b7d2]839        m_RedrawOffscreen = false;
840        RedrawOffscreen();
[5809313]841    }
842
[c6d95d8]843    const wxRegion& region = GetUpdateRegion();
844
[5809313]845    dc.BeginDrawing();
846
847    // Get the areas to redraw and update them.
[a06eb11]848    wxRegionIterator iter(region);
[5809313]849    while (iter) {
[421b7d2]850        // Blit the bitmap onto the window.
[5809313]851
[421b7d2]852        int x = iter.GetX();
853        int y = iter.GetY();
854        int width = iter.GetW();
855        int height = iter.GetH();
[5809313]856
[421b7d2]857        dc.Blit(x, y, width, height, &m_DrawDC, x, y);
[5809313]858
[421b7d2]859        iter++;
[5809313]860    }
[1d93721]861
[e2df869]862    if (!m_Rotating && !m_SwitchingTo) {
[c072746]863        int here_x = INT_MAX, here_y = INT_MAX;
[cd6ea75]864        // Draw "here" and "there".
865        if (m_here.x != DBL_MAX) {
866            dc.SetPen(*wxWHITE_PEN);
867            dc.SetBrush(*wxTRANSPARENT_BRUSH);
[b6de07d]868            here_x = (int)GridXToScreen(m_here);
869            here_y = (int)GridYToScreen(m_here);
[6009a17]870            dc.DrawLines(10, blob, here_x, here_y);
[cd6ea75]871        }
872        if (m_there.x != DBL_MAX) {
[b6de07d]873            if (here_x == INT_MAX) dc.SetPen(*wxWHITE_PEN);
[cd6ea75]874            dc.SetBrush(*wxWHITE_BRUSH);
[b6de07d]875            int there_x = (int)GridXToScreen(m_there);
876            int there_y = (int)GridYToScreen(m_there);
877            if (here_x != INT_MAX) {
[2173dbd]878                dc.DrawLine(here_x, here_y, there_x, there_y);
[cd6ea75]879            }
[6009a17]880            dc.DrawLines(9, ring, there_x, there_y);
[cd6ea75]881        }
882    }
[5809313]883
884    dc.EndDrawing();
885}
886
[c6d95d8]887Double GfxCore::GridXToScreen(Double x, Double y, Double z)
[c1cf79d]888{
889    x += m_Params.translation.x;
890    y += m_Params.translation.y;
891    z += m_Params.translation.z;
892
[6df24ee]893    return (XToScreen(x, y, z) * m_Params.scale) + m_XCentre;
[c1cf79d]894}
895
[c6d95d8]896Double GfxCore::GridYToScreen(Double x, Double y, Double z)
[c1cf79d]897{
898    x += m_Params.translation.x;
899    y += m_Params.translation.y;
900    z += m_Params.translation.z;
901
[6df24ee]902    return m_YCentre - ((ZToScreen(x, y, z) * m_Params.scale));
[c1cf79d]903}
904
905void GfxCore::DrawGrid()
906{
907    // Draw the grid.
[156dc16]908    SetColour(col_RED);
[c1cf79d]909
910    // Calculate the extent of the survey, in metres across the screen plane.
[c6d95d8]911    Double m_across_screen = Double(m_XSize / m_Params.scale);
[c1cf79d]912    // Calculate the length of the scale bar in metres.
913    //--move this elsewhere
[c6d95d8]914    Double size_snap = pow(10.0, floor(log10(0.75 * m_across_screen)));
915    Double t = m_across_screen * 0.75 / size_snap;
[c1cf79d]916    if (t >= 5.0) {
[421b7d2]917        size_snap *= 5.0;
[c1cf79d]918    }
919    else if (t >= 2.0) {
[421b7d2]920        size_snap *= 2.0;
[c1cf79d]921    }
922
[c6d95d8]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;
[c1cf79d]930    int count_x = (int) ceil((right - left) / grid_size);
931    int count_y = (int) ceil((top - bottom) / grid_size);
[c6d95d8]932    Double actual_right = left + count_x*grid_size;
933    Double actual_top = bottom + count_y*grid_size;
[c1cf79d]934
935    for (int xc = 0; xc <= count_x; xc++) {
[421b7d2]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));
[c1cf79d]939    }
940
941    for (int yc = 0; yc <= count_y; yc++) {
[421b7d2]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));
[c1cf79d]946    }
947}
948
[c300a04]949wxCoord GfxCore::GetClinoOffset()
950{
951    return m_Compass ? CLINO_OFFSET_X : INDICATOR_OFFSET_X;
952}
953
[c6d95d8]954wxPoint GfxCore::CompassPtToScreen(Double x, Double y, Double z)
[5809313]955{
[6606406]956    return wxPoint(long(-XToScreen(x, y, z)) + m_XSize - COMPASS_OFFSET_X,
[421b7d2]957                   long(ZToScreen(x, y, z)) + m_YSize - COMPASS_OFFSET_Y);
[6606406]958}
959
960wxPoint GfxCore::IndicatorCompassToScreenPan(int angle)
961{
[c6d95d8]962    Double theta = (angle * M_PI / 180.0) + m_PanAngle;
[6606406]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,
[421b7d2]968                   m_YSize - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE/2 - y);
[6606406]969}
970
971wxPoint GfxCore::IndicatorCompassToScreenElev(int angle)
972{
[303a525]973    Double theta = (angle * M_PI / 180.0) + m_TiltAngle + M_PI_2;
[6606406]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
[c300a04]978    return wxPoint(m_XSize - GetClinoOffset() - INDICATOR_BOX_SIZE/2 - x,
[421b7d2]979                   m_YSize - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE/2 - y);
[6606406]980}
981
[b56df45]982void GfxCore::DrawTick(wxCoord cx, wxCoord cy, int angle_cw)
983{
[c6d95d8]984    Double theta = angle_cw * M_PI / 180.0;
[b56df45]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
[6606406]995void GfxCore::Draw2dIndicators()
996{
997    // Draw the "traditional" elevation and compass indicators.
998
[c300a04]999    //-- code is a bit messy...
1000
[b56df45]1001    // Indicator backgrounds
[156dc16]1002    SetColour(col_GREY, true);
1003    SetColour(col_LIGHT_GREY_2);
1004
[303a525]1005    if (m_Compass && m_RotationOK) {
[421b7d2]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);
[c300a04]1010    }
[303a525]1011    if (m_Clino && m_Lock == lock_NONE) {
[421b7d2]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
[b56df45]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;
[c300a04]1037    wxCoord elev_centre_x = m_XSize - GetClinoOffset() - INDICATOR_BOX_SIZE/2;
[303a525]1038    if (m_Compass && m_RotationOK) {
[421b7d2]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        }
[b56df45]1051    }
[303a525]1052    if (m_Clino && m_Lock == lock_NONE) {
[421b7d2]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        }
[b56df45]1064    }
1065
[6606406]1066    // Pan arrow
[303a525]1067    if (m_Compass && m_RotationOK) {
[421b7d2]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);
[c300a04]1079    }
[6606406]1080
1081    // Elevation arrow
[303a525]1082    if (m_Clino && m_Lock == lock_NONE) {
[421b7d2]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);
[c300a04]1094    }
[6606406]1095
1096    // Text
1097    m_DrawDC.SetTextBackground(wxColour(0, 0, 0));
1098    m_DrawDC.SetTextForeground(TEXT_COLOUR);
1099
[da55c71]1100    wxCoord w, h;
1101    wxCoord width, height;
[c300a04]1102    wxString str;
[6606406]1103
[da55c71]1104    m_DrawDC.GetTextExtent(wxString("000"), &width, &h);
1105    height = m_YSize - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE - INDICATOR_GAP - h;
[421b7d2]1106
[303a525]1107    if (m_Compass && m_RotationOK) {
[7a89dc2]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));
[1d93721]1112        }
[421b7d2]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);
[c300a04]1118    }
1119
[303a525]1120    if (m_Clino && m_Lock == lock_NONE) {
[7a89dc2]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);
[1d93721]1126        }
[421b7d2]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);
[c300a04]1133    }
[5809313]1134}
1135
1136void GfxCore::DrawNames()
1137{
[84cab34]1138    // Draw station names.
1139    m_DrawDC.SetTextBackground(wxColour(0, 0, 0));
1140    m_DrawDC.SetTextForeground(LABEL_COLOUR);
[5809313]1141
[d5de678]1142    if (m_OverlappingNames) {
[421b7d2]1143        SimpleDrawNames();
[d5de678]1144    } else {
[421b7d2]1145        NattyDrawNames();
[84cab34]1146    }
[5809313]1147}
1148
1149void GfxCore::NattyDrawNames()
1150{
[84cab34]1151    // Draw station names, without overlapping.
[fa42426]1152    // FIXME: copied to OnSize()
[246426e]1153    const int quantise(FONT_SIZE / QUANTISE_FACTOR);
[84cab34]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;
[6097cd2]1157    if (!m_LabelGrid) m_LabelGrid = new char[buffer_size];
[d5de678]1158    memset((void*)m_LabelGrid, 0, buffer_size);
[5809313]1159
[d5de678]1160    list<LabelInfo*>::const_iterator label = m_Parent->GetLabels();
[865fc6e]1161    for ( ; label != m_Parent->GetLabelsEnd(); ++label) {
[246426e]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
[6df24ee]1170        Double x = GridXToScreen((*label)->x, (*label)->y, (*label)->z);
1171        Double y = GridYToScreen((*label)->x, (*label)->y, (*label)->z)
[d5de678]1172            + CROSS_SIZE - FONT_SIZE;
[84cab34]1173
[d5de678]1174        wxString str = (*label)->GetText();
[421b7d2]1175
[6df24ee]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;
[421b7d2]1182
[d5de678]1183        bool reject = true;
[5809313]1184
[d5de678]1185        if (ix >= 0 && ix < quantised_x && iy >= 0 && iy < quantised_y) {
[865fc6e]1186            char * test = &m_LabelGrid[ix + iy * quantised_x];
[246426e]1187            int len = str.Length() * QUANTISE_FACTOR + 1;
[d5de678]1188            reject = (ix + len >= quantised_x);
1189            int i = 0;
1190            while (!reject && i++ < len) {
1191                reject = *test++;
[421b7d2]1192            }
1193
[d5de678]1194            if (!reject) {
[6df24ee]1195                m_DrawDC.DrawText(str, (wxCoord)x, (wxCoord)y);
[421b7d2]1196
[d5de678]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++) {
[865fc6e]1200                    assert((ix + y0 * quantised_x) < (quantised_x * quantised_y));
[d5de678]1201                    memset((void*) &m_LabelGrid[ix + y0 * quantised_x], 1, len);
1202                }
[421b7d2]1203            }
1204        }
[84cab34]1205    }
[5809313]1206}
1207
1208void GfxCore::SimpleDrawNames()
1209{
[6c4bf98]1210    // Draw all station names, without worrying about overlaps
[d5de678]1211    list<LabelInfo*>::const_iterator label = m_Parent->GetLabels();
[fa42426]1212    while (label != m_Parent->GetLabelsEnd()) {
[d5de678]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;
[84cab34]1217    }
[5809313]1218}
1219
1220void GfxCore::DrawDepthbar()
1221{
[303a525]1222    if (m_Parent->GetZExtent() == 0.0) return;
1223
[84cab34]1224    m_DrawDC.SetTextBackground(wxColour(0, 0, 0));
1225    m_DrawDC.SetTextForeground(TEXT_COLOUR);
[5809313]1226
[303a525]1227    int y = DEPTH_BAR_BLOCK_HEIGHT * (m_Bands - 1) + DEPTH_BAR_OFFSET_Y;
[c300a04]1228    int size = 0;
1229
[303a525]1230    wxString* strs = new wxString[m_Bands];
[6120efa]1231    int band;
1232    for (band = 0; band < m_Bands; band++) {
[303a525]1233        Double z = m_Parent->GetZMin() + m_Parent->GetZExtent() * band
1234                / (m_Bands - 1);
[865fc6e]1235
[421b7d2]1236        strs[band] = FormatLength(z, false);
[1d93721]1237
[2dfd768]1238        int x, dummy;
1239        m_DrawDC.GetTextExtent(strs[band], &x, &dummy);
[865fc6e]1240        if (x > size) size = x;
[c300a04]1241    }
1242
[cac1865]1243    int x_min = m_XSize - DEPTH_BAR_OFFSET_X - DEPTH_BAR_BLOCK_WIDTH
1244            - DEPTH_BAR_MARGIN - size;
[c300a04]1245
[156dc16]1246    SetColour(col_BLACK);
1247    SetColour(col_DARK_GREY, true);
[cac1865]1248    m_DrawDC.DrawRectangle(x_min - DEPTH_BAR_MARGIN
[1d93721]1249                             - DEPTH_BAR_EXTRA_LEFT_MARGIN,
[421b7d2]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,
[303a525]1253                           DEPTH_BAR_BLOCK_HEIGHT*(m_Bands - 1) + DEPTH_BAR_MARGIN*4);
[5809313]1254
[6120efa]1255    for (band = 0; band < m_Bands; band++) {
[303a525]1256        if (band < m_Bands - 1) {
[421b7d2]1257            m_DrawDC.SetPen(m_Parent->GetPen(band));
1258            m_DrawDC.SetBrush(m_Parent->GetBrush(band));
[cac1865]1259            m_DrawDC.DrawRectangle(x_min,
[1d93721]1260                                   y - DEPTH_BAR_BLOCK_HEIGHT,
[cac1865]1261                                   DEPTH_BAR_BLOCK_WIDTH,
1262                                   DEPTH_BAR_BLOCK_HEIGHT);
[421b7d2]1263        }
[5809313]1264
[421b7d2]1265        m_DrawDC.DrawText(strs[band], x_min + DEPTH_BAR_BLOCK_WIDTH + 5,
[303a525]1266                          y - (FONT_SIZE / 2) - 1);
[5809313]1267
[421b7d2]1268        y -= DEPTH_BAR_BLOCK_HEIGHT;
[84cab34]1269    }
[c300a04]1270
1271    delete[] strs;
[5809313]1272}
1273
[c6d95d8]1274wxString GfxCore::FormatLength(Double size_snap, bool scalebar)
[ac537e9]1275{
1276    wxString str;
[1eb2590]1277    bool negative = (size_snap < 0.0);
1278
1279    if (negative) {
[421b7d2]1280        size_snap = -size_snap;
[1eb2590]1281    }
[ac537e9]1282
1283    if (size_snap == 0.0) {
[421b7d2]1284        str = "0";
[7a89dc2]1285    } else if (m_Metric) {
[ac537e9]1286#ifdef SILLY_UNITS
[421b7d2]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);
[ac537e9]1295#else
[421b7d2]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);
[ac537e9]1305#ifdef SILLY_UNITS
[421b7d2]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);
[ac537e9]1312#else
[421b7d2]1313        } else {
1314            str = wxString::Format(scalebar ? "%.fkm" : "%.2fkm", size_snap * 1e-3);
[ac537e9]1315#endif
[421b7d2]1316        }
[7a89dc2]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        }
[ac537e9]1333    }
1334
[46007bf]1335    return negative ? wxString("-") + str : str;
[ac537e9]1336}
1337
[5809313]1338void GfxCore::DrawScalebar()
1339{
[84cab34]1340    // Draw the scalebar.
[5809313]1341
[865fc6e]1342    if (m_Lock == lock_POINT) return;
[156dc16]1343
[865fc6e]1344    // Calculate how many metres of survey are currently displayed across the
1345    // screen.
1346    int x_size = m_XSize;
[7a89dc2]1347    Double across_screen = Double(x_size / m_Params.scale);
[98860c5]1348
[865fc6e]1349    // Convert to imperial measurements if required.
1350    Double multiplier = 1.0;
[7a89dc2]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    }
[5757725]1359
[7a89dc2]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;
[98860c5]1363    if (t >= 5.0) {
[421b7d2]1364        size_snap *= 5.0;
[7a89dc2]1365    } else if (t >= 2.0) {
[421b7d2]1366        size_snap *= 2.0;
[98860c5]1367    }
1368
[7a89dc2]1369    if (!m_Metric) size_snap *= multiplier;
1370
[84cab34]1371    // Actual size of the thing in pixels:
[5809313]1372    int size = int(size_snap * m_Params.scale);
[865fc6e]1373    m_ScaleBar.width = size;
[421b7d2]1374
[5809313]1375    // Draw it...
[156dc16]1376    //--FIXME: improve this
[42adb19]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;
[5809313]1380    int interval = size / 10;
1381
[84cab34]1382    bool solid = true;
[5809313]1383    for (int ix = 0; ix < 10; ix++) {
[421b7d2]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);
[156dc16]1388
[421b7d2]1389        m_DrawDC.DrawRectangle(x, end_y, interval + 2, height);
1390
1391        solid = !solid;
[5809313]1392    }
1393
[84cab34]1394    // Add labels.
[ac537e9]1395    wxString str = FormatLength(size_snap);
[84cab34]1396
1397    m_DrawDC.SetTextBackground(wxColour(0, 0, 0));
1398    m_DrawDC.SetTextForeground(TEXT_COLOUR);
[ac537e9]1399    m_DrawDC.DrawText("0", end_x, end_y - FONT_SIZE - 4);
[5809313]1400
[84cab34]1401    int text_width, text_height;
[ac537e9]1402    m_DrawDC.GetTextExtent(str, &text_width, &text_height);
1403    m_DrawDC.DrawText(str, end_x + size - text_width, end_y - FONT_SIZE - 4);
[5809313]1404}
1405
1406//
1407//  Mouse event handling methods
1408//
1409
1410void GfxCore::OnLButtonDown(wxMouseEvent& event)
1411{
[168df28]1412    SetFocus();
[303a525]1413    if (m_PlotData && m_Lock != lock_POINT) {
[421b7d2]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());
[5b2ea2c]1418
1419        CaptureMouse();
[0324bd8]1420    }
[5809313]1421}
1422
1423void GfxCore::OnLButtonUp(wxMouseEvent& event)
1424{
[303a525]1425    if (m_PlotData && m_Lock != lock_POINT) {
[421b7d2]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();
[0324bd8]1440    }
[5809313]1441}
1442
1443void GfxCore::OnMButtonDown(wxMouseEvent& event)
1444{
[168df28]1445    SetFocus();
[73b17b8]1446    if (m_PlotData && m_Lock == lock_NONE) {
[421b7d2]1447        m_DraggingMiddle = true;
1448        m_DragStart = wxPoint(event.GetX(), event.GetY());
1449
[5b2ea2c]1450        CaptureMouse();
[5809313]1451    }
1452}
1453
[2dfd768]1454void GfxCore::OnMButtonUp(wxMouseEvent&)
[5809313]1455{
[de7a879]1456    if (m_PlotData && m_Lock == lock_NONE) {
[421b7d2]1457        m_DraggingMiddle = false;
1458        ReleaseMouse();
[5809313]1459    }
1460}
1461
1462void GfxCore::OnRButtonDown(wxMouseEvent& event)
1463{
[168df28]1464    SetFocus();
[0324bd8]1465    if (m_PlotData) {
[421b7d2]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
[5b2ea2c]1471        CaptureMouse();
[0324bd8]1472    }
[5809313]1473}
1474
[2dfd768]1475void GfxCore::OnRButtonUp(wxMouseEvent&)
[5809313]1476{
1477    m_DraggingRight = false;
[e02d6b8]1478    m_LastDrag = drag_NONE;
[5b2ea2c]1479    ReleaseMouse();
[5809313]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
[cd8d79a]1488    if (m_ReverseControls) {
1489        dx = -dx;
1490        dy = -dy;
1491    }
1492
[303a525]1493    Double pan_angle = m_RotationOK ? (Double(dx) * (-M_PI / 500.0)) : 0.0;
[5809313]1494
[cc9150c]1495    // left/right => rotate, up/down => scale
1496    TurnCave(pan_angle);
[5809313]1497
[cc9150c]1498    if (control) {
1499        // For now...
[5806785]1500        if (m_RotationOK) TiltCave(Double(-dy) * M_PI / 500.0);
[cc9150c]1501    } else {
1502        SetScale(m_Params.scale *= pow(1.06, 0.08 * dy));
[5809313]1503    }
1504
[fa42426]1505    ForceRefresh();
[5809313]1506
1507    m_DragStart = point;
1508}
1509
[c6d95d8]1510void GfxCore::TurnCave(Double angle)
[5809313]1511{
1512    // Turn the cave around its z-axis by a given angle.
1513    m_PanAngle += angle;
[cc9150c]1514    if (m_PanAngle >= M_PI * 2.0) {
[3e784b5]1515        m_PanAngle -= M_PI * 2.0;
[cc9150c]1516    } else if (m_PanAngle < 0.0) {
[3e784b5]1517        m_PanAngle += M_PI * 2.0;
[5809313]1518    }
[f3ddc29]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();
[5809313]1524}
1525
[c6d95d8]1526void GfxCore::TurnCaveTo(Double angle)
[f626e1f]1527{
1528    // Turn the cave to a particular pan angle.
1529    TurnCave(angle - m_PanAngle);
1530}
1531
[c6d95d8]1532void GfxCore::TiltCave(Double tilt_angle)
[5809313]1533{
1534    // Tilt the cave by a given angle.
[303a525]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;
[5809313]1539    }
1540
1541    m_TiltAngle += tilt_angle;
1542
[f3ddc29]1543    m_Params.rotation.setFromEulerAngles(0.0, 0.0, m_PanAngle);
[5809313]1544    Quaternion q;
[f3ddc29]1545    q.setFromEulerAngles(m_TiltAngle, 0.0, 0.0);
[5809313]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.
[cc9150c]1553    int dy = point.y - m_DragStart.y;
[5809313]1554
[cc9150c]1555    if (m_ReverseControls) dy = -dy;
[5809313]1556
[cc9150c]1557    TiltCave(Double(-dy) * M_PI / 500.0);
[cd8d79a]1558
[cc9150c]1559    m_DragStart = point;
[5809313]1560
[cc9150c]1561    ForceRefresh();
[5809313]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;
[1d93721]1569
[c8c116c]1570    if (m_ReverseControls) {
1571        dx = -dx;
1572        dy = -dy;
1573    }
[5809313]1574
[c8c116c]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.
[c6d95d8]1582    Double x = Double(dx / m_Params.scale);
1583    Double z = Double(-dy / m_Params.scale);
[cd8d79a]1584
[5809313]1585    Matrix4 inverse_rotation = m_Params.rotation.asInverseMatrix();
[156dc16]1586
[c6d95d8]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);
[421b7d2]1590
[5809313]1591    // Update parameters and redraw.
1592    m_Params.translation.x += cx;
1593    m_Params.translation.y += cy;
1594    m_Params.translation.z += cz;
1595
[fa42426]1596    ForceRefresh();
[5809313]1597}
1598
[2effbf1]1599void GfxCore::CheckHitTestGrid(wxPoint& point, bool centre)
1600{
[fa42426]1601    if (point.x < 0 || point.x >= m_XSize || point.y < 0 || point.y >= m_YSize) {
[421b7d2]1602        return;
[137e31b]1603    }
[421b7d2]1604
[fa42426]1605    if (!m_HitTestGridValid) CreateHitTestGrid();
1606
[2effbf1]1607    int grid_x = (point.x * (HITTEST_SIZE - 1)) / m_XSize;
1608    int grid_y = (point.y * (HITTEST_SIZE - 1)) / m_YSize;
[137e31b]1609
[fa42426]1610    LabelInfo *best = NULL;
1611    int dist_sqrd = 25;
[2effbf1]1612    int square = grid_x + grid_y * HITTEST_SIZE;
[fa42426]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;
[697774f]1626
[fa42426]1627        dist_sqrd = ds;
1628        best = pt;
[697774f]1629
[fa42426]1630        if (ds == 0) break;
[2effbf1]1631    }
[697774f]1632
[fa42426]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 {
[421b7d2]1641        m_Parent->SetMouseOverStation(NULL);
[2effbf1]1642    }
1643}
1644
[5809313]1645void GfxCore::OnMouseMove(wxMouseEvent& event)
1646{
[189f179]1647    // Mouse motion event handler.
1648    if (!m_PlotData) return;
[5809313]1649
1650    wxPoint point = wxPoint(event.GetX(), event.GetY());
1651
[dfe4454c]1652    // Check hit-test grid (only if no buttons are pressed).
1653    if (!event.LeftIsDown() && !event.MiddleIsDown() && !event.RightIsDown()) {
[421b7d2]1654        CheckHitTestGrid(point, false);
[8000d8f]1655    }
[39e460c9]1656
[e073702]1657    // Update coordinate display if in plan view, or altitude if in elevation
1658    // view.
[303a525]1659    if (m_TiltAngle == M_PI_2) {
[c8c116c]1660        int x = event.GetX() - m_XCentre;
1661        int y = -(event.GetY() - m_YCentre);
[421b7d2]1662        Matrix4 inverse_rotation = m_Params.rotation.asInverseMatrix();
[156dc16]1663
[421b7d2]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);
[156dc16]1666
[421b7d2]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());
[7a89dc2]1669    } else if (m_TiltAngle == 0.0) {
[c8c116c]1670        int z = -(event.GetY() - m_YCentre);
[7a89dc2]1671        m_Parent->SetAltitude(z / m_Params.scale - m_Params.translation.z + m_Parent->GetZOffset());
1672    } else {
[421b7d2]1673        m_Parent->ClearCoords();
[156dc16]1674    }
1675
[3d00693]1676    if (!m_SwitchingTo) {
[421b7d2]1677        if (m_DraggingLeft) {
[cc9150c]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
[865fc6e]1701                double angle = atan2(double(dx0), double(dy)) - M_PI;
[cc9150c]1702                if (dx0 * dx0 + dy * dy <= radius * radius) {
[2b94be8]1703                    TurnCaveTo(angle);
[cc9150c]1704                    m_MouseOutsideCompass = false;
1705                }
1706                else {
[2b94be8]1707                    TurnCaveTo(int(int(angle * 180.0 / M_PI) / 45) *
[cc9150c]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) {
[865fc6e]1716                    TiltCave(atan2(double(dy), double(dx1)) - m_TiltAngle);
[cc9150c]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            }
[421b7d2]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;
[fa42426]1766                  ForceRefresh();
[421b7d2]1767            }
1768            else {
1769                m_LastDrag = drag_MAIN;
1770                HandleTranslate(point);
1771            }
1772        }
[5809313]1773    }
[26f3bd8]1774
1775    m_DragLast = point;
[5809313]1776}
1777
[421b7d2]1778void GfxCore::OnSize(wxSizeEvent& event)
[5809313]1779{
1780    // Handle a change in window size.
1781
1782    wxSize size = event.GetSize();
1783
[a7b2985]1784    if (size.GetWidth() <= 0 || size.GetHeight() <= 0) {
[c464728]1785        // Before things are fully initialised, we sometimes get a bogus
1786        // resize message...
1787        return;
1788    }
1789
[5809313]1790    m_XSize = size.GetWidth();
1791    m_YSize = size.GetHeight();
1792    m_XCentre = m_XSize / 2;
1793    m_YCentre = m_YSize / 2;
1794
[137bf99]1795    if (m_InitialisePending) {
[421b7d2]1796        Initialise();
1797        m_InitialisePending = false;
1798        m_DoneFirstShow = true;
[137bf99]1799    }
[068b4f2]1800
[5809313]1801    if (m_DoneFirstShow) {
[fa42426]1802        // FIXME: copied from NattyDrawNames()
[246426e]1803        const int quantise(FONT_SIZE / QUANTISE_FACTOR);
[fa42426]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];
[421b7d2]1810        CreateHitTestGrid();
[6ebc0ce]1811
[47d495f]1812#ifndef __WXMOTIF__
[421b7d2]1813        m_DrawDC.SelectObject(wxNullBitmap);
[47d495f]1814#endif
[421b7d2]1815        if (m_OffscreenBitmap) {
1816            delete m_OffscreenBitmap;
[d8622e1]1817        }
1818        m_OffscreenBitmap = new wxBitmap;
[421b7d2]1819        m_OffscreenBitmap->Create(m_XSize, m_YSize);
1820        m_DrawDC.SelectObject(*m_OffscreenBitmap);
1821        RedrawOffscreen();
1822        Refresh(false);
[5809313]1823    }
1824}
1825
[4b1fc48]1826void GfxCore::OnDisplayOverlappingNames()
[5809313]1827{
[84cab34]1828    m_OverlappingNames = !m_OverlappingNames;
[fa42426]1829    ForceRefresh();
[5809313]1830}
1831
[421b7d2]1832void GfxCore::OnDisplayOverlappingNamesUpdate(wxUpdateUIEvent& cmd)
[5809313]1833{
[84cab34]1834    cmd.Enable(m_PlotData != NULL && m_Names);
1835    cmd.Check(m_OverlappingNames);
[5809313]1836}
1837
[4b1fc48]1838void GfxCore::OnShowCrosses()
[5809313]1839{
[84cab34]1840    m_Crosses = !m_Crosses;
[fa42426]1841    ForceRefresh();
[5809313]1842}
1843
[421b7d2]1844void GfxCore::OnShowCrossesUpdate(wxUpdateUIEvent& cmd)
[5809313]1845{
[189f179]1846    cmd.Enable(m_PlotData != NULL);
[84cab34]1847    cmd.Check(m_Crosses);
[5809313]1848}
1849
[4b1fc48]1850void GfxCore::OnShowStationNames()
[5809313]1851{
[84cab34]1852    m_Names = !m_Names;
[fa42426]1853    ForceRefresh();
[5809313]1854}
1855
[421b7d2]1856void GfxCore::OnShowStationNamesUpdate(wxUpdateUIEvent& cmd)
[5809313]1857{
[84cab34]1858    cmd.Enable(m_PlotData != NULL);
1859    cmd.Check(m_Names);
[5809313]1860}
1861
[421b7d2]1862void GfxCore::OnShowSurveyLegs()
[5809313]1863{
[84cab34]1864    m_Legs = !m_Legs;
[fa42426]1865    ForceRefresh();
[5809313]1866}
1867
[421b7d2]1868void GfxCore::OnShowSurveyLegsUpdate(wxUpdateUIEvent& cmd)
[5809313]1869{
[e8bc148]1870    cmd.Enable(m_PlotData != NULL && m_Lock != lock_POINT && m_UndergroundLegs);
[84cab34]1871    cmd.Check(m_Legs);
[5809313]1872}
1873
[421b7d2]1874void GfxCore::OnMoveEast()
[5809313]1875{
[303a525]1876    TurnCaveTo(M_PI_2);
[cc9150c]1877    ForceRefresh();
[5809313]1878}
1879
[421b7d2]1880void GfxCore::OnMoveEastUpdate(wxUpdateUIEvent& cmd)
[5809313]1881{
[303a525]1882    cmd.Enable(m_PlotData != NULL && !(m_Lock & lock_Y));
[5809313]1883}
1884
[421b7d2]1885void GfxCore::OnMoveNorth()
[5809313]1886{
[f626e1f]1887    TurnCaveTo(0.0);
[cc9150c]1888    ForceRefresh();
[5809313]1889}
1890
[421b7d2]1891void GfxCore::OnMoveNorthUpdate(wxUpdateUIEvent& cmd)
[5809313]1892{
[303a525]1893    cmd.Enable(m_PlotData != NULL && !(m_Lock & lock_X));
[5809313]1894}
1895
[421b7d2]1896void GfxCore::OnMoveSouth()
[5809313]1897{
[f626e1f]1898    TurnCaveTo(M_PI);
[cc9150c]1899    ForceRefresh();
[5809313]1900}
1901
[421b7d2]1902void GfxCore::OnMoveSouthUpdate(wxUpdateUIEvent& cmd)
[5809313]1903{
[303a525]1904    cmd.Enable(m_PlotData != NULL && !(m_Lock & lock_X));
[5809313]1905}
1906
[4b1fc48]1907void GfxCore::OnMoveWest()
[5809313]1908{
[f626e1f]1909    TurnCaveTo(M_PI * 1.5);
[cc9150c]1910    ForceRefresh();
[5809313]1911}
1912
[421b7d2]1913void GfxCore::OnMoveWestUpdate(wxUpdateUIEvent& cmd)
[5809313]1914{
[303a525]1915    cmd.Enable(m_PlotData != NULL && !(m_Lock & lock_Y));
[5809313]1916}
1917
[421b7d2]1918void GfxCore::OnStartRotation()
[5809313]1919{
1920    m_Rotating = true;
[a8e9fde]1921    timer.Start(drawtime);
[5809313]1922}
1923
[421b7d2]1924void GfxCore::OnStartRotationUpdate(wxUpdateUIEvent& cmd)
[5809313]1925{
[303a525]1926    cmd.Enable(m_PlotData != NULL && !m_Rotating && m_RotationOK);
[5809313]1927}
1928
[4b1fc48]1929void GfxCore::OnToggleRotation()
[7ebc3d1]1930{
[303a525]1931    m_Rotating = !m_Rotating;
[a8e9fde]1932    if (m_Rotating) timer.Start(drawtime);
[7ebc3d1]1933}
1934
[421b7d2]1935void GfxCore::OnToggleRotationUpdate(wxUpdateUIEvent& cmd)
[7ebc3d1]1936{
[303a525]1937    cmd.Enable(m_PlotData != NULL && m_RotationOK);
[7ebc3d1]1938    cmd.Check(m_PlotData != NULL && m_Rotating);
1939}
1940
[421b7d2]1941void GfxCore::OnStopRotation()
[5809313]1942{
[84cab34]1943    m_Rotating = false;
[5809313]1944}
1945
[421b7d2]1946void GfxCore::OnStopRotationUpdate(wxUpdateUIEvent& cmd)
[5809313]1947{
[cd8d79a]1948    cmd.Enable(m_PlotData != NULL && m_Rotating);
[5809313]1949}
1950
[421b7d2]1951void GfxCore::OnReverseControls()
[5809313]1952{
[84cab34]1953    m_ReverseControls = !m_ReverseControls;
[5809313]1954}
1955
[421b7d2]1956void GfxCore::OnReverseControlsUpdate(wxUpdateUIEvent& cmd)
[5809313]1957{
[cd8d79a]1958    cmd.Enable(m_PlotData != NULL);
[84cab34]1959    cmd.Check(m_ReverseControls);
[5809313]1960}
1961
[4b1fc48]1962void GfxCore::OnReverseDirectionOfRotation()
[5809313]1963{
[84cab34]1964    m_RotationStep = -m_RotationStep;
[5809313]1965}
1966
[421b7d2]1967void GfxCore::OnReverseDirectionOfRotationUpdate(wxUpdateUIEvent& cmd)
[5809313]1968{
[bd7a61b]1969    cmd.Enable(m_PlotData != NULL && m_RotationOK);
[5809313]1970}
1971
[ba2c5b1]1972void GfxCore::OnSlowDown(bool accel)
[5809313]1973{
[ba2c5b1]1974    m_RotationStep /= accel ? 1.44 : 1.2;
[a8e9fde]1975    if (m_RotationStep < M_PI / 180.0) {
1976        m_RotationStep = (Double) M_PI / 180.0;
[84cab34]1977    }
[5809313]1978}
1979
[421b7d2]1980void GfxCore::OnSlowDownUpdate(wxUpdateUIEvent& cmd)
[5809313]1981{
[bd7a61b]1982    cmd.Enable(m_PlotData != NULL && m_RotationOK);
[5809313]1983}
1984
[ba2c5b1]1985void GfxCore::OnSpeedUp(bool accel)
[5809313]1986{
[ba2c5b1]1987    m_RotationStep *= accel ? 1.44 : 1.2;
[a8e9fde]1988    if (m_RotationStep > 2.5 * M_PI) {
1989        m_RotationStep = (Double) 2.5 * M_PI;
[84cab34]1990    }
[5809313]1991}
1992
[421b7d2]1993void GfxCore::OnSpeedUpUpdate(wxUpdateUIEvent& cmd)
[5809313]1994{
[bd7a61b]1995    cmd.Enable(m_PlotData != NULL && m_RotationOK);
[5809313]1996}
1997
[ba2c5b1]1998void GfxCore::OnStepOnceAnticlockwise(bool accel)
[5809313]1999{
[ba2c5b1]2000    TurnCave(accel ? 5.0 * M_PI / 18.0 : M_PI / 18.0);
[cc9150c]2001    ForceRefresh();
[5809313]2002}
2003
[421b7d2]2004void GfxCore::OnStepOnceAnticlockwiseUpdate(wxUpdateUIEvent& cmd)
[5809313]2005{
[bd7a61b]2006    cmd.Enable(m_PlotData != NULL && m_RotationOK && !m_Rotating);
[5809313]2007}
2008
[ba2c5b1]2009void GfxCore::OnStepOnceClockwise(bool accel)
[5809313]2010{
[ba2c5b1]2011    TurnCave(accel ? 5.0 * -M_PI / 18.0 : -M_PI / 18.0);
[cc9150c]2012    ForceRefresh();
[5809313]2013}
2014
[421b7d2]2015void GfxCore::OnStepOnceClockwiseUpdate(wxUpdateUIEvent& cmd)
[5809313]2016{
[bd7a61b]2017    cmd.Enable(m_PlotData != NULL && m_RotationOK && !m_Rotating);
[5809313]2018}
[84cab34]2019
[421b7d2]2020void GfxCore::OnDefaults()
[5809313]2021{
[5806785]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
[714daae]2027    m_PanAngle = 0.0;
[5806785]2028    m_TiltAngle = M_PI_2;
[5dedcca]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    }
[714daae]2059
2060    m_Params.rotation.setFromEulerAngles(m_TiltAngle, 0.0, m_PanAngle);
[5809313]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;
[421b7d2]2066
[e8bc148]2067    m_Surface = false;
2068    m_SurfaceDepth = false;
[1911ea1]2069    m_SurfaceDashed = true;
[a8e9fde]2070    m_RotationStep = M_PI / 6.0;
[5809313]2071    m_Rotating = false;
[3d00693]2072    m_SwitchingTo = 0;
[fe444b8]2073    m_Entrances = false;
2074    m_FixedPts = false;
2075    m_ExportedPts = false;
[c1cf79d]2076    m_Grid = false;
[de7a879]2077    SetScale(m_InitialScale);
[fa42426]2078    ForceRefresh();
[5809313]2079}
[84cab34]2080
[421b7d2]2081void GfxCore::OnDefaultsUpdate(wxUpdateUIEvent& cmd)
[5809313]2082{
[84cab34]2083    cmd.Enable(m_PlotData != NULL);
[5809313]2084}
[84cab34]2085
[4b1fc48]2086void GfxCore::OnElevation()
[5809313]2087{
2088    // Switch to elevation view.
[bd7a61b]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
[b625917]2099            TiltCave(-m_TiltAngle);
[bd7a61b]2100            m_SwitchingTo = 0;
[cc9150c]2101            ForceRefresh();
[1d93721]2102    }
[5809313]2103}
[84cab34]2104
[421b7d2]2105void GfxCore::OnElevationUpdate(wxUpdateUIEvent& cmd)
[5809313]2106{
[cc9150c]2107    cmd.Enable(m_PlotData != NULL && m_Lock == lock_NONE && m_TiltAngle != 0.0);
[5809313]2108}
2109
[ba2c5b1]2110void GfxCore::OnHigherViewpoint(bool accel)
[5809313]2111{
[84cab34]2112    // Raise the viewpoint.
[ba2c5b1]2113    TiltCave(accel ? 5.0 * M_PI / 18.0 : M_PI / 18.0);
[cc9150c]2114    ForceRefresh();
[5809313]2115}
2116
[421b7d2]2117void GfxCore::OnHigherViewpointUpdate(wxUpdateUIEvent& cmd)
[5809313]2118{
[303a525]2119    cmd.Enable(m_PlotData != NULL && m_TiltAngle < M_PI_2 &&
[421b7d2]2120               m_Lock == lock_NONE);
[5809313]2121}
2122
[ba2c5b1]2123void GfxCore::OnLowerViewpoint(bool accel)
[5809313]2124{
[84cab34]2125    // Lower the viewpoint.
[ba2c5b1]2126    TiltCave(accel ? 5.0 * -M_PI / 18.0 : -M_PI / 18.0);
[cc9150c]2127    ForceRefresh();
[5809313]2128}
2129
[421b7d2]2130void GfxCore::OnLowerViewpointUpdate(wxUpdateUIEvent& cmd)
[5809313]2131{
[303a525]2132    cmd.Enable(m_PlotData != NULL && m_TiltAngle > -M_PI_2 &&
[421b7d2]2133               m_Lock == lock_NONE);
[5809313]2134}
[84cab34]2135
[4b1fc48]2136void GfxCore::OnPlan()
[5809313]2137{
2138    // Switch to plan view.
[bd7a61b]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;
[cc9150c]2151            ForceRefresh();
[865fc6e]2152    }
[5809313]2153}
[84cab34]2154
[421b7d2]2155void GfxCore::OnPlanUpdate(wxUpdateUIEvent& cmd)
[5809313]2156{
[cc9150c]2157    cmd.Enable(m_PlotData != NULL && m_Lock == lock_NONE &&
[bd7a61b]2158               m_TiltAngle != M_PI_2);
[5809313]2159}
2160
[ba2c5b1]2161void GfxCore::OnShiftDisplayDown(bool accel)
[5809313]2162{
[c8c116c]2163    TranslateCave(0, accel ? 5 * DISPLAY_SHIFT : DISPLAY_SHIFT);
[5809313]2164}
2165
[84cab34]2166void GfxCore::OnShiftDisplayDownUpdate(wxUpdateUIEvent& cmd)
[5809313]2167{
[84cab34]2168    cmd.Enable(m_PlotData != NULL);
[5809313]2169}
2170
[ba2c5b1]2171void GfxCore::OnShiftDisplayLeft(bool accel)
[5809313]2172{
[c8c116c]2173    TranslateCave(accel ? -5 * DISPLAY_SHIFT : -DISPLAY_SHIFT, 0);
[5809313]2174}
2175
[84cab34]2176void GfxCore::OnShiftDisplayLeftUpdate(wxUpdateUIEvent& cmd)
[5809313]2177{
[84cab34]2178    cmd.Enable(m_PlotData != NULL);
[5809313]2179}
2180
[ba2c5b1]2181void GfxCore::OnShiftDisplayRight(bool accel)
[5809313]2182{
[c8c116c]2183    TranslateCave(accel ? 5 * DISPLAY_SHIFT : DISPLAY_SHIFT, 0);
[5809313]2184}
2185
[421b7d2]2186void GfxCore::OnShiftDisplayRightUpdate(wxUpdateUIEvent& cmd)
[5809313]2187{
[84cab34]2188    cmd.Enable(m_PlotData != NULL);
[5809313]2189}
2190
[ba2c5b1]2191void GfxCore::OnShiftDisplayUp(bool accel)
[5809313]2192{
[c8c116c]2193    TranslateCave(0, accel ? -5 * DISPLAY_SHIFT : -DISPLAY_SHIFT);
[5809313]2194}
2195
[421b7d2]2196void GfxCore::OnShiftDisplayUpUpdate(wxUpdateUIEvent& cmd)
[5809313]2197{
[84cab34]2198    cmd.Enable(m_PlotData != NULL);
[5809313]2199}
2200
[ba2c5b1]2201void GfxCore::OnZoomIn(bool accel)
[5809313]2202{
[84cab34]2203    // Increase the scale.
[5809313]2204
[b6de07d]2205    SetScale(m_Params.scale * (accel ? 1.1236 : 1.06));
[fa42426]2206    ForceRefresh();
[5809313]2207}
2208
[421b7d2]2209void GfxCore::OnZoomInUpdate(wxUpdateUIEvent& cmd)
[5809313]2210{
[de7a879]2211    cmd.Enable(m_PlotData != NULL && m_Lock != lock_POINT);
[5809313]2212}
2213
[ba2c5b1]2214void GfxCore::OnZoomOut(bool accel)
[5809313]2215{
[84cab34]2216    // Decrease the scale.
[5809313]2217
[b6de07d]2218    SetScale(m_Params.scale / (accel ? 1.1236 : 1.06));
[fa42426]2219    ForceRefresh();
[5809313]2220}
2221
[421b7d2]2222void GfxCore::OnZoomOutUpdate(wxUpdateUIEvent& cmd)
[5809313]2223{
[de7a879]2224    cmd.Enable(m_PlotData != NULL && m_Lock != lock_POINT);
[5809313]2225}
[84cab34]2226
[a8e9fde]2227void GfxCore::OnIdle(wxIdleEvent& event)
[5809313]2228{
[7757a4ed]2229    // Handle an idle event.
[582b10b]2230    if (Animate()) ForceRefresh();
[7757a4ed]2231}
2232
2233// return: true if animation occured (and ForceRefresh() needs to be called)
[582b10b]2234bool GfxCore::Animate()
[7757a4ed]2235{
[e2df869]2236    if (!m_Rotating && !m_SwitchingTo) {
[7757a4ed]2237        return false;
2238    }
2239
[d08c44b]2240    static double last_t = 0;
[a8e9fde]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;
[d08c44b]2245    if (last_t > 0) t = (t + last_t) / 2;
2246    last_t = t;
[5809313]2247
2248    // When rotating...
2249    if (m_Rotating) {
[a8e9fde]2250        TurnCave(m_RotationStep * t);
[5809313]2251    }
[7757a4ed]2252
[3d00693]2253    if (m_SwitchingTo == PLAN) {
[bd7a61b]2254        // When switching to plan view...
[a8e9fde]2255        TiltCave(M_PI_2 * t);
[7757a4ed]2256        if (m_TiltAngle == M_PI_2) m_SwitchingTo = 0;
[bd7a61b]2257    } else if (m_SwitchingTo == ELEVATION) {
2258        // When switching to elevation view...
[7757a4ed]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);
[421b7d2]2266        }
[5809313]2267    }
[156dc16]2268
[7757a4ed]2269    return true;
[5809313]2270}
[84cab34]2271
[421b7d2]2272void GfxCore::OnToggleScalebar()
[5809313]2273{
[84cab34]2274    m_Scalebar = !m_Scalebar;
[fa42426]2275    ForceRefresh();
[5809313]2276}
2277
[421b7d2]2278void GfxCore::OnToggleScalebarUpdate(wxUpdateUIEvent& cmd)
[5809313]2279{
[303a525]2280    cmd.Enable(m_PlotData != NULL && m_Lock != lock_POINT);
[84cab34]2281    cmd.Check(m_Scalebar);
[5809313]2282}
2283
[421b7d2]2284void GfxCore::OnToggleDepthbar()
[5809313]2285{
[84cab34]2286    m_Depthbar = !m_Depthbar;
[fa42426]2287    ForceRefresh();
[5809313]2288}
2289
[421b7d2]2290void GfxCore::OnToggleDepthbarUpdate(wxUpdateUIEvent& cmd)
[5809313]2291{
[303a525]2292    cmd.Enable(m_PlotData != NULL && !(m_Lock && lock_Z));
[84cab34]2293    cmd.Check(m_Depthbar);
[5809313]2294}
2295
[421b7d2]2296void GfxCore::OnViewCompass()
[5809313]2297{
[84cab34]2298    m_Compass = !m_Compass;
[fa42426]2299    ForceRefresh();
[5809313]2300}
2301
[421b7d2]2302void GfxCore::OnViewCompassUpdate(wxUpdateUIEvent& cmd)
[5809313]2303{
[cc9150c]2304    cmd.Enable(m_PlotData != NULL && m_RotationOK);
[84cab34]2305    cmd.Check(m_Compass);
[5809313]2306}
[c300a04]2307
[421b7d2]2308void GfxCore::OnViewClino()
[c300a04]2309{
2310    m_Clino = !m_Clino;
[fa42426]2311    ForceRefresh();
[c300a04]2312}
2313
[421b7d2]2314void GfxCore::OnViewClinoUpdate(wxUpdateUIEvent& cmd)
[c300a04]2315{
[cc9150c]2316    cmd.Enable(m_PlotData != NULL && m_Lock == lock_NONE);
[c300a04]2317    cmd.Check(m_Clino);
2318}
[e8bc148]2319
[4b1fc48]2320void GfxCore::OnShowSurface()
[e8bc148]2321{
2322    m_Surface = !m_Surface;
[fa42426]2323    ForceRefresh();
[e8bc148]2324}
2325
[4b1fc48]2326void GfxCore::OnShowSurfaceDepth()
[e8bc148]2327{
2328    m_SurfaceDepth = !m_SurfaceDepth;
[fa42426]2329    ForceRefresh();
[e8bc148]2330}
2331
[4b1fc48]2332void GfxCore::OnShowSurfaceDashed()
[e8bc148]2333{
2334    m_SurfaceDashed = !m_SurfaceDashed;
[fa42426]2335    ForceRefresh();
[e8bc148]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}
[fe444b8]2355
[4b1fc48]2356void GfxCore::OnShowEntrances()
[fe444b8]2357{
2358    m_Entrances = !m_Entrances;
[fa42426]2359    ForceRefresh();
[fe444b8]2360}
2361
2362void GfxCore::OnShowEntrancesUpdate(wxUpdateUIEvent& cmd)
2363{
[a9a32f2]2364    cmd.Enable(m_PlotData && (m_Parent->GetNumEntrances() > 0));
[fe444b8]2365    cmd.Check(m_Entrances);
2366}
2367
[4b1fc48]2368void GfxCore::OnShowFixedPts()
[fe444b8]2369{
2370    m_FixedPts = !m_FixedPts;
[fa42426]2371    ForceRefresh();
[fe444b8]2372}
2373
2374void GfxCore::OnShowFixedPtsUpdate(wxUpdateUIEvent& cmd)
2375{
[a9a32f2]2376    cmd.Enable(m_PlotData && (m_Parent->GetNumFixedPts() > 0));
[fe444b8]2377    cmd.Check(m_FixedPts);
2378}
2379
[4b1fc48]2380void GfxCore::OnShowExportedPts()
[fe444b8]2381{
2382    m_ExportedPts = !m_ExportedPts;
[fa42426]2383    ForceRefresh();
[fe444b8]2384}
2385
2386void GfxCore::OnShowExportedPtsUpdate(wxUpdateUIEvent& cmd)
2387{
[a9a32f2]2388    cmd.Enable(m_PlotData && (m_Parent->GetNumExportedPts() > 0));
[fe444b8]2389    cmd.Check(m_ExportedPts);
2390}
[c1cf79d]2391
[4b1fc48]2392void GfxCore::OnViewGrid()
[c1cf79d]2393{
2394    m_Grid = !m_Grid;
[fa42426]2395    ForceRefresh();
[c1cf79d]2396}
2397
2398void GfxCore::OnViewGridUpdate(wxUpdateUIEvent& cmd)
2399{
2400    cmd.Enable(m_PlotData);
[bd17d3f2]2401    cmd.Check(m_Grid);
[c1cf79d]2402}
[c6d95d8]2403
[156dc16]2404void GfxCore::OnIndicatorsUpdate(wxUpdateUIEvent& cmd)
2405{
2406    cmd.Enable(m_PlotData);
2407}
2408
[7a89dc2]2409void GfxCore::OnToggleMetric()
2410{
2411    m_Metric = !m_Metric;
[6804731]2412    wxConfigBase::Get()->Write("metric", m_Metric);
2413    wxConfigBase::Get()->Flush();
[7b86ec9]2414    ForceRefresh();
[7a89dc2]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;
[6804731]2426    wxConfigBase::Get()->Write("degrees", m_Degrees);
2427    wxConfigBase::Get()->Flush();
[7b86ec9]2428    ForceRefresh();
[7a89dc2]2429}
2430
2431void GfxCore::OnToggleDegreesUpdate(wxUpdateUIEvent& cmd)
2432{
2433    cmd.Enable(m_PlotData);
2434    cmd.Check(m_Degrees);
2435}
2436
[156dc16]2437void GfxCore::CentreOn(Double x, Double y, Double z)
[e3513a5]2438{
[156dc16]2439    m_Params.translation.x = -x;
2440    m_Params.translation.y = -y;
2441    m_Params.translation.z = -z;
[fa42426]2442    ForceRefresh();
[156dc16]2443}
2444
[fa42426]2445void GfxCore::ForceRefresh()
[2a02de2]2446{
[fa42426]2447    // Redraw the offscreen bitmap
[2a02de2]2448    m_RedrawOffscreen = true;
2449    Refresh(false);
2450}
[dfe4454c]2451
[6009a17]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)
[2173dbd]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;
[6009a17]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);
[2173dbd]2483    }
2484    const wxRect R(l, d, r - l, u - d);
2485    Refresh(false, &R);
2486}
2487
[fd6e0d5]2488void GfxCore::SetHere()
2489{
[6009a17]2490    if (m_here.x == DBL_MAX) return;
[2173dbd]2491    Point old = m_here;
[fd6e0d5]2492    m_here.x = DBL_MAX;
[6009a17]2493    RefreshLine(old, m_there, m_here);
[fd6e0d5]2494}
2495
2496void GfxCore::SetHere(Double x, Double y, Double z)
2497{
[2173dbd]2498    Point old = m_here;
[fd6e0d5]2499    m_here.x = x;
2500    m_here.y = y;
2501    m_here.z = z;
[6009a17]2502    RefreshLine(old, m_there, m_here);
[fd6e0d5]2503}
2504
2505void GfxCore::SetThere()
2506{
[6009a17]2507    if (m_there.x == DBL_MAX) return;
[2173dbd]2508    Point old = m_there;
[fd6e0d5]2509    m_there.x = DBL_MAX;
[6009a17]2510    RefreshLine(m_here, old, m_there);
[fd6e0d5]2511}
2512
2513void GfxCore::SetThere(Double x, Double y, Double z)
2514{
[2173dbd]2515    Point old = m_there;
[fd6e0d5]2516    m_there.x = x;
2517    m_there.y = y;
2518    m_there.z = z;
[6009a17]2519    RefreshLine(m_here, old, m_there);
[fd6e0d5]2520}
[1fd2edb]2521
[6d109e5]2522void GfxCore::OnCancelDistLine()
[1fd2edb]2523{
2524    m_Parent->ClearTreeSelection();
2525}
2526
2527void GfxCore::OnCancelDistLineUpdate(wxUpdateUIEvent& cmd)
2528{
2529    cmd.Enable(m_there.x != DBL_MAX);
2530}
2531
[dfe4454c]2532void GfxCore::CreateHitTestGrid()
2533{
2534    // Clear hit-test grid.
2535    for (int i = 0; i < HITTEST_SIZE * HITTEST_SIZE; i++) {
[421b7d2]2536        m_PointGrid[i].clear();
[dfe4454c]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) {
[fa42426]2543        LabelInfo *label = *pos++;
2544
2545        if (!((m_Surface && label->IsSurface()) ||
[7878072]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)
[fa42426]2550            continue;
2551        }
[dfe4454c]2552
[421b7d2]2553        // Calculate screen coordinates.
[6caa5a9]2554        int cx = (int)GridXToScreen(label->GetX(), label->GetY(), label->GetZ());
[1d93721]2555        if (cx < 0 || cx >= m_XSize) continue;
[6caa5a9]2556        int cy = (int)GridYToScreen(label->GetX(), label->GetY(), label->GetZ());
[1d93721]2557        if (cy < 0 || cy >= m_YSize) continue;
[dfe4454c]2558
[fa42426]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);
[dfe4454c]2564    }
2565
2566    m_HitTestGridValid = true;
2567}
[8000d8f]2568
[4b1fc48]2569void GfxCore::OnKeyPress(wxKeyEvent &e)
2570{
[bd7a61b]2571    if (!m_PlotData) {
2572        e.Skip();
2573        return;
2574    }
2575
[7757a4ed]2576    m_RedrawOffscreen = Animate();
2577
[4b1fc48]2578    switch (e.m_keyCode) {
[026ae3c]2579        case '/': case '?':
[bd7a61b]2580            if (m_TiltAngle > -M_PI_2 && m_Lock == lock_NONE)
2581                OnLowerViewpoint(e.m_shiftDown);
[4b1fc48]2582            break;
[026ae3c]2583        case '\'': case '@': case '"': // both shifted forms - US and UK kbd
[bd7a61b]2584            if (m_TiltAngle < M_PI_2 && m_Lock == lock_NONE)
2585                OnHigherViewpoint(e.m_shiftDown);
[4b1fc48]2586            break;
2587        case 'C': case 'c':
[bd7a61b]2588            if (m_RotationOK && !m_Rotating)
2589                OnStepOnceAnticlockwise(e.m_shiftDown);
[4b1fc48]2590            break;
2591        case 'V': case 'v':
[bd7a61b]2592            if (m_RotationOK && !m_Rotating)
2593                OnStepOnceClockwise(e.m_shiftDown);
[4b1fc48]2594            break;
2595        case ']': case '}':
[bd7a61b]2596            if (m_Lock != lock_POINT)
2597                OnZoomIn(e.m_shiftDown);
[4b1fc48]2598            break;
2599        case '[': case '{':
[bd7a61b]2600            if (m_Lock != lock_POINT)
2601                OnZoomOut(e.m_shiftDown);
[4b1fc48]2602            break;
[026ae3c]2603        case 'N': case 'n':
[bd7a61b]2604            if (!(m_Lock & lock_X))
2605                OnMoveNorth();
[026ae3c]2606            break;
2607        case 'S': case 's':
[bd7a61b]2608            if (!(m_Lock & lock_X))
2609                OnMoveSouth();
[026ae3c]2610            break;
2611        case 'E': case 'e':
[bd7a61b]2612            if (!(m_Lock & lock_Y))
2613                OnMoveEast();
[026ae3c]2614            break;
2615        case 'W': case 'w':
[bd7a61b]2616            if (!(m_Lock & lock_Y))
2617                OnMoveWest();
[026ae3c]2618            break;
2619        case 'Z': case 'z':
[bd7a61b]2620            if (m_RotationOK)
2621                OnSpeedUp(e.m_shiftDown);
[026ae3c]2622            break;
2623        case 'X': case 'x':
[bd7a61b]2624            if (m_RotationOK)
2625                OnSlowDown(e.m_shiftDown);
[026ae3c]2626            break;
2627        case 'R': case 'r':
[bd7a61b]2628            if (m_RotationOK)
2629                OnReverseDirectionOfRotation();
[026ae3c]2630            break;
2631        case 'P': case 'p':
[cc9150c]2632            if (m_Lock == lock_NONE && m_TiltAngle != M_PI_2)
[bd7a61b]2633                OnPlan();
[026ae3c]2634            break;
2635        case 'L': case 'l':
[cc9150c]2636            if (m_Lock == lock_NONE && m_TiltAngle != 0.0)
[bd7a61b]2637                OnElevation();
[026ae3c]2638            break;
2639        case 'O': case 'o':
2640            OnDisplayOverlappingNames();
2641            break;
[4b1fc48]2642        case WXK_DELETE:
2643            OnDefaults();
2644            break;
2645        case WXK_RETURN:
[bd7a61b]2646            if (m_RotationOK && !m_Rotating)
2647                OnStartRotation();
[4b1fc48]2648            break;
2649        case WXK_SPACE:
[bd7a61b]2650            if (m_Rotating)
2651                OnStopRotation();
[421b7d2]2652            break;
[4b1fc48]2653        case WXK_LEFT:
[bd7a61b]2654            if (e.m_controlDown) {
2655                if (m_RotationOK && !m_Rotating)
2656                    OnStepOnceAnticlockwise(e.m_shiftDown);
2657            } else {
[ba2c5b1]2658                OnShiftDisplayLeft(e.m_shiftDown);
[bd7a61b]2659            }
[4b1fc48]2660            break;
2661        case WXK_RIGHT:
[bd7a61b]2662            if (e.m_controlDown) {
2663                if (m_RotationOK && !m_Rotating)
2664                    OnStepOnceClockwise(e.m_shiftDown);
2665            } else {
[ba2c5b1]2666                OnShiftDisplayRight(e.m_shiftDown);
[bd7a61b]2667            }
[4b1fc48]2668            break;
2669        case WXK_UP:
[bd7a61b]2670            if (e.m_controlDown) {
2671                if (m_TiltAngle < M_PI_2 && m_Lock == lock_NONE)
2672                    OnHigherViewpoint(e.m_shiftDown);
2673            } else {
[ba2c5b1]2674                OnShiftDisplayUp(e.m_shiftDown);
[bd7a61b]2675            }
[4b1fc48]2676            break;
2677        case WXK_DOWN:
[bd7a61b]2678            if (e.m_controlDown) {
2679                if (m_TiltAngle > -M_PI_2 && m_Lock == lock_NONE)
2680                    OnLowerViewpoint(e.m_shiftDown);
2681            } else {
[ba2c5b1]2682                OnShiftDisplayDown(e.m_shiftDown);
[bd7a61b]2683            }
[4b1fc48]2684            break;
[ac0a709]2685        case WXK_ESCAPE:
[bd7a61b]2686            if (m_there.x != DBL_MAX)
2687                OnCancelDistLine();
[421b7d2]2688            break;
[4b1fc48]2689        default:
2690            e.Skip();
2691    }
[da4ddfe]2692
[7757a4ed]2693    // OnPaint clears m_RedrawOffscreen so it'll only still be set if we need
2694    // to redraw
2695    if (m_RedrawOffscreen) ForceRefresh();
[4b1fc48]2696}
[09d50cc]2697
2698void
2699GfxCore::OnPrint(const wxString &filename, const wxString &title,
2700                 const wxString &datestamp)
2701{
[61448c85]2702    svxPrintDlg * p;
2703    p = new svxPrintDlg(m_Parent, filename, title, datestamp,
[246426e]2704                        deg(m_PanAngle), deg(m_TiltAngle),
[61448c85]2705                        m_Names, m_Crosses, m_Legs, m_Surface);
2706    p->Show(TRUE);
[09d50cc]2707}
[d8a725c]2708
2709bool
[301bbc1]2710GfxCore::OnExport(const wxString &filename, const wxString &title)
[d8a725c]2711{
[301bbc1]2712    return Export(filename, title, m_Parent,
[d8a725c]2713           m_PanAngle, m_TiltAngle, m_Names, m_Crosses, m_Legs, m_Surface);
2714}
Note: See TracBrowser for help on using the repository browser.