/*
* curve_editor.cc
* DIN Is Noise is copyright (c) 2006-2024 Jagannathan Sampath
* DIN Is Noise is released under GNU Public License 2.0
* For more information, please visit https://dinisnoise.org/
*/

#include "input.h"
#include "curve_editor.h"
#include "curve_library.h"
#include "console.h"
#include "font.h"
#include "utils.h"
#include "random.h"
#include "sine_mixer.h"
#include "beat2value.h"
#include "vector2d.h"
#include "fft.h"
#include "main.h"
#include "ui_list.h"
#include "chrono.h"
#include "viewwin.h"
#include "spiraler.h"
#include "rose_milker.h"
#include "instrument.h"
#include "capturer.h"

using namespace std;

extern int lmb, rmb;
extern ofstream dlog;
extern string user_data_dir;
extern int SAMPLE_RATE;
extern int FPS;
extern double TIME_PER_FRAME;
extern const float TWO_PI, PI;
extern char BUFFER [];
extern instrument* get_current_instrument ();

void show_curve_picker () {
  curve_picker.show ();
}

curve_editor::curve_editor (const std::string& settingsf) {

  curves = 0;
  curcrv = 0;
  curcrvchgd = 1;
  savedrots = 0;

  todo = next_todo = NOTHING;
  cursor_mesg = next_cursor_mesg = "";

  waved = fft_enabled = samples_enabled = draw_curve_only = 0;

  load  (settingsf);

  lmb_move = 0;
  lmb_clicked = 0;

  gl_pts = 0;
  n_pts = 0;

	hlabel_only = 0;
	
	mark_segments = 0;

}

curve_editor::~curve_editor () {
  save ();
	dlog << "-- destroyed curve editor: " << name << " ---" << endl;
}

void curve_editor::load (const std::string& fname) {

  // load settings from file
  std::string feditor = user_data_dir + fname;
  ifstream file (feditor.c_str (), ios::in);
  if (!file) {
    dlog << "!!! cannot open curve editor settings from: " << fname << " !!!" << endl;
    return;
  } else dlog << "<<< loading curve editor settings from: " << feditor;

  settings_filename = fname;

  std::string ignore;

  basic_editor::load (file);

  int ct; file >> ignore >> ct;
  carry_tangents = ct;

  int mt; file >> ignore >> mt;
  mirror_tangents = mt;

  file >> ignore >> label_vertices;

  tb.load (file);

  file >> ignore >> waved;
  if (waved) {
    file >> ignore >> fft_enabled;
    file >> ignore >> samples_enabled;
    file >> ignore >> hz >> ignore >> nperiods;
    cs.set (hz, nperiods);
  }

  file >> ignore >> draw_curve_only;
	file >> ignore >> kbkb_attack_editor;
	file >> ignore >> overlay;
	file >> ignore >> axis;
	file >> ignore >> draw_plugin_output;

  file >> ignore >> savedrots;
  if (savedrots) {
    curveinfo.resize (savedrots);
    float deg;
    for (int i = 0; i < savedrots; ++i) {
      curve_info& ci = curveinfo[i];
      float s, t;
      file >> ci.lastrpm >> ci.angle >> ci.totang >> ci.dir >> deg >> ci.autoflip >> ci.randoflip >> s >> t; 
      ci.rd.set (s, t);
      ci.every0 = deg; 
      ci.every = ci.every0;
      ci.startt = 0;
    }
  }

  dlog << ", done >>>" << endl;

}

void curve_editor::save () {

  // save settings to file
  //
  ofstream file ((user_data_dir + settings_filename).c_str(), ios::out);
  if (!file) {
    dlog << "cannot save " << settings_filename << endl;
    return;
  }

  basic_editor::save (file);

  file << "carry_tangents " << carry_tangents << endl;
  file << "mirror_tangents " << mirror_tangents << endl;
  file << "label_vertex " << label_vertices << endl;

  tb.clear ();
  tb.save (file);

  file << "waved " << waved << endl;
  if (waved) {
    file << "fft_enabled " << fft_enabled << endl;
    file << "samples_enabled " << samples_enabled << endl;
    file << "hz " << hz << endl;
    file << "nperiods " << nperiods << endl;
  }
  file << "draw_curve_only " << draw_curve_only << endl;
	file << "sustain_editing " << kbkb_attack_editor << endl;
	file << "overlay " << overlay << endl;
	file << "mirror_axis " << axis << endl;
	file << "draw_plugin_output " << draw_plugin_output << endl;

  file << "rotatingcurves " << curves << endl;
  for (int i = 0; i < curves; ++i) {
    curve_info& ci = curveinfo[i];
    file << ci.lastrpm << spc << ci.angle << spc << ci.totang << spc << ci.dir << spc << ci.every0.deg << spc << ci.autoflip << spc << ci.randoflip << spc << ci.rd.min << spc << ci.rd.max << spc;
  }

}

void curve_editor::prep_move () {
	undos.push_back (undo_t (pik.crv_id, *pik.crv, win));
	if (keydown (SDLK_SPACE)) {
		cons << YELLOW << "Moving whole curve";
		todo = MOVE_ALL;
	} else {
		todo = MOVE_PICKED;
		cons << YELLOW << "Moving " << pik;
	}
	cons << eol;
	lmb_move = PREP;
}

