Wix removal: Wix is now officially abandonned (a futur desktop frontend will replace it)
This commit is contained in:
parent
1b0d6ecc30
commit
f45e55388f
11 changed files with 0 additions and 2327 deletions
|
|
@ -1,261 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# wix: a SAT frontend
|
||||
# Copyright (C) 2009, 2010, 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/>.
|
||||
|
||||
|
||||
from sat.core.i18n import _
|
||||
import wx
|
||||
import os.path, glob
|
||||
from sat.core.log import getLogger
|
||||
log = getLogger(__name__)
|
||||
from sat_frontends.tools.games import TarotCard
|
||||
from sat_frontends.quick_frontend.quick_card_game import QuickCardGame
|
||||
from sat_frontends.wix import xmlui
|
||||
|
||||
CARD_WIDTH = 74
|
||||
CARD_HEIGHT = 136
|
||||
MIN_WIDTH = 950 #Minimum size of the panel
|
||||
MIN_HEIGHT = 500
|
||||
|
||||
|
||||
class WxCard(TarotCard):
|
||||
"""This class is used to represent a card, graphically and logically"""
|
||||
|
||||
def __init__(self, file):
|
||||
"""@param file: path of the PNG file"""
|
||||
self.bitmap = wx.Image(file).ConvertToBitmap()
|
||||
root_name = os.path.splitext(os.path.basename(file))[0]
|
||||
suit,value = root_name.split('_')
|
||||
TarotCard.__init__(self, (suit, value))
|
||||
log.debug("Card: %s %s" % (suit, value)) #, self.bout
|
||||
|
||||
def draw(self, dc, x, y):
|
||||
"""Draw the card on the device context
|
||||
@param dc: device context
|
||||
@param x: abscissa
|
||||
@param y: ordinate"""
|
||||
dc.DrawBitmap(self.bitmap, x, y, True)
|
||||
|
||||
|
||||
class CardPanel(QuickCardGame, wx.Panel):
|
||||
"""This class is used to display the cards"""
|
||||
|
||||
def __init__(self, parent, referee, players, player_nick):
|
||||
QuickCardGame.__init__(self, parent, referee, players, player_nick)
|
||||
wx.Panel.__init__(self, parent)
|
||||
self.SetMinSize(wx.Size(MIN_WIDTH, MIN_HEIGHT))
|
||||
self.loadCards(os.path.join(self.parent.host.media_dir, 'games/cards/tarot'))
|
||||
self.mouse_over_card = None #contain the card to highlight
|
||||
self.visible_size = CARD_WIDTH/2 #number of pixels visible for cards
|
||||
self.hand = []
|
||||
self.to_show = []
|
||||
self.state = None
|
||||
self.SetBackgroundColour(wx.GREEN)
|
||||
self.Bind(wx.EVT_SIZE, self.onResize)
|
||||
self.Bind(wx.EVT_PAINT, self.onPaint)
|
||||
self.Bind(wx.EVT_MOTION, self.onMouseMove)
|
||||
self.Bind(wx.EVT_LEFT_UP, self.onMouseClick)
|
||||
|
||||
self.parent.host.bridge.tarotGameReady(player_nick, referee, self.parent.host.profile)
|
||||
|
||||
def loadCards(self, dir):
|
||||
"""Load all the cards in memory
|
||||
@param dir: directory where the PNG files are"""
|
||||
QuickCardGame.loadCards(self)
|
||||
for file in glob.glob(dir+'/*_*.png'):
|
||||
card = WxCard(file)
|
||||
self.cards[card.suit, card.value]=card
|
||||
self.deck.append(card)
|
||||
|
||||
def newGame(self, hand):
|
||||
"""Start a new game, with given hand"""
|
||||
if hand is []: # reset the display after the scores have been showed
|
||||
self.resetRound()
|
||||
self.Refresh()
|
||||
self.parent.host.bridge.tarotGameReady(self.player_nick, self.referee, self.parent.host.profile)
|
||||
return
|
||||
QuickCardGame.newGame(self, hand)
|
||||
self._recalc_ori()
|
||||
self.Refresh()
|
||||
|
||||
def contratSelected(self, data):
|
||||
"""Called when the contrat has been choosed
|
||||
@param data: form result"""
|
||||
log.debug (_("Contrat choosed"))
|
||||
contrat = data[0][1]
|
||||
QuickCardGame.contratSelected(self, contrat)
|
||||
|
||||
def chooseContrat(self, xml_data):
|
||||
"""Called when the player has to select his contrat
|
||||
@param xml_data: SàT xml representation of the form"""
|
||||
xmlui.create(self.parent.host, xml_data, title=_('Please choose your contrat'), flags=['NO_CANCEL'])
|
||||
|
||||
def showScores(self, xml_data, winners, loosers):
|
||||
"""Called when the round is over, display the scores
|
||||
@param xml_data: SàT xml representation of the form"""
|
||||
if not winners and not loosers:
|
||||
title = _("Draw game")
|
||||
else:
|
||||
title = _('You win \o/') if self.player_nick in winners else _('You loose :(')
|
||||
xmlui.create(self.parent.host, xml_data, title=title, flags=['NO_CANCEL'])
|
||||
|
||||
def cardsPlayed(self, player, cards):
|
||||
"""A card has been played by player"""
|
||||
QuickCardGame.cardsPlayed(self, player, cards)
|
||||
self.Refresh()
|
||||
|
||||
def invalidCards(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"""
|
||||
QuickCardGame.invalidCards(self, phase, played_cards, invalid_cards)
|
||||
|
||||
self._recalc_ori()
|
||||
self.Refresh()
|
||||
if self._autoplay==None: #No dialog if there is autoplay
|
||||
wx.MessageDialog(self, _("Cards played are invalid !"), _("Error"), wx.OK | wx.ICON_ERROR).ShowModal()
|
||||
|
||||
def _is_on_hand(self, pos_x, pos_y):
|
||||
"""Return True if the coordinate are on the hand cards"""
|
||||
if pos_x > self.orig_x and pos_y > self.orig_y \
|
||||
and pos_x < self.orig_x + (len(self.hand)+1) * self.visible_size \
|
||||
and pos_y < self.end_y:
|
||||
return True
|
||||
return False
|
||||
|
||||
def onResize(self, event):
|
||||
self._recalc_ori()
|
||||
|
||||
def _recalc_ori(self):
|
||||
"""Recalculate origins of hand, must be call when hand size change"""
|
||||
self.orig_x = (self.GetSizeTuple()[0]-(len(self.hand)+1)*self.visible_size)/2 #where we start to draw cards
|
||||
self.orig_y = self.GetSizeTuple()[1] - CARD_HEIGHT - 20
|
||||
self.end_y = self.orig_y + CARD_HEIGHT
|
||||
|
||||
def onPaint(self, event):
|
||||
dc = wx.PaintDC(self)
|
||||
|
||||
#We print the names to know who play where TODO: print avatars when available
|
||||
max_x, max_y = self.GetSize()
|
||||
border = 10 #border between nick and end of panel
|
||||
right_y = left_y = 200
|
||||
right_width, right_height = dc.GetTextExtent(self.right_nick)
|
||||
right_x = max_x - right_width - border
|
||||
left_x = border
|
||||
top_width, top_height = dc.GetTextExtent(self.top_nick)
|
||||
top_x = (max_x - top_width) / 2
|
||||
top_y = border
|
||||
dc.DrawText(self.right_nick, right_x, right_y)
|
||||
dc.DrawText(self.top_nick, top_x, top_y)
|
||||
dc.DrawText(self.left_nick, left_x, left_y)
|
||||
|
||||
#We draw the played cards:
|
||||
center_y = 200 #ordinate used as center point
|
||||
left_x = (max_x - CARD_WIDTH)/2 - CARD_WIDTH - 5
|
||||
right_x = (max_x/2) + (CARD_WIDTH/2) + 5
|
||||
left_y = right_y = center_y - CARD_HEIGHT/2
|
||||
top_x = bottom_x = (max_x - CARD_WIDTH)/2
|
||||
top_y = center_y - CARD_HEIGHT - 5
|
||||
bottom_y = center_y + 5
|
||||
for side in ['left', 'top', 'right', 'bottom']:
|
||||
card = self.played[getattr(self, side+'_nick')]
|
||||
if card != None:
|
||||
card.draw(dc,locals()[side+'_x'], locals()[side+'_y'])
|
||||
|
||||
x=self.orig_x
|
||||
for card in self.hand:
|
||||
if (self.state == "play" or self.state == "ecart") and card == self.mouse_over_card \
|
||||
or self.state == "ecart" and card in self.selected:
|
||||
y = self.orig_y - 30
|
||||
else:
|
||||
y = self.orig_y
|
||||
|
||||
card.draw(dc,x,y)
|
||||
x+=self.visible_size
|
||||
|
||||
if self.to_show:
|
||||
"""There are cards to display in the middle"""
|
||||
size = len(self.to_show)*(CARD_WIDTH+10)-10
|
||||
x = (max_x - size)/2
|
||||
for card in self.to_show:
|
||||
card.draw(dc, x, 150)
|
||||
x+=CARD_WIDTH+10
|
||||
|
||||
def onMouseMove(self, event):
|
||||
pos_x,pos_y = event.GetPosition()
|
||||
if self._is_on_hand(pos_x, pos_y):
|
||||
try:
|
||||
self.mouse_over_card = self.hand[(pos_x-self.orig_x)/self.visible_size]
|
||||
except IndexError:
|
||||
self.mouse_over_card = self.hand[-1]
|
||||
self.Refresh()
|
||||
else:
|
||||
self.mouse_over_card = None
|
||||
self.Refresh()
|
||||
|
||||
def onMouseClick(self, event):
|
||||
log.debug("mouse click: %s" % event.GetPosition())
|
||||
pos_x,pos_y = event.GetPosition()
|
||||
|
||||
if self.state == "chien":
|
||||
self.to_show = []
|
||||
self.state = "wait"
|
||||
return
|
||||
elif self.state == "wait_for_ecart":
|
||||
self.state = "ecart"
|
||||
self.hand.extend(self.to_show)
|
||||
self.hand.sort()
|
||||
self.to_show = []
|
||||
self._recalc_ori()
|
||||
self.Refresh()
|
||||
return
|
||||
|
||||
if self._is_on_hand(pos_x, pos_y):
|
||||
idx = (pos_x-self.orig_x)/self.visible_size
|
||||
if idx == len(self.hand):
|
||||
idx-=1
|
||||
if self.hand[idx] == self.mouse_over_card:
|
||||
if self.state == "ecart":
|
||||
if self.hand[idx] in self.selected:
|
||||
self.selected.remove(self.hand[idx])
|
||||
else:
|
||||
self.selected.append(self.hand[idx])
|
||||
if len(self.selected) == 6: #TODO: use variable here, as chien len can change with variants
|
||||
dlg = wx.MessageDialog(self, _("Do you put these cards in chien ?"), _(u"Écart"), wx.YES_NO | wx.ICON_QUESTION)
|
||||
answer = dlg.ShowModal()
|
||||
if answer == wx.ID_YES:
|
||||
ecart = []
|
||||
for card in self.selected:
|
||||
ecart.append((card.suit, card.value))
|
||||
self.hand.remove(card)
|
||||
del self.selected[:]
|
||||
self.parent.host.bridge.tarotGamePlayCards(self.player_nick, self.referee, ecart, self.parent.host.profile)
|
||||
self.state = "wait"
|
||||
|
||||
self._recalc_ori()
|
||||
self.Refresh()
|
||||
if self.state == "play":
|
||||
card = self.hand[idx]
|
||||
self.parent.host.bridge.tarotGamePlayCards(self.player_nick, self.referee, [(card.suit, card.value)], self.parent.host.profile)
|
||||
del self.hand[idx]
|
||||
self.state = "wait"
|
||||
self._recalc_ori()
|
||||
self.Refresh()
|
||||
|
||||
|
||||
|
|
@ -1,289 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# wix: a SAT frontend
|
||||
# Copyright (C) 2009, 2010, 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/>.
|
||||
|
||||
|
||||
from sat.core.i18n import _
|
||||
from sat_frontends.wix.constants import Const as C
|
||||
import wx
|
||||
import os.path
|
||||
import time
|
||||
from sat.core.log import getLogger
|
||||
log = getLogger(__name__)
|
||||
from sat_frontends.tools.jid import JID
|
||||
from sat_frontends.quick_frontend.quick_chat import QuickChat
|
||||
from sat_frontends.wix.contact_list import ContactList
|
||||
from sat_frontends.wix.card_game import CardPanel
|
||||
from sat_frontends.wix.quiz_game import QuizPanel
|
||||
|
||||
idSEND = 1
|
||||
idTAROT = 2
|
||||
|
||||
|
||||
class Chat(wx.Frame, QuickChat):
|
||||
"""The chat Window for one to one conversations"""
|
||||
|
||||
def __init__(self, target, host, type_='one2one'):
|
||||
wx.Frame.__init__(self, None, title=target, pos=(0,0), size=(400,200))
|
||||
QuickChat.__init__(self, target, host, type_)
|
||||
|
||||
self.sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.SetSizer(self.sizer)
|
||||
|
||||
self.splitter = wx.SplitterWindow(self, -1)
|
||||
self.sizer.Add(self.splitter, 1, flag = wx.EXPAND)
|
||||
|
||||
self.conv_panel = wx.Panel(self.splitter)
|
||||
self.conv_panel.sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.subjectBox = wx.TextCtrl(self.conv_panel, -1, style = wx.TE_READONLY)
|
||||
self.chatWindow = wx.TextCtrl(self.conv_panel, -1, style = wx.TE_MULTILINE | wx.TE_RICH | wx.TE_READONLY)
|
||||
self.textBox = wx.TextCtrl(self.conv_panel, -1, style = wx.TE_PROCESS_ENTER)
|
||||
self.conv_panel.sizer.Add(self.subjectBox, flag=wx.EXPAND)
|
||||
self.conv_panel.sizer.Add(self.chatWindow, 1, flag=wx.EXPAND)
|
||||
self.conv_panel.sizer.Add(self.textBox, 0, flag=wx.EXPAND)
|
||||
self.conv_panel.SetSizer(self.conv_panel.sizer)
|
||||
self.splitter.Initialize(self.conv_panel)
|
||||
self.SetMenuBar(wx.MenuBar())
|
||||
|
||||
#events
|
||||
self.Bind(wx.EVT_CLOSE, self.onClose, self)
|
||||
self.Bind(wx.EVT_TEXT_ENTER, self.onEnterPressed, self.textBox)
|
||||
|
||||
#fonts
|
||||
self.font={}
|
||||
self.font["points"] = self.chatWindow.GetFont().GetPointSize()
|
||||
self.font["family"] = self.chatWindow.GetFont().GetFamily()
|
||||
|
||||
|
||||
#misc
|
||||
self.day_change = time.strptime(time.strftime("%a %b %d 00:00:00 %Y")) #struct_time of day changing time
|
||||
self.setType(self.type)
|
||||
self.textBox.SetFocus()
|
||||
self.Hide() #We hide because of the show toggle
|
||||
|
||||
def __createPresents(self):
|
||||
"""Create a list of present people in a group chat"""
|
||||
self.present_panel = wx.Panel(self.splitter)
|
||||
self.present_panel.sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.present_panel.presents = ContactList(self.present_panel, self.host, type_='nicks')
|
||||
self.present_panel.presents.SetMinSize(wx.Size(80,20))
|
||||
self.present_panel.sizer.Add(self.present_panel.presents, 1, wx.EXPAND)
|
||||
self.present_panel.SetSizer(self.present_panel.sizer)
|
||||
self.splitter.SplitVertically(self.present_panel, self.conv_panel, 80)
|
||||
|
||||
def setType(self, type_):
|
||||
QuickChat.setType(self, type_)
|
||||
if type_ is 'group' and not self.splitter.IsSplit():
|
||||
self.__createPresents()
|
||||
self.subjectBox.Show()
|
||||
self.__eraseMenus()
|
||||
self.__createMenus_group()
|
||||
self.sizer.Layout()
|
||||
elif type_ is 'one2one' and self.splitter.IsSplit():
|
||||
self.splitter.Unsplit(self.present_panel)
|
||||
del self.present_panel
|
||||
self.GetMenuBar().Show()
|
||||
self.subjectBox.Hide()
|
||||
self.__eraseMenus()
|
||||
self.__createMenus_O2O()
|
||||
self.nick = None
|
||||
else:
|
||||
self.subjectBox.Hide()
|
||||
self.__eraseMenus()
|
||||
self.__createMenus_O2O()
|
||||
self.historyPrint(profile=self.host.profile)
|
||||
|
||||
def startGame(self, game_type, referee, players):
|
||||
"""Configure the chat window to start a game"""
|
||||
if game_type=="Tarot":
|
||||
log.debug (_("configure chat window for Tarot game"))
|
||||
self.tarot_panel = CardPanel(self, referee, players, self.nick)
|
||||
self.sizer.Prepend(self.tarot_panel, 0, flag=wx.EXPAND)
|
||||
self.sizer.Layout()
|
||||
self.Fit()
|
||||
self.splitter.UpdateSize()
|
||||
elif game_type=="Quiz":
|
||||
log.debug (_("configure chat window for Quiz game"))
|
||||
self.quiz_panel = QuizPanel(self, referee, players, self.nick)
|
||||
self.sizer.Prepend(self.quiz_panel, 0, flag=wx.EXPAND)
|
||||
self.sizer.Layout()
|
||||
self.Fit()
|
||||
self.splitter.UpdateSize()
|
||||
|
||||
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=="Quiz":
|
||||
return self.quiz_panel
|
||||
|
||||
def setPresents(self, nicks):
|
||||
"""Set the users presents in the contact list for a group chat
|
||||
@param nicks: list of nicknames
|
||||
"""
|
||||
QuickChat.setPresents(self, nicks)
|
||||
for nick in nicks:
|
||||
self.present_panel.presents.replace(nick)
|
||||
|
||||
def replaceUser(self, nick, show_info=True):
|
||||
"""Add user if it is not in the group list"""
|
||||
log.debug (_("Replacing user %s") % nick)
|
||||
if self.type != "group":
|
||||
log.error (_("[INTERNAL] trying to replace user for a non group chat window"))
|
||||
return
|
||||
QuickChat.replaceUser(self, nick, show_info)
|
||||
self.present_panel.presents.replace(nick)
|
||||
|
||||
def removeUser(self, nick, show_info=True):
|
||||
"""Remove a user from the group list"""
|
||||
QuickChat.removeUser(self, nick, show_info)
|
||||
self.present_panel.presents.remove(nick)
|
||||
|
||||
def setSubject(self, subject):
|
||||
"""Set title for a group chat"""
|
||||
QuickChat.setSubject(self, subject)
|
||||
self.subjectBox.SetValue(subject)
|
||||
|
||||
def __eraseMenus(self):
|
||||
"""erase all menus"""
|
||||
menuBar = self.GetMenuBar()
|
||||
for i in range(menuBar.GetMenuCount()):
|
||||
menuBar.Remove(i)
|
||||
|
||||
def __createMenus_O2O(self):
|
||||
"""create menu bar for one 2 one chat"""
|
||||
log.info("Creating menus")
|
||||
self.__eraseMenus()
|
||||
menuBar = self.GetMenuBar()
|
||||
actionMenu = wx.Menu()
|
||||
actionMenu.Append(idSEND, _("&SendFile CTRL-s"),_(" Send a file to contact"))
|
||||
menuBar.Append(actionMenu,_("&Action"))
|
||||
self.host.addMenus(menuBar, C.MENU_SINGLE, {'jid': self.target})
|
||||
|
||||
#events
|
||||
wx.EVT_MENU(self, idSEND, self.onSendFile)
|
||||
|
||||
def __createMenus_group(self):
|
||||
"""create menu bar for group chat"""
|
||||
log.info("Creating menus")
|
||||
self.__eraseMenus()
|
||||
menuBar = self.GetMenuBar()
|
||||
actionMenu = wx.Menu()
|
||||
actionMenu.Append(idTAROT, _("Start &Tarot game CTRL-t"),_(" Start a Tarot card game")) #tmp
|
||||
menuBar.Append(actionMenu,_("&Games"))
|
||||
self.host.addMenus(menuBar, C.MENU_ROOM, {'room_jid': self.target.bare})
|
||||
|
||||
#events
|
||||
wx.EVT_MENU(self, idTAROT, self.onStartTarot)
|
||||
|
||||
def __del__(self):
|
||||
wx.Frame.__del__(self)
|
||||
|
||||
def onClose(self, event):
|
||||
"""Close event: we only hide the frame."""
|
||||
event.Veto()
|
||||
self.Hide()
|
||||
|
||||
def onEnterPressed(self, event):
|
||||
"""Behaviour when enter pressed in send line."""
|
||||
self.host.sendMessage(self.target.bare if self.type == 'group' else self.target,
|
||||
event.GetString(),
|
||||
mess_type="groupchat" if self.type == 'group' else "chat",
|
||||
profile_key=self.host.profile)
|
||||
self.textBox.Clear()
|
||||
|
||||
def __blink(self):
|
||||
"""Do wizzz and buzzz to show window to user or
|
||||
at least inform him of something new"""
|
||||
#TODO: use notification system
|
||||
if not self.IsActive():
|
||||
self.RequestUserAttention()
|
||||
if not self.IsShown():
|
||||
self.Show()
|
||||
|
||||
def printMessage(self, from_jid, msg, profile, timestamp=None):
|
||||
"""Print the message with differents colors depending on where it comes from."""
|
||||
try:
|
||||
jid,nick,mymess = QuickChat.printMessage(self, from_jid, msg, profile, timestamp)
|
||||
except TypeError:
|
||||
return
|
||||
log.debug("printMessage, jid = %s type = %s" % (jid, self.type))
|
||||
_font_bold = wx.Font(self.font["points"], self.font["family"], wx.NORMAL, wx.BOLD)
|
||||
_font_normal = wx.Font(self.font["points"], self.font["family"], wx.NORMAL, wx.NORMAL)
|
||||
_font_italic = wx.Font(self.font["points"], self.font["family"], wx.ITALIC if mymess else wx.NORMAL, wx.NORMAL)
|
||||
self.chatWindow.SetDefaultStyle(wx.TextAttr("GREY", font=_font_normal))
|
||||
msg_time = time.localtime(timestamp or None)
|
||||
time_format = "%c" if msg_time < self.day_change else "%H:%M" #if the message was sent before today, we print the full date
|
||||
self.chatWindow.AppendText("[%s]" % time.strftime(time_format, msg_time ))
|
||||
self.chatWindow.SetDefaultStyle(wx.TextAttr( "BLACK" if mymess else "BLUE", font=_font_bold))
|
||||
self.chatWindow.AppendText("[%s] " % nick)
|
||||
self.chatWindow.SetDefaultStyle(wx.TextAttr("BLACK", font=_font_italic))
|
||||
self.chatWindow.AppendText("%s\n" % msg)
|
||||
if not mymess:
|
||||
self.__blink()
|
||||
|
||||
def printInfo(self, msg, type_='normal', timestamp=None):
|
||||
"""Print general info
|
||||
@param msg: message to print
|
||||
@type_: one of:
|
||||
normal: general info like "toto has joined the room"
|
||||
me: "/me" information like "/me clenches his fist" ==> "toto clenches his fist"
|
||||
@param timestamp (float): number of seconds since epoch
|
||||
"""
|
||||
_font_bold = wx.Font(self.font["points"], self.font["family"], wx.NORMAL, wx.BOLD)
|
||||
_font_normal = wx.Font(self.font["points"], self.font["family"], wx.NORMAL, wx.NORMAL)
|
||||
self.chatWindow.SetDefaultStyle(wx.TextAttr("BLACK", font=_font_bold if type_ == 'normal' else _font_normal))
|
||||
self.chatWindow.AppendText("%s\n" % msg)
|
||||
if type_=="me":
|
||||
self.__blink()
|
||||
|
||||
### events ###
|
||||
|
||||
def onSendFile(self, e):
|
||||
log.debug(_("Send File"))
|
||||
filename = wx.FileSelector(_("Choose a file to send"), flags = wx.FD_FILE_MUST_EXIST)
|
||||
if filename:
|
||||
log.debug(_("filename: %s"),filename)
|
||||
#FIXME: check last_resource: what if self.target.resource exists ?
|
||||
last_resource = self.host.bridge.getLastResource(unicode(self.target.bare), self.host.profile)
|
||||
if last_resource:
|
||||
full_jid = JID("%s/%s" % (self.target.bare, last_resource))
|
||||
else:
|
||||
full_jid = self.target
|
||||
id = self.host.bridge.sendFile(full_jid, filename, {}, self.host.profile)
|
||||
self.host.waitProgress(id, _("File Transfer"), _("Copying %s") % os.path.basename(filename), self.host.profile)
|
||||
|
||||
def onStartTarot(self, e):
|
||||
log.debug(_("Starting Tarot game"))
|
||||
log.warning(_("FIXME: temporary menu, must be changed"))
|
||||
if len(self.occupants) != 4:
|
||||
err_dlg = wx.MessageDialog(self, _("You need to be exactly 4 peoples in the room to start a Tarot game"), _("Can't start game"), style = wx.OK | wx.ICON_ERROR) #FIXME: gof: temporary only, need to choose the people with who the game has to be started
|
||||
err_dlg.ShowModal()
|
||||
else:
|
||||
self.host.bridge.tarotGameCreate(self.id, list(self.occupants), self.host.profile)
|
||||
|
||||
def updateChatState(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
|
||||
Const.ALL_OCCUPANTS to set the state of a participant within a MUC.
|
||||
@param state: the new chat state
|
||||
@param nick: None for one2one, the MUC user nick or Const.ALL_OCCUPANTS
|
||||
"""
|
||||
#TODO: chat states not implemented yet
|
||||
pass
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Primitivus: a SAT frontend
|
||||
# Copyright (C) 2009, 2010, 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/>.
|
||||
|
||||
from sat.core.i18n import _
|
||||
import os.path
|
||||
import sat_frontends.wix
|
||||
from sat_frontends.quick_frontend import constants
|
||||
|
||||
|
||||
wix_root = os.path.dirname(sat_frontends.wix.__file__)
|
||||
|
||||
|
||||
class Const(constants.Const):
|
||||
|
||||
APP_NAME = "Wix"
|
||||
LICENCE_PATH = os.path.join(wix_root, "COPYING")
|
||||
msgOFFLINE = _("offline")
|
||||
msgONLINE = _("online")
|
||||
DEFAULT_GROUP = "Unclassed"
|
||||
PRESENCE = [("", _("Online"), None),
|
||||
("chat", _("Free for chat"), "green"),
|
||||
("away", _("AFK"), "brown"),
|
||||
("dnd", _("DND"), "red"),
|
||||
("xa", _("Away"), "red")
|
||||
]
|
||||
LOG_OPT_SECTION = APP_NAME.lower()
|
||||
|
|
@ -1,251 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# wix: a SAT frontend
|
||||
# Copyright (C) 2009, 2010, 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/>.
|
||||
|
||||
from sat.core.i18n import _
|
||||
import wx
|
||||
from sat_frontends.quick_frontend.quick_contact_list import QuickContactList
|
||||
from sat_frontends.wix.constants import Const
|
||||
from sat.core.log import getLogger
|
||||
log = getLogger(__name__)
|
||||
from cgi import escape
|
||||
from sat_frontends.tools.jid import JID
|
||||
from os.path import join
|
||||
|
||||
|
||||
class Group(unicode):
|
||||
"""Class used to recognize groups"""
|
||||
|
||||
class Contact(unicode):
|
||||
"""Class used to recognize groups"""
|
||||
|
||||
class ContactList(wx.SimpleHtmlListBox, QuickContactList):
|
||||
"""Customized control to manage contacts."""
|
||||
|
||||
def __init__(self, parent, host, type_="JID"):
|
||||
"""init the contact list
|
||||
@param parent: WxWidgets parent of the widget
|
||||
@param host: wix main app class
|
||||
@param type_: type of contact list: "JID" for the usual big jid contact list
|
||||
"CUSTOM" for a customized contact list (self._presentItem must then be overrided)
|
||||
"""
|
||||
wx.SimpleHtmlListBox.__init__(self, parent, -1)
|
||||
QuickContactList.__init__(self)
|
||||
self.host = host
|
||||
self.type = type_
|
||||
self.__typeSwitch()
|
||||
self.groups = {} #list contacts in each groups, key = group
|
||||
self.empty_avatar = join(host.media_dir, 'misc/empty_avatar')
|
||||
self.Bind(wx.EVT_LISTBOX, self.onSelected)
|
||||
self.Bind(wx.EVT_LISTBOX_DCLICK, self.onActivated)
|
||||
|
||||
def __contains__(self, jid):
|
||||
return bool(self.__find_idx(jid))
|
||||
|
||||
def __typeSwitch(self):
|
||||
if self.type == "JID":
|
||||
self._presentItem = self._presentItemJID
|
||||
elif self.type != "CUSTOM":
|
||||
self._presentItem = self._presentItemDefault
|
||||
|
||||
def __find_idx(self, entity):
|
||||
"""Find indexes of given contact (or groups) in contact list, manage jid
|
||||
@return: list of indexes"""
|
||||
result=[]
|
||||
for i in range(self.GetCount()):
|
||||
if (type(entity) == JID and type(self.GetClientData(i)) == JID and self.GetClientData(i).bare == entity.bare) or\
|
||||
self.GetClientData(i) == entity:
|
||||
result.append(i)
|
||||
return result
|
||||
|
||||
def update_jid(self, jid):
|
||||
self.replace(jid)
|
||||
|
||||
def replace(self, contact, groups=None, attributes=None):
|
||||
"""Add a contact to the list if doesn't exist, else update it.
|
||||
|
||||
This method can be called with groups=None for the purpose of updating
|
||||
the contact's attributes (e.g. nickname). In that case, the groups
|
||||
attribute must not be set to the default group but ignored. If not,
|
||||
you may move your contact from its actual group(s) to the default one.
|
||||
|
||||
None value for 'groups' has a different meaning than [None] which is for the default group.
|
||||
|
||||
@param jid (JID)
|
||||
@param groups (list): list of groups or None to ignore the groups membership.
|
||||
@param attributes (dict)
|
||||
"""
|
||||
log.debug(_("update %s") % contact)
|
||||
if not self.__find_idx(contact):
|
||||
self.add(contact, groups)
|
||||
else:
|
||||
for i in self.__find_idx(contact):
|
||||
_present = self._presentItem(contact)
|
||||
if _present != None:
|
||||
self.SetString(i, _present)
|
||||
|
||||
def __eraseGroup(self, group):
|
||||
"""Erase all contacts in group
|
||||
@param group: group to erase
|
||||
@return: True if something as been erased"""
|
||||
erased = False
|
||||
indexes = self.__find_idx(group)
|
||||
for idx in indexes:
|
||||
while idx<self.GetCount()-1 and type(self.GetClientData(idx+1)) != Group:
|
||||
erased = True
|
||||
self.Delete(idx+1)
|
||||
return erased
|
||||
|
||||
|
||||
def _presentGroup(self, group):
|
||||
"""Make a nice presentation for the contact groups"""
|
||||
html = u"""-- [%s] --""" % group
|
||||
|
||||
return html
|
||||
|
||||
def _presentItemDefault(self, contact):
|
||||
"""Make a basic presentation of string contacts in the list."""
|
||||
return contact
|
||||
|
||||
def _presentItemJID(self, jid):
|
||||
"""Make a nice presentation of the contact in the list for JID contacts."""
|
||||
name = self.getCache(jid,'name')
|
||||
nick = self.getCache(jid,'nick')
|
||||
_show = self.getCache(jid,'show')
|
||||
if _show == None or _show == 'unavailable':
|
||||
return None
|
||||
show = [x for x in Const.PRESENCE if x[0] == _show][0]
|
||||
|
||||
#show[0]==shortcut
|
||||
#show[1]==human readable
|
||||
#show[2]==color (or None)
|
||||
show_html = "<font color='%s'>[%s]</font>" % (show[2], show[1]) if show[2] else ""
|
||||
status = self.getCache(jid,'status') or ''
|
||||
avatar = self.getCache(jid,'avatar') or self.empty_avatar #XXX: there is a weird bug here: if the image has an extension (i.e. empty_avatar.png),
|
||||
#WxPython segfault, and it doesn't without nothing. I couldn't reproduce the case with a basic test script, so it need further investigation before reporting it
|
||||
#to WxPython dev. Anyway, the program crash with a segfault, not a python exception, so there is definitely something wrong with WxPython.
|
||||
#The case seems to happen when SimpleHtmlListBox parse the HTML with the <img> tag
|
||||
|
||||
html = """
|
||||
<table border='0'>
|
||||
<td>
|
||||
<img height='64' width='64' src='%s' />
|
||||
</td>
|
||||
<td>
|
||||
<b>%s</b> %s<br />
|
||||
<i>%s</i>
|
||||
</td>
|
||||
</table>
|
||||
""" % (avatar,
|
||||
escape(nick or name or jid.node or jid.bare),
|
||||
show_html,
|
||||
escape(status))
|
||||
|
||||
return html
|
||||
|
||||
def clearContacts(self):
|
||||
"""Clear all the contact list"""
|
||||
self.Clear()
|
||||
|
||||
def add(self, contact, groups = None):
|
||||
"""add a contact to the list"""
|
||||
log.debug (_("adding %s"),contact)
|
||||
if not groups:
|
||||
_present = self._presentItem(contact)
|
||||
if _present:
|
||||
idx = self.Insert(_present, 0, contact)
|
||||
else:
|
||||
for group in groups:
|
||||
indexes = self.__find_idx(group)
|
||||
gp_idx = 0
|
||||
if not indexes: #this is a new group, we have to create it
|
||||
gp_idx = self.Append(self._presentGroup(group), Group(group))
|
||||
else:
|
||||
gp_idx = indexes[0]
|
||||
|
||||
_present = self._presentItem(contact)
|
||||
if _present:
|
||||
self.Insert(_present, gp_idx+1, contact)
|
||||
|
||||
def setSpecial(self, special_jid, special_type, show=False):
|
||||
"""Set entity as a special
|
||||
@param jid: jid of the entity
|
||||
@param _type: special type (e.g.: "MUC")
|
||||
@param show: True to display the dialog to chat with this entity
|
||||
"""
|
||||
QuickContactList.setSpecial(self, special_jid, special_type, show)
|
||||
if show:
|
||||
self._showDialog(special_jid)
|
||||
|
||||
def _showDialog(self, jid):
|
||||
"""Show the dialog associated to the given jid."""
|
||||
indexes = self.__find_idx(jid)
|
||||
if not indexes:
|
||||
return
|
||||
self.DeselectAll()
|
||||
self.SetSelection(indexes[0])
|
||||
self.onActivated(wx.MouseEvent())
|
||||
|
||||
def remove(self, contact):
|
||||
"""remove a contact from the list"""
|
||||
log.debug (_("removing %s"), contact)
|
||||
list_idx = self.__find_idx(contact)
|
||||
list_idx.reverse() #as we make some deletions, we have to reverse the order
|
||||
for i in list_idx:
|
||||
self.Delete(i)
|
||||
|
||||
def onSelected(self, event):
|
||||
"""Called when a contact is selected."""
|
||||
data = self.getSelection()
|
||||
if data == None: #we have a group
|
||||
first_visible = self.GetVisibleBegin()
|
||||
group = self.GetClientData(self.GetSelection())
|
||||
erased = self.__eraseGroup(group)
|
||||
if not erased: #the group was already erased, we can add again the contacts
|
||||
contacts = [JID(contact) for contact in self.host.bridge.getContactsFromGroup(group, self.host.profile)]
|
||||
contacts.sort()
|
||||
id_insert = self.GetSelection()+1
|
||||
for contact in contacts:
|
||||
_present = self._presentItem(contact)
|
||||
if _present:
|
||||
self.Insert(_present, id_insert, contact)
|
||||
self.SetSelection(wx.NOT_FOUND)
|
||||
self.ScrollToLine(first_visible)
|
||||
event.Skip(False)
|
||||
else:
|
||||
event.Skip()
|
||||
|
||||
def onActivated(self, event):
|
||||
"""Called when a contact is clicked or activated with keyboard."""
|
||||
data = self.getSelection()
|
||||
self.onActivatedCB(data)
|
||||
event.Skip()
|
||||
|
||||
def getSelection(self):
|
||||
"""Return the selected contact, or an empty string if there is not"""
|
||||
if self.GetSelection() == wx.NOT_FOUND:
|
||||
return None
|
||||
data = self.GetClientData(self.GetSelection())
|
||||
if type(data) == Group:
|
||||
return None
|
||||
return data
|
||||
|
||||
def registerActivatedCB(self, cb):
|
||||
"""Register a callback with manage contact activation."""
|
||||
self.onActivatedCB=cb
|
||||
|
||||
|
|
@ -1,498 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# wix: a SAT frontend
|
||||
# Copyright (C) 2009, 2010, 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/>.
|
||||
|
||||
|
||||
from sat.core.i18n import _
|
||||
from sat_frontends.wix.constants import Const as C
|
||||
from sat_frontends.quick_frontend.quick_chat_list import QuickChatList
|
||||
from sat_frontends.quick_frontend.quick_app import QuickApp
|
||||
import wx
|
||||
from sat_frontends.wix.contact_list import ContactList
|
||||
from sat_frontends.wix.chat import Chat
|
||||
from sat_frontends.wix import xmlui
|
||||
from sat_frontends.wix.profile import Profile
|
||||
from sat_frontends.wix.profile_manager import ProfileManager
|
||||
import os.path
|
||||
from sat_frontends.tools.jid import JID
|
||||
from sat.core.log import getLogger
|
||||
log = getLogger(__name__)
|
||||
from sat_frontends.wix.constants import Const
|
||||
|
||||
idCONNECT,\
|
||||
idDISCONNECT,\
|
||||
idEXIT,\
|
||||
idABOUT,\
|
||||
idPARAM,\
|
||||
idSHOW_PROFILE,\
|
||||
idJOIN_ROOM,\
|
||||
= range(7)
|
||||
|
||||
class ChatList(QuickChatList):
|
||||
"""This class manage the list of chat windows"""
|
||||
|
||||
def createChat(self, target):
|
||||
return Chat(target, self.host)
|
||||
|
||||
class MainWindow(wx.Frame, QuickApp):
|
||||
"""main app window"""
|
||||
|
||||
def __init__(self):
|
||||
QuickApp.__init__(self)
|
||||
wx.Frame.__init__(self,None, title="SàT Wix", size=(350,500))
|
||||
|
||||
#sizer
|
||||
self.sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.SetSizer(self.sizer)
|
||||
|
||||
#Frame elements
|
||||
self.contact_list = ContactList(self, self)
|
||||
self.contact_list.registerActivatedCB(self.onContactActivated)
|
||||
self.contact_list.Hide()
|
||||
self.sizer.Add(self.contact_list, 1, flag=wx.EXPAND)
|
||||
|
||||
self.chat_wins=ChatList(self)
|
||||
self.CreateStatusBar()
|
||||
|
||||
#ToolBar
|
||||
self.tools=self.CreateToolBar()
|
||||
self.statusBox = wx.ComboBox(self.tools, -1, "Online", choices=[status[1] for status in Const.PRESENCE],
|
||||
style=wx.CB_DROPDOWN | wx.CB_READONLY)
|
||||
self.tools.AddControl(self.statusBox)
|
||||
self.tools.AddSeparator()
|
||||
self.statusTxt = wx.TextCtrl(self.tools, -1, style=wx.TE_PROCESS_ENTER)
|
||||
self.tools.AddControl(self.statusTxt)
|
||||
self.Bind(wx.EVT_COMBOBOX, self.onStatusChange, self.statusBox)
|
||||
self.Bind(wx.EVT_TEXT_ENTER, self.onStatusChange, self.statusTxt)
|
||||
self.tools.Disable()
|
||||
|
||||
#tray icon
|
||||
ticon = wx.Icon(os.path.join(self.media_dir, 'icons/crystal/32/tray_icon.xpm'), wx.BITMAP_TYPE_XPM)
|
||||
self.tray_icon = wx.TaskBarIcon()
|
||||
if ticon.IsOk():
|
||||
self.tray_icon.SetIcon(ticon, _("Wix jabber client"))
|
||||
wx.EVT_TASKBAR_LEFT_UP(self.tray_icon, self.onTrayClick)
|
||||
|
||||
|
||||
#events
|
||||
self.Bind(wx.EVT_CLOSE, self.onClose, self)
|
||||
|
||||
|
||||
#profile panel
|
||||
self.profile_pan = ProfileManager(self)
|
||||
self.sizer.Add(self.profile_pan, 1, flag=wx.EXPAND)
|
||||
|
||||
self.postInit()
|
||||
|
||||
self.Show()
|
||||
|
||||
def plug_profile_1(self, profile_key='@DEFAULT@'):
|
||||
"""Hide profile panel then plug profile"""
|
||||
log.debug (_('plugin profile %s' % profile_key))
|
||||
self.profile_pan.Hide()
|
||||
self.contact_list.Show()
|
||||
self.sizer.Layout()
|
||||
super(MainWindow, self).plug_profile_1(profile_key)
|
||||
#menus
|
||||
self.createMenus()
|
||||
|
||||
def addMenus(self, menubar, type_, menu_data=None):
|
||||
"""Add cached menus to instance
|
||||
@param menu: wx.MenuBar instance
|
||||
@param type_: menu type like is sat.core.sat_main.importMenu
|
||||
@param menu_data: data to send with these menus
|
||||
|
||||
"""
|
||||
menus = self.profiles[self.profile]['menus'].get(type_,[])
|
||||
for id_, path, path_i18n in menus:
|
||||
if len(path) != 2:
|
||||
raise NotImplementedError("Menu with a path != 2 are not implemented yet")
|
||||
category = path_i18n[0] # TODO: manage path with more than 2 levels
|
||||
name = path_i18n[1]
|
||||
menu_idx = menubar.FindMenu(category)
|
||||
current_menu = None
|
||||
if menu_idx == wx.NOT_FOUND:
|
||||
#the menu is new, we create it
|
||||
current_menu = wx.Menu()
|
||||
menubar.Append(current_menu, category)
|
||||
else:
|
||||
current_menu = menubar.GetMenu(menu_idx)
|
||||
assert(current_menu != None)
|
||||
item_id = wx.NewId()
|
||||
help_string = self.bridge.getMenuHelp(id_, '')
|
||||
current_menu.Append(item_id, name, help=help_string)
|
||||
#now we register the event
|
||||
def event_answer(e, id_=id_):
|
||||
self.launchAction(id_, menu_data, profile_key = self.profile)
|
||||
|
||||
wx.EVT_MENU(menubar.Parent, item_id, event_answer)
|
||||
|
||||
def createMenus(self):
|
||||
log.info(_("Creating menus"))
|
||||
connectMenu = wx.Menu()
|
||||
connectMenu.Append(idCONNECT, _("&Connect CTRL-c"),_(" Connect to the server"))
|
||||
connectMenu.Append(idDISCONNECT, _("&Disconnect CTRL-d"),_(" Disconnect from the server"))
|
||||
connectMenu.Append(idPARAM,_("&Parameters"),_(" Configure the program"))
|
||||
connectMenu.AppendSeparator()
|
||||
connectMenu.Append(idABOUT, _("A&bout"), _(" About %s") % Const.APP_NAME)
|
||||
connectMenu.Append(idEXIT,_("E&xit"),_(" Terminate the program"))
|
||||
contactMenu = wx.Menu()
|
||||
communicationMenu = wx.Menu()
|
||||
communicationMenu.Append(idJOIN_ROOM, _("&Join Room"),_(" Join a Multi-User Chat room"))
|
||||
self.menuBar = wx.MenuBar()
|
||||
self.menuBar.Append(connectMenu,_("&General"))
|
||||
self.menuBar.Append(contactMenu,_("&Contacts"))
|
||||
self.menuBar.Append(communicationMenu,_("&Communication"))
|
||||
self.SetMenuBar(self.menuBar)
|
||||
|
||||
#additionals menus
|
||||
#FIXME: do this in a more generic way (in quickapp)
|
||||
self.addMenus(self.menuBar, C.MENU_GLOBAL)
|
||||
|
||||
# menu items that should be displayed after the automatically added ones
|
||||
contactMenu.AppendSeparator()
|
||||
contactMenu.Append(idSHOW_PROFILE, _("&Show profile"), _(" Show contact's profile"))
|
||||
|
||||
#events
|
||||
wx.EVT_MENU(self, idCONNECT, self.onConnectRequest)
|
||||
wx.EVT_MENU(self, idDISCONNECT, self.onDisconnectRequest)
|
||||
wx.EVT_MENU(self, idPARAM, self.onParam)
|
||||
wx.EVT_MENU(self, idABOUT, self.onAbout)
|
||||
wx.EVT_MENU(self, idEXIT, self.onExit)
|
||||
wx.EVT_MENU(self, idSHOW_PROFILE, self.onShowProfile)
|
||||
wx.EVT_MENU(self, idJOIN_ROOM, self.onJoinRoom)
|
||||
|
||||
def newMessageHandler(self, from_jid, to_jid, msg, _type, extra, profile):
|
||||
QuickApp.newMessageHandler(self, from_jid, to_jid, msg, _type, extra, profile)
|
||||
|
||||
def showAlert(self, message):
|
||||
# TODO: place this in a separate class
|
||||
popup=wx.PopupWindow(self)
|
||||
### following code come from wxpython demo
|
||||
popup.SetBackgroundColour("CADET BLUE")
|
||||
st = wx.StaticText(popup, -1, message, pos=(10,10))
|
||||
sz = st.GetBestSize()
|
||||
popup.SetSize( (sz.width+20, sz.height+20) )
|
||||
x=(wx.DisplaySize()[0]-popup.GetSize()[0])/2
|
||||
popup.SetPosition((x,0))
|
||||
popup.Show()
|
||||
wx.CallLater(5000,popup.Destroy)
|
||||
|
||||
def showDialog(self, message, title="", type_="info", answer_cb = None, answer_data = None):
|
||||
if type_ == 'info':
|
||||
flags = wx.OK | wx.ICON_INFORMATION
|
||||
elif type_ == 'error':
|
||||
flags = wx.OK | wx.ICON_ERROR
|
||||
elif type_ == 'yes/no':
|
||||
flags = wx.YES_NO | wx.ICON_QUESTION
|
||||
else:
|
||||
flags = wx.OK | wx.ICON_INFORMATION
|
||||
log.error(_('unmanaged dialog type: %s'), type_)
|
||||
dlg = wx.MessageDialog(self, message, title, flags)
|
||||
answer = dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
if answer_cb:
|
||||
data = [answer_data] if answer_data else []
|
||||
answer_cb(True if (answer == wx.ID_YES or answer == wx.ID_OK) else False, *data)
|
||||
|
||||
def setStatusOnline(self, online=True, show="", statuses={}):
|
||||
"""enable/disable controls, must be called when local user online status change"""
|
||||
if online:
|
||||
self.SetStatusText(Const.msgONLINE)
|
||||
self.tools.Enable()
|
||||
try:
|
||||
presence = [x for x in Const.PRESENCE if x[0] == show][0][1]
|
||||
self.statusBox.SetValue(presence)
|
||||
except (TypeError, IndexError):
|
||||
pass
|
||||
try:
|
||||
self.statusTxt.SetValue(statuses['default'])
|
||||
except (TypeError, KeyError):
|
||||
pass
|
||||
else:
|
||||
self.SetStatusText(Const.msgOFFLINE)
|
||||
self.tools.Disable()
|
||||
return
|
||||
|
||||
def launchAction(self, callback_id, data=None, profile_key="@NONE@"):
|
||||
""" Launch a dynamic action
|
||||
@param callback_id: id of the action to launch
|
||||
@param data: data needed only for certain actions
|
||||
@param profile_key: %(doc_profile_key)s
|
||||
|
||||
"""
|
||||
if data is None:
|
||||
data = dict()
|
||||
def action_cb(data):
|
||||
if not data:
|
||||
# action was a one shot, nothing to do
|
||||
pass
|
||||
elif "xmlui" in data:
|
||||
log.debug (_("XML user interface received"))
|
||||
ui = xmlui.create(self, xml_data = data['xmlui'])
|
||||
ui.show()
|
||||
elif "authenticated_profile" in data:
|
||||
assert("caller" in data)
|
||||
if data["caller"] == "profile_manager":
|
||||
assert(self.profile_pan.IsShown())
|
||||
self.profile_pan.getXMPPParams(data['authenticated_profile'])
|
||||
elif data["caller"] == "plug_profile":
|
||||
self.plug_profile_1(data['authenticated_profile'])
|
||||
else:
|
||||
raise NotImplementedError
|
||||
else:
|
||||
dlg = wx.MessageDialog(self, _(u"Unmanaged action result"),
|
||||
_('Error'),
|
||||
wx.OK | wx.ICON_ERROR
|
||||
)
|
||||
dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
def action_eb(failure):
|
||||
dlg = wx.MessageDialog(self, failure.message,
|
||||
failure.fullname,
|
||||
wx.OK | wx.ICON_ERROR
|
||||
)
|
||||
dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
|
||||
self.bridge.launchAction(callback_id, data, profile_key, callback=action_cb, errback=action_eb)
|
||||
|
||||
def askConfirmationHandler(self, confirmation_id, confirmation_type, data, profile):
|
||||
#TODO: refactor this in QuickApp
|
||||
if not self.check_profile(profile):
|
||||
return
|
||||
log.debug (_("Confirmation asked"))
|
||||
answer_data={}
|
||||
if confirmation_type == "FILE_TRANSFER":
|
||||
log.debug (_("File transfer confirmation asked"))
|
||||
dlg = wx.MessageDialog(self, _("The contact %(jid)s wants to send you the file %(filename)s\nDo you accept ?") % {'jid':data["from"], 'filename':data["filename"]},
|
||||
_('File Request'),
|
||||
wx.YES_NO | wx.ICON_QUESTION
|
||||
)
|
||||
answer=dlg.ShowModal()
|
||||
if answer==wx.ID_YES:
|
||||
filename = wx.FileSelector(_("Where do you want to save the file ?"), flags = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
|
||||
if filename:
|
||||
answer_data["dest_path"] = filename
|
||||
self.bridge.confirmationAnswer(confirmation_id, True, answer_data, profile)
|
||||
self.waitProgress(confirmation_id, _("File Transfer"), _("Copying %s") % os.path.basename(filename), profile)
|
||||
else:
|
||||
answer = wx.ID_NO
|
||||
if answer==wx.ID_NO:
|
||||
self.bridge.confirmationAnswer(confirmation_id, False, answer_data, profile)
|
||||
|
||||
dlg.Destroy()
|
||||
|
||||
elif confirmation_type == "YES/NO":
|
||||
log.debug (_("Yes/No confirmation asked"))
|
||||
dlg = wx.MessageDialog(self, data["message"],
|
||||
_('Confirmation'),
|
||||
wx.YES_NO | wx.ICON_QUESTION
|
||||
)
|
||||
answer=dlg.ShowModal()
|
||||
if answer==wx.ID_YES:
|
||||
self.bridge.confirmationAnswer(confirmation_id, True, {}, profile)
|
||||
if answer==wx.ID_NO:
|
||||
self.bridge.confirmationAnswer(confirmation_id, False, {}, profile)
|
||||
|
||||
dlg.Destroy()
|
||||
|
||||
def actionResultHandler(self, type_, id_, data, profile):
|
||||
if not self.check_profile(profile):
|
||||
return
|
||||
log.debug (_("actionResult: type_ = [%(type_)s] id_ = [%(id_)s] data = [%(data)s]") % {'type_':type_, 'id_':id_, 'data':data})
|
||||
if not id_ in self.current_action_ids:
|
||||
log.debug (_('unknown id_, ignoring'))
|
||||
return
|
||||
if type_ == "SUPPRESS":
|
||||
self.current_action_ids.remove(id_)
|
||||
elif type_ == "SUCCESS":
|
||||
self.current_action_ids.remove(id_)
|
||||
dlg = wx.MessageDialog(self, data["message"],
|
||||
_('Success'),
|
||||
wx.OK | wx.ICON_INFORMATION
|
||||
)
|
||||
dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
elif type_ == "ERROR":
|
||||
self.current_action_ids.remove(id_)
|
||||
dlg = wx.MessageDialog(self, data["message"],
|
||||
_('Error'),
|
||||
wx.OK | wx.ICON_ERROR
|
||||
)
|
||||
dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
elif type_ == "XMLUI":
|
||||
self.current_action_ids.remove(id_)
|
||||
log.debug (_("XML user interface received"))
|
||||
misc = {}
|
||||
#FIXME FIXME FIXME: must clean all this crap !
|
||||
title = _('Form')
|
||||
if data['type_'] == _('registration'):
|
||||
title = _('Registration')
|
||||
misc['target'] = data['target']
|
||||
misc['action_back'] = self.bridge.gatewayRegister
|
||||
xmlui.create(self, title=title, xml_data = data['xml'], misc = misc)
|
||||
elif type_ == "RESULT":
|
||||
self.current_action_ids.remove(id_)
|
||||
if self.current_action_ids_cb.has_key(id_):
|
||||
callback = self.current_action_ids_cb[id_]
|
||||
del self.current_action_ids_cb[id_]
|
||||
callback(data)
|
||||
elif type_ == "DICT_DICT":
|
||||
self.current_action_ids.remove(id_)
|
||||
if self.current_action_ids_cb.has_key(id_):
|
||||
callback = self.current_action_ids_cb[id_]
|
||||
del self.current_action_ids_cb[id_]
|
||||
callback(data)
|
||||
else:
|
||||
log.error (_("FIXME FIXME FIXME: type_ [%s] not implemented") % type_)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
|
||||
def progressCB(self, progress_id, title, message, profile):
|
||||
data = self.bridge.getProgress(progress_id, profile)
|
||||
if data:
|
||||
if not self.pbar:
|
||||
#first answer, we must construct the bar
|
||||
self.pbar = wx.ProgressDialog(title, message, float(data['size']), None,
|
||||
wx.PD_SMOOTH | wx.PD_ELAPSED_TIME | wx.PD_ESTIMATED_TIME | wx.PD_REMAINING_TIME)
|
||||
self.pbar.finish_value = float(data['size'])
|
||||
|
||||
self.pbar.Update(int(data['position']))
|
||||
elif self.pbar:
|
||||
self.pbar.Update(self.pbar.finish_value)
|
||||
return
|
||||
|
||||
wx.CallLater(10, self.progressCB, progress_id, title, message, profile)
|
||||
|
||||
def waitProgress (self, progress_id, title, message, profile):
|
||||
self.pbar = None
|
||||
wx.CallLater(10, self.progressCB, progress_id, title, message, profile)
|
||||
|
||||
|
||||
|
||||
### events ###
|
||||
|
||||
def onContactActivated(self, jid):
|
||||
log.debug (_("onContactActivated: %s"), jid)
|
||||
if self.chat_wins[jid.bare].IsShown():
|
||||
self.chat_wins[jid.bare].Hide()
|
||||
else:
|
||||
self.chat_wins[jid.bare].Show()
|
||||
|
||||
def onConnectRequest(self, e):
|
||||
QuickApp.asyncConnect(self, self.profile)
|
||||
|
||||
def onDisconnectRequest(self, e):
|
||||
self.bridge.disconnect(self.profile)
|
||||
|
||||
def __updateStatus(self):
|
||||
show = [x for x in Const.PRESENCE if x[1] == self.statusBox.GetValue()][0][0]
|
||||
status = self.statusTxt.GetValue()
|
||||
self.bridge.setPresence(show=show, statuses={'default': status}, profile_key=self.profile) #FIXME: manage multilingual statuses
|
||||
|
||||
def onStatusChange(self, e):
|
||||
log.debug(_("Status change request"))
|
||||
self.__updateStatus()
|
||||
|
||||
def onParam(self, e):
|
||||
log.debug(_("Param request"))
|
||||
def success(params):
|
||||
xmlui.create(self, xml_data=params, title=_("Configuration"))
|
||||
|
||||
def failure(error):
|
||||
dlg = wx.MessageDialog(self, error.message,
|
||||
error.fullname,
|
||||
wx.OK | wx.ICON_ERROR
|
||||
)
|
||||
dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
self.bridge.getParamsUI(app=Const.APP_NAME, profile_key=self.profile, callback=success, errback=failure)
|
||||
|
||||
def onAbout(self, e):
|
||||
about = wx.AboutDialogInfo()
|
||||
about.SetName(Const.APP_NAME)
|
||||
about.SetVersion (unicode(self.bridge.getVersion()))
|
||||
about.SetCopyright(u"(C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson aka Goffi")
|
||||
about.SetDescription( _(u"%(name)s is a SàT (Salut à Toi) frontend\n"+
|
||||
u"%(name)s is based on WxPython, and is the standard graphic interface of SàT") % {'name': Const.APP_NAME})
|
||||
about.SetWebSite(("http://www.goffi.org", "Goffi's non-hebdo (french)"))
|
||||
about.SetDevelopers([ "Goffi (Jérôme Poisson)"])
|
||||
try:
|
||||
with open(Const.LICENCE_PATH, "r") as licence:
|
||||
about.SetLicence(''.join(licence.readlines()))
|
||||
except:
|
||||
pass
|
||||
|
||||
wx.AboutBox(about)
|
||||
|
||||
def onExit(self, e):
|
||||
self.Close()
|
||||
|
||||
def onShowProfile(self, e):
|
||||
log.debug(_("Show contact's profile request"))
|
||||
target = self.contact_list.getSelection()
|
||||
if not target:
|
||||
dlg = wx.MessageDialog(self, _("You haven't selected any contact !"),
|
||||
_('Error'),
|
||||
wx.OK | wx.ICON_ERROR
|
||||
)
|
||||
dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
return
|
||||
_id = self.bridge.getCard(target.bare, self.profile)
|
||||
self.current_action_ids.add(_id)
|
||||
self.current_action_ids_cb[_id] = self.onProfileReceived
|
||||
|
||||
def onProfileReceived(self, data):
|
||||
"""Called when a profile is received"""
|
||||
log.debug (_('Profile received: [%s]') % data)
|
||||
Profile(self, data)
|
||||
|
||||
def onJoinRoom(self, e):
|
||||
log.warning('FIXME: temporary menu, must be improved')
|
||||
#TODO: a proper MUC room joining dialog with nickname etc
|
||||
dlg = wx.TextEntryDialog(
|
||||
self, _("Please enter MUC's JID"),
|
||||
#_('Entering a MUC room'), 'test@conference.necton2.int')
|
||||
_('Entering a MUC room'), 'room@muc_service.server.tld')
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
room_jid=JID(dlg.GetValue())
|
||||
if room_jid.is_valid():
|
||||
self.bridge.joinMUC(room_jid, self.profiles[self.profile]['whoami'].node, {}, self.profile)
|
||||
else:
|
||||
log.error (_("'%s' is an invalid JID !"), room_jid)
|
||||
|
||||
def onClose(self, e):
|
||||
QuickApp.onExit(self)
|
||||
log.info(_("Exiting..."))
|
||||
for win in self.chat_wins:
|
||||
self.chat_wins[win].Destroy()
|
||||
self.tray_icon.Destroy()
|
||||
e.Skip()
|
||||
|
||||
def onTrayClick(self, e):
|
||||
log.debug(_("Tray Click"))
|
||||
if self.IsShown():
|
||||
self.Hide()
|
||||
else:
|
||||
self.Show()
|
||||
self.Raise()
|
||||
e.Skip()
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# wix: a SAT frontend
|
||||
# Copyright (C) 2009, 2010, 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/>.
|
||||
|
||||
from sat.core.i18n import _
|
||||
import wx
|
||||
import pdb
|
||||
from sat.core.log import getLogger
|
||||
log = getLogger(__name__)
|
||||
from sat_frontends.tools.jid import JID
|
||||
|
||||
|
||||
class Profile(wx.Frame):
|
||||
"""This class is used to show/modify profile given by SàT"""
|
||||
|
||||
def __init__(self, host, data, title="Profile"):
|
||||
super(Profile, self).__init__(None, title=title)
|
||||
self.host = host
|
||||
|
||||
self.name_dict = { 'fullname': _('Full Name'),
|
||||
'nick' : _('Nickname'),
|
||||
'birthday' : _('Birthday'),
|
||||
'phone' : _('Phone #'),
|
||||
'website' : _('Website'),
|
||||
'email' : _('E-mail'),
|
||||
'avatar' : _('Avatar')
|
||||
}
|
||||
self.ctl_list = {} # usefull to access ctrl, key = (name)
|
||||
|
||||
self.sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.notebook=wx.Notebook(self, -1)
|
||||
self.sizer.Add(self.notebook, 1, flag=wx.EXPAND)
|
||||
self.SetSizer(self.sizer)
|
||||
self.SetAutoLayout(True)
|
||||
|
||||
#events
|
||||
self.Bind(wx.EVT_CLOSE, self.onClose, self)
|
||||
|
||||
self.MakeModal()
|
||||
self.showData(data)
|
||||
self.Show()
|
||||
|
||||
def showData(self, data):
|
||||
flags = wx.TE_READONLY
|
||||
|
||||
#General tab
|
||||
generaltab = wx.Panel(self.notebook)
|
||||
sizer = wx.FlexGridSizer(cols=2)
|
||||
sizer.AddGrowableCol(1)
|
||||
generaltab.SetSizer(sizer)
|
||||
generaltab.SetAutoLayout(True)
|
||||
for field in ['fullname','nick', 'birthday', 'phone', 'website', 'email']:
|
||||
value = data[field] if data.has_key(field) else ''
|
||||
label=wx.StaticText(generaltab, -1, self.name_dict[field]+": ")
|
||||
sizer.Add(label)
|
||||
self.ctl_list[field] = wx.TextCtrl(generaltab, -1, value, style = flags)
|
||||
sizer.Add(self.ctl_list[field], 1, flag = wx.EXPAND)
|
||||
#Avatar
|
||||
if data.has_key('avatar'):
|
||||
filename = self.host.bridge.getAvatarFile(data['avatar'])
|
||||
label=wx.StaticText(generaltab, -1, self.name_dict['avatar']+": ")
|
||||
sizer.Add(label)
|
||||
img = wx.Image(filename).ConvertToBitmap()
|
||||
self.ctl_list['avatar'] = wx.StaticBitmap(generaltab, -1, img)
|
||||
sizer.Add(self.ctl_list['avatar'], 0)
|
||||
|
||||
|
||||
|
||||
self.notebook.AddPage(generaltab, _("General"))
|
||||
|
||||
|
||||
def onClose(self, event):
|
||||
"""Close event"""
|
||||
log.debug(_("close"))
|
||||
self.MakeModal(False)
|
||||
event.Skip()
|
||||
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# wix: a SAT frontend
|
||||
# Copyright (C) 2009, 2010, 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/>.
|
||||
|
||||
|
||||
|
||||
from sat.core.i18n import _
|
||||
from sat_frontends.primitivus.constants import Const as C
|
||||
import wx
|
||||
from sat.core.log import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
NO_SELECTION_ENTRY = ' '
|
||||
|
||||
|
||||
class ProfileManager(wx.Panel):
|
||||
def __init__(self, host):
|
||||
super(ProfileManager, self).__init__(host)
|
||||
self.host = host
|
||||
|
||||
#self.sizer = wx.FlexGridSizer(cols=2)
|
||||
self.sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.SetSizer(self.sizer)
|
||||
|
||||
self.selected_profile = NO_SELECTION_ENTRY # allow to reselect the previous selection until the profile is authenticated
|
||||
self.profile_name = wx.ComboBox(self, -1, style=wx.CB_READONLY|wx.CB_SORT)
|
||||
self.__refillProfiles()
|
||||
self.Bind(wx.EVT_COMBOBOX, self.onProfileChange)
|
||||
self.panel_id = wx
|
||||
|
||||
self.sizer.Add(wx.Window(self, -1), 1)
|
||||
self.sizer.Add(wx.StaticText(self, -1, _("Profile:")), 0, flag=wx.ALIGN_CENTER)
|
||||
self.sizer.Add(self.profile_name, 0, flag=wx.ALIGN_CENTER)
|
||||
button_panel = wx.Panel(self)
|
||||
button_panel.sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
button_panel.SetSizer(button_panel.sizer)
|
||||
button_new = wx.Button(button_panel, -1, _("New"))
|
||||
button_del = wx.Button(button_panel, -1, _("Delete"))
|
||||
button_panel.sizer.Add(button_new)
|
||||
button_panel.sizer.Add(button_del)
|
||||
self.sizer.Add(button_panel, flag=wx.CENTER)
|
||||
self.Bind(wx.EVT_BUTTON, self.onNewProfile, button_new)
|
||||
self.Bind(wx.EVT_BUTTON, self.onDeleteProfile, button_del)
|
||||
|
||||
login_box = wx.StaticBox(self, -1, _("Login"))
|
||||
self.login_sizer = wx.StaticBoxSizer(login_box, wx.VERTICAL)
|
||||
self.sizer.Add(self.login_sizer, 1, wx.EXPAND | wx.ALL)
|
||||
self.login_jid = wx.TextCtrl(self, -1)
|
||||
self.login_sizer.Add(wx.StaticText(self, -1, "JID:"), 0, flag=wx.ALIGN_CENTER)
|
||||
self.login_sizer.Add(self.login_jid, flag=wx.EXPAND)
|
||||
self.login_pass = wx.TextCtrl(self, -1, style = wx.TE_PASSWORD)
|
||||
self.login_sizer.Add(wx.StaticText(self, -1, _("Password:")), 0, flag=wx.ALIGN_CENTER)
|
||||
self.login_sizer.Add(self.login_pass, flag=wx.EXPAND)
|
||||
|
||||
loggin_button = wx.Button(self, -1, _("Connect"))
|
||||
self.Bind(wx.EVT_BUTTON, self.onConnectButton, loggin_button)
|
||||
self.login_sizer.Add(loggin_button, flag=wx.ALIGN_CENTER)
|
||||
|
||||
self.sizer.Add(wx.Window(self, -1), 1)
|
||||
|
||||
#Now we can set the default value
|
||||
self.__setDefault()
|
||||
|
||||
def __setDefault(self):
|
||||
profile_default = NO_SELECTION_ENTRY if self.host.options.profile else self.host.bridge.getProfileName("@DEFAULT@")
|
||||
if profile_default:
|
||||
self.profile_name.SetValue(profile_default)
|
||||
self.onProfileChange(None)
|
||||
|
||||
def __refillProfiles(self):
|
||||
"""Update profiles with current names. Must be called after a profile change"""
|
||||
self.profile_name.Clear()
|
||||
profiles = self.host.bridge.getProfilesList()
|
||||
profiles.sort()
|
||||
self.profile_name.Append(NO_SELECTION_ENTRY)
|
||||
for profile in profiles:
|
||||
self.profile_name.Append(profile)
|
||||
|
||||
def onNewProfile(self, event):
|
||||
dlg = wx.TextEntryDialog(self, _("Please enter the new profile name"), _("New profile"), style = wx.OK | wx.CANCEL)
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
name = dlg.GetValue()
|
||||
if name:
|
||||
if name[0]=='@':
|
||||
wx.MessageDialog(self, _("A profile name can't start with a @"), _("Bad profile name"), wx.ICON_ERROR).ShowModal()
|
||||
else:
|
||||
def cb():
|
||||
self.__refillProfiles()
|
||||
self.profile_name.SetValue(name)
|
||||
self.selected_profile = name
|
||||
self.getXMPPParams(name)
|
||||
self.host.bridge.asyncCreateProfile(name, callback=cb)
|
||||
dlg.Destroy()
|
||||
|
||||
def onDeleteProfile(self, event):
|
||||
name = self.profile_name.GetValue()
|
||||
if not name:
|
||||
return
|
||||
dlg = wx.MessageDialog(self, _("Are you sure to delete the profile [%s]") % name, _("Confirmation"), wx.ICON_QUESTION | wx.YES_NO)
|
||||
if dlg.ShowModal() == wx.ID_YES:
|
||||
def cb():
|
||||
self.__refillProfiles()
|
||||
self.__setDefault()
|
||||
self.host.bridge.asyncDeleteProfile(name, callback=cb)
|
||||
dlg.Destroy()
|
||||
|
||||
def getXMPPParams(self, profile):
|
||||
"""This is called from MainWindow.launchAction when the profile has been authenticated.
|
||||
|
||||
@param profile: %(doc_profile)s
|
||||
"""
|
||||
def setJID(jabberID):
|
||||
self.login_jid.SetValue(jabberID)
|
||||
|
||||
def setPassword(password):
|
||||
self.login_pass.SetValue(password)
|
||||
|
||||
self.profile_name.SetValue(profile)
|
||||
self.selected_profile = profile
|
||||
self.host.bridge.asyncGetParamA("JabberID", "Connection", profile_key=profile, callback=setJID, errback=self.getParamError)
|
||||
self.host.bridge.asyncGetParamA("Password", "Connection", profile_key=profile, callback=setPassword, errback=self.getParamError)
|
||||
|
||||
def onProfileChange(self, event):
|
||||
"""Called when a profile is choosen in the combo box"""
|
||||
profile_name = self.profile_name.GetValue()
|
||||
if not profile_name or profile_name == self.selected_profile:
|
||||
return # avoid infinite loop
|
||||
if profile_name == NO_SELECTION_ENTRY:
|
||||
self.selected_profile = NO_SELECTION_ENTRY
|
||||
return
|
||||
if self.selected_profile:
|
||||
self.profile_name.SetValue(self.selected_profile)
|
||||
self.host.profile = profile_name # FIXME: EXTREMELY DIRTY, needed for sat_frontends.tools.xmlui.XMLUI.submit
|
||||
self.host.launchAction(C.AUTHENTICATE_PROFILE_ID, {'caller': 'profile_manager'}, profile_key=profile_name)
|
||||
|
||||
def onConnectButton(self, event):
|
||||
"""Called when the Connect button is pressed"""
|
||||
name = self.profile_name.GetValue()
|
||||
assert(name == self.selected_profile) # if not, there's a bug somewhere...
|
||||
if not name or name == NO_SELECTION_ENTRY:
|
||||
wx.MessageDialog(self, _("You must select a profile or create a new one before connecting"), _("No profile selected"), wx.ICON_ERROR).ShowModal()
|
||||
return
|
||||
if name[0]=='@':
|
||||
wx.MessageDialog(self, _("A profile name can't start with a @"), _("Bad profile name"), wx.ICON_ERROR).ShowModal()
|
||||
return
|
||||
profile = self.host.bridge.getProfileName(name)
|
||||
assert(profile)
|
||||
|
||||
self.host.bridge.asyncGetParamA("JabberID", "Connection", profile_key=profile, callback=lambda old_jid: self.__old_jidReceived(old_jid, profile), errback=self.getParamError)
|
||||
|
||||
def __old_jidReceived(self, old_jid, profile):
|
||||
self.host.bridge.asyncGetParamA("Password", "Connection", profile_key=profile, callback=lambda old_pass: self.__old_passReceived(old_jid, old_pass, profile), errback=self.getParamError)
|
||||
|
||||
def __old_passReceived(self, old_jid, old_pass, profile):
|
||||
new_jid = self.login_jid.GetValue()
|
||||
new_pass = self.login_pass.GetValue()
|
||||
if old_jid != new_jid:
|
||||
log.debug(_('Saving new JID and server'))
|
||||
self.host.bridge.setParam("JabberID", new_jid, "Connection", profile_key=profile)
|
||||
if old_pass != new_pass:
|
||||
log.debug(_('Saving new password'))
|
||||
self.host.bridge.setParam("Password", new_pass, "Connection", profile_key=profile)
|
||||
self.host.plug_profile(profile)
|
||||
|
||||
|
||||
def getParamError(self, ignore):
|
||||
wx.MessageDialog(self, _("Can't get profile parameter"), _("Profile error"), wx.ICON_ERROR).ShowModal()
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# wix: a SAT frontend
|
||||
# Copyright (C) 2009, 2010, 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/>.
|
||||
|
||||
|
||||
|
||||
from sat.core.i18n import _
|
||||
import wx
|
||||
import os.path, glob
|
||||
import pdb
|
||||
from sat.core.log import getLogger
|
||||
log = getLogger(__name__)
|
||||
from sat_frontends.tools.jid import JID
|
||||
from time import time
|
||||
from math import sin, cos, pi
|
||||
|
||||
CARD_WIDTH = 74
|
||||
CARD_HEIGHT = 136
|
||||
WIDTH = 800
|
||||
HEIGHT = 600
|
||||
|
||||
class GraphicElement(object):
|
||||
"""This class is used to represent a card, graphically and logically"""
|
||||
|
||||
def __init__(self, file, x=0, y=0, zindex=10, transparent=True):
|
||||
""" Image used to build the game visual
|
||||
@param file: path of the PNG file
|
||||
@param zindex: layer of the element (0=background; the bigger, the more in the foreground)"""
|
||||
self.bitmap = wx.Image(file).ConvertToBitmap()
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.zindex = zindex
|
||||
self.transparent = transparent
|
||||
|
||||
def __cmp__(self, other):
|
||||
return self.zindex.__cmp__(other.zindex)
|
||||
|
||||
def draw(self, dc, x=None, y=None):
|
||||
"""Draw the card on the device context
|
||||
@param dc: device context
|
||||
@param x: abscissa
|
||||
@param y: ordinate"""
|
||||
dc.DrawBitmap(self.bitmap, x or self.x, y or self.y, self.transparent)
|
||||
|
||||
class BaseWindow(wx.Window):
|
||||
"""This is the panel where the game is drawed, under the other widgets"""
|
||||
|
||||
def __init__(self, parent):
|
||||
wx.Window.__init__(self, parent, pos=(0,0), size=(WIDTH, HEIGHT))
|
||||
self.parent = parent
|
||||
self.SetMinSize(wx.Size(WIDTH, HEIGHT))
|
||||
self.Bind(wx.EVT_PAINT, self.onPaint)
|
||||
self.graphic_elts = {}
|
||||
self.loadImages(os.path.join(parent.parent.host.media_dir, 'games/quiz/'))
|
||||
|
||||
def loadImages(self, dir):
|
||||
"""Load all the images needed for the game
|
||||
@param dir: directory where the PNG files are"""
|
||||
x_player = 24
|
||||
for name, sub_dir, filename, x, y, zindex, transparent in [("background", "background", "blue_background.png", 0, 0, 0, False),
|
||||
("joueur0", "characters", "zombie.png", x_player+0*184, 170, 5, True),
|
||||
("joueur1", "characters", "nerd.png", x_player+1*184, 170, 5, True),
|
||||
("joueur2", "characters", "zombie.png", x_player+2*184, 170, 5, True),
|
||||
("joueur3", "characters", "zombie.png", x_player+3*184, 170, 5, True),
|
||||
("foreground", "foreground", "foreground.png", 0, 0, 10, True)]:
|
||||
self.graphic_elts[name] = GraphicElement(os.path.join(dir, sub_dir, filename), x = x, y = y, zindex=zindex, transparent=transparent)
|
||||
|
||||
self.right_image = wx.Image(os.path.join(dir, "foreground", "right.png")).ConvertToBitmap()
|
||||
self.wrong_image = wx.Image(os.path.join(dir, "foreground", "wrong.png")).ConvertToBitmap()
|
||||
|
||||
def fullPaint(self, device_context):
|
||||
"""Paint all the game on the given dc
|
||||
@param device_context: wx.DC"""
|
||||
elements = self.graphic_elts.values()
|
||||
elements.sort()
|
||||
for elem in elements:
|
||||
elem.draw(device_context)
|
||||
|
||||
_font = wx.Font(65, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
|
||||
device_context.SetFont(_font)
|
||||
device_context.SetTextForeground(wx.BLACK)
|
||||
|
||||
for i in range(4):
|
||||
answer = self.parent.players_data[i]["answer"]
|
||||
score = self.parent.players_data[i]["score"]
|
||||
if answer == None:
|
||||
device_context.DrawText("%d" % score, 100 + i*184, 355)
|
||||
else:
|
||||
device_context.DrawBitmap(self.right_image if answer else self.wrong_image, 39+i*184, 348, True)
|
||||
|
||||
|
||||
if self.parent.time_origin:
|
||||
device_context.SetPen(wx.BLACK_PEN)
|
||||
radius = 20
|
||||
center_x = 760
|
||||
center_y = 147
|
||||
origin = self.parent.time_origin
|
||||
current = self.parent.time_pause or time()
|
||||
limit = self.parent.time_limit
|
||||
total = limit - origin
|
||||
left = self.parent.time_left = max(0,limit - current)
|
||||
device_context.SetBrush(wx.RED_BRUSH if left/total < 1/4.0 else wx.WHITE_BRUSH)
|
||||
if left:
|
||||
#we now draw the timer
|
||||
angle = ((-2*pi)*((total-left)/total) + (pi/2))
|
||||
x = center_x + radius * cos(angle)
|
||||
y = center_y - radius * sin(angle)
|
||||
device_context.DrawArc(center_x, center_y-radius, x, y, center_x, center_y)
|
||||
|
||||
def onPaint(self, event):
|
||||
dc = wx.PaintDC(self)
|
||||
self.fullPaint(dc)
|
||||
|
||||
|
||||
|
||||
class QuizPanel(wx.Panel):
|
||||
"""This class is used to display the quiz game"""
|
||||
|
||||
def __init__(self, parent, referee, players, player_nick):
|
||||
wx.Panel.__init__(self, parent)
|
||||
self.referee = referee
|
||||
self.player_nick = player_nick
|
||||
self.players = players
|
||||
self.time_origin = None #set to unix time when the timer start
|
||||
self.time_limit = None
|
||||
self.time_left = None
|
||||
self.time_pause = None
|
||||
self.last_answer = None
|
||||
self.parent = parent
|
||||
self.SetMinSize(wx.Size(WIDTH, HEIGHT))
|
||||
self.SetSize(wx.Size(WIDTH, HEIGHT))
|
||||
self.base = BaseWindow(self)
|
||||
self.question = wx.TextCtrl(self, -1, pos=(168,17), size=(613, 94), style=wx.TE_MULTILINE | wx.TE_READONLY)
|
||||
self.answer = wx.TextCtrl(self, -1, pos=(410,569), size=(342, 21), style=wx.TE_PROCESS_ENTER)
|
||||
self.players_data = [{}, {}, {}, {}]
|
||||
for i in range(4):
|
||||
self.players_data[i]['bubble'] = wx.TextCtrl(self, -1, pos=(39+i*184, 120), size=(180, 56), style=wx.TE_MULTILINE | wx.TE_READONLY)
|
||||
self.players_data[i]['bubble'].Hide()
|
||||
self.players_data[i]['answer'] = None #True if the player gave a good answer
|
||||
self.players_data[i]['score'] = 0
|
||||
self.answer.Bind(wx.EVT_TEXT_ENTER, self.answered)
|
||||
self.parent.host.bridge.quizGameReady(player_nick, referee, self.parent.host.profile)
|
||||
self.state = None
|
||||
|
||||
def answered(self, event):
|
||||
"""Called when the player gave an answer in the box"""
|
||||
self.last_answer = self.answer.GetValue()
|
||||
self.answer.Clear()
|
||||
if self.last_answer:
|
||||
self.parent.host.bridge.quizGameAnswer(self.player_nick, self.referee, self.last_answer, self.parent.host.profile)
|
||||
|
||||
def quizGameTimerExpired(self):
|
||||
"""Called when nobody answered the question in time"""
|
||||
self.question.SetValue(_(u"Quel dommage, personne n'a trouvé la réponse\n\nAttention, la prochaine question arrive..."))
|
||||
|
||||
def quizGameTimerRestarted(self, time_left):
|
||||
"""Called when nobody answered the question in time"""
|
||||
timer_orig = self.time_limit - self.time_origin
|
||||
self.time_left = time_left
|
||||
self.time_limit = time() + time_left
|
||||
self.time_origin = self.time_limit - timer_orig
|
||||
self.time_pause = None
|
||||
self.__timer_refresh()
|
||||
|
||||
def startTimer(self, timer=60):
|
||||
"""Start the timer to answer the question"""
|
||||
self.time_left = timer
|
||||
self.time_origin = time()
|
||||
self.time_limit = self.time_origin + timer
|
||||
self.time_pause = None
|
||||
self.__timer_refresh()
|
||||
|
||||
def __timer_refresh(self):
|
||||
self.Refresh()
|
||||
if self.time_left:
|
||||
wx.CallLater(1000, self.__timer_refresh)
|
||||
|
||||
def quizGameNew(self, data):
|
||||
"""Start a new game, with given hand"""
|
||||
if data.has_key('instructions'):
|
||||
self.question.ChangeValue(data['instructions'])
|
||||
self.Refresh()
|
||||
|
||||
def quizGameQuestion(self, question_id, question, timer):
|
||||
"""Called when a new question is available
|
||||
@param question: question to ask"""
|
||||
self.question.ChangeValue(question)
|
||||
self.startTimer(timer)
|
||||
self.last_answer = None
|
||||
self.answer.Clear()
|
||||
|
||||
def quizGamePlayerBuzzed(self, player, pause):
|
||||
"""Called when the player pushed the buzzer
|
||||
@param player: player who pushed the buzzer
|
||||
@param pause: should we stop the timer ?"""
|
||||
if pause:
|
||||
self.time_pause = time()
|
||||
|
||||
def quizGamePlayerSays(self, player, text, delay):
|
||||
"""Called when the player says something
|
||||
@param player: who is talking
|
||||
@param text: what the player says"""
|
||||
if player != self.player_nick and self.last_answer:
|
||||
#if we are not the player talking, and we have an answer, that mean that our answer has not been validated
|
||||
#we can put it again in the answering box
|
||||
self.answer.SetValue(self.last_answer)
|
||||
idx = self.players.index(player)
|
||||
bubble = self.players_data[idx]['bubble']
|
||||
bubble.SetValue(text)
|
||||
bubble.Show()
|
||||
self.Refresh()
|
||||
wx.CallLater(delay * 1000, bubble.Hide)
|
||||
|
||||
def quizGameAnswerResult(self, player, good_answer, score):
|
||||
"""Result of the just given answer
|
||||
@param player: who gave the answer
|
||||
@good_answer: True if the answer is right
|
||||
@score: dict of score"""
|
||||
player_idx = self.players.index(player)
|
||||
self.players_data[player_idx]['answer'] = good_answer
|
||||
for _player in score:
|
||||
_idx = self.players.index(_player)
|
||||
self.players_data[_idx]['score'] = score[_player]
|
||||
def removeAnswer():
|
||||
self.players_data[player_idx]['answer'] = None
|
||||
self.Refresh()
|
||||
wx.CallLater(2000, removeAnswer)
|
||||
self.Refresh()
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# wix: a SAT frontend
|
||||
# Copyright (C) 2009, 2010, 2011, 2012, 2013Jé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/>.
|
||||
|
||||
|
||||
from sat_frontends.wix.constants import Const as C
|
||||
from sat.core import log_config
|
||||
log_config.satConfigure(C.LOG_BACKEND_STANDARD, C)
|
||||
import wx
|
||||
from sat_frontends.wix.main_window import MainWindow
|
||||
|
||||
|
||||
class SATApp(wx.App):
|
||||
def __init__(self, redirect=False, filename=None, useBestVisual=False, clearSigInt=True):
|
||||
super(SATApp,self).__init__(redirect, filename, useBestVisual, clearSigInt)
|
||||
|
||||
def OnInit(self):
|
||||
self.main = MainWindow()
|
||||
self.main.Show(True)
|
||||
self.SetTopWindow(self.main)
|
||||
return True
|
||||
|
||||
|
||||
sat = SATApp()
|
||||
sat.MainLoop()
|
||||
|
|
@ -1,428 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# wix: a SAT frontend
|
||||
# Copyright (C) 2009, 2010, 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/>.
|
||||
|
||||
|
||||
|
||||
from sat.core.i18n import _
|
||||
import wx
|
||||
from sat.core.log import getLogger
|
||||
log = getLogger(__name__)
|
||||
from sat_frontends.tools import xmlui
|
||||
from sat_frontends.constants import Const as C
|
||||
|
||||
|
||||
class EventWidget(object):
|
||||
""" Used to manage change event of widgets """
|
||||
|
||||
def _xmluiOnChange(self, callback):
|
||||
""" Call callback with widget as only argument """
|
||||
def change_cb(event):
|
||||
callback(self)
|
||||
self.Bind(self._xmlui_change_event, change_cb)
|
||||
|
||||
|
||||
class WixWidget(object):
|
||||
_xmlui_proportion = 0
|
||||
|
||||
|
||||
class ValueWidget(WixWidget):
|
||||
|
||||
def _xmluiSetValue(self, value):
|
||||
self.SetValue(value)
|
||||
|
||||
def _xmluiGetValue(self):
|
||||
return self.GetValue()
|
||||
|
||||
|
||||
class EmptyWidget(WixWidget, xmlui.EmptyWidget, wx.Window):
|
||||
|
||||
def __init__(self, _xmlui_parent):
|
||||
wx.Window.__init__(self, _xmlui_parent, -1)
|
||||
|
||||
|
||||
class TextWidget(WixWidget, xmlui.TextWidget, wx.StaticText):
|
||||
|
||||
def __init__(self, _xmlui_parent, value):
|
||||
wx.StaticText.__init__(self, _xmlui_parent, -1, value)
|
||||
|
||||
|
||||
class LabelWidget(xmlui.LabelWidget, TextWidget):
|
||||
|
||||
def __init__(self, _xmlui_parent, value):
|
||||
super(LabelWidget, self).__init__(_xmlui_parent, value+": ")
|
||||
|
||||
|
||||
class JidWidget(xmlui.JidWidget, TextWidget):
|
||||
pass
|
||||
|
||||
|
||||
class DividerWidget(WixWidget, xmlui.DividerWidget, wx.StaticLine):
|
||||
|
||||
def __init__(self, _xmlui_parent, style='line'):
|
||||
wx.StaticLine.__init__(self, _xmlui_parent, -1)
|
||||
|
||||
|
||||
class StringWidget(EventWidget, ValueWidget, xmlui.StringWidget, wx.TextCtrl):
|
||||
_xmlui_change_event = wx.EVT_TEXT
|
||||
|
||||
def __init__(self, _xmlui_parent, value, read_only=False):
|
||||
style = wx.TE_READONLY if read_only else 0
|
||||
wx.TextCtrl.__init__(self, _xmlui_parent, -1, value, style=style)
|
||||
self._xmlui_proportion = 1
|
||||
|
||||
|
||||
class PasswordWidget(EventWidget, ValueWidget, xmlui.PasswordWidget, wx.TextCtrl):
|
||||
_xmlui_change_event = wx.EVT_TEXT
|
||||
|
||||
def __init__(self, _xmlui_parent, value, read_only=False):
|
||||
style = wx.TE_PASSWORD
|
||||
if read_only:
|
||||
style |= wx.TE_READONLY
|
||||
wx.TextCtrl.__init__(self, _xmlui_parent, -1, value, style=style)
|
||||
self._xmlui_proportion = 1
|
||||
|
||||
|
||||
class TextBoxWidget(EventWidget, ValueWidget, xmlui.TextBoxWidget, wx.TextCtrl):
|
||||
_xmlui_change_event = wx.EVT_TEXT
|
||||
|
||||
def __init__(self, _xmlui_parent, value, read_only=False):
|
||||
style = wx.TE_MULTILINE
|
||||
if read_only:
|
||||
style |= wx.TE_READONLY
|
||||
wx.TextCtrl.__init__(self, _xmlui_parent, -1, value, style=style)
|
||||
self._xmlui_proportion = 1
|
||||
|
||||
|
||||
class BoolWidget(EventWidget, ValueWidget, xmlui.BoolWidget, wx.CheckBox):
|
||||
_xmlui_change_event = wx.EVT_CHECKBOX
|
||||
|
||||
def __init__(self, _xmlui_parent, state, read_only=False):
|
||||
style = wx.CHK_2STATE
|
||||
if read_only:
|
||||
style |= wx.TE_READONLY
|
||||
wx.CheckBox.__init__(self, _xmlui_parent, -1, "", style=wx.CHK_2STATE)
|
||||
self.SetValue(state)
|
||||
self._xmlui_proportion = 1
|
||||
|
||||
def _xmluiSetValue(self, value):
|
||||
self.SetValue(value == 'true')
|
||||
|
||||
def _xmluiGetValue(self):
|
||||
return "true" if self.GetValue() else "false"
|
||||
|
||||
|
||||
# TODO: use wx.SpinCtrl instead of wx.TextCtrl
|
||||
class IntWidget(EventWidget, ValueWidget, xmlui.IntWidget, wx.TextCtrl):
|
||||
_xmlui_change_event = wx.EVT_TEXT
|
||||
|
||||
def __init__(self, _xmlui_parent, value, read_only=False):
|
||||
style = wx.TE_READONLY if read_only else 0
|
||||
wx.TextCtrl.__init__(self, _xmlui_parent, -1, value, style=style)
|
||||
self._xmlui_proportion = 1
|
||||
|
||||
|
||||
class ButtonWidget(EventWidget, WixWidget, xmlui.ButtonWidget, wx.Button):
|
||||
_xmlui_change_event = wx.EVT_BUTTON
|
||||
|
||||
def __init__(self, _xmlui_parent, value, click_callback):
|
||||
wx.Button.__init__(self, _xmlui_parent, -1, value)
|
||||
self._xmlui_click_callback = click_callback
|
||||
_xmlui_parent.Bind(wx.EVT_BUTTON, lambda evt: click_callback(evt.GetEventObject()), self)
|
||||
self._xmlui_parent = _xmlui_parent
|
||||
|
||||
def _xmluiOnClick(self, callback):
|
||||
self._xmlui_parent.Bind(wx.EVT_BUTTON, lambda evt: callback(evt.GetEventObject()), self)
|
||||
|
||||
|
||||
class ListWidget(EventWidget, WixWidget, xmlui.ListWidget, wx.ListBox):
|
||||
_xmlui_change_event = wx.EVT_LISTBOX
|
||||
|
||||
def __init__(self, _xmlui_parent, options, selected, flags):
|
||||
styles = wx.LB_MULTIPLE if not 'single' in flags else wx.LB_SINGLE
|
||||
wx.ListBox.__init__(self, _xmlui_parent, -1, choices=[option[1] for option in options], style=styles)
|
||||
self._xmlui_attr_map = {label: value for value, label in options}
|
||||
self._xmlui_proportion = 1
|
||||
self._xmluiSelectValues(selected)
|
||||
|
||||
def _xmluiSelectValue(self, value):
|
||||
try:
|
||||
label = [label for label, _value in self._xmlui_attr_map.items() if _value == value][0]
|
||||
except IndexError:
|
||||
log.warning(_("Can't find value [%s] to select" % value))
|
||||
return
|
||||
for idx in xrange(self.GetCount()):
|
||||
self.SetSelection(idx, self.GetString(idx) == label)
|
||||
|
||||
def _xmluiSelectValues(self, values):
|
||||
labels = [label for label, _value in self._xmlui_attr_map.items() if _value in values]
|
||||
for idx in xrange(self.GetCount()):
|
||||
self.SetSelection(idx, self.GetString(idx) in labels)
|
||||
|
||||
def _xmluiGetSelectedValues(self):
|
||||
ret = []
|
||||
labels = [self.GetString(idx) for idx in self.GetSelections()]
|
||||
for label in labels:
|
||||
ret.append(self._xmlui_attr_map[label])
|
||||
return ret
|
||||
|
||||
def _xmluiAddValues(self, values, select=True):
|
||||
selected = self._xmluiGetSelectedValues()
|
||||
for value in values:
|
||||
if value not in self._xmlui_attr_map.values():
|
||||
wx.ListBox.Append(self, value)
|
||||
self._xmlui_attr_map[value] = value
|
||||
if value not in selected:
|
||||
selected.append(value)
|
||||
self._xmluiSelectValues(selected)
|
||||
|
||||
|
||||
class WixContainer(object):
|
||||
_xmlui_proportion = 1
|
||||
|
||||
def _xmluiAppend(self, widget):
|
||||
self.sizer.Add(widget, self._xmlui_proportion, flag=wx.EXPAND)
|
||||
|
||||
|
||||
class AdvancedListContainer(WixContainer, xmlui.AdvancedListContainer, wx.ScrolledWindow):
|
||||
|
||||
def __init__(self, _xmlui_parent, columns, selectable='no'):
|
||||
wx.ScrolledWindow.__init__(self, _xmlui_parent)
|
||||
self._xmlui_selectable = selectable != 'no'
|
||||
if selectable:
|
||||
columns += 1
|
||||
self.sizer = wx.FlexGridSizer(cols=columns)
|
||||
self.SetSizer(self.sizer)
|
||||
self._xmlui_select_cb = None
|
||||
self._xmlui_select_idx = None
|
||||
self._xmlui_select_widgets = []
|
||||
|
||||
def _xmluiAddRow(self, idx):
|
||||
# XXX: select_button is a Q&D way to implement row selection
|
||||
# FIXME: must be done properly
|
||||
if not self._xmlui_selectable:
|
||||
return
|
||||
select_button = wx.Button(self, wx.ID_OK, label=_("select"))
|
||||
self.sizer.Add(select_button)
|
||||
def click_cb(event, idx=idx):
|
||||
cb = self._xmlui_select_cb
|
||||
self._xmlui_select_idx = idx
|
||||
# TODO: fill self._xmlui_select_widgets
|
||||
if cb is not None:
|
||||
cb(self)
|
||||
event.Skip()
|
||||
self.Bind(wx.EVT_BUTTON, click_cb)
|
||||
|
||||
def _xmluiGetSelectedWidgets(self):
|
||||
return self._xmlui_select_widgets
|
||||
|
||||
def _xmluiGetSelectedIndex(self):
|
||||
return self._xmlui_select_idx
|
||||
|
||||
def _xmluiOnSelect(self, callback):
|
||||
self._xmlui_select_cb = callback
|
||||
|
||||
class PairsContainer(WixContainer, xmlui.PairsContainer, wx.Panel):
|
||||
|
||||
def __init__(self, _xmlui_parent):
|
||||
wx.Panel.__init__(self, _xmlui_parent)
|
||||
self.sizer = wx.FlexGridSizer(cols=2)
|
||||
self.sizer.AddGrowableCol(1) #The growable column need most of time to be the right one in pairs
|
||||
self.SetSizer(self.sizer)
|
||||
|
||||
|
||||
class TabsContainer(WixContainer, xmlui.TabsContainer, wx.Notebook):
|
||||
|
||||
def __init__(self, _xmlui_parent):
|
||||
wx.Notebook.__init__(self, _xmlui_parent, -1, style=wx.NB_LEFT if self._xmlui_main.type=='param' else 0)
|
||||
|
||||
def _xmluiAddTab(self, label):
|
||||
tab_panel = wx.Panel(self, -1)
|
||||
tab_panel.sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
tab_panel.SetSizer(tab_panel.sizer)
|
||||
self.AddPage(tab_panel, label)
|
||||
VerticalContainer._xmluiAdapt(tab_panel)
|
||||
return tab_panel
|
||||
|
||||
|
||||
class VerticalContainer(WixContainer, xmlui.VerticalContainer, wx.Panel):
|
||||
|
||||
def __init__(self, _xmlui_parent):
|
||||
wx.Panel.__init__(self, _xmlui_parent)
|
||||
self.sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.SetSizer(self.sizer)
|
||||
|
||||
|
||||
## Dialogs ##
|
||||
|
||||
|
||||
class WixDialog(object):
|
||||
|
||||
def __init__(self, _xmlui_parent, level):
|
||||
self.host = _xmlui_parent.host
|
||||
self.ok_cb = None
|
||||
self.cancel_cb = None
|
||||
if level == C.XMLUI_DATA_LVL_INFO:
|
||||
self.flags = wx.ICON_INFORMATION
|
||||
elif level == C.XMLUI_DATA_LVL_ERROR:
|
||||
self.flags = wx.ICON_ERROR
|
||||
else:
|
||||
self.flags = wx.ICON_INFORMATION
|
||||
log.warning(_("Unmanaged dialog level: %s") % level)
|
||||
|
||||
def _xmluiShow(self):
|
||||
answer = self.ShowModal()
|
||||
if answer == wx.ID_YES or answer == wx.ID_OK:
|
||||
self._xmluiValidated()
|
||||
else:
|
||||
self._xmluiCancelled()
|
||||
|
||||
def _xmluiClose(self):
|
||||
self.Destroy()
|
||||
|
||||
|
||||
class MessageDialog(WixDialog, xmlui.MessageDialog, wx.MessageDialog):
|
||||
|
||||
def __init__(self, _xmlui_parent, title, message, level):
|
||||
WixDialog.__init__(self, _xmlui_parent, level)
|
||||
xmlui.MessageDialog.__init__(self, _xmlui_parent)
|
||||
self.flags |= wx.OK
|
||||
wx.MessageDialog.__init__(self, _xmlui_parent.host, message, title, style = self.flags)
|
||||
|
||||
|
||||
class NoteDialog(xmlui.NoteDialog, MessageDialog):
|
||||
# TODO: separate NoteDialog
|
||||
pass
|
||||
|
||||
|
||||
class ConfirmDialog(WixDialog, xmlui.ConfirmDialog, wx.MessageDialog):
|
||||
|
||||
def __init__(self, _xmlui_parent, title, message, level, buttons_set):
|
||||
WixDialog.__init__(self, _xmlui_parent, level)
|
||||
xmlui.ConfirmDialog.__init__(self, _xmlui_parent)
|
||||
if buttons_set == C.XMLUI_DATA_BTNS_SET_YESNO:
|
||||
self.flags |= wx.YES_NO
|
||||
else:
|
||||
self.flags |= wx.OK | wx.CANCEL
|
||||
wx.MessageDialog.__init__(self, _xmlui_parent.host, message, title, style = self.flags)
|
||||
|
||||
|
||||
class FileDialog(WixDialog, xmlui.FileDialog, wx.FileDialog):
|
||||
|
||||
def __init__(self, _xmlui_parent, title, message, level, filetype):
|
||||
# TODO: message and filetype are not managed yet
|
||||
WixDialog.__init__(self, _xmlui_parent, level)
|
||||
self.flags = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT # FIXME: use the legacy flags, but must manage cases like dir or open
|
||||
xmlui.FileDialog.__init__(self, _xmlui_parent)
|
||||
wx.FileDialog.__init__(self, _xmlui_parent.host, title, style = self.flags)
|
||||
|
||||
def _xmluiShow(self):
|
||||
answer = self.ShowModal()
|
||||
if answer == wx.ID_OK:
|
||||
self._xmluiValidated({'path': self.GetPath()})
|
||||
else:
|
||||
self._xmluiCancelled()
|
||||
|
||||
|
||||
class GenericFactory(object):
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr.startswith("create"):
|
||||
cls = globals()[attr[6:]]
|
||||
return cls
|
||||
|
||||
|
||||
class WidgetFactory(GenericFactory):
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr.startswith("create"):
|
||||
cls = GenericFactory.__getattr__(self, attr)
|
||||
cls._xmlui_main = self._xmlui_main
|
||||
return cls
|
||||
|
||||
|
||||
class XMLUIPanel(xmlui.XMLUIPanel, wx.Frame):
|
||||
"""Create an user interface from a SàT XML"""
|
||||
widget_factory = WidgetFactory()
|
||||
|
||||
def __init__(self, host, parsed_xml, title=None, flags = None,):
|
||||
self.widget_factory._xmlui_main = self
|
||||
xmlui.XMLUIPanel.__init__(self, host, parsed_xml, title, flags)
|
||||
|
||||
def constructUI(self, parsed_dom):
|
||||
style = wx.DEFAULT_FRAME_STYLE & ~wx.CLOSE_BOX if 'NO_CANCEL' in self.flags else wx.DEFAULT_FRAME_STYLE
|
||||
wx.Frame.__init__(self, None, style=style)
|
||||
self.sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.SetSizer(self.sizer)
|
||||
|
||||
def postTreat():
|
||||
if self.title:
|
||||
self.SetTitle(self.title)
|
||||
|
||||
if self.type == 'form':
|
||||
dialogButtons = wx.StdDialogButtonSizer()
|
||||
submitButton = wx.Button(self.main_cont,wx.ID_OK, label=_("Submit"))
|
||||
dialogButtons.AddButton(submitButton)
|
||||
self.main_cont.Bind(wx.EVT_BUTTON, self.onFormSubmitted, submitButton)
|
||||
if not 'NO_CANCEL' in self.flags:
|
||||
cancelButton = wx.Button(self.main_cont,wx.ID_CANCEL)
|
||||
dialogButtons.AddButton(cancelButton)
|
||||
self.main_cont.Bind(wx.EVT_BUTTON, self.onFormCancelled, cancelButton)
|
||||
dialogButtons.Realize()
|
||||
self.main_cont.sizer.Add(dialogButtons, flag=wx.ALIGN_CENTER_HORIZONTAL)
|
||||
|
||||
self.sizer.Add(self.main_cont, 1, flag=wx.EXPAND)
|
||||
self.sizer.Fit(self)
|
||||
self.Show()
|
||||
|
||||
super(XMLUIPanel, self).constructUI(parsed_dom, postTreat)
|
||||
if not 'NO_CANCEL' in self.flags:
|
||||
self.Bind(wx.EVT_CLOSE, self.onClose, self)
|
||||
self.MakeModal()
|
||||
|
||||
def _xmluiClose(self):
|
||||
self.MakeModal(False)
|
||||
self.Destroy()
|
||||
|
||||
###events
|
||||
|
||||
def onParamChange(self, ctrl):
|
||||
super(XMLUIPanel, self).onParamChange(ctrl)
|
||||
|
||||
def onFormSubmitted(self, event):
|
||||
"""Called when submit button is clicked"""
|
||||
button = event.GetEventObject()
|
||||
super(XMLUIPanel, self).onFormSubmitted(button)
|
||||
|
||||
def onClose(self, event):
|
||||
"""Close event: we have to send the form."""
|
||||
log.debug(_("close"))
|
||||
if self.type == 'param':
|
||||
self.onSaveParams()
|
||||
else:
|
||||
self._xmluiClose()
|
||||
event.Skip()
|
||||
|
||||
|
||||
class XMLUIDialog(xmlui.XMLUIDialog):
|
||||
dialog_factory = WidgetFactory()
|
||||
|
||||
|
||||
xmlui.registerClass(xmlui.CLASS_PANEL, XMLUIPanel)
|
||||
xmlui.registerClass(xmlui.CLASS_DIALOG, XMLUIDialog)
|
||||
create = xmlui.create
|
||||
Loading…
Reference in a new issue