/* IUP/OpenGL Capture using IM

  Uses IUP for user interface
       IM for image I/O and capture
       OpenGL for drawing

  Needs IUP: comctl32.lib iup.lib iupcontrols.lib iupgl.lib cd.lib cdiup.lib 
        IM: im.lib im_capture.lib im_process.lib im_avi.lib strmiids.lib vfw32.lib 
        OpenGL: opengl32.lib glu32.lib

  Libraries Available at:
    http://www.tecgraf.puc-rio.br/iup/
    http://www.tecgraf.puc-rio.br/im/
    http://www.tecgraf.puc-rio.br/cd/

  The "strmiids.lib" may have to be updated to the version in:
         http://www.tecgraf.puc-rio.br/im/download/strmiids.zip

  You should re-write the ARProcess.c module keeping the interface (ARProcess.h).

  You must first connect to a capture device, if the connection is successful
  images will be captured from the device and shown on the left canvas. 

  You may change capture device settings and control if the capture is live or not.

  Images can also be played from a video file. 
  Frames will also be reproduced in idle function not considering the original frame rate.

  When you process the capture data, the result is always displayed in the right canvas. 
  The objective is to preserve the original image to display 3D data on top of it.

  The right canvas is a grey level image computed by the ARProcess module.

  Version:
            2.0 - Decopling between interface and process.
			      When the user establishes a connection the interface initializes the ARProcess module
                  informing the with and the height of the incoming images.
                  For every capture frame the interface passes the RGB buffer and expects to receive 
                  back the grey image it will display.
                  Drawing of the images is now a responsibility of the ARProcess module, the interface 
                  only makes canvas current and treats the swap buffer operations.
                  The ARProcess has also the responsibility to collect their allocated memory when the 
                  program ends.

			1.3 - Can save frame sequence in TIFF, GIF and AVI. GIF can only be used for the right image.
                  arProcess API changed to include gldata.
                  Dial replaced by another Valuator.
            1.2 - New video file input.
                  Changed some button image sizes to 23x23 in "iconlib.c".
                  Fixed image type initialization of the save right video file.
                  New save left video file.
            1.1 - Full iconlib.c 

  Authors:
            Antonio Scuri
            Marcelo Gattass

  Tecgraf/PUC-Rio - 2004
*/

#ifdef WIN32            
#include <windows.h>    /* necessary because of the Microsoft OpenGL headers dependency */
#endif

#include <GL/gl.h>
#include <GL/glu.h>

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#include <iup.h>
#include <iupcontrols.h>
#include <iupgl.h>

#include <im.h>
#include <im_capture.h>
#include <im_image.h>
#include <im_util.h>
#include <im_convert.h>
#include <im_process.h>
#include <im_format_avi.h>

#include "ARProcess.h"

/* implemented in "iconlib.c" to load standard icon images into IUP */
void IconLibOpen(void);


typedef struct _appData
{
  imFile* file_cap;             /* file input */
  imVideoCapture* video_cap;    /* live capture input */

  int file_image_count,
      file_image_index;         /* [0, image_count] or -1 = paused */

  unsigned char* gl_data;       /* opengl display buffer (left) [RGB]*/
  imImage* image;               /* process buffer (right) [grey]     */
//  imImage* aux_image;           /* auxiliar process buffer, this is necessary for spatial filters. */

  imFile *videoright_file,      /* video file to save frames of the image buffer */
         *videoleft_file;
  int videoright_started,       /* flag to indicate that at least one frame has been saved. */
      videoleft_started;

  /* Global to help enable/disable items in the interface */
  Ihandle *left_canvas, *right_canvas,
          *status_label,
          *config_but, *config_menu,
          *capture_but, *capture_menu;

  int user_flags[3];
  double user_value1, user_value2;
} appData;

int user1_cb(Ihandle *self)
{
  appData* app_data = (appData*)IupGetAttribute(self, "APP_DATA");

  if (app_data->user_flags[0] == 0)
  {
    app_data->user_flags[0] = 1;
  }
  else
    app_data->user_flags[0] = 0;

  return IUP_DEFAULT;
}

