Files reorganisation

This commit is contained in:
Goffi 2010-12-29 01:06:29 +01:00
commit e4452c4b03
8 changed files with 10 additions and 8 deletions

View file

@ -0,0 +1,335 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Primitivus: a SAT frontend
Copyright (C) 2009, 2010 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import urwid
from tools.games import TarotCard
from quick_frontend.quick_card_game import QuickCardGame
from xmlui import XMLUI
from urwid_satext import sat_widgets
class CardDisplayer(urwid.Text):
"""Show a card"""
signals = ['click']
def __init__(self, card):
self.__selected = False
self.card = card
urwid.Text.__init__(self, card.getAttrText())
def selectable(self):
return True
def keypress(self, size, key):
if key == ' ':
self.select(not self.__selected)
self._emit('click')
return key
def mouse_event(self, size, event, button, x, y, focus):
if urwid.is_mouse_press(event) and button == 1:
self.select(not self.__selected)
self._emit('click')
return True
return False
def select(self, state=True):
self.__selected = state
attr,txt = self.card.getAttrText()
if self.__selected:
attr+='_selected'
self.set_text((attr,txt))
self._invalidate()
def isSelected(self):
return self.__selected
def getCard(self):
return self.card
def render(self, size, focus=False):
canvas = urwid.CompositeCanvas(urwid.Text.render(self, size, focus))
if focus:
canvas.set_cursor((0,0))
return canvas
class Hand(urwid.WidgetWrap):
"""Used to display several cards, and manage a hand"""
signals = ['click']
def __init__(self, hand=[], selectable = False, on_click=None, user_data=None):
"""@param hand: list of Card"""
self.__selectable = selectable
self.columns = urwid.Columns([],dividechars=1)
if on_click:
urwid.connect_signal(self, 'click', on_click, user_data)
if hand:
self.update(hand)
urwid.WidgetWrap.__init__(self, self.columns)
def selectable(self):
return self.__selectable
def keypress(self, size, key):
if CardDisplayer in [wid.__class__ for wid in self.columns.widget_list]:
return self.columns.keypress(size,key)
else:
#No card displayed, we still have to manage the clicks
if key == ' ':
self._emit('click', None)
return key
def getSelected(self):
"""Return a list of selected cards"""
_selected = []
for wid in self.columns.widget_list:
if isinstance(wid, CardDisplayer) and wid.isSelected():
_selected.append(wid.getCard())
return _selected
def update(self, hand):
"""Update the hand displayed in this widget
@param hand: list of Card"""
del self.columns.widget_list[:]
del self.columns.column_types[:]
self.columns.widget_list.append(urwid.Text(''))
self.columns.column_types.append(('weight',1))
for card in hand:
widget = CardDisplayer(card)
self.columns.widget_list.append(widget)
self.columns.column_types.append(('fixed',3))
urwid.connect_signal(widget, 'click', self.__onClick)
self.columns.widget_list.append(urwid.Text(''))
self.columns.column_types.append(('weight',1))
self.columns.set_focus(1)
def __onClick(self,card_wid):
self._emit('click', card_wid)
class Card(TarotCard):
"""This class is used to represent a card, logically
and give a text representation with attributes"""
SIZE = 3 #size of a displayed card
def __init__(self, suit, value):
"""@param file: path of the PNG file"""
TarotCard.__init__(self, (suit, value))
def getAttrText(self):
"""return text representation of the card with attributes"""
try:
value = "%02i" % int(self.value)
except ValueError:
value = self.value[0].upper()+self.value[1]
if self.suit == "atout":
if self.value == "excuse":
suit = 'c'
else:
suit = 'A'
color = 'neutral'
elif self.suit == "pique":
suit = u'♠'
color = 'black'
elif self.suit == "trefle":
suit = u'♣'
color = 'black'
elif self.suit == "coeur":
suit = u'♥'
color = 'red'
elif self.suit == "carreau":
suit = u'♦'
color = 'red'
if self.bout:
color = 'special'
return ('card_%s' % color,u"%s%s" % (value,suit))
def getWidget(self):
"""Return a widget representing the card"""
return CardDisplayer(self)
class Table(urwid.FlowWidget):
"""Represent the cards currently on the table"""
def __init__(self):
self.top = self.left = self.bottom = self.right = None
def putCard(self, location, card):
"""Put a card on the table
@param location: where to put the card (top, left, bottom or right)
@param card: Card to play or None"""
assert location in ['top','left','bottom','right']
assert isinstance(card,Card) or card == None
if [getattr(self, place) for place in ['top','left','bottom','right']].count(None) == 0:
#If the table is full of card, we remove them
self.top = self.left = self.bottom = self.right = None
setattr(self, location, card)
self._invalidate()
def rows(self,size,focus=False):
return self.display_widget(size, focus).rows(size, focus)
def render(self, size, focus=False):
return self.display_widget(size, focus).render(size, focus)
def display_widget(self, size, focus):
cards={}
max_col, = size
separator = " - "
margin = max((max_col-Card.SIZE)/2,0) * ' '
margin_center = max((max_col-Card.SIZE*2-len(separator))/2,0) * ' '
for location in ['top', 'left', 'bottom', 'right']:
card = getattr(self,location)
cards[location] = card.getAttrText() if card else Card.SIZE * ' '
render_wid = [urwid.Text([margin,cards['top']]),
urwid.Text([margin_center,cards['left'],separator,cards['right']]),
urwid.Text([margin,cards['bottom']])]
return urwid.Pile(render_wid)
class CardGame(QuickCardGame,urwid.WidgetWrap):
"""Widget for card games"""
def __init__(self, parent, referee, players, player_nick):
QuickCardGame.__init__(self, parent, referee, players, player_nick)
self.loadCards()
self.top = urwid.Pile([urwid.Padding(urwid.Text(self.top_nick), 'center')])
#self.parent.host.debug()
self.table = Table()
self.center = urwid.Columns([('fixed',len(self.left_nick),urwid.Filler(urwid.Text(self.left_nick))),
urwid.Filler(self.table),
('fixed',len(self.right_nick),urwid.Filler(urwid.Text(self.right_nick)))
])
"""urwid.Pile([urwid.Padding(self.top_card_wid,'center'),
urwid.Columns([('fixed',len(self.left_nick),urwid.Text(self.left_nick)),
urwid.Padding(self.center_cards_wid,'center'),
('fixed',len(self.right_nick),urwid.Text(self.right_nick))
]),
urwid.Padding(self.bottom_card_wid,'center')
])"""
self.hand_wid = Hand(selectable = True, on_click = self.onClick)
self.main_frame = urwid.Frame(self.center,header=self.top, footer=self.hand_wid, focus_part='footer')
urwid.WidgetWrap.__init__(self,self.main_frame)
self.parent.host.bridge.tarotGameReady(player_nick, referee, profile_key = self.parent.host.profile)
def loadCards(self):
"""Load all the cards in memory"""
QuickCardGame.loadCards(self)
for value in map(str,range(1,22))+['excuse']:
card = Card('atout',value)
self.cards[card.suit, card.value]=card
self.deck.append(card)
for suit in ["pique", "coeur", "carreau", "trefle"]:
for value in map(str,range(1,11))+["valet","cavalier","dame","roi"]:
card = Card(suit,value)
self.cards[card.suit, card.value]=card
self.deck.append(card)
def newGame(self, hand):
"""Start a new game, with given hand"""
QuickCardGame.newGame(self, hand)
self.hand_wid.update(self.hand)
self.parent.host.redraw()
def contratSelected(self, data):
"""Called when the contrat has been choosed
@param data: form result"""
contrat = data[0][1]
QuickCardGame.contratSelected(self, contrat)
def chooseContrat(self, xml_data):
"""Called when the player as to select his contrat
@param xml_data: SàT xml representation of the form"""
misc = {'callback': self.contratSelected}
form = XMLUI(self.parent.host, xml_data, title = _('Please choose your contrat'), options = ['NO_CANCEL'], misc = misc)
form.show()
def showCards(self, game_stage, cards, data):
"""Display cards in the middle of the game (to show for e.g. chien ou poignée)"""
QuickCardGame.showCards(self, game_stage, cards, data)
self.center.widget_list[1] = urwid.Filler(Hand(self.to_show))
self.parent.host.redraw()
def myTurn(self):
QuickCardGame.myTurn(self)
def showScores(self, xml_data, winners, loosers):
"""Called when the player as to select hist contrat
@param xml_data: SàT xml representation of the form"""
form = XMLUI(self.parent.host, xml_data, title = _('You win \o/') if self.player_nick in winners else _('You loose :('), options = ['NO_CANCEL'])
form.show()
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.hand_wid.update(self.hand)
if self._autoplay==None: #No dialog if there is autoplay
self.parent.host.notify(_('Cards played are invalid !'))
self.parent.host.redraw()
def cardsPlayed(self, player, cards):
"""A card has been played by player"""
QuickCardGame.cardsPlayed(self, player, cards)
self.table.putCard(self.getPlayerLocation(player),self.played[player])
self.parent.host.redraw()
##EVENTS##
def onClick(self, hand, card_wid):
"""Called when user do an action on the hand"""
if not self.state in ['play','ecart','wait_for_ecart']:
#it's not our turn, we ignore the click
card_wid.select(False)
return
if isinstance(self.center.widget_list[1].original_widget, Hand): #if we have a hand displayed
self.center.widget_list[1] = urwid.Filler(self.table) #we show again the table
if self.state == "chien":
self.to_show = []
self.state = "wait"
elif self.state == "wait_for_ecart":
self.state = "ecart"
self.hand.extend(self.to_show)
self.hand.sort()
self.to_show = []
self.hand_wid.update(self.hand)
if self.state == "ecart":
if len(self.hand_wid.getSelected()) == 6:
pop_up_widget = sat_widgets.ConfirmDialog(_("Do you put these cards in chien ?"), yes_cb=self.onEcartDone, no_cb=self.parent.host.removePopUp)
self.parent.host.showPopUp(pop_up_widget)
elif self.state == "play":
card = card_wid.getCard()
self.parent.host.bridge.tarotGamePlayCards(self.player_nick, self.referee, [(card.suit, card.value)], profile_key = self.parent.host.profile)
self.hand.remove(card)
self.hand_wid.update(self.hand)
self.state = "wait"
def onEcartDone(self,button):
"""Called when player has finished is écart"""
ecart = []
for card in self.hand_wid.getSelected():
ecart.append((card.suit, card.value))
self.hand.remove(card)
self.hand_wid.update(self.hand)
self.parent.host.bridge.tarotGamePlayCards(self.player_nick, self.referee, ecart, profile_key = self.parent.host.profile)
self.state = "wait"
self.parent.host.removePopUp()

