root/branches/2.4.x/libffado/support/mixer-qt4/ffado/widgets/matrixmixer.py

Revision 2829, 59.6 kB (checked in by jwoithe, 2 years ago)

v2.4.x branch: merge r2825 - r2828 from trunk.

All these patches are effectively bugfixes for 2.4.x. This merge is in
preparation for the release of version 2.4.6.

Command used:

svn merge /trunk -r2824:HEAD

This was run in the branches/2.4.x/ directory, not within the libffado
subdirectory.

  • Property svn:mergeinfo set to
Line 
1 # coding=utf8
2 #
3 # Copyright (C) 2009 by Arnold Krille
4 #               2013 by Philippe Carriere
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 # from PyQt4.QtCore import pyqtSignal
25 # from PyQt4.QtGui import QColor, QAbstractSlider, QDoubleSpinBox, QWidgetAction
26 # from PyQt4.QtGui import QAction, QPainter, QWidget, QGridLayout, QLabel
27 # from PyQt4.QtGui import QLayout, QSlider, QLineEdit, QPalette
28 # from PyQt4.QtGui import QVBoxLayout, QHBoxLayout, QTabWidget, QToolBar
29 # from PyQt4.QtGui import QComboBox, QScrollArea, QPushButton, QSizePolicy
30 from ffado.import_pyqt import *
31
32 import dbus, math, decimal
33
34 import ffado.config
35
36 import logging
37 log = logging.getLogger("matrixmixer")
38
39 def toDBvalue(value):
40     n = int(value)
41     c2p14 = 16384.0
42     if n > 164:
43         return round(20.0*math.log10(float(n)/c2p14), 2)
44     else:
45         return -40.0
46
47 def fromDBvalue(value):
48     v = float(value)
49     c2p14 = 16384.0
50     if (v > -40):
51         return int(round(math.pow(10.0, (value/20.0))*c2p14, 0))
52     else:
53         return 0
54
55 # v, vl, vr in linear scale
56 # b range in [-1:1]
57 def getVolumeLeft(v, b):
58     return int(round(0.5*v*(1.0-b),0))
59 def getVolumeRight(v, b):
60     return v-int(round(0.5*v*(1.0-b),0))
61 def getStereoVolume(vl, vr):
62     return int(round(vl+vr,0))
63 def getStereoBalance(vl, vr):
64     if ((vl+vr) == 0):
65         return 0
66     else:
67         return round(float(vr-vl)/float(vr+vl),2)
68
69 class ColorForNumber:
70     def __init__(self):
71         self.colors = dict()
72
73     def addColor(self, n, color):
74         self.colors[n] = color
75
76     def getColor(self, n):
77         #print( "ColorForNumber.getColor( %g )" % (n) )
78         keys = sorted(self.colors.keys())
79         low = keys[-1]
80         high = keys[-1]
81         for i in range(len(keys)-1):
82             if keys[i] <= n and keys[i+1] > n:
83                 low = keys[i]
84                 high = keys[i+1]
85         #print( "%g is between %g and %g" % (n, low, high) )
86         f = 0
87         if high != low:
88             f = (n-low) / (high-low)
89         lc = self.colors[low]
90         hc = self.colors[high]
91         return QColor(
92                 int((1-f)*lc.red()   + f*hc.red()),
93                 int((1-f)*lc.green() + f*hc.green()),
94                 int((1-f)*lc.blue()  + f*hc.blue()) )
95
96 class BckgrdColorForNumber(ColorForNumber):
97     def __init__(self):
98         ColorForNumber.__init__(self)
99         self.addColor(             0.0, QColor(  0,   0,   0))
100         self.addColor(             1.0, QColor(  0,   0, 128))
101         self.addColor(   math.pow(2,6), QColor(  0, 255,   0))
102         self.addColor(  math.pow(2,14), QColor(255, 255,   0))
103         self.addColor(math.pow(2,16)-1, QColor(255,   0,   0))
104
105     def getFrgdColor(self, color):
106         if color.valueF() < 0.6:
107             return QColor(255, 255, 255)
108         else:
109             return QColor(0, 0, 0)
110    
111 class MixerNode(QAbstractSlider):
112     nodeValueChanged = pyqtSignal(tuple)
113     def __init__(self, input, output, value, max, muted, inverted, parent, matrix_obj):
114         QAbstractSlider.__init__(self, parent)
115         #log.debug("MixerNode.__init__( %i, %i, %i, %i, %s )" % (input, output, value, max, str(parent)) )
116
117         # Store a direct link back to the underlying matrix object so the mute
118         # and invert interfaces can be easily found.  By the time the matrix
119         # has been set into the full widget hierarchy, its parent is unlikely
120         # to still be the top-level matrix object.
121         self.matrix_obj = matrix_obj;
122
123         self.pos = QtCore.QPointF(0, 0)
124         self.input = input
125         self.output = output
126         self.setOrientation(Qt.Vertical)
127         if max == -1:
128             max = pow(2, 16)-1
129         self.setRange(0, max)
130         self.setValue(int(value))
131         self.valueChanged.connect(self.internalValueChanged)
132
133         self.setSmall(False)
134
135         self.bgcolors = BckgrdColorForNumber()
136
137         self.setContextMenuPolicy(Qt.ActionsContextMenu)
138         self.mapper = QtCore.QSignalMapper(self)
139         self.mapper.mapped['QString'].connect(self.directValues)
140
141         self.spinbox = QDoubleSpinBox(self)
142         self.spinbox.setRange(-40, 12)
143         self.spinbox.setSuffix(" dB")
144         if value != 0:
145             self.spinbox.setValue(toDBvalue(value))           
146
147         self.spinbox.valueChanged.connect(self.directValues)
148         action = QWidgetAction(self)
149         action.setDefaultWidget(self.spinbox)
150         self.addAction(action)
151
152         for text in ["3 dB", "0 dB", "-3 dB", "-20 dB", "-inf dB"]:
153             action = QAction(text, self)
154             action.triggered.connect(self.mapper.map)
155             self.mapper.setMapping(action, text)
156             self.addAction(action)
157
158         # Only show the mute menu item if a value has been supplied
159         self.mute_action = None
160         if (muted != None):
161             action = QAction(text, self)
162             action.setSeparator(True)
163             self.addAction(action)
164             self.mute_action = QAction("Mute", self)
165             self.mute_action.setCheckable(True)
166             self.mute_action.setChecked(bool(muted))
167             self.mute_action.triggered.connect(self.mapper.map)
168             self.mapper.setMapping(self.mute_action, "Mute")
169             self.addAction(self.mute_action)
170
171         # Similarly, only show a phase inversion menu item if in use
172         self.inv_action = None
173         if (inverted != None):
174             if (muted == None):
175                 action = QAction(text, self)
176                 action.setSeparator(True)
177                 self.addAction(action)
178             self.inv_action = QAction("Invert", self)
179             self.inv_action.setCheckable(True)
180             self.inv_action.setChecked(bool(inverted))
181             self.inv_action.triggered.connect(self.mapper.map)
182             self.mapper.setMapping(self.inv_action, "Invert")
183             self.addAction(self.inv_action)
184
185     def directValues(self,text):
186         #log.debug("MixerNode.directValues( '%s' )" % text)
187         if text == "Mute":
188             #log.debug("Mute %d" % self.mute_action.isChecked())
189             self.update()
190             self.matrix_obj.mutes_interface.setValue(self.output, self.input, self.mute_action.isChecked())
191         elif text == "Invert":
192             #log.debug("Invert %d" % self.inv_action.isChecked())
193             self.update()
194             self.matrix_obj.inverts_interface.setValue(self.output, self.input, self.inv_action.isChecked())
195         else:
196             text = str(text).split(" ")[0].replace(",",".")
197             n = fromDBvalue(float(text))
198             #log.debug("  linear value: %g" % n)
199             self.setValue(n)
200
201     def mousePressEvent(self, ev):
202         if ev.buttons() & Qt.LeftButton:
203             self.pos = ev.posF() if ffado_pyqt_version == 4 else ev.localPos()
204             self.tmpvalue = self.value()
205             ev.accept()
206             #log.debug("MixerNode.mousePressEvent() %s" % str(self.pos))
207
208     def mouseMoveEvent(self, ev):
209         if hasattr(self, "tmpvalue") and self.pos is not QtCore.QPointF(0, 0):
210             newpos = ev.posF() if ffado_pyqt_version == 4 else ev.localPos()
211             change = newpos.y() - self.pos.y()
212             #log.debug("MixerNode.mouseReleaseEvent() change %s" % (str(change)))
213             self.setValue(
214                 int(self.tmpvalue - math.copysign(pow(abs(change), 2), change))
215             )
216             ev.accept()
217
218     def mouseReleaseEvent(self, ev):
219         if hasattr(self, "tmpvalue") and self.pos is not QtCore.QPointF(0, 0):
220             newpos = ev.posF() if ffado_pyqt_version == 4 else ev.localPos()
221             change = newpos.y() - self.pos.y()
222             #log.debug("MixerNode.mouseReleaseEvent() change %s" % (str(change)))
223             self.setValue(
224                 int(self.tmpvalue - math.copysign(pow(abs(change), 2), change))
225             )
226             self.pos = QtCore.QPointF(0, 0)
227             del self.tmpvalue
228             ev.accept()
229
230     # Wheel event is mainly for scrolling inside the mixer window
231     #   Additionnaly press Control key for wheel controling the values
232     def wheelEvent (self, ev):
233         if (ev.modifiers() & Qt.ControlModifier):
234             tmpvalue = self.value()
235             change = ev.delta()/8
236             self.setValue(
237                 int(tmpvalue + math.copysign(pow(abs(change), 2), change))
238             )
239             ev.accept()
240         else:
241             ev.ignore()
242
243     def paintEvent(self, ev):
244         p = QPainter(self)
245         rect = self.rect()
246         v = self.value()
247         if (self.mute_action!=None and self.mute_action.isChecked()):
248             color = QColor(64, 64, 64)
249         else:
250             color = self.bgcolors.getColor(v)
251         p.fillRect(rect, color)
252
253         if self.small:
254             return
255
256         p.setPen(self.bgcolors.getFrgdColor(color))
257
258         lv=decimal.Decimal('-Infinity')
259         if v != 0:
260             lv = toDBvalue(v)
261             #log.debug("new value is %g dB" % lv)
262         text = "%.2g dB" % lv
263         if v == 0:
264             symb_inf = u"\u221E"
265             text = "-" + symb_inf + " dB"
266         if ffado_python3 or ffado_pyqt_version == 5:
267             # Python3 uses native python UTF strings rather than QString.
268             # This therefore appears to be the correct way to display this
269             # UTF8 string, but testing may prove otherwise.
270             p.drawText(rect, Qt.AlignCenter, text)
271         else:
272             p.drawText(rect, Qt.AlignCenter, QString.fromUtf8(text))
273         if (self.inv_action!=None and self.inv_action.isChecked()):
274             if ffado_python3 or ffado_pyqt_version == 5:
275                 # Refer to the comment about about Python UTF8 strings.
276                 p.drawText(rect, Qt.AlignLeft|Qt.AlignTop, " ϕ")
277             else:
278                 p.drawText(rect, Qt.AlignLeft|Qt.AlignTop, QString.fromUtf8(" ϕ"))
279
280     def internalValueChanged(self, value):
281         #log.debug("MixerNode.internalValueChanged( %i )" % value)
282         if value != 0:
283             dB = toDBvalue(value)
284             if self.spinbox.value() is not dB:
285                 self.spinbox.setValue(dB)
286         self.nodeValueChanged.emit((self.input, self.output, value))
287         self.update()
288
289     def setSmall(self, small):
290         self.small = small
291         if small:
292             self.setMinimumSize(10, 10)
293         else:
294             fontmetrics = self.fontMetrics()
295             self.setMinimumSize(fontmetrics.boundingRect("-0.0 dB").size()*1.1)
296         self.update()
297
298 class MixerChannel(QWidget):
299     hide = pyqtSignal(int, bool, name='hide')
300     def __init__(self, number, parent=None, name="", smallFont=False):
301         QWidget.__init__(self, parent)
302         layout = QGridLayout(self)
303         self.number = number
304         self.name = name
305         self.lbl = QLabel(self)
306         self.lbl.setAlignment(Qt.AlignCenter)
307         if (smallFont):
308             font = self.lbl.font()
309             font.setPointSize(int(font.pointSize()/1.5 + 0.5))
310             self.lbl.setFont(font)
311         layout.addWidget(self.lbl, 0, 0, 1, 2)
312         self.hideChannel(False)
313
314         self.setContextMenuPolicy(Qt.ActionsContextMenu)
315
316         action = QAction("Make this channel small", self)
317         action.setCheckable(True)
318         action.triggered.connect(self.hideChannel)
319         self.addAction(action)
320
321     def hideChannel(self, hide):
322         if hide:
323             self.lbl.setText("%i" % (self.number+1));
324         else:
325             self.lbl.setText(self.name)
326         self.hide.emit(self.number, hide)
327         self.update()
328
329 # Matrix view widget
330 class MatrixControlView(QWidget):
331     valueChanged = pyqtSignal([tuple])
332     def __init__(self, servername, basepath, parent=None, sliderMaxValue=-1, mutespath=None, invertspath=None, smallFont=False, shortname=False, shortcolname="Ch", shortrowname="Ch", transpose=False):
333         QWidget.__init__(self, parent)
334
335         if not ffado.config.bypassdbus:
336             self.bus = dbus.SessionBus()
337             self.dev = self.bus.get_object(servername, basepath)
338             self.interface = dbus.Interface(self.dev, dbus_interface="org.ffado.Control.Element.MatrixMixer")
339
340         self.transpose = transpose
341         if (transpose):
342             self.shortcolname = shortrowname
343             self.shortrowname = shortcolname
344             if ffado.config.bypassdbus:
345                 self.cols = 2
346                 self.rows = 2
347             else:
348                 self.cols = self.interface.getRowCount()
349                 self.rows = self.interface.getColCount()
350         else:
351             self.shortcolname = shortcolname
352             self.shortrowname = shortrowname
353             if ffado.config.bypassdbus:
354                 self.cols = 2
355                 self.rows = 2
356             else:
357                 self.cols = self.interface.getColCount()
358                 self.rows = self.interface.getRowCount()
359
360         log.debug("Mixer has %i rows and %i columns" % (self.rows, self.cols))
361
362         self.mutes_dev = None
363         self.mutes_interface = None
364         if not ffado.config.bypassdbus and (mutespath != None):
365             self.mutes_dev = self.bus.get_object(servername, mutespath)
366             self.mutes_interface = dbus.Interface(self.mutes_dev, dbus_interface="org.ffado.Control.Element.MatrixMixer")
367
368         self.inverts_dev = None
369         self.inverts_interface = None
370         if not ffado.config.bypassdbus and (invertspath != None):
371             self.inverts_dev = self.bus.get_object(servername, invertspath)
372             self.inverts_interface = dbus.Interface(self.inverts_dev, dbus_interface="org.ffado.Control.Element.MatrixMixer")
373
374         layout = QGridLayout(self)
375         layout.setSizeConstraint(QLayout.SetNoConstraint);
376         self.setLayout(layout)
377
378         self.rowHeaders = []
379         self.columnHeaders = []
380         self.items = []
381         self.shortname = shortname
382
383         # Add row/column headers, but only if there's more than one
384         # row/column
385         if (self.cols > 1):
386             for i in range(self.cols):
387                 ch = MixerChannel(i, self, self.getColName(i, self.shortname), smallFont)
388                 ch.hide.connect(self.hideColumn)
389                 layout.addWidget(ch, 0, i+1)
390                 self.columnHeaders.append( ch )
391             layout.setRowStretch(0, 0)
392             layout.setRowStretch(1, 10)
393         if (self.rows > 1):
394             for i in range(self.rows):
395                 ch = MixerChannel(i, self, self.getRowName(i, self.shortname), smallFont)
396                 ch.hide.connect(self.hideRow)
397                 layout.addWidget(ch, i+1, 0)
398                 self.rowHeaders.append( ch )
399
400         # Add node-widgets
401         for i in range(self.rows):
402             self.items.append([])
403             for j in range(self.cols):
404                 if (transpose):
405                     mute_value = None
406                     if (self.mutes_interface != None):
407                         mute_value = self.mutes_interface.getValue(j,i)
408                     inv_value = None
409                     if (self.inverts_interface != None):
410                         inv_value = self.inverts_interface.getValue(j,i)
411                     if ffado.config.bypassdbus:
412                         val = 0
413                     else:
414                         val = self.interface.getValue(j,i)
415                     node = MixerNode(i, j, val, sliderMaxValue, mute_value, inv_value, self, self)
416                 else:
417                     mute_value = None
418                     if (self.mutes_interface != None):
419                         mute_value = self.mutes_interface.getValue(i,j)
420                     inv_value = None
421                     if (self.inverts_interface != None):
422                         inv_value = self.inverts_interface.getValue(i,j)
423                     if ffado.config.bypassdbus:
424                         val = 0
425                     else:
426                         val = self.interface.getValue(i,j)
427                     node = MixerNode(j, i, val, sliderMaxValue, mute_value, inv_value, self, self)
428                 if (smallFont):
429                     font = node.font()
430                     font.setPointSize(int(font.pointSize()/1.5 + 0.5))
431                     node.setFont(font)
432                 self.nodeConnect(node)
433                 layout.addWidget(node, i+1, j+1)
434                 self.items[i].append(node)
435
436         self.hiddenRows = []
437         self.hiddenCols = []
438
439     def nodeConnect(self, node):
440         node.nodeValueChanged.connect(self.valueChangedFn)
441
442     def nodeDisconnect(self, node):
443         node.nodeValueChanged.disconnect(self.valueChangedFn)
444
445     def checkVisibilities(self):
446         for x in range(len(self.items)):
447             for y in range(len(self.items[x])):
448                 self.items[x][y].setSmall(
449                         (x in self.hiddenRows)
450                         | (y in self.hiddenCols)
451                         )
452
453     def hideColumn(self, column, hide):
454         if hide:
455             self.hiddenCols.append(column)
456         else:
457             self.hiddenCols.remove(column)
458         self.checkVisibilities()
459
460     def hideRow(self, row, hide):
461         if hide:
462             self.hiddenRows.append(row)
463         else:
464             self.hiddenRows.remove(row)
465         self.checkVisibilities()
466
467     # Columns and rows
468     def getColName(self, i, shortname):
469         if ffado.config.bypassdbus:
470             return 'col ' + str(i)
471         if (self.transpose):
472             name = self.interface.getRowName(i)
473         else:
474             name = self.interface.getColName(i)
475         self.shortname = shortname
476         if (shortname or (name == '')):
477             number = " %d" % (i+1)
478             name = self.shortcolname + number
479         return name
480
481     def getRowName(self, j, shortname):
482         if ffado.config.bypassdbus:
483             return 'row ' + str(j)
484         if (self.transpose):
485             name = self.interface.getColName(j)
486         else:
487             name = self.interface.getRowName(j)
488         self.shortname = shortname
489         if (shortname or (name == '')):
490             number = " %d" % (j+1)
491             name = self.shortrowname + number
492         return name
493
494     def valueChangedFn(self, n):
495         #log.debug("MatrixNode.valueChangedFn( %s )" % str(n))
496         if not ffado.config.bypassdbus:
497             self.interface.setValue(n[1], n[0], n[2])
498         self.valueChanged.emit(n)
499        
500     # Update when routing is modified
501     def updateRouting(self):
502         if (self.cols > 1):
503             for i in range(self.cols):
504                 last_name = self.columnHeaders[i].lbl.text()
505                 col_name = self.getColName(i, self.shortname)
506                 if last_name != col_name:
507                     #log.debug("MatrixControlView.updateRouting( %s )" % str(col_name))
508                     self.columnHeaders[i].name = col_name
509                     self.columnHeaders[i].lbl.setText(col_name)
510      
511         if (self.rows > 1):
512             for j in range(self.rows):
513                 last_name = self.rowHeaders[j].lbl.text()
514                 row_name = self.getRowName(j, self.shortname)
515                 if last_name != row_name:
516                     #log.debug("MatrixControlView.updateRouting( %s )" % str(row_name))
517                     self.rowHeaders[j].name = row_name
518                     self.rowHeaders[j].lbl.setText(row_name)
519
520     def updateValues(self, n):
521         nbitems = len(n) // 3
522         for i in range(nbitems):
523             n_0 = n[3*i]   
524             n_1 = n[3*i+1]   
525             n_2 = n[3*i+2]
526             self.nodeDisconnect(self.items[n_0][n_1])
527             self.items[n_0][n_1].setValue(n_2)
528             self.nodeConnect(self.items[n_0][n_1])
529
530     def refreshValues(self):
531         if ffado.config.bypassdbus:
532             return
533         for x in range(len(self.items)):
534             for y in range(len(self.items[x])):
535                 val = self.interface.getValue(x,y)
536                 if (self.transpose):
537                     self.items[y][x].setValue(int(val))
538                     self.items[y][x].internalValueChanged(val)
539                 else:
540                     self.items[x][y].setValue(int(val))
541                     self.items[x][y].internalValueChanged(val)
542
543     def saveSettings(self, indent):
544         matrixSaveString = []
545         matrixSaveString.append('%s  <row_number>\n' % indent)
546         matrixSaveString.append('%s    %d\n' % (indent, self.rows))       
547         matrixSaveString.append('%s  </row_number>\n' % indent)
548         matrixSaveString.append('%s  <col_number>\n' % indent)
549         matrixSaveString.append('%s    %d\n' % (indent, self.cols))       
550         matrixSaveString.append('%s  </col_number>\n' % indent)
551         matrixSaveString.append('%s  <coefficients>\n' % indent)
552         for i in range(self.rows):
553             line = '%s    ' % indent
554             for j in range(self.cols):
555                 line += '%d ' % self.interface.getValue(i,j)
556             line += '\n'
557             matrixSaveString.append(line)       
558         matrixSaveString.append('%s  </coefficients>\n' % indent)
559         if (self.mutes_interface != None):
560             matrixSaveString.append('%s  <mutes>\n' % indent)
561             for i in range(self.rows):
562                 line = '%s    ' % indent
563                 for j in range(self.cols):
564                     line += '%d ' % self.mutes_interface.getValue(i,j)
565                 line += '\n'
566                 matrixSaveString.append(line)       
567             matrixSaveString.append('%s  </mutes>\n' % indent)
568
569         if (self.inverts_interface != None):
570             matrixSaveString.append('%s  <inverts>\n' % indent)
571             for i in range(self.rows):
572                 line = '%s    ' % indent
573                 for j in range(self.cols):
574                     line += '%d ' % self.inverts_interface.getValue(i,j)
575                 line += '\n'
576                 matrixSaveString.append(line)       
577             matrixSaveString.append('%s  </inverts>\n' % indent)
578
579         return matrixSaveString
580
581     def readSettings(self, readMatrixString, transpose_coeff):
582         if readMatrixString[0].find("<row_number>") == -1:
583             log.debug("Number of matrix rows must be specified")
584             return False
585         if readMatrixString[2].find("</row_number>") == -1:
586             log.debug("Non-conformal xml file")
587             return False
588         n_rows = int(readMatrixString[1])
589
590         if readMatrixString[3].find("<col_number>") == -1:
591             log.debug("Number of matrix columns must be specified")
592             return False
593         if readMatrixString[5].find("</col_number>") == -1:
594             log.debug("Non-conformal xml file")
595             return False
596         n_cols = int(readMatrixString[4])
597
598         if transpose_coeff:
599             if n_rows > self.cols:
600                 n_rows = self.cols
601             if n_cols > self.rows:
602                 n_cols = self.rows
603         else:
604             if n_rows > self.rows:
605                 n_rows = self.rows
606             if n_cols > self.cols:
607                 n_cols = self.cols
608         log.debug("Setting %d rows and %d columns coefficients" % (n_rows, n_cols))
609
610         try:
611             idxb = readMatrixString.index('<coefficients>')
612             idxe = readMatrixString.index('</coefficients>')
613         except Exception:
614             log.debug("No mixer matrix coefficients specified")
615             idxb = -1
616             idxe = -1
617         if idxb >= 0:
618             if idxe < idxb + n_rows + 1:
619                 log.debug("Incoherent number of rows in coefficients")
620                 return False
621             i = 0
622             for s in readMatrixString[idxb+1:idxb + n_rows + 1]:
623                 coeffs = s.split()
624                 if len(coeffs) < n_cols:
625                     log.debug("Incoherent number of columns in coefficients")
626                     return False
627                 j = 0
628                 for c in coeffs[0:n_cols]:
629                     if transpose_coeff:
630                         self.interface.setValue(j, i, int(c))
631                     else:
632                         self.interface.setValue(i, j, int(c))
633                     j += 1
634                 i += 1
635                 del coeffs
636
637         try:
638             idxb = readMatrixString.index('<mutes>')
639             idxe = readMatrixString.index('</mutes>')
640         except Exception:
641             log.debug("No mixer mute coefficients specified")
642             idxb = -1
643             idxe = -1
644         if idxb >= 0:
645             if idxe < idxb + n_rows + 1:
646                 log.debug("Incoherent number of rows in mute")
647                 return false
648             i = 0
649             for s in readMatrixString[idxb+1:idxb + n_rows + 1]:
650                 coeffs = s.split()
651                 if len(coeffs) < n_cols:
652                     log.debug("Incoherent number of columns in mute")
653                     return false
654                 j = 0
655                 for c in coeffs[0:n_cols]:
656                     if transpose_coeff:
657                         self.mutes_interface.setValue(j, i, int(c))
658                     else:
659                         self.mutes_interface.setValue(i, j, int(c))
660                     j += 1
661                 i += 1
662                 del coeffs
663
664         try:
665             idxb = readMatrixString.index('<inverts>')
666             idxe = readMatrixString.index('</inverts>')
667         except Exception:
668             log.debug("No mixer inverts coefficients specified")
669             idxb = -1
670             idxe = -1
671         if idxb >= 0:
672             if idxe < idxb + n_rows + 1:
673                 log.debug("Incoherent number of rows in inverts")
674                 return false
675             i = 0
676             for s in readMatrixString[idxb+1:idxb + n_rows + 1]:
677                 coeffs = s.split()
678                 if len(coeffs) < n_cols:
679                     log.debug("Incoherent number of columns in inverts")
680                     return false
681                 j = 0
682                 for c in coeffs[0:n_cols]:
683                     if transpose_coeff:
684                         self.inverts_interface.setValue(j, i, int(c))
685                     else:
686                         self.inverts_interface.setValue(i, j, int(c))
687                     j += 1
688                 i += 1
689                 del coeffs
690
691         self.refreshValues()
692         return True
693
694 class VolumeSlider(QSlider):
695     sliderChanged = pyqtSignal(tuple)
696     def __init__(self, In, Out, value, parent):
697         QSlider.__init__(self, QtCore.Qt.Vertical, parent)
698
699         self.setTickPosition(QSlider.TicksBothSides)
700         v_min = 10.0*toDBvalue(0)
701         v_max = 10.0*toDBvalue(65536)
702         self.setTickInterval(int((v_max-v_min)/10))
703         self.setMinimum(int(v_min))
704         self.setMaximum(int(v_max))
705         self.setSingleStep(1)
706         self.sliderSetValue(value)
707         self.In = In
708         self.Out = Out
709         self.valueChanged.connect(self.sliderValueChanged)
710
711     def sliderSetValue(self, value):
712         #log.debug("Volume slider value changed( %i )" % value)
713         v = 10.0*toDBvalue(value)
714         #log.debug("Volume slider value changed(dB: %g )" % (0.1*v))
715         self.setValue(int(v))
716
717     def sliderReadValue(self, value):
718         return fromDBvalue(0.1*value)
719
720     # Restore absolute value from DB
721     # Emit signal for further use, especially for matrix view
722     def sliderValueChanged(self, value):
723         value = fromDBvalue(0.1*value)
724         self.sliderChanged.emit((self.In, self.Out, value))
725         self.update()
726
727
728 class VolumeSliderValueInfo(QLineEdit):
729     def __init__(self, In, Out, value, parent):
730         QLineEdit.__init__(self, parent)
731
732         self.setReadOnly(True)
733         self.setAlignment(Qt.AlignCenter)
734         self.setAutoFillBackground(True)
735         self.setFrame(False)
736
737         self.sliderSetMinimalDim()
738
739         self.bgcolors = BckgrdColorForNumber()
740
741         self.sliderSetValue(value)
742
743     def sliderSetMinimalDim(self):
744         fontmetrics = self.fontMetrics()
745         self.setMinimumSize(fontmetrics.boundingRect("-00.0 dB").size()*1.1)
746        
747     def sliderSetValue(self, value):
748         color = self.bgcolors.getColor(value)
749         palette = self.palette()
750         palette.setColor(QPalette.Active, QPalette.Base, color)
751         palette.setColor(QPalette.Active, QPalette.Text, self.bgcolors.getFrgdColor(color))
752         self.setPalette(palette)
753
754         v = round(toDBvalue(value),1)
755         if (v > -40):
756             text = "%.1f dB" % v
757         else:
758             symb_inf = u"\u221E"
759             text = "-" + symb_inf + " dB"
760
761         self.setText(text)
762        
763 class BalanceSlider(QSlider):
764     sliderChanged = pyqtSignal(tuple)
765     def __init__(self, In, Out, value, parent):
766         QSlider.__init__(self, QtCore.Qt.Horizontal, parent)
767
768         v_min = -50
769         v_max = 50
770         self.setTickPosition(QSlider.TicksBothSides)
771         self.setTickInterval(int((v_max-v_min)/2))
772         self.setMinimum(v_min)
773         self.setMaximum(v_max)
774         self.setSingleStep(1)
775         self.In = In
776         self.Out = Out
777         self.sliderSetValue(value)
778         self.valueChanged.connect(self.sliderValueChanged)
779
780     def sliderSetValue(self, value):
781         #log.debug("Balance fader value set( %d, %d, %f )" % (self.In, self.Out, value))
782         v = int(round(50.0*value, 2))
783         self.setValue(v)
784
785     def sliderReadValue(self):
786         return float(round(self.value()/50.0, 2))
787
788     def sliderValueChanged(self, value):
789         value = float(round(self.value()/50.0, 2))
790         #log.debug("Balance fader value changed( %d, %d, %f )" % (self.In, self.Out, value))
791         self.sliderChanged.emit((self.In, self.Out, value))
792
793 # Slider view widget
794 class SliderControlView(QWidget):
795     valueChanged = pyqtSignal(tuple)
796     def __init__(self, parent, servername, basepath, rule="Columns_are_inputs", shortname=False, shortinname="Ch", shortoutname="Ch", stereochannels = []):
797         QWidget.__init__(self, parent)
798
799         if not ffado.config.bypassdbus:
800             self.bus = dbus.SessionBus()
801             self.dev = self.bus.get_object(servername, basepath)
802             self.interface = dbus.Interface(self.dev, dbus_interface="org.ffado.Control.Element.MatrixMixer")
803
804         self.rule = rule
805         self.shortname = shortname
806         self.shortinname = shortinname
807         self.shortoutname = shortoutname
808
809         self.stereochannels = stereochannels
810
811         self.out = []
812         self.nbIn = self.getNbIn()
813         self.nbOut = self.getNbOut()
814         self.outmatrix = []
815
816         k = 0
817         for i in range(self.nbOut):
818             widget = QWidget(parent)
819             v_layout = QVBoxLayout(widget)
820             v_layout.setAlignment(Qt.AlignCenter)
821             widget.setLayout(v_layout)
822             self.out.append(widget)
823
824             self.out[i].is_stereo = False
825             self.out[i].out_1 = k
826             self.outmatrix.append(i)
827             self.out[i].outname = "Out %d" % (k+1)
828             if k in self.stereochannels:
829                 self.out[i].is_stereo = True
830                 self.out[i].outname += "+%d" % (k+2)
831                 self.outmatrix.append(i)
832
833             self.out[i].lbl = []
834
835             # Mixer/Out info label
836             if (self.nbOut > 1):
837                 lbl = QLabel(widget)
838                 lbl.setText(self.getOutName(i, self.shortname))
839                 lbl.setAlignment(Qt.AlignCenter)
840                 v_layout.addWidget(lbl)
841                 self.out[i].lbl.append(lbl)
842
843             h_layout_wid = QWidget(widget)
844             h_layout = QHBoxLayout(h_layout_wid)
845             h_layout.setAlignment(Qt.AlignCenter)
846             h_layout_wid.setLayout(h_layout)
847             v_layout.addWidget(h_layout_wid)
848             self.out[i].volume = []
849             self.out[i].svl = []
850             self.out[i].balance = []
851
852             for j in range(self.nbIn):
853                 h_v_layout_wid = QWidget(h_layout_wid)
854                 h_v_layout = QVBoxLayout(h_v_layout_wid)
855                 h_v_layout.setAlignment(Qt.AlignCenter)
856                 h_v_layout_wid.setLayout(h_v_layout)
857                 h_layout.addWidget(h_v_layout_wid)
858
859                 # Mixer/In info label
860                 if (self.nbIn > 1):
861                     lbl = QLabel(h_v_layout_wid)
862                     lbl.setText(self.getInName(j, self.shortname))
863                     lbl.setAlignment(Qt.AlignCenter)
864                     h_v_layout.addWidget(lbl)
865                     self.out[i].lbl.append(lbl)
866
867                 h_v_h_layout_wid = QWidget(h_v_layout_wid)
868                 h_v_h_layout = QHBoxLayout(h_v_h_layout_wid)
869                 h_v_h_layout.setAlignment(Qt.AlignCenter)
870                 h_v_h_layout_wid.setLayout(h_v_h_layout)
871                 h_v_layout.addWidget(h_v_h_layout_wid)
872
873                 volume = VolumeSlider(j, i, self.getVolumeValue(j,i), h_v_h_layout_wid)
874                 h_v_h_layout.addWidget(volume)
875                 self.out[i].volume.append(volume)
876                 self.volumeConnect(volume)
877
878                 # Volume slider info
879                 svl = VolumeSliderValueInfo(j, i, self.getVolumeValue(j,i), h_v_layout_wid)
880                 h_v_layout.addWidget(svl)
881                 self.out[i].svl.append(svl)
882
883                 # Balance fader
884                 if self.out[i].is_stereo:
885                     balance = BalanceSlider(j, i, self.getBalanceValue(j,i), h_v_layout_wid)
886                     h_v_layout.addWidget(balance)
887                     self.out[i].balance.append(balance)
888                     self.balanceConnect(balance)
889             k += 1
890             if self.out[i].is_stereo:
891                 k += 1
892
893     def volumeConnect(self, volume):
894         volume.sliderChanged.connect(self.valueChangedVolume)
895
896     def volumeDisconnect(self, volume):
897         volume.sliderChanged.disconnect(self.valueChangedVolume)
898
899     def balanceConnect(self, balance):
900         balance.sliderChanged.connect(self.valueChangedBalance)
901
902     def balanceDisconnect(self, balance):
903         balance.sliderChanged.disconnect(self.valueChangedBalance)
904
905     def getNbIn(self):
906         if ffado.config.bypassdbus:
907             return 2
908         if (self.rule == "Columns_are_inputs"):
909             return self.interface.getColCount()
910         else:
911             return self.interface.getRowCount()
912        
913     def getNbOut(self):
914         if ffado.config.bypassdbus:
915             return 2
916         if (self.rule == "Columns_are_inputs"):
917             nbout = self.interface.getRowCount()
918         else:
919             nbout = self.interface.getColCount()
920         return nbout-len(self.stereochannels)
921        
922     def getVolumeValue(self, In, i):
923         if ffado.config.bypassdbus:
924             return 1
925         Out = self.out[i].out_1
926         if (self.rule == "Columns_are_inputs"):
927             vl = self.interface.getValue(Out, In)           
928         else:
929             vl = self.interface.getValue(In, Out)
930         if (self.out[i].is_stereo):
931             if (self.rule == "Columns_are_inputs"):
932                 vr = self.interface.getValue(Out+1, In)           
933             else:
934                 vr = self.interface.getValue(In, Out+1)
935             return getStereoVolume(vl, vr)
936         else:
937             return vl;     
938
939     def getBalanceValue(self, In, i):
940         if ffado.config.bypassdbus:
941             return 0.5
942         Out = self.out[i].out_1
943         if (self.rule == "Columns_are_inputs"):
944             vl = self.interface.getValue(Out, In)           
945             vr = self.interface.getValue(Out+1, In)           
946         else:
947             vl = self.interface.getValue(In, Out)
948             vr = self.interface.getValue(In, Out+1)
949         return getStereoBalance(vl, vr)
950
951     def setValue(self, In, Out, val):
952         if ffado.config.bypassdbus:
953             return
954         if (self.rule == "Columns_are_inputs"):
955             return self.interface.setValue(Out, In, val)           
956         else:
957             return self.interface.setValue(In, Out, val)           
958
959     def updateValues(self, n):
960         nbitems = len(n) // 3
961         for j in range(nbitems):
962             n_0 = n[3*j]   
963             n_1 = n[3*j+1]   
964             n_2 = n[3*j+2]
965             i = self.outmatrix[n_1]
966             if (self.out[i].is_stereo):
967                 v = self.getVolumeValue(n_0, i)
968                 self.volumeDisconnect(self.out[i].volume[n_0])
969                 self.balanceDisconnect(self.out[i].balance[n_0])
970                 self.out[i].volume[n_0].sliderSetValue(v)
971                 self.out[i].svl[n_0].sliderSetValue(v)
972                 b = self.getBalanceValue(n_0, i)       
973                 # log.debug("update Value (%d %d %d %f)" % (n_0, i, v, b))
974                 self.out[i].balance[n_0].sliderSetValue(b)
975                 self.volumeConnect(self.out[i].volume[n_0])
976                 self.balanceConnect(self.out[i].balance[n_0])
977             else:
978                 v = n_2
979                 # log.debug("update Value (%d %d %d)" % (n_0, i, v))
980                 self.volumeDisconnect(self.out[i].volume[n_0])
981                 self.out[i].volume[n_0].sliderSetValue(v)
982                 self.out[i].svl[n_0].sliderSetValue(v)
983                 self.volumeConnect(self.out[i].volume[n_0])
984        
985     def valueChangedVolume(self, n):
986         #log.debug("VolumeSlider.valueChanged( %s )" % str(n))
987         v = n[2]
988         n1 = self.out[n[1]].out_1
989         if (self.out[n[1]].is_stereo):
990             b = self.out[n[1]].balance[n[0]].value()/50.0
991             vl = int(getVolumeLeft(v, b))
992             self.setValue(n[0], n1, vl)
993             n2 = n1+1
994             vr = int(getVolumeRight(v, b))
995             self.setValue(n[0], n2, vr)
996             n_t = (n[0], n1, vl, n[0], n2, vr)
997             self.valueChanged.emit(n_t)
998         else:
999             self.setValue(n[0], n1, v)
1000             n_t = (n[0], n1, v)
1001             self.valueChanged.emit(n_t)
1002         self.out[n[1]].svl[n[0]].sliderSetValue(v)
1003
1004     def valueChangedBalance(self, n):
1005         #log.debug("BalanceSlider.valueChanged( %s )" % str(n))
1006         n1 = self.out[n[1]].out_1
1007         v = fromDBvalue(0.1*self.out[n[1]].volume[n[0]].value())
1008         b = n[2]
1009         vl = int(getVolumeLeft(v, b))
1010         self.setValue(n[0], n1, vl)
1011         n2 = n1+1
1012         vr = int(getVolumeRight(v, b))
1013         self.setValue(n[0], n2, vr)
1014         n_t = (n[0], n1, vl, n[0], n2, vr)
1015         self.valueChanged.emit(n_t)
1016
1017     def getOutName(self, i, shortname):
1018         self.shortname = shortname
1019         k = self.out[i].out_1
1020         if (shortname):
1021             if (self.out[i].is_stereo):
1022                 number = " %d+%d" % (k+1, k+2)
1023             else:
1024                 number = " %d" % (k+1)
1025             name = self.shortoutname + number
1026             return name
1027         else:
1028             if ffado.config.bypassdbus:
1029                 return 'OutName ' + str(i)
1030             if (self.rule == "Columns_are_inputs"):               
1031                 if (self.out[i].is_stereo):
1032                     name = self.interface.getRowName(k).replace('\n','')+" + "+self.interface.getRowName(k+1).replace('\n','')
1033                 else:
1034                     name = self.interface.getRowName(k).replace('\n','')
1035                 return name
1036             else:
1037                 if (self.out[i].is_stereo):
1038                     name = self.interface.getColName(k).replace('\n','')+" + "+self.interface.getColName(k+1).replace('\n','')
1039                 else:
1040                     name = self.interface.getColName(k).replace('\n','')
1041                 return name
1042
1043     def getInName(self, j, shortname):
1044         self.shortname = shortname
1045         if (shortname):
1046             number = " %d" % (j+1)
1047             name = self.shortinname + number
1048             return name
1049         else:
1050             if ffado.config.bypassdbus:
1051                 return 'InName ' + str(j)
1052             if (self.rule == "Columns_are_inputs"):
1053                 return self.interface.getColName(j)           
1054             else:
1055                 return self.interface.getRowName(j)           
1056
1057     # Update when routing is modified
1058     def updateRouting(self):
1059         for i in range(self.nbOut):
1060             if (self.nbOut > 1):
1061                 last_name = self.out[i].lbl[0].text()
1062                 out_name = self.getOutName(i, self.shortname)
1063                 if last_name != out_name:
1064                     #log.debug("SliderControlView.updateRouting( %s )" % str(out_name))
1065                     self.out[i].lbl[0].setText(out_name)
1066             if (self.nbIn > 1):
1067                 for j in range(self.nbIn):
1068                     last_name = self.out[i].lbl[j+1].text()
1069                     in_name = self.getInName(j, self.shortname)
1070                     if last_name != in_name:
1071                         #log.debug("SliderControlView.updateRouting( %s )" % str(in_name))
1072                         self.out[i].lbl[j+1].setText(in_name)
1073
1074     def refreshValues(self):
1075         for n_out in range(self.nbOut):
1076             for n_in in range(self.nbIn):
1077                 i = self.outmatrix[n_out]
1078                 v = self.getVolumeValue(n_in, i)
1079                 if (self.out[i].is_stereo):
1080                     self.volumeDisconnect(self.out[i].volume[n_in])
1081                     self.out[i].volume[n_in].sliderSetValue(v)
1082                     self.out[i].svl[n_in].sliderSetValue(v)
1083                     b = self.getBalanceValue(n_in, i)       
1084                     # log.debug("update Value (%d %d %d %f)" % (n_0, i, v, b))
1085                     self.out[i].balance[n_in].sliderSetValue(b)
1086                     self.volumeConnect(self.out[i].volume[n_in])
1087                 else:
1088                     # log.debug("update Value (%d %d %d)" % (n_0, i, v))
1089                     self.out[i].volume[n_in].sliderSetValue(v)
1090                     self.out[i].svl[n_in].sliderSetValue(v)
1091
1092     def saveSettings(self, indent):
1093         if ffado.config.bypassdbus:
1094             rows = 2
1095             cols = 2
1096         else:
1097             rows = self.interface.getRowCount()
1098             cols = self.interface.getColCount()
1099         matrixSaveString = []
1100         matrixSaveString.append('%s  <row_number>\n' % indent)
1101         matrixSaveString.append('%s    %d\n' % (indent, rows))       
1102         matrixSaveString.append('%s  </row_number>\n' % indent)
1103         matrixSaveString.append('%s  <col_number>\n' % indent)
1104         matrixSaveString.append('%s    %d\n' % (indent, cols))       
1105         matrixSaveString.append('%s  </col_number>\n' % indent)
1106         matrixSaveString.append('%s  <coefficients>\n' % indent)
1107         for i in range(rows):
1108             line = '%s    ' % indent
1109             for j in range(cols):
1110                 line += '%d ' % self.interface.getValue(i,j)
1111             line += '\n'
1112             matrixSaveString.append(line)       
1113         matrixSaveString.append('%s  </coefficients>\n' % indent)
1114
1115         return matrixSaveString
1116
1117     def readSettings(self, readMatrixString, transpose_coeff):
1118         if ffado.config.bypassdbus:
1119             rows = 2
1120             cols = 2
1121         else:
1122             rows = self.interface.getRowCount()
1123             cols = self.interface.getColCount()
1124         if readMatrixString[0].find("<row_number>") == -1:
1125             log.debug("Number of matrix rows must be specified")
1126             return False
1127         if readMatrixString[2].find("</row_number>") == -1:
1128             log.debug("Non-conformal xml file")
1129             return False
1130         n_rows = int(readMatrixString[1])
1131
1132         if readMatrixString[3].find("<col_number>") == -1:
1133             log.debug("Number of matrix columns must be specified")
1134             return False
1135         if readMatrixString[5].find("</col_number>") == -1:
1136             log.debug("Non-conformal xml file")
1137             return False
1138         n_cols = int(readMatrixString[4])
1139
1140         if transpose_coeff:
1141             if n_rows > cols:
1142                 n_rows = cols
1143             if n_cols > rows:
1144                 n_cols = rows
1145         else:
1146             if n_rows > rows:
1147                 n_rows = rows
1148             if n_cols > cols:
1149                 n_cols = cols
1150         log.debug("Setting %d rows and %d columns coefficients" % (n_rows, n_cols))
1151
1152         try:
1153             idxb = readMatrixString.index('<coefficients>')
1154             idxe = readMatrixString.index('</coefficients>')
1155         except Exception:
1156             log.debug("No mixer matrix coefficients specified")
1157             idxb = -1
1158             idxe = -1
1159         if idxb >= 0:
1160             if idxe < idxb + n_rows + 1:
1161                 log.debug("Incoherent number of rows in coefficients")
1162                 return False
1163             i = 0
1164             for s in readMatrixString[idxb+1:idxb + n_rows + 1]:
1165                 coeffs = s.split()
1166                 if len(coeffs) < n_cols:
1167                     log.debug("Incoherent number of columns in coefficients")
1168                     return False
1169                 j = 0
1170                 for c in coeffs[0:n_cols]:
1171                     if transpose_coeff:
1172                         self.interface.setValue(j, i, int(c))
1173                     else:
1174                         self.interface.setValue(i, j, int(c))
1175                     j += 1
1176                 i += 1
1177                 del coeffs
1178
1179         self.refreshValues()
1180         return True
1181
1182 from functools import partial
1183
1184 class MatrixMixer(QWidget):
1185     def __init__(self, servername, basepath, parent=None, rule="Columns_are_inputs", sliderMaxValue=-1, mutespath=None, invertspath=None, smallFont=False, taborientation=QTabWidget.West, tabshape=QTabWidget.Triangular):
1186         QWidget.__init__(self, parent)
1187         self.servername = servername
1188         self.basepath = basepath
1189         self.sliderMaxValue = sliderMaxValue
1190         self.mutespath = mutespath
1191         self.invertspath = invertspath
1192         self.smallFont = smallFont
1193
1194         self.layout = QVBoxLayout(self)
1195         self.setLayout(self.layout)
1196
1197         # Mixer view Tool bar
1198         mxv_set = QToolBar("View settings", self)
1199
1200         # Here is a hack; the first action button appears to behaves strangely,
1201         # possibly a PyQt bug (or an unsufficient fair implementation of it)
1202         # Feel free to remove the next three lines at a time in the future
1203         hack = QAction(" ", mxv_set)
1204         hack.setDisabled(True)
1205         mxv_set.addAction(hack)
1206
1207         transpose_matrix = QAction("Transpose", mxv_set)
1208         self.transpose = False
1209         transpose_matrix.setShortcut('Ctrl+T')
1210         transpose_matrix.setToolTip("Invert rows and columns in Matrix view")
1211         mxv_set.addAction(transpose_matrix)
1212         transpose_matrix.triggered.connect(self.transposeMatrixView)
1213         mxv_set.addSeparator()
1214
1215         self.hide_matrix = QAction("Hide matrix", mxv_set)
1216         self.hide_matrix_bool = False
1217         mxv_set.addAction(self.hide_matrix)
1218         self.hide_matrix.triggered.connect(self.hideMatrixView)
1219         mxv_set.addSeparator()
1220
1221         self.hide_per_output = QAction("Hide per Output", mxv_set)
1222         self.hide_per_output_bool = False
1223         mxv_set.addAction(self.hide_per_output)
1224         self.hide_per_output.triggered.connect(self.hidePerOutputView)
1225         mxv_set.addSeparator()
1226
1227         self.use_short_names = QAction("Short names", mxv_set)
1228         self.short_names_bool = False
1229         mxv_set.addAction(self.use_short_names)
1230         self.use_short_names.setToolTip("Use short or full names for input and output channels")
1231         self.use_short_names.triggered.connect(self.shortChannelNames)
1232         mxv_set.addSeparator()
1233
1234         font_switch_lbl = QLabel(mxv_set)
1235         font_switch_lbl.setText("Font size ")
1236         mxv_set.addWidget(font_switch_lbl)
1237         font_switch = QComboBox(mxv_set)
1238         font_switch.setToolTip("Labels font size")
1239         font = font_switch.font()
1240         for i in range(10):
1241             font_switch.addItem(" %d " % (font.pointSize()+4-i))
1242         font_switch.setCurrentIndex(font_switch.findText(" %d " % font.pointSize()))
1243         mxv_set.addWidget(font_switch)
1244         mxv_set.addSeparator()
1245         font_switch.activated.connect(self.changeFontSize)
1246
1247         self.layout.addWidget(mxv_set)
1248         self.mxv_set = mxv_set
1249
1250         # First tab is for matrix view
1251         # Next are for "per Out" view
1252         self.tabs = QTabWidget(self)
1253         self.tabs.setTabPosition(taborientation)
1254         self.tabs.setTabShape(tabshape)
1255         self.layout.addWidget(self.tabs)
1256
1257         # Inputs/Outputs versus rows/columns rule
1258         self.rule = rule
1259
1260         # Matrix view tab
1261         if (rule == "Columns_are_inputs"):
1262             self.matrix = MatrixControlView(servername, basepath, self, sliderMaxValue, mutespath, invertspath, smallFont, self.short_names_bool, "In", "Out", self.transpose)
1263         else:
1264             self.matrix = MatrixControlView(servername, basepath, self, sliderMaxValue, mutespath, invertspath, smallFont, self.short_names_bool, "Out", "In", self.transpose)
1265         self.matrix.valueChanged.connect(self.matrixControlChanged)
1266
1267         self.scrollarea_matrix = QScrollArea(self.tabs)
1268         self.scrollarea_matrix.setWidgetResizable(True)
1269         self.scrollarea_matrix.setWidget(self.matrix)
1270         self.tabs.addTab(self.scrollarea_matrix, " Matrix ")
1271
1272         # Add stereo/mono output choice in tool bar
1273         if (rule == "Columns_are_inputs"):
1274             if (self.transpose):
1275                 nb_out_mono = self.matrix.cols
1276             else:
1277                 nb_out_mono = self.matrix.rows
1278         else:
1279             if (self.transpose):
1280                 nb_out_mono = self.matrix.rows
1281             else:
1282                 nb_out_mono = self.matrix.cols
1283
1284         stereo_switch_lbl = QLabel(mxv_set)
1285         stereo_switch_lbl.setText("Stereo: ")
1286         mxv_set.addWidget(stereo_switch_lbl)
1287
1288         self.stereo_channels = []
1289
1290         self.stereo_switch = []
1291         for i in range(int(nb_out_mono/2)):
1292             stereo_switch = QPushButton("%d+%d" % (2*i+1, 2*i+2), mxv_set)
1293             stereo_switch.setToolTip("Set these output channels as stereo")
1294             stereo_switch.setCheckable(True)
1295             stereo_switch.clicked.connect(partial(self.switchStereoChannel, i))
1296             stereo_switch.setMinimumSize(stereo_switch_lbl.fontMetrics().boundingRect("%d+%d" % (nb_out_mono, nb_out_mono)).size()*1.05)
1297             stereo_switch.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
1298             stereo_switch.is_stereo = False
1299             mxv_set.addWidget(stereo_switch)
1300             self.stereo_switch.append(stereo_switch)
1301         mxv_set.addSeparator()
1302
1303         # Per out view tabs
1304         self.perOut = SliderControlView(self, servername, basepath, rule, self.short_names_bool, "In", "Out", self.stereo_channels)
1305         self.perOut.valueChanged.connect(self.sliderControlChanged)
1306         for i in range(self.perOut.nbOut):
1307             self.perOut.out[i].scrollarea = QScrollArea(self.tabs)
1308             self.perOut.out[i].scrollarea.setWidgetResizable(True)
1309             self.perOut.out[i].scrollarea.setWidget(self.perOut.out[i])
1310             self.tabs.addTab(self.perOut.out[i].scrollarea, " %s " % self.perOut.out[i].outname)
1311
1312     def transposeMatrixView(self):
1313         self.transpose = not(self.transpose)
1314         self.tabs.removeTab(0)
1315         self.scrollarea_matrix.destroy()
1316         if (self.rule == "Columns_are_inputs"):
1317             self.matrix = MatrixControlView(self.servername, self.basepath, self, self.sliderMaxValue, self.mutespath, self.invertspath, self.smallFont, self.short_names_bool, "In", "Out", self.transpose)
1318         else:
1319             self.matrix = MatrixControlView(self.servername, self.basepath, self, self.sliderMaxValue, self.mutespath, self.invertspath, self.smallFont, self.short_names_bool, "Out", "In", self.transpose)
1320         self.matrix.valueChanged.connect(self.matrixControlChanged)
1321
1322         self.scrollarea_matrix = QScrollArea(self.tabs)
1323         self.scrollarea_matrix.setWidgetResizable(True)
1324         self.scrollarea_matrix.setWidget(self.matrix)
1325         self.tabs.insertTab(0, self.scrollarea_matrix, "Matrix")
1326         self.tabs.setCurrentIndex(0)
1327        
1328     def hideMatrixView(self):
1329         self.hide_matrix_bool = not(self.hide_matrix_bool)
1330         if (self.hide_matrix_bool):
1331             self.tabs.removeTab(0)
1332             self.hide_matrix.setText("Show Matrix")
1333         else:
1334             self.tabs.insertTab(0, self.scrollarea_matrix, "Matrix")
1335             self.tabs.setCurrentIndex(0)
1336             self.hide_matrix.setText("Hide Matrix")
1337            
1338     def hidePerOutputView(self):
1339         self.hide_per_output_bool = not(self.hide_per_output_bool)
1340         if (self.hide_per_output_bool):
1341             index_0 = 1
1342             if (self.hide_matrix_bool):
1343                 index_0 = 0
1344             for i in range(self.perOut.nbOut):
1345                 self.tabs.removeTab(index_0)
1346             self.hide_per_output.setText("Show per Output")
1347         else:
1348             for i in range(self.perOut.nbOut):
1349                 self.tabs.insertTab(i+1, self.perOut.out[i].scrollarea, " %s " % self.perOut.out[i].outname)
1350             self.hide_per_output.setText("Hide per Output")
1351
1352     # Font size for channel names
1353     def changeFontSize(self, size):
1354         font = self.mxv_set.font()
1355         font.setPointSize(int(size))
1356         self.mxv_set.setFont(font)
1357
1358         font = self.tabs.font()
1359         font.setPointSize(int(size))
1360         self.tabs.setFont(font)
1361
1362         font = self.matrix.font()
1363         font.setPointSize(int(size))
1364         self.matrix.setFont(font)
1365
1366         font = self.perOut.font()
1367         font.setPointSize(int(size))
1368         self.perOut.setFont(font)
1369
1370         for i in range(self.perOut.nbOut):
1371             for j in range(self.perOut.nbIn):
1372                 self.perOut.out[i].svl[j].sliderSetMinimalDim()
1373
1374     # Allows long name for Mixer/Out and /In to be hidden
1375     def shortChannelNames(self):
1376         checked = not(self.short_names_bool)
1377         if (self.matrix.cols > 1):
1378             for i in range(self.matrix.cols):
1379                 self.matrix.columnHeaders[i].name = self.matrix.getColName(i, checked)
1380                 self.matrix.columnHeaders[i].lbl.setText(self.matrix.columnHeaders[i].name)
1381
1382         if (self.matrix.rows > 1):
1383             for j in range(self.matrix.rows):
1384                 self.matrix.rowHeaders[j].name = self.matrix.getRowName(j, checked)
1385                 self.matrix.rowHeaders[j].lbl.setText(self.matrix.rowHeaders[j].name)       
1386
1387         for i in range(self.perOut.nbOut):
1388             if (self.perOut.nbOut > 1):
1389                 self.perOut.out[i].lbl[0].setText(self.perOut.getOutName(i, checked))
1390             if (self.perOut.nbIn > 1):
1391                 for j in range(self.perOut.nbIn):
1392                     self.perOut.out[i].lbl[j+1].setText(self.perOut.getInName(j, checked))
1393
1394         # Care for hidden columns
1395         if (self.matrix.cols > 1):
1396             for i in self.matrix.hiddenCols:
1397                 self.matrix.columnHeaders[i].lbl.setText("%d" % (i+1))
1398         # Care for hidden rows
1399         if (self.matrix.rows > 1):
1400             for j in self.matrix.hiddenRows:
1401                 self.matrix.rowHeaders[j].lbl.setText("%d" % (j+1))
1402
1403         self.short_names_bool = checked
1404         if (self.short_names_bool):
1405             self.use_short_names.setText("Long names")
1406         else:
1407             self.use_short_names.setText("Short names")
1408
1409     # Sliders value change
1410     #   Care that some recursive process is involved and only stop when exactly same values are involved
1411     # Matrix view
1412     def matrixControlChanged(self, n):
1413         # Update value needed for "per Out" view
1414         #log.debug("Update per Output( %s )" % str(n))
1415         nbitems = len(n) // 3
1416         if (self.rule == "Columns_are_inputs"):
1417            n_t = n
1418         else:
1419             n_t = ()
1420             for i in range(nbitems):
1421                 n_t += (n[3*i+1], n[3*i], n[3*i+2])
1422
1423         self.perOut.updateValues(n_t)
1424
1425     # "per Out" view
1426     def sliderControlChanged(self, n):
1427         # Update value needed for matrix view
1428         #log.debug("Update Matrix( %s )" % str(n))
1429         nbitems = len(n) // 3
1430         if (((self.rule == "Columns_are_inputs") and not self.transpose) or ((self.rule != "Columns_are_inputs") and self.transpose)):
1431             n_t = ()
1432             for i in range(nbitems):
1433                 n_t += (n[3*i+1], n[3*i], n[3*i+2])
1434         else:
1435             n_t = n
1436
1437         self.matrix.updateValues(n_t)
1438
1439     def refreshValues(self):
1440         # Refreshing matrix coefficient should be sufficient,
1441         #  propagating the changes to perOut view
1442         self.matrix.refreshValues()
1443
1444     def switchStereoChannel(self, channel, is_stereo):
1445         #log.debug(" switching channels %d+%d to stereo/mono" % (2*channel, 2*channel+1))
1446         self.stereo_switch[channel].is_stereo = self.stereo_switch[channel].isChecked();
1447         if (self.stereo_switch[channel].is_stereo):
1448             self.stereo_channels.append(2*channel)
1449         else:
1450             self.stereo_channels.remove(2*channel)
1451
1452         # tab 0 is for matrix except if it is hidden
1453         index_0 = 1
1454         if (self.hide_matrix_bool):
1455             index_0 = 0
1456         for i in range(self.perOut.nbOut):
1457             self.tabs.removeTab(index_0)
1458         self.perOut.destroy()
1459         self.perOut = SliderControlView(self, self.servername, self.basepath, self.rule, self.short_names_bool, "In", "Out", self.stereo_channels)
1460         self.perOut.valueChanged.connect(self.sliderControlChanged)
1461         current = 0
1462         for i in range(self.perOut.nbOut):
1463             self.perOut.out[i].scrollarea = QScrollArea(self.tabs)
1464             self.perOut.out[i].scrollarea.setWidgetResizable(True)
1465             self.perOut.out[i].scrollarea.setWidget(self.perOut.out[i])
1466             self.tabs.addTab(self.perOut.out[i].scrollarea, " %s " % self.perOut.out[i].outname)
1467             if self.perOut.out[i].out_1 == 2*channel:
1468                 current = i
1469
1470         self.tabs.setCurrentWidget(self.perOut.out[current].scrollarea)
1471
1472     # Update when routing is modified
1473     def updateRouting(self):
1474         self.matrix.updateRouting()
1475         self.perOut.updateRouting()
1476        
1477     def saveSettings(self, indent):
1478         mixerString = []
1479         mixerString.append("%s<matrices>\n" % indent)
1480         mixerString.extend(self.matrix.saveSettings(indent))
1481         mixerString.append("%s</matrices>\n" % indent)
1482         mixerString.append("%s<stereo_outputs>\n" % indent)
1483         mixerString.append("%s  <number>\n" % indent)
1484         n = len(self.stereo_channels)
1485         mixerString.append("%s    %d\n" % (indent, n))
1486         mixerString.append("%s  </number>\n" % indent)
1487         if n > 0:
1488             mixerString.append("%s  <channels>\n" % indent)
1489             for i in self.stereo_channels:
1490                 mixerString.append("%s    %d %d\n" % (indent, i+1, i+2))
1491             mixerString.append("%s  </channels>\n" % indent)
1492         mixerString.append("%s</stereo_outputs>\n" % indent)
1493         return mixerString
1494
1495     def readSettings(self, readMixerString, transpose_coeff):
1496         try:
1497             idxb = readMixerString.index('<matrices>')
1498             idxe = readMixerString.index('</matrices>')
1499         except Exception:
1500             log.debug("No matrices found")
1501             idxb = -1
1502             idxe = -1
1503         if idxb >= 0:
1504             if idxe > idxb+1:
1505                 readString = []
1506                 for s in readMixerString[idxb+1:idxe]:
1507                     readString.append(s)
1508                 if self.matrix.readSettings(readString, transpose_coeff):
1509                     log.debug("Mixer matrices settings modified")
1510                 del readString
1511         try:
1512             idx = readMixerString.index('<stereo_outputs>')
1513         except Exception:
1514             log.debug("No stereo outputs channels information found")
1515             idx = -1
1516         if idx >= 0:
1517             if readMixerString[idx+1].find('<number>') == -1:
1518                 log.debug("Number of stereo output channels must be specified")
1519                 return False
1520             n = int(readMixerString[idx+2])
1521             if n > self.perOut.nbOut // 2:
1522                 log.debug("Incoherent number of stereo channels")
1523                 return False
1524             if n > 0:
1525                 if readMixerString[idx+3].find('</number>') == -1:
1526                     log.debug("No </number> tag found")
1527                     return False
1528                 if readMixerString[idx+4].find('<channels>') == -1:
1529                     log.debug("No <channels> tag found")
1530                     return False
1531                 for s in readMixerString[idx+5:idx+5+n]:
1532                     i = (int(s.split()[0]) - 1)/2
1533                     self.stereo_switch[i].setChecked(True);
1534                     self.switchStereoChannel(i, True)
1535         return True
1536                    
1537 #
1538 # vim: et ts=4 sw=4 fileencoding=utf8
Note: See TracBrowser for help on using the browser.