/*
    Copyright (C) 2001 Paul Davis 

    This program 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 of the License, or
    (at your option) any later version.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    $Id: regionview.cc,v 1.148 2005/09/22 22:32:48 essej Exp $
*/

#include <cmath>
#include <algorithm>

#include <gtk--.h>

#include <gtkmmext/gtk_ui.h>

#include <ardour/playlist.h>
#include <ardour/audioregion.h>
#include <ardour/sndfilesource.h>
#include <ardour/diskstream.h>

#include "streamview.h"
#include "regionview.h"
#include "audio_time_axis.h"
#include "canvas-simplerect.h"
#include "canvas-simpleline.h"
#include "canvas-waveview.h"
#include "public_editor.h"
#include "region_editor.h"
#include "region_gain_line.h"
#include "ghostregion.h"
#include "audio_time_axis.h"
#include "utils.h"
#include "rgb_macros.h"
#include "gui_thread.h"

#include "i18n.h"

using namespace SigC;
using namespace ARDOUR;
using namespace Editing;

static const int32_t sync_mark_width = 9;

SigC::Signal1<void,AudioRegionView*> AudioRegionView::AudioRegionViewGoingAway;

AudioRegionView::AudioRegionView (GtkCanvasGroup *parent, AudioTimeAxisView &tv, 
				  AudioRegion& r, 
				  double spu, 
				  double amplitude_above_axis,
				  GdkColor& basic_color,
				  bool wfw)

	: TimeAxisViewItem (r.name(), parent, tv, spu, basic_color, r.position(), r.length(),
			    TimeAxisViewItem::Visibility (TimeAxisViewItem::ShowNameText|
				                          TimeAxisViewItem::ShowNameHighlight|
							  TimeAxisViewItem::ShowFrame)),

	  region (r)
{
	GtkCanvasPoints *shape;
	XMLNode *node;

	editor = 0;
	valid = true;
	in_destructor = false;
	_amplitude_above_axis = amplitude_above_axis;
	zero_line = 0;
	wait_for_waves = wfw;
	_height = 0;

	_flags = 0;

	if ((node = region.extra_xml ("GUI")) != 0) {
		set_flags (node);
	} else {
		_flags = WaveformVisible;
		store_flags ();
	}

	if (trackview.editor.new_regionviews_display_gain()) {
		_flags |= EnvelopeVisible;
	}

	compute_colors (basic_color);

	create_waves ();

	gtk_object_set_data (GTK_OBJECT(name_highlight), "regionview", this);
	gtk_object_set_data (GTK_OBJECT(name_text), "regionview", this);

	shape = gtk_canvas_points_new (4);

	/* an equilateral triangle */

	shape->coords[0] = -((sync_mark_width-1)/2);
	shape->coords[1] = 1;

	shape->coords[2] = (sync_mark_width - 1)/2;
	shape->coords[3] = 1;

	shape->coords[4] = 0;
	shape->coords[5] = sync_mark_width - 1;

	shape->coords[6] = -((sync_mark_width-1)/2);
	shape->coords[7] = 1;

	// cerr << "set sync mark al points, nc = " << shape->num_points << endl;
	sync_mark = gtk_canvas_item_new (GTK_CANVAS_GROUP(group),
					 gtk_canvas_polygon_get_type(),
					 "points", shape,
					 "fill_color_rgba", fill_color,
					 NULL);
	gtk_canvas_item_hide (sync_mark);
	gtk_canvas_points_unref (shape);

	
	fade_in_shape = gtk_canvas_item_new (GTK_CANVAS_GROUP(group),
					     gtk_canvas_polygon_get_type(),
					     "fill_color_rgba", fade_color,
					     NULL);
	gtk_object_set_data (GTK_OBJECT(fade_in_shape), "regionview", this);

	fade_out_shape = gtk_canvas_item_new (GTK_CANVAS_GROUP(group),
					      gtk_canvas_polygon_get_type(),
					      "fill_color_rgba", fade_color,
					      NULL);
	gtk_object_set_data (GTK_OBJECT(fade_out_shape), "regionview", this);



	{
			uint32_t r,g,b,a;
			UINT_TO_RGBA(fill_color,&r,&g,&b,&a);
	

	fade_in_handle = gtk_canvas_item_new (GTK_CANVAS_GROUP(group),
					      gtk_canvas_simplerect_get_type(),
					      "fill_color_rgba", RGBA_TO_UINT(r,g,b,0),
					      "outline_pixels", 0,
					      "y1", 2.0,
					      "y2", 7.0,
					      NULL);
	gtk_object_set_data (GTK_OBJECT(fade_in_handle), "regionview", this);
	
	fade_out_handle = gtk_canvas_item_new (GTK_CANVAS_GROUP(group),
					       gtk_canvas_simplerect_get_type(),
					       "fill_color_rgba", RGBA_TO_UINT(r,g,b,0),
					       "outline_pixels", 0,
					       "y1", 2.0,
					       "y2", 7.0,
					       NULL);
	gtk_object_set_data (GTK_OBJECT(fade_out_handle), "regionview", this);
	}

	string foo = region.name();
	foo += ':';
	foo += "gain";

	gain_line = new AudioRegionGainLine (foo, tv.session(), *this, group, region.envelope(),
					     PublicEditor::canvas_control_point_event,
					     PublicEditor::canvas_line_event);

	if (!(_flags & EnvelopeVisible)) {
		gain_line->hide ();
	} else {
		gain_line->show ();
	}

	reset_width_dependent_items ((double) region.length() / samples_per_unit);

	gain_line->reset ();

	set_height (trackview.height);

	region_muted ();
	region_sync_changed ();
	region_resized (BoundsChanged);
	set_waveview_data_src();
	region_locked ();
	envelope_active_changed ();
	fade_in_active_changed ();
	fade_out_active_changed ();

	region.StateChanged.connect (slot (*this, &AudioRegionView::region_changed));

	gtk_signal_connect (GTK_OBJECT(group), "event",
			    (GtkSignalFunc) PublicEditor::canvas_region_view_event,
			    this);
	gtk_signal_connect (GTK_OBJECT(name_highlight), "event",
			    (GtkSignalFunc) PublicEditor::canvas_region_view_name_highlight_event,
			    this);

	gtk_signal_connect (GTK_OBJECT(name_text), "event",
			    (GtkSignalFunc) PublicEditor::canvas_region_view_name_event,
			    this);

	gtk_signal_connect (GTK_OBJECT(fade_in_shape), "event",
			    (GtkSignalFunc) PublicEditor::canvas_fade_in_event,
			    this);

	gtk_signal_connect (GTK_OBJECT(fade_in_handle), "event",
			    (GtkSignalFunc) PublicEditor::canvas_fade_in_handle_event,
			    this);

	gtk_signal_connect (GTK_OBJECT(fade_out_shape), "event",
			    (GtkSignalFunc) PublicEditor::canvas_fade_out_event,
			    this);

	gtk_signal_connect (GTK_OBJECT(fade_out_handle), "event",
			    (GtkSignalFunc) PublicEditor::canvas_fade_out_handle_event,
			    this);

	set_colors ();

	/* XXX sync mark drag? */

}

