/*

Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2005, 2006 Matthew P. Hodges
This file is part of XMakemol.

XMakemol 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, or (at your option)
any later version.

XMakemol 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 XMakemol; see the file COPYING.  If not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
/* #include <libgen.h> */
char * gnu_basename (char *);
#include <unistd.h>

#include <Xm/FileSB.h>
#include <Xm/Label.h>
#include <Xm/MessageB.h>
#include <Xm/PushB.h>
#include <Xm/RowColumn.h>
#include <Xm/Scale.h>
#include <Xm/ScrollBar.h>
#include <Xm/TextF.h>
#include <Xm/ToggleBG.h>

#ifdef XPM
#include <X11/xpm.h>
#endif

#include "draw.h"
#include "globals.h"

void echo_to_message_area(char *);
void clear_message_area (void);
void place_dialog_cb (Widget, XtPointer, XtPointer);

#ifdef HAVE_USLEEP
static int frame_speed = 1;
#endif

static int stop_anim;

#ifdef XPM
static Widget save_anim_dialog = NULL;
#endif

static Widget label_w,stop_b;

#ifdef HAVE_USLEEP
static Widget speed_scale_w, scale_scroll_bar;
#endif

void
make_frame_dlg(Widget parent)
{

  void bounce_cb(Widget, XtPointer, XtPointer);
  void centre_b_cb(Widget, XtPointer, XtPointer);
  void frame_dlg_cancel_cb(Widget, XtPointer, XtPointer);
  void frame_no_cb(Widget, XtPointer, XtPointer);
#ifdef HAVE_USLEEP
  void frame_speed_cb(Widget, XtPointer, XtPointer);
#endif
  void next_frame_cb(Widget, XtPointer, XtPointer);
  void prev_frame_cb(Widget, XtPointer, XtPointer);
  void rewind_cb(Widget, XtPointer, XtPointer);
  void start_anim_cb(Widget, XtPointer, XtPointer);
  void stop_anim_cb(Widget, XtPointer, XtPointer);
  void update_frame_label(void);
  
#ifdef XPM
  void make_anim_cb(Widget, XtPointer, XtPointer);
#endif

  int n=0;

#ifdef XPM
  Widget make_anim_b;
#endif

  Widget rc_ver,rc_hor,start_b,next_b,prev_b,bounce_b,rewind_b;
  Widget centre_b, child;

#ifdef HAVE_USLEEP
  int s, t, nkids;

  Arg tmpargs[2];

  Widget *kids;
#endif

  XmString label = "";

  if (! frames_dialog) {

    label = XmStringCreateLocalized ("Frames");   

    XtSetArg (args[n], XmNautoUnmanage, False); n++;
    XtSetArg (args[n], XmNdialogTitle, label); n++;

    frames_dialog = (Widget)
      XmCreateMessageDialog (parent, "frames", args, n);

    rc_ver = XtVaCreateManagedWidget
      ("rc_ver",xmRowColumnWidgetClass,frames_dialog,
       NULL);

    label_w=XtVaCreateManagedWidget
      ("Comment",
       xmLabelWidgetClass, rc_ver,
       XmNlabelString, label,
       XmNwidth, 400,
       NULL);

    update_frame_label();

    rc_hor = XtVaCreateManagedWidget
      ("rc_hor",xmRowColumnWidgetClass,rc_ver,
       XmNorientation, XmHORIZONTAL,
       NULL);
    
    start_b = XtVaCreateManagedWidget
      ("Start",xmPushButtonWidgetClass,rc_hor,
       NULL);
    
    XtAddCallback(start_b, XmNactivateCallback, start_anim_cb, NULL);

    stop_b = XtVaCreateManagedWidget
      ("Stop",xmPushButtonWidgetClass,rc_hor,
       NULL);
    
    XtAddCallback(stop_b, XmNactivateCallback, stop_anim_cb, NULL);
    
    next_b = XtVaCreateManagedWidget
      ("Next (>)",xmPushButtonWidgetClass,rc_hor,
       NULL);
    
    XtAddCallback(next_b, XmNactivateCallback, next_frame_cb, NULL);

    prev_b = XtVaCreateManagedWidget
      ("Previous (<)",xmPushButtonWidgetClass,rc_hor,
       NULL);
    
    XtAddCallback(prev_b, XmNactivateCallback, prev_frame_cb, NULL);

    rewind_b = XtVaCreateManagedWidget
      ("Rewind (^)",xmPushButtonWidgetClass,rc_hor,
       NULL);
    
    XtAddCallback(rewind_b, XmNactivateCallback, rewind_cb, NULL);

    bounce_b = XtVaCreateManagedWidget
      ("Bounce",xmPushButtonWidgetClass,rc_hor,
       NULL);

    XtAddCallback(bounce_b, XmNactivateCallback, bounce_cb, NULL);

#ifdef XPM
    make_anim_b = XtVaCreateManagedWidget
      ("Make anim",xmPushButtonWidgetClass,rc_hor,
       NULL);
    
    XtAddCallback(make_anim_b, XmNactivateCallback, make_anim_cb, NULL);
    
#endif

#ifdef HAVE_USLEEP
    label=XmStringCreateLocalized("Select speed");

    speed_scale_w = XtVaCreateManagedWidget
      ("Speed",xmScaleWidgetClass,
       rc_ver,
       XmNmaximum, 20,
       XmNminimum,  1,
       XmNvalue, (int) (20-frame_speed+1),
       XmNshowValue, True,
       XmNorientation, XmHORIZONTAL,
       XmNtitleString, label,
       NULL);

    /* need to access scale bar widget for callbacks */

    s=0;
    XtSetArg(tmpargs[s], XmNnumChildren, &nkids); s++;
    XtSetArg(tmpargs[s], XmNchildren, &kids); s++;

    XtGetValues(speed_scale_w, tmpargs, s);

    for(t=0;t<nkids;t++){
      if(XmIsScrollBar((Widget)kids[t])){
        scale_scroll_bar=(Widget)kids[t];
      }
    }

    XtAddCallback(speed_scale_w, XmNvalueChangedCallback,frame_speed_cb,NULL); 
    XtAddCallback(speed_scale_w, XmNdragCallback, frame_speed_cb, NULL);

#endif

    XtAddCallback(frames_dialog, XmNcancelCallback,
                  frame_dlg_cancel_cb, NULL);

    centre_b = XtVaCreateManagedWidget
      ("Centre each frame",xmToggleButtonGadgetClass,rc_ver,NULL);

    XtVaSetValues(centre_b,XmNset,False,NULL);
    centre_each_frame=0;
    
    XtAddCallback
      (centre_b,XmNvalueChangedCallback,centre_b_cb,NULL);
    
    XtVaCreateManagedWidget
      ("Select frame:", xmLabelWidgetClass, rc_ver,
         XmNalignment, XmALIGNMENT_BEGINNING,
         NULL);

    child = XtVaCreateManagedWidget
      ("select_frame", xmTextFieldWidgetClass, rc_ver, NULL);

    XtVaSetValues (child, XmNvalue, "1", NULL);

    XtAddCallback(frames_dialog, XmNokCallback, frame_no_cb, child);

    XtUnmanageChild (XmMessageBoxGetChild
                     (frames_dialog, XmDIALOG_HELP_BUTTON));
    
  }

  XtAddCallback (frames_dialog, XmNmapCallback, place_dialog_cb, NULL);

  XtManageChild(frames_dialog);

  XmStringFree (label);
}