int curve_editor::handle_input () {

  basic_editor::handle_input (); // pan, zoom, snap etc

  if (lmb) {

    if (lmb_clicked == 0) { // first lmb

			lmb_clicked = 1;

			if (lmb_move == FINISH_ON_CLICK) {
				cons << RED << "Finished moving " << pik << eol;
				quick_nothing ();
				return 1;
			}

      if (todo == NOTHING) { // try to hit something to move
				hittest ();
        if (pik()) { // hit
					if (pomo.hit (pik))
						; // point modulator is moving hit element so ignore
					else
						prep_move ();
          return 1;
        }
      } else { 
				switch (todo) {
					case ADD_VERTEX:
						add_vertex ();
						return 1;
					case INSERT_VERTEX:
						if (!pik()) pik = pick_cur_curve (); 
						insert ();
						return 1;
					case REMOVE_VERTEX:
						hittest ();
						if (pik ()) remove ();
						return 1;
					case MODULATE_POINT:
						hittest ();
						modulate_point (1);
						return 1;
					case FOLD_VERTEX:
						hittest ();
						if (pik()) fold_tangents_of_vertex (pik);
						return 1;
					case UNFOLD_VERTEX:
						hittest ();
						if (pik()) unfold_tangents_of_vertex (pik);
						return 1;
					case MIRROR_VERTEX:
						hittest ();
						if (pik()) mirror ();
						return 1;
					case START_CAPTURE:
						abs_nothing ();
						return 1;
					case ASSIGN_CAPTURE:
						hittest ();
						assign_mouse_capture ();
						return 1;
					case PICK_CURVE:
            hittest ();
						if (pik()) curve_picked ();
						return 1;
				}
			}
    } 
  } else {
		lmb_clicked = 0;
		if (lmb_move && !MENU.show) {
			if (todo == MOVE_PICKED) move (); else 
			if (todo == MOVE_ALL) move (1); // all
			lmb_move = FINISH_ON_CLICK; 
			return 1;
		}
	}
  
  if (library) {
    if (library->num_curves () && !mouse_slider0.active) {
      if (keypressedd (SDLK_9)) { 
        load_curve (-1); 
        return 1;
      } else
      if (keypressedd (SDLK_0)) { 
        load_curve (1); 
        return 1;
      } else
      if (keypressed (SDLK_MINUS)) { delete_curve (); return 1;} 
    }
    if (keypressed (SDLK_EQUALS)) { add_curve (); return 1; }
  }

	if (keypressed (SDLK_i)) insert_using_menu (); 
  else if (keypressed (SDLK_v)) {
    if (CTRL) { // ctrl + v ie paste
			paste_using_menu ();
    } else { // delete vertex or tanget
			todo = REMOVE_VERTEX;
      cursor_mesg = " Click on vertex to delete. ESC to stop.";
      hittest ();
      if (pik()) {
        remove ();
        quick_nothing ();
      }
    }
  }
  else if (keypressedd (SDLK_z)) { // undo & redo
    if (SHIFT) // l_shift + z - redo
      do_redo ();
    else
      do_undo ();
  }
	else if (keypressedd (SDLK_r)) apply_plugin (uis.plugin__browser.get_cur ()); // apply current plugin
  else if (keypressed (SDLK_m)) { // mirror tangents ie move one tangent and the other mirrors it
    mirror_tangents = !mirror_tangents;
    if (mirror_tangents) cons << YELLOW << "tangents are mirrored" << eol; else cons << YELLOW<< "tangents are not mirrored" << eol;
    MENU.set_mirror_tangents (mirror_tangents);
  } else if (keypressed (SDLK_COMMA)) {
    if (!mouse_slider0.active) {
      mark_segments = !mark_segments;
      dont_call_listener (MENU.cb_mark_segments, mark_segments);
    }
	}
  else if (keypressedd (SDLK_PERIOD)) {
    if (!mouse_slider0.active) {
      draw_curve_only = !draw_curve_only;
      dont_call_listener (MENU.cb_draw_curve, draw_curve_only);
    }
  }
  else if (keypressed (SDLK_c)) {
    if (CTRL) { // l_ctrl + c - copy curve
      if (onecurve()) {
        pik = pick_cur_curve ();
        copy_curve ();
      } else {
        hittest ();
				if (pik()) copy_curve (); else if (hitlist.size()) copy_using_menu ();
      }
    } else { // carry tangents?
      carry_tangents = !carry_tangents;
      if (carry_tangents) 
        cons << YELLOW << "vertices carry their tangents" << eol; 
      else 
        cons << YELLOW << "vertices desert their tangents" << eol;
      MENU.set_vertices_carry_tangents (carry_tangents);
    }
  }
  else if (keypressed (SDLK_SLASH)) { 
    if (!mouse_slider0.active) toggle_waveform_samples_display ();
  } else if (keypressed (SDLK_l)) {
		label_vertices = !label_vertices;
  	dont_call_listener (MENU.cb_label_vertices, label_vertices);
	}
  else if (keypressed (SDLK_F8)) { // apply mouse capture
		if (mocap0()) {
			hittest ();
			todo = ASSIGN_CAPTURE;
			next_todo = NOTHING;
			assign_mouse_capture ();
		}
  } else if (keypressed (SDLK_o)) { // toggle overlay of instrument/editor
    overlay = !overlay;
		if (overlay) 
			cons << GREEN << "Overlaid the " << uis.over->name << eol;  
		else
			cons << GREEN << "Removed the " << uis.over->name << " from overlay" << eol;  
		MENU.cb_overlay.set_state (overlay, 0);
  }
  else if (keypressed (SDLK_F1)) helptext(); 

  return 1;

}

void curve_editor::apply_mocap () {
	float x, y;
	int vx, vy;
  for (int i = 0, j = macros.size(); i < j; ++i) {
    mouse_macro& m = macros[i];
		if (m.paused == 0) {
			hit_t& h = m.hit;
			if (h(1)) { 
				m.mo.get (x, y);
				move (h, x, y);
				obj2view (x, y, vx, vy);
				capturer.set (m, vx, vy);
			}
		}
  }
}

void curve_editor::calc_hit_params (hit_t& hit) {
  if (!hit()) return;
  multi_curve* crv = get_curve (hit.crv_id);
  float vx, vy, lx, ly, rx, ry;
  int id = hit.id;
  crv->get_vertex (id, vx, vy);
  crv->get_left_tangent (id, lx, ly);
  crv->get_right_tangent (id, rx, ry);
  direction (hit.left_tangent.x, hit.left_tangent.y, vx, vy, lx, ly);
  direction (hit.right_tangent.x, hit.right_tangent.y, vx, vy, rx, ry);
  hit.left_tangent_magnitude = (float) magnitude (hit.left_tangent.x, hit.left_tangent.y);
  hit.right_tangent_magnitude = (float) magnitude (hit.right_tangent.x, hit.right_tangent.y);
	const point<float>& hp = hit.get (); obj2mouse (hp.x, hp.y);
	curcrv = hit.crv_id;
  curcrvchgd = 1;
}

void curve_editor::hittest () {

	hitlist.clear ();
  for (int i = 0, j = curves; i < j; ++i) {
    curve_info& ci = curveinfo[i];
    multi_curve* crv = ci.curve;
    if (ci.visible) {
      hittest (crv, i, crv->vertices, hit_t::VERTEX);
      hittest (crv, i, crv->left_tangents, hit_t::LEFT_TANGENT);
      hittest (crv, i, crv->right_tangents, hit_t::RIGHT_TANGENT);
    }
  }

  int n = hitlist.size();
	if (n) 
    n = filter_hitlist (); 
  else {
    pik = hit_t ();
    return;
  }
  if (n == 1) {
		pik = hitlist [0];
    calc_hit_params (pik);
  } else {
    pik = hit_t ();
    if (n > 1) show_curve_picker ();
	}
}

void curve_editor::hittest (multi_curve* crv, int crv_id, const points_array& points, unsigned int what) {
	for (int i = 0, j = points.size (); i < j; ++i) {
		const point<float>& v = points[i];
		float vx, vy; obj2win (v, vx, vy);
		float d2 = magnitude2<float> (vx, vy, win.mousex, win.mousey);
		if (d2 > win.handle_radius2) ; else hitlist.push_back (hit_t (crv, crv_id, what, i));
	}
}

int curve_editor::filter_hitlist () {

	for (int i = 0, j = curves; i < j; ++i) curveinfo[i].picked = 0;
	for (vector<hit_t>::iterator i = hitlist.begin (), j = hitlist.end (); i != j;) {
		hit_t& hit = *i;
		if (todo == PICK_CURVE) {
			int& picked = curveinfo[hit.crv_id].picked;
			if (picked) { // picked already, so remove
				hitlist.erase (i);
				j = hitlist.end ();
			} else { // pick one item per curve
				picked = 1;
				++i;
			}
		} else 
		if (todo == REMOVE_VERTEX && hit.what != hit_t::VERTEX) { // have to select vertex to delete 
			i = hitlist.erase (i); 
			j = hitlist.end ();
		} else
		if ( (hit.what == hit_t::LEFT_TANGENT && hit.id == 0) || (hit.what == hit_t::RIGHT_TANGENT && hit.id == (hit.crv->last_vertex)) ) { 
			// remove 1st vertex's left tangent and last vertex's right tangent
			i = hitlist.erase (i);
			j = hitlist.end ();
		} 
		else {
			++i;
		}
	}

	return hitlist.size ();
}

void curve_editor::set_pick_from_hitlist (int i) {
  pik = hitlist [i]; 
  calc_hit_params (pik);
  cons << GREEN << "Picked " << pik << eol;
}

