/*
    Copyright (C) 2000 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: editor.cc,v 1.357 2006/02/16 21:49:14 sampo Exp $
*/

#include <unistd.h>
#include <cstdlib>
#include <cmath>
#include <string>
#include <algorithm>

#include <sigc++/bind.h>

#include <gtk-canvas.h>
#include <pbd/error.h>

#include <gtkmmext/gtk_ui.h>
#include <gtkmmext/tearoff.h>
#include <gtkmmext/utils.h>

#include <ardour/audio_track.h>
#include <ardour/diskstream.h>
#include <ardour/plugin_manager.h>
#include <ardour/location.h>
#include <ardour/audioplaylist.h>
#include <ardour/audioregion.h>
#include <ardour/session.h>
#include <ardour/session_route.h>
#include <ardour/tempo.h>
#include <ardour/utils.h>

#include "ardour_ui.h"
#include "canvas-ruler.h"
#include "canvas-simpleline.h"
#include "canvas-simplerect.h"
#include "canvas-waveview.h"
#include "check_mark.h"
#include "editor.h"
#include "grouped_buttons.h"
#include "keyboard.h"
#include "library_ui.h"
#include "marker.h"
#include "playlist_selector.h"
#include "regionview.h"
#include "rgb_macros.h"
#include "selection.h"
#include "streamview.h"
#include "time_axis_view.h"
#include "utils.h"
#include "crossfade_view.h"
#include "editing.h"
#include "public_editor.h"
#include "crossfade_edit.h"
#include "extra_bind.h"
#include "audio_time_axis.h"
#include "gui_thread.h"

#include "i18n.h"

/* <CMT Additions> */
#include "imageframe_socket_handler.h"
/* </CMT Additions> */

using namespace std;
using namespace SigC;
using namespace ARDOUR;
using namespace Gtk;
using namespace Gtkmmext;
using namespace Editing;

/* XXX this is a hack. it ought to be the maximum value of an jack_nframes_t */

const double max_canvas_coordinate = 100000000.0;
const double Editor::timebar_height = 15.0;

#include "editor_xpms"

static const gchar *route_list_titles[] = {
	N_("Tracks"),
	0
};

static const gchar *edit_group_list_titles[] = {
	"foo", "bar", 0
};

static const gchar *region_list_display_titles[] = {
	N_("Regions/name"), 
	0
};

static const gchar *named_selection_display_titles[] = {
	N_("Chunks"), 
	0
};

static const int32_t slide_index = 0;
static const int32_t splice_index = 1;

static const gchar *edit_mode_strings[] = {
	N_("Slide"),
	N_("Splice"),
	0
};

static const gchar *snap_type_strings[] = {
	N_("None"),
	N_("CD Frames"),
	N_("SMPTE Frames"),
	N_("SMPTE Seconds"),
	N_("SMPTE Minutes"),
	N_("Seconds"),
	N_("Minutes"),
	N_("Beats/32"),
	N_("Beats/16"),
	N_("Beats/8"),
	N_("Beats/4"),
	N_("Beats/3"),
	N_("Beats"),
	N_("Bars"),
	N_("Marks"),
	N_("Edit Cursor"),
	N_("Region starts"),
	N_("Region ends"),
	N_("Region syncs"),
	N_("Region bounds"),
	0
};

static const gchar *snap_mode_strings[] = {
	N_("Normal"),
	N_("Magnetic"),
	0
};

static const gchar *zoom_focus_strings[] = {
	N_("Left"),
	N_("Right"),
	N_("Center"),
	N_("Playhead"),
	N_("Edit Cursor"),
	0
};

/* Soundfile  drag-n-drop */

enum {
  TARGET_STRING,
  TARGET_ROOTWIN,
  TARGET_URL
};

static GtkTargetEntry target_table[] = {
  { "STRING",     0, TARGET_STRING },
  { "text/plain", 0, TARGET_STRING },
  { "text/uri-list", 0, TARGET_URL },
  { "application/x-rootwin-drop", 0, TARGET_ROOTWIN }
};

static guint n_targets = sizeof(target_table) / sizeof(target_table[0]);

GdkCursor* Editor::cross_hair_cursor = 0;
GdkCursor* Editor::selector_cursor = 0;
GdkCursor* Editor::trimmer_cursor = 0;
GdkCursor* Editor::grabber_cursor = 0;
GdkCursor* Editor::zoom_cursor = 0;
GdkCursor* Editor::time_fx_cursor = 0;
GdkCursor* Editor::fader_cursor = 0;
GdkCursor* Editor::speaker_cursor = 0;
GdkCursor* Editor::null_cursor = 0;
GdkCursor* Editor::wait_cursor = 0;
GdkCursor* Editor::timebar_cursor = 0;

GdkPixmap *Editor::check_pixmap = 0;
GdkBitmap *Editor::check_mask = 0;
GdkPixmap *Editor::empty_pixmap = 0;
GdkBitmap *Editor::empty_mask = 0;

extern gint route_list_compare_func (GtkCList*,gconstpointer,gconstpointer);

Editor::Editor (AudioEngine& eng) 
	: engine (eng),

	  /* time display buttons */

	  minsec_label (_("Mins:Secs")),
	  bbt_label (_("Bars:Beats")),
	  smpte_label (_("SMPTE")),
	  frame_label (_("Frames")),
	  tempo_label (_("Tempo")),
	  meter_label (_("Meter")),
	  mark_label (_("Location Markers")),
	  range_mark_label (_("Range Markers")),
	  transport_mark_label (_("Loop/Punch Ranges")),

	  edit_packer (3, 3, false),
	  edit_hscroll_left_arrow (GTK_ARROW_LEFT, GTK_SHADOW_OUT),
	  edit_hscroll_right_arrow (GTK_ARROW_RIGHT, GTK_SHADOW_OUT),

	  region_list_display (internationalize (region_list_display_titles)),

	  named_selection_display (internationalize (named_selection_display_titles)),
	  
	  /* tool bar related */

	  selection_start_clock (X_("SelectionStartClock"), true),
	  selection_end_clock (X_("SelectionEndClock"), true),
	  edit_cursor_clock (X_("EditCursorClock"), true),
	  zoom_range_clock (X_("ZoomRangeClock"), true, true),
	  
	  toolbar_selection_clock_table (2,3),
	  
	  mouse_mode_button_table (2, 3),

	  mouse_select_button (_("range")),
	  mouse_move_button (_("object")),
	  mouse_gain_button (_("gain")),
	  mouse_zoom_button (_("zoom")),
	  mouse_timefx_button (_("timefx")),
	  mouse_audition_button (_("listen")),

	  automation_mode_button (_("mode")),
	  global_automation_button (_("automation")),

	  edit_mode_label (_("Edit Mode")),
	  snap_type_label (_("Snap To")),
	  snap_mode_label(_("Snap Mode")),
	  zoom_focus_label (_("Zoom Focus")),

	  route_list (internationalize (route_list_titles)),
	  edit_group_list (internationalize (edit_group_list_titles)),
	  
	  /* <CMT Additions> */
	  image_socket_listener(0),
	  /* </CMT Additions> */

	  /* nudge */

	  nudge_label (_("Nudge")),
	  nudge_clock (X_("NudgeClock"), true, true)

