// UI.cs created with MonoDevelop
// User: klose at 13:33 27.12.2008
// CVS release: $Id: UI.cs,v 1.94 2011-06-24 13:48:23 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.IO;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading;
using System.Linq;

using Gtk;
using Glade;

using Hiperscan.Spectroscopy;
using Hiperscan.FileIO;
using Hiperscan.SGS;
using Hiperscan.GUI.Figure;
using Hiperscan.Extensions;
using Hiperscan.Unix;

namespace Hiperscan.GUI
{

	public partial class UserInterface : Hiperscan.Extensions.IHost
	{
		[Widget] Gtk.VBox ui_vbox = null;
		
		[Widget] Gtk.MenuBar menubar = null;
		
//		[Widget] Gtk.Frame plotframe          = null;
		[Widget] Gtk.Frame data_sources_frame = null;
		[Widget] Gtk.Frame spectrometer_frame = null; 
		[Widget] Gtk.Frame plot_browser_frame = null; 
		[Widget] Gtk.Frame plot_tv_frame      = null; 

		[Widget] Gtk.HandleBox property_handlebox = null;

		[Widget] Gtk.Paned data_source_paned  = null;
		[Widget] Gtk.Paned spectrometer_paned = null;
		[Widget] Gtk.VBox  property_vbox = null;
				
		[Widget] Gtk.Statusbar statusbar  = null;
		[Widget] Gtk.Statusbar statusbar2 = null;

		[Widget] Gtk.ProgressBar progressbar = null;
		
		[Widget] Gtk.Notebook plot_notebook     = null;
		[Widget] Gtk.Notebook file_notebook     = null;
		[Widget] Gtk.Notebook property_notebook = null;

		
		[Widget] Gtk.Frame property_frame = null;
		[Widget] Gtk.Button attach_property_editor_button = null;
		
		[Widget] Gtk.CheckButton preview_checkbutton = null;
		[Widget] Gtk.CheckButton legend_checkbutton  = null;
		
		[Widget] Gtk.Label current_object_label = null;
		
		[Widget] Gtk.Menu help_menu = null;
		[Widget] Gtk.ImageMenuItem help_item = null;
		[Widget] Gtk.ImageMenuItem quit_item = null;
		[Widget] Gtk.SeparatorMenuItem help_separator_item = null;
		
		[Widget] Gtk.Alignment service_dialog_align   = null;
		[Widget] Gtk.Alignment extract_data_align     = null;
		[Widget] Gtk.Alignment browse_directory_align = null;
		
		[Widget] Gtk.Image add_plot_tab_image = null;
		
	
		internal static Queues        Queues;
		internal static List<Thread>  Threads;
		internal static UserInterface UI;
		
		private List<Widget> standard_toolbar_widgets  = new List<Widget>();
		private List<Widget> extension_toolbar_widgets = new List<Widget>();
		
		private Dictionary<string,FileFilter> file_filters = new Dictionary<string,FileFilter>();
		
		internal Glade.XML   gui;
		private Window       parent;

		private ScrolledSpectrometerTv spectrometer_tv; 
		private ScrolledPlotTv         plot_tv;
		
		private volatile bool initialized = false;
		private bool bg_message_is_pending = false;

		private Gdk.Pixbuf clipboard_pixbuf_data;
		private string clipboard_csv_data;
		
