/*
 * $Id: menucb.cxx,v 4.15 2005/02/05 14:45:59 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.
 * */

/*------------------------------------------------------------------------
 *
 * This file contains all the global functions called by the menu
 * callbacks and keyboard accelerators.
 *
 *      
 *-----------------------------------------------------------------------*/

#include "imnmspc.hxx" // contains namespace definition, if needed

#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Menu_Bar.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_File_Chooser.H>
#include <FL/Fl_Help_Dialog.H>
#include <FL/fl_ask.H>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "imunistd.h"
#include <errno.h>
#include <sys/types.h>
#include <map>    // C++ stl container.
#include <string> // C++ strings

#include "imview.hxx"
#include "nocase.hxx"
#include "imageIO.hxx"
#include "pointfile.hxx"
#include "imageViewer.hxx"
#include "menubar.hxx"
#include "printPrefs.hxx"
#include "savePrefs.hxx"
#include "imageInfo.hxx"
#include "spectraBox.hxx"
#include "transferFunction.hxx"
#include "transferRGBFunction.hxx"
#include "profileBox.hxx"
#include "userPrefs.hxx"
#include "progressInfo.hxx"
#include "bivHist.hxx"
#include "machine.hxx"
#include "StatusBox.hxx"
#include "toolbar.hxx"
#include "io/newprefio.hxx"
#include "sliceSlider.hxx"

using std::map;

typedef map<string,image_psfile>::iterator mi;  // Map iterator (can't be const)

// components of the application
extern imviewWindow       *mainWindow;
extern imageViewer        *mainViewer;
extern imViewMenuBar      *mainMenuBar;
extern imageIO            *IOBlackBox;
extern pointfile          *PtFileMngr;
extern Fl_File_Chooser    *ImviewFC;
extern int                 debugIsOn;
extern int                 stopDebug;
extern bool                lutWraparound;
extern volatile bool       syncDisplay;
extern int                 makeImageCharIfFits;
extern int                 RGBdisplaysRGB;
extern int                 fitCharOverAllSamples;
extern int                 fitCharOverAllSlices;
extern const char         *patchlevel;
extern int                 totalbnb;
extern char               *builder;
extern int                 deleteAfter;
extern const char         *psonlyfile;
extern map<string,image_psfile>  psfilemap;
extern int                 appMaxHeight;

// fluid-generated
extern printprefs     *printPrefsPanel;
extern saveprefs      *savePrefsPanel;
extern imageinfo      *imageInfoPanel;
extern transfer       *transferEditorPanel;
extern transferRGB    *transferRGBEditorPanel;
extern spectra        *spectraPanel;
extern spectra        *depthProfile;
extern profile        *profilePanel;
extern userprefs      *userprefsPanel;
extern bivhist        *bivHistPanel;
extern progress       *progressPanel;
extern Fl_Help_Dialog *help;
extern toolbar        *toolbarPanel;
extern slideinput     *slice3D, *sliceComponent, *sliceSeries;

// others
extern imprefs        *fileprefs;
extern int             applyTransferToAll;
extern bool            main_terminate;
extern bool            hideMainMenu;
extern bool            fullScreen;

int    keepPoints = 0; // a local preference