{
	constructed = false;

	/* we are a singleton */

	PublicEditor::_instance = this;

	init_colormap ();

	check_pixmap = gdk_pixmap_colormap_create_from_xpm_d (NULL, 
							      gtk_widget_get_colormap (GTK_WIDGET(edit_group_list.gtkobj())),
							      &check_mask, NULL, (gchar **) check_xpm);
	empty_pixmap = gdk_pixmap_colormap_create_from_xpm_d (NULL, 
							      gtk_widget_get_colormap (GTK_WIDGET(edit_group_list.gtkobj())),
							      &empty_mask, NULL, (gchar **) empty_xpm);

	session = 0;

	selection = new Selection;
	cut_buffer = new Selection;

	selection->TimeChanged.connect (slot (*this, &Editor::time_selection_changed));
	selection->TracksChanged.connect (slot (*this, &Editor::track_selection_changed));
	selection->RegionsChanged.connect (slot (*this, &Editor::region_selection_changed));
	selection->PointsChanged.connect (slot (*this, &Editor::point_selection_changed));

	clicked_regionview = 0;
	clicked_trackview = 0;
	clicked_audio_trackview = 0;
	clicked_crossfadeview = 0;
	clicked_control_point = 0;
	latest_regionview = 0;
	region_list_display_drag_region = 0;
	last_update_frame = 0;
	drag_info.item = 0;
	last_audition_region = 0;
	region_list_button_region = 0;
	current_mixer_strip = 0;
	current_bbt_points = 0;

	snap_type = SnapToFrame;
	set_snap_to (snap_type);
	snap_mode = SnapNormal;
	set_snap_mode (snap_mode);
	snap_threshold = 5.0;
	bbt_beat_subdivision = 4;
	canvas_width = 0;
	canvas_height = 0;
	autoscroll_timeout_tag = -1;
	interthread_progress_window = 0;
	current_interthread_info = 0;
	_show_measures = true;
	_show_waveforms = true;
	_show_waveforms_recording = true;
	first_action_message = 0;
	export_dialog = 0;
	show_gain_after_trim = false;
	no_zoom_repos_update = false;
	need_wave_cursor = 0;
	ignore_route_list_reorder = false;
	verbose_cursor_on = true;
	route_removal = false;
	track_spacing = 0;
	show_automatic_regions_in_region_list = true;
	have_pending_keyboard_selection = false;
	_follow_playhead = true;
	_xfade_visibility = true;
	editor_ruler_menu = 0;
	no_ruler_shown_update = false;
	edit_group_list_menu = 0;
	route_list_menu = 0;
	region_list_menu = 0;
	marker_menu = 0;
	marker_menu_item = 0;
	tm_marker_menu = 0;
	transport_marker_menu = 0;
	new_transport_marker_menu = 0;
	editor_mixer_strip_width = Wide;
	repos_zoom_queued = false;
	import_audio_item = 0;
	embed_audio_item = 0;
	region_edit_menu_split_item = 0;
	temp_location = 0;
	region_edit_menu_split_multichannel_item = 0;
	edit_hscroll_dragging = false;
	leftmost_frame = 0;
	ignore_mouse_mode_toggle = false;
	current_stepping_trackview = 0;
	entered_track = 0;
	entered_regionview = 0;
	clear_entered_track = false;
	_new_regionviews_show_envelope = false;
	current_timestretch = 0;

	edit_cursor = 0;
	playhead_cursor = 0;

	location_marker_color = color_map[cLocationMarker];
	location_range_color = color_map[cLocationRange];
	location_cd_marker_color = color_map[cLocationCDMarker];
	location_loop_color = color_map[cLocationLoop];
	location_punch_color = color_map[cLocationPunch];

	range_marker_drag_rect = 0;
	marker_drag_line = 0;
	
	mouse_mode = MouseZoom; /* force change in next call */
	set_mouse_mode (MouseObject, true);

	frames_per_unit = 2048; /* too early to use set_frames_per_unit */
	zoom_focus = ZoomFocusLeft;
 	zoom_range_clock.ValueChanged.connect (slot (*this, &Editor::zoom_adjustment_changed));

	initialize_rulers ();
	initialize_canvas ();

	track_canvas_scroller.add (*track_canvas);
	track_canvas_scroller.set_policy (GTK_POLICY_NEVER, GTK_POLICY_NEVER);
	track_canvas_scroller.set_name ("TrackCanvasScroller");

	track_canvas_scroller.get_vadjustment()->value_changed.connect (slot (*this, &Editor::tie_vertical_scrolling));
	track_canvas_scroller.get_vadjustment()->set_step_increment (10.0);

	track_canvas_scroller.get_hadjustment()->set_lower (0.0);
	track_canvas_scroller.get_hadjustment()->set_upper (1200.0);
	track_canvas_scroller.get_hadjustment()->set_step_increment (20.0);
	track_canvas_scroller.get_hadjustment()->value_changed.connect (slot (*this, &Editor::canvas_horizontally_scrolled));
	
	edit_vscrollbar.set_adjustment(track_canvas_scroller.get_vadjustment());
	edit_hscrollbar.set_adjustment(track_canvas_scroller.get_hadjustment());

 	edit_hscrollbar.button_press_event.connect (slot (*this, &Editor::hscroll_slider_button_press));
 	edit_hscrollbar.button_release_event.connect (slot (*this, &Editor::hscroll_slider_button_release));
 	edit_hscrollbar.size_allocate.connect (slot (*this, &Editor::hscroll_slider_allocate));
	
	time_canvas_scroller.add (*time_canvas);
	time_canvas_scroller.set_policy (GTK_POLICY_NEVER, GTK_POLICY_NEVER);
	time_canvas_scroller.set_hadjustment (track_canvas_scroller.get_hadjustment());
	time_canvas_scroller.set_name ("TimeCanvasScroller");

	edit_controls_vbox.set_spacing (track_spacing);
	edit_controls_hbox.pack_start (edit_controls_vbox, true, true);
	edit_controls_scroller.add_with_viewport (edit_controls_hbox);
	edit_controls_scroller.set_name ("EditControlsBase");
	edit_controls_scroller.set_policy (GTK_POLICY_NEVER, GTK_POLICY_NEVER);

	Viewport* viewport = static_cast<Viewport*> (edit_controls_scroller.get_child());

	viewport->set_shadow_type (GTK_SHADOW_NONE);
	viewport->set_name ("EditControlsBase");
	viewport->add_events (GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK|GDK_ENTER_NOTIFY_MASK|GDK_LEAVE_NOTIFY_MASK);
	viewport->button_release_event.connect (slot (*this, &Editor::edit_controls_button_release));

	build_cursors ();
	setup_toolbar ();

	XMLNode* node = ARDOUR_UI::instance()->editor_settings();
	set_state (*node);

 	edit_cursor_clock.ValueChanged.connect (slot (*this, &Editor::edit_cursor_clock_changed));
	
	time_canvas_vbox.pack_start (*minsec_ruler, false, false);
	time_canvas_vbox.pack_start (*smpte_ruler, false, false);
	time_canvas_vbox.pack_start (*frames_ruler, false, false);
	time_canvas_vbox.pack_start (*bbt_ruler, false, false);
	time_canvas_vbox.pack_start (time_canvas_scroller, true, true);
	time_canvas_vbox.set_usize (-1, (int)(timebar_height * visible_timebars));

	bbt_label.set_name ("EditorTimeButton");
	bbt_label.set_usize (-1, (int)timebar_height);
	bbt_label.set_alignment (1.0, 0.5);
	bbt_label.set_padding (5,0);
	minsec_label.set_name ("EditorTimeButton");
	minsec_label.set_usize (-1, (int)timebar_height);
	minsec_label.set_alignment (1.0, 0.5);
	minsec_label.set_padding (5,0);
	smpte_label.set_name ("EditorTimeButton");
	smpte_label.set_usize (-1, (int)timebar_height);
	smpte_label.set_alignment (1.0, 0.5);
	smpte_label.set_padding (5,0);
	frame_label.set_name ("EditorTimeButton");
	frame_label.set_usize (-1, (int)timebar_height);
	frame_label.set_alignment (1.0, 0.5);
	frame_label.set_padding (5,0);
	tempo_label.set_name ("EditorTimeButton");
	tempo_label.set_usize (-1, (int)timebar_height);
	tempo_label.set_alignment (1.0, 0.5);
	tempo_label.set_padding (5,0);
	meter_label.set_name ("EditorTimeButton");
	meter_label.set_usize (-1, (int)timebar_height);
	meter_label.set_alignment (1.0, 0.5);
	meter_label.set_padding (5,0);
	mark_label.set_name ("EditorTimeButton");
	mark_label.set_usize (-1, (int)timebar_height);
	mark_label.set_alignment (1.0, 0.5);
	mark_label.set_padding (5,0);
	range_mark_label.set_name ("EditorTimeButton");
	range_mark_label.set_usize (-1, (int)timebar_height);
	range_mark_label.set_alignment (1.0, 0.5);
	range_mark_label.set_padding (5,0);
	transport_mark_label.set_name ("EditorTimeButton");
	transport_mark_label.set_usize (-1, (int)timebar_height);
	transport_mark_label.set_alignment (1.0, 0.5);
	transport_mark_label.set_padding (5,0);
	
	time_button_vbox.pack_start (minsec_label, false, false);
	time_button_vbox.pack_start (smpte_label, false, false);
	time_button_vbox.pack_start (frame_label, false, false);
	time_button_vbox.pack_start (bbt_label, false, false);
	time_button_vbox.pack_start (meter_label, false, false);
	time_button_vbox.pack_start (tempo_label, false, false);
	time_button_vbox.pack_start (mark_label, false, false);

	time_button_event_box.add (time_button_vbox);
	
	time_button_event_box.set_events (GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK);
	time_button_event_box.set_name ("TimebarLabelBase");
	time_button_event_box.button_release_event.connect (slot (*this, &Editor::ruler_label_button_release));

	/* these enable us to have a dedicated window (for cursor setting, etc.) 
	   for the canvas areas.
	*/

	track_canvas_event_box.add (track_canvas_scroller);

	time_canvas_event_box.add (time_canvas_vbox);
	time_canvas_event_box.set_events (GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK|GDK_POINTER_MOTION_MASK);

	
	edit_packer.set_col_spacings (0);
	edit_packer.set_row_spacings (0);
	edit_packer.set_homogeneous (false);
	edit_packer.set_name ("EditorWindow");

// 	edit_packer.attach (edit_hscroll_left_arrow_event,  0, 1, 0, 1,    GTK_FILL,  0, 0, 0);
// 	edit_packer.attach (edit_hscroll_slider,            1, 2, 0, 1,    GTK_FILL|GTK_EXPAND,  0, 0, 0);
// 	edit_packer.attach (edit_hscroll_right_arrow_event, 2, 3, 0, 1,    GTK_FILL,  0, 0, 0);
 	edit_packer.attach (edit_hscrollbar,            1, 2, 0, 1,    GTK_FILL|GTK_EXPAND,  0, 0, 0);

	edit_packer.attach (time_button_event_box,               0, 1, 1, 2,    GTK_FILL,            0, 0, 0);
	edit_packer.attach (time_canvas_event_box,          1, 2, 1, 2,    GTK_FILL|GTK_EXPAND, 0, 0, 0);

	edit_packer.attach (edit_controls_scroller,         0, 1, 2, 3,    GTK_FILL,                   GTK_FILL|GTK_EXPAND, 0, 0);
	edit_packer.attach (track_canvas_event_box,         1, 2, 2, 3,    GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 0);
	edit_packer.attach (edit_vscrollbar,                2, 3, 2, 3,    0,                   GTK_FILL|GTK_EXPAND, 0, 0);

	edit_frame.set_name ("BaseFrame");
	edit_frame.set_shadow_type (GTK_SHADOW_IN);
	edit_frame.add (edit_packer);

	zoom_in_button.set_name ("EditorTimeButton");
	zoom_out_button.set_name ("EditorTimeButton");
	ARDOUR_UI::instance()->tooltips().set_tip (zoom_in_button, _("Zoom in"));
	ARDOUR_UI::instance()->tooltips().set_tip (zoom_out_button, _("Zoom out"));

//	zoom_onetoone_button.set_name ("EditorTimeButton");
	zoom_out_full_button.set_name ("EditorTimeButton");
//	ARDOUR_UI::instance()->tooltips().set_tip (zoom_onetoone_button, _("Zoom in 1:1"));
	ARDOUR_UI::instance()->tooltips().set_tip (zoom_out_full_button, _("Zoom to session"));

	zoom_in_button.add (*(manage (new Gtk::Pixmap (zoom_in_button_xpm))));
	zoom_out_button.add (*(manage (new Gtk::Pixmap (zoom_out_button_xpm))));
	zoom_out_full_button.add (*(manage (new Gtk::Pixmap (zoom_out_full_button_xpm))));
//	zoom_onetoone_button.add (*(manage (new Gtk::Pixmap (zoom_onetoone_button_xpm))));

	
	zoom_in_button.clicked.connect (bind (slot (*this, &Editor::temporal_zoom_step), false));
	zoom_out_button.clicked.connect (bind (slot (*this, &Editor::temporal_zoom_step), true));
	zoom_out_full_button.clicked.connect (slot (*this, &Editor::temporal_zoom_session));
//	zoom_onetoone_button.clicked.connect (bind (slot (*this, &Editor::temporal_zoom), 1.0));
	
	zoom_indicator_box.pack_start (zoom_out_button, false, false);
	zoom_indicator_box.pack_start (zoom_in_button, false, false);
 	zoom_indicator_box.pack_start (zoom_range_clock, false, false);	
//	zoom_indicator_box.pack_start (zoom_onetoone_button, false, false);
	zoom_indicator_box.pack_start (zoom_out_full_button, false, false);
	
	zoom_indicator_label.set_text (_("Zoom Span"));
	zoom_indicator_label.set_name ("ToolBarLabel");


	zoom_indicator_vbox.set_spacing (3);
	zoom_indicator_vbox.set_border_width (3);
	zoom_indicator_vbox.pack_start (zoom_indicator_label, false, false);
	zoom_indicator_vbox.pack_start (zoom_indicator_box, false, false);


	bottom_hbox.set_border_width (3);
	bottom_hbox.set_spacing (3);

	route_list.set_name ("TrackListDisplay");
	route_list.set_usize (75,-1);
	route_list.column_titles_active();
	route_list.set_compare_func (route_list_compare_func);
	route_list.set_shadow_type (GTK_SHADOW_IN);
	route_list.set_selection_mode (GTK_SELECTION_MULTIPLE);
	route_list.set_reorderable (true);
	edit_group_list.set_usize (75, -1);

	route_list_scroller.add (route_list);
	route_list_scroller.set_policy (GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);

	route_list.select_row.connect (slot (*this, &Editor::route_list_selected));
	route_list.unselect_row.connect (slot (*this, &Editor::route_list_unselected));
	route_list.row_move.connect (slot (*this, &Editor::queue_route_list_reordered));
	route_list.click_column.connect (slot (*this, &Editor::route_list_column_click));

	edit_group_list_button_label.set_text (_("Edit Groups"));
	edit_group_list_button_label.set_name ("EditGroupTitleButton");
	edit_group_list_button.add (edit_group_list_button_label);
	edit_group_list_button.set_name ("EditGroupTitleButton");

	edit_group_list.column_titles_hide();
	edit_group_list.set_name ("MixerGroupList");
	edit_group_list.set_shadow_type (GTK_SHADOW_IN);
	edit_group_list.set_selection_mode (GTK_SELECTION_MULTIPLE);
	edit_group_list.set_reorderable (false);
	edit_group_list.set_usize (75, -1);
	edit_group_list.set_column_auto_resize (0, true);
	edit_group_list.columns_autosize ();

	edit_group_list_scroller.add (edit_group_list);
	edit_group_list_scroller.set_policy (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	edit_group_list_button.clicked.connect (slot (*this, &Editor::edit_group_list_button_clicked));
	edit_group_list.button_press_event.connect (slot (*this, &Editor::edit_group_list_button_press_event));
	edit_group_list.select_row.connect (slot (*this, &Editor::edit_group_selected));
	edit_group_list.unselect_row.connect (slot (*this, &Editor::edit_group_unselected));

	list<string> stupid_list;

	stupid_list.push_back ("*");
	stupid_list.push_back (_("-all-"));

	edit_group_list.rows().push_back (stupid_list);
	edit_group_list.rows().back().set_data (0);
	edit_group_list.rows().back().select();
	
	edit_group_vbox.pack_start (edit_group_list_button, false, false);
	edit_group_vbox.pack_start (edit_group_list_scroller, true, true);
	
	route_list_frame.set_name ("BaseFrame");
	route_list_frame.set_shadow_type (GTK_SHADOW_IN);
	route_list_frame.add (route_list_scroller);

	edit_group_list_frame.set_name ("BaseFrame");
	edit_group_list_frame.set_shadow_type (GTK_SHADOW_IN);
	edit_group_list_frame.add (edit_group_vbox);

	route_group_vpane.add1 (route_list_frame);
	route_group_vpane.add2 (edit_group_list_frame);

	list_vpacker.pack_start (route_group_vpane, true, true);

	region_list_hidden_node = region_list_display.rows().end();

	region_list_display.add_events (GDK_ENTER_NOTIFY_MASK|GDK_LEAVE_NOTIFY_MASK|GDK_POINTER_MOTION_MASK);

	region_list_display.drag_dest_set (GTK_DEST_DEFAULT_ALL,
					   target_table, n_targets - 1,
					   GdkDragAction (GDK_ACTION_COPY|GDK_ACTION_MOVE));
	region_list_display.drag_data_received.connect (slot (*this, &Editor::region_list_display_drag_data_received));

	region_list_scroller.add (region_list_display);
	region_list_scroller.set_policy (GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);

	region_list_display.set_name ("RegionListDisplay");
	region_list_display.set_usize (100, -1);
	region_list_display.column_titles_active ();
	region_list_display.set_selection_mode (GTK_SELECTION_SINGLE);

	region_list_display.set_data ("editor", this);
	region_list_display.set_compare_func (_region_list_sorter);
	region_list_sort_type = ByName;
	reset_region_list_sort_type (region_list_sort_type);

	region_list_display.set_flags (GTK_CAN_FOCUS);

	region_list_display.key_press_event.connect (slot (*this, &Editor::region_list_display_key_press));
	region_list_display.key_release_event.connect (slot (*this, &Editor::region_list_display_key_release));
	region_list_display.button_press_event.connect (slot (*this, &Editor::region_list_display_button_press));
	region_list_display.button_release_event.connect (slot (*this, &Editor::region_list_display_button_release));
	region_list_display.motion_notify_event.connect (slot (*this, &Editor::region_list_display_motion));
	region_list_display.enter_notify_event.connect (slot (*this, &Editor::region_list_display_enter_notify));
	region_list_display.leave_notify_event.connect (slot (*this, &Editor::region_list_display_leave_notify));
	region_list_display.select_row.connect (slot (*this, &Editor::region_list_display_selected));
	region_list_display.unselect_row.connect (slot (*this, &Editor::region_list_display_unselected));
	region_list_display.click_column.connect (slot (*this, &Editor::region_list_column_click));
	
	named_selection_scroller.add (named_selection_display);
	named_selection_scroller.set_policy (GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);

	named_selection_display.set_name ("RegionListDisplay");
	named_selection_display.set_usize (100, -1);
	named_selection_display.column_titles_active ();
	named_selection_display.set_selection_mode (GTK_SELECTION_SINGLE);

	named_selection_display.button_press_event.connect (slot (*this, &Editor::named_selection_display_button_press));
	named_selection_display.select_row.connect (slot (*this, &Editor::named_selection_display_selected));
	named_selection_display.unselect_row.connect (slot (*this, &Editor::named_selection_display_unselected));

	region_selection_vpane.pack1 (region_list_scroller, true, true);
	region_selection_vpane.pack2 (named_selection_scroller, true, true);

	canvas_region_list_pane.pack1 (edit_frame, true, true);
	canvas_region_list_pane.pack2 (region_selection_vpane, true, true);

	track_list_canvas_pane.size_allocate.connect_after (bind (slot (*this, &Editor::pane_allocation_handler),
								  static_cast<Gtk::Paned*> (&track_list_canvas_pane)));
	canvas_region_list_pane.size_allocate.connect_after (bind (slot (*this, &Editor::pane_allocation_handler),
								   static_cast<Gtk::Paned*> (&canvas_region_list_pane)));
	route_group_vpane.size_allocate.connect_after (bind (slot (*this, &Editor::pane_allocation_handler),
							     static_cast<Gtk::Paned*> (&route_group_vpane)));
	region_selection_vpane.size_allocate.connect_after (bind (slot (*this, &Editor::pane_allocation_handler),
								  static_cast<Gtk::Paned*> (&region_selection_vpane)));
	
	track_list_canvas_pane.pack1 (list_vpacker, true, true);
	track_list_canvas_pane.pack2 (canvas_region_list_pane, true, true);

	/* provide special pane-handle event handling for easy "hide" action */

	/* 0: collapse to show left/upper child
	   1: collapse to show right/lower child
	*/

	route_group_vpane.set_data ("collapse-direction", (gpointer) 0);
	region_selection_vpane.set_data ("collapse-direction", (gpointer) 0);
	canvas_region_list_pane.set_data ("collapse-direction", (gpointer) 0);
	track_list_canvas_pane.set_data ("collapse-direction", (gpointer) 1);

	route_group_vpane.button_release_event.connect (bind (slot (pane_handler), static_cast<Paned*> (&route_group_vpane)));
	region_selection_vpane.button_release_event.connect (bind (slot (pane_handler), static_cast<Paned*> (&region_selection_vpane)));
	canvas_region_list_pane.button_release_event.connect (bind (slot (pane_handler), static_cast<Paned*> (&canvas_region_list_pane)));
	track_list_canvas_pane.button_release_event.connect (bind (slot (pane_handler), static_cast<Paned*> (&track_list_canvas_pane)));

	top_hbox.pack_start (toolbar_frame, true, true);

	HBox *hbox = manage (new HBox);
	hbox->pack_start (track_list_canvas_pane, true, true);

	global_vpacker.pack_start (top_hbox, false, false);
	global_vpacker.pack_start (*hbox, true, true);

	global_hpacker.pack_start (global_vpacker, true, true);

	set_name ("EditorWindow");

	vpacker.pack_end (global_hpacker, true, true);
	
	_playlist_selector = new PlaylistSelector();
	_playlist_selector->delete_event.connect (bind (slot (just_hide_it), static_cast<Window *> (_playlist_selector)));

	AudioRegionView::AudioRegionViewGoingAway.connect (slot (*this, &Editor::catch_vanishing_audio_regionview));

	/* nudge stuff */

	nudge_forward_button.add (*(manage (new Gtk::Pixmap (right_arrow_xpm))));
	nudge_backward_button.add (*(manage (new Gtk::Pixmap (left_arrow_xpm))));

	ARDOUR_UI::instance()->tooltips().set_tip (nudge_forward_button, _("Nudge region/selection forwards"));
	ARDOUR_UI::instance()->tooltips().set_tip (nudge_backward_button, _("Nudge region/selection backwards"));

	nudge_forward_button.set_name ("TransportButton");
	nudge_backward_button.set_name ("TransportButton");

	fade_context_menu.set_name ("ArdourContextMenu");

	install_keybindings ();

	set_title (_("ardour: editor"));
	set_wmclass (_("ardour_editor"), "Ardour");

	add (vpacker);
	add_events (GDK_KEY_PRESS_MASK|GDK_KEY_RELEASE_MASK);

	configure_event.connect (slot (*ARDOUR_UI::instance(), &ARDOUR_UI::configure_handler));
	delete_event.connect (slot (*ARDOUR_UI::instance(), &ARDOUR_UI::exit_on_main_window_close));

	constructed = true;
	instant_save ();
}

Editor::~Editor()
{
	/* <CMT Additions> */
	if(image_socket_listener)
	{
		if(image_socket_listener->is_connected())
		{
			image_socket_listener->close_connection() ;
		}
		
		delete image_socket_listener ;
		image_socket_listener = 0 ;
	}
	/* </CMT Additions> */
}

void
Editor::add_toplevel_controls (Container& cont)
{
	vpacker.pack_start (cont, false, false);
	cont.show_all ();
}

void
Editor::catch_vanishing_audio_regionview (AudioRegionView *rv)
{
	/* note: the selection will take care of the vanishing
	   audioregionview by itself.
	*/

	if (clicked_regionview == rv) {
		clicked_regionview = 0;
	}

	if (entered_regionview == rv) {
		set_entered_regionview (0);
	}
}

void
Editor::set_entered_regionview (AudioRegionView* rv)
{
	if (rv == entered_regionview) {
		return;
	}

	if (entered_regionview) {
		entered_regionview->exited ();
	}

	if ((entered_regionview = rv) != 0) {
		entered_regionview->entered ();
	}
}

void
Editor::set_entered_track (TimeAxisView* tav)
{
	if (entered_track) {
		entered_track->exited ();
	}

	if ((entered_track = tav) != 0) {
		entered_track->entered ();
	}
}

gint
Editor::left_track_canvas (GdkEventCrossing *ev)
{
	set_entered_track (0);
	set_entered_regionview (0);
	return FALSE;
}


void
Editor::initialize_canvas ()
{
	gtk_canvas_init ();

	track_gtk_canvas = gtk_canvas_new_aa ();

	/* adjust sensitivity for "picking" items */

	// GTK_CANVAS(track_gtk_canvas)->close_enough = 2;

	gtk_signal_connect (GTK_OBJECT(gtk_canvas_root (GTK_CANVAS(track_gtk_canvas))), "event",
			    (GtkSignalFunc) Editor::_track_canvas_event, this);
	track_canvas = wrap (track_gtk_canvas);
	track_canvas->set_name ("EditorMainCanvas");

	track_canvas->add_events (GDK_POINTER_MOTION_HINT_MASK);

	track_canvas->leave_notify_event.connect (slot (*this, &Editor::left_track_canvas));
	
	/* set up drag-n-drop */

	track_canvas->drag_dest_set (GTK_DEST_DEFAULT_ALL,
				     target_table, n_targets - 1,
				     GdkDragAction (GDK_ACTION_COPY|GDK_ACTION_MOVE));
	track_canvas->drag_data_received.connect (slot (*this, &Editor::track_canvas_drag_data_received));

	/* stuff for the verbose canvas cursor */

	string fontname = get_font_for_style (N_("VerboseCanvasCursor"));

	verbose_canvas_cursor = gtk_canvas_item_new (gtk_canvas_root(GTK_CANVAS(track_gtk_canvas)),
						     gtk_canvas_text_get_type(),
						     "font", fontname.c_str(),
						     "anchor", GTK_ANCHOR_NW,
						     "fill_color_rgba", color_map[cVerboseCanvasCursor],
						     NULL);
	verbose_cursor_visible = false;

	/* a group to hold time (measure) lines */

	time_line_group = gtk_canvas_item_new (gtk_canvas_root(GTK_CANVAS(track_gtk_canvas)),
						  gtk_canvas_group_get_type(),
						  "x", 0.0,
						  "y", 0.0,
						  NULL);

	cursor_group = gtk_canvas_item_new (gtk_canvas_root(GTK_CANVAS(track_gtk_canvas)),
						  gtk_canvas_group_get_type(),
						  "x", 0.0,
						  "y", 0.0,
						  NULL);
	
	time_gtk_canvas = gtk_canvas_new_aa ();
	time_canvas = wrap (time_gtk_canvas);
	time_canvas->set_name ("EditorTimeCanvas");

	time_canvas->add_events (GDK_POINTER_MOTION_HINT_MASK);

	meter_group = gtk_canvas_item_new (gtk_canvas_root(GTK_CANVAS(time_gtk_canvas)),
					    gtk_canvas_group_get_type(),
					    "x", 0.0,
					    "y", 0.0,
					    NULL);
	tempo_group = gtk_canvas_item_new (gtk_canvas_root(GTK_CANVAS(time_gtk_canvas)),
					    gtk_canvas_group_get_type(),
					    "x", 0.0,
					    "y", timebar_height,
					    NULL);
	marker_group = gtk_canvas_item_new (gtk_canvas_root(GTK_CANVAS(time_gtk_canvas)),
					    gtk_canvas_group_get_type(),
					    "x", 0.0,
					    "y", timebar_height * 2.0,
					    NULL);
	range_marker_group = gtk_canvas_item_new (gtk_canvas_root(GTK_CANVAS(time_gtk_canvas)),
					    gtk_canvas_group_get_type(),
					    "x", 0.0,
					    "y", timebar_height * 3.0,
					    NULL);
	transport_marker_group = gtk_canvas_item_new (gtk_canvas_root(GTK_CANVAS(time_gtk_canvas)),
					    gtk_canvas_group_get_type(),
					    "x", 0.0,
					    "y", timebar_height * 4.0,
					    NULL);
	
	tempo_bar = gtk_canvas_item_new (GTK_CANVAS_GROUP(tempo_group),
					 gtk_canvas_simplerect_get_type(),
					 "x1", 0.0,
					 "y1", 0.0,
					 "x2", max_canvas_coordinate,
					 "y2", timebar_height,
					 "fill_color_rgba", color_map[cTempoBar],
					 "outline_pixels", 0,
					  NULL);
	meter_bar = gtk_canvas_item_new (GTK_CANVAS_GROUP(meter_group),
					 gtk_canvas_simplerect_get_type(),
					 "x1", 0.0,
					 "y1", 0.0,
					 "x2", max_canvas_coordinate,
					 "y2", timebar_height,
					 "fill_color_rgba", color_map[cMeterBar],
					 "outline_pixels", 0,
					 NULL);
	marker_bar = gtk_canvas_item_new (GTK_CANVAS_GROUP(marker_group),
					  gtk_canvas_simplerect_get_type(),
					  "x1", 0.0,
					  "y1", 0.0,
					  "x2", max_canvas_coordinate,
					  "y2", timebar_height,
					  "fill_color_rgba", color_map[cMarkerBar],
					  "outline_pixels", 0,
					  NULL);
	range_marker_bar = gtk_canvas_item_new (GTK_CANVAS_GROUP(range_marker_group),
						gtk_canvas_simplerect_get_type(),
						"x1", 0.0,
						"y1", 0.0,
						"x2", max_canvas_coordinate,
						"y2", timebar_height,
						"fill_color_rgba", color_map[cRangeMarkerBar],
						"outline_pixels", 0,
						NULL);
	transport_marker_bar = gtk_canvas_item_new (GTK_CANVAS_GROUP(transport_marker_group),
						    gtk_canvas_simplerect_get_type(),
						    "x1", 0.0,
						    "y1", 0.0,
						    "x2", max_canvas_coordinate,
						    "y2", timebar_height,
						    "fill_color_rgba", color_map[cTransportMarkerBar],
						    "outline_pixels", 0,
						    NULL);
	
	range_bar_drag_rect = gtk_canvas_item_new (GTK_CANVAS_GROUP(range_marker_group),
						   gtk_canvas_simplerect_get_type(),
						   "x1", 0.0,
						   "y1", 0.0,
						   "x2", 0.0,
						   "y2", timebar_height,
						   "fill_color_rgba", color_map[cRangeDragBarRectFill],
						   "outline_color_rgba", color_map[cRangeDragBarRect],
						   NULL);
	gtk_canvas_item_hide (range_bar_drag_rect);

	transport_bar_drag_rect = gtk_canvas_item_new (GTK_CANVAS_GROUP(transport_marker_group),
						       gtk_canvas_simplerect_get_type(),
						       "x1", 0.0,
						       "y1", 0.0,
						       "x2", 0.0,
						       "y2", timebar_height,
						       "fill_color_rgba", color_map[cTransportDragRectFill],
						       "outline_color_rgba", color_map[cTransportDragRect],
						       NULL);
	gtk_canvas_item_hide (transport_bar_drag_rect);

	
	marker_drag_line_points = gtk_canvas_points_new (2);
	marker_drag_line_points->coords[0] = 0.0;
	marker_drag_line_points->coords[1] = 0.0;
	marker_drag_line_points->coords[2] = 0.0;
	marker_drag_line_points->coords[3] = 0.0;
	
	
	//cerr << "set mdl points, nc = " << marker_drag_line_points->num_points << endl;
	marker_drag_line = gtk_canvas_item_new (gtk_canvas_root(GTK_CANVAS(track_gtk_canvas)),
						gtk_canvas_line_get_type(),
						"width_pixels", 1,
						"fill_color_rgba", color_map[cMarkerDragLine],
						"points", marker_drag_line_points,
						NULL);
	gtk_canvas_item_hide (marker_drag_line);

	range_marker_drag_rect = gtk_canvas_item_new (gtk_canvas_root(GTK_CANVAS(track_gtk_canvas)),
					       gtk_canvas_simplerect_get_type(),
					       "x1", 0.0,
					       "y1", 0.0,
					       "x2", 0.0,
					       "y2", 0.0,
					       "fill_color_rgba", color_map[cRangeDragRectFill],
					       "outline_color_rgba", color_map[cRangeDragRect],
					       NULL);
	gtk_canvas_item_hide (range_marker_drag_rect);
	

	transport_loop_range_rect = gtk_canvas_item_new ((GTK_CANVAS_GROUP(time_line_group)),
					       gtk_canvas_simplerect_get_type(),
					       "x1", 0.0,
					       "y1", 0.0,
					       "x2", 0.0,
					       "y2", 0.0,
					       "fill_color_rgba", color_map[cTransportLoopRectFill],
					       "outline_color_rgba", color_map[cTransportLoopRect],
					       "outline_pixels", 1,
					       NULL);
	gtk_canvas_item_hide (transport_loop_range_rect);

	transport_punch_range_rect = gtk_canvas_item_new ((GTK_CANVAS_GROUP(time_line_group)),
					       gtk_canvas_simplerect_get_type(),
					       "x1", 0.0,
					       "y1", 0.0,
					       "x2", 0.0,
					       "y2", 0.0,
					       "fill_color_rgba", color_map[cTransportPunchRectFill],
					       "outline_color_rgba", color_map[cTransportPunchRect],
					       "outline_pixels", 0,
					       NULL);
	gtk_canvas_item_lower_to_bottom (transport_punch_range_rect);
	gtk_canvas_item_lower_to_bottom (transport_loop_range_rect); // loop on the bottom
	gtk_canvas_item_hide (transport_punch_range_rect);

	transport_punchin_line = gtk_canvas_item_new ((GTK_CANVAS_GROUP(time_line_group)),
					       gtk_canvas_simplerect_get_type(),
					       "x1", 0.0,
					       "y1", 0.0,
					       "x2", 0.0,
					       "y2", 0.0,
					       "outline_color_rgba", color_map[cPunchInLine],
					       "outline_pixels", 1,
					       NULL);
	gtk_canvas_item_hide (transport_punchin_line);

	transport_punchout_line  = gtk_canvas_item_new ((GTK_CANVAS_GROUP(time_line_group)),
					       gtk_canvas_simplerect_get_type(),
					       "x1", 0.0,
					       "y1", 0.0,
					       "x2", 0.0,
					       "y2", 0.0,
					       "outline_color_rgba", color_map[cPunchOutLine],
					       "outline_pixels", 1,
					       NULL);
	gtk_canvas_item_hide (transport_punchout_line);



	
	// used to show zoom mode active zooming
	zoom_rect = gtk_canvas_item_new (gtk_canvas_root(GTK_CANVAS(track_gtk_canvas)),
					 gtk_canvas_simplerect_get_type(),
					 "x1", 0.0,
					 "y1", 0.0,
					 "x2", 0.0,
					 "y2", 0.0,
					 "fill_color_rgba", color_map[cZoomRectFill],
					 "outline_color_rgba", color_map[cZoomRect],
					 "outline_pixels", 1,
					 NULL);
	gtk_canvas_item_hide (zoom_rect);
	gtk_signal_connect (GTK_OBJECT(zoom_rect), "event",
			    (GtkSignalFunc) PublicEditor::canvas_zoom_rect_event,
			    this);

	// used as rubberband rect
	rubberband_rect = gtk_canvas_item_new (gtk_canvas_root(GTK_CANVAS(track_gtk_canvas)),
					       gtk_canvas_simplerect_get_type(),
					       "x1", 0.0,
					       "y1", 0.0,
					       "x2", 0.0,
					       "y2", 0.0,
					       "outline_color_rgba", color_map[cRubberBandRect],
					       "fill_color_rgba", (guint32) color_map[cRubberBandRectFill],
					       "outline_pixels", 1,
					       NULL);
	gtk_canvas_item_hide (rubberband_rect);

	

	gtk_signal_connect (GTK_OBJECT(tempo_bar), "event",
			    (GtkSignalFunc) PublicEditor::canvas_tempo_bar_event,
			    this);

	gtk_signal_connect (GTK_OBJECT(meter_bar), "event",
			    (GtkSignalFunc) PublicEditor::canvas_meter_bar_event,
			    this);

	gtk_signal_connect (GTK_OBJECT(marker_bar), "event",
			    (GtkSignalFunc) PublicEditor::canvas_marker_bar_event,
			    this);

	gtk_signal_connect (GTK_OBJECT(range_marker_bar), "event",
			    (GtkSignalFunc) PublicEditor::canvas_range_marker_bar_event,
			    this);

	gtk_signal_connect (GTK_OBJECT(transport_marker_bar), "event",
			    (GtkSignalFunc) PublicEditor::canvas_transport_marker_bar_event,
			    this);
	
	/* separator lines */

	tempo_line_points = gtk_canvas_points_new (2);
	tempo_line_points->coords[0] = 0;
	tempo_line_points->coords[1] = timebar_height;
	tempo_line_points->coords[2] = max_canvas_coordinate;
	tempo_line_points->coords[3] = timebar_height;
	//cerr << "set tl points, nc = " << tempo_line_points->num_points << endl;
	tempo_line = gtk_canvas_item_new (GTK_CANVAS_GROUP(tempo_group),
					  gtk_canvas_line_get_type(),
					  "width_pixels", 0,
					  "fill_color", "black",
					  "points", tempo_line_points,
					  NULL);

	// cerr << "tempo line @ " << tempo_line << endl;

	meter_line_points = gtk_canvas_points_new (2);
	meter_line_points->coords[0] = 0;
	meter_line_points->coords[1] = timebar_height;
	meter_line_points->coords[2] = max_canvas_coordinate;
	meter_line_points->coords[3] = timebar_height;
	// cerr << "set ml points, nc = " << tempo_line_points->num_points << endl;
	meter_line = gtk_canvas_item_new (GTK_CANVAS_GROUP(meter_group),
					  gtk_canvas_line_get_type(),
					  "width_pixels", 0,
					  "fill_color", "black",
					  "points", meter_line_points,
					  NULL);

	// cerr << "meter line @ " << tempo_line << endl;
	
	marker_line_points = gtk_canvas_points_new (2);
	marker_line_points->coords[0] = 0;
	marker_line_points->coords[1] = timebar_height;
	marker_line_points->coords[2] = max_canvas_coordinate;
	marker_line_points->coords[3] = timebar_height;
	// cerr << "set ml2 points, nc = " << marker_line_points->num_points << endl;
	marker_line = gtk_canvas_item_new (GTK_CANVAS_GROUP(marker_group),
					   gtk_canvas_line_get_type(),
					   "width_pixels", 0,
					   "fill_color", "black",
					   "points", marker_line_points,
					   NULL);
	// cerr << "set rml points, nc = " << marker_line_points->num_points << endl;
	range_marker_line = gtk_canvas_item_new (GTK_CANVAS_GROUP(range_marker_group),
					   gtk_canvas_line_get_type(),
					   "width_pixels", 0,
					   "fill_color", "black",
					   "points", marker_line_points,
					   NULL);
	// cerr << "set tml2 points, nc = " << marker_line_points->num_points << endl;
	transport_marker_line = gtk_canvas_item_new (GTK_CANVAS_GROUP(transport_marker_group),
					   gtk_canvas_line_get_type(),
					   "width_pixels", 0,
					   "fill_color", "black",
					   "points", marker_line_points,
					   NULL);

	// cerr << "marker line @ " << marker_line << endl;

	ZoomChanged.connect (bind (slot (*this, &Editor::update_loop_range_view), false));
	ZoomChanged.connect (bind (slot (*this, &Editor::update_punch_range_view), false));
	
	double time_height = timebar_height * 5;
	double time_width = FLT_MAX/frames_per_unit;
	gtk_canvas_set_scroll_region (GTK_CANVAS(time_gtk_canvas), 0.0, 0.0, time_width, time_height);

	edit_cursor = new Cursor (*this, "blue", (GtkSignalFunc) _canvas_edit_cursor_event);
	playhead_cursor = new Cursor (*this, "red", (GtkSignalFunc) _canvas_playhead_cursor_event);

	track_canvas->size_allocate.connect (slot (*this, &Editor::track_canvas_allocate));
}

void
Editor::show_window ()
{
	show_all ();
	
	/* now reset all audio_time_axis heights, because widgets might need
	   to be re-hidden
	*/
	
	TimeAxisView *tv;
	
	for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
		tv = (static_cast<TimeAxisView*>(*i));
		tv->reset_height ();
	}
}