int user2_cb(Ihandle *self)
{
  appData* app_data = (appData*)IupGetAttribute(self, "APP_DATA");

  if (app_data->user_flags[1] == 0)
  {
    app_data->user_flags[1] = 1;
  }
  else
    app_data->user_flags[1] = 0;
  return IUP_DEFAULT;
}

int user3_cb(Ihandle *self)
{
  appData* app_data = (appData*)IupGetAttribute(self, "APP_DATA");
  if (app_data->user_flags[2] == 0)
  {
    app_data->user_flags[2] = 1;
  }
  else
    app_data->user_flags[2] = 0;
  return IUP_DEFAULT;
}

int user_val2_cb(Ihandle *self, double value)
{
  appData* app_data = (appData*)IupGetAttribute(self, "APP_DATA");
  app_data->user_value2 = value;
  return IUP_DEFAULT;
}

int user_val1_cb(Ihandle *self, double value)
{
   appData* app_data = (appData*)IupGetAttribute(self, "APP_DATA");
   app_data->user_value1 = value;
   return IUP_DEFAULT;
}


int app_left_repaint_cb(Ihandle* self)
{
  IupGLMakeCurrent(self);        /* activates this GL Canvas as the current drawing area. */
  arRepaintLeftCanvas( );
  IupGLSwapBuffers(self);        /* swap data from back buffer to front buffer */
  return IUP_DEFAULT;
} 

int app_right_repaint_cb(Ihandle* self)
{
  IupGLMakeCurrent(self);        /* activates this GL Canvas as the current drawing area. */
  arRepaintRightCanvas( );
  IupGLSwapBuffers(self);        /* swap data from back buffer to front buffer */
  return IUP_DEFAULT;
} 

void appGLInit(int width, int height)
{
  glClearColor(0., 0., 0., 1.0);                   /* window background */
  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);           /* data alignment is 1 */

  glViewport(0, 0, width, height);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluOrtho2D (0.0, (GLdouble)width, 0.0, (GLdouble)height);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

}

int app_resize_cb(Ihandle* self, int w, int h)
{
  IupGLMakeCurrent(self);
  appGLInit(w, h);
  return IUP_DEFAULT;
} 

void appSaveImage(imImage* image, unsigned char* gl_data)
{
  int ret, error, format_count;
  char filename[1024] = ".\\*.*";
  imFile* ifile;
  char* format_list[50];

  if (!image)
  {
    IupMessage("Error", "Image not initialized."); 
    return;
  }

  ret = IupGetFile(filename); /* get a file name */
  if (ret == -1)
    return;

  /* get a file format */
  imFormatList(format_list, &format_count);
  ret = IupListDialog(1,"Format Selection",format_count,format_list,1,20,10,NULL);
  if (ret == -1)
    return;

  ifile = imFileNew(filename, format_list[ret], &error);
  if (!ifile) 
  {
    IupMessage("Error", "Error creating image file."); 
    return;
  }

  if (gl_data)
  {
    /* save the raw image data */
    imFileWriteImageInfo(ifile, image->width, image->height, IM_RGB|IM_PACKED, IM_BYTE);
    error = imFileWriteImageData(ifile, gl_data);
  }
  else
    error = imFileSaveImage(ifile, image);   /* save the processed image */

  if (error)
    IupMessage("Error", "Error writing image file."); 

  imFileClose(ifile);
}

int app_leftimage_cb(Ihandle* self)
{
  appData* app_data = (appData*)IupGetAttribute(self, "APP_DATA");
  appSaveImage(app_data->image, app_data->gl_data);
  return IUP_DEFAULT;
}

int app_rightimage_cb(Ihandle* self)
{
  appData* app_data = (appData*)IupGetAttribute(self, "APP_DATA");
  appSaveImage(app_data->image, NULL);
  return IUP_DEFAULT;
}