void
frame_dlg_cancel_cb(Widget widget, XtPointer client_data, XtPointer call_data)
{
  
  XtDestroyWidget(frames_dialog);
  frames_dialog=NULL;

}


void
frame_no_cb (Widget widget,
             XtPointer client_data,
             XtPointer call_data)
{
  void change_frame (int, Boolean, Boolean);

  char string[124], *temp_frame_string;
  int temp_frame;

  XtVaGetValues (client_data, XmNvalue, &temp_frame_string, NULL);

  if ((strlen (temp_frame_string) == 0) ||
      (sscanf (temp_frame_string, "%ld", &temp_frame) <= 0))
    {
      echo_to_message_area ("Cannot parse string!");
      return;
    }
  
  temp_frame--; /* Frames from 0 to no_frames -1 */

  if((temp_frame < 0) || (temp_frame > (no_frames - 1)))
    {
    echo_to_message_area("Choice of frame out of bounds!");
    return;
    }

  /* if OK, change frame_no */

  frame_no = temp_frame;

  sprintf(string,"Frame %d selected",frame_no+1);
  echo_to_message_area(string);
    
  if(centre_each_frame){
    /* Change frame, reset title _and_ centre the frame */
    change_frame(frame_no, True, True);
  }else{
    /* Change frame, reset title, _don't_ centre the frame */
    change_frame(frame_no, True, False);
  }

}


