Files reorganisation
This commit is contained in:
parent
c8b5f1db90
commit
e4452c4b03
8 changed files with 10 additions and 8 deletions
335
frontends/src/primitivus/card_game.py
Normal file
335
frontends/src/primitivus/card_game.py
Normal 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()
|
||||
279
frontends/src/primitivus/chat.py
Normal file
279
frontends/src/primitivus/chat.py
Normal 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)
|
||||
183
frontends/src/primitivus/contact_list.py
Normal file
183
frontends/src/primitivus/contact_list.py
Normal 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)
|
||||
|
||||
88
frontends/src/primitivus/gateways.py
Normal file
88
frontends/src/primitivus/gateways.py
Normal 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)
|
||||
509
frontends/src/primitivus/primitivus
Executable file
509
frontends/src/primitivus/primitivus
Executable 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()
|
||||
|
||||
124
frontends/src/primitivus/profile_manager.py
Normal file
124
frontends/src/primitivus/profile_manager.py
Normal 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)
|
||||
|
||||
96
frontends/src/primitivus/progress.py
Normal file
96
frontends/src/primitivus/progress.py
Normal 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)
|
||||
|
||||
261
frontends/src/primitivus/xmlui.py
Normal file
261
frontends/src/primitivus/xmlui.py
Normal 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()
|
||||
Loading…
Reference in a new issue