// -*- C++ -*-
// $RCSfile: imageform.C,v $
// $Revision: 1.15 $
// $Author: langer $
// $Date: 2001/02/18 02:29:34 $

/* This software was produced by NIST, an agency of the U.S. government,
 * and by statute is not subject to copyright in the United States.
 * Recipients of this software assume all responsibilities associated
 * with its operation, modification and maintenance. However, to
 * facilitate maintenance we ask that before distributing modifed
 * versions of this software, you first contact the authors at
 * oof_manager@ctcms.nist.gov. 
 */

#include "adaptmesh.h"
#include "autodash.h"
#include "amtriangle.h"
#include "color.h"
#include "colorutils.h"
#include "coordinates.h"
#include "cursor.h"
#include "displaydash.h"
#include "filename.h"
#include "forms.h"
#include "gfx.h"
#include "goof.h"
#include "imagecanvas.h"
#include "imageform.h"
#include "image_drawer2.h"
#include "imagemandash.h"
#include "imagemoddash.h"
#include "infodash.h"
#include "menucolor.h"
#include "ppm2oof.h"
#include "rastack.h"
#include "selectdash.h"
#include "shape.h"
#include "stdlib.h"

#include "oofstipple.xbm"

Vec<ImageForm*> ImageForm::all_imageforms;
int ImageForm::imageformcount = 0;
int ImageForm::dflt_dashboard_no = 0;

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

// Pixel values used in RW colormaps. "pixel" here means "index into a
// colormap", not "coordinate in an image". That's the X definition.
// The actual RGB values are assigned to Color objects by the
// dashboards that control the color. Most of them are in
// displaydash.C.

unsigned long const ImageFormGfx::BLACKPIXEL = 0;
unsigned long const ImageFormGfx::WHITEPIXEL = 1;
unsigned long const ImageFormGfx::SELECTEDPIXEL = 2;
unsigned long const ImageFormGfx::FIREWALLPIXEL = 3;
unsigned long const ImageFormGfx::MESHPIXEL = 4;
unsigned long const ImageFormGfx::SELMESHPIXEL = 5;
unsigned long const ImageFormGfx::SELMESHFADEPIXEL = 6;
unsigned long const ImageFormGfx::NOMATERIALPIXEL = 7;
unsigned long const ImageFormGfx::RUBBERBANDPIXEL = 8;
unsigned long const ImageFormGfx::WALLPAPER_FG = 9;
unsigned long const ImageFormGfx::WALLPAPER_BG = 10;
// colors used in InfoDashboard:
unsigned long const ImageFormGfx::HIGHLIGHTPIXEL = 11; // for mesh triangle
unsigned long const ImageFormGfx::CLICKEDPIXEL = 12; // cell clicked on

int ImageFormGfx::nreserved_colors = 13; // number of colors listed above 
Color ImageFormGfx::rubberband_color_dflt(255, 255, 255);

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

ImageForm::ImageForm(Goof *gf, const Image &firstimage)
  : goof(gf),
    imageform_id(to_charstring(imageformcount)),
    current_dashboard_no(dflt_dashboard_no),
    beenclicked(0),
    inhibit_(0)
{
  imageformcount++;
  goof->imageforms.grow(1, this);
  all_imageforms.grow(1, this);

  // The menu must be created before the dashboards because the
  // dashboards install commands in the menu.
  menu = graphicsmenu()->AddSubMenu(imageform_id, "simulated mouse", "graphics",
#ifndef DEBUG
				    MOPT_SECRET |
#endif
				    MOPT_NOPRINT
				    );

  dashboardmenu = menu->AddSubMenu("dashboard", "simulated dashboard mouse");

  AddVariable(menu, "dashboardnumber", "set dashboard",
	      current_dashboard_no, setdashboard_cmd);
  menu->AddCommand(&robomousecmd);
  menu->AddCommand(&zoombuttoncmd);
  menu->AddCommand(&scrollcmd);
  menu->AddCommand(&resizecmd);
  menu->AddCommand("close", "close this window", close_cmd);
  menu->AddCommand(&savecmd);
}