AudioRegionView::~AudioRegionView ()
{
	in_destructor = true;

	AudioRegionViewGoingAway (this); /* EMIT_SIGNAL */

	for (vector<GtkCanvasWaveViewCache *>::iterator cache = wave_caches.begin(); cache != wave_caches.end() ; ++cache) {
		gtk_canvas_waveview_cache_destroy (*cache);
	}

	/* all waveviews will be destroyed when the group is destroyed */

	for (vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
		delete *g;
	}

	if (editor) {
		delete editor;
	}

	delete gain_line;
}

gint
AudioRegionView::_lock_toggle (GtkCanvasItem* item, GdkEvent* ev, void* arg)
{
	switch (ev->type) {
	case GDK_BUTTON_RELEASE:
		static_cast<AudioRegionView*>(arg)->lock_toggle ();
		return TRUE;
		break;
	default:
		break;
	} 
	return FALSE;
}

void
AudioRegionView::lock_toggle ()
{
	region.set_locked (!region.locked());
}

void
AudioRegionView::region_changed (Change what_changed)
{
	ENSURE_GUI_THREAD (bind (slot (*this, &AudioRegionView::region_changed), what_changed));

	if (what_changed & BoundsChanged) {
		region_resized (what_changed);
		region_sync_changed ();
	}
	if (what_changed & Region::MuteChanged) {
		region_muted ();
	}
	if (what_changed & Region::OpacityChanged) {
		region_opacity ();
	}
	if (what_changed & ARDOUR::NameChanged) {
		region_renamed ();
	}
	if (what_changed & Region::SyncOffsetChanged) {
		region_sync_changed ();
	}
	if (what_changed & Region::LayerChanged) {
		region_layered ();
	}
	if (what_changed & Region::LockChanged) {
		region_locked ();
	}
	if (what_changed & AudioRegion::ScaleAmplitudeChanged) {
		region_scale_amplitude_changed ();
	}
	if (what_changed & AudioRegion::FadeInChanged) {
		fade_in_changed ();
	}
	if (what_changed & AudioRegion::FadeOutChanged) {
		fade_out_changed ();
	}
	if (what_changed & AudioRegion::FadeInActiveChanged) {
		fade_in_active_changed ();
	}
	if (what_changed & AudioRegion::FadeOutActiveChanged) {
		fade_out_active_changed ();
	}
	if (what_changed & AudioRegion::EnvelopeActiveChanged) {
		envelope_active_changed ();
	}
}

void
AudioRegionView::fade_in_changed ()
{
	reset_fade_in_shape ();
}

void
AudioRegionView::fade_out_changed ()
{
	reset_fade_out_shape ();
}

void
AudioRegionView::set_fade_in_active (bool yn)
{
	region.set_fade_in_active (yn);
}