int curve_editor::move (hit_t& hit, float x, float y, int eval_now) {

  curve_info& ci = curveinfo [hit.crv_id];
  multi_curve* crv = ci.curve;

  int need_eval = 0;

  switch (hit.what) {

    case hit_t::VERTEX:
        need_eval = crv->set_vertex (hit.id, x, y, carry_tangents);
      break;

    case hit_t::LEFT_TANGENT:
        need_eval = crv->set_left_tangent (hit.id, x, y);
        if (mirror_tangents && need_eval) {
          float vx, vy, lvx, lvy;
          crv->get_vertex (hit.id, vx, vy);
          unit_vector (lvx, lvy, vx, vy, x, y);
          crv->set_right_tangent (hit.id, vx - hit.right_tangent_magnitude * lvx, vy - hit.right_tangent_magnitude * lvy);
        }
      break;

    case hit_t::RIGHT_TANGENT:
        need_eval = crv->set_right_tangent (hit.id, x, y);
        if (mirror_tangents && need_eval) {
          float vx, vy, rvx, rvy;
          crv->get_vertex (hit.id, vx, vy);
          unit_vector (rvx, rvy, vx, vy, x, y);
          crv->set_left_tangent (hit.id, vx - hit.left_tangent_magnitude * rvx, vy - hit.left_tangent_magnitude * rvy);
        }
      break;

  }

  if (need_eval && eval_now) {
    crv->evaluate();
    ci.lisner->edited (this, hit.crv_id);
  }

  return need_eval;

}

int curve_editor::move () {
  float sx, sy; snap (sx, sy);
  float cx, cy; win2obj (sx, sy, cx, cy);
  return move (pik, cx, cy);
}

int curve_editor::move (int) {

  float sx, sy; snap (sx, sy);

  float cx, cy; 
	win2obj (sx, sy, cx, cy);

  curve_info& ci = curveinfo [pik.crv_id];
  multi_curve* crv = ci.curve;

  float vx, vy;
  float dx, dy;

  switch (pik.what) {
    case hit_t::VERTEX:
        crv->get_vertex (pik.id, vx, vy);
        break;
    case hit_t::LEFT_TANGENT:
        crv->get_left_tangent (pik.id, vx, vy);
        break;
    case hit_t::RIGHT_TANGENT:
        crv->get_right_tangent (pik.id, vx, vy);
        break;
  }

  dx = cx - vx;
  dy = cy - vy;

  int result = 0;
  for (int i = 0, j = crv->num_vertices; i < j; ++i) {
    crv->get_vertex (i, vx, vy);
    result |= crv->set_vertex (i, vx + dx, vy + dy);
    crv->get_left_tangent (i, vx, vy);
    result |= crv->set_left_tangent (i, vx + dx, vy + dy);
    crv->get_right_tangent (i, vx, vy);
    result |= crv->set_right_tangent (i, vx + dx, vy + dy);
  }

  if (result) {
    crv->evaluate();
    ci.lisner->edited (this, pik.crv_id);
  }

  return result;

}

void curve_editor::remove () {
  curve_info& ci = curveinfo [pik.crv_id];
  multi_curve* crv = ci.curve;
	mix = *crv;
  undos.push_back (undo_t (pik.crv_id, *crv, win));
  if (crv->remove (pik.id)) {
		pomo.remove (pik);
    crv->evaluate();
    ci.lisner->edited (this, pik.crv_id);
  } else {
    cons << RED << "Cannot delete curve: " << crv->name << eol;
    undos.pop_back ();
  }
  clear_hit (pik);
}

void curve_editor::swap () {
	if (curves == 2) {

		multi_curve& crv0 = *get_curve (0);
		multi_curve& crv1 = *get_curve (1);

		multi_curve swp (crv0);

		undos.push_back (undo_t (0, crv0, win));
		string name0 (crv0.name);
		float r0 = crv0.r, g0 = crv0.g, b0 = crv0.b;
			crv0 = crv1;
		crv0.name = name0;
		crv0.set_color (r0, g0, b0);

		undos.push_back (undo_t (1, crv1, win));
		string name1 (crv1.name);
		float r1 = crv1.r, g1 = crv1.g, b1 = crv1.b;
			crv1 = swp;
		crv1.name = name1;
		crv1.set_color (r1, g1, b1);

		cons << "Swapped curve " << crv0.name << " with " << crv1.name << eol;
		curve_info &ci0 = curveinfo[0], &ci1 = curveinfo[1]; 

		pomo.validate ();
		ci0.lisner->edited (this, 0);
		ci1.lisner->edited (this, 1);


	} 
}

void curve_editor::insert () {
  curve_info& ci = curveinfo [pik.crv_id];
  multi_curve* crv = ci.curve;
	float sx, sy; snap (sx, sy);
	float cx, cy; win2obj (sx, sy, cx, cy);
	mix = *crv;
  undos.push_back (undo_t (pik.crv_id, *crv, win));
  const point<float>& obj = get_obj_chunk ();
  if (crv->insert (cx, cy, obj.x, obj.y)) {
    crv->evaluate();
    ci.lisner->edited (this, pik.crv_id);
  } else undos.pop_back ();
}

void curve_editor::replace () {
  if (curv_scratch_points.size () > 1) {
    curve_info& ci = curveinfo [pik.crv_id];
    multi_curve& crv = *ci.curve;
    int shapeform = crv.shapeform;
		mix = crv;
    undos.push_back (undo_t (pik.crv_id, crv, win));
    create_polyline (crv, curv_scratch_points);
    if (shapeform) crv.set_shapeform (1);
		pomo.validate ();
    ci.lisner->edited (this, pik.crv_id);
    cons << GREEN << "Replaced curve." << eol;
  } else cons << RED << "Aborted drawn curve!" << eol;

  clear_scratch_curve ();
}

void curve_editor::mirror (int whole_curve) {

  // mirrors vertex (or curve) about global x-axis, or local axes of bounding box
  curve_info& ci = curveinfo [pik.crv_id];
  multi_curve* crv = ci.curve;
  undos.push_back (undo_t (pik.crv_id, *crv, win));

	box<float> bbox;
	if (axis > MIRROR_Y) crv->calc_bbox (bbox);

  float xx, yy, dx, dy, _2mx = 2 * bbox.midx, _2my = 2 * bbox.midy;
  if (whole_curve) {
		if ((axis == MIRROR_Y) || (axis == MIRROR_BBY)) { 
			for (int i = 0; i < crv->num_vertices; ++i) {
				crv->get_vertex (i, xx, yy);  dx = _2mx - xx; crv->set_vertex (i, dx, yy);
				crv->get_left_tangent (i, xx, yy); dx = _2mx - xx; crv->set_left_tangent (i, dx, yy);
				crv->get_right_tangent (i, xx, yy); dx = _2mx - xx; crv->set_right_tangent (i, dx, yy);
			}
		} else {
			for (int i = 0; i < crv->num_vertices; ++i) {
				crv->get_vertex (i, xx, yy); dy = _2my - yy; crv->set_vertex (i, xx, dy);
				crv->get_left_tangent (i, xx, yy); dy = _2my - yy; crv->set_left_tangent (i, xx, dy);
				crv->get_right_tangent (i, xx, yy); dy = _2my - yy; crv->set_right_tangent (i, xx, dy);
			}
		}
		mix = *crv;
    crv->evaluate();
    ci.lisner->edited (this, pik.crv_id);
  } else {
    switch (pik.what) {
      case hit_t::VERTEX:
        crv->get_vertex (pik.id, xx, yy);
        break;
      case hit_t::LEFT_TANGENT:
        crv->get_left_tangent (pik.id, xx, yy);
        break;
      case hit_t::RIGHT_TANGENT:
        crv->get_right_tangent (pik.id, xx, yy);
        break;
    }
		mix = *crv;
		if ((axis == MIRROR_Y) || (axis == MIRROR_BBY)) {
			dx = _2mx - xx;
			move (pik, dx, yy);
		} else {
			dy = _2my - yy;
			move (pik, xx, dy);
		}
  }
}