// openimage is a wrapper for all the read() calls
int openimage(const char *imname, int frame)
{
    int          ret;
    static char  clutname[DFLTSTRLEN+1];
    const char   *pf;
    int          nbframes=0;
    IMAGEPARM   *p; 

    if (IOBlackBox->isOverlay(imname)) { // are we opening an overlay?
	dbgprintf("Image %s is an overlay\n", imname);
	p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
	if (p) {
	    p->ovlpath = strdup(imname);
	    // apply overlay to current image
	    int retval = IOBlackBox->readOverlay(imname);
	    if (retval == 0) {
		// redisplay image with overlay present
		IOBlackBox->applyImageParameters(p, 0, 0, true);
		mainViewer->displayCurrentImage();
	    }
	    return retval; // don't go any further in reading this image!
	} else {
	    // close the overlay, it can never be displayed
	    IOBlackBox->closeHeader(imname);
	    return 20;
	}
    } else if  (mainMenuBar->isAlreadyInItemList(imname, IMAGE_LIST_ID)) { // are we opening a new image?
	// image may have already been opened once, but we are not sure yet
	// selects that item
	mainMenuBar->addToItemList(imname, IMAGE_LIST_ID);
	p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
	if (p->nbframes > 0) {
	    // now we are quite sure that the image has been opened before
	    if (frame == -1) // default is wrong here
		frame = p->frame;
	    nbframes = p->nbframes;
	} else {
	    // image has never been opened before
	    nbframes = IOBlackBox->getNbFrames(imname);
	}
    } else {
	// image has never been opened before
	nbframes = IOBlackBox->getNbFrames(imname);
    }

    if (nbframes <= -10) {
	return (nbframes); // don't go any further, waste of time...
    } else if (nbframes > 1) {
	// enable the menu items
	mainMenuBar->setFlagsByName(NEXTFRAME_ITEM, 0);
	mainMenuBar->setFlagsByName(PREVIOUSFRAME_ITEM, 0);
	mainMenuBar->setFlagsByName(SELECTFRAME_ITEM, FL_MENU_DIVIDER);
    } else {
	mainMenuBar->setFlagsByName(NEXTFRAME_ITEM, FL_PUP_INVISIBLE);
	mainMenuBar->setFlagsByName(PREVIOUSFRAME_ITEM, FL_PUP_INVISIBLE);
	mainMenuBar->setFlagsByName(SELECTFRAME_ITEM, FL_PUP_INVISIBLE | FL_MENU_DIVIDER);
    }

    // correct the default if by that time it hasn't been done yet
    if (frame == -1) frame = 0;
    
    // before image switch, backup the points
    PtFileMngr->savelist(false);
    
    if ((ret = IOBlackBox->read(imname, frame)) == 0) {
        // we have successfully read the image
        // remove the titleBox if it is still present
        removeSimpleBanner();

        // selects this file in the image list
        mainMenuBar->addToItemList(imname, IMAGE_LIST_ID);
	if (mainMenuBar->getNbImagesInList() > 1) {
	    mainMenuBar->setFlagsByName(NEXTIMAGE_ITEM, 0);
	    mainMenuBar->setFlagsByName(PREVIOUSIMAGE_ITEM, FL_MENU_DIVIDER);
	} else {
	    mainMenuBar->setFlagsByName(NEXTIMAGE_ITEM, FL_PUP_GREY);
	    mainMenuBar->setFlagsByName(PREVIOUSIMAGE_ITEM, FL_PUP_GREY | FL_MENU_DIVIDER);
	}
        // check for LUT being attached to this image.
        p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
	// records the number of frames (computed earlier)
	p->nbframes = nbframes;
	// re-set this, because the number of frame is only
	// computed the first time the image is read.
	IOBlackBox->setCurrImgNbFrames(nbframes);

        // apply the gamma, contrast, etc, that have been saved for this image
	// except if the `apply gamma to all images' has been checked, in which
	// case the stored values for the image don't apply.
	if (transferEditorPanel && !applyTransferToAll) {
	    char cval[100];
	    
	    dbgprintf("Setting editor panel values\n");
	    transferEditorPanel->setGammaSliderValue(log(p->gamma));
	    sprintf(cval, "%1.4g", p->gamma);
	    transferEditorPanel->setGammaInputValue((const char *)cval);
	    transferEditorPanel->setContrastSliderValue(p->contrast);
	    transferEditorPanel->setBrightnessSliderValue(p->brightness);
	    transferEditorPanel->setDrawingParms();
	    transferEditorPanel->redrawWindowIfNeeded();
	    dbgprintf("Values found for %s:\ngamma=%g, contrast=%g, brightness=%g\n",
		      p->itempath,
		      p->gamma, p->contrast, p->brightness
		);
	}

	// same thing for RGB panel
	if (transferRGBEditorPanel && !applyTransferToAll) {
	    char cval[100];
	    
	    dbgprintf("Setting RGB editor panel values\n");
	    // Gamma sliders
	    transferRGBEditorPanel->setRGammaSliderValue(log(p->Rgamma));
	    transferRGBEditorPanel->setGGammaSliderValue(log(p->Ggamma));
	    transferRGBEditorPanel->setBGammaSliderValue(log(p->Bgamma));
	    // Gamma inputs
	    sprintf(cval, "%1.4g", p->Rgamma);
	    transferRGBEditorPanel->setRGammaInputValue((const char *)cval);
	    sprintf(cval, "%1.4g", p->Ggamma);
	    transferRGBEditorPanel->setGGammaInputValue((const char *)cval);
	    sprintf(cval, "%1.4g", p->Bgamma);
	    transferRGBEditorPanel->setBGammaInputValue((const char *)cval);
	    // Contrast sliders
	    transferRGBEditorPanel->setRContrastSliderValue(p->Rcontrast);
	    transferRGBEditorPanel->setGContrastSliderValue(p->Gcontrast);
	    transferRGBEditorPanel->setBContrastSliderValue(p->Bcontrast);
	    // Brightness sliders
	    transferRGBEditorPanel->setRBrightnessSliderValue(p->Rbrightness);
	    transferRGBEditorPanel->setGBrightnessSliderValue(p->Gbrightness);
	    transferRGBEditorPanel->setBBrightnessSliderValue(p->Bbrightness);
	    //
	    transferRGBEditorPanel->setDrawingParms();
	    transferRGBEditorPanel->redrawWindowIfNeeded();
	    dbgprintf("Values found for %s:\ngamma=%g, contrast=%g, brightness=%g\n",
		      p->itempath,
		      p->gamma, p->contrast, p->brightness
		);
	}

        // check if pointfile name has been set
        if (p->pfpath==0) {
          // check if an image specific pointfile exists
          if ((pf=PtFileMngr->getPtFileNameIfPresent(imname)) != 0)
            PtFileMngr->setPtFileName(pf);

          // set the pointfile name accordingly, default = pointfile
          p->pfpath = strdup(PtFileMngr->getPtFileName());
        }

	// clears the pointfile
	if (!fileprefs->keepPoints())
          PtFileMngr->rmAllPoints();
	PtFileMngr->imageName(imname);


	// read the overlay, if any
	if (p->ovlpath) 
	    IOBlackBox->readOverlay(p->ovlpath);
	else // else remove it
	    IOBlackBox->setOverlay(0);
	
	    
	
        // apply the display parameters that have been saved for this image
	// IOBlackBox->applyImageParameters(p, imname, clutname, false);

	if ((p->gamma != 1.0) || (p->contrast != 0.5) || (p->brightness != 0.5))
	    IOBlackBox->applyImageParameters(p, imname, clutname, true);
	else 
	    IOBlackBox->applyImageParameters(p, imname, clutname, false);
	
        // selects the CLUT or unselects it (if no CLUT)
        mainMenuBar->addToItemList((const char *)clutname, CLUT_LIST_ID);
	// now let's use some information on the image
	//  first of all, is the image 3-d?
	if (IOBlackBox->getCurrImgThickness() > 1) {
	    // image is 3-d, we'll want to wade through the Z plane
	    mainMenuBar->setFlagsByName(NEXTPLANE_ITEM, 0);
	    mainMenuBar->setFlagsByName(PREVIOUSPLANE_ITEM, 0);
	    mainMenuBar->setFlagsByName(SELECTPLANE_ITEM, FL_MENU_DIVIDER);
            // allow 3D rotations
            mainMenuBar->setFlagsByName(ROTATE3DMENU_ITEM, FL_SUBMENU);
	    // allow 3D images to behave as hyperspectral images for some
	    // applications
	    if (IOBlackBox->getCurrImgThickness() > 1) {
		mainMenuBar->setFlagsByName(DEPTHPROFILE_ITEM, 0);
	    } else {
		mainMenuBar->setFlagsByName(DEPTHPROFILE_ITEM, FL_PUP_INVISIBLE);
	    }
	} else {
	    // image is not 3-d, no point in confusing the user
	    mainMenuBar->setFlagsByName(NEXTPLANE_ITEM, FL_PUP_INVISIBLE);
	    mainMenuBar->setFlagsByName(PREVIOUSPLANE_ITEM, FL_PUP_INVISIBLE);
	    mainMenuBar->setFlagsByName(SELECTPLANE_ITEM, FL_PUP_INVISIBLE | FL_MENU_DIVIDER);
            // disallow 3D rotations
            mainMenuBar->setFlagsByName(ROTATE3DMENU_ITEM, FL_SUBMENU|FL_PUP_INVISIBLE);
	}
	// next: is the image multispectral?
	if (IOBlackBox->getCurrImgNbSamples() > 1) {
	    // image is multispectral we'll want to wade through the components
	    mainMenuBar->setFlagsByName(NEXTCOMPONENT_ITEM, 0);
	    mainMenuBar->setFlagsByName(PREVIOUSCOMPONENT_ITEM, 0);
	    mainMenuBar->setFlagsByName(SELECTCOMPONENT_ITEM, FL_MENU_DIVIDER);
	    mainMenuBar->setFlagsByName(BIVHIST_ITEM, 0);
	    // Re-enable when it works
	    mainMenuBar->setFlagsByName(RGBTRANSFER_ITEM, 0);
	    if (IOBlackBox->getCurrImgNbSamples() >= 3) {
		mainMenuBar->setFlagsByName(HYPERSPECTRAL_ITEM, 0);
	    } else {
		mainMenuBar->setFlagsByName(HYPERSPECTRAL_ITEM, FL_PUP_INVISIBLE);
	    }
	} else {
	    // image is not multispectral, no point in confusing the user
	    mainMenuBar->setFlagsByName(NEXTCOMPONENT_ITEM, FL_PUP_INVISIBLE);
	    mainMenuBar->setFlagsByName(PREVIOUSCOMPONENT_ITEM, FL_PUP_INVISIBLE);
	    mainMenuBar->setFlagsByName(BIVHIST_ITEM, FL_PUP_INVISIBLE);
	    mainMenuBar->setFlagsByName(RGBTRANSFER_ITEM, FL_PUP_INVISIBLE);
	    // same as above, we want to allow 3D images to behave as hyperspectral images
	    // sometimes
	    if (IOBlackBox->getCurrImgThickness() <= 3) {
		mainMenuBar->setFlagsByName(HYPERSPECTRAL_ITEM, FL_PUP_INVISIBLE);
		mainMenuBar->setFlagsByName(SELECTCOMPONENT_ITEM, FL_PUP_INVISIBLE | FL_MENU_DIVIDER);
	    }
	}

	
        // actually display the image
        mainViewer->displayCurrentImage();
    } else {
	errprintf("Read of %s failed, error=%d\n", imname, ret);
    }
    
    return ret;
}

// all the callback functions: they have to be globals

// menu callback functions
void test_cb(Fl_Object *,void *i)
{
    dbgprintf("Not implemented yet, sorry! userdata = %ld\n",(long)i);
}