View file

@ -0,0 +1,279 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Primitivus: a SAT frontend
Copyright (C) 2009, 2010 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import urwid
from quick_frontend.quick_contact_list import QuickContactList
from quick_frontend.quick_chat import QuickChat
from urwid_satext import sat_widgets
import time
from tools.jid import JID
from card_game import CardGame
from urwid_satext.files_management import FileDialog
class ChatText(urwid.FlowWidget):
"""Manage the printing of chat message"""
def __init__(self, parent, timestamp, nick, my_mess, message, align='left'):
self.parent = parent
self.timestamp = time.localtime(timestamp)
self.nick = nick
self.my_mess = my_mess
self.message = unicode(message)
self.align = align
def selectable(self):
return True
def keypress(self, size, key):
return key
def rows(self,size,focus=False):
return self.display_widget(size, focus).rows(size, focus)
def render(self, size, focus=False):
canvas = urwid.CompositeCanvas(self.display_widget(size, focus).render(size, focus))
if focus:
canvas.set_cursor(self.get_cursor_coords(size))
return canvas
def get_cursor_coords(self, size):
#(maxcol,) = size
return 0, 0
def display_widget(self, size, focus):
render_txt = []
if self.parent.show_timestamp:
time_format = "%c" if self.timestamp < self.parent.day_change else "%H:%M" #if the message was sent before today, we print the full date
render_txt.append(('date',"[%s]" % time.strftime(time_format, self.timestamp).decode('utf-8')))
if self.parent.show_short_nick:
render_txt.append(('my_nick' if self.my_mess else 'other_nick',"**" if self.my_mess else "*"))
else:
render_txt.append(('my_nick' if self.my_mess else 'other_nick',"[%s] " % self.nick))
render_txt.append(self.message)
return urwid.Text(render_txt, align=self.align)
class Chat(urwid.WidgetWrap, QuickChat):
def __init__(self, target, host, type='one2one'):
self.target = target
QuickChat.__init__(self, target, host, type)
self.content = urwid.SimpleListWalker([])
self.text_list = urwid.ListBox(self.content)
self.chat_widget = urwid.Frame(self.text_list)
self.chat_colums = urwid.Columns([('weight', 8, self.chat_widget)])
self.chat_colums = urwid.Columns([('weight', 8, self.chat_widget)])
self.pile = urwid.Pile([self.chat_colums])
urwid.WidgetWrap.__init__(self, self.__getDecoration(self.pile))
self.setType(type)
self.day_change = time.strptime(time.strftime("%a %b %d 00:00:00 %Y")) #struct_time of day changing time
self.show_timestamp = True
self.show_short_nick = False
self.show_title = 1 #0: clip title; 1: full title; 2: no title
self.subject = None
def keypress(self, size, key):
if key == "meta p": #user wants to (un)hide the presents panel
if self.type == 'group':
widgets = self.chat_colums.widget_list
if self.present_panel in widgets:
self.__removePresentPanel()
else:
self.__appendPresentPanel()
elif key == "meta t": #user wants to (un)hide timestamp
self.show_timestamp = not self.show_timestamp
for wid in self.content:
wid._invalidate()
elif key == "meta n": #user wants to (not) use short nick
self.show_short_nick = not self.show_short_nick
for wid in self.content:
wid._invalidate()
elif key == "meta l": #user wants to (un)hide widget decoration
show = not isinstance(self._w, sat_widgets.LabelLine)
self.showDecoration(show)
self._invalidate()
elif key == "meta s": #user wants to (un)hide group's subject or change its apperance
if self.subject:
self.show_title = (self.show_title + 1) % 3
if self.show_title == 0:
self.setSubject(self.subject,'clip')
elif self.show_title == 1:
self.setSubject(self.subject,'space')
elif self.show_title == 2:
self.chat_widget.header = None
self._invalidate()
return super(Chat, self).keypress(size, key)
def getMenu(self):
"""Return Menu bar"""
menu = sat_widgets.Menu(self.host.loop)
if self.type == 'group':
game = _("Game")
menu.addMenu(game, "Tarot", self.onTarotRequest)
elif self.type == 'one2one':
menu.addMenu(_("Action"), _("Send file"), self.onSendFileRequest)
return menu
def setType(self, type):
QuickChat.setType(self, type)
if type == 'one2one':
self.historyPrint(profile=self.host.profile)
elif type == 'group':
if len(self.chat_colums.widget_list) == 1:
present_widget = self.__buildPresentList()
self.present_panel = sat_widgets.VerticalSeparator(present_widget)
self.__appendPresentPanel()
def __getDecoration(self, widget):
return sat_widgets.LabelLine(widget, sat_widgets.SurroundedText(unicode(self.target)))
def showDecoration(self, show=True):
"""Show/Hide the decoration around the chat window"""
if show:
main_widget = self.__getDecoration(self.pile)
else:
main_widget = self.pile
self._w = main_widget
def __buildPresentList(self):
self.present_wid = sat_widgets.GenericList([],option_type = sat_widgets.ClickableText)
return self.present_wid
def __appendPresentPanel(self):
self.chat_colums.widget_list.append(self.present_panel)
self.chat_colums.column_types.append(('weight', 2))
def __removePresentPanel(self):
self.chat_colums.set_focus(0) #necessary as the focus change to the next object, we can go out of range if we are on the last object of self.chat_colums
self.chat_colums.widget_list.remove(self.present_panel)
del self.chat_colums.column_types[-1]
def __appendGamePanel(self, widget):
assert (len(self.pile.widget_list) == 1)
self.pile.widget_list.insert(0,widget)
self.pile.item_types.insert(0,('weight', 1))
self.pile.widget_list.insert(1,urwid.Filler(urwid.Divider('-')))
self.pile.item_types.insert(1,('fixed', 1))
self.host.redraw()
def __removeGamePanel(self):
assert (len(self.pile.widget_list) == 3)
self.pile.set_focus(0) #necessary as the focus change to the next object, we can go out of range if we are on the last object of self.chat_colums
del self.pile.widget_list[0]
del self.pile.item_types[0]
self.host.redraw()
def setSubject(self, subject, wrap='space'):
"""Set title for a group chat"""
QuickChat.setSubject(self, subject)
self.subject = subject
self.subj_wid = urwid.Text(unicode(subject.replace('\n','|') if wrap == 'clip' else subject ),
align='left' if wrap=='clip' else 'center',wrap=wrap)
self.chat_widget.header = urwid.AttrMap(self.subj_wid,'title')
self.host.redraw()
def setPresents(self, param_nicks):
"""Set the users presents in the contact list for a group chat
@param nicks: list of nicknames
"""
nicks = [unicode(nick) for nick in param_nicks] #FIXME: should be done in DBus bridge
nicks.sort()
QuickChat.setPresents(self, nicks)
self.present_wid.changeValues(nicks)
self.host.redraw()
def replaceUser(self, param_nick):
"""Add user if it is not in the group list"""
nick = unicode(param_nick) #FIXME: should be done in DBus bridge
if "facebook" in nick:
self.host.debug()
QuickChat.replaceUser(self, nick)
presents = self.present_wid.getAllValues()
if nick not in presents:
presents.append(nick)
presents.sort()
self.present_wid.changeValues(presents)
self.host.redraw()
def removeUser(self, param_nick):
"""Remove a user from the group list"""
nick = unicode(param_nick) #FIXME: should be done in DBus bridge
QuickChat.removeUser(self, nick)
self.present_wid.deleteValue(nick)
self.host.redraw()
def printMessage(self, from_jid, msg, profile, timestamp=""):
assert isinstance(from_jid, JID)
try:
jid,nick,mymess = QuickChat.printMessage(self, from_jid, msg, profile, timestamp)
except TypeError:
return
my_jid = self.host.profiles[profile]['whoami']
self.content.append(ChatText(self, timestamp or None, nick, mymess, msg))
self.text_list.set_focus(len(self.content)-1)
self.host.redraw()
def printInfo(self, msg, type='normal'):
"""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"
"""
self.content.append(sat_widgets.ClickableText(msg))
self.text_list.set_focus(len(self.content)-1)
self.host.redraw()
def startGame(self, game_type, referee, players):
"""Configure the chat window to start a game"""
if game_type=="Tarot":
try:
self.tarot_wid = CardGame(self, referee, players, self.nick)
self.__appendGamePanel(self.tarot_wid)
except e:
self.host.debug()
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_wid
#MENU EVENTS#
def onTarotRequest(self, menu):
if len(self.occupants) != 4:
self.host.showPopUp(sat_widgets.Alert(_("Can't start game"), _("You need to be exactly 4 peoples in the room to start a Tarot game"), ok_cb=self.host.removePopUp))
else:
self.host.bridge.tarotGameCreate(self.id, list(self.occupants), self.host.profile)
def onSendFileRequest(self, menu):
dialog = FileDialog(ok_cb=self.onFileSelected, cancel_cb=self.host.removePopUp)
self.host.showPopUp(dialog, 80, 80)
#MISC EVENTS#
def onFileSelected(self, filepath):
self.host.removePopUp()
full_jid = self.host.CM.get_full(self.target)
id = self.host.bridge.sendFile(full_jid, filepath)
self.host.addProgress(id,filepath)

