<commit_before>#include <QtGui>
#include <QtOpenGL>
#include <math.h>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/foreach.hpp>
#include <values.h>  // DBL_MAX
#include <GL/glut.h>

#include "libwatcher/watcherGraph.h"

#include "manetglview.h"
#include "watcherScrollingGraphControl.h"
#include "singletonConfig.h"
#include "backgroundImage.h"

#include "libwatcher/seekWatcherMessage.h"  // for epoch, eof

INIT_LOGGER(manetGLView, "manetGLView");

using namespace watcher;
using namespace watcher::event;
using namespace std;
using namespace libconfig;
using namespace boost;
using namespace boost::graph;
using namespace boost::date_time;
using namespace boost::posix_time;

/*
 * Get the world coordinate (x,y,z) for the projected coordinates (x, y)
 * at the world coordinate "z" using the given transformation matrices.
 */
int manetGLView::xyAtZForModelProjViewXY(
        XYWorldZToWorldXWorldY *xyz,
        size_t xyz_count,
        GLdouble modelmatrix[16], 
        GLdouble projmatrix[16], 
        GLint viewport[4])
{
    int ret;
    GLdouble modelmatrixinv[16];
    // get camera position in world coordinates
    invert4x4(modelmatrixinv, modelmatrix);
    for(;;)
    {
        if(xyz_count)
        {
            GLdouble wx, wy, wz;
            --xyz_count;
            if(gluUnProject(
                        xyz[xyz_count].x, 
                        xyz[xyz_count].y,
                        0,
                        modelmatrix, 
                        projmatrix, 
                        viewport, 
                        &wx, &wy, &wz) == GL_TRUE)
            {
                if(modelmatrixinv[15] != 0.0)
                {
                    // camera position is modelmatrixinv*[0 0 0 1]
                    GLdouble cx = modelmatrixinv[12]/modelmatrixinv[15];
                    GLdouble cy = modelmatrixinv[13]/modelmatrixinv[15];
                    GLdouble cz = modelmatrixinv[14]/modelmatrixinv[15];
                    // get (x, y) at the given z plane
                    GLdouble ratio = (cz)/(cz - wz);
                    xyz[xyz_count].worldX_ret = (cx - wx)*ratio;
                    xyz[xyz_count].worldY_ret = (cy - wy)*ratio;
                }
                else
                {
                    // orthographic projection, camera essentially at infinity.
                    xyz[xyz_count].worldX_ret = wx;
                    xyz[xyz_count].worldY_ret = wy;
                }
            }
            else
            {
                ret = EINVAL;
                break;
            }
        }
        else
        {
            ret = 0;
            break;
        }
    }
    return ret;
} // xyAtZForModelProjViewXY

void manetGLView::invert4x4(GLdouble dst[16], GLdouble const src[16])
{
    // use Cramer's Method
    // From Intel document AP-928 "Streaming SIMD Extensions - Inverse
    // of 4x4 Matrix",
    // <http://www.intel.com/design/pentiumiii/sml/24504301.pdf>
    GLdouble tmp[16];
    GLdouble det;
    size_t i;

    // pairs for first 8 elements 
    // From here on, we use the transpose of
    GLdouble pair0 = src[10]*src[15];
    GLdouble pair1 = src[14]*src[11];
    GLdouble pair2 = src[6]*src[15];
    GLdouble pair3 = src[14]*src[7];
    GLdouble pair4 = src[6]*src[11];
    GLdouble pair5 = src[10]*src[7];
    GLdouble pair6 = src[2]*src[15];
    GLdouble pair7 = src[14]*src[3];
    GLdouble pair8 = src[2]*src[11];
    GLdouble pair9 = src[10]*src[3];
    GLdouble pair10 = src[2]*src[7];
    GLdouble pair11 = src[6]*src[3];
    // pairs for second 8 elements
    GLdouble pair12 = src[8]*src[13];
    GLdouble pair13 = src[12]*src[9];
    GLdouble pair14 = src[4]*src[13];
    GLdouble pair15 = src[12]*src[5];
    GLdouble pair16 = src[4]*src[9];
    GLdouble pair17 = src[8]*src[5];
    GLdouble pair18 = src[0]*src[13];
    GLdouble pair19 = src[12]*src[1];
    GLdouble pair20 = src[0]*src[9];
    GLdouble pair21 = src[8]*src[1];
    GLdouble pair22 = src[0]*src[5];
    GLdouble pair23 = src[4]*src[1];

    // first 8 elements
    tmp[0] = (pair0*src[5]) + (pair3*src[9]) + (pair4*src[13]);
    tmp[0] -= (pair1*src[5]) + (pair2*src[9]) + (pair5*src[13]);
    tmp[1] = (pair1*src[1]) + (pair6*src[9]) + (pair9*src[13]);
    tmp[1] -= (pair0*src[1]) + (pair7*src[9]) + (pair8*src[13]);
    tmp[2] = (pair2*src[1]) + (pair7*src[5]) + (pair10*src[13]);
    tmp[2] -= (pair3*src[1]) + (pair6*src[5]) + (pair11*src[13]);
    tmp[3] = (pair5*src[1]) + (pair8*src[5]) + (pair11*src[9]);
    tmp[3] -= (pair4*src[1]) + (pair9*src[5]) + (pair10*src[9]);
    tmp[4] = (pair1*src[4]) + (pair2*src[8]) + (pair5*src[12]);
    tmp[4] -= (pair0*src[4]) + (pair3*src[8]) + (pair4*src[12]);
    tmp[5] = (pair0*src[0]) + (pair7*src[8]) + (pair8*src[12]);
    tmp[5] -= (pair1*src[0]) + (pair6*src[8]) + (pair9*src[12]);
    tmp[6] = (pair3*src[0]) + (pair6*src[4]) + (pair11*src[12]);
    tmp[6] -= (pair2*src[0]) + (pair7*src[4]) + (pair10*src[12]);
    tmp[7] = (pair4*src[0]) + (pair9*src[4]) + (pair10*src[8]);
    tmp[7] -= (pair5*src[0]) + (pair8*src[4]) + (pair11*src[8]);
    // second 8 elements
    tmp[8] = (pair12*src[7]) + (pair15*src[11]) + (pair16*src[15]);
    tmp[8] -= (pair13*src[7]) + (pair14*src[11]) + (pair17*src[15]);
    tmp[9] = (pair13*src[3]) + (pair18*src[11]) + (pair21*src[15]);
    tmp[9] -= (pair12*src[3]) + (pair19*src[11]) + (pair20*src[15]);
    tmp[10] = (pair14*src[3]) + (pair19*src[7]) + (pair22*src[15]);
    tmp[10] -= (pair15*src[3]) + (pair18*src[7]) + (pair23*src[15]);
    tmp[11] = (pair17*src[3]) + (pair20*src[7]) + (pair23*src[11]);
    tmp[11] -= (pair16*src[3]) + (pair21*src[7]) + (pair22*src[11]);
    tmp[12] = (pair14*src[10]) + (pair17*src[14]) + (pair13*src[6]);
    tmp[12] -= (pair16*src[14]) + (pair12*src[6]) + (pair15*src[10]);
    tmp[13] = (pair20*src[14]) + (pair12*src[2]) + (pair19*src[10]);
    tmp[13] -= (pair18*src[10]) + (pair21*src[14]) + (pair13*src[2]);
    tmp[14] = (pair18*src[6]) + (pair23*src[14]) + (pair15*src[2]);
    tmp[14] -= (pair22*src[14]) + (pair14*src[2]) + (pair19*src[6]);
    tmp[15] = (pair22*src[10]) + (pair16*src[2]) + (pair21*src[6]);
    tmp[15] -= (pair20*src[6]) + (pair23*src[10]) + (pair17*src[2]);

    // determinent
    det = (src[0]*tmp[0]) + (src[4]*tmp[1]) + (src[8]*tmp[2]) +
        (src[12]*tmp[3]);
    // adjust inverse
    for(i = 0;i < 16; i++) 
    {
        tmp[i] /= det;
    }
#if 0
    // check
    {
#  define MULV(i,j) ((src[((i)*4)]*tmp[(j)]) + (src[((i)*4)+1]*tmp[(j)+4]) + \
        (src[((i)*4)+2]*tmp[(j)+8]) + (src[((i)*4)+3]*tmp[(j)+12]))
        GLdouble foo[16] =
        {
            MULV(0,0), MULV(0,1), MULV(0,2), MULV(0,3), 
            MULV(1,0), MULV(1,1), MULV(1,2), MULV(1,3), 
            MULV(2,0), MULV(2,1), MULV(2,2), MULV(2,3), 
            MULV(3,0), MULV(3,1), MULV(3,2), MULV(3,3)
        }; 
        fprintf(stderr,
                "%s: --------------------------------------------\n",
                __func__);
        fprintf(stderr, "%s: %6.2lf %6.2lf %6.2lf %6.2lf     "
                "%6.2lf %6.2lf %6.2lf %6.2lf     "
                "%6.2lf %6.2lf %6.2lf %6.2lf\n", 
                __func__, src[0], src[4], src[8], src[12],
                tmp[0], tmp[4], tmp[8], tmp[12],
                foo[0], foo[4], foo[8], foo[12]);
        fprintf(stderr, "%s: %6.2lf %6.2lf %6.2lf %6.2lf  X  "
                "%6.2lf %6.2lf %6.2lf %6.2lf  =  "
                "%6.2lf %6.2lf %6.2lf %6.2lf\n", 
                __func__, src[1], src[5], src[9], src[13],
                tmp[1], tmp[5], tmp[9], tmp[13],
                foo[1], foo[5], foo[9], foo[13]);
        fprintf(stderr, "%s: %6.2lf %6.2lf %6.2lf %6.2lf     "
                "%6.2lf %6.2lf %6.2lf %6.2lf     "
                "%6.2lf %6.2lf %6.2lf %6.2lf\n", 
                __func__, src[2], src[6], src[10], src[14],
                tmp[2], tmp[6], tmp[10], tmp[14],
                foo[2], foo[6], foo[10], foo[14]);
        fprintf(stderr, "%s: %6.2lf %6.2lf %6.2lf %6.2lf     "
                "%6.2lf %6.2lf %6.2lf %6.2lf     "
                "%6.2lf %6.2lf %6.2lf %6.2lf\n", 
                __func__, src[3], src[7], src[11], src[15],
                tmp[3], tmp[7], tmp[11], tmp[15],
                foo[3], foo[7], foo[11], foo[15]);
        fprintf(stderr,
                "%s: --------------------------------------------\n",
                __func__);
    }
# undef MULV
#endif
    memcpy(dst, tmp, 16*sizeof(GLdouble));
    return;
} /* invert4x4 */
//
// Get a box at z that can be seen though the given viewport
//
// (assumes viewport[0] = viewport[1] = 0)
//
// returns non-zero on success.
//
int manetGLView::visibleDrawBoxAtZ(
        GLint *viewport,
        GLdouble z,
        GLdouble modelmatrix[16],
        GLdouble projmatrix[16],
        double maxRectAspectRatio,
        double *xMinRet,
        double *yMinRet,
        double *xMaxRet,
        double *yMaxRet)
{
    int ret;
    // Set up a border in the screen between the edges and the
    // drawing area for prettiness sake. The border goes away
    // for width or height less then 80. It then grows as 10% of
    // the width greater than 80 until it reaches a maximum
    // width of 50. These values were determined via rectal
    // extraction but the results seems pleasing to the eye.
    int borderX = viewport[2] < 80 ? 0 : viewport[2] < 580 ? (viewport[2] - 80) / 20 : 70;
    int borderY = viewport[3] < 80 ? 0 : viewport[3] < 580 ? (viewport[3] - 80) / 20 : 70;
    XYWorldZToWorldXWorldY xyz[4] =
    {
        // corners ll, lr, ul, ur
        { borderX, borderY, z, 0, 0 },
        { viewport[2] - borderX, borderY, z, 0, 0 },
        { viewport[2] - borderX, viewport[3] - borderY, z, 0, 0 },
        { borderX, viewport[3] - borderY, z, 0, 0 },
    };
    // get the drawing area corners with no shifting or scaling
    if(xyAtZForModelProjViewXY(xyz, sizeof(xyz) / sizeof(xyz[0]),
                modelmatrix, projmatrix, viewport) ==  0)
    {
        Quadrangle q = 
        { {
              { xyz[0].worldX_ret, xyz[0].worldY_ret },
              { xyz[1].worldX_ret, xyz[1].worldY_ret },
              { xyz[2].worldX_ret, xyz[2].worldY_ret },
              { xyz[3].worldX_ret, xyz[3].worldY_ret }
          } };
        maxRectangle(&q, maxRectAspectRatio, xMinRet, yMinRet, xMaxRet, yMaxRet);
        ret = !0;
    }
    else
    {
        ret = 0;
    }
    return ret;
} // visibleDrawBoxAtZ

