Misc fixes
[klaudia.git] / src / mainw.py
blob3bc283d22314ff30476d97a95c2f53b72220e4a7
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
5 # Imports
6 import jack
7 from os import path
8 from dbus import UInt32
9 from functools import partial
10 from time import ctime
11 from PyQt4.QtCore import Qt, QFile, QIODevice, QSize, QTextStream, QTimer, SIGNAL, SLOT
12 from PyQt4.QtGui import QDialog, QDialogButtonBox, QTableWidgetItem
13 from PyQt4.QtXml import QDomDocument
14 from PyKDE4.kdecore import i18n, KUrl
15 from PyKDE4.kdeui import KAction, KIcon, KMessageBox, KStandardAction, KXmlGuiWindow
16 from PyKDE4.kio import KFileDialog
17 import shared, renderw, tb_main, tb_apps, tb_connections, tb_mixer, ui_mainw, ui_dia_studios
20 # Main Window
21 class KlaudiaMainW(KXmlGuiWindow, ui_mainw.Ui_KlaudiaMainW):
22 def __init__(self, *args):
23 KXmlGuiWindow.__init__(self, *args)
24 self.setupUi(self)
26 # Load a project if an argument is given
27 if (shared.OpenFolder):
28 url = shared.OpenFolder
29 if (path.exists(url+"/project.klaudia")):
30 shared.p_folder = url
31 else:
32 KMessageBox.sorry(self, i18n("The Klaudia project cannot be loaded!"), i18n("Sorry"))
33 jack.detach()
34 exit(-1)
36 if (not shared.TESTING):
37 # Read project file
38 xml = QDomDocument()
39 filename = QFile(shared.p_folder+"/project.klaudia")
40 if not filename.open(QIODevice.ReadOnly):
41 print "error here, 1"
42 if not xml.setContent(filename):
43 print "error here, 2"
44 filename.close()
46 # Check if project file is not corrupted
47 content = xml.documentElement()
48 if (content.tagName() != "KLAUDIA"):
49 print "error here, 3"
51 # Get values from XML - the big code
52 node = content.firstChild()
53 while not node.isNull():
54 if (node.toElement().tagName() == "Properties"):
55 properties = node.toElement().firstChild()
56 while not properties.isNull():
57 name = properties.toElement().tagName()
58 text = properties.toElement().text()
59 if (name == "Name"): shared.p_name = text
60 elif (name == "Author"): shared.p_author = text
61 elif (name == "Composer"): shared.p_composer = text
62 elif (name == "Album"): shared.p_album = text
63 elif (name == "Track"): shared.p_track = text
64 elif (name == "BPM"): shared.p_bpm = text
65 elif (name == "Time-Start"): shared.p_time_start = text
66 elif (name == "Time-End"): shared.p_time_end = text
67 properties = properties.nextSibling()
68 node = node.nextSibling()
69 if (node.toElement().tagName() == "Ladish"):
70 properties = node.toElement().firstChild()
71 while not properties.isNull():
72 name = properties.toElement().tagName()
73 text = properties.toElement().text()
74 if (name == "Project-Name"): shared.ladish_project_name = text
75 properties = properties.nextSibling()
76 node = node.nextSibling()
78 if (not shared.TESTING):
79 if (str(shared.ladish_project_name) != str(shared.studioBus.GetName())):
80 print str(shared.ladish_project_name)
81 print str(shared.studioBus.GetName())
82 shared.controlBus.LoadStudio(str(shared.ladish_project_name))
83 shared.jackBus.StartServer()
84 if (not bool(shared.jackBus.IsStarted())):
85 shared.jackBus.StartServer()
86 jack.attach("Klaudia")
88 # Set BPM
89 self.spin_bpm.setValue(float(shared.p_bpm))
91 # Internal timers
92 self.jackTimer = QTimer()
93 self.cpuTimer = QTimer()
94 self.jackTimer.start(250)
95 self.cpuTimer.start(2000)
97 # Set-up actions
98 self.act_render = KAction(KIcon("media-record"), i18n("&Render..."), self)
99 self.act_export_zip = KAction(i18n("&Export Project to Zip file..."), self)
100 self.act_playpause = KAction(KIcon("media-playback-start"), i18n("&Play"), self)
101 self.act_playpause.setCheckable(True)
102 self.act_stop = KAction(KIcon("media-playback-stop"), i18n("&Stop"), self)
103 self.act_backwards = KAction(KIcon("media-seek-backward"), i18n("&Backwards"), self)
104 self.act_forwards = KAction(KIcon("media-seek-forward"), i18n("&Forwards"), self)
105 self.act_bf_16 = KAction(i18n("16"), self)
106 self.act_bf_32 = KAction(i18n("32"), self)
107 self.act_bf_64 = KAction(i18n("64"), self)
108 self.act_bf_128 = KAction(i18n("128"), self)
109 self.act_bf_256 = KAction(i18n("256"), self)
110 self.act_bf_512 = KAction(i18n("512"), self)
111 self.act_bf_1024 = KAction(i18n("1024"), self)
112 self.act_bf_2048 = KAction(i18n("2048"), self)
113 self.act_bf_4096 = KAction(i18n("4096"), self)
114 self.act_import_ladish = KAction(i18n("Import &Ladish Project..."), self)
115 self.act_show_fake_toolbar = KAction(i18n("Show &Toolbar"), self)
116 self.act_show_fake_toolbar.setCheckable(True)
117 self.act_show_fake_statusbar = KAction(i18n("Show St&atusbar"), self)
118 self.act_show_fake_statusbar.setCheckable(True)
120 self.act_export_zip.setEnabled(False)
122 self.act_bf_list = ( self.act_bf_16, self.act_bf_32, self.act_bf_64, self.act_bf_128, self.act_bf_256, self.act_bf_512,
123 self.act_bf_1024, self.act_bf_2048, self.act_bf_4096 )
125 size = str(shared.jackBus.GetBufferSize())
126 for i in range(len(self.act_bf_list)):
127 self.act_bf_list[i].setCheckable(True)
128 if (self.act_bf_list[i].text().replace("&","") == size):
129 self.act_bf_list[i].setChecked(True)
131 setting_tbV = True
132 self.act_show_fake_toolbar.setChecked(setting_tbV)
133 self.toolbarW.setVisible(setting_tbV)
135 setting_sbV = True
136 self.act_show_fake_statusbar.setChecked(setting_sbV)
137 self.statusbarW.setVisible(setting_sbV)
139 # Standard actions
140 KStandardAction.open(self.NIY, self.actionCollection())
141 KStandardAction.openRecent(self.NIY, self.actionCollection())
142 KStandardAction.save(self.saveProject, self.actionCollection())
143 KStandardAction.saveAs(self.saveAs, self.actionCollection())
144 KStandardAction.close(self.close, self.actionCollection())
145 KStandardAction.preferences(self.NIY, self.actionCollection())
147 # Add actions
148 self.actionCollection().addAction("render", self.act_render)
149 self.actionCollection().addAction("export_zip", self.act_export_zip)
150 self.actionCollection().addAction("tr_play", self.act_playpause)
151 self.actionCollection().addAction("tr_stop", self.act_stop)
152 self.actionCollection().addAction("tr_back", self.act_backwards)
153 self.actionCollection().addAction("tr_next", self.act_forwards)
154 self.actionCollection().addAction("bf_16", self.act_bf_16)
155 self.actionCollection().addAction("bf_32", self.act_bf_32)
156 self.actionCollection().addAction("bf_64", self.act_bf_64)
157 self.actionCollection().addAction("bf_128", self.act_bf_128)
158 self.actionCollection().addAction("bf_256", self.act_bf_256)
159 self.actionCollection().addAction("bf_512", self.act_bf_512)
160 self.actionCollection().addAction("bf_1024", self.act_bf_1024)
161 self.actionCollection().addAction("bf_2048", self.act_bf_2048)
162 self.actionCollection().addAction("bf_4096", self.act_bf_4096)
163 self.actionCollection().addAction("import_ladish", self.act_import_ladish)
164 self.actionCollection().addAction("show_fake_toolbar", self.act_show_fake_toolbar)
165 self.actionCollection().addAction("show_fake_statusbar", self.act_show_fake_statusbar)
167 # Set Button Icons
168 self.b_playpause.setIcon(KIcon("media-playback-start"))
169 self.b_stop.setIcon(KIcon("media-playback-stop"))
170 self.b_backwards.setIcon(KIcon("media-seek-backward"))
171 self.b_forwards.setIcon(KIcon("media-seek-forward"))
172 self.b_open.setIcon(KIcon("document-open"))
173 self.b_save.setIcon(KIcon("document-save"))
174 self.b_save_as.setIcon(KIcon("document-save-as"))
175 self.b_render.setIcon(KIcon("media-record"))
176 self.b_add_new.setIcon(KIcon("list-add"))
178 # Interact actions to buttons
179 self.connect(self.act_playpause, SIGNAL("triggered(bool)"), self.b_playpause, SLOT("setChecked(bool)"))
180 self.connect(self.b_playpause, SIGNAL("clicked(bool)"), self.act_playpause, SLOT("setChecked(bool)"))
182 # Custom toolbar and statusbar
183 self.connect(self.act_show_fake_toolbar, SIGNAL("triggered(bool)"), self.toolbarW, SLOT("setVisible(bool)"))
184 self.connect(self.act_show_fake_statusbar, SIGNAL("triggered(bool)"), self.statusbarW, SLOT("setVisible(bool)"))
186 # Connect actions to functions
187 self.connect(self.act_render, SIGNAL("triggered()"), self.renderW)
188 self.connect(self.act_playpause, SIGNAL("triggered(bool)"), self.jackPlayPause)
189 self.connect(self.act_stop, SIGNAL("triggered()"), self.jackStop)
190 self.connect(self.act_backwards, SIGNAL("triggered()"), self.jackBackwards)
191 self.connect(self.act_forwards, SIGNAL("triggered()"), self.jackForwards)
192 self.connect(self.act_bf_16, SIGNAL("triggered()"), partial(self.setBufferSize, 16))
193 self.connect(self.act_bf_32, SIGNAL("triggered()"), partial(self.setBufferSize, 32))
194 self.connect(self.act_bf_64, SIGNAL("triggered()"), partial(self.setBufferSize, 64))
195 self.connect(self.act_bf_128, SIGNAL("triggered()"), partial(self.setBufferSize, 128))
196 self.connect(self.act_bf_256, SIGNAL("triggered()"), partial(self.setBufferSize, 256))
197 self.connect(self.act_bf_512, SIGNAL("triggered()"), partial(self.setBufferSize, 512))
198 self.connect(self.act_bf_1024, SIGNAL("triggered()"), partial(self.setBufferSize, 1024))
199 self.connect(self.act_bf_2048, SIGNAL("triggered()"), partial(self.setBufferSize, 2048))
200 self.connect(self.act_bf_4096, SIGNAL("triggered()"), partial(self.setBufferSize, 4096))
201 self.connect(self.act_import_ladish, SIGNAL("triggered()"), self.importLadishStudio)
203 # Connect tab-buttons to pages
204 self.connect(self.tb_main, SIGNAL("clicked()"), partial(self.changeTabButton, "main"))
205 self.connect(self.tb_apps, SIGNAL("clicked()"), partial(self.changeTabButton, "apps"))
206 self.connect(self.tb_connections, SIGNAL("clicked()"), partial(self.changeTabButton, "connections"))
207 self.connect(self.tb_mixer, SIGNAL("clicked()"), partial(self.changeTabButton, "mixer"))
209 # Connect buttons to functions
210 self.connect(self.b_playpause, SIGNAL("clicked(bool)"), self.jackPlayPause)
211 self.connect(self.b_stop, SIGNAL("clicked()"), self.jackStop)
212 self.connect(self.b_backwards, SIGNAL("clicked()"), self.jackBackwards)
213 self.connect(self.b_forwards, SIGNAL("clicked()"), self.jackForwards)
214 self.connect(self.b_open, SIGNAL("clicked()"), self.NIY)
215 self.connect(self.b_save, SIGNAL("clicked()"), self.saveProject)
216 self.connect(self.b_save_as, SIGNAL("clicked()"), self.saveAs)
217 self.connect(self.b_render, SIGNAL("clicked()"), self.renderW)
218 self.connect(self.b_add_new, SIGNAL("clicked()"), self.addCustom)
220 # Connect other stuff
221 self.connect(self.cpuTimer, SIGNAL("timeout()"), self.updateDSPLoad)
222 self.connect(self.cpuTimer, SIGNAL("timeout()"), self.updateJackVars)
223 self.connect(self.jackTimer, SIGNAL("timeout()"), self.updateTransport)
224 self.connect(self.jackTimer, SIGNAL("timeout()"), self.updateXruns) # FIXME - get xrun callback instead!
225 self.connect(self.transportSlider, SIGNAL("sliderReleased()"), self.updateSlider)
227 shared.loopBus.add_signal_receiver(self.updateJackDBus, sender_keyword='sender', destination_keyword='dest', interface_keyword='interface',
228 member_keyword='member', path_keyword='path')
231 # Set-up the GUI (Part 1, create the KDE stuff)
232 self.setupGUI(QSize(600, 400), KXmlGuiWindow.Default, shared.Path+"/klaudia.rc")
234 # Set-up the GUI (Part 2, set custom menu, tool-bar and status-bar)
235 self.menuLayout.addWidget(self.menuBar())
236 self.toolBar().deleteLater()
237 self.statusBar().setVisible(False)
238 self.actionCollection().action(u'options_configure_toolbars').setVisible(False)
239 self.actionCollection().action(u'options_show_statusbar').setVisible(False)
240 self.connect(self.statusBar(), SIGNAL("messageChanged(QString)"), self.sbW_msg_label, SLOT("setText(QString)"))
242 # Set-up the GUI (Part 3, Load pages)
243 self.w_main = tb_main.MainW()
244 self.w_apps = tb_apps.AppsW()
245 self.w_connections = tb_connections.ConnectionsW()
246 self.w_mixer = tb_mixer.MixerW()
247 self.w_main.setVisible(False)
248 self.w_apps.setVisible(False)
249 #self.w_connections.setVisible(False)
250 self.w_mixer.setVisible(False)
252 # Set-up the GUI (Part 4, set default page)
253 #self.w_main.setVisible(True)
254 self.centralW.layout().addWidget(self.w_connections)
255 self.tb_connections.setChecked(True)
256 self.currentTabPage = "connections"
258 # Set-up the GUI (Part 5, update variables)
259 self.updateJackVars()
260 self.updateDSPLoad()
261 self.updateXruns()
262 self.updateTransport()
265 def NIY(self):
266 KMessageBox.sorry(self, i18n("NOT IMPLEMENTED YET"), i18n("Sorry"))
268 def addCustom(self):
269 tb_main.NewAppW().exec_()
271 def saveProject(self):
272 url = shared.p_folder
273 filename = QFile(url+"/project.klaudia")
274 if not filename.open(QIODevice.WriteOnly):
275 KMessageBox.error(self, i18n("Cannot write to selected directory - Permission Denied"), i18n("Error"))
276 jack.detach()
277 exit(-2)
278 #raise IOError, unicode(filename.errorString())
279 stream = QTextStream(filename)
280 stream.setCodec("UTF-8")
281 stream << ("<?xml version='1.0' encoding='UTF-8'?>\n"
282 "<!DOCTYPE KLAUDIA>\n"
283 "<KLAUDIA VERSION='0.1'>\n"
284 "<Properties>\n"
285 " <Name>%s</Name>\n"
286 " <Author>%s</Author>\n"
287 " <Composer>%s</Composer>\n"
288 " <Album>%s</Album>\n"
289 " <Track>%s</Track>\n"
290 " <BPM>%s</BPM>\n"
291 " <Time-Start>%s</Time-Start>\n"
292 " <Time-End>%s</Time-End>\n"
293 "</Properties>\n"
294 "<Ladish>\n"
295 " <Project-Name>%s</Project-Name>\n"
296 "</Ladish>\n"
297 "</KLAUDIA>\n" % (shared.p_name, shared.p_author, shared.p_composer,
298 shared.p_album, shared.p_track, shared.p_bpm, shared.p_time_start, shared.p_time_end, shared.ladish_project_name))
299 iconfile = open(url+"/.directory", "w")
300 iconfile.write("[Desktop Entry]\nIcon=%s\n" % shared.p_icon)
301 iconfile.close()
302 shared.studioBus.Save()
304 def saveAs(self):
305 pUrl = KFileDialog.getExistingDirectoryUrl(KUrl(shared.p_folder), self, i18n("Open Klaudia Project Folder"))
306 if not pUrl.fileName():
307 return
308 elif not pUrl.isLocalFile():
309 KMessageBox.error(self, i18n("The Selected folder cannot be used for a Klaudia project!"), i18n("Error"))
310 return
311 shared.p_folder = str(pUrl.toLocalFile())
312 self.saveProject()
314 def jackPlayPause(self, play):
315 if (play):
316 jack.transport_start()
317 self.act_playpause.setText("&Pause")
318 self.act_playpause.setIcon(KIcon("media-playback-pause"))
319 self.b_playpause.setIcon(KIcon("media-playback-pause"))
320 else:
321 jack.transport_stop()
322 self.act_playpause.setText("&Play")
323 self.act_playpause.setIcon(KIcon("media-playback-start"))
324 self.b_playpause.setIcon(KIcon("media-playback-start"))
326 def jackStop(self):
327 self.jackPlayPause(False)
328 self.b_playpause.setChecked(False)
329 jack.transport_locate(0)
330 #jack.transport_locate(shared.Globals.sliderMinValue*jack.get_sample_rate())
332 def jackBackwards(self):
333 pos = int(jack.get_current_transport_frame()) - 100000
334 if pos < 0:
335 jack.transport_locate(0)
336 else:
337 jack.transport_locate(pos)
339 def jackForwards(self):
340 jack.transport_locate(jack.get_current_transport_frame()+100000)
342 def setBufferSize(self, size):
343 shared.jackBus.SetBufferSize(UInt32(size))
344 for i in range(len(self.act_bf_list)):
345 if (self.act_bf_list[i].text().replace("&","") == str(size)):
346 self.act_bf_list[i].setChecked(True)
347 else:
348 self.act_bf_list[i].setChecked(False)
350 def changeTabButton(self, button):
351 if (button != self.currentTabPage):
352 self.currentTabPage = button
353 # Remove central widget
354 self.centralW.layout().takeAt(0)
355 # Restore all button states
356 self.tb_main.setChecked(False)
357 self.tb_apps.setChecked(False)
358 self.tb_connections.setChecked(False)
359 self.tb_mixer.setChecked(False)
360 # Hide all pages (we only need one)
361 self.w_main.setVisible(False)
362 self.w_apps.setVisible(False)
363 self.w_connections.setVisible(False)
364 self.w_mixer.setVisible(False)
365 if (button == "main"):
366 self.tb_main.setChecked(True)
367 wToLoad = self.w_main
368 elif (button == "apps"):
369 self.tb_apps.setChecked(True)
370 wToLoad = self.w_apps
371 elif (button == "connections"):
372 self.tb_connections.setChecked(True)
373 wToLoad = self.w_connections
374 elif (button == "mixer"):
375 self.tb_mixer.setChecked(True)
376 wToLoad = self.w_mixer
377 self.centralW.layout().addWidget(wToLoad)
378 wToLoad.setVisible(True)
379 else:
380 if (button == "main"): self.tb_main.setChecked(True)
381 elif (button == "apps"): self.tb_apps.setChecked(True)
382 elif (button == "connections"): self.tb_connections.setChecked(True)
383 elif (button == "mixer"): self.tb_mixer.setChecked(True)
386 def updateTransport(self):
387 state = jack.get_transport_state()
388 frame = jack.get_current_transport_frame()
389 rate = int(shared.jackBus.GetSampleRate())
391 # Jack State
392 if (state == 0):
393 self.b_playpause.setChecked(False)
394 self.act_playpause.setText("&Play")
395 self.act_playpause.setIcon(KIcon("media-playback-start"))
396 self.b_playpause.setIcon(KIcon("media-playback-start"))
397 elif (state == 1 or state == 2):
398 self.b_playpause.setChecked(True)
399 self.act_playpause.setText("&Pause")
400 self.act_playpause.setIcon(KIcon("media-playback-pause"))
401 self.b_playpause.setIcon(KIcon("media-playback-pause"))
403 # Current Time
404 time = frame / rate
405 secs = time % 60
406 mins = (time / 60) % 60
407 hrs = (time / 3600) % 60
408 secH = minH = hrsH = ""
409 if secs < 10: secH = "0"
410 if mins < 10: minH = "0"
411 if hrs < 10: hrsH = "0"
412 self.l_time.setText(hrsH+str(hrs)+":"+minH+str(mins)+":"+secH+str(secs))
414 # Transport Slider
415 if not self.transportSlider.isSliderDown():
416 self.transportSlider.setMinimum(shared.Globals.sliderMinValue)
417 self.transportSlider.setMaximum(shared.Globals.sliderMaxValue)
418 self.transportSlider.setValue(time)
420 def updateXruns(self):
421 xruns = str(shared.jackBus.GetXruns())
422 self.sbW_drops_count.setText(xruns)
424 def updateDSPLoad(self):
425 load = int(shared.jackBus.GetLoad())
426 self.sbW_cpu_bar.setValue(load)
428 def updateSlider(self):
429 value = self.transportSlider.value()
430 jack.transport_locate(value*int(shared.jackBus.GetSampleRate()))
432 def updateJackVars(self):
433 self.sbW_jack_SampleRate.setText(str(shared.jackBus.GetSampleRate())+" Hz")
434 self.sbW_jack_BufferSize.setText(str(shared.jackBus.GetBufferSize()))
435 if (bool(shared.jackBus.IsRealtime())):
436 self.sbW_jack_RT.setText("RT")
437 else:
438 self.sbW_jack_RT.setText("")
440 def updateSizes(self):
441 self.sbW_msg.setMinimumSize(self.width()/4, 16)
442 self.sbW_msg.setMaximumSize(self.width()/4, 16)
443 self.sbW_jack.setMaximumSize(self.width()/3, 16)
444 self.sbW_cpu.setMaximumSize(self.width()/4, 16)
445 self.sbW_drops.setMaximumSize(self.width()/8, 16)
447 def resizeEvent(self, event):
448 self.updateSizes()
450 def renderW(self):
451 renderw.KlaudiaRenderW().exec_()
453 def importLadishStudio(self):
454 StudiosW().exec_()
456 def updateJackDBus(self, *args, **kwds):
457 if (kwds['interface'] == "org.jackaudio.JackControl"):
458 self.updateJackVars()
460 class StudiosW(QDialog, ui_dia_studios.Ui_StudiosW):
461 def __init__(self, *args):
462 QDialog.__init__(self, *args)
463 self.setupUi(self)
465 self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)
466 self.listStudio.setColumnWidth(0, 200)
468 self.connect(self, SIGNAL("accepted()"), self.Done)
469 self.connect(self.listStudio, SIGNAL("currentCellChanged(int, int, int, int)"), self.checkOkButton)
471 list_of_ladish_studios = shared.controlBus.GetStudioList()
472 for i in range(len(list_of_ladish_studios)):
473 name = QTableWidgetItem(str(list_of_ladish_studios[i][0]))
474 time = QTableWidgetItem(ctime(float(list_of_ladish_studios[i][1][u'Modification Time'])))
475 self.listStudio.insertRow(i)
476 self.listStudio.setItem(i, 0, name)
477 self.listStudio.setItem(i, 1, time)
479 self.listStudio.sortByColumn(0, Qt.AscendingOrder)
481 def Done(self):
482 jack.detach()
483 shared.controlBus.LoadStudio(str(self.listStudio.item(self.listStudio.currentRow(), 0).text()))
484 shared.jackBus.StartServer()
485 jack.attach("Klaudia")
487 def checkOkButton(self, row, b, c, d):
488 if (row < 0):
489 self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)
490 else:
491 self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True)