diff --git a/TODO.pep b/TODO.pep new file mode 100644 index 000000000..05c924dbf --- /dev/null +++ b/TODO.pep @@ -0,0 +1,15 @@ +• configure access model when changing it in the combobox +• PEP in status change + +Tune use cases: +• on disconnection of an account set Tune to None + +Tooltips use cases: +• Show PEP in contact tooltips +• Show PEP in GC tooltips +• Show PEP in account tooltips + +Mood/Activity use cases +• on connection of an account set them to None +• on disconnection of an account set them to None +• on explicit set publish them diff --git a/data/glade/account_context_menu.glade b/data/glade/account_context_menu.glade index 08bd16859..4316c5f0c 100644 --- a/data/glade/account_context_menu.glade +++ b/data/glade/account_context_menu.glade @@ -17,6 +17,20 @@ + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Personal Events + True + + + gtk-home + 1 + + + + True diff --git a/data/glade/change_activity_dialog.glade b/data/glade/change_activity_dialog.glade new file mode 100644 index 000000000..7665916fd --- /dev/null +++ b/data/glade/change_activity_dialog.glade @@ -0,0 +1,158 @@ + + + + + + + 6 + + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 270 + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + True + + + + + True + False + 6 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-cancel + True + GTK_RELIEF_NORMAL + True + -6 + + + + + + + True + True + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + -5 + + + + + + 0 + False + False + GTK_PACK_END + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + 6 + True + False + 6 + + + + True + False + True + + + 0 + False + False + + + + + + True + False + True + + + 0 + False + False + + + + + + True + True + True + True + 0 + + True + + False + + + 0 + False + False + + + + + + + + + 0 + True + True + + + + + + + diff --git a/data/glade/change_mood_dialog.glade b/data/glade/change_mood_dialog.glade new file mode 100644 index 000000000..47122c872 --- /dev/null +++ b/data/glade/change_mood_dialog.glade @@ -0,0 +1,145 @@ + + + + + + + 6 + + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 270 + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + True + + + + + True + False + 6 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-cancel + True + GTK_RELIEF_NORMAL + True + -6 + + + + + + + True + True + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + -5 + + + + + + 0 + False + False + GTK_PACK_END + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + 6 + True + False + 6 + + + + True + False + True + + + 0 + False + False + + + + + + True + True + True + True + 0 + + True + + False + + + 0 + False + False + + + + + + + + + 0 + True + True + + + + + + + diff --git a/data/glade/manage_pep_services_window.glade b/data/glade/manage_pep_services_window.glade new file mode 100644 index 000000000..a3e5e6155 --- /dev/null +++ b/data/glade/manage_pep_services_window.glade @@ -0,0 +1,135 @@ + + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 350 + 150 + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + True + False + + + + + True + False + 0 + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_NONE + GTK_CORNER_TOP_LEFT + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + False + False + True + False + False + False + + + + + 0 + True + True + + + + + + True + GTK_BUTTONBOX_DEFAULT_STYLE + 0 + + + + True + True + True + True + gtk-add + True + GTK_RELIEF_NORMAL + True + -5 + + + + + + True + True + True + True + gtk-delete + True + GTK_RELIEF_NORMAL + True + -5 + + + + + + True + True + True + gtk-cancel + True + GTK_RELIEF_NORMAL + True + -6 + + + + + + + True + True + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + -5 + + + + + + 0 + True + True + + + + + + + diff --git a/data/glade/preferences_window.glade b/data/glade/preferences_window.glade index 0bd3604fa..831599d4b 100644 --- a/data/glade/preferences_window.glade +++ b/data/glade/preferences_window.glade @@ -438,9 +438,6 @@ Single message - - False - @@ -449,7 +446,6 @@ Single message tab - False False @@ -880,7 +876,6 @@ Disabled 1 - False @@ -891,7 +886,6 @@ Disabled tab 1 - False False @@ -1229,7 +1223,6 @@ Show only in roster 2 - False @@ -1240,7 +1233,6 @@ Show only in roster tab 2 - False False @@ -1638,7 +1630,6 @@ Show only in roster 3 - False @@ -1649,7 +1640,6 @@ Show only in roster tab 3 - False False @@ -2048,7 +2038,6 @@ Custom 4 - False @@ -2059,7 +2048,193 @@ Custom tab 4 - False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 6 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Publish _Mood + True + 0 + True + + + + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Publish _Activity + True + 0 + True + + + + False + 1 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Publish _Tune + True + 0 + True + + + + False + 2 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Publish Personal Events</b> + True + + + label_item + + + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Subscribe to M_ood + True + 0 + True + + + + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Subscribe to A_ctivity + True + 0 + True + + + + False + 1 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Subscribe to T_une + True + 0 + True + + + + False + 2 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Subscribe to Personal Events</b> + True + + + label_item + + + + + False + 1 + + + + + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Personal Events + + + tab + 5 False diff --git a/data/glade/roster_window.glade b/data/glade/roster_window.glade index 625a713b2..dc482bb8a 100644 --- a/data/glade/roster_window.glade +++ b/data/glade/roster_window.glade @@ -193,6 +193,14 @@ + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Services + True + + diff --git a/po/fr.po b/po/fr.po index eb9988722..7149ff9ff 100644 --- a/po/fr.po +++ b/po/fr.po @@ -6537,6 +6537,406 @@ msgstr "Connecté" msgid "Disconnected" msgstr "Déconnecté" +#pep +msgid "afraid" +msgstr "appeuré" + +msgid "amazed" +msgstr "surpris, amusé" + +msgid "angry" +msgstr "en colère" + +msgid "annoyed" +msgstr "dérangé" + +msgid "anxious" +msgstr "anxieux" + +msgid "aroused" +msgstr "excité" + +msgid "ashamed" +msgstr "honteux/éhonté" + +msgid "bored" +msgstr "ennuyé" + +msgid "brave" +msgstr "courageux" + +msgid "calm" +msgstr "calme" + +msgid "cold" +msgstr "froid" + +msgid "confused" +msgstr "confus" + +msgid "contented" +msgstr "contenté" + +msgid "cranky" +msgstr "excentrique" + +msgid "curious" +msgstr "curieux" + +msgid "depressed" +msgstr "déprimé" + +msgid "disappointed" +msgstr "décu" + +msgid "disgusted" +msgstr "dégouté" + +msgid "distracted" +msgstr "distrait" + +msgid "embarrassed" +msgstr "embarssé" + +msgid "excited" +msgstr "excité" + +msgid "flirtatious" +msgstr "coquet" + +msgid "frustrated" +msgstr "frustré" + +msgid "grumpy" +msgstr "grognon" + +msgid "guilty" +msgstr "coupable" + +msgid "happy" +msgstr "joyeux" + +msgid "hot" +msgstr "chaud" + +msgid "humbled" +msgstr "humilié" + +msgid "humiliated" +msgstr "humilié" + +msgid "hungry" +msgstr "affamé" + +msgid "hurt" +msgstr "blessé" + +msgid "impressed" +msgstr "impressionné" + +msgid "in_awe" +msgstr "dans la crainte" + +msgid "in_love" +msgstr "amoureux" + +msgid "indignant" +msgstr "indigné" + +msgid "interested" +msgstr "interessé" + +msgid "intoxicated" +msgstr "intoxiqué" + +msgid "invincible" +msgstr "invincible" + +msgid "jealous" +msgstr "jaloux" + +msgid "lonely" +msgstr "seul/esseulé" + +msgid "mean" +msgstr "méchant" + +msgid "moody" +msgstr "déprimé" + +msgid "nervous" +msgstr "nerveux" + +msgid "neutral" +msgstr "neutre" + +msgid "offended" +msgstr "offensé" + +msgid "playful" +msgstr "joueur" + +msgid "proud" +msgstr "fier" + +msgid "relieved" +msgstr "soulagé" + +msgid "remorseful" +msgstr "plein de remord" + +msgid "restless" +msgstr "infatiguable" + +msgid "sad" +msgstr "triste" + +msgid "sarcastic" +msgstr "sarcastique" + +msgid "serious" +msgstr "serieux" + +msgid "shocked" +msgstr "choqué" + +msgid "shy" +msgstr "timide" + +msgid "sick" +msgstr "malade" + +msgid "sleepy" +msgstr "endormi" + +msgid "stressed" +msgstr "stressé" + +msgid "surprised" +msgstr "surpris" + +msgid "thirsty" +msgstr "assoiffé" + +msgid "worried" +msgstr "inquiet" + +msgid "_Personnal Events" +msgstr "Évènements _Personnels" + +msgid "Activity" +msgstr "Activité" + +msgid "doing_chores" +msgstr "fait des corvées" + +msgid "buying_groceries" +msgstr "achète des épiceries" + +msgid "cleaning" +msgstr "nettoie" + +msgid "cooking" +msgstr "cuisine" + +msgid "doing_maintenance" +msgstr "fait de la maintenance" + +msgid "doing_the_dishes" +msgstr "fait la vaiselle" + +msgid "doing_the_laundry" +msgstr "fait la blanchisserie" + +msgid "gardening" +msgstr "jardine" + +msgid "running_an_errand" +msgstr "fait une course" + +msgid "walking_the_dog" +msgstr "promène le chien" + +msgid "drinking" +msgstr "boit" + +msgid "having_a_beer" +msgstr "prend une bière" + +msgid "having_coffee" +msgstr "prend un café" + +msgid "having_tea" +msgstr "prend un thé" + +msgid "eating" +msgstr "mange" + +msgid "having_a_snack" +msgstr "prend un snack" + +msgid "having_breakfast" +msgstr "prend le petit-déjeuner" + +msgid "having_dinner" +msgstr "soupe" + +msgid "having_lunch" +msgstr "dîne" + +msgid "exercising" +msgstr "fait de l'exercice" + +msgid "cycling" +msgstr "fait du vélo" + +msgid "hiking" +msgstr "fait de la randonnée" + +msgid "jogging" +msgstr "fait un jogging" + +msgid "playing_sports" +msgstr "fait du sport" + +msgid "running" +msgstr "court" + +msgid "skiing" +msgstr "skie" + +msgid "swimming" +msgstr "nage" + +msgid "working_out" +msgstr "élabore" + +msgid "grooming" +msgstr "se toilette" + +msgid "at_the_spa" +msgstr "à la station thermale" + +msgid "brushing_teeth" +msgstr "se brosse les dents" + +msgid "getting_a_haircut" +msgstr "se fait couper les cheveux" + +msgid "shaving" +msgstr "se rase" + +msgid "taking_a_bath" +msgstr "prend un bain" + +msgid "taking_a_shower" +msgstr "prend une douche" + +msgid "having_appointment" +msgstr "à un rendez-vous" + +msgid "inactive" +msgstr "inactif" + +msgid "day_off" +msgstr "en congé" + +msgid "hanging_out" +msgstr "traîne" + +msgid "on_vacation" +msgstr "en vacances" + +msgid "scheduled_holiday" +msgstr "en vacances organisées" + +msgid "sleeping" +msgstr "dort" + +msgid "relaxing" +msgstr "se relaxe" + +msgid "gaming" +msgstr "joue" + +msgid "going_out" +msgstr "sort" + +msgid "partying" +msgstr "fait la fête" + +msgid "reading" +msgstr "lit" + +msgid "rehearsing" +msgstr "se prépare" + +msgid "shopping" +msgstr "fait les magasins" + +msgid "socializing" +msgstr "se socialise" + +msgid "sunbathing" +msgstr "prend un bain de soleil" + +msgid "watching_tv" +msgstr "regarde la TV" + +msgid "watching_a_movie" +msgstr "regarde un film" + +msgid "talking" +msgstr "discute" + +msgid "in_real_life" +msgstr "dans la vraie vie" + +msgid "on_the_phone" +msgstr "au téléphone" + +msgid "traveling" +msgstr "voyage" + +msgid "commuting" +msgstr "permute" + +msgid "driving" +msgstr "conduit" + +msgid "in_a_car" +msgstr "en voiture" + +msgid "on_a_bus" +msgstr "en bus" + +msgid "on_a_plane" +msgstr "en avion" + +msgid "on_a_train" +msgstr "en train" + +msgid "on_a_trip" +msgstr "en séjour" + +msgid "walking" +msgstr "marche" + +msgid "working" +msgstr "travaille" + +msgid "coding" +msgstr "programme" + +msgid "in_a_meeting" +msgstr "en réunion" + +msgid "studying" +msgstr "étudie" + +msgid "writing" +msgstr "écrit" + #~ msgid "2003-12-13T18:30:02Z" #~ msgstr "2003-12-13T18:30:02Z" #~ msgid "Romeo and Juliet" diff --git a/src/common/config.py b/src/common/config.py index 0e8282daf..1570059ad 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -250,7 +250,14 @@ class Config: 'use_latex': [opt_bool, False, _('If True, Gajim will convert string between $$ and $$ to an image using dvips and convert before insterting it in chat window.')], 'change_status_window_timeout': [opt_int, 15, _('Time of inactivity needed before the change status window closes down.')], 'max_conversation_lines': [opt_int, 500, _('Maximum number of lines that are printed in conversations. Oldest lines are cleared.')], + 'publish_mood': [opt_bool, False], + 'publish_activity': [opt_bool, False], + 'publish_tune': [opt_bool, False], + 'subscribe_mood': [opt_bool, True], + 'subscribe_activity': [opt_bool, True], + 'subscribe_tune': [opt_bool, True], 'attach_notifications_to_systray': [opt_bool, False, _('If True, notification windows from notification-daemon will be attached to systray icon.')], + 'use_pep': [opt_bool, False, 'temporary variable to enable pep support'], } __options_per_key = { diff --git a/src/common/connection.py b/src/common/connection.py index 34527e45a..91c89f220 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -186,6 +186,8 @@ class Connection(ConnectionHandlers): # We are doing disconnect at so many places, better use one function in all def disconnect(self, on_purpose=False): + #FIXME: set the Tune to None before disconnection per account + #gajim.interface.roster._music_track_changed(None, None) self.on_purpose = on_purpose self.connected = 0 self.time_to_reconnect = None diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index baea7e087..6423d4154 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -39,11 +39,17 @@ from common import GnuPG from common import helpers from common import gajim from common import atom +from common import pep from common import exceptions from common.commands import ConnectionCommands from common.pubsub import ConnectionPubSub from common.caps import ConnectionCaps +from common import dbus_support +if dbus_support.supported: + import dbus + from music_track_listener import MusicTrackListener + from common.stanza_session import EncryptedStanzaSession STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', @@ -54,6 +60,7 @@ VCARD_ARRIVED = 'vcard_arrived' AGENT_REMOVED = 'agent_removed' METACONTACTS_ARRIVED = 'metacontacts_arrived' PRIVACY_ARRIVED = 'privacy_arrived' +PEP_ACCESS_MODEL = 'pep_access_model' HAS_IDLE = True try: import idle @@ -750,6 +757,13 @@ class ConnectionDisco: q.addChild('feature', attrs = {'var': common.xmpp.NS_MUC}) q.addChild('feature', attrs = {'var': common.xmpp.NS_COMMANDS}) q.addChild('feature', attrs = {'var': common.xmpp.NS_DISCO_INFO}) + if gajim.config.get('use_pep'): + q.addChild('feature', attrs = {'var': common.xmpp.NS_ACTIVITY}) + q.addChild('feature', attrs = {'var': common.xmpp.NS_ACTIVITY + '+notify'}) + q.addChild('feature', attrs = {'var': common.xmpp.NS_TUNE}) + q.addChild('feature', attrs = {'var': common.xmpp.NS_TUNE + '+notify'}) + q.addChild('feature', attrs = {'var': common.xmpp.NS_MOOD}) + q.addChild('feature', attrs = {'var': common.xmpp.NS_MOOD + '+notify'}) q.addChild('feature', attrs = {'var': common.xmpp.NS_ESESSION_INIT}) if (node is None or extension == 'cstates') and gajim.config.get('outgoing_chat_state_notifactions') != 'disabled': @@ -821,6 +835,11 @@ class ConnectionDisco: if identity['category'] == 'pubsub' and identity['type'] == \ 'pep': self.pep_supported = True + listener = MusicTrackListener.get() + track = listener.get_playing_track() + if gajim.config.get('publish_tune'): + gajim.interface.roster._music_track_changed(listener, + track, self.name) break if features.__contains__(common.xmpp.NS_BYTESTREAM): gajim.proxy65_manager.resolve(jid, self.connection, self.name) @@ -1099,6 +1118,15 @@ class ConnectionVcard: self.get_privacy_list('block') # Ask metacontacts before roster self.get_metacontacts() + elif self.awaiting_answers[id][0] == PEP_ACCESS_MODEL: + conf = iq_obj.getTag('pubsub').getTag('configure') + node = conf.getAttr('node') + form_tag = conf.getTag('x', namespace=common.xmpp.NS_DATA) + form = common.dataforms.ExtendForm(node=form_tag) + for field in form.iter_fields(): + if field.var == 'pubsub#access_model': + self.dispatch('PEP_ACCESS_MODEL', (node, field.value)) + break del self.awaiting_answers[id] @@ -1770,8 +1798,22 @@ returns the session that we last sent a message to.''' ''' Called when we receive with pubsub event. ''' # TODO: Logging? (actually services where logging would be useful, should # TODO: allow to access archives remotely...) + jid = helpers.get_full_jid_from_iq(msg) event = msg.getTag('event') + # XEP-0107: User Mood + items = event.getTag('items', {'node': common.xmpp.NS_MOOD}) + if items: pep.user_mood(items, self.name, jid) + # XEP-0118: User Tune + items = event.getTag('items', {'node': common.xmpp.NS_TUNE}) + if items: pep.user_tune(items, self.name, jid) + # XEP-0080: User Geolocation + items = event.getTag('items', {'node': common.xmpp.NS_GEOLOC}) + if items: pep.user_geoloc(items, self.name, jid) + # XEP-0108: User Activity + items = event.getTag('items', {'node': common.xmpp.NS_ACTIVITY}) + if items: pep.user_activity(items, self.name, jid) + items = event.getTag('items') if items is None: return diff --git a/src/common/contacts.py b/src/common/contacts.py index 262edd1d8..ef02f43da 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -34,6 +34,10 @@ class Contact: self.groups = groups self.show = show self.status = status + # FIXME + self.mood = dict() + self.activity = dict() + self.tune = dict() self.sub = sub self.ask = ask self.resource = resource @@ -169,8 +173,8 @@ class Contacts: def copy_contact(self, contact): return self.create_contact(jid = contact.jid, name = contact.name, - groups = contact.groups, show = contact.show, status = contact.status, - sub = contact.sub, ask = contact.ask, resource = contact.resource, + groups = contact.groups, show = contact.show, status = + contact.status, sub = contact.sub, ask = contact.ask, resource = contact.resource, priority = contact.priority, keyID = contact.keyID, our_chatstate = contact.our_chatstate, chatstate = contact.chatstate, last_status_time = contact.last_status_time) diff --git a/src/common/gajim.py b/src/common/gajim.py index dcb294034..bfc7a555a 100644 --- a/src/common/gajim.py +++ b/src/common/gajim.py @@ -56,7 +56,7 @@ If you start gajim from svn: interface = None # The actual interface (the gtk one for the moment) config = config.Config() -version = config.get('version') +version = config.get('version') + 'dh' connections = {} # 'account name': 'account (connection.Connection) instance' verbose = False diff --git a/src/common/pep.py b/src/common/pep.py new file mode 100644 index 000000000..9259820f5 --- /dev/null +++ b/src/common/pep.py @@ -0,0 +1,129 @@ +from common import gajim, xmpp + +def user_mood(items, name, jid): + (user, resource) = gajim.get_room_and_nick_from_fjid(jid) + contact = gajim.contacts.get_contact(name, user, resource=resource) + if not contact: + return + for item in items.getTags('item'): + child = item.getTag('mood') + if child is not None: + if contact.mood.has_key('mood'): + del contact.mood['mood'] + if contact.mood.has_key('text'): + del contact.mood['text'] + for ch in child.getChildren(): + if ch.getName() != 'text': + contact.mood['mood'] = ch.getName() + else: + contact.mood['text'] = ch.getData() + +def user_tune(items, name, jid): + (user, resource) = gajim.get_room_and_nick_from_fjid(jid) + contact = gajim.contacts.get_contact(name, user, resource=resource) + if not contact: + return + for item in items.getTags('item'): + child = item.getTag('tune') + if child is not None: + if contact.tune.has_key('artist'): + del contact.tune['artist'] + if contact.tune.has_key('title'): + del contact.tune['title'] + if contact.tune.has_key('source'): + del contact.tune['source'] + if contact.tune.has_key('track'): + del contact.tune['track'] + if contact.tune.has_key('length'): + del contact.tune['length'] + for ch in child.getChildren(): + if ch.getName() == 'artist': + contact.tune['artist'] = ch.getData() + elif ch.getName() == 'title': + contact.tune['title'] = ch.getData() + elif ch.getName() == 'source': + contact.tune['source'] = ch.getData() + elif ch.getName() == 'track': + contact.tune['track'] = ch.getData() + elif ch.getName() == 'length': + contact.tune['length'] = ch.getData() + +def user_geoloc(items, name, jid): + pass + +def user_activity(items, name, jid): + (user, resource) = gajim.get_room_and_nick_from_fjid(jid) + contact = gajim.contacts.get_contact(name, user, resource=resource) + if not contact: + return + for item in items.getTags('item'): + child = item.getTag('activity') + if child is not None: + if contact.activity.has_key('activity'): + del contact.activity['activity'] + if contact.activity.has_key('subactivity'): + del contact.activity['subactivity'] + if contact.activity.has_key('text'): + del contact.activity['text'] + for ch in child.getChildren(): + if ch.getName() != 'text': + contact.activity['activity'] = ch.getName() + for chi in ch.getChildren(): + contact.activity['subactivity'] = chi.getName() + else: + contact.activity['text'] = ch.getData() + +def user_send_mood(account, mood, message = ''): + print "Sending %s: %s" % (mood, message) + if gajim.config.get('publish_mood') == False: + return + item = xmpp.Node('mood', {'xmlns': xmpp.NS_MOOD}) + if mood != '': + item.addChild(mood) + if message != '': + i = item.addChild('text') + i.addData(message) + + gajim.connections[account].send_pb_publish('', xmpp.NS_MOOD, item, '0') + +def user_send_activity(account, activity, subactivity = '', message = ''): + if gajim.config.get('publish_activity') == False: + return + item = xmpp.Node('activity', {'xmlns': xmpp.NS_ACTIVITY}) + if activity != '': + i = item.addChild(activity) + if subactivity != '': + i.addChild(subactivity) + if message != '': + i = item.addChild('text') + i.addData(message) + + gajim.connections[account].send_pb_publish('', xmpp.NS_ACTIVITY, item, '0') + +def user_send_tune(account, artist = '', title = '', source = '', track = 0,length = 0, items = None): + print "Tune to be created" + if (gajim.config.get('publish_tune') == False) or \ + (gajim.connections[account].pep_supported == False): + return + print "publish_tune == True and pep_supported" + item = xmpp.Node('tune', {'xmlns': xmpp.NS_TUNE}) + if artist != '': + i = item.addChild('artist') + i.addData(artist) + if title != '': + i = item.addChild('title') + i.addData(title) + if source != '': + i = item.addChild('source') + i.addData(source) + if track != 0: + i = item.addChild('track') + i.addData(track) + if length != 0: + i = item.addChild('length') + i.addData(length) + if items is not None: + item.addChild(payload=items) + + gajim.connections[account].send_pb_publish('', xmpp.NS_TUNE, item, '0') + print "Tune published" diff --git a/src/common/pubsub.py b/src/common/pubsub.py index 8a6da05cc..1cead4d4c 100644 --- a/src/common/pubsub.py +++ b/src/common/pubsub.py @@ -1,5 +1,7 @@ import xmpp import gajim +import dataforms +import connection_handlers class ConnectionPubSub: def __init__(self): @@ -43,9 +45,61 @@ class ConnectionPubSub: self.connection.send(query) + def send_pb_delete(self, jid, node): + '''Deletes node.''' + query = xmpp.Iq('set', to=jid) + d = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + d = d.addChild('delete', {'node': node}) + + self.connection.send(query) + + def send_pb_create(self, jid, node, configure = False, configure_form = None): + '''Creates new node.''' + query = xmpp.Iq('set', to=jid) + c = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + c = c.addChild('create', {'node': node}) + if configure: + conf = c.addChild('configure') + if configure_form is not None: + conf.addChild(node=configuration_form) + + self.connection.send(query) + + def send_pb_configure(self, jid, node, cb, *cbargs, **cbkwargs): + query = xmpp.Iq('set', to=jid) + c = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + c = c.addChild('configure', {'node': node}) + + id = self.connection.send(query) + + def on_configure(self, connection, query): + try: + filledform = cb(stanza['pubsub']['configure']['x'], *cbargs, **cbkwargs) + #TODO: Build a form + #TODO: Send it + + except CancelConfigure: + cancel = xmpp.Iq('set', to=jid) + ca = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + ca = ca.addChild('configure', {'node': node}) + #ca = ca.addChild('x', namespace=xmpp.NS_DATA, {'type': 'cancel'}) + + self.connection.send(cancel) + + self.__callbacks[id] = (on_configure, (), {}) + def _PubSubCB(self, conn, stanza): try: cb, args, kwargs = self.__callbacks.pop(stanza.getID()) cb(conn, stanza, *args, **kwargs) except: pass + + def request_pb_configuration(self, jid, node): + query = xmpp.Iq('get', to=jid) + e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB_OWNER) + e = e.addChild('configure', {'node': node}) + id = self.connection.getAnID() + query.setID(id) + self.awaiting_answers[id] = (connection_handlers.PEP_ACCESS_MODEL,) + self.connection.send(query) diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 2edb5be83..c09b9921f 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -27,11 +27,14 @@ NS_AGENTS ='jabber:iq:agents' NS_AMP ='http://jabber.org/protocol/amp' NS_AMP_ERRORS =NS_AMP+'#errors' NS_AUTH ='jabber:iq:auth' +NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata' NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind' NS_BROWSE ='jabber:iq:browse' -NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # XEP-0065 -NS_CAPS ='http://jabber.org/protocol/caps' # XEP-0115 -NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # XEP-0085 +NS_BROWSING ='http://jabber.org/protocol/browsing' # XEP-0195 +NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # JEP-0065 +NS_CAPS ='http://jabber.org/protocol/caps' # JEP-0115 +NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # JEP-0085 +NS_CHATTING ='http://jabber.org/protocol/chatting' # XEP-0194 NS_CLIENT ='jabber:client' NS_COMMANDS ='http://jabber.org/protocol/commands' NS_COMPONENT_ACCEPT='jabber:component:accept' @@ -48,8 +51,9 @@ NS_ENCRYPTED ='jabber:x:encrypted' # XEP-00 NS_ESESSION_INIT='http://www.xmpp.org/extensions/xep-0116.html#ns-init' # XEP-0116 NS_EVENT ='jabber:x:event' # XEP-0022 NS_FEATURE ='http://jabber.org/protocol/feature-neg' -NS_FILE ='http://jabber.org/protocol/si/profile/file-transfer' # XEP-0096 -NS_GEOLOC ='http://jabber.org/protocol/geoloc' # XEP-0080 +NS_FILE ='http://jabber.org/protocol/si/profile/file-transfer' # JEP-0096 +NS_GAMING ='http://jabber.org/protocol/gaming' # XEP-0196 +NS_GEOLOC ='http://jabber.org/protocol/geoloc' # JEP-0080 NS_GROUPCHAT ='gc-1.0' NS_HTTP_AUTH ='http://jabber.org/protocol/http-auth' # XEP-0070 NS_HTTP_BIND ='http://jabber.org/protocol/httpbind' # XEP-0124 @@ -72,6 +76,7 @@ NS_PRIVACY ='jabber:iq:privacy' NS_PRIVATE ='jabber:iq:private' NS_PROFILE ='http://jabber.org/protocol/profile' # XEP-0154 NS_PUBSUB ='http://jabber.org/protocol/pubsub' # XEP-0060 +NS_PUBSUB_OWNER ='http://jabber.org/protocol/pubsub#owner' # JEP-0060 NS_REGISTER ='jabber:iq:register' NS_ROSTER ='jabber:iq:roster' NS_ROSTERX ='http://jabber.org/protocol/rosterx' # XEP-0144 @@ -90,12 +95,14 @@ NS_STREAMS ='http://etherx.jabber.org/streams' NS_TIME ='jabber:iq:time' # XEP-0900 NS_TIME_REVISED ='urn:xmpp:time' # XEP-0202 NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls' +NS_TUNE ='http://jabber.org/protocol/tune' # XEP-0118 NS_VACATION ='http://jabber.org/protocol/vacation' NS_VCARD ='vcard-temp' NS_GMAILNOTIFY ='google:mail:notify' NS_GTALKSETTING ='google:setting' NS_VCARD_UPDATE =NS_VCARD+':x:update' NS_VERSION ='jabber:iq:version' +NS_VIEWING ='http://jabber.org/protocol/viewing' # XEP--197 NS_PING ='urn:xmpp:ping' # XEP-0199 NS_WAITINGLIST ='http://jabber.org/protocol/waitinglist' # XEP-0130 NS_XHTML_IM ='http://jabber.org/protocol/xhtml-im' # XEP-0071 diff --git a/src/config.py b/src/config.py index a47844400..e2a8999dc 100644 --- a/src/config.py +++ b/src/config.py @@ -49,6 +49,7 @@ from common import passwords from common import zeroconf from common import dbus_support from common import dataforms +from common import pep from common.exceptions import GajimGeneralException @@ -458,6 +459,25 @@ class PreferencesWindow: else: widget.set_sensitive(False) + # PEP + st = gajim.config.get('publish_mood') + self.xml.get_widget('publish_mood_checkbutton').set_active(st) + + st = gajim.config.get('publish_activity') + self.xml.get_widget('publish_activity_checkbutton').set_active(st) + + st = gajim.config.get('publish_tune') + self.xml.get_widget('publish_tune_checkbutton').set_active(st) + + st = gajim.config.get('subscribe_mood') + self.xml.get_widget('subscribe_mood_checkbutton').set_active(st) + + st = gajim.config.get('subscribe_activity') + self.xml.get_widget('subscribe_activity_checkbutton').set_active(st) + + st = gajim.config.get('subscribe_tune') + self.xml.get_widget('subscribe_tune_checkbutton').set_active(st) + # Notify user of new gmail e-mail messages, # only show checkbox if user has a gtalk account frame_gmail = self.xml.get_widget('frame_gmail') @@ -521,6 +541,8 @@ class PreferencesWindow: self.theme_preferences = None self.notebook.set_current_page(0) + if not gajim.config.get('use_pep'): + self.notebook.remove_page(5) self.window.show_all() gtkgui_helpers.possibly_move_window_in_current_desktop(self.window) @@ -536,6 +558,45 @@ class PreferencesWindow: w.set_sensitive(widget.get_active()) gajim.interface.save_config() + def on_publish_mood_checkbutton_toggled(self, widget): + if widget.get_active() == False: + for account in gajim.connections: + if gajim.connections[account].pep_supported: + pep.user_send_mood(account, '') + self.on_checkbutton_toggled(widget, 'publish_mood') + + def on_publish_activity_checkbutton_toggled(self, widget): + if widget.get_active() == False: + for account in gajim.connections: + if gajim.connections[account].pep_supported: + pep.user_send_activity(account, '') + self.on_checkbutton_toggled(widget, 'publish_activity') + + def on_publish_tune_checkbutton_toggled(self, widget): + if widget.get_active() == False: + for account in gajim.connections: + if gajim.connections[account].pep_supported: + pep.user_send_tune(account, '') + self.on_checkbutton_toggled(widget, 'publish_tune') + gajim.interface.roster.enable_syncing_status_msg_from_current_music_track( + widget.get_active()) + + def on_subscribe_mood_checkbutton_toggled(self, widget): + self.on_checkbutton_toggled(widget, 'subscribe_mood') + + def on_subscribe_activity_checkbutton_toggled(self, widget): + self.on_checkbutton_toggled(widget, 'subscribe_activity') + + def on_subscribe_tune_checkbutton_toggled(self, widget): + self.on_checkbutton_toggled(widget, 'subscribe_tune') + + def on_save_position_checkbutton_toggled(self, widget): + self.on_checkbutton_toggled(widget, 'saveposition') + + def on_sort_by_show_checkbutton_toggled(self, widget): + self.on_checkbutton_toggled(widget, 'sort_by_show') + gajim.interface.roster.draw_roster() + def on_show_avatars_in_roster_checkbutton_toggled(self, widget): self.on_checkbutton_toggled(widget, 'show_avatars_in_roster') gajim.interface.roster.draw_roster() @@ -3406,3 +3467,278 @@ class AccountCreationWizardWindow: gajim.interface.roster.draw_roster() gajim.interface.roster.set_actions_menu_needs_rebuild() gajim.interface.save_config() + +#---------- ZeroconfPropertiesWindow class -------------# +class ZeroconfPropertiesWindow: + def __init__(self): + self.xml = gtkgui_helpers.get_glade('zeroconf_properties_window.glade') + self.window = self.xml.get_widget('zeroconf_properties_window') + self.window.set_transient_for(gajim.interface.roster.window) + self.xml.signal_autoconnect(self) + + self.init_account() + self.init_account_gpg() + + self.xml.get_widget('save_button').grab_focus() + self.window.show_all() + + def init_account(self): + st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'autoconnect') + if st: + self.xml.get_widget('autoconnect_checkbutton').set_active(st) + + list_no_log_for = gajim.config.get_per('accounts', + gajim.ZEROCONF_ACC_NAME,'no_log_for').split() + if gajim.ZEROCONF_ACC_NAME in list_no_log_for: + self.xml.get_widget('log_history_checkbutton').set_active(0) + else: + self.xml.get_widget('log_history_checkbutton').set_active(1) + + + st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'sync_with_global_status') + if st: + self.xml.get_widget('sync_with_global_status_checkbutton').set_active( + st) + + for opt in ('first_name', 'last_name', 'jabber_id', 'email'): + st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_' + opt) + if st: + self.xml.get_widget(opt + '_entry').set_text(st) + + st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'custom_port') + if st: + self.xml.get_widget('custom_port_entry').set_text(str(st)) + + st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'use_custom_host') + if st: + self.xml.get_widget('custom_port_checkbutton').set_active(st) + + self.xml.get_widget('custom_port_entry').set_sensitive(bool(st)) + + if not st: + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'custom_port', '5298') + + def init_account_gpg(self): + keyid = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'keyid') + keyname = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'keyname') + savegpgpass = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'savegpgpass') + + if not keyid or not gajim.config.get('usegpg'): + return + + self.xml.get_widget('gpg_key_label').set_text(keyid) + self.xml.get_widget('gpg_name_label').set_text(keyname) + gpg_save_password_checkbutton = \ + self.xml.get_widget('gpg_save_password_checkbutton') + gpg_save_password_checkbutton.set_sensitive(True) + gpg_save_password_checkbutton.set_active(savegpgpass) + + if savegpgpass: + entry = self.xml.get_widget('gpg_password_entry') + entry.set_sensitive(True) + gpgpassword = gajim.config.get_per('accounts', + gajim.ZEROCONF_ACC_NAME, 'gpgpassword') + entry.set_text(gpgpassword) + + def on_zeroconf_properties_window_destroy(self, widget): + # close window + if gajim.interface.instances.has_key('zeroconf_properties'): + del gajim.interface.instances['zeroconf_properties'] + + def on_custom_port_checkbutton_toggled(self, widget): + st = self.xml.get_widget('custom_port_checkbutton').get_active() + self.xml.get_widget('custom_port_entry').set_sensitive(bool(st)) + + def on_cancel_button_clicked(self, widget): + self.window.destroy() + + def on_save_button_clicked(self, widget): + config = {} + + st = self.xml.get_widget('autoconnect_checkbutton').get_active() + config['autoconnect'] = st + list_no_log_for = gajim.config.get_per('accounts', + gajim.ZEROCONF_ACC_NAME, 'no_log_for').split() + if gajim.ZEROCONF_ACC_NAME in list_no_log_for: + list_no_log_for.remove(gajim.ZEROCONF_ACC_NAME) + if not self.xml.get_widget('log_history_checkbutton').get_active(): + list_no_log_for.append(gajim.ZEROCONF_ACC_NAME) + config['no_log_for'] = ' '.join(list_no_log_for) + + st = self.xml.get_widget('sync_with_global_status_checkbutton').\ + get_active() + config['sync_with_global_status'] = st + + st = self.xml.get_widget('first_name_entry').get_text() + config['zeroconf_first_name'] = st.decode('utf-8') + + st = self.xml.get_widget('last_name_entry').get_text() + config['zeroconf_last_name'] = st.decode('utf-8') + + st = self.xml.get_widget('jabber_id_entry').get_text() + config['zeroconf_jabber_id'] = st.decode('utf-8') + + st = self.xml.get_widget('email_entry').get_text() + config['zeroconf_email'] = st.decode('utf-8') + + use_custom_port = self.xml.get_widget('custom_port_checkbutton').\ + get_active() + config['use_custom_host'] = use_custom_port + + old_port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'custom_port') + if use_custom_port: + port = self.xml.get_widget('custom_port_entry').get_text() + else: + port = 5298 + + config['custom_port'] = port + + config['keyname'] = self.xml.get_widget('gpg_name_label').get_text().\ + decode('utf-8') + if config['keyname'] == '': # no key selected + config['keyid'] = '' + config['savegpgpass'] = False + config['gpgpassword'] = '' + else: + config['keyid'] = self.xml.get_widget('gpg_key_label').get_text().\ + decode('utf-8') + config['savegpgpass'] = self.xml.get_widget( + 'gpg_save_password_checkbutton').get_active() + config['gpgpassword'] = self.xml.get_widget('gpg_password_entry' + ).get_text().decode('utf-8') + + reconnect = False + for opt in ('zeroconf_first_name','zeroconf_last_name', + 'zeroconf_jabber_id', 'zeroconf_email', 'custom_port'): + if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, opt) != \ + config[opt]: + reconnect = True + + for opt in config: + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, opt, + config[opt]) + + if gajim.connections.has_key(gajim.ZEROCONF_ACC_NAME): + if port != old_port or reconnect: + gajim.connections[gajim.ZEROCONF_ACC_NAME].update_details() + + self.window.destroy() + + def on_gpg_choose_button_clicked(self, widget, data = None): + if gajim.connections.has_key(gajim.ZEROCONF_ACC_NAME): + secret_keys = gajim.connections[gajim.ZEROCONF_ACC_NAME].\ + ask_gpg_secrete_keys() + + # self.account is None and/or gajim.connections is {} + else: + from common import GnuPG + if GnuPG.USE_GPG: + secret_keys = GnuPG.GnuPG().get_secret_keys() + else: + secret_keys = [] + if not secret_keys: + dialogs.ErrorDialog(_('Failed to get secret keys'), + _('There was a problem retrieving your OpenPGP secret keys.')) + return + secret_keys[_('None')] = _('None') + instance = dialogs.ChooseGPGKeyDialog(_('OpenPGP Key Selection'), + _('Choose your OpenPGP key'), secret_keys) + keyID = instance.run() + if keyID is None: + return + checkbutton = self.xml.get_widget('gpg_save_password_checkbutton') + gpg_key_label = self.xml.get_widget('gpg_key_label') + gpg_name_label = self.xml.get_widget('gpg_name_label') + if keyID[0] == _('None'): + gpg_key_label.set_text(_('No key selected')) + gpg_name_label.set_text('') + checkbutton.set_sensitive(False) + self.xml.get_widget('gpg_password_entry').set_sensitive(False) + else: + gpg_key_label.set_text(keyID[0]) + gpg_name_label.set_text(keyID[1]) + checkbutton.set_sensitive(True) + checkbutton.set_active(False) + self.xml.get_widget('gpg_password_entry').set_text('') + + def on_gpg_save_password_checkbutton_toggled(self, widget): + st = widget.get_active() + w = self.xml.get_widget('gpg_password_entry') + w.set_sensitive(bool(st)) + +class ManagePEPServicesWindow: + def __init__(self, account): + self.xml = gtkgui_helpers.get_glade('manage_pep_services_window.glade') + self.window = self.xml.get_widget('manage_pep_services_window') + self.window.set_transient_for(gajim.interface.roster.window) + self.xml.signal_autoconnect(self) + self.account = account + + self.init_services() + self.window.show_all() + + def on_manage_pep_services_window_destroy(self, widget): + '''close window''' + del gajim.interface.instances[self.account]['pep_services'] + + def on_ok_button_clicked(self, widget): + pass + + def on_cancel_button_clicked(self, widget): + self.window.destroy() + + def cellrenderer_combo_edited(self, cellrenderer, path, new_text): + self.treestore[path][1] = new_text + + def init_services(self): + treeview = self.xml.get_widget('services_treeview') + # service, access_model, group + self.treestore = gtk.ListStore(str, str, str) + treeview.set_model(self.treestore) + + col = gtk.TreeViewColumn('Service') + treeview.append_column(col) + + cellrenderer_text = gtk.CellRendererText() + col.pack_start(cellrenderer_text) + col.add_attribute(cellrenderer_text, 'text', 0) + + col = gtk.TreeViewColumn('access model') + treeview.append_column(col) + + model = gtk.ListStore(str) + model.append(['open']) + model.append(['presence']) + model.append(['roster']) + model.append(['whitelist']) + cellrenderer_combo = gtk.CellRendererCombo() + cellrenderer_combo.set_property('text-column', 0) + cellrenderer_combo.set_property('model', model) + cellrenderer_combo.set_property('has-entry', False) + cellrenderer_combo.set_property('editable', True) + cellrenderer_combo.connect('edited', self.cellrenderer_combo_edited) + col.pack_start(cellrenderer_combo) + col.add_attribute(cellrenderer_combo, 'text', 1) + + our_jid = gajim.get_jid_from_account(self.account) + gajim.connections[self.account].discoverItems(our_jid) + + def items_received(self, items): + our_jid = gajim.get_jid_from_account(self.account) + for item in items: + if 'jid' in item and item['jid'] == our_jid and 'node' in item: + # ask to have access model + gajim.connections[self.account].request_pb_configuration( + item['jid'], item['node']) + + def new_service(self, node, model): + self.treestore.append([node, model, '']) diff --git a/src/dialogs.py b/src/dialogs.py index 28e77f9de..e50b3abbd 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -370,6 +370,125 @@ class ChooseGPGKeyDialog: self.keys_treeview.set_cursor(path) +class ChangeActivityDialog: + activities = [_('doing_chores'), _('drinking'), _('eating'), + _('excercising'), _('grooming'), _('having_appointment'), + _('inactive'), _('relaxing'), _('talking'), _('traveling'), + _('working'), ] + subactivities = [_('at_the_spa'), _('brushing_teeth'), + _('buying_groceries'), _('cleaning'), _('coding'), + _('commuting'), _('cooking'), _('cycling'), _('day_off'), + _('doing_maintenance'), _('doing_the_dishes'), + _('doing_the_laundry'), _('driving'), _('gaming'), + _('gardening'), _('getting_a_haircut'), _('going_out'), + _('hanging_out'), _('having_a_beer'), _('having_a_snack'), + _('having_breakfast'), _('having_coffee'), + _('having_dinner'), _('having_lunch'), _('having_tea'), + _('hiking'), _('in_a_car'), _('in_a_meeting'), + _('in_real_life'), _('jogging'), _('on_a_bus'), + _('on_a_plane'), _('on_a_train'), _('on_a_trip'), + _('on_the_phone'), _('on_vacation'), _('other'), + _('partying'), _('playing_sports'), _('reading'), + _('rehearsing'), _('running'), _('running_an_errand'), + _('scheduled_holiday'), _('shaving'), _('shopping'), + _('skiing'), _('sleeping'), _('socializing'), + _('studying'), _('sunbathing'), _('swimming'), + _('taking_a_bath'), _('taking_a_shower'), _('walking'), + _('walking_the_dog'), _('watching_tv'), + _('watching_a_movie'), _('working_out'), _('writing'), ] + def __init__(self, account): + self.account = account + self.xml = gtkgui_helpers.get_glade('change_activity_dialog.glade') + self.window = self.xml.get_widget('change_activity_dialog') + self.window.set_transient_for(gajim.interface.roster.window) + self.window.set_title(_('Activity')) + + self.entry = self.xml.get_widget('entry') + + self.combo1 = self.xml.get_widget('combobox1') + self.liststore1 = gtk.ListStore(str) + self.combo1.set_model(self.liststore1) + + for activity in self.activities: + self.liststore1.append((activity,)) + + cellrenderertext = gtk.CellRendererText() + self.combo1.pack_start(cellrenderertext, True) + self.combo1.add_attribute(cellrenderertext, 'text', 0) + + self.combo2 = self.xml.get_widget('combobox2') + self.liststore2 = gtk.ListStore(str) + self.combo2.set_model(self.liststore2) + + for subactivity in self.subactivities: + self.liststore2.append((subactivity,)) + + cellrenderertext = gtk.CellRendererText() + self.combo2.pack_start(cellrenderertext, True) + self.combo2.add_attribute(cellrenderertext, 'text', 0) + + self.xml.signal_autoconnect(self) + self.window.show_all() + + def on_ok_button_clicked(self, widget): + '''Return activity and messsage (None if no activity selected)''' + activity = None + subactivity = None + message = None + active1 = self.combo1.get_active() + active2 = self.combo2.get_active() + if active1 > -1: + activity = self.liststore1[active1][0].decode('utf-8') + if active2 > -1: + subactivity = self.liststore2[active2][0].decode('utf-8') + message = self.entry.get_text().decode('utf-8') + from common import pep + pep.user_send_activity(self.account, activity, + subactivity, message) + self.window.destroy() + + def on_cancel_button_clicked(self, widget): + self.window.destroy() + +class ChangeMoodDialog: + moods = [_('afraid'), _('amazed'), _('angry'), _('annoyed'), _('anxious'), _('aroused'), _('ashamed'), _('bored'), _('brave'), _('calm'), _('cold'), _('confused'), _('contented'), _('cranky'), _('curious'), _('depressed'), _('disappointed'), _('disgusted'), _('distracted'), _('embarrassed'), _('excited'), _('flirtatious'), _('frustrated'), _('grumpy'), _('guilty'), _('happy'), _('hot'), _('humbled'), _('humiliated'), _('hungry'), _('hurt'), _('impressed'), _('in_awe'), _('in_love'), _('indignant'), _('interested'), _('intoxicated'), _('invincible'), _('jealous'), _('lonely'), _('mean'), _('moody'), _('nervous'), _('neutral'), _('offended'), _('playful'), _('proud'), _('relieved'), _('remorseful'), _('restless'), _('sad'), _('sarcastic'), _('serious'), _('shocked'), _('shy'), _('sick'), _('sleepy'), _('stressed'), _('surprised'), _('thirsty'), _('worried')] + def __init__(self, account): + self.account = account + self.xml = gtkgui_helpers.get_glade('change_mood_dialog.glade') + self.window = self.xml.get_widget('change_mood_dialog') + self.window.set_transient_for(gajim.interface.roster.window) + self.window.set_title(_('Mood')) + + self.entry = self.xml.get_widget('entry') + + self.combo = self.xml.get_widget('combobox') + self.liststore = gtk.ListStore(str) + self.combo.set_model(self.liststore) + + for mood in self.moods: + self.liststore.append((mood,)) + + cellrenderertext = gtk.CellRendererText() + self.combo.pack_start(cellrenderertext, True) + self.combo.add_attribute(cellrenderertext, 'text', 0) + self.xml.signal_autoconnect(self) + self.window.show_all() + + def on_ok_button_clicked(self, widget): + '''Return mood and messsage (None if no mood selected)''' + mood = None + message = None + active = self.combo.get_active() + if active > -1: + mood = self.liststore[active][0].decode('utf-8') + message = self.entry.get_text().decode('utf-8') + from common import pep + pep.user_send_mood(self.account, mood, message) + self.window.destroy() + + def on_cancel_button_clicked(self, widget): + self.window.destroy() + class ChangeStatusMessageDialog: def __init__(self, show = None): self.show = show diff --git a/src/gajim.py b/src/gajim.py index 4453ab8be..33cd8a6b4 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -999,6 +999,11 @@ class Interface: def handle_event_agent_info_items(self, account, array): #('AGENT_INFO_ITEMS', account, (agent, node, items)) + our_jid = gajim.get_jid_from_account(account) + if gajim.interface.instances[account].has_key('pep_services') and \ + array[0] == our_jid: + gajim.interface.instances[account]['pep_services'].items_received( + array[2]) try: gajim.connections[account].services_cache.agent_items(array[0], array[1], array[2]) @@ -2169,6 +2174,11 @@ class Interface: _('You are already connected to this account with the same resource. Please type a new one'), input_str = gajim.connections[account].server_resource, is_modal = False, ok_handler = on_ok) + def handle_event_pep_access_model(self, account, data): + # ('PEP_ACCESS_MODEL', account, (node, model)) + if self.instances[account].has_key('pep_services'): + self.instances[account]['pep_services'].new_service(data[0], data[1]) + def handle_event_unique_room_id_supported(self, account, data): '''Receive confirmation that unique_room_id are supported''' # ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id) @@ -2551,6 +2561,7 @@ class Interface: 'SEARCH_FORM': self.handle_event_search_form, 'SEARCH_RESULT': self.handle_event_search_result, 'RESOURCE_CONFLICT': self.handle_event_resource_conflict, + 'PEP_ACCESS_MODEL': self.handle_event_pep_access_model, 'UNIQUE_ROOM_ID_UNSUPPORTED': \ self.handle_event_unique_room_id_unsupported, 'UNIQUE_ROOM_ID_SUPPORTED': self.handle_event_unique_room_id_supported, diff --git a/src/roster_window.py b/src/roster_window.py index da17f4c3b..e7334dae2 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -931,7 +931,11 @@ class RosterWindow: service_disco_menuitem = self.xml.get_widget('service_disco_menuitem') advanced_menuitem = self.xml.get_widget('advanced_menuitem') profile_avatar_menuitem = self.xml.get_widget('profile_avatar_menuitem') + pep_services_menuitem = self.xml.get_widget('pep_services_menuitem') + if not gajim.config.get('use_pep'): + pep_services_menuitem.set_no_show_all(True) + pep_services_menuitem.hide() # destroy old advanced menus for m in self.advanced_menus: m.destroy() @@ -954,6 +958,11 @@ class RosterWindow: self.new_chat_menuitem_handler_id) self.new_chat_menuitem_handler_id = None + if self.pep_services_menuitem_handler_id: + pep_services_menuitem.handler_disconnect( + self.pep_services_menuitem_handler_id) + self.pep_services_menuitem_handler_id = None + if self.single_message_menuitem_handler_id: single_message_menuitem.handler_disconnect( self.single_message_menuitem_handler_id) @@ -964,7 +973,6 @@ class RosterWindow: self.profile_avatar_menuitem_handler_id) self.profile_avatar_menuitem_handler_id = None - # remove the existing submenus add_new_contact_menuitem.remove_submenu() service_disco_menuitem.remove_submenu() @@ -973,6 +981,7 @@ class RosterWindow: new_chat_menuitem.remove_submenu() advanced_menuitem.remove_submenu() profile_avatar_menuitem.remove_submenu() + pep_services_menuitem.remove_submenu() # remove the existing accelerator if self.have_new_chat_accel: @@ -1131,7 +1140,21 @@ class RosterWindow: if len(connected_accounts_with_vcard) > 1: # 2 or more accounts? make submenus profile_avatar_sub_menu = gtk.Menu() + pep_services_sub_menu = gtk.Menu() for account in connected_accounts_with_vcard: + if gajim.connections[account].pep_supported: + # profile, avatar + profile_avatar_item = gtk.MenuItem(_('of account %s') % account, + False) + profile_avatar_sub_menu.append(profile_avatar_item) + profile_avatar_item.connect('activate', + self.on_profile_avatar_menuitem_activate, account) + # PEP services + pep_services_item = gtk.MenuItem(_('of account %s') % account, + False) + pep_services_sub_menu.append(pep_services_item) + pep_services_item.connect('activate', + self.on_pep_services_menuitem_activate, account) # profile, avatar profile_avatar_item = gtk.MenuItem(_('of account %s') % account, False) @@ -1140,18 +1163,27 @@ class RosterWindow: self.on_profile_avatar_menuitem_activate, account) profile_avatar_menuitem.set_submenu(profile_avatar_sub_menu) profile_avatar_sub_menu.show_all() + pep_services_menuitem.set_submenu(pep_services_sub_menu) + pep_services_sub_menu.show_all() elif len(connected_accounts_with_vcard) == 1: # user has only one account account = connected_accounts_with_vcard[0] # profile, avatar if not self.profile_avatar_menuitem_handler_id: self.profile_avatar_menuitem_handler_id = \ - profile_avatar_menuitem.connect('activate', self.\ - on_profile_avatar_menuitem_activate, account) + profile_avatar_menuitem.connect('activate', + self.on_profile_avatar_menuitem_activate, account) + # PEP services + if not self.pep_services_menuitem_handler_id: + self.pep_services_menuitem_handler_id = \ + pep_services_menuitem.connect('activate', + self.on_pep_services_menuitem_activate, account) if len(connected_accounts_with_vcard) == 0: profile_avatar_menuitem.set_sensitive(False) + pep_services_menuitem.set_sensitive(False) else: profile_avatar_menuitem.set_sensitive(True) + pep_services_menuitem.set_sensitive(True) # Advanced Actions if len(gajim.connections) == 0: # user has no accounts @@ -2945,6 +2977,12 @@ class RosterWindow: if url: helpers.launch_browser_mailer('url', url) + def on_change_activity_activate(self, widget, account): + dlg = dialogs.ChangeActivityDialog(account) + + def on_change_mood_activate(self, widget, account): + dlg = dialogs.ChangeMoodDialog(account) + def on_change_status_message_activate(self, widget, account): show = gajim.SHOW_LIST[gajim.connections[account].connected] dlg = dialogs.ChangeStatusMessageDialog(show) @@ -3010,6 +3048,23 @@ class RosterWindow: sub_menu.append(item) item.connect('activate', self.change_status, account, 'offline') + pep_menuitem = xml.get_widget('pep_menuitem') + if gajim.connections[account].pep_supported and gajim.config.get('use_pep'): + pep_submenu = gtk.Menu() + pep_menuitem.set_submenu(pep_submenu) + if gajim.config.get('publish_mood'): + item = gtk.MenuItem('Mood') + pep_submenu.append(item) + item.connect('activate', self.on_change_mood_activate, account) + if gajim.config.get('publish_activity'): + item = gtk.MenuItem('Activity') + pep_submenu.append(item) + item.connect('activate', self.on_change_activity_activate, + account) + else: + pep_menuitem.set_no_show_all(True) + pep_menuitem.hide() + if not gajim.connections[account].gmail_url: open_gmail_inbox_menuitem.set_no_show_all(True) open_gmail_inbox_menuitem.hide() @@ -3038,8 +3093,7 @@ class RosterWindow: # make some items insensitive if account is offline if gajim.connections[account].connected < 2: for widget in [add_contact_menuitem, service_discovery_menuitem, - join_group_chat_menuitem, - execute_command_menuitem, + join_group_chat_menuitem, execute_command_menuitem, pep_menuitem, start_chat_menuitem]: widget.set_sensitive(False) else: @@ -3663,14 +3717,14 @@ class RosterWindow: listener = MusicTrackListener.get() self._music_track_changed_signal = listener.connect( 'music-track-changed', self._music_track_changed) - track = listener.get_playing_track() - self._music_track_changed(listener, track) + track = listener.get_playing_track() + self._music_track_changed(listener, track) else: if self._music_track_changed_signal is not None: listener = MusicTrackListener.get() listener.disconnect(self._music_track_changed_signal) self._music_track_changed_signal = None - self._music_track_changed(None, None) + self._music_track_changed(None, None) ## enable setting status msg from a Last.fm account def enable_syncing_status_msg_from_lastfm(self, enabled): @@ -3718,7 +3772,43 @@ class RosterWindow: except Exception, e: pass - def _music_track_changed(self, unused_listener, music_track_info): + def _music_track_changed(self, unused_listener, music_track_info, + account=''): + if gajim.config.get('use_pep'): + from common import pep + if account == '': + accounts = gajim.connections.keys() + if music_track_info is None: + artist = '' + title = '' + source = '' + track = '' + length = '' + elif hasattr(music_track_info, 'paused') and \ + music_track_info.paused == 0: + artist = '' + title = '' + source = '' + track = '' + length = '' + else: + artist = music_track_info.artist + title = music_track_info.title + source = music_track_info.album + if account == '': + print "Multi accounts" + for account in accounts: + if not gajim.config.get_per('accounts', account, + 'sync_with_global_status'): + continue + if not gajim.connections[account].pep_supported: + continue + pep.user_send_tune(account, artist, title, source) + else: + print "Single account" + pep.user_send_tune(account, artist, title, source) + return + # No PEP accounts = gajim.connections.keys() if music_track_info is None: status_message = '' @@ -4002,6 +4092,13 @@ class RosterWindow: else: gajim.interface.instances['preferences'] = config.PreferencesWindow() + def on_pep_services_menuitem_activate(self, widget, account): + if gajim.interface.instances[account].has_key('pep_services'): + gajim.interface.instances[account]['pep_services'].window.present() + else: + gajim.interface.instances[account]['pep_services'] = \ + config.ManagePEPServicesWindow(account) + def on_add_new_contact(self, widget, account): dialogs.AddNewContactWindow(account) @@ -5249,6 +5346,7 @@ class RosterWindow: self.new_chat_menuitem_handler_id = False self.single_message_menuitem_handler_id = False self.profile_avatar_menuitem_handler_id = False + self.pep_services_menuitem_handler_id = False self.actions_menu_needs_rebuild = True self.regroup = gajim.config.get('mergeaccounts') self.clicked_path = None # Used remember on wich row we clicked @@ -5414,18 +5512,20 @@ class RosterWindow: self.tooltip = tooltips.RosterTooltip() self.draw_roster() - ## Music Track notifications - ## FIXME: we use a timeout because changing status of - ## accounts has no effect until they are connected. - st = gajim.config.get('set_status_msg_from_current_music_track') - if st: - gobject.timeout_add(1000, - self.enable_syncing_status_msg_from_current_music_track, - st) + if gajim.config.get('use_pep'): + self.enable_syncing_status_msg_from_current_music_track(gajim.config.get('publish_tune')) else: - gobject.timeout_add(1000, - self.enable_syncing_status_msg_from_lastfm, - gajim.config.get('set_status_msg_from_lastfm')) + ## Music Track notifications + ## FIXME: we use a timeout because changing status of + ## accounts has no effect until they are connected. + st = gajim.config.get('set_status_msg_from_current_music_track') + if st: + gobject.timeout_add(1000, + self.enable_syncing_status_msg_from_current_music_track, st) + else: + gobject.timeout_add(1000, + self.enable_syncing_status_msg_from_lastfm, + gajim.config.get('set_status_msg_from_lastfm')) if gajim.config.get('show_roster_on_startup'): self.window.show_all() diff --git a/src/tooltips.py b/src/tooltips.py index e07ae4675..ecc8741b8 100644 --- a/src/tooltips.py +++ b/src/tooltips.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- ## tooltips.py ## ## Copyright (C) 2005-2006 Dimitur Kirov @@ -463,6 +464,10 @@ class RosterTooltip(NotificationAreaTooltip): contact.last_status_time) properties.append((self.table, None)) else: # only one resource + + #FIXME: User {Mood, Activity, Tune} not shown if there are + #multiple resources + #FIXME: User {Mood, Activity, Tune} not shown for self if contact.show: show = helpers.get_uf_show(contact.show) if contact.last_status_time: @@ -494,6 +499,42 @@ class RosterTooltip(NotificationAreaTooltip): show = '' + show + '' # we append show below + if contact.mood.has_key('mood'): + mood_string = 'Mood: %s' % contact.mood['mood'].strip() + if contact.mood.has_key('text') and contact.mood['text'] != '': + mood_string += ' (%s)' % contact.mood['text'].strip() + properties.append((mood_string, None)) + + if contact.activity.has_key('activity'): + activity = contact.activity['activity'].strip() + activity_string = 'Activity: %s' % activity + if contact.activity.has_key('subactivity'): + activity_sub = contact.activity['subactivity'].strip() + activity_string += ' (%s)' % activity_sub + else: + activity_string += '' + if contact.activity.has_key('text'): + activity_text = contact.activity['text'].strip() + activity_string += ' (%s)' % activity_text + properties.append((activity_string, None)) + + if contact.tune.has_key('artist') or contact.tune.has_key('title'): + if contact.tune.has_key('artist'): + artist = contact.tune['artist'].strip() + else: + artist = _('Unknown Artist') + if contact.tune.has_key('title'): + title = contact.tune['title'].strip() + else: + title = _('Unknown Title') + if contact.tune.has_key('source'): + source = contact.tune['source'].strip() + else: + source = _('Unknown Source') + tune_string = '♪ ' + _('"%(title)s" by %(artist)s\nfrom %(source)s' %\ + {'title': title, 'artist': artist, 'source': source}) + ' ♪' + properties.append((tune_string, None)) + if contact.status: status = contact.status.strip() if status: