/* xmorph.c : Digital Image Warping for X Window System
**
** A graphical user interface to a mesh warping algorithm
**
** Written and Copyright (C) 1994, 1995, 1996 by Michael J. Gourlay
**
** NO WARRANTEES, EXPRESS OR IMPLIED.
*/

#include <stdio.h>

#ifdef AMIGA
#include "amigapragmas.h"
#endif

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>

#include <X11/Xaw/Form.h>
#include <X11/Xaw/Box.h>
#include <X11/Xaw/Paned.h>
#include <X11/Xaw/Scrollbar.h>
#ifdef AMIGA
#include <exec/exec.h>
#include <proto/exec.h>
#endif

#include "diw_map.h"
#include "RgbaImage.h"
#include "image_diw.h"
#include "sequence.h"
#include "file_menu.h"
#include "mesh_menu.h"
#include "warp_menu.h"
#include "help_menu.h"
#include "my_malloc.h"




int verbose = FALSE;




/* --------------------------------------------------------------- */

/* actions: action-to-function mappings
** determines the behaviour of this application
*/
static XtActionsRec actions[] = {
  {"refresh", RefreshImage},
  {"draw_meshes", DrawMeshes},
  {"draw_all_meshes", DrawAllMeshes},
  {"redither", ReditherImage},
  {"redither_all", ReditherAllImages},
  {"warp", WarpImage},
  {"change_mesh", ChangeMeshLine},
  {"pick", PickMeshpoint},
  {"unpick", UnpickMeshpoint},
  {"drag", DragMeshpoint},
  {"start_drag", StartDragMeshpoint},
  {"warp_sequence", WarpSequence},
};

/* --------------------------------------------------------------- */

