// UI.Figure.cs created with MonoDevelop
// User: klose at 14:52 29.01.2009
// CVS release: $Id: UI.Figure.cs,v 1.44 2011-06-15 15:52:58 klose Exp $
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2009  Thomas Klose, Hiperscan GmbH Dresden, Germany
//
//    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 3 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, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Reflection;
using System.IO;
using Gtk;

using Hiperscan.SGS;
using Hiperscan.FileIO;
using Hiperscan.Spectroscopy;
using Hiperscan.GUI.Figure;
using Hiperscan.Extensions;
using Hiperscan.Unix;
	
namespace Hiperscan.GUI
{
	
	public enum AxisType
	{
		Wavelength,
		Wavecount
	}
	
	public class PlotInfo
	{
		private string name;
		
		public CurrentSpectra CurrentSpectra { get; private set; }
		public ScrolledPlot ScrolledPlot     { get; private set; }
		public string UUID                   { get; private set; }
		public Gdk.Pixbuf Thumbnail          { get; private set; }
		public TreeIter TreeIter             { get; set; }
		public PlotTypeInfo PlotType         { get; set; }
		public bool IsBackground             { get; set; }
		
		public DataSet.XConstraints x_constraints;
		
		public delegate void ThumbnailChangedHandler(PlotInfo plot_info);
		public event         ThumbnailChangedHandler ThumbnailChanged;
		
		public delegate void NameChangedHandler(string name);
		public event         NameChangedHandler NameChanged;
		
		private static int count = 0;
		
		public PlotInfo(PlotTypeInfo plot_type)
		{
			this.CurrentSpectra = new CurrentSpectra(this);
			this.Name = string.Format(Catalog.GetString("Figure {0}"), ++count);
			this.ScrolledPlot = new ScrolledPlot(this);
			this.ScrolledPlot.HasPopupDialog = true;
			this.UUID = Guid.NewGuid().ToString();
			this.TreeIter = TreeIter.Zero;
			this.PlotType = plot_type;
			this.Thumbnail = this.ScrolledPlot.Plot.Thumbnail;
			
			this.ScrolledPlot.Plot.DataSetsChanged += this.OnDataSetsChanged;
		}
		
		~PlotInfo()
		{
			this.ScrolledPlot.Plot.DataSetsChanged -= this.OnDataSetsChanged;
		}
		
		private void OnDataSetsChanged(object o, EventArgs e)
		{
			this.Thumbnail = this.ScrolledPlot.Plot.Thumbnail;
			if (this.ThumbnailChanged != null)
				this.ThumbnailChanged(this);
		}
		
		public string Name
		{
			get { return this.name; }
			set
			{
				if (this.name == value)
					return;
				this.name = value;
				
				this.NotifyNameChanged();
			}
		}
		
		public void NotifyNameChanged()
		{
			if (this.NameChanged == null)
				return;

			if (this.IsBackground)
				this.NameChanged(this.Name + "*");
			else
				this.NameChanged(this.Name);
		}
		
		public DataSet.XConstraints XConstraints
		{
			get { return this.x_constraints ?? DataSet.XConstraints.Empty; }
			set { this.x_constraints = value; }
		}
	}
	
	public class PlotInfoList : Dictionary<string,PlotInfo>
	{
		private PlotInfo current = null;
		private PlotInfo background = null;
		private List<PlotInfo> list;
		
		public BackgroundType BackgroundType { get; set; }
		
		public delegate void CurrentSpectraChangedHandler(CurrentSpectra current_spectra, bool ds_changed);
		public event         CurrentSpectraChangedHandler CurrentSpectraChanged;
		
		public delegate void LastEntryReachedHandler(CurrentSpectra current_spectra);
		public event         LastEntryReachedHandler LastEntryReached;
		
		public delegate void FirstEntryReachedHandler(CurrentSpectra current_spectra);
		public event         FirstEntryReachedHandler FirstEntryReached;
		
		public delegate void UndoPossibleHandler(CurrentSpectra current_spectra);
		public event         UndoPossibleHandler UndoPossible;
		
		public delegate void RedoPossibleHandler(CurrentSpectra current_spectra);
		public event         RedoPossibleHandler RedoPossible;
		

		public PlotInfoList() : base()
		{
			this.BackgroundType = BackgroundType.Unchanged;
			this.list = new List<PlotInfo>();
		}
		
		public new void Add(string key, PlotInfo item)
		{
			this.ConnectEvents(item.CurrentSpectra);
			
			base.Add(key, item);
			this.list.Add(item);
			
			if (this.current == null)
				this.current = item;
		}
		
		public new bool Remove(string key)
		{
			if (this.ContainsKey(key) == false)
				return false;
			
			this.DisconnectEvents(this[key].CurrentSpectra);
			this.list.Remove(this[key]);
			return base.Remove(key);
		}
		
		public new void Clear()
		{
			foreach (PlotInfo item in this.Values)
			{
				this.DisconnectEvents(item.CurrentSpectra);
			}
			base.Clear();
			this.list.Clear();
		}
		