View file

@ -0,0 +1,183 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Primitivus: a SAT frontend
Copyright (C) 2009, 2010 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import urwid
from quick_frontend.quick_contact_list import QuickContactList
from tools.jid import JID
from urwid_satext import sat_widgets
class ContactList(urwid.WidgetWrap, QuickContactList):
signals = ['click','change']
def __init__(self, host, CM, on_click=None, on_change=None, user_data=None):
self.host = host
self.selected = None
self.groups={}
self.alert_jid=set()
#we now build the widget
self.frame = urwid.Frame(self.__buildList())
self.main_widget = sat_widgets.LabelLine(self.frame, sat_widgets.SurroundedText(_("Contacts")))
urwid.WidgetWrap.__init__(self, self.main_widget)
if on_click:
urwid.connect_signal(self, 'click', on_click, user_data)
if on_change:
urwid.connect_signal(self, 'change', on_change, user_data)
QuickContactList.__init__(self, CM)
def __contains__(self, jid):
for group in self.groups:
if jid.short in self.groups[group][1]:
return True
return False
def setFocus(self, name):
"""give focus to the first group or contact with the given name"""
idx = 0
for widget in self.frame.body.body:
if widget.getValue() == name:
self.frame.body.set_focus(idx)
return
idx+=1
def putAlert(self, jid):
"""Put an alert on the jid to get attention from user (e.g. for new message)"""
self.alert_jid.add(jid.short)
self.frame.body = self.__buildList()
self.host.redraw()
def __groupClicked(self, group_wid):
group = self.groups[group_wid.getValue()]
group[0] = not group[0]
self.frame.body = self.__buildList()
self.host.redraw()
self.setFocus(group_wid.getValue())
def __contactClicked(self, contact_wid, selected):
self.selected = contact_wid.data
for widget in self.frame.body.body:
if widget.__class__ == sat_widgets.SelectableText:
widget.setState(widget.data == self.selected, invisible=True)
if self.selected in self.alert_jid:
self.alert_jid.remove(self.selected)
self.frame.body = self.__buildList()
self.host.redraw()
self._emit('click')
def __buildContact(self, content, param_contacts):
"""Add contact representation in widget list
@param content: widget list, e.g. SimpleListWalker
@param contacts: list of JID"""
contacts = list(param_contacts)
contacts.sort()
for contact in contacts:
jid=JID(contact)
name = self.CM.getAttr(jid,'name')
nick = self.CM.getAttr(jid,'nick')
display = nick or name or jid.node or jid.short
header = '(*) ' if contact in self.alert_jid else ''
widget = sat_widgets.SelectableText(('alert' if contact in self.alert_jid else 'default',display),
selected = contact==self.selected, header=header)
widget.data = contact
content.append(widget)
urwid.connect_signal(widget, 'change', self.__contactClicked)
def __buildList(self):
"""Build the main contact list widget"""
content = urwid.SimpleListWalker([])
group_keys = self.groups.keys()
group_keys.sort()
for key in group_keys:
unfolded = self.groups[key][0]
if key!=None:
header = '[-]' if unfolded else '[+]'
widget = sat_widgets.ClickableText(key,header=header+' ')
content.append(widget)
urwid.connect_signal(widget, 'click', self.__groupClicked)
if unfolded:
self.__buildContact(content, self.groups[key][1])
return urwid.ListBox(content)
def unselectAll(self):
"""Unselect all contacts"""
self.selected = None
for widget in self.frame.body.body:
if widget.__class__ == sat_widgets.SelectableText:
widget.setState(False, invisible=True)
def get_contact(self):
"""Return contact currently selected"""
return self.selected
def clear_contacts(self):
"""clear all the contact list"""
self.groups={}
self.selected = None
self.unselectAll()
self.frame.body = self.__buildList()
self.host.redraw()
def replace(self, jid, groups=[None]):
"""add a contact to the list if doesn't exist, else update it"""
assert isinstance(groups, list)
assert isinstance(jid, JID)
if not groups:
groups=[None]
for group in groups:
if not self.groups.has_key(group):
self.groups[group] = [True,set()] #[unfold,list_of_contacts]
self.groups[group][1].add(jid.short)
self.frame.body = self.__buildList()
self.host.redraw()
"""contacts = self.list_wid.getAllValues()
if jid.short not in contacts:
contacts.append(jid.short)
contacts.sort()
self.list_wid.changeValues(contacts)
self._emit('change')"""
def disconnect(self, jid):
"""mark a contact disconnected"""
self.remove(jid.short)
def remove(self, param_jid):
"""remove a contact from the list"""
groups_to_remove = []
jid = JID(param_jid)
for group in self.groups:
contacts = self.groups[group][1]
if jid.short in contacts:
contacts.remove(jid.short)
if not len(contacts):
groups_to_remove.append(group)
for group in groups_to_remove:
del self.groups[group]
self.frame.body = self.__buildList()
self.host.redraw()
def add(self, jid, param_groups=[None]):
"""add a contact to the list"""
self.replace(jid,param_groups)