void
start_anim_cb(Widget widget, XtPointer client_data, XtPointer call_data)
{

  void change_frame(int, Boolean, Boolean);

  XEvent event;
  
  stop_anim=0;

  if(no_frames < 2){
    return;
  }

  while(1){ /* infinite loop, stop with button press */
    
    if(frame_no<no_frames-1){
      frame_no++;
    }else{
      frame_no=0;
    }

    if(centre_each_frame){
      /* Change frame, _don't_ reset title _and_ centre the frame */
      change_frame(frame_no, False, True);
    }else{
      /* Change frame, _don't_ reset title, _don't_ centre the frame */
      change_frame(frame_no, False, False);
    }
    
    XmUpdateDisplay(toplevel); /* handle all expose events during loop */

     /* Handle some events */
    
    while(XCheckMaskEvent
          (XtDisplay(toplevel),
           ButtonPressMask | ButtonReleaseMask | ButtonMotionMask,
           &event)){

      if(event.xany.window == XtWindow(stop_b)){
        XtDispatchEvent(&event);
      }
        
#ifdef HAVE_USLEEP
      if(event.xany.window == XtWindow(scale_scroll_bar)){
        XtDispatchEvent(&event);
      }
#endif

      if(event.xany.window == XtWindow(canvas)){
        XtDispatchEvent(&event);
        XSync(XtDisplay(toplevel),True); /* One event at a time */
      }

    }
      
    XFlush(XtDisplay(toplevel));

#ifdef HAVE_USLEEP
    /* Vary the speed */
    
    usleep(10000*(frame_speed - 1));
#endif

    if(stop_anim == 1){
      return;
    } /* stop button pressed; leave loop */
  }
}


void
stop_anim_cb(Widget widget, XtPointer client_data, XtPointer call_data)
{

  void change_frame(int, Boolean, Boolean);

  stop_anim=1;

  if(centre_each_frame){
    /* Change frame, reset title _and_ centre the frame */
    change_frame(frame_no, True, True);
  }else{
    /* Change frame, reset title, _don't_ centre the frame */
    change_frame(frame_no, True, False);
  }

}


void
next_frame_cb(Widget w, XtPointer client_data, XtPointer call_data)
{

  void change_frame(int, Boolean, Boolean);

  if(frame_no==no_frames-1){
    frame_no=0;
  }else{
    frame_no++;
  }
  
  if(centre_each_frame){
    /* Change frame, reset title _and_ centre the frame */
    change_frame(frame_no, True, True);
  }else{
    /* Change frame, reset title, _don't_ centre the frame */
    change_frame(frame_no, True, False);
  }

}


void
prev_frame_cb(Widget w, XtPointer client_data, XtPointer call_data)
{

  void change_frame(int, Boolean, Boolean);
  
  if(frame_no==0){
    frame_no=no_frames-1;
  }else{
    frame_no--;
  }

  if(centre_each_frame){
    /* Change frame, reset title _and_ centre the frame */
    change_frame(frame_no, True, True);
  }else{
    /* Change frame, reset title, _don't_ centre the frame */
    change_frame(frame_no, True, False);
  }

}