void curve_editor::draw () {
  project ();
    basic_editor::draw ();
    draw_all ();
    if (draw_plugin_output) uis.plugin__browser.draw (this);
    if (show_scratch_curve) draw_scratch_curve ();
    if (curve_picker.visible) hilite_item (curve_picker.id);
  unproject ();
  if (overlay) ui::over->drawerlay ();
}

void curve_editor::draw_scratch_curve () {
	int spn = win_scratch_points.size ();
	if (spn == 0) return;
	int n = spn + 1;
	alloc_gl_pts (n);
  glColor3f (1, 1, 0.7f);
  int k = 0;
  for (int i = 0; i < spn; ++i) {
    const point<float>& p = win_scratch_points[i];
    gl_pts[k++]=p.x;
    gl_pts[k++]=p.y;
  }

  float sx, sy; 
	snap (sx, sy);
  gl_pts[k++]=sx;
  gl_pts[k++]=sy;

  glVertexPointer (2, GL_FLOAT, 0, gl_pts);
  glDrawArrays (GL_LINE_STRIP, 0, n);
}

void curve_editor::draw_all () {

	// draw labels
	tb.draw (hlabel_only);
	tb.clear ();

	if (mark_segments) mark_curve_segments ();

  // draw curve profile, vertices, tangents and handles
	int j = curves;
  for (int i = 0; i < j; ++i) {
    curve_info& ci = curveinfo [i];
    if (ci.visible) {
      multi_curve* crv = ci.curve;
      if (draw_curve_only) {
        glColor3f (crv->r, crv->g, crv->b);
        draw_curve (crv);
      } else {
        glColor3f (crv->rt, crv->gt, crv->bt);
        draw_tangents (crv);
        glColor3f (crv->r, crv->g, crv->b);
        draw_vertices (crv);
        draw_curve (crv);
      }
    }
	}

  // mark beat/value (gater, fm/am, octave shift)
  //
  int n = bv.size ();
  if (n) {
    float nowx, nowy;
    float x, y;
    static const int marker_size = 35;
    for (int i = 0; i < n; ++i) {
      beat2value* bvi = bv[i];
      multi_curve& crv = *bvi->crv;
      if (crv.shapeform) crv.get_xy (bvi->now, nowx, nowy);
      else {
        nowx = bvi->now;
        nowy = bvi->sol (nowx);
      }
      obj2win (nowx, nowy, x, y);
      glColor3f (crv.r, crv.g, crv.b);
      glLineWidth (2);
      mkr[0]=x-marker_size;mkr[1]=y;
      mkr[2]=x;mkr[3]=y-marker_size;
      mkr[4]=x+marker_size;mkr[5]=y;
      mkr[6]=x;mkr[7]=y+marker_size;
      glVertexPointer (2, GL_INT, 0, mkr);
      glDrawArrays (GL_LINE_LOOP, 0, 4);
      glLineWidth (1);
			
    }
  }

  // label vertices
  if (label_vertices) {
    for (int i = 0, j = curves; i < j; ++i) {
      curve_info& ci = curveinfo[i];
      multi_curve* crv = ci.curve;
      for (int m = 0, n = crv->num_vertices; m < n; ++m) {
        point<float>& vm = crv->vertices[m];
        sprintf (BUFFER, "%d", m);
        tb.add (text (BUFFER, vm.x, vm.y, vtxlbl.r, vtxlbl.g, vtxlbl.b));
      }
      tb.refresh (this);
    }
  }

  if (samples_enabled && curves) cs.draw (this, curveinfo[0].curve); 

  if (kbkb_attack_editor) {
    mkr[0]=susx;mkr[1]=susbox.bottom;
    mkr[2]=susx;mkr[3]=win.bottom;
		glEnable (GL_LINE_STIPPLE);
		glLineStipple (1, 0x00ff);
		glColor3f (0, 0.5, 1);
    glBegin (GL_LINES);
      glVertex2i (susx, susbox.bottom);
      glVertex2i (susx, win.bottom);
    glEnd ();
		/*glVertexPointer (2, GL_INT, 0, mkr);
    glDrawArrays (GL_LINES, 0, 2);*/
    glDisable (GL_LINE_STIPPLE);
    mkr[0]=susbox.left;mkr[1]=susbox.top;
    mkr[2]=susbox.midx;mkr[3]=susbox.bottom;
    mkr[4]=susbox.right;mkr[5]=susbox.top;
		glVertexPointer (2, GL_INT, 0, mkr);
    glDrawArrays (GL_LINE_LOOP, 0, 3);
  }

  draw_cursor ();

}


void curve_editor::mark_curve_segments () {

  // drop vertical lines from current curve's profile points
  glColor3f (0.2f, 0.2f, 0.2f);
	curve_info& ci = curveinfo [curcrv];
  if (ci.visible) {
    multi_curve* crv = ci.curve;
    vector<curve>& curv = crv->curv;
    int m = 0;
    for (int i = 0, j = curv.size(); i < j; ++i) m += curv[i].vpts.size ();
    int e = 2 * m;
    int n = 2 * e;
    alloc_gl_pts (n);
    for (int i = 0, j = curv.size(), k = 0; i < j; ++i) {
      vector<crvpt>& vpts = curv[i].vpts;
      for (int p = 0, q = vpts.size (); p < q; ++p) {
        float dx, dy; obj2win (vpts[p].x, vpts[p].y, dx, dy);
        gl_pts [k++]=dx;
        gl_pts [k++]=dy;
        gl_pts [k++]=dx;
        gl_pts [k++]=0;
      }
    }
    glVertexPointer (2, GL_FLOAT, 0, gl_pts);
    glDrawArrays (GL_LINES, 0, e);
  }
}

void curve_editor::draw_handle (const point<float>& p) {
  float x, y; obj2win (p, x, y);
  float handle [] = {x - win.handle_radius, y, x, y + win.handle_radius, x + win.handle_radius, y, x, y - win.handle_radius};
  glVertexPointer (2, GL_FLOAT, 0, handle);
  glDrawArrays (GL_LINE_LOOP, 0, 4);
}

void curve_editor::draw_tangent (const point<float>& p, const point<float>& t) {
  float x, y; obj2win (p, x, y);
  float tx, ty; obj2win (t, tx, ty);
  /*float pts [4] = {x, y, tx, ty};
  glVertexPointer (2, GL_FLOAT, 0, pts);
  glDrawArrays (GL_LINES, 0, 2);*/
  glBegin (GL_LINES);
    glVertex2f (x, y);
    glVertex2f (tx, ty);
  glEnd ();
}

