Wix removal: Wix is now officially abandonned (a futur desktop frontend will replace it)

This commit is contained in:
Goffi 2015-01-24 00:15:01 +01:00
commit f45e55388f
11 changed files with 0 additions and 2327 deletions

View file

@ -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()

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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