Mercurial > libervia-backend
changeset 1289:653f2e2eea31 frontends_multi_profiles
Wix removal: Wix is now officially abandonned (a futur desktop frontend will replace it)
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 24 Jan 2015 00:15:01 +0100 |
parents | 7cf32aeeebdb |
children | faa1129559b8 |
files | frontends/src/wix/__init__.py frontends/src/wix/card_game.py frontends/src/wix/chat.py frontends/src/wix/constants.py frontends/src/wix/contact_list.py frontends/src/wix/main_window.py frontends/src/wix/profile.py frontends/src/wix/profile_manager.py frontends/src/wix/quiz_game.py frontends/src/wix/wix frontends/src/wix/xmlui.py |
diffstat | 10 files changed, 0 insertions(+), 2327 deletions(-) [+] |
line wrap: on
line diff
--- a/frontends/src/wix/card_game.py Sat Jan 24 00:14:58 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -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() - -
--- a/frontends/src/wix/chat.py Sat Jan 24 00:14:58 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -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
--- a/frontends/src/wix/constants.py Sat Jan 24 00:14:58 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -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()
--- a/frontends/src/wix/contact_list.py Sat Jan 24 00:14:58 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -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 -
--- a/frontends/src/wix/main_window.py Sat Jan 24 00:14:58 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -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()
--- a/frontends/src/wix/profile.py Sat Jan 24 00:14:58 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -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() -
--- a/frontends/src/wix/profile_manager.py Sat Jan 24 00:14:58 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -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()
--- a/frontends/src/wix/quiz_game.py Sat Jan 24 00:14:58 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -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()
--- a/frontends/src/wix/wix Sat Jan 24 00:14:58 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -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()
--- a/frontends/src/wix/xmlui.py Sat Jan 24 00:14:58 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -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