void manetGLView::maxRectangle(
        Quadrangle const *q,
        double , // maxRectAspectRatio,
        double *xMinRet,
        double *yMinRet,
        double *xMaxRet,
        double *yMaxRet)
{
    size_t i;
    QuadranglePoint const *p = q->p;
    double a01 = atan((p[1].y - p[0].y)/(p[1].x - p[0].x));
    double a12 = atan((p[2].y - p[1].y)/(p[2].x - p[1].x));
    double a23 = atan((p[3].y - p[2].y)/(p[3].x - p[2].x));
    double a30 = atan((p[0].y - p[3].y)/(p[0].x - p[3].x));
    // short circuit rectangles that are aligned with the x and y axis.
    // (if all lines are within 10 degrees of horizontal or vertical,
    // the rectanglish shape is assumed to be aligned enough).
    if((a01 < (M_PI/18) && a01 > (-M_PI/18)) &&
            (a23 < (M_PI/18) && a23 > (-M_PI/18)) &&
            (a12 > ((M_PI/2) - (M_PI/18)) || a12 < ((-M_PI/2) + (M_PI/18))) &&
            (a30 > ((M_PI/2) - (M_PI/18)) || a30 < ((-M_PI/2) + (M_PI/18))))

    {
        // got an aligned rectanglish shape
        fprintf(stderr, "aligned (%g,%g,%g,%g) (%g,%g)-(%g,%g)-(%g,%g)-(%g,%g)\n",
                round(a01*180/M_PI), round(a12*180/M_PI), round(a23*180/M_PI), round(a30*180/M_PI),
                p[0].x, p[0].y, p[1].x, p[1].y, p[2].x, p[2].y, p[3].x, p[3].y);
        if(p[0].x < p[1].x)
        {
            *xMinRet = p[0].x;
            *xMaxRet = p[1].x;
        }
        else
        {
            *xMinRet = p[1].x;
            *xMaxRet = p[0].x;
        }
        if(p[0].y < p[2].y)
        {
            *yMinRet = p[0].y;
            *yMaxRet = p[2].y;
        }
        else
        {
            *yMinRet = p[2].y;
            *yMaxRet = p[0].y;
        }
    }
    else if((a12 < (M_PI/18) && a12 > (-M_PI/18)) &&
            (a30 < (M_PI/18) && a30 > (-M_PI/18)) &&
            (a01 > ((M_PI/2) - (M_PI/18)) || a01 < ((-M_PI/2) + (M_PI/18))) &&
            (a23 > ((M_PI/2) - (M_PI/18)) || a23 < ((-M_PI/2) + (M_PI/18))))
    {
        // got an aligned rectanglish shape rotated 90 degrees from the
        // one above
        fprintf(stderr, "90deg aligned (%g,%g,%g,%g) (%g,%g)-(%g,%g)-(%g,%g)-(%g,%g)\n",
                round(a01*180/M_PI), round(a12*180/M_PI), round(a23*180/M_PI), round(a30*180/M_PI),
                p[0].x, p[0].y, p[1].x, p[1].y, p[2].x, p[2].y, p[3].x, p[3].y);
        if(p[0].x < p[2].x)
        {
            *xMinRet = p[0].x;
            *xMaxRet = p[2].x;
        }
        else
        {
            *xMinRet = p[2].x;
            *xMaxRet = p[0].x;
        }
        if(p[0].y < p[1].y)
        {
            *yMinRet = p[0].y;
            *yMaxRet = p[1].y;
        }
        else
        {
            *yMinRet = p[1].y;
            *yMaxRet = p[0].y;
        }
    }
    else
    {
        // dang, got something lopsided.
#if 0
        //
        // I think there are thirty-two cases to check of which four
        // would produce valid solutions - these solutions would then
        // have to be simply compared to find the true maximum.
        //
        // Just figuring out the cases has already taken me too long so
        // I'm punting.
        //
        static size_t nextClockwise[] = { 1, 2, 3, 0 };
        static size_t nextCounterClockwise[] = { 3, 0, 1, 2 };
        size_t *next;

        // find leftmost point
        size_t leftmost_i = 0;
        for(i = 1; i < 4; ++i)
        {
            if(p[i].x < p[leftmost_i].x)
            {
                leftmost_i = i;
            }
        }
        // find direction
        next = (p[nextClockwise[leftmost_i]].y > p[nextCounterClockwise[leftmost_i]].y) ? nextClockwise : nextCounterClockwise;
#else
        // dumb but serviceable way - just make the returned rectangle
        // half size of the enclosing rectangle.
        double xMin = p[0].x;
        double yMin = p[0].y;
        double xMax = p[0].x;
        double yMax = p[0].y;
        fprintf(stderr, "oddball (%g,%g,%g,%g) (%g,%g)-(%g,%g)-(%g,%g)-(%g,%g)\n",
                round(a01*180/M_PI), round(a12*180/M_PI), round(a23*180/M_PI), round(a30*180/M_PI),
                p[0].x, p[0].y, p[1].x, p[1].y, p[2].x, p[2].y, p[3].x, p[3].y);
        for(i = 1; i < 4; ++i)
        {
            if(p[i].x < xMin)
            {
                xMin = p[i].x;
            }
            if(p[i].y < yMin)
            {
                yMin = p[i].y;
            }
            if(p[i].x > xMax)
            {
                xMax = p[i].x;
            }
            if(p[i].y > yMax)
            {
                yMax = p[i].y;
            }
        }
        *xMinRet = xMin + ((xMax - xMin)/4);
        *yMinRet = yMin + ((yMax - yMin)/4);
        *xMaxRet = xMax - ((xMax - xMin)/4);
        *yMaxRet = yMax - ((yMax - yMin)/4);
#endif
    }
    return;
} // maxRectangle

//
// Make the (xMin,yMin)-(xMax,yMax) rectangle on the xy-plane at
// z visible on the Manet screen
//
// onChangeOrAlways - ScaleAndShiftUpdateOnChange to only update scale
//                    and shift values if things have changed enough,
//                    ScaleAndShiftUpdateAlways to update even if things
//                    haven't changed at all Usually will not update
//                    scaling or shifting if nothing has changed
//
void manetGLView::scaleAndShiftToSeeOnManet(
        double xMin, 
        double yMin, 
        double xMax, 
        double yMax, 
        double z,
        ScaleAndShiftUpdate onChangeOrAlways)
{
    static int upfcnt = 0;
    static int nccnt = 0;
    static double prevXMin = DBL_MAX;
    static double prevXMax = -DBL_MAX;
    static double prevYMin = DBL_MAX;
    static double prevYMax = -DBL_MAX;
    static double prevZ = DBL_MAX;
    static GLint prevWidth;
    static GLint prevHeight;
    GLint viewport[4];
    // adjust min and max values so the change less often. Do this
    // by reducing the precision to only three digits of precision 
    // by rounding the lower values down and higher values up.
    double dx = xMax - xMin;
    double dy = yMax - yMin;
    double mul = exp10(floor(log10(dx < dy ? dx : dy)) - 2);
    xMin = mul * floor(xMin / mul);
    yMin = mul * floor(yMin / mul);
    xMax = mul * ceil(xMax / mul);
    yMax = mul * ceil(yMax / mul);
    // get the viewport because the scale and centering might change
    // based on a screen resize
    glGetIntegerv(GL_VIEWPORT, viewport);
    if(onChangeOrAlways == ScaleAndShiftUpdateAlways ||
            prevXMin != xMin ||
            prevYMin != yMin ||
            prevXMax != xMax ||
            prevYMax != yMax ||
            prevZ != z ||
            prevWidth != viewport[2] ||
            prevHeight != viewport[3])
    {
        double wXMin;
        double wYMin;
        double wXMax;
        double wYMax;
        double  nodesWidth = xMax == xMin ? 1 : xMax - xMin;
        double nodesHeight = yMax == yMin ? 1 : yMax - yMin;
        GLdouble modelmatrix[16];
        GLdouble projmatrix[16];
        // Matrices and viewport to convert from world to screen
        // coordinates. 
        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
        glLoadIdentity();
        glTranslatef(0.0, 0.0, -20.0);
        glScalef(1.0, 1.0, manetAdj.scaleZ); // getting scale x and y so start with unity
        glRotatef(manetAdj.angleX, 1.0, 0.0, 0.0);
        glRotatef(manetAdj.angleY, 0.0, 1.0, 0.0);
        glRotatef(manetAdj.angleZ, 0.0, 0.0, 1.0);
        glTranslatef(0.0, 0.0, manetAdj.shiftZ + 3); // getting shift x and y so start with zero
        glGetDoublev(GL_MODELVIEW_MATRIX, modelmatrix);
        glGetDoublev(GL_PROJECTION_MATRIX, projmatrix);
        glPopMatrix();
        if(visibleDrawBoxAtZ(viewport, z, modelmatrix, projmatrix, nodesWidth/nodesHeight, &wXMin, &wYMin, &wXMax, &wYMax)) 
        {
            // static time_t tick = 0;
            // time_t now = time(0);
            // get shift and scale
            manetAdj.shiftX = ((wXMin + wXMax) / 2) - ((xMin + xMax) / 2);
            manetAdj.shiftY = ((wYMin + wYMax) / 2) - ((yMin + yMax) / 2);
            manetAdj.scaleX = (wXMax - wXMin) / nodesWidth;
            manetAdj.scaleY = (wYMax - wYMin) / nodesHeight;
            if(manetAdj.scaleX > manetAdj.scaleY)
                manetAdj.scaleX = manetAdj.scaleY;
            else
                manetAdj.scaleY = manetAdj.scaleX;

            BackgroundImage &bgImage=BackgroundImage::getInstance(); 
            if (bgImage.centerImage())
            {
                GLfloat x,y,z,w,h;
                bgImage.getDrawingCoords(x,w,y,h,z);
                bgImage.setDrawingCoords(manetAdj.shiftX, w, manetAdj.shiftY, h, z); 
                bgImage.centerImage(false); 
            }
        }
        prevXMin = xMin;
        prevYMin = yMin;
        prevXMax = xMax;
        prevYMax = yMax;
        prevZ = z;
        prevWidth = viewport[2];
        prevHeight = viewport[3];
    }
    else
    {
        ++nccnt;
    }
    {
        static time_t tick = 0;
        time_t now = time(0);
        if(now > tick)
        {
            if(nccnt)
                nccnt = 0;
            if(upfcnt)
                upfcnt = 0;
            tick = now + 5;
        }
    }
} // scaleAndShiftToSee

//
// Scale and shift the Manet to the center of the Manet screen
//
// onChangeOrAlways - ScaleAndShiftUpdateOnChange to only update scale
//                    and shift values if things have changed enough,
//                    ScaleAndShiftUpdateAlways to update even if things
//                    haven't changed at all Usually will not update
//                    scaling or shifting if nothing has changed
//
void manetGLView::scaleAndShiftToCenter(ScaleAndShiftUpdate onChangeOrAlways)
{
    double xMin = DBL_MAX;
    double xMax = -DBL_MAX;
    double yMin = DBL_MAX;
    double yMax = -DBL_MAX;
    double zMin = DBL_MAX;
    bool includeAntenna = isActive(ANTENNARADIUS_LAYER); 
    // bool includeHierarchy = isActive(HIERARCHY_LAYER); 

    // find drawing extents
    WatcherGraph::vertexIterator vi, vend;
    for(tie(vi, vend)=vertices(wGraph.theGraph); vi!=vend; ++vi)
    {
        WatcherGraphNode &n=wGraph.theGraph[*vi]; 
        GLdouble x, y, z; 
        gps2openGLPixels(n.gpsData->dataFormat, n.gpsData->x, n.gpsData->y, n.gpsData->z, x, y, z); 

        double r = 0;
        if(includeAntenna)
            r = antennaRadius; 

        // if(includeHierarchy)
        // {
        //     double hr = HIERARCHY_RADIUS(m->nlist[i].level);
        //     if(r < hr)
        //     {
        //         r = hr;
        //     }
        // }
        {
            double nodeXMin = x - r;
            double nodeXMax = x + r;
            double nodeYMin = y - r;
            double nodeYMax = y + r;
            if(nodeXMin < xMin) xMin = nodeXMin;
            if(nodeXMax > xMax) xMax = nodeXMax;
            if(nodeYMin < yMin) yMin = nodeYMin;
            if(nodeYMax > yMax) yMax = nodeYMax;
        }
        if(z < zMin)
            zMin = z;
    }
    scaleAndShiftToSeeOnManet(xMin, yMin, xMax, yMax, zMin, onChangeOrAlways);
} // scaleAndShiftToCenter

void manetGLView::getShiftAmount(GLdouble &x_ret, GLdouble &y_ret)
{
    GLdouble z;
    GLdouble modelmatrix[16];
    GLdouble projmatrix[16];
    GLint viewport[4];
    glPushMatrix();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, 0.0, -20.0);
    glScalef(manetAdj.scaleX, manetAdj.scaleY, manetAdj.scaleZ);
    glRotatef(manetAdj.angleX, 1.0, 0.0, 0.0);
    glRotatef(manetAdj.angleY, 0.0, 1.0, 0.0);
    glRotatef(manetAdj.angleZ, 0.0, 0.0, 1.0);
    glTranslatef(manetAdj.shiftX, manetAdj.shiftY, manetAdj.shiftZ + 3);
    glGetDoublev(GL_MODELVIEW_MATRIX, modelmatrix);
    glGetDoublev(GL_PROJECTION_MATRIX, projmatrix);
    glGetIntegerv(GL_VIEWPORT, viewport);
    glPopMatrix();

    // if(globalManet)
    // {
    //     for(i = 0; ; ++i)
    //     {
    //         if(i == globalManet->numnodes)
    //         {
    //             z = -20;
    //             break;
    //         }
    //         if(globalGpsValidFlag[i])
    //         {
    //             z = globalManet->nlist[i].z;
    //             break;
    //         }
    //     }
    // }
    // else
    // {
    //     z = -20;
    // }
    z=-20;
    {
        int xmid = viewport[2] / 2;
        int ymid = viewport[3] / 2;
        int dx = viewport[2] / 20; // shift by 1/20th the screen
        int dy = viewport[3] / 20; // shift by 1/20th the screen
        XYWorldZToWorldXWorldY xyz[2] =
        {
            { xmid, ymid, z, 0, 0 },
            { xmid + dx, ymid - dy, z, 0, 0 },
        };
        if(xyAtZForModelProjViewXY( xyz, sizeof(xyz) / sizeof(xyz[0]), modelmatrix, projmatrix, viewport) ==  0)
        {
            x_ret = xyz[0].worldX_ret - xyz[1].worldX_ret;
            y_ret = xyz[1].worldY_ret - xyz[2].worldY_ret;
        }
        else
        {
            x_ret = 20.;
            y_ret = 20.;
        }
    }
    return;
}

void manetGLView::shiftBackgroundCenterLeft(double dx)
{
    GLfloat x, y, w, h, z;
    BackgroundImage &bg=BackgroundImage::getInstance();
    bg.getDrawingCoords(x, w, y, h, z);
    bg.setDrawingCoords(x+dx, w, y, h, z);
}

void manetGLView::shiftBackgroundCenterUp(double dy)
{
    GLfloat x, y, w, h, z;
    BackgroundImage &bg=BackgroundImage::getInstance();
    bg.getDrawingCoords(x, w, y, h, z);
    bg.setDrawingCoords(x, w, y+dy, h, z);
}

void manetGLView::shiftCenterRight()
{
    GLdouble shift, dummy;
    getShiftAmount(shift, dummy);
    shiftCenterRight(shift);
}
void manetGLView::shiftCenterRight(double shift)
{
    manetAdj.shiftX -= shift;
    autoCenterNodesFlag=false;
} 

void manetGLView::shiftCenterLeft()
{
    GLdouble shift, dummy;
    getShiftAmount(shift, dummy);
    shiftCenterLeft(shift);
}
void manetGLView::shiftCenterLeft(double shift)
{
    manetAdj.shiftX += shift;
    autoCenterNodesFlag=false;
} 

void manetGLView::shiftCenterDown()
{
    GLdouble shift, dummy;
    getShiftAmount(shift, dummy);
    shiftCenterDown(shift);
}
void manetGLView::shiftCenterDown(double shift)
{
    manetAdj.shiftY += shift;
    autoCenterNodesFlag=false;
} 

void manetGLView::shiftCenterUp()
{
    GLdouble shift, dummy;
    getShiftAmount(shift, dummy);
    shiftCenterUp(shift);
}
void manetGLView::shiftCenterUp(double shift)
{
    manetAdj.shiftY -= shift;
    autoCenterNodesFlag=false;
} 