		public void UpdateBackground()
		{
			foreach (PlotInfo item in this.Values)
			{
				if (this.Background != null && item.UUID == this.Background.UUID)
				{
					item.IsBackground = true;
				}
				else
				{
					item.ScrolledPlot.Plot.ClearBackgroundPlot();
					item.IsBackground = false;
				}
					
				if (this.Background == null || item.UUID == this.Background.UUID)
					continue;

				item.ScrolledPlot.Plot.SetBackgroundPlot(this.Background.ScrolledPlot.Plot, this.BackgroundType);
				item.ScrolledPlot.Plot.RequestUpdate();
			}
		}
		
		private void ConnectEvents(CurrentSpectra item)
		{
			item.Changed           += this.OnCurrentSpectraChanged;
			item.LastEntryReached  += this.OnLastEntryReached;
			item.FirstEntryReached += this.OnFirstEntryReached;
			item.UndoPossible      += this.OnUndoPossible;
			item.RedoPossible      += this.OnRedoPossible;
		}
		
		private void DisconnectEvents(CurrentSpectra item)
		{
			item.Changed           -= this.OnCurrentSpectraChanged;
			item.LastEntryReached  -= this.OnLastEntryReached;
			item.FirstEntryReached -= this.OnFirstEntryReached;
			item.UndoPossible      -= this.OnUndoPossible;
			item.RedoPossible      -= this.OnRedoPossible;
		}

		private void OnCurrentSpectraChanged(ICurrentSpectra current_spectra, bool ds_changed)
		{
			if (this.CurrentSpectraChanged != null)
				this.CurrentSpectraChanged((CurrentSpectra)current_spectra, ds_changed);
		}
		
		private void OnLastEntryReached(CurrentSpectra current_spectra)
		{
			if (this.LastEntryReached != null)
				this.LastEntryReached(current_spectra);
		}
		
		private void OnFirstEntryReached(CurrentSpectra current_spectra)
		{
			if (this.FirstEntryReached != null)
				this.FirstEntryReached(current_spectra);
		}
		
		private void OnUndoPossible(CurrentSpectra current_spectra)
		{
			if (this.UndoPossible != null)
				this.UndoPossible(current_spectra);
		}
		
		private void OnRedoPossible(CurrentSpectra current_spectra)
		{
			if (this.RedoPossible != null)
				this.RedoPossible(current_spectra);
		}

		public PlotInfo Current
		{
			get { return this.current;  }
			set { this.current = value; }
		}
		
		public PlotInfo Background
		{
			get { return this.background; }
			set
			{
				this.background = value;
				this.UpdateBackground();
			}
		}
		
		public PlotInfo this[int ix]
		{
			get
			{
				return this.list[ix];
			}
		}
	}
	

	public partial class UserInterface
	{

		private struct AxisInfo
		{
			public double x_min;
			public double x_max;
			public double y_min;
			public double y_max;			
		}
		
		internal struct SNVInfo
		{
			public double x_min;
			public double x_max;
			public List<double> wavelengths;
			public bool valid;

			public SNVInfo(double x_min, double x_max)
			{
				if (x_min < 0)
					throw new Exception(Catalog.GetString("Lower SNV boundary must not be less than null."));

				if (x_min >= x_max)
					x_max = x_min + 1f;
				
				this.x_min = x_min;
				this.x_max = x_max;

				this.wavelengths = new List<double>();
				for (double lambda = x_min; lambda < x_max; lambda += 1f)
				{
					this.wavelengths.Add(lambda);
				}
				this.wavelengths.Add(x_max);
				
				this.valid = true;
			}
			
			public SNVInfo(bool valid)
			{
				this.x_min = 1000.0;
				this.x_max = 1001.0;
				this.wavelengths = new List<double>();
				this.valid = valid;
			}
		}
		
		private PlotInfoList plot_info_list = new PlotInfoList();
		
		private Dictionary<string,PlotTypeInfo[]> plot_types = new Dictionary<string,PlotTypeInfo[]>();
		private Dictionary<string,FileTypeInfo[]> file_types = new Dictionary<string,FileTypeInfo[]>();
		
		private AxisInfo axis_info;
		
		
		private void InitializeFigure()
		{
			this.plot_info_list.FirstEntryReached += this.OnFirstEntryReached;
			this.plot_info_list.LastEntryReached  += this.OnLastEntryReached;
			this.plot_info_list.UndoPossible      += this.OnUndoPossible;
			this.plot_info_list.RedoPossible      += this.OnRedoPossible;

			if (MainClass.OS == MainClass.OSType.Windows)
			{
				this.add_plot_tab_image.HeightRequest = this.add_plot_tab_image.SizeRequest().Height;
				this.add_plot_tab_image.WidthRequest  = this.add_plot_tab_image.SizeRequest().Width;
				this.add_plot_tab_image.Pixbuf = MainWindow.RenderIcon(Stock.Add, IconSize.Menu, string.Empty);
			}
			
			PlotTypeInfo[] qs_plot_types = new PlotTypeInfo[] {new IntensityPlotTypeInfo(), 
				                                               new AbsorbancePlotTypeInfo(),
				                                               new AbsorbanceSNVPlotTypeInfo(),
				                                               new ReflectancePlotTypeInfo(),
			                                                   new BackgroundPlotTypeInfo()};
			this.plot_types.Add("quickstep", qs_plot_types);
			
			this.plot_notebook.SwitchPage += this.OnPlotNotebookSwitchPage;
			this.plot_notebook.Scrollable = false;
			this.AddPlotTab();

			this.UpdateFigureControls();
			this.UpdatePlotTypes();
			
			this.axis_info.x_min = this.x_min_spin.Value;
			this.axis_info.x_max = this.x_max_spin.Value;
			this.axis_info.y_min = this.y_min_spin.Value;
			this.axis_info.y_max = this.y_max_spin.Value;
			
			this.SetLockAxesImage(false);
			this.lock_axes_image.Sensitive = false;
		}
		
