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