/* newmain.c -- User interface event handling and graphics
 * Copyright (C) 1991  Douglas M. DeCarlo
 *
 * Modifications for the Amiga port Copyright (C) 1994  Torsten Klein
 *
 * This file is part of ASpringies, a mass and spring simulation system for the Amiga
 *
 * ASpringies 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 1, or (at your option)
 * any later version.
 *
 * ASpringies 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 ASpringies; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: newmain.c,v 2.9 1994/08/19 11:52:22 Torsten_Klein Exp $  
 */

#include <proto/timer.h>
#include <math.h>

#include "main.h"
#include "obj.h"

#define SetFL(obj, value)  (sprintf(temp, "%lf", (value)), \
			    cut0s(temp),\
			    setstring((obj), temp))

#define FOREachSelString(command) for (i = 0; i < num_spring; i++) \
  if (springs[i].status & S_SELECTED) (command);

#define GetForceGrav(obj, which) \
  mst.cur_grav_val[which] = GetFL(obj, cur_grav_min[which],cur_grav_max[which])

#define GetForceMisc(obj, which) \
  mst.cur_misc_val[which] = GetFL(obj, cur_misc_min[which],cur_misc_max[which])

#define DRAWLINE(rp, x1, y1, x2, y2) \
{ Move(rp, x1, y1); Draw(rp, x2, y2); }

/* Mode values */
#define M_EDIT		0L
#define M_MASS		1L
#define M_SPRING	2L

/* Behavior function modes */
#define M_BF		3

/* Number of previous mouse state saves */
#define MOUSE_PREV	4

#define NAIL_SIZE	12

/* Number of milliseconds that each drawing takes (to prevent skips) */
#define MIN_DIFFT	3000

#define AnyButton 66  
#define Button1 1
#define Button2 2
#define Button3 3

/* local prototypes 
 */
static void redraw_system(void);
static void view_system(void);
static void view_subsystem(int ulx, int uly, int wid, int ht);
static void mouse_event(int, WORD, WORD, int, int);
static void draw_edit_frame(int, int, int, int);
static void handle_edit_message(struct IntuiMessage *);
static void DoVanillaKey(struct IntuiMessage *);
static void DoRestoreState(void);
static void DoRestore(void);
static boolean GetCK(APTR);
static void draw_mass(boolean, int, int, double, boolean, boolean center);
static boolean event_handler(void);

/* global vars
 */
int draw_wid, draw_ht;

t_state mst = {
  1.0, 1.0,
  1.0, 1.0,
  FALSE, TRUE,
  -1,
  { 0, 0, 0, 0 },
  { 10.0, 5.0, 10.0, 10000.0 },
  { 0.0, 2.0, 0.0, 1.0 },
  0.0, 0.0,
  DEF_TSTEP, 1.0,
  FALSE, FALSE,
  20.0,
  TRUE, TRUE, TRUE, TRUE
  };

/* local vars
 */
typedef struct {
  int x, y;
  unsigned long t;
} mouse_info;

static mouse_info mprev[MOUSE_PREV];
static int moffset = 0;

static ULONG mode = M_EDIT;
static int front = 0; back = 1;
static BOOL erase = TRUE;
static boolean quitting = FALSE, action = FALSE;

static t_state initst, sst;
 
static double cur_grav_max[BF_NUM] = { 10000000.0,  10000000.0, 
				       10000000.0,  10000000.0   };
static double cur_grav_min[BF_NUM] = {        0.0, -10000000.0, 
				      -10000000.0, -10000000.0   };
static double cur_misc_max[BF_NUM] = {      360.0,  10000000.0, 
					   1000.0,      1000.0   };
static double cur_misc_min[BF_NUM] = {     -360.0,         0.0,
					     0.0,         -0.0   };

static char temp[20];		/* for SetSL */
     
/*****************************************************/

void new_main(void)
{
  initst = sst = mst;

  draw_wid = anim_wnd->Width;
  draw_ht  = anim_wnd->Height;
  SetDrMd(anim_rport[back], JAM1);
  SetDrMd(anim_rport[front], JAM1);

  redisplay_widgets();
  review_system();
  init_objects();

  save_state(); sst = mst;	/* kludge */

  while (event_handler()); 

  delete_all();
}

/*****************************************************/

static void cut0s(char *str)
{
  char *cp = &str[strlen(str)-1];

  while (*cp=='0' && *(cp-1)!='.') *(cp--) = '\0';
}