/* fallback_resources: default application resources
** determines the look of this application
*/
String fallback_resources[] = {
  /* Application looks */
  "XMorph*font:                  -*-helvetica-medium-r-normal-*-14-*-*-*-*-*-*",
  "XMorph*foreground:                    MidnightBlue",
  "XMorph*background:                    LightGray",
  "XMorph*borderColor:                   DimGray",

  /* Dialog look and feel */
  "XMorph*Dialog.label.font:       -*-helvetica-bold-r-normal-*-14-*-*-*-*-*-*",
  "XMorph*Dialog.value:                    ",
  "XMorph*Dialog.Command.foreground:       black",
  "XMorph*Dialog.cancel.label:             Cancel",
  "XMorph*Dialog.okay.label:               Okay",
  "XMorph*Dialog.Text.translations:        #override <Key>Return: mjg_okay()",

  /* Menu looks */
  "XMorph*MenuButton.font:       -*-helvetica-bold-o-normal-*-14-*-*-*-*-*-*",
  "XMorph*SimpleMenu*font:       -*-helvetica-bold-o-normal-*-14-*-*-*-*-*-*",
  "XMorph*SimpleMenu*leftMargin:         24",
  "XMorph*SimpleMenu*rightMargin:        24",

  /* File titlebar menu */
  "XMorph*file_menu_button.label:        File",
  "XMorph*file_menu.fm_osi_sme.label:    Open source image...",
  "XMorph*file_menu.fm_odi_sme.label:    Open destination image...",
  "XMorph*file_menu.fm_osm_sme.label:    Open source mesh...",
  "XMorph*file_menu.fm_odm_sme.label:    Open destination mesh...",
  "XMorph*file_menu.fm_ssi_sme.label:    Save source image...",
  "XMorph*file_menu.fm_sdi_sme.label:    Save destination image...",
  "XMorph*file_menu.fm_ssm_sme.label:    Save source mesh...",
  "XMorph*file_menu.fm_sdm_sme.label:    Save destination mesh...",
  "XMorph*file_menu.fm_quit_sme.label:   Quit",
  "XMorph*TransientShell.md_osi.label:     Load source image from filename:",
  "XMorph*TransientShell.md_odi.label:     Load destination image from filename:",
  "XMorph*TransientShell.md_osm.label:     Load source mesh from filename:",
  "XMorph*TransientShell.md_odm.label:     Load destination mesh from filename:",
  "XMorph*TransientShell.md_ssm.label:     Save source mesh to filename:",
  "XMorph*TransientShell.md_sdm.label:     Save destination mesh to filename:",
  "XMorph*TransientShell.md_si.label:      Save image to filename:",

  /* Mesh titlebar menu */
  "XMorph*mesh_menu_button.label:        Mesh",
  "XMorph*mesh_menu.mm_rsm_sme.label:    Reset source mesh",
  "XMorph*mesh_menu.mm_rdm_sme.label:    Reset destination mesh",
  "XMorph*mesh_menu.mm_msm_sme.label:    Functionalize source mesh",
  "XMorph*mesh_menu.mm_mdm_sme.label:    Functionalize destination mesh",

  /* Warp titlebar menu */
  "XMorph*warp_menu_button.label:        Morph Sequence",
  "XMorph*warp_menu.wm_ssfn_sme.label:   Set sequence file name...",
  "XMorph*warp_menu.wm_ssns_sme.label:   Set sequence number of steps...",
  "XMorph*warp_menu.wm_seq_sme.label:    Warp sequence",
  "XMorph*TransientShell.md_ssfn.label:    Warp Sequence filename:",
  "XMorph*TransientShell.md_ssns.label:    Number of warp frames:",
  "XMorph*TransientShell.md_ssfn.value:    warp",
  "XMorph*TransientShell.md_ssns.value:    30",

  /* Help titlebar menu */
  "XMorph*help_menu_button.label:          Help",
  "XMorph*help_menu_button.horizDistance:  100",
  "XMorph*help_menu.hm_about_sme.label:    About...",
  "XMorph*help_menu.hm_mmp_sme.label:      Manipulating the mesh...",
  "XMorph*help_menu.hm_file_sme.label:     File menu help...",
  "XMorph*help_menu.hm_mesh_sme.label:     Mesh menu help...",
  "XMorph*help_menu.hm_warp_sme.label:     Warp menu help...",
  "XMorph*help_menu.hm_dpm_sme.label:      DIW Properties menu help...",
  "XMorph*help_menu.hm_dcm_sme.label:      DIW Commands menu help...",
  "XMorph*help_menu.hm_quit_sme.label:     Quit XMorph",

  "XMorph*TransientShell.md_h_about.Text*font: -*-helvetica-medium-r-normal-*-18-*-*-*-*-*-*",
  "XMorph*TransientShell.md_h_about.label:                About XMorph",
  "XMorph*TransientShell.md_h_about.Text.width:           640",
  "XMorph*TransientShell.md_h_about.Text.height:          480",
  "XMorph*TransientShell.md_h_about.Text.scrollHoriontal: whenneeded",
  "XMorph*TransientShell.md_h_about.okay.label:           Dismiss",

  "XMorph*TransientShell.md_h_mmp.Text*font: -*-helvetica-medium-r-normal-*-18-*-*-*-*-*-*",
  "XMorph*TransientShell.md_h_mmp.label:                Manipulating the mesh",
  "XMorph*TransientShell.md_h_mmp.Text.width:           640",
  "XMorph*TransientShell.md_h_mmp.Text.height:          480",
  "XMorph*TransientShell.md_h_mmp.Text.scrollHoriontal: whenneeded",
  "XMorph*TransientShell.md_h_mmp.okay.label:           Dismiss",

  "XMorph*TransientShell.md_h_file.Text*font: -*-helvetica-medium-r-normal-*-18-*-*-*-*-*-*",
  "XMorph*TransientShell.md_h_file.label:                File menu Help",
  "XMorph*TransientShell.md_h_file.Text.width:           640",
  "XMorph*TransientShell.md_h_file.Text.height:          480",
  "XMorph*TransientShell.md_h_file.Text.scrollHoriontal: whenneeded",
  "XMorph*TransientShell.md_h_file.okay.label:           Dismiss",

  "XMorph*TransientShell.md_h_mesh.Text*font: -*-helvetica-medium-r-normal-*-18-*-*-*-*-*-*",
  "XMorph*TransientShell.md_h_mesh.label:                Mesh menu Help",
  "XMorph*TransientShell.md_h_mesh.Text.width:           640",
  "XMorph*TransientShell.md_h_mesh.Text.height:          480",
  "XMorph*TransientShell.md_h_mesh.Text.scrollHoriontal: whenneeded",
  "XMorph*TransientShell.md_h_mesh.okay.label:           Dismiss",

  "XMorph*TransientShell.md_h_morph.Text*font: -*-helvetica-medium-r-normal-*-18-*-*-*-*-*-*",
  "XMorph*TransientShell.md_h_morph.label:              Morph Sequence menu Help",
  "XMorph*TransientShell.md_h_morph.Text.width:         640",
  "XMorph*TransientShell.md_h_morph.Text.height:        480",
  "XMorph*TransientShell.md_h_morph.Text.scrollHoriontal: whenneeded",
  "XMorph*TransientShell.md_h_morph.okay.label:         Dismiss",

  "XMorph*TransientShell.md_h_dpm.Text*font: -*-helvetica-medium-r-normal-*-18-*-*-*-*-*-*",
  "XMorph*TransientShell.md_h_dpm.label:                DIW Properties menu Help",
  "XMorph*TransientShell.md_h_dpm.Text.width:           640",
  "XMorph*TransientShell.md_h_dpm.Text.height:          480",
  "XMorph*TransientShell.md_h_dpm.Text.scrollHoriontal: whenneeded",
  "XMorph*TransientShell.md_h_dpm.okay.label:           Dismiss",

  "XMorph*TransientShell.md_h_dcm.Text*font: -*-helvetica-medium-r-normal-*-18-*-*-*-*-*-*",
  "XMorph*TransientShell.md_h_dcm.label:                DIW Commands menu Help",
  "XMorph*TransientShell.md_h_dcm.Text.width:           640",
  "XMorph*TransientShell.md_h_dcm.Text.height:          480",
  "XMorph*TransientShell.md_h_dcm.Text.scrollHoriontal: whenneeded",
  "XMorph*TransientShell.md_h_dcm.okay.label:           Dismiss",

  /* ----------------- */
  /* DIW box resources */
  "XMorph*diw_box.SimpleMenu.background:         white",

  /* Viewport scrollbars */
  "XMorph*Viewport.allowHoriz:           True",
  "XMorph*Viewport.allowVert:            True",

  /* warp and dissolve scrollbars */
  "XMorph*mesh_label.label:              warp:",
  "XMorph*source_mesh.foreground:        dark green",
  "XMorph*source_mesh.label:             source mesh",
  "XMorph*destination_mesh.foreground:   red",
  "XMorph*destination_mesh.label:        destination mesh",
  "XMorph*scrollbar_mesh.foreground:     yellow",
  "XMorph*scrollbar_mesh.orientation:    horizontal",
  "XMorph*scrollbar_image.orientation:   horizontal",
  "XMorph*Scrollbar.foreground:          LightGray",
  "XMorph*Scrollbar.background:          DimGray",
  "XMorph*image_label.label:             dissolve:",
  "XMorph*source_image.label:            source image",
  "XMorph*destination_image.label:       destination image",

  /* DIW Properties menu */
  "XMorph*diw_prop_menu_button.label:            Properties",
  "XMorph*diw_box.SimpleMenu.smp_sme.label:      Show source mesh points",
  "XMorph*diw_box.SimpleMenu.sml_sme.label:      Show source mesh lines",
  "XMorph*diw_box.SimpleMenu.dmp_sme.label:      Show destination mesh points",
  "XMorph*diw_box.SimpleMenu.dml_sme.label:      Show destination mesh lines",
  "XMorph*diw_box.SimpleMenu.tmp_sme.label:      Show tween mesh points",
  "XMorph*diw_box.SimpleMenu.tml_sme.label:      Show tween mesh lines",
  "XMorph*diw_box.SimpleMenu.dim_sme.label:      Dim Image",

  /* DIW Commands menu */
  "XMorph*diw_cmd_menu_button.label:             Commands",
  "XMorph*diw_box.SimpleMenu.si_sme.label:       Save Image...",
  "XMorph*diw_box.SimpleMenu.warp_sme.label:     Warp Image",
  "XMorph*diw_box.SimpleMenu.redither_sme.label: Redither Image",

  NULL
};