void
AudioRegionView::set_fade_out_active (bool yn)
{
	region.set_fade_out_active (yn);
}

void
AudioRegionView::fade_in_active_changed ()
{
	uint32_t r,g,b,a;
	uint32_t col;
	UINT_TO_RGBA(fade_color,&r,&g,&b,&a);

	if (region.fade_in_active()) {
		col = RGBA_TO_UINT(r,g,b,120);
		gtk_canvas_item_set (fade_in_shape, 
				     "fill_color_rgba", col,
				     "width_pixels", 0,
				     "outline_color_rgba", RGBA_TO_UINT(r,g,b,0),
				     NULL);
	} else { 
		col = RGBA_TO_UINT(r,g,b,0);
		gtk_canvas_item_set (fade_in_shape, 
				     "fill_color_rgba", col,
				     "width_pixels", 1,
				     "outline_color_rgba", RGBA_TO_UINT(r,g,b,255),
				     NULL);
	}
}

void
AudioRegionView::fade_out_active_changed ()
{
	uint32_t r,g,b,a;
	uint32_t col;
	UINT_TO_RGBA(fade_color,&r,&g,&b,&a);

	if (region.fade_out_active()) {
		col = RGBA_TO_UINT(r,g,b,120);
		gtk_canvas_item_set (fade_out_shape, 
				     "fill_color_rgba", col,
				     "width_pixels", 0,
				     "outline_color_rgba", RGBA_TO_UINT(r,g,b,0),
				     NULL);
	} else { 
		col = RGBA_TO_UINT(r,g,b,0);
		gtk_canvas_item_set (fade_out_shape, 
				     "fill_color_rgba", col,
				     "width_pixels", 1,
				     "outline_color_rgba", RGBA_TO_UINT(r,g,b,255),
				     NULL);
	}
}


void
AudioRegionView::region_scale_amplitude_changed ()
{
	ENSURE_GUI_THREAD (slot (*this, &AudioRegionView::region_scale_amplitude_changed));

	for (uint32_t n = 0; n < waves.size(); ++n) {
		// force a reload of the cache
		gtk_canvas_item_set (waves[n], "data_src", &region, NULL);
	}
}

void
AudioRegionView::region_locked ()
{
	/* name will show locked status */
	region_renamed ();
}

void
AudioRegionView::region_resized (Change what_changed)
{
	double unit_length;

	if (what_changed & ARDOUR::PositionChanged) {
		set_position (region.position(), 0);
	}

	if (what_changed & Change (StartChanged|LengthChanged)) {

		set_duration (region.length(), 0);

		unit_length = region.length() / samples_per_unit;
		
		reset_width_dependent_items (unit_length);
		
	 	for (uint32_t n = 0; n < waves.size(); ++n) {
 			gtk_canvas_item_set (waves[n], "region_start", (guint32) region.start(), NULL);
 		}
		
 		for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {

 			(*i)->set_duration (unit_length);

 			for (vector<GtkCanvasItem*>::iterator w = (*i)->waves.begin(); w != (*i)->waves.end(); ++w) {
 				gtk_canvas_item_set ((*w), "region_start", region.start(), NULL);
 			}
 		}
	}
}

void
AudioRegionView::reset_width_dependent_items (double pixel_width)
{
	TimeAxisViewItem::reset_width_dependent_items (pixel_width);
	_pixel_width = pixel_width;

	if (zero_line) {
		gtk_canvas_item_set (zero_line, "x2", pixel_width - 1.0, NULL);
	}

	if (pixel_width <= 6.0) {
		gtk_canvas_item_hide (fade_in_handle);
		gtk_canvas_item_hide (fade_out_handle);
	} else {
		if (_height < 5.0) {
			gtk_canvas_item_hide (fade_in_handle);
			gtk_canvas_item_hide (fade_out_handle);
		} else {
			gtk_canvas_item_show (fade_in_handle);
			gtk_canvas_item_show (fade_out_handle);
		}
	}

	reset_fade_shapes ();
}

void
AudioRegionView::region_layered ()
{
	AudioTimeAxisView *atv = dynamic_cast<AudioTimeAxisView*> (&get_time_axis_view());
	atv->view->region_layered (this);
}
	
void
AudioRegionView::region_muted ()
{
	set_frame_color ();
	region_renamed ();

	for (uint32_t n=0; n < waves.size(); ++n) {
		if (region.muted()) {
			gtk_canvas_item_set (waves[n], "wave_color", color_map[cMutedWaveForm], NULL);
		} else {
			gtk_canvas_item_set (waves[n], "wave_color", color_map[cWaveForm], NULL);
		}
	}
}

void
AudioRegionView::region_opacity ()
{
	set_frame_color ();
}

void
AudioRegionView::raise ()
{
	region.raise ();
}

void
AudioRegionView::raise_to_top ()
{
	region.raise_to_top ();
}

void
AudioRegionView::lower ()
{
	region.lower ();
}

void
AudioRegionView::lower_to_bottom ()
{
	region.lower_to_bottom ();
}