/*****************************************************/

static double GetFL(APTR obj, double min, double max)
{
  char *contents;
  double result;

  get(obj, MUIA_String_Contents, &contents);
  sscanf(contents, "%lf", &result);

  if (result<min) result=min;
  if (result>max) result=max;

  SetFL(obj, result); 
  return(result);
}

/*****************************************************/

static boolean GetCK(APTR obj) {
  ULONG result;
  
  get(obj, MUIA_Selected, &result);
  return((boolean)result);
}

/*****************************************************/

void redisplay_widgets(void) 
{
  setcheckmark(CK_Action,       action);
  setcheckmark(CK_DrawSpr,      mst.show_spring);
  setcheckmark(CK_AdaptTStep,   mst.adaptive_step);
  setcheckmark(CK_FixedMass,    mst.fix_mass);
  setcheckmark(CK_WallTop,      mst.w_top);
  setcheckmark(CK_WallLeft,     mst.w_left);
  setcheckmark(CK_WallRight,    mst.w_right);
  setcheckmark(CK_WallBottom,   mst.w_bottom);
  setcheckmark(CK_GravActive,   mst.bf_mode[FR_GRAV]);
  setcheckmark(CK_CenterActive, mst.bf_mode[FR_CMASS]);
  setcheckmark(CK_PtAttrActive, mst.bf_mode[FR_PTATTRACT]);
  setcheckmark(CK_WallFActive,  mst.bf_mode[FR_WALL]);

  setslider(SL_Grid, mst.grid_snap ? (ULONG)mst.cur_gsnap : 1L);

  SetFL(ST_Mass,       mst.cur_mass);
  SetFL(ST_Elas,       mst.cur_rest);
  SetFL(ST_Kspr,       mst.cur_ks);
  SetFL(ST_Kdmp,       mst.cur_kd);
  SetFL(ST_Visc,       mst.cur_visc);
  SetFL(ST_Stic,       mst.cur_stick);
  SetFL(ST_Step,       mst.cur_dt);
  SetFL(ST_Prec,       mst.cur_prec);
  SetFL(ST_GravGrav,   mst.cur_grav_val[FR_GRAV]);
  SetFL(ST_GravDir,    mst.cur_misc_val[FR_GRAV]);
  SetFL(ST_CenterMagn, mst.cur_grav_val[FR_CMASS]);
  SetFL(ST_CenterDamp, mst.cur_misc_val[FR_CMASS]);
  SetFL(ST_PtAttrMagn, mst.cur_grav_val[FR_PTATTRACT]);
  SetFL(ST_PtAttrExpo, mst.cur_misc_val[FR_PTATTRACT]);
  SetFL(ST_WallFMagn,  mst.cur_grav_val[FR_WALL]);
  SetFL(ST_WallFExpo,  mst.cur_misc_val[FR_WALL]);
}

/*****************************************************/