void manetGLView::shiftCenterIn()
{
    GLdouble shift, dummy;
    getShiftAmount(shift, dummy);
    shiftCenterIn(shift);
}
void manetGLView::shiftCenterIn(double shift)
{
    manetAdj.shiftZ -= shift;
    autoCenterNodesFlag=false;
} 

void manetGLView::shiftCenterOut()
{
    GLdouble shift, dummy;
    getShiftAmount(shift, dummy);
    shiftCenterOut(shift);
}
void manetGLView::shiftCenterOut(double shift)
{
    manetAdj.shiftZ += shift;
    autoCenterNodesFlag=false;
} 

void manetGLView::viewpointReset(void)
{
    manetAdj = manetAdjInit;
}

void manetGLView::zoomOut()
{
    manetAdj.scaleX /= 1.05;
    if (manetAdj.scaleX < 0.001) 
        manetAdj.scaleX = 0.001;
    manetAdj.scaleY = manetAdj.scaleX;
    autoCenterNodesFlag = 0;
}

void manetGLView::zoomIn()
{
    manetAdj.scaleX *= 1.05;
    manetAdj.scaleY = manetAdj.scaleX;
    autoCenterNodesFlag = 0;
}

void manetGLView::compressDistance()
{
    manetAdj.scaleZ -= 0.1;
    if (manetAdj.scaleZ < 0.02)
        manetAdj.scaleZ = 0.02;
    if(autoCenterNodesFlag)
        scaleAndShiftToCenter(ScaleAndShiftUpdateAlways);
} 

void manetGLView::expandDistance()
{
    manetAdj.scaleZ += 0.1;
} 

#define TEXT_SCALE 0.08
#define TEXT_SCALE_ZOOM_FACTOR 1.05
#define ARROW_SCALE_ZOOM_FACTOR 1.05

void manetGLView::textZoomReset(void)
{
    scaleText= 0.08;
}

void manetGLView::textZoomIn(void)
{
    scaleText*= TEXT_SCALE_ZOOM_FACTOR;
}

void manetGLView::textZoomOut(void)
{
    scaleText/= TEXT_SCALE_ZOOM_FACTOR;
}

void manetGLView::arrowZoomReset(void)
{
    scaleLine= 1.0;
}

void manetGLView::arrowZoomIn(void)
{
    scaleLine*= ARROW_SCALE_ZOOM_FACTOR;
}

void manetGLView::arrowZoomOut(void)
{
    scaleLine/= ARROW_SCALE_ZOOM_FACTOR;
}

void manetGLView::rotateX(float deg)
{
    manetAdj.angleX += deg;
    while(manetAdj.angleX >= 360.0)
        manetAdj.angleX -= 360.0;
    while(manetAdj.angleX < 0)
        manetAdj.angleX += 360.0;
    if(autoCenterNodesFlag)
        scaleAndShiftToCenter(ScaleAndShiftUpdateAlways);
} 

void manetGLView::rotateY(float deg)
{
    manetAdj.angleY += deg;
    while(manetAdj.angleY >= 360.0)
        manetAdj.angleY -= 360.0;
    while(manetAdj.angleY < 0)
        manetAdj.angleY += 360.0;
    if(autoCenterNodesFlag)
        scaleAndShiftToCenter(ScaleAndShiftUpdateAlways);
} 

void manetGLView::rotateZ(float deg)
{
    manetAdj.angleZ += deg;
    while(manetAdj.angleZ >= 360.0)
        manetAdj.angleZ -= 360.0;
    while(manetAdj.angleZ < 0)
        manetAdj.angleZ += 360.0;
    if(autoCenterNodesFlag)
        scaleAndShiftToCenter(ScaleAndShiftUpdateAlways);
} 

void manetGLView::gps2openGLPixels(const GPSMessage::DataFormat &format, const double inx, const double iny, const double inz, GLdouble &x, GLdouble &y, GLdouble &z) 
{
    TRACE_ENTER();

    // GTL - this should be done when the message is recv'd - not every time we need to 
    // compute the points - which is a whole hell of a lot of times.

    if (format==GPSMessage::UTM)
    {
        //
        // There is no UTM zone information in the UTM GPS packet, so we assume all data is in a single
        // zone. Because of this, no attempt is made to place the nodes in the correct positions on the 
        // planet surface. We just use the "lat" "long" data as pure x and y coords in a plane, offset
        // by the first coord we get. (Nodes are all centered around 0,0 where, 0,0 is defined 
        // by the first coord we receive. 
        //
        if (iny < 91 && inx > 0) 
            LOG_WARN("Received GPS data that looks like lat/long in degrees, but GPS data format mode is set to UTM in cfg file."); 

        static double utmXOffset=0.0, utmYOffset=0.0;
        static bool utmOffInit=false;
        if (utmOffInit==false)
        {
            utmOffInit=true;
            utmXOffset=inx;
            utmYOffset=iny; 

            LOG_INFO("Got first UTM coordinate. Using it for x and y offsets for all other coords. Offsets are: x=" << utmXOffset << " y=" << utmYOffset);
        }

        x=inx-utmXOffset;
        y=iny-utmYOffset;    
        z=inz;

        // LOG_DEBUG("UTM given locations: lon=" << location->lon << " lat=" << location->lat << " alt=" << location->alt);
        // LOG_DEBUG("UTM node coords: x=" << us->x << " y=" << us->y << " z=" << us->z);
    }
    else // default to lat/long/alt WGS84
    {
        if (inx > 180)
            LOG_WARN("Received GPS data that may be UTM (long>180), but GPS data format mode is set to lat/long degrees in cfg file."); 

        x=inx*gpsScale;
        y=iny*gpsScale;
        z=inz-20;

        static double xOff=0.0, yOff=0.0;
        static bool xOffInit=false;
        if (xOffInit==false)
        {
            xOffInit=true;
            xOff=inx;
            yOff=iny;

            LOG_INFO("Got first Lat/Long coordinate. Using it for x and y offsets for all other coords. Offsets are: x=" 
                    << xOff << " y=" << yOff);
        }

        x-=xOff;
        y-=yOff;

        // LOG_DEBUG("Got GPS: long:" << location->lon << " lat:" << location->lat << " alt:" << location->alt); 
        // LOG_DEBUG("translated GPS: x:" << us->x << " y:" << us->y << " z:" << us->z); 
    }
    // LOG_DEBUG("Converted GPS from " << inx << ", " << iny << ", " << inz << " to " << x << ", " << y << ", " << z); 
    TRACE_EXIT();
}

manetGLView::manetGLView(QWidget *parent) : 
    QGLWidget(parent),
    autoCenterNodesFlag(false)
{
    TRACE_ENTER();
    showPositionFlag=true;
    manetAdjInit.angleX=0.0;
    manetAdjInit.angleY=0.0;
    manetAdjInit.angleZ=0.0;
    manetAdjInit.scaleX=0.035;
    manetAdjInit.scaleY=0.035;
    manetAdjInit.scaleZ=0.03;
    manetAdjInit.shiftX=0.0;
    manetAdjInit.shiftY=0.0;
    manetAdjInit.shiftZ=0.0;
    setFocusPolicy(Qt::StrongFocus); // tab and click to focus
    TRACE_EXIT();
}

manetGLView::~manetGLView()
{
    TRACE_ENTER();
    TRACE_EXIT();
}

bool manetGLView::loadConfiguration()
{
    TRACE_ENTER();

    scaleText=0.08;
    scaleLine=1.0;
    monochromeMode = false;
    threeDView = true;
    backgroundImage = true;

    // causes goodinw control buttons to show/not show.
    emit runningGoodwin(true); 

    //
    // Check configuration for GUI settings.
    //
    Config &cfg=SingletonConfig::instance();
    SingletonConfig::lock();
    libconfig::Setting &root=cfg.getRoot();

    string prop="server";
    if (!root.lookupValue(prop, serverName))
    {
        LOG_FATAL("Please specify the server name in the cfg file");
        LOG_FATAL("I set the default to localhost, but that may not be what you want."); 
        root.add(prop, Setting::TypeString)="localhost"; 
        TRACE_EXIT_RET_BOOL(false); 
        return false;
    }

    prop="layers";
    if (!root.exists(prop))
        root.add(prop, libconfig::Setting::TypeGroup);

    bool boolVal=false;
    libconfig::Setting &layers=cfg.lookup(prop);

    struct 
    {
        const char *prop;
        GUILayer layer;
    } layerVals[] =
    {
        { "bandwidth", BANDWIDTH_LAYER },
        { "undefined", UNDEFINED_LAYER },
        { "neighbors", PHYSICAL_LAYER },
        { "hierarchy", HIERARCHY_LAYER },
        { "routing", ROUTING_LAYER },
        { "routingOneHop", ONE_HOP_ROUTING_LAYER },
        { "antennaRadius", ANTENNARADIUS_LAYER },
        { "sanityCheck", SANITY_CHECK_LAYER },
        { "anomPaths", ANOMPATHS_LAYER }, 
        { "correlation", CORROLATION_LAYER },
        { "alert", ALERT_LAYER }, 
        { "correlation3Hop", CORROLATION_3HOP_LAYER },
        { "wormholeRouting", ROUTING2_LAYER },
        { "wormholeRoutingOnehop", ROUTING2_ONE_HOP_LAYER },
        { "normPaths", NORMAL_PATHS_LAYER }
    };
    for (size_t i=0; i<sizeof(layerVals)/sizeof(layerVals[0]); i++)
    {
        boolVal=false;
        LOG_DEBUG("Looking up layer " << layerVals[i].prop); 
        if (!layers.lookupValue(layerVals[i].prop, boolVal))
            layers.add(layerVals[i].prop, libconfig::Setting::TypeBoolean)=boolVal;

        LOG_DEBUG("Adding " << (boolVal?"active":"inactive") << " layer " << layerVals[i].layer << " to list of known layers"); 
        LayerListItemPtr item(new LayerListItem);
        item->layer=layerVals[i].layer;
        item->active=boolVal;
        knownLayers.push_back(item); 
    }

    // Give the GUI the current toggle state of the display.
    // GTL: to do - make all this stuff dynamic
    emit bandwidthToggled(isActive(BANDWIDTH_LAYER)); 
    emit undefinedToggled(isActive(UNDEFINED_LAYER)); 
    emit neighborsToggled(isActive(PHYSICAL_LAYER)); 
    emit hierarchyToggled(isActive(HIERARCHY_LAYER));
    emit routingToggled(isActive(ROUTING_LAYER));
    emit routingOnehopToggled(isActive(ONE_HOP_ROUTING_LAYER));
    emit antennaRadiusToggled(isActive(ANTENNARADIUS_LAYER));
    emit sanityCheckToggled(isActive(SANITY_CHECK_LAYER));
    emit anomPathsToggled(isActive(ANOMPATHS_LAYER)); 
    emit correlationToggled(isActive(CORROLATION_LAYER)); 
    emit alertToggled(isActive(ALERT_LAYER)); 
    emit correlation3HopToggled(isActive(CORROLATION_3HOP_LAYER)); 
    emit wormholeRoutingToggled(isActive(ROUTING2_LAYER)); 
    emit wormholeRoutingOnehopToggled(isActive(ROUTING2_ONE_HOP_LAYER)); 
    emit normPathsToggled(isActive(NORMAL_PATHS_LAYER)); 

    struct 
    {
        const char *prop; 
        bool def; 
        bool *val; 
    } boolVals[] = 
    {
        { "nodes3d", true, &threeDView },
        { "monochrome", false, &monochromeMode }, 
        { "displayBackgroundImage", true, &backgroundImage }
    }; 
    for (size_t i=0; i<sizeof(boolVals)/sizeof(boolVals[0]); i++)
    {
        prop=boolVals[i].prop;
        bool boolVal=boolVals[i].def; 
        if (root.lookupValue(prop, boolVal))
            *boolVals[i].val=boolVal; 
        else
            root.add(prop, libconfig::Setting::TypeBoolean)=boolVal;
    }
    emit threeDViewToggled(threeDView);
    emit monochromeToggled(monochromeMode);
    emit backgroundImageToggled(backgroundImage);

    struct 
    {
        const char *prop; 
        float def; 
        float *val; 
    } floatVals[] = 
    {
        { "scaleText", 1.0, &scaleText }, 
        { "scaleLine", 1.0, &scaleLine }, 
        { "layerPadding", 1.0, &layerPadding }, 
        { "gpsScale", 80000.0, &gpsScale }, 
        { "antennaRadius", 200.0, &antennaRadius } 
    }; 
    for (size_t i=0; i<sizeof(floatVals)/sizeof(floatVals[0]); i++)
    {
        prop=floatVals[i].prop;
        float floatVal=floatVals[i].def; 
        if (root.lookupValue(prop, floatVal))
            *floatVals[i].val=floatVal; 
        else
            root.add(prop, libconfig::Setting::TypeFloat)=floatVal;
    }

    //
    // Load background image settings
    //
    prop="backgroundImage";
    if (!root.exists(prop))
        root.add(prop, libconfig::Setting::TypeGroup);
    libconfig::Setting &s=cfg.lookup(prop);

    prop="imageFile"; 
    string strVal;
    if (!s.lookupValue(prop, strVal) || strVal=="none")   // If we don't have the setting or the it's set to 'do not use bg image'
    {
        LOG_INFO("watcherBackground:imageFile entry not found (or it equals \"none\") in configuration file, "
                "disabling background image functionality");
        if (strVal.empty())
            s.add(prop, libconfig::Setting::TypeString)="none";
        toggleBackgroundImage(false);
        emit enableBackgroundImage(false);
    }
    else
    {
        BackgroundImage &bgImage=BackgroundImage::getInstance(); 
        char *ext=rindex(strVal.data(), '.');
        if (!ext)
        {
            LOG_ERROR("I have no idea what kind of file the background image " << strVal << " is. I only support BMP and PPM"); 
            exit(1);
        }
        else if (0==strncasecmp(ext+sizeof(char), "bmp", 3))
        {
            if (!bgImage.loadBMPFile(strVal.data()))
            {
                LOG_FATAL("Unable to load background BMP image in watcher from file: " << strVal); 
                TRACE_EXIT_RET_BOOL(false);
                return false;
            }
        }
        else if (0==strncmp("ppm", ext+sizeof(char), 3))
        {
            if (!bgImage.loadPPMFile(strVal.data()))
            {
                LOG_FATAL("Unable to load background PPM image in watcher from file: " << strVal); 
                TRACE_EXIT_RET_BOOL(false);
                return false;
            }
        }
    }
    // bg image location and size.
    prop="coordinates";
    float coordVals[5]={0.0, 0.0, 0.0, 0.0, 0.0};
    if (!s.exists(prop))
    {
        s.add(prop, libconfig::Setting::TypeArray);
        for (size_t i=0; i<sizeof(coordVals)/sizeof(coordVals[0]); i++)
            s[prop].add(libconfig::Setting::TypeFloat)=coordVals[i];
    }
    else
    {
        for (size_t i=0; i<sizeof(coordVals)/sizeof(coordVals[0]); i++)
            coordVals[i]=s[prop][i];
        BackgroundImage &bg=BackgroundImage::getInstance();
        bg.setDrawingCoords(coordVals[0], coordVals[1], coordVals[2], coordVals[3], coordVals[4]); 
    }

    //
    // Load viewpoint
    //
    prop="viewPoint";
    if (!root.exists(prop))
        root.add(prop, libconfig::Setting::TypeGroup);
    libconfig::Setting &vp=cfg.lookup(prop); 

    struct 
    {
        const char *type;
        float *data[3];
    } viewPoints[] =
    {
        { "angle", { &manetAdj.angleX, &manetAdj.angleY, &manetAdj.angleZ }},
        { "scale", { &manetAdj.scaleX, &manetAdj.scaleY, &manetAdj.scaleZ }},
        { "shift", { &manetAdj.shiftX, &manetAdj.shiftY, &manetAdj.shiftZ }}
    };
    for (size_t i=0; i<sizeof(viewPoints)/sizeof(viewPoints[0]);i++)
    {
        if (!vp.exists(viewPoints[i].type))
        {
            vp.add(viewPoints[i].type, libconfig::Setting::TypeArray);
            for (size_t j=0; j<sizeof(viewPoints[i].data)/sizeof(viewPoints[i].data[0]); j++)
                vp[viewPoints[i].type].add(libconfig::Setting::TypeFloat);
        }
        else
        {
            libconfig::Setting &s=vp[viewPoints[i].type];
            for (int j=0; j<s.getLength(); j++)
                *viewPoints[i].data[j]=s[j];
        }
    }

    LOG_INFO("Set viewpoint - angle: " << manetAdj.angleX << ", " << manetAdj.angleY << ", " << manetAdj.angleZ);
    LOG_INFO("Set viewpoint - scale: " << manetAdj.scaleX << ", " << manetAdj.scaleY << ", " << manetAdj.scaleZ);
    LOG_INFO("Set viewpoint - shift: " << manetAdj.shiftX << ", " << manetAdj.shiftY << ", " << manetAdj.shiftZ);

    // background color
    prop="backgroundColor";
    if (!root.exists(prop))
        root.add(prop, libconfig::Setting::TypeGroup);
    libconfig::Setting &bgColSet=cfg.lookup(prop);

    struct 
    {
        const char *name;
        float val;
    } bgColors[] = 
    {
        { "r", 0.0 }, 
        { "g", 0.0 }, 
        { "b", 0.0 }, 
        { "a", 255.0 }
    };
    for (size_t i=0; i<sizeof(bgColors)/sizeof(bgColors[0]);i++)
        if (!bgColSet.lookupValue(bgColors[i].name, bgColors[i].val))
            bgColSet.add(bgColors[i].name, libconfig::Setting::TypeFloat)=bgColors[i].val;

    glClearColor(bgColors[0].val, bgColors[1].val,bgColors[2].val,bgColors[3].val);

    SingletonConfig::unlock();

    // 
    // Set up timer callbacks.
    //
    QTimer *checkIOTimer = new QTimer(this);
    QObject::connect(checkIOTimer, SIGNAL(timeout()), this, SLOT(checkIO()));
    checkIOTimer->start(100);

    QTimer *watcherIdleTimer = new QTimer(this);
    QObject::connect(watcherIdleTimer, SIGNAL(timeout()), this, SLOT(watcherIdle()));
    watcherIdleTimer->start(33); // Let's shoot for 30 frames a second.

    TRACE_EXIT();
    return true;
}

