/*
 * $Id: imageViewer.cxx,v 4.16 2004/06/23 15:58:16 hut66au Exp $
 *
 * Imview, the portable image analysis application
 * http://www.cmis.csiro.au/Hugues.Talbot/imview
 * ----------------------------------------------------------
 *
 *  Imview is an attempt to provide an image display application
 *  suitable for professional image analysis. It was started in
 *  1997 and is mostly the result of the efforts of Hugues Talbot,
 *  Image Analysis Project, CSIRO Mathematical and Information
 *  Sciences, with help from others (see the CREDITS files for
 *  more information)
 *
 *  Imview is Copyrighted (C) 1997-2001 by Hugues Talbot and was
 *  supported in parts by the Australian Commonwealth Science and 
 *  Industry Research Organisation. Please see the COPYRIGHT file 
 *  for full details. Imview also includes the contributions of 
 *  many others. Please see the CREDITS file for full details.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 * */

/*------------------------------------------------------------------------
 *
 * Code for the ImageViewer Class
 *
 * Hugues Talbot        27 Oct 1997
 *
 * This is my first attempt at a serious derivation. Don't laugh at
 * the code...
 *
 * This is very complex code, I get lost in there all the time.
 *
 * Hugues Talbot	12 Sep 2003: added two more viewing mode, 3 in total:
 *
 * - old mode, called `window fits the image', i.e. normally the
 * window sits tight around the image with no gap. The exceptions are
 * 1- if the image dimensions are so small that the main menu would
 * not be visible, so in this case the image is centered in a
 * minimal-size frame with a white background and 2- if the image is
 * too big to fit in the screen or if the user shrinks the main window
 * by hand, which is permitted (expanding the window by dragging the
 * frame is not). In this case scrollbars are created so that the user
 * can still access the whole data. Changes of zoom factor obviously
 * can make the window resize as long as the windows dimensions fit in
 * the screen and are not too small. A window can be shrunk but not
 * expanded. I like this mode best but it has been hard to implement
 * in X11 and is still somewhat shaky sometimes for unknown reasons.
 * Sometimes imview gets into a weird state with scrollbars in the
 * middle of the image, but I can never reliably invoke this faulty
 * mode.  Let me know if you can so I can correct it. This is the
 * imview default mode.
 *
 * - `image fits window' mode. There can be no scrollbar, only changes
 * in dimension of the main window. This is the only mode where the
 * aspect ratio can be changed interactively. Zooming also changes the
 * dimension of the window. There is no limit to the size of the main
 * window. This is the way XV by John Bradley behaves.
 *
 * - `window and image decoupled' mode. Zoom operations change the
 * image dimensions on the one hand, and windows operations change the
 * window dimensions on the other. If the image is too large for the
 * window scrollbars are created (and removed if they are no longer
 * needed). This is the way the GIMP operates (except for the GIMP the
 * scrollbars are always present). In this mode the window background
 * when visible is black.
 *
 * In short, the first mode is fine as default, but you might want to 
 * use the second for changing the aspect ratio interactively, and the
 * second mode in fullscreen mode (I'm working on that).
 *
 *-----------------------------------------------------------------------*/

#include "imnmspc.hxx" // namespace definitions etc.

#include <stdio.h>
#include <assert.h>
#include <string> // not used here but necessary for nocase.H
#include <FL/Fl_Window.H>
#include <FL/fl_draw.H>
#include <FL/Fl.H>
#include <FL/math.h>
#include "imview.hxx"
#include "imviewWindow.hxx"
#include "imageIO.hxx"
#include "menubar.hxx"
#include "pointfile.hxx"
#include "imageViewer.hxx"
#include "imageInfo.hxx"
#include "spectraBox.hxx"
#include "profileBox.hxx"
#include "annotatePoints.hxx"
#include "my_Image.hxx"
#include "nocase.hxx"
#include "io/newprefio.hxx"
#include "server/interpreter.hxx"

// needed external variables 
extern imviewWindow *mainWindow;
extern pointfile    *PtFileMngr;
extern imageinfo    *imageInfoPanel;
extern spectra      *spectraPanel;
extern spectra      *depthProfile;
extern profile      *profilePanel;
extern imprefs      *fileprefs;
extern annotatept   *annotatePointPanel;
extern linkmanager  *imview_linkmanager;
extern int          debugIsOn;
extern int          appMaxWidth, appMaxHeight;
extern bool         useOffscreenBuff;
extern bool         hideMainMenu;
extern bool         fullScreen;
extern displaymode   displaymodepref; // set by command line before object created...

imageViewer::imageViewer(int x, int y, int w, int h, const char *label)
    : Fl_Group(x,y,w,h,label)
{
    dbgprintf("ImageViewer constructor called with args: %d, %d, %d, %d \n",
           x, y, w, h);
    Ox = x;
    Oy = y;
    originalWidth = w;
    originalHeight = h;
    vposition_ = hposition_ = 0;
    hscrollbar = 0;
    vscrollbar = 0;
    theImage = 0;
    parentWindow = 0; 
    imageObject = 0;         // there is no image yet
    statusLine = 0;          // status line is invisible by default
    zoomBox = 0;
    zoomBoxDefined = 0;
    imageZoomFactor_ = defaultZoomFactor_= 1.0;
    xZoomFactor_ = yZoomFactor_ = 1.0;
    minZoomFactor_ = 1.0;
    nbPointDamage = 0;
    angleLine = 0;
    dbgprintf("ResizeInProgress set to false\n");
//    resizeInProgress = false;
    zoomDragInProgress = false;
    aspectConstrained = false;
    sizeConstrained = true;
    lineProfileInProgress = false;
    pointmode = false;
    keepProfileLine = false;
    pmode_ = IMV_POINTER_ZOOM_MODE;
    dmode_ = displaymodepref;
    currentPtColour_ = 0;

    // must call this:
    end();
}

imageViewer::~imageViewer()
{
    dbgprintf("Image Viewer destructor called\n");
    // delete the image Object if need be.
    zapImageObject();
	
    // always delete the image IO object
}


void imageViewer::displayCurrentImage(void)
{
    int realMinWidth, realMinHeight;
    static int oldImageWidth = -1, oldImageHeight = -1;
    double oldZoomFactor, df;
    
    // get rid of the previous image object
    zapImageObject();
    imageObject = new Fl_RGB_Image(theImage->imageData(),
				   theImage->imageWidth(),
				   theImage->imageHeight(),
				   theImage->imageDepth());
    oldZoomFactor = zoomFactor(); // this is the zoom factor of the previous image
    // work out the defaultZoomFactor
    df = trivmin((double)MINHEIGHT/theImage->imageHeight(), (double)MINWIDTH/theImage->imageWidth());
    // if the default zoom factor is smaller than 1.0, ignore it
    //defaultZoomFactor_ = trivmax(df, 1.0);
    df =  trivmax(df, 1.0);
    if ((oldImageWidth != theImage->imageWidth())
	|| (oldImageHeight != theImage->imageHeight())) {
	// we do not call zoomFactor() here because we don't
	// want the automatic resize() that goes with it. the
	// main window will be resized and redrawn at the end
	// of this function (see below) anyway.
	if (fullScreen) 
	    imageZoomFactor_ = defaultZoomFactor_ = computeMaxZoomFactor();
	else 
	    imageZoomFactor_ = defaultZoomFactor_ * df; // might be clever?
    }
    // on the other hand...
    minZoomFactor_ = trivmax(1.0/theImage->imageHeight(), 1.0/theImage->imageWidth());
    if (zoomFactor() < minZoomFactor_)
	zoomFactor(minZoomFactor_);
    dbgprintf("Minimal zoom factor = %g\n", minZoomFactor_);
    dbgprintf("Default zoom factor = %g\n", defaultZoomFactor_);
    
    // resize the parent window
    if (!parentWindow)
	parentWindow = (imviewWindow *)parent(); // by now parent ought to have been initialized
    // resize only if need be, that is: if the zoom factor is 1.0 (expected behaviour)
    // or if the window is too large for the current image.
    realMinWidth = trivmin(appMaxWidth,currentImageWidth());
    realMinHeight = trivmin(appMaxHeight,currentImageHeight());
    
    sizeConstraints(realMinWidth, realMinHeight);
    
    if ((Ox+currentImageWidth() < visibleWidth())
	|| (Oy+currentImageHeight() < visibleHeight())
	|| (zoomFactor() == defaultZoomFactor_) || (zoomFactor() == minZoomFactor_)) {
	// possibly resizing the parent window
	if (!sizeParent(Ox + realMinWidth, Oy + realMinHeight)) {
	    dbgprintf("Manual redraw call needed\n");
	    redraw_image(); // then the image will need a manual nudge to be redisplayed
	}
    } else {
	redraw_image(); // here too
	dbgprintf("Manual redraw call needed here too\n");
    }

    // remember the current image dimensions
    oldImageWidth = theImage->imageWidth();
    oldImageHeight = theImage->imageHeight();
	
    return;
}

// this method is called when we want to display NO image
// the window dimensions are returned to the initialization values
int imageViewer::close(void)
{
    if (imageObject) {
	zapImageObject();
	if (parentWindow) {
	    int realMinWidth = trivmin(appMaxWidth, originalWidth);
	    int realMinHeight = trivmin(appMaxHeight, originalHeight);
	    parentWindow->size(trivmax(Ox+originalWidth, MINWIDTH), Oy+originalHeight);
	    sizeParent(Ox + realMinWidth, Oy + realMinHeight);
	    sizeConstraints(realMinWidth, realMinHeight);
	}
	redraw();
    }
    
    return 0;
}


// resize the image to the minimum of the current displayed width and height
// of the _window_ and the width and height of the _image_, if need be...
bool imageViewer::resize(void)
{
    int minWidth, minHeight;
    int realNewWidth, realNewHeight;
    int wlimit, hlimit;
    bool retval = false;
    
    minWidth = trivmin(w(), currentImageWidth());
    minHeight = trivmin(h(), currentImageHeight());

    dbgprintf("Called resize(void)\n");
    // window size limits depend only on the image and the screen limits
    wlimit = trivmin(appMaxWidth, currentImageWidth());
    wlimit = trivmax(wlimit, MINWIDTH);
    hlimit = trivmin(appMaxHeight, currentImageHeight());
    hlimit = trivmax(hlimit, MINHEIGHT);
    
    // if the user asked for too big a resize, we have to resize
    // the window too...
    if ((getdisplaymode() ==  IMV_DISPLAY_WINDOW_FIT_IMG) 
	&& parentWindow 
	&& (((w() > currentImageWidth()) && (w() > MINWIDTH))
	    || ((h() > currentImageHeight()) && (h() > MINHEIGHT)))) {

	sizeConstraints(wlimit, hlimit);

	realNewWidth = trivmin(appMaxWidth, minWidth);
	realNewWidth = trivmax(realNewWidth, MINWIDTH);
	realNewHeight = trivmin(appMaxHeight, minHeight);
	realNewHeight = trivmax(realNewHeight, MINHEIGHT);
	if (!sizeParent(Ox + realNewWidth, Oy + realNewHeight)) {
	    dbgprintf("There was no resize but the image will require redrawing\n");
	    redraw_image();
	}
	    
	// We HAVE to worry about the scrollbars...
	if (hscrollbar)
	    hscrollbar->resize(x(),
			       y()+realNewHeight-HSLIDER_HEIGHT,
			       realNewWidth-VSLIDER_WIDTH,HSLIDER_HEIGHT);
	if (vscrollbar)
	    vscrollbar->resize(x()+realNewWidth-VSLIDER_WIDTH,
			       y(),
			       VSLIDER_WIDTH,
			       realNewHeight-HSLIDER_HEIGHT);
	retval = true; // we did do a further resize to fit the window around the image
    } else {
	if (parentWindow) {
	    sizeConstraints(wlimit, hlimit);
	}
	// no resize occured, but we still need to redraw the image
	// dbgprintf("No change in size, but we still require redraw\n");
	// THIS REDRAW here is only necessary when doing a zoom without
	// resizing the window (alt + >, etc). IF doing a zoom with redraw,
	// (>, etc), this cause a double redraw that should be. Fixing this
	// is not simple...
	// redraw_image();
	retval = false;
    }


    // There was an old omment here saying that we
    // didn't have to worry about resizing scrollbar.
    // The point was valid in the past but not since maximum values
    // for the size of the window have been set, because there
    // is no guarantee anymore that the window will be tight around
    // the image...

    return retval;
}