/* OpenGL does not supports palette based images, so convert to RGB */
/* this function can also be use for RGBA images */
void ConvertMapToGLData(unsigned char* data, int count, int depth, long* palette, int palette_count)
{
  int c, i;
  unsigned char r[256], g[256], b[256];

  unsigned char* src_data = data + count-1;
  unsigned char* dst_data = data + depth*(count-1);

  for (c = 0; c < palette_count; c++)
    imColorDecode(&r[c], &g[c], &b[c], palette[c]);

  for (i = 0; i < count; i++)
  {
    int index = *src_data;
    *dst_data       =  r[index];
    *(dst_data+1) = g[index];
    *(dst_data+2) = b[index];

    dst_data -= depth;
    src_data--;
  }
}

void appVideoClose(imFile* *video_file, int *video_started)
{
  imFileClose(*video_file);
  IupMessage("Message", "Video file closed.");
  *video_file = NULL;
  *video_started = 0;
}

void appVideoFrameSave(imFile* *video_file, int *video_started, int width, int height, int color_mode, unsigned char* data)
{
  /* save the captured video in a file */
  int error = imFileWriteImageInfo(*video_file, width, height, color_mode, IM_BYTE);
  if (error == IM_ERR_NONE)
  {
    imFileWriteImageData(*video_file, data);
    *video_started = 1; /* at least one frame has been written */
  }
  else
  {
    IupMessage("Error", "Error writing video frame.");
    appVideoClose(video_file, video_started);
  }
}

int idle_cb(void)
{
  int process = 0;
  appData* app_data = (appData*)IupGetGlobal("APP_DATA");

  if (app_data->file_cap)
  {
    int width = 0, height = 0, file_color_mode;
    imFileReadImageInfo(app_data->file_cap, app_data->file_image_index, &width, &height, &file_color_mode, NULL);

//    if (width != app_data->image->width || height != app_data->image->height)
//      return IUP_DEFAULT;
   if (app_data->image)
       if (width != app_data->image->width || height != app_data->image->height)
           return IUP_DEFAULT;

//    imFileReadImageData(app_data->file_cap, app_data->gl_data, 1, IM_PACKED);
	imFileReadImageData(app_data->file_cap, app_data->gl_data, 0, -1);
    app_data->file_image_index++;

    if (file_color_mode & IM_MAP || file_color_mode & IM_GRAY || file_color_mode & IM_BINARY)
    {
      long palette[256];
      int palette_count;
      imFileGetPalette(app_data->file_cap, palette, &palette_count);
      ConvertMapToGLData(app_data->gl_data, width*height, 3, palette, palette_count);
    }

    if (app_data->file_image_index == app_data->file_image_count)
      app_data->file_image_index = 0;

    process = 1;
  }
  else if (app_data->video_cap && imVideoCaptureLive(app_data->video_cap, -1))
  {
    /* retrieve a frame available at the capture buffer.
       normally this will be a new frame, except when a timeout occours. */
    imVideoCaptureFrame(app_data->video_cap, app_data->gl_data, IM_RGB|IM_PACKED, 1000);

    process = 1;
  }

  if (app_data->videoleft_file)
  {
    /* save the captured video in a file */
    appVideoFrameSave(&app_data->videoleft_file, &app_data->videoleft_started, app_data->image->width, app_data->image->height, IM_RGB|IM_PACKED, app_data->gl_data);
  }

  if (process)
  {
	app_data->image = arFrameProcess(app_data->gl_data,  app_data->user_flags, app_data->user_value1, app_data->user_value2);

    if (app_data->videoright_file)
    {
      /* save the processed video in a file */
      appVideoFrameSave(&app_data->videoright_file, &app_data->videoright_started, app_data->image->width, app_data->image->height, IM_GRAY, app_data->image->data[0]);
    }

    /* force a repaint */
    app_left_repaint_cb(app_data->left_canvas);
    app_right_repaint_cb(app_data->right_canvas);

    if (1)
    {
      /* estimate the number of frames per second */
      static int frame_count = 1;
      static int start_time = 0;
      int current_time = clock();

      if (current_time - start_time > CLOCKS_PER_SEC)
      {
        IupSetfAttribute(app_data->status_label, "TITLE", "fps: %3.2f  flags:%1d%1d%1d value1: %.2lf  value2: %.2lf", 
            (double)(frame_count*CLOCKS_PER_SEC)/(double)(current_time-start_time),
            app_data->user_flags[0],
            app_data->user_flags[1],
            app_data->user_flags[2],
            app_data->user_value1,
            app_data->user_value2);

        frame_count = 1;
        start_time = current_time;
      }

      frame_count++;
    }
  }

  return IUP_DEFAULT;
}

