...
 
Commits (2)
......@@ -13,7 +13,7 @@ The UI is done with PyQt5.
* Make sure to have Python>3.5 installed (`sudo apt-get install python3` otherwise)
* Make sure to have PyQt5 installed (`sudo pip install pyqt5` otherwise)
* Compile the .ui and .rc files: `./devtools.sh ui`
* Execute main.py: `python3 babyfut.py`
* Execute the software: `./devtools.sh exec`
## Edit the UI
The UI is mostly created using Qt-Designer:
......
......@@ -11,16 +11,16 @@ import sys
import logging
from os.path import dirname, abspath, join
from PyQt5 import QtWidgets, QtCore
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QApplication
def getContent(path):
contentFolder = join(dirname(dirname(abspath(__file__))), 'content')
return join(contentFolder, path)
def getMainWin():
from ui.mainwin import MainWin
from Babyfut.ui.mainwin import MainWin
# Global function to find the (open) QMainWindow in application
for widget in QApplication.instance().topLevelWidgets():
if isinstance(widget, QMainWindow):
......@@ -28,46 +28,40 @@ def getMainWin():
return None
if __name__=='__main__':
from ui.mainwin import MainWin
from modules import GameModule
from player import Side
from settings import Settings
from input import GPIOThread
from database import Database
from replay import Replay as ReplayThread
__package__ = 'Babyfut'
from Babyfut.ui.mainwin import MainWin
from Babyfut.modules import GameModule
from Babyfut.core.player import Side
from Babyfut.core.input import GPIOThread
from Babyfut.core.database import Database
from Babyfut.core.replay import Replay as ReplayThread
try:
#logging.basicConfig(filename='babyfoot.log', level=logging.DEBUG)
logging.basicConfig(level=logging.DEBUG)
app = QtWidgets.QApplication(sys.argv)
lang = Settings['ui.language']
qtTranslator = QtCore.QTranslator()
if lang!='en' and qtTranslator.load("translations/babyfut_{}.qm".format(lang)):
app.installTranslator(qtTranslator)
app = QApplication(sys.argv)
myapp = MainWin()
if ReplayThread.isCamAvailable():
threadReplay = ReplayThread(Side.Left)
threadReplay.start()
myapp.dispatchMessage({'replayThread': threadReplay}, toType=GameModule)
threadGPIO = GPIOThread(myapp)
threadGPIO.start()
myapp.show()
app.exec_()
threadGPIO.stop()
if ReplayThread.isCamAvailable():
threadReplay.stop()
threadReplay.join()
threadGPIO.join()
finally:
GPIOThread.clean()
Database.instance().close()
......@@ -11,42 +11,42 @@ class DatabaseError(Exception):
class Database():
__db = None
def __init__(self):
if not Database.__db:
from babyfut import getContent
from Babyfut.babyfut import getContent
db_path = getContent('babyfut.sqlite')
self._connection = sqlite3.connect(db_path)
@staticmethod
def instance():
'''
Singleton
'''
if not Database.__db:
Database.__db = Database()
return Database.__db
@property
def _cursor(self):
return self._connection.cursor()
def select_one(self, query, *args):
res = self._cursor.execute(query, args).fetchone()
if not res:
raise DatabaseError('Query \"{}\" returned nothing with args {}'.format(query, args))
return res
def select_guest_team(self):
return self.select_one('SELECT id FROM players WHERE fname LIKE "guest"')[0]
def insert_team(self, players, goals):
if len(players)<2:
players.append(None)
self._cursor.execute('INSERT INTO Teams (nGoals, player1, player2) VALUES (?, ?, ?)', (goals, players[0], players[1],))
self._connection.commit()
return self._cursor.execute('SELECT seq FROM sqlite_sequence WHERE name="Teams"').fetchone()[0]
......@@ -56,7 +56,7 @@ class Database():
self._connection.commit()
def select_all_rfid(self, debug=False):
from settings import Settings
from Babyfut.core.settings import Settings
if Settings['app.mode']=='prod':
return self._cursor.execute('SELECT rfid FROM Players WHERE rfid>0').fetchall()
else:
......@@ -65,6 +65,6 @@ class Database():
def delete_player(self, playerID):
self._cursor.execute('DELETE FROM Players WHERE id==?', (playerID,))
self._connection.commit()
def close(self):
self._connection.close()
......@@ -8,13 +8,12 @@ import logging
import pyautogui # PyPi library
from threading import Thread
from babyfut import OnRasp
from Babyfut.babyfut import OnRasp
from Babyfut.core.player import Side
if OnRasp:
import RPi.GPIO as GPIO
from player import Side
class GPIOThread(Thread):
_keyButtonBindings = {
26: 'up',
......@@ -29,7 +28,7 @@ class GPIOThread(Thread):
Thread.__init__(self)
self.dispatcher = dispatcher
self.continueRunning = True
if OnRasp:
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
......@@ -54,10 +53,10 @@ class GPIOThread(Thread):
key = GPIOThread._keyButtonBindings[button_pin]
logging.debug('Sending {} as {}'.format(button_pin, key))
pyautogui.press(key)
def stop(self):
self.continueRunning = False
@staticmethod
def clean():
if OnRasp:
......
......@@ -15,11 +15,11 @@ class Module(QWidget):
self.mainwin = parent
self.ui = widget
self.ui.setupUi(self)
def switchModule(self, new_type):
curmod_idx = self.mainwin.findMod(type(self))
newmod_idx = self.mainwin.findMod(new_type)
if curmod_idx<0:
logging.error('Unknown panel {}'.format(type(self)))
elif newmod_idx<0:
......@@ -28,26 +28,27 @@ class Module(QWidget):
# Unfocus the current module
if QApplication.focusWidget() != None:
QApplication.focusWidget().clearFocus()
# Swap modules by unloading, changing the ui then loading
self.mainwin.modules[curmod_idx].unload()
self.mainwin.ui.panels.setCurrentIndex(newmod_idx)
self.mainwin.ui.panels.setFocusProxy(self.mainwin.modules[newmod_idx])
self.mainwin.modules[newmod_idx].setFocus()
# Select first element of the Module
self.mainwin.modules[newmod_idx].focusNextChild()
self.mainwin.modules[newmod_idx].focusPreviousChild()
self.mainwin.modules[newmod_idx].ui.retranslateUi(self.mainwin)
self.mainwin.modules[newmod_idx].load()
def send(self, to, **kwargs):
mod_idx = self.mainwin.findMod(to)
if mod_idx<0:
logging.error('Unknown panel {}'.format(to))
else:
self.mainwin.modules[mod_idx].other(**kwargs)
def load(self):
logging.warning('Unimplemented method "load" for {}'.format(self.__class__))
......
......@@ -8,12 +8,13 @@ Created on Wed Apr 18 18:34:40 2018
import logging
from enum import Enum
from database import Database, DatabaseError
from PyQt5.QtCore import Qt, QCoreApplication
from PyQt5.QtWidgets import QDialog
from ui.consent_dialog_ui import Ui_Dialog as ConsentDialogUI
from Babyfut.babyfut import getMainWin
from Babyfut.core.database import Database, DatabaseError
from Babyfut.ui.consent_dialog_ui import Ui_Dialog as ConsentDialogUI
class Side(Enum):
'''
......@@ -22,17 +23,17 @@ class Side(Enum):
Undef = -1
Left = 0
Right = 1
@property
def opposite(self):
return Side.Right if self==Side.Left else Side.Left
return Side.Right if self==Side.Left else Side.Left
class ConsentDialog(QDialog):
def __init__(self, parent):
QDialog.__init__(self, parent)
self.ui = ConsentDialogUI()
self.ui.setupUi(self)
self.ui.txtConsent.setHtml(QCoreApplication.translate('consent', '''<p>
You are about to connect yourself for the first time. We will need to access:
<ul>
......@@ -40,29 +41,29 @@ class ConsentDialog(QDialog):
<li>Your Picture (if public)</li>
<li>...</li>
</ul>
</p>
</p>
<p>
It is possible to play withtout connecting yourslef, but this will allow you to keep track of your score and to provide a better experience for you and the ones you play with!
<br/><br/>
Do you agree with this?
</p>'''))
def keyPressEvent(self, e):
if e.key()==Qt.Key_Return:
self.accept()
else:
self.reject()
class Player():
__query_infos = 'SELECT id, fname, lname, pic FROM Players WHERE rfid==?'
__query_time_goals_games = 'SELECT SUM(Matchs.duration) AS timePlayed, SUM(Teams.nGoals) AS goalsScored, COUNT(*) AS gamesPlayed FROM Teams INNER JOIN Matchs ON (Teams.id==Matchs.winningTeam OR Teams.id==Matchs.losingTeam) WHERE (Teams.player1==? OR player2==?)'
__query_victories = 'SELECT COUNT(*) AS victories FROM Players INNER JOIN Teams ON (Players.id==Teams.player1 OR Players.id==Teams.player2) INNER JOIN Matchs ON (Teams.id==Matchs.winningTeam) WHERE Players.id==?'
_placeholder_pic_path = ':ui/img/placeholder_head.jpg'
_first_time = True # Debug
def __init__(self, id, rfid, fname, lname, pic_path, stats):
self.id = id
self.rfid = rfid
......@@ -70,52 +71,60 @@ class Player():
self.lname = lname
self.pic_path = pic_path if pic_path else Player._placeholder_pic_path # Default pic if None
self.stats = stats
@staticmethod
def fromRFID(rfid):
db = Database.instance()
try:
# Retrieve generic informations
id, fname, lname, pic = db.select_one(Player.__query_infos, rfid)
# Ask for consent
if rfid==-2 and Player._first_time:
import babyfut
consentDialog = ConsentDialog(babyfut.getMainWin())
consentDialog = ConsentDialog(getMainWin())
consentDialog.exec()
if not consentDialog.result()==QDialog.Accepted:
Player._first_time = False
return PlayerGuest
# Retrieve stats
stats = {}
stats['time_played'], stats['goals_scored'], stats['games_played'] = db.select_one(Player.__query_time_goals_games, id, id)
stats['victories'], = db.select_one(Player.__query_victories, id)
for key, val in stats.items():
if val==None:
stats[key] = 0
return Player(id, rfid, fname, lname, pic, stats)
except DatabaseError as e:
logging.warn('DB Error: {}'.format(e))
return PlayerGuest
def displayImg(self, containerWidget):
containerWidget.setStyleSheet('border-image: url({});'.format(self.pic_path))
def displayImg(self, container_widget):
self.pic_container = container_widget
if self.pic_path.startswith('http'):
# Download from the internet
container_widget.setStyleSheet('border-image: url({});'.format(Player._placeholder_pic_path))
else:
# Already downloaded and stored locally
container_widget.setStyleSheet('border-image: url({});'.format(self.pic_path))
def save(self):
'''
Update or create the player in database
'''
pass
# DONT SAVE PIC PATH, IT IS CHANGED FOR THE LOCAL PATH
@property
def name(self):
return '{} {}'.format(self.fname, self.lname.upper())
@property
def stats_property(self):
'''
......@@ -129,9 +138,9 @@ class Player():
self.time_played = stats['time_played']
self.goals_scored = stats['goals_scored']
self.games_played = stats['games_played']
return Stat(self.stats)
@staticmethod
def allStoredPlayers():
return [Player.fromRFID(rfid) for rfid, in Database.instance().select_all_rfid()]
......
......@@ -6,8 +6,8 @@
from threading import Thread, Event
from babyfut import getContent, OnRasp
from settings import Settings
from Babyfut.babyfut import getContent, OnRasp
from Babyfut.core.settings import Settings
if OnRasp:
import picamera
......@@ -17,7 +17,7 @@ class Replay(Thread):
Thread.__init__(self)
self.replayPath = getContent('Replay {}.mp4'.format(side.name))
self.shutdown = False
self.start_flag = Event()
self.stop_flag = Event()
self.stopped_flag = Event()
......@@ -33,12 +33,12 @@ class Replay(Thread):
def start_recording(self):
if OnRasp:
self.start_flag.set()
def stop_recording(self):
if OnRasp:
self.stop_flag.set()
self.stopped_flag.wait()
# Clear all control flags
self.stop_flag.clear()
self.start_flag.clear()
......@@ -49,20 +49,20 @@ class Replay(Thread):
def stop(self):
self.start_flag.set()
self.shutdown = True
def run(self):
while not self.shutdown:
self.start_flag.wait()
if not self.shutdown:
self.cam.start_recording(self.stream, Settings['picam.format'])
try:
while not self.stop_flag.is_set():
self.cam.wait_recording(1)
finally :
self.cam.stop_recording()
self.stream.copy_to(self.replayPath)
self.stream.clear()
......@@ -71,11 +71,11 @@ class Replay(Thread):
self.cam.close()
self.stream.close()
@classmethod
def Dummy(cls):
return getContent('Replay Left.mp4')
@staticmethod
def isCamAvailable():
return OnRasp # and other checks (ToDo)
......@@ -8,7 +8,7 @@ import json
class MyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Setting):
if isinstance(obj, Setting):
return obj.__dict__
elif isinstance(obj, String) and obj=='settingsPath':
return None
......@@ -17,65 +17,65 @@ class MyEncoder(json.JSONEncoder):
class Setting(object):
TypeName = ''
def __init__(self, value):
self.type = type(self).TypeName
self.value = value
class SettingBoolean(Setting):
TypeName = 'boolean'
def __init__(self, value):
Setting.__init__(self, value)
class SettingCombo(Setting):
TypeName = 'combo'
def __init__(self, value, values):
Setting.__init__(self, value)
self.values = values
if self.value not in values:
raise ValueError('Setting value {} not in list of possible values {}'.format(self.value, self.values))
class SettingRange(Setting):
TypeName = 'range'
def __init__(self, value, limits):
Setting.__init__(self, value)
self.range = [min(limits), max(limits)]
if self.value<self.range[0] or self.value>self.range[1]:
raise ValueError('Setting value {} not in range {}'.format(self.value, (self.lower_limit, self.upper_limit)))
class SettingsHolder(object):
def __init__(self, settingsPath):
self.settingsPath = settingsPath
self.loadSettingsFromJSON()
def __delitem__(self, key):
pass
def __getitem__(self, key):
subkeys = SettingsHolder._parseKey(key)
if len(subkeys) == 2:
return getattr(self, subkeys[0])[subkeys[1]].value
elif len(subkeys) == 1:
return getattr(self, subkeys[0]).value
else:
raise IndexError('Invalid key {}'.format(key))
def __setitem__(self, key, value):
subkeys = SettingsHolder._parseKey(key)
if len(subkeys) == 2:
getattr(self, subkeys[0])[subkeys[1]].value = value
elif len(subkeys) == 1:
getattr(self, subkeys[0]).value = value
else:
raise IndexError('Invalid key {}'.format(key))
@staticmethod
def _parseKey(key):
return [k for k in key.split('.') if k]
......@@ -83,18 +83,18 @@ class SettingsHolder(object):
def loadSettingsFromJSON(self):
with open(self.settingsPath, 'r') as f:
content = json.load(f)
# Outer loop, setting category
for cat in content:
setattr(self, cat, dict())
content_outer = content[cat]
# Inner loop, setting type
for name in content_outer:
content_inner = content_outer[name]
typeName = content_inner['type']
value = content_inner['value']
# Switch over types
if typeName == SettingBoolean.TypeName:
setting = SettingBoolean(value)
......@@ -108,14 +108,14 @@ class SettingsHolder(object):
getattr(self, cat)[name] = setting
def saveSettingsToJSON(self):
# Deletes the settings path member to prevent it from being saved in the JSON
# Deletes the settings path member to prevent it from being saved in the JSON
settingsPath = self.settingsPath
del self.settingsPath
with open(settingsPath, 'w') as f:
json.dump(self.__dict__, f, cls=MyEncoder, indent=4)
self.settingsPath = settingsPath
from babyfut import getContent
from Babyfut.babyfut import getContent
Settings = SettingsHolder(getContent('settings.json'))
......@@ -7,25 +7,25 @@ cd "$( dirname "${BASH_SOURCE[0]}" )"
case "$1" in
"ui"|"update_ui"|"build_ui")
echo "Building UI..."
echo " Modules"
pyuic5 --import-from=ui ui/main.ui -o ui/main_ui.py
pyuic5 --import-from=ui ui/menu.ui -o ui/menu_ui.py
pyuic5 --import-from=ui ui/game.ui -o ui/game_ui.py
pyuic5 --import-from=ui ui/endgame.ui -o ui/endgame_ui.py
pyuic5 --import-from=ui ui/options.ui -o ui/options_ui.py
pyuic5 --import-from=ui ui/authquick.ui -o ui/authquick_ui.py
pyuic5 --import-from=ui ui/authleague.ui -o ui/authleague_ui.py
pyuic5 --import-from=ui ui/leaderboard.ui -o ui/leaderboard_ui.py
pyuic5 --import-from=ui ui/privacy.ui -o ui/privacy_ui.py
pyuic5 --import-from=Babyfut.ui ui/main.ui -o ui/main_ui.py
pyuic5 --import-from=Babyfut.ui ui/menu.ui -o ui/menu_ui.py
pyuic5 --import-from=Babyfut.ui ui/game.ui -o ui/game_ui.py
pyuic5 --import-from=Babyfut.ui ui/endgame.ui -o ui/endgame_ui.py
pyuic5 --import-from=Babyfut.ui ui/options.ui -o ui/options_ui.py
pyuic5 --import-from=Babyfut.ui ui/authquick.ui -o ui/authquick_ui.py
pyuic5 --import-from=Babyfut.ui ui/authleague.ui -o ui/authleague_ui.py
pyuic5 --import-from=Babyfut.ui ui/leaderboard.ui -o ui/leaderboard_ui.py
pyuic5 --import-from=Babyfut.ui ui/privacy.ui -o ui/privacy_ui.py
echo " Custom Widgets"
pyuic5 --import-from=ui ui/playerlist.ui -o ui/playerlist_ui.py
pyuic5 --import-from=ui ui/delete_dialog.ui -o ui/delete_dialog_ui.py
pyuic5 --import-from=ui ui/consent_dialog.ui -o ui/consent_dialog_ui.py
pyuic5 --import-from=Babyfut.ui ui/playerlist.ui -o ui/playerlist_ui.py
pyuic5 --import-from=Babyfut.ui ui/delete_dialog.ui -o ui/delete_dialog_ui.py
pyuic5 --import-from=Babyfut.ui ui/consent_dialog.ui -o ui/consent_dialog_ui.py
echo " Resources"
pyrcc5 -root /ui ui/assets.qrc -o ui/assets_rc.py
pyrcc5 -root /Babyfut/ui ui/assets.qrc -o ui/assets_rc.py
echo "Done."
;;
"clean"|"clear")
......@@ -36,6 +36,7 @@ case "$1" in
rm -rf __pycache__
rm -rf ui/__pycache__
rm -rf core/__pycache__
rm -rf modules/__pycache__
echo "Done."
;;
......@@ -58,6 +59,10 @@ case "$1" in
bash ./devtools.sh "clean"
bash ./devtools.sh "all"
;;
"exec")
cd ..
python -m Babyfut.babyfut
;;
*)
echo "Unknown command \"$1\". See content of script for available commands."
;;
......
from modules.authquick import AuthQuickModule
from modules.authleague import AuthLeagueModule
from modules.game import GameModule
from modules.endgame import EndGameModule
from modules.menu import MenuModule
from modules.options import OptionsModule
from modules.leaderboard import LeaderboardModule
from modules.privacy import PrivacyModule
from Babyfut.modules.authquick import AuthQuickModule
from Babyfut.modules.authleague import AuthLeagueModule
from Babyfut.modules.game import GameModule
from Babyfut.modules.endgame import EndGameModule
from Babyfut.modules.menu import MenuModule
from Babyfut.modules.options import OptionsModule
from Babyfut.modules.leaderboard import LeaderboardModule
from Babyfut.modules.privacy import PrivacyModule
......@@ -8,9 +8,9 @@ import logging
from PyQt5.QtCore import Qt
import modules
from module import Module
from player import Side, Player
from Babyfut import modules
from Babyfut.core.module import Module
from Babyfut.core.player import Side, Player
class AuthModuleBase(Module):
def __init__(self, parent, widget):
......@@ -20,12 +20,12 @@ class AuthModuleBase(Module):
def load(self):
pass
def unload(self):
self.createPlayerList()
self.numPlayers = 0
def other(self, **kwargs):
def other(self, **kwargs):
for key, val in kwargs.items():
if key=='rfid' and 'source' in kwargs:
side = kwargs['source']
......@@ -35,10 +35,10 @@ class AuthModuleBase(Module):
def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape:
self.handleCancel()
elif e.key() == Qt.Key_Return:
self.handleDone()
elif e.key() == Qt.Key_Left or e.key() == Qt.Key_Right:
side = Side.Left if e.key() == Qt.Key_Left else Side.Right
rfid = -(2 + self.numPlayers%5)
......@@ -46,7 +46,7 @@ class AuthModuleBase(Module):
def createPlayerList(self):
logging.warning('Base function meant to be reimplemented')
def handleCancel(self):
self.switchModule(modules.MenuModule)
......
......@@ -7,57 +7,56 @@
import logging
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtWidgets import QAbstractItemView
from modules.auth import AuthModuleBase
from ui.authleague_ui import Ui_Form as AuthLeagueWidget
from player import Side, PlayerEmpty
from Babyfut.modules.auth import AuthModuleBase
from Babyfut.ui.authleague_ui import Ui_Form as AuthLeagueWidget
from Babyfut.core.player import Side, PlayerEmpty
class AuthLeagueModule(AuthModuleBase):
def __init__(self, parent):
super().__init__(parent, AuthLeagueWidget())
def load(self):
logging.debug('Loading AuthLeagueModule')
super().load()
self.addPlayer(Side.Left, PlayerEmpty)
def unload(self):
logging.debug('Loading AuthLeagueModule')
super().unload()
self.ui.playersList.clear()
def createPlayerList(self):
'''
Duplicates the player list to be the same on both sides.
That way, adding a player on the left or on the right have the exact same effect,
and thus the AuthModuleBase code can remain generic.
'''
l = list()
self.players = {Side.Left: l, Side.Right: l}
def addPlayer(self, side, player):
_translate = QCoreApplication.translate
# Add the player if not already in the list
if all([p.id!=player.id for p in self.players[side]]):
if player!=PlayerEmpty:
self.players[side].append(player)
# Update the left side description
player.displayImg(self.ui.img)
self.ui.lblName.setText(player.name)
self.ui.lblStat1.setText(_translate('{} Victories').format(player.stats['victories']))
self.ui.lblStat2.setText(_translate('{} Games Played').format(player.stats['games_played']))
self.ui.lblStat3.setText(_translate('{} Goals Scored').format(player.stats['goals_scored']))
self.ui.lblStat1.setText(_translate('Form', '{} Victories').format(player.stats['victories']))
self.ui.lblStat2.setText(_translate('Form', '{} Games Played').format(player.stats['games_played']))
self.ui.lblStat3.setText(_translate('Form', '{} Goals Scored').format(player.stats['goals_scored']))
if player!=PlayerEmpty:
# Update the right side list, making sure that the added player is showed
self.ui.playersList.addItem('{}. {}'.format(len(self.players[side]), player.name))
widgetItem = self.ui.playersList.item(self.ui.playersList.count()-1)
self.ui.playersList.scrollToItem(widgetItem, QAbstractItemView.PositionAtBottom)
def handleDone(self):
super().handleDone()
......@@ -8,14 +8,14 @@ import logging
from PyQt5.QtWidgets import QSizePolicy
from modules.auth import AuthModuleBase
from player import Side, PlayerGuest
from ui.authquick_ui import Ui_Form as AuthQuickWidget
from Babyfut.modules.auth import AuthModuleBase
from Babyfut.core.player import Side, PlayerGuest
from Babyfut.ui.authquick_ui import Ui_Form as AuthQuickWidget
class AuthQuickModule(AuthModuleBase):
def __init__(self, parent):
super().__init__(parent, AuthQuickWidget())
self.smallPicSize = 200
self.bigPicSize = 300
self.leftSpacerWidth = self.ui.widgetLayoutP1.layout().itemAt(0).spacerItem().geometry().width()
......@@ -24,13 +24,13 @@ class AuthQuickModule(AuthModuleBase):
def load(self):
logging.debug('Loading AuthQuickModule')
super().load()
for side in [Side.Left, Side.Right]:
if len(self.players[side])==0:
self.addPlayer(side, PlayerGuest)
self.updateSides()
def unload(self):
logging.debug('Unloading AuthQuickModule')
super().unload()
......@@ -38,20 +38,20 @@ class AuthQuickModule(AuthModuleBase):
def createPlayerList(self):
self.players = {Side.Left: list(), Side.Right: list()}
def addPlayer(self, side, player):
# If there is a placeholder Guest, clear it from the list, we don't need it anymore
if len(self.players[side])>0 and self.players[side][0]==PlayerGuest:
self.players[side].clear()
if len(self.players[side])<2:
self.players[side].append(player)
if len(self.players[Side.Left])>1 and len(self.players[Side.Right])>1:
self.handleDone()
else:
self.updateSides()
def updateSides(self):
'''
There might be issues with this function in the future:
......@@ -63,7 +63,7 @@ class AuthQuickModule(AuthModuleBase):
spacerLeft = self.ui.widgetLayoutP1.layout().itemAt(0).spacerItem()
spacerRight = self.ui.widgetLayoutP3.layout().itemAt(0).spacerItem()
# Update Left P1
if len(self.players[Side.Left])>0:
self.players[Side.Left][0].displayImg(self.ui.imgP1)
......@@ -71,7 +71,7 @@ class AuthQuickModule(AuthModuleBase):
else:
PlayerGuest.displayImg(self.ui.imgP1)
self.ui.lblP1.setText('')
# Update Left P2
if len(self.players[Side.Left])>1:
self.ui.imgP1.setMaximumSize(self.smallPicSize, self.smallPicSize)
......@@ -85,7 +85,7 @@ class AuthQuickModule(AuthModuleBase):
self.ui.lblP2.setText('')
self.ui.widgetLayoutP2.setVisible(False)
spacerLeft.changeSize(0, spacerLeft.geometry().height(), QSizePolicy.Ignored, QSizePolicy.Expanding)
# Update Right P1
if len(self.players[Side.Right])>0:
self.players[Side.Right][0].displayImg(self.ui.imgP3)
......@@ -93,7 +93,7 @@ class AuthQuickModule(AuthModuleBase):
else:
PlayerGuest.displayImg(self.ui.imgP3)
self.ui.lblP3.setText('')
# Update Right P2
if len(self.players[Side.Right])>1:
self.ui.imgP3.setMaximumSize(self.smallPicSize, self.smallPicSize)
......
......@@ -9,11 +9,11 @@ import logging
from PyQt5 import QtWidgets
from PyQt5.QtCore import QTimer, Qt
from database import Database
from player import Side, PlayerGuest
from module import Module
import modules
from ui.endgame_ui import Ui_Form as EndGameWidget
from Babyfut import modules
from Babyfut.core.database import Database
from Babyfut.core.player import Side, PlayerGuest
from Babyfut.core.module import Module
from Babyfut.ui.endgame_ui import Ui_Form as EndGameWidget
class EndGameModule(Module):
def __init__(self, parent=None):
......@@ -25,38 +25,38 @@ class EndGameModule(Module):
def load(self):
logging.debug('Loading EndGameModule')
self.setActiveP2(len(self.players[self.winSide])>1)
self.displayPlayers()
db = Database.instance()
idTeams = {}
for side in [Side.Left, Side.Right]:
if PlayerGuest in self.players[side]:
idTeams[side] = db.select_guest_team()
else:
idTeams[side] = db.insert_team([player.id for player in self.players[side]], self.scores[side])
db.insert_match(int(self.start_time), int(self.duration), idTeams[self.winSide], idTeams[self.winSide.opposite])
db.insert_match(int(self.start_time), int(self.duration), idTeams[self.winSide], idTeams[self.winSide.opposite])
# Quit the screen after 5 seconds if the user doesn't do it before
#self.screenTimeout.start(5000)
def unload(self):
logging.debug('Unloading EndGameModule')
self.screenTimeout.stop()
del self.players
del self.gameType
del self.winSide
del self.scores
del self.start_time
del self.duration
def other(self, **kwargs):
logging.debug('Other EndGameModule')
for key, val in kwargs.items():
if key=='players':
self.players = val
......@@ -70,29 +70,29 @@ class EndGameModule(Module):
self.start_time = val
elif key=='duration':
self.duration = val
def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape or e.key() == Qt.Key_Return:
self.handleQuit()
def setActiveP2(self, active):
self.ui.widgetLayoutP2.setVisible(active)
spacer = self.ui.horizontalLayout.itemAt(2).spacerItem()
if active:
spacer.changeSize(self.middleSpacerWidth, spacer.geometry().height(), QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
else:
spacer.changeSize(0, spacer.geometry().height(), QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding)
def displayPlayers(self):
players = self.players[self.winSide]
players[0].displayImg(self.ui.imgP1)
self.ui.lblP1.setText(players[0].name)
if len(self.players[self.winSide])>1:
players[1].displayImg(self.ui.imgP2)
self.ui.lblP2.setText(players[1].name)
def handleQuit(self):
self.switchModule(modules.MenuModule)
......@@ -14,12 +14,12 @@ from PyQt5.QtCore import QDateTime, QDate, QTime, QTimer, QRect, Qt, QUrl
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtMultimediaWidgets import QVideoWidget
from player import Side, PlayerGuest
from replay import Replay
from module import Module
from settings import Settings
import modules
from ui.game_ui import Ui_Form as GameWidget
from Babyfut import modules
from Babyfut.core.player import Side, PlayerGuest
from Babyfut.core.replay import Replay
from Babyfut.core.module import Module
from Babyfut.core.settings import Settings
from Babyfut.ui.game_ui import Ui_Form as GameWidget
class GameOverChecker():
def __init__(self, conditionType, limit):
......@@ -48,30 +48,30 @@ class ReplayHolder(QVideoWidget):
def __init__(self, mediaPlayer, parent):
super().__init__(parent)
self.mediaPlayer = mediaPlayer
def keyPressEvent(self, e):
self.mediaPlayer.stop_replay(QMediaPlayer.StoppedState)
class ReplayPlayer(QMediaPlayer):
def __init__(self, parent):
super().__init__(parent, QMediaPlayer.VideoSurface)
self.stateChanged.connect(self.stop_replay)
self.setMuted(True)
def start_replay(self, video_file):
self.setMedia(QMediaContent(QUrl.fromLocalFile(video_file)))
self._playerWidget = ReplayHolder(self, self.parent())
self.setVideoOutput(self._playerWidget)
self.play()
self._playerWidget.setFullScreen(True)
def stop_replay(self, status):
if status==QMediaPlayer.StoppedState:
self._playerWidget.setFullScreen(False);
self._playerWidget.setVisible(False);
self.parent().endOfReplay()
class GameModule(Module):
def __init__(self, parent=None):
super().__init__(parent, GameWidget())
......@@ -83,7 +83,7 @@ class GameModule(Module):
# Button connections
self.ui.btnScore1.clicked.connect(lambda: self.goal(Side.Left))
self.ui.btnScore2.clicked.connect(lambda: self.goal(Side.Right))
self.camera = None
self.video_player = None
......@@ -115,10 +115,10 @@ class GameModule(Module):
def unload(self):
logging.debug('Unloading GameModule')
self.timerUpdateChrono.stop()
self.gameStartTime = None
if self.camera:
self.camera.stop_recording()
......@@ -134,7 +134,7 @@ class GameModule(Module):
elif key=='replayThread':
self.replayer = val
def resizeEvent(self, event):
# 40% of the window width to have (5% margin)-(40% circle)-(10% middle)-(40% circle)-(5% margin)
btnDiameter = self.mainwin.width()*0.4
......@@ -151,10 +151,10 @@ class GameModule(Module):
ret = QMessageBox.question(self, 'Stop the match?', 'Do you really want to stop this match? It wont be saved.')
if ret == QMessageBox.Yes:
self.handleCancel()
elif e.key() == Qt.Key_Left:
self.goal(Side.Left)
elif e.key() == Qt.Key_Right:
self.goal(Side.Right)
......@@ -197,10 +197,10 @@ class GameModule(Module):
def endOfReplay(self):
self.video_player = None
if self.gameStartTime:
self.updateScores()
if self.camera:
self.camera.start_recording()
......@@ -212,7 +212,7 @@ class GameModule(Module):
if winSide!=Side.Undef:
start_timestamp = int(QDateTime(QDate.currentDate(), self.gameStartTime).toMSecsSinceEpoch()/1000)
self.send(modules.EndGameModule, players=self.players, winSide=winSide, scores=self.scores)
self.send(modules.EndGameModule, start_time=start_timestamp, duration=self.getGameTime(), gameType=self)
self.switchModule(modules.EndGameModule)
......@@ -11,30 +11,29 @@ from operator import attrgetter
from PyQt5.QtWidgets import QWidget, QDialog, QListWidgetItem
from PyQt5.QtCore import Qt, QItemSelectionModel
import modules
from module import Module
from player import Player, Side
from database import Database
from ui.leaderboard_ui import Ui_Form as LeaderboardWidget
from ui.playerlist_ui import Ui_Form as PlayerListWidget
from ui.delete_dialog_ui import Ui_Dialog as PlayerDeleteDialog
from Babyfut import modules
from Babyfut.core.module import Module
from Babyfut.core.player import Player, Side
from Babyfut.core.database import Database
from Babyfut.ui.leaderboard_ui import Ui_Form as LeaderboardWidget
from Babyfut.ui.playerlist_ui import Ui_Form as PlayerListWidget
from Babyfut.ui.delete_dialog_ui import Ui_Dialog as PlayerDeleteDialog
class LeaderboardItemWidget(QWidget):
def __init__(self, parent, player):
QWidget.__init__(self, parent)
self.ui = PlayerListWidget()
self.ui.setupUi(self)
player.displayImg(self.ui.picHolder)
self.ui.lblFName.setText(player.fname)
self.ui.lblLName.setText(player.lname)
self.ui.lblVictories.setText (self.ui.lblVictories.text().replace('####', str(player.stats['victories'])))
self.ui.lblGamesPlayed.setText (self.ui.lblGamesPlayed.text().replace('####', str(player.stats['games_played'])))
self.ui.lblGoalsScored.setText (self.ui.lblGoalsScored.text().replace('####', str(player.stats['goals_scored'])))
self.ui.lblMinutesPlayed.setText(self.ui.lblMinutesPlayed.text().replace('####', str(player.stats['time_played'])))
self.ui.pushButton.clicked.connect(lambda: logging.debug('clicked'))
class DeleteDialog(QDialog):
......@@ -42,29 +41,29 @@ class DeleteDialog(QDialog):
DeleteAll = 0
DeletePicture = 1
HideAccount = 2
def __init__(self, parent, player):
QDialog.__init__(self, parent)
self.ui = PlayerDeleteDialog()
self.ui.setupUi(self)
self.player = player
self.ui.lblTitle.setText(self.ui.lblTitle.text().format(player.name))
def check(self, rfid):
return rfid == -self.player.id
def action(self):
dict_actions = {
self.ui.rbDeleteAll: DeleteDialog.Actions.DeleteAll,
self.ui.rbDeletePicture: DeleteDialog.Actions.DeletePicture,
self.ui.rbHideAccount: DeleteDialog.Actions.HideAccount
}
for key, val in dict_actions.items():
if key.isChecked():
return val
return None
def keyPressEvent(self, e):
if e.key() == Qt.Key_Return:
# Debug
......@@ -76,17 +75,17 @@ class LeaderboardModule(Module):
def __init__(self, parent):
super().__init__(parent, LeaderboardWidget())
self.players = []
self.ui.rbName.clicked.connect(lambda: self.changeSort(self.ui.rbName))
self.ui.rbVictories.clicked.connect(lambda: self.changeSort(self.ui.rbVictories))
self.ui.rbScore.clicked.connect(lambda: self.changeSort(self.ui.rbScore))
self.ui.rbGamesPlayed.clicked.connect(lambda: self.changeSort(self.ui.rbGamesPlayed))
self.ui.rbTimePlayed.clicked.connect(lambda: self.changeSort(self.ui.rbTimePlayed))
self.selectedSort = 0
self.sortMethodRB = [self.ui.rbName, self.ui.rbVictories, self.ui.rbScore, self.ui.rbGamesPlayed, self.ui.rbTimePlayed]
self.sortMethodAttr = ['lname', 'stats_property.victories', 'stats_property.goals_scored', 'stats_property.games_played', 'stats_property.time_played']
self.sortMethodRB[self.selectedSort].setChecked(True)
self.deleteDialog = None
......@@ -98,10 +97,11 @@ class LeaderboardModule(Module):
def unload(self):
logging.debug('Unloading LeaderboardModule')
self.players = []
self.ui.listWidget.clear()
def other(self, **kwargs):
logging.debug('Other LeaderboardModule')
for key, val in kwargs.items():
if key=='rfid' and self.deleteDialog and self.deleteDialog.check(val):
# Do something corresponding to the selected action
......@@ -114,7 +114,7 @@ class LeaderboardModule(Module):
logging.error('Unimplemented action: Hide account')
else:
logging.error('Unknown action {}'.format(action))
# Reset the dialog and the player list
self.deleteDialog.close()
del self.deleteDialog
......@@ -122,51 +122,51 @@ class LeaderboardModule(Module):
self.players = []
self.ui.listWidget.clear()
self.loadList()
def changeSort(self, rbSort):
self.selectedSort = self.sortMethodRB.index(rbSort)
self.loadList()
def loadList(self):
if self.players:
self.ui.listWidget.clear()
else:
self.players = Player.allStoredPlayers()
self.players.sort(key=attrgetter(self.sortMethodAttr[self.selectedSort]), reverse=(self.sortMethodAttr[self.selectedSort]!='lname'))
for player in self.players:
item = QListWidgetItem()
playerWidget = LeaderboardItemWidget(self.ui.listWidget, player)
item.setSizeHint(playerWidget.size())
self.ui.listWidget.addItem(item)
self.ui.listWidget.setItemWidget(item, playerWidget)
self.ui.listWidget.setCurrentRow(0, QItemSelectionModel.Select)
def keyPressEvent(self, e):
curRow = self.ui.listWidget.currentRow()
curSort = self.selectedSort
if e.key() == Qt.Key_Escape:
self.handleExit()
elif e.key() == Qt.Key_Up:
newRow = curRow-1 if curRow!=0 else self.ui.listWidget.count()-1
self.ui.listWidget.setCurrentRow(newRow, QItemSelectionModel.SelectCurrent)
elif e.key() == Qt.Key_Down:
newRow = curRow+1 if curRow!=self.ui.listWidget.count()-1 else 0
self.ui.listWidget.setCurrentRow(newRow, QItemSelectionModel.SelectCurrent)
elif e.key() == Qt.Key_Left:
newSort = curSort-1 if curSort!=0 else len(self.sortMethodRB)-1
self.sortMethodRB[newSort].animateClick()
elif e.key() == Qt.Key_Right:
newSort = curSort+1 if curSort!=len(self.sortMethodRB)-1 else 0
self.sortMethodRB[newSort].animateClick()
elif e.key() == Qt.Key_Delete:
self.deleteDialog = DeleteDialog(self, self.players[curRow])
self.deleteDialog.open()
......
......@@ -9,11 +9,11 @@ import logging
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt
import modules
from module import Module
from settings import Settings
from ui.menu_ui import Ui_Form as MenuWidget
from player import Side
from Babyfut import modules
from Babyfut.core.module import Module
from Babyfut.core.settings import Settings
from Babyfut.ui.menu_ui import Ui_Form as MenuWidget
from Babyfut.core.player import Side
class MenuModule(Module):
def __init__(self, parent):
......@@ -32,10 +32,10 @@ class MenuModule(Module):
def unload(self):
logging.debug('Unloading MenuModule')
def other(self, **kwargs):
logging.debug('Other MenuModule')
if 'rfid' in kwargs and 'source' in kwargs:
self.send(modules.AuthQuickModule, **kwargs)
self.ui.btnStartQuick.animateClick()
......@@ -43,25 +43,25 @@ class MenuModule(Module):
def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape and Settings['app.mode']=='dev':
self.handleExit()
elif e.key() == Qt.Key_Up:
self.parent().focusPreviousChild()
elif e.key() == Qt.Key_Down:
self.parent().focusNextChild()
elif e.key() == Qt.Key_Left:
self.send(modules.MenuModule, rfid=-2, source=Side.Left)
elif e.key() == Qt.Key_Right:
self.send(modules.MenuModule, rfid=-3, source=Side.Right)
elif e.key() == Qt.Key_Return:
if QApplication.focusWidget()==None:
logging.error('No focused widget to activate')
else:
QApplication.focusWidget().animateClick()
def handleExit(self):
logging.info('Closing..')
self.mainwin.close()
......@@ -9,10 +9,10 @@ import logging
from PyQt5.QtCore import Qt, QEvent
from PyQt5.QtWidgets import QRadioButton, QSlider
from settings import Settings
from module import Module
import modules
from ui.options_ui import Ui_Form as OptionsWidget
from Babyfut import modules
from Babyfut.core.settings import Settings
from Babyfut.core.module import Module
from Babyfut.ui.options_ui import Ui_Form as OptionsWidget
class OptionsModule(Module):
def __init__(self, parent):
......@@ -20,32 +20,32 @@ class OptionsModule(Module):
self.ui.sliderGameOverValue.valueChanged.connect(self.updateGameOverLabel)
self.ui.rbGameOver_Score.clicked.connect(self.updateGameOverLabel)
self.ui.rbGameOver_Time.clicked.connect(self.updateGameOverLabel)
self.ui.sliderGameOverValue.installEventFilter(self)
def load(self):
logging.debug('Loading OptionsModule')
# Set Gameover condition from settings
self.ui.rbGameOver_Score.setChecked(Settings['gameover.type']=='score')
self.ui.rbGameOver_Time.setChecked(Settings['gameover.type']=='time')
self.ui.sliderGameOverValue.setValue(Settings['gameover.value'])
# Set League players from settings
self.ui.rbNumPlayerLeague_1.setChecked(Settings['league.playerPerTeam']==1)
self.ui.rbNumPlayerLeague_2.setChecked(Settings['league.playerPerTeam']==2)
# Set Language from settings
self.ui.rbLanguage_English.setChecked(Settings['ui.language']=='en')
self.ui.rbLanguage_French.setChecked(Settings['ui.language']=='fr')
self.selectIndex = 0
self.updateSelection()
self.updateGameOverLabel(0)
def unload(self):
logging.debug('Unloading OptionsModule')
def other(self, **kwargs):
logging.debug('Other OptionsModule')
......@@ -53,41 +53,41 @@ class OptionsModule(Module):
if obj==self.ui.sliderGameOverValue and event.type()==QEvent.KeyPress and (event.key()==Qt.Key_Up or event.key()==Qt.Key_Down):
self.keyPressEvent(event)
return True
return False
def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape:
self.handleBack()
elif e.key() == Qt.Key_Return:
self.handleSave()
elif e.key() == Qt.Key_Up:
#self.parent().focusPreviousChild()
self.selectIndex = self.selectIndex-1 if self.selectIndex!=0 else len(self.selectables)-1
self.updateSelection()
elif e.key() == Qt.Key_Down:
#self.parent().focusNextChild()
self.selectIndex = self.selectIndex+1 if self.selectIndex!=len(self.selectables)-1 else 0
self.updateSelection()
def updateSelection(self):
self.selectables = []
isSelectable = lambda widget: (isinstance(widget, QRadioButton) and widget.isChecked()) or isinstance(widget, QSlider)
for gb in [self.ui.gbGameOver, self.ui.gbLeaguePlayers, self.ui.gbLanguage]:
self.selectables.extend([child for child in gb.children() if isSelectable(child)])
self.selectables[self.selectIndex].setFocus()
def updateGameOverLabel(self, val):
sliderVal = self.ui.sliderGameOverValue.value()
strPoints = '{} point{}'.format(sliderVal, 's' if sliderVal>1 else '')
strTime = '{} minute{}'.format(sliderVal, 's' if sliderVal>1 else '')
self.ui.lblGameOverValue.setText(strPoints if self.ui.rbGameOver_Score.isChecked() else strTime)
def handleSave(self):
Settings['ui.language'] = 'en' if self.ui.rbLanguage_English.isChecked() else 'fr'
Settings['gameover.type'] = 'score' if self.ui.rbGameOver_Score.isChecked() else 'time'
......
......@@ -8,9 +8,9 @@ import logging
from PyQt5.QtCore import Qt, QCoreApplication
from module import Module
import modules
from ui.privacy_ui import Ui_Form as PrivacyWidget
from Babyfut import modules
from Babyfut.core.module import Module
from Babyfut.ui.privacy_ui import Ui_Form as PrivacyWidget
class PrivacyModule(Module):
def __init__(self, parent):
......@@ -22,7 +22,7 @@ class PrivacyModule(Module):
<li>Your Picture (if public)</li>
<li>...</li>
</ul>
</p>
</p>
<p>
That way players can keep track of their score and compare it with others.
......@@ -35,15 +35,15 @@ class PrivacyModule(Module):
def unload(self):
logging.debug('Unloading PrivacyModule')
def other(self, **kwargs):
logging.debug('Other PrivacyModule')
def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape:
self.handleBack()
super().keyPressEvent(e)
def handleBack(self):
self.switchModule(modules.MenuModule)
......@@ -245,6 +245,21 @@
<source>Privacy</source>
<translation>Vie Privée</translation>
</message>
<message>
<location filename="../modules/authleague.py" line="51"/>
<source>{} Victories</source>
<translation>{} Victoires</translation>
</message>
<message>
<location filename="../modules/authleague.py" line="52"/>
<source>{} Games Played</source>
<translation>{} Parties Jouées</translation>
</message>
<message>