// so that we get passed the resize events
void imageViewer::resize(int newx, int newy, int neww, int newh)
{
    int minWidth, minHeight;
    int realNewWidth, realNewHeight;
    static int forgetNextEvent = 0;
    static int calls = 0;

    
    dbgprintf("imageViewer: Got a resize event: x:%d, y:%d, w:%d, h:%d\n",
	      newx, newy, neww, newh);

    if (getdisplaymode() == IMV_DISPLAY_WINDOW_FIT_IMG) {
	calls++; // count the calls

	if ((calls < 3) && (neww == (SWIDTH-Ox)) && (newh == (SHEIGHT-Oy))) {
	    dbgprintf("Listen, I'm not proud of this, but I can't figure out why FLTK\n");
	    dbgprintf("wants to resize this window\n");
	    dbgprintf("to the original size again!!, so I'm ignoring the event, so be it\n");
	    return; // forget it!
	}
   
    
	if (!forgetNextEvent) {
	    // for the time being keep the size tight around the image
	    minWidth = trivmin(neww, currentImageWidth());
	    minHeight = trivmin(newh, currentImageHeight());

	    realNewWidth = trivmin(appMaxWidth, minWidth);
	    realNewHeight = trivmin(appMaxHeight, minHeight);

	    realNewWidth = trivmax(realNewWidth, MINWIDTH);
	    realNewHeight = trivmax(realNewHeight, MINHEIGHT);
	
	
	    if (hscrollbar)
		hscrollbar->resize(newx,
				   newy+realNewHeight-HSLIDER_HEIGHT,
				   realNewWidth-VSLIDER_WIDTH,HSLIDER_HEIGHT);
	    if (vscrollbar)
		vscrollbar->resize(newx+realNewWidth-VSLIDER_WIDTH,
				   newy,
				   VSLIDER_WIDTH,
				   realNewHeight-HSLIDER_HEIGHT);

	    // do the base class work as well
	    Fl_Object::resize(newx, newy, realNewWidth, realNewHeight);

	} else  {
	    dbgprintf("Resize event discarded: now returning to normal\n");
	    forgetNextEvent = 0;
	} 
    } else if (getdisplaymode() == IMV_DISPLAY_IMG_FIT_WINDOW) {
	dbgprintf("Stretching to fit\n");
	Fl_Object::resize(newx, newy, neww, newh);
    } else if (getdisplaymode() == IMV_DISPLAY_DECOUPLE_WIN_IMG) {
        // resize the sliders
        if (hscrollbar)
            hscrollbar->resize(newx,
                               newy + newh - HSLIDER_HEIGHT,
                               neww - VSLIDER_WIDTH,
                               HSLIDER_HEIGHT);
        if (vscrollbar)
            vscrollbar->resize(newx + neww - VSLIDER_WIDTH,
                               newy,
                               VSLIDER_WIDTH,
                               newh - HSLIDER_HEIGHT);

        // resize base class too
        Fl_Object::resize(newx, newy, neww, newh);
    } else {
        warnprintf("Resize(non-void) called with unsupported display mode\n");
    }
    // else nothing to do (maybe)
    
    return ;
}

void imageViewer::hposition(int newx)
{
    if (newx == hposition_) return;
    hposition_ = trivmax(newx, 0);
    redraw_image();
}

void imageViewer::vposition(int newy)
{
    if (newy == vposition_) return;
    vposition_ = trivmax(newy, 0);
    redraw_image();
}

void imageViewer::pan(int newx, int newy)
{
    if (imageObject) {
        hposition_ = trivmax((int)(newx * imageZoomFactor_ + 0.5), 0) ;
        vposition_ = trivmax((int)(newy * imageZoomFactor_ + 0.5), 0) ;
        redraw_image();
    }
    return;
}

double imageViewer::setDefaultZoomFactor(double f, bool forward)
{
    defaultZoomFactor_ = f;
    
    if (forward && imview_linkmanager) {
        if (imview_linkmanager->setDefaultZoomFactor(f) > 0) 
            dbgprintf("Remote set default zoom factor %g failed\n", f);
    }

    return(zoomFactor(f));
}

// if useMinOfBoth is true, the whole image will be visible
// if not, the whole screen will be filled with data, but
// the image will exceed the screen boundary.

// helper function: computes the max zoom
// this is the zoom that will let the whole image be shown on screen.
double imageViewer::computeMaxZoomFactor(bool useMinOfBoth)
{
    double mxz = (double)(parentWindow->currentDisplayWidth())/(theImage->imageWidth());
    double myz = (double)(parentWindow->currentDisplayHeight())/(theImage->imageHeight());
    double fz;
    if (useMinOfBoth) {
        fz = trivmin(mxz, myz);
    } else {
        fz = trivmax(mxz, myz);
    }

    return fz;
}

// actually applies the max zoom and sets it as default zoom factor
double imageViewer::applyMaxZoomFactor(bool useMinOfBoth)
{
    defaultZoomFactor_ = computeMaxZoomFactor(useMinOfBoth);
    return zoomFactor(defaultZoomFactor_);
}

// changes the zoom factor of the image, and redisplays it
double imageViewer::zoomFactor(double f, bool doRedraw, bool forward)
{
    double oldfactor = imageZoomFactor_;
    double relf = f/oldfactor;

    if ((getdisplaymode() != IMV_DISPLAY_IMG_FIT_WINDOW) 
	&& imageObject && (f != oldfactor)) {
	// update cursors's posistions
	hposition_ = trivmax((hposition_ * relf) + ((relf - 1.0)*visibleHeight())/2.0, 0);
	vposition_ = trivmax((vposition_ * relf) + ((relf - 1.0)*visibleWidth())/2.0, 0);

	imageZoomFactor_ = trivmax(f,minZoomFactor_);

	// if resize returns true, the parent window will get resized,
	// there is no need to call redraw by hand. On the other hand,
	// if redraw is forced or the window is not redrawn, we should do it.
	if (!resize() && doRedraw) {
	    dbgprintf("Redraw forced after zoom factor changed\n");
	    redraw_image();
	}

	if (forward && imview_linkmanager) {
	    if (imview_linkmanager->zoomfactor(f) > 0)
		dbgprintf("Remote zoom factor %g failed\n", f);
	}
    } else {
        dbgprintf("zoomFactor not updated\n");
    }
    
    return (imageZoomFactor_);
}

// zooms to the box defined in the parameters
// one of bw or bh can be 0, in which case the
// aspect ratio is preserved.
double imageViewer::zoomToBox(int bx, int by, int bw, int bh, bool forward)
{
    double xzoom, yzoom;

    if (((bh > 0) || (bw > 0)) && imageObject) {
	dbgprintf("Zooming to x=%d, y=%d, w=%d, h= %d\n", bx, by, bw, bh);

	// corrections
	if (bx < 0) bx = 0;
	if (by < 0) by = 0;
	if (bw >= dataWidth()) bw =  dataWidth();
	if (bh >= dataHeight()) bh =  dataHeight();

        // if one of bw or bh is zero, change them so that
        // the current aspect ratio is unchanged.
        if (bw == 0) {
            bw = (int)(bh * ((double)visibleWidth()/visibleHeight()) + 0.5);
        } else if (bh == 0) {
            bh = (int)(bw * ((double)visibleHeight()/visibleWidth()) + 0.5);
        }
	
	if (hscrollbar && hscrollbar->visible()) {
	    // horizontal scroll bar is already visible
	    xzoom = (double)visibleWidth()/bw;
	} else {
	    // scrollbar is not yet visible but will be.
	    double rw = bw + ((double)VSLIDER_WIDTH*bw)/visibleWidth();
	    xzoom = (double)visibleWidth()/rw;
	}

	// same issue as above.
	if (vscrollbar && vscrollbar->visible()) {
	    yzoom = (double)visibleHeight()/bh;
	} else {
	    double rh = bh + ((double)HSLIDER_HEIGHT*bh)/visibleHeight();
	    yzoom = (double)visibleHeight()/rh;
	}
    

	// if the wanted aspect ratio and the current one are too different,
	// we resize the window to fit the bill
	if (!aspectConstrained) {
	    // work out the zoom factor
	    imageZoomFactor_ = trivmax(xzoom,yzoom);
	    // but don't make the image wider or taller than the screen!
	    imageZoomFactor_ = trivmin(imageZoomFactor_, (double)(appMaxWidth-Ox)/bw);
	    imageZoomFactor_ = trivmin(imageZoomFactor_, (double)(appMaxHeight-Oy)/bh);
	    hposition_ = trivmax(bx * imageZoomFactor_, 0) ;
	    vposition_ = trivmax(by * imageZoomFactor_, 0) ;

	    // window size limits depend only on the image and the screen limits
	    int wlimit = trivmin(appMaxWidth, currentImageWidth());
	    wlimit = trivmax(wlimit, MINWIDTH);
	    int hlimit = trivmin(appMaxHeight, currentImageHeight());
	    hlimit = trivmax(hlimit, MINHEIGHT);
	    sizeConstraints(wlimit, hlimit);
	    // actually size the parent
	    dbgprintf("sizing the Parent from within zoomToBox\n");
	    sizeParent((int)(Ox + bw*imageZoomFactor_ + VSLIDER_WIDTH + 0.5),
		       (int)(Oy + bh*imageZoomFactor_ + HSLIDER_HEIGHT + 0.5));
            dbgprintf("Forcing redraw\n");
            redraw_image();
	} else {
	    // if zooming in a square way:
	    // work out the zoom factor
	    imageZoomFactor_ = trivmin(xzoom, yzoom);
	    hposition_ = trivmax(bx * imageZoomFactor_, 0) ;
	    vposition_ = trivmax(by * imageZoomFactor_, 0) ;
	    resize();
	    dbgprintf("Redraw forced after aspectConstrained zoomToBox\n");
	    redraw_image();
	}

	if (forward && imview_linkmanager) {
	    if (imview_linkmanager->zoombox(bx,by,bw,bh) > 0) // force other connected imviews to do the same
		dbgprintf("Remote zoombox failed\n");
	}
    }
    
    return imageZoomFactor_; 
}

int imageViewer::currentImageWidth(void)
{
    if (imageObject)
	return ((int)(theImage->imageWidth() * imageZoomFactor_ + 0.5));
    else
	return originalWidth;
}

int imageViewer::currentImageHeight(void)
{
    if (imageObject)
	return ((int)(theImage->imageHeight() * imageZoomFactor_ + 0.5));
    else
	return originalHeight;
}


void imageViewer::redraw_image()
{
    dbgprintf("Redraw_image called\n");
    damage(32); // experimental
}

// now the hard bits

void imageViewer::drawScrollbars_decoupled(int imw, int imh, int visibleW, int visibleH)
{
    if (scrollbartype_ != IMV_DISPLAY_DECOUPLE_WIN_IMG) {
        dbgprintf("In drawScrollbars_decoupled: wrong kind of scrollbars\n");
        destroy_scrollbars();
    }

    // create, show or hide horizontal scrollbar if necessary
    if (imw > visibleW) {
        if (!hscrollbar)
            create_hscrollbar_decoupled();
        else if (!hscrollbar->visible()) {
            hscrollbar->show();
        }
    } else {
        if (hscrollbar && hscrollbar->visible()) {
            hscrollbar->hide();
        }
    }
    // create, show or hide vertical scrollbar if necessary
    if (imh > visibleH) {
        if (!vscrollbar)
            create_vscrollbar_decoupled();
        else if (!vscrollbar->visible()) {
            vscrollbar->show();
        }
    } else {
        if (vscrollbar && vscrollbar->visible()) {
            vscrollbar->hide();
        }
    }
	    
   

    // measure the dimensions of the actual display
    // area, taking slidebars presence into account
    // adjust positions if need be
    if (hposition_ + visibleW > imw) {
        // hposition is actually false, probably because of a resize event!
        hposition_ = trivmax(imw - visibleW, 0);
    }
    if (vposition_ + visibleH > imh) {
        // hposition is actually false, probably because of a resize event!
        vposition_ = trivmax(imh - visibleH, 0);
    }
                
    if (hscrollbar && hscrollbar->visible()) {
        hscrollbar->scrollvalue((int)(hposition_+0.5), // horizontal position of the window
                                visibleW,   // visible width
                                0,          // by definition, horizontal origin of the image
                                imw);       // width of the image
    }
    if (vscrollbar && vscrollbar->visible()) {
        vscrollbar->scrollvalue((int)(vposition_ + 0.5), // vertical positin of the window
                                visibleH,   // visible height
                                0,          // by definition, vertical origin of the image
                                imh);       // height of the image
    }
    
    return;
}