// Zoom in
void zoomin_cb(Fl_Object *, void *)
{
    double zf;

    zf = mainViewer->zoomFactor();
    dbgprintf("Zoom factor = %f\n", (double)(mainViewer->zoomFactor(zf * 2.0)));
}

void smallzoomin_cb(Fl_Object *, void *)
{
    double zf;

    zf = mainViewer->zoomFactor();
    dbgprintf("Zoom factor = %f\n", (double)(mainViewer->zoomFactor(zf * 1.1)));
}

// Zoom out
void zoomout_cb(Fl_Object *, void *)
{
    double zf;

    zf = mainViewer->zoomFactor();
    dbgprintf("Zoom factor = %f\n", (double)(mainViewer->zoomFactor(zf / 2.0)));
}

void smallzoomout_cb(Fl_Object *, void *)
{
    double zf;

    zf = mainViewer->zoomFactor();
    dbgprintf("Zoom factor = %f\n", (double)(mainViewer->zoomFactor(zf / 1.1)));
}

void unzoom_cb(Fl_Object *, void *)
{
    dbgprintf("Zoom factor = %f\n", (double)(mainViewer->applyDefaultZoomFactor()));
}

void selectzoom_cb(Fl_Object *, void *)
{
    static char  currzoomtxt[100];
    
    sprintf(currzoomtxt, "%f", (double)(mainViewer->zoomFactor()));
    
    const char *zoomtxt = fl_input("Enter zoom factor", currzoomtxt);
    double newzoom = (zoomtxt != 0) ? atof(zoomtxt):mainViewer->zoomFactor();

    // minimal check
    if (newzoom > 0)
	mainViewer->zoomFactor(newzoom);
    else
	errprintf("Zoom factor must be strictly positive\n");

    return;
}

void thiszoom_cb(Fl_Object *, void *)
{

    dbgprintf("Current zoom factor %f is new the default\n",
	      mainViewer->setDefaultZoomFactor(mainViewer->zoomFactor()));
    
    return;
}

void selectdefaultzoom_cb(Fl_Object *, void *)
{
    static char  currzoomtxt[100];
    
    sprintf(currzoomtxt, "%f", (double)(mainViewer->zoomFactor()));
    
    const char *zoomtxt = fl_input("Enter default zoom factor", currzoomtxt);
    double newzoom = (zoomtxt != 0) ? atof(zoomtxt):mainViewer->zoomFactor();

    // minimal check
    if (newzoom > 0)
	mainViewer->setDefaultZoomFactor(newzoom);
    else
	errprintf("Zoom factor must be strictly positive\n");

    return;
}

// rotations
static float mymodulo(float &f)
{
    // modulo is not portable enough in this case
    while (f >= 360)
	f -= 360;
    while (f < 0)
	f += 360;
    dbgprintf("Picture angle is %d\n", f);
    return f;
}

void rotate90right_cb(Fl_Object *, void *)
{
    IMAGEPARM          *p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    
    if (p) {
        IOBlackBox->rotateRight();
        IOBlackBox->applyImageParameters(p, 0, 0, true);
        p->angle += 90;
        mymodulo(p->angle);

        mainViewer->displayCurrentImage();
    }
    return;
}

void rotate90left_cb(Fl_Object *,	 /*g  */
		     void *)	 /*  */
{
    IMAGEPARM          *p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);

    if (p) {
        IOBlackBox->rotateLeft();
        IOBlackBox->applyImageParameters(p, 0, 0, true);
        p->angle -= 90;
        mymodulo(p->angle);
    
        mainViewer->displayCurrentImage();
    }
    return;
}

void rotate180_cb(Fl_Object *, void *)
{
    IMAGEPARM          *p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    
    if (p) {
        IOBlackBox->rotate180();
        IOBlackBox->applyImageParameters(p, 0, 0, true);
        p->angle += 180;
        mymodulo(p->angle);

        mainViewer->displayCurrentImage();
    }
    return;
}

void rotate3dup_cb(Fl_Object *, void *)
{
    IMAGEPARM          *p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    
    if (p) {
        IOBlackBox->rotate3Dup();
        IOBlackBox->applyImageParameters(p, 0, 0, true);
        // This will complicate matters...
        //p->angle += 180;
        //mymodulo(p->angle);

        mainViewer->displayCurrentImage();
    }
    return;
}

void rotate3ddown_cb(Fl_Object *, void *)
{
    IMAGEPARM          *p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    
    if (p) {
        IOBlackBox->rotate3Ddown();
        IOBlackBox->applyImageParameters(p, 0, 0, true);
        // This will complicate matters...
        //p->angle += 180;
        //mymodulo(p->angle);

        mainViewer->displayCurrentImage();
    }
    return;
}


// flips
void flipv_cb(Fl_Object *, void *)
{
    IMAGEPARM          *p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);

    if (p) {
        IOBlackBox->flipv();
        IOBlackBox->applyImageParameters(p, 0, 0, true);
        if ((p->angle == 90)
            || (p->angle == 270)) {
            p->angle += 180;
            mymodulo(p->angle);
        }
        p->mirror = 1 - p->mirror; // swap the `mirror' status
        
        mainViewer->displayCurrentImage();
    }
    return;
}

void fliph_cb(Fl_Object *, void *)
{
    IMAGEPARM          *p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    
    if (p) {
        IOBlackBox->fliph();
        IOBlackBox->applyImageParameters(p, 0, 0, true);
        if ((p->angle == 0)
            || (p->angle == 180)) {
            p->angle += 180;
            mymodulo(p->angle);
        }
        p->mirror = 1 - p->mirror; // swap the `mirror' status
        
        mainViewer->displayCurrentImage();
    }
    return;
}

void filechoosernew_cb(Fl_File_Chooser *fc, void *data)
{
    dbgprintf("new file_chooser callback (fc=%p, data=%p)\n", fc, data);
    dbgprintf("  filename = \"%s\"\n", fc->value());
}

void create_fc(void)
{
    const static char *file_filter = \
	"Image Files (*.{bmp,gif,jpg,png,tif,tiff})\t"
	"Pointfiles (*.pf)";

    if (ImviewFC == 0) {
	ImviewFC = new Fl_File_Chooser(".", "*", Fl_File_Chooser::MULTI|Fl_File_Chooser::CREATE, "Imview File Chooser");
	ImviewFC->callback(filechoosernew_cb);
	ImviewFC->filter(file_filter);
    }
    assert(ImviewFC != 0); // eh.
}

// Lauches the openfile dialog
void open_cb(Fl_Object *, void *i)
{
    int count;
    char locfilename[DFLTSTRLEN];
    
    dbgprintf("Open dialog callback with option %ld\n", (long) i);
    create_fc();

    ImviewFC->show();

    // modal
    while (ImviewFC->visible())
	Fl::wait();

    count = ImviewFC->count();
    // add all the files to the item list and open the last one
    if (count > 0) {
	for (int i = 1 ; i <= count ; ++i) {
	    if (!ImviewFC->value(i))
		break;
	    
	    fl_filename_expand(locfilename, sizeof(locfilename), ImviewFC->value(i));
	    mainMenuBar->addToItemList(locfilename, IMAGE_LIST_ID);
	}
	// truly open the last one
	openimage(locfilename);
    }

#if 0
    p = fl_file_chooser("Open Image","*",currentfile);
    if (p)
        currentfile = p;
    if (p == 0) {
        dbgprintf("User cancelled choice\n");
    } else {
        dbgprintf("User chose %s\n", currentfile);
        // open an image
        openimage(currentfile);
    }
#endif

    return;
}