ImageFormGfx::ImageFormGfx(Goof *gf, const Image &firstimage)
  : Form("PPM2OOF " + to_charstring(imageformcount)),
    ImageForm(gf, firstimage),
    //    logmotions(0),
    canvas(0), // so that it's not referred to until it's created
    rubberband_color(rubberband_color_dflt),
    black(0, 0, 0),
    white(255, 255, 255),
    wallpaper_fg(wallpaper_foreground),
    wallpaper_bg(wallpaper_background)
{
  // Create graphics
  fdui = create_form_image_drawer();
  fdui->vdata = this;
  the_form = fdui->image_drawer;

  fl_set_object_color(fdui->background, COL_BACKGROUND, 0);
  fl_set_object_shortcutkey(fdui->button_home, XK_Home);

  // The colormap must be created before the dashboards, because
  // colors are stored and set in the dashboards.
  xcolormap = gfxinfo.colormap();

  // The dashboards must be created before the canvas, because the
  // canvas has callbacks that refer to the dashboards (in particular,
  // when the pixmap is created the limits of the visible region are
  // displayed in the CoordDashboard).

  // The order in which the dashboards are installed with adddashboard
  // is the order in which they appear in the dashboard selector.

  coorddash = new CoordDashboard(this, fdui->coordinates);
  displaydash = new DisplayDashboard(this, fdui->display);
  selectdash = new SelectDashboard(this, fdui->selectmethod);
  pixelinfodash = new PixelInfoDashboard(this, fdui->pixelinfo);
  meshinfodash = new MeshInfoDashboard(this, fdui->meshinfo);
  imagemanager = new ImageManDashboard(this, fdui->imagemanager);
  imagemodification = new ImageModDashboard(this, fdui->imagemodification);
  automation = new AutomationDashboard(this, fdui->automation);

  adddashboard(displaydash);
  adddashboard(coorddash);
  adddashboard(selectdash);
  adddashboard(pixelinfodash);
  adddashboard(meshinfodash);
  adddashboard(imagemodification);
  adddashboard(imagemanager);
  adddashboard(automation);
  fl_set_choice_fontstyle(fdui->current_dashboard_menu, FL_BOLD_STYLE);
  fl_set_choice(fdui->current_dashboard_menu, current_dashboard_no+1);
  currentdashboard = dashboard[current_dashboard_no];

  // create the canvas

  canvas = new ImageCanvas(this, fdui->image_region->x, fdui->image_region->y,
			   fdui->image_region->w, fdui->image_region->h,
			   goof->query_width(), goof->query_height(),
			   coorddash->scalefactor,
			   &CB_scrollcallback);

  fl_set_form_atclose(the_form, wm_close, this);

  // allow form to be shrunk below original size
  fl_set_form_minsize(the_form, 100, 100);

  canvas->set_colormap(xcolormap);

  // These colors aren't set in the dashboards
  getpixel(white, WHITEPIXEL);
  getpixel(black, BLACKPIXEL);
  getpixel(wallpaper_bg, WALLPAPER_BG);
  getpixel(wallpaper_fg, WALLPAPER_FG);
  getpixel(rubberband_color, RUBBERBANDPIXEL);

  set_all_colors_(); // won't be needed once all colors defs are in dashboards ?

  canvas->set_zoombutton_callback(CB_zoombutton, this);
  canvas->set_xyscrollcallbacks(CB_xscroll, CB_yscroll, this);
  canvas->set_resize_callback(CB_resize, this);
  canvas->setstipple(oofstipple_width, oofstipple_height, oofstipple_bits,
		     wallpaper_fg.pixel, wallpaper_bg.pixel);

  // the dashboards can't be hidden or shown until after the canvas is
  // created, because switching dashboards changes how the canvas
  // handles mouse clicks.

  // when displaying on an SGI running IRIX6.4, an X error is
  // generated during the first hide_dashboard() ???

  for(int i=0; i<dashboard.capacity(); i++)
    dashboard[i]->hide_dashboard();

  // Put the given image on the stack.
  // This makes a copy of the image!
  imagemodification->imagestack.push(firstimage);
  update_gallery_listing(goof->gallery);
  fl_select_browser_line(fdui->browser_imagelist, goof->gallerysize());

  show(0);

  // do these after show() so that the window exists, and the cursor can be set
  currentdashboard->show_dashboard();
  currentdashboard->activate();
  draw();
  canvas->do_scrollcallback();
}