		public event EventHandler<EventArgs> ReplaceGui;
		public event EventHandler<EventArgs> Exit;
		public event EventHandler<EventArgs> UnhandledException;
		public event EventHandler<EventArgs> InfoMessageShown;
		
		
		public UserInterface(Window parent)
		{
			this.parent = parent;
			UserInterface.UI = this;

			// FIXME: replace libglade with GtkBuilder if available (gtk-sharp >= 2.14)
//			Glade.Global.RegisterWidget(Gtk.LinkButton.GType, 
//			                            new NewFunc(UserInterface.NewLabelButton),
//			                            null, null);
			Console.WriteLine("Create GUI...");
			try
			{
				this.gui = new Glade.XML(null, "GUI.UserInterface.glade", "ui_vbox", MainClass.ApplicationDomain);				
//				this.gui = new Glade.XML("UserInterface.glade", "ui_vbox", MainClass.ApplicationDomain);				
			}
			catch(Exception ex)
			{
				throw new Exception("Cannot create GUI: " + ex.Message);
			}
			Console.WriteLine("Autoconnect signals...");
			gui.Autoconnect(this);
			this.parent.AddAccelGroup(this.gui.EnsureAccel());
			LinkButton.SetUriHook(delegate(LinkButton l, string s) {});
			
			Console.WriteLine("Initialize components:");

			Console.WriteLine("\tSettings...");
			this.LoadSettings();
			
			Console.WriteLine("\tThemes...");
			this.InitializeThemes();
			Console.WriteLine("\tQueues...");
			this.InitializeQueues();
			Console.WriteLine("\tMainWindow...");
			this.InitializeMainWindow();
			Console.WriteLine("\tSpectrometerTv...");
			this.InitializeSpectrometerTv();
			Console.WriteLine("\tPlotTv...");
			this.InitializePlotTv();
			Console.WriteLine("\tFigure...");
			this.InitializeFigure();
			Console.WriteLine("\tLinkButtons...");
			this.InitializeLinkButtons();
			Console.WriteLine("\tFileTv...");
			this.InitializeFileTv();
			Console.WriteLine("\tPropertiesSpectrometer...");
			this.InitializePropertiesSpectrometer();
			Console.WriteLine("\tPropertiesDirectory...");
			this.InitializePropertiesDirectory();
			Console.WriteLine("\tPropertiesSpectrum...");
			this.InitializePropertiesSpectrum();
			Console.WriteLine("\tTools...");
			this.InitializeTools();
			Console.WriteLine("\tFileTypes...");
			this.InitializeFileTypes();
			Console.WriteLine("\tMenu...");
			this.InitializeMenu();
			Console.WriteLine("\tToolbar...");
			this.InitializeToolbar();
			Console.WriteLine("\tStatusbar...");
			this.InitializeStatusBar();
			Console.WriteLine("\tPlugins...");
			this.InitializePlugins();
			Console.WriteLine("\tAcceleration editor...");
			this.InitializeAccelerationEditor();
			Console.WriteLine("\tReleaseVersion...");
			this.InitializeReleaseVersion();
			Console.WriteLine("Init done.");
			this.initialized = true;
//			Thread tester = new Thread(new ParameterizedThreadStart(Tester.DoGenerateSpectra));
//			tester.IsBackground = true;
//			tester.Start(this);
		}
		
		public static Gtk.Widget NewLabelButton(Glade.XML xml, GLib.GType widget_type, Glade.WidgetInfo info)
		{
			return new LinkButton(info.properties.Value);
		}
		
		public bool DoPostInitTasks()
		{
//			this.background_color = Rc.GetStyle(this.ScrolledPlot.Plot.GtkWidget).Background(StateType.Normal);
//			this.ScrolledPlot.Plot.BackgroundColor = this.BackgroundColor;
			
			UserInterface.Queues.StartProcessing();
			
			if (MainClass.AutoSharePorts.Count > 0)
			{
				GLib.Timeout.Add(15000, delegate {
					foreach (uint port in MainClass.AutoSharePorts)
					{
						foreach (Device dev in this.DeviceList.Values)
						{
							if (!dev.IsIdle)
								continue;

							this.ShareDevice(this.parent, dev, port);
							break;
						}
					}
					TrayIcon.SetTooltip(SharedUSB.SharedCount, SharedUSB.ConnectedCount);
					return false;
				});
			}
			
			StatusMessage.EnableMessages = true;

			if (Environment.GetEnvironmentVariable(Hiperscan.Common.Env.QuickStep.NO_DEVICE_HOTPLUG) != "1")
			{
				Thread t = new Thread(new ThreadStart(delegate { this.device_list.StartAutoUpdate(); }));
				t.Start();
			}
			else
			{
				Thread t = new Thread(new ThreadStart(delegate { this.device_list.Update(); }));
				t.Start();
			}

			if (this.ReplaceGui != null)
				this.ReplaceGui(null, EventArgs.Empty);

			bool changed = false;
			int plot_tab_page = this.plot_notebook.CurrentPage;
			
			foreach (string fname in MainClass.AutoOpenFiles)
			{
				try
				{
					Spectrum[] spectra;
					List<MetaData> meta_data = null;
					
					bool is_container;
					
					if (fname.ToLower().EndsWith(".csv"))
					{
						spectra = new Spectrum[] { CSV.Read(fname) };
						is_container = false;
					}
					else if (fname.ToLower().EndsWith(".csvc"))
					{
						spectra = CSV.ReadContainer(fname, out meta_data);
						is_container = true;
					}
					else
						throw new ArgumentException(Catalog.GetString("Not supported file type."));
					
//					if (is_container)
//					{
//						this.AddPlotTab();
//						// FIXME: restore figure name
//					}
//					else
					{
						this.plot_notebook.CurrentPage = plot_tab_page;
					}
					
					foreach (Spectrum spectrum in spectra)
					{
						this.AddSpectrum(spectrum);
					}
					
					this.ScrolledPlot.RestoreAnnotations(meta_data);
					changed = true;
				}
				catch (Exception ex)
				{
					new InfoMessage(this.parent, 
					                Catalog.GetString("Cannot open spectrum file: {0}"), 
					                ex.Message);
				}
			}
			if (changed)
				this.ScrolledPlot.Plot.RequestUpdate();

			return false;
		}
		