// Opens an overlay
void openoverlay_cb(Fl_Object *, void *i)
{
    IMAGEPARM          *pp = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    static const char  *currentfile = 0, *p = 0;
    
    dbgprintf("Open overlay dialog callback with option %ld\n", (long) i);
    p = fl_file_chooser("Open Overlay Image","*",currentfile);
    if (p)
        currentfile = p;
    if (p == 0) {
        dbgprintf("User cancelled choice\n");
    } else {
        dbgprintf("User chose %s\n", currentfile);
	pp->ovlpath = strdup(p);
        // apply overlay to current image
        int retval = IOBlackBox->readOverlay(currentfile);
	if (retval == 0) {
	    // redisplay image with overlay present
	    // don't call this
	    //IOBlackBox->applyOverlayToCurrentImage();
	    // call that instead
	    IOBlackBox->applyImageParameters(pp, 0, 0, true);
	    mainViewer->displayCurrentImage();
	}
    }
    return;
}

// close overlay by hand
void closeoverlay_cb(Fl_Object *, void *i)
{
    IMAGEPARM          *p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);

    // this call free extra buffers associated with socket I/O
    // this is the logical place for this to happen, as the image can't be
    // recalled if its name drops from the list.
    IOBlackBox->closeHeader(p->ovlpath);

    if (p->ovlpath) {
	free(p->ovlpath);
	p->ovlpath = 0;
    }    

    IOBlackBox->setOverlay(0);
    IOBlackBox->applyImageParameters(p, 0, 0, true);
    mainViewer->displayCurrentImage();
}

// launches the save preferences dialog, and then the savefile dialog
void save_cb(Fl_Object *, void *)
{
    dbgprintf("Save preferences callback\n");

    if (savePrefsPanel == 0) {
	savePrefsPanel = new saveprefs;
	saveprefs &saveref = *savePrefsPanel;
	// initializes the fluid-generated panel
	saveprefs_panel(saveref);
	// do our own private initialisation
	savePrefsPanel->setDefaults();
    }

    // show the panel
    savePrefsPanel->show();

    
    return;
}

void printprefs_cb(Fl_Object *, void *)
{
    // get the name of the image currently being displayed
    IMAGEPARM *p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    char      *shortname = strrchr(p->itempath, '/');

    if (shortname == NULL) 
	shortname = p->itempath; // revert to full name if name is local 
    else
	shortname++; // go beyond the last `/'
    
    dbgprintf("Print preferences callback\n");

    if (printPrefsPanel == 0) {
	// initializes the fluid-generated panel
	printPrefsPanel = new printprefs;
	printprefs &printref = *printPrefsPanel;
	printprefs_panel(printref);
	// do our own initialisation
	printPrefsPanel->setDefaults();
    }
    // shows the panel
    printPrefsPanel->setTitle((const char*)shortname);
    printPrefsPanel->show();
    
    return;
}

void imageinfo_cb(Fl_Object *, void *)
{
    dbgprintf("Image info callback\n");

    if (imageInfoPanel == 0) {
	// initializes the fluid-generated panel
	imageInfoPanel = new imageinfo;
	imageinfo &imageref = *imageInfoPanel;
	imageinfo_panel(imageref);
	// do our own initialisation
	imageInfoPanel->setDefaults();
    }
    // shows the panel
    imageInfoPanel->show();
    
    return;
}

void toolbar_cb(Fl_Object *, void *)
{
    dbgprintf("Toolbar callback\n");

    if (toolbarPanel == 0) {
	// initializes the fluid-generated panel
	toolbarPanel = new toolbar;
	toolbar_panel(*toolbarPanel);
	// init
	toolbarPanel->setDefaults();
    }
    // Show the panel
    toolbarPanel->show();
    
    return;
}
    

void transfer_cb(Fl_Object *, void *)
{
    dbgprintf("Transfer function editor callback\n");

    if (transferEditorPanel == 0) {
	transferEditorPanel = new transfer;
	transfer &transferref = *transferEditorPanel;
	// initializes the fluid-generated panel
	transfer_panel(transferref);
	// do our own initialisation
	transferEditorPanel->setDefaults();
    }
    // shows the panel
    transferEditorPanel->show();
    
    return;
}

void transferRGB_cb(Fl_Object *, void *)
{
    dbgprintf("RGB Transfer function editor callback\n");
    if (transferRGBEditorPanel == 0) {
	transferRGBEditorPanel = new transferRGB;
	// initializes the fluid-generated panel
	RGB_transfer_panel(*transferRGBEditorPanel);
	// do our own initialisation
	transferRGBEditorPanel->setDefaults();
    }
    // shows the panel
    transferRGBEditorPanel->show();
    
    return;
}

void threshold_cb(Fl_Object *, void *)
{
    return;
}

// who is responsible for this heap of crap?
void whodunit_cb(Fl_Object *, void *)
{
    static char buff[300];

    sprintf(buff,
	    "Imview version %s build #%d for %s\n"
	    "Made on %s by %s\n"
	    "Copyright Hugues Talbot, CMIS Image Analysis 1997-2003",
	    patchlevel,
	    totalbnb,
	    systemName(),
	    __DATE__,
	    builder);
    fl_message((const char *)buff); 
    return;
}

// online help: just loading a pre-defined image
// void onlinehelp_cb(Fl_Object *, void *)
// {
//     // for now, online help is only an image...
//     openimage(ONLINEHELP);
//     // this is a bit brutal, but we want to be able to view
//     // the damn thing...
//     mainViewer->zoomFactor(1.0);
//     mainViewer->resetDisplay(false);
//     return;
// }

int starthelp(char *returnPath, char *fileToShow)
{
    const  char *p;
    static char  pathToHelp[DFLTSTRLEN], tmppath[DFLTSTRLEN], basePath[DFLTSTRLEN];
    int          result = 0;
    
    if (help == 0) {
	int         i, j;

	// make up the directories where we might look for
	// the documentation
	strcpy(pathToHelp, "."); // first look in present directory
	if ((p = getenv("IMVIEWHOME")) != 0) { // then in ~/.imview, etc
	    snprintf(tmppath, DFLTSTRLEN, "%c%s", PATHSEP, p);
	    strncat(pathToHelp, tmppath, DFLTSTRLEN);
	}
        snprintf(tmppath, DFLTSTRLEN-4, "%c%c%s", PATHSEP, SLASH, PrefPath);
	strncat(pathToHelp, tmppath, DFLTSTRLEN); // then in install directory

	// hunt for documentation now
	for ( i = 0, j = 0; ; ) { // ever
	    if ((pathToHelp[i] != PATHSEP) && (pathToHelp[i] != '\0')) {
		tmppath[j++] = pathToHelp[i++];
	    } else {
		tmppath[j] = '\0';
		strcat(tmppath,"/");
		strcat(tmppath, fileToShow);
		dbgprintf("looking in %s\n", tmppath);
		if (access(tmppath, R_OK) == 0) {
		    help = new Fl_Help_Dialog;
		    strncpy(returnPath, tmppath, DFLTSTRLEN);
		    tmppath[j] = '\0';
		    strcat(tmppath,"/");
		    strncpy(basePath, tmppath, DFLTSTRLEN); // to keep (static)
		    result = 1;
		    break; // found preferences, finished
		}
		// try with doc/blablab.html
		tmppath[j] = '\0';
		strcat(tmppath,"/doc/");
		strcat(tmppath, fileToShow);
		dbgprintf("looking in %s\n", tmppath);
		if (access(tmppath, R_OK) == 0) {
		    help = new Fl_Help_Dialog;
		    strncpy(returnPath, tmppath, DFLTSTRLEN);
		    result = 1; // success!
		    tmppath[j] = '\0';
                    strcat(tmppath,"/doc/");
		    strncpy(basePath, tmppath, DFLTSTRLEN); // to keep (static)
		    break; // found preferences, finished
		}
		// no joy there, try next string if possible
		if (pathToHelp[i] != '\0') {
		    j = 0;
		    i++;
		} else {
		    strncpy(returnPath, pathToHelp, DFLTSTRLEN);
		    basePath[0] = '\0';
		    result = 0; // failure
		    break;
		}
	    }
	}
    } else {
	// help has been started already, but check that the file we want it still there
	strncpy(tmppath, basePath, DFLTSTRLEN); // ends with a `/'
	strncat(tmppath, fileToShow, DFLTSTRLEN);
	dbgprintf("Looking in %s\n", tmppath);
	if (access(tmppath, R_OK) == 0) {
	    strncpy(returnPath, tmppath, DFLTSTRLEN);
	    result = 1; // success!
	} else {
	    result = 0;
	}
    }

    return result;
}