void appUpdateInterface(appData* app_data)
{
  if ((!app_data->video_cap || imVideoCaptureConnect(app_data->video_cap, -1) == -1) &&
      !app_data->file_cap)
  {
    /* if no input, disable everything */
    IupSetAttribute(app_data->config_but,"ACTIVE","NO");
    IupSetAttribute(app_data->capture_but,"ACTIVE","NO");
    IupSetAttribute(app_data->config_menu,"ACTIVE","NO");
    IupSetAttribute(app_data->capture_menu,"ACTIVE","NO");
    return;
  }

  /* if any input, enable almost everything */
  IupSetAttribute(app_data->capture_but,"ACTIVE","YES");
  IupSetAttribute(app_data->capture_menu,"ACTIVE","YES");

  /* enable config only when capture device connected */
  if (app_data->video_cap && imVideoCaptureConnect(app_data->video_cap, -1) != -1)
  {
    IupSetAttribute(app_data->config_but,"ACTIVE","YES");
    IupSetAttribute(app_data->config_menu,"ACTIVE","YES");
  }
  else
  {
    IupSetAttribute(app_data->config_but,"ACTIVE","NO");
    IupSetAttribute(app_data->config_menu,"ACTIVE","NO");
  }

  /* if live set the idle and update the start/stop toggle */
  if ((app_data->video_cap && imVideoCaptureLive(app_data->video_cap, -1)) ||
      (app_data->file_cap && app_data->file_image_index != -1))
  {
    IupSetFunction(IUP_IDLE_ACTION, (Icallback)idle_cb);
    IupSetAttribute(app_data->capture_but,"VALUE","ON");
  }
  else
  {
    IupSetFunction(IUP_IDLE_ACTION, (Icallback)NULL);
    IupSetAttribute(app_data->capture_but,"VALUE","OFF");
  }
}

void appResetImageData(appData* app_data)
{
  int width = 0, height = 0;

  /* retrieve the image size */
  if (app_data->file_cap)
    imFileReadImageInfo(app_data->file_cap, 0, &width, &height, NULL, NULL);
  else if (app_data->video_cap)
    imVideoCaptureGetImageSize(app_data->video_cap, &width, &height);

  if (width == 0 || height == 0)
  {
    IupMessage("Error!", "Invalid image size.");
    return;
  }

  /* alocates the buffers */
  if (app_data->gl_data) free(app_data->gl_data);
  app_data->gl_data = malloc(width*height*3);

#if 0
  if (app_data->image) imImageDestroy(app_data->image);
  if (app_data->aux_image) imImageDestroy(app_data->aux_image);
  app_data->image = imImageCreate(width, height, IM_GRAY, IM_BYTE);
  app_data->aux_image = imImageClone(app_data->image);
#endif
  arInit(width,height);


  if (app_data->videoright_file && app_data->videoright_started) 
  {
    /* if the video file is already started it should be stopped */
    appVideoClose(&app_data->videoright_file, &app_data->videoright_started);
  }

  if (app_data->videoleft_file && app_data->videoleft_started) 
  {
    /* if the video file is already started it should be stopped */
    appVideoClose(&app_data->videoleft_file, &app_data->videoleft_started);
  }
}