void
Editor::tie_vertical_scrolling ()
{
	edit_controls_scroller.get_vadjustment()->set_value (track_canvas_scroller.get_vadjustment()->get_value());

	float y1 = track_canvas_scroller.get_vadjustment()->get_value();
	playhead_cursor->set_y_axis(y1);
	edit_cursor->set_y_axis(y1);
}

void
Editor::set_frames_per_unit (double fpu)
{
	jack_nframes_t frames;

	if (fpu == frames_per_unit) {
		return;
	}

	if (fpu < 1.0) {
		fpu = 1.0;
	}

	// convert fpu to frame count

	frames = (jack_nframes_t) (fpu * canvas_width);
	
	/* don't allow zooms that fit more than the maximum number
	   of frames into an 800 pixel wide space.
	*/

	if (max_frames / fpu < 800.0) {
		return;
	}

	frames_per_unit = fpu;

	if (frames != zoom_range_clock.current_duration()) {
		zoom_range_clock.set (frames);
	}

	/* only update these if we not about to call reposition_x_origin,
	   which will do the same updates.
	*/
	
	if (session) {
		track_canvas_scroller.get_hadjustment()->set_upper (session->current_end_frame() / frames_per_unit);
	}
	
	if (!no_zoom_repos_update) {
		track_canvas_scroller.get_hadjustment()->set_value (leftmost_frame/frames_per_unit);
		update_hscroller ();
		update_fixed_rulers ();
		tempo_map_changed (Change (0));
	}

	if (mouse_mode == MouseRange && selection->time.start () != selection->time.end_frame ()) {
		for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
			(*i)->reshow_selection (selection->time);
		}
	}

	ZoomChanged (); /* EMIT_SIGNAL */

	if (edit_cursor) edit_cursor->set_position (edit_cursor->current_frame);
	if (playhead_cursor) playhead_cursor->set_position (playhead_cursor->current_frame);

	instant_save ();

}

void
Editor::instant_save ()
{
        if (!constructed || !ARDOUR_UI::instance()->session_loaded) {
		return;
	}

	if (session) {
		session->add_instant_xml(get_state(), session->path());
	} else {
		Config->add_instant_xml(get_state(), Config->get_user_ardour_path());
	}
}

void
Editor::reposition_x_origin (jack_nframes_t frame)
{
	if (frame != leftmost_frame) {
		leftmost_frame = frame;
		double pixel = frame_to_pixel (frame);
		if (pixel >= track_canvas_scroller.get_hadjustment()->get_upper()) {
			track_canvas_scroller.get_hadjustment()->set_upper (frame_to_pixel (frame + (current_page_frames())));
		}
		track_canvas_scroller.get_hadjustment()->set_value (frame/frames_per_unit);
		XOriginChanged (); /* EMIT_SIGNAL */
	}
}

void
Editor::edit_cursor_clock_changed()
{
	if (edit_cursor->current_frame != edit_cursor_clock.current_time()) {
		edit_cursor->set_position (edit_cursor_clock.current_time());
	}
}


void
Editor::zoom_adjustment_changed ()
{
	if (session == 0 || no_zoom_repos_update) {
		return;
	}

	double fpu = (double) zoom_range_clock.current_duration() / (double) canvas_width;

	if (fpu < 1.0) {
		fpu = 1.0;
		zoom_range_clock.set ((jack_nframes_t) (fpu * canvas_width));
	}
	else if (fpu > session->current_end_frame() / (double) canvas_width) {
		fpu = session->current_end_frame() / (double) canvas_width;
		zoom_range_clock.set ((jack_nframes_t) (fpu * canvas_width));
	}
	
	temporal_zoom (fpu);
}

void 
Editor::canvas_horizontally_scrolled ()
{
	/* XXX note the potential loss of accuracy here caused by
	   adjustments being 32bit floats with only a 24 bit mantissa,
	   whereas jack_nframes_t is at least a 32 bit uint32_teger.
	*/
	
	leftmost_frame = (jack_nframes_t) floor (track_canvas_scroller.get_hadjustment()->get_value() * frames_per_unit);
	
	update_hscroller ();
	update_fixed_rulers ();
	
	if (!edit_hscroll_dragging) {
		tempo_map_changed (Change (0));
	} else {
	        update_tempo_based_rulers();
	}
}

void
Editor::reposition_and_zoom (jack_nframes_t frame, double nfpu)
{
	if (!repos_zoom_queued) {
		Main::idle.connect (bind (slot (*this, &Editor::deferred_reposition_and_zoom), frame, nfpu));
		repos_zoom_queued = true;
	}
}

gint
Editor::deferred_reposition_and_zoom (jack_nframes_t frame, double nfpu)
{
 	/* if we need to force an update to the hscroller stuff,
	   don't set no_zoom_repos_update.
	*/

	no_zoom_repos_update = (frame != leftmost_frame);
	
	set_frames_per_unit (nfpu);
	if (no_zoom_repos_update) {
		reposition_x_origin  (frame);
	}
	no_zoom_repos_update = false;
	repos_zoom_queued = false;
	
	return FALSE;
}

void
Editor::realize_impl ()
{
	/* Even though we're not using acceleration, we want the
	   labels to show up.
	*/

	track_context_menu.accelerate (*this->get_toplevel());
	track_region_context_menu.accelerate (*this->get_toplevel());
	
	Window::realize_impl ();

	GdkPixmap* empty_pixmap = gdk_pixmap_new (get_window(), 1, 1, 1);
	GdkPixmap* empty_bitmap = gdk_pixmap_new (get_window(), 1, 1, 1);
	GdkColor white = { 0, 0, 0 };

	null_cursor = gdk_cursor_new_from_pixmap (empty_pixmap, empty_bitmap, &white, &white, 0, 0);
}

void
Editor::map__impl ()
{
	Window::map__impl ();

	track_canvas_scroller.get_window().set_cursor (current_canvas_cursor);
	time_canvas_scroller.get_window().set_cursor (timebar_cursor);
}

void
Editor::track_canvas_allocate (GtkAllocation *alloc)
{
	canvas_width = alloc->width;
	canvas_height = alloc->height;

	if (session == 0 && !ARDOUR_UI::instance()->will_create_new_session_automatically()) {

		string fontname = get_font_for_style (N_("FirstActionMessage"));

		const char *txt1 = _("Start a new session\n");
		const char *txt2 = _("via Session menu");

		/* this mess of code is here to find out how wide this text is and
		   position the message in the center of the editor window. there
		   are two lines, so we use the longer of the the lines to
		   compute width, and multiply the height by 2.
		*/
			
		gint width;
		gint lbearing;
		gint rbearing;
		gint ascent;
		gint descent;
		
		/* this is a dummy widget that exists so that we can get the
		   style from the RC file. 
		*/
		
		Label foo (_(txt2));
		foo.set_name ("NoSessionMessage");
		foo.ensure_style ();
		
		gdk_string_extents (foo.get_style()->get_font(),
				    _(txt2),
				    &lbearing,
				    &rbearing,
				    &width,
				    &ascent,
				    &descent);
			
		if (first_action_message == 0) {
			
			char txt[strlen(txt1)+strlen(txt2)+1];
			
			/* merge both lines */
			
			strcpy (txt, _(txt1));
			strcat (txt, _(txt2));
			
			first_action_message = gtk_canvas_item_new (gtk_canvas_root(GTK_CANVAS(track_gtk_canvas)),
								    gtk_canvas_text_get_type(),
								    "font", fontname.c_str(),
								    "fill_color_rgba", color_map[cFirstActionMessage],
								    "x", (gdouble) (canvas_width - width) / 2.0,
								    "y", (gdouble) (canvas_height/2.0) - (2.0 * (ascent+descent)),
								    "anchor", GTK_ANCHOR_NORTH_WEST,
								    "text", txt,
								    NULL);
			
		} else {

			/* center it */

			gtk_canvas_item_set (first_action_message,
					     "x", (gdouble) (canvas_width - width) / 2.0,
					     "y", (gdouble) (canvas_height/2.0) - (2.0 * (ascent+descent)),
					     NULL);
		}
	}

	zoom_range_clock.set ((jack_nframes_t) (canvas_width * frames_per_unit));
	edit_cursor->set_position (edit_cursor->current_frame);
	playhead_cursor->set_position (playhead_cursor->current_frame);
	reset_scrolling_region (alloc);
	
	Resized (); /* EMIT_SIGNAL */
}

void
Editor::reset_scrolling_region (GtkAllocation *alloc)
{
	guint32 last_canvas_unit;
	double height;
	guint32 canvas_alloc_height, canvas_alloc_width;
	TrackViewList::iterator i;
	static bool first_time = true;

	/* We need to make sure that the canvas always has its
	   scrolling region set to larger of:

	   - the size allocated for it (within the container its packed in)
	   - the size required to see the entire session

	   If we don't ensure at least the first of these, the canvas
	   does some wierd and in my view unnecessary stuff to center
	   itself within the allocated area, which causes bad, bad
	   results.
	   
	   XXX GnomeCanvas has fixed this, and has an option to
	   control the centering behaviour.
	*/

	last_canvas_unit = (guint32) ceil ((float) max_frames / frames_per_unit);

	height = 0;

	if (session) {
		for (i = track_views.begin(); i != track_views.end(); ++i) {
			if ((*i)->control_parent) {
				height += (*i)->effective_height;
				height += track_spacing;
			}
		}
		
		if (height) {
			height -= track_spacing;
		}
	}

	canvas_height = (guint32) height;
	
	if (alloc) {
		canvas_alloc_height = alloc->height;
		canvas_alloc_width = alloc->width;
	} else {
		canvas_alloc_height = track_gtk_canvas->allocation.height;
		canvas_alloc_width = track_gtk_canvas->allocation.width;
	}

	canvas_height = max (canvas_height, canvas_alloc_height);

	gtk_canvas_set_scroll_region (GTK_CANVAS(track_gtk_canvas), 0.0, 0.0, 
				      max (last_canvas_unit, canvas_alloc_width),
				      canvas_height);

	if (edit_cursor) edit_cursor->set_length (canvas_alloc_height);
	if (playhead_cursor) playhead_cursor->set_length (canvas_alloc_height);

	if (marker_drag_line) {
		marker_drag_line_points->coords[3] = canvas_height;
		// cerr << "set mlA points, nc = " << marker_drag_line_points->num_points << endl;
		gtk_canvas_item_set (marker_drag_line, "points", marker_drag_line_points, NULL);
	}
	if (range_marker_drag_rect) {
		gtk_canvas_item_set (range_marker_drag_rect, "y1", 0.0, "y2", (double) canvas_height, NULL);
	}
	if (transport_loop_range_rect) {
		gtk_canvas_item_set (transport_loop_range_rect, "y1", 0.0, "y2", (double) canvas_height, NULL);
	}
	if (transport_punch_range_rect) {
		gtk_canvas_item_set (transport_punch_range_rect, "y1", 0.0, "y2", (double) canvas_height, NULL);
	}
	if (transport_punchin_line) {
		gtk_canvas_item_set (transport_punchin_line, "y1", 0.0, "y2", (double) canvas_height, NULL);
	}
	if (transport_punchout_line) {
		gtk_canvas_item_set (transport_punchout_line, "y1", 0.0, "y2", (double) canvas_height, NULL);
	}
	
	
	update_fixed_rulers ();

	if (is_visible() && first_time) {
		tempo_map_changed (Change (0));
		first_time = false;
	} else {
		redisplay_tempo ();
	}
}

void
Editor::queue_session_control_changed (Session::ControlType t)
{
	Gtkmmext::UI::instance()->call_slot (bind (slot (*this, &Editor::session_control_changed), t));
}

void
Editor::session_control_changed (Session::ControlType t)
{
	// right now we're only tracking the loop and punch state

	switch (t) {
	case Session::AutoLoop:
		update_loop_range_view (true);
		break;
	case Session::PunchIn:
	case Session::PunchOut:
		update_punch_range_view (true);
		break;

	default:
		break;
	}
}