QSize manetGLView::minimumSizeHint() const
{
    TRACE_ENTER();
    QSize retVal=QSize(50, 50);
    TRACE_EXIT_RET(retVal.width() << retVal.height()); 
    return retVal;
}

QSize manetGLView::sizeHint() const
{
    TRACE_ENTER();
    QSize retVal=QSize(400, 400);
    TRACE_EXIT_RET(retVal.width() << retVal.height()); 
    return retVal;
}

void manetGLView::initializeGL()
{
    TRACE_ENTER();
    // glEnable(GL_DEPTH_TEST);
    // glEnable(GL_BLEND);
    // // glEnable(GL_LINE_SMOOTH);
    // // glEnable(GL_CULL_FACE); 
    // glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    // glShadeModel(GL_SMOOTH);

    // glEnable(GL_LIGHTING);

    // // GLfloat ambLight[] = { 0.25, 0.25, 0.25, 1.0 };    // Ambient light is not full strength, but white
    // // glLightfv(GL_LIGHT1, GL_AMBIENT, ambLight);
    // // glLightfv(GL_LIGHT1, GL_SPECULAR, ambLight);

    // // GLfloat diffLight[]= { 1.0f, 1.0f, 1.0f, 1.0f };    // diffuse light is full strength
    // // glLightfv(GL_LIGHT1, GL_DIFFUSE, diffLight);

    // // GLfloat posLight[]= { 200.0f, 200.0f, 200.0f, 1.0f }; // light is over right shoulder
    // // glLightfv(GL_LIGHT1, GL_POSITION, posLight);

    // // glEnable(GL_LIGHT1);

    // enable lighting
    glEnable(GL_LIGHTING); 

    // enable objects behind one another
    glEnable(GL_DEPTH_TEST);

    // Allow colors to bleed through by alpha (orange edges when nieghor and one hop routing draw the same link).
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // Set the shading model
    glShadeModel(GL_SMOOTH); 

    // Uncomment these if you want to use glColor(c) instead of 
    // glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c);
    // glEnable(GL_COLOR_MATERIAL);
    // glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

    // Spec is LIGHT0
    GLfloat specular[]={0.5, 0.5, 0.5, 1.0};
    GLfloat posSpecLight[]= { -500.0f, 500.0f, 100.0f, 1.0f };
    glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
    glLightfv(GL_LIGHT0, GL_POSITION, posSpecLight);
    glEnable(GL_LIGHT0); 

    // Amb is LIGHT1
    GLfloat ambLight[] = { 0.25, 0.25, 0.25, 1.0 };
    glLightfv(GL_LIGHT1, GL_AMBIENT, ambLight);
    glEnable(GL_LIGHT1);

    // or use the ambient light model
    // GLfloat global_ambient[] = { 0.5f, 0.5f, 0.5f, 1.0f };
    // glLightModelfv(GL_LIGHT_MODEL_AMBIENT, global_ambient);

    // Diffuse is LIGHT2
    GLfloat diffLight[]= { 0.5, 0.5, 0.5, 1.0f };    
    glLightfv(GL_LIGHT2, GL_DIFFUSE, diffLight);
    GLfloat posDiffLight[]= { 500.0, 500.0f, 100.0f, 1.0f };
    glLightfv(GL_LIGHT2, GL_POSITION, posDiffLight); 
    glEnable(GL_LIGHT2);

    TRACE_EXIT();
}

void manetGLView::checkIO()
{
    TRACE_ENTER();

    if (!messageStream)
    {
        messageStream=MessageStream::createNewMessageStream(serverName); 
        messageStream->setStreamTimeStart(SeekMessage::eof);
        messageStream->startStream();
    }

    while(messageStream && messageStream->isStreamReadable())
    {
        MessagePtr message;
        messageStream->getNextMessage(message);
        LOG_DEBUG("Got message: " << *message);
        wGraph.updateGraph(message);
    }

    wGraph.doMaintanence(); // check expiration, etc. 

    updateGL();  // redraw

    TRACE_EXIT();
}

void manetGLView::watcherIdle()
{
    TRACE_ENTER();

    bool update=false;
    Timestamp now=(Timestamp(time(NULL)))*1000; 

    // GTL - Should this code be moved into watcherGraph?
    WatcherGraph::vertexIterator vi, vend;
    for(tie(vi, vend)=vertices(wGraph.theGraph); vi!=vend; ++vi)
    {
        WatcherGraphNode &node=wGraph.theGraph[*vi]; 

        if (node.displayInfo->spin && now > node.displayInfo->nextSpinUpdate)
        {
            node.displayInfo->spinRotation_x+=node.displayInfo->spinIncrement;
            node.displayInfo->spinRotation_y+=node.displayInfo->spinIncrement;
            node.displayInfo->spinRotation_z+=node.displayInfo->spinIncrement;
            node.displayInfo->nextSpinUpdate=now+node.displayInfo->spinTimeout;
            update=true;
        }

        // Are we flashing and do we need to invert the color?
        if (node.displayInfo->flash && now > node.displayInfo->nextFlashUpdate)  
        {
            LOG_DEBUG("Toggling flash for node " << node.nodeId); 
            node.displayInfo->isFlashed=node.displayInfo->isFlashed?true:false;
            node.displayInfo->nextFlashUpdate=now+node.displayInfo->flashInterval;
            update=true;
        }
    }

    WatcherGraph::edgeIterator ei, eend;
    for(tie(ei, eend)=edges(wGraph.theGraph); ei!=eend; ++ei)
    {
        WatcherGraphEdge &edge=wGraph.theGraph[*ei]; 
        if (edge.displayInfo->flash && now > edge.displayInfo->nextFlashUpdate)
        {
            edge.displayInfo->isFlashed=edge.displayInfo->isFlashed?true:false;
            edge.displayInfo->nextFlashUpdate=now+edge.displayInfo->flashInterval;
            update=true;
        }
    }

    if (update)
        updateGL();

    TRACE_EXIT();
}

void manetGLView::paintGL()
{
    TRACE_ENTER();

    drawManet();

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();

    // QPainter painter;
    // painter.begin(this);
    // painter.setRenderHint(QPainter::Antialiasing);

    // QString text = tr("Click and drag with the left mouse button to rotate the Qt logo.");
    // QFontMetrics metrics = QFontMetrics(font());
    // int border = qMax(4, metrics.leading());

    // QRect rect = metrics.boundingRect(0, 0, width() - 2*border, int(height()*0.125), Qt::AlignCenter | Qt::TextWordWrap, text);
    // painter.setRenderHint(QPainter::TextAntialiasing);
    // painter.setPen(Qt::white);
    // painter.fillRect(QRect(0, 0, width(), rect.height() + 2*border), QColor(0, 0, 0, 127));
    // painter.drawText((width() - rect.width())/2, border, rect.width(), rect.height(), Qt::AlignCenter | Qt::TextWordWrap, text);
    // painter.end(); 

    // glPopMatrix(); 

    TRACE_EXIT();
}

void manetGLView::drawText( GLdouble x, GLdouble y, GLdouble z, GLdouble scale, char *text, GLdouble lineWidth)
{
    int i;
    GLfloat lineheight=- glutStrokeWidth(GLUT_STROKE_ROMAN,'W') * scale;   // TOJ: need scale arg here?  

    glPushMatrix();
    glPushAttrib(GL_LINE_WIDTH);
    glLineWidth(lineWidth); 

    glTranslatef(x,y,z);
    glScaled(scale,scale,scale);
    for (i = 0; text[i]; i++)
        switch(text[i])
        {
            case '\n':
                glPopMatrix();
                glTranslatef(0.0,lineheight,0.0);
                glPushMatrix();
                glScaled(scale,scale,scale);
                break;
            default:
                glutStrokeCharacter(GLUT_STROKE_ROMAN, text[i]);
                break;
        }
    glPopAttrib(); 
    glPopMatrix();

}
void manetGLView::drawManet(void)
{
    // watcher::Skybox *sb=watcher::Skybox::getSkybox();
    // if (sb)
    //     sb->drawSkybox(manetAdj.angleX, manetAdj.angleY, manetAdj.angleZ);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, 0.0, -20.0);

    glPushMatrix();

    GLfloat black[]={0.0,0.0,0.0,1.0};
    GLfloat blue[]={0.0,0.0,1.0,0.6};

    ptime now = from_time_t(time(NULL));
    glScalef(0.02, 0.02, 0.02);
    if (monochromeMode)
        glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, black);
    else
        glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blue);

    string buf;
    if (showPositionFlag)
    {
        char buff[190];
        snprintf(buff, sizeof(buff), "Location: %3.1f, %3.1f, %3.1f scale %f  -  ",
                manetAdj.shiftX, manetAdj.shiftY, manetAdj.shiftZ, manetAdj.scaleX);
        buf+=string(buff); 
    }
    buf+=posix_time::to_simple_string(now);
    drawText(-500, -360, 0, scaleText*2.0, const_cast<char*>(buf.c_str()), 1.0);

    glPopMatrix();

    glScalef(manetAdj.scaleX, manetAdj.scaleY, manetAdj.scaleZ);
    glRotatef(manetAdj.angleX, 1.0, 0.0, 0.0);
    glRotatef(manetAdj.angleY, 0.0, 1.0, 0.0);
    glRotatef(manetAdj.angleZ, 0.0, 0.0, 1.0);
    glTranslatef(manetAdj.shiftX, manetAdj.shiftY, manetAdj.shiftZ);

    for (LayerList::iterator li=knownLayers.begin(); li!=knownLayers.end(); ++li)
    {
        if ((*li)->active)
        {
            // each layer is 'layerPadding' above the rest. 
            // layer order is currently defined by order of appearance
            // in the cfg file. 
            glPushMatrix();
            glTranslatef(0.0, 0.0, layerPadding);  
            drawLayer((*li)->layer); 
            glPopMatrix();

        }
    }

    if (backgroundImage)
        BackgroundImage::getInstance().drawImage(); 

    glPopMatrix();
    // glFlush();
}