View file

@ -0,0 +1,88 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Primitivus: a SAT frontend
Copyright (C) 2009, 2010 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import urwid
from urwid_satext import sat_widgets
from tools.jid import JID
from quick_frontend.quick_gateways import QuickGatewaysManager
class GatewaysManager(urwid.WidgetWrap, QuickGatewaysManager):
def __init__(self, host, gateways, title=_("Gateways manager"), server=None):
QuickGatewaysManager.__init__(self, host, gateways, server)
if server:
title+=" (%s)" % server
widget_list = urwid.SimpleListWalker([])
widget_list.append(urwid.Text(self.WARNING_MSG))
widget_list.append(urwid.Divider('-'))
for gateway in gateways:
self.addGateway(widget_list,gateway, gateways[gateway])
widget_list.append(urwid.Divider())
self.ext_serv = sat_widgets.AdvancedEdit(_("Use external XMPP server: "))
go_button = sat_widgets.CustomButton( _("GO !"),self.browseExternalServer)
ext_serv_col = urwid.Columns([self.ext_serv,('fixed',go_button.getSize(),go_button)])
widget_list.append(ext_serv_col)
list_wid = urwid.ListBox(widget_list)
decorated = sat_widgets.LabelLine(list_wid, sat_widgets.SurroundedText(title))
urwid.WidgetWrap.__init__(self, decorated)
def browseExternalServer(self, button):
"""Open the gateway manager on given server"""
server = self.ext_serv.get_edit_text()
if not server:
popup = sat_widgets.Alert(_("Error"), _("You must enter an external server JID"), ok_cb=self.host.removePopUp)
self.host.showPopUp(popup)
return
id = self.host.bridge.findGateways(server, self.host.profile)
self.host.current_action_ids.add(id)
self.host.current_action_ids_cb[id] = self.host.onGatewaysFound
self.host.removeWindow()
def addGateway(self, widget_list, gateway, param):
widget_col = []
widget_col.append(('weight',4,urwid.Text(unicode(param['name'])))) #FIXME: unicode to be remove when DBus bridge will not give dbus.String anymore
#Then the transport type message
widget_col.append(('weight',6,urwid.Text(self.getGatewayDesc(param['type']))))
#The buttons
reg_button = sat_widgets.CustomButton( _("Register"), self.onRegister)
reg_button.gateway_jid = JID(gateway)
widget_col.append(('fixed',reg_button.getSize(),reg_button))
unreg_button = sat_widgets.CustomButton( _("Unregister"), self.onUnregister)
unreg_button.gateway_jid = JID(gateway)
widget_col.append(('fixed',unreg_button.getSize(),unreg_button))
widget_list.append(urwid.Columns(widget_col,1))
def onRegister(self, button):
"""Called when register button is clicked"""
gateway_jid = button.gateway_jid
id = self.host.bridge.in_band_register(gateway_jid, self.host.profile)
self.host.current_action_ids.add(id)
def onUnregister(self, button):
"""Called when unregister button is clicked"""
gateway_jid = button.gateway_jid
id = self.host.bridge.gatewayRegister("CANCEL",gateway_jid, None, self.host.profile)
self.host.current_action_ids.add(id)

View file