void
Editor::fake_add_edit_group (RouteGroup *group)
{
	Gtkmmext::UI::instance()->call_slot (bind (slot (*this, &Editor::add_edit_group), group));
}

void
Editor::fake_handle_new_audio_region (AudioRegion *region)
{
	Gtkmmext::UI::instance()->call_slot (bind (slot (*this, &Editor::handle_new_audio_region), region));
}

void
Editor::fake_handle_audio_region_removed (AudioRegion *region)
{
	Gtkmmext::UI::instance()->call_slot (bind (slot (*this, &Editor::handle_audio_region_removed), region));
}

void
Editor::fake_handle_new_duration ()
{
	Gtkmmext::UI::instance()->call_slot (slot (*this, &Editor::handle_new_duration));
}

void
Editor::start_scrolling ()
{
	scroll_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect 
		(slot (*this, &Editor::update_current_screen));

	slower_update_connection = ARDOUR_UI::instance()->RapidScreenUpdate.connect 
		(slot (*this, &Editor::update_slower));
}

void
Editor::stop_scrolling ()
{
	scroll_connection.disconnect ();
	slower_update_connection.disconnect ();
}

void
Editor::map_position_change (jack_nframes_t frame)
{
	ENSURE_GUI_THREAD (bind (slot (*this, &Editor::map_position_change), frame));

	if (session == 0 || !_follow_playhead) {
		return;
	}

	center_screen (frame);
	playhead_cursor->set_position (frame);
}	

void
Editor::center_screen (jack_nframes_t frame)
{
	float page = canvas_width * frames_per_unit;

	/* if we're off the page, then scroll.
	 */
	
	if (frame < leftmost_frame || frame >= leftmost_frame + page) {
		center_screen_internal (frame,page);
	}
}

void
Editor::center_screen_internal (jack_nframes_t frame, float page)
{
	page /= 2;
		
	if (frame > page) {
		frame -= (jack_nframes_t) page;
	} else {
		frame = 0;
	}

    reposition_x_origin (frame);
}

void
Editor::handle_new_duration ()
{
	reset_scrolling_region ();

	if (session) {
		track_canvas_scroller.get_hadjustment()->set_upper (session->current_end_frame() / frames_per_unit);
		track_canvas_scroller.get_hadjustment()->set_value (leftmost_frame/frames_per_unit);
	}
	
	update_hscroller ();
}

void
Editor::update_title_s (string snap_name)
{
	ENSURE_GUI_THREAD(bind (slot (*this, &Editor::update_title_s), snap_name));
	
	update_title ();
}

void
Editor::update_title ()
{
	ENSURE_GUI_THREAD (slot (*this, &Editor::update_title));

	if (session) {
		bool dirty = session->dirty();

		string wintitle = _("ardour: editor: ");

		if (dirty) {
			wintitle += '[';
		}

		wintitle += session->name();

		if (session->snap_name() != session->name()) {
			wintitle += ':';
			wintitle += session->snap_name();
		}

		if (dirty) {
			wintitle += ']';
		}

		set_title (wintitle);
	}
}

gint
Editor::autosave_session ()
{
	if (!Config->get_autosave_enabled())
		return 1;
	
	if (session) {
		session->maybe_write_autosave();
	}

	return 1;
}

void
Editor::toggle_autosave ()
{
	ENSURE_GUI_THREAD (slot (*this, &Editor::toggle_autosave));
	
	if (session->dirty()) {
		if (autosave_timeout_connection.connected()) {
			autosave_timeout_connection.disconnect();
		}

		autosave_timeout_connection = Gtk::Main::timeout.connect (slot (*this, &Editor::autosave_session), 60000);
	} else {
		if (autosave_timeout_connection.connected()) {
			autosave_timeout_connection.disconnect();
		}		
	}
}

void
Editor::connect_to_session (Session *t)
{
	session = t;

	if (first_action_message) {
		gtk_canvas_item_hide (first_action_message);
	}

	flush_track_canvas();

	update_title ();

	session->going_away.connect (slot (*this, &Editor::session_going_away));

	/* These signals can all be emitted by a non-GUI thread. Therefore the
	   handlers for them must not attempt to directly interact with the GUI,
	   but use Gtkmmext::UI::instance()->call_slot();
	*/

	session_connections.push_back (session->TransportStateChange.connect (slot (*this, &Editor::map_transport_state)));
	session_connections.push_back (session->PositionChanged.connect (slot (*this, &Editor::map_position_change)));
	session_connections.push_back (session->RouteAdded.connect (slot (*this, &Editor::handle_new_route_p)));
	session_connections.push_back (session->AudioRegionAdded.connect (slot (*this, &Editor::fake_handle_new_audio_region)));
	session_connections.push_back (session->AudioRegionRemoved.connect (slot (*this, &Editor::fake_handle_audio_region_removed)));
	session_connections.push_back (session->DurationChanged.connect (slot (*this, &Editor::fake_handle_new_duration)));
	session_connections.push_back (session->edit_group_added.connect (slot (*this, &Editor::fake_add_edit_group)));
	session_connections.push_back (session->NamedSelectionAdded.connect (slot (*this, &Editor::handle_new_named_selection)));
	session_connections.push_back (session->NamedSelectionRemoved.connect (slot (*this, &Editor::handle_new_named_selection)));
	session_connections.push_back (session->DirtyChanged.connect (slot (*this, &Editor::update_title)));
	session_connections.push_back (session->DirtyChanged.connect (slot (*this, &Editor::toggle_autosave)));
	session_connections.push_back (session->StateSaved.connect (slot (*this, &Editor::update_title_s)));
	session_connections.push_back (session->AskAboutPlaylistDeletion.connect (slot (*this, &Editor::playlist_deletion_dialog)));
	session_connections.push_back (session->RegionHiddenChange.connect (slot (*this, &Editor::region_hidden)));

	session_connections.push_back (session->SMPTEOffsetChanged.connect (slot (*this, &Editor::update_just_smpte)));
	session_connections.push_back (session->SMPTETypeChanged.connect (slot (*this, &Editor::update_just_smpte)));

	session_connections.push_back (session->tempo_map().StateChanged.connect (slot (*this, &Editor::tempo_map_changed)));

	session->foreach_edit_group(this, &Editor::add_edit_group);

	ARDOUR_UI::instance()->editor_mixer_button().toggled.connect (slot (*this, &Editor::editor_mixer_button_toggled));

	edit_cursor_clock.set_session (session);
	selection_start_clock.set_session (session);
	selection_end_clock.set_session (session);
	zoom_range_clock.set_session (session);
	_playlist_selector->set_session (session);
	nudge_clock.set_session (session);

	switch (session->get_edit_mode()) {
	case Splice:
		edit_mode_selector.get_entry()->set_text (edit_mode_strings[splice_index]);
		break;

	case Slide:
		edit_mode_selector.get_entry()->set_text (edit_mode_strings[slide_index]);
		break;
	}

	Location* loc = session->locations()->auto_loop_location();
	if (loc == 0) {
		loc = new Location (0, session->current_end_frame(), _("Loop"),(Location::Flags) (Location::IsAutoLoop | Location::IsHidden));
		if (loc->start() == loc->end()) {
			loc->set_end (loc->start() + 1);
		}
		session->locations()->add (loc, false);
		session->set_auto_loop_location (loc);
	}
	else {
		// force name
		loc->set_name (_("Loop"));
	}
	
	loc = session->locations()->auto_punch_location();
	if (loc == 0) {
		loc = new Location (0, session->current_end_frame(), _("Punch"), (Location::Flags) (Location::IsAutoPunch | Location::IsHidden));
		if (loc->start() == loc->end()) {
			loc->set_end (loc->start() + 1);
		}
		session->locations()->add (loc, false);
		session->set_auto_punch_location (loc);
	}
	else {
		// force name
		loc->set_name (_("Punch"));
	}

	update_loop_range_view (true);
	update_punch_range_view (true);
	
	session->ControlChanged.connect (slot (*this, &Editor::queue_session_control_changed));

	
	refresh_location_display ();
	session->locations()->added.connect (slot (*this, &Editor::add_new_location));
	session->locations()->removed.connect (slot (*this, &Editor::location_gone));
	session->locations()->changed.connect (slot (*this, &Editor::refresh_location_display));
	session->locations()->StateChanged.connect (slot (*this, &Editor::refresh_location_display_s));
	session->locations()->end_location()->changed.connect (slot (*this, &Editor::end_location_changed));

	reset_scrolling_region ();

	redisplay_regions ();
	redisplay_named_selections ();

	route_list.freeze ();
	route_list.clear ();
	session->foreach_route (this, &Editor::handle_new_route);
	// route_list.select_all ();
	route_list.sort ();
	route_list_reordered ();
	route_list.thaw ();

	if (embed_audio_item) {
		embed_audio_item->set_sensitive (true);
	} 
	if (import_audio_item) {
		import_audio_item->set_sensitive (true);
	}

	for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
		(static_cast<TimeAxisView*>(*i))->set_samples_per_unit (frames_per_unit);
	}

	/* ::reposition_x_origin() doesn't work right here, since the old
	   position may be zero already, and it does nothing in such
	   circumstances.
	*/

	leftmost_frame = 0;
	
	track_canvas_scroller.get_hadjustment()->set_upper (session->current_end_frame() / frames_per_unit);
	track_canvas_scroller.get_hadjustment()->set_value (0);

	update_hscroller ();
	restore_ruler_visibility ();
	tempo_map_changed (Change (0));

	edit_cursor->set_position (0);
	playhead_cursor->set_position (0);

	start_scrolling ();

	XMLNode* node = ARDOUR_UI::instance()->editor_settings();
	set_state (*node);

	/* don't show master bus in a new session */

	if (ARDOUR_UI::instance()->session_is_new ()) {

		Gtk::CList_Helpers::RowList::iterator i;
		Gtk::CList_Helpers::RowList& rowlist = route_list.rows();

		route_list.freeze ();
		
		for (i = rowlist.begin(); i != rowlist.end(); ++i) {
			TimeAxisView *tv = (TimeAxisView *) i->get_data ();
			AudioTimeAxisView *atv;

			if ((atv = dynamic_cast<AudioTimeAxisView*>(tv)) != 0) {
				if (atv->route().master()) {
					(*i)->unselect ();
				}
			}
		}

		route_list.thaw ();
	}
}

void
Editor::build_cursors ()
{
	GdkPixmap *source, *mask;
	GdkColor fg = { 0, 65535, 0, 0 }; /* Red. */
	GdkColor bg = { 0, 0, 0, 65535 }; /* Blue. */
	
	source = gdk_bitmap_create_from_data (NULL, hand_bits,
					      hand_width, hand_height);
	mask = gdk_bitmap_create_from_data (NULL, handmask_bits,
					    handmask_width, handmask_height);
	grabber_cursor = gdk_cursor_new_from_pixmap (source, mask, &fg, &bg, hand_x_hot, hand_y_hot);
	gdk_pixmap_unref (source);
	gdk_pixmap_unref (mask);


	GdkColor mbg = { 0, 0, 0, 0 }; /* Black */
	GdkColor mfg = { 0, 0, 0, 65535 }; /* Blue. */
	
	source = gdk_bitmap_create_from_data (NULL, mag_bits,
					      mag_width, mag_height);
	mask = gdk_bitmap_create_from_data (NULL, magmask_bits,
					    mag_width, mag_height);
	zoom_cursor = gdk_cursor_new_from_pixmap (source, mask, &mfg, &mbg, mag_x_hot, mag_y_hot);
	gdk_pixmap_unref (source);
	gdk_pixmap_unref (mask);

	GdkColor fbg = { 0, 65535, 65535, 65535 };
	GdkColor ffg = { 0, 0, 0, 0 };
	
	source = gdk_bitmap_create_from_data (NULL, fader_cursor_bits,
					      fader_cursor_width, fader_cursor_height);
	mask = gdk_bitmap_create_from_data (NULL, fader_cursor_mask_bits,
					    fader_cursor_width, fader_cursor_height);
	fader_cursor = gdk_cursor_new_from_pixmap (source, mask, &ffg, &fbg, fader_cursor_x_hot, fader_cursor_y_hot);
	gdk_pixmap_unref (source);
	gdk_pixmap_unref (mask);

	source = gdk_bitmap_create_from_data (NULL, speaker_cursor_bits,
					      speaker_cursor_width, speaker_cursor_height);
	mask = gdk_bitmap_create_from_data (NULL, speaker_cursor_mask_bits,
					    speaker_cursor_width, speaker_cursor_height);
	speaker_cursor = gdk_cursor_new_from_pixmap (source, mask, &ffg, &fbg, speaker_cursor_x_hot, speaker_cursor_y_hot);
	gdk_pixmap_unref (source);
	gdk_pixmap_unref (mask);

	cross_hair_cursor = gdk_cursor_new (GDK_CROSSHAIR);
	trimmer_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
	selector_cursor = gdk_cursor_new (GDK_XTERM);
	time_fx_cursor = gdk_cursor_new (GDK_SIZING);
	wait_cursor = gdk_cursor_new (GDK_WATCH);
	timebar_cursor = gdk_cursor_new (GDK_LEFT_PTR);
}

void
Editor::popup_fade_context_menu (int button, int32_t time, GtkCanvasItem* item, ItemType item_type)
{
	using namespace Menu_Helpers;
	AudioRegionView* arv = static_cast<AudioRegionView*> (gtk_object_get_data (GTK_OBJECT(item), "regionview"));

	if (arv == 0) {
		fatal << _("programming error: fade in canvas item has no regionview data pointer!") << endmsg;
		/*NOTREACHED*/
	}

	MenuList& items (fade_context_menu.items());

	items.clear ();

	switch (item_type) {
	case FadeInItem:
	case FadeInHandleItem:
		if (arv->region.fade_in_active()) {
			items.push_back (MenuElem (_("Deactivate"), bind (slot (*arv, &AudioRegionView::set_fade_in_active), false)));
		} else {
			items.push_back (MenuElem (_("Activate"), bind (slot (*arv, &AudioRegionView::set_fade_in_active), true)));
		}
		
		items.push_back (SeparatorElem());
		
		items.push_back (MenuElem (_("Linear"), bind (slot (arv->region, &AudioRegion::set_fade_in_shape), AudioRegion::Linear)));
		items.push_back (MenuElem (_("Slowest"), bind (slot (arv->region, &AudioRegion::set_fade_in_shape), AudioRegion::LogB)));
		items.push_back (MenuElem (_("Slow"), bind (slot (arv->region, &AudioRegion::set_fade_in_shape), AudioRegion::Fast)));
		items.push_back (MenuElem (_("Fast"), bind (slot (arv->region, &AudioRegion::set_fade_in_shape), AudioRegion::LogA)));
		items.push_back (MenuElem (_("Fastest"), bind (slot (arv->region, &AudioRegion::set_fade_in_shape), AudioRegion::Slow)));
		break;

	case FadeOutItem:
	case FadeOutHandleItem:
		if (arv->region.fade_out_active()) {
			items.push_back (MenuElem (_("Deactivate"), bind (slot (*arv, &AudioRegionView::set_fade_out_active), false)));
		} else {
			items.push_back (MenuElem (_("Activate"), bind (slot (*arv, &AudioRegionView::set_fade_out_active), true)));
		}
		
		items.push_back (SeparatorElem());
		
		items.push_back (MenuElem (_("Linear"), bind (slot (arv->region, &AudioRegion::set_fade_out_shape), AudioRegion::Linear)));
		items.push_back (MenuElem (_("Slowest"), bind (slot (arv->region, &AudioRegion::set_fade_out_shape), AudioRegion::Fast)));
		items.push_back (MenuElem (_("Slow"), bind (slot (arv->region, &AudioRegion::set_fade_out_shape), AudioRegion::LogB)));
		items.push_back (MenuElem (_("Fast"), bind (slot (arv->region, &AudioRegion::set_fade_out_shape), AudioRegion::LogA)));
		items.push_back (MenuElem (_("Fastest"), bind (slot (arv->region, &AudioRegion::set_fade_out_shape), AudioRegion::Slow)));

		break;
	default:
		fatal << _("programming error: ")
		      << X_("non-fade canvas item passed to popup_fade_context_menu()")
		      << endmsg;
		/*NOTREACHED*/
	}

	fade_context_menu.popup (button, time);
}

void
Editor::popup_track_context_menu (int button, int32_t time, ItemType item_type, bool with_selection, jack_nframes_t frame)
{
	using namespace Menu_Helpers;
	Menu* (Editor::*build_menu_function)(jack_nframes_t);
	Menu *menu;

	switch (item_type) {
	case RegionItem:
	case AudioRegionViewName:
	case AudioRegionViewNameHighlight:
		if (with_selection) {
			build_menu_function = &Editor::build_track_selection_context_menu;
		} else {
			build_menu_function = &Editor::build_track_region_context_menu;
		}
		break;

	case SelectionItem:
		if (with_selection) {
			build_menu_function = &Editor::build_track_selection_context_menu;
		} else {
			build_menu_function = &Editor::build_track_context_menu;
		}
		break;

	case CrossfadeViewItem:
		build_menu_function = &Editor::build_track_crossfade_context_menu;
		break;

	case StreamItem:
		if (clicked_audio_trackview->get_diskstream()) {
			build_menu_function = &Editor::build_track_context_menu;
		} else {
			build_menu_function = &Editor::build_track_bus_context_menu;
		}
		break;

	default:
		/* probably shouldn't happen but if it does, we don't care */
		return;
	}

	menu = (this->*build_menu_function)(frame);
	menu->set_name ("ArdourContextMenu");
	
	/* now handle specific situations */

	switch (item_type) {
	case RegionItem:
	case AudioRegionViewName:
	case AudioRegionViewNameHighlight:
		if (!with_selection) {
			if (region_edit_menu_split_item) {
				if (clicked_regionview && clicked_regionview->region.covers (edit_cursor->current_frame)) {
					region_edit_menu_split_item->set_sensitive (true);
				} else {
					region_edit_menu_split_item->set_sensitive (false);
				}
			}
			if (region_edit_menu_split_multichannel_item) {
				if (clicked_regionview && clicked_regionview->region.n_channels() > 1) {
					region_edit_menu_split_multichannel_item->set_sensitive (true);
				} else {
					region_edit_menu_split_multichannel_item->set_sensitive (false);
				}
			}
		}
		break;

	case SelectionItem:
		break;

	case CrossfadeViewItem:
		break;

	case StreamItem:
		break;

	default:
		/* probably shouldn't happen but if it does, we don't care */
		return;
	}

	if (clicked_audio_trackview && clicked_audio_trackview->audio_track()) {

		/* Bounce to disk */
		
		using namespace Menu_Helpers;
		MenuList& edit_items  = menu->items();
		
		edit_items.push_back (SeparatorElem());

		switch (clicked_audio_trackview->audio_track()->freeze_state()) {
		case AudioTrack::NoFreeze:
			edit_items.push_back (MenuElem (_("Freeze"), slot (*this, &Editor::freeze_route)));
			break;

		case AudioTrack::Frozen:
			edit_items.push_back (MenuElem (_("Unfreeze"), slot (*this, &Editor::unfreeze_route)));
			break;
			
		case AudioTrack::UnFrozen:
			edit_items.push_back (MenuElem (_("Freeze"), slot (*this, &Editor::freeze_route)));
			break;
		}

	}

	menu->popup (button, time);
}

Menu*
Editor::build_track_context_menu (jack_nframes_t ignored)
{
	using namespace Menu_Helpers;

 	MenuList& edit_items = track_context_menu.items();
	edit_items.clear();

	add_dstream_context_items (edit_items);
	return &track_context_menu;
}

Menu*
Editor::build_track_bus_context_menu (jack_nframes_t ignored)
{
	using namespace Menu_Helpers;

 	MenuList& edit_items = track_context_menu.items();
	edit_items.clear();

	add_bus_context_items (edit_items);
	return &track_context_menu;
}