static boolean event_handler(void)
{
  static LONGBITS MUI_Signals;
  static boolean quitting = FALSE;

  struct IntuiMessage *msg;
  int i;

  do
    switch (DoMethod(AP_ASpringies, MUIM_Application_Input, &MUI_Signals))
      {
      case MUIV_Application_ReturnID_Quit :
	quitting = (BOOL)MUI_Request(AP_ASpringies, WI_ASpringies, 0,
				     "Quit request", "Yes|*No",
				     "Would you like to quit ASpringies?");
	break;

      case ID_RA_Mode       : get(RA_Mode, MUIA_Radio_Active, &mode); break;
      case ID_CK_DrawSpr    : mst.show_spring=GetCK(CK_DrawSpr); review_system();break;
      case ID_CK_AdaptTStep : mst.adaptive_step=GetCK(CK_AdaptTStep); break;
      case ID_CK_Action     : action=           GetCK(CK_Action);     break;
      case ID_CK_FixedMass  : 
	mst.fix_mass = GetCK(CK_FixedMass);
	for (i = 0; i < num_mass; i++) 
	  if (masses[i].status & S_SELECTED) {
	    if (mst.fix_mass) masses[i].status |= S_FIXED;
	    else              masses[i].status &= ~(S_FIXED | S_TEMPFIXED);
	  }
	review_system();
	break;

      case ID_CK_WallTop    : mst.w_top    = GetCK(CK_WallTop);    review_system();
	break;
      case ID_CK_WallLeft   : mst.w_left   = GetCK(CK_WallLeft);   review_system();
	break;
      case ID_CK_WallRight  : mst.w_right  = GetCK(CK_WallRight);  review_system();
	break;
      case ID_CK_WallBottom : mst.w_bottom = GetCK(CK_WallBottom); review_system();
	break;

      case ID_CK_GravActive  : mst.bf_mode[FR_GRAV]      =GetCK(CK_GravActive);  break;
      case ID_CK_CenterActive: mst.bf_mode[FR_CMASS]     =GetCK(CK_CenterActive);break;
      case ID_CK_PtAttrActive: mst.bf_mode[FR_PTATTRACT] =GetCK(CK_PtAttrActive);break;
      case ID_CK_WallFActive : mst.bf_mode[FR_WALL]      =GetCK(CK_WallFActive); break;

      case ID_BT_SelAll    : select_all(); eval_selection(); review_system(); break;
      case ID_BT_SetRLen   : set_sel_restlen(); break;
      case ID_BT_SetCenter : set_center(); review_system(); break;

      case ID_SL_Grid : {
	ULONG result;
	get(SL_Grid, MUIA_Slider_Level, &result);
	mst.grid_snap = (mst.cur_gsnap = result) != 1;
      } 
	break;

      case ID_ST_Mass : 
	mst.cur_mass = GetFL(ST_Mass, 0.01, 10000000.0);
	for (i = 0; i < num_mass; i++) 
	  if (masses[i].status & S_SELECTED) {
	    masses[i].mass = mst.cur_mass;
	    masses[i].radius = mass_radius(mst.cur_mass);
	  }
	review_system();
	break;
      case ID_ST_Elas :
	mst.cur_rest = GetFL(ST_Elas, 0.0, 1.0);
	for (i = 0; i < num_mass; i++) {
	  if (masses[i].status & S_SELECTED)
	    masses[i].elastic = mst.cur_rest;
	}
	break;
      case ID_ST_Kspr : 
	mst.cur_ks = GetFL(ST_Kspr, 0.01, 10000000.0);
	FOREachSelString(springs[i].ks = mst.cur_ks);
	break;
      case ID_ST_Kdmp : 
	mst.cur_kd = GetFL(ST_Kdmp, 0.0, 10000000.0);
	FOREachSelString(springs[i].kd = mst.cur_kd);
	break;
      case ID_ST_Visc : mst.cur_visc  = GetFL(ST_Visc, 0.0,    10000000.0); break;
      case ID_ST_Stic : mst.cur_stick = GetFL(ST_Stic, 0.0,    10000000.0); break;
      case ID_ST_Step : mst.cur_dt    = GetFL(ST_Step, 0.0001,        1.0); break;
      case ID_ST_Prec : mst.cur_prec  = GetFL(ST_Prec, 0.0001,     1000.0); break;

      case ID_ST_GravGrav   : GetForceGrav(ST_GravGrav,   FR_GRAV);      break;
      case ID_ST_GravDir    : GetForceMisc(ST_GravDir,    FR_GRAV);      break;
      case ID_ST_CenterMagn : GetForceGrav(ST_CenterMagn, FR_CMASS);     break;
      case ID_ST_CenterDamp : GetForceMisc(ST_CenterDamp, FR_CMASS);     break;
      case ID_ST_PtAttrMagn : GetForceGrav(ST_PtAttrMagn, FR_PTATTRACT); break;
      case ID_ST_PtAttrExpo : GetForceMisc(ST_PtAttrExpo, FR_PTATTRACT); break;
      case ID_ST_WallFMagn  : GetForceGrav(ST_WallFMagn,  FR_WALL);      break;
      case ID_ST_WallFExpo  : GetForceMisc(ST_WallFExpo,  FR_WALL);      break;

      case ID_ME_About      : DoInfo();                break;
      case ID_ME_Help       : 
	DoMethod(AP_ASpringies, MUIM_Application_ShowHelp, WI_ASpringies,
		 HELP_FILE, "Main", 0);
	break;
      case ID_ME_Load       : FileIO(F_LOAD);   break;
      case ID_ME_Insert     : FileIO(F_INSERT); break;
      case ID_ME_Save       : FileIO(F_SAVE);   break;
      case ID_ME_SaveAs     : FileIO(F_SAVEAS); break;
      case ID_ME_DupSel     : duplicate_selected(); review_system(); break;
      case ID_ME_DeleteSel  : delete_selected();    review_system(); break;
      case ID_ME_Restore    : 
	if (MUI_Request(AP_ASpringies, WI_ASpringies, 0, "Restore request", "Yes|*No",
			"Restore last Snapshot?"))
	  DoRestoreState();
	break;
      case ID_ME_Snapshot   : save_state(); sst = mst; break;
      case ID_ME_Reset      : 
	if (MUI_Request(AP_ASpringies, WI_ASpringies, 0, "Reset request", "Yes|*No",
			"Reset ASpringies?"))
	  DoRestore();             
	break;
      case ID_ME_ScreenMode :	/* noch besser machen! */
	CloseAnimDisplay(); OpenAnimDisplay(TRUE);
	draw_wid = anim_wnd->Width;
	draw_ht  = anim_wnd->Height;
	SetDrMd(anim_rport[back], JAM1);
	SetDrMd(anim_rport[front], JAM1);
	review_system();
	break;
      case ID_KY_AnimToFront : 
	ScreenToFront(anim_scr);
	ActivateWindow(anim_wnd);
	break;
      } while (!MUI_Signals);

  if (quitting) 
    return(FALSE);

  if (action && animate_obj()) {
    static struct timeval tp, tpo;
    static int totaldiff = 0;
    static boolean started = FALSE;
    static ULONG av_fps, fpscount;
    int difft;

    if (!started) {
      GetSysTime(&tp);
      started = TRUE;
    }
    tpo = tp;

    GetSysTime(&tp);
    difft = (tp.tv_secs - tpo.tv_secs) * 1000000 + (tp.tv_micro - tpo.tv_micro);

    if (difft < MIN_DIFFT)
      difft = MIN_DIFFT;

    if (difft > 0) {
      totaldiff += difft;

      fpscount++;      
      av_fps += (ULONG)(1.e6/difft+.5);
      if (fpscount == 10) {
	set(TX_Fps, MUIA_Text_Contents, (sprintf(temp, "%2ld", av_fps/10), temp));
	av_fps = fpscount = 0;
      }
      /* Do updates about 30 frames per second */
      if ((totaldiff > MIN_DIFFT)) { 
	review_system();      
      } 
    }
  } 
  else if (!action && MUI_Signals) {
    Wait(MUI_Signals | (1 << anim_wnd->UserPort->mp_SigBit));
  }

  while (msg= (struct IntuiMessage *)GetMsg(anim_wnd->UserPort)) 
    handle_edit_message(msg);

  return(TRUE);
}