int app_capture_cb(Ihandle *self)
{
  int paused = 0;
  appData* app_data = (appData*)IupGetAttribute(self, "APP_DATA");

  if (app_data->file_cap)
  {
    if (app_data->file_image_index == -1)
      app_data->file_image_index = 0;
    else
    {
      app_data->file_image_index = -1;  
      paused = 1;
    }
  }
  else if (app_data->video_cap && imVideoCaptureConnect(app_data->video_cap, -1) != -1)
  {
    int live = !imVideoCaptureLive(app_data->video_cap, -1); /* invert live state */

    if (!imVideoCaptureLive(app_data->video_cap, live)) 
      IupMessage("Error!", "Could not change live state!");

    if (!live)
      paused = 1;
  }

  if (paused)
  {
    if (app_data->videoright_file && app_data->videoright_started) 
    {
      /* if the video file is already started it should be stopped */
      appVideoClose(&app_data->videoright_file, &app_data->videoright_started);
    }

    if (app_data->videoleft_file && app_data->videoleft_started) 
    {
      /* if the video file is already started it should be stopped */
      appVideoClose(&app_data->videoleft_file, &app_data->videoleft_started);
    }
  }

  appUpdateInterface(app_data);
  return IUP_DEFAULT;
}

int app_connect_cb(Ihandle *self)
{
  int device = 0;
  appData* app_data = (appData*)IupGetAttribute(self, "APP_DATA");

  /* get a capture device */
  int cap_count = imVideoCaptureDeviceCount();
  if (cap_count > 1)
  {
    int i, ret;
    char* cap_list[50];

    for (i = 0; i < cap_count; i++)
      cap_list[i] = (char*)imVideoCaptureDeviceDesc(i);

    ret = IupListDialog(1,"Capture Device Selection",cap_count,cap_list,1,40,10,NULL);
    if (ret == -1)
      return IUP_DEFAULT;

    device = ret;
  }

  if (!imVideoCaptureConnect(app_data->video_cap, device)) 
  {
    IupMessage("Error!", "Could not connect to the capture device.");
    appUpdateInterface(app_data);
    return IUP_DEFAULT;
  }

  /* if input file oppened, close it */
  if (app_data->file_cap) 
  {
    imFileClose(app_data->file_cap);
    app_data->file_cap = NULL;
  }

  appResetImageData(app_data);

  return app_capture_cb(self);
}

int app_configure_cb(Ihandle *self)
{
  int dlg = 0, live;
  appData* app_data = (appData*)IupGetAttribute(self, "APP_DATA");

  /* get a configuration dialog */
  int dlg_count = imVideoCaptureDialogCount(app_data->video_cap);
  if (dlg_count > 1)
  {
    int i, ret;
    char* dlg_list[50];

    for (i = 0; i < dlg_count; i++)
      dlg_list[i] = (char*)imVideoCaptureDialogDesc(app_data->video_cap, i);

    ret = IupListDialog(1,"Configuration Dialog Selection",dlg_count,dlg_list,1,20,10,NULL);
    if (ret == -1)
      return IUP_DEFAULT;

    dlg = ret;
  }

  live = imVideoCaptureLive(app_data->video_cap, -1);
  if (live)
    imVideoCaptureLive(app_data->video_cap, 0);  /* deactivate the capture before calling the dialog */

  if (!imVideoCaptureShowDialog(app_data->video_cap, dlg, IupGetAttribute((Ihandle*)IupGetGlobal("app_dialog"), "WID"))) 
  {
    IupMessage("Error!", "Could not set configuration.");
    appUpdateInterface(app_data);
    return IUP_DEFAULT;
  }

  /* reset data because the dialogs can change the image size */
  appResetImageData(app_data);

  if (live)
    imVideoCaptureLive(app_data->video_cap, 1);  /* restore the live state */

  appUpdateInterface(app_data);
  return IUP_DEFAULT;
}

int app_open_cb(Ihandle* self)
{
  int ret, error;
  appData* app_data = (appData*)IupGetAttribute(self, "APP_DATA");
  char videoin_filename[1024] = ".\\*.*";

  /* if input file oppened, close it */
  if (app_data->file_cap)
  {
    imFileClose(app_data->file_cap);
    app_data->file_cap = NULL;
  }

  /* get a file name */
  ret = IupGetFile(videoin_filename);
  if (ret == -1)
    return IUP_DEFAULT;

  app_data->file_cap = imFileOpen(videoin_filename, &error);
  if (!app_data->file_cap)
  {
    IupMessage("Error", "Error reading video file.");
    appUpdateInterface(app_data);
    return IUP_DEFAULT;
  }
  imFileGetInfo(app_data->file_cap, NULL, NULL, &app_data->file_image_count);
  app_data->file_image_index = -1; /* start as paused, to toggle in app_capture_cb */

  /* if connected, disconnect */
  if (app_data->video_cap)
    imVideoCaptureDisconnect(app_data->video_cap);

  appResetImageData(app_data);

  return app_capture_cb(self);
}