void
rewind_cb(Widget w, XtPointer client_data, XtPointer call_data)
{
  
  void change_frame(int, Boolean, Boolean);

  frame_no=0;

  if(centre_each_frame){
    /* Change frame, reset title _and_ centre the frame */
    change_frame(frame_no, True, True);
  }else{
    /* Change frame, reset title, _don't_ centre the frame */
    change_frame(frame_no, True, False);
  }
  
}


void
bounce_cb(Widget widget, XtPointer client_data, XtPointer call_data)
{

  void change_frame(int, Boolean, Boolean);

  static int frame_step=1;

  XEvent event;
  
  stop_anim=0;

  if(no_frames < 2){
    return;
  }

  while(1){ /* infinite loop, stop with button press */
    
    if(frame_no==no_frames-1){
      frame_step=-1;
#ifdef HAVE_USLEEP
      usleep(200000*(frame_speed));
#endif
    }else if(frame_no==0){
      frame_step=1;
#ifdef HAVE_USLEEP
      usleep(200000*(frame_speed));
#endif
    }

    frame_no+=frame_step;
    
    if(centre_each_frame){
      /* Change frame, _don't_ reset title _and_ centre the frame */
      change_frame(frame_no, False, True);
    }else{
      /* Change frame, _don't_ reset title, _don't_ centre the frame */
      change_frame(frame_no, False, False);
    }
    
    XmUpdateDisplay(toplevel); /* handle all expose events during loop */

     /* Handle some events */
    
    while(XCheckMaskEvent
          (XtDisplay(toplevel),
           ButtonPressMask | ButtonReleaseMask | ButtonMotionMask,
           &event)){

      if(event.xany.window == XtWindow(stop_b)){
        XtDispatchEvent(&event);
      }

#ifdef HAVE_USLEEP
      if(event.xany.window == XtWindow(scale_scroll_bar)){
        XtDispatchEvent(&event);
      }
#endif      

      if(event.xany.window == XtWindow(canvas)){
        XtDispatchEvent(&event);
        XSync(XtDisplay(toplevel),True); /* One event at a time */
      }

    }
      
    XFlush(XtDisplay(toplevel));

#ifdef HAVE_USLEEP
    /* Vary the speed */

    usleep(10000*(frame_speed-1));
#endif
    
    if(stop_anim == 1){
      return;
    } /* stop button pressed; leave loop */
  }
}


#ifdef XPM

void
make_anim_cb(Widget widget, XtPointer client_data, XtPointer call_data)
{

  void save_anim_cb(Widget, XtPointer, XtPointer);
  XmString get_current_directory ();

  XmString title;

  if(!save_anim_dialog){
    save_anim_dialog=XmCreateFileSelectionDialog
      (toplevel,"anim_sel",NULL,0);

    title=XmStringCreateLocalized("Save Frames Animation");
    XtVaSetValues(save_anim_dialog, XmNdialogTitle, title, NULL);
    XmStringFree (title);

    XtAddCallback (save_anim_dialog, XmNokCallback, save_anim_cb, NULL);
    XtAddCallback (save_anim_dialog, XmNcancelCallback, 
                     (XtCallbackProc)XtUnmanageChild, NULL);

    /* No help available ... */
    
    XtUnmanageChild
        (XmFileSelectionBoxGetChild(save_anim_dialog,XmDIALOG_HELP_BUTTON));
  }

  /* Change to a directory that was just recently operated in any of the
     file selection dialogs and refresh the file list so that newly
     created files do show up */
  
  XtVaSetValues (save_anim_dialog, XmNdirectory, get_current_directory(),NULL);
  XmFileSelectionDoSearch (save_anim_dialog, NULL);

  XtManageChild(save_anim_dialog);
  XtPopup(XtParent(save_anim_dialog),XtGrabNone);
  
}