bool
AudioRegionView::set_position (jack_nframes_t pos, void* src, double* ignored)
{
	double delta;
	bool ret;

	if (!(ret = TimeAxisViewItem::set_position (pos, this, &delta))) {
		return false;
	}

	if (ignored) {
		*ignored = delta;
	}

	if (delta) {
		for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
			gtk_canvas_item_move ((*i)->group, delta, 0.0);
		}
	}

	return ret;
}

void
AudioRegionView::set_height (gdouble height)
{
	uint32_t wcnt = waves.size();

	TimeAxisViewItem::set_height (height - 2);
	
	_height = height;

	for (uint32_t n=0; n < wcnt; ++n) {
		gdouble ht;

		if ((height) < NAME_HIGHLIGHT_THRESH) {
			ht = ((height-2*wcnt) / (double) wcnt);
		} else {
			ht = (((height-2*wcnt) - NAME_HIGHLIGHT_SIZE) / (double) wcnt);
		}
		
		gdouble yoff = n * (ht+1);
		
		gtk_canvas_item_set (waves[n], "height", ht, NULL);
		gtk_canvas_item_set (waves[n], "y", yoff + 2, NULL);
	}

	if ((height/wcnt) < NAME_HIGHLIGHT_SIZE) {
		gain_line->hide ();
	} else {
		if (_flags & EnvelopeVisible) {
			gain_line->show ();
		}
	}

	manage_zero_line ();
	gain_line->set_height ((uint32_t) rint (height - NAME_HIGHLIGHT_SIZE));
	reset_fade_shapes ();

	gtk_canvas_item_raise_to_top (name_text) ;
}

void
AudioRegionView::manage_zero_line ()
{
	if (!zero_line) {
		return;
	}

	if (_height >= 100) {
		gdouble wave_midpoint = (_height - NAME_HIGHLIGHT_SIZE) / 2.0;
		gtk_canvas_item_set (zero_line, 
				     "y1", wave_midpoint, 
				     "y2", wave_midpoint, 
				     NULL);
		gtk_canvas_item_show (zero_line);
	} else {
		gtk_canvas_item_hide (zero_line);
	}
}

void
AudioRegionView::reset_fade_shapes ()
{
	reset_fade_in_shape ();
	reset_fade_out_shape ();
}

void
AudioRegionView::reset_fade_in_shape ()
{
	reset_fade_in_shape_width ((jack_nframes_t) region.fade_in().back()->when);
}
	
void
AudioRegionView::reset_fade_in_shape_width (jack_nframes_t width)
{
	/* smallest size for a fade is 64 frames */

	width = std::max ((jack_nframes_t) 64, width);

	GtkCanvasPoints* points;
	double pwidth = width / samples_per_unit;
	uint32_t npoints = std::min (gdk_screen_width(), (int) pwidth);
	double h; 
	
	if (_height < 5) {
		gtk_canvas_item_hide (fade_in_shape);
		gtk_canvas_item_hide (fade_in_handle);
		return;
	}

	double handle_center;
	handle_center = pwidth;
	
	if (handle_center > 7.0) {
		handle_center -= 3.0;
	} else {
		handle_center = 3.0;
	}
	
	gtk_canvas_item_set (fade_in_handle, 
			     "x1",  handle_center - 3.0,
			     "x2",  handle_center + 3.0,
			     NULL);
	
	if (pwidth < 5) {
		gtk_canvas_item_hide (fade_in_shape);
		return;
	}

	gtk_canvas_item_show (fade_in_shape);

	float curve[npoints];
	region.fade_in().get_vector (0, region.fade_in().back()->when, curve, npoints);

	points = get_canvas_points ("fade in shape", npoints+3);

	if (_height > NAME_HIGHLIGHT_THRESH) {
		h = _height - NAME_HIGHLIGHT_SIZE;
	} else {
		h = _height;
	}

	/* points *MUST* be in anti-clockwise order */

	uint32_t pi, pc;
	double xdelta = pwidth/npoints;

	for (pi = 0, pc = 0; pc < npoints; ++pc) {
		points->coords[pi++] = 1 + (pc * xdelta);
		points->coords[pi++] = 2 + (h - (curve[pc] * h));
	}
	
	/* fold back */

	points->coords[pi++] = pwidth;
	points->coords[pi++] = 2;

	points->coords[pi++] = 1;
	points->coords[pi++] = 2;

	/* connect the dots ... */

	points->coords[pi++] = points->coords[0];
	points->coords[pi] = points->coords[1];
	
	gtk_canvas_item_set (fade_in_shape, "points", points, NULL);
	gtk_canvas_points_unref (points);
}

void
AudioRegionView::reset_fade_out_shape ()
{
	reset_fade_out_shape_width ((jack_nframes_t) region.fade_out().back()->when);
}