void manetGLView::drawEdge(const WatcherGraphEdge &edge, const WatcherGraphNode &node1, const WatcherGraphNode &node2)
{
    TRACE_ENTER(); 

    GLdouble x1, y1, z1, x2, y2, z2, width;
    gps2openGLPixels(node1.gpsData->dataFormat, node1.gpsData->x, node1.gpsData->y, node1.gpsData->z, x1, y1, z1); 
    gps2openGLPixels(node2.gpsData->dataFormat, node2.gpsData->x, node2.gpsData->y, node2.gpsData->z, x2, y2, z2); 

    width=edge.displayInfo->width;
<commit_msg>Handle leayer padding correctly.<commit_after>#include <QtGui>
#include <QtOpenGL>
#include <math.h>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/foreach.hpp>
#include <values.h>  // DBL_MAX
#include <GL/glut.h>

#include "libwatcher/watcherGraph.h"

#include "manetglview.h"
#include "watcherScrollingGraphControl.h"
#include "singletonConfig.h"
#include "backgroundImage.h"

#include "libwatcher/seekWatcherMessage.h"  // for epoch, eof

INIT_LOGGER(manetGLView, "manetGLView");

using namespace watcher;
using namespace watcher::event;
using namespace std;
using namespace libconfig;
using namespace boost;
using namespace boost::graph;
using namespace boost::date_time;
using namespace boost::posix_time;

/*
 * Get the world coordinate (x,y,z) for the projected coordinates (x, y)
 * at the world coordinate "z" using the given transformation matrices.
 */
int manetGLView::xyAtZForModelProjViewXY(
        XYWorldZToWorldXWorldY *xyz,
        size_t xyz_count,
        GLdouble modelmatrix[16], 
        GLdouble projmatrix[16], 
        GLint viewport[4])
{
    int ret;
    GLdouble modelmatrixinv[16];
    // get camera position in world coordinates
    invert4x4(modelmatrixinv, modelmatrix);
    for(;;)
    {
        if(xyz_count)
        {
            GLdouble wx, wy, wz;
            --xyz_count;
            if(gluUnProject(
                        xyz[xyz_count].x, 
                        xyz[xyz_count].y,
                        0,
                        modelmatrix, 
                        projmatrix, 
                        viewport, 
                        &wx, &wy, &wz) == GL_TRUE)
            {
                if(modelmatrixinv[15] != 0.0)
                {
                    // camera position is modelmatrixinv*[0 0 0 1]
                    GLdouble cx = modelmatrixinv[12]/modelmatrixinv[15];
                    GLdouble cy = modelmatrixinv[13]/modelmatrixinv[15];
                    GLdouble cz = modelmatrixinv[14]/modelmatrixinv[15];
                    // get (x, y) at the given z plane
                    GLdouble ratio = (cz)/(cz - wz);
                    xyz[xyz_count].worldX_ret = (cx - wx)*ratio;
                    xyz[xyz_count].worldY_ret = (cy - wy)*ratio;
                }
                else
                {
                    // orthographic projection, camera essentially at infinity.
                    xyz[xyz_count].worldX_ret = wx;
                    xyz[xyz_count].worldY_ret = wy;
                }
            }
            else
            {
                ret = EINVAL;
                break;
            }
        }
        else
        {
            ret = 0;
            break;
        }
    }
    return ret;
} // xyAtZForModelProjViewXY

void manetGLView::invert4x4(GLdouble dst[16], GLdouble const src[16])
{
    // use Cramer's Method
    // From Intel document AP-928 "Streaming SIMD Extensions - Inverse
    // of 4x4 Matrix",
    // <http://www.intel.com/design/pentiumiii/sml/24504301.pdf>
    GLdouble tmp[16];
    GLdouble det;
    size_t i;

    // pairs for first 8 elements 
    // From here on, we use the transpose of
    GLdouble pair0 = src[10]*src[15];
    GLdouble pair1 = src[14]*src[11];
    GLdouble pair2 = src[6]*src[15];
    GLdouble pair3 = src[14]*src[7];
    GLdouble pair4 = src[6]*src[11];
    GLdouble pair5 = src[10]*src[7];
    GLdouble pair6 = src[2]*src[15];
    GLdouble pair7 = src[14]*src[3];
    GLdouble pair8 = src[2]*src[11];
    GLdouble pair9 = src[10]*src[3];
    GLdouble pair10 = src[2]*src[7];
    GLdouble pair11 = src[6]*src[3];
    // pairs for second 8 elements
    GLdouble pair12 = src[8]*src[13];
    GLdouble pair13 = src[12]*src[9];
    GLdouble pair14 = src[4]*src[13];
    GLdouble pair15 = src[12]*src[5];
    GLdouble pair16 = src[4]*src[9];
    GLdouble pair17 = src[8]*src[5];
    GLdouble pair18 = src[0]*src[13];
    GLdouble pair19 = src[12]*src[1];
    GLdouble pair20 = src[0]*src[9];
    GLdouble pair21 = src[8]*src[1];
    GLdouble pair22 = src[0]*src[5];
    GLdouble pair23 = src[4]*src[1];

    // first 8 elements
    tmp[0] = (pair0*src[5]) + (pair3*src[9]) + (pair4*src[13]);
    tmp[0] -= (pair1*src[5]) + (pair2*src[9]) + (pair5*src[13]);
    tmp[1] = (pair1*src[1]) + (pair6*src[9]) + (pair9*src[13]);
    tmp[1] -= (pair0*src[1]) + (pair7*src[9]) + (pair8*src[13]);
    tmp[2] = (pair2*src[1]) + (pair7*src[5]) + (pair10*src[13]);
    tmp[2] -= (pair3*src[1]) + (pair6*src[5]) + (pair11*src[13]);
    tmp[3] = (pair5*src[1]) + (pair8*src[5]) + (pair11*src[9]);
    tmp[3] -= (pair4*src[1]) + (pair9*src[5]) + (pair10*src[9]);
    tmp[4] = (pair1*src[4]) + (pair2*src[8]) + (pair5*src[12]);
    tmp[4] -= (pair0*src[4]) + (pair3*src[8]) + (pair4*src[12]);
    tmp[5] = (pair0*src[0]) + (pair7*src[8]) + (pair8*src[12]);
    tmp[5] -= (pair1*src[0]) + (pair6*src[8]) + (pair9*src[12]);
    tmp[6] = (pair3*src[0]) + (pair6*src[4]) + (pair11*src[12]);
    tmp[6] -= (pair2*src[0]) + (pair7*src[4]) + (pair10*src[12]);
    tmp[7] = (pair4*src[0]) + (pair9*src[4]) + (pair10*src[8]);
    tmp[7] -= (pair5*src[0]) + (pair8*src[4]) + (pair11*src[8]);
    // second 8 elements
    tmp[8] = (pair12*src[7]) + (pair15*src[11]) + (pair16*src[15]);
    tmp[8] -= (pair13*src[7]) + (pair14*src[11]) + (pair17*src[15]);
    tmp[9] = (pair13*src[3]) + (pair18*src[11]) + (pair21*src[15]);
    tmp[9] -= (pair12*src[3]) + (pair19*src[11]) + (pair20*src[15]);
    tmp[10] = (pair14*src[3]) + (pair19*src[7]) + (pair22*src[15]);
    tmp[10] -= (pair15*src[3]) + (pair18*src[7]) + (pair23*src[15]);
    tmp[11] = (pair17*src[3]) + (pair20*src[7]) + (pair23*src[11]);
    tmp[11] -= (pair16*src[3]) + (pair21*src[7]) + (pair22*src[11]);
    tmp[12] = (pair14*src[10]) + (pair17*src[14]) + (pair13*src[6]);
    tmp[12] -= (pair16*src[14]) + (pair12*src[6]) + (pair15*src[10]);
    tmp[13] = (pair20*src[14]) + (pair12*src[2]) + (pair19*src[10]);
    tmp[13] -= (pair18*src[10]) + (pair21*src[14]) + (pair13*src[2]);
    tmp[14] = (pair18*src[6]) + (pair23*src[14]) + (pair15*src[2]);
    tmp[14] -= (pair22*src[14]) + (pair14*src[2]) + (pair19*src[6]);
    tmp[15] = (pair22*src[10]) + (pair16*src[2]) + (pair21*src[6]);
    tmp[15] -= (pair20*src[6]) + (pair23*src[10]) + (pair17*src[2]);

    // determinent
    det = (src[0]*tmp[0]) + (src[4]*tmp[1]) + (src[8]*tmp[2]) +
        (src[12]*tmp[3]);
    // adjust inverse
    for(i = 0;i < 16; i++) 
    {
        tmp[i] /= det;
    }
#if 0
    // check
    {
#  define MULV(i,j) ((src[((i)*4)]*tmp[(j)]) + (src[((i)*4)+1]*tmp[(j)+4]) + \
        (src[((i)*4)+2]*tmp[(j)+8]) + (src[((i)*4)+3]*tmp[(j)+12]))
        GLdouble foo[16] =
        {
            MULV(0,0), MULV(0,1), MULV(0,2), MULV(0,3), 
            MULV(1,0), MULV(1,1), MULV(1,2), MULV(1,3), 
            MULV(2,0), MULV(2,1), MULV(2,2), MULV(2,3), 
            MULV(3,0), MULV(3,1), MULV(3,2), MULV(3,3)
        }; 
        fprintf(stderr,
                "%s: --------------------------------------------\n",
                __func__);
        fprintf(stderr, "%s: %6.2lf %6.2lf %6.2lf %6.2lf     "
                "%6.2lf %6.2lf %6.2lf %6.2lf     "
                "%6.2lf %6.2lf %6.2lf %6.2lf\n", 
                __func__, src[0], src[4], src[8], src[12],
                tmp[0], tmp[4], tmp[8], tmp[12],
                foo[0], foo[4], foo[8], foo[12]);
        fprintf(stderr, "%s: %6.2lf %6.2lf %6.2lf %6.2lf  X  "
                "%6.2lf %6.2lf %6.2lf %6.2lf  =  "
                "%6.2lf %6.2lf %6.2lf %6.2lf\n", 
                __func__, src[1], src[5], src[9], src[13],
                tmp[1], tmp[5], tmp[9], tmp[13],
                foo[1], foo[5], foo[9], foo[13]);
        fprintf(stderr, "%s: %6.2lf %6.2lf %6.2lf %6.2lf     "
                "%6.2lf %6.2lf %6.2lf %6.2lf     "
                "%6.2lf %6.2lf %6.2lf %6.2lf\n", 
                __func__, src[2], src[6], src[10], src[14],
                tmp[2], tmp[6], tmp[10], tmp[14],
                foo[2], foo[6], foo[10], foo[14]);
        fprintf(stderr, "%s: %6.2lf %6.2lf %6.2lf %6.2lf     "
                "%6.2lf %6.2lf %6.2lf %6.2lf     "
                "%6.2lf %6.2lf %6.2lf %6.2lf\n", 
                __func__, src[3], src[7], src[11], src[15],
                tmp[3], tmp[7], tmp[11], tmp[15],
                foo[3], foo[7], foo[11], foo[15]);
        fprintf(stderr,
                "%s: --------------------------------------------\n",
                __func__);
    }
# undef MULV
#endif
    memcpy(dst, tmp, 16*sizeof(GLdouble));
    return;
} /* invert4x4 */
//
// Get a box at z that can be seen though the given viewport
//
// (assumes viewport[0] = viewport[1] = 0)
//
// returns non-zero on success.
//
int manetGLView::visibleDrawBoxAtZ(
        GLint *viewport,
        GLdouble z,
        GLdouble modelmatrix[16],
        GLdouble projmatrix[16],
        double maxRectAspectRatio,
        double *xMinRet,
        double *yMinRet,
        double *xMaxRet,
        double *yMaxRet)
{
    int ret;
    // Set up a border in the screen between the edges and the
    // drawing area for prettiness sake. The border goes away
    // for width or height less then 80. It then grows as 10% of
    // the width greater than 80 until it reaches a maximum
    // width of 50. These values were determined via rectal
    // extraction but the results seems pleasing to the eye.
    int borderX = viewport[2] < 80 ? 0 : viewport[2] < 580 ? (viewport[2] - 80) / 20 : 70;
    int borderY = viewport[3] < 80 ? 0 : viewport[3] < 580 ? (viewport[3] - 80) / 20 : 70;
    XYWorldZToWorldXWorldY xyz[4] =
    {
        // corners ll, lr, ul, ur
        { borderX, borderY, z, 0, 0 },
        { viewport[2] - borderX, borderY, z, 0, 0 },
        { viewport[2] - borderX, viewport[3] - borderY, z, 0, 0 },
        { borderX, viewport[3] - borderY, z, 0, 0 },
    };
    // get the drawing area corners with no shifting or scaling
    if(xyAtZForModelProjViewXY(xyz, sizeof(xyz) / sizeof(xyz[0]),
                modelmatrix, projmatrix, viewport) ==  0)
    {
        Quadrangle q = 
        { {
              { xyz[0].worldX_ret, xyz[0].worldY_ret },
              { xyz[1].worldX_ret, xyz[1].worldY_ret },
              { xyz[2].worldX_ret, xyz[2].worldY_ret },
              { xyz[3].worldX_ret, xyz[3].worldY_ret }
          } };
        maxRectangle(&q, maxRectAspectRatio, xMinRet, yMinRet, xMaxRet, yMaxRet);
        ret = !0;
    }
    else
    {
        ret = 0;
    }
    return ret;
} // visibleDrawBoxAtZ