		public void NotifyExit()
		{
			if (this.Exit != null)
				this.Exit(this, EventArgs.Empty);
		}
		
		public void NotifyUnhandledException()
		{
			if (this.UnhandledException != null)
				this.UnhandledException(this, EventArgs.Empty);
		}
		
		private void InitializeReleaseVersion()
		{
			if (!MainClass.IsRelease)
				return;
			
			this.help_menu.Remove(this.help_item);
			this.help_menu.Remove(this.help_separator_item);
		}
			
		private void InitializeMainWindow()
		{
			if (Settings.Current.MainWindowWidth > 0)
				this.parent.DefaultWidth = Settings.Current.MainWindowWidth;
			else
				this.parent.DefaultWidth = 1100;
			
			if (Settings.Current.MainWindowHeight > 0)
				this.parent.DefaultHeight = Settings.Current.MainWindowHeight;
			else
				this.parent.DefaultHeight = 700;
			
			if (Settings.Current.MainWindowMaximized)
				this.parent.Maximize();
			
			if (Settings.Current.SpectrometerPanedPosition > 0)
				this.spectrometer_paned.Position = Settings.Current.SpectrometerPanedPosition;
			
			if (Settings.Current.DataSourcePanedPosition > 0)
				this.data_source_paned.Position = Settings.Current.DataSourcePanedPosition;
			
			Drag.DestSet(this.parent, DestDefaults.All, UserInterface.TargetEntries, Gdk.DragAction.Copy);
			this.parent.DragDataReceived += new DragDataReceivedHandler(this.OnDragDataReceived);

			// FIXME: workaround for broken treeview redraw (bug 565)
//			if (MainClass.OS == MainClass.OSType.Windows)
//			{
//				this.data_sources_frame.ExposeEvent += delegate {
//					this.property_vbox.QueueDraw();
//				};
//			}
			
			Key.SnooperInstall(this.OnKeyPressed);
		}

		private void InitializeQueues()
		{
			UserInterface.Queues  = new Queues(this);
			UserInterface.Queues.Statusbar = this.statusbar;
			UserInterface.Threads = new List<Thread>();
			
			UserInterface.Queues.InfoMessageShown += delegate(object sender, EventArgs e) {
				if (this.InfoMessageShown != null)
					this.InfoMessageShown(sender, e);
			};
		}
		
		private void InitializeTools()
		{
			this.StandardToolbarVisible   = Settings.Current.StandardToolbarVisible;
			this.FigureToolbarVisible     = Settings.Current.FigureToolbarVisible;
			this.ExtensionsToolbarVisible = Settings.Current.ExtensionsToolbarVisible;
			this.DataSourcesVisible       = Settings.Current.DataSourcesVisible;
			this.PropertyEditorVisible    = Settings.Current.PropertyEditorVisible;
			this.PlotBrowserVisible       = Settings.Current.PlotBrowserVisible;
			
			ListStore store = new ListStore(typeof(Gdk.Pixbuf), typeof(LineStyle));
			CellRendererPixbuf pixbuf = new CellRendererPixbuf();
			this.plot_style_combo.Clear();
			this.plot_style_combo.PackStart(pixbuf, false);
			this.plot_style_combo.SetAttributes(pixbuf, "pixbuf", 0);
			foreach (LineStyle style in Enum.GetValues(typeof(LineStyle)))
			{
				store.AppendValues(this.ScrolledPlot.Plot.GetLineStylePixbuf(style), style);
			}
			this.plot_style_combo.Model = store;
		}
		
		private void InitializeLinkButtons()
		{
			this.service_dialog_button = new LinkButton(Catalog.GetString("Service dialog"));
			this.service_dialog_button.Clicked += this.OnServiceDialogButtonClicked;
			this.service_dialog_align.Add(this.service_dialog_button);
			
			LinkButton b = new LinkButton(Catalog.GetString("Extract data"));
			b.Clicked += this.OnAdditionalDataClicked;
			this.extract_data_align.Add(b);
			
			b = new LinkButton(Catalog.GetString("Browse directory"));
			b.Clicked += this.OnBrowseDirClicked;
			this.browse_directory_align.Add(b);
		}
		
		private void InitializeStatusBar()
		{
			UserInterface.Queues.StatusQueue.Enqueue(new Queues.StatusEvent("gui", Catalog.GetString("Ready")));
			StatusMessage.XOffset = 10;
			StatusMessage.YOffset = this.statusbar.SizeRequest().Height;
			this.progressbar.Visible = false;
			this.progressbar.NoShowAll = true;
		}
		