		private void AddPlotTab()
		{
			PlotInfo plot_info = new PlotInfo(this.plot_types["quickstep"][0]);
			
			plot_info.ScrolledPlot.Plot.ReverseXAxes = Settings.Current.AxisType == AxisType.Wavecount;
			plot_info.ScrolledPlot.XAxesReversed += delegate(IPlotEngine plot) {
				Hiperscan.Unix.Timeout.Add(50, delegate {
					plot.FitView();
					this.ScrolledPlot.XZoom = 1.0;
					this.ScrolledPlot.YZoom = 1.0;
					return false;
				});
			};

			this.plot_info_list.Add(plot_info.UUID, plot_info);
			int pos = this.plot_notebook.NPages - 1;
			
			this.plot_info_list.UpdateBackground();
			this.plot_tv.AddFigure(plot_info);

			NotebookLabel nl = new NotebookLabel(plot_info.Name, null);
			plot_info.NameChanged += delegate(string name) { 
				nl.Label = name; 
			};
			nl.CloseClicked += delegate { 
				this.RemovePlotTab(plot_info); 
			};
			nl.Button3Clicked += delegate {
				int _pos = this.plot_notebook.PageNum(plot_info.ScrolledPlot);
				this.plot_notebook.CurrentPage = _pos;
				this.figure_menu.Popup();
			};
			
			int ix = this.plot_notebook.InsertPage(plot_info.ScrolledPlot, nl, pos);
			plot_info.ScrolledPlot.ShowAll();
			this.plot_notebook.CurrentPage = ix;

			this.ScrolledPlot.PointerCoordsChanged += delegate(double x, double y) {
				this.statusbar2.Pop(0);
				this.statusbar2.Push(0, x.ToString("f2") + " : " + y.ToString("f3"));
			};
			
			this.ScrolledPlot.Plot.LegendVisible = !this.plot_browser_button.Active || this.legend_checkbutton.Active;

			this.ScrolledPlot.Plot.PlotChanged        += this.OnPlotChanged;
			this.ScrolledPlot.Plot.AutoRemoveLegend   += this.OnAutoRemoveLegend;
			this.ScrolledPlot.Plot.GtkWidget.StyleSet += delegate(object o, StyleSetArgs args) {
				foreach (PlotInfo p_info in this.plot_info_list.Values)
				{
					p_info.ScrolledPlot.Plot.BackgroundColor = this.BackgroundColor;
				}
			};
			this.ScrolledPlot.Plot.BackgroundColor = this.BackgroundColor;
			this.ScrolledPlot.Plot.LockAxes = this.lock_axes_item.Active;

			this.plot_info_list.Current.CurrentSpectra.NotifyChanges();
			
			this.OnPlotChanged(this.ScrolledPlot.Plot, EventArgs.Empty);
			this.UpdatePlotNotebookLabels();
			this.PlotType = this.plot_types["quickstep"][0];
		}
		
		public void RemovePlotTab(PlotInfo plot_info)
		{
//			if (this.ScrolledPlot == plot_info.ScrolledPlot)
//				this.StopAllStreams();
			
			if (this.plot_notebook.NPages < 3)
				return;

			Console.WriteLine("Remove figure tab: " + this.plot_notebook.CurrentPage);
			
			if (this.plot_notebook.CurrentPage == this.plot_notebook.NPages-2)
				this.plot_notebook.CurrentPage--;
			else 
				this.plot_notebook.CurrentPage++;
			
			this.plot_notebook.Remove(plot_info.ScrolledPlot);
			plot_info.ScrolledPlot.Dispose();
		
			if (plot_info_list.Background!= null && plot_info_list.Background.UUID == plot_info.UUID)
				this.plot_info_list.Background = null;
			
			this.plot_info_list.Remove(plot_info.UUID);
			this.plot_tv.RemoveFigure(plot_info);
			
			this.UpdatePlotNotebookLabels();
			this.OnPlotNotebookSwitchPage(null, EventArgs.Empty);
			
			this.UpdateFigureControls();
		}
		
		private void UpdatePlotNotebookLabels()
		{
			bool button_visible = this.plot_info_list.Count > 1;
			foreach (Widget w in this.plot_notebook.Children)
			{
				NotebookLabel nll = this.plot_notebook.GetTabLabel(w) as NotebookLabel;
				
				if (nll == null)
					continue;
				
				if (this.plot_notebook.GetTabLabel(this.plot_notebook.CurrentPageWidget) == nll)
					nll.ButtonVisible = button_visible;
				else
					nll.ButtonVisible = false;
			}
			
			this.close_tab_item.Sensitive = button_visible;
		}