void manetGLView::maxRectangle(
        Quadrangle const *q,
        double , // maxRectAspectRatio,
        double *xMinRet,
        double *yMinRet,
        double *xMaxRet,
        double *yMaxRet)
{
    size_t i;
    QuadranglePoint const *p = q->p;
    double a01 = atan((p[1].y - p[0].y)/(p[1].x - p[0].x));
    double a12 = atan((p[2].y - p[1].y)/(p[2].x - p[1].x));
    double a23 = atan((p[3].y - p[2].y)/(p[3].x - p[2].x));
    double a30 = atan((p[0].y - p[3].y)/(p[0].x - p[3].x));
    // short circuit rectangles that are aligned with the x and y axis.
    // (if all lines are within 10 degrees of horizontal or vertical,
    // the rectanglish shape is assumed to be aligned enough).
    if((a01 < (M_PI/18) && a01 > (-M_PI/18)) &&
            (a23 < (M_PI/18) && a23 > (-M_PI/18)) &&
            (a12 > ((M_PI/2) - (M_PI/18)) || a12 < ((-M_PI/2) + (M_PI/18))) &&
            (a30 > ((M_PI/2) - (M_PI/18)) || a30 < ((-M_PI/2) + (M_PI/18))))

    {
        // got an aligned rectanglish shape
        fprintf(stderr, "aligned (%g,%g,%g,%g) (%g,%g)-(%g,%g)-(%g,%g)-(%g,%g)\n",
                round(a01*180/M_PI), round(a12*180/M_PI), round(a23*180/M_PI), round(a30*180/M_PI),
                p[0].x, p[0].y, p[1].x, p[1].y, p[2].x, p[2].y, p[3].x, p[3].y);
        if(p[0].x < p[1].x)
        {
            *xMinRet = p[0].x;
            *xMaxRet = p[1].x;
        }
        else
        {
            *xMinRet = p[1].x;
            *xMaxRet = p[0].x;
        }
        if(p[0].y < p[2].y)
        {
            *yMinRet = p[0].y;
            *yMaxRet = p[2].y;
        }
        else
        {
            *yMinRet = p[2].y;
            *yMaxRet = p[0].y;
        }
    }
    else if((a12 < (M_PI/18) && a12 > (-M_PI/18)) &&
            (a30 < (M_PI/18) && a30 > (-M_PI/18)) &&
            (a01 > ((M_PI/2) - (M_PI/18)) || a01 < ((-M_PI/2) + (M_PI/18))) &&
            (a23 > ((M_PI/2) - (M_PI/18)) || a23 < ((-M_PI/2) + (M_PI/18))))
    {
        // got an aligned rectanglish shape rotated 90 degrees from the
        // one above
        fprintf(stderr, "90deg aligned (%g,%g,%g,%g) (%g,%g)-(%g,%g)-(%g,%g)-(%g,%g)\n",
                round(a01*180/M_PI), round(a12*180/M_PI), round(a23*180/M_PI), round(a30*180/M_PI),
                p[0].x, p[0].y, p[1].x, p[1].y, p[2].x, p[2].y, p[3].x, p[3].y);
        if(p[0].x < p[2].x)
        {
            *xMinRet = p[0].x;
            *xMaxRet = p[2].x;
        }
        else
        {
            *xMinRet = p[2].x;
            *xMaxRet = p[0].x;
        }
        if(p[0].y < p[1].y)
        {
            *yMinRet = p[0].y;
            *yMaxRet = p[1].y;
        }
        else
        {
            *yMinRet = p[1].y;
            *yMaxRet = p[0].y;
        }
    }
    else
    {
        // dang, got something lopsided.
#if 0
        //
        // I think there are thirty-two cases to check of which four
        // would produce valid solutions - these solutions would then
        // have to be simply compared to find the true maximum.
        //
        // Just figuring out the cases has already taken me too long so
        // I'm punting.
        //
        static size_t nextClockwise[] = { 1, 2, 3, 0 };
        static size_t nextCounterClockwise[] = { 3, 0, 1, 2 };
        size_t *next;

        // find leftmost point
        size_t leftmost_i = 0;
        for(i = 1; i < 4; ++i)
        {
            if(p[i].x < p[leftmost_i].x)
            {
                leftmost_i = i;
            }
        }
        // find direction
        next = (p[nextClockwise[leftmost_i]].y > p[nextCounterClockwise[leftmost_i]].y) ? nextClockwise : nextCounterClockwise;
#else
        // dumb but serviceable way - just make the returned rectangle
        // half size of the enclosing rectangle.
        double xMin = p[0].x;
        double yMin = p[0].y;
        double xMax = p[0].x;
        double yMax = p[0].y;
        fprintf(stderr, "oddball (%g,%g,%g,%g) (%g,%g)-(%g,%g)-(%g,%g)-(%g,%g)\n",
                round(a01*180/M_PI), round(a12*180/M_PI), round(a23*180/M_PI), round(a30*180/M_PI),
                p[0].x, p[0].y, p[1].x, p[1].y, p[2].x, p[2].y, p[3].x, p[3].y);
        for(i = 1; i < 4; ++i)
        {
            if(p[i].x < xMin)
            {
                xMin = p[i].x;
            }
            if(p[i].y < yMin)
            {
                yMin = p[i].y;
            }
            if(p[i].x > xMax)
            {
                xMax = p[i].x;
            }
            if(p[i].y > yMax)
            {
                yMax = p[i].y;
            }
        }
        *xMinRet = xMin + ((xMax - xMin)/4);
        *yMinRet = yMin + ((yMax - yMin)/4);
        *xMaxRet = xMax - ((xMax - xMin)/4);
        *yMaxRet = yMax - ((yMax - yMin)/4);
#endif
    }
    return;
} // maxRectangle

//
// Make the (xMin,yMin)-(xMax,yMax) rectangle on the xy-plane at
// z visible on the Manet screen
//
// onChangeOrAlways - ScaleAndShiftUpdateOnChange to only update scale
//                    and shift values if things have changed enough,
//                    ScaleAndShiftUpdateAlways to update even if things
//                    haven't changed at all Usually will not update
//                    scaling or shifting if nothing has changed
//
void manetGLView::scaleAndShiftToSeeOnManet(
        double xMin, 
        double yMin, 
        double xMax, 
        double yMax, 
        double z,
        ScaleAndShiftUpdate onChangeOrAlways)
{
    static int upfcnt = 0;
    static int nccnt = 0;
    static double prevXMin = DBL_MAX;
    static double prevXMax = -DBL_MAX;
    static double prevYMin = DBL_MAX;
    static double prevYMax = -DBL_MAX;
    static double prevZ = DBL_MAX;
    static GLint prevWidth;
    static GLint prevHeight;
    GLint viewport[4];
    // adjust min and max values so the change less often. Do this
    // by reducing the precision to only three digits of precision 
    // by rounding the lower values down and higher values up.
    double dx = xMax - xMin;
    double dy = yMax - yMin;
    double mul = exp10(floor(log10(dx < dy ? dx : dy)) - 2);
    xMin = mul * floor(xMin / mul);
    yMin = mul * floor(yMin / mul);
    xMax = mul * ceil(xMax / mul);
    yMax = mul * ceil(yMax / mul);
    // get the viewport because the scale and centering might change
    // based on a screen resize
    glGetIntegerv(GL_VIEWPORT, viewport);
    if(onChangeOrAlways == ScaleAndShiftUpdateAlways ||
            prevXMin != xMin ||
            prevYMin != yMin ||
            prevXMax != xMax ||
            prevYMax != yMax ||
            prevZ != z ||
            prevWidth != viewport[2] ||
            prevHeight != viewport[3])
    {
        double wXMin;
        double wYMin;
        double wXMax;
        double wYMax;
        double  nodesWidth = xMax == xMin ? 1 : xMax - xMin;
        double nodesHeight = yMax == yMin ? 1 : yMax - yMin;
        GLdouble modelmatrix[16];
        GLdouble projmatrix[16];
        // Matrices and viewport to convert from world to screen
        // coordinates. 
        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
        glLoadIdentity();
        glTranslatef(0.0, 0.0, -20.0);
        glScalef(1.0, 1.0, manetAdj.scaleZ); // getting scale x and y so start with unity
        glRotatef(manetAdj.angleX, 1.0, 0.0, 0.0);
        glRotatef(manetAdj.angleY, 0.0, 1.0, 0.0);
        glRotatef(manetAdj.angleZ, 0.0, 0.0, 1.0);
        glTranslatef(0.0, 0.0, manetAdj.shiftZ + 3); // getting shift x and y so start with zero
        glGetDoublev(GL_MODELVIEW_MATRIX, modelmatrix);
        glGetDoublev(GL_PROJECTION_MATRIX, projmatrix);
        glPopMatrix();
        if(visibleDrawBoxAtZ(viewport, z, modelmatrix, projmatrix, nodesWidth/nodesHeight, &wXMin, &wYMin, &wXMax, &wYMax)) 
        {
            // static time_t tick = 0;
            // time_t now = time(0);
            // get shift and scale
            manetAdj.shiftX = ((wXMin + wXMax) / 2) - ((xMin + xMax) / 2);
            manetAdj.shiftY = ((wYMin + wYMax) / 2) - ((yMin + yMax) / 2);
            manetAdj.scaleX = (wXMax - wXMin) / nodesWidth;
            manetAdj.scaleY = (wYMax - wYMin) / nodesHeight;
            if(manetAdj.scaleX > manetAdj.scaleY)
                manetAdj.scaleX = manetAdj.scaleY;
            else
                manetAdj.scaleY = manetAdj.scaleX;

            BackgroundImage &bgImage=BackgroundImage::getInstance(); 
            if (bgImage.centerImage())
            {
                GLfloat x,y,z,w,h;
                bgImage.getDrawingCoords(x,w,y,h,z);
                bgImage.setDrawingCoords(manetAdj.shiftX, w, manetAdj.shiftY, h, z); 
                bgImage.centerImage(false); 
            }
        }
        prevXMin = xMin;
        prevYMin = yMin;
        prevXMax = xMax;
        prevYMax = yMax;
        prevZ = z;
        prevWidth = viewport[2];
        prevHeight = viewport[3];
    }
    else
    {
        ++nccnt;
    }
    {
        static time_t tick = 0;
        time_t now = time(0);
        if(now > tick)
        {
            if(nccnt)
                nccnt = 0;
            if(upfcnt)
                upfcnt = 0;
            tick = now + 5;
        }
    }
} // scaleAndShiftToSee

//
// Scale and shift the Manet to the center of the Manet screen
//
// onChangeOrAlways - ScaleAndShiftUpdateOnChange to only update scale
//                    and shift values if things have changed enough,
//                    ScaleAndShiftUpdateAlways to update even if things
//                    haven't changed at all Usually will not update
//                    scaling or shifting if nothing has changed
//
void manetGLView::scaleAndShiftToCenter(ScaleAndShiftUpdate onChangeOrAlways)
{
    double xMin = DBL_MAX;
    double xMax = -DBL_MAX;
    double yMin = DBL_MAX;
    double yMax = -DBL_MAX;
    double zMin = DBL_MAX;
    bool includeAntenna = isActive(ANTENNARADIUS_LAYER); 
    // bool includeHierarchy = isActive(HIERARCHY_LAYER); 

    // find drawing extents
    WatcherGraph::vertexIterator vi, vend;
    for(tie(vi, vend)=vertices(wGraph.theGraph); vi!=vend; ++vi)
    {
        WatcherGraphNode &n=wGraph.theGraph[*vi]; 
        GLdouble x, y, z; 
        gps2openGLPixels(n.gpsData->dataFormat, n.gpsData->x, n.gpsData->y, n.gpsData->z, x, y, z); 

        double r = 0;
        if(includeAntenna)
            r = antennaRadius; 

        // if(includeHierarchy)
        // {
        //     double hr = HIERARCHY_RADIUS(m->nlist[i].level);
        //     if(r < hr)
        //     {
        //         r = hr;
        //     }
        // }
        {
            double nodeXMin = x - r;
            double nodeXMax = x + r;
            double nodeYMin = y - r;
            double nodeYMax = y + r;
            if(nodeXMin < xMin) xMin = nodeXMin;
            if(nodeXMax > xMax) xMax = nodeXMax;
            if(nodeYMin < yMin) yMin = nodeYMin;
            if(nodeYMax > yMax) yMax = nodeYMax;
        }
        if(z < zMin)
            zMin = z;
    }
    scaleAndShiftToSeeOnManet(xMin, yMin, xMax, yMax, zMin, onChangeOrAlways);
} // scaleAndShiftToCenter

void manetGLView::getShiftAmount(GLdouble &x_ret, GLdouble &y_ret)
{
    GLdouble z;
    GLdouble modelmatrix[16];
    GLdouble projmatrix[16];
    GLint viewport[4];
    glPushMatrix();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, 0.0, -20.0);
    glScalef(manetAdj.scaleX, manetAdj.scaleY, manetAdj.scaleZ);
    glRotatef(manetAdj.angleX, 1.0, 0.0, 0.0);
    glRotatef(manetAdj.angleY, 0.0, 1.0, 0.0);
    glRotatef(manetAdj.angleZ, 0.0, 0.0, 1.0);
    glTranslatef(manetAdj.shiftX, manetAdj.shiftY, manetAdj.shiftZ + 3);
    glGetDoublev(GL_MODELVIEW_MATRIX, modelmatrix);
    glGetDoublev(GL_PROJECTION_MATRIX, projmatrix);
    glGetIntegerv(GL_VIEWPORT, viewport);
    glPopMatrix();

    // if(globalManet)
    // {
    //     for(i = 0; ; ++i)
    //     {
    //         if(i == globalManet->numnodes)
    //         {
    //             z = -20;
    //             break;
    //         }
    //         if(globalGpsValidFlag[i])
    //         {
    //             z = globalManet->nlist[i].z;
    //             break;
    //         }
    //     }
    // }
    // else
    // {
    //     z = -20;
    // }
    z=-20;
    {
        int xmid = viewport[2] / 2;
        int ymid = viewport[3] / 2;
        int dx = viewport[2] / 20; // shift by 1/20th the screen
        int dy = viewport[3] / 20; // shift by 1/20th the screen
        XYWorldZToWorldXWorldY xyz[2] =
        {
            { xmid, ymid, z, 0, 0 },
            { xmid + dx, ymid - dy, z, 0, 0 },
        };
        if(xyAtZForModelProjViewXY( xyz, sizeof(xyz) / sizeof(xyz[0]), modelmatrix, projmatrix, viewport) ==  0)
        {
            x_ret = xyz[0].worldX_ret - xyz[1].worldX_ret;
            y_ret = xyz[1].worldY_ret - xyz[2].worldY_ret;
        }
        else
        {
            x_ret = 20.;
            y_ret = 20.;
        }
    }
    return;
}

void manetGLView::shiftBackgroundCenterLeft(double dx)
{
    GLfloat x, y, w, h, z;
    BackgroundImage &bg=BackgroundImage::getInstance();
    bg.getDrawingCoords(x, w, y, h, z);
    bg.setDrawingCoords(x+dx, w, y, h, z);
}

void manetGLView::shiftBackgroundCenterUp(double dy)
{
    GLfloat x, y, w, h, z;
    BackgroundImage &bg=BackgroundImage::getInstance();
    bg.getDrawingCoords(x, w, y, h, z);
    bg.setDrawingCoords(x, w, y+dy, h, z);
}

void manetGLView::shiftCenterRight()
{
    GLdouble shift, dummy;
    getShiftAmount(shift, dummy);
    shiftCenterRight(shift);
}
void manetGLView::shiftCenterRight(double shift)
{
    manetAdj.shiftX -= shift;
    autoCenterNodesFlag=false;
} 

void manetGLView::shiftCenterLeft()
{
    GLdouble shift, dummy;
    getShiftAmount(shift, dummy);
    shiftCenterLeft(shift);
}
void manetGLView::shiftCenterLeft(double shift)
{
    manetAdj.shiftX += shift;
    autoCenterNodesFlag=false;
} 

void manetGLView::shiftCenterDown()
{
    GLdouble shift, dummy;
    getShiftAmount(shift, dummy);
    shiftCenterDown(shift);
}
void manetGLView::shiftCenterDown(double shift)
{
    manetAdj.shiftY += shift;
    autoCenterNodesFlag=false;
} 

void manetGLView::shiftCenterUp()
{
    GLdouble shift, dummy;
    getShiftAmount(shift, dummy);
    shiftCenterUp(shift);
}
void manetGLView::shiftCenterUp(double shift)
{
    manetAdj.shiftY -= shift;
    autoCenterNodesFlag=false;
} 