void imageViewer::drawScrollbars_win_fit_img(int imw, int imh, int &visibleW, int &visibleH)
{
    bool scrollchanged = false;

    dbgprintf("Draw scrollbars, window-fit-image policy\n");
    if (scrollbartype_ != IMV_DISPLAY_WINDOW_FIT_IMG) {
        dbgprintf("Wrong kind of scrollbar present\n");
        destroy_scrollbars();
    }
    
    // create, show or hide horizontal scrollbar if necessary
    if (imw > w()) {
        if (!hscrollbar)
            create_hscrollbar_win_fit_img();
        else if (!hscrollbar->visible()) {
            dbgprintf("Showing horizontal scrollbar\n");
            visibleH -= hscrollbar->h();
            hscrollbar->show();
            scrollchanged = true;
        }
    } else {
        if (hscrollbar && hscrollbar->visible()) {
            dbgprintf("Hiding horizontal scrollbar\n");
            visibleH += hscrollbar->h();
            hscrollbar->hide();
            scrollchanged = true;
        }
    }
    // create, show or hide vertical scrollbar if necessary
    if (imh > h()) {
        if (!vscrollbar)
            create_vscrollbar_win_fit_img();
        else if (!vscrollbar->visible()) {
            dbgprintf("Showing vertical scrollbar\n");
            visibleW -= vscrollbar->w();
            vscrollbar->show();
            scrollchanged = true;
        }
    } else {
        if (vscrollbar && vscrollbar->visible()) {
            dbgprintf("Hiding vertical scrollbar\n");
            visibleW += vscrollbar->w();
            vscrollbar->hide();
            scrollchanged = true;
        }
    }

    if (scrollchanged)
        dbgprintf("now, VisibleW = %d, VisibleH = %d\n", visibleW, visibleH);
    else
        dbgprintf("VisibleW and VisibleH unchanged\n");
   

    // measure the dimensions of the actual display
    // area, taking slidebars presence into account
    // adjust positions if need be
    if (hposition_ + visibleW > imw) {
        // hposition is actually false, probably because of a resize event!
        hposition_ = trivmax(imw - visibleW, 0);
    }
    if (vposition_ + visibleH > imh) {
        // hposition is actually false, probably because of a resize event!
        vposition_ = trivmax(imh - visibleH, 0);
    }
                
    if (hscrollbar && hscrollbar->visible()) {
        hscrollbar->scrollvalue((int)(hposition_+0.5), // horizontal position of the window
                                visibleW,   // visible width
                                0,          // by definition, horizontal origin of the image
                                imw);       // width of the image
    }
    if (vscrollbar && vscrollbar->visible()) {
        vscrollbar->scrollvalue((int)(vposition_ + 0.5), // vertical positin of the window
                                visibleH,   // visible height
                                0,          // by definition, vertical origin of the image
                                imh);       // height of the image
    }
    
    return;
}


// draw the image when the window size and the image size are
// no longer made to match
void imageViewer::drawImage_decoupled(int imw, 
                                      int imh, 
                                      int imd, 
                                      bool redrawBackground, 
                                      bool smooth,
                                      int visibleW, 
                                      int visibleH, 
                                      int &d) // careful -- r/w
{
    static int oldOx = -1, oldOy = -1, oldw = -1, oldh = -1;
		
    dbgprintf("    drawImage_decoupled:\n");

    if ((zoomFactor() == 1.0) && useOffscreenBuff) { // we use hand-made double buffering.
        dbgprintf("        (zoomfactor == 1 && useOffscreenBuff)\n");
        if ((imageObject->w() <= appMaxWidth)
            && (imageObject->h() <= appMaxHeight)) {
            // this is noticeable faster than
            // the generic function for any zoom factor
            // we allow
            dbgprintf("            imgdims <= appMax\n");
            if ((imw >= visibleW) && (imh >= visibleH)) {
                oldw = -1; // reset the black box condition
                dbgprintf("            fl_draw_clipped -- imw and h >= visible\n");
                fl_draw_clipped(imageObject,
                                Ox, Oy,
                                (int)(hposition_+0.5),
                                (int)(vposition_+0.5),
                                visibleW, visibleH);
            } else {
                // center image
                int newOx = (Ox + visibleW - imw)/2;
                newOx = trivmax(newOx, Ox);
                int newOy = (Oy + visibleH - imh)/2;
                newOy = trivmax(newOy, Oy);
                int neww = trivmin(imw, visibleW);
                int newh = trivmin(imh, visibleH);
                
		dbgprintf("            imw or h < visible\n");			    
                dbgprintf("            NewOx=%d, newOy=%d\n", newOx, newOy);
                // draw a black box if necessary
                if ((newOx != oldOx) || (newOy != oldOy)
                    || (neww != oldw) || (newh != oldh)
                    || redrawBackground || (d != 32)) {
                    dbgprintf("                draw white box, quick fill\n");
                    fl_color(FL_BLACK);
                    // fill whole window
                    fl_rectf(Ox,Oy,parentWindow->w(),parentWindow->h()); // quick fill
                    //fl_color(FL_WHITE);
                    //fl_rect(newOx-1, newOy-1,imw+2, imh+2); 
                    oldOx = newOx;
                    oldOy = newOy;
                    oldw = neww;
                    oldh = newh;
                }
                dbgprintf("            fl_draw_clipped\n", newOx, newOy);
                fl_draw_clipped(imageObject,
                                newOx,
                                newOy,
                                (int)(hposition_+0.5),
                                (int)(vposition_+0.5),
                                neww,
                                newh);
                d = 0;
            }
        } else {
            dbgprintf("            one of imgdims > appMax\n");
            // this is costing far less memory
            if ((imw >= visibleW) && (imh >= visibleH)) {
                oldw = -1; // reset the white box condition
                dbgprintf("                 draw_cliped_nobuf, imw & h > visible\n");
                fl_draw_clipped_nobuf(imageObject,
                                      Ox, Oy,
                                      (int)(hposition_+0.5),
                                      (int)(vposition_+0.5),
                                      visibleW, visibleH);
            } else {
                // center image
                int newOx = (Ox + visibleW - imw)/2;
                newOx = trivmax(newOx, Ox);
                int newOy = (Oy + visibleH - imh)/2;
                newOy = trivmax(newOy, Oy);
                int neww = trivmin(imw, visibleW);
                int newh = trivmin(imh, visibleH);
		
                dbgprintf("                 imw or h <= visible\n");
                dbgprintf("                 NewOx=%d, newOy=%d\n", newOx, newOy);
                // draw a white box if necessary
                if ((newOx != oldOx) || (newOy != oldOy)
                    || (neww != oldw) || (newh != oldh) 
                    || redrawBackground || (d != 32)) {
                    dbgprintf("                      quick fill (white box)\n");
                    fl_color(FL_BLACK);
                    // fill whole window
                    fl_rectf(Ox,Oy,parentWindow->w(),parentWindow->h()); // quick fill
                    //fl_color(FL_WHITE);
                    //fl_rect(newOx-1, newOy-1,imw+2, imh+2); 
                    oldOx = newOx;
                    oldOy = newOy;
                    oldw = neww;
                    oldh = newh;
                }
                dbgprintf("                 draw_clipped_nobuf\n");
                fl_draw_clipped_nobuf(imageObject,
                                      newOx,
                                      newOy,
                                      (int)(hposition_+0.5),
                                      (int)(vposition_+0.5),
                                      neww,
                                      newh);
                d = 0;
            }
        }
			
    } else  {
        dbgprintf("        (zoomfactor != 1 || !useOffscreenBuff)\n");
        // this works for all zoom factors > 0.0...
        if ((imw >= visibleW) && (imh >= visibleH)) {
            oldw = -1; // reset the white box condition
            dbgprintf("             fl_draw_zcnb -- imw and h > visible\n");
            fl_draw_zcnb(imageObject,
                         Ox, Oy, visibleW, visibleH,
                         (int)(hposition_/zoomFactor()),
                         (int)(vposition_/zoomFactor()),
                         (int)(visibleW/zoomFactor() + 0.5),
                         (int)(visibleH/zoomFactor() + 0.5),
                         smooth);
        } else { // white box
            // center the image
            int newOx = (Ox + visibleW - imw)/2;
            newOx = trivmax(newOx, Ox);
            int newOy = (Oy + visibleH - imh)/2;
            newOy = trivmax(newOy, Oy);
            int neww = trivmin(imw, visibleW);
            int newh = trivmin(imh, visibleH);
		
            dbgprintf("             imw or h <= visible\n");
            // draw a white box if necessary
            dbgprintf("             NewOx=%d, newOy=%d\n", newOx, newOy);
            if ((newOx != oldOx) || (newOy != oldOy)
                || (neww != oldw) || (newh != oldh)
                || redrawBackground || (d != 32)) {
                dbgprintf("             draw white box\n");
                fl_color(FL_BLACK);
                // fill whole window
                fl_rectf(Ox,Oy,parentWindow->w(),parentWindow->h()); // quick fill
                //fl_color(FL_WHITE);
                //fl_rect(newOx-1, newOy-1,imw+2, imh+2); 
                oldOx = newOx;
                oldOy = newOy;
                oldw = neww;
                oldh = newh;
            }
            dbgprintf("             fl_draw_zcnb\n");
            fl_draw_zcnb(imageObject,
                         newOx,
                         newOy,
                         neww,
                         newh,
                         (int)(hposition_/zoomFactor()),
                         (int)(vposition_/zoomFactor()), 
                         (int)(neww/zoomFactor() + 0.5),
                         (int)(newh/zoomFactor() + 0.5),
                         smooth);
            d = 0;
        }
    }
    nbPointDamage = 0; // reset point semaphore
    // reset the drawn status of lines linking point
    PtFileMngr->resetLinesStatus();
    // reset the drawn status of profile line when we want it sticky
    if (angleLine && keepProfileLine)
        angleLine->drawnStatus(0);
}

