Mercurial > libervia-web
changeset 672:b39a9eddfe56 frontends_multi_profiles
browser_side: fixes room games:
- use quick_frontend.quick_games
- rename the signals handlers to fit the convention (e.g.: tarotGameScoreHandler)
- rename card_game to game_tarot, radiocol to game_radiocol, CardGame to TarotPanel
author | souliane <souliane@mailoo.org> |
---|---|
date | Wed, 11 Mar 2015 12:50:19 +0100 |
parents | 2201ff543a05 |
children | e489218886d7 |
files | src/browser/sat_browser/card_game.py src/browser/sat_browser/chat.py src/browser/sat_browser/game_radiocol.py src/browser/sat_browser/game_tarot.py src/browser/sat_browser/radiocol.py |
diffstat | 5 files changed, 730 insertions(+), 738 deletions(-) [+] |
line wrap: on
line diff
--- a/src/browser/sat_browser/card_game.py Mon Mar 09 16:30:06 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,387 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Libervia: a Salut à Toi frontend -# Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson <goffi@goffi.org> - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero 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 Affero General Public License for more details. - -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import pyjd # this is dummy in pyjs -from sat.core.log import getLogger -log = getLogger(__name__) -from sat_frontends.tools.games import TarotCard -from sat.core.i18n import _ - -from pyjamas.ui.AbsolutePanel import AbsolutePanel -from pyjamas.ui.DockPanel import DockPanel -from pyjamas.ui.SimplePanel import SimplePanel -from pyjamas.ui.Image import Image -from pyjamas.ui.Label import Label -from pyjamas.ui.ClickListener import ClickHandler -from pyjamas.ui.MouseListener import MouseHandler -from pyjamas.ui import HasAlignment -from pyjamas import Window -from pyjamas import DOM - -import dialog -import xmlui - - -CARD_WIDTH = 74 -CARD_HEIGHT = 136 -CARD_DELTA_Y = 30 -MIN_WIDTH = 950 # Minimum size of the panel -MIN_HEIGHT = 500 - - -class CardWidget(TarotCard, Image, MouseHandler): - """This class is used to represent a card, graphically and logically""" - - def __init__(self, parent, file_): - """@param file: path of the PNG file""" - self._parent = parent - Image.__init__(self, file_) - root_name = file_[file_.rfind("/") + 1:-4] - suit, value = root_name.split('_') - TarotCard.__init__(self, (suit, value)) - MouseHandler.__init__(self) - self.addMouseListener(self) - - def onMouseEnter(self, sender): - if self._parent.state == "ecart" or self._parent.state == "play": - DOM.setStyleAttribute(self.getElement(), "top", "0px") - - def onMouseLeave(self, sender): - if not self in self._parent.hand: - return - if not self in list(self._parent.selected): # FIXME: Workaround pyjs bug, must report it - DOM.setStyleAttribute(self.getElement(), "top", "%dpx" % CARD_DELTA_Y) - - def onMouseUp(self, sender, x, y): - if self._parent.state == "ecart": - if self not in list(self._parent.selected): - self._parent.addToSelection(self) - else: - self._parent.removeFromSelection(self) - elif self._parent.state == "play": - self._parent.playCard(self) - - -class CardPanel(DockPanel, ClickHandler): - - def __init__(self, parent, referee, player_nick, players): - DockPanel.__init__(self) - ClickHandler.__init__(self) - self._parent = parent - self._autoplay = None # XXX: use 0 to activate fake play, None else - self.referee = referee - self.players = players - self.player_nick = player_nick - self.bottom_nick = self.player_nick - idx = self.players.index(self.player_nick) - idx = (idx + 1) % len(self.players) - self.right_nick = self.players[idx] - idx = (idx + 1) % len(self.players) - self.top_nick = self.players[idx] - idx = (idx + 1) % len(self.players) - self.left_nick = self.players[idx] - self.bottom_nick = player_nick - self.selected = set() # Card choosed by the player (e.g. during ecart) - self.hand_size = 13 # number of cards in a hand - self.hand = [] - self.to_show = [] - self.state = None - self.setSize("%dpx" % MIN_WIDTH, "%dpx" % MIN_HEIGHT) - self.setStyleName("cardPanel") - - # Now we set up the layout - _label = Label(self.top_nick) - _label.setStyleName('cardGamePlayerNick') - self.add(_label, DockPanel.NORTH) - self.setCellWidth(_label, '100%') - self.setCellHorizontalAlignment(_label, HasAlignment.ALIGN_CENTER) - - self.hand_panel = AbsolutePanel() - self.add(self.hand_panel, DockPanel.SOUTH) - self.setCellWidth(self.hand_panel, '100%') - self.setCellHorizontalAlignment(self.hand_panel, HasAlignment.ALIGN_CENTER) - - _label = Label(self.left_nick) - _label.setStyleName('cardGamePlayerNick') - self.add(_label, DockPanel.WEST) - self.setCellHeight(_label, '100%') - self.setCellVerticalAlignment(_label, HasAlignment.ALIGN_MIDDLE) - - _label = Label(self.right_nick) - _label.setStyleName('cardGamePlayerNick') - self.add(_label, DockPanel.EAST) - self.setCellHeight(_label, '100%') - self.setCellHorizontalAlignment(_label, HasAlignment.ALIGN_RIGHT) - self.setCellVerticalAlignment(_label, HasAlignment.ALIGN_MIDDLE) - - self.center_panel = DockPanel() - self.inner_left = SimplePanel() - self.inner_left.setSize("%dpx" % CARD_WIDTH, "%dpx" % CARD_HEIGHT) - self.center_panel.add(self.inner_left, DockPanel.WEST) - self.center_panel.setCellHeight(self.inner_left, '100%') - self.center_panel.setCellHorizontalAlignment(self.inner_left, HasAlignment.ALIGN_RIGHT) - self.center_panel.setCellVerticalAlignment(self.inner_left, HasAlignment.ALIGN_MIDDLE) - - self.inner_right = SimplePanel() - self.inner_right.setSize("%dpx" % CARD_WIDTH, "%dpx" % CARD_HEIGHT) - self.center_panel.add(self.inner_right, DockPanel.EAST) - self.center_panel.setCellHeight(self.inner_right, '100%') - self.center_panel.setCellVerticalAlignment(self.inner_right, HasAlignment.ALIGN_MIDDLE) - - self.inner_top = SimplePanel() - self.inner_top.setSize("%dpx" % CARD_WIDTH, "%dpx" % CARD_HEIGHT) - self.center_panel.add(self.inner_top, DockPanel.NORTH) - self.center_panel.setCellHorizontalAlignment(self.inner_top, HasAlignment.ALIGN_CENTER) - self.center_panel.setCellVerticalAlignment(self.inner_top, HasAlignment.ALIGN_BOTTOM) - - self.inner_bottom = SimplePanel() - self.inner_bottom.setSize("%dpx" % CARD_WIDTH, "%dpx" % CARD_HEIGHT) - self.center_panel.add(self.inner_bottom, DockPanel.SOUTH) - self.center_panel.setCellHorizontalAlignment(self.inner_bottom, HasAlignment.ALIGN_CENTER) - self.center_panel.setCellVerticalAlignment(self.inner_bottom, HasAlignment.ALIGN_TOP) - - self.inner_center = SimplePanel() - self.center_panel.add(self.inner_center, DockPanel.CENTER) - self.center_panel.setCellHorizontalAlignment(self.inner_center, HasAlignment.ALIGN_CENTER) - self.center_panel.setCellVerticalAlignment(self.inner_center, HasAlignment.ALIGN_MIDDLE) - - self.add(self.center_panel, DockPanel.CENTER) - self.setCellWidth(self.center_panel, '100%') - self.setCellHeight(self.center_panel, '100%') - self.setCellVerticalAlignment(self.center_panel, HasAlignment.ALIGN_MIDDLE) - self.setCellHorizontalAlignment(self.center_panel, HasAlignment.ALIGN_CENTER) - - self.loadCards() - self.mouse_over_card = None # contain the card to highlight - self.visible_size = CARD_WIDTH / 2 # number of pixels visible for cards - self.addClickListener(self) - - def loadCards(self): - """Load all the cards in memory""" - def _getTarotCardsPathsCb(paths): - log.debug("_getTarotCardsPathsCb") - for file_ in paths: - log.debug("path: %s" % file_) - card = CardWidget(self, file_) - log.debug("card: %s" % card) - self.cards[(card.suit, card.value)] = card - self.deck.append(card) - self._parent.host.bridge.call('tarotGameReady', None, self.player_nick, self.referee) - self.cards = {} - self.deck = [] - self.cards["atout"] = {} # As Tarot is a french game, it's more handy & logical to keep french names - self.cards["pique"] = {} # spade - self.cards["coeur"] = {} # heart - self.cards["carreau"] = {} # diamond - self.cards["trefle"] = {} # club - self._parent.host.bridge.call('getTarotCardsPaths', _getTarotCardsPathsCb) - - def onClick(self, sender): - if self.state == "chien": - self.to_show = [] - self.state = "wait" - self.updateToShow() - elif self.state == "wait_for_ecart": - self.state = "ecart" - self.hand.extend(self.to_show) - self.hand.sort() - self.to_show = [] - self.updateToShow() - self.updateHand() - - def tarotGameNew(self, hand): - """Start a new game, with given hand""" - if hand is []: # reset the display after the scores have been showed - self.selected.clear() - del self.hand[:] - del self.to_show[:] - self.state = None - #empty hand - self.updateHand() - #nothing on the table - self.updateToShow() - for pos in ['top', 'left', 'bottom', 'right']: - getattr(self, "inner_%s" % pos).setWidget(None) - self._parent.host.bridge.call('tarotGameReady', None, self.player_nick, self.referee) - return - for suit, value in hand: - self.hand.append(self.cards[(suit, value)]) - self.hand.sort() - self.state = "init" - self.updateHand() - - def updateHand(self): - """Show the cards in the hand in the hand_panel (SOUTH panel)""" - self.hand_panel.clear() - self.hand_panel.setSize("%dpx" % (self.visible_size * (len(self.hand) + 1)), "%dpx" % (CARD_HEIGHT + CARD_DELTA_Y + 10)) - x_pos = 0 - y_pos = CARD_DELTA_Y - for card in self.hand: - self.hand_panel.add(card, x_pos, y_pos) - x_pos += self.visible_size - - def updateToShow(self): - """Show cards in the center panel""" - if not self.to_show: - _widget = self.inner_center.getWidget() - if _widget: - self.inner_center.remove(_widget) - return - panel = AbsolutePanel() - panel.setSize("%dpx" % ((CARD_WIDTH + 5) * len(self.to_show) - 5), "%dpx" % (CARD_HEIGHT)) - x_pos = 0 - y_pos = 0 - for card in self.to_show: - panel.add(card, x_pos, y_pos) - x_pos += CARD_WIDTH + 5 - self.inner_center.setWidget(panel) - - def _ecartConfirm(self, confirm): - if not confirm: - return - ecart = [] - for card in self.selected: - ecart.append((card.suit, card.value)) - self.hand.remove(card) - self.selected.clear() - self._parent.host.bridge.call('tarotGamePlayCards', None, self.player_nick, self.referee, ecart) - self.state = "wait" - self.updateHand() - - def addToSelection(self, card): - self.selected.add(card) - if len(self.selected) == 6: - dialog.ConfirmDialog(self._ecartConfirm, "Put these cards into chien ?").show() - - def tarotGameInvalidCards(self, phase, played_cards, invalid_cards): - """Invalid cards have been played - @param phase: phase of the game - @param played_cards: all the cards played - @param invalid_cards: cards which are invalid""" - - if phase == "play": - self.state = "play" - elif phase == "ecart": - self.state = "ecart" - else: - log.error("INTERNAL ERROR: unmanaged game phase") # FIXME: raise an exception here - - for suit, value in played_cards: - self.hand.append(self.cards[(suit, value)]) - - self.hand.sort() - self.updateHand() - if self._autoplay == None: # No dialog if there is autoplay - Window.alert('Cards played are invalid !') - self.__fakePlay() - - def removeFromSelection(self, card): - self.selected.remove(card) - if len(self.selected) == 6: - dialog.ConfirmDialog(self._ecartConfirm, "Put these cards into chien ?").show() - - def tarotGameChooseContrat(self, xml_data): - """Called when the player has to select his contrat - @param xml_data: SàT xml representation of the form""" - body = xmlui.create(self._parent.host, xml_data, flags=['NO_CANCEL']) - _dialog = dialog.GenericDialog(_('Please choose your contrat'), body, options=['NO_CLOSE']) - body.setCloseCb(_dialog.close) - _dialog.show() - - def tarotGameShowCards(self, game_stage, cards, data): - """Display cards in the middle of the game (to show for e.g. chien ou poignée)""" - self.to_show = [] - for suit, value in cards: - self.to_show.append(self.cards[(suit, value)]) - self.updateToShow() - if game_stage == "chien" and data['attaquant'] == self.player_nick: - self.state = "wait_for_ecart" - else: - self.state = "chien" - - def getPlayerLocation(self, nick): - """return player location (top,bottom,left or right)""" - for location in ['top', 'left', 'bottom', 'right']: - if getattr(self, '%s_nick' % location) == nick: - return location - log.error("This line should not be reached") - - def tarotGameCardsPlayed(self, player, cards): - """A card has been played by player""" - if not len(cards): - log.warning("cards should not be empty") - return - if len(cards) > 1: - log.error("can't manage several cards played") - if self.to_show: - self.to_show = [] - self.updateToShow() - suit, value = cards[0] - player_pos = self.getPlayerLocation(player) - player_panel = getattr(self, "inner_%s" % player_pos) - - if player_panel.getWidget() != None: - #We have already cards on the table, we remove them - for pos in ['top', 'left', 'bottom', 'right']: - getattr(self, "inner_%s" % pos).setWidget(None) - - card = self.cards[(suit, value)] - DOM.setElemAttribute(card.getElement(), "style", "") - player_panel.setWidget(card) - - def tarotGameYourTurn(self): - """Called when we have to play :)""" - if self.state == "chien": - self.to_show = [] - self.updateToShow() - self.state = "play" - self.__fakePlay() - - def __fakePlay(self): - """Convenience method for stupid autoplay - /!\ don't forgot to comment any interactive dialog for invalid card""" - if self._autoplay == None: - return - if self._autoplay >= len(self.hand): - self._autoplay = 0 - card = self.hand[self._autoplay] - self._parent.host.bridge.call('tarotGamePlayCards', None, self.player_nick, self.referee, [(card.suit, card.value)]) - del self.hand[self._autoplay] - self.state = "wait" - self._autoplay += 1 - - def playCard(self, card): - self.hand.remove(card) - self._parent.host.bridge.call('tarotGamePlayCards', None, self.player_nick, self.referee, [(card.suit, card.value)]) - self.state = "wait" - self.updateHand() - - def tarotGameScore(self, xml_data, winners, loosers): - """Show score at the end of a round""" - if not winners and not loosers: - title = "Draw game" - else: - if self.player_nick in winners: - title = "You <b>win</b> !" - else: - title = "You <b>loose</b> :(" - body = xmlui.create(self._parent.host, xml_data, title=title, flags=['NO_CANCEL']) - _dialog = dialog.GenericDialog(title, body, options=['NO_CLOSE']) - body.setCloseCb(_dialog.close) - _dialog.show()
--- a/src/browser/sat_browser/chat.py Mon Mar 09 16:30:06 2015 +0100 +++ b/src/browser/sat_browser/chat.py Wed Mar 11 12:50:19 2015 +0100 @@ -23,7 +23,7 @@ from sat_frontends.tools.games import SYMBOLS from sat_frontends.tools import strings from sat_frontends.tools import jid -from sat_frontends.quick_frontend import quick_widgets +from sat_frontends.quick_frontend import quick_widgets, quick_games from sat_frontends.quick_frontend.quick_chat import QuickChat from sat.core.i18n import _ @@ -43,11 +43,11 @@ import base_panel import contact_panel import editor_widget -import card_game -import radiocol import contact_list from constants import Const as C import plugin_xep_0085 +import game_tarot +import game_radiocol unicode = str # FIXME: pyjamas workaround @@ -281,32 +281,6 @@ self.content.add(ChatText(nick, mymess, msg, extra)) self.content_scroll.scrollToBottom() - def startGame(self, game_type, waiting, referee, players, *args): - """Configure the chat window to start a game""" - classes = {"Tarot": card_game.CardPanel, "RadioCol": radiocol.RadioColPanel} - if game_type not in classes.keys(): - return # unknown game - attr = game_type.lower() - # self.occupants_panel.updateSpecials(players, SYMBOLS[attr]) # FIXME - if waiting or not self.nick in players: - return # waiting for player or not playing - attr = "%s_panel" % attr - if hasattr(self, attr): - return - log.info("%s Game Started \o/" % game_type) - panel = classes[game_type](self, referee, self.nick, players, *args) - setattr(self, attr, panel) - self.vpanel.insert(panel, 0) - self.vpanel.setCellHeight(panel, panel.getHeight()) - - def getGame(self, game_type): - """Return class managing the game type""" - # TODO: check that the game is launched, and manage errors - if game_type == "Tarot": - return self.tarot_panel - elif game_type == "RadioCol": - return self.radiocol_panel - def setState(self, state, nick=None): """Set the chat state (XEP-0085) of the contact. Leave nick to None to set the state for a one2one conversation, or give a nickname or @@ -351,6 +325,23 @@ #TODO pass + def addGamePanel(self, widget): + """Insert a game panel to this Chat dialog. + + @param widget (Widget): the game panel + """ + self.vpanel.insert(widget, 0) + self.vpanel.setCellHeight(widget, widget.getHeight()) + + def removeGamePanel(self, widget): + """Remove the game panel from this Chat dialog. + + @param widget (Widget): the game panel + """ + self.vpanel.remove(widget) + quick_widgets.register(QuickChat, Chat) +quick_widgets.register(quick_games.Tarot, game_tarot.TarotPanel) +quick_widgets.register(quick_games.Radiocol, game_radiocol.RadioColPanel) libervia_widget.LiberviaWidget.addDropKey("CONTACT", lambda host, item: host.displayWidget(Chat, jid.JID(item), dropped=True))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/browser/sat_browser/game_radiocol.py Wed Mar 11 12:50:19 2015 +0100 @@ -0,0 +1,322 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Libervia: a Salut à Toi frontend +# Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson <goffi@goffi.org> + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import pyjd # this is dummy in pyjs +from sat.core.log import getLogger +log = getLogger(__name__) + +from sat.core.i18n import _ +from sat_frontends.tools.misc import DEFAULT_MUC +from constants import Const as C + +from pyjamas.ui.VerticalPanel import VerticalPanel +from pyjamas.ui.HorizontalPanel import HorizontalPanel +from pyjamas.ui.FlexTable import FlexTable +from pyjamas.ui.FormPanel import FormPanel +from pyjamas.ui.Label import Label +from pyjamas.ui.Button import Button +from pyjamas.ui.ClickListener import ClickHandler +from pyjamas.ui.Hidden import Hidden +from pyjamas.ui.CaptionPanel import CaptionPanel +from pyjamas.media.Audio import Audio +from pyjamas import Window +from pyjamas.Timer import Timer + +import html_tools +import file_tools + + +class MetadataPanel(FlexTable): + + def __init__(self): + FlexTable.__init__(self) + title_lbl = Label("title:") + title_lbl.setStyleName('radiocol_metadata_lbl') + artist_lbl = Label("artist:") + artist_lbl.setStyleName('radiocol_metadata_lbl') + album_lbl = Label("album:") + album_lbl.setStyleName('radiocol_metadata_lbl') + self.title = Label("") + self.title.setStyleName('radiocol_metadata') + self.artist = Label("") + self.artist.setStyleName('radiocol_metadata') + self.album = Label("") + self.album.setStyleName('radiocol_metadata') + self.setWidget(0, 0, title_lbl) + self.setWidget(1, 0, artist_lbl) + self.setWidget(2, 0, album_lbl) + self.setWidget(0, 1, self.title) + self.setWidget(1, 1, self.artist) + self.setWidget(2, 1, self.album) + self.setStyleName("radiocol_metadata_pnl") + + def setTitle(self, title): + self.title.setText(title) + + def setArtist(self, artist): + self.artist.setText(artist) + + def setAlbum(self, album): + self.album.setText(album) + + +class ControlPanel(FormPanel): + """Panel used to show controls to add a song, or vote for the current one""" + + def __init__(self, parent): + FormPanel.__init__(self) + self.setEncoding(FormPanel.ENCODING_MULTIPART) + self.setMethod(FormPanel.METHOD_POST) + self.setAction("upload_radiocol") + self.timer_on = False + self._parent = parent + vPanel = VerticalPanel() + + types = [('audio/ogg', '*.ogg', 'Ogg Vorbis Audio'), + ('video/ogg', '*.ogv', 'Ogg Vorbis Video'), + ('application/ogg', '*.ogx', 'Ogg Vorbis Multiplex'), + ('audio/mpeg', '*.mp3', 'MPEG-Layer 3'), + ('audio/mp3', '*.mp3', 'MPEG-Layer 3'), + ] + self.file_upload = file_tools.FilterFileUpload("song", 10, types) + vPanel.add(self.file_upload) + + hPanel = HorizontalPanel() + self.upload_btn = Button("Upload song", getattr(self, "onBtnClick")) + hPanel.add(self.upload_btn) + self.status = Label() + self.updateStatus() + hPanel.add(self.status) + #We need to know the filename and the referee + self.filename_field = Hidden('filename', '') + hPanel.add(self.filename_field) + referee_field = Hidden('referee', self._parent.referee) + hPanel.add(self.filename_field) + hPanel.add(referee_field) + vPanel.add(hPanel) + + self.add(vPanel) + self.addFormHandler(self) + + def updateStatus(self): + if self.timer_on: + return + # TODO: the status should be different if a song is being played or not + queue = self._parent.getQueueSize() + queue_data = self._parent.queue_data + if queue < queue_data[0]: + left = queue_data[0] - queue + self.status.setText("[we need %d more song%s]" % (left, "s" if left > 1 else "")) + elif queue < queue_data[1]: + left = queue_data[1] - queue + self.status.setText("[%d available spot%s]" % (left, "s" if left > 1 else "")) + elif queue >= queue_data[1]: + self.status.setText("[The queue is currently full]") + self.status.setStyleName('radiocol_status') + + def onBtnClick(self): + if self.file_upload.check(): + self.status.setText('[Submitting, please wait...]') + self.filename_field.setValue(self.file_upload.getFilename()) + if self.file_upload.getFilename().lower().endswith('.mp3'): + self._parent._parent.host.showWarning('STATUS', 'For a better support, it is recommended to submit Ogg Vorbis file instead of MP3. You can convert your files easily, ask for help if needed!', 5000) + self.submit() + self.file_upload.setFilename("") + + def onSubmit(self, event): + pass + + def blockUpload(self): + self.file_upload.setVisible(False) + self.upload_btn.setEnabled(False) + + def unblockUpload(self): + self.file_upload.setVisible(True) + self.upload_btn.setEnabled(True) + + def setTemporaryStatus(self, text, style): + self.status.setText(text) + self.status.setStyleName('radiocol_upload_status_%s' % style) + self.timer_on = True + + def cb(timer): + self.timer_on = False + self.updateStatus() + + Timer(5000, cb) + + def onSubmitComplete(self, event): + result = event.getResults() + if result == C.UPLOAD_OK: + # the song can still be rejected (not readable, full queue...) + self.setTemporaryStatus('[Your song has been submitted to the radio]', "ok") + elif result == C.UPLOAD_KO: + self.setTemporaryStatus('[Something went wrong during your song upload]', "ko") + self._parent.radiocolSongRejectedHandler(_("The uploaded file has been rejected, only Ogg Vorbis and MP3 songs are accepted.")) + # TODO: would be great to re-use the original Exception class and message + # but it is lost in the middle of the traceback and encapsulated within + # a DBusException instance --> extract the data from the traceback? + else: + Window.alert(_('Submit error: %s' % result)) + self.status.setText('') + + +class Player(Audio): + + def __init__(self, player_id, metadata_panel): + Audio.__init__(self) + self._id = player_id + self.metadata = metadata_panel + self.timestamp = "" + self.title = "" + self.artist = "" + self.album = "" + self.filename = None + self.played = False # True when the song is playing/has played, becomes False on preload + self.setAutobuffer(True) + self.setAutoplay(False) + self.setVisible(False) + + def preload(self, timestamp, filename, title, artist, album): + """preload the song but doesn't play it""" + self.timestamp = timestamp + self.filename = filename + self.title = title + self.artist = artist + self.album = album + self.played = False + self.setSrc("radiocol/%s" % html_tools.html_sanitize(filename)) + log.debug("preloading %s in %s" % (title, self._id)) + + def play(self, play=True): + """Play or pause the song + @param play: set to True to play or to False to pause + """ + if play: + self.played = True + self.metadata.setTitle(self.title) + self.metadata.setArtist(self.artist) + self.metadata.setAlbum(self.album) + Audio.play(self) + else: + self.pause() + + +class RadioColPanel(HorizontalPanel, ClickHandler): + + def __init__(self, parent, referee, players, queue_data): + """ + @param parent + @param referee + @param players + @param queue_data: list of integers (queue to start, queue limit) + """ + # We need to set it here and not in the CSS :( + HorizontalPanel.__init__(self, Height="90px") + ClickHandler.__init__(self) + self._parent = parent + self.referee = referee + self.queue_data = queue_data + self.setStyleName("radiocolPanel") + + # Now we set up the layout + self.metadata_panel = MetadataPanel() + self.add(CaptionPanel("Now playing", self.metadata_panel)) + self.playlist_panel = VerticalPanel() + self.add(CaptionPanel("Songs queue", self.playlist_panel)) + self.control_panel = ControlPanel(self) + self.add(CaptionPanel("Controls", self.control_panel)) + + self.next_songs = [] + self.players = [Player("player_%d" % i, self.metadata_panel) for i in xrange(queue_data[1] + 1)] + self.current_player = None + for player in self.players: + self.add(player) + self.addClickListener(self) + + help_msg = """Accepted file formats: Ogg Vorbis (recommended), MP3.<br /> + Please do not submit files that are protected by copyright.<br /> + Click <a style="color: red;">here</a> if you need some support :)""" + link_cb = lambda: self._parent.host.bridge.call('joinMUC', None, DEFAULT_MUC, self._parent.nick) + self._parent.printInfo(help_msg, type_='link', link_cb=link_cb) + + def pushNextSong(self, title): + """Add a song to the left panel's next songs queue""" + next_song = Label(title) + next_song.setStyleName("radiocol_next_song") + self.next_songs.append(next_song) + self.playlist_panel.append(next_song) + self.control_panel.updateStatus() + + def popNextSong(self): + """Remove the first song of next songs list + should be called when the song is played""" + #FIXME: should check that the song we remove is the one we play + next_song = self.next_songs.pop(0) + self.playlist_panel.remove(next_song) + self.control_panel.updateStatus() + + def getQueueSize(self): + return len(self.playlist_panel.getChildren()) + + def radiocolCheckPreload(self, timestamp): + for player in self.players: + if player.timestamp == timestamp: + return False + return True + + def radiocolPreloadHandler(self, timestamp, filename, title, artist, album, sender): + if not self.radiocolCheckPreload(timestamp): + return # song already preloaded + preloaded = False + for player in self.players: + if not player.filename or \ + (player.played and player != self.current_player): + #if player has no file loaded, or it has already played its song + #we use it to preload the next one + player.preload(timestamp, filename, title, artist, album) + preloaded = True + break + if not preloaded: + log.warning("Can't preload song, we are getting too many songs to preload, we shouldn't have more than %d at once" % self.queue_data[1]) + else: + self.pushNextSong(title) + self._parent.printInfo(_('%(user)s uploaded %(artist)s - %(title)s') % {'user': sender, 'artist': artist, 'title': title}) + + def radiocolPlayHandler(self, filename): + found = False + for player in self.players: + if not found and player.filename == filename: + player.play() + self.popNextSong() + self.current_player = player + found = True + else: + player.play(False) # in case the previous player was not sync + if not found: + log.error("Song not found in queue, can't play it. This should not happen") + + def radiocolNoUploadHandler(self): + self.control_panel.blockUpload() + + def radiocolUploadOkHandler(self): + self.control_panel.unblockUpload() + + def radiocolSongRejectedHandler(self, reason): + Window.alert("Song rejected: %s" % reason)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/browser/sat_browser/game_tarot.py Wed Mar 11 12:50:19 2015 +0100 @@ -0,0 +1,388 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Libervia: a Salut à Toi frontend +# Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson <goffi@goffi.org> + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import pyjd # this is dummy in pyjs +from sat.core.log import getLogger +log = getLogger(__name__) + +from sat.core.i18n import _ +from sat_frontends.tools.games import TarotCard + +from pyjamas.ui.AbsolutePanel import AbsolutePanel +from pyjamas.ui.DockPanel import DockPanel +from pyjamas.ui.SimplePanel import SimplePanel +from pyjamas.ui.Image import Image +from pyjamas.ui.Label import Label +from pyjamas.ui.ClickListener import ClickHandler +from pyjamas.ui.MouseListener import MouseHandler +from pyjamas.ui import HasAlignment +from pyjamas import Window +from pyjamas import DOM + +import dialog +import xmlui + + +CARD_WIDTH = 74 +CARD_HEIGHT = 136 +CARD_DELTA_Y = 30 +MIN_WIDTH = 950 # Minimum size of the panel +MIN_HEIGHT = 500 + + +class CardWidget(TarotCard, Image, MouseHandler): + """This class is used to represent a card, graphically and logically""" + + def __init__(self, parent, file_): + """@param file: path of the PNG file""" + self._parent = parent + Image.__init__(self, file_) + root_name = file_[file_.rfind("/") + 1:-4] + suit, value = root_name.split('_') + TarotCard.__init__(self, (suit, value)) + MouseHandler.__init__(self) + self.addMouseListener(self) + + def onMouseEnter(self, sender): + if self._parent.state == "ecart" or self._parent.state == "play": + DOM.setStyleAttribute(self.getElement(), "top", "0px") + + def onMouseLeave(self, sender): + if not self in self._parent.hand: + return + if not self in list(self._parent.selected): # FIXME: Workaround pyjs bug, must report it + DOM.setStyleAttribute(self.getElement(), "top", "%dpx" % CARD_DELTA_Y) + + def onMouseUp(self, sender, x, y): + if self._parent.state == "ecart": + if self not in list(self._parent.selected): + self._parent.addToSelection(self) + else: + self._parent.removeFromSelection(self) + elif self._parent.state == "play": + self._parent.playCard(self) + + +class TarotPanel(DockPanel, ClickHandler): + + def __init__(self, parent, referee, players): + DockPanel.__init__(self) + ClickHandler.__init__(self) + self._parent = parent + self._autoplay = None # XXX: use 0 to activate fake play, None else + self.referee = referee + self.players = players + self.player_nick = parent.nick + self.bottom_nick = self.player_nick + idx = self.players.index(self.player_nick) + idx = (idx + 1) % len(self.players) + self.right_nick = self.players[idx] + idx = (idx + 1) % len(self.players) + self.top_nick = self.players[idx] + idx = (idx + 1) % len(self.players) + self.left_nick = self.players[idx] + self.bottom_nick = self.player_nick + self.selected = set() # Card choosed by the player (e.g. during ecart) + self.hand_size = 13 # number of cards in a hand + self.hand = [] + self.to_show = [] + self.state = None + self.setSize("%dpx" % MIN_WIDTH, "%dpx" % MIN_HEIGHT) + self.setStyleName("cardPanel") + + # Now we set up the layout + _label = Label(self.top_nick) + _label.setStyleName('cardGamePlayerNick') + self.add(_label, DockPanel.NORTH) + self.setCellWidth(_label, '100%') + self.setCellHorizontalAlignment(_label, HasAlignment.ALIGN_CENTER) + + self.hand_panel = AbsolutePanel() + self.add(self.hand_panel, DockPanel.SOUTH) + self.setCellWidth(self.hand_panel, '100%') + self.setCellHorizontalAlignment(self.hand_panel, HasAlignment.ALIGN_CENTER) + + _label = Label(self.left_nick) + _label.setStyleName('cardGamePlayerNick') + self.add(_label, DockPanel.WEST) + self.setCellHeight(_label, '100%') + self.setCellVerticalAlignment(_label, HasAlignment.ALIGN_MIDDLE) + + _label = Label(self.right_nick) + _label.setStyleName('cardGamePlayerNick') + self.add(_label, DockPanel.EAST) + self.setCellHeight(_label, '100%') + self.setCellHorizontalAlignment(_label, HasAlignment.ALIGN_RIGHT) + self.setCellVerticalAlignment(_label, HasAlignment.ALIGN_MIDDLE) + + self.center_panel = DockPanel() + self.inner_left = SimplePanel() + self.inner_left.setSize("%dpx" % CARD_WIDTH, "%dpx" % CARD_HEIGHT) + self.center_panel.add(self.inner_left, DockPanel.WEST) + self.center_panel.setCellHeight(self.inner_left, '100%') + self.center_panel.setCellHorizontalAlignment(self.inner_left, HasAlignment.ALIGN_RIGHT) + self.center_panel.setCellVerticalAlignment(self.inner_left, HasAlignment.ALIGN_MIDDLE) + + self.inner_right = SimplePanel() + self.inner_right.setSize("%dpx" % CARD_WIDTH, "%dpx" % CARD_HEIGHT) + self.center_panel.add(self.inner_right, DockPanel.EAST) + self.center_panel.setCellHeight(self.inner_right, '100%') + self.center_panel.setCellVerticalAlignment(self.inner_right, HasAlignment.ALIGN_MIDDLE) + + self.inner_top = SimplePanel() + self.inner_top.setSize("%dpx" % CARD_WIDTH, "%dpx" % CARD_HEIGHT) + self.center_panel.add(self.inner_top, DockPanel.NORTH) + self.center_panel.setCellHorizontalAlignment(self.inner_top, HasAlignment.ALIGN_CENTER) + self.center_panel.setCellVerticalAlignment(self.inner_top, HasAlignment.ALIGN_BOTTOM) + + self.inner_bottom = SimplePanel() + self.inner_bottom.setSize("%dpx" % CARD_WIDTH, "%dpx" % CARD_HEIGHT) + self.center_panel.add(self.inner_bottom, DockPanel.SOUTH) + self.center_panel.setCellHorizontalAlignment(self.inner_bottom, HasAlignment.ALIGN_CENTER) + self.center_panel.setCellVerticalAlignment(self.inner_bottom, HasAlignment.ALIGN_TOP) + + self.inner_center = SimplePanel() + self.center_panel.add(self.inner_center, DockPanel.CENTER) + self.center_panel.setCellHorizontalAlignment(self.inner_center, HasAlignment.ALIGN_CENTER) + self.center_panel.setCellVerticalAlignment(self.inner_center, HasAlignment.ALIGN_MIDDLE) + + self.add(self.center_panel, DockPanel.CENTER) + self.setCellWidth(self.center_panel, '100%') + self.setCellHeight(self.center_panel, '100%') + self.setCellVerticalAlignment(self.center_panel, HasAlignment.ALIGN_MIDDLE) + self.setCellHorizontalAlignment(self.center_panel, HasAlignment.ALIGN_CENTER) + + self.loadCards() + self.mouse_over_card = None # contain the card to highlight + self.visible_size = CARD_WIDTH / 2 # number of pixels visible for cards + self.addClickListener(self) + + def loadCards(self): + """Load all the cards in memory""" + def _getTarotCardsPathsCb(paths): + log.debug("_getTarotCardsPathsCb") + for file_ in paths: + log.debug("path: %s" % file_) + card = CardWidget(self, file_) + log.debug("card: %s" % card) + self.cards[(card.suit, card.value)] = card + self.deck.append(card) + self._parent.host.bridge.call('tarotGameReady', None, self.player_nick, self.referee) + self.cards = {} + self.deck = [] + self.cards["atout"] = {} # As Tarot is a french game, it's more handy & logical to keep french names + self.cards["pique"] = {} # spade + self.cards["coeur"] = {} # heart + self.cards["carreau"] = {} # diamond + self.cards["trefle"] = {} # club + self._parent.host.bridge.call('getTarotCardsPaths', _getTarotCardsPathsCb) + + def onClick(self, sender): + if self.state == "chien": + self.to_show = [] + self.state = "wait" + self.updateToShow() + elif self.state == "wait_for_ecart": + self.state = "ecart" + self.hand.extend(self.to_show) + self.hand.sort() + self.to_show = [] + self.updateToShow() + self.updateHand() + + def tarotGameNewHandler(self, hand): + """Start a new game, with given hand""" + if hand is []: # reset the display after the scores have been showed + self.selected.clear() + del self.hand[:] + del self.to_show[:] + self.state = None + #empty hand + self.updateHand() + #nothing on the table + self.updateToShow() + for pos in ['top', 'left', 'bottom', 'right']: + getattr(self, "inner_%s" % pos).setWidget(None) + self._parent.host.bridge.call('tarotGameReady', None, self.player_nick, self.referee) + return + for suit, value in hand: + self.hand.append(self.cards[(suit, value)]) + self.hand.sort() + self.state = "init" + self.updateHand() + + def updateHand(self): + """Show the cards in the hand in the hand_panel (SOUTH panel)""" + self.hand_panel.clear() + self.hand_panel.setSize("%dpx" % (self.visible_size * (len(self.hand) + 1)), "%dpx" % (CARD_HEIGHT + CARD_DELTA_Y + 10)) + x_pos = 0 + y_pos = CARD_DELTA_Y + for card in self.hand: + self.hand_panel.add(card, x_pos, y_pos) + x_pos += self.visible_size + + def updateToShow(self): + """Show cards in the center panel""" + if not self.to_show: + _widget = self.inner_center.getWidget() + if _widget: + self.inner_center.remove(_widget) + return + panel = AbsolutePanel() + panel.setSize("%dpx" % ((CARD_WIDTH + 5) * len(self.to_show) - 5), "%dpx" % (CARD_HEIGHT)) + x_pos = 0 + y_pos = 0 + for card in self.to_show: + panel.add(card, x_pos, y_pos) + x_pos += CARD_WIDTH + 5 + self.inner_center.setWidget(panel) + + def _ecartConfirm(self, confirm): + if not confirm: + return + ecart = [] + for card in self.selected: + ecart.append((card.suit, card.value)) + self.hand.remove(card) + self.selected.clear() + self._parent.host.bridge.call('tarotGamePlayCards', None, self.player_nick, self.referee, ecart) + self.state = "wait" + self.updateHand() + + def addToSelection(self, card): + self.selected.add(card) + if len(self.selected) == 6: + dialog.ConfirmDialog(self._ecartConfirm, "Put these cards into chien ?").show() + + def tarotGameInvalidCardsHandler(self, phase, played_cards, invalid_cards): + """Invalid cards have been played + @param phase: phase of the game + @param played_cards: all the cards played + @param invalid_cards: cards which are invalid""" + + if phase == "play": + self.state = "play" + elif phase == "ecart": + self.state = "ecart" + else: + log.error("INTERNAL ERROR: unmanaged game phase") # FIXME: raise an exception here + + for suit, value in played_cards: + self.hand.append(self.cards[(suit, value)]) + + self.hand.sort() + self.updateHand() + if self._autoplay == None: # No dialog if there is autoplay + Window.alert('Cards played are invalid !') + self.__fakePlay() + + def removeFromSelection(self, card): + self.selected.remove(card) + if len(self.selected) == 6: + dialog.ConfirmDialog(self._ecartConfirm, "Put these cards into chien ?").show() + + def tarotGameChooseContratHandler(self, xml_data): + """Called when the player has to select his contrat + @param xml_data: SàT xml representation of the form""" + body = xmlui.create(self._parent.host, xml_data, flags=['NO_CANCEL']) + _dialog = dialog.GenericDialog(_('Please choose your contrat'), body, options=['NO_CLOSE']) + body.setCloseCb(_dialog.close) + _dialog.show() + + def tarotGameShowCardsHandler(self, game_stage, cards, data): + """Display cards in the middle of the game (to show for e.g. chien ou poignée)""" + self.to_show = [] + for suit, value in cards: + self.to_show.append(self.cards[(suit, value)]) + self.updateToShow() + if game_stage == "chien" and data['attaquant'] == self.player_nick: + self.state = "wait_for_ecart" + else: + self.state = "chien" + + def getPlayerLocation(self, nick): + """return player location (top,bottom,left or right)""" + for location in ['top', 'left', 'bottom', 'right']: + if getattr(self, '%s_nick' % location) == nick: + return location + log.error("This line should not be reached") + + def tarotGameCardsPlayedHandler(self, player, cards): + """A card has been played by player""" + if not len(cards): + log.warning("cards should not be empty") + return + if len(cards) > 1: + log.error("can't manage several cards played") + if self.to_show: + self.to_show = [] + self.updateToShow() + suit, value = cards[0] + player_pos = self.getPlayerLocation(player) + player_panel = getattr(self, "inner_%s" % player_pos) + + if player_panel.getWidget() != None: + #We have already cards on the table, we remove them + for pos in ['top', 'left', 'bottom', 'right']: + getattr(self, "inner_%s" % pos).setWidget(None) + + card = self.cards[(suit, value)] + DOM.setElemAttribute(card.getElement(), "style", "") + player_panel.setWidget(card) + + def tarotGameYourTurnHandler(self): + """Called when we have to play :)""" + if self.state == "chien": + self.to_show = [] + self.updateToShow() + self.state = "play" + self.__fakePlay() + + def __fakePlay(self): + """Convenience method for stupid autoplay + /!\ don't forgot to comment any interactive dialog for invalid card""" + if self._autoplay == None: + return + if self._autoplay >= len(self.hand): + self._autoplay = 0 + card = self.hand[self._autoplay] + self._parent.host.bridge.call('tarotGamePlayCards', None, self.player_nick, self.referee, [(card.suit, card.value)]) + del self.hand[self._autoplay] + self.state = "wait" + self._autoplay += 1 + + def playCard(self, card): + self.hand.remove(card) + self._parent.host.bridge.call('tarotGamePlayCards', None, self.player_nick, self.referee, [(card.suit, card.value)]) + self.state = "wait" + self.updateHand() + + def tarotGameScoreHandler(self, xml_data, winners, loosers): + """Show score at the end of a round""" + if not winners and not loosers: + title = "Draw game" + else: + if self.player_nick in winners: + title = "You <b>win</b> !" + else: + title = "You <b>loose</b> :(" + body = xmlui.create(self._parent.host, xml_data, title=title, flags=['NO_CANCEL']) + _dialog = dialog.GenericDialog(title, body, options=['NO_CLOSE']) + body.setCloseCb(_dialog.close) + _dialog.show()
--- a/src/browser/sat_browser/radiocol.py Mon Mar 09 16:30:06 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,322 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Libervia: a Salut à Toi frontend -# Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson <goffi@goffi.org> - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero 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 Affero General Public License for more details. - -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import pyjd # this is dummy in pyjs -from sat.core.log import getLogger -log = getLogger(__name__) -from sat_frontends.tools.misc import DEFAULT_MUC -from sat.core.i18n import _ -from constants import Const as C - -from pyjamas.ui.VerticalPanel import VerticalPanel -from pyjamas.ui.HorizontalPanel import HorizontalPanel -from pyjamas.ui.FlexTable import FlexTable -from pyjamas.ui.FormPanel import FormPanel -from pyjamas.ui.Label import Label -from pyjamas.ui.Button import Button -from pyjamas.ui.ClickListener import ClickHandler -from pyjamas.ui.Hidden import Hidden -from pyjamas.ui.CaptionPanel import CaptionPanel -from pyjamas.media.Audio import Audio -from pyjamas import Window -from pyjamas.Timer import Timer - -import html_tools -import file_tools - - -class MetadataPanel(FlexTable): - - def __init__(self): - FlexTable.__init__(self) - title_lbl = Label("title:") - title_lbl.setStyleName('radiocol_metadata_lbl') - artist_lbl = Label("artist:") - artist_lbl.setStyleName('radiocol_metadata_lbl') - album_lbl = Label("album:") - album_lbl.setStyleName('radiocol_metadata_lbl') - self.title = Label("") - self.title.setStyleName('radiocol_metadata') - self.artist = Label("") - self.artist.setStyleName('radiocol_metadata') - self.album = Label("") - self.album.setStyleName('radiocol_metadata') - self.setWidget(0, 0, title_lbl) - self.setWidget(1, 0, artist_lbl) - self.setWidget(2, 0, album_lbl) - self.setWidget(0, 1, self.title) - self.setWidget(1, 1, self.artist) - self.setWidget(2, 1, self.album) - self.setStyleName("radiocol_metadata_pnl") - - def setTitle(self, title): - self.title.setText(title) - - def setArtist(self, artist): - self.artist.setText(artist) - - def setAlbum(self, album): - self.album.setText(album) - - -class ControlPanel(FormPanel): - """Panel used to show controls to add a song, or vote for the current one""" - - def __init__(self, parent): - FormPanel.__init__(self) - self.setEncoding(FormPanel.ENCODING_MULTIPART) - self.setMethod(FormPanel.METHOD_POST) - self.setAction("upload_radiocol") - self.timer_on = False - self._parent = parent - vPanel = VerticalPanel() - - types = [('audio/ogg', '*.ogg', 'Ogg Vorbis Audio'), - ('video/ogg', '*.ogv', 'Ogg Vorbis Video'), - ('application/ogg', '*.ogx', 'Ogg Vorbis Multiplex'), - ('audio/mpeg', '*.mp3', 'MPEG-Layer 3'), - ('audio/mp3', '*.mp3', 'MPEG-Layer 3'), - ] - self.file_upload = file_tools.FilterFileUpload("song", 10, types) - vPanel.add(self.file_upload) - - hPanel = HorizontalPanel() - self.upload_btn = Button("Upload song", getattr(self, "onBtnClick")) - hPanel.add(self.upload_btn) - self.status = Label() - self.updateStatus() - hPanel.add(self.status) - #We need to know the filename and the referee - self.filename_field = Hidden('filename', '') - hPanel.add(self.filename_field) - referee_field = Hidden('referee', self._parent.referee) - hPanel.add(self.filename_field) - hPanel.add(referee_field) - vPanel.add(hPanel) - - self.add(vPanel) - self.addFormHandler(self) - - def updateStatus(self): - if self.timer_on: - return - # TODO: the status should be different if a song is being played or not - queue = self._parent.getQueueSize() - queue_data = self._parent.queue_data - if queue < queue_data[0]: - left = queue_data[0] - queue - self.status.setText("[we need %d more song%s]" % (left, "s" if left > 1 else "")) - elif queue < queue_data[1]: - left = queue_data[1] - queue - self.status.setText("[%d available spot%s]" % (left, "s" if left > 1 else "")) - elif queue >= queue_data[1]: - self.status.setText("[The queue is currently full]") - self.status.setStyleName('radiocol_status') - - def onBtnClick(self): - if self.file_upload.check(): - self.status.setText('[Submitting, please wait...]') - self.filename_field.setValue(self.file_upload.getFilename()) - if self.file_upload.getFilename().lower().endswith('.mp3'): - self._parent._parent.host.showWarning('STATUS', 'For a better support, it is recommended to submit Ogg Vorbis file instead of MP3. You can convert your files easily, ask for help if needed!', 5000) - self.submit() - self.file_upload.setFilename("") - - def onSubmit(self, event): - pass - - def blockUpload(self): - self.file_upload.setVisible(False) - self.upload_btn.setEnabled(False) - - def unblockUpload(self): - self.file_upload.setVisible(True) - self.upload_btn.setEnabled(True) - - def setTemporaryStatus(self, text, style): - self.status.setText(text) - self.status.setStyleName('radiocol_upload_status_%s' % style) - self.timer_on = True - - def cb(timer): - self.timer_on = False - self.updateStatus() - - Timer(5000, cb) - - def onSubmitComplete(self, event): - result = event.getResults() - if result == C.UPLOAD_OK: - # the song can still be rejected (not readable, full queue...) - self.setTemporaryStatus('[Your song has been submitted to the radio]', "ok") - elif result == C.UPLOAD_KO: - self.setTemporaryStatus('[Something went wrong during your song upload]', "ko") - self._parent.radiocolSongRejected(_("The uploaded file has been rejected, only Ogg Vorbis and MP3 songs are accepted.")) - # TODO: would be great to re-use the original Exception class and message - # but it is lost in the middle of the traceback and encapsulated within - # a DBusException instance --> extract the data from the traceback? - else: - Window.alert(_('Submit error: %s' % result)) - self.status.setText('') - - -class Player(Audio): - - def __init__(self, player_id, metadata_panel): - Audio.__init__(self) - self._id = player_id - self.metadata = metadata_panel - self.timestamp = "" - self.title = "" - self.artist = "" - self.album = "" - self.filename = None - self.played = False # True when the song is playing/has played, becomes False on preload - self.setAutobuffer(True) - self.setAutoplay(False) - self.setVisible(False) - - def preload(self, timestamp, filename, title, artist, album): - """preload the song but doesn't play it""" - self.timestamp = timestamp - self.filename = filename - self.title = title - self.artist = artist - self.album = album - self.played = False - self.setSrc("radiocol/%s" % html_tools.html_sanitize(filename)) - log.debug("preloading %s in %s" % (title, self._id)) - - def play(self, play=True): - """Play or pause the song - @param play: set to True to play or to False to pause - """ - if play: - self.played = True - self.metadata.setTitle(self.title) - self.metadata.setArtist(self.artist) - self.metadata.setAlbum(self.album) - Audio.play(self) - else: - self.pause() - - -class RadioColPanel(HorizontalPanel, ClickHandler): - - def __init__(self, parent, referee, player_nick, players, queue_data): - """ - @param parent - @param referee - @param player_nick - @param players - @param queue_data: list of integers (queue to start, queue limit) - """ - # We need to set it here and not in the CSS :( - HorizontalPanel.__init__(self, Height="90px") - ClickHandler.__init__(self) - self._parent = parent - self.referee = referee - self.queue_data = queue_data - self.setStyleName("radiocolPanel") - - # Now we set up the layout - self.metadata_panel = MetadataPanel() - self.add(CaptionPanel("Now playing", self.metadata_panel)) - self.playlist_panel = VerticalPanel() - self.add(CaptionPanel("Songs queue", self.playlist_panel)) - self.control_panel = ControlPanel(self) - self.add(CaptionPanel("Controls", self.control_panel)) - - self.next_songs = [] - self.players = [Player("player_%d" % i, self.metadata_panel) for i in xrange(queue_data[1] + 1)] - self.current_player = None - for player in self.players: - self.add(player) - self.addClickListener(self) - - help_msg = """Accepted file formats: Ogg Vorbis (recommended), MP3.<br /> - Please do not submit files that are protected by copyright.<br /> - Click <a style="color: red;">here</a> if you need some support :)""" - link_cb = lambda: self._parent.host.bridge.call('joinMUC', None, DEFAULT_MUC, self._parent.nick) - self._parent.printInfo(help_msg, type_='link', link_cb=link_cb) - - def pushNextSong(self, title): - """Add a song to the left panel's next songs queue""" - next_song = Label(title) - next_song.setStyleName("radiocol_next_song") - self.next_songs.append(next_song) - self.playlist_panel.append(next_song) - self.control_panel.updateStatus() - - def popNextSong(self): - """Remove the first song of next songs list - should be called when the song is played""" - #FIXME: should check that the song we remove is the one we play - next_song = self.next_songs.pop(0) - self.playlist_panel.remove(next_song) - self.control_panel.updateStatus() - - def getQueueSize(self): - return len(self.playlist_panel.getChildren()) - - def radiocolCheckPreload(self, timestamp): - for player in self.players: - if player.timestamp == timestamp: - return False - return True - - def radiocolPreload(self, timestamp, filename, title, artist, album, sender): - if not self.radiocolCheckPreload(timestamp): - return # song already preloaded - preloaded = False - for player in self.players: - if not player.filename or \ - (player.played and player != self.current_player): - #if player has no file loaded, or it has already played its song - #we use it to preload the next one - player.preload(timestamp, filename, title, artist, album) - preloaded = True - break - if not preloaded: - log.warning("Can't preload song, we are getting too many songs to preload, we shouldn't have more than %d at once" % self.queue_data[1]) - else: - self.pushNextSong(title) - self._parent.printInfo(_('%(user)s uploaded %(artist)s - %(title)s') % {'user': sender, 'artist': artist, 'title': title}) - - def radiocolPlay(self, filename): - found = False - for player in self.players: - if not found and player.filename == filename: - player.play() - self.popNextSong() - self.current_player = player - found = True - else: - player.play(False) # in case the previous player was not sync - if not found: - log.error("Song not found in queue, can't play it. This should not happen") - - def radiocolNoUpload(self): - self.control_panel.blockUpload() - - def radiocolUploadOk(self): - self.control_panel.unblockUpload() - - def radiocolSongRejected(self, reason): - Window.alert("Song rejected: %s" % reason)