//----------------------

ImageFormTxt::ImageFormTxt(Goof *gf, const Image &firstimage)
  : ImageForm(gf, firstimage)
{
  // The order in which the dashboards are installed with adddashboard
  // is the order in which they appear in the dashboard selector.

  coorddash = new CoordDashboard(this, 0);
  displaydash = new DisplayDashboard(this, 0);
  selectdash = new SelectDashboard(this, 0);
  pixelinfodash = new PixelInfoDashboard(this, 0);
  meshinfodash = new MeshInfoDashboard(this, 0);
  imagemanager = new ImageManDashboard(this, 0);
  imagemodification = new ImageModDashboard(this, 0);
  automation = new AutomationDashboard(this, 0);

  adddashboard(displaydash);
  adddashboard(coorddash);
  adddashboard(selectdash);
  adddashboard(pixelinfodash);
  adddashboard(meshinfodash);
  adddashboard(imagemodification);
  adddashboard(imagemanager);
  adddashboard(automation);
  currentdashboard = dashboard[current_dashboard_no];

  // Put the given image on the stack.
  // This makes a copy of the image!
  imagemodification->imagestack.push(firstimage);
  update_gallery_listing(goof->gallery);
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//


// put a dashboard in the menu and in the list of dashboards
void ImageFormGfx::adddashboard(Dashboard *db) {
  dashboard.grow(1, db);
  fl_addto_choice(fdui->current_dashboard_menu, db->name_.charstar());
}

void ImageFormTxt::adddashboard(Dashboard *db) {
  dashboard.grow(1, db);
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

ImageForm::~ImageForm() {
  for(int i=0; i<dashboard.capacity(); i++)
    delete dashboard[i];
  menu->RemoveOption("dashboardnumber");
  menu->RemoveOption("close");
  delete menu; // robomouse menu
  graphicsmenu()->RemoveOption(imageform_id);
  goof->imageforms.remove(this);
  // don't remove this from all_imageforms, or get_imageform() won't
  // work, but zero out the dangling pointer.
  for(int i=0; i<all_imageforms.capacity(); i++)
    if(all_imageforms[i] == this)
      all_imageforms[i] = 0;
}

ImageFormGfx::~ImageFormGfx() {
  gfxinfo.freecolormap(xcolormap);
  delete canvas;
  free(fdui);
}

ImageFormTxt::~ImageFormTxt() {}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

int ImageFormGfx::wm_close(FL_FORM*, void *ptr) {  // Window-manager close
  ((ImageFormGfx*) ptr)->close_gui();
  return FL_IGNORE;
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

void ImageFormGfx::change_goof(Goof *newgoof) {
  goof->imageforms.remove(this);
  newgoof->imageforms.grow(1, this);
  goof = newgoof;
  canvas->resizepixmap(goof->query_width(), goof->query_height());
  canvas->setcoords(0, goof->query_width(), 0, goof->query_height());
  set_picture(*goof->get_image(goof->gallerysize() - 1));
  update_gallery_listing(goof->gallery);
}

void ImageFormTxt::change_goof(Goof *newgoof) {
  goof->imageforms.remove(this);
  newgoof->imageforms.grow(1, this);
  goof = newgoof;
  update_gallery_listing(goof->gallery);
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

void ImageFormGfx::mesh_created() const {
  displaydash->activate_mesh_controls();
  meshinfodash->mesh_created();
}

void ImageFormGfx::mesh_destroyed() const {
  displaydash->deactivate_mesh_controls();
  meshinfodash->mesh_destroyed();
}

void ImageFormGfx::triangle_destroyed(const AMTriangle *tri) const {
  meshinfodash->triangle_destroyed(tri);
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

// Form an object is in, used by callbacks
ImageFormGfx *ImageFormGfx::objform(FL_OBJECT *obj) {
  return (ImageFormGfx*) ((FD_image_drawer*)(obj->form->fdui))->vdata;
}

// Dashboard an object is in, used by callbacks
Dashboard *ImageFormGfx::objdash(FL_OBJECT *obj) {
  return objform(obj)->current_dashboard();
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

// Get a pointer to a dashboard, using information from a command path.

Dashboard *ImageForm::get_dashboard(const CharString &nm) const {
  for(int i=0; i<dashboard.capacity(); i++)
    if(nm == dashboard[i]->name())
      return dashboard[i];
  return 0;
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

CharString ImageForm::menucommand() const {
  return "graphics " + imageform_id + " ";
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

void ImageFormGfx::robomouse(const MouseClick &click) {
  canvas->robomouse(click);
}

void ImageFormTxt::robomouse(const MouseClick &click) {
  // This is an amalgam of ScrollCanvas::robomouse() and
  // ImageCanvas::mouse() to handle scripted mouse clicks in the
  // absence of a gui.
  static MouseClick lastclick;
  if(click.event == MOUSE_DN)
    lastclick = click;

  garcon()->log(menucommand() + "mouse click = " + to_charstring(click));
  if(click.event == MOUSE_DN)
    return;			// don't do anything until mouse goes up
  if(click.event == MOUSE_UP) {
    if(!lastclick) return;	// up but not down makes no sense
    if(click.window != lastclick.window) return;
    if(click.button != lastclick.button) return;
    if(abs(click.xpoint.x - lastclick.xpoint.x) > ImageCanvas::dragmove ||
       abs(click.xpoint.y - lastclick.xpoint.y) > ImageCanvas::dragmove)
      current_dashboard()->draghandler(lastclick, click);
    else
      current_dashboard()->clickhandler(click);
    return;
  }
  if(click.event == MOUSE_MV) {
    // does this have to be handled? I don't think so....
  }
  if(click.event == MOUSE_UP)
    lastclick.event = MOUSE_TRAP;
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

void ImageFormGfx::draw() {
  if(inhibit_) return;
  current_dashboard()->update();

  // if the material is being displayed by triangles, special things
  // happen, so set a flag if this is the case.
  int trimatdisp = displaydash->material_display_method == MDM_TRIANGLE &&
       displaydash->background == BG_MATERIAL;

  // which image is being drawn?
  if(displaydash->background == BG_IMAGE)
    localimage.copy_image(current_picture());
  else { // BG_MATERIAL
    localimage.copy_image(goof->materialimage);
    goof->mark_nomaterial(localimage, displaydash->nomaterialcolor);
  }

  // mark selections and other modifications
  if(!trimatdisp) {
    if(displaydash->selected_pixels_visible)
      goof->mark_selected_pixels(localimage, displaydash->selectedcolor);
    if(displaydash->firewalls_visible)
      goof->mark_firewalls(localimage, displaydash->firewallcolor);
  }

  // fade the pixels outside the active region
  if(!trimatdisp) {
    ArrayIterator iter(localimage);
    Cell_coordinate pxl;
    while(iter(pxl))
      if(!current_goof->active(pxl))
	localimage[pxl] = localimage[pxl].fade(ActiveArea::fade);
  }

  if(!trimatdisp) {
    localimage.getpixels(reserved_colors(), colormap());
    canvas->loadimage(&localimage);	// copy image to pixmap
  }

  // remaining things are drawn directly onto the pixmap because they
  // should be drawn at the screen resolution, not the image
  // resolution.
  if(goof->mesh()) {
    if(trimatdisp) {
      goof->mesh()->inherit_pixel_materials();
      goof->mesh()->setcolors(displaydash->nomaterialcolor);
      // If drawing material and selection, it's easiest to do them
      // both at once. So pretend that the material color is the
      // selected color for the selected triangles.
      if(displaydash->selected_mesh_visible)
	goof->mesh()->setselectedcolors(displaydash->selectedmeshcolor);
      goof->mesh()->getpixels(reserved_colors(), colormap());
      goof->mesh()->draw_material(*canvas);
    }
    else {
      // Draw selection, but not material
      if(displaydash->selected_mesh_visible)
	goof->mesh()->draw_selected(*canvas, displaydash->selectedmeshcolor,
				    displaydash->selectedmeshfadecolor);
    }
  }
  current_dashboard()->draw_extras();
  if(goof->mesh() && displaydash->mesh_visible)	// draw mesh last!
    goof->mesh()->draw(*canvas, displaydash->meshcolor, displaydash->meshwidth);
  canvas->refresh();		// copy pixmap to screen
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

int ImageFormGfx::reserved_colors() const {
  return nreserved_colors;
}

void ImageFormGfx::getpixel(Color &color, unsigned long pixelno) {
  switch(gfxinfo.c_class()) {
  case TrueColor:
    color.RO_pixel();
    break;
  case PseudoColor:
    color.RW_pixel(xcolormap, pixelno);
    break;
  }
}

void ImageFormGfx::set_all_colors_() {
  getpixel(white, WHITEPIXEL);
  getpixel(black, BLACKPIXEL);
  getpixel(wallpaper_fg, WALLPAPER_FG);
  getpixel(wallpaper_bg, WALLPAPER_BG);
  getpixel(displaydash->firewallcolor, FIREWALLPIXEL);
  getpixel(displaydash->selectedcolor, SELECTEDPIXEL);
  getpixel(displaydash->meshcolor, MESHPIXEL);
  getpixel(displaydash->selectedmeshcolor, SELMESHPIXEL);

  displaydash->selectedmeshfadecolor =
    displaydash->selectedmeshcolor.fade(ActiveArea::fade);
  getpixel(displaydash->selectedmeshfadecolor, SELMESHFADEPIXEL);

  getpixel(displaydash->nomaterialcolor, NOMATERIALPIXEL);
  getpixel(rubberband_color, RUBBERBANDPIXEL);
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

void ImageFormGfx::center_image() {
  canvas->fullzoom();
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

void ImageFormGfx::setcursor(const XCursor &cursor) {
  if(the_form->visible && canvas->visible()) {
    Window w = canvas->window();
    if(w)
      XDefineCursor(gfxinfo.display(), w, cursor);
  }
}

void ImageFormGfx::unsetcursor() {
  if(the_form->visible && canvas->visible()) {
    Window w = canvas->window();
    if(w)
      XDefineCursor(gfxinfo.display(), w, None);
  }
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

void ImageFormGfx::CB_zoombutton(int button, void *ptr) {
  ((ImageFormGfx*) ptr)->zoombutton_callback(button);
}

void ImageFormGfx::zoombutton_callback(int button) {
  garcon()->log(menucommand() + "zoombutton button=" + to_charstring(button));
}

void ImageFormGfx::zoombutton(int button) {
  canvas->zoom_button(button);
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

void ImageFormGfx::CB_scrollcallback(const MeshCoord &ul, const MeshCoord &lr,
			       void *ptr)
{
  ((ImageFormGfx*) ptr)->scrollcallback(ul, lr);
}

void ImageFormGfx::scrollcallback(const MeshCoord &ul, const MeshCoord &lr) {
  coorddash->show_range(ul, lr);
}

void ImageFormGfx::CB_xscroll(int dx, void *ptr) {
  ((ImageFormGfx*) ptr)->xscroll_callback(dx);
}

void ImageFormGfx::xscroll_callback(int dx) {
  if(dx == 0) return;
  garcon()->log(menucommand() + "scroll dx=" + to_charstring(dx) + " dy=0");
}

void ImageFormGfx::CB_yscroll(int dy, void *ptr) {
  ((ImageFormGfx*) ptr)->yscroll_callback(dy);
}

void ImageFormGfx::yscroll_callback(int dy) {
  if(dy == 0) return;
  garcon()->log(menucommand() + "scroll dx=0 dy=" + to_charstring(dy));
}

void ImageFormGfx::xyscroll(int dx, int dy) {
  canvas->scroll(dx, dy);
}

void ImageFormGfx::CB_resize(int, int, void *ptr) { // size has changed
  ((ImageFormGfx*) ptr)->resize_callback();
}

void ImageFormGfx::resize_callback() {
  garcon()->log(menucommand() + "resize w=" + to_charstring(the_form->w) +
		" h=" + to_charstring(the_form->h));
}

void ImageFormGfx::resize(int w, int h) {  // change size
  // unless minsize is set explicitly, the window manager won't let the
  // form be shrunk below w by h.
  fl_set_form_minsize(the_form, 100, 100);

  fl_set_form_size(the_form, w, h);

  // This is an ugly kludge. Changing the size of the form generates
  // some exposure events, which have to be handled so that all the
  // objects on the form are redrawn properly. I don't know how many
  // events have to be processed. 100 is probably too many.
  fl_deactivate_all_forms();
  for(int i=0; i<100; i++)
    fl_check_only_forms();
  fl_activate_all_forms(); 
}

//  double ImageFormGfx::scalefactor() const {
//    return coorddash->scalefactor;
//  }

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

void ImageFormGfx::CB_dashboard(FL_OBJECT *obj, long) {
  objform(obj)->set_dashboard_gui();
}

void ImageFormGfx::set_dashboard_gui() {
  int which = fl_get_choice(fdui->current_dashboard_menu) - 1;
  set_dashboard(which);
  garcon()->log(menucommand() + "dashboardnumber = " + to_charstring(which));
}

// called by menus, which sets current_dashboard_no
void ImageForm::setdashboard_cmd(const CharString&) { // static
  get_imageform()->set_dashboard();
}

// set dashboard to pulldown menu choice, and set default dashboard
// for new windows
void ImageFormGfx::set_dashboard() {
  dflt_dashboard_no = current_dashboard_no;
  fl_set_choice(fdui->current_dashboard_menu, dflt_dashboard_no+1);
  set_dashboard(current_dashboard_no);
}

void ImageFormTxt::set_dashboard() {
  dflt_dashboard_no = current_dashboard_no;
  currentdashboard = dashboard[current_dashboard_no];
}

void ImageFormGfx::set_dashboard(int which) {
  if(currentdashboard != dashboard[which]) {
    fl_freeze_form(the_form);
    dflt_dashboard_no = which;
    int redraw = currentdashboard->hide_dashboard();
    fl_redraw_object(fdui->dashboard_area); // cover up redrawing mistakes
    current_dashboard_no = which;
    currentdashboard = dashboard[current_dashboard_no];
    redraw |= currentdashboard->show_dashboard();
    if(redraw)
      draw();
    fl_unfreeze_form(the_form);
  }
}

Dashboard *ImageForm::current_dashboard() const {
  return currentdashboard;
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

void ImageFormGfx::CB_close(FL_OBJECT *obj, long) { // static
  objform(obj)->close_gui();
}

void ImageFormGfx::close_gui() {
  // Close the form before logging the "close" command, so that
  // dashboards that log changes in their parameters log them before
  // the "close" command is logged. But the menucommand() function
  // won't get the proper index for the graphics window after the
  // window is closed, so it's necessary to call menucommand() before
  // closing the window.
  CharString logstring = menucommand() + "close";
  close();
  garcon()->log(logstring);
}

CommandFn ImageForm::close_cmd() { // static
  get_imageform()->close();
}

void ImageFormGfx::close() {
  fl_freeze_form(the_form);
  currentdashboard->hide_dashboard(); // give dashboard a chance to clean up
  hide();
  delete this;
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

void ImageFormGfx::save_image(const FileName &filename) const {
  FILE *file = fopen(filename, "w");
  if(file) {
    if(filename.tail(3) == ".ps" || filename.tail(4) == ".eps")
      draw_ps(file);
    else
      canvas->ppmfile(file);
    fclose(file);
  }
}

void ImageFormGfx::CB_save(FL_OBJECT *obj, long) {
  objform(obj)->save_gui();
}

void ImageFormGfx::save_gui() const {
  static FileName lastname;
  FileName filename;
  if(lastname)
    filename = FileName(fl_show_fselector("Enter PPM or PS file name", ".",
					  "*.p[bgp]m", lastname.charstar()));
  else
    filename = FileName(fl_show_fselector("Enter PPM or PS file name", ".",
					  "*.p[bgp]m", ""));
  if(filename) {
    lastname = filename;
    save_image(filename);
    garcon()->log(menucommand() + "save filename=" + filename);
  }
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

void ImageFormGfx::CB_home(FL_OBJECT *obj, long) {
  objform(obj)->home();
}

void ImageFormGfx::home() {
  formwaiter().show_homeform();
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

void ImageFormGfx::set_undoredo_buttons(int undoable, int redoable) {
  fl_freeze_form(fdui->image_drawer);
  if(undoable) {
    fl_set_object_lcol(fdui->button_undo_selection, FL_BLACK);
    fl_activate_object(fdui->button_undo_selection);
  }
  else {
    fl_set_object_lcol(fdui->button_undo_selection, FL_INACTIVE_COL);
    fl_deactivate_object(fdui->button_undo_selection);
  }
  if(redoable) {
    fl_set_object_lcol(fdui->button_redo_selection, FL_BLACK);
    fl_activate_object(fdui->button_redo_selection);
  }
  else {
    fl_set_object_lcol(fdui->button_redo_selection, FL_INACTIVE_COL);
    fl_deactivate_object(fdui->button_redo_selection);
  }
  fl_unfreeze_form(fdui->image_drawer);
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

void ImageForm::update_gallery_listing(const Vec<NamedImage*> &gallery) {
  if(imagemanager)
    imagemanager->update_gallery_listing(gallery);
}

const Image &ImageForm::current_picture() const {
  return *imagemodification->imagestack.current();
}

Image &ImageForm::current_picture() {
  return *imagemodification->imagestack.current();
}

void ImageFormGfx::set_picture(const Image &image) {
  if(imagemodification->imagestack.atbottom()
     || garcon()->question("OK to overwrite modified image in Window "
			   + imageform_id + "?", 0)) {
    imagemodification->imagestack.clear();
    imagemodification->imagestack.push(image);
    imagemodification->set_mod_undoredobuttons();
    draw();
  }
}

void ImageFormTxt::set_picture(const Image &image) {
  if(imagemodification->imagestack.atbottom()) {
    imagemodification->imagestack.clear();
    imagemodification->imagestack.push(image);
  }
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

// configuration file stuff

// format:
// imageform   (starts parsing from imageformconfigmenu)
// image       (repeat for each image on stack)
//   ppm data  (repeat for each image on stack)
// currentimage = n
// end_imageform (stops parsing from imageformconfigmenu)

void ImageForm::save_imageforms(ostream &file) { // static
  for(int i=0; i<all_imageforms.capacity(); i++)
    if(all_imageforms[i])
      all_imageforms[i]->save(file);
}

void ImageForm::save(ostream &file) const {
  file << "imageform" << endl;
  // the actual images
  for(int i=0; i<imagemodification->imagestack.size(); i++) {
    file << "image" << endl;
    file << *imagemodification->imagestack[i] << endl;
  }
  // index of current image
  file << "currentimage=" << imagemodification->imagestack.current_index()
       << endl;

  save_extras(file);
  menu->print(file, "");
  file << "end_imageform" << endl;
}

void ImageFormGfx::save_extras(ostream &file) const {
  file << "resize w=" << the_form->w << " h=" << the_form->h << endl;
  if(beenclicked)
    file << "clicked=1" << endl;
  file << "scrollbars" << endl;
  canvas->save_scroll(file);
}


// static data used when creating imageform from the config file
istream *ImageForm::configfile = 0;// the configuration file
ImageForm *ImageForm::incipient_form = 0; // form being created
bool ImageForm::done_loading = 0; // are we done yet?
int ImageForm::currentimage = 0; // image currently displayed
Menu *ImageForm::configmenu = 0;

CommandFn ImageForm::set_end_imageform() { // static
  done_loading = 1;
}

CommandFn ImageForm::load_image() { // static
  Image image;
  *configfile >> image;
  if(*configfile) {
    if(incipient_form == 0) {
      // having read the first image, we can now create the form
      if(textmode)
	incipient_form = new ImageFormTxt(current_goof, image);
      else
	incipient_form = new ImageFormGfx(current_goof, image);
      // add the form's variables and commands to the menu
      configmenu->AddSubMenu("dashboard", "simulated dashboards",
			     incipient_form->dashboardmenu);
      AddVariable(configmenu, "clicked", "has the form been clicked on?",
		  incipient_form->beenclicked);
      AddVariable(configmenu, "dashboardnumber", "set dashboard",
		  incipient_form->current_dashboard_no, setdashboard_cmd);

      // don't let any of the commands have any effect until the form
      // is completely read
      incipient_form->inhibit_drawing();
    }
    else
      incipient_form->imagemodification->imagestack.push(image);
  }
}

void ImageForm::set_currentimage(const CharString&) { // static
  if(incipient_form)
    incipient_form->imagemodification->imagestack.go_to(currentimage);
}

CommandFn ImageForm::read_scrollbars() { // static
  if(incipient_form)
    incipient_form->readscrollbars();
}

void ImageFormGfx::readscrollbars() { // virtual
  canvas->read_scroll(*configfile);
}

void ImageFormTxt::readscrollbars() {
  char discard[1000];
  configfile->getline(discard, sizeof(discard));
}

void ImageForm::read_imageform(istream &file) { // static

  // ImageFormGfxs are read from the config file by parsing a menu. The
  // form is created only *after* the first image is read by the
  // "image" command.  A pointer to the ImageFormGfx is stored in
  // incipient_form so that subsequent menu commands can use it.  The
  // command "end_imageform" sets the variable done_loading, which
  // indicates that the parsing should stop.
  done_loading = 0;
  incipient_form = 0;
  currentimage = 0;
  configfile = &file;

  // Create a new menu. Since each form that's read adds local
  // variables to the menu, the menu has to be recreated for each new
  // form.  The commands that can be done before the form is created
  // are added here; the ones that depend on the form's existence are
  // added in localconfigmenu().
  if(configmenu)
    delete configmenu;
  configmenu = new Menu("imageformconfig");
  configmenu->AddCommand("image", "load an image", load_image);
  configmenu->AddCommand(new ResizeCmd);
  configmenu->AddCommand("scrollbars", "set scrollbars", read_scrollbars);
  configmenu->AddCommand("end_imageform", "stop reading imageform data",
		   set_end_imageform);
  AddVariable(configmenu, "currentimage", "set current image", currentimage,
	      set_currentimage);
  // run until end_imageform() returns 1, which is after the
  // "end_imageform" command has been executed.
  garcon()->runfile(file, configmenu, 0, end_imageform);

  if(incipient_form) {
    incipient_form->allow_drawing();
    incipient_form->imagemodification->set_mod_undoredobuttons();
    incipient_form->set_all_colors();
    incipient_form->draw();
  }
}