int appSaveVideo(imFile* *video_file, int *video_started)
{
  int ret;
  char filename[1024] = ".\\*.*"; 

  /* if output file oppened, close it */
  if (*video_file) 
  {
    appVideoClose(video_file, video_started);
    return IUP_DEFAULT;
  }

  /* get a file name */
  ret = IupGetFile(filename);
  if (ret != -1)
  {
    int error, ret;
    char* format_list[3] = {
      "TIFF",
      "GIF",    /* this can only be used for the right image */
      "AVI",
    };

    ret = IupListDialog(1,"Format Selection",3,format_list,1,20,10,NULL);
    if (ret == -1)
      return IUP_DEFAULT;

    *video_file = imFileNew(filename, format_list[ret], &error);
    if (!(*video_file))
      IupMessage("Error", "Error creating video file.");
  }

  return IUP_DEFAULT;
}

int app_leftvideo_cb(Ihandle* self)
{
  appData* app_data = (appData*)IupGetAttribute(self, "APP_DATA");
  return appSaveVideo(&app_data->videoleft_file, &app_data->videoleft_started);
}

int app_rightvideo_cb(Ihandle* self)
{
  appData* app_data = (appData*)IupGetAttribute(self, "APP_DATA");
  return appSaveVideo(&app_data->videoright_file, &app_data->videoright_started);
}

int app_exit_cb(Ihandle *self)
{
  appData* app_data = (appData*)IupGetAttribute(self, "APP_DATA");

  /* destroy buffers, disconnect devices, close files */

  if (app_data->videoright_file) 
    imFileClose(app_data->videoright_file);

  if (app_data->videoleft_file) 
    imFileClose(app_data->videoleft_file);

  if (app_data->file_cap) 
    imFileClose(app_data->file_cap);

  if (app_data->video_cap)
    imVideoCaptureDestroy(app_data->video_cap);

  arEnd( );

#if 0
  if (app_data->image) 
    imImageDestroy(app_data->image);

  if (app_data->aux_image) 
    imImageDestroy(app_data->aux_image);
#endif


  if (app_data->gl_data) 
    free(app_data->gl_data);

  free(app_data);

  return IUP_CLOSE;
}

int app_about_cb(Ihandle *self)
{
  IupMessagef("About", "IUPGLCapture 1.3\n"
                       "Tecgraf/PUC-Rio\n"
                       " ---------------- \n"
                       "IUP Version %s\n"
                       "IM Version %s\n"
                       " ---------------- \n"
                       "OpenGL:\n"
                       "  Vendor: %s\n"
                       "  Renderer: %s\n"
                       "  Version: %s\n"
                       , IUP_RELEASE_VERSION, IM_VERSION, 
                         glGetString(GL_VENDOR),glGetString(GL_RENDERER),glGetString(GL_VERSION));
  return IUP_DEFAULT;
}