void
save_anim_cb(Widget widget, XtPointer client_data, XtPointer call_data)
{
  void change_frame(int, Boolean, Boolean);
  void set_current_directory (XmString dir);
#ifdef GL
  void set_gl_copy_canvas (int);
#endif
  int write_xpm(char *, int);

  int i, success = 1;

  char buf[BUFSIZ], *anim_file, pixmap_file[1024];

  XmFileSelectionBoxCallbackStruct *cbs =
    (XmFileSelectionBoxCallbackStruct *) call_data;
   
  if (cbs) {
    if (!XmStringGetLtoR (cbs->value, XmFONTLIST_DEFAULT_TAG, &anim_file))
      return; /* internal error */

    /* Save directory shown in the dialog so that all the other file selection
       dialogs could be made to operate the very same directory */
    
    set_current_directory (cbs->dir);
  }

#ifdef GL
  set_gl_copy_canvas (1);
#endif

  for(i = 0; i < no_frames; i++)
    {
      frame_no = i;
    
      XmUpdateDisplay(toplevel); /* handle all expose events during loop */
    
      if(centre_each_frame)
        {
          /* Change frame, reset title _and_ centre the frame */
          change_frame(frame_no, True, True);
        }
      else
        {
          /* Change frame, reset title, _don't_ centre the frame */
          change_frame(frame_no, True, False);
        }
    
      sprintf(pixmap_file, "%s.%d.xpm", anim_file, (i + 1));

      /* Write the `canvas_pm' to the file. */
      if (write_xpm (pixmap_file, 1) == /*failure*/0)
        {
          /* Could not write the file.  Flag the error so that we know not to
             overwrite the error message currently in the message area with
             our status message. */
          success = /*failure*/ 0;
        }
    }

#ifdef GL
  set_gl_copy_canvas (0);
#endif

  if (success)
    {
      if(no_frames == 1)
        {
          sprintf(buf, "File %s.1.xpm written",
              gnu_basename(anim_file));
        }
      else
        {
          sprintf(buf, "Files %s.1..%d.xpm written",
              gnu_basename(anim_file), no_frames);
        }
      
      echo_to_message_area(buf);
    }

}
#endif


#ifdef HAVE_USLEEP
void
frame_speed_cb(Widget w, XtPointer client_data, XtPointer call_data)
{

  XmScaleCallbackStruct *cbs=
    (XmScaleCallbackStruct *)call_data;

  frame_speed = 20-cbs->value+1;

}
#endif


void
centre_b_cb(Widget widget, XtPointer client_data, XtPointer call_data)
{
  
  if(centre_each_frame==1){
    centre_each_frame=0;
    echo_to_message_area("Not centering each frame");
  }else{
    centre_each_frame=1;
    echo_to_message_area("Centering each frame");
  }

}

/* update_label is redundant - remove at some point from calls to
   change_frame */

void
change_frame(int frame_no, Boolean update_label, Boolean centre_this_frame)
{

  double get_angle_axis(double *);
  void canvas_cb(Widget, XtPointer, XtPointer);
  void centre_atoms(void);
  void make_label_uppercase (char *);
  void rotate_atoms(double *,double,Boolean,Boolean);
  void update_bbox(void);
  void update_bond_matrix(Boolean);
  void update_frame_label(void);
  void frame_content_to_atoms (int);

  int i,j;

  double angle,axis[3];

  /* if no frames, return ... */

  if(no_frames == 0) return;

  changing_frame = True;

  frame_content_to_atoms (frame_no);

  /* Centre frame if required, by modifying global_vector[] */
  
  if(centre_this_frame){
    centre_atoms();
  }

  /* get angle-axis rotation from global_matrix positions... */

  for(i=0;i<3;i++){
    for(j=0;j<3;j++){
      angle_axis_matrix[i][j]=global_matrix[i][j];
    }
  }

  angle=get_angle_axis(axis);

  update_bbox();
  update_bond_matrix (True);

  redraw=0;
  rotate_atoms(axis,angle,0,False);

  redraw=1;
  canvas_cb(canvas,NULL,NULL);

  update_frame_label();

  changing_frame = False;
}


