Collection of tools useful for audio production
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

579 lines
21KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Common/Shared code related to Canvas and JACK
  4. # Copyright (C) 2010-2013 Filipe Coelho <falktx@falktx.com>
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # For a full copy of the GNU General Public License see the COPYING file
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (Global)
  19. from PyQt4.QtCore import pyqtSlot, QTimer
  20. from PyQt4.QtGui import QCursor, QFontMetrics, QImage, QMainWindow, QMenu, QPainter, QPrinter, QPrintDialog
  21. # ------------------------------------------------------------------------------------------------------------
  22. # Imports (Custom Stuff)
  23. import patchcanvas
  24. import jacksettings
  25. import logs
  26. import render
  27. from shared import *
  28. from jacklib_helpers import *
  29. # ------------------------------------------------------------------------------------------------------------
  30. # Have JACK2 ?
  31. if DEBUG and jacklib and jacklib.JACK2:
  32. print("Using JACK2, version %s" % cString(jacklib.get_version_string()))
  33. # ------------------------------------------------------------------------------------------------------------
  34. # Static Variables
  35. TRANSPORT_VIEW_HMS = 0
  36. TRANSPORT_VIEW_BBT = 1
  37. TRANSPORT_VIEW_FRAMES = 2
  38. BUFFER_SIZE_LIST = (16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192)
  39. SAMPLE_RATE_LIST = (22050, 32000, 44100, 48000, 88200, 96000, 192000)
  40. # ------------------------------------------------------------------------------------------------------------
  41. # Global DBus object
  42. class DBusObject(object):
  43. __slots__ = [
  44. 'loop',
  45. 'bus',
  46. 'a2j',
  47. 'jack',
  48. 'ladish_control',
  49. 'ladish_studio',
  50. 'ladish_room',
  51. 'ladish_graph',
  52. 'ladish_manager',
  53. 'ladish_app_iface',
  54. 'ladish_app_daemon',
  55. 'patchbay'
  56. ]
  57. gDBus = DBusObject()
  58. gDBus.loop = None
  59. gDBus.bus = None
  60. gDBus.a2j = None
  61. gDBus.jack = None
  62. gDBus.ladish_control = None
  63. gDBus.ladish_studio = None
  64. gDBus.ladish_room = None
  65. gDBus.ladish_graph = None
  66. gDBus.ladish_app_iface = None
  67. gDBus.ladish_app_daemon = None
  68. gDBus.patchbay = None
  69. # ------------------------------------------------------------------------------------------------------------
  70. # Global JACK object
  71. class JackObject(object):
  72. __slots__ = [
  73. 'client'
  74. ]
  75. gJack = JackObject()
  76. gJack.client = None
  77. # ------------------------------------------------------------------------------------------------------------
  78. # Abstract Canvas and JACK Class
  79. class AbstractCanvasJackClass(QMainWindow):
  80. def __init__(self, appName, UiClass, parent):
  81. QMainWindow.__init__(self, parent)
  82. self.ui = UiClass()
  83. self.ui.setupUi(self)
  84. self.fAppName = appName
  85. self.fCurTransportView = TRANSPORT_VIEW_HMS
  86. self.fLastBPM = None
  87. self.fLastTransportState = None
  88. self.fXruns = -1
  89. self.fBufferSize = 0
  90. self.fSampleRate = 0.0
  91. self.fNextSampleRate = 0.0
  92. self.fLogsW = None
  93. self.scene = None
  94. # -----------------------------------------------------------------
  95. # Abstract calls
  96. def initPorts(self):
  97. pass
  98. def jackStopped(self):
  99. pass
  100. # -----------------------------------------------------------------
  101. # JACK Property change calls
  102. def jack_setBufferSize(self, bufferSize):
  103. if self.fBufferSize == bufferSize:
  104. return
  105. if gJack.client:
  106. failed = bool(jacklib.set_buffer_size(gJack.client, bufferSize) != 0)
  107. else:
  108. failed = bool(jacksettings.setBufferSize(bufferSize))
  109. if failed:
  110. print("Failed to change buffer-size as %i, reset to %i" % (bufferSize, self.fBufferSize))
  111. self.ui_setBufferSize(self.fBufferSize, True)
  112. def jack_setSampleRate(self, sampleRate):
  113. if gJack.client:
  114. # Show change-in-future dialog
  115. self.ui_setSampleRate(sampleRate, True)
  116. else:
  117. # Try to set sampleRate via dbus now
  118. if jacksettings.setSampleRate(sampleRate):
  119. self.ui_setSampleRate(sampleRate)
  120. @pyqtSlot(bool)
  121. def slot_jackBufferSize_Menu(self, clicked):
  122. sender = self.sender()
  123. # ignore non-clicks
  124. if not clicked:
  125. sender.blockSignals(True)
  126. sender.setChecked(True)
  127. sender.blockSignals(False)
  128. return
  129. text = sender.text()
  130. if text and text.isdigit():
  131. self.jack_setBufferSize(int(text))
  132. @pyqtSlot(str)
  133. def slot_jackBufferSize_ComboBox(self, text):
  134. if text and text.isdigit():
  135. self.jack_setBufferSize(int(text))
  136. @pyqtSlot(str)
  137. def slot_jackSampleRate_ComboBox(self, text):
  138. if text and text.isdigit():
  139. self.jack_setSampleRate(int(text))
  140. # -----------------------------------------------------------------
  141. # JACK Transport calls
  142. def setTransportView(self, view):
  143. if view == TRANSPORT_VIEW_HMS:
  144. self.fCurTransportView = TRANSPORT_VIEW_HMS
  145. self.ui.label_time.setMinimumWidth(QFontMetrics(self.ui.label_time.font()).width("00:00:00") + 3)
  146. elif view == TRANSPORT_VIEW_BBT:
  147. self.fCurTransportView = TRANSPORT_VIEW_BBT
  148. self.ui.label_time.setMinimumWidth(QFontMetrics(self.ui.label_time.font()).width("000|00|0000") + 3)
  149. elif view == TRANSPORT_VIEW_FRAMES:
  150. self.fCurTransportView = TRANSPORT_VIEW_FRAMES
  151. self.ui.label_time.setMinimumWidth(QFontMetrics(self.ui.label_time.font()).width("000'000'000") + 3)
  152. else:
  153. self.setTransportView(TRANSPORT_VIEW_HMS)
  154. @pyqtSlot(bool)
  155. def slot_transportPlayPause(self, play):
  156. if not gJack.client:
  157. return
  158. if play:
  159. jacklib.transport_start(gJack.client)
  160. else:
  161. jacklib.transport_stop(gJack.client)
  162. self.refreshTransport()
  163. @pyqtSlot()
  164. def slot_transportStop(self):
  165. if not gJack.client:
  166. return
  167. jacklib.transport_stop(gJack.client)
  168. jacklib.transport_locate(gJack.client, 0)
  169. self.refreshTransport()
  170. @pyqtSlot()
  171. def slot_transportBackwards(self):
  172. if self.fSampleRate == 0.0 or not gJack.client:
  173. return
  174. curFrame = jacklib.get_current_transport_frame(gJack.client)
  175. if curFrame == 0:
  176. return
  177. newFrame = curFrame - int(self.fSampleRate*2.5)
  178. if newFrame < 0:
  179. newFrame = 0
  180. jacklib.transport_locate(gJack.client, newFrame)
  181. @pyqtSlot()
  182. def slot_transportForwards(self):
  183. if self.fSampleRate == 0.0 or not gJack.client:
  184. return
  185. newFrame = jacklib.get_current_transport_frame(gJack.client) + int(self.fSampleRate*2.5)
  186. jacklib.transport_locate(gJack.client, newFrame)
  187. @pyqtSlot()
  188. def slot_transportViewMenu(self):
  189. menu = QMenu(self)
  190. actHMS = menu.addAction("Hours:Minutes:Seconds")
  191. actBBT = menu.addAction("Beat:Bar:Tick")
  192. actFrames = menu.addAction("Frames")
  193. actHMS.setCheckable(True)
  194. actBBT.setCheckable(True)
  195. actFrames.setCheckable(True)
  196. if self.fCurTransportView == TRANSPORT_VIEW_HMS:
  197. actHMS.setChecked(True)
  198. elif self.fCurTransportView == TRANSPORT_VIEW_BBT:
  199. actBBT.setChecked(True)
  200. elif self.fCurTransportView == TRANSPORT_VIEW_FRAMES:
  201. actFrames.setChecked(True)
  202. actSelected = menu.exec_(QCursor().pos())
  203. if actSelected == actHMS:
  204. self.setTransportView(TRANSPORT_VIEW_HMS)
  205. elif actSelected == actBBT:
  206. self.setTransportView(TRANSPORT_VIEW_BBT)
  207. elif actSelected == actFrames:
  208. self.setTransportView(TRANSPORT_VIEW_FRAMES)
  209. # -----------------------------------------------------------------
  210. # Refresh JACK stuff
  211. def refreshDSPLoad(self):
  212. if not gJack.client:
  213. return
  214. self.ui_setDSPLoad(int(jacklib.cpu_load(gJack.client)))
  215. def refreshTransport(self):
  216. if not gJack.client:
  217. return
  218. pos = jacklib.jack_position_t()
  219. pos.valid = 0
  220. state = jacklib.transport_query(gJack.client, jacklib.pointer(pos))
  221. if self.fCurTransportView == TRANSPORT_VIEW_HMS:
  222. time = pos.frame / int(self.fSampleRate)
  223. secs = time % 60
  224. mins = (time / 60) % 60
  225. hrs = (time / 3600) % 60
  226. self.ui.label_time.setText("%02i:%02i:%02i" % (hrs, mins, secs))
  227. elif self.fCurTransportView == TRANSPORT_VIEW_BBT:
  228. if pos.valid & jacklib.JackPositionBBT:
  229. bar = pos.bar
  230. beat = pos.beat if bar != 0 else 0
  231. tick = pos.tick if bar != 0 else 0
  232. else:
  233. bar = 0
  234. beat = 0
  235. tick = 0
  236. self.ui.label_time.setText("%03i|%02i|%04i" % (bar, beat, tick))
  237. elif self.fCurTransportView == TRANSPORT_VIEW_FRAMES:
  238. frame1 = pos.frame % 1000
  239. frame2 = (pos.frame / 1000) % 1000
  240. frame3 = (pos.frame / 1000000) % 1000
  241. self.ui.label_time.setText("%03i'%03i'%03i" % (frame3, frame2, frame1))
  242. if pos.valid & jacklib.JackPositionBBT:
  243. if self.fLastBPM != pos.beats_per_minute:
  244. self.ui.sb_bpm.setValue(pos.beats_per_minute)
  245. self.ui.sb_bpm.setStyleSheet("")
  246. else:
  247. pos.beats_per_minute = -1.0
  248. if self.fLastBPM != pos.beats_per_minute:
  249. self.ui.sb_bpm.setStyleSheet("QDoubleSpinBox { color: palette(mid); }")
  250. self.fLastBPM = pos.beats_per_minute
  251. if state != self.fLastTransportState:
  252. self.fLastTransportState = state
  253. if state == jacklib.JackTransportStopped:
  254. icon = getIcon("media-playback-start")
  255. self.ui.act_transport_play.setChecked(False)
  256. self.ui.act_transport_play.setText(self.tr("&Play"))
  257. self.ui.b_transport_play.setChecked(False)
  258. else:
  259. icon = getIcon("media-playback-pause")
  260. self.ui.act_transport_play.setChecked(True)
  261. self.ui.act_transport_play.setText(self.tr("&Pause"))
  262. self.ui.b_transport_play.setChecked(True)
  263. self.ui.act_transport_play.setIcon(icon)
  264. self.ui.b_transport_play.setIcon(icon)
  265. # -----------------------------------------------------------------
  266. # Set JACK stuff
  267. def ui_setBufferSize(self, bufferSize, forced=False):
  268. if self.fBufferSize == bufferSize and not forced:
  269. return
  270. self.fBufferSize = bufferSize
  271. if bufferSize:
  272. self.ui.cb_buffer_size.blockSignals(True)
  273. if bufferSize == 16:
  274. self.ui.cb_buffer_size.setCurrentIndex(0)
  275. elif bufferSize == 32:
  276. self.ui.cb_buffer_size.setCurrentIndex(1)
  277. elif bufferSize == 64:
  278. self.ui.cb_buffer_size.setCurrentIndex(2)
  279. elif bufferSize == 128:
  280. self.ui.cb_buffer_size.setCurrentIndex(3)
  281. elif bufferSize == 256:
  282. self.ui.cb_buffer_size.setCurrentIndex(4)
  283. elif bufferSize == 512:
  284. self.ui.cb_buffer_size.setCurrentIndex(5)
  285. elif bufferSize == 1024:
  286. self.ui.cb_buffer_size.setCurrentIndex(6)
  287. elif bufferSize == 2048:
  288. self.ui.cb_buffer_size.setCurrentIndex(7)
  289. elif bufferSize == 4096:
  290. self.ui.cb_buffer_size.setCurrentIndex(8)
  291. elif bufferSize == 8192:
  292. self.ui.cb_buffer_size.setCurrentIndex(9)
  293. else:
  294. self.ui.cb_buffer_size.setCurrentIndex(-1)
  295. self.ui.cb_buffer_size.blockSignals(False)
  296. if self.fAppName == "Catia" and bufferSize:
  297. for actBufSize in self.ui.act_jack_bf_list:
  298. actBufSize.blockSignals(True)
  299. actBufSize.setEnabled(True)
  300. if actBufSize.text().replace("&", "") == str(bufferSize):
  301. actBufSize.setChecked(True)
  302. else:
  303. actBufSize.setChecked(False)
  304. actBufSize.blockSignals(False)
  305. def ui_setSampleRate(self, sampleRate, future=False):
  306. if self.fSampleRate == sampleRate:
  307. return
  308. if future:
  309. ask = QMessageBox.question(self, self.tr("Change Sample Rate"),
  310. self.tr("It's not possible to change Sample Rate while JACK is running.\n"
  311. "Do you want to change as soon as JACK stops?"), QMessageBox.Ok | QMessageBox.Cancel)
  312. if ask == QMessageBox.Ok:
  313. self.fNextSampleRate = sampleRate
  314. else:
  315. self.fNextSampleRate = 0.0
  316. # not future
  317. else:
  318. self.fSampleRate = sampleRate
  319. self.fNextSampleRate = 0.0
  320. for i in range(len(SAMPLE_RATE_LIST)):
  321. sampleRateI = SAMPLE_RATE_LIST[i]
  322. if self.fSampleRate == sampleRateI:
  323. self.ui.cb_sample_rate.blockSignals(True)
  324. self.ui.cb_sample_rate.setCurrentIndex(i)
  325. self.ui.cb_sample_rate.blockSignals(False)
  326. break
  327. def ui_setRealTime(self, isRealtime):
  328. self.ui.label_realtime.setText(" RT " if isRealtime else " <s>RT</s> ")
  329. self.ui.label_realtime.setEnabled(isRealtime)
  330. def ui_setDSPLoad(self, dspLoad):
  331. self.ui.pb_dsp_load.setValue(dspLoad)
  332. def ui_setXruns(self, xruns):
  333. txt1 = str(xruns) if (xruns >= 0) else "--"
  334. txt2 = "" if (xruns == 1) else "s"
  335. self.ui.b_xruns.setText("%s Xrun%s" % (txt1, txt2))
  336. # -----------------------------------------------------------------
  337. # External Dialogs
  338. @pyqtSlot()
  339. def slot_showJackSettings(self):
  340. jacksettingsW = jacksettings.JackSettingsW(self)
  341. jacksettingsW.exec_()
  342. del jacksettingsW
  343. # Force update of gui widgets
  344. if not gJack.client:
  345. self.jackStopped()
  346. @pyqtSlot()
  347. def slot_showLogs(self):
  348. if self.fLogsW is None:
  349. self.fLogsW = logs.LogsW(self)
  350. self.fLogsW.show()
  351. @pyqtSlot()
  352. def slot_showRender(self):
  353. renderW = render.RenderW(self)
  354. renderW.exec_()
  355. del renderW
  356. # -----------------------------------------------------------------
  357. # Shared Canvas code
  358. @pyqtSlot()
  359. def slot_canvasArrange(self):
  360. patchcanvas.arrange()
  361. @pyqtSlot()
  362. def slot_canvasRefresh(self):
  363. patchcanvas.clear()
  364. self.initPorts()
  365. @pyqtSlot()
  366. def slot_canvasZoomFit(self):
  367. self.scene.zoom_fit()
  368. @pyqtSlot()
  369. def slot_canvasZoomIn(self):
  370. self.scene.zoom_in()
  371. @pyqtSlot()
  372. def slot_canvasZoomOut(self):
  373. self.scene.zoom_out()
  374. @pyqtSlot()
  375. def slot_canvasZoomReset(self):
  376. self.scene.zoom_reset()
  377. @pyqtSlot()
  378. def slot_canvasPrint(self):
  379. self.scene.clearSelection()
  380. self.fExportPrinter = QPrinter()
  381. dialog = QPrintDialog(self.fExportPrinter, self)
  382. if dialog.exec_():
  383. painter = QPainter(self.fExportPrinter)
  384. painter.save()
  385. painter.setRenderHint(QPainter.Antialiasing, True)
  386. painter.setRenderHint(QPainter.TextAntialiasing, True)
  387. self.scene.render(painter)
  388. painter.restore()
  389. @pyqtSlot()
  390. def slot_canvasSaveImage(self):
  391. newPath = QFileDialog.getSaveFileName(self, self.tr("Save Image"), filter=self.tr("PNG Image (*.png);;JPEG Image (*.jpg)"))
  392. if not newPath:
  393. return
  394. self.scene.clearSelection()
  395. if newPath.lower().endswith(".jpg"):
  396. imgFormat = "JPG"
  397. elif newPath.lower().endswith(".png"):
  398. imgFormat = "PNG"
  399. else:
  400. # File-dialog may not auto-add the extension
  401. imgFormat = "PNG"
  402. newPath += ".png"
  403. self.fExportImage = QImage(self.scene.sceneRect().width(), self.scene.sceneRect().height(), QImage.Format_RGB32)
  404. painter = QPainter(self.fExportImage)
  405. painter.save()
  406. painter.setRenderHint(QPainter.Antialiasing, True)
  407. painter.setRenderHint(QPainter.TextAntialiasing, True)
  408. self.scene.render(painter)
  409. self.fExportImage.save(newPath, imgFormat, 100)
  410. painter.restore()
  411. # -----------------------------------------------------------------
  412. # Shared Connections
  413. def setCanvasConnections(self):
  414. self.ui.act_canvas_arrange.setEnabled(False) # TODO, later
  415. self.connect(self.ui.act_canvas_arrange, SIGNAL("triggered()"), SLOT("slot_canvasArrange()"))
  416. self.connect(self.ui.act_canvas_refresh, SIGNAL("triggered()"), SLOT("slot_canvasRefresh()"))
  417. self.connect(self.ui.act_canvas_zoom_fit, SIGNAL("triggered()"), SLOT("slot_canvasZoomFit()"))
  418. self.connect(self.ui.act_canvas_zoom_in, SIGNAL("triggered()"), SLOT("slot_canvasZoomIn()"))
  419. self.connect(self.ui.act_canvas_zoom_out, SIGNAL("triggered()"), SLOT("slot_canvasZoomOut()"))
  420. self.connect(self.ui.act_canvas_zoom_100, SIGNAL("triggered()"), SLOT("slot_canvasZoomReset()"))
  421. self.connect(self.ui.act_canvas_print, SIGNAL("triggered()"), SLOT("slot_canvasPrint()"))
  422. self.connect(self.ui.act_canvas_save_image, SIGNAL("triggered()"), SLOT("slot_canvasSaveImage()"))
  423. self.connect(self.ui.b_canvas_zoom_fit, SIGNAL("clicked()"), SLOT("slot_canvasZoomFit()"))
  424. self.connect(self.ui.b_canvas_zoom_in, SIGNAL("clicked()"), SLOT("slot_canvasZoomIn()"))
  425. self.connect(self.ui.b_canvas_zoom_out, SIGNAL("clicked()"), SLOT("slot_canvasZoomOut()"))
  426. self.connect(self.ui.b_canvas_zoom_100, SIGNAL("clicked()"), SLOT("slot_canvasZoomReset()"))
  427. def setJackConnections(self, modes):
  428. if "jack" in modes:
  429. self.connect(self.ui.act_jack_clear_xruns, SIGNAL("triggered()"), SLOT("slot_JackClearXruns()"))
  430. self.connect(self.ui.act_jack_render, SIGNAL("triggered()"), SLOT("slot_showRender()"))
  431. self.connect(self.ui.act_jack_configure, SIGNAL("triggered()"), SLOT("slot_showJackSettings()"))
  432. self.connect(self.ui.b_jack_clear_xruns, SIGNAL("clicked()"), SLOT("slot_JackClearXruns()"))
  433. self.connect(self.ui.b_jack_configure, SIGNAL("clicked()"), SLOT("slot_showJackSettings()"))
  434. self.connect(self.ui.b_jack_render, SIGNAL("clicked()"), SLOT("slot_showRender()"))
  435. self.connect(self.ui.cb_buffer_size, SIGNAL("currentIndexChanged(QString)"), SLOT("slot_jackBufferSize_ComboBox(QString)"))
  436. self.connect(self.ui.cb_sample_rate, SIGNAL("currentIndexChanged(QString)"), SLOT("slot_jackSampleRate_ComboBox(QString)"))
  437. self.connect(self.ui.b_xruns, SIGNAL("clicked()"), SLOT("slot_JackClearXruns()"))
  438. if "buffer-size" in modes:
  439. self.connect(self.ui.act_jack_bf_16, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
  440. self.connect(self.ui.act_jack_bf_32, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
  441. self.connect(self.ui.act_jack_bf_64, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
  442. self.connect(self.ui.act_jack_bf_128, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
  443. self.connect(self.ui.act_jack_bf_256, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
  444. self.connect(self.ui.act_jack_bf_512, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
  445. self.connect(self.ui.act_jack_bf_1024, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
  446. self.connect(self.ui.act_jack_bf_2048, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
  447. self.connect(self.ui.act_jack_bf_4096, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
  448. self.connect(self.ui.act_jack_bf_8192, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
  449. if "transport" in modes:
  450. self.connect(self.ui.act_transport_play, SIGNAL("triggered(bool)"), SLOT("slot_transportPlayPause(bool)"))
  451. self.connect(self.ui.act_transport_stop, SIGNAL("triggered()"), SLOT("slot_transportStop()"))
  452. self.connect(self.ui.act_transport_backwards, SIGNAL("triggered()"), SLOT("slot_transportBackwards()"))
  453. self.connect(self.ui.act_transport_forwards, SIGNAL("triggered()"), SLOT("slot_transportForwards()"))
  454. self.connect(self.ui.b_transport_play, SIGNAL("clicked(bool)"), SLOT("slot_transportPlayPause(bool)"))
  455. self.connect(self.ui.b_transport_stop, SIGNAL("clicked()"), SLOT("slot_transportStop()"))
  456. self.connect(self.ui.b_transport_backwards, SIGNAL("clicked()"), SLOT("slot_transportBackwards()"))
  457. self.connect(self.ui.b_transport_forwards, SIGNAL("clicked()"), SLOT("slot_transportForwards()"))
  458. self.connect(self.ui.label_time, SIGNAL("customContextMenuRequested(QPoint)"), SLOT("slot_transportViewMenu()"))
  459. if "misc" in modes:
  460. if LINUX:
  461. self.connect(self.ui.act_show_logs, SIGNAL("triggered()"), SLOT("slot_showLogs()"))
  462. else:
  463. self.ui.act_show_logs.setEnabled(False)