#!/usr/bin/python

import pygtk
pygtk.require("2.0")
import gtk
import gtk.glade
import sys, os
import gobject
import pygst
pygst.require("0.10")
import gst

import xml.dom.minidom as xml

import AddInstrumentDialog
import TimeView
import CompactMixView
import PreferencesDialog
import RecordingView
import NewProjectDialog
import Project
import ConfigParser
import Globals
import WelcomeDialog
import InstrumentConnectionsDialog
import StatusBar
import AlsaDevices

gobject.threads_init()

#=========================================================================

class MainApp:
	
	#in case we are imported from another python file
	GLADE_PATH = None		#set in __init__()
	
	# Class Constants
	MODE_RECORDING = 1
	MODE_COMPACT_MIX = 2

	#_____________________________________________________________________

	def __init__(self):
		
		#Find the absolute path in case we were imported from another directory
		Globals.JOKOSHER_PATH = os.path.dirname(os.path.abspath(__file__))
		self.GLADE_PATH = os.path.join(Globals.JOKOSHER_PATH, "Jokosher.glade")
		
		self.wTree = gtk.glade.XML(self.GLADE_PATH, "MainWindow")
		
		#Connect event handlers
		signals = {
			"on_MainWindow_destroy" : self.OnDestroy,
			"on_AddInstrument_clicked" : self.OnShowAddInstrumentDialog,
			"on_About_activate" : self.About,
			"on_Record_toggled" : self.Record,
			"on_Play_toggled" : self.Play,
			"on_Stop_clicked" : self.Stop,
			"on_Recording_toggled" : self.OnRecordingView,
			"on_CompactMix_toggled" : self.OnCompactMixView,
			"on_export_activate" : self.OnExport,
			"on_preferences_activate" : self.OnPreferences,
			"on_open_activate" : self.OnOpenProject,
			"on_save_activate" : self.OnSaveProject,
			"on_save_as_activate" : self.OnSaveAsProject,
			"on_new_activate" : self.OnNewProject,
			"on_close_activate" : self.OnCloseProject,
			"on_show_as_bars_beats_ticks_toggled" : self.OnShowBarsBeats,
			"on_show_as_hours_minutes_seconds_toggled" : self.OnShowHoursMins,
			"on_undo_activate" : self.OnUndo,
			"on_redo_activate" : self.OnRedo,
			"on_cut_activate" : self.OnCut,
			"on_copy_activate" : self.OnCopy,
			"on_paste_activate" : self.OnPaste,
			"on_delete_activate" : self.OnDelete,
			"on_MouseDown" : self.OnMouseDown,
			"on_instrumentconnections_activate" : self.OnInstrumentConnectonsDialog,
			"on_editmenu_activate" : self.OnEditMenu,
			"on_projectmenu_activate" : self.OnProjectMenu,
			"on_prereleasenotes_activate" : self.OnPreReleaseNotes,
			"on_contributing_activate" : self.OnContributingDialog

		}
		self.wTree.signal_autoconnect(signals)
		
		# grab some references to bits of the GUI
		self.window = self.wTree.get_widget("MainWindow")
		self.play = self.wTree.get_widget("Play")
		self.stop = self.wTree.get_widget("Stop")
		self.record = self.wTree.get_widget("Record")
		self.save = self.wTree.get_widget("save")
		self.save_as = self.wTree.get_widget("save_as")
		self.close = self.wTree.get_widget("close")
		self.reverse = self.wTree.get_widget("Rewind")
		self.forward = self.wTree.get_widget("Forward")
		self.addInstrumentButton = self.wTree.get_widget("AddInstrument")
		self.forward = self.wTree.get_widget("Forward")
		self.editmenu = self.wTree.get_widget("editmenu")
		self.undo = self.wTree.get_widget("undo")
		self.redo = self.wTree.get_widget("redo")
		self.cut = self.wTree.get_widget("cut")
		self.copy = self.wTree.get_widget("copy")
		self.paste = self.wTree.get_widget("paste")
		self.delete = self.wTree.get_widget("delete")
		self.projectmenu = self.wTree.get_widget("projectmenu")
		self.export = self.wTree.get_widget("export")
		self.recentprojects = self.wTree.get_widget("recentprojects")
		self.menubar = self.wTree.get_widget("menubar")
		
		self.recentprojectitems = []
		self.lastopenedproject = None

		self.recentprojectsmenu = gtk.Menu()
		self.recentprojects.set_submenu(self.recentprojectsmenu)
		
		self.project = None
		self.recording = None
		self.headerhbox = None
		self.timeview = None
		self.tvtoolitem = None #wrapper for putting timeview in toolbar
		self.recording = None
		self.compactmix = None
		self.instrNameEntry = None #the gtk.Entry when editing an instrument name
		self.main_vbox = self.wTree.get_widget("main_vbox")
		
		self.statusbar = StatusBar.StatusBar()
		self.main_vbox.pack_end(self.statusbar, False)
		
		# Initialise some useful vars
		self.mode = None
		self.settingButtons = True
		self.wTree.get_widget("Recording").set_active(True)
		self.settingButtons = False
		
		self.isRecording = False
		self.isPlaying = False
		
		# set sensitivity
		self.SetGUIProjectLoaded()

		# Connect up the forward and reverse handlers. We can't use the autoconnect as we need child items
		
		innerbtn = self.reverse.get_children()[0]
		innerbtn.connect("pressed", self.OnRewindPressed)
		innerbtn.connect("released", self.OnRewindReleased)
		
		innerbtn = self.forward.get_children()[0]
		innerbtn.connect("pressed", self.OnForwardPressed)
		innerbtn.connect("released", self.OnForwardReleased)
		
		# populate the Recent Projects menu
		self.OpenRecentProjects()
		self.PopulateRecentProjects()
		
		#set window icon
		self.window.set_icon_from_file(os.path.join(Globals.JOKOSHER_PATH, "jokosher.png"))
		#make icon available to others
		self.icon = self.window.get_icon()
		
		# Show the main window
		self.window.show_all()
		
		# Make sure we can import for the instruments folder
		sys.path.append("Instruments")
		
		self.window.add_events(gtk.gdk.KEY_PRESS_MASK)
		self.window.connect_after("key-press-event", self.OnKeyPress)
		self.window.connect("button_press_event", self.OnMouseDown)

		self.CheckGstreamerVersions()
		
		# check if we should display the startup dialog
		if Globals.settings.general["startupaction"] == PreferencesDialog.STARTUP_LAST_PROJECT:
			self.OpenLastProject()
		elif Globals.settings.general["startupaction"] == PreferencesDialog.STARTUP_NOTHING:
			pass
		else: #default option if no preference is set
			WelcomeDialog.WelcomeDialog(self)
		
	#_____________________________________________________________________

	def clean(self):
		self.project.clean()
		
	#_____________________________________________________________________	
		
	def OnChangeView(self, view, mode):
		if not self.settingButtons:
			self.settingButtons = True
			self.wTree.get_widget("Recording").set_active(mode == self.MODE_RECORDING)
			self.wTree.get_widget("CompactMix").set_active(mode == self.MODE_COMPACT_MIX)
			self.settingButtons = False
			
			if view:
				children = self.main_vbox.get_children()
				if self.recording in children:
					self.main_vbox.remove(self.recording)
				elif self.compactmix in children:
					self.main_vbox.remove(self.compactmix)
				
				self.main_vbox.pack_end(view, True, True)
				self.window.show_all()
				self.mode = mode
				self.UpdateCurrentDisplay()

	#_____________________________________________________________________
	
	def OnRecordingView(self, window=None):
		if hasattr(self, "recording"):
			self.OnChangeView(self.recording, self.MODE_RECORDING)	
	#_____________________________________________________________________
	
	def OnCompactMixView(self, window=None):
		if hasattr(self, "compactmix"):
			self.OnChangeView(self.compactmix, self.MODE_COMPACT_MIX)
	#_____________________________________________________________________
	
	def OnDestroy(self, widget=None, event=None):
		if self.CloseProject() == 0:
			gtk.main_quit()
		else:
			return True #stop signal propogation
		
	#_____________________________________________________________________
	
	def OnShowAddInstrumentDialog(self, widget):
		""" Creates and shows the 'Add Instrument' dialog box """
		dlg = AddInstrumentDialog.AddInstrumentDialog(self.project, self)
	
	#_____________________________________________________________________
	
	def About(self, widget = None):
		'''Display about dialog'''
		aboutTree = gtk.glade.XML(self.GLADE_PATH, "AboutDialog")
		dlg = aboutTree.get_widget("AboutDialog")
		dlg.set_transient_for(self.window)
		dlg.set_icon(self.icon)
		
	#_____________________________________________________________________

	def Record(self, widget = None):
		'''Toggle recording'''
		
		if self.settingButtons:
			return
		
		canRecord = False
		for i in self.project.instruments:
			if i.isArmed:
				canRecord = True

		#Check to see if any instruments are trying to use the same input channel
		usedChannels = {}
		for instr in self.project.instruments:
			if instr.isArmed:
				if usedChannels.has_key(instr.input):
					if usedChannels[instr.input].has_key(instr.inTrack):
						dlg = gtk.MessageDialog(self.window,
							gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
							gtk.MESSAGE_INFO,
							gtk.BUTTONS_CLOSE,
							"The instruments '%s' and '%s' both have the same input selected (%s). Please either disarm one, or connect it to a different input through 'Project -> Instrument Connections'"%(usedChannels[instr.input][instr.inTrack], instr.name, instr.inTrack))
						dlg.connect('response', lambda dlg, response: dlg.destroy())
						dlg.run()
						self.settingButtons = True
						widget.set_active(False)
						self.settingButtons = False
						return
					else:
						usedChannels[instr.input][instr.inTrack] = instr.name
				else:
					usedChannels[instr.input] = {instr.inTrack : instr.name}
				
		if not canRecord:
			dlg = gtk.MessageDialog(self.window,
				gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
				gtk.MESSAGE_INFO,
				gtk.BUTTONS_CLOSE,
				"No instruments are armed for recording. You need to arm an instrument before you can begin recording.")
			dlg.connect('response', lambda dlg, response: dlg.destroy())
			dlg.run()
			self.settingButtons = True
			widget.set_active(False)
			self.settingButtons = False
		else:		
			#Deselect all input channels (the required ones will be reselected by each instrument)
			devices = AlsaDevices.GetAlsaList("capture").values()
			for device in devices: 
				mixer = gst.element_factory_make('alsamixer')
				mixer.set_property("device", device)
				mixer.set_state(gst.STATE_READY)

				for track in mixer.list_tracks():
					if track.flags & gst.interfaces.MIXER_TRACK_INPUT:
						mixer.set_record(track, False)
					#Most cards incapable of multiple simultanious input have a channel called 'Capture' which must be enabled along with the actual input channel
					if track.label == 'Capture':
						mixer.set_record(track, True)

				mixer.set_state(gst.STATE_NULL)
							
			self.isRecording = not self.isRecording
			self.stop.set_sensitive(self.isRecording)
			self.play.set_sensitive(not self.isRecording)
			if self.isRecording:
				try:
					self.project.record()
				except Project.MultipleInputsError:
					dlg = gtk.MessageDialog(self.window,
						gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
						gtk.MESSAGE_INFO,
						gtk.BUTTONS_CLOSE,
						"Your sound card isn't capable of recording from multiple sources at the same time. Please disarm all but one instrument.")
					dlg.connect('response', lambda dlg, response: dlg.destroy())
					dlg.run()
					self.project.terminate()
					self.isRecording = not self.isRecording
					self.stop.set_sensitive(self.isRecording)
					self.play.set_sensitive(not self.isRecording)
					self.record.set_active(self.isRecording)
			else:
				self.project.stop()

	#_____________________________________________________________________
	
	def Play(self, widget = None):
		'''Toggle playing'''
		self.isPlaying = not self.isPlaying
		self.stop.set_sensitive(self.isPlaying)
		self.record.set_sensitive(not self.isPlaying)
		self.compactmix.StartUpdateTimeout()
		if self.isPlaying:
			self.project.play()
		else:
			self.project.stop()

	#_____________________________________________________________________

	#The stop button is really just an alias for toggling play/record to off
	def Stop(self, widget = None):
		'''Stop recording/playing (whichever is happening)'''
		if self.isRecording: 
			self.record.set_active(False)
		if self.isPlaying: 
			self.play.set_active(False)

	#_____________________________________________________________________

	def OnRewindPressed(self, widget = None):
		self.project.transport.Reverse(True)
		
	#_____________________________________________________________________
		
	def OnRewindReleased(self, widget = None):
		self.project.transport.Reverse(False)
		
	#_____________________________________________________________________
		
	def OnForwardPressed(self, widget = None):
		self.project.transport.Forward(True)
		
	#_____________________________________________________________________
		
	def OnForwardReleased(self, widget = None):
		self.project.transport.Forward(False)
	
	#_____________________________________________________________________
	
	def InstrumentSelected(self, widget = None, event = None):
		'''If an instrument has been selected, enable the record button'''
		for instr in self.project.instruments:
			if instr.isSelected:
				self.record.set_sensitive(True)

	#_____________________________________________________________________
	
	def OnExport(self, widget = None):
		'''Display a save dialog allowing the user to export as ogg or mp3'''
		buttons = (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK)
		chooser = gtk.FileChooserDialog("Mixdown Project", self.window, gtk.FILE_CHOOSER_ACTION_SAVE, buttons)
		
		saveLabel = gtk.Label("Save as file type:")		
		typeCombo = gtk.combo_box_new_text()
		#tuples containing the display test, and the extension (without ".")
		types = [("Ogg Vorbis (.ogg)","ogg"), ("MP3 (.mp3)","mp3"), 
					("Flac (.flac)","flac"), ("WAV (.wav)","wav")]
		for i in types:
			typeCombo.append_text(i[0])
		#Default file type is ogg
		typeCombo.set_active(0)
		
		extraHBox = gtk.HBox()
		extraHBox.pack_start(saveLabel, False)
		extraHBox.pack_end(typeCombo, False)
		extraHBox.show_all()
		chooser.set_extra_widget(extraHBox)
		
		response = chooser.run()
		if response == gtk.RESPONSE_OK:
			filename = chooser.get_filename()
			#If they haven't already appended the extension for the 
			#chosen file type, add it to the end of the file.
			filetype = types[typeCombo.get_active()][1]
			if not filename.lower().endswith(filetype):
				filename = filename + "." + filetype
				
			chooser.destroy()
		
			export = gtk.glade.XML (self.GLADE_PATH, "ProgressDialog")
			export.signal_connect("on_cancel_clicked", self.OnExportCancel)
			
			self.exportdlg = export.get_widget("ProgressDialog")
			self.exportdlg.set_icon(self.icon)
			self.exportdlg.set_transient_for(self.window)
			
			label = export.get_widget("progressLabel")
			label.set_text("Mixing project to file: %s" %filename)
			
			self.exportprogress = export.get_widget("progressBar")
			
			gobject.timeout_add(100, self.UpdateExportDialog)
			self.project.export(filename)
		else:
			chooser.destroy()
		
	#_____________________________________________________________________
	
	def UpdateExportDialog(self):
		tuple = self.project.get_export_progress()
		if tuple[0] == -1:
			self.exportprogress.set_fraction(0.0)
			self.exportprogress.set_text("Preparing to mixdown project")
		elif tuple[0] == tuple[1] == 100:
			self.exportdlg.destroy()
			return False
		else:
			self.exportprogress.set_fraction(tuple[0]/tuple[1])
			self.exportprogress.set_text("%d of %d seconds completed" % (tuple[0], tuple[1]))
			
		return True
	
	#_____________________________________________________________________
	
	def OnExportCancel(self, widget=None):
		self.exportdlg.destroy()
		self.project.export_eos()
	
	#_____________________________________________________________________
	
	def OnPreferences(self, widget, destroyCallback=None):
		prefsdlg = PreferencesDialog.PreferencesDialog(self.project, self, self.icon)
			
		if destroyCallback:
			prefsdlg.dlg.connect("destroy", destroyCallback)
	
	#_____________________________________________________________________
	
	def OnShowBarsBeats(self, widget):
		if self.project and self.project.transport:
			self.project.transport.SetMode(self.project.transport.MODE_BARS_BEATS)
		
	#_____________________________________________________________________
	
	def OnShowHoursMins(self, widget):
		if self.project and self.project.transport:
			self.project.transport.SetMode(self.project.transport.MODE_HOURS_MINS_SECS)
		
	#_____________________________________________________________________
	
	def UpdateCurrentDisplay(self):
		if self.mode == self.MODE_RECORDING:
			self.recording.Update()
		elif self.mode == self.MODE_COMPACT_MIX:
			self.compactmix.Update()
	
	#_____________________________________________________________________
	
	def UpdateDisplay(self):
		if self.mode == self.MODE_RECORDING:
			self.recording.Update()
			gobject.idle_add(self.compactmix.Update)
		elif self.mode == self.MODE_COMPACT_MIX:
			self.compactmix.Update()
			gobject.idle_add(self.recording.Update)
		
	#_____________________________________________________________________

	def OnOpenProject(self, widget, destroyCallback=None):
		
		chooser = gtk.FileChooserDialog(('Choose a Jokosher project file'), None, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
		chooser.set_default_response(gtk.RESPONSE_OK)
		chooser.set_transient_for(self.window)
		allfilter = gtk.FileFilter()
		allfilter.set_name("All Files")
		allfilter.add_pattern("*")
		
		jokfilter = gtk.FileFilter()
		jokfilter.set_name("Jokosher Project File (*.jokosher)")
		jokfilter.add_pattern("*.jokosher")
		
		chooser.add_filter(jokfilter)
		chooser.add_filter(allfilter)
		
		if destroyCallback:
			chooser.connect("destroy", destroyCallback)
		
		while True:
			response = chooser.run()
			
			if response == gtk.RESPONSE_OK:
				
				filename = chooser.get_filename()
				
				try:
					self.SetProject(Project.LoadFromFile(filename))
				except Project.OpenProjectError, e:
					self.ShowOpenProjectErrorDialog(e, chooser)
				else:
					break
				
			elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
				break
		
		chooser.destroy()
		
	#_____________________________________________________________________
		
	def OnSaveProject(self, widget=None):		
		if self.project:
			self.project.ClearInstrumentSelections()
			self.project.ClearEventSelections()
			self.project.saveProjectFile()
			
	#_____________________________________________________________________
	
	def OnSaveAsProject(self, widget=None):
		buttons = (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK)
		chooser = gtk.FileChooserDialog("Choose a location to save the project", self.window, gtk.FILE_CHOOSER_ACTION_SAVE, buttons)
		
		response = chooser.run()
		if response == gtk.RESPONSE_OK:
			filename = chooser.get_filename()
			
			self.project.ClearInstrumentSelections()
			self.project.ClearEventSelections()
			self.project.saveProjectFile(filename)
			
		chooser.destroy()
		
	#_____________________________________________________________________

	def OnNewProject(self, widget, destroyCallback=None):
		""" Creates and shows the 'New Project' dialog box """
		newdlg = NewProjectDialog.NewProjectDialog(self)
		if destroyCallback:
			newdlg.dlg.connect("destroy", destroyCallback)
		
	#_____________________________________________________________________
		
	def OnCloseProject(self, widget):
		""" Closes a project """
		if self.CloseProject() == 0:
			self.SetGUIProjectLoaded()
	#_____________________________________________________________________
	
	def CloseProject(self):
		#return values: 0 == okay, 1 == cancel and return to program
		if not self.project:
			return 0
		
		print "Shutting down",
		self.Stop()
		if self.project.CheckUnsavedChanges():
			message = "<span size='large' weight='bold'>Save changes to project \"%s\" before closing?</span>\n\nYour changes will be lost if you don't save them." % self.project.name
			
			dlg = gtk.MessageDialog(self.window,
				gtk.DIALOG_MODAL |
				gtk.DIALOG_DESTROY_WITH_PARENT,
				gtk.MESSAGE_WARNING,
				gtk.BUTTONS_NONE)
			dlg.set_markup(message)
			
			dlg.add_button("Close _Without Saving", gtk.RESPONSE_NO)
			dlg.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
			defaultAction = dlg.add_button(gtk.STOCK_SAVE, gtk.RESPONSE_YES)
			#make save the default action when enter is pressed
			dlg.set_default(defaultAction)
			
			dlg.set_transient_for(self.window)
			
			response = dlg.run()
			dlg.destroy()
			if response == gtk.RESPONSE_YES:
				self.OnSaveProject()
			elif response == gtk.RESPONSE_NO:
				pass
			elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
				return 1
				
		self.project.closeProject()
		print "Done"
		
		self.project = None
		self.mode = None
		return 0
		
	#_____________________________________________________________________
	
	def OnUndo(self, widget):
		self.project.Undo()
		self.UpdateDisplay()
		
	#_____________________________________________________________________
	
	def OnRedo(self, widget):
		self.project.Redo()
		self.UpdateDisplay()

	#_____________________________________________________________________
	
	def OnStateChanged(self, obj=None, change=None):
		#for when undo and redo history change
		undo = len(self.project.undoStack) or len(self.project.savedUndoStack)
		self.undo.set_sensitive(undo)
		redo = len(self.project.redoStack) or len(self.project.savedRedoStack)
		self.redo.set_sensitive(redo)
		
		if self.project.CheckUnsavedChanges():
			self.window.set_title('*%s - Jokosher' % self.project.name)
		else:
			self.window.set_title('%s - Jokosher' % self.project.name)
		
	#_____________________________________________________________________

	def InsertRecentProject(self, path, name):
		for item in self.recentprojectitems:
			if path == item[0]:
				self.recentprojectitems.remove(item)
				break
		
		self.recentprojectitems.insert(0, (path, name))
		self.SaveRecentProjects()
		self.PopulateRecentProjects()

	#_____________________________________________________________________

	def PopulateRecentProjects(self):
		'''Populate the Recent Projects menu with items from self.recentprojectitems'''
		
		menuitems = self.recentprojectsmenu.get_children()

		for c in menuitems:
			self.recentprojectsmenu.remove(c)
		
		for item in self.recentprojectitems:
			self.mitem = gtk.MenuItem(item[1])
			self.recentprojectsmenu.append(self.mitem)
			self.mitem.connect("activate", self.OnRecentProjectsItem, item[0], item[1])
				
			self.mitem.show()

		self.recentprojectsmenu.show()
	#_____________________________________________________________________
	
	def OpenRecentProjects(self):
		'''Populate the self.recentprojectpaths with items from global settings'''
		self.recentprojectitems = []
		if Globals.settings.general.has_key("recentprojects"):
			filestring = Globals.settings.general["recentprojects"]
			filestring = filestring.split(",")
			recentprojectitems = []
			for i in filestring:
				if len(i.split("|")) == 2:
					recentprojectitems.append(i.split("|"))	
					
			for path, name in recentprojectitems:
				#TODO - see ticket 80; should it check if the project is valid?
				if not os.path.exists(path):
					print "Error: Couldn't open recent project", path
				else:
					self.recentprojectitems.append((path, name))
			
			#the first project is our last opened project
			if recentprojectitems and os.path.exists(recentprojectitems[0][0]):
				self.lastopenedproject = recentprojectitems[0]
			
		self.SaveRecentProjects()

	#_____________________________________________________________________
	
	def OnRecentProjectsItem(self, widget, path, name):
		try:
			self.SetProject(Project.LoadFromFile(path))
		except Project.OpenProjectError, e:
			self.ShowOpenProjectErrorDialog(e)
	
	#_____________________________________________________________________

	def SaveRecentProjects(self):
		string = ""

		# Cut list to 8 items
		self.recentprojectitems = self.recentprojectitems[:8]
		
		for path, name in self.recentprojectitems:
			string = string + str(path) + "|" + str(name) + ","
			
		string = string[:-1]
		Globals.settings.general['recentprojects'] = string
		Globals.settings.write()
		
	#______________________________________________________________________
	
	def OnCut(self, widget, cut=True):
		if self.instrNameEntry:
			#if an instrument name is currently being edited
			if cut:
				self.instrNameEntry.cut_clipboard()
			else:
				self.instrNameEntry.copy_clipboard()
			return
	
		#Wipe the clipboard clean
		self.project.clipboardList = []
		for instr in self.project.instruments:
			for event in instr.events:
				if event.isSelected:
					#Add to the clipboard
					self.project.clipboardList.append(event)
					if cut:
						#if we are cutting (as opposed to copying)
						event.Delete()

		self.UpdateDisplay()
	
	#______________________________________________________________________
	
	def OnCopy(self, widget):
		self.OnCut(widget, False)
	
	#______________________________________________________________________
	
	def OnPaste(self, widget):
		if self.instrNameEntry:
			#if an instrument name is currently being edited
			self.instrNameEntry.paste_clipboard()
			return
	
		for instr in self.project.instruments:
			if instr.isSelected:
				for event in self.project.clipboardList:
					instr.addEventFromEvent(0, event)
				break
		
		self.UpdateDisplay()
	
	#______________________________________________________________________
	
	def OnDelete(self, widget=None):
		# Delete any select instruments
		for instr in self.project.instruments:
			if (instr.isSelected):
				#set not selected so when we undo we don't get two selected instruments
				instr.isSelected = False
				self.project.DeleteInstrument(instr.id)
			else:
				# Delete any selected events
				for ev in instr.events:
					if ev.isSelected:
						ev.Delete()
	
		self.UpdateDisplay()
	
	#______________________________________________________________________

	def OnMouseDown(self, widget, mouse):
		if self.project:
			self.project.ClearEventSelections()
			self.project.ClearInstrumentSelections()
		self.UpdateCurrentDisplay()
		
	#______________________________________________________________________
	
	def SetGUIProjectLoaded(self):
		children = self.main_vbox.get_children()
		if self.recording in children:
			self.main_vbox.remove(self.recording)
		elif self.compactmix in children:
			self.main_vbox.remove(self.compactmix)
		
		if self.headerhbox in children:
			self.main_vbox.remove(self.headerhbox)
		if self.tvtoolitem in self.wTree.get_widget("MainToolbar").get_children():
			self.wTree.get_widget("MainToolbar").remove(self.tvtoolitem)
		
		RecordingToggle = self.wTree.get_widget("Recording")
		CompactToggle = self.wTree.get_widget("CompactMix")
		
		ctrls = (self.save, self.save_as, self.close, self.addInstrumentButton,
			self.reverse, self.forward, self.play, self.stop, self.record,
			self.projectmenu, self.export, self.cut, self.copy, self.paste,
			self.undo, self.redo, self.delete,
			RecordingToggle, CompactToggle, 
			self.wTree.get_widget("WorkspacesLabel"))
		
		if self.project:
			# make various buttons and menu items enabled now we have a project option
			for c in ctrls:
				c.set_sensitive(True)
			
			#set undo/redo if there is saved undo history
			self.OnStateChanged()
				
			# Create our custom widgets
			self.timeview = TimeView.TimeView(self.project)
			self.compactmix = CompactMixView.CompactMixView(self.project, self)
			self.recording = RecordingView.RecordingView(self.project, self)
			
			# Add them to the main window
			self.main_vbox.pack_start(self.recording, True, True)
			
			self.tvtoolitem = gtk.ToolItem()
			self.tvtoolitem.add(self.timeview)
			self.wTree.get_widget("MainToolbar").insert(self.tvtoolitem, -1)
			
			self.compactmix.Update()
			self.OnRecordingView()
			
		else:
			for c in ctrls:
				c.set_sensitive(False)
			
			#untoggle all toggle buttons when the project is unloaded
			self.settingButtons = True
			for t in (RecordingToggle, CompactToggle):
				t.set_active(False)
			self.settingButtons = False
				
			# Set window title with no project name
			self.window.set_title('Jokosher')
			
			# Destroy our custom widgets
			if self.recording:
				self.recording.destroy()
				self.recording = None
			if self.compactmix:
				self.compactmix.destroy()
				self.compactmix = None
			if self.tvtoolitem:
				self.tvtoolitem.destroy()
				self.tvtoolitem = None
	
	#_____________________________________________________________________
	
	def OnKeyPress(self, widget, event):
		
		keysdict = {
			65471:self.OnRecordingView, # F2 - Recording View
			65472:self.OnCompactMixView, # F3 - Compact Mix View
			65535:self.OnDelete, # delete key - remove selected item
			65288:self.OnDelete, # backspace key
		}		

		if event.keyval in keysdict:
			keysdict[event.keyval]()
		
	#_____________________________________________________________________
	
	def OnInstrumentConnectonsDialog(self, widget):
		dlg = InstrumentConnectionsDialog.InstrumentConnectionsDialog(self.project, self)
		
	#_____________________________________________________________________
	
	def OnEditMenu(self, widget):
		#HACK: when the edit menu opens, check if any events or
		#instruments are selected and set the cut, copy, paste and delete accordingly
		instrSelected = False
		eventSelected = False
		for instr in self.project.instruments:
			if instr.isSelected:
				instrSelected = True
				break
			else:
				for ev in instr.events:
					if ev.isSelected:
						eventSelected = True
		
		self.cut.set_sensitive(instrSelected or eventSelected)
		self.copy.set_sensitive(instrSelected or eventSelected)
		self.paste.set_sensitive(instrSelected and bool(self.project.clipboardList))
		self.delete.set_sensitive(instrSelected or eventSelected)
	
	#_____________________________________________________________________
	
	def OnProjectMenu(self, widget):
		#HACK: when project menu opens, put the time format to the right
		#one so that we don't have to monitor transportmanager
		if self.settingButtons:
			return
		self.settingButtons = True
		a = self.wTree.get_widget("show_as_bars_beats_ticks")
		b = self.wTree.get_widget("show_as_hours_minutes_seconds")
		transport = self.project.transport
		
		a.set_active(transport.mode == transport.MODE_BARS_BEATS)
		b.set_active(transport.mode == transport.MODE_HOURS_MINS_SECS)
		
		self.settingButtons = False
	
	#_____________________________________________________________________
	
	def OpenLastProject(self):
		if self.lastopenedproject:
			path = self.lastopenedproject[0]
			name = self.lastopenedproject[1]
			try:
				self.SetProject(Project.LoadFromFile(path))
			except Project.OpenProjectError, e:
				self.ShowOpenProjectErrorDialog(e)
				#launch welcome dialog instead
				WelcomeDialog.WelcomeDialog(self)
	
	#_____________________________________________________________________
	
	def SetProject(self, project):
		try:
			project.ValidateProject()
		except Project.InvalidProjectError, e:
			message=""
			if e.files:
				message+="The project references non-existant files:\n"
				for f in e.files:
					message += f + "\n"
			if e.images:
				message+="\nThe project references non-existant images:\n"
				for f in e.images:
					message += f + "\n"

			dlg = gtk.MessageDialog(self.window,
				gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
				gtk.MESSAGE_ERROR,
				gtk.BUTTONS_OK,
				"%s\n Invalid or corrupt project file, will not open."%message)
			dlg.run()
			dlg.destroy()
			return

		if self.project:
			if self.CloseProject() != 0:
				return
			
		self.project = project
		self.project.AddListener(self)
		self.InsertRecentProject(project.projectfile, project.name)
		
		Project.GlobalProjectObject = project

		# make various buttons and menu items enabled now we have a project
		self.SetGUIProjectLoaded()
		
	#_____________________________________________________________________
	
	def CheckGstreamerVersions(self):
		#Check for CVS versions of Gstreamer and gnonlin
		message = ""
		v = gst.version()
		if (v[1] < 10) or (v[2] < 9):
			message += "You must have Gstreamer version 0.10.9 or higher.\n"
		gnl = gst.registry_get_default().find_plugin("gnonlin")
		if gnl and gnl.get_version() != "0.10.4.2" and gnl.get_version() != "0.10.5":
			message += "You must have Gstreamer plugin gnonlin version 0.10.4.2 (CVS) or 0.10.5.\n"
		elif not gnl:
			message += "Gstreamer plugin gnonlin is not installed." + \
			"\nSee http://jokosher.org/trac/wiki/GettingJokosher for more details.\n"
		try:
			import alsaaudio
		except:
			message += 'You must have the Python alsaaudio package installed.\n' + \
			'Please install python-alsaaudio or fetch from ' + \
			'http://www.wilstrup.net/pyalsaaudio/.'
		if message:
			dlg = gtk.MessageDialog(self.window,
				gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
				gtk.MESSAGE_WARNING,
				gtk.BUTTONS_CLOSE)
			dlg.set_markup("<big>Some functionality will not work correctly or at all.</big>\n\n%s" % message)
			dlg.run()
			dlg.destroy()
	
	#_____________________________________________________________________

	def SetStatusBar(self, message):
		return self.statusbar.Push(message)
	
	#_____________________________________________________________________

	def ClearStatusBar(self, messageID):
		self.statusbar.Remove(messageID)
	
	#_____________________________________________________________________

	def OnPreReleaseNotes(self, widget):
		dlg = gtk.MessageDialog(self.window,
			gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
			gtk.MESSAGE_WARNING,
			gtk.BUTTONS_CLOSE)
		dlg.set_markup("<big>Notes about this release</big>\n\nThis version of Jokosher (0.1) is a pre-release version, and is infact, our very first release. As such, you may encounter some bugs and functionality that is not present.")
		dlg.run()
		dlg.destroy()

	#_____________________________________________________________________

	def OnContributingDialog(self, widget):
		
		self.contribTree = gtk.glade.XML(self.GLADE_PATH, "ContributingDialog")

		self.topimage = self.contribTree.get_widget("topimage")
		self.topimage.set_from_file(os.path.join(Globals.JOKOSHER_PATH, "images", "jokosher-logo.png"))
		
		# grab some references to bits of the GUI
		self.contribdialog = self.wTree.get_widget("ContributingDialog")
		#self.contribdialog.show_all()
		
	#_____________________________________________________________________
	
	def ShowOpenProjectErrorDialog(self, error, parent=None):
		if not parent:
			parent = self.window
		
		if type(error.version) != str:
			message = "The project file could not be opened.\n"
		else:
			message = "The project file was created with version \"%s\" of Jokosher.\n"%error.version + \
						"Projects from version \"%s\" are incompatible with this release.\n"%error.version
			
		dlg = gtk.MessageDialog(parent,
			gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
			gtk.MESSAGE_ERROR,
			gtk.BUTTONS_OK,
			message)
		dlg.set_icon(self.icon)
		dlg.run()
		dlg.destroy()
	
	#_____________________________________________________________________

#=========================================================================
		
print "Starting up"

def main():
	app=MainApp()
	gtk.threads_init()
	gtk.main()

if __name__ == "__main__":
	main()