// draw an image that fits the window as best we can.
void imageViewer::drawImage_win_fit_img(int imw, 
                                        int imh, 
                                        int imd, 
                                        bool redrawBackground, 
                                        bool smooth,
                                        int visibleW, 
                                        int visibleH, 
                                        int &d) // careful -- r/w
{
    static int oldOx = -1, oldOy = -1, oldw = -1, oldh = -1;
		
    dbgprintf("    drawImage:\n");

    if ((zoomFactor() == 1.0) && useOffscreenBuff) { // we use hand-made double buffering.
        dbgprintf("        (zoomfactor == 1 && useOffscreenBuff)\n");
        if ((imageObject->w() <= appMaxWidth)
            && (imageObject->h() <= appMaxHeight)) {
            // this is noticeable faster than
            // the generic function for any zoom factor
            // we allow
            dbgprintf("            imgdims <= appMax\n");
            if ((imw >= visibleW) && (imh >= visibleH)) {
                oldw = -1; // reset the white box condition
                dbgprintf("            fl_draw_clipped -- imw and h >= visible\n");
                fl_draw_clipped(imageObject,
                                Ox, Oy,
                                (int)(hposition_+0.5),
                                (int)(vposition_+0.5),
                                visibleW, visibleH);
            } else {
                // center image
                int newOx = Ox + (visibleW - imw)/2;
                newOx = trivmax(newOx, Ox);
                int newOy = Oy + (visibleH - imh)/2;
                newOy = trivmax(newOy, Oy);
                int neww = trivmin(imw, visibleW);
                int newh = trivmin(imh, visibleH);
                
		dbgprintf("            imw or h < visible\n");			    
                dbgprintf("            NewOx=%d, newOy=%d\n", newOx, newOy);
                // draw a white box if necessary
                if ((newOx != oldOx) || (newOy != oldOy)
                    || (neww != oldw) || (newh != oldh)
                    || redrawBackground || (d != 32)) {
                    dbgprintf("                draw white box, quick fill\n");
                    fl_color(FL_WHITE);
                    fl_rectf(Ox,Oy,w(),h()); // quick fill
                    fl_color(FL_BLACK);
                    fl_rect(newOx-1, newOy-1,imw+2, imh+2); 
                    oldOx = newOx;
                    oldOy = newOy;
                    oldw = neww;
                    oldh = newh;
                }
                dbgprintf("            fl_draw_clipped\n", newOx, newOy);
                fl_draw_clipped(imageObject,
                                newOx,
                                newOy,
                                (int)(hposition_+0.5),
                                (int)(vposition_+0.5),
                                neww,
                                newh);
                d = 0;
            }
        } else {
            dbgprintf("            one of imgdims > appMax\n");
            // this is costing far less memory
            if ((imw >= visibleW) && (imh >= visibleH)) {
                oldw = -1; // reset the white box condition
                dbgprintf("                 draw_cliped_nobuf, imw & h > visible\n");
                fl_draw_clipped_nobuf(imageObject,
                                      Ox, Oy,
                                      (int)(hposition_+0.5),
                                      (int)(vposition_+0.5),
                                      visibleW, visibleH);
            } else {
                // center image
                int newOx = Ox + (visibleW - imw)/2;
                newOx = trivmax(newOx, Ox);
                int newOy = Oy + (visibleH - imh)/2;
                newOy = trivmax(newOy, Oy);
                int neww = trivmin(imw, visibleW);
                int newh = trivmin(imh, visibleH);
		
                dbgprintf("                 imw or h <= visible\n");
                dbgprintf("                 NewOx=%d, newOy=%d\n", newOx, newOy);
                // draw a white box if necessary
                if ((newOx != oldOx) || (newOy != oldOy)
                    || (neww != oldw) || (newh != oldh) 
                    || redrawBackground || (d != 32)) {
                    dbgprintf("                      quick fill (white box)\n");
                    fl_color(FL_WHITE);
                    fl_rectf(Ox,Oy,w(),h()); // quick fill
                    fl_color(FL_BLACK);
                    fl_rect(newOx-1, newOy-1,imw+2, imh+2); 
                    oldOx = newOx;
                    oldOy = newOy;
                    oldw = neww;
                    oldh = newh;
                }
                dbgprintf("                 draw_clipped_nobuf\n");
                fl_draw_clipped_nobuf(imageObject,
                                      newOx,
                                      newOy,
                                      (int)(hposition_+0.5),
                                      (int)(vposition_+0.5),
                                      neww,
                                      newh);
                d = 0;
            }
        }
			
    } else  {
        dbgprintf("        (zoomfactor != 1 || !useOffscreenBuff)\n");
        // this works for all zoom factors > 0.0...
        if ((imw >= visibleW) && (imh >= visibleH)) {
            oldw = -1; // reset the white box condition
            dbgprintf("             fl_draw_zcnb -- imw and h > visible\n");
            fl_draw_zcnb(imageObject,
                         Ox, Oy, visibleW, visibleH,
                         (int)(hposition_/zoomFactor()),
                         (int)(vposition_/zoomFactor()),
                         (int)(visibleW/zoomFactor() + 0.5),
                         (int)(visibleH/zoomFactor() + 0.5),
                         smooth);
        } else { // white box
            // center the image
            
            int newOx = Ox + (visibleW - imw)/2;
            newOx = trivmax(newOx, Ox);
            int newOy = Oy + (visibleH - imh)/2;
            newOy = trivmax(newOy, Oy);
            int neww = trivmin(imw, visibleW);
            int newh = trivmin(imh, visibleH);
		
            dbgprintf("             imw or h <= visible\n");
            // draw a white box if necessary
            dbgprintf("             NewOx=%d, newOy=%d\n", newOx, newOy);
            if ((newOx != oldOx) || (newOy != oldOy)
                || (neww != oldw) || (newh != oldh)
                || redrawBackground || (d != 32)) {
                dbgprintf("             draw white box\n");
                fl_color(FL_WHITE);
                fl_rectf(Ox,Oy,w(),h()); // quick fill
                fl_color(FL_BLACK);
                fl_rect(newOx-1, newOy-1,imw+2, imh+2); 
                oldOx = newOx;
                oldOy = newOy;
                oldw = neww;
                oldh = newh;
            }
            dbgprintf("             fl_draw_zcnb\n");
            fl_draw_zcnb(imageObject,
                         newOx,
                         newOy,
                         neww,
                         newh,
                         (int)(hposition_/zoomFactor()),
                         (int)(vposition_/zoomFactor()), 
                         (int)(neww/zoomFactor() + 0.5),
                         (int)(newh/zoomFactor() + 0.5),
                         smooth);
            d = 0;
        }
    }
    nbPointDamage = 0; // reset point semaphore
    // reset the drawn status of lines linking point
    PtFileMngr->resetLinesStatus();
    // reset the drawn status of profile line when we want it sticky
    if (angleLine && keepProfileLine)
        angleLine->drawnStatus(0);
}

void imageViewer::draw()
{
    int imw,imh,imd;
    int i;
    int d = damage();
    bool    redrawBackground;
    bool smooth;

    if (!parentWindow) // nothing to see
        return;

    if (hideMainMenu) {
        //position(x(), 0);
        Oy = 0;
    } else {
        //position(x(), MENUHEIGHT);
        Oy = MENUHEIGHT;
    }

    dbgprintf("Drawing the image now\n");
    
    if (d == FL_DAMAGE_ALL) {
	d = 32 ;  // 32 redraws absolutely everything...
	redrawBackground = true;
	dbgprintf("Damage is FL_DAMAGE_ALL, reduced to 32\n");
    } else {
	redrawBackground = false;
    }
    if ((d & 32) || (d & 16) || (d & 8) || (d & 4)) {

	// get the smooth preference issue sorted out
	if (fileprefs)
	    smooth = fileprefs->smoothZoomout(); // use the users' pref when available
	else
	    smooth = false;  // by default do not draw smoothly when (un)zooming
	

        // redraw parts or all of the image:
        if (imageObject) {
	    imw = currentImageWidth();
	    imh = currentImageHeight();
	    imd = currentImageDepth();

	    if (getdisplaymode() == IMV_DISPLAY_IMG_FIT_WINDOW) {
		dbgprintf("Draw to fit windows w=%d, h=%d\n",
			  w(), h());
		fl_draw_zcnb(imageObject,
			     Ox, Oy, w(), h(),
			     0, 0, imw, imh, smooth);
                xZoomFactor_ = w()/(double)imw;
                yZoomFactor_ = h()/(double)imh;
	    } else {
                int visibleW = visibleWidth();
                int visibleH = visibleHeight();
                dbgprintf("VisibleW = %d, visibleH =%d\n", visibleW, visibleH);
                if (getdisplaymode() == IMV_DISPLAY_WINDOW_FIT_IMG)
                    drawScrollbars_win_fit_img(imw, imh, visibleW, visibleH);
                else if (getdisplaymode() == IMV_DISPLAY_DECOUPLE_WIN_IMG)
                    drawScrollbars_decoupled(imw, imh, parentWindow->w(), parentWindow->h());

	    
		if (d & 32 || (imw < visibleW) || (imh < visibleH)) {
                    // complete redraw of the image
                    if (getdisplaymode() == IMV_DISPLAY_WINDOW_FIT_IMG)
                        drawImage_win_fit_img(imw, imh, imd, 
                                              redrawBackground, smooth,
                                              visibleW, visibleH,
                                              d);
                    else if (getdisplaymode() == IMV_DISPLAY_DECOUPLE_WIN_IMG)
                        drawImage_decoupled(imw, imh, imd, 
                                            redrawBackground, smooth,
                                            parentWindow->w(), parentWindow->h(),
                                            d);
                }
		if (d & 16) {
		    // only redraws the top of the image (where the information box was)
		    if (zoomFactor() == 1.0) {
			fl_draw_clipped_nobuf(imageObject,
					      Ox, Oy,
					      (int)(hposition_+0.5),
					      (int)(vposition_+0.5),
					      visibleW, STATUS_HEIGHT);
		    } else if (zoomFactor() > 1.0 ) {
			fl_draw_zcnb(imageObject,
				     Ox,
				     Oy,
				     visibleW,
				     STATUS_HEIGHT,
				     (int)(hposition_/zoomFactor()),
				     (int)(vposition_/zoomFactor()),
				     (int)(visibleW/zoomFactor() + 0.5),
				     (int)(STATUS_HEIGHT/zoomFactor() + 0.5),
				     smooth);
		    } else {
			// unfortunately, in this particular situation the zoom is broken.
			// we need to redraw the whole thing
			fl_draw_zcnb(imageObject,
				     Ox, Oy, visibleW, visibleH,
				     (int)(hposition_/zoomFactor()),
				     (int)(vposition_/zoomFactor()),
				     (int)(visibleW/zoomFactor() + 0.5),
				     (int)(visibleH/zoomFactor() + 0.5),
				     smooth);
		    }
		}

		if (d & 8) {
		    // only redraws the bottom of the image (where the information box was)
		    if (zoomFactor() == 1.0) {
			fl_draw_clipped_nobuf(imageObject,
					      Ox,
					      Oy+visibleHeight()-STATUS_HEIGHT,
					      (int)(hposition_+0.5),
					      (int)(vposition_+visibleHeight()-STATUS_HEIGHT+0.5),
					      visibleW, STATUS_HEIGHT);
		    } else {
			fl_draw_zcnb(imageObject,
				     Ox,
				     Oy+visibleHeight()-STATUS_HEIGHT,
				     visibleW,
				     STATUS_HEIGHT,
				     (int)(hposition_/zoomFactor() + 0.5),
				     (int)((vposition_+visibleHeight()-STATUS_HEIGHT)/zoomFactor() +0.5),
				     (int)(visibleW/zoomFactor() + 0.5),
				     (int)(STATUS_HEIGHT/zoomFactor() + 0.5),
				     smooth);
		    }
		}


		if (d & 4) {
		    // delete point from pointfiles
		    dbgprintf("Actually deleting point at %d,%d\n", delPtX, delPtY);
		    if (zoomFactor() == 1.0) {
			fl_draw_clipped_nobuf(imageObject,
					      (int)(Ox - hposition_ + delPtX-4 + 0.5),
					      (int)(Oy - vposition_ + delPtY-4 + 0.5),
					      delPtX - 4,
					      delPtY - 4,
					      9, 9);
		    } else if (zoomFactor() > 1.0) {
			int minx, miny;
			int wWin, hWin;
			minx = delPtX - 4;
			miny = delPtY - 4;
			wWin = 9;
			hWin = 9;
			fl_draw_zcnb(imageObject,
				     xOnWindow(minx-0.5),
				     yOnWindow(miny-0.5),
				     (int)(wWin*zoomFactor()+0.5),
				     (int)(hWin*zoomFactor()+0.5),
				     minx,
				     miny,
				     wWin,
				     hWin,
				     smooth);
		    } else {
			int minx, miny;
			int wWin, hWin;
			minx = delPtX - (int)(4/zoomFactor()+0.5);
			miny = delPtY - (int)(4/zoomFactor()+0.5);
			wWin = 9;
			hWin = 9;
			fl_draw_zcnb(imageObject,
				     xOnWindow(minx-0.5),
				     yOnWindow(miny-0.5),
				     wWin,
				     hWin,
				     minx,
				     miny,
				     (int)(wWin/zoomFactor()+0.5),
				     (int)(hWin/zoomFactor()+0.5),
				     smooth);
		    }
		    nbPointDamage--;

		}
	    }
	}
	// draws the children
	dbgprintf("Redrawing %d children\a", children());
	for (i = 0 ; i < children() ; i ++) {
	    if (child(i)->visible()) {
		dbgprintf(" %d...", i);
		child(i)->redraw();
		child(i)->draw();
	    }
	}
	dbgprintf(" done\b");
    } else if (d & 1) {      // only redraw the children that need it
	//dbgprintf("Damage(1)\n");
        dbgprintf("Only redrawing %d children\a", children());
	for (i = 0 ; i < children() ; i++) {
	    if (child(i)->visible()) {
                dbgprintf(" %d...", i);
		child(i)->redraw();
		child(i)->draw();
	    }
	}
    }

    // update the image information if possible
    if (imageInfoPanel)
	imageInfoPanel->update();
    
    return;
}