		public void AddSpectrum(Spectrum spectrum)
		{
			this.AddSpectrum(this.plot_info_list.Current, spectrum);
		}
		
		public void AddSpectrum(Spectrum spectrum, bool add_history_entry, bool notify)
		{
			this.AddSpectrum(this.plot_info_list.Current, spectrum, add_history_entry, notify);
		}

		public void AddSpectrum(Spectrum spectrum, bool add_history_entry, bool notify, bool ignore_interdependence)
		{
			this.AddSpectrum(this.plot_info_list.Current, spectrum, add_history_entry, notify, ignore_interdependence);
		}

		public void AddSpectrum(PlotInfo plot_info, Spectrum spectrum)
		{
			this.AddSpectrum(plot_info, spectrum, true, true);
		}
		
		public void AddSpectrum(PlotInfo plot_info, Spectrum spectrum, bool add_history_entry, bool notify)
		{
			this.AddSpectrum(plot_info, spectrum, add_history_entry, notify, false);
		}
		
		public void AddSpectrum(PlotInfo plot_info, Spectrum spectrum, bool add_history_entry, bool notify, bool ignore_interdependence)
		{
			if (spectrum == null)
				return;

			if (!spectrum.HasBackground && !spectrum.IsFromFile)
			{
				try
				{
					Device dev = this.device_list[spectrum.Serial];
					
					if (dev.CurrentConfig.HasBackgroundSpectrum)
						spectrum.Background = dev.CurrentConfig.BackgroundSpectrum;
				}
				catch (Exception ex)
				{
					Console.WriteLine("Cannot set background spectrum: " + ex.Message);
				}	
			}

			if (!spectrum.HasDarkIntensity && !spectrum.IsFromFile)
			{
				try
				{
					Device dev = this.device_list[spectrum.Serial];
					spectrum.DarkIntensity = dev.CurrentConfig.DarkIntensity;
				}
				catch (Exception ex)
				{
					Console.WriteLine("Cannot set dark intensity: " + ex.Message);
				}	
			}
			
			if (!spectrum.HasWavelengthCorrection && !spectrum.IsFromFile)
			{
				try
				{
					Device dev = this.device_list[spectrum.Serial];
					spectrum.WavelengthCorrection = dev.CurrentConfig.WavelengthCorrection;
				}
				catch (Exception ex)
				{
					Console.WriteLine("Cannot set wavelength correction: " + ex.Message);
				}	
			}

			if (!plot_info.CurrentSpectra.Contains(spectrum.Id))
			{
				if ((spectrum.SpectrumType == SpectrumType.Single ||
				     spectrum.SpectrumType == SpectrumType.Unknown) &&
				    add_history_entry)
					plot_info.CurrentSpectra.AddHistoryEntry();

				plot_info.CurrentSpectra.Add(spectrum.Id, spectrum, notify);
			}
			else
			{
				plot_info.CurrentSpectra[spectrum.Id] = spectrum;
				if (notify)
					plot_info.CurrentSpectra.NotifyChanges();
			}

			
			if (plot_info == this.plot_info_list.Current && this.PlotType is UndefinedPlotTypeInfo)
			{
				this.PlotType = this.plot_types["quickstep"][0];
				return;
			}
			
			if (plot_info.PlotType.Interdependent && !ignore_interdependence)
			{
				this.UpdateCurrentPlot();
				return;
			}

			DataSet ds;
			try
			{
				plot_info.PlotType.Reset();
				ds = plot_info.PlotType.ComputeDataSet(spectrum, plot_info.XConstraints);
				ds = plot_info.XConstraints.Apply(ds);
			}
			catch (Exception ex)
			{
				new InfoMessageNonModal(this.parent, 
				                        Catalog.GetString("Cannot display spectrum \"{0}\": {1}"),
				                        spectrum.Label, ex.Message);
				Console.WriteLine(ex.ToString());
				ds = null;
			}
			
			this.bg_message_is_pending = this.PlotType.NeedAbsorbance && !spectrum.HasAbsorbance;

			if (ds == null)
				return;
			
			ds.XLabel = this.PlotType.XLabel;
			ds.YLabel = this.PlotType.YLabel;

			if (Settings.Current.AxisType == AxisType.Wavecount)
				UserInterface.ConvertXAxisToWavecount(ref ds);

			ds.CopyPropertiesFrom(spectrum);
			
			if (plot_info.ScrolledPlot.Plot.DataSets.ContainsKey(ds.Id))
				plot_info.ScrolledPlot.Plot.DataSets[ds.Id] = ds;
			else
				plot_info.ScrolledPlot.Plot.DataSets.Add(ds.Id, ds);
		}
		