void onlinehelp_cb(Fl_Object *, void *)
{
    const  char *p;
    static char pathToHelp[DFLTSTRLEN];
    int         helpStarted;

    helpStarted = starthelp(pathToHelp, ONLINEHELP);
    
    if (help && helpStarted) {
	help->load(pathToHelp);
	help->show();
    } else {
	if ((p = getenv("IMVIEWHOME")) == 0) {
	    errprintf("On-line help not found anywhere,\n"
		      "try setting IMVIEWHOME. Sorry\n");
	} else {
	    errprintf("On-line help not found anywhere in\n%s", pathToHelp);
	}
    }
}

// lauches netscape to read the full documentation
void fulldocument_cb(Fl_Object *, void *)
{
    static char command[1024];

    sprintf(command,
	    "netscape -remote openURL\\(%s,new-window\\)", HELPURL);
    int ret = system((const char *)command);
    if (ret != 0) {
	// try calling netscape directly!
	sprintf(command,
		"netscape %s &", HELPURL);
	ret = system((const char *)command);
	if (ret != 0)
	    errprintf("Sorry, can't start netscape\n"
		      "Help URL is:\n"
		      "%s",
		      HELPURL);
    }
}

// GNU Public Licence
void licence_cb(Fl_Object *, void *)
{
    const char *p;
    static char pathToLicense[DFLTSTRLEN];
    int         helpStarted;

    helpStarted = starthelp(pathToLicense, LICENSE);
    
    if (help && helpStarted) {
	help->load(pathToLicense);
	help->show();
    } else {
	if ((p = getenv("IMVIEWHOME")) == 0) {
	    errprintf("License file not found anywhere,\n"
		      "try setting IMVIEWHOME Sorry\n");
	} else {
	    errprintf("License file not found anywhere in\n%s", pathToLicense);
	}
    }
}

// NO WARRANTY
void warranty_cb(Fl_Object *, void *)
{
    const char *p;
    static char pathToWarranty[DFLTSTRLEN];
    int         helpStarted;

    helpStarted = starthelp(pathToWarranty, WARRANTY);
    
    if (help && helpStarted) {
	help->load(pathToWarranty);
	help->show();
    } else {
	if ((p = getenv("IMVIEWHOME")) == 0) {
	    errprintf("Warranty file not found anywhere,\n"
		      "try setting IMVIEWHOME Sorry\n");
	} else {
	    errprintf("Warranty file not found anywhere in\n%s", pathToWarranty);
	}
    }
}

void close_cb(Fl_Object *, void *)
{
    IMAGEPARM          *p;
    mi                  q;
    const char         *imTempPSFileName;

    
    dbgprintf("Close dialog callback\n");

    // replace the image being displayed
    do  {
        p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID); // this is the image being displayed at present

	if (p == 0)
	    return; // this is possible if called from the interpreter
	    
	// case when the file is a Postscript file, rendered into
	// a different file...
	if ((q = psfilemap.find(p->itempath)) != psfilemap.end()) {
//	if (psonlyfile && (strcmp(p->itempath, psonlyfile) == 0)) {
	    // erase the temp file
	    imTempPSFileName = (q->second).getfilename().c_str();
	    dbgprintf("Erasing the rendered PS file %s\n", imTempPSFileName);
	    unlink(imTempPSFileName);
	    psfilemap.erase(q); // get rid of it
	} else 	if (deleteAfter) {
	    // be a bit conservative: remove the image only if it is in one of the tmp directories
	    if (strstr(p->itempath, "/tmp/") != 0) {
		dbgprintf("Physically deleting the image %s\n", p->itempath);
		unlink(p->itempath);
	    } else {
		dbgprintf("Not deleting %s after all\n", p->itempath);
	    }
	}
	// this call free extra buffers associated with socket I/O
	// this is the logical place for this to happen, as the image can't be
	// recalled if its name drops from the list.
	IOBlackBox->closeHeader(p->itempath);
	// normal menu handling
	mainMenuBar->removeFromItemList(p->itempath, IMAGE_LIST_ID);     // this will change the selection...
        p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID); // therefore it is NO LONGER the same image as above
    } while ((p != 0) && (openimage(p->itempath) != 0));

    if (p == 0) {
        // there isn't any image left in the list: show the simple banner instead
        mainViewer->close();
        showSimpleBanner();
    }
    
    return;
}

void filechooser_cb(const char *name)
{
    dbgprintf("File chooser callback '%s'\n",name);
}


// called when a image name is selected from the image menu
void image_cb(Fl_Object *, void *arg)
{
    IMAGEPARM *p;

    p = (IMAGEPARM *)arg;
    dbgprintf("Image callback with argument %s\n", p->itempath);
    if (openimage(p->itempath) != 0) {
        // this image couldn't be opened: remove it from the list!
        do  {
            mainMenuBar->removeFromItemList(p->itempath, IMAGE_LIST_ID);
            // this will get the new selection...
            p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
        } while ((p != 0) && (openimage(p->itempath) != 0));

        if (p == 0) {
            // there isn't any image left in the list: show the simple banner instead
            mainViewer->close();
            showSimpleBanner();
        }
    }
}

// get the next image on the list if any
void nextimage_cb(Fl_Object *, void *)
{
    IMAGEPARM *p;
    
    dbgprintf("Next image callback\n");

    if ((p = (IMAGEPARM *)mainMenuBar->getNextItemInList(IMAGE_LIST_ID)) != 0) {
        if (openimage(p->itempath) != 0) {
            // this image couldn't be opened: remove it from the list!
            do {
                mainMenuBar->removeFromItemList(p->itempath, IMAGE_LIST_ID);  
                // this will get the new selection...
                p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
            } while ((p != 0) && (openimage(p->itempath) != 0));
            
            if (p == 0) {
                // there isn't any image left in the list: show the simple banner instead
                mainViewer->close();
                showSimpleBanner();
            }
        }
    } else {
        warnprintf("Null pointer in image list: this is a bug\n");
    }
      
    return;
}

void previousimage_cb(Fl_Object *, void *)
{
    IMAGEPARM *p;
    
    dbgprintf("Previous image callback\n");

    if ((p = (IMAGEPARM *)mainMenuBar->getPreviousItemInList(IMAGE_LIST_ID)) != 0) {
        if (openimage(p->itempath) != 0) {
            // this image couldn't be opened: remove it from the list!
            do {
                mainMenuBar->removeFromItemList(p->itempath, IMAGE_LIST_ID);  
                // this will get the new selection...
                p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
            } while ((p != 0) && (openimage(p->itempath) != 0));
            
            if (p == 0) {
                // there isn't any image left in the list: show the simple banner instead
                mainViewer->close();
                showSimpleBanner();
            }
        }
    } else {
        warnprintf("Null pointer in image list: this is a bug\n");
    }
    
    return;
}