void
AudioRegionView::reset_fade_out_shape_width (jack_nframes_t width)
{	
	/* smallest size for a fade is 64 frames */

	width = std::max ((jack_nframes_t) 64, width);

	GtkCanvasPoints* points;
	double pwidth = width / samples_per_unit;
	uint32_t npoints = std::min (gdk_screen_width(), (int) pwidth);
	double h;

	if (_height < 5) {
		gtk_canvas_item_hide (fade_out_shape);
		gtk_canvas_item_hide (fade_out_handle);
		return;
	}

	double handle_center;
	handle_center = (region.length() - width) / samples_per_unit;
	
	if (handle_center > 7.0) {
		handle_center -= 3.0;
	} else {
		handle_center = 3.0;
	}
	
	gtk_canvas_item_set (fade_out_handle, 
			     "x1",  handle_center - 3.0,
			     "x2",  handle_center + 3.0,
			     NULL);

	/* don't show shape if its too small */
	
	if (pwidth < 5) {
		gtk_canvas_item_hide (fade_out_shape);
		return;
	} 
	
	gtk_canvas_item_show (fade_out_shape);

	float curve[npoints];
	region.fade_out().get_vector (0, region.fade_out().back()->when, curve, npoints);

	if (_height > NAME_HIGHLIGHT_THRESH) {
		h = _height - NAME_HIGHLIGHT_SIZE;
	} else {
		h = _height;
	}

	/* points *MUST* be in anti-clockwise order */

	points = get_canvas_points ("fade out shape", npoints+3);

	uint32_t pi, pc;
	double xdelta = pwidth/npoints;

	for (pi = 0, pc = 0; pc < npoints; ++pc) {
		points->coords[pi++] = _pixel_width - 1 - pwidth + (pc*xdelta);
		points->coords[pi++] = 2 + (h - (curve[pc] * h));
	}
	
	/* fold back */

	points->coords[pi++] = _pixel_width;
	points->coords[pi++] = h;

	points->coords[pi++] = _pixel_width;
	points->coords[pi++] = 2;

	/* connect the dots ... */

	points->coords[pi++] = points->coords[0];
	points->coords[pi] = points->coords[1];

	gtk_canvas_item_set (fade_out_shape, "points", points, NULL);
	gtk_canvas_points_unref (points);
}

void
AudioRegionView::set_samples_per_unit (gdouble spu)
{
	TimeAxisViewItem::set_samples_per_unit (spu);

	for (uint32_t n=0; n < waves.size(); ++n) {
		gtk_canvas_item_set (waves[n], "samples_per_unit", spu, NULL);
	}

	for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
		(*i)->set_samples_per_unit (spu);
		(*i)->set_duration (region.length() / samples_per_unit);
	}

	gain_line->reset ();
	reset_fade_shapes ();
	region_sync_changed ();
}

bool
AudioRegionView::set_duration (jack_nframes_t frames, void *src)
{
	if (!TimeAxisViewItem::set_duration (frames, src)) {
		return false;
	}
	
	for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
		(*i)->set_duration (region.length() / samples_per_unit);
	}

	return true;
}

void
AudioRegionView::set_amplitude_above_axis (gdouble spp)
{
	for (uint32_t n=0; n < waves.size(); ++n) {
		gtk_canvas_item_set (waves[n], "amplitude_above_axis", spp, NULL);
	}
}

void
AudioRegionView::compute_colors (GdkColor& basic_color)
{
	TimeAxisViewItem::compute_colors (basic_color);
	uint32_t r, g, b, a;

	/* gain color computed in envelope_active_changed() */

	UINT_TO_RGBA (fill_color, &r, &g, &b, &a);
	fade_color = RGBA_TO_UINT(r,g,b,120);
}

void
AudioRegionView::set_colors ()
{
	TimeAxisViewItem::set_colors ();
	
	gain_line->set_line_color (region.envelope_active() ? color_map[cGainLine] : color_map[cGainLineInactive]);
	gtk_canvas_item_set (sync_mark, "fill_color_rgba", fill_color, NULL);

	for (uint32_t n=0; n < waves.size(); ++n) {
		if (region.muted()) {
			gtk_canvas_item_set (waves[n], "wave_color", color_map[cMutedWaveForm], NULL);
		} else {
			gtk_canvas_item_set (waves[n], "wave_color", color_map[cWaveForm], NULL);
		}
	}
}

void
AudioRegionView::set_frame_color ()
{
	if (region.opaque()) {
		fill_opacity = 180;
	} else {
		fill_opacity = 100;
	}

	TimeAxisViewItem::set_frame_color ();
}

void
AudioRegionView::show_region_editor ()
{
	if (editor == 0) {
		editor = new AudioRegionEditor (trackview.session(), region, *this);
		editor->realize ();
		trackview.editor.ensure_float (*editor);
	} 

	editor->show_all ();
	editor->get_window().raise();
}

void
AudioRegionView::hide_region_editor()
{
	if (editor) {
		editor->hide_all ();
	}
}