/* --------------------------------------------------------------- */

/* initialize_application: lots of once-only initialization
** Build the widget heirarchy and initializes all GUI variables
*/
XtAppContext
initialize_application(int width, int height, int *argc, char **argv)
{
  XtAppContext app;
  Widget         toplevel;
  Widget           form;
  Widget             file_menu_button;
  Widget             mesh_menu_button;
  Widget             warp_menu_button;
  Widget             help_menu_button;
  Widget             diw_box;
  Widget               diw_widget1, diw_widget2;
  Arg args[25];
  int argn;

  Display *display;
  int     screen_num;

  /* initialize the DIW maps */
  for(argn=0; argn<NUM_DIW_MAPS; argn++) {
    global_diw_map[argn].width = 0;
    global_diw_map[argn].height = 0;
  }

  /* Create the application context */
  toplevel = XtAppInitialize(&app, "XMorph",
    NULL, 0, argc, argv, fallback_resources, NULL, 0);

  XtAppAddActions(app, actions, XtNumber(actions));

  display = XtDisplay(toplevel);
  screen_num = XDefaultScreen(display);

  XSynchronize(display, 1); /* for debugging */

  /* ---------------------------------------------------- */
  /* Create the Form widget for the buttons and diw_map's */
  form = XtVaCreateManagedWidget("form", formWidgetClass, toplevel, NULL);

  /* ------------------------------------------------------------ */
  /* Create the file menu, its button, and register its callbacks */
  file_menu_button = create_file_menu(form, toplevel, NULL);

  /* ------------------------------------------------------------ */
  /* Create the mesh menu, its button, and register its callbacks */
  mesh_menu_button = create_mesh_menu(form, toplevel, file_menu_button);

  /* ------------------------------------------------------------ */
  /* Create the warp menu, its button, and register its callbacks */
  warp_menu_button = create_warp_menu(form, toplevel, mesh_menu_button);

  /* ------------------------------------------------------------ */
  /* Create the help menu, its button, and register its callbacks */
  help_menu_button = create_help_menu(form, toplevel, warp_menu_button);
  XtVaSetValues(help_menu_button, XtNright, XtChainRight, NULL);

  /* ================================== */
  /* Create the box for the DIW widgets */
  argn=0;
  XtSetArg(args[argn], XtNorientation, XtorientHorizontal); argn++;
  XtSetArg(args[argn], XtNfromVert, file_menu_button); argn++;
  /* box changed to paned: WA (MJG 13sep95) */
  diw_box = XtCreateManagedWidget("diw_box", panedWidgetClass, form, args, argn);

  /* Initialize GC's, cursors, bitmaps, palette... */
  init_diw_stuff(toplevel);

    /* === Create the DIW panels === */
    /* --- Make the first diw_map --- */
    global_diw_map[0].mesh_t = 0.0;
    global_diw_map[0].img_t = 0.0; /* for the scrollbar Thumb */
    diw_widget1 = create_diw_widget(diw_box, &global_diw_map[0], width, height);

    /* Turn on the source and tween mesh on the first diw_map */
    dp_menu_cb(global_diw_map[0].sml, (XtPointer)&global_diw_map[0], NULL);
    dp_menu_cb(global_diw_map[0].smp, (XtPointer)&global_diw_map[0], NULL);
    dp_menu_cb(global_diw_map[0].tml, (XtPointer)&global_diw_map[0], NULL);

    /* --- Make the second diw_map --- */
    global_diw_map[1].mesh_t = 1.0;
    global_diw_map[1].img_t = 1.0; /* for the scrollbar Thumb */
    diw_widget2 = create_diw_widget(diw_box, &global_diw_map[1], width, height);

    /* These arrays are not used, so I'll free them */
    my_free(global_diw_map[1].mesh_src.x, "initialize_application");
    my_free(global_diw_map[1].mesh_src.y, "initialize_application");
    my_free(global_diw_map[1].mesh_dst.x, "initialize_application");
    my_free(global_diw_map[1].mesh_dst.y, "initialize_application");

    /* Make the 2 diw_maps share the same meshes */
    /*diw_map_of_widget(NULL);*/
    global_diw_map[1].mesh_src.x = global_diw_map[0].mesh_src.x;
    global_diw_map[1].mesh_src.y = global_diw_map[0].mesh_src.y;
    global_diw_map[1].mesh_dst.x = global_diw_map[0].mesh_dst.x;
    global_diw_map[1].mesh_dst.y = global_diw_map[0].mesh_dst.y;

    /* Turn on the destination and tween mesh on the second diw_map */
    dp_menu_cb(global_diw_map[1].dml, (XtPointer)&global_diw_map[1], NULL);
    dp_menu_cb(global_diw_map[1].dmp, (XtPointer)&global_diw_map[1], NULL);
    dp_menu_cb(global_diw_map[1].tml, (XtPointer)&global_diw_map[1], NULL);

  /* --------------------- */
  XtRealizeWidget(toplevel);

  /* Copy "original" images into the diw_map image spaces */
  reset_images(1);
  reset_images(2);

  /* Send the diw_map images to the X server */
  ReditherImage(diw_widget1, NULL, NULL, NULL);
  ReditherImage(diw_widget2, NULL, NULL, NULL);

  /* X Toolkit Main Loop (should be in main()) */
  return(app);
}