void curve_editor::draw_curve (multi_curve* crv) {

	int r = 0, n = crv->get_total_points ();
	alloc_gl_pts (n);

	vector<curve>& curv = crv->curv;
	int j = curv.size (), k = j - 1;
	float dx, dy; 
  for (int i = 0; i < j; ++i) {
    vector<crvpt>& vpts = curv[i].vpts;
    for (int p = 0, q = vpts.size () - 1; p < q; ++p) {
      obj2win (vpts[p].x, vpts[p].y, dx, dy);
      gl_pts[r++] = dx;
      gl_pts[r++] = dy;
    }
  }

	// last point
	int l = curv[k].vpts.size () - 1;
	crvpt& pl = curv[k].vpts[l];
	obj2win (pl.x, pl.y, dx, dy);
	gl_pts[r++]=dx;
	gl_pts[r++]=dy;

	glVertexPointer (2, GL_FLOAT, 0, gl_pts);
	glDrawArrays (GL_LINE_STRIP, 0, n);

}

void curve_editor::draw_vertices (multi_curve* crv) {
  const points_array& vertices = crv->vertices;
  for (int p = 0, q = vertices.size(); p < q; ++p) {
    const point<float>& v = vertices[p];
    draw_handle (v);
  }
}

void curve_editor::draw_tangents (multi_curve* crv) {
  // draw tangents of ith curve
  const points_array& vertices = crv->vertices;
  const points_array& left_tangents = crv->left_tangents;
  const points_array& right_tangents = crv->right_tangents;
  for (int p = 0, q = vertices.size(); p < q; ++p) {
    const point<float>& v = vertices[p];
    const point<float>& lt = left_tangents[p];
    const point<float>& rt = right_tangents[p];
    draw_tangent (v, lt);
    draw_handle (lt);
    draw_tangent (v, rt);
    draw_handle (rt);
  }
}

void curve_editor::dodo (list<undo_t>& do1, list<undo_t>& do2, std::string mesg) {
  if (do1.size() > 0) {
    undo_t& last = do1.back ();
    if (last.i < curves) {
      curve_info& ci = curveinfo[last.i];
      multi_curve* crv = ci.curve;
			mix = *crv;
      do2.push_back (undo_t (last.i, *crv, win));
        float r, g, b;
        crv->get_color (r, g, b);
          *crv = last.curve;
        crv->set_color (r, g, b);
      if (MENU.show) setup_tools_menu (); else curcrvchgd = 1;
			pomo.validate ();
      ci.lisner->edited (this, last.i);
      win = last.win;
      do1.pop_back ();
    }
    hitlist.clear ();
    calc_visual_params ();
  } else cons << RED << mesg << eol;
}

void curve_editor::add (multi_curve* crv, curve_listener* lsnr) {
  if (curves < savedrots) {
    curve_info& cc = curveinfo [curves];
    cc.curve = crv;
    cc.lisner = lsnr;
  } else {
    curveinfo.push_back (curve_info (crv, lsnr));
  }
  if (++curves == 1) {
    render_curve_samples ();
		hlabel_only = crv->shapeform;
  }
  ++visibles;
}

void curve_editor::clear () {
  curveinfo.clear ();
  hitlist.clear ();
  clear_hit (pik);
	curves = 0;
}

multi_curve* curve_editor::get_curve (int i) {
  curve_info& ci = curveinfo [i];
  return ci.curve;
}

void curve_editor::enter () {
	calc_visual_params ();
  MENU.populatecurvestab (this);
  MENU.set_vertices_carry_tangents (carry_tangents);
  MENU.set_mirror_tangents (mirror_tangents);
  MENU.crvwav.hz.set_value (hz);
  MENU.crvwav.periods.set_value (nperiods);
  MENU.crvwav.time.set_value (curve_samples::nsec);
  curcrvchgd = 1;

	dont_call_listener (MENU.cb_show_waveform_samples, samples_enabled);
	dont_call_listener (MENU.cb_label_vertices, label_vertices);
	dont_call_listener (MENU.cb_draw_curve, draw_curve_only);
	dont_call_listener (MENU.cb_overlay, overlay);
	if (uis.is_widget_on_screen (&uis.plugin__browser, this)) {
		uis.plugin__browser.set_ed (this);
		uis.plugin__browser.set_fold (!draw_plugin_output);
	}
}

void curve_editor::calc_visual_params () {
	float var = view.height * 1.0 / view.width;
	float wh = win.width * var, wh2 = wh / 2.0f;
	win.set (win.left, win.midy - wh2, win.right, win.midy + wh2);
	win.calc ();
  basic_editor::calc_visual_params ();
}

void curve_editor::clear_scratch_curve () {
  win_scratch_points.clear ();
  curv_scratch_points.clear ();
  scratch_curve.clear ();
  show_scratch_curve = 0;
}

void curve_editor::attach_library (curve_library* lib) {
  library = lib;
}

void curve_editor::bg () {
  apply_mocap ();
  rotate ();
}



void curve_editor::clear_hit (hit_t& h) {
  h.clear ();
}

void curve_editor::load_curve (int dir) {
  if (!pik()) pik = pick_cur_curve (); 
  multi_curve& crv = *pik.crv;
	mix = crv;
  undos.push_back (undo_t (pik.crv_id, crv, win));
  if (dir == 1) crv = library->next (); else crv = library->prev();
  crv.set_color (mix.r, mix.g, mix.b);
	crv.name = mix.name;
  if (MENU.show) setup_tools_menu (); else curcrvchgd = 1;
	pomo.validate ();
  curveinfo[pik.crv_id].lisner->edited (this, pik.crv_id);
  cons.rollup (1);
}

void curve_editor::add_curve () {
  if (!library) return;
  if (!pik()) pik = pick_cur_curve (); 
  curve_info& ci = curveinfo [pik.crv_id];
  library->add (*ci.curve);
}

void curve_editor::replace_curve () {
  if (!library) return;
  if (!pik()) pik = pick_cur_curve (); 
  library->replace (*get_curve (pik.crv_id));
}

void curve_editor::insert_curve () {
  if (!library) return;
  if (!pik()) pik = pick_cur_curve (); 
  library->insert (*get_curve (pik.crv_id));
}

void curve_editor::paste (hit_t& hit) {
  if (hit()) {
    curve_info& ci = curveinfo [hit.crv_id];
    multi_curve* crv = ci.curve;
    if (copy.num_vertices) {
			mix = *crv;
      undos.push_back (undo_t (hit.crv_id, *crv, win));
        std::string crv_name (crv->name); 
        float cr, cg, cb;
        crv->get_color (cr, cg, cb);
          *crv = copy;
        crv->set_color (cr, cg, cb);
        crv->name = crv_name;
			pomo.validate ();
      ci.lisner->edited (this, hit.crv_id);
      cons << GREEN << "Pasted into " << crv->name << eol;
    } else 
      cons << RED << "Nothing to paste!" << eol;
    quick_nothing ();
  } else cons << RED << "Pick a curve to paste." << eol;
}

std::string curve_editor::selection () {
  std::stringstream ss;
  const char* what [] = {"invalid", "v", "lt", "rt"};
  for (int i = 0, j = hitlist.size (); i < j; ++i) {
    hit_t& h = hitlist[i];
    multi_curve* c = h.crv;
    float x, y;
    switch (h.what) {
      case hit_t::VERTEX:
        c->get_vertex (h.id, x, y);
        break;
      case hit_t::LEFT_TANGENT:
        c->get_left_tangent (h.id, x, y);
        break;
      case hit_t::RIGHT_TANGENT:
        c->get_right_tangent (h.id, x, y);
        break;
    }
    ss << '{' << c->name << ' ' << what[h.what] << ' ' << h.id << ' ' << x << ' ' << y << "} ";
  }
  return ss.str ();
}