void
AudioRegionView::region_renamed ()
{
	string str;

	if (region.locked()) {
		str += '>';
		str += region.name();
		str += '<';
	} else {
		str = region.name();
	}

	if (region.muted()) {
		str = string ("!") + str;
	}

	set_item_name (region.name(), this);
	set_name_text (str);
}

void
AudioRegionView::region_sync_changed ()
{
	int sync_dir;
	jack_nframes_t sync_offset;

	sync_offset = region.sync_offset (sync_dir);

	/* this has to handle both a genuine change of position, a change of samples_per_unit,
	   and a change in the bounds of the region.
	 */

	if (sync_offset == 0) {

		/* no sync mark - its the start of the region */

		gtk_canvas_item_hide (sync_mark);

	} else {

		if ((sync_dir < 0) || ((sync_dir > 0) && (sync_offset > region.length()))) { 

			/* no sync mark - its out of the bounds of the region */

			gtk_canvas_item_hide (sync_mark);

		} else {

			/* lets do it */

			GtkArg args[1];
			GtkCanvasPoints* points;
			
			args[0].name = X_("points");
			
			gtk_object_getv (GTK_OBJECT(sync_mark), 1, args);
			points = static_cast<GtkCanvasPoints *> (GTK_VALUE_POINTER(args[0]));
			
			double offset = sync_offset / samples_per_unit;
			
			points->coords[0] = offset - ((sync_mark_width-1)/2);
			points->coords[1] = 1;
			
			points->coords[2] = offset + (sync_mark_width-1)/2;
			points->coords[3] = 1;
			
			points->coords[4] = offset;
			points->coords[5] = sync_mark_width - 1;
			
			points->coords[6] = offset - ((sync_mark_width-1)/2);
			points->coords[7] = 1;
			
			gtk_canvas_item_show (sync_mark);
			gtk_canvas_item_set (sync_mark, "points", points, NULL);

			gtk_canvas_points_unref (points);
		}
	}
}

void
AudioRegionView::set_waveform_visible (bool yn)
{
	if (((_flags & WaveformVisible) != yn)) {
		if (yn) {
			for (uint32_t n=0; n < waves.size(); ++n) {
				gtk_canvas_item_show (waves[n]);
			}
			_flags |= WaveformVisible;
		} else {
			for (uint32_t n=0; n < waves.size(); ++n) {
				gtk_canvas_item_hide (waves[n]);
			}
			_flags &= ~WaveformVisible;
		}
		store_flags ();
	}
}

void
AudioRegionView::temporarily_hide_envelope ()
{
	gain_line->hide ();
}

void
AudioRegionView::unhide_envelope ()
{
	if (_flags & EnvelopeVisible) {
		gain_line->show ();
	}
}

void
AudioRegionView::set_envelope_visible (bool yn)
{
	if ((_flags & EnvelopeVisible) != yn) {
		if (yn) {
			gain_line->show ();
			_flags |= EnvelopeVisible;
		} else {
			gain_line->hide ();
			_flags &= ~EnvelopeVisible;
		}
		store_flags ();
	}
}

void
AudioRegionView::create_waves ()
{
	bool create_zero_line = true;

	AudioTimeAxisView& atv (*(dynamic_cast<AudioTimeAxisView*>(&trackview))); // ick

	if (!atv.get_diskstream()) {
		return;
	}

	uint32_t nchans = atv.get_diskstream()->n_channels();
	
//	if (wait_for_waves) {
		/* in tmp_waves, set up null pointers for each channel so the vector is allocated */
		for (uint32_t n = 0; n < nchans; ++n) {
			tmp_waves.push_back (0);
		}
//	}
	
	for (uint32_t n = 0; n < nchans; ++n) {
		
		if (n >= region.n_channels()) {
			break;
		}
		
		wave_caches.push_back (gtk_canvas_waveview_cache_new ());

		if (wait_for_waves) {
			if (region.source(n).peaks_ready (bind (slot (*this, &AudioRegionView::peaks_ready_handler), n))) {
				create_one_wave (n, true);
			} else {
				create_zero_line = false;
			}
		} else {
			create_one_wave (n, true);
		}
	}

	if (create_zero_line) {
		zero_line = gtk_canvas_item_new (GTK_CANVAS_GROUP(group),
						 gtk_canvas_simpleline_get_type(),
						 "x1", (gdouble) 1.0,
						 "x2", (gdouble) (region.length() / samples_per_unit) - 1.0,
						 "color_rgba", (guint) color_map[cZeroLine],
						 NULL);
		manage_zero_line ();
	}
}