Menu*
Editor::build_track_region_context_menu (jack_nframes_t frame)
{
	using namespace Menu_Helpers;
	MenuList& edit_items  = track_region_context_menu.items();
	edit_items.clear();

	AudioTimeAxisView* atv = dynamic_cast<AudioTimeAxisView*> (clicked_trackview);

	if (atv) {
		DiskStream* ds;
		Playlist* pl;
		
		if ((ds = atv->get_diskstream()) && ((pl = ds->playlist()))) {
			Playlist::RegionList* regions = pl->regions_at ((jack_nframes_t) floor ( (double)frame * ds->speed()));
			for (Playlist::RegionList::iterator i = regions->begin(); i != regions->end(); ++i) {
				add_region_context_items (atv->view, (*i), edit_items);
			}
			delete regions;
		}
	}

	add_dstream_context_items (edit_items);

	return &track_region_context_menu;
}

Menu*
Editor::build_track_crossfade_context_menu (jack_nframes_t frame)
{
	using namespace Menu_Helpers;
	MenuList& edit_items  = track_crossfade_context_menu.items();
	edit_items.clear ();

	AudioTimeAxisView* atv = dynamic_cast<AudioTimeAxisView*> (clicked_trackview);

	if (atv) {
		DiskStream* ds;
		Playlist* pl;
		AudioPlaylist* apl;

		if ((ds = atv->get_diskstream()) && ((pl = ds->playlist()) != 0) && ((apl = dynamic_cast<AudioPlaylist*> (pl)) != 0)) {

			Playlist::RegionList* regions = pl->regions_at (frame);
			AudioPlaylist::Crossfades xfades;

			apl->crossfades_at (frame, xfades);

			bool many = xfades.size() > 1;

			for (AudioPlaylist::Crossfades::iterator i = xfades.begin(); i != xfades.end(); ++i) {
				add_crossfade_context_items (atv->view, (*i), edit_items, many);
			}

			for (Playlist::RegionList::iterator i = regions->begin(); i != regions->end(); ++i) {
				add_region_context_items (atv->view, (*i), edit_items);
			}

			delete regions;
		}
	}

	add_dstream_context_items (edit_items);

	return &track_crossfade_context_menu;
}

Menu*
Editor::build_track_selection_context_menu (jack_nframes_t ignored)
{
	using namespace Menu_Helpers;
	MenuList& edit_items  = track_selection_context_menu.items();
	edit_items.clear ();

	add_selection_context_items (edit_items);
	add_dstream_context_items (edit_items);

	return &track_selection_context_menu;
}

void
Editor::add_crossfade_context_items (StreamView* view, Crossfade* xfade, Menu_Helpers::MenuList& edit_items, bool many)
{
	using namespace Menu_Helpers;
	Menu     *xfade_menu = manage (new Menu);
	MenuList& items       = xfade_menu->items();
	xfade_menu->set_name ("ArdourContextMenu");
	string str;

	if (xfade->active()) {
		str = _("Mute");
	} else { 
		str = _("Unmute");
	}

	items.push_back (MenuElem (str, bind (slot (*this, &Editor::toggle_xfade_active), xfade)));
	items.push_back (MenuElem (_("Edit"), bind (slot (*this, &Editor::edit_xfade), xfade)));

	if (xfade->can_follow_overlap()) {

		if (xfade->following_overlap()) {
			str = _("Convert to short");
		} else {
			str = _("Convert to full");
		}

		items.push_back (MenuElem (str, bind (slot (*this, &Editor::toggle_xfade_length), xfade)));
	}

	if (many) {
		str = xfade->out().name();
		str += "->";
		str += xfade->in().name();
	} else {
		str = _("Crossfade");
	}

	edit_items.push_back (MenuElem (str, *xfade_menu));
	edit_items.push_back (SeparatorElem());
}

void
Editor::xfade_edit_left_region ()
{
	if (clicked_crossfadeview) {
		clicked_crossfadeview->left_view.show_region_editor ();
	}
}

void
Editor::xfade_edit_right_region ()
{
	if (clicked_crossfadeview) {
		clicked_crossfadeview->right_view.show_region_editor ();
	}
}

void
Editor::add_region_context_items (StreamView* sv, Region* region, Menu_Helpers::MenuList& edit_items)
{
	using namespace Menu_Helpers;
	Menu     *region_menu = manage (new Menu);
	MenuList& items       = region_menu->items();
	region_menu->set_name ("ArdourContextMenu");
	
	AudioRegion* ar = 0;

	if (region) {
		ar = dynamic_cast<AudioRegion*> (region);
	}

	/* when this particular menu pops up, make the relevant region 
	   become selected.
	*/

	region_menu->map_event.connect (bind (slot (*this, &Editor::set_selected_regionview_from_map_event), sv, region));

	items.push_back (MenuElem (_("Popup region editor"), slot (*this, &Editor::edit_region)));
	items.push_back (MenuElem (_("Raise to top layer"), slot (*this, &Editor::raise_region_to_top)));
	items.push_back (MenuElem (_("Lower to bottom layer"), slot  (*this, &Editor::lower_region_to_bottom)));
	items.push_back (SeparatorElem());
	items.push_back (MenuElem (_("Define sync point"), slot (*this, &Editor::set_region_sync_from_edit_cursor)));
	items.push_back (MenuElem (_("Remove sync point"), slot (*this, &Editor::remove_region_sync)));
	items.push_back (SeparatorElem());

	items.push_back (MenuElem (_("Audition"), slot (*this, &Editor::audition_selected_region)));
	items.push_back (MenuElem (_("Export"), slot (*this, &Editor::export_region)));
	items.push_back (MenuElem (_("Bounce"), slot (*this, &Editor::bounce_region_selection)));
	items.push_back (SeparatorElem());

	/* XXX hopefully this nonsense will go away with SigC++ 2.X, where the compiler
	   might be able to figure out which overloaded member function to use in
	   a bind() call ...
	*/

	void (Editor::*type_A_pmf)(void (Region::*pmf)(bool), bool) = &Editor::region_selection_op;

	items.push_back (MenuElem (_("Lock"), bind (slot (*this, type_A_pmf), &Region::set_locked, true)));
	items.push_back (MenuElem (_("Unlock"), bind (slot (*this, type_A_pmf), &Region::set_locked, false)));
	items.push_back (SeparatorElem());

	if (region->muted()) {
		items.push_back (MenuElem (_("Unmute"), bind (slot (*this, type_A_pmf), &Region::set_muted, false)));
	} else {
		items.push_back (MenuElem (_("Mute"), bind (slot (*this, type_A_pmf), &Region::set_muted, true)));
	}
	items.push_back (SeparatorElem());

	items.push_back (MenuElem (_("Original position"), slot (*this, &Editor::naturalize)));
	items.push_back (SeparatorElem());


	if (ar) {

		items.push_back (MenuElem (_("Toggle envelope visibility"), slot (*this, &Editor::toggle_gain_envelope_visibility)));
		items.push_back (MenuElem (_("Toggle envelope active"), slot (*this, &Editor::toggle_gain_envelope_active)));
		items.push_back (SeparatorElem());

		if (ar->scale_amplitude() != 1.0f) {
			items.push_back (MenuElem (_("DeNormalize"), slot (*this, &Editor::denormalize_region)));
		} else {
			items.push_back (MenuElem (_("Normalize"), slot (*this, &Editor::normalize_region)));
		}
	}
	items.push_back (MenuElem (_("Reverse"), slot (*this, &Editor::reverse_region)));
	items.push_back (SeparatorElem());

	/* Nudge region */

	Menu *nudge_menu = manage (new Menu());
	MenuList& nudge_items = nudge_menu->items();
	nudge_menu->set_name ("ArdourContextMenu");
	
	nudge_items.push_back (MenuElem (_("Nudge fwd"), (bind (slot (*this, &Editor::nudge_forward), false))));
	nudge_items.push_back (MenuElem (_("Nudge bwd"), (bind (slot (*this, &Editor::nudge_backward), false))));
	nudge_items.push_back (MenuElem (_("Nudge fwd by capture offset"), (slot (*this, &Editor::nudge_forward_capture_offset))));
	nudge_items.push_back (MenuElem (_("Nudge bwd by capture offset"), (slot (*this, &Editor::nudge_backward_capture_offset))));

	items.push_back (MenuElem (_("Nudge"), *nudge_menu));
	items.push_back (SeparatorElem());

	Menu *trim_menu = manage (new Menu);
	MenuList& trim_items = trim_menu->items();
	trim_menu->set_name ("ArdourContextMenu");
	
	trim_items.push_back (MenuElem (_("Start to edit cursor"), slot (*this, &Editor::trim_region_from_edit_cursor)));
	trim_items.push_back (MenuElem (_("Edit cursor to end"), slot (*this, &Editor::trim_region_to_edit_cursor)));
			     
	items.push_back (MenuElem (_("Trim"), *trim_menu));
	items.push_back (SeparatorElem());

	items.push_back (MenuElem (_("Split"), (slot (*this, &Editor::split_region))));
	region_edit_menu_split_item = items.back();

	items.push_back (MenuElem (_("Make mono regions"), (slot (*this, &Editor::split_multichannel_region))));
	region_edit_menu_split_multichannel_item = items.back();

	items.push_back (MenuElem (_("Duplicate"), (bind (slot (*this, &Editor::duplicate_dialog), true))));
	items.push_back (MenuElem (_("Fill Track"), (slot (*this, &Editor::region_fill_track))));
	items.push_back (SeparatorElem());
	items.push_back (MenuElem (_("Remove"), slot (*this, &Editor::remove_clicked_region)));
	items.push_back (SeparatorElem());
	items.push_back (MenuElem (_("Destroy"), slot (*this, &Editor::destroy_clicked_region)));

	/* OK, stick the region submenu at the top of the list, and then add
	   the standard items.
	*/

	/* we have to hack up the region name because "_" has a special
	   meaning for menu titles.
	*/

	string::size_type pos = 0;
	string menu_item_name = region->name();

	while ((pos = menu_item_name.find ("_", pos)) != string::npos) {
		menu_item_name.replace (pos, 1, "__");
		pos += 2;
	}
	
	edit_items.push_back (MenuElem (menu_item_name, *region_menu));
	edit_items.push_back (SeparatorElem());
}

void
Editor::add_selection_context_items (Menu_Helpers::MenuList& edit_items)
{
	using namespace Menu_Helpers;
	Menu     *selection_menu = manage (new Menu);
	MenuList& items       = selection_menu->items();
	selection_menu->set_name ("ArdourContextMenu");

	items.push_back (MenuElem (_("Play range"), slot (*this, &Editor::play_selection)));
	items.push_back (MenuElem (_("Loop range"), slot (*this, &Editor::set_route_loop_selection)));
	items.push_back (SeparatorElem());
	items.push_back (MenuElem (_("Create chunk from range"), slot (*this, &Editor::name_selection)));
	items.push_back (SeparatorElem());
	items.push_back (MenuElem (_("Create Region"), slot (*this, &Editor::new_region_from_selection)));
	items.push_back (MenuElem (_("Separate Region"), slot (*this, &Editor::separate_region_from_selection)));
	items.push_back (MenuElem (_("Crop Region to range"), slot (*this, &Editor::crop_region_to_selection)));
	items.push_back (MenuElem (_("Bounce range"), slot (*this, &Editor::bounce_range_selection)));
	items.push_back (SeparatorElem());
	items.push_back (MenuElem (_("Duplicate"), bind (slot (*this, &Editor::duplicate_dialog), false)));
	items.push_back (SeparatorElem());
	items.push_back (MenuElem (_("Export"), slot (*this, &Editor::export_selection)));
	items.push_back (SeparatorElem());
	items.push_back (MenuElem (_("Fill range w/Region"), slot (*this, &Editor::region_fill_selection)));
	
	edit_items.push_back (MenuElem (_("Range"), *selection_menu));
	edit_items.push_back (SeparatorElem());
}

void
Editor::add_dstream_context_items (Menu_Helpers::MenuList& edit_items)
{
	using namespace Menu_Helpers;

	/* Playback */

	Menu *play_menu = manage (new Menu);
	MenuList& play_items = play_menu->items();
	play_menu->set_name ("ArdourContextMenu");
	
	play_items.push_back (MenuElem (_("Play from edit cursor")));
	play_items.push_back (MenuElem (_("Play from start"), slot (*this, &Editor::play_from_start)));
	play_items.push_back (MenuElem (_("Play region"), slot (*this, &Editor::play_selected_region)));
	play_items.push_back (SeparatorElem());
	play_items.push_back (MenuElem (_("Loop Region"), slot (*this, &Editor::loop_selected_region)));
	
	edit_items.push_back (MenuElem (_("Play"), *play_menu));

	/* Selection */

	Menu *select_menu = manage (new Menu);
	MenuList& select_items = select_menu->items();
	select_menu->set_name ("ArdourContextMenu");
	
	select_items.push_back (MenuElem (_("Select All in track"), bind (slot (*this, &Editor::select_all_in_track), false)));
	select_items.push_back (MenuElem (_("Select All"), bind (slot (*this, &Editor::select_all), false)));
	select_items.push_back (MenuElem (_("Invert in track"), slot (*this, &Editor::invert_selection_in_track)));
	select_items.push_back (MenuElem (_("Invert"), slot (*this, &Editor::invert_selection)));
	select_items.push_back (SeparatorElem());
	select_items.push_back (MenuElem (_("Select loop range"), slot (*this, &Editor::set_selection_from_loop)));
	select_items.push_back (MenuElem (_("Select punch range"), slot (*this, &Editor::set_selection_from_punch)));
	select_items.push_back (SeparatorElem());

	edit_items.push_back (MenuElem (_("Select"), *select_menu));

	/* Cut-n-Paste */

	Menu *cutnpaste_menu = manage (new Menu);
	MenuList& cutnpaste_items = cutnpaste_menu->items();
	cutnpaste_menu->set_name ("ArdourContextMenu");
	
	cutnpaste_items.push_back (MenuElem (_("Cut"), slot (*this, &Editor::cut)));
	cutnpaste_items.push_back (MenuElem (_("Copy"), slot (*this, &Editor::copy)));
	cutnpaste_items.push_back (MenuElem (_("Paste at edit cursor"), bind (slot (*this, &Editor::paste), 1.0f)));
	cutnpaste_items.push_back (MenuElem (_("Paste at mouse"), slot (*this, &Editor::mouse_paste)));

	cutnpaste_items.push_back (SeparatorElem());

	cutnpaste_items.push_back (MenuElem (_("Align"), bind (slot (*this, &Editor::align), ARDOUR::SyncPoint)));
	cutnpaste_items.push_back (MenuElem (_("Align Relative"), bind (slot (*this, &Editor::align_relative), ARDOUR::SyncPoint)));

	cutnpaste_items.push_back (SeparatorElem());

	cutnpaste_items.push_back (MenuElem (_("Insert chunk"), bind (slot (*this, &Editor::paste_named_selection), 1.0f)));

	cutnpaste_items.push_back (SeparatorElem());

	cutnpaste_items.push_back (MenuElem (_("New Region from range"), slot (*this, &Editor::new_region_from_selection)));
	cutnpaste_items.push_back (MenuElem (_("Separate Range"), slot (*this, &Editor::separate_region_from_selection)));

	edit_items.push_back (MenuElem (_("Edit"), *cutnpaste_menu));

	/* Adding new material */
	
	Menu *import_menu = manage (new Menu());
	MenuList& import_items = import_menu->items();
	import_menu->set_name ("ArdourContextMenu");
	
	import_items.push_back (MenuElem (_("Insert Region"), bind (slot (*this, &Editor::insert_region_list_selection), 1.0f)));
	import_items.push_back (MenuElem (_("Insert external sndfile"), bind (slot (*this, &Editor::insert_sndfile), false)));

	edit_items.push_back (MenuElem (_("Import"), *import_menu));

	/* Nudge track */

	Menu *nudge_menu = manage (new Menu());
	MenuList& nudge_items = nudge_menu->items();
	nudge_menu->set_name ("ArdourContextMenu");
	
	edit_items.push_back (SeparatorElem());
	nudge_items.push_back (MenuElem (_("Nudge entire track fwd"), (bind (slot (*this, &Editor::nudge_track), false, true))));
	nudge_items.push_back (MenuElem (_("Nudge track after edit cursor fwd"), (bind (slot (*this, &Editor::nudge_track), true, true))));
	nudge_items.push_back (MenuElem (_("Nudge entire track bwd"), (bind (slot (*this, &Editor::nudge_track), false, false))));
	nudge_items.push_back (MenuElem (_("Nudge track after edit cursor bwd"), (bind (slot (*this, &Editor::nudge_track), true, false))));

	edit_items.push_back (MenuElem (_("Nudge"), *nudge_menu));
}

void
Editor::add_bus_context_items (Menu_Helpers::MenuList& edit_items)
{
	using namespace Menu_Helpers;

	/* Playback */

	Menu *play_menu = manage (new Menu);
	MenuList& play_items = play_menu->items();
	play_menu->set_name ("ArdourContextMenu");
	
	play_items.push_back (MenuElem (_("Play from edit cursor")));
	play_items.push_back (MenuElem (_("Play from start"), slot (*this, &Editor::play_from_start)));
	edit_items.push_back (MenuElem (_("Play"), *play_menu));

	/* Selection */

	Menu *select_menu = manage (new Menu);
	MenuList& select_items = select_menu->items();
	select_menu->set_name ("ArdourContextMenu");
	
	select_items.push_back (MenuElem (_("Select All in track"), bind (slot (*this, &Editor::select_all_in_track), false)));
	select_items.push_back (MenuElem (_("Select All"), bind (slot (*this, &Editor::select_all), false)));
	select_items.push_back (MenuElem (_("Invert in track"), slot (*this, &Editor::invert_selection_in_track)));
	select_items.push_back (MenuElem (_("Invert"), slot (*this, &Editor::invert_selection)));
	select_items.push_back (SeparatorElem());
	select_items.push_back (MenuElem (_("Select loop range"), slot (*this, &Editor::set_selection_from_loop)));
	select_items.push_back (MenuElem (_("Select punch range"), slot (*this, &Editor::set_selection_from_punch)));
	select_items.push_back (SeparatorElem());

	edit_items.push_back (MenuElem (_("Select"), *select_menu));

	/* Cut-n-Paste */

	Menu *cutnpaste_menu = manage (new Menu);
	MenuList& cutnpaste_items = cutnpaste_menu->items();
	cutnpaste_menu->set_name ("ArdourContextMenu");
	
	cutnpaste_items.push_back (MenuElem (_("Cut"), slot (*this, &Editor::cut)));
	cutnpaste_items.push_back (MenuElem (_("Copy"), slot (*this, &Editor::copy)));
	cutnpaste_items.push_back (MenuElem (_("Paste"), bind (slot (*this, &Editor::paste), 1.0f)));

	Menu *nudge_menu = manage (new Menu());
	MenuList& nudge_items = nudge_menu->items();
	nudge_menu->set_name ("ArdourContextMenu");
	
	edit_items.push_back (SeparatorElem());
	nudge_items.push_back (MenuElem (_("Nudge entire track fwd"), (bind (slot (*this, &Editor::nudge_track), false, true))));
	nudge_items.push_back (MenuElem (_("Nudge track after edit cursor fwd"), (bind (slot (*this, &Editor::nudge_track), true, true))));
	nudge_items.push_back (MenuElem (_("Nudge entire track bwd"), (bind (slot (*this, &Editor::nudge_track), false, false))));
	nudge_items.push_back (MenuElem (_("Nudge track after edit cursor bwd"), (bind (slot (*this, &Editor::nudge_track), true, false))));

	edit_items.push_back (MenuElem (_("Nudge"), *nudge_menu));
}

/* CURSOR SETTING AND MARKS AND STUFF */

void
Editor::set_snap_to (SnapType st)
{
	snap_type = st;
	vector<string> txt = internationalize (snap_type_strings);
	snap_type_selector.get_entry()->set_text (txt[(int)st]);

	instant_save ();

	switch (snap_type) {
	case SnapToAThirtysecondBeat:
	case SnapToASixteenthBeat:
	case SnapToAEighthBeat:
	case SnapToAQuarterBeat:
	case SnapToAThirdBeat:
                update_tempo_based_rulers ();
	default:
		/* relax */
		break;
    }
}

