root/trunk/libffado/support/mixer-qt4/ffado/widgets/matrixmixer.py

Revision 1709, 9.7 kB (checked in by arnonym, 11 years ago)

Some more gui-niceness.

  • Property svn:mergeinfo set to
Line 
1 #!/usr/bin/env python
2 # coding=utf8
3 #
4 # Copyright (C) 2009 by Arnold Krille
5 #
6 # This file is part of FFADO
7 # FFADO = Free Firewire (pro-)audio drivers for linux
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 2 of the License, or
12 # (at your option) version 3 of the License.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 #
22
23 from PyQt4 import QtGui, QtCore, Qt
24 import dbus, math, decimal
25
26 import logging
27 log = logging.getLogger("matrixmixer")
28
29 class ColorForNumber:
30     def __init__(self):
31         self.colors = dict()
32
33     def addColor(self, n, color):
34         self.colors[n] = color
35
36     def getColor(self, n):
37         #print "ColorForNumber.getColor( %g )" % (n)
38         keys = self.colors.keys()
39         keys.sort()
40         low = keys[-1]
41         high = keys[-1]
42         for i in range(len(keys)-1):
43             if keys[i] <= n and keys[i+1] > n:
44                 low = keys[i]
45                 high = keys[i+1]
46         #print "%g is between %g and %g" % (n, low, high)
47         f = 0
48         if high != low:
49             f = (n-low) / (high-low)
50         lc = self.colors[low]
51         hc = self.colors[high]
52         return QtGui.QColor(
53                 (1-f)*lc.red()   + f*hc.red(),
54                 (1-f)*lc.green() + f*hc.green(),
55                 (1-f)*lc.blue()  + f*hc.blue() )
56
57 class MixerNode(QtGui.QAbstractSlider):
58     def __init__(self, input, output, value, parent):
59         QtGui.QAbstractSlider.__init__(self, parent)
60         #log.debug("MixerNode.__init__( %i, %i, %i, %s )" % (input, output, value, str(parent)) )
61
62         self.pos = QtCore.QPointF(0, 0)
63         self.input = input
64         self.output = output
65         self.setOrientation(Qt.Qt.Vertical)
66         self.setRange(0, pow(2, 16)-1)
67         self.setValue(value)
68         self.connect(self, QtCore.SIGNAL("valueChanged(int)"), self.internalValueChanged)
69
70         self.setSmall(False)
71
72         self.bgcolors = ColorForNumber()
73         self.bgcolors.addColor(             0.0, QtGui.QColor(  0,   0,   0))
74         self.bgcolors.addColor(             1.0, QtGui.QColor(  0,   0, 128))
75         self.bgcolors.addColor(   math.pow(2,6), QtGui.QColor(  0, 255,   0))
76         self.bgcolors.addColor(  math.pow(2,14), QtGui.QColor(255, 255,   0))
77         self.bgcolors.addColor(math.pow(2,16)-1, QtGui.QColor(255,   0,   0))
78
79         self.setContextMenuPolicy(Qt.Qt.ActionsContextMenu)
80         self.mapper = QtCore.QSignalMapper(self)
81         self.connect(self.mapper, QtCore.SIGNAL("mapped(const QString&)"), self.directValues)
82
83         self.spinbox = QtGui.QDoubleSpinBox(self)
84         self.spinbox.setRange(-40, 12)
85         self.spinbox.setSuffix(" dB")
86         self.connect(self.spinbox, QtCore.SIGNAL("valueChanged(const QString&)"), self.directValues)
87         action = QtGui.QWidgetAction(self)
88         action.setDefaultWidget(self.spinbox)
89         self.addAction(action)
90
91         for text in ["3 dB", "0 dB", "-3 dB", "-20 dB", "-inf dB"]:
92             action = QtGui.QAction(text, self)
93             self.connect(action, QtCore.SIGNAL("triggered()"), self.mapper, QtCore.SLOT("map()"))
94             self.mapper.setMapping(action, text)
95             self.addAction(action)
96
97     def directValues(self,text):
98         #log.debug("MixerNode.directValues( '%s' )" % text)
99         text = text.split(" ")[0].replace(",",".")
100         n = pow(10, (float(text)/20)) * pow(2,14)
101         #log.debug("%g" % n)
102         self.setValue(n)
103
104     def mousePressEvent(self, ev):
105         if ev.buttons() & Qt.Qt.LeftButton:
106             self.pos = ev.posF()
107             self.tmpvalue = self.value()
108             ev.accept()
109             #log.debug("MixerNode.mousePressEvent() %s" % str(self.pos))
110
111     def mouseMoveEvent(self, ev):
112         if hasattr(self, "tmpvalue") and self.pos is not QtCore.QPointF(0, 0):
113             newpos = ev.posF()
114             change = newpos.y() - self.pos.y()
115             #log.debug("MixerNode.mouseReleaseEvent() change %s" % (str(change)))
116             self.setValue( self.tmpvalue - math.copysign(pow(abs(change), 2), change) )
117             ev.accept()
118
119     def mouseReleaseEvent(self, ev):
120         if hasattr(self, "tmpvalue") and self.pos is not QtCore.QPointF(0, 0):
121             newpos = ev.posF()
122             change = newpos.y() - self.pos.y()
123             #log.debug("MixerNode.mouseReleaseEvent() change %s" % (str(change)))
124             self.setValue( self.tmpvalue - math.copysign(pow(abs(change), 2), change) )
125             self.pos = QtCore.QPointF(0, 0)
126             del self.tmpvalue
127             ev.accept()
128
129     def paintEvent(self, ev):
130         p = QtGui.QPainter(self)
131         rect = self.rect()
132         v = self.value()
133         color = self.bgcolors.getColor(v)
134         p.fillRect(rect, color)
135
136         if self.small:
137             return
138
139         if color.valueF() < 0.6:
140             p.setPen(QtGui.QColor(255, 255, 255))
141         else:
142             p.setPen(QtGui.QColor(0, 0, 0))
143         lv=decimal.Decimal('-Infinity')
144         if v != 0:
145             lv = 20 * math.log10(v * 1.0 / math.pow(2,14))
146             #log.debug("new value is %g dB" % lv)
147         text = "%.2g dB" % lv
148         if v == 0:
149             text = "-ꝏ dB"
150         p.drawText(rect, Qt.Qt.AlignCenter, QtCore.QString.fromUtf8(text))
151
152     def internalValueChanged(self, value):
153         #log.debug("MixerNode.internalValueChanged( %i )" % value)
154         if value is not 0:
155             dB = 20 * math.log10(value / math.pow(2,14))
156             if self.spinbox.value() is not dB:
157                 self.spinbox.setValue(dB)
158         self.emit(QtCore.SIGNAL("valueChanged"), (self.input, self.output, value) )
159         self.update()
160
161     def setSmall(self, small):
162         self.small = small
163         if small:
164             self.setMinimumSize(10, 10)
165         else:
166             fontmetrics = self.fontMetrics()
167             self.setMinimumSize(fontmetrics.boundingRect("-0.0 dB").size()*1.1)
168         self.update()
169
170
171 class MixerChannel(QtGui.QWidget):
172     def __init__(self, number, parent=None, name=""):
173         QtGui.QWidget.__init__(self, parent)
174         layout = QtGui.QGridLayout(self)
175         self.number = number
176         if name is not "":
177             name = "\n(%s)" % name
178         self.name = name
179         self.lbl = QtGui.QLabel(self)
180         layout.addWidget(self.lbl, 0, 0, 1, 2)
181         self.hideChannel(False)
182
183         self.setContextMenuPolicy(Qt.Qt.ActionsContextMenu)
184
185         action = QtGui.QAction("Make this channel small", self)
186         action.setCheckable(True)
187         self.connect(action, QtCore.SIGNAL("triggered(bool)"), self.hideChannel)
188         self.addAction(action)
189
190     def hideChannel(self, hide):
191         if hide:
192             self.lbl.setText("Ch. %i" % self.number);
193         else:
194             self.lbl.setText("Ch. %i%s" % (self.number, self.name))
195         self.emit(QtCore.SIGNAL("hide"), self.number, hide)
196         self.update()
197
198
199
200 class MatrixMixer(QtGui.QWidget):
201     def __init__(self, servername, basepath, parent=None):
202         QtGui.QWidget.__init__(self, parent)
203         self.bus = dbus.SessionBus()
204         self.dev = self.bus.get_object(servername, basepath)
205         self.interface = dbus.Interface(self.dev, dbus_interface="org.ffado.Control.Element.MatrixMixer")
206
207         #palette = self.palette()
208         #palette.setColor(QtGui.QPalette.Window, palette.color(QtGui.QPalette.Window).darker());
209         #self.setPalette(palette)
210
211         rows = self.interface.getColCount()
212         cols = self.interface.getRowCount()
213         log.debug("Mixer has %i rows and %i columns" % (rows, cols))
214
215         layout = QtGui.QGridLayout(self)
216         self.setLayout(layout)
217
218         self.rowHeaders = []
219         self.columnHeaders = []
220         self.items = []
221
222         # Add row/column headers
223         for i in range(cols):
224             ch = MixerChannel(i, self, self.interface.getColName(i))
225             self.connect(ch, QtCore.SIGNAL("hide"), self.hideColumn)
226             layout.addWidget(ch, 0, i+1)
227             self.columnHeaders.append( ch )
228         for i in range(rows):
229             ch = MixerChannel(i, self, self.interface.getRowName(i))
230             self.connect(ch, QtCore.SIGNAL("hide"), self.hideRow)
231             layout.addWidget(ch, i+1, 0)
232             self.rowHeaders.append( ch )
233
234         # Add node-widgets
235         for i in range(rows):
236             self.items.append([])
237             for j in range(cols):
238                 node = MixerNode(j, i, self.interface.getValue(i,j), self)
239                 self.connect(node, QtCore.SIGNAL("valueChanged"), self.valueChanged)
240                 layout.addWidget(node, i+1, j+1)
241                 self.items[i].append(node)
242
243         self.hiddenRows = []
244         self.hiddenCols = []
245
246
247     def checkVisibilities(self):
248         for x in range(len(self.items)):
249             for y in range(len(self.items[x])):
250                 self.items[x][y].setSmall(
251                         (x in self.hiddenRows)
252                         | (y in self.hiddenCols)
253                         )
254
255
256     def hideColumn(self, column, hide):
257         if hide:
258             self.hiddenCols.append(column)
259         else:
260             self.hiddenCols.remove(column)
261         self.checkVisibilities()
262     def hideRow(self, row, hide):
263         if hide:
264             self.hiddenRows.append(row)
265         else:
266             self.hiddenRows.remove(row)
267         self.checkVisibilities()
268
269     def valueChanged(self, n):
270         #log.debug("MatrixNode.valueChanged( %s )" % str(n))
271         self.interface.setValue(n[1], n[0], n[2])
272
273
274 #
275 # vim: et ts=4 sw=4 fileencoding=utf8
Note: See TracBrowser for help on using the browser.