void
AudioRegionView::create_one_wave (uint32_t which, bool direct)
{
	AudioTimeAxisView& atv (*(dynamic_cast<AudioTimeAxisView*>(&trackview))); // ick
	uint32_t nchans = atv.get_diskstream()->n_channels();
	uint32_t n;
	uint32_t nwaves = std::min (nchans, region.n_channels());
	
	gdouble ht;
	if (trackview.height < NAME_HIGHLIGHT_SIZE) {
		ht = ((trackview.height) / (double) nchans);
	} else {
		ht = ((trackview.height - NAME_HIGHLIGHT_SIZE) / (double) nchans);
	}
	gdouble yoff = which * ht;

	GtkCanvasItem *wave = gtk_canvas_item_new (GTK_CANVAS_GROUP(group),
						   gtk_canvas_waveview_get_type (),
						   "data_src", (gpointer) &region,
						   "cache", wave_caches[which],
						   "cache_updater", (gboolean) true,
						   "channel", (guint32) which,
						   "length_function", (gpointer) region_length_from_c,
						   "sourcefile_length_function",(gpointer) sourcefile_length_from_c,
						   "peak_function", (gpointer) region_read_peaks_from_c,
						   "x", 0.0,
						   "y", yoff,
						   "height", (double) ht,
						   "samples_per_unit", samples_per_unit,
						   "amplitude_above_axis", _amplitude_above_axis,
						   "wave_color", (guint32) (region.muted() ? color_map[cMutedWaveForm] : color_map[cWaveForm]),
						   "region_start",(guint32) region.start(),
						   NULL);
	
	if (!(_flags & WaveformVisible)) {
		gtk_canvas_item_hide (wave);
	}

	/* note: calling this function is serialized by the lock
	   held in the peak building thread that signals that
	   peaks are ready for use *or* by the fact that it is
	   called one by one from the GUI thread.
	*/

	if (which < nchans) {
		tmp_waves[which] = (wave);
	} else {
		/* n-channel track, >n-channel source */
	}
	
	/* see if we're all ready */
	
	for (n = 0; n < nchans; ++n) {
		if (tmp_waves[n] == 0) {
			break;
		}
	}
	
	if (n == nwaves) {
		/* all waves are ready */
		tmp_waves.resize(nwaves);
		waves = tmp_waves;
		tmp_waves.clear ();
		
		if (!zero_line) {
			zero_line = gtk_canvas_item_new (GTK_CANVAS_GROUP(group),
							 gtk_canvas_simpleline_get_type(),
							 "x1", (gdouble) 1.0,
							 "x2", (gdouble) (region.length() / samples_per_unit) - 1.0,
							 "color_rgba", (guint) color_map[cZeroLine],
							 NULL);
			manage_zero_line ();
		}
	}
}

void
AudioRegionView::peaks_ready_handler (uint32_t which)
{
	Gtkmmext::UI::instance()->call_slot (bind (slot (*this, &AudioRegionView::create_one_wave), which, false));
}

void
AudioRegionView::add_gain_point_event (GtkCanvasItem *item, GdkEvent *ev)
{
	double x, y;

	/* don't create points that can't be seen */

	set_envelope_visible (true);
	
	x = ev->button.x;
	y = ev->button.y;

	gtk_canvas_item_w2i (item, &x, &y);

	jack_nframes_t fx = trackview.editor.pixel_to_frame (x);

	if (fx > region.length()) {
		return;
	}

	/* compute vertical fractional position */

	y = 1.0 - (y / (trackview.height - NAME_HIGHLIGHT_SIZE));
	
	/* map using gain line */

	gain_line->view_to_model_y (y);

	trackview.session().begin_reversible_command (_("add gain control point"));
	trackview.session().add_undo (region.envelope().get_memento());


	if (!region.envelope_active()) {
		trackview.session().add_undo( bind( slot(region, &AudioRegion::set_envelope_active), false) );
		region.set_envelope_active(true);
		trackview.session().add_redo( bind( slot(region, &AudioRegion::set_envelope_active), true) );
	}

	region.envelope().add (fx, y);
	
	trackview.session().add_redo_no_execute (region.envelope().get_memento());
	trackview.session().commit_reversible_command ();
}

void
AudioRegionView::remove_gain_point_event (GtkCanvasItem *item, GdkEvent *ev)
{
	ControlPoint *cp = reinterpret_cast<ControlPoint *> (gtk_object_get_data(GTK_OBJECT(item), "control_point"));
	region.envelope().erase (cp->model);
}

void
AudioRegionView::store_flags()
{
	XMLNode *node = new XMLNode ("GUI");

	node->add_property ("waveform-visible", (_flags & WaveformVisible) ? "yes" : "no");
	node->add_property ("envelope-visible", (_flags & EnvelopeVisible) ? "yes" : "no");

	region.add_extra_xml (*node);
}

void
AudioRegionView::set_flags (XMLNode* node)
{
	XMLProperty *prop;

	if ((prop = node->property ("waveform-visible")) != 0) {
		if (prop->value() == "yes") {
			_flags |= WaveformVisible;
		}
	}

	if ((prop = node->property ("envelope-visible")) != 0) {
		if (prop->value() == "yes") {
			_flags |= EnvelopeVisible;
		}
	}
}
	
