seopardy/windows.py

613 lines
20 KiB
Python

from PySide import QtGui, QtCore
import copy
from gamestate import QuestionAnswers
from music import MusicBox
from video import VideoPlayer
from player import Player, ButtonEvent, nobodyColor
class QuestionWindow(QtGui.QDialog):
buzzersOpen = QtCore.Signal(bool)
playerGotQuestion = QtCore.Signal(int)
def __init__(self, players, section, qnumber, question, answers=None, dj=None, parent=None):
super(QuestionWindow, self).__init__(parent)
self.players = players
self.section = section
self.qnumber = qnumber
self.question = question
self.dj = dj
self._windowSetup = False
self._videoPlayer = None
if answers is not None:
self.answers = answers
else:
self.answers = QuestionAnswers(self.section, self.qnumber)
if not self.answers.is_answered():
if not self._question_has_audio():
MusicBox.play_music("questionSong")
if self.dj:
self.answers.set_dj_points(self.dj[1])
playerNo = self.players.index(self.dj[0])+1
QtCore.QCoreApplication.postEvent(self, ButtonEvent(playerNo))
self._setupGui()
self.setWindowTitle("Seopardy - %s - %d" % (section, qnumber*100))
self._inWindow = False
if self.question["Type"] == "Music":
tag = "%s-%s" % (self.section, self.qnumber)
MusicBox.add_music(tag, self.question["Question"])
MusicBox.play_music(tag)
elif self.question["Type"] == "Video":
self._videoPlayer.play()
def get_answers(self):
return self.answers
def closeEvent(self, event):
if not self._inWindow:
self.buzzersOpen.emit(False)
MusicBox.stop_music()
if self._videoPlayer:
self._videoPlayer.stop()
event.accept()
else:
event.ignore()
def _mkQuestionLabel(self, text, monospaced=False):
question = QtGui.QLabel(text, alignment=QtCore.Qt.AlignCenter)
question.setWordWrap(True)
extra = ""
if monospaced:
extra += "font-family: monospace;"
question.setStyleSheet("QLabel { font-size: 40px; %s }" % (extra,))
return question
def _setupGui(self):
self.layout = QtGui.QVBoxLayout()
seclabel = QtGui.QLabel(self.section)
seclabel.setStyleSheet("QLabel { font-size: 30px; }")
self.layout.addWidget(seclabel, alignment=QtCore.Qt.AlignCenter)
self.layout.addStretch()
qlabel = None
stretch = 0
if self.question["Type"] == "Text":
qlabel = self._mkQuestionLabel(self.question["Question"])
elif self.question["Type"] == "Music":
qlabel = self._mkQuestionLabel("Listen...")
elif self.question["Type"] == "Image":
qlabel = QtGui.QLabel()
pixmap = QtGui.QPixmap()
pixmap.loadFromData(open(self.question["Question"]).read())
qlabel.setPixmap(pixmap)
qlabel.setAlignment(QtCore.Qt.AlignCenter)
elif self.question["Type"] == "Code":
# to have this a) aligned but b) centered we "cheat" a bit with the spaces...
code = self.question["Question"].split("\n")
code = map(lambda x: x.replace("\t", " "), code)
maxLineLen = max(map(len, code))
code = map(lambda x: x + ((maxLineLen-len(x))*" "), code)
qlabel = self._mkQuestionLabel("<pre>"+"\n".join(code)+"</pre>", monospaced=True)
elif self.question["Type"] == "Video":
self._videoPlayer = VideoPlayer(self.question["Question"], self.question["Audio"])
qlabel = self._videoPlayer.get_widget()
stretch = 1
else:
raise ValueError("%s is an unknown type for section %s question name %s" % (self.question["Type"], self.section, self.question["Name"]))
self.layout.addWidget(qlabel, stretch=stretch)
self.layout.addStretch()
self.setLayout(self.layout)
def event(self, e):
if e.type() == ButtonEvent.eventType:
if e.get_playerno() <= len(self.players) and not self._inWindow:
done = False
if self.question["Type"] == "Music":
MusicBox.stop_music()
elif self.question["Type"] == "Video":
self._videoPlayer.pause()
self._inWindow = True
player = self.players[e.get_playerno()-1]
qawin = QuestionAnswerWindow(player, self)
self.playerGotQuestion.emit(e.get_playerno())
res = qawin.exec_()
if res == QuestionAnswerWindow.CORRECT:
self.answers.add_try(player, correct=True)
self._inWindow = False
done = True
self.close()
elif res == QuestionAnswerWindow.WRONG:
self.answers.add_try(player, correct=False)
self._inWindow = False
if not done:
self.buzzersOpen.emit(True)
if self.question["Type"] == "Music":
# resume music if question was not answered
MusicBox.play_music("%s-%s" % (self.section, self.qnumber))
elif self.question["Type"] == "Video":
# resume video if question was not answered
self._videoPlayer.play()
elif e.get_playerno() > len(self.players) and not self._inWindow:
# unknown player! to not confuse certain devices we send a buttons open event
self.buzzersOpen.emit(True)
print("unknown player", e.get_playerno())
return True
else:
ret = super(QuestionWindow, self).event(e)
# we want to do this after the window has focus for the first time
if not self._windowSetup and e.type() == QtCore.QEvent.FocusIn:
if not self.answers.is_answered():
self.buzzersOpen.emit(True)
self._windowSetup = True
return ret
def keyPressEvent(self, e):
if e.key() == QtCore.Qt.Key_Escape:
if not self.answers.is_answered():
self.answers.nobody_knew()
self.close()
elif not self.answers.is_answered() and e.key() >= ord('1') and e.key() <= ord(str(len(self.players))):
QtCore.QCoreApplication.postEvent(self, ButtonEvent(int(chr(e.key()))))
@QtCore.Slot(int)
def playerButtonPress(self, no):
QtCore.QCoreApplication.postEvent(self, ButtonEvent(int(no)))
def _question_has_audio(self):
return self.question["Type"] == "Music" or (self.question["Type"] == "Video" and self.question["Audio"])
class QuestionAnswerWindow(QtGui.QDialog):
CORRECT = 1
WRONG = 2
OOPS = 3
def __init__(self, player, parent=None):
super(QuestionAnswerWindow, self).__init__(parent)
self.player = player
self._setupGui()
# move window to bottom right of screen
g = QtGui.QApplication.desktop().screenGeometry()
self.show()
cPos = self.rect()
self.move(g.width() - cPos.width(), g.height() - cPos.height())
def _setupGui(self):
self.layout = QtGui.QVBoxLayout()
self.plabel = QtGui.QLabel(self.player.name)
self.plabel.setStyleSheet("QLabel { font-size: 60px; }")
self.layout.addWidget(self.plabel, alignment=QtCore.Qt.AlignCenter)
btnbox = QtGui.QHBoxLayout()
right = QtGui.QPushButton("Correct")
right.clicked.connect(lambda: self.done(self.CORRECT))
btnbox.addWidget(right)
wrong = QtGui.QPushButton("Wrong")
wrong.clicked.connect(lambda: self.done(self.WRONG))
btnbox.addWidget(wrong)
btnbox.addStretch()
oops = QtGui.QPushButton("Oops")
oops.clicked.connect(lambda: self.done(self.OOPS))
btnbox.addWidget(oops)
self.layout.addLayout(btnbox)
self.setLayout(self.layout)
class EditAnswersWindow(QtGui.QDialog):
def __init__(self, players, answers, parent=None):
super(EditAnswersWindow, self).__init__(parent)
self._players = players
self._answers = answers
self._nobody = not answers.got_answered()
self._dj_points = answers.get_dj_points()
self._in_window = False
if self._answers:
self._tries = copy.copy(self._answers.get_tries())
else:
self._tries = []
self._setupGui()
self.installEventFilter(KeyGrabber((QtCore.Qt.Key.Key_Left, QtCore.Qt.Key.Key_Right), self))
def get_new_answers(self):
qa = QuestionAnswers(self._answers.section, self._answers.qnumber, dj_points=self._dj_points)
rightPlayer = None
for player, correct in self._tries:
if correct:
rightPlayer = player
else:
qa.add_try(player, correct)
if rightPlayer:
qa.add_try(rightPlayer, True)
elif self._nobody:
qa.nobody_knew()
return qa
def closeEvent(self, event):
if not self._in_window:
event.accept()
else:
event.ignore()
def _setupGui(self):
self.layout = QtGui.QVBoxLayout()
self.qlabel = QtGui.QLabel("Questions")
self.qlabel.setStyleSheet("QLabel { font-size: 30px; }")
self.layout.addWidget(self.qlabel, alignment=QtCore.Qt.AlignCenter)
self.answerlayout = QtGui.QVBoxLayout()
self._resetAllButtons()
self.layout.addLayout(self.answerlayout)
self.xlabel = QtGui.QLabel("+/- right/wrong, d delete, a add, arrows change player\ne edit double jeopardy")
self.xlabel.setStyleSheet("QLabel { font-size: 15px; }")
self.layout.addWidget(self.xlabel)
self.setLayout(self.layout)
def keyPressEvent(self, e):
if e.key() == QtCore.Qt.Key_Escape:
self.close()
elif e.key() == QtCore.Qt.Key_E:
# edit double jeopardy
if self._dj_points is not None and len(self._tries) > 0 and not self._in_window:
self._in_window = True
dwin = DoubleJeopardyWindow(self._players, self._answers.qnumber*100, self._tries[0][0], current_points=self._dj_points, parent=self)
dwin.exec_()
self._dj_points = dwin.get_chosen_points()
self._tries[0] = (dwin.get_player(), self._tries[0][1])
self._in_window = False
else:
currFocus = self._getCurrFocus()
if currFocus is None and e.key() != QtCore.Qt.Key_A:
return
nothingDone = False
if e.key() == QtCore.Qt.Key_Plus:
self._nobody = False
for i, (player, correct) in enumerate(self._tries):
self._tries[i] = (player, i==currFocus)
if currFocus == len(self._tries):
self._nobody = True
elif e.key() == QtCore.Qt.Key_Minus:
if currFocus == len(self._tries):
if len(self._tries) == 0:
self._nobody = False
else:
if self._tries[currFocus][1]:
# give nobody the score for this one
self._nobody = True
self._tries[currFocus] = (self._tries[currFocus][0], False)
elif e.key() == QtCore.Qt.Key_A:
self._tries.append((self._players[0], False))
self._makeNobodyConsistent()
currFocus = len(self._tries) - 1
elif e.key() == QtCore.Qt.Key_D and currFocus < len(self._tries):
del self._tries[currFocus]
elif e.key() == QtCore.Qt.Key_Left and currFocus < len(self._players):
playlen = len(self._players)
prevPlayer = self._players[(self._players.index(self._tries[currFocus][0])-1+playlen)%playlen]
self._tries[currFocus] = (prevPlayer, self._tries[currFocus][1])
elif e.key() == QtCore.Qt.Key_Right and currFocus < len(self._tries):
playlen = len(self._players)
nextPlayer = self._players[(self._players.index(self._tries[currFocus][0])+1+playlen)%playlen]
self._tries[currFocus] = (nextPlayer, self._tries[currFocus][1])
else:
super(EditAnswersWindow, self).keyPressEvent(e)
nothingDone = True
if not nothingDone:
self._resetAllButtons()
self.updateGeometry()
# reset focus
#print("setting focus to", min(currFocus, self.answerlayout.count()), self.answerlayout.itemAt(min(currFocus, self.answerlayout.count())).widget().text(), self.answerlayout.itemAt(min(currFocus, self.answerlayout.count())).widget().hasFocus())
self.answerlayout.itemAt(min(currFocus, self.answerlayout.count())).widget().setFocus(QtCore.Qt.FocusReason.NoFocusReason)
#print("setting focus to", min(currFocus, self.answerlayout.count()), self.answerlayout.itemAt(min(currFocus, self.answerlayout.count())).widget().text(), self.answerlayout.itemAt(min(currFocus, self.answerlayout.count())).widget().hasFocus())
#print(self.focusWidget())
#if self.focusWidget():
# print("widget", self.focusWidget().text())
def _makeNobodyConsistent(self):
if len(self._tries) > 0:
someoneKnew = any([c for p, c in self._tries])
self._nobody = not someoneKnew
def _getCurrFocus(self):
if self.focusWidget() is not None:
for i in range(self.answerlayout.count()):
if self.answerlayout.itemAt(i).widget() == self.focusWidget():
return i
return None
def _resetAllButtons(self):
f = self.answerlayout.takeAt(0)
while f is not None:
f.widget().deleteLater()
f = self.answerlayout.takeAt(0)
# add buttons
first = True
for player, correct in self._tries:
self._addAnswerButton(player, correct, first)
first = False
# add the nobody button
self._addAnswerButton(Player("nobody", nobodyColor), self._nobody)
def _addAnswerButton(self, player, correct=False, first=False):
prefix = "+" if correct else "-"
dj = " (D)" if first and self._dj_points is not None else ""
btn = QtGui.QPushButton("\n%s%s%s\n" % (prefix, player.name, dj), self)
btn.setStyleSheet("QPushButton { background-color: %s; color: white; font-size: 20px; border: none; }" % (player.color.name(),))
btn.installEventFilter(KeyGrabber((QtCore.Qt.Key.Key_Left, QtCore.Qt.Key.Key_Right), self))
self.answerlayout.addWidget(btn)
class KeyGrabber(QtCore.QObject):
def __init__(self, keys, parent, *args, **kwargs):
super(KeyGrabber, self).__init__(parent, *args, **kwargs)
self._keys = keys
self._parent = parent
def eventFilter(self, obj, e):
if e.type() == QtCore.QEvent.KeyPress:
if e.key() in self._keys:
self._parent.keyPressEvent(e)
return True
return QtCore.QObject.eventFilter(self, obj, e)
class PlayerStartWindow(QtGui.QDialog):
buzzersOpen = QtCore.Signal(bool)
playerGotQuestion = QtCore.Signal(int)
def __init__(self, players, parent):
super(PlayerStartWindow, self).__init__(parent)
self._players = players
self._parent = parent
self._playerLineEdits = []
self._windowSetup = False
self._setupGui()
self.show()
MusicBox.play_music("startSong")
# center window
g = QtGui.QApplication.desktop().screenGeometry()
cPos = self.rect()
self.move(g.width()/2 - cPos.width()/2, g.height()/2 - cPos.height()/2)
def event(self, e):
if e.type() == ButtonEvent.eventType:
if e.get_playerno() <= len(self._players)+1:
if e.get_playerno() == len(self._players)+1:
# add a new player
self._parent.add_player(addToGui=True)
self._guiAddPlayer(self._players[-1])
layoutItem = self.playerGrid.itemAtPosition(e.get_playerno(), 1)
if layoutItem:
widget = layoutItem.widget()
widget.selectAll()
widget.setFocus()
self.playerGotQuestion.emit(e.get_playerno())
return True
else:
ret = super(PlayerStartWindow, self).event(e)
if not self._windowSetup and e.type() == QtCore.QEvent.WindowActivate:
self.buzzersOpen.emit(True)
self._windowSetup = True
return ret
def closeEvent(self, event):
MusicBox.stop_music()
self.buzzersOpen.emit(False)
event.accept()
def keyPressEvent(self, e):
# needed to circumvent dialogclosing when enter is pressed
if e.key() == QtCore.Qt.Key_Escape:
self.close()
elif e.key() == QtCore.Qt.Key_K:
# FIXME: Remove this
QtCore.QCoreApplication.postEvent(self, ButtonEvent(1))
def _setupGui(self):
self.layout = QtGui.QVBoxLayout()
self.playerGrid = QtGui.QGridLayout()
for player in self._players:
self._guiAddPlayer(player)
self.layout.addLayout(self.playerGrid)
startbtn = QtGui.QPushButton("\nStart\n", self)
startbtn.setStyleSheet("QPushButton { font-size: 40px; }")
startbtn.clicked.connect(self.close)
self.layout.addWidget(startbtn)
self.setLayout(self.layout)
def _guiAddPlayer(self, player):
row = self.playerGrid.rowCount()
label = QtGui.QLabel("Player %d" % (row,), self)
label.setStyleSheet("QLabel { font-size: 40px; background-color: %s; color: white; }" % (player.color.name(),))
self.playerGrid.addWidget(label, row, 0)
edit = QtGui.QLineEdit(player.name, self)
edit.setStyleSheet("QLineEdit { font-size: 40px; }")
edit.editingFinished.connect(lambda widget=edit, player=player: self._playerDoneEditing(widget, player))
self.playerGrid.addWidget(edit, row, 1)
def _playerDoneEditing(self, widget, player):
player.change_name(widget.text())
self.buzzersOpen.emit(True)
@QtCore.Slot(int)
def playerButtonPress(self, no):
QtCore.QCoreApplication.postEvent(self, ButtonEvent(int(no)))
class VictoryWindow(QtGui.QDialog):
def __init__(self, players, parent):
super(VictoryWindow, self).__init__(parent)
self._players = players
self._parent = parent
self._setupGui()
MusicBox.play_music("closingSong")
def _setupGui(self):
self.layout = QtGui.QVBoxLayout()
self.playerGrid = QtGui.QGridLayout()
winPlayers = sorted(self._players, key=lambda p: p.points, reverse=True)
for i, player in enumerate(winPlayers):
label = QtGui.QLabel(player.name, self)
fsize = 60 if i == 0 else 25
label.setStyleSheet("QLabel { font-size: %dpx; background-color: %s; color: white; }" % (fsize, player.color.name(),))
self.playerGrid.addWidget(label, i, 0)
points = QtGui.QLabel(str(player.points), self)
points.setStyleSheet("QLabel { font-size: %dpx; }" % (fsize,))
self.playerGrid.addWidget(points, i, 1, alignment=QtCore.Qt.AlignRight)
self.layout.addLayout(self.playerGrid)
self.setLayout(self.layout)
def keyPressEvent(self, e):
if e.key() == QtCore.Qt.Key_Escape:
self.close()
def closeEvent(self, event):
MusicBox.stop_music()
event.accept()
class DoubleJeopardyWindow(QtGui.QDialog):
def __init__(self, player, points, current_player=None, current_points=None, parent=None):
super(DoubleJeopardyWindow, self).__init__(parent)
self._player = player
self._currentPlayer = current_player
self._points = points
if current_points:
self._currentPoints = current_points
else:
self._currentPoints = points
self._setupGui()
# move window to bottom right of screen
g = QtGui.QApplication.desktop().screenGeometry()
self.show()
cPos = self.rect()
self.move(g.width() - cPos.width(), g.height() - cPos.height())
def get_chosen_points(self):
return self._currentPoints
def get_player(self):
return self._currentPlayer
def _setupGui(self):
self.layout = QtGui.QVBoxLayout()
header = QtGui.QLabel("Double Jeopardy", self)
header.setStyleSheet("QLabel { font-size: 50px; }")
self.layout.addWidget(header, alignment=QtCore.Qt.AlignCenter)
self.pbtn = QtGui.QPushButton("", self)
self._stylePlayerButton()
self.pbtn.installEventFilter(KeyGrabber((QtCore.Qt.Key.Key_Left, QtCore.Qt.Key.Key_Right), self))
self.layout.addWidget(self.pbtn)
self.pointsbtn = QtGui.QPushButton("% 5s" % self._currentPoints)
self.pointsbtn.setStyleSheet("QPushButton { font-size: 50px; border: none; }")
self.pointsbtn.installEventFilter(KeyGrabber((QtCore.Qt.Key.Key_Left, QtCore.Qt.Key.Key_Right), self))
self.layout.addWidget(self.pointsbtn, alignment=QtCore.Qt.AlignCenter)
self.pointsbtn.setFocus()
start = QtGui.QPushButton("Done", self)
start.clicked.connect(self.closeIfNotNobody)
self.layout.addWidget(start)
self.setLayout(self.layout)
def closeIfNotNobody(self):
if self.get_player() is not None:
self.close()
def keyPressEvent(self, e):
if e.key() == QtCore.Qt.Key_Escape or e.key() == QtCore.Qt.Key_Enter:
self.closeIfNotNobody()
elif e.key() == QtCore.Qt.Key_Left or e.key() == QtCore.Qt.Key_Right:
if self.focusWidget() == self.pbtn:
if e.key() == QtCore.Qt.Key_Right:
if self._currentPlayer == None:
self._currentPlayer = self._player[0]
elif self._currentPlayer == self._player[-1]:
self._currentPlayer = None
else:
self._currentPlayer = self._player[self._player.index(self._currentPlayer)+1]
elif e.key() == QtCore.Qt.Key_Left:
if self._currentPlayer == None:
self._currentPlayer = self._player[-1]
elif self._currentPlayer == self._player[0]:
self._currentPlayer = None
else:
self._currentPlayer = self._player[self._player.index(self._currentPlayer)-1]
self._stylePlayerButton()
elif self.focusWidget() == self.pointsbtn:
if e.key() == QtCore.Qt.Key_Right and self._currentPoints < self._points*2:
self._currentPoints += 50
elif e.key() == QtCore.Qt.Key_Left and self._currentPoints > self._points/2:
self._currentPoints += -50
self.pointsbtn.setText("% 4s" % self._currentPoints)
def _stylePlayerButton(self):
player = None
color = None
if self._currentPlayer == None:
player = "nobody"
color = nobodyColor
else:
player = self._currentPlayer.name
color = self._currentPlayer.color
self.pbtn.setText(player)
self.pbtn.setStyleSheet("QPushButton { background-color: %s; color: white; font-size: 50px; border: none; }" % (color.name(),))