// Walk down the list of images and save them to file...
void saveimagelist_cb(Fl_Object *, void *)
{
    static char currSavePath[100], errmsg[300];
    int         nbitems;
    char *res = mainMenuBar->getItemListContent(IMAGE_LIST_ID, nbitems);

    strcpy(currSavePath, "/usr/tmp/imagelist");
    
    const char *savePathTxt = fl_input("Enter list save path", currSavePath);

    FILE *saveListFile = fopen(savePathTxt, "w");
    if (saveListFile != NULL) {
	fprintf(saveListFile, res);
	fclose(saveListFile);
	dbgprintf("Saving %d image names to %s\n", nbitems, savePathTxt); 

    } else {
	sprintf(errmsg, "Error saving list to %s\n%s", savePathTxt, strerror(errno));
	fl_alert(errmsg);
    }

    
    delete[] res;

    
    return;
}

// 1-D profiles handling
void showprofile_cb(Fl_Object *, void *)
{
    dbgprintf("Profile panel callback\n");

    if (profilePanel == 0) {
	profilePanel = new profile;
	profile &profileref = *profilePanel;
	profile_panel(profileref);
	// do our own init
	profilePanel->setDefaults();
    }
    // shows the panel
    profilePanel->show();
    
    return;
}

void startserver_cb(Fl_Object *, void *)
{
    
}

void stopserver_cb(Fl_Object *, void *)
{
    
}

// server download progress panel
void showprogress_cb(Fl_Object *, void *)
{
    dbgprintf("Progress panel callback\n");
    if (progressPanel == 0) {
	progressPanel = new progress;
	progress_panel(*progressPanel);
	// standard init
	progressPanel->setDefaults();
    }
    // show the panel
    progressPanel->show();
    
    return;
}

// multispectral image handling
void nextcomponent_cb(Fl_Object *, void *)
{
    IMAGEPARM *p;
    
    dbgprintf("Next sample\n");
    p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    if (p->comp < (IOBlackBox->getCurrImgNbSamples()-1)) {
	p->comp++;
	if (sliceComponent) {
	    sliceComponent->setSlice(p->comp);
	} else {
	    IOBlackBox->applyImageParameters(p, 0, 0, true); // we keep the current gamma 
	    // actually display the image
	    mainViewer->displayCurrentImage();    
	}
    }
	
    return;
}

void previouscomponent_cb(Fl_Object *, void *)
{
    IMAGEPARM *p;
    
    dbgprintf("Previous sample\n");
    p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    if (p->comp > 0) {
	p->comp--;
	if (sliceComponent) {
	    sliceComponent->setSlice(p->comp);
	} else {
	    IOBlackBox->applyImageParameters(p, 0, 0, true); // we keep the current gamma if defined
	    // actually display the image
	    mainViewer->displayCurrentImage();    
	}
    }

    return;
}

void showspectrum_cb(Fl_Object *, void *)
{
    dbgprintf("Spectra panel callback\n");

    if (spectraPanel == 0) {
	spectraPanel = new spectra;
	spectra &spectraref = *spectraPanel;
	spectra_panel(spectraref);
	// do our own init
	spectraPanel->setDefaults();
    }
    // shows the panel
    spectraPanel->show();
    
    return;
}

void showbivhist_cb(Fl_Object *, void *)
{
    dbgprintf("Bivariate histogram panel callback\n");

    if (bivHistPanel == 0) {
	bivHistPanel = new bivhist;
	bivhist &bivhistref = *bivHistPanel;
	bivhist_panel(bivhistref);
	// do our own init
	bivHistPanel->setDefaults();
    }
    // shows the panel
    bivHistPanel->show();
    
    return;
}

void setcomponentvalue(int v)
{
    IMAGEPARM    *p;
    
    dbgprintf("Set sample to %d\n", v);
    p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    
    // minimal check
    if ((v >= 0) && (v < IOBlackBox->getCurrImgNbSamples())) {
	p->comp = v;
	IOBlackBox->applyImageParameters(p, 0, 0, true); // we keep the gamma if defined
	mainViewer->displayCurrentImage();
    } else {
	errprintf("Sample number is out of bounds\n");
    }

    return;
}

void selectcomponent_cb(Fl_Object *, void *)
{
    IMAGEPARM    *p;
    int           cmin, cmax;
    
    dbgprintf("Select sample\n");
    p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    
    if (sliceComponent == 0) {
	sliceComponent = new slideinput;
	slider_panel(*sliceComponent);
	sliceComponent->setDefaults(); // own init
	sliceComponent->setWindowTitle("Select sample");
	sliceComponent->setValueTitle("sample:");
	sliceComponent->setSliceCB(setcomponentvalue);
    }
    cmin = 0;
    cmax = IOBlackBox->getCurrImgNbSamples()-1;
    sliceComponent->setLimits(cmin, cmax);
    sliceComponent->setSlice(p->comp);
    sliceComponent->show();

    return;
}


// 3-D bare bone functions
void showdepthprofile_cb(Fl_Object *, void *)
{
    dbgprintf("Depth profile panel callback\n");

    if (depthProfile == 0) {
	depthProfile = new spectra;
	spectra_panel(*depthProfile);
	// do our own init
	depthProfile->setDefaults();
	depthProfile->setWindowTitle("Depth profile panel");
	depthProfile->setXValueLabel("Plane:");
    }
    // shows the panel
    depthProfile->show();
    
    return;
}


void nextplane_cb(Fl_Object *, void *)
{
    IMAGEPARM *p;

    dbgprintf("Next plane\n");
    p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    if (p->zslice < (IOBlackBox->getCurrImgThickness()-1)) {
	p->zslice++;
	if (slice3D) {
	    slice3D->setSlice(p->zslice);
	} else {
	    IOBlackBox->applyImageParameters(p, 0, 0, true); // we keep the current gamma if defined
	    // actually display the image
	    mainViewer->displayCurrentImage();
	}
    }
    
    return;
}

void previousplane_cb(Fl_Object *, void *)
{
    IMAGEPARM *p;

    dbgprintf("Previous plane\n");
    p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    if (p->zslice > 0) {
	p->zslice--;
	if (slice3D) {
	    slice3D->setSlice(p->zslice);
	} else {
	    IOBlackBox->applyImageParameters(p, 0, 0, true); // we keep the current gamma if defined
	    // actually display the image
	    mainViewer->displayCurrentImage();
	}
    }
    
    return;
}

// Z offset is not dealt with as it should here
void setplanevalue(int v)
{
    IMAGEPARM    *p;
    int           zt = IOBlackBox->getCurrImgThickness();

    dbgprintf("Set plane to %d\n", v);
    p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    
    // minimal check
    if ((v >= 0) && (v < zt)) {
	p->zslice = v;
	IOBlackBox->applyImageParameters(p, 0, 0, true); // we keep the current gamma if defined
	mainViewer->displayCurrentImage();
    } else {
	errprintf("Plane number is out of bounds\n");
    }

    return;
}