		private void OnPlotNotebookSwitchPage(object o, EventArgs e)
		{
			int ix = this.plot_notebook.CurrentPage;
			Console.WriteLine("Switching tab. Current page: " + ix);

			if (ix == this.plot_notebook.NPages-1)
			{
				this.AddPlotTab();
				return;
			}
			
			if (this.plot_info_list.Current.CurrentSpectra.Contains("9_preview"))
			{
				this.plot_info_list.Current.CurrentSpectra.Remove("9_preview", false);
				this.UpdatePlot(this.plot_info_list.Current);
			}

			this.plot_info_list.Current = this.plot_info_list[ix];
			this.__SetPlotTypeCombo(this.plot_info_list.Current.PlotType.Name, false);

			this.ScrolledPlot.Plot.LegendVisible = !this.plot_browser_button.Active || this.legend_checkbutton.Active;
			this.ScrolledPlot.Plot.LockAxes = this.lock_axes_item.Active;

			this.UIPlotControlsSensitive(!this.ScrolledPlot.Plot.IsEmpty);
			this.UpdatePlotNotebookLabels();
			this.plot_tv.Update();
		}
		
		private void OnSpectrumDoubleClicked(Spectrum spectrum)
		{
			this.CurrentSpectra.AddHistoryEntry();
			this.AddSpectrum(spectrum);
			if (!this.preview_checkbutton.Active)
				this.ScrolledPlot.Plot.RequestUpdate();
		}
		
		private void OnOpenAllClicked(List<Spectrum> spectra)
		{
			this.CurrentSpectra.AddHistoryEntry();

			double count = spectra.Count;
			int ix = 0;

			foreach (Spectrum spectrum in spectra)
			{
				this.ShowProgress((double)(ix++)/count);
				this.AddSpectrum(this.plot_info_list.Current, spectrum, false, false, ix < count);
			}
			this.ScrolledPlot.Plot.RequestUpdate();
			this.CurrentSpectra.NotifyChanges();
			this.HideProgress();
		}
		
		private void OnPlotChanged(object o, EventArgs e)
		{
			IPlotEngine plot = o as IPlotEngine;
			
			if (plot == this.ScrolledPlot.Plot)
			{
			
				if (this.ScrolledPlot.Plot.IsEmpty)
				{
					this.UIPlotControlsSensitive(false);
					this.statusbar2.Pop(0);
					return;
				}

				if (plot.ReverseXAxes && plot.XLabel.Contains(Catalog.GetString("Wavelength / nm")))
					plot.XLabel = plot.XLabel.Replace(Catalog.GetString("Wavelength / nm"), Catalog.GetString("Wavenumber / cm^-1"));
				else if (!plot.ReverseXAxes && plot.XLabel.Contains(Catalog.GetString("Wavenumber / cm^-1")))
					plot.XLabel = plot.XLabel.Replace(Catalog.GetString("Wavenumber / cm^-1"), Catalog.GetString("Wavelength / nm"));
				
				if (plot.HasBackgroundPlot)
				{
					plot.UseSecondAxes = true;
					if (plot.ReverseXAxes && plot.XLabel.Contains(Catalog.GetString("Wavelength / nm")))
						plot.XLabel = plot.XLabel.Replace(Catalog.GetString("Wavelength / nm"), Catalog.GetString("Wavenumber / cm^-1"));
					else if (!plot.ReverseXAxes && plot.XLabel.Contains(Catalog.GetString("Wavenumber / cm^-1")))
						plot.XLabel = plot.XLabel.Replace(Catalog.GetString("Wavenumber / cm^-1"), Catalog.GetString("Wavelength / nm"));
					plot.UseSecondAxes = false;
				}

				this.UpdateMinMaxSpinAdjustments();
				this.UIPlotControlsSensitive(true);
			
				this.zoom_out_button.Sensitive = (this.ScrolledPlot.XZoom > 1.0);
				this.zoom_out_item.Sensitive   = (this.ScrolledPlot.XZoom > 1.0);
				this.zoom_in_button.Sensitive  = (this.ScrolledPlot.XZoom < this.ScrolledPlot.MaxZoom);
				this.zoom_in_item.Sensitive    = (this.ScrolledPlot.XZoom < this.ScrolledPlot.MaxZoom);
			}
			
			if (this.plot_info_list.Background != null && 
			    this.plot_info_list.Background.ScrolledPlot.Plot == plot)
			{
				this.plot_info_list.UpdateBackground();
			}
		}
		
		private void UpdateXConstraintsEntry()
		{
			if (this.plot_info_list.Current.XConstraints.IsEmpty)
			{
				this.edit_x_constraints_button.Active = false;
			}
			else
			{
				this.edit_x_constraints_button.Active = true;
				this.x_constraints_entry.Text = this.plot_info_list.Current.XConstraints.ToString();
			}
		}
		