		private void InitializeSpectrometerTv()
		{
			this.device_list = new DeviceList(MainClass.VendorId, MainClass.ProductId.ToArray());
			this.device_list.DisableTimeout = MainClass.DisableTimeout;

			this.active_devices = new ActiveDevices();

            this.spectrometer_tv = new ScrolledSpectrometerTv(this.device_list);
            this.spectrometer_frame.Add(this.spectrometer_tv);
			this.spectrometer_frame.ShowAll();
			
			this.spectrometer_tv.SelectionChanged += this.OnSpectrometerTvSelectionChanged;
			this.spectrometer_tv.DoubleClicked    += this.OnSpectrometerTvDoubleClicked;

			this.device_list.DeviceAdded        += this.OnDeviceAdded;
			this.device_list.DeviceRemoved      += this.OnDeviceRemoved;
			this.device_list.DeviceStateChanged += this.OnDeviceStateChanged;

			SharedUSB.StateChanged += delegate { 
				TrayIcon.SetTooltip(SharedUSB.SharedCount, SharedUSB.ConnectedCount);
			};
			
			this.device_list.IdleTask += UserInterface.ProcessEvents_;
			this.device_list.DeviceException +=	this.OnDeviceException;
			this.device_list.DeviceWarning   += this.OnDeviceWarning;
			
			this.device_list.ActivateQueues = true;

            this.auto_connect_checkbutton.Active = Settings.Current.AutoConnect;
            this.auto_select_checkbutton.Active  = Settings.Current.AutoSelect;			
		}

		private void InitializeFileTv()
		{
			List<string> paths   = Settings.Current.DataBrowserPaths;
			List<string> current = Settings.Current.DataBrowserPaths;
			
			int remove_count = 0;
			for (int ix=0; ix < paths.Count; ++ix)
			{
				if (string.IsNullOrEmpty(paths[ix]) == false && Directory.Exists(paths[ix]))
				{
					try
					{
						this.AddFileTv(paths[ix]);
					}
					catch (System.Exception ex)
					{
						new InfoMessage(null, Catalog.GetString("Cannot open directory: {0}"), ex.Message);
						current.RemoveAt(ix-remove_count++);
					}
				}
				else
				{
					current.RemoveAt(ix-remove_count++);
				}
			}
			
			Settings.Current.DataBrowserPaths = current;
			
			this.preview_checkbutton.Active = UserInterface.Settings.Current.ShowPreviewInPlot;
			
			this.file_notebook.PopupEnable();
			this.file_notebook.Removed += new RemovedHandler(this.OnFileNotebookRemoved);
			this.file_notebook.SwitchPage += this.OnFileNotebookChangeCurrentPage;
		}

		private void InitializePlotTv()
		{
			this.plot_tv = new ScrolledPlotTv(this.plot_info_list);
			this.plot_tv.SelectionChanged                 += this.OnPlotTvSelectionChanged;
			this.plot_tv.SpectrumSelectionPositionChanged += this.OnPlotTvSpectrumSelectionPositionChanged;
			this.plot_tv.SpectrumDoubleClicked            += this.OnPlotTvSpectrumDoubleClicked;
			this.plot_tv.FigureDoubleClicked              += this.OnPlotTvFigureDoubleClicked;
			this.plot_tv_frame.Add(this.plot_tv);
			
			this.plot_tv.ZOrderChanged += this.UpdatePlot;
			
			this.legend_checkbutton.Active = UserInterface.Settings.Current.AlwaysShowLegend;
		}

		private void ShowProgress(double fraction)
		{
			this.progressbar.Fraction = fraction;
			this.progressbar.Show();
			UserInterface.ProcessEvents_();
		}

		private void HideProgress()
		{
			this.progressbar.Hide();
			UserInterface.ProcessEvents_();
		}

		private void AddFileTv(string path)
		{
			string name = Path.GetFileName(path);

			ScrolledFileTv tv = new ScrolledFileTv(path);
			NotebookLabel  nl = new NotebookLabel(name, path);
			Label  menu_label = new Label(name);

			nl.CloseClicked += tv.Close;
			
			tv.SpectrumDoubleClicked     += this.OnSpectrumDoubleClicked;
			tv.OpenAllClicked            += this.OnOpenAllClicked;
			tv.DirectoryDoubleClicked    += this.OnFileTvDirectoryDoubleClicked;
			tv.DirectorySelectionChanged += this.OnFileTvDirectorySelectionChanged;
			tv.SpectrumSelectionChanged  += this.OnFileTvSpectrumSelectionChanged;
			tv.Closed                    += this.UpdatePropertiesSpectrometer;
	
			int ix = this.file_notebook.AppendPageMenu(tv, nl, menu_label);
			this.file_notebook.Page = ix;
			
			this.preview_checkbutton.Sensitive = true;
		}
		