double
get_angle_axis(double *axis)
{

  int i,j;

  double angle,s,z,mod,r[3][3];

  for(i=0;i<3;i++){
    for(j=0;j<3;j++){
      r[i][j]=angle_axis_matrix[i][j];
    }
  }

  /* adapted from quater.f (ajs) */

  z=r[0][0]+r[1][1]+r[2][2]+1;

  if(z>0){
    angle=0.5*sqrt(z);
  }else{
    angle=0;
  }

  if(angle > 0.0001){
    axis[0]=0.25*(r[2][1]-r[1][2])/angle;
    axis[1]=0.25*(r[0][2]-r[2][0])/angle;
    axis[2]=0.25*(r[1][0]-r[0][1])/angle;
  }else{
    s=1-r[0][0]-r[1][1]-r[2][2];
    for(i=0;i<3;i++){
      z=2*r[i][i]+s;
      if(z > 0){
        axis[i]=0.5*sqrt(z);
      }else{
        axis[i]=0;
      }
    }

    if(angle > 0){
      if(r[1][0] < r[0][1]){axis[2]=-axis[2];}
      if(r[2][1] < r[1][2]){axis[0]=-axis[0];}
      if(r[0][2] < r[2][0]){axis[1]=-axis[1];}
    }else{
      if(axis[0] > 0.5){
        if(r[0][1]+r[1][0] < 0){axis[1]=-axis[1];}
        if(r[0][2]+r[2][0] < 0){axis[2]=-axis[2];}
      }else if(axis[1] > 0.5){
        if(r[0][1]+r[1][0] < 0){axis[0]=-axis[0];}
        if(r[1][2]+r[2][1] < 0){axis[2]=-axis[2];}
      }else if(axis[2] > 0.5){
        if(r[0][2]+r[2][0] < 0){axis[0]=-axis[0];}
        if(r[1][2]+r[2][1] < 0){axis[1]=-axis[1];}
      }
    }
  }
  
  angle=2*acos(angle);

  mod = (axis[0] * axis[0]) + (axis[1] * axis[1]) + (axis[2] * axis[2]);

  if(mod==0){
    axis[2]=1;
  }

  return(angle);

}


void
update_frame_label(void)
{

  struct frame * get_selected_frame (int);

  char string[1024];

  struct frame *this_frame;

  XmString xm_str;

  this_frame = get_selected_frame (frame_no);

  if(no_frames == 0)
    {
      sprintf(string, "No frames loaded");
    }
  else if(strlen(this_frame->comment) == 0)
    {
      sprintf(string, "Frame %d (%d atoms); comment \"(empty)\".",
              frame_no + 1,
              this_frame->no_atoms);
    }
  else
    {
      sprintf(string, "Frame %d (%d atoms); comment \"%s\".",
              frame_no + 1,
              this_frame->no_atoms,
              this_frame->comment);
    }

  if(frames_dialog != NULL)
    {

      /* Set label in frames_dialog ... */

      xm_str = XmStringCreateLocalized(string);
      XtVaSetValues (label_w, XmNlabelString, xm_str, NULL);

      XmStringFree(xm_str);
      clear_message_area (); 
    }
  else
    {
      echo_to_message_area (string);
    }
}


void
frame_key (Widget widget, XEvent *event, String *args, int *num_args)
{

  void change_frame(int, Boolean, Boolean);

  if (*num_args != 1)
    {
      XtError ("Wrong number of args!");
    }

  if (strcmp (args[0], "next") == 0)
    {
      if (frame_no == (no_frames - 1))
        {
          frame_no = 0;
        }
      else
        {
          frame_no++;
        }
    }
  else if (strcmp (args[0], "prev") == 0)
    {
      if (frame_no == 0)
        {
          frame_no = no_frames - 1;
        }
      else
        {
          frame_no--;
        }
    }
  else if (strcmp (args[0], "start") == 0)
    {
      frame_no = 0;
    }

  /* Select the frame */

  if (centre_each_frame)
    {
      change_frame(frame_no, True, True);
    }
  else
    {
      change_frame(frame_no, True, False);
    }
}


syntax highlighted by Code2HTML, v. 0.9.1