		private void UpdateMinMaxSpinAdjustments()
		{
			if (!this.ScrolledPlot.Plot.IsValid)
				return;
			
			Adjustment adjust;
			
			lock (this.ScrolledPlot.Plot)
			{
				if (this.ScrolledPlot.Plot.ReverseXAxes)
				{
					adjust = new Adjustment(this.ScrolledPlot.XMax,
					                        this.ScrolledPlot.XMin,
					                        this.ScrolledPlot.Plot.GlobalXMax,
					                        50f, 100f, 0f);
					if (this.x_min_spin.Adjustment != adjust)
						this.x_min_spin.Adjustment  = adjust;
					if (this.axis_info.x_min != this.ScrolledPlot.XMax)
					{
						this.x_min_spin.Value = this.ScrolledPlot.XMax;
						this.axis_info.x_min  = this.ScrolledPlot.XMax;
					}
					
					adjust = new Adjustment(this.ScrolledPlot.XMin,
					                        this.ScrolledPlot.Plot.GlobalXMin,
					                        this.ScrolledPlot.XMax,
					                        50f, 100f, 0f);
					if (this.x_max_spin.Adjustment != adjust)
						this.x_max_spin.Adjustment  = adjust;
					if (this.axis_info.x_max != this.ScrolledPlot.XMin)
					{
						this.x_max_spin.Value = this.ScrolledPlot.XMin;
						this.axis_info.x_max  = this.ScrolledPlot.XMin;
					}
				}
				else
				{
					if (this.x_min_spin.Digits != 0)
						this.x_min_spin.Digits = 0;
					adjust = new Adjustment(this.ScrolledPlot.XMin,
					                        this.ScrolledPlot.Plot.GlobalXMin,
					                        this.ScrolledPlot.XMax,
					                        10f, 100f, 0f);
					if (this.x_min_spin.Adjustment != adjust)
						this.x_min_spin.Adjustment  = adjust;
					if (this.axis_info.x_min != this.ScrolledPlot.XMin)
					{
						this.x_min_spin.Value = this.ScrolledPlot.XMin;
						this.axis_info.x_min  = this.ScrolledPlot.XMin;
					}
					
					if (this.x_max_spin.Digits != 0)
						this.x_max_spin.Digits = 0;
					adjust = new Adjustment(this.ScrolledPlot.XMax,
					                        this.ScrolledPlot.XMin,
					                        this.ScrolledPlot.Plot.GlobalXMax,
					                        10f, 100f, 0f);
					if (this.x_max_spin.Adjustment != adjust)
						this.x_max_spin.Adjustment  = adjust;
					if (this.axis_info.x_max != this.ScrolledPlot.XMax)
					{
						this.x_max_spin.Value = this.ScrolledPlot.XMax;
						this.axis_info.x_max  = this.ScrolledPlot.XMax;
					}
				}
				
				adjust = new Adjustment(this.ScrolledPlot.YMin,
				                        this.PlotType.LowerLimit,
				                        this.ScrolledPlot.YMax,
				                        0.05f, 0.5f, 0f);
				if (this.y_min_spin.Adjustment != adjust)
					this.y_min_spin.Adjustment  = adjust;
				if (this.axis_info.y_min != this.ScrolledPlot.YMin)
				{
					this.y_min_spin.Value = this.ScrolledPlot.YMin;
					this.axis_info.y_min  = this.ScrolledPlot.YMin;
				}
				
				adjust = new Adjustment(this.ScrolledPlot.YMax,
				                        this.ScrolledPlot.Plot.YMin,
				                        this.PlotType.UpperLimit,
				                        0.05f, 0.5f, 0f);
				if (this.y_max_spin.Adjustment != adjust)
					this.y_max_spin.Adjustment  = adjust;
				if (this.axis_info.y_max != this.ScrolledPlot.YMax)
				{
					this.y_max_spin.Value = this.ScrolledPlot.YMax;
					this.axis_info.y_max  = this.ScrolledPlot.YMax;
				}
			}
		}
		
		private bool disable_auto_remove_legend_message = false;
		
		private void OnAutoRemoveLegend(object o, EventArgs e)
		{
			if (!this.disable_auto_remove_legend_message)
			{
				new StatusMessage(this.parent,
				                  Catalog.GetString("The plot legend has been removed because its dimensions " +
				                                    "would exceed the size of the plot window."));
				this.disable_auto_remove_legend_message = true;
			}
		}
		
		internal void OnPreviewCheckbuttonToggled(object o, System.EventArgs e)
		{
			CheckButton c = o as CheckButton;
			
			if (!c.Active && this.plot_info_list.Current.CurrentSpectra.Contains("9_preview"))
			{
				this.plot_info_list.Current.CurrentSpectra.Remove("9_preview", false);
				this.UpdatePlot(this.plot_info_list.Current);
			}
			else
			{
				if (this.file_notebook.Children.Length == 0)
					return;
				
				int ix = this.file_notebook.CurrentPage;
				ScrolledFileTv sf = file_notebook.Children[ix] as ScrolledFileTv;

				if (sf == null)
					return;
				
				this.OnFileTvSpectrumSelectionChanged(sf.GetSelectedSpectra(sf.Tv.Selection));
			}
			
			UserInterface.Settings.Current.ShowPreviewInPlot = c.Active;
		}
		
		private void OnFileNotebookChangeCurrentPage(object o, System.EventArgs e)
		{
			if (this.file_notebook.NPages == 0)
				return;
			
			int ix = this.file_notebook.CurrentPage;
			ScrolledFileTv sf = file_notebook.Children[ix] as ScrolledFileTv;
			
			if (sf == null)
				return;
			
			this.OnFileTvSpectrumSelectionChanged(sf.GetSelectedSpectra(sf.Tv.Selection));			
		}
		
