290 lines
11 KiB
Python
290 lines
11 KiB
Python
|
#!/usr/bin/env python
|
||
|
|
||
|
# Copyright (C) 2010 Alex Yatskov
|
||
|
#
|
||
|
# This program 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 3 of the License, or
|
||
|
# (at your option) any later version.
|
||
|
#
|
||
|
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
import os
|
||
|
import sys
|
||
|
import copy
|
||
|
import pygame
|
||
|
from PyQt4 import QtGui
|
||
|
from PyQt4 import QtCore
|
||
|
from PyQt4 import QtXml
|
||
|
import ankiqt
|
||
|
|
||
|
|
||
|
class JoyDialogCapture(QtGui.QDialog):
|
||
|
def __init__(self, parent, action):
|
||
|
QtGui.QDialog.__init__(self, parent)
|
||
|
|
||
|
self.setWindowTitle('Button capture')
|
||
|
self.resize(300, 90)
|
||
|
self.setResult(-1)
|
||
|
|
||
|
self.label = QtGui.QLabel(self)
|
||
|
self.label.setWordWrap(True)
|
||
|
self.label.setText('Press the gamepad or joystick button you wish to capture for action: %s...' % action)
|
||
|
|
||
|
self.buttonBox = QtGui.QDialogButtonBox(self)
|
||
|
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
|
||
|
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel)
|
||
|
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL('accepted()'), self.accept)
|
||
|
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL('rejected()'), self.reject)
|
||
|
|
||
|
self.verticalLayout = QtGui.QVBoxLayout(self)
|
||
|
self.verticalLayout.setSizeConstraint(QtGui.QLayout.SetFixedSize)
|
||
|
self.verticalLayout.addWidget(self.label)
|
||
|
self.verticalLayout.addWidget(self.buttonBox)
|
||
|
|
||
|
|
||
|
def onButton(self, value):
|
||
|
self.done(value)
|
||
|
|
||
|
|
||
|
class JoyDialogOptions(QtGui.QDialog):
|
||
|
def __init__(self, parent, plugin):
|
||
|
QtGui.QDialog.__init__(self, parent)
|
||
|
self.plugin = plugin
|
||
|
|
||
|
self.setWindowTitle('Gamepad settings')
|
||
|
self.resize(400, 150)
|
||
|
|
||
|
self.groupBox = QtGui.QGroupBox(self)
|
||
|
self.groupBox.setTitle('Actions')
|
||
|
|
||
|
self.lineEditId = QtGui.QLineEdit(self.groupBox)
|
||
|
self.lineEditId.setReadOnly(True)
|
||
|
|
||
|
self.comboBoxActions = QtGui.QComboBox(self.groupBox)
|
||
|
for button in self.plugin.buttonMgr.buttons:
|
||
|
self.comboBoxActions.addItem(button.name)
|
||
|
QtCore.QObject.connect(self.comboBoxActions, QtCore.SIGNAL('currentIndexChanged(const QString&)'), self.onActionChanged)
|
||
|
|
||
|
self.pushButtonCapture = QtGui.QPushButton(self.groupBox)
|
||
|
self.pushButtonCapture.setText('Capture...')
|
||
|
QtCore.QObject.connect(self.pushButtonCapture, QtCore.SIGNAL('clicked()'), self.onCapture)
|
||
|
|
||
|
self.checkBoxEnable = QtGui.QCheckBox(self.groupBox)
|
||
|
self.checkBoxEnable.setText('Map to button')
|
||
|
QtCore.QObject.connect(self.checkBoxEnable, QtCore.SIGNAL('stateChanged(int)'), self.onEnableChanged)
|
||
|
|
||
|
self.buttonBox = QtGui.QDialogButtonBox(self)
|
||
|
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok)
|
||
|
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL('accepted()'), self.accept)
|
||
|
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL('rejected()'), self.reject)
|
||
|
|
||
|
self.gridLayout = QtGui.QGridLayout(self.groupBox)
|
||
|
self.gridLayout.addWidget(self.comboBoxActions, 0, 0, 1, 3)
|
||
|
self.gridLayout.addWidget(self.checkBoxEnable, 1, 0, 1, 1)
|
||
|
self.gridLayout.addWidget(self.lineEditId, 1, 1, 1, 1)
|
||
|
self.gridLayout.addWidget(self.pushButtonCapture, 1, 2, 1, 1)
|
||
|
|
||
|
self.verticalLayout = QtGui.QVBoxLayout(self)
|
||
|
self.verticalLayout.addWidget(self.groupBox)
|
||
|
self.verticalLayout.addItem(QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding))
|
||
|
self.verticalLayout.addWidget(self.buttonBox)
|
||
|
|
||
|
self.onActionChanged(self.comboBoxActions.currentText())
|
||
|
self.onEnableChanged(self.checkBoxEnable.checkState())
|
||
|
|
||
|
|
||
|
def onCapture(self):
|
||
|
dialog = JoyDialogCapture(self, self.comboBoxActions.currentText())
|
||
|
self.plugin.handlers.append(dialog.onButton)
|
||
|
value = dialog.exec_()
|
||
|
self.plugin.handlers.remove(dialog.onButton)
|
||
|
if value >= 0:
|
||
|
name = self.comboBoxActions.currentText()
|
||
|
button = self.plugin.buttonMgr.findButtonByName(name)
|
||
|
button.value = value
|
||
|
self.onActionChanged(name)
|
||
|
|
||
|
|
||
|
def onActionChanged(self, name):
|
||
|
button = self.plugin.buttonMgr.findButtonByName(name)
|
||
|
self.checkBoxEnable.setChecked(button.enabled)
|
||
|
self.lineEditId.setText(str(button.value))
|
||
|
|
||
|
|
||
|
def onEnableChanged(self, state):
|
||
|
enabled = state == QtCore.Qt.Checked
|
||
|
self.lineEditId.setEnabled(enabled)
|
||
|
self.pushButtonCapture.setEnabled(enabled)
|
||
|
name = self.comboBoxActions.currentText()
|
||
|
button = self.plugin.buttonMgr.findButtonByName(name)
|
||
|
button.enabled = enabled
|
||
|
|
||
|
|
||
|
class JoyButtonMap:
|
||
|
def __init__(self, name, value, enabled, state, callback):
|
||
|
self.name = name
|
||
|
self.value = value
|
||
|
self.enabled = enabled
|
||
|
self.state = state
|
||
|
self.callback = callback
|
||
|
|
||
|
|
||
|
class JoyButtonMapManager:
|
||
|
def __init__(self):
|
||
|
mainWin = ankiqt.mw.mainWin
|
||
|
self.buttons = [
|
||
|
JoyButtonMap('Answer 1', -1, False, "showAnswer", lambda: self.clickButton(mainWin.easeButton1)),
|
||
|
JoyButtonMap('Answer 2', -1, False, "showAnswer", lambda: self.clickButton(mainWin.easeButton2)),
|
||
|
JoyButtonMap('Answer 3', -1, False, "showAnswer", lambda: self.clickButton(mainWin.easeButton3)),
|
||
|
JoyButtonMap('Answer 4', -1, False, "showAnswer", lambda: self.clickButton(mainWin.easeButton4)),
|
||
|
JoyButtonMap('Answer Default', -1, False, "showAnswer", lambda: self.clickButton([mainWin.easeButton1, mainWin.easeButton2, mainWin.easeButton3, mainWin.easeButton4][ankiqt.mw.defaultEaseButton() - 1])),
|
||
|
JoyButtonMap('Answer Incorrect', -1, False, "showAnswer", lambda: self.clickButton(mainWin.easeButton1)),
|
||
|
JoyButtonMap('Bury Card', -1, False, None, lambda: self.triggerAction(mainWin.actionBuryFact)),
|
||
|
JoyButtonMap('Mark Card', -1, False, None, lambda: self.triggerAction(mainWin.actionMarkCard)),
|
||
|
JoyButtonMap('Repeat Audio', -1, False, None, lambda: self.triggerAction(mainWin.actionRepeatAudio)),
|
||
|
JoyButtonMap('Show Answer', -1, False, "showQuestion", lambda: self.clickButton(mainWin.showAnswerButton)),
|
||
|
JoyButtonMap('Suspend Card', -1, False, None, lambda: self.triggerAction(mainWin.actionSuspendCard)),
|
||
|
JoyButtonMap('Undo', -1, False, None, lambda: self.triggerAction(mainWin.actionUndo))
|
||
|
]
|
||
|
|
||
|
|
||
|
def findButtonByName(self, name):
|
||
|
for button in self.buttons:
|
||
|
if button.name == name:
|
||
|
return button
|
||
|
|
||
|
|
||
|
def findButtonsByValue(self, value):
|
||
|
return [button for button in self.buttons if button.value == value]
|
||
|
|
||
|
|
||
|
def handleButton(self, value):
|
||
|
buttons = self.findButtonsByValue(value)
|
||
|
for button in buttons:
|
||
|
if button.enabled and (button.state == None or button.state == ankiqt.mw.state):
|
||
|
button.callback()
|
||
|
|
||
|
|
||
|
def clickButton(self, button):
|
||
|
if button.isEnabled():
|
||
|
button.animateClick()
|
||
|
|
||
|
|
||
|
def triggerAction(self, action):
|
||
|
if action.isEnabled():
|
||
|
action.trigger()
|
||
|
|
||
|
|
||
|
def loadSettings(self):
|
||
|
try:
|
||
|
fileXml = open(self.getFilename(), 'r')
|
||
|
textXml = fileXml.read()
|
||
|
fileXml.close()
|
||
|
except IOError:
|
||
|
return False
|
||
|
|
||
|
document = QtXml.QDomDocument()
|
||
|
if not document.setContent(textXml):
|
||
|
return False
|
||
|
|
||
|
root = document.documentElement()
|
||
|
if root.tagName() != 'settings':
|
||
|
return False
|
||
|
|
||
|
buttonElements = root.elementsByTagName('button')
|
||
|
if buttonElements != None:
|
||
|
for i in xrange(0, len(buttonElements)):
|
||
|
buttonElement = buttonElements.at(i).toElement()
|
||
|
button = self.findButtonByName(buttonElement.attribute('name'))
|
||
|
if button != None:
|
||
|
button.value = int(buttonElement.attribute('value'))
|
||
|
button.enabled = buttonElement.attribute('enabled') == 'True'
|
||
|
|
||
|
return True
|
||
|
|
||
|
|
||
|
def saveSettings(self):
|
||
|
document = QtXml.QDomDocument()
|
||
|
|
||
|
rootElement = document.createElement('settings')
|
||
|
document.appendChild(rootElement)
|
||
|
|
||
|
for button in self.buttons:
|
||
|
buttonElement = document.createElement('button')
|
||
|
buttonElement.setAttribute('name', button.name)
|
||
|
buttonElement.setAttribute('value', str(button.value))
|
||
|
buttonElement.setAttribute('enabled', str(button.enabled))
|
||
|
rootElement.appendChild(buttonElement)
|
||
|
|
||
|
textXml = document.toString(4)
|
||
|
|
||
|
try:
|
||
|
fileXml = open(self.getFilename(), 'w')
|
||
|
fileXml.write(textXml)
|
||
|
fileXml.close()
|
||
|
except IOError:
|
||
|
return False
|
||
|
else:
|
||
|
return True
|
||
|
|
||
|
|
||
|
def getFilename(self):
|
||
|
return os.path.splitext(__file__)[0] + '.xml'
|
||
|
|
||
|
|
||
|
class JoyPlugin:
|
||
|
def __init__(self):
|
||
|
pygame.init()
|
||
|
|
||
|
self.handlers = []
|
||
|
self.joysticks = []
|
||
|
for i in xrange(pygame.joystick.get_count()):
|
||
|
joystick = pygame.joystick.Joystick(i)
|
||
|
joystick.init()
|
||
|
self.joysticks.append(joystick)
|
||
|
|
||
|
self.buttonMgr = JoyButtonMapManager()
|
||
|
self.buttonMgr.loadSettings()
|
||
|
|
||
|
action = QtGui.QAction(ankiqt.mw)
|
||
|
action.setText('&Joypad Options...')
|
||
|
QtCore.QObject.connect(action, QtCore.SIGNAL('triggered()'), self.onOptions)
|
||
|
ankiqt.mw.mainWin.menu_Settings.insertAction(ankiqt.mw.mainWin.actionStudyOptions, action)
|
||
|
|
||
|
self.timer = QtCore.QTimer()
|
||
|
QtCore.QObject.connect(self.timer, QtCore.SIGNAL('timeout()'), self.onTimer)
|
||
|
self.timer.start(50)
|
||
|
|
||
|
|
||
|
def onTimer(self):
|
||
|
for event in pygame.event.get():
|
||
|
if event.type == pygame.JOYBUTTONDOWN:
|
||
|
self.onButton(event.button)
|
||
|
|
||
|
|
||
|
def onOptions(self):
|
||
|
dialog = JoyDialogOptions(ankiqt.mw, self)
|
||
|
buttonMgrOld = copy.deepcopy(self.buttonMgr)
|
||
|
if dialog.exec_() == QtGui.QDialog.Accepted:
|
||
|
self.buttonMgr.saveSettings()
|
||
|
else:
|
||
|
self.buttonMgr = buttonMgrOld
|
||
|
|
||
|
|
||
|
def onButton(self, value):
|
||
|
if len(self.handlers) > 0:
|
||
|
for handler in self.handlers:
|
||
|
handler(value)
|
||
|
else:
|
||
|
self.buttonMgr.handleButton(value)
|
||
|
|
||
|
|
||
|
plugin = JoyPlugin()
|