#!/usr/bin/python

#   Copyright (C) 2002-2003 Yannick Gingras <ygingras@ygingras.net>
#   Copyright (C) 2002-2003 Vincent Barbin <vbarbin@openbeatbox.org>

#   This file is part of Open Beat Box.

#   Open Beat Box is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.

#   Open Beat Box is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.

#   You should have received a copy of the GNU General Public License
#   along with Open Beat Box; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


from qt import *
from OBBFuncts    import *
from PixmapSet    import *

from OBBGui       import OBBGui
from Floater      import Floater
from PushButton   import PushButton
from OBBLight     import OBBLight
from OBBLabel     import OBBLabel
from OBBSpinBox   import OBBSpinBox
from OBBSlider    import OBBSlider
from HitButton    import HitButton
from StretchFrame import StretchFrame

import OBBUtils.OBBSignals as sigs

from pprint import pprint

import config
import os

ROW_PER_BAR = 2
BUT_PER_GRP = 4


class PatEditorGui(OBBGui):
    def __init__(self, skin, parent=None):
        OBBGui.__init__(self, parent)
        self.skin = skin
        self._lambdas    = []
        self._hits       = []
        self._balSliders = []

        self.initHitHolders()
        self.createControlBar()


    def show(self):
        # place everything at the right position
        floaters = self.getFloaters()
        controlBar = floaters[-1][0]
        controlBar.move( (qApp.desktop().width() - controlBar.width())/2, 10)

        instrRack = floaters[0][0]
        instrRack.move( (qApp.desktop().width() - controlBar.width())/2,
                        controlBar.height() + 5 )
        
        OBBGui.show(self)


    def createInstrHolder(self):
        instrRack = Floater()
        pixSet   = self.skin.pixmapSets["vholderPixmaps"]
        vholder  = StretchFrame(pixSet, instrRack, 0, 0, "y", 200, 10, 620)
        self.addFloater(instrRack)
        return instrRack
    
        
    def createInstrBar(self, instrId, instrRack):
        yPos = instrId*73+15
        pixSet = self.skin.pixmapSets["hitHolderPixmaps"]
        instrBar = StretchFrame(pixSet, instrRack, 25, yPos, "x", 100, 20, 720)
        return instrBar


    def addHitButton(self, instrBar, x, y, instrId, butId):
        params = {"parent": instrBar, "x":x, "y":y}
        button = self.skin.createWidget(HitButton, "hitButton", params)
        self._hits[instrId].append(button)
        funct = lambda iId=instrId, bId=butId: \
                self.emit(sigs.hitToggled, (iId, bId))
        self._lambdas.append(funct)
        self.connect(button, PYSIGNAL("clicked()"), funct)


    def addHitButtonGroup( self, instrId, instrBar, first, xBeg, y, rowID):
        for i in range(first, first+BUT_PER_GRP):
            x = (i-config.HIT_PER_CYCLE*(rowID)/ROW_PER_BAR)*58+xBeg
            self.addHitButton(instrBar, x, y, instrId, i)


    def addHitButtons(self, instrId, instrBar):
        for i in range(ROW_PER_BAR):
            butId = i*8
            y = (i*3+1)*9
            x = 205-(i*30)
            # TODO : compute the 2nd group from BUT_PER_GRP
            self.addHitButtonGroup( instrId, instrBar, butId,   x,    y, i)
            self.addHitButtonGroup( instrId, instrBar, butId+4, x+15, y, i)


    def initHitHolders(self):
        instrRack = self.createInstrHolder()
        for instrId in range(config.INSTR_NUMBER):
            self._hits.append([])
            instrBar = self.createInstrBar(instrId, instrRack)

            params = {"parent":instrBar, "x":116, "y":44}
            led = self.skin.createWidget(OBBLight, "led", params)

            self.addHitButtons(instrId, instrBar)
            settingsBar = self.addSettingsFloater(300, instrId*73+15, instrId)
            funct = lambda b=settingsBar, l=led:self.toggleSettingBar(b, l)
            self._lambdas.append(funct)
            self.addPushButton("settings", instrBar, 80, 9, funct, 1)

            self._lambdas.append(lambda iId=instrId : self.previewInstr(iId))
            self.addPushButton("prev", instrBar, 20, 9, self._lambdas[-1], 1)
                                    
            
        instrRack.reCenter()


    def addPushButton(self, name, parent, x, y, command=None, enabled=0):
        params = {"parent":parent, "x":x, "y":y}
        button = self.skin.createWidget(PushButton, name, params)
        if command:
            self.connect(button, PYSIGNAL("clicked()"), command)
        button.enable(enabled)


    def addControlButtons(self, controlBar):
        self._lambdas.append(lambda : self.emit(sigs.quit, ()))
        self.addPushButton("pow",    controlBar,  27, 51, self._lambdas[-1], 1)
        
        self._lambdas.append(lambda : self.emit(sigs.play, ()))
        self.addPushButton("play",   controlBar,  60, 17, self._lambdas[-1], 1)
        
        self.addPushButton("rec",    controlBar,  92, 51)
        
        self._lambdas.append(lambda : self.emit(sigs.stop, ()))
        self.addPushButton("stop",   controlBar, 125, 17, self._lambdas[-1], 1)
        
        self.addPushButton("save",   controlBar, 157, 51, self.savePattern, 1)
        self.addPushButton("open",   controlBar, 189, 17, self.openPattern,  1)
        self.addPushButton("saveAs", controlBar, 221, 51, self.savePatAs, 1)
        
        self._lambdas.append(lambda : self.emit(sigs.newFile, ()))
        self.addPushButton("new",    controlBar, 253, 17, self._lambdas[-1], 1)


    def createControlBar(self):
        floater = Floater()
        controlBar = self.skin.createWidget(StretchFrame,
                                            "controlBarFrame",
                                            {"parent":floater})
        self.addControlButtons(controlBar)
        
        self.filenameLabel = self.skin.createWidget(OBBLabel,
                                                    "filenameLabel",
                                                    {"parent":controlBar})
        self.connect(self, sigs.scroll, self.filenameLabel.scroll)

        self.tempoSpiner = self.skin.createWidget(OBBSpinBox,
                                                  "tempoSpiner",
                                                  {"parent":controlBar})
        funct = lambda val:self.emit(sigs.newTempo, (val,))
        self.connect(self.tempoSpiner,
                     PYSIGNAL("valueChanged(int)"),
                     funct)
        self._lambdas.append(funct)

        self.volumeSlider = self.skin.createWidget(OBBSlider,
                                              "volumeSlider",
                                              {"parent":controlBar})
        funct = lambda val:self.emit(sigs.newPatVolume, (val,))        
        self.connect(self.volumeSlider,
                     PYSIGNAL("valueChanged(int)"),
                     funct)
        # Yup ! Both sigs on the same function
        self.connect(self.volumeSlider,
                     PYSIGNAL("valueChanging(int)"),
                     funct)
        self._lambdas.append(funct)
        self.addFloater(floater)


    def addSettingsFloater(self, x, y, instrId):
        settingsBar = Floater()
        holder = self.skin.createWidget(StretchFrame,
                                        "settingsBarFrame",
                                        {"parent":settingsBar})
        self.addFloater(settingsBar, 0)
        settingsBar.move(x, y)
        label = self.skin.createWidget(OBBLabel,
                                       "instrIdLabel",
                                       {"parent":settingsBar})
        label.setText("instr.#%d" % (instrId + 1))

        label = self.skin.createWidget(OBBLabel,
                                       "balanceLabel",
                                       {"parent":settingsBar})
        label.setText("balance :")

        balSlider = self.skin.createWidget(OBBSlider,
                                           "balanceSlider",
                                           {"parent":settingsBar})
        self._balSliders.append(balSlider)
        funct = lambda val, iId=instrId: self.emit(sigs.newInstrBal, \
                                                   (iId, val))
        self._lambdas.append(funct)
        self.connect(balSlider, PYSIGNAL("valueChanged(int)"),  funct)
        # Argh ! VBarbin is not open to change...
        #self.connect(balSlider, PYSIGNAL("valueChanging(int)"), funct)
        
        label = self.skin.createWidget(OBBLabel,
                                       "sampleNameLabel",
                                       {"parent":settingsBar})
        funct = lambda iId, file, lbl=label, wId=instrId : \
                    self.updateInstrFilename(lbl, wId, iId, file)
        self._lambdas.append(funct)
        self.connect(self, sigs.instrFileChanged, funct)

        funct = lambda iId=instrId:self.openSample(iId)
        self._lambdas.append(funct)        
        self.addPushButton("open", settingsBar,  27, 49, funct, 1)

        return settingsBar


    def setTempo(self, value):
        self.tempoSpiner.setValue(value)


    def setPatVolume(self, value):
        self.volumeSlider.setValue(value)


    def previewInstr(self, instrId):
        self.emit(sigs.previewInstr, (instrId,))


    def setInstrBalance(self, instrId, newVal):
        self._balSliders[instrId].setValue(newVal)


    def updateInstrBalance(self, widget, widgetId, updatedId, newVal):
        if widgetId == updatedId:
            widget.setValue(newVal)


    def setInstrFilename(self, instrId, newFile):
        self.emit(sigs.instrFileChanged, (instrId, newFile))


    def setPatFile(self, filename, userSelected):
        self._patFilename = (filename, userSelected)
        msg = " "*75
        msg += "working on " + filename
        self.filenameLabel.setText(msg)
                

    def updateInstrFilename(self, widget, widgetId, updatedId, newFile):
        if widgetId == updatedId:
            widget.setText(newFile)


    def toggleSettingBar(self, bar, led):
        if bar.isVisible():
            bar.hide()
            led.turnOff()
        else:
            bar.show()
            led.turnOn()


    def scroll(self):
        self.emit(sigs.scroll, ())


    def clearHits(self):
        map(lambda bar:map(lambda hit:hit.setState(DESACTIVATED), bar),
            self._hits)
            

    def resetHitProgress(self):
        map(lambda bar:map(lambda hit:hit.turnOff(), bar), self._hits)


    def openPattern(self):
        filename = QFileDialog.getOpenFileName( self._patFilename[0],
                                                "OBB Pattern (*.opt)")
        # QStrings are not false when empty (and we like pyStrings...)
        filename = unicode(filename).strip()
        
        if filename:
            self.emit(sigs.loadPattern, (filename,))


    def openSample(self, instrId):
        filename = QFileDialog.getOpenFileName( getSndDir(),
                                                "Sounds (*.wav)" )

        # QStrings are not false when empty (and we like pyStrings...)
        filename = str(filename).strip() # FIXME : this chock on unicode
        
        if filename:
            filename = os.path.join( getSndDir(),
                                     os.path.basename(filename))
            self.emit(sigs.newInstrFile, (instrId, filename))


    def toggleHit(self, instrId, hitPos):
        self._hits[instrId][hitPos].toggleState()


    def showHitPos(self, hitPos):
        prevHit = (hitPos - 1) % config.HIT_PER_CYCLE
        ids = range(config.INSTR_NUMBER)
        map(lambda i:self._hits[i][prevHit].turnOff(), ids)
        map(lambda i:self._hits[i][hitPos].turnOn(),   ids)


    def savePattern(self):
        if self._patFilename[1]:
            self.emit(sigs.savePattern, (self._patFilename[0],))
        else:
            self.savePatAs()            
            
        
    def savePatAs(self):
        filename = QFileDialog.getSaveFileName( self._patFilename[0],
                                                "OBB Pattern (*.opt)")
        # QStrings are not false when empty (and we like pyStrings...)
        filename = unicode(filename).strip()
               
        if filename:
            if filename == os.path.splitext(filename)[0]:
                filename = filename + ".opt"
            self.emit(sigs.savePattern, (filename,))