void manetGLView::shiftCenterIn()
{
    GLdouble shift, dummy;
    getShiftAmount(shift, dummy);
    shiftCenterIn(shift);
}
void manetGLView::shiftCenterIn(double shift)
{
    manetAdj.shiftZ -= shift;
    autoCenterNodesFlag=false;
} 

void manetGLView::shiftCenterOut()
{
    GLdouble shift, dummy;
    getShiftAmount(shift, dummy);
    shiftCenterOut(shift);
}
void manetGLView::shiftCenterOut(double shift)
{
    manetAdj.shiftZ += shift;
    autoCenterNodesFlag=false;
} 

void manetGLView::viewpointReset(void)
{
    manetAdj = manetAdjInit;
}

void manetGLView::zoomOut()
{
    manetAdj.scaleX /= 1.05;
    if (manetAdj.scaleX < 0.001) 
        manetAdj.scaleX = 0.001;
    manetAdj.scaleY = manetAdj.scaleX;
    autoCenterNodesFlag = 0;
}

void manetGLView::zoomIn()
{
    manetAdj.scaleX *= 1.05;
    manetAdj.scaleY = manetAdj.scaleX;
    autoCenterNodesFlag = 0;
}

void manetGLView::compressDistance()
{
    manetAdj.scaleZ -= 0.1;
    if (manetAdj.scaleZ < 0.02)
        manetAdj.scaleZ = 0.02;
    if(autoCenterNodesFlag)
        scaleAndShiftToCenter(ScaleAndShiftUpdateAlways);
} 

void manetGLView::expandDistance()
{
    manetAdj.scaleZ += 0.1;
} 

#define TEXT_SCALE 0.08
#define TEXT_SCALE_ZOOM_FACTOR 1.05
#define ARROW_SCALE_ZOOM_FACTOR 1.05

void manetGLView::textZoomReset(void)
{
    scaleText= 0.08;
}

void manetGLView::textZoomIn(void)
{
    scaleText*= TEXT_SCALE_ZOOM_FACTOR;
}

void manetGLView::textZoomOut(void)
{
    scaleText/= TEXT_SCALE_ZOOM_FACTOR;
}

void manetGLView::arrowZoomReset(void)
{
    scaleLine= 1.0;
}

void manetGLView::arrowZoomIn(void)
{
    scaleLine*= ARROW_SCALE_ZOOM_FACTOR;
}

void manetGLView::arrowZoomOut(void)
{
    scaleLine/= ARROW_SCALE_ZOOM_FACTOR;
}

void manetGLView::rotateX(float deg)
{
    manetAdj.angleX += deg;
    while(manetAdj.angleX >= 360.0)
        manetAdj.angleX -= 360.0;
    while(manetAdj.angleX < 0)
        manetAdj.angleX += 360.0;
    if(autoCenterNodesFlag)
        scaleAndShiftToCenter(ScaleAndShiftUpdateAlways);
} 

void manetGLView::rotateY(float deg)
{
    manetAdj.angleY += deg;
    while(manetAdj.angleY >= 360.0)
        manetAdj.angleY -= 360.0;
    while(manetAdj.angleY < 0)
        manetAdj.angleY += 360.0;
    if(autoCenterNodesFlag)
        scaleAndShiftToCenter(ScaleAndShiftUpdateAlways);
} 

void manetGLView::rotateZ(float deg)
{
    manetAdj.angleZ += deg;
    while(manetAdj.angleZ >= 360.0)
        manetAdj.angleZ -= 360.0;
    while(manetAdj.angleZ < 0)
        manetAdj.angleZ += 360.0;
    if(autoCenterNodesFlag)
        scaleAndShiftToCenter(ScaleAndShiftUpdateAlways);
} 

void manetGLView::gps2openGLPixels(const GPSMessage::DataFormat &format, const double inx, const double iny, const double inz, GLdouble &x, GLdouble &y, GLdouble &z) 
{
    TRACE_ENTER();

    // GTL - this should be done when the message is recv'd - not every time we need to 
    // compute the points - which is a whole hell of a lot of times.

    if (format==GPSMessage::UTM)
    {
        //
        // There is no UTM zone information in the UTM GPS packet, so we assume all data is in a single
        // zone. Because of this, no attempt is made to place the nodes in the correct positions on the 
        // planet surface. We just use the "lat" "long" data as pure x and y coords in a plane, offset
        // by the first coord we get. (Nodes are all centered around 0,0 where, 0,0 is defined 
        // by the first coord we receive. 
        //
        if (iny < 91 && inx > 0) 
            LOG_WARN("Received GPS data that looks like lat/long in degrees, but GPS data format mode is set to UTM in cfg file."); 

        static double utmXOffset=0.0, utmYOffset=0.0;
        static bool utmOffInit=false;
        if (utmOffInit==false)
        {
            utmOffInit=true;
            utmXOffset=inx;
            utmYOffset=iny; 

            LOG_INFO("Got first UTM coordinate. Using it for x and y offsets for all other coords. Offsets are: x=" << utmXOffset << " y=" << utmYOffset);
        }

        x=inx-utmXOffset;
        y=iny-utmYOffset;    
        z=inz;

        // LOG_DEBUG("UTM given locations: lon=" << location->lon << " lat=" << location->lat << " alt=" << location->alt);
        // LOG_DEBUG("UTM node coords: x=" << us->x << " y=" << us->y << " z=" << us->z);
    }
    else // default to lat/long/alt WGS84
    {
        if (inx > 180)
            LOG_WARN("Received GPS data that may be UTM (long>180), but GPS data format mode is set to lat/long degrees in cfg file."); 

        x=inx*gpsScale;
        y=iny*gpsScale;
        z=inz-20;

        static double xOff=0.0, yOff=0.0;
        static bool xOffInit=false;
        if (xOffInit==false)
        {
            xOffInit=true;
            xOff=inx;
            yOff=iny;

            LOG_INFO("Got first Lat/Long coordinate. Using it for x and y offsets for all other coords. Offsets are: x=" 
                    << xOff << " y=" << yOff);
        }

        x-=xOff;
        y-=yOff;

        // LOG_DEBUG("Got GPS: long:" << location->lon << " lat:" << location->lat << " alt:" << location->alt); 
        // LOG_DEBUG("translated GPS: x:" << us->x << " y:" << us->y << " z:" << us->z); 
    }
    // LOG_DEBUG("Converted GPS from " << inx << ", " << iny << ", " << inz << " to " << x << ", " << y << ", " << z); 
    TRACE_EXIT();
}

manetGLView::manetGLView(QWidget *parent) : 
    QGLWidget(parent),
    autoCenterNodesFlag(false)
{
    TRACE_ENTER();
    showPositionFlag=true;
    manetAdjInit.angleX=0.0;
    manetAdjInit.angleY=0.0;
    manetAdjInit.angleZ=0.0;
    manetAdjInit.scaleX=0.035;
    manetAdjInit.scaleY=0.035;
    manetAdjInit.scaleZ=0.03;
    manetAdjInit.shiftX=0.0;
    manetAdjInit.shiftY=0.0;
    manetAdjInit.shiftZ=0.0;
    setFocusPolicy(Qt::StrongFocus); // tab and click to focus
    TRACE_EXIT();
}

manetGLView::~manetGLView()
{
    TRACE_ENTER();
    TRACE_EXIT();
}

bool manetGLView::loadConfiguration()
{
    TRACE_ENTER();

    scaleText=0.08;
    scaleLine=1.0;
    monochromeMode = false;
    threeDView = true;
    backgroundImage = true;

    // causes goodinw control buttons to show/not show.
    emit runningGoodwin(true); 

    //
    // Check configuration for GUI settings.
    //
    Config &cfg=SingletonConfig::instance();
    SingletonConfig::lock();
    libconfig::Setting &root=cfg.getRoot();

    string prop="server";
    if (!root.lookupValue(prop, serverName))
    {
        LOG_FATAL("Please specify the server name in the cfg file");
        LOG_FATAL("I set the default to localhost, but that may not be what you want."); 
        root.add(prop, Setting::TypeString)="localhost"; 
        TRACE_EXIT_RET_BOOL(false); 
        return false;
    }

    prop="layers";
    if (!root.exists(prop))
        root.add(prop, libconfig::Setting::TypeGroup);

    bool boolVal=false;
    libconfig::Setting &layers=cfg.lookup(prop);

    struct 
    {
        const char *prop;
        GUILayer layer;
    } layerVals[] =
    {
        { "bandwidth", BANDWIDTH_LAYER },
        { "undefined", UNDEFINED_LAYER },
        { "neighbors", PHYSICAL_LAYER },
        { "hierarchy", HIERARCHY_LAYER },
        { "routing", ROUTING_LAYER },
        { "routingOneHop", ONE_HOP_ROUTING_LAYER },
        { "antennaRadius", ANTENNARADIUS_LAYER },
        { "sanityCheck", SANITY_CHECK_LAYER },
        { "anomPaths", ANOMPATHS_LAYER }, 
        { "correlation", CORROLATION_LAYER },
        { "alert", ALERT_LAYER }, 
        { "correlation3Hop", CORROLATION_3HOP_LAYER },
        { "wormholeRouting", ROUTING2_LAYER },
        { "wormholeRoutingOnehop", ROUTING2_ONE_HOP_LAYER },
        { "normPaths", NORMAL_PATHS_LAYER }
    };
    for (size_t i=0; i<sizeof(layerVals)/sizeof(layerVals[0]); i++)
    {
        boolVal=false;
        LOG_DEBUG("Looking up layer " << layerVals[i].prop); 
        if (!layers.lookupValue(layerVals[i].prop, boolVal))
            layers.add(layerVals[i].prop, libconfig::Setting::TypeBoolean)=boolVal;

        LOG_DEBUG("Adding " << (boolVal?"active":"inactive") << " layer " << layerVals[i].layer << " to list of known layers"); 
        LayerListItemPtr item(new LayerListItem);
        item->layer=layerVals[i].layer;
        item->active=boolVal;
        knownLayers.push_back(item); 
    }

    // Give the GUI the current toggle state of the display.
    // GTL: to do - make all this stuff dynamic
    emit bandwidthToggled(isActive(BANDWIDTH_LAYER)); 
    emit undefinedToggled(isActive(UNDEFINED_LAYER)); 
    emit neighborsToggled(isActive(PHYSICAL_LAYER)); 
    emit hierarchyToggled(isActive(HIERARCHY_LAYER));
    emit routingToggled(isActive(ROUTING_LAYER));
    emit routingOnehopToggled(isActive(ONE_HOP_ROUTING_LAYER));
    emit antennaRadiusToggled(isActive(ANTENNARADIUS_LAYER));
    emit sanityCheckToggled(isActive(SANITY_CHECK_LAYER));
    emit anomPathsToggled(isActive(ANOMPATHS_LAYER)); 
    emit correlationToggled(isActive(CORROLATION_LAYER)); 
    emit alertToggled(isActive(ALERT_LAYER)); 
    emit correlation3HopToggled(isActive(CORROLATION_3HOP_LAYER)); 
    emit wormholeRoutingToggled(isActive(ROUTING2_LAYER)); 
    emit wormholeRoutingOnehopToggled(isActive(ROUTING2_ONE_HOP_LAYER)); 
    emit normPathsToggled(isActive(NORMAL_PATHS_LAYER)); 

    struct 
    {
        const char *prop; 
        bool def; 
        bool *val; 
    } boolVals[] = 
    {
        { "nodes3d", true, &threeDView },
        { "monochrome", false, &monochromeMode }, 
        { "displayBackgroundImage", true, &backgroundImage }
    }; 
    for (size_t i=0; i<sizeof(boolVals)/sizeof(boolVals[0]); i++)
    {
        prop=boolVals[i].prop;
        bool boolVal=boolVals[i].def; 
        if (root.lookupValue(prop, boolVal))
            *boolVals[i].val=boolVal; 
        else
            root.add(prop, libconfig::Setting::TypeBoolean)=boolVal;
    }
    emit threeDViewToggled(threeDView);
    emit monochromeToggled(monochromeMode);
    emit backgroundImageToggled(backgroundImage);

    struct 
    {
        const char *prop; 
        float def; 
        float *val; 
    } floatVals[] = 
    {
        { "scaleText", 1.0, &scaleText }, 
        { "scaleLine", 1.0, &scaleLine }, 
        { "layerPadding", 1.0, &layerPadding }, 
        { "gpsScale", 80000.0, &gpsScale }, 
        { "antennaRadius", 200.0, &antennaRadius } 
    }; 
    for (size_t i=0; i<sizeof(floatVals)/sizeof(floatVals[0]); i++)
    {
        prop=floatVals[i].prop;
        float floatVal=floatVals[i].def; 
        if (root.lookupValue(prop, floatVal))
            *floatVals[i].val=floatVal; 
        else
            root.add(prop, libconfig::Setting::TypeFloat)=floatVal;
    }

    //
    // Load background image settings
    //
    prop="backgroundImage";
    if (!root.exists(prop))
        root.add(prop, libconfig::Setting::TypeGroup);
    libconfig::Setting &s=cfg.lookup(prop);

    prop="imageFile"; 
    string strVal;
    if (!s.lookupValue(prop, strVal) || strVal=="none")   // If we don't have the setting or the it's set to 'do not use bg image'
    {
        LOG_INFO("watcherBackground:imageFile entry not found (or it equals \"none\") in configuration file, "
                "disabling background image functionality");
        if (strVal.empty())
            s.add(prop, libconfig::Setting::TypeString)="none";
        toggleBackgroundImage(false);
        emit enableBackgroundImage(false);
    }
    else
    {
        BackgroundImage &bgImage=BackgroundImage::getInstance(); 
        char *ext=rindex(strVal.data(), '.');
        if (!ext)
        {
            LOG_ERROR("I have no idea what kind of file the background image " << strVal << " is. I only support BMP and PPM"); 
            exit(1);
        }
        else if (0==strncasecmp(ext+sizeof(char), "bmp", 3))
        {
            if (!bgImage.loadBMPFile(strVal.data()))
            {
                LOG_FATAL("Unable to load background BMP image in watcher from file: " << strVal); 
                TRACE_EXIT_RET_BOOL(false);
                return false;
            }
        }
        else if (0==strncmp("ppm", ext+sizeof(char), 3))
        {
            if (!bgImage.loadPPMFile(strVal.data()))
            {
                LOG_FATAL("Unable to load background PPM image in watcher from file: " << strVal); 
                TRACE_EXIT_RET_BOOL(false);
                return false;
            }
        }
    }
    // bg image location and size.
    prop="coordinates";
    float coordVals[5]={0.0, 0.0, 0.0, 0.0, 0.0};
    if (!s.exists(prop))
    {
        s.add(prop, libconfig::Setting::TypeArray);
        for (size_t i=0; i<sizeof(coordVals)/sizeof(coordVals[0]); i++)
            s[prop].add(libconfig::Setting::TypeFloat)=coordVals[i];
    }
    else
    {
        for (size_t i=0; i<sizeof(coordVals)/sizeof(coordVals[0]); i++)
            coordVals[i]=s[prop][i];
        BackgroundImage &bg=BackgroundImage::getInstance();
        bg.setDrawingCoords(coordVals[0], coordVals[1], coordVals[2], coordVals[3], coordVals[4]); 
    }

    //
    // Load viewpoint
    //
    prop="viewPoint";
    if (!root.exists(prop))
        root.add(prop, libconfig::Setting::TypeGroup);
    libconfig::Setting &vp=cfg.lookup(prop); 

    struct 
    {
        const char *type;
        float *data[3];
    } viewPoints[] =
    {
        { "angle", { &manetAdj.angleX, &manetAdj.angleY, &manetAdj.angleZ }},
        { "scale", { &manetAdj.scaleX, &manetAdj.scaleY, &manetAdj.scaleZ }},
        { "shift", { &manetAdj.shiftX, &manetAdj.shiftY, &manetAdj.shiftZ }}
    };
    for (size_t i=0; i<sizeof(viewPoints)/sizeof(viewPoints[0]);i++)
    {
        if (!vp.exists(viewPoints[i].type))
        {
            vp.add(viewPoints[i].type, libconfig::Setting::TypeArray);
            for (size_t j=0; j<sizeof(viewPoints[i].data)/sizeof(viewPoints[i].data[0]); j++)
                vp[viewPoints[i].type].add(libconfig::Setting::TypeFloat);
        }
        else
        {
            libconfig::Setting &s=vp[viewPoints[i].type];
            for (int j=0; j<s.getLength(); j++)
                *viewPoints[i].data[j]=s[j];
        }
    }

    LOG_INFO("Set viewpoint - angle: " << manetAdj.angleX << ", " << manetAdj.angleY << ", " << manetAdj.angleZ);
    LOG_INFO("Set viewpoint - scale: " << manetAdj.scaleX << ", " << manetAdj.scaleY << ", " << manetAdj.scaleZ);
    LOG_INFO("Set viewpoint - shift: " << manetAdj.shiftX << ", " << manetAdj.shiftY << ", " << manetAdj.shiftZ);

    // background color
    prop="backgroundColor";
    if (!root.exists(prop))
        root.add(prop, libconfig::Setting::TypeGroup);
    libconfig::Setting &bgColSet=cfg.lookup(prop);

    struct 
    {
        const char *name;
        float val;
    } bgColors[] = 
    {
        { "r", 0.0 }, 
        { "g", 0.0 }, 
        { "b", 0.0 }, 
        { "a", 255.0 }
    };
    for (size_t i=0; i<sizeof(bgColors)/sizeof(bgColors[0]);i++)
        if (!bgColSet.lookupValue(bgColors[i].name, bgColors[i].val))
            bgColSet.add(bgColors[i].name, libconfig::Setting::TypeFloat)=bgColors[i].val;

    glClearColor(bgColors[0].val, bgColors[1].val,bgColors[2].val,bgColors[3].val);

    SingletonConfig::unlock();

    // 
    // Set up timer callbacks.
    //
    QTimer *checkIOTimer = new QTimer(this);
    QObject::connect(checkIOTimer, SIGNAL(timeout()), this, SLOT(checkIO()));
    checkIOTimer->start(100);

    QTimer *watcherIdleTimer = new QTimer(this);
    QObject::connect(watcherIdleTimer, SIGNAL(timeout()), this, SLOT(watcherIdle()));
    watcherIdleTimer->start(33); // Let's shoot for 30 frames a second.

    TRACE_EXIT();
    return true;
}