/*****************************************************/

static void handle_edit_message(struct IntuiMessage *msg)
{
  int
    shifted = msg->Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT),
    alt     = msg->Qualifier & (IEQUALIFIER_LALT   | IEQUALIFIER_RALT);

  switch (msg->Class)     
    {
    case IDCMP_MOUSEBUTTONS: 
      switch(msg->Code)
	{
	case SELECTDOWN:
	  msg->IDCMPWindow->Flags |= WFLG_REPORTMOUSE;
	  memset(mprev, 0, sizeof(mprev));
	  mouse_event(M_DOWN, msg->MouseX, msg->MouseY, 
		      alt ? Button2 : Button1, shifted);
	  break;

	case SELECTUP:
	  msg->IDCMPWindow->Flags &= ~WFLG_REPORTMOUSE;
	  mouse_event(M_UP,   msg->MouseX, msg->MouseY, 
		      alt ? Button2 : Button1, FALSE);
	  break;

	case MIDDLEDOWN: alt = TRUE;
	case MENUDOWN:
	  msg->IDCMPWindow->Flags |= WFLG_REPORTMOUSE;
	  memset(mprev, 0, sizeof(mprev));  
	  mouse_event(M_DOWN, msg->MouseX, msg->MouseY, 
		      alt ? Button2 : Button3, shifted);
	  break;

	case MIDDLEUP: alt = TRUE;
	case MENUUP:
	  msg->IDCMPWindow->Flags &= ~WFLG_REPORTMOUSE;
	  mouse_event(M_UP,   msg->MouseX, msg->MouseY, 
		      alt ? Button2 : Button3, FALSE);
	  break;
	}
    case IDCMP_MOUSEMOVE: 
      mprev[moffset].x = msg->MouseX;
      mprev[moffset].y = msg->MouseY;
      mprev[moffset].t = (unsigned long)1000 * msg->Seconds + .001 * msg->Micros;
      moffset = (moffset + 1) % MOUSE_PREV;
      mouse_event(M_DRAG, msg->MouseX, msg->MouseY, AnyButton, FALSE);
      break;	
    case IDCMP_VANILLAKEY:
      DoVanillaKey(msg);
      break;
    case IDCMP_RAWKEY:
      break;
    }
  ReplyMsg((struct Message *)msg);
}