void mainMenuCreate(appData* app_data) 
{
  Ihandle* file_menu = IupMenu(
     IupItem( "Save Left Video...", "app_leftvideo_cb"),
     IupItem( "Save Right Video...", "app_rightvideo_cb"),
     IupSeparator(),
     IupItem( "Save Left Image...", "app_leftimage_cb"),
     IupItem( "Save Right Image...", "app_rightimage_cb"),
     IupSeparator(),
     IupItem( "Exit", "app_exit_cb"),
     NULL
  );

  Ihandle* capture_menu = IupMenu(
     IupItem( "Open Input Video File...", "app_open_cb"),
     IupSeparator(),
     IupItem( "Connect Input Device...", "app_connect_cb"),
     app_data->config_menu = IupItem( "Configure Device...", "app_configure_cb"),
     IupSeparator(),
     app_data->capture_menu = IupItem( "Start/Stop", "app_capture_cb"),
     NULL
  );

  Ihandle* process_menu = IupMenu(
     IupItem( "user1", "user1_cb"),   /* EDIT_HERE */
     IupItem( "user2", "user2_cb"),
     IupItem( "user3", "user3_cb"),
     NULL
  );

  Ihandle* help_menu = IupMenu(
     IupItem( "About...", "app_about_cb"),
     NULL
  );

  Ihandle* menu = IupMenu(
     IupSubmenu("File", file_menu),
     IupSubmenu("Capture", capture_menu),
     IupSubmenu("Process", process_menu),
     IupSubmenu("Help", help_menu),
     NULL
  );

  IupSetAttribute(app_data->config_menu,"ACTIVE","NO");
  IupSetAttribute(app_data->capture_menu,"ACTIVE","NO");

  /* this will be used by the dialog */
  IupSetHandle("app_menu", menu);

  /* associate callbacks with names */
  IupSetFunction("app_exit_cb", app_exit_cb);
  IupSetFunction("app_about_cb", app_about_cb);
  IupSetFunction("app_leftvideo_cb", app_leftvideo_cb);
  IupSetFunction("app_rightvideo_cb", app_rightvideo_cb);
  IupSetFunction("app_leftimage_cb", app_leftimage_cb);
  IupSetFunction("app_rightimage_cb", app_rightimage_cb);
}

Ihandle* mainToolbarCreate(appData* app_data) 
{
  Ihandle *toolbar, *val1, *val2, *connect, *open, *configure, *capture;

  /* creates the toolbar and its buttons */

  open = IupButton("", "app_open_cb");
  IupSetAttribute(open,"TIP","Opens an input video file.");
  IupSetAttribute(open,"IMAGE","icon_lib_open");
  IupSetFunction("app_open_cb", (Icallback)app_open_cb);

  connect = IupButton("", "app_connect_cb");
  IupSetAttribute(connect,"TIP","Connects to a capture device.");
  IupSetAttribute(connect,"IMAGE","icon_lib_connect");
  IupSetFunction("app_connect_cb", (Icallback)app_connect_cb);

  configure = IupButton("", "app_configure_cb");
  IupSetAttribute(configure,"TIP","Connects to a capture device.");
  IupSetAttribute(configure,"IMAGE","icon_lib_configure");
  IupSetAttribute(configure,"IMINACTIVE","icon_lib_configure_inactive");
  IupSetAttribute(configure,"ACTIVE","NO");
  IupSetFunction("app_configure_cb", (Icallback)app_configure_cb);
  app_data->config_but = configure;

  capture = IupToggle("", "app_capture_cb");
  IupSetAttribute(capture,"TIP","Connects to a capture device.");
  IupSetAttribute(capture,"IMAGE","icon_lib_capture");
  IupSetAttribute(capture,"IMINACTIVE","icon_lib_capture_inactive");
  IupSetAttribute(capture,"ACTIVE","NO");
  IupSetFunction("app_capture_cb", (Icallback)app_capture_cb);
  app_data->capture_but = capture;

  /* these are user controls */

  val2 = IupVal("HORIZONTAL");
  IupSetAttribute(val2, "MOUSEMOVE_CB", "user_val2_cb");
  IupSetFunction("user_val2_cb", (Icallback)user_val2_cb);

  val1 = IupVal("HORIZONTAL");
  IupSetAttribute(val1, "MOUSEMOVE_CB", "user_val1_cb");
  IupSetFunction("user_val1_cb", (Icallback)user_val1_cb);

  toolbar = IupHbox(
       open, 
       connect, 
       configure, 
       capture, 
       IupFill(),
       IupButton("user1", "user1_cb"),  /* EDIT_HERE */
       IupButton("user2", "user2_cb"),
       IupButton("user3", "user3_cb"),
       val1,
       val2,
       NULL);

  IupSetAttribute(toolbar, "ALIGNMENT", "ACENTER");
  IupSetAttribute(toolbar, "MARGIN", "2x2");

  IupSetFunction("user1_cb", (Icallback)user1_cb);  /* EDIT_HERE */
  IupSetFunction("user2_cb", (Icallback)user2_cb);
  IupSetFunction("user3_cb", (Icallback)user3_cb);

  return toolbar;
}

