#!/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 . """ 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 import custom_widgets import pdb import logging from logging import debug, info, error import sys, os from tools.jid import JID from xmlui import XMLUI ### 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'), ('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'), ('selected_menu', 'light gray,bold', 'dark green'), ('menuitem', 'light gray,bold', 'dark red'), ('menuitem_focus', 'light gray,bold', 'dark green'), ('notifs', 'black,bold', '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'), ] 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 = custom_widgets.NotificationBar() urwid.connect_signal(self.notBar,'change',self.onNotification) self.__saved_overlay = None def debug(self): """convenient method to reset screen and launch pdb""" import os os.system('reset') print 'Entered debug mode' pdb.set_trace() def write_log(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': 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 = custom_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.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(category, item, add_menu_cb) menu_roller = custom_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 = custom_widgets.AdvancedEdit('> ') urwid.connect_signal(self.editBar,'click',self.onTextEntered) self.menu_roller = self.__buildMenuRoller() self.main_widget = custom_widgets.FocusFrame(self.center_part, header=self.menu_roller, footer=self.editBar, focus_part='footer') return self.main_widget 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 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 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 onNotification(self, notBar): """Called when a new notification has been received""" if not isinstance(self.main_widget, custom_widgets.FocusFrame): #if we are not in the main configuration, we ignore the notifications bar return if isinstance(self.main_widget.footer,custom_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(custom_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(custom_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(custom_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): raise urwid.ExitMainLoop() def onJoinRoomRequest(self, menu): """User wants to join a MUC room""" pop_up_widget = custom_widgets.InputDialog(_("Entering a MUC room"), _("Please enter MUC's JID"), default_txt = 'test@conference.necton2.int', 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 = custom_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(custom_widgets.Alert(_("Error"), _("You have not selected any contact to delete !"), ok_cb=self.removePopUp)) else: pop_up_widget = custom_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(custom_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()