/*****************************************************/


static void DoVanillaKey(struct IntuiMessage *msg)
{
  switch(msg->Code) 
    {
    case IECODE_ASCII_DEL :  delete_selected(); review_system(); break;
    case '0' : erase = FALSE; break;
    case '1' : erase = TRUE; break;
    case ' ' : 
      DoMethod(WI_ASpringies, MUIM_Window_ScreenToFront);
      DoMethod(WI_ASpringies, MUIM_Window_ToFront);
      set(WI_ASpringies, MUIA_Window_Activate, TRUE);
      break;
    }
}

/*****************************************************/

int mass_radius(double m)
{
  int rad;

  rad = (int)(2 * log(4.0 * m + 1.0));

  if (rad < 1) rad = 1;
  if (rad > 64) rad = 64;

  return rad;
}

/*****************************************************/

static void mouse_vel(int *mx, int *my)
{
  int i, totalx = 0, totaly = 0, scale = 0, dx, dy, dt;
  int fudge = 256;

  for (i = 0; i < MOUSE_PREV - 1; i++) {
    dx = mprev[(moffset + 2 + i) % MOUSE_PREV].x 
      -  mprev[(moffset + 1 + i) % MOUSE_PREV].x;
    dy = mprev[(moffset + 2 + i) % MOUSE_PREV].y 
      -  mprev[(moffset + 1 + i) % MOUSE_PREV].y;
    dt = mprev[(moffset + 2 + i) % MOUSE_PREV].t 
      -  mprev[(moffset + 1 + i) % MOUSE_PREV].t;
	
    if (dt) {
      scale += 64 * i * i;
      totalx += 64 * i * i * fudge * dx / dt;
      totaly += 64 * i * i * fudge * dy / dt;
    }
  }

  if (scale) {
    totalx /= scale;
    totaly /= scale;
  }
  
  *mx = totalx;
  *my = totaly;
}

/*****************************************************/

static void view_subsystem(int ulx, int uly, int wid, int ht)
{
  if (ulx < 0) {
    wid += ulx;
    ulx = 0;
  }
  if (uly < 0) {
    ht += uly;
    uly = 0;
  }
  if (wid < 0 || ht < 0)
    return;

  if (ulx + wid >= draw_wid)
    wid -= (ulx + wid - draw_wid);
  if (uly + ht >= draw_ht)
    ht -= (uly + ht - draw_ht);

  if (wid < 0 || ht < 0)
    return;

  if (!action)
    ClipBlit(anim_rport[back], ulx, uly, anim_rport[front], ulx, uly,
	     wid, ht, 0xC0);
}

/*****************************************************/

void review_system(void)
{
  /* Clear the old pixmap */
  if (erase) SetRast(anim_rport[back], 0);

  redraw_system();
  view_system();

  mouse_event(M_REDISPLAY, 0, 0, AnyButton, FALSE);

  if (mst.adaptive_step) 
    SetFL(ST_Step, mst.cur_dt);
}

/*****************************************************/