@ -0,0 +1,509 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Primitivus: a SAT frontend
Copyright (C) 2009, 2010 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from quick_frontend.quick_app import QuickApp
from quick_frontend.quick_chat_list import QuickChatList
from quick_frontend.quick_contact_list import QuickContactList
from quick_frontend.quick_contact_management import QuickContactManagement
import urwid
from profile_manager import ProfileManager
from contact_list import ContactList
from chat import Chat
from gateways import GatewaysManager
from urwid_satext import sat_widgets
import logging
from logging import debug, info, error
import sys, os
from tools.jid import JID
from xmlui import XMLUI
from progress import Progress
### logging configuration FIXME: put this elsewhere ###
logging.basicConfig(level=logging.CRITICAL, #TODO: configure it to put messages in a log file
format='%(message)s')
###
const_APP_NAME = "Primitivus"
const_PALETTE = [('title', 'black', 'light gray', 'standout,underline'),
('title_focus', 'white,bold', 'light gray', 'standout,underline'),
('selected', 'default', 'dark red'),
('selected_focus', 'default,bold', 'dark red'),
('default', 'default', 'default'),
('default_focus', 'default,bold', 'default'),
('alert', 'default,underline', 'default'),
('alert_focus', 'default,bold,underline', 'default'),
('date', 'light gray', 'default'),
('my_nick', 'dark red,bold', 'default'),
('other_nick', 'dark cyan,bold', 'default'),
('menubar', 'light gray,bold', 'dark red'),
('menubar_focus', 'light gray,bold', 'dark green'),
('selected_menu', 'light gray,bold', 'dark green'),
('menuitem', 'light gray,bold', 'dark red'),
('menuitem_focus', 'light gray,bold', 'dark green'),
('notifs', 'black,bold', 'yellow'),
('notifs_focus', 'dark red', 'yellow'),
('card_neutral', 'dark gray', 'white', 'standout,underline'),
('card_neutral_selected', 'dark gray', 'dark green', 'standout,underline'),
('card_special', 'brown', 'white', 'standout,underline'),
('card_special_selected', 'brown', 'dark green', 'standout,underline'),
('card_red', 'dark red', 'white', 'standout,underline'),
('card_red_selected', 'dark red', 'dark green', 'standout,underline'),
('card_black', 'black', 'white', 'standout,underline'),
('card_black_selected', 'black', 'dark green', 'standout,underline'),
('directory', 'dark cyan, bold', 'default'),
('directory_focus', 'dark cyan, bold', 'dark green'),
('separator', 'brown', 'default'),
('warning', 'light red', 'default'),
('progress_normal', 'default', 'black'),
('progress_complete', 'default', 'light red'),
]
class ChatList(QuickChatList):
"""This class manage the list of chat windows"""
def __init__(self, host):
QuickChatList.__init__(self, host)
def createChat(self, target):
return Chat(target, self.host)
class PrimitivusApp(QuickApp):
def __init__(self):
self.CM = QuickContactManagement() #FIXME: not the best place
QuickApp.__init__(self)
## main loop setup ##
self.main_widget = ProfileManager(self)
self.loop = urwid.MainLoop(self.main_widget, const_PALETTE, event_loop=urwid.GLibEventLoop(), input_filter=self.inputFilter, unhandled_input=self.keyHandler)
##misc setup##
self.chat_wins=ChatList(self)
self.notBar = sat_widgets.NotificationBar()
urwid.connect_signal(self.notBar,'change',self.onNotification)
self.progress_wid = Progress(self)
urwid.connect_signal(self.notBar.progress,'click',lambda x:self.addWindow(self.progress_wid))
self.__saved_overlay = None
def debug(self):
"""convenient method to reset screen and launch p(u)db"""
try:
import pudb
pudb.set_trace()
except:
import os,pdb
os.system('reset')
print 'Entered debug mode'
pdb.set_trace()
def writeLog(self, log, file_name='/tmp/primitivus_log'):
"""method to write log in a temporary file, useful for debugging"""
with open(file_name, 'a') as f:
f.write(log+"\n")
def redraw(self):
"""redraw the screen"""
self.loop.draw_screen()
def start(self):
self.i = 0
self.loop.set_alarm_in(0,lambda a,b: self.postInit())
self.loop.run()
def inputFilter(self, input, raw):
if self.__saved_overlay and input != ['ctrl s']:
return
for i in input:
if isinstance(i,tuple):
if i[0] == 'mouse press':
if i[1] == 4: #Mouse wheel up
input[input.index(i)] = 'up'
if i[1] == 5: #Mouse wheel down
input[input.index(i)] = 'down'
return input
def keyHandler(self, input):
if input == 'meta m':
"""User want to (un)hide the menu roller"""
try:
if self.main_widget.header == None:
self.main_widget.header = self.menu_roller
else:
self.main_widget.header = None
except AttributeError:
pass
elif input == 'ctrl n':
"""User wants to see next notification"""
self.notBar.showNext()
elif input == 'ctrl s':
"""User wants to (un)hide overlay window"""
if isinstance(self.loop.widget,urwid.Overlay):
self.__saved_overlay = self.loop.widget
self.loop.widget = self.main_widget
else:
if self.__saved_overlay:
self.loop.widget = self.__saved_overlay
self.__saved_overlay = None
elif input == 'ctrl d' and 'D' in self.bridge.getVersion(): #Debug only for dev versions
self.debug()
elif input == 'f2': #user wants to (un)hide the contact_list
try:
center_widgets = self.center_part.widget_list
if self.contactList in center_widgets:
self.center_part.set_focus(0) #necessary as the focus change to the next object, we can go out of range if we are on the last object of self.center_part
center_widgets.remove(self.contactList)
del self.center_part.column_types[0]
else:
center_widgets.insert(0, self.contactList)
self.center_part.column_types.insert(0, ('weight', 2))
except AttributeError:
#The main widget is not built (probably in Profile Manager)
pass
elif input == 'window resize':
width,height = self.loop.screen_size
if height<=5 and width<=35:
if not 'save_main_widget' in dir(self):
self.save_main_widget = self.loop.widget
self.loop.widget = urwid.Filler(urwid.Text(_("Pleeeeasse, I can't even breathe !")))
else:
if 'save_main_widget' in dir(self):
self.loop.widget = self.save_main_widget
del self.save_main_widget
try:
return self.menu_roller.checkShortcuts(input)
except AttributeError:
return input
def __buildMenuRoller(self):
menu = sat_widgets.Menu(self.loop)
general = _("General")
menu.addMenu(general, _("Connect"), self.onConnectRequest)
menu.addMenu(general, _("Disconnect"), self.onDisconnectRequest)
menu.addMenu(general, _("Parameters"), self.onParam)
menu.addMenu(general, _("About"), self.onAboutRequest)
menu.addMenu(general, _("Exit"), self.onExitRequest, 'ctrl x')
contact = _("Contact")
menu.addMenu(contact, _("Add contact"), self.onAddContactRequest)
menu.addMenu(contact, _("Remove contact"), self.onRemoveContactRequest)
communication = _("Communication")
menu.addMenu(communication, _("Join room"), self.onJoinRoomRequest, 'meta j')
menu.addMenu(communication, _("Find Gateways"), self.onFindGatewaysRequest, 'meta g')
#additionals menus
#FIXME: do this in a more generic way (in quickapp)
add_menus = self.bridge.getMenus()
def add_menu_cb(menu):
category, item = menu
id = self.bridge.callMenu(category, item, "NORMAL", self.profile)
self.current_action_ids.add(id)
for new_menu in add_menus:
category,item,type = new_menu
assert(type=="NORMAL") #TODO: manage other types
menu.addMenu(unicode(category), unicode(item), add_menu_cb)
menu_roller = sat_widgets.MenuRoller([(_('Main menu'),menu)])
return menu_roller
def __buildMainWidget(self):
self.contactList = ContactList(self, self.CM, on_click = self.contactSelected, on_change=lambda w: self.redraw())
#self.center_part = urwid.Columns([('weight',2,self.contactList),('weight',8,Chat('',self))])
self.center_part = urwid.Columns([('weight',2,self.contactList), ('weight',8,urwid.Filler(urwid.Text('')))])
self.editBar = sat_widgets.AdvancedEdit('> ')
self.editBar.setCompletionMethod(self._nick_completion)
urwid.connect_signal(self.editBar,'click',self.onTextEntered)
self.menu_roller = self.__buildMenuRoller()
self.main_widget = sat_widgets.FocusFrame(self.center_part, header=self.menu_roller, footer=self.editBar, focus_part='footer')
return self.main_widget
def _nick_completion(self, text, completion_data):
"""Completion method which complete pseudo in group chat
for params, see AdvancedEdit"""
contact = self.contactList.get_contact() ###Based on the fact that there is currently only one contact selectableat once
if contact:
chat = self.chat_wins[contact]
if chat.type != "group":
return text
space = text.rfind(" ")
start = text[space+1:]
nicks = list(chat.occupants)
nicks.sort()
try:
start_idx=nicks.index(completion_data['last_nick'])+1
if start_idx == len(nicks):
start_idx = 0
except (KeyError,ValueError):
start_idx = 0
for idx in range(start_idx,len(nicks)) + range(0,start_idx):
if nicks[idx].lower().startswith(start.lower()):
completion_data['last_nick'] = nicks[idx]
return text[:space+1] + nicks[idx] + (': ' if space < 0 else '')
return text
def plug_profile(self, profile_key='@DEFAULT@'):
self.loop.widget = self.__buildMainWidget()
QuickApp.plug_profile(self, profile_key)
def removePopUp(self, widget=None):
"Remove current pop-up, and if there is other in queue, show it"
self.loop.widget = self.main_widget
next_popup = self.notBar.getNextPopup()
if next_popup:
#we still have popup to show, we display it
self.showPopUp(next_popup)
def showPopUp(self, pop_up_widget, perc_width=40, perc_height=40):
"Show a pop-up window if possible, else put it in queue"
if not isinstance(self.loop.widget,urwid.Overlay):
display_widget = urwid.Overlay(pop_up_widget, self.main_widget, 'center', ('relative', perc_width), 'middle', ('relative', perc_height))
self.loop.widget = display_widget
self.redraw()
else:
self.notBar.addPopUp(pop_up_widget)
def notify(self, message):
""""Notify message to user via notification bar"""
self.notBar.addMessage(message)
def addWindow(self, widget):
"""Display a window if possible,
else add it in the notification bar queue
@param widget: BoxWidget"""
assert(len(self.center_part.widget_list)<=2)
wid_idx = len(self.center_part.widget_list)-1
self.center_part.widget_list[wid_idx] = widget
self.menu_roller.removeMenu(_('Chat menu'))
self.contactList.unselectAll()
self.redraw()
def removeWindow(self):
"""Remove window showed on the right column"""
#TODO: to a better Window management than this crappy hack
assert(len(self.center_part.widget_list)<=2)
wid_idx = len(self.center_part.widget_list)-1
self.center_part.widget_list[wid_idx] = urwid.Filler(urwid.Text(''))
self.center_part.set_focus(0)
self.redraw()
def addProgress (self, id, message):
"""Follow a SàT progress bar
@param id: SàT id of the progression
@param message: message to show to identify the progression"""
self.progress_wid.addProgress(id, message)
def setProgress(self, percentage):
"""Set the progression shown in notification bar"""
self.notBar.setProgress(percentage)
def contactSelected(self, contact_list):
contact = contact_list.get_contact()
if contact:
assert(len(self.center_part.widget_list)==2)
self.center_part.widget_list[1] = self.chat_wins[contact]
self.menu_roller.addMenu(_('Chat menu'), self.chat_wins[contact].getMenu())
def onTextEntered(self, editBar):
"""Called when text is entered in the main edit bar"""
contact = self.contactList.get_contact() ###Based on the fact that there is currently only one contact selectableat once
if contact:
chat = self.chat_wins[contact]
self.bridge.sendMessage(contact,
editBar.get_edit_text(),
type = "groupchat" if chat.type == 'group' else "chat",
profile_key=self.profile)
editBar.set_edit_text('')
def newMessage(self, from_jid, msg, type, to_jid, profile):
if not self.check_profile(profile):
return
QuickApp.newMessage(self, from_jid, msg, type, to_jid, profile)
sender = JID(from_jid)
if JID(self.contactList.selected).short != sender.short:
self.contactList.putAlert(sender)
def _dialogOkCb(self, widget, data):
self.removePopUp()
answer_cb = data[0]
answer_data = [data[1]] if data[1] else []
answer_cb(True, *answer_data)
def _dialogCancelCb(self, widget, data):
self.removePopUp()
answer_cb = data[0]
answer_data = [data[1]] if data[1] else []
answer_cb(False, *answer_data)
def showDialog(self, message, title="", type="info", answer_cb = None, answer_data = None):
if type == 'info':
popup = sat_widgets.Alert(unicode(title), unicode(message), ok_cb=answer_cb or self.removePopUp) #FIXME: remove unicode here when DBus Bridge will no return dbus.String anymore
flags = wx.OK | wx.ICON_INFORMATION
elif type == 'error':
popup = sat_widgets.Alert(unicode(title), unicode(message), ok_cb=answer_cb or self.removePopUp) #FIXME: remove unicode here when DBus Bridge will no return dbus.String anymore
elif type == 'yes/no':
popup = sat_widgets.ConfirmDialog(unicode(message),
yes_cb=self._dialogOkCb, yes_value = (answer_cb, answer_data),
no_cb=self._dialogCancelCb, no_value = (answer_cb, answer_data))
else:
popup = sat_widgets.Alert(unicode(title), unicode(message), ok_cb=answer_cb or self.removePopUp) #FIXME: remove unicode here when DBus Bridge will no return dbus.String anymore
error(_('unmanaged dialog type: %s'), type)
self.showPopUp(popup)
def onNotification(self, notBar):
"""Called when a new notification has been received"""
if not isinstance(self.main_widget, sat_widgets.FocusFrame):
#if we are not in the main configuration, we ignore the notifications bar
return
if isinstance(self.main_widget.footer,sat_widgets.AdvancedEdit):
if not self.notBar.canHide():
#the notification bar is not visible and has usefull informations, we show it
pile = urwid.Pile([self.notBar, self.editBar])
self.main_widget.footer = pile
else:
if not isinstance(self.main_widget.footer, urwid.Pile):
error(_("INTERNAL ERROR: Unexpected class for main widget's footer"))
assert(False)
if self.notBar.canHide():
#No notification left, we can hide the bar
self.main_widget.footer = self.editBar
def actionResult(self, type, id, data):
if not id in self.current_action_ids:
debug (_('unknown id, ignoring'))
return
if type == "SUPPRESS":
self.current_action_ids.remove(id)
elif type == "XMLUI":
self.current_action_ids.remove(id)
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
ui = XMLUI(self, title=title, xml_data = data['xml'], misc = misc)
if data['type'] == 'registration':
ui.show('popup')
else:
ui.show('window')
elif type == "ERROR":
self.current_action_ids.remove(id)
self.showPopUp(sat_widgets.Alert(_("Error"), unicode(data["message"]), ok_cb=self.removePopUp)) #FIXME: remove unicode here when DBus Bridge will no return dbus.String anymore
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:
error (_("FIXME FIXME FIXME: type [%s] not implemented") % type)
raise NotImplementedError
##DIALOGS CALLBACKS##
def onJoinRoom(self, button, edit):
self.removePopUp()
room_jid = JID(edit.get_edit_text())
if room_jid.is_valid():
self.bridge.joinMUC(room_jid.domain, room_jid.node, self.profiles[self.profile]['whoami'].node, self.profile)
else:
message = _("'%s' is an invalid JID !") % room_jid
error (message)
self.showPopUp(sat_widgets.Alert(_("Error"), message, ok_cb=self.removePopUp))
def onAddContact(self, button, edit):
self.removePopUp()
jid=JID(edit.get_edit_text())
if jid.is_valid():
self.bridge.addContact(jid.short, profile_key=self.profile)
else:
message = _("'%s' is an invalid JID !") % jid
error (message)
self.showPopUp(sat_widgets.Alert(_("Error"), message, ok_cb=self.removePopUp))
def onRemoveContact(self, button):
self.removePopUp()
info(_("Unsubscribing %s presence"),self.contactList.get_contact())
self.bridge.delContact(self.contactList.get_contact(), profile_key=self.profile)
#MENU EVENTS#
def onConnectRequest(self, menu):
self.bridge.connect(self.profile)
def onDisconnectRequest(self, menu):
self.bridge.disconnect(self.profile)
def onParam(self, menu):
params = XMLUI(self,xml_data=self.bridge.getParamsUI(self.profile))
self.addWindow(params)
def onExitRequest(self, menu):
QuickApp.onExit(self)
raise urwid.ExitMainLoop()
def onJoinRoomRequest(self, menu):
"""User wants to join a MUC room"""
pop_up_widget = sat_widgets.InputDialog(_("Entering a MUC room"), _("Please enter MUC's JID"), default_txt = 'room@muc_service.server.tld', cancel_cb=self.removePopUp, ok_cb=self.onJoinRoom)
self.showPopUp(pop_up_widget)
def onFindGatewaysRequest(self, e):
debug(_("Find Gateways request"))
id = self.bridge.findGateways(self.profiles[self.profile]['whoami'].domain, self.profile)
self.current_action_ids.add(id)
self.current_action_ids_cb[id] = self.onGatewaysFound
def onAddContactRequest(self, menu):
pop_up_widget = sat_widgets.InputDialog(_("Adding a contact"), _("Please enter new contact JID"), default_txt = 'name@server.tld', cancel_cb=self.removePopUp, ok_cb=self.onAddContact)
self.showPopUp(pop_up_widget)
def onRemoveContactRequest(self, menu):
contact = self.contactList.get_contact()
if not contact:
self.showPopUp(sat_widgets.Alert(_("Error"), _("You have not selected any contact to delete !"), ok_cb=self.removePopUp))
else:
pop_up_widget = sat_widgets.ConfirmDialog(_("Are you sure you want to delete the contact [%s] ?" % contact), yes_cb=self.onRemoveContact, no_cb=self.removePopUp)
self.showPopUp(pop_up_widget)
def onAboutRequest(self, menu):
self.showPopUp(sat_widgets.Alert(_("About"), const_APP_NAME + " v" + self.bridge.getVersion(), ok_cb=self.removePopUp))
#MISC CALLBACKS#
def onGatewaysFound(self, data):
"""Called when SàT has found the server gateways"""
target = data['__private__']['target']
del data['__private__']
gatewayManager = GatewaysManager(self, data, server=target)
self.addWindow(gatewayManager)
sat = PrimitivusApp()
sat.start()