void curve_editor::picked_using_picker (int id) {
  set_pick_from_hitlist (id);
  switch (todo) {
		case NOTHING:
			prep_move ();
			break;
    case PICK_CURVE:
      curve_picked ();
      break;
    case REMOVE_VERTEX:
      remove ();
      break;
    case FOLD_VERTEX:
      fold_tangents_of_vertex (pik);
      break;
    case UNFOLD_VERTEX:
      unfold_tangents_of_vertex (pik);
      break;
    case MIRROR_VERTEX:
      mirror ();
      break;
		case ASSIGN_CAPTURE:
			assign_mouse_capture ();
			break;
		case MODULATE_POINT:
			modulate_point (1);
			break;
  }
}

void curve_editor::remove_using_menu () {
  todo = REMOVE_VERTEX;
  cursor_mesg = " Click on vertex to delete. ESC to stop.";
}

void curve_editor::modulate_point () {
	todo = MODULATE_POINT;
	cursor_mesg = " Click on vertex or tangent to modulate. ESC to stop.";
}

void curve_editor::modulate_point (int) {
	if (pik()) {
		undos.push_back (undo_t (pik.crv_id, *pik.crv, win));
		pomo.add (pik);
	}
}

void curve_editor::insert_using_menu () {
  if (onecurve()) {
    todo = INSERT_VERTEX;
    cursor_mesg = " Click to insert vertex. ESC to stop.";
  } else {
    todo = PICK_CURVE;
    cursor_mesg = " Pick curve to insert vertex. ESC to abort!";
    next_todo = INSERT_VERTEX;
    next_cursor_mesg = " Click to insert vertex. ESC to stop.";
  }
}

void curve_editor::fold_tangents_using_menu () {
  int fold_vertex = MENU.cb_selection_only.state;
  if (fold_vertex) {
    todo = FOLD_VERTEX; 
    cursor_mesg = " Click on vertex to fold tangents. ESC to stop.";
  } else {
    if (onecurve()) {
      pik = pick_cur_curve (); 
      fold_all_tangents (pik);
      quick_nothing ();
    } else {
      todo = PICK_CURVE;
      next_todo = FOLD_ALL;
      next_cursor_mesg = "";
      cursor_mesg = " Pick curve to fold tangents. ESC to stop.";
    }
  }
}

void curve_editor::unfold_tangents_using_menu () {
  int unfold_vertex = MENU.cb_selection_only.state;
  if (unfold_vertex) {
    todo = UNFOLD_VERTEX; 
    cursor_mesg = " Click on vertex to unfold tangents. ESC to stop.";
  } else {
    if (onecurve()) {
      pik = pick_cur_curve (); 
      unfold_all_tangents (pik);
      quick_nothing ();
    } else {
      todo = PICK_CURVE;
      cursor_mesg = " Pick curve to unfold tangents. ESC to stop.";
      next_todo = UNFOLD_ALL;
      next_cursor_mesg = "";
    }
  }
}

void curve_editor::mirror_using_menu () {
  int mirror_vertex = MENU.cb_selection_only.state;
  if (mirror_vertex) {
    todo = MIRROR_VERTEX;
    cursor_mesg = " Click on vertex or tangent to flip. ESC to stop.";
  } else {
    if (onecurve()) {
      pik = pick_cur_curve (); 
      int whole_curve = 1;
      mirror (whole_curve);
      quick_nothing ();
    } else {
      todo = PICK_CURVE;
      cursor_mesg = " Pick the curve to flip. ESC to stop.";
      next_todo = MIRROR_ALL;
      next_cursor_mesg = "";
    }
  }
}

void curve_editor::copy_using_menu () {
  if (onecurve()) {
    copy = *curveinfo[curcrv].curve;
    cons << GREEN << "Copied " << copy.name << eol;
  } else {
    todo = PICK_CURVE;
    cursor_mesg = " Pick the curve to copy.  ESC to stop.";
    next_todo = COPY;
    next_cursor_mesg = "";
  }
}

void curve_editor::paste_using_menu () {
  if (onecurve()) {
		pik = pick_cur_curve ();
    paste (pik);
  } else {
    todo = PICK_CURVE;
    cursor_mesg = " Pick the curve to paste.  ESC to stop.";
    next_todo = PASTE;
    next_cursor_mesg = "";
  }
}

void curve_editor::fold_all_tangents (hit_t& hit) {
  curve_info& ci = curveinfo [hit.crv_id];
  multi_curve& crv = *ci.curve;
	mix = crv;
  undos.push_back (undo_t (hit.crv_id, crv, win));
  convert2_polyline (crv);
  ci.lisner->edited (this, hit.crv_id);
}

void curve_editor::unfold_all_tangents (hit_t& hit) {
  curve_info& ci = curveinfo [hit.crv_id];
  multi_curve& crv = *ci.curve;
	mix = crv;
  undos.push_back (undo_t (hit.crv_id, crv, win));
  const point<float>& obj = get_obj_chunk ();
  convert2_catmull_rom (crv, max (obj.x, obj.y)); // turn bezier curve into 'catmull-rom' spline; still a bezier curve
  ci.lisner->edited (this, hit.crv_id);
}

void curve_editor::fold_tangents_of_vertex (hit_t& hit) {
  if (hit()) {
		curve_info& ci = curveinfo[hit.crv_id];
    multi_curve& crv = *ci.curve;
		mix = crv;
    undos.push_back (undo_t (hit.crv_id, crv, win));
    float vx, vy; crv.get_vertex (hit.id, vx, vy);
    crv.set_left_tangent (hit.id, vx, vy);
    crv.set_right_tangent (hit.id, vx, vy);
    crv.evaluate ();
    ci.lisner->edited (this, hit.crv_id);
    quick_nothing ();
  }
}

void curve_editor::unfold_tangents_of_vertex (hit_t& hit) {
  if (hit()) {
    curve_info& ci = curveinfo [hit.crv_id];
    multi_curve& crv = *ci.curve;
		mix = crv;
    undos.push_back (undo_t (hit.crv_id, crv, win));
    const point<float>& obj = get_obj_chunk ();
    float vx, vy; crv.get_vertex (hit.id, vx, vy);
    crv.set_left_tangent (hit.id, vx - obj.x, vy);
    crv.set_right_tangent (hit.id, vx + obj.x, vy);
    crv.evaluate ();
    ci.lisner->edited (this, hit.crv_id);
    quick_nothing ();
  }
}

void curve_editor::render_curve_samples () {
  if (samples_enabled) {
    if (!pik()) pik = pick_cur_curve ();
    cs.render (pik.crv, this);
  }
}

void curve_editor::toggle_waveform_samples_display () {
  if (waved) {
    samples_enabled = !samples_enabled;
    render_curve_samples ();
    dont_call_listener (MENU.cb_show_waveform_samples, samples_enabled);
  }
}

void curve_editor::set_hz (float zh) {
  hz = zh;
  if (hz < 0) hz = SAMPLE_RATE / 100;
  cs.set (hz, cs.nperiods);
  render_curve_samples ();
}

void curve_editor::set_periods (int p) {
  cs.set (cs.hz, p);
  render_curve_samples ();
}

void curve_editor::abs_nothing () {
	stop_mouse_capture ();
	if (todo == ADD_VERTEX) {
		if (rmb) 
			replace ();
		else 
			clear_scratch_curve (); 
	}
  quick_nothing ();
}

void curve_editor::quick_nothing () {
  todo = next_todo = NOTHING;
  cursor_mesg = next_cursor_mesg = "";
  lmb_move = 0;
}

void curve_editor::copy_curve () {
  if (pik()) {
    copy = *get_curve(pik.crv_id);
    cons << GREEN << "Copied "<< copy.name << "." << eol;
    quick_nothing ();
  }
}