		private void InitializeToolbar()
		{
			MainClass.InitializeIconFactory();
			this.insert_text_button.StockId        = "hiperscan.text";
			this.insert_line_button.StockId        = "hiperscan.line";
			this.insert_rectangle_button.StockId   = "hiperscan.rectangle";
			this.insert_ellipse_button.StockId     = "hiperscan.ellipse";
			this.insert_arrow_button.StockId       = "hiperscan.arrow";
			this.insert_doublearrow_button.StockId = "hiperscan.doublearrow";
			this.edit_x_constraints_button.StockId = "hiperscan.x-constraints";
			
			this.StandardOutputCulture = Settings.Current.StandardOutputCulture;
			
			foreach (Widget w in this.standard_toolbar)
			{
				this.standard_toolbar_widgets.Add(w);
			}
			
			this.x_constraints_entry = new XConstraintsEntry(UserInterface.entry_error_color);
			this.x_constraints_entry.TooltipText = Catalog.GetString("Define constraints of X-axis");
			this.x_constraints_entry.FocusInEvent += delegate {
				GLib.Timeout.Add(50, delegate {
					if (this.x_constraints_entry.Text.Length > 0)
						this.x_constraints_entry.SelectRegion(0, this.x_constraints_entry.Text.Length);
					return false;
				});
			};
			this.x_constraints_entry.FocusOutEvent += this.OnXConstraintsFocusOut;
			this.x_constraints_entry.ReturnPressed += delegate {
				this.OnXConstraintsFocusOut(this.x_constraints_entry, null);
			}; 
			this.x_constraints_align.Add(this.x_constraints_entry);
			this.x_constraints_entry._XConstraintsCompletion.Selected += delegate {
				GLib.Timeout.Add(0, delegate {
					this.OnXConstraintsFocusOut(this.x_constraints_entry, null);
					return false;
				});
			};
		}
		
		private void InitializeMenu()
		{
			this.data_sources_item.Active = Settings.Current.DataSourcesVisible;
			this.properties_item.Active   = Settings.Current.PropertyEditorVisible;
			this.plot_browser_item.Active = Settings.Current.PlotBrowserVisible;
			
			Gtk.AboutDialog.SetUrlHook(delegate(AboutDialog dialog, string link) {
				this.ShowURL(link);
			});
			
			if (Settings.Current.ReopenPaths == null || 
			    Settings.Current.ReopenPaths.Count == 0 || 
			    string.IsNullOrEmpty(Settings.Current.ReopenPaths[0]))
				this.reopen_item.Sensitive = false;
			
			this.extensions_menu.AccelGroup = this.gui.EnsureAccel();
			
			if (MainClass.OS == MainClass.OSType.MacOSX)
				MacIntegration.InstallGlobalMenu(this.menubar, this.quit_item);
			
			switch (Settings.Current.AxisType)
			{
				
			case AxisType.Wavelength:
				this.axis_type_wavelength_item.Active = true;
				break;
				
			case AxisType.Wavecount:
				this.axis_type_wavecount_item.Active = true;
				break;
				
			}
		}
		
		private void InitializeFileTypes()
		{
			this.file_types.Add("quickstep", new FileTypeInfo[] { new CsvFileTypeInfo(), 
				                                                  new CsvContainerFileTypeInfo() });
		}
		
		public string GenerateFilename(Spectrum spectrum)
		{
			string fname = Catalog.GetString("untitled_spectrum");
			
			if (spectrum.SpectrumType == SpectrumType.Single ||
			    spectrum.SpectrumType == SpectrumType.Stream ||
			    spectrum.SpectrumType == SpectrumType.ProcessedFluidSpecimen ||
			    spectrum.SpectrumType == SpectrumType.ProcessedSolidSpecimen)
				fname = spectrum.Id.Remove(0, 2);
			
			if (spectrum.IsFromFile && spectrum.Id.Length > 12)
				fname = Path.GetFileName(spectrum.Id.Remove(0, 12));
			
			return fname;
		}
		
		private string AnticipateFilename(string last_fname)
		{
			Console.WriteLine("Anticipate filename from \"{0}\":", last_fname);
			Regex regex = new Regex(@"_[0-9]+\..*$", RegexOptions.IgnoreCase);
			Match m = regex.Match(last_fname);
			if (!m.Success)
				return null;
			
			Group g = m.Groups[0];
			
			regex = new Regex(@"[0-9]+");
			m = regex.Match(g.ToString());
			if (!m.Success)
				return last_fname;

			g = m.Groups[0];
			
			int len = g.ToString().Length;
			int val = int.Parse(g.ToString());

			string new_fname = last_fname.Replace("_" + g.ToString() + ".", 
			                                      "_" + (++val).ToString("D" + len.ToString()) + ".");
			
			Console.WriteLine(new_fname);
			return new_fname;
		}
		