// this is now much easier to write since 0.96
int imageViewer::handle(int event)
{
    int  retval = 0;
    static int  button = 0;
    static bool pushedHere = false; // we want to drag the scrollbar only if the push occured in the scrollbar.

    //dbgprintf("Image Viewer: Got event %d"
    //       " Mouse position: x=%d, y=%d\n", 
    //       event, Fl::event_x(), Fl::event_y());

    // got to figure out where the scrollbars are.
    if (vscrollbar && vscrollbar->visible() && (Fl::event_inside(vscrollbar))) {
	if ((event != FL_PUSH) && pushedHere) {
	    if (event == FL_DRAG) {
		if (Fl::event_state(FL_BUTTON1)) {
		    handleLeftButtonDragged();
		    retval = 1;
		} else if (Fl::event_state(FL_BUTTON2)) {
		    handleMiddleButtonDragged();
		    retval = 1;
		} else if (Fl::event_state(FL_BUTTON3)) {
		    // do nothing
		    retval = 1;
		}
	    } else if (event == FL_RELEASE) {
		dbgprintf("Release, event state %d\n", Fl::event_state());
		// in this instance, Fl::event_state() doesn't help
		button = Fl::event_button();
		if (button == LEFT_BUTTON) {
		    handleLeftButtonReleased();
		    retval = 1;
		} else if (button == MIDDLE_BUTTON) {
		    handleMiddleButtonReleased();
		    retval = 1;
		} else if (button == RIGHT_BUTTON) {
		    handleRightButtonReleased();
		    retval = 1;
		}
	    } 
	} else {
	    // simply returning 0 and hoping for the best will not be suffficient
	    // for the children to handle what they need to handle
	    if (event == FL_PUSH) pushedHere = false; // push event occurs in the scroll bars
	    // the documentation says explicitely to call this:
	    if (Fl_Group::handle(event)) return 1;
	    // verify that no stone has been left unturned
	    //handleLeftButtonReleased();
	} 
    } else if (hscrollbar && hscrollbar->visible() && (Fl::event_inside(hscrollbar))) {
	if ((event != FL_PUSH) && pushedHere) {
	    if (event == FL_DRAG) {
		if (Fl::event_state(FL_BUTTON1)) {
		    handleLeftButtonDragged();
		    retval = 1;
		} else if (Fl::event_state(FL_BUTTON2)) {
		    handleMiddleButtonDragged();
		    retval = 1;
		} else if (Fl::event_state(FL_BUTTON3)) {
		    // do nothing
		    retval = 1;
		}
	    } else if (event == FL_RELEASE) {
		button = Fl::event_button();
		if (button == LEFT_BUTTON) {
		    handleLeftButtonReleased();
		    retval = 1;
		} else if (button == MIDDLE_BUTTON) {
		    handleMiddleButtonReleased();
		    retval = 1;
		} else if (button == RIGHT_BUTTON) {
		    handleRightButtonReleased();
		    retval = 1;
		}
	    }
	} else {
	    if (event == FL_PUSH) pushedHere = false; // push event occurs in the scroll bars
	    // verify that no stone has been left unturned
	    // the documentation says explicitely to call this:
	    if (Fl_Group::handle(event)) return 1;
	    // verify that no stone has been left unturned
	    //handleLeftButtonReleased();
	}
    } else if (imageObject) { // otherwise there is nothing to do
        // we have to take care of the event by ourselves
        switch (event) {
          case FL_PUSH:
	    //button = Fl::event_button();
	    pushedHere = true;
	    if (Fl::event_state(FL_BUTTON1)) {
		handleLeftButtonPushed();
		retval = 1;
	    } else if  (Fl::event_state(FL_BUTTON2)) {
		handleMiddleButtonPushed();
		retval = 1;
	    } else if (Fl::event_state(FL_BUTTON3)) {
		// we do nothing, but we handle the event
		retval = 1;
	    }
	    break;
            
          case FL_DRAG:
	    if (Fl::event_state(FL_BUTTON1)) {
		handleLeftButtonDragged();
		retval = 1;
	    } else if  (Fl::event_state(FL_BUTTON2)) {
		handleMiddleButtonDragged();
		retval = 1;
	    } else if (Fl::event_state(FL_BUTTON3)) {
		// we still do nothing, but we expect the release event!
		retval = 1;
	    }
	    break;

          case FL_RELEASE:
	    pushedHere = false;
	    button = Fl::event_button();
	    if (button == LEFT_BUTTON) {
		handleLeftButtonReleased();
		retval = 1;
	    } else if (button == MIDDLE_BUTTON) {
		handleMiddleButtonReleased();
		retval = 1;
	    } else if (button == RIGHT_BUTTON) {
		handleRightButtonReleased();
		retval = 1;
	    }
            break;

	  case FL_ENTER:
	    // Mouse focus, change the cursor to the local default
	    chg_cursor(MY_CURSOR_DEFAULT);
	    retval = 1;
	    break;

	  case FL_MOVE:
	  { pointermode m = getpointermode(0);
	  
	  if (Fl::event_state(FL_SHIFT)
	      || (m == IMV_POINTER_EDITPT_MODE)
	      || (m == IMV_POINTER_DELPT_MODE)
	      || (m == IMV_POINTER_DELGRP_MODE)) {
	      // inexpensive calculation first
	      int x, y, X, Y;
	      x = Fl::event_x();
	      y = Fl::event_y();
		
	      // make sure we stay in the image
	      if (x < Ox)
		  x = Ox;
	      if (y < Oy)
		  y = Oy;
	      if (x < Ox) x = Ox;
	      if (x >= visibleWidth()+Ox) x = visibleWidth()+Ox -1;
	      if (y < Oy) y = Oy;
	      if (y >= visibleHeight()+Oy) y = visibleHeight()+Oy -1;
	    
	      X = xInImage(x); // convert local coordinates to image ones.
	      Y = yInImage(y);

		
	      if (Fl::event_state(FL_CTRL)
		  || Fl::event_state(FL_ALT)
		  || (m == IMV_POINTER_EDITPT_MODE)
		  || (m == IMV_POINTER_DELPT_MODE)
		  || (m == IMV_POINTER_DELGRP_MODE)) {
		  // expensive bit here
		  if (PtFileMngr->isValidPoint(X,Y)) {
		      if (Fl::event_state(FL_CTRL) || 
			  (m == IMV_POINTER_EDITPT_MODE)) // re-test
			  chg_cursor(MY_CURSOR_INSERT);
		      else
			  chg_cursor(MY_CURSOR_KILL);
		  } else { // not near a valid poing
		      if (m == IMV_POINTER_EDITPT_MODE)
			  chg_cursor(MY_CURSOR_RHAND); // inverted contrast
		      else if ((m == IMV_POINTER_DELPT_MODE)
			       || (m == IMV_POINTER_DELGRP_MODE))
			  chg_cursor(MY_CURSOR_LHAND);
		      else
			  chg_cursor(MY_CURSOR_DEFAULT);
		  }
	      } 
	  } else {
	      // more mundane modes (no need to look if close to a point)
	      switch (m) {
              case IMV_POINTER_SELECT_MODE:
		  chg_cursor(MY_CURSOR_SELECT);
		  break;
              case IMV_POINTER_MEASURE_MODE:
		  chg_cursor(MY_CURSOR_MEASURE);
		  break;
              case IMV_POINTER_ADDPT_MODE:
		  chg_cursor(MY_CURSOR_DOT);
		  break;
              case IMV_POINTER_DRAWPT_MODE:
                  chg_cursor(MY_CURSOR_DRAW);
                  break;
              default:
		  chg_cursor(MY_CURSOR_DEFAULT);
		  break;
	      }
	  }
	  }
	  break;

	  case FL_LEAVE:
	    // Return to normal cursor
	    chg_cursor(FL_CURSOR_DEFAULT);
	    retval = 1;
	    break;

	  case FL_FOCUS:
	    dbgprintf("Image viewer has keyboard focus\n");
	    retval = 1; // we want the focus!
	    break;

	  case FL_UNFOCUS:
	    dbgprintf("Image viewer focus released\n");
	    retval = 1; // we release the focus
	    break;

	  case FL_SHORTCUT:
	    dbgprintf("Event is a shortcut\n");
	  case FL_KEYBOARD:
	    // try to handle the event, and if not, send it to the children
	    if ((retval = handleKeyboardEvent()) == 0)
		retval = Fl_Group::handle(event);
	    break;
        }
    }


    return retval;
}

// private methods

static void hscrollbar_callback(Fl_Object *hs,void *) {
    int localpos = int(((Fl_Slider *)hs)->value());

    //dbgprintf("Horizontal scrollbar callback called, local pos = %d\n", localpos);
    ((imageViewer *)(hs->parent()))->hposition(localpos);
}

static void vscrollbar_callback(Fl_Object *vs,void *) {
    int localpos = int(((Fl_Slider *)vs)->value());
    //dbgprintf("Vertical scrollbar callback called, local pos = %d\n", localpos);
    ((imageViewer *)(vs->parent()))->vposition(localpos);
}

// returns true if resize does occur
bool imageViewer::sizeParent(int sizeX, int sizeY)
{
    static int oldSizeX = -1, oldSizeY = -1;
    static int realSizeX, realSizeY;
    bool   retval = false;

    if (getdisplaymode() != IMV_DISPLAY_DECOUPLE_WIN_IMG) {
	realSizeX = trivmax(sizeX, Ox + MINWIDTH);
	realSizeY = trivmax(sizeY, Oy + MINHEIGHT);
    
	// do the resize only if needed
	if ((realSizeX != oldSizeX) || (realSizeY != oldSizeY)
	    || (realSizeX != parentWindow->w()) || (realSizeY != parentWindow->h())) {
	    dbgprintf("Resizing parent window to [%d]x[%d]\n", realSizeX, realSizeY);
	    parentWindow->size(realSizeX, realSizeY);
	    oldSizeX = realSizeX;
	    oldSizeY = realSizeY;
	    retval = true;
	} 
    }
    
    return retval;
}

void imageViewer::setdisplaymode(displaymode m)
{
    dmode_ = m;

    switch (m) {
    case IMV_DISPLAY_WINDOW_FIT_IMG:
        allowSizeConstraints(true);
        break;
    default:
        allowSizeConstraints(false);
        break;
    }

    return;
}

// allow or disallows size constraints
void imageViewer::allowSizeConstraints(bool tf)
{
    if (tf) {
        sizeConstrained = true;
        resize();
    } else {
        sizeConstrained = false;
        // might be dangerous...
        parentWindow->size_range(MINWIDTH,MINHEIGHT,0,0);
    }
    return;
}

// handles the max/min size constraints
void imageViewer::sizeConstraints(int maxwidth, int maxheight)
{
    int windowMaxWidth, windowMaxHeight;
    int windowMinWidth, windowMinHeight;
    static int oldMaxWidth = -1, oldMaxHeight = -1;
    static int oldMinWidth = -1, oldMinHeight = -1;

    if (getdisplaymode() != IMV_DISPLAY_WINDOW_FIT_IMG) {
	dbgprintf("Display mode not compatible with constraints\n");
	parentWindow->size_range(MINWIDTH,MINHEIGHT,0,0);
        return; // no constraint here
    }
    
    windowMaxWidth = Ox + maxwidth;
    windowMaxHeight = Oy + maxheight;
    windowMinWidth = Ox+MINWIDTH;
    windowMinHeight = Oy+MINHEIGHT;

    // now weird things can happen if the max size as given
    // above are larger than the minimum size, which can
    // happen if the zoom factor is large and the image quite small...
    if (windowMaxWidth <= windowMinWidth) {
	dbgprintf("Rounding error in window width measurement:%d\n", windowMaxWidth);
	// MinWidth is not all that important as long at the menu can still be seen
	windowMaxWidth = windowMinWidth + 2;
    }
    
    if (windowMaxHeight <= windowMinHeight) {
	dbgprintf("Rounding error in window height measurement:%d\n", windowMaxHeight);
	// minheight is more critical as it is much smaller
	windowMaxHeight = windowMinHeight + 2;
    }

    if ((windowMaxWidth != oldMaxWidth)
	|| (windowMaxHeight != oldMaxHeight)
	|| (windowMinWidth != oldMinWidth)
	|| (windowMinHeight != oldMinHeight)) {
	// we need to update the limits
	dbgprintf("Setting up limits to: minw= %d, minh= %d, maxw = %d, minw = %d\n",
		  windowMinWidth, windowMinHeight,
		  windowMaxWidth, windowMaxHeight);
	
	parentWindow->size_range(windowMinWidth,
				 windowMinHeight,
				 windowMaxWidth,
				 windowMaxHeight);
	oldMaxWidth = windowMaxWidth;
	oldMaxHeight = windowMaxHeight;
	oldMinWidth = windowMinWidth;
	oldMinHeight = windowMinHeight;
    } else {
	dbgprintf("Windows limits unchanged\n");
    }
    return;
}

// return the position and dimensions of the subset
// of the image currently being displayed.
void imageViewer::getCurrentBox(int &bx, int &by, int &bw, int &bh)
{
    // this is now not so bad
    if (zoomDragInProgress) {
	bx = dragX;
	by = dragY;
	bw = dragW;
	bh = dragH;
    } else {
	if ((currentImageWidth() >= visibleWidth())
	    && (currentImageHeight() >= visibleHeight())) {
	    bx = (int)(hposition_/zoomFactor());
	    by = (int)(vposition_/zoomFactor());
	    bw = (int)(visibleWidth()/zoomFactor() + 0.5);
	    bh = (int)(visibleHeight()/zoomFactor() + 0.5);
	} else {
	    int neww = trivmin(currentImageWidth(), visibleWidth());
	    int newh = trivmin(currentImageHeight(), visibleHeight());
	    bx = (int)(hposition_/zoomFactor());
	    by = (int)(vposition_/zoomFactor());
	    bw = (int)(neww/zoomFactor() + 0.5);
	    bh = (int)(newh/zoomFactor() + 0.5);
	}
    }
    
    return;
}

void imageViewer::removeLine(void)
{
    keepProfileLine = false;
    if (angleLine != 0) {
	// remove the last line
	parentWindow->make_current();
	angleLine->undraw();
	remove(angleLine);
	delete angleLine;
	angleLine = 0;
    }
    return;
}

// the X and Y position in the image are difficult to
// compute from the position of the cursor, because an integer
// number of pixels is always displayed, even at very high magnifications.

// 18 Jul 1998: this has become even more difficult now that the actual
// image may be lost in the middle of the display area at low magnification,
// of for small images.

// computes the x position in the image given a X position on the window
int imageViewer::xInImage(int x) {
    int imw = currentImageWidth();
    int visibleW = visibleWidth();
    int newWidth = trivmin(imw, visibleW);
    int newOx = Ox + (visibleW - imw)/2;
    newOx = trivmax(newOx, Ox);
    
    //double realxf = (double)visibleW/((int)(visibleW/zoomFactor() + 0.5));
    double realxf = (double)newWidth/((int)(newWidth/zoomFactor() + 0.5));
    return ((int)((x - newOx)/realxf) + (int)(hposition_ / zoomFactor()));
}

// computes the X position on the window given a x position in the image
int imageViewer::xOnWindow(double X) {
    double realxf = (double)visibleWidth()/((int)(visibleWidth()/zoomFactor() + 0.5));
    int    woffset = Ox + (visibleWidth() - currentImageWidth())/2;
    woffset = trivmax(woffset, Ox);
    return((int)(realxf *((X+0.5) - (int)(hposition_/zoomFactor())) + woffset));
}

// Computes the x position in the window given an x position in the window !
inline int imageViewer::xWtoW(int x) {
    double realxf = (double)visibleWidth()/((int)(visibleWidth()/zoomFactor() + 0.5));
    return((int)(realxf * (int)((x - Ox)/realxf)) + Ox);
}

// adjust a width in the window to fall on an integer number of pixels.
inline int imageViewer::adjW(int w) {
    double realxf = (double)visibleWidth()/((int)(visibleWidth()/zoomFactor() + 0.5));
    return((int)(realxf * (int)(w/realxf + 1) + 0.5));
}

// computes the y position in the image given a Y position on the window
inline int  imageViewer::yInImage(int y) {
    int imh = currentImageHeight();
    int visibleH = visibleHeight();
    int newHeight = trivmin(imh, visibleH);
    int newOy = Oy + (visibleH - imh)/2;
    newOy = trivmax(newOy, Oy);
    
    //double realyf = (double)visibleHeight()/((int)(visibleHeight()/zoomFactor() + 0.5));
    double realyf = (double)newHeight/((int)(newHeight/zoomFactor() + 0.5));
    return ((int)((y - newOy)/realyf) + (int)(vposition_ / zoomFactor()));
}

// computes the Y position on the window given a y position in the image
int imageViewer::yOnWindow(double Y) {
    double realyf = (double)visibleHeight()/((int)(visibleHeight()/zoomFactor() + 0.5));
    int    hoffset = Oy + (visibleHeight() - currentImageHeight())/2;
    hoffset = trivmax(hoffset, Oy);
    return((int)(realyf *((Y+0.5) - (int)(vposition_/zoomFactor())) + hoffset));
}

// computes the y position in the window given a y position in the window !
inline int  imageViewer::yWtoW(int y) {
    double realyf = (double)visibleHeight()/((int)(visibleHeight()/zoomFactor() + 0.5));
    return((int)(realyf * (int)((y - Oy)/realyf)) + Oy);
}

// adjust a height in the window to fall on an integer number of pixels
inline int imageViewer::adjH(int h) {
    double realyf = (double)visibleHeight()/((int)(visibleHeight()/zoomFactor() + 0.5));
    return((int)(realyf * (int)(h/realyf + 1) + 0.5));
}

inline int imageViewer::wInImage(int w) {
    double realxf = (double)visibleWidth()/((int)(visibleWidth()/zoomFactor() + 0.5));
    return ((int)(w/realxf + 0.5));
}

inline int imageViewer::hInImage(int h) {
    double realyf = (double)visibleHeight()/((int)(visibleHeight()/zoomFactor() + 0.5));
    return ((int)(h / realyf + 0.5));
}


/////////////////////
/// Left button operations


void imageViewer::handleLeftButtonPushed() {
    int         x, y, xs, ys, ws, hs;
    pointermode  currentMode = getpointermode(LEFT_BUTTON);

    // create the status line
    x = Fl::event_x();
    y = Fl::event_y();
    xs = Ox;
    ws = visibleWidth();
    hs = STATUS_HEIGHT;  // defined in imageViewer.hxx
    if (y > visibleHeight()/2) {
	ys = Oy;
    } else {
	ys = Oy+visibleHeight()-STATUS_HEIGHT;
    }

    // quick fix to a slightly unusual problem:
    // previous status line had not been deleted!
    if (statusLine) {
	if (statusLine->y() == Oy)
	    damage(16); // redisplay top of the image
	else
	    damage(8); // redisplay bottom of the image
	remove(statusLine);
	delete statusLine;
	statusLine = 0;
    }

    if (currentMode != IMV_POINTER_SELECT_MODE) {
	statusLine = new Fl_Output(xs, ys,
				   ws, hs,
				   0);  // no label

	// mode handling
	switch (currentMode) {
        case IMV_POINTER_ADDPT_MODE:
	    // invert colours
	    statusLine->color(FL_BLACK);
	    statusLine->textcolor(FL_WHITE);
	    chg_cursor(MY_CURSOR_DOT);
	    break;
            
        case IMV_POINTER_DRAWPT_MODE:
	    // invert colours
	    statusLine->color(FL_BLACK);
	    statusLine->textcolor(FL_WHITE);
	    chg_cursor(MY_CURSOR_DRAW);
	    break;

        case IMV_POINTER_DELPT_MODE:
        case IMV_POINTER_DELGRP_MODE:
	    // invert colours
	    statusLine->color(FL_BLACK);
	    statusLine->textcolor(FL_WHITE);
	    chg_cursor(MY_CURSOR_KILL);
	    break;

        case IMV_POINTER_MEASURE_MODE:
	    lineProfileInProgress = true;
	    statusLine->color(FL_BLUE);
	    statusLine->textcolor(FL_YELLOW);
	    chg_cursor(FL_CURSOR_HAND);
	    angleStartX = x;
	    angleStartY = y;
	    break;

        default:
	    // just change the cursor to a crosshair
	    chg_cursor(MY_CURSOR_CROSS);
	    break;
	}
    
	statusLine->textfont(STATUS_FONT);
	statusLine->textsize(STATUS_SIZE);
	add(statusLine);
    } else { // select mode doesn't require the statusLine, rather, it slows things down.
	selectBoxX = x;
	selectBoxY = y;
	selectBoxDefined = 1;
	nbFlBoxDamage = 0; 
	chg_cursor(MY_CURSOR_SELECT);
    }

    // call `left button dragged' handler
    // so that the status line appears without having to wiggle the mouse around...
    handleLeftButtonDragged();
    
    return;
}