void curve_editor::do_load_curve (int dir) {
  if (library && library->num_curves()) load_curve (dir);
}

void curve_editor::do_redo () {
  dodo (redos, undos, "no more redos.");
}

void curve_editor::do_undo () {
  dodo (undos, redos, "no more undos.");
}

void curve_editor::do_pick_curve () {
  clear_hit (pik);
  if (onecurve()) {
    pik = pick_cur_curve ();
    cons << "Picked " << pik.crv->name << eol;
    quick_nothing ();
    return;
  } else {
    todo = PICK_CURVE;
    cursor_mesg = " Click to pick curve, ESC to abort!";
    next_todo = NOTHING;
    next_cursor_mesg = "";
  }
}

hit_t curve_editor::pick_cur_curve () {
  multi_curve* crv = curveinfo[curcrv].curve;
  curcrvchgd = 1;
  return hit_t (crv, curcrv);
}

multi_curve* curve_editor::get_picked_curve () {
  if (!pik()) {
    pik = pick_cur_curve ();
    return pik.crv;
  } else 
    return pik.crv;
}


void curve_editor::delete_curve () {
  if (library) library->del ();
}

void curve_editor::draw_replacement_curve_using_menu () {
  if (onecurve()) {
    pik = pick_cur_curve ();
    show_scratch_curve = 1;
    todo = ADD_VERTEX;
    cursor_mesg = " Click to add vertex. ESC to abort!";
  } else {
    todo = PICK_CURVE;
    cursor_mesg = " Pick curve to replace. ESC to abort!";
    next_todo = ADD_VERTEX;
    next_cursor_mesg = " Click to add vertex. ESC to abort!";
  }
}

void curve_editor::add_vertex () {
  show_scratch_curve = 1;
  float sx, sy; snap (sx, sy);
  win_scratch_points.push_back (point<float> (sx, sy));
  float curvx, curvy; win2obj (sx, sy, curvx, curvy);
  curv_scratch_points.push_back (point<float>(curvx, curvy));
  if (curv_scratch_points.size () > 1) cursor_mesg = "Click to add vertex. Right click to complete.";
}

void curve_editor::assign_mouse_capture () {
  if (pik()) {
    undos.push_back (undo_t (pik.crv_id, *get_curve(pik.crv_id), win));
    macros.push_back (mouse_macro (mocap0, pik, capturer.add ()));
    cons << GREEN << "Assigned mouse capture to " << pik << eol;
		quick_nothing ();
  }
}

void curve_editor::assign_mouse_capture_from_menu () {
  if (mocap0()) {
    todo = ASSIGN_CAPTURE;
		next_todo = NOTHING;
    cursor_mesg = " Pick vertex or tangent to assign mouse capture. ESC to abort!";
  } else cons << RED << "No mouse capture found!" << eol;
}

void curve_editor::remove_mouse_capture (state_button* sb) {
	for (vector<mouse_macro>::iterator i = macros.begin(), j = macros.end(); i != j;) {
		mouse_macro& m = *i;
		if (m.sb == sb) {
			cons << GREEN << "Removed mouse capture from " << m.hit << eol;
			i = macros.erase (i);
			j = macros.end ();
			if (macros.empty()) {
				quick_nothing ();
				return;
			}
		} else ++i;
	}
}

void curve_editor::toggle_mouse_capture (vector<state_button*>& caps) {
	int p = 0, q = 0;
	for (vector<mouse_macro>::iterator i = macros.begin(), j = macros.end(); i != j; ++i) {
		mouse_macro& mm = *i;
		for (int i = 0, ncaps = caps.size (); i < ncaps; ++i) {
			state_button* sb = caps[i];
			if (sb->state && mm.sb == sb) {
				mm.paused = !mm.paused;
				if (mm.paused) ++p; else ++q;
			}
		}
	}
	cons << GREEN << "Played " << q << " and Paused " << p << " mouse captures" << eol;
}

void curve_editor::start_mouse_capture_from_menu () {
  start_mouse_capture ();
  todo = START_CAPTURE;
  cursor_mesg = " Capturing mouse. Click or ESC to finish!";
}

void curve_editor::set_picked_curve_name (const string& name) {
  if (!pik()) pik = pick_cur_curve ();
  cons << "Curve " << pik.crv->name;  
  pik.crv->set_name (name);
  cons << " changed to " << name << eol;
}

void curve_editor::set_limit (float f) {
  if (!pik()) pik = pick_cur_curve ();
  curve_info& ci = curveinfo [pik.crv_id];
  multi_curve* crv = ci.curve;
  crv->set_limit (f);
  crv->evaluate ();
  ci.lisner->edited (this, pik.crv_id);
  cons << "Curve roughness = " << f << eol;
}

void curve_editor::set_curve_style (multi_curve* crv) {
  const char* pre [] = {" Curve is", " Waveform is"};
  int i = waved;
  int s = crv->shapeform;
  const char* awhat [] = {"classical", "a shape" };
  sprintf (BUFFER, "%s %s", pre[i], awhat[s]);
  MENU.ol_curve_style.set_text(BUFFER);
  float cx [] = {0.0f, 0.5f};
  crv->cx = cx[s];
  crv->cy = 0.0f;
}

void curve_editor::toggle_curve_style () {
  multi_curve* crv = get_curve (curcrv);
	mix = *crv;
  crv->set_shapeform (!crv->shapeform);
  set_curve_style (crv);
  render_curve_samples ();
  curveinfo[curcrv].lisner->edited (this, curcrv);
}

void curve_editor::curve_picked () {
  if (next_todo == FOLD_ALL) fold_all_tangents (pik);
  else if (next_todo == UNFOLD_ALL) unfold_all_tangents (pik);
  else if (next_todo == MIRROR_ALL) {int whole_curve = 1; mirror (whole_curve);}
  else if (next_todo == COPY) copy_curve ();
  else if (next_todo == PASTE) paste (pik);
  else {
    todo = next_todo;
    cursor_mesg = next_cursor_mesg;
    cons << GREEN << "Picked " << pik.crv->name << eol;
  }
}

void curve_editor::set_rpm (float r) {
  cons << "RPM = " << r << eol;
  curve_info& ci = curveinfo [curcrv];
  if (r != 0) {
    float rps = r / 60.0;
    float rpf = rps / FPS;
    ci.angle = rpf * TWO_PI;
    if (ci.lastrpm == 0) {
      undos.push_back (undo_t (curcrv, *ci.curve, win));
      ci.totang = 0.0f;
    }
    ci.startt = ui_clk() - TIME_PER_FRAME;
  }
  ci.lastrpm = r;
}

void curve_editor::rotate () {
  for (int i = 0; i < curves; ++i) {
    curve_info& ci = curveinfo[i];
    if (ci.lastrpm) {
      float now = ui_clk();
      float elapsed = now - ci.startt;
      if (elapsed >= TIME_PER_FRAME) {
        float mult = elapsed / TIME_PER_FRAME;
        float byangle = mult * ci.angle;
        float sum = ci.totang + byangle;
        if (ci.autoflip && sum >= ci.every.rad) {
          float finrot = ci.every.rad - ci.totang;
          ci.curve->rotate (ci.dir * finrot); 
          ci.totang = 0.0f;
          ci.dir *= -1;
          if (ci.randoflip) ci.randeg ();
        } else {
          ci.totang = sum;
          ci.curve->rotate (ci.dir * byangle); 
        }
        ci.lisner->edited (this, i);
        ci.startt = ui_clk ();
      }
    }
  }
}