View file

@ -0,0 +1,124 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Primitivus: a SAT frontend
Copyright (C) 2009, 2010 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import urwid
from urwid_satext.sat_widgets import Password,List,InputDialog,ConfirmDialog,Alert,FocusFrame
from tools.jid import JID
class ProfileManager(urwid.WidgetWrap):
def __init__(self, host):
self.host = host
#profiles list
profiles = self.host.bridge.getProfilesList()
profiles.sort()
#login & password box must be created before list because of onProfileChange
self.login_wid = urwid.Edit(_('Login:'),align='center')
self.pass_wid = Password(_('Password:'),align='center')
self.list_profile = List(profiles, style=['single'], align='center', on_click=self.onProfileChange)
#new & delete buttons
buttons = [urwid.Button(_("New"), self.onNewProfile),
urwid.Button(_("Delete"), self.onDeleteProfile)]
buttons_flow = urwid.GridFlow(buttons, max([len(button.get_label()) for button in buttons])+4, 1, 1, 'center')
#second part: login information:
divider = urwid.Divider('-')
#connect button
connect_button = urwid.Button(_("Connect"), self.onConnectProfile)
#we now build the widget
body_content = urwid.SimpleListWalker([buttons_flow,self.list_profile,divider,self.login_wid, self.pass_wid, connect_button])
frame_body = urwid.ListBox(body_content)
frame = urwid.Frame(frame_body,urwid.AttrMap(urwid.Text(_("Profile Manager"),align='center'),'title'))
self.main_widget = urwid.LineBox(frame)
urwid.WidgetWrap.__init__(self, self.main_widget)
def __refillProfiles(self):
"""Update the list of profiles"""
profiles = self.host.bridge.getProfilesList()
profiles.sort()
self.list_profile.changeValues(profiles)
def cancelDialog(self, button):
self.host.removePopUp()
def newProfile(self, button, edit):
"""Create the profile"""
name = edit.get_edit_text()
self.host.bridge.createProfile(name)
self.__refillProfiles()
#We select the profile created in the list
self.list_profile.selectValue(name)
self.host.removePopUp()
def deleteProfile(self, button):
profile_name = self.list_profile.getSelectedValue()
if profile_name:
self.host.bridge.deleteProfile(profile_name)
self.__refillProfiles()
self.host.removePopUp()
def onNewProfile(self, e):
pop_up_widget = InputDialog(_("New profile"), _("Please enter a new profile name"), cancel_cb=self.cancelDialog, ok_cb=self.newProfile)
self.host.showPopUp(pop_up_widget)
def onDeleteProfile(self, e):
pop_up_widget = ConfirmDialog(_("Are you sure you want to delete the profile %s ?") % self.list_profile.getSelectedValue(), no_cb=self.cancelDialog, yes_cb=self.deleteProfile)
self.host.showPopUp(pop_up_widget)
def onProfileChange(self, list_wid):
profile_name = list_wid.getSelectedValue()
if profile_name:
jabberID = self.host.bridge.getParamA("JabberID", "Connection", profile_key=profile_name)
password = self.host.bridge.getParamA("Password", "Connection", profile_key=profile_name)
self.login_wid.set_edit_text(jabberID)
self.pass_wid.set_edit_text(password)
def onConnectProfile(self, button):
profile_name = self.list_profile.getSelectedValue()
if not profile_name:
pop_up_widget = Alert(_('No profile selected'), _('You need to create and select a profile before connecting'), ok_cb=self.cancelDialog)
self.host.showPopUp(pop_up_widget)
elif profile_name[0] == '@':
pop_up_widget = Alert(_('Bad profile name'), _("A profile name can't start with a @"), ok_cb=self.cancelDialog)
self.host.showPopUp(pop_up_widget)
else:
profile = self.host.bridge.getProfileName(profile_name)
assert(profile)
#TODO: move this to quick_app
old_jid = self.host.bridge.getParamA("JabberID", "Connection", profile_key=profile)
old_pass = self.host.bridge.getParamA("Password", "Connection", profile_key=profile)
new_jid = self.login_wid.get_edit_text()
new_pass = self.pass_wid.get_edit_text()
if old_jid != new_jid:
self.host.bridge.setParam("JabberID", new_jid, "Connection", profile)
self.host.bridge.setParam("Server", JID(new_jid).domain, "Connection", profile)
if old_pass != new_pass:
self.host.bridge.setParam("Password", new_pass, "Connection", profile)
self.host.plug_profile(profile)