void imageViewer::handleLeftButtonDragged() {
    int x, y, X, Y, d;
    const unsigned char *p;
    unsigned char r, g, b;

    // it is possible to get there without handleLeftButtonPushed
    // having been called. This can happen if you push the left button
    // in one of the scrollbar and drag past its boundary. In this
    // case this present routine is called, but there never was any statusLine
    // allocated. 
    if (statusLine) {
	x = Fl::event_x();
	y = Fl::event_y();

	// fix up the x and y values:
	if (x < Ox)
	    x = Ox;
	if (y < Oy)
	    y = Oy;
	if (x > visibleWidth() + Ox - 1)
	    x = visibleWidth() + Ox - 1;
	if (y > visibleHeight() + Oy - 1)
	    y = visibleHeight() + Oy - 1;

	// which mode are we in
	switch (getpointermode(LEFT_BUTTON)) {
        case IMV_POINTER_ADDPT_MODE:
	    statusLine->color(FL_BLACK);
	    statusLine->textcolor(FL_WHITE);
	    chg_cursor(MY_CURSOR_DOT);
	    break;

        case IMV_POINTER_DRAWPT_MODE:
	    statusLine->color(FL_BLACK);
	    statusLine->textcolor(FL_YELLOW);
	    chg_cursor(MY_CURSOR_DRAW);
            { // add point
		pointParams mypp; // initialized with defaults

		mypp.size=1;
		mypp.pts=1;
		mypp.annotated=true;
                // draw in the current colour, increment colour at break
		mypp.col=fl_closest_index(currentPtColour_);
                X = xInImage(x); // convert local coordinates to image ones.
                Y = yInImage(y);
                d = imageObject->d();
                p = (unsigned char *)(imageObject->data()[0] + d *(X + Y * imageObject->w()));

		if (d == 3) {
		    PtFileMngr->addPoint(X,Y,theImage->getCurrZpos(),
					 *p,
					 *(p+1),
					 *(p+2),
					 theImage->hasRawData(), // is there more data yet?
					 mypp); 
		} else {
		    PtFileMngr->addPoint(X,Y,theImage->getCurrZpos(),
					 *p,
					 *p,
					 *p,
					 theImage->hasRawData(), // is there more data yet?
					 mypp);
		}
	    }
	    break;

        case IMV_POINTER_DELPT_MODE:
        case IMV_POINTER_DELGRP_MODE:
	    statusLine->color(FL_BLACK);
	    statusLine->textcolor(FL_WHITE);
	    chg_cursor(MY_CURSOR_KILL);
	    break;

        case IMV_POINTER_MEASURE_MODE:
	    statusLine->color(FL_BLUE);
	    statusLine->textcolor(FL_YELLOW);
	    chg_cursor(FL_CURSOR_HAND);
	    break;

        case IMV_POINTER_SELECT_MODE:
	    statusLine->color(FL_BLACK);
	    statusLine->textcolor(FL_WHITE);
	    chg_cursor(MY_CURSOR_SELECT);
	    break;

        default:
	    statusLine->color(FL_WHITE);
	    statusLine->textcolor(FL_BLACK);
	    chg_cursor(MY_CURSOR_CROSS);
	    break;
	}

	if (!lineProfileInProgress) {
	    // decide the content of the status line in the pointwise cases

	    X = xInImage(x); // convert local coordinates to image ones.
	    Y = yInImage(y);
	    if (X >= theImage->imageWidth())
		X = theImage->imageWidth() - 1;
	    else if (X < 0)
		X = 0;
	    if (Y >= theImage->imageHeight())
		Y = theImage->imageHeight() - 1;
	    else if (Y < 0)
		Y = 0;
	
	    d = imageObject->d();
	    // serious change in FLTK-1.1.x
	    p = (const unsigned char *)imageObject->data()[0] + d *(X + Y * imageObject->w());
	    if (d == 3) {
		r = *p;
		g = *(p+1);
		b = *(p+2);
		if (theImage->getCurrImgThickness() == 1) {
		    // no 3-D information
		    sprintf(statusMessage,"x=%d, y=%d, r=%d, g=%d, b=%d %s",
			    X, Y,
			    r, g, b,
			    theImage->getRawDataInfo(X, Y));
		} else {
		    sprintf(statusMessage,"x=%d, y=%d, z=%d, r=%d, g=%d, b=%d %s",
			    X, Y, theImage->getCurrZpos(),
			    r, g, b,
			    theImage->getRawDataInfo(X, Y));
		}
	    } else {
		g = b = r = *p;
		if (theImage->getCurrImgThickness() == 1) {
		    sprintf(statusMessage,"x=%d, y=%d, gl=%d %s",
			    X, Y,
			    r,
			    theImage->getRawDataInfo(X, Y));
		} else {
		    sprintf(statusMessage,"x=%d, y=%d, z=%d, gl=%d %s",
			    X, Y, theImage->getCurrZpos(),
			    r,
			    theImage->getRawDataInfo(X, Y));
		}
	    }
	    // hyperspectral data
	    if ((theImage->getCurrImgNbSamples() > 1) 
		&& spectraPanel 
		&& spectraPanel->visible()) {
		int nb;
		double *s;

		dbgprintf("sendind data to spectra panel\n");
		s = theImage->getRawDataVector(X,Y,&nb);
		spectraPanel->setData(s,nb, X, Y);
		spectraPanel->redraw();
	    } else {
                dbgprintf("Nbcomp=%d, spectrapanel=%p, visible=%d\n", theImage->getCurrImgNbComps(), spectraPanel, (spectraPanel) ? spectraPanel->visible():0);
            }
	    // 3D data depth profile
	    if ((theImage->getCurrImgThickness() > 1) 
		&& depthProfile 
		&& depthProfile->visible()) {
		int nb;
		double *s;
		dbgprintf("sendind data to depth profile panel\n");
		s = theImage->get3DRawDataVector(X, Y, &nb);
		depthProfile->setData(s,nb, X, Y);
		depthProfile->redraw();
	    }
	} else {
	    // decides the content of the status line in profile mode
	    int X2, Y2;
	    double angle, distance;
	    X = xInImage(x);
	    Y = yInImage(y);
	    X2 = xInImage(angleStartX);
	    Y2 = yInImage(angleStartY);
	    distance = sqrt((double)(X-X2)*(X-X2)+(Y-Y2)*(Y-Y2));
	    if ((X-X2) > 0)
		angle = atan((double)(Y2-Y)/(X-X2)) * 180.0/M_PI;
	    else if ((X-X2) < 0) {
		if (Y <= Y2)
		    angle = (atan((double)(Y2-Y)/(X-X2)) + M_PI) * 180.0/M_PI;
		else
		    angle = (atan((double)(Y2-Y)/(X-X2)) - M_PI) * 180.0/M_PI;
	    } else if ((Y-Y2) < 0)
		angle = 90.0;
	    else if ((Y-Y2) > 0)
		angle = -90;
	    else
		angle = 0;

	    sprintf(statusMessage, "dist: %7.7g, angle: %7.7g", distance, angle);

	    // remove the old line
	    if (angleLine != 0) {
		// remove the last line
		parentWindow->make_current();
		angleLine->undraw();
		remove(angleLine);
		delete angleLine;
		angleLine = 0;
	    }
	    // actually draw a line
	    angleLine = new imline(X2,
				   Y2,
				   X,
				   Y,
				   STYLE_XOR, 0x1b2dc3a4);
	    add(angleLine);

	    // display the profile
	    if (profilePanel && profilePanel->visible()) {
		dbgprintf("Sending data to the profile panel\n");
		int nb;
		double *red = 0, *green = 0, *blue = 0;
		theImage->getLineDataVectors(X2,Y2,X,Y,red,green,blue,nb);
		profilePanel->setData(red,green,blue,nb,X2,Y2,X,Y);
		profilePanel->redraw();
		if (red != 0)
		    delete[] red;
		if (green != 0)
		    delete[] green;
		if (blue != 0)
		    delete[] blue;
	    }
	}
	
	statusLine->value((const char *)statusMessage);
	damage(1);  // just redraw the children
    }

    if (selectBoxDefined && (nbFlBoxDamage == 0)) {
	x = Fl::event_x();
	y = Fl::event_y();

	if (x > visibleWidth() + Ox - 1)
	    x = visibleWidth() + Ox - 1;
	if (y > visibleHeight() + Oy - 1)
	    y = visibleHeight() + Oy - 1;

	selectBoxWidth = x - selectBoxX;
	selectBoxHeight = y - selectBoxY;
	
        // delete the old selectBox
	if (selectBox != 0) {
	    parentWindow->make_current();
	    fl_overlay_clear();
	}
        // create a new one if possible

	if ((selectBoxWidth > 0) && (selectBoxHeight > 0)) {
	    selectBox = 1;
	    parentWindow->make_current();
	    fl_overlay_rect(selectBoxX,
			    selectBoxY,
			    selectBoxWidth,
			    selectBoxHeight);
			    
	}
    }
    
    return;
}

void imageViewer::handleLeftButtonReleased() {
    // see discussion in handleLeftButtonDragged
    // about when this present routine can be called without
    // statusLine having been initialized...
    pointermode m = getpointermode(LEFT_BUTTON);
    
    if (statusLine) {
//	if (Fl::event_state(FL_SHIFT) || pointmode) {
	if ((m == IMV_POINTER_DELPT_MODE) 
            || (m == IMV_POINTER_DELGRP_MODE)
            || (m == IMV_POINTER_ADDPT_MODE) 
            || (m == IMV_POINTER_EDITPT_MODE)
            || (m == IMV_POINTER_DRAWPT_MODE)) {
	    int x, y, X, Y, d;
	    const unsigned char *p;
	    
	    x = Fl::event_x();
	    y = Fl::event_y();
	    // make sure we stay in the image
	    if (x < Ox)
		x = Ox;
	    if (y < Oy)
		y = Oy;
	    if (x < Ox) x = Ox;
	    if (x >= visibleWidth()+Ox) x = visibleWidth()+Ox -1;
	    if (y < Oy) y = Oy;
	    if (y >= visibleHeight()+Oy) y = visibleHeight()+Oy -1;
	    
	    X = xInImage(x); // convert local coordinates to image ones.
	    Y = yInImage(y);
	    d = imageObject->d();
	    // serious change in FLTK-1.1.x
	    p = (unsigned char *)(imageObject->data()[0] + d *(X + Y * imageObject->w()));  
	    //if (Fl::event_state(FL_ALT)) { // delete point
	    if (m == IMV_POINTER_DELPT_MODE) {
		PtFileMngr->rmPoint(X,Y); 
		//} else if (Fl::event_state(FL_CTRL)) { // annotate point
	    } else if (m == IMV_POINTER_DELGRP_MODE) {
                PtFileMngr->rmPointGroup(X,Y);
            } else if (m == IMV_POINTER_EDITPT_MODE) {
		if (PtFileMngr->isValidPoint(X,Y))
		    showAnnotatePanel(X,Y); // point annotation
	    } else if (m == IMV_POINTER_DRAWPT_MODE) {
                dbgprintf("Break point entered\n");
                parentWindow->make_current(); // we are drawing!
                assert(currentPtColour_ < MAX_FL_COLOURS);
                // blue is for a start
                PtFileMngr->addBreak(true, 
                                     STYLE_FILLED_COLOUR, 
                                     fl_closest_index(currentPtColour_++)); // increment colour
                currentPtColour_ %= MAX_FL_COLOURS;
            } else  { // add point
		if (d == 3) {
		    PtFileMngr->addPoint(X,Y,theImage->getCurrZpos(),
					 *p,
					 *(p+1),
					 *(p+2),
					 theImage->hasRawData()); // is there more data yet?
		} else {
		    PtFileMngr->addPoint(X,Y,theImage->getCurrZpos(),
					 *p,
					 *p,
					 *p,
					 theImage->hasRawData()); // is there more data yet?
					
		}
		//if (debugIsOn) {
		//    // draw a quick circle
		//    parentWindow->make_current();
		//    fl_circle(x,y,3);
		//}
	    } 	
	}
	if (lineProfileInProgress) {
	     // remove the existing line if needed and wanted
	    if (angleLine != 0 && !keepProfileLine) {
		// remove the last line
		parentWindow->make_current();
		angleLine->undraw();
		remove(angleLine);
		delete angleLine;
		angleLine = 0;
	    }
	    lineProfileInProgress=false;
	}
	// change the cursor back to the local default
	chg_cursor(MY_CURSOR_DEFAULT);

	// what do we need to redraw?
	if (statusLine->y() == Oy)
	    damage(16); // top of the image
	else
	    damage(8); // bottom of the image
	remove(statusLine);
	delete statusLine;
	statusLine = 0;
    }

    if (selectBoxDefined && selectBox) {
	int X,Y,W,H;

	
	parentWindow->make_current();
	fl_overlay_clear();
	selectBoxDefined = 0;
	selectBox = 0;
	// select points
	X = xInImage(selectBoxX);
	Y = yInImage(selectBoxY);
	W = wInImage(selectBoxWidth);
	H = hInImage(selectBoxHeight);

	if (Fl::event_state(FL_SHIFT)) {
	    PtFileMngr->selectPoints(X,Y,W,H, PT_SELECT_RM);
	} else if (Fl::event_state(FL_CTRL)) {
	    PtFileMngr->selectPoints(X,Y,W,H, PT_SELECT_INVERT);
	} else
	    PtFileMngr->selectPoints(X,Y,W,H, PT_SELECT_ADD);
	
	damage(1); // redraw the children
    }
    
    return;
}


////////////////////////////////////
/// Middle button operations

void imageViewer::handleMiddleButtonPushed()
{
    int x, y;

    x = Fl::event_x();
    y = Fl::event_y();
    
    zoomBoxRatio = (double)visibleHeight()/visibleWidth();
    zoomBoxX = x;
    zoomBoxY = y;
    zoomBoxDefined = 1;
    nbFlBoxDamage = 0;

    chg_cursor(MY_CURSOR_ZOOMBOX);
    
    return;
}