void curve_editor::scale (float sx, float sy) {
  get_picked_curve ();
  undos.push_back (undo_t (pik.crv_id, *pik.crv, win));
  pik.crv->scale (sx, sy);
}

void curve_editor::hilite_item (int id) {
  hit_t& hit = hitlist[id];
  glLineWidth (2);
  point<float>& v = hit.crv->vertices [hit.id];
  point<float>& lt = hit.crv->left_tangents [hit.id];
  point<float>& rt = hit.crv->right_tangents [hit.id];
  glColor3f (1.0f, 0.6f, 1.0f);
  draw_handle (v);
  switch (hit.what) {
    case hit_t::LEFT_TANGENT:
      draw_tangent (v, lt);
      draw_handle (lt);
      break;
    case hit_t::RIGHT_TANGENT:
      draw_tangent (v, rt);
      draw_handle (rt);
      break;
  }
  glLineWidth (1);
}

void curve_editor::apply_plugin (plugin* p) {
  int r = 0;
  curve_info& ci = curveinfo [curcrv];
  multi_curve* crv = ci.curve;
	if (p->mix) mix = *crv;
  if (p->undo) {
    undos.push_back (undo_t (curcrv, *crv, win));
    r = p->apply (*crv);
    if (r == 0) {
      undos.pop_back ();
      return;
    }
  } else {
    r = p->apply (*crv);
  }

  if (r) {
    pomo.validate ();
    ci.lisner->edited (this, curcrv);
  }

  ci.totang = 0.0f;

}

int curve_editor::stop_todo () {
	if (todo != curve_editor::NOTHING) {
		abs_nothing ();
		return 1;
	}
	return 0;
}

#ifdef __SVG__
void curve_editor::write_samples (ofstream& svg) {
  float x = 0, y = 0;
  float ox, oy;
  obj2win (x, y, ox, oy);
  static float cx = cs.x[0];
  float dx, dy; 
  for (int i = 0; i < cs.n; ++i) {
    obj2win (cs.x[i], cs.y[i], dx, dy);
    svg << "<polyline fill=\"none\" stroke=\"black\" points=\"";
    float cdx = cx + dx;
    svg << cdx << ',' << dy << ' ' << cdx << ',' << oy << endl;
    svg << "\"/>" << endl;
  }
  cx += dx;
}

void curve_editor::write_curve (multi_curve* crv, ofstream& svg, float w, float h, float t, float left, float top, box<float>& bbox) {
	svg << "<polyline style=\"stroke-width:" << t << ";stroke-linecap:round;stroke-miterlimit:" << t << ";\" fill=\"none\" stroke=\"black\" points=\"";
	vector<crvpt> vpts; crv->get_profile_points (vpts);
	for (int p = 0, q = vpts.size (); p < q; ++p) {
    float dx = vpts[p].x, dy = vpts[p].y;
  	svg << left + w * (dx - bbox.left) / bbox.width << "," << top + h * (dy - bbox.bottom) / bbox.height << spc;
	}
  svg << "\"/>" << endl;
}

void curve_editor::write_svg (float h, float t, const string& fn, float left, float top) {
	multi_curve* crv = get_picked_curve ();
  box<float> bbox; crv->calc_bbox (bbox);
	string fname = user_data_dir + fn;
	ofstream svg (fname.c_str(), ios::out);
	float w = bbox.width / bbox.height * h;
  cons << "w = " << w << " h = " << h << "bbox: " << bbox << eol;
	svg << "<?xml version=\"1.0\"?>" << endl;
	svg << "<svg xmlns=\"https://www.w3.org/2000/svg\" width=\"" << w << "\" height=\"" << h << "\" viewBox=\"" << left << spc << top << spc << w << spc << h << "\">" << endl; 
		write_curve (crv, svg, w, h, t, left, top, bbox);
	svg << "</svg>" << endl;
	cons << GREEN << "written curve to: " << fname << eol;
}

#endif

#ifdef __HPGL__

void curve_editor::write_curve (multi_curve* crv, ofstream& hpgl, float scale, float penmag) {
	vector<crvpt> vpts; crv->get_profile_points (vpts);
	float minx = -1.0f, miny = -1.0f, maxx = 1.0f, maxy = 1.0f, width = maxx - minx, height = maxy - miny;
	for (int i = 0, j = vpts.size (); i < j; ++i) {
		float x = vpts[i].x, y = vpts[i].y;
		float ox = (x - minx) * scale / width;
		float oy = (y - miny) * scale / height;
		vpts[i].x = ox;
		vpts[i].y = oy;
	}
	float lx = vpts[0].x, ly = vpts[0].y;
	float m = 0;
	rnd<int> pen (1, 8);
	static const char semicolon = ';';
	for (int p = 1, q = vpts.size (); p < q; ++p) {
		float px = vpts[p].x, py = vpts[p].y;
		float dx = (px - lx), dy = (py - ly);
		m += magnitude (dx, dy);
		if (m >= penmag) { // switch pen for fun
			m -= penmag;
			hpgl << "SP" << pen () << semicolon << eol;
		}
		hpgl << "PR" << dx << ", " << dy << semicolon << eol;
		lx = px;
		ly = py;
	}
}

void curve_editor::write_hpgl (float scale, float penmag) {
	multi_curve* crv = get_picked_curve ();
	string fname = user_data_dir + crv->name + ".hpgl";
	ofstream hpgl (fname.c_str(), ios::out);
	hpgl << "IN;" << eol;
	hpgl << "PD;" << eol;
	write_curve (crv, hpgl, scale, penmag);
	hpgl << "PU;" << eol;
	cons << GREEN << "written curve to: " << fname << eol;
}

#endif

int curve_editor::esc () {
	int ret = 0;
	if (stop_todo ()) 
		ret = 1; 
	else {
		if (!rmb) {
			load_instrument ();
			ret = 1;
		} else 
			ret = 0;
	}
	if (sinemixer.harms.editing) {
		sinemixer.harms.stop_editing ();
		ret = 1;
	}
	return ret;
}

void curve_editor::drawerlay () {
  // called from plugin sin/cos/radius editors
  project ();
		if (over) uis.plugin__browser.plugins[uis.plugin__browser.cur]->draw (dynamic_cast<curve_editor*>(over));
  unproject ();
}

int curve_editor::setcrvvis (int i, int v) {

  int& vi = curveinfo[i].visible;
  vi = v;

  static const int d [] = {-1, 1};
  visibles += d[v];

  if (visibles == 0) {
    visibles = 1;
    vi = 1;
    curcrv = i;
    pik = pick_cur_curve ();
    setup_tools_menu ();
    return 0;
  } else if (visibles == 1) {
    curcrv = -1;
    for (int i = 0; i < curves; ++i) {
      curve_info& ci = curveinfo[i];
      if (ci.visible) curcrv = i;
    }
    pik = pick_cur_curve ();
    setup_tools_menu ();
  }

  return 1;

}

curve_info::curve_info  (multi_curve* c, curve_listener* l, int p) {
  init ();
  curve = c;
  lisner = l;
  picked = p;
}

curve_info::curve_info () {
  init ();
  curve = 0;
  lisner = 0;
}

void curve_info::init () {
  picked = 0;
  visible = 1;
  angle = 0;
  lastrpm = 0;
  dir = 1;
  autoflip = 0;
  randoflip = 0;
  rd.set (0.0f, 1.0f);
  totang = 0.0f;
  every0 = 180.0f;
  every = every0;
  startt = 0.0;
}