QSize manetGLView::minimumSizeHint() const
{
    TRACE_ENTER();
    QSize retVal=QSize(50, 50);
    TRACE_EXIT_RET(retVal.width() << retVal.height()); 
    return retVal;
}

QSize manetGLView::sizeHint() const
{
    TRACE_ENTER();
    QSize retVal=QSize(400, 400);
    TRACE_EXIT_RET(retVal.width() << retVal.height()); 
    return retVal;
}

void manetGLView::initializeGL()
{
    TRACE_ENTER();
    // glEnable(GL_DEPTH_TEST);
    // glEnable(GL_BLEND);
    // // glEnable(GL_LINE_SMOOTH);
    // // glEnable(GL_CULL_FACE); 
    // glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    // glShadeModel(GL_SMOOTH);

    // glEnable(GL_LIGHTING);

    // // GLfloat ambLight[] = { 0.25, 0.25, 0.25, 1.0 };    // Ambient light is not full strength, but white
    // // glLightfv(GL_LIGHT1, GL_AMBIENT, ambLight);
    // // glLightfv(GL_LIGHT1, GL_SPECULAR, ambLight);

    // // GLfloat diffLight[]= { 1.0f, 1.0f, 1.0f, 1.0f };    // diffuse light is full strength
    // // glLightfv(GL_LIGHT1, GL_DIFFUSE, diffLight);

    // // GLfloat posLight[]= { 200.0f, 200.0f, 200.0f, 1.0f }; // light is over right shoulder
    // // glLightfv(GL_LIGHT1, GL_POSITION, posLight);

    // // glEnable(GL_LIGHT1);

    // enable lighting
    glEnable(GL_LIGHTING); 

    // enable objects behind one another
    glEnable(GL_DEPTH_TEST);

    // Allow colors to bleed through by alpha (orange edges when nieghor and one hop routing draw the same link).
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // Set the shading model
    glShadeModel(GL_SMOOTH); 

    // Uncomment these if you want to use glColor(c) instead of 
    // glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c);
    // glEnable(GL_COLOR_MATERIAL);
    // glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

    // Spec is LIGHT0
    GLfloat specular[]={0.5, 0.5, 0.5, 1.0};
    GLfloat posSpecLight[]= { -500.0f, 500.0f, 100.0f, 1.0f };
    glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
    glLightfv(GL_LIGHT0, GL_POSITION, posSpecLight);
    glEnable(GL_LIGHT0); 

    // Amb is LIGHT1
    GLfloat ambLight[] = { 0.25, 0.25, 0.25, 1.0 };
    glLightfv(GL_LIGHT1, GL_AMBIENT, ambLight);
    glEnable(GL_LIGHT1);

    // or use the ambient light model
    // GLfloat global_ambient[] = { 0.5f, 0.5f, 0.5f, 1.0f };
    // glLightModelfv(GL_LIGHT_MODEL_AMBIENT, global_ambient);

    // Diffuse is LIGHT2
    GLfloat diffLight[]= { 0.5, 0.5, 0.5, 1.0f };    
    glLightfv(GL_LIGHT2, GL_DIFFUSE, diffLight);
    GLfloat posDiffLight[]= { 500.0, 500.0f, 100.0f, 1.0f };
    glLightfv(GL_LIGHT2, GL_POSITION, posDiffLight); 
    glEnable(GL_LIGHT2);

    TRACE_EXIT();
}

void manetGLView::checkIO()
{
    TRACE_ENTER();

    if (!messageStream)
    {
        messageStream=MessageStream::createNewMessageStream(serverName); 
        messageStream->setStreamTimeStart(SeekMessage::eof);
        messageStream->startStream();
    }

    while(messageStream && messageStream->isStreamReadable())
    {
        MessagePtr message;
        messageStream->getNextMessage(message);
        LOG_DEBUG("Got message: " << *message);
        wGraph.updateGraph(message);
    }

    wGraph.doMaintanence(); // check expiration, etc. 

    updateGL();  // redraw

    TRACE_EXIT();
}

void manetGLView::watcherIdle()
{
    TRACE_ENTER();

    bool update=false;
    Timestamp now=(Timestamp(time(NULL)))*1000; 

    // GTL - Should this code be moved into watcherGraph?
    WatcherGraph::vertexIterator vi, vend;
    for(tie(vi, vend)=vertices(wGraph.theGraph); vi!=vend; ++vi)
    {
        WatcherGraphNode &node=wGraph.theGraph[*vi]; 

        if (node.displayInfo->spin && now > node.displayInfo->nextSpinUpdate)
        {
            node.displayInfo->spinRotation_x+=node.displayInfo->spinIncrement;
            node.displayInfo->spinRotation_y+=node.displayInfo->spinIncrement;
            node.displayInfo->spinRotation_z+=node.displayInfo->spinIncrement;
            node.displayInfo->nextSpinUpdate=now+node.displayInfo->spinTimeout;
            update=true;
        }

        // Are we flashing and do we need to invert the color?
        if (node.displayInfo->flash && now > node.displayInfo->nextFlashUpdate)  
        {
            LOG_DEBUG("Toggling flash for node " << node.nodeId); 
            node.displayInfo->isFlashed=node.displayInfo->isFlashed?true:false;
            node.displayInfo->nextFlashUpdate=now+node.displayInfo->flashInterval;
            update=true;
        }
    }

    WatcherGraph::edgeIterator ei, eend;
    for(tie(ei, eend)=edges(wGraph.theGraph); ei!=eend; ++ei)
    {
        WatcherGraphEdge &edge=wGraph.theGraph[*ei]; 
        if (edge.displayInfo->flash && now > edge.displayInfo->nextFlashUpdate)
        {
            edge.displayInfo->isFlashed=edge.displayInfo->isFlashed?true:false;
            edge.displayInfo->nextFlashUpdate=now+edge.displayInfo->flashInterval;
            update=true;
        }
    }

    if (update)
        updateGL();

    TRACE_EXIT();
}

void manetGLView::paintGL()
{
    TRACE_ENTER();

    drawManet();

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();

    // QPainter painter;
    // painter.begin(this);
    // painter.setRenderHint(QPainter::Antialiasing);

    // QString text = tr("Click and drag with the left mouse button to rotate the Qt logo.");
    // QFontMetrics metrics = QFontMetrics(font());
    // int border = qMax(4, metrics.leading());

    // QRect rect = metrics.boundingRect(0, 0, width() - 2*border, int(height()*0.125), Qt::AlignCenter | Qt::TextWordWrap, text);
    // painter.setRenderHint(QPainter::TextAntialiasing);
    // painter.setPen(Qt::white);
    // painter.fillRect(QRect(0, 0, width(), rect.height() + 2*border), QColor(0, 0, 0, 127));
    // painter.drawText((width() - rect.width())/2, border, rect.width(), rect.height(), Qt::AlignCenter | Qt::TextWordWrap, text);
    // painter.end(); 

    glPopMatrix(); 

    TRACE_EXIT();
}

void manetGLView::drawText( GLdouble x, GLdouble y, GLdouble z, GLdouble scale, char *text, GLdouble lineWidth)
{
    int i;
    GLfloat lineheight=- glutStrokeWidth(GLUT_STROKE_ROMAN,'W') * scale;   // TOJ: need scale arg here?  

    glPushMatrix();
    glPushAttrib(GL_LINE_WIDTH);
    glLineWidth(lineWidth); 

    glTranslatef(x,y,z);
    glScaled(scale,scale,scale);
    for (i = 0; text[i]; i++)
        switch(text[i])
        {
            case '\n':
                glPopMatrix();
                glTranslatef(0.0,lineheight,0.0);
                glPushMatrix();
                glScaled(scale,scale,scale);
                break;
            default:
                glutStrokeCharacter(GLUT_STROKE_ROMAN, text[i]);
                break;
        }
    glPopAttrib(); 
    glPopMatrix();

}
void manetGLView::drawManet(void)
{
    // watcher::Skybox *sb=watcher::Skybox::getSkybox();
    // if (sb)
    //     sb->drawSkybox(manetAdj.angleX, manetAdj.angleY, manetAdj.angleZ);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, 0.0, -20.0);

    glPushMatrix();

    GLfloat black[]={0.0,0.0,0.0,1.0};
    GLfloat blue[]={0.0,0.0,1.0,0.6};

    ptime now = from_time_t(time(NULL));
    glScalef(0.02, 0.02, 0.02);
    if (monochromeMode)
        glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, black);
    else
        glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blue);

    string buf;
    if (showPositionFlag)
    {
        char buff[190];
        snprintf(buff, sizeof(buff), "Location: %3.1f, %3.1f, %3.1f scale %f  -  ",
                manetAdj.shiftX, manetAdj.shiftY, manetAdj.shiftZ, manetAdj.scaleX);
        buf+=string(buff); 
    }
    buf+=posix_time::to_simple_string(now);
    drawText(-500, -360, 0, scaleText*2.0, const_cast<char*>(buf.c_str()), 1.0);

    glPopMatrix();

    glScalef(manetAdj.scaleX, manetAdj.scaleY, manetAdj.scaleZ);
    glRotatef(manetAdj.angleX, 1.0, 0.0, 0.0);
    glRotatef(manetAdj.angleY, 0.0, 1.0, 0.0);
    glRotatef(manetAdj.angleZ, 0.0, 0.0, 1.0);
    glTranslatef(manetAdj.shiftX, manetAdj.shiftY, manetAdj.shiftZ);

    for (LayerList::iterator li=knownLayers.begin(); li!=knownLayers.end(); ++li)
    {
        if ((*li)->active)
        {
            // each layer is 'layerPadding' above the rest. 
            // layer order is currently defined by order of appearance
            // in the cfg file. 
            glTranslatef(0.0, 0.0, -layerPadding);  
            glPushMatrix();
            drawLayer((*li)->layer); 
            glPopMatrix();

        }
    }

    if (backgroundImage)
        BackgroundImage::getInstance().drawImage(); 

    glPopMatrix();
    // glFlush();
}

void manetGLView::drawEdge(const WatcherGraphEdge &edge, const WatcherGraphNode &node1, const WatcherGraphNode &node2)
{
    TRACE_ENTER(); 

    GLdouble x1, y1, z1, x2, y2, z2, width;
    gps2openGLPixels(node1.gpsData->dataFormat, node1.gpsData->x, node1.gpsData->y, node1.gpsData->z, x1, y1, z1); 
    gps2openGLPixels(node2.gpsData->dataFormat, node2.gpsData->x, node2.gpsData->y, node2.gpsData->z, x2, y2, z2); 

    width=edge.displayInfo->width;

{'fraction_non_alphanumeric': 0.1528325682288251, 'fraction_numerical': 0.03254805024466529, 'mean_word_length': 2.2423697209591418, 'pattern_counts': {'":': 2, '<': 365, '<?xml version=': 0, '>': 193, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7577272', 'n_tokens_mistral': 37765, 'n_tokens_neox': 35386, 'n_words': 7061}