		private void OnFileTvSpectrumSelectionChanged(List<Spectrum> selected_spectra)
		{
			if (!this.preview_checkbutton.Active)
				return;
			
			if (selected_spectra.Count == 0)
			{
				if (this.plot_info_list.Current.CurrentSpectra.Contains("9_preview"))
				{
					this.plot_info_list.Current.CurrentSpectra.Remove("9_preview", false);
					this.UpdatePlot(this.plot_info_list.Current);
				}
				return;
			}

			Spectrum spectrum = new Spectrum(selected_spectra[0]);
			spectrum.Id    = "9_preview";
			spectrum.Label = Catalog.GetString("Preview");
			spectrum.SpectrumType = SpectrumType.Preview;
			this.AddSpectrum(spectrum);
			this.ScrolledPlot.Plot.RequestUpdate();
		}
		
		private void OnFileNotebookRemoved(object o, System.EventArgs e)
		{
			Notebook nb = o as Notebook;
			
			if (nb.Children.Length == 0)
			{
				this.preview_checkbutton.Sensitive = false;
				if (this.plot_info_list.Current.CurrentSpectra.Contains("9_preview"))
				{
					this.plot_info_list.Current.CurrentSpectra.Remove("9_preview", false);
					this.UpdatePlot(this.plot_info_list.Current);
				}

				return;
			}
			
			int ix = this.file_notebook.CurrentPage;
			ScrolledFileTv sf = null;

			if (file_notebook.Children.Length > ix)
				sf = file_notebook.Children[ix] as ScrolledFileTv;
			
			if (sf == null)
				return;
			
			this.OnFileTvSpectrumSelectionChanged(sf.GetSelectedSpectra(sf.Tv.Selection));			
		}
		
		public void ClearPlot()
		{
			this.plot_info_list.Current.CurrentSpectra.AddHistoryEntry();
			this.plot_info_list.Current.CurrentSpectra.Clear();
			this.ScrolledPlot.Plot.Annotations.Clear();
			this.ScrolledPlot.Plot.Clear();
			this.ScrolledPlot.XZoom = 1.0;
			this.ScrolledPlot.YZoom = 1.0;
			if (this.plot_info_list.Background != null && 
			    this.plot_info_list.Background.UUID == this.plot_info_list.Current.UUID)
				this.OnClearBackgroundItemClicked(this.clear_background_item, EventArgs.Empty);
		}
		
		public void FitPlot()
		{
			this.ScrolledPlot.Plot.FitView();
		}
		
		public void UpdateCurrentPlot()
		{
			this.UpdatePlot(this.plot_info_list.Current);
		}
		
		internal void UpdatePlot(List<string> plot_ids)
		{
			foreach (string id in plot_ids)
			{
				this.UpdatePlot(this.plot_info_list[id]);
			}
		}
		
		internal void UpdatePlot(PlotInfo plot_info)
		{
			if (plot_info.ScrolledPlot == null)
				return;

			plot_info.ScrolledPlot.Plot.DataSets.Clear();
			
			this.bg_message_is_pending = false;

			// we need a copy because the list will be changed within the loop
			CurrentSpectra spectra = new CurrentSpectra(plot_info.CurrentSpectra); 

			double count = spectra.Count;
			int ix = 0;
			foreach (Spectrum spectrum in spectra.Values)
			{
				this.ShowProgress((double)(ix++)/count);
				this.AddSpectrum(plot_info, spectrum, false, false, true);
			}
			this.plot_info_list.Current.CurrentSpectra.NotifyChanges();

			this.plot_tv.Update();
			plot_info.ScrolledPlot.Plot.RequestUpdate();

			this.HideProgress();
		}
		
		private void CopyToClipboard(IPlotEngine plot, bool figure_only)
		{
            if (MainClass.OS == MainClass.OSType.Windows || figure_only == false)
			{
				this.CopyToClipboardWin32(plot, figure_only);
				return;
			}
			
			this.clipboard_pixbuf_data = plot.GetPixbuf(this.ScrolledPlot.Plot.Width,
			                                            this.ScrolledPlot.Plot.Height,
			                                            150f, 150f);
			this.clipboard_csv_data = this.GetDataSetsString(plot);

			Clipboard clipboard = Clipboard.Get(Gdk.Selection.Clipboard);
			TargetEntry[] targets = new TargetEntry[figure_only ? 1 : 3];
			targets[0] = new TargetEntry("image/bmp",  TargetFlags.OtherApp, 0);
			if (!figure_only)
            {
                targets[1] = new TargetEntry("text/plain", TargetFlags.OtherApp, 1);
                targets[2] = new TargetEntry("text/plain;charset=utf-8", TargetFlags.OtherApp, 2);
            }

			clipboard.SetWithData(targets,
			                      new ClipboardGetFunc(this.ClipboardGetFunc), 
			                      new ClipboardClearFunc(this.ClipboardClearFunc)
			                      );
		}
		