void selectplane_cb(Fl_Object *, void *)
{
    IMAGEPARM    *p;
    int           cmin, cmax, zt = IOBlackBox->getCurrImgThickness();

    dbgprintf("Select plane\n");
    p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);

    if (slice3D == 0) {
	slice3D = new slideinput;
	slider_panel(*slice3D);
	slice3D->setDefaults();
	slice3D->setWindowTitle("Select plane");
	slice3D->setValueTitle("plane:");
	slice3D->setSliceCB(setplanevalue);
    }
    cmin = 0;
    cmax = zt-1;
    slice3D->setLimits(cmin, cmax);
    slice3D->setSlice(p->zslice);
    slice3D->show();

    return;
}

// unlike the other slices, this one allows loops (time loops
// are frequent, think of GIF animations.
void nextframe_cb(Fl_Object *, void *)
{
    IMAGEPARM *p;

    p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    if (p != 0) {
	if (p->frame < p->nbframes-1)
	    p->frame++;
	else
	    p->frame = 0;

	dbgprintf("Opening frame #%d for file %s\n",
		  p->frame, p->itempath);
	if (sliceSeries) {
	    sliceSeries->setSlice(p->frame);
	}
	openimage(p->itempath, p->frame);
    }
    
    return;
}

void previousframe_cb(Fl_Object *, void *)
{
    IMAGEPARM *p;
    
    p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    if (p != 0) {
	if (p->frame > 0)
	    p->frame--;
	else
	    p->frame = p->nbframes -1;
	dbgprintf("Opening frame #%d for file %s\n",
		  p->frame, p->itempath);

	if (sliceSeries) {
	    sliceSeries->setSlice(p->frame);
	}
	
	openimage(p->itempath, p->frame);
    }
    
    return;
}


void setframevalue(int v)
{
    IMAGEPARM    *p;
    int           pt;

    dbgprintf("Set frame to %d\n", v);
    p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
    if (p != 0) {
	pt =  p->nbframes;   // minimal check

	// minimal check
	if ((v >= 0) && (v < pt)) {
	    p->frame = v;
	    openimage(p->itempath, p->frame);
	} else {
	    errprintf("Frame number is out of bounds\n");
	}
    }

    return;
}

void selectframe_cb(Fl_Object *, void *)
{
    IMAGEPARM    *p;
    int           cmin, cmax, pt;
    
    dbgprintf("Select frame\n");
    p = (IMAGEPARM *)mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);

    if (p != 0) {
	pt = p->nbframes;
    
	if (sliceSeries == 0) {
	    sliceSeries = new slideinput;
	    slider_panel(*sliceSeries);
	    sliceSeries->setDefaults();
	    sliceSeries->setWindowTitle("Select frame");
	    sliceSeries->setSliceCB(setframevalue);
	}
	cmin = 0;
	cmax = pt-1;
	sliceSeries->setLimits(cmin, cmax);
	sliceSeries->setSlice(p->frame);
	sliceSeries->show();
    }

    return;
}

// Hmmmm.
int setClut(const char *label)
{
    int res = 0;
    IMAGEPARM *xlut;
    
    xlut = (IMAGEPARM *)mainMenuBar->getItemByLabel(label, CLUT_LIST_ID);
    if (xlut != 0)
	clut_cb(0, xlut); // hmmm
    else
	res = 1;

    return res;
}

// called when a CLUT is chosen for the current image
void clut_cb(Fl_Object *,void *arg)
{
    IMAGEPARM *xlut, *ximg;

    xlut = (IMAGEPARM *)arg;
    dbgprintf("CLUT callback called with argument %s\n", xlut->itempath);
    if (IOBlackBox->applyCLUTtoCurrentImage((const char *)xlut->itempath) == 0) {
        // we have successfully opened the image
        // remove the titleBox if it is still present
        removeSimpleBanner();
        // selects the CLUT
        mainMenuBar->addToItemList(xlut->itempath, CLUT_LIST_ID);
        // specify that we always want to display the current image with
        // the chosen LUT in the future.
        ximg = (IMAGEPARM *) mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID);
        if (ximg->clutpath != 0)
            free(ximg->clutpath);
        ximg->clutpath = strdup(xlut->itempath); // this should work.
        dbgprintf("Attaching clut %s to image%s\n", ximg->clutpath, ximg->itempath);
        // let us not forget to display the image!
        mainViewer->displayCurrentImage();
    }

    return;
}

// Remove all colour use with the image
void noclut_cb(Fl_Object *,void *)
{
    IMAGEPARM *ximg;
    Fl_Menu_Item  *selectedMenu;
    const char *currentfile;

    // get the selected image ID
    if ((ximg = (IMAGEPARM *) mainMenuBar->getSelectedItemArgument(IMAGE_LIST_ID)) != 0) {
        // delete the association with any colour map...
        if (ximg->clutpath != 0) {
            free(ximg->clutpath);
            ximg->clutpath = 0;
        }
        // unselects the current CLUT
        if ((selectedMenu = mainMenuBar->getSelectedMenuItem(CLUT_LIST_ID)) != 0) {
            // uncheck the thing
            selectedMenu->flags &= ~FL_PUP_CHECK;
        }
        currentfile = ximg->itempath;
        dbgprintf("Removing colour map associated with image%s\n", currentfile);
        // let us not forget to display the image!
	bool oldkeepPoints = fileprefs->keepPoints();
	fileprefs->keepPoints(true);
        openimage(currentfile);
	if (!oldkeepPoints)
	    fileprefs->keepPoints(false); // otherwise no change needed
    }

    return;
}


void userprefspanel_cb(Fl_Object *, void *)
{
    dbgprintf("User preferences panel callback\n");

    if (userprefsPanel == 0) {
	userprefsPanel = new userprefs;
	userprefs &userprefsref = *userprefsPanel;
	userprefs_panel(userprefsref);
	// do our own init
	userprefsPanel->setDefaults();
    }
    // shows the panel
    userprefsPanel->show();
    // set the debug stuff
    userprefsPanel->setDebug(debugIsOn);
    userprefsPanel->setStopDebug(stopDebug);
    
    return;
}