void
Editor::set_snap_mode (SnapMode mode)
{
	snap_mode = mode;
	vector<string> txt = internationalize (snap_mode_strings);
	snap_mode_selector.get_entry()->set_text (txt[(int)mode]);

	instant_save ();
}

void
Editor::add_location_from_selection ()
{
	if (selection->time.empty()) {
		return;
	}

	if (session == 0 || clicked_trackview == 0) {
		return;
	}

	jack_nframes_t start = selection->time[clicked_selection].start;
	jack_nframes_t end = selection->time[clicked_selection].end;

	Location *location = new Location (start, end, "selection");

	session->begin_reversible_command (_("add marker"));
	session->add_undo (session->locations()->get_memento());
	session->locations()->add (location, true);
	session->add_redo_no_execute (session->locations()->get_memento());
	session->commit_reversible_command ();
}

void
Editor::add_location_from_playhead_cursor ()
{
	jack_nframes_t where = session->audible_frame();
	
	Location *location = new Location (where, where, "mark", Location::IsMark);
	session->begin_reversible_command (_("add marker"));
	session->add_undo (session->locations()->get_memento());
	session->locations()->add (location, true);
	session->add_redo_no_execute (session->locations()->get_memento());
	session->commit_reversible_command ();
}


int
Editor::set_state (const XMLNode& node)
{
	const XMLProperty* prop;
	XMLNode* geometry;
	int x, y, width, height, xoff, yoff;

	if ((geometry = find_named_node (node, "geometry")) == 0) {

		width = default_width;
		height = default_height;
		x = 1;
		y = 1;
		xoff = 0;
		yoff = 21;

	} else {

		width = atoi(geometry->property("x_size")->value());
		height = atoi(geometry->property("y_size")->value());
		x = atoi(geometry->property("x_pos")->value());
		y = atoi(geometry->property("y_pos")->value());
		xoff = atoi(geometry->property("x_off")->value());
		yoff = atoi(geometry->property("y_off")->value());
	}

	set_default_size(width, height);
	set_uposition(x, y-yoff);

	if ((prop = node.property ("zoom-focus"))) {
		set_zoom_focus ((ZoomFocus) atoi (prop->value()));
	}

	if ((prop = node.property ("zoom"))) {
		set_frames_per_unit (atof (prop->value()));
	}

	if ((prop = node.property ("snap-to"))) {
		set_snap_to ((SnapType) atoi (prop->value()));
	}

	if ((prop = node.property ("snap-mode"))) {
		set_snap_mode ((SnapMode) atoi (prop->value()));
	}

	if ((prop = node.property ("show-waveforms"))) {
		bool yn = (prop->value() == "yes");
		_show_waveforms = !yn;
		set_show_waveforms (yn);
	}

	if ((prop = node.property ("show-waveforms-recording"))) {
		bool yn = (prop->value() == "yes");
		_show_waveforms_recording = !yn;
		set_show_waveforms_recording (yn);
	}
	
	if ((prop = node.property ("show-measures"))) {
		bool yn = (prop->value() == "yes");
		_show_measures = !yn;
		set_show_measures (yn);
	}

	if ((prop = node.property ("follow-playhead"))) {
		bool yn = (prop->value() == "yes");
		_follow_playhead = !yn;
		set_follow_playhead (yn);
	}

	if ((prop = node.property ("xfades-visible"))) {
		bool yn = (prop->value() == "yes");
		_xfade_visibility = !yn;
		set_xfade_visibility (yn);
	}

	if ((prop = node.property ("region-list-sort-type"))) {
		region_list_sort_type = (Editing::RegionListSortType) -1; /* force change */
		reset_region_list_sort_type(str2regionlistsorttype(prop->value()));
	}

	if ((prop = node.property ("mouse-mode"))) {
		MouseMode m = str2mousemode(prop->value());
		mouse_mode = MouseMode ((int) m + 1); /* lie, force mode switch */
		set_mouse_mode (m, true);
	} else {
		mouse_mode = MouseGain; /* lie, to force the mode switch */
		set_mouse_mode (MouseObject, true);
	}

	if ((prop = node.property ("editor-mixer-button"))) {
		ARDOUR_UI::instance()->editor_mixer_button().set_active(prop->value() == "yes");
	}

	return 0;
}

XMLNode&
Editor::get_state ()
{
	XMLNode* node = new XMLNode ("Editor");
	char buf[32];

	if (is_realized()) {
		Gdk_Window win = get_window();
		
		int x, y, xoff, yoff, width, height;
		win.get_root_origin(x, y);
		win.get_position(xoff, yoff);
		win.get_size(width, height);
		
		XMLNode* geometry = new XMLNode ("geometry");
		char buf[32];
		snprintf(buf, sizeof(buf), "%d", width);
		geometry->add_property("x_size", string(buf));
		snprintf(buf, sizeof(buf), "%d", height);
		geometry->add_property("y_size", string(buf));
		snprintf(buf, sizeof(buf), "%d", x);
		geometry->add_property("x_pos", string(buf));
		snprintf(buf, sizeof(buf), "%d", y);
		geometry->add_property("y_pos", string(buf));
		snprintf(buf, sizeof(buf), "%d", xoff);
		geometry->add_property("x_off", string(buf));
		snprintf(buf, sizeof(buf), "%d", yoff);
		geometry->add_property("y_off", string(buf));
		snprintf(buf,sizeof(buf), "%d",gtk_paned_get_position (static_cast<Paned*>(&canvas_region_list_pane)->gtkobj()));
		geometry->add_property("canvas_region_list_pane_pos", string(buf));
		snprintf(buf,sizeof(buf), "%d", gtk_paned_get_position (static_cast<Paned*>(&track_list_canvas_pane)->gtkobj()));
		geometry->add_property("track_list_canvas_pane_pos", string(buf));
		snprintf(buf,sizeof(buf), "%d", gtk_paned_get_position (static_cast<Paned*>(&region_selection_vpane)->gtkobj()));
		geometry->add_property("region_selection_pane_pos", string(buf));
		snprintf(buf,sizeof(buf), "%d", gtk_paned_get_position (static_cast<Paned*>(&route_group_vpane)->gtkobj()));
		geometry->add_property("route_group_pane_pos", string(buf));

		node->add_child_nocopy (*geometry);
	}

	snprintf (buf, sizeof(buf), "%d", (int) zoom_focus);
	node->add_property ("zoom-focus", buf);
	snprintf (buf, sizeof(buf), "%f", frames_per_unit);
	node->add_property ("zoom", buf);
	snprintf (buf, sizeof(buf), "%d", (int) snap_type);
	node->add_property ("snap-to", buf);
	snprintf (buf, sizeof(buf), "%d", (int) snap_mode);
	node->add_property ("snap-mode", buf);

	node->add_property ("show-waveforms", _show_waveforms ? "yes" : "no");
	node->add_property ("show-waveforms-recording", _show_waveforms_recording ? "yes" : "no");
	node->add_property ("show-measures", _show_measures ? "yes" : "no");
	node->add_property ("follow-playhead", _follow_playhead ? "yes" : "no");
	node->add_property ("xfades-visible", _xfade_visibility ? "yes" : "no");
	node->add_property ("region-list-sort-type", enum2str(region_list_sort_type));
	node->add_property ("mouse-mode", enum2str(mouse_mode));
	node->add_property ("editor-mixer-button", ARDOUR_UI::instance()->editor_mixer_button().get_active() ? "yes" : "no");
	
	return *node;
}



TimeAxisView *
Editor::trackview_by_y_position (double y)
{
	TrackViewList::iterator iter;
	TimeAxisView *tv;

	for (iter = track_views.begin(); iter != track_views.end(); ++iter) {

		tv = *iter;

		if (tv->hidden()) {
			continue;
		}

		if (tv->y_position <= y && y < ((tv->y_position + tv->height + track_spacing))) {
			return tv;
		}
	}

	return 0;
}

void
Editor::snap_to (jack_nframes_t& start, int32_t direction, bool for_mark)
{
	Location* before = 0;
	Location* after = 0;

	if (!session) {
		return;
	}

	const jack_nframes_t one_second = session->frame_rate();
	const jack_nframes_t one_minute = session->frame_rate() * 60;

	jack_nframes_t presnap = start;

	switch (snap_type) {
	case SnapToFrame:
		break;

	case SnapToCDFrame:
		if (direction) {
			start = (jack_nframes_t) ceil ((double) start / (one_second / 75)) * (one_second / 75);
		} else {
			start = (jack_nframes_t) floor ((double) start / (one_second / 75)) * (one_second / 75);
		}
		break;
	case SnapToSMPTEFrame:
		if (direction) {
			start = (jack_nframes_t) (ceil ((double) start / session->frames_per_smpte_frame()) * session->frames_per_smpte_frame());
		} else {
			start = (jack_nframes_t) (floor ((double) start / session->frames_per_smpte_frame()) *  session->frames_per_smpte_frame());
		}
		break;

	case SnapToSMPTESeconds:
		if (session->smpte_offset_negative())
		{
			start += session->smpte_offset ();
		} else {
			start -= session->smpte_offset ();
		}    
		if (direction > 0) {
			start = (jack_nframes_t) ceil ((double) start / one_second) * one_second;
		} else {
			start = (jack_nframes_t) floor ((double) start / one_second) * one_second;
		}
		
		if (session->smpte_offset_negative())
		{
			start -= session->smpte_offset ();
		} else {
			start += session->smpte_offset ();
		}
		break;
		
	case SnapToSMPTEMinutes:
		if (session->smpte_offset_negative())
		{
			start += session->smpte_offset ();
		} else {
			start -= session->smpte_offset ();
		}
		if (direction) {
			start = (jack_nframes_t) ceil ((double) start / one_minute) * one_minute;
		} else {
			start = (jack_nframes_t) floor ((double) start / one_minute) * one_minute;
		}
		if (session->smpte_offset_negative())
		{
			start -= session->smpte_offset ();
		} else {
			start += session->smpte_offset ();
		}
		break;
		
	case SnapToSeconds:
		if (direction) {
			start = (jack_nframes_t) ceil ((double) start / one_second) * one_second;
		} else {
			start = (jack_nframes_t) floor ((double) start / one_second) * one_second;
		}
		break;
		
	case SnapToMinutes:
		if (direction) {
			start = (jack_nframes_t) ceil ((double) start / one_minute) * one_minute;
		} else {
			start = (jack_nframes_t) floor ((double) start / one_minute) * one_minute;
		}
		break;

	case SnapToBar:
		start = session->tempo_map().round_to_bar (start, direction);
		break;

	case SnapToBeat:
		start = session->tempo_map().round_to_beat (start, direction);
		break;

	case SnapToAThirtysecondBeat:
                start = session->tempo_map().round_to_beat_subdivision (start, 32);
                break;

	case SnapToASixteenthBeat:
                start = session->tempo_map().round_to_beat_subdivision (start, 16);
                break;

	case SnapToAEighthBeat:
                start = session->tempo_map().round_to_beat_subdivision (start, 8);
                break;

	case SnapToAQuarterBeat:
                start = session->tempo_map().round_to_beat_subdivision (start, 4);
                break;

        case SnapToAThirdBeat:
                start = session->tempo_map().round_to_beat_subdivision (start, 3);
                break;

	case SnapToEditCursor:
		start = edit_cursor->current_frame;
		break;

	case SnapToMark:
		if (for_mark) {
			return;
		}

		before = session->locations()->first_location_before (start);
		after = session->locations()->first_location_after (start);

		if (direction < 0) {
			if (before) {
				start = before->start();
			} else {
				start = 0;
			}
		} else if (direction > 0) {
			if (after) {
				start = after->start();
			} else {
				start = session->current_end_frame();
			}
		} else {
			if (before) {
				if (after) {
					/* find nearest of the two */
					if ((start - before->start()) < (after->start() - start)) {
						start = before->start();
					} else {
						start = after->start();
					}
				} else {
					start = before->start();
				}
			} else if (after) {
				start = after->start();
			} else {
				/* relax */
			}
		}
		break;

	case SnapToRegionStart:
	case SnapToRegionEnd:
	case SnapToRegionSync:
	case SnapToRegionBoundary:
		if (!region_boundary_cache.empty()) {
			vector<jack_nframes_t>::iterator i;

			if (direction > 0) {
				i = std::upper_bound (region_boundary_cache.begin(), region_boundary_cache.end(), start);
			} else {
				i = std::lower_bound (region_boundary_cache.begin(), region_boundary_cache.end(), start);
			}
			
			if (i != region_boundary_cache.end()) {
				start = *i;
			} else {
				start = region_boundary_cache.back();
			}
		}
		break;
	}

	switch (snap_mode) {
	case SnapNormal:
		return;			
		
	case SnapMagnetic:
		
		if (presnap > start) {
			if (presnap > (start + unit_to_frame(snap_threshold))) {
				start = presnap;
			}
			
		} else if (presnap < start) {
			if (presnap < (start - unit_to_frame(snap_threshold))) {
				start = presnap;
			}
		}
		
	default:
		return;
		
	}
}

void
Editor::setup_toolbar ()
{
	nstring pixmap_path;
	vector<ToggleButton *> mouse_mode_buttons;

	mouse_mode_buttons.push_back (&mouse_move_button);
	mouse_mode_buttons.push_back (&mouse_select_button);
	mouse_mode_buttons.push_back (&mouse_gain_button);
	mouse_mode_buttons.push_back (&mouse_zoom_button);
	mouse_mode_buttons.push_back (&mouse_timefx_button);
	mouse_mode_buttons.push_back (&mouse_audition_button);
	mouse_mode_button_set = new GroupedButtons (mouse_mode_buttons);

	mouse_mode_button_table.set_homogeneous (true);
	mouse_mode_button_table.set_col_spacings (2);
	mouse_mode_button_table.set_row_spacings (2);
	mouse_mode_button_table.set_border_width (5);

	mouse_mode_button_table.attach (mouse_move_button, 0, 1, 0, 1);
	mouse_mode_button_table.attach (mouse_select_button, 1, 2, 0, 1);
	mouse_mode_button_table.attach (mouse_zoom_button, 2, 3, 0, 1);
 
	mouse_mode_button_table.attach (mouse_gain_button, 0, 1, 1, 2);
	mouse_mode_button_table.attach (mouse_timefx_button, 1, 2, 1, 2);
	mouse_mode_button_table.attach (mouse_audition_button, 2, 3, 1, 2);

	mouse_mode_tearoff = manage (new TearOff (mouse_mode_button_table));
	mouse_mode_tearoff->set_name ("MouseModeBase");

	mouse_mode_tearoff->Detach.connect (bind (slot (*this, &Editor::detach_tearoff), static_cast<Gtk::Box*>(&toolbar_hbox), 
						  static_cast<Gtk::Widget*>(&mouse_mode_button_table)));
	mouse_mode_tearoff->Attach.connect (bind (slot (*this, &Editor::reattach_tearoff), static_cast<Gtk::Box*> (&toolbar_hbox), 
						  static_cast<Gtk::Widget*> (&mouse_mode_button_table), 1));

	mouse_move_button.set_name ("MouseModeButton");
	mouse_select_button.set_name ("MouseModeButton");
	mouse_gain_button.set_name ("MouseModeButton");
	mouse_zoom_button.set_name ("MouseModeButton");
	mouse_timefx_button.set_name ("MouseModeButton");
	mouse_audition_button.set_name ("MouseModeButton");

	ARDOUR_UI::instance()->tooltips().set_tip (mouse_move_button, _("select/move objects"));
	ARDOUR_UI::instance()->tooltips().set_tip (mouse_select_button, _("select/move ranges"));
	ARDOUR_UI::instance()->tooltips().set_tip (mouse_gain_button, _("draw gain automation"));
	ARDOUR_UI::instance()->tooltips().set_tip (mouse_zoom_button, _("select zoom range"));
	ARDOUR_UI::instance()->tooltips().set_tip (mouse_timefx_button, _("stretch/shrink regions"));
	ARDOUR_UI::instance()->tooltips().set_tip (mouse_audition_button, _("listen to specific regions"));

	mouse_move_button.unset_flags (GTK_CAN_FOCUS);
	mouse_select_button.unset_flags (GTK_CAN_FOCUS);
	mouse_gain_button.unset_flags (GTK_CAN_FOCUS);
	mouse_zoom_button.unset_flags (GTK_CAN_FOCUS);
	mouse_timefx_button.unset_flags (GTK_CAN_FOCUS);
	mouse_audition_button.unset_flags (GTK_CAN_FOCUS);

	mouse_select_button.toggled.connect (bind (slot (*this, &Editor::mouse_mode_toggled), Editing::MouseRange));
	mouse_select_button.button_release_event.connect (slot (*this, &Editor::mouse_select_button_release));

	mouse_move_button.toggled.connect (bind (slot (*this, &Editor::mouse_mode_toggled), Editing::MouseObject));
	mouse_gain_button.toggled.connect (bind (slot (*this, &Editor::mouse_mode_toggled), Editing::MouseGain));
	mouse_zoom_button.toggled.connect (bind (slot (*this, &Editor::mouse_mode_toggled), Editing::MouseZoom));
	mouse_timefx_button.toggled.connect (bind (slot (*this, &Editor::mouse_mode_toggled), Editing::MouseTimeFX));
	mouse_audition_button.toggled.connect (bind (slot (*this, &Editor::mouse_mode_toggled), Editing::MouseAudition));

	// mouse_move_button.set_active (true);

	/* automation control */

	global_automation_button.set_name ("MouseModeButton");
	automation_mode_button.set_name ("MouseModeButton");

	automation_box.set_spacing (2);
	automation_box.set_border_width (2);
	automation_box.pack_start (global_automation_button, false, false);
	automation_box.pack_start (automation_mode_button, false, false);

	/* Edit mode */

	edit_mode_label.set_name ("ToolBarLabel");

	edit_mode_selector.set_name ("EditModeSelector");
	edit_mode_selector.get_entry()->set_name ("EditModeSelector");
	edit_mode_selector.get_popwin()->set_name ("EditModeSelector");

	edit_mode_box.set_spacing (3);
	edit_mode_box.set_border_width (3);

	/* XXX another disgusting hack because of the way combo boxes size themselves */

	Gtkmmext::set_usize_to_display_given_text (*edit_mode_selector.get_entry(), "EdgtMode", 2, 10);
	edit_mode_selector.set_popdown_strings (internationalize (edit_mode_strings));
	edit_mode_selector.set_value_in_list (true, false);

	edit_mode_box.pack_start (edit_mode_label, false, false);
	edit_mode_box.pack_start (edit_mode_selector, false, false);

	edit_mode_selector.get_popwin()->unmap_event.connect (slot (*this, &Editor::edit_mode_selection_done));

	/* Snap Type */

	snap_type_label.set_name ("ToolBarLabel");

	snap_type_selector.set_name ("SnapTypeSelector");
	snap_type_selector.get_entry()->set_name ("SnapTypeSelector");
	snap_type_selector.get_popwin()->set_name ("SnapTypeSelector");

	snap_type_box.set_spacing (3);
	snap_type_box.set_border_width (3);

	/* XXX another disgusting hack because of the way combo boxes size themselves */

	const guint32 FUDGE = 10; // Combo's are stupid - they steal space from the entry for the button
	Gtkmmext::set_usize_to_display_given_text (*snap_type_selector.get_entry(), "Region bounds", 2+FUDGE, 10);
	snap_type_selector.set_popdown_strings (internationalize (snap_type_strings));
	snap_type_selector.set_value_in_list (true, false);

	snap_type_box.pack_start (snap_type_label, false, false);
	snap_type_box.pack_start (snap_type_selector, false, false);

	snap_type_selector.get_popwin()->unmap_event.connect (slot (*this, &Editor::snap_type_selection_done));

	/* Snap mode, not snap type */

	snap_mode_label.set_name ("ToolBarLabel");

	snap_mode_selector.set_name ("SnapModeSelector");
	snap_mode_selector.get_entry()->set_name ("SnapModeSelector");
	snap_mode_selector.get_popwin()->set_name ("SnapModeSelector");
	
	snap_mode_box.set_spacing (3);
	snap_mode_box.set_border_width (3);

	Gtkmmext::set_usize_to_display_given_text (*snap_mode_selector.get_entry(), "SngpMode", 2, 10);
	snap_mode_selector.set_popdown_strings (internationalize (snap_mode_strings));
	snap_mode_selector.set_value_in_list (true, false);

	snap_mode_box.pack_start (snap_mode_label, false, false);
	snap_mode_box.pack_start (snap_mode_selector, false, false);

	snap_mode_selector.get_popwin()->unmap_event.connect (slot (*this, &Editor::snap_mode_selection_done));

	/* Zoom focus mode */

	zoom_focus_label.set_name ("ToolBarLabel");

	zoom_focus_selector.set_name ("ZoomFocusSelector");
	zoom_focus_selector.get_entry()->set_name ("ZoomFocusSelector");
	zoom_focus_selector.get_popwin()->set_name ("ZoomFocusSelector");

	zoom_focus_box.set_spacing (3);
	zoom_focus_box.set_border_width (3);

	/* XXX another disgusting hack because of the way combo boxes size themselves */

	Gtkmmext::set_usize_to_display_given_text (*zoom_focus_selector.get_entry(), "Edgt Cursor", 2, 10);
	zoom_focus_selector.set_popdown_strings (internationalize (zoom_focus_strings));
	zoom_focus_selector.set_value_in_list (true, false);

	zoom_focus_box.pack_start (zoom_focus_label, false, false);
	zoom_focus_box.pack_start (zoom_focus_selector, false, false);

	zoom_focus_selector.get_popwin()->unmap_event.connect (slot (*this, &Editor::zoom_focus_selection_done));

	/* selection/cursor clocks */

	toolbar_selection_cursor_label.set_name ("ToolBarLabel");
	selection_start_clock_label.set_name ("ToolBarLabel");
	selection_end_clock_label.set_name ("ToolBarLabel");
	edit_cursor_clock_label.set_name ("ToolBarLabel");

	selection_start_clock_label.set_text (_("Start:"));
	selection_end_clock_label.set_text (_("End:"));
	edit_cursor_clock_label.set_text (_("Edit:"));

	toolbar_selection_clock_table.set_border_width (5);
	toolbar_selection_clock_table.set_col_spacings (2);
	toolbar_selection_clock_table.set_homogeneous (false);

//	toolbar_selection_clock_table.attach (selection_start_clock_label, 0, 1, 0, 1, 0, 0, 0, 0);
//	toolbar_selection_clock_table.attach (selection_end_clock_label, 1, 2, 0, 1, 0, 0, 0, 0);
	toolbar_selection_clock_table.attach (edit_cursor_clock_label, 2, 3, 0, 1, 0, 0, 0, 0);

//	toolbar_selection_clock_table.attach (selection_start_clock, 0, 1, 1, 2, 0, 0);
//	toolbar_selection_clock_table.attach (selection_end_clock, 1, 2, 1, 2, 0, 0);
	toolbar_selection_clock_table.attach (edit_cursor_clock, 2, 3, 1, 2, 0, 0);


//	toolbar_clock_vbox.set_spacing (2);
//	toolbar_clock_vbox.set_border_width (10);
	/* the editor/mixer button will be enabled at session connect */


	HBox* hbox = new HBox;

	hbox->pack_start (toolbar_selection_clock_table, false, false);
	hbox->pack_start (zoom_indicator_vbox, false, false); 
	hbox->pack_start (zoom_focus_box, false, false);
	hbox->pack_start (snap_type_box, false, false);
	hbox->pack_start (snap_mode_box, false, false);
	hbox->pack_start (edit_mode_box, false, false);

	VBox *vbox = manage (new VBox);

	vbox->set_spacing (3);
	vbox->set_border_width (3);

	HBox *nbox = manage (new HBox);
	
	nudge_forward_button.clicked.connect (bind (slot (*this, &Editor::nudge_forward), false));
	nudge_backward_button.clicked.connect (bind (slot (*this, &Editor::nudge_backward), false));

	nbox->pack_start (nudge_backward_button, false, false);
	nbox->pack_start (nudge_forward_button, false, false);
	nbox->pack_start (nudge_clock, false, false, 5);

	nudge_label.set_name ("ToolBarLabel");

	vbox->pack_start (nudge_label, false, false);
	vbox->pack_start (*nbox, false, false);

	hbox->pack_start (*vbox, false, false);

	hbox->show_all ();

	tools_tearoff = new TearOff (*hbox);
	tools_tearoff->set_name ("MouseModeBase");

	tools_tearoff->Detach.connect (bind (slot (*this, &Editor::detach_tearoff), static_cast<Gtk::Box*>(&toolbar_hbox), 
					     static_cast<Gtk::Widget*>(hbox)));
	tools_tearoff->Attach.connect (bind (slot (*this, &Editor::reattach_tearoff), static_cast<Gtk::Box*> (&toolbar_hbox), 
					     static_cast<Gtk::Widget*> (hbox), 0));

	toolbar_hbox.set_spacing (8);
	toolbar_hbox.set_border_width (0);

	toolbar_hbox.pack_start (*tools_tearoff, false, false);
	toolbar_hbox.pack_start (*mouse_mode_tearoff, false, false);
	
	toolbar_base.set_name ("ToolBarBase");
	toolbar_base.add (toolbar_hbox);

	toolbar_frame.set_shadow_type (GTK_SHADOW_OUT);
	toolbar_frame.set_name ("BaseFrame");
	toolbar_frame.add (toolbar_base);
}