void mainDialogCreate(void)
{
  appData* app_data;
  Ihandle *dialog, *statusbar, *toolbar, *box,
          *label, *left_canvas, *right_canvas;

  /* initialize application context */
  app_data = malloc(sizeof(appData));
  memset(app_data, 0, sizeof(appData));

  app_data->video_cap = imVideoCaptureCreate();
  if (!app_data->video_cap)
    IupMessage("Error!", "No Capture Device available!");

  /* initialize interface */

  toolbar = mainToolbarCreate(app_data);

  /* left canvas for the captured image and the final results (3D data) */
  /* right canvas for intermediate results */

  left_canvas = IupGLCanvas("app_left_repaint_cb");
  IupSetAttribute(left_canvas, "BORDER", "NO");
  IupSetAttribute(left_canvas, "BUFFER", "DOUBLE");   /* use double buffer */
  IupSetAttribute(left_canvas, "RESIZE_CB", "app_resize_cb");   /* configure the resize callback */
  app_data->left_canvas = left_canvas;

  right_canvas = IupGLCanvas("app_right_repaint_cb");
  IupSetAttribute(right_canvas, "BORDER", "NO");
  IupSetAttribute(right_canvas, "BUFFER", "DOUBLE");  /* use double buffer */
  IupSetAttribute(right_canvas, "RESIZE_CB", "app_resize_cb");  /* configure the resize callback */
  app_data->right_canvas = right_canvas;

  IupSetFunction("app_resize_cb", (Icallback)app_resize_cb);
  IupSetFunction("app_left_repaint_cb", (Icallback)app_left_repaint_cb);
  IupSetFunction("app_right_repaint_cb", (Icallback)app_right_repaint_cb);

  /* the status bar is just a label to put some usefull information in run time */
  label = IupLabel("status");
  IupSetAttribute(label, "EXPAND", "HORIZONTAL");
  IupSetAttribute(label, "FONT", "COURIER_NORMAL_10");
  statusbar = IupSetAttributes(IupHbox(
                IupFrame(IupHbox(label, NULL)), 
                NULL), "MARGIN=5x5");
  app_data->status_label = label;

  /* this is the most external box that puts together
     the toolbar, the two canvas and the status bar */
  box = IupVbox(
          toolbar,
          IupSetAttributes(IupHbox(
            left_canvas, 
            right_canvas, 
            NULL), "GAP=5"),
          statusbar, 
          NULL);

  /* create the dialog and set its attributes */

  mainMenuCreate(app_data);

  dialog = IupDialog(box);
  IupSetAttribute(dialog, "MENU", "app_menu");     /* configure the menu */
  IupSetAttribute(dialog, "CLOSE_CB", "app_exit_cb");
  IupSetAttribute(dialog, "TITLE", "IUPGLCapture");
  IupSetAttribute(dialog, "RASTERSIZE", "680x380"); /* initial size */
  IupSetAttribute(dialog, "SHRINK", "YES");
  IupSetHandle("app_dialog", dialog);
  IupSetGlobal(IUP_PARENTDIALOG, "app_dialog");     /* to be used by the idle callback */

  IupSetAttribute(dialog, "APP_DATA", (char*)app_data);
  IupSetGlobal("APP_DATA", (char*)app_data);

  IupShowXY(dialog, IUP_CENTER, IUP_CENTER);
}

int main(int argc, char* argv[]) 
{
  /* IUP initialization */
  IupOpen();
  IupControlsOpen();
  IupGLCanvasOpen();
  IconLibOpen();

  /* Initialize the AVI format */
  imFormatRegisterAVI();

  /* Create and show the main dialog */
  mainDialogCreate();

  /* IUP event loop */
  IupMainLoop();

  /* IUP closing */
  IupControlsClose();
  IupClose();

  return 0;
}