// sets or unsets the individual preferences
void preference_cb(Fl_Object *obj, void *arg)
{
    long           intarg;
    Fl_Menu_     *m = (Fl_Menu_*)obj;
    Fl_Menu_Item*      i = (Fl_Menu_Item*)(m->mvalue()); // get the menu item
    bool           currentMenuHideStatus = hideMainMenu;

    intarg = (long)(arg);
    // non-toggle ones
    if (intarg == PREF_WARNING)
	StatusBox::Instance()->show(); // easy...
    
    if (i->value()) {
	switch (intarg) {
        case PREF_SMOOTHUNZOOM:
	    mainViewer->drawSmooth(1);
	    break;
        case PREF_DEBUG:
	    debugIsOn = 1;
	    break;
        case PREF_STOPDEBUG:
	    stopDebug = 1;
	    break;
        case PREF_KEEPPOINTS:
	    keepPoints = 1;
	    break;
        case PREF_WRAPAROUND:
	    lutWraparound = true;
	    break;
        case PREF_MAKECHAR:
	    makeImageCharIfFits = 1;
	    break;
        case PREF_RGBRGB:
	    RGBdisplaysRGB = 1;
	    break;
        case PREF_FITALL:
	    fitCharOverAllSamples = 1;
	    IOBlackBox->flush_minmax_cache();
	    break;
        case PREF_FIT3D:
            fitCharOverAllSlices = 1;
	    IOBlackBox->flush_minmax_cache();
            break;
        case PREF_SYNCDISP:
            syncDisplay = true;
            break;
        case PREF_HIDEMENU:
            hideMainMenu = true;
            break;
	case PREF_FULLSCREEN:
	    fullscreen_cb(0,0);
	    break;
	default:
	    warnprintf("Unknown preference argument %d\n", intarg);
	    break;
	}
	
    } else {
	switch (intarg) {
        case PREF_SMOOTHUNZOOM:
	    mainViewer->drawSmooth(0);
	    break;
        case PREF_DEBUG:
	    debugIsOn = 0;
	    break;
        case PREF_STOPDEBUG:
	    stopDebug = 0;
	    break;
        case PREF_KEEPPOINTS:
	    keepPoints = 0;
	    break;
        case PREF_WRAPAROUND:
	    lutWraparound = false;
	    break;
        case PREF_MAKECHAR:
	    makeImageCharIfFits = 0;
	    break;
        case PREF_RGBRGB:
	    RGBdisplaysRGB = 0;
	    break;
        case PREF_FITALL:
	    fitCharOverAllSamples = 0;
	    IOBlackBox->flush_minmax_cache();
	    break;
        case PREF_FIT3D:
            fitCharOverAllSlices = 0;
	    IOBlackBox->flush_minmax_cache();
            break;
        case PREF_SYNCDISP:
            syncDisplay = false;
            break;
        case PREF_HIDEMENU:
            hideMainMenu = false;
            break;
	case PREF_FULLSCREEN:
	    fullscreen_cb(0,0);
	    break;
	default:
	    warnprintf("Unknown preference argument %d\n", intarg);
	    break;
	}
    }
    
    if (hideMainMenu != currentMenuHideStatus) { // avoid infinite loop
        dbgprintf("Hiding menu requested\n");
        if (hideMainMenu)
            mainWindow->removeMainMenu();
        else
            mainWindow->addMainMenu();
    }
    mainMenuBar->redraw();
    mainViewer->redraw();

    return;
}

void displaymode_cb(Fl_Object *obj, void *arg)
{
    long chosenmode = reinterpret_cast<long>(arg);
    dbgprintf("Display mode = %ld\n", chosenmode);
    mainViewer->setdisplaymode(static_cast<displaymode>(chosenmode));

    return;
}

// this acts as a toggle.
void fullscreen_cb(Fl_Object *, void *arg)
{
    static bool save_hidemenu_status = false;
    static double save_zoomfactor = 1.0;

    if (!fullScreen) {
	// coming into fullscreen, hide the main menu regardless of user preference
	save_hidemenu_status = hideMainMenu;
	hidemainmenu_cb(0,0);
    } else {
	if (!save_hidemenu_status)
	    // but only restablish it coming out of fs if the user wants the menu.
	    showmainmenu_cb(0,0);
    }
    mainViewer->toggle_fullscreen();
    if (fullScreen) {
	save_zoomfactor = mainViewer->zoomFactor();
	mainViewer->applyMaxZoomFactor(); // fill the screen!
    } else
	mainViewer->setDefaultZoomFactor(save_zoomfactor); // restore previous zoom

    dbgprintf("Fullscreen-status is now %s\n", (fullScreen) ? "on":"off");
    return;
}

void hidemainmenu_cb(Fl_Object *obj, void *arg)
{
    hideMainMenu = true;
    mainWindow->removeMainMenu();
    appMaxHeight += MENUHEIGHT;
    if (arg) {
	mainMenuBar->redraw();
	mainViewer->redraw();
    }
    return;
}

void showmainmenu_cb(Fl_Object *obj, void *arg)
{
    hideMainMenu = false;
    mainWindow->addMainMenu();
    appMaxHeight -= MENUHEIGHT;
    if (arg) {
	mainMenuBar->redraw();
	mainViewer->redraw();
    }
    return;
}


void mode_cb(Fl_Object *obj, void *arg)
{
    long           intarg;
    Fl_Menu_     *m = (Fl_Menu_*)obj;
    Fl_Menu_Item*      i = (Fl_Menu_Item*)(m->mvalue()); // get the menu item

    intarg = (long)(arg); 
    if (i->value()) {
	switch (intarg) {
	  case MODE_POINTFILE:
	    mainViewer->setPointMode(true);
	    break;
	}
    } else {
	switch (intarg) {
	  case MODE_POINTFILE:
	    mainViewer->setPointMode(false);
	    break;
	}
    }
    
    mainMenuBar->redraw();
    mainViewer->redraw();

    return;
}


// pointfile management
// open an existing pointfile
void openpointfile_cb(Fl_Object *,void *)
{
    static const char *currentfile = 0, *p = 0;

    dbgprintf("Opening new point file\n");
    
    p = fl_file_chooser("Open New Point File","*",currentfile);
    if (p)
        currentfile = p;
    if (p == 0) {
        dbgprintf("User cancelled choice\n");
    } else {
        dbgprintf("User chose %s\n", currentfile);
        // open a pointfile
	PtFileMngr->setPtFileName((char *)currentfile);
	// interpret its content
	PtFileMngr->readlist();
    }
    return;
}


void newpointfile_cb(Fl_Object *,void *)
{
    static const char *currentfile = 0, *p = 0;

    dbgprintf("Opening new point file\n");
    
    p = fl_file_chooser("Open New Point File","*",currentfile);
    if (p)
        currentfile = p;
    if (p == 0) {
        dbgprintf("User cancelled choice\n");
    } else {
        dbgprintf("User chose %s\n", currentfile);
        // open a pointfile
	PtFileMngr->setPtFileName((char *)currentfile);
    }
    return;
}

void closepointfile_cb(Fl_Object *,void *)
{
    static char buff[300];
    
    dbgprintf("Closing point file\n");
    if (PtFileMngr->savelist() == 0) {
	int nbpts = PtFileMngr->nbPointsInList();
	int nbbrks = PtFileMngr->nbBreaksInList();
	const char *pts = (nbpts > 1) ? "points":"point";
	const char *brks = (nbbrks > 1) ? "groups":"group";

	sprintf(buff, "%d %s in %d %s saved to\n%s\n",
		nbpts,
		pts,
		nbbrks,
		brks,
		PtFileMngr->getPtFileName());
	
	fl_message((const char *)buff);
    }
    return;
}

void deleteallpoints_cb(Fl_Object *,void *)
{
    PtFileMngr->rmAllPoints();
    mainViewer->displayCurrentImage(); // stronger than just redraw
    dbgprintf("Deleting all currently defined points\n");
    return;
}

void deletelastpoint_cb(Fl_Object *,void *)
{
    if (!PtFileMngr->rmSelectedPoints())
	PtFileMngr->rmLastPoint(); // delete the last point if none selected
    mainViewer->displayCurrentImage();
    dbgprintf("Deleting point from pointfile\n");
    return;
}

// set the XOR value when drawing a xor line
void setxorvalue_cb(Fl_Object *,void *)
{
    static char  currxortxt[100];
    
    sprintf(currxortxt, "%#x", PtFileMngr->xorValueForLines());
    
    const char *xortxt = fl_input("Enter xor value", currxortxt);
    int newxor = (xortxt != 0) ? (int)strtol(xortxt, 0, 0):PtFileMngr->xorValueForLines();

    // minimal check
    if (newxor > 0)
	PtFileMngr->xorValueForLines(newxor);
    else
	errprintf("Xor value must be positive\n");
    
    PtFileMngr->xorValueForLines(newxor);

    return;
}

// quits the application
void quit_cb(Fl_Object *,void *)
{
    // The End.
    // imview_exit(0);
    main_terminate = true;
}

// never quits the application
void noquit_cb(Fl_Object *,void *)
{
    dbgprintf("I will not quit!\n");
    // nothing
}