		private string GetDataSetsString(IPlotEngine plot)
		{
			List<string> headers = new List<string>();
			headers.Add(string.Empty);
			foreach (DataSet ds in plot.DataSets.Values)
			{
				headers.Add(ds.Label);
			}
			
			List<DataSet> data_sets = new List<DataSet>(plot.DataSets.Values);
			
			MemoryStream stream = new MemoryStream();
			StreamWriter writer = new StreamWriter(stream);
			
			switch (Settings.Current.AxisType)
			{
				
			case AxisType.Wavelength:
				headers[0] = Catalog.GetString("Wavelength / nm");
				CSV.Write(headers, data_sets, 1.0, writer, this.OutputCultureInfo(OutputCulture.System));
				break;
							
			case AxisType.Wavecount:
				headers[0] = Catalog.GetString("Wavenumber / cm^-1");
				CSV.Write(headers, data_sets, 5.0, writer, this.OutputCultureInfo(OutputCulture.System));
				break;
			
			}
			
			writer.Flush();
			stream.Position = 0;
			StreamReader reader = new StreamReader(stream);
			
			string separator = this.OutputCultureInfo(OutputCulture.System).TextInfo.ListSeparator;
			return reader.ReadToEnd().Replace(separator, "\t");
		}
		
		private void CopyToClipboardWin32(IPlotEngine plot, bool figure_only)
		{
			System.Windows.Forms.DataObject data_object = new System.Windows.Forms.DataObject();
			Bitmap bmp = plot.GetBitmap(this.ScrolledPlot.Plot.Width,
			                            this.ScrolledPlot.Plot.Height,
			                            100f, 100f);
			if (!figure_only)
				data_object.SetData(System.Windows.Forms.DataFormats.Text, true, this.GetDataSetsString(plot));
			
			data_object.SetData(System.Windows.Forms.DataFormats.Bitmap, true, bmp);
			System.Windows.Forms.Clipboard.SetDataObject(data_object, true);
		}
		
		internal static void ConvertXAxisToWavecount(ref DataSet ds)
		{
			for (int ix=0; ix < ds.Count; ++ix)
			{
				ds.X[ix] = WavelengthToWavecount(ds.X[ix]);
			}
		}
		
		internal static double WavelengthToWavecount(double wl)
		{
			return 1e7 / wl;
		}
		
		internal static double WavecountToWavelength(double wl)
		{
			return 1e7 / wl;
		}

		public void AddToCurrentPlot(Spectrum spectrum)
		{
			this.AddSpectrum(spectrum);
			this.ScrolledPlot.Plot.RequestUpdate();
		}

		public void AddToCurrentPlot(List<Spectrum> spectra)
		{
			this.CurrentSpectra.AddHistoryEntry();
			for (int ix=0; ix < spectra.Count; ++ix)
			{
				this.ShowProgress((double)ix / (double)spectra.Count);
				bool ignore_interdependence = ix < spectra.Count-1;
				this.AddSpectrum(spectra[ix], false, false, ignore_interdependence);
			}
			this.ScrolledPlot.Plot.RequestUpdate();
			this.CurrentSpectra.NotifyChanges();
			this.HideProgress();
		}
		
		public string GetCurrentPlotType()
		{
			return this.PlotType.Name;
		}
		
		public bool TrySetCurrentPlotType(string plot_type_name)
		{
			foreach (string key in this.plot_types.Keys)
			{
				foreach (PlotTypeInfo plot_type in this.plot_types[key])
				{
					if (plot_type.Name == plot_type_name || 
					    plot_type.Name == Catalog.GetString(plot_type_name))
					{
						this.PlotType = plot_type;
						return true;
					}
				}
			}
			return false;
		}
		
		public static Font GetFontFromString(string font_name)
		{
			try
			{
				Regex regex = new Regex(" ");
				
				string[] s = regex.Split(font_name);
				string size = s[s.Length-1];
				
				string family = font_name.Replace(size, string.Empty).Trim();
				
				FontStyle style = FontStyle.Regular;
				if (font_name.ToLower().Contains("bold"))
					style = FontStyle.Bold;
				else if (font_name.ToLower().Contains("italic") || 
				         font_name.ToLower().Contains("oblique"))
					style = FontStyle.Italic;
				
				return new Font(family, float.Parse(size), style, GraphicsUnit.Pixel);
			}
			catch (Exception ex)
			{
				Console.WriteLine("Cannot convert font type '{0}': {1}", font_name, ex.Message);
				Console.WriteLine("Setting fall back font.");
				return new Font(FontFamily.GenericSansSerif, 12f, GraphicsUnit.Pixel);
			}
		}
		
		private bool CancelAnnotations()
		{
			bool canceled = false;
			
			foreach (Annotation a in new List<Annotation>(this.ScrolledPlot.Plot.Annotations))
			{
				if (a.Interactive)
				{
					this.ScrolledPlot.Plot.Annotations.Remove(a);
					a.Interactive = false;
					canceled = true;
				}
			}
			
			return canceled;
		}

		public double SnvLowerBoundary
		{
			get { return Settings.Current.SNVLowerBoundary; }
		}
		
		public double SnvUpperBoundary
		{
			get { return Settings.Current.SNVUpperBoundary; }
		}
	}
}