void
AudioRegionView::set_waveform_shape (WaveformShape shape)
{
	bool yn;

	/* this slightly odd approach is to leave the door open to 
	   other "shapes" such as spectral displays, etc.
	*/

	switch (shape) {
	case Rectified:
		yn = true;
		break;

	default:
		yn = false;
		break;
	}

	if (yn != (bool) (_flags & WaveformRectified)) {
		for (vector<GtkCanvasItem *>::iterator wave = waves.begin(); wave != waves.end() ; ++wave) {
			gtk_canvas_item_set ((*wave), "rectified", (gboolean) yn, NULL);
		}

		if (zero_line) {
			if (yn) {
				gtk_canvas_item_hide (zero_line);
			} else {
				gtk_canvas_item_show (zero_line);
			}
		}

		if (yn) {
			_flags |= WaveformRectified;
		} else {
			_flags &= ~WaveformRectified;
		}
	}
}

std::string
AudioRegionView::get_item_name ()
{
	return region.name();
}

void
AudioRegionView::move (double x_delta, double y_delta)
{
	if (region.locked() || (x_delta == 0 && y_delta == 0)) {
		return;
	}

	gtk_canvas_item_move (get_canvas_group(), x_delta, y_delta);

	/* note: ghosts never leave their tracks so y_delta for them is always zero */

	for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
		gtk_canvas_item_move ((*i)->group, x_delta, 0.0);
	}
}

GhostRegion*
AudioRegionView::add_ghost (AutomationTimeAxisView& atv)
{
	AudioTimeAxisView& myatv (*(dynamic_cast<AudioTimeAxisView*>(&trackview))); // ick
	double unit_position = region.position () / samples_per_unit;
	GhostRegion* ghost = new GhostRegion (atv, unit_position);
	uint32_t nchans;
	
	nchans = myatv.get_diskstream()->n_channels();

	for (uint32_t n = 0; n < nchans; ++n) {
		
		if (n >= region.n_channels()) {
			break;
		}
		
		GtkCanvasItem *wave = gtk_canvas_item_new (GTK_CANVAS_GROUP(ghost->group),
							   gtk_canvas_waveview_get_type (),
							   "data_src", (gpointer) &region,
							   "cache", wave_caches[n],
							   "cache_updater", (gboolean) false,
							   "channel", (guint32) n,
							   "length_function", (gpointer) region_length_from_c,
							   "sourcefile_length_function",(gpointer) sourcefile_length_from_c,
							   "peak_function", (gpointer) region_read_peaks_from_c,
							   "x", 0.0,
							   "samples_per_unit", samples_per_unit,
							   "amplitude_above_axis", _amplitude_above_axis,
							   "wave_color", color_map[cGhostTrackWave],
							   "region_start", (guint32) region.start(),
							   NULL);

		
		ghost->waves.push_back(wave);
	}

	ghost->set_height ();
	ghost->set_duration (region.length() / samples_per_unit);
	ghosts.push_back (ghost);

	ghost->GoingAway.connect (slot (*this, &AudioRegionView::remove_ghost));

	return ghost;
}

void
AudioRegionView::remove_ghost (GhostRegion* ghost)
{
	if (in_destructor) {
		return;
	}

	for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
		if (*i == ghost) {
			ghosts.erase (i);
			break;
		}
	}
}

uint32_t
AudioRegionView::get_fill_color ()
{
	return fill_color;
}

void
AudioRegionView::entered ()
{
	if (_flags & EnvelopeVisible) {
		gain_line->show_all_control_points ();
	}

	uint32_t r,g,b,a;
	UINT_TO_RGBA(fade_color,&r,&g,&b,&a);
	a=255;
	
	gtk_canvas_item_set (fade_in_handle,  "fill_color_rgba", RGBA_TO_UINT(r,g,b,a), NULL);
	gtk_canvas_item_set (fade_out_handle, "fill_color_rgba", RGBA_TO_UINT(r,g,b,a), NULL);
}

void
AudioRegionView::exited ()
{
	gain_line->hide_all_but_selected_control_points ();
	
	uint32_t r,g,b,a;
	UINT_TO_RGBA(fade_color,&r,&g,&b,&a);
	a=0;
	
	gtk_canvas_item_set (fade_in_handle,  "fill_color_rgba", RGBA_TO_UINT(r,g,b,a), NULL);
	gtk_canvas_item_set (fade_out_handle, "fill_color_rgba", RGBA_TO_UINT(r,g,b,a), NULL);
}

void
AudioRegionView::envelope_active_changed ()
{
	gain_line->set_line_color (region.envelope_active() ? color_map[cGainLine] : color_map[cGainLineInactive]);
}

void
AudioRegionView::set_waveview_data_src()
{

	double unit_length= region.length() / samples_per_unit;

	for (uint32_t n = 0; n < waves.size(); ++n) {
		// TODO: something else to let it know the channel
		gtk_canvas_item_set (waves[n], "data_src", &region, NULL);
	}
	
	for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
		
		(*i)->set_duration (unit_length);
		
		for (vector<GtkCanvasItem*>::iterator w = (*i)->waves.begin(); w != (*i)->waves.end(); ++w) {
			gtk_canvas_item_set ((*w), "data_src", &region, NULL);
		}
	}

}