/* =============================================================== */

#define cl_match_arg(cmd) (!strcmp(argv[apc], cmd) && ((apc+1)<argc))

#define USAGE \
"usage:  %s [options] [Xt options]\n\
     options:\n\
       -start  <starting image>\n\
       -finish <finishing image>\n\
       -src    <source mesh>\n\
       -dst    <destination mesh>\n"

int
main(argc, argv)
int argc;
char **argv;
{
  char *src_img_fn, *dst_img_fn;
  char *src_mesh_fn, *dst_mesh_fn;
  RgbaImageT *src_imgP, *dst_imgP;
  XtAppContext app;

  int apc;

#ifdef AMIGA
  struct Task *task;
  task=FindTask(0);
  if (((LONG)task->tc_SPUpper-(LONG)task->tc_SPLower)<(200000-500))
    {
    printf("This program needs at least 200000 bytes of stack!(current size=%ld)\n",((LONG)task->tc_SPUpper-(LONG)task->tc_SPLower));
    exit(1);
    }
#endif



#ifdef SUNOS
  malloc_debug(2);
#endif

  src_img_fn=dst_img_fn=NULL;
  src_mesh_fn=dst_mesh_fn=NULL;

  for(apc=1; apc<argc; apc++) {
    if(argv[apc][0] != '-') {
    } else {
      if(cl_match_arg("-start")) {
        src_img_fn = argv[++apc];
      } else if(cl_match_arg("-finish")) {
        dst_img_fn = argv[++apc];
      } else if(cl_match_arg("-src")) {
        src_mesh_fn = argv[++apc];
      } else if(cl_match_arg("-dst")) {
        dst_mesh_fn = argv[++apc];
      } else if(!strcmp(argv[apc], "-verbose")) {
        verbose ++;
        fprintf(stderr, "%s: verbose reporting\n", argv[0]);
      } else if(argv[apc][1] == '?') {
        fprintf(stderr, USAGE, argv[0]);
        exit(1);
      }
    }
  }

  if(src_img_fn==NULL && dst_img_fn!=NULL) {
    fprintf(stderr,
            "%s: can't have finish image without start image.\n", argv[0]);
    exit(1);
  }


  /* Load the source image or create a test pattern image */
  src_imgP = &orig_image[0];
  src_imgP->ri = src_imgP->gi = src_imgP->bi = src_imgP->ai = NULL;
  if(src_img_fn!=NULL) {
    if(rgbaImageRead(src_img_fn, src_imgP)) {
      fprintf(stderr, "%s: could not open '%s'\n", argv[0], src_img_fn);
      exit(1);
    }
  } else {
    /* Create test pattern image */
    src_imgP->ncols = 320;
    src_imgP->nrows = 240;
    rgbaImageAlloc(src_imgP, "main");
    rgbaImageCreateTest(src_imgP, 2);
  }


  /* Load the destination image */
  dst_imgP = &orig_image[1];
  dst_imgP->ri = dst_imgP->gi = dst_imgP->bi = dst_imgP->ai = NULL;
  if(src_img_fn!=NULL && dst_img_fn!=NULL) {
    if(rgbaImageRead(dst_img_fn, dst_imgP)) {
      fprintf(stderr, "%s: could not open '%s'\n", argv[0], src_img_fn);
      exit(1);
    }

    /* Make sure images are the same shape */
    if((dst_imgP->ncols != src_imgP->ncols)
       || (dst_imgP->nrows != src_imgP->nrows))
    {
      fprintf(stderr, "%s: images are not the same size\n", argv[0]);
      exit(1);
    }
  } else {
    dst_imgP->ncols = src_imgP->ncols;
    dst_imgP->nrows = src_imgP->nrows;
    rgbaImageAlloc(dst_imgP, "main");
    rgbaImageCreateTest(dst_imgP, 1);
  }

  app=initialize_application(src_imgP->ncols, src_imgP->nrows, &argc, argv);

  XtAppMainLoop(app);
}
