#!/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 . 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()