View file

@ -0,0 +1,96 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Primitivus: a SAT frontend
Copyright (C) 2009, 2010 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import urwid
from urwid_satext import sat_widgets
from tools.jid import JID
class Progress(urwid.WidgetWrap):
def __init__(self, host):
self.host = host
self.progress_list = urwid.SimpleListWalker([])
self.progress_dict = {}
listbox = urwid.ListBox(self.progress_list)
buttons = []
buttons.append(sat_widgets.CustomButton(_('Clear progress list'), self.__onClear))
max_len = max([button.getSize() for button in buttons])
buttons_wid = urwid.GridFlow(buttons,max_len,1,0,'center')
main_wid = sat_widgets.FocusFrame(listbox, footer=buttons_wid)
urwid.WidgetWrap.__init__(self, main_wid)
def addProgress(self, id, message):
mess_wid = urwid.Text(message)
progr_wid = urwid.ProgressBar('progress_normal', 'progress_complete')
column = urwid.Columns([mess_wid, progr_wid])
self.progress_dict[id] = {'full':column,'progress':progr_wid,'state':'init'}
self.progress_list.append(column)
self.progressCB(self.host.loop, (id, message))
def progressCB(self, loop, data):
id, message = data
data = self.host.bridge.getProgress(id)
pbar = self.progress_dict[id]['progress']
if data:
if self.progress_dict[id]['state'] == 'init':
#first answer, we must construct the bar
self.progress_dict[id]['state'] = 'progress'
pbar.done = float(data['size'])
pbar.set_completion(float(data['position']))
self.updateNotBar()
else:
if self.progress_dict[id]['state'] == 'progress':
self.progress_dict[id]['state'] = 'done'
pbar.set_completion(pbar.done)
self.updateNotBar()
return
loop.set_alarm_in(1,self.progressCB, (id, message))
def __removeBar(self, id):
wid = self.progress_dict[id]['full']
self.progress_list.remove(wid)
del(self.progress_dict[id])
def __onClear(self, button):
to_remove = []
for id in self.progress_dict:
if self.progress_dict[id]['state'] == 'done':
to_remove.append(id)
for id in to_remove:
self.__removeBar(id)
self.updateNotBar()
def updateNotBar(self):
if not self.progress_dict:
self.host.setProgress(None)
return
progress = 0
nb_bars = 0
for id in self.progress_dict:
pbar = self.progress_dict[id]['progress']
progress += pbar.current/pbar.done*100
nb_bars+=1
av_progress = progress/float(nb_bars)
self.host.setProgress(av_progress)

View file