static void mouse_event(int type, WORD mx, WORD my, int mbutton, int shifted)
 {
  static int 
    startx = 0, prevx = 0, starty = 0, prevy = 0, 
    cur_button = 0, selection = -1;
  static boolean 
    active = FALSE, cur_shift = FALSE, static_spring = FALSE;

  /* Skip restarts when active or continuations when inactive */
  if ((type != M_DOWN && !active) || (type == M_DOWN && active))
    return;

  /* Do grid snapping operation */
  if (mst.grid_snap && mode != M_EDIT ){
    mx = ((mx + (int)mst.cur_gsnap/2)/(int)mst.cur_gsnap) * (int)mst.cur_gsnap;
    my = ((my + (int)mst.cur_gsnap/2)/(int)mst.cur_gsnap) * (int)mst.cur_gsnap;
  }

  switch (type) 
    {
    case M_REDISPLAY:
      switch (mode)
	{
	case M_EDIT:
	  switch (cur_button) 
	    {
	    case Button1:
	      if (selection < 0) 
		draw_edit_frame(startx, starty, prevx, prevy);
	      break;
	    case Button2:
	      break;
	    case Button3:
	      break;
	    }
	  break;
	case M_MASS:
	  draw_mass(TRUE, prevx, prevy, mst.cur_mass, FALSE, mst.fix_mass);
	  break;
	case M_SPRING:
	  if (static_spring) {
	    startx = COORD_X(masses[selection].x);
	    starty = COORD_Y(masses[selection].y);
	    DRAWLINE(anim_rport[front], startx, starty, prevx, prevy);
	  }
	  break;
	}
      break;

    case M_DOWN:
      review_system();
      startx = prevx = mx;
      starty = prevy = my;
      cur_button = mbutton;
      active = TRUE;
      cur_shift = shifted;

      switch (mode) 
	{
	case M_EDIT:
	  switch (cur_button) 
	    {
	    case Button1:
	      {
		boolean is_mass = FALSE;
		selection = nearest_object(COORD_X(mx), COORD_Y(my), &is_mass);
		
		/* If not shift clicking, unselect all currently selected items 
		 */
		if (!shifted) {
		  unselect_all();
		  review_system();
		}
	      }
	      break;
	    case Button2:
	    case Button3:
	      tempfixed_obj(TRUE);
	      break;
	    }
	  break;
	case M_MASS:
	  draw_mass(TRUE, mx, my, mst.cur_mass, FALSE, mst.fix_mass);
	  break;
	case M_SPRING:		/* start setting a new spring */
	  {
	    boolean is_mass = TRUE;

	    /* static = doesn't affect the mass it's attached to
	     */
	    static_spring = (!action || cur_button == Button3);

	    selection = nearest_object(COORD_X(mx), COORD_Y(my), &is_mass);
	    if (selection >= 0 && is_mass) {
	      startx = COORD_X(masses[selection].x);
	      starty = COORD_Y(masses[selection].y);
	      if (static_spring) {
		DRAWLINE(anim_rport[front], startx, starty, prevx, prevy);
	      } else {
		attach_fake_spring(selection);
		move_fake_mass(COORD_X(mx), COORD_Y(my));
		review_system();
	      }
	    } else {
	      active = FALSE;
	    }
	  }
	  break;
	}
      break;

    case M_DRAG:
      switch (mode) 
	{
	case M_EDIT:
	  switch (cur_button) 
	    {
	    case Button1:
	      if (selection < 0) { /* redraw frame */
		view_subsystem(MIN(startx, prevx), MIN(starty, prevy), 
			       ABS(prevx - startx)+1, ABS(prevy - starty)+1);
		prevx = mx;
		prevy = my;
		draw_edit_frame(startx, starty, prevx, prevy);
	      }
	      break;
	    case Button2:
	    case Button3:
				/* Move objects relative to mouse */
	      translate_selobj(COORD_DX(mx - prevx), COORD_DY(my - prevy));
	      review_system();
	      prevx = mx;
	      prevy = my;
	      break;
	    }	
	  break;
	case M_MASS:		/* move dragged mass to new position */
	  {
	    int rad = mst.fix_mass ? NAIL_SIZE : mass_radius(mst.cur_mass); 
	    view_subsystem(prevx - rad, prevy - rad, rad * 2 + 1, rad * 2 + 1);
	  }
	  prevx = mx;
	  prevy = my;
	  draw_mass(TRUE, prevx, prevy, mst.cur_mass, FALSE, mst.fix_mass);
	  break;
	case M_SPRING:		/* redraw spring being edited to new end position */
	  if (static_spring) {
	    view_subsystem(MIN(startx, prevx), MIN(starty, prevy), 
			   ABS(prevx - startx) + 1, ABS(prevy - starty) + 1);
	    prevx = mx;
	    prevy = my;
	    startx = COORD_X(masses[selection].x);
	    starty = COORD_Y(masses[selection].y);
	    DRAWLINE(action ? anim_rport[back] : anim_rport[front], startx, starty, prevx, prevy);
	  } else {
	    move_fake_mass(COORD_X(mx), COORD_Y(my));
	  }
	  break;
	}
      break;

    case M_UP:
      active = FALSE;
      switch (mode) {
      case M_EDIT:
	switch (cur_button) {
	case Button1:
	  if (selection < 0) {	/* select objects in current frame*/
	    select_objects(MIN(COORD_X(startx), COORD_X(mx)), 
			   MIN(COORD_Y(starty), COORD_Y(my)),
			   MAX(COORD_X(startx), COORD_X(mx)), 
			   MAX(COORD_Y(starty), COORD_Y(my)), cur_shift);
	    eval_selection();
	    review_system();
	  } else {		/* select single object */
	    boolean is_mass = FALSE;
		
	    if ((selection = nearest_object(COORD_X(mx), COORD_Y(my), 
					    &is_mass)) >= 0) {
	      select_object(selection, is_mass, cur_shift);
	      eval_selection();
	      review_system();
	    }
	  }
	  break;
	case Button2:
	  tempfixed_obj(FALSE);
	  review_system();
	  break;
	case Button3:		/* throw objects */
	  {
	    int mvx, mvy;
	    
	    mouse_vel(&mvx, &mvy);

	    changevel_selobj(COORD_DX(mvx), COORD_DY(mvy), FALSE);
	    tempfixed_obj(FALSE);
	    review_system();
	  }
	  break;
	}
	break;
      case M_MASS:		/* create new mass */
	{
	  int newm;

	  newm = create_mass();

	  masses[newm].x = COORD_X((double)mx);
	  masses[newm].y = COORD_Y((double)my);
	  masses[newm].mass = mst.cur_mass;
	  masses[newm].radius = mass_radius(mst.cur_mass);
	  masses[newm].elastic = mst.cur_rest;
	  if (mst.fix_mass) masses[newm].status |= S_FIXED;

	  review_system();
	}
	break;

      case M_SPRING:		/* create new spring */
	{
	  boolean is_mass = TRUE;
	  int start_sel = selection, news, endx, endy;

	  if (!static_spring) 
	    kill_fake_spring();
		
	  selection = nearest_object(COORD_X(mx), COORD_Y(my), &is_mass);
	  if ((static_spring || !action || cur_button == Button1)
	      && selection >= 0 && is_mass && selection != start_sel) {
	    startx = COORD_X(masses[start_sel].x);
	    starty = COORD_Y(masses[start_sel].y);
	    endx = COORD_X(masses[selection].x);
	    endy = COORD_Y(masses[selection].y);
		
	    news = create_spring();
	    springs[news].m1 = start_sel;
	    springs[news].m2 = selection;
	    springs[news].ks = mst.cur_ks;
	    springs[news].kd = mst.cur_kd;
	    springs[news].restlen = sqrt(  (double)SQR(startx - endx)
					 + (double)SQR(starty - endy));
	    add_massparent(start_sel, news);
	    add_massparent(selection, news);
	  }
	  review_system();
	}
	break;
      }
      break;
    }
}