void imageViewer::handleMiddleButtonDragged()
{
    int x, y;

    // like for handleLeftButtonDragged, it is possible to come
    // here without handleMiddleButtonPushed having been called...
    if (zoomBoxDefined && (nbFlBoxDamage == 0)) {
	x = Fl::event_x();
	y = Fl::event_y();

	if (x > visibleWidth() + Ox - 1)
	    x = visibleWidth() + Ox - 1;
	if (y > visibleHeight() + Oy - 1)
	    y = visibleHeight() + Oy - 1;
    
	zoomBoxWidth = x - zoomBoxX;
	zoomBoxHeight = y - zoomBoxY;

	// shift_down inverts the current state
	aspectConstrained = (fileprefs->mouseZoomType() == ZOOM_CONSTRAINED);
	if (Fl::event_state(FL_SHIFT)) aspectConstrained=!aspectConstrained; // ^ doesn't work for some reason

	dbgprintf("Aspect ratio is %sconstrained\n", aspectConstrained ? "":"un");

	if (aspectConstrained) {
	    // revisit this to enforce the current aspect ratio
	    zoomBoxWidth = trivmin(zoomBoxWidth, (int)(zoomBoxHeight / zoomBoxRatio + 0.5));
	    zoomBoxHeight = trivmin(zoomBoxHeight, (int)(zoomBoxWidth * zoomBoxRatio + 0.5));
	} 

	// delete the old zoomBox if need be
	if (zoomBox != 0) {
	    parentWindow->make_current();
	    fl_overlay_clear();
	}

	// create a new one if possible
	if ((zoomBoxWidth > 0 && zoomBoxHeight > 0)) {
	    double xzoom, yzoom, zf;
	    int    bw, bh;
	    double rw, rh;
	    
	    zoomBox = 1;
	    parentWindow->make_current();
	    zoomDragInProgress = true;
	    dragX = xInImage(zoomBoxX);
	    dragY = yInImage(zoomBoxY);

	    dbgprintf("DragX = %d, DragY = %d\n", dragX, dragY);
	    
	    /* CAREFUL: same formulae as in zoomToBox */
	    /* dragW = wInImage(zoomBoxWidth)+1; */
	    bw = wInImage(zoomBoxWidth)+1;
	    bh = hInImage(zoomBoxHeight)+1;
	    if (hscrollbar && hscrollbar->visible()) {
	        xzoom = (double)visibleWidth()/bw;
	    } else {
		rw = bw + ((double)VSLIDER_WIDTH*bw)/visibleWidth();
		xzoom = (double)visibleWidth()/rw;
	    }

	    /* dragH = hInImage(zoomBoxHeight)+1; */
	    if (vscrollbar && vscrollbar->visible()) {
		yzoom = (double)visibleHeight()/(hInImage(zoomBoxHeight)+1);
	    } else {
		rh = bh + ((double)HSLIDER_HEIGHT*bh)/visibleHeight();
		yzoom = (double)visibleHeight()/rh;
	    }

	    dbgprintf("xzoom: %g, yzoom:%g\n",
		      xzoom, yzoom);

	    if (!aspectConstrained) {
		zf = trivmax(xzoom, yzoom);
		zf = trivmin(zf, (double)(appMaxWidth-Ox)/bw);
	        zf = trivmin(zf, (double)(appMaxHeight-Oy)/bh);
		if (vscrollbar && vscrollbar->visible()) {
		    dragW = (int)(visibleWidth()/xzoom + 0.5);
		} else {
		    dragW = (int)((visibleWidth()-VSLIDER_WIDTH)/xzoom + 0.5);
		}
		    
		if (hscrollbar && hscrollbar->visible()) {
		    dragH = (int)(visibleHeight()/yzoom + 0.5);
		} else {
		    dragH = (int)((visibleHeight()-HSLIDER_HEIGHT)/yzoom + 0.5);
		}

		fl_overlay_rect(xWtoW(zoomBoxX),
				yWtoW(zoomBoxY),
				(int)(dragW * zoomFactor()), // adjW(zoomBoxWidth),
				(int)(dragH * zoomFactor())); // adjH(zoomBoxHeight));
	    } else {
		zf = trivmin(xzoom, yzoom);
		/* this is just too hard... */
		if (vscrollbar && vscrollbar->visible()) {
		    dragW = (int)(visibleWidth()/zf + 0.5);
		} else {
		    dragW = (int)((visibleWidth()-VSLIDER_WIDTH)/zf + 0.5);
		}
		if (hscrollbar && hscrollbar->visible()) {
		    dragH = (int)(visibleHeight()/zf + 0.5);
		} else {
		    dragH = (int)((visibleHeight()-HSLIDER_HEIGHT)/zf + 0.5);
		}
	    
		fl_overlay_rect(xWtoW(zoomBoxX),
				yWtoW(zoomBoxY),
				(int)(dragW * zoomFactor()), // adjW(zoomBoxWidth),
				(int)(dragH * zoomFactor())); // adjH(zoomBoxHeight));
	    }


	    if (imageInfoPanel)
		imageInfoPanel->update();
	} 
    } else if (nbFlBoxDamage > 1) {
	errprintf("FlBox damage = %d, how can this happen?\n", nbFlBoxDamage);
    }

    return;
}

void imageViewer::handleMiddleButtonReleased()
{
    if (zoomBoxDefined && zoomBox) {
	parentWindow->make_current();
	fl_overlay_clear();
	zoomBoxDefined = 0;
	zoomBox = 0;
	zoomDragInProgress = false;
	// now zooms:
	zoomToBox(xInImage(zoomBoxX),
		  yInImage(zoomBoxY),
		  wInImage(zoomBoxWidth)+1,
		  hInImage(zoomBoxHeight)+1);
    } 
    // in any case, back to local default
    chg_cursor(MY_CURSOR_DEFAULT);


    
    return;
}


void imageViewer::handleRightButtonReleased()
{
    pointermode m = getpointermode(RIGHT_BUTTON);

    if (Fl::event_state(FL_SHIFT) || (m == IMV_POINTER_ADDPT_MODE)) {
	dbgprintf("Break point entered\n");
	parentWindow->make_current(); // we are drawing!
	PtFileMngr->addBreak();
    } else {
	// unzoom back to normal
	zoomFactor(defaultZoomFactor_, true, true); // maybe redraw
    }
    return;
}


void imageViewer::deletePoint(int atX, int atY)
{
    delPtX = atX;
    delPtY = atY;
    dbgprintf("About to delete point at (%d,%d)\n", atX, atY);
    nbPointDamage++;
    
    if (nbPointDamage <= 1)
	damage(4);   // minimal damage
    else {
	damage(32); // calling the big boy to clear the crap
    }

    return;
}


void imageViewer::create_hscrollbar_win_fit_img()
{
    dbgprintf("Horizontal scrollbar created\n");
    hscrollbar = new Fl_Scrollbar(x(),
				  y() + h() -  HSLIDER_HEIGHT,
				  w() - VSLIDER_WIDTH,
				  HSLIDER_HEIGHT, 0);
    hscrollbar->type(FL_HORIZONTAL);
    hscrollbar->callback(hscrollbar_callback);
    hscrollbar->box(FL_DOWN_BOX);
    add(hscrollbar);
    scrollbartype_ = IMV_DISPLAY_WINDOW_FIT_IMG;
    return;
}

void imageViewer::create_vscrollbar_win_fit_img()
{
    dbgprintf("Vertical scrollbar created\n");
    vscrollbar = new Fl_Scrollbar(x() + w() - VSLIDER_WIDTH,
				  y(),
				  VSLIDER_WIDTH,
				  h()- HSLIDER_HEIGHT, 0);
    vscrollbar->type(FL_VERTICAL);
    vscrollbar->callback(vscrollbar_callback);
    vscrollbar->box(FL_DOWN_BOX);
    add(vscrollbar);
    scrollbartype_ = IMV_DISPLAY_WINDOW_FIT_IMG;
    return;
}

void imageViewer::create_hscrollbar_decoupled()
{
    dbgprintf("Horizontal decoupled scrollbar created\n");
    hscrollbar = new Fl_Scrollbar(x(),
				  y() + h() -  HSLIDER_HEIGHT,
				  w() - VSLIDER_WIDTH,
				  HSLIDER_HEIGHT, 0);
    hscrollbar->type(FL_HORIZONTAL);
    hscrollbar->callback(hscrollbar_callback);
    hscrollbar->box(FL_DOWN_BOX);
    add(hscrollbar);
    scrollbartype_ = IMV_DISPLAY_DECOUPLE_WIN_IMG;
    return;
}

void imageViewer::create_vscrollbar_decoupled()
{
    dbgprintf("Vertical decoupled scrollbar created\n");
    vscrollbar = new Fl_Scrollbar(x() + w() - VSLIDER_WIDTH,
				  y(),
				  VSLIDER_WIDTH,
				  h()- HSLIDER_HEIGHT, 0);
    vscrollbar->type(FL_VERTICAL);
    vscrollbar->callback(vscrollbar_callback);
    vscrollbar->box(FL_DOWN_BOX);
    add(vscrollbar);
    scrollbartype_ = IMV_DISPLAY_DECOUPLE_WIN_IMG;
    return;
}

void imageViewer::destroy_scrollbars()
{
    // incorrect kind of scrollbar was used..
    if (hscrollbar) {
        dbgprintf("Destroying horizontal scrollbar\n");
        remove(hscrollbar);
        delete(hscrollbar);
        hscrollbar=0;
    }
    if (vscrollbar) {
        dbgprintf("Destroying vertical scrollbar\n");
        remove(vscrollbar);
        delete(vscrollbar);
        vscrollbar = 0;
    }
    return;
}

// re-coloring doesn't work on all X displays. Abandon idea
void imageViewer::chg_cursor(Fl_Cursor newCursor, bool invert)
{
    static Fl_Cursor currentCursor = FL_CURSOR_NONE;
    
    if (parentWindow && (newCursor != currentCursor)) {
	parentWindow->make_current();
	if (invert) {
	    dbgprintf("INverted cursor\n");
	    fl_cursor(newCursor, FL_WHITE, FL_BLACK);
	} else {
            fl_cursor(newCursor, FL_BLACK, FL_WHITE);
        }
        currentCursor = newCursor; // changing cursor flashes the screen on some displays
    }
}

int imageViewer::visibleWidth(void)
{
    int vw = w();
    if (vscrollbar && vscrollbar->visible()) {
        vw -= VSLIDER_WIDTH;
    }
    vw = trivmax(vw, 1); // vw must always be > 0
    
    return vw;
}
    
int imageViewer::visibleHeight(void)
{
    int vh = h();
    if (hscrollbar && hscrollbar->visible()) {
        vh -= HSLIDER_HEIGHT;
    }
    vh = trivmax(vh, 1);
        
    return vh;
}
   
inline void imageViewer::zapImageObject(void)
{
    if (imageObject) {
	freeOffScreenPixmap(imageObject);
	delete imageObject;
	imageObject = 0;
    }
}


void imageViewer::showAnnotatePanel(int X, int Y)
{
    dbgprintf("Annotate panel\n");
    if (annotatePointPanel == 0) {
	dbgprintf("Panel created\n");
	annotatePointPanel = new annotatept;
	annotatept_panel(*annotatePointPanel);
	// standard init
	annotatePointPanel->setDefaults();
    }
    // show the panel
    annotatePointPanel->setXY(X,Y);
    // get the annotation stuff from the nearest point
    annotatePointPanel->getPointParams();
    // finally show the panel
    annotatePointPanel->show();
    // the return callback from the panel will set the annotation.
    return;
}

pointermode imageViewer::getpointermode(int button)
{
    pointermode retval = pmode_;
    
    if (pmode_ == IMV_POINTER_ZOOM_MODE) {
	// lots of modifiers there
	if (button == LEFT_BUTTON) {
	    if (Fl::event_state(FL_SHIFT) || pointmode)
		if (Fl::event_state(FL_ALT))
		    retval = IMV_POINTER_DELPT_MODE;
		else if (Fl::event_state(FL_CTRL))
		    retval = IMV_POINTER_EDITPT_MODE;
		else
		    retval = IMV_POINTER_ADDPT_MODE;

	    else if (Fl::event_state(FL_CTRL))
		retval = IMV_POINTER_MEASURE_MODE;
	}
    }

    return retval;
}