gint
Editor::_autoscroll_canvas (void *arg)
{
	return ((Editor *) arg)->autoscroll_canvas ();
}

gint
Editor::autoscroll_canvas ()
{
	jack_nframes_t new_frame;
	bool keep_calling = true;

	if (autoscroll_direction < 0) {
		if (leftmost_frame < autoscroll_distance) {
			new_frame = 0;
		} else {
			new_frame = leftmost_frame - autoscroll_distance;
		}
 	} else {
		if (leftmost_frame > max_frames - autoscroll_distance) {
			new_frame = max_frames;
		} else {
			new_frame = leftmost_frame + autoscroll_distance;
		}
	}

	if (new_frame != leftmost_frame) {
		reposition_x_origin (new_frame);
	}

	if (new_frame == 0 || new_frame == max_frames) {
		/* we are done */
		return FALSE;
	}

	autoscroll_cnt++;

	if (autoscroll_cnt == 1) {

		/* connect the timeout so that we get called repeatedly */
		
		autoscroll_timeout_tag = gtk_timeout_add (100, _autoscroll_canvas, this);
		keep_calling = false;

	} else if (autoscroll_cnt > 10 && autoscroll_cnt < 20) {
		
		/* after about a while, speed up a bit by changing the timeout interval */

		autoscroll_timeout_tag = gtk_timeout_add (50, _autoscroll_canvas, this);
		keep_calling = false;
		
	} else if (autoscroll_cnt >= 20 && autoscroll_cnt < 30) {

		/* after about another while, speed up some more */

		autoscroll_timeout_tag = gtk_timeout_add (25, _autoscroll_canvas, this);
		keep_calling = false;

	} else if (autoscroll_cnt >= 30) {

		/* we've been scrolling for a while ... crank it up */

		autoscroll_distance = 10 * (jack_nframes_t) floor (canvas_width * frames_per_unit);
	}

	return keep_calling;
}

void
Editor::start_canvas_autoscroll (int dir)
{
	if (!session) {
		return;
	}

	stop_canvas_autoscroll ();

	autoscroll_direction = dir;
	autoscroll_distance = (jack_nframes_t) floor ((canvas_width * frames_per_unit)/10.0);
	autoscroll_cnt = 0;
	
	/* do it right now, which will start the repeated callbacks */
	
	autoscroll_canvas ();
}

void
Editor::stop_canvas_autoscroll ()
{
	if (autoscroll_timeout_tag >= 0) {
		gtk_timeout_remove (autoscroll_timeout_tag);
		autoscroll_timeout_tag = -1;
	}
}

int
Editor::convert_drop_to_paths (vector<string>& paths, 
			       GdkDragContext     *context,
			       gint                x,
			       gint                y,
			       GtkSelectionData   *data,
			       guint               info,
			       guint               time)			       

{	
	string spath;
	char *path;
	int state;
	gchar *tname = gdk_atom_name (data->type);

	if (session == 0 || strcmp (tname, "text/plain") != 0) {
		return -1;
	}

	/* Parse the "uri-list" format that Nautilus provides, 
	   where each pathname is delimited by \r\n
	*/

	path = (char *) data->data;
	state = 0;

	for (int n = 0; n < data->length; ++n) {

		switch (state) {
		case 0:
			if (path[n] == '\r') {
				state = 1;
			} else {
				spath += path[n];
			}
			break;
		case 1:
			if (path[n] == '\n') {
				paths.push_back (spath);
				spath = "";
				state = 0;
			} else {
				warning << _("incorrectly formatted URI list, ignored")
					<< endmsg;
				return -1;
			}
			break;
		}
	}

	/* nautilus and presumably some other file managers prefix even text/plain with file:// */
		
	for (vector<string>::iterator p = paths.begin(); p != paths.end(); ++p) {

		// cerr << "dropped text was " << *p << endl;

		url_decode (*p);

		// cerr << "decoded was " << *p << endl;

		if ((*p).substr (0,7) == "file://") {
			(*p) = (*p).substr (7);
		}
	}
	
	return 0;
}

void  
Editor::track_canvas_drag_data_received  (GdkDragContext     *context,
					  gint                x,
					  gint                y,
					  GtkSelectionData   *data,
					  guint               info,
					  guint               time)
{
	TimeAxisView* tvp;
	AudioTimeAxisView* tv;
	double cy;
	vector<string> paths;
	string spath;
	GdkEvent ev;
	jack_nframes_t frame;

	if (convert_drop_to_paths (paths, context, x, y, data, info, time)) {
		goto out;
	}

	/* D-n-D coordinates are window-relative, so convert to "world" coordinates
	 */

	double wx;
	double wy;

	gtk_canvas_window_to_world (GTK_CANVAS(track_gtk_canvas), (double) x, (double) y, &wx, &wy);

	ev.type = GDK_BUTTON_RELEASE;
	ev.button.x = wx;
	ev.button.y = wy;

	frame = event_frame (&ev, 0, &cy);

	snap_to (frame);

	if ((tvp = trackview_by_y_position (cy)) == 0) {

		/* drop onto canvas background: create a new track */

		insert_paths_as_new_tracks (paths, false);

		
	} else if ((tv = dynamic_cast<AudioTimeAxisView*>(tvp)) != 0) {

		/* check that its an audio track, not a bus */

		if (tv->get_diskstream()) {

			for (vector<string>::iterator p = paths.begin(); p != paths.end(); ++p) {
				insert_sndfile_into (*p, true, tv, frame);
			}
		}

	}

  out:
	gtk_drag_finish (context, TRUE, FALSE, time);
}

void
Editor::new_tempo_section ()

{
}

void
Editor::map_transport_state ()
{
	ENSURE_GUI_THREAD (slot (*this, &Editor::map_transport_state));

	if (session->transport_stopped()) {
		have_pending_keyboard_selection = false;
	}
}

/* UNDO/REDO */

Editor::State::State ()
{
	selection = new Selection;
}

Editor::State::~State ()
{
	delete selection;
}

UndoAction
Editor::get_memento () const
{
	State *state = new State;

	store_state (*state);
	return bind (slot (*(const_cast<Editor*>(this)), &Editor::restore_state), state);
}

void
Editor::store_state (State& state) const
{
	*state.selection = *selection;
}

void
Editor::restore_state (State *state)
{
	if (*selection == *state->selection) {
		return;
	}

	*selection = *state->selection;
	time_selection_changed ();
	region_selection_changed ();

	/* XXX other selection change handlers? */
}

void
Editor::begin_reversible_command (string name)
{
	if (session) {
		UndoAction ua = get_memento();
		session->begin_reversible_command (name, &ua);
	}
}

void
Editor::commit_reversible_command ()
{
	if (session) {
		UndoAction ua = get_memento();
		session->commit_reversible_command (&ua);
	}
}

void
Editor::flush_track_canvas ()
{
	/* I don't think this is necessary, and only causes more problems.
	   I'm commenting it out
	   and if the imageframe folks don't have any issues, we can take
	   out this method entirely
	*/
	
	//gtk_canvas_update_now (GTK_CANVAS(track_gtk_canvas));
	//gtk_main_iteration ();
}

void
Editor::set_selected_track_from_click (bool add, bool with_undo, bool no_remove)
{
	if (!clicked_trackview) {
		return;
	}

	if (with_undo) {
		begin_reversible_command (_("set selected trackview"));
	}

	if (add) {
		
		if (selection->selected (clicked_trackview)) {
			if (!no_remove) {
				selection->remove (clicked_trackview);
			}
		} else {
			selection->add (clicked_trackview);
		}
		
	} else {

		if (selection->selected (clicked_trackview) && selection->tracks.size() == 1) {
			/* no commit necessary */
			return;
		} 

		selection->set (clicked_trackview);
	}
	
	if (with_undo) {
		commit_reversible_command ();
	}
}

void
Editor::set_selected_control_point_from_click (bool add, bool with_undo, bool no_remove)
{
	if (!clicked_control_point) {
		return;
	}

	if (with_undo) {
		begin_reversible_command (_("set selected control point"));
	}

	if (add) {
		
	} else {

	}
	
	if (with_undo) {
		commit_reversible_command ();
	}
}

void
Editor::set_selected_regionview_from_click (bool add, bool no_track_remove)
{
	if (!clicked_regionview) {
		return;
	}

	AudioTimeAxisView* atv = dynamic_cast<AudioTimeAxisView*>(&clicked_regionview->get_time_axis_view());

	if (!atv) {
		return;
	}

	RouteGroup* group = atv->route().edit_group();
	vector<AudioRegionView*> all_equivalent_regions;
	
	if (group && group->is_active()) {

		for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {

			AudioTimeAxisView* tatv;

			if ((tatv = dynamic_cast<AudioTimeAxisView*> (*i)) != 0) {

				if (tatv->route().edit_group() != group) {
					continue;
				}
			
				AudioPlaylist* pl;
				vector<AudioRegion*> results;
				AudioRegionView* marv;
				DiskStream* ds;
				
				if ((ds = tatv->get_diskstream()) == 0) {
					/* bus */
					continue;
				}
				
				if ((pl = ds->playlist()) != 0) {
					pl->get_equivalent_regions (clicked_regionview->region, 
								    results);
				}
				
				for (vector<AudioRegion*>::iterator ir = results.begin(); ir != results.end(); ++ir) {
					if ((marv = tatv->view->find_view (**ir)) != 0) {
						all_equivalent_regions.push_back (marv);
					}
				}
				
			}
		}

	} else {

		all_equivalent_regions.push_back (clicked_regionview);

	}
	
	begin_reversible_command (_("set selected regionview"));
	
	if (add) {

		if (clicked_regionview->get_selected()) {
			if (group && group->is_active() && selection->audio_regions.size() > 1) {
				/* reduce selection down to just the one clicked */
				selection->set (clicked_regionview);
			} else {
				selection->remove (clicked_regionview);
			}
		} else {
			selection->add (all_equivalent_regions);
		}

		set_selected_track_from_click (add, false, no_track_remove);
		
	} else {

		// karsten wiese suggested these two lines to make
		// a selected region rise to the top. but this
		// leads to a mismatch between actual layering
		// and visual layering. resolution required ....
		//
		// gtk_canvas_item_raise_to_top (clicked_regionview->get_canvas_group());
		// gtk_canvas_item_raise_to_top (clicked_regionview->get_time_axis_view().canvas_display);

		if (clicked_regionview->get_selected()) {
			/* no commit necessary: we are the one selected. */
			return;

		} else {
			
			selection->set (all_equivalent_regions);
			set_selected_track_from_click (add, false, false);
		}
	}

	commit_reversible_command () ;
}

void
Editor::set_selected_regionview_from_region_list (Region& r, bool add)
{
	vector<AudioRegionView*> all_equivalent_regions;
	AudioRegion* region;

	if ((region = dynamic_cast<AudioRegion*>(&r)) == 0) {
		return;
	}

	for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
		
		AudioTimeAxisView* tatv;
		
		if ((tatv = dynamic_cast<AudioTimeAxisView*> (*i)) != 0) {
			
			AudioPlaylist* pl;
			vector<AudioRegion*> results;
			AudioRegionView* marv;
			DiskStream* ds;
			
			if ((ds = tatv->get_diskstream()) == 0) {
				/* bus */
				continue;
			}

			if ((pl = ds->playlist()) != 0) {
				pl->get_region_list_equivalent_regions (*region, results);
			}
			
			for (vector<AudioRegion*>::iterator ir = results.begin(); ir != results.end(); ++ir) {
				if ((marv = tatv->view->find_view (**ir)) != 0) {
					all_equivalent_regions.push_back (marv);
				}
			}
			
		}
	}
	
	begin_reversible_command (_("set selected regions"));
	
	if (add) {

		selection->add (all_equivalent_regions);
		
	} else {

		selection->set (all_equivalent_regions);
	}

	commit_reversible_command () ;
}

gint
Editor::set_selected_regionview_from_map_event (GdkEventAny* ev, StreamView* sv, Region* r)
{
	AudioRegionView* rv;
	AudioRegion* ar;

	if ((ar = dynamic_cast<AudioRegion*> (r)) == 0) {
		return TRUE;
	}

	if ((rv = sv->find_view (*ar)) == 0) {
		return TRUE;
	}

	/* don't reset the selection if its something other than 
	   a single other region.
	*/

	if (selection->audio_regions.size() > 1) {
		return TRUE;
	}
	
	begin_reversible_command (_("set selected regions"));
	
	selection->set (rv);

	commit_reversible_command () ;

	return TRUE;
}

void
Editor::set_edit_group_solo (Route& route, bool yn)
{
	RouteGroup *edit_group;

	if ((edit_group = route.edit_group()) != 0) {
		edit_group->apply (&Route::set_solo, yn, this);
	} else {
		route.set_solo (yn, this);
	}
}

void
Editor::set_edit_group_mute (Route& route, bool yn)
{
	RouteGroup *edit_group = 0;

	if ((edit_group == route.edit_group()) != 0) {
		edit_group->apply (&Route::set_mute, yn, this);
	} else {
		route.set_mute (yn, this);
	}
}
		
void
Editor::set_edit_menu (Menu& menu)
{
	edit_menu = &menu;
	edit_menu->map_.connect (slot (*this, &Editor::edit_menu_map_handler));
}

void
Editor::edit_menu_map_handler ()
{
	using namespace Menu_Helpers;
	MenuList& edit_items = edit_menu->items();
	string label;

	/* Nuke all the old items */
		
	edit_items.clear ();

	if (session == 0) {
		return;
	}

	if (session->undo_depth() == 0) {
		label = _("Undo");
	} else {
		label = compose(_("Undo (%1)"), session->next_undo());
	}
	
	edit_items.push_back (MenuElem (label, bind (slot (*this, &Editor::undo), 1U)));
	
	if (session->undo_depth() == 0) {
		edit_items.back()->set_sensitive (false);
	}
	
	if (session->redo_depth() == 0) {
		label = _("Redo");
	} else {
		label = compose(_("Redo (%1)"), session->next_redo());
	}
	
	edit_items.push_back (MenuElem (label, bind (slot (*this, &Editor::redo), 1U)));
	if (session->redo_depth() == 0) {
		edit_items.back()->set_sensitive (false);
	}

	vector<MenuItem*> mitems;

	edit_items.push_back (SeparatorElem());
	edit_items.push_back (MenuElem (_("Cut"), slot (*this, &Editor::cut)));
	mitems.push_back (edit_items.back());
	edit_items.push_back (MenuElem (_("Copy"), slot (*this, &Editor::copy)));
	mitems.push_back (edit_items.back());
	edit_items.push_back (MenuElem (_("Paste"), bind (slot (*this, &Editor::paste), 1.0f)));
	mitems.push_back (edit_items.back());
	edit_items.push_back (SeparatorElem());
	edit_items.push_back (MenuElem (_("Align"), bind (slot (*this, &Editor::align), ARDOUR::SyncPoint)));
	mitems.push_back (edit_items.back());
	edit_items.push_back (MenuElem (_("Align Relative"), bind (slot (*this, &Editor::align_relative), ARDOUR::SyncPoint)));
	mitems.push_back (edit_items.back());
	edit_items.push_back (SeparatorElem());

	if (selection->empty()) {
		for (vector<MenuItem*>::iterator i = mitems.begin(); i != mitems.end(); ++i) {
			(*i)->set_sensitive (false);
		}
	}

	Menu* import_menu = manage (new Menu());
	import_menu->set_name ("ArdourContextMenu");
	MenuList& import_items = import_menu->items();
	
	import_items.push_back (MenuElem (_("... as new track"), bind (slot (*this, &Editor::import_audio), true)));
	import_items.push_back (MenuElem (_("... as new region"), bind (slot (*this, &Editor::import_audio), false)));

	Menu* embed_menu = manage (new Menu());
	embed_menu->set_name ("ArdourContextMenu");
	MenuList& embed_items = embed_menu->items();

	embed_items.push_back (MenuElem (_("... as new track"), bind (slot (*this, &Editor::insert_sndfile), true)));
	embed_items.push_back (MenuElem (_("... as new region"), slot (*this, &Editor::embed_audio)));

	edit_items.push_back (MenuElem (_("Import audio (copy)"), *import_menu));
	edit_items.push_back (MenuElem (_("Embed audio (link)"), *embed_menu));
	edit_items.push_back (SeparatorElem());

	edit_items.push_back (MenuElem (_("Remove last capture"), slot (*this, &Editor::remove_last_capture)));
	if (!session->have_captured()) {
		edit_items.back()->set_sensitive (false);
	}
}