/*****************************************************/

static void view_system(void)
{
  int old_back = back;

  ShowView(back);
  back = front;
  front = old_back;
}

/*****************************************************/

static void redraw_system(void)
{  
  int     i, rad, num_spr;  
  mass   *m1ptr, *m2ptr;
  spring *sptr;

  num_spr = (mst.show_spring) ? num_spring : 1;

  SetDrPt(anim_rport[back], 0xffff);
  if (mst.w_top)    DRAWLINE(anim_rport[back],1,          1,         draw_wid-2, 1        );
  if (mst.w_left)   DRAWLINE(anim_rport[back],1,          1,         1,          draw_ht-2);
  if (mst.w_right)  DRAWLINE(anim_rport[back],draw_wid-2, 1,         draw_wid-2, draw_ht-2);
  if (mst.w_bottom) DRAWLINE(anim_rport[back],1,          draw_ht-2, draw_wid-2, draw_ht-2);
  
  {
    static UWORD selspringpat = 0x0f0f;
    
    if (action)
      selspringpat = (selspringpat >> 15) + (selspringpat << 1);

    for (i=0, sptr = springs; i<num_spr; i++, sptr++) {
      m1ptr = masses + sptr->m1;
      m2ptr = masses + sptr->m2;
      if (sptr->status & S_ALIVE) {
	if (sptr->status & S_SELECTED)
	  SetDrPt(anim_rport[back], selspringpat);
	DRAWLINE(anim_rport[back], (int)COORD_X(m1ptr->x), (int)COORD_Y(m1ptr->y),
		 (int)COORD_X(m2ptr->x), (int)COORD_Y(m2ptr->y));
	SetDrPt(anim_rport[back], 0xffff);
      }
    }
  }

  for (i=0, m1ptr = masses; i<num_mass; i++, m1ptr++) 
    if (m1ptr->status & S_ALIVE)
      draw_mass(FALSE, (int)(COORD_X(m1ptr->x)), (int)(COORD_Y(m1ptr->y)),
		m1ptr->mass, (boolean)(m1ptr->status & S_SELECTED),
		(boolean)((m1ptr->status & S_FIXED) 
			  && !(m1ptr->status & S_TEMPFIXED)));      
  

  if (mst.center_id != -1)
    if (((m1ptr = &masses[mst.center_id])->status & S_FIXED)
	&& !(m1ptr->status & S_TEMPFIXED)) { 
				/* Draw center (fixed mass) */
      int 
	cx = (int)COORD_X(m1ptr->x),
	cy = (int)COORD_Y(m1ptr->y);

      SetAPen(anim_rport[back], 1);
      Move(anim_rport[back], cx-6, cy-12); 
      Draw(anim_rport[back], cx+6, cy-12);
      Draw(anim_rport[back], cx+6, cy+1);
      Draw(anim_rport[back], cx-6, cy+1);
      Draw(anim_rport[back], cx-6, cy-12);
    }
    else {
      rad = m1ptr->radius;
      
      SetAPen(anim_rport[back], 0);
      SetAfPt(anim_rport[back], 0,1);
      RectFill(anim_rport[back],
	       (int)COORD_X(m1ptr->x) - rad + 1, (int)COORD_Y(m1ptr->y) - rad + 1,
	       (int)COORD_X(m1ptr->x) + rad - 1, (int)COORD_Y(m1ptr->y) + rad - 1);
      SetAPen(anim_rport[back], 1);
    }
}