		public string SaveNewSpectrum(Spectrum spectrum, string fname)
		{
			FileTypeInfo file_type = new CsvFileTypeInfo();
			bool save_all = false;
			bool again    = false;
			bool? cancel  = false;
			OutputCulture output_culture = this.StandardOutputCulture; // OutputCulture.System;
			return this.SaveNewSpectrum(spectrum, fname, ref file_type, ref save_all, ref again, ref cancel, ref output_culture);
		}
		
		private string SaveNewSpectrum(Spectrum spectrum, string fname, ref FileTypeInfo file_type, ref bool save_all, ref bool again, ref bool? cancel, ref OutputCulture output_culture)
		{
			const int SAVE_ALL = 1000;
			using (QSFileChooserDialog chooser = new QSFileChooserDialog(Catalog.GetString("Specify file name to save a new spectrum"), 
			                                                             this.parent,
			                                                             FileChooserAction.Save,
			                                                             Stock.Save,
			                                                             ResponseType.Ok,
			                                                             Catalog.GetString("Save a_ll"),
				                                                         SAVE_ALL,
			                                                             Stock.Cancel,
			                                                             ResponseType.Cancel)
			       )
			{
				foreach (FileTypeInfo type in this.FileTypes)
				{
					if (type.RwType != FileRwType.ReadOnly && !type.IsContainer)
                        chooser.AddFilter(this.GetFileFilter(type), type.Extensions.First());
				}
				chooser._CurrentName  = fname;
				chooser.Filter = this.GetFileFilter(file_type);
				chooser.OutputCulture = output_culture;
				chooser.ActionArea.Children[4].Sensitive = save_all;
				
				if (!string.IsNullOrEmpty(Settings.Current.SaveFilePath))
					chooser.SetCurrentFolder(Settings.Current.SaveFilePath);
				else
					chooser.SetCurrentFolder(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
				
				chooser.DoOverwriteConfirmation = true;
				string actual_fname = string.Empty;
				save_all = false;
				again = false;

				try
				{
					int choice = chooser._Run();
					file_type = this.GetFileTypeInfo(chooser.Filter, false, false);

					output_culture = chooser.OutputCulture;
					
					switch (choice)
					{
						
					case (int)ResponseType.Ok:
						chooser.Hide();
						file_type.Write(spectrum, chooser.Filename, this.OutputCultureInfo(chooser.OutputCulture));
						spectrum.FileInfo = new FileInfo(chooser.Filename);
						Settings.Current.SaveFilePath = chooser.CurrentFolder;
						actual_fname = Path.GetFileName(chooser.Filename);
						break;
						
					case SAVE_ALL:
						save_all = true;
						FileInfo file = new FileInfo(chooser.Filename);
						if (!file.Exists)
							goto case (int)ResponseType.Ok;
						chooser.Hide();
						Dialog dialog = new OverwriteSpectrumDialog(this.parent, Path.GetFileName(chooser.Filename));
						choice = dialog.Run();
						dialog.Destroy();
						if ((ResponseType)choice == ResponseType.Yes)
							goto case (int)ResponseType.Ok;
						actual_fname = Path.GetFileName(chooser.Filename);
						again = true;
						break;

					case (int)ResponseType.Cancel:
						if (cancel != null)
							break;
						QSMessageDialog cancel_dialog = new QSMessageDialog(
							chooser,
							DialogFlags.Modal, 
							MessageType.Question, 
							ButtonsType.YesNo,
							Catalog.GetString("Do you want to cancel all pending file save operations?"));
						cancel = cancel_dialog.Run() == (int)ResponseType.Yes;
						cancel_dialog.Destroy();
						break;

					}
				}
				catch (Exception ex)
				{
					Console.WriteLine(ex);
					new InfoMessage(this.parent, 
					                Catalog.GetString("Error while saving spectrum: {0}"),
					                ex.Message);
				}
				finally
				{
					chooser.Destroy();
				}
				
				return actual_fname;
			}
		}

		public void OpenFileBrowser(string path)
		{
			UserInterface.OpenFileBrowser(this.parent, path);
		}
		
		public static void OpenFileBrowser(Window parent, string path)
		{
			System.Diagnostics.Process process = new System.Diagnostics.Process();

			process.StartInfo.CreateNoWindow = true;
			process.StartInfo.UseShellExecute = false;

			if (MainClass.OS == MainClass.OSType.Windows)
			{
				// start explorer
				process.StartInfo.Arguments = "/e,\"" + path + "\"";
				process.StartInfo.FileName = "explorer.exe";
				
				try
				{
					process.Start();
					return;
				}
				catch {
					try 
					{
						string windows_path = Directory.GetParent (Environment.GetFolderPath (Environment.SpecialFolder.System)).FullName;
						process.StartInfo.FileName = Path.Combine (windows_path, "explorer.exe");
						process.Start ();
						return;
					} 
					catch 
					{
					}
				}
			}
			else
			{
				// try to start one of Linux' file browsers
				process.StartInfo.Arguments = "\"" + path + "\"";
				string[] filemanagers = new string[] {"nautilus", "dolphin", "konqueror", "thunar"};
				
				foreach (string filemanager in filemanagers)
				{
					process.StartInfo.FileName = filemanager;
					try
					{
						process.Start();
						return;
					}
					catch {}
				}
			}

			new InfoMessage(parent, Catalog.GetString("Cannot start file browser."));
		}
		
		private void AttachPropertyEditor()
		{
			this.property_handlebox.Remove(this.property_frame);
			
			this.property_vbox.Remove(this.property_handlebox);
			this.property_handlebox.Destroy();
			
			HandleBox hb = new HandleBox();
			hb.ShadowType     = ShadowType.In;
			hb.HandlePosition = PositionType.Top;
			hb.SnapEdge       = PositionType.Bottom;
			hb.SnapEdgeSet    = true;
			hb.Add(this.property_frame);
			hb.ChildAttached += new ChildAttachedHandler(this.OnPropertyEditorAttached);
			hb.ChildDetached += new ChildDetachedHandler(this.OnPropertyEditorDetached);
			this.property_handlebox = hb;
			
			this.attach_property_editor_button.Sensitive = false;
			
			this.property_handlebox.ShowAll();
			this.property_frame.ShowAll();
			
			this.property_vbox.PackEnd(this.property_handlebox, false, false, 0);
		}
		
		internal void ShowURL(string link)
		{
			try
			{
				System.Diagnostics.Process.Start(link);
			}
			catch (Exception ex)
			{
				new InfoMessage(this.parent, Catalog.GetString("Cannot open ULR '{0}': {1}"),
				                link, ex.Message);
			}
		}
		
		public void ProcessEvents()
		{
			UserInterface.ProcessEvents_();
		}
		
		public bool CheckForUnsavedSpectra()
		{
			if (MainClass.EnableDebugging)
				return true;
			
			bool unsaved = false;
			foreach (Spectrum spectrum in this.CurrentSpectra.Values)
			{
				if (spectrum.FileInfo == null && 
				    spectrum.SpectrumType != SpectrumType.QuickStream)
				{
					unsaved = true;
					break;
				}
			}
			
			if (unsaved)
			{
                MessageDialog dialog = new DiscardOnExitDialog(this.parent);
				ResponseType response = (ResponseType)dialog.Run();
				dialog.Destroy();
				if (response != ResponseType.Yes)
					return false;
			}
			
			return true;
		}
		
		public void Quit()
		{
			MainClass.Quit(false);
		}
		
		public void Quit(bool force)
		{
			MainClass.Quit(force);
		}

		public static void ProcessEvents_()
		{
			UserInterface.ProcessEvents_(Thread.CurrentThread.GetHashCode());
		}

		public static void ProcessEvents_(int thread_id)
		{
			lock (UserInterface.UI)
			{
				if (thread_id == MainClass.MainThreadId)
					UserInterface.__ProcessEvents();
			}
		}

		private static void __ProcessEvents()
		{
			if (Thread.CurrentThread.GetHashCode() == MainClass.MainThreadId)
			{
				int count = 0;
				while (Application.EventsPending())
				{
					if (++count > 200)
					{
						Console.WriteLine("__ProcessEvents(): reached break condition (too many events pending)");
						break;
					}
					Application.RunIteration();
				}
			}
		}
		
		private int OnKeyPressed(Widget w, Gdk.EventKey e)
		{
			if (e.Type != Gdk.EventType.KeyPress)
				return 0;
			
			try
			{
				switch (e.Key)
				{
					
				case Gdk.Key.Escape:
					if (this.CancelAnnotations())
						this.ScrolledPlot.Plot.AsyncRefresh(true);
					break;
					
				case Gdk.Key.Tab:
				case Gdk.Key.Page_Down:
					if ((e.State & Gdk.ModifierType.ControlMask) == Gdk.ModifierType.ControlMask)
					{
						if (this.plot_notebook.CurrentPage >= this.plot_notebook.NPages-2)
							this.plot_notebook.CurrentPage = 0;
						else
							this.plot_notebook.CurrentPage++;
						return 1;
					}
					break;
					
				case Gdk.Key.Page_Up:
					if ((e.State & Gdk.ModifierType.ControlMask) == Gdk.ModifierType.ControlMask)
					{
						if (this.plot_notebook.CurrentPage == 0)
							this.plot_notebook.CurrentPage = this.plot_notebook.NPages-2;
						else
							this.plot_notebook.CurrentPage--;
						return 1;
					}
					break;
				
				case Gdk.Key.F4:
					if ((e.State & Gdk.ModifierType.ControlMask) == Gdk.ModifierType.ControlMask)
					{
						this.RemovePlotTab(this.plot_info_list.Current);
						return 1;
					}
					break;
				}
			}
			catch (Exception ex)
			{
				Console.WriteLine("Error in global key snooper: " + ex.Message);
			}
			
			return 0;
		}
		
		internal void OnToolCloseClicked(object o, System.EventArgs e)
		{
			Type t = o.GetType();
			
			if (t.FullName == "Gtk.ToolButton" || t.FullName == "Gtk.MenuItem")
			{
				this.DataSourcesVisible    = false;
				this.PropertyEditorVisible = false;
				this.PlotBrowserVisible    = false;
				return;
			}
			
			Button b = o as Button;
			
			switch (b.Name)
			{
				
			case "close_data_sources_button":
				this.DataSourcesVisible = false;
				break;
				
			case "close_property_editor_button":
				this.PropertyEditorVisible = false;
				break;
				
			case "close_plot_browser_button":
				this.PlotBrowserVisible = false;
				break;
								
			default:
				throw new ArgumentException("Unknown CloseButton.");
				
			}
			b.InButton = false;
		}

		internal void OnToolAttachClicked(object o, System.EventArgs e)
		{
			Button b = o as Button;
			
			switch (b.Name)
			{
				
			case "attach_property_editor_button":
				this.AttachPropertyEditor();
				break;
								
			default:
				throw new ArgumentException("Unknown AttachButton.");
				
			}
			b.InButton = false;
		}

		internal void OnPropertyEditorDetached(object o, Gtk.ChildDetachedArgs e)
		{
			this.attach_property_editor_button.Sensitive = true;
		}

		internal void OnPropertyEditorAttached(object o, Gtk.ChildAttachedArgs e)
		{
			this.attach_property_editor_button.Sensitive = false;
		}
		
		public ScrolledPlot ScrolledPlot
		{
			get { return this.plot_info_list.Current.ScrolledPlot; }
		}
		
		public Widget GUI
		{
			get { return this.ui_vbox; }
		}
		
		public ICurrentSpectra CurrentSpectra
		{
			get { return this.plot_info_list.Current.CurrentSpectra; }
		}
		
		public Gdk.Color BackgroundColor
		{
			get { return Rc.GetStyle(this.ScrolledPlot.Plot.GtkWidget).Background(StateType.Normal); }
		}
		
		public Window MainWindow
		{
			get { return this.parent; }
		}
		
		public AccelGroup AccelGroup
		{
			get { return this.gui.EnsureAccel(); }
		}
		
		public Menu SpectrometerMenu
		{
			get { return this.spectrometer_menu; }
		}
		
		public Menu EditMenu
		{
			get { return this.edit_menu; }
		}
		
		public FileTypeInfo[] FileTypes
		{
			get 
			{ 
				List<FileTypeInfo> file_types = new List<FileTypeInfo>();
				foreach (FileTypeInfo[] infos in this.file_types.Values)
				{
					file_types.AddRange(infos);
				}

				file_types.Sort(delegate(FileTypeInfo lhs, FileTypeInfo rhs) {
					if (lhs.Name.ToLower().StartsWith("quickstep"))
						return -1;
					if (rhs.Name.ToLower().StartsWith("quickstep"))
						return 1;
					return lhs.Name.CompareTo(rhs.Name);
				});
				
				return file_types.ToArray();
			}
		}
		
		public FileFilter GetFileFilter(FileTypeInfo file_type_info)
		{
			// we need to keep the object references to set the file filter later
			if (!this.file_filters.ContainsKey(file_type_info.Name))
			{
				FileFilter filter = new FileFilter();
				filter.Name = file_type_info.Name;
                foreach (string extension in file_type_info.Extensions)
                {
                    filter.AddPattern("*." + extension.ToLower());
                    filter.AddPattern("*." + extension.ToUpper());
                }
				this.file_filters.Add(filter.Name, filter);
			}
			return this.file_filters[file_type_info.Name];
		}
		
		public FileTypeInfo GetFileTypeInfo(FileFilter filter, bool read, bool force_container)
		{
			foreach (FileTypeInfo file_type in this.FileTypes)
			{
				if (read && file_type.RwType == FileRwType.WriteOnly)
					continue;

				if (force_container && file_type.IsContainer == false)
					continue;

				if (filter.Name == file_type.Name)
					return file_type;
			}
			return new CsvFileTypeInfo();
		}
		
		public string ConfigurationDirPath
		{
			get { return UserInterface.ConfigDirPath; }
		}

		public string VersionInfo
		{
			get { return MainClass.VersionInfo; }
		}

        public string [] Args { get; set; }
		public bool ProfileChangedMode { get; set;  } = false;
		public string MainWindowTitelExtension { get; set; } = String.Empty;
       
    }	
}