void
Editor::duplicate_dialog (bool dup_region)
{
	if (dup_region) {
		if (clicked_regionview == 0) {
			return;
		}
	} else {
		if (selection->time.length() == 0) {
			return;
		}
	}

	ArdourDialog win ("duplicate dialog");
	Entry  entry;
	Label  label (_("Duplicate how many times?"));
	HBox   hbox;
	HBox   button_box;
	Button ok_button (_("OK"));
	Button cancel_button (_("Cancel"));
	VBox   vbox;

	button_box.set_spacing (7);
	set_usize_to_display_given_text (ok_button, _("Cancel"), 20, 15); // this is cancel on purpose
	set_usize_to_display_given_text (cancel_button, _("Cancel"), 20, 15);
	button_box.pack_end (ok_button, false, false);
	button_box.pack_end (cancel_button, false, false);
	
	hbox.set_spacing (5);
	hbox.pack_start (label);
	hbox.pack_start (entry, true, true);
	
	vbox.set_spacing (5);
	vbox.set_border_width (5);
	vbox.pack_start (hbox);
	vbox.pack_start (button_box);

	win.add (vbox);
	win.set_position (GTK_WIN_POS_MOUSE);
	win.show_all ();

	ok_button.clicked.connect (bind (slot (win, &ArdourDialog::stop), 0));
	entry.activate.connect (bind (slot (win, &ArdourDialog::stop), 0));
	cancel_button.clicked.connect (bind (slot (win, &ArdourDialog::stop), 1));

	entry.focus_in_event.connect (slot (ARDOUR_UI::generic_focus_in_event));
	entry.focus_out_event.connect (slot (ARDOUR_UI::generic_focus_out_event));
	
	entry.set_text ("1");
	set_usize_to_display_given_text (entry, X_("12345678"), 20, 15);
	entry.select_region (0, entry.get_text_length());

	win.set_position (GTK_WIN_POS_MOUSE);
	win.realize ();
	win.get_window().set_decorations (GdkWMDecoration (GDK_DECOR_BORDER|GDK_DECOR_RESIZEH));

	entry.grab_focus ();

	win.run ();

	if (win.run_status() != 0) {
		return;
	}

	string text = entry.get_text();
	float times;

	if (sscanf (text.c_str(), "%f", &times) == 1) {
		if (dup_region) {
			AudioRegionSelection regions;
			regions.add (clicked_regionview);
			duplicate_some_regions (regions, times);
		} else {
			duplicate_selection (times);
		}
	}
}

void
Editor::show_verbose_canvas_cursor ()
{
	gtk_canvas_item_raise_to_top (verbose_canvas_cursor);
	gtk_canvas_item_show (verbose_canvas_cursor);
	verbose_cursor_visible = true;
}

void
Editor::hide_verbose_canvas_cursor ()
{
	gtk_canvas_item_hide (verbose_canvas_cursor);
	verbose_cursor_visible = false;
}

void
Editor::set_verbose_canvas_cursor (string txt, double x, double y)
{
	/* XXX get origin of canvas relative to root window,
	   add x and y and check compared to gdk_screen_{width,height}
	*/
	gtk_canvas_item_set (verbose_canvas_cursor, "text", txt.c_str(), "x", x, "y", y, NULL);	
}

void
Editor::set_verbose_canvas_cursor_text (string txt)
{
	gtk_canvas_item_set (verbose_canvas_cursor, "text", txt.c_str(), NULL);
}

gint
Editor::edit_mode_selection_done (GdkEventAny *ev)
{
	if (session == 0) {
		return FALSE;
	}

	string choice = edit_mode_selector.get_entry()->get_text();
	EditMode mode = Slide;

	if (choice == _("Splice")) {
		mode = Splice;
	} else if (choice == _("Slide")) {
		mode = Slide;
	}

	session->set_edit_mode (mode);

	return FALSE;
}	

gint
Editor::snap_type_selection_done (GdkEventAny *ev)
{
	if (session == 0) {
		return FALSE;
	}

	string choice = snap_type_selector.get_entry()->get_text();
	SnapType snaptype = SnapToFrame;
	
	if (choice == _("Beats/3")) {
                snaptype = SnapToAThirdBeat;
        } else if (choice == _("Beats/4")) {
                snaptype = SnapToAQuarterBeat;
        } else if (choice == _("Beats/8")) {
                snaptype = SnapToAEighthBeat;
        } else if (choice == _("Beats/16")) {
                snaptype = SnapToASixteenthBeat;
        } else if (choice == _("Beats/32")) {
                snaptype = SnapToAThirtysecondBeat;
        } else if (choice == _("Beats")) {
		snaptype = SnapToBeat;
	} else if (choice == _("Bars")) {
		snaptype = SnapToBar;
	} else if (choice == _("Marks")) {
		snaptype = SnapToMark;
	} else if (choice == _("Edit Cursor")) {
		snaptype = SnapToEditCursor;
	} else if (choice == _("Region starts")) {
		snaptype = SnapToRegionStart;
	} else if (choice == _("Region ends")) {
		snaptype = SnapToRegionEnd;
	} else if (choice == _("Region bounds")) {
		snaptype = SnapToRegionBoundary;
	} else if (choice == _("Region syncs")) {
		snaptype = SnapToRegionSync;
	} else if (choice == _("CD Frames")) {
		snaptype = SnapToCDFrame;
	} else if (choice == _("SMPTE Frames")) {
		snaptype = SnapToSMPTEFrame;
	} else if (choice == _("SMPTE Seconds")) {
		snaptype = SnapToSMPTESeconds;
	} else if (choice == _("SMPTE Minutes")) {
		snaptype = SnapToSMPTEMinutes;
	} else if (choice == _("Seconds")) {
		snaptype = SnapToSeconds;
	} else if (choice == _("Minutes")) {
		snaptype = SnapToMinutes;
        } else if (choice == _("None")) {
		snaptype = SnapToFrame;
	}
	
	set_snap_to (snaptype);

	return FALSE;
}	

gint
Editor::snap_mode_selection_done (GdkEventAny *ev)
{
	if(session == 0) return FALSE;

	string choice = snap_mode_selector.get_entry()->get_text();
	SnapMode mode = SnapNormal;

	if (choice == _("Normal")) {
		mode = SnapNormal;
	} else if (choice == _("Magnetic")) {
		mode = SnapMagnetic;
	}

	set_snap_mode (mode);

	return FALSE;
}

gint
Editor::zoom_focus_selection_done (GdkEventAny *ev)
{
	if (session == 0) {
		return FALSE;
	}

	string choice = zoom_focus_selector.get_entry()->get_text();
	ZoomFocus focus_type = ZoomFocusLeft;

	if (choice == _("Left")) {
		focus_type = ZoomFocusLeft;
	} else if (choice == _("Right")) {
		focus_type = ZoomFocusRight;
	} else if (choice == _("Center")) {
		focus_type = ZoomFocusCenter;
        } else if (choice == _("Playhead")) {
		focus_type = ZoomFocusPlayhead;
        } else if (choice == _("Edit Cursor")) {
		focus_type = ZoomFocusEdit;
	} 

	set_zoom_focus (focus_type);

	return FALSE;
}	

gint
Editor::edit_controls_button_release (GdkEventButton* ev)
{
	if (Keyboard::is_context_menu_event (ev)) {
		ARDOUR_UI::instance()->add_route ();
	}
	return TRUE;
}

void
Editor::track_selection_changed ()
{
	switch (selection->tracks.size()){
	case 0:
		break;
	default:
		set_selected_mixer_strip (*(selection->tracks.front()));
		break;
	}

	for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
		(*i)->set_selected (false);
		if (mouse_mode == MouseRange) {
			(*i)->hide_selection ();
		}
	}

	for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
		(*i)->set_selected (true);
		if (mouse_mode == MouseRange) {
			(*i)->show_selection (selection->time);
		}
	}
}

void
Editor::time_selection_changed ()
{
	for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
		(*i)->hide_selection ();
	}

	if (selection->tracks.empty()) {
		for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
			(*i)->show_selection (selection->time);
		}
	} else {
		for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
			(*i)->show_selection (selection->time);
		}
	}
}

void
Editor::region_selection_changed ()
{
	for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
		(*i)->set_selected_regionviews (selection->audio_regions);
	}
}

void
Editor::point_selection_changed ()
{
	for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
		(*i)->set_selected_points (selection->points);
	}
}

void
Editor::run_sub_event_loop ()
{
	Keyboard::the_keyboard().allow_focus (true);
	sub_event_loop_status = 0;
	Main::run ();
}

void
Editor::finish_sub_event_loop (int status)
{
	Main::quit ();
	Keyboard::the_keyboard().allow_focus (false);
	sub_event_loop_status = status;
}

gint
Editor::finish_sub_event_loop_on_delete (GdkEventAny *ignored, int32_t status)
{
	finish_sub_event_loop (status);
	return TRUE;
}

gint
Editor::mouse_select_button_release (GdkEventButton* ev)
{
	/* this handles just right-clicks */

	if (ev->button != 3) {
		return FALSE;
	}

	return TRUE;
}

Editor::TrackViewList *
Editor::get_valid_views (TimeAxisView* track, RouteGroup* group)
{
	TrackViewList *v;
	TrackViewList::iterator i;

	v = new TrackViewList;

	if (track == 0 && group == 0) {

		/* all views */

		for (i = track_views.begin(); i != track_views.end (); ++i) {
			v->push_back (*i);
		}

	} else if (track != 0 && group == 0 || (track != 0 && group != 0 && !group->is_active())) {
		
		/* just the view for this track
		 */

		v->push_back (track);

	} else {
		
		/* views for all tracks in the edit group */
		
		for (i  = track_views.begin(); i != track_views.end (); ++i) {

			if (group == 0 || (*i)->edit_group() == group) {
				v->push_back (*i);
			}
		}
	}
	
	return v;
}

void
Editor::set_zoom_focus (ZoomFocus f)
{
	if (zoom_focus != f) {
		zoom_focus = f;
		vector<string> txt = internationalize (zoom_focus_strings);
		zoom_focus_selector.get_entry()->set_text (txt[(int)f]);
		ZoomFocusChanged (); /* EMIT_SIGNAL */

		instant_save ();
	}
}

void
Editor::ensure_float (Window& win)
{
	win.set_transient_for (*this);
}

void 
Editor::pane_allocation_handler (GtkAllocation *alloc, Gtk::Paned* which)
{
	/* recover or initialize pane positions. do this here rather than earlier because
	   we don't want the positions to change the child allocations, which they seem to do.
	 */

	int pos;
	XMLProperty* prop;
	char buf[32];
	XMLNode* node = ARDOUR_UI::instance()->editor_settings();
	int width, height;
	static int32_t done[4] = { 0, 0, 0, 0 };
	XMLNode* geometry;

	if ((geometry = find_named_node (*node, "geometry")) == 0) {
		width = default_width;
		height = default_height;
	} else {
		width = atoi(geometry->property("x_size")->value());
		height = atoi(geometry->property("y_size")->value());
	}

	if (which == static_cast<Gtk::Paned*> (&track_list_canvas_pane)) {

		if (done[0]) {
			return;
		}

		if (!geometry || (prop = geometry->property("track_list_canvas_pane_pos")) == 0) {
			pos = 75;
			snprintf (buf, sizeof(buf), "%d", pos);
		} else {
			pos = atoi (prop->value());
		}

		if ((done[0] = GTK_WIDGET(track_list_canvas_pane.gtkobj())->allocation.width > pos)) {
			track_list_canvas_pane.set_position (pos);
		}

	} else if (which == static_cast<Gtk::Paned*> (&canvas_region_list_pane)) {

		if (done[1]) {
			return;
		}

		if (!geometry || (prop = geometry->property("canvas_region_list_pane_pos")) == 0) {
			pos = width - (95 * 2);
			snprintf (buf, sizeof(buf), "%d", pos);
		} else {
			pos = atoi (prop->value());
		}

		if ((done[1] = GTK_WIDGET(canvas_region_list_pane.gtkobj())->allocation.width > pos)) {
			canvas_region_list_pane.set_position (pos);
		}

	} else if (which == static_cast<Gtk::Paned*> (&route_group_vpane)) {

		if (done[2]) {
			return;
		}

		if (!geometry || (prop = geometry->property("route_group_pane_pos")) == 0) {
			pos = width - (95 * 2);
			snprintf (buf, sizeof(buf), "%d", pos);
		} else {
			pos = atoi (prop->value());
		}

		if ((done[2] = GTK_WIDGET(route_group_vpane.gtkobj())->allocation.height > pos)) {
			route_group_vpane.set_position (pos);
		}

	} else if (which == static_cast<Gtk::Paned*> (&region_selection_vpane)) {

		if (done[3]) {
			return;
		}

		if (!geometry || (prop = geometry->property("region_selection_pane_pos")) == 0) {
			pos = width - (95 * 2);
			snprintf (buf, sizeof(buf), "%d", pos);
		} else {
			pos = atoi (prop->value());
		}

		if ((done[3] = GTK_WIDGET(region_selection_vpane.gtkobj())->allocation.height > pos)) { 
			region_selection_vpane.set_position (pos);
		}
	}
}

void
Editor::detach_tearoff (Gtk::Box* b, Gtk::Widget* w)
{
	if (tools_tearoff->torn_off() && 
	    mouse_mode_tearoff->torn_off()) {
		top_hbox.remove (toolbar_frame);
	}
	
	ensure_float (*w->get_toplevel());
}

void
Editor::reattach_tearoff (Gtk::Box* b, Gtk::Widget* w, int32_t n)
{
	if (toolbar_frame.get_parent() == 0) {
		top_hbox.pack_end (toolbar_frame);
	}
}

void
Editor::set_show_measures (bool yn)
{
	if (_show_measures != yn) {
		hide_measures ();

		if ((_show_measures = yn) == true) {
			draw_measures ();
		}
		DisplayControlChanged (ShowMeasures);
		instant_save ();
	}
}

void
Editor::set_follow_playhead (bool yn)
{
	if (_follow_playhead != yn) {
		if ((_follow_playhead = yn) == true) {
			/* catch up */
			update_current_screen ();
		}
		DisplayControlChanged (FollowPlayhead);
		instant_save ();
	}
}

void
Editor::toggle_xfade_active (Crossfade* xfade)
{
	xfade->set_active (!xfade->active());
}

void
Editor::toggle_xfade_length (Crossfade* xfade)
{
	xfade->set_follow_overlap (!xfade->following_overlap());
}

void
Editor::edit_xfade (Crossfade* xfade)
{
	CrossfadeEditor cew (*session, *xfade, xfade->fade_in().get_min_y(), 1.0);
		
	ensure_float (cew);
	
	cew.ok_button.clicked.connect (bind (slot (cew, &ArdourDialog::stop), 1));
	cew.cancel_button.clicked.connect (bind (slot (cew, &ArdourDialog::stop), 0));
	cew.delete_event.connect (slot (cew, &ArdourDialog::wm_doi_event_stop));

	cew.run ();
	
	if (cew.run_status() == 1) {
		cew.apply ();
		xfade->StateChanged (Change (~0));
	}
}

PlaylistSelector&
Editor::playlist_selector () const
{
	return *_playlist_selector;
}

jack_nframes_t
Editor::get_nudge_distance (jack_nframes_t pos, jack_nframes_t& next)
{
	jack_nframes_t ret;

	ret = nudge_clock.current_duration (pos);
	next = ret + 1; /* XXXX fix me */

	return ret;
}

void
Editor::end_location_changed (Location* location)
{
	ENSURE_GUI_THREAD (bind (slot (*this, &Editor::end_location_changed), location));
	track_canvas_scroller.get_hadjustment()->set_upper (location->end() / frames_per_unit);
}

int
Editor::playlist_deletion_dialog (Playlist* pl)
{
	ArdourDialog dialog ("playlist deletion dialog");
	Label  label (compose (_("Playlist %1 is currently unused.\n"
				 "If left alone, no audio files used by it will be cleaned.\n"
				 "If deleted, audio files used by it alone by will cleaned."),
			       pl->name()));
	HBox   button_box;
	Button del_button (_("Delete playlist"));
	Button keep_button (_("Keep playlist"));
	Button abort_button (_("Cancel cleanup"));
	VBox   vbox;

	button_box.set_spacing (7);
	button_box.set_homogeneous (true);
	button_box.pack_end (del_button, false, false);
	button_box.pack_end (keep_button, false, false);
	button_box.pack_end (abort_button, false, false);
	
	vbox.set_spacing (5);
	vbox.set_border_width (5);
	vbox.pack_start (label);
	vbox.pack_start (button_box);

	dialog.add (vbox);
	dialog.set_position (GTK_WIN_POS_CENTER);
	dialog.show_all ();

	del_button.clicked.connect (bind (slot (dialog, &ArdourDialog::stop), 0));
	keep_button.clicked.connect (bind (slot (dialog, &ArdourDialog::stop), 1));
	abort_button.clicked.connect (bind (slot (dialog, &ArdourDialog::stop), 2));

	dialog.realize ();
	dialog.get_window().set_decorations (GdkWMDecoration (GDK_DECOR_BORDER|GDK_DECOR_RESIZEH));

	dialog.run ();

	switch (dialog.run_status()) {
	case 1:
		/* keep the playlist */
		return 1;
		break;
	case 0:
		/* delete the playlist */
		return 0;
		break;
	case 2:
		/* abort cleanup */
		return -1;
		break;
	default:
		/* keep the playlist */
		return 1;
	}
}

bool
Editor::audio_region_selection_covers (jack_nframes_t where)
{
	for (AudioRegionSelection::iterator a = selection->audio_regions.begin(); a != selection->audio_regions.end(); ++a) {
		if ((*a)->region.covers (where)) {
			return true;
		}
	}

	return false;
}

void
Editor::prepare_for_cleanup ()
{
	cut_buffer->clear_audio_regions ();
	cut_buffer->clear_playlists ();

	selection->clear_audio_regions ();
	selection->clear_playlists ();
}

void
Editor::init_colormap ()
{
	for (size_t x = 0; x < sizeof (color_id_strs) / sizeof (color_id_strs[0]); ++x) {
		pair<ColorID,int> newpair;
		
		newpair.first = (ColorID) x;
		newpair.second = rgba_from_style (enum2str (newpair.first), 0, 0, 0, 255);
		color_map.insert (newpair);
	}
}

Location*
Editor::transport_loop_location()
{
	if (session) {
		return session->locations()->auto_loop_location();
	} else {
		return 0;
	}
}

Location*
Editor::transport_punch_location()
{
	if (session) {
		return session->locations()->auto_punch_location();
	} else {
		return 0;
	}
}