/*****************************************************/

static void draw_mass
  (
   boolean to_front_rp,
   int mx,
   int my,
   double mass,
   boolean selected,
   boolean fixed
   )
{
  static __chip UWORD
    data_unsel[20]  = {7936,24768,32800,32800,32800,57568,32704,7936,1024,
			 1024,1024},
    data_sel[20]    = {2560,8320,32800,0,32800,0,21824,2560,1024,0,1024},
    selpattern[2]   = {0x5555, 0xaaaa},
    unselpattern[2] = {0xffff, 0xffff};
  static struct Image 
    UnselNailImage = {0, 0, 11,11, 1, &data_unsel[0], 0x01, 0x00, NULL},
    SelNailImage   = {0, 0, 11,11, 1, &data_sel[0],   0x01, 0x00, NULL};
  struct RastPort
    *rp = to_front_rp ? anim_rport[front] : anim_rport[back];
  int
    rad = fixed ? NAIL_SIZE : mass_radius(mass); 

  if (fixed) {
    if (mx+5 >= 0 && mx-5 <= draw_wid && my >= 0 && my-11 <= draw_ht)
      DrawImage(rp, selected ? &SelNailImage : &UnselNailImage, mx - 5, my - 11);
  } else if (mx+rad >= 0 && mx-rad <= draw_wid && my+rad >= 0 && my-rad <= draw_ht) {
    SetAfPt(rp, selected ? selpattern : unselpattern,1);
    RectFill(rp, mx - rad, my - rad, mx+rad, my+rad);
  }
}

/*****************************************************/

static void draw_edit_frame(int x1, int y1, int x2, int y2)
{
  static UWORD pattern = 0x0f0f;

  if (action)
    pattern = (pattern >> 15) + (pattern << 1);
  SetDrPt(anim_rport[front], pattern);
  Move(anim_rport[front], x1, y1);
  Draw(anim_rport[front], x2, y1); Draw(anim_rport[front], x2, y2);
  Draw(anim_rport[front], x1, y2); Draw(anim_rport[front], x1, y1);  
  SetDrPt(anim_rport[front], 0xffff);
}

/*****************************************************/

static void DoRestoreState(void)
{
  action = FALSE;
  restore_state();
  mst = sst;

  redisplay_widgets();
  review_system();
  mouse_event(M_REDISPLAY, 0, 0, AnyButton, FALSE);
}

/*****************************************************/

static void DoRestore(void)
{
  action = FALSE;
  delete_all();
  mst = initst;
  init_objects();
  
  redisplay_widgets();
  review_system();
  mouse_event(M_REDISPLAY, 0, 0, AnyButton, FALSE);
}

/*****************************************************/
