#!/usr/bin/python
#
# THIS FILE IS PART OF THE JOKOSHER PROJECT AND LICENSED UNDER THE GPL. SEE
# THE 'COPYING' FILE FOR DETAILS
#
# Jokosher's main class. It creates the majority of the main window GUI
# and gets everything up and running.
#
#-------------------------------------------------------------------------------
import pygtk
pygtk.require("2.0")
import gtk.glade, gobject
import sys
import os.path
import pygst
pygst.require("0.10")
import gst
from subprocess import Popen, PIPE
import gettext
_ = gettext.gettext
import AddInstrumentDialog, TimeView, Workspace
import PreferencesDialog, ExtensionManagerDialog, RecordingView, NewProjectDialog
import ProjectManager, Globals, WelcomeDialog
import InstrumentConnectionsDialog
import EffectPresets, Extension, ExtensionManager
import Utils, AudioPreview, MixdownProfileDialog, MixdownActions
import PlatformUtils
import ui.StatusBar as StatusBar
#=========================================================================
class MainApp:
"""
Jokosher's main class. It creates the majority of the main window GUI and
gets everything up and running.
"""
# Class Constants
""" Constant value used to indicate Jokosher's recording mode """
MODE_RECORDING = 1
""" Constant value used to indicate Jokosher's mixing mode """
MODE_COMPACT_MIX = 2
#_____________________________________________________________________
def __init__(self, openproject=None, loadExtensions=True, startuptype=None):
"""
Creates a new instance of MainApp.
Parameters:
openproject -- filename of the project to open at startup.
loadExtensions -- whether the extensions should be loaded.
startuptype -- determines the startup state of Jokosher:
0 = Open the project referred by the openproject parameter.
1 = Do not display the welcome dialog or open a the previous project.
2 = Display the welcome dialog.
"""
# create tooltip messages for buttons
self.recTipEnabled = _("Stop recording")
self.recTipDisabled = _("Arm an instrument, then click here to begin recording")
self.recStopTipEnabled = _("Stop recording")
self.recStopTipDisabled = _("Stop playback")
self.mixingViewEnabledTip = _("Hide the audio level mixers")
self.mixingViewDisabledTip = _("Show the audio level mixers")
gtk.glade.bindtextdomain(Globals.LOCALE_APP, Globals.LOCALE_PATH)
gtk.glade.textdomain(Globals.LOCALE_APP)
self.wTree = gtk.glade.XML(Globals.GLADE_PATH, "MainWindow")
#Connect event handlers
signals = {
"on_MainWindow_destroy" : self.OnDestroy,
"on_MainWindow_configure_event" : self.OnResize,
"on_AddInstrument_clicked" : self.OnShowAddInstrumentDialog,
"on_About_activate" : self.About,
"on_Record_toggled" : self.Record,
"on_Play_clicked" : self.Play,
"on_Stop_clicked" : self.Stop,
"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.OnInstrumentConnectionsDialog,
"on_filemenu_activate" : self.OnFileMenu,
"on_editmenu_activate" : self.OnEditMenu,
"on_help_contents_activate" : self.OnHelpContentsMenu,
"on_forums_activate" : self.OnForumsMenu,
"on_contributing_activate" : self.OnContributingDialog,
"on_ExtensionManager_activate" : self.OnExtensionManagerDialog,
"on_instrumentmenu_activate" : self.OnInstrumentMenu,
"on_instrMenu_add_audio" : self.OnAddAudio,
"on_change_instr_type_activate" : self.OnChangeInstrument,
"on_remove_instr_activate" : self.OnRemoveInstrument,
"on_report_bug_activate" : self.OnReportBug,
"on_project_add_audio" : self.OnAddAudioFile,
"on_system_information_activate" : self.OnSystemInformation,
"on_properties_activate" : self.OnProjectProperties,
}
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.compactMixButton = self.wTree.get_widget("CompactMix")
self.editmenu = self.wTree.get_widget("editmenu")
self.filemenu = self.wTree.get_widget("filemenu")
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.instrumentMenu = self.wTree.get_widget("instrumentmenu")
self.export = self.wTree.get_widget("export")
self.recentprojects = self.wTree.get_widget("recentprojects")
self.recentprojectsmenu = self.wTree.get_widget("recentprojects_menu")
self.menubar = self.wTree.get_widget("menubar")
self.toolbar = self.wTree.get_widget("MainToolbar")
self.addAudioMenuItem = self.wTree.get_widget("add_audio_file_instrument_menu")
self.changeInstrMenuItem = self.wTree.get_widget("change_instrument_type")
self.removeInstrMenuItem = self.wTree.get_widget("remove_selected_instrument")
self.addAudioFileButton = self.wTree.get_widget("addAudioFileButton")
self.addAudioFileMenuItem = self.wTree.get_widget("add_audio_file_project_menu")
self.addInstrumentFileMenuItem = self.wTree.get_widget("add_instrument1")
self.recordingInputsFileMenuItem = self.wTree.get_widget("instrument_connections1")
self.timeFormatFileMenuItem = self.wTree.get_widget("time_format1")
self.properties_menu_item = self.wTree.get_widget("project_properties")
self.recentprojectitems = []
self.lastopenedproject = None
self.project = None
self.headerhbox = None
self.timeview = None
self.tvtoolitem = None #wrapper for putting timeview in toolbar
self.workspace = 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.compactMixButton.set_active(False)
self.settingButtons = False
self.isRecording = False
self.isPlaying = False
self.isPaused = False
# Intialise context sensitive tooltips for workspace buttons
self.compactMixButton.set_tooltip_text(self.mixingViewDisabledTip)
# set the window size to the last saved value
x = int(Globals.settings.general["windowwidth"])
y = int(Globals.settings.general["windowheight"])
self.window.resize(x, y)
# 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)
miximg = gtk.Image()
miximg.set_from_file(os.path.join(Globals.IMAGE_PATH, "icon_mix.png"))
self.compactMixButton.set_icon_widget(miximg)
miximg.show()
#get the audiofile image from Globals
self.audioFilePixbuf = Globals.getCachedInstrumentPixbuf("audiofile")
audioimg = gtk.Image()
size = gtk.icon_size_lookup(gtk.ICON_SIZE_MENU)
pixbuf = self.audioFilePixbuf.scale_simple(size[0], size[1], gtk.gdk.INTERP_BILINEAR)
audioimg.set_from_pixbuf(pixbuf)
# set the add audio menu item icon
self.addAudioMenuItem.set_image(audioimg)
size = gtk.icon_size_lookup(self.toolbar.get_icon_size())
pixbuf = self.audioFilePixbuf.scale_simple(size[0], size[1], gtk.gdk.INTERP_BILINEAR)
audioimg = gtk.Image()
audioimg.set_from_pixbuf(pixbuf)
self.addAudioFileButton.set_icon_widget(audioimg)
audioimg.show()
# populate the Recent Projects menu
self.OpenRecentProjects()
self.PopulateRecentProjects()
# set window icon
icon_theme = gtk.icon_theme_get_default()
try:
pixbuf = icon_theme.load_icon("jokosher", 48, 0)
self.window.set_icon(pixbuf)
except gobject.GError, exc:
self.window.set_icon_from_file(os.path.join(Globals.IMAGE_PATH, "jokosher.png"))
# make icon available to others
self.window.realize()
self.icon = self.window.get_icon()
# 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()
# set up presets registry - this should probably be removed here
EffectPresets.EffectPresets()
Globals.PopulateEncoders()
Globals.PopulateAudioBackends()
# seems like this is the best place to instantiate RegisterMixdownActionAPI
# as extensions and the mixdown profile dialog can use it through mainapp
self.registerMixdownActionAPI = MixdownActions.RegisterMixdownActionAPI()
# register the default MixdownActions
self.registerMixdownActionAPI.RegisterMixdownActions((MixdownActions.RunAScript, MixdownActions.ExportAsFileType))
if loadExtensions:
# Load extensions -- this should probably go somewhere more appropriate
self.extensionManager = ExtensionManager.ExtensionManager(self)
## Setup is complete so start up the GUI and perhaps load a project
## any new setup code needs to go above here
# Show the main window
self.window.show_all()
# command line options override preferences so check for them first,
# then preferences, then default to the welcome dialog
if startuptype == 2: # welcomedialog cmdline switch
WelcomeDialog.WelcomeDialog(self)
return
elif startuptype == 1: # no-project cmdline switch
return
elif openproject: # a project name on the cmdline
self.OpenProjectFromPath(openproject)
elif Globals.settings.general["startupaction"] == PreferencesDialog.STARTUP_LAST_PROJECT:
if self.lastopenedproject:
self.OpenProjectFromPath(self.lastopenedproject[0])
elif Globals.settings.general["startupaction"] == PreferencesDialog.STARTUP_NOTHING:
return
#if everything else bombs out resort to the welcome dialog
if self.project == None:
WelcomeDialog.WelcomeDialog(self)
#_____________________________________________________________________
def OnCompactMixView(self, button=None):
"""
Updates the main window after switching to the compact view mixing mode.
Parameters:
button -- Button object calling this method.
"""
if self.workspace:
self.workspace.ToggleCompactMix()
#_____________________________________________________________________
def OnF3Pressed(self):
"""
Toggle to compact mix view button when F3 is pressed.
"""
self.compactMixButton.set_active(not self.compactMixButton.get_active())
#_____________________________________________________________________
def OnResize(self, widget, event):
"""
Called when the main window gets resized.
Parameters:
widget -- GTK callback parameter.
event -- GTK callback parameter.
Returns:
False -- continue GTK signal propagation.
"""
(self.width, self.height) = widget.get_size()
return False
#_____________________________________________________________________
def OnDestroy(self, widget=None, event=None):
"""
Called when the main window is destroyed.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
event -- reserved for GTK callbacks, don't use it explicitly.
Returns:
True -- the current project can't be properly closed.
This stops signal propagation.
"""
# save the current window size
Globals.settings.general["windowwidth"] = self.width
Globals.settings.general["windowheight"] = self.height
Globals.settings.write()
if self.CloseProject() == 0:
gtk.main_quit()
else:
return True #stop signal propogation
#_____________________________________________________________________
def OnShowAddInstrumentDialog(self, widget=None):
"""
Creates and shows the "Add Instrument" dialog box.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
AddInstrumentDialog.AddInstrumentDialog(self.project, self)
#_____________________________________________________________________
def OnChangeInstrument(self, widget=None):
"""
Changes the type of the selected Instrument.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
# Change the type of a select instrument
for instr in self.project.instruments:
if (instr.isSelected):
AddInstrumentDialog.AddInstrumentDialog(self.project, self, instr)
return
#_____________________________________________________________________
def About(self, widget=None):
"""
Creates and shows the "About" dialog box.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
gtk.about_dialog_set_url_hook(self.AboutLinkActivate)
aboutTree = gtk.glade.XML(Globals.GLADE_PATH, "AboutDialog")
dlg = aboutTree.get_widget("AboutDialog")
dlg.set_transient_for(self.window)
dlg.set_icon(self.icon)
dlg.run()
dlg.destroy()
#_____________________________________________________________________
def AboutLinkActivate(self, widget, link):
"""
Opens the Jokosher website in the user's default web browser.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
Utils.OpenExternalURL(url=link, message=_("Couldn't launch the jokosher website automatically.\n\nPlease visit %s to access it."), parent=self.window)
#_____________________________________________________________________
def OnReportBug(self, widget):
"""
Opens the report bug launchpad website in the user's default web browser.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
Utils.OpenExternalURL(url="https://bugs.launchpad.net/jokosher/+filebug/", message=_("Couldn't launch the launchpad website automatically.\n\nPlease visit %s to access it."), parent=self.window)
#_____________________________________________________________________
def Record(self, widget=None):
"""
Toggles recording. If there's an error, a warning/error message is
issued to the user.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
# toggling the record button invokes this function so we use the settingButtons var to
# indicate that we're just changing the GUI state and dont need to do anything code-wise
if self.settingButtons:
return
if self.isRecording:
self.project.Stop()
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 = []
armed_instrs = [x for x in self.project.instruments if x.isArmed]
for instrA in armed_instrs:
for instrB in armed_instrs:
if instrA is not instrB and instrA.input == instrB.input and instrA.inTrack == instrB.inTrack:
string = _("The instruments '%(name1)s' and '%(name2)s' both have the same input selected. Please either disarm one, or connect it to a different input through 'Project -> Recording Inputs'")
message = string % {"name1" : instrA.name, "name2" : instrB.name}
dlg = gtk.MessageDialog(self.window,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_INFO,
gtk.BUTTONS_CLOSE,
message)
dlg.connect('response', lambda dlg, response: dlg.destroy())
dlg.run()
self.settingButtons = True
widget.set_active(False)
self.settingButtons = False
return
if not canRecord:
Globals.debug("can not record")
if self.project.instruments:
errmsg = "No instruments are armed for recording. You need to arm an instrument before you can begin recording."
else:
errmsg = "No instruments have been added. You must add an instrument before recording"
dlg = gtk.MessageDialog(self.window,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_INFO,
gtk.BUTTONS_CLOSE,
_(errmsg))
dlg.connect('response', lambda dlg, response: dlg.destroy())
dlg.run()
self.settingButtons = True
widget.set_active(False)
self.settingButtons = False
else:
Globals.debug("can record")
self.project.Record()
#_____________________________________________________________________
def TogglePlayIcon(self):
"""
Changes the play button icon/tooltip from play to pause and viceversa.
"""
if not self.isPlaying:
self.play.set_stock_id(gtk.STOCK_MEDIA_PLAY)
else:
self.play.set_stock_id(gtk.STOCK_MEDIA_PAUSE)
# TODO: change the tooltips in 1.0
#self.contextTooltips.set_tip(play, tooltip)
#_____________________________________________________________________
def Play(self, widget=None):
"""
Toggles playback.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
if self.settingButtons == True:
return
self.TogglePlayIcon()
if not self.isPlaying:
self.project.Play()
else:
self.project.Pause()
#_____________________________________________________________________
#The stop button is really just an alias for toggling play/record to off
def Stop(self, widget=None):
"""
Stops the current record/playback (whichever is happening) operation.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
self.project.Stop()
#_____________________________________________________________________
def OnRewindPressed(self, widget=None):
"""
Starts moving backward within the project's timeline.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
self.project.transport.Reverse(True)
#_____________________________________________________________________
def OnRewindReleased(self, widget=None):
"""
Stops the current rewind operation.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
self.project.transport.Reverse(False)
#_____________________________________________________________________
def OnForwardPressed(self, widget=None):
"""
Starts moving forward within the project's timeline.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
self.project.transport.Forward(True)
#_____________________________________________________________________
def OnForwardReleased(self, widget=None):
"""
Stops the current forward operation.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
self.project.transport.Forward(False)
#_____________________________________________________________________
def OnExport(self, widget=None, profile=None):
"""
Displays the Mixdown Profiles dialog, which allows the user to
(simply) export the project as ogg or mp3 (replacing the old
export dialog), or create a mixdown profile that does a set of
complicated things.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
MixdownProfileDialog.MixdownProfileDialog(self, self.project, profile)
#_____________________________________________________________________
def OnExport_old(self, widget=None):
"""
Creates and shows a save file dialog which allows the user to export
the project as ogg or mp3.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
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)
if os.path.exists(Globals.settings.general["projectfolder"]):
chooser.set_current_folder(Globals.settings.general["projectfolder"])
else:
chooser.set_current_folder(os.path.expanduser("~"))
chooser.set_do_overwrite_confirmation(True)
chooser.set_default_response(gtk.RESPONSE_OK)
chooser.set_current_name(self.project.name)
saveLabel = gtk.Label(_("Save as file type:"))
typeCombo = gtk.combo_box_new_text()
for format in Globals.EXPORT_FORMATS:
typeCombo.append_text("%s (.%s)" % (format["description"], format["extension"]))
#Make the first item the default
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:
exportFilename = chooser.get_filename()
Globals.settings.general["projectfolder"] = os.path.dirname(exportFilename)
Globals.settings.write()
#If they haven't already appended the extension for the
#chosen file type, add it to the end of the file.
filetypeDict = Globals.EXPORT_FORMATS[typeCombo.get_active()]
if not exportFilename.lower().endswith(filetypeDict["extension"]):
exportFilename += "." + filetypeDict["extension"]
chooser.destroy()
self.project.Export(exportFilename, filetypeDict["pipeline"])
else:
chooser.destroy()
#_____________________________________________________________________
def UpdateExportDialog(self):
"""
Updates the progress bar corresponding to the current export operation.
"""
progress = self.project.GetExportProgress()
if progress[0] == -1 or progress[1] == 0:
self.exportprogress.set_fraction(0.0)
self.exportprogress.set_text(_("Preparing to mixdown project"))
elif progress[0] == progress[1] == 100:
self.exportdlg.destroy()
return False
else:
self.exportprogress.set_fraction(progress[0]/progress[1])
self.exportprogress.set_text(_("%(progress)d%% of %(total)d seconds completed") % {"progress":(progress[0]/progress[1]*100), "total":progress[1] } )
return True
#_____________________________________________________________________
def OnExportCancel(self, widget=None):
"""
Cancels a running export operation and destroys the export progress dialog.
Parameters:
widget: reserved for GTK callbacks, don't use it explicitly.
"""
self.exportdlg.destroy()
self.project.TerminateExport()
#_____________________________________________________________________
def OnPreferences(self, widget, destroyCallback=None):
"""
Creates and shows the "Jokosher Preferences" dialog.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
destroyCallback -- function that'll get called when the preferences
dialog gets destroyed.
"""
prefsdlg = PreferencesDialog.PreferencesDialog(self.project, self, self.icon)
if destroyCallback:
prefsdlg.dlg.connect("destroy", destroyCallback)
#_____________________________________________________________________
def OnShowBarsBeats(self, widget):
"""
Sets and updates the current timeline view to Bars, Beats and Ticks.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
if self.settingButtons:
return
if widget.get_active() and self.project and self.project.transport:
self.project.SetTransportMode(self.project.transport.MODE_BARS_BEATS)
#_____________________________________________________________________
def OnShowHoursMins(self, widget):
"""
Sets and updates the current timeline view to Hours, Minutes and Seconds.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
if self.settingButtons:
return
if widget.get_active() and self.project and self.project.transport:
self.project.SetTransportMode(self.project.transport.MODE_HOURS_MINS_SECS)
#_____________________________________________________________________
def OnOpenProject(self, widget, destroyCallback=None):
"""
Creates and shows a open file dialog which allows the user to open
an existing Jokosher project.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
destroyCallback -- function that'll get called when the open file
dialog gets destroyed.
"""
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))
if os.path.exists(Globals.settings.general["projectfolder"]):
chooser.set_current_folder(Globals.settings.general["projectfolder"])
else:
chooser.set_current_folder(os.path.expanduser("~"))
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()
Globals.settings.general["projectfolder"] = os.path.dirname(filename)
Globals.settings.write()
if self.OpenProjectFromPath(filename,chooser):
break
elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
break
chooser.destroy()
#_____________________________________________________________________
def OnSaveProject(self, widget=None):
"""
Saves the current project file.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
if self.project:
self.project.SelectInstrument(None)
self.project.ClearEventSelections()
self.project.SaveProjectFile()
#_____________________________________________________________________
def OnSaveAsProject(self, widget=None):
"""
Creates and shows a save as file dialog which allows the user to save
the current project to an specific file name.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
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)
chooser.set_do_overwrite_confirmation(False)
chooser.set_current_name(self.project.name)
chooser.set_default_response(gtk.RESPONSE_OK)
if os.path.exists(Globals.settings.general["projectfolder"]):
chooser.set_current_folder(Globals.settings.general["projectfolder"])
else:
chooser.set_current_folder(os.path.expanduser("~"))
response = chooser.run()
if response == gtk.RESPONSE_OK:
# InitProjectLocation expects a URI
folder = PlatformUtils.pathname2url(chooser.get_current_folder())
# Save the selected folder as the default folder
Globals.settings.general["projectfolder"] = folder
Globals.settings.write()
name = os.path.basename(chooser.get_filename())
old_audio_path = self.project.audio_path
old_levels_path = self.project.levels_path
try:
ProjectManager.InitProjectLocation(folder, name, self.project)
except ProjectManager.CreateProjectError, e:
chooser.hide()
if e.errno == 2:
message = _("A file or folder with this name already exists. Please choose a different project name and try again.")
elif e.errno == 3:
message = _("The file or folder location is write-protected.")
elif e.errno == 5:
message = _("The URI scheme given is either invalid or not supported")
# show the error dialog with the relavent error message
dlg = gtk.MessageDialog(self.window,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR,
gtk.BUTTONS_OK,
_("Unable to create project.\n\n%s") % message)
dlg.run()
dlg.destroy()
else:
self.project.SelectInstrument()
self.project.ClearEventSelections()
self.project.SaveProjectFile(self.project.projectfile)
Globals.CopyAllFiles(old_audio_path, self.project.audio_path, self.project.GetLocalAudioFilenames())
Globals.CopyAllFiles(old_levels_path, self.project.levels_path, self.project.GetLevelsFilenames())
chooser.destroy()
#_____________________________________________________________________
def OnNewProject(self, widget, destroyCallback=None):
"""
Creates and shows the "New Project" dialog.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
destroyCallback -- function that'll get called when the new project
dialog gets destroyed.
"""
newdlg = NewProjectDialog.NewProjectDialog(self)
if destroyCallback:
newdlg.dlg.connect("destroy", destroyCallback)
#_____________________________________________________________________
def OnCloseProject(self, widget):
"""
Closes the current project by calling CloseProject().
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
if self.CloseProject() == 0:
self.SetGUIProjectLoaded()
#_____________________________________________________________________
def CloseProject(self):
"""
Closes the current project. If there's changes pending, it'll ask the user for confirmation.
Returns:
the status of the close operation:
0 = there was no project open or it was closed succesfully.
1 = cancel the operation and return to the normal program flow.
"""
if not self.project:
return 0
self.Stop()
if self.project.CheckUnsavedChanges():
message = _("Save changes to project \"%s\" before closing?\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()
self.project = None
self.mode = None
return 0
#_____________________________________________________________________
def OnUndo(self, widget):
"""
Undoes the last change made to the project and updates the displays.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
self.project.Undo()
#_____________________________________________________________________
def OnRedo(self, widget):
"""
Redoes the last undo operation and updates the displays.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
self.project.Redo()
#_____________________________________________________________________
def OnProjectAudioState(self, project):
"""
Callback for when the project starts playing or recording, or when it is
paused or stopped.
Parameters:
project -- The project instance that send the signal.
"""
self.isPlaying = (self.project.audioState == self.project.AUDIO_PLAYING)
self.isPaused = (self.project.audioState == self.project.AUDIO_PAUSED)
self.isRecording = (self.project.audioState == self.project.AUDIO_RECORDING)
self.stop.set_sensitive(True) #stop should always be clickable
self.record.set_sensitive(not self.isPlaying)
controls = (self.play, self.reverse, self.forward, self.editmenu, self.instrumentMenu,
self.workspace.recordingView.timelinebar.headerhbox,
self.addInstrumentButton, self.addAudioFileButton)
for widget in controls:
widget.set_sensitive(not self.isRecording)
self.settingButtons = True
self.record.set_active(self.isRecording)
self.TogglePlayIcon()
self.settingButtons = False
# update the tooltips depending on the current recording state
if self.isRecording:
self.record.set_tooltip_text(self.recTipEnabled)
self.stop.set_tooltip_text(self.recStopTipEnabled)
else:
self.record.set_tooltip_text(self.recTipDisabled)
self.stop.set_tooltip_text(self.recStopTipDisabled)
self.workspace.mixView.StartUpdateTimeout()
#_____________________________________________________________________
def OnProjectExportStart(self, project):
"""
Callback for when the project starts exporting audio to a file.
Parameters:
project -- The project instance that send the signal.
"""
export = gtk.glade.XML (Globals.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") % self.project.exportFilename)
self.exportprogress = export.get_widget("progressBar")
gobject.timeout_add(100, self.UpdateExportDialog)
#_____________________________________________________________________
def OnProjectExportStop(self, project):
"""
Callback for when the project has finished exporting audio to a file.
Parameters:
project -- The project instance that send the signal.
"""
if self.exportdlg:
self.exportdlg.destroy()
#_____________________________________________________________________
def OnProjectUndo(self, project=None):
"""
Callback for when the project's undo or redo stacks change.
Parameters:
project -- The project instance that send the signal.
"""
self.undo.set_sensitive(self.project.CanPerformUndo())
self.redo.set_sensitive(self.project.CanPerformRedo())
if self.project.CheckUnsavedChanges():
self.window.set_title(_('*%s - Jokosher') % self.project.name)
else:
self.window.set_title(_('%s - Jokosher') % self.project.name)
#_____________________________________________________________________
def OnTransportMode(self, transportManager=None, mode=None):
"""
Callback for signal when the transport mode changes.
Parameters:
transportManager -- the TransportManager instance that send the signal.
mode -- the mode type that the transport changed to.
"""
if self.settingButtons:
return
self.settingButtons = True
modeBars = self.wTree.get_widget("show_as_bars_beats_ticks")
modeHours = self.wTree.get_widget("show_as_hours_minutes_seconds")
transport = self.project.transport
modeBars.set_active(transport.mode == transport.MODE_BARS_BEATS)
modeHours.set_active(transport.mode == transport.MODE_HOURS_MINS_SECS)
self.settingButtons = False
#_____________________________________________________________________
def InsertRecentProject(self, path, name):
"""
Inserts a new project with its corresponding path to the recent project list.
Parameters:
path -- path to the project file.
name -- name of the project being added.
"""
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 OnClearRecentProjects(self, widget):
"""
Clears the recent projects list. It then updates the user interface to reflect
the changes.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
self.recentprojectitems = []
self.SaveRecentProjects()
self.PopulateRecentProjects()
#_____________________________________________________________________
def PopulateRecentProjects(self):
"""
Populates the Recent Projects menu with items from self.recentprojectitems.
"""
menuitems = self.recentprojectsmenu.get_children()
for c in menuitems:
self.recentprojectsmenu.remove(c)
if self.recentprojectitems:
for item in self.recentprojectitems:
mitem = gtk.MenuItem(item[1])
mitem.set_tooltip_text(item[0])
self.recentprojectsmenu.append(mitem)
mitem.connect("activate", self.OnRecentProjectsItem, item[0], item[1])
mitem = gtk.SeparatorMenuItem()
self.recentprojectsmenu.append(mitem)
mitem = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
mitem.set_tooltip_text(_("Clear the list of recent projects"))
self.recentprojectsmenu.append(mitem)
mitem.connect("activate", self.OnClearRecentProjects)
self.recentprojects.set_sensitive(True)
self.recentprojectsmenu.show_all()
else:
#there are no items, so just make it insensitive
self.recentprojects.set_sensitive(False)
#_____________________________________________________________________
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):
Globals.debug("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):
"""
Opens the project selected from the "Recent Projects" drop-down menu.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
path -- path to the project file.
name -- name of the project being opened.
"""
return self.OpenProjectFromPath(path)
#_____________________________________________________________________
def SaveRecentProjects(self):
"""
Saves the list of the last 8 recent projects to the Jokosher config file.
"""
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=None, cut=True):
"""
Cuts the portion of selected audio and puts it in the clipboard.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
cut -- determines whether the operation should perform a cut or copy operation:
True = perform a cut operation.
False = perform a copy operation.
"""
if self.isPlaying or self.isPaused:
return
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()
#______________________________________________________________________
def OnCopy(self, widget=None):
"""
Copies the portion of selected audio to the clipboard.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
self.OnCut(widget, False)
#______________________________________________________________________
def OnPaste(self, widget=None):
"""
Pastes the portion of audio in the clipboard to the selected instrument,
at the selected position in time.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
if self.isPlaying or self.isPaused:
return
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
#______________________________________________________________________
def OnDelete(self, widget=None):
"""
Deletes the currently selected instruments or events.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
if self.project.GetIsRecording() or self.isPlaying or self.isPaused:
return
# list to store instruments to delete, so we don't modify the list while we are iterating
instrOrEventList = []
eventList = []
# 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
instrOrEventList.append(instr)
else:
# Delete any selected events
for ev in instr.events:
if ev.isSelected:
instrOrEventList.append(ev)
if instrOrEventList:
self.project.DeleteInstrumentsOrEvents(instrOrEventList)
#______________________________________________________________________
def OnMouseDown(self, widget, mouse):
"""
If there's a project open, clears event and instrument selections.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
mouse -- reserved for GTK callbacks, don't use it explicitly.
"""
if self.project:
self.project.ClearEventSelections()
self.project.SelectInstrument(None)
#______________________________________________________________________
def SetGUIProjectLoaded(self):
"""
Refreshes the main window and it's components when a project is opened or closed.
For example, buttons are enabled/disabled whether there's a project currently open or not.
"""
children = self.main_vbox.get_children()
if self.workspace in children:
self.main_vbox.remove(self.workspace)
if self.headerhbox in children:
self.main_vbox.remove(self.headerhbox)
if self.tvtoolitem in self.toolbar.get_children():
self.toolbar.remove(self.tvtoolitem)
ctrls = (self.save, self.save_as, self.close, self.addInstrumentButton, self.addAudioFileButton,
self.reverse, self.forward, self.play, self.stop, self.record,
self.instrumentMenu, self.export, self.cut, self.copy, self.paste,
self.undo, self.redo, self.delete, self.compactMixButton, self.properties_menu_item,
self.addAudioFileMenuItem, self.addInstrumentFileMenuItem, self.recordingInputsFileMenuItem,
self.timeFormatFileMenuItem)
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.OnProjectUndo()
# Create our custom widgets
self.timeview = TimeView.TimeView(self.project)
self.workspace = Workspace.Workspace(self.project, self)
# Add them to the main window
self.main_vbox.pack_start(self.workspace, True, True)
self.tvtoolitem = gtk.ToolItem()
self.tvtoolitem.add(self.timeview)
self.toolbar.insert(self.tvtoolitem, -1)
self.tvtoolitem.show_all()
#reset toggle buttons
self.settingButtons = True
self.compactMixButton.set_active(False)
self.settingButtons = False
else:
#reset toggle buttons when the project is unloaded
self.settingButtons = True
self.compactMixButton.set_active(False)
self.settingButtons = False
for c in ctrls:
c.set_sensitive(False)
# Set window title with no project name
self.window.set_title(_('Jokosher'))
# Destroy our custom widgets
if self.workspace:
self.workspace.destroy()
self.workspace = None
if self.tvtoolitem:
self.tvtoolitem.destroy()
self.tvtoolitem = None
#_____________________________________________________________________
def OnKeyPress(self, widget, event):
"""
Handles the hotkeys, calling whichever function they are assigned to.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
event -- reserved for GTK callbacks, don't use it explicitly.
"""
key = gtk.gdk.keyval_name(event.keyval)
if 'GDK_CONTROL_MASK' in event.state.value_names:
keysdict = {
"x" : self.OnCut, # Ctrl-X
"c" : self.OnCopy, # Ctrl-C
"v" : self.OnPaste, # Ctrl-V
}
else:
keysdict = {
"F1" : self.OnHelpContentsMenu, # F1 - Help Contents
"F3" : self.OnF3Pressed, # F3 - Compact Mix View
"Delete" : self.OnDelete, # delete key - remove selected item
"BackSpace" : self.OnDelete, # backspace key
"space" : self.Play,
"p" : self.Play,
"r" : self.Record
}
if key in keysdict:
keysdict[key]()
#very important; return True if we successfully handled the key press
#so that someone else doesn't handle it afterwards as well.
return True
else:
return False
#_____________________________________________________________________
def OnInstrumentConnectionsDialog(self, widget):
"""
Creates and shows the "Instrument Connections Dialog".
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
InstrumentConnectionsDialog.InstrumentConnectionsDialog(self.project, self)
#_____________________________________________________________________
def OnFileMenu(self, widget):
"""
When the file menu opens, check if there are any events and set the mixdown project menu item's
sensitivity accordingly and also the 'mixdown as' sensitivity.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
self.PopulateMixdownAsMenu()
if self.isRecording:
self.export.set_sensitive(False)
self.addInstrumentFileMenuItem.set_sensitive(False)
self.addAudioFileMenuItem.set_sensitive(False)
self.recordingInputsFileMenuItem.set_sensitive(False)
if self.mixdown_as_header:
self.mixdown_as_header.set_sensitive(False)
return
eventList = False
if self.project:
self.addInstrumentFileMenuItem.set_sensitive(True)
self.addAudioFileMenuItem.set_sensitive(True)
self.recordingInputsFileMenuItem.set_sensitive(True)
for instr in self.project.instruments:
if instr.events:
eventList = True
break
self.export.set_sensitive(eventList)
if self.mixdown_as_header:
self.mixdown_as_header.set_sensitive(eventList)
#_____________________________________________________________________
def OnEditMenu(self, widget):
"""
HACK: When the edit menu opens, checks if any events or instruments are selected
and sets the cut, copy, paste and delete menu items sensitivity accordingly.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
instrSelected = False
eventSelected = False
if self.project:
for instr in self.project.instruments:
if instrSelected and eventSelected:
break
if instr.isSelected:
instrSelected = True
else:
for ev in instr.events:
if ev.isSelected:
eventSelected = True
self.cut.set_sensitive(eventSelected)
self.copy.set_sensitive(eventSelected)
self.paste.set_sensitive(instrSelected and bool(self.project.clipboardList))
self.delete.set_sensitive(instrSelected or eventSelected)
#_____________________________________________________________________
def OnInstrumentMenu(self, widget):
"""
HACK: When the instrument menu opens, set sensitivity depending on
whether there's a selected instrument or not.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
instrCount = 0
if self.project:
for instr in self.project.instruments:
if instr.isSelected:
instrCount += 1
self.addAudioMenuItem.set_sensitive(instrCount == 1)
self.changeInstrMenuItem.set_sensitive(instrCount == 1)
self.removeInstrMenuItem.set_sensitive(instrCount > 0)
#_____________________________________________________________________
def OpenProjectFromPath(self,path, parent=None):
"""
Opens the project file referred by the path parameter.
Parameters:
path -- path to the project to be opened.
parent -- parent window of the error message dialog.
Returns:
the status of the loading operation:
True = the project could be successfully opened and
set as the current project.
False = loading the project failed. A dialog will be
displayed to user detailing the error.
"""
try:
uri = PlatformUtils.pathname2url(path)
self.SetProject(ProjectManager.LoadProjectFile(uri))
return True
except ProjectManager.OpenProjectError, e:
self.ShowOpenProjectErrorDialog(e,parent)
return False
#_____________________________________________________________________
def SetProject(self, project):
"""
Tries to establish the Project parameter as the current project.
If there are errors, an error message is issued to the user.
Parameters:
project -- the Project object to set as the main project.
"""
try:
ProjectManager.ValidateProject(project)
except ProjectManager.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.connect("audio-state::play", self.OnProjectAudioState)
self.project.connect("audio-state::pause", self.OnProjectAudioState)
self.project.connect("audio-state::record", self.OnProjectAudioState)
self.project.connect("audio-state::stop", self.OnProjectAudioState)
self.project.connect("audio-state::export-start", self.OnProjectExportStart)
self.project.connect("audio-state::export-stop", self.OnProjectExportStop)
self.project.connect("undo", self.OnProjectUndo)
self.project.transport.connect("transport-mode", self.OnTransportMode)
self.OnTransportMode()
self.InsertRecentProject(project.projectfile, project.name)
self.project.PrepareClick()
# 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. If requirements are not met,
a warning message is issued to the user.
"""
#Check for CVS versions of Gstreamer and gnonlin
message = ""
gstVersion = gst.version()
if ((gstVersion[1] <= 10 and gstVersion[2] < 9) or gstVersion[1] < 10):
message += _("You must have Gstreamer version 0.10.9 or higher.\n")
gnl = gst.registry_get_default().find_plugin("gnonlin")
if gnl:
ignored, gnlMajor, gnlMinor = gnl.get_version().split(".", 2)
#Compare gnlMajor and gnlMinor as a float so later versions of gnonlin will work
gnlMajor = float(gnlMajor)
gnlMinor = float(gnlMinor)
if gnlMajor < 10 or gnlMinor < 4.2:
message += _("You must have Gstreamer plugin gnonlin version 0.10.4.2 or later.\n")
elif not gnl:
message += _("Gstreamer plugin gnonlin is not installed.") + \
_("\nSee http://doc.jokosher.org/Installation for more details.\n")
if not gst.registry_get_default().find_plugin("level"):
message += _("You must have the Gstreamer plugin packs gst-plugins-base and gst-plugins-good installed.\n")
if message:
dlg = gtk.MessageDialog(self.window,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_WARNING,
gtk.BUTTONS_CLOSE)
dlg.set_markup(_("Some functionality will not work correctly or at all.\n\n%s") % message)
dlg.run()
dlg.destroy()
#_____________________________________________________________________
def SetStatusBar(self, message):
"""
Appends the message parameter to the status bar text.
Parameters:
message -- string to append to the status bar text.
"""
return self.statusbar.Push(message)
#_____________________________________________________________________
def ClearStatusBar(self, messageID):
"""
Clears the status bar text in the position pointed by messageID.
Parameters:
messageID -- the message identifier of the text to be cleared.
"""
self.statusbar.Remove(messageID)
#_____________________________________________________________________
def OnHelpContentsMenu(self, widget=None):
"""
Calls the appropiate help tool with the user manual in the correct
locale.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
if gtk.pygtk_version[0] == 2 and gtk.pygtk_version[1] < 14:
helpfile = "http://doc.jokosher.org"
elif Globals.USE_LOCAL_HELP:
helpfile = "ghelp:" + Globals.HELP_PATH
else:
helpfile = "ghelp:jokosher"
Utils.OpenExternalURL(url=helpfile, message=_("Couldn't launch the Jokosher documentation site.\n\nPlease visit %s to access it."), parent=self.window)
#_____________________________________________________________________
def OnForumsMenu(self, widget):
"""
Opens the Jokosher forum in the user's default web browser.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
Utils.OpenExternalURL(url="http://www.jokosher.org/forums", message=_("Couldn't launch the forums website automatically.\n\nPlease visit %s to access them."), parent=self.window)
#_____________________________________________________________________
def OnContributingDialog(self, widget):
"""
Creates and shows the "Contributing to Jokosher" dialog.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
self.contribTree = gtk.glade.XML(Globals.GLADE_PATH, "ContributingDialog")
# grab references to the ContributingDialog window and vbox
self.contribdialog = self.contribTree.get_widget("ContributingDialog")
self.contribvbox = self.contribTree.get_widget("vbox14")
self.contribdialog.set_icon(self.icon)
# centre the ContributingDialog window on MainWindow
self.contribdialog.set_transient_for(self.window)
# set the contributing image
self.topimage = self.contribTree.get_widget("topimage")
self.topimage.set_from_file(os.path.join(Globals.IMAGE_PATH, "jokosher-logo.png"))
# create the bottom vbox containing the contributing website link
vbox = gtk.VBox()
label = gtk.Label()
label.set_markup(_("To find out more, visit:"))
vbox.pack_start(label, False, False)
if gtk.pygtk_version >= (2, 10, 0) and gtk.gtk_version >= (2, 10, 0):
contriblnkbtn = gtk.LinkButton("http://www.jokosher.org/contribute", label="http://www.jokosher.org/contribute")
contriblnkbtn.connect("clicked", self.OnContributingLinkButtonClicked)
vbox.pack_start(contriblnkbtn, False, False)
else:
vbox.pack_start(gtk.Label("http://www.jokosher.org/contribute"), False, False)
self.contribvbox.pack_start(vbox, False, False)
self.contribdialog.show_all()
#_____________________________________________________________________
def OnContributingLinkButtonClicked(self, widget):
"""
Opens the Jokosher contributing website in the user's default web browser.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
Utils.OpenExternalURL(url="http://www.jokosher.org/contribute",
message=_("Couldn't launch the contributing website automatically.\n\nPlease visit %s to access it."), parent=self.window)
#_____________________________________________________________________
def GetDistroVersion(self):
"""
Obtain a string with the distribution name and version.
Returns:
A string with the distribution name and version.
"""
versionStr = ""
try:
#distro name
output = Popen(args=["lsb_release", "-i"], stdout=PIPE).stdout.read()
versionStr += output[output.find("\t")+1:len(output)-1]
#distro version
output = Popen(args=["lsb_release", "-r"], stdout=PIPE).stdout.read()
versionStr += " " + output[output.find("\t")+1:len(output)-1]
except OSError:
versionStr = None
return versionStr
#_____________________________________________________________________
def OnSystemInformation(self, widget):
"""
Displays a small window with the system information.
Parameters:
widget -- Gtk callback parameter.
"""
self.sysInfoTree = gtk.glade.XML(Globals.GLADE_PATH, "SystemInformationDialog")
# grab references to the SystemInformationDialog window and vbox
self.sysInfoDialog = self.sysInfoTree.get_widget("SystemInformationDialog")
self.gstVersionStr = self.sysInfoTree.get_widget("labelGStreamerVersion")
self.gnonlinVersionStr = self.sysInfoTree.get_widget("labelGnonlinVersion")
self.distroVersionStr = self.sysInfoTree.get_widget("labelDistributionVersion")
sysInfoCloseButton = self.sysInfoTree.get_widget("closeButton")
#connect the close button
sysInfoCloseButton.connect("clicked", lambda dialog: self.sysInfoDialog.destroy())
#set the version strings to the appropriate value
gstVersion = "%s.%s.%s.%s" % gst.version()
self.gstVersionStr.set_text(gstVersion)
gnlVersion = gst.registry_get_default().find_plugin("gnonlin")
if gnlVersion:
ignored, gnlMajor, gnlMinor = gnlVersion.get_version().split(".", 2)
message = "%s.%s" % (gnlMajor, gnlMinor)
elif not gnlVersion:
message += _("Gnonlin is missing!")
self.gnonlinVersionStr.set_text(message)
distroVersion = self.GetDistroVersion()
if distroVersion is not None:
self.distroVersionStr.set_text(distroVersion)
else:
self.distroVersionStr.set_text(_("Unknown"))
#_____________________________________________________________________
def ShowOpenProjectErrorDialog(self, error, parent=None):
"""
Creates and shows a dialog to inform the user about an error that has ocurred.
Parameters:
error -- string with the error(s) description.
parent -- parent window of the error message dialog.
"""
if not parent:
parent = self.window
if error.errno == 1:
message = _("The URI scheme '%s' is either invalid or not supported.") % error.info
elif error.errno == 2:
message = _("Unable to unzip the project file %s") % error.info
elif error.errno == 3:
message = _("The project file was created with version \"%s\" of Jokosher.\n") % error.info + \
_("Projects from version \"%s\" are incompatible with this release.\n") % error.info
elif error.errno == 4:
message = _("The project:\n%s\n\ndoes not exist.\n") % error.info
elif error.errno == 5:
first = _("The project file could not be opened.\n")
second = _("It is recommended that you report this to the Jokosher developers or get help at http://www.jokosher.org/forums/")
message = "%s\n%s\n\n%s" % (first, second, error.info)
else:
message = _("The project file could not be opened.\n")
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()
#_____________________________________________________________________
def OnExtensionManagerDialog(self, widget):
"""
Creates and shows the "Extension Manager" dialog.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
ExtensionManagerDialog.ExtensionManagerDialog(self)
#_____________________________________________________________________
def OnAddAudio(self, widget):
"""
Adds an audio file to the selected Instrument.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
instrID = None
for instr in self.project.instruments:
if instr.isSelected:
instrID = instr.id
break
if instrID != None:
for id, instrViewer in self.workspace.recordingView.views:
if instrID == id:
instrViewer.eventLane.CreateEventFromFile()
#_____________________________________________________________________
def OnRemoveInstrument(self, widget):
"""
Removes all selected Instruments from the Project.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
# list to store instruments to delete, so we don't modify the list while we are iterating
instrList = []
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
instrList.append(instr)
if instrList:
self.project.DeleteInstrumentsOrEvents(instrList)
#_____________________________________________________________________
def ShowImportFileChooser(self):
"""
Creates a file chooser dialog and gets the filename to be imported,
as well as if the file should be copied to the project folder or not.
Returns:
A 2-tuple containing the a list of file paths to be imported and a boolean
that will be true if the user requested the file to be copied to the project folder.
Both entries in the tuple will be None is the dialog was cancelled.
"""
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)
copyfile = gtk.CheckButton(_("Copy file to project"))
# Make it copy files to audio dir by default
copyfile.set_active(True)
copyfile.show()
dlg = gtk.FileChooserDialog(_("Add Audio File..."), action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=buttons)
dlg.set_default_response(gtk.RESPONSE_OK)
dlg.set_icon(self.icon)
if os.path.exists(Globals.settings.general["projectfolder"]):
dlg.set_current_folder(Globals.settings.general["projectfolder"])
else:
dlg.set_current_folder(os.path.expanduser("~"))
dlg.set_extra_widget(copyfile)
dlg.set_select_multiple(True)
vbox = gtk.VBox()
audiopreview = AudioPreview.AudioPreview()
vbox.pack_start(audiopreview, True, False)
vbox.show_all()
dlg.set_preview_widget(vbox)
dlg.set_use_preview_label(False)
dlg.connect("selection-changed", audiopreview.OnSelection)
response = dlg.run()
if response == gtk.RESPONSE_OK:
#stop the preview audio from playing without destorying the dialog
audiopreview.OnDestroy()
dlg.hide()
Globals.settings.general["projectfolder"] = os.path.dirname(dlg.get_filename())
Globals.settings.write()
filenames = dlg.get_filenames()
copyfileBool = copyfile.get_active()
dlg.destroy()
return (filenames, copyfileBool)
dlg.destroy()
return (None, None)
#_____________________________________________________________________
def OnAddAudioFile(self, widget=None):
"""
Called when the "Add Audio File Instrument" in the project menu is clicked.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
filenames, copyfile = self.ShowImportFileChooser()
#check if None in case the user click cancel on the dialog.
if filenames:
self.project.AddInstrumentAndEvents(filenames, copyfile)
#_____________________________________________________________________
def PopulateMixdownAsMenu(self):
"""
If there are any saved mixdown profiles, create a Mixdown As submenu in
the file menu and add links to them.
"""
self.mixdown_as_header = None
savefolder = os.path.join(Globals.JOKOSHER_DATA_HOME, 'mixdownprofiles') # created by Globals
profiles = os.listdir(savefolder)
if not profiles: return
# remove any old mixdown profiles
for item in profiles:
if not item.endswith(".profile"):
path = os.path.join(Globals.MIXDOWN_PROFILES_PATH, item)
os.remove(path)
break
profiles = os.listdir(savefolder)
filemenulist = self.filemenu.get_submenu()
# If there's already a Mixdown As submenu, delete it and recreate it
for i in filemenulist.get_children():
if i.get_children():
if i.get_children()[0].get_label() == _("Mix_down As"):
filemenulist.remove(i)
i.destroy()
# Create a Mixdown As submenu header
self.mixdown_as_header = gtk.MenuItem(label=_("Mix_down As"))
submenu = gtk.Menu()
for p in profiles:
profilenames = p.split(".")[0]
menuitem = gtk.MenuItem(label=profilenames)
menuitem.connect("activate", self.OnExport, profilenames)
submenu.append(menuitem)
self.mixdown_as_header.set_submenu(submenu)
# insert it after Mixdown Project
counter = 0
insert_position = None
for i in filemenulist.get_children():
if i.get_children():
if i.get_children()[0].get_label() == _("_Mixdown Project..."):
insert_position = counter
counter += 1
if insert_position:
self.filemenu.get_submenu().insert(self.mixdown_as_header,insert_position + 1)
self.filemenu.show_all()
#_____________________________________________________________________
def OnProjectProperties(self, widget=None):
"""
Called when the "Properties..." in the project menu is clicked.
Parameters:
widget -- reserved for GTK callbacks, don't use it explicitly.
"""
if not self.project:
return
propertiesTree = gtk.glade.XML(Globals.GLADE_PATH, "ProjectPropertiesDialog")
dlg = propertiesTree.get_widget("ProjectPropertiesDialog")
nameEntry = propertiesTree.get_widget("nameEntry")
authorEntry = propertiesTree.get_widget("authorEntry")
notesTextView = propertiesTree.get_widget("notesTextView")
nameEntry.set_text(self.project.name)
authorEntry.set_text(self.project.author)
buffer = gtk.TextBuffer()
buffer.set_text(self.project.notes)
notesTextView.set_buffer(buffer)
dlg.connect("response", self.OnProjectPropertiesClose, nameEntry, authorEntry, notesTextView)
dlg.show_all()
#_____________________________________________________________________
def OnProjectPropertiesClose(self, dialog, response, nameEntry, authorEntry, notesTextView):
"""
Called when the "Project Properties" windows is closed.
Parameters:
dialog -- reserved for GTK callbacks, don't use it explicitly.
"""
if self.project and response == gtk.RESPONSE_CLOSE:
name = nameEntry.get_text()
author = authorEntry.get_text()
buffer = notesTextView.get_buffer()
notes = buffer.get_text(*buffer.get_bounds())
self.project.SetName(name)
self.project.SetAuthor(author)
self.project.SetNotes(notes)
dialog.destroy()
#_____________________________________________________________________
#=========================================================================
def main():
"""
Main entry point for Jokosher.
"""
MainApp()
gobject.threads_init()
gtk.main()
if __name__ == "__main__":
main()