@ -0,0 +1,261 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Primitivus: a SAT frontend
Copyright (C) 2009, 2010 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import urwid
from logging import debug, info, warning, error
from urwid_satext import sat_widgets
from xml.dom import minidom
class Pairs(urwid.WidgetWrap):
def __init__(self, weight_0='1', weight_1='1'):
self.idx = 0
self.weight_0 = weight_0
self.weight_1 = weight_1
columns = urwid.Columns([urwid.Text(''), urwid.Text('')])
#XXX: empty Text hack needed because Pile doesn't support empty list
urwid.WidgetWrap.__init__(self,columns)
def append(self, widget):
pile = self._w.widget_list[self.idx]
if isinstance(pile, urwid.Text):
self._w.widget_list[self.idx] = urwid.Pile([widget])
if self.idx == 1:
self._w.set_focus(1)
else:
pile.widget_list.append(widget)
pile.item_types.append(('weight',getattr(self,'weight_'+str(self.idx))))
self.idx = (self.idx + 1) % 2
class InvalidXMLUI(Exception):
pass
class XMLUI(urwid.WidgetWrap):
def __init__(self, host, xml_data, title = None, options = [], misc={}):
self.host = host
self.title = title
self.options = options
self.misc = misc
self.__dest = "window"
self.ctrl_list = {} # usefull to access ctrl
widget = self.constructUI(xml_data)
urwid.WidgetWrap.__init__(self,widget)
def __parseElems(self, node, parent):
"""Parse elements inside a <layout> tags, and add them to the parent"""
for elem in node.childNodes:
if elem.nodeName != "elem":
message=_("Unmanaged tag")
error(message)
raise Exception(message)
id = elem.getAttribute("id")
name = elem.getAttribute("name")
type = elem.getAttribute("type")
value = elem.getAttribute("value") if elem.hasAttribute('value') else u''
if type=="empty":
ctrl = urwid.Text('')
elif type=="text":
try:
value = elem.childNodes[0].wholeText
except IndexError:
warning (_("text node has no child !"))
ctrl = urwid.Text(value)
elif type=="label":
ctrl = urwid.Text(value+": ")
elif type=="string":
ctrl = sat_widgets.AdvancedEdit(edit_text = value)
self.ctrl_list[name] = ({'type':type, 'control':ctrl})
elif type=="password":
ctrl = sat_widgets.Password(edit_text = value)
self.ctrl_list[name] = ({'type':type, 'control':ctrl})
elif type=="textbox":
ctrl = sat_widgets.AdvancedEdit(edit_text = value, multiline=True)
self.ctrl_list[name] = ({'type':type, 'control':ctrl})
elif type=="bool":
ctrl = urwid.CheckBox('', state = value=="true")
self.ctrl_list[name] = ({'type':type, 'control':ctrl})
elif type=="list":
style=[] if elem.getAttribute("multi")=='yes' else ['single']
ctrl = sat_widgets.List(options=[option.getAttribute("value") for option in elem.getElementsByTagName("option")], style=style)
self.ctrl_list[name] = ({'type':type, 'control':ctrl})
elif type=="button":
callback_id = elem.getAttribute("callback_id")
ctrl = sat_widgets.CustomButton(value, on_press=self.onButtonPress)
ctrl.param_id = (callback_id,[field.getAttribute('name') for field in elem.getElementsByTagName("field_back")])
else:
error(_("FIXME FIXME FIXME: type [%s] is not implemented") % type) #FIXME !
raise NotImplementedError
if self.type == 'param':
if isinstance(ctrl,urwid.Edit) or isinstance(ctrl,urwid.CheckBox):
urwid.connect_signal(ctrl,'change',self.onParamChange)
ctrl._param_category = self._current_category
ctrl._param_name = name
parent.append(ctrl)
def __parseChilds(self, current, elem, wanted = ['layout'], data = None):
"""Recursively parse childNodes of an elemen
@param current: widget container with 'append' method
@param elem: element from which childs will be parsed
@param wanted: list of tag names that can be present in the childs to be SàT XMLUI compliant"""
for node in elem.childNodes:
if wanted and not node.nodeName in wanted:
raise InvalidXMLUI
if node.nodeName == "layout":
type = node.getAttribute('type')
if type == "tabs":
tab_cont = sat_widgets.TabsContainer()
self.__parseChilds(current, node, ['category'], tab_cont)
current.append(tab_cont)
elif type == "vertical":
self.__parseElems(node, current)
elif type == "pairs":
pairs = Pairs()
self.__parseElems(node, pairs)
current.append(pairs)
else:
warning(_("Unknown layout, using default one"))
self.__parseElems(node, current)
elif node.nodeName == "category":
name = node.getAttribute('name')
label = node.getAttribute('label')
if not name or not isinstance(data,sat_widgets.TabsContainer):
raise InvalidXMLUI
if self.type == 'param':
self._current_category = name #XXX: awful hack because params need category and we don't keep parent
tab_cont = data
listbox = tab_cont.addTab(label or name)
self.__parseChilds(listbox.body, node, ['layout'])
else:
message=_("Unknown tag")
error(message)
raise NotImplementedError
def constructUI(self, xml_data):
ret_wid = urwid.ListBox(urwid.SimpleListWalker([]))
cat_dom = minidom.parseString(xml_data.encode('utf-8'))
top=cat_dom.documentElement
self.type = top.getAttribute("type")
self.title = top.getAttribute("title") or self.title
if top.nodeName != "sat_xmlui" or not self.type in ['form', 'param', 'window']:
raise InvalidXMLUI
if self.type == 'param':
self.param_changed = set()
self.__parseChilds(ret_wid.body, cat_dom.documentElement)
assert ret_wid.body
if isinstance(ret_wid.body[0],sat_widgets.TabsContainer):
ret_wid = ret_wid.body[0] #xxx: awfull hack cause TabsContainer is a BoxWidget, can't be inside a ListBox
if self.type == 'form':
buttons = []
buttons.append(urwid.Button(_('Submit'),self.onFormSubmitted))
if not 'NO_CANCEL' in self.options:
buttons.append(urwid.Button(_('Cancel'),self.onFormCancelled))
max_len = max([len(button.get_label()) for button in buttons])
grid_wid = urwid.GridFlow(buttons,max_len+4,1,0,'center')
ret_wid.body.append(grid_wid)
elif self.type == 'param':
assert(isinstance(ret_wid,sat_widgets.TabsContainer))
buttons = []
buttons.append(sat_widgets.CustomButton(_('Save'),self.onSaveParams))
buttons.append(sat_widgets.CustomButton(_('Cancel'),lambda x:self.host.removeWindow()))
max_len = max([button.getSize() for button in buttons])
grid_wid = urwid.GridFlow(buttons,max_len,1,0,'center')
ret_wid.addFooter(grid_wid)
return ret_wid
def show(self,show_type = 'popup'):
"""Show the constructed UI
@param show_type: how to show the UI:
- popup
- window"""
self.__dest = "popup"
decorated = sat_widgets.LabelLine(self, sat_widgets.SurroundedText(self.title or ''))
if show_type == 'popup':
self.host.showPopUp(decorated)
elif show_type == 'window':
self.host.addWindow(decorated)
else:
error(_('INTERNAL ERROR: Unmanaged show_type (%s)') % show_type)
assert(False)
self.host.redraw()
##EVENTS##
def onButtonPress(self, button):
callback_id, fields = button.param_id
data = {"callback_id":callback_id}
for field in fields:
ctrl = self.ctrl_list[field]
if isinstance(ctrl['control'],sat_widgets.List):
data[field] = '\t'.join(ctrl['control'].getSelectedValues())
else:
data[field] = ctrl['control'].getValue()
id = self.host.bridge.launchAction("button", data, profile_key = self.host.profile)
self.host.current_action_ids.add(id)
def onParamChange(self, widget, extra=None):
"""Called when type is param and a widget to save is modified"""
assert(self.type == "param")
self.param_changed.add(widget)
def onFormSubmitted(self, button):
data = []
for ctrl_name in self.ctrl_list:
ctrl = self.ctrl_list[ctrl_name]
if isinstance(ctrl['control'], sat_widgets.List):
data.append((ctrl_name, ctrl['control'].getSelectedValue()))
elif isinstance(ctrl['control'], urwid.CheckBox):
data.append((ctrl_name, "true" if ctrl['control'].get_state() else "false"))
else:
data.append((ctrl_name, ctrl['control'].get_edit_text()))
if self.misc.has_key('action_back'): #FIXME FIXME FIXME: WTF ! Must be cleaned
raise NotImplementedError
self.host.debug()
elif self.misc.has_key('callback'):
self.misc['callback'](data)
else:
warning (_("The form data is not sent back, the type is not managed properly"))
self.host.removePopUp()
def onFormCancelled(self, button):
if self.__dest == 'window':
self.host.removeWindow()
else:
self.host.removePopUp()
def onSaveParams(self, button):
for ctrl in self.param_changed:
if isinstance(ctrl, urwid.CheckBox):
value = "true" if ctrl.get_state() else "false"
else:
value = ctrl.get_edit_text()
self.host.bridge.setParam(ctrl._param_name, value, ctrl._param_category, profile_key = self.host.profile)
